From 0c96b4d1304cc15275d7af235df7ca48e7b6933a Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Sat, 23 Nov 2024 11:20:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=AE=9E=E8=AE=AD?= =?UTF-8?q?=E6=95=99=E5=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- practical_training/README.md | 2 +- src/i18n/locales/en-US.ts | 41 +- src/i18n/locales/zh-CN.ts | 71 +- .../configManage/configParamApply/index.vue | 713 ++++++++++ .../components/OpeateDrawer.vue | 327 +++++ .../hooks/useConfigArray.ts | 342 +++++ .../hooks/useConfigArrayChild.ts | 349 +++++ .../hooks/useConfigList.ts | 139 ++ .../configParamTreeTable/hooks/useOptions.ts | 192 +++ .../hooks/usePtOptions.ts | 228 +++ .../configParamTreeTable/index.vue | 1249 +++++++++++++++++ src/views/dashboard/amfUE/index.vue | 4 +- src/views/dashboard/imsCDR/index.vue | 4 +- src/views/dashboard/mmeUE/index.vue | 4 +- src/views/dashboard/smfCDR/index.vue | 4 +- .../monitor/topologyArchitecture/index.vue | 18 +- src/views/ne/neInfo/index.vue | 65 +- src/views/plugins/auth-user.ts | 66 + src/views/store/modules/user.ts | 171 +++ 19 files changed, 3909 insertions(+), 80 deletions(-) create mode 100644 src/views/configManage/configParamApply/index.vue create mode 100644 src/views/configManage/configParamTreeTable/components/OpeateDrawer.vue create mode 100644 src/views/configManage/configParamTreeTable/hooks/useConfigArray.ts create mode 100644 src/views/configManage/configParamTreeTable/hooks/useConfigArrayChild.ts create mode 100644 src/views/configManage/configParamTreeTable/hooks/useConfigList.ts create mode 100644 src/views/configManage/configParamTreeTable/hooks/useOptions.ts create mode 100644 src/views/configManage/configParamTreeTable/hooks/usePtOptions.ts create mode 100644 src/views/configManage/configParamTreeTable/index.vue create mode 100644 src/views/plugins/auth-user.ts create mode 100644 src/views/store/modules/user.ts diff --git a/practical_training/README.md b/practical_training/README.md index fd9e3e34..0a16334f 100644 --- a/practical_training/README.md +++ b/practical_training/README.md @@ -4,7 +4,7 @@ ## 静态资源 -将目录下文件放置到对应目录 +将目录下文件放置到对应目录 替换约18个文件 - i18n 对应覆盖 src\i18n - views 对应覆盖 src\views diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts index 8df6dc8e..3cf352c9 100644 --- a/src/i18n/locales/en-US.ts +++ b/src/i18n/locales/en-US.ts @@ -341,7 +341,7 @@ export default { profile: { phonenumber: "Phone", email: "Email", - deptName: "Department", + deptName: "Class", postGroup: "Possession of posts", roleGroup: "Ownership", loginIp: "Log in", @@ -506,6 +506,23 @@ export default { updateItemTip: "Confirm updating the data item with Index [{num}]?", delItemTip: "Confirm deleting the data item with Index [{num}]?", arrayMore: "Expand", + ptDiff: 'Comparison Example', + ptDiffExample: 'Example Configuration', + ptDiffSelf: 'Current Individuals', + ptDiffLoad: 'Load More', + ptDiffMerge: 'Comparative Differences', + ptDiffRest: 'Restore this version', + ptHistory: 'History', + ptReset: 'Reset To Example', + ptLoad: 'Load Current Configuration', + ptExport: "Export Excel", + ptExportTip: "Exporting NE Configuration Data to an Excel file", + ptApplyShow: 'View Student', + ptApply: 'request', + ptApplyNE: 'Application To NE', + ptApplyStu: 'Application To {ne}', + ptApplyStuRack: 'Return Request', + ptApplyStuNE: 'Application Request', }, }, dashboard: { @@ -1708,7 +1725,7 @@ export default { account: 'Account', userName: 'Nick Name', permission: 'Role', - className: 'Department', + className: 'Class', loginIp: 'Login Address', loginTime: 'Login Time', status: 'Status', @@ -1733,7 +1750,7 @@ export default { userTop:'User profile', sex:'User Gender', email:'E-mail', - fromClass:'Department', + fromClass:'Class', userWork:'User position', userWorkPlease: 'Please select user post', userTip:'User Description', @@ -1834,8 +1851,8 @@ export default { role:{ allScopeOptions:'All data permissions', byMyselfScopeOptions:'Custom data permissions', - onlyClassScopeOptions:'Data permissions of this department', - classAllScopeOptions:'Data permissions for this department and the following', + onlyClassScopeOptions:'Data permissions of this class', + classAllScopeOptions:'Data permissions for this class and the following', myselfScopeOptions:'Only personal data permissions', roleId:'Role Number', roleName:'Role Name', @@ -1876,20 +1893,20 @@ export default { batchCancel:'Batch cancellation of authorization', }, dept:{ - classInfo:' Department Information', - className:'Department Name', - classId:'Department Number', - classSort:'Department Sorting', - status:'Department Status', + classInfo:' Class Information', + className:'Class Name', + classId:'Class Number', + classSort:'Class Sorting', + status:'Class Status', createTime:'Creation Time', highClass:'Higher Office', emailTip:'Please input the correct email address', phoneTip:'Please enter the correct phone number', node:'Root Node', - delSure:'Are you sure to delete the data item with department number [{deptId}]?', + delSure:'Are you sure to delete the data item with class number [{deptId}]?', open:'Exhibition', close:'Fold', - addClass:'Add new sub-department', + addClass:'Add new sub-class', admin:'Principal', phone:'Contact Number', email:'Mail', diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 55f1b37a..0a3a3561 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -341,8 +341,8 @@ export default { profile: { phonenumber: "手机号码", email: "用户邮箱", - deptName: "所属部门", - postGroup: "拥有岗位", + deptName: "所属班级", + postGroup: "拥有职位", roleGroup: "拥有角色", loginIp: "登录地址", loginDate: "登录时间", @@ -506,6 +506,23 @@ export default { updateItemTip: "确认更新Index为 【{num}】 的数据项?", delItemTip: "确认删除Index为 【{num}】 的数据项?", arrayMore: "展开", + ptDiff: '对比示例', + ptDiffExample: '示例配置', + ptDiffSelf: '当前个人', + ptDiffLoad: '加载更多', + ptDiffMerge: '差异对比', + ptDiffRest: '还原此版本', + ptHistory: '历史记录', + ptReset: '重置为示例', + ptLoad: '载入当前网元配置', + ptExport: "导出Excel", + ptExportTip: "导出网元配置数据到Excel文件中", + ptApplyShow: '查看学生', + ptApply: '申请', + ptApplyNE: '应用配置到网元', + ptApplyStu: '申请配置应用到 {ne}', + ptApplyStuRack: '退回该学生配置', + ptApplyStuNE: '应用该学生配置', }, }, dashboard: { @@ -1296,7 +1313,7 @@ export default { type:'网元类型', neId:'网元唯一标识', MML:'MML', - logTime:'log Time' + logTime:'记录时间', }, forwarding:{ type:'网元类型', @@ -1708,7 +1725,7 @@ export default { account: '登录账号', userName: '用户昵称', permission: '用户权限', - className: '部门名称', + className: '班级名称', loginIp: '登录地址', loginTime: '登录时间', status: '用户状态', @@ -1733,9 +1750,9 @@ export default { userTop:'用户头像', sex:'用户性别', email:'电子邮箱', - fromClass:'所属部门', - userWork:'用户岗位', - userWorkPlease: '请选择用户岗位', + fromClass:'所属班级', + userWork:'用户职位', + userWorkPlease: '请选择用户职位', userTip:'用户说明', loginPwd:'登录密码', updateSure:'是否更新已经存在的数据', @@ -1834,8 +1851,8 @@ export default { role:{ allScopeOptions:'全部数据权限', byMyselfScopeOptions:'自定数据权限', - onlyClassScopeOptions:'本部门数据权限', - classAllScopeOptions:'本部门及以下数据权限', + onlyClassScopeOptions:'本班级数据权限', + classAllScopeOptions:'本班级及以下数据权限', myselfScopeOptions:'仅本人数据权限', roleId:'角色编号', roleName:'角色名称', @@ -1876,36 +1893,36 @@ export default { batchCancel:'批量取消授权', }, dept:{ - classInfo:'部门信息', - className:'部门名称', - classId:'部门编号', - classSort:'部门排序', - status:'部门状态', + classInfo:'班级信息', + className:'班级名称', + classId:'班级编号', + classSort:'班级排序', + status:'班级状态', createTime:'创建时间', - highClass:'上级部门', + highClass:'上级班级', emailTip:'请输入正确的邮箱地址', phoneTip:'请输入正确的手机号码', node:'根节点', - delSure:'确认删除部门编号为 【{deptId}】 的数据项?', + delSure:'确认删除班级编号为 【{deptId}】 的数据项?', open:'展', close:'折', - addClass:'新增子部门', + addClass:'新增子班级', admin:'负责人', phone:'联系电话', email:'邮箱', }, post:{ - positionInfo:'岗位信息', - positionId:'岗位编号', - positionCode:'岗位编码', - positionName:'岗位名称', - positionSort:'岗位排序', - positionStatus:'岗位状态', - positionMark:'岗位说明', + positionInfo:'职位信息', + positionId:'职位编号', + positionCode:'职位编码', + positionName:'职位名称', + positionSort:'职位排序', + positionStatus:'职位状态', + positionMark:'职位说明', createTime:'创建时间', - codeTip:'请正确输入岗位编码', - nameTip:'请正确输入岗位名称', - delSure:'确认删除岗位编号为 【{postId}】 的数据项?', + codeTip:'请正确输入职位编码', + nameTip:'请正确输入职位名称', + delSure:'确认删除职位编号为 【{postId}】 的数据项?', }, log:{ operate:{ diff --git a/src/views/configManage/configParamApply/index.vue b/src/views/configManage/configParamApply/index.vue new file mode 100644 index 00000000..75cfa32b --- /dev/null +++ b/src/views/configManage/configParamApply/index.vue @@ -0,0 +1,713 @@ + + + + + + + + + + + + + + + + + + + + + + + + + {{ t('common.search') }} + + + + {{ t('common.reset') }} + + + + + + + + + + + + + + + 批量退回 + + + + + + + + + {{ t('common.searchBarText') }} + + + + {{ t('common.reloadText') }} + + + + + + {{ t('common.sizeText') }} + + + + + + + + {{ t('common.size.default') }} + + + {{ t('common.size.middle') }} + + + {{ t('common.size.small') }} + + + + + + + + + + + + + + + + + + {{ t('common.viewText') }} + + + + + + 撤回 + + + + + + {{ t('common.editText') }} + + + + + + + + + + + + + + + + + {{ modalState.from.neType }} + + + + + + + + + + + + + {{ modalState.from.createBy }} + + + + + {{ parseDateToStr(+modalState.from.createTime) }} + + + + + + + {{ modalState.from.updateBy }} + + + + + {{ parseDateToStr(+modalState.from.updateTime) }} + + + + + + {{ modalState.from.backInfo }} + + + + + {{ t('common.close') }} + + + + + + + + + + + {{ modalState.from.neType }} + + + + + + + + {{ modalState.from.createBy }} + + + + + {{ parseDateToStr(+modalState.from.createTime) }} + + + + + + + + + + + + + + + + + + diff --git a/src/views/configManage/configParamTreeTable/components/OpeateDrawer.vue b/src/views/configManage/configParamTreeTable/components/OpeateDrawer.vue new file mode 100644 index 00000000..0bce5ec6 --- /dev/null +++ b/src/views/configManage/configParamTreeTable/components/OpeateDrawer.vue @@ -0,0 +1,327 @@ + + + + + + + + + + {{ t('views.configManage.configParamForm.ptDiffLoad') }} + + + + + + + + + {{ t('views.configManage.configParamForm.ptDiffMerge') }} + + + + + + + + + + + + {{ parseDateToStr(item.createTime) }} + + + + + + + + + + + {{ parseDateToStr(state.dataDiff.createTime) }} + + + + + + {{ t('views.configManage.configParamForm.ptDiffRest') }} + + + + + + {{ t('views.configManage.configParamForm.ptDiffRest') }} + + + + + + + + + diff --git a/src/views/configManage/configParamTreeTable/hooks/useConfigArray.ts b/src/views/configManage/configParamTreeTable/hooks/useConfigArray.ts new file mode 100644 index 00000000..037ff139 --- /dev/null +++ b/src/views/configManage/configParamTreeTable/hooks/useConfigArray.ts @@ -0,0 +1,342 @@ +import { + addPtNeConfigData, + delPtNeConfigData, + editPtNeConfigData, +} from '@/api/pt/neConfig'; +import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; +import { Modal } from 'ant-design-vue/lib'; +import { SizeType } from 'ant-design-vue/lib/config-provider'; +import message from 'ant-design-vue/lib/message'; +import { reactive, watch } from 'vue'; + +/** + * 参数配置array类型 + * @param param 父级传入 { t, treeState, fnActiveConfigNode, ruleVerification, modalState, fnModalCancel} + * @returns + */ +export default function useConfigArray({ + t, + treeState, + fnActiveConfigNode, + ruleVerification, + modalState, + fnModalCancel, +}: any) { + /**多列列表状态类型 */ + type ArrayStateType = { + /**紧凑型 */ + size: SizeType; + /**多列嵌套记录字段 */ + columns: Record[]; + /**表格字段列排序 */ + columnsDnd: Record[]; + /**多列记录数据 */ + columnsData: Record[]; + /**多列嵌套展开key */ + arrayChildExpandKeys: any[]; + + /**多列记录数据 */ + data: Record[]; + /**多列记录规则 */ + dataRule: Record; + }; + + /**多列列表状态 */ + let arrayState: ArrayStateType = reactive({ + size: 'small', + columns: [], + columnsDnd: [], + columnsData: [], + arrayChildExpandKeys: [], + data: [], + dataRule: {}, + }); + + /**多列表编辑 */ + function arrayEdit(rowIndex: Record) { + const item = arrayState.data.find((s: any) => s.key === rowIndex.value); + if (!item) return; + const from = arrayInitEdit(item, arrayState.dataRule); + // 处理信息 + const row: Record = {}; + for (const v of from.record) { + if (Array.isArray(v.array)) { + continue; + } + row[v.name] = Object.assign({}, v); + } + + modalState.from = row; + modalState.type = 'arrayEdit'; + modalState.title = `${treeState.selectNode.paramDisplay} ${from.title}`; + modalState.key = from.key; + modalState.data = from.record.filter((v: any) => !Array.isArray(v.array)); + modalState.open = true; + + // 关闭嵌套 + arrayState.arrayChildExpandKeys = []; + } + + /**多列表编辑关闭 */ + function arrayEditClose() { + arrayState.arrayChildExpandKeys = []; + fnModalCancel(); + } + + /**多列表编辑确认 */ + function arrayEditOk(from: Record) { + // 遍历提取属性和值 + let data: Record = {}; + for (const key in from) { + // 子嵌套的不插入 + if (from[key]['array']) { + continue; + } + // 检查规则 + const [ok, msg] = ruleVerification(from[key]); + if (!ok) { + message.warning({ + content: `${msg}`, + duration: 3, + }); + return; + } + data[key] = from[key]['value']; + } + + // 发送 + const hide = message.loading(t('common.loading'), 0); + editPtNeConfigData({ + neType: treeState.neType, + paramName: treeState.selectNode.paramName, + paramData: data, + loc: `${from['index']['value']}`, + }) + .then(res => { + if (res.code === RESULT_CODE_SUCCESS) { + message.success({ + content: t('views.configManage.configParamForm.updateItem', { + num: modalState.title, + }), + duration: 3, + }); + fnActiveConfigNode('#'); + } else { + message.warning({ + content: t('views.configManage.configParamForm.updateItemErr'), + duration: 3, + }); + } + }) + .finally(() => { + hide(); + arrayEditClose(); + }); + } + + /**多列表删除单行 */ + function arrayDelete(rowIndex: Record) { + const loc = `${rowIndex.value}`; + const title = `${treeState.selectNode.paramDisplay} Index-${loc}`; + + Modal.confirm({ + title: t('common.tipTitle'), + content: t('views.configManage.configParamForm.delItemTip', { + num: title, + }), + onOk() { + delPtNeConfigData({ + neType: treeState.neType, + paramName: treeState.selectNode.paramName, + loc: loc, + }).then(res => { + if (res.code === RESULT_CODE_SUCCESS) { + message.success({ + content: t('views.configManage.configParamForm.delItemOk', { + num: title, + }), + duration: 2, + }); + arrayEditClose(); + fnActiveConfigNode('#'); + } else { + message.error({ + content: `${res.msg}`, + duration: 2, + }); + } + }); + }, + }); + } + + /**多列表新增单行 */ + function arrayAdd() { + const from = arrayInitAdd(arrayState.data, arrayState.dataRule); + + // 处理信息 + const row: Record = {}; + for (const v of from.record) { + if (Array.isArray(v.array)) { + continue; + } + row[v.name] = Object.assign({}, v); + } + + modalState.from = row; + modalState.type = 'arrayAdd'; + modalState.title = `${treeState.selectNode.paramDisplay} ${from.title}`; + modalState.key = from.key; + modalState.data = from.record.filter((v: any) => !Array.isArray(v.array)); + modalState.open = true; + } + + /**多列表新增单行确认 */ + function arrayAddOk(from: Record) { + // 遍历提取属性和值 + let data: Record = {}; + for (const key in from) { + // 子嵌套的不插入 + if (from[key]['array']) { + continue; + } + // 检查规则 + const [ok, msg] = ruleVerification(from[key]); + if (!ok) { + message.warning({ + content: `${msg}`, + duration: 3, + }); + return; + } + data[key] = from[key]['value']; + } + + // 发送 + const hide = message.loading(t('common.loading'), 0); + addPtNeConfigData({ + neType: treeState.neType, + paramName: treeState.selectNode.paramName, + paramData: data, + loc: `${from['index']['value']}`, + }) + .then(res => { + if (res.code === RESULT_CODE_SUCCESS) { + message.success({ + content: t('views.configManage.configParamForm.addItemOk', { + num: modalState.title, + }), + duration: 3, + }); + fnActiveConfigNode('#'); + } else { + message.warning({ + content: t('views.configManage.configParamForm.addItemErr'), + duration: 3, + }); + } + }) + .finally(() => { + hide(); + arrayEditClose(); + }); + } + + /**多列表编辑行数据初始化 */ + function arrayInitEdit(data: Record, dataRule: any) { + const dataFrom = data.record; + const ruleFrom = Object.assign({}, JSON.parse(JSON.stringify(dataRule))); + for (const row of ruleFrom.record) { + // 子嵌套的不初始 + if (row.array) { + row.value = []; + continue; + } + // 查找项的值 + const item = dataFrom.find((s: any) => s.name === row.name); + if (!item) { + continue; + } + // 可选的 + row.optional = 'true'; + // 根据规则类型转值 + if (['enum', 'int'].includes(row.type)) { + row.value = Number(item.value); + } else if ('bool' === row.type) { + row.value = Boolean(item.value); + } else { + row.value = item.value; + } + } + ruleFrom.key = data.key; + ruleFrom.title = data.title; + return ruleFrom; + } + + /**多列表新增行数据初始化 */ + function arrayInitAdd(data: any[], dataRule: any) { + // 有数据时取得最后的index + let dataLastIndex = 0; + if (data.length !== 0) { + const lastFrom = Object.assign( + {}, + JSON.parse(JSON.stringify(data.at(-1))) + ); + if (lastFrom.record.length > 0) { + dataLastIndex = parseInt(lastFrom.key); + dataLastIndex += 1; + } + } + + const ruleFrom = Object.assign({}, JSON.parse(JSON.stringify(dataRule))); + for (const row of ruleFrom.record) { + // 子嵌套的不初始 + if (row.array) { + row.value = []; + continue; + } + // 可选的 + row.optional = 'true'; + // index值 + if (row.name === 'index') { + let newIndex = + dataLastIndex !== 0 ? dataLastIndex : parseInt(row.value); + if (isNaN(newIndex)) { + newIndex = 0; + } + row.value = newIndex; + ruleFrom.key = newIndex; + ruleFrom.title = `Index-${newIndex}`; + continue; + } + // 根据规则类型转值 + if (['enum', 'int'].includes(row.type)) { + row.value = Number(row.value); + } + if ('bool' === row.type) { + row.value = Boolean(row.value); + } + } + return ruleFrom; + } + + // 监听表格字段列排序变化关闭展开 + watch( + () => arrayState.columnsDnd, + () => { + arrayEditClose(); + } + ); + + return { + arrayState, + arrayEdit, + arrayEditClose, + arrayEditOk, + arrayDelete, + arrayAdd, + arrayAddOk, + arrayInitEdit, + arrayInitAdd, + }; +} diff --git a/src/views/configManage/configParamTreeTable/hooks/useConfigArrayChild.ts b/src/views/configManage/configParamTreeTable/hooks/useConfigArrayChild.ts new file mode 100644 index 00000000..ba61b9a9 --- /dev/null +++ b/src/views/configManage/configParamTreeTable/hooks/useConfigArrayChild.ts @@ -0,0 +1,349 @@ +import { + addPtNeConfigData, + delPtNeConfigData, + editPtNeConfigData, +} from '@/api/pt/neConfig'; +import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; +import { Modal } from 'ant-design-vue/lib'; +import { SizeType } from 'ant-design-vue/lib/config-provider'; +import message from 'ant-design-vue/lib/message'; +import { nextTick, reactive } from 'vue'; + +/** + * 参数配置array类型的嵌套array + * @param param 父级传入 { t, treeState, fnActiveConfigNode, ruleVerification, modalState, arrayState, arrayInitEdit, arrayInitAdd, arrayEditClose} + * @returns + */ +export default function useConfigArrayChild({ + t, + treeState, + fnActiveConfigNode, + ruleVerification, + modalState, + arrayState, + arrayInitEdit, + arrayInitAdd, + arrayEditClose, +}: any) { + /**多列嵌套列表状态类型 */ + type ArrayChildStateType = { + /**标题 */ + title: string; + /**层级index */ + loc: string; + /**紧凑型 */ + size: SizeType; + /**多列嵌套记录字段 */ + columns: Record[]; + /**表格字段列排序 */ + columnsDnd: Record[]; + /**多列记录数据 */ + columnsData: Record[]; + + /**多列嵌套记录数据 */ + data: Record[]; + /**多列嵌套记录规则 */ + dataRule: Record; + }; + + /**多列嵌套表格状态 */ + let arrayChildState: ArrayChildStateType = reactive({ + title: '', + loc: '', + size: 'small', + columns: [], + columnsDnd: [], + columnsData: [], + data: [], + dataRule: {}, + }); + + /**多列表展开嵌套行 */ + function arrayChildExpand( + indexRow: Record, + row: Record + ) { + const loc = indexRow.value; + if (arrayChildState.loc === `${loc}/${row.name}`) { + arrayChildState.loc = ''; + arrayState.arrayChildExpandKeys = []; + return; + } + arrayChildState.loc = ''; + arrayState.arrayChildExpandKeys = []; + const from = Object.assign({}, JSON.parse(JSON.stringify(row))); + // 无数据时 + if (!Array.isArray(from.value)) { + from.value = []; + } + const dataArr = Object.freeze(from.value); + const ruleArr = Object.freeze(from.array); + + // 列表项数据 + const dataArray: Record[] = []; + for (const item of dataArr) { + const index = item['index']; + let record: Record[] = []; + for (const key of Object.keys(item)) { + // 规则为准 + for (const rule of ruleArr) { + if (rule['name'] === key) { + const ruleItem = Object.assign({ optional: 'true' }, rule, { + value: item[key], + }); + record.push(ruleItem); + break; + } + } + } + // dataArray.push(record); + dataArray.push({ title: `Index-${index}`, key: index, record }); + } + arrayChildState.data = dataArray; + + // 无数据时,用于新增 + arrayChildState.dataRule = { + title: `Index-0`, + key: 0, + record: ruleArr, + }; + + // 列表数据 + const columnsData: Record[] = []; + for (const v of arrayChildState.data) { + const row: Record = {}; + for (const item of v.record) { + row[item.name] = item; + } + columnsData.push(row); + } + arrayChildState.columnsData = columnsData; + + // 列表字段 + const columns: Record[] = []; + for (const rule of arrayChildState.dataRule.record) { + columns.push({ + title: rule.display, + dataIndex: rule.name, + align: 'left', + resizable: true, + width: 50, + minWidth: 50, + maxWidth: 250, + }); + } + columns.push({ + title: t('common.operate'), + dataIndex: 'index', + key: 'index', + align: 'center', + fixed: 'right', + width: 100, + }); + arrayChildState.columns = columns; + + nextTick(() => { + // 设置展开key + arrayState.arrayChildExpandKeys = [indexRow]; + // 层级标识 + arrayChildState.loc = `${loc}/${from['name']}`; + // 设置展开列表标题 + arrayChildState.title = `${from['display']}`; + }); + } + + /**多列表嵌套行编辑 */ + function arrayChildEdit(rowIndex: Record) { + const item = arrayChildState.data.find( + (s: any) => s.key === rowIndex.value + ); + if (!item) return; + const from = arrayInitEdit(item, arrayChildState.dataRule); + // 处理信息 + const row: Record = {}; + for (const v of from.record) { + if (Array.isArray(v.array)) { + continue; + } + row[v.name] = Object.assign({}, v); + } + + modalState.from = row; + modalState.type = 'arrayChildEdit'; + modalState.title = `${arrayChildState.title} ${from.title}`; + modalState.key = from.key; + modalState.data = from.record.filter((v: any) => !Array.isArray(v.array)); + modalState.open = true; + } + + /**多列表嵌套行编辑确认 */ + function arrayChildEditOk(from: Record) { + const loc = `${arrayChildState.loc}/${from['index']['value']}`; + + let data: Record = {}; + for (const key in from) { + // 子嵌套的不插入 + if (from[key]['array']) { + continue; + } + // 检查规则 + const [ok, msg] = ruleVerification(from[key]); + if (!ok) { + message.warning({ + content: `${msg}`, + duration: 3, + }); + return; + } + data[key] = from[key]['value']; + } + + // 发送 + const hide = message.loading(t('common.loading'), 0); + editPtNeConfigData({ + neType: treeState.neType, + paramName: treeState.selectNode.paramName, + paramData: data, + loc, + }) + .then(res => { + if (res.code === RESULT_CODE_SUCCESS) { + message.success({ + content: t('views.configManage.configParamForm.updateItem', { + num: modalState.title, + }), + duration: 3, + }); + fnActiveConfigNode('#'); + } else { + message.warning({ + content: t('views.configManage.configParamForm.updateItemErr'), + duration: 3, + }); + } + }) + .finally(() => { + hide(); + arrayEditClose(); + }); + } + + /**多列表嵌套行删除单行 */ + function arrayChildDelete(rowIndex: Record) { + const index = rowIndex.value; + const loc = `${arrayChildState.loc}/${index}`; + const title = `${arrayChildState.title} Index-${index}`; + + Modal.confirm({ + title: t('common.tipTitle'), + content: t('views.configManage.configParamForm.delItemTip', { + num: title, + }), + onOk() { + delPtNeConfigData({ + neType: treeState.neType, + paramName: treeState.selectNode.paramName, + loc, + }).then(res => { + if (res.code === RESULT_CODE_SUCCESS) { + message.success({ + content: t('views.configManage.configParamForm.delItemOk', { + num: title, + }), + duration: 2, + }); + arrayEditClose(); + fnActiveConfigNode('#'); + } else { + message.error({ + content: `${res.msg}`, + duration: 2, + }); + } + }); + }, + }); + } + + /**多列表嵌套行新增单行 */ + function arrayChildAdd() { + const from = arrayInitAdd(arrayChildState.data, arrayChildState.dataRule); + // 处理信息 + const row: Record = {}; + for (const v of from.record) { + if (Array.isArray(v.array)) { + continue; + } + row[v.name] = Object.assign({}, v); + } + + modalState.from = row; + modalState.type = 'arrayChildAdd'; + modalState.title = `${arrayChildState.title} ${from.title}`; + modalState.key = from.key; + modalState.data = from.record.filter((v: any) => !Array.isArray(v.array)); + modalState.open = true; + } + + /**多列表新增单行确认 */ + function arrayChildAddOk(from: Record) { + const loc = `${arrayChildState.loc}/${from['index']['value']}`; + + let data: Record = {}; + for (const key in from) { + // 子嵌套的不插入 + if (from[key]['array']) { + continue; + } + // 检查规则 + const [ok, msg] = ruleVerification(from[key]); + if (!ok) { + message.warning({ + content: `${msg}`, + duration: 3, + }); + return; + } + data[key] = from[key]['value']; + } + + // 发送 + const hide = message.loading(t('common.loading'), 0); + addPtNeConfigData({ + neType: treeState.neType, + paramName: treeState.selectNode.paramName, + paramData: data, + loc, + }) + .then(res => { + if (res.code === RESULT_CODE_SUCCESS) { + message.success({ + content: t('views.configManage.configParamForm.addItemOk', { + num: modalState.title, + }), + duration: 3, + }); + fnActiveConfigNode('#'); + } else { + message.warning({ + content: t('views.configManage.configParamForm.addItemErr'), + duration: 3, + }); + } + }) + .finally(() => { + hide(); + arrayEditClose(); + }); + } + + return { + arrayChildState, + arrayChildExpand, + arrayChildEdit, + arrayChildEditOk, + arrayChildDelete, + arrayChildAdd, + arrayChildAddOk, + }; +} diff --git a/src/views/configManage/configParamTreeTable/hooks/useConfigList.ts b/src/views/configManage/configParamTreeTable/hooks/useConfigList.ts new file mode 100644 index 00000000..ba198f68 --- /dev/null +++ b/src/views/configManage/configParamTreeTable/hooks/useConfigList.ts @@ -0,0 +1,139 @@ +import { editPtNeConfigData } from '@/api/pt/neConfig'; +import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; +import { SizeType } from 'ant-design-vue/es/config-provider'; +import message from 'ant-design-vue/es/message'; +import { reactive, toRaw } from 'vue'; + +/** + * list类型参数处理 + * @param param 父级传入 {t, treeState, ruleVerification} + * @returns + */ +export default function useConfigList({ t, treeState, ruleVerification }: any) { + /**单列表状态类型 */ + type ListStateType = { + /**紧凑型 */ + size: SizeType; + /**单列记录字段 */ + columns: Record[]; + /**单列记录数据 */ + data: Record[]; + /**编辑行记录 */ + editRecord: Record; + /**确认提交等待 */ + confirmLoading: boolean; + }; + + /**单列表状态 */ + let listState: ListStateType = reactive({ + size: 'small', + columns: [ + { + title: 'Key', + dataIndex: 'display', + align: 'left', + width: '30%', + }, + { + title: 'Value', + dataIndex: 'value', + align: 'left', + width: '70%', + }, + ], + data: [], + confirmLoading: false, + editRecord: {}, + }); + + /**单列表编辑 */ + function listEdit(row: Record) { + listState.editRecord = Object.assign({}, row); + } + + /**单列表编辑关闭 */ + function listEditClose() { + listState.confirmLoading = false; + listState.editRecord = {}; + } + + /**单列表编辑确认 */ + function listEditOk() { + if (listState.confirmLoading) return; + const from = toRaw(listState.editRecord); + // 检查规则 + const [ok, msg] = ruleVerification(from); + if (!ok) { + message.warning({ + content: `${msg}`, + duration: 3, + }); + return; + } + + // 发送 + listState.confirmLoading = true; + const hide = message.loading(t('common.loading'), 0); + editPtNeConfigData({ + neType: treeState.neType, + paramName: treeState.selectNode.paramName, + paramData: { + [from['name']]: from['value'], + }, + }) + .then(res => { + if (res.code === RESULT_CODE_SUCCESS) { + message.success({ + content: t('views.configManage.configParamForm.updateValue', { + num: from['display'], + }), + duration: 3, + }); + // 改变表格数据 + const item = listState.data.find( + (item: Record) => from['name'] === item['name'] + ); + if (item) { + Object.assign(item, listState.editRecord); + } + } else { + message.warning({ + content: t('views.configManage.configParamForm.updateValueErr'), + duration: 3, + }); + } + }) + .finally(() => { + hide(); + listState.confirmLoading = false; + listState.editRecord = {}; + }); + } + + /**表格分页器参数 */ + let tablePagination = reactive({ + /**当前页数 */ + current: 1, + /**每页条数 */ + pageSize: 10, + /**默认的每页条数 */ + defaultPageSize: 10, + /**指定每页可以显示多少条 */ + pageSizeOptions: ['10', '20', '50', '100'], + /**只有一页时是否隐藏分页器 */ + hideOnSinglePage: true, + /**是否可以快速跳转至某页 */ + showQuickJumper: true, + /**是否可以改变 pageSize */ + showSizeChanger: true, + /**数据总数 */ + total: 0, + showTotal: (total: number) => t('common.tablePaginationTotal', { total }), + onChange: (page: number, pageSize: number) => { + tablePagination.current = page; + tablePagination.pageSize = pageSize; + }, + }); + + return { tablePagination, listState, listEdit, listEditClose, listEditOk }; +} diff --git a/src/views/configManage/configParamTreeTable/hooks/useOptions.ts b/src/views/configManage/configParamTreeTable/hooks/useOptions.ts new file mode 100644 index 00000000..b7220371 --- /dev/null +++ b/src/views/configManage/configParamTreeTable/hooks/useOptions.ts @@ -0,0 +1,192 @@ +import { getNeConfigData } from '@/api/ne/neConfig'; +import { regExpIPv4, regExpIPv6, validURL } from '@/utils/regular-utils'; +import { ref } from 'vue'; + +/** + * 参数公共函数 + * @param param 父级传入 {t} + * @returns + */ +export default function useOptions({ t }: any) { + /**规则校验 */ + function ruleVerification(row: Record): (string | boolean)[] { + let result = [true, '']; + const type = row.type; + const value = row.value; + const filter = row.filter; + const display = row.display; + + // 子嵌套的不检查 + if (row.array) { + return result; + } + + // 可选的同时没有值不检查 + if (row.optional === 'true' && !value) { + return result; + } + switch (type) { + case 'int': + // filter: "0~128" + + if (filter && filter.indexOf('~') !== -1) { + const filterArr = filter.split('~'); + const minInt = parseInt(filterArr[0]); + const maxInt = parseInt(filterArr[1]); + const valueInt = parseInt(value); + if (valueInt < minInt || valueInt > maxInt) { + return [ + false, + t('views.configManage.configParamForm.requireInt', { + display, + filter, + }), + ]; + } + } + break; + case 'ipv4': + if (!regExpIPv4.test(value)) { + return [ + false, + t('views.configManage.configParamForm.requireIpv4', { display }), + ]; + } + break; + case 'ipv6': + if (!regExpIPv6.test(value)) { + return [ + false, + t('views.configManage.configParamForm.requireIpv6', { display }), + ]; + } + break; + case 'enum': + if (filter && filter.indexOf('{') === 1) { + let filterJson: Record = {}; + try { + filterJson = JSON.parse(filter); //string---json + } catch (error) { + console.error(error); + } + + if (!Object.keys(filterJson).includes(`${value}`)) { + return [ + false, + t('views.configManage.configParamForm.requireEnum', { display }), + ]; + } + } + break; + case 'bool': + // filter: '{"0":"false", "1":"true"}' + + if (filter && filter.indexOf('{') === 1) { + let filterJson: Record = {}; + try { + filterJson = JSON.parse(filter); //string---json + } catch (error) { + console.error(error); + } + + if (!Object.values(filterJson).includes(`${value}`)) { + return [ + false, + t('views.configManage.configParamForm.requireBool', { display }), + ]; + } + } + break; + case 'string': + // filter: "0~128" + + // 字符串长度判断 + if (filter && filter.indexOf('~') !== -1) { + try { + const filterArr = filter.split('~'); + let rule = new RegExp( + '^\\S{' + filterArr[0] + ',' + filterArr[1] + '}$' + ); + if (!rule.test(value)) { + return [ + false, + t('views.configManage.configParamForm.requireString', { + display, + }), + ]; + } + } catch (error) { + console.error(error); + } + } + // 字符串http判断 + if (value.startsWith('http')) { + try { + if (!validURL(value)) { + return [ + false, + t('views.configManage.configParamForm.requireString', { + display, + }), + ]; + } + } catch (error) { + console.error(error); + } + } + + break; + case 'regex': + // filter: "^[0-9]{3}$" + + if (filter) { + try { + let regex = new RegExp(filter); + if (!regex.test(value)) { + return [ + false, + t('views.configManage.configParamForm.requireString', { + display, + }), + ]; + } + } catch (error) { + console.error(error); + } + } + break; + + default: + return [ + false, + t('views.configManage.configParamForm.requireUn', { display }), + ]; + } + return result; + } + + /**upfId可选择 */ + const SMFByUPFIdOptions = ref<{ value: string; label: string }[]>([]); + /**加载smf配置的upfId */ + function getConfigSMFByUPFIds(neId: string) { + getNeConfigData({ + neType: 'SMF', + neId: neId, + paramName: 'upfConfig', + }).then(res => { + SMFByUPFIdOptions.value = []; + for (const s of res.data) { + SMFByUPFIdOptions.value.push({ + value: s.id, + label: s.id, + }); + } + }); + } + + return { + ruleVerification, + getConfigSMFByUPFIds, + SMFByUPFIdOptions, + }; +} diff --git a/src/views/configManage/configParamTreeTable/hooks/usePtOptions.ts b/src/views/configManage/configParamTreeTable/hooks/usePtOptions.ts new file mode 100644 index 00000000..c2d7d464 --- /dev/null +++ b/src/views/configManage/configParamTreeTable/hooks/usePtOptions.ts @@ -0,0 +1,228 @@ +import { ptSaveAsDefault, ptResetAsDefault } from '@/api/pt/neConfig'; +import { + getPtClassStudents, + stuPtNeConfigApply, + updatePtNeConfigApply, +} from '@/api/pt/neConfigApply'; +import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; +import { hasRoles } from '@/plugins/auth-user'; +import { message } from 'ant-design-vue/lib'; +import { computed, onMounted, reactive } from 'vue'; + +/** + * 实训教学函数 + * @param param 父级传入 {t,fnActiveConfigNode} + * @returns + */ +export default function usePtOptions({ t, fnActiveConfigNode }: any) { + const ptConfigState = reactive({ + saveLoading: false, + restLoading: false, + applyLoading: false, + }); + /**(管理员)保存网元下所有配置为示例配置 */ + function ptConfigSave(neType: string) { + ptConfigState.saveLoading = true; + ptSaveAsDefault(neType, '001') + .then(res => { + if (res.code === RESULT_CODE_SUCCESS) { + message.success({ + content: t('common.operateOk'), + duration: 3, + }); + } else { + message.error({ + content: `${res.msg}`, + duration: 3, + }); + } + }) + .finally(() => { + ptConfigState.saveLoading = false; + fnActiveConfigNode('#'); + }); + } + + /**重置网元下所有配置 */ + function ptConfigReset(neType: string) { + ptConfigState.restLoading = true; + ptResetAsDefault(neType) + .then(res => { + if (res.code === RESULT_CODE_SUCCESS) { + message.success({ + content: t('common.operateOk'), + duration: 3, + }); + } else { + message.error({ + content: `${res.msg}`, + duration: 3, + }); + } + }) + .finally(() => { + ptConfigState.restLoading = false; + fnActiveConfigNode('#'); + }); + } + + /**配置下方应用(学生)申请撤回和(管理/教师)应用退回 */ + function ptConfigApply( + neType: string, + status: '0' | '1' | '2' | '3', + student?: string + ) { + let result: any; + if (status === '2' || status === '3') { + let from: { + neType: string; + status: string; + student?: string; + backInfo?: string; + } = { + neType, + status: '2', + }; + if (student) { + if (status === '2') { + from = { neType, status, student }; + } + if (status === '3') { + from = { neType, status, student, backInfo: '请重新检查配置' }; + } + } + result = updatePtNeConfigApply(from); + } + if (status === '0' || status === '1') { + result = stuPtNeConfigApply({ neType, status }); + } + if (!result) return; + ptConfigState.applyLoading = true; + result + .then((res: any) => { + if (res.code === RESULT_CODE_SUCCESS) { + message.success({ + content: t('common.operateOk'), + duration: 3, + }); + // 教师修改学生时改变状态 + if (student) { + const item = classState.studentOptionsDef.find( + s => s.value === classState.student + ); + if (item) { + item.applyStatus = status; + } + } + } else { + message.error({ + content: `${res.msg}`, + duration: 3, + }); + } + }) + .finally(() => { + ptConfigState.applyLoading = false; + }); + } + + const classState = reactive<{ + /**学生账号 */ + student: string | undefined; + /**学生可选择列表 */ + studentOptions: { + value: string; + label: string; + applyId: string; + applyStatus: string; + }[]; + studentOptionsDef: { + value: string; + label: string; + applyId: string; + applyStatus: string; + }[]; + }>({ + student: undefined, + studentOptions: [], + studentOptionsDef: [], + }); + + // 仅教师加载 + if (hasRoles(['teacher'])) { + onMounted(() => { + classStudents(); // 初始学生列表 + }); + } + + /**学生选择搜索 */ + function studentChange(v: any) { + if (!v) { + Object.assign(classState.studentOptions, classState.studentOptionsDef); + } + fnActiveConfigNode('#'); + } + + let timeout: any; + + /**学生选择搜索 */ + function studentSearch(val: string) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + if (!val) { + Object.assign(classState.studentOptions, classState.studentOptionsDef); + return; + } + timeout = setTimeout(() => classStudents(val), 500); + } + + /**班级学生列表 */ + function classStudents(val?: string) { + getPtClassStudents({ userName: val }).then(res => { + classState.studentOptions = []; + if (!Array.isArray(res.data) || res.data.length <= 0) { + return; + } + for (const v of res.data) { + classState.studentOptions.push({ + value: v.userName, + label: v.userName, + applyId: v.applyId, + applyStatus: v.applyStatus, + }); + // 设为最新状态 + const item = classState.studentOptionsDef.find( + s => s.value === v.userName + ); + if (item) { + item.applyStatus = v.applyStatus; + } + } + if (!val) { + Object.assign(classState.studentOptionsDef, classState.studentOptions); + } + }); + } + + // 学生状态 + const studentStatus = computed(() => { + const item = classState.studentOptionsDef.find( + s => s.value === classState.student + ); + if (item) return item.applyStatus; + return ''; + }); + + return { + ptConfigState, + ptConfigSave, + ptConfigReset, + ptConfigApply, + classState, + studentStatus, + studentSearch, + studentChange, + }; +} diff --git a/src/views/configManage/configParamTreeTable/index.vue b/src/views/configManage/configParamTreeTable/index.vue new file mode 100644 index 00000000..50bac0b9 --- /dev/null +++ b/src/views/configManage/configParamTreeTable/index.vue @@ -0,0 +1,1249 @@ + + + + + + + + + + + {{ label }} + + {{ + applyStatus && + t('views.configManage.configParamForm.ptApply') + }} + + + + + + + + + + + + {{ t('views.configManage.configParamForm.ptApplyStuNE') }} + + + {{ t('views.configManage.configParamForm.ptApplyStuRack') }} + + + + + {{ t('views.configManage.configParamForm.ptApplyNE') }} + + + {{ t('views.configManage.configParamForm.ptLoad') }} + + + {{ t('views.configManage.configParamForm.ptReset') }} + + + {{ t('views.configManage.configParamForm.ptExport') }} + + + + + {{ t('views.configManage.configParamForm.ptReset') }} + + + {{ + t('views.configManage.configParamForm.ptApplyStu', { + ne: treeState.neType, + }) + }} + + + {{ t('views.configManage.configParamForm.ptExport') }} + + + + + + + + + + + + + {{ t('views.configManage.configParamForm.treeTitle') }} + + {{ classState.student }} + + + + + + + + + + + + + + + + + + + + + + + + + {{ treeState.selectNode.paramDisplay }} + + + {{ t('views.configManage.configParamForm.treeSelectTip') }} + + + + + + + + {{ t('views.configManage.configParamForm.ptDiff') }} + + + + + + + + + + + {{ t('views.configManage.configParamForm.ptHistory') }} + + + + + + + + + {{ t('common.reloadText') }} + + + + + + + + + + + + + + + + {{ record.comment }} + + + + + + + + {{ k }} + + + + + + {{ t('common.ok') }} + + + + + + + + + + {{ t('common.cancel') }} + + + + + + + + + + + {{ JSON.parse(record['filter'])[text] }} + + {{ `${text}` || ' ' }} + + + + + + + + + + + (col.width = w)" + :show-expand-column="false" + v-model:expanded-row-keys="arrayState.arrayChildExpandKeys" + > + + + + + + {{ t('common.addText') }} + + + + + + + + + + + {{ t('common.editText') }} + + + + + + {{ t('common.deleteText') }} + + + + + + + + + + {{ text.comment }} + + + + + + {{ + t('views.configManage.configParamForm.arrayMore') + }} + + + + ({{ + text.value.length > 4 + ? `${text.value + .slice(0, 3) + .map((s: any) => s.dnn) + .join()}...${text.value.length}` + : text.value.map((s: any) => s.dnn).join() + }}) + + + + + + {{ JSON.parse(text['filter'])[text.value] }} + + + {{ `${text.value}` || ' ' }} + + + + + + + + + + (col.width = w)" + > + + + + + {{ t('common.addText') }} {{ arrayChildState.title }} + + + + + + + + + {{ t('common.editText') }} + + + + + + + {{ t('common.deleteText') }} + + + + + + + + + + + {{ text.comment }} + + + + + + {{ + t( + 'views.configManage.configParamForm.arrayMore' + ) + }} + + + + + + {{ JSON.parse(text['filter'])[text.value] }} + + + {{ `${text.value}` || ' ' }} + + + + + + + + + + + + + + + + + + + + + {{ item.comment }} + + + + + + + + + + + + + {{ k }} + + + + + + {{ `${item.value || ' '}` }} + + + + + + + + + + + + {{ t('views.configManage.configParamForm.ptDiffExample') }} + + + {{ t('views.configManage.configParamForm.ptDiffSelf') }} + + + + + + + + + + + diff --git a/src/views/dashboard/amfUE/index.vue b/src/views/dashboard/amfUE/index.vue index 79712569..e08cf16f 100644 --- a/src/views/dashboard/amfUE/index.vue +++ b/src/views/dashboard/amfUE/index.vue @@ -15,6 +15,7 @@ import { listAMFDataUE, delAMFDataUE, exportAMFDataUE } from '@/api/neData/amf'; import { OptionsType, WS } from '@/plugins/ws-websocket'; import saveAs from 'file-saver'; import PQueue from 'p-queue'; +import { hasRoles } from '@/plugins/auth-user'; const { t } = useI18n(); const { getDict } = useDictStore(); const ws = new WS(); @@ -522,6 +523,7 @@ onBeforeUnmount(() => { :disabled="tableState.selectedRowKeys.length <= 0" :loading="modalState.confirmLoading" @click.prevent="fnRecordDelete('0')" + v-if="!hasRoles(['student'])" > {{ t('common.deleteText') }} @@ -583,7 +585,7 @@ onBeforeUnmount(() => { { :disabled="tableState.selectedRowKeys.length <= 0" :loading="modalState.confirmLoading" @click.prevent="fnRecordDelete('0')" + v-if="!hasRoles(['student'])" > {{ t('common.deleteText') }} @@ -690,7 +692,7 @@ onBeforeUnmount(() => { { :disabled="tableState.selectedRowKeys.length <= 0" :loading="modalState.confirmLoading" @click.prevent="fnRecordDelete('0')" + v-if="!hasRoles(['student'])" > {{ t('common.deleteText') }} @@ -634,7 +636,7 @@ onBeforeUnmount(() => { { :disabled="tableState.selectedRowKeys.length <= 0" :loading="modalState.confirmLoading" @click.prevent="fnRecordDelete('0')" + v-if="!hasRoles(['student'])" > {{ t('common.deleteText') }} @@ -653,7 +655,7 @@ onBeforeUnmount(() => { ${label || id}`; } + let notStudentInfo = ''; + if (hasRoles(['teacher', 'admin'])) { + notStudentInfo = ` + ${t('views.monitor.topology.serialNum')}: + ${neState.sn ?? '--'} + + ${t('views.monitor.topology.expiryDate')}: + ${neState.expire ?? '--'} + `; + } return ` - + {{ t('common.addText') }} @@ -578,26 +579,31 @@ onMounted(() => { - - {{ t('common.editText') }} - - - - - - - {{ t('views.ne.common.restart') }} - - - - - + + + {{ t('common.editText') }} + + + + + + + + + {{ t('views.ne.common.restart') }} + + + + + + + {{ t('common.moreText') }} @@ -610,31 +616,30 @@ onMounted(() => { {{ t('views.ne.common.log') }} - + {{ t('views.ne.common.start') }} - + {{ t('views.ne.common.stop') }} {{ t('views.ne.common.reload') }} - + {{ t('common.deleteText') }} - + {{ t('views.ne.common.oam') }} @@ -655,7 +660,7 @@ onMounted(() => { - + {{ t('views.ne.neInfo.info') }} diff --git a/src/views/plugins/auth-user.ts b/src/views/plugins/auth-user.ts new file mode 100644 index 00000000..e2fb9efb --- /dev/null +++ b/src/views/plugins/auth-user.ts @@ -0,0 +1,66 @@ +import { ADMIN_PERMISSION, ADMIN_ROLE_KEY } from '@/constants/admin-constants'; +import useUserStore from '@/store/modules/user'; + +/** + * 是否系统管理员 + * @returns true | false + */ +export function isSystemAdmin(): boolean { + const userPermissions = useUserStore().permissions; + if (userPermissions.includes(ADMIN_PERMISSION)) return true; + const userRoles = useUserStore().roles; + if (userRoles.includes(ADMIN_ROLE_KEY)) return true; + return false; +} + +/** + * 只需含有其中权限 + * @param role 权限字符数组 + * @returns true | false + */ +export function hasPermissions(permissions: string[]): boolean { + if (!permissions || permissions.length === 0) return false; + const userPermissions = useUserStore().permissions; + if (!userPermissions || userPermissions.length === 0) return false; + if (userPermissions.includes(ADMIN_PERMISSION)) return true; + return permissions.some(p => userPermissions.some(up => up === p)); +} + +/** + * 同时匹配其中权限 + * @param role 权限字符数组 + * @returns true | false + */ +export function matchPermissions(permissions: string[]): boolean { + if (!permissions || permissions.length === 0) return false; + const userPermissions = useUserStore().permissions; + if (!userPermissions || userPermissions.length === 0) return false; + if (userPermissions.includes(ADMIN_PERMISSION)) return true; + return permissions.every(p => userPermissions.some(up => up === p)); +} + +/** + * 只需含有其中角色 + * @param role 角色字符数组 + * @returns true | false + */ +export function hasRoles(roles: string[]): boolean { + if (!roles || roles.length === 0) return false; + const userRoles = useUserStore().roles; + if (!userRoles || userRoles.length === 0) return false; + if (userRoles.includes(ADMIN_ROLE_KEY)) return true; + return roles.some(r => userRoles.some(ur => ur === r)); +} + +/** + * 同时匹配其中角色 + * @param role 角色字符数组 + * @returns true | false + */ +export function matchRoles(roles: string[]): boolean { + if (!roles || roles.length === 0) return false; + const userRoles = useUserStore().roles; + if (!userRoles || userRoles.length === 0) return false; + if (userRoles.includes(ADMIN_ROLE_KEY)) return true; + return roles.every(r => userRoles.some(ur => ur === r)); +} diff --git a/src/views/store/modules/user.ts b/src/views/store/modules/user.ts new file mode 100644 index 00000000..3b8b5d42 --- /dev/null +++ b/src/views/store/modules/user.ts @@ -0,0 +1,171 @@ +import defaultAvatar from '@/assets/images/default_avatar.png'; +import useLayoutStore from './layout'; +import { login, logout, getInfo } from '@/api/login'; +import { setToken, removeToken } from '@/plugins/auth-token'; +import { defineStore } from 'pinia'; +import { TOKEN_RESPONSE_FIELD } from '@/constants/token-constants'; +import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; +import { parseUrlPath } from '@/plugins/file-static-url'; + +/**用户信息类型 */ +type UserInfo = { + /**用户ID */ + userId: string; + /**登录账号 */ + userName: string; + /**用户角色 字符串数组 */ + roles: string[]; + /**用户权限 字符串数组 */ + permissions: string[]; + /**用户头像 */ + avatar: string; + /**用户昵称 */ + nickName: string; + /**用户手机号 */ + phonenumber: string; + /**用户邮箱 */ + email: string; + /**用户性别 */ + sex: string | undefined; + /**其他信息 */ + profile: Record; +}; + +const useUserStore = defineStore('user', { + state: (): UserInfo => ({ + userId: '', + userName: '', + roles: [], + permissions: [], + avatar: '', + nickName: '', + phonenumber: '', + email: '', + sex: undefined, + profile: {}, + }), + getters: { + /** + * 获取正确头像地址 + * @param state 内部属性不用传入 + * @returns 头像地址url + */ + getAvatar(state) { + if (!state.avatar) { + return defaultAvatar; + } + return parseUrlPath(state.avatar); + }, + /** + * 获取基础信息属性 + * @param state 内部属性不用传入 + * @returns 基础信息 + */ + getBaseInfo(state) { + return { + nickName: state.nickName, + phonenumber: state.phonenumber, + email: state.email, + sex: state.sex, + }; + }, + }, + actions: { + /** + * 更新基础信息属性 + * @param data 变更信息 + */ + setBaseInfo(data: Record) { + this.nickName = data.nickName; + this.phonenumber = data.phonenumber; + this.email = data.email; + this.sex = data.sex; + }, + /** + * 更新头像 + * @param avatar 上传后的地址 + */ + setAvatar(avatar: string) { + this.avatar = avatar; + }, + /** + * 获取正确头像地址 + * @param avatar + */ + fnAvatar(avatar: string) { + if (!avatar) { + return defaultAvatar; + } + return parseUrlPath(avatar); + }, + // 登录 + async fnLogin(loginBody: Record) { + const res = await login(loginBody); + if (res.code === RESULT_CODE_SUCCESS && res.data) { + const token = res.data[TOKEN_RESPONSE_FIELD]; + setToken(token); + } + return res; + }, + // 获取用户信息 + async fnGetInfo() { + const res = await getInfo(); + if (res.code === RESULT_CODE_SUCCESS && res.data) { + const { user, roles, permissions } = res.data; + this.userId = user.userId; + // 登录账号 + this.userName = user.userName; + // 用户头像 + this.avatar = user.avatar; + // 基础信息 + this.nickName = user.nickName; + this.phonenumber = user.phonenumber; + this.email = user.email; + this.sex = user.sex; + + // 验证返回的roles是否是一个非空数组 + if (Array.isArray(roles) && roles.length > 0) { + this.roles = roles; + this.permissions = permissions; + } else { + this.roles = ['ROLE_DEFAULT']; + this.permissions = []; + } + + // 水印文字信息=用户昵称 手机号 + let waterMarkContent = this.userName; + if (this.phonenumber) { + waterMarkContent = `${this.userName} ${this.phonenumber}`; + } + // useLayoutStore().changeWaterMark(waterMarkContent); + useLayoutStore().changeWaterMark(''); + // 学生布局用不一样的 + if (this.roles.includes('student')) { + useLayoutStore().changeConf('layout', 'side'); + useLayoutStore().changeConf('menuTheme', 'dark'); + useLayoutStore().changeConf('tabRender', false); + } + } + // 网络错误时退出登录状态 + if (res.code === 0) { + removeToken(); + window.location.reload(); + } + return res; + }, + // 退出系统 + async fnLogOut() { + try { + await logout(); + } catch (error) { + throw error; + } finally { + this.roles = []; + this.permissions = []; + removeToken(); + } + }, + }, +}); + +export default useUserStore;