feat(basicmanage): 新增医嘱组套对话框组件和相关API
- 实现 MedicalOrderSetDialog.vue 组件,支持医嘱组套的新增、编辑功能 - 添加中药医嘱基础列表组件 tcmMedicineList.vue,支持键盘导航选择 - 创建医嘱组套相关API接口文件,包含个人、科室、全院组套的增删改查功能 - 实现医嘱组套的组合拆组功能,支持批量操作 - 集成分页、搜索、缓存等优化功能提升用户体验 - 添加表单验证和数据校验机制确保数据完整性
This commit is contained in:
@@ -0,0 +1,885 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog :title="dialogTitle" v-model="dialogVisible" width="1300px" @close="handleDialogClose">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
|
||||||
|
<div class="dialog-top-row">
|
||||||
|
<el-form-item label="名称" prop="name">
|
||||||
|
<el-input v-model="formData.name" placeholder="请输入名称" style="width: 220px" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="使用范围" v-if="showRangeSelector && !isEdit">
|
||||||
|
<el-select
|
||||||
|
v-model="rangeSelectValue"
|
||||||
|
placeholder="个人/科室/全院"
|
||||||
|
style="width: 220px"
|
||||||
|
@change="handleRangeChange"
|
||||||
|
>
|
||||||
|
<el-option label="个人" value="personal" />
|
||||||
|
<el-option label="科室" value="department" />
|
||||||
|
<el-option label="全院" value="hospital" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item
|
||||||
|
label="参与者"
|
||||||
|
prop="practitionerId"
|
||||||
|
v-if="currentTab === 'personal' && !isDoctorStation && !showRangeSelector"
|
||||||
|
>
|
||||||
|
<el-select
|
||||||
|
v-model="formData.practitionerId"
|
||||||
|
placeholder="请选择参与者"
|
||||||
|
clearable
|
||||||
|
style="width: 220px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in participantListOptions"
|
||||||
|
:key="item.practitionerId"
|
||||||
|
:label="item.practitionerName"
|
||||||
|
:value="item.practitionerId"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="科室" prop="organizationId" v-if="currentTab === 'department'">
|
||||||
|
<el-tree-select
|
||||||
|
clearable
|
||||||
|
v-model="formData.organizationId"
|
||||||
|
:data="organization"
|
||||||
|
:props="{ value: 'id', label: 'name', children: 'children' }"
|
||||||
|
value-key="id"
|
||||||
|
check-strictly
|
||||||
|
default-expand-all
|
||||||
|
placeholder="请选择科室"
|
||||||
|
style="width: 220px"
|
||||||
|
:render-after-expand="false"
|
||||||
|
@change="handleOrgChange"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 10px">
|
||||||
|
<el-button type="primary" @click="handleAddRow">新增</el-button>
|
||||||
|
<el-button @click="handleCombineGroup">组合</el-button>
|
||||||
|
<el-button @click="handleSplitGroup">拆组</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
max-height="650"
|
||||||
|
ref="prescriptionRef"
|
||||||
|
:data="prescriptionList"
|
||||||
|
row-key="uniqueKey"
|
||||||
|
border
|
||||||
|
@cell-click="clickRow"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
:expand-row-keys="expandOrder"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="55" :selectable="isRowSelectable" />
|
||||||
|
<el-table-column label="组" align="center" width="60">
|
||||||
|
<template #default="scope">
|
||||||
|
<span v-if="scope.row.groupId">{{ getGroupIcon(scope.row) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="类型" align="center" width="120">
|
||||||
|
<template #default="scope">
|
||||||
|
<template v-if="!scope.row.groupPackageId">
|
||||||
|
<el-radio-group v-model="scope.row.therapyEnum" size="small">
|
||||||
|
<el-radio-button label="1">长期</el-radio-button>
|
||||||
|
<el-radio-button label="2">临时</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</template>
|
||||||
|
<span v-else>
|
||||||
|
{{
|
||||||
|
scope.row.therapyEnum == '1' ? '长期' : scope.row.therapyEnum == '2' ? '临时' : '-'
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="医嘱" align="center" prop="productName" width="300">
|
||||||
|
<template #default="scope">
|
||||||
|
<template v-if="getRowDisabled(scope.row)">
|
||||||
|
<el-popover
|
||||||
|
:popper-style="{ padding: '0' }"
|
||||||
|
placement="bottom-start"
|
||||||
|
:visible="scope.row.showPopover"
|
||||||
|
:width="1200"
|
||||||
|
>
|
||||||
|
<adviceBaseList
|
||||||
|
ref="adviceTableRef"
|
||||||
|
:popoverVisible="scope.row.showPopover"
|
||||||
|
:adviceQueryParams="adviceQueryParams"
|
||||||
|
@selectAdviceBase="(row) => selectAdviceBase(scope.row.uniqueKey, row)"
|
||||||
|
/>
|
||||||
|
<template #reference>
|
||||||
|
<el-input
|
||||||
|
:ref="'adviceRef' + scope.$index"
|
||||||
|
style="width: 100%"
|
||||||
|
v-model="scope.row.adviceName"
|
||||||
|
placeholder="请选择项目"
|
||||||
|
@input="(value) => handleInput(value, scope.row, scope.$index)"
|
||||||
|
@click="handleFocus(scope.row, scope.$index)"
|
||||||
|
@keyup.enter.stop="handleFocus(scope.row, scope.$index)"
|
||||||
|
@keydown="
|
||||||
|
(e) => {
|
||||||
|
if (!scope.row.showPopover) return;
|
||||||
|
if (['ArrowUp', 'ArrowDown', 'Enter'].includes(e.key)) {
|
||||||
|
e.preventDefault();
|
||||||
|
adviceTableRef.handleKeyDown(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@blur="handleBlur(scope.row)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
</template>
|
||||||
|
<span v-else>{{ scope.row.adviceName }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="单次剂量" align="center" width="250" prop="sortNumber">
|
||||||
|
<template #default="scope">
|
||||||
|
<template v-if="scope.row.adviceType == 1">
|
||||||
|
<!-- 新增/未保存行:统一按“数量 + 单位 = 剂量 + 单位”展示 -->
|
||||||
|
<template v-if="!scope.row.groupPackageId">
|
||||||
|
<el-input
|
||||||
|
style="width: 70px; margin-right: 10px"
|
||||||
|
v-model="scope.row.doseQuantity"
|
||||||
|
@input="
|
||||||
|
(value) => {
|
||||||
|
scope.row.dose = value * scope.row.unitConversionRatio;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
scope.row.minUnitCode_dictText ||
|
||||||
|
scope.row.unitCodeName ||
|
||||||
|
scope.row.unitCode_dictText ||
|
||||||
|
''
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span>{{ ' = ' }}</span>
|
||||||
|
<el-input
|
||||||
|
style="width: 70px; margin-right: 10px"
|
||||||
|
v-model="scope.row.dose"
|
||||||
|
@input="
|
||||||
|
(value) => {
|
||||||
|
scope.row.doseQuantity = value / scope.row.unitConversionRatio;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
scope.row.doseUnitCode_dictText ||
|
||||||
|
scope.row.unitCodeName ||
|
||||||
|
scope.row.unitCode_dictText ||
|
||||||
|
''
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<span v-else>{{ scope.row.dose }}</span>
|
||||||
|
</template>
|
||||||
|
<span v-else>{{ '-' }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="给药途径" align="center" width="150" prop="sortNumber">
|
||||||
|
<template #default="scope">
|
||||||
|
<template v-if="scope.row.adviceType == 1">
|
||||||
|
<template v-if="!scope.row.groupPackageId">
|
||||||
|
<el-select v-model="scope.row.methodCode" placeholder="给药途径" clearable filterable>
|
||||||
|
<el-option
|
||||||
|
v-for="dict in method_code"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
<span v-else>{{ scope.row.methodCode }}</span>
|
||||||
|
</template>
|
||||||
|
<span v-else>{{ '-' }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="用药频次" align="center" width="150" prop="sortNumber">
|
||||||
|
<template #default="scope">
|
||||||
|
<template v-if="scope.row.adviceType == 1">
|
||||||
|
<template v-if="!scope.row.groupPackageId">
|
||||||
|
<el-select
|
||||||
|
v-model="scope.row.rateCode"
|
||||||
|
placeholder="频次"
|
||||||
|
style="width: 120px"
|
||||||
|
filterable
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="dict in getRateOptions(scope.row)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
<span v-else>{{ scope.row.rateCode_dictText }}</span>
|
||||||
|
</template>
|
||||||
|
<span v-else>{{ '-' }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="用药天数" align="center" width="100" prop="sortNumber">
|
||||||
|
<template #default="scope">
|
||||||
|
<template v-if="scope.row.adviceType == 1">
|
||||||
|
<template v-if="!scope.row.groupPackageId">
|
||||||
|
<el-input
|
||||||
|
v-model="scope.row.dispensePerDuration"
|
||||||
|
@change="handleQuantityChange(scope.row)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<span v-else>{{ scope.row.dispensePerDuration }}</span>
|
||||||
|
</template>
|
||||||
|
<span v-else>{{ '-' }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="总量/执行次数" align="center" width="150" prop="sortNumber">
|
||||||
|
<template #default="scope">
|
||||||
|
<template v-if="!scope.row.groupPackageId">
|
||||||
|
<el-input
|
||||||
|
v-model="scope.row.sortNumber"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
@change="handleQuantityChange(scope.row)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<span v-else>{{ scope.row.sortNumber }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="单位" align="center" width="120" prop="unitCode">
|
||||||
|
<template #default="scope">
|
||||||
|
<template v-if="!scope.row.groupPackageId">
|
||||||
|
<el-select
|
||||||
|
v-model="scope.row.selectUnitCode"
|
||||||
|
placeholder="请选择单位"
|
||||||
|
@change="handleUnitChange(scope.row)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-if="scope.row.minUnitCode"
|
||||||
|
:key="scope.row.minUnitCode"
|
||||||
|
:label="scope.row.minUnitCode_dictText || scope.row.minUnitCode"
|
||||||
|
:value="scope.row.minUnitCode"
|
||||||
|
/>
|
||||||
|
<el-option
|
||||||
|
v-if="scope.row.unitCode"
|
||||||
|
:key="scope.row.unitCode"
|
||||||
|
:label="scope.row.unitCode_dictText || scope.row.unitCode"
|
||||||
|
:value="scope.row.unitCode"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
<span>{{ scope.row.unitCodeName }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="操作" align="center" width="80">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
icon="Delete"
|
||||||
|
circle
|
||||||
|
size="small"
|
||||||
|
@click="handleDeleteRow(scope.$index, scope.row)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
<el-button type="primary" @click="submitForm" :loading="submitLoading">确 定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, computed, onMounted, getCurrentInstance } from 'vue';
|
||||||
|
import adviceBaseList from './adviceBaseList';
|
||||||
|
import {
|
||||||
|
queryParticipantList,
|
||||||
|
queryGroupDetail,
|
||||||
|
getOrgTree,
|
||||||
|
} from './api.js';
|
||||||
|
import { saveOrderGroup } from '@/views/doctorstation/components/api.js';
|
||||||
|
import useUserStore from '@/store/modules/user';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// 是否在“医生站/住院医生站”场景复用:个人不选人(practitionerId 置空),科室可选科室
|
||||||
|
isDoctorStation: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
method_code: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
rate_code: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
// 顶部是否显示“个人/科室/全院”切换(用于住院医生站顶部【组套】按钮场景)
|
||||||
|
showRangeSelector: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['saved']);
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const dialogVisible = ref(false);
|
||||||
|
const dialogTitle = ref('');
|
||||||
|
const currentTab = ref('personal'); // personal | department | hospital
|
||||||
|
const isEdit = ref(false);
|
||||||
|
const submitLoading = ref(false);
|
||||||
|
const rangeSelectValue = ref('personal');
|
||||||
|
|
||||||
|
const participantListOptions = ref([]);
|
||||||
|
const organization = ref([]);
|
||||||
|
const adviceQueryParams = reactive({});
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
groupPackageId: undefined,
|
||||||
|
name: '',
|
||||||
|
practitionerId: undefined,
|
||||||
|
organizationId: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const prescriptionList = ref([]);
|
||||||
|
const expandOrder = ref([]);
|
||||||
|
const nextId = ref(1);
|
||||||
|
const rowIndex = ref(0);
|
||||||
|
const selectedRows = ref([]);
|
||||||
|
const groupIndex = ref(1);
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
const prescriptionRef = ref();
|
||||||
|
const adviceTableRef = ref();
|
||||||
|
|
||||||
|
const formRules = computed(() => {
|
||||||
|
const rules = {
|
||||||
|
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
|
||||||
|
};
|
||||||
|
// 独立页面:个人必须选参与者;科室必须选科室
|
||||||
|
if (!props.isDoctorStation && currentTab.value === 'personal') {
|
||||||
|
rules.practitionerId = [{ required: true, message: '请选择参与者', trigger: 'change' }];
|
||||||
|
}
|
||||||
|
if (currentTab.value === 'department') {
|
||||||
|
rules.organizationId = [{ required: true, message: '请选择科室', trigger: 'change' }];
|
||||||
|
}
|
||||||
|
return rules;
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
queryParticipantList().then((res) => {
|
||||||
|
participantListOptions.value = res.data || [];
|
||||||
|
});
|
||||||
|
getOrgTree().then((res) => {
|
||||||
|
organization.value = res.data?.records || [];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function openAdd(tab) {
|
||||||
|
currentTab.value = tab;
|
||||||
|
isEdit.value = false;
|
||||||
|
dialogTitle.value = '新增医嘱';
|
||||||
|
dialogVisible.value = true;
|
||||||
|
|
||||||
|
if (props.showRangeSelector) rangeSelectValue.value = tab;
|
||||||
|
|
||||||
|
prescriptionList.value = [];
|
||||||
|
expandOrder.value = [];
|
||||||
|
nextId.value = 1;
|
||||||
|
rowIndex.value = 0;
|
||||||
|
selectedRows.value = [];
|
||||||
|
groupIndex.value = 1;
|
||||||
|
|
||||||
|
formData.groupPackageId = undefined;
|
||||||
|
formData.name = '';
|
||||||
|
formData.practitionerId = undefined;
|
||||||
|
formData.organizationId = undefined;
|
||||||
|
|
||||||
|
Object.keys(adviceQueryParams).forEach((key) => {
|
||||||
|
delete adviceQueryParams[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
prescriptionRef.value?.clearSelection?.();
|
||||||
|
|
||||||
|
if (tab === 'personal') {
|
||||||
|
// 医生站场景:个人组套需要关联当前医生才能查询到
|
||||||
|
formData.practitionerId = userStore.practitionerId;
|
||||||
|
} else if (tab === 'department') {
|
||||||
|
formData.organizationId = userStore.orgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
addEmptyRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRangeChange(tab) {
|
||||||
|
openAdd(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从外部选中的医嘱直接生成组套明细
|
||||||
|
* @param tab 使用范围:personal / department / hospital
|
||||||
|
* @param rows 选中的处方行列表
|
||||||
|
*/
|
||||||
|
function openFromSelection(tab, rows = []) {
|
||||||
|
currentTab.value = tab;
|
||||||
|
isEdit.value = false;
|
||||||
|
dialogTitle.value = '另存组套';
|
||||||
|
dialogVisible.value = true;
|
||||||
|
|
||||||
|
if (props.showRangeSelector) rangeSelectValue.value = tab;
|
||||||
|
|
||||||
|
// 重置表单
|
||||||
|
formData.groupPackageId = undefined;
|
||||||
|
formData.name = '';
|
||||||
|
formData.practitionerId = undefined;
|
||||||
|
formData.organizationId = undefined;
|
||||||
|
|
||||||
|
if (tab === 'personal') {
|
||||||
|
// 医生站场景:个人组套需要关联当前医生才能查询到
|
||||||
|
formData.practitionerId = userStore.practitionerId;
|
||||||
|
} else if (tab === 'department') {
|
||||||
|
formData.organizationId = userStore.orgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validRows = (rows || []).filter((i) => i.adviceDefinitionId);
|
||||||
|
if (validRows.length === 0) {
|
||||||
|
proxy.$modal.msgWarning('所选医嘱中没有有效的医嘱项,请先选择医嘱后再另存组套');
|
||||||
|
dialogVisible.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prescriptionList.value = validRows.map((row, index) => {
|
||||||
|
let therapyEnum = row.therapyEnum;
|
||||||
|
if (!therapyEnum && row.contentJson) {
|
||||||
|
try {
|
||||||
|
const content = JSON.parse(row.contentJson);
|
||||||
|
therapyEnum = content.therapyEnum;
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
therapyEnum = therapyEnum != null ? String(therapyEnum) : '1';
|
||||||
|
|
||||||
|
return {
|
||||||
|
uniqueKey: index + 1,
|
||||||
|
showPopover: false,
|
||||||
|
check: false,
|
||||||
|
isEdit: false,
|
||||||
|
statusEnum: 1,
|
||||||
|
adviceDefinitionId: row.adviceDefinitionId,
|
||||||
|
adviceTableName: row.adviceTableName || 'advice_definition',
|
||||||
|
sortNumber: row.quantity ?? row.sortNumber ?? 1,
|
||||||
|
selectUnitCode: row.selectUnitCode || row.unitCode,
|
||||||
|
adviceName: row.adviceName || row.productName,
|
||||||
|
unitCodeName: row.unitCodeName || row.unitCode_dictText || '',
|
||||||
|
methodCode: row.methodCode,
|
||||||
|
rateCode: row.rateCode,
|
||||||
|
dispensePerDuration: row.dispensePerDuration,
|
||||||
|
dose: row.dose,
|
||||||
|
doseQuantity: row.doseQuantity,
|
||||||
|
minUnitCode: row.minUnitCode,
|
||||||
|
minUnitCode_dictText: row.minUnitCode_dictText,
|
||||||
|
unitCode: row.unitCode,
|
||||||
|
unitCode_dictText: row.unitCode_dictText,
|
||||||
|
groupId: row.groupId,
|
||||||
|
groupOrder: row.groupOrder,
|
||||||
|
therapyEnum: therapyEnum,
|
||||||
|
adviceType: row.adviceType,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
expandOrder.value = [];
|
||||||
|
nextId.value = prescriptionList.value.length + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEdit(tab, row) {
|
||||||
|
currentTab.value = tab;
|
||||||
|
isEdit.value = true;
|
||||||
|
dialogTitle.value = '编辑医嘱';
|
||||||
|
dialogVisible.value = true;
|
||||||
|
|
||||||
|
formData.groupPackageId = row.groupPackageId;
|
||||||
|
formData.name = row.name;
|
||||||
|
formData.practitionerId = row.practitionerId;
|
||||||
|
formData.organizationId = row.organizationId;
|
||||||
|
|
||||||
|
queryGroupDetail({ groupPackageId: row.groupPackageId }).then((res) => {
|
||||||
|
const detailList = res.data || [];
|
||||||
|
prescriptionList.value = detailList.map((item, index) => {
|
||||||
|
const therapyEnum = item.therapyEnum != null ? String(item.therapyEnum) : '1'; // 统一转成字符串
|
||||||
|
|
||||||
|
return {
|
||||||
|
uniqueKey: index + 1,
|
||||||
|
showPopover: false,
|
||||||
|
check: false,
|
||||||
|
isEdit: false,
|
||||||
|
statusEnum: 1,
|
||||||
|
groupPackageId: item.groupPackageId,
|
||||||
|
adviceDefinitionId: item.orderDefinitionId,
|
||||||
|
adviceTableName: item.orderDefinitionTable,
|
||||||
|
sortNumber: item.quantity,
|
||||||
|
selectUnitCode: item.unitCode,
|
||||||
|
adviceName: item.orderDefinitionName,
|
||||||
|
unitCodeName: item.unitCodeName,
|
||||||
|
groupId: item.groupId,
|
||||||
|
groupOrder: item.groupOrder,
|
||||||
|
therapyEnum, // 长期/临时类型:1-长期,2-临时
|
||||||
|
// 回显单次剂量/给药途径/用药频次/天数等字段
|
||||||
|
dispensePerDuration: item.dispensePerDuration,
|
||||||
|
dose: item.dose,
|
||||||
|
doseQuantity: item.doseQuantity,
|
||||||
|
methodCode: item.methodCode,
|
||||||
|
rateCode: item.rateCode,
|
||||||
|
// 医嘱类型(药品=1):没有则按表名推断(药品表 -> 药品)
|
||||||
|
adviceType:
|
||||||
|
item.adviceType !== undefined
|
||||||
|
? item.adviceType
|
||||||
|
: item.orderDefinitionTable === 'med_medication_definition'
|
||||||
|
? 1
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
nextId.value = prescriptionList.value.length + 1;
|
||||||
|
addEmptyRow();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addEmptyRow() {
|
||||||
|
prescriptionList.value.unshift({
|
||||||
|
uniqueKey: nextId.value++,
|
||||||
|
showPopover: false,
|
||||||
|
check: false,
|
||||||
|
isEdit: true,
|
||||||
|
statusEnum: 1,
|
||||||
|
therapyEnum: '1', // 默认长期
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAddRow() {
|
||||||
|
addEmptyRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelectionChange(selection) {
|
||||||
|
selectedRows.value = selection || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCombineGroup() {
|
||||||
|
const rows = selectedRows.value || [];
|
||||||
|
if (rows.length <= 1) {
|
||||||
|
proxy.$modal.msgWarning('至少选择两项');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 必须都已选择医嘱
|
||||||
|
if (rows.some((r) => !r.adviceDefinitionId)) {
|
||||||
|
proxy.$modal.msgWarning('请先完成医嘱选择');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 相同分组用法需要相同
|
||||||
|
const methodSet = new Set();
|
||||||
|
rows.forEach((r) => {
|
||||||
|
if (r.methodCode != null) methodSet.add(r.methodCode);
|
||||||
|
});
|
||||||
|
if (methodSet.size > 1) {
|
||||||
|
proxy.$modal.msgWarning('同一分组药品用法必须相同');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 相同分组频次需要相同
|
||||||
|
const rateSet = new Set();
|
||||||
|
rows.forEach((r) => {
|
||||||
|
if (r.rateCode != null) rateSet.add(r.rateCode);
|
||||||
|
});
|
||||||
|
if (rateSet.size > 1) {
|
||||||
|
proxy.$modal.msgWarning('同一分组药品频次必须相同');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = Date.now().toString();
|
||||||
|
const gid = timestamp + groupIndex.value;
|
||||||
|
|
||||||
|
const newList = [...prescriptionList.value];
|
||||||
|
rows.forEach((item, selectIndex) => {
|
||||||
|
const idx = newList.findIndex((r) => r.uniqueKey === item.uniqueKey);
|
||||||
|
if (idx !== -1) {
|
||||||
|
newList[idx].groupId = gid;
|
||||||
|
newList[idx].groupOrder = selectIndex + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
groupIndex.value += 1;
|
||||||
|
prescriptionList.value = newList;
|
||||||
|
prescriptionRef.value?.clearSelection?.();
|
||||||
|
proxy.$modal.msgSuccess('组套成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRowSelectable(row) {
|
||||||
|
return !!row.adviceDefinitionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGroupIcon(row) {
|
||||||
|
if (!row.groupId) return '';
|
||||||
|
const sameGroup = prescriptionList.value
|
||||||
|
.filter((item) => item.groupId === row.groupId)
|
||||||
|
.sort((a, b) => {
|
||||||
|
const aOrder = a.groupOrder ?? 0;
|
||||||
|
const bOrder = b.groupOrder ?? 0;
|
||||||
|
return aOrder - bOrder;
|
||||||
|
});
|
||||||
|
if (sameGroup.length === 0) return '';
|
||||||
|
const index = sameGroup.findIndex((item) => item.uniqueKey === row.uniqueKey);
|
||||||
|
if (index === 0) {
|
||||||
|
return '┏';
|
||||||
|
}
|
||||||
|
if (index === sameGroup.length - 1) {
|
||||||
|
return '┗';
|
||||||
|
}
|
||||||
|
return '┃';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRateOptions(row) {
|
||||||
|
const all = props.rate_code || [];
|
||||||
|
if (row?.therapyEnum == '2') {
|
||||||
|
return all.filter(
|
||||||
|
(dict) => dict.value === 'ST' || (dict.label && dict.label.indexOf('立即') !== -1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return all;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSplitGroup() {
|
||||||
|
const rows = selectedRows.value || [];
|
||||||
|
if (rows.length < 1) {
|
||||||
|
proxy.$modal.msgWarning('至少选择一项');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (rows.some((r) => !r.groupId)) {
|
||||||
|
proxy.$modal.msgWarning('包含非组合数据无法拆组');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newList = [...prescriptionList.value];
|
||||||
|
rows.forEach((item) => {
|
||||||
|
const idx = newList.findIndex((r) => r.uniqueKey === item.uniqueKey);
|
||||||
|
if (idx !== -1) {
|
||||||
|
newList[idx].groupId = undefined;
|
||||||
|
newList[idx].groupOrder = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
prescriptionList.value = newList;
|
||||||
|
prescriptionRef.value?.clearSelection?.();
|
||||||
|
proxy.$modal.msgSuccess('拆组成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitForm() {
|
||||||
|
formRef.value.validate((valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
|
||||||
|
// 验证至少选择了一条医嘱
|
||||||
|
const detailList = prescriptionList.value.filter((item) => item.adviceDefinitionId);
|
||||||
|
if (detailList.length === 0) {
|
||||||
|
proxy?.$modal?.msgWarning?.('请至少选择一条医嘱');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
submitLoading.value = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('[submitForm] 当前tab:', currentTab.value);
|
||||||
|
const params = { ...formData };
|
||||||
|
console.log('[submitForm] formData:', formData);
|
||||||
|
// 添加 rangeCode 字段,用于后端判断组套类型
|
||||||
|
if (currentTab.value === 'personal') {
|
||||||
|
params.rangeCode = 1;
|
||||||
|
} else if (currentTab.value === 'department') {
|
||||||
|
params.rangeCode = 2;
|
||||||
|
} else {
|
||||||
|
params.rangeCode = 3;
|
||||||
|
}
|
||||||
|
console.log('[submitForm] 设置后的rangeCode:', params.rangeCode);
|
||||||
|
// 医生站场景:个人组套需要设置 practitionerId 为当前医生,否则查询时查不到
|
||||||
|
console.log('[submitForm] isDoctorStation:', props.isDoctorStation, 'currentTab:', currentTab.value);
|
||||||
|
console.log('[submitForm] userStore.practitionerId:', userStore.practitionerId);
|
||||||
|
if (props.isDoctorStation && currentTab.value === 'personal') {
|
||||||
|
params.practitionerId = userStore.practitionerId;
|
||||||
|
console.log('[submitForm] 已设置 practitionerId:', params.practitionerId);
|
||||||
|
} else {
|
||||||
|
console.log('[submitForm] 未设置 practitionerId, 当前值:', params.practitionerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
params.detailList = detailList.map((item) => ({
|
||||||
|
orderDefinitionId: item.adviceDefinitionId,
|
||||||
|
orderDefinitionTable: item.adviceTableName,
|
||||||
|
quantity: item.sortNumber,
|
||||||
|
unitCode: item.selectUnitCode,
|
||||||
|
methodCode: item.methodCode,
|
||||||
|
rateCode: item.rateCode,
|
||||||
|
dispensePerDuration: item.dispensePerDuration,
|
||||||
|
dose: item.dose,
|
||||||
|
doseQuantity: item.doseQuantity,
|
||||||
|
groupId: item.groupId,
|
||||||
|
groupOrder: item.groupOrder,
|
||||||
|
therapyEnum: item.therapyEnum || '1',
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log('[submitForm] 准备保存组套, params:', params);
|
||||||
|
// 使用 saveOrderGroup,它会根据 rangeCode 自动选择正确的API
|
||||||
|
saveOrderGroup(params)
|
||||||
|
.then((res) => {
|
||||||
|
console.log('[submitForm] 保存API返回:', res);
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success(res.message || '保存成功');
|
||||||
|
console.log('[submitForm] 准备触发 saved 事件');
|
||||||
|
emit('saved');
|
||||||
|
console.log('[submitForm] saved 事件已触发');
|
||||||
|
dialogVisible.value = false;
|
||||||
|
prescriptionList.value = [];
|
||||||
|
} else {
|
||||||
|
proxy?.$modal?.msgError?.(res.message || '保存失败');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('保存失败:', err);
|
||||||
|
proxy?.$modal?.msgError?.(err?.message || '保存失败,请检查网络或联系管理员');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
submitLoading.value = false;
|
||||||
|
});
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAdviceBase(key, row) {
|
||||||
|
const currentRow = prescriptionList.value[rowIndex.value];
|
||||||
|
const preservedTherapyEnum = currentRow?.therapyEnum || row.therapyEnum || '1';
|
||||||
|
|
||||||
|
prescriptionList.value[rowIndex.value] = {
|
||||||
|
...prescriptionList.value[rowIndex.value],
|
||||||
|
...JSON.parse(JSON.stringify(row)),
|
||||||
|
};
|
||||||
|
prescriptionList.value[rowIndex.value].dose = undefined;
|
||||||
|
prescriptionList.value[rowIndex.value].doseQuantity = undefined;
|
||||||
|
prescriptionList.value[rowIndex.value].doseUnitCode = row.doseUnitCode;
|
||||||
|
prescriptionList.value[rowIndex.value].minUnitCode = row.minUnitCode;
|
||||||
|
prescriptionList.value[rowIndex.value].unitCode =
|
||||||
|
row.partAttributeEnum == 1 ? row.minUnitCode : row.unitCode;
|
||||||
|
prescriptionList.value[rowIndex.value].categoryEnum = row.categoryCode;
|
||||||
|
prescriptionList.value[rowIndex.value].isEdit = false;
|
||||||
|
prescriptionList.value[rowIndex.value].definitionId = JSON.parse(
|
||||||
|
JSON.stringify(row)
|
||||||
|
).chargeItemDefinitionId;
|
||||||
|
prescriptionList.value[rowIndex.value].therapyEnum = preservedTherapyEnum;
|
||||||
|
addEmptyRow();
|
||||||
|
expandOrder.value = [key];
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFocus(row, index) {
|
||||||
|
rowIndex.value = index;
|
||||||
|
row.showPopover = true;
|
||||||
|
adviceQueryParams.searchKey = row.adviceName || '';
|
||||||
|
// 设置 organizationId,否则 adviceBaseList 会跳过 API 调用
|
||||||
|
adviceQueryParams.organizationId = userStore.orgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInput(value, row, index) {
|
||||||
|
adviceQueryParams.searchKey = value || '';
|
||||||
|
handleFocus(row, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBlur(row) {
|
||||||
|
row.showPopover = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickRow() {}
|
||||||
|
|
||||||
|
function getRowDisabled(row) {
|
||||||
|
return row.isEdit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleQuantityChange(row) {
|
||||||
|
if (row.sortNumber && row.sortNumber > 0) row.sortNumber = parseInt(row.sortNumber);
|
||||||
|
else row.sortNumber = 1;
|
||||||
|
prescriptionList.value = [...prescriptionList.value];
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUnitChange(row) {
|
||||||
|
prescriptionList.value = [...prescriptionList.value];
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDeleteRow(index) {
|
||||||
|
if (prescriptionList.value.length <= 1) {
|
||||||
|
proxy.$modal.msgWarning('至少保留一行');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
proxy.$modal
|
||||||
|
.confirm('确定要删除该行吗?')
|
||||||
|
.then(() => {
|
||||||
|
prescriptionList.value.splice(index, 1);
|
||||||
|
proxy.$modal.msgSuccess('删除成功');
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLeafNode(node) {
|
||||||
|
return !node.children || node.children.length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDialogClose() {
|
||||||
|
formRef.value?.resetFields?.();
|
||||||
|
prescriptionList.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOrgChange(value) {
|
||||||
|
if (!value) return;
|
||||||
|
const findNode = (nodes) => {
|
||||||
|
for (let node of nodes) {
|
||||||
|
if (node.id === value) return node;
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
const found = findNode(node.children);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
const selectedNode = findNode(organization.value);
|
||||||
|
if (selectedNode && !isLeafNode(selectedNode)) {
|
||||||
|
proxy.$modal.msgWarning('只能选择末级科室');
|
||||||
|
formData.organizationId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
openAdd,
|
||||||
|
openEdit,
|
||||||
|
openFromSelection,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dialog-top-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-top-row :deep(.el-form-item) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取个人组套
|
||||||
|
* @param {*} queryParams
|
||||||
|
*/
|
||||||
|
export function getPersonalList(queryParams) {
|
||||||
|
return request({
|
||||||
|
url: '/personalization/orders-group-package/get-personal',
|
||||||
|
method: 'get',
|
||||||
|
params: queryParams
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取科室组套
|
||||||
|
* @param {*} queryParams
|
||||||
|
*/
|
||||||
|
export function getDeptList(queryParams) {
|
||||||
|
return request({
|
||||||
|
url: '/personalization/orders-group-package/get-organization',
|
||||||
|
method: 'get',
|
||||||
|
params: queryParams
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取全院组套
|
||||||
|
* @param {*} queryParams
|
||||||
|
*/
|
||||||
|
export function getAllList(queryParams) {
|
||||||
|
return request({
|
||||||
|
url: '/personalization/orders-group-package/get-hospital',
|
||||||
|
method: 'get',
|
||||||
|
params: queryParams
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存个人组套
|
||||||
|
* @param {*} data
|
||||||
|
*/
|
||||||
|
export function savePersonal(data) {
|
||||||
|
return request({
|
||||||
|
url: '/personalization/orders-group-package/save-personal',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存科室组套
|
||||||
|
* @param {*} data
|
||||||
|
*/
|
||||||
|
export function saveDepartment(data) {
|
||||||
|
return request({
|
||||||
|
url: '/personalization/orders-group-package/save-organization',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存全院组套
|
||||||
|
* @param {*} data
|
||||||
|
*/
|
||||||
|
export function saveAll(data) {
|
||||||
|
return request({
|
||||||
|
url: '/personalization/orders-group-package/save-hospital',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询组套明细
|
||||||
|
* @param {*} data
|
||||||
|
*/
|
||||||
|
export function queryGroupDetail(params) {
|
||||||
|
return request({
|
||||||
|
url: '/personalization/orders-group-package/get-group-package-detail',
|
||||||
|
method: 'get',
|
||||||
|
params: params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除组套
|
||||||
|
* @param {*} data
|
||||||
|
*/
|
||||||
|
export function deleteGroup(data) {
|
||||||
|
return request({
|
||||||
|
url: '/personalization/orders-group-package/group-package-detail?groupPackageId=' + data.groupPackageId,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询参与者下拉列表
|
||||||
|
* @param {*} data
|
||||||
|
*/
|
||||||
|
export function queryParticipantList(params) {
|
||||||
|
return request({
|
||||||
|
url: '/app-common/practitioner-list',
|
||||||
|
method: 'get',
|
||||||
|
params: params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取科室列表
|
||||||
|
*/
|
||||||
|
export function getOrgTree() {
|
||||||
|
return request({
|
||||||
|
url: '/base-data-manage/organization/organization',
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取中药列表
|
||||||
|
*/
|
||||||
|
export function getTcmMedicine(params) {
|
||||||
|
return request({
|
||||||
|
url: '/doctor-station/chinese-medical/tcm-advice-base-info',
|
||||||
|
method: 'get',
|
||||||
|
params: params,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
<template>
|
||||||
|
<div @keyup="handleKeyDown" tabindex="0" ref="tableWrapper" class="table-container">
|
||||||
|
<el-table
|
||||||
|
ref="adviceBaseRef"
|
||||||
|
height="350"
|
||||||
|
:data="adviceBaseList"
|
||||||
|
highlight-current-row
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
row-key="adviceCode"
|
||||||
|
v-loading="loading"
|
||||||
|
@cell-click="clickRow"
|
||||||
|
>
|
||||||
|
<el-table-column label="名称" align="center" prop="adviceName" />
|
||||||
|
<el-table-column label="类型" align="center" prop="categoryCodeText" />
|
||||||
|
<el-table-column label="医保等级" align="center" prop="chrgitmLv_dictText" />
|
||||||
|
<el-table-column label="包装单位" align="center" prop="unitCode_dictText" />
|
||||||
|
<el-table-column label="最小单位" align="center" prop="minUnitCode_dictText" />
|
||||||
|
<el-table-column label="库存数量" align="center">
|
||||||
|
<template #default="scope">{{ handleQuantity(scope.row) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!-- 分页组件 -->
|
||||||
|
<div class="pagination-wrapper">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="queryParams.pageNum"
|
||||||
|
:page-size="20"
|
||||||
|
:total="total"
|
||||||
|
layout="prev, pager, next"
|
||||||
|
@current-change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch, nextTick } from 'vue';
|
||||||
|
import { getTcmMedicine } from './api';
|
||||||
|
import { debounce } from 'lodash-es';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
searchKey: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
organizationId: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['selectMedicine']);
|
||||||
|
|
||||||
|
const total = ref(0);
|
||||||
|
const loading = ref(false);
|
||||||
|
const adviceBaseRef = ref();
|
||||||
|
const tableWrapper = ref();
|
||||||
|
const currentIndex = ref(0);
|
||||||
|
const currentSelectRow = ref({});
|
||||||
|
const queryParams = ref({
|
||||||
|
pageSize: 20,
|
||||||
|
pageNum: 1,
|
||||||
|
searchKey: '',
|
||||||
|
organizationId: props.organizationId,
|
||||||
|
});
|
||||||
|
const adviceBaseList = ref([]);
|
||||||
|
|
||||||
|
// 前端缓存 - 使用 sessionStorage 持久化缓存
|
||||||
|
const TCM_CACHE_PREFIX = 'tcm_advice_cache_';
|
||||||
|
const TCM_CACHE_EXPIRE = 5 * 60 * 1000; // 缓存5分钟
|
||||||
|
|
||||||
|
function getTcmCacheKey(orgId, pageNum, searchKey) {
|
||||||
|
return TCM_CACHE_PREFIX + orgId + '_' + pageNum + '_' + (searchKey || 'none');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFromTcmCache(orgId, pageNum, searchKey) {
|
||||||
|
try {
|
||||||
|
const key = getTcmCacheKey(orgId, pageNum, searchKey);
|
||||||
|
const cachedData = sessionStorage.getItem(key);
|
||||||
|
if (!cachedData) return null;
|
||||||
|
|
||||||
|
const cacheData = JSON.parse(cachedData);
|
||||||
|
if (Date.now() - cacheData.timestamp > TCM_CACHE_EXPIRE) {
|
||||||
|
sessionStorage.removeItem(key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
console.log('从TCM前端缓存获取:', key);
|
||||||
|
return cacheData;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('读取TCM缓存失败:', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setToTcmCache(orgId, pageNum, searchKey, data, total) {
|
||||||
|
try {
|
||||||
|
const key = getTcmCacheKey(orgId, pageNum, searchKey);
|
||||||
|
const cacheData = {
|
||||||
|
data: data,
|
||||||
|
total: total,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
sessionStorage.setItem(key, JSON.stringify(cacheData));
|
||||||
|
console.log('写入TCM前端缓存:', key);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('写入TCM缓存失败:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防抖函数 - 避免重复请求
|
||||||
|
const debouncedGetList = debounce(
|
||||||
|
() => {
|
||||||
|
// 只有当 organizationId 有效时才请求
|
||||||
|
if (!queryParams.value.organizationId) {
|
||||||
|
console.log('organizationId 无效,跳过请求');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getList();
|
||||||
|
},
|
||||||
|
300,
|
||||||
|
{ leading: false, trailing: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听搜索关键字变化
|
||||||
|
watch(
|
||||||
|
() => props.searchKey,
|
||||||
|
(newValue) => {
|
||||||
|
queryParams.value.searchKey = newValue || '';
|
||||||
|
debouncedGetList();
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听 organizationId 变化
|
||||||
|
watch(
|
||||||
|
() => props.organizationId,
|
||||||
|
(newValue) => {
|
||||||
|
queryParams.value.organizationId = newValue;
|
||||||
|
// organizationId 变化时重新加载
|
||||||
|
if (newValue) {
|
||||||
|
debouncedGetList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 不再页面加载时立即请求,等待用户点击时再请求
|
||||||
|
|
||||||
|
function getList() {
|
||||||
|
const orgId = queryParams.value.organizationId;
|
||||||
|
const pageNum = queryParams.value.pageNum;
|
||||||
|
const searchKey = queryParams.value.searchKey;
|
||||||
|
|
||||||
|
// 尝试从本地缓存获取(只有第一页且无搜索关键字时使用缓存)
|
||||||
|
if (pageNum === 1 && !searchKey) {
|
||||||
|
const cached = getFromTcmCache(orgId, pageNum, searchKey);
|
||||||
|
if (cached) {
|
||||||
|
adviceBaseList.value = cached.data;
|
||||||
|
total.value = cached.total;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示 loading
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
getTcmMedicine(queryParams.value).then((res) => {
|
||||||
|
adviceBaseList.value = res.data.records || [];
|
||||||
|
total.value = res.data.total || 0;
|
||||||
|
|
||||||
|
// 缓存第一页数据
|
||||||
|
if (pageNum === 1 && !searchKey) {
|
||||||
|
setToTcmCache(orgId, pageNum, searchKey, adviceBaseList.value, total.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
currentIndex.value = 0;
|
||||||
|
if (adviceBaseList.value.length > 0 && adviceBaseRef.value) {
|
||||||
|
adviceBaseRef.value.setCurrentRow(adviceBaseList.value[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前页改变
|
||||||
|
function handlePageChange(page) {
|
||||||
|
queryParams.value.pageNum = page;
|
||||||
|
getList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理键盘事件
|
||||||
|
const handleKeyDown = (event) => {
|
||||||
|
const key = event.key;
|
||||||
|
const data = adviceBaseList.value;
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case 'ArrowUp':
|
||||||
|
event.preventDefault();
|
||||||
|
if (currentIndex.value > 0) {
|
||||||
|
currentIndex.value--;
|
||||||
|
setCurrentRow(data[currentIndex.value]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'ArrowDown':
|
||||||
|
event.preventDefault();
|
||||||
|
if (currentIndex.value < data.length - 1) {
|
||||||
|
currentIndex.value++;
|
||||||
|
setCurrentRow(data[currentIndex.value]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Enter':
|
||||||
|
event.preventDefault();
|
||||||
|
if (currentSelectRow.value) {
|
||||||
|
emit('selectMedicine', currentSelectRow.value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleQuantity(row) {
|
||||||
|
if (row.inventoryList) {
|
||||||
|
const totalQuantity = row.inventoryList.reduce((sum, item) => sum + (item.quantity || 0), 0);
|
||||||
|
return totalQuantity.toString() + (row.minUnitCode_dictText || '');
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置选中行(带滚动)
|
||||||
|
const setCurrentRow = (row) => {
|
||||||
|
adviceBaseRef.value.setCurrentRow(row);
|
||||||
|
const tableBody = adviceBaseRef.value.$el.querySelector('.el-table__body-wrapper');
|
||||||
|
const currentRowEl = adviceBaseRef.value.$el.querySelector('.current-row');
|
||||||
|
if (tableBody && currentRowEl) {
|
||||||
|
currentRowEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 当前行变化时更新索引
|
||||||
|
const handleCurrentChange = (currentRow) => {
|
||||||
|
currentIndex.value = adviceBaseList.value.findIndex((item) => item === currentRow);
|
||||||
|
currentSelectRow.value = currentRow;
|
||||||
|
};
|
||||||
|
|
||||||
|
function clickRow(row) {
|
||||||
|
emit('selectMedicine', row);
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
handleKeyDown,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.table-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.pagination-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px 0;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.popover-table-wrapper:focus {
|
||||||
|
outline: 2px solid #409eff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,820 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container tcm-medical-order-set">
|
||||||
|
<el-row :gutter="20" class="full-height">
|
||||||
|
<!-- 左侧树形结构 -->
|
||||||
|
<el-col :span="5" class="left-panel">
|
||||||
|
<el-card shadow="never" class="tree-card panel-shadow">
|
||||||
|
<template #header>
|
||||||
|
<span>中医组套</span>
|
||||||
|
</template>
|
||||||
|
<el-tree
|
||||||
|
:data="treeData"
|
||||||
|
node-key="id"
|
||||||
|
:props="treeProps"
|
||||||
|
default-expand-all
|
||||||
|
highlight-current
|
||||||
|
@node-click="(data, node) => handleTreeNodeClick(data, node)"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<!-- 右侧操作区域 -->
|
||||||
|
<el-col :span="19" class="right-panel">
|
||||||
|
<!-- 上方表单:组套公共字段 -->
|
||||||
|
<el-card shadow="never" class="form-card panel-shadow">
|
||||||
|
<div style="display: flex; justify-content: flex-end; margin-bottom: 10px">
|
||||||
|
<el-button type="primary" plain @click="clearForm">清空 </el-button>
|
||||||
|
<el-button type="primary" @click="createNew">新建 </el-button>
|
||||||
|
<el-button type="primary" @click="saveCommonForm">保存 </el-button>
|
||||||
|
</div>
|
||||||
|
<div class="common-bar">
|
||||||
|
<el-form :model="commonForm" :inline="true" class="demo-form-inline">
|
||||||
|
<el-form-item label="组套名称">
|
||||||
|
<el-input v-model="commonForm.name" placeholder="组套名称" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="使用范围">
|
||||||
|
<el-select
|
||||||
|
v-model="commonForm.useRange"
|
||||||
|
placeholder="个人/科室/全院"
|
||||||
|
clearable
|
||||||
|
@change="handleRangeChange"
|
||||||
|
>
|
||||||
|
<el-option label="个人" value="personal" />
|
||||||
|
<el-option label="科室" value="department" />
|
||||||
|
<el-option label="全院" value="hospital" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="使用人" v-if="commonForm.useRange === 'personal'">
|
||||||
|
<el-select
|
||||||
|
v-model="commonForm.practitionerId"
|
||||||
|
placeholder="请选择使用人"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in participantListOptions"
|
||||||
|
:key="item.practitionerId"
|
||||||
|
:label="item.practitionerName"
|
||||||
|
:value="item.practitionerId"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="科室" v-if="commonForm.useRange === 'department'">
|
||||||
|
<el-tree-select
|
||||||
|
clearable
|
||||||
|
v-model="commonForm.organizationId"
|
||||||
|
:data="organization"
|
||||||
|
:props="{ value: 'id', label: 'name', children: 'children' }"
|
||||||
|
value-key="id"
|
||||||
|
check-strictly
|
||||||
|
default-expand-all
|
||||||
|
placeholder="请选择科室"
|
||||||
|
style="width: 200px"
|
||||||
|
:render-after-expand="false"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="用法">
|
||||||
|
<el-select v-model="commonForm.method" placeholder="用法" clearable>
|
||||||
|
<el-option
|
||||||
|
v-for="item in method_code"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="频次">
|
||||||
|
<el-select v-model="commonForm.frequency" placeholder="频次" clearable>
|
||||||
|
<el-option
|
||||||
|
v-for="item in rate_code"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 下方:中药明细卡片列表 -->
|
||||||
|
<el-card shadow="never" class="detail-card panel-shadow" style="height: 100%">
|
||||||
|
<div class="drug-card-list">
|
||||||
|
<!-- 已有药品卡片 -->
|
||||||
|
<el-card
|
||||||
|
v-for="(item, index) in drugList"
|
||||||
|
:key="item.id"
|
||||||
|
class="drug-card"
|
||||||
|
shadow="never"
|
||||||
|
>
|
||||||
|
<template v-if="!item.isEditing">
|
||||||
|
<div class="drug-card-body">
|
||||||
|
<div class="drug-top">
|
||||||
|
<div class="drug-view-name">{{ item.drugName || '-' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="drug-divider"></div>
|
||||||
|
<div class="drug-bottom" style="margin-top: 10px">
|
||||||
|
<div class="drug-bottom-left">
|
||||||
|
<span class="drug-view-qty">{{ item.quantity }} {{ item.unit }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="drug-view-actions">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
text
|
||||||
|
:icon="Edit"
|
||||||
|
@click.stop="editDrug(item)"
|
||||||
|
title="编辑"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
v-if="drugList.length > 1"
|
||||||
|
type="danger"
|
||||||
|
text
|
||||||
|
:icon="Delete"
|
||||||
|
@click.stop="removeDrug(index)"
|
||||||
|
title="删除"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<div class="drug-card-body">
|
||||||
|
<div class="drug-top">
|
||||||
|
<el-form :model="item" class="drug-card-form">
|
||||||
|
<el-form-item>
|
||||||
|
<el-popover
|
||||||
|
:popper-style="{ padding: '0' }"
|
||||||
|
placement="bottom-start"
|
||||||
|
:visible="item.showPopover"
|
||||||
|
:width="800"
|
||||||
|
>
|
||||||
|
<tcmMedicineList
|
||||||
|
:ref="(el) => setMedicineTableRef(el, item)"
|
||||||
|
:searchKey="item.searchKey"
|
||||||
|
:organizationId="commonForm.organizationId"
|
||||||
|
@selectMedicine="(row) => handleSelectMedicine(row, item)"
|
||||||
|
/>
|
||||||
|
<template #reference>
|
||||||
|
<el-input
|
||||||
|
v-model="item.drugName"
|
||||||
|
placeholder="请选择中药"
|
||||||
|
clearable
|
||||||
|
@input="(val) => handleSearchKeyChange(val, item)"
|
||||||
|
@click="() => handleClick(item)"
|
||||||
|
@keydown="(e) => handleKeyDown(e, item)"
|
||||||
|
@blur="() => handleBlur(item)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<div class="drug-bottom">
|
||||||
|
<div class="drug-bottom-left">
|
||||||
|
<div class="quantity-wrap">
|
||||||
|
<el-input v-model="item.quantity" />
|
||||||
|
<el-input value="g" style="width: 80px" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="drug-edit-actions">
|
||||||
|
<el-button type="primary" link @click.stop="saveDrug(item)"> 保存 </el-button>
|
||||||
|
<el-button type="danger" link @click.stop="removeDrug(index)"> 删除 </el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 始终存在的新增卡片 -->
|
||||||
|
<el-card class="drug-card add-card" shadow="never" @click="addDrug">
|
||||||
|
<div class="add-card-content">
|
||||||
|
<el-icon class="add-icon">
|
||||||
|
<Plus />
|
||||||
|
</el-icon>
|
||||||
|
<span>新增</span>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="TcmMedicalOrderSet">
|
||||||
|
import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import {
|
||||||
|
getPersonalList,
|
||||||
|
getDeptList,
|
||||||
|
getAllList,
|
||||||
|
queryGroupDetail,
|
||||||
|
queryParticipantList,
|
||||||
|
getOrgTree,
|
||||||
|
savePersonal,
|
||||||
|
saveDepartment,
|
||||||
|
saveAll,
|
||||||
|
} from './components/api';
|
||||||
|
import TcmMedicineList from './components/tcmMedicineList.vue';
|
||||||
|
import { Edit, Delete, Plus } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
|
||||||
|
// 字典数据
|
||||||
|
const { method_code, rate_code } = proxy.useDict('method_code', 'rate_code');
|
||||||
|
|
||||||
|
// 左侧树形结构数据
|
||||||
|
const treeData = ref([]);
|
||||||
|
|
||||||
|
const treeProps = {
|
||||||
|
children: 'children',
|
||||||
|
label: 'label',
|
||||||
|
value: 'value',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载左侧树数据
|
||||||
|
function loadTreeData() {
|
||||||
|
Promise.all([
|
||||||
|
getPersonalList({ tcmFlag: 1 }),
|
||||||
|
getDeptList({ tcmFlag: 1 }),
|
||||||
|
getAllList({ tcmFlag: 1 }),
|
||||||
|
])
|
||||||
|
.then(([personalRes, deptRes, allRes]) => {
|
||||||
|
const personalList = personalRes?.data || [];
|
||||||
|
const deptList = deptRes?.data || [];
|
||||||
|
const allList = allRes?.data || [];
|
||||||
|
|
||||||
|
treeData.value = [
|
||||||
|
{
|
||||||
|
id: 'personal',
|
||||||
|
label: '个人',
|
||||||
|
children: personalList.map((item) => ({
|
||||||
|
id: item.groupPackageId,
|
||||||
|
value: item.groupPackageId,
|
||||||
|
label: item.name,
|
||||||
|
practitionerId: item.practitionerId || '',
|
||||||
|
organizationId: '',
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'department',
|
||||||
|
label: '科室',
|
||||||
|
children: deptList.map((item) => ({
|
||||||
|
id: item.groupPackageId,
|
||||||
|
value: item.groupPackageId,
|
||||||
|
label: item.name,
|
||||||
|
practitionerId: '',
|
||||||
|
organizationId: item.organizationId || '',
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'hospital',
|
||||||
|
label: '全院',
|
||||||
|
children: allList.map((item) => ({
|
||||||
|
id: item.groupPackageId,
|
||||||
|
value: item.groupPackageId,
|
||||||
|
label: item.name,
|
||||||
|
practitionerId: '',
|
||||||
|
organizationId: '',
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
ElMessage.error('加载组套树失败');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadTreeData();
|
||||||
|
loadParticipantList();
|
||||||
|
loadOrganization();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 使用人列表
|
||||||
|
const participantListOptions = ref([]);
|
||||||
|
|
||||||
|
// 科室树形数据
|
||||||
|
const organization = ref([]);
|
||||||
|
|
||||||
|
// 加载使用人列表
|
||||||
|
function loadParticipantList() {
|
||||||
|
queryParticipantList().then((res) => {
|
||||||
|
participantListOptions.value = res.data || [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载科室树
|
||||||
|
function loadOrganization() {
|
||||||
|
getOrgTree().then((res) => {
|
||||||
|
organization.value = res.data.records || [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用范围变更
|
||||||
|
function handleRangeChange() {
|
||||||
|
commonForm.practitionerId = '';
|
||||||
|
commonForm.organizationId = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上方公共字段表单
|
||||||
|
const commonForm = reactive({
|
||||||
|
name: '',
|
||||||
|
useRange: '',
|
||||||
|
practitionerId: '',
|
||||||
|
organizationId: '',
|
||||||
|
method: '',
|
||||||
|
frequency: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 下方药品卡片列表
|
||||||
|
const drugList = ref([]);
|
||||||
|
const nextDrugId = ref(1);
|
||||||
|
const currentSetName = ref('');
|
||||||
|
|
||||||
|
// 动态设置表格组件引用
|
||||||
|
function setMedicineTableRef(el, item) {
|
||||||
|
if (el) {
|
||||||
|
item.tableRef = el;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEmptyDrug() {
|
||||||
|
return {
|
||||||
|
id: nextDrugId.value++,
|
||||||
|
drugCode: '',
|
||||||
|
drugName: '',
|
||||||
|
orderDefinitionId: '',
|
||||||
|
quantity: 1,
|
||||||
|
unitCode: '',
|
||||||
|
unit: 'g',
|
||||||
|
isEditing: true,
|
||||||
|
showPopover: false,
|
||||||
|
searchKey: '',
|
||||||
|
tableRef: null,
|
||||||
|
rateCode: '',
|
||||||
|
methodCode: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击树节点
|
||||||
|
function handleTreeNodeClick(data, node) {
|
||||||
|
if (data.children != undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 从父节点获取使用范围:personal/department/hospital
|
||||||
|
const useRange = node.parent?.data?.id || '';
|
||||||
|
// 组套名称从树节点获取
|
||||||
|
commonForm.name = data.label || '';
|
||||||
|
commonForm.useRange = useRange;
|
||||||
|
// 从树节点数据中获取使用人/科室
|
||||||
|
commonForm.practitionerId = data.practitionerId || '';
|
||||||
|
commonForm.organizationId = data.organizationId || '';
|
||||||
|
|
||||||
|
// 调用查询接口回显数据
|
||||||
|
queryGroupDetail({ groupPackageId: data.value })
|
||||||
|
.then((res) => {
|
||||||
|
const detailList = res.data || [];
|
||||||
|
if (detailList.length === 0) {
|
||||||
|
drugList.value = [createEmptyDrug()];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取第一个药品的频次和用法赋值到表单
|
||||||
|
const firstDrug = detailList[0];
|
||||||
|
commonForm.method = firstDrug.methodCode || '';
|
||||||
|
commonForm.frequency = firstDrug.rateCode || '';
|
||||||
|
|
||||||
|
// 回显药品列表
|
||||||
|
drugList.value = detailList.map((detail, index) => ({
|
||||||
|
id: index,
|
||||||
|
drugCode: '',
|
||||||
|
drugName: detail.orderDefinitionName || '',
|
||||||
|
orderDefinitionId: detail.orderDefinitionId,
|
||||||
|
quantity: detail.quantity,
|
||||||
|
unitCode: detail.unitCode,
|
||||||
|
unit: detail.unitCode_dictText || 'g',
|
||||||
|
isEditing: false,
|
||||||
|
showPopover: false,
|
||||||
|
searchKey: '',
|
||||||
|
tableRef: null,
|
||||||
|
rateCode: detail.rateCode,
|
||||||
|
methodCode: detail.methodCode,
|
||||||
|
}));
|
||||||
|
currentSetName.value = data.label;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
ElMessage.error('加载组套详情失败');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增药品
|
||||||
|
function addDrug() {
|
||||||
|
// 如果有药品列表,检查上一个药品是否未保存
|
||||||
|
if (drugList.value.length > 0) {
|
||||||
|
const last = drugList.value[drugList.value.length - 1];
|
||||||
|
// 如果上一个药品未选择药品,不允许添加下一个
|
||||||
|
if (!last.orderDefinitionId) {
|
||||||
|
ElMessage.warning('请先选择上一个药品');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 如果上一个药品已有药品ID且处于编辑状态,自动保存
|
||||||
|
if (last.isEditing) {
|
||||||
|
const msg = validateDrug(last);
|
||||||
|
if (!msg) {
|
||||||
|
last.isEditing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drugList.value.push(createEmptyDrug());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除药品
|
||||||
|
function removeDrug(index) {
|
||||||
|
if (drugList.value.length <= 1) {
|
||||||
|
ElMessage.warning('至少保留一条药品信息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
drugList.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateDrug(item) {
|
||||||
|
if (!item.orderDefinitionId) return '请选择中药';
|
||||||
|
if (!item.quantity || Number(item.quantity) <= 0) return '请填写数量';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveDrug(item) {
|
||||||
|
const msg = validateDrug(item);
|
||||||
|
if (msg) {
|
||||||
|
ElMessage.warning(msg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
item.isEditing = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function editDrug(item) {
|
||||||
|
item.isEditing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理搜索关键字变化
|
||||||
|
function handleSearchKeyChange(value, item) {
|
||||||
|
item.searchKey = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理点击输入框
|
||||||
|
function handleClick(item) {
|
||||||
|
item.showPopover = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理失焦
|
||||||
|
function handleBlur(item) {
|
||||||
|
item.showPopover = false;
|
||||||
|
item.searchKey = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理键盘事件(上下键和回车)
|
||||||
|
function handleKeyDown(event, item) {
|
||||||
|
if (!item.showPopover) return;
|
||||||
|
const key = event.key;
|
||||||
|
if (['ArrowUp', 'ArrowDown', 'Enter'].includes(key)) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (item.tableRef) {
|
||||||
|
item.tableRef.handleKeyDown(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择中药时,回填信息
|
||||||
|
function handleSelectMedicine(row, item) {
|
||||||
|
item.drugCode = row.adviceCode;
|
||||||
|
item.drugName = row.adviceName;
|
||||||
|
item.orderDefinitionId = row.adviceDefinitionId;
|
||||||
|
item.unitCode = row.unitCode;
|
||||||
|
item.unit = row.unitCode_dictText || 'g';
|
||||||
|
item.showPopover = false;
|
||||||
|
item.searchKey = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空表单
|
||||||
|
function clearForm() {
|
||||||
|
commonForm.name = '';
|
||||||
|
commonForm.useRange = '';
|
||||||
|
commonForm.practitionerId = '';
|
||||||
|
commonForm.organizationId = '';
|
||||||
|
commonForm.method = '';
|
||||||
|
commonForm.frequency = '';
|
||||||
|
drugList.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新建组套
|
||||||
|
function createNew() {
|
||||||
|
clearForm();
|
||||||
|
commonForm.useRange = 'personal';
|
||||||
|
drugList.value = [createEmptyDrug()];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存组套
|
||||||
|
function saveCommonForm() {
|
||||||
|
// 校验组套名称
|
||||||
|
if (!commonForm.name) {
|
||||||
|
ElMessage.warning('请填写组套名称');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 校验使用范围
|
||||||
|
if (!commonForm.useRange) {
|
||||||
|
ElMessage.warning('请选择使用范围');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 校验个人时选择使用人
|
||||||
|
if (commonForm.useRange === 'personal' && !commonForm.practitionerId) {
|
||||||
|
ElMessage.warning('请选择使用人');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 校验科室时选择科室
|
||||||
|
if (commonForm.useRange === 'department' && !commonForm.organizationId) {
|
||||||
|
ElMessage.warning('请选择科室');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 校验药品列表
|
||||||
|
if (drugList.value.length === 0) {
|
||||||
|
ElMessage.warning('请添加药品');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 校验每个药品
|
||||||
|
for (let i = 0; i < drugList.value.length; i++) {
|
||||||
|
const item = drugList.value[i];
|
||||||
|
if (!item.orderDefinitionId) {
|
||||||
|
ElMessage.warning(`请选择第${i + 1}个药品`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!item.quantity || Number(item.quantity) <= 0) {
|
||||||
|
ElMessage.warning(`请填写第${i + 1}个药品的数量`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成组套ID(时间戳)
|
||||||
|
const groupId = Date.now();
|
||||||
|
|
||||||
|
// 构建详情列表
|
||||||
|
const detailList = drugList.value.map((item) => ({
|
||||||
|
orderDefinitionId: item.orderDefinitionId,
|
||||||
|
orderDefinitionTable: 'order_definition',
|
||||||
|
quantity: item.quantity,
|
||||||
|
unitCode: item.unitCode,
|
||||||
|
dose: item.quantity,
|
||||||
|
rateCode: commonForm.frequency,
|
||||||
|
dispensePerDuration: 7,
|
||||||
|
methodCode: commonForm.method,
|
||||||
|
doseQuantity: item.quantity,
|
||||||
|
groupId: groupId,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 构建请求数据
|
||||||
|
const data = {
|
||||||
|
name: commonForm.name,
|
||||||
|
tcmFlag: 1,
|
||||||
|
detailList: detailList,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据使用范围添加不同字段
|
||||||
|
if (commonForm.useRange === 'personal') {
|
||||||
|
data.practitionerId = commonForm.practitionerId;
|
||||||
|
} else if (commonForm.useRange === 'department') {
|
||||||
|
data.organizationId = commonForm.organizationId;
|
||||||
|
}
|
||||||
|
// 全院不传 practitionerId 和 organizationId
|
||||||
|
|
||||||
|
// 调用对应接口
|
||||||
|
let saveApi;
|
||||||
|
if (commonForm.useRange === 'personal') {
|
||||||
|
saveApi = savePersonal;
|
||||||
|
} else if (commonForm.useRange === 'department') {
|
||||||
|
saveApi = saveDepartment;
|
||||||
|
} else {
|
||||||
|
saveApi = saveAll;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveApi(data)
|
||||||
|
.then(() => {
|
||||||
|
ElMessage.success('保存成功');
|
||||||
|
loadTreeData();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
ElMessage.error('保存失败:' + (err.message || '未知错误'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tcm-medical-order-set {
|
||||||
|
height: 90vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-height {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel,
|
||||||
|
.right-panel {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-card {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-card,
|
||||||
|
.detail-card {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-shadow {
|
||||||
|
border: 1px solid var(--el-border-color);
|
||||||
|
box-shadow: 0 3px 14px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-form {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-item {
|
||||||
|
width: 300px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-item :deep(.el-input__wrapper),
|
||||||
|
.common-item :deep(.el-select__wrapper) {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-item :deep(.el-input__inner) {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-card-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-card {
|
||||||
|
width: 220px;
|
||||||
|
border: 1px solid var(--el-border-color);
|
||||||
|
box-shadow: 0 3px 14px rgba(0, 0, 0, 0.12);
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-card :deep(.el-card__body) {
|
||||||
|
padding: 10px 10px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-card-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-top {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-divider {
|
||||||
|
margin: 6px 0;
|
||||||
|
border-top: 1px dashed var(--el-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-bottom {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-bottom-left {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-card-form {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact-item {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact-item :deep(.el-form-item__label) {
|
||||||
|
padding: 0 0 4px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact-item :deep(.el-form-item__content) {
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-select {
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-select {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-edit-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0px;
|
||||||
|
padding-top: 2px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-view-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-view-qty {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-view-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 6px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-view-actions :deep(.el-button),
|
||||||
|
.drug-edit-actions :deep(.el-button) {
|
||||||
|
padding: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-card {
|
||||||
|
border-style: dashed;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
box-shadow: 0 3px 14px rgba(64, 158, 255, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-card-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-tip {
|
||||||
|
margin-top: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-form-inline .el-input {
|
||||||
|
--el-input-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-form-inline .el-select {
|
||||||
|
--el-select-width: 200px;
|
||||||
|
}
|
||||||
|
.el-form-item--default {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user