|
|
@@ -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>
|