From d8d7001b9be04e3628d702740d3ba3e17aac337b Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Wed, 4 Nov 2026 12:15:00 +0800 Subject: [PATCH] feat(project): group project pages under a management menu Add a second-level project management directory to match the requested navigation hierarchy. Preserve page permissions and legacy URLs, and migrate existing role grants to include the new ancestor. User will validate after integration. --- backend/README.md | 8 ++- backend/app/api/menu.py | 22 +++++--- .../2026-11-04-project_management_group.sql | 46 ++++++++++++++++ .../specs/project-management/spec.md | 9 ++++ .../project-business-statistics/tasks.md | 11 ++++ web-admin/src/router/modules/sqxp.ts | 52 +++++++++++-------- web-admin/src/router/routes/staticRoutes.ts | 18 ++++--- 7 files changed, 126 insertions(+), 40 deletions(-) create mode 100644 docs/sql/migrations/2026-11-04-project_management_group.sql diff --git a/backend/README.md b/backend/README.md index 1eeeb84e..3f330b71 100644 --- a/backend/README.md +++ b/backend/README.md @@ -58,8 +58,12 @@ SQL 位于 [docs/sql](../docs/sql/)。当前初始化基线不完整,部分历 [2026-11-02-project_management.sql](../docs/sql/migrations/2026-11-02-project_management.sql), 再依次执行[菜单种子](../docs/sql/migrations/2026-11-02-project_management_menu_seed.sql)与 [项目一级菜单迁移](../docs/sql/migrations/2026-11-02-project_top_menu.sql), -最后执行[全部项目权限种子](../docs/sql/migrations/2026-11-02-project_all_permission.sql)。 -“项目”与“市场、生产、质检”平级,包含项目组管理、项目管理、业务统计、历史归属维护。 +再执行[全部项目权限种子](../docs/sql/migrations/2026-11-02-project_all_permission.sql), +最后执行[项目管理分组迁移](../docs/sql/migrations/2026-11-04-project_management_group.sql)。 +已执行前四个脚本的数据库只需追加分组迁移;完整重跑时也须将分组迁移放在最后。 +“项目”与“市场、生产、质检”平级,下设“项目管理”目录,其下包含项目组管理、项目管理、业务统计、历史归属维护。 +分组迁移保留四个页面的 ID 和权限,仅为已授权子页面/编辑按钮的角色补齐祖先; +旧 `/project/{页面}` 和 `/market/project-management/{页面}` 地址跳转至 `/project/management/{页面}`。 菜单种子不会给角色新增业务权限,应在现有角色管理分配项目管理权限及祖先菜单; 一级菜单迁移保留已有叶子 ID/授权,仅给已授权项目子菜单的角色补齐新祖先。 “所属项目”默认只列出本人担任负责人或成员的项目组内项目(多组取并集)。 diff --git a/backend/app/api/menu.py b/backend/app/api/menu.py index 6470f56d..b8034d13 100644 --- a/backend/app/api/menu.py +++ b/backend/app/api/menu.py @@ -19,14 +19,20 @@ STATIC_MENU_TREE = [ "isTopMenu": True, "menuCode": "menu:project", "roles": ["admin"], }, "children": [ - {"name": "ProjectManagementTeams", "path": "teams", "component": "/project-management/teams/index", - "meta": {"title": "项目组管理", "keepAlive": True, "menuCode": "project:team:list"}}, - {"name": "ProjectManagementProjects", "path": "projects", "component": "/project-management/projects/index", - "meta": {"title": "项目管理", "keepAlive": True, "menuCode": "project:project:list"}}, - {"name": "ProjectManagementStatistics", "path": "statistics", "component": "/project-management/statistics/index", - "meta": {"title": "业务统计", "keepAlive": True, "menuCode": "project:statistics:list"}}, - {"name": "ProjectManagementAssignments", "path": "assignments", "component": "/project-management/assignments/index", - "meta": {"title": "历史归属维护", "keepAlive": True, "menuCode": "project:assignment:edit"}}, + { + "name": "ProjectManagement", "path": "management", "component": "", + "meta": {"title": "项目管理", "keepAlive": False, "menuCode": "menu:project:management"}, + "children": [ + {"name": "ProjectManagementTeams", "path": "teams", "component": "/project-management/teams/index", + "meta": {"title": "项目组管理", "keepAlive": True, "menuCode": "project:team:list"}}, + {"name": "ProjectManagementProjects", "path": "projects", "component": "/project-management/projects/index", + "meta": {"title": "项目管理", "keepAlive": True, "menuCode": "project:project:list"}}, + {"name": "ProjectManagementStatistics", "path": "statistics", "component": "/project-management/statistics/index", + "meta": {"title": "业务统计", "keepAlive": True, "menuCode": "project:statistics:list"}}, + {"name": "ProjectManagementAssignments", "path": "assignments", "component": "/project-management/assignments/index", + "meta": {"title": "历史归属维护", "keepAlive": True, "menuCode": "project:assignment:edit"}}, + ], + }, ], }, # ──────────────────────────────────────────── diff --git a/docs/sql/migrations/2026-11-04-project_management_group.sql b/docs/sql/migrations/2026-11-04-project_management_group.sql new file mode 100644 index 00000000..3c44b0c1 --- /dev/null +++ b/docs/sql/migrations/2026-11-04-project_management_group.sql @@ -0,0 +1,46 @@ +-- Issue #13:在 project_all_permission 之后执行,可重复执行。 +-- 项目 → 项目管理 → 四个原页面。保留页面 ID/权限及移动菜单。 +-- 全部项目权限仍挂在项目一级节点,不以目录授权隐式授予业务权限。 +START TRANSACTION; + +INSERT INTO sys_menu (id, menu_name, parent_id, menu_type, path, component, perms, + is_frame, visible, status, tenant_no, order_num, create_dept_id, create_time, update_time, del_flag) +SELECT MD5(CONCAT(top_menu.tenant_no, ':menu:project:management')), '项目管理', top_menu.id, + 'M', 'management', '', 'menu:project:management', 1, '1', '1', top_menu.tenant_no, + 1, '', NOW(), NOW(), 0 +FROM sys_menu top_menu +WHERE top_menu.perms='menu:project' AND top_menu.del_flag=0 +AND NOT EXISTS (SELECT 1 FROM sys_menu existing + WHERE existing.tenant_no=top_menu.tenant_no AND existing.perms='menu:project:management'); + +UPDATE sys_menu leaf +JOIN sys_menu group_menu ON group_menu.tenant_no=leaf.tenant_no + AND group_menu.perms='menu:project:management' AND group_menu.del_flag=0 +SET leaf.parent_id=group_menu.id, leaf.update_time=NOW() +WHERE leaf.del_flag=0 AND leaf.perms IN ( + 'project:team:list', 'project:project:list', 'project:statistics:list', 'project:assignment:edit'); + +-- 仅已有页面/编辑按钮授权的角色补齐目录、项目一级和 Web 根。 +-- 只拥有全部项目选择权限的角色不增加四个管理页面的访问权限。 +INSERT INTO sys_role_menu (id, role_id, menu_id, tenant_no, create_dept_id, create_time, update_time, del_flag) +SELECT DISTINCT MD5(CONCAT(grant_row.tenant_no, ':project-management-ancestor:', grant_row.role_id, ':', ancestor.id)), + grant_row.role_id, ancestor.id, grant_row.tenant_no, '', NOW(), NOW(), 0 +FROM sys_role_menu grant_row +JOIN sys_role role_row ON role_row.id=grant_row.role_id AND role_row.tenant_no=grant_row.tenant_no + AND role_row.del_flag=0 +JOIN sys_menu leaf ON leaf.id=grant_row.menu_id AND leaf.tenant_no=grant_row.tenant_no + AND leaf.del_flag=0 AND leaf.perms IN ('project:team:list', 'project:project:list', + 'project:statistics:list', 'project:assignment:edit', 'project:team:edit', 'project:project:edit') +JOIN sys_menu group_menu ON group_menu.tenant_no=grant_row.tenant_no + AND group_menu.perms='menu:project:management' AND group_menu.del_flag=0 +JOIN sys_menu top_menu ON top_menu.id=group_menu.parent_id AND top_menu.tenant_no=grant_row.tenant_no + AND top_menu.perms='menu:project' AND top_menu.del_flag=0 +JOIN sys_menu ancestor ON ancestor.tenant_no=grant_row.tenant_no AND ancestor.del_flag=0 + AND ancestor.id IN (group_menu.id, top_menu.id, top_menu.parent_id) +WHERE grant_row.del_flag=0 +AND NOT EXISTS (SELECT 1 FROM sys_role_menu existing + WHERE existing.tenant_no=grant_row.tenant_no AND existing.role_id=grant_row.role_id + AND existing.menu_id=ancestor.id AND existing.del_flag=0) +ON DUPLICATE KEY UPDATE del_flag=0, update_time=NOW(); + +COMMIT; diff --git a/openspec/changes/project-business-statistics/specs/project-management/spec.md b/openspec/changes/project-business-statistics/specs/project-management/spec.md index a5d990dd..246fdd09 100644 --- a/openspec/changes/project-business-statistics/specs/project-management/spec.md +++ b/openspec/changes/project-business-statistics/specs/project-management/spec.md @@ -1,5 +1,14 @@ ## ADDED Requirements +### Requirement: 项目管理三级导航 +Web SHALL 使用“项目 → 项目管理 → 项目组管理 / 项目管理 / 业务统计 / 历史归属维护”层级,复用现有导航组件;原页面权限、名称和功能保持。 +#### Scenario: 现有角色升级 +- **WHEN** 执行项目管理分组迁移 +- **THEN** 保留子页面 ID 与原授权,仅为已授权子页面/编辑按钮的角色补齐目录祖先;全部项目选择权限仍独立挂在项目一级下,不授予管理页面权限。 +#### Scenario: 旧地址访问 +- **WHEN** 访问 `/project/{页面}` 或 `/market/project-management/{页面}` +- **THEN** 携带原查询参数及 hash 跳转至 `/project/management/{页面}`,目标仍按原页面权限检查。 + ### Requirement: 组织信息与业务地点独立 系统 SHALL 维护项目组负责人、成员及区域医院资料,区域医院仅为信息记录。 #### Scenario: 修改负责医院 diff --git a/openspec/changes/project-business-statistics/tasks.md b/openspec/changes/project-business-statistics/tasks.md index eda4cedc..90b95c5d 100644 --- a/openspec/changes/project-business-statistics/tasks.md +++ b/openspec/changes/project-business-statistics/tasks.md @@ -4,6 +4,17 @@ **架构:** 新管理模块统一校验与统计;现有业务显式接入;前端复用已有样式。 **技术栈:** FastAPI/SQLAlchemy/Pydantic、Vue3/Element Plus、Vue3/Vant。 +## 2026-11-04 菜单增加项目管理分组 + +用户明确指定层级:项目 → 项目管理 → 项目组管理 / 项目管理 / 业务统计 / 历史归属维护。仅重组现有菜单配置,复用市场三级导航,不新增业务页面或修改页面样式。沿用用户自测及先合入 V2.0 再测试的安排,工单保持开放。 +- [x] 同步 Web `sqxp.ts` 与后端 `menu.py`,新增 `menu:project:management` 目录,保留四个页面名称、组件和权限码。 +- [x] `staticRoutes.ts` 兼容两代旧地址,保留 query 与 hash。 +- [x] 新增 `2026-11-04-project_management_group.sql`,保留叶子 ID 与角色授权,仅补新祖先;全部项目权限仍独立挂在项目一级下,更新 README 顺序。 +- [x] 独立源码审查未发现 P1/P2,Git 差异核对通过;用户测试模式未运行测试或构建。 +- [ ] 提交续接 PR、合入 V2.0、同步主目录;备份并更新已确认测试库菜单,交付用户验收。 + +以下为此前阶段历史记录;其中待合并等状态已由 2026-11-04 用户明确先合并再测试的指令取代。 + 用户追加所属项目权限要求:将跨组选择从归属维护权限分离为 `project:project:all`(全部项目),默认仅项目组负责人/有效成员所在组内项目;Web与移动共用后端范围校验。 - [x] 在 `backend/tests/test_project_selection_permissions.py` 增加成员/多组/无组、独立全部项目权限、多角色及职位继承、撤权、跨租户和越权提交用例,先执行确认失败。 - [x] 在 `backend/app/services/project_management.py` 统一候选范围,赋值校验改用独立权限;筛选仍叠加原订单数据范围,历史回显与转换继承保持原有规则。 diff --git a/web-admin/src/router/modules/sqxp.ts b/web-admin/src/router/modules/sqxp.ts index cdb3c945..d9c3375b 100644 --- a/web-admin/src/router/modules/sqxp.ts +++ b/web-admin/src/router/modules/sqxp.ts @@ -20,28 +20,36 @@ export const sqxpAdminRoutes: AppRouteRecord[] = [ }, children: [ { - name: 'ProjectManagementTeams', - path: 'teams', - component: '/project-management/teams/index', - meta: { title: '项目组管理', keepAlive: true, menuCode: 'project:team:list' } - }, - { - name: 'ProjectManagementProjects', - path: 'projects', - component: '/project-management/projects/index', - meta: { title: '项目管理', keepAlive: true, menuCode: 'project:project:list' } - }, - { - name: 'ProjectManagementStatistics', - path: 'statistics', - component: '/project-management/statistics/index', - meta: { title: '业务统计', keepAlive: true, menuCode: 'project:statistics:list' } - }, - { - name: 'ProjectManagementAssignments', - path: 'assignments', - component: '/project-management/assignments/index', - meta: { title: '历史归属维护', keepAlive: true, menuCode: 'project:assignment:edit' } + name: 'ProjectManagement', + path: 'management', + component: '', + meta: { title: '项目管理', keepAlive: false, menuCode: 'menu:project:management' }, + children: [ + { + name: 'ProjectManagementTeams', + path: 'teams', + component: '/project-management/teams/index', + meta: { title: '项目组管理', keepAlive: true, menuCode: 'project:team:list' } + }, + { + name: 'ProjectManagementProjects', + path: 'projects', + component: '/project-management/projects/index', + meta: { title: '项目管理', keepAlive: true, menuCode: 'project:project:list' } + }, + { + name: 'ProjectManagementStatistics', + path: 'statistics', + component: '/project-management/statistics/index', + meta: { title: '业务统计', keepAlive: true, menuCode: 'project:statistics:list' } + }, + { + name: 'ProjectManagementAssignments', + path: 'assignments', + component: '/project-management/assignments/index', + meta: { title: '历史归属维护', keepAlive: true, menuCode: 'project:assignment:edit' } + } + ] } ] }, diff --git a/web-admin/src/router/routes/staticRoutes.ts b/web-admin/src/router/routes/staticRoutes.ts index f70cd1f9..952eb27a 100644 --- a/web-admin/src/router/routes/staticRoutes.ts +++ b/web-admin/src/router/routes/staticRoutes.ts @@ -11,14 +11,16 @@ import { AppRouteRecordRaw } from '@/utils/router' * 2、静态路由不管是否登录都可以访问 */ export const staticRoutes: AppRouteRecordRaw[] = [ - ...['teams', 'projects', 'statistics', 'assignments'].map((section) => ({ - path: `/market/project-management/${section}`, - redirect: (to: { query: Record; hash: string }) => ({ - path: `/project/${section}`, - query: to.query, - hash: to.hash - }) - })), + ...['/market/project-management', '/project'].flatMap((prefix) => + ['teams', 'projects', 'statistics', 'assignments'].map((section) => ({ + path: `${prefix}/${section}`, + redirect: (to: { query: Record; hash: string }) => ({ + path: `/project/management/${section}`, + query: to.query, + hash: to.hash + }) + })) + ), { path: '/auth/login', name: 'Login',