feat(medicalOrderSet): 优化医嘱组套功能实现

- 实现医嘱基础列表的分页功能,添加loading状态和缓存机制
- 添加防抖处理和组织机构ID参数支持,优化性能表现
- 实现医嘱组套的完整编辑功能,包括增删改查操作界面
- 添加医嘱组套预览、应用和管理功能模块
- 实现西医组套筛选功能,确保tcmFlag参数正确传递
- 优化医嘱组套数据结构,完善明细项信息处理逻辑
- 添加表单验证和错误处理,提升用户体验和系统稳定性
- 重构代码结构,采用响应式设计提高可维护性
This commit is contained in:
2026-03-17 09:57:21 +08:00
parent 03939fb232
commit 8a7d2abb4a
30 changed files with 2066 additions and 543 deletions

View File

@@ -1,38 +1,310 @@
<template>
<el-drawer v-model="drawer" title="组套信息" direction="ltr">
<div style="margin: 10px 0px">
<el-input
v-model="queryParams.searchKey"
placeholder="请输入组套信息"
clearable
style="width: 45%; margin-bottom: -6px; margin-right: 50px"
@keyup.enter="getList"
<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"
>
<template #append>
<el-button icon="Search" @click="getList" />
</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-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>
<el-table :data="orderList">
<el-table-column label="组套名称" align="center" prop="name" />
<!-- <el-table-column label="组套类型" align="center" prop="typeEnum_enumText" /> -->
<el-table-column label="使用范围" align="center" prop="rangeCode_dictText" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button type="primary" link @click="handleUseOrderGroup(scope.row)">选择</el-button>
</template>
</el-table-column>
</el-table>
<!-- 编辑视图 -->
<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} from '../api';
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: {
@@ -40,52 +312,364 @@ const props = defineProps({
required: true,
},
organizationId: {
type: String,
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({
typeEnum: 1,
searchKey: '',
rangeCode: 2,
});
function handleOpen() {
// 根据搜索关键字过滤组套列表
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;
orderList.value = result.value.personalList || [];
break;
case 2:
orderList.value = result.value.organizationList;
orderList.value = result.value.organizationList || [];
break;
case 3:
orderList.value = result.value.hospitalList;
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);
}
// 单击应用按钮
function handleUseOrderGroup(row) {
emit('useOrderGroup', row.detailList);
if (!row.detailList || row.detailList.length === 0) {
ElMessage.warning('该组套没有明细项');
return;
}
// 🔧 数据预处理:确保每个明细项都有完整的医嘱信息
const processedDetailList = row.detailList.map(item => {
const orderDetail = item.orderDetailInfos || {};
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,
positionId: orderDetail.positionId,
defaultLotNumber: orderDetail.defaultLotNumber,
// 单位信息
unitCode: item.unitCode || orderDetail.unitCode,
unitCodeName: item.unitCodeName || orderDetail.unitCode_dictText,
minUnitCode: orderDetail.minUnitCode,
doseUnitCode: orderDetail.doseUnitCode,
// 合并后的完整对象(用于 setValue
mergedDetail: {
...orderDetail,
adviceName: orderDetail.adviceName || item.orderDefinitionName || '未知项目',
adviceType: orderDetail.adviceType,
quantity: item.quantity,
unitCode: item.unitCode || orderDetail.unitCode,
unitCodeName: item.unitCodeName,
dose: item.dose || orderDetail.dose,
rateCode: item.rateCode || orderDetail.rateCode,
methodCode: item.methodCode || orderDetail.methodCode,
dispensePerDuration: item.dispensePerDuration || orderDetail.dispensePerDuration,
doseQuantity: item.doseQuantity,
inventoryList: orderDetail.inventoryList || [],
priceList: orderDetail.priceList || [],
partPercent: orderDetail.partPercent || 1,
positionId: orderDetail.positionId,
defaultLotNumber: orderDetail.defaultLotNumber,
}
};
});
emit('useOrderGroup', processedDetailList);
drawer.value = false;
editMode.value = false;
}
// 预览组套
function handlePreviewGroup(row) {
currentGroup.value = row;
previewVisible.value = true;
}
// 确认应用组套(从预览对话框)
function confirmUseGroup() {
if (currentGroup.value) {
handleUseOrderGroup(currentGroup.value);
previewVisible.value = false;
}
}
// 跳转到组套管理页面
function handleManageGroup() {
window.open('/basicmanage/ordersCombination', '_blank');
}
function getList() {
getOrderGroup({ organizationId: props.organizationId }).then((res) => {
result.value = res.data;
handelRadioChange(queryParams.value.rangeCode);
});
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>
</script>
<style scoped>
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div style="width: 100%">
<div style="width: 100%; display: flex; flex-direction: column;">
<div style="margin-bottom: 5px">
<el-button type="primary" @click="handleAddPrescription()" :disabled="false">
新增
@@ -7,8 +7,8 @@
<el-button type="primary" @click="handleSaveBatch()" :disabled="isSaving"> 保存 </el-button>
<el-button type="primary" @click="handleSave()" :disabled="isSaving"> 签发 </el-button>
<el-button type="warning" plain @click="handleSingOut()" :disabled="false"> 撤回 </el-button>
<el-button type="primary" plain @click="proxy.$refs.orderGroupRef.handleOpen()" :disabled="false">
组套
<el-button type="primary" plain @click="openOrderSetDialog('personal')" :disabled="false">
另存组套
</el-button>
<el-button type="primary" plain :disabled="false" @click="proxy.$refs.prescriptionHistoryRef.handleOpen()">
历史
@@ -49,9 +49,141 @@
刷新
</el-button>
</div>
<el-table max-height="650" ref="prescriptionRef" :data="prescriptionList" row-key="uniqueKey" border
@cell-click="clickRow" @row-dblclick="clickRowDb" v-loading="loading" :expand-row-keys="expandOrder"
@select="handleSelectionChange">
<!-- V1.3 风格侧边栏组套列表 -->
<div class="order-group-container">
<div class="order-group-sidebar">
<div class="order-group-section">
<div class="order-group-section-header" @click="toggleOrderGroupSection('personal')">
<span>个人</span>
<el-icon @click.stop="toggleOrderGroupSection('personal')">
<Minus v-if="personalOrderGroupsExpanded" />
<Plus v-else />
</el-icon>
</div>
<div v-show="personalOrderGroupsExpanded" class="order-group-section-body">
<div
v-for="item in personalOrderGroups"
:key="item.id || item.groupPackageId || item.name"
class="order-group-item"
@dblclick="handleClickOrderGroup(item)"
@contextmenu.prevent="handleOrderGroupContextMenu($event, item, 'personal')"
>
<span class="order-group-item-name">{{ item.name }}</span>
<div class="order-group-item-actions" @click.stop>
<el-icon
v-if="hasGroupPermission(item, 'personal')"
class="order-group-action-icon"
@click="handleEditOrderGroup(item, 'personal')"
title="编辑"
>
<Edit />
</el-icon>
<el-icon
v-if="hasGroupPermission(item, 'personal')"
class="order-group-action-icon"
@click="handleDeleteOrderGroup(item, 'personal')"
title="删除"
>
<Delete />
</el-icon>
</div>
</div>
<div class="order-group-add" @click="openOrderSetDialog('personal')">
<el-icon><Plus /></el-icon>
添加
</div>
</div>
</div>
<div class="order-group-section">
<div class="order-group-section-header" @click="toggleOrderGroupSection('department')">
<span>科室</span>
<el-icon @click.stop="toggleOrderGroupSection('department')">
<Minus v-if="deptOrderGroupsExpanded" />
<Plus v-else />
</el-icon>
</div>
<div v-show="deptOrderGroupsExpanded" class="order-group-section-body">
<div
v-for="item in deptOrderGroups"
:key="item.id || item.groupPackageId || item.name"
class="order-group-item"
@dblclick="handleClickOrderGroup(item)"
@contextmenu.prevent="handleOrderGroupContextMenu($event, item, 'department')"
>
<span class="order-group-item-name">{{ item.name }}</span>
<div class="order-group-item-actions" @click.stop>
<el-icon
v-if="hasGroupPermission(item, 'department')"
class="order-group-action-icon"
@click="handleEditOrderGroup(item, 'department')"
title="编辑"
>
<Edit />
</el-icon>
<el-icon
v-if="hasGroupPermission(item, 'department')"
class="order-group-action-icon"
@click="handleDeleteOrderGroup(item, 'department')"
title="删除"
>
<Delete />
</el-icon>
</div>
</div>
<div class="order-group-add" @click="openOrderSetDialog('department')">
<el-icon><Plus /></el-icon>
添加
</div>
</div>
</div>
<div class="order-group-section">
<div class="order-group-section-header" @click="toggleOrderGroupSection('hospital')">
<span>全院</span>
<el-icon @click.stop="toggleOrderGroupSection('hospital')">
<Minus v-if="hospitalOrderGroupsExpanded" />
<Plus v-else />
</el-icon>
</div>
<div v-show="hospitalOrderGroupsExpanded" class="order-group-section-body">
<div
v-for="item in hospitalOrderGroups"
:key="item.id || item.groupPackageId || item.name"
class="order-group-item"
@dblclick="handleClickOrderGroup(item)"
@contextmenu.prevent="handleOrderGroupContextMenu($event, item, 'hospital')"
>
<span class="order-group-item-name">{{ item.name }}</span>
<div class="order-group-item-actions" @click.stop>
<el-icon
v-if="hasGroupPermission(item, 'hospital')"
class="order-group-action-icon"
@click="handleEditOrderGroup(item, 'hospital')"
title="编辑"
>
<Edit />
</el-icon>
<el-icon
v-if="hasGroupPermission(item, 'hospital')"
class="order-group-action-icon"
@click="handleDeleteOrderGroup(item, 'hospital')"
title="删除"
>
<Delete />
</el-icon>
</div>
</div>
<div class="order-group-add" @click="openOrderSetDialog('hospital')">
<el-icon><Plus /></el-icon>
添加
</div>
</div>
</div>
</div>
<el-table max-height="650" ref="prescriptionRef" :data="prescriptionList" row-key="uniqueKey" border
@cell-click="clickRow" @row-dblclick="clickRowDb" v-loading="loading" :expand-row-keys="expandOrder"
@select="handleSelectionChange">
<el-table-column type="expand" width="1" style="width: 0">
<template #default="scope">
<el-form :model="scope.row" :rules="rowRules" :ref="'formRef' + scope.$index" v-arrow-navigate>
@@ -454,7 +586,10 @@
<el-popover :popper-style="{ padding: '0' }" placement="bottom-start" :visible="scope.row.showPopover"
:width="1200" trigger="manual">
<adviceBaseList ref="adviceTableRef" :popoverVisible="scope.row.showPopover"
:adviceQueryParams="adviceQueryParams" :patientInfo="props.patientInfo" @selectAdviceBase="
:adviceQueryParams="adviceQueryParams" :patientInfo="props.patientInfo"
:preloadedData="preloadedAdviceData"
:preloadedLoaded="preloadedAdviceLoaded"
@selectAdviceBase="
(row) => {
selectAdviceBase(scope.row.uniqueKey, row);
}
@@ -578,8 +713,15 @@
</template>
</el-table-column>
</el-table>
<OrderGroupDrawer ref="orderGroupRef" :diagnosis="diagnosisInfo" :organizationId="props.patientInfo.orgId"
@useOrderGroup="handleSaveGroup" />
</div>
<MedicalOrderSetDialog
ref="orderSetDialogRef"
:is-doctor-station="true"
:method_code="method_code"
:rate_code="rate_code"
:show-range-selector="true"
@saved="handleOrderSetSaved"
/>
<PrescriptionHistory ref="prescriptionHistoryRef" :diagnosis="diagnosisInfo" :patientInfo="props.patientInfo"
@userPrescriptionHistory="handleSaveHistory" />
<OrderBindInfo ref="orderBindInfoRef" @submit="handleOrderBindInfo" />
@@ -631,19 +773,22 @@ import {
savePrescriptionSign,
singOut,
updateGroupId,
getOrderGroup,
deleteGroup,
queryGroupDetail,
} from '../api';
import { advicePrint, getAdjustPriceSwitchState } from '@/api/public';
import adviceBaseList from '../adviceBaseList.vue';
import { computed, getCurrentInstance, nextTick, ref, watch, onMounted, onBeforeUnmount } from 'vue';
import { calculateQuantityByDays } from '@/utils/his';
import OrderGroupDrawer from './orderGroupDrawer';
import MedicalOrderSetDialog from '@/views/basicmanage/medicalOrderSet/components/MedicalOrderSetDialog.vue';
import PrescriptionHistory from './prescriptionHistory';
import OrderBindInfo from './orderBindInfo';
import SkinTestInfo from './skinTestInfo';
import Decimal from 'decimal.js';
import useUserStore from '@/store/modules/user';
import { ElMessage, ElMessageBox } from 'element-plus';
import { ArrowDown, Search, Memo } from '@element-plus/icons-vue';
import { ArrowDown, Search, Memo, Minus, Plus, Edit, Delete } from '@element-plus/icons-vue';
import printUtils, { getPrinterList, PRINT_TEMPLATE, savePrinterToCache, } from '@/utils/printUtils';
import Template from "@/views/inpatientDoctor/home/emr/components/template.vue";
@@ -706,6 +851,27 @@ const isAdding = ref(false);
const isSaving = ref(false);
const prescriptionRef = ref();
const expandOrder = ref([]); //目前的展开行
// 预加载的医嘱基础数据 - 用于优化搜索响应速度
const preloadedAdviceData = ref([]);
const preloadedAdviceLoading = ref(false);
const preloadedAdviceLoaded = ref(false);
// V1.3 风格侧边栏 - 组套列表相关变量
const personalOrderGroups = ref([]);
const deptOrderGroups = ref([]);
const hospitalOrderGroups = ref([]);
const personalOrderGroupsExpanded = ref(true);
const deptOrderGroupsExpanded = ref(false);
const hospitalOrderGroupsExpanded = ref(false);
const orderGroupLoaded = ref({
personal: false,
department: false,
hospital: false,
});
// 更新展开行的函数
const updateExpandOrder = (keys) => {
expandOrder.value = keys;
@@ -810,15 +976,58 @@ const currentPrescriptionId = ref(null); // 当前活跃的处方ID
const prescriptionCounter = ref(0); // 处方计数器
const allPrescriptionsData = ref({}); // 存储所有处方的数据,格式: { prescriptionId: [...prescriptionList] }
const allPrescriptionCheckStates = ref({}); // 存储每个处方的选中状态,格式: { prescriptionId: { checkedIndexes: [], checkAll: false } }
/**
* 预加载医嘱基础数据 - 在后台加载药品/诊疗数据,提升搜索响应速度
*/
async function preloadAdviceData() {
if (preloadedAdviceLoading.value || preloadedAdviceLoaded.value) return;
if (!props.patientInfo?.orgId) return;
preloadedAdviceLoading.value = true;
console.log('[Preload] 开始预加载医嘱基础数据...');
try {
const res = await getAdviceBaseInfo({
pageSize: 100,
pageNo: 1,
adviceTypes: '1,2,3', // 药品、耗材、诊疗
organizationId: props.patientInfo.orgId,
});
if (res.data?.records?.length > 0) {
// 过滤无库存的药品
preloadedAdviceData.value = res.data.records.filter((item) => {
if (item.adviceType == 1 || item.adviceType == 2) {
const inventoryList = item.inventoryList || [];
const totalQuantity = inventoryList.reduce((sum, inv) => sum + (inv.quantity || 0), 0);
return totalQuantity > 0;
}
return true;
});
preloadedAdviceLoaded.value = true;
console.log(`[Preload] 预加载完成,共 ${preloadedAdviceData.value.length} 条数据`);
}
} catch (error) {
console.error('[Preload] 预加载失败:', error);
} finally {
preloadedAdviceLoading.value = false;
}
}
onMounted(() => {
document.addEventListener('keydown', escKeyListener);
// 初始化时预加载组织数据,避免选择诊疗/会诊项目时显示"无数据"
// 初始化时预加载组织数据避免选择诊疗/会诊项目时显示"无数据"
getOrgList();
// 初始化时自动创建第一个西药处方
if (westernPrescriptions.value.length === 0) {
createNewPrescription();
handleAddPrescription(null, false);
}
// 默认展开个人:只请求个人组套
if (props.patientInfo?.orgId) {
fetchOrderGroups('personal');
}
});
onBeforeUnmount(() => {
@@ -863,6 +1072,20 @@ watch(
}
);
// 初次进入页面时 patientInfo 可能还未就绪,导致个人组套未拉取;这里等 orgId 有值后再补拉一次
watch(
() => props.patientInfo?.orgId,
(orgId) => {
if (!orgId) return;
if (!orderGroupLoaded.value.personal) {
fetchOrderGroups('personal');
}
// 预加载医嘱基础数据,提升搜索响应速度
preloadAdviceData();
},
{ immediate: true }
);
// // 打印机相关响应式变量
// const printerList = ref([]);
// const selectedPrinter = ref('');
@@ -1282,7 +1505,14 @@ function getListInfo(addNewRow) {
const res = await getPrescriptionList(props.patientInfo.encounterId);
prescriptionList.value = res.data.map((item) => {
const contentJson = JSON.parse(item.contentJson);
// 防止 contentJson 为空或 undefined 导致 JSON.parse 报错
let contentJson = {};
try {
contentJson = item.contentJson ? JSON.parse(item.contentJson) : {};
} catch (e) {
console.warn('JSON.parse contentJson 失败:', e, 'item:', item);
contentJson = {};
}
// 🎯 判断是否为会诊医嘱:
// 方法1检查 category_enum 字段(需要后端重新编译)
@@ -1388,6 +1618,13 @@ function handleSelectionChange(selection, row) {
// 新增医嘱
function handleAddPrescription(prescriptionId, showWarning = true) {
// 关闭所有已打开的popover避免遮挡新增按钮
prescriptionList.value.forEach(row => {
if (row.showPopover) {
row.showPopover = false;
}
});
// 如果传入了处方ID先切换到该处方
if (prescriptionId && prescriptionId !== currentPrescriptionId.value) {
switchToActivePrescription(prescriptionId);
@@ -2613,7 +2850,8 @@ function setValue(row) {
// 🔧 Bug #147 修复:耗材(adviceType=4)和诊疗(adviceType=3, adviceType=5)不检查库存
// 耗材从getDeviceList接口获取inventoryList为空但需要设置价格
if (row.adviceType != 3 && row.adviceType != 4 && row.adviceType != 5) {
if (row.inventoryList && row.inventoryList.length == 0) {
// 🔧 Bug #144 补充修复:检查 inventoryList 是否存在,避免 undefined 错误
if (!row.inventoryList || row.inventoryList.length == 0) {
expandOrder.value = [];
proxy.$modal.msgWarning(row.adviceName + '无库存');
return;
@@ -2693,19 +2931,29 @@ function setValue(row) {
// 诊疗类型adviceType == 3
prescriptionList.value[rowIndex.value].orgId = JSON.parse(JSON.stringify(row)).positionId;
prescriptionList.value[rowIndex.value].quantity = 1;
prescriptionList.value[rowIndex.value].unitPrice = row.priceList[0].price;
prescriptionList.value[rowIndex.value].totalPrice = row.priceList[0].price;
// 🔧 Bug #144 修复:安全访问 priceList防止 orderDetailInfos 为空时出错
if (row.priceList && row.priceList.length > 0) {
prescriptionList.value[rowIndex.value].unitPrice = row.priceList[0].price;
prescriptionList.value[rowIndex.value].totalPrice = row.priceList[0].price;
} else {
prescriptionList.value[rowIndex.value].unitPrice = 0;
prescriptionList.value[rowIndex.value].totalPrice = 0;
console.warn('医嘱项价格列表为空:', row.adviceName || '未知医嘱');
}
}
}
}
// 选择组套
// 选择组套 - 适配新版 OrderGroupDrawer 组件
function handleSaveGroup(orderGroupList) {
if (!orderGroupList || !Array.isArray(orderGroupList) || orderGroupList.length === 0) {
proxy.$modal.msgWarning('组套数据为空');
return;
}
// 记录成功添加的数量
let successCount = 0;
orderGroupList.forEach((item) => {
rowIndex.value = prescriptionList.value.length;
@@ -2715,40 +2963,62 @@ function handleSaveGroup(orderGroupList) {
return;
}
// 获取实际的医嘱项目数据
// 如果 item 包含 orderDetailInfos则使用它否则直接使用 item
const orderDetail = item.orderDetailInfos || item;
// 🔥 新版组件已经预处理了数据,优先使用 mergedDetail
// 如果没有 mergedDetail则回退到旧版处理逻辑
const mergedDetail = item.mergedDetail || {
// 医嘱库基础信息
...(item.orderDetailInfos || {}),
// 组套级别字段
adviceName: item.orderDetailInfos?.adviceName || item.orderDefinitionName || '未知项目',
adviceType: item.orderDetailInfos?.adviceType,
adviceDefinitionId: item.orderDefinitionId || item.orderDetailInfos?.adviceDefinitionId,
quantity: item.quantity,
unitCode: item.unitCode || item.orderDetailInfos?.unitCode,
unitCodeName: item.unitCodeName,
dose: item.dose || item.orderDetailInfos?.dose,
rateCode: item.rateCode || item.orderDetailInfos?.rateCode,
methodCode: item.methodCode || item.orderDetailInfos?.methodCode,
dispensePerDuration: item.dispensePerDuration || item.orderDetailInfos?.dispensePerDuration,
doseQuantity: item.doseQuantity,
inventoryList: item.orderDetailInfos?.inventoryList || [],
priceList: item.orderDetailInfos?.priceList || [],
partPercent: item.orderDetailInfos?.partPercent || 1,
positionId: item.orderDetailInfos?.positionId,
defaultLotNumber: item.orderDetailInfos?.defaultLotNumber,
};
if (!orderDetail) {
console.warn('组套中的项目详情为空');
return;
}
// 在 setValue 之前预初始化空行
prescriptionList.value[rowIndex.value] = {
uniqueKey: nextId.value++,
isEdit: true,
statusEnum: 1,
};
// 使用医嘱项目详情设置值
setValue(orderDetail);
setValue(mergedDetail);
// 创建新的处方项目
const newRow = {
...prescriptionList.value[rowIndex.value],
uniqueKey: nextId.value++,
patientId: props.patientInfo.patientId,
encounterId: props.patientInfo.encounterId,
accountId: accountId.value, // 费用性质
quantity: item.quantity, // 数量
methodCode: item.methodCode, // 用法
rateCode: item.rateCode, // 频次
dispensePerDuration: item.dispensePerDuration, // 用药天数
dose: item.dose, // 单次用量
doseQuantity: item.doseQuantity, // 单次用量(小单位)
executeNum: 1, // 执行次数默认1
unitCode: item.unitCode, // 大单位
accountId: accountId.value,
quantity: item.quantity,
methodCode: item.methodCode,
rateCode: item.rateCode,
dispensePerDuration: item.dispensePerDuration,
dose: item.dose,
doseQuantity: item.doseQuantity,
executeNum: 1,
unitCode: item.unitCode,
unitCode_dictText: item.unitCodeName || '',
statusEnum: 1, // 签发状态 默认是待保存
orgId: item.positionId, // 默认执行科室
dbOpType: prescriptionList.value[rowIndex.value].requestId ? '2' : '1', // 1新增2编辑
conditionId: conditionId.value, // 诊断id
conditionDefinitionId: conditionDefinitionId.value, // 诊断定义id
encounterDiagnosisId: encounterDiagnosisId.value, // 就诊诊断id
statusEnum: 1,
orgId: item.orderDetailInfos?.positionId || mergedDetail.positionId,
dbOpType: prescriptionList.value[rowIndex.value].requestId ? '2' : '1',
conditionId: conditionId.value,
conditionDefinitionId: conditionDefinitionId.value,
encounterDiagnosisId: encounterDiagnosisId.value,
};
// 计算价格和总量
@@ -2760,12 +3030,18 @@ function handleSaveGroup(orderGroupList) {
} else {
newRow.price = newRow.unitPrice;
newRow.totalPrice = (item.quantity * newRow.unitPrice).toFixed(6);
newRow.minUnitQuantity = item.quantity * (orderDetail.partPercent || 1);
newRow.minUnitQuantity = item.quantity * (item.orderDetailInfos?.partPercent || mergedDetail.partPercent || 1);
}
newRow.contentJson = JSON.stringify(newRow);
prescriptionList.value[rowIndex.value] = newRow;
successCount++;
});
// 显示成功提示
if (successCount > 0) {
proxy.$modal.msgSuccess(`成功添加 ${successCount} 个医嘱项`);
}
}
// 历史医嘱复用
@@ -3229,6 +3505,326 @@ function calculateQuantity(type, dose, count, partPercent) {
return Math.ceil(dose) * count;
}
}
// V1.3 风格侧边栏 - 组套列表方法
async function fetchOrderGroups(scope, { force = false } = {}) {
console.log('[fetchOrderGroups] 开始加载, scope:', scope, 'force:', force, '当前loaded状态:', orderGroupLoaded.value[scope]);
if (!force && orderGroupLoaded.value[scope]) {
console.log('[fetchOrderGroups] 已加载过且非强制刷新,跳过');
return;
}
const orgId = props.patientInfo?.orgId;
console.log('[fetchOrderGroups] orgId:', orgId);
if (!orgId) {
console.log('[fetchOrderGroups] orgId 为空,返回');
return;
}
try {
if (scope === 'personal') {
// 查询个人组套:不传 organizationId后端使用登录用户的 practitionerId 查询
console.log('[fetchOrderGroups] 查询个人组套, params:', { organizationId: null, practitionerId: null });
const res = await getOrderGroup({ organizationId: null });
console.log('[fetchOrderGroups] 个人组套API返回完整数据:', JSON.stringify(res, null, 2));
const data = res?.data || {};
console.log('[fetchOrderGroups] data Keys:', Object.keys(data));
personalOrderGroups.value = data.personalList || [];
console.log('[fetchOrderGroups] 个人组套数据:', personalOrderGroups.value);
} else if (scope === 'department') {
const res = await getOrderGroup({ organizationId: null });
const data = res?.data || {};
deptOrderGroups.value = data.organizationList || [];
} else if (scope === 'hospital') {
const res = await getOrderGroup({ organizationId: orgId });
const data = res?.data || {};
hospitalOrderGroups.value = data.hospitalList || [];
}
orderGroupLoaded.value[scope] = true;
console.log('[fetchOrderGroups] 加载完成, scope:', scope);
} catch (e) {
console.error('[fetchOrderGroups] 加载组套列表失败:', e);
}
}
function toggleOrderGroupSection(scope) {
if (scope === 'personal') {
personalOrderGroupsExpanded.value = !personalOrderGroupsExpanded.value;
if (personalOrderGroupsExpanded.value) fetchOrderGroups('personal');
return;
}
if (scope === 'department') {
deptOrderGroupsExpanded.value = !deptOrderGroupsExpanded.value;
if (deptOrderGroupsExpanded.value) fetchOrderGroups('department');
return;
}
if (scope === 'hospital') {
hospitalOrderGroupsExpanded.value = !hospitalOrderGroupsExpanded.value;
if (hospitalOrderGroupsExpanded.value) fetchOrderGroups('hospital');
}
}
// 解析 childrenJson
const parseChildrenJson = (childrenJson) => {
if (!childrenJson) return [];
try {
const childrenData = typeof childrenJson === 'string' ? JSON.parse(childrenJson) : childrenJson;
return Array.isArray(childrenData) ? childrenData : [];
} catch (e) {
return [];
}
};
// 点击组套 - 双击添加到处方
async function handleClickOrderGroup(group) {
if (!group) return;
// 获取组套详情
let detailList = group.detailList || parseChildrenJson(group.childrenJson);
if (!detailList || detailList.length === 0) {
if (group.groupPackageId) {
try {
const res = await queryGroupDetail({ groupPackageId: group.groupPackageId });
detailList = res.data || [];
} catch (error) {
console.error('获取组套详情失败:', error);
proxy.$modal.msgError('获取组套详情失败');
return;
}
} else {
return;
}
}
// 检查库存和取药科室配置
const checkResults = await checkOrderGroupAvailability(detailList);
if (!checkResults.canUse) {
proxy.$modal.msgWarning(checkResults.message);
return;
}
handleSaveGroup(detailList);
}
// 检查组套的库存和取药科室配置
async function checkOrderGroupAvailability(detailList) {
if (!detailList || detailList.length === 0) {
return { canUse: false, message: '组套为空,无法使用' };
}
const orgId = props.patientInfo?.orgId;
if (!orgId) {
return { canUse: false, message: '无法获取科室信息' };
}
const unavailableItems = [];
for (const item of detailList) {
const orderDefinitionId = item.orderDefinitionId || item.adviceDefinitionId;
if (!orderDefinitionId) continue;
try {
const res = await getAdviceBaseInfo({
adviceDefinitionIdParamList: orderDefinitionId,
organizationId: orgId,
});
if (!res.data || !res.data.records || res.data.records.length === 0) {
unavailableItems.push({
name: item.orderDefinitionName || item.adviceName || '未知项目',
reason: '无法获取项目信息',
});
continue;
}
const adviceInfo = res.data.records[0];
if (adviceInfo.adviceType != 3) {
if (!adviceInfo.positionId) {
unavailableItems.push({
name: adviceInfo.adviceName || adviceInfo.productName,
reason: '未配置取药科室',
});
continue;
}
if (!adviceInfo.inventoryList || adviceInfo.inventoryList.length === 0) {
unavailableItems.push({
name: adviceInfo.adviceName || adviceInfo.productName,
reason: '无库存',
});
continue;
}
const hasAvailableStock = adviceInfo.inventoryList.some(
(stock) => stock.quantity > 0 && stock.locationId === adviceInfo.positionId
);
if (!hasAvailableStock) {
unavailableItems.push({
name: adviceInfo.adviceName || adviceInfo.productName,
reason: '库存不足',
});
}
}
} catch (e) {
console.error('检查组套可用性失败:', e);
}
}
if (unavailableItems.length > 0) {
const itemNames = unavailableItems.slice(0, 5).map((item) => item.name).join('、');
return {
canUse: false,
message: `以下项目无法使用:${itemNames},请检查后再试`,
};
}
return { canUse: true, message: '' };
}
// 编辑组套
function handleEditOrderGroup(group, scope) {
if (!group.groupPackageId) {
proxy.$modal.msgWarning('该组套无法编辑');
return;
}
// 权限检查
const currentPractitionerId = userStore.practitionerId;
const currentOrgId = userStore.orgId;
if (scope === 'personal') {
// 个人组套:只能编辑自己创建的
if (group.practitionerId && group.practitionerId !== currentPractitionerId) {
proxy.$modal.msgWarning('只能编辑自己创建的个人组套');
return;
}
} else if (scope === 'department') {
// 科室组套:只能编辑自己科室的
if (group.organizationId && group.organizationId !== currentOrgId) {
proxy.$modal.msgWarning('只能编辑自己科室的组套');
return;
}
} else if (scope === 'hospital') {
// 全院组套:需要管理权限
// 暂时允许所有医生编辑,后续可根据需要添加权限控制
}
orderSetDialogScope.value = scope;
orderSetDialogRef.value?.openEdit?.(scope, group);
}
// 删除组套
function handleDeleteOrderGroup(group, scope) {
if (!group.groupPackageId) {
proxy.$modal.msgWarning('该组套无法删除');
return;
}
// 权限检查
const currentPractitionerId = userStore.practitionerId;
const currentOrgId = userStore.orgId;
if (scope === 'personal') {
// 个人组套:只能删除自己创建的
if (group.practitionerId && group.practitionerId !== currentPractitionerId) {
proxy.$modal.msgWarning('只能删除自己创建的个人组套');
return;
}
} else if (scope === 'department') {
// 科室组套:只能删除自己科室的
if (group.organizationId && group.organizationId !== currentOrgId) {
proxy.$modal.msgWarning('只能删除自己科室的组套');
return;
}
} else if (scope === 'hospital') {
// 全院组套:需要管理权限
// 暂时允许所有医生删除,后续可根据需要添加权限控制
}
proxy.$modal
.confirm('确定要删除该组套吗?')
.then(() => {
deleteGroup({ groupPackageId: group.groupPackageId })
.then((res) => {
if (res.code === 200) {
proxy.$modal.msgSuccess('删除成功');
// 刷新对应范围的组套列表
orderGroupLoaded.value[scope] = false;
fetchOrderGroups(scope, { force: true });
} else {
proxy.$modal.msgError(res.message || '删除失败');
}
})
.catch((error) => {
console.error('删除组套失败:', error);
proxy.$modal.msgError('删除失败');
});
})
.catch(() => {});
}
// 处理组套右键菜单(预留)
function handleOrderGroupContextMenu(event, group, scope) {
// 可以在这里实现右键菜单功能
}
// 检查组套编辑/删除权限
function hasGroupPermission(group, scope) {
const currentPractitionerId = userStore.practitionerId;
const currentOrgId = userStore.orgId;
if (scope === 'personal') {
// 个人组套:只能编辑/删除自己创建的
return !group.practitionerId || group.practitionerId === currentPractitionerId;
} else if (scope === 'department') {
// 科室组套:只能编辑/删除自己科室的
return !group.organizationId || group.organizationId === currentOrgId;
} else if (scope === 'hospital') {
// 全院组套:暂时允许所有医生操作
return true;
}
return false;
}
const orderSetDialogRef = ref(null);
const orderSetDialogScope = ref('personal');
// 打开组套弹窗
function openOrderSetDialog(scope) {
orderSetDialogScope.value = scope || 'personal';
const selectedRaw =
prescriptionRef.value && prescriptionRef.value.getSelectionRows
? prescriptionRef.value.getSelectionRows()
: [];
const selected = (selectedRaw || []).map((row) => {
const clone = { ...row };
if (clone.therapyEnum == null) {
clone.therapyEnum = '2';
}
return clone;
});
if (selected && selected.length > 0) {
// 列表有选中行:弹窗中直接展示这些选中项用于"另存组套"
orderSetDialogRef.value?.openFromSelection?.(orderSetDialogScope.value, selected);
} else {
// 无选中行:按原逻辑,打开空白弹窗
orderSetDialogRef.value?.openAdd?.(orderSetDialogScope.value);
}
}
function handleOrderSetSaved() {
console.log('[handleOrderSetSaved] ******************** 事件被触发 ********************');
console.log('[handleOrderSetSaved] 当前scope:', orderSetDialogScope.value);
// 保存后刷新当前 scope 的组套列表(懒加载 + 强制刷新)
orderGroupLoaded.value[orderSetDialogScope.value] = false;
console.log('[handleOrderSetSaved] orderGroupLoaded 已重置:', orderGroupLoaded.value);
fetchOrderGroups(orderSetDialogScope.value, { force: true });
}
defineExpose({ getListInfo, getDiagnosisInfo });
</script>
@@ -3282,4 +3878,137 @@ defineExpose({ getListInfo, getDiagnosisInfo });
.el-table__cell .el-form-item--default {
margin-bottom: 0px;
}
/* V1.3 风格侧边栏 - 组套列表样式 */
.order-group-container {
display: flex;
flex-direction: row;
width: 100%;
align-items: flex-start;
}
.order-group-sidebar {
width: 260px;
border-right: 1px solid #ebeef5;
display: flex;
flex-direction: column;
gap: 8px;
flex-shrink: 0;
}
/* 确保表格与侧边栏顶部对齐 */
.order-group-container .el-table {
flex: 1;
min-width: 0;
}
/* 组套区域样式 */
.order-group-section {
background: #fff;
border-radius: 4px;
border: 1px solid #ebeef5;
overflow: hidden;
display: flex;
flex-direction: column;
max-height: 100%;
}
.order-group-section:first-child {
margin-top: 0;
}
.order-group-section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
font-weight: 600;
font-size: 14px;
background: #f8fafc;
border-bottom: 1px solid #ebeef5;
cursor: pointer;
user-select: none;
}
.order-group-section-header:hover {
background: #f0f2f5;
}
.order-group-section-body {
max-height: 300px;
overflow-y: auto;
display: flex;
flex-direction: column;
}
.order-group-item {
padding: 8px 12px;
font-size: 13px;
cursor: pointer;
border-top: 1px solid #f2f3f5;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
}
.order-group-item:hover {
background: #f5f7fa;
}
.order-group-item:hover .order-group-item-actions {
display: flex;
}
.order-group-item-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.order-group-item-actions {
display: none;
align-items: center;
gap: 4px;
margin-left: 8px;
flex-shrink: 0;
}
.order-group-action-icon {
cursor: pointer;
color: #909399;
font-size: 14px;
transition: color 0.2s;
padding: 2px;
}
.order-group-action-icon:hover {
color: #409eff;
}
.order-group-add {
padding: 8px 12px;
cursor: pointer;
text-align: center;
color: #409eff;
font-size: 13px;
border-top: 1px solid #f2f3f5;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
margin-top: auto;
flex-shrink: 0;
}
.order-group-add:hover {
background: #f5f7fa;
}
/* 确保表格填充剩余空间 */
.el-table {
flex: 1;
min-width: 0;
}
</style>