Files
his/openhis-ui-vue3/src/views/doctorstation/components/inspection/inspectionApplication.vue
chenqi 2fe6d45ad4 fix(doctorstation): 修复诊断组件和住院办理功能的数据处理问题
- 修复诊断组件中el-popover模板语法错误,添加template标签
- 优化患者历史数据处理逻辑,确保数组类型安全并正确构建树形结构
- 完善住院办理流程中的组织机构数据获取和筛选逻辑
- 添加详细的控制台日志用于调试住院办理功能
- 修复办理住院按钮的禁用状态计算逻辑
- 优化患者卡片点击事件处理,确保就诊ID正确传递
- 添加诊断信息完整性检查并提供用户引导
- 修复检验申请组件中的监听器和暴露方法逻辑
2026-01-18 00:37:54 +08:00

1478 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="applicationNo" width="140" align="center" />
<el-table-column label="检验项目" prop="inspectionItem" width="150" align="center" />
<el-table-column label="申请医生" prop="doctorName" width="120" align="center" />
<el-table-column label="急" width="60" align="center">
<template #default="scope">
<el-checkbox :model-value="scope.row.isUrgent" disabled />
</template>
</el-table-column>
<el-table-column label="收费" width="80" align="center">
<template #default="scope">
<el-checkbox :model-value="scope.row.needCharge" 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 type="text" size="small" @click="handlePrint(scope.row)">打印</el-button>
<el-button type="text" 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.applicationNo" disabled 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">姓名</label>
<el-input v-model="formData.patientName" disabled size="small" />
</div>
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">就诊卡号</label>
<el-input v-model="formData.cardNo" disabled size="small" />
</div>
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">费用性质</label>
<el-select v-model="formData.feeType" 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">申请日期</label>
<el-date-picker
v-model="formData.applicationDate"
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-select v-model="formData.departmentName" placeholder="请选择申请科室" size="small" style="width: 100%" disabled>
<el-option label="内科" value="internal" />
<el-option label="外科" value="surgery" />
<el-option label="儿科" value="pediatrics" />
</el-select>
</div>
<div>
<label style="display: block; margin-bottom: 5px; font-weight: bold">申请医生</label>
<el-select v-model="formData.doctorName" placeholder="请选择申请医生" size="small" style="width: 100%" disabled>
<el-option label="张医生" value="doctor_zhang" />
<el-option label="李医生" value="doctor_li" />
<el-option label="王医生" value="doctor_wang" />
</el-select>
</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.diagnosisDesc }">
<label style="display: block; margin-bottom: 5px; font-weight: bold">诊断描述 <span style="color: #f56c6c">*</span></label>
<el-input
v-model="formData.diagnosisDesc"
type="textarea"
:rows="2"
size="small"
:class="{ 'is-error': validationErrors.diagnosisDesc }"
/>
<div v-if="validationErrors.diagnosisDesc" class="error-message">请输入诊断描述</div>
</div>
<div :class="{ 'form-item-error': validationErrors.clinicalDiagnosis }">
<label style="display: block; margin-bottom: 5px; font-weight: bold">临床诊断 <span style="color: #f56c6c">*</span></label>
<el-input
v-model="formData.clinicalDiagnosis"
type="textarea"
:rows="2"
size="small"
:class="{ 'is-error': validationErrors.clinicalDiagnosis }"
/>
<div v-if="validationErrors.clinicalDiagnosis" 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.contraindications" type="textarea" :rows="2" size="small" />
</div>
<div :class="{ 'form-item-error': validationErrors.medicalHistory }">
<label style="display: block; margin-bottom: 5px; font-weight: bold">病史摘要 <span style="color: #f56c6c">*</span></label>
<el-input
v-model="formData.medicalHistory"
type="textarea"
:rows="2"
size="small"
:class="{ 'is-error': validationErrors.medicalHistory }"
/>
<div v-if="validationErrors.medicalHistory" class="error-message">请输入病史摘要</div>
</div>
<div :class="{ 'form-item-error': validationErrors.inspectionPurpose }">
<label style="display: block; margin-bottom: 5px; font-weight: bold">检验目的 <span style="color: #f56c6c">*</span></label>
<el-input
v-model="formData.inspectionPurpose"
type="textarea"
:rows="2"
size="small"
:class="{ 'is-error': validationErrors.inspectionPurpose }"
/>
<div v-if="validationErrors.inspectionPurpose" 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.remark" 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.isUrgent"></el-checkbox>
<el-checkbox v-model="formData.needCharge" checked>收费</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="name" 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="总量" width="60" align="center">
<template #default="scope">
<span>{{ scope.row.quantity || 1 }}</span>
</template>
</el-table-column>
<el-table-column label="单价" width="70" align="right">
<template #default="scope">
¥{{ scope.row.price }}
</template>
</el-table-column>
<el-table-column label="金额" width="70" align="right">
<template #default="scope">
¥{{ (scope.row.quantity || 1) * scope.row.price }}
</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-name">{{ item.name }}</span>
<span class="item-price">¥{{ item.price }}</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 type="text" @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-name">{{ item.name }}</span>
<span class="item-price">¥{{ item.price }}</span>
<el-button
type="text"
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'
// 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')
// 查询参数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
encounterId: ''
})
// 检验申请单列表
const inspectionList = ref([])
// 表单数据
const formData = reactive({
applicationId: null,
applicationNo: '202511210001',
patientName: '',
cardNo: '',
feeType: 'self',
applicationDate: new Date(),
departmentName: '',
doctorName: '',
executeDepartment: 'medical_lab',
diagnosisDesc: '',
contraindications: '',
clinicalDiagnosis: '',
medicalHistory: '',
inspectionPurpose: '',
physicalExam: '',
inspectionItems: [],
inspectionItemsText: '',
remark: '',
isUrgent: false,
needCharge: true,
needRefund: false,
needExecute: false,
inspectionDoctor: '',
inspectionTime: null,
auditDoctor: '',
auditTime: null
})
// 表单验证规则
const formRules = {
feeType: [{ required: true, message: '请选择费用性质', trigger: 'change' }],
applicationDate: [{ required: true, message: '请选择申请日期', trigger: 'change' }],
executeDepartment: [{ required: true, message: '请选择执行科室', trigger: 'change' }],
diagnosisDesc: [{ required: true, message: '请输入诊断描述', trigger: 'blur' }],
clinicalDiagnosis: [{ required: true, message: '请输入临床诊断', trigger: 'blur' }],
medicalHistory: [{ required: true, message: '请输入病史摘要', trigger: 'blur' }],
inspectionPurpose: [{ required: true, message: '请输入检验目的', trigger: 'blur' }],
inspectionItems: [{ required: true, message: '请至少选择一个检验项目', trigger: 'change' }]
}
// 表单引用
const formRef = ref()
// 表格引用
const inspectionTableRef = ref()
// 验证错误状态
const validationErrors = reactive({
executeDepartment: false,
diagnosisDesc: false,
clinicalDiagnosis: false,
medicalHistory: false,
inspectionPurpose: false,
inspectionItems: false
})
// 已选择的表格行
const selectedRows = ref([])
// 已选择的检验项目
const selectedInspectionItems = ref([])
// 搜索关键词
const searchKeyword = ref('')
// 活动分类
const activeCategory = ref('biochemical')
// 检验项目分类(树形结构)
const inspectionCategories = ref([
{
key: 'biochemical',
label: '生化',
expanded: true,
items: [
{ id: 1, name: '肝功能', price: 31, sampleType: '血清', unit: 'U/L', quantity: 1, serviceFee: 0, type: '生化', isSelfPay: false },
{ id: 2, name: '肾功能', price: 28, sampleType: '血清', unit: 'U/L', quantity: 1, serviceFee: 0, type: '生化', isSelfPay: false },
{ id: 3, name: '血糖', price: 15, sampleType: '血清', unit: 'mmol/L', quantity: 1, serviceFee: 0, type: '生化', isSelfPay: false }
]
},
{
key: 'blood',
label: '临检',
expanded: false,
items: [
{ id: 4, name: '血常规+crp', price: 50, sampleType: '全血', unit: '×10^9/L', quantity: 1, serviceFee: 0, type: '血液', isSelfPay: false },
{ id: 5, name: '血常规(五分类)', price: 15, sampleType: '全血', unit: '×10^9/L', quantity: 1, serviceFee: 0, type: '血液', isSelfPay: false }
]
},
{
key: 'urine',
label: '尿液',
expanded: false,
items: [
{ id: 6, name: '尿常规', price: 20, sampleType: '尿液', unit: '细胞/μl', quantity: 1, serviceFee: 0, type: '尿液', isSelfPay: false }
]
},
{
key: 'immunity',
label: '免疫',
expanded: false,
items: [
{ id: 7, name: '总IgE测定', price: 30, sampleType: '血清', unit: 'IU/ml', quantity: 1, serviceFee: 0, type: '免疫', isSelfPay: false },
{ id: 8, name: '风湿', price: 119, sampleType: '血清', unit: 'U/ml', quantity: 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.name.toLowerCase().includes(searchKeyword.value.toLowerCase())
)
}
// 初始化数据
function initData() {
console.log('检验组件初始化patientInfo:', props.patientInfo)
if (props.patientInfo) {
// 确保 encounterId 存在且有效,优先使用 encounterId其次尝试 id最后尝试 patientId
queryParams.encounterId = props.patientInfo.encounterId || props.patientInfo.id || props.patientInfo.patientId
formData.patientName = props.patientInfo.patientName || ''
formData.cardNo = props.patientInfo.cardNo || ''
formData.departmentName = props.patientInfo.departmentName || ''
formData.doctorName = props.patientInfo.doctorName || ''
}
// 只有在存在有效的 encounterId 时才调用接口
if (queryParams.encounterId && queryParams.encounterId !== 'undefined' && queryParams.encounterId !== 'null' && queryParams.encounterId !== '') {
getInspectionList()
} else {
console.warn('缺少有效的就诊ID无法获取检验申请单列表')
inspectionList.value = []
total.value = 0
}
}
// 获取检验申请单列表
function getInspectionList() {
// 先检查是否有有效的encounterId
if (!queryParams.encounterId || queryParams.encounterId === 'undefined' || queryParams.encounterId === 'null') {
console.warn('缺少有效的就诊ID无法获取检验申请单列表')
inspectionList.value = []
total.value = 0
loading.value = false
return
}
loading.value = true
// 调用真实的API只传递 encounterId 参数
getInspectionApplicationList({ encounterId: queryParams.encounterId }).then((res) => {
if (res.code === 200) {
inspectionList.value = res.data || []
total.value = res.data?.length || 0
} else {
inspectionList.value = []
total.value = 0
console.error('获取检验申请单列表失败:', res.message || res.msg)
ElMessage.error(res.message || res.msg || '获取检验申请单列表失败')
}
}).catch((error) => {
console.error('获取检验申请单列表异常:', error)
inspectionList.value = []
total.value = 0
// 提供更友好的错误信息
let errorMessage = '获取检验申请单列表异常'
if (error.response) {
errorMessage += ` (${error.response.status}): ${error.response.data.message || error.response.statusText}`
} else if (error.request) {
errorMessage += ': 网络请求失败,请检查网络连接'
} else {
errorMessage += `: ${error.message}`
}
ElMessage.error(errorMessage)
}).finally(() => {
loading.value = false
})
}
// 新增申请单
function handleNewApplication() {
console.log('点击新增按钮')
resetForm()
// 生成申请单号
formData.applicationNo = generateApplicationNo()
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)}`
}
// 重置表单
function resetForm() {
Object.assign(formData, {
applicationId: null,
applicationNo: '',
patientName: props.patientInfo.patientName || '',
cardNo: props.patientInfo.cardNo || '',
feeType: 'self',
applicationDate: new Date(),
departmentName: props.patientInfo.departmentName || '',
doctorName: props.patientInfo.doctorName || '',
executeDepartment: 'medical_lab',
diagnosisDesc: '',
contraindications: '',
clinicalDiagnosis: '',
medicalHistory: '',
inspectionPurpose: '',
physicalExam: '',
inspectionItems: [],
remark: '',
isUrgent: false,
needCharge: true,
needRefund: false,
needExecute: false
})
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.diagnosisDesc.trim()) {
validationErrors.diagnosisDesc = true
hasErrors = true
}
if (!formData.clinicalDiagnosis.trim()) {
validationErrors.clinicalDiagnosis = true
hasErrors = true
}
if (!formData.medicalHistory.trim()) {
validationErrors.medicalHistory = true
hasErrors = true
}
if (!formData.inspectionPurpose.trim()) {
validationErrors.inspectionPurpose = true
hasErrors = true
}
if (selectedInspectionItems.value.length === 0) {
validationErrors.inspectionItems = true
hasErrors = true
ElMessage.error('请至少选择一项检验项目')
return
}
if (hasErrors) {
ElMessage.error('请填写所有必填字段')
return
}
// 准备保存数据
const saveData = {
...formData,
inspectionItems: selectedInspectionItems.value,
inspectionItemsText: selectedInspectionItems.value.map(item => item.name).join('、'),
amount: selectedInspectionItems.value.reduce((sum, item) => sum + item.price, 0),
serviceFee: selectedInspectionItems.value.reduce((sum, item) => sum + (item.serviceFee || 0), 0),
totalAmount: selectedInspectionItems.value.reduce((sum, item) => sum + item.price + (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.inspectionItems = 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 {
selectedInspectionItems.value.push({ ...item })
}
}
// 移除检验项目
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.applicationNo}" 吗?此操作不可撤销。`,
'删除确认',
{
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, (newVal) => {
if (newVal === 'inspection') {
initData()
// 默认展开生化分类
activeCategory.value = 'biochemical'
inspectionCategories.value.forEach(cat => {
cat.expanded = cat.key === 'biochemical'
})
}
})
// 监听patientInfo变化确保在患者信息更新时也更新检验申请单列表
watch(() => props.patientInfo, (newPatientInfo) => {
if (newPatientInfo && Object.keys(newPatientInfo).length > 0) {
// 更新encounterId
queryParams.encounterId = newPatientInfo.encounterId || newPatientInfo.id || newPatientInfo.patientId
// 如果有有效的encounterId则获取检验申请单列表
if (queryParams.encounterId && queryParams.encounterId !== 'undefined' && queryParams.encounterId !== 'null' && queryParams.encounterId !== '') {
getInspectionList()
}
}
}, { deep: true })
// 初始化
onMounted(() => {
initData()
})
// 暴露方法
defineExpose({
getList() {
// 在调用getList时先检查是否有有效的patientInfo如果有则更新encounterId
if (props.patientInfo && Object.keys(props.patientInfo).length > 0) {
queryParams.encounterId = props.patientInfo.encounterId || props.patientInfo.id || props.patientInfo.patientId;
}
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-name {
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-name {
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-name {
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>