23 KiB
患者列表、动态菜单与权限一致性审计
审计日期:2026-08-10
管理端唯一事实来源:D:\web\zyt\admin\src\views\**
Python 对照范围:D:\web\zyt\app\src\doctor_workstation\**
1. 结论摘要
| 审计项 | 结论 | 摘要 |
|---|---|---|
| “患者列表”真实业务页面识别 | EXACT | 医生工作站语义下的真实页面是 first_visit/my_patients/index.vue,不是同样含“患者列表”字样的 doctor/progress.vue。 |
| Python 患者列表基础查询 | PARTIAL | 使用同一“我的患者”业务语义、相同五种状态和服务端数据范围,但只实现了主列表的子集。 |
| 患者详情与历史关联 | MISSING | Python 没有进入诊单编辑/只读详情链,也没有详情请求;右侧内容直接取列表行及其 raw。 |
| 患者订单管理、面诊进度 | MISSING | 管理端同一路由内的两个完整工作区在 Python 中均不存在。 |
| 五模块主导航 | PARTIAL | Python 解析并保存 mySelf.menu,登录时只判断菜单非空;Shell 随后忽略菜单树,使用固定五项加扁平权限码。 |
| 按钮权限 | PARTIAL | 多数已有写操作有权限门禁,但存在 canonical 权限码偏差、额外别名放行、患者动作大量缺失;处方库所有者判断存在 fail-open。 |
| 医生角色/部门数据范围 | PARTIAL | 患者主列表的请求未注入任意医生 ID,保持服务端权威范围;但 Python 丢弃 extend.scope、汇总和 ownership 模式,且缺少订单/进度工作区。 |
判定口径:
- EXACT:当前证据范围内,业务入口、条件、权限或数据语义一致。
- PARTIAL:主链存在,但字段、筛选、动作、菜单约束或范围提示不完整/不一致。
- MISSING:管理端已存在的业务能力,在 Python 中未找到入口或调用链。
2. 证据边界
- 本报告没有读取
D:\web\zyt\admin\src\views之外的管理端文件。因此可以确认 Vue 页面实际引用的 API 函数名、参数和交互,但不能读取@/api/**的实现来反推真实 HTTP URL。 views/**内没有auth.admin/mySelf调用或该接口返回的真实菜单 JSON。五个主页面的paths/component/perms/sort/is_show/is_disable精确菜单记录无法从本次允许范围内恢复。- 管理端的菜单管理页证明动态菜单记录至少分别保存“路由路径”“组件路径”“权限字符”“是否显示”“菜单状态”:
D:\web\zyt\admin\src\views\permission\menu\edit.vue:41-94,125-150;其中隐藏菜单仍可访问的语义写在D:\web\zyt\admin\src\views\permission\menu\edit.vue:135-137。因此,不能把单一扁平权限码等同于完整路由记录。 - 对服务端是否再次执行权限/所有权校验不作推断;本报告的 P0 是客户端授权门禁一致性问题。
3. “患者列表”真实页面识别
3.1 主页面:first_visit/my_patients/index.vue
确认依据:
- 页面直接声明“一诊 / 我的患者”,并说明“患者、挂号与诊单信息按当前角色和部门数据范围展示”:
D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:5-10。 - 同一路由内明确包含“患者列表 / 订单管理 / 面诊进度”三个工作区:
D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:15-20。 - 主表由
myPatientLists驱动,并异步装载诊单编辑、预约、订单和进度组件:D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:330-344,404-409。
结论:Python PatientsPage 应对标这一页面及其子工作区,而不应只对标某张通用患者/挂号表。
3.2 排除项:doctor/progress.vue
该文件虽在表头使用“患者列表”,但它是医生面诊进度看板:
- 页面要求先选择医生,再展示该医生挂号:
D:\web\zyt\admin\src\views\doctor\progress.vue:57-88。 - 组件名为
doctorProgress,API 为doctorLists与appointmentLists:D:\web\zyt\admin\src\views\doctor\progress.vue:176-182。 - 它显式传
role_id: 1拉医生,并用doctor_id + progress_board=1拉挂号;注释说明不按医生/医助角色收窄:D:\web\zyt\admin\src\views\doctor\progress.vue:408-448,460。
因此该页面是跨医生进度看板,不是“我的患者”主数据页。把它的数据范围套到患者模块会错误扩大语义范围。
4. 管理端患者模块真实合同
4.1 主列表、筛选和服务端范围
| 能力 | 管理端证据 |
|---|---|
| 关键词 | 患者姓名、手机号、助理、医生:D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:23-43 |
| 状态 | 未预约、待面诊、已完成、已过号:D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:346-393 |
| 挂号日期 | 今日、明日、后天、近 7 天、近 30 天、自定义区间:D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:46-69,395-401 |
| 汇总 | 今日/明日/后天预约人数来自 pager.extend.summary/dates:D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:72-88,411-416 |
| 范围提示 | 顶部和表格显示 pager.extend.scope.label:D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:90-96,417 |
| 列表字段 | 患者性别年龄/脱敏手机、归属助理、预约医生及状态、预约时间、复诊次数、确认信息、诊单日期:D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:105-148 |
主列表 API 符号为 myPatientLists,调用参数为 keyword/status_filter/start_date/end_date/page:D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:330-336,380-408。
4.2 行级动作与权限码
| 动作 | 管理端权限/条件 | 证据 |
|---|---|---|
| 编辑诊单 | tcm.diagnosis/edit |
D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:152,418,492-498 |
| 只读查看 | tcm.diagnosis/readonlyDetail |
D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:153,419,500-510 |
| 预约/取消挂号 | tcm.diagnosis/guahao;取消仅状态 1/4 |
D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:154,179-186,420,681-696 |
| 指派/重新指派医助 | tcm.diagnosis/assign |
D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:155-162,421,527-575 |
| 补全身份证 | 复用 tcm.diagnosis/edit |
D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:163-170,422,578-628 |
| 诊单二维码 | 复用挂号权限,且要求有效挂号、状态 1、医生 ID | D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:171-178,630-673 |
只读诊单不是列表内的摘要面板。页面先从动态路由中查找 meta.perms === 'tcm.diagnosis/readonlyDetail',再携带诊单 ID 跳转:D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:500-510。
4.3 订单管理子工作区
- 数据口径明确为“当前患者范围内”的处方业务订单,且“订单创建人不参与数据归属判断”:
D:\web\zyt\admin\src\views\first_visit\my_patients\components\OrderPanel.vue:3-10。 - 支持处方审核、支付单审核、履约状态、关键词与日期筛选:
D:\web\zyt\admin\src\views\first_visit\my_patients\components\OrderPanel.vue:14-79。 - 汇总包含订单数、有效金额、待审核、完成/签收、拒收数和拒收率:
D:\web\zyt\admin\src\views\first_visit\my_patients\components\OrderPanel.vue:82-123,289-297。 - 列表显示订单/患者/处方与诊单/金额/双审/履约/支付单/助理/医生/创建人:
D:\web\zyt\admin\src\views\first_visit\my_patients\components\OrderPanel.vue:125-188。 - 数据由
myPatientOrderLists分页加载:D:\web\zyt\admin\src\views\first_visit\my_patients\components\OrderPanel.vue:234-244,282-286。 - 订单按钮以 canonical
tcm.prescriptionOrder/*权限和业务状态共同判定;完整矩阵在D:\web\zyt\admin\src\views\first_visit\my_patients\components\order-actions.ts:32-124,包括详情、编辑、双审及撤回、快递、补支付、完成、退款、撤回、上传药房。
4.4 面诊进度子工作区
- 服务端通过
extend.schedule_mode决定“按本人归属”或“与排班合并”,而不是客户端按角色 ID 猜测:D:\web\zyt\admin\src\views\first_visit\my_patients\components\ProgressPanel.vue:7-26,206-214。 - “本人患者”语义、近七日安排、医生聚合、候诊队列均在同一工作区:
D:\web\zyt\admin\src\views\first_visit\my_patients\components\ProgressPanel.vue:31-117,120-178。 - API 符号为
myPatientProgressLists,数据范围标签来自extend.scope.label,本人患者由row.is_self_patient标识:D:\web\zyt\admin\src\views\first_visit\my_patients\components\ProgressPanel.vue:185-203,263,308-309。 - 每 15 秒自动刷新:
D:\web\zyt\admin\src\views\first_visit\my_patients\components\ProgressPanel.vue:318-325。
4.5 详情与历史关联链
管理端患者行会进入诊单编辑或只读详情,而后继续关联:
- 只读页真实标题为“患者信息详情”,无权限/不存在时明确空态:
D:\web\zyt\admin\src\views\tcm\diagnosis\readonly.vue:4-36。 - 基本信息、完整病历、日常记录、医生备注/舌苔/报告:
D:\web\zyt\admin\src\views\tcm\diagnosis\readonly.vue:38-72。 - 业务订单、视频回放、聊天、医助指派历史、挂号历史分别按权限展示:
D:\web\zyt\admin\src\views\tcm\diagnosis\readonly.vue:74-146。 - 详情由
diagnosisReadonlyDetail({id})拉取,备注另用getDoctorNotes({diagnosis_id})补齐:D:\web\zyt\admin\src\views\tcm\diagnosis\readonly.vue:151-188,209-234。 - 编辑页还提供处方病历、订单、视频、聊天、指派与挂号记录分页签:
D:\web\zyt\admin\src\views\tcm\diagnosis\edit.vue:655-759。 - 挂号历史不是列表行快照,而是按诊单 ID 单独调用
appointmentLists,携带diag_scope_relax: 1、最多 500 条:D:\web\zyt\admin\src\views\tcm\diagnosis\components\AppointmentRecordPanel.vue:79-89,139-152。 - 指派历史同样按诊单 ID 调用
tcmDiagnosisAssignLogList:D:\web\zyt\admin\src\views\tcm\diagnosis\components\AssignLogPanel.vue:47-57,102-125。 - 业务订单按
context_diagnosis_id + patient_id + scene=diagnosis_edit独立分页:D:\web\zyt\admin\src\views\tcm\diagnosis\components\PatientOrderList.vue:118-148,170-179。
5. Python 患者模块逐项对照
| 项目 | Python 当前实现 | 判定 | 说明 |
|---|---|---|---|
| 页面身份 | 标题“我的患者”,描述服务端授权范围:D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:84-92 |
EXACT | 对标对象正确。 |
| 状态筛选 | 五种状态一致:D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:94-119 |
EXACT | 状态值与管理端一致。 |
| 关键词 | Python 提示“姓名、手机号或诊单号”:D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:99-103 |
PARTIAL | 管理端还明确支持助理/医生。 |
| 日期与汇总 | 无日期快捷项、区间和三日汇总 | MISSING | 管理端证据见 4.1。 |
| 表格 | 患者、性别年龄、电话、进度、医助、最近预约、复诊:D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:132-196 |
PARTIAL | 缺预约医生独立列、确认信息、诊单日期和全部操作。 |
| 列表请求 | patients(keyword,status,page,page_size):D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:307-326 |
PARTIAL | 无 start_date/end_date;服务层正确映射 status -> status_filter。 |
| 服务端范围 | list_patients 使用 firstvisit.myPatient/lists,不注入医生 ID:D:\web\zyt\app\src\doctor_workstation\services\repository.py:398-412 |
EXACT | 保持服务端权威数据范围,没有客户端越权扩大证据。 |
extend |
PageResult 完整保留 extend:D:\web\zyt\app\src\doctor_workstation\core\models.py:590-598,618-672;页面只读 items/total:D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:328-335 |
PARTIAL | scope、summary、dates 已被解析却未消费。 |
| 患者模型 | 覆盖主列表核心字段并保留 raw:D:\web\zyt\app\src\doctor_workstation\core\models.py:241-318 |
EXACT | 解析层可承载主表数据。 |
| 详情 | 选择行后直接 _render_detail(patient):D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:349-415 |
MISSING | 没有诊单详情请求,不能等价于管理端只读/编辑页。 |
| 历史预约 | 从列表行 appointments 取最多 6 条:D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:417-446 |
MISSING | 管理端按诊单单独请求最多 500 条并显示更完整字段。 |
| 患者行动作 | 无编辑/只读/预约/指派/补身份证/二维码/取消挂号按钮 | MISSING | D:\web\zyt\app\src\doctor_workstation\ui\pages\patients.py:1-454 没有动作链。 |
| 订单/进度工作区 | 无 | MISSING | Python 只有列表与右侧摘要。 |
补充:get_value 会在 dataclass 属性缺失时回退到 .raw:D:\web\zyt\app\src\doctor_workstation\ui\widgets.py:48-66。因此右侧摘要“可能”显示列表响应附带的额外字段,但它仍是列表快照,不是独立详情/历史合同。
6. 五模块动态路由、API 与按钮权限复核
6.1 主路由组件与 Python 导航
| 模块 | 管理端实际组件(views 证据) | Python 固定导航权限 | 判定 |
|---|---|---|---|
| 接诊台 | D:\web\zyt\admin\src\views\patient\reception\index.vue:1-18 |
doctor.appointment/lists:D:\web\zyt\app\src\doctor_workstation\ui\shell.py:40-47 |
PARTIAL |
| 处方库 | D:\web\zyt\admin\src\views\consumer\prescription\list.vue:1-4,234-245 |
tcm.prescriptionLibrary/lists:D:\web\zyt\app\src\doctor_workstation\ui\shell.py:48-54 |
PARTIAL |
| 已开处方 | D:\web\zyt\admin\src\views\consumer\prescription\index.vue:1-4,1697-1711 |
tcm.prescription/lists:D:\web\zyt\app\src\doctor_workstation\ui\shell.py:55-61 |
PARTIAL |
| 患者 | D:\web\zyt\admin\src\views\first_visit\my_patients\index.vue:5-20 |
firstvisit.myPatient/lists:D:\web\zyt\app\src\doctor_workstation\ui\shell.py:62-68 |
PARTIAL |
| 问诊/诊单 | D:\web\zyt\admin\src\views\tcm\diagnosis\index.vue:726-738 |
tcm.diagnosis/lists:D:\web\zyt\app\src\doctor_workstation\ui\shell.py:69-75 |
PARTIAL |
这里的 PARTIAL 不是说固定权限码必然错误,而是:在 views/** 证据范围内无法恢复五条真实 mySelf.menu 记录,Python 又没有使用已经解析到的菜单树来做组件/顺序/显示/停用约束。
6.2 mySelf 菜单链
- Python 正确请求
auth.admin/mySelf,解析用户、权限与 menu:D:\web\zyt\app\src\doctor_workstation\services\repository.py:164-195—— EXACT。 Session正确保留menu:D:\web\zyt\app\src\doctor_workstation\core\session.py:12-22—— EXACT。- 正式登录只检查
session.menu非空,然后把权限交给 Shell:D:\web\zyt\app\src\doctor_workstation\app.py:413-443—— PARTIAL。 - Shell 注册页面时遍历固定
NAVIGATION,只调用扁平has_permission,没有读取session.menu:D:\web\zyt\app\src\doctor_workstation\ui\shell.py:248-280—— MISSING(菜单绑定)。
实际影响:菜单中任意一项存在即可通过登录守卫;随后五模块的显示、顺序、名称及停用状态均由本地固定表决定。动态路由中的隐藏/停用/组件路径/排序语义没有落地。
6.3 按钮权限与功能矩阵
接诊台 — PARTIAL
- 管理端备注、编辑病历、完成接诊分别使用
doctor.appointment/addDoctorNote、tcm.diagnosis/edit、doctor.appointment/complete:D:\web\zyt\admin\src\views\patient\reception\index.vue:144-171。 - 管理端队列内“通知医助/发起通话”按钮本身没有
v-perms:D:\web\zyt\admin\src\views\patient\reception\index.vue:75-90。 - Python 备注与完成权限一致,但通知增加了
doctor.appointment/notifyAssistant,视频增加了tcm.diagnosis/videoQr;缺“编辑病历”:D:\web\zyt\app\src\doctor_workstation\ui\pages\reception.py:322-331。 - Python 的通知、备注、完成调用链存在:
D:\web\zyt\app\src\doctor_workstation\ui\pages\reception.py:558-641;管理端对应 API 调用见D:\web\zyt\admin\src\views\patient\reception\index.vue:460-527。
处方库 — PARTIAL,含 P0 权限门禁问题
- 管理端 canonical 按钮码是
wcf.prescription/add|read|edit|delete:D:\web\zyt\admin\src\views\consumer\prescription\list.vue:35-41,83-105。 - 管理端编辑/删除还要求创建人本人,或 root/角色 0、3:
D:\web\zyt\admin\src\views\consumer\prescription\list.vue:278-301。 - Python 新增/编辑/删除同时接受 canonical
wcf.*和额外tcm.prescriptionLibrary/*:D:\web\zyt\app\src\doctor_workstation\ui\pages\prescription_library.py:271-279,330-348。 - Python root/角色 0、3 的例外与管理端一致,但当
user_id或creator_id缺失时直接返回可管理:D:\web\zyt\app\src\doctor_workstation\ui\pages\prescription_library.py:460-472。管理端的Number(row.creator_id) === Number(user.id)不会对缺失创建人默认放行。
已开处方 — PARTIAL/MISSING actions
- 管理端支持业务订单跳转、新增、查看、修正患者、创建订单、编辑、审核、删除,对应权限码见
D:\web\zyt\admin\src\views\consumer\prescription\index.vue:102-117,214-265。 - 业务订单导航依赖动态路由中的
meta.perms === 'tcm.prescriptionOrder/lists',菜单不存在会拒绝跳转并提示:D:\web\zyt\admin\src\views\consumer\prescription\index.vue:1862-1871。 - Python 只实现关键词/审核状态的列表和详情请求:
D:\web\zyt\app\src\doctor_workstation\ui\pages\prescriptions.py:91-188,284-340;上述写操作与订单导航均不存在。
患者 — MISSING actions
完整动作矩阵见第 4.2 节;Python 患者页没有对应按钮或权限检查。
问诊/诊单 — PARTIAL/MISSING actions
- 管理端新增、批量指派、查看、诊单、开方、预约、补身份证、指派/取消指派、视频二维码、确认二维码/取消挂号、挂号日志、订单、删除的权限矩阵见
D:\web\zyt\admin\src\views\tcm\diagnosis\index.vue:154-175,342-405。 - Python 仅保留列表筛选与
tcm.diagnosis/videoQr视频入口:D:\web\zyt\app\src\doctor_workstation\ui\pages\consultations.py:101-107,110-236,264-323。
6.4 权限匹配语义
PermissionSet支持精确码、全局*和prefix/*:D:\web\zyt\app\src\doctor_workstation\core\permissions.py:24-84,OR 语义见D:\web\zyt\app\src\doctor_workstation\core\permissions.py:96-104。- UI
has_permission额外把/与.互换后尝试匹配:D:\web\zyt\app\src\doctor_workstation\ui\widgets.py:135-180。 - 管理端视图只声明 canonical 形态,例如
tcm.diagnosis/edit、wcf.prescription/edit。由于本次不能读取管理端权限工具实现,不能证明管理端也接受点/斜杠变体;Python 的变体放行应判为 PARTIAL,不应当作已证明的兼容要求。
7. 医生角色与数据范围
7.1 已证明一致的部分
- 患者列表:管理端要求按当前角色/部门展示并把最终范围作为
extend.scope.label返回;Python 调用相同业务域列表,不传任意doctor_id,所以没有客户端扩大数据集的证据。判定 EXACT(请求边界)。 - 处方库:普通医生只能管理自己创建的模板,root/角色 0、3 才可管理全部。Python 的正常 ID 分支与此一致。判定 EXACT(正常分支)。
7.2 部分或缺失
- Python 不显示
extend.scope.label,用户无法验证当前是本人、部门还是其他服务端范围:PARTIAL。 - Python 缺失患者订单工作区,因而没有落实“当前患者范围内、订单创建人不参与归属”的口径:MISSING。
- Python 缺失面诊进度工作区,因而没有落实服务端
schedule_mode=ownership、is_self_patient和近七日聚合:MISSING。 - 除患者模块视图明确写出的范围文字外,接诊、处方、诊单视图没有在允许证据范围内定义服务端角色/部门过滤算法。Python 使用同域列表接口且未见额外越权
doctor_id注入,但无法仅凭 views 宣称五模块的后端数据范围 EXACT;应保留 PARTIAL / 服务端待证。
8. P0 / P1 缺口
P0
- 处方库所有权校验 fail-open。
Python 在当前用户 ID 或模板creator_id任一缺失时允许编辑/删除:D:\web\zyt\app\src\doctor_workstation\ui\pages\prescription_library.py:460-466;管理端仅允许严格所有者匹配或 root/角色 0、3:D:\web\zyt\admin\src\views\consumer\prescription\list.vue:286-301。应改成缺失即拒绝,并只在明确 root/角色例外时放行。若服务端另有强制校验,实际数据写入风险会降低,但客户端授权偏差仍已成立。
P1
- 患者模块只有主表子集。 缺日期筛选、三日汇总、范围标签、确认/诊单日期列和全部行级动作。
- 患者详情不是管理端真实详情。 Python 不请求诊单只读/编辑详情,病历摘要与最多 6 条历史预约依赖列表行快照,可能为空或陈旧。
- 患者订单管理与面诊进度两个工作区完全缺失。 同时丢失订单数据范围、订单操作权限矩阵、ownership 模式和 15 秒候诊刷新。
- Shell 忽略
mySelf.menu。 已解析的动态菜单只用于“非空”登录守卫;五模块由固定表和扁平权限决定,未落实组件路径、显示/停用、排序和真实菜单成员关系。 - 处方库权限码存在额外别名放行。 Python 接受
tcm.prescriptionLibrary/add|edit|delete,而视图仅证明 canonicalwcf.prescription/*;应在拿到真实mySelf.permissions/menu样本后收敛。 - 通用权限帮助器接受
//.互换。 该兼容未被 views-only 证据证明,可能让非 canonical grant 意外点亮按钮。 - 接诊台按钮矩阵不一致。 Python 比管理端额外门禁通知/视频,同时缺少
tcm.diagnosis/edit的编辑病历动作。 - 已开处方和问诊页均被压缩为只读子集。 管理端已有的新增/编辑/审核/删除/订单/预约/指派等受权操作未落地。
9. 建议的验收优先级
- 先修复处方库所有权 fail-open,并用“缺
creator_id、缺当前用户 ID、本人、他人、root、角色 0/3”六组合同测试覆盖。 - 让 Shell 以
Session.menu为主约束,再叠加权限码;不要仅以硬编码五项替代动态菜单。 - 将患者页拆成与管理端一致的三个工作区,优先补真实只读详情/历史请求和主表行级动作。
- 消费
PageResult.extend.scope/summary/dates/schedule_mode,把服务端权威范围直接呈现给医生。 - 获取一份真实医生角色
mySelf响应样本后,补做菜单paths/component/perms/is_show/is_disable/sort的精确合同测试;这一步无法由views/**单独完成。