
系统配置工作台是系统功能入口聚合页,负责把用户、角色、部门、菜单、日志、附件、任务、消息、智能助手和三方服务等系统能力整理成可搜索、可记忆最近使用的工作台。
本文基于 MenuViewSet.sys_router、系统菜单权限树和 SystemSettingWorkWorkbenche 组件,说明如何把系统菜单、角色权限、使用文档注入、搜索和路由跳转,转换成 Codex 可执行的项目代码生成任务。
文章目录
设计与需求
系统配置工作台不能按普通 CRUD 页面理解。它没有独立业务表,后端入口是 server_backend/dvadmin/system/views/menu.py 中的 sys_router,该接口读取当前用户可见菜单树,并返回一级菜单名称为"系统功能"的树。前端入口为 server_vue3/src/views/system/Workbenches/SystemSetting/index.vue,实际渲染逻辑在 server_vue3/src/views/system/Workbenches/components/SystemSettingWorkWorkbenche.vue。
交给 Codex 的任务需要把它理解成"系统菜单工作台"。后端负责按角色权限返回系统功能菜单,前端负责把菜单树转换成分组卡片,提供关键词搜索、最近使用、激活 tab 记忆和路由跳转。组件还会把"使用文档"卡片注入到智能助手相关分组,这个行为需要写进 PDD,而不是当作静态菜单误删。
#mermaid-svg-ZlCjtCx5nrNZjYDk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZlCjtCx5nrNZjYDk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZlCjtCx5nrNZjYDk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZlCjtCx5nrNZjYDk .error-icon{fill:#552222;}#mermaid-svg-ZlCjtCx5nrNZjYDk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZlCjtCx5nrNZjYDk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZlCjtCx5nrNZjYDk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZlCjtCx5nrNZjYDk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZlCjtCx5nrNZjYDk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZlCjtCx5nrNZjYDk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZlCjtCx5nrNZjYDk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZlCjtCx5nrNZjYDk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZlCjtCx5nrNZjYDk .marker.cross{stroke:#333333;}#mermaid-svg-ZlCjtCx5nrNZjYDk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZlCjtCx5nrNZjYDk p{margin:0;}#mermaid-svg-ZlCjtCx5nrNZjYDk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZlCjtCx5nrNZjYDk .cluster-label text{fill:#333;}#mermaid-svg-ZlCjtCx5nrNZjYDk .cluster-label span{color:#333;}#mermaid-svg-ZlCjtCx5nrNZjYDk .cluster-label span p{background-color:transparent;}#mermaid-svg-ZlCjtCx5nrNZjYDk .label text,#mermaid-svg-ZlCjtCx5nrNZjYDk span{fill:#333;color:#333;}#mermaid-svg-ZlCjtCx5nrNZjYDk .node rect,#mermaid-svg-ZlCjtCx5nrNZjYDk .node circle,#mermaid-svg-ZlCjtCx5nrNZjYDk .node ellipse,#mermaid-svg-ZlCjtCx5nrNZjYDk .node polygon,#mermaid-svg-ZlCjtCx5nrNZjYDk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZlCjtCx5nrNZjYDk .rough-node .label text,#mermaid-svg-ZlCjtCx5nrNZjYDk .node .label text,#mermaid-svg-ZlCjtCx5nrNZjYDk .image-shape .label,#mermaid-svg-ZlCjtCx5nrNZjYDk .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZlCjtCx5nrNZjYDk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZlCjtCx5nrNZjYDk .rough-node .label,#mermaid-svg-ZlCjtCx5nrNZjYDk .node .label,#mermaid-svg-ZlCjtCx5nrNZjYDk .image-shape .label,#mermaid-svg-ZlCjtCx5nrNZjYDk .icon-shape .label{text-align:center;}#mermaid-svg-ZlCjtCx5nrNZjYDk .node.clickable{cursor:pointer;}#mermaid-svg-ZlCjtCx5nrNZjYDk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZlCjtCx5nrNZjYDk .arrowheadPath{fill:#333333;}#mermaid-svg-ZlCjtCx5nrNZjYDk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZlCjtCx5nrNZjYDk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZlCjtCx5nrNZjYDk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZlCjtCx5nrNZjYDk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZlCjtCx5nrNZjYDk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZlCjtCx5nrNZjYDk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZlCjtCx5nrNZjYDk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZlCjtCx5nrNZjYDk .cluster text{fill:#333;}#mermaid-svg-ZlCjtCx5nrNZjYDk .cluster span{color:#333;}#mermaid-svg-ZlCjtCx5nrNZjYDk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ZlCjtCx5nrNZjYDk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZlCjtCx5nrNZjYDk rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZlCjtCx5nrNZjYDk .icon-shape,#mermaid-svg-ZlCjtCx5nrNZjYDk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZlCjtCx5nrNZjYDk .icon-shape p,#mermaid-svg-ZlCjtCx5nrNZjYDk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZlCjtCx5nrNZjYDk .icon-shape .label rect,#mermaid-svg-ZlCjtCx5nrNZjYDk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZlCjtCx5nrNZjYDk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZlCjtCx5nrNZjYDk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZlCjtCx5nrNZjYDk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-ZlCjtCx5nrNZjYDk .input>*{fill:#EEF4FF!important;stroke:#5B8FF9!important;color:#1D39C4!important;stroke-width:1.2px!important;}#mermaid-svg-ZlCjtCx5nrNZjYDk .input span{fill:#EEF4FF!important;stroke:#5B8FF9!important;color:#1D39C4!important;stroke-width:1.2px!important;}#mermaid-svg-ZlCjtCx5nrNZjYDk .input tspan{fill:#1D39C4!important;}#mermaid-svg-ZlCjtCx5nrNZjYDk .design>*{fill:#F6FFED!important;stroke:#52C41A!important;color:#237804!important;stroke-width:1.2px!important;}#mermaid-svg-ZlCjtCx5nrNZjYDk .design span{fill:#F6FFED!important;stroke:#52C41A!important;color:#237804!important;stroke-width:1.2px!important;}#mermaid-svg-ZlCjtCx5nrNZjYDk .design tspan{fill:#237804!important;}#mermaid-svg-ZlCjtCx5nrNZjYDk .process>*{fill:#FFF7E6!important;stroke:#FA8C16!important;color:#AD4E00!important;stroke-width:1.2px!important;}#mermaid-svg-ZlCjtCx5nrNZjYDk .process span{fill:#FFF7E6!important;stroke:#FA8C16!important;color:#AD4E00!important;stroke-width:1.2px!important;}#mermaid-svg-ZlCjtCx5nrNZjYDk .process tspan{fill:#AD4E00!important;}#mermaid-svg-ZlCjtCx5nrNZjYDk .check>*{fill:#F9F0FF!important;stroke:#722ED1!important;color:#531DAB!important;stroke-width:1.2px!important;}#mermaid-svg-ZlCjtCx5nrNZjYDk .check span{fill:#F9F0FF!important;stroke:#722ED1!important;color:#531DAB!important;stroke-width:1.2px!important;}#mermaid-svg-ZlCjtCx5nrNZjYDk .check tspan{fill:#531DAB!important;}#mermaid-svg-ZlCjtCx5nrNZjYDk .output>*{fill:#E6FFFB!important;stroke:#13C2C2!important;color:#006D75!important;stroke-width:1.2px!important;}#mermaid-svg-ZlCjtCx5nrNZjYDk .output span{fill:#E6FFFB!important;stroke:#13C2C2!important;color:#006D75!important;stroke-width:1.2px!important;}#mermaid-svg-ZlCjtCx5nrNZjYDk .output tspan{fill:#006D75!important;} 验收阶段
生成阶段
设计阶段
输入阶段
系统菜单
角色权限
本地状态
菜单树
文档卡片
搜索最近
路由跳转
读取源码
生成后端
生成前端
状态回显
PDD验收
| 需求层描述 | 设计层转换 | Codex 代码生成方向 |
|---|---|---|
| 系统能力需要统一入口 | sys_router 返回"系统功能"菜单树。 |
生成系统菜单接口说明、角色权限过滤和空树处理。 |
| 菜单树需要变成卡片 | SystemSettingWorkWorkbenche 将分组 children 转成可点击模块。 |
生成树形转换、叶子节点提取和分组渲染。 |
| 用户需要快速定位功能 | keyword 对当前分组的模块名称做搜索。 |
生成关键词过滤、空结果提示和输入状态。 |
| 常用入口需要回显 | recentIds 使用 system_workbench_recent 存入 localStorage。 |
生成最近使用读取、写入、去重和数量限制。 |
| 页面需要记住位置 | activeName 使用 system_workbench_active_tab 保存激活 tab。 |
生成激活分组读取、校验和回退策略。 |
| 使用文档需要入口 | injectManualCard 把"使用文档"卡片注入智能助手分组。 |
生成手动卡片注入、去重和旧路径清理验收。 |
可以直接使用下面的Prompt进行模块功能的设计
text
请 Codex 基于教育管理系统真实源码设计"系统配置工作台"模块。
模块定位:
系统配置工作台用于聚合系统功能菜单入口,支持菜单分组、关键词搜索、最近使用记录、激活 tab 记忆、使用文档卡片注入和路由跳转。
源码范围:
server_backend/dvadmin/system/views/menu.py
server_vue3/src/views/system/Workbenches/SystemSetting/index.vue
server_vue3/src/views/system/Workbenches/SystemSetting/api.ts
server_vue3/src/views/system/Workbenches/components/SystemSettingWorkWorkbenche.vue
接口范围:
GET /api/system/menu/sys_router/
关键状态:
systemList:系统工作台分组
activeName:当前激活分组
keyword:搜索关键词
recentIds:最近使用菜单 ID
system_workbench_recent:最近使用本地缓存 key
system_workbench_active_tab:激活 tab 本地缓存 key
设计要求:
1. 输出页面结构、后端菜单查询、权限规则、前端状态和验收标准。
2. 说明 sys_router 如何返回"系统功能"菜单树。
3. 说明前端如何把树形菜单转成分组和入口卡片。
4. 说明 injectManualCard 如何注入"使用文档"卡片。
5. 说明最近使用和激活 tab 只存在 localStorage,不进入后端模型。
6. 只允许设计源码中存在的数据联动、本地状态回显和手动卡片注入能力。
后端设计
后端设计重点不是新增系统配置模型,而是返回当前用户可见的系统功能菜单树。server_backend/dvadmin/system/views/menu.py 中的 sys_router 会调用 _get_role_filtered_menu_queryset(request.user) 获取按角色过滤后的菜单,再通过 WebRouterSerializer 和 _build_tree 构造成树。接口只取一级目录 name == "系统功能" 的那棵树。
这个设计让系统配置工作台入口受菜单权限控制。后端只返回用户可访问的系统功能树,不需要前端再判断角色,也不需要在工作台里硬编码用户、角色、部门、菜单或日志入口。Codex 补齐时应重点检查空树、无权限、菜单名称变化和树形序列化字段是否稳定。
| 后端设计项 | 设计重点 | Codex 生成方向 |
|---|---|---|
| 接口入口 | GET /api/system/menu/sys_router/。 |
保持系统菜单接口路径和 SuccessResponse 返回结构。 |
| 权限菜单 | _get_role_filtered_menu_queryset 返回当前用户可见菜单。 |
生成角色权限过滤和越权菜单不可见测试。 |
| 树形构造 | WebRouterSerializer 后使用 _build_tree 构造菜单树。 |
保持 children、path、name、image、component 字段。 |
| 节点选择 | 返回一级菜单名称为"系统功能"的树。 | 生成空数组处理和菜单命名验收。 |
| 返回结构 | 返回 data=target_tree,total=len(target_tree)。 |
前端按根节点 children 渲染分组。 |
可以直接使用下面的Prompt进行后端代码的设计
text
请 Codex 按"系统配置工作台"业务从零设计或补齐后端代码。
后端源码范围:
server_backend/dvadmin/system/views/menu.py
必须遵守的现有结构:
1. 接口为 MenuViewSet 的 sys_router action。
2. 接口路径为 GET /api/system/menu/sys_router/。
3. 菜单数据来自当前用户角色过滤后的 Menu 查询集。
4. 序列化使用 WebRouterSerializer。
5. 返回一级菜单名称为"系统功能"的树。
需要实现或校准:
1. 保持角色权限过滤。
2. 保持树形菜单 children、path、name、image、component 字段。
3. 找不到目标节点时返回空数组,不报 500。
4. 返回 total 和 msg,便于前端处理空状态。
5. 不新增系统配置工作台业务模型。
输出涉及文件、接口返回结构、权限规则、空状态处理和测试建议。
前端设计
前端设计重点不是静态卡片,而是把系统功能菜单树转换成可使用的工作台状态。server_vue3/src/views/system/Workbenches/SystemSetting/index.vue 只挂载 SystemSettingWorkWorkbenche。组件内部通过 GetSystemList() 请求 /api/system/menu/sys_router/,再取 res.data?.[0]?.children 作为系统功能分组。
SystemSettingWorkWorkbenche.vue 提供完整页面状态:keyword 做模块名称搜索,activeName 记录当前 tab,recentIds 记录最近使用,RECENT_LIMIT=8 控制最近入口数量。injectManualCard 会把"使用文档"卡片注入到智能助手相关分组,并清理旧的使用文档卡片,避免入口重复。handleOpen 会先写入最近使用,再执行路由跳转。
| 前端设计项 | 设计重点 | Codex 生成方向 |
|---|---|---|
| 页面入口 | SystemSetting/index.vue 挂载 SystemSettingWorkWorkbenche。 |
保持入口轻量,不重复写业务逻辑。 |
| 接口调用 | GetSystemList() 请求 /api/system/menu/sys_router/。 |
生成 loading、异常和空状态处理。 |
| 数据转换 | 取 res.data?.[0]?.children 作为分组。 |
生成树形转换、叶子节点收集和分组回显。 |
| 搜索过滤 | keyword 对当前分组模块名称做过滤。 |
生成搜索输入、清空状态和空结果提示。 |
| 最近使用 | recentIds 写入 localStorage,限制 8 条。 |
生成去重、持久化和点击回显。 |
| 文档卡片 | injectManualCard 注入 /system/userManual。 |
生成去重、旧入口过滤和智能助手分组定位。 |
可以直接使用下面的Prompt进行前端代码的设计
text
请 Codex 按"系统配置工作台"业务生成或补齐前端页面代码。
前端源码范围:
server_vue3/src/views/system/Workbenches/SystemSetting/index.vue
server_vue3/src/views/system/Workbenches/SystemSetting/api.ts
server_vue3/src/views/system/Workbenches/components/SystemSettingWorkWorkbenche.vue
页面现状:
1. SystemSetting/index.vue 挂载 SystemSettingWorkWorkbenche。
2. GetSystemList 请求 /api/system/menu/sys_router/。
3. systemList 使用 res.data?.[0]?.children。
4. keyword 支持模块名称搜索。
5. recentIds 记录最近使用,使用 localStorage 持久化。
6. activeName 记录当前 tab,使用 localStorage 持久化。
7. injectManualCard 注入"使用文档"入口。
8. handleOpen 负责写入最近使用并 router.push 跳转。
需要生成或修正:
1. 保持菜单树转换和叶子节点提取。
2. 保持最近使用去重和数量限制。
3. 保持激活 tab 本地记忆,保存的 tab 不存在时回退第一组。
4. 搜索结果为空时要有明确状态。
5. 使用文档卡片注入要去重,避免旧入口重复。
6. 不新增源码中不存在的后端字段或业务接口。
输出页面结构、接口调用、状态管理、搜索过滤、最近使用、文档卡片注入、路由跳转和验收步骤。
扩展功能
系统配置工作台的扩展能力来自数据联动、本地状态回显和手动卡片注入。它超出静态导航页的地方,是后端菜单树、角色权限、前端搜索、最近使用、激活 tab 和使用文档入口共同参与同一个工作台体验。
| 扩展功能 | 主要用途 | 落地重点 |
|---|---|---|
| 数据联动 | 将系统菜单树、分组、搜索、卡片和路由跳转串成稳定数据流。 | 后端返回结构与前端分组转换必须一致。 |
| 本地状态回显 | 记录最近使用和当前激活分组,提升重复访问效率。 | 状态只保存在 localStorage,不能当作后端业务数据。 |
| 手动卡片注入 | 将使用文档入口放入智能助手相关分组。 | 注入前要去重,旧路径要清理,不能破坏权限菜单结构。 |
数据联动
数据联动从 sys_router 返回的系统功能菜单树开始。前端取根节点 children 作为分组,再递归提取叶子菜单,搜索时过滤当前分组下的卡片,点击时把菜单 path 交给路由。id、name、path、image 是卡片渲染与最近使用回显的关键字段。
#mermaid-svg-s7G5lY0iRJ7yWIhO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-s7G5lY0iRJ7yWIhO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-s7G5lY0iRJ7yWIhO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-s7G5lY0iRJ7yWIhO .error-icon{fill:#552222;}#mermaid-svg-s7G5lY0iRJ7yWIhO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-s7G5lY0iRJ7yWIhO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-s7G5lY0iRJ7yWIhO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-s7G5lY0iRJ7yWIhO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-s7G5lY0iRJ7yWIhO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-s7G5lY0iRJ7yWIhO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-s7G5lY0iRJ7yWIhO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-s7G5lY0iRJ7yWIhO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-s7G5lY0iRJ7yWIhO .marker.cross{stroke:#333333;}#mermaid-svg-s7G5lY0iRJ7yWIhO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-s7G5lY0iRJ7yWIhO p{margin:0;}#mermaid-svg-s7G5lY0iRJ7yWIhO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-s7G5lY0iRJ7yWIhO .cluster-label text{fill:#333;}#mermaid-svg-s7G5lY0iRJ7yWIhO .cluster-label span{color:#333;}#mermaid-svg-s7G5lY0iRJ7yWIhO .cluster-label span p{background-color:transparent;}#mermaid-svg-s7G5lY0iRJ7yWIhO .label text,#mermaid-svg-s7G5lY0iRJ7yWIhO span{fill:#333;color:#333;}#mermaid-svg-s7G5lY0iRJ7yWIhO .node rect,#mermaid-svg-s7G5lY0iRJ7yWIhO .node circle,#mermaid-svg-s7G5lY0iRJ7yWIhO .node ellipse,#mermaid-svg-s7G5lY0iRJ7yWIhO .node polygon,#mermaid-svg-s7G5lY0iRJ7yWIhO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-s7G5lY0iRJ7yWIhO .rough-node .label text,#mermaid-svg-s7G5lY0iRJ7yWIhO .node .label text,#mermaid-svg-s7G5lY0iRJ7yWIhO .image-shape .label,#mermaid-svg-s7G5lY0iRJ7yWIhO .icon-shape .label{text-anchor:middle;}#mermaid-svg-s7G5lY0iRJ7yWIhO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-s7G5lY0iRJ7yWIhO .rough-node .label,#mermaid-svg-s7G5lY0iRJ7yWIhO .node .label,#mermaid-svg-s7G5lY0iRJ7yWIhO .image-shape .label,#mermaid-svg-s7G5lY0iRJ7yWIhO .icon-shape .label{text-align:center;}#mermaid-svg-s7G5lY0iRJ7yWIhO .node.clickable{cursor:pointer;}#mermaid-svg-s7G5lY0iRJ7yWIhO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-s7G5lY0iRJ7yWIhO .arrowheadPath{fill:#333333;}#mermaid-svg-s7G5lY0iRJ7yWIhO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-s7G5lY0iRJ7yWIhO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-s7G5lY0iRJ7yWIhO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s7G5lY0iRJ7yWIhO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-s7G5lY0iRJ7yWIhO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s7G5lY0iRJ7yWIhO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-s7G5lY0iRJ7yWIhO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-s7G5lY0iRJ7yWIhO .cluster text{fill:#333;}#mermaid-svg-s7G5lY0iRJ7yWIhO .cluster span{color:#333;}#mermaid-svg-s7G5lY0iRJ7yWIhO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-s7G5lY0iRJ7yWIhO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-s7G5lY0iRJ7yWIhO rect.text{fill:none;stroke-width:0;}#mermaid-svg-s7G5lY0iRJ7yWIhO .icon-shape,#mermaid-svg-s7G5lY0iRJ7yWIhO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s7G5lY0iRJ7yWIhO .icon-shape p,#mermaid-svg-s7G5lY0iRJ7yWIhO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-s7G5lY0iRJ7yWIhO .icon-shape .label rect,#mermaid-svg-s7G5lY0iRJ7yWIhO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s7G5lY0iRJ7yWIhO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-s7G5lY0iRJ7yWIhO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-s7G5lY0iRJ7yWIhO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-s7G5lY0iRJ7yWIhO .input>*{fill:#EEF4FF!important;stroke:#5B8FF9!important;color:#1D39C4!important;stroke-width:1.2px!important;}#mermaid-svg-s7G5lY0iRJ7yWIhO .input span{fill:#EEF4FF!important;stroke:#5B8FF9!important;color:#1D39C4!important;stroke-width:1.2px!important;}#mermaid-svg-s7G5lY0iRJ7yWIhO .input tspan{fill:#1D39C4!important;}#mermaid-svg-s7G5lY0iRJ7yWIhO .process>*{fill:#FFF7E6!important;stroke:#FA8C16!important;color:#AD4E00!important;stroke-width:1.2px!important;}#mermaid-svg-s7G5lY0iRJ7yWIhO .process span{fill:#FFF7E6!important;stroke:#FA8C16!important;color:#AD4E00!important;stroke-width:1.2px!important;}#mermaid-svg-s7G5lY0iRJ7yWIhO .process tspan{fill:#AD4E00!important;}#mermaid-svg-s7G5lY0iRJ7yWIhO .check>*{fill:#F9F0FF!important;stroke:#722ED1!important;color:#531DAB!important;stroke-width:1.2px!important;}#mermaid-svg-s7G5lY0iRJ7yWIhO .check span{fill:#F9F0FF!important;stroke:#722ED1!important;color:#531DAB!important;stroke-width:1.2px!important;}#mermaid-svg-s7G5lY0iRJ7yWIhO .check tspan{fill:#531DAB!important;}#mermaid-svg-s7G5lY0iRJ7yWIhO .output>*{fill:#E6FFFB!important;stroke:#13C2C2!important;color:#006D75!important;stroke-width:1.2px!important;}#mermaid-svg-s7G5lY0iRJ7yWIhO .output span{fill:#E6FFFB!important;stroke:#13C2C2!important;color:#006D75!important;stroke-width:1.2px!important;}#mermaid-svg-s7G5lY0iRJ7yWIhO .output tspan{fill:#006D75!important;} 结果阶段
处理阶段
输入阶段
菜单树
搜索词
当前分组
分组转换
叶子提取
过滤卡片
卡片列表
最近使用
路由跳转
交给 Codex 生成时,字段边界要明确。name 用于搜索和展示,path 用于跳转,image 用于卡片图片,id 用于最近使用;如果 children 仍有层级,需要递归压平后再展示。
可以直接使用下面的Prompt进行数据联动设计
text
请 Codex 基于真实源码补齐"系统配置工作台"的数据联动设计。
源码范围:
server_backend/dvadmin/system/views/menu.py
server_vue3/src/views/system/Workbenches/SystemSetting/index.vue
server_vue3/src/views/system/Workbenches/SystemSetting/api.ts
server_vue3/src/views/system/Workbenches/components/SystemSettingWorkWorkbenche.vue
联动对象:
sys_router、systemList、activeName、keyword、recentIds、children、id、name、path、image
联动要求:
1. 后端返回当前用户可见的系统功能菜单树。
2. 前端取根节点 children 转换成工作台分组。
3. 深层 children 要递归压平成可点击卡片。
4. keyword 只过滤当前展示范围内的模块名称。
5. 点击卡片时写入最近使用并跳转 path。
6. 不新增源码中不存在的业务接口。
输出接口设计、前端交互、字段转换、异常处理、权限约束和验收清单。
本地状态回显
本地状态回显体现在最近使用和激活 tab。system_workbench_recent 保存最近点击过的菜单 ID,system_workbench_active_tab 保存当前激活分组。页面加载时先读取本地状态,再请求菜单数据;如果本地保存的分组已经不存在,则回退到第一组。
#mermaid-svg-rmacf5xpXQ9gHgB7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rmacf5xpXQ9gHgB7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rmacf5xpXQ9gHgB7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rmacf5xpXQ9gHgB7 .error-icon{fill:#552222;}#mermaid-svg-rmacf5xpXQ9gHgB7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rmacf5xpXQ9gHgB7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rmacf5xpXQ9gHgB7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rmacf5xpXQ9gHgB7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rmacf5xpXQ9gHgB7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rmacf5xpXQ9gHgB7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rmacf5xpXQ9gHgB7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rmacf5xpXQ9gHgB7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rmacf5xpXQ9gHgB7 .marker.cross{stroke:#333333;}#mermaid-svg-rmacf5xpXQ9gHgB7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rmacf5xpXQ9gHgB7 p{margin:0;}#mermaid-svg-rmacf5xpXQ9gHgB7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rmacf5xpXQ9gHgB7 .cluster-label text{fill:#333;}#mermaid-svg-rmacf5xpXQ9gHgB7 .cluster-label span{color:#333;}#mermaid-svg-rmacf5xpXQ9gHgB7 .cluster-label span p{background-color:transparent;}#mermaid-svg-rmacf5xpXQ9gHgB7 .label text,#mermaid-svg-rmacf5xpXQ9gHgB7 span{fill:#333;color:#333;}#mermaid-svg-rmacf5xpXQ9gHgB7 .node rect,#mermaid-svg-rmacf5xpXQ9gHgB7 .node circle,#mermaid-svg-rmacf5xpXQ9gHgB7 .node ellipse,#mermaid-svg-rmacf5xpXQ9gHgB7 .node polygon,#mermaid-svg-rmacf5xpXQ9gHgB7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rmacf5xpXQ9gHgB7 .rough-node .label text,#mermaid-svg-rmacf5xpXQ9gHgB7 .node .label text,#mermaid-svg-rmacf5xpXQ9gHgB7 .image-shape .label,#mermaid-svg-rmacf5xpXQ9gHgB7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-rmacf5xpXQ9gHgB7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rmacf5xpXQ9gHgB7 .rough-node .label,#mermaid-svg-rmacf5xpXQ9gHgB7 .node .label,#mermaid-svg-rmacf5xpXQ9gHgB7 .image-shape .label,#mermaid-svg-rmacf5xpXQ9gHgB7 .icon-shape .label{text-align:center;}#mermaid-svg-rmacf5xpXQ9gHgB7 .node.clickable{cursor:pointer;}#mermaid-svg-rmacf5xpXQ9gHgB7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rmacf5xpXQ9gHgB7 .arrowheadPath{fill:#333333;}#mermaid-svg-rmacf5xpXQ9gHgB7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rmacf5xpXQ9gHgB7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rmacf5xpXQ9gHgB7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rmacf5xpXQ9gHgB7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rmacf5xpXQ9gHgB7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rmacf5xpXQ9gHgB7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rmacf5xpXQ9gHgB7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rmacf5xpXQ9gHgB7 .cluster text{fill:#333;}#mermaid-svg-rmacf5xpXQ9gHgB7 .cluster span{color:#333;}#mermaid-svg-rmacf5xpXQ9gHgB7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rmacf5xpXQ9gHgB7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rmacf5xpXQ9gHgB7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-rmacf5xpXQ9gHgB7 .icon-shape,#mermaid-svg-rmacf5xpXQ9gHgB7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rmacf5xpXQ9gHgB7 .icon-shape p,#mermaid-svg-rmacf5xpXQ9gHgB7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rmacf5xpXQ9gHgB7 .icon-shape .label rect,#mermaid-svg-rmacf5xpXQ9gHgB7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rmacf5xpXQ9gHgB7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rmacf5xpXQ9gHgB7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rmacf5xpXQ9gHgB7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-rmacf5xpXQ9gHgB7 .input>*{fill:#EEF4FF!important;stroke:#5B8FF9!important;color:#1D39C4!important;stroke-width:1.2px!important;}#mermaid-svg-rmacf5xpXQ9gHgB7 .input span{fill:#EEF4FF!important;stroke:#5B8FF9!important;color:#1D39C4!important;stroke-width:1.2px!important;}#mermaid-svg-rmacf5xpXQ9gHgB7 .input tspan{fill:#1D39C4!important;}#mermaid-svg-rmacf5xpXQ9gHgB7 .process>*{fill:#FFF7E6!important;stroke:#FA8C16!important;color:#AD4E00!important;stroke-width:1.2px!important;}#mermaid-svg-rmacf5xpXQ9gHgB7 .process span{fill:#FFF7E6!important;stroke:#FA8C16!important;color:#AD4E00!important;stroke-width:1.2px!important;}#mermaid-svg-rmacf5xpXQ9gHgB7 .process tspan{fill:#AD4E00!important;}#mermaid-svg-rmacf5xpXQ9gHgB7 .check>*{fill:#F9F0FF!important;stroke:#722ED1!important;color:#531DAB!important;stroke-width:1.2px!important;}#mermaid-svg-rmacf5xpXQ9gHgB7 .check span{fill:#F9F0FF!important;stroke:#722ED1!important;color:#531DAB!important;stroke-width:1.2px!important;}#mermaid-svg-rmacf5xpXQ9gHgB7 .check tspan{fill:#531DAB!important;}#mermaid-svg-rmacf5xpXQ9gHgB7 .output>*{fill:#E6FFFB!important;stroke:#13C2C2!important;color:#006D75!important;stroke-width:1.2px!important;}#mermaid-svg-rmacf5xpXQ9gHgB7 .output span{fill:#E6FFFB!important;stroke:#13C2C2!important;color:#006D75!important;stroke-width:1.2px!important;}#mermaid-svg-rmacf5xpXQ9gHgB7 .output tspan{fill:#006D75!important;} 结果阶段
处理阶段
输入阶段
本地缓存
菜单数据
读取最近
校验Tab
写入状态
最近入口
恢复分组
本地状态不能绕过权限。最近使用里保存的 ID 如果不在本次后端返回菜单中,就不展示;激活 tab 如果已无权限或已删除,也要回退到可见分组。
可以直接使用下面的Prompt进行本地状态回显设计
text
请 Codex 基于真实源码补齐"系统配置工作台"的本地状态回显能力。
源码范围:
server_vue3/src/views/system/Workbenches/components/SystemSettingWorkWorkbenche.vue
状态字段:
RECENT_KEY = system_workbench_recent
ACTIVE_TAB_KEY = system_workbench_active_tab
RECENT_LIMIT = 8
recentIds
activeName
设计要求:
1. 页面加载时读取最近使用和激活 tab。
2. 菜单接口返回后校验本地 tab 是否仍然存在。
3. 点击卡片时最近使用去重并限制 8 条。
4. 最近使用只展示当前菜单数据中仍然存在的卡片。
5. localStorage 读写异常不能影响页面加载。
输出状态流程、异常边界、前端实现建议和验收用例。
手动卡片注入
手动卡片注入用于保证使用文档入口稳定出现在系统工作台。createManualCard 生成 /system/userManual 卡片,injectManualCard 会清理旧的使用文档入口,再把新卡片放入智能助手相关分组;如果没有找到智能助手分组,则放入第一个分组。
#mermaid-svg-PDxlCFxQ9ZSOGAmB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PDxlCFxQ9ZSOGAmB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .error-icon{fill:#552222;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .marker.cross{stroke:#333333;}#mermaid-svg-PDxlCFxQ9ZSOGAmB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PDxlCFxQ9ZSOGAmB p{margin:0;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .cluster-label text{fill:#333;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .cluster-label span{color:#333;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .cluster-label span p{background-color:transparent;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .label text,#mermaid-svg-PDxlCFxQ9ZSOGAmB span{fill:#333;color:#333;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .node rect,#mermaid-svg-PDxlCFxQ9ZSOGAmB .node circle,#mermaid-svg-PDxlCFxQ9ZSOGAmB .node ellipse,#mermaid-svg-PDxlCFxQ9ZSOGAmB .node polygon,#mermaid-svg-PDxlCFxQ9ZSOGAmB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .rough-node .label text,#mermaid-svg-PDxlCFxQ9ZSOGAmB .node .label text,#mermaid-svg-PDxlCFxQ9ZSOGAmB .image-shape .label,#mermaid-svg-PDxlCFxQ9ZSOGAmB .icon-shape .label{text-anchor:middle;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .rough-node .label,#mermaid-svg-PDxlCFxQ9ZSOGAmB .node .label,#mermaid-svg-PDxlCFxQ9ZSOGAmB .image-shape .label,#mermaid-svg-PDxlCFxQ9ZSOGAmB .icon-shape .label{text-align:center;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .node.clickable{cursor:pointer;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .arrowheadPath{fill:#333333;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PDxlCFxQ9ZSOGAmB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PDxlCFxQ9ZSOGAmB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PDxlCFxQ9ZSOGAmB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .cluster text{fill:#333;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .cluster span{color:#333;}#mermaid-svg-PDxlCFxQ9ZSOGAmB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PDxlCFxQ9ZSOGAmB rect.text{fill:none;stroke-width:0;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .icon-shape,#mermaid-svg-PDxlCFxQ9ZSOGAmB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .icon-shape p,#mermaid-svg-PDxlCFxQ9ZSOGAmB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .icon-shape .label rect,#mermaid-svg-PDxlCFxQ9ZSOGAmB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PDxlCFxQ9ZSOGAmB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PDxlCFxQ9ZSOGAmB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .input>*{fill:#EEF4FF!important;stroke:#5B8FF9!important;color:#1D39C4!important;stroke-width:1.2px!important;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .input span{fill:#EEF4FF!important;stroke:#5B8FF9!important;color:#1D39C4!important;stroke-width:1.2px!important;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .input tspan{fill:#1D39C4!important;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .process>*{fill:#FFF7E6!important;stroke:#FA8C16!important;color:#AD4E00!important;stroke-width:1.2px!important;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .process span{fill:#FFF7E6!important;stroke:#FA8C16!important;color:#AD4E00!important;stroke-width:1.2px!important;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .process tspan{fill:#AD4E00!important;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .check>*{fill:#F9F0FF!important;stroke:#722ED1!important;color:#531DAB!important;stroke-width:1.2px!important;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .check span{fill:#F9F0FF!important;stroke:#722ED1!important;color:#531DAB!important;stroke-width:1.2px!important;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .check tspan{fill:#531DAB!important;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .output>*{fill:#E6FFFB!important;stroke:#13C2C2!important;color:#006D75!important;stroke-width:1.2px!important;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .output span{fill:#E6FFFB!important;stroke:#13C2C2!important;color:#006D75!important;stroke-width:1.2px!important;}#mermaid-svg-PDxlCFxQ9ZSOGAmB .output tspan{fill:#006D75!important;} 结果阶段
处理阶段
输入阶段
菜单分组
文档配置
过滤旧卡
定位分组
注入卡片
文档入口
去重结果
Codex 补齐时不能把这个逻辑当作多余代码删除。它解决的是使用文档入口的稳定性问题,但也要保持去重,避免菜单里已有入口时重复展示。
可以直接使用下面的Prompt进行手动卡片注入设计
text
请 Codex 基于真实源码补齐"系统配置工作台"的手动卡片注入设计。
源码范围:
server_vue3/src/views/system/Workbenches/components/SystemSettingWorkWorkbenche.vue
相关常量:
MANUAL_CARD_ID = system-manual-doc
MANUAL_CARD_NAME = 使用文档
MANUAL_CARD_PATH = /system/userManual
SMART_ASSISTANT_NAME = 智能助手
设计要求:
1. 清理旧的使用文档卡片,避免重复。
2. 优先把使用文档注入智能助手相关分组。
3. 找不到智能助手分组时回退第一个分组。
4. 注入卡片要参与搜索、最近使用和路由跳转。
5. 不破坏后端返回的其他授权菜单。
输出注入流程、去重策略、前端实现建议和验收用例。
Codex开发标准
使用 Codex 开发系统配置工作台时,不能让它生成静态导航页。PDD、SOP、接口权限规则和验收标准需要约束后端菜单查询、前端分组转换、本地状态回显、使用文档注入和路由跳转。
#mermaid-svg-lOJ1Ag3qLSC3t8BM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lOJ1Ag3qLSC3t8BM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .error-icon{fill:#552222;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .marker.cross{stroke:#333333;}#mermaid-svg-lOJ1Ag3qLSC3t8BM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lOJ1Ag3qLSC3t8BM p{margin:0;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .cluster-label text{fill:#333;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .cluster-label span{color:#333;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .cluster-label span p{background-color:transparent;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .label text,#mermaid-svg-lOJ1Ag3qLSC3t8BM span{fill:#333;color:#333;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .node rect,#mermaid-svg-lOJ1Ag3qLSC3t8BM .node circle,#mermaid-svg-lOJ1Ag3qLSC3t8BM .node ellipse,#mermaid-svg-lOJ1Ag3qLSC3t8BM .node polygon,#mermaid-svg-lOJ1Ag3qLSC3t8BM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .rough-node .label text,#mermaid-svg-lOJ1Ag3qLSC3t8BM .node .label text,#mermaid-svg-lOJ1Ag3qLSC3t8BM .image-shape .label,#mermaid-svg-lOJ1Ag3qLSC3t8BM .icon-shape .label{text-anchor:middle;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .rough-node .label,#mermaid-svg-lOJ1Ag3qLSC3t8BM .node .label,#mermaid-svg-lOJ1Ag3qLSC3t8BM .image-shape .label,#mermaid-svg-lOJ1Ag3qLSC3t8BM .icon-shape .label{text-align:center;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .node.clickable{cursor:pointer;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .arrowheadPath{fill:#333333;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lOJ1Ag3qLSC3t8BM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lOJ1Ag3qLSC3t8BM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lOJ1Ag3qLSC3t8BM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .cluster text{fill:#333;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .cluster span{color:#333;}#mermaid-svg-lOJ1Ag3qLSC3t8BM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lOJ1Ag3qLSC3t8BM rect.text{fill:none;stroke-width:0;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .icon-shape,#mermaid-svg-lOJ1Ag3qLSC3t8BM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .icon-shape p,#mermaid-svg-lOJ1Ag3qLSC3t8BM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .icon-shape .label rect,#mermaid-svg-lOJ1Ag3qLSC3t8BM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lOJ1Ag3qLSC3t8BM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lOJ1Ag3qLSC3t8BM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .input>*{fill:#EEF4FF!important;stroke:#5B8FF9!important;color:#1D39C4!important;stroke-width:1.2px!important;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .input span{fill:#EEF4FF!important;stroke:#5B8FF9!important;color:#1D39C4!important;stroke-width:1.2px!important;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .input tspan{fill:#1D39C4!important;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .design>*{fill:#F6FFED!important;stroke:#52C41A!important;color:#237804!important;stroke-width:1.2px!important;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .design span{fill:#F6FFED!important;stroke:#52C41A!important;color:#237804!important;stroke-width:1.2px!important;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .design tspan{fill:#237804!important;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .dev>*{fill:#FFF7E6!important;stroke:#FA8C16!important;color:#AD4E00!important;stroke-width:1.2px!important;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .dev span{fill:#FFF7E6!important;stroke:#FA8C16!important;color:#AD4E00!important;stroke-width:1.2px!important;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .dev tspan{fill:#AD4E00!important;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .check>*{fill:#F9F0FF!important;stroke:#722ED1!important;color:#531DAB!important;stroke-width:1.2px!important;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .check span{fill:#F9F0FF!important;stroke:#722ED1!important;color:#531DAB!important;stroke-width:1.2px!important;}#mermaid-svg-lOJ1Ag3qLSC3t8BM .check tspan{fill:#531DAB!important;} 验收交付
Codex开发
模块设计
输入约束
需求边界
PDD设计
SOP目录
接口权限
后端设计
前端设计
状态注入
读取上下文
生成后端
生成前端
联动补齐
功能自检
PDD验收
问题修复
模块交付
SOP 标准
SOP 用于约束代码目录、文件职责和开发顺序。系统配置工作台代码涉及系统菜单接口、系统配置入口、工作台组件和请求封装,Codex 需要先确认这些职责边界。
text
docs/modules/系统配置工作台/
├── pdd.md
├── api.md
├── test-cases.md
└── codex-sop.md
server_backend/dvadmin/system/views/
└── menu.py
server_vue3/src/views/system/Workbenches/SystemSetting/
├── index.vue
└── api.ts
server_vue3/src/views/system/Workbenches/components/
└── SystemSettingWorkWorkbenche.vue
| 开发阶段 | Codex 执行目标 | 输出结果 |
|---|---|---|
| 模块设计 | 读取 sys_router、SystemSetting/index.vue、api.ts 和 SystemSettingWorkWorkbenche.vue。 |
输出接口、菜单结构、页面状态和权限边界。 |
| 文档规划 | 建立 PDD、API、测试和 SOP 文档。 | 形成 pdd.md、api.md、test-cases.md、codex-sop.md。 |
| 后端实现 | 校准菜单权限过滤、系统功能节点选择、树形返回和空状态。 | 接口返回系统功能菜单树。 |
| 前端实现 | 校准分组转换、搜索、最近使用、激活 tab、文档注入和跳转。 | 页面可搜索、回显最近入口并跳转。 |
| 数据联动 | 打通菜单树、分组、卡片、搜索和本地状态。 | 后端返回与前端展示一致。 |
| 验收修复 | 按 PDD 检查权限、空状态、本地缓存、文档注入和路由跳转。 | 输出验收结果与需修复文件位置。 |
可以直接使用下面的Prompt进行SOP撰写
text
请 Codex 按教育管理系统模块开发 SOP,从零实现或补齐"系统配置工作台"模块。
执行要求:
1. 先输出目录结构,不要直接写代码。
2. 先生成 docs/modules/系统配置工作台/pdd.md、api.md、test-cases.md 和 codex-sop.md。
3. 文档确认模块边界后,再根据文档生成或修正项目代码。
4. 后端范围限定在 dvadmin/system/views/menu.py 的 sys_router。
5. 前端范围限定在 SystemSetting/index.vue、api.ts 和 SystemSettingWorkWorkbenche.vue。
6. 后端需要覆盖菜单权限过滤、系统功能节点选择、树形返回和空状态。
7. 前端需要覆盖分组转换、搜索过滤、最近使用、激活 tab、使用文档注入、图片回退和路由跳转。
8. 不生成源码中不存在的扩展能力。
输出目录结构、开发阶段表、后端任务清单、前端任务清单、状态注入清单和验收修复清单。
PDD 标准
PDD 是模块设计与验收文档,用来约束 Codex 输出是否符合真实业务。系统配置工作台的 PDD 要把菜单来源、权限过滤、分组转换、搜索、最近使用、使用文档注入和路由跳转写成可验证条目。
| 验收维度 | 验收标准 | 需要检查的文件 |
|---|---|---|
| 业务目标 | 模块能按权限聚合系统功能入口,并展示为可搜索工作台。 | pdd.md、menu.py、SystemSettingWorkWorkbenche.vue |
| 页面结构 | 页面包含分组、搜索、最近使用、卡片、图片或图标回退、空状态。 | SystemSetting/index.vue、SystemSettingWorkWorkbenche.vue |
| 数据模型 | 不新增业务模型,菜单数据来自系统菜单树。 | menu.py |
| 接口规则 | /api/system/menu/sys_router/ 返回"系统功能"树。 |
menu.py、api.md |
| 权限控制 | 普通用户只看到角色授权菜单。 | menu.py、test-cases.md |
| 数据联动 | 菜单树、分组、搜索、最近使用和路由跳转保持一致。 | SystemSettingWorkWorkbenche.vue |
| 本地状态回显 | 最近使用和激活 tab 可恢复,但不能展示无权限菜单。 | SystemSettingWorkWorkbenche.vue |
| 手动卡片注入 | 使用文档入口能注入并去重。 | SystemSettingWorkWorkbenche.vue |
| 测试用例 | 覆盖有菜单、无菜单、无权限、搜索为空、本地缓存损坏、文档注入和路由跳转。 | test-cases.md |
可以直接使用下面的Prompt进行PDD 验收
text
请 Codex 根据 docs/modules/系统配置工作台/pdd.md 对"系统配置工作台"模块进行验收。
验收范围:
server_backend/dvadmin/system/views/menu.py
server_vue3/src/views/system/Workbenches/SystemSetting/index.vue
server_vue3/src/views/system/Workbenches/SystemSetting/api.ts
server_vue3/src/views/system/Workbenches/components/SystemSettingWorkWorkbenche.vue
docs/modules/系统配置工作台/api.md
docs/modules/系统配置工作台/test-cases.md
验收维度:
1. 业务目标:是否能聚合系统功能入口。
2. 页面结构:是否包含分组、搜索、最近使用和卡片跳转。
3. 数据模型:是否未误建业务模型。
4. 接口规则:sys_router 返回结构是否符合前端转换。
5. 权限控制:是否只展示授权菜单。
6. 数据联动:菜单树、搜索、最近使用和 router.push 是否一致。
7. 本地状态回显:缓存损坏、tab 不存在、菜单变更是否处理。
8. 手动卡片注入:使用文档入口是否注入并去重。
9. 测试用例:是否覆盖成功路径和失败路径。
输出验收结果表,标记通过、未通过和需要修复的文件位置。不要只给结论,需要指出具体问题、影响范围和修复建议。
总结
系统配置工作台不是静态导航页,而是教育管理系统中由系统菜单权限、本地状态和手动卡片注入共同驱动的入口闭环。它把系统功能菜单、搜索、最近使用、使用文档和路由跳转连接起来。
用 Codex 开发该模块时,PDD 负责定义菜单来源和验收标准,SOP 负责约束系统菜单接口与前端组件职责,Prompt 负责把权限查询、分组转换、本地状态回显、文档注入和路由跳转拆成分阶段任务。这样生成的代码不会偏离真实工作台结构。