Files
his/openhis-ui-vue3/src/views/inpatientNurse/medicalOrderExecution/components/prescriptionList.vue
yangkexiang 82ef66794b 住院护士站-》医嘱执行页面:勾选医嘱后点击“执行选中”按钮无反应,无法完成执行操作.
疾病报告管理-》报告卡管理:审核报卡界面内容与门诊医生站登记界面不一致
2026-04-30 13:33:03 +08:00

799 lines
27 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div style="height: calc(100vh - 126px)">
<div
style="
height: 51px;
border-bottom: 2px solid #e4e7ed;
display: flex;
align-items: center;
justify-content: space-between;
"
>
<div>
<span class="descriptions-item-label">截止时间</span>
<el-date-picker
v-model="deadline"
type="datetime"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
:clearable="false"
@change="handleGetPrescription"
/>
<el-radio-group v-model="therapyEnum" class="ml20" @change="handleRadioChange">
<el-radio :value="undefined">全部</el-radio>
<el-radio :value="1">长期</el-radio>
<el-radio :value="2">临时</el-radio>
</el-radio-group>
<el-button class="ml20" type="primary" plain @click="handleGetPrescription">
查询
</el-button>
</div>
<div>
<span class="descriptions-item-label">实际执行时间</span>
<el-date-picker
v-model="exeDate"
type="datetime"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
:clearable="false"
@change="handleGetPrescription"
/>
<span class="descriptions-item-label">全选</span>
<el-switch v-model="chooseAll" @change="handelSwicthChange" />
<el-button
class="ml20"
type="primary"
@click="handleExecute"
:disabled="props.exeStatus == 6"
>
执行选中
</el-button>
<el-button
class="ml20"
type="primary"
@click="handleNoExecute"
:disabled="props.exeStatus == 6"
>
不执行
</el-button>
<el-button
class="ml20 mr20"
type="danger"
:disabled="props.exeStatus != 6"
plain
@click="handleCancel"
>
取消执行
</el-button>
</div>
</div>
<div
style="padding: 10px; background-color: #eef9fd; height: 100%; overflow-y: auto"
v-loading="loading"
>
<el-collapse
v-model="activeNames"
expand-icon-position="left"
v-if="prescriptionList.length > 0"
style="
border-bottom: 0px;
border-top: 0px;
border-radius: 0px;
overflow-y: auto;
max-height: calc(100vh - 200px);
"
>
<el-collapse-item
v-for="(item, index) in prescriptionList"
:key="index"
:name="item[0].encounterId"
style="
border: 2px solid #e4e7ed;
border-radius: 8px;
margin-bottom: 10px;
overflow: hidden;
"
>
<template #title>
<div style="display: flex; justify-content: space-between; align-items: center">
<div>
<span>{{ item[0].bedName + '【' + item[0].busNo + '】' }}</span>
<span>
{{
item[0].patientName + ' / ' + item[0].genderEnum_enumText + ' / ' + item[0].age
}}
</span>
<el-tag style="margin-left: 10px">{{ item[0].contractName }}</el-tag>
<span style="margin-left: 30px; font-weight: 600">
{{ item[0].conditionNames }}
</span>
</div>
<div
style="
display: flex;
justify-content: space-between;
gap: 20px;
align-items: center;
margin-right: 20px;
"
>
<div style="display: inline-block; margin-right: 10px">
<span class="descriptions-item-label">住院医生</span>
<span>{{ item[0].requesterId_dictText }}</span>
</div>
<div style="display: inline-block; margin-right: 10px">
<div
class="descriptions-item-label"
style="height: 20px; line-height: 20px; text-align: center; margin: 0"
>
预交金额
</div>
<div
style="
height: 20px;
line-height: 20px;
text-align: center;
font-size: 15px;
color: #ec8c43;
"
>
{{ item[0].balanceAmount?.toFixed(2) }}
</div>
</div>
</div>
</div>
</template>
<el-table
:data="item"
border
:ref="'tableRef' + index"
:header-cell-style="{ background: '#eef9fd !important' }"
@select="(selection, row) => handleRowSelect(selection, row, index)"
@select-all="(selection) => handleSelectAll(selection, index)"
>
<el-table-column type="selection" align="center" width="50" />
<el-table-column label="类型" align="center" prop="therapyEnum_enumText" width="80">
<template #default="scope">
<span :style="scope.row.therapyEnum == '1' ? 'color: #a6745c' : 'color: #3787a5'">
{{ scope.row.therapyEnum_enumText }}
</span>
</template>
</el-table-column>
<el-table-column label="医嘱内容" prop="adviceName">
<template #default="scope">
<span>
{{ scope.row.adviceName }}
</span>
<template v-if="scope.row.adviceTable == 'med_medication_request'">
<span>
{{ ' 【' + scope.row.volume + '】' }}
</span>
<span style="color: #447c95 !important">
{{
'(' +
scope.row.methodCode_dictText +
' ' +
scope.row.dose +
scope.row.doseUnitCode_dictText +
' ' +
scope.row.rateCode_dictText +
')'
}}
</span>
</template>
</template>
</el-table-column>
<el-table-column label="开始/终止" prop="requestTime" width="200" />
<el-table-column :label="props.exeStatus == 1 ? '预计执行' : '执行时间'" prop="times">
<template #default="scope">
<div
v-for="(item, timeIndex) in scope.row.times"
:key="timeIndex"
style="padding-bottom: 5px"
>
<span>{{ item }}</span>
<el-checkbox-group
v-model="scope.row.checkedRates[timeIndex]"
style="display: inline-block; margin-left: 15px"
>
<el-checkbox
v-for="(rateItem, rateIndex) in scope.row.rate[item]"
:key="rateIndex"
:value="rateItem.rate"
@change="
(value) => handleRateChange(value, item, rateItem.rate, scope.row, rateItem)
"
border
size="small"
style="margin-right: 15px"
>
{{ rateItem.rate }}
<template v-if="rateItem.practitionerName">
{{ rateItem.practitionerName }}
</template>
</el-checkbox>
</el-checkbox-group>
</div>
</template>
</el-table-column>
</el-table>
</el-collapse-item>
</el-collapse>
<el-empty v-else description="点击查询获取患者医嘱信息"></el-empty>
</div>
</div>
</template>
<script setup>
import {adviceCancel, adviceExecute, adviceNoExecute, getPrescriptionList} from './api';
import {patientInfoList} from '../../components/store/patient.js';
import {lotNumberMatch} from '@/api/public';
import {formatDateStr} from '@/utils/index';
import {getCurrentInstance, nextTick, ref} from 'vue';
const activeNames = ref([]);
const prescriptionList = ref([]);
const deadline = ref(formatDateStr(new Date(), 'YYYY-MM-DD') + ' 23:59:59');
const exeDate = ref(formatDateStr(new Date(), 'YYYY-MM-DD') + ' 23:59:59');
const therapyEnum = ref(undefined);
const { proxy } = getCurrentInstance();
const loading = ref(false);
const chooseAll = ref(false);
const props = defineProps({
exeStatus: {
type: Number,
default: 1,
},
requestStatus: {
type: Number,
default: 1,
},
});
function handleRadioChange() {
handleGetPrescription();
}
/** 频次时间规整为 HH:mm与 formatDateStr 得到的时分一致,避免 8:00 vs 08:00 对不上) */
function normalizeDayTimeHm(part) {
if (part == null || String(part).trim() === '') {
return '00:00';
}
const s = String(part).trim();
const [hRaw, mRaw = '0'] = s.split(':').map((x) => String(x).trim());
const hNum = Number.parseInt(hRaw || '0', 10);
const mNum = Number.parseInt(mRaw || '0', 10);
const h = Number.isNaN(hNum) ? '00' : String(hNum).padStart(2, '0');
const m = Number.isNaN(mNum) ? '00' : String(mNum).padStart(2, '0');
return `${h}:${m}`;
}
function handleGetPrescription() {
if (patientInfoList.value.length > 0) {
loading.value = true;
let encounterIds = patientInfoList.value.map((i) => i.encounterId).join(',');
getPrescriptionList({
encounterIds: encounterIds,
pageSize: 10000,
pageNo: 1,
therapyEnum: therapyEnum.value,
exeStatus: props.exeStatus,
requestStatus: props.requestStatus,
}).then((res) => {
// try {
// 根据encounterId分组
const groupedPrescriptions = res.data.records.reduce((groups, prescription) => {
// 获取当前医嘱全部执行频次
let rate;
let times;
if (prescription.therapyEnum == 1) {
// 长期医嘱dayTimes 可能为「8:00,12:00」未补零需与后端 occurrenceTime 格式化后对齐
rate = prescription.dayTimes
?.split(',')
.map((x) => x.trim())
.filter((x) => x !== '')
.map((x) => normalizeDayTimeHm(x));
// 用截止时间和医嘱签发时间算出全部执行日期
times = getDateRange(prescription.requestTime, deadline.value);
} else {
// 临时医嘱 直接取医嘱签发时间截取执行时间和执行日期
rate = [formatDateStr(prescription.requestTime, 'HH:mm')];
times = [formatDateStr(prescription.requestTime, 'MM-DD')];
}
prescription.year = formatDateStr(prescription.requestTime, 'YYYY');
// 初始化执行时间列表
prescription.executeTimes = [];
// 初始化取消执行Id列表
prescription.procedureIds = [];
// 添加复选框状态管理
prescription.checkedRates = {};
// 已执行时间点列表
let exeTimeList = prescription.exePerformRecordList.map((item) => {
return formatDateStr(item.occurrenceTime, 'YYYY-MM-DD HH:mm:ss');
});
// 不执行时间点列表
let stopTimeList = prescription.stopPerformRecordList.map((item) => {
return formatDateStr(item.occurrenceTime, 'YYYY-MM-DD HH:mm:ss');
});
// 取消执行时间点列表
let cancelTimeList = prescription.cancelPerformRecordList.map((item) => {
return formatDateStr(item.occurrenceTime, 'YYYY-MM-DD HH:mm:ss');
});
if (rate) {
// 拼成日期加全部时间点的形式显示示例03-01 [09:00, 10:00, 11:00]
// 重构后的逻辑
let validTimes = [];
let validRates = {};
let newCheckedRates = {};
times.forEach((time, timeIndex) => {
let checkedRatesForTime = [];
let rateItems = [];
rate.forEach((rateItem) => {
let dateStr = prescription.year + '-' + time + ' ' + rateItem + ':00';
switch (props.exeStatus) {
case 1: // 待执行tab
// 如果该时间点未执行并且不在不执行列表中,则加入待选中列表
if (!exeTimeList.includes(dateStr) && !stopTimeList.includes(dateStr)) {
rateItems.push({
rate: rateItem,
});
checkedRatesForTime.push(rateItem);
prescription.executeTimes.push(dateStr);
}
break;
case 6: // 已执行tab
let index = exeTimeList.findIndex((item) => {
return item == dateStr;
});
if (index != -1) {
// 显示执行人practitionerName
rateItems.push({
procedureId: prescription.exePerformRecordList[index].procedureId,
practitionerName: prescription.exePerformRecordList[index].practitionerName,
rate: rateItem,
});
checkedRatesForTime.push(rateItem);
}
// 这里把时间点都加到执行时间列表中后续遍历全部执行者id用
prescription.executeTimes.push(dateStr);
break;
case 5: // 不执行tab
let index1 = stopTimeList.findIndex((item) => {
return item == dateStr;
});
if (stopTimeList.includes(dateStr)) {
// 显示执行人practitionerName
rateItems.push({
practitionerName: prescription.stopPerformRecordList[index1].practitionerName,
rate: rateItem,
});
checkedRatesForTime.push(rateItem);
}
break;
case 9: // 取消执行tab
let index2 = cancelTimeList.findIndex((item) => {
return item == dateStr;
});
if (cancelTimeList.includes(dateStr)) {
rateItems.push({
practitionerName:
prescription.cancelPerformRecordList[index2].practitionerName,
rate: rateItem,
});
checkedRatesForTime.push(rateItem);
}
break;
}
});
validRates[time] = rateItems;
// 只有当该时间点有待执行的项目时,才保留该时间点
if (checkedRatesForTime.length > 0) {
let newIndex = validTimes.length;
validTimes.push(time);
newCheckedRates[newIndex] = checkedRatesForTime;
}
});
console.log(validRates, '111');
// 更新处方对象
prescription.times = validTimes;
prescription.rate = validRates;
prescription.checkedRates = newCheckedRates;
// 处理已执行记录拿到全部的执行人id取消执行时候要传id
prescription.exePerformRecordList.forEach((item) => {
if (prescription.executeTimes.includes(item.occurrenceTime)) {
prescription.procedureIds.push(item.procedureId);
}
});
}
// 「待执行」tab 下医嘱所有时间点都已执行/不执行时,跳过该医嘱
// (后端待执行查询未做该过滤,需要前端补充;其他 tab 后端已按记录列表过滤,不再处理)
if (props.exeStatus === 1 && (!prescription.times || prescription.times.length === 0)) {
return groups;
}
// 把相同encounterId的医嘱放在同一个数组中
const encounterId = prescription.encounterId;
if (!groups[encounterId]) {
groups[encounterId] = [];
}
if (!activeNames.value.includes(encounterId)) {
activeNames.value.push(encounterId);
}
groups[encounterId].push(prescription);
return groups;
}, {});
// 将分组结果转换为数组形式
prescriptionList.value = Object.values(groupedPrescriptions);
loading.value = false;
// 默认选中全部行
nextTick(() => {
defaultSelectAllRows();
});
// } catch {
// loading.value = false;
// }
});
chooseAll.value = false;
} else {
prescriptionList.value = [];
// proxy.$message.warning('请选择患者');
}
}
// 执行
function handleExecute() {
let list = getSelectRows();
let encounterIds = patientInfoList.value.map((i) => i.encounterId).join(',');
list = list.map((item) => {
return {
requestId: item.requestId,
accountId: item.accountId,
adviceTable: item.adviceTable,
executeTimes: item.executeTimes,
};
});
console.log(list, 'list');
adviceExecute({ exeDate: exeDate.value, adviceExecuteDetailList: list }).then((res) => {
if (res.code == 200) {
proxy.$modal.msgSuccess(res.msg || '医嘱执行成功');
handleGetPrescription();
lotNumberMatch({ encounterIdList: encounterIds }, { skipErrorMsg: true }).catch((error) => {
console.warn('lotNumberMatch failed after adviceExecute:', error);
});
} else {
proxy.$modal.msgError(res.msg || '医嘱执行失败');
}
});
}
// 不执行
function handleNoExecute() {
let list = getSelectRows();
list = list.map((item) => {
return {
requestId: item.requestId,
encounterId: item.encounterId,
patientId: item.patientId,
adviceTable: item.adviceTable,
executeTimes: item.executeTimes,
};
});
console.log(list, 'list');
adviceNoExecute({ adviceExecuteDetailList: list }).then((res) => {
if (res.code == 200) {
proxy.$modal.msgSuccess(res.msg || '操作成功');
handleGetPrescription();
} else {
proxy.$modal.msgError(res.msg || '操作失败');
}
});
}
// 取消执行
function handleCancel() {
let list = getSelectRows();
let producerIds = [];
list.forEach((item) => {
producerIds.push(
...item.procedureIds.map((value) => {
return {
procedureId: value,
therapyEnum: item.therapyEnum,
};
})
);
});
adviceCancel({ adviceExecuteDetailList: producerIds }).then((res) => {
if (res.code == 200) {
proxy.$modal.msgSuccess(res.msg || '取消执行成功');
handleGetPrescription();
} else {
proxy.$modal.msgError(res.msg || '取消执行失败');
}
});
}
function getSelectRows() {
// 获取选中的医嘱信息
let list = [];
prescriptionList.value.forEach((item, index) => {
list = [...list, ...proxy.$refs['tableRef' + index][0].getSelectionRows()];
});
return list;
}
/**
* 计算两个日期之间的所有日期(包含起始和结束日期)
* @param {string|Date} startDate - 开始日期
* @param {string|Date} endDate - 结束日期
* @returns {Array<string>} 格式为MM-DD的日期数组
*/
function getDateRange(startDate, endDate) {
const start = new Date(startDate);
const end = new Date(endDate);
// 重置时间部分,只保留日期
start.setHours(0, 0, 0, 0);
end.setHours(0, 0, 0, 0);
const dates = [];
const current = new Date(start);
// 循环添加日期直到结束日期
while (current <= end) {
// 格式化为MM-DD
const month = String(current.getMonth() + 1).padStart(2, '0');
const day = String(current.getDate()).padStart(2, '0');
dates.push(`${month}-${day}`);
// 移动到下一天
current.setDate(current.getDate() + 1);
}
return dates;
}
function handleRateChange(value, date, time, row, rateItem) {
// 拼接当前选中时间
let timeStr = row.year + '-' + date + ' ' + time + ':00';
let timeIndex = row.executeTimes.indexOf(timeStr);
if (value) {
// 选中checkbox如果时间不在执行时间列表中则添加
if (timeIndex === -1) {
row.executeTimes.push(timeStr);
}
// 如果有procedureId添加到列表中
if (rateItem.procedureId && !row.procedureIds.includes(rateItem.procedureId)) {
row.procedureIds.push(rateItem.procedureId);
}
} else {
// 取消选中checkbox从执行时间列表中移除
if (timeIndex !== -1) {
row.executeTimes.splice(timeIndex, 1);
}
// 移除对应的procedureId
if (rateItem.procedureId) {
const procedureIndex = row.procedureIds.indexOf(rateItem.procedureId);
if (procedureIndex !== -1) {
row.procedureIds.splice(procedureIndex, 1);
}
}
}
// 检查该行所有checkbox是否全部选中联动表格行选中状态
nextTick(() => {
checkAndToggleRowSelection(row);
});
}
function handelSwicthChange(value) {
prescriptionList.value.forEach((item, index) => {
const tableRef = proxy.$refs['tableRef' + index];
if (tableRef && tableRef[0]) {
if (value) {
// 全选选中所有行并联动checkbox
item.forEach((row) => {
tableRef[0].toggleRowSelection(row, true);
selectAllCheckboxesInRow(row);
});
} else {
// 取消全选取消选中所有行并联动checkbox
item.forEach((row) => {
tableRef[0].toggleRowSelection(row, false);
unselectAllCheckboxesInRow(row);
});
}
}
});
}
// 默认选中全部行
function defaultSelectAllRows() {
prescriptionList.value.forEach((item, index) => {
const tableRef = proxy.$refs['tableRef' + index];
if (tableRef && tableRef[0]) {
// 选中该表格的所有行
item.forEach((row) => {
tableRef[0].toggleRowSelection(row, true);
// 同时选中该行内部的所有checkbox
selectAllCheckboxesInRow(row);
});
}
});
// 更新全选开关状态
chooseAll.value = true;
}
// 选中行内部的所有checkbox
function selectAllCheckboxesInRow(row) {
if (!row.times || !row.rate) return;
// 遍历所有时间点选中所有checkbox
row.times.forEach((time, timeIndex) => {
const rateItems = row.rate[time] || [];
if (rateItems.length > 0) {
// 获取该时间点的所有rate值
const allRates = rateItems.map((item) => item.rate);
// 设置为全选
row.checkedRates[timeIndex] = [...allRates];
// 更新执行时间列表
allRates.forEach((rate) => {
const timeStr = row.year + '-' + time + ' ' + rate + ':00';
if (!row.executeTimes.includes(timeStr)) {
row.executeTimes.push(timeStr);
}
// 添加procedureId
const rateItem = rateItems.find((item) => item.rate === rate);
if (rateItem && rateItem.procedureId && !row.procedureIds.includes(rateItem.procedureId)) {
row.procedureIds.push(rateItem.procedureId);
}
});
}
});
}
// 取消选中行内部的所有checkbox
function unselectAllCheckboxesInRow(row) {
if (!row.times) return;
// 清空所有checkbox选中状态
row.times.forEach((time, timeIndex) => {
row.checkedRates[timeIndex] = [];
});
// 清空执行时间列表和procedureIds
row.executeTimes = [];
row.procedureIds = [];
}
// 检查行内部所有checkbox是否全部选中
function isAllCheckboxesSelected(row) {
if (!row.times || !row.rate) return false;
// 遍历所有时间点,检查是否全部选中
for (let timeIndex = 0; timeIndex < row.times.length; timeIndex++) {
const time = row.times[timeIndex];
const rateItems = row.rate[time] || [];
const checkedRates = row.checkedRates[timeIndex] || [];
// 如果该时间点的checkbox没有全部选中返回false
if (rateItems.length > 0 && checkedRates.length !== rateItems.length) {
return false;
}
}
return true;
}
// 检查并联动表格行选中状态
function checkAndToggleRowSelection(row) {
prescriptionList.value.forEach((item, tableIndex) => {
const rowIndex = item.findIndex((r) => r.requestId === row.requestId);
if (rowIndex !== -1) {
const tableRef = proxy.$refs['tableRef' + tableIndex];
if (tableRef && tableRef[0]) {
const isAllSelected = isAllCheckboxesSelected(row);
const selectedRows = tableRef[0].getSelectionRows();
const isCurrentlySelected = selectedRows.some((r) => r.requestId === row.requestId);
// 根据checkbox状态更新表格行选中状态
if (isAllSelected && !isCurrentlySelected) {
tableRef[0].toggleRowSelection(row, true);
} else if (!isAllSelected && isCurrentlySelected) {
tableRef[0].toggleRowSelection(row, false);
}
}
}
});
}
// 处理表格行选中事件
function handleRowSelect(selection, row, tableIndex) {
const isSelected = selection.some((item) => item.requestId === row.requestId);
if (isSelected) {
// 选中行时选中该行内部的所有checkbox
selectAllCheckboxesInRow(row);
} else {
// 取消选中行时取消选中该行内部的所有checkbox
unselectAllCheckboxesInRow(row);
}
// 更新全选开关状态
updateChooseAllStatus();
}
// 处理表格全选事件
function handleSelectAll(selection, tableIndex) {
const tableData = prescriptionList.value[tableIndex];
if (!tableData) return;
if (selection.length > 0) {
// 全选时选中所有行内部的所有checkbox
tableData.forEach((row) => {
selectAllCheckboxesInRow(row);
});
} else {
// 取消全选时取消选中所有行内部的所有checkbox
tableData.forEach((row) => {
unselectAllCheckboxesInRow(row);
});
}
// 更新全选开关状态
updateChooseAllStatus();
}
// 更新全选开关状态
function updateChooseAllStatus() {
let allSelected = true;
prescriptionList.value.forEach((item, index) => {
const tableRef = proxy.$refs['tableRef' + index];
if (tableRef && tableRef[0]) {
const selectedRows = tableRef[0].getSelectionRows();
if (selectedRows.length !== item.length) {
allSelected = false;
}
} else {
allSelected = false;
}
});
chooseAll.value = allSelected;
}
// 处理后端返回的时间集合
function handleTime() {}
defineExpose({
handleGetPrescription,
});
</script>
<style scoped>
.el-collapse-icon-position-left :deep(.el-collapse-item__header) {
padding: 10px;
}
:deep(.el-collapse-item__wrap) {
padding: 10px;
}
/* 表头背景色 */
:deep(.prescription-table .el-table__header th) {
background-color: #eef9fd !important;
}
:deep(.el-table__row:hover > td) {
background-color: #eef9fd !important;
}
.item-value {
color: #606266;
font-size: 15px;
font-weight: 500;
}
</style>