Files
his/openhis-ui-vue3/src/views/doctorstation/components/consultation.vue
zhaoyun 22b47fcc95 fix: 修复前端Bug#431 #433 #434 #435
#431 会诊申请单:标签文案修改「需要病员及会诊目的」为「简要病史及会诊目的」
#433 手术安排编辑:麻醉方法回显为代码 - 添加Number类型转换
#434 手术安排编辑:切口类型未回显 - 添加Number类型转换
#435 手术安排编辑:费用类别未回显 - 确保字段正确赋值
2026-04-24 14:39:49 +08:00

1494 lines
46 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="consultation-container">
<!-- 顶部操作按钮 -->
<div class="top-actions">
<div class="left-actions">
<el-button type="info" @click="handlePrint">打印</el-button>
<el-button type="primary" @click="handleNew">新增</el-button>
</div>
<div class="right-actions">
<el-button type="danger" @click="handleComplete" :disabled="!selectedRow || selectedRow.consultationStatus !== 30">结束</el-button>
<el-button type="success" @click="handleSave" :disabled="selectedRow && selectedRow.consultationStatus > 0">保存</el-button>
</div>
</div>
<!-- 会诊申请列表 -->
<div class="consultation-list">
<el-table
ref="consultationTableRef"
:data="consultationList"
border
stripe
highlight-current-row
@current-change="handleRowClick"
style="width: 100%"
max-height="250"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="急" width="60" align="center">
<template #default="{ row }">
<span v-if="row.consultationUrgency === '2'"></span>
</template>
</el-table-column>
<el-table-column prop="consultationId" label="申请单号" width="160" align="center" />
<el-table-column prop="consultationDate" label="会诊时间" width="160" align="center" />
<el-table-column prop="invitedObjectText" label="邀请对象" min-width="120" show-overflow-tooltip />
<el-table-column prop="department" label="申请科室" width="100" align="center" />
<el-table-column prop="requestingPhysician" label="申请医师" width="100" align="center" />
<el-table-column prop="consultationRequestDate" label="申请时间" width="160" align="center" />
<el-table-column label="提交" width="60" align="center">
<template #default="{ row }">
<el-checkbox v-model="row.submitted" disabled />
</template>
</el-table-column>
<el-table-column label="结束" width="60" align="center">
<template #default="{ row }">
<el-checkbox v-model="row.completed" disabled />
</template>
</el-table-column>
<el-table-column label="操作" width="180" align="center" fixed="right">
<template #default="{ row }">
<!-- 新开状态显示提交和作废按钮 -->
<template v-if="row.consultationStatus === 0">
<el-button type="primary" size="small" @click="handleSubmitRow(row)">
提交
</el-button>
<el-button type="danger" size="small" @click="handleDeleteRow(row)">
作废
</el-button>
</template>
<!-- 已提交状态显示取消提交按钮 -->
<!-- 🎯 修复如果有医生已确认或签名则隐藏取消提交按钮 -->
<template v-else-if="row.consultationStatus === 10 && !hasConfirmedOrSignedDoctor(row)">
<el-button type="warning" size="small" @click="handleCancelSubmit(row)">
取消提交
</el-button>
</template>
<!-- 已提交但有医生确认/签名显示查看按钮 -->
<template v-else-if="row.consultationStatus === 10 && hasConfirmedOrSignedDoctor(row)">
<el-button type="info" size="small" @click="handleView(row)">
查看
</el-button>
</template>
<!-- 已确认状态显示待签名按钮 -->
<template v-else-if="row.consultationStatus === 20">
<el-button type="success" size="small" @click="handleView(row)">
待签名
</el-button>
</template>
<!-- 已签名状态显示结束按钮 -->
<template v-else-if="row.consultationStatus === 30">
<el-button
type="danger"
size="small"
@click="handleCompleteRow(row)"
style="background-color: #f56c6c; border-color: #f56c6c;"
>
结束
</el-button>
</template>
<!-- 已完成或已取消显示查看按钮 -->
<template v-else>
<el-button type="info" size="small" @click="handleView(row)">
查看
</el-button>
</template>
</template>
</el-table-column>
</el-table>
</div>
<!-- 下方内容区域 -->
<div class="content-area">
<!-- 左侧会诊申请单 -->
<div class="left-panel">
<div class="panel-title">会诊申请单</div>
<el-form ref="formRef" :model="formData" :rules="rules" label-width="100px" class="consultation-form">
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="申请单号:">
<el-input v-model="formData.consultationId" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="病人姓名:">
<el-input v-model="formData.patientName" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="性别:">
<el-input v-model="formData.genderText" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="申请时间:">
<el-input v-model="displayApplicationTime" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="就诊卡号:">
<el-input v-model="formData.patientBusNo" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="年龄:">
<el-input v-model="formData.age" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="申请医师:">
<el-input v-model="formData.requestingPhysician" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="申请科室:">
<el-input v-model="formData.department" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="门诊诊断:">
<el-input v-model="formData.provisionalDiagnosis" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="会诊项目:" prop="consultationActivityId">
<el-select
v-model="formData.consultationActivityId"
placeholder="请选择会诊项目"
style="width: 100%"
@change="handleActivityChange"
>
<el-option
v-for="item in activityList"
:key="item.id"
:label="`${item.name} (¥${item.price})`"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="会诊时间:" prop="consultationDate">
<el-date-picker
v-model="formData.consultationDate"
type="datetime"
placeholder="请选择"
style="width: 100%"
format="YYYY/MM/DD HH:mm"
value-format="YYYY-MM-DD HH:mm:ss"
:disabled-date="disabledDate"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="急诊:">
<el-checkbox v-model="formData.isUrgent" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="简要病史及会诊目的:" prop="consultationPurpose">
<el-input
v-model="formData.consultationPurpose"
type="textarea"
:rows="4"
placeholder="请输入会诊目的"
/>
</el-form-item>
<!-- 已选择的邀请对象显示 -->
<el-form-item label="会诊邀请对象:">
<div class="invited-display">
<div v-if="selectedPhysiciansList.length > 0" class="invited-list">
<el-tag
v-for="physician in selectedPhysiciansList"
:key="physician.id"
type="success"
style="margin: 5px"
>
{{ physician.deptName }} - {{ physician.name }}
</el-tag>
</div>
<div v-else class="empty-invited">
请在右侧选择会诊医生
</div>
</div>
</el-form-item>
<div class="section-title">会诊记录</div>
<el-form-item label="会诊确认参加医师:">
<el-input
v-model="formData.invitedPhysiciansText"
type="textarea"
:rows="2"
placeholder="会诊后自动填充"
disabled
/>
</el-form-item>
<el-form-item label="会诊意见:">
<el-input
v-model="formData.consultationOpinion"
type="textarea"
:rows="4"
placeholder="会诊后填写"
disabled
/>
</el-form-item>
<!-- 🎯 新增参与医生列表表格形式 -->
<el-form-item label="参与医生签名:">
<el-table
:data="participatingPhysicians"
border
stripe
style="width: 100%"
max-height="300"
>
<el-table-column type="index" label="序号" min-width="60" align="center" />
<el-table-column prop="confirmingDeptName" label="代表科室" min-width="120" align="center" />
<el-table-column prop="confirmingPhysicianName" label="所属医生" min-width="100" align="center" />
<el-table-column prop="signature" label="签名医生" min-width="100" align="center" />
<el-table-column prop="signatureDate" label="签名时间" min-width="160" align="center">
<template #default="{ row }">
{{ formatDateTime(row.signatureDate) }}
</template>
</el-table-column>
</el-table>
</el-form-item>
</el-form>
</div>
<!-- 右侧会诊邀请对象 -->
<div class="right-panel">
<div class="panel-title">会诊邀请对象</div>
<!-- 已选择医生统计 -->
<div class="selected-summary">
<span class="summary-text">已选择</span>
<span class="summary-count">{{ selectedPhysiciansList.length }}</span>
<span class="summary-text">位医生</span>
<el-button
v-if="selectedPhysiciansList.length > 0"
type="text"
size="small"
@click="clearAllSelections"
style="margin-left: auto; color: #f56c6c;"
>
清空
</el-button>
</div>
<!-- 科室医生树带复选框 -->
<div class="invite-section">
<div class="section-label">选择会诊科室和医生</div>
<el-scrollbar height="600px">
<div class="department-list">
<div
v-for="dept in departmentTree"
:key="dept.id"
class="department-item"
>
<!-- 科室标题 -->
<div
class="dept-header"
:class="{ 'expanded': expandedDepts.includes(dept.id) }"
@click="toggleDept(dept.id)"
>
<i
class="el-icon"
:class="expandedDepts.includes(dept.id) ? 'el-icon-arrow-down' : 'el-icon-arrow-right'"
></i>
<span class="dept-name">{{ dept.label }}</span>
<span
v-if="getDeptSelectedCount(dept.id) > 0"
class="selected-badge"
>
{{ getDeptSelectedCount(dept.id) }}
</span>
</div>
<!-- 医生列表 -->
<div v-show="expandedDepts.includes(dept.id)" class="physicians-list">
<div
v-for="physician in dept.children"
:key="physician.id"
class="physician-item"
>
<el-checkbox
:model-value="isPhysicianSelected(physician.id)"
@change="(checked) => handlePhysicianCheck(checked, physician, dept)"
>
{{ physician.label }}
</el-checkbox>
</div>
<div v-if="!dept.children || dept.children.length === 0" class="no-physicians">
该科室暂无医生
</div>
</div>
</div>
</div>
</el-scrollbar>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted, onUnmounted, watch, defineProps } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Delete } from '@element-plus/icons-vue';
import useUserStore from '@/store/modules/user';
import {
getConsultationList,
saveConsultation,
submitConsultation,
cancelConsultation,
completeConsultation,
getDepartmentTree,
getMainDiagnosis,
getConsultationActivities,
} from './api';
// 接收父组件传递的患者信息和激活状态
const props = defineProps({
patientInfo: {
type: Object,
default: () => ({}),
},
activeTab: {
type: String,
default: '',
},
});
// 表单引用
const formRef = ref(null);
// 树形引用
const treeRef = ref(null);
// 会诊项目列表
const activityList = ref([]);
// 当前选中的行
const selectedRow = ref(null);
// 实时显示的当前时间
const currentTime = ref('');
let timeTimer = null; // 定时器引用
// 计算显示的申请时间 - 如果有选中行则显示其申请时间,否则显示当前时间
const displayApplicationTime = computed(() => {
if (selectedRow.value && selectedRow.value.consultationRequestDate) {
// 如果是编辑已有记录,则显示其申请时间(不是动态时间)
return selectedRow.value.consultationRequestDate;
} else {
// 如果是新增记录,则显示当前时间(动态时间)
return currentTime.value;
}
});
// 表单数据
const formData = reactive({
consultationId: '',
consultationActivityId: null,
consultationActivityName: '',
patientName: '',
genderText: '',
age: '',
patientBusNo: '',
requestingPhysician: '',
department: '',
provisionalDiagnosis: '',
consultationDate: null,
isUrgent: false,
consultationPurpose: '',
invitedPhysiciansText: '',
consultationOpinion: '',
attendingPhysician: '',
representDepartment: '',
signPhysician: '',
signTime: null,
createTime: '',
});
// 表单验证规则
const rules = {
consultationActivityId: [
{ required: true, message: '请选择会诊项目', trigger: 'change' },
],
consultationDate: [
{ required: true, message: '请选择会诊时间', trigger: 'change' },
],
consultationPurpose: [
{ required: true, message: '请输入会诊目的', trigger: 'blur' },
],
};
// 科室医生树
const departmentTree = ref([]);
// 展开的科室列表
const expandedDepts = ref([]);
// 选中的医生列表(用于显示和提交)
const selectedPhysiciansList = ref([]);
// 🎯 新增:参与医生列表(用于显示已确认/已签名的医生)
const participatingPhysicians = ref([]);
// 会诊列表
const consultationList = ref([]);
// 禁用过去的日期
const disabledDate = (time) => {
return time.getTime() < Date.now() - 8.64e7;
};
// 加载会诊项目列表
const loadActivityList = async () => {
try {
console.log('开始调用 getConsultationActivities 接口...');
const res = await getConsultationActivities();
console.log('getConsultationActivities 接口返回:', res);
// 检查响应是否存在
if (!res) {
console.error('接口返回空响应');
ElMessage.warning('会诊项目列表加载失败:未获取到响应数据');
activityList.value = [];
return;
}
// 检查响应代码
if (res.code === 200) {
activityList.value = res.data || [];
console.log('会诊项目列表加载成功,数量:', activityList.value.length);
// 如果列表为空,给出提示
if (activityList.value.length === 0) {
console.warn('会诊项目列表为空,请检查数据库配置');
ElMessage.warning('暂无可用会诊项目,请联系系统管理员配置');
}
} else {
console.error('接口返回错误:', res.msg || res.message);
ElMessage.error(res.msg || '加载会诊项目失败');
activityList.value = [];
}
} catch (error) {
console.error('加载会诊项目失败,错误详情:', error);
// 网络错误或其他异常
const errorMsg = error.response?.data?.msg || error.message || '未知错误';
ElMessage.error('加载会诊项目失败: ' + errorMsg);
activityList.value = [];
}
};
// 会诊项目选择变化
const handleActivityChange = (activityId) => {
const activity = activityList.value.find((item) => item.id === activityId);
if (activity) {
formData.consultationActivityName = activity.name;
console.log('选择的会诊项目:', activity.name, '价格:', activity.price);
}
};
// 加载科室医生树
const loadDepartmentTree = async () => {
try {
const res = await getDepartmentTree();
// 检查响应是否存在
if (!res) {
console.error('接口返回空响应');
ElMessage.warning('科室医生树加载失败:未获取到响应数据');
departmentTree.value = [];
return;
}
if (res.code === 200) {
departmentTree.value = res.data || [];
console.log('科室医生树加载成功:', departmentTree.value);
// 如果树为空,给出提示
if (departmentTree.value.length === 0) {
console.warn('科室医生树为空,请检查数据库配置');
ElMessage.warning('暂无可用科室医生数据,请联系系统管理员配置');
}
} else {
console.error('接口返回错误:', res.msg || res.message);
ElMessage.warning(res.msg || '加载科室医生树失败');
departmentTree.value = [];
}
} catch (error) {
console.error('加载科室医生树失败:', error);
// 网络错误或其他异常
const errorMsg = error.response?.data?.msg || error.message || '未知错误';
ElMessage.warning('加载科室医生树失败: ' + errorMsg);
departmentTree.value = [];
}
};
// 处理树节点点击
const handleNodeClick = (data, node) => {
console.log('点击节点:', data, node);
// 如果点击的是科室节点有children
if (data.children && data.children.length > 0) {
currentDepartmentId.value = data.id;
currentDepartmentName.value = data.label;
// 显示该科室下的所有医生
currentDepartmentPhysicians.value = data.children.map(physician => ({
id: physician.id,
name: physician.label,
deptId: data.id,
deptName: data.label,
}));
console.log('当前科室医生:', currentDepartmentPhysicians.value);
}
};
// 处理医生选择变化
// const handlePhysicianSelectionChange = (selectedIds) => {
// console.log('选中的医生ID:', selectedIds);
// // 更新选中的医生列表
// selectedPhysiciansList.value = currentDepartmentPhysicians.value.filter(physician =>
// selectedIds.includes(physician.id)
// );
// console.log('选中的医生列表:', selectedPhysiciansList.value);
// };
const handlePhysicianSelectionChange = (selectedIds) => {
console.log('选中的医生ID:', selectedIds);
const deptId = currentDepartmentId.value;
// 1) 先保留“非当前科室”的已选医生
const preserved = selectedPhysiciansList.value.filter(p => p.deptId !== deptId);
// 2) 再计算“当前科室”被选中的医生
const selectedInCurrentDept = currentDepartmentPhysicians.value.filter(p =>
selectedIds.includes(p.id)
);
// 3) 合并:跨科室累积
selectedPhysiciansList.value = [...preserved, ...selectedInCurrentDept];
console.log('更新后的选中医生列表:', selectedPhysiciansList.value);
};
// 切换科室展开/收起
const toggleDept = (deptId) => {
const index = expandedDepts.value.indexOf(deptId);
if (index > -1) {
expandedDepts.value.splice(index, 1);
} else {
expandedDepts.value.push(deptId);
}
};
// 判断医生是否被选中
const isPhysicianSelected = (physicianId) => {
return selectedPhysiciansList.value.some(p => p.id === physicianId);
};
// 处理医生复选框变化
const handlePhysicianCheck = (checked, physician, dept) => {
if (checked) {
// 添加医生到选中列表
selectedPhysiciansList.value.push({
id: physician.id,
name: physician.label,
deptId: dept.id,
deptName: dept.label,
});
} else {
// 从选中列表中移除
selectedPhysiciansList.value = selectedPhysiciansList.value.filter(
p => p.id !== physician.id
);
}
console.log('当前选中的医生:', selectedPhysiciansList.value);
};
// 获取某个科室已选中的医生数量
const getDeptSelectedCount = (deptId) => {
return selectedPhysiciansList.value.filter(p => p.deptId === deptId).length;
};
// 清空所有选择
const clearAllSelections = () => {
selectedPhysiciansList.value = [];
};
// 🎯 检查是否有医生已确认或签名
const hasConfirmedOrSignedDoctor = (row) => {
if (!row.invitedList || row.invitedList.length === 0) {
return false;
}
return row.invitedList.some(inv => inv.invitedStatus >= 20);
};
// 加载会诊列表
const loadConsultationList = async () => {
if (!props.patientInfo?.encounterId) {
console.log('没有就诊ID无法加载会诊列表');
consultationList.value = [];
return;
}
try {
const res = await getConsultationList({ encounterId: props.patientInfo.encounterId });
// 检查响应是否存在
if (!res) {
console.error('接口返回空响应');
ElMessage.warning('会诊列表加载失败:未获取到响应数据');
consultationList.value = [];
return;
}
if (res.code === 200) {
consultationList.value = (res.data || []).map(item => {
console.log('列表项数据:', item);
return {
...item,
submitted: item.consultationStatus >= 10,
completed: item.consultationStatus === 40,
invitedObjectText: item.invitedObject || (item.invitedList?.map(inv => inv.physicianName).join('、') || ''),
};
});
console.log('会诊列表:', consultationList.value);
} else {
console.error('接口返回错误:', res.msg || res.message);
ElMessage.warning(res.msg || '加载会诊列表失败');
consultationList.value = [];
}
} catch (error) {
console.error('加载会诊列表失败:', error);
// 网络错误或其他异常
const errorMsg = error.response?.data?.msg || error.message || '未知错误';
ElMessage.warning('加载会诊列表失败: ' + errorMsg);
consultationList.value = [];
}
};
// 加载主诊断
const loadMainDiagnosis = async () => {
if (!props.patientInfo?.encounterId) {
return;
}
try {
const res = await getMainDiagnosis({ encounterId: props.patientInfo.encounterId });
// 检查响应是否存在
if (!res) {
console.error('获取主诊断接口返回空响应');
return;
}
if (res.code === 200 && res.data) {
formData.provisionalDiagnosis = res.data.diagnosis || '';
console.log('主诊断加载成功:', formData.provisionalDiagnosis);
} else {
console.warn('获取主诊断失败:', res.msg || res.message);
formData.provisionalDiagnosis = '';
}
} catch (error) {
console.error('加载主诊断失败:', error);
// 主诊断加载失败不应该阻塞整个会诊功能,只是不显示诊断信息
formData.provisionalDiagnosis = '';
}
};
// 点击表格行
const handleRowClick = async (row) => {
console.log('点击行,当前 selectedRow:', selectedRow.value, '新 row:', row);
// 先清空表单验证状态
if (formRef.value) {
formRef.value.clearValidate();
}
selectedRow.value = row;
if (row) {
console.log('点击的行数据:', row);
// 🎯 处理会诊确认参加医师字段可能是JSON格式
let physiciansText = row.invitedPhysiciansText || '';
if (physiciansText) {
try {
// 尝试解析JSON格式
const physicians = JSON.parse(physiciansText);
if (Array.isArray(physicians)) {
// 格式化为:科室-姓名、科室-姓名
physiciansText = physicians
.map(p => `${p.deptName || ''}-${p.physicianName || ''}`)
.filter(text => text !== '-')
.join('、');
}
} catch (e) {
// 如果不是JSON保持原值
console.log('invitedPhysiciansText不是JSON格式使用原值:', physiciansText);
}
}
// 填充表单数据
Object.assign(formData, {
consultationId: row.consultationId,
consultationActivityId: row.consultationActivityId,
consultationActivityName: row.consultationActivityName,
patientName: row.patientName,
genderText: row.genderEnum === 1 ? '男' : '女',
age: row.age,
patientBusNo: row.patientBusNo,
requestingPhysician: row.requestingPhysician,
department: row.department,
provisionalDiagnosis: row.provisionalDiagnosis,
consultationDate: row.consultationDate,
isUrgent: row.consultationUrgency === '2', // 2=紧急
consultationPurpose: row.consultationPurpose,
createTime: row.consultationRequestDate || row.createTime, // 申请时间
// 🎯 填充会诊记录字段(如果会诊已完成或已签名)
invitedPhysiciansText: physiciansText,
consultationOpinion: row.consultationOpinion || '',
attendingPhysician: row.attendingPhysician || '',
representDepartment: row.representDepartment || '',
signPhysician: row.signPhysician || '',
signTime: row.signTime || null,
});
console.log('填充后的表单数据:', formData);
// 填充选中的医生
if (row.invitedList && row.invitedList.length > 0) {
selectedPhysiciansList.value = row.invitedList.map(inv => ({
id: inv.physicianId,
name: inv.physicianName,
deptId: inv.deptId,
deptName: inv.deptName,
}));
console.log('填充的医生列表:', selectedPhysiciansList.value);
// 🎯 填充参与医生列表(显示确认和签名状态)
participatingPhysicians.value = row.invitedList
.filter(inv => inv.invitedStatus >= 20) // 只显示已确认或已签名的医生
.map(inv => ({
physicianId: inv.physicianId,
confirmingPhysicianName: inv.physicianName || '',
confirmingDeptName: inv.deptName || '',
signature: inv.invitedStatus >= 30 ? inv.physicianName : '', // 已签名才显示签名医生
signatureDate: inv.signatureTime || null,
}));
console.log('参与医生列表:', participatingPhysicians.value);
} else {
selectedPhysiciansList.value = [];
participatingPhysicians.value = [];
}
}
console.log('行点击处理完成selectedRow:', selectedRow.value);
};
// 表格引用
const consultationTableRef = ref(null);
// 新增
const handleNew = () => {
console.log('点击新增按钮');
// 先清空表单验证状态
if (formRef.value) {
formRef.value.clearValidate();
}
// 🔧 Bug #262 修复:清除表格的当前行选中状态,确保后续点击行能正常触发 current-change 事件
if (consultationTableRef.value) {
consultationTableRef.value.setCurrentRow(null);
}
selectedRow.value = null;
// 获取当前登录用户信息
const userStore = useUserStore();
// 获取当前时间
const now = new Date();
const formatDateTime = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
// 重置表单,填充患者信息和当前医生信息
Object.assign(formData, {
consultationId: '保存后自动生成',
consultationActivityId: null,
consultationActivityName: '',
patientName: props.patientInfo.patientName || '',
genderText: props.patientInfo.genderEnum === 1 ? '男' : '女',
age: props.patientInfo.age || '',
// 🔧 Bug #263 修复:就诊卡号应取值于 identifierNo而非 busNo
patientBusNo: props.patientInfo.identifierNo || '',
requestingPhysician: userStore.nickName || '',
department: userStore.orgName || '',
provisionalDiagnosis: '',
consultationDate: null,
isUrgent: false,
consultationPurpose: '',
invitedPhysiciansText: '',
consultationOpinion: '',
attendingPhysician: '',
representDepartment: '',
signPhysician: '',
signTime: null,
createTime: formatDateTime(now),
});
// 清空选中状态
selectedPhysiciansList.value = [];
participatingPhysicians.value = [];
console.log('新增模式初始化完成selectedRow:', selectedRow.value);
// 加载主诊断
loadMainDiagnosis();
};
// 保存
const handleSave = async () => {
if (!props.patientInfo?.encounterId) {
ElMessage.warning('请先选择患者');
return;
}
try {
await formRef.value.validate();
// 检查是否选择了会诊医生
if (selectedPhysiciansList.value.length === 0) {
ElMessage.warning('请至少选择1位会诊专家');
return;
}
// 校验会诊时间不能早于当前时间
if (formData.consultationDate) {
const scheduledDate = new Date(formData.consultationDate);
const now = new Date();
if (scheduledDate < now) {
ElMessage.warning('会诊时间不能早于当前时间');
return;
}
}
// 构建邀请对象列表
const invitedList = selectedPhysiciansList.value.map(physician => ({
physicianId: physician.id,
physicianName: physician.name,
deptId: physician.deptId,
deptName: physician.deptName,
}));
// 处理年龄:去掉"岁"字,转换为纯数字
let ageValue = props.patientInfo.age;
if (typeof ageValue === 'string') {
ageValue = parseInt(ageValue.replace(/[^\d]/g, '')) || 0;
}
// 获取当前登录用户信息
const userStore = useUserStore();
const data = {
id: selectedRow.value?.id || null,
consultationId: selectedRow.value?.consultationId || null,
consultationActivityId: formData.consultationActivityId,
consultationActivityName: formData.consultationActivityName,
consultationUrgency: formData.isUrgent ? '2' : '1',
consultationPurpose: formData.consultationPurpose,
consultationDate: formData.consultationDate,
consultationRequestDate: currentTime.value, // 使用动态申请时间
patientId: props.patientInfo.patientId,
encounterId: props.patientInfo.encounterId,
patientName: props.patientInfo.patientName,
// 🔧 Bug #263 修复:就诊卡号应取值于 identifierNo
patientBusNo: props.patientInfo.identifierNo || '',
patientIdentifierNo: props.patientInfo.identifierNo,
genderEnum: props.patientInfo.genderEnum,
age: ageValue,
provisionalDiagnosis: formData.provisionalDiagnosis,
// 申请医生和科室信息
requestingPhysician: formData.requestingPhysician || userStore.nickName,
requestingPhysicianId: userStore.practitionerId,
department: formData.department || userStore.orgName,
departmentId: userStore.orgId,
invitedList: invitedList,
};
console.log('保存会诊申请数据:', data);
const res = await saveConsultation(data);
if (res.code === 200) {
ElMessage.success('保存成功');
// 刷新列表
await loadConsultationList();
// 如果是新增,自动选中新创建的记录
if (!selectedRow.value && res.data) {
const newRow = consultationList.value.find(item => item.consultationId === res.data);
if (newRow) {
handleRowClick(newRow);
}
}
} else {
ElMessage.error(res.msg || '保存失败');
}
} catch (error) {
console.error('保存失败:', error);
if (error !== 'cancel') {
ElMessage.error('保存失败: ' + (error.message || error));
}
}
};
// 提交单行
const handleSubmitRow = async (row) => {
// 校验必填字段
if (!row.consultationDate) {
ElMessage.warning('请先设置会诊时间');
return;
}
if (!row.invitedList || row.invitedList.length === 0) {
ElMessage.warning('请至少选择1位会诊专家');
return;
}
try {
const res = await submitConsultation({ consultationId: row.consultationId });
if (res.code === 200) {
ElMessage.success('提交成功');
loadConsultationList();
} else {
ElMessage.error(res.msg || '提交失败');
}
} catch (error) {
console.error('提交失败:', error);
ElMessage.error('提交失败');
}
};
// 取消提交
const handleCancelSubmit = async (row) => {
try {
await ElMessageBox.confirm('确定要取消提交吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
// 调用后端接口取消提交将状态从10改为0
const res = await cancelConsultation({
consultationId: row.consultationId,
cancelReason: '取消提交',
});
if (res.code === 200) {
ElMessage.success('取消提交成功');
loadConsultationList();
} else {
ElMessage.error(res.msg || '取消提交失败');
}
} catch (error) {
if (error !== 'cancel') {
console.error('取消提交失败:', error);
ElMessage.error('取消提交失败');
}
}
};
// 结束会诊(顶部按钮)
const handleComplete = async () => {
if (!selectedRow.value) {
ElMessage.warning('请先选择要结束的会诊申请');
return;
}
// 校验状态只有已签名状态30才能结束
if (selectedRow.value.consultationStatus !== 30) {
ElMessage.warning('只有已签名状态的会诊申请才能结束');
return;
}
try {
await ElMessageBox.confirm('确定要结束该会诊吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
// 调用后端接口结束会诊
const res = await completeConsultation(selectedRow.value.consultationId);
if (res.code === 200) {
ElMessage.success('会诊已结束');
loadConsultationList();
} else {
ElMessage.error(res.msg || '结束失败');
}
} catch (error) {
if (error !== 'cancel') {
console.error('结束失败:', error);
ElMessage.error('结束失败');
}
}
};
// 结束会诊(表格行按钮)
const handleCompleteRow = async (row) => {
// 校验状态只有已签名状态30才能结束
if (row.consultationStatus !== 30) {
ElMessage.warning('只有已签名状态的会诊申请才能结束');
return;
}
try {
await ElMessageBox.confirm('确定要结束该会诊吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
// 调用后端接口结束会诊
const res = await completeConsultation(row.consultationId);
if (res.code === 200) {
ElMessage.success('会诊已结束');
loadConsultationList();
} else {
ElMessage.error(res.msg || '结束失败');
}
} catch (error) {
if (error !== 'cancel') {
console.error('结束失败:', error);
ElMessage.error('结束失败');
}
}
};
// 查看详情
const handleView = (row) => {
handleRowClick(row);
// 根据状态显示不同的提示消息
if (row.consultationStatus === 10 && hasConfirmedOrSignedDoctor(row)) {
ElMessage.info('已有医生确认或签名,无法取消提交,仅可查看');
} else if (row.consultationStatus === 20) {
ElMessage.info('该申请已确认,等待签名');
} else if (row.consultationStatus === 40) {
ElMessage.info('该申请已完成,仅可查看');
} else if (row.consultationStatus === 50) {
ElMessage.info('该申请已取消,仅可查看');
}
};
// 作废
const handleCancel = async () => {
if (!selectedRow.value) {
ElMessage.warning('请先选择一条记录');
return;
}
try {
const { value } = await ElMessageBox.prompt('请输入作废原因', '作废会诊申请', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /.+/,
inputErrorMessage: '请输入作废原因',
});
const res = await cancelConsultation({
consultationId: selectedRow.value.consultationId,
cancelReason: value,
});
if (res.code === 200) {
ElMessage.success('作废成功');
loadConsultationList();
handleNew();
} else {
ElMessage.error(res.msg || '作废失败');
}
} catch (error) {
if (error !== 'cancel') {
console.error('作废失败:', error);
}
}
};
// 作废行
const handleDeleteRow = async (row) => {
try {
await ElMessageBox.confirm('确定要作废这条会诊申请吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
const res = await cancelConsultation({
consultationId: row.consultationId,
cancelReason: '作废',
});
if (res.code === 200) {
ElMessage.success('作废成功');
loadConsultationList();
if (selectedRow.value?.consultationId === row.consultationId) {
handleNew();
}
} else {
ElMessage.error(res.msg || '作废失败');
}
} catch (error) {
if (error !== 'cancel') {
console.error('作废失败:', error);
}
}
};
// 打印
const handlePrint = () => {
ElMessage.info('打印功能开发中...');
};
// 暴露给父组件的方法
const fetchConsultationList = () => {
loadConsultationList();
};
// 暴露方法给父组件
defineExpose({
fetchConsultationList,
});
// 🎯 格式化日期时间
const formatDateTime = (dateTime) => {
if (!dateTime) return '-';
const date = new Date(dateTime);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
// 监听 activeTab 变化
watch(
() => props.activeTab,
(newVal) => {
if (newVal === 'consultation' && props.patientInfo?.encounterId) {
console.log('切换到会诊tab加载数据');
// 并行加载数据,提升性能
loadConsultationList();
handleNew();
} else if (newVal === 'consultation') {
console.log('切换到会诊tab但没有患者信息');
// 即使没有患者信息,也要加载基础数据(会诊项目和科室树)
handleNew();
}
}
);
// 监听患者信息变化
watch(
() => props.patientInfo?.encounterId,
(newVal) => {
if (newVal && props.activeTab === 'consultation') {
console.log('患者信息变化,重新加载数据');
loadConsultationList();
handleNew();
}
}
);
// 初始化
onMounted(() => {
console.log('会诊组件已挂载');
loadActivityList();
loadDepartmentTree();
// 初始化当前时间并启动实时更新(每秒更新一次)
currentTime.value = formatDateTime(new Date());
timeTimer = setInterval(() => {
currentTime.value = formatDateTime(new Date());
}, 1000);
if (props.activeTab === 'consultation' && props.patientInfo?.encounterId) {
loadConsultationList();
handleNew();
}
});
// 组件卸载时清除定时器
onUnmounted(() => {
if (timeTimer) {
clearInterval(timeTimer);
}
});
</script>
<style scoped lang="scss">
.consultation-container {
padding: 10px;
background: #f5f7fa;
height: calc(100vh - 200px);
display: flex;
flex-direction: column;
.top-actions {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
.left-actions {
display: flex;
gap: 10px;
}
.right-actions {
display: flex;
gap: 10px;
}
}
.consultation-list {
background: #fff;
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
}
.content-area {
display: flex;
gap: 10px;
flex: 1;
overflow: hidden;
.left-panel {
flex: 1;
background: #fff;
padding: 15px;
border-radius: 4px;
overflow-y: auto;
.panel-title {
font-size: 16px;
font-weight: bold;
color: #303133;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #409eff;
}
.section-title {
font-size: 14px;
font-weight: bold;
color: #303133;
margin: 20px 0 10px 0;
}
.consultation-form {
:deep(.el-form-item) {
margin-bottom: 12px;
}
:deep(.el-form-item__label) {
font-size: 13px;
}
.invited-display {
min-height: 60px;
padding: 10px;
background: #f0f9ff;
border: 1px dashed #409eff;
border-radius: 4px;
.invited-list {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.empty-invited {
color: #909399;
font-size: 13px;
text-align: center;
padding: 10px 0;
}
}
}
}
.right-panel {
width: 280px;
background: #fff;
padding: 15px;
border-radius: 4px;
overflow-y: auto;
.panel-title {
font-size: 16px;
font-weight: bold;
color: #303133;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #409eff;
}
.selected-summary {
display: flex;
align-items: center;
padding: 10px;
background: #f0f9ff;
border: 1px solid #b3d8ff;
border-radius: 4px;
margin-bottom: 15px;
.summary-text {
font-size: 13px;
color: #606266;
}
.summary-count {
font-size: 18px;
font-weight: bold;
color: #409eff;
margin: 0 4px;
}
}
.invite-section {
margin-bottom: 15px;
.section-label {
font-size: 14px;
font-weight: 500;
color: #606266;
margin-bottom: 10px;
}
.department-list {
.department-item {
margin-bottom: 8px;
.dept-header {
display: flex;
align-items: center;
padding: 10px;
background: #f5f7fa;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
&:hover {
background: #e6f7ff;
}
&.expanded {
background: #e6f7ff;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.el-icon {
font-size: 14px;
color: #606266;
margin-right: 8px;
transition: transform 0.3s;
}
.dept-name {
flex: 1;
font-size: 14px;
font-weight: 500;
color: #303133;
}
.selected-badge {
display: inline-block;
min-width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
background: #67c23a;
color: #fff;
border-radius: 10px;
font-size: 12px;
padding: 0 6px;
}
}
.physicians-list {
background: #fafafa;
border: 1px solid #e6f7ff;
border-top: none;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
padding: 10px;
.physician-item {
padding: 6px 10px;
:deep(.el-checkbox) {
width: 100%;
.el-checkbox__label {
font-size: 13px;
color: #606266;
}
&.is-checked .el-checkbox__label {
color: #409eff;
font-weight: 500;
}
}
}
.no-physicians {
color: #909399;
font-size: 12px;
text-align: center;
padding: 10px 0;
}
}
}
}
}
}
}
}
</style>