Files
his/openhis-ui-vue3/src/views/doctorstation/components/inspection/inspectionApplication.vue
wangjian963 b747f80507 feat(doctorstation): 检验申请单列表添加申请ID字段
- DTO添加applicationId(自增主键)字段
- Mapper返回类型从实体类改为DTO
- 前端表格显示申请ID替代行号
- 调整UI布局和分页器样式
2026-04-02 17:59:21 +08:00

2357 lines
69 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="60px">
<el-row class="action-buttons" type="flex" justify="end" :gutter="10">
<el-button type="primary" 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="280px"
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="申请ID" prop="applicationId" width="80" align="center" header-align="center">
<template #default="scope">
<span>{{ scope.row.applicationId || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="申请单号" prop="applyNo" min-width="160" 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>
<!-- 分页 -->
<div style="width: 100%; text-align: center; margin-top: 8px;">
<el-pagination
v-model:current-page="queryParams.pageNo"
v-model:page-size="queryParams.pageSize"
:total="total"
layout="prev, pager, next"
:pager-count="5"
:hide-on-single-page="true"
small
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
style="display: inline-flex;"
/>
</div>
</el-card>
</el-main>
<!-- 底部表单与项目选择区 -->
<el-main class="bottom-content-area">
<el-row :gutter="12">
<!-- 左侧申请单表单区60% -->
<el-col :span="14" 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: 2px">
<el-input v-model="formData.applyNo" disabled size="small" />
</el-form-item>
<!-- 患者信息行 -->
<el-row :gutter="12" style="margin-bottom: 0">
<el-col :span="8">
<el-form-item label="姓名" required style="margin-bottom: 4px">
<el-input v-model="formData.patientName" readonly size="small" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="就诊卡号" required style="margin-bottom: 4px">
<el-input v-model="formData.medicalrecordNumber" readonly size="small" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="费用性质" required style="margin-bottom: 4px">
<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="12" style="margin-bottom: 0">
<!--申请日期-->
<el-col :span="8">
<el-form-item label="申请日期" required style="margin-bottom: 4px">
<el-input
v-model="formData.applyTime"
readonly
size="small"
/>
</el-form-item>
</el-col>
<!--申请科室-->
<el-col :span="8">
<el-form-item label="申请科室" required style="margin-bottom: 4px">
<el-input v-model="formData.applyDepartment" readonly size="small" />
</el-form-item>
</el-col>
<!--申请医生-->
<el-col :span="8">
<el-form-item label="申请医生" required style="margin-bottom: 4px">
<el-input v-model="formData.applyDocName" readonly size="small" />
</el-form-item>
</el-col>
</el-row>
<!-- 执行科室 -->
<el-form-item
label="执行科室"
required
style="margin-bottom: 4px"
: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
v-for="item in inspection_lab_dept"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<!-- 诊断描述与临床诊断 -->
<el-row :gutter="12" style="margin-bottom: 0">
<el-col :span="12">
<el-form-item
label="诊断描述"
required
style="margin-bottom: 4px"
:class="{ 'form-item-error': validationErrors.clinicDesc }"
:error="validationErrors.clinicDesc ? '请输入诊断描述' : ''"
>
<el-input
v-model="formData.clinicDesc"
type="textarea"
:rows="1"
size="small"
:class="{ 'is-error': validationErrors.clinicDesc }"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="临床诊断"
required
style="margin-bottom: 4px"
:class="{ 'form-item-error': validationErrors.clinicDiag }"
:error="validationErrors.clinicDiag ? '请输入临床诊断' : ''"
>
<el-input
v-model="formData.clinicDiag"
type="textarea"
:rows="1"
size="small"
:class="{ 'is-error': validationErrors.clinicDiag }"
/>
</el-form-item>
</el-col>
</el-row>
<!-- 病史摘要与检验目的 -->
<el-row :gutter="12" style="margin-bottom: 0">
<el-col :span="12">
<el-form-item
label="病史摘要"
required
style="margin-bottom: 4px"
:class="{ 'form-item-error': validationErrors.medicalHistorySummary }"
:error="validationErrors.medicalHistorySummary ? '请输入病史摘要' : ''"
>
<el-input
v-model="formData.medicalHistorySummary"
type="textarea"
:rows="1"
size="small"
:class="{ 'is-error': validationErrors.medicalHistorySummary }"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="检验目的"
required
style="margin-bottom: 4px"
:class="{ 'form-item-error': validationErrors.purposeofInspection }"
:error="validationErrors.purposeofInspection ? '请输入检验目的' : ''"
>
<el-input
v-model="formData.purposeofInspection"
type="textarea"
:rows="1"
size="small"
:class="{ 'is-error': validationErrors.purposeofInspection }"
/>
</el-form-item>
</el-col>
</el-row>
<!-- 禁忌症与体格检查 -->
<el-row :gutter="12" style="margin-bottom: 0">
<el-col :span="12">
<el-form-item label="禁忌症" style="margin-bottom: 4px">
<el-input v-model="formData.contraindication" size="small" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="体格检查" style="margin-bottom: 4px">
<el-input v-model="formData.physicalExam" size="small" />
</el-form-item>
</el-col>
</el-row>
<!-- 检验项目和备注 -->
<el-row :gutter="12" style="margin-bottom: 0">
<el-col :span="12">
<el-form-item label="检验项目" style="margin-bottom: 4px">
<el-input v-model="formData.inspectionItemsText" size="small" readonly />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备注" style="margin-bottom: 4px">
<el-input v-model="formData.applyRemark" size="small" />
</el-form-item>
</el-col>
</el-row>
<!-- 状态复选框组 -->
<el-card style="margin-bottom: 4px; padding: 8px 12px; background: #f8f9fa; border-radius: 4px; border: 1px solid #e9ecef" shadow="never">
<template #header>
<span style="font-weight: bold; color: #1a2b6d; font-size: 13px">
状态设置
</span>
</template>
<el-row type="flex" :gutter="12" wrap>
<el-col :xs="12" :sm="6" :md="6" :lg="6">
<!-- 只有急标记能编辑 -->
<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">
<!-- 收费标记默认不勾选并不可编辑 -->
<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">
<!-- 退费标记默认不勾选并不可编辑 -->
<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">
<!-- 执行标记默认不勾选并不可编辑 -->
<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: 10px; overflow-y: auto; border: 1px solid #e4e7ed; border-radius: 4px; margin: 5px; width: 100%">
<el-form :model="formData" label-width="100px" style="margin-bottom: 10px">
<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: 10px; 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="250">
<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>
<!-- 右侧项目选择区40% -->
<el-col :span="10" 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: 220px"
@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: 220px">
<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, onUnmounted, 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 {
deleteInspectionApplication, getApplyList,
saveInspectionApplication,
getInspectionTypeList,
getInspectionItemList,
getEncounterDiagnosis,
getInspectionApplyDetail
} 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, inspection_lab_dept } = proxy.useDict('activity_category_code', 'inspection_lab_dept')
// 获取"检验"分类的字典值(与检验项目设置维护保持一致)
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')
// 申请日期实时更新定时器
let applyTimeTimer = null
// 用户信息store
const userStore = useUserStore()
const { id: userId, name: userName, nickName: userNickName } = storeToRefs(userStore)
// 修改 initData 函数
const initData = async () => {
// 先初始化患者信息(如果有)
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
// 申请单号在保存时由后端生成,此处显示"自动生成"
formData.applyNo = '自动生成'
// 申请日期实时更新(启动定时器)
startApplyTimeTimer()
// 获取主诊断信息
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) {
formData.clinicDiag = ''
}
}
}
// 查询参数
const queryParams = reactive({
pageNo: 1,
pageSize: 3,
encounterId: props.patientInfo.encounterId
})
// 检验申请单列表
const inspectionList = ref([])
// 表单数据
const formData = reactive({
applyOrganizationId: '',
applicationId: null,
applyNo: '',
patientId: '',
patientName: '',
medicalrecordNumber: '',
natureofCost: 'self',
applyTime: '', // 初始值设为空字符串,在新增时设置为当前时间
applyDepartment: '',
applyDocName: '',
executeDepartment: '',
clinicDesc: '',
contraindication: '',
clinicDiag: '',
medicalHistorySummary: '',
purposeofInspection: '',
physicalExam: '',
labApplyItemList: [],
inspectionItemsText: '',
applyRemark: '',
priorityCode: 0,
// 收费标记默认不勾选
applyStatus: 0,
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
// 加载检验类型分类列表(只加载分类,项目懒加载)
const loadInspectionData = async () => {
// 如果已经加载过分类,直接返回
if (inspectionCategories.value.length > 0) {
return
}
inspectionLoading.value = true
try {
// 只获取检验类型列表
const typeRes = await getInspectionTypeList().catch(() => {
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 {
}
} catch (error) {
} finally {
inspectionLoading.value = false
}
}
// 懒加载分类项目(分页)
const loadCategoryItems = async (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) {
// 加载失败时设置空数据
if (!loadMore) {
category.items = []
category.total = 0
category.hasMore = false
category.loaded = true
}
} finally {
category.loading = false
}
}
// 加载更多项目
const loadMoreItems = (categoryKey) => {
const category = inspectionCategories.value.find(c => c.key === categoryKey)
if (!category || !category.hasMore || category.loading) return
category.pageNo++
loadCategoryItems(categoryKey, true)
}
// 处理滚动事件(无限滚动)
const 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
}
// 搜索建议查询(自动完成)
const querySearchInspectionItems = async (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) {
cb([])
}
}
// 搜索选择处理
const handleSearchSelect = (item) => {
// 直接添加到已选列表
if (!isItemSelected(item)) {
selectedInspectionItems.value.push({
...item,
itemName: item.itemName
})
}
// 清空搜索关键词
searchKeyword.value = ''
}
// 搜索框清空处理
const handleSearchClear = () => {
searchKeyword.value = ''
}
// 获取检验申请单列表
const 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 = res.data.records
total.value = res.data.total || res.data.records.length
}
// 如果返回的是普通数组
else if (Array.isArray(res.data)) {
// 直接使用后端返回的数据
inspectionList.value = 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
})
}
// 合并检验申请单记录:将同一个申请单的多个明细合并成一条记录
const 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())
}
// 格式化金额:确保显示两位小数
const 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)
}
// 格式化日期时间为字符串 YYYY-MM-DD HH:mm:ss
const formatDateTime = (date) => {
if (!date) return ''
const d = new Date(date)
const year = d.getFullYear()
const month = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
const hours = String(d.getHours()).padStart(2, '0')
const minutes = String(d.getMinutes()).padStart(2, '0')
const seconds = String(d.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
// 启动申请日期实时更新定时器
const startApplyTimeTimer = () => {
// 先清除已存在的定时器
stopApplyTimeTimer()
// 立即更新一次
formData.applyTime = formatDateTime(new Date())
// 每秒更新
applyTimeTimer = setInterval(() => {
formData.applyTime = formatDateTime(new Date())
}, 1000)
}
// 停止申请日期实时更新定时器
const stopApplyTimeTimer = () => {
if (applyTimeTimer) {
clearInterval(applyTimeTimer)
applyTimeTimer = null
}
}
// 新增申请单
const handleNewApplication = async () => {
resetForm()
// 申请单号在保存时由后端生成,此处显示"待生成"
formData.applyNo = '自动生成'
// 申请日期实时更新(启动定时器)
startApplyTimeTimer()
// 确保申请医生是当前登录医生
formData.applyDocName = userNickName.value || userName.value || ''
leftActiveTab.value = 'application'
}
// 重置表单
const resetForm = async () => {
Object.assign(formData, {
applicationId: null,
applyOrganizationId: props.patientInfo.orgId || '',
patientName: props.patientInfo.patientName || '',
medicalrecordNumber: props.patientInfo.identifierNo || '',
natureofCost: 'self',
applyTime: '', // 申请日期由定时器实时更新
applyDepartment: props.patientInfo.organizationName || '',
applyDeptCode: props.patientInfo.organizationName,
applyDocCode: userId.value || '',
applyDocName: userNickName.value || userName.value || '',
executeDepartment: '',
clinicDesc: '',
contraindication: '',
clinicDiag: '',
medicalHistorySummary: '',
purposeofInspection: '',
physicalExam: '',
labApplyItemList: [],
applyRemark: '',
priorityCode: 0,
applyStatus: 0,
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) {
formData.clinicDiag = ''
}
}
}
// 保存
const handleSave = () => {
// P1防重复提交 - 立即设置标志
if (saving.value) return
saving.value = true
// 重置验证错误状态
Object.keys(validationErrors).forEach(key => {
validationErrors[key] = false
})
let hasErrors = false
// P0检查患者信息是否已加载
if (!formData.patientName?.trim() || !formData.medicalrecordNumber?.trim()) {
ElMessage.error('患者信息未加载,请稍后重试')
saving.value = false
return
}
// P0检查就诊信息是否有效
if (!formData.encounterId) {
ElMessage.error('就诊信息无效,请重新选择患者')
saving.value = false
return
}
// 检查必填字段,执行科室
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('请至少选择一项检验项目')
saving.value = false
return
}
// 申请日期由后端在保存时自动生成,无需前端校验
if (hasErrors) {
ElMessage.error('请填写所有必填字段')
saving.value = false
return
}
// 准备保存数据
const prepareSaveData = () => {
// 将执行科室代码赋值给每个检验项目
const labApplyItemList = selectedInspectionItems.value.map(item => ({
...item,
performDeptCode: formData.executeDepartment // 从字典获取的执行科室代码
}))
return {
...formData,
labApplyItemList,
physicalExamination: formData.physicalExam, // 字段名映射:前端 physicalExam -> 后端 physicalExamination
inspectionItemsText: selectedInspectionItems.value.map(item => item.itemName).join('+')
// 金额由后端计算,前端不传递
}
}
// 申请单号由后端在保存时生成,直接保存
const saveData = prepareSaveData();
executeSave(saveData);
}
const executeSave = (saveData) => {
saveInspectionApplication(saveData).then((res) => {
if (res.code === 200) {
ElMessage.success('保存成功')
// 停止申请日期实时更新
stopApplyTimeTimer()
// 从后端返回获取生成的申请单号
if (res.data && res.data.applyNo) {
ElMessage.info(`申请单号:${res.data.applyNo}`)
// 显示后端返回的实际申请时间
if (res.data.applyTime) {
formData.applyTime = res.data.applyTime
}
}
emit('save', res.data) // 通知父组件保存成功
resetForm()
// 设置下一个新单为"待生成"
formData.applyNo = '自动生成'
// 启动新的申请日期实时更新
startApplyTimeTimer()
leftActiveTab.value = 'application'
// 刷新列表
getInspectionList()
} else {
// 对于其他错误,也使用弹窗提示
ElMessageBox.alert(res.message || '保存失败', '错误', {
confirmButtonText: '确定',
type: 'error',
}).catch(() => {
});
}
}).catch(() => {
// 处理请求失败的其他错误
ElMessage.error('保存失败,请稍后重试');
}).finally(() => {
saving.value = false
})
}
// 查看详情
const handleView = (row) => {
// 停止申请日期实时更新(查看已保存的申请单)
stopApplyTimeTimer()
// 加载表单数据
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'
}
// 切换分类(修改为懒加载)
const 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)
}
}
}
// 处理项目项点击(排除勾选框点击)
const handleItemClick = (item) => {
toggleInspectionItem(item)
}
// 判断项目是否已选择
const isItemSelected = (item) => {
return selectedInspectionItems.value.some(selected => selected.itemId === item.itemId)
}
// 切换检验项目选择
const 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);
}
}
// 移除检验项目
const removeInspectionItem = (item) => {
const index = selectedInspectionItems.value.findIndex(selected => selected.itemId === item.itemId)
if (index > -1) {
selectedInspectionItems.value.splice(index, 1)
}
}
// 清空所有选择
const clearAllSelected = () => {
selectedInspectionItems.value = []
}
// 分页大小改变
const handleSizeChange = (size) => {
queryParams.pageSize = size
getInspectionList()
}
const handleCurrentChange = (page) => {
queryParams.pageNo = page
getInspectionList()
}
// 选择框变化
const handleSelectionChange = (selection) => {
selectedRows.value = selection
}
const 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)
}
// 删除申请单
const 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('删除检验<E6A380><E9AA8C>请单异常:', error)
ElMessage.error('删除异常')
})
}).catch(() => {
// 用户取消删除
})
}
// 单元格点击 - 点击表格行时加载申请单详情
const handleCellClick = (row, column) => {
// 点击表格行时,将该申请单的数据加载到表单中
// 使用 applyNo 判断是否有效
if (row && row.applyNo) {
loadApplicationToForm(row);
}
}
// 行点击事件处理
const handleRowClick = (currentRow, oldRow) => {
// 点击表格行时,将该申请单的数据加载到表单中
// 使用 applyNo 判断是否有效
if (currentRow && currentRow.applyNo) {
loadApplicationToForm(currentRow);
}
}
// 提取公共方法加载申请单到表单
const loadApplicationToForm = async (row) => {
// 停止申请日期实时更新(加载已保存的申请单)
stopApplyTimeTimer()
// 切换到申请单 TAB
leftActiveTab.value = 'application'
// 先用列表数据设置基本信息
Object.assign(formData, {
applyNo: row.applyNo,
applyDocName: row.applyDocName,
priorityCode: row.priorityCode || 0,
applyStatus: row.applyStatus || 0
})
// 根据申请单号获取完整详情
try {
const res = await getInspectionApplyDetail(row.applyNo)
console.log('申请单详情API返回:', res) // 临时调试日志
if (res.code === 200 && res.data) {
const detail = res.data
// 加载完整的表单数据
Object.assign(formData, {
applicationId: detail.applicationId || null,
applyNo: detail.applyNo,
patientId: detail.patientId,
patientName: detail.patientName,
medicalrecordNumber: detail.medicalrecordNumber,
natureofCost: detail.natureofCost || 'self',
applyTime: detail.applyTime,
applyDepartment: detail.applyDepartment,
applyDocName: detail.applyDocName,
applyDocCode: detail.applyDocCode,
applyDeptCode: detail.applyDeptCode,
applyOrganizationId: detail.applyOrganizationId,
executeDepartment: detail.executeDepartment || '',
clinicDesc: detail.clinicDesc,
contraindication: detail.contraindication,
clinicDiag: detail.clinicDiag,
medicalHistorySummary: detail.medicalHistorySummary,
purposeofInspection: detail.purposeofInspection,
physicalExam: detail.physicalExamination,
applyRemark: detail.applyRemark,
priorityCode: detail.priorityCode || 0,
applyStatus: detail.applyStatus || 0,
needRefund: detail.needRefund || false,
needExecute: detail.needExecute || false,
inspectionDoctor: detail.inspectionDoctor,
inspectionTime: detail.inspectionTime,
auditDoctor: detail.auditDoctor,
auditTime: detail.auditTime,
visitNo: detail.visitNo,
specimenName: detail.specimenName,
encounterId: detail.encounterId
})
// 加载检验项目数据
selectedInspectionItems.value = []
if (detail.labApplyItemList && detail.labApplyItemList.length > 0) {
selectedInspectionItems.value = detail.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 (detail.inspectionItem || detail.itemName) {
// 如果只有项目名称,尝试从本地分类中查找匹配项
const itemNames = (detail.inspectionItem || detail.itemName).split(/[+,]/)
inspectionCategories.value.forEach(category => {
category.items.forEach(item => {
if (itemNames.includes(item.itemName)) {
selectedInspectionItems.value.push({ ...item })
}
})
})
}
}
} catch (error) {
// 如果获取详情失败,至少显示列表中的基本信息
Object.assign(formData, row)
}
// 重置验证错误状态
Object.keys(validationErrors).forEach(key => {
validationErrors[key] = false
})
}
// 监听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 })
// 监听执行科室字典数据,设置默认值为第一个选项
watch(() => inspection_lab_dept.value, (newVal) => {
if (newVal && newVal.length > 0 && !formData.executeDepartment) {
formData.executeDepartment = newVal[0].value
}
}, { immediate: true })
// 组件挂载时预加载检验项目数据不依赖patientInfo
onMounted(async () => {
await loadInspectionData()
})
// 组件卸载时清除定时器
onUnmounted(() => {
stopApplyTimeTimer()
})
// 暴露方法
defineExpose({
getList: getInspectionList
})
</script>
<style lang="scss" scoped>
/* 页面容器 - 紧凑布局 */
.inspection-application-container {
height: auto;
max-height: none;
overflow: visible;
padding: 0;
}
/* 覆盖 el-main 默认 padding */
.inspection-application-container .el-main {
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;
}
/* 新增按钮样式 - PRD要求蓝色背景 #4a89dc */
.new-btn {
background-color: #4a89dc !important;
border-color: #4a89dc !important;
color: #fff !important;
}
.new-btn:hover {
background-color: #5a9aec !important;
border-color: #5a9aec !important;
}
/* 保存按钮样式 - PRD要求绿色背景 #48cfad */
.save-btn {
background-color: #48cfad !important;
border-color: #48cfad !important;
color: #fff !important;
}
.save-btn:hover {
background-color: #58dfbd !important;
border-color: #58dfbd !important;
}
/* 检验信息表格区 - 紧凑高度 */
.inspection-section {
padding: 4px 10px 0 10px;
}
.table-card {
height: auto;
}
.table-card :deep(.el-card__body) {
padding-bottom: 8px;
}
.card-header {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
color: var(--el-text-color-primary);
}
/* 底部内容区域 */
.bottom-content-area {
padding: 4px 10px;
}
/* 表单区域 */
.form-card {
height: auto;
}
/* 表单区域使用主色调 */
.form-tabs :deep(.el-tabs__item.is-active) {
color: #51A3F3 !important;
}
.form-tabs :deep(.el-tabs__active-bar) {
background-color: #51A3F3 !important;
}
.form-tabs {
height: auto;
}
.application-form {
overflow: visible;
padding: 6px 8px;
border: 1px solid #e4e7ed;
border-radius: 4px;
margin: 2px;
}
/* 选择区域 */
.selection-area {
display: flex;
flex-direction: column;
gap: 8px;
}
.inspection-selector,
.selected-items-area {
height: auto;
}
.card-title {
font-weight: 600;
color: var(--el-text-color-primary);
}
.search-input {
margin-bottom: 8px;
}
.selected-header {
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
: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);
}
/* 主色调 #51A3F3 - PRD样式规范 */
: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;
}
}
/* 选中状态使用主色调 #51A3F3 */
:deep(.inspection-table .el-checkbox__input.is-checked .el-checkbox__inner) {
background-color: #51A3F3 !important;
border-color: #51A3F3 !important;
}
:deep(.inspection-table .el-checkbox__input.is-indeterminate .el-checkbox__inner) {
background-color: #51A3F3 !important;
border-color: #51A3F3 !important;
}
: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;
}
/* 新的树形结构样式 - PRD要求高度约350px */
.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: #51A3F3;
font-weight: bold;
}
.category-tree-icon {
margin-right: 8px;
font-size: 12px;
width: 12px;
text-align: center;
color: #51A3F3;
}
.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 #51A3F3;
}
.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;
}
/* 已选项目区样式 - PRD要求高度约350px */
.selected-items-area {
display: flex;
flex-direction: column;
}
.selected-tree {
flex: 1;
max-height: 280px;
}
.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>