inspect-front/src/views/Department-entry/Batch-review.vue
2025-06-26 17:31:25 +08:00

757 lines
29 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 id="OrgDataPrintElementOptionSetting" style="display: none"></div>
<ContentWrap style="height: 120px; display: flex; align-items: center; padding: 20px 0">
<el-form
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
style="width: 100%"
>
<el-form-item label="打印日期" prop="printTimeRange">
<el-date-picker
v-model="queryParams.printTimeRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD HH:mm:ss"
class="!w-300px"
@change="handleDateChange"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> 查询
</el-button>
<el-button
type="success"
:disabled="selectedRows.length === 0"
@click="handleBatchReview"
>
<Icon icon="ep:check" class="mr-5px" /> 批量审核
</el-button>
<span style="margin-left: 8px; color: #666;">
已选择 {{ selectedRows.length }} 项
</span>
</el-form-item>
</el-form>
</ContentWrap>
<ContentWrap>
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
:header-cell-style="{ background: 'rgb(235, 241, 250)', height: '56px', color: '#333333' }"
:row-style="{ height: '56px' }"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column label="体检编号" align="center" prop="medicalSn" />
<el-table-column label="姓名" align="center" prop="pname" />
<el-table-column label="性别" align="center" prop="gender" />
<el-table-column label="身份证号" align="center" prop="cardId" />
<el-table-column label="联系电话" align="center" prop="phoneNum" />
</el-table>
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="handleQuery"
/>
</ContentWrap>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { PatientApi, type PatientVO } from '@/api/inspect/inspectpatient'
import { ElMessage } from 'element-plus'
import Pagination from '@/components/Pagination/index.vue'
import dayjs from 'dayjs'
import { InspectOrgApi, InspectOrgVO } from '@/api/inspect/inspectorg/index'
import { getUserProfile } from '@/api/system/user/profile'
import { ElLoading } from 'element-plus'
import { PacsDataApi } from '@/api/inspect/inspectpacsdata'
import { PatientitemsApi } from '@/api/inspect/inspectpatientitems'
import { DoctorApi } from '@/api/inspect/inspectdoctor'
const loading = ref(false)
const list = ref<PatientVO[]>([])
const total = ref(0)
const queryFormRef = ref()
const today = dayjs().format('YYYY-MM-DD')
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
printTimeRange: [`${today} 00:00:00`, `${today} 23:59:59`] as [string, string] | undefined,
status: 0
})
const selectedRows = ref<PatientVO[]>([])
const handleDateChange = (val: [string, string] | null) => {
if (val) {
// 设置开始时间为当天的 00:00:00
const startTime = `${val[0].split(' ')[0]} 00:00:00`
// 设置结束时间为当天的 23:59:59
const endTime = `${val[1].split(' ')[0]} 23:59:59`
queryParams.printTimeRange = [startTime, endTime]
} else {
queryParams.printTimeRange = undefined
}
}
const handleQuery = async () => {
// 校验日期
if (!queryParams.printTimeRange || queryParams.printTimeRange.length !== 2) {
ElMessage.warning('请选择打印日期')
return
}
loading.value = true
try {
const userInfo = await getUserProfile()
const examinfo = await InspectOrgApi.getInspectOrg(userInfo.deptId)
if (examinfo.orgid == undefined || examinfo.orgid == null) {
return
}
else{
const params = {
pageNo: queryParams.pageNo,
pageSize: queryParams.pageSize,
isprint: 1,
printTimeRange: queryParams.printTimeRange,
examhoscode: examinfo.orgid,
examhosname: examinfo.orgName,
status: 0
}
const data = await PatientApi.getPatientPage(params)
list.value = data.list || []
total.value = data.total || 0
}
} catch (error) {
console.error('查询失败:', error)
} finally {
loading.value = false
}
}
const handleBatchReview = async () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请选择要审核的项目')
return
}
try {
const loading = ElLoading.service({
lock: true,
text: '批量同步中...',
background: 'rgba(255, 255, 255, 0.7)'
})
let successCount = 0
let failCount = 0
// 获取用户信息
const userProfile = await getUserProfile()
if (!userProfile || !userProfile.nickname) {
ElMessage.error('获取用户信息失败,请重新登录')
loading.close()
return
}
const currentTimestamp = Date.now()
const currentDate = formatDate(new Date())
// 逐条处理选中的体检编号
for (const row of selectedRows.value) {
const medicalSn = row.medicalSn
const cardId = row.cardId
console.log('正在处理体检编号:', medicalSn)
try {
// 定义需要同步的类型
const types = ['XCG', 'NCG', 'SHQX', 'ECG', 'YBJC']
// 首先检查每种类型是否已存在数据
const existingDataPromises = types.map((type) =>
PacsDataApi.getPacsDataPage({
code: medicalSn,
type: type,
pageNo: 1,
pageSize: 1
}).catch((error) => {
console.warn(`检查${type}数据是否存在时出错:`, error)
return { list: [] }
})
)
// 检查超声数据是否存在
const usExistingData = await PacsDataApi.getPacsDataPage({
code: medicalSn,
type: 'US',
pageNo: 1,
pageSize: 1
}).catch((error) => {
console.warn('检查超声数据是否存在时出错:', error)
return { list: [] }
})
// 检查心电图数据是否存在
const ecgExistingData = await PacsDataApi.getPacsDataPage({
code: medicalSn,
type: 'ECG',
pageNo: 1,
pageSize: 1
}).catch((error) => {
console.warn('检查心电图数据是否存在时出错:', error)
return { list: [] }
})
// 获取所有类型的现有数据结果
const existingDataResults = await Promise.all(existingDataPromises)
// 确定哪些类型需要同步
const typesToSync: string[] = []
existingDataResults.forEach((result, index) => {
const type = types[index]
// 如果没有数据或数据为空,则需要同步
const needsSync =
!result.list ||
result.list.length === 0 ||
(result.list[0] && (!result.list[0].data || result.list[0].data === ''))
if (needsSync) {
typesToSync.push(type)
} else {
console.log(`${type}数据已存在且不为空,跳过同步`)
}
})
// 检查超声数据是否需要同步
const needsSyncUS =
!usExistingData.list ||
usExistingData.list.length === 0 ||
(usExistingData.list[0] &&
(!usExistingData.list[0].data || usExistingData.list[0].data === ''))
// 检查心电图数据是否需要同步
const needsSyncECG =
!ecgExistingData.list ||
ecgExistingData.list.length === 0 ||
(ecgExistingData.list[0] &&
(!ecgExistingData.list[0].data || ecgExistingData.list[0].data === ''))
// 准备同步请求
const syncPromises: Promise<any>[] = []
// 添加需要同步的检验报告请求
typesToSync.forEach((type) => {
syncPromises.push(
PatientApi.getReportTj(medicalSn, type).catch((error) => {
console.warn(`获取${type}报告失败:`, error)
return null
})
)
})
// ========== 新增获取超声报告并写入conclusions.ultrasound ==========
let usReport = null;
try {
usReport = await PatientApi.getUSTj(medicalSn);
console.log('usReport:', usReport); // 调试输出,便于确认字段名和内容
} catch (error) {
console.warn('获取超声报告失败:', error);
}
// ========== 新增 END ==========
// 如果需要,添加心电图报告请求
if (needsSyncECG) {
syncPromises.push(
PatientApi.GetApiEcgInfo(medicalSn).catch((error) => {
console.warn('获取心电图报告失败:', error)
return null
})
)
}
// 如果需要,添加一般检查报告请求
if (typesToSync.includes('YBJC')) {
syncPromises.push(
PatientApi.GetApiYbjcInfo(medicalSn, cardId).catch((error) => {
console.warn('获取一般检查报告失败:', error)
return null
})
)
}
// 添加中医体质辨识报告请求
await PatientApi.getZytzInfo(medicalSn, cardId).catch((error) => {
console.warn('获取中医体质辨识报告失败:', error)
})
// 调用getGwPatientInfo接口获取公卫患者信息
if (cardId) {
try {
await PatientApi.getGwPatientInfo(cardId)
console.log(`${medicalSn} 公卫患者信息获取成功`)
} catch (error) {
console.warn(`${medicalSn} 获取公卫患者信息失败:`, error)
// 这里不中断流程,只是记录警告
}
// 新增调用updatePatientSupplement接口
try {
await PatientApi.updatePatientSupplement(medicalSn, cardId)
console.log(`${medicalSn} 补充信息更新成功`)
} catch (error) {
console.warn(`${medicalSn} 更新患者补充信息失败:`, error)
}
}
// 如果没有需要同步的项目,直接进行保存
if (syncPromises.length === 0) {
console.log(`${medicalSn} 所有报告数据已存在,直接进行保存`)
} else {
const results = await Promise.all(syncPromises)
// 检查是否所有请求都失败了
const allFailed = results.every((result) => result === null)
if (allFailed) {
console.error(`${medicalSn} 所有报告同步失败`)
failCount++
continue
}
}
// 等待一小段时间确保后端数据同步完成
await new Promise((resolve) => setTimeout(resolve, 500))
// 在保存之前检查各个检查项目是否有值
console.log(`${medicalSn} 开始检查检查项目数据...`)
// 检查PACS数据是否完整
const pacsDataCheck = await checkPacsDataComplete(medicalSn)
if (!pacsDataCheck.isComplete) {
console.warn(`${medicalSn} 检查项目数据不完整,跳过保存:`, pacsDataCheck.missingItems)
failCount++
continue
}
// 同步完成后,立即进行保存操作
try {
console.log(`${medicalSn} 开始保存检查结果...`)
// 1. 获取患者检查项目
const itemsRes = await PatientitemsApi.getPatientitemsPage({
medicalSn: medicalSn,
pageNo: 1,
pageSize: 100
})
if (itemsRes.list && itemsRes.list.length > 0) {
// 2. 处理PACS数据并生成结论数据
const conclusions = {
general: { summary: '未见异常' },
ultrasound: { finding: '未见异常', diagnosis: '未见异常' },
ecg: { finding: '心电图未见异常', diagnosis: '心电图未见异常' },
blood: { summary: '未见异常' },
urine: { summary: '未见异常' },
biochemical: { summary: '未见异常' }
}
// ========== 处理超声报告内容优先从已有项目中获取其次从API获取 ==========
// 首先从检查项目中获取已有的超声数据
let hasExistingUltrasoundData = false;
itemsRes.list.forEach((item) => {
const itemName = (item.itemName || '').toLowerCase();
if (itemName.includes('超声') || itemName.includes('彩超') || itemName.includes('b超')) {
// 处理超声项目的所见和结果
if (item.examDescription && item.examDescription.trim()) {
conclusions.ultrasound.finding = item.examDescription;
hasExistingUltrasoundData = true;
}
if (item.itemResult && item.itemResult.trim()) {
conclusions.ultrasound.diagnosis = item.itemResult;
hasExistingUltrasoundData = true;
}
// 如果有analyse字段尝试从中解析检查所见和结果
if (item.analyse) {
const parts = item.analyse.split('\n');
parts.forEach((part) => {
if (part.startsWith('检查所见:')) {
const finding = part.replace('检查所见:', '').trim();
if (finding) {
conclusions.ultrasound.finding = finding;
hasExistingUltrasoundData = true;
}
} else if (part.startsWith('检查结果:')) {
const diagnosis = part.replace('检查结果:', '').trim();
if (diagnosis) {
conclusions.ultrasound.diagnosis = diagnosis;
hasExistingUltrasoundData = true;
}
}
});
}
}
});
// 如果没有已存在的超声数据且API获取到了超声报告则使用API数据
if (!hasExistingUltrasoundData && usReport) {
const us = usReport as { examDescription?: string; diagResults?: string } | null;
if (us && (us.examDescription || us.diagResults)) {
conclusions.ultrasound.finding = (us.examDescription && us.examDescription.trim()) ? us.examDescription.replace(/\n/g, ' ') : '未见异常';
conclusions.ultrasound.diagnosis = (us.diagResults && us.diagResults.trim()) ? us.diagResults.replace(/\n/g, ' ') : '未见异常';
}
}
// ========== 处理超声报告内容 END ==========
// 获取PACS数据
try {
const pacsRes = await PacsDataApi.getPacsDataDetail(medicalSn)
if (pacsRes && pacsRes.length > 0) {
const typeGroups: Record<string, any[]> = {}
pacsRes.forEach((item) => {
const type = (item.type || '').toLowerCase()
if (!typeGroups[type]) {
typeGroups[type] = []
}
typeGroups[type].push(item)
})
// 类型映射关系
const typeToTabMapping = {
cbc: 'blood',
rt: 'urine',
bt: 'biochemical'
}
// 填充PACS数据到对应标签页
Object.entries(typeGroups).forEach(([type, items]) => {
const lowerType = type.toLowerCase()
const tabKey = typeToTabMapping[lowerType]
if (tabKey) {
// 对于血常规、尿常规、生化使用item字段
const validItems = (items as any[]).filter(item => item.item && item.item.trim() !== '')
if (validItems.length > 0) {
const combinedData = validItems.map(item => item.item).join(';')
conclusions[tabKey].summary = combinedData
}
}
})
}
} catch (error) {
console.warn('获取PACS数据失败:', error)
}
// ========== 自动分析一般检查项目异常并写入结论 ==========
// 新增:更全面的一般检查异常分析
const abnormalSummary: string[] = [];
itemsRes.list.forEach((item) => {
// 跳过弃检
if (item.itemStatus === '2') return;
const itemName = (item.itemName || '').toLowerCase();
// 跳过身高和体重
if (itemName.includes('身高') || itemName.includes('体重')||itemName.includes('腰围')) return;
// 参考值判断(只要有参考值)
if (
item.lowValue != null &&
item.highValue != null &&
item.itemResult != null &&
item.itemResult !== '' &&
!isNaN(Number(item.itemResult))
) {
const value = parseFloat(item.itemResult);
if (!isNaN(value)) {
if (value < item.lowValue) {
abnormalSummary.push(`${item.itemName}:偏低 ,结果:${item.itemResult}${item.unit || ''},参考值(${item.lowValue}-${item.highValue})】`);
} else if (value > item.highValue) {
abnormalSummary.push(`${item.itemName}:偏高 ,结果:${item.itemResult}${item.unit || ''},参考值(${item.lowValue}-${item.highValue})】`);
}
}
}
// BMI
if ((itemName.includes('bmi') || itemName.includes('体质指数')) && item.itemResult) {
const bmiValue = parseFloat(item.itemResult);
if (!isNaN(bmiValue)) {
if (bmiValue < 18.5) {
abnormalSummary.push(`【体质指数(BMI)】${bmiValue},体重过低`);
} else if (bmiValue >= 24 && bmiValue < 28) {
abnormalSummary.push(`【体质指数(BMI)】${bmiValue},超重`);
} else if (bmiValue >= 28) {
abnormalSummary.push(`【体质指数(BMI)】${bmiValue},肥胖`);
}
}
}
// 血压
if (itemName.includes('血压') && item.itemResult) {
const bpMatch = item.itemResult.match(/(\d+)[\/\\](\d+)/);
if (bpMatch) {
const systolic = parseInt(bpMatch[1]);
const diastolic = parseInt(bpMatch[2]);
if (!isNaN(systolic) && !isNaN(diastolic)) {
if (systolic >= 130 || diastolic >= 85) {
abnormalSummary.push(`【血压】${item.itemResult},偏高`);
}
}
}
}
// 你可以继续扩展其他一般检查项目的异常判断
});
conclusions.general.summary = abnormalSummary.length > 0 ? abnormalSummary.join('\n') : '未见异常';
// ========== 自动分析一般检查项目异常并写入结论 END ==========
// 3. 处理检查项目数据并生成更新数据
const allUpdatedItems = itemsRes.list.map((item) => {
const itemName = (item.itemName || '').toLowerCase()
// 基础字段 - 确保检查医生信息正确保存
const baseFields = {
id: item.id,
medicalSn: medicalSn,
itemStatus: '1', // 设置为已检查状态
inspectdoctor: userProfile.nickname || userProfile.username || '未知医生', // 确保有检查医生信息
inspecttime: currentTimestamp,
sectionID: userProfile?.deptId || '',
itemResult: item.itemResult || ''
}
// 根据项目类型处理不同的字段
if (itemName.includes('超声') || itemName.includes('彩超') || itemName.includes('b超')) {
// 使用 conclusions 中的超声数据
const finding = conclusions.ultrasound?.finding?.trim() || '未见异常';
const diagnosis = conclusions.ultrasound?.diagnosis?.trim() || '未见异常';
// 拼接成指定格式的字符串保存到analyse字段
const analyseContent = `检查所见:${finding}\n检查结果${diagnosis}`;
return {
...baseFields,
examDescription: finding,
itemResult: diagnosis,
analyse: analyseContent
}
} else if (itemName.includes('心电图') || itemName.includes('ecg') || itemName.includes('ekg')) {
return {
...baseFields,
examDescription: conclusions.ecg.finding,
itemResult: conclusions.ecg.diagnosis,
analyse: `检查所见:${conclusions.ecg.finding}\n检查结果${conclusions.ecg.diagnosis}`
}
} else if (itemName.includes('血常规') || itemName.includes('血细胞') || itemName.includes('血红蛋白')) {
return {
...baseFields,
analyse: conclusions.blood.summary,
itemResult: item.itemResult || ''
}
} else if (itemName.includes('尿常规') || itemName.includes('尿液分析')) {
return {
...baseFields,
analyse: conclusions.urine.summary,
itemResult: item.itemResult || ''
}
} else if (itemName.includes('生化') || itemName.includes('肝功能') || itemName.includes('肾功能') ||
itemName.includes('血脂') || itemName.includes('血糖') || itemName.includes('电解质')) {
return {
...baseFields,
analyse: conclusions.biochemical.summary,
itemResult: item.itemResult || ''
}
} else {
// 一般检查项目
return {
...baseFields,
analyse: conclusions.general.summary
}
}
})
// 4. 批量更新检查项目
await PatientitemsApi.updatePatientitemsBatch(allUpdatedItems)
// 5. 更新患者状态为已检查
await PatientApi.updatePatient({
id: row.id,
status: 1
})
// 新增:保存总检医生
const res = await DoctorApi.getDoctorPage({
pageNo: 1,
pageSize: 100, // 设置较大的数值以获取所有医生
orgid: userProfile.deptId
})
const doctor = res.list.find(doctor => doctor.doctorname === userProfile.nickname)
await PatientApi.updateChiefinspector(medicalSn, doctor.doctorid, doctor.doctorname)
// 6. 生成汇总内容并保存
let summaryContent = ''
// 一般检查
summaryContent += '【一般检查】\n' + (conclusions.general.summary || '未见异常') + '\n\n'
// 超声
summaryContent += '【超声】\n'
summaryContent += '检查所见:' + (conclusions.ultrasound.finding || '未见异常') + '\n'
summaryContent += '检查结果:' + (conclusions.ultrasound.diagnosis || '未见异常') + '\n\n'
// 心电图
summaryContent += '【心电图】\n'
summaryContent += '检查所见:' + (conclusions.ecg.finding || '未见异常') + '\n'
summaryContent += '检查结果:' + (conclusions.ecg.diagnosis || '未见异常') + '\n\n'
// 血常规
summaryContent += '【血常规】\n' + (conclusions.blood.summary || '未见异常') + '\n\n'
// 尿常规
summaryContent += '【尿常规】\n' + (conclusions.urine.summary || '未见异常') + '\n\n'
// 生化
summaryContent += '【生化】\n' + (conclusions.biochemical.summary || '未见异常') + '\n\n'
// 【重点】同步一般检查结论到患者主表summaryResult字段
if (conclusions.general.summary && conclusions.general.summary.trim()) {
await PatientApi.updatemedicalSn({
medicalSn: medicalSn,
summaryResult: conclusions.general.summary
} as any)
}
// 如果有汇总内容,保存到患者信息
if (summaryContent.trim()) {
await PatientApi.updatemedicalSn({
medicalSn: medicalSn,
summaryResult: summaryContent
} as any)
}
// 7. 生成体检报告
PatientApi.generateReport(medicalSn)
// 8. 体检报告信息回传
PatientApi.PushJYPatientInfo(medicalSn)
console.log(`${medicalSn} 保存完成`)
successCount++
} else {
console.warn(`${medicalSn} 没有找到检查项目,跳过保存`)
successCount++
}
} catch (saveError) {
console.error(`${medicalSn} 保存失败:`, saveError)
failCount++
}
// 可以添加延迟避免请求过快
await new Promise(resolve => setTimeout(resolve, 200))
} catch (error) {
console.error(`处理体检编号 ${medicalSn} 失败:`, error)
failCount++
}
}
loading.close()
if (failCount === 0) {
ElMessage.success(`批量同步完成,共处理 ${successCount}`)
} else {
ElMessage.warning(`批量同步完成,成功 ${successCount} 项,失败 ${failCount}`)
}
// 清空选择
selectedRows.value = []
// 重新查询数据
await handleQuery()
} catch (error) {
console.error('批量同步失败:', error)
ElMessage.error('批量同步失败,请重试')
}
}
// 添加检查PACS数据是否完整的函数
const checkPacsDataComplete = async (medicalSn: string) => {
try {
// 获取所有PACS数据
const pacsRes = await PacsDataApi.getPacsDataDetail(medicalSn)
if (!pacsRes || pacsRes.length === 0) {
return {
isComplete: false,
missingItems: ['所有检查项目']
}
}
// 按类型分组数据,检查不同类型的字段
const typeGroups: Record<string, any[]> = {}
pacsRes.forEach((item) => {
const type = (item.type || '').toLowerCase()
if (!typeGroups[type]) {
typeGroups[type] = []
}
typeGroups[type].push(item)
})
console.log(`${medicalSn} PACS数据类型分组:`, Object.keys(typeGroups))
// 检查各个检查项目是否有值
const missingItems: string[] = []
// 检查超声数据 (us类型) - 检查data字段
if (!typeGroups['us'] || typeGroups['us'].length === 0 ||
!typeGroups['us'].some(item => item.data && item.data.trim() !== '')) {
missingItems.push('超声')
}
// 检查心电图数据 (ecg类型) - 检查data字段
if (!typeGroups['ecg'] || typeGroups['ecg'].length === 0 ||
!typeGroups['ecg'].some(item => item.data && item.data.trim() !== '')) {
missingItems.push('心电图')
}
// 检查血常规数据 (cbc类型) - 检查item字段
if (!typeGroups['cbc'] || typeGroups['cbc'].length === 0 ||
!typeGroups['cbc'].some(item => item.item && item.item.trim() !== '')) {
missingItems.push('血常规')
}
// 检查尿常规数据 (rt类型) - 检查item字段
if (!typeGroups['rt'] || typeGroups['rt'].length === 0 ||
!typeGroups['rt'].some(item => item.item && item.item.trim() !== '')) {
missingItems.push('尿常规')
}
// 检查生化数据 (bt类型) - 检查item字段
if (!typeGroups['bt'] || typeGroups['bt'].length === 0 ||
!typeGroups['bt'].some(item => item.item && item.item.trim() !== '')) {
missingItems.push('生化')
}
console.log(`${medicalSn} 缺少的检查项目:`, missingItems)
return {
isComplete: missingItems.length === 0,
missingItems: missingItems
}
} catch (error) {
console.error('检查PACS数据完整性失败:', error)
return {
isComplete: false,
missingItems: ['检查失败']
}
}
}
// 添加日期格式化函数
const formatDate = (date: Date) => {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
const handleSelectionChange = (rows: PatientVO[]) => {
selectedRows.value = rows
}
</script>
<style scoped>
</style>