Files
zkxw/docs/superpowers/specs/2026-05-26-checkup-management-page-design.md
T
2026-05-26 12:32:19 +08:00

17 KiB
Raw Blame History

体检管理页面设计

日期:2026-05-26 状态:待审核 需求来源:用户提出新增独立的体检管理界面

1. 背景与目标

现状

  • 体检信息管理目前嵌入在中试服务申请单详情弹窗的 Tab 3 中(ApplicationDetailDialog.vue)
  • biz_pilot_checkup 表按 pilot_application_id 关联,一条申请单对应一条体检记录
  • 后端 API 只有 get_checkup_by_application(按申请单ID查单条),无分页列表接口
  • 运营人员需要进入每个申请单才能操作体检,效率低

目标

新增独立的体检管理页面,以患者为维度管理体检全流程:

  1. 查看所有需要体检的中试服务订单中的患者
  2. 批量选择患者,统一登记体检机构和体检项目
  3. 对单个患者上传体检报告、登记实际项目和价格
  4. 标记已体检、费用结算

关键数据模型变更

当前 biz_pilot_checkup 只有 pilot_application_id,没有 pilot_patient_id。需要增加该字段,使体检记录对应到具体患者。

2. 数据模型变更

2.1 biz_pilot_checkup 新增字段

ALTER TABLE biz_pilot_checkup
ADD COLUMN pilot_patient_id VARCHAR(64) DEFAULT NULL COMMENT '中试患者ID'
AFTER pilot_application_id;

影响范围:

  • backend/app/models/models.py — BizPilotCheckup 类新增 pilot_patient_id 列
  • backend/app/crud/pilot_checkup.py — _row_to_dict_checkup、create_checkup、update_checkup 需处理该字段
  • 前端 API 和表单数据需同步

2.2 兼容性

  • 现有申请单详情弹窗中的体检 Tab 继续工作(查询时 pilot_patient_id 为空则按 pilot_application_id 匹配)
  • 新的体检管理页面同时使用 pilot_application_id 和 pilot_patient_id

3. 后端 API 变更

3.1 新增:分页列表

GET /web/admin/pilot-checkup/list

查询参数:

参数 类型 必填 说明
page int 是 页码
pageSize int 是 每页条数
checkupStatus string 否 体检状态:dtj/ytj
feeStatus string 否 费用状态:wjs/yjs
checkupInstitutionId string 否 体检机构ID
applicationNo string 否 申请单号(模糊搜索)
patientName string 否 患者姓名(模糊搜索)
channelId string 否 渠道ID

返回数据:JOIN biz_pilot_patient + biz_pilot_application + biz_checkup_institution,返回:

{
  "id": "checkup_id",
  "pilotApplicationId": "...",
  "pilotPatientId": "...",
  "applicationNo": "ZSSQ202605260001",
  "channelName": "XX医院",
  "patientName": "张三",
  "sex": "1",
  "age": 45,
  "phoneNo": "138xxxx",
  "checkupInstitutionId": "...",
  "institutionName": "XX体检中心",
  "reserveTime": "2026-05-28 10:00:00",
  "reserveFee": 500.00,
  "actualCheckupTime": "",
  "actualCheckupFee": null,
  "checkupStatus": "dtj",
  "feeStatus": "wjs",
  "reportAttachments": "",
  "checkupRemark": "",
  "createTime": "2026-05-26 14:00:00"
}

3.2 新增:未登记体检的患者列表

用于体检管理页面的"待登记"患者数据源——查询所有 need_checkup=1 但尚未创建 biz_pilot_checkup 记录的患者:

GET /web/admin/pilot-checkup/pending-patients

查询参数:

参数 类型 必填 说明
page int 是 页码
pageSize int 是 每页条数
applicationNo string 否 申请单号(模糊搜索)
patientName string 否 患者姓名(模糊搜索)
channelId string 否 渠道ID

返回数据:JOIN biz_pilot_patient + biz_pilot_application,返回患者信息 + 申请单信息。

3.3 新增:批量创建体检记录

POST /web/admin/pilot-checkup/batch-save

请求体:

{
  "patientIds": ["patient_id_1", "patient_id_2"],
  "checkupInstitutionId": "institution_id",
  "reserveTime": "2026-05-28 10:00:00",
  "reserveItems": [
    { "projectName": "血常规", "projectFee": 50.00 },
    { "projectName": "肝功能", "projectFee": 120.00 }
  ]
}

为每个 patientId 创建独立的 biz_pilot_checkup 记录 + biz_pilot_checkup_reserve_item 记录。

3.4 现有 API 适配

  • CheckupSaveBody 新增可选字段 patient_id
  • create_checkup 同时写入 pilot_patient_id
  • _row_to_dict_checkup 返回 pilotPatientId 字段
  • get_checkup_by_application 保持不变(向后兼容)

4. 前端设计

4.1 路由

在 sqxp.ts 的 PilotService 路由组下新增:

{
  id: '80000000000000000000000000000011',
  name: 'CheckupManagement',
  path: 'checkup-management',
  component: '/pilot-service/checkup-management/index',
  meta: {
    title: '体检管理',
    keepAlive: true,
    menuCode: 'pilot-service:checkup-management:list'
  }
}

菜单位置:「中试服务管理」→「体检管理」,排在「体检机构及项目价格」之后。

4.2 文件结构

web-admin/src/views/pilot-service/checkup-management/
├── index.vue                       # 主列表页
├── CheckupRegisterDialog.vue       # 登记体检弹窗(批量选择患者 + 填写机构/项目/时间)
├── CheckupDetailDialog.vue         # 详情弹窗(查看信息 + 上传报告 + 登记实际项目)

4.3 列表页 (index.vue)

模板参考:channel-service-order/index.vue(复杂列表模式 P07)

页面布局:

┌──────────────────────────────────────────────────┐
│ 体检管理                                         │
├──────────────────────────────────────────────────┤
│ 筛选条件:                                        │
│ [体检状态▼] [费用状态▼] [体检机构▼]               │
│ [申请单号____] [患者姓名____] [渠道____]          │
│                              [查询] [重置]        │
├──────────────────────────────────────────────────┤
│ 已选 0 项  [登记体检]                             │
│ ─────────────────────────────────────────────────  │
│ ☐ | 申请单号 | 患者 | 性别 | 年龄 | 渠道 |        │
│   | 体检机构 | 预约时间 | 体检状态 | 费用状态 |     │
│   | 实际费用 | 操作                               │
│                                                   │
│ [分页]                                            │
└──────────────────────────────────────────────────┘

表格列定义:

列 字段 宽度 说明
☐选择 — 55 type="selection",仅 checkupStatus=dtj 的行可勾选
申请单号 applicationNo 160
患者姓名 patientName 100
性别 sex 60 el-tag:男/女
年龄 age 60
渠道名称 channelName 140
体检机构 institutionName 160 未登记时显示 "—"
预约时间 reserveTime 160
体检状态 checkupStatus 100 el-tag:待体检(warning)/已体检(success)
费用状态 feeStatus 100 el-tag:未结算(info)/已结算(success)
实际费用 actualCheckupFee 100 formatMoneyDisplay
操作 — 260 fixed="right":查看、编辑项目、标记已体检、费用结算

操作按钮逻辑:

  • 查看:所有状态可用 → 打开 CheckupDetailDialog(只读模式)
  • 编辑项目:checkupStatus=dtj 可用 → 打开 CheckupDetailDialog(编辑模式)
  • 标记已体检:checkupStatus=dtj 可用 → 调用 markPilotCheckupChecked
  • 费用结算:checkupStatus=ytj 且 feeStatus=wjs 可用 → 调用 settlePilotCheckupFee
  • 登记体检(工具栏):选中 1+ 条 checkupStatus=dtj 的记录 → 打开 CheckupRegisterDialog

双数据源:

列表页有两组数据:

  1. 已登记的患者(有 biz_pilot_checkup 记录):通过 /pilot-checkup/list 获取
  2. 未登记的患者(need_checkup=1 但无体检记录):通过 /pilot-checkup/pending-patients 获取

使用 Tab 切换:「全部」「待登记」「已登记」「待体检」「已体检」。

或者简化为:默认只展示已登记的(有 biz_pilot_checkup 记录),通过体检状态筛选。待登记的患者通过独立的"登记体检"按钮触发,弹窗中搜索并选择。

最终选择简化方案:列表只展示已有体检记录的患者。工具栏"登记体检"按钮打开弹窗,弹窗内搜索并勾选未登记的患者。

4.4 登记体检弹窗 (CheckupRegisterDialog.vue)

交互流程:

┌─ 登记体检 ─────────────────────────────────────┐
│                                                  │
│ 搜索患者:[申请单号/患者姓名________] [搜索]      │
│                                                  │
│ ☐ | 申请单号 | 患者姓名 | 性别 | 年龄 | 渠道 |    │
│ ☑ | ZSSQ...001 | 张三 | 男 | 45 | XX医院 |       │
│ ☑ | ZSSQ...001 | 李四 | 女 | 52 | XX医院 |       │
│ ☐ | ZSSQ...002 | 王五 | 男 | 38 | YY医院 |       │
│                                                  │
│ 已选 2 名患者                                     │
│                                                  │
│ ── 体检信息 ─────────────────────────────────── │
│                                                  │
│ 体检机构:  [选择体检机构           ▼]            │
│ 预约时间:  [2026-05-28 10:00     🕐]            │
│                                                  │
│ ── 体检项目 ─────────────────────────────────── │
│ [从套餐添加] [添加单项]                           │
│                                                  │
│ | 项目名称 | 项目费用 | 操作 |                    │
│ | 血常规   | ¥50.00  | [删除] |                  │
│ | 肝功能   | ¥120.00 | [删除] |                  │
│                                                  │
│ 预约费用合计:¥170.00                             │
│                                                  │
│                          [取消] [确定]            │
└──────────────────────────────────────────────────┘

关键组件交互:

  1. 搜索患者:调用 /pilot-checkup/pending-patients 接口,搜索 need_checkup=1 且无体检记录的患者
  2. 选择体检机构:下拉选择,数据来自 fetchCheckupInstitutionList(现有 API)
  3. 从套餐添加:选择机构后,展示该机构的套餐列表,选择套餐后自动填充项目
  4. 添加单项:展示该机构的所有项目列表,多选后添加
  5. 保存:调用 /pilot-checkup/batch-save,为每个选中的患者创建体检记录 + 预约项目

项目选择器(复用 CheckupPackagePanel.vue 的模式):

  • Tab 1:套餐选择 — 按机构筛选套餐 → 选择套餐 → 自动填充项目
  • Tab 2:单项添加 — 按机构筛选项目 → 多选添加

4.5 详情弹窗 (CheckupDetailDialog.vue)

模板参考:ApplicationDetailDialog.vue 的体检 Tab 部分

布局:

┌─ 体检详情 ─ 张三 ──────────────────────────────┐
│                                                  │
│ ── 基本信息 ──────────────────────────────────  │
│ 申请单号:ZSSQ202605260001                       │
│ 患者姓名:张三     性别:男     年龄:45          │
│ 渠道名称:XX医院                                  │
│ 体检机构:XX体检中心                              │
│ 预约时间:2026-05-28 10:00                       │
│ 体检状态:[待体检]   费用状态:[未结算]           │
│                                                  │
│ ── 预约项目 ──────────────────────────────────  │
│ | 项目名称 | 项目费用 |                          │
│ | 血常规   | ¥50.00  |                           │
│ | 肝功能   | ¥120.00 |                           │
│ 合计:¥170.00                                    │
│                                                  │
│ ── 实际项目 ───────────────── [添加单项] ────── │
│ | 项目名称 | 项目费用 | 操作 |                    │
│ | 血常规   | ¥50.00  | [删除] |                  │
│ | 肝功能   | ¥120.00 | [删除] |                  │
│ | 胸部CT   | ¥300.00 | [删除] |                  │
│ 合计:¥470.00                                    │
│                                                  │
│ ── 体检报告 ──────────────────────────────────  │
│ [上传附件] [报告1.pdf] [报告2.jpg]               │
│                                                  │
│ ── 备注 ──────────────────────────────────────  │
│ 患者体检一切正常...                               │
│                                                  │
│          [标记已体检] [费用结算] [取消]            │
└──────────────────────────────────────────────────┘

交互:

  • 打开时加载 fetchPilotCheckupDetail + fetchPilotCheckupReserveItems + fetchPilotCheckupActualItems
  • 实际项目可增删,调用现有 savePilotCheckupActualItems / deletePilotCheckupActualItem
  • 附件上传使用卡片化上传模式(参考 ProtocolFormDialog.vue),调用 fetchUploadAttachment
  • 标记已体检 / 费用结算复用现有 API

4.6 前端 API 新增

// web-admin/src/api/admin.ts

// 体检记录分页列表
export const fetchPilotCheckupList = (params: any) =>
  request.get({ url: '/web/admin/pilot-checkup/list', params })

// 待登记体检的患者列表
export const fetchPilotCheckupPendingPatients = (params: any) =>
  request.get({ url: '/web/admin/pilot-checkup/pending-patients', params })

// 批量创建体检记录
export const batchSavePilotCheckup = (data: any) =>
  request.post({ url: '/web/admin/pilot-checkup/batch-save', params: data })

5. 改动范围总结

后端

文件 改动
app/models/models.py BizPilotCheckup 新增 pilot_patient_id 字段
app/crud/pilot_checkup.py _row_to_dict_checkup 返回新字段;新增 list_checkups_paginated、list_pending_patients、batch_create_checkups 函数
app/api/pilot_checkup.py 新增 3 个 API 端点;CheckupSaveBody 新增 patient_id 字段
docs/sql/ 新增迁移 SQL

前端

文件 改动
src/views/pilot-service/checkup-management/index.vue 新建 — 主列表页
src/views/pilot-service/checkup-management/CheckupRegisterDialog.vue 新建 — 登记体检弹窗
src/views/pilot-service/checkup-management/CheckupDetailDialog.vue 新建 — 详情弹窗
src/api/admin.ts 新增 3 个 API 函数
src/router/modules/sqxp.ts 新增路由

复用的现有代码

  • status-tag-registry.ts — checkupStatusLabel / checkupStatusTagType(现有)
  • fetchCheckupInstitutionList — 体检机构列表 API(现有)
  • fetchCheckupInstitutionProjects — 机构项目列表 API(现有)
  • fetchCheckupInstitutionPackages — 机构套餐列表 API(现有)
  • fetchUploadAttachment / fetchAttachmentDownloadUrl — 附件上传下载(现有)
  • savePilotCheckupReserveItems / savePilotCheckupActualItems — 预约/实际项目保存(现有)
  • markPilotCheckupChecked / settlePilotCheckupFee — 状态变更(现有)
  • formatMoneyDisplay / AmountInput — 金额显示/输入(现有)

6. 不做的事

  • 不修改现有申请单详情弹窗中的体检 Tab(保持向后兼容)
  • 不新增数据库表(复用现有 3 张体检表 + 增加一个字段)
  • 不做体检结果录入(如检查指标、异常标记等)——仅管理项目、费用和报告附件
  • 不做自动费用计算——实际费用由运营手动录入