Files
his/openhis-ui-vue3/src/views/doctorstation/components/inspection/inspectionApplication.vue
chenqi 4f0cc1a0c4 refactor(ui): 优化按钮样式和数据加载逻辑
- 将多个按钮组件从 type="text" 改为 link 属性,提升界面美观性
- 修复 PatientList 组件中姓名显示的文本截断功能
- 在住院记录模板中添加对 patientInfo 变化的监听,自动更新表单数据
- 优化打印机列表获取逻辑,添加连接状态检查和警告信息
- 移除不必要的防抖和重复请求防护逻辑,简化代码实现
- 修复多处组件中对 patientInfo 属性访问的安全性问题
- 优化病历数据加载时机,移除防抖包装直接调用加载函数
- 改进数据设置逻辑,避免覆盖未传入字段的原有值
- 调整组件属性定义,使 patientInfo 参数变为可选并设置默认值
- 优化患者切换时的组件重置和数据加载流程
2026-01-27 17:32:03 +08:00

1505 lines
45 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 style="max-height: 750px; overflow-y: auto">
<!-- 顶部检验信息表格 -->
<div class="inspection-section" style="margin-bottom: 20px">
<div class="section-header">
<div class="title">
<i class="el-icon-document-checked"></i>
<span>检验信息</span>
</div>
<div class="actions">
<el-button type="primary" @click="handleNewApplication" plain>
新增
</el-button>
<el-button type="primary" @click="handleSave" plain>
保存
</el-button>
</div>
</div>
<!--检验信息-->
<el-table
ref="inspectionTableRef"
:data="inspectionList"
border
style="width: 100%"
max-height="300"
@selection-change="handleSelectionChange"
@cell-click="handleCellClick"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="申请ID" prop="applicationId" width="90" align="center" />
<el-table-column label="申请单号" prop="applyNo" width="140" align="center" />
<el-table-column label="检验项目" prop="inspectionItem" width="150" align="center" />
<el-table-column label="申请医生" prop="applyDocName" width="120" align="center" />
<el-table-column label="急" width="60" align="center">
<template #default="scope">
<el-checkbox :model-value="scope.row.priorityCode" disabled />
</template>
</el-table-column>
<el-table-column label="收费" width="80" align="center">
<template #default="scope">
<el-checkbox :model-value="scope.row.applyStatus" disabled />
</template>
</el-table-column>
<el-table-column label="退费" width="80" align="center">
<template #default="scope">
<el-checkbox :model-value="scope.row.needRefund" disabled />
</template>
</el-table-column>
<el-table-column label="执行" width="80" align="center">
<template #default="scope">
<el-checkbox :model-value="scope.row.needExecute" disabled />
</template>
</el-table-column>
<el-table-column label="金额" prop="amount" width="90" align="right">
<template #default="scope">
¥{{ scope.row.amount || '0.00' }}
</template>
</el-table-column>
<el-table-column label="操作" width="120" align="center">
<template #default="scope">
<el-button link size="small" @click="handlePrint(scope.row)">打印</el-button>
<el-button link size="small" style="color: #f56c6c" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container" style="text-align: center; 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="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
<!-- 下方申请单和检验项目选择器 -->
<div style="display: flex; gap: 20px">
<!-- 左侧申请单和检验信息 -->
<div style="width: 55%">
<el-tabs v-model="leftActiveTab" style="width: 100%">
<el-tab-pane label="申请单" name="application">
<div class="application-form" style="padding: 20px; height: 700px; overflow-y: auto; border: 1px solid #e4e7ed; border-radius: 4px; margin: 10px;">
<div style="margin-bottom: 20px">
<label style="display: block; margin-bottom: 5px; font-weight: bold">申请单号</label>
<el-input v-model="formData.applyNo" readonly size="small" />
</div>
<!-- 患者信息行 -->
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px; margin-bottom: 20px">
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">姓名<span style="color: #f56c6c">*</span></label>
<el-input v-model="formData.patientName" readonly size="small" />
</div>
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">就诊卡号<span style="color: #f56c6c">*</span></label>
<el-input v-model="formData.medicalrecordNumber" readonly size="small" />
</div>
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">费用性质<span style="color: #f56c6c">*</span></label>
<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>
</div>
</div>
<!-- 申请信息行 -->
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px; margin-bottom: 20px">
<!--申请日期-->
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">申请日期<span style="color: #f56c6c">*</span></label>
<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%"
/>
</div>
<!--申请科室-->
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">申请科室<span style="color: #f56c6c">*</span></label>
<el-input v-model="formData.applyDepartment" readonly size="small" />
</div>
<!--申请医生-->
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">申请医生<span style="color: #f56c6c">*</span></label>
<el-input v-model="formData.applyDocName" readonly size="small" />
</div>
</div>
<!-- 执行科室 -->
<div style="margin-bottom: 20px" :class="{ 'form-item-error': validationErrors.executeDepartment }">
<label style="display: block; margin-bottom: 5px; font-weight: bold">执行科室 <span style="color: #f56c6c">*</span></label>
<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>
<div v-if="validationErrors.executeDepartment" class="error-message">请选择执行科室</div>
</div>
<!-- 诊断描述 -->
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 20px">
<div :class="{ 'form-item-error': validationErrors.clinicDesc }">
<label style="display: block; margin-bottom: 5px; font-weight: bold">诊断描述 <span style="color: #f56c6c">*</span></label>
<el-input
v-model="formData.clinicDesc"
type="textarea"
:rows="2"
size="small"
:class="{ 'is-error': validationErrors.clinicDesc }"
/>
<div v-if="validationErrors.clinicDesc" class="error-message">请输入诊断描述</div>
</div>
<div :class="{ 'form-item-error': validationErrors.clinicDiag }">
<label style="display: block; margin-bottom: 5px; font-weight: bold">临床诊断 <span style="color: #f56c6c">*</span></label>
<el-input
v-model="formData.clinicDiag"
type="textarea"
:rows="2"
size="small"
:class="{ 'is-error': validationErrors.clinicDiag }"
/>
<div v-if="validationErrors.clinicDiag" class="error-message">请输入临床诊断</div>
</div>
</div>
<!-- 禁忌症病史摘要检验目的体格检查 -->
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 20px">
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">禁忌症</label>
<el-input v-model="formData.contraindication" type="textarea" :rows="2" size="small" />
</div>
<div :class="{ 'form-item-error': validationErrors.medicalHistorySummary }">
<label style="display: block; margin-bottom: 5px; font-weight: bold">病史摘要 <span style="color: #f56c6c">*</span></label>
<el-input
v-model="formData.medicalHistorySummary"
type="textarea"
:rows="2"
size="small"
:class="{ 'is-error': validationErrors.medicalHistorySummary }"
/>
<div v-if="validationErrors.medicalHistorySummary" class="error-message">请输入病史摘要</div>
</div>
<div :class="{ 'form-item-error': validationErrors.purposeofInspection }">
<label style="display: block; margin-bottom: 5px; font-weight: bold">检验目的 <span style="color: #f56c6c">*</span></label>
<el-input
v-model="formData.purposeofInspection"
type="textarea"
:rows="2"
size="small"
:class="{ 'is-error': validationErrors.purposeofInspection }"
/>
<div v-if="validationErrors.purposeofInspection" class="error-message">请输入检验目的</div>
</div>
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">体格检查</label>
<el-input v-model="formData.physicalExam" type="textarea" :rows="2" size="small" />
</div>
</div>
<!-- 检验项目和备注 -->
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 20px">
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">检验项目</label>
<el-input v-model="formData.inspectionItemsText" type="textarea" :rows="2" size="small" readonly />
</div>
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">备注</label>
<el-input v-model="formData.applyRemark" type="textarea" :rows="2" size="small" />
</div>
</div>
<!-- 状态复选框组 -->
<div style="margin-bottom: 20px; padding: 15px; background: #f8f9fa; border-radius: 4px; border: 1px solid #e9ecef">
<label style="display: block; margin-bottom: 10px; font-weight: bold; color: #1a2b6d">状态设置</label>
<div style="display: flex; gap: 30px; flex-wrap: wrap">
<el-checkbox v-model="formData.priorityCode" :true-value="1" :false-value="0"></el-checkbox>
<el-checkbox v-model="formData.applyStatus" :true-value="1" :false-value="0">收费</el-checkbox>
<el-checkbox v-model="formData.needRefund">退费</el-checkbox>
<el-checkbox v-model="formData.needExecute">执行</el-checkbox>
</div>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="检验信息" name="inspectionInfo">
<div style="padding: 20px; height: 700px; overflow-y: auto; border: 1px solid #e4e7ed; border-radius: 4px; margin: 10px;">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 20px">
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">检验医生</label>
<el-input v-model="formData.inspectionDoctor" size="small" />
</div>
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">检验时间</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%"
/>
</div>
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">审核医生</label>
<el-input v-model="formData.auditDoctor" size="small" />
</div>
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">审核时间</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%"
/>
</div>
</div>
<!-- 检验信息详情表格 -->
<div style="margin-top: 20px">
<h4 style="margin-bottom: 10px; font-weight: bold">检验信息详情</h4>
<el-table :data="selectedInspectionItems" border size="small" style="width: 100%" max-height="300">
<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>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
<!-- 右侧检验项目选择和已选项目 -->
<div style="width: 42%; display: flex; flex-direction: column; gap: 20px">
<!-- 上部检验项目选择区 -->
<div class="inspection-selector" style="height: 350px; border: 1px solid #ddd; border-radius: 4px; padding: 15px; background: white;">
<h4 style="margin-bottom: 10px; font-weight: bold">检验项目选择</h4>
<!-- 搜索框 -->
<div style="margin-bottom: 15px">
<el-input
v-model="searchKeyword"
placeholder="搜索检验项目..."
size="small"
clearable
prefix-icon="Search"
@input="handleSearch"
/>
</div>
<!-- 分类树 -->
<div class="category-tree" style="max-height: 250px; overflow-y: auto">
<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.items.length }})</span>
</div>
<div v-if="category.expanded" class="category-tree-children">
<div
v-for="item in getFilteredItems(category.key)"
:key="item.id"
: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>
</div>
</div>
</div>
<!-- 下部已选项目区 -->
<div class="selected-items-area" style="height: 350px; border: 1px solid #ddd; border-radius: 4px; padding: 15px; background: white;">
<!-- 标题栏 -->
<div class="selected-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #eee;">
<span style="font-weight: bold; color: #1a2b6d">已选择</span>
<el-button link @click="clearAllSelected" style="color: #f56c6c">清空</el-button>
</div>
<!-- 已选项目列表 -->
<div class="selected-tree" style="max-height: 270px; overflow-y: auto">
<div
v-for="item in selectedInspectionItems"
:key="item.id"
class="selected-tree-item"
>
<div class="selected-item-content">
<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>
</div>
</div>
<div v-if="selectedInspectionItems.length === 0" class="no-selection">
<span style="color: #999">暂无选择项目</span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import {onMounted, reactive, ref, watch} from 'vue'
import {ElMessage, ElMessageBox} from 'element-plus'
import {deleteInspectionApplication, getInspectionApplicationList, saveInspectionApplication} from '../api'
import useUserStore from '@/store/modules/user.js'
import {storeToRefs} from 'pinia'
// 在 onMounted 中调用初始化函数
onMounted(async () => {
await initData();
})
// Props
const props = defineProps({
patientInfo: {
type: Object,
required: true
},
activeTab: {
type: String
}
})
// Emits
const emit = defineEmits(['save'])
// 响应式数据
const showForm = ref(false)
const loading = ref(false)
const total = ref(0)
const leftActiveTab = ref('application')
// 用户信息store
const userStore = useUserStore()
const { id: userId, name: userName, nickName: userNickName } = storeToRefs(userStore)
// 修改 initData 函数
async function initData() {
// 然后执行原有的初始化逻辑
if (props.patientInfo) {
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.operatorId = userId.value || ''
formData.applyDocName = userNickName.value || userName.value || ''
formData.applyDocCode = userId.value || ''
formData.specimenName = '血液'
}
// 只有在存在 encounterId 时才调用接口
if (queryParams.encounterId) {
getInspectionList()
}
}
// 查询参数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
encounterId: ''
})
// 检验申请单列表
const inspectionList = ref([])
// 表单数据
const formData = reactive({
applicationId: null,
applyNo: '202511210001',
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: '',
operatorId: '',
specimenName: '血液',
})
// 表单验证规则
const formRules = {
natureofCost: [{ required: true, message: '请选择费用性质', trigger: 'change' }],
applyTime: [{ required: true, message: '请选择申请日期', trigger: 'change' }],
executeDepartment: [{ required: true, message: '请选择执行科室', trigger: 'change' }],
clinicDesc: [{ required: true, message: '请输入诊断描述', trigger: 'blur' }],
clinicDiag: [{ required: true, message: '请输入临床诊断', trigger: 'blur' }],
medicalHistorySummary: [{ required: true, message: '请输入病史摘要', trigger: 'blur' }],
purposeofInspection: [{ required: true, message: '请输入检验目的', trigger: 'blur' }],
labApplyItemList: [{ required: true, message: '请至少选择一个检验项目', trigger: 'change' }]
}
// 表单引用
const formRef = ref()
// 表格引用
const inspectionTableRef = ref()
// 验证错误状态
const validationErrors = reactive({
executeDepartment: false,
clinicDesc: false,
clinicDiag: false,
medicalHistorySummary: false,
purposeofInspection: false,
labApplyItemList: false
})
// 已选择的表格行
const selectedRows = ref([])
// 已选择的检验项目
const selectedInspectionItems = ref([])
// 搜索关键词
const searchKeyword = ref('')
// 活动分类
const activeCategory = ref('biochemical')
// 检验项目分类(树形结构)
let inspectionCategories = ref([
{
key: 'biochemical',
label: '生化',
expanded: true,
items: [
{ id: 1, itemName: '肝功能', itemPrice: 31, itemAmount: 31, sampleType: '血清', unit: 'U/L', itemQty: 1, serviceFee: 0, type: '生化', isSelfPay: false },
{ id: 2, itemName: '肾功能', itemPrice: 28, itemAmount: 28, sampleType: '血清', unit: 'U/L', itemQty: 1, serviceFee: 0, type: '生化', isSelfPay: false },
{ id: 3, itemName: '血糖', itemPrice: 15, itemAmount: 15, sampleType: '血清', unit: 'mmol/L', itemQty: 1, serviceFee: 0, type: '生化', isSelfPay: false }
]
},
{
key: 'blood',
label: '临检',
expanded: false,
items: [
{ id: 4, itemName: '血常规+crp', itemPrice: 50, itemAmount: 50, sampleType: '全血', unit: '×10^9/L', itemQty: 1, serviceFee: 0, type: '血液', isSelfPay: false },
{ id: 5, itemName: '血常规(五分类)', itemPrice: 15, itemAmount: 15, sampleType: '全血', unit: '×10^9/L', itemQty: 1, serviceFee: 0, type: '血液', isSelfPay: false }
]
},
{
key: 'urine',
label: '尿液',
expanded: false,
items: [
{ id: 6, itemName: '尿常规', itemPrice: 20, itemAmount: 20, sampleType: '尿液', unit: '细胞/μl', itemQty: 1, serviceFee: 0, type: '尿液', isSelfPay: false }
]
},
{
key: 'immunity',
label: '免疫',
expanded: false,
items: [
{ id: 7, itemName: '总IgE测定', itemPrice: 30, itemAmount: 30, sampleType: '血清', unit: 'IU/ml', itemQty: 1, serviceFee: 0, type: '免疫', isSelfPay: false },
{ id: 8, itemName: '风湿', itemPrice: 119, itemAmount: 119, sampleType: '血清', unit: 'U/ml', itemQty: 1, serviceFee: 0, type: '免疫', isSelfPay: false }
]
}
])
// 获取过滤后的项目
const getFilteredItems = (categoryKey) => {
const category = inspectionCategories.value.find(cat => cat.key === categoryKey)
if (!category) return []
if (!searchKeyword.value) {
return category.items
}
return category.items.filter(item =>
item.itemName.toLowerCase().includes(searchKeyword.value.toLowerCase())
)
}
// 获取检验申请单列表
function getInspectionList() {
// 如果没有encounterId,不调用接口
if (!queryParams.encounterId) {
// console.warn('【检验】encounterId为空,不调用接口')
return
}
loading.value = true
// 调用真实的API只传递 encounterId 参数
// console.log('【检验】调用API,encounterId:', queryParams.encounterId)
getInspectionApplicationList({ encounterId: queryParams.encounterId }).then((res) => {
if (res.code === 200) {
inspectionList.value = res.data || []
total.value = res.data?.length || 0
// console.log('【检验】获取数据成功,数量:', inspectionList.value.length)
} else {
inspectionList.value = []
total.value = 0
ElMessage.error('获取检验申请单列表失败')
}
}).catch((error) => {
// console.error('获取检验申请单列表异常:', error)
inspectionList.value = []
total.value = 0
ElMessage.error('获取检验申请单列表异常')
}).finally(() => {
loading.value = false
})
}
// 新增申请单
function handleNewApplication() {
// console.log('点击新增按钮')
resetForm()
// 生成申请单号
formData.applyNo = generateApplicationNo()
// 确保申请医生是当前登录医生
formData.applyDocName = userNickName.value || userName.value || ''
leftActiveTab.value = 'application'
}
// 生成申请单号
function generateApplicationNo() {
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
const timestamp = now.getTime()
return `${year}${month}${day}${String(timestamp).slice(-3)}`
}
// 重置表单
async function resetForm() {
Object.assign(formData, {
applicationId: null,
apply: '',
patientName: props.patientInfo.patientName || '',
medicalrecordNumber: props.patientInfo.identifierNo || '',
natureofCost: 'self',
applyTime: new Date(),
applyDepartment: props.patientInfo.organizationName || '',
applyDeptCode: '',
applyDocCode: userId.value || '',
applyDocName: userNickName.value || userName.value || '',
operatorId: userId.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: '血液',
})
selectedInspectionItems.value = []
// 重置验证错误状态
Object.keys(validationErrors).forEach(key => {
validationErrors[key] = false
})
formRef.value?.clearValidate()
}
// 返回列表
function handleBack() {
showForm.value = false
getInspectionList()
}
// 保存
function handleSave() {
// 重置验证错误状态
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 saveData = {
...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)
}
console.log('保存检验申请单数据:', saveData)
// 调用真实的API保存
saveInspectionApplication(saveData).then((res) => {
if (res.code === 200) {
ElMessage.success('保存成功')
resetForm()
leftActiveTab.value = 'application'
// 刷新列表
getInspectionList()
} else {
ElMessage.error(res.message || '保存失败')
}
}).catch((error) => {
// console.error('保存检验申请单异常:', error)
ElMessage.error('保存异常')
})
}
// 表单保存
function handleFormSave() {
formRef.value?.validate((valid) => {
if (valid) {
formData.labApplyItemList = selectedInspectionItems.value.map(item => item.id)
// console.log('保存检验申请单表单数据:', formData)
ElMessage.success('保存成功')
showForm.value = false
getInspectionList()
}
})
}
// 查看详情
function handleView(row) {
// console.log('点击查看按钮,数据:', row)
// 加载表单数据
Object.assign(formData, row)
// 根据检验项目名称找到对应的项目数据
selectedInspectionItems.value = []
const itemNames = row.inspectionItem.split('、')
inspectionCategories.value.forEach(category => {
category.items.forEach(item => {
if (itemNames.includes(item.name)) {
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
})
}
}
// 处理搜索
function handleSearch() {
// 搜索逻辑已在getFilteredItems中实现这里可以添加额外的搜索逻辑
// console.log('搜索关键词:', searchKeyword.value)
}
// 处理项目项点击(排除勾选框点击)
function handleItemClick(item) {
toggleInspectionItem(item)
}
// 判断项目是否已选择
function isItemSelected(item) {
return selectedInspectionItems.value.some(selected => selected.id === item.id)
}
// 切换检验项目选择
function toggleInspectionItem(item) {
const index = selectedInspectionItems.value.findIndex(selected => selected.id === item.id)
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.id === item.id)
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) {
// console.log('打印申请单:', row)
// 切换到申请单TAB
leftActiveTab.value = 'application'
// 加载要打印的数据
handleView(row)
// 等待DOM更新后执行打印
setTimeout(() => {
// 添加打印样式
const printStyle = document.createElement('style')
printStyle.innerHTML = `
@media print {
body * {
visibility: hidden;
}
.application-form,
.application-form * {
visibility: visible;
}
.application-form {
position: absolute;
left: 0;
top: 0;
width: 100%;
max-width: none;
box-shadow: none;
border: none;
margin: 0;
padding: 20px;
}
.el-tabs__header {
display: none;
}
.el-tabs__content {
padding: 0;
}
.section-header,
.pagination-container,
.inspection-selector,
.selected-items-area,
.actions {
display: none !important;
}
.application-form .el-input__inner,
.application-form .el-select .el-input__inner,
.application-form .el-textarea__inner {
border: 1px solid #ddd !important;
background: white !important;
}
.application-form .el-checkbox__input.is-checked .el-checkbox__inner {
background-color: #409eff;
border-color: #409eff;
}
}
`
document.head.appendChild(printStyle)
// 执行打印
window.print()
// 移除打印样式
setTimeout(() => {
document.head.removeChild(printStyle)
}, 1000)
ElMessage.success('正在准备打印...')
}, 100)
}
// 删除申请单
function handleDelete(row) {
ElMessageBox.confirm(
`确定要删除申请单 "${row.applyNo}" 吗?此操作不可撤销。`,
'删除确认',
{
confirmButtonText: '确定删除',
cancelButtonText: '取消',
type: 'warning',
confirmButtonClass: 'el-button--danger'
}
).then(() => {
// console.log('删除申请单:', row)
// 调用真实的API删除
deleteInspectionApplication(row.applicationId).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) {
// 可以添加更多的交互逻辑
}
// 监听activeTab变化
watch(() => props.activeTab, async (newVal) => {
if (newVal === 'inspection') {
await initData()
// 默认展开生化分类
activeCategory.value = 'biochemical'
inspectionCategories.value.forEach(cat => {
cat.expanded = cat.key === 'biochemical'
})
}
})
// 监听patientInfo变化,确保encounterId及时更新
watch(() => props.patientInfo, async (newVal) => {
// console.log('【检验】patientInfo变化:', newVal)
console.log('【检验】接收到的完整patientInfo:', JSON.stringify(newVal, null, 2))
if (newVal && newVal.encounterId) {
queryParams.encounterId = newVal.encounterId
// console.log('【检验】更新encounterId:', queryParams.encounterId)
// 更新科室编码
// const currentDeptCode = await getCurrentDeptCode();
// formData.applyDeptCode = currentDeptCode || '';
}
}, { deep: true, immediate: true })
// 初始化
onMounted(async () => {
await initData()
})
// 暴露方法
defineExpose({
getList: getInspectionList
})
</script>
<style lang="scss" scoped>
.inspection-section {
background: white;
padding: 0 10px 10px 10px;
margin-bottom: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #eee;
padding: 15px 0;
margin-bottom: 15px;
}
.section-header .title {
display: flex;
align-items: center;
font-size: 16px;
font-weight: bold;
color: #1a2b6d;
}
.section-header .title i {
margin-right: 10px;
font-size: 24px;
}
.actions {
display: flex;
gap: 10px;
}
.pagination-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
: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;
}
/* 新的树形结构样式 */
.category-tree {
border: 1px solid #ebeef5;
border-radius: 4px;
background: #fafafa;
}
.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;
}
.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-header {
flex-shrink: 0;
}
.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;
}
</style>