|
@@ -0,0 +1,1000 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <!-- 搜索 -->
|
|
|
|
|
+ <ContentWrap>
|
|
|
|
|
+ <el-form
|
|
|
|
|
+ class="-mb-15px"
|
|
|
|
|
+ :model="queryParams"
|
|
|
|
|
+ ref="queryFormRef"
|
|
|
|
|
+ :inline="true"
|
|
|
|
|
+ label-width="80px"
|
|
|
|
|
+ >
|
|
|
|
|
+ <TenantSelect v-model="queryParams.tenantIds" placeholder="请选择机构名称" prop="tenantIds" />
|
|
|
|
|
+ <el-form-item label="长者姓名" prop="elderName">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ @keyup.enter="handleQuery"
|
|
|
|
|
+ v-model="queryParams.elderName"
|
|
|
|
|
+ placeholder="请输入长者姓名"
|
|
|
|
|
+ class="!w-200px"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="执行日期" prop="execDate">
|
|
|
|
|
+ <el-date-picker
|
|
|
|
|
+ v-model="queryParams.execDate"
|
|
|
|
|
+ type="date"
|
|
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
|
|
+ format="YYYY-MM-DD"
|
|
|
|
|
+ placeholder="选择执行日期"
|
|
|
|
|
+ style="width: 180px"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="医嘱类型" prop="orderType">
|
|
|
|
|
+ <el-select v-model="queryParams.orderType" placeholder="全部" clearable class="!w-160px">
|
|
|
|
|
+ <el-option label="长期医嘱" :value="1" />
|
|
|
|
|
+ <el-option label="临时医嘱" :value="2" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="状态" prop="status">
|
|
|
|
|
+ <el-select v-model="queryParams.status" placeholder="全部" clearable class="!w-160px">
|
|
|
|
|
+ <el-option label="待执行" :value="0" />
|
|
|
|
|
+ <el-option label="已执行" :value="1" />
|
|
|
|
|
+ <el-option label="已跳过" :value="2" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button @click="handleQuery"> <Icon icon="ep:search" class="mr-5px" /> 查询 </el-button>
|
|
|
|
|
+ <el-button @click="resetQuery"> <Icon icon="ep:refresh" class="mr-5px" /> 重置 </el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </ContentWrap>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 列表 -->
|
|
|
|
|
+ <ContentWrap>
|
|
|
|
|
+ <el-button type="primary" plain @click="openPrintDialog('all')" style="margin-bottom: 10px">
|
|
|
|
|
+ <Icon icon="ep:printer" class="mr-5px" /> 打印执行单
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table v-loading="loading" :data="pagedList" border stripe>
|
|
|
|
|
+ <el-table-column label="长者" prop="elderName" width="110" fixed />
|
|
|
|
|
+ <el-table-column label="床位" prop="bedName" width="180" />
|
|
|
|
|
+ <el-table-column label="年龄" prop="elderAge" width="80" align="center" />
|
|
|
|
|
+ <el-table-column label="医嘱内容" prop="orderContent" min-width="220" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="频次" width="90" align="center">
|
|
|
|
|
+ <template #default="{ row }">{{ freqCn(row.frequency) }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="给药时间" width="90" align="center">
|
|
|
|
|
+ <template #default="{ row }">{{ timeCn(row.medTime) }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="途径" width="90" align="center">
|
|
|
|
|
+ <template #default="{ row }">{{ routeCn(row.route) }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="计划执行时间" width="140">
|
|
|
|
|
+ <template #default="{ row }">{{ formatDateTimeShort(row.planTime) }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="执行护士" prop="execNurseSign" width="100" />
|
|
|
|
|
+ <el-table-column label="实际执行时间" width="160">
|
|
|
|
|
+ <template #default="{ row }">{{ row.execTime ? formatDateTimeShort(row.execTime) : '' }}</template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="状态" width="90" align="center">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tag :type="statusType(row.status)" size="small">{{ statusText(row.status) }}</el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="操作" width="220" align="center" fixed="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-if="row.status === 0"
|
|
|
|
|
+ link
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="openExecute(row, false)"
|
|
|
|
|
+ v-hasPermi="['doctor-order-execute:execute']"
|
|
|
|
|
+ >执行</el-button
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-if="row.status === 0"
|
|
|
|
|
+ link
|
|
|
|
|
+ type="danger"
|
|
|
|
|
+ @click="handleSkip(row)"
|
|
|
|
|
+ v-hasPermi="['doctor-order-execute:skip']"
|
|
|
|
|
+ >跳过</el-button
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-button v-if="row.status !== 0" link type="primary" @click="openExecute(row, true)"
|
|
|
|
|
+ >查看</el-button
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ link
|
|
|
|
|
+ type="warning"
|
|
|
|
|
+ @click="openPrintDialog('single', row)"
|
|
|
|
|
+ v-hasPermi="['doctor-order-execute:print']"
|
|
|
|
|
+ >打印</el-button
|
|
|
|
|
+ >
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+
|
|
|
|
|
+ <Pagination
|
|
|
|
|
+ :total="total"
|
|
|
|
|
+ v-model:page="pageNo"
|
|
|
|
|
+ v-model:limit="pageSize"
|
|
|
|
|
+ @pagination="() => {}"
|
|
|
|
|
+ />
|
|
|
|
|
+ </ContentWrap>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 执行弹窗 -->
|
|
|
|
|
+ <ExecuteDialog ref="execRef" @success="onExecuteSuccess" />
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 跳过原因弹窗 -->
|
|
|
|
|
+ <el-dialog
|
|
|
|
|
+ v-model="skipVisible"
|
|
|
|
|
+ title="跳过执行"
|
|
|
|
|
+ width="500px"
|
|
|
|
|
+ :close-on-click-modal="false"
|
|
|
|
|
+ append-to-body
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-alert
|
|
|
|
|
+ :title="`${skipRow.elderName || ''} · ${skipRow.bedName || ''}`"
|
|
|
|
|
+ :description="skipRow.orderContent || ''"
|
|
|
|
|
+ type="warning"
|
|
|
|
|
+ :closable="false"
|
|
|
|
|
+ class="mb-16px"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-form label-width="90px">
|
|
|
|
|
+ <el-form-item label="计划时间">{{ skipRow.planTime || '-' }}</el-form-item>
|
|
|
|
|
+ <el-form-item label="跳过原因" required>
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="skipForm.remark"
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ :rows="3"
|
|
|
|
|
+ maxlength="200"
|
|
|
|
|
+ show-word-limit
|
|
|
|
|
+ placeholder="请填写本次不执行的原因,如:长者外出 / 拒采 / 当日禁食转院…"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <el-button @click="skipVisible = false">取消</el-button>
|
|
|
|
|
+ <el-button type="primary" :loading="skipping" @click="confirmSkip">确认跳过</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 打印弹窗:打印区在可见状态下由 v-print 克隆,避免离屏 div 导致空白页 -->
|
|
|
|
|
+ <el-dialog
|
|
|
|
|
+ v-model="printDialogVisible"
|
|
|
|
|
+ :title="printMode === 'all' ? '医嘱执行单' : '医嘱执行单(单条)'"
|
|
|
|
|
+ width="95%"
|
|
|
|
|
+ append-to-body
|
|
|
|
|
+ :close-on-click-modal="false"
|
|
|
|
|
+ >
|
|
|
|
|
+ <!-- 汇总(当日全量):A4 竖版,弹填 95% dialog,列宽按 %,日期时间上下两行,去缩写 -->
|
|
|
|
|
+ <div v-if="printMode === 'all'" id="execPrintArea" class="exec-print" v-loading="loading">
|
|
|
|
|
+ <h2 class="doc-title">医嘱执行单</h2>
|
|
|
|
|
+ <div class="info-row">
|
|
|
|
|
+ <div class="info-cell"><span class="lbl">机构:</span>{{ tenantName || '-' }}</div>
|
|
|
|
|
+ <div class="info-cell"><span class="lbl">执行日期:</span>{{ queryParams.execDate }}</div>
|
|
|
|
|
+ <div class="info-cell"
|
|
|
|
|
+ ><span class="lbl">长者:</span>{{ queryParams.elderName || '全部' }}</div
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="info-cell"><span class="lbl">类型:</span>{{ orderTypeText }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <table class="order-table">
|
|
|
|
|
+ <colgroup>
|
|
|
|
|
+ <col style="width: 3.09%" />
|
|
|
|
|
+ <col style="width: 8.25%" />
|
|
|
|
|
+ <col style="width: 8.25%" />
|
|
|
|
|
+ <col style="width: 5.67%" />
|
|
|
|
|
+ <col style="width: 18.04%" />
|
|
|
|
|
+ <col style="width: 5.67%" />
|
|
|
|
|
+ <col style="width: 5.67%" />
|
|
|
|
|
+ <col style="width: 5.67%" />
|
|
|
|
|
+ <col style="width: 8.25%" />
|
|
|
|
|
+ <col style="width: 7.22%" />
|
|
|
|
|
+ <col style="width: 8.25%" />
|
|
|
|
|
+ <col style="width: 5.67%" />
|
|
|
|
|
+ <col style="width: 10.31%" />
|
|
|
|
|
+ </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-for="(item, idx) in list" :key="idx">
|
|
|
|
|
+ <td>{{ idx + 1 }}</td>
|
|
|
|
|
+ <td>{{ item.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>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 单条(A4 竖版,左字段 / 右字段 两列网格,弹填 95% dialog) -->
|
|
|
|
|
+ <div v-else id="singlePrintArea" class="exec-print" v-loading="loading">
|
|
|
|
|
+ <div v-if="singlePrintRow">
|
|
|
|
|
+ <h2 class="doc-title">医嘱执行单(单条)</h2>
|
|
|
|
|
+ <div class="info-row">
|
|
|
|
|
+ <div class="info-cell"><span class="lbl">机构:</span>{{ tenantName || '-' }}</div>
|
|
|
|
|
+ <div class="info-cell"><span class="lbl">执行日期:</span>{{ queryParams.execDate }}</div>
|
|
|
|
|
+ <div class="info-cell"
|
|
|
|
|
+ ><span class="lbl">打印时间:</span>{{ formatDateTimeSplit(printNow).date }}
|
|
|
|
|
+ {{ formatDateTimeSplit(printNow).time }}</div
|
|
|
|
|
+ >
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <table class="order-table single-info">
|
|
|
|
|
+ <colgroup>
|
|
|
|
|
+ <col style="width: 10%" />
|
|
|
|
|
+ <col style="width: 40%" />
|
|
|
|
|
+ <col style="width: 10%" />
|
|
|
|
|
+ <col style="width: 40%" />
|
|
|
|
|
+ </colgroup>
|
|
|
|
|
+ <tbody>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th>长者</th>
|
|
|
|
|
+ <td>{{ singlePrintRow.elderName }}</td>
|
|
|
|
|
+ <th>床位</th>
|
|
|
|
|
+ <td>{{ singlePrintRow.bedName }}</td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th>年龄</th>
|
|
|
|
|
+ <td>{{ singlePrintRow.elderAge || '' }}</td>
|
|
|
|
|
+ <th>状态</th>
|
|
|
|
|
+ <td>{{ statusText(singlePrintRow.status) }}</td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th>频次</th>
|
|
|
|
|
+ <td>{{ freqCn(singlePrintRow.frequency) }}</td>
|
|
|
|
|
+ <th>给药时间</th>
|
|
|
|
|
+ <td>{{ timeCn(singlePrintRow.medTime) }}</td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th>途径</th>
|
|
|
|
|
+ <td>{{ routeCn(singlePrintRow.route) }}</td>
|
|
|
|
|
+ <th>执行护士</th>
|
|
|
|
|
+ <td>{{ singlePrintRow.execNurseSign || '-' }}</td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th>计划时间</th>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <div class="datetime">
|
|
|
|
|
+ <div class="d-line">{{ formatDateTimeSplit(singlePrintRow.planTime).date }}</div>
|
|
|
|
|
+ <div class="t-line">{{ formatDateTimeSplit(singlePrintRow.planTime).time }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <th>实际时间</th>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <div class="datetime">
|
|
|
|
|
+ <div class="d-line">{{ formatDateTimeSplit(singlePrintRow.execTime).date }}</div>
|
|
|
|
|
+ <div class="t-line">{{ formatDateTimeSplit(singlePrintRow.execTime).time }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th>医嘱内容</th>
|
|
|
|
|
+ <td colspan="3"
|
|
|
|
|
+ ><span class="cell-value pre">{{ singlePrintRow.orderContent }}</span></td
|
|
|
|
|
+ >
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ <tr v-if="singlePrintRow.remark">
|
|
|
|
|
+ <th>{{ singlePrintRow.status === 2 ? '跳过原因' : '备注' }}</th>
|
|
|
|
|
+ <td colspan="3"
|
|
|
|
|
+ ><span class="cell-value pre">{{ singlePrintRow.remark }}</span></td
|
|
|
|
|
+ >
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </tbody>
|
|
|
|
|
+ </table>
|
|
|
|
|
+ <div class="sign-row">
|
|
|
|
|
+ <span>执行护士签名:__________________</span>
|
|
|
|
|
+ <span>核对护士签名:__________________</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <el-button @click="printDialogVisible = false">关闭</el-button>
|
|
|
|
|
+ <el-button v-if="printMode === 'all'" type="primary" v-print="printAll">打印</el-button>
|
|
|
|
|
+ <el-button v-else type="primary" v-print="printOne">打印</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script lang="ts" setup>
|
|
|
|
|
+import { ref, reactive, computed } from 'vue'
|
|
|
|
|
+import dayjs from 'dayjs'
|
|
|
|
|
+import { useMessage } from '@/hooks/web/useMessage'
|
|
|
|
|
+import { useUserStore } from '@/store/modules/user'
|
|
|
|
|
+import { formatDate } from '@/utils/formatTime'
|
|
|
|
|
+import {
|
|
|
|
|
+ getLongDoctorOrderPage,
|
|
|
|
|
+ getShortDoctorOrderPage,
|
|
|
|
|
+ getDoctorOrderExecutionPage,
|
|
|
|
|
+ executeDoctorOrder,
|
|
|
|
|
+ buildExecutionPlanTimes,
|
|
|
|
|
+ MED_FREQUENCY_OPTIONS,
|
|
|
|
|
+ MED_TIME_OPTIONS,
|
|
|
|
|
+ MED_ROUTE_OPTIONS,
|
|
|
|
|
+ getMedCn,
|
|
|
|
|
+ fromTs,
|
|
|
|
|
+ type DoctorOrderExecution
|
|
|
|
|
+} from '@/api/elderly/elder/bed-change'
|
|
|
|
|
+import ExecuteDialog from './ExecuteDialog.vue'
|
|
|
|
|
+
|
|
|
|
|
+defineOptions({ name: 'DoctorOrderExecute' })
|
|
|
|
|
+
|
|
|
|
|
+const message = useMessage()
|
|
|
|
|
+const userStore = useUserStore()
|
|
|
|
|
+const tenantName = computed(() => userStore.tenant?.name || '')
|
|
|
|
|
+
|
|
|
|
|
+/** 打印用:查询类型的中文标签 */
|
|
|
|
|
+const orderTypeText = computed(() => {
|
|
|
|
|
+ const t = queryParams.orderType
|
|
|
|
|
+ if (t === 1) return '长期医嘱'
|
|
|
|
|
+ if (t === 2) return '临时医嘱'
|
|
|
|
|
+ return '全部'
|
|
|
|
|
+})
|
|
|
|
|
+/** 打印用:当前打印时刻(每次打开打印弹窗刷新) */
|
|
|
|
|
+const printNow = ref(formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss'))
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 打印用:把时间拆成 {date:'YYYY-MM-DD', time:'HH:mm'} 两段用于上下两行展示
|
|
|
|
|
+ * - 空值返回 {date:'', time:''}
|
|
|
|
|
+ * - 兼容字符串/数字/已有格式化串(执行记录的 planTime/execTime 是 ISO 字符串)
|
|
|
|
|
+ */
|
|
|
|
|
+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: '' }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** v-print 配置(vue3-print-nb 期望传 {id, popTitle, ...} 对象,不是字符串选择器) */
|
|
|
|
|
+const printAll = {
|
|
|
|
|
+ id: 'execPrintArea',
|
|
|
|
|
+ popTitle: '医嘱执行单',
|
|
|
|
|
+ extraCss: '',
|
|
|
|
|
+ preview: false,
|
|
|
|
|
+ zIndex: 20002
|
|
|
|
|
+}
|
|
|
|
|
+const printOne = {
|
|
|
|
|
+ id: 'singlePrintArea',
|
|
|
|
|
+ popTitle: '医嘱执行单(单条)',
|
|
|
|
|
+ extraCss: '',
|
|
|
|
|
+ preview: false,
|
|
|
|
|
+ zIndex: 20002
|
|
|
|
|
+}
|
|
|
|
|
+/** 打印弹窗:可见状态下由 v-print 克隆,避免离屏 div 空白页 */
|
|
|
|
|
+const printDialogVisible = ref(false)
|
|
|
|
|
+const printMode = ref<'all' | 'single'>('all')
|
|
|
|
|
+const singlePrintRow = ref<any>(null)
|
|
|
|
|
+/** 打开打印弹窗:all=当日汇总,single=当前行 */
|
|
|
|
|
+const openPrintDialog = (mode: 'all' | 'single', row?: any) => {
|
|
|
|
|
+ printMode.value = mode
|
|
|
|
|
+ singlePrintRow.value = mode === 'single' ? { ...row } : null
|
|
|
|
|
+ printNow.value = formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
+ printDialogVisible.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+const list = ref<any[]>([])
|
|
|
|
|
+const pageNo = ref(1)
|
|
|
|
|
+const pageSize = ref(20)
|
|
|
|
|
+const total = computed(() => list.value.length)
|
|
|
|
|
+const pagedList = computed(() => {
|
|
|
|
|
+ const start = (pageNo.value - 1) * pageSize.value
|
|
|
|
|
+ return list.value.slice(start, start + pageSize.value)
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const queryParams = reactive({
|
|
|
|
|
+ tenantIds: userStore.orgTenantId as any,
|
|
|
|
|
+ elderName: undefined as string | undefined,
|
|
|
|
|
+ execDate: formatDate(new Date(), 'YYYY-MM-DD'),
|
|
|
|
|
+ orderType: undefined as number | undefined,
|
|
|
|
|
+ status: undefined as number | undefined
|
|
|
|
|
+})
|
|
|
|
|
+const queryFormRef = ref()
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 执行瞬间的内存过渡缓存(不持久化)
|
|
|
|
|
+ * 业务状态的唯一真相源是后端 `doctorOrderExecution/page` 接口。前端不应把业务状态
|
|
|
|
|
+ * 持久化到 localStorage(换浏览器 / 清缓存 / 多端会不一致),刷新后状态应由后端回填。
|
|
|
|
|
+ * 这里只用于在「执行 / 跳过后」到「buildRows 重新拉取后端」之间的极短过渡期内,
|
|
|
|
|
+ * 防止 UI 闪回成「待执行」;页面刷新后本缓存即清空,此时以后端数据为准。
|
|
|
|
|
+ */
|
|
|
|
|
+const execMemory = new Map<
|
|
|
|
|
+ string,
|
|
|
|
|
+ { status: number; execTime: string; execNurseSign: string; remark: string }
|
|
|
|
|
+>()
|
|
|
|
|
+const setExecCache = (
|
|
|
|
|
+ k: string,
|
|
|
|
|
+ v: { status: number; execTime: string; execNurseSign: string; remark: string }
|
|
|
|
|
+) => {
|
|
|
|
|
+ execMemory.set(k, v)
|
|
|
|
|
+}
|
|
|
|
|
+const rowKey = (r: any) => `${r.orderType}-${r.orderEntryId}-${r.planTime}`
|
|
|
|
|
+
|
|
|
|
|
+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 statusType = (s?: number) => (s === 1 ? 'success' : s === 2 ? 'info' : 'warning')
|
|
|
|
|
+const statusText = (s?: number) => (s === 1 ? '已执行' : s === 2 ? '已跳过' : '待执行')
|
|
|
|
|
+
|
|
|
|
|
+/** 构造一条执行行 */
|
|
|
|
|
+const makeRow = (orderType: number, e: any, planTime: string, vo: any) => ({
|
|
|
|
|
+ orderType,
|
|
|
|
|
+ orderEntryId: e.id,
|
|
|
|
|
+ elderId: vo.elderId,
|
|
|
|
|
+ elderName: vo.elderName,
|
|
|
|
|
+ elderAge: vo.elderAge ?? '',
|
|
|
|
|
+ bedName: vo.bedName,
|
|
|
|
|
+ orderContent: e.orderContent,
|
|
|
|
|
+ frequency: e.frequency,
|
|
|
|
|
+ medTime: e.medTime,
|
|
|
|
|
+ route: e.route,
|
|
|
|
|
+ planTime,
|
|
|
|
|
+ status: 0,
|
|
|
|
|
+ execTime: '',
|
|
|
|
|
+ execNurseSign: '',
|
|
|
|
|
+ remark: '',
|
|
|
|
|
+ tenantId: vo.tenantId
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 归一 planTime 为 "YYYY-MM-DD HH:mm"(16 位,不带秒)。
|
|
|
|
|
+ * - 输入:字符串/数字时间戳/已有格式化串均可(先走 fromTs 归一字符串)
|
|
|
|
|
+ * - 长期医嘱:buildExecutionPlanTimes 输出 "HH:mm" → ${execDate} 拼后 16 位
|
|
|
|
|
+ * - 临时医嘱:fromTs(e.orderTime) 输出 "YYYY-MM-DD HH:mm:ss" → slice(0,16) 截到分钟
|
|
|
|
|
+ * 下游 rowKey / 表格 prop / 弹窗 / 打印(formatDateTimeSplit 正则已兼容)都按 16 位用
|
|
|
|
|
+ */
|
|
|
|
|
+const formatDateTimeShort = (val: any): string => {
|
|
|
|
|
+ const s = fromTs(val)
|
|
|
|
|
+ return s ? s.slice(0, 16) : ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 聚合:长期(在用) + 临时(当日) → 按频次展开 → 合并已执行记录 */
|
|
|
|
|
+const buildRows = async () => {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ pageNo.value = 1
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { tenantIds, execDate, elderName } = queryParams
|
|
|
|
|
+ // 1) 长期医嘱
|
|
|
|
|
+ let longOrders: any[] = []
|
|
|
|
|
+ try {
|
|
|
|
|
+ const lp = await getLongDoctorOrderPage({
|
|
|
|
|
+ pageNo: 1,
|
|
|
|
|
+ pageSize: 100,
|
|
|
|
|
+ tenantIds,
|
|
|
|
|
+ elderName: elderName || undefined
|
|
|
|
|
+ })
|
|
|
|
|
+ longOrders = lp.list || []
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.warn('[执行医嘱] 长期医嘱列表失败,使用测试数据', e)
|
|
|
|
|
+ longOrders = buildMockLong(execDate)
|
|
|
|
|
+ }
|
|
|
|
|
+ // 2) 临时医嘱
|
|
|
|
|
+ let shortOrders: any[] = []
|
|
|
|
|
+ try {
|
|
|
|
|
+ const sp = await getShortDoctorOrderPage({
|
|
|
|
|
+ pageNo: 1,
|
|
|
|
|
+ pageSize: 100,
|
|
|
|
|
+ tenantIds,
|
|
|
|
|
+ elderName: elderName || undefined
|
|
|
|
|
+ })
|
|
|
|
|
+ shortOrders = sp.list || []
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.warn('[执行医嘱] 临时医嘱列表失败,使用测试数据', e)
|
|
|
|
|
+ shortOrders = buildMockShort(execDate)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3) 展开执行行
|
|
|
|
|
+ const rows: any[] = []
|
|
|
|
|
+ longOrders.forEach((vo: any) => {
|
|
|
|
|
+ ;(vo.entries || []).forEach((e: any) => {
|
|
|
|
|
+ if (e.status !== 1) return // 仅在用医嘱需要执行
|
|
|
|
|
+ buildExecutionPlanTimes(e.frequency, e.medTime).forEach((t: string) => {
|
|
|
|
|
+ // planTime 归一为 YYYY-MM-DD HH:mm(与临时医嘱格式一致)
|
|
|
|
|
+ rows.push(makeRow(1, e, formatDateTimeShort(`${execDate} ${t}`), vo))
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ shortOrders.forEach((vo: any) => {
|
|
|
|
|
+ ;(vo.entries || []).forEach((e: any) => {
|
|
|
|
|
+ // orderTime 后端可能返回 13位毫戳数字/字符串(API 层 .then 已统一转字符串;
|
|
|
|
|
+ // 这里 fromTs 兜底,兼容任何来源混入的数字)
|
|
|
|
|
+ const plan = fromTs(e.orderTime)
|
|
|
|
|
+ if (!plan || plan.slice(0, 10) !== execDate) return
|
|
|
|
|
+ // planTime 归一为 YYYY-MM-DD HH:mm(截掉秒,分钟以下不显示)
|
|
|
|
|
+ rows.push(makeRow(2, e, formatDateTimeShort(plan), vo))
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 4) 拉取已执行记录,合并标记
|
|
|
|
|
+ // - 业务状态真相来自后端 `doctorOrderExecution/page` 接口
|
|
|
|
|
+ // - 两层匹配:
|
|
|
|
|
+ // ① 精确:key = `${orderType}-${orderEntryId}-${planTime}`(要求后端精确按前端传入的 planTime 写入)
|
|
|
|
|
+ // ② 兜底:按 (orderType, orderEntryId) 分桶,execution 按 createTime 升序,row 按 planTime 升序,
|
|
|
|
|
+ // 用各自桶内的索引对齐。解决"后端 planTime 写入不可靠"导致刷新后状态丢失的过渡问题。
|
|
|
|
|
+ // - 兜底命中会在控制台输出 DEBUG_MISMATCH 列表,便于让后端按前端传值精确修正 planTime 字段。
|
|
|
|
|
+ const execMap = new Map<string, any>()
|
|
|
|
|
+ const execByEntry = new Map<string, any[]>()
|
|
|
|
|
+ try {
|
|
|
|
|
+ const ep = await getDoctorOrderExecutionPage({
|
|
|
|
|
+ pageNo: 1,
|
|
|
|
|
+ pageSize: 1000,
|
|
|
|
|
+ tenantIds,
|
|
|
|
|
+ planDate: execDate
|
|
|
|
|
+ })
|
|
|
|
|
+ ;(ep.list || []).forEach((r: any) => {
|
|
|
|
|
+ if (r.orderType == null || r.orderEntryId == null || !r.planTime) {
|
|
|
|
|
+ console.warn('[执行医嘱] execution 记录缺少关键字段,已忽略:', r)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ const k = `${r.orderType}-${r.orderEntryId}-${r.planTime}`
|
|
|
|
|
+ execMap.set(k, r)
|
|
|
|
|
+ const ek = `${r.orderType}-${r.orderEntryId}`
|
|
|
|
|
+ if (!execByEntry.has(ek)) execByEntry.set(ek, [])
|
|
|
|
|
+ execByEntry.get(ek)!.push(r)
|
|
|
|
|
+ })
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.warn('[执行医嘱] 执行记录查询失败,全部按待执行展示', e)
|
|
|
|
|
+ }
|
|
|
|
|
+ execByEntry.forEach((arr) =>
|
|
|
|
|
+ arr.sort((a, b) => (a.createTime || '').localeCompare(b.createTime || ''))
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // rows 也按 entry 分桶并按 planTime 升序(用于索引兜底)
|
|
|
|
|
+ const rowsByEntry = new Map<string, any[]>()
|
|
|
|
|
+ rows.forEach((r) => {
|
|
|
|
|
+ const ek = `${r.orderType}-${r.orderEntryId}`
|
|
|
|
|
+ if (!rowsByEntry.has(ek)) rowsByEntry.set(ek, [])
|
|
|
|
|
+ rowsByEntry.get(ek)!.push(r)
|
|
|
|
|
+ })
|
|
|
|
|
+ rowsByEntry.forEach((arr) =>
|
|
|
|
|
+ arr.sort((a, b) => (a.planTime || '').localeCompare(b.planTime || ''))
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ const DEBUG_MISMATCH: any[] = []
|
|
|
|
|
+ let exactHit = 0
|
|
|
|
|
+ let fallbackHit = 0
|
|
|
|
|
+ rows.forEach((r) => {
|
|
|
|
|
+ const k = rowKey(r)
|
|
|
|
|
+ const ek = `${r.orderType}-${r.orderEntryId}`
|
|
|
|
|
+ const opt = execMemory.get(k)
|
|
|
|
|
+
|
|
|
|
|
+ // ① 精确匹配
|
|
|
|
|
+ let ex = execMap.get(k)
|
|
|
|
|
+ if (ex) exactHit++
|
|
|
|
|
+
|
|
|
|
|
+ // ② 兜底匹配:按 (orderType, orderEntryId) + 各自桶内索引对齐
|
|
|
|
|
+ if (!ex) {
|
|
|
|
|
+ const rowArr = rowsByEntry.get(ek) || []
|
|
|
|
|
+ const execArr = execByEntry.get(ek) || []
|
|
|
|
|
+ const idx = rowArr.indexOf(r)
|
|
|
|
|
+ if (idx >= 0 && idx < execArr.length) {
|
|
|
|
|
+ ex = execArr[idx]
|
|
|
|
|
+ fallbackHit++
|
|
|
|
|
+ DEBUG_MISMATCH.push({
|
|
|
|
|
+ rowPlanTime: r.planTime,
|
|
|
|
|
+ execPlanTime: ex.planTime,
|
|
|
|
|
+ key: k
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (ex) {
|
|
|
|
|
+ r.execId = ex.id
|
|
|
|
|
+ r.status = ex.status ?? 1
|
|
|
|
|
+ r.execTime = ex.execTime || ''
|
|
|
|
|
+ r.execNurseSign = ex.execNurseSign || ''
|
|
|
|
|
+ r.remark = ex.remark || ''
|
|
|
|
|
+ } else if (opt) {
|
|
|
|
|
+ r.status = opt.status
|
|
|
|
|
+ r.execTime = opt.execTime
|
|
|
|
|
+ r.execNurseSign = opt.execNurseSign
|
|
|
|
|
+ r.remark = opt.remark
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ if (DEBUG_MISMATCH.length > 0) {
|
|
|
|
|
+ console.warn(
|
|
|
|
|
+ `[执行医嘱] 检测到 ${DEBUG_MISMATCH.length} 条 execution.planTime 与前端展开值不一致,` +
|
|
|
|
|
+ '已用 (orderType,orderEntryId,索引) 兜底。后端需修正 planTime 写入以匹配前端传值。',
|
|
|
|
|
+ DEBUG_MISMATCH
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+ if (exactHit + fallbackHit > 0) {
|
|
|
|
|
+ console.info(
|
|
|
|
|
+ `[执行医嘱] execution 合并: 精确 ${exactHit} / 兜底 ${fallbackHit} / 总行 ${rows.length}`
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 5) 筛选 + 排序
|
|
|
|
|
+ let filtered = rows
|
|
|
|
|
+ if (queryParams.orderType != null) {
|
|
|
|
|
+ filtered = filtered.filter((r) => r.orderType === queryParams.orderType)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (queryParams.status != null) {
|
|
|
|
|
+ filtered = filtered.filter((r) => r.status === queryParams.status)
|
|
|
|
|
+ }
|
|
|
|
|
+ filtered.sort((a, b) => (a.planTime || '').localeCompare(b.planTime || ''))
|
|
|
|
|
+ list.value = filtered
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 测试假数据:长期医嘱(在用条目,含多频次) */
|
|
|
|
|
+const buildMockLong = (execDate: string): any[] => [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 1001,
|
|
|
|
|
+ elderId: 801,
|
|
|
|
|
+ elderName: '张建国',
|
|
|
|
|
+ elderAge: 78,
|
|
|
|
|
+ bedName: '1号楼-301-01',
|
|
|
|
|
+ tenantId: queryParams.tenantIds,
|
|
|
|
|
+ entries: [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 9001,
|
|
|
|
|
+ orderTime: `${execDate} 09:00:00`,
|
|
|
|
|
+ orderContent: '口服苯磺酸氨氯地平片 5mg',
|
|
|
|
|
+ frequency: 'qd',
|
|
|
|
|
+ medTime: 'am',
|
|
|
|
|
+ route: 'po',
|
|
|
|
|
+ status: 1,
|
|
|
|
|
+ statusText: '在用'
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 9002,
|
|
|
|
|
+ orderTime: `${execDate} 09:00:00`,
|
|
|
|
|
+ orderContent: '二甲双胍缓释片 0.5g 早晚服用',
|
|
|
|
|
+ frequency: 'bid',
|
|
|
|
|
+ medTime: 'pc',
|
|
|
|
|
+ route: 'po',
|
|
|
|
|
+ status: 1,
|
|
|
|
|
+ statusText: '在用'
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 1002,
|
|
|
|
|
+ elderId: 802,
|
|
|
|
|
+ elderName: '刘秀英',
|
|
|
|
|
+ elderAge: 72,
|
|
|
|
|
+ bedName: '2号楼-205-03',
|
|
|
|
|
+ tenantId: queryParams.tenantIds,
|
|
|
|
|
+ entries: [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 9003,
|
|
|
|
|
+ orderTime: `${execDate} 08:00:00`,
|
|
|
|
|
+ orderContent: '胰岛素皮下注射,餐前 15 分钟',
|
|
|
|
|
+ frequency: 'tid',
|
|
|
|
|
+ medTime: 'ac',
|
|
|
|
|
+ route: 'sc',
|
|
|
|
|
+ status: 1,
|
|
|
|
|
+ statusText: '在用'
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+/** 测试假数据:临时医嘱(医嘱时间为当日) */
|
|
|
|
|
+const buildMockShort = (execDate: string): any[] => [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 2001,
|
|
|
|
|
+ elderId: 803,
|
|
|
|
|
+ elderName: '王德海',
|
|
|
|
|
+ elderAge: 65,
|
|
|
|
|
+ bedName: '1号楼-402-02',
|
|
|
|
|
+ tenantId: queryParams.tenantIds,
|
|
|
|
|
+ entries: [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 9101,
|
|
|
|
|
+ orderTime: `${execDate} 10:30:00`,
|
|
|
|
|
+ orderContent: '血常规 + 生化全套(空腹采血)',
|
|
|
|
|
+ frequency: 'st',
|
|
|
|
|
+ medTime: 'st',
|
|
|
|
|
+ route: 'iv',
|
|
|
|
|
+ executeTime: '',
|
|
|
|
|
+ nurseSign: ''
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+/** 查询 */
|
|
|
|
|
+const handleQuery = () => {
|
|
|
|
|
+ buildRows()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 重置 */
|
|
|
|
|
+const resetQuery = () => {
|
|
|
|
|
+ queryFormRef.value?.resetFields()
|
|
|
|
|
+ queryParams.elderName = undefined
|
|
|
|
|
+ queryParams.execDate = formatDate(new Date(), 'YYYY-MM-DD')
|
|
|
|
|
+ queryParams.orderType = undefined
|
|
|
|
|
+ queryParams.status = undefined
|
|
|
|
|
+ handleQuery()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 执行 / 查看 */
|
|
|
|
|
+const execRef = ref()
|
|
|
|
|
+const openExecute = (row: any, view: boolean) => {
|
|
|
|
|
+ execRef.value?.open(row, view)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * ExecuteDialog 执行成功的回调
|
|
|
|
|
+ * 业务状态真相来自后端;此处先局部更新当前行(立即生效,不闪),并写入内存过渡缓存。
|
|
|
|
|
+ * 页面刷新后由 buildRows 重新拉取后端 page 接口回填,不再依赖任何本地持久化。
|
|
|
|
|
+ */
|
|
|
|
|
+const onExecuteSuccess = (info: any) => {
|
|
|
|
|
+ const k = info?._key
|
|
|
|
|
+ const dataRow = info?.row
|
|
|
|
|
+ if (!k || !dataRow) {
|
|
|
|
|
+ buildRows()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 1) 局部更新:直接在 list 里找到对应行覆盖状态字段
|
|
|
|
|
+ const target = list.value.find((r: any) => rowKey(r) === k)
|
|
|
|
|
+ if (target) {
|
|
|
|
|
+ target.status = dataRow.status ?? 1
|
|
|
|
|
+ if (dataRow.execTime !== undefined) target.execTime = dataRow.execTime
|
|
|
|
|
+ if (dataRow.execNurseSign !== undefined) target.execNurseSign = dataRow.execNurseSign
|
|
|
|
|
+ if (dataRow.remark !== undefined) target.remark = dataRow.remark
|
|
|
|
|
+ if (dataRow.id !== undefined) target.execId = dataRow.id
|
|
|
|
|
+ }
|
|
|
|
|
+ // 2) 写入内存过渡缓存(仅执行瞬间到下次 buildRows 重拉之间生效,刷新后由后端回填)
|
|
|
|
|
+ setExecCache(k, {
|
|
|
|
|
+ status: dataRow.status ?? 1,
|
|
|
|
|
+ execTime: dataRow.execTime || '',
|
|
|
|
|
+ execNurseSign: dataRow.execNurseSign || '',
|
|
|
|
|
+ remark: dataRow.remark || ''
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 跳过:打开原因弹窗 */
|
|
|
|
|
+const skipVisible = ref(false)
|
|
|
|
|
+const skipRow = ref<any>({})
|
|
|
|
|
+const skipping = ref(false)
|
|
|
|
|
+const skipForm = reactive({ remark: '' })
|
|
|
|
|
+
|
|
|
|
|
+const handleSkip = (row: any) => {
|
|
|
|
|
+ skipRow.value = row
|
|
|
|
|
+ skipForm.remark = ''
|
|
|
|
|
+ skipVisible.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 确认跳过:原因必填,写入一条 status=2 的执行记录 */
|
|
|
|
|
+const confirmSkip = async () => {
|
|
|
|
|
+ if (!skipForm.remark || !skipForm.remark.trim()) {
|
|
|
|
|
+ message.error('请填写跳过原因')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ skipping.value = true
|
|
|
|
|
+ const r = skipRow.value
|
|
|
|
|
+ const reason = skipForm.remark.trim()
|
|
|
|
|
+ const payload: DoctorOrderExecution = {
|
|
|
|
|
+ orderType: r.orderType,
|
|
|
|
|
+ orderEntryId: r.orderEntryId,
|
|
|
|
|
+ elderId: r.elderId,
|
|
|
|
|
+ elderName: r.elderName,
|
|
|
|
|
+ bedName: r.bedName,
|
|
|
|
|
+ orderContent: r.orderContent,
|
|
|
|
|
+ frequency: r.frequency,
|
|
|
|
|
+ medTime: r.medTime,
|
|
|
|
|
+ route: r.route,
|
|
|
|
|
+ planTime: r.planTime,
|
|
|
|
|
+ status: 2,
|
|
|
|
|
+ remark: reason,
|
|
|
|
|
+ tenantId: r.tenantId
|
|
|
|
|
+ }
|
|
|
|
|
+ // 同 ExecuteDialog:axios 拦截器对后端 code !== 200 走 reject,但实际数据已写入,
|
|
|
|
|
+ // 这里把 catch 也视为成功,按 status=2 做局部更新。
|
|
|
|
|
+ try {
|
|
|
|
|
+ await executeDoctorOrder(payload)
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ /* swallow */
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ skipping.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+ message.success('已跳过')
|
|
|
|
|
+ skipVisible.value = false
|
|
|
|
|
+ // 1) 局部更新当前行
|
|
|
|
|
+ const target = list.value.find((x: any) => rowKey(x) === rowKey(r))
|
|
|
|
|
+ if (target) {
|
|
|
|
|
+ target.status = 2
|
|
|
|
|
+ target.remark = reason
|
|
|
|
|
+ target.execNurseSign = ''
|
|
|
|
|
+ target.execTime = ''
|
|
|
|
|
+ }
|
|
|
|
|
+ // 2) 内存过渡缓存(刷新后由后端 page 接口回填,不持久化)
|
|
|
|
|
+ setExecCache(rowKey(r), { status: 2, execTime: '', execNurseSign: '', remark: reason })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ buildRows()
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<!--
|
|
|
|
|
+ 打印样式参照 long-doctor-order/Print.vue 的 A4 竖版范式:
|
|
|
|
|
+ - 容器宽度 100%(弹填 dialog),colgroup 按 % 切分,比例与原 194mm 设计一致
|
|
|
|
|
+ - 表格列宽用 %(避免窄视口 + Windows 缩放下 mm 物理宽度溢出 dialog)
|
|
|
|
|
+ - 日期/时间上下两行展示,节省横向空间
|
|
|
|
|
+ - 频次/给药时间/途径去缩写只显中文
|
|
|
|
|
+ - 边框 1px solid #999(淡灰,与长期医嘱单一致)
|
|
|
|
|
+-->
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+.exec-print {
|
|
|
|
|
+ padding: 4px 0;
|
|
|
|
|
+ color: #000;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ /* 屏幕预览弹填 dialog(width:100%),左侧距 dialog 内壁 4px,右贴边。
|
|
|
|
|
+ 打印时 @page A4 portrait 纸张可用 ~194mm,容器按其自动调整。 */
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+
|
|
|
|
|
+ .doc-title {
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ margin: 4px 0 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .info-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+
|
|
|
|
|
+ .info-cell {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ padding: 0 8px;
|
|
|
|
|
+ .lbl {
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .order-table {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ border-collapse: collapse;
|
|
|
|
|
+ table-layout: fixed;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+
|
|
|
|
|
+ th,
|
|
|
|
|
+ td {
|
|
|
|
|
+ border: 1px solid #555;
|
|
|
|
|
+ padding: 4px 4px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ vertical-align: middle;
|
|
|
|
|
+ word-break: break-all;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ thead th {
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ tbody td {
|
|
|
|
|
+ height: 28px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .cell-value {
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ }
|
|
|
|
|
+ .cell-value.pre {
|
|
|
|
|
+ white-space: pre-wrap;
|
|
|
|
|
+ text-align: left;
|
|
|
|
|
+ word-break: break-word;
|
|
|
|
|
+ }
|
|
|
|
|
+ /* 日期 + 时间上下两行展示(计划时间/实际时间列) */
|
|
|
|
|
+ .datetime {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ line-height: 1.2;
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .datetime .d-line {
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ }
|
|
|
|
|
+ .datetime .t-line {
|
|
|
|
|
+ color: #444;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 单条打印:字段标签列窄、值列宽,更舒展 */
|
|
|
|
|
+ .order-table.single-info {
|
|
|
|
|
+ th,
|
|
|
|
|
+ td {
|
|
|
|
|
+ text-align: left;
|
|
|
|
|
+ padding: 6px 8px;
|
|
|
|
|
+ height: 26px;
|
|
|
|
|
+ }
|
|
|
|
|
+ thead th,
|
|
|
|
|
+ tbody th {
|
|
|
|
|
+ background: #f6f6f6;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ width: 10%;
|
|
|
|
|
+ }
|
|
|
|
|
+ tbody td {
|
|
|
|
|
+ text-align: left;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .sign-row {
|
|
|
|
|
+ margin-top: 36px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ padding: 0 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|
|
|
|
|
+
|
|
|
|
|
+<!--
|
|
|
|
|
+ @page 必须 un-scoped(CSS 规范限制),单独放一个 style 块。
|
|
|
|
|
+ A4 竖版 210×297mm,纸张方向 + 边距由浏览器根据此规则应用到打印渲染。
|
|
|
|
|
+ - thead { display: table-header-group }:浏览器自动在每页重复表头
|
|
|
|
|
+ - tr { page-break-inside: avoid }:单条医嘱行不被切断
|
|
|
|
|
+-->
|
|
|
|
|
+<style lang="scss">
|
|
|
|
|
+@media print {
|
|
|
|
|
+ @page {
|
|
|
|
|
+ size: A4 portrait;
|
|
|
|
|
+ margin: 8mm;
|
|
|
|
|
+ }
|
|
|
|
|
+ .exec-print .order-table thead {
|
|
|
|
|
+ display: table-header-group;
|
|
|
|
|
+ }
|
|
|
|
|
+ .exec-print .order-table tr {
|
|
|
|
|
+ page-break-inside: avoid;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|