feat: login

This commit is contained in:
wangdan-fit2cloud 2025-04-25 16:27:13 +08:00
parent b1d13f501f
commit f805f6dce1
53 changed files with 1169 additions and 2408 deletions

6
package-lock.json generated
View File

@ -1,6 +0,0 @@
{
"name": "MaxKB",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

View File

@ -13,7 +13,9 @@
"format": "prettier --write src/"
},
"dependencies": {
"axios": "^1.8.4",
"element-plus": "^2.9.7",
"nprogress": "^0.2.0",
"pinia": "^3.0.1",
"vue": "^3.5.13",
"vue-i18n": "^11.1.3",
@ -22,6 +24,7 @@
"devDependencies": {
"@tsconfig/node22": "^22.0.1",
"@types/node": "^22.14.0",
"@types/nprogress": "^0.2.3",
"@vitejs/plugin-vue": "^5.2.3",
"@vitejs/plugin-vue-jsx": "^4.1.2",
"@vue/eslint-config-prettier": "^10.2.0",

View File

@ -7,9 +7,9 @@ interface LoginRequest {
*
*/
password: string
/**
/**
*
*/
code: string
captcha: string
}
export type { LoginRequest }

121
ui/src/api/type/user.ts Normal file
View File

@ -0,0 +1,121 @@
interface User {
/**
* id
*/
id: string
/**
*
*/
username: string
/**
*
*/
email: string
/**
*
*/
role: string
/**
*
*/
permissions: Array<string>
/**
*
*/
is_edit_password?: boolean
IS_XPACK?: boolean
XPACK_LICENSE_IS_VALID?: boolean
language: string
}
interface LoginRequest {
/**
*
*/
username: string
/**
*
*/
password: string
}
interface RegisterRequest {
/**
*
*/
username: string
/**
*
*/
password: string
/**
*
*/
re_password: string
/**
*
*/
email: string
/**
*
*/
code: string
}
interface CheckCodeRequest {
/**
*
*/
email: string
/**
*
*/
code: string
/**
*
*/
type: 'register' | 'reset_password'
}
interface ResetCurrentUserPasswordRequest {
/**
*
*/
code: string
/**
*
*/
password: string
/**
*
*/
re_password: string
}
interface ResetPasswordRequest {
/**
*
*/
email?: string
/**
*
*/
code?: string
/**
*
*/
password: string
/**
*
*/
re_password: string
}
export type {
LoginRequest,
RegisterRequest,
CheckCodeRequest,
ResetPasswordRequest,
User,
ResetCurrentUserPasswordRequest
}

View File

@ -0,0 +1,30 @@
import { Result } from '@/request/Result'
import { get, post } from '@/request/index'
import type { LoginRequest } from '@/api/type/login'
import type { Ref } from 'vue'
/**
*
* @param request
* @param loading
* @returns
*/
const login: (request: LoginRequest, loading?: Ref<boolean>) => Promise<Result<string>> = (
request,
loading,
) => {
return post('/user/login', request, undefined, loading)
}
/**
*
* @param loading
*/
const getCaptcha: (loading?: Ref<boolean>) => Promise<Result<string>> = (loading) => {
return get('/user/captcha', undefined, loading)
}
export default {
login,
getCaptcha,
}

24
ui/src/api/user/user.ts Normal file
View File

@ -0,0 +1,24 @@
import { Result } from '@/request/Result'
import { get, post } from '@/request/index'
import type { User } from '@/api/type/user'
import type { Ref } from 'vue'
/**
*
* @param loading
* @returns
*/
const getUserProfile: (loading?: Ref<boolean>) => Promise<Result<User>> = (loading) => {
return get('/user/profile', undefined, loading)
}
/**
* profile
*/
// const getProfile: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {
// return get('/profile', undefined, loading)
// }
export default {
getUserProfile,
}

View File

@ -2,6 +2,7 @@ import en from 'element-plus/es/locale/lang/en'
import components from './components'
import layout from './layout'
import views from './views'
import theme from './theme'
import common from './common'
import dynamicsForm from './dynamics-form'
import chat from './ai-chat'
@ -9,6 +10,7 @@ export default {
lang: 'English',
layout,
views,
theme,
components,
en,
common,

View File

@ -0,0 +1,43 @@
export default {
title: 'Appearance Settings',
defaultSlogan: 'Ready-to-use, flexible RAG Chatbot',
platformDisplayTheme: 'Platform Display Theme',
customTheme: 'Custom Theme',
platformLoginSettings: 'Platform Login Settings',
custom: 'Custom',
pagePreview: 'Page Preview',
default: 'Default',
restoreDefaults: 'Restore Defaults',
orange: 'Orange',
green: 'Green',
purple: 'Purple',
red: 'Red',
loginBackground: 'Login Background Image',
loginLogo: 'Login Logo',
websiteLogo: 'Website Logo',
replacePicture: 'Replace Image',
websiteLogoTip:
'Logo displayed at the top of the website. Recommended size: 48x48. Supports JPG, PNG, GIF. Maximum size: 10MB',
loginLogoTip:
'Logo on the right side of the login page. Recommended size: 204x52. Supports JPG, PNG, GIF. Maximum size: 10MB',
loginBackgroundTip:
'Left-side background image. Vector graphics recommended size: 576x900; Bitmap recommended size: 1152x1800. Supports JPG, PNG, GIF. Maximum size: 10MB',
websiteName: 'Website Name',
websiteNamePlaceholder: 'Please enter the website name',
websiteNameTip: 'The platform name displayed in the web page tab',
websiteSlogan: 'Welcome Slogan',
websiteSloganPlaceholder: 'Please enter the welcome slogan',
websiteSloganTip: 'The welcome slogan below the product logo',
defaultTip: 'The default is the MaxKB platform interface, supports custom settings',
logoDefaultTip: 'The default is the MaxKB login interface, supports custom settings',
platformSetting: 'Platform Settings',
showUserManual: 'Show User Manual',
showForum: 'Show Forum Support',
showProject: 'Show Project Address',
urlPlaceholder: 'Please enter the URL address',
abandonUpdate: 'Abandon Update',
saveAndApply: 'Save and Apply',
fileMessageError: 'File size exceeds 10MB',
saveSuccess: 'Appearance settings successfully applied',
}

View File

@ -1,5 +1,25 @@
export default {
title: 'Login',
loginForm: {
username: {
label: 'Username',
placeholder: 'Please enter username',
requiredMessage: 'Please enter username',
lengthMessage: 'Length must be between 6 and 20 words',
},
password: {
label: 'Login Password',
placeholder: 'Please enter password',
requiredMessage: 'Please enter password',
lengthMessage: 'Length must be between 6 and 20 words',
},
captcha: {
label: 'Verification Code',
placeholder: 'Please enter verification code',
requiredMessage: 'Please enter verification code',
validatorMessage: 'Verification code is incorrect',
},
},
jump_tip: 'You will be redirected to the authentication source page for authentication',
jump: 'Redirect',
resetPassword: 'Change Password',
@ -9,7 +29,7 @@ export default {
login: 'Login',
register: 'Register',
backLogin: 'Back to Login',
checkCode: 'Verify Now'
checkCode: 'Verify Now',
},
newPassword: 'New Password',
enterPassword: 'Please enter your new password',
@ -19,6 +39,6 @@ export default {
placeholder: 'Please enter the verification code',
getVerificationCode: 'Get Verification Code',
successMessage: 'Verification code sent successfully',
resend: 'Resend'
}
resend: 'Resend',
},
}

View File

@ -93,48 +93,6 @@ export default {
access: 'Access'
}
},
theme: {
title: 'Appearance Settings',
platformDisplayTheme: 'Platform Display Theme',
customTheme: 'Custom Theme',
platformLoginSettings: 'Platform Login Settings',
custom: 'Custom',
pagePreview: 'Page Preview',
default: 'Default',
restoreDefaults: 'Restore Defaults',
orange: 'Orange',
green: 'Green',
purple: 'Purple',
red: 'Red',
loginBackground: 'Login Background Image',
loginLogo: 'Login Logo',
websiteLogo: 'Website Logo',
replacePicture: 'Replace Image',
websiteLogoTip:
'Logo displayed at the top of the website. Recommended size: 48x48. Supports JPG, PNG, GIF. Maximum size: 10MB',
loginLogoTip:
'Logo on the right side of the login page. Recommended size: 204x52. Supports JPG, PNG, GIF. Maximum size: 10MB',
loginBackgroundTip:
'Left-side background image. Vector graphics recommended size: 576x900; Bitmap recommended size: 1152x1800. Supports JPG, PNG, GIF. Maximum size: 10MB',
websiteName: 'Website Name',
websiteNamePlaceholder: 'Please enter the website name',
websiteNameTip: 'The platform name displayed in the web page tab',
websiteSlogan: 'Welcome Slogan',
websiteSloganPlaceholder: 'Please enter the welcome slogan',
websiteSloganTip: 'The welcome slogan below the product logo',
defaultSlogan: 'Ready-to-use, flexible RAG Chatbot',
defaultTip: 'The default is the MaxKB platform interface, supports custom settings',
logoDefaultTip: 'The default is the MaxKB login interface, supports custom settings',
platformSetting: 'Platform Settings',
showUserManual: 'Show User Manual',
showForum: 'Show Forum Support',
showProject: 'Show Project Address',
urlPlaceholder: 'Please enter the URL address',
abandonUpdate: 'Abandon Update',
saveAndApply: 'Save and Apply',
fileMessageError: 'File size exceeds 10MB',
saveSuccess: 'Appearance settings successfully applied'
},
email: {
title: 'Email Settings',
smtpHost: 'SMTP Host',

View File

@ -3,68 +3,57 @@ export default {
createUser: 'Create User',
editUser: 'Edit User',
setting: {
updatePwd: 'Change Password'
updatePwd: 'Change Password',
},
tip: {
professionalMessage:
'The community edition supports up to 2 users. For more users, please upgrade to the professional edition.',
updatePwdSuccess: 'User password updated successfully'
updatePwdSuccess: 'User password updated successfully',
},
delete: {
confirmTitle: 'Confirm deletion of user:',
confirmMessage:
'Deleting this user will also delete all resources (APP, knowledge, models) created by this user. Please proceed with caution.'
'Deleting this user will also delete all resources (APP, knowledge, models) created by this user. Please proceed with caution.',
},
disabled: {
confirmTitle: 'Confirm disable function:',
confirmMessage:
'Disabling this function will cause errors when APP that reference it are queried. Please proceed with caution.'
'Disabling this function will cause errors when APP that reference it are queried. Please proceed with caution.',
},
userForm: {
form: {
username: {
label: 'Username',
placeholder: 'Please enter username',
requiredMessage: 'Please enter username',
lengthMessage: 'Length must be between 6 and 20 words'
},
nick_name: {
label: 'Name',
placeholder: 'Please enter name'
placeholder: 'Please enter name',
},
email: {
label: 'Email',
placeholder: 'Please enter email',
requiredMessage: 'Please enter email'
requiredMessage: 'Please enter email',
},
phone: {
label: 'Phone',
placeholder: 'Please enter phone'
},
password: {
label: 'Login Password',
placeholder: 'Please enter password',
requiredMessage: 'Please enter password',
lengthMessage: 'Length must be between 6 and 20 words'
placeholder: 'Please enter phone',
},
new_password: {
label: 'New Password',
placeholder: 'Please enter new password',
requiredMessage: 'Please enter new password'
requiredMessage: 'Please enter new password',
},
re_password: {
label: 'Confirm Password',
placeholder: 'Please enter confirm password',
requiredMessage: 'Please enter confirm password',
validatorMessage: 'Passwords do not match'
}
}
validatorMessage: 'Passwords do not match',
},
},
},
source: {
label: 'User Type',
local: 'System User',
wecom: 'WeCom',
lark: 'Lark',
dingtalk: 'DingTalk'
}
dingtalk: 'DingTalk',
},
}

View File

@ -1,96 +0,0 @@
export default {
noHistory: '暂无历史记录',
createChat: '新建对话',
history: '历史记录',
only20history: '仅显示最近 20 条对话',
question_count: '条提问',
exportRecords: '导出聊天记录',
chatId: '对话 ID',
userInput: '用户输入',
quote: '引用',
download: '点击下载文件',
passwordValidator: {
title: '请输入密码打开链接',
errorMessage1: '密码不能为空',
errorMessage2: '密码错误'
},
operation: {
play: '点击播放',
pause: '停止',
regeneration: '换个答案',
like: '赞同',
cancelLike: '取消赞同',
oppose: '反对',
cancelOppose: '取消反对',
continue: '继续',
stopChat: '停止回答',
startChat: '开始回答',
},
tip: {
error500Message: '抱歉,当前正在维护,无法提供服务,请稍后再试!',
errorIdentifyMessage: '无法识别用户身份',
errorLimitMessage: '抱歉,您的提问已达到最大限制,请明天再来吧!',
answerMessage: '抱歉,没有查找到相关内容,请重新描述您的问题或提供更多信息。',
stopAnswer: '已停止回答',
answerLoading: '回答中',
recorderTip: `<p>该功能需要使用麦克风,浏览器禁止不安全页面录音,解决方案如下:<br/>
1 https <br/>
2 https Chrome <br/>
(1) chrome://flags/#unsafely-treat-insecure-origin-as-secure<br/>
(2) http 例如: http://127.0.0.1:8080。</p>`,
recorderError: '录音失败',
confirm: '我知道了',
requiredMessage: '请填写所有必填字段',
inputParamMessage1: '请在URL中填写参数',
inputParamMessage2: '的值',
prologueMessage: '抱歉,当前正在维护,无法提供服务,请稍后再试!'
},
inputPlaceholder: {
speaking: '说话中',
recorderLoading: '转文字中',
default: '请输入问题'
},
uploadFile: {
label: '上传文件',
most: '最多',
limit: '个,每个文件限制',
fileType: '文件类型',
tipMessage: '请在文件上传配置中选择文件类型',
limitMessage1: '最多上传',
limitMessage2: '个文件',
sizeLimit: '单个文件大小不能超过',
imageMessage: '请解析图片内容',
errorMessage: '上传失败'
},
executionDetails: {
title: '执行详情',
paramOutputTooltip: '每个文档仅支持预览500字',
audioFile: '语音文件',
searchContent: '检索内容',
searchResult: '检索结果',
conditionResult: '判断结果',
currentChat: '本次对话',
answer: 'AI 回答',
replyContent: '回复内容',
textContent: '文本内容',
input: '输入',
output: '输出',
rerankerContent: '重排内容',
rerankerResult: '重排结果',
paragraph: '分段',
noSubmit: '用户未提交',
errMessage: '错误日志'
},
KnowledgeSource: {
title: '知识来源',
referenceParagraph: '引用分段',
consume: '消耗tokens',
consumeTime: '耗时'
},
paragraphSource: {
title: '知识库引用',
question: '用户问题',
optimizationQuestion: '优化后问题'
},
editTitle: '编辑标题',
}

View File

@ -1,63 +0,0 @@
export default {
create: '创建',
createSuccess: '创建成功',
copy: '复制',
copySuccess: '复制成功',
copyError: '复制失败',
save: '保存',
saveSuccess: '保存成功',
delete: '删除',
deleteSuccess: '删除成功',
setting: '设置',
settingSuccess: '设置成功',
submit: '提交',
submitSuccess: '提交成功',
edit: '编辑',
editSuccess: '编辑成功',
modify: '修改',
modifySuccess: '修改成功',
cancel: '取消',
confirm: '确定',
tip: '提示',
add: '添加',
refresh: '刷新',
search: '搜索',
clear: '清空',
professional: '购买专业版',
createDate: '创建日期',
createTime: '创建时间',
operation: '操作',
character: '字符',
export: '导出',
exportSuccess: '导出成功',
unavailable: '(不可用)',
public: '公有',
private: '私有',
paramSetting: '参数设置',
creator: '创建者',
author: '作者',
debug: '调试',
required: '必填',
noData: '暂无数据',
result: '结果',
fileUpload: {
document: '文档',
image: '图片',
audio: '音频',
video: '视频'
},
status: {
label: '状态',
enableSuccess: '启用成功',
disableSuccess: '禁用成功'
},
inputPlaceholder: '请输入',
title: '标题',
content: '内容',
param: {
outputParam: '输出参数',
inputParam: '输入参数',
initParam: '启动参数',
},
rename:'重命名'
}

View File

@ -1,12 +0,0 @@
export default {
quickCreatePlaceholder: '快速创建空白文档',
quickCreateName: '文档名称',
noData: '无匹配数据',
loading: '加载中',
noMore: '到底啦!',
selectParagraph: {
title: '选择分段',
error: '仅执行未成功分段',
all: '全部分段'
}
}

View File

@ -1,102 +0,0 @@
export default {
input_type_list: {
TextInput: '文本框',
PasswordInput: '密码框',
Slider: '滑块',
SwitchInput: '开关',
SingleSelect: '单选框',
MultiSelect: '多选框',
DatePicker: '日期',
JsonInput: 'JSON文本框',
RadioCard: '选项卡',
RadioRow: '单行选项卡'
},
default: {
label: '默认值',
placeholder: '请输入默认值',
requiredMessage: '为必填属性',
show: '显示默认值'
},
tip: {
requiredMessage: '不能为空',
jsonMessage: 'JSON格式不正确'
},
searchBar: {
placeholder: '请输入关键字搜索'
},
paramForm: {
field: {
label: '参数',
placeholder: '请输入参数',
requiredMessage: '参数 为必填属性',
requiredMessage2: '只能输入字母数字和下划线'
},
name: {
label: '显示名称',
placeholder: '请输入显示名称',
requiredMessage: '显示名称 为必填属性'
},
tooltip: {
label: '参数提示说明',
placeholder: '请输入参数提示说明'
},
required: {
label: '是否必填',
requiredMessage: '是否必填 为必填属性'
},
input_type: {
label: '组件类型',
placeholder: '请选择组件类型',
requiredMessage: '组建类型 为必填属性'
}
},
DatePicker: {
placeholder: '选择日期',
year: '年',
month: '月',
date: '日期',
datetime: '日期时间',
dataType: {
label: '时间类型',
placeholder: '请选择时间类型'
},
format: {
label: '格式',
placeholder: '请选择格式'
}
},
Select: {
label: '选项值',
placeholder: '请输入选项值'
},
tag: {
label: '标签',
placeholder: '请输入选项标签'
},
Slider: {
showInput: {
label: '是否带输入框'
},
valueRange: {
label: '取值范围',
minRequired: '最小值必填',
maxRequired: '最大值必填'
},
step: {
label: '步长值',
requiredMessage1: '步长值必填',
requiredMessage2: '步长不能为0'
}
},
TextInput: {
length: {
label: '文本长度',
minRequired: '最小长度必填',
maxRequired: '最大长度必填',
requiredMessage1: '长度在',
requiredMessage2: '到',
requiredMessage3: '个字符',
requiredMessage4: '文本长度为必填参数'
}
}
}

View File

@ -1,17 +1,13 @@
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import components from './components'
import layout from './layout'
// import components from './components'
import views from './views'
import common from './common'
import dynamicsForm from './dynamics-form'
import chat from './ai-chat'
import theme from './theme'
// import common from './common'
// import dynamicsForm from './dynamics-form'
// import chat from './ai-chat'
export default {
lang: '简体中文',
layout,
views,
components,
zhCn,
common,
dynamicsForm,
chat
views,
theme
}

View File

@ -1,33 +0,0 @@
export default {
github: '项目地址',
wiki: '用户手册',
forum: '论坛求助',
logout: '退出',
apiKey: 'API Key 管理',
apiServiceAddress: 'API 服务地址',
language: '语言',
isExpire: '未上传 License 或 License 已过期。',
about: {
title: '关于',
expiredTime: '到期时间',
edition: {
label: '版本',
community: '社区版',
professional: '专业版'
},
version: '版本号',
serialNo: '序列号',
remark: '备注',
update: '更新',
authorize: '授权给'
},
time: {
daysLater: '天后',
hoursLater: '小时后',
expired: '已过期',
expiringSoon: '即将到期'
},
copyright: '版权所有 © 2014-2025 杭州飞致云信息科技有限公司',
userManualUrl: 'https://maxkb.cn/docs/',
forumUrl: 'https://bbs.fit2cloud.com/c/mk/11'
}

View File

@ -0,0 +1,40 @@
export default {
title: '外观设置',
defaultSlogan: '欢迎使用 MaxKB 开源 AI 助手',
platformDisplayTheme: '平台显示主题',
customTheme: '自定义主题',
platformLoginSettings: '平台登录设置',
custom: '自定义',
pagePreview: '页面预览',
default: '默认',
restoreDefaults: '恢复默认',
orange: '活力橙',
green: '松石绿',
purple: '神秘紫',
red: '胭脂红',
loginBackground: '登录背景图',
loginLogo: '登录 Logo',
websiteLogo: '网站 Logo',
replacePicture: '替换图片',
websiteLogoTip: '顶部网站显示的 Logo建议尺寸 48*48支持 JPG、PNG、GIF大小不超过 10MB',
loginLogoTip: '登录页面右侧 Logo建议尺寸 204*52支持 JPG、PNG、GIF大小不超过 10 MB',
loginBackgroundTip:
'左侧背景图,矢量图建议尺寸 576*900位图建议尺寸 1152*1800支持 JPG、PNG、GIF大小不超过 10 MB',
websiteName: '网站名称',
websiteNamePlaceholder: '请输入网站名称',
websiteNameTip: '显示在网页 Tab 的平台名称',
websiteSlogan: '欢迎语',
websiteSloganPlaceholder: '请输入欢迎语',
websiteSloganTip: '产品 Logo 下的欢迎语',
logoDefaultTip: '默认为 MaxKB 登录界面,支持自定义设置',
defaultTip: '默认为 MaxKB 平台界面,支持自定义设置',
platformSetting: '平台设置',
showUserManual: '显示用户手册',
showForum: '显示论坛求助',
showProject: '显示项目地址',
urlPlaceholder: '请输入 URL 地址',
abandonUpdate: '放弃更新',
saveAndApply: '保存并应用',
fileMessageError: '文件大小超过 10M',
saveSuccess: '外观设置成功',
}

View File

@ -1,5 +0,0 @@
export default {
title: "404",
message: "无法访问应用",
operate: "返回首页",
};

View File

@ -1,112 +0,0 @@
export default {
title: '概览',
appInfo: {
header: '应用信息',
publicAccessLink: '公开访问链接',
openText: '开',
closeText: '关',
copyLinkText: '复制链接',
refreshLinkText: '刷新链接',
demo: '演示',
embedInWebsite: '嵌入第三方',
accessControl: '访问限制',
displaySetting: '显示设置',
apiAccessCredentials: 'API 访问凭据',
apiKey: 'API Key',
refreshToken: {
msgConfirm1: '是否重新生成公开访问链接?',
msgConfirm2:
'重新生成公开访问链接会影响嵌入第三方脚本变更,需要将新脚本重新嵌入第三方,请谨慎操作!',
refreshSuccess: '刷新成功'
},
APIKeyDialog: {
saveSettings: '保存设置',
msgConfirm1: '是否删除API Key',
msgConfirm2: '删除API Key后将无法恢复请确认是否删除',
enabledSuccess: '已启用',
disabledSuccess: '已禁用'
},
EditAvatarDialog: {
title: '应用头像',
customizeUpload: '自定义上传',
upload: '上传',
default: '默认logo',
custom: '自定义',
sizeTip: '建议尺寸 32*32支持 JPG、PNG、GIF大小不超过 10 MB',
fileSizeExceeded: '文件大小超过 10 MB',
uploadImagePrompt: '请上传一张图片'
},
EmbedDialog: {
fullscreenModeTitle: '全屏模式',
copyInstructions: '复制以下代码进行嵌入',
floatingModeTitle: '浮窗模式',
mobileModeTitle: '移动端模式'
},
LimitDialog: {
showSourceLabel: '显示知识来源',
clientQueryLimitLabel: '每个客户端提问限制',
timesDays: '次/天',
authentication: '身份验证',
authenticationValue: '验证密码',
whitelistLabel: '白名单',
whitelistPlaceholder:
'请输入允许嵌入第三方的源地址,一行一个,如:\nhttp://127.0.0.1:5678\nhttps://dataease.io'
},
SettingAPIKeyDialog: {
dialogTitle: '设置',
allowCrossDomainLabel: '允许跨域地址',
crossDomainPlaceholder:
'请输入允许的跨域地址,开启后不输入跨域地址则不限制。\n跨域地址一行一个\nhttp://127.0.0.1:5678 \nhttps://dataease.io'
},
SettingDisplayDialog: {
dialogTitle: '显示设置',
languageLabel: '语言',
showSourceLabel: '显示知识来源',
showExecutionDetail: '显示执行详情',
restoreDefault: '恢复默认',
customThemeColor: '自定义主题色',
headerTitleFontColor: '头部标题字体颜色',
default: '默认',
askUserAvatar: '提问用户头像',
replace: '替换',
imageMessage: '建议尺寸 32*32支持 JPG、PNG、GIF大小不超过 10 MB',
AIAvatar: 'AI 回复头像',
floatIcon: '浮窗入口图标',
iconDefaultPosition: '图标默认位置',
iconPosition: {
left: '左',
right: '右',
bottom: '下',
top: '上'
},
draggablePosition: '可拖拽位置',
showHistory: '显示历史记录',
displayGuide: '显示引导图(浮窗模式)',
disclaimer: '免责声明',
disclaimerValue: '「以上内容均由 AI 生成,仅供参考和借鉴」'
}
},
monitor: {
monitoringStatistics: '监控统计',
customRange: '自定义范围',
startDatePlaceholder: '开始时间',
endDatePlaceholder: '结束时间',
pastDayOptions: {
past7Days: '过去7天',
past30Days: '过去30天',
past90Days: '过去90天',
past183Days: '过去半年',
other: '自定义'
},
charts: {
customerTotal: '用户总数',
customerNew: '用户新增数',
queryCount: '提问次数',
tokensTotal: 'Tokens 总数',
userSatisfaction: '用户满意度',
approval: '赞同',
disapproval: '反对'
}
}
}

View File

@ -1,287 +0,0 @@
export default {
node: '节点',
nodeName: '节点名称',
baseComponent: '基础组件',
nodeSetting: '节点设置',
workflow: '工作流',
searchBar: {
placeholder: '按名称搜索'
},
info: {
previewVersion: '预览版本:',
saveTime: '保存时间:'
},
setting: {
restoreVersion: '恢复版本',
restoreCurrentVersion: '恢复此版本',
addComponent: '添加组件',
public: '发布',
releaseHistory: '发布历史',
autoSave: '自动保存',
latestRelease: '最近发布',
copyParam: '复制参数',
debug: '调试',
exit: '直接退出',
exitSave: '保存并退出',
},
tip: {
publicSuccess: '发布成功',
noData: '没有找到相关结果',
nameMessage: '名字不能为空!',
onlyRight: '只允许从右边的锚点连出',
notRecyclable: '不可循环连线',
onlyLeft: '只允许连接左边的锚点',
applicationNodeError: '该应用不可用',
functionNodeError: '该函数不可用',
repeatedNodeError: '节点名称已存在!',
cannotCopy: '不能被复制',
copyError: '已复制节点',
paramErrorMessage: '参数已存在: ',
saveMessage: '当前的更改尚未保存,是否保存后退出?',
},
delete: {
confirmTitle: '确定删除该节点?',
deleteMessage: '节点不允许删除'
},
control: {
zoomOut: '缩小',
zoomIn: '放大',
fitView: '适应',
retract: '收起全部节点',
extend: '展开全部节点',
beautify: '一键美化'
},
variable: {
label: '变量',
global: '全局变量',
Referencing: '引用变量',
ReferencingRequired: '引用变量必填',
ReferencingError: '引用变量错误',
NoReferencing: '不存在的引用变量',
placeholder: '请选择变量'
},
condition: {
title: '执行条件',
front: '前置',
AND: '所有',
OR: '任一',
text: '连线节点执行完,执行当前节点'
},
validate: {
startNodeRequired: '开始节点必填',
startNodeOnly: '开始节点只能有一个',
baseNodeRequired: '基本信息节点必填',
baseNodeOnly: '基本信息节点只能有一个',
notInWorkFlowNode: '未在流程中的节点',
noNextNode: '不存在的下一个节点',
nodeUnavailable: '节点不可用',
needConnect1: '节点的',
needConnect2: '分支需要连接',
cannotEndNode: '节点不能当做结束节点'
},
nodes: {
startNode: {
label: '开始',
question: '用户问题',
currentTime: '当前时间'
},
baseNode: {
label: '基本信息',
appName: {
label: '应用名称'
},
appDescription: {
label: '应用描述'
},
fileUpload: {
label: '文件上传',
tooltip: '开启后,问答页面会显示上传文件的按钮。'
},
FileUploadSetting: {
title: '文件上传设置',
maxFiles: '单次上传最多文件数',
fileLimit: '每个文件最大MB',
fileUploadType: {
label: '上传的文件类型',
documentText: '需要使用“文档内容提取”节点解析文档内容',
imageText: '需要使用“视觉模型”节点解析图片内容',
audioText: '需要使用“语音转文本”节点解析音频内容'
}
}
},
aiChatNode: {
label: 'AI 对话',
text: '与 AI 大模型进行对话',
answer: 'AI 回答内容',
returnContent: {
label: '返回内容',
tooltip: `关闭后该节点的内容则不输出给用户。
`
},
defaultPrompt: '已知信息',
think: '思考过程'
},
searchDatasetNode: {
label: '知识库检索',
text: '关联知识库,查找与问题相关的分段',
paragraph_list: '检索结果的分段列表',
is_hit_handling_method_list: '满足直接回答的分段列表',
result: '检索结果',
directly_return: '满足直接回答的分段内容',
searchParam: '检索参数',
searchQuestion: {
label: '检索问题',
placeholder: '请选择检索问题',
requiredMessage: '请选择检索问题'
}
},
questionNode: {
label: '问题优化',
text: '根据历史聊天记录优化完善当前问题,更利于匹配知识库分段',
result: '问题优化结果',
defaultPrompt1: `根据上下文优化和完善用户问题`,
defaultPrompt2: `请输出一个优化后的问题。`,
systemDefault: '你是一个问题优化大师'
},
conditionNode: {
label: '判断器',
text: '根据不同条件执行不同的节点',
branch_name: '分支名称',
conditions: {
label: '条件',
info: '符合以下',
requiredMessage: '请选择条件'
},
valueMessage: '请输入值',
addCondition: '添加条件',
addBranch: '添加分支'
},
replyNode: {
label: '指定回复',
text: '指定回复内容,引用变量会转换为字符串进行输出',
content: '内容',
replyContent: {
label: '回复内容',
custom: '自定义',
reference: '引用变量'
}
},
rerankerNode: {
label: '多路召回',
text: '使用重排模型对多个知识库的检索结果进行二次召回',
result_list: '重排结果列表',
result: '重排结果',
rerankerContent: {
label: '重排内容',
requiredMessage: '请选择重排内容'
},
higher: '高于',
ScoreTooltip: 'Score越高相关性越强。',
max_paragraph_char_number: '最大引用字符数',
reranker_model: {
label: '重排模型',
placeholder: '请选择重排模型'
}
},
formNode: {
label: '表单收集',
text: '在问答过程中用于收集用户信息,可以根据收集到表单数据执行后续流程',
form_content_format1: '你好,请先填写下面表单内容:',
form_content_format2: '填写后请点击【提交】按钮进行提交。',
form_data: '表单全部内容',
formContent: {
label: '表单输出内容',
requiredMessage: '请表单输出内容',
tooltip: '设置执行该节点输出的内容,{ form } 为表单的占位符。'
},
formAllContent: '表单全部内容',
formSetting: '表单配置'
},
documentExtractNode: {
label: '文档内容提取',
text: '提取文档中的内容',
content: '文档内容'
},
imageUnderstandNode: {
label: '图片理解',
text: '识别出图片中的对象、场景等信息回答用户问题',
answer: 'AI 回答内容',
model: {
label: '视觉模型',
requiredMessage: '请选择视觉模型'
},
image: {
label: '选择图片',
requiredMessage: '请选择图片'
}
},
variableAssignNode: {
label: '变量赋值',
text: '更新全局变量的值',
assign: '赋值'
},
imageGenerateNode: {
label: '图片生成',
text: '根据提供的文本内容生成图片',
answer: 'AI 回答内容',
model: {
label: '图片生成模型',
requiredMessage: '请选择图片生成模型'
},
prompt: {
label: '提示词(正向)',
tooltip: '正向提示词,用来描述生成图像中期望包含的元素和视觉特点'
},
negative_prompt: {
label: '提示词(负向)',
tooltip: '反向提示词,用来描述不希望在画面中看到的内容,可以对画面进行限制。',
placeholder: '请描述不想生成的图片内容,比如:颜色、血腥内容'
}
},
speechToTextNode: {
label: '语音转文本',
text: '将音频通过语音识别模型转换为文本',
stt_model: {
label: '语音识别模型'
},
audio: {
label: '选择语音文件',
placeholder: '请选择语音文件'
}
},
textToSpeechNode: {
label: '文本转语音',
text: '将文本通过语音合成模型转换为音频',
tts_model: {
label: '语音识别模型'
},
content: {
label: '选择文本内容'
}
},
functionNode: {
label: '自定义函数',
text: '通过执行自定义脚本,实现数据处理'
},
applicationNode: {
label: '应用节点'
}
},
compare: {
is_null: '为空',
is_not_null: '不为空',
contain: '包含',
not_contain: '不包含',
eq: '等于',
ge: '大于等于',
gt: '大于',
le: '小于等于',
lt: '小于',
len_eq: '长度等于',
len_ge: '长度大于等于',
len_gt: '长度大于',
len_le: '长度小于等于',
len_lt: '长度小于'
},
FileUploadSetting: {}
}

View File

@ -1,215 +0,0 @@
export default {
title: '应用',
createApplication: '创建应用',
importApplication: '导入应用',
copyApplication: '复制应用',
workflow: '高级编排',
simple: '简单配置',
searchBar: {
placeholder: '按名称搜索'
},
setting: {
demo: '演示'
},
delete: {
confirmTitle: '是否删除应用:',
confirmMessage: '删除后该应用将不再提供服务,请谨慎操作。'
},
tip: {
ExportError: '导出失败',
professionalMessage: '社区版最多支持 5 个应用,如需拥有更多应用,请升级为专业版。',
saveErrorMessage: '保存失败,请检查输入或稍后再试',
loadingErrorMessage: '加载配置失败,请检查输入或稍后再试'
},
applicationForm: {
title: {
appTest: '调试预览',
copy: '副本'
},
form: {
appName: {
label: '名称',
placeholder: '请输入应用名称',
requiredMessage: '请输入应用名称'
},
appDescription: {
label: '描述',
placeholder: '描述该应用的应用场景及用途XXX 小助手回答用户提出的 XXX 产品使用问题'
},
appType: {
label: '类型',
simplePlaceholder: '适合新手创建小助手',
workflowPlaceholder: '适合高级用户自定义小助手的工作流'
},
appTemplate: {
blankApp: '空白应用',
assistantApp: '知识库问答助手'
},
aiModel: {
label: 'AI 模型',
placeholder: '请选择 AI 模型'
},
roleSettings: {
label: '系统角色',
placeholder: '你是 xxx 小助手'
},
prompt: {
label: '提示词',
noReferences: ' (无引用知识库)',
references: ' (引用知识库)',
placeholder: '请输入提示词',
requiredMessage: '请输入提示词',
tooltip:
'通过调整提示词内容,可以引导大模型聊天方向,该提示词会被固定在上下文的开头,可以使用变量。',
noReferencesTooltip:
'通过调整提示词内容,可以引导大模型聊天方向,该提示词会被固定在上下文的开头。可以使用变量:{question} 是用户提出问题的占位符。',
referencesTooltip:
'通过调整提示词内容,可以引导大模型聊天方向,该提示词会被固定在上下文的开头。可以使用变量:{data} 是引用知识库中分段的占位符;{question} 是用户提出问题的占位符。',
defaultPrompt: `已知信息:{data}
{question}
- 使`
},
historyRecord: {
label: '历史聊天记录'
},
relatedKnowledge: {
label: '关联知识库',
placeholder: '关联的知识库展示在这里'
},
multipleRoundsDialogue: '多轮对话',
prologue: '开场白',
defaultPrologue:
'您好,我是 XXX 小助手,您可以向我提出 XXX 使用问题。\n- XXX 主要功能有什么?\n- XXX 如何收费?\n- 需要转人工服务',
problemOptimization: {
label: '问题优化',
tooltip: '根据历史聊天优化完善当前问题,更利于匹配知识点。'
},
voiceInput: {
label: '语音输入',
placeholder: '请选择语音识别模型',
requiredMessage: '请选择语音输入模型',
autoSend: '自动发送'
},
voicePlay: {
label: '语音播放',
placeholder: '请选择语音合成模型',
requiredMessage: '请选择语音播放模型',
autoPlay: '自动播放',
browser: '浏览器播放(免费)',
tts: 'TTS模型',
listeningTest: '试听'
},
reasoningContent: {
label: '输出思考',
tooltip: '请根据模型返回的思考标签设置,标签中间的内容将会认定为思考过程',
start: '开始',
end: '结束'
}
},
buttons: {
publish: '保存并发布',
addModel: '添加模型'
},
dialog: {
addDataset: '添加关联知识库',
addDatasetPlaceholder: '所选知识库必须使用相同的 Embedding 模型',
selected: '已选',
countDataset: '个知识库',
selectSearchMode: '检索模式',
vectorSearch: '向量检索',
vectorSearchTooltip: '向量检索是一种基于向量相似度的检索方式,适用于知识库中的大数据量场景。',
fullTextSearch: '全文检索',
fullTextSearchTooltip:
'全文检索是一种基于文本相似度的检索方式,适用于知识库中的小数据量场景。',
hybridSearch: '混合检索',
hybridSearchTooltip:
'混合检索是一种基于向量和文本相似度的检索方式,适用于知识库中的中等数据量场景。',
similarityThreshold: '相似度高于',
similarityTooltip: '相似度越高相关性越强。',
topReferences: '引用分段数 TOP',
maxCharacters: '最多引用字符数',
noReferencesAction: '无引用知识库分段时',
continueQuestioning: '继续向 AI 模型提问',
provideAnswer: '指定回答内容',
designated_answer:
'你好,我是 XXX 小助手,我的知识库只包含了 XXX 产品相关知识,请重新描述您的问题。',
defaultPrompt1:
'()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在',
defaultPrompt2: '标签中'
}
},
applicationAccess: {
title: '应用接入',
wecom: '企业微信应用',
wecomTip: '打造企业微信智能应用',
dingtalk: '钉钉应用',
dingtalkTip: '打造钉钉智能应用',
wechat: '公众号',
wechatTip: '打造公众号智能应用',
lark: '飞书应用',
larkTip: '打造飞书智能应用',
slack: 'Slack',
slackTip: '打造 Slack 智能应用',
setting: '配置',
callback: '回调地址',
callbackTip: '请输入回调地址',
wecomPlatform: '企业微信后台',
wechatPlatform: '微信公众平台',
dingtalkPlatform: '钉钉开放平台',
larkPlatform: '飞书开放平台',
wecomSetting: {
title: '企业微信应用配置',
cropId: '企业 ID',
cropIdPlaceholder: '请输入企业 ID',
agentIdPlaceholder: '请输入Agent ID',
secretPlaceholder: '请输入Secret',
tokenPlaceholder: '请输入Token',
encodingAesKeyPlaceholder: '请输入EncodingAESKey',
authenticationSuccessful: '认证成功',
urlInfo: '-应用管理-自建-创建的应用-接收消息-设置 API 接收的 "URL" 中'
},
dingtalkSetting: {
title: '钉钉应用配置',
clientIdPlaceholder: '请输入Client ID',
clientSecretPlaceholder: '请输入Client Secret',
urlInfo: '-机器人页面,设置 "消息接收模式" 为 HTTP模式 并把上面URL填写到"消息接收地址"中'
},
wechatSetting: {
title: '公众号应用配置',
appId: '开发者ID (APP ID)',
appIdPlaceholder: '请输入开发者ID (APP ID)',
appSecret: '开发者密钥 (APP SECRET)',
appSecretPlaceholder: '请输入开发者密钥 (APP SECRET)',
token: '令牌 (TOKEN)',
tokenPlaceholder: '请输入令牌 (TOKEN)',
aesKey: '消息加解密密钥',
aesKeyPlaceholder: '请输入消息加解密密钥',
urlInfo: '-设置与开发-基本配置-服务器配置的 "服务器地址URL" 中'
},
larkSetting: {
title: '飞书应用配置',
appIdPlaceholder: '请输入App ID',
appSecretPlaceholder: '请输入App Secret',
verificationTokenPlaceholder: '请输入Verification Token',
urlInfo: '-事件与回调-事件配置-配置订阅方式的 "请求地址" 中'
},
slackSetting: {
title: 'Slack 应用配置',
signingSecretPlaceholder: '请输入 Signing Secret',
botUserTokenPlaceholder: '请输入 Bot User Token'
},
copyUrl: '复制链接填入到'
},
hitTest: {
title: '命中测试',
text: '针对用户提问调试段落匹配情况,保障回答效果。',
emptyMessage1: '命中段落显示在这里',
emptyMessage2: '没有命中的分段'
}
}

View File

@ -1,84 +0,0 @@
export default {
title: '知识库',
createDataset: '创建知识库',
general: '通用型',
web: 'web 站点',
relatedApplications: '关联应用',
document_count: '文档数',
relatedApp_count: '关联应用',
searchBar: {
placeholder: '按名称搜索'
},
setting: {
vectorization: '向量化',
sync: '同步'
},
tip: {
professionalMessage: '社区版最多支持 50 个知识库,如需拥有更多知识库,请升级为专业版。',
syncSuccess: '同步任务发送成功',
updateModeMessage: '修改知识库向量模型后,需要对知识库向量化,是否继续保存?'
},
delete: {
confirmTitle: '是否删除知识库:',
confirmMessage1: '此知识库关联',
confirmMessage2: '个应用,删除后无法恢复,请谨慎操作。'
},
datasetForm: {
title: {
info: '基本信息'
},
form: {
datasetName: {
label: '知识库名称',
placeholder: '请输入知识库名称',
requiredMessage: '请输入应用名称'
},
datasetDescription: {
label: '知识库描述',
placeholder:
'描述知识库的内容详尽的描述将帮助AI能深入理解该知识库的内容能更准确的检索到内容提高该知识库的命中率。',
requiredMessage: '请输入知识库描述'
},
EmbeddingModel: {
label: '向量模型',
placeholder: '请选择向量模型',
requiredMessage: '请输入Embedding模型'
},
datasetType: {
label: '知识库类型',
generalInfo: '上传本地文档',
webInfo: '同步Web网站文本数据'
},
source_url: {
label: 'Web 根地址',
placeholder: '请输入 Web 根地址',
requiredMessage: ' 请输入 Web 根地址'
},
selector: {
label: '选择器',
placeholder: '默认为 body可输入 .classname/#idname/tagname'
}
}
},
ResultSuccess: {
title: '知识库创建成功',
paragraph: '分段',
paragraph_count: '个分段',
documentList: '文档列表',
loading: '导入中',
buttons: {
toDataset: '返回知识库列表',
toDocument: '前往文档'
}
},
syncWeb: {
title: '同步知识库',
syncMethod: '同步方式',
replace: '替换同步',
replaceText: '重新获取 Web 站点文档,覆盖替换本地知识库中的文档',
complete: '整体同步',
completeText: '先删除本地知识库所有文档,重新获取 Web 站点文档',
tip: '注意:所有同步都会删除已有数据重新获取新数据,请谨慎操作。'
}
}

View File

@ -1,174 +0,0 @@
export default {
uploadDocument: '上传文档',
importDocument: '导入文档',
syncDocument: '同步文档',
selected: '已选',
items: '项',
searchBar: {
placeholder: '按 文档名称 搜索'
},
setting: {
migration: '迁移',
cancelGenerateQuestion: '取消生成问题',
cancelVectorization: '取消向量化',
cancelGenerate: '取消生成',
export: '导出'
},
tip: {
saveMessage: '当前的更改尚未保存,确认退出吗?',
cancelSuccess: '批量取消成功',
sendMessage: '发送成功',
vectorizationSuccess: '批量向量化成功',
nameMessage: '文件名称不能为空!',
importMessage: '导入成功',
migrationSuccess: '迁移成功'
},
upload: {
selectFile: '选择文件',
selectFiles: '选择文件夹',
uploadMessage: '拖拽文件至此上传或',
formats: '支持格式:',
requiredMessage: '请上传文件',
errorMessage1: '文件大小超过 100MB',
errorMessage2: '文件格式不支持',
errorMessage3: '文件不能为空',
errorMessage4: '每次最多上传50个文件',
template: '模版',
download: '下载'
},
fileType: {
txt: {
label: '文本文件',
tip1: '1、文件上传前建议规范文件的分段标识',
tip2: '2、每次最多上传 50 个文件,每个文件不超过 100MB'
},
table: {
label: '表格',
tip1: '1、点击下载对应模版并完善信息',
tip2: '2、第一行必须是列标题且列标题必须是有意义的术语表中每条记录将作为一个分段',
tip3: '3、上传的表格文件中每个 sheet 会作为一个文档sheet名称为文档名称',
tip4: '4、每次最多上传 50 个文件,每个文件不超过 100MB'
},
QA: {
label: 'QA 问答对',
tip1: '1、点击下载对应模版并完善信息',
tip2: '2、上传的表格文件中每个 sheet 会作为一个文档sheet名称为文档名称',
tip3: '3、每次最多上传 50 个文件,每个文件不超过 100MB'
}
},
setRules: {
title: {
setting: '设置分段规则',
preview: '分段预览'
},
intelligent: {
label: '智能分段(推荐)',
text: '不了解如何设置分段规则推荐使用智能分段'
},
advanced: {
label: '高级分段',
text: '用户可根据文档规范自行设置分段标识符、分段长度以及清洗规则'
},
patterns: {
label: '分段标识',
tooltip: '按照所选符号先后顺序做递归分割,分割结果超出分段长度将截取至分段长度。',
placeholder: '请选择'
},
limit: {
label: '分段长度'
},
with_filter: {
label: '自动清洗',
text: '去掉重复多余符号空格、空行、制表符'
},
checkedConnect: {
label: '导入时添加分段标题为关联问题(适用于标题为问题的问答对)'
}
},
buttons: {
prev: '上一步',
next: '下一步',
import: '开始导入',
preview: '生成预览'
},
table: {
name: '文件名称',
char_length: '字符数',
paragraph: '分段',
all: '全部',
updateTime: '更新时间'
},
fileStatus: {
label: '文件状态',
SUCCESS: '成功',
FAILURE: '失败',
EMBEDDING: '索引中',
PENDING: '排队中',
GENERATE: '生成中',
SYNC: '同步中',
REVOKE: '取消中',
finish: '完成'
},
enableStatus: {
label: '启用状态',
enable: '开启',
close: '关闭'
},
sync: {
label: '同步',
confirmTitle: '确认同步文档?',
confirmMessage1: '同步将删除已有数据重新获取新数据,请谨慎操作。',
confirmMessage2: '无法同步,请先去设置文档 URL地址',
successMessage: '同步文档成功'
},
delete: {
confirmTitle1: '是否批量删除',
confirmTitle2: '个文档?',
confirmMessage: '所选文档中的分段会跟随删除,请谨慎操作。',
successMessage: '批量删除成功',
confirmTitle3: '是否删除文档:',
confirmMessage1: '此文档下的',
confirmMessage2: '个分段都会被删除,请谨慎操作。'
},
form: {
source_url: {
label: '文档地址',
placeholder: '请输入文档地址,一行一个,地址不正确文档会导入失败。',
requiredMessage: '请输入文档地址'
},
selector: {
label: '选择器',
placeholder: '默认为 body可输入 .classname/#idname/tagname'
},
hit_handling_method: {
label: '命中处理方式',
tooltip: '用户提问时,命中文档下的分段时按照设置的方式进行处理。'
},
similarity: {
label: '相似度高于',
placeholder: '直接返回分段内容',
requiredMessage: '请输入相似度'
}
},
hitHandlingMethod: {
optimization: '模型优化',
directly_return: '直接回答'
},
generateQuestion: {
title: '生成问题',
successMessage: '生成问题成功',
tip1: '提示词中的 {data} 为分段内容的占位符,执行时替换为分段内容发送给 AI 模型;',
tip2: 'AI 模型根据分段内容生成相关问题,请将生成的问题放至',
tip3: '标签中,系统会自动关联标签中的问题;',
tip4: '生成效果依赖于所选模型和提示词,用户可自行调整至最佳效果。',
prompt1: `内容:{data}\n\n请总结上面的内容并根据内容总结生成 5 个问题。\n回答要求\n- 请只输出问题;\n- 请将每个问题放置`,
prompt2: `标签中。`
},
feishu: {
selectDocument: '选择文档',
tip1: '仅支持文档和表格类型文档会根据标题分段表格会转为Markdown格式后再分段。',
tip2: '系统不存储原始文档,导入文档前,建议规范文档的分段标识。',
allCheck: '全选'
}
}

View File

@ -1,77 +0,0 @@
export default {
title: '函数库',
internalTitle: '内置函数',
added: '已添加',
createFunction: '创建函数',
editFunction: '编辑函数',
copyFunction: '复制函数',
importFunction: '导入函数',
searchBar: {
placeholder: '按函数名称搜索'
},
setting: {
disabled: '禁用'
},
tip: {
saveMessage: '当前的更改尚未保存,确认退出吗?'
},
delete: {
confirmTitle: '是否删除函数:',
confirmMessage: '删除后,引用了该函数的应用提问时会报错 ,请谨慎操作。'
},
disabled: {
confirmTitle: '是否禁用函数:',
confirmMessage: '禁用后,引用了该函数的应用提问时会报错 ,请谨慎操作。'
},
functionForm: {
title: {
copy: '副本',
baseInfo: '基础信息'
},
form: {
functionName: {
label: '名称',
placeholder: '请输入函数名称',
requiredMessage: '请输入函数名称'
},
functionDescription: {
label: '描述',
placeholder: '请输入函数的描述'
},
permission_type: {
label: '权限',
requiredMessage: '请选择'
},
paramName: {
label: '参数名',
placeholder: '请输入参数名',
requiredMessage: '请输入参数名'
},
dataType: {
label: '数据类型'
},
source: {
label: '来源',
custom: '自定义',
reference: '引用参数'
},
required: {
label: '是否必填'
},
param: {
paramInfo1: '使用函数时显示',
paramInfo2: '使用函数时不显示',
code: '函数内容Python',
selectPlaceholder: '请选择参数',
inputPlaceholder: '请输入参数值',
},
debug: {
run: '运行',
output: '输出',
runResult: '运行结果',
runSuccess: '运行成功',
runFailed: '运行失败'
}
}
}
}

View File

@ -1,34 +1,34 @@
import notFound from './404'
import application from './application'
import applicationOverview from './application-overview'
import dataset from './dataset'
import system from './system'
import functionLib from './function-lib'
import user from './user'
import team from './team'
import template from './template'
import document from './document'
import paragraph from './paragraph'
import problem from './problem'
import log from './log'
import applicationWorkflow from './application-workflow'
// import notFound from './404'
// import application from './application'
// import applicationOverview from './application-overview'
// import dataset from './dataset'
// import system from './system'
// import functionLib from './function-lib'
// import user from './user'
// import team from './team'
// import template from './template'
// import document from './document'
// import paragraph from './paragraph'
// import problem from './problem'
// import log from './log'
// import applicationWorkflow from './application-workflow'
import login from './login'
import operateLog from './operate-log'
// import operateLog from './operate-log'
export default {
notFound,
application,
applicationOverview,
dataset,
system,
functionLib,
user,
team,
template,
document,
paragraph,
problem,
log,
applicationWorkflow,
// notFound,
// application,
// applicationOverview,
// dataset,
// system,
// functionLib,
// user,
// team,
// template,
// document,
// paragraph,
// problem,
// log,
// applicationWorkflow,
login,
operateLog
// operateLog
}

View File

@ -1,41 +0,0 @@
export default {
title: '对话日志',
delete: {
confirmTitle: '是否删除问题:',
confirmMessage1: '删除问题关联的',
confirmMessage2: '个分段会被取消关联,请谨慎操作。'
},
buttons: {
clearStrategy: '清除策略',
prev: '上一条',
next: '下一条'
},
table: {
abstract: '摘要',
chat_record_count: '对话提问数',
user: '用户',
feedback: {
label: '用户反馈',
star: '赞同',
trample: '反对'
},
mark: '改进标注',
recenTimes: '最近对话时间'
},
addToDataset: '添加至知识库',
daysText: '天之前的对话记录',
selectDataset: '选择知识库',
selectDatasetPlaceholder: '请选择知识库',
saveToDocument: '保存至文档',
documentPlaceholder: '请选择文档',
editContent: '修改内容',
editMark: '修改标注',
form: {
content: {
placeholder: '请输入内容'
},
title: {
placeholder: '请给当前内容设置一个标题,以便管理查看'
}
}
}

View File

@ -1,5 +1,25 @@
export default {
title: '普通登录',
loginForm: {
username: {
label: '用户名',
placeholder: '请输入用户名',
requiredMessage: '请输入用户名',
lengthMessage: '长度在 6 到 20 个字符',
},
password: {
label: '登录密码',
placeholder: '请输入密码',
requiredMessage: '请输入密码',
lengthMessage: '长度在 6 到 20 个字符',
},
captcha: {
label: '验证码',
placeholder: '请输入验证码',
requiredMessage: '请输入验证码',
validatorMessage: '验证码不正确',
},
},
jump_tip: '即将跳转至认证源页面进行认证',
jump: '跳转',
resetPassword: '修改密码',
@ -9,7 +29,7 @@ export default {
login: '登录',
register: '注册',
backLogin: '返回登录',
checkCode: '立即验证'
checkCode: '立即验证',
},
newPassword: '新密码',
enterPassword: '请输入修改密码',
@ -19,6 +39,6 @@ export default {
placeholder: '请输入验证码',
getVerificationCode: '获取验证码',
successMessage: '验证码发送成功',
resend: '重新发送'
}
resend: '重新发送',
},
}

View File

@ -1,30 +0,0 @@
export default {
title: '操作日志',
table: {
menu: {
label: '操作菜单'
},
operate: {
label: '操作'
},
user: {
label: '操作用户'
},
status: {
label: '状态',
success: '成功',
fail: '失败',
all: '全部'
},
ip_address: {
label: 'IP地址'
},
opt: {
label: 'API详情'
},
operateTime: {
label: '操作时间'
}
},
close: '关闭'
}

View File

@ -1,32 +0,0 @@
export default {
title: '段落',
paragraph_count: '段落',
editParagraph: '编辑分段',
addParagraph: '添加分段',
paragraphDetail: '分段详情',
character_count: '个字符',
setting: {
batchSelected: '批量选择',
cancelSelected: '取消选择'
},
delete: {
confirmTitle: '是否删除段落:',
confirmMessage: '删除后无法恢复,请谨慎操作。'
},
relatedProblem: {
title: '关联问题',
placeholder: '请选择问题'
},
form: {
paragraphTitle: {
label: '分段标题',
placeholder: '请输入分段标题'
},
content: {
label: '分段内容',
placeholder: '请输入分段内容',
requiredMessage1: '请输入分段内容',
requiredMessage2: '内容最多不超过 100000 个字'
}
}
}

View File

@ -1,37 +0,0 @@
export default {
title: '问题',
createProblem: '创建问题',
detailProblem: '问题详情',
quickCreateProblem: '快速创建问题',
quickCreateName: '问题',
tip: {
placeholder: '请输入问题,支持输入多个,一行一个。',
errorMessage: '问题不能为空!',
requiredMessage: '请输入问题',
relatedSuccess:'批量关联分段成功'
},
setting: {
batchDelete: '批量删除',
cancelRelated: '取消关联'
},
searchBar: {
placeholder: '按名称搜索'
},
table: {
paragraph_count: '关联分段数',
updateTime: '更新时间'
},
delete: {
confirmTitle: '是否删除问题:',
confirmMessage1: '删除问题关联的',
confirmMessage2: '个分段会被取消关联,请谨慎操作。'
},
relateParagraph: {
title: '关联分段',
selectDocument: '选择文档',
placeholder: '按 文档名称 搜索',
selectedParagraph: '已选分段',
count: '个'
},
}

View File

@ -1,151 +0,0 @@
export default {
title: '系统设置',
test: '测试连接',
testSuccess: '测试连接成功',
testFailed: '测试连接失败',
password: '密码',
authentication: {
title: '登录认证',
ldap: {
title: 'LDAP',
address: 'LDAP 地址',
serverPlaceholder: '请输入LDAP 地址',
bindDN: '绑定DN',
bindDNPlaceholder: '请输入绑定 DN',
ou: '用户OU',
ouPlaceholder: '请输入用户 OU',
ldap_filter: '用户过滤器',
ldap_filterPlaceholder: '请输入用户过滤器',
ldap_mapping: 'LDAP 属性映射',
ldap_mappingPlaceholder: '请输入 LDAP 属性映射',
enableAuthentication: '启用 LDAP 认证'
},
cas: {
title: 'CAS',
ldpUri: 'ldpUri',
ldpUriPlaceholder: '请输入ldpUri',
validateUrl: '验证地址',
validateUrlPlaceholder: '请输入验证地址',
redirectUrl: '回调地址',
redirectUrlPlaceholder: '请输入回调地址',
enableAuthentication: '启用 CAS 认证'
},
oidc: {
title: 'OIDC',
authEndpoint: '授权端地址',
authEndpointPlaceholder: '请输入授权端地址',
tokenEndpoint: 'Token端地址',
tokenEndpointPlaceholder: '请输入 Token 端地址',
userInfoEndpoint: '用户信息端地址',
userInfoEndpointPlaceholder: '请输入用户信息端地址',
scopePlaceholder: '请输入连接范围',
clientId: '客户端 ID',
clientIdPlaceholder: '请输入客户端 ID',
clientSecret: '客户端密钥',
clientSecretPlaceholder: '请输入客户端密钥',
logoutEndpoint: '注销端地址',
logoutEndpointPlaceholder: '请输入注销端地址',
redirectUrl: '回调地址',
redirectUrlPlaceholder: '请输入回调地址',
enableAuthentication: '启用 OIDC 认证'
},
oauth2: {
title: 'OAuth2',
authEndpoint: '授权端地址',
authEndpointPlaceholder: '请输入授权端地址',
tokenEndpoint: 'Token 端地址',
tokenEndpointPlaceholder: '请输入 Token 端地址',
userInfoEndpoint: '用户信息端地址',
userInfoEndpointPlaceholder: '请输入用户信息端地址',
scope: '连接范围',
scopePlaceholder: '请输入连接范围',
clientId: '客户端 ID',
clientIdPlaceholder: '请输入客户端 ID',
clientSecret: '客户端密钥',
clientSecretPlaceholder: '请输入客户端密钥',
redirectUrl: '回调地址',
redirectUrlPlaceholder: '请输入回调地址',
filedMapping: '字段映射',
filedMappingPlaceholder: '请输入字段映射',
enableAuthentication: '启用 OAuth2 认证'
},
scanTheQRCode: {
title: '扫码登录',
wecom: '企业微信',
dingtalk: '钉钉',
lark: '飞书',
effective: '有效',
alreadyTurnedOn: '已开启',
notEnabled: '未开启',
validate: '校验',
validateSuccess: '校验成功',
validateFailed: '校验失败',
validateFailedTip: '请填写所有必填项并确保格式正确',
appKeyPlaceholder: '请输入 App Key',
appSecretPlaceholder: '请输入 App Secret',
corpIdPlaceholder: '请输入 Corp Id',
agentIdPlaceholder: '请输入 Agent Id',
callbackWarning: '请输入有效的 URL 地址',
larkQrCode: '飞书扫码登录',
dingtalkQrCode: '钉钉扫码登录',
setting: '设置',
access: '接入'
}
},
theme: {
title: '外观设置',
platformDisplayTheme: '平台显示主题',
customTheme: '自定义主题',
platformLoginSettings: '平台登录设置',
custom: '自定义',
pagePreview: '页面预览',
default: '默认',
restoreDefaults: '恢复默认',
orange: '活力橙',
green: '松石绿',
purple: '神秘紫',
red: '胭脂红',
loginBackground: '登录背景图',
loginLogo: '登录 Logo',
websiteLogo: '网站 Logo',
replacePicture: '替换图片',
websiteLogoTip: '顶部网站显示的 Logo建议尺寸 48*48支持 JPG、PNG、GIF大小不超过 10MB',
loginLogoTip: '登录页面右侧 Logo建议尺寸 204*52支持 JPG、PNG、GIF大小不超过 10 MB',
loginBackgroundTip:
'左侧背景图,矢量图建议尺寸 576*900位图建议尺寸 1152*1800支持 JPG、PNG、GIF大小不超过 10 MB',
websiteName: '网站名称',
websiteNamePlaceholder: '请输入网站名称',
websiteNameTip: '显示在网页 Tab 的平台名称',
websiteSlogan: '欢迎语',
websiteSloganPlaceholder: '请输入欢迎语',
websiteSloganTip: '产品 Logo 下的欢迎语',
defaultSlogan: '欢迎使用 MaxKB 智能知识库问答系统',
logoDefaultTip: '默认为 MaxKB 登录界面,支持自定义设置',
defaultTip: '默认为 MaxKB 平台界面,支持自定义设置',
platformSetting: '平台设置',
showUserManual: '显示用户手册',
showForum: '显示论坛求助',
showProject: '显示项目地址',
urlPlaceholder: '请输入 URL 地址',
abandonUpdate: '放弃更新',
saveAndApply: '保存并应用',
fileMessageError: '文件大小超过 10M',
saveSuccess: '外观设置成功'
},
email: {
title: '邮箱设置',
smtpHost: 'SMTP Host',
smtpHostPlaceholder: '请输入 SMTP Host',
smtpPort: 'SMTP Port',
smtpPortPlaceholder: '请输入 SMTP Port',
smtpUser: 'SMTP 账户',
smtpUserPlaceholder: '请输入 SMTP 账户',
sendEmail: '发件人邮箱',
sendEmailPlaceholder: '请输入发件人邮箱',
smtpPassword: '发件人密码',
smtpPasswordPlaceholder: '请输入发件人密码',
enableSSL: '启用 SSL如果 SMTP 端口是 465通常需要启用 SSL',
enableTLS: '启用 TLS如果 SMTP 端口是 587通常需要启用 TLS'
}
}

View File

@ -1,31 +0,0 @@
export default {
title: '团队成员',
member: '成员',
manage: '所有者',
permissionSetting: '权限设置',
addMember: '添加成员',
addSubTitle: '成员登录后可以访问到您授权的数据。',
searchBar: {
placeholder: '请输入用户名搜索'
},
delete: {
button: '移除',
confirmTitle: '是否移除成员:',
confirmMessage: '移除后将会取消成员拥有的知识库和应用权限。'
},
setting: {
management: '管理',
check: '查看'
},
teamForm: {
form: {
userName: {
label: '用户名/邮箱',
placeholder: '请输入成员的用户名或邮箱',
requiredMessage: '请输入用户名/邮箱'
},
},
}
}

View File

@ -1,83 +0,0 @@
export default {
title: '模型设置',
provider: '供应商',
providerPlaceholder: '选择供应商',
addModel: '添加模型',
searchBar: {
placeholder: '按名称搜索'
},
delete: {
confirmTitle: '删除模型',
confirmMessage: '是否删除模型:'
},
tip: {
createSuccessMessage: '创建模型成功',
createErrorMessage: '基础信息有填写错误',
errorMessage: '变量已存在: ',
emptyMessage1: '请先选择基础信息的模型类型和基础模型',
emptyMessage2: '所选模型不支持参数设置',
updateSuccessMessage: '修改模型成功',
saveSuccessMessage: '模型参数保存成功',
downloadError: '下载失败',
noModel: '模型在Ollama不存在'
},
model: {
allModel: '全部模型',
publicModel: '公有模型',
privateModel: '私有模型',
LLM: '大语言模型',
EMBEDDING: '向量模型',
RERANKER: '重排模型',
STT: '语音识别',
TTS: '语音合成',
IMAGE: '视觉模型',
TTI: '图片生成'
},
templateForm: {
title: {
baseInfo: '基础信息',
advancedInfo: '高级设置',
modelParams: '模型参数',
editParam: '编辑参数',
addParam: '添加参数',
paramSetting: '模型参数设置',
apiParamPassing: '接口传参'
},
form: {
templateName: {
label: '模型名称',
placeholder: '请给基础模型设置一个名称',
tooltip: 'MaxKB 中自定义的模型名称',
requiredMessage: '模型名称不能为空'
},
permissionType: {
label: '权限',
privateDesc: '仅当前用户使用',
publicDesc: '所有用户都可使用',
requiredMessage: '权限不能为空'
},
model_type: {
label: '模型类型',
placeholder: '请选择模型类型',
tooltip1: '大语言模型在应用中与AI对话的推理模型。',
tooltip2: '向量模型:在知识库中对文档内容进行向量化的模型。',
tooltip3: '语音识别:在应用中开启语音识别后用于语音转文字的模型。',
tooltip4: '语音合成:在应用中开启语音播放后用于文字转语音的模型。',
tooltip5: '重排模型:在高级编排应用中使用多路召回时,对候选分段进行重新排序的模型。',
tooltip6: '视觉模型:在高级编排应用中用于图片理解的视觉模型。',
tooltip7: '图片生成:在高级编排应用中用于图片生成的视觉模型。',
requiredMessage: '模型类型不能为空'
},
base_model: {
label: '基础模型',
tooltip: '列表中未列出的模型,直接输入模型名称,回车即可添加',
placeholder: '自定义输入基础模型后回车即可',
requiredMessage: '基础模型不能为空'
}
}
},
download: {
downloading: '正在下载中',
cancelDownload: '取消下载'
}
}

View File

@ -1,68 +0,0 @@
export default {
title: '用户管理',
createUser: '创建用户',
editUser: '编辑用户',
setting: {
updatePwd: '修改用户密码'
},
tip: {
professionalMessage: '社区版最多支持 2 个用户,如需拥有更多用户,请升级为专业版。',
updatePwdSuccess: '修改用户密码成功'
},
delete: {
confirmTitle: '是否删除用户:',
confirmMessage: '删除用户,该用户创建的资源(应用、知识库、模型)都会删除,请谨慎操作。'
},
disabled: {
confirmTitle: '是否禁用函数:',
confirmMessage: '禁用后,引用了该函数的应用提问时会报错 ,请谨慎操作。'
},
userForm: {
form: {
username: {
label: '用户名',
placeholder: '请输入用户名',
requiredMessage: '请输入用户名',
lengthMessage: '长度在 6 到 20 个字符'
},
nick_name: {
label: '姓名',
placeholder: '请输入姓名'
},
email: {
label: '邮箱',
placeholder: '请输入邮箱',
requiredMessage: '请输入邮箱',
validatorEmail: '请输入有效邮箱格式!',
},
phone: {
label: '手机号',
placeholder: '请输入手机号'
},
password: {
label: '登录密码',
placeholder: '请输入密码',
requiredMessage: '请输入密码',
lengthMessage: '长度在 6 到 20 个字符'
},
new_password: {
label: '新密码',
placeholder: '请输入新密码',
requiredMessage: '请输入新密码',
},
re_password: {
label: '确认密码',
placeholder: '请输入确认密码',
requiredMessage: '请输入确认密码',
validatorMessage: '密码不一致',
}
}
},
source: {
label: '用户类型',
local: '系统用户',
wecom: '企业微信',
lark: '飞书',
dingtalk: '钉钉'
}
}

View File

@ -2,16 +2,18 @@ import zhTw from 'element-plus/es/locale/lang/zh-tw'
import components from './components'
import layout from './layout'
import views from './views'
import theme from './theme'
import common from './common'
import dynamicsForm from './dynamics-form'
import chat from './ai-chat'
export default {
lang: '繁體中文',
layout,
common,
views,
theme,
common,
components,
zhTw,
dynamicsForm,
chat
chat,
}

View File

@ -0,0 +1,41 @@
export default {
title: '外觀設置',
defaultSlogan: '歡迎使用 MaxKB 智能知識庫問答系統',
platformDisplayTheme: '平台顯示主題',
customTheme: '自定義主題',
platformLoginSettings: '平台登錄設置',
custom: '自定義',
pagePreview: '頁面預覽',
default: '默認',
restoreDefaults: '恢復默認',
orange: '活力橙',
green: '松石綠',
purple: '神秘紫',
red: '胭脂紅',
loginBackground: '登錄背景圖',
loginLogo: '登錄 Logo',
websiteLogo: '網站 Logo',
replacePicture: '替換圖片',
websiteLogoTip: '頂部網站顯示的 Logo建議尺寸 48*48支持 JPG、PNG、GIF大小不超過 10MB',
loginLogoTip: '登錄頁面右側 Logo建議尺寸 204*52支持 JPG、PNG、GIF大小不超過 10 MB',
loginBackgroundTip:
'左側背景圖,矢量圖建議尺寸 576*900位圖建議尺寸 1152*1800支持 JPG、PNG、GIF大小不超過 10 MB',
websiteName: '網站名稱',
websiteNamePlaceholder: '請輸入網站名稱',
websiteNameTip: '顯示在網頁 Tab 的平台名稱',
websiteSlogan: '歡迎語',
websiteSloganPlaceholder: '請輸入歡迎語',
websiteSloganTip: '產品 Logo 下的歡迎語',
logoDefaultTip: '默认为 MaxKB 登錄界面,支持自定义设置',
defaultTip: '默認為 MaxKB 平台界面,支持自定義設置',
platformSetting: '平台設置',
showUserManual: '顯示用戶手冊',
showForum: '顯示論壇求助',
showProject: '顯示項目地址',
urlPlaceholder: '請輸入 URL 地址',
abandonUpdate: '放棄更新',
saveAndApply: '保存並應用',
fileMessageError: '文件大小超過 10M',
saveSuccess: '外觀設置成功',
}

View File

@ -1,5 +1,25 @@
export default {
title: '普通登錄',
loginForm: {
username: {
label: '使用者名稱',
placeholder: '請輸入使用者名稱',
requiredMessage: '請輸入使用者名稱',
lengthMessage: '長度須介於 6 到 20 個字元之間'
},
password: {
label: '登入密碼',
placeholder: '請輸入密碼',
requiredMessage: '請輸入密碼',
lengthMessage: '長度須介於 6 到 20 個字元之間'
},
captcha: {
label: '驗證碼',
placeholder: '請輸入驗證碼',
requiredMessage: '請輸入驗證碼',
validatorMessage: '驗證碼不正確',
},
},
jump_tip: '即將跳轉至認證源頁面進行認證',
jump: '跳轉',
resetPassword: '修改密碼',

View File

@ -19,7 +19,7 @@ export default {
ldap_filterPlaceholder: '請輸入使用者過濾器',
ldap_mapping: 'LDAP 屬性對應',
ldap_mappingPlaceholder: '請輸入 LDAP 屬性對應',
enableAuthentication: '啟用 LDAP 認證'
enableAuthentication: '啟用 LDAP 認證',
},
cas: {
title: 'CAS',
@ -29,7 +29,7 @@ export default {
validateUrlPlaceholder: '請輸入驗證位址',
redirectUrl: '回呼位址',
redirectUrlPlaceholder: '請輸入回呼位址',
enableAuthentication: '啟用 CAS 認證'
enableAuthentication: '啟用 CAS 認證',
},
oidc: {
title: 'OIDC',
@ -48,7 +48,7 @@ export default {
logoutEndpointPlaceholder: '請輸入登出端位址',
redirectUrl: '回呼位址',
redirectUrlPlaceholder: '請輸入回呼位址',
enableAuthentication: '啟用 OIDC 認證'
enableAuthentication: '啟用 OIDC 認證',
},
oauth2: {
@ -69,7 +69,7 @@ export default {
redirectUrlPlaceholder: '請輸入回呼位址',
filedMapping: '欄位對應',
filedMappingPlaceholder: '請輸入欄位對應',
enableAuthentication: '啟用 OAuth2 認證'
enableAuthentication: '啟用 OAuth2 認證',
},
scanTheQRCode: {
title: '掃碼登入',
@ -91,49 +91,10 @@ export default {
larkQrCode: '飛書掃碼登錄',
dingtalkQrCode: '釘釘掃碼登錄',
setting: '設置',
access: '接入'
}
},
theme: {
title: '外觀設置',
platformDisplayTheme: '平台顯示主題',
customTheme: '自定義主題',
platformLoginSettings: '平台登錄設置',
custom: '自定義',
pagePreview: '頁面預覽',
default: '默認',
restoreDefaults: '恢復默認',
orange: '活力橙',
green: '松石綠',
purple: '神秘紫',
red: '胭脂紅',
loginBackground: '登錄背景圖',
loginLogo: '登錄 Logo',
websiteLogo: '網站 Logo',
replacePicture: '替換圖片',
websiteLogoTip: '頂部網站顯示的 Logo建議尺寸 48*48支持 JPG、PNG、GIF大小不超過 10MB',
loginLogoTip: '登錄頁面右側 Logo建議尺寸 204*52支持 JPG、PNG、GIF大小不超過 10 MB',
loginBackgroundTip:
'左側背景圖,矢量圖建議尺寸 576*900位圖建議尺寸 1152*1800支持 JPG、PNG、GIF大小不超過 10 MB',
websiteName: '網站名稱',
websiteNamePlaceholder: '請輸入網站名稱',
websiteNameTip: '顯示在網頁 Tab 的平台名稱',
websiteSlogan: '歡迎語',
websiteSloganPlaceholder: '請輸入歡迎語',
websiteSloganTip: '產品 Logo 下的歡迎語',
logoDefaultTip: '默认为 MaxKB 登錄界面,支持自定义设置',
defaultSlogan: '歡迎使用 MaxKB 智能知識庫問答系統',
defaultTip: '默認為 MaxKB 平台界面,支持自定義設置',
platformSetting: '平台設置',
showUserManual: '顯示用戶手冊',
showForum: '顯示論壇求助',
showProject: '顯示項目地址',
urlPlaceholder: '請輸入 URL 地址',
abandonUpdate: '放棄更新',
saveAndApply: '保存並應用',
fileMessageError: '文件大小超過 10M',
saveSuccess: '外觀設置成功'
access: '接入',
},
},
email: {
title: '郵箱設置',
smtpHost: 'SMTP Host',
@ -147,6 +108,6 @@ export default {
smtpPassword: '發件人密碼',
smtpPasswordPlaceholder: '請輸入發件人密碼',
enableSSL: '啟用 SSL如果 SMTP 端口是 465通常需要啟用 SSL',
enableTLS: '啟用 TLS如果 SMTP 端口是 587通常需要啟用 TLS'
}
enableTLS: '啟用 TLS如果 SMTP 端口是 587通常需要啟用 TLS',
},
}

19
ui/src/request/Result.ts Normal file
View File

@ -0,0 +1,19 @@
export class Result<T> {
message: string
code: number
data: T
constructor(message: string, code: number, data: T) {
this.message = message
this.code = code
this.data = data
}
static success(data: any) {
return new Result('请求成功', 200, data)
}
static error(message: string, code: number) {
return new Result(message, code, null)
}
}
export default Result

349
ui/src/request/index.ts Normal file
View File

@ -0,0 +1,349 @@
import axios, { type InternalAxiosRequestConfig, AxiosHeaders } from 'axios'
import { MsgError } from '@/utils/message'
import type { NProgress } from 'nprogress'
import type { Ref } from 'vue'
import type { Result } from '@/request/Result'
import useStore from '@/stores'
import router from '@/router'
import { ref, type WritableComputedRef } from 'vue'
const axiosConfig = {
baseURL: '/api',
withCredentials: false,
timeout: 600000,
headers: {},
}
const instance = axios.create(axiosConfig)
/* 设置请求拦截器 */
instance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
if (config.headers === undefined) {
config.headers = new AxiosHeaders()
}
const { user, login } = useStore()
const token = login.getToken()
const language = user.getLanguage()
config.headers['Accept-Language'] = `${language}`
if (token) {
config.headers['AUTHORIZATION'] = `${token}`
}
return config
},
(err: any) => {
return Promise.reject(err)
},
)
//设置响应拦截器
instance.interceptors.response.use(
(response: any) => {
if (response.data) {
if (response.data.code !== 200 && !(response.data instanceof Blob)) {
if (response.config.url.includes('/application/authentication')) {
return Promise.reject(response.data)
}
if (
!response.config.url.includes('/valid') &&
!response.config.url.includes('/function_lib/debug')
) {
MsgError(response.data.message)
return Promise.reject(response.data)
}
}
}
return response
},
(err: any) => {
if (err.code === 'ECONNABORTED') {
MsgError(err.message)
console.error(err)
}
if (err.response?.status === 404) {
if (!err.response.config.url.includes('/application/authentication')) {
router.push('/404 ')
}
}
if (err.response?.status === 401) {
if (
!err.response.config.url.includes('chat/open') &&
!err.response.config.url.includes('application/profile')
) {
router.push({ name: 'login' })
}
}
if (err.response?.status === 403 && !err.response.config.url.includes('chat/open')) {
MsgError(
err.response.data && err.response.data.message ? err.response.data.message : '没有权限访问',
)
}
return Promise.reject(err)
},
)
export const request = instance
/* 简化请求方法统一处理返回结果并增加loading处理这里以{success,data,message}格式的返回值为例,具体项目根据实际需求修改 */
const promise: (
request: Promise<any>,
loading?: NProgress | Ref<boolean> | WritableComputedRef<boolean>,
) => Promise<Result<any>> = (request, loading = ref(false)) => {
return new Promise((resolve, reject) => {
if ((loading as NProgress).start) {
;(loading as NProgress).start()
} else {
;(loading as Ref).value = true
}
request
.then((response) => {
// blob类型的返回状态是response.status
if (response.status === 200) {
resolve(response?.data || response)
} else {
reject(response?.data || response)
}
})
.catch((error) => {
reject(error)
})
.finally(() => {
if ((loading as NProgress).start) {
;(loading as NProgress).done()
} else {
;(loading as Ref).value = false
}
})
})
}
/**
* get请求
* @param url url
* @param params
* @param loading loading
* @returns promise对象
*/
export const get: (
url: string,
params?: unknown,
loading?: NProgress | Ref<boolean>,
timeout?: number,
) => Promise<Result<any>> = (
url: string,
params: unknown,
loading?: NProgress | Ref<boolean>,
timeout?: number,
) => {
return promise(request({ url: url, method: 'get', params, timeout: timeout }), loading)
}
/**
* faso post请求
* @param url url
* @param params
* @param data
* @param loading loading
* @returns promise对象
*/
export const post: (
url: string,
data?: unknown,
params?: unknown,
loading?: NProgress | Ref<boolean>,
timeout?: number,
) => Promise<Result<any> | any> = (url, data, params, loading, timeout) => {
return promise(request({ url: url, method: 'post', data, params, timeout }), loading)
}
/**|
* put请求
* @param url
* @param params params参数地址
* @param data
* @param loading
* @returns
*/
export const put: (
url: string,
data?: unknown,
params?: unknown,
loading?: NProgress | Ref<boolean>,
timeout?: number,
) => Promise<Result<any>> = (url, data, params, loading, timeout) => {
return promise(request({ url: url, method: 'put', data, params, timeout }), loading)
}
/**
*
* @param url url
* @param params params参数
* @param loading
* @returns
*/
export const del: (
url: string,
params?: unknown,
data?: unknown,
loading?: NProgress | Ref<boolean>,
timeout?: number,
) => Promise<Result<any>> = (url, params, data, loading, timeout) => {
return promise(request({ url: url, method: 'delete', params, data, timeout }), loading)
}
/**
*
* @param url url地址
* @param data body
* @returns
*/
export const postStream: (url: string, data?: unknown) => Promise<Result<any> | any> = (
url,
data,
) => {
const { user, login } = useStore()
const token = login.getToken()
const language = user.getLanguage()
const headers: HeadersInit = { 'Content-Type': 'application/json' }
if (token) {
headers['AUTHORIZATION'] = `${token}`
}
headers['Accept-Language'] = `${language}`
return fetch(url, {
method: 'POST',
body: data ? JSON.stringify(data) : undefined,
headers: headers,
})
}
export const exportExcel: (
fileName: string,
url: string,
params: any,
loading?: NProgress | Ref<boolean>,
) => Promise<any> = (
fileName: string,
url: string,
params: any,
loading?: NProgress | Ref<boolean>,
) => {
return promise(request({ url: url, method: 'get', params, responseType: 'blob' }), loading).then(
(res: any) => {
if (res) {
const blob = new Blob([res], {
type: 'application/vnd.ms-excel',
})
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = fileName
link.click()
//释放内存
window.URL.revokeObjectURL(link.href)
}
return true
},
)
}
export const exportFile: (
fileName: string,
url: string,
params: any,
loading?: NProgress | Ref<boolean>,
) => Promise<any> = (
fileName: string,
url: string,
params: any,
loading?: NProgress | Ref<boolean>,
) => {
return promise(request({ url: url, method: 'get', params, responseType: 'blob' }), loading).then(
(res: any) => {
if (res) {
const blob = new Blob([res], {
type: 'application/octet-stream',
})
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = fileName
link.click()
//释放内存
window.URL.revokeObjectURL(link.href)
}
return true
},
)
}
export const exportExcelPost: (
fileName: string,
url: string,
params: any,
data: any,
loading?: NProgress | Ref<boolean>,
) => Promise<any> = (
fileName: string,
url: string,
params: any,
data: any,
loading?: NProgress | Ref<boolean>,
) => {
return promise(
request({
url: url,
method: 'post',
params, // 查询字符串参数
data, // 请求体数据
responseType: 'blob',
}),
loading,
).then((res: any) => {
if (res) {
const blob = new Blob([res], {
type: 'application/vnd.ms-excel',
})
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = fileName
link.click()
// 释放内存
window.URL.revokeObjectURL(link.href)
}
return true
})
}
export const download: (
url: string,
method: string,
data?: any,
params?: any,
loading?: NProgress | Ref<boolean>,
) => Promise<any> = (
url: string,
method: string,
data?: any,
params?: any,
loading?: NProgress | Ref<boolean>,
) => {
return promise(request({ url: url, method: method, data, params, responseType: 'blob' }), loading)
}
/**
* ws链接
* @param url websocket路径
* @returns websocket实例
*/
export const socket = (url: string) => {
let protocol = 'ws://'
if (window.location.protocol === 'https:') {
protocol = 'wss://'
}
let uri = protocol + window.location.host + url
if (!import.meta.env.DEV) {
uri = protocol + window.location.host + import.meta.env.VITE_BASE_PATH + url
}
return new WebSocket(uri)
}
export default instance

View File

@ -1,9 +1,82 @@
import { createRouter, createWebHistory } from 'vue-router'
import { hasPermission } from '@/utils/permission/index'
import NProgress from 'nprogress'
import {
createRouter,
createWebHistory,
type NavigationGuardNext,
type RouteLocationNormalized,
type RouteRecordRaw,
type RouteRecordName,
} from 'vue-router'
import useStore from '@/stores'
import { routes } from '@/router/routes'
NProgress.configure({ showSpinner: false, speed: 500, minimum: 0.3 })
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: routes,
})
// 路由前置拦截器
router.beforeEach(
async (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
NProgress.start()
if (to.name === '404') {
next()
return
}
const { user, login } = useStore()
const notAuthRouteNameList = ['register', 'login', 'forgot_password', 'reset_password', 'Chat']
if (!notAuthRouteNameList.includes(to.name ? to.name.toString() : '')) {
if (to.query && to.query.token) {
localStorage.setItem('token', to.query.token.toString())
}
const token = login.getToken()
if (!token) {
next({
path: '/login',
})
return
}
if (!user.userInfo) {
await user.profile()
}
}
// 判断是否有菜单权限
if (to.meta.permission ? hasPermission(to.meta.permission as any, 'OR') : true) {
next()
} else {
// 如果没有权限则直接取404页面
next('404')
}
},
)
router.afterEach(() => {
NProgress.done()
})
export const getChildRouteListByPathAndName = (path: any, name?: RouteRecordName | any) => {
return getChildRouteList(routes, path, name)
}
export const getChildRouteList: (
routeList: Array<RouteRecordRaw>,
path: string,
name?: RouteRecordName | null | undefined,
) => Array<RouteRecordRaw> = (routeList, path, name) => {
for (let index = 0; index < routeList.length; index++) {
const route = routeList[index]
if (name === route.name && path === route.path) {
return route.children || []
}
if (route.children && route.children.length > 0) {
const result = getChildRouteList(route.children, path, name)
if (result && result?.length > 0) {
return result
}
}
}
return []
}
export default router

View File

@ -1,210 +1,43 @@
import { defineStore } from 'pinia'
import { type Ref } from 'vue'
// import type { User } from '@/api/type/user'
// import { cloneDeep } from 'lodash'
// import UserApi from '@/api/user'
// import ThemeApi from '@/api/theme'
// import { useElementPlusTheme } from 'use-element-plus-theme'
// import { defaultPlatformSetting } from '@/utils/theme'
import { useLocalStorage } from '@vueuse/core'
import { localeConfigKey, getBrowserLang } from '@/locales/index'
export interface userStateTypes {
userType: number // 1 系统操作者 2 对话用户
// userInfo: User | null
token: any
version?: string
userAccessToken?: string
XPACK_LICENSE_IS_VALID: false
isXPack: false
themeInfo: any
}
import loginApi from '@/api/user/login'
import type { LoginRequest } from '@/api/type/login'
import useUserStore from './user'
const useLoginStore = defineStore('login',{
state: (): userStateTypes => ({
userType: 1,
// userInfo: null,
const useLoginStore = defineStore('login', {
state: () => ({
token: '',
version: '',
userAccessToken: '',
XPACK_LICENSE_IS_VALID: false,
isXPack: false,
themeInfo: null
}),
actions: {
// getLanguage() {
// return this.userType === 1
// ? localStorage.getItem('MaxKB-locale') || getBrowserLang()
// : sessionStorage.getItem('language') || getBrowserLang()
// },
// showXpack() {
// return this.isXPack
// },
// isDefaultTheme() {
// return !this.themeInfo?.theme || this.themeInfo?.theme === '#3370FF'
// },
// setTheme(data: any) {
// const { changeTheme } = useElementPlusTheme(this.themeInfo?.theme)
// changeTheme(data?.['theme'])
// this.themeInfo = cloneDeep(data)
// },
// isExpire() {
// return this.isXPack && !this.XPACK_LICENSE_IS_VALID
// },
// isEnterprise() {
// return this.isXPack && this.XPACK_LICENSE_IS_VALID
// },
// getToken(): String | null {
// if (this.token) {
// return this.token
// }
// return this.userType === 1 ? localStorage.getItem('token') : this.getAccessToken()
// },
// getAccessToken() {
// const token = sessionStorage.getItem(`${this.userAccessToken}-accessToken`)
// if (token) {
// return token
// }
// const local_token = localStorage.getItem(`${token}-accessToken`)
// if (local_token) {
// return local_token
// }
// return localStorage.getItem(`accessToken`)
// },
getToken(): String | null {
if (this.token) {
return this.token
}
const user = useUserStore()
return user.userType === 1 ? localStorage.getItem('token') : this.getAccessToken()
},
getAccessToken() {
const token = sessionStorage.getItem(`${this.userAccessToken}-accessToken`)
if (token) {
return token
}
const local_token = localStorage.getItem(`${token}-accessToken`)
if (local_token) {
return local_token
}
return localStorage.getItem(`accessToken`)
},
// getPermissions() {
// if (this.userInfo) {
// return this.isXPack && this.XPACK_LICENSE_IS_VALID
// ? [...this.userInfo?.permissions, 'x-pack']
// : this.userInfo?.permissions
// } else {
// return []
// }
// },
// getRole() {
// if (this.userInfo) {
// return this.userInfo?.role
// } else {
// return ''
// }
// },
// changeUserType(num: number, token?: string) {
// this.userType = num
// this.userAccessToken = token
// },
// async asyncGetProfile() {
// return new Promise((resolve, reject) => {
// UserApi.getProfile()
// .then(async (ok) => {
// this.version = ok.data?.version || '-'
// this.isXPack = ok.data?.IS_XPACK
// this.XPACK_LICENSE_IS_VALID = ok.data?.XPACK_LICENSE_IS_VALID
// if (this.isEnterprise()) {
// await this.theme()
// } else {
// this.themeInfo = {
// ...defaultPlatformSetting
// }
// }
// resolve(ok)
// })
// .catch((error) => {
// reject(error)
// })
// })
// },
// async theme(loading?: Ref<boolean>) {
// return await ThemeApi.getThemeInfo(loading).then((ok) => {
// this.setTheme(ok.data)
// // window.document.title = this.themeInfo['title'] || 'MaxKB'
// // const link = document.querySelector('link[rel="icon"]') as any
// // if (link) {
// // link['href'] = this.themeInfo['icon'] || '/favicon.ico'
// // }
// })
// },
// async profile() {
// return UserApi.profile().then(async (ok) => {
// this.userInfo = ok.data
// useLocalStorage(localeConfigKey, 'en-US').value = ok.data?.language || this.getLanguage()
// return this.asyncGetProfile()
// })
// },
// async login(auth_type: string, username: string, password: string) {
// return UserApi.login(auth_type, { username, password }).then((ok) => {
// this.token = ok.data
// localStorage.setItem('token', ok.data)
// return this.profile()
// })
// },
// async dingCallback(code: string) {
// return UserApi.getDingCallback(code).then((ok) => {
// this.token = ok.data
// localStorage.setItem('token', ok.data)
// return this.profile()
// })
// },
// async dingOauth2Callback(code: string) {
// return UserApi.getDingOauth2Callback(code).then((ok) => {
// this.token = ok.data
// localStorage.setItem('token', ok.data)
// return this.profile()
// })
// },
// async wecomCallback(code: string) {
// return UserApi.getWecomCallback(code).then((ok) => {
// this.token = ok.data
// localStorage.setItem('token', ok.data)
// return this.profile()
// })
// },
// async larkCallback(code: string) {
// return UserApi.getlarkCallback(code).then((ok) => {
// this.token = ok.data
// localStorage.setItem('token', ok.data)
// return this.profile()
// })
// },
// async logout() {
// return UserApi.logout().then(() => {
// localStorage.removeItem('token')
// return true
// })
// },
// async getAuthType() {
// return UserApi.getAuthType().then((ok) => {
// return ok.data
// })
// },
// async getQrType() {
// return UserApi.getQrType().then((ok) => {
// return ok.data
// })
// },
// async getQrSource() {
// return UserApi.getQrSource().then((ok) => {
// return ok.data
// })
// },
// async postUserLanguage(lang: string, loading?: Ref<boolean>) {
// return new Promise((resolve, reject) => {
// UserApi.postLanguage({ language: lang }, loading)
// .then(async (ok) => {
// useLocalStorage(localeConfigKey, 'en-US').value = lang
// window.location.reload()
// resolve(ok)
// })
// .catch((error) => {
// reject(error)
// })
// })
// }
}
async asyncLogin(data: LoginRequest, loading?: Ref<boolean>) {
return loginApi.login(data).then((ok) => {
this.token = ok.data
localStorage.setItem('token', ok.data)
const user = useUserStore()
return user.profile()
})
},
},
})
export default useLoginStore

View File

@ -1,8 +1,8 @@
import { defineStore } from 'pinia'
import { type Ref } from 'vue'
// import type { User } from '@/api/type/user'
import type { User } from '@/api/type/user'
import { cloneDeep } from 'lodash'
// import UserApi from '@/api/user'
import UserApi from '@/api/user/user'
// import ThemeApi from '@/api/theme'
// import { useElementPlusTheme } from 'use-element-plus-theme'
// import { defaultPlatformSetting } from '@/utils/theme'
@ -10,10 +10,8 @@ import { useLocalStorage } from '@vueuse/core'
import { localeConfigKey, getBrowserLang } from '@/locales/index'
export interface userStateTypes {
userType: number // 1 系统操作者 2 对话用户
// userInfo: User | null
token: any
userInfo: User | null
version?: string
userAccessToken?: string
XPACK_LICENSE_IS_VALID: false
isXPack: false
themeInfo: any
@ -21,14 +19,12 @@ export interface userStateTypes {
const useLoginStore = defineStore('user', {
state: (): userStateTypes => ({
userType: 1,
// userInfo: null,
token: '',
userType: 1, // 1 系统操作者 2 对话用户
userInfo: null,
version: '',
userAccessToken: '',
XPACK_LICENSE_IS_VALID: false,
isXPack: false,
themeInfo: null
themeInfo: null,
}),
actions: {
getLanguage() {
@ -36,62 +32,17 @@ const useLoginStore = defineStore('user', {
? localStorage.getItem('MaxKB-locale') || getBrowserLang()
: sessionStorage.getItem('language') || getBrowserLang()
},
// showXpack() {
// return this.isXPack
// },
isDefaultTheme() {
return !this.themeInfo?.theme || this.themeInfo?.theme === '#3370FF'
},
// setTheme(data: any) {
// const { changeTheme } = useElementPlusTheme(this.themeInfo?.theme)
// changeTheme(data?.['theme'])
// this.themeInfo = cloneDeep(data)
// },
// isExpire() {
// return this.isXPack && !this.XPACK_LICENSE_IS_VALID
// },
// isEnterprise() {
// return this.isXPack && this.XPACK_LICENSE_IS_VALID
// },
// getToken(): String | null {
// if (this.token) {
// return this.token
// }
// return this.userType === 1 ? localStorage.getItem('token') : this.getAccessToken()
// },
// getAccessToken() {
// const token = sessionStorage.getItem(`${this.userAccessToken}-accessToken`)
// if (token) {
// return token
// }
// const local_token = localStorage.getItem(`${token}-accessToken`)
// if (local_token) {
// return local_token
// }
// return localStorage.getItem(`accessToken`)
// },
// getPermissions() {
// if (this.userInfo) {
// return this.isXPack && this.XPACK_LICENSE_IS_VALID
// ? [...this.userInfo?.permissions, 'x-pack']
// : this.userInfo?.permissions
// } else {
// return []
// }
// },
// getRole() {
// if (this.userInfo) {
// return this.userInfo?.role
// } else {
// return ''
// }
// },
// changeUserType(num: number, token?: string) {
// this.userType = num
// this.userAccessToken = token
// },
async profile() {
return UserApi.getUserProfile().then((ok: { data: User }) => {
this.userInfo = ok.data
useLocalStorage<string>(localeConfigKey, 'en-US').value =
ok.data?.language || this.getLanguage()
// return this.asyncGetProfile()
})
},
// async asyncGetProfile() {
// return new Promise((resolve, reject) => {
// UserApi.getProfile()
@ -115,6 +66,43 @@ const useLoginStore = defineStore('user', {
// })
// },
getPermissions() {
if (this.userInfo) {
return this.isXPack && this.XPACK_LICENSE_IS_VALID
? [...this.userInfo?.permissions, 'x-pack']
: this.userInfo?.permissions
} else {
return []
}
},
getRole() {
if (this.userInfo) {
return this.userInfo?.role
} else {
return ''
}
},
// showXpack() {
// return this.isXPack
// },
// setTheme(data: any) {
// const { changeTheme } = useElementPlusTheme(this.themeInfo?.theme)
// changeTheme(data?.['theme'])
// this.themeInfo = cloneDeep(data)
// },
// isExpire() {
// return this.isXPack && !this.XPACK_LICENSE_IS_VALID
// },
// isEnterprise() {
// return this.isXPack && this.XPACK_LICENSE_IS_VALID
// },
// changeUserType(num: number, token?: string) {
// this.userType = num
// this.userAccessToken = token
// },
// async theme(loading?: Ref<boolean>) {
// return await ThemeApi.getThemeInfo(loading).then((ok) => {
// this.setTheme(ok.data)
@ -126,21 +114,6 @@ const useLoginStore = defineStore('user', {
// })
// },
// async profile() {
// return UserApi.profile().then(async (ok) => {
// this.userInfo = ok.data
// useLocalStorage(localeConfigKey, 'en-US').value = ok.data?.language || this.getLanguage()
// return this.asyncGetProfile()
// })
// },
// async login(auth_type: string, username: string, password: string) {
// return UserApi.login(auth_type, { username, password }).then((ok) => {
// this.token = ok.data
// localStorage.setItem('token', ok.data)
// return this.profile()
// })
// },
// async dingCallback(code: string) {
// return UserApi.getDingCallback(code).then((ok) => {
// this.token = ok.data
@ -204,7 +177,7 @@ const useLoginStore = defineStore('user', {
// })
// })
// }
}
},
})
export default useLoginStore

View File

@ -2,3 +2,4 @@
@use './element-plus.scss';
@use './variables.scss';
@use './app.scss';
@import 'nprogress/nprogress.css';

61
ui/src/utils/message.ts Normal file
View File

@ -0,0 +1,61 @@
import { ElMessageBox, ElMessage } from 'element-plus'
import { t } from '@/locales'
export const MsgSuccess = (message: string) => {
ElMessage.success({
message: message,
type: 'success',
showClose: true,
duration: 3000
})
}
export const MsgInfo = (message: string) => {
ElMessage.info({
message: message,
type: 'info',
showClose: true,
duration: 3000
})
}
export const MsgWarning = (message: string) => {
ElMessage.warning({
message: message,
type: 'warning',
showClose: true,
duration: 3000
})
}
export const MsgError = (message: string) => {
ElMessage.error({
message: message,
type: 'error',
showClose: true,
duration: 3000
})
}
export const MsgAlert = (title: string, description: string, options?: any) => {
const defaultOptions: Object = {
confirmButtonText: t('common.confirm'),
...options
}
return ElMessageBox.alert(description, title, defaultOptions)
}
/**
*
* @param message: {title, description,type}
*/
export const MsgConfirm = (title: string, description: string, options?: any) => {
const defaultOptions: Object = {
showCancelButton: true,
confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'),
...options
}
return ElMessageBox.confirm(description, title, defaultOptions)
}

View File

@ -0,0 +1,55 @@
import useStore from '@/stores';
import { Role, Permission, ComplexPermission } from '@/utils/permission/type'
/**
*
* @param permission
* @returns True false
*/
const hasPermissionChild = (permission: Role | string | Permission | ComplexPermission) => {
const { user } = useStore();
const permissions = user.getPermissions()
const role = user.getRole()
if (!permission) {
return true
}
if (permission instanceof Role) {
return role === permission.role
}
if (permission instanceof Permission) {
return permissions.includes(permission.permission)
}
if (permission instanceof ComplexPermission) {
const permissionOk = permission.permissionList.some((p) => permissions.includes(p))
const roleOk = permission.roleList.includes(role)
return permission.compare === 'AND' ? permissionOk && roleOk : permissionOk || roleOk
}
if (typeof permission === 'string') {
return permissions.includes(permission)
}
return false
}
/**
*
* @param role
* @param permissions
* @param requiredPermissions
* @returns
*/
export const hasPermission = (
permission:
| Array<Role | string | Permission | ComplexPermission>
| Role
| string
| Permission
| ComplexPermission,
compare: 'OR' | 'AND'
): boolean => {
if (permission instanceof Array) {
return compare === 'OR'
? permission.some((p) => hasPermissionChild(p))
: permission.every((p) => hasPermissionChild(p))
} else {
return hasPermissionChild(permission)
}
}

View File

@ -0,0 +1,36 @@
/**
*
*/
export class Role {
role: string
constructor(role: string) {
this.role = role
}
}
/**
*
*/
export class Permission {
permission: string
constructor(permission: string) {
this.permission = permission
}
}
/**
*
*/
export class ComplexPermission {
roleList: Array<string>
permissionList: Array<string>
compare: 'OR' | 'AND'
constructor(roleList: Array<string>, permissionList: Array<string>, compare: 'OR' | 'AND') {
this.roleList = roleList
this.permissionList = permissionList
this.compare = compare
}
}

View File

@ -2,27 +2,27 @@ import { t } from '@/locales'
export const themeList = [
{
label: t('views.system.theme.default'),
label: t('theme.default'),
value: '#3370FF',
loginBackground: 'default',
},
{
label: t('views.system.theme.orange'),
label: t('theme.orange'),
value: '#FF8800',
loginBackground: 'orange',
},
{
label: t('views.system.theme.green'),
label: t('theme.green'),
value: '#00B69D',
loginBackground: 'green',
},
{
label: t('views.system.theme.purple'),
label: t('theme.purple'),
value: '#7F3BF5',
loginBackground: 'purple',
},
{
label: t('views.system.theme.red'),
label: t('theme.red'),
value: '#F01D94',
loginBackground: 'red',
},
@ -38,7 +38,7 @@ export const defaultSetting = {
loginLogo: '',
loginImage: '',
title: 'MaxKB',
slogan: t('views.system.theme.defaultSlogan'),
slogan: t('theme.defaultSlogan'),
}
export const defaultPlatformSetting = {

View File

@ -1,6 +1,6 @@
<template>
<login-layout>
<LoginContainer :subTitle="$t('views.system.theme.defaultSlogan')">
<LoginContainer :subTitle="$t('theme.defaultSlogan')">
<h2 class="mb-24">{{ $t('views.login.resetPassword') }}</h2>
<el-form
class="reset-password-form"

View File

@ -1,6 +1,6 @@
<template>
<login-layout>
<LoginContainer :subTitle="$t('views.system.theme.defaultSlogan')">
<LoginContainer :subTitle="$t('theme.defaultSlogan')">
<h2 class="mb-24">{{ $t('views.login.resetPassword') }}</h2>
<el-form
class="reset-password-form"

View File

@ -1,172 +0,0 @@
<template>
<div class="VerifyCode">
<canvas
id="VerifyCode-canvas"
:width="props.contentWidth"
:height="props.contentHeight"
@click="refreshCode"
></canvas>
</div>
</template>
<script setup lang="ts">
import { onMounted, watch, computed } from 'vue'
const props = defineProps({
code: {
type: String,
default: '1234',
},
fontSizeMin: {
type: Number,
default: 25,
},
fontSizeMax: {
type: Number,
default: 35,
},
backgroundColorMin: {
type: Number,
default: 255,
},
backgroundColorMax: {
type: Number,
default: 255,
},
colorMin: {
type: Number,
default: 0,
},
colorMax: {
type: Number,
default: 160,
},
lineColorMin: {
type: Number,
default: 40,
},
lineColorMax: {
type: Number,
default: 180,
},
dotColorMin: {
type: Number,
default: 0,
},
dotColorMax: {
type: Number,
default: 255,
},
contentWidth: {
type: Number,
default: 112,
},
contentHeight: {
type: Number,
default: 40,
},
})
//
const emit = defineEmits(['update:code'])
const verifyCode = computed({
get: () => {
return props.code
},
set: (data) => {
emit('update:code', data)
},
})
//
const makeCode = (len = 4) => {
let code = ''
const codeLength = len
const identifyCodes = '123456789abcdefjhijkinpqrsduvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'
for (let i = 0; i < codeLength; i++) {
code += identifyCodes[randomNum(0, identifyCodes.length)]
}
return code
}
//
const randomNum = (min = 0, max: number) => Math.floor(Math.random() * (max - min)) + min
//
function randomColor(min: number, max: number) {
let r = randomNum(min, max)
let g = randomNum(min, max)
let b = randomNum(min, max)
return 'rgb(' + r + ',' + g + ',' + b + ')'
}
// 线
const drawLine = (ctx: CanvasRenderingContext2D) => {
for (let i = 0; i < 5; i++) {
ctx.strokeStyle = randomColor(props.lineColorMin, props.lineColorMax)
ctx.beginPath()
ctx.moveTo(randomNum(0, props.contentWidth), randomNum(0, props.contentHeight))
ctx.lineTo(randomNum(0, props.contentWidth), randomNum(0, props.contentHeight))
ctx.stroke()
}
}
//
const drawText = (ctx: CanvasRenderingContext2D, txt: string, i: number) => {
ctx.fillStyle = randomColor(props.colorMin, props.colorMax)
ctx.font = randomNum(props.fontSizeMin, props.fontSizeMax) + 'px SimHei'
let x = (i + 1) * (props.contentWidth / (txt.length + 1))
let y = randomNum(props.fontSizeMax, props.contentHeight - 5)
var deg = randomNum(-45, 45)
//
ctx.translate(x, y)
ctx.rotate((deg * Math.PI) / 180)
ctx.fillText(txt[i], 0, 0)
//
ctx.rotate((-deg * Math.PI) / 180)
ctx.translate(-x, -y)
}
//
const drawDot = (ctx: CanvasRenderingContext2D) => {
for (let i = 0; i < 80; i++) {
ctx.fillStyle = randomColor(0, 255)
ctx.beginPath()
ctx.arc(randomNum(0, props.contentWidth), randomNum(0, props.contentHeight), 1, 0, 2 * Math.PI)
ctx.fill()
}
}
//
const drawPic = () => {
let canvas = document.getElementById('VerifyCode-canvas') as HTMLCanvasElement
if (!canvas) {
return
}
let ctx = canvas.getContext('2d') as CanvasRenderingContext2D
ctx.textBaseline = 'bottom'
//
ctx.fillStyle = randomColor(props.backgroundColorMin, props.backgroundColorMax)
ctx.fillRect(0, 0, props.contentWidth, props.contentHeight)
//
for (let i = 0; i < verifyCode.value.length; i++) {
drawText(ctx, verifyCode.value, i)
}
drawLine(ctx)
drawDot(ctx)
}
//
const refreshCode = () => {
emit('update:code', makeCode())
drawPic()
}
// defineExpose({ refreshCode });
//
onMounted(() => {
drawPic()
})
</script>
<style scoped lang="scss">
.VerifyCode {
cursor: pointer;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<login-layout v-if="!loading" v-loading="loading">
<LoginContainer :subTitle="user.themeInfo?.slogan || $t('views.system.theme.defaultSlogan')">
<LoginContainer :subTitle="user.themeInfo?.slogan || $t('theme.defaultSlogan')">
<h2 class="mb-24">{{ $t('views.login.title') }}</h2>
<div>
<el-form
@ -16,7 +16,7 @@
size="large"
class="input-item"
v-model="loginForm.username"
:placeholder="$t('views.user.userForm.form.username.placeholder')"
:placeholder="$t('views.login.loginForm.username.placeholder')"
>
</el-input>
</el-form-item>
@ -28,36 +28,41 @@
size="large"
class="input-item"
v-model="loginForm.password"
:placeholder="$t('views.user.userForm.form.password.placeholder')"
:placeholder="$t('views.login.loginForm.password.placeholder')"
show-password
>
</el-input>
</el-form-item>
</div>
<div class="mb-24">
<el-form-item prop="code">
<el-form-item prop="captcha">
<div class="flex-between w-full">
<el-input
size="large"
class="input-item"
v-model="loginForm.code"
placeholder="请输入验证码"
v-model="loginForm.captcha"
:placeholder="$t('views.login.loginForm.captcha.placeholder')"
>
</el-input>
<VerifyCode v-model:code="identifyCode" />
<img :src="identifyCode" alt="" height="40" class="ml-8 cursor" @click="makeCode" />
</div>
</el-form-item>
</div>
</el-form>
<el-button size="large" type="primary" class="w-full" @click="login"
<el-button
size="large"
type="primary"
class="w-full"
@click="loginHandle"
:loading="loading"
>
>{{ $t('views.login.buttons.login') }}
</el-button>
<div class="operate-container flex-between mt-12">
<!-- <el-button class="register" @click="router.push('/register')" link type="primary">
注册
</el-button> -->
<el-button
:loading="loading"
class="forgot-password"
@click="router.push('/forgot_password')"
link
@ -77,67 +82,64 @@ import type { FormInstance, FormRules } from 'element-plus'
import type { LoginRequest } from '@/api/type/login'
import LoginContainer from '@/views/login/components/LoginContainer.vue'
import LoginLayout from '@/views/login/components/LoginLayout.vue'
import VerifyCode from './components/VerifyCode.vue'
import loginApi from '@/api/user/login'
import { t, getBrowserLang } from '@/locales'
import useStore from '@/stores'
import { useI18n } from 'vue-i18n'
const router = useRouter()
const { user } = useStore()
// const { locale } = useI18n({ useScope: 'global' })
const { login, user } = useStore()
const { locale } = useI18n({ useScope: 'global' })
const loading = ref<boolean>(false)
const identifyCode = ref<string>('1234')
const identifyCode = ref<string>('')
const loginFormRef = ref<FormInstance>()
const loginForm = ref<LoginRequest>({
username: '',
password: '',
code: '',
captcha: '',
})
const rules = ref<FormRules<LoginRequest>>({
username: [
{
required: true,
message: t('views.user.userForm.form.username.requiredMessage'),
message: t('views.login.loginForm.username.requiredMessage'),
trigger: 'blur',
},
],
password: [
{
required: true,
message: t('views.user.userForm.form.password.requiredMessage'),
message: t('views.login.loginForm.password.requiredMessage'),
trigger: 'blur',
},
],
captcha: [
{
required: true,
message: t('views.login.loginForm.captcha.requiredMessage'),
trigger: 'blur',
},
],
})
// const modeList = ref<string[]>([''])
// const QrList = ref<any[]>([''])
// const loginMode = ref('')
// const showQrCodeTab = ref(false)
// interface qrOption {
// key: string
// value: string
// }
// const orgOptions = ref<qrOption[]>([])
const login = () => {
// loginFormRef.value?.validate().then(() => {
// loading.value = true
// user
// .login(loginMode.value, loginForm.value.username, loginForm.value.password)
// .then(() => {
// locale.value = localStorage.getItem('MaxKB-locale') || getBrowserLang() || 'en-US'
// router.push({ name: 'home' })
// })
// .finally(() => (loading.value = false))
// })
const loginHandle = () => {
loginFormRef.value?.validate().then(() => {
login.asyncLogin(loginForm.value, loading).then(() => {
locale.value = localStorage.getItem('MaxKB-locale') || getBrowserLang() || 'en-US'
router.push({ name: 'home' })
})
})
}
function makeCode() {
loginApi.getCaptcha().then((res: any) => {
identifyCode.value = res.data.captcha
})
}
onBeforeMount(() => {
// loading.value = true
makeCode()
})
onMounted(() => {})