Files
his/openhis-ui-vue3/src/views/inpatientNurse/inOut/components/transferInDialog.vue
zhangfei 9c3e603b94 Fix Bug #443: 手术计费:点击签发耗材时异常报错
当手术计费弹窗中点击"签发"耗材时,因耗材的locationId(发放库房)为空导致后端异常。
在DoctorStationAdviceAppServiceImpl.handDevice方法中,当locationId为null时,使用登录用户的科室ID作为默认值,
与NurseBillingAppService中的处理方式保持一致。
2026-05-08 09:14:18 +08:00

713 lines
24 KiB
Vue
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<el-dialog
v-model="visible"
top="6vh"
:width="width"
title="入科选床"
@open="openAct"
@closed="closedAct"
:z-index="20"
destroy-on-close
>
<div class="transferIn-container">
<el-form :model="interventionForm" :rules="rules" ref="interventionFormRef">
<div class="admission-information">
<el-row>
<el-col :span="24">
<div class="patient-info">
<div style="display: flex; align-items: center; margin-bottom: 16px">
<div style="margin-right: 36px; font-size: 18px; font-weight: 700">
{{ (props.pendingInfo.houseName || '-') + '-' + (props.pendingInfo.bedName || '-') }}
</div>
<div style="border-radius: 50px; border: 2px solid slategray; padding: 4px 12px">
{{ props.pendingInfo.contractName }}
</div>
</div>
<div>
<el-row>
<el-col :span="12">
<div style="margin-bottom: 12px">
{{ props.pendingInfo.patientName }}
<span style="color: #9f9f9f"
>{{ props.pendingInfo.genderEnum_enumText }}/</span
>
<span style="color: #9f9f9f">{{ props.pendingInfo.age }}</span>
</div>
</el-col>
<el-col :span="12">
<div>电话{{ pendingInfo.phone }}</div>
</el-col>
<el-col :span="12">
<div>住院诊断{{ pendingInfo.conditionNames }}</div>
</el-col>
<el-col :span="12">{{ props.pendingInfo.patientId }}</el-col>
</el-row>
</div>
</div>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<div class="info-title">入院体征</div>
</el-col>
<el-col :span="8">
<el-form-item label="身高" label-width="50px">
<!-- <el-input-number
:controls="false"
style="width: 160px"
clearable
v-model="interventionForm.height"
placeholder="请输入"
:min="0"
:max="999"
></el-input-number
><span class="unit">cm</span> -->
<el-input
class="right-aligned"
style="width: 160px"
clearable
v-model="interventionForm.height"
placeholder="请输入"
:min="0"
:max="999"
></el-input>
<span class="unit">cm</span>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="体重" label-width="50px">
<!-- <el-input-number
:controls="false"
style="width: 160px"
v-model="interventionForm.weight"
clearable
placeholder="请输入"
:min="0"
:max="999"
></el-input-number>
<span class="unit">kg</span> -->
<el-input
class="right-aligned"
style="width: 160px"
v-model="interventionForm.weight"
clearable
placeholder="请输入"
></el-input>
<span class="unit">kg</span>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="体温" label-width="50px">
<el-input-number
:controls="false"
style="width: 160px"
v-model="interventionForm.temperature"
clearable
placeholder="请输入"
:min="0"
:max="99"
></el-input-number>
<span class="unit"></span>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="心率" label-width="50px">
<el-input-number
:controls="false"
style="width: 160px"
v-model="interventionForm.hertRate"
clearable
placeholder="请输入"
:min="0"
:max="999"
></el-input-number>
<span class="unit">BPM</span>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="脉搏" label-width="50px">
<el-input-number
:controls="false"
style="width: 160px"
v-model="interventionForm.pulse"
clearable
placeholder="请输入"
:min="0"
:max="999"
></el-input-number
><span class="unit">P</span>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="血压" label-width="50px">
<el-input-number
:controls="false"
style="width: 72px"
v-model="interventionForm.endBloodPressure"
clearable
placeholder="请输入"
:min="0"
:max="999"
></el-input-number>
<span style="display: inline-block; width: 8px; margin: 0 4px"> ~ </span>
<el-input-number
:controls="false"
style="width: 72px"
v-model="interventionForm.highBloodPressure"
clearable
placeholder="请输入"
:min="0"
:max="999"
></el-input-number>
<span class="unit">mmHg</span>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<div class="info-title">入院信息</div>
</el-col>
<el-col :span="8">
<el-form-item label="入院科室" label-width="100px">
<el-input
class="w-p100"
clearable
disabled
v-model="interventionForm.organizationName"
placeholder="请输入"
></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="入院病区" label-width="100px">
<el-input
class="w-p100"
v-model="interventionForm.wardName"
disabled
clearable
placeholder="请输入"
></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="入住床位" label-width="100px">
<el-input
class="w-p100"
v-model="interventionForm.bedName"
disabled
clearable
placeholder="请输入"
></el-input>
<!-- <el-select v-model="interventionForm.bedLocationId" placeholder="请选择入住床位" style="width: 100%">
<el-option
v-for="item in bedInfoOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select> -->
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="住院医生" label-width="100px" prop="admittingDoctorId">
<el-select
v-model="interventionForm.admittingDoctorId"
placeholder="请选择住院医生"
style="width: 240px"
:disabled="
props.pendingInfo.encounterStatus == 5 && props.pendingInfo.entranceType == 2
"
>
<el-option
v-for="item in residentDoctorOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="主治医生" label-width="100px">
<el-select
v-model="interventionForm.attendingDoctorId"
placeholder="请选择主治医生"
style="width: 240px"
:disabled="
props.pendingInfo.encounterStatus == 5 && props.pendingInfo.entranceType == 2
"
>
<el-option
v-for="item in attendingDoctorOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="主任医生" label-width="100px">
<el-select
v-model="interventionForm.chiefDoctorId"
placeholder="请选择主任医生"
style="width: 240px"
:disabled="
props.pendingInfo.encounterStatus == 5 && props.pendingInfo.entranceType == 2
"
>
<el-option
v-for="item in chiefDoctorOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="入科时间" label-width="100px">
<el-date-picker
class="w-p100"
v-model="interventionForm.startTime"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请输入"
:disabled="props.pendingInfo.encounterStatus == 5"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="责任护士" label-width="100px" prop="primaryNurseId">
<el-select
v-model="interventionForm.primaryNurseId"
placeholder="请选择责任护士"
style="width: 240px"
:disabled="
props.pendingInfo.encounterStatus == 5 && props.pendingInfo.entranceType == 2
"
>
<el-option
v-for="item in nurseInfoOptions"
:key="item.practitionerId"
:label="item.name"
:value="item.practitionerId"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="患者病情" label-width="100px">
<el-select
v-model="interventionForm.priorityEnum"
placeholder="请选择患者病情"
style="width: 240px"
:disabled="
props.pendingInfo.encounterStatus == 5 && props.pendingInfo.entranceType == 2
"
>
<el-option
v-for="item in props.priorityOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
</div>
<template #footer>
<!-- <div class="transferInDialog-footer"> -->
<div class="isPrintWristband">
<el-checkbox v-model="printWristband">打印腕带</el-checkbox>
</div>
<el-button class="margin-left-auto" @click="cancelAct">取消 </el-button>
<el-button type="primary" @click="handleSubmit">入科</el-button>
<!-- </div> -->
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import {computed, nextTick, onMounted, reactive, ref, watch} from 'vue';
import type {FormInstance, FormRules} from 'element-plus';
import {dayjs, ElMessage} from 'element-plus';
// import type { IInPatient } from '@/model/IInPatient'
import {bedAssignment, getBedInfo, getDoctorInfo, getInit, getNurseInfo, getPatientInfo,} from './api';
const props = defineProps({
pendingInfo: {
type: Object,
require: true,
default: () => ({}),
},
priorityOptions: {
type: Object,
require: true,
default: () => ({}),
},
});
const currentInPatient = ref<Partial<IInPatient>>({});
const bedInfoOptions = ref<{ label: string; value: string }[]>([]);
const doctorInfoOptions = ref<{ name: string; id: string; drProfttlCode?: string }[]>([]);
const nurseInfoOptions = ref<{ name: string; practitionerId: string }[]>([]);
// 住院医生只显示医师职称编码234
const residentDoctorOptions = computed(() => {
return doctorInfoOptions.value.filter(item => item.drProfttlCode === '234');
});
// 主治医生只显示主治医师职称编码233
const attendingDoctorOptions = computed(() => {
return doctorInfoOptions.value.filter(item => item.drProfttlCode === '233');
});
// 主任医生显示副主任医师232和主任医师231
const chiefDoctorOptions = computed(() => {
return doctorInfoOptions.value.filter(item => item.drProfttlCode === '231' || item.drProfttlCode === '232');
});
const InitInfoOptions = ref<any>({});
const priorityListOptions = ref<{ info: string; value: string }[]>([]);
const pendingInfo = ref<any>({});
const initCurrentInPatient = () => {
currentInPatient.value = {
feeType: '08',
sexName: '男',
age: '0',
};
};
/* 入科 */
const interventionForm = ref({
height: '',
weight: '',
temperature: '',
hertRate: '',
pulse: '',
endBloodPressure: '',
highBloodPressure: '',
bedLocationId: '', // 床号
admittingDoctorId: '', // 住院医师
attendingDoctorId: '', // 主治医师
chiefDoctorId: '', // 主任医师
primaryNurseId: '', // 责任护士
priorityEnum: '', //患者病情
organizationName: '',
wardName: '',
bedName: '',
attendingDocUpdateId: '',
startTime: '', //入院时间
});
/**
* 获取患者详细信息并填充表单
*/
const loadPatientInfo = () => {
if (!props.pendingInfo?.encounterId) {
return;
}
console.log('查询患者信息的 encounterId:', props.pendingInfo.encounterId);
getPatientInfo({ encounterId: props.pendingInfo.encounterId }).then((res) => {
console.log('后端返回的患者信息:', res.data);
pendingInfo.value = res.data;
// 从后端获取数据后设置医生和护士 ID
// 医生使用 ToStringSerializer返回字符串护士直接返回数字
console.log('admittingDoctorId:', res.data.admittingDoctorId);
console.log('attendingDoctorId:', res.data.attendingDoctorId);
console.log('chiefDoctorId:', res.data.chiefDoctorId);
console.log('primaryNurseId:', res.data.primaryNurseId);
if (res.data.admittingDoctorId) {
const doctorId = String(res.data.admittingDoctorId);
// 检查该医生是否在住院医生列表中
const existsInResident = residentDoctorOptions.value.some(item => item.id === doctorId);
if (existsInResident) {
interventionForm.value.admittingDoctorId = doctorId;
}
}
if (res.data.attendingDoctorId) {
const doctorId = String(res.data.attendingDoctorId);
// 检查该医生是否在主治医生列表中
const existsInAttending = attendingDoctorOptions.value.some(item => item.id === doctorId);
if (existsInAttending) {
interventionForm.value.attendingDoctorId = doctorId;
}
}
if (res.data.chiefDoctorId) {
const doctorId = String(res.data.chiefDoctorId);
// 检查该医生是否在主任医生列表中
const existsInChief = chiefDoctorOptions.value.some(item => item.id === doctorId);
if (existsInChief) {
interventionForm.value.chiefDoctorId = doctorId;
}
}
if (res.data.primaryNurseId) {
// 护士ID也转换为字符串以匹配护士选项
interventionForm.value.primaryNurseId = String(res.data.primaryNurseId);
}
if (res.data.startTime) {
interventionForm.value.startTime = dayjs(res.data.startTime).format(
'YYYY-MM-DD HH:mm:ss'
);
} else {
interventionForm.value.startTime = dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss');
}
interventionForm.value.height = res.data.height;
interventionForm.value.weight = res.data.weight;
interventionForm.value.temperature = res.data.temperature;
interventionForm.value.hertRate = res.data.hertRate;
interventionForm.value.pulse = res.data.pulse;
interventionForm.value.endBloodPressure = res.data.endBloodPressure;
interventionForm.value.highBloodPressure = res.data.highBloodPressure;
});
interventionForm.value.priorityEnum = props.pendingInfo.priorityEnum || '';
interventionForm.value.organizationName = props.pendingInfo.organizationName || '';
interventionForm.value.wardName = props.pendingInfo.wardName || '';
interventionForm.value.bedName = props.pendingInfo.bedName || '';
};
watch(
() => props.pendingInfo?.encounterId,
(newVal, oldVal) => {
// encounterId 存在时就获取数据
if (newVal) {
console.log('watch 触发, newVal:', newVal, 'oldVal:', oldVal);
loadPatientInfo();
}
}
);
/* 初始化数据 */
const init = () => {
initCurrentInPatient();
const promises = [];
const initPromise = getInit()
.then((res) => {
InitInfoOptions.value = res.data;
if (res.data && res.data.priorityListOptions) {
priorityListOptions.value = res.data.priorityListOptions;
}
})
.catch((error) => {
console.error('获取初始化数据失败:', error);
});
promises.push(initPromise);
if (props.pendingInfo.wardLocationId) {
const bedPromise = getBedInfo({ wardLocationId: props.pendingInfo.wardLocationId })
.then((res) => {
bedInfoOptions.value = res.data || [];
})
.catch((error) => {
console.error('获取床位信息失败:', error);
bedInfoOptions.value = [];
});
promises.push(bedPromise);
}
if (props.pendingInfo.organizationId) {
const doctorPromise = getDoctorInfo({ organizationId: props.pendingInfo.organizationId })
.then((res) => {
doctorInfoOptions.value = res.data.records || [];
// 只有在新分配床位模式entranceType != 1时才设置默认主任医生
// 并且只在当前没有选择主任医生时才设置默认值(避免覆盖已从后端获取的数据)
if (props.pendingInfo.entranceType != 1) {
nextTick(() => {
if (chiefDoctorOptions.value.length > 0 && !interventionForm.value.chiefDoctorId) {
interventionForm.value.chiefDoctorId = chiefDoctorOptions.value[0].id;
}
});
}
})
.catch((error) => {
console.error('获取医生信息失败:', error);
doctorInfoOptions.value = [];
});
promises.push(doctorPromise);
const nursePromise = getNurseInfo({ organizationId: props.pendingInfo.organizationId })
.then((res) => {
// 将护士ID转换为字符串以匹配医生选项的数据类型
nurseInfoOptions.value = (res.data || []).map((item: any) => ({
...item,
practitionerId: String(item.practitionerId),
}));
})
.catch((error) => {
console.error('获取护士信息失败:', error);
nurseInfoOptions.value = [];
});
promises.push(nursePromise);
}
return Promise.all(promises);
};
const rules = reactive<FormRules>({
admittingDoctorId: [{ required: true, message: '请选择住院医生', trigger: ['blur', 'change'] }],
primaryNurseId: [{ required: true, message: '请选择责任护士', trigger: ['blur', 'change'] }],
bedLocationId: [{ required: true, message: '请选择入住床位', trigger: ['blur', 'change'] }],
// 主治医生和主任医生为可选字段,不添加 required 验证
});
const printWristband = ref(false);
const emits = defineEmits(['okAct']);
const visible = defineModel('visible');
const width = '950px';
/* 取消 */
const cancelAct = () => {
resetForm();
visible.value = false;
};
const resetForm = () => {
// 只重置表单验证状态,不清空数据
// 数据会在下次打开对话框时通过 loadPatientInfo 重新加载
if (interventionFormRef.value) {
interventionFormRef.value.clearValidate();
}
};
/* 入科 */
const interventionFormRef = ref<FormInstance | null>(null);
const handleSubmit = async () => {
// TODO 登记入科
if (!interventionFormRef.value) {
console.error('表单引用不存在');
return;
}
try {
const valid = await interventionFormRef.value.validate();
if (valid) {
// 过滤掉空字符串的字段,只保留用户实际选择的值
const formData = {};
Object.keys(interventionForm.value).forEach(key => {
const value = interventionForm.value[key];
// 保留非空的值0、false等有效值也需要保留
// 特别注意体征字段height, weight等即使为空字符串也要提交用于清除已有数据
const vitalSignFields = ['height', 'weight', 'temperature', 'hertRate', 'pulse', 'endBloodPressure', 'highBloodPressure'];
if (vitalSignFields.includes(key)) {
// 体征字段:提交所有值,包括空字符串(用于清除数据)
// 处理 undefined 和 null 都转为空字符串
formData[key] = (value === undefined || value === null) ? '' : String(value);
} else if (value !== '' && value !== null && value !== undefined) {
// 其他字段:只保留非空值
formData[key] = value;
}
});
const params = {
...pendingInfo.value,
...formData,
targetBedId: props.pendingInfo.bedId,
busNo: props.pendingInfo.busNo,
inHosTime: props.pendingInfo.inHosTime,
targetHouseId: props.pendingInfo.targetHouseId,
targetEncounterId: props.pendingInfo.targetEncounterId,
editFlag: props.pendingInfo.entranceType == 1 ? '1' : '0',
};
console.log('提交参数:', params);
console.log('startTime:', interventionForm.value.startTime);
bedAssignment(params)
.then((res: any) => {
ElMessage({
message: '登记成功!',
type: 'success',
grouping: true,
showClose: true,
});
resetForm();
emits('okAct');
visible.value = false; // 关闭对话框
})
.catch((error: any) => {
console.error('登记失败:', error);
ElMessage({
message: '登记失败!',
type: 'error',
grouping: true,
showClose: true,
});
});
}
} catch (error) {
console.error('表单验证失败:', error);
}
};
const openAct = () => {
// 先初始化数据(包括医生护士列表),等待完成后再加载患者信息
init().then(() => {
loadPatientInfo();
});
};
const closedAct = () => {
resetForm();
visible.value = false;
};
onMounted(() => {});
</script>
<style lang="scss" scoped>
.transferIn-container {
width: 100%;
.admission-information {
width: 896px;
.unit {
display: inline-block;
margin-left: 10px;
color: #bbb;
font-weight: 400;
font-size: 14px;
font-family: '思源黑体 CN';
}
}
.beds {
margin-bottom: 8px;
}
}
.patient-info {
padding: 16px;
margin: 10px;
border-radius: 4px;
box-shadow: 0 2px 2px 0 rgba(58, 69, 86, 0.2);
position: relative;
transition: all 0.2s ease;
}
.isPrintWristband {
float: left;
display: inline-block;
}
.w-p100 {
width: 100%;
}
.w-80 {
width: 80px;
}
.info-title {
background: #f6f7f9;
color: #4f6877;
padding: 5px 10px;
margin-bottom: 16px;
}
.right-aligned {
:deep(.el-input__inner) {
text-align: center;
}
}
</style>