Files
his/openhis-ui-vue3/src/views/doctorstation/components/prescription/orderGroupDrawer.vue
关羽 4c7d362946 Fix Bug #403: 住院医生工作站:应用医嘱组套后,药品明细字段内容丢失未正确引入表格
组套应用时数据预处理缺失部分关键字段(doseUnitCode_dictText/positionName/
injectFlag/skinTestFlag),导致父组件构建行数据时无法获取完整信息。
在orderGroupDrawer的processed item中显式补充这些字段。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 11:57:54 +08:00

753 lines
26 KiB
Vue
Executable File
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>
<el-drawer v-model="drawer" title="医嘱组套" direction="ltr" size="700px">
<!-- 列表视图 -->
<div v-if="!editMode">
<div style="margin: 10px 0px">
<el-input
v-model="queryParams.searchKey"
placeholder="请输入组套名称搜索"
clearable
style="width: 45%; margin-bottom: -6px; margin-right: 20px"
@keyup.enter="handleSearch"
>
<template #append>
<el-button icon="Search" @click="handleSearch" />
</template>
</el-input>
<el-radio-group v-model="queryParams.rangeCode" @change="handelRadioChange">
<el-radio-button :label="1">个人</el-radio-button>
<el-radio-button :label="2">科室</el-radio-button>
<el-radio-button :label="3">全院</el-radio-button>
</el-radio-group>
<el-button
type="primary"
style="float: right"
@click="handleManageGroup"
v-if="checkPermi(['personalization:ordersGroupPackage:manage'])"
>
管理组套
</el-button>
</div>
<el-table
:data="filteredOrderList"
highlight-current-row
v-loading="loading"
border
stripe
style="margin-top: 10px"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="组套名称" align="left" prop="name" min-width="180" show-overflow-tooltip>
<template #default="scope">
<el-tooltip content="点击进入编辑" placement="top">
<span
style="cursor: pointer; color: #409eff; font-weight: 500; text-decoration: underline;"
@click="handleEditGroup(scope.row)"
>
{{ scope.row.name }}
</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="使用范围" align="center" prop="rangeCode_dictText" width="90" />
<el-table-column label="明细数量" align="center" width="80">
<template #default="scope">
<el-tag size="small" type="info">{{ scope.row.detailList?.length || 0 }} </el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150" fixed="right">
<template #default="scope">
<el-button type="primary" link @click="handlePreviewGroup(scope.row)">
预览
</el-button>
<el-button type="success" link @click="handleUseOrderGroup(scope.row)">
<el-icon><Select /></el-icon>
应用
</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="filteredOrderList.length === 0 && !loading" style="text-align: center; padding: 40px; color: #909399;">
<el-icon :size="48" style="margin-bottom: 10px"><Box /></el-icon>
<p>暂无组套数据</p>
<p style="font-size: 12px; margin-top: 5px;">您可以在"个性化设置 > 医嘱组套"中创建组套</p>
</div>
</div>
<!-- 编辑视图 -->
<div v-else>
<!-- 返回按钮 -->
<div style="margin-bottom: 15px; border-bottom: 1px solid #ebeef5; padding-bottom: 10px;">
<el-button @click="handleCancelEdit">
<el-icon><ArrowLeft /></el-icon>
返回列表
</el-button>
<span style="margin-left: 15px; font-weight: 600; font-size: 16px;">编辑组套</span>
<el-button type="primary" style="float: right" @click="handleSaveGroup" :loading="saving">
<el-icon><Check /></el-icon>
保存组套
</el-button>
</div>
<!-- 基本信息 -->
<div style="margin-bottom: 20px;">
<h4 style="margin: 0 0 15px 0; color: #606266;">基本信息</h4>
<el-form :model="editingGroup" label-width="100px" :inline="true">
<el-form-item label="组套名称" required>
<el-input
v-model="editingGroup.name"
placeholder="请输入组套名称"
style="width: 220px"
clearable
/>
</el-form-item>
<el-form-item label="组套类型">
<el-select v-model="editingGroup.typeEnum" placeholder="请选择" style="width: 150px">
<el-option label="医嘱组套" :value="1" />
<el-option label="诊疗组套" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="使用范围">
<el-select v-model="editingGroup.rangeCode" placeholder="请选择" style="width: 150px">
<el-option label="个人" :value="1" />
<el-option label="科室" :value="2" />
<el-option label="全院" :value="3" />
</el-select>
</el-form-item>
</el-form>
</div>
<!-- 项目列表 -->
<div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h4 style="margin: 0; color: #606266;">
项目列表
<el-tag size="small" type="info">{{ editingGroup.detailList?.length || 0 }} </el-tag>
</h4>
<el-button type="primary" link @click="handleAddItem">
<el-icon><Plus /></el-icon>
添加项目
</el-button>
</div>
<el-table :data="editingGroup.detailList" border size="small" v-loading="detailLoading">
<el-table-column type="index" label="#" width="40" align="center" />
<el-table-column label="医嘱名称" prop="orderDefinitionName" min-width="150" show-overflow-tooltip />
<el-table-column label="数量" width="80" align="center">
<template #default="scope">
<el-input-number
v-model="scope.row.quantity"
:min="1"
:controls="false"
size="small"
style="width: 60px"
/>
</template>
</el-table-column>
<el-table-column label="单位" prop="unitCodeName" width="60" align="center" />
<el-table-column label="用法" width="100" align="center">
<template #default="scope">
<el-select
v-model="scope.row.methodCode"
placeholder="用法"
size="small"
filterable
clearable
>
<el-option
v-for="dict in method_code"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="频次" width="100" align="center">
<template #default="scope">
<el-select
v-model="scope.row.rateCode"
placeholder="频次"
size="small"
filterable
clearable
>
<el-option
v-for="dict in rate_code"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="操作" width="60" align="center" fixed="right">
<template #default="scope">
<el-button type="danger" link size="small" @click="handleRemoveItem(scope.$index)">
<el-icon><Delete /></el-icon>
</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="!editingGroup.detailList || editingGroup.detailList.length === 0"
style="text-align: center; padding: 30px; color: #909399; border: 1px dashed #dcdfe6; margin-top: 10px;">
<p>暂无项目点击"添加项目"按钮添加</p>
</div>
</div>
</div>
<!-- 组套预览对话框 -->
<el-dialog v-model="previewVisible" title="组套预览" width="600px" append-to-body>
<div v-if="currentGroup">
<h4>{{ currentGroup.name }}</h4>
<p style="color: #909399; font-size: 14px;">
使用范围{{ currentGroup.rangeCode_dictText }} |
包含 {{ currentGroup.detailList?.length || 0 }} 个医嘱项
</p>
<el-table :data="currentGroup.detailList" border size="small" style="margin-top: 15px;">
<el-table-column type="index" label="#" width="40" align="center" />
<el-table-column label="医嘱名称" prop="orderDefinitionName" min-width="180" show-overflow-tooltip />
<el-table-column label="数量" prop="quantity" width="80" align="center" />
<el-table-column label="单位" prop="unitCodeName" width="80" align="center" />
<el-table-column label="用法" prop="methodCode_dictText" width="100" align="center" />
<el-table-column label="频次" prop="rateCode_dictText" width="100" align="center" />
</el-table>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="previewVisible = false">取消</el-button>
<el-button type="primary" @click="confirmUseGroup">应用到当前患者</el-button>
</span>
</template>
</el-dialog>
<!-- 添加项目对话框 -->
<el-dialog v-model="addItemVisible" title="添加医嘱项目" width="800px" append-to-body>
<div style="margin-bottom: 15px;">
<el-input
v-model="itemSearchKey"
placeholder="输入医嘱名称或拼音码搜索"
clearable
@keyup.enter="searchAdviceItems"
style="width: 300px"
>
<template #append>
<el-button icon="Search" @click="searchAdviceItems" />
</template>
</el-input>
</div>
<el-table
:data="adviceItemList"
border
highlight-current-row
@current-change="handleItemSelect"
v-loading="itemLoading"
max-height="400"
>
<el-table-column type="index" label="#" width="40" align="center" />
<el-table-column label="医嘱名称" prop="adviceName" min-width="150" show-overflow-tooltip />
<el-table-column label="类型" prop="adviceType_dictText" width="80" align="center" />
<el-table-column label="规格" prop="volume" width="100" show-overflow-tooltip />
<el-table-column label="单价" width="80" align="right">
<template #default="scope">
{{ scope.row.unitPrice?.toFixed(2) || '-' }}
</template>
</el-table-column>
<el-table-column label="单位" prop="unitCode_dictText" width="60" align="center" />
</el-table>
<div v-if="adviceItemList.length === 0 && !itemLoading"
style="text-align: center; padding: 30px; color: #909399;">
<p>请输入关键词搜索医嘱项目</p>
</div>
<!-- 选中项目的配置 -->
<div v-if="selectedItem" style="margin-top: 15px; padding: 15px; background: #f5f7fa; border-radius: 4px;">
<h4 style="margin: 0 0 10px 0;">配置项目{{ selectedItem.adviceName }}</h4>
<el-form :inline="true" label-width="80px">
<el-form-item label="数量">
<el-input-number v-model="newItemConfig.quantity" :min="1" :controls="false" />
</el-form-item>
<el-form-item label="用法">
<el-select v-model="newItemConfig.methodCode" placeholder="用法" filterable clearable style="width: 120px">
<el-option v-for="dict in method_code" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="频次">
<el-select v-model="newItemConfig.rateCode" placeholder="频次" filterable clearable style="width: 120px">
<el-option v-for="dict in rate_code" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="addItemVisible = false">取消</el-button>
<el-button type="primary" @click="confirmAddItem" :disabled="!selectedItem">
添加到组套
</el-button>
</span>
</template>
</el-dialog>
</el-drawer>
</template>
<script setup>
import { getOrderGroup, getAdviceBaseInfo, saveOrderGroup } from '../api';
import { Select, Box, ArrowLeft, Check, Plus, Delete } from '@element-plus/icons-vue';
import { checkPermi } from '@/utils/permission';
import { ElMessage } from 'element-plus';
const { proxy } = getCurrentInstance();
const { method_code, rate_code } = proxy.useDict('method_code', 'rate_code');
const props = defineProps({
diagnosis: {
type: Object,
required: true,
},
organizationId: {
type: [String, Number],
required: true,
},
patientInfo: {
type: Object,
default: () => ({})
}
});
const drawer = ref(false);
const orderList = ref([]);
const result = ref([]);
const loading = ref(false);
const previewVisible = ref(false);
const currentGroup = ref(null);
// 编辑模式相关
const editMode = ref(false);
const editingGroup = ref({});
const saving = ref(false);
const detailLoading = ref(false);
// 添加项目相关
const addItemVisible = ref(false);
const itemSearchKey = ref('');
const adviceItemList = ref([]);
const itemLoading = ref(false);
const selectedItem = ref(null);
const newItemConfig = ref({
quantity: 1,
methodCode: '',
rateCode: ''
});
const emit = defineEmits(['useOrderGroup']);
const queryParams = ref({
searchKey: '',
rangeCode: 2,
});
// 根据搜索关键字过滤组套列表
const filteredOrderList = computed(() => {
if (!queryParams.value.searchKey) {
return orderList.value;
}
const keyword = queryParams.value.searchKey.toLowerCase();
return orderList.value.filter(item =>
item.name && item.name.toLowerCase().includes(keyword)
);
});
function handleOpen(group = null, scope = 'personal') {
drawer.value = true;
// 设置范围
if (scope === 'personal') {
queryParams.value.rangeCode = 1;
} else if (scope === 'department') {
queryParams.value.rangeCode = 2;
} else if (scope === 'hospital') {
queryParams.value.rangeCode = 3;
}
// 如果有传入组套数据,进入编辑模式
if (group && group.groupPackageId) {
editMode.value = true;
editingGroup.value = JSON.parse(JSON.stringify(group));
if (!editingGroup.value.detailList) {
editingGroup.value.detailList = [];
}
} else {
editMode.value = false;
}
getList();
}
function handleSearch() {
console.log('搜索组套:', queryParams.value.searchKey);
}
function handelRadioChange(value) {
queryParams.value.rangeCode = value;
switch (value) {
case 1:
orderList.value = result.value.personalList || [];
break;
case 2:
orderList.value = result.value.organizationList || [];
break;
case 3:
orderList.value = result.value.hospitalList || [];
break;
}
}
// 进入编辑模式
function handleEditGroup(row) {
editMode.value = true;
// 深拷贝组套数据,避免直接修改原数据
editingGroup.value = JSON.parse(JSON.stringify(row));
// 确保 detailList 存在
if (!editingGroup.value.detailList) {
editingGroup.value.detailList = [];
}
}
// 取消编辑
function handleCancelEdit() {
editMode.value = false;
editingGroup.value = {};
}
// 保存组套
async function handleSaveGroup() {
if (!editingGroup.value.name?.trim()) {
ElMessage.warning('请输入组套名称');
return;
}
saving.value = true;
try {
// 构建保存数据
const saveData = {
groupPackageId: editingGroup.value.groupPackageId || editingGroup.value.id,
name: editingGroup.value.name,
packageTypeEnum: editingGroup.value.typeEnum || 1,
rangeCode: editingGroup.value.rangeCode,
detailList: editingGroup.value.detailList?.map((item, index) => ({
id: item.id,
orderDefinitionId: item.orderDefinitionId,
orderDefinitionName: item.orderDefinitionName,
quantity: item.quantity,
unitCode: item.unitCode,
unitCodeName: item.unitCodeName,
methodCode: item.methodCode,
rateCode: item.rateCode,
dose: item.dose,
doseQuantity: item.doseQuantity,
dispensePerDuration: item.dispensePerDuration,
sortOrder: index
})) || []
};
const res = await saveOrderGroup(saveData);
if (res.code === 200) {
ElMessage.success('保存成功');
editMode.value = false;
editingGroup.value = {};
// 刷新列表
getList();
} else {
ElMessage.error(res.message || '保存失败');
}
} catch (error) {
console.error('保存组套失败:', error);
ElMessage.error('保存失败:' + (error.message || '未知错误'));
} finally {
saving.value = false;
}
}
// 打开添加项目对话框
function handleAddItem() {
addItemVisible.value = true;
itemSearchKey.value = '';
adviceItemList.value = [];
selectedItem.value = null;
newItemConfig.value = {
quantity: 1,
methodCode: '',
rateCode: ''
};
}
// 搜索医嘱项目
async function searchAdviceItems() {
console.log('点击搜索按钮, itemSearchKey:', itemSearchKey.value, 'organizationId:', props.organizationId);
if (!itemSearchKey.value.trim()) {
ElMessage.warning('请输入搜索关键词');
return;
}
// 确保 organizationId 是数字类型
const orgId = Number(props.organizationId) || null;
itemLoading.value = true;
try {
const params = {
searchKey: itemSearchKey.value,
organizationId: orgId,
pageNo: 1,
pageSize: 50,
adviceTypes: '1,2,3'
};
console.log('搜索参数:', params);
const res = await getAdviceBaseInfo(params);
console.log('搜索返回结果:', res);
adviceItemList.value = res.data?.records || res.rows || [];
} catch (error) {
console.error('搜索医嘱项目失败:', error);
ElMessage.error('搜索失败: ' + (error.message || '未知错误'));
} finally {
itemLoading.value = false;
}
}
// 选择医嘱项目
function handleItemSelect(row) {
selectedItem.value = row;
// 设置默认值
newItemConfig.value = {
quantity: 1,
methodCode: row?.methodCode || '',
rateCode: row?.rateCode || ''
};
}
// 确认添加项目到组套
function confirmAddItem() {
if (!selectedItem.value) return;
const newItem = {
orderDefinitionId: selectedItem.value.adviceDefinitionId,
orderDefinitionName: selectedItem.value.adviceName,
quantity: newItemConfig.value.quantity,
unitCode: selectedItem.value.unitCode,
unitCodeName: selectedItem.value.unitCode_dictText,
methodCode: newItemConfig.value.methodCode,
rateCode: newItemConfig.value.rateCode,
dose: selectedItem.value.dose,
doseQuantity: selectedItem.value.doseQuantity,
dispensePerDuration: selectedItem.value.dispensePerDuration,
// 保留医嘱库完整信息用于后续应用
orderDetailInfos: selectedItem.value
};
if (!editingGroup.value.detailList) {
editingGroup.value.detailList = [];
}
editingGroup.value.detailList.push(newItem);
addItemVisible.value = false;
ElMessage.success('添加成功');
}
// 移除项目
function handleRemoveItem(index) {
editingGroup.value.detailList.splice(index, 1);
}
// 单击应用按钮(应用组套)
async function handleUseOrderGroup(row) {
if (!row.detailList || row.detailList.length === 0) {
ElMessage.warning('该组套没有明细项');
return;
}
// 🔧 Bug 修复:组套保存时未持久化 orderDetailInfos导致应用时缺失医嘱库信息。
// 通过 API 批量查询补全,确保 setValue 能获取到 adviceType、inventoryList、priceList 等关键字段。
const itemsMissingDetail = row.detailList.filter(
item => !item.orderDetailInfos || Object.keys(item.orderDetailInfos).length === 0
);
const detailMap = {};
if (itemsMissingDetail.length > 0) {
const ids = itemsMissingDetail
.map(item => item.orderDefinitionId)
.filter(Boolean)
.join(',');
if (ids) {
try {
const res = await getAdviceBaseInfo({
adviceDefinitionIdParamList: ids,
organizationId: props.organizationId,
});
const records = res.data?.records || res.rows || [];
records.forEach(rec => {
if (rec.adviceDefinitionId) {
detailMap[rec.adviceDefinitionId] = rec;
}
});
} catch (e) {
// 批量查询失败,使用原始 orderDetailInfos
}
}
}
// 🔧 辅助函数:根据字典 code 查找对应的 dictText
const findDictText = (dictList, code) => {
if (!code || !dictList?.value?.length) return '';
const found = dictList.value.find(d => d.value === code);
return found?.label || '';
};
// 🔧 数据预处理:确保每个明细项都有完整的医嘱信息
const processedDetailList = row.detailList.map(item => {
// 优先使用组套中已带的 orderDetailInfos否则使用 API 查询结果
const orderDetail = (item.orderDetailInfos && Object.keys(item.orderDetailInfos).length > 0)
? item.orderDetailInfos
: (detailMap[item.orderDefinitionId] || {});
// 🔧 修复:组套明细只存了 methodCode/rateCode没有 dictText。
// 用字典查找补充 dictText确保界面显示正确的用法/频次名称。
const resolvedMethodCode = item.methodCode ?? orderDetail.methodCode;
const resolvedRateCode = item.rateCode ?? orderDetail.rateCode;
const methodCodeDictText = item.methodCode_dictText
|| findDictText(method_code, resolvedMethodCode)
|| orderDetail.methodCode_dictText
|| '';
const rateCodeDictText = item.rateCode_dictText
|| findDictText(rate_code, resolvedRateCode)
|| orderDetail.rateCode_dictText
|| '';
return {
// 组套明细字段
...item,
// 医嘱库字段
adviceName: orderDetail.adviceName || item.orderDefinitionName || '未知项目',
adviceType: orderDetail.adviceType,
adviceDefinitionId: item.orderDefinitionId || orderDetail.adviceDefinitionId,
// 价格和库存
unitPrice: orderDetail.unitPrice,
minUnitPrice: orderDetail.minUnitPrice,
inventoryList: orderDetail.inventoryList || [],
priceList: orderDetail.priceList || [],
partPercent: orderDetail.partPercent ?? 1,
partAttributeEnum: orderDetail.partAttributeEnum,
unitConversionRatio: orderDetail.unitConversionRatio,
positionId: item.positionId ?? orderDetail.positionId,
defaultLotNumber: orderDetail.defaultLotNumber,
// 单位信息
unitCode: item.unitCode ?? orderDetail.unitCode,
categoryCode: item.categoryCode ?? orderDetail.categoryCode,
unitCodeName: item.unitCodeName || orderDetail.unitCode_dictText,
minUnitCode: orderDetail.minUnitCode,
doseUnitCode: orderDetail.doseUnitCode,
doseUnitCode_dictText: orderDetail.doseUnitCode_dictText || '',
// 药房/科室名称setValue 通过库存查找设置,但需确保 orderDetail 中有)
positionName: orderDetail.positionName || '',
// 注射/皮试标识(表格列显示依赖这些字段)
injectFlag: orderDetail.injectFlag,
injectFlag_enumText: orderDetail.injectFlag_enumText || '',
skinTestFlag: orderDetail.skinTestFlag,
skinTestFlag_enumText: orderDetail.skinTestFlag_enumText || '',
// 字典文本(传递到 item 层级,避免后续代码依赖 mergedDetail 再查一次)
methodCode_dictText: methodCodeDictText,
rateCode_dictText: rateCodeDictText,
// 合并后的完整对象(用于 setValue
// 先展开 orderDetail 获取所有药品基础字段categoryCode、minUnitCode、doseUnitCode、
// partPercent、partAttributeEnum、unitConversionRatio、defaultLotNumber 等),
// 再用组套用户覆盖值覆盖,确保单次剂量/频次/用法/用药天数/总量等不被丢失
mergedDetail: {
...orderDetail,
adviceName: orderDetail.adviceName || item.orderDefinitionName || '未知项目',
adviceType: orderDetail.adviceType,
quantity: item.quantity,
unitCode: item.unitCode ?? orderDetail.unitCode,
categoryCode: item.categoryCode ?? orderDetail.categoryCode,
unitCodeName: item.unitCodeName,
dose: item.dose ?? orderDetail.dose,
rateCode: resolvedRateCode,
rateCode_dictText: rateCodeDictText,
methodCode: resolvedMethodCode,
methodCode_dictText: methodCodeDictText,
dispensePerDuration: item.dispensePerDuration ?? orderDetail.dispensePerDuration,
doseQuantity: item.doseQuantity ?? orderDetail.doseQuantity,
positionId: item.positionId ?? orderDetail.positionId,
orgId: item.orgId ?? orderDetail.orgId,
orgName: item.orgName ?? orderDetail.orgName,
groupId: item.groupId,
groupOrder: item.groupOrder,
// 🔧 类型默认为临时医嘱2=临时1=长期)
therapyEnum: item.therapyEnum ?? orderDetail.therapyEnum ?? '2',
}
};
});
emit('useOrderGroup', processedDetailList);
drawer.value = false;
editMode.value = false;
}
// 预览组套
function handlePreviewGroup(row) {
currentGroup.value = row;
previewVisible.value = true;
}
// 确认应用组套(从预览对话框)
async function confirmUseGroup() {
if (currentGroup.value) {
await handleUseOrderGroup(currentGroup.value);
previewVisible.value = false;
}
}
// 跳转到组套管理页面
function handleManageGroup() {
window.open('/basicmanage/ordersCombination', '_blank');
}
function getList() {
loading.value = true;
getOrderGroup({ organizationId: props.organizationId })
.then((res) => {
result.value = res.data || {};
handelRadioChange(queryParams.value.rangeCode);
})
.catch((err) => {
console.error('获取组套列表失败:', err);
ElMessage.error('获取组套列表失败');
})
.finally(() => {
loading.value = false;
});
}
defineExpose({
handleOpen,
});
</script>
<style scoped>
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>