Files
his/openhis-ui-vue3/src/views/consultationmanagement/consultationconfirmation/index.vue

637 lines
19 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 consultation-confirmation">
<div class="page-header">
<span class="tab-title">会诊确认</span>
</div>
<div class="action-bar no-print">
<el-button type="success" @click="handlePrint">打印</el-button>
<el-button @click="handleRefresh">刷新</el-button>
<el-button
type="primary"
:disabled="!currentRow"
@click="handleConfirm"
>
{{ confirmButtonText }}
</el-button>
<el-button
type="primary"
:disabled="!canSign"
@click="handleSign"
>
签名
</el-button>
</div>
<div class="list-section no-print">
<el-table
:data="tableData"
border
stripe
highlight-current-row
@current-change="handleRowChange"
v-loading="loading"
>
<el-table-column type="index" width="60" label="序号" align="center" />
<el-table-column label="紧急" width="70" align="center">
<template #default="scope">
<el-checkbox v-model="scope.row.urgent" disabled />
</template>
</el-table-column>
<el-table-column prop="consultationId" label="申请单号" min-width="160" />
<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) }}
</template>
</el-table-column>
<el-table-column prop="invitedObject" label="邀请对象" min-width="120" />
<el-table-column prop="applyDept" label="申请科室" min-width="120" />
<el-table-column prop="applyDoctor" label="申请医师" min-width="120" />
<el-table-column prop="applyTime" label="申请时间" min-width="160">
<template #default="scope">
{{ formatDateTime(scope.row.applyTime) }}
</template>
</el-table-column>
<el-table-column label="确认" width="70" align="center">
<template #default="scope">
<el-checkbox :model-value="scope.row.myInvitedStatus >= 20" disabled />
</template>
</el-table-column>
<el-table-column label="签名" width="70" align="center">
<template #default="scope">
<el-checkbox :model-value="scope.row.myInvitedStatus >= 30" disabled />
</template>
</el-table-column>
</el-table>
</div>
<div class="form-section">
<div class="section-title">会诊记录单</div>
<el-form :model="formData" label-width="110px">
<el-row :gutter="16">
<el-col :span="6">
<el-form-item label="病人姓名">
<el-input v-model="formData.patientName" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="性别">
<el-input v-model="formData.genderText" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="年龄">
<el-input v-model="formData.age" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="就诊卡号">
<el-input v-model="formData.patientIdentifierNo" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="申请单号">
<el-input v-model="formData.consultationId" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="申请科室">
<el-input v-model="formData.applyDept" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="会诊时间">
<el-input :model-value="formatDateTime(formData.consultationDate)" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="紧急标志">
<el-input :model-value="formData.urgent ? '是' : '否'" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="会诊邀请对象">
<el-input v-model="formData.invitedObject" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="提交医生">
<el-input v-model="formData.submittingPhysician" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="提交时间">
<el-input :model-value="formatDateTime(formData.submittingTime)" disabled />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="病史及目的">
<el-input v-model="formData.consultationPurpose" type="textarea" :rows="3" disabled />
</el-form-item>
<el-form-item label="门诊诊断">
<el-input v-model="formData.provisionalDiagnosis" type="textarea" :rows="2" disabled />
</el-form-item>
<el-form-item label="会诊意见" required>
<el-input
v-model="formData.consultationOpinion"
type="textarea"
:rows="4"
placeholder="请输入会诊意见"
:disabled="!canEdit"
/>
</el-form-item>
<!-- 会诊意见列表 -->
<el-form-item label="所有会诊意见">
<div class="opinions-container">
<el-timeline v-if="opinionList.length > 0">
<el-timeline-item
v-for="opinion in opinionList"
:key="opinion.physicianId"
:timestamp="formatDateTime(opinion.confirmTime)"
placement="top"
>
<el-card shadow="hover">
<div class="opinion-header">
<span class="physician-info">
<el-tag type="info" size="small">{{ opinion.deptName }}</el-tag>
<span style="margin-left: 8px; font-weight: bold;">{{ opinion.physicianName }}</span>
</span>
<el-tag v-if="opinion.isSigned" type="success" size="small">
<el-icon><Check /></el-icon> 已签名
</el-tag>
<el-tag v-else type="warning" size="small">已确认</el-tag>
</div>
<div class="opinion-content">
{{ opinion.opinion }}
</div>
<div v-if="opinion.signatureTime" class="opinion-footer">
签名时间:{{ formatDateTime(opinion.signatureTime) }}
</div>
</el-card>
</el-timeline-item>
</el-timeline>
<el-empty v-else description="暂无会诊意见" :image-size="80" />
</div>
</el-form-item>
<el-row :gutter="16">
<el-col :span="6">
<el-form-item label="所属医生">
<el-input v-model="formData.confirmingPhysicianName" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="代表科室">
<el-input v-model="formData.confirmingDeptName" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="签名医生">
<el-input v-model="formData.signature" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="签名时间">
<el-input :model-value="formatDateTime(formData.signatureDate)" disabled />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
</div>
</template>
<script setup name="consultationConfirmation">
import { computed, ref, onMounted, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Check } from '@element-plus/icons-vue'
import useUserStore from '@/store/modules/user'
import {
getPendingConfirmationList,
confirmConsultation,
cancelConfirmation,
signConsultation,
getConsultationOpinions
} from './api'
const userStore = useUserStore()
const loading = ref(false)
const tableData = ref([])
const currentRow = ref(null)
const opinionList = ref([]) // 会诊意见列表
const formData = ref({
id: null,
consultationRequestId: null,
consultationId: '',
patientName: '',
genderText: '',
age: '',
patientIdentifierNo: '',
applyDept: '',
applyDoctor: '',
applyTime: null,
consultationDate: null,
urgent: false,
invitedObject: '',
consultationPurpose: '',
provisionalDiagnosis: '',
confirmingPhysician: '',
consultationOpinion: '',
confirmingPhysicianName: '',
confirmingDeptName: '',
signature: '',
signatureDate: null,
submittingPhysician: '',
submittingTime: null,
consultationStatus: 0
})
const confirmButtonText = computed(() => {
if (!currentRow.value) {
return '确认'
}
// 基于当前医生的个人状态判断使用ConsultationStatusEnum20=已确认)
return currentRow.value.myInvitedStatus >= 20 ? '取消确认' : '确认'
})
const canSign = computed(() => {
// 只要当前医生已确认但未签名,就可以签名(不需要等待所有医生都确认)
// 使用ConsultationStatusEnum20=已确认30=已签名
return !!currentRow.value &&
currentRow.value.myInvitedStatus >= 20 && // 已确认
currentRow.value.myInvitedStatus < 30 // 但还未签名
})
const canEdit = computed(() => {
// 当前医生未确认时可以编辑使用ConsultationStatusEnum10=已提交/待确认20=已确认)
return !!currentRow.value && (!currentRow.value.myInvitedStatus || currentRow.value.myInvitedStatus < 20)
})
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 getDoctorName = () => userStore.nickName || userStore.name || '当前医生'
const getDoctorDept = () => userStore.orgName || '当前科室'
const applyRowToForm = (row) => {
if (!row) {
Object.keys(formData.value).forEach((key) => {
if (key === 'urgent') {
formData.value[key] = false
} else if (key === 'consultationStatus') {
formData.value[key] = 0
} else {
formData.value[key] = key === 'id' || key === 'consultationRequestId' ? null : ''
}
})
return
}
formData.value = {
id: row.id,
consultationRequestId: row.consultationRequestId,
consultationId: row.consultationId,
patientName: row.patientName,
genderText: row.genderText,
age: row.age,
patientIdentifierNo: row.patientIdentifierNo,
applyDept: row.applyDept,
applyDoctor: row.applyDoctor,
applyTime: row.applyTime,
consultationDate: row.consultationDate,
urgent: row.urgent,
invitedObject: row.invitedObject,
consultationPurpose: row.consultationPurpose,
provisionalDiagnosis: row.provisionalDiagnosis,
confirmingPhysician: row.confirmingPhysician || '',
consultationOpinion: '', // 先清空,后面从 opinionList 中获取
confirmingPhysicianName: row.confirmingPhysicianName || '',
confirmingDeptName: row.confirmingDeptName || '',
signature: row.signature || '',
signatureDate: row.signatureDate,
submittingPhysician: row.submittingPhysician,
submittingTime: row.submittingTime,
consultationStatus: row.consultationStatus
}
// 从会诊意见列表中获取当前医生的信息
if (opinionList.value.length > 0) {
const currentPhysicianId = userStore.practitionerId || userStore.user?.practitionerId
const myOpinion = opinionList.value.find(op => op.physicianId === currentPhysicianId)
if (myOpinion) {
// 如果当前医生已确认,回显其信息
formData.value.confirmingPhysicianName = myOpinion.physicianName
formData.value.confirmingDeptName = myOpinion.deptName
// 回显会诊意见(去掉前缀"科室-医生"
if (myOpinion.opinion) {
// 格式:科室-医生:意见内容
const opinionText = myOpinion.opinion
const colonIndex = opinionText.indexOf('')
if (colonIndex > 0) {
// 提取冒号后面的内容
formData.value.consultationOpinion = opinionText.substring(colonIndex + 1)
} else {
formData.value.consultationOpinion = opinionText
}
}
if (myOpinion.isSigned) {
formData.value.signature = myOpinion.physicianName
formData.value.signatureDate = myOpinion.signatureTime
}
}
}
}
const handleRowChange = async (row) => {
currentRow.value = row
// 先加载会诊意见列表
if (row) {
await loadConsultationOpinions(row.consultationId)
} else {
opinionList.value = []
}
// 然后应用数据到表单(这样可以使用 opinionList 中的数据)
applyRowToForm(row)
}
// 加载会诊意见列表
const loadConsultationOpinions = async (consultationId) => {
try {
const res = await getConsultationOpinions(consultationId)
if (res.code === 200) {
opinionList.value = res.data || []
}
} catch (error) {
console.error('加载会诊意见失败', error)
}
}
const handleConfirm = async () => {
if (!currentRow.value) {
return
}
try {
// 如果已确认则取消确认使用ConsultationStatusEnum20=已确认30=已签名)
if (currentRow.value.myInvitedStatus >= 20) {
// 已签名不能取消确认
if (currentRow.value.myInvitedStatus >= 30) {
ElMessage.warning('已签名的会诊无法取消确认')
return
}
await ElMessageBox.confirm('确定要取消确认吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
loading.value = true
const res = await cancelConfirmation(currentRow.value.consultationId)
if (res.code === 200) {
ElMessage.success('取消确认成功')
await loadData()
} else {
ElMessage.error(res.msg || '取消确认失败')
}
} else {
// 确认会诊
if (!formData.value.consultationOpinion.trim()) {
ElMessage.warning('请先填写会诊意见')
return
}
loading.value = true
const data = {
consultationId: formData.value.consultationId,
consultationOpinion: formData.value.consultationOpinion,
confirmingPhysician: getDoctorName(), // 自动使用当前医生姓名
confirmingDeptName: getDoctorDept() // 自动使用当前科室
}
const res = await confirmConsultation(data)
if (res.code === 200) {
ElMessage.success('会诊确认成功')
await loadData()
} else {
ElMessage.error(res.msg || '会诊确认失败')
}
}
} catch (error) {
if (error !== 'cancel') {
console.error('操作失败:', error)
ElMessage.error('操作失败')
}
} finally {
loading.value = false
}
}
const handleSign = async () => {
if (!currentRow.value) {
return
}
// 检查当前医生是否已确认使用ConsultationStatusEnum20=已确认30=已签名)
if (!currentRow.value.myInvitedStatus || currentRow.value.myInvitedStatus < 20) {
ElMessage.warning('请先确认会诊后再签名')
return
}
// 检查是否已签名
if (currentRow.value.myInvitedStatus >= 30) {
ElMessage.warning('您已经签名过了')
return
}
// 🎯 移除整体状态检查:只要当前医生已确认就可以签名,不需要等待所有医生都确认
try {
await ElMessageBox.confirm('确定要签名吗?签名后将无法修改会诊意见。', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
loading.value = true
const res = await signConsultation(currentRow.value.consultationId)
if (res.code === 200) {
ElMessage.success('签名成功')
await loadData()
} else {
ElMessage.error(res.msg || '签名失败')
}
} catch (error) {
if (error !== 'cancel') {
console.error('签名失败:', error)
ElMessage.error('签名失败')
}
} finally {
loading.value = false
}
}
const handlePrint = () => {
if (!currentRow.value) {
ElMessage.warning('请先选择会诊申请')
return
}
window.print()
}
const handleRefresh = async () => {
await loadData()
ElMessage.success('已刷新')
}
const loadData = async () => {
try {
loading.value = true
const res = await getPendingConfirmationList()
if (res.code === 200) {
tableData.value = res.data || []
// 如果当前选中的行还在列表中,重新选中
if (currentRow.value) {
const updatedRow = tableData.value.find(
item => item.consultationId === currentRow.value.consultationId
)
if (updatedRow) {
currentRow.value = updatedRow
// 重新加载会诊意见
await loadConsultationOpinions(updatedRow.consultationId)
// 然后应用数据到表单
applyRowToForm(updatedRow)
} else {
currentRow.value = null
applyRowToForm(null)
opinionList.value = []
}
}
} else {
ElMessage.error(res.msg || '加载数据失败')
}
} catch (error) {
console.error('加载数据失败:', error)
ElMessage.error('加载数据失败')
} finally {
loading.value = false
}
}
onMounted(() => {
loadData()
})
</script>
<style scoped>
.consultation-confirmation .page-header {
padding-bottom: 8px;
border-bottom: 1px solid #e5e7eb;
}
.consultation-confirmation .tab-title {
display: inline-block;
font-size: 16px;
font-weight: 700;
color: #4a89dc;
border-bottom: 2px solid #4a89dc;
padding-bottom: 6px;
}
.action-bar {
margin: 16px 0;
display: flex;
gap: 12px;
}
.list-section {
margin-bottom: 16px;
}
.section-title {
font-size: 15px;
font-weight: 600;
margin-bottom: 12px;
}
@media print {
.no-print {
display: none !important;
}
.consultation-confirmation {
padding: 0;
}
}
.opinions-container {
width: 100%;
max-height: 500px;
overflow-y: auto;
padding: 10px;
background-color: #f9fafb;
border-radius: 4px;
}
.opinion-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.physician-info {
display: flex;
align-items: center;
}
.opinion-content {
padding: 12px;
background-color: #f5f7fa;
border-radius: 4px;
line-height: 1.6;
color: #333;
white-space: pre-wrap;
word-break: break-word;
}
.opinion-footer {
margin-top: 8px;
font-size: 12px;
color: #909399;
text-align: right;
}
.el-timeline {
padding-left: 0;
}
</style>