Files
his/openhis-ui-vue3/src/views/basicmanage/medicalOrderSet/index.vue
chenqi 8a7d2abb4a feat(medicalOrderSet): 优化医嘱组套功能实现
- 实现医嘱基础列表的分页功能,添加loading状态和缓存机制
- 添加防抖处理和组织机构ID参数支持,优化性能表现
- 实现医嘱组套的完整编辑功能,包括增删改查操作界面
- 添加医嘱组套预览、应用和管理功能模块
- 实现西医组套筛选功能,确保tcmFlag参数正确传递
- 优化医嘱组套数据结构,完善明细项信息处理逻辑
- 添加表单验证和错误处理,提升用户体验和系统稳定性
- 重构代码结构,采用响应式设计提高可维护性
2026-03-17 09:57:21 +08:00

857 lines
28 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<div class="app-container">
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
<el-tab-pane label="个人" name="personal">
<div class="mb10">
<el-row :gutter="10" class="mb10">
<el-col :span="6">
<el-input
v-model="personalQuery.searchKey"
placeholder="请输入名称"
clearable
@keyup.enter="getPersonalListData"
/>
</el-col>
<el-col :span="1.5">
<el-button type="primary" icon="Search" @click="getPersonalListData">查询</el-button>
</el-col>
</el-row>
<el-button type="primary" plain icon="Plus" @click="handleAdd('personal')"
>新增</el-button
>
</div>
<el-table :data="personalList" border v-loading="loading.personal">
<el-table-column prop="name" label="名称" />
<el-table-column prop="practitionerName" label="参与者" />
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleEdit('personal', scope.row)"
>编辑</el-button
>
<el-button
link
type="danger"
icon="Delete"
@click="handleDelete('personal', scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="科室" name="department">
<div class="mb10">
<el-row :gutter="10" class="mb10">
<el-col :span="6">
<el-input
v-model="departmentQuery.searchKey"
placeholder="请输入名称"
clearable
@keyup.enter="getDepartmentListData"
/>
</el-col>
<el-col :span="1.5">
<el-button type="primary" icon="Search" @click="getDepartmentListData"
>查询</el-button
>
</el-col>
</el-row>
<el-button type="primary" plain icon="Plus" @click="handleAdd('department')"
>新增</el-button
>
</div>
<el-table :data="departmentList" border v-loading="loading.department">
<el-table-column prop="name" label="名称" />
<el-table-column prop="organizationName" label="科室" />
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button
link
type="primary"
icon="Edit"
@click="handleEdit('department', scope.row)"
>编辑</el-button
>
<el-button
link
type="danger"
icon="Delete"
@click="handleDelete('department', scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="全院" name="hospital">
<div class="mb10">
<el-row :gutter="10" class="mb10">
<el-col :span="6">
<el-input
v-model="hospitalQuery.searchKey"
placeholder="请输入名称"
clearable
@keyup.enter="getHospitalListData"
/>
</el-col>
<el-col :span="1.5">
<el-button type="primary" icon="Search" @click="getHospitalListData">查询</el-button>
</el-col>
</el-row>
<el-button type="primary" plain icon="Plus" @click="handleAdd('hospital')"
>新增</el-button
>
</div>
<el-table :data="hospitalList" border v-loading="loading.hospital">
<el-table-column prop="name" label="名称" />
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleEdit('hospital', scope.row)"
>编辑</el-button
>
<el-button
link
type="danger"
icon="Delete"
@click="handleDelete('hospital', scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
<!-- 新增/编辑弹窗 -->
<el-dialog
:title="dialogTitle"
v-model="dialogVisible"
width="1300px"
@close="handleDialogClose"
>
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
<el-row>
<el-col :span="12">
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="参与者" prop="practitionerId" v-if="currentTab == 'personal'">
<el-select v-model="formData.practitionerId" placeholder="请选择参与者" clearable>
<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="请选择科室"
:render-after-expand="false"
@change="handleOrgChange"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-table
max-height="650"
ref="prescriptionRef"
:data="prescriptionList"
row-key="uniqueKey"
border
@cell-click="clickRow"
:expand-row-keys="expandOrder"
>
<el-table-column label="医嘱" align="center" prop="productName">
<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: 50%"
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 }}</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 }}</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 rate_code"
: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>
</div>
</template>
<script setup>
import {onMounted, reactive, ref} from 'vue';
import {
deleteGroup,
getAllList,
getDeptList,
getOrgTree,
getPersonalList,
queryGroupDetail,
queryParticipantList,
saveAll,
saveDepartment,
savePersonal,
} from './components/api.js';
import adviceBaseList from './components/adviceBaseList';
import {ElMessage} from 'element-plus';
import useUserStore from '@/store/modules/user';
// 获取当前实例用于使用proxy
const { proxy } = getCurrentInstance();
// 定义tab相关数据
const activeTab = ref('personal');
// 定义各tab列表数据
const personalList = ref([]);
const departmentList = ref([]);
const hospitalList = ref([]);
const prescriptionList = ref([]);
const expandOrder = ref([]); //目前的展开行
const { method_code, rate_code, distribution_category_code } = proxy.useDict(
'method_code',
'rate_code',
'distribution_category_code'
);
// 查询参数 - 西医组套 tcmFlag = 0
const personalQuery = reactive({ searchKey: '', tcmFlag: 0 });
const departmentQuery = reactive({ searchKey: '', tcmFlag: 0 });
const hospitalQuery = reactive({ searchKey: '', tcmFlag: 0 });
// 加载状态
const loading = reactive({
personal: false,
department: false,
hospital: false,
});
// 提交按钮加载状态
const submitLoading = ref(false);
// 弹窗相关数据
const dialogVisible = ref(false);
const dialogTitle = ref('');
const currentTab = ref(''); // 记录当前操作的tab
const isEdit = ref(false); // 是否为编辑模式
const currentRow = ref(null); // 当前编辑的行数据
const participantListOptions = ref([]);
const organization = ref([]);
const adviceQueryParams = reactive({});
// 表单数据
const formData = reactive({
id: undefined,
name: '',
});
const nextId = ref(1);
const rowIndex = ref(0);
// 表单验证规则
const formRules = {
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
};
// 表单引用
const formRef = ref();
// 页面加载时获取所有列表数据
onMounted(() => {
// 可以添加初始查询参数
const initialParams = {
searchKey: '', // 初始查询关键字
};
fetchAllData(initialParams);
getInit();
});
function getInit() {
queryParticipantList().then((res) => {
participantListOptions.value = res.data;
});
getOrgTree().then((res) => {
organization.value = res.data.records;
});
}
// 获取所有数据(不传参,保留查询框已有的参数)
function fetchAllData() {
getPersonalListData();
getDepartmentListData();
getHospitalListData();
}
// 获取个人医嘱列表
function getPersonalListData() {
loading.personal = true;
getPersonalList(personalQuery)
.then((response) => {
personalList.value = response.data;
loading.personal = false;
})
.catch((error) => {
console.error('获取个人医嘱列表失败:', error);
loading.personal = false;
proxy.$modal.msgError('获取个人医嘱列表失败');
});
}
// 获取科室医嘱列表
function getDepartmentListData() {
loading.department = true;
getDeptList(departmentQuery)
.then((response) => {
departmentList.value = response.data;
loading.department = false;
})
.catch((error) => {
console.error('获取科室医嘱列表失败:', error);
loading.department = false;
proxy.$modal.msgError('获取科室医嘱列表失败');
});
}
// 获取全院医嘱列表
function getHospitalListData() {
loading.hospital = true;
getAllList(hospitalQuery)
.then((response) => {
hospitalList.value = response.data;
loading.hospital = false;
})
.catch((error) => {
console.error('获取全院医嘱列表失败:', error);
loading.hospital = false;
proxy.$modal.msgError('获取全院医嘱列表失败');
});
}
// tab切换处理
function handleTabChange(tab) {
// 根据切换到的tab调用相应的接口获取数据
switch (tab) {
case 'personal':
getPersonalListData();
break;
case 'department':
getDepartmentListData();
break;
case 'hospital':
getHospitalListData();
break;
}
}
// 新增按钮处理
function handleAdd(tab) {
currentTab.value = tab;
isEdit.value = false;
dialogTitle.value = '新增医嘱';
dialogVisible.value = true;
// 重置表单
prescriptionList.value.unshift({
uniqueKey: nextId.value++,
showPopover: false,
check: false,
isEdit: true,
statusEnum: 1,
});
formData.groupPackageId = undefined;
formData.name = '';
// 获取当前登录用户信息
const userStore = useUserStore();
const userId = userStore.id;
const orgId = userStore.orgId;
const practitionerId = userStore.practitionerId;
// 根据不同tab设置默认值
if (tab === 'personal') {
// 个人医嘱默认设置当前用户为参与者
formData.practitionerId = practitionerId;
} else if (tab === 'department') {
// 科室医嘱默认设置当前科室
formData.organizationId = orgId;
}
// 全院医嘱不需要设置默认值
}
// 编辑按钮处理
function handleEdit(tab, row) {
currentTab.value = tab;
isEdit.value = true;
currentRow.value = row;
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) => {
prescriptionList.value = res.data.map((item) => {
return {
groupPackageId: item.groupPackageId,
adviceDefinitionId: item.orderDefinitionId,
adviceTableName: item.orderDefinitionTable,
sortNumber: item.quantity,
selectUnitCode: item.unitCode,
adviceName: item.orderDefinitionName,
unitCodeName: item.unitCodeName,
};
});
prescriptionList.value.unshift({
uniqueKey: nextId.value++,
showPopover: false,
check: false,
isEdit: true,
statusEnum: 1,
});
});
}
// 删除按钮处理
function handleDelete(tab, row) {
// 显示确认框
proxy.$modal
.confirm('确定要删除该医嘱吗?')
.then(() => {
deleteGroup({ groupPackageId: row.groupPackageId }).then((res) => {
proxy.$modal.msgSuccess(res.msg);
fetchAllData();
});
})
.catch(() => {});
}
// 提交表单
function submitForm() {
formRef.value.validate((valid) => {
if (valid) {
submitLoading.value = true;
// 提交数据
console.log('提交表单数据:', formData);
let params = { ...formData };
// 添加 tcmFlag西医组套为 0
params.tcmFlag = 0;
// 过滤掉空列表项没有adviceDefinitionId的项
params.detailList = prescriptionList.value
.filter((item) => item.adviceDefinitionId) // 过滤掉空列表项
.map((item) => {
return {
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,
};
});
console.log('保存参数:', params);
// 编辑模式
switch (currentTab.value) {
case 'personal':
savePersonal(params).then((res) => {
console.log('保存个人组套返回:', res);
if (res.code === 200) {
ElMessage.success(res.msg || '保存成功');
// 重新获取数据以保持一致性
fetchAllData();
} else {
ElMessage.error(res.msg || '保存失败');
}
submitLoading.value = false;
dialogVisible.value = false;
// 清空处方列表
prescriptionList.value = [];
}).catch((err) => {
console.error('保存个人组套失败:', err);
ElMessage.error('保存失败: ' + (err.message || '未知错误'));
submitLoading.value = false;
});
break;
case 'department':
saveDepartment(params).then((res) => {
console.log('保存科室组套返回:', res);
if (res.code === 200) {
ElMessage.success(res.msg || '保存成功');
// 重新获取数据以保持一致性
fetchAllData();
} else {
ElMessage.error(res.msg || '保存失败');
}
submitLoading.value = false;
dialogVisible.value = false;
// 清空处方列表
prescriptionList.value = [];
}).catch((err) => {
console.error('保存科室组套失败:', err);
ElMessage.error('保存失败: ' + (err.message || '未知错误'));
submitLoading.value = false;
});
break;
case 'hospital':
saveAll(params).then((res) => {
console.log('保存全院组套返回:', res);
if (res.code === 200) {
ElMessage.success(res.msg || '保存成功');
// 重新获取数据以保持一致性
fetchAllData();
} else {
ElMessage.error(res.msg || '保存失败');
}
submitLoading.value = false;
dialogVisible.value = false;
// 清空处方列表
prescriptionList.value = [];
}).catch((err) => {
console.error('保存全院组套失败:', err);
ElMessage.error('保存失败: ' + (err.message || '未知错误'));
submitLoading.value = false;
});
break;
}
}
});
}
/**
* 选择药品回调
*/
function selectAdviceBase(key, row) {
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.unshift({
uniqueKey: nextId.value++,
showPopover: false,
check: false,
isEdit: true,
statusEnum: 1,
});
expandOrder.value = [key];
}
function handleFocus(row, index) {
rowIndex.value = index;
row.showPopover = true;
// 将输入框的值传递给adviceBaseList组件作为查询条件
adviceQueryParams.searchKey = row.adviceName || '';
// 获取当前登录用户的科室ID
const userStore = useUserStore();
adviceQueryParams.organizationId = userStore.orgId;
console.log('handleFocus - organizationId:', userStore.orgId, 'userStore:', userStore);
}
// 处理输入事件
function handleInput(value, row, index) {
// 更新查询参数
adviceQueryParams.searchKey = value || '';
// 显示弹窗
handleFocus(row, index);
}
function handleBlur(row) {
row.showPopover = false;
}
/**
* 点击行赋值
*/
function clickRow(row) {}
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) {
// 当单位改变时触发的逻辑可以在这里添加
console.log('单位已更改:', row.unitCode);
// 触发更新列表事件
prescriptionList.value = [...prescriptionList.value];
}
// 删除行
function handleDeleteRow(index, row) {
// 确保至少保留一行
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;
}
}
</script>
<style scoped>
.mb10 {
margin-bottom: 10px;
}
</style>