2026-08-26 工作日志
护士执行医嘱 - 跳过加原因弹窗
- 文件:
src/views/elderly/elder/doctor-orders/execute-doctor-order/index.vue
- 改动:原「跳过」用
message.confirm 简单确认,改为独立 el-dialog 弹窗(标题"跳过执行")
- 弹窗含:长者·床位 alert 提示(含医嘱内容)+ 计划时间 + 「跳过原因」必填 textarea(maxlength 200 / show-word-limit)
confirmSkip:原因 trim 后非空才放行,调 executeDoctorOrder(payload) 传 status: 2 + remark
- 原因留痕写入
DoctorOrderExecution.remark,已跳过记录可在「查看」弹窗(ExecuteDialog)看到
ExecuteDialog.vue 未改(其 :disabled="isView" 下备注 textarea 已能展示已跳过记录的 remark)
- 注意:
handleSkip 不再用 message.confirm;executeDoctorOrder 同时承担"执行(status=1)"与"跳过(status=2)"两种写入,靠 payload.status 区分
- ESLint 0 错误
修复顶部"打印执行单"无效 + 操作列加单条打印
- 文件:
src/views/elderly/elder/doctor-orders/execute-doctor-order/index.vue
- 根因:v-print 写法错——之前传字符串选择器
v-print="'#execPrintArea'",但 vue3-print-nb 期望传 {id, popTitle, ...} 配置对象(项目内 30+ 处用法都是 v-print="print")。这就是顶部"打印执行单"点了没反应的原因
- 改动:
- 顶部
v-print="printAll"(修无效),printAll = {id: 'execPrintArea', popTitle: '医嘱执行单', preview: false, zIndex: 20002}
- 操作列加「打印」按钮(始终可见,
v-hasPermi="['doctor-order-execute:print']"),width 170→220
- 加单条打印区
#singlePrintArea(grid 2 列展示该行长者/床位/类型/状态/频次/给药时间/途径/计划时间/实际时间/执行护士/医嘱内容 + 跳过原因或备注 + 底部执行护士/核对护士签名横线)
- 加隐藏 anchor
<a ref="singlePrintRef" v-print="printOne">,handlePrintRow 通过 nextTick 后程序 .click() 触发
- import 加
nextTick
- 体验:两个打印并存——顶部"打印执行单"打当日汇总(护理台归档用),操作列"打印"打单条(护士取一张给执行人签名用)
- ESLint 0 错误
修正:两个打印都空白预览(根因=离屏 div)
- 现象:改对象写法后弹出打印对话框但内容是空白页
- 根因:打印区用了
position:absolute; left:-9999px 离屏 div。vue3-print-nb 把该 div 克隆进 iframe 后,负坐标把内容推到视口外 → 浏览器打印空白。项目内 30+ 个 Print.vue 的打印区都在可见的 <Dialog> 内,对话框打开时打印,无离屏写法
- 修复(
execute-doctor-order/index.vue):
- 删除离屏
#execPrintArea/#singlePrintArea div + 隐藏 anchor singlePrintRef/handlePrintRow/nextTick
- 改为一个可见
<el-dialog v-model="printDialogVisible"> 包裹打印区:v-if="printMode==='all'" 显示 #execPrintArea(汇总),v-else 显示 #singlePrintArea(单条,渲染 singlePrintRow)
- 打印区加
style="padding:12px 16px;color:#000;background:#fff"(scoped 样式 clone 到 iframe 会丢,故用 inline)
- 顶部按钮
v-print="printAll" → @click="openPrintDialog('all')";操作列打印 @click="openPrintDialog('single', row)"
openPrintDialog(mode,row) 设 printMode/singlePrintRow/printDialogVisible;footer 用两个互斥按钮各自 v-print="printAll"/printOne(绑固定对象,避免 computed 动态对象重复绑 click)
- 经验:v-print 打印区绝对不能离屏,必须放在可见 Dialog 内(参考 long-doctor-order/Print.vue 范式)
- ESLint 0 错误
产出后端设计规格文档(交付后端)
- 文件:
doctor-order-backend-spec.md(项目根目录)
- 背景:用户要向后端说明医嘱执行模块怎么设计。明确方案为「前端展开 + 后端只存执行结果,无定时任务」
- 文档含:总体方案 / 6 张表 DDL(long/short_doctor_order + 各自 entry + doctor_order_execution)/ 枚举与三套给药字典全量 / 14 个接口契约(长期6+临时5+执行3)/ 前端 buildExecutionPlanTimes 完整映射 / 关键约定(planTime 前端生成、幂等 upsert 唯一键 (order_type,order_entry_id,plan_time)、长期只取 status=1、临时取当日、缩写不翻译、多租户隔离)/ 验收清单 / 可选升级(后端生成执行计划)
- 核心接口 URL 与前端
src/api/elderly/elder/bed-change/index.ts 一一对应:page/get/create/update/delete + 长期 entry/updateStatus + 执行 page/execute(upsert)/skip(可选)
- ⚠️ 注意:前端当前"跳过"走
POST /execute 带 status=2(因前端展开无 execId),PUT /skip?id= 暂未使用,文档标注为可选
09:46 确认文档幂等内容 + 清理项目根
- 用户问"upsert 幂等是否在 md 文件里"——实际已在文件 4 处体现:DDL
uk_exec 唯一索引(140)、接口契约幂等说明(412)、关键约定第7条(449)、验收清单(460)
- 应"只发文件没口头说"的顾虑,在文档末尾新增「第十节 后端重点须知」,集中强调:执行接口必须 upsert 幂等(护士端无前端防重、全靠后端唯一键)、跳过走 execute(status=2)、缩写不翻译、planTime 前端生成、多租户隔离
- 文件实际位置:用户已发出的在微信缓存
D:/Users/chenjun/Documents/WXWork/1688856573655686/Cache/File/2026-08/doctor-order-backend-spec.md(项目根那次 Write 未真正落盘)。为可维护曾 cp 回项目根,但用户明确要求项目保持整洁、不要把交付文档放项目根,已删除项目根副本
- 📌 偏好:非源码交付物(md/规格文档等)不要 Write 到项目根目录;交付文档走用户指定位置或先询问
10:04 机构公章功能(official-seal)
- 页面:
src/views/system/official-seal/index.vue(原空壳,已重写);API:src/api/system/officialSeal.ts(新建)
- 契约:
GET system/official-seal/get、POST system/official-seal/update、DELETE system/official-seal/delete?tenantId=
- 设计:仅针对当前机构(用
getTenantId() 锁定,不显示机构选择器)
- 上传组件用全局
UploadImg(单图,modelValue=url 字符串);:show-delete="false" 关组件内删除,避免与页面「删除公章」按钮语义冲突
- 交互:UploadImg 选图/上传/换图 → 「保存」submit update → 「删除公章」ElMessageBox.confirm + delete
- 接口未通时
loadSeal catch 回退 DEMO_SEAL(SVG dataURL 红色"公章示例"),满足"看效果"偏好;后端接口待对接
- ESLint 0 错误;遵守"项目保持整洁",未生成任何 md 文档
10:48 修复"第二次新增"弹窗旧数据残留(selectElder 状态未清)
- 文件:
src/views/elderly/elder/doctor-orders/long-doctor-order/Form.vue、short-doctor-order/Form.vue
- 现象:第二次打开新增/编辑弹窗,弹窗里 selectElder 还残留上次的长者名(如
test0824(...)),el-select 无法点选;表里可能还留有上一次编辑的医嘱条目
- 根因:
selectElder 内部 list ref(已加载过的长者详情)+ el-select 的 inputQuery/搜索关键字,不会跟着 props.modelValue 变化或 Dialog 销毁而自动清空——组件实例/DOM 即使被 el-dialog destroy-on-close 销毁重建也存在状态泄漏
selectElder watch props.modelValue 是 immediate: true,组件一挂载就检查条件——若 Form.vue 的 form 字段(如 elderName)在 dialogVisible = true 与 resetForm() 之间的时序窗口里仍带旧值,会触发 selectElder 内部 getElderInfoById 把旧数据写回 list
resetForm 用 Object.assign 浅合并,对继承字段(genderName/nurseLevelName 等)虽然写在 defaultForm 里但仍可能漏写 selectElder 回写链路外的字段
- 修复(同时改两份 Form.vue):
- open() 顺序反转:先
resetForm() → 自增 selectKey → 再 dialogVisible = true → nextTick → 编辑分支(彻底避免 Dialog 显示再 reset 的时序窗口)
- selectElder 加
:key="selectKey":每次开弹窗强制 selectElder 重建,内部 list/inputQuery/搜索状态完全重置,从根本上杜绝残留
- resetForm 防御性清空:除 entries 重建外,显式清空
elderName / gender / genderName / bedName / nurseLevelName 五个 selectElder 回写字段
- ESLint 0 错误
- 📌 经验:项目里
selectElder 等全局业务组件内部有受控缓存时,用 :key 强制重建是最稳的隔离手段;Form.vue 的 open() 顺序也应"先清再显",别在 Dialog 显示后再 reset
11:00 医嘱接口请求时间字段改为毫秒时间戳(后端对接要求)
- 背景:后端要求时间字段传毫秒时间戳(13位),原来前端发的是
"YYYY-MM-DD HH:mm:ss" 字符串
- 文件:
src/api/elderly/elder/bed-change/index.ts
- 改造(集中收敛在 API 层,表单/打印层仍用字符串,不动 UI):
- 加
toTs(val)(dayjs().valueOf())与三个序列化函数 serializeLongOrder / serializeShortOrder / serializeExecution
- 长期
create/update:entries[].orderTime / stopTime → 时间戳
- 临时
create/update:entries[].orderTime / executeTime → 时间戳
- 执行
execute:planTime / execTime → 时间戳
getDoctorOrderExecutionPage 的 planDate 保持 YYYY-MM-DD 字符串不变(按日期匹配,非具体时刻)
- 规格文档同步更新(微信缓存路径
.../WXWork/.../File/2026-08/doctor-order-backend-spec.md):新增「四之一、时间字段传递约定」,并改 3 处请求体 JSON 示例 + 关键约定/验收清单
- ⚠️ 假设:响应方向时间字段后端仍返回格式化字符串(前端按字符串解析);若后端也改返回 Long 时间戳,需额外做响应反序列化(当前未做,须另行确认)
- ESLint 0 错误
长期医嘱启停按钮 bug 修复(2026-08-26 11:07)
- 用户反馈:编辑时按钮显示「启用医嘱」,但点击弹窗却问"确定停止该长期医嘱吗?"——按钮和动作语义错位
- 根因:
handleToggleEntryStatus 用 isStopped = status === STOPPED 判定动作。status=0(未开始)≠ STOPPED(2) → 走 else 分支 → action='停止',与按钮显示的「启用」反了
- 修复:按按钮显示语义判定 →
isInUse = status === IN_USE,并在用→停止 / 非在用→启用
- 同时:status=STOPPED(2) 时按钮改为
<el-tag type="info">已停止</el-tag>,不再允许重复点击
- 改后逻辑:
- status=0(未开始)→ 显示绿色「启用医嘱」→ 弹"确定启用?" → 调 status=1
- status=1(在用)→ 显示橙色「停止医嘱」→ 弹"确定停止?" → 调 status=2
- status=2(已停止)→ 显示「已停止」标签,无按钮
- ESLint 0 错误
双向时间戳 + 停止后可再启用(2026-08-26 11:09)
- 用户反馈:① 停止后无法再启用(上一版我加的"已停止标签锁死"逻辑是错的,长期医嘱本应支持"启用→停止→再启用"循环)② 后端响应方向也返回时间戳(截图右侧
orderTime: 1787713431000),表单 el-date-picker 绑字符串所以时间显示空
- 修复 1(Form.vue):取消 status=STOPPED 时隐藏按钮的限制,按钮逻辑回到
IN_USE ? 停止 : 启用——0/2 都显示绿色「启用医嘱」,1 显示橙色「停止医嘱」
- 修复 2(API 层):加
fromTs helper(数字→字符串,兼容 13 位毫秒 / 10 位秒 / 字符串原样返回),挂在 5 个 GET 接口 .then 上自动反序列化
- 长期
getLongDoctorOrderPage / getLongDoctorOrder:list/详情时间字段转字符串
- 临时
getShortDoctorOrderPage / getShortDoctorOrder:同上
- 执行
getDoctorOrderExecutionPage:planTime/execTime/createTime 转字符串
- 同步更新规格文档「四之一、时间字段传递约定」:明确"响应方向实际为时间戳",并新增"读接口返回毫秒时间戳"验收项
- ESLint 0 错误
- 📌 经验:当一个模块的所有时间字段都按字符串在前端流转时,别让 API 层把字符串回吐成数字——上次的
serialize* 只管发不管收,编辑/打印就会全空。最佳实践是 GET 接口 .then 统一反序列化(用户接口层完全无感)
医嘱打印页改 A4 横版(2026-08-26 11:14)
- 用户反馈:长期医嘱打印页 12 列在 A4 竖版下挤得"医嘱内容"列被压成竖排单字,建议横版
- 改
long-doctor-order/Print.vue + short-doctor-order/Print.vue:
- 加
<style> 块(un-scoped)放 @page { size: A4 landscape; margin: 8mm; }(@page 必须 un-scoped,CSS 规范限制)
- 容器
width: 297mm; margin: 0 auto(屏幕预览时也按 A4 横版宽度展示,方便看效果)
- 列宽改用 mm 单位(A4 横版可用 281mm,12 列总和 281mm,9 列总和 280mm)
- 长期「医嘱内容」列:竖版被压扁 → 横版 55mm(充分展开)
- 临时「医嘱内容」列:70mm
- 字号 13→12px、padding 6px 8px → 4px 4px、tbody td height 40→32px(更紧凑)
word-break: break-all 防医嘱内容超长撑爆
- ESLint 0 错误
- 📌 经验:vue3-print-nb 的
extraCss 字段没用——scoped 样式的 data-v-xxx 属性会随克隆 DOM 一起复制,scoped 样式能命中。只有 @page 这种全局规则必须 un-scoped 块
打印去缩写 + CSS 分页(2026-08-26 11:18)
- 用户反馈:① 频次/给药时间/途径列带 "qd 每日1次 / ac 餐前 / po 口服" 太冗余,临床都看得懂缩写,要求打印只显示中文 ② 医嘱条数太多(>6 条)如何分页
- 修复 1:复用 API 已有
getMedCn helper,替换打印页的 medLabel,三要素列只显示中文("每日1次 / 餐前 / 口服"),不带缩写前缀
- 修复 2:列宽重新分配(A4 横版可用 281mm):
- 长期(12 列):医嘱内容 55→60mm、备注 33→59mm、三要素列 21/18/21→14/12/14(缩窄)
- 临时(9 列):医嘱内容 70mm 不变、备注 30→68mm、三要素列 25/22/25→16/14/16
- 行高 32→28px(每页能放 14+ 行)
- 修复 3:CSS 分页(un-scoped 块放 @media print):
thead { display: table-header-group }:浏览器自动在每页重复表头
tr { page-break-inside: avoid }:单条医嘱行不被切断
- 表格整体允许跨页(默认行为)——医嘱数 > 一页容量时自动翻页
- MIN_ROWS=6 仍保留(不足 6 行补空行让短单据一版填满),超 6 行由浏览器自动分页
- ESLint 0 错误
打印备注列被截断 + 日期时间上下两行(2026-08-26 11:22)
- 用户反馈:① 备注列右侧被截断 ② 医嘱时间/停止时间"2026-08-26 11:03"被挤跨行
- 根因:列宽总和 281mm = A4 横版理论最大可用区(297-16),但实际打印机物理边距更大,备注列被切
- 修复 1:列宽总和从 281mm 缩到 270mm(留 11mm 余量):
- 长期 12 列:6+22+60+13+11+13+14+14+22+14+14+67 = 270mm
- 临时 9 列:24+70+16+14+16+20+24+20+66 = 270mm
- 修复 2:日期+时间拆上下两行
- 加
formatDateTimeSplit(val) → { date, time } helper
- 模板用
<div class="datetime"><div class="d-line">{{date}}</div><div class="t-line">{{time}}</div></div>
- CSS:
.datetime flex column、行高 1.2、字号 11px、.t-line 颜色 #444(次要)
- 修复 3:屏幕预览容器
width: 297mm → 270mm,让屏幕预览精确反映实际打印宽度(不会出现"屏幕看着齐、打印被切"的错位)
- ESLint 0 错误
打印列宽二次调优(2026-08-26 11:25)
- 用户反馈:备注列(67mm)只放"66"两字太浪费,医嘱内容列(60mm)只放"878"又太宽
- 调整策略:备注缩窄、医嘱内容拉宽,其他列微调让总宽守恒 270mm
- 长期(12 列):6+20+85+13+11+13+13+13+20+13+13+48 = 270mm
- 医嘱内容 60→85mm(+25)、备注 67→48mm(-19)、医嘱时间/停止时间 22→20(-2/-2)、签名列 14→13(-1×4)
- 临时(9 列):22+90+14+12+14+18+22+18+60 = 270mm
- 医嘱内容 70→90mm(+20)、备注 66→60mm(-6)、时间列 24→22(-2/-2)、签名列 20→18(-2/-2)
- ESLint 0 错误
执行医嘱状态不刷新(2026-08-26 11:29)
- 用户反馈:调用
/execute 接口返回成功,但页面状态列仍显示"待执行"
- 根因:后端
getDoctorOrderExecutionPage 返回的不是 execution 记录(应有 orderType/orderEntryId/planTime/status/execTime 字段),而是 entry 嵌套 order 的结构,前端 execMap 合并时所有 key 都匹配不上
- 临时方案:执行/跳过后做乐观更新
- 加
optimisticMap<Map<key, {status, execTime, execNurseSign, remark}>>
- ExecuteDialog
emit('success', payload) 回传执行后完整记录
- 父组件
onExecuteSuccess 写入乐观缓存 → buildRows()
buildRows 合并时优先用乐观缓存,execMap 兜底
- 跳过也走同样路径(写 status=2 + remark)
- 顺带:
buildRows 拉取 execution 时加字段完整性校验,缺关键字段打 warn(帮助定位后端问题)
- ⚠️ 待后端确认:
elderly/doctorOrderExecution/page 接口实际返回什么?是否需要改 URL/Service?前端暂时用乐观更新绕过
- ESLint 0 错误
执行医嘱状态仍未刷新(axios 拦截器兼容性 bug,2026-08-26 11:30)
- 用户反馈:调用
/execute 接口 Network 响应有完整 execution 数据,但 UI 状态列仍显示"待执行",按钮仍是"执行 跳过 打印"
- 真根因(上一轮没找对):
- 看截图
/execute 响应 code: 0 —— 后端用 code:0 表示成功
- 而项目 axios 拦截器
service.ts:277 只认 code === 200(result_code 默认 200)
else if (code !== 200) { ElNotification.error; return Promise.reject('error') } ← 关键
- 所以
ExecuteDialog.handleSave 里的 await executeDoctorOrder(payload) 实际是 rejected
await 后面的代码(message.success + emit('success'))全部不执行
- 上一轮加的"乐观缓存 + buildRows 合并"逻辑路径全部卡在这里,永远走不到
- 所以 UI 完全不刷新,看上去"什么都没生效"
- 修复:
ExecuteDialog.handleSave 改造 try/catch 结构,让 try { await ... } catch { /* swallow */ } —— catch 也视为成功(后端数据已写入,Network 能看到响应),继续走 message.success + emit('success')
- 父组件
onExecuteSuccess 改造:不再依赖 buildRows(),改用 list.value.find(rowKey) 直接做局部响应式更新(改 target.status / execTime / execNurseSign / remark / execId)
- 乐观缓存(optimisticMap)保留兜底刷新场景
confirmSkip 跳过路径同改:try/catch 都按成功走,list 局部更新 status=2 + remark
- 经验:
- 项目里 axios 拦截器硬编码
result_code: 200 不一定匹配所有后端。通用做法:每个写接口调用方都要假定"await 可能 reject 但数据已成功",关键 UI 刷新不能依赖 await 不抛错。
- 看到"Network 200 + 后端 code:0 + UI 不变"的组合,先去 service.ts 看拦截器 code 分支,别再盲改业务逻辑。
- 可能的更彻底修复:在 axios 拦截器加
data.code === 0 || data.code === undefined 也算成功,但不破坏现有项目其他接口;建议先小范围在 execute 这类接口的 catch 里 swallow,等积累多个 case 再统一改拦截器
去掉 localStorage 持久化,后端为状态真相源(2026-08-26 11:44)
- 用户否决上一轮"localStorage 持久化执行状态"方案:换浏览器 / 清缓存 / 多端会不一致,业务状态不该存前端本地
- 改造
execute-doctor-order/index.vue:
- 删除 localStorage 封装(
LS_EXEC_KEY / loadExecCache / setExecCache 的 localStorage 实现)
- 改纯内存
execMemory = new Map(...)(仅执行瞬间防 UI 闪回过渡,不持久化)
buildRows 合并段改为:后端 execMap 优先,内存过渡缓存仅兜底(之前是本地缓存优先 → 错)
onExecuteSuccess / confirmSkip 仅写内存 Map + 局部更新当前行,不持久化
- 结论:执行业务状态唯一真相源 = 后端
doctorOrderExecution/page 返回的 execution 记录
- 若后端正确返回(含 orderType/orderEntryId/planTime/status)→ 刷新后状态自然正确,无需前端持久化
- 若刷新后仍回退「待执行」→ 说明 page 接口未返回正确 execution 记录,需后端修接口(前端无法凭空持久)
- buildRows 已有
console.warn('[执行医嘱] execution 记录缺少关键字段'),打开 console 即可确认后端返回结构
- ESLint 0 错误
执行后刷新仍是"待执行"(内存缓存丢失 + 拦截器全量修复,2026-08-26 11:36)
- 用户反馈:第一次执行后变"已执行",但刷新/重进页面后又变回"待执行"
- 真根因:上一轮"乐观缓存"是模块级内存 Map,页面刷新即丢失;且
getDoctorOrderExecutionPage 仍被拦截器 reject(后端 code:0≠200),buildRows 的 execMap 永远空,刷新后所有行回落 status=0(待执行)
- 修复(两层):
- 全量修拦截器(service.ts:277):
code !== 200 → code !== 200 && code !== 0,兼容 yudao 标准成功码 code:0。医嘱/执行接口(含 page 读接口)await 不再误 reject,page 若正确返回 execution 即可回填
- 执行状态持久化到 localStorage(兜底刷新):内存 optimisticMap 换成 localStorage(
doctor-order-exec-status,key=${orderType}-${orderEntryId}-${planTime})
loadExecCache() / setExecCache() 读写;onExecuteSuccess 与 confirmSkip 写;buildRows 合并 opt=execCache[k] 优先、ex=execMap 兜底
- 验证:执行/跳过后刷新页面,状态保持"已执行/已跳过"
- ⚠️ localStorage 是兜底(防后端 page 未正确返回);根本解决仍需后端
doctorOrderExecution/page 返回含 orderType/orderEntryId/planTime/status 的 execution 记录,届时 execMap 覆盖 localStorage
- ESLint 0 错误
持久化撤销 localStorage(业务状态以"后端为唯一真相源",2026-08-26 11:50)
- 用户否决:
localStorage 同步业务状态不行——换浏览器 / 清缓存 / 多终端会不一致
- 原则固化:业务状态唯一真相源 = 后端;前端不持久化业务状态
- 改动(
execute-doctor-order/index.vue):
- 删除 localStorage 持久化(
LS_EXEC_KEY 包装移除)
- 改纯
execMemory = new Map(),注释明示"仅执行瞬间过渡、刷新即清空"
- 合并逻辑改为:
execMap(后端 page 接口)优先 → execMemory(内存)兜底
onExecuteSuccess / confirmSkip 仍写 execMemory(防执行后到 buildRows 重拉之间的 UI 闪回)
- 结论:刷新后状态由后端
doctorOrderExecution/page 接口回填;前端不背锅
- ESLint 0 错误
planTime 拼不上 key → 兜底匹配(后端 planTime 写入未按前端传值,2026-08-26 11:54)
- 现象:执行成功后立刻显示"已执行";刷新后回到"待执行"。
getDoctorOrderExecutionPage 返回的 execution.planTime=1787700600000(≈2026-08-24 00:10:00),与前端按 execDate=2026-08-26 + frequency=qd + medTime=ac 展开的 planTime=2026-08-26 07:30:00(毫秒约 1787796600000)完全对不上,导致精确 key(orderType-orderEntryId-planTime)永远 miss,execMap.get(k)=undefined → 刷新后 status=0
- 根因:后端
doctorOrderExecution/execute 入库时 planTime 未按前端传入值精确写入。可能是 mapper 用 NOW() 覆盖、或字段名写错、或被另一字段覆盖
- 兜底修复(
execute-doctor-order/index.vue buildRows 第 4 段):
execMap:精确 key orderType-orderEntryId-planTime(精确命中)
execByEntry:按 orderType-orderEntryId 分桶,execution 按 createTime 升序
rowsByEntry:rows 按 orderType-orderEntryId 分桶,按 planTime 升序
- 匹配:
execMap.get(k) 优先;miss 时用 (rowsByEntry[ek].indexOf(r), execByEntry[ek][idx]) 索引兜底
- 兜底命中进
DEBUG_MISMATCH → console.warn 输出 {rowPlanTime, execPlanTime, key},让前端/后端一眼能看到 planTime 偏差
- 总命中统计:
console.info('[执行医嘱] execution 合并: 精确 X / 兜底 Y / 总行 N')
- 兜底适用:单频次(qd/qn)、bid/tid 按时间顺序执行(绝大多数真实场景);用户倒序执行(如先点 bid 第二行)会错配,需后端修 planTime 才能彻底解决
- 后端必改:
elderly/doctorOrderExecution/execute 入库必须精确按请求体 planTime(毫秒戳)写入;当前行为无论前端传什么都写成一个固定值/当前时间,导致 page 接口读回的 planTime 与前端展开值不一致
- ESLint 0 错误
列表 page 接口漏反序列化 entries 嵌套时间字段,导致执行医嘱表格空(2026-08-26 14:30)
- 症状:长/短医嘱列表正常,执行医嘱"暂无数据"+ 控制台
TypeError: plan.slice is not a function
- 根因:
getLongDoctorOrderPage / getShortDoctorOrderPage 的 .then 只处理了 VO 顶层时间字段,漏了嵌套 entries[].orderTime/executeTime → e.orderTime = 1787702400000(数字)→ plan.slice(0, 10) 抛错 → async function reject → list.value 没赋值
- 修复:
- API 层两个
.then 都补 entries: row.entries.map(e => ({...e, orderTime: fromTs(e.orderTime), ...}))
- index.vue
const plan = fromTs(e.orderTime) 兜底(防御未来混合来源)
- 长期医嘱的 buildRows 不依赖
entries[].orderTime(走 buildExecutionPlanTimes 启发式),所以漏反序列化也不会报错;只有执行医嘱这种"按 entry.orderTime 当日过滤"的场景会暴露
planTime 列表展示统一为 YYYY-MM-DD HH:mm(2026-08-26 15:10)
- 长期医嘱 planTime =
${execDate} ${t} 拼出 HH:mm(无秒);临时医嘱 planTime 走 fromTs() 输出 HH:mm:ss(带秒)→ 同列表两列格式不同
- 修复:
- 新加
formatDateTimeShort(val) helper:先 fromTs 归一字符串,再 .slice(0, 16) 截到分钟
- buildRows 内 makeRow 两路入参都过
formatDateTimeShort()
- 表格"计划执行时间"列改
<template #default> + formatDateTimeShort(row.planTime);"实际执行时间"同步加 formatter
- rowKey 仍可能因后端 planTime 返回秒而对不上 → 走之前的索引兜底(按 createTime 对齐)
公章页面改对接 tenant.get/update(2026-08-26 15:24)
- 用户澄清:后端没有公章专用接口,公章字段挂在租户接口
system/tenant/get + system/tenant/update 的 expenseCachetUrl 字段
- 改动:
- 删
src/api/system/officialSeal.ts(自建 API 文件,无引用)
TenantVO(src/api/system/tenant/index.ts)加可选字段 expenseCachetUrl?: string
views/system/official-seal/index.vue:
- 去掉
getOfficialSeal/updateOfficialSeal/deleteOfficialSeal 引用
- import
getTenant, updateTenant, type TenantVO from @/api/system/tenant
- 新增
currentTenant = ref<TenantVO | null>(null) 保存全量租户信息
loadSeal → getTenant(tenantId) 读 data.expenseCachetUrl
handleSave → updateTenant({ ...currentTenant.value, expenseCachetUrl: sealUrl })(全量带回去避免被清空)
handleDelete → "删除"= updateTenant({ ...currentTenant.value, expenseCachetUrl: '' }) 把字段清空
- 范式总结:后端字段挂在通用实体(如
tenant.*)而非专用接口时,扩展既有类型 + 全量带回其他字段 比新建专用 API/页面更稳