Files
his/openhis-ui-vue3/src/views/doctorstation/components/inspection/inspectionApplication.vue
chenqi 91a0b48662 fix(consultation): 解决会诊流程中的多个功能问题
- 在 deptappthoursManage.js 中添加 status 参数以仅获取已启动的机构
- 为 consultationapplication 组件添加已确认和已签名状态选项
- 扩展操作列宽度并添加打印功能按钮
- 优化 handlePrint 方法以支持行参数和性别枚举转换
- 为 consultationconfirmation 组件添加必填验证和编辑权限控制
- 修复会诊确认医师信息回显逻辑
- 在 inspectionApplication 组件中修复表格行点击事件和检验项目加载
- 禁用非紧急标记的编辑权限以解决Bug #268
- 为 surgeryApplication 组件添加响应码验证和错误处理
- 在 consultation 组件中添加表单验证清除功能
- 为 PackageManagement 组件实现动态机构选项加载
- 重构 PackageSettings 组件的套餐金额显示和只读模式
- 为检查项目设置组件添加套餐筛选和下级类型选择功能
- 实现检验套餐的编辑和查看模式切换功能
2026-03-26 18:22:21 +08:00

2348 lines
70 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>
<el-container class="inspection-application-container">
<!-- 顶部操作按钮区 -->
<el-header class="top-action-bar" height="50px">
<el-row class="action-buttons" type="flex" justify="end" :gutter="10">
<el-button type="success" size="large" @click="handleSave" class="save-btn" :loading="saving">
<el-icon><Document /></el-icon>
保存
</el-button>
<el-button type="primary" size="large" @click="handleNewApplication" class="new-btn">
<el-icon><Plus /></el-icon>
新增
</el-button>
</el-row>
</el-header>
<!-- 检验信息表格区 -->
<el-main class="inspection-section" style="width: 100%; max-width: 100%">
<el-card class="table-card" style="width: 100%">
<template #header>
<el-row class="card-header" type="flex" align="middle">
<el-icon><DocumentChecked /></el-icon>
<span>检验信息</span>
</el-row>
</template>
<el-table
ref="inspectionTableRef"
:data="inspectionList"
border
stripe
size="small"
max-height="300px"
style="width: 100%; min-width: 100%"
class="inspection-table"
highlight-current-row
@selection-change="handleSelectionChange"
@current-change="handleRowClick"
@cell-click="handleCellClick"
>
<el-table-column type="selection" width="55" align="center" header-align="center" />
<el-table-column label="序号" width="60" align="center" header-align="center">
<template #default="{ $index }">
<span>{{ $index + 1 }}</span>
</template>
</el-table-column>
<el-table-column label="申请单号" prop="applyNo" min-width="180" align="center" header-align="center" />
<el-table-column label="检验项目" prop="itemName" min-width="170px" align="center" header-align="center" />
<el-table-column label="申请医生" prop="applyDocName" width="120" align="center" header-align="center" />
<el-table-column label="急" width="60" align="center" header-align="center">
<template #default="scope">
<el-icon v-if="scope.row.priorityCode == 1" color="#409EFF" :size="18"><Check /></el-icon>
</template>
</el-table-column>
<el-table-column label="收费" width="60" align="center" header-align="center">
<template #default="scope">
<el-icon v-if="scope.row.applyStatus == 1" color="#409EFF" :size="18"><Check /></el-icon>
</template>
</el-table-column>
<el-table-column label="退费" width="60" align="center" header-align="center">
<template #default="scope">
<el-icon v-if="scope.row.needRefund" color="#409EFF" :size="18"><Check /></el-icon>
</template>
</el-table-column>
<el-table-column label="执行" width="60" align="center" header-align="center">
<template #default="scope">
<el-icon v-if="scope.row.needExecute" color="#409EFF" :size="18"><Check /></el-icon>
</template>
</el-table-column>
<el-table-column label="金额" prop="amount" width="90" align="center" header-align="center">
<template #default="scope">
¥{{ formatAmount(scope.row.itemAmount) }}
</template>
</el-table-column>
<el-table-column label="操作" width="120" align="center" header-align="center">
<template #default="scope">
<el-row type="flex" align="middle" justify="center" :gutter="8">
<el-button link size="default" @click="handlePrint(scope.row)" :icon="Printer" title="打印" style="font-size: 16px"></el-button>
<el-button link size="default" @click="handleDelete(scope.row)" :icon="Delete" style="color: #f56c6c; font-size: 16px" title="删除"></el-button>
</el-row>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-row class="pagination-container" justify="center" style="margin-top: 10px">
<el-pagination
v-model:current-page="queryParams.pageNo"
v-model:page-size="queryParams.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="prev, pager, next, jumper, total"
:pager-count="5"
:hide-on-single-page="false"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-row>
</el-card>
</el-main>
<!-- 底部表单与项目选择区 -->
<el-main class="bottom-content-area">
<el-row :gutter="16">
<!-- 左侧申请单表单区65% -->
<el-col :span="15" class="form-area">
<el-card class="form-card" style="width: 100%">
<el-tabs v-model="leftActiveTab" class="form-tabs">
<el-tab-pane label="申请单" name="application">
<el-form class="application-form" :model="formData" label-width="auto">
<el-form-item label="申请单号" style="margin-bottom: 1px">
<el-input v-model="formData.applyNo" readonly size="small" />
</el-form-item>
<!-- 患者信息行 -->
<el-row :gutter="20" style="margin-bottom: 1px">
<el-col :span="8">
<el-form-item label="姓名" required>
<el-input v-model="formData.patientName" readonly size="small" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="就诊卡号" required>
<el-input v-model="formData.medicalrecordNumber" readonly size="small" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="费用性质" required>
<el-select v-model="formData.natureofCost" placeholder="请选择费用性质" size="small" style="width: 100%">
<el-option label="自费医疗" value="self" />
<el-option label="医保" value="medical" />
<el-option label="公费医疗" value="public" />
<el-option label="商业保险" value="commercial" />
<el-option label="其他" value="other" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 申请信息行 -->
<el-row :gutter="20" style="margin-bottom: 1px">
<!--申请日期-->
<el-col :span="8">
<el-form-item label="申请日期" required>
<el-date-picker
v-model="formData.applyTime"
type="datetime"
placeholder="选择日期时间"
size="small"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
</el-col>
<!--申请科室-->
<el-col :span="8">
<el-form-item label="申请科室" required>
<el-input v-model="formData.applyDepartment" readonly size="small" />
</el-form-item>
</el-col>
<!--申请医生-->
<el-col :span="8">
<el-form-item label="申请医生" required>
<el-input v-model="formData.applyDocName" readonly size="small" />
</el-form-item>
</el-col>
</el-row>
<!-- 执行科室 -->
<el-form-item
label="执行科室"
required
style="margin-bottom: 1px"
:class="{ 'form-item-error': validationErrors.executeDepartment }"
:error="validationErrors.executeDepartment ? '请选择执行科室' : ''"
>
<el-select
v-model="formData.executeDepartment"
placeholder="请选择执行科室"
size="small"
style="width: 100%"
:class="{ 'is-error': validationErrors.executeDepartment }"
>
<el-option label="医学检验科" value="medical_lab" />
<el-option label="放射科" value="radiology" />
<el-option label="超声科" value="ultrasound" />
<el-option label="病理科" value="pathology" />
<el-option label="核医学科" value="nuclear_medicine" />
</el-select>
</el-form-item>
<!-- 诊断描述 -->
<el-row :gutter="20" style="margin-bottom: 1px">
<el-col :span="12">
<el-form-item
label="诊断描述"
required
:class="{ 'form-item-error': validationErrors.clinicDesc }"
:error="validationErrors.clinicDesc ? '请输入诊断描述' : ''"
>
<el-input
v-model="formData.clinicDesc"
type="textarea"
:rows="2"
size="small"
:class="{ 'is-error': validationErrors.clinicDesc }"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="临床诊断"
required
:class="{ 'form-item-error': validationErrors.clinicDiag }"
:error="validationErrors.clinicDiag ? '请输入临床诊断' : ''"
>
<el-input
v-model="formData.clinicDiag"
type="textarea"
:rows="2"
size="small"
:class="{ 'is-error': validationErrors.clinicDiag }"
/>
</el-form-item>
</el-col>
</el-row>
<!-- 禁忌症病史摘要检验目的体格检查 -->
<el-row :gutter="20" style="margin-bottom: 1px">
<el-col :span="12">
<el-form-item label="禁忌症">
<el-input v-model="formData.contraindication" type="textarea" :rows="2" size="small" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="病史摘要"
required
:class="{ 'form-item-error': validationErrors.medicalHistorySummary }"
:error="validationErrors.medicalHistorySummary ? '请输入病史摘要' : ''"
>
<el-input
v-model="formData.medicalHistorySummary"
type="textarea"
:rows="2"
size="small"
:class="{ 'is-error': validationErrors.medicalHistorySummary }"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="检验目的"
required
:class="{ 'form-item-error': validationErrors.purposeofInspection }"
:error="validationErrors.purposeofInspection ? '请输入检验目的' : ''"
>
<el-input
v-model="formData.purposeofInspection"
type="textarea"
:rows="2"
size="small"
:class="{ 'is-error': validationErrors.purposeofInspection }"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="体格检查">
<el-input v-model="formData.physicalExam" type="textarea" :rows="2" size="small" />
</el-form-item>
</el-col>
</el-row>
<!-- 检验项目和备注 -->
<el-row :gutter="20" style="margin-bottom: 1px">
<el-col :span="12">
<el-form-item label="检验项目">
<el-input v-model="formData.inspectionItemsText" type="textarea" :rows="2" size="small" readonly />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备注">
<el-input v-model="formData.applyRemark" type="textarea" :rows="2" size="small" />
</el-form-item>
</el-col>
</el-row>
<!-- 状态复选框组 -->
<el-card style="margin-bottom: 16px; padding: 16px; background: #f8f9fa; border-radius: 4px; border: 1px solid #e9ecef" shadow="never">
<template #header>
<span style="font-weight: bold; color: #1a2b6d; font-size: 14px">
状态设置
</span>
</template>
<el-row type="flex" :gutter="16" wrap>
<el-col :xs="12" :sm="6" :md="6" :lg="6">
<!-- 🔧 Bug #268 修复只有急标记能编辑 -->
<el-checkbox v-model="formData.priorityCode" :true-value="1" :false-value="0"></el-checkbox>
</el-col>
<el-col :xs="12" :sm="6" :md="6" :lg="6">
<!-- 🔧 Bug #268 修复收费标记默认不勾选并不可编辑 -->
<el-checkbox v-model="formData.applyStatus" :true-value="1" :false-value="0" disabled>收费</el-checkbox>
</el-col>
<el-col :xs="12" :sm="6" :md="6" :lg="6">
<!-- 🔧 Bug #268 修复退费标记默认不勾选并不可编辑 -->
<el-checkbox v-model="formData.needRefund" :true-value="true" :false-value="false" disabled>退费</el-checkbox>
</el-col>
<el-col :xs="12" :sm="6" :md="6" :lg="6">
<!-- 🔧 Bug #268 修复执行标记默认不勾选并不可编辑 -->
<el-checkbox v-model="formData.needExecute" :true-value="true" :false-value="false" disabled>执行</el-checkbox>
</el-col>
</el-row>
</el-card>
</el-form>
</el-tab-pane>
<el-tab-pane label="检验信息" name="inspectionInfo">
<el-card style="padding: 20px; height: 700px; overflow-y: auto; border: 1px solid #e4e7ed; border-radius: 4px; margin: 10px; width: 100%">
<el-form :model="formData" label-width="100px" style="margin-bottom: 20px">
<el-row :gutter="15">
<el-col :span="12">
<el-form-item label="检验医生">
<el-input v-model="formData.inspectionDoctor" size="small" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="检验时间">
<el-date-picker
v-model="formData.inspectionTime"
type="datetime"
placeholder="选择时间"
size="small"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="审核医生">
<el-input v-model="formData.auditDoctor" size="small" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="审核时间">
<el-date-picker
v-model="formData.auditTime"
type="datetime"
placeholder="选择时间"
size="small"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- 检验信息详情表格 -->
<el-card style="margin-top: 20px; width: 100%" shadow="never">
<template #header>
<h4 style="margin: 0; font-weight: bold">检验信息详情</h4>
</template>
<el-table :data="selectedInspectionItems" border size="small" style="width: 100%; min-width: 100%" max-height="350">
<el-table-column label="项目名称" prop="itemName" width="200" />
<el-table-column label="样本类型" prop="sampleType" width="80" align="center" />
<el-table-column label="单位" prop="unit" width="60" align="center" />
<el-table-column label="总量" prop="itemQty" width="60" align="center">
<template #default="scope">
<span>{{ scope.row.itemQty || 1 }}</span>
</template>
</el-table-column>
<el-table-column label="单价" prop="itemPrice" width="70" align="right">
<template #default="scope">
¥{{ scope.row.itemPrice }}
</template>
</el-table-column>
<el-table-column label="金额" prop="itemAmount" width="70" align="right">
<template #default="scope">
¥{{ scope.row.itemAmount }}
</template>
</el-table-column>
<el-table-column label="服务费" width="70" align="right">
<template #default="scope">
¥{{ scope.row.serviceFee || 0 }}
</template>
</el-table-column>
<el-table-column label="类型" prop="type" width="60" align="center" />
<el-table-column label="自费" width="50" align="center">
<template #default="scope">
<el-checkbox :model-value="scope.row.isSelfPay" disabled />
</template>
</el-table-column>
</el-table>
</el-card>
</el-card>
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
<!-- 右侧项目选择区35% -->
<el-col :span="9" class="selection-area">
<!-- 检验项目选择区上部50% -->
<el-card class="inspection-selector" v-loading="inspectionLoading" element-loading-text="正在加载检验项目...">
<template #header>
<span class="card-title">检验项目选择</span>
</template>
<!-- 搜索框自动完成 -->
<el-autocomplete
v-model="searchKeyword"
:fetch-suggestions="querySearchInspectionItems"
placeholder="搜索检验项目..."
size="small"
clearable
prefix-icon="Search"
@select="handleSearchSelect"
@clear="handleSearchClear"
value-key="itemName"
class="search-input"
:debounce="300"
>
<template #default="{ item }">
<div class="suggestion-item">
<span class="suggestion-name">{{ item.itemName }}</span>
<el-tag size="small" type="info" class="suggestion-category">{{ item.typeName || '检验' }}</el-tag>
<span class="suggestion-price">¥{{ item.itemPrice }}</span>
</div>
</template>
</el-autocomplete>
<!-- 分类树 -->
<el-scrollbar
class="category-tree"
style="max-height: 280px"
@scroll="handleScroll"
>
<!-- 无数据提示 -->
<el-empty v-if="!inspectionLoading && inspectionCategories.length === 0" description="暂无检验项目数据" :image-size="80" />
<!-- 数据列表 -->
<div
v-for="category in inspectionCategories"
:key="category.key"
class="category-tree-item"
>
<div
:class="['category-tree-header', { active: activeCategory === category.key }]"
@click="switchCategory(category.key)"
>
<span class="category-tree-icon">{{ category.expanded ? '▼' : '▶' }}</span>
<span>{{ category.label }}</span>
<span class="category-count">({{ category.total || category.items.length }})</span>
<!-- 加载状态图标 -->
<el-icon v-if="category.loading" class="is-loading" style="margin-left: 8px; color: #409eff;">
<Loading />
</el-icon>
</div>
<div v-if="category.expanded" class="category-tree-children">
<!-- 加载中占位 -->
<div v-if="category.loading && category.items.length === 0" class="loading-placeholder">
<el-icon class="is-loading" style="margin-right: 8px;"><Loading /></el-icon>
<span>加载中...</span>
</div>
<!-- 项目列表 -->
<div
v-for="item in getFilteredItems(category.key)"
:key="item.itemId"
:class="['inspection-tree-item', { selected: isItemSelected(item) }]"
@click="handleItemClick(item)"
>
<el-checkbox
:model-value="isItemSelected(item)"
@change="toggleInspectionItem(item)"
@click.stop
/>
<span class="item-itemName">{{ item.itemName }}</span>
<span class="item-price">¥{{ item.itemPrice }}</span>
</div>
<!-- 加载更多 -->
<div v-if="category.hasMore && category.items.length > 0" class="load-more">
<el-button
link
size="small"
:loading="category.loading"
@click.stop="loadMoreItems(category.key)"
>
{{ category.loading ? '加载中...' : '加载更多' }}
</el-button>
<span class="load-info">(已加载 {{ category.items.length }}/{{ category.total }} )</span>
</div>
<!-- 加载完成提示 -->
<div v-if="!category.hasMore && category.items.length > 0" class="no-more">
已全部加载 ( {{ category.total }} )
</div>
</div>
</div>
</el-scrollbar>
</el-card>
<!-- 下部已选项目区 -->
<el-card class="selected-items-area">
<template #header>
<el-row class="selected-header" type="flex" justify="space-between" align="middle">
<span class="card-title">已选择</span>
<el-button link @click="clearAllSelected" type="danger" size="small">清空</el-button>
</el-row>
</template>
<!-- 已选项目列表 -->
<el-scrollbar class="selected-tree" style="max-height: 300px">
<el-list v-if="selectedInspectionItems.length > 0" :data="selectedInspectionItems" class="selected-items-list">
<el-list-item
v-for="item in selectedInspectionItems"
:key="item.itemId"
class="selected-list-item"
>
<el-row class="selected-item-content" type="flex" align="middle" style="width: 100%">
<span class="item-itemName">{{ item.itemName }}</span>
<span class="item-price">¥{{ item.itemPrice }}</span>
<el-button
link
size="small"
style="color: #f56c6c; margin-left: auto"
@click="removeInspectionItem(item)"
>
删除
</el-button>
</el-row>
</el-list-item>
</el-list>
<el-empty v-if="selectedInspectionItems.length === 0" class="no-selection" description="暂无选择项目" />
</el-scrollbar>
</el-card>
</el-col>
</el-row>
</el-main>
</el-container>
</template>
<script setup>
import {onMounted, reactive, ref, watch, computed, getCurrentInstance} from 'vue'
import {ElMessage, ElMessageBox} from 'element-plus'
import { DocumentChecked, Plus, Document, Printer, Delete, Check, Loading } from '@element-plus/icons-vue'
import {
checkInspectionApplicationNo,
deleteInspectionApplication, getApplyList,
saveInspectionApplication,
getInspectionTypeList,
getInspectionItemList,
getEncounterDiagnosis
} from '../api'
import useUserStore from '@/store/modules/user.js'
// 迁移到 hiprint
import { previewPrint } from '@/utils/printUtils.js'
import {storeToRefs} from 'pinia'
import { debounce } from 'lodash-es'
// 获取当前组件实例和字典
const { proxy } = getCurrentInstance()
const { activity_category_code } = proxy.useDict('activity_category_code')
// 获取"检验"分类的字典值(与检验项目设置维护保持一致)
const inspectionCategoryCode = computed(() => {
const dictList = activity_category_code.value
const inspectionItem = dictList?.find(item => item.label === '检验')
return inspectionItem?.value || '22' // 默认使用数据库中检验的 category_code 值
})
// Props
const props = defineProps({
patientInfo: {
type: Object,
required: true
},
activeTab: {
type: String
}
})
// Emits
const emit = defineEmits(['save'])
// 响应式数据
const loading = ref(false)
const saving = ref(false) // 保存状态
const total = ref(0)
const leftActiveTab = ref('application')
const isGeneratingNewApplyNo = ref(false) // 标志:是否正在生成新申请单号
// 用户信息store
const userStore = useUserStore()
const { id: userId, name: userName, nickName: userNickName } = storeToRefs(userStore)
// 修改 initData 函数
async function initData() {
// 先初始化患者信息(如果有)
if (props.patientInfo && props.patientInfo.encounterId) {
queryParams.encounterId = props.patientInfo.encounterId
formData.visitNo = props.patientInfo.busNo || ''
formData.patientId = props.patientInfo.patientId || ''
formData.patientName = props.patientInfo.patientName || ''
formData.medicalrecordNumber = props.patientInfo.identifierNo || ''
formData.applyDepartment = props.patientInfo.organizationName || ''
formData.applyDocName = userNickName.value || userName.value || ''
formData.applyDocCode = userId.value || ''
formData.specimenName = '血液'
formData.applyDeptCode = props.patientInfo.organizationName || ''
formData.applyOrganizationId = props.patientInfo.orgId || ''
formData.encounterId = props.patientInfo.encounterId
// 生成申请单号
generateApplicationNo().then((newApplyNo) => {
formData.applyNo = newApplyNo
})
// 获取主诊断信息
try {
const res = await getEncounterDiagnosis(props.patientInfo.encounterId)
if (res.code === 200 && res.data && res.data.length > 0) {
// 查找主诊断maindiseFlag === 1
const mainDiagnosis = res.data.find(item => item.maindiseFlag === 1)
if (mainDiagnosis) {
formData.clinicDiag = mainDiagnosis.name || ''
} else {
// 没有主诊断时清空临床诊断
formData.clinicDiag = ''
}
} else {
// 没有诊断数据时清空临床诊断
formData.clinicDiag = ''
}
} catch (error) {
console.error('获取主诊断信息失败:', error)
formData.clinicDiag = ''
}
}
}
// 查询参数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
encounterId: props.patientInfo.encounterId
})
// 检验申请单列表
const inspectionList = ref([])
// 表单数据
const formData = reactive({
applyOrganizationId: '',
applicationId: null,
applyNo: '',
patientId: '',
patientName: '',
medicalrecordNumber: '',
natureofCost: 'self',
applyTime: new Date(),
applyDepartment: '',
applyDocName: '',
executeDepartment: 'medical_lab',
clinicDesc: '',
contraindication: '',
clinicDiag: '',
medicalHistorySummary: '',
purposeofInspection: '',
physicalExam: '',
labApplyItemList: [],
inspectionItemsText: '',
applyRemark: '',
priorityCode: 0,
applyStatus: 1,
needRefund: false,
needExecute: false,
inspectionDoctor: '',
inspectionTime: null,
auditDoctor: '',
auditTime: null,
visitNo: '',
applyDocCode: '',
applyDeptCode: '',
specimenName: '血液',
encounterId: ''
})
// 表单引用
const formRef = ref()
// 表格引用
const inspectionTableRef = ref()
// 验证错误状态
const validationErrors = reactive({
executeDepartment: false,
clinicDesc: false,
clinicDiag: false,
medicalHistorySummary: false,
purposeofInspection: false,
labApplyItemList: false,
applyTime: false
})
// 已选择的表格行
const selectedRows = ref([])
// 已选择的检验项目
const selectedInspectionItems = ref([])
// 搜索关键词
const searchKeyword = ref('')
// 活动分类
const activeCategory = ref('')
// 检验项目分类动态从API获取支持懒加载和分页
const inspectionCategories = ref([])
// 检验项目加载状态(整体)
const inspectionLoading = ref(false)
// 每页加载条数
const PAGE_SIZE = 50
// 搜索防抖时间(毫秒)
const SEARCH_DEBOUNCE_TIME = 300
// 加载检验类型分类列表(只加载分类,项目懒加载)
async function loadInspectionData() {
// 如果已经加载过分类,直接返回
if (inspectionCategories.value.length > 0) {
return
}
inspectionLoading.value = true
try {
// 只获取检验类型列表
const typeRes = await getInspectionTypeList().catch(error => {
console.error('获取检验类型失败:', error)
return { data: [] }
})
const typeList = typeRes.data || []
// 创建分类结构,但不加载项目(懒加载)
const categories = typeList
.filter(type => type.validFlag === 1 || type.validFlag === undefined)
.map((type, index) => ({
key: type.code || `type_${index}`,
label: type.name || `分类${index + 1}`,
typeId: type.id, // 保存类型ID用于分页查询
expanded: index === 0, // 默认展开第一个
loaded: false, // 是否已加载项目
loading: false, // 是否正在加载
items: [], // 项目列表
pageNo: 1, // 当前页码
pageSize: PAGE_SIZE, // 每页条数
total: 0, // 总条数
hasMore: true // 是否还有更多数据
}))
if (categories.length > 0) {
inspectionCategories.value = categories
activeCategory.value = categories[0].key
// 预加载第一个分类的项目
await loadCategoryItems(categories[0].key)
} else {
console.warn('未获取到检验类型分类')
}
} catch (error) {
console.error('加载检验类型分类失败:', error)
} finally {
inspectionLoading.value = false
}
}
// 懒加载分类项目(分页)
async function loadCategoryItems(categoryKey, loadMore = false) {
const category = inspectionCategories.value.find(c => c.key === categoryKey)
if (!category) return
// 已加载完成且不是加载更多,或正在加载中,跳过
if ((category.loaded && !loadMore) || category.loading) return
// 没有更多数据了,跳过
if (loadMore && !category.hasMore) return
category.loading = true
try {
const params = {
pageNo: category.pageNo,
pageSize: category.pageSize,
categoryCode: inspectionCategoryCode.value,
searchKey: searchKeyword.value || ''
}
// 如果有类型ID添加筛选条件
if (category.typeId) {
params.inspectionTypeId = category.typeId
}
const res = await getInspectionItemList(params)
// 解析数据
let records = []
let total = 0
if (res.data && res.data.records) {
records = res.data.records
total = res.data.total || 0
} else if (res.data && Array.isArray(res.data)) {
records = res.data
total = records.length
} else if (Array.isArray(res)) {
records = res
total = records.length
}
// 映射数据格式
const mappedItems = records.map(item => ({
itemId: item.id || item.activityId || Math.random().toString(36).substring(2, 11),
itemName: item.name || item.itemName || '',
itemPrice: item.retailPrice || item.price || 0,
itemAmount: item.retailPrice || item.price || 0,
sampleType: item.specimenCode_dictText || item.sampleType || '血液',
unit: item.unit || '',
itemQty: 1,
serviceFee: 0,
type: category.label,
isSelfPay: false,
activityId: item.activityId,
code: item.busNo || item.code || item.activityCode,
inspectionTypeId: item.inspectionTypeId || null
}))
// 更新分类数据
if (loadMore) {
// 追加数据
category.items.push(...mappedItems)
} else {
// 首次加载
category.items = mappedItems
}
category.total = total
category.hasMore = category.items.length < total
category.loaded = true
} catch (error) {
console.error(`加载分类 [${category.label}] 项目失败:`, error)
// 加载失败时设置空数据
if (!loadMore) {
category.items = []
category.total = 0
category.hasMore = false
category.loaded = true
}
} finally {
category.loading = false
}
}
// 加载更多项目
function loadMoreItems(categoryKey) {
const category = inspectionCategories.value.find(c => c.key === categoryKey)
if (!category || !category.hasMore || category.loading) return
category.pageNo++
loadCategoryItems(categoryKey, true)
}
// 处理滚动事件(无限滚动)
function handleScroll({ scrollTop, scrollHeight, clientHeight }) {
// 距离底部 50px 时触发加载更多
if (scrollHeight - scrollTop - clientHeight < 50) {
const expandedCategory = inspectionCategories.value.find(c => c.expanded)
if (expandedCategory && expandedCategory.hasMore && !expandedCategory.loading) {
loadMoreItems(expandedCategory.key)
}
}
}
// 防抖搜索处理
const handleSearchDebounced = debounce(() => {
// 重新加载当前展开分类的数据
const expandedCategory = inspectionCategories.value.find(c => c.expanded)
if (expandedCategory) {
// 重置分页状态
expandedCategory.pageNo = 1
expandedCategory.loaded = false
expandedCategory.hasMore = true
expandedCategory.items = []
// 重新加载
loadCategoryItems(expandedCategory.key)
}
}, SEARCH_DEBOUNCE_TIME)
// 获取过滤后的项目(本地搜索)
const getFilteredItems = (categoryKey) => {
const category = inspectionCategories.value.find(cat => cat.key === categoryKey)
if (!category) return []
// 如果正在加载,返回现有数据
if (category.loading) {
return category.items
}
// 本地过滤(安全检查 itemName
if (searchKeyword.value) {
const keyword = searchKeyword.value.toLowerCase()
return category.items.filter(item =>
item.itemName && item.itemName.toLowerCase().includes(keyword)
)
}
return category.items
}
// 搜索建议查询(自动完成)
async function querySearchInspectionItems(queryString, cb) {
if (!queryString) {
cb([])
return
}
try {
const params = {
pageNo: 1,
pageSize: 20, // 限制返回数量
categoryCode: inspectionCategoryCode.value,
searchKey: queryString
}
const res = await getInspectionItemList(params)
let suggestions = []
if (res.data && res.data.records) {
// 映射数据格式,与 loadInspectionItemsByType 保持一致
suggestions = res.data.records.map(item => ({
itemId: item.id || item.activityId,
itemName: item.name || item.itemName || '',
itemPrice: item.retailPrice || item.price || 0,
sampleType: item.specimenCode_dictText || item.sampleType || '血液',
unit: item.unit || '',
code: item.busNo || item.code || item.activityCode,
activityId: item.activityId,
inspectionTypeId: item.inspectionTypeId || null
}))
}
cb(suggestions)
} catch (error) {
console.error('搜索检验项目失败:', error)
cb([])
}
}
// 搜索选择处理
function handleSearchSelect(item) {
// 直接添加到已选列表
if (!isItemSelected(item)) {
selectedInspectionItems.value.push({
...item,
itemName: item.itemName
})
}
// 清空搜索关键词
searchKeyword.value = ''
}
// 搜索框清空处理
function handleSearchClear() {
searchKeyword.value = ''
}
// 获取检验申请单列表
function getInspectionList() {
// 如果没有encounterId,不调用接口
if (!queryParams.encounterId) {
return
}
loading.value = true
getApplyList({
encounterId: queryParams.encounterId,
pageNo: queryParams.pageNo,
pageSize: queryParams.pageSize
}).then((res) => {
if (res.code === 200) {
// 处理分页响应数据
if (res.data && typeof res.data === 'object') {
// 如果返回的是分页对象 {records: [...], total: 100}
if (Array.isArray(res.data.records)) {
// 处理数据:将同一个申请单的多个明细合并成一条记录
inspectionList.value = mergeInspectionApplyRecords(res.data.records)
total.value = res.data.total || 0
}
// 如果返回的是普通数组
else if (Array.isArray(res.data)) {
// 处理数据:将同一个申请单的多个明细合并成一条记录
inspectionList.value = mergeInspectionApplyRecords(res.data)
total.value = res.data.length
}
// 如果返回的是其他对象结构
else {
inspectionList.value = []
total.value = 0
}
} else {
inspectionList.value = []
total.value = 0
}
} else {
inspectionList.value = []
total.value = 0
ElMessage.error(res.message || '获取检验申请单列表失败')
}
}).catch((error) => {
inspectionList.value = []
total.value = 0
ElMessage.error('获取检验申请单列表异常: ' + (error.message || ''))
}).finally(() => {
loading.value = false
})
}
// 合并检验申请单记录:将同一个申请单的多个明细合并成一条记录
function mergeInspectionApplyRecords(records) {
if (!records || records.length === 0) {
return []
}
// 使用Map按申请单号分组
const applyMap = new Map()
records.forEach(record => {
const applyNo = record.applyNo
if (applyMap.has(applyNo)) {
// 如果申请单已存在,合并检验项目
const existing = applyMap.get(applyNo)
existing.itemName = existing.itemName + '+' + record.itemName
// 累加金额,保留两位小数
const totalAmount = (parseFloat(existing.itemAmount) || 0) + (parseFloat(record.itemAmount) || 0)
existing.itemAmount = parseFloat(totalAmount.toFixed(2))
} else {
// 如果申请单不存在,直接添加
applyMap.set(applyNo, { ...record })
}
})
// 将Map转换为数组
return Array.from(applyMap.values())
}
// 格式化金额:确保显示两位小数
function formatAmount(amount) {
if (amount === null || amount === undefined || amount === '') {
return '0.00'
}
const num = parseFloat(amount)
if (isNaN(num)) {
return '0.00'
}
return num.toFixed(2)
}
// 新增申请单
async function handleNewApplication() {
resetForm()
// 生成新的申请单号
formData.applyNo = await generateApplicationNo()
// 确保申请医生是当前登录医生
formData.applyDocName = userNickName.value || userName.value || ''
leftActiveTab.value = 'application'
}
// 查询数据库中是否已存在指定申请单号
const checkApplicationNoExists = async (applyNo) => {
try {
// 这里调用API检查申请单号是否已存在
// 注意你需要根据实际API接口调整此调用
const response = await checkInspectionApplicationNo(applyNo);
// 后端返回格式:如果存在返回{applyNo: "..."}不存在返回null
return response.code === 200 && response.data && response.data.applyNo;
} catch (error) {
return false;
}
};
// 生成申请单号
const generateApplicationNo = async () => {
let applyNo;
let isUnique = false;
let attempts = 0;
const maxAttempts = 10; // 最大尝试次数
// 循环直到生成唯一的申请单号
while (!isUnique && attempts < maxAttempts) {
const now = new Date();
// 生成20位申请单号年(4位) + 月(2位) + 日(2位) + 时(2位) + 分(2位) + 秒(2位) + 毫秒(3位) + 随机数(3位)
const year = String(now.getFullYear()); // 取4位年份
const month = String(now.getMonth() + 1).padStart(2, '0'); // 月份从0开始需要+1
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
const milliseconds = String(now.getMilliseconds()).padStart(3, '0'); // 毫秒取3位
// 生成3位随机数增加唯一性避免并发冲突
const randomNum = Math.floor(Math.random() * 1000).toString().padStart(3, '0'); // 3位随机数
// 年(4)+月(2)+日(2)+时(2)+分(2)+秒(2)+毫秒(3)+随机数(3) = 20位
applyNo = `${year}${month}${day}${hours}${minutes}${seconds}${milliseconds}${randomNum}`;
// 确保生成的申请单号不为空
if (!applyNo || applyNo.trim() === '') {
console.debug('生成的申请单号为空,重新生成');
attempts++;
continue;
}
// 检查生成的单号是否已存在于数据库中
const existsInDatabase = await checkApplicationNoExists(applyNo);
// 只有当单号不在数据库中时,才是唯一的
isUnique = !existsInDatabase;
attempts++;
}
if (!isUnique) {
const timestamp = Date.now().toString();
const randomPart = Math.floor(Math.random() * 1000).toString().padStart(3, '0');
const fallbackNo = 'F' + timestamp.slice(-17) + randomPart; // 使用F开头表示备用方案
return fallbackNo;
}
return applyNo;
};
// 重置表单
async function resetForm() {
Object.assign(formData, {
applicationId: null,
applyOrganizationId: props.patientInfo.orgId || '',
patientName: props.patientInfo.patientName || '',
medicalrecordNumber: props.patientInfo.identifierNo || '',
natureofCost: 'self',
applyTime: new Date(),
applyDepartment: props.patientInfo.organizationName || '',
applyDeptCode: props.patientInfo.organizationName,
applyDocCode: userId.value || '',
applyDocName: userNickName.value || userName.value || '',
executeDepartment: 'medical_lab',
clinicDesc: '',
contraindication: '',
clinicDiag: '',
medicalHistorySummary: '',
purposeofInspection: '',
physicalExam: '',
labApplyItemList: [],
applyRemark: '',
priorityCode: 0,
applyStatus: 1,
needRefund: false,
needExecute: false,
patientId: props.patientInfo.patientId || '',
visitNo: '',
specimenName: '血液',
encounterId: props.patientInfo.encounterId || '',
})
selectedInspectionItems.value = []
// 重置验证错误状态
Object.keys(validationErrors).forEach(key => {
validationErrors[key] = false
})
formRef.value?.clearValidate()
// 获取主诊断信息
if (props.patientInfo && props.patientInfo.encounterId) {
try {
const res = await getEncounterDiagnosis(props.patientInfo.encounterId)
if (res.code === 200 && res.data && res.data.length > 0) {
const mainDiagnosis = res.data.find(item => item.maindiseFlag === 1)
if (mainDiagnosis) {
formData.clinicDiag = mainDiagnosis.name || ''
} else {
// 没有主诊断时清空临床诊断
formData.clinicDiag = ''
}
} else {
// 没有诊断数据时清空临床诊断
formData.clinicDiag = ''
}
} catch (error) {
console.error('获取主诊断信息失败:', error)
formData.clinicDiag = ''
}
}
}
// 保存
function handleSave() {
// 如果正在保存,直接返回
if (saving.value) return
// 重置验证错误状态
Object.keys(validationErrors).forEach(key => {
validationErrors[key] = false
})
let hasErrors = false
// 检查必填字段,执行科室
if (!formData.executeDepartment) {
validationErrors.executeDepartment = true
hasErrors = true
}
// 检查必填字段,诊断描述
if (!formData.clinicDesc?.trim()) {
validationErrors.clinicDesc = true
hasErrors = true
}
// 检查必填字段,临床诊断
if (!formData.clinicDiag?.trim()) {
validationErrors.clinicDiag = true
hasErrors = true
}
// 检查必填字段,病史摘要
if (!formData.medicalHistorySummary?.trim()) {
validationErrors.medicalHistorySummary = true
hasErrors = true
}
// 检查必填字段,检验目的
if (!formData.purposeofInspection?.trim()) {
validationErrors.purposeofInspection = true
hasErrors = true
}
// 检查必填字段,检验项目
if (selectedInspectionItems.value.length === 0) {
validationErrors.labApplyItemList = true
hasErrors = true
ElMessage.error('请至少选择一项检验项目')
return
}
// 检查必填字段,申请日期
if(!formData.applyTime || (typeof formData.applyTime === 'string' && !formData.applyTime?.trim())) {
validationErrors.applyTime = true
hasErrors = true
}
if (hasErrors) {
ElMessage.error('请填写所有必填字段')
return
}
// 准备保存数据
const prepareSaveData = () => {
return {
...formData,
labApplyItemList: selectedInspectionItems.value,
inspectionItemsText: selectedInspectionItems.value.map(item => item.itemName).join('+'),
amount: selectedInspectionItems.value.reduce((sum, item) => sum + item.itemAmount, 0),
serviceFee: selectedInspectionItems.value.reduce((sum, item) => sum + (item.serviceFee || 0), 0),
totalAmount: selectedInspectionItems.value.reduce((sum, item) => sum + item.itemAmount + (item.serviceFee || 0), 0)
}
}
// 先检查申请单号是否已存在
if (isGeneratingNewApplyNo.value) {
// 正在使用新生成的单号,直接保存
isGeneratingNewApplyNo.value = false; // 重置标志
const saveData = prepareSaveData();
executeSave(saveData);
return;
}
checkInspectionApplicationNo(formData.applyNo).then((res) => {
if (res.code === 200 && res.data) {
// res.data有值表示申请单存在
// 注意res.data可能是{applyNo: "..."}或包含其他字段
const exists = res.data && (res.data.applyNo || res.data.exists);
if (exists) {
// 申请单号已存在,提示用户
ElMessageBox.confirm(
'当前申请单号已重复,将重新为您生成新的申请单号',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => {
// 设置标志,表示正在生成新申请单号
isGeneratingNewApplyNo.value = true;
// 重新生成申请单号
generateApplicationNo().then((newApplyNo) => {
if (newApplyNo && newApplyNo.trim() !== '') {
formData.applyNo = newApplyNo;
ElMessage.success('新申请单号已生成,请点击保存按钮继续,请核对数据');
// 不自动保存,等待用户再次点击保存
} else {
console.debug('生成的申请单号为空,不设置');
isGeneratingNewApplyNo.value = false;
}
});
}).catch((error) => {
// 用户点击取消或其他原因导致的拒绝
if (error !== 'cancel' && error !== 'close') {
console.error('MessageBox操作出错:', error);
}
console.debug('用户取消了重新生成申请单号');
});
} else {
// 申请单号不存在,继续保存操作
const saveData = prepareSaveData();
executeSave(saveData);
}
} else {
// 其他情况继续保存
const saveData = prepareSaveData();
executeSave(saveData);
}
}).catch((error) => {
console.error('检查申请单号时发生错误:', error);
// 如果检查过程出错,仍然继续保存操作,以免影响正常使用
const saveData = prepareSaveData();
executeSave(saveData);
})
}
const executeSave = (saveData) => {
saving.value = true
saveInspectionApplication(saveData).then((res) => {
if (res.code === 200) {
ElMessage.success('保存成功')
emit('save', res.data) // 通知父组件保存成功
resetForm()
// 生成新的申请单号
generateApplicationNo().then((newApplyNo) => {
if (newApplyNo && newApplyNo.trim() !== '') {
formData.applyNo = newApplyNo;
} else {
console.debug('生成的申请单号为空,不设置');
}
});
leftActiveTab.value = 'application'
// 刷新列表
getInspectionList()
} else {
// 对于其他错误,也使用弹窗提示
ElMessageBox.alert(res.message || '保存失败', '错误', {
confirmButtonText: '确定',
type: 'error',
}).catch((error) => {
console.error('错误提示框操作出错:', error);
});
console.debug(res.message || '保存失败')
}
}).catch((error) => {
// 处理请求失败的其他错误
console.error('保存检验申请单时发生错误:', error);
ElMessage.error('保存失败,请稍后重试');
}).finally(() => {
saving.value = false
})
}
// 查看详情
function handleView(row) {
// 加载表单数据
Object.assign(formData, row)
// 根据检验项目名称找到对应的项目数据
selectedInspectionItems.value = []
const itemNames = row.itemName?.split('、') || row.inspectionItem?.split('、') || []
inspectionCategories.value.forEach(category => {
category.items.forEach(item => {
if (itemNames.includes(item.itemName)) {
selectedInspectionItems.value.push(item)
}
})
})
leftActiveTab.value = 'application'
}
// 切换分类(修改为懒加载)
function switchCategory(category) {
if (activeCategory.value === category) {
// 如果点击的是当前激活的分类,则收起
activeCategory.value = ''
inspectionCategories.value.forEach(cat => {
if (cat.key === category) {
cat.expanded = false
}
})
} else {
// 否则切换到新的分类并展开
activeCategory.value = category
inspectionCategories.value.forEach(cat => {
cat.expanded = cat.key === category
})
// 懒加载该分类的项目
const targetCategory = inspectionCategories.value.find(c => c.key === category)
if (targetCategory && !targetCategory.loaded) {
loadCategoryItems(category)
}
}
}
// 处理项目项点击(排除勾选框点击)
function handleItemClick(item) {
toggleInspectionItem(item)
}
// 判断项目是否已选择
function isItemSelected(item) {
return selectedInspectionItems.value.some(selected => selected.itemId === item.itemId)
}
// 切换检验项目选择
function toggleInspectionItem(item) {
const index = selectedInspectionItems.value.findIndex(selected => selected.itemId === item.itemId)
if (index > -1) {
selectedInspectionItems.value.splice(index, 1)
} else {
// 创建新对象,包含 itemName 属性(等于 name 属性)
const newItem = {
...item,
itemName: item.itemName
};
selectedInspectionItems.value.push(newItem);
}
}
// 移除检验项目
function removeInspectionItem(item) {
const index = selectedInspectionItems.value.findIndex(selected => selected.itemId === item.itemId)
if (index > -1) {
selectedInspectionItems.value.splice(index, 1)
}
}
// 清空所有选择
function clearAllSelected() {
selectedInspectionItems.value = []
}
// 分页大小改变
function handleSizeChange(size) {
queryParams.pageSize = size
getInspectionList()
}
// 分页页码改变
function handleCurrentChange(page) {
queryParams.pageNo = page
getInspectionList()
}
// 选择框变化
function handleSelectionChange(selection) {
selectedRows.value = selection
}
// 打印申请单
function handlePrint(row) {
// 切换到申请单TAB
leftActiveTab.value = 'application'
// 加载要打印的数据
handleView(row)
// 等待DOM更新后执行打印
setTimeout(() => {
// 使用 hiprint 的 previewPrint 方法
const printDom = document.querySelector('.application-form')
if (printDom) {
previewPrint(printDom)
ElMessage.success('正在准备打印...')
} else {
ElMessage.warning('未找到打印内容')
}
}, 100)
}
// 删除申请单
function handleDelete(row) {
ElMessageBox.confirm(
`确定要删除申请单 "${row.applyNo}" 吗?此操作将同时删除对应的医嘱。`,
'删除确认',
{
confirmButtonText: '确定删除',
cancelButtonText: '取消',
type: 'warning',
confirmButtonClass: 'el-button--danger'
}
).then(() => {
// 调用真实的 API 删除(传递 applyNo
// 调用真实的API删除
deleteInspectionApplication(row.applyNo).then((res) => {
if (res.code === 200) {
ElMessage.success('删除成功')
// 刷新列表
getInspectionList()
} else {
ElMessage.error(res.message || '删除失败')
}
}).catch((error) => {
console.error('删除检验申请单异常:', error)
ElMessage.error('删除异常')
})
}).catch(() => {
// 用户取消删除
})
}
// 单元格点击 - 点击表格行时加载申请单详情
function handleCellClick(row, column) {
// 点击表格行时,将该申请单的数据加载到表单中
if (row && row.applicationId) {
loadApplicationToForm(row);
}
}
// 🔧 Bug #269 修复:行点击事件处理
function handleRowClick(currentRow, oldRow) {
// 点击表格行时,将该申请单的数据加载到表单中
if (currentRow && currentRow.applicationId) {
loadApplicationToForm(currentRow);
}
}
// 🔧 Bug #269 修复:提取公共方法加载申请单到表单
function loadApplicationToForm(row) {
// 切换到申请单 TAB
leftActiveTab.value = 'application'
// 加载表单数据
Object.assign(formData, {
applicationId: row.applicationId,
applyNo: row.applyNo,
patientName: row.patientName,
medicalrecordNumber: row.medicalrecordNumber,
natureofCost: row.natureofCost || 'self',
applyTime: row.applyTime,
applyDepartment: row.applyDepartment,
applyDocName: row.applyDocName,
executeDepartment: row.executeDepartment || 'medical_lab',
clinicDesc: row.clinicDesc,
contraindication: row.contraindication,
clinicDiag: row.clinicDiag,
medicalHistorySummary: row.medicalHistorySummary,
purposeofInspection: row.purposeofInspection,
physicalExam: row.physicalExam,
applyRemark: row.applyRemark,
priorityCode: row.priorityCode || 0,
applyStatus: row.applyStatus || 0, // 🔧 Bug #268: 默认为 0
needRefund: row.needRefund || false,
needExecute: row.needExecute || false,
inspectionDoctor: row.inspectionDoctor,
inspectionTime: row.inspectionTime,
auditDoctor: row.auditDoctor,
auditTime: row.auditTime,
visitNo: row.visitNo,
applyDocCode: row.applyDocCode,
applyDeptCode: row.applyDeptCode,
specimenName: row.specimenName,
encounterId: row.encounterId,
patientId: row.patientId,
applyOrganizationId: row.applyOrganizationId
})
// 🔧 Bug #269 修复:根据检验项目 ID 加载详细的检验项目数据
selectedInspectionItems.value = []
if (row.labApplyItemList && row.labApplyItemList.length > 0) {
// 如果后端返回了检验项目列表,直接使用
selectedInspectionItems.value = row.labApplyItemList.map(item => ({
...item,
itemId: item.itemId || item.id || Math.random().toString(36).substring(2, 11),
itemName: item.itemName || item.name || '',
itemPrice: item.itemPrice || item.price || 0,
itemAmount: item.itemAmount || item.price || 0,
}))
} else if (row.itemName || row.inspectionItem) {
// 如果只有项目名称,尝试从本地分类中查找匹配项
const itemNames = (row.itemName || row.inspectionItem).split(/[+,]/) // 支持多种分隔符
inspectionCategories.value.forEach(category => {
category.items.forEach(item => {
if (itemNames.includes(item.itemName)) {
selectedInspectionItems.value.push({ ...item })
}
})
})
}
// 重置验证错误状态
Object.keys(validationErrors).forEach(key => {
validationErrors[key] = false
})
console.log('已加载申请单到表单:', row.applyNo);
}
// 监听activeTab变化
watch(() => props.activeTab, async (newVal) => {
if (newVal === 'inspection') {
await initData()
// 根据动态加载的分类设置默认展开
if (inspectionCategories.value.length > 0) {
// 展开第一个分类
activeCategory.value = inspectionCategories.value[0].key
inspectionCategories.value.forEach((cat, index) => {
cat.expanded = index === 0
})
}
}
})
// 监听patientInfo变化,确保encounterId及时更新并重新加载数据
watch(() => props.patientInfo, async (newVal) => {
if (newVal && newVal.encounterId) {
const oldEncounterId = queryParams.encounterId
queryParams.encounterId = newVal.encounterId
// 初始化数据
await initData();
// 如果encounterId发生变化重新加载检验申请单列表
if (oldEncounterId !== newVal.encounterId) {
getInspectionList()
}
// 更新科室编码
// const currentDeptCode = await getCurrentDeptCode();
// formData.applyDeptCode = currentDeptCode || '';
}
}, { deep: true, immediate: true })
// 监听已选择的检验项目,自动更新检验项目文本(用+号拼接)
watch(() => selectedInspectionItems.value, (newVal) => {
if (newVal && newVal.length > 0) {
formData.inspectionItemsText = newVal.map(item => item.itemName).join('+')
} else {
formData.inspectionItemsText = ''
}
}, { deep: true })
// 组件挂载时预加载检验项目数据不依赖patientInfo
onMounted(async () => {
await loadInspectionData()
})
// 暴露方法
defineExpose({
getList: getInspectionList
})
</script>
<style lang="scss" scoped>
/* 页面容器 */
.inspection-application-container {
max-height: 750px;
overflow-y: auto;
padding: 0;
}
/* 顶部操作按钮区 */
.top-action-bar {
display: flex;
align-items: center;
justify-content: flex-end;
border-bottom: 1px solid var(--el-border-color-light);
background: var(--el-bg-color);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.action-buttons {
display: flex;
gap: 10px;
}
/* 检验信息表格区 */
.inspection-section {
padding: 20px;
}
.table-card {
height: 300px;
display: flex;
flex-direction: column;
}
.card-header {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
color: var(--el-text-color-primary);
}
/* 底部内容区域 */
.bottom-content-area {
padding: 20px;
}
/* 表单区域 */
.form-card {
height: 700px;
}
.form-tabs {
height: 100%;
}
.application-form {
height: 650px;
overflow-y: auto;
padding: 20px;
border: 1px solid #e4e7ed;
border-radius: 4px;
margin: 10px;
}
/* 选择区域 */
.selection-area {
display: flex;
flex-direction: column;
gap: 20px;
}
.inspection-selector,
.selected-items-area {
height: 350px;
}
.card-title {
font-weight: 600;
color: var(--el-text-color-primary);
}
.search-input {
margin-bottom: 15px;
}
.selected-header {
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
.pagination-container {
display: flex;
justify-content: center;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid var(--el-border-color-light);
}
:deep(.el-pagination) {
.el-pager li {
border-radius: 4px;
margin: 0 2px;
min-width: 32px;
height: 32px;
line-height: 30px;
border: 1px solid #dcdfe6;
background-color: #fff;
color: #606266;
transition: all 0.3s ease;
}
.el-pager li:hover {
border-color: #409eff;
color: #409eff;
}
.el-pager li.is-active {
border-color: #409eff;
background-color: #409eff;
color: #fff;
}
.el-pagination__jump {
margin-left: 10px;
}
}
.inspection-form {
background: white;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.form-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #eee;
padding: 15px 20px;
margin-bottom: 15px;
}
.form-header .title {
display: flex;
align-items: center;
font-size: 16px;
font-weight: bold;
color: #1a2b6d;
}
.form-header .title i {
margin-right: 10px;
font-size: 24px;
}
.inspection-items-section {
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 15px;
}
.selected-items {
margin-bottom: 20px;
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
font-weight: bold;
}
.selected-list {
min-height: 40px;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 10px;
}
.item-selector {
margin-top: 20px;
}
.category-tabs {
display: flex;
border-bottom: 1px solid #ebeef5;
margin-bottom: 15px;
}
.category-tab {
padding: 10px 20px;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 0.3s ease;
}
.category-tab:hover {
color: #409eff;
}
.category-tab.active {
color: #409eff;
border-bottom-color: #409eff;
font-weight: bold;
}
.items-list {
max-height: 300px;
overflow-y: auto;
}
.inspection-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
margin-bottom: 5px;
border: 1px solid #ebeef5;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
}
.inspection-item:hover {
border-color: #409eff;
background-color: #ecf5ff;
}
.inspection-item.selected {
border-color: #409eff;
background-color: #ecf5ff;
}
.item-itemName {
font-weight: 500;
}
.item-price {
color: #e6a23c;
font-weight: bold;
}
.inspection-details {
margin-top: 30px;
padding: 20px;
border: 1px solid #ebeef5;
border-radius: 4px;
}
.details-header {
margin-bottom: 15px;
}
.details-header h3 {
margin: 0;
color: #1a2b6d;
font-size: 16px;
font-weight: bold;
}
.inspection-selector {
border: 1px solid #ddd;
border-radius: 4px;
padding: 15px;
background: white;
}
.category-list {
margin-bottom: 15px;
}
.category-item {
display: flex;
align-items: center;
padding: 8px 12px;
cursor: pointer;
border-radius: 4px;
transition: all 0.3s ease;
margin-bottom: 2px;
}
.category-item:hover {
background-color: #f5f5f5;
}
.category-item.active {
background-color: #e6f7ff;
color: #409eff;
font-weight: bold;
}
.category-icon {
margin-right: 8px;
font-weight: bold;
width: 12px;
text-align: center;
}
.selected-summary {
margin: 10px 0;
padding: 10px;
background: #f5f5f5;
border-radius: 4px;
}
.selected-items {
margin-top: 5px;
}
.inspection-selector .items-list {
max-height: 200px;
overflow-y: auto;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 10px;
}
.inspection-selector .inspection-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
margin-bottom: 5px;
border: 1px solid #f0f0f0;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
background: #fafafa;
}
.inspection-selector .inspection-item:hover {
border-color: #409eff;
background-color: #ecf5ff;
}
.inspection-selector .inspection-item.selected {
border-color: #409eff;
background-color: #ecf5ff;
font-weight: bold;
}
/* 检验信息表格样式 */
:deep(.inspection-table) {
border: 1px solid #e4e7ed;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
}
:deep(.inspection-table .el-table__header) {
th {
background: linear-gradient(to bottom, #f8fafc 0%, #f1f5f9 100%);
border-bottom: 2px solid #e2e8f0;
color: #1e293b;
font-weight: 600;
font-size: 13px;
padding: 12px 8px;
}
}
:deep(.inspection-table .el-table__body) {
td {
border-bottom: 1px solid #f1f5f9;
padding: 10px 8px;
font-size: 13px;
color: #475569;
}
}
:deep(.inspection-table .el-table__row:hover > td) {
background-color: #f8fafc !important;
}
:deep(.inspection-table .el-table__row--striped) {
background-color: #fafbfc;
}
:deep(.inspection-table .el-table__row--striped:hover > td) {
background-color: #f1f5f9 !important;
}
:deep(.inspection-table .el-checkbox) {
margin: 0;
}
:deep(.inspection-table .el-button--small) {
font-size: 12px;
padding: 4px 8px;
}
:deep(.inspection-table .el-button--link) {
color: #409eff;
transition: color 0.3s ease;
}
:deep(.inspection-table .el-button--link:hover) {
color: #66b1ff;
}
:deep(.inspection-table .el-button--link:last-child) {
color: #f56c6c;
}
:deep(.inspection-table .el-button--link:last-child:hover) {
color: #f78989;
}
:deep(.inspection-table .cell) {
line-height: 1.4;
}
/* 新的树形结构样式 */
.category-tree {
border: 1px solid #ebeef5;
border-radius: 4px;
background: #fafafa;
max-height: 280px;
overflow-y: auto;
}
.category-tree-item {
border-bottom: 1px solid #ebeef5;
}
.category-tree-item:last-child {
border-bottom: none;
}
.category-tree-header {
display: flex;
align-items: center;
padding: 12px 15px;
cursor: pointer;
transition: all 0.3s ease;
background: #fff;
border-radius: 4px;
margin: 2px;
}
.category-tree-header:hover {
background-color: #f5f5f5;
}
.category-tree-header.active {
background-color: #e6f7ff;
color: #409eff;
font-weight: bold;
}
.category-tree-icon {
margin-right: 8px;
font-size: 12px;
width: 12px;
text-align: center;
color: #409eff;
}
.category-count {
margin-left: auto;
color: #999;
font-size: 12px;
}
.category-tree-children {
background: #fff;
border-top: 1px solid #ebeef5;
}
/* 加载中占位样式 */
.loading-placeholder {
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
color: #909399;
font-size: 14px;
}
/* 加载更多样式 */
.load-more {
display: flex;
align-items: center;
justify-content: center;
padding: 10px;
border-top: 1px dashed #ebeef5;
}
.load-info {
margin-left: 8px;
font-size: 12px;
color: #909399;
}
/* 没有更多数据提示 */
.no-more {
text-align: center;
padding: 10px;
font-size: 12px;
color: #c0c4cc;
}
.inspection-tree-item {
display: flex;
align-items: center;
padding: 8px 15px 8px 35px;
cursor: pointer;
transition: all 0.3s ease;
border-bottom: 1px solid #f0f0f0;
}
.inspection-tree-item:last-child {
border-bottom: none;
}
.inspection-tree-item:hover {
background-color: #f5f5f5;
}
.inspection-tree-item.selected {
background-color: #ecf5ff;
border-left: 3px solid #409eff;
}
.inspection-tree-item .item-itemName {
flex: 1;
margin-left: 10px;
font-weight: 500;
}
.inspection-tree-item .item-price {
color: #e6a23c;
font-weight: bold;
margin-left: 10px;
}
/* 已选项目区样式 */
.selected-items-area {
display: flex;
flex-direction: column;
}
.selected-tree {
flex: 1;
}
.selected-tree-item {
padding: 8px 0;
border-bottom: 1px solid #f0f0f0;
}
.selected-tree-item:last-child {
border-bottom: none;
}
.selected-item-content {
display: flex;
align-items: center;
padding: 8px 12px;
background: #f8f9fa;
border-radius: 4px;
margin: 2px 0;
}
.selected-item-content .item-itemName {
flex: 1;
font-weight: 500;
}
.selected-item-content .item-price {
color: #e6a23c;
font-weight: bold;
margin-right: 10px;
}
.no-selection {
text-align: center;
padding: 40px 20px;
color: #999;
}
/* 表单验证错误样式 */
.form-item-error .el-input__inner,
.form-item-error .el-textarea__inner {
border-color: #f56c6c !important;
}
.form-item-error .el-select .el-input__inner {
border-color: #f56c6c !important;
}
.is-error.el-input .el-input__inner,
.is-error.el-textarea .el-textarea__inner {
border-color: #f56c6c !important;
}
.error-message {
color: #f56c6c;
font-size: 12px;
margin-top: 4px;
line-height: 1;
}
/* 自定义消息框样式,确保其显示在最顶层 */
.custom-message-box {
z-index: 9999 !important;
}
/* 确保模态框相关元素具有高z-index */
.el-popup-parent--hidden {
overflow: hidden;
padding-right: 0 !important;
}
.v-modal {
z-index: 2000 !important;
}
/* 响应式布局 */
@media (max-width: 992px) {
.bottom-content-area {
flex-direction: column;
}
.form-area,
.selection-area {
width: 100%;
}
.el-col {
width: 100% !important;
max-width: 100% !important;
flex: 0 0 100% !important;
}
}
@media (max-width: 768px) {
.inspection-section {
padding: 10px;
}
.bottom-content-area {
padding: 0 10px 10px 10px;
gap: 15px;
}
/* 表单字段纵向排列 */
.application-form .el-form-item {
margin-bottom: 15px;
}
/* 隐藏表格非关键列 */
.inspection-table .el-table__cell:nth-child(n+4):nth-child(-n+7) {
display: none;
}
.top-action-bar {
padding: 0 10px;
}
.action-buttons {
flex-direction: column;
gap: 5px;
}
.el-button--large {
width: 100%;
}
}
/* 优化搜索框样式 */
.inspection-selector .el-input {
margin-bottom: 15px;
}
</style>