Files
zkxw/docs/superpowers/plans/2026-06-12-menu-restructuring.md
T
zlwuandCursor 7f7979679f fix(menu): 修复两级菜单结构和嵌套问题
1. 菜单类型默认值改为 TOP_LEFT(顶部一级+左侧二级)
2. 二级菜单 component 设为空字符串避免嵌套布局
3. 前端和后端菜单数据同步更新

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 20:02:47 +08:00

500 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 菜单结构调整实现计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 将 14 个一级菜单重组为 5 个一级菜单(市场、生产、质检、运营支撑、系统管理),新增菜单记录到 sys_menu 表并支持角色权限控制。
**Architecture:** 采用方案 A(一级菜单独立权限),每个新一级菜单设置独立 `perms`(如 `menu:market`),前端改造菜单组件支持两级菜单结构(顶部一级 + 左侧二级),后端同步改造菜单接口。
**Tech Stack:** Vue 3 + Element Plus + Pinia + FastAPI + SQLAlchemy
---
## 变更范围总览
| 层级 | 文件 | 变更类型 |
|------|------|----------|
| 前端路由 | `web-admin/src/router/modules/sqxp.ts` | 结构调整 |
| 前端菜单组件 | `web-admin/src/components/core/layouts/art-menus/*` | 支持两级结构 |
| 前端状态 | `web-admin/src/store/modules/menu.ts`, `setting.ts` | 新增一级菜单状态 |
| 后端菜单接口 | `backend/app/api/menu.py` | 新增一级菜单数据 |
| 后端权限服务 | `backend/app/services/web_auth_roles.py` | 支持一级菜单权限过滤 |
| 数据库 | `sys_menu` 表 | 新增 5 个一级菜单记录 |
---
## Task 1: 后端 - 更新菜单接口数据
**Files:**
- Modify: `backend/app/api/menu.py:10-773`
- [ ] **Step 1: 添加新一级菜单定义**
在 `STATIC_MENU_TREE` 开头添加 5 个新一级菜单:
```python
# 新增一级菜单定义(放在 STATIC_MENU_TREE 最前面)
TOP_MENU_MARKET = {
"id": "TOP00000000000000000000000000001",
"name": "MarketTop",
"path": "/market",
"component": "/index/index",
"meta": {
"title": "市场",
"icon": "ri:shopping-cart-line",
"keepAlive": False,
"isTopMenu": True,
"menuCode": "menu:market",
},
"children": [
# 细胞存储、科研捐赠、中试服务、基础实验、市场基础信息、临床应用
],
}
TOP_MENU_PRODUCTION = {
"id": "TOP00000000000000000000000000002",
"name": "ProductionTop",
"path": "/production-group",
"component": "/index/index",
"meta": {
"title": "生产",
"icon": "ri:factory-line",
"keepAlive": False,
"isTopMenu": True,
"menuCode": "menu:production",
},
"children": [
# 生产管理、生产基础信息
],
}
TOP_MENU_QUALITY = {
"id": "TOP00000000000000000000000000003",
"name": "QualityTop",
"path": "/quality-group",
"component": "/index/index",
"meta": {
"title": "质检",
"icon": "ri:shield-check-line",
"keepAlive": False,
"isTopMenu": True,
"menuCode": "menu:quality",
},
"children": [
# 质检管理、质检基础信息
],
}
TOP_MENU_OPERATIONS = {
"id": "TOP00000000000000000000000000004",
"name": "OperationsTop",
"path": "/operations-group",
"component": "/index/index",
"meta": {
"title": "运营支撑",
"icon": "ri:settings-3-line",
"keepAlive": False,
"isTopMenu": True,
"menuCode": "menu:operations",
},
"children": [
# 系统基础信息、运营支撑
],
}
TOP_MENU_SYSTEM = {
"id": "TOP00000000000000000000000000005",
"name": "SystemTop",
"path": "/system-manage",
"component": "/index/index",
"meta": {
"title": "系统管理",
"icon": "ri:server-line",
"keepAlive": False,
"isTopMenu": True,
"menuCode": "menu:system-manage",
},
"children": [
# 系统管理、工作流管理
],
}
```
- [ ] **Step 2: 调整原有菜单到新父级下**
将现有 14 个一级菜单作为二级菜单,重新分配 parent_id 和 path:
| 原菜单 | 新父级 | 新 path |
|--------|--------|---------|
| 细胞存储 | TOP00000000000000000000000000001 | `/market/cell-storage` |
| 科研捐赠 | TOP00000000000000000000000000001 | `/market/research-donation` |
| 中试服务 | TOP00000000000000000000000000001 | `/market/pilot-service` |
| 基础实验 | TOP00000000000000000000000000001 | `/market/basic-experiment` |
| 市场基础信息 | TOP00000000000000000000000000001 | `/market/market-info` |
| 临床应用 | TOP00000000000000000000000000002 | `/production-group/clinical-application` |
| 生产管理 | TOP00000000000000000000000000002 | `/production-group/production` |
| 生产基础信息 | TOP00000000000000000000000000002 | `/production-group/production-base` |
| 质检管理 | TOP00000000000000000000000000003 | `/quality-group/quality-manage` |
| 质检基础信息 | TOP00000000000000000000000000003 | `/quality-group/quality-base` |
| 系统基础信息 | TOP00000000000000000000000000004 | `/operations-group/system-base` |
| 运营支撑 | TOP00000000000000000000000000004 | `/operations-group/operations` |
| 系统管理 | TOP00000000000000000000000000005 | `/system-manage/system` |
| 工作流管理 | TOP00000000000000000000000000005 | `/system-manage/workflow` |
- [ ] **Step 3: 更新 STATIC_MENU_TREE 结构**
将 STATIC_MENU_TREE 改为只包含 5 个新一级菜单,每个一级菜单的 children 包含对应的原菜单作为二级菜单。
- [ ] **Step 4: Commit**
```bash
git add backend/app/api/menu.py
git commit -m "feat(menu): 重构菜单为5个一级分组菜单结构"
```
---
## Task 2: 后端 - 更新权限服务支持一级菜单过滤
**Files:**
- Modify: `backend/app/services/web_auth_roles.py`
- [ ] **Step 1: 添加一级菜单权限码常量**
```python
# 一级菜单权限码
TOP_MENU_PERMS = {"menu:market", "menu:production", "menu:quality", "menu:operations", "menu:system-manage"}
```
- [ ] **Step 2: 确保 iter_user_menu_perm_codes 返回一级菜单 perms**
当前 `iter_user_menu_perm_codes` 通过 `sys_role_menu → sys_menu` 查询用户拥有的菜单权限。一级菜单的 `perms`(如 `menu:market`)已经在 sys_menu 表中定义,因此无需修改查询逻辑。只需确保 sys_menu 表中有这 5 个一级菜单的记录(Task 3 处理)。
- [ ] **Step 3: Commit**
```bash
git add backend/app/services/web_auth_roles.py
git commit -m "feat(auth): 支持一级菜单权限码过滤"
```
---
## Task 3: 数据库 - 插入一级菜单数据到 sys_menu
**Files:**
- Create: `docs/sql/menu_restructuring.sql`
- [ ] **Step 1: 编写 SQL 插入脚本**
创建 SQL 脚本,包含 5 个一级菜单的 INSERT 语句(menu_type = 'M' 表示目录):
```sql
-- 一级菜单
INSERT INTO sys_menu (id, menu_name, parent_id, menu_type, path, component, perms, is_frame, visible, status, tenant_no, order_num, create_by, create_dept_id, create_time, update_by, update_time, del_flag) VALUES
('TOP00000000000000000000000000001', '市场', '0', 'M', '/market', '/index/index', 'menu:market', 1, '1', '1', 'zkxw', 1, 'system', '', NOW(), 'system', NOW(), 0),
('TOP00000000000000000000000000002', '生产', '0', 'M', '/production-group', '/index/index', 'menu:production', 1, '1', '1', 'zkxw', 2, 'system', '', NOW(), 'system', NOW(), 0),
('TOP00000000000000000000000000003', '质检', '0', 'M', '/quality-group', '/index/index', 'menu:quality', 1, '1', '1', 'zkxw', 3, 'system', '', NOW(), 'system', NOW(), 0),
('TOP00000000000000000000000000004', '运营支撑', '0', 'M', '/operations-group', '/index/index', 'menu:operations', 1, '1', '1', 'zkxw', 4, 'system', '', NOW(), 'system', NOW(), 0),
('TOP00000000000000000000000000005', '系统管理', '0', 'M', '/system-manage', '/index/index', 'menu:system-manage', 1, '1', '1', 'zkxw', 5, 'system', '', NOW(), 'system', NOW(), 0);
```
- [ ] **Step 2: Commit**
```bash
git add docs/sql/menu_restructuring.sql
git commit -m "feat(db): 添加5个一级菜单到sys_menu表"
```
---
## Task 4: 前端 - 改造 sqxp.ts 路由结构
**Files:**
- Modify: `web-admin/src/router/modules/sqxp.ts:1-1264`
- [ ] **Step 1: 添加新一级菜单定义**
在文件开头添加 5 个新一级菜单(与后端对应):
```typescript
// 新一级菜单
const MARKET_TOP_MENU: AppRouteRecord = {
id: 'TOP00000000000000000000000000001',
name: 'MarketTop',
path: '/market',
component: '/index/index',
meta: {
title: '市场',
icon: 'ri:shopping-cart-line',
keepAlive: false,
isTopMenu: true,
roles: ['admin'],
menuCode: 'menu:market'
},
children: [
// 细胞存储、科研捐赠、中试服务、基础实验、市场基础信息、临床应用
]
}
const PRODUCTION_TOP_MENU: AppRouteRecord = {
id: 'TOP00000000000000000000000000002',
name: 'ProductionTop',
path: '/production-group',
component: '/index/index',
meta: {
title: '生产',
icon: 'ri:factory-line',
keepAlive: false,
isTopMenu: true,
roles: ['admin'],
menuCode: 'menu:production'
},
children: [
// 临床应用、生产管理、生产基础信息
]
}
const QUALITY_TOP_MENU: AppRouteRecord = {
id: 'TOP00000000000000000000000000003',
name: 'QualityTop',
path: '/quality-group',
component: '/index/index',
meta: {
title: '质检',
icon: 'ri:shield-check-line',
keepAlive: false,
isTopMenu: true,
roles: ['admin'],
menuCode: 'menu:quality'
},
children: [
// 质检管理、质检基础信息
]
}
const OPERATIONS_TOP_MENU: AppRouteRecord = {
id: 'TOP00000000000000000000000000004',
name: 'OperationsTop',
path: '/operations-group',
component: '/index/index',
meta: {
title: '运营支撑',
icon: 'ri:settings-3-line',
keepAlive: false,
isTopMenu: true,
roles: ['admin'],
menuCode: 'menu:operations'
},
children: [
// 系统基础信息、运营支撑
]
}
const SYSTEM_TOP_MENU: AppRouteRecord = {
id: 'TOP00000000000000000000000000005',
name: 'SystemTop',
path: '/system-manage',
component: '/index/index',
meta: {
title: '系统管理',
icon: 'ri:server-line',
keepAlive: false,
isTopMenu: true,
roles: ['admin'],
menuCode: 'menu:system-manage'
},
children: [
// 系统管理、工作流管理
]
}
```
- [ ] **Step 2: 将原有 14 个菜单作为二级菜单移动到对应一级菜单下**
将细胞存储、科研捐赠、中试服务、基础实验、市场基础信息移到 MARKET_TOP_MENU.children 下;
将临床应用移到 PRODUCTION_TOP_MENU.children 下;
以此类推。
- [ ] **Step 3: 更新 export 导出**
将 sqxpAdminRoutes 改为只包含 5 个新一级菜单。
- [ ] **Step 4: Commit**
```bash
git add web-admin/src/router/modules/sqxp.ts
git commit -m "feat(router): 重构路由为5个一级菜单分组"
```
---
## Task 5: 前端 - 改造菜单组件支持两级结构
**Files:**
- Modify: `web-admin/src/components/core/layouts/art-menus/art-horizontal-menu/index.vue`
- Modify: `web-admin/src/components/core/layouts/art-menus/art-mixed-menu/index.vue`
- Modify: `web-admin/src/components/core/layouts/art-menus/art-sidebar-menu/index.vue`
- [ ] **Step 1: 识别当前活跃的一级菜单**
新增逻辑:根据当前路由路径判断所属的一级菜单,提取出 `activeTopMenuId`。
```typescript
// 在组件中添加 computed
const activeTopMenuId = computed(() => {
const path = route.path
// 从路径中提取一级菜单 path,如 /market/cell-storage → /market
const topPath = '/' + path.split('/')[1]
return topPath
})
```
- [ ] **Step 2: 分离一级菜单和二级菜单数据**
```typescript
// 过滤出一级菜单(isTopMenu: true)
const topMenus = computed(() => {
return props.list.filter(item => item.meta?.isTopMenu)
})
// 根据活跃的一级菜单获取二级菜单
const subMenus = computed(() => {
const active = topMenus.value.find(item => item.path === activeTopMenuId.value)
return active?.children || []
})
```
- [ ] **Step 3: 修改模板**
水平菜单只显示一级菜单,点击后更新 activeTopMenuId,并触发侧边栏显示对应的二级菜单。
- [ ] **Step 4: Commit**
```bash
git add web-admin/src/components/core/layouts/art-menus/art-horizontal-menu/index.vue
git add web-admin/src/components/core/layouts/art-menus/art-mixed-menu/index.vue
git commit -m "feat(menu): 改造顶部菜单支持两级结构"
```
---
## Task 6: 前端 - 改造侧边栏组件
**Files:**
- Modify: `web-admin/src/components/core/layouts/art-menus/art-sidebar-menu/index.vue`
- [ ] **Step 1: 修改菜单列表计算逻辑**
当前 `menuList` computed 根据 `isTopLeftMenu` 或 `isDualMenu` 返回对应子菜单。改为:
- 当存在活跃一级菜单时,返回该一级菜单的 children
- 否则返回完整菜单列表(兼容一级菜单页面)
```typescript
const menuList = computed(() => {
const menuStore = useMenuStore()
const allMenus = menuStore.menuList
if (!isTopLeftMenu.value && !isDualMenu.value) {
return allMenus
}
// 处理 iframe 路径
if (isIframe(route.path)) {
return findIframeMenuList(route.path, allMenus)
}
// 一级菜单页面直接返回空
if (route.meta.isFirstLevel) {
return []
}
// 返回当前一级菜单的子菜单
const topPath = '/' + route.path.split('/')[1]
const currentTopMenu = allMenus.find(menu =>
menu.meta?.isTopMenu && menu.path === topPath
)
if (currentTopMenu?.children) {
return currentTopMenu.children.filter((child: AppRouteRecord) => !child.meta?.isHide)
}
return []
})
```
- [ ] **Step 2: Commit**
```bash
git add web-admin/src/components/core/layouts/art-menus/art-sidebar-menu/index.vue
git commit -m "feat(menu): 改造侧边栏显示二级菜单"
```
---
## Task 7: 前端 - 更新菜单状态管理
**Files:**
- Modify: `web-admin/src/store/modules/menu.ts`
- Modify: `web-admin/src/store/modules/setting.ts`
- [ ] **Step 1: 在 menu.ts 中添加活跃一级菜单状态**
```typescript
/** 活跃的一级菜单路径 */
const activeTopMenuPath = ref('')
/** 设置活跃一级菜单 */
const setActiveTopMenuPath = (path: string) => {
activeTopMenuPath.value = path
}
```
- [ ] **Step 2: Commit**
```bash
git add web-admin/src/store/modules/menu.ts
git commit -m "feat(store): 添加一级菜单活跃状态管理"
```
---
## Task 8: 测试验证
- [ ] **Step 1: 启动后端服务**
```bash
cd backend && ./start-local.sh
```
- [ ] **Step 2: 启动前端服务**
```bash
cd web-admin && pnpm dev
```
- [ ] **Step 3: 验证菜单显示**
1. 登录管理端
2. 检查顶部是否显示 5 个一级菜单
3. 点击一级菜单,检查左侧是否显示对应的二级菜单
4. 验证权限控制:没有 `menu:market` 权限的角色不应看到「市场」菜单
- [ ] **Step 4: Commit**
```bash
git add -A
git commit -m "test: 验证菜单结构调整功能"
```
---
## 执行方式
**Two execution options:**
**1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration
**2. Inline Execution** - Execute tasks in this session using executing-plans, batch execution with checkpoints
**Which approach?**