|
|
@@ -5,6 +5,7 @@
|
|
|
class="-mb-15px"
|
|
|
:model="queryParams"
|
|
|
ref="queryFormRef"
|
|
|
+ :rules="queryRules"
|
|
|
:inline="true"
|
|
|
label-width="80px"
|
|
|
>
|
|
|
@@ -18,14 +19,18 @@
|
|
|
clearable
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
- <el-form-item label="执行日期" prop="execDate">
|
|
|
+ <el-form-item label="执行日期" prop="dateRange">
|
|
|
<el-date-picker
|
|
|
- v-model="queryParams.execDate"
|
|
|
- type="date"
|
|
|
+ v-model="queryParams.dateRange"
|
|
|
+ type="daterange"
|
|
|
value-format="YYYY-MM-DD"
|
|
|
format="YYYY-MM-DD"
|
|
|
- placeholder="选择执行日期"
|
|
|
- style="width: 180px"
|
|
|
+ range-separator="至"
|
|
|
+ start-placeholder="开始日期"
|
|
|
+ end-placeholder="结束日期"
|
|
|
+ :clearable="false"
|
|
|
+ :editable="false"
|
|
|
+ style="width: 280px"
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="医嘱类型" prop="orderType">
|
|
|
@@ -50,11 +55,29 @@
|
|
|
|
|
|
<!-- 列表 -->
|
|
|
<ContentWrap>
|
|
|
- <el-button type="primary" plain @click="openPrintDialog('all')" style="margin-bottom: 10px">
|
|
|
- <Icon icon="ep:printer" class="mr-5px" /> 打印执行单
|
|
|
- </el-button>
|
|
|
+ <div class="mb-10px flex items-center gap-10px">
|
|
|
+ <el-button type="primary" plain @click="openPrintDialog('all')">
|
|
|
+ <Icon icon="ep:printer" class="mr-5px" /> 打印执行单
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ type="success"
|
|
|
+ :disabled="selectedRows.length === 0"
|
|
|
+ @click="openBatchExecute"
|
|
|
+ v-hasPermi="['doctor-order-execute:execute']"
|
|
|
+ >
|
|
|
+ <Icon icon="ep:check" class="mr-5px" />
|
|
|
+ 批量执行{{ selectedRows.length ? `(已选 ${selectedRows.length} 项)` : '' }}
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
|
|
|
- <el-table v-loading="loading" :data="pagedList" border stripe>
|
|
|
+ <el-table
|
|
|
+ v-loading="loading"
|
|
|
+ :data="pagedList"
|
|
|
+ border
|
|
|
+ stripe
|
|
|
+ @selection-change="onSelectionChange"
|
|
|
+ >
|
|
|
+ <el-table-column type="selection" width="48" fixed :selectable="selectable" />
|
|
|
<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" />
|
|
|
@@ -123,6 +146,9 @@
|
|
|
<!-- 执行弹窗 -->
|
|
|
<ExecuteDialog ref="execRef" @success="onExecuteSuccess" />
|
|
|
|
|
|
+ <!-- 批量执行弹窗 -->
|
|
|
+ <BatchExecuteDialog ref="batchExecRef" @success="onBatchSuccess" />
|
|
|
+
|
|
|
<!-- 跳过原因弹窗 -->
|
|
|
<el-dialog
|
|
|
v-model="skipVisible"
|
|
|
@@ -170,7 +196,7 @@
|
|
|
<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>{{ dateRangeText }}</div>
|
|
|
<div class="info-cell"
|
|
|
><span class="lbl">长者:</span>{{ queryParams.elderName || '全部' }}</div
|
|
|
>
|
|
|
@@ -249,7 +275,7 @@
|
|
|
<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>{{ dateRangeText }}</div>
|
|
|
<div class="info-cell"
|
|
|
><span class="lbl">打印时间:</span>{{ formatDateTimeSplit(printNow).date }}
|
|
|
{{ formatDateTimeSplit(printNow).time }}</div
|
|
|
@@ -343,7 +369,7 @@ import {
|
|
|
getShortDoctorOrderPage,
|
|
|
getDoctorOrderExecutionPage,
|
|
|
executeDoctorOrder,
|
|
|
- buildExecutionPlanTimes,
|
|
|
+ buildPendingPlanTimes,
|
|
|
MED_FREQUENCY_OPTIONS,
|
|
|
MED_TIME_OPTIONS,
|
|
|
MED_ROUTE_OPTIONS,
|
|
|
@@ -352,6 +378,7 @@ import {
|
|
|
type DoctorOrderExecution
|
|
|
} from '@/api/elderly/elder/bed-change'
|
|
|
import ExecuteDialog from './ExecuteDialog.vue'
|
|
|
+import BatchExecuteDialog from './BatchExecuteDialog.vue'
|
|
|
|
|
|
defineOptions({ name: 'DoctorOrderExecute' })
|
|
|
|
|
|
@@ -429,12 +456,46 @@ const pagedList = computed(() => {
|
|
|
const queryParams = reactive({
|
|
|
tenantIds: userStore.orgTenantId as any,
|
|
|
elderName: undefined as string | undefined,
|
|
|
- execDate: formatDate(new Date(), 'YYYY-MM-DD'),
|
|
|
+ // 执行日期范围:默认「前 3 天 ~ 今天」、必填、不可清除。
|
|
|
+ // 用户 2026-09-08 反馈:默认 30 天数据量太大,前 3 天够看近期欠账;想看更早手动拉宽范围。
|
|
|
+ // 展开窗口 = 用户所选范围(替换硬编码 HISTORY_DAYS=30),上界 dateEnd 23:59:59 不展未来。
|
|
|
+ dateRange: [
|
|
|
+ dayjs().subtract(30, 'day').format('YYYY-MM-DD'),
|
|
|
+ dayjs().format('YYYY-MM-DD')
|
|
|
+ ] as [string, string],
|
|
|
orderType: undefined as number | undefined,
|
|
|
- status: undefined as number | undefined
|
|
|
+ // 默认筛选「未执行」(0),减少首屏噪声;想看全部清掉该筛选项即可
|
|
|
+ status: 0 as number | undefined
|
|
|
})
|
|
|
const queryFormRef = ref()
|
|
|
|
|
|
+/** 查询表单校验:执行日期范围必选(不选则无法查询) */
|
|
|
+const queryRules = {
|
|
|
+ dateRange: [
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ validator: (_: any, v: [string, string] | undefined, cb: any) => {
|
|
|
+ if (!v || !Array.isArray(v) || v.length !== 2 || !v[0] || !v[1]) {
|
|
|
+ return cb(new Error('请选择执行日期范围'))
|
|
|
+ }
|
|
|
+ if (v[0] > v[1]) {
|
|
|
+ return cb(new Error('开始日期不能晚于结束日期'))
|
|
|
+ }
|
|
|
+ cb()
|
|
|
+ },
|
|
|
+ trigger: 'change'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+/** 表头/打印用:执行日期范围显示文案(单日隐藏范围) */
|
|
|
+const dateRangeText = computed(() => {
|
|
|
+ const r = queryParams.dateRange
|
|
|
+ if (!r || r.length !== 2 || !r[0] || !r[1]) return '-'
|
|
|
+ if (r[0] === r[1]) return r[0]
|
|
|
+ return `${r[0]} 至 ${r[1]}`
|
|
|
+})
|
|
|
+
|
|
|
/**
|
|
|
* 执行瞬间的内存过渡缓存(不持久化)
|
|
|
* 业务状态的唯一真相源是后端 `doctorOrderExecution/page` 接口。前端不应把业务状态
|
|
|
@@ -492,136 +553,144 @@ const formatDateTimeShort = (val: any): string => {
|
|
|
return s ? s.slice(0, 16) : ''
|
|
|
}
|
|
|
|
|
|
-/** 聚合:长期(在用) + 临时(当日) → 按频次展开 → 合并已执行记录 */
|
|
|
+/**
|
|
|
+ * 聚合:长期(欠账累积) + 临时(当日一次性) → 合并已执行记录
|
|
|
+ *
|
|
|
+ * 长期医嘱语义(用户 2026-09-08 确认):从「医嘱时间」开始按频次推进,
|
|
|
+ * 未执行的时点会一直顺延累积展示——今天没做,明天就是 2 条,以此类推。
|
|
|
+ * 停止的医嘱截止到停止时间,但停止前欠的仍要补。
|
|
|
+ */
|
|
|
+
|
|
|
const buildRows = async () => {
|
|
|
loading.value = true
|
|
|
pageNo.value = 1
|
|
|
try {
|
|
|
- const { tenantIds, execDate, elderName } = queryParams
|
|
|
+ const { tenantIds, dateRange, elderName } = queryParams
|
|
|
+ // 执行日期范围必填(表单校验已拦,但 onMounted 直接调用时仍兜底)
|
|
|
+ if (!dateRange || dateRange.length !== 2 || !dateRange[0] || !dateRange[1]) {
|
|
|
+ list.value = []
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const dateBegin = dateRange[0]
|
|
|
+ const dateEnd = dateRange[1]
|
|
|
+ // 后端范围查询参数约定(详见后端规格文档 "四之一·时间字段传递约定" 与最新对接):
|
|
|
+ // - latestOrderTime / planTime 是 array[string],元素为 YYYY-MM-DD 日期字符串
|
|
|
+ // - 后端按日期范围过滤(不解析具体时刻)
|
|
|
+ // - axios 序列化为 ?planTime=2026-08-09&planTime=2026-09-08
|
|
|
+ // 后端 Spring MVC 用 @RequestParam List<String> 接收
|
|
|
+ const rangeDates: [string, string] = [dateBegin, dateEnd]
|
|
|
// 1) 长期医嘱
|
|
|
let longOrders: any[] = []
|
|
|
try {
|
|
|
const lp = await getLongDoctorOrderPage({
|
|
|
pageNo: 1,
|
|
|
- pageSize: 100,
|
|
|
+ pageSize: 1000,
|
|
|
tenantIds,
|
|
|
- elderName: elderName || undefined
|
|
|
+ elderName: elderName || undefined,
|
|
|
+ // 按 entry 最大 order_time 过滤(后端 longDoctorOrder/page 新增参数)
|
|
|
+ latestOrderTime: rangeDates
|
|
|
})
|
|
|
longOrders = lp.list || []
|
|
|
} catch (e) {
|
|
|
console.warn('[执行医嘱] 长期医嘱列表失败,使用测试数据', e)
|
|
|
- longOrders = buildMockLong(execDate)
|
|
|
+ longOrders = buildMockLong(dateEnd)
|
|
|
}
|
|
|
// 2) 临时医嘱
|
|
|
let shortOrders: any[] = []
|
|
|
try {
|
|
|
const sp = await getShortDoctorOrderPage({
|
|
|
pageNo: 1,
|
|
|
- pageSize: 100,
|
|
|
+ pageSize: 1000,
|
|
|
tenantIds,
|
|
|
- elderName: elderName || undefined
|
|
|
+ elderName: elderName || undefined,
|
|
|
+ // 同上:按 entry 最大 order_time 过滤
|
|
|
+ latestOrderTime: rangeDates
|
|
|
})
|
|
|
shortOrders = sp.list || []
|
|
|
} catch (e) {
|
|
|
console.warn('[执行医嘱] 临时医嘱列表失败,使用测试数据', e)
|
|
|
- shortOrders = buildMockShort(execDate)
|
|
|
+ shortOrders = buildMockShort(dateEnd)
|
|
|
}
|
|
|
|
|
|
- // 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[]>()
|
|
|
+ // 3) 拉取执行记录(按 planTime 范围过滤,避免全量 2000 拉回匹配)
|
|
|
+ // 长期医嘱要判断"历史欠账",必须拿到该条目在窗口内全部已闭环记录。
|
|
|
+ // 已闭环 = 已执行(1) 或 已跳过(2);待执行(0) 不算闭环,仍需展开。
|
|
|
+ // ⚠️ 强依赖:后端必须精确写入前端传入的 planTime(YYYY-MM-DD HH:mm),
|
|
|
+ // 否则欠账无法正确销账。此处已取消旧的"索引兜底"匹配——欠账模型下
|
|
|
+ // 索引对齐会造成错配,宁可显示为待执行也不能销错账。
|
|
|
+ // 后端 doctorOrderExecution/page 新增 planTime 数组参数(按 plan_time 范围过滤);
|
|
|
+ // 旧的 planDate 单日参数本次不传(始终走范围,更通用)
|
|
|
+ const execMap = new Map<string, any>() // 精确 key → execution 记录(用于回填状态:已执行/已跳过/待执行)
|
|
|
try {
|
|
|
const ep = await getDoctorOrderExecutionPage({
|
|
|
pageNo: 1,
|
|
|
- pageSize: 1000,
|
|
|
+ pageSize: 2000,
|
|
|
tenantIds,
|
|
|
- planDate: execDate
|
|
|
+ // 范围过滤:传 YYYY-MM-DD 日期字符串数组
|
|
|
+ planTime: rangeDates
|
|
|
})
|
|
|
;(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)
|
|
|
+ const plan = String(r.planTime).slice(0, 16)
|
|
|
+ const k = `${r.orderType}-${r.orderEntryId}`
|
|
|
+ execMap.set(`${k}-${plan}`, r)
|
|
|
+ // 状态回填统一走 step 5 的精确匹配;不再用 closedSet 减集合
|
|
|
+ // (展示逻辑改为「展示全部应执行时点 + 回填状态」,已执行/已跳过也保留在列表)
|
|
|
})
|
|
|
} 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)
|
|
|
+ // 4) 展开执行行
|
|
|
+ const rows: any[] = []
|
|
|
+ // 展开窗口 = 用户所选 [dateBegin, dateEnd](替换硬编码 30 天)
|
|
|
+ // - 上界 dateEnd 23:59:59 不展未来
|
|
|
+ // - 下界 viewStartDate 00:00:00 不展无限历史
|
|
|
+ // 类型筛选只过滤类型(长期/临时/全部),不改变时间窗口
|
|
|
+ const viewStartDate = dateBegin
|
|
|
+ // 4.1 长期医嘱:展开 [viewStartDate, dateEnd] 内的应执行时点(不展未来)
|
|
|
+ // - 展示全部时点(含已执行/已跳过),状态由 step 5 精确回填
|
|
|
+ // - 在用(1) 与 已停止(2) 都展开;未开始(0) 不展开
|
|
|
+ longOrders.forEach((vo: any) => {
|
|
|
+ ;(vo.entries || []).forEach((e: any) => {
|
|
|
+ if (e.status !== 1 && e.status !== 2) return
|
|
|
+ buildPendingPlanTimes({
|
|
|
+ frequency: e.frequency,
|
|
|
+ medTime: e.medTime,
|
|
|
+ startTime: fromTs(e.orderTime),
|
|
|
+ stopTime: e.status === 2 ? fromTs(e.stopTime) : '',
|
|
|
+ // 上界=用户所选结束日 23:59:59(不展未来)
|
|
|
+ now: `${dateEnd} 23:59:59`,
|
|
|
+ // 下界=用户所选开始日 00:00:00
|
|
|
+ viewStart: `${viewStartDate} 00:00:00`
|
|
|
+ })
|
|
|
+ // 双保险:planTime 必须落在 [viewStartDate, dateEnd] 区间内
|
|
|
+ .filter((t) => t.slice(0, 10) >= viewStartDate && t.slice(0, 10) <= dateEnd)
|
|
|
+ .forEach((t: string) => rows.push(makeRow(1, e, t, vo)))
|
|
|
+ })
|
|
|
+ })
|
|
|
+ // 4.2 临时医嘱:一次性,planTime 固定为开单时间;落在窗口内即展示(含历史开单)
|
|
|
+ shortOrders.forEach((vo: any) => {
|
|
|
+ ;(vo.entries || []).forEach((e: any) => {
|
|
|
+ // orderTime 后端可能返回 13位毫戳数字/字符串(API 层 .then 已统一转字符串;
|
|
|
+ // 这里 fromTs 兜底,兼容任何来源混入的数字)
|
|
|
+ const plan = fromTs(e.orderTime)
|
|
|
+ if (!plan) return
|
|
|
+ const planDate = plan.slice(0, 10)
|
|
|
+ if (planDate < viewStartDate || planDate > dateEnd) return
|
|
|
+ // planTime 归一为 YYYY-MM-DD HH:mm(截掉秒,分钟以下不显示)
|
|
|
+ rows.push(makeRow(2, e, formatDateTimeShort(plan), vo))
|
|
|
+ })
|
|
|
})
|
|
|
- rowsByEntry.forEach((arr) =>
|
|
|
- arr.sort((a, b) => (a.planTime || '').localeCompare(b.planTime || ''))
|
|
|
- )
|
|
|
|
|
|
- const DEBUG_MISMATCH: any[] = []
|
|
|
- let exactHit = 0
|
|
|
- let fallbackHit = 0
|
|
|
+ // 5) 回填状态:按 planTime 精确匹配(展示全部时点,已执行/已跳过也保留并回填)
|
|
|
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
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
+ const ex = execMap.get(k)
|
|
|
if (ex) {
|
|
|
r.execId = ex.id
|
|
|
r.status = ex.status ?? 1
|
|
|
@@ -636,20 +705,19 @@ const buildRows = async () => {
|
|
|
}
|
|
|
})
|
|
|
|
|
|
- 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}`
|
|
|
- )
|
|
|
- }
|
|
|
+ // ⚠️ 临时诊断(核对后端 planTime 写入精度用,确认后删除):
|
|
|
+ // 若「未命中_后端planTime与前端展开不一致」> 0,说明后端 planTime 未按请求体精确入库,
|
|
|
+ // 已执行/已跳过的记录将无法正确回填 → 表现为「已执行的仍显示待执行」。
|
|
|
+ const expandedKeys = new Set(rows.map((r) => rowKey(r)))
|
|
|
+ const matchedExec = [...execMap.keys()].filter((k) => expandedKeys.has(k)).length
|
|
|
+ console.log('[planTime-诊断]', {
|
|
|
+ 展开行数: rows.length,
|
|
|
+ 后端execution记录: execMap.size,
|
|
|
+ 命中展开行: matchedExec,
|
|
|
+ 未命中_后端planTime与前端展开不一致: execMap.size - matchedExec
|
|
|
+ })
|
|
|
|
|
|
- // 5) 筛选 + 排序
|
|
|
+ // 6) 筛选 + 排序
|
|
|
let filtered = rows
|
|
|
if (queryParams.orderType != null) {
|
|
|
filtered = filtered.filter((r) => r.orderType === queryParams.orderType)
|
|
|
@@ -676,7 +744,9 @@ const buildMockLong = (execDate: string): any[] => [
|
|
|
entries: [
|
|
|
{
|
|
|
id: 9001,
|
|
|
- orderTime: `${execDate} 09:00:00`,
|
|
|
+ // 故意设为 3 天前开立且从未执行:用于演示「欠账累积」——
|
|
|
+ // qd 频次会展开 09-05~09-08 共 4 条待执行,而不是只有今天 1 条
|
|
|
+ orderTime: `${dayjs(execDate).subtract(30, 'day').format('YYYY-MM-DD')} 09:00:00`,
|
|
|
orderContent: '口服苯磺酸氨氯地平片 5mg',
|
|
|
frequency: 'qd',
|
|
|
medTime: 'am',
|
|
|
@@ -742,19 +812,34 @@ const buildMockShort = (execDate: string): any[] => [
|
|
|
}
|
|
|
]
|
|
|
|
|
|
-/** 查询 */
|
|
|
-const handleQuery = () => {
|
|
|
+/** 查询:先校验必填的执行日期,通过后再拉数 */
|
|
|
+const handleQuery = async () => {
|
|
|
+ try {
|
|
|
+ await queryFormRef.value?.validate()
|
|
|
+ } catch {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ clearSelection()
|
|
|
buildRows()
|
|
|
}
|
|
|
|
|
|
-/** 重置 */
|
|
|
+/** 列表变化前清掉选中(数据已变,选中的 row 引用可能已失效) */
|
|
|
+const clearSelection = () => {
|
|
|
+ selectedRows.value = []
|
|
|
+}
|
|
|
+
|
|
|
+/** 重置:执行日期范围回到「前 3 天 ~ 今天」(不可清除)、状态回到「未执行」,再重新加载 */
|
|
|
const resetQuery = () => {
|
|
|
queryFormRef.value?.resetFields()
|
|
|
queryParams.elderName = undefined
|
|
|
- queryParams.execDate = formatDate(new Date(), 'YYYY-MM-DD')
|
|
|
+ queryParams.dateRange = [
|
|
|
+ dayjs().subtract(30, 'day').format('YYYY-MM-DD'),
|
|
|
+ dayjs().format('YYYY-MM-DD')
|
|
|
+ ] as [string, string]
|
|
|
queryParams.orderType = undefined
|
|
|
- queryParams.status = undefined
|
|
|
- handleQuery()
|
|
|
+ queryParams.status = 0
|
|
|
+ clearSelection()
|
|
|
+ buildRows()
|
|
|
}
|
|
|
|
|
|
/** 执行 / 查看 */
|
|
|
@@ -799,6 +884,27 @@ const skipRow = ref<any>({})
|
|
|
const skipping = ref(false)
|
|
|
const skipForm = reactive({ remark: '' })
|
|
|
|
|
|
+/* ========== 批量执行:表格多选 + 弹窗统一提交 ========== */
|
|
|
+/** 选中的行(仅 status=0 可选) */
|
|
|
+const selectedRows = ref<any[]>([])
|
|
|
+/** 表格 selectable:仅「待执行」(status=0) 可勾选 */
|
|
|
+const selectable = (row: any) => row?.status === 0
|
|
|
+const onSelectionChange = (rows: any[]) => {
|
|
|
+ selectedRows.value = rows || []
|
|
|
+}
|
|
|
+/** 打开批量执行弹窗(弹窗内自取 selectedRows 副本,避免父组件列表变化影响) */
|
|
|
+const batchExecRef = ref()
|
|
|
+const openBatchExecute = () => {
|
|
|
+ if (selectedRows.value.length === 0) return
|
|
|
+ batchExecRef.value?.open(selectedRows.value)
|
|
|
+}
|
|
|
+/** 批量执行成功的回调:刷新列表(避免按行局部更新时 rowKey 不一致) */
|
|
|
+const onBatchSuccess = (_info: any) => {
|
|
|
+ selectedRows.value = []
|
|
|
+ buildRows()
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
const handleSkip = (row: any) => {
|
|
|
skipRow.value = row
|
|
|
skipForm.remark = ''
|