inspect-front/src/views/Department-entry/CT.vue

832 lines
21 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="ct-container">
<!-- 基本信息 -->
<div class="basic-info">
<div class="photo-box">
<img v-if="reportData.headPicUrl" :src="reportData.headPicUrl" alt="头像" class="photo"/>
<div v-else class="no-photo">个人照片</div>
</div>
<div class="info-grid">
<div class="info-row">
<div class="info-item">
<label>体检编号:</label>
<span>{{ reportData.medicalSn }}</span>
</div>
<div class="info-item">
<label>身份证号:</label>
<span>{{ reportData.cardId }}</span>
</div>
<div class="info-item">
<label>姓名:</label>
<span>{{ reportData.pname }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<label>性别:</label>
<span>{{ reportData.gender }}</span>
</div>
<div class="info-item">
<label>年龄:</label>
<span>{{ age }}岁</span>
</div>
<div class="info-item">
<label>电话:</label>
<span>{{ reportData.phoneNum }}</span>
</div>
</div>
</div>
</div>
<!-- 检查结果部分 -->
<div class="exam-results">
<!-- 检查项目标签 -->
<div class="exam-tabs">
<div
v-for="tab in filteredExamTabs"
:key="tab.id"
:class="['tab-item', { active: currentTab === tab.id }]"
@click="switchTab(tab.id)"
>
<div class="tab-indicator" :style="{ background: tab.color }"></div>
<span>{{ tab.name }}</span>
</div>
</div>
<!-- 影像检查结果 -->
<div class="image-results">
<!-- 影像图片展示区 -->
<div class="image-gallery">
<div class="gallery-title">影像图片</div>
<div class="image-container">
<div v-if="imageUrls.length > 0" class="image-grid" :class="{'single-image': imageUrls.length === 1}">
<div v-for="(url, index) in imageUrls" :key="index" class="image-item">
<el-image
:src="url"
:preview-src-list="imageUrls"
:initial-index="index"
fit="contain"
class="medical-image"
@error="handleImageError"
:preview-teleported="true"
>
<template #error>
<div class="image-error">
<el-icon><Picture /></el-icon>
<div class="error-text">加载失败</div>
</div>
</template>
</el-image>
</div>
</div>
<div v-else class="no-images">
<el-empty description="暂无影像资料" />
</div>
</div>
</div>
<!-- 检查所见和诊断 -->
<div class="findings-diagnosis-container">
<div class="findings-section">
<div class="section-title">检查所见</div>
<textarea
v-model="imageFinding"
placeholder="请输入检查所见"
class="findings-textarea"
:readonly="isReadOnly"
></textarea>
</div>
<div class="diagnosis-section">
<div class="section-title">检查结果</div>
<textarea
v-model="imageDiagnosis"
placeholder="请输入检查结果"
class="diagnosis-textarea"
:readonly="isReadOnly"
></textarea>
</div>
</div>
</div>
</div>
<!-- 底部操作栏 -->
<div class="action-footer"></div>
<div class="footer-content">
<div class="doctor-info">
<span>检查医生:{{ user?.nickname || '暂无' }}</span>
<span>检查日期:{{ formattedMedicalDateTime }}</span>
</div>
<div class="action-buttons">
<button class="action-btn" @click="checkEditPermission">同步结果</button>
<button class="action-btn" @click="checkEditPermission">弃检</button>
<button
v-if="!isReadOnly"
class="action-btn primary"
@click="handleSaveResults"
>
保存结果
</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue'
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'
import { PatientApi } from '@/api/inspect/inspectpatient'
import { PatientitemsApi } from '@/api/inspect/inspectpatientitems'
import { getUserProfile } from '@/api/system/user/profile'
import { PacsDataApi } from '@/api/inspect/inspectpacsdata'
import { Picture } from '@element-plus/icons-vue'
// 添加props定义
const props = defineProps({
patient: {
type: Object,
required: true
}
})
// 添加响应式引用
const reportData = ref({})
const imageUrls = ref([])
const imageFinding = ref('')
const imageDiagnosis = ref('')
const examConclusion = ref('')
const user = ref(null)
const patientDataCache = ref(new Map())
const pacsData = ref([])
const isImageDepartment = ref(false)
// 添加检查类型标签
const examTabs = ref([
{ id: 'ct', name: 'CT检查', color: '#409EFF', keywords: ['ct', 'CT'] },
{ id: 'us', name: '超声检查', color: '#F56C6C', keywords: ['超声', '彩超', 'us', 'US'] },
{ id: 'ecg', name: '心电图检查', color: '#909399', keywords: ['心电图', 'ecg', 'ECG'] }
])
// 当前选中的检查类型
const currentTab = ref('ct')
// 根据模块名称过滤标签
const filteredExamTabs = ref([])
// 添加一个变量来存储当前检查项目
const currentItem = ref(null)
// 切换检查类型
const switchTab = async (tabId) => {
console.log('切换到检查类型:', tabId)
currentTab.value = tabId
// 查找当前标签对应的检查项目
if (reportData.value.medicalSn) {
try {
const patientItemsResponse = await PatientitemsApi.getPatientitemsPage({
medicalSn: reportData.value.medicalSn,
pageNo: 1,
pageSize: 100
})
if (patientItemsResponse?.list && patientItemsResponse.list.length > 0) {
const currentTabKeywords = examTabs.value.find(tab => tab.id === tabId)?.keywords || []
currentItem.value = patientItemsResponse.list.find(item =>
item.moduleName && currentTabKeywords.some(keyword => item.moduleName.includes(keyword))
)
console.log('切换标签后找到的检查项目:', currentItem.value)
// 如果找到了检查项目,加载其检查所见和结果
if (currentItem.value) {
imageFinding.value = currentItem.value.examDescription || ''
imageDiagnosis.value = currentItem.value.itemResult || ''
} else {
// 如果没找到,清空检查所见和结果
imageFinding.value = ''
imageDiagnosis.value = ''
}
}
} catch (error) {
console.error('获取患者检查项目失败:', error)
}
}
imageUrls.value = await getImageUrlsByType(tabId)
console.log('获取到的图片URLs:', imageUrls.value)
}
// 修改获取影像图片URL的方法
const getImageUrlsByType = async (type) => {
try {
console.log('开始获取PACS数据, 参数:', {
code: reportData.value.medicalSn,
type: type.toUpperCase(),
pageNo: 1,
pageSize: 100
})
// 检查必要参数
if (!reportData.value.medicalSn) {
console.warn('缺少体检编号(medicalSn)')
return []
}
// 简化请求参数,只传递必要的字段
const response = await PacsDataApi.getPacsDataPage({
code: reportData.value.medicalSn,
type: type.toUpperCase(),
pageNo: 1,
pageSize: 100
})
if (!response?.list) {
console.warn('PACS数据列表为空')
return []
}
pacsData.value = response.list
// 直接使用data字段作为图片URL过滤掉空值
const urls = response.list
.map(item => item.data)
.filter(url => url)
return urls
} catch (error) {
console.error('获取PACS数据失败:', error)
ElMessage.error('获取影像数据失败,请联系管理员')
return []
}
}
// 处理图片加载错误
const handleImageError = (e) => {
e.target.src = 'https://via.placeholder.com/500x400?text=Image+Not+Found'
}
// 年龄计算属性
const age = computed(() => {
if (!reportData.value.birthday) return ''
// 处理 YYYY-MM-DD 格式的生日
const birthDate = new Date(reportData.value.birthday)
if (isNaN(birthDate.getTime())) return '' // 日期无效时返回空字符串
const today = new Date()
let age = today.getFullYear() - birthDate.getFullYear()
const monthDiff = today.getMonth() - birthDate.getMonth()
// 如果还没到生日月份或者是生日月但还没到具体日期年龄减1
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--
}
return age
})
// 格式化后的检查日期计算属性
const formattedMedicalDateTime = computed(() => {
if (!reportData.value.medicalDateTime) return ''
const date = new Date(reportData.value.medicalDateTime)
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).replace(/\//g, '-')
})
// 是否只读的计算属性
const isReadOnly = computed(() => {
console.log('检查项目状态:', currentItem.value?.itemStatus)
// 项目状态为0表示未检查应该可以编辑
// 项目状态为1表示已检查应该只读
return currentItem.value?.itemStatus === '1'
})
// 检查编辑权限的方法
const checkEditPermission = () => {
if (isReadOnly.value) {
ElMessage.warning('检查结果已保存,不可进行编辑')
return false
}
return true
}
// 保存结果方法
const handleSaveResults = async () => {
try {
if (!checkEditPermission()) {
return
}
console.log('开始保存检查结果')
console.log('检查所见:', imageFinding.value)
console.log('检查结果:', imageDiagnosis.value)
const userProfile = await getUserProfile()
user.value = userProfile
console.log('获取到的用户信息:', user.value)
// 获取当前时间戳
const currentTime = new Date().getTime()
// 检查当前项目
console.log('当前检查项目:', currentItem.value)
if (!currentItem.value || !currentItem.value.id) {
ElMessage.error('未找到当前检查项目信息')
return
}
// 更新检查项目结果
const updatedItems = [{
id: currentItem.value.id, // 使用检查项目ID
examDescription: imageFinding.value, // 检查所见
itemResult: imageDiagnosis.value, // 检查结果
itemStatus: '1', // 已检查
inspectdoctor: user.value?.nickname || '',
inspecttime: currentTime
}]
console.log('准备提交的检查项目数据:', updatedItems)
const updateResult = await PatientitemsApi.updatePatientitemsBatch(updatedItems)
console.log('检查项目更新结果:', updateResult)
// 更新当前项目状态
currentItem.value.itemStatus = '1'
currentItem.value.examDescription = imageFinding.value
currentItem.value.itemResult = imageDiagnosis.value
ElMessage.success('保存成功')
} catch (error) {
console.error('保存失败:', error)
console.error('错误详情:', error.response || error)
ElMessage.error(`保存失败: ${error.message || '未知错误'}`)
}
}
// 加载患者数据和检查项目
const loadPatientData = async (patient) => {
if (!patient || !patient.id) {
console.log('没有有效的患者数据')
return
}
try {
console.log('开始加载患者数据:', patient.id)
const cacheKey = patient.id
if (patientDataCache.value.has(cacheKey)) {
console.log('使用缓存的患者数据')
const cachedData = patientDataCache.value.get(cacheKey)
reportData.value = cachedData.reportData
return
}
const loading = ElLoading.service({
lock: true,
text: '加载中...',
background: 'rgba(255, 255, 255, 0.7)'
})
try {
// 获取用户信息和患者数据
const [userProfile, patientData] = await Promise.all([
getUserProfile(),
PatientApi.getPatient(patient.id)
])
user.value = userProfile
reportData.value = patientData
// 获取患者检查项目数据
if (reportData.value.medicalSn) {
try {
const patientItemsResponse = await PatientitemsApi.getPatientitemsPage({
medicalSn: reportData.value.medicalSn,
pageNo: 1,
pageSize: 100
})
if (patientItemsResponse?.list && patientItemsResponse.list.length > 0) {
// 从检查项目中提取moduleName
const moduleNames = patientItemsResponse.list.map(item => item.moduleName).filter(Boolean)
// 根据moduleName过滤标签
if (moduleNames.length > 0) {
const matchedTabs = []
// 检查每个moduleName是否匹配任何标签
for (const moduleName of moduleNames) {
for (const tab of examTabs.value) {
if (tab.keywords.some(keyword => moduleName.includes(keyword)) &&
!matchedTabs.some(t => t.id === tab.id)) {
matchedTabs.push(tab)
}
}
}
// 设置匹配的标签
if (matchedTabs.length > 0) {
// 只显示匹配的标签
filteredExamTabs.value = matchedTabs
// 设置第一个匹配的标签为当前标签
currentTab.value = matchedTabs[0].id
// 查找当前标签对应的检查项目
const currentTabKeywords = examTabs.value.find(tab => tab.id === currentTab.value)?.keywords || []
currentItem.value = patientItemsResponse.list.find(item =>
item.moduleName && currentTabKeywords.some(keyword => item.moduleName.includes(keyword))
)
console.log('找到的当前检查项目:', currentItem.value)
// 如果找到了检查项目,加载其检查所见和结果
if (currentItem.value) {
imageFinding.value = currentItem.value.examDescription || ''
imageDiagnosis.value = currentItem.value.itemResult || ''
}
// 加载当前标签的影像数据
imageUrls.value = await getImageUrlsByType(currentTab.value)
}
}
}
} catch (error) {
console.error('获取患者检查项目失败:', error)
}
}
// 缓存数据
patientDataCache.value.set(cacheKey, {
reportData: { ...reportData.value }
})
} finally {
loading.close()
}
} catch (error) {
console.error('获取患者详细信息出错:', error)
ElMessage.error('获取患者详细信息失败')
}
}
// 监听患者变化
watch(() => props.patient, (newPatient) => {
if (newPatient) {
loadPatientData(newPatient)
}
}, { immediate: true })
// 修改组件挂载时的逻辑
onMounted(async () => {
if (props.patient) {
// 初始化为所有标签,防止界面空白
filteredExamTabs.value = examTabs.value
// 加载患者数据和检查项目
await loadPatientData(props.patient)
}
})
</script>
<style scoped>
.ct-container {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
background: #fff;
height: 100%;
padding: 20px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
.basic-info {
flex-shrink: 0; /* 防止基本信息被压缩 */
display: flex;
gap: 20px;
padding: 15px; /* 减小内边距 */
background: #F8F9FA;
border-radius: 4px;
margin-bottom: 15px; /* 减小底部间距 */
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
}
.photo-box {
width: 120px;
height: 160px;
background: #fff;
border: 1px dashed #dcdfe6;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.no-photo {
color: #909399;
font-size: 14px;
}
.photo {
width: 100%;
height: 100%;
object-fit: cover;
}
.info-grid {
flex: 1;
}
.info-row {
display: flex;
margin-bottom: 15px;
}
.info-row:last-child {
margin-bottom: 0;
}
.info-item {
flex: 1;
display: flex;
align-items: center;
min-width: 0;
}
.info-item label {
color: #606266;
margin-right: 8px;
white-space: nowrap;
font-weight: 500;
}
.info-item span {
color: #333;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.exam-results {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0; /* 关键:允许内容收缩 */
overflow: hidden; /* 防止溢出 */
}
.exam-tabs {
flex-shrink: 0;
display: flex;
margin-bottom: 15px;
position: sticky;
top: 0;
z-index: 10;
background: #fff;
padding: 10px 0;
}
.tab-item {
position: relative;
padding: 8px 16px;
margin-right: 10px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
border-radius: 4px;
background: #fff;
color: #606266;
font-size: 14px;
border: 1px solid #dcdfe6;
transition: all 0.3s;
z-index: 1;
}
.tab-indicator {
width: 12px;
height: 12px;
border-radius: 2px;
}
.tab-item.active {
background: #f5f7fa;
border-color: #409EFF;
font-weight: 500;
transform: translateY(-2px);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
.tab-item:hover:not(.active) {
border-color: #c0c4cc;
background: #f9f9f9;
}
.image-results {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0; /* 关键:允许内容收缩 */
overflow: hidden;
}
.image-gallery {
flex: 1;
display: flex;
flex-direction: column;
margin-bottom: 20px;
border: 1px solid #ebeef5;
border-radius: 4px;
overflow: hidden;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
}
.gallery-title {
padding: 12px 15px;
font-weight: 500;
background: #f5f7fa;
border-bottom: 1px solid #ebeef5;
color: #303133;
}
.image-container {
flex: 1;
padding: 15px;
min-height: 0; /* 允许容器收缩 */
overflow: auto; /* 如果内容过多,只在图片区域显示滚动条 */
}
.image-grid {
display: flex;
flex-wrap: wrap;
gap: 600px; /* 增加间距从20px到40px */
justify-content: center;
}
.image-item {
border: 1px solid #ebeef5;
border-radius: 4px;
overflow: hidden;
transition: transform 0.3s;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
aspect-ratio: 4/3;
width: calc(45% - 20px); /* 减小宽度比例,增加间距 */
max-width: 480px;
}
/* 单张图片时的样式 */
.single-image .image-item {
width: 70%;
}
.medical-image {
width: 100%;
height: 100%;
}
.no-images {
display: flex;
justify-content: center;
align-items: center;
height: 300px;
color: #909399;
}
.findings-diagnosis-container {
flex-shrink: 0; /* 防止诊断区域被压缩 */
display: flex;
gap: 20px;
margin: 15px 0;
}
.findings-section,
.diagnosis-section {
flex: 1;
padding: 15px;
background: #f8f9fa;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
}
.section-title {
font-size: 16px;
font-weight: 500;
color: #303133;
margin-bottom: 15px;
padding-left: 10px;
border-left: 4px solid #409eff;
}
.findings-textarea,
.diagnosis-textarea {
height: 100px; /* 减小文本框高度 */
width: 100%;
padding: 12px;
border: 1px solid #dcdfe6;
border-radius: 4px;
resize: none;
font-size: 14px;
line-height: 1.5;
background: #fff;
transition: border-color 0.3s;
margin-bottom: 15px;
}
.findings-textarea:focus,
.diagnosis-textarea:focus {
outline: none;
border-color: #409EFF;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
.findings-textarea[readonly],
.diagnosis-textarea[readonly] {
background-color: #f5f7fa;
cursor: not-allowed;
}
.action-footer {
flex-shrink: 0; /* 防止底部操作栏被压缩 */
margin-top: 15px;
background: #fff;
border-top: 1px solid #ebeef5;
}
.footer-content {
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.doctor-info {
display: flex;
gap: 20px;
color: #606266;
font-size: 14px;
}
.action-buttons {
display: flex;
gap: 15px;
}
.action-btn {
padding: 8px 20px;
border: none;
border-radius: 4px;
background: #f0f2f5;
color: #606266;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
font-weight: 500;
}
.action-btn:hover {
background: #e6e8eb;
}
.action-btn.primary {
background: #409EFF;
color: white;
}
.action-btn.primary:hover {
background: #66b1ff;
}
/* 添加图片错误状态的样式 */
.image-error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
background: #f8f9fa;
color: #909399;
}
.error-text {
margin-top: 8px;
font-size: 14px;
}
/* 自定义 el-image 预览样式 */
:deep(.el-image-viewer__wrapper) {
.el-image-viewer__btn {
background-color: rgba(0, 0, 0, 0.3);
&:hover {
background-color: rgba(0, 0, 0, 0.5);
}
}
}
</style>