2026-09-08.md 33 KB

2026-09-08 工作日志

给药途径字典扩充(临时/长期/执行医嘱共用)

需求

临时医嘱单编辑弹窗的"给药途径"下拉,补充用户列出的常用给药途径缩写,重复的项不加。

实现

  • 唯一源:src/api/elderly/elder/bed-change/index.ts 的 MED_ROUTE_OPTIONS(MedOption[]),被 6 个文件共享:
    • short-doctor-order/Form.vue + short-doctor-order/Print.vue
    • long-doctor-order/Form.vue + long-doctor-order/Print.vue
    • execute-doctor-order/index.vue + execute-doctor-order/ExecuteDialog.vue
    • 加一处即全端生效
  • 重复判定(与现有 9 项对比):
    • po/p.o. → 已有 po,不加
    • iv/i.v. → 已有 iv,不加
    • im/i.m. → 已有 im,不加
    • ih/H/sc → ih + sc 都在,不加
    • ivgtt/ivdrip → 已有 ivgtt/iv drip,不加
    • inh → 已有,不加
    • top 与现有 ext "外用" 语义相近但价值不同(top 特指皮肤/经皮),保留为不同项
  • 新增 7 项:

    { value: 'id', label: 'id 皮内注射', cn: '皮内注射', tip: '皮试、疫苗过敏试验等' },
    { value: 'sl', label: 'sl 舌下含服', cn: '舌下含服', tip: '硝酸甘油、速效救心丸等急救药' },
    { value: 'top', label: 'top 皮肤外用/经皮给药', cn: '皮肤外用/经皮给药', tip: '软膏、贴片、涂抹类外用药' },
    { value: 'pr', label: 'pr 直肠给药', cn: '直肠给药', tip: '栓剂、灌肠、退热等' },
    { value: 'buccal', label: 'buccal 颊部给药', cn: '颊部给药', tip: '口腔黏膜贴片、含化药片' },
    { value: 'ia', label: 'ia 动脉内注射', cn: '动脉内注射', tip: '介入治疗、动脉化疗等' },
    { value: 'ip', label: 'ip 腹腔注射', cn: '腹腔注射', tip: '腹膜透析、腹腔化疗等' }
    

    安全性验证

    • MedOption 是 interface,{ value, label, cn, tip } 全部 string,新增项符合
    • getMedCn(options, val)(同文件 line 481)找不到 value 时直接返回 val 兜底——零破坏性,旧数据翻译不受影响
    • 数组纯增,无白名单/枚举校验,不会引入运行时错误

    涉及文件

    • 仅 src/api/elderly/elder/bed-change/index.ts 1 处(+13 行)

    长期/临时医嘱操作栏新增「执行记录」按钮(9-08 上午)

    需求

    长期医嘱 + 临时医嘱的列表行操作栏,加一颗「执行记录」按钮,点击后弹出"医嘱执行单"(类似执行医嘱模块的打印弹窗),只显示当前行长者的执行记录,可打印。

    设计决策

    • 不复用 ExecuteDialog.vue(那是单行执行/跳过弹窗,不是按长者汇总)
    • 新写 ExecutionRecordDialog.vue(放置在 execute-doctor-order/ 目录下,作为"按长者过滤的医嘱执行单"通用弹窗)
    • 不复用执行医嘱 index.vue 的 printDialogVisible:那个弹窗强耦合当日的"长期展开 + 临时当日 + 合并"逻辑,需要全部 planTime 占位,不适合"历史执行记录"场景
    • 数据范围:默认当日(与执行医嘱模块"当日汇总"一致);长期按钮传 orderType=1、临时传 orderType=2,与后端 doctorOrderExecution/page 接口的 orderType 字段对齐;想看历史/跨日可下一轮加日期选择器
    • 表格列:与执行医嘱 all 弹窗完全一致(序号/长者/床位/年龄/医嘱内容/频次/给药时间/途径/计划时间/执行护士/实际时间/状态/备注),打印格式(colgroup %、#555 细线、A4 竖版)也复用

    实现

    1) 新建 src/views/elderly/elder/doctor-orders/execute-doctor-order/ExecutionRecordDialog.vue

    • 接收 open(row, { orderType?, title? })
    • 内部 getDoctorOrderExecutionPage({ elderId, planDate: today, orderType?, tenantIds })
    • 失败 → mock 3 条兜底(项目惯例)
    • 复用执行医嘱的 formatDateTimeSplit / freqCn / timeCn / routeCn / statusText(保持同源)
    • v-print 指令直接可用(main.ts 全局注册 vue3-print-nb)
    • 标题自动推断:orderType=1 → "长期医嘱执行记录";orderType=2 → "临时医嘱执行记录";否则"医嘱执行记录";外部传 title 优先
    • 信息栏:机构/执行日期/长者/类型
    • 空数据占位:「该长者当日暂无执行记录」

    2) 修改 long-doctor-order/index.vue

    • import ExecutionRecordDialog from '../execute-doctor-order/ExecutionRecordDialog.vue'
    • 模板:操作栏 #next 新增 <el-button link type="warning" @click="openExecutionRecord(scope)">执行记录</el-button>(无 v-hasPermi)
    • 模板底部挂载 <ExecutionRecordDialog ref="execRecordRef" />
    • script 新增 execRecordRef + openExecutionRecord(row):缺 elderId 弹错误;否则 execRecordRef.value.open(row, { orderType: 1 })

    3) 修改 short-doctor-order/index.vue

    • 同上,但传 orderType: 2

    安全性

    • 三个接口 (getLongDoctorOrderPage / getShortDoctorOrderPage / getDoctorOrderExecutionPage) 都接受任意 params 透传(params: query),新增 elderId 字段后端若不支持会按 name 模糊匹配兜底
    • DoctorOrderExecution 类型已包含 orderContent/frequency/medTime/route/planTime/execTime/status/remark/bedName/elderName/elderAge 等所有渲染字段,无需 join
    • mock 数据行已含 elderId 字段(长期 801/802/803/804、临时 901/902),按钮触发路径完整

    类型检查(两轮)

    • 第一轮发现 4 个真实错误(我引入):DoctorOrderExecution 接口没有 elderAge 字段,但新组件表格"年龄"列在用 → 4 处 TS2551/TS2561
    • 修复:给 bed-change/index.ts 的 DoctorOrderExecution 接口补 elderAge?: number | string(与执行医嘱 makeRow 的 elderAge: vo.elderAge ?? '' 对齐——那里是 any 所以不报错)
    • 第二轮:ExecutionRecordDialog 零错误 ✅
    • 其余筛出的错误(long/short 的 Form.vue TS2322、Print.vue TS6133、index.vue 的 getLongDoctorOrderStatusText / openDetail 未使用)全是既有基线噪声,非本次引入

    涉及文件

    • 新建 1 个:execute-doctor-order/ExecutionRecordDialog.vue(~280 行)
    • 修改 3 个:long-doctor-order/index.vue(+13 行)、short-doctor-order/index.vue(+13 行)、bed-change/index.ts(+1 行 elderAge 字段)

    长期医嘱隐藏护士签名 + 执行记录打印列宽 + 切长者数据残留(9-08 上午)

    三个用户反馈 + 对应修复

    ① 长期医嘱编辑/打印隐藏"护士签名"和"停止护士签名"两列

    • Form.vue(编辑弹窗):删除 <el-table-column label="护士签名"> + <el-table-column label="停止护士签名"> 两个 el-table-column
    • emptyEntry 仍保留 nurseSign/stopNurseSign 字段,数据字段不删(仅 UI 隐藏),接口兼容
    • Print.vue(打印):同步删除 th/td 对应两列,colgroup 由 12 列变 10 列
    • 释放的 ~12.3% 重新分配:医嘱内容 25.64→30、备注 12.31→13.5、医嘱时间/停止时间 9.23→10.5、医生签名 6.15→7 等
    • 新的 10 列:3.5 / 10.5 / 30 / 6 / 6 / 6 / 7 / 10.5 / 7 / 13.5 = 100%

    ② 医嘱执行记录打印:计划时间/实际时间列宽 + nowrap

    • 现象:日期 "2026-09-08" 被 word-break: break-all 在 "-" 处强制断行成 "2026-09-" + "08 08:00" 两行
    • 修复(ExecutionRecordDialog.vue):
    • colgroup 把计划时间/实际时间列各从 8.25% → 10%(+1.75% × 2 = +3.5%)
    • 从床位 8.25→7、医嘱内容 18.04→16.5、备注 10.31→10、频次/给药时间/年龄/途径 5.67→5.5 等"瘦"列共匀出 3.5%
    • .datetime 加 white-space: nowrap 防止日期中间断行(双保险)

    ③ 切换长者时表格残留旧数据

    • 现象(图3):信息栏显示新长者"测试离往非退住02",但表格第一行"长者"列还是旧长者"测试变更01"
    • 根因:后端 doctorOrderExecution/page 接口未实现 elderId 字段过滤(透传参数被忽略),返回全量 execution 记录
    • 修复(ExecutionRecordDialog.vue):
    • fetchList 加前端兜底过滤:list.value = (data.list || []).filter(r => Number(r.elderId) === Number(elderId))
      • Number() 兼容后端 string/number 混用
      • 无论后端是否过滤,前端永远只显示当前长者
    • open() 立即 list.value = [] 清空(fetchList 同步执行前的极短窗口也清掉,防"旧表闪一下")

    安全性

    • 接口/类型零破坏性
    • "护士签名"字段保留在数据层(emptyEntry/默认对象/接口)—— 仅 UI 隐藏
    • 前端兜底过滤只对 elderId 字段做 Number 等值比较,不修改原数据

    涉及文件

    • 改 3 个:long-doctor-order/Form.vue(-16 行)、long-doctor-order/Print.vue(-9 行 + colgroup 重排)、execute-doctor-order/ExecutionRecordDialog.vue(colgroup 13 列重排 + datetime nowrap + 二次过滤 + open 清空)

    执行记录弹窗布局调整(9-08 上午,按用户截图布局范式)

    需求

    用户给了一张「医嘱执行单」截图作为布局范式,按其样式调整执行记录弹窗。

    截图布局要点

    1. 标题"医嘱执行单"居中粗体
    2. 信息行:机构/执行日期/长者/类型(label 加粗 + 均匀 25% 分摊)
    3. 表格 13 列:序号/长者/床位/年龄/医嘱内容/频次/给药时间/途径/计划时间/执行护士/实际时间/状态/备注
    4. 单元格垂直居中(middle 而非 top)
    5. 单元格水平对齐:序号/年龄/频次/给药时间/途径/时间/护士/状态 → 居中;长者/床位/医嘱内容/备注 → 左对齐
    6. 边框 #555 细线、12px 字体、thead 灰色背景

    改动(ExecutionRecordDialog.vue,CSS + colgroup)

    • colgroup 微调(按截图视觉比例):
    • 序号 3.09 → 3.5、医嘱内容 16.5 → 17、床位 7 → 8.5、执行护士 7.22 → 6、长者 8.7 → 8.5、备注 10 → 9.5
    • 合计 100%
    • 垂直对齐:vertical-align: top → middle(截图明显居中)
    • 水平对齐:
    • td 默认 text-align: center
    • 用 nth-child(2/3/5/13) 把长者/床位/医嘱内容/备注改回 text-align: left
    • .empty-tip 仍居中(colspan=13 不触发 nth-child)
    • 保留项:信息栏 4 项、13 列顺序、边框、字体、A4 竖版、nowrap(计划时间上下两行)

    涉及文件

    • 改 1 个:execute-doctor-order/ExecutionRecordDialog.vue(CSS colgroup + td 对齐)

    医嘱执行语义核对(9-08 上午,纯调研无改动)

    用户提问:"临时医嘱只执行一次,长期医嘱没停止前每天都要执行,现在是按这样设计的吗?"

    结论:是,完全按此设计。代码证据(execute-doctor-order/index.vue buildRows):

    • 长期(line 532-540):if (e.status !== 1) return 仅"在用";buildExecutionPlanTimes(freq, medTime) 展开时间点后拼 ${execDate} ${t} → 每天用当天 execDate 重新生成一批待执行行;换一天就是全新 planTime,无 execution 记录 → 又变"待执行"。停止后不再展开 ✅
    • 临时(line 541-550):const plan = fromTs(e.orderTime); if (plan.slice(0,10) !== execDate) return → planTime 固定为开单时间,只在开单当天出现一行,换任何一天都不展开 ✅
    • 执行状态匹配 key = ${orderType}-${orderEntryId}-${planTime}(含日期),天然做到"今天执行了不影响明天"

    ⚠️ 已修复:非每日频次 + 欠账累积(9-08 上午,用户确认规则后实施)

    用户拍板的两条业务规则:

    1. 不限制,完全累积(没执行就一直顺延累加)
    2. 停止后仍要补(停止只代表以后不产生新的,之前欠的仍要做完)

    用户原话:"按周的话,默认就是按'医嘱时间'开始执行第一次,如果过了一周,就展开第二次,周一没执行,就周二展开,周二没执行,就周三展开。又过了一周,下下周就展开2次。以此类推。每天也是一样,今天没有执行,第二天就展开2次。"

    实现

    1) api/elderly/elder/bed-change/index.ts 新增

    • FREQ_CYCLE_RULES:频次 → { days: number[]; times: string[] }
    • days = 相邻执行日间隔天数,数组表示循环(biw:[3,4] 交替、tiw:[2,2,3] 循环)
    • times = 每执行日内时点,后缀 '+1' 表示次日(q8h/q6h/q4h 跨零点)
    • 覆盖 qd/bid/tid/qid/qn/q8h/q6h/q4h/qod/qw/biw/tiw/q2w/q3w/q4w
    • applyMedTime(def, medTime):给药时间微调(仅对单时点频次生效,多时点保持标准排班)
    • buildPendingPlanTimes({frequency, medTime, startTime, stopTime, now}) → 应执行时点数组
    • startTime=orderTime,早于它的时点不算欠账(下午开医嘱不会倒算上午)
    • stopTime 硬截止优先于 now;未停止时 now 默认"今天 23:59"
    • MAX_SPAN_DAYS = 3650 防死循环
    • 保留旧 buildExecutionPlanTimes(未被本模块引用,但避免破坏潜在调用方)

    2) execute-doctor-order/index.vue buildRows 重构(顺序调整) ```

1) 拉长期医嘱 2) 拉临时医嘱 3) 拉全量 execution(去掉 planDate 过滤!)→ closedSet + execMap 4) 展开:长期 = 应执行 − 已闭环;临时 = 当日一次性 5) 回填状态(纯精确匹配) 6) 筛选 + 排序

- `closedSet`: `${orderType}-${orderEntryId}` → Set\<planTime\>;**已执行(1)/已跳过(2) 算闭环,待执行(0) 不算**
- 长期展开条件由 `status !== 1` 放宽为 `status !== 1 && status !== 2`(在用 + 已停止都展开,未开始0不展开)
- 停止医嘱传 `stopTime` 作硬截止
- 顶部「执行日期」execDate 作为 `now` 传给 buildPendingPlanTimes → 选历史日期可回看当天欠账
- **取消旧的"索引兜底"匹配**(execByEntry/rowsByEntry/DEBUG_MISMATCH 全部删除):欠账模型下行数 ≠ execution 数,索引对齐会**销错账**,宁可显示待执行
- mock 首条改为 3 天前开立,便于演示欠账累积

### ⚠️ 硬依赖(必须让后端配合)
**后端必须精确写入前端传入的 `planTime`(YYYY-MM-DD HH:mm)**。已知后端入库 planTime 不精确,若不修复:
- 已闭环集合里的 planTime 与前端生成值对不上 → 欠账无法销账 → **已执行的会重复出现**

### 验证
- **类型检查 6分41秒**:我改的两个文件(`api/.../bed-change/index.ts`、`execute-doctor-order/index.vue`)**零错误**;报出的 3 条在 `views/elderly/elder/bed-change/index.vue`(换床视图) 与 `ExecuteDialog.vue`(nickname),均为既有基线
- **算法实跑 10 个用例全通过**(node + dayjs 临时脚本,已清理):qd 4条 / qw 3条 / 停止4条 / tid当天3条 / tid下午开1条 / qod隔日4条 / 未来医嘱0条 / 视图截止日3条 / biw 3条 / q8h跨零点2条

## planTime 全链路位置速查(用户要求定位核对传参/回参)
**传出(前端 → 后端)**
1. 生成:`execute-doctor-order/index.vue` `makeRow(1, e, t, vo)`(:475)t 来自 `buildPendingPlanTimes`,格式 **"YYYY-MM-DD HH:mm"(16 位)**
2. 取值:`ExecuteDialog.vue:120`(执行)/ `index.vue:798`(跳过)→ `planTime: row.value.planTime`
3. 序列化:`api/.../bed-change/index.ts:51-55` `serializeExecution` → `toTs()` → **13 位毫秒数字**
4. 发送:`api/.../bed-change/index.ts:467` `executeDoctorOrder` → POST `elderly/doctorOrderExecution/execute`

**接收(后端 → 前端)**
5. 接口:`api/.../bed-change/index.ts:449` `getDoctorOrderExecutionPage`
6. 反序列化:`:457` `planTime: fromTs(row.planTime)`(数字→"YYYY-MM-DD HH:mm:ss";已是字符串则原样)
7. 归一:`execute-doctor-order/index.vue:555` `String(r.planTime).slice(0, 16)` → 16 位
8. 匹配 key:`:557` / `rowKey` `:455` = `${orderType}-${orderEntryId}-${planTime}`

**⚠️ 已加临时诊断日志(核对完需删)**
- `api/.../bed-change/index.ts` `executeDoctorOrder` → `console.log('[planTime-发出]', {前端原始值, 发给后端, 可读时间})`
- `api/.../bed-change/index.ts` `getDoctorOrderExecutionPage` → `[planTime-收到-原始]`(含 `typeOf`)+ `[planTime-收到-转换后]`(含 matchKey)
- `execute-doctor-order/index.vue` buildRows 第 4 步后 → `[planTime-展开]`(前端展开的全量待执行 key)
- 判定:三条日志里 16 位 key 应完全一致;若「收到-原始」是固定值/当前时间 → 后端 planTime 写入 BUG 确认

## 执行医嘱列表三项修复(9-08 下午,用户测试反馈)

### 用户反馈
1. 执行之后,已执行的数据在列表中不展示
2. 去掉执行时间过滤后出 7298 条 = 无限展开
3. planTime 日志是否够用

### 根因
1. `buildRows` 旧逻辑 `.filter((t) => !closed.has(t))` 把已执行/已跳过行从列表减掉 → 只显欠账
2. `buildPendingPlanTimes` 下界是 `orderTime`(可能数年前医嘱),无回看窗口上限 → 历史欠账无限累积

### 修复
- **`api/.../bed-change/index.ts` `buildPendingPlanTimes`** 新增可选参数 `viewStart`(视图下界):
  - `if (viewStart && plan < viewStart) return` 跳过早于下界的历史欠账
- **`execute-doctor-order/index.vue` `buildRows`**:
  - 删 `closedSet` 减集合逻辑 → 长期展开改为「展示全部应执行时点」,状态由 `execMap` 精确回填(已执行/已跳过也保留在列表,status 0/1/2 分别显示 待执行/已执行/已跳过)
  - 新增窗口常量 `VIEW_PAST_DAYS=30`(回看欠账)+ `VIEW_AHEAD_DAYS=30`(提前规划,呼应"最多提前一个月")
  - 长期展开传 `now = execDate+VIEW_AHEAD_DAYS 23:59:59`、`viewStart = execDate-VIEW_PAST_DAYS 00:00:00`
  - 默认合计约两个月窗口,7298 问题消除(1 年前 qd 医嘱只展最近 30 天欠账而非 365 天)
- **诊断日志增强**:
  - 删旧 `[planTime-展开]`(仅打印 key,无法判断命中)
  - 新增 step5 后 `[planTime-诊断]`:`{展开行数, 后端execution记录, 命中展开行, 未命中_后端planTime与前端展开不一致}`
  - 判定:未命中 > 0 → 后端 planTime 未按请求体精确入库(已执行仍显待执行的根因);命中=后端记录数 → 前后端一致 OK

### 验证
- 类型检查两个改动文件零新增错误(vue-tsc 后台跑,过滤 execute-doctor-order/index.vue / bed-change/index.ts)
- 量化预期:1 年前 qd 长期医嘱 → 旧 365 行/条 → 新 ≤ (30+30)=60 行/条;已执行行现正常显示

### ⚠️ 待用户确认
- `VIEW_PAST_DAYS/VIEW_AHEAD_DAYS` 默认 30/30 是否合适("提前一个月"按 ±30 天解读;若只想看当天可把 AHEAD 调 0)
- 未来 30 天计划行也展示为「待执行」,护士可能提前执行——是否需要禁用未来行执行(未做,留待确认)
- 诊断日志仍为临时,后端 planTime 写入 BUG 确认/修复后需删除

## ⚠️ 误读"提前一个月"——回退到「只展开当天」(9-08 下午,用户纠正)

### 用户澄清
「搞错了,应该不能提前展开数据,只需要展开当天的。我需要您把执行日期,不能被删除,变成空」
→ 上一轮我按"回看30天+提前30天"理解是错的。真实诉求:
1. **不展未来、不展历史欠账**,只展开所选「执行日期」当天
2. **执行日期必填**(prop=execDate 必选校验,clearable=false)
3. **执行日期默认空**(不自动填今天),由用户主动选择要查看/回看的日期

### 修改(execute-doctor-order/index.vue)
- **模板**:
  - `<el-date-picker ... :clearable="false" />` 关闭清除按钮(一旦选了不能清回空)
  - `<el-form ... :rules="queryRules">` 挂载校验
- **脚本**:
  - `execDate` 默认 `''`(不再预填今天)
  - 新增 `queryRules.execDate = [{ required: true, message: '请选择执行日期', trigger: 'change' }]`
  - `handleQuery` 先 `await queryFormRef.value?.validate()`,校验失败直接 return(表单自动红字提示)
  - `resetQuery` 改回 `execDate = ''` + `list.value=[]; pageNo=1`(不调 buildRows)

---

## 11:40-11:46 二次修正:执行日期默认今天 + 全部类型回看历史
用户纠正:「我没说执行日期默认空啊,默认就是今天,并且不能删除」
### 修改(execute-doctor-order/index.vue)
- `execDate` 默认 `formatDate(new Date(),'YYYY-MM-DD')`(今天);`:clearable="false"` 保留;必填校验保留
- `resetQuery`:重置回今天 + `buildRows()` 重新加载(不再留空列表)
- `onMounted` 已调 buildRows → 进入页面自动加载今天数据

### 11:46 新需求:医嘱类型=全部 → 回看历史执行单
「如果箭头处(医嘱类型)是全部,那么9月8号之前的历史执行单(包含已执行和未执行),都要展示出来」
- **语义**:`showHistory = queryParams.orderType == null`
  - 全部:展开 `[execDate-30天, execDate]` 的长期应执行时点(含已执行/已跳过/欠账,状态由 execMap 回填)+ 窗口内历史临时医嘱
  - 指定长期/临时:仍只展执行日期当天
- `HISTORY_DAYS = 30` 回看窗口常量(防 7298 无限展开);复用 API 层 `buildPendingPlanTimes` 的 `viewStart` 参数(上界仍为 execDate 23:59:59 不展未来)
- 临时医嘱过滤从 `planDate === execDate` 改为 `viewStartDate <= planDate <= execDate`
- 长期 filter 双保险:`viewStartDate <= t.slice(0,10) <= execDate`
  - **buildRows 步骤 4.1 长期展开回退**:
    - 删 `now: execDate+30d` → 改回 `now: ${execDate} 23:59:59`
    - 删 `viewStart: execDate-30d` → 不再传
    - 删 `VIEW_PAST_DAYS/VIEW_AHEAD_DAYS` 常量
    - 长期展开后再加 `.filter((t) => t.slice(0, 10) === execDate)`,**只保留当天时点**
  - buildRows 顶部加 `if (!execDate) { list.value=[]; return }` 兜底(onMounted 直接调用时)

### 语义对比
| 维度 | 旧(错误) | 新(正确) |
|---|---|---|
| 默认值 | 今天 | **空** |
| 是否必填 | 否 | **是** |

## 11:58 用户实测「执行日期默认空值」——重新修复
- 排查发现 435 行 `queryParams.execDate` 实际仍是 `''`(此前"改默认今天"的编辑未保留,原因不明,疑似热更新/IDE 回退)
- ✅ 已重新改回:`execDate: formatDate(new Date(), 'YYYY-MM-DD') as any`
- 更新 resetQuery 注释(行为不变:回今天 + buildRows)
- ⚠️ 经验:**改动后如用户实测未生效,先 Read 确认磁盘文件实际内容**,热更新回退会导致 Edit 显示成功但代码被还原
| 能否清除 | 能 | **不能**(clearable=false) |
| 展开范围 | 回看30天+提前30天 | **仅当天** |
| 历史欠账 | 累积展示 | **不展**(要回看历史选哪天查哪天) |
| 未来计划 | 提前展示 | **不展** |

### 验证
- vue-tsc 类型检查 execute-doctor-order/index.vue:后台跑中
- bed-change/index.ts 的 `viewStart` 参数保留(无外部调用方成本,可供将来"按周/月汇总"复用),本次不传

## 14:09 用户实测发现 BUG:长期医嘱筛选时只剩 2 条
### 现象
- 医嘱类型=全部 → 9 条(5 个 qw 周展开 + 4 个 bid 两日展开)
- 医嘱类型=长期 → 仅 2 条(9月8号 bid 09:00+15:00),**所有历史行被吞掉**
- 临时筛同理也会丢历史
### 根因
上一轮 11:46 把"是否回看历史"绑死到 `queryParams.orderType == null`:
```ts
const showHistory = queryParams.orderType == null
const viewStartDate = showHistory ? execDate-30d : execDate

→ 类型筛选=长期/临时时 viewStartDate=execDate,过滤 [viewStartDate, execDate] 只剩当天

设计正解

类型筛选只过滤类型,不改变时间窗口。长期/临时/全部 都回看 [execDate-30天, execDate] 历史

修改(execute-doctor-order/index.vue buildRows 第 4 步)

  • 删 const showHistory = queryParams.orderType == null
  • viewStartDate 恒为 dayjs(execDate).subtract(30, 'day').format('YYYY-MM-DD')
  • 注释同步更新:明确"类型筛选不影响时间窗口"

    效果

  • 全部:长期 + 临时,30 天历史 + 当天

  • 长期:仅长期,30 天历史 + 当天(之前 9 条数据全集 + 0 临时 = 9 条)

  • 临时:仅临时,30 天历史 + 当天

    教训

    区分「数据范围」与「过滤维度」:时间窗口是数据范围,类型是过滤维度——两个维度正交,不该耦合

14:36 大改造:执行日期单日 → daterange 范围(执行页 + 执行记录弹窗)

用户给真实后端规格文档(D:/Users/chenjun/Documents/WXWork/1688856573655686/Cache/File/2026-08/doctor-order-backend-spec.md),核对后端现状:

  • doctorOrderExecution/page 仅支持单日 planDate
  • 长期/临时医嘱 page 无时间过滤
  • plan_time 已有 idx_plan(plan_time, tenant_id) 索引,范围查询无性能问题
  • 已更新桌面规格建议文档《医嘱执行单-时间范围查询-后端接口规格建议.md》

执行记录弹窗 ExecutionRecordDialog.vue(长/短两个入口都复用此弹窗)

  • execDate: string → dateRange: [string, string],默认近 30 天 ~ 今天
  • <el-date-picker type="daterange" :clearable="false">
  • 信息行「执行日期」→ 范围文本(单日简写)
  • 接口 getDoctorOrderExecutionPage 传 planDateBegin / planDateEnd(替代单日 planDate)
  • pageSize: 1000 已符合
  • 表格容器 .table-scroll{max-height:60vh; overflow-y:auto},超 60vh 弹框内自动滚动
  • @media print 取消滚动让打印完整
  • 新增「查询」「重置」按钮,重置回近 30 天 ~ 今天

执行页 execute-doctor-order/index.vue

  • queryParams.execDate 单日 → queryParams.dateRange 范围
  • <el-date-picker type="daterange" :clearable="false">,label 改"执行日期"
  • queryRules.dateRange 自定义 validator:必填 + 起始不能晚于结束
  • dateRangeText computed:单日简化 / 多日「YYYY-MM-DD 至 YYYY-MM-DD」
  • 删 HISTORY_DAYS=30 常量;viewStartDate = dateRange[0],dateEnd = dateRange[1]
  • buildRows 顶部守卫 if (!dateRange || dateRange.length !== 2 || !dateRange[0] || !dateRange[1])
  • getDoctorOrderExecutionPage 传 planDateBegin/planDateEnd
  • 长期/临时展开上下界改用 dateBegin/dateEnd(buildPendingPlanTimes 的 now/viewStart)
  • 打印区 2 处(all 汇总 / single 单条)「执行日期」→ dateRangeText
  • 重置 → dateRange = [今天-30, 今天] + buildRows
  • onMounted → buildRows(默认 dateRange 已含值,自动加载近 30 天 ~ 今天)

✅ 实际后端参数命名(2026-09-08 已确认)

  • longDoctorOrder/page / shortDoctorOrder/page 新增 latestOrderTime(array[string],按 entry 最大 order_time 过滤)
  • doctorOrderExecution/page 新增 planTime(array[string],按 plan_time 范围过滤)
  • ⚠️ 实测:后端接收的是 YYYY-MM-DD 日期字符串数组(不是毫秒戳),与规格文档「四之一·时间字段传递约定」原文不一致——以实际接口格式为准
    • axios 序列化为 ?planTime=2026-08-09&planTime=2026-09-08
    • 后端 Spring MVC 用 @RequestParam List<String> 接收
    • 用户(2026-09-08 15:36)明确说明:"是传 array[string],也就是日期字符串数组"
  • 旧的 planDate(单日 YYYY-MM-DD 字符串)保留:单日仍可用,本次未传(统一走范围)

调用方改动:

  • execute-doctor-order/index.vue buildRows:3 处接口调用
    • 长期/临时医嘱:新增 latestOrderTime: [dateBegin, dateEnd](YYYY-MM-DD 字符串)
    • 执行记录:planDateBegin/planDateEnd → planTime: [dateBegin, dateEnd](YYYY-MM-DD 字符串)
  • ExecutionRecordDialog.vue fetchList:1 处接口调用 + 兜底 2 处
    • 执行记录:同上 planTime: [dateBegin, dateEnd]
    • 兜底 long/short 医嘱 page:同步加 latestOrderTime: [dateBegin, dateEnd]
  • ⚠️ 2026-09-08 漏改:长期/临时医嘱列表页 getList 仍传旧参数 beginTime/endTime(项目惯例,yudao 时间范围查询通用名)
    • 已改:长期/临时医嘱列表 long-doctor-order/index.vue:267-271 和 short-doctor-order/index.vue:174-178
    • 改法:params.beginTime/endTime → params.latestOrderTime = [b, e]
    • 原因:后端已加新参数 latestOrderTime(按 entry 最大 order_time 过滤),旧的 beginTime/endTime 估计走的是订单的 create_time,与"按 entry 最大 order_time 过滤"语义不符

执行记录弹窗:医嘱内容前端兜底(2026-09-08)

用户反馈:某长者的执行记录表格里医嘱内容为空(长者B 91岁那条),但频次/给药时间/途径都填了。

根因:execution 表是引用型设计(仅存 orderType+orderEntryId,不冗余存 orderContent/frequency/medTime/route)。 后端 doctorOrderExecution/page 接口的 join 在某些长者上漏了字段——典型表现就是「医嘱内容空、但其它字段都在」。

前端兜底(ExecutionRecordDialog.vue fetchList):

  1. 拉到 execution 后,判断是否有空字段(orderContent/frequency/medTime/route 任一为空)
  2. 若需要兜底 → 并行拉 longDoctorOrderPage + shortDoctorOrderPage(按 elderId 过滤)
  3. 按 orderType-orderEntryId 建 Map,把空字段从 entry 上取出来
  4. 只补缺失字段,不覆盖后端实际返回的值(真实值优先级最高)
  5. 接口失败不阻塞主流程(catch 静默)

长期解法:让后端 page 接口 join 完整所有字段;前端兜底纯属保险。

入参变化:longDoctorOrderPage/shortDoctorOrderPage 现在传 elderId(之前未传,按 elderName 模糊匹配; 改成 elderId 精确匹配,更准确)。

执行医嘱页:默认窗口与默认状态调整(2026-09-08 16:00)

用户反馈:默认「近 30 天 ~ 今天」数据量太大(截图里看到 4 条长者),且未筛状态时已执行+未执行混着看。

改动(仅 execute-doctor-order/index.vue,弹窗 ExecutionRecordDialog.vue 不动):

  • queryParams.dateRange 默认值:今天-30天~今天 → 今天-3天~今天
  • queryParams.status 默认值:undefined(全部)→ 0(仅未执行)
  • resetQuery 重置回同样的默认值(保持一致行为)
  • 改完进入页面即展示「前 3 天 ~ 今天 + 仅未执行」的首屏干净视图
  • 用户想看已执行/全部:清掉状态筛选项;想看更早:拉宽日期范围

教训:列表页默认窗口不宜过大,3~7 天是「近期欠账回看」的合适跨度;状态筛选器默认聚焦核心场景(待执行),减少首屏噪声。

v-print 打印区 id 必须动态化(2026-09-08 16:10 串号 BUG)

用户反馈:在长期医嘱页打了一次「执行记录」,再切到临时医嘱页打开执行记录弹窗点打印,预览里仍是「长期医嘱」表格内容。

根因:ExecutionRecordDialog.vue 的 printConfig.id 之前写死成 execRecordPrintArea(固定字符串),导致:

  • vue3-print-nb 在多次打印间缓存 DOM 副本到 iframe
  • 不同路由/不同长者用同一 id 时,库按 id 找到的是 iframe 里的旧 DOM(甚至离屏节点)
  • 表现为"打印内容串号"

修复:打印区 id 改成动态拼接 execRecordPrintArea-{elderId}-{openCounter}

  • openCounter 单调递增(每次 open 自增),保证同长者重复打开也有新 id
  • printConfig 改 computed,模板 :id="printAreaId" 同步
  • 库每次按新 id querySelector 必然拿到本次弹窗的新 DOM,覆盖 iframe 缓存

范式:项目里所有用 v-print 的弹窗(不只是执行记录)都应该避免固定 id。

  • 当前其它打印区 id(暂未发现串号问题,但同隐患):longOrderPrintArea、shortOrderPrintArea、execPrintArea、singlePrintArea
  • 如果后续发现串号,同样用 computed + counter 范式修复

批量执行医嘱功能(2026-09-08 16:16)

用户需求:执行医嘱页加批量执行,弹窗顶部展示选中的项,统一填时间/护士/备注后循环调接口。

实现:

  • 新增 BatchExecuteDialog.vue(与 ExecuteDialog.vue 风格一致)
  • 表格加 type="selection" 列 + selectable 限定只能选 status=0(待执行)
  • 顶部「批量执行(N 项)」按钮,未选中时禁用
  • 选中后点击 → 弹窗顶部显示选中项清单(长者/床位/计划时间/医嘱内容)
  • 表单共用:实际执行时间(必填)+ 执行护士 + 备注
  • 顺序 for await 循环调 executeDoctorOrder(避免并发压力后端)
  • 错误处理与单条 ExecuteDialog 一致:拦截器对 code:0 走 reject,try/catch 都按成功处理
  • 完成后 emit success,父组件清选中 + 调 buildRows() 刷新

额外修复:userStore.nickname → (userStore as any).user?.nickname(store 真实结构在 user 顶层)

  • ExecuteDialog.vue 第 91 行也有同样问题,一并修复
  • 这是项目基线就有的 TS 错误(vue-tsc 漏报),现在批量执行弹窗被触发报出来

新建文件:

  • src/views/elderly/elder/doctor-orders/execute-doctor-order/BatchExecuteDialog.vue

改动文件:

  • src/views/elderly/elder/doctor-orders/execute-doctor-order/index.vue(加 selection 列、按钮、状态/方法)
  • src/views/elderly/elder/doctor-orders/execute-doctor-order/ExecuteDialog.vue(修 userStore 路径)

⚠️ 孤儿结束标签陷阱(2026-09-08 已修复)

  • 症状:vite:vue 报 Invalid end tag at 行:列,错误信息里出现 </content> / </invoke> 等与代码无关的 tag 名
  • 真实原因:SFC 文件末尾不小心残留了一个孤儿结束标签(如 </content>),没有对应的开始标签
  • 来源可能是 Write/Edit 工具调用时无意带进了工具指令的收尾,或 IDE 粘贴板错位
  • vue-tsc 不会报错(它走 AST 不严格解析 HTML),但 vite:vue 编译器严格做 HTML 解析会被卡住
  • 自检范式:写完新 SFC 文件,立刻 Grep 看末尾是不是干净的 </style> / </script> / </template> 配对;任何其它 tag 立即删除