feat(会诊管理): 优化会诊申请功能并新增会诊意见列表

- 新增获取会诊意见列表的API接口
- 重构会诊记录信息填充逻辑,支持已确认和已签名状态
- 优化前端会诊申请页面,调整时间类型选项和状态筛选
- 添加紧急程度复选框和会诊确认参加医师显示
- 实现会诊意见列表加载和自动填充功能
This commit is contained in:
wangjian963
2026-03-30 13:36:11 +08:00
parent 488c311788
commit 7073ef0be0
3 changed files with 182 additions and 212 deletions

View File

@@ -725,43 +725,47 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
dto.setInvitedList(invitedDtoList);
// 🎯 如果会诊已完成或已签名,填充会诊记录信息(从已签名的医生中获取)
if (entity.getConsultationStatus() != null &&
(entity.getConsultationStatus() == ConsultationStatusEnum.SIGNED.getCode() ||
entity.getConsultationStatus() == ConsultationStatusEnum.COMPLETED.getCode())) {
// 查询所有已确认已签名的医生invited_status >= 2
// invited_status: 1-已提交2-已确认3-已签名
// 🎯 如果会诊已确认已签名或已完成,填充会诊记录信息
// 会诊状态20=已确认30=已签名40=已完成
if (entity.getConsultationStatus() != null &&
entity.getConsultationStatus() >= ConsultationStatusEnum.CONFIRMED.getCode()) {
// 查询所有已确认和已签名的医生invited_status >= 20
List<ConsultationInvited> confirmedAndSignedPhysicians = invitedList.stream()
.filter(inv -> inv.getInvitedStatus() != null && inv.getInvitedStatus() >= 2)
.filter(inv -> inv.getInvitedStatus() != null && inv.getInvitedStatus() >= ConsultationStatusEnum.CONFIRMED.getCode())
.collect(Collectors.toList());
// 查询所有已签名的医生invited_status = 3
// 查询所有已签名的医生invited_status >= 30
List<ConsultationInvited> signedPhysicians = invitedList.stream()
.filter(inv -> inv.getInvitedStatus() != null && inv.getInvitedStatus() >= 3)
.filter(inv -> inv.getInvitedStatus() != null && inv.getInvitedStatus() >= ConsultationStatusEnum.SIGNED.getCode())
.collect(Collectors.toList());
if (!confirmedAndSignedPhysicians.isEmpty()) {
// 1. 会诊邀请参加医师:拼接所有已确认和已签名医生的"科室 - 姓名"
// 1. 会诊确认参加医师:拼接所有已确认和已签名医生的"科室 - 姓名"
String invitedPhysiciansText = confirmedAndSignedPhysicians.stream()
.map(inv -> inv.getInvitedDepartmentName() + "-" + inv.getInvitedPhysicianName())
.collect(Collectors.joining(""));
dto.setInvitedPhysiciansText(invitedPhysiciansText);
// 2. 会诊意见:汇总所有已签名医生的意见(只有已签名医生才能填写意见)
if (!signedPhysicians.isEmpty()) {
String consultationOpinion = signedPhysicians.stream()
// 2. 会诊意见:汇总所有已确认医生的意见
String consultationOpinion = confirmedAndSignedPhysicians.stream()
.filter(inv -> StringUtils.hasText(inv.getConfirmOpinion()))
.map(ConsultationInvited::getConfirmOpinion)
.collect(Collectors.joining("\n"));
dto.setConsultationOpinion(consultationOpinion);
// 3. 所属医生、代表科室、签名医生、签名时间:使用第一个签名的医生
// 3. 所属医生、代表科室:使用第一个确认的医生
ConsultationInvited firstConfirmed = confirmedAndSignedPhysicians.get(0);
dto.setAttendingPhysician(firstConfirmed.getInvitedPhysicianName());
dto.setRepresentDepartment(firstConfirmed.getInvitedDepartmentName());
// 4. 签名医生、签名时间:只有已签名医生才有
if (!signedPhysicians.isEmpty()) {
ConsultationInvited firstSigned = signedPhysicians.get(0);
dto.setAttendingPhysician(firstSigned.getInvitedPhysicianName());
dto.setRepresentDepartment(firstSigned.getInvitedDepartmentName());
dto.setSignPhysician(firstSigned.getInvitedPhysicianName());
dto.setSignTime(firstSigned.getSignatureTime());
}
log.info("填充会诊记录信息,已确认和已签名医生数:{},已签名医生数:{}",
confirmedAndSignedPhysicians.size(), signedPhysicians.size());
@@ -769,7 +773,6 @@ public class ConsultationAppServiceImpl implements IConsultationAppService {
}
}
}
}
// 如果门诊诊断为空,尝试从数据库自动获取
if (!StringUtils.hasText(entity.getProvisionalDiagnosis()) && entity.getEncounterId() != null) {

View File

@@ -123,3 +123,15 @@ export function getConsultationActivities() {
})
}
/**
* 获取会诊意见列表
* @param {String} consultationId 会诊申请单号
*/
export function getConsultationOpinions(consultationId) {
return request({
url: '/consultation/confirmation/opinions',
method: 'get',
params: { consultationId }
})
}

View File

@@ -11,8 +11,7 @@
<el-form-item label="时间类型">
<el-select v-model="queryParams.timeType" placeholder="请选择" style="width: 120px">
<el-option label="会诊时间" value="consultation" />
<el-option label="开始时间" value="start" />
<el-option label="结束时间" value="end" />
<el-option label="申请时间" value="request" />
</el-select>
</el-form-item>
@@ -69,8 +68,6 @@
<el-option label="全部" value="" />
<el-option label="未提交" value="0" />
<el-option label="提交" value="10" />
<el-option label="已确认" value="20" />
<el-option label="已签名" value="30" />
<el-option label="结束" value="40" />
</el-select>
</el-form-item>
@@ -109,7 +106,7 @@
v-loading="loading"
height="400"
>
<el-table-column type="index" label="ID" width="60" align="center" />
<el-table-column prop="consultationId" label="ID" min-width="150" align="center" show-overflow-tooltip />
<el-table-column label="急" width="60" align="center">
<template #default="scope">
<el-checkbox :model-value="scope.row.consultationUrgency === '2'" disabled />
@@ -118,25 +115,25 @@
<el-table-column prop="patientName" label="病人姓名" min-width="100" />
<el-table-column prop="consultationDate" label="会诊时间" min-width="160">
<template #default="scope">
{{ formatDateTime(scope.row.consultationDate) }}
{{ formatDate(scope.row.consultationDate) }}
</template>
</el-table-column>
<el-table-column prop="department" label="申请科室" min-width="120" />
<el-table-column prop="invitedObject" label="邀请对象" min-width="150" show-overflow-tooltip />
<el-table-column prop="consultationRequestDate" label="申请时间" min-width="160">
<template #default="scope">
{{ formatDateTime(scope.row.consultationRequestDate) }}
{{ formatDate(scope.row.consultationRequestDate) }}
</template>
</el-table-column>
<el-table-column prop="requestingPhysician" label="申请医师" min-width="100" />
<el-table-column label="提交" width="70" align="center">
<template #default="scope">
<el-checkbox :model-value="scope.row.consultationStatus >= 10" disabled />
<el-checkbox :model-value="scope.row.consultationStatus >= STATUS.SUBMITTED" disabled />
</template>
</el-table-column>
<el-table-column label="结束" width="70" align="center">
<template #default="scope">
<el-checkbox :model-value="scope.row.consultationStatus === 40" disabled />
<el-checkbox :model-value="scope.row.consultationStatus === STATUS.ENDED" disabled />
</template>
</el-table-column>
<el-table-column label="操作" width="250" fixed="right" align="center">
@@ -153,7 +150,7 @@
size="small"
:icon="Edit"
@click="handleEdit(scope.row)"
:disabled="scope.row.consultationStatus >= 40"
:disabled="scope.row.consultationStatus >= STATUS.ENDED"
title="编辑"
/>
<el-button
@@ -168,7 +165,7 @@
size="small"
:icon="Delete"
@click="handleDelete(scope.row)"
:disabled="scope.row.consultationStatus >= 40"
:disabled="scope.row.consultationStatus >= STATUS.ENDED"
title="作废"
/>
</template>
@@ -213,7 +210,7 @@
</el-col>
<el-col :span="12">
<el-form-item label="申请时间">
<el-input :model-value="formatDateTime(formData.consultationRequestDate)" disabled />
<el-input :model-value="formatDate(formData.consultationRequestDate)" disabled />
</el-form-item>
</el-col>
</el-row>
@@ -270,6 +267,14 @@
<el-input v-model="formData.department" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="紧急程度">
<el-checkbox v-model="formData.isUrgent" :true-value="true" :false-value="false">紧急</el-checkbox>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="门诊诊断">
<el-input v-model="formData.provisionalDiagnosis" />
@@ -293,8 +298,8 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="会诊邀请参加医师">
<el-input placeholder="请输入参加医师姓名" disabled />
<el-form-item label="会诊确认参加医师">
<el-input v-model="formData.confirmingPhysician" disabled />
</el-form-item>
</el-col>
</el-row>
@@ -360,9 +365,9 @@
import { ref, reactive, onMounted, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Refresh, Printer, Edit, View, Delete } from '@element-plus/icons-vue'
import { queryConsultationListPage, cancelConsultation, saveConsultation } from './api'
// 迁移到 hiprint
import { queryConsultationListPage, cancelConsultation, saveConsultation, getConsultationOpinions } from './api'
import { simplePrint, PRINT_TEMPLATE } from '@/utils/printUtils.js'
import { formatDate } from '@/utils/index.js'
const loading = ref(false)
const saving = ref(false)
@@ -371,11 +376,10 @@ const currentRow = ref(null)
const dialogVisible = ref(false)
const isViewMode = ref(false)
const formRef = ref(null)
const opinionList = ref([]) // 会诊意见列表
// 用于取消请求的控制器
let abortController = null
// 分页参数
const pagination = reactive({
currentPage: 1,
pageSize: 10,
@@ -393,7 +397,17 @@ const queryParams = reactive({
patientName: ''
})
const formData = ref({
// 会诊状态常量
const STATUS = {
NOT_SUBMITTED: 0,
SUBMITTED: 10,
CONFIRMED: 20,
SIGNED: 30,
ENDED: 40
}
// 初始表单数据模板
const getInitialFormData = () => ({
id: null,
consultationId: '',
patientId: null,
@@ -415,6 +429,7 @@ const formData = ref({
consultationActivityId: null,
consultationActivityName: '',
consultationUrgency: '',
isUrgent: false,
consultationStatus: 0,
consultationOpinion: '',
consultingPhysicians: '',
@@ -425,9 +440,50 @@ const formData = ref({
attendingPhysician: '',
representDepartment: '',
signPhysician: '',
signTime: null
signTime: null,
confirmingPhysician: ''
})
// 从 row 数据填充表单
const populateFormData = (row) => ({
id: row.id || null,
consultationId: row.consultationId || '',
patientId: row.patientId || null,
patientName: row.patientName || '',
genderEnum: row.genderEnum || null,
age: row.age || null,
patientBusNo: row.patientBusNo || '',
patientIdentifierNo: row.patientIdentifierNo || '',
encounterId: row.encounterId || null,
departmentId: row.departmentId || null,
department: row.department || '',
requestingPhysicianId: row.requestingPhysicianId || null,
requestingPhysician: row.requestingPhysician || '',
invitedObject: row.invitedObject || '',
consultationDate: row.consultationDate || '',
consultationRequestDate: row.consultationRequestDate || null,
consultationPurpose: row.consultationPurpose || '',
provisionalDiagnosis: row.provisionalDiagnosis || '',
consultationActivityId: row.consultationActivityId || null,
consultationActivityName: row.consultationActivityName || '',
consultationUrgency: row.consultationUrgency || '',
isUrgent: row.consultationUrgency === '2',
consultationStatus: row.consultationStatus || 0,
consultationOpinion: row.consultationOpinion || '',
consultingPhysicians: row.consultingPhysicians || '',
signingPhysicianId: row.signingPhysicianId || null,
signingPhysicianName: row.signingPhysicianName || '',
signingTime: row.signingTime || null,
invitedPhysiciansText: row.invitedPhysiciansText || '',
attendingPhysician: row.attendingPhysician || '',
representDepartment: row.representDepartment || '',
signPhysician: row.signPhysician || '',
signTime: row.signTime || null,
confirmingPhysician: ''
})
const formData = ref(getInitialFormData())
const formRules = {
requestingPhysician: [
{ required: true, message: '请输入申请医师', trigger: 'blur' }
@@ -444,23 +500,7 @@ const dialogTitle = computed(() => {
return isViewMode.value ? '会诊申请查看' : '会诊申请编辑'
})
const formatDateTime = (date) => {
if (!date) return ''
const d = new Date(date)
if (isNaN(d.getTime())) return ''
const pad = (value) => String(value).padStart(2, '0')
const yyyy = d.getFullYear()
const mm = pad(d.getMonth() + 1)
const dd = pad(d.getDate())
const hh = pad(d.getHours())
const mi = pad(d.getMinutes())
const ss = pad(d.getSeconds())
return `${yyyy}-${mm}-${dd} ${hh}:${mi}:${ss}`
}
const handleQuery = async () => {
// 验证时间范围
if (queryParams.startTime && queryParams.endTime) {
const start = new Date(queryParams.startTime)
const end = new Date(queryParams.endTime)
@@ -469,8 +509,6 @@ const handleQuery = async () => {
return
}
}
// 重置到第一页
pagination.currentPage = 1
await loadData()
}
@@ -494,16 +532,12 @@ const handleSizeChange = (val) => {
}
const handleCurrentChange = (val) => {
// 如果正在加载,忽略此次操作
if (loading.value) {
return
}
if (loading.value) return
pagination.currentPage = val
loadData()
}
const handlePrint = async (row) => {
// 优先使用传入的 row如果没有传入则使用 currentRow
const printRow = row || currentRow.value
if (!printRow) {
@@ -512,7 +546,6 @@ const handlePrint = async (row) => {
}
try {
// 构建打印数据
const printData = {
patientName: printRow.patientName || '',
gender: printRow.genderEnum === 1 ? '男' : '女',
@@ -534,137 +567,76 @@ const handleRowChange = (row) => {
currentRow.value = row
}
const handleEdit = (row) => {
// 检查是否已结束
if (row.consultationStatus >= 40) {
// 加载会诊意见列表
const loadConsultationOpinions = async (consultationId) => {
try {
const res = await getConsultationOpinions(consultationId)
if (res.code === 200) {
opinionList.value = res.data || []
}
} catch (error) {
console.error('加载会诊意见失败', error)
opinionList.value = []
}
}
// 打开弹窗的统一方法
const openDialog = async (row, viewOnly = false) => {
if (!viewOnly && row.consultationStatus >= STATUS.ENDED) {
ElMessage.warning('已结束的会诊申请不可编辑')
return
}
isViewMode.value = viewOnly
formData.value = populateFormData(row)
isViewMode.value = false
// 确保所有字段都复制到formData中
formData.value = {
id: row.id || null,
consultationId: row.consultationId || '',
patientId: row.patientId || null,
patientName: row.patientName || '',
genderEnum: row.genderEnum || null,
age: row.age || null,
patientBusNo: row.patientBusNo || '',
patientIdentifierNo: row.patientIdentifierNo || '',
encounterId: row.encounterId || null,
departmentId: row.departmentId || null,
department: row.department || '',
requestingPhysicianId: row.requestingPhysicianId || null,
requestingPhysician: row.requestingPhysician || '',
invitedObject: row.invitedObject || '',
consultationDate: row.consultationDate || '',
consultationRequestDate: row.consultationRequestDate || null,
consultationPurpose: row.consultationPurpose || '',
provisionalDiagnosis: row.provisionalDiagnosis || '',
consultationActivityId: row.consultationActivityId || null,
consultationActivityName: row.consultationActivityName || '',
consultationUrgency: row.consultationUrgency || '',
consultationStatus: row.consultationStatus || 0,
consultationOpinion: row.consultationOpinion || '',
consultingPhysicians: row.consultingPhysicians || '',
signingPhysicianId: row.signingPhysicianId || null,
signingPhysicianName: row.signingPhysicianName || '',
signingTime: row.signingTime || null,
invitedPhysiciansText: row.invitedPhysiciansText || '',
attendingPhysician: row.attendingPhysician || '',
representDepartment: row.representDepartment || '',
signPhysician: row.signPhysician || '',
signTime: row.signTime || null
// 加载会诊意见列表
await loadConsultationOpinions(row.consultationId)
// 从会诊意见中填充会诊记录相关字段
if (opinionList.value.length > 0) {
// 获取所有已确认参加的医师名称
const confirmedPhysicians = opinionList.value
.filter(op => op.physicianName)
.map(op => op.physicianName)
.join('、')
formData.value.confirmingPhysician = confirmedPhysicians
// 取第一条意见填充会诊意见字段
const firstOpinion = opinionList.value[0]
formData.value.consultationOpinion = firstOpinion.opinion || ''
formData.value.attendingPhysician = firstOpinion.physicianName || ''
formData.value.representDepartment = firstOpinion.deptName || ''
// 查找已签名的意见
const signedOpinion = opinionList.value.find(op => op.isSigned)
if (signedOpinion) {
formData.value.signPhysician = signedOpinion.physicianName || ''
formData.value.signTime = signedOpinion.signatureTime || null
}
}
dialogVisible.value = true
}
const handleView = (row) => {
isViewMode.value = true
// 确保所有字段都复制到formData中
formData.value = {
id: row.id || null,
consultationId: row.consultationId || '',
patientId: row.patientId || null,
patientName: row.patientName || '',
genderEnum: row.genderEnum || null,
age: row.age || null,
patientBusNo: row.patientBusNo || '',
patientIdentifierNo: row.patientIdentifierNo || '',
encounterId: row.encounterId || null,
departmentId: row.departmentId || null,
department: row.department || '',
requestingPhysicianId: row.requestingPhysicianId || null,
requestingPhysician: row.requestingPhysician || '',
invitedObject: row.invitedObject || '',
consultationDate: row.consultationDate || '',
consultationRequestDate: row.consultationRequestDate || null,
consultationPurpose: row.consultationPurpose || '',
provisionalDiagnosis: row.provisionalDiagnosis || '',
consultationActivityId: row.consultationActivityId || null,
consultationActivityName: row.consultationActivityName || '',
consultationUrgency: row.consultationUrgency || '',
consultationStatus: row.consultationStatus || 0,
consultationOpinion: row.consultationOpinion || '',
consultingPhysicians: row.consultingPhysicians || '',
signingPhysicianId: row.signingPhysicianId || null,
signingPhysicianName: row.signingPhysicianName || '',
signingTime: row.signingTime || null,
invitedPhysiciansText: row.invitedPhysiciansText || '',
attendingPhysician: row.attendingPhysician || '',
representDepartment: row.representDepartment || '',
signPhysician: row.signPhysician || '',
signTime: row.signTime || null
}
dialogVisible.value = true
}
const handleEdit = (row) => openDialog(row, false)
const handleView = (row) => openDialog(row, true)
const handleDialogClose = () => {
formRef.value?.clearValidate()
formData.value = {
id: null,
consultationId: '',
patientId: null,
patientName: '',
genderEnum: null,
age: null,
patientBusNo: '',
patientIdentifierNo: '',
encounterId: null,
departmentId: null,
department: '',
requestingPhysicianId: null,
requestingPhysician: '',
invitedObject: '',
consultationDate: '',
consultationRequestDate: null,
consultationPurpose: '',
provisionalDiagnosis: '',
consultationActivityId: null,
consultationActivityName: '',
consultationUrgency: '',
consultationStatus: 0,
consultationOpinion: '',
consultingPhysicians: '',
signingPhysicianId: null,
signingPhysicianName: '',
signingTime: null,
invitedPhysiciansText: '',
attendingPhysician: '',
representDepartment: '',
signPhysician: '',
signTime: null
}
formData.value = getInitialFormData()
opinionList.value = []
}
const handleSave = async () => {
try {
// 表单验证
await formRef.value.validate()
saving.value = true
const res = await saveConsultation(formData.value)
// 将 isUrgent 转换回 consultationUrgency
const submitData = {
...formData.value,
consultationUrgency: formData.value.isUrgent ? '2' : '1'
}
const res = await saveConsultation(submitData)
if (res.code === 200) {
ElMessage.success('保存成功')
@@ -684,8 +656,7 @@ const handleSave = async () => {
}
const handleDelete = async (row) => {
// 检查是否已结束
if (row.consultationStatus >= 40) {
if (row.consultationStatus >= STATUS.ENDED) {
ElMessage.warning('已结束的会诊申请不可作废')
return
}
@@ -717,18 +688,14 @@ const handleDelete = async (row) => {
}
const loadData = async () => {
// 如果正在加载,取消上一个请求
if (loading.value && abortController) {
abortController.abort()
}
try {
loading.value = true
// 创建新的 AbortController
abortController = new AbortController()
// 构建查询参数
const queryData = {
department: queryParams.applyDept,
requestingPhysician: queryParams.applyDoctor,
@@ -738,22 +705,15 @@ const loadData = async () => {
consultationUrgency: queryParams.urgency
}
console.log('查询参数:', queryData, '页码:', pagination.currentPage, '每页:', pagination.pageSize)
const res = await queryConsultationListPage(queryData, pagination.currentPage, pagination.pageSize)
console.log('查询结果:', res)
if (res.code === 200) {
tableData.value = res.data.records || []
pagination.total = res.data.total || 0
// 如果没有查询结果,显示提示
if (tableData.value.length === 0 && pagination.currentPage === 1) {
ElMessage.info('暂无查询结果')
}
} else {
// 忽略"数据正在处理"的错误提示
if (!res.msg || !res.msg.includes('数据正在处理')) {
ElMessage.error(res.msg || '加载数据失败')
}
@@ -761,15 +721,10 @@ const loadData = async () => {
pagination.total = 0
}
} catch (error) {
// 忽略取消请求的错误
if (error.name === 'AbortError' || error.name === 'CanceledError') {
console.log('请求已取消')
return
}
console.error('加载数据失败:', error)
// 忽略"数据正在处理"的错误提示
const errorMsg = error.message || error.msg || '网络错误'
if (!errorMsg.includes('数据正在处理')) {
ElMessage.error('加载数据失败: ' + errorMsg)