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

1770 lines
43 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">人员列表</div>
<!-- <label class="radio-label">
<input type="radio" name="status" checked/>
<span>未检</span>
</label>
<label class="radio-label">
<input type="radio" name="status"/>
<span>已检</span>
</label> -->
</div>
<div class="view-options">
<div class="view-buttons">
<button
v-for="period in timePeriods"
:key="period.value"
class="view-btn"
:class="{ active: selectedPeriod === period.value }"
@click="handlePeriodChange(period.value)"
>
{{ period.label }}
</button>
</div>
<!-- 添加日期选择器组件和搜索按钮 -->
<div v-if="showDatePicker" class="date-picker-container">
<div class="date-picker-wrapper">
<el-date-picker
v-model="customDateRange"
type="daterange"
unlink-panels
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
:shortcuts="shortcuts"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
/>
</div>
<el-button
type="primary"
@click="handleDateSearch"
class="search-button"
>搜索</el-button>
</div>
</div>
<div class="search-box">
<input
type="text"
v-model="searchQuery"
placeholder="请输入姓名"
@input="handleSearch"
/>
<i class="el-icon-search search-icon"></i>
</div>
</div>
<div class="list-content">
<div
v-for="patient in patients"
: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>
</div>
</div>
<!-- 将分页组件移到list-content内部底部 -->
<div class="pagination-container">
<el-pagination
v-model:current-page="pageNo"
:page-size="20"
small
:total="total"
layout="total, prev, pager, next, jumper"
@current-change="handleCurrentChange"
>
<template #total>
<span>共 {{ total }} 条</span>
</template>
</el-pagination>
</div>
</div>
</div>
<!-- 右侧检查结果 -->
<div class="report-content" v-if="selectedPatient">
<!-- 基本信息 -->
<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 class="info-item">
<label>性别:</label>
<span>{{ reportData.gender }}</span>
</div>
</div>
<div class="info-row">
<div class="info-item">
<label>年龄:</label>
<span>{{ age }}岁</span>
</div>
<div class="info-item">
<label>电话:</label>
<span>{{ reportData.phoneNum }}</span>
</div>
<div class="info-item">
<label>国籍:</label>
<span>{{ reportData.nationality }}</span>
</div>
<div class="info-item">
<label>民族:</label>
<span>{{ reportData.nation }}</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">
<!-- 表头部分 -->
<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-for="(item, index) in sortedExamItems"
:key="item.id"
class="table-row"
:class="{
'warning-row': item.status === 'warning',
'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)"
class="cell-input"
:disabled="isItemDisabled(item)"
/>
</div>
<div class="table-cell" style="width: 10%">{{ item.unit }}</div>
<div class="table-cell" style="width: 15%">{{ 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">
更多操作
<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>
<!-- 将体检小结移到这里,与表格同级 -->
<div class="summary-section">
<div class="section-title">体检小结</div>
<textarea
v-model="examConclusion"
placeholder="输入多个以分号隔开"
class="summary-textarea"
></textarea>
</div>
</div>
<!-- 底部操作栏 -->
<div class="action-footer">
<div class="left-section">
<span>检查医生:</span>
<span>{{ user?.nickname || '暂无' }}</span>
<span style="margin-left: 100px;">检查日期:{{ formattedMedicalDateTime }}</span>
</div>
<div class="right-section">
<div class="action-buttons">
<button class="action-btn">同步结果</button>
<button class="action-btn">报告预览</button>
<button class="action-btn">复检</button>
<button class="action-btn">弃检</button>
<button class="action-btn primary" @click="handleSaveResults">保存结果</button>
</div>
</div>
</div>
</div>
<!-- 添加未选择患者时的提示 -->
<div class="empty-content" v-else>
<el-empty
description="请选择左侧人员查看详细信息"
/>
</div>
<!-- 添加日期选择对话框 -->
<el-dialog
v-model="dateDialogVisible"
title="自定义日期"
width="580px"
>
<el-date-picker
v-model="customDateRange"
type="daterange"
unlink-panels
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
:shortcuts="shortcuts"
style="width: 100%"
/>
<template #footer>
<span class="dialog-footer">
<el-button @click="dateDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmCustomDate">
确定
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted, computed, onBeforeUnmount } 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 } from '@element-plus/icons-vue'
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([])
const currentTab = ref('urine')
// 检查结果数据
const examItems = ref({})
// 当前显示的检查结果
const currentExamItems = computed(() => 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;
});
});
// 切换标签页
const switchTab = (tabId) => {
currentTab.value = tabId
}
// 其他数据
const examConclusion = ref('')
const examDate = ref('')
// 时间周期选项
const timePeriods = [
{ label: '今日', value: 'today' },
{ label: '本周', value: 'week' },
{ label: '本月', value: 'month' },
{ label: '自定义', value: 'custom' },
{ label: '重置', value: 'reset' }
]
const pageNo = ref(1)
const pageSize = ref(20)
const total = ref(0)
// 添加搜索相关的状态
const searchQuery = ref('')
const originalPatients = ref([]) // 保存原始患者列表
// 添加数据缓存
const patientDataCache = ref(new Map())
// 修改获取患者列表数据的函数
const getPatientList = async () => {
try {
const params = {
pageNo: pageNo.value,
pageSize: pageSize.value
}
const res = await PatientApi.getPatientPage(params)
originalPatients.value = res.list // 保存原始列表
patients.value = res.list
total.value = res.total
} catch (error) {
console.error('获取患者列表出错:', error)
message.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]
// 获取用户信息
try {
const userProfile = await getUserProfile()
user.value = userProfile
} catch (userError) {
console.error('获取用户信息失败:', userError)
}
}
} catch (error) {
console.error('获取患者体检项目数据出错:', error)
message.error('获取患者体检项目数据出错')
}
}
// 修改获取患者详细数据的函数
const loadPatientData = async (patient) => {
try {
// 检查缓存中是否已有数据
const cacheKey = patient.id
if (patientDataCache.value.has(cacheKey)) {
const cachedData = patientDataCache.value.get(cacheKey)
reportData.value = cachedData.reportData
examItems.value = cachedData.examItems
examTabs.value = cachedData.examTabs
currentTab.value = cachedData.currentTab
return
}
const loading = ElLoading.service({
lock: true,
text: '加载中...',
background: 'rgba(255, 255, 255, 0.7)'
})
try {
const [userData, patientData, itemsRes] = await Promise.all([
getUserProfile(),
PatientApi.getPatient(patient.id),
PatientitemsApi.getPatientitemsPage({
medicalSn: patient.medicalSn,
pageNo: 1,
pageSize: 100
})
])
user.value = userData
reportData.value = patientData
if (itemsRes.list && itemsRes.list.length > 0) {
examConclusion.value = itemsRes.list[0].analyse || ''
const itemsBySection = {}
itemsRes.list.forEach(item => {
const sectionType = '一般检查'
if (!itemsBySection[sectionType]) {
itemsBySection[sectionType] = []
}
const processedItem = {
id: item.id,
name: item.itemName,
value: item.itemResult,
unit: item.unit,
reference: `${item.lowValue}-${item.highValue}`,
note: getStatusNote(item),
risk: getRiskLevel(item),
status: getRowStatus(item),
positive: item.positive === '1' ? '阳性' : '阴性',
itemStatus: item.itemStatus
}
itemsBySection[sectionType].push(processedItem)
})
examTabs.value = Object.keys(itemsBySection).map((sectionType, index) => ({
id: sectionType,
name: sectionType,
color: getTabColor(index)
}))
examItems.value = itemsBySection
if (examTabs.value.length > 0) {
currentTab.value = examTabs.value[0].id
}
}
patientDataCache.value.set(cacheKey, {
reportData: { ...reportData.value },
examItems: { ...examItems.value },
examTabs: [...examTabs.value],
currentTab: currentTab.value
})
} finally {
loading.close()
}
} catch (error) {
console.error('获取患者详细信息出错:', error)
message.error('获取患者详细信息失败')
}
}
// 添加辅助函数
const getStatusNote = (item) => {
const value = parseFloat(item.itemResult)
const low = item.lowValue
const high = item.highValue
if (value < low) return '↓'
if (value > high) return '↑'
return '-'
}
const getRiskLevel = (item) => {
const value = parseFloat(item.itemResult)
const low = item.lowValue
const high = item.highValue
if (value < low) return '低于正常值'
if (value > high) return '高于正常值'
return '正常'
}
const getRowStatus = (item) => {
const value = parseFloat(item.itemResult)
const low = item.lowValue
const high = item.highValue
if (value > high) {
return 'danger'
}
else {
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']
const getTabColor = (index) => {
return tabColors[index % tabColors.length]
}
// 修改患者选择处理函数
const handlePatientSelect = async (patient) => {
try {
// 清理当前数据
examConclusion.value = ''
examItems.value = {}
examTabs.value = []
currentTab.value = ''
// 清理该患者的缓存
if (patientDataCache.value.has(patient.id)) {
patientDataCache.value.delete(patient.id)
}
// 设置选中患者
selectedPatient.value = patient
// 重新加载患者数据
await loadPatientData(patient)
// 如果有体检编号,获取体检项目数据
if (patient.medicalSn) {
await getpatientitemData(patient.medicalSn)
}
} catch (error) {
console.error('切换患者失败:', error)
message.error('切换患者失败')
}
}
onMounted(() => {
// 清理页面状态
patients.value = []
selectedPatient.value = null
reportData.value = {
medicalSn: '',
cardId: '',
pName: '',
gender: '',
birthday: '',
nationality: '',
nation: '',
race: '',
phoneNum: '',
status: 0,
reportType: '',
medicalDateTime: '',
chargeType: '',
totalPrice: 0,
headPicUrl: '',
summaryResult: '',
auditor: '',
auditorTime: ''
}
patientitemData.value = {
medicalSn: '',
itemName: '',
itemCode: '',
price: 0,
discountedPrice: 0,
discounted: 0,
sectionID: '',
examDescription: '',
itemResult: '',
unit: '',
highValue: 0,
lowValue: 0,
itemStatus: '',
createTime: '',
positive: '',
inspectdoctor: '',
inspecttime: ''
}
customDateRange.value = []
// 设置默认为今日
const today = new Date()
selectedPeriod.value = 'today'
const todayStart = new Date(today.setHours(0, 0, 0, 0))
const todayEnd = new Date(today.setHours(23, 59, 59, 999))
customDateRange.value = [todayStart, todayEnd]
// 获取今日数据
fetchPatientsByDate()
})
// 当前选中的时间周期
const selectedPeriod = ref('today')
// 日期选择相关
const dateDialogVisible = ref(false)
const customDateRange = ref([])
// 快捷选项
const shortcuts = [
{
text: '最近一周',
value: () => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
return [start, end]
},
},
{
text: '最近一个月',
value: () => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
return [start, end]
},
},
{
text: '最近三个月',
value: () => {
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
return [start, end]
},
},
]
const showDatePicker = ref(false)
// 修改按日期获取患者列表的方法
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)
originalPatients.value = res.list
patients.value = res.list
total.value = res.total
// 如果没有查询到患者,清除右侧详情
if (!res.list || res.list.length === 0) {
selectedPatient.value = null
reportData.value = {
medicalSn: '',
cardId: '',
pName: '',
gender: '',
birthday: '',
nationality: '',
nation: '',
race: '',
phoneNum: '',
status: 0,
reportType: '',
medicalDateTime: '',
chargeType: '',
totalPrice: 0,
headPicUrl: '',
summaryResult: '',
auditor: '',
auditorTime: ''
}
}
} catch (error) {
console.error('按日期获取患者列表失败:', error)
message.error('获取患者列表失败')
}
}
// 修改处理时间周期切换的函数
const handlePeriodChange = (period) => {
selectedPeriod.value = period
// 清除选中状态
selectedPatient.value = null
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
patients.value = [] // 清空患者列表
customDateRange.value = [] // 清空日期范围
break
case 'reset':
selectedPeriod.value = 'today'
const todayStart = new Date(today.setHours(0, 0, 0, 0))
const todayEnd = new Date(today.setHours(23, 59, 59, 999))
customDateRange.value = [todayStart, todayEnd]
showDatePicker.value = false
fetchPatientsByDate()
break
}
}
// 修改日期格式化函数,确保格式正确
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 handleDateSearch = () => {
// 清除选中状态
selectedPatient.value = null
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.birthday || !Array.isArray(reportData.value.birthday)) return ''
const [year, month, day] = reportData.value.birthday
const birthDate = new Date(year, month - 1, day) // 注意月份要减1因为JS中月份从0开始
const today = new Date()
let age = today.getFullYear() - birthDate.getFullYear()
const monthDiff = today.getMonth() - birthDate.getMonth()
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--
}
return age
})
// 添加选中患者的状态
const selectedPatient = ref(null)
// 添加用户信息的响应式引用
const user = ref(null)
// 加载数据
const loadData = async () => {
reportData.value = await PatientApi.getPatient(1)
console.log(reportData.value.birthday)
}
// 添加搜索处理函数
const handleSearch = () => {
if (!searchQuery.value) {
patients.value = originalPatients.value
return
}
const query = searchQuery.value.toLowerCase()
patients.value = originalPatients.value.filter(patient =>
patient.pname.toLowerCase().includes(query) ||
patient.medicalSn.toLowerCase().includes(query)
)
}
// 修改判断项目是否禁用的方法
const isItemDisabled = (item) => {
// 使用 itemStatus 判断2 表示放弃
return item.itemStatus === '2'
}
// 修改操作处理函数
const handleOperation = async ({ type, item }) => {
switch (type) {
case 'positive':
if (isItemDisabled(item)) {
ElMessage.warning('已弃检的项目不能修改')
return
}
item.positive = '阳性'
break
case 'negative':
if (isItemDisabled(item)) {
ElMessage.warning('已弃检的项目不能修改')
return
}
item.positive = '阴性'
break
case 'abandon':
// 如果当前是弃检状态,则恢复为正常状态
if (item.itemStatus === '2') {
item.itemStatus = '0' // 恢复为未检状态
ElMessage.success('已恢复正常状态')
return
}
// 如果是正常状态,则确认是否弃检
ElMessageBox.confirm(
'确定要弃检该项目吗?弃检后将无法编辑该项目。',
'警告',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => {
item.itemStatus = '2' // 设置弃检状态
ElMessage.success('已设置为弃检')
}).catch(() => {
ElMessage.info('已取消弃检')
})
break
}
}
// 修改结果变化处理函数
const handleResultChange = (item) => {
if (isItemDisabled(item)) {
ElMessage.warning('已弃检的项目不能修改')
return
}
item.itemStatus = '1' // 设置为已检状态
const value = parseFloat(item.value)
const [low, high] = item.reference.split('-').map(Number)
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 handleSaveResults = async () => {
try {
// 先获取用户信息
const userProfile = await getUserProfile()
user.value = userProfile
const updatedItems = Object.values(examItems.value).flatMap(sectionItems =>
sectionItems.map(item => ({
id: item.id,
itemResult: item.value || '',
positive: item.positive === '阳性' ? '1' : '0',
analyse: examConclusion.value,
inspectdoctor: user.value?.nickname || '',
itemStatus: item.itemStatus || '1', // 包含弃检状态
inspecttime: new Date().toISOString()
}))
)
console.log('发送的数据:', updatedItems)
await PatientitemsApi.updatePatientitemsBatch(updatedItems)
message.success('保存成功')
} catch (error) {
console.error('保存失败:', error)
message.error(`保存失败: ${error.message}`)
}
}
// 添加分页处理函数
const handleCurrentChange = (val) => {
pageNo.value = val
fetchPatientsByDate()
}
// 添加签名加载错误处理
const handleSignatureError = (e) => {
e.target.style.display = 'none'
console.warn('医生签名图片加载失败')
}
// 添加缓存清理函数
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('心超')
)
})
})
</script>
<style scoped>
.medical-report {
display: flex;
height: 83vh;
min-height: 600px;
overflow: hidden;
}
.patient-list {
width: 280px;
background: #fff;
border-right: 1px solid #e6e6e6;
}
.list-header {
padding: 0;
}
.header-title {
font-size: 14px;
background: #fff;
border-bottom: 1px solid #fff;
color: #333;
}
.filter-options {
padding: 10px 15px;
border-bottom: 1px solid #e6e6e6;
}
.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;
}
.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 {
display: flex;
gap: 20px;
padding: 20px;
background: #F8F9FA;
border-radius: 4px;
}
.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;
}
.tab-item {
position: relative;
padding: 8px 20px;
margin-right: -1px;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
border-radius: 4px;
background: #fff;
color: #606266;
font-size: 14px;
}
.tab-indicator {
width: 12px;
height: 12px;
border-radius: 2px;
}
.tab-item.active {
background: #f5f7fa;
border-bottom-color: transparent;
border: 1px solid #409EFF;
border-radius: 4px;
font-weight: 500;
font-size: 14px;
}
.result-table {
margin: 20px 0;
border: 1px solid #ebeef5;
border-radius: 4px;
height: 520px; /* 固定高度与Charge.vue保持一致 */
display: flex;
flex-direction: column;
background: #fff;
}
/* 表头样式 */
.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: 20px;
padding: 20px;
background: #f8f9fa;
border-radius: 4px;
}
.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: 120px; /* 调整文本框高度 */
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;
}
.action-footer {
position: absolute;
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;
}
.left-section {
display: flex;
align-items: center;
gap: 20px;
}
.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;
gap: 10px;
align-items: center;
}
.medical-sn {
color: #909399;
font-size: 12px;
}
.list-content {
height: calc(100% - 180px);
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;
display: flex;
justify-content: center;
}
/* 自定义分页组件样式 */
:deep(.el-pagination) {
font-size: 12px;
justify-content: flex-start;
width: 100%;
}
: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;
margin: 0 10px;
}
/* 添加图片展示区域样式 */
.result-table {
flex: 1;
overflow: auto;
background: #fff;
}
/* 确保图片展示组件能够正确填充空间 */
:deep(.exam-images) {
height: 100%;
display: flex;
flex-direction: column;
}
/* 美化滚动条样式 */
.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: #ebeef5;
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%;
}
</style>