Bläddra i källkod

优化医嘱功能

chenjun 1 månad sedan
förälder
incheckning
d64490b858

+ 149 - 0
.workbuddy/memory/2026-09-08.md

@@ -0,0 +1,149 @@
+# 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 项**:
+  ```ts
+  { 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):
+  1. colgroup 把计划时间/实际时间列各从 8.25% → 10%(+1.75% × 2 = +3.5%)
+  2. 从床位 8.25→7、医嘱内容 18.04→16.5、备注 10.31→10、频次/给药时间/年龄/途径 5.67→5.5 等"瘦"列共匀出 3.5%
+  3. `.datetime` 加 `white-space: nowrap` 防止日期中间断行(双保险)
+
+#### ③ 切换长者时表格残留旧数据
+- **现象**(图3):信息栏显示新长者"测试离往非退住02",但表格第一行"长者"列还是旧长者"测试变更01"
+- **根因**:后端 `doctorOrderExecution/page` 接口**未实现 elderId 字段过滤**(透传参数被忽略),返回全量 execution 记录
+- **修复**(ExecutionRecordDialog.vue):
+  1. **fetchList 加前端兜底过滤**:`list.value = (data.list || []).filter(r => Number(r.elderId) === Number(elderId))`
+     - `Number()` 兼容后端 string/number 混用
+     - 无论后端是否过滤,前端永远只显示当前长者
+  2. **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 对齐)

+ 10 - 1
src/api/elderly/elder/bed-change/index.ts

@@ -294,7 +294,15 @@ export const MED_ROUTE_OPTIONS: MedOption[] = [
   { value: 'ih', label: 'ih 皮下注射', cn: '皮下注射', tip: '胰岛素、低分子肝素等药物' },
   { value: 'igtt', label: 'igtt 胃肠滴注', cn: '胃肠滴注', tip: '鼻饲、肠内营养输注' },
   { value: 'inh', label: 'inh 吸入给药', cn: '吸入给药', tip: '哮喘、慢阻肺的雾化治疗' },
-  { value: 'ext', label: 'ext 外用', cn: '外用', tip: '皮肤药膏、黏膜局部用药' }
+  { value: 'ext', label: 'ext 外用', cn: '外用', tip: '皮肤药膏、黏膜局部用药' },
+  // ↓↓↓ 9-08 新增给药途径项(与现有项 value 不重复)
+  { 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: '腹膜透析、腹腔化疗等' }
 ]
 
 // ==================临时医嘱单====================
@@ -410,6 +418,7 @@ export interface DoctorOrderExecution {
   orderEntryId?: number // 关联原医嘱条目 id(长期/临时各自的 entry id)
   elderId?: number
   elderName?: string
+  elderAge?: number | string // 年龄(执行医嘱 makeRow / 长者执行记录弹窗都用到)
   bedName?: string
   orderContent?: string
   frequency?: string // 给药频次缩写

+ 426 - 0
src/views/elderly/elder/doctor-orders/execute-doctor-order/ExecutionRecordDialog.vue

@@ -0,0 +1,426 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    :title="dialogTitle"
+    width="95%"
+    append-to-body
+    :close-on-click-modal="false"
+  >
+    <div id="execRecordPrintArea" class="exec-print" v-loading="loading">
+      <h2 class="doc-title">{{ dialogTitle }}</h2>
+      <div class="info-row">
+        <div class="info-cell"><span class="lbl">机构:</span>{{ tenantName || '-' }}</div>
+        <div class="info-cell"><span class="lbl">执行日期:</span>{{ execDate }}</div>
+        <div class="info-cell"><span class="lbl">长者:</span>{{ elderName || '-' }}</div>
+        <div class="info-cell"><span class="lbl">类型:</span>{{ orderTypeText }}</div>
+      </div>
+      <table class="order-table">
+        <colgroup>
+          <col style="width: 3.5%" />
+          <col style="width: 8.5%" />
+          <col style="width: 8.5%" />
+          <col style="width: 5%" />
+          <col style="width: 17%" />
+          <col style="width: 5.5%" />
+          <col style="width: 5.5%" />
+          <col style="width: 5.5%" />
+          <col style="width: 10%" />
+          <col style="width: 6%" />
+          <col style="width: 10%" />
+          <col style="width: 5.5%" />
+          <col style="width: 9.5%" />
+        </colgroup>
+        <thead>
+          <tr>
+            <th>序号</th>
+            <th>长者</th>
+            <th>床位</th>
+            <th>年龄</th>
+            <th>医嘱内容</th>
+            <th>频次</th>
+            <th>给药时间</th>
+            <th>途径</th>
+            <th>计划时间</th>
+            <th>执行护士</th>
+            <th>实际时间</th>
+            <th>状态</th>
+            <th>备注</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-if="!list.length">
+            <td colspan="13" class="empty-tip">该长者当日暂无执行记录</td>
+          </tr>
+          <tr v-for="(item, idx) in list" :key="idx">
+            <td>{{ idx + 1 }}</td>
+            <td>{{ item.elderName || elderName || '-' }}</td>
+            <td>{{ item.bedName || '-' }}</td>
+            <td>{{ item.elderAge || '' }}</td>
+            <td><span class="cell-value pre">{{ item.orderContent || '-' }}</span></td>
+            <td>{{ freqCn(item.frequency) }}</td>
+            <td>{{ timeCn(item.medTime) }}</td>
+            <td>{{ routeCn(item.route) }}</td>
+            <td>
+              <div class="datetime">
+                <div class="d-line">{{ formatDateTimeSplit(item.planTime).date }}</div>
+                <div class="t-line">{{ formatDateTimeSplit(item.planTime).time }}</div>
+              </div>
+            </td>
+            <td>{{ item.execNurseSign || '-' }}</td>
+            <td>
+              <div class="datetime">
+                <div class="d-line">{{ formatDateTimeSplit(item.execTime).date }}</div>
+                <div class="t-line">{{ formatDateTimeSplit(item.execTime).time }}</div>
+              </div>
+            </td>
+            <td>{{ statusText(item.status) }}</td>
+            <td><span class="cell-value pre">{{ item.remark || '' }}</span></td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <template #footer>
+      <el-button @click="visible = false">关闭</el-button>
+      <el-button type="primary" v-print="printConfig">打印</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+/**
+ * 医嘱执行记录(按长者过滤)弹窗
+ *
+ * 设计目标:长期/临时医嘱列表行操作栏的「执行记录」按钮复用此组件。
+ * 数据范围:按 elderId 过滤的 doctorOrderExecution 记录(与执行医嘱模块同一份接口),
+ *          默认展示当日 planDate 的所有执行/待执行/跳过记录。
+ *
+ * 复用点:
+ *  - 数据接口:getDoctorOrderExecutionPage(API 层 .then 已 fromTs 归一字符串)
+ *  - 打印样式:沿用 execute-doctor-order/index.vue 的 A4 竖版 colgroup
+ *  - 打印指令:项目 main.ts 全局注册了 vue3-print-nb(v-print)
+ *
+ * Props(由父组件调用 open() 注入;本组件不直接用 props,方便父组件不用双向绑值):
+ *  - elderId: 长者 id(必填)
+ *  - elderName: 长者姓名(用于表头与表格空值兜底)
+ *  - bedName / elderAge: 表格空值兜底用
+ *  - orderType?: 1=长期 2=临时 undefined=全部,用于过滤与表头「类型」字段
+ *  - title?: 弹窗标题,不传则按 orderType 推断
+ */
+import { ref, computed } from 'vue'
+import dayjs from 'dayjs'
+import { useUserStore } from '@/store/modules/user'
+import { formatDate } from '@/utils/formatTime'
+import {
+  getDoctorOrderExecutionPage,
+  MED_FREQUENCY_OPTIONS,
+  MED_TIME_OPTIONS,
+  MED_ROUTE_OPTIONS,
+  getMedCn,
+  type DoctorOrderExecution
+} from '@/api/elderly/elder/bed-change'
+
+const userStore = useUserStore()
+const tenantName = computed(() => userStore.tenant?.name || '')
+
+const visible = ref(false)
+const loading = ref(false)
+const list = ref<DoctorOrderExecution[]>([])
+
+// 弹窗入参(按需在 open() 里赋值)
+let elderId: number | undefined
+let elderName = ''
+let bedName = ''
+let elderAge: number | string | undefined
+let orderType: number | undefined
+let customTitle = ''
+const execDate = ref(formatDate(new Date(), 'YYYY-MM-DD'))
+
+const dialogTitle = computed(() => {
+  if (customTitle) return customTitle
+  if (orderType === 1) return '长期医嘱执行记录'
+  if (orderType === 2) return '临时医嘱执行记录'
+  return '医嘱执行记录'
+})
+
+const orderTypeText = computed(() => {
+  if (orderType === 1) return '长期医嘱'
+  if (orderType === 2) return '临时医嘱'
+  return '全部'
+})
+
+/** v-print 配置(vue3-print-nb 期望 {id, popTitle, ...} 对象) */
+const printConfig = {
+  id: 'execRecordPrintArea',
+  popTitle: dialogTitle,
+  extraCss: '',
+  preview: false,
+  zIndex: 20002
+}
+
+const freqCn = (v?: string) => getMedCn(MED_FREQUENCY_OPTIONS, v)
+const timeCn = (v?: string) => getMedCn(MED_TIME_OPTIONS, v)
+const routeCn = (v?: string) => getMedCn(MED_ROUTE_OPTIONS, v)
+const statusText = (s?: number) => (s === 1 ? '已执行' : s === 2 ? '已跳过' : '待执行')
+
+/**
+ * 把时间拆成 {date:'YYYY-MM-DD', time:'HH:mm'} 两段用于上下两行展示
+ * - 空值返回 {date:'', time:''}
+ * - 兼容字符串/数字/已有格式化串(execution.planTime/execTime 是字符串)
+ */
+const formatDateTimeSplit = (val?: any): { date: string; time: string } => {
+  if (val === null || val === undefined || val === '') return { date: '', time: '' }
+  let s = ''
+  if (typeof val === 'string') {
+    s = val
+  } else if (typeof val === 'number') {
+    const d = dayjs(val)
+    if (!d.isValid()) return { date: '', time: '' }
+    return { date: d.format('YYYY-MM-DD'), time: d.format('HH:mm') }
+  }
+  const m = s.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2})/)
+  if (m) return { date: m[1], time: m[2] }
+  return { date: s, time: '' }
+}
+
+/** 测试假数据:执行记录接口失败时填充(项目惯例) */
+const buildMockExecutionRecords = (): DoctorOrderExecution[] => {
+  const today = execDate.value
+  const base: DoctorOrderExecution[] = [
+    {
+      id: 7001,
+      orderType: 1,
+      orderEntryId: 9001,
+      elderId: elderId!,
+      elderName: elderName || '长者A',
+      bedName: bedName || '1号楼-201-01',
+      elderAge: elderAge || 78,
+      orderContent: '按医嘱服药',
+      frequency: 'tid',
+      medTime: 'pc',
+      route: 'po',
+      planTime: `${today} 08:00:00`,
+      execTime: `${today} 08:05:00`,
+      execNurseSign: '张护士',
+      status: 1,
+      remark: '',
+      tenantId: userStore.orgTenantId as any
+    },
+    {
+      id: 7002,
+      orderType: 1,
+      orderEntryId: 9001,
+      elderId: elderId!,
+      elderName: elderName || '长者A',
+      bedName: bedName || '1号楼-201-01',
+      elderAge: elderAge || 78,
+      orderContent: '按医嘱服药',
+      frequency: 'tid',
+      medTime: 'pc',
+      route: 'po',
+      planTime: `${today} 12:00:00`,
+      execTime: '',
+      execNurseSign: '',
+      status: 0,
+      remark: '',
+      tenantId: userStore.orgTenantId as any
+    },
+    {
+      id: 7003,
+      orderType: 2,
+      orderEntryId: 9002,
+      elderId: elderId!,
+      elderName: elderName || '长者A',
+      bedName: bedName || '1号楼-201-01',
+      elderAge: elderAge || 78,
+      orderContent: '临时加测血压',
+      frequency: 'st',
+      medTime: 'st',
+      route: 'po',
+      planTime: `${today} 14:30:00`,
+      execTime: `${today} 14:35:00`,
+      execNurseSign: '李护士',
+      status: 1,
+      remark: '已测 130/85',
+      tenantId: userStore.orgTenantId as any
+    }
+  ]
+  return orderType
+    ? base.filter((b) => b.orderType === orderType)
+    : base
+}
+
+/**
+ * 拉取该长者当日的执行记录
+ * - 默认 planDate=今日(与执行医嘱模块的"当日汇总"保持一致)
+ * - 接口失败时填充 mock,避免空弹窗
+ * - 后端 `doctorOrderExecution/page` 接口可能未实现 elderId 字段过滤(用户 9-08 反馈
+ *   "切换长者时表格残留前一位"),所以前端在拿到数据后再按 elderId 兜底过滤一次,
+ *   保证无论后端是否过滤,弹窗永远只显示当前长者的执行记录。
+ */
+const fetchList = async () => {
+  if (!elderId) {
+    list.value = []
+    return
+  }
+  loading.value = true
+  try {
+    const params: Recordable = {
+      pageNo: 1,
+      pageSize: 1000,
+      tenantIds: userStore.orgTenantId,
+      elderId,
+      planDate: execDate.value
+    }
+    if (orderType) params.orderType = orderType
+    const data = await getDoctorOrderExecutionPage(params)
+    // 前端兜底过滤:只保留 elderId 匹配的记录(Number() 兼容后端返回 string/number)
+    const targetElderId = Number(elderId)
+    list.value = (data.list || []).filter(
+      (r: any) => r.elderId !== undefined && r.elderId !== null && Number(r.elderId) === targetElderId
+    ) as DoctorOrderExecution[]
+  } catch (e) {
+    console.warn('[医嘱执行记录] 拉取失败,已使用测试假数据。', e)
+    list.value = buildMockExecutionRecords()
+  } finally {
+    loading.value = false
+  }
+}
+
+/**
+ * 打开弹窗
+ * @param row 行数据(必传 elderId,其余字段兜底用)
+ * @param options.title? 自定义标题
+ * @param options.orderType? 1=长期 2=临时 undefined=全部
+ */
+const open = async (
+  row: {
+    elderId: number
+    elderName?: string
+    bedName?: string
+    elderAge?: number | string
+  },
+  options: { title?: string; orderType?: number } = {}
+) => {
+  elderId = row.elderId
+  elderName = row.elderName || ''
+  bedName = row.bedName || ''
+  elderAge = row.elderAge
+  orderType = options.orderType
+  customTitle = options.title || ''
+  // 每次打开刷新执行日期(跨日场景)
+  execDate.value = formatDate(new Date(), 'YYYY-MM-DD')
+  // 立即清空旧数据,避免"切换长者时表格残留前一位"(用户 9-08 反馈)
+  list.value = []
+  visible.value = true
+  await fetchList()
+}
+
+defineExpose({ open })
+</script>
+
+<style lang="scss" scoped>
+/* 沿用 execute-doctor-order/index.vue 的 A4 竖版打印范式:
+   - 容器 width:100% 弹填 95% dialog
+   - @page A4 portrait
+   - colgroup 用 % 而非 mm
+   - #555 细线 */
+.exec-print {
+  width: 100%;
+  padding: 4px 0;
+}
+.doc-title {
+  text-align: center;
+  font-size: 20px;
+  font-weight: 600;
+  margin: 0 0 12px;
+}
+.info-row {
+  display: flex;
+  flex-wrap: wrap;
+  margin-bottom: 10px;
+  font-size: 13px;
+  line-height: 1.7;
+}
+.info-cell {
+  flex: 1 1 25%;
+  min-width: 180px;
+  padding-right: 8px;
+  box-sizing: border-box;
+}
+.info-cell .lbl {
+  font-weight: 600;
+  margin-right: 2px;
+}
+.order-table {
+  width: 100%;
+  border-collapse: collapse;
+  table-layout: fixed;
+  font-size: 12px;
+}
+.order-table th,
+.order-table td {
+  border: 1px solid #555;
+  padding: 4px 6px;
+  /* 默认全部居中:序号 / 年龄 / 频次 / 给药时间 / 途径 / 时间 / 护士 / 状态
+     再用 nth-child 单独把 2/3/5/13 列改回左对齐(长者/床位/医嘱内容/备注) */
+  text-align: center;
+  vertical-align: middle;
+  word-break: break-all;
+}
+/* 左对齐列:长者(2) / 床位(3) / 医嘱内容(5) / 备注(13) */
+.order-table td:nth-child(2),
+.order-table td:nth-child(3),
+.order-table td:nth-child(5),
+.order-table td:nth-child(13) {
+  text-align: left;
+}
+.order-table thead th {
+  background: #f5f7fa;
+  text-align: center;
+  font-weight: 600;
+}
+.cell-value {
+  display: block;
+}
+.cell-value.pre {
+  white-space: pre-wrap;
+}
+.empty-tip {
+  text-align: center;
+  color: #909399;
+  padding: 24px 0;
+}
+.datetime {
+  display: flex;
+  flex-direction: column;
+  line-height: 1.3;
+  /* 防止 "2026-09-08" 在 "-" 处被 word-break 拆成两行(单元格窄时常见) */
+  white-space: nowrap;
+}
+.datetime .d-line {
+  font-size: 12px;
+}
+.datetime .t-line {
+  font-size: 12px;
+  color: #606266;
+}
+
+/* 打印样式:v-print 复制到 iframe 渲染,A4 竖版,容器自适应 */
+@page {
+  size: A4 portrait;
+  margin: 8mm;
+}
+@media print {
+  .exec-print {
+    width: 100% !important;
+    padding: 0 !important;
+  }
+  .order-table thead {
+    display: table-header-group;
+  }
+  .order-table tr {
+    page-break-inside: avoid;
+  }
+}
+</style>

+ 0 - 10
src/views/elderly/elder/doctor-orders/long-doctor-order/Form.vue

@@ -152,11 +152,6 @@
             <el-input v-model="row.doctorSign" placeholder="医生签名" :disabled="isDetail" />
           </template>
         </el-table-column>
-        <el-table-column label="护士签名" width="130">
-          <template #default="{ row }">
-            <el-input v-model="row.nurseSign" placeholder="护士签名" :disabled="isDetail" />
-          </template>
-        </el-table-column>
         <el-table-column label="停止时间" width="190">
           <template #default="{ row }">
             <el-date-picker
@@ -179,11 +174,6 @@
             />
           </template>
         </el-table-column>
-        <el-table-column label="停止护士签名" width="140">
-          <template #default="{ row }">
-            <el-input v-model="row.stopNurseSign" placeholder="停止护士签名" :disabled="isDetail" />
-          </template>
-        </el-table-column>
         <el-table-column label="备注" min-width="160">
           <template #default="{ row }">
             <el-input

+ 13 - 18
src/views/elderly/elder/doctor-orders/long-doctor-order/Print.vue

@@ -14,22 +14,21 @@
         </div>
       </div>
 
-      <!-- 医嘱表(屏幕预览用 % 列宽,弹填 dialog 95% 容器;打印纸张 A4 portrait 时按父容器 100% 等比缩放,
-     比例与原 195mm 设计一致:6/18/50/10/10/11/12/12/18/12/12/24 = 3.08/9.23/25.64/5.13/5.13/5.64/6.15/6.15/9.23/6.15/6.15/12.31 %) -->
+      <!-- 医嘱表(屏幕预览用 % 列宽,弹填 dialog 95% 容器;打印纸张 A4 portrait 时按父容器 100% 等比缩放)。
+           9-08 调整:去除「护士签名/停止护士签名」两列后,由原 12 列变 10 列,
+           释放的 ~12.3% 重新分配到医嘱内容、备注、医嘱时间/停止时间等列。 -->
       <table class="order-table">
         <colgroup>
-          <col style="width: 3.08%" />
-          <col style="width: 9.23%" />
-          <col style="width: 25.64%" />
-          <col style="width: 5.13%" />
-          <col style="width: 5.13%" />
-          <col style="width: 5.64%" />
-          <col style="width: 6.15%" />
-          <col style="width: 6.15%" />
-          <col style="width: 9.23%" />
-          <col style="width: 6.15%" />
-          <col style="width: 6.15%" />
-          <col style="width: 12.31%" />
+          <col style="width: 3.5%" />
+          <col style="width: 10.5%" />
+          <col style="width: 30%" />
+          <col style="width: 6%" />
+          <col style="width: 6%" />
+          <col style="width: 6%" />
+          <col style="width: 7%" />
+          <col style="width: 10.5%" />
+          <col style="width: 7%" />
+          <col style="width: 13.5%" />
         </colgroup>
         <thead>
           <tr>
@@ -40,10 +39,8 @@
             <th>给药时间</th>
             <th>途径</th>
             <th>医生<br />签名</th>
-            <th>护士<br />签名</th>
             <th>停止时间</th>
             <th>停止<br />医生签名</th>
-            <th>停止<br />护士签名</th>
             <th>备注</th>
           </tr>
         </thead>
@@ -71,7 +68,6 @@
               <span class="cell-value">{{ getMedCn(MED_ROUTE_OPTIONS, item.route) }}</span>
             </td>
             <td>{{ item.doctorSign || '' }}</td>
-            <td>{{ item.nurseSign || '' }}</td>
             <td>
               <div class="datetime">
                 <div class="d-line">{{ formatDateTimeSplit(item.stopTime).date }}</div>
@@ -79,7 +75,6 @@
               </div>
             </td>
             <td>{{ item.stopDoctorSign || '' }}</td>
-            <td>{{ item.stopNurseSign || '' }}</td>
             <td>
               <span class="cell-value pre">{{ item.remark || '' }}</span>
             </td>

+ 18 - 1
src/views/elderly/elder/doctor-orders/long-doctor-order/index.vue

@@ -49,7 +49,7 @@
       :data="list"
       :columns="LongDoctorOrderColumns"
       :queryParams="queryParams"
-      :op-width="240"
+      :op-width="250"
       :show-edit-condition="() => false"
       :show-delete-condition="() => false"
       :show-detail-condition="() => false"
@@ -71,6 +71,9 @@
           >删除</el-button
         >
         <el-button link type="primary" @click="openPrint(scope)">打印</el-button>
+        <el-button link type="warning" @click="openExecutionRecord(scope)"
+          >执行记录</el-button
+        >
       </template>
     </Table2>
 
@@ -88,6 +91,9 @@
 
   <!-- 打印(详情页) -->
   <Print ref="printRef" />
+
+  <!-- 执行记录(按长者过滤,弹窗 + 打印复用) -->
+  <ExecutionRecordDialog ref="execRecordRef" />
 </template>
 
 <script lang="ts" setup>
@@ -99,6 +105,7 @@ import {
 import { LongDoctorOrderColumns } from './columns'
 import Form from './Form.vue'
 import Print from './Print.vue'
+import ExecutionRecordDialog from '../execute-doctor-order/ExecutionRecordDialog.vue'
 import { useUserStore } from '@/store/modules/user'
 
 defineOptions({ name: 'LongDoctorOrder' })
@@ -317,6 +324,16 @@ const openPrint = (row: any = {}) => {
   printRef.value.open(row)
 }
 
+/** 执行记录:按当前行长者过滤,复用执行医嘱模块的弹窗 + 打印样式 */
+const execRecordRef = ref()
+const openExecutionRecord = (row: any = {}) => {
+  if (!row?.elderId) {
+    message.error('当前行缺少长者信息')
+    return
+  }
+  execRecordRef.value.open(row, { orderType: 1 })
+}
+
 /** 删除 */
 const handleDelete = async (id: number) => {
   try {

+ 18 - 1
src/views/elderly/elder/doctor-orders/short-doctor-order/index.vue

@@ -49,7 +49,7 @@
       :data="list"
       :columns="ShortDoctorOrderColumns"
       :queryParams="queryParams"
-      :op-width="240"
+      :op-width="250"
       :show-edit-condition="() => false"
       :show-delete-condition="() => false"
       :show-detail-condition="() => false"
@@ -71,6 +71,9 @@
           >删除</el-button
         >
         <el-button link type="primary" @click="openPrint(scope)">打印</el-button>
+        <el-button link type="warning" @click="openExecutionRecord(scope)"
+          >执行记录</el-button
+        >
       </template>
     </Table2>
 
@@ -88,6 +91,9 @@
 
   <!-- 打印(详情页) -->
   <Print ref="printRef" />
+
+  <!-- 执行记录(按长者过滤,弹窗 + 打印复用) -->
+  <ExecutionRecordDialog ref="execRecordRef" />
 </template>
 
 <script lang="ts" setup>
@@ -98,6 +104,7 @@ import {
 import { ShortDoctorOrderColumns } from './columns'
 import Form from './Form.vue'
 import Print from './Print.vue'
+import ExecutionRecordDialog from '../execute-doctor-order/ExecutionRecordDialog.vue'
 import { useUserStore } from '@/store/modules/user'
 
 defineOptions({ name: 'ShortDoctorOrder' })
@@ -214,6 +221,16 @@ const openPrint = (row: any = {}) => {
   printRef.value.open(row)
 }
 
+/** 执行记录:按当前行长者过滤,复用执行医嘱模块的弹窗 + 打印样式 */
+const execRecordRef = ref()
+const openExecutionRecord = (row: any = {}) => {
+  if (!row?.elderId) {
+    message.error('当前行缺少长者信息')
+    return
+  }
+  execRecordRef.value.open(row, { orderType: 2 })
+}
+
 /** 删除 */
 const handleDelete = async (id: number) => {
   try {