inspect-front/src/views/Department-entry/Medical-examination-vehicle.vue
2025-03-15 17:29:49 +08:00

2804 lines
72 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="medical-report">
<!-- 左侧人员列表 -->
<div class="patient-list">
<div class="list-header">
<div class="filter-options">
<div class="header-title">
<span>患者列表</span>
<div class="header-buttons">
<el-button
type="primary"
size="small"
@click="handleSync"
:icon="RefreshRight"
:disabled="statusFilter === '1' || (selectedPatient && (selectedPatient.status === '1' || selectedPatient.status === 1))"
>
同步
</el-button>
<el-button
type="success"
size="small"
@click="handleRefresh"
:icon="Refresh"
>
刷新列表
</el-button>
</div>
</div>
</div>
<!-- 添加检查状态筛选 -->
<div class="status-filter">
<el-radio-group v-model="statusFilter" @change="handleStatusFilterChange">
<el-radio label="0">待检查</el-radio>
<el-radio label="1">已检查</el-radio>
<el-radio label="">全部</el-radio>
</el-radio-group>
</div>
<!-- 简化体检编号搜索框 -->
<div class="search-box medical-sn-search">
<input
v-model="searchQuery"
placeholder="请输入姓名或体检编号"
@input="handleLocalSearch"
/>
</div>
</div>
<div class="list-content">
<div
v-for="patient in filteredPatients"
:key="patient.id"
class="patient-item"
@click="handlePatientSelect(patient)"
:class="{ active: selectedPatient?.id === patient.id }"
>
<div class="patient-info">
<span class="name">{{ patient.pname }}</span>
<span class="medical-sn">{{ patient.medicalSn }}</span>
<el-tag
:type="getStatusTagType(patient)"
size="small"
class="status-tag"
>
{{ getStatusText(patient) }}
</el-tag>
</div>
</div>
<!-- 将分页组件移到list-content外部底部 -->
<div class="pagination-container">
<el-pagination
v-model:current-page="pageNo"
:page-size="20"
small
:total="total"
:pager-count="3"
layout="total, prev, pager, next"
@current-change="handleCurrentChange"
>
<template #total>
<span>共 {{ total }} 条</span>
</template>
</el-pagination>
</div>
</div>
</div>
<!-- 右侧内容区域 -->
<div class="right-content">
<!-- 常规检查结果界面 -->
<div class="report-content" v-if="selectedPatient">
<!-- 基本信息 -->
<div class="basic-info">
<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>{{ 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 examTabs"
: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="result-table">
<!-- 特殊检查类型 -->
<All
v-if="isSpecialExam"
:patient="selectedPatient"
:exam-type="currentTab"
/>
<!-- 常规检查项目表格 -->
<template v-else>
<div class="table-header">
<div class="header-row">
<div class="header-cell" style="width: 5%">序号</div>
<div class="header-cell" style="width: 15%">检查项目</div>
<div class="header-cell" style="width: 20%">检查结果</div>
<div class="header-cell" style="width: 10%">单位</div>
<div class="header-cell" style="width: 15%">参考值</div>
<div class="header-cell" style="width: 10%">提示</div>
<div class="header-cell" style="width: 15%">风险</div>
<div class="header-cell" style="width: 10%">阳性/阴性</div>
<div class="header-cell" style="width: 10%">操作</div>
</div>
</div>
<!-- 表格内容 -->
<div class="table-body">
<div v-if="sortedExamItems.length === 0" class="empty-content">
<el-empty description="暂无检查项目" />
</div>
<div v-else>
<div
v-for="(item, index) in sortedExamItems"
:key="item.id"
class="table-row"
:class="{ 'danger-row': item.status === 'danger' }"
:data-item-status="item.itemStatus"
>
<div class="table-cell" style="width: 5%">{{ index + 1 }}</div>
<div class="table-cell" style="width: 15%">{{ item.name }}</div>
<div class="table-cell" style="width: 20%">
<input
type="text"
v-model="item.value"
@change="handleResultChange(item)"
@click="checkEditPermission"
class="cell-input"
:readonly="isReadOnly"
/>
</div>
<div class="table-cell" style="width: 10%">{{ item.unit }}</div>
<div class="table-cell" style="width: 15%">
{{ formatReference(item.reference) }}
</div>
<div class="table-cell" style="width: 10%">{{ item.note }}</div>
<div class="table-cell" style="width: 15%">{{ item.risk }}</div>
<div class="table-cell" style="width: 10%">
<el-tag
v-if="item.positive === '阳性'"
type="danger"
size="small"
effect="light"
class="positive-indicator"
>
阳性
</el-tag>
<span v-else class="negative-text">阴性</span>
</div>
<div class="table-cell" style="width: 10%">
<el-dropdown @command="handleOperation">
<el-button size="small" :class="{ 'view-only': isReadOnly }">
更多操作
<el-icon class="el-icon--right"><arrow-down /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
:command="{ type: 'positive', item: item }"
:class="{ 'is-active': item.positive === '阳性' }"
>
阳性
</el-dropdown-item>
<el-dropdown-item
:command="{ type: 'negative', item: item }"
:class="{ 'is-active': item.positive === '阴性' }"
>
阴性
</el-dropdown-item>
<el-dropdown-item
divided
:command="{ type: 'abandon', item: item }"
:class="{ 'is-danger': item.itemStatus !== '2' }"
style="color: red;"
>
{{ item.itemStatus === '2' ? '恢复正常' : '弃检' }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
</div>
</template>
</div>
<!-- 修改体检小结部分 -->
<div v-if="['ultrasound', 'ecg'].includes(currentTab)" class="findings-diagnosis-container">
<div class="findings-section">
<div class="section-title">检查所见</div>
<textarea
v-model="conclusionData[currentTab].finding"
placeholder="请输入检查所见"
class="findings-textarea"
:readonly="isReadOnly"
></textarea>
</div>
<div class="diagnosis-section">
<div class="section-title">检查结果</div>
<textarea
v-model="conclusionData[currentTab].diagnosis"
placeholder="请输入检查结果"
class="diagnosis-textarea"
:readonly="isReadOnly"
></textarea>
</div>
</div>
<!-- 其他类型使用单栏小结 -->
<div v-else class="summary-section">
<div class="section-title">体检小结</div>
<textarea
v-model="conclusionData[currentTab].summary"
placeholder="输入多个以分号隔开"
class="summary-textarea"
:readonly="isReadOnly"
></textarea>
</div>
</div>
<!-- 底部操作栏 -->
<div class="action-footer">
<div class="left-section">
<div class="doctor-select-container">
<span>检查医生:</span>
<!-- 根据检查状态显示不同内容 -->
<template v-if="isExamCompleted">
<span>{{ inspectDoctor }}</span>
</template>
<template v-else>
<!-- 修改选择控件 -->
<el-select v-model="inspectDoctor" placeholder="请选择检查医生" :disabled="isDisabled" class="doctor-select">
<el-option
v-for="item in getStrDictOptions('doctor_unit')"
:key="item.value"
:label="item.label"
:value="`${item.label}|${item.value}`"
/>
</el-select>
</template>
</div>
<div class="date-container">
<span>检查日期:</span>
<span>{{ inspectTime || formatDate(new Date()) }}</span>
</div>
</div>
<div class="right-section">
<div class="action-buttons">
<button class="action-btn" @click="checkEditPermission" :disabled="isDisabled">弃检</button>
<button
class="action-btn primary"
@click="handleSaveAllResults"
:disabled="isDisabled"
>
保存所有结果
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, computed, onBeforeUnmount, watch } from 'vue'
import { ElDialog, ElLoading, ElMessage, ElMessageBox } from 'element-plus'
import { PatientApi } from '@/api/inspect/inspectpatient'
import { PatientitemsApi } from '@/api/inspect/inspectpatientitems'
import { getUserProfile } from '@/api/system/user/profile'
import ExamImages from './Exam_images.vue'
import { ArrowDown, Refresh, RefreshRight } from '@element-plus/icons-vue'
import All from './All.vue' // 导入All组件用于超声、心电图等
import { PacsDataApi } from '@/api/inspect/inspectpacsdata' // 导入PacsDataApi
import { getStrDictOptions } from '@/utils/dict'//导入字典
const dialogTitle = ref('体检报告')
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const status = ref('')
const patients = ref([])
// 患者基本信息数据
const reportData = ref({
medicalSn: '', // 体检编号
cardId: '', // 证件ID
pName: '', // 患者姓名
gender: '', // 性别
birthday: '', // 出生日期
nationality: '', // 国籍
nation: '', // 民族
race: '', // 人种
phoneNum: '', // 电话
status: 0, // 状态
reportType: '', // 报告类型
medicalDateTime: '', // 体检日期
chargeType: '', // 收费方式
totalPrice: 0, // 实际收款金额
headPicUrl: '', // 头像图片路径
summaryResult: '', // 汇总分析结果
auditor: '', // 审核人
auditorTime: '', // 审核时间
})
// 患者体检项目数据
const patientitemData = ref({
medicalSn: '', // 体检编号
itemName: '', // 检查项目名称
itemCode: '', // 项目代号
price: 0, // 项目单价
discountedPrice: 0, // 折扣价
discounted: 0, // 折扣 百分比
sectionID: '', // 科室ID
examDescription: '', // 检查所见
itemResult: '', // 检查结论
unit: '', // 项目单位
highValue: 0, // 取值上限
lowValue: 0, // 取值下限
itemStatus: '', // 0未检 1已检 2放弃 3挂起择日检(待查)
createTime: '', // 创建时间
positive: '', // 是否阳性 1阳性
inspectdoctor: '', // 检查医生
inspecttime: '', // 检查时间
})
const doctorSignature = ref('')
// 检查项目标签数据
const examTabs = ref([
{ id: 'general', name: '一般检查', color: '#006699' },
{ id: 'ultrasound', name: '超声', color: '#0099CC' },
{ id: 'ecg', name: '心电图', color: '#00CCFF' },
{ id: 'blood', name: '血常规', color: '#66CCFF' },
{ id: 'urine', name: '尿常规', color: '#99CCFF' },
{ id: 'biochemical', name: '生化', color: '#0099CC' }
])
const currentTab = ref('general')
// 检查结果数据
const examItems = ref({})
// 当前显示的检查结果
const currentExamItems = computed(() => {
return examItems.value[currentTab.value] || [];
})
// 添加排序后的计算属性
const sortedExamItems = computed(() => {
if (!currentExamItems.value) return [];
// 对当前显示的检查项目进行排序
return [...currentExamItems.value].sort((a, b) => {
// 首先按照 itemStatus 排序(未检的排在前面)
if (a.itemStatus !== b.itemStatus) {
// 优先级:未检(0) > 已检(1) > 待查(3) > 弃检(2)
const statusOrder = { '0': 0, '1': 1, '3': 2, '2': 3 };
return statusOrder[a.itemStatus] - statusOrder[b.itemStatus];
}
// 如果状态相同,则按照 id 排序
return a.id - b.id;
});
});
// 修改 switchTab 函数,添加防止重复设置的逻辑
const switchTab = async (tabId) => {
// 设置新的当前标签
currentTab.value = tabId
}
// 修改 conclusionData 的数据结构
const conclusionData = ref({
general: { summary: '' },
ultrasound: { finding: '', diagnosis: '' },
ecg: { finding: '', diagnosis: '' },
blood: { summary: '' },
urine: { summary: '' },
biochemical: { summary: '' } // 添加生化检查的小结
})
// 其他数据
const examConclusion = ref('')
const examDate = ref('')
const pageNo = ref(1)
const pageSize = ref(100)
const total = ref(0)
// 添加搜索相关的状态
const searchQuery = ref('')
const originalPatients = ref([]) // 保存原始患者列表
// 添加数据缓存
const patientDataCache = ref(new Map())
// 添加体检编号搜索相关
const medicalSnQuery = ref('')
// 添加本地搜索处理函数
const handleLocalSearch = async () => {
if (!searchQuery.value) {
// 如果搜索框为空,恢复原始列表
await getPatientList()
return
}
try {
const loading = ElLoading.service({
lock: true,
text: '搜索中...',
background: 'rgba(255, 255, 255, 0.7)'
})
const params = {
pageNo: pageNo.value,
pageSize: pageSize.value,
// 添加搜索条件,根据姓名或体检编号搜索
pname: searchQuery.value,
medicalSn: searchQuery.value
}
const res = await PatientApi.getPatientPage(params)
if (res.list && res.list.length > 0) {
patients.value = res.list
total.value = res.total
} else {
patients.value = []
total.value = 0
}
loading.close()
} catch (error) {
console.error('搜索患者失败:', error)
ElMessage.error('搜索患者失败')
}
}
// 修改获取患者列表数据的函数
const getPatientList = async () => {
try {
const loading = ElLoading.service({
lock: true,
text: '加载中...',
background: 'rgba(255, 255, 255, 0.7)'
})
const params = {
pageNo: pageNo.value,
pageSize: pageSize.value
}
const res = await PatientApi.getPatientPage(params)
if (res.list && res.list.length > 0) {
patients.value = res.list
originalPatients.value = res.list
total.value = res.total
} else {
patients.value = []
originalPatients.value = []
total.value = 0
}
loading.close()
} catch (error) {
console.error('获取患者列表出错:', error)
ElMessage.error('获取患者列表失败')
}
}
// 修改获取患者体检项目数据的方法
const getpatientitemData = async (medicalSn) => {
try {
const params = {
medicalSn: medicalSn,
pageNo: 1,
pageSize: 100
}
const itemsRes = await PatientitemsApi.getPatientitemsPage(params)
if (itemsRes.list && itemsRes.list.length > 0) {
patientitemData.value = itemsRes.list[0]
// 如果有项目且都是已检查状态,更新患者状态
if (currentDeptItems.length > 0 && currentDeptItems.every(item => item.itemStatus === '1')) {
// 更新患者列表中的状态
const patientIndex = patients.value.findIndex(p => p.medicalSn === medicalSn)
if (patientIndex !== -1) {
patients.value[patientIndex] = {
...patients.value[patientIndex],
examStatus: '1' // 添加检查状态属性
}
}
}
}
} catch (error) {
console.error('获取患者体检项目数据出错:', error)
message.error('获取患者体检项目数据出错')
}
}
// 修改 loadPatientData 函数,确保正确加载已检查患者的医生和日期
const loadPatientData = async (patient) => {
try {
const loading = ElLoading.service({
lock: true,
text: '加载中...',
background: 'rgba(255, 255, 255, 0.7)'
})
try {
// 获取用户信息和部门ID
const userProfile = await getUserProfile()
user.value = userProfile
// 获取患者基本信息
const patientData = await PatientApi.getPatient(patient.id)
reportData.value = patientData
// 重置检查医生和日期
inspectDoctor.value = ''
inspectTime.value = ''
// 检查患者状态如果已检查则设置检查完成状态为true
isExamCompleted.value = patient.status === '1' || patient.status === 1
// 获取检查项目
const itemsRes = await PatientitemsApi.getPatientitemsPage({
medicalSn: patient.medicalSn,
pageNo: 1,
pageSize: 100
})
// 处理检查项目数据
if (itemsRes.list && itemsRes.list.length > 0) {
// 按照类别分组
const groupedItems = {}
// 根据不同类型的检查项目,加载对应的小结
const conclusions = {
general: { summary: '' },
ultrasound: { finding: '', diagnosis: '' },
ecg: { finding: '', diagnosis: '' },
blood: { summary: '' },
urine: { summary: '' },
biochemical: { summary: '' }
}
// 查找已检查的项目,获取检查医生和日期
const checkedItems = itemsRes.list.filter(item => item.itemStatus === '1')
if (checkedItems.length > 0) {
// 使用第一个已检查项目的检查医生和日期
const firstCheckedItem = checkedItems[0]
// 设置检查医生
if (firstCheckedItem.inspectdoctor) {
inspectDoctor.value = firstCheckedItem.inspectdoctor
}
// 格式化检查时间
if (firstCheckedItem.inspecttime) {
const inspectDate = new Date(Number(firstCheckedItem.inspecttime))
inspectTime.value = formatDate(inspectDate)
}
}
itemsRes.list.forEach(item => {
// 处理项目分类
const category = getCategoryByItemName(item.itemName || '')
if (!groupedItems[category]) {
groupedItems[category] = []
}
// 处理项目数据
const processedItem = processItemData(item)
groupedItems[category].push(processedItem)
// 处理小结数据
processConclusion(item, category, conclusions)
})
examItems.value = groupedItems
conclusionData.value = conclusions
}
} catch (error) {
console.error('加载患者数据失败:', error)
ElMessage.error('加载患者数据失败')
} finally {
loading.close()
}
} catch (error) {
console.error('加载患者数据失败:', error)
ElMessage.error('加载患者数据失败')
}
}
// 修改 handlePatientSelect 函数,确保正确设置检查完成状态
const handlePatientSelect = async (patient) => {
try {
// 清理当前数据
examConclusion.value = ''
examItems.value = {}
// 设置选中患者
selectedPatient.value = patient
// 重置检查医生和日期
inspectDoctor.value = ''
inspectTime.value = ''
// 根据患者状态设置检查完成状态
isExamCompleted.value = patient.status === '1' || patient.status === 1
await loadPatientData(patient)
currentTab.value = 'general'
} catch (error) {
console.error('[handlePatientSelect] 切换患者失败:', error)
message.error('切换患者失败')
isImageDepartment.value = false
}
}
// 辅助函数:根据项目名称获取分类
const getCategoryByItemName = (itemName) => {
itemName = itemName.toLowerCase()
if (itemName.includes('超声') || itemName.includes('彩超') || itemName.includes('b超')) {
return 'ultrasound'
} else if (itemName.includes('心电图') || itemName.includes('ecg') || itemName.includes('ekg')) {
return 'ecg'
} else if (itemName.includes('血常规') || itemName.includes('血细胞') || itemName.includes('血红蛋白')) {
return 'blood'
} else if (itemName.includes('尿常规') || itemName.includes('尿液分析')) {
return 'urine'
} else if (itemName.includes('生化') || itemName.includes('肝功能') || itemName.includes('肾功能') ||
itemName.includes('血脂') || itemName.includes('血糖') || itemName.includes('电解质')) {
return 'biochemical'
} else {
return 'general'
}
}
// 辅助函数:处理项目数据
const processItemData = (item) => {
return {
id: item.id,
name: item.itemName,
code: item.itemCode,
value: item.itemResult,
originalValue: item.itemResult,
unit: item.unit,
reference: item.lowValue !== null && item.highValue !== null ?
`${item.lowValue}-${item.highValue}` : 'null-null',
status: getRowStatus(item),
risk: getRiskLevel(item),
note: getStatusNote(item),
itemStatus: item.itemStatus || '0',
sectionID: item.sectionID
}
}
// 辅助函数:处理小结数据
const processConclusion = (item, category, conclusions) => {
if (category === 'ultrasound' || category === 'ecg') {
// 处理超声和心电图的所见和结果
if (item.examDescription) {
conclusions[category].finding = item.examDescription
}
if (item.itemResult) {
conclusions[category].diagnosis = item.itemResult
}
// 如果有analyse字段尝试从中解析检查所见和结果
if (item.analyse) {
const parts = item.analyse.split('\n')
parts.forEach(part => {
if (part.startsWith('检查所见:')) {
conclusions[category].finding = part.replace('检查所见:', '')
} else if (part.startsWith('检查结果:')) {
conclusions[category].diagnosis = part.replace('检查结果:', '')
}
})
}
} else {
// 处理其他类型的小结 - 只使用analyse字段
if (item.analyse) {
conclusions[category].summary = item.analyse
}
}
}
// 修改检查是否为影像科的函数
const checkIsImageDepartment = (deptId) => {
// 强制返回true用于测试
// return true;
// 如果deptId是对象尝试获取其id属性
const id = typeof deptId === 'object' ? deptId.id || deptId.deptId : deptId
// 影像科的ID列表 - 根据实际情况调整
const imageDeptIds = ['7', '8', '9', '10', '11', '12', '13', 'CT室', 'DR室', '超声科', '放射科', '影像科', 'CT', 'MRI', '超声', '放射']
// 检查ID是否在列表中
const result = imageDeptIds.includes(String(id))
return result
}
// 修改获取状态提示的函数
const getStatusNote = (item) => {
if (!item.reference || item.reference === 'null-null') {
return ''
}
const value = parseFloat(item.itemResult)
const [low, high] = item.reference.split('-').map(val => val === 'null' ? null : Number(val))
if (low === null || high === null) {
return ''
}
if (value < low) return '↓'
if (value > high) return '↑'
return '-'
}
// 修改获取风险等级的函数
const getRiskLevel = (item) => {
if (!item.reference || item.reference === 'null-null') {
return ''
}
const value = parseFloat(item.itemResult)
const [low, high] = item.reference.split('-').map(val => val === 'null' ? null : Number(val))
if (low === null || high === null) {
return ''
}
if (value < low) return '低于正常值'
if (value > high) return '高于正常值'
return '正常'
}
// 修改获取行状态的函数
const getRowStatus = (item) => {
if (!item.reference || item.reference === 'null-null') {
return ''
}
const value = parseFloat(item.itemResult)
const [low, high] = item.reference.split('-').map(val => val === 'null' ? null : Number(val))
if (low === null || high === null) {
return ''
}
if (value > high) {
return 'danger'
}
return ''
}
// 修改科室名称映射函数
const getSectionName = (sectionId) => {
// 获取该科室下的所有检查项目
const items = examItems.value[sectionId] || []
// 检查是否包含特定检查项目
const hasBloodTest = items.some(item => item.name.includes('血常规'))
const hasUrineTest = items.some(item => item.name.includes('尿常规'))
const hasImageTest = items.some(item =>
item.name.includes('CT') ||
item.name.includes('MRI') ||
item.name.includes('超声') ||
item.name.includes('X线') ||
item.name.includes('影像')
)
// 根据检查项目类型返回对应科室名称
if (hasBloodTest) return '血液检查'
if (hasUrineTest) return '尿液检查'
if (hasImageTest) return '影像检查'
return '一般检查'
}
// 标签颜色映射
const tabColors = ['#003366', '#006699', '#0099CC', '#00CCFF', '#0099CC']
const getTabColor = (index) => {
return tabColors[index % tabColors.length]
}
onMounted(async () => {
// 清理页面状态
patients.value = []
selectedPatient.value = null
// 设置合理的分页大小
pageSize.value = 20 // 或其他合适的数值
pageNo.value = 1
// 直接调用获取列表方法,不附加任何过滤条件
await getPatientList()
})
// 修改处理时间周期切换的函数
const handlePeriodChange = (period) => {
selectedPeriod.value = period
// 清除选中状态
selectedPatient.value = null
if (period === 'reset') {
// 重置所有筛选条件
selectedPeriod.value = '' // 清除选中的时间周期
customDateRange.value = [] // 清除日期范围
showDatePicker.value = false
// 重新获取所有数据,不带日期过滤
getPatientList()
return
}
const today = new Date()
let startDate, endDate
switch (period) {
case 'today':
startDate = new Date(today.setHours(0, 0, 0, 0))
endDate = new Date(today.setHours(23, 59, 59, 999))
customDateRange.value = [startDate, endDate]
showDatePicker.value = false
fetchPatientsByDate()
break
case 'week':
startDate = new Date(today)
startDate.setDate(today.getDate() - today.getDay())
endDate = new Date(today)
endDate.setDate(startDate.getDate() + 6)
customDateRange.value = [startDate, endDate]
showDatePicker.value = false
fetchPatientsByDate()
break
case 'month':
startDate = new Date(today.getFullYear(), today.getMonth(), 1)
endDate = new Date(today.getFullYear(), today.getMonth() + 1, 0)
customDateRange.value = [startDate, endDate]
showDatePicker.value = false
fetchPatientsByDate()
break
case 'custom':
showDatePicker.value = !showDatePicker.value
break
}
}
// 修改按日期获取患者列表方法 - 不再自动同步
const fetchPatientsByDate = async () => {
try {
if (!customDateRange.value || customDateRange.value.length !== 2) return
const [startDate, endDate] = customDateRange.value
const params = {
pageNo: pageNo.value,
pageSize: pageSize.value,
medicalDateTime: [
`${formatDate(startDate)} 00:00:00`,
`${formatDate(endDate)} 23:59:59`
]
}
const res = await PatientApi.getPatientPage(params)
patients.value = res.list
originalPatients.value = res.list
total.value = res.total
// 如果没有查询到患者,清除右侧详情
if (!res.list || res.list.length === 0) {
selectedPatient.value = null
}
} catch (error) {
console.error('按日期获取患者列表失败:', error)
message.error('获取患者列表失败')
}
}
// 修改日期搜索处理方法
const handleDateSearch = () => {
if (customDateRange.value && customDateRange.value.length === 2) {
fetchPatientsByDate()
} else {
message.warning('请选择日期范围')
}
}
// 添加格式化后的检查日期计算属性
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 age = computed(() => {
if (!reportData.value.cardId) return ''
// 从身份证号提取出生日期
const idCard = reportData.value.cardId
if (idCard.length !== 18) return '' // 确保是18位身份证
// 提取出生年月日 (格式为 YYYYMMDD)
const birthYear = idCard.substring(6, 10)
const birthMonth = idCard.substring(10, 12)
const birthDay = idCard.substring(12, 14)
const birthDate = new Date(`${birthYear}-${birthMonth}-${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 gender = computed(() => {
// 直接使用reportData中的gender字段
return reportData.value.gender || ''
})
// 添加选中患者的状态
const selectedPatient = ref(null)
// 添加用户信息的响应式引用
const user = ref(null)
// 添加计算属性检查是否所有项目都已完成
const isAllItemsChecked = computed(() => {
if (!sortedExamItems.value || sortedExamItems.value.length === 0) return false
return sortedExamItems.value.every(item => item.itemStatus === '1')
})
// 修改检查编辑权限的方法
const checkEditPermission = () => {
// 检查当前患者的检查状态
if (selectedPatient.value && (selectedPatient.value.status === '1' || selectedPatient.value.status === 1)) {
ElMessage.warning('该患者检查已完成,不可进行编辑')
return false
}
// 检查当前科室的检查项目是否全部完成
const currentDeptItems = Object.values(examItems.value)
.flat()
.filter(item => item.sectionID === user.value?.deptId)
if (currentDeptItems.length > 0 && currentDeptItems.every(item => item.itemStatus === '1')) {
ElMessage.warning('所有检查项目已完成,不可进行编辑')
return false
}
return true
}
// 修改同步结果处理函数
const handleSync = async () => {
try {
const loading = ElLoading.service({
lock: true,
text: '同步中...',
background: 'rgba(255, 255, 255, 0.7)'
})
try {
if (!selectedPatient.value?.medicalSn) {
throw new Error('未选择患者或体检编号为空')
}
// 保存当前选中的患者,以便在同步后恢复选中状态
const currentSelectedPatient = { ...selectedPatient.value }
const medicalSn = currentSelectedPatient.medicalSn
// 定义需要同步的类型
const types = ['XCG', 'NCG', 'SHQX', 'ECG', 'YBJC']
// 首先检查每种类型是否已存在数据
const existingDataPromises = types.map(type =>
PacsDataApi.getPacsDataPage({
code: medicalSn,
type: type,
pageNo: 1,
pageSize: 1
}).catch(error => {
console.warn(`检查${type}数据是否存在时出错:`, error)
return { list: [] }
})
)
// 检查超声数据是否存在
const usExistingData = await PacsDataApi.getPacsDataPage({
code: medicalSn,
type: 'US',
pageNo: 1,
pageSize: 1
}).catch(error => {
console.warn('检查超声数据是否存在时出错:', error)
return { list: [] }
})
// 检查心电图数据是否存在
const ecgExistingData = await PacsDataApi.getPacsDataPage({
code: medicalSn,
type: 'ECG',
pageNo: 1,
pageSize: 1
}).catch(error => {
console.warn('检查心电图数据是否存在时出错:', error)
return { list: [] }
})
// 获取所有类型的现有数据结果
const existingDataResults = await Promise.all(existingDataPromises)
// 确定哪些类型需要同步
const typesToSync = []
existingDataResults.forEach((result, index) => {
const type = types[index]
// 如果没有数据或数据为空,则需要同步
const needsSync = !result.list || result.list.length === 0 ||
(result.list[0] && (!result.list[0].data || result.list[0].data === ''))
if (needsSync) {
typesToSync.push(type)
} else {
console.log(`${type}数据已存在且不为空,跳过同步`)
}
})
// 检查超声数据是否需要同步
const needsSyncUS = !usExistingData.list || usExistingData.list.length === 0 ||
(usExistingData.list[0] && (!usExistingData.list[0].data || usExistingData.list[0].data === ''))
// 检查心电图数据是否需要同步
const needsSyncECG = !ecgExistingData.list || ecgExistingData.list.length === 0 ||
(ecgExistingData.list[0] && (!ecgExistingData.list[0].data || ecgExistingData.list[0].data === ''))
// 准备同步请求
const syncPromises = []
// 添加需要同步的检验报告请求
typesToSync.forEach(type => {
syncPromises.push(
PatientApi.getReportTj(medicalSn, type)
.catch(error => {
console.warn(`获取${type}报告失败:`, error)
return null
})
)
})
// 如果需要,添加超声报告请求
if (needsSyncUS) {
syncPromises.push(
PatientApi.getUSTj(medicalSn)
.catch(error => {
console.warn('获取超声报告失败:', error)
return null
})
)
}
// 如果需要,添加心电图报告请求
if (needsSyncECG) {
syncPromises.push(
PatientApi.GetApiEcgInfo(medicalSn)
.catch(error => {
console.warn('获取心电图报告失败:', error)
return null
})
)
}
// 如果需要,添加一般检查报告请求
if (typesToSync.includes('YBJC')) {
syncPromises.push(
PatientApi.GetApiYbjcInfo(medicalSn, currentSelectedPatient.cardId)
.catch(error => {
console.warn('获取一般检查报告失败:', error)
return null
})
)
}
// 如果没有需要同步的项目,直接返回
if (syncPromises.length === 0) {
loading.close()
ElMessage.info('所有报告数据已存在,无需同步')
return
}
const results = await Promise.all(syncPromises)
// 检查是否所有请求都失败了
const allFailed = results.every(result => result === null)
if (allFailed) {
throw new Error('所有报告同步失败')
}
// 处理同步结果
let successCount = 0
results.forEach(result => {
if (result) {
successCount++
}
})
// 等待一小段时间确保后端数据同步完成
await new Promise(resolve => setTimeout(resolve, 1000))
// 清除缓存
patientDataCache.value.clear()
// 重新加载当前患者的数据,但不清空左侧列表
await loadPatientData(currentSelectedPatient)
loading.close()
ElMessage.success(`同步成功,共同步了${successCount}个报告`)
} catch (error) {
loading.close()
console.error('同步数据失败:', error)
ElMessage.error(error.message || '同步数据失败,请稍后重试')
}
} catch (error) {
console.error('同步失败:', error)
ElMessage.error(error.message || '同步失败,请稍后重试')
}
}
const handleRefresh = async (e) => {
// 阻止默认行为,防止触发浏览器刷新
if (e) e.preventDefault()
try {
const loading = ElLoading.service({
lock: true,
text: '刷新中...',
background: 'rgba(255, 255, 255, 0.7)'
})
// 清除选中状态和右侧内容
selectedPatient.value = null // 清除选中患者
reportData.value = {} // 清空报告数据
examItems.value = {} // 清空检查项目
conclusionData.value = { // 重置小结数据
general: { summary: '' },
ultrasound: { finding: '', diagnosis: '' },
ecg: { finding: '', diagnosis: '' },
blood: { summary: '' },
urine: { summary: '' },
biochemical: { summary: '' }
}
// 清空搜索栏
searchQuery.value = ''
// 重置状态筛选
statusFilter.value = '0'
// 刷新患者列表
await getPatientList()
//重置标签
currentTab.value = 'general'
loading.close()
ElMessage.success('刷新成功')
} catch (error) {
console.error('刷新失败:', error)
ElMessage.error('刷新失败,请稍后重试')
}
}
// 添加一个新方法,用于检查患者是否已有检查项目
const checkPatientHasItems = async (medicalSn) => {
try {
const existingItems = await PatientitemsApi.getPatientitemsPage({
medicalSn: medicalSn,
pageNo: 1,
pageSize: 1
})
return existingItems.list && existingItems.list.length > 0
} catch (error) {
console.error('检查患者检查项目失败:', error)
return false
}
}
// 修改结果变化处理函数
const handleResultChange = (item) => {
if (!checkEditPermission()) {
// 如果没有编辑权限,恢复原值
item.value = item.originalValue || ''
return
}
// 如果有输入值,设置为已检状态
if (item.value) {
item.itemStatus = '1'
} else {
item.itemStatus = '0'
}
item.originalValue = item.value
if (item.reference && item.reference !== 'null-null') {
const value = parseFloat(item.value)
const [low, high] = item.reference.split('-').map(val => val === 'null' ? null : Number(val))
if (low !== null && high !== null) {
item.note = getStatusNote({ itemResult: value, lowValue: low, highValue: high })
item.risk = getRiskLevel({ itemResult: value, lowValue: low, highValue: high })
item.status = getRowStatus({ itemResult: value, lowValue: low, highValue: high })
}
}
}
// 修改操作处理函数
const handleOperation = ({ type, item }) => {
if (!checkEditPermission()) return
switch (type) {
case 'positive':
item.positive = '阳性'
item.itemStatus = '1'
break
case 'negative':
item.positive = '阴性'
item.itemStatus = '1'
break
case 'abandon':
if (item.itemStatus === '2') {
item.itemStatus = '0'
ElMessage.success('已恢复正常状态')
} else {
ElMessageBox.confirm(
'确定要弃检该项目吗?弃检后将无法编辑该项目。',
'警告',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => {
item.itemStatus = '2'
ElMessage.success('已设置为弃检')
}).catch(() => {
ElMessage.info('已取消弃检')
})
}
break
}
}
// 修改禁用状态的计算属性
const isDisabled = computed(() => {
// 如果患者状态为已检查,则禁用
if (selectedPatient.value?.status === '1' || selectedPatient.value?.status === 1) return true
// 检查当前科室的检查项目是否全部完成
const currentDeptItems = Object.values(examItems.value)
.flat()
.filter(item => item.sectionID === user.value?.deptId)
if (currentDeptItems.length > 0 && currentDeptItems.every(item => item.itemStatus === '1')) {
return true
}
return false
})
// 修改只读状态的计算属性
const isReadOnly = computed(() => {
return isDisabled.value
})
// 修改分页处理函数
const handleCurrentChange = (val) => {
pageNo.value = val
getPatientList() // 直接调用获取列表函数
}
// 修改签名加载错误处理
const handleSignatureError = (e) => {
e.target.style.display = 'none'
}
// 添加缓存清理函数
const clearPatientCache = () => {
patientDataCache.value.clear()
}
// 在组件卸载时清理缓存
onBeforeUnmount(() => {
clearPatientCache()
})
// 添加判断是否为影像类检查的计算属性
const isImageExam = computed(() => {
const items = currentExamItems.value || []
return items.some(item => {
const itemName = (item.name || '').toLowerCase()
return (
itemName.includes('ct') ||
itemName.includes('mri') ||
itemName.includes('超声') ||
itemName.includes('x线') ||
itemName.includes('心电图') ||
itemName.includes('影像') ||
itemName.includes('胸片') ||
itemName.includes('内镜') ||
itemName.includes('放射') ||
itemName.includes('b超') ||
itemName.includes('彩超') ||
itemName.includes('心超')
)
})
})
// 添加检查项目状态的计算属性
const patientExamStatus = computed(() => {
if (!selectedPatient.value?.medicalSn) return '0'
const currentItems = Object.values(examItems.value)
.flat()
.filter(item => item.sectionID === user.value?.deptId)
if (!currentItems.length) return '0'
const allChecked = currentItems.every(item => item.itemStatus === '1')
return allChecked ? '1' : '0'
})
// 修改获取状态文本的方法
const getStatusText = (patient) => {
// 直接使用数据库中的 status 字段判断
if (patient.status === '1' || patient.status === 1) {
return '已检查'
}
return '待检查'
}
// 修改状态标签类型
const getStatusTagType = (patient) => {
return getStatusText(patient) === '已检查' ? 'success' : 'info'
}
// 添加参考值格式化函数
const formatReference = (reference) => {
if (!reference || reference === 'null-null') {
return '-'
}
const [low, high] = reference.split('-')
if (!low || !high || low === 'null' || high === 'null') {
return '-'
}
return `${low}-${high}`
}
// 添加isImageDepartment的响应式引用
const isImageDepartment = ref(false)
// 添加判断是否为血常规检查的计算属性
const isBloodTest = computed(() => {
if (!selectedPatient.value) return false
const currentItems = Object.values(examItems.value)
.flat()
.filter(item => {
const itemName = (item.name || '').toLowerCase()
return itemName.includes('血常规') ||
itemName.includes('血细胞') ||
itemName.includes('血红蛋白') ||
itemName.includes('白细胞') ||
itemName.includes('红细胞') ||
itemName.includes('血小板')
})
return currentItems.length > 0
})
// 添加日期格式化函数
const formatDate = (date) => {
if (!date) return ''
const d = new Date(date)
const year = d.getFullYear()
const month = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
// 修改验证方法,添加对一般检查项目的验证
const validateAllResults = () => {
const errors = []
// 检查超声
if (conclusionData.value.ultrasound) {
if (!conclusionData.value.ultrasound.finding?.trim()) {
errors.push('超声检查所见不能为空')
}
if (!conclusionData.value.ultrasound.diagnosis?.trim()) {
errors.push('超声检查结果不能为空')
}
}
// 检查心电图
if (conclusionData.value.ecg) {
if (!conclusionData.value.ecg.finding?.trim()) {
errors.push('心电图检查所见不能为空')
}
if (!conclusionData.value.ecg.diagnosis?.trim()) {
errors.push('心电图检查结果不能为空')
}
}
// 检查血常规
if (conclusionData.value.blood) {
if (!conclusionData.value.blood.summary?.trim()) {
errors.push('血常规体检小结不能为空')
}
}
// 检查尿常规
if (conclusionData.value.urine) {
if (!conclusionData.value.urine.summary?.trim()) {
errors.push('尿常规体检小结不能为空')
}
}
// 检查生化
if (conclusionData.value.biochemical) {
if (!conclusionData.value.biochemical.summary?.trim()) {
errors.push('生化检查体检小结不能为空')
}
}
// 添加对一般检查项目的验证
if (currentTab.value === 'general') {
const generalItems = sortedExamItems.value || []
// 检查每个一般检查项目是否有值
generalItems.forEach((item, index) => {
// 只验证非弃检的项目
if (item.itemStatus !== '2' && !item.value?.trim()) {
errors.push(`${index + 1}项 "${item.name}" 的检查结果不能为空`)
}
})
}
if (errors.length > 0) {
ElMessage({
message: errors.join('\n'),
type: 'warning',
duration: 5000,
showClose: true,
grouping: true
})
return false
}
return true
}
// 修改统一保存方法,更新检查医生和检查日期,同时更新患者状态和体检日期
const handleSaveAllResults = async () => {
if (!checkEditPermission()) return
// 验证所有标签页的内容
if (!validateAllResults()) return
// 检查是否选择了医生
if (!inspectDoctor.value) {
ElMessage.warning('请选择检查医生')
return
}
try {
const userProfile = await getUserProfile()
user.value = userProfile
const currentTimestamp = Date.now()
const currentDate = formatDate(new Date())
// 获取选择的医生信息
let doctorName = inspectDoctor.value
if (doctorName && doctorName.includes('|')) {
doctorName = doctorName.split('|')[0] // 只取医生姓名部分
}
// 收集所有标签页的所有检查项目
let allUpdatedItems = []
// 遍历所有检查项目
Object.keys(examItems.value).forEach(tabKey => {
const items = examItems.value[tabKey] || []
items.forEach(item => {
const itemName = (item.name || '').toLowerCase()
// 基础字段
const baseFields = {
id: item.id,
medicalSn: selectedPatient.value?.medicalSn,
itemStatus: '1', // 设置为已检查状态
inspectdoctor: doctorName || user.value?.nickname || '',
inspecttime: currentTimestamp, // 使用当前时间戳
sectionID: user.value?.deptId,
itemResult: item.value || '' // 添加检查结果值
}
// 根据项目类型处理不同的字段
if (itemName.includes('超声') || itemName.includes('彩超') || itemName.includes('b超')) {
const finding = conclusionData.value.ultrasound?.finding?.trim() || ''
const diagnosis = conclusionData.value.ultrasound?.diagnosis?.trim() || ''
// 拼接成指定格式的字符串保存到analyse字段
const analyseContent = `检查所见:${finding}\n检查结果${diagnosis}`
allUpdatedItems.push({
...baseFields,
examDescription: finding,
itemResult: diagnosis,
analyse: analyseContent
})
} else if (itemName.includes('心电图') || itemName.includes('ecg') || itemName.includes('ekg')) {
const finding = conclusionData.value.ecg?.finding?.trim() || ''
const diagnosis = conclusionData.value.ecg?.diagnosis?.trim() || ''
// 拼接成指定格式的字符串保存到analyse字段
const analyseContent = `检查所见:${finding}\n检查结果${diagnosis}`
allUpdatedItems.push({
...baseFields,
examDescription: finding,
itemResult: diagnosis,
analyse: analyseContent
})
} else if (itemName.includes('血常规') || itemName.includes('血细胞') || itemName.includes('血红蛋白')) {
allUpdatedItems.push({
...baseFields,
analyse: conclusionData.value.blood?.summary?.trim() || '', // 只将小结保存到analyse字段
itemResult: item.value || '' // 保留原始检查结果值
})
} else if (itemName.includes('尿常规') || itemName.includes('尿液分析')) {
allUpdatedItems.push({
...baseFields,
analyse: conclusionData.value.urine?.summary?.trim() || '', // 只将小结保存到analyse字段
itemResult: item.value || '' // 保留原始检查结果值
})
} else if (itemName.includes('生化') || itemName.includes('肝功能') || itemName.includes('肾功能') ||
itemName.includes('血脂') || itemName.includes('血糖') || itemName.includes('电解质')) {
allUpdatedItems.push({
...baseFields,
analyse: conclusionData.value.biochemical?.summary?.trim() || '', // 只将小结保存到analyse字段
itemResult: item.value || '' // 保留原始检查结果值
})
} else {
// 一般检查项目
allUpdatedItems.push({
...baseFields,
analyse: conclusionData.value.general?.summary?.trim() || '', // 只将小结保存到analyse字段
})
}
})
})
if (allUpdatedItems.length === 0) {
ElMessage.warning('没有需要保存的检查项目')
return
}
// 批量更新所有检查项目
await PatientitemsApi.updatePatientitemsBatch(allUpdatedItems)
// 更新检查医生和检查日期
inspectTime.value = currentDate
// 更新患者状态为已检查 - 调用更新患者状态的API
if (selectedPatient.value) {
// 更新数据库中的status字段为1同时更新体检日期为当前时间戳
await PatientApi.updatePatient({
id: selectedPatient.value.id,
status: 1, // 设置为已检查状态
medicalDateTime: currentTimestamp // 更新体检日期为当前时间
})
// 更新本地状态
selectedPatient.value.status = 1
selectedPatient.value.medicalDateTime = currentTimestamp
// 更新列表中的患者状态
const patientIndex = patients.value.findIndex(p => p.id === selectedPatient.value.id)
if (patientIndex !== -1) {
patients.value[patientIndex].status = 1
patients.value[patientIndex].medicalDateTime = currentTimestamp
}
// 更新过滤后的列表中的患者状态
const filteredIndex = filteredPatients.value.findIndex(p => p.id === selectedPatient.value.id)
if (filteredIndex !== -1) {
filteredPatients.value[filteredIndex].status = 1
filteredPatients.value[filteredIndex].medicalDateTime = currentTimestamp
}
}
ElMessage.success('所有检查结果保存成功')
// 重新加载数据以更新界面
await refreshExamData()
} catch (error) {
ElMessage.error(`保存失败: ${error.message || '请检查数据是否完整'}`)
}
}
// 修改 refreshExamData 函数,确保正确更新检查状态和医生信息
const refreshExamData = async () => {
try {
// 重新获取患者信息以更新状态
if (selectedPatient.value) {
const updatedPatient = await PatientApi.getPatient(selectedPatient.value.id)
if (updatedPatient) {
// 更新选中患者的状态
selectedPatient.value = updatedPatient
// 更新列表中的患者状态
const patientIndex = patients.value.findIndex(p => p.id === selectedPatient.value.id)
if (patientIndex !== -1) {
patients.value[patientIndex] = updatedPatient
}
// 更新过滤后的列表中的患者状态
const filteredIndex = filteredPatients.value.findIndex(p => p.id === selectedPatient.value.id)
if (filteredIndex !== -1) {
filteredPatients.value[filteredIndex] = updatedPatient
}
}
}
// 获取检查项目数据
const params = {
medicalSn: selectedPatient.value?.medicalSn,
pageNo: 1,
pageSize: 100
}
const itemsRes = await PatientitemsApi.getPatientitemsPage(params)
if (itemsRes.list && itemsRes.list.length > 0) {
// 查找已检查的项目,获取检查医生
const checkedItems = itemsRes.list.filter(item =>
item.itemStatus === '1' &&
item.sectionID === user.value.deptId
)
if (checkedItems.length > 0) {
// 使用第一个已检查项目的检查医生
const firstCheckedItem = checkedItems[0]
// 设置检查医生
if (firstCheckedItem.inspectdoctor) {
inspectDoctor.value = firstCheckedItem.inspectdoctor
// 更新检查状态为已完成,这样就不会显示选择控件
isExamCompleted.value = true
}
// 格式化检查时间
if (firstCheckedItem.inspecttime) {
const inspectDate = new Date(Number(firstCheckedItem.inspecttime))
inspectTime.value = formatDate(inspectDate)
}
}
}
} catch (error) {
console.error('刷新数据失败:', error)
}
}
// 添加检查医生和检查日期的响应式引用
const inspectDoctor = ref('')
const inspectTime = ref('')
// 不再需要 inspectDate 变量,直接使用当前日期
// 添加特殊检查类型的计算属性
const isSpecialExam = computed(() => {
return ['ultrasound', 'ecg', 'blood', 'urine', 'biochemical'].includes(currentTab.value)
})
// 添加状态筛选的响应式引用
const statusFilter = ref('0') // 默认选择待检查
// 修改过滤后的患者列表计算属性
const filteredPatients = computed(() => {
let result = patients.value
// 先按状态筛选
if (statusFilter.value !== '') {
result = result.filter(patient =>
String(patient.status) === statusFilter.value
)
}
// 再按搜索词筛选
if (searchQuery.value) {
const query = searchQuery.value.toLowerCase()
result = result.filter(patient =>
(patient.pname && patient.pname.toLowerCase().includes(query)) ||
(patient.medicalSn && patient.medicalSn.toLowerCase().includes(query))
)
}
return result
})
// 添加状态筛选变化处理函数
const handleStatusFilterChange = (value) => {
statusFilter.value = value
// 重置选中的患者
selectedPatient.value = null
}
// 添加检查是否已完成的响应式引用
const isExamCompleted = ref(false)
</script>
<style scoped>
.medical-report {
display: flex;
height: calc(100vh - 120px); /* 使用视口高度减去头部和其他元素的高度 */
min-height: 600px;
overflow: hidden;
width: 100%;
}
.patient-list {
width: 280px;
min-width: 250px; /* 设置最小宽度 */
background: #fff;
border-right: 1px solid #e6e6e6;
display: flex;
flex-direction: column; /* 确保子元素垂直排列 */
height: 100%;
}
.list-header {
padding: 0;
}
.header-title {
font-size: 14px;
background: #fff;
border-bottom: 1px solid #fff;
color: #333;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 8px 0;
}
.filter-options {
width: 100%;
padding: 0 10px;
}
.radio-label {
margin-right: 20px;
cursor: pointer;
font-size: 14px;
color: #606266;
}
.radio-label input[type="radio"] {
margin-right: 4px;
vertical-align: middle;
}
.view-options {
padding: 10px 15px;
display: flex;
flex-direction: column;
border-bottom: 1px solid #e6e6e6;
}
.view-buttons {
display: flex;
gap: 8px;
}
.view-btn {
padding: 5px 12px;
border: none;
background: none;
cursor: pointer;
font-size: 13px;
color: #606266;
border-radius: 3px;
}
.view-btn:hover {
color: #409EFF;
background: #ecf5ff;
}
.view-btn.active {
color: #409EFF;
background: #ecf5ff;
}
.search-box {
padding: 10px 15px;
position: relative;
border-bottom: 1px solid #e6e6e6;
}
.search-box input {
width: 100%;
padding: 8px 30px 8px 10px;
border: 1px solid #dcdfe6;
border-radius: 4px;
font-size: 13px;
}
.search-box input:focus {
border-color: #409EFF;
outline: none;
}
.search-icon {
position: absolute;
right: 25px;
top: 50%;
transform: translateY(-50%);
color: #c0c4cc;
}
.patient-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 15px;
border-bottom: 1px solid #e6e6e6;
}
.report-content {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
background: #fff;
overflow: auto; /* 添加滚动条 */
height: 100%;
}
.main-content {
flex: 1;
overflow-y: auto;
padding: 20px;
padding-bottom: 60px;
}
.progress-nav {
display: flex;
margin-bottom: 20px;
position: relative;
}
.nav-item {
position: relative;
padding: 8px 40px;
background: #E4E7ED;
color: #606266;
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
margin-right: 4px;
}
.nav-item:after {
content: '';
position: absolute;
right: -20px;
top: 0;
border-left: 20px solid #E4E7ED;
border-top: 18px solid transparent;
border-bottom: 18px solid transparent;
z-index: 1;
}
.nav-item.active {
background: #409EFF;
color: white;
}
.nav-item.active:after {
border-left-color: #409EFF;
}
.basic-info {
padding: 10px;
background: #F8F9FA;
border-radius: 4px;
flex-shrink: 0; /* 防止基本信息区域被压缩 */
}
.photo-box {
width: 120px;
height: 160px;
background: #fff;
border: 1px dashed #dcdfe6;
display: flex;
align-items: center;
justify-content: center;
}
.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;
}
.info-item span {
color: #333;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.full-width {
width: 100%;
}
.full-width .info-item {
flex: none;
width: 100%;
}
.full-width .info-item span {
flex: 1;
}
.exam-tabs {
display: flex;
gap: 10px; /* 添加间隔 */
margin-bottom: 15px;
padding: 10px 0;
}
.tab-item {
position: relative;
padding: 8px 16px;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
border-radius: 4px;
background: #f5f7fa; /* 添加底色 */
color: #606266;
font-size: 14px;
border: 1px solid #dcdfe6; /* 添加边框 */
transition: all 0.3s;
}
.tab-indicator {
width: 10px;
height: 10px;
border-radius: 2px;
}
.tab-item:hover {
background: #ecf5ff;
border-color: #c6e2ff;
}
.tab-item.active {
background: #ecf5ff;
border-color: #409EFF;
color: #409EFF;
font-weight: 500;
}
.result-table {
flex: 1;
overflow: auto; /* 使表格可滚动 */
border: 1px solid #ebeef5;
border-radius: 4px;
display: flex;
flex-direction: column;
background: #fff;
min-height: 200px; /* 设置最小高度 */
}
/* 表头样式 */
.table-header {
background: #f5f7fa;
border-bottom: 1px solid #ebeef5;
}
.header-row {
display: flex;
width: 100%;
}
.header-cell {
padding: 12px 8px;
font-weight: 500;
color: #606266;
text-align: left;
border-right: 1px solid #ebeef5;
}
/* 表格主体样式 */
.table-body {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
}
.table-row {
display: flex;
border-bottom: 1px solid #ebeef5;
}
.table-cell {
padding: 12px 8px;
border-right: 1px solid #ebeef5;
display: flex;
align-items: center;
}
/* 输入框和下拉框样式 */
.cell-input,
.cell-select {
width: 100%;
border: none;
background: transparent;
outline: none;
padding: 0;
}
/* 警告和危险行样式 */
.danger-row {
background: #fc00262d;
}
/* 美化滚动条 */
.table-body::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.table-body::-webkit-scrollbar-thumb {
background: #c0c4cc;
border-radius: 3px;
}
.table-body::-webkit-scrollbar-track {
background: #f5f7fa;
}
/* 修改体检小结样式 */
.summary-section {
margin-top: 15px;
padding: 15px;
background: #f8f9fa;
border-radius: 4px;
flex-shrink: 0; /* 防止被压缩 */
}
.section-title {
font-size: 16px;
font-weight: 500;
color: #303133;
margin-bottom: 15px;
padding-left: 10px;
border-left: 4px solid #409eff;
}
.summary-textarea {
width: 100%;
height: 100px; /* 减小高度 */
padding: 12px;
border: 1px solid #dcdfe6;
border-radius: 4px;
resize: none;
font-size: 14px;
line-height: 1.5;
background: #fff;
}
.summary-textarea:focus {
outline: none;
border-color: #409EFF;
}
.summary-textarea[readonly] {
background-color: #f5f7fa;
cursor: not-allowed;
}
.action-footer {
position: sticky; /* 改为粘性定位 */
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
background: #fff;
border-top: 1px solid #ebeef5;
z-index: 10;
flex-shrink: 0; /* 防止被压缩 */
}
.left-section {
display: flex;
align-items: center;
gap: 20px;
flex-wrap: nowrap; /* 防止换行 */
}
.signature {
height: 30px;
margin: 0 10px;
object-fit: contain;
}
.right-section {
display: flex;
gap: 10px;
}
.action-btn {
padding: 6px 16px;
border: none;
border-radius: 4px;
background: #40B6FF;
color: white;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.action-btn:hover {
opacity: 0.9;
}
.action-btn.primary {
background: #40B6FF;
}
.action-buttons {
display: flex;
gap: 15px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
margin-top: 20px;
gap: 10px;
}
.date-picker-container {
margin-top: 10px;
background: #fff;
padding: 10px 0;
width: 100%;
}
.date-picker-wrapper {
display: flex;
gap: 10px;
align-items: center;
margin-bottom: 10px;
}
.search-button {
width: 100%;
margin-top: 8px;
height: 32px;
}
.patient-info {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.name {
min-width: 64px; /* 预留四个汉字的宽度 */
max-width: 64px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
}
.medical-sn {
color: #909399;
font-size: 12px;
margin-right: 4px;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.status-tag {
margin-left: auto; /* 将状态标签推到右侧 */
font-size: 12px;
flex-shrink: 0; /* 防止标签被压缩 */
}
.list-content {
flex: 1; /* 让列表内容区域占据剩余空间 */
overflow-y: auto;
position: relative;
display: flex;
flex-direction: column;
}
/* 添加空状态样式 */
.empty-content {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
background: #fff;
border-radius: 4px;
}
/* 添加选中状态样式 */
.patient-item.active {
background-color: #ecf5ff;
}
.patient-item:hover {
background-color: #f5f7fa;
cursor: pointer;
}
.result-table td input {
width: 100%;
padding: 4px 8px;
border: none;
background: transparent;
box-sizing: border-box;
}
.result-table td {
padding: 8px;
border: 1px solid #ebeef5;
text-align: left;
min-width: 100px; /* 设置最小宽度 */
}
/* 为明细结果列设置固定宽度 */
.result-table th:nth-child(3),
.result-table td:nth-child(3) {
width: 200px; /* 可以根据需要调整宽度 */
}
/* 修改分页容器样式 */
.pagination-container {
padding: 8px;
background: #fff;
border-top: 1px solid #e6e6e6;
margin-top: auto; /* 确保分页始终在底部 */
position: sticky; /* 添加粘性定位 */
bottom: 0; /* 固定在底部 */
z-index: 1; /* 确保在内容之上 */
width: 100%; /* 确保宽度填满容器 */
}
/* 自定义分页组件样式 */
:deep(.el-pagination) {
font-size: 12px;
justify-content: flex-start;
width: 100%;
white-space: nowrap; /* 防止内容换行 */
}
:deep(.el-pagination .el-pagination__total) {
margin-right: 8px;
}
:deep(.el-pagination .el-pagination__jump) {
margin-left: 8px;
}
:deep(.el-pagination .el-input__inner) {
height: 24px;
line-height: 24px;
}
:deep(.el-pagination .el-pagination__editor.el-input) {
width: 50px;
}
.no-signature {
color: #909399;
font-size: 14px;
}
/* 添加图片展示区域样式 */
.result-table {
flex: 1;
overflow: auto;
background: #fff;
}
/* 确保图片展示组件能够正确填充空间 */
:deep(.exam-images) {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
overflow: auto;
}
/* 美化滚动条样式 */
.result-table::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.result-table::-webkit-scrollbar-thumb {
background: #c0c4cc;
border-radius: 3px;
}
.result-table::-webkit-scrollbar-track {
background: #f5f7fa;
}
/* 添加文字按钮样式 */
.text-button {
cursor: pointer;
font-size: 15px;
transition: all 0.3s;
}
.text-button.danger {
color: #F56C6C;
}
.text-button.danger:hover {
color: #f78989;
}
.positive-indicator {
margin-left: 8px;
font-size: 12px;
}
.table-cell {
display: flex;
align-items: center;
gap: 4px;
}
/* 添加下拉菜单相关样式 */
.el-dropdown-link {
cursor: pointer;
color: #409EFF;
display: flex;
align-items: center;
font-size: 14px;
}
.el-dropdown-link:hover {
color: #66b1ff;
}
:deep(.el-dropdown-menu__item.is-active) {
color: #409EFF;
background-color: #ecf5ff;
}
:deep(.el-dropdown-menu__item--divided) {
margin-top: 6px;
border-top: 1px solid #ebeef5;
}
:deep(.el-dropdown-menu__item--divided:before) {
height: 1px;
margin: 0 -20px;
background-color: #ebeef5;
}
.negative-text {
color: #333;
font-size: 14px;
}
/* 修改下拉按钮样式以匹配 baseUI */
:deep(.el-dropdown) {
margin-right: 8px;
}
:deep(.el-button) {
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
}
/* 添加禁用状态的样式 */
.cell-input:disabled {
background-color: #f5f7fa;
cursor: not-allowed;
color: #606266;
}
/* 修改弃检行的样式判断 */
.table-row[data-item-status="2"] {
background-color: #ebeef5;
color: #606266;
}
/* 修改弃检状态下的下拉菜单样式判断 */
.table-row[data-item-status="2"] .el-dropdown {
opacity: 0.8;
}
/* 固定列样式 */
.fixed-cell {
position: sticky;
background: #fff;
z-index: 1;
}
.table-row .fixed-cell:nth-child(1) {
left: 0;
}
.table-row .fixed-cell:nth-child(2) {
left: 5%;
}
/* 更新搜索框样式 */
.medical-sn-search {
margin-top: 10px;
}
.medical-sn-search input {
width: 100%;
padding: 6px 12px;
border: 1px solid #dcdfe6;
border-radius: 4px;
font-size: 13px;
}
.medical-sn-search input:focus {
border-color: #409EFF;
outline: none;
}
.status-tag {
margin-left: 8px;
font-size: 12px;
}
.patient-info {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.medical-sn {
color: #909399;
font-size: 12px;
margin-right: 4px;
}
.action-btn:disabled {
background-color: #a0cfff;
cursor: not-allowed;
opacity: 0.7;
}
:deep(.el-dropdown.is-disabled) {
opacity: 0.7;
cursor: not-allowed;
}
:deep(.el-button.is-disabled) {
background-color: #a0cfff;
cursor: not-allowed;
opacity: 0.7;
}
/* 修改只读输入框样式 */
.cell-input[readonly] {
background-color: transparent;
cursor: not-allowed;
}
/* 只读状态的文本框样式 */
.summary-textarea[readonly],
.findings-textarea[readonly],
.diagnosis-textarea[readonly] {
background-color: #f5f7fa;
cursor: not-allowed;
}
.right-content {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.tabs-container {
flex-shrink: 0;
padding: 10px 15px;
border-bottom: 1px solid #ebeef5;
}
.content-container {
flex: 1;
overflow: auto;
position: relative;
}
.footer-container {
flex-shrink: 0;
padding: 10px 15px;
border-top: 1px solid #ebeef5;
background: #f8f9fa;
}
.no-patient-selected {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.refresh-btn {
padding: 6px;
margin-left: 8px;
}
.refresh-btn:hover {
opacity: 0.8;
}
/* 添加双栏布局相关样式 */
.findings-diagnosis-container {
display: flex;
gap: 20px;
margin-top: 15px;
flex-shrink: 0; /* 防止被压缩 */
}
.findings-section, .diagnosis-section {
flex: 1;
display: flex;
flex-direction: column;
}
.findings-textarea, .diagnosis-textarea {
width: 100%;
height: 120px;
padding: 12px;
border: 1px solid #dcdfe6;
border-radius: 4px;
resize: none;
font-size: 14px;
line-height: 1.5;
background: #fff;
}
.findings-textarea:focus,
.diagnosis-textarea:focus {
outline: none;
border-color: #409EFF;
}
.findings-textarea[readonly],
.diagnosis-textarea[readonly] {
background-color: #f5f7fa;
cursor: not-allowed;
}
/* 修改按钮样式 */
.header-buttons {
display: flex;
flex-direction: row;
gap: 8px;
align-items: center;
justify-content: flex-end;
}
/* 确保按钮容器有足够的宽度 */
.filter-options {
width: 100%;
}
/* 确保按钮不会换行 */
.header-buttons .el-button {
flex-shrink: 0;
margin-left: 8px;
white-space: nowrap;
}
/* 添加状态筛选样式 */
.status-filter {
padding: 10px 15px;
border-bottom: 1px solid #e6e6e6;
}
:deep(.el-radio-group) {
display: flex;
justify-content: space-around;
width: 100%;
}
:deep(.el-radio) {
margin-right: 0;
}
:deep(.el-radio__label) {
font-size: 13px;
}
/* 添加媒体查询,适应不同屏幕尺寸 */
@media screen and (max-width: 1366px) {
.findings-diagnosis-container {
flex-direction: column;
gap: 10px;
}
.findings-textarea, .diagnosis-textarea, .summary-textarea {
height: 80px;
}
.exam-tabs {
flex-wrap: wrap;
}
}
@media screen and (max-width: 1024px) {
.patient-list {
width: 240px;
}
.info-row {
flex-direction: column;
gap: 8px;
}
.info-item {
width: 100%;
}
}
/* 添加这些新样式 */
.doctor-select-container {
display: flex;
align-items: center;
white-space: nowrap;
}
.doctor-select {
margin-left: 8px;
width: 180px;
}
.date-container {
display: flex;
align-items: center;
white-space: nowrap;
}
/* 修改左侧区域样式 */
.left-section {
display: flex;
align-items: center;
gap: 20px;
flex-wrap: nowrap; /* 防止换行 */
}
/* 确保 el-select 不会被挤压 */
:deep(.el-select) {
width: auto;
min-width: 180px;
}
</style>