新增科室录入界面区分以及接口

This commit is contained in:
Euni4U 2025-03-03 17:16:44 +08:00
parent 9c6305a0d2
commit 81762466b2
7 changed files with 1489 additions and 254 deletions

View File

@ -0,0 +1,49 @@
import request from '@/config/axios'
// lis抓取数据 VO
export interface LisDataVO {
id: number // 主键
code: string // 体检编号
type: string // 类型
equipmentManufacturerModel: string // 设备厂商型号
item: string // 项目
personName: string // 检测者姓名
time: Date // 检测时间
data: string // 数据
remark: string // 备注
delFlag: number // 是否删除(0-未删除1-已删除)
createId: string // 创建人
}
// lis抓取数据 API
export const LisDataApi = {
// 查询lis抓取数据分页
getLisDataPage: async (params: any) => {
return await request.get({ url: `/inspect/lis-data/page`, params })
},
// 查询lis抓取数据详情
getLisData: async (id: number) => {
return await request.get({ url: `/inspect/lis-data/get?id=` + id })
},
// 新增lis抓取数据
createLisData: async (data: LisDataVO) => {
return await request.post({ url: `/inspect/lis-data/create`, data })
},
// 修改lis抓取数据
updateLisData: async (data: LisDataVO) => {
return await request.put({ url: `/inspect/lis-data/update`, data })
},
// 删除lis抓取数据
deleteLisData: async (id: number) => {
return await request.delete({ url: `/inspect/lis-data/delete?id=` + id })
},
// 导出lis抓取数据 Excel
exportLisData: async (params) => {
return await request.download({ url: `/inspect/lis-data/export-excel`, params })
},
}

View File

@ -0,0 +1,50 @@
import request from '@/config/axios'
// pacs抓取数据 VO
export interface PacsDataVO {
id: number // 主键
code: string // 体检编号
data: string // 数据
time: Date // 监测时间
equipmentManufacturerModel: string // 设备厂商型号
item: string // 项目
type: string // 类型
remark: string // 备注
delFlag: number // 是否删除(0-未删除1-已删除)
createId: string // 创建人
createTime: Date // 创建时间
personName: string // 检测者姓名
}
// pacs抓取数据 API
export const PacsDataApi = {
// 查询pacs抓取数据分页
getPacsDataPage: async (params: any) => {
return await request.get({ url: `/inspect/pacs-data/page`, params })
},
// 查询pacs抓取数据详情
getPacsData: async (id: number) => {
return await request.get({ url: `/inspect/pacs-data/get?id=` + id })
},
// 新增pacs抓取数据
createPacsData: async (data: PacsDataVO) => {
return await request.post({ url: `/inspect/pacs-data/create`, data })
},
// 修改pacs抓取数据
updatePacsData: async (data: PacsDataVO) => {
return await request.put({ url: `/inspect/pacs-data/update`, data })
},
// 删除pacs抓取数据
deletePacsData: async (id: number) => {
return await request.delete({ url: `/inspect/pacs-data/delete?id=` + id })
},
// 导出pacs抓取数据 Excel
exportPacsData: async (params) => {
return await request.download({ url: `/inspect/pacs-data/export-excel`, params })
},
}

View File

@ -447,22 +447,15 @@ const fetchPatientsByDate = async () => {
`${formatDate(endDate)} 23:59:59`
],
//
pname: searchQuery.value || undefined
pname: searchQuery.value || undefined,
//
chargeStatus: chargeStatus.value === 'paid' ? '1' : '0'
}
const res = await PatientApi.getPatientPage(params)
// chargeType
if (chargeStatus.value === 'unpaid') {
// chargeTypenullundefined
patients.value = res.list.filter(patient => !patient.chargeType || patient.chargeType === '')
} else {
// chargeType
patients.value = res.list.filter(patient => patient.chargeType && patient.chargeType !== '')
}
//
total.value = patients.value.length
patients.value = res.list || []
total.value = res.total || 0
//
if (patients.value.length === 0) {
@ -576,8 +569,10 @@ onMounted(() => {
//
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))
const todayStart = new Date(today)
todayStart.setHours(0, 0, 0, 0)
const todayEnd = new Date(today)
todayEnd.setHours(23, 59, 59, 999)
customDateRange.value = [todayStart, todayEnd]
//

View File

@ -0,0 +1,710 @@
<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 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="image-results">
<!-- 影像图片展示区 -->
<div class="image-gallery">
<div class="gallery-title">影像图片</div>
<div class="image-container">
<div v-if="imageUrls.length > 0" class="image-grid">
<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 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">报告预览</button>
<button class="action-btn" @click="checkEditPermission">弃检</button>
<button
v-if="!isReadOnly"
class="action-btn primary"
@click="handleSaveResults"
>
保存结果
</button>
</div>
</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' },
{ id: 'mri', name: 'MRI检查', color: '#67C23A' },
{ id: 'xray', name: 'X光检查', color: '#E6A23C' },
{ id: 'ultrasound', name: '超声检查', color: '#F56C6C' },
{ id: 'ecg', name: '心电图检查', color: '#909399' }
])
//
const currentTab = ref('ct')
//
const switchTab = async (tabId) => {
console.log('切换到检查类型:', tabId)
currentTab.value = tabId
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
// 使dataURL
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(() => {
return props.patient?.status === 1
})
//
const checkEditPermission = () => {
if (isReadOnly.value) {
ElMessage.warning('检查结果已保存,不可进行编辑')
return false
}
return true
}
//
const handleSaveResults = async () => {
try {
const userProfile = await getUserProfile()
user.value = userProfile
//
const currentTime = new Date().getTime()
//
const updatedItems = [{
id: props.patient.id,
examDescription: imageFinding.value,
itemResult: imageDiagnosis.value,
analyse: examConclusion.value,
inspectdoctor: user.value?.nickname || '',
itemStatus: '1', //
inspecttime: currentTime
}]
await PatientitemsApi.updatePatientitemsBatch(updatedItems)
//
if (props.patient) {
const patientData = {
...props.patient,
status: 1,
medicalDateTime: currentTime
}
await PatientApi.updatePatient(patientData)
}
ElMessage.success('保存成功')
} catch (error) {
console.error('保存失败:', 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
//
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) {
await loadPatientData(props.patient)
// tab
imageUrls.value = await getImageUrlsByType(currentTab.value)
}
})
</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: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
gap: 20px;
}
.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;
}
.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>

View File

@ -53,191 +53,203 @@
</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>
<template v-if="selectedPatient">
<!-- 根据科室类型动态显示不同组件 -->
<CT v-if="isImageDepartment" :patient="selectedPatient" />
<blood-test
v-else-if="isBloodTest"
:patient="selectedPatient"
:user="user"
/>
<!-- 常规检查结果界面 -->
<div class="report-content" v-else>
<!-- 基本信息 -->
<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-row">
<div class="info-item">
<label>性别</label>
<span>{{ reportData.gender }}</span>
<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-item">
<label>年龄</label>
<span>{{ age }}</span>
</div>
<div class="info-item">
<label>电话</label>
<span>{{ reportData.phoneNum }}</span>
<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>
<!-- 检查结果部分 -->
<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 class="exam-results">
<!-- 检查项目标签 -->
<div class="exam-tabs">
<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"
v-for="tab in examTabs"
:key="tab.id"
:class="['tab-item', { active: currentTab === tab.id }]"
@click="switchTab(tab.id)"
>
<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 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 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 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)"
@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>
<!-- 将体检小结移到这里与表格同级 -->
<div class="summary-section">
<div class="section-title">体检小结</div>
<textarea
v-model="examConclusion"
placeholder="输入多个以分号隔开"
class="summary-textarea"
:readonly="isReadOnly"
></textarea>
</div>
</div>
<!-- 将体检小结移到这里与表格同级 -->
<div class="summary-section">
<div class="section-title">体检小结</div>
<textarea
v-model="examConclusion"
placeholder="输入多个以分号隔开"
class="summary-textarea"
:readonly="isReadOnly"
></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" @click="checkEditPermission">同步结果</button>
<button class="action-btn">报告预览</button>
<button class="action-btn" @click="checkEditPermission">弃检</button>
<button
v-if="!isReadOnly"
class="action-btn primary"
@click="handleSaveResults"
>
保存结果
</button>
<!-- 底部操作栏 -->
<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" @click="checkEditPermission">同步结果</button>
<button class="action-btn">报告预览</button>
<button class="action-btn" @click="checkEditPermission">弃检</button>
<button
v-if="!isReadOnly"
class="action-btn primary"
@click="handleSaveResults"
>
保存结果
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<!-- 添加未选择患者时的提示 -->
<div class="empty-content" v-else>
@ -281,7 +293,9 @@ 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 CT from './CT.vue' // CT
import { ArrowDown } from '@element-plus/icons-vue'
import BloodTest from './blood.vue' //
const dialogTitle = ref('体检报告')
const { t } = useI18n() //
@ -456,7 +470,6 @@ const getpatientitemData = async (medicalSn) => {
try {
const userProfile = await getUserProfile()
user.value = userProfile
console.log('当前登录用户信息:', userProfile)
} catch (userError) {
console.error('获取用户信息失败:', userError)
}
@ -500,48 +513,53 @@ const loadPatientData = async (patient) => {
])
user.value = userProfile
const userDeptId = userProfile // ID
//
isImageDepartment.value = checkIsImageDepartment(userProfile.deptId)
reportData.value = patientData
if (itemsRes.list && itemsRes.list.length > 0) {
examConclusion.value = itemsRes.list[0].analyse || ''
const itemsBySection = {}
//
const filteredItems = itemsRes.list.filter(item => item.sectionID == userProfile.deptId)
console.log('当前登录用户信息:',filteredItems )
filteredItems.forEach(item => {
const sectionType = '一般检查'
if (!itemsBySection[sectionType]) {
itemsBySection[sectionType] = []
}
//
if (!isImageDepartment.value && !isBloodTest.value) {
if (itemsRes.list && itemsRes.list.length > 0) {
examConclusion.value = itemsRes.list[0].analyse || ''
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
}
const itemsBySection = {}
//
const filteredItems = itemsRes.list.filter(item => item.sectionID == userProfile.deptId)
filteredItems.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)
})
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
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
}
}
}
@ -562,6 +580,23 @@ const loadPatientData = async (patient) => {
}
}
//
const checkIsImageDepartment = (deptId) => {
// true
// return true;
// deptIdid
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') {
@ -673,10 +708,12 @@ const handlePatientSelect = async (patient) => {
} catch (error) {
console.error('切换患者失败:', error)
message.error('切换患者失败')
//
isImageDepartment.value = false
}
}
onMounted(() => {
onMounted(async () => {
//
patients.value = []
selectedPatient.value = null
@ -720,6 +757,16 @@ onMounted(() => {
inspecttime: ''
}
customDateRange.value = []
//
try {
const userProfile = await getUserProfile()
user.value = userProfile
isImageDepartment.value = checkIsImageDepartment(userProfile.deptId)
} catch (error) {
console.error('获取用户信息失败:', error)
isImageDepartment.value = false
}
})
//
@ -928,7 +975,6 @@ const user = ref(null)
//
const loadData = async () => {
reportData.value = await PatientApi.getPatient(1)
console.log(reportData.value.birthday)
}
//
@ -1190,6 +1236,30 @@ const formatReference = (reference) => {
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
})
</script>
<style scoped>
@ -1866,7 +1936,6 @@ const formatReference = (reference) => {
background-color: #f5f7fa;
cursor: not-allowed;
color: #606266;
border: none;
}
/* 修改弃检行的样式判断 */

View File

@ -0,0 +1,284 @@
<template>
<div class="blood-test-container">
<!-- 检查结果表格 -->
<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>
</div>
<!-- 表格内容部分 -->
<div class="table-body">
<div
v-for="(item, index) in bloodTestItems"
:key="item.id"
class="table-row"
:class="{
'warning-row': item.status === 'warning',
'danger-row': item.status === 'danger'
}"
>
<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%">{{ item.value }}</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"
>
阳性
</el-tag>
<span v-else>阴性</span>
</div>
</div>
</div>
</div>
<!-- 底部操作栏 -->
<div class="action-footer">
<div class="left-section">
<span>检查医生{{ user?.nickname || '暂无' }}</span>
<span style="margin-left: 100px;">检查日期{{ formattedMedicalDateTime }}</span>
</div>
<div class="right-section">
<div class="action-buttons">
<button class="action-btn" @click="handleSyncResults">同步结果</button>
<button class="action-btn">报告预览</button>
<button
class="action-btn primary"
@click="handleSaveResults"
>
保存结果
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { PatientitemsApi } from '@/api/inspect/inspectpatientitems'
export default {
name: 'BloodTest',
props: {
patient: {
type: Object,
required: true
},
user: {
type: Object,
required: true
}
},
setup(props) {
const bloodTestItems = ref([])
const formattedMedicalDateTime = computed(() => {
return new Date().toLocaleDateString('zh-CN')
})
//
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}`
}
//
const handleSyncResults = async () => {
try {
const params = {
medicalSn: props.patient.medicalSn,
pageNo: 1,
pageSize: 100
}
const res = await PatientitemsApi.getPatientitemsPage(params)
if (res.list && res.list.length > 0) {
bloodTestItems.value = res.list.map(item => ({
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' ? '阳性' : '阴性'
}))
ElMessage.success('同步结果成功')
}
} catch (error) {
console.error('同步结果失败:', error)
ElMessage.error('同步结果失败')
}
}
//
const handleSaveResults = async () => {
try {
const updatedItems = bloodTestItems.value.map(item => ({
id: item.id,
itemResult: item.value,
positive: item.positive === '阳性' ? '1' : '0',
inspectdoctor: props.user?.nickname || '',
inspecttime: new Date().getTime()
}))
await PatientitemsApi.updatePatientitemsBatch(updatedItems)
ElMessage.success('保存成功')
} catch (error) {
console.error('保存失败:', error)
ElMessage.error('保存失败')
}
}
//
const getStatusNote = (item) => {
if (!item.itemResult || !item.lowValue || !item.highValue) return ''
const value = parseFloat(item.itemResult)
const low = parseFloat(item.lowValue)
const high = parseFloat(item.highValue)
if (value < low) return '↓'
if (value > high) return '↑'
return '-'
}
const getRiskLevel = (item) => {
if (!item.itemResult || !item.lowValue || !item.highValue) return ''
const value = parseFloat(item.itemResult)
const low = parseFloat(item.lowValue)
const high = parseFloat(item.highValue)
if (value < low) return '低于正常值'
if (value > high) return '高于正常值'
return '正常'
}
const getRowStatus = (item) => {
if (!item.itemResult || !item.lowValue || !item.highValue) return ''
const value = parseFloat(item.itemResult)
const high = parseFloat(item.highValue)
if (value > high) return 'danger'
return ''
}
return {
bloodTestItems,
formattedMedicalDateTime,
formatReference,
handleSyncResults,
handleSaveResults
}
}
}
</script>
<style scoped>
.blood-test-container {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
background: #fff;
height: 100%;
}
.result-table {
margin: 20px;
border: 1px solid #ebeef5;
border-radius: 4px;
flex: 1;
display: flex;
flex-direction: column;
}
.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;
}
.table-row {
display: flex;
border-bottom: 1px solid #ebeef5;
}
.table-row.danger-row {
background: #fef0f0;
}
.table-cell {
padding: 12px 8px;
border-right: 1px solid #ebeef5;
display: flex;
align-items: center;
}
.action-footer {
padding: 16px 20px;
border-top: 1px solid #ebeef5;
display: flex;
justify-content: space-between;
align-items: center;
}
.action-buttons {
display: flex;
gap: 12px;
}
.action-btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
background: #409EFF;
color: white;
cursor: pointer;
font-size: 14px;
}
.action-btn:hover {
opacity: 0.9;
}
.action-btn.primary {
background: #67C23A;
}
</style>

View File

@ -1005,20 +1005,43 @@ const handlePersonInfoSave = async () => {
if (result) {
//
if (comboInfoTable1.value.length > 0) {
const saveItems: PatientitemsVO[] = comboInfoTable1.value.map(item => ({
//
const existingItems = await PatientitemsApi.getPatientitemsPage({
medicalSn: personParam.value.testNum,
itemName: item.name,
itemCode: item.itemCode,
price: item.price,
discountedPrice: item.discountPrice,
discounted: item.discount,
sectionID: item.sectionID,
itemStatus: '0',
createTime: new Date().getTime(), //
headimage: avatar
}))
pageNo: 1,
pageSize: 100
})
await PatientitemsApi.createPatientitemsBatch(saveItems)
// itemCode
const existingItemCodes = new Set(
existingItems?.list?.map(item => item.itemCode) || []
)
//
const newItems = comboInfoTable1.value.filter(
item => !existingItemCodes.has(item.itemCode)
)
if (newItems.length > 0) {
// 使使
const saveItems: PatientitemsVO[] = newItems.map(item => ({
medicalSn: personParam.value.testNum,
itemName: item.name,
itemCode: item.itemCode,
price: item.price,
discountedPrice: item.discountPrice,
discounted: item.discount,
sectionID: item.sectionID,
itemStatus: '0',
createTime: new Date().getTime(),
headimage: avatar,
groupname: item.groupname,
groupcode: item.groupcode,
mealfrontorafter: item.mealfrontorafter
}))
await PatientitemsApi.createPatientitemsBatch(saveItems)
}
}
message.success('保存成功')
@ -1170,7 +1193,36 @@ watch(() => searchForm_extra.value.passStatus, () => {
const selectedPatient = ref(null)
const selectedPatientId = ref(null)
// API
//
const departmentCache = ref(new Map())
//
const getSectionName = async (sectionID) => {
if (!sectionID) return '暂无科室'
//
if (departmentCache.value.has(sectionID)) {
return departmentCache.value.get(sectionID)
}
try {
//
const departments = await DepartmentApi.getListDepartment()
//
departments.forEach(dept => {
departmentCache.value.set(dept.departmentCode, dept.departmentName)
})
//
return departmentCache.value.get(sectionID) || '暂无科室'
} catch (error) {
console.error('获取科室信息失败:', error)
return '暂无科室'
}
}
//
const loadPatientProjects = async (medicalSn) => {
if (!medicalSn) {
comboInfoTable1.value = []
@ -1191,16 +1243,26 @@ const loadPatientProjects = async (medicalSn) => {
}
// API
const projectDetails: ProjectDetail[] = patientItemsRes.list.map(item => ({
name: item.itemName,
price: item.price || 0,
discount: item.discounted || 0,
discountPrice: item.discountedPrice || 0,
moduleName: item.moduleName || '',
section: item.section || '',
sectionID: item.sectionID || '',
itemCode: item.itemCode
}))
const projectDetails: ProjectDetail[] = []
for (const item of patientItemsRes.list) {
//
const sectionName = await getSectionName(item.sectionID)
projectDetails.push({
name: item.itemName,
price: item.price || 0,
discount: item.discounted || 0,
discountPrice: item.discountedPrice || 0,
moduleName: item.moduleName || '',
section: sectionName,
sectionID: item.sectionID || '',
itemCode: item.itemCode,
groupname: item.groupname || '', //
groupcode: item.groupcode || '', // ID
mealfrontorafter: item.mealfrontorafter || '' //
})
}
//
comboInfoTable1.value = []
@ -1391,7 +1453,7 @@ const handleCheckInfoSelection = (selection) => {
checkInfoSelectedData.value = selection
}
//
//
const confirmSelectedProjects = async () => {
try {
let selectedProjects: ProjectDetail[] = []
@ -1424,16 +1486,23 @@ const confirmSelectedProjects = async () => {
if (itemDetail?.list?.[0]) {
const item = itemDetail.list[0]
//
const sectionName = await getSectionName(item.sectionID)
processedItemCodes.add(moduleItem.itemCode)
selectedProjects.push({
name: item.itemName,
section: item.section, // 使
sectionID: item.sectionID, // ID
section: sectionName,
sectionID: item.sectionID,
price: item.price || 0,
discount: item.discounted || 0,
discountPrice: item.discountedPrice || 0,
moduleName: item.moduleName || moduleItem.examModuleName,
itemCode: moduleItem.itemCode
itemCode: moduleItem.itemCode,
groupname: item.groupname || '',
groupcode: item.groupcode || '',
mealfrontorafter: item.mealfrontorafter || ''
})
}
} catch (itemError) {
@ -1450,16 +1519,22 @@ const confirmSelectedProjects = async () => {
continue
}
//
const sectionName = await getSectionName(item.sectionID)
processedItemCodes.add(item.itemCode)
selectedProjects.push({
name: item.itemName,
section: item.section, // 使
sectionID: item.sectionID, // ID
section: sectionName,
sectionID: item.sectionID,
price: item.price,
discount: item.discounted,
discountPrice: item.discountedPrice,
moduleName: item.moduleName,
itemCode: item.itemCode
itemCode: item.itemCode,
groupname: item.groupname || '',
groupcode: item.groupcode || '',
mealfrontorafter: item.mealfrontorafter || ''
})
}
}
@ -1509,6 +1584,9 @@ interface ProjectDetail {
discountPrice: number
moduleName: string
itemCode: string
groupname?: string //
groupcode?: string // ID
mealfrontorafter?: string //
}
//