考试中心数据工作台是考试业务模块的入口聚合页,负责把考试安排、历史试卷、题库中心、审核中心、成绩分析和知识点推荐等入口按权限组织成卡片。它的核心价值是让考试业务从统一工作台进入,而不是把入口散落在菜单树里。

本文基于 WorkbenchesViewSet、菜单权限查询和 CommonWorkbenches 通用组件,说明如何把考试中心的菜单树、角色权限、分区展示和路由跳转,转换成 Codex 可执行的项目代码生成任务。
文章目录
设计与需求
考试中心数据工作台不能按普通 CRUD 页面理解。它没有独立业务表,后端通过 server_backend/dvadmin/utils/workbenches.py 中的 WorkbenchesViewSet.web_router 读取系统菜单,前端通过 server_vue3/src/views/modules/TestingCenter/Workbenches/index.vue 传入 /api/TestingCenter/Workbenches/web_router/,再由 server_vue3/src/components/commonWorkbenches/index.vue 和 server_vue3/src/views/system/Workbenches/components/EachModuleWorkWorkbenche.vue 渲染卡片。
交给 Codex 的任务需要把考试中心工作台理解成"菜单权限驱动的业务入口模块"。后端根据请求路径推导 /TestingCenterWorkbenchesData、/TestingCenterWorkbenchesStatistics 等父菜单路径,按角色权限过滤可见菜单,再返回 System、Data、Setting、Statistics、Application 分区。这里的 Statistics 是工作台分区,不代表工作台自身实现成绩计算。
#mermaid-svg-qcUW9b8zGxilQZSe{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-qcUW9b8zGxilQZSe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qcUW9b8zGxilQZSe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qcUW9b8zGxilQZSe .error-icon{fill:#552222;}#mermaid-svg-qcUW9b8zGxilQZSe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qcUW9b8zGxilQZSe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qcUW9b8zGxilQZSe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qcUW9b8zGxilQZSe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qcUW9b8zGxilQZSe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qcUW9b8zGxilQZSe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qcUW9b8zGxilQZSe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qcUW9b8zGxilQZSe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qcUW9b8zGxilQZSe .marker.cross{stroke:#333333;}#mermaid-svg-qcUW9b8zGxilQZSe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qcUW9b8zGxilQZSe p{margin:0;}#mermaid-svg-qcUW9b8zGxilQZSe .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qcUW9b8zGxilQZSe .cluster-label text{fill:#333;}#mermaid-svg-qcUW9b8zGxilQZSe .cluster-label span{color:#333;}#mermaid-svg-qcUW9b8zGxilQZSe .cluster-label span p{background-color:transparent;}#mermaid-svg-qcUW9b8zGxilQZSe .label text,#mermaid-svg-qcUW9b8zGxilQZSe span{fill:#333;color:#333;}#mermaid-svg-qcUW9b8zGxilQZSe .node rect,#mermaid-svg-qcUW9b8zGxilQZSe .node circle,#mermaid-svg-qcUW9b8zGxilQZSe .node ellipse,#mermaid-svg-qcUW9b8zGxilQZSe .node polygon,#mermaid-svg-qcUW9b8zGxilQZSe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qcUW9b8zGxilQZSe .rough-node .label text,#mermaid-svg-qcUW9b8zGxilQZSe .node .label text,#mermaid-svg-qcUW9b8zGxilQZSe .image-shape .label,#mermaid-svg-qcUW9b8zGxilQZSe .icon-shape .label{text-anchor:middle;}#mermaid-svg-qcUW9b8zGxilQZSe .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qcUW9b8zGxilQZSe .rough-node .label,#mermaid-svg-qcUW9b8zGxilQZSe .node .label,#mermaid-svg-qcUW9b8zGxilQZSe .image-shape .label,#mermaid-svg-qcUW9b8zGxilQZSe .icon-shape .label{text-align:center;}#mermaid-svg-qcUW9b8zGxilQZSe .node.clickable{cursor:pointer;}#mermaid-svg-qcUW9b8zGxilQZSe .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qcUW9b8zGxilQZSe .arrowheadPath{fill:#333333;}#mermaid-svg-qcUW9b8zGxilQZSe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qcUW9b8zGxilQZSe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qcUW9b8zGxilQZSe .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qcUW9b8zGxilQZSe .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qcUW9b8zGxilQZSe .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qcUW9b8zGxilQZSe .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qcUW9b8zGxilQZSe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qcUW9b8zGxilQZSe .cluster text{fill:#333;}#mermaid-svg-qcUW9b8zGxilQZSe .cluster span{color:#333;}#mermaid-svg-qcUW9b8zGxilQZSe 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-qcUW9b8zGxilQZSe .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qcUW9b8zGxilQZSe rect.text{fill:none;stroke-width:0;}#mermaid-svg-qcUW9b8zGxilQZSe .icon-shape,#mermaid-svg-qcUW9b8zGxilQZSe .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qcUW9b8zGxilQZSe .icon-shape p,#mermaid-svg-qcUW9b8zGxilQZSe .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qcUW9b8zGxilQZSe .icon-shape .label rect,#mermaid-svg-qcUW9b8zGxilQZSe .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qcUW9b8zGxilQZSe .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qcUW9b8zGxilQZSe .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qcUW9b8zGxilQZSe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-qcUW9b8zGxilQZSe .input>*{fill:#EEF4FF!important;stroke:#5B8FF9!important;color:#1D39C4!important;stroke-width:1.2px!important;}#mermaid-svg-qcUW9b8zGxilQZSe .input span{fill:#EEF4FF!important;stroke:#5B8FF9!important;color:#1D39C4!important;stroke-width:1.2px!important;}#mermaid-svg-qcUW9b8zGxilQZSe .input tspan{fill:#1D39C4!important;}#mermaid-svg-qcUW9b8zGxilQZSe .design>*{fill:#F6FFED!important;stroke:#52C41A!important;color:#237804!important;stroke-width:1.2px!important;}#mermaid-svg-qcUW9b8zGxilQZSe .design span{fill:#F6FFED!important;stroke:#52C41A!important;color:#237804!important;stroke-width:1.2px!important;}#mermaid-svg-qcUW9b8zGxilQZSe .design tspan{fill:#237804!important;}#mermaid-svg-qcUW9b8zGxilQZSe .process>*{fill:#FFF7E6!important;stroke:#FA8C16!important;color:#AD4E00!important;stroke-width:1.2px!important;}#mermaid-svg-qcUW9b8zGxilQZSe .process span{fill:#FFF7E6!important;stroke:#FA8C16!important;color:#AD4E00!important;stroke-width:1.2px!important;}#mermaid-svg-qcUW9b8zGxilQZSe .process tspan{fill:#AD4E00!important;}#mermaid-svg-qcUW9b8zGxilQZSe .check>*{fill:#F9F0FF!important;stroke:#722ED1!important;color:#531DAB!important;stroke-width:1.2px!important;}#mermaid-svg-qcUW9b8zGxilQZSe .check span{fill:#F9F0FF!important;stroke:#722ED1!important;color:#531DAB!important;stroke-width:1.2px!important;}#mermaid-svg-qcUW9b8zGxilQZSe .check tspan{fill:#531DAB!important;}#mermaid-svg-qcUW9b8zGxilQZSe .output>*{fill:#E6FFFB!important;stroke:#13C2C2!important;color:#006D75!important;stroke-width:1.2px!important;}#mermaid-svg-qcUW9b8zGxilQZSe .output span{fill:#E6FFFB!important;stroke:#13C2C2!important;color:#006D75!important;stroke-width:1.2px!important;}#mermaid-svg-qcUW9b8zGxilQZSe .output tspan{fill:#006D75!important;} 验收阶段
生成阶段
设计阶段
输入阶段
考试入口
菜单权限
组件源码
菜单分组
权限过滤
卡片布局
路由跳转
读取源码
生成后端
生成前端
入口检查
PDD验收
| 需求层描述 | 设计层转换 | Codex 代码生成方向 |
|---|---|---|
| 考试中心需要统一入口 | TestingCenter/Workbenches/index.vue 传入考试中心工作台 API。 |
生成轻量入口页面,复用通用工作台组件。 |
| 菜单需要按考试中心归集 | web_router 根据请求路径推导考试中心工作台菜单父路径。 |
生成路径解析、菜单父节点查找和空结果处理。 |
| 权限需要控制可见功能 | 非超级管理员按 RoleMenuPermission 过滤菜单。 |
生成角色权限过滤、越权菜单不可见测试。 |
| 前端需要分区展示 | EachModuleWorkWorkbenche 按固定顺序渲染工作台 tabs。 |
生成分区标题、卡片列表、空状态和默认激活项。 |
| 考试功能需要可跳转 | 每个卡片使用菜单 path 进入具体考试功能页。 |
生成点击、键盘访问和路由跳转验收。 |
| 图片需要稳定展示 | 菜单 image 存在时展示图片,缺失时使用回退图标。 |
生成图片地址拼接、版本缓存参数和错误回退。 |
可以直接使用下面的Prompt进行模块功能的设计
text
请 Codex 基于教育管理系统真实源码设计"考试中心数据工作台"模块。
模块定位:
考试中心数据工作台用于聚合考试安排、历史试卷、题库中心、题库审核、成绩分析、知识点推荐等菜单入口。后端按当前用户菜单权限返回分区数据,前端用通用工作台组件渲染卡片并跳转到具体页面。
源码范围:
server_backend/dvadmin/utils/workbenches.py
server_backend/modules/TestingCenter/urls.py
server_vue3/src/views/modules/TestingCenter/Workbenches/index.vue
server_vue3/src/components/commonWorkbenches/api.ts
server_vue3/src/components/commonWorkbenches/index.vue
server_vue3/src/views/system/Workbenches/components/EachModuleWorkWorkbenche.vue
接口范围:
GET /api/TestingCenter/Workbenches/web_router/
返回结构:
System、Data、Setting、Statistics、Application
每个分区包含 label、value、children
children 使用 WebRouterSerializer 返回菜单路由字段
设计要求:
1. 输出页面结构、后端菜单查询、权限规则、前端卡片渲染和验收标准。
2. 说明 WorkbenchesViewSet 如何根据请求路径推导考试中心菜单后缀。
3. 说明 RoleMenuPermission 如何限制非超级管理员可见菜单。
4. 说明 CommonWorkbenches 与 EachModuleWorkWorkbenche 的组件职责。
5. 只允许设计源码中存在的菜单分组、权限过滤和卡片导航能力。
后端设计
后端设计重点不是新增考试工作台模型,而是把系统菜单树整理成考试中心可用的工作台数据。WorkbenchesViewSet 使用 DummyModel.objects.none() 和 DummySerializer 作为占位,真实数据来自 Menu、RoleMenuPermission 和 WebRouterSerializer。server_backend/modules/TestingCenter/urls.py 注册 WorkbenchesViewSet 后,考试中心工作台接口为 /api/TestingCenter/Workbenches/web_router/。
_menu_block_serializer 会从请求路径中解析模块名和路由名,组合 /{web}{router}{suffix} 作为目标父菜单路径。对于考试中心,请求路径会推导出 /TestingCenterWorkbenchesData、/TestingCenterWorkbenchesStatistics 等菜单块。命中父菜单后,后端把第一层菜单作为目录组,继续收集其下所有叶子菜单,返回给前端渲染卡片。
| 后端设计项 | 设计重点 | Codex 生成方向 |
|---|---|---|
| 视图入口 | WorkbenchesViewSet.web_router 返回考试中心工作台菜单数据。 |
保持 GET /api/TestingCenter/Workbenches/web_router/。 |
| 路径解析 | 根据 request.path 解析 TestingCenter 和 Workbenches。 |
生成路径校验、父菜单路径拼接和空数组返回。 |
| 菜单查询 | 通过 Menu.web_path 命中父菜单,读取目录组和叶子菜单。 |
生成层级查询、排序和叶子节点收集。 |
| 权限过滤 | 普通用户按角色权限可见,超级管理员可见全部启用菜单。 | 生成权限过滤和越权菜单不可见测试。 |
| 返回结构 | 返回 System、Data、Setting、Statistics、Application 五组。 |
保持 label、value、children 结构稳定。 |
可以直接使用下面的Prompt进行后端代码的设计
text
请 Codex 按"考试中心数据工作台"业务从零设计或补齐后端代码。
后端源码范围:
server_backend/dvadmin/utils/workbenches.py
server_backend/modules/TestingCenter/urls.py
server_backend/dvadmin/system/models.py
必须遵守的现有结构:
1. WorkbenchesViewSet 使用 DummyModel 和 DummySerializer。
2. 考试中心路由注册 WorkbenchesViewSet。
3. 菜单数据来自 Menu 和 RoleMenuPermission。
4. 叶子菜单序列化使用 WebRouterSerializer。
需要实现或校准:
1. 根据 /api/TestingCenter/Workbenches/web_router/ 推导考试中心工作台菜单父路径。
2. 按 Data、Setting、Statistics、Application、System 读取菜单块。
3. 普通用户只能看到角色授权菜单。
4. 找不到菜单或无权限时返回空数组。
5. 返回结构保持 label、value、children。
输出涉及文件、接口返回结构、权限规则、空状态处理和测试建议。
前端设计
前端设计重点是复用通用工作台能力,而不是为考试中心重新写一套卡片。server_vue3/src/views/modules/TestingCenter/Workbenches/index.vue 只声明 apiUrl = '/api/TestingCenter/Workbenches/web_router/' 并传给 CommonWorkbenches。CommonWorkbenches 再把参数传给 EachModuleWorkWorkbenche。
EachModuleWorkWorkbenche.vue 请求后端分区数据后,按 Data、System、Setting、Statistics、Application 生成 tabs,并将 label 显示为二级目录标题,将 children 渲染为入口卡片。卡片包含图片、名称、描述和箭头,支持鼠标点击、Enter 和 Space 键跳转。
| 前端设计项 | 设计重点 | Codex 生成方向 |
|---|---|---|
| 模块入口 | 考试中心入口页面只传 apiUrl。 |
保持组件复用,避免重复卡片逻辑。 |
| 接口封装 | commonWorkbenches/api.ts 的 GetList(url) 使用传入 URL。 |
生成通用请求、错误提示和 loading 处理。 |
| 分区渲染 | 按固定分区顺序渲染 tabs,空分区不展示。 | 生成默认激活项和空状态。 |
| 卡片展示 | 使用菜单 path、name、image、desc。 |
生成图片回退、图标回退和响应式布局。 |
| 路由跳转 | handleToSubMenu 调用 router.push({ path })。 |
生成点击和键盘可访问交互。 |
可以直接使用下面的Prompt进行前端代码的设计
text
请 Codex 按"考试中心数据工作台"业务生成或补齐前端页面代码。
前端源码范围:
server_vue3/src/views/modules/TestingCenter/Workbenches/index.vue
server_vue3/src/components/commonWorkbenches/api.ts
server_vue3/src/components/commonWorkbenches/index.vue
server_vue3/src/views/system/Workbenches/components/EachModuleWorkWorkbenche.vue
页面现状:
1. TestingCenter/Workbenches/index.vue 传入 apiUrl:/api/TestingCenter/Workbenches/web_router/。
2. CommonWorkbenches 负责转发 apiUrl 和 limit。
3. EachModuleWorkWorkbenche 请求后端分区数据并渲染 tabs 和卡片。
4. 卡片使用菜单 path、name、image、desc。
5. 点击卡片通过 router.push 进入具体考试功能。
需要生成或修正:
1. 保持通用工作台组件,不在考试中心入口重复实现卡片。
2. 对空分区、空 children、图片加载失败进行处理。
3. 保持 Enter 和 Space 键跳转。
4. 只展示后端返回的授权菜单,不硬编码未授权考试入口。
5. Statistics 只作为菜单分区,不生成额外图表接口。
输出页面结构、接口调用、分区渲染、卡片交互、权限回显和验收步骤。
扩展功能
考试中心数据工作台的扩展能力主要是数据联动。它超出静态导航页的地方,是入口卡片由后端菜单树和当前用户权限动态生成,前端 tabs、目录、卡片、图片和跳转都依赖同一份 web_router 返回数据。
| 扩展功能 | 主要用途 | 落地重点 |
|---|---|---|
| 数据联动 | 将请求路径、考试菜单、角色权限、分区 tabs 和卡片跳转串成稳定数据流。 | 后端返回结构与前端分区渲染必须一致,不能前端硬编码权限菜单。 |
数据联动
数据联动从 /api/TestingCenter/Workbenches/web_router/ 开始。后端根据请求路径定位考试中心工作台菜单,按角色权限过滤可见菜单,再输出分区数组;前端根据分区生成 tabs,把目录下的叶子菜单渲染成入口卡片。用户点击卡片时,菜单 path 会成为真实路由跳转目标。
#mermaid-svg-pZpEXPT0ajAcZsDm{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-pZpEXPT0ajAcZsDm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pZpEXPT0ajAcZsDm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pZpEXPT0ajAcZsDm .error-icon{fill:#552222;}#mermaid-svg-pZpEXPT0ajAcZsDm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pZpEXPT0ajAcZsDm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pZpEXPT0ajAcZsDm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pZpEXPT0ajAcZsDm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pZpEXPT0ajAcZsDm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pZpEXPT0ajAcZsDm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pZpEXPT0ajAcZsDm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pZpEXPT0ajAcZsDm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pZpEXPT0ajAcZsDm .marker.cross{stroke:#333333;}#mermaid-svg-pZpEXPT0ajAcZsDm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pZpEXPT0ajAcZsDm p{margin:0;}#mermaid-svg-pZpEXPT0ajAcZsDm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-pZpEXPT0ajAcZsDm .cluster-label text{fill:#333;}#mermaid-svg-pZpEXPT0ajAcZsDm .cluster-label span{color:#333;}#mermaid-svg-pZpEXPT0ajAcZsDm .cluster-label span p{background-color:transparent;}#mermaid-svg-pZpEXPT0ajAcZsDm .label text,#mermaid-svg-pZpEXPT0ajAcZsDm span{fill:#333;color:#333;}#mermaid-svg-pZpEXPT0ajAcZsDm .node rect,#mermaid-svg-pZpEXPT0ajAcZsDm .node circle,#mermaid-svg-pZpEXPT0ajAcZsDm .node ellipse,#mermaid-svg-pZpEXPT0ajAcZsDm .node polygon,#mermaid-svg-pZpEXPT0ajAcZsDm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pZpEXPT0ajAcZsDm .rough-node .label text,#mermaid-svg-pZpEXPT0ajAcZsDm .node .label text,#mermaid-svg-pZpEXPT0ajAcZsDm .image-shape .label,#mermaid-svg-pZpEXPT0ajAcZsDm .icon-shape .label{text-anchor:middle;}#mermaid-svg-pZpEXPT0ajAcZsDm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-pZpEXPT0ajAcZsDm .rough-node .label,#mermaid-svg-pZpEXPT0ajAcZsDm .node .label,#mermaid-svg-pZpEXPT0ajAcZsDm .image-shape .label,#mermaid-svg-pZpEXPT0ajAcZsDm .icon-shape .label{text-align:center;}#mermaid-svg-pZpEXPT0ajAcZsDm .node.clickable{cursor:pointer;}#mermaid-svg-pZpEXPT0ajAcZsDm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-pZpEXPT0ajAcZsDm .arrowheadPath{fill:#333333;}#mermaid-svg-pZpEXPT0ajAcZsDm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-pZpEXPT0ajAcZsDm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-pZpEXPT0ajAcZsDm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pZpEXPT0ajAcZsDm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-pZpEXPT0ajAcZsDm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pZpEXPT0ajAcZsDm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-pZpEXPT0ajAcZsDm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-pZpEXPT0ajAcZsDm .cluster text{fill:#333;}#mermaid-svg-pZpEXPT0ajAcZsDm .cluster span{color:#333;}#mermaid-svg-pZpEXPT0ajAcZsDm 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-pZpEXPT0ajAcZsDm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pZpEXPT0ajAcZsDm rect.text{fill:none;stroke-width:0;}#mermaid-svg-pZpEXPT0ajAcZsDm .icon-shape,#mermaid-svg-pZpEXPT0ajAcZsDm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pZpEXPT0ajAcZsDm .icon-shape p,#mermaid-svg-pZpEXPT0ajAcZsDm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-pZpEXPT0ajAcZsDm .icon-shape .label rect,#mermaid-svg-pZpEXPT0ajAcZsDm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pZpEXPT0ajAcZsDm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-pZpEXPT0ajAcZsDm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-pZpEXPT0ajAcZsDm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-pZpEXPT0ajAcZsDm .input>*{fill:#EEF4FF!important;stroke:#5B8FF9!important;color:#1D39C4!important;stroke-width:1.2px!important;}#mermaid-svg-pZpEXPT0ajAcZsDm .input span{fill:#EEF4FF!important;stroke:#5B8FF9!important;color:#1D39C4!important;stroke-width:1.2px!important;}#mermaid-svg-pZpEXPT0ajAcZsDm .input tspan{fill:#1D39C4!important;}#mermaid-svg-pZpEXPT0ajAcZsDm .process>*{fill:#FFF7E6!important;stroke:#FA8C16!important;color:#AD4E00!important;stroke-width:1.2px!important;}#mermaid-svg-pZpEXPT0ajAcZsDm .process span{fill:#FFF7E6!important;stroke:#FA8C16!important;color:#AD4E00!important;stroke-width:1.2px!important;}#mermaid-svg-pZpEXPT0ajAcZsDm .process tspan{fill:#AD4E00!important;}#mermaid-svg-pZpEXPT0ajAcZsDm .check>*{fill:#F9F0FF!important;stroke:#722ED1!important;color:#531DAB!important;stroke-width:1.2px!important;}#mermaid-svg-pZpEXPT0ajAcZsDm .check span{fill:#F9F0FF!important;stroke:#722ED1!important;color:#531DAB!important;stroke-width:1.2px!important;}#mermaid-svg-pZpEXPT0ajAcZsDm .check tspan{fill:#531DAB!important;}#mermaid-svg-pZpEXPT0ajAcZsDm .output>*{fill:#E6FFFB!important;stroke:#13C2C2!important;color:#006D75!important;stroke-width:1.2px!important;}#mermaid-svg-pZpEXPT0ajAcZsDm .output span{fill:#E6FFFB!important;stroke:#13C2C2!important;color:#006D75!important;stroke-width:1.2px!important;}#mermaid-svg-pZpEXPT0ajAcZsDm .output tspan{fill:#006D75!important;} 结果阶段
处理阶段
输入阶段
请求路径
角色权限
考试菜单
解析模块
过滤菜单
收集叶子
分区Tabs
入口卡片
路由跳转
交给 Codex 生成时,边界要限定在菜单和权限联动上。工作台不应该新增考试业务表,也不应该在前端硬编码考试安排、题库或成绩分析入口;所有入口应来自菜单配置和后端权限过滤。
可以直接使用下面的Prompt进行数据联动设计
text
请 Codex 基于真实源码补齐"考试中心数据工作台"的数据联动设计。
源码范围:
server_backend/dvadmin/utils/workbenches.py
server_backend/modules/TestingCenter/urls.py
server_vue3/src/views/modules/TestingCenter/Workbenches/index.vue
server_vue3/src/components/commonWorkbenches/api.ts
server_vue3/src/components/commonWorkbenches/index.vue
server_vue3/src/views/system/Workbenches/components/EachModuleWorkWorkbenche.vue
联动对象:
request.path、Menu.web_path、RoleMenuPermission、System、Data、Setting、Statistics、Application、path、name、image、desc
联动要求:
1. 后端根据请求路径推导考试中心工作台菜单。
2. 普通用户只能看到角色授权菜单。
3. 后端返回 label、value、children 分组结构。
4. 前端按固定顺序渲染 tabs,空分区不展示。
5. 卡片点击和键盘操作都能跳转到菜单 path。
6. 不新增源码中不存在的业务能力。
输出接口设计、前端交互、权限约束、空状态处理和验收清单。
Codex开发标准
使用 Codex 开发考试中心数据工作台时,不能让它生成静态导航页。PDD、SOP、接口权限规则和验收标准需要约束后端菜单查询、角色过滤、前端分区和路由跳转,保证入口与真实权限一致。
#mermaid-svg-3yl2tq5RS0r6zx9E{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-3yl2tq5RS0r6zx9E .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3yl2tq5RS0r6zx9E .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3yl2tq5RS0r6zx9E .error-icon{fill:#552222;}#mermaid-svg-3yl2tq5RS0r6zx9E .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3yl2tq5RS0r6zx9E .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3yl2tq5RS0r6zx9E .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3yl2tq5RS0r6zx9E .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3yl2tq5RS0r6zx9E .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3yl2tq5RS0r6zx9E .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3yl2tq5RS0r6zx9E .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3yl2tq5RS0r6zx9E .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3yl2tq5RS0r6zx9E .marker.cross{stroke:#333333;}#mermaid-svg-3yl2tq5RS0r6zx9E svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3yl2tq5RS0r6zx9E p{margin:0;}#mermaid-svg-3yl2tq5RS0r6zx9E .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3yl2tq5RS0r6zx9E .cluster-label text{fill:#333;}#mermaid-svg-3yl2tq5RS0r6zx9E .cluster-label span{color:#333;}#mermaid-svg-3yl2tq5RS0r6zx9E .cluster-label span p{background-color:transparent;}#mermaid-svg-3yl2tq5RS0r6zx9E .label text,#mermaid-svg-3yl2tq5RS0r6zx9E span{fill:#333;color:#333;}#mermaid-svg-3yl2tq5RS0r6zx9E .node rect,#mermaid-svg-3yl2tq5RS0r6zx9E .node circle,#mermaid-svg-3yl2tq5RS0r6zx9E .node ellipse,#mermaid-svg-3yl2tq5RS0r6zx9E .node polygon,#mermaid-svg-3yl2tq5RS0r6zx9E .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3yl2tq5RS0r6zx9E .rough-node .label text,#mermaid-svg-3yl2tq5RS0r6zx9E .node .label text,#mermaid-svg-3yl2tq5RS0r6zx9E .image-shape .label,#mermaid-svg-3yl2tq5RS0r6zx9E .icon-shape .label{text-anchor:middle;}#mermaid-svg-3yl2tq5RS0r6zx9E .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3yl2tq5RS0r6zx9E .rough-node .label,#mermaid-svg-3yl2tq5RS0r6zx9E .node .label,#mermaid-svg-3yl2tq5RS0r6zx9E .image-shape .label,#mermaid-svg-3yl2tq5RS0r6zx9E .icon-shape .label{text-align:center;}#mermaid-svg-3yl2tq5RS0r6zx9E .node.clickable{cursor:pointer;}#mermaid-svg-3yl2tq5RS0r6zx9E .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3yl2tq5RS0r6zx9E .arrowheadPath{fill:#333333;}#mermaid-svg-3yl2tq5RS0r6zx9E .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3yl2tq5RS0r6zx9E .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3yl2tq5RS0r6zx9E .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3yl2tq5RS0r6zx9E .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3yl2tq5RS0r6zx9E .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3yl2tq5RS0r6zx9E .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3yl2tq5RS0r6zx9E .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3yl2tq5RS0r6zx9E .cluster text{fill:#333;}#mermaid-svg-3yl2tq5RS0r6zx9E .cluster span{color:#333;}#mermaid-svg-3yl2tq5RS0r6zx9E 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-3yl2tq5RS0r6zx9E .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3yl2tq5RS0r6zx9E rect.text{fill:none;stroke-width:0;}#mermaid-svg-3yl2tq5RS0r6zx9E .icon-shape,#mermaid-svg-3yl2tq5RS0r6zx9E .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3yl2tq5RS0r6zx9E .icon-shape p,#mermaid-svg-3yl2tq5RS0r6zx9E .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3yl2tq5RS0r6zx9E .icon-shape .label rect,#mermaid-svg-3yl2tq5RS0r6zx9E .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3yl2tq5RS0r6zx9E .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3yl2tq5RS0r6zx9E .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3yl2tq5RS0r6zx9E :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-3yl2tq5RS0r6zx9E .input>*{fill:#EEF4FF!important;stroke:#5B8FF9!important;color:#1D39C4!important;stroke-width:1.2px!important;}#mermaid-svg-3yl2tq5RS0r6zx9E .input span{fill:#EEF4FF!important;stroke:#5B8FF9!important;color:#1D39C4!important;stroke-width:1.2px!important;}#mermaid-svg-3yl2tq5RS0r6zx9E .input tspan{fill:#1D39C4!important;}#mermaid-svg-3yl2tq5RS0r6zx9E .design>*{fill:#F6FFED!important;stroke:#52C41A!important;color:#237804!important;stroke-width:1.2px!important;}#mermaid-svg-3yl2tq5RS0r6zx9E .design span{fill:#F6FFED!important;stroke:#52C41A!important;color:#237804!important;stroke-width:1.2px!important;}#mermaid-svg-3yl2tq5RS0r6zx9E .design tspan{fill:#237804!important;}#mermaid-svg-3yl2tq5RS0r6zx9E .dev>*{fill:#FFF7E6!important;stroke:#FA8C16!important;color:#AD4E00!important;stroke-width:1.2px!important;}#mermaid-svg-3yl2tq5RS0r6zx9E .dev span{fill:#FFF7E6!important;stroke:#FA8C16!important;color:#AD4E00!important;stroke-width:1.2px!important;}#mermaid-svg-3yl2tq5RS0r6zx9E .dev tspan{fill:#AD4E00!important;}#mermaid-svg-3yl2tq5RS0r6zx9E .check>*{fill:#F9F0FF!important;stroke:#722ED1!important;color:#531DAB!important;stroke-width:1.2px!important;}#mermaid-svg-3yl2tq5RS0r6zx9E .check span{fill:#F9F0FF!important;stroke:#722ED1!important;color:#531DAB!important;stroke-width:1.2px!important;}#mermaid-svg-3yl2tq5RS0r6zx9E .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/utils/
└── workbenches.py
server_backend/modules/TestingCenter/
└── urls.py
server_vue3/src/views/modules/TestingCenter/Workbenches/
└── index.vue
server_vue3/src/components/commonWorkbenches/
├── api.ts
└── index.vue
server_vue3/src/views/system/Workbenches/components/
└── EachModuleWorkWorkbenche.vue
| 开发阶段 | Codex 执行目标 | 输出结果 |
|---|---|---|
| 模块设计 | 读取 WorkbenchesViewSet、考试中心路由、通用工作台组件和考试入口。 |
输出接口、菜单结构、权限和前端职责。 |
| 文档规划 | 建立 PDD、API、测试和 SOP 文档。 | 形成 pdd.md、api.md、test-cases.md、codex-sop.md。 |
| 后端实现 | 校准菜单路径解析、权限过滤、分组返回和空状态。 | 工作台接口返回授权考试入口。 |
| 前端实现 | 校准入口、通用组件、tabs、卡片、图片回退和跳转。 | 页面能展示考试入口并跳转。 |
| 数据联动 | 打通请求路径、菜单权限、分区 tabs 和卡片路由。 | 后端返回与前端展示一致。 |
| 验收修复 | 按 PDD 检查权限、空状态、路由跳转和响应式展示。 | 输出验收结果与需修复文件位置。 |
可以直接使用下面的Prompt进行SOP撰写
text
请 Codex 按教育管理系统模块开发 SOP,从零实现或补齐"考试中心数据工作台"模块。
执行要求:
1. 先输出目录结构,不要直接写代码。
2. 先生成 docs/modules/考试中心数据工作台/pdd.md、api.md、test-cases.md 和 codex-sop.md。
3. 文档确认模块边界后,再根据文档生成或修正项目代码。
4. 后端范围限定在 dvadmin/utils/workbenches.py 和 modules/TestingCenter/urls.py。
5. 前端范围限定在 TestingCenter/Workbenches/index.vue、commonWorkbenches、EachModuleWorkWorkbenche.vue。
6. 后端需要覆盖菜单路径解析、RoleMenuPermission 权限过滤、叶子菜单收集和分区返回。
7. 前端需要覆盖 apiUrl 传入、tabs 渲染、卡片展示、图片回退、空状态和路由跳转。
8. 不生成源码中不存在的扩展能力。
输出目录结构、开发阶段表、后端任务清单、前端任务清单、数据联动清单和验收修复清单。
PDD 标准
PDD 是模块设计与验收文档,用来约束 Codex 输出是否符合真实业务。考试中心数据工作台的 PDD 要把菜单来源、权限过滤、返回结构、卡片渲染和路由跳转写成可验证条目。
| 验收维度 | 验收标准 | 需要检查的文件 |
|---|---|---|
| 业务目标 | 模块能按权限聚合考试中心菜单入口,并展示为工作台卡片。 | pdd.md、workbenches.py、EachModuleWorkWorkbenche.vue |
| 页面结构 | 页面包含分区 tabs、目录标题、入口卡片、图片或回退图标、空状态。 | TestingCenter/Workbenches/index.vue、EachModuleWorkWorkbenche.vue |
| 数据模型 | 不新增业务模型,使用 DummyModel 占位,菜单数据来自 Menu。 |
workbenches.py |
| 接口规则 | /api/TestingCenter/Workbenches/web_router/ 返回五类分区和菜单 children。 |
workbenches.py、api.md |
| 权限控制 | 普通用户只看到角色授权菜单,超级管理员看到全部启用菜单。 | workbenches.py、test-cases.md |
| 数据联动 | 请求路径、菜单父路径、分区数据、卡片 path 和前端跳转保持一致。 | workbenches.py、EachModuleWorkWorkbenche.vue |
| 测试用例 | 覆盖超级管理员、普通角色、无权限、无菜单、图片缺失和路由跳转。 | test-cases.md |
可以直接使用下面的Prompt进行PDD 验收
text
请 Codex 根据 docs/modules/考试中心数据工作台/pdd.md 对"考试中心数据工作台"模块进行验收。
验收范围:
server_backend/dvadmin/utils/workbenches.py
server_backend/modules/TestingCenter/urls.py
server_vue3/src/views/modules/TestingCenter/Workbenches/index.vue
server_vue3/src/components/commonWorkbenches/api.ts
server_vue3/src/components/commonWorkbenches/index.vue
server_vue3/src/views/system/Workbenches/components/EachModuleWorkWorkbenche.vue
docs/modules/考试中心数据工作台/api.md
docs/modules/考试中心数据工作台/test-cases.md
验收维度:
1. 业务目标:是否能聚合考试中心菜单入口。
2. 页面结构:是否包含 tabs、分组、卡片、图片回退和空状态。
3. 数据模型:是否未误建业务模型。
4. 接口规则:web_router 返回结构是否包含五类分区。
5. 权限控制:不同角色是否只看到授权菜单。
6. 数据联动:request.path、Menu.web_path、children.path 和 router.push 是否一致。
7. 测试用例:是否覆盖权限、空菜单、图片失败和跳转。
输出验收结果表,标记通过、未通过和需要修复的文件位置。不要只给结论,需要指出具体问题、影响范围和修复建议。
总结
考试中心数据工作台不是静态导航页,而是教育管理系统中由菜单权限驱动的考试入口闭环。它把后端菜单树、角色权限、分区数据和前端卡片跳转连接起来,让用户只看到自己可访问的考试中心功能。
用 Codex 开发该模块时,PDD 负责定义菜单来源和验收标准,SOP 负责约束通用工作台与考试入口的职责边界,Prompt 负责把后端权限查询、前端分区渲染和路由跳转拆成分阶段任务。这样生成的代码不会偏离真实工作台结构。