角色管理和审批模板管理完成

This commit is contained in:
2026-08-17 17:09:27 +08:00
parent 8008754996
commit df9f9b3693
12 changed files with 937 additions and 151 deletions
+7 -7
View File
@@ -2855,8 +2855,8 @@ packages:
date-fns@3.6.0: date-fns@3.6.0:
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
date-fns@4.1.0: date-fns@4.4.0:
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==}
dayjs@1.11.20: dayjs@1.11.20:
resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==}
@@ -8119,13 +8119,13 @@ snapshots:
es-errors: 1.3.0 es-errors: 1.3.0
is-data-view: 1.0.2 is-data-view: 1.0.2
date-fns-tz@3.2.0(date-fns@4.1.0): date-fns-tz@3.2.0(date-fns@4.4.0):
dependencies: dependencies:
date-fns: 4.1.0 date-fns: 4.4.0
date-fns@3.6.0: {} date-fns@3.6.0: {}
date-fns@4.1.0: {} date-fns@4.4.0: {}
dayjs@1.11.20: {} dayjs@1.11.20: {}
@@ -9418,8 +9418,8 @@ snapshots:
async-validator: 4.2.5 async-validator: 4.2.5
css-render: 0.15.14 css-render: 0.15.14
csstype: 3.2.3 csstype: 3.2.3
date-fns: 4.1.0 date-fns: 4.4.0
date-fns-tz: 3.2.0(date-fns@4.1.0) date-fns-tz: 3.2.0(date-fns@4.4.0)
evtd: 0.2.4 evtd: 0.2.4
highlight.js: 11.11.1 highlight.js: 11.11.1
lodash: 4.18.1 lodash: 4.18.1
+4
View File
@@ -238,6 +238,10 @@ const local: App.I18n.Schema = {
docs: '资料管理', docs: '资料管理',
docs_archives: '资料档案库', docs_archives: '资料档案库',
docs_query: '变更归档查询', docs_query: '变更归档查询',
systemmanage: '系统设置',
systemmanage_user: '架构及用户管理',
systemmanage_role: '角色管理',
systemmanage_template: '审批模板管理',
}, },
form: { form: {
required: '不能为空', required: '不能为空',
+3
View File
@@ -30,4 +30,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
home_process: () => import("@/views/home/process/index.vue"), home_process: () => import("@/views/home/process/index.vue"),
home_processdetail: () => import("@/views/home/processDetail/index.vue"), home_processdetail: () => import("@/views/home/processDetail/index.vue"),
login: () => import("@/views/login/index.vue"), login: () => import("@/views/login/index.vue"),
systemmanage_role: () => import("@/views/systemManage/role/index.vue"),
systemmanage_template: () => import("@/views/systemManage/template/index.vue"),
systemmanage_user: () => import("@/views/systemManage/user/index.vue"),
}; };
+43
View File
@@ -202,5 +202,48 @@ export const generatedRoutes: GeneratedRoute[] = [
constant: true, constant: true,
hideInMenu: true hideInMenu: true
} }
},
{
name: 'systemmanage',
path: '/systemmanage',
component: 'layout.base',
meta: {
title: 'systemmanage',
i18nKey: 'route.systemmanage',
icon: 'uil:setting',
order: 5
},
children: [
{
name: 'systemmanage_role',
path: '/systemmanage/role',
component: 'view.systemmanage_role',
meta: {
title: 'systemmanage_role',
i18nKey: 'route.systemmanage_role',
icon: ''
}
},
{
name: 'systemmanage_template',
path: '/systemmanage/template',
component: 'view.systemmanage_template',
meta: {
title: 'systemmanage_template',
i18nKey: 'route.systemmanage_template',
icon: ''
}
},
{
name: 'systemmanage_user',
path: '/systemmanage/user',
component: 'view.systemmanage_user',
meta: {
title: 'systemmanage_user',
i18nKey: 'route.systemmanage_user',
icon: ''
}
}
]
} }
]; ];
+5 -1
View File
@@ -180,7 +180,11 @@ const routeMap: RouteMap = {
"home_pending": "/home/pending", "home_pending": "/home/pending",
"home_process": "/home/process", "home_process": "/home/process",
"home_processdetail": "/home/processdetail", "home_processdetail": "/home/processdetail",
"login": "/login/:module(pwd-login)?" "login": "/login/:module(pwd-login)?",
"systemmanage": "/systemmanage",
"systemmanage_role": "/systemmanage/role",
"systemmanage_template": "/systemmanage/template",
"systemmanage_user": "/systemmanage/user"
}; };
/** /**
+2 -120
View File
@@ -23,118 +23,59 @@ declare module 'vue' {
GithubLink: typeof import('./../components/custom/github-link.vue')['default'] GithubLink: typeof import('./../components/custom/github-link.vue')['default']
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default'] IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default'] IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default']
IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
IconCarbonPlay: typeof import('~icons/carbon/play')['default']
IconCarbonStop: typeof import('~icons/carbon/stop')['default']
IconCharmDownload: typeof import('~icons/charm/download')['default']
IconF7CircleFill: typeof import('~icons/f7/circle-fill')['default']
IconF7FlagCircleFill: typeof import('~icons/f7/flag-circle-fill')['default']
IconFeQuestion: typeof import('~icons/fe/question')['default']
IconFileIconsMicrosoftExcel: typeof import('~icons/file-icons/microsoft-excel')['default']
IconGgRatio: typeof import('~icons/gg/ratio')['default']
IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default'] IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default'] IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
IconIconParkOutlineEqualRatio: typeof import('~icons/icon-park-outline/equal-ratio')['default']
IconIcRoundDelete: typeof import('~icons/ic/round-delete')['default']
IconIcRoundPlus: typeof import('~icons/ic/round-plus')['default']
IconIcRoundRefresh: typeof import('~icons/ic/round-refresh')['default']
IconIcRoundRemove: typeof import('~icons/ic/round-remove')['default']
IconIcRoundSearch: typeof import('~icons/ic/round-search')['default']
IconLocalActivity: typeof import('~icons/local/activity')['default']
IconLocalBanner: typeof import('~icons/local/banner')['default']
IconLocalCast: typeof import('~icons/local/cast')['default']
IconMaterialSymbolsLightRotate90DegreesCcwOutlineRounded: typeof import('~icons/material-symbols-light/rotate90-degrees-ccw-outline-rounded')['default']
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default'] IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default'] IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
IconMdiDrag: typeof import('~icons/mdi/drag')['default']
IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default'] IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default']
IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default'] IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
IconMdiPrinter: typeof import('~icons/mdi/printer')['default']
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
IconMingcuteZoomInLine: typeof import('~icons/mingcute/zoom-in-line')['default']
IconMingcuteZoomOutLine: typeof import('~icons/mingcute/zoom-out-line')['default']
IconOcticonPin16: typeof import('~icons/octicon/pin16')['default']
IconOcticonPinSlash16: typeof import('~icons/octicon/pin-slash16')['default']
IconTooltip: typeof import('./../components/common/icon-tooltip.vue')['default'] IconTooltip: typeof import('./../components/common/icon-tooltip.vue')['default']
IconUilSearch: typeof import('~icons/uil/search')['default'] IconUilSearch: typeof import('~icons/uil/search')['default']
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default'] LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
LookForward: typeof import('./../components/custom/look-forward.vue')['default'] LookForward: typeof import('./../components/custom/look-forward.vue')['default']
MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default'] MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
NAlert: typeof import('naive-ui')['NAlert']
NBadge: typeof import('naive-ui')['NBadge'] NBadge: typeof import('naive-ui')['NBadge']
NBreadcrumb: typeof import('naive-ui')['NBreadcrumb'] NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem'] NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
NButton: typeof import('naive-ui')['NButton'] NButton: typeof import('naive-ui')['NButton']
NButtonGroup: typeof import('naive-ui')['NButtonGroup']
NCard: typeof import('naive-ui')['NCard'] NCard: typeof import('naive-ui')['NCard']
NCascader: typeof import('naive-ui')['NCascader']
NCheckbox: typeof import('naive-ui')['NCheckbox'] NCheckbox: typeof import('naive-ui')['NCheckbox']
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup'] NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
NCollapse: typeof import('naive-ui')['NCollapse']
NCollapseItem: typeof import('naive-ui')['NCollapseItem']
NColorPicker: typeof import('naive-ui')['NColorPicker'] NColorPicker: typeof import('naive-ui')['NColorPicker']
NDataTable: typeof import('naive-ui')['NDataTable'] NDataTable: typeof import('naive-ui')['NDataTable']
NDatePicker: typeof import('naive-ui')['NDatePicker'] NDatePicker: typeof import('naive-ui')['NDatePicker']
NDescriptions: typeof import('naive-ui')['NDescriptions']
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
NDialogProvider: typeof import('naive-ui')['NDialogProvider'] NDialogProvider: typeof import('naive-ui')['NDialogProvider']
NDivider: typeof import('naive-ui')['NDivider'] NDivider: typeof import('naive-ui')['NDivider']
NDrawer: typeof import('naive-ui')['NDrawer'] NDrawer: typeof import('naive-ui')['NDrawer']
NDrawerContent: typeof import('naive-ui')['NDrawerContent'] NDrawerContent: typeof import('naive-ui')['NDrawerContent']
NDropdown: typeof import('naive-ui')['NDropdown'] NDropdown: typeof import('naive-ui')['NDropdown']
NDynamicInput: typeof import('naive-ui')['NDynamicInput']
NEmpty: typeof import('naive-ui')['NEmpty'] NEmpty: typeof import('naive-ui')['NEmpty']
NFlex: typeof import('naive-ui')['NFlex']
NForm: typeof import('naive-ui')['NForm'] NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem'] NFormItem: typeof import('naive-ui')['NFormItem']
NFormItemGi: typeof import('naive-ui')['NFormItemGi'] NFormItemGi: typeof import('naive-ui')['NFormItemGi']
NGi: typeof import('naive-ui')['NGi'] NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid'] NGrid: typeof import('naive-ui')['NGrid']
NIcon: typeof import('naive-ui')['NIcon']
NInput: typeof import('naive-ui')['NInput'] NInput: typeof import('naive-ui')['NInput']
NInputGroup: typeof import('naive-ui')['NInputGroup'] NInputGroup: typeof import('naive-ui')['NInputGroup']
NInputNumber: typeof import('naive-ui')['NInputNumber'] NInputNumber: typeof import('naive-ui')['NInputNumber']
NIput: typeof import('naive-ui')['NIput']
NList: typeof import('naive-ui')['NList']
NListItem: typeof import('naive-ui')['NListItem']
NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider'] NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
NMenu: typeof import('naive-ui')['NMenu'] NMenu: typeof import('naive-ui')['NMenu']
NMessageProvider: typeof import('naive-ui')['NMessageProvider'] NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal'] NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NPagination: typeof import('naive-ui')['NPagination']
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
NPopover: typeof import('naive-ui')['NPopover']
NProgress: typeof import('naive-ui')['NProgress']
NRadio: typeof import('naive-ui')['NRadio'] NRadio: typeof import('naive-ui')['NRadio']
NRadioGroup: typeof import('naive-ui')['NRadioGroup'] NRadioGroup: typeof import('naive-ui')['NRadioGroup']
NScrollbar: typeof import('naive-ui')['NScrollbar'] NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect'] NSelect: typeof import('naive-ui')['NSelect']
NSkeleton: typeof import('naive-ui')['NSkeleton']
NSpace: typeof import('naive-ui')['NSpace'] NSpace: typeof import('naive-ui')['NSpace']
NSpin: typeof import('naive-ui')['NSpin']
NStatistic: typeof import('naive-ui')['NStatistic']
NStep: typeof import('naive-ui')['NStep']
NSteps: typeof import('naive-ui')['NSteps']
NSwitch: typeof import('naive-ui')['NSwitch'] NSwitch: typeof import('naive-ui')['NSwitch']
NTab: typeof import('naive-ui')['NTab'] NTab: typeof import('naive-ui')['NTab']
NTabPane: typeof import('naive-ui')['NTabPane']
NTabs: typeof import('naive-ui')['NTabs'] NTabs: typeof import('naive-ui')['NTabs']
NTag: typeof import('naive-ui')['NTag'] NTag: typeof import('naive-ui')['NTag']
NThing: typeof import('naive-ui')['NThing']
NTooltip: typeof import('naive-ui')['NTooltip'] NTooltip: typeof import('naive-ui')['NTooltip']
NTree: typeof import('naive-ui')['NTree']
NWatermark: typeof import('naive-ui')['NWatermark'] NWatermark: typeof import('naive-ui')['NWatermark']
PinToggler: typeof import('./../components/common/pin-toggler.vue')['default'] PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']
ProCard: typeof import('pro-naive-ui')['ProCard']
ProConfigProvider: typeof import('pro-naive-ui')['ProConfigProvider']
ProDataTable: typeof import('pro-naive-ui')['ProDataTable']
ProDate: typeof import('pro-naive-ui')['ProDate']
ProEditDataTable: typeof import('pro-naive-ui')['ProEditDataTable']
ProForm: typeof import('pro-naive-ui')['ProForm']
ProFormList: typeof import('pro-naive-ui')['ProFormList']
ProInput: typeof import('pro-naive-ui')['ProInput']
ProSearchForm: typeof import('pro-naive-ui')['ProSearchForm']
ProSelect: typeof import('pro-naive-ui')['ProSelect']
ReloadButton: typeof import('./../components/common/reload-button.vue')['default'] ReloadButton: typeof import('./../components/common/reload-button.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
@@ -162,118 +103,59 @@ declare global {
const GithubLink: typeof import('./../components/custom/github-link.vue')['default'] const GithubLink: typeof import('./../components/custom/github-link.vue')['default']
const IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default'] const IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
const IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default'] const IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default']
const IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
const IconCarbonPlay: typeof import('~icons/carbon/play')['default']
const IconCarbonStop: typeof import('~icons/carbon/stop')['default']
const IconCharmDownload: typeof import('~icons/charm/download')['default']
const IconF7CircleFill: typeof import('~icons/f7/circle-fill')['default']
const IconF7FlagCircleFill: typeof import('~icons/f7/flag-circle-fill')['default']
const IconFeQuestion: typeof import('~icons/fe/question')['default']
const IconFileIconsMicrosoftExcel: typeof import('~icons/file-icons/microsoft-excel')['default']
const IconGgRatio: typeof import('~icons/gg/ratio')['default']
const IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default'] const IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
const IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default'] const IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
const IconIconParkOutlineEqualRatio: typeof import('~icons/icon-park-outline/equal-ratio')['default']
const IconIcRoundDelete: typeof import('~icons/ic/round-delete')['default']
const IconIcRoundPlus: typeof import('~icons/ic/round-plus')['default']
const IconIcRoundRefresh: typeof import('~icons/ic/round-refresh')['default']
const IconIcRoundRemove: typeof import('~icons/ic/round-remove')['default']
const IconIcRoundSearch: typeof import('~icons/ic/round-search')['default']
const IconLocalActivity: typeof import('~icons/local/activity')['default']
const IconLocalBanner: typeof import('~icons/local/banner')['default']
const IconLocalCast: typeof import('~icons/local/cast')['default']
const IconMaterialSymbolsLightRotate90DegreesCcwOutlineRounded: typeof import('~icons/material-symbols-light/rotate90-degrees-ccw-outline-rounded')['default']
const IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default'] const IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
const IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default'] const IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
const IconMdiDrag: typeof import('~icons/mdi/drag')['default']
const IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default'] const IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default']
const IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default'] const IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
const IconMdiPrinter: typeof import('~icons/mdi/printer')['default']
const IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
const IconMingcuteZoomInLine: typeof import('~icons/mingcute/zoom-in-line')['default']
const IconMingcuteZoomOutLine: typeof import('~icons/mingcute/zoom-out-line')['default']
const IconOcticonPin16: typeof import('~icons/octicon/pin16')['default']
const IconOcticonPinSlash16: typeof import('~icons/octicon/pin-slash16')['default']
const IconTooltip: typeof import('./../components/common/icon-tooltip.vue')['default'] const IconTooltip: typeof import('./../components/common/icon-tooltip.vue')['default']
const IconUilSearch: typeof import('~icons/uil/search')['default'] const IconUilSearch: typeof import('~icons/uil/search')['default']
const LangSwitch: typeof import('./../components/common/lang-switch.vue')['default'] const LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
const LookForward: typeof import('./../components/custom/look-forward.vue')['default'] const LookForward: typeof import('./../components/custom/look-forward.vue')['default']
const MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default'] const MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
const NAlert: typeof import('naive-ui')['NAlert']
const NBadge: typeof import('naive-ui')['NBadge'] const NBadge: typeof import('naive-ui')['NBadge']
const NBreadcrumb: typeof import('naive-ui')['NBreadcrumb'] const NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
const NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem'] const NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
const NButton: typeof import('naive-ui')['NButton'] const NButton: typeof import('naive-ui')['NButton']
const NButtonGroup: typeof import('naive-ui')['NButtonGroup']
const NCard: typeof import('naive-ui')['NCard'] const NCard: typeof import('naive-ui')['NCard']
const NCascader: typeof import('naive-ui')['NCascader']
const NCheckbox: typeof import('naive-ui')['NCheckbox'] const NCheckbox: typeof import('naive-ui')['NCheckbox']
const NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup'] const NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
const NCollapse: typeof import('naive-ui')['NCollapse']
const NCollapseItem: typeof import('naive-ui')['NCollapseItem']
const NColorPicker: typeof import('naive-ui')['NColorPicker'] const NColorPicker: typeof import('naive-ui')['NColorPicker']
const NDataTable: typeof import('naive-ui')['NDataTable'] const NDataTable: typeof import('naive-ui')['NDataTable']
const NDatePicker: typeof import('naive-ui')['NDatePicker'] const NDatePicker: typeof import('naive-ui')['NDatePicker']
const NDescriptions: typeof import('naive-ui')['NDescriptions']
const NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
const NDialogProvider: typeof import('naive-ui')['NDialogProvider'] const NDialogProvider: typeof import('naive-ui')['NDialogProvider']
const NDivider: typeof import('naive-ui')['NDivider'] const NDivider: typeof import('naive-ui')['NDivider']
const NDrawer: typeof import('naive-ui')['NDrawer'] const NDrawer: typeof import('naive-ui')['NDrawer']
const NDrawerContent: typeof import('naive-ui')['NDrawerContent'] const NDrawerContent: typeof import('naive-ui')['NDrawerContent']
const NDropdown: typeof import('naive-ui')['NDropdown'] const NDropdown: typeof import('naive-ui')['NDropdown']
const NDynamicInput: typeof import('naive-ui')['NDynamicInput']
const NEmpty: typeof import('naive-ui')['NEmpty'] const NEmpty: typeof import('naive-ui')['NEmpty']
const NFlex: typeof import('naive-ui')['NFlex']
const NForm: typeof import('naive-ui')['NForm'] const NForm: typeof import('naive-ui')['NForm']
const NFormItem: typeof import('naive-ui')['NFormItem'] const NFormItem: typeof import('naive-ui')['NFormItem']
const NFormItemGi: typeof import('naive-ui')['NFormItemGi'] const NFormItemGi: typeof import('naive-ui')['NFormItemGi']
const NGi: typeof import('naive-ui')['NGi'] const NGi: typeof import('naive-ui')['NGi']
const NGrid: typeof import('naive-ui')['NGrid'] const NGrid: typeof import('naive-ui')['NGrid']
const NIcon: typeof import('naive-ui')['NIcon']
const NInput: typeof import('naive-ui')['NInput'] const NInput: typeof import('naive-ui')['NInput']
const NInputGroup: typeof import('naive-ui')['NInputGroup'] const NInputGroup: typeof import('naive-ui')['NInputGroup']
const NInputNumber: typeof import('naive-ui')['NInputNumber'] const NInputNumber: typeof import('naive-ui')['NInputNumber']
const NIput: typeof import('naive-ui')['NIput']
const NList: typeof import('naive-ui')['NList']
const NListItem: typeof import('naive-ui')['NListItem']
const NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider'] const NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
const NMenu: typeof import('naive-ui')['NMenu'] const NMenu: typeof import('naive-ui')['NMenu']
const NMessageProvider: typeof import('naive-ui')['NMessageProvider'] const NMessageProvider: typeof import('naive-ui')['NMessageProvider']
const NModal: typeof import('naive-ui')['NModal'] const NModal: typeof import('naive-ui')['NModal']
const NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] const NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
const NPagination: typeof import('naive-ui')['NPagination']
const NPopconfirm: typeof import('naive-ui')['NPopconfirm']
const NPopover: typeof import('naive-ui')['NPopover']
const NProgress: typeof import('naive-ui')['NProgress']
const NRadio: typeof import('naive-ui')['NRadio'] const NRadio: typeof import('naive-ui')['NRadio']
const NRadioGroup: typeof import('naive-ui')['NRadioGroup'] const NRadioGroup: typeof import('naive-ui')['NRadioGroup']
const NScrollbar: typeof import('naive-ui')['NScrollbar'] const NScrollbar: typeof import('naive-ui')['NScrollbar']
const NSelect: typeof import('naive-ui')['NSelect'] const NSelect: typeof import('naive-ui')['NSelect']
const NSkeleton: typeof import('naive-ui')['NSkeleton']
const NSpace: typeof import('naive-ui')['NSpace'] const NSpace: typeof import('naive-ui')['NSpace']
const NSpin: typeof import('naive-ui')['NSpin']
const NStatistic: typeof import('naive-ui')['NStatistic']
const NStep: typeof import('naive-ui')['NStep']
const NSteps: typeof import('naive-ui')['NSteps']
const NSwitch: typeof import('naive-ui')['NSwitch'] const NSwitch: typeof import('naive-ui')['NSwitch']
const NTab: typeof import('naive-ui')['NTab'] const NTab: typeof import('naive-ui')['NTab']
const NTabPane: typeof import('naive-ui')['NTabPane']
const NTabs: typeof import('naive-ui')['NTabs'] const NTabs: typeof import('naive-ui')['NTabs']
const NTag: typeof import('naive-ui')['NTag'] const NTag: typeof import('naive-ui')['NTag']
const NThing: typeof import('naive-ui')['NThing']
const NTooltip: typeof import('naive-ui')['NTooltip'] const NTooltip: typeof import('naive-ui')['NTooltip']
const NTree: typeof import('naive-ui')['NTree']
const NWatermark: typeof import('naive-ui')['NWatermark'] const NWatermark: typeof import('naive-ui')['NWatermark']
const PinToggler: typeof import('./../components/common/pin-toggler.vue')['default'] const PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']
const ProCard: typeof import('pro-naive-ui')['ProCard']
const ProConfigProvider: typeof import('pro-naive-ui')['ProConfigProvider']
const ProDataTable: typeof import('pro-naive-ui')['ProDataTable']
const ProDate: typeof import('pro-naive-ui')['ProDate']
const ProEditDataTable: typeof import('pro-naive-ui')['ProEditDataTable']
const ProForm: typeof import('pro-naive-ui')['ProForm']
const ProFormList: typeof import('pro-naive-ui')['ProFormList']
const ProInput: typeof import('pro-naive-ui')['ProInput']
const ProSearchForm: typeof import('pro-naive-ui')['ProSearchForm']
const ProSelect: typeof import('pro-naive-ui')['ProSelect']
const ReloadButton: typeof import('./../components/common/reload-button.vue')['default'] const ReloadButton: typeof import('./../components/common/reload-button.vue')['default']
const RouterLink: typeof import('vue-router')['RouterLink'] const RouterLink: typeof import('vue-router')['RouterLink']
const RouterView: typeof import('vue-router')['RouterView'] const RouterView: typeof import('vue-router')['RouterView']
+8
View File
@@ -35,6 +35,10 @@ declare module "@elegant-router/types" {
"home_process": "/home/process"; "home_process": "/home/process";
"home_processdetail": "/home/processdetail"; "home_processdetail": "/home/processdetail";
"login": "/login/:module(pwd-login)?"; "login": "/login/:module(pwd-login)?";
"systemmanage": "/systemmanage";
"systemmanage_role": "/systemmanage/role";
"systemmanage_template": "/systemmanage/template";
"systemmanage_user": "/systemmanage/user";
}; };
/** /**
@@ -74,6 +78,7 @@ declare module "@elegant-router/types" {
| "docs" | "docs"
| "home" | "home"
| "login" | "login"
| "systemmanage"
>; >;
/** /**
@@ -105,6 +110,9 @@ declare module "@elegant-router/types" {
| "home_process" | "home_process"
| "home_processdetail" | "home_processdetail"
| "login" | "login"
| "systemmanage_role"
| "systemmanage_template"
| "systemmanage_user"
>; >;
/** /**
+3 -6
View File
@@ -217,7 +217,7 @@ const downloadFile = (file:any) => {
<Tag v-if="p.supplement" class="border-amber-200 bg-amber-100 px-1 py-0 text-[10px] text-amber-700">补充</Tag> <Tag v-if="p.supplement" class="border-amber-200 bg-amber-100 px-1 py-0 text-[10px] text-amber-700">补充</Tag>
</button> </button>
</template> </template>
<div v-else class="scroll-left"> <template v-else>
<div v-for="u in tree" :key="u.unit"> <div v-for="u in tree" :key="u.unit">
<button @click="openUnits[u.unit] = !openUnits[u.unit]" <button @click="openUnits[u.unit] = !openUnits[u.unit]"
class="flex w-full items-center gap-1.5 rounded-lg px-2 py-1.5 text-left text-xs font-medium hover:bg-slate-50 bg-transparent"> class="flex w-full items-center gap-1.5 rounded-lg px-2 py-1.5 text-left text-xs font-medium hover:bg-slate-50 bg-transparent">
@@ -246,7 +246,8 @@ const downloadFile = (file:any) => {
</div> </div>
</template> </template>
</div> </div>
</div>
</template>
</div> </div>
<!-- 归档包详情 --> <!-- 归档包详情 -->
<div v-if="sel" class="rounded-lg border col-span-2"> <div v-if="sel" class="rounded-lg border col-span-2">
@@ -324,10 +325,6 @@ const downloadFile = (file:any) => {
</style> </style>
<style scoped lang="scss"> <style scoped lang="scss">
.scroll-left {
height: calc(100vh - 365px);
overflow-y: auto;
}
.scroll-right { .scroll-right {
height: calc(100vh - 460px); height: calc(100vh - 460px);
overflow-y: auto; overflow-y: auto;
+19 -17
View File
@@ -42,22 +42,24 @@ const progressPct = (p: string) => {
<NSpace vertical :size="16"> <NSpace vertical :size="16">
<!-- 我的草稿箱 --> <!-- 我的草稿箱 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }"> <NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive class="pb-[16px]"> <div class="border mb-3 py-3 px-4" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NGi span="24 s:24 m:16"> <NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<div class="h-100% flex items-center gap-2"> <NGi span="24 s:24 m:16">
<Icon icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" /> <div class="h-100% flex items-center gap-2">
<p class="text-sm font-600">我的草稿箱</p> <Icon icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
<span class="rounded bg-slate-100 px-1.5 text-xs font-semibold text-slate-700">{{ drafts.length }}</span> <p class="text-sm font-600">我的草稿箱</p>
<p class="text-xs text-#94a3b8">草稿仅本人可见提交后自动从草稿箱移除</p> <span class="rounded bg-slate-100 px-1.5 text-xs font-semibold text-slate-700">{{ drafts.length }}</span>
</div> <p class="text-xs text-#94a3b8">草稿仅本人可见提交后自动从草稿箱移除</p>
</NGi> </div>
<NGi span="24 s:24 m:8" class="flex items-center justify-end gap-12px"> </NGi>
<NButton type="primary" size="small" @click="emit('go', 'apply')"> <NGi span="24 s:24 m:8" class="flex items-center justify-end gap-12px">
<Icon icon="ic:round-plus" class="size-18px" /> <NButton type="primary" size="small" @click="emit('go', 'apply')">
新建空白变更 <Icon icon="ic:round-plus" class="size-16px" />
</NButton> 新建空白变更
</NGi> </NButton>
</NGrid> </NGi>
</NGrid>
</div>
<div class="space-y-2 scroll"> <div class="space-y-2 scroll">
<div v-for="d in drafts" :key="d.id" class="rounded-lg border px-4 py-3"> <div v-for="d in drafts" :key="d.id" class="rounded-lg border px-4 py-3">
<div class="flex flex-wrap items-start gap-3"> <div class="flex flex-wrap items-start gap-3">
@@ -95,7 +97,7 @@ const progressPct = (p: string) => {
</style> </style>
<style scoped lang="scss"> <style scoped lang="scss">
.scroll { .scroll {
height: calc(100vh - 190px); height: calc(100vh - 212px);
overflow-y: auto; overflow-y: auto;
} }
</style> </style>
+266
View File
@@ -0,0 +1,266 @@
<script setup lang="ts">
import { ref, computed, h, onMounted } from 'vue';
import { useAppStore } from '@/store/modules/app';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { useRouter } from 'vue-router';
import { NButton, NPopconfirm } from 'naive-ui';
const router = useRouter();
const appStore = useAppStore();
const themeStore = useThemeStore();
const gap = computed(() => (appStore.isMobile ? 0 : 16));
const loading = ref<boolean>(false);
const btnLoading = ref<boolean>(false);
const currentType = ref<string>('add');
const formRef = ref<any>(null);
const currentRole = ref<{ id: number, name: string }>({
id: 0,
name: '',
});
const rules = ref({
name: { required: true, message: '角色名称不能为空', trigger: ['blur'] }
})
const roleModal = ref<boolean>(false);
const permissionModal = ref<boolean>(false);
const pagination = ref<{ page: number, pageSize: number, pageCount: number }>({
page: 1,
pageSize: 20,
pageCount: 0,
})
const columns = ref([
{
title: '序号',
key: 'index',
width: 5,
align: 'center',
render: (_row: any, index: number) => {
return h('span', {}, { default: () => (pagination.value.page - 1) * pagination.value.pageSize + index + 1 })
}
},
{
title: '角色名称',
key: 'name',
width: 44,
align: 'center',
},
{
title: '操作',
key: 'action',
width: 8,
align: 'center',
render: (row: any) => {
return h('div', { class: 'flex items-center justify-center gap-3' }, {
default: () => [
h(NButton, {
text: true,
size: 'tiny',
type: 'primary',
onClick: () => handleSetPermission(row)
}, {
// 渲染按钮文本
default: () => '设置权限'
}),
h(NButton, {
text: true,
size: 'tiny',
type: 'primary',
onClick: () => handleEdit(row)
}, {
// 渲染按钮文本
default: () => '编辑'
}),
h(NPopconfirm, {
// 组件 Props
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
handleDelete(row)
},
// 可选:自定义确认/取消按钮的样式
positiveButtonProps: { size: 'tiny', type: 'primary' },
negativeButtonProps: { size: 'tiny' }
}, {
// 插槽 (Slots)
trigger: () => h(NButton,
{
text: true,
size: 'tiny',
type: 'error',
},
{
default: () => '删除' // 按钮文字
}
),
default: () => '确定要删除吗?' // 弹出框的提示内容
}),
]
})
}
},
])
const list = ref<{ id: number, name: string }[]>([])
// 获取列表
const getList = async () => {
loading.value = true;
setTimeout(() => {
loading.value = false;
list.value = [
{id:1,name:'角色名称1'},
{id:2,name:'角色名称2'},
{id:3,name:'角色名称3'},
{id:4,name:'角色名称4'},
{id:5,name:'角色名称5'},
{id:6,name:'角色名称6'},
{id:7,name:'角色名称7'},
{id:8,name:'角色名称8'},
{id:9,name:'角色名称9'},
{id:10,name:'角色名称10'},
{id:11,name:'角色名称11'},
{id:12,name:'角色名称12'},
]
}, 1000)
}
// 打开添加角色弹框
const addRole = () => {
currentType.value = 'add';
currentRole.value = {
id: 0,
name: '',
};
roleModal.value = true;
}
// 设置权限
const handleSetPermission = (row: any) => {
currentRole.value = {
id: row.id,
name: row.name,
};
permissionModal.value = true;
}
// 编辑角色
const handleEdit = (row: any) => {
currentType.value = 'edit';
currentRole.value = {
id: row.id,
name: row.name,
};
roleModal.value = true;
}
// 删除角色
const handleDelete = (row: any) => {
loading.value = true;
setTimeout(() => {
loading.value = false;
list.value = list.value.filter((item: any) => item.id !== row.id);
window.$message?.success('删除成功')
}, 1000)
}
// 保存角色
const saveRole = async (e: MouseEvent) => {
e.preventDefault()
formRef.value?.validate((errors: any) => {
if (!errors) {
btnLoading.value = true;
setTimeout(() => {
// 新增
if(currentType.value === 'add'){
list.value.push({id:list.value.length+1,name:currentRole.value?.name})
}
// 编辑
if(currentType.value === 'edit'){
const target = list.value.find((item:any) => item.id === currentRole.value.id);
if (target) {
target.name = currentRole.value?.name;
}
}
window.$message?.success('操作成功')
btnLoading.value = false;
roleModal.value = false;
}, 1000)
}
})
}
onMounted(() => {
const allRoutes = router.getRoutes();
getList()
})
</script>
<template>
<NSpace vertical :size="16">
<!-- 角色管理 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="border mb-3 py-2 px-3" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:12 m:12">
<div class="h-100% flex items-center gap-2">
<Icon icon="lucide:user" class="size-16px" :style="{ color: themeStore.themeColor }" />
<p class="text-[16px] font-600">角色管理</p>
</div>
</NGi>
<NGi span="24 s:12 m:12" class="flex items-center justify-end gap-12px">
<NButton type="primary" size="small" @click="addRole">
<Icon icon="ic:round-plus" class="size-16px" />
添加角色
</NButton>
</NGi>
</NGrid>
</div>
<div class="scroll">
<NDataTable
size="small"
:loading="loading"
:single-line="false"
:columns="columns"
:data="list"
:pagination="pagination"
max-height="calc(100vh - 290px)"
/>
</div>
<!-- 新增或编辑弹框 -->
<NModal
v-model:show="roleModal"
preset="card"
:title="currentType === 'add' ? '新增角色' : '编辑角色'"
:auto-focus="false"
:style="{ width: '450px', height: 'auto' }"
:segmented="{ content: true, footer: true }"
>
<template #default>
<NForm ref="formRef" :model="currentRole" :rules="rules" label-placement="left" label-width="auto">
<NFormItem label="角色名称" path="name">
<NInput
v-model:value="currentRole.name"
placeholder="请输入角色名称"
/>
</NFormItem>
</NForm>
</template>
<template #footer>
<div class="flex justify-end gap-[10px]">
<NButton @click="roleModal = false">取消</NButton>
<NButton type="primary" :loading="btnLoading" @click="saveRole">确定</NButton>
</div>
</template>
</NModal>
<!-- 设置权限抽屉 -->
<NDrawer v-model:show="permissionModal" :width="502" placement="right">
<NDrawerContent :title="`设置权限-${currentRole.name}`" closable>
</NDrawerContent>
</NDrawer>
</NCard>
</NSpace>
</template>
<style scoped lang="scss">
.scroll {
height: calc(100vh - 205px);
overflow-y: auto;
}
</style>
+547
View File
@@ -0,0 +1,547 @@
<script setup lang="ts">
import { ref, computed, h, onMounted } from 'vue';
import { useAppStore } from '@/store/modules/app';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { useRouter } from 'vue-router';
import { NButton, NPopconfirm, NTag } from 'naive-ui';
const router = useRouter();
const appStore = useAppStore();
const themeStore = useThemeStore();
const gap = computed(() => (appStore.isMobile ? 0 : 16));
const loading = ref<boolean>(false);
const btnLoading = ref<boolean>(false);
const currentType = ref<string>('add');
const readonly = computed(() => currentType.value === "view");
const formRef = ref<any>(null);
const currentTemplate = ref<{ id: number, category: string | null, level: string | null, timeLimit: string | null, steps: any[] }>({
id: 0,
category: null,
level: null,
timeLimit: null,
steps: [],
});
const rules = ref({
category: { required: true, message: '变更类别不能为空', trigger: ['blur'] },
level: { required: true, message: '变更等级不能为空', trigger: ['blur'] },
timeLimit: { required: true, message: '变更时效不能为空', trigger: ['blur'] },
step: { required: true, message: '审批步骤不能为空', trigger: ['blur'] }
})
const templateModal = ref<boolean>(false);
const categoryOptions = ref([
{
label: '工艺',
value: '工艺'
},
{
label: '设备',
value: '设备'
},
{
label: '管理',
value: '管理'
}
])
const levelOptions = ref([
{
label: '一般',
value: '一般'
},
{
label: '重要',
value: '重要'
},
])
const timeLimitOptions = ref([
{
label: '永久',
value: '永久'
},
{
label: '临时',
value: '临时'
},
])
const SPECIALTIES = ref<{ label: string, value: string }[]>([
{ label: '工艺', value: 'process' },
{ label: '设备', value: 'equipment' },
{ label: '仪表', value: 'instrument' },
{ label: '电气', value: 'electrical' },
{ label: '安全', value: 'safety' },
{ label: '质量', value: 'quality' }
]);
const pagination = ref<{ page: number, pageSize: number, pageCount: number }>({
page: 1,
pageSize: 20,
pageCount: 0,
})
const columns = ref([
{
title: '序号',
key: 'index',
width: 6,
align: 'center',
render: (row: any, index: number) => {
return h('span', {}, { default: () => (pagination.value.page - 1) * pagination.value.pageSize + index + 1 })
}
},
{
title: '变更类别',
key: 'category',
width: 8,
align: 'center',
render: (row: any, index: number) => {
return h(NTag, {
size: 'small',
type: 'default'
}, { default: () => `${row.category}` })
}
},
{
title: '变更等级',
key: 'level',
width: 8,
align: 'center',
render: (row: any, index: number) => {
return h(NTag, {
size: 'small',
type: `${row.level==='重要'?'error':'default'}`
}, { default: () => `${row.level}` })
}
},
{
title: '变更时效',
key: 'timeLimit',
width: 8,
align: 'center',
render: (row: any, index: number) => {
return h(NTag, {
size: 'small',
type: `${row.timeLimit==='临时'?'warning':'info'}`
}, { default: () => `${row.timeLimit}` })
}
},
{
title: '审批步骤',
key: 'step',
width: 44,
align: 'center',
render: (row: any) => {
const stepNames = row.steps?.map((step: any) => step.name) || [];
const text = stepNames.join(' → ');
return h('p', { class: 'text-left text-xs' }, text || '');
}
},
{
title: '操作',
key: 'action',
width: 12,
align: 'center',
fixed: 'right',
render: (row: any) => {
return h('div', { class: 'flex items-center justify-center gap-3' }, {
default: () => [
h(NButton, {
text: true,
size: 'tiny',
type: 'primary',
onClick: () => handleView(row)
}, {
// 渲染按钮文本
default: () => '查看'
}),
h(NButton, {
text: true,
size: 'tiny',
type: 'primary',
onClick: () => handleEdit(row)
}, {
// 渲染按钮文本
default: () => '编辑'
}),
h(NPopconfirm, {
// 组件 Props
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
handleDelete(row)
},
// 可选:自定义确认/取消按钮的样式
positiveButtonProps: { size: 'tiny', type: 'primary' },
negativeButtonProps: { size: 'tiny' }
}, {
// 插槽 (Slots)
trigger: () => h(NButton,
{
text: true,
size: 'tiny',
type: 'error',
},
{
default: () => '删除' // 按钮文字
}
),
default: () => '确定要删除吗?' // 弹出框的提示内容
}),
]
})
}
},
])
const list = ref<{ id: number, category: string, level: string, timeLimit: string, steps: any[] }[]>([])
// 获取列表
const getList = async () => {
loading.value = true;
setTimeout(() => {
loading.value = false;
list.value = [
{id:1,category:'工艺',level:'重要',timeLimit:'临时',steps: [
{
id:101,
name:'部门审核',
countersign: false,
members: [{person:'测试人员01',specialty:'',countersign:false}]
},
{
id:102,
name:'专业审批',
countersign: false,
members: [{person:'测试人员02',specialty:'',countersign:false}]
},
{id: 103,
name: "部门负责人批准",
countersign: false,
members: [ {person:'测试人员03',specialty:'',countersign:false}]
}
]},
{id:2,category:'设备',level:'一般',timeLimit:'临时',steps:[
]},
{id:3,category:'管理',level:'重要',timeLimit:'永久',steps:[
]},
{id:4,category:'工艺',level:'重要',timeLimit:'临时',steps:[
]},
]
}, 1000)
}
// 打开添加模板弹框
const addTemplate = () => {
currentType.value = 'add';
currentTemplate.value = {
id: 0,
category: null,
level: null,
timeLimit: null,
steps: [],
};
templateModal.value = true;
}
// 查看模板
const handleView = (row: any) => {
currentType.value = 'view';
currentTemplate.value = {
id: row.id,
category: row.category,
level: row.level,
timeLimit: row.timeLimit,
steps: row.steps,
};
templateModal.value = true;
}
// 编辑角色
const handleEdit = (row: any) => {
currentType.value = 'edit';
currentTemplate.value = {
id: row.id,
category: row.category,
level: row.level,
timeLimit: row.timeLimit,
steps: row.steps,
};
templateModal.value = true;
}
// 删除角色
const handleDelete = (row: any) => {
loading.value = true;
setTimeout(() => {
loading.value = false;
list.value = list.value.filter((item: any) => item.id !== row.id);
window.$message?.success('删除成功')
}, 1000)
}
// 保存角色
const saveRole = async (e: MouseEvent) => {
e.preventDefault()
formRef.value?.validate((errors: any) => {
if (!errors) {
btnLoading.value = true;
setTimeout(() => {
// 新增
if(currentType.value === 'add'){
list.value.push({
id:list.value.length+1,
category:currentTemplate.value?.category as string,
level:currentTemplate.value?.level as string,
timeLimit:currentTemplate.value?.timeLimit as string,
steps:currentTemplate.value?.steps as any[]
})
}
// 编辑
if(currentType.value === 'edit'){
const target = list.value.find((item:any) => item.id === currentTemplate.value.id);
if (target) {
target.category = currentTemplate.value?.category as string;
target.level = currentTemplate.value?.level as string;
target.timeLimit = currentTemplate.value?.timeLimit as string;
target.steps = currentTemplate.value?.steps as any[];
}
}
window.$message?.success('操作成功')
btnLoading.value = false;
templateModal.value = false;
}, 1000)
}
})
}
// 会签步骤:一个模板最多一步;只有会签步骤能选专业。
// 勾选/取消后,所有非会签步骤的人员专业与「必须会签」标记一并清空
const setStepCountersign = (target: any, v: boolean) => {
currentTemplate.value.steps.forEach((s:any) => {
s.countersign = s === target ? v : false;
if (!s.countersign) s.members.forEach((m:any) => { m.specialty = ""; m.countersign = false; });
});
}
// 当前打开人员选择器的步骤(步骤行「+」按钮切换)
const pickerStepId = ref<number | null>(null);
const pick = ref<string | null>(null);
const togglePicker = (s: any) => {
pickerStepId.value = pickerStepId.value === s.id ? null : s.id;
pick.value = null;
}
const addMember = (s: any, v: string | null) => {
if (v && !s.members.some((m:any) => m.person === v)) s.members.push({ person: v, specialty: "", countersign: false });
pick.value = null;
pickerStepId.value = null;
}
const delMember = (s: any, p: string) => {
s.members = s.members.filter((m:any) => m.person !== p);
}
const removeStep = (idx: number) => {
currentTemplate.value.steps.splice(idx, 1);
}
let sid = 100;
const mkStep = (name: string, extra: Partial<any> = {}): any => ({ id: ++sid, name, countersign: false, members: [], ...extra });
const addStepAfter = (idx: number) => {
currentTemplate.value.steps.splice(idx + 1, 0, mkStep(""));
}
const ORG_TREE = [
{
id: "factory", name: "演示工厂", short: "YS", leaf: false, sort: 0,
children: [
{
id: "scb", name: "生产部", short: "SCB", leaf: false, sort: 0,
children: [
{ id: "cj1", name: "测试车间一", short: "CJ1", leaf: true, sort: 0 },
{ id: "cj2", name: "测试车间二", short: "CJ2", leaf: true, sort: 1 },
{ id: "cj3", name: "测试车间三", short: "CJ3", leaf: true, sort: 2 },
],
},
{
id: "ahb", name: "安环部", short: "AHB", leaf: false, sort: 1,
children: [
{ id: "aqk", name: "安全科", short: "AQK", leaf: true, sort: 0 },
{ id: "hbk", name: "环保科", short: "HBK", leaf: true, sort: 1 },
],
},
{
id: "sbb", name: "设备部", short: "SBB", leaf: false, sort: 2,
children: [
{ id: "sbk", name: "设备科", short: "SBK", leaf: true, sort: 0 },
],
},
{
id: "zlb", name: "质量部", short: "ZLB", leaf: false, sort: 3,
children: [
{ id: "zjk", name: "质检科", short: "ZJK", leaf: true, sort: 0 },
],
},
],
},
];
const LEVEL2_ORGS = ORG_TREE[0]?.children ?? [];
const ORG_MEMBERS = [
{ id: 1, name: "测试人员01", dept: "测试车间一", gender: "男", phone: "13800000001", email: "test01@demo.com", account: "TEST001", role: "操作人员", scope: ["factory"], enabled: true },
{ id: 2, name: "测试人员02", dept: "测试车间二", gender: "女", phone: "13800000002", email: "test02@demo.com", account: "TEST002", role: "班长", scope: ["factory"], enabled: true },
{ id: 3, name: "测试人员03", dept: "测试车间三", gender: "男", phone: "13800000003", email: "test03@demo.com", account: "TEST003", role: "车间主任", scope: ["factory"], enabled: true },
{ id: 4, name: "测试人员04", dept: "安全科", gender: "女", phone: "13800000004", email: "test04@demo.com", account: "TEST004", role: "安全工程师", scope: ["factory"], enabled: true },
{ id: 5, name: "测试人员05", dept: "环保科", gender: "男", phone: "13800000005", email: "test05@demo.com", account: "TEST005", role: "环保专员", scope: ["factory"], enabled: true },
{ id: 6, name: "测试人员06", dept: "设备科", gender: "男", phone: "13800000006", email: "test06@demo.com", account: "TEST006", role: "设备工程师", scope: ["factory"], enabled: true },
{ id: 7, name: "测试人员07", dept: "质检科", gender: "女", phone: "13800000007", email: "test07@demo.com", account: "TEST007", role: "质量工程师", scope: ["factory"], enabled: true },
];
function orgSubtreeNames(n: any): string[] {
return [n.name, ...(n.children ?? []).flatMap(orgSubtreeNames)];
}
// 预设人员:级联选项(第二层组织 → 组织下可选成员,已选成员不再出现,空组织不展示)
const peopleOpts = (s: any) =>
LEVEL2_ORGS.map((o) => ({
label: o.name,
value: o.id,
children: ORG_MEMBERS
.filter((m) => m.enabled && orgSubtreeNames(o).includes(m.dept) && !s.members.some((x) => x.person === m.name))
.map((m) => ({ label: m.name, value: m.name })),
})).filter((o) => o.children.length > 0);
onMounted(() => {
getList()
})
</script>
<template>
<NSpace vertical :size="16">
<!-- 角色管理 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="border mb-3 py-2 px-3" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
<NGi span="24 s:12 m:12">
<div class="h-100% flex items-center gap-2">
<Icon icon="carbon:flow-connection" class="size-18px" :style="{ color: themeStore.themeColor }" />
<p class="text-[16px] font-600">审批模板管理</p>
</div>
</NGi>
<NGi span="24 s:12 m:12" class="flex items-center justify-end gap-12px">
<NButton type="primary" size="small" @click="addTemplate">
<Icon icon="ic:round-plus" class="size-16px" />
添加模板
</NButton>
</NGi>
</NGrid>
</div>
<div class="scroll">
<NDataTable
size="small"
:loading="loading"
:single-line="false"
:columns="columns"
:data="list"
:pagination="pagination"
max-height="calc(100vh - 290px)"
:scroll-x="820"
/>
</div>
<!-- 新增/编辑/查看弹框 -->
<NModal
v-model:show="templateModal"
preset="card"
:title="currentType === 'add' ? '新增模板' : currentType === 'edit' ? '编辑模板':'查看模板'"
:auto-focus="false"
:style="{ width: '800px', height: 'auto' }"
:segmented="{ content: true, footer: true }"
>
<template #default>
<NForm class="w-full" ref="formRef" :model="currentTemplate" :rules="rules" label-placement="left" label-width="auto">
<NGrid :cols="24" :x-gap="24">
<NFormItemGi :span="8" label="变更类别" path="category">
<NSelect
v-model:value="currentTemplate.category"
:options="categoryOptions"
:disabled="readonly"
placeholder="请选择"
/>
</NFormItemGi>
<NFormItemGi :span="8" label="变更等级" path="level">
<NSelect
v-model:value="currentTemplate.level"
:options="levelOptions"
:disabled="readonly"
placeholder="请选择"
/>
</NFormItemGi>
<NFormItemGi :span="8" label="变更时效" path="timeLimit">
<NSelect
v-model:value="currentTemplate.timeLimit"
:options="timeLimitOptions"
:disabled="readonly"
placeholder="请选择"
/>
</NFormItemGi>
</NGrid>
<!-- 审批步骤 -->
<div class="flex flex-col gap-3 mb-3">
<div v-for="(s, idx) in currentTemplate.steps" :key="s.id" class="rounded-lg border border-slate-100 bg-slate-50/40 p-3">
<div class="flex flex-wrap items-center gap-2">
<span class="w-7 shrink-0 text-center text-xs text-slate-400">{{ idx + 1 }}</span>
<NInput v-model:value="s.name" maxlength="12" :disabled="readonly" placeholder="步骤名称(如:部门审核)" class="!w-48 text-sm" />
<NCheckbox :checked="s.countersign" :disabled="readonly" size="small" @update:checked="(v) => setStepCountersign(s, v)">
<span class="text-xs text-slate-500">专业会签步骤</span>
</NCheckbox>
<NButton v-if="!readonly" ghost type="primary" size="tiny" @click="togglePicker(s)" title="添加预设人员" class="ml-auto">
<Icon icon="ic:round-plus" class="size-16px" />
</NButton>
<NButton v-if="!readonly" text type="error" @click="removeStep(idx)" title="删除该步骤">
<Icon icon="iconamoon:trash" class="size-16px" />
</NButton>
</div>
<!-- 预设人员人员必选仅会签步骤可选择专业必选且人员可勾选必须进行专业会签 -->
<div class="mt-2 flex gap-2 pl-10">
<span class="shrink-0 text-xs leading-7 text-slate-400">预设人员:</span>
<div class="min-w-0 flex-1 space-y-1.5">
<div v-for="m in s.members" :key="m.person" class="flex min-h-7 flex-wrap items-center gap-2">
<span class="w-20 shrink-0 truncate text-xs font-medium text-slate-700">{{ m.person }}</span>
<template v-if="s.countersign">
<span class="text-xs text-slate-400"><span class="mr-0.5 text-red-500">*</span>专业:</span>
<NSelect :value="m.specialty || null" clearable size="small" placeholder="请选择专业" class="w-28"
:options="SPECIALTIES"
@update:value="(v) => m.specialty = v ?? ''"
/>
<NCheckbox :checked="m.countersign" size="small"
@update:checked="(v) => m.countersign = v"
>
<span class="text-xs text-slate-500">必须参与会签</span>
</NCheckbox>
</template>
<NButton v-if="!readonly" text type="error" @click="delMember(s, m.person)" title="删除该人员">
<Icon icon="iconamoon:trash" class="size-16px" />
</NButton>
</div>
<p v-if="s.countersign && s.members.length" class="text-[11px] leading-5 text-slate-400">
必须参与会签勾选的人员必须参与专业会签未勾选的人员为可选会签按需参加
</p>
<NCascader v-if="!readonly && pickerStepId === s.id" :value="pick" :options="peopleOpts(s)"
placeholder="请选择人员" size="small" check-strategy="child" :show-path="false" expand-trigger="click"
class="w-56" @update:value="(v) => addMember(s, v == null ? null : String(v))" />
<span v-if="readonly && s.members.length === 0" class="text-xs leading-7 text-slate-300">未预设</span>
</div>
</div>
</div>
</div>
<NButton v-if="!readonly" size="small" ghost dashed class="text-xs" @click="addStepAfter(currentTemplate.steps.length - 1)"><Icon icon="ic:round-plus" class="size-14px" />添加审批步骤</NButton>
</NForm>
</template>
<template #footer>
<div v-if="!readonly" class="flex justify-end gap-[10px]">
<NButton @click="templateModal = false">取消</NButton>
<NButton type="primary" :loading="btnLoading" @click="saveRole">确定</NButton>
</div>
</template>
</NModal>
</NCard>
</NSpace>
</template>
<style scoped lang="scss">
.scroll {
height: calc(100vh - 205px);
overflow-y: auto;
}
</style>
+30
View File
@@ -0,0 +1,30 @@
<script setup lang="ts">
import { ref, computed, h, onMounted } from 'vue';
import { useAppStore } from '@/store/modules/app';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { useRouter } from 'vue-router';
import { NButton, NPopconfirm } from 'naive-ui';
const router = useRouter();
const appStore = useAppStore();
const themeStore = useThemeStore();
const gap = computed(() => (appStore.isMobile ? 0 : 16));
onMounted(() => {
})
</script>
<template>
<NSpace vertical :size="16">
用户
</NSpace>
</template>
<style scoped lang="scss">
.scroll {
height: calc(100vh - 205px);
overflow-y: auto;
}
</style>