肺功能
This commit is contained in:
parent
fbb696066f
commit
9749201e5e
@ -1,4 +1,5 @@
|
||||
import request from '@/config/axios'
|
||||
import { patientinfoVO } from '../patientinfo'
|
||||
|
||||
// 肺功能患者 VO
|
||||
export interface PftVO {
|
||||
@ -50,5 +51,26 @@ export const PftApi = {
|
||||
// 导出肺功能患者 Excel
|
||||
exportPft: async (params) => {
|
||||
return await request.download({ url: `/system/pft/export-excel`, params })
|
||||
},
|
||||
|
||||
// 批量新增肺功能患者数据
|
||||
insertPftPatientData: async (data: patientinfoVO[]) => {
|
||||
return await request.post({ url: `/system/pft/insertPftPatientData`, data })
|
||||
},
|
||||
|
||||
// 根据examid更新肺功能分析结果
|
||||
updatePftAnalysis: async (examid: string, analysisResult: string) => {
|
||||
return await request.put({
|
||||
url: `/system/pft/updatePftAnalysis`,
|
||||
params: {
|
||||
examid,
|
||||
analysisResult
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 根据examid查询肺功能分析结果
|
||||
getPftAnalysis: async (examid: string) => {
|
||||
return await request.get({ url: `/system/pft/getPftAnalysis`, params: { examid } })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,5 +45,17 @@ export const PftdataApi = {
|
||||
// 导出肺功能数据 Excel
|
||||
exportPftdata: async (params) => {
|
||||
return await request.download({ url: `/system/pftdata/export-excel`, params })
|
||||
},
|
||||
|
||||
// 根据检查ID&患者ID获取肺功能数据
|
||||
getPftDataByExamidAndRegid: async (examid: string, regid: string) => {
|
||||
return await request.get({
|
||||
url: `/system/pftdata/getByExamidAndRegid?examid=` + examid + '®id=' + regid
|
||||
})
|
||||
},
|
||||
|
||||
//根据examid®id保存诊断结论
|
||||
savePftDataDiagnosis: async (data: any) => {
|
||||
return await request.put({ url: `/system/pftdata/saveDiagnosisByExamidAndRegid`, data })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,6 +210,7 @@ import { StaticecgApi } from '@/api/staticecg'
|
||||
import { Spo2infoApi } from '@/api/spo2info'
|
||||
import { CgmApi } from '@/api/cgm/index'
|
||||
import { arterialApi } from '@/api/arterial/index'
|
||||
import { PftApi } from '@/api/pft/index'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
@ -373,6 +374,10 @@ const handleConfirm = async () => {
|
||||
// 批量新增动脉硬化患者数据
|
||||
await arterialApi.insertArterialPatientData(selectedPatients.value)
|
||||
}
|
||||
if (props.type === 'pft') {
|
||||
// 批量新增肺功能患者数据
|
||||
await PftApi.insertPftPatientData(selectedPatients.value)
|
||||
}
|
||||
handleCancel()
|
||||
} catch (error) {
|
||||
message.error('操作失败,请重试')
|
||||
|
||||
447
src/views/analysis/PFT/PFT.vue
Normal file
447
src/views/analysis/PFT/PFT.vue
Normal file
@ -0,0 +1,447 @@
|
||||
<template>
|
||||
<div class="pft-analysis">
|
||||
<!-- 肺功能分析弹窗组件 -->
|
||||
<PFT_analysis ref="pftAnalysisRef" v-model="showAnalysisDialog" />
|
||||
|
||||
<!-- 患者选择组件(后续可补充) -->
|
||||
<PatientSelect
|
||||
ref="patientSelectRef"
|
||||
title="选择患者"
|
||||
type="pft"
|
||||
:multiple="true"
|
||||
:max-select="50"
|
||||
@confirm="handlePatientConfirm"
|
||||
@cancel="handlePatientCancel"
|
||||
/>
|
||||
|
||||
<!-- 顶部筛选区 -->
|
||||
<el-card class="filter-card" shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="filter-form">
|
||||
<el-form-item label="姓名:">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入姓名"
|
||||
clearable
|
||||
style="width: 120px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="性别:">
|
||||
<el-select
|
||||
v-model="queryParams.gender"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 100px"
|
||||
>
|
||||
<el-option label="男" value="1" />
|
||||
<el-option label="女" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="日期:">
|
||||
<el-date-picker
|
||||
v-model="queryParams.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
style="width: 240px"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 140px"
|
||||
>
|
||||
<el-option label="已申请" value="1" />
|
||||
<el-option label="未申请" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
<el-button @click="resetQuery"
|
||||
><el-icon><Refresh /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button type="primary" @click="handleSelectPatients"
|
||||
><el-icon><Plus /></el-icon>选择患者</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 数据表格区 -->
|
||||
<el-card class="table-card" shadow="never" style="margin-top: 16px">
|
||||
<el-table v-loading="loading" :data="tableData" border stripe style="width: 100%">
|
||||
<el-table-column prop="examid" label="检查ID" align="center" min-width="220" />
|
||||
<el-table-column prop="name" label="姓名" align="center" min-width="90" />
|
||||
<el-table-column prop="gender" label="性别" align="center" min-width="60">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.gender === '1' ? '男' : row.gender === '2' ? '女' : '未知' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="age" label="年龄" align="center" min-width="60">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.age + '岁' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="weartime" label="佩戴时间" align="center" min-width="180">
|
||||
<template #default="{ row, $index }">
|
||||
<!-- 编辑状态 -->
|
||||
<div v-if="row.editingWearTime" style="display: flex; align-items: center; gap: 8px">
|
||||
<el-date-picker
|
||||
v-model="row.weartime"
|
||||
type="datetime"
|
||||
placeholder="选择时间"
|
||||
size="small"
|
||||
style="width: 130px"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
<el-button type="primary" size="small" @click="saveWearTime(row, $index)" link>
|
||||
<el-icon><Check /></el-icon>
|
||||
</el-button>
|
||||
<el-button size="small" @click="cancelEditWearTime(row, $index)" link>
|
||||
<el-icon><Close /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 显示状态 -->
|
||||
<div
|
||||
v-else
|
||||
style="display: flex; align-items: center; justify-content: center; gap: 8px"
|
||||
>
|
||||
<span>{{ formatTime(row.weartime) }}</span>
|
||||
<el-button type="primary" size="small" @click="editWearTime(row, $index)" link>
|
||||
<el-icon><Edit /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="device" label="检测设备" align="center" min-width="150">
|
||||
<template #default="{ row, $index }">
|
||||
<!-- 编辑状态 -->
|
||||
<div v-if="row.editingDevice" style="display: flex; align-items: center; gap: 8px">
|
||||
<el-select
|
||||
v-model="row.deviceid"
|
||||
placeholder="选择设备"
|
||||
size="small"
|
||||
style="width: 110px"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in deviceList"
|
||||
:key="item.id"
|
||||
:label="item.deviceName"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button type="primary" size="small" @click="saveDevice(row, $index)" link>
|
||||
<el-icon><Check /></el-icon>
|
||||
</el-button>
|
||||
<el-button size="small" @click="cancelEditDevice(row, $index)" link>
|
||||
<el-icon><Close /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 显示状态 -->
|
||||
<div
|
||||
v-else
|
||||
style="display: flex; align-items: center; justify-content: center; gap: 8px"
|
||||
>
|
||||
<span>{{ row.devicename }}</span>
|
||||
<el-button type="primary" size="small" @click="editDevice(row, $index)" link>
|
||||
<el-icon><Edit /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="orgname" label="机构" align="center" min-width="120" />
|
||||
<el-table-column label="分析" align="center" min-width="70">
|
||||
<template #default="{ row }">
|
||||
<el-link type="primary" @click="onAnalyze(row)">分析</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="报告" align="center" min-width="70">
|
||||
<template #default="{ row }">
|
||||
<el-link type="primary" @click="onViewReport(row)">查看</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" align="center" min-width="70">
|
||||
<template #default="{ row }">
|
||||
<el-link v-if="row.status == '1'" type="success" :underline="false" disabled
|
||||
>已申请</el-link
|
||||
>
|
||||
<el-link v-else type="primary" @click="handleApply(row)">申请</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import PatientSelect from '@/patientcom/index.vue'
|
||||
import { getUserProfile } from '@/api/system/user/profile'
|
||||
import { OrgApi } from '@/api/org'
|
||||
import { PftApi } from '@/api/pft'
|
||||
import { PftdataApi } from '@/api/pftdata'
|
||||
import { Search, Refresh, Plus, Edit, Check, Close } from '@element-plus/icons-vue'
|
||||
import dayjs from 'dayjs'
|
||||
import PFT_analysis from './PFT_analysis.vue'
|
||||
|
||||
const loading = ref(false)
|
||||
const total = ref(0)
|
||||
const showAnalysisDialog = ref(false)
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: '',
|
||||
gender: '',
|
||||
dateRange: [],
|
||||
status: '',
|
||||
orgid: '',
|
||||
orgname: '',
|
||||
age: ''
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref<any[]>([])
|
||||
|
||||
const patientSelectRef = ref()
|
||||
const pftAnalysisRef = ref()
|
||||
|
||||
// 设备下拉测试数据
|
||||
const deviceList = ref<any[]>([])
|
||||
const getDeviceList = () => {
|
||||
deviceList.value = [
|
||||
{ id: '1001', deviceName: '肺功能检测仪-1001' },
|
||||
{ id: '1002', deviceName: '肺功能检测仪-1002' },
|
||||
{ id: '1003', deviceName: '肺功能检测仪-1003' }
|
||||
]
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDeviceList()
|
||||
getList()
|
||||
})
|
||||
|
||||
// 获取列表数据
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 处理日期范围参数
|
||||
const params = { ...queryParams }
|
||||
if (params.dateRange && params.dateRange.length === 2) {
|
||||
params.startDate = params.dateRange[0]
|
||||
params.endDate = params.dateRange[1]
|
||||
delete params.dateRange
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
const userinfo = await getUserProfile()
|
||||
const orginfo = await OrgApi.getOrgByOrgId(userinfo.orgid)
|
||||
// 判断登陆人机构ID是否为超级管理员[0],或者为空
|
||||
if (userinfo.orgid != 0 && userinfo.orgid != null) {
|
||||
// 判断登陆人机构ID是否有等于父&上级机构ID,如果等于则查询子&下级机构ID
|
||||
if (userinfo.orgid != orginfo.parentOrgId) {
|
||||
params.orgid = userinfo.orgid
|
||||
const data = await PftApi.getPftPage(params)
|
||||
tableData.value = data.list
|
||||
total.value = data.total
|
||||
} else if (userinfo.orgid == orginfo.parentOrgId) {
|
||||
// 查询子&下级机构ID并查询对应机构的患者数据
|
||||
const childinfo = await OrgApi.getOrgByParentOrgId(userinfo.orgid)
|
||||
params.orgid = childinfo.orgId
|
||||
params.status = '1'
|
||||
const data = await PftApi.getPftPage(params)
|
||||
tableData.value = data.list
|
||||
total.value = data.total
|
||||
}
|
||||
} else if (userinfo.orgid == 0) {
|
||||
// 如果是超级管理员,查询全部
|
||||
const data = await PftApi.getPftPage(params)
|
||||
tableData.value = data.list
|
||||
total.value = data.total
|
||||
} else {
|
||||
console.error('用户机构为空')
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取肺功能数据失败:', error)
|
||||
ElMessage.error('获取数据失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 查询、重置、选择患者等方法
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
const resetQuery = () => {
|
||||
Object.assign(queryParams, {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: '',
|
||||
gender: '',
|
||||
dateRange: [],
|
||||
status: '',
|
||||
age: ''
|
||||
})
|
||||
getList()
|
||||
}
|
||||
|
||||
const handleSelectPatients = () => {
|
||||
patientSelectRef.value?.open()
|
||||
}
|
||||
|
||||
const handlePatientConfirm = async (selectedPatients: any[]) => {
|
||||
try {
|
||||
await PftApi.insertPftPatientData(selectedPatients)
|
||||
ElMessage.success(`已成功添加 ${selectedPatients.length} 位患者到肺功能检测系统`)
|
||||
// 这里刷新表格数据以显示新添加的患者
|
||||
handleQuery()
|
||||
} catch (error) {
|
||||
console.error('添加肺功能患者数据失败:', error)
|
||||
ElMessage.error('添加患者数据失败,请重试')
|
||||
}
|
||||
}
|
||||
|
||||
const handlePatientCancel = () => {
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
const onAnalyze = async (row: any) => {
|
||||
try {
|
||||
const res = await PftdataApi.getPftDataByExamidAndRegid(row.examid, row.regid)
|
||||
if (res) {
|
||||
showAnalysisDialog.value = true
|
||||
await pftAnalysisRef.value?.open(row)
|
||||
} else {
|
||||
ElMessage.error('暂无数据')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取分析数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const onViewReport = (row: any) => {
|
||||
ElMessage.success(`查看报告:${row.name}`)
|
||||
}
|
||||
|
||||
const handleApply = async (row: any) => {
|
||||
try {
|
||||
const userinfo = await getUserProfile()
|
||||
const orginfo = await OrgApi.getOrg(userinfo.orgid)
|
||||
if (orginfo.parentOrgId != null) {
|
||||
const updateData = { ...row, status: 1, managerorg: orginfo.parentOrgId }
|
||||
await PftApi.updatePft(updateData)
|
||||
ElMessage.success(`${row.name} 申请成功`)
|
||||
getList()
|
||||
} else {
|
||||
ElMessage.error('无上级机构')
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('申请失败:', error)
|
||||
ElMessage.error('申请失败,请重试')
|
||||
}
|
||||
}
|
||||
|
||||
const formatTime = (timestamp: number | string) => {
|
||||
if (!timestamp) return ''
|
||||
const ts = typeof timestamp === 'string' ? parseInt(timestamp) : timestamp
|
||||
return dayjs(ts).isValid() ? dayjs(ts).format('YYYY-MM-DD HH:mm:ss') : timestamp
|
||||
}
|
||||
|
||||
// 编辑佩戴时间
|
||||
const editWearTime = (row: any, index: number) => {
|
||||
row.editingWearTime = true
|
||||
row.originalWeartime = row.weartime // 保存原始值
|
||||
}
|
||||
// 保存佩戴时间(传递时间戳)
|
||||
const saveWearTime = async (row: any, index: number) => {
|
||||
if (!row.weartime) {
|
||||
ElMessage.error('佩戴时间不能为空')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const timestamp =
|
||||
typeof row.weartime === 'string' ? new Date(row.weartime).getTime() : row.weartime
|
||||
await PftApi.updatePft({ ...row, weartime: timestamp })
|
||||
row.weartime = timestamp // 本地同步为时间戳
|
||||
ElMessage.success('佩戴时间更新成功')
|
||||
row.editingWearTime = false
|
||||
getList()
|
||||
} catch (error) {
|
||||
console.error('更新佩戴时间失败:', error)
|
||||
ElMessage.error('更新佩戴时间失败,请重试')
|
||||
row.weartime = row.originalWeartime // 恢复原始值
|
||||
}
|
||||
}
|
||||
// 取消编辑佩戴时间
|
||||
const cancelEditWearTime = (row: any, index: number) => {
|
||||
row.weartime = row.originalWeartime
|
||||
row.editingWearTime = false
|
||||
}
|
||||
// 编辑设备
|
||||
const editDevice = (row: any, index: number) => {
|
||||
row.editingDevice = true
|
||||
row.originalDeviceId = row.deviceid // 保存原始值
|
||||
}
|
||||
// 保存设备
|
||||
const saveDevice = async (row: any, index: number) => {
|
||||
if (!row.deviceid) {
|
||||
ElMessage.error('设备不能为空')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await PftApi.updatePft(row)
|
||||
ElMessage.success('设备更新成功')
|
||||
row.editingDevice = false
|
||||
getList()
|
||||
} catch (error) {
|
||||
console.error('更新设备失败:', error)
|
||||
ElMessage.error('更新设备失败,请重试')
|
||||
row.deviceid = row.originalDeviceId // 恢复原始值
|
||||
}
|
||||
}
|
||||
// 取消编辑设备
|
||||
const cancelEditDevice = (row: any, index: number) => {
|
||||
row.deviceid = row.originalDeviceId
|
||||
row.editingDevice = false
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.pft-analysis {
|
||||
padding: 24px;
|
||||
background: #f6f8fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.filter-card {
|
||||
margin-bottom: 0;
|
||||
background: #fff;
|
||||
}
|
||||
.filter-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px 16px;
|
||||
}
|
||||
.table-card {
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
639
src/views/analysis/PFT/PFT_analysis.vue
Normal file
639
src/views/analysis/PFT/PFT_analysis.vue
Normal file
@ -0,0 +1,639 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="肺功能分析"
|
||||
:fullscreen="true"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
class="pft-analysis-dialog"
|
||||
@close="handleClose"
|
||||
>
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="main-content">
|
||||
<!-- 左侧图片展示区域 (3/4) -->
|
||||
<div class="left-panel">
|
||||
<!-- 图片展示区域 -->
|
||||
<div class="image-display-section">
|
||||
<div class="image-container full-height">
|
||||
<!-- 图片展示区域 -->
|
||||
<div v-if="pftData && pftData.data" class="image-wrapper">
|
||||
<img
|
||||
:src="pftData.data"
|
||||
alt="肺功能检测图像"
|
||||
class="detection-image"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
</div>
|
||||
<!-- 占位符 -->
|
||||
<div v-else class="no-image-placeholder">
|
||||
<el-empty description="暂无影像" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧诊断模板和结论区域 (1/4) -->
|
||||
<div class="right-panel">
|
||||
<!-- 诊断模板选择区域 -->
|
||||
<div class="diagnosis-selector">
|
||||
<h5 class="selector-title">
|
||||
诊断模板
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="请输入关键词"
|
||||
clearable
|
||||
style="height: 40px; margin-top: 5px"
|
||||
size="medium"
|
||||
prefix-icon="Search"
|
||||
/>
|
||||
</h5>
|
||||
|
||||
<div class="diagnosis-list">
|
||||
<div
|
||||
v-for="diag in filteredDiagnoses"
|
||||
:key="diag.code"
|
||||
class="diagnosis-item"
|
||||
@click="insertDiagnosis(diag.name)"
|
||||
>
|
||||
{{ diag.name }}
|
||||
</div>
|
||||
<div v-if="filteredDiagnoses.length == 0" class="no-results"> 暂无匹配的诊断模板 </div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 诊断结论输入区域 -->
|
||||
<div class="analysis-section">
|
||||
<h3>诊断结论</h3>
|
||||
<el-form :model="analysisResult" label-width="80px" size="medium">
|
||||
<el-input
|
||||
v-model="analysisResult.overallAssessment"
|
||||
type="textarea"
|
||||
:rows="8"
|
||||
resize="none"
|
||||
placeholder="请输入诊断结论"
|
||||
/>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<template #footer>
|
||||
<div class="bottom-actions">
|
||||
<el-button @click="handleClose">关闭</el-button>
|
||||
<el-button type="primary" @click="handleSave">保存结论</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, nextTick, watch, computed } from 'vue'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { PftdataApi } from '@/api/pftdata'
|
||||
|
||||
// 组件属性
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
patientData: any
|
||||
recordId?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: false,
|
||||
patientData: () => ({}),
|
||||
recordId: ''
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'saved'])
|
||||
|
||||
// 响应式数据
|
||||
const dialogVisible = ref(false)
|
||||
const currentRowData = ref<any>(null) // 存储传入的行数据
|
||||
const searchKeyword = ref('') // 搜索关键词
|
||||
|
||||
// 分析结果 - 只保留诊断结论字段
|
||||
const analysisResult = reactive({
|
||||
overallAssessment: ''
|
||||
})
|
||||
|
||||
// 肺功能检测数据
|
||||
const pftData = ref<any>(null)
|
||||
|
||||
// 肺功能诊断模板数据
|
||||
interface PftDiagnosis {
|
||||
name: string
|
||||
code: string
|
||||
category: string
|
||||
}
|
||||
|
||||
const pftDiagnoses: PftDiagnosis[] = [
|
||||
// 正常结果
|
||||
{ name: '肺功能正常', code: 'PFT001', category: 'normal' },
|
||||
{ name: '通气功能正常', code: 'PFT002', category: 'normal' },
|
||||
{ name: '弥散功能正常', code: 'PFT003', category: 'normal' },
|
||||
{ name: '气道阻力正常', code: 'PFT004', category: 'normal' },
|
||||
{ name: '肺容量正常', code: 'PFT005', category: 'normal' },
|
||||
|
||||
// 阻塞性通气功能障碍
|
||||
{ name: '轻度阻塞性通气功能障碍', code: 'PFT006', category: 'obstructive' },
|
||||
{ name: '中度阻塞性通气功能障碍', code: 'PFT007', category: 'obstructive' },
|
||||
{ name: '重度阻塞性通气功能障碍', code: 'PFT008', category: 'obstructive' },
|
||||
{ name: '极重度阻塞性通气功能障碍', code: 'PFT009', category: 'obstructive' },
|
||||
{ name: '小气道功能障碍', code: 'PFT010', category: 'obstructive' },
|
||||
|
||||
// 限制性通气功能障碍
|
||||
{ name: '轻度限制性通气功能障碍', code: 'PFT011', category: 'restrictive' },
|
||||
{ name: '中度限制性通气功能障碍', code: 'PFT012', category: 'restrictive' },
|
||||
{ name: '重度限制性通气功能障碍', code: 'PFT013', category: 'restrictive' },
|
||||
{ name: '肺容量减少', code: 'PFT014', category: 'restrictive' },
|
||||
{ name: '肺顺应性下降', code: 'PFT015', category: 'restrictive' },
|
||||
|
||||
// 混合性通气功能障碍
|
||||
{ name: '混合性通气功能障碍', code: 'PFT016', category: 'mixed' },
|
||||
{ name: '阻塞性为主的混合性障碍', code: 'PFT017', category: 'mixed' },
|
||||
{ name: '限制性为主的混合性障碍', code: 'PFT018', category: 'mixed' },
|
||||
|
||||
// 弥散功能障碍
|
||||
{ name: '轻度弥散功能障碍', code: 'PFT019', category: 'diffusion' },
|
||||
{ name: '中度弥散功能障碍', code: 'PFT020', category: 'diffusion' },
|
||||
{ name: '重度弥散功能障碍', code: 'PFT021', category: 'diffusion' },
|
||||
{ name: '弥散功能减退', code: 'PFT022', category: 'diffusion' },
|
||||
|
||||
// 气道反应性
|
||||
{ name: '气道高反应性', code: 'PFT023', category: 'reactivity' },
|
||||
{ name: '支气管舒张试验阳性', code: 'PFT024', category: 'reactivity' },
|
||||
{ name: '支气管激发试验阳性', code: 'PFT025', category: 'reactivity' },
|
||||
{ name: '可逆性气道阻塞', code: 'PFT026', category: 'reactivity' },
|
||||
|
||||
// 特殊情况
|
||||
{ name: '肺气肿改变', code: 'PFT027', category: 'special' },
|
||||
{ name: '肺纤维化改变', code: 'PFT028', category: 'special' },
|
||||
{ name: '胸廓畸形影响', code: 'PFT029', category: 'special' },
|
||||
{ name: '神经肌肉疾病影响', code: 'PFT030', category: 'special' },
|
||||
{ name: '配合度欠佳', code: 'PFT031', category: 'special' },
|
||||
{ name: '建议复查', code: 'PFT032', category: 'special' }
|
||||
]
|
||||
|
||||
// 过滤后的诊断模板
|
||||
const filteredDiagnoses = computed(() => {
|
||||
if (!searchKeyword.value) {
|
||||
return pftDiagnoses
|
||||
}
|
||||
return pftDiagnoses.filter((diag) =>
|
||||
diag.name.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
|
||||
// 监听对话框显示状态
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
dialogVisible.value = newVal
|
||||
if (newVal) {
|
||||
nextTick(() => {
|
||||
loadAnalysisData()
|
||||
})
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => dialogVisible.value,
|
||||
(newVal) => {
|
||||
emit('update:modelValue', newVal)
|
||||
}
|
||||
)
|
||||
|
||||
// 移除图表初始化函数,因为不再需要
|
||||
|
||||
// 加载分析数据
|
||||
const loadAnalysisData = async () => {
|
||||
try {
|
||||
if (!currentRowData.value) {
|
||||
ElMessage.warning('缺少必要的检查数据')
|
||||
return
|
||||
}
|
||||
|
||||
const { examid, regid } = currentRowData.value
|
||||
if (!examid || !regid) {
|
||||
ElMessage.warning('缺少检查ID或患者ID')
|
||||
return
|
||||
}
|
||||
|
||||
// 调用API获取pftdata数据
|
||||
const response = await PftdataApi.getPftDataByExamidAndRegid(examid, regid)
|
||||
|
||||
if (response) {
|
||||
pftData.value = response
|
||||
|
||||
// 处理data字段,去除可能的反引号和空格
|
||||
if (pftData.value && pftData.value.pftdata) {
|
||||
// 如果pftdata是字符串,直接处理
|
||||
if (typeof pftData.value.pftdata === 'string') {
|
||||
pftData.value.data = pftData.value.pftdata.trim().replace(/^`|`$/g, '')
|
||||
}
|
||||
} else if (typeof pftData.value === 'string') {
|
||||
// 如果整个response.data就是图片URL字符串,创建一个包含data字段的对象
|
||||
const imageUrl = pftData.value.trim().replace(/^`|`$/g, '')
|
||||
pftData.value = {
|
||||
data: imageUrl
|
||||
}
|
||||
}
|
||||
// 检查是否有已保存的诊断结论
|
||||
if (pftData.value.diagnosis) {
|
||||
// 现在diagnosis直接是字符串,不需要JSON解析
|
||||
analysisResult.overallAssessment = pftData.value.diagnosis
|
||||
} else {
|
||||
// 如果没有已保存的诊断结论,保持空白
|
||||
analysisResult.overallAssessment = ''
|
||||
}
|
||||
} else {
|
||||
// 如果没有数据,重置为空白
|
||||
analysisResult.overallAssessment = ''
|
||||
}
|
||||
// 如果没有已保存的分析结果,保持空白状态,不自动填写
|
||||
} catch (error) {
|
||||
console.error('加载分析数据失败:', error)
|
||||
ElMessage.error('加载分析数据失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 移除getStatusType函数,因为不再需要
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (dateTime: string | Date) => {
|
||||
if (!dateTime) return ''
|
||||
return formatDate(dateTime, 'YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
||||
// 处理图片加载错误
|
||||
const handleImageError = (event: Event) => {
|
||||
console.log('图片加载失败:', event)
|
||||
ElMessage.warning('图片加载失败')
|
||||
}
|
||||
|
||||
// 保存分析结果
|
||||
const handleSave = async () => {
|
||||
if (!analysisResult.overallAssessment.trim()) {
|
||||
ElMessage.warning('请输入诊断结论')
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentRowData.value?.examid || !currentRowData.value?.regid) {
|
||||
ElMessage.warning('缺少必要的患者信息')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 只传递必要的字段:examid、regid和诊断结论
|
||||
const saveData = {
|
||||
examid: currentRowData.value.examid,
|
||||
regid: currentRowData.value.regid,
|
||||
diagnosis: analysisResult.overallAssessment.trim()
|
||||
}
|
||||
|
||||
await PftdataApi.savePftDataDiagnosis(saveData)
|
||||
ElMessage.success('诊断结论已保存')
|
||||
} catch (error) {
|
||||
console.error('保存诊断失败:', error)
|
||||
ElMessage.error('保存诊断失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 插入诊断结论
|
||||
const insertDiagnosis = (diagnosis: string) => {
|
||||
const cursorPosition = analysisResult.overallAssessment.length
|
||||
if (
|
||||
analysisResult.overallAssessment &&
|
||||
!analysisResult.overallAssessment.endsWith('。') &&
|
||||
!analysisResult.overallAssessment.endsWith(',')
|
||||
) {
|
||||
analysisResult.overallAssessment += ',' + diagnosis
|
||||
} else {
|
||||
analysisResult.overallAssessment += diagnosis
|
||||
}
|
||||
}
|
||||
|
||||
// 应用诊断模板(保留原有功能,但不自动填写)
|
||||
const applyTemplate = (templateType: string) => {
|
||||
// 模板功能保留,但不自动填写诊断结论
|
||||
// 诊断结论应该根据接口查询结果显示
|
||||
ElMessage.success('模板应用成功')
|
||||
}
|
||||
|
||||
// 关闭对话框
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
// 打开对话框的方法
|
||||
const open = async (rowData?: any) => {
|
||||
dialogVisible.value = true
|
||||
|
||||
// 存储传入的行数据
|
||||
if (rowData) {
|
||||
currentRowData.value = rowData
|
||||
// 先加载数据,再决定是否重置
|
||||
await loadAnalysisData()
|
||||
} else {
|
||||
// 如果没有行数据,重置所有字段
|
||||
analysisResult.overallAssessment = ''
|
||||
pftData.value = null
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pft-analysis-dialog {
|
||||
--el-dialog-padding-primary: 0;
|
||||
}
|
||||
|
||||
.pft-analysis-dialog .el-dialog__body {
|
||||
padding: 0;
|
||||
height: calc(100vh - 120px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pft-analysis-dialog .el-dialog__footer {
|
||||
padding: 15px 20px;
|
||||
border-top: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
width: 75%;
|
||||
overflow-y: auto;
|
||||
border-right: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
width: 25%;
|
||||
overflow-y: auto;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.image-display-section,
|
||||
.analysis-section {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin: 0 0 15px 0;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.image-display-section {
|
||||
height: 100%;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
background: #fafafa;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.image-container.full-height {
|
||||
height: calc(100vh - 160px);
|
||||
}
|
||||
|
||||
.placeholder-image {
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.placeholder-image p {
|
||||
margin: 10px 0 0 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.placeholder-image .sub-text {
|
||||
font-size: 12px;
|
||||
color: #c0c4cc;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* 数据展示样式 */
|
||||
.data-display {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.data-display h3 {
|
||||
margin: 0 0 20px 0;
|
||||
color: #303133;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid #409eff;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
/* 图片展示区域样式 */
|
||||
.image-section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.image-section h4 {
|
||||
margin: 0 0 15px 0;
|
||||
color: #606266;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.image-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.detection-image {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.no-image-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.no-image-placeholder p {
|
||||
margin: 10px 0 0 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.data-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.data-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.data-item label {
|
||||
font-weight: bold;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.data-item span {
|
||||
color: #303133;
|
||||
font-size: 14px;
|
||||
padding: 5px 10px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.data-json {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.data-json pre {
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
color: #303133;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.diagnosis-selector {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.selector-title {
|
||||
font-size: 16px;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.diagnosis-list {
|
||||
max-height: 45vh;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.diagnosis-item {
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.diagnosis-item:hover {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.diagnosis-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.analysis-section {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.analysis-section h3 {
|
||||
margin: 0 0 15px 0;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
background: #f5f7fa;
|
||||
padding: 15px 20px;
|
||||
border-top: 1px solid #e4e7ed;
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.bottom-actions .el-button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 1200px) {
|
||||
.left-panel {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.main-content {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.left-panel,
|
||||
.right-panel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.patient-info-inline {
|
||||
gap: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user