Files

6.0 KiB
Raw Permalink Blame History

T1+T2 · 二诊只读病例页 + 跟踪矩阵

来源:~/.claude/plans/6-7-fluffy-sunset.mdT1 + T2 节) 依赖:T4(已完成 — 提供 PatientInfoCard / PatientCaseCard / blood-thresholds.ts);T3(已完成 — 顺手把 unserved_days 用到 readonly 页头部 badge 范围:单 PR,前端 + 后端 + 菜单 SQL,整合进入 fix-diagnosis-20250505 分支

用户故事

二中心医助每天进诊单列表,双击行或点**「查看」**按钮 → 直达「只读病例页」:

  • 患者基本信息 + 完整病例(PatientInfoCard / PatientCaseCard,与接诊台像素级一致)
  • 跟踪矩阵:日期为列、6 项指标为行(血糖|血压|西药|胰岛素|饮食|运动),同日血糖三段挤一格,超阈值红字 + ↑
  • 顶部 hero:未服务 X 天的红/橙/绿 badge

后端

接口 GET /tcm.diagnosis/readonlyDetail?id=:diagnosisId

ValidateDiagnosisValidate::sceneReadonlyDetailonly(['id']) + id 必填且 checkDiagnosis

Logic DiagnosisLogic::readonlyDetail($params, $adminId, $adminInfo)

  1. 校验诊单存在
  2. 数据权限:医助 (role_id=2) 仅看 assistant_id == selfDataScope 启用时 assistant_id IN visibleAdminIds
  3. 拼装 diagnosis(复用 self::detail + AppointmentLogic::enrichDiagnosisLabels
  4. 取最近一条挂号(用于 PatientInfoCard 显示挂号信息);没有就给个默认空对象
  5. 取最近 30 天的 blood / diet / exercise 三类记录
  6. 取医生备注(DoctorNoteLogic
  7. 计算 unserved_days= today MAX(blood.record_date) 天)
  8. 返回:{ appointment, diagnosis, blood_records, diet_records, exercise_records, doctor_notes, unserved_days, last_blood_record_at }

ControllerDiagnosisController::readonlyDetail() thin wrapper

AppointmentLogic 小改enrichDiagnosisLabelsprivatepublic static,便于 DiagnosisLogic 复用。其它行为不变。

菜单 SQLserver/sql/1.9.20260420/add_diagnosis_readonly_menu.sql —— 注册 tcm.diagnosis/readonlyDetail 权限节点。

前端

路由

/tcm/diagnosis-readonly?id=:diagnosisId — 注册在 admin/src/router/routes.tsconstantRoutes(不依赖菜单驱动),用 LAYOUT 包裹保留侧边栏;meta.hidden=true 不显示在导航;meta.activeMenu='/tcm/diagnosis' 高亮诊单父菜单。

实践教训:admin 的动态路由是后端菜单表驱动的(createRouteRecord 只看 menu API);因此「隐藏路由」不能仅靠不加菜单 SQL 解决,否则路径根本没注册到 vue-router。最稳的做法是写 constantRoutes 静态路由 + 用与父菜单不同的路径前缀(这里用连字符 -readonly 而非 /readonly 子路径,避免和动态菜单同前缀互相影响)。

入口(在 diagnosis/index.vue 上)

  • 操作列最前新增 <el-button link>查看</el-button>router.push({ path: '/tcm/diagnosis/readonly', query: { id: row.id } })
  • <el-table @row-dblclick="goReadonly"> 双击同样跳转
  • 操作列原有按钮加 @click.stop(避免冒泡触发双击)

新建 admin/src/views/tcm/diagnosis/readonly.vue

┌─ Hero:返回 + 患者姓名 + 「未服务 X 天」 badgecolor 同 T3
├─ <PatientInfoCard :apt :diag />
├─ <PatientCaseCard :apt :diag />
├─ <TrackingMatrix>
└─ <NoteTimeline>(可选;先复用 reception 的样式)

新建 admin/src/views/tcm/diagnosis/components/TrackingMatrix.vue

Props

{
  diagnosisId: number
  patientId?: number
  age?: number | null      // 阈值依赖
  bloodRecords:    BloodRecord[]
  dietRecords:     DietRecord[]
  exerciseRecords: ExerciseRecord[]
}

渲染规则:

来源 cell
日期 (fixed left) union(blood/diet/exercise.record_date) desc 去重 MM-DD(周X
血糖 bloodRecords by date 空 6.5 餐 8.9 其他 7.2,缺值 ;任一段超阈值 → 红字 + ↑
血压 bloodRecords by date 135/90;超 → 红 + ↑
西药 bloodRecords.western_medicine tooltip 全文,cell 截断
胰岛素 bloodRecords.insulin 同上
饮食 dietRecords by date 已打卡 / ;点 cell 弹出当日早/午/晚
运动 exerciseRecords by date 已打卡 · 30min · 中强度 / ;点弹详情

时间窗:7 / 30 / 90 / 365 / 自定义(默认 30);切换时由父组件重新拉数据或客户端过滤。本期实现简化:父组件一次拉 30 天,组件本地过滤切换的窗口。

CSS 类:.matrix-table .cell-blood .cell-bp .cell-empty .cell-high .cell-up(红字 + ↑),文字截断/tooltip 走 el-table 的 show-overflow-tooltip

阈值判定:@/utils/blood-thresholds 的 isHigh*T4 提供)。

API

admin/src/api/tcm.ts 新增:

export function diagnosisReadonlyDetail(params: { id: number }) {
    return request.get({ url: '/tcm.diagnosis/readonlyDetail', params })
}

不做(边界)

  • 不做评论 / 编辑 / 删除按钮 — 整页严格只读
  • 不引入 useRouter 之外的全局状态(页面无状态)
  • 不做日期窗口的服务端拉取(窗口切换走前端过滤),后续真有量再做
  • 不做表头排序(矩阵本身是日期 desc,无意义)
  • 不在菜单里展示路由(不配 system_menu type='C'
  • DoctorNote 区块复用 reception 的 NoteTimeline 但不绑 refresh 事件(只读)
  • 跟踪矩阵不展示「备注」字段(跟踪场景看血糖/血压数值即可)

验收

  1. 在诊单列表点「查看」 / 双击行 → 跳到 readonly 页
  2. PatientInfoCard / PatientCaseCard 视觉与接诊台一致(T4 已确保)
  3. 跟踪矩阵:同一天血糖三段挤一格,红色 + ↑ 与 reception 视觉一致
  4. 没有任何打卡时矩阵显示空状态(el-empty
  5. 编辑/删除按钮全程不存在 — 页面纯只读
  6. 越权访问 → 提示无权限,无 500
  7. 时间窗 7/30/90/365 切换有效
  8. 操作列其它按钮(编辑/删除/指派)的点击不会触发跳转(@click.stop