diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 2699c67..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(pnpm gen-route *)", - "Bash(pnpm typecheck *)", - "Bash(pnpm lint *)", - "WebFetch(domain:raw.githubusercontent.com)", - "Bash(Remove-Item *)", - "PowerShell(pnpm typecheck *)", - "WebFetch(domain:www.wangeditor.com)", - "Bash(node *)", - "Bash(dir \"rdms-project-boot-*\")", - "Bash(git stash *)", - "Bash(pnpm eslint *)", - "Bash(Select-String -Pattern \"business-rich-text-editor|task-operate-dialog\")", - "Bash(powershell *)" - ] - } -} diff --git a/.gitignore b/.gitignore index c844a81..94b43ea 100644 --- a/.gitignore +++ b/.gitignore @@ -38,5 +38,8 @@ yarn.lock /docs/* !/docs/frontend-page-resource-manifest.json +# Claude +/.claude/* + # Temp /codeTemp/* diff --git a/build/plugins/router.ts b/build/plugins/router.ts index f5543c7..a1dd133 100644 --- a/build/plugins/router.ts +++ b/build/plugins/router.ts @@ -27,8 +27,13 @@ export function setupElegantRouter() { onRouteMetaGen(routeName) { const key = routeName as RouteKey; - const constantRoutes: RouteKey[] = ['login', '403', '404', '500']; + const constantRoutes: RouteKey[] = ['login', '403', '404', '500', 'workbench']; const routeMetaMap: Partial>> = { + workbench: { + icon: 'mdi:view-dashboard-outline', + order: 1, + keepAlive: true + }, product: { icon: 'carbon:product', order: 4 @@ -79,6 +84,73 @@ export function setupElegantRouter() { hideInMenu: true, activeMenu: 'project_list' }, + ticket: { + icon: 'mdi:ticket-confirmation-outline', + order: 6 + }, + 'ticket_my-submitted': { + icon: 'mdi:upload-outline', + order: 1, + keepAlive: true + }, + 'ticket_my-pending': { + icon: 'mdi:inbox-arrow-down-outline', + order: 2, + keepAlive: true + }, + metrics: { + icon: 'mdi:chart-line', + order: 7 + }, + 'metrics_project-progress': { + icon: 'mdi:progress-clock', + order: 1, + keepAlive: true + }, + 'metrics_member-efficiency': { + icon: 'mdi:account-multiple-check-outline', + order: 2, + keepAlive: true + }, + metrics_worktime: { + icon: 'mdi:clock-time-five-outline', + order: 3, + keepAlive: true + }, + 'personal-center': { + icon: 'mdi:account-circle-outline', + order: 8 + }, + 'personal-center_my-profile': { + icon: 'mdi:account-box-outline', + order: 0, + keepAlive: true + }, + 'personal-center_my-weekly': { + icon: 'mdi:calendar-week-outline', + order: 1, + keepAlive: true + }, + 'personal-center_my-monthly': { + icon: 'mdi:calendar-month-outline', + order: 2, + keepAlive: true + }, + 'personal-center_my-performance': { + icon: 'mdi:trophy-outline', + order: 3, + keepAlive: true + }, + 'personal-center_my-application': { + icon: 'mdi:file-document-outline', + order: 4, + keepAlive: true + }, + 'personal-center_pending-approval': { + icon: 'mdi:check-decagram-outline', + order: 5, + keepAlive: true + }, system: { icon: 'carbon:cloud-service-management', order: 9, @@ -110,6 +182,20 @@ export function setupElegantRouter() { hideInMenu: true, roles: ['R_ADMIN'], activeMenu: 'system_user' + }, + infra: { + icon: 'ep:monitor', + order: 20 + }, + 'infra_state-machine': { + icon: 'mdi:state-machine', + order: 1, + keepAlive: true + }, + 'infra_rd-code': { + icon: 'mdi:identifier', + order: 2, + keepAlive: true } }; diff --git a/docs/frontend-page-resource-manifest.json b/docs/frontend-page-resource-manifest.json index 16adfcb..e531a4b 100644 --- a/docs/frontend-page-resource-manifest.json +++ b/docs/frontend-page-resource-manifest.json @@ -1,13 +1,46 @@ { - "generatedAt": "2026-04-29T08:18:14.397Z", + "generatedAt": "2026-05-13T10:54:08.684Z", "description": "Frontend visible page resource whitelist for backend route/menu configuration.", "rules": { "directoryComponent": "layout.base", "pageComponentPattern": "view.", "singlePageComponentPattern": "layout.$view." }, - "total": 8, + "total": 21, "items": [ + { + "name": "workbench", + "path": "/workbench", + "component": "layout.base$view.workbench", + "title": "workbench", + "routeTitle": "workbench", + "i18nKey": "route.workbench", + "icon": "mdi:view-dashboard-outline", + "localIcon": null, + "order": 1, + "hideInMenu": false, + "keepAlive": true, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null, + "redirect": null, + "props": null, + "meta": { + "title": "workbench", + "i18nKey": "route.workbench", + "icon": "mdi:view-dashboard-outline", + "localIcon": null, + "order": 1, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null + }, + "parentName": null, + "pageType": "single", + "source": "generated" + }, { "name": "product_list", "path": "/product/list", @@ -74,6 +107,336 @@ "pageType": "leaf", "source": "generated" }, + { + "name": "ticket_my-submitted", + "path": "/ticket/my-submitted", + "component": "view.ticket_my-submitted", + "title": "ticket_my-submitted", + "routeTitle": "ticket_my-submitted", + "i18nKey": "route.ticket_my-submitted", + "icon": "mdi:upload-outline", + "localIcon": null, + "order": 1, + "hideInMenu": false, + "keepAlive": true, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null, + "redirect": null, + "props": null, + "meta": { + "title": "ticket_my-submitted", + "i18nKey": "route.ticket_my-submitted", + "icon": "mdi:upload-outline", + "localIcon": null, + "order": 1, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null + }, + "parentName": "ticket", + "pageType": "leaf", + "source": "generated" + }, + { + "name": "ticket_my-pending", + "path": "/ticket/my-pending", + "component": "view.ticket_my-pending", + "title": "ticket_my-pending", + "routeTitle": "ticket_my-pending", + "i18nKey": "route.ticket_my-pending", + "icon": "mdi:inbox-arrow-down-outline", + "localIcon": null, + "order": 2, + "hideInMenu": false, + "keepAlive": true, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null, + "redirect": null, + "props": null, + "meta": { + "title": "ticket_my-pending", + "i18nKey": "route.ticket_my-pending", + "icon": "mdi:inbox-arrow-down-outline", + "localIcon": null, + "order": 2, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null + }, + "parentName": "ticket", + "pageType": "leaf", + "source": "generated" + }, + { + "name": "metrics_project-progress", + "path": "/metrics/project-progress", + "component": "view.metrics_project-progress", + "title": "metrics_project-progress", + "routeTitle": "metrics_project-progress", + "i18nKey": "route.metrics_project-progress", + "icon": "mdi:progress-clock", + "localIcon": null, + "order": 1, + "hideInMenu": false, + "keepAlive": true, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null, + "redirect": null, + "props": null, + "meta": { + "title": "metrics_project-progress", + "i18nKey": "route.metrics_project-progress", + "icon": "mdi:progress-clock", + "localIcon": null, + "order": 1, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null + }, + "parentName": "metrics", + "pageType": "leaf", + "source": "generated" + }, + { + "name": "metrics_member-efficiency", + "path": "/metrics/member-efficiency", + "component": "view.metrics_member-efficiency", + "title": "metrics_member-efficiency", + "routeTitle": "metrics_member-efficiency", + "i18nKey": "route.metrics_member-efficiency", + "icon": "mdi:account-multiple-check-outline", + "localIcon": null, + "order": 2, + "hideInMenu": false, + "keepAlive": true, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null, + "redirect": null, + "props": null, + "meta": { + "title": "metrics_member-efficiency", + "i18nKey": "route.metrics_member-efficiency", + "icon": "mdi:account-multiple-check-outline", + "localIcon": null, + "order": 2, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null + }, + "parentName": "metrics", + "pageType": "leaf", + "source": "generated" + }, + { + "name": "metrics_worktime", + "path": "/metrics/worktime", + "component": "view.metrics_worktime", + "title": "metrics_worktime", + "routeTitle": "metrics_worktime", + "i18nKey": "route.metrics_worktime", + "icon": "mdi:clock-time-five-outline", + "localIcon": null, + "order": 3, + "hideInMenu": false, + "keepAlive": true, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null, + "redirect": null, + "props": null, + "meta": { + "title": "metrics_worktime", + "i18nKey": "route.metrics_worktime", + "icon": "mdi:clock-time-five-outline", + "localIcon": null, + "order": 3, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null + }, + "parentName": "metrics", + "pageType": "leaf", + "source": "generated" + }, + { + "name": "personal-center_my-weekly", + "path": "/personal-center/my-weekly", + "component": "view.personal-center_my-weekly", + "title": "personal-center_my-weekly", + "routeTitle": "personal-center_my-weekly", + "i18nKey": "route.personal-center_my-weekly", + "icon": "mdi:calendar-week-outline", + "localIcon": null, + "order": 1, + "hideInMenu": false, + "keepAlive": true, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null, + "redirect": null, + "props": null, + "meta": { + "title": "personal-center_my-weekly", + "i18nKey": "route.personal-center_my-weekly", + "icon": "mdi:calendar-week-outline", + "localIcon": null, + "order": 1, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null + }, + "parentName": "personal-center", + "pageType": "leaf", + "source": "generated" + }, + { + "name": "personal-center_my-monthly", + "path": "/personal-center/my-monthly", + "component": "view.personal-center_my-monthly", + "title": "personal-center_my-monthly", + "routeTitle": "personal-center_my-monthly", + "i18nKey": "route.personal-center_my-monthly", + "icon": "mdi:calendar-month-outline", + "localIcon": null, + "order": 2, + "hideInMenu": false, + "keepAlive": true, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null, + "redirect": null, + "props": null, + "meta": { + "title": "personal-center_my-monthly", + "i18nKey": "route.personal-center_my-monthly", + "icon": "mdi:calendar-month-outline", + "localIcon": null, + "order": 2, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null + }, + "parentName": "personal-center", + "pageType": "leaf", + "source": "generated" + }, + { + "name": "personal-center_my-performance", + "path": "/personal-center/my-performance", + "component": "view.personal-center_my-performance", + "title": "personal-center_my-performance", + "routeTitle": "personal-center_my-performance", + "i18nKey": "route.personal-center_my-performance", + "icon": "mdi:trophy-outline", + "localIcon": null, + "order": 3, + "hideInMenu": false, + "keepAlive": true, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null, + "redirect": null, + "props": null, + "meta": { + "title": "personal-center_my-performance", + "i18nKey": "route.personal-center_my-performance", + "icon": "mdi:trophy-outline", + "localIcon": null, + "order": 3, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null + }, + "parentName": "personal-center", + "pageType": "leaf", + "source": "generated" + }, + { + "name": "personal-center_my-application", + "path": "/personal-center/my-application", + "component": "view.personal-center_my-application", + "title": "personal-center_my-application", + "routeTitle": "personal-center_my-application", + "i18nKey": "route.personal-center_my-application", + "icon": "mdi:file-document-outline", + "localIcon": null, + "order": 4, + "hideInMenu": false, + "keepAlive": true, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null, + "redirect": null, + "props": null, + "meta": { + "title": "personal-center_my-application", + "i18nKey": "route.personal-center_my-application", + "icon": "mdi:file-document-outline", + "localIcon": null, + "order": 4, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null + }, + "parentName": "personal-center", + "pageType": "leaf", + "source": "generated" + }, + { + "name": "personal-center_pending-approval", + "path": "/personal-center/pending-approval", + "component": "view.personal-center_pending-approval", + "title": "personal-center_pending-approval", + "routeTitle": "personal-center_pending-approval", + "i18nKey": "route.personal-center_pending-approval", + "icon": "mdi:check-decagram-outline", + "localIcon": null, + "order": 5, + "hideInMenu": false, + "keepAlive": true, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null, + "redirect": null, + "props": null, + "meta": { + "title": "personal-center_pending-approval", + "i18nKey": "route.personal-center_pending-approval", + "icon": "mdi:check-decagram-outline", + "localIcon": null, + "order": 5, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null + }, + "parentName": "personal-center", + "pageType": "leaf", + "source": "generated" + }, { "name": "system_user", "path": "/system/user", @@ -271,6 +634,72 @@ "parentName": "system", "pageType": "leaf", "source": "generated" + }, + { + "name": "infra_state-machine", + "path": "/infra/state-machine", + "component": "view.infra_state-machine", + "title": "infra_state-machine", + "routeTitle": "infra_state-machine", + "i18nKey": "route.infra_state-machine", + "icon": "mdi:state-machine", + "localIcon": null, + "order": 1, + "hideInMenu": false, + "keepAlive": true, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null, + "redirect": null, + "props": null, + "meta": { + "title": "infra_state-machine", + "i18nKey": "route.infra_state-machine", + "icon": "mdi:state-machine", + "localIcon": null, + "order": 1, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null + }, + "parentName": "infra", + "pageType": "leaf", + "source": "generated" + }, + { + "name": "infra_rd-code", + "path": "/infra/rd-code", + "component": "view.infra_rd-code", + "title": "infra_rd-code", + "routeTitle": "infra_rd-code", + "i18nKey": "route.infra_rd-code", + "icon": "mdi:identifier", + "localIcon": null, + "order": 2, + "hideInMenu": false, + "keepAlive": true, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null, + "redirect": null, + "props": null, + "meta": { + "title": "infra_rd-code", + "i18nKey": "route.infra_rd-code", + "icon": "mdi:identifier", + "localIcon": null, + "order": 2, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null + }, + "parentName": "infra", + "pageType": "leaf", + "source": "generated" } ] } diff --git a/src/components/custom/look-forward.vue b/src/components/custom/look-forward.vue index 0d1de23..a854cce 100644 --- a/src/components/custom/look-forward.vue +++ b/src/components/custom/look-forward.vue @@ -2,6 +2,13 @@ import { $t } from '@/locales'; defineOptions({ name: 'LookForward' }); + +interface Props { + title?: string; + subtitle?: string; +} + +defineProps(); diff --git a/src/layouts/modules/global-header/components/user-avatar.vue b/src/layouts/modules/global-header/components/user-avatar.vue index 1ace8b1..3900bf7 100644 --- a/src/layouts/modules/global-header/components/user-avatar.vue +++ b/src/layouts/modules/global-header/components/user-avatar.vue @@ -18,7 +18,7 @@ function loginOrRegister() { toLogin(); } -type DropdownKey = 'user-center' | 'logout'; +type DropdownKey = 'personal-center_my-profile' | 'logout'; type DropdownOption = { key: DropdownKey; @@ -29,8 +29,8 @@ type DropdownOption = { const options = computed(() => { const opts: DropdownOption[] = [ { - label: $t('common.userCenter'), - key: 'user-center', + label: $t('common.myProfile'), + key: 'personal-center_my-profile', icon: SvgIconVNode({ icon: 'ph:user-circle', fontSize: 18 }) }, { diff --git a/src/locales/langs/en-us.ts b/src/locales/langs/en-us.ts index c0d2c99..6689d3f 100644 --- a/src/locales/langs/en-us.ts +++ b/src/locales/langs/en-us.ts @@ -40,7 +40,7 @@ const local: App.I18n.Schema = { trigger: 'Trigger', update: 'Update', updateSuccess: 'Update Success', - userCenter: 'User Center', + myProfile: 'My Profile', yesOrNo: { yes: 'Yes', no: 'No' @@ -158,7 +158,24 @@ const local: App.I18n.Schema = { 404: 'Page Not Found', 500: 'Server Error', 'iframe-page': 'Iframe', - 'user-center': 'User Center', + workbench: 'Workbench', + ticket: 'Ticket', + 'ticket_my-submitted': 'My Submitted', + 'ticket_my-pending': 'My Pending', + metrics: 'Metrics', + 'metrics_project-progress': 'Project Progress', + 'metrics_member-efficiency': 'Member Efficiency', + metrics_worktime: 'Worktime', + 'personal-center': 'Personal Center', + 'personal-center_my-profile': 'My Profile', + 'personal-center_my-weekly': 'My Weekly Report', + 'personal-center_my-monthly': 'My Monthly Report', + 'personal-center_my-performance': 'My Performance', + 'personal-center_my-application': 'My Application', + 'personal-center_pending-approval': 'Pending Approval', + infra: 'Infra', + 'infra_state-machine': 'State Machine', + 'infra_rd-code': 'R&D Code', function: 'System Function', function_tab: 'Tab', 'function_multi-tab': 'Multi Tab', @@ -495,6 +512,7 @@ const local: App.I18n.Schema = { orgType: { company: 'Company', dept: 'Department', + function: 'Functional Department', direction: 'Direction', team: 'Team' }, diff --git a/src/locales/langs/zh-cn.ts b/src/locales/langs/zh-cn.ts index b2866f9..e789e47 100644 --- a/src/locales/langs/zh-cn.ts +++ b/src/locales/langs/zh-cn.ts @@ -40,7 +40,7 @@ const local: App.I18n.Schema = { trigger: '触发', update: '更新', updateSuccess: '更新成功', - userCenter: '个人中心', + myProfile: '个人信息', yesOrNo: { yes: '是', no: '否' @@ -158,7 +158,24 @@ const local: App.I18n.Schema = { 404: '页面不存在', 500: '服务器错误', 'iframe-page': '外链页面', - 'user-center': '个人中心', + workbench: '工作台', + ticket: '工单', + 'ticket_my-submitted': '我提交的工单', + 'ticket_my-pending': '待我处理的工单', + metrics: '效能度量', + 'metrics_project-progress': '项目进度', + 'metrics_member-efficiency': '员工能效', + metrics_worktime: '工时统计', + 'personal-center': '个人中心', + 'personal-center_my-profile': '个人信息', + 'personal-center_my-weekly': '我的周报', + 'personal-center_my-monthly': '我的月报', + 'personal-center_my-performance': '我的绩效', + 'personal-center_my-application': '我的申请', + 'personal-center_pending-approval': '待我审批', + infra: '基础设施', + 'infra_state-machine': '状态机管理', + 'infra_rd-code': '研发令号', function: '系统功能', function_tab: '标签页', 'function_multi-tab': '多标签页', @@ -491,6 +508,7 @@ const local: App.I18n.Schema = { orgType: { company: '公司', dept: '部门', + function: '职能部门', direction: '方向', team: '团队' }, diff --git a/src/router/elegant/imports.ts b/src/router/elegant/imports.ts index bdc123c..e093da4 100644 --- a/src/router/elegant/imports.ts +++ b/src/router/elegant/imports.ts @@ -28,6 +28,17 @@ export const views: Record Promise import("@/views/function/super-page/index.vue"), function_tab: () => import("@/views/function/tab/index.vue"), "function_toggle-auth": () => import("@/views/function/toggle-auth/index.vue"), + "infra_rd-code": () => import("@/views/infra/rd-code/index.vue"), + "infra_state-machine": () => import("@/views/infra/state-machine/index.vue"), + "metrics_member-efficiency": () => import("@/views/metrics/member-efficiency/index.vue"), + "metrics_project-progress": () => import("@/views/metrics/project-progress/index.vue"), + metrics_worktime: () => import("@/views/metrics/worktime/index.vue"), + "personal-center_my-application": () => import("@/views/personal-center/my-application/index.vue"), + "personal-center_my-monthly": () => import("@/views/personal-center/my-monthly/index.vue"), + "personal-center_my-performance": () => import("@/views/personal-center/my-performance/index.vue"), + "personal-center_my-profile": () => import("@/views/personal-center/my-profile/index.vue"), + "personal-center_my-weekly": () => import("@/views/personal-center/my-weekly/index.vue"), + "personal-center_pending-approval": () => import("@/views/personal-center/pending-approval/index.vue"), plugin_barcode: () => import("@/views/plugin/barcode/index.vue"), plugin_charts_antv: () => import("@/views/plugin/charts/antv/index.vue"), plugin_charts_echarts: () => import("@/views/plugin/charts/echarts/index.vue"), @@ -63,5 +74,7 @@ export const views: Record Promise import("@/views/system/user-detail/[id].vue"), "system_user-management-relation": () => import("@/views/system/user-management-relation/index.vue"), system_user: () => import("@/views/system/user/index.vue"), - "user-center": () => import("@/views/user-center/index.vue"), + "ticket_my-pending": () => import("@/views/ticket/my-pending/index.vue"), + "ticket_my-submitted": () => import("@/views/ticket/my-submitted/index.vue"), + workbench: () => import("@/views/workbench/index.vue"), }; diff --git a/src/router/elegant/routes.ts b/src/router/elegant/routes.ts index dfb1692..f085092 100644 --- a/src/router/elegant/routes.ts +++ b/src/router/elegant/routes.ts @@ -170,6 +170,43 @@ export const generatedRoutes: GeneratedRoute[] = [ keepAlive: true } }, + { + name: 'infra', + path: '/infra', + component: 'layout.base', + meta: { + title: 'infra', + i18nKey: 'route.infra', + icon: 'ep:monitor', + order: 20 + }, + children: [ + { + name: 'infra_rd-code', + path: '/infra/rd-code', + component: 'view.infra_rd-code', + meta: { + title: 'infra_rd-code', + i18nKey: 'route.infra_rd-code', + icon: 'mdi:identifier', + order: 2, + keepAlive: true + } + }, + { + name: 'infra_state-machine', + path: '/infra/state-machine', + component: 'view.infra_state-machine', + meta: { + title: 'infra_state-machine', + i18nKey: 'route.infra_state-machine', + icon: 'mdi:state-machine', + order: 1, + keepAlive: true + } + } + ] + }, { name: 'login', path: '/login/:module(pwd-login|reset-pwd)?', @@ -182,6 +219,140 @@ export const generatedRoutes: GeneratedRoute[] = [ hideInMenu: true } }, + { + name: 'metrics', + path: '/metrics', + component: 'layout.base', + meta: { + title: 'metrics', + i18nKey: 'route.metrics', + icon: 'mdi:chart-line', + order: 7 + }, + children: [ + { + name: 'metrics_member-efficiency', + path: '/metrics/member-efficiency', + component: 'view.metrics_member-efficiency', + meta: { + title: 'metrics_member-efficiency', + i18nKey: 'route.metrics_member-efficiency', + icon: 'mdi:account-multiple-check-outline', + order: 2, + keepAlive: true + } + }, + { + name: 'metrics_project-progress', + path: '/metrics/project-progress', + component: 'view.metrics_project-progress', + meta: { + title: 'metrics_project-progress', + i18nKey: 'route.metrics_project-progress', + icon: 'mdi:progress-clock', + order: 1, + keepAlive: true + } + }, + { + name: 'metrics_worktime', + path: '/metrics/worktime', + component: 'view.metrics_worktime', + meta: { + title: 'metrics_worktime', + i18nKey: 'route.metrics_worktime', + icon: 'mdi:clock-time-five-outline', + order: 3, + keepAlive: true + } + } + ] + }, + { + name: 'personal-center', + path: '/personal-center', + component: 'layout.base', + meta: { + title: 'personal-center', + i18nKey: 'route.personal-center', + icon: 'mdi:account-circle-outline', + order: 8 + }, + children: [ + { + name: 'personal-center_my-application', + path: '/personal-center/my-application', + component: 'view.personal-center_my-application', + meta: { + title: 'personal-center_my-application', + i18nKey: 'route.personal-center_my-application', + icon: 'mdi:file-document-outline', + order: 4, + keepAlive: true + } + }, + { + name: 'personal-center_my-monthly', + path: '/personal-center/my-monthly', + component: 'view.personal-center_my-monthly', + meta: { + title: 'personal-center_my-monthly', + i18nKey: 'route.personal-center_my-monthly', + icon: 'mdi:calendar-month-outline', + order: 2, + keepAlive: true + } + }, + { + name: 'personal-center_my-performance', + path: '/personal-center/my-performance', + component: 'view.personal-center_my-performance', + meta: { + title: 'personal-center_my-performance', + i18nKey: 'route.personal-center_my-performance', + icon: 'mdi:trophy-outline', + order: 3, + keepAlive: true + } + }, + { + name: 'personal-center_my-profile', + path: '/personal-center/my-profile', + component: 'view.personal-center_my-profile', + meta: { + title: 'personal-center_my-profile', + i18nKey: 'route.personal-center_my-profile', + icon: 'mdi:account-box-outline', + order: 0, + keepAlive: true + } + }, + { + name: 'personal-center_my-weekly', + path: '/personal-center/my-weekly', + component: 'view.personal-center_my-weekly', + meta: { + title: 'personal-center_my-weekly', + i18nKey: 'route.personal-center_my-weekly', + icon: 'mdi:calendar-week-outline', + order: 1, + keepAlive: true + } + }, + { + name: 'personal-center_pending-approval', + path: '/personal-center/pending-approval', + component: 'view.personal-center_pending-approval', + meta: { + title: 'personal-center_pending-approval', + i18nKey: 'route.personal-center_pending-approval', + icon: 'mdi:check-decagram-outline', + order: 5, + keepAlive: true + } + } + ] + }, { name: 'plugin', path: '/plugin', @@ -664,13 +835,53 @@ export const generatedRoutes: GeneratedRoute[] = [ ] }, { - name: 'user-center', - path: '/user-center', - component: 'layout.base$view.user-center', + name: 'ticket', + path: '/ticket', + component: 'layout.base', meta: { - title: 'user-center', - i18nKey: 'route.user-center', - hideInMenu: true + title: 'ticket', + i18nKey: 'route.ticket', + icon: 'mdi:ticket-confirmation-outline', + order: 6 + }, + children: [ + { + name: 'ticket_my-pending', + path: '/ticket/my-pending', + component: 'view.ticket_my-pending', + meta: { + title: 'ticket_my-pending', + i18nKey: 'route.ticket_my-pending', + icon: 'mdi:inbox-arrow-down-outline', + order: 2, + keepAlive: true + } + }, + { + name: 'ticket_my-submitted', + path: '/ticket/my-submitted', + component: 'view.ticket_my-submitted', + meta: { + title: 'ticket_my-submitted', + i18nKey: 'route.ticket_my-submitted', + icon: 'mdi:upload-outline', + order: 1, + keepAlive: true + } + } + ] + }, + { + name: 'workbench', + path: '/workbench', + component: 'layout.base$view.workbench', + meta: { + title: 'workbench', + i18nKey: 'route.workbench', + icon: 'mdi:view-dashboard-outline', + order: 1, + keepAlive: true, + constant: true } } ]; diff --git a/src/router/elegant/transform.ts b/src/router/elegant/transform.ts index d9a9345..18cbe25 100644 --- a/src/router/elegant/transform.ts +++ b/src/router/elegant/transform.ts @@ -181,7 +181,21 @@ const routeMap: RouteMap = { "function_tab": "/function/tab", "function_toggle-auth": "/function/toggle-auth", "iframe-page": "/iframe-page/:url", + "infra": "/infra", + "infra_rd-code": "/infra/rd-code", + "infra_state-machine": "/infra/state-machine", "login": "/login/:module(pwd-login|reset-pwd)?", + "metrics": "/metrics", + "metrics_member-efficiency": "/metrics/member-efficiency", + "metrics_project-progress": "/metrics/project-progress", + "metrics_worktime": "/metrics/worktime", + "personal-center": "/personal-center", + "personal-center_my-application": "/personal-center/my-application", + "personal-center_my-monthly": "/personal-center/my-monthly", + "personal-center_my-performance": "/personal-center/my-performance", + "personal-center_my-profile": "/personal-center/my-profile", + "personal-center_my-weekly": "/personal-center/my-weekly", + "personal-center_pending-approval": "/personal-center/pending-approval", "plugin": "/plugin", "plugin_barcode": "/plugin/barcode", "plugin_charts": "/plugin/charts", @@ -226,7 +240,10 @@ const routeMap: RouteMap = { "system_user": "/system/user", "system_user-detail": "/system/user-detail/:id", "system_user-management-relation": "/system/user-management-relation", - "user-center": "/user-center" + "ticket": "/ticket", + "ticket_my-pending": "/ticket/my-pending", + "ticket_my-submitted": "/ticket/my-submitted", + "workbench": "/workbench" }; /** diff --git a/src/service/api/project.ts b/src/service/api/project.ts index 721120b..8748387 100644 --- a/src/service/api/project.ts +++ b/src/service/api/project.ts @@ -48,6 +48,16 @@ type ProjectPageResponse = Api.Project.PageResult; type ProjectExecutionPageResponse = Api.Project.PageResult; type ProjectTaskPageResponse = Api.Project.PageResult; type StatusBoardResponse = Api.Project.StatusBoard; +type ProjectTaskBoardPageResponse = { + items: Array<{ + statusCode: string; + statusName: string; + sort: number; + terminal?: boolean; + list: ProjectTaskResponse[]; + total: number; + }>; +}; type ProjectContextResponse = Omit & { currentProject: Omit & { id: string | number }; @@ -523,6 +533,32 @@ export function fetchGetProjectTaskStatusBoard( }); } +/** + * 任务看板按状态分组的分页接口。 + * + * 看板模式专用:一次请求拿到所有列(或指定列)的首屏 + 总数,替代"5 列 5 次 page"的旧方式。 + * 列内向下滚续页时再传 `statusCode=[X]&pageNo=N+1` 单列查询。 + */ +export async function fetchGetProjectTaskBoardPage( + projectId: string, + executionId: string, + params?: Api.Project.ProjectTaskBoardPageParams +) { + const result = await request({ + ...safeJsonRequestConfig, + url: `${getTaskPrefix(projectId, executionId)}/board-page`, + method: 'get', + params + }); + + return mapServiceResult(result as ServiceRequestResult, data => ({ + items: data.items.map(item => ({ + ...item, + list: item.list.map(normalizeProjectTask) + })) + })); +} + /** 获取项目任务详情 */ export async function fetchGetProjectTask(projectId: string, executionId: string, taskId: string) { const result = await request({ diff --git a/src/service/api/workbench.ts b/src/service/api/workbench.ts new file mode 100644 index 0000000..a987a0a --- /dev/null +++ b/src/service/api/workbench.ts @@ -0,0 +1,8 @@ +// 工作台聚合接口尚未开通,当前页面使用 src/views/workbench/mock.ts 的本地假数据。 +// 接口契约确认后,在此处补: +// - fetchGetWorkbenchSummary (Banner 摘要 + KPI) +// - fetchGetWorkbenchTodos (我的待办) +// - fetchGetWorkbenchActivity (最近动态) +// - fetchGetWorkbenchProjects (我参与的项目) +// 全部走 src/service/request/index.ts 的统一实例,并保持 ID 字符串口径。 +export {}; diff --git a/src/store/modules/route/index.ts b/src/store/modules/route/index.ts index 1fce824..b236653 100644 --- a/src/store/modules/route/index.ts +++ b/src/store/modules/route/index.ts @@ -242,7 +242,10 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => { /** 统一处理常量路由和权限路由 */ async function handleConstantAndAuthRoutes() { const { getAuthVueRoutes } = await loadRouteModule(); - const allRoutes = [...constantRoutes.value, ...authRoutes.value]; + // 常量路由优先:动态权限路由中与常量路由 name 重复的项剔除,避免菜单出现重复入口(如 workbench) + const constantRouteNames = new Set(constantRoutes.value.map(route => route.name)); + const dedupedAuthRoutes = authRoutes.value.filter(route => !constantRouteNames.has(route.name)); + const allRoutes = [...constantRoutes.value, ...dedupedAuthRoutes]; const sortRoutes = sortRoutesByOrder(allRoutes); diff --git a/src/typings/api/project.d.ts b/src/typings/api/project.d.ts index daf3fa2..7b4bff1 100644 --- a/src/typings/api/project.d.ts +++ b/src/typings/api/project.d.ts @@ -317,6 +317,36 @@ declare namespace Api { updateTime: string[]; }>; + /** + * 任务看板按状态分组的分页入参。 + * + * - `statusCode` 缺省 → 返回该执行下任务状态字典中的全部状态(即使该状态下当前没有任务,也要回该列、`total=0`、`list=[]`)。 + * - 传入数组 → 只返回这些状态的列。 + * - `pageNo` / `pageSize` 应用到所有返回的状态(同一页码下各状态各自分页),前端不需要"每列独立 pageNo"。 + */ + type ProjectTaskBoardPageParams = CommonType.RecordNullable< + Pick & { + statusCode: string[]; + keyword: string; + parentTaskId: string; + ownerId: string; + updateTime: string[]; + } + >; + + interface ProjectTaskBoardColumn { + statusCode: string; + statusName: string; + sort: number; + terminal?: boolean; + list: ProjectTask[]; + total: number; + } + + interface ProjectTaskBoardPage { + items: ProjectTaskBoardColumn[]; + } + interface SaveProjectTaskParams { parentTaskId: string | null; taskTitle: string; diff --git a/src/typings/api/system-manage.d.ts b/src/typings/api/system-manage.d.ts index 313719b..dd629aa 100644 --- a/src/typings/api/system-manage.d.ts +++ b/src/typings/api/system-manage.d.ts @@ -69,7 +69,7 @@ declare namespace Api { roleCode: string; }; - type DeptOrgType = 'company' | 'dept' | 'direction' | 'team'; + type DeptOrgType = 'company' | 'dept' | 'function' | 'direction' | 'team'; interface Dept { id: number; diff --git a/src/typings/app.d.ts b/src/typings/app.d.ts index 18622ef..1d69cee 100644 --- a/src/typings/app.d.ts +++ b/src/typings/app.d.ts @@ -333,7 +333,7 @@ declare namespace App { trigger: string; update: string; updateSuccess: string; - userCenter: string; + myProfile: string; yesOrNo: { yes: string; no: string; @@ -684,6 +684,7 @@ declare namespace App { orgType: { company: string; dept: string; + function: string; direction: string; team: string; }; diff --git a/src/typings/elegant-router.d.ts b/src/typings/elegant-router.d.ts index 8755cde..5626b89 100644 --- a/src/typings/elegant-router.d.ts +++ b/src/typings/elegant-router.d.ts @@ -35,7 +35,21 @@ declare module "@elegant-router/types" { "function_tab": "/function/tab"; "function_toggle-auth": "/function/toggle-auth"; "iframe-page": "/iframe-page/:url"; + "infra": "/infra"; + "infra_rd-code": "/infra/rd-code"; + "infra_state-machine": "/infra/state-machine"; "login": "/login/:module(pwd-login|reset-pwd)?"; + "metrics": "/metrics"; + "metrics_member-efficiency": "/metrics/member-efficiency"; + "metrics_project-progress": "/metrics/project-progress"; + "metrics_worktime": "/metrics/worktime"; + "personal-center": "/personal-center"; + "personal-center_my-application": "/personal-center/my-application"; + "personal-center_my-monthly": "/personal-center/my-monthly"; + "personal-center_my-performance": "/personal-center/my-performance"; + "personal-center_my-profile": "/personal-center/my-profile"; + "personal-center_my-weekly": "/personal-center/my-weekly"; + "personal-center_pending-approval": "/personal-center/pending-approval"; "plugin": "/plugin"; "plugin_barcode": "/plugin/barcode"; "plugin_charts": "/plugin/charts"; @@ -80,7 +94,10 @@ declare module "@elegant-router/types" { "system_user": "/system/user"; "system_user-detail": "/system/user-detail/:id"; "system_user-management-relation": "/system/user-management-relation"; - "user-center": "/user-center"; + "ticket": "/ticket"; + "ticket_my-pending": "/ticket/my-pending"; + "ticket_my-submitted": "/ticket/my-submitted"; + "workbench": "/workbench"; }; /** @@ -121,12 +138,16 @@ declare module "@elegant-router/types" { | "500" | "function" | "iframe-page" + | "infra" | "login" + | "metrics" + | "personal-center" | "plugin" | "product" | "project" | "system" - | "user-center" + | "ticket" + | "workbench" >; /** @@ -157,6 +178,17 @@ declare module "@elegant-router/types" { | "function_super-page" | "function_tab" | "function_toggle-auth" + | "infra_rd-code" + | "infra_state-machine" + | "metrics_member-efficiency" + | "metrics_project-progress" + | "metrics_worktime" + | "personal-center_my-application" + | "personal-center_my-monthly" + | "personal-center_my-performance" + | "personal-center_my-profile" + | "personal-center_my-weekly" + | "personal-center_pending-approval" | "plugin_barcode" | "plugin_charts_antv" | "plugin_charts_echarts" @@ -192,7 +224,9 @@ declare module "@elegant-router/types" { | "system_user-detail" | "system_user-management-relation" | "system_user" - | "user-center" + | "ticket_my-pending" + | "ticket_my-submitted" + | "workbench" >; /** diff --git a/src/views/infra/rd-code/index.vue b/src/views/infra/rd-code/index.vue new file mode 100644 index 0000000..92409d7 --- /dev/null +++ b/src/views/infra/rd-code/index.vue @@ -0,0 +1,3 @@ + diff --git a/src/views/infra/state-machine/index.vue b/src/views/infra/state-machine/index.vue new file mode 100644 index 0000000..1450eab --- /dev/null +++ b/src/views/infra/state-machine/index.vue @@ -0,0 +1,3 @@ + diff --git a/src/views/metrics/member-efficiency/index.vue b/src/views/metrics/member-efficiency/index.vue new file mode 100644 index 0000000..d198bf2 --- /dev/null +++ b/src/views/metrics/member-efficiency/index.vue @@ -0,0 +1,3 @@ + diff --git a/src/views/metrics/project-progress/index.vue b/src/views/metrics/project-progress/index.vue new file mode 100644 index 0000000..4ba63c8 --- /dev/null +++ b/src/views/metrics/project-progress/index.vue @@ -0,0 +1,3 @@ + diff --git a/src/views/metrics/worktime/index.vue b/src/views/metrics/worktime/index.vue new file mode 100644 index 0000000..34112f6 --- /dev/null +++ b/src/views/metrics/worktime/index.vue @@ -0,0 +1,3 @@ + diff --git a/src/views/personal-center/my-application/index.vue b/src/views/personal-center/my-application/index.vue new file mode 100644 index 0000000..2eabde3 --- /dev/null +++ b/src/views/personal-center/my-application/index.vue @@ -0,0 +1,3 @@ + diff --git a/src/views/personal-center/my-monthly/index.vue b/src/views/personal-center/my-monthly/index.vue new file mode 100644 index 0000000..cee5721 --- /dev/null +++ b/src/views/personal-center/my-monthly/index.vue @@ -0,0 +1,3 @@ + diff --git a/src/views/personal-center/my-performance/index.vue b/src/views/personal-center/my-performance/index.vue new file mode 100644 index 0000000..8a219ed --- /dev/null +++ b/src/views/personal-center/my-performance/index.vue @@ -0,0 +1,3 @@ + diff --git a/src/views/personal-center/my-profile/index.vue b/src/views/personal-center/my-profile/index.vue new file mode 100644 index 0000000..63ed180 --- /dev/null +++ b/src/views/personal-center/my-profile/index.vue @@ -0,0 +1,3 @@ + diff --git a/src/views/personal-center/my-weekly/index.vue b/src/views/personal-center/my-weekly/index.vue new file mode 100644 index 0000000..34c3ea2 --- /dev/null +++ b/src/views/personal-center/my-weekly/index.vue @@ -0,0 +1,3 @@ + diff --git a/src/views/personal-center/pending-approval/index.vue b/src/views/personal-center/pending-approval/index.vue new file mode 100644 index 0000000..2b93787 --- /dev/null +++ b/src/views/personal-center/pending-approval/index.vue @@ -0,0 +1,3 @@ + diff --git a/src/views/product/dashboard/index.vue b/src/views/product/dashboard/index.vue index cabdd8a..8b8f00e 100644 --- a/src/views/product/dashboard/index.vue +++ b/src/views/product/dashboard/index.vue @@ -39,40 +39,88 @@ const homepageBanner = computed(() => ); const extensionModules = computed(() => getProductHomepageExtensionModules(productHomepageExtensionMock)); const directionLabel = computed(() => getDirectionDictLabel(homepageBanner.value.identity.directionCode, '--')); -const bannerFacts = computed(() => { - const [managerFact, roleFact] = homepageBanner.value.identity.facts; - return [ - { - label: '产品方向', - value: directionLabel.value, - fullWidth: false - }, - { - label: managerFact?.label || '产品经理', - value: managerFact?.value || '--', - fullWidth: false - }, - { - label: roleFact?.label || '角色摘要', - value: roleFact?.value || '--', - fullWidth: true - } - ]; -}); -const bannerStatusClass = computed(() => { +const managerFact = computed(() => homepageBanner.value.identity.facts[0]); +const roleFact = computed(() => homepageBanner.value.identity.facts[1]); + +const identityToneClass = computed(() => { const statusCode = homepageBanner.value.identity.statusCode; - - return statusCode ? `product-homepage-banner--${statusCode}` : 'product-homepage-banner--default'; + return statusCode ? `product-overview__identity--${statusCode}` : 'product-overview__identity--default'; }); -const bannerStatusWordClass = computed(() => { + +const statusBadgeClass = computed(() => { const statusCode = homepageBanner.value.identity.statusCode; - - return statusCode - ? `product-homepage-banner__status-word--${statusCode}` - : 'product-homepage-banner__status-word--default'; + return statusCode ? `product-overview__identity-status--${statusCode}` : 'product-overview__identity-status--default'; }); +interface KpiCard { + key: string; + label: string; + value: string; + hint: string; + icon: string; + tone: 'teal' | 'emerald' | 'amber' | 'slate'; + compact: boolean; +} + +const kpiTones: Array = ['teal', 'emerald', 'amber', 'slate']; +const kpiIcons = [ + 'mdi:account-group-outline', + 'mdi:clipboard-list-outline', + 'mdi:timer-sand', + 'mdi:clock-time-eight-outline' +]; + +const kpiCards = computed(() => + homepageBanner.value.metrics.map((metric, index) => ({ + key: metric.label, + label: metric.label, + value: metric.value, + hint: metric.hint, + icon: kpiIcons[index] || 'mdi:chart-line', + tone: kpiTones[index] || 'slate', + compact: index === 3 + })) +); + +const distributionIcons: Record = { + 待处理: 'mdi:timer-sand', + 分析中: 'mdi:magnify-scan', + 已规划: 'mdi:calendar-check-outline', + 已完成: 'mdi:check-circle-outline' +}; + +const distributionWithIcons = computed(() => + requirementPoolSummary.value.distribution.map((item, index) => ({ + ...item, + icon: distributionIcons[item.label] || 'mdi:circle-outline', + tone: ['amber', 'sky', 'violet', 'emerald'][index] || 'slate' + })) +); + +const poolCompletionRate = computed(() => { + const { total, distribution } = requirementPoolSummary.value; + const done = Number(distribution.find(item => item.label === '已完成')?.value || 0); + if (!total) return 0; + return Math.min(100, Math.max(0, Math.round((done / total) * 100))); +}); + +const poolProgressColor = computed(() => [ + { color: '#f59e0b', percentage: 30 }, + { color: '#0ea5e9', percentage: 60 }, + { color: '#10b981', percentage: 100 } +]); + +const extensionMeta: Record = { + milestone: { icon: 'mdi:flag-checkered', tone: 'amber' }, + risk: { icon: 'mdi:alert-octagon-outline', tone: 'rose' }, + document: { icon: 'mdi:file-document-outline', tone: 'sky' } +}; + +function getExtensionMeta(key: string) { + return extensionMeta[key] || { icon: 'mdi:dots-grid', tone: 'sky' }; +} + function handleLatestActivityTimeChange(value: string) { latestActivityTime.value = value; } @@ -113,128 +161,217 @@ watch( diff --git a/src/views/product/dashboard/index.vue.bak b/src/views/product/dashboard/index.vue.bak new file mode 100644 index 0000000..cabdd8a --- /dev/null +++ b/src/views/product/dashboard/index.vue.bak @@ -0,0 +1,767 @@ + + + + + diff --git a/src/views/product/dashboard/modules/product-activity-timeline-panel.vue b/src/views/product/dashboard/modules/product-activity-timeline-panel.vue index 5776846..378b4d7 100644 --- a/src/views/product/dashboard/modules/product-activity-timeline-panel.vue +++ b/src/views/product/dashboard/modules/product-activity-timeline-panel.vue @@ -1,5 +1,5 @@