1380 lines
47 KiB
Vue
1380 lines
47 KiB
Vue
<template>
|
||
<div class="exam-app-container">
|
||
<!-- ====== 顶部卡片:申请单列表 ====== -->
|
||
<div class="top-section">
|
||
<div class="section-header">
|
||
<span class="section-title">检查项目 ({{ applicationList.length }})</span>
|
||
<div class="header-actions">
|
||
<el-button type="primary" @click="handleAdd" icon="Plus">新增</el-button>
|
||
<el-button type="success" @click="handleSave" icon="Finished">保存</el-button>
|
||
</div>
|
||
</div>
|
||
<el-table
|
||
v-loading="loading"
|
||
:data="applicationList"
|
||
:max-height="200"
|
||
highlight-current-row
|
||
@row-click="handleRowClick"
|
||
border
|
||
size="small"
|
||
:header-cell-style="{ background: '#f5f5f5', color: '#303133', fontWeight: '600' }"
|
||
>
|
||
<el-table-column type="selection" width="40" align="center" />
|
||
<el-table-column label="申请ID" prop="id" width="80" align="center" />
|
||
<el-table-column label="申请单号" prop="applyNo" min-width="140" align="center" />
|
||
<el-table-column label="申检部位" prop="inspectionArea" min-width="100" align="center" />
|
||
<el-table-column label="申请医生" prop="applyDocCode" min-width="90" align="center" />
|
||
<el-table-column label="急" prop="isUrgent" width="50" align="center">
|
||
<template #default="{ row }">
|
||
<el-checkbox v-model="row.isUrgent" :true-label="1" :false-label="0" disabled />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="收费" prop="isCharged" width="50" align="center">
|
||
<template #default="{ row }">
|
||
<el-checkbox v-model="row.isCharged" :true-label="1" :false-label="0" disabled />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="退费" prop="isRefunded" width="50" align="center">
|
||
<template #default="{ row }">
|
||
<el-checkbox v-model="row.isRefunded" :true-label="1" :false-label="0" disabled />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="执行" prop="isExecuted" width="50" align="center">
|
||
<template #default="{ row }">
|
||
<el-checkbox v-model="row.isExecuted" :true-label="1" :false-label="0" disabled />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="金额" prop="totalAmount" width="90" align="right">
|
||
<template #default="{ row }">
|
||
{{ (row.totalAmount || 0).toFixed(2) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="80" align="center" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button link @click.stop="handlePrint(row)" title="打印">
|
||
<el-icon><Printer /></el-icon>
|
||
</el-button>
|
||
<el-button link type="danger" @click.stop="handleDelete(row)" title="删除">
|
||
<el-icon><Delete /></el-icon>
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<!-- ====== 底部主区:左表单 + 右分类 ====== -->
|
||
<div class="bottom-section">
|
||
<!-- 左:表单区 -->
|
||
<div class="form-panel">
|
||
<el-tabs v-model="activeDetailTab" class="form-tabs">
|
||
<!-- TAB1:检查申请单 -->
|
||
<el-tab-pane label="检查申请单" name="applyForm">
|
||
<el-form ref="formRef" :model="form" :rules="rules" size="small" class="apply-form">
|
||
<el-row :gutter="12">
|
||
<el-col :span="8">
|
||
<el-form-item label="申请单号" prop="applyNo">
|
||
<el-input v-model="form.applyNo" readonly placeholder="自动生成" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="姓名" prop="patientName">
|
||
<el-input v-model="form.patientName" readonly />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="就诊卡号" prop="medicalrecordNumber">
|
||
<el-input v-model="form.medicalrecordNumber" readonly />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="12">
|
||
<el-col :span="8">
|
||
<el-form-item label="费用性质" prop="natureofCost">
|
||
<el-select v-model="form.natureofCost" style="width:100%">
|
||
<el-option label="自费医疗" value="自费医疗" />
|
||
<el-option label="医保报销" value="医保报销" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="申请日期" prop="applyTime">
|
||
<el-date-picker v-model="form.applyTime" type="date" style="width:100%"
|
||
format="YYYY-MM-DD" value-format="YYYY-MM-DD HH:mm:ss" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="申请科室" prop="applyDeptCode">
|
||
<el-input v-model="form.applyDeptCode" disabled />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="12">
|
||
<el-col :span="8">
|
||
<el-form-item label="申请医生" prop="applyDocCode">
|
||
<el-input v-model="form.applyDocCode" disabled />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="执行科室" prop="performDeptCode">
|
||
<el-select
|
||
v-model="form.performDeptCode"
|
||
style="width: 100%"
|
||
filterable
|
||
remote
|
||
reserve-keyword
|
||
clearable
|
||
placeholder="请选择执行科室(支持模糊查询)"
|
||
:remote-method="handleOrgRemoteSearch"
|
||
:loading="orgLoading"
|
||
>
|
||
<el-option
|
||
v-for="opt in orgFilteredOptions"
|
||
:key="opt.value"
|
||
:label="opt.label"
|
||
:value="opt.value"
|
||
/>
|
||
<template #empty>
|
||
<div style="padding: 10px 0; color: #909399; text-align: center;">
|
||
{{ orgLoading ? '加载中...' : '暂无匹配科室' }}
|
||
</div>
|
||
</template>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="12">
|
||
<el-col :span="24">
|
||
<el-form-item label="诊断描述" prop="clinicDesc">
|
||
<el-input v-model="form.clinicDesc" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="12">
|
||
<el-col :span="8">
|
||
<el-form-item label="禁忌症" prop="contraindication">
|
||
<el-input v-model="form.contraindication" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="临床诊断" prop="clinicalDiag">
|
||
<el-input v-model="form.clinicalDiag" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="病史摘要" prop="medicalHistorySummary">
|
||
<el-input v-model="form.medicalHistorySummary" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="12">
|
||
<el-col :span="24">
|
||
<el-form-item label="检查目的" prop="purposeDesc">
|
||
<el-input v-model="form.purposeDesc" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="12">
|
||
<el-col :span="8">
|
||
<el-form-item label="体格检查">
|
||
<el-input v-model="form.purposeofInspection" placeholder="T(摄氏度) P次/分 R次/分 BF" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="申检部位">
|
||
<el-input v-model="form.inspectionArea" readonly />
|
||
</el-form-item>
|
||
</el-col>
|
||
<!-- Bug #384修复: 添加检查方法只读输入框,联动显示选中的检查方法 -->
|
||
<el-col :span="8">
|
||
<el-form-item label="检查方法">
|
||
<el-input v-model="form.selectedMethodDisplay" readonly placeholder="请在右侧选择" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="12">
|
||
<el-col :span="8">
|
||
<el-form-item label="备注" prop="applyRemark">
|
||
<el-input v-model="form.applyRemark" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="16">
|
||
<el-form-item label="状态">
|
||
<el-checkbox v-model="form.isUrgent" :true-label="1" :false-label="0">急</el-checkbox>
|
||
<el-checkbox v-model="form.isCharged" :true-label="1" :false-label="0" disabled>收费</el-checkbox>
|
||
<el-checkbox v-model="form.isRefunded" :true-label="1" :false-label="0" disabled>退费</el-checkbox>
|
||
<el-checkbox v-model="form.isExecuted" :true-label="1" :false-label="0" disabled>执行</el-checkbox>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
</el-tab-pane>
|
||
|
||
<!-- TAB2:检查明细 -->
|
||
<el-tab-pane label="检查明细" name="applyDetail">
|
||
<!-- 🔧 BugFix#426: 支持树形展开显示套餐明细 -->
|
||
<el-table
|
||
ref="detailTableRef"
|
||
:data="selectedItems"
|
||
row-key="id"
|
||
border
|
||
size="small"
|
||
style="width:100%"
|
||
:max-height="350"
|
||
:header-cell-style="{ background: '#f5f5f5', color: '#303133' }"
|
||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||
:load="loadPackageDetails"
|
||
lazy
|
||
>
|
||
<el-table-column label="行" type="index" width="45" align="center" />
|
||
<el-table-column label="检查项目" prop="name" min-width="120">
|
||
<template #default="scope">
|
||
<el-tag v-if="scope.row.isPackage" size="small" type="warning" style="margin-right: 4px">套餐</el-tag>
|
||
<span>{{ scope.row.name }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="部位" prop="applyPart" min-width="90">
|
||
<template #default="scope">
|
||
<el-input v-model="scope.row.applyPart" size="small" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="检查方法" min-width="120">
|
||
<template #default="scope">
|
||
<!-- Bug #384修复: 显示检查方法名称,不显示套餐名称 -->
|
||
<span v-if="scope.row.selectedMethod">
|
||
{{ scope.row.selectedMethod.name }}
|
||
</span>
|
||
<span v-else-if="scope.row.methods && scope.row.methods.length > 0" style="color: #909399;">
|
||
未选择
|
||
</span>
|
||
<span v-else style="color: #c0c4cc;">-</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="单位" prop="unit" width="55" align="center" />
|
||
<el-table-column label="总量" prop="quantity" width="70" align="center">
|
||
<template #default="scope">
|
||
<el-input-number v-model="scope.row.quantity" :min="1" size="small" :controls="false" style="width:100%" />
|
||
</template>
|
||
</el-table-column>
|
||
<!-- Bug #384修复: 单价显示套餐价格(如果选中)或部位价格 -->
|
||
<el-table-column label="单价" width="75" align="right">
|
||
<template #default="scope">
|
||
{{ scope.row.selectedMethod?.packagePrice || scope.row.price }}
|
||
</template>
|
||
</el-table-column>
|
||
<!-- Bug #384修复: 金额使用有效价格计算 -->
|
||
<el-table-column label="金额" width="80" align="right">
|
||
<template #default="scope">
|
||
{{ ((scope.row.selectedMethod?.packagePrice || scope.row.price || 0) * (scope.row.quantity || 1)).toFixed(2) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="类型" prop="checkType" width="70" align="center" />
|
||
<el-table-column label="国码" prop="nationalCode" width="70" align="center" />
|
||
<el-table-column label="自费" width="50" align="center">
|
||
<template #default>
|
||
<el-checkbox disabled />
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="total-row">
|
||
合计:<span class="total-amount">{{ totalAmountCalc }}</span>
|
||
</div>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</div>
|
||
|
||
<!-- 右:检查项目分类面板 -->
|
||
<div class="category-panel">
|
||
<div class="panel-top">
|
||
<!-- 左侧:分类搜索 + 折叠树 -->
|
||
<div class="category-left">
|
||
<div class="panel-label">检查项目分类</div>
|
||
<el-input
|
||
v-model="dictSearchKey"
|
||
placeholder="搜索检查项目(支持拼音首字母)"
|
||
prefix-icon="Search"
|
||
clearable
|
||
size="small"
|
||
class="search-input"
|
||
/>
|
||
<!-- 分类折叠列表 -->
|
||
<div class="collapse-scroll" v-loading="dictLoading">
|
||
<div v-if="filteredCategoryList.length === 0" class="empty-hint">
|
||
{{ dictLoading ? '' : '暂无检查项目,请在"检查项目设置"中配置' }}
|
||
</div>
|
||
<el-collapse v-else v-model="activeNames">
|
||
<el-collapse-item
|
||
v-for="cat in filteredCategoryList"
|
||
:key="cat.typeId"
|
||
:name="cat.typeId"
|
||
>
|
||
<template #title>
|
||
<span class="cat-title">{{ cat.categoryName }}</span>
|
||
</template>
|
||
<div
|
||
v-for="item in cat.items"
|
||
:key="item.id"
|
||
class="item-row"
|
||
>
|
||
<el-checkbox
|
||
v-model="item.checked"
|
||
@change="(val) => handleItemSelect(val, item, cat)"
|
||
class="item-checkbox"
|
||
>
|
||
{{ item.name }}
|
||
</el-checkbox>
|
||
<span class="item-price">¥{{ item.price }}/{{ item.unit || "次" }}</span>
|
||
</div>
|
||
</el-collapse-item>
|
||
</el-collapse>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右侧:已选择 项目卡片(可展开显示检查方法) -->
|
||
<div class="selected-panel">
|
||
<div class="panel-label">已选择:</div>
|
||
<div class="selected-tags">
|
||
<div v-if="selectedItems.length === 0" class="empty-selected">–</div>
|
||
<div
|
||
v-else
|
||
v-for="(item, idx) in selectedItems"
|
||
:key="idx"
|
||
class="selected-item-card"
|
||
>
|
||
<!-- Bug #384修复: 项目卡片头部,可展开/收起 -->
|
||
<div class="card-header" @click="toggleItemExpand(item)">
|
||
<span class="card-name">{{ item.name }}</span>
|
||
<span class="card-price">¥{{ item.price }}</span>
|
||
<!-- 展开图标 -->
|
||
<el-icon :class="['expand-icon', { expanded: item.expanded }]">
|
||
<ArrowDown v-if="!item.expanded" />
|
||
<ArrowUp v-if="item.expanded" />
|
||
</el-icon>
|
||
<!-- 删除按钮 -->
|
||
<el-button link type="danger" size="small" @click.stop="handleRemoveItem(idx, item)">
|
||
<el-icon><Close /></el-icon>
|
||
</el-button>
|
||
</div>
|
||
<!-- Bug #384修复: 展开后显示检查方法勾选框列表 -->
|
||
<div v-if="item.expanded && item.methods && item.methods.length > 0" class="method-list">
|
||
<div
|
||
v-for="method in item.methods"
|
||
:key="method.id"
|
||
class="method-option"
|
||
>
|
||
<el-checkbox
|
||
:model-value="item.selectedMethod?.id === method.id"
|
||
@change="(val) => selectMethodCheckbox(val, item, method)"
|
||
>
|
||
<span class="method-name">{{ method.name }}</span>
|
||
<span class="method-price">¥{{ method.packagePrice || item.price }}</span>
|
||
</el-checkbox>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, computed, watch, onMounted, nextTick } from 'vue';
|
||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||
import { Printer, Delete, ArrowDown, ArrowUp, Close } from '@element-plus/icons-vue';
|
||
import useUserStore from '@/store/modules/user';
|
||
import request from '@/utils/request';
|
||
import { listCheckMethod, searchCheckMethod } from '@/api/system/checkType';
|
||
import { getEncounterDiagnosis } from '../api.js';
|
||
|
||
const props = defineProps({
|
||
patientInfo: { type: Object, default: () => ({}) },
|
||
activeTab: { type: String, default: '' }
|
||
});
|
||
|
||
// 保存成功后通知父组件刷新医嘱列表
|
||
const emit = defineEmits(['saved']);
|
||
|
||
const userStore = useUserStore();
|
||
const loading = ref(false);
|
||
const dictLoading = ref(false);
|
||
const activeDetailTab = ref('applyForm');
|
||
const applicationList = ref([]);
|
||
const selectedItems = ref([]);
|
||
|
||
// 🔧 BugFix#426: 懒加载套餐明细
|
||
async function loadPackageDetails(row, treeNode, resolve) {
|
||
if (!row.isPackage || !row.packageId) {
|
||
resolve([]);
|
||
return;
|
||
}
|
||
try {
|
||
const res = await request({
|
||
url: `/exam/package/${row.packageId}/details`,
|
||
method: 'get'
|
||
});
|
||
if (res.code === 200 && res.data) {
|
||
const children = res.data.map(item => ({
|
||
...item,
|
||
name: item.name || item.itemName,
|
||
unit: item.unit || '次',
|
||
price: item.price || item.itemPrice || 0,
|
||
quantity: row.quantity || 1,
|
||
isPackageDetail: true
|
||
}));
|
||
resolve(children);
|
||
} else {
|
||
resolve([]);
|
||
}
|
||
} catch (err) {
|
||
console.error('加载套餐明细失败:', err);
|
||
resolve([]);
|
||
}
|
||
}
|
||
const detailTableRef = ref(null);
|
||
const formRef = ref(null);
|
||
|
||
// ====== 表单数据 ======
|
||
const form = reactive({
|
||
applyNo: '',
|
||
patientName: '',
|
||
patientId: '',
|
||
visitNo: '',
|
||
applyDeptCode: '',
|
||
performDeptCode: '',
|
||
applyDocCode: '',
|
||
applyTime: '',
|
||
medicalrecordNumber: '',
|
||
natureofCost: '自费医疗',
|
||
clinicDesc: '',
|
||
contraindication: '',
|
||
medicalHistorySummary: '',
|
||
purposeofInspection: '',
|
||
inspectionArea: '',
|
||
inspectionMethod: '',
|
||
applyRemark: '',
|
||
clinicalDiag: '',
|
||
purposeDesc: '',
|
||
isUrgent: 0,
|
||
pregnancyState: 0,
|
||
allergyDesc: '',
|
||
applyStatus: 0,
|
||
isCharged: 0,
|
||
isRefunded: 0,
|
||
isExecuted: 0,
|
||
examTypeCode: '', // 检查类型编码,必填字段,保存时从已选项目自动推导
|
||
selectedMethodDisplay: '' // Bug #384修复: 检查方法显示字段(联动)
|
||
});
|
||
|
||
const rules = {
|
||
natureofCost: [{ required: true, message: '请选择费用性质', trigger: 'change' }],
|
||
applyDeptCode: [{ required: true, message: '请输入申请科室', trigger: 'blur' }],
|
||
applyDocCode: [{ required: true, message: '请输入申请医生', trigger: 'blur' }],
|
||
performDeptCode: [{ required: true, message: '请输入执行科室', trigger: 'blur' }],
|
||
clinicDesc: [{ required: true, message: '请输入诊断描述', trigger: 'blur' }],
|
||
clinicalDiag: [{ required: true, message: '请输入临床诊断', trigger: 'blur' }],
|
||
medicalHistorySummary: [{ required: true, message: '请输入病史摘要', trigger: 'blur' }],
|
||
purposeDesc: [{ required: true, message: '请输入检查目的', trigger: 'blur' }]
|
||
};
|
||
|
||
// ====== 检查项目分类 ======
|
||
const categoryList = ref([]); // 原始分类+项目数据
|
||
const dictSearchKey = ref('');
|
||
const activeNames = ref([]); // 当前展开的折叠项
|
||
|
||
const allMethods = ref([]);
|
||
|
||
// ====== 科室下拉(来源:科室管理)======
|
||
const orgLoading = ref(false);
|
||
const orgOptions = ref([]); // { label, value }
|
||
const orgFilteredOptions = ref([]); // 展示用(截断前200条)
|
||
|
||
// 加载所有检查方法
|
||
async function loadAllMethods() {
|
||
try {
|
||
const res = await listCheckMethod(); // 使用已导入的或者直接利用 request 请求
|
||
let methods = [];
|
||
if (res && res.data) {
|
||
if (Array.isArray(res.data)) {
|
||
methods = res.data;
|
||
} else if (res.data.records) {
|
||
methods = res.data.records;
|
||
} else if (res.data.data && Array.isArray(res.data.data)) {
|
||
methods = res.data.data;
|
||
}
|
||
} else if (Array.isArray(res)) {
|
||
methods = res;
|
||
} else if (res && res.rows) {
|
||
methods = res.rows;
|
||
}
|
||
allMethods.value = methods;
|
||
} catch (err) {
|
||
console.error('加载检查方法失败', err);
|
||
}
|
||
}
|
||
|
||
onMounted(async () => {
|
||
await loadOrgOptions();
|
||
await loadAllMethods();
|
||
await loadCategoryList();
|
||
});
|
||
|
||
async function loadOrgOptions() {
|
||
orgLoading.value = true;
|
||
try {
|
||
const res = await request({
|
||
url: '/base-data-manage/organization/organization',
|
||
method: 'get',
|
||
});
|
||
const records = res?.data?.records || res?.data || [];
|
||
|
||
const flat = [];
|
||
const walk = (nodes) => {
|
||
if (!Array.isArray(nodes)) return;
|
||
for (const n of nodes) {
|
||
if (!n) continue;
|
||
// 约定:typeEnum=2 为科室;若没有 typeEnum 也兜底收集
|
||
if (n.name && (n.typeEnum === 2 || n.typeEnum === '2' || n.typeEnum == null)) {
|
||
flat.push({ label: n.name, value: n.name });
|
||
}
|
||
if (Array.isArray(n.children) && n.children.length > 0) walk(n.children);
|
||
}
|
||
};
|
||
walk(records);
|
||
|
||
// 去重 + 排序
|
||
const uniq = Array.from(new Map(flat.map(o => [o.value, o])).values())
|
||
.filter(o => o?.value)
|
||
.sort((a, b) => (a.label || '').localeCompare(b.label || '', 'zh-CN'));
|
||
|
||
orgOptions.value = uniq;
|
||
orgFilteredOptions.value = uniq.slice(0, 200);
|
||
} catch (e) {
|
||
console.error('加载科室列表失败', e);
|
||
orgOptions.value = [];
|
||
orgFilteredOptions.value = [];
|
||
} finally {
|
||
orgLoading.value = false;
|
||
}
|
||
}
|
||
|
||
function handleOrgRemoteSearch(keyword) {
|
||
const key = (keyword || '').trim().toLowerCase();
|
||
if (!key) {
|
||
orgFilteredOptions.value = orgOptions.value.slice(0, 200);
|
||
return;
|
||
}
|
||
orgFilteredOptions.value = orgOptions.value
|
||
.filter((o) => (o.label || '').toLowerCase().includes(key))
|
||
.slice(0, 200);
|
||
}
|
||
|
||
// 动态可用的检查方法(根据已选部位所属的检查类型进行过滤)
|
||
const normalizeTypeValue = value => String(value ?? '').trim().toLowerCase();
|
||
|
||
const availableMethods = computed(() => {
|
||
// 获取当前已选部位的检查类型(可取第一个选中的部位的 checkType)
|
||
const currentType = form.examTypeCode || (selectedItems.value.length > 0 ? selectedItems.value[0].checkType : '');
|
||
const normalizedCurrentType = normalizeTypeValue(currentType);
|
||
if (normalizedCurrentType) {
|
||
// 兼容脏数据:method 的类型可能落在 checkType/type/typeCode/code/typeName/categoryName 中
|
||
const filtered = allMethods.value.filter(m => {
|
||
const typeCandidates = [
|
||
m.checkType,
|
||
m.type,
|
||
m.typeCode,
|
||
m.code,
|
||
m.typeName,
|
||
m.categoryName
|
||
].map(normalizeTypeValue).filter(Boolean);
|
||
return typeCandidates.includes(normalizedCurrentType);
|
||
});
|
||
return filtered.length > 0 ? filtered : allMethods.value;
|
||
}
|
||
return allMethods.value;
|
||
});
|
||
|
||
// 当可选方法列表改变时,如果当前选中的方法不在新列表中,则清空
|
||
watch(availableMethods, (newMethods) => {
|
||
if (form.inspectionMethod && !newMethods.find(m => m.name === form.inspectionMethod)) {
|
||
form.inspectionMethod = '';
|
||
}
|
||
});
|
||
|
||
/**
|
||
* 加载检查类型(分类)和检查项目(部位/项目),按类型分组展示
|
||
*/
|
||
async function loadCategoryList() {
|
||
dictLoading.value = true;
|
||
try {
|
||
// 1. 加载检查类型(分类名称),只取父级
|
||
const typeRes = await request({
|
||
url: '/system/check-type/list',
|
||
method: 'get',
|
||
params: { pageNo: 1, pageSize: 500 } // 取全量分类数据
|
||
});
|
||
let types = [];
|
||
if (typeRes && typeRes.data) {
|
||
if (Array.isArray(typeRes.data)) {
|
||
types = typeRes.data;
|
||
} else if (typeRes.data.records) {
|
||
types = typeRes.data.records;
|
||
} else if (typeRes.data.data && Array.isArray(typeRes.data.data)) {
|
||
types = typeRes.data.data;
|
||
}
|
||
} else if (Array.isArray(typeRes)) {
|
||
types = typeRes;
|
||
} else if (typeRes && typeRes.rows) {
|
||
types = typeRes.rows;
|
||
}
|
||
|
||
// 2. 加载检查项目(检查部位项目)
|
||
const partRes = await request({ url: '/check/part/list', method: 'get' });
|
||
let parts = [];
|
||
if (partRes && partRes.data) {
|
||
if (Array.isArray(partRes.data)) {
|
||
parts = partRes.data;
|
||
} else if (partRes.data.records) {
|
||
parts = partRes.data.records;
|
||
} else if (partRes.data.data && Array.isArray(partRes.data.data)) {
|
||
parts = partRes.data.data;
|
||
}
|
||
} else if (Array.isArray(partRes)) {
|
||
parts = partRes;
|
||
} else if (partRes && partRes.rows) {
|
||
parts = partRes.rows;
|
||
}
|
||
|
||
// 3. 按 checkType 归类
|
||
const dict = [];
|
||
for (const t of types) {
|
||
dict.push({
|
||
typeId: t.id,
|
||
typeCode: t.code, // 保存 code 用于后备匹配
|
||
orgType: t.type, // 保存 type 用于后备匹配
|
||
typeName: t.name, // 保存 name
|
||
categoryName: t.name,
|
||
// “检查类型管理”里配置的执行科室(图三)
|
||
performDeptName: t.department || '',
|
||
items: []
|
||
});
|
||
}
|
||
const unclassified = [];
|
||
for (const p of parts) {
|
||
const mapped = {
|
||
id: p.id,
|
||
name: p.name,
|
||
price: p.price || 0,
|
||
serviceFee: p.serviceFee || 0,
|
||
unit: '次',
|
||
checkType: p.checkType || '',
|
||
nationalCode: p.nationalCode || '',
|
||
packageName: p.packageName || '',
|
||
checked: false
|
||
};
|
||
|
||
// 增强匹配逻辑:部位的 checkType (如 'ECG', 'CT') 优先去匹配大类的 orgType,
|
||
// 如果大类的 type 字段脏了(比如填了中文),则尝试匹配 code,甚至是分类名称
|
||
const target = dict.find(d =>
|
||
d.orgType === p.checkType ||
|
||
d.typeCode === p.checkType ||
|
||
d.typeName === p.checkType
|
||
);
|
||
|
||
if (target) target.items.push(mapped);
|
||
else unclassified.push(mapped);
|
||
}
|
||
if (unclassified.length > 0) {
|
||
dict.push({ typeId: 'uncls', typeCode: '', categoryName: '其他', items: unclassified });
|
||
}
|
||
categoryList.value = dict.filter(d => d.items.length > 0);
|
||
|
||
// 默认展开第一个
|
||
if (categoryList.value.length > 0) {
|
||
activeNames.value = [categoryList.value[0].typeId];
|
||
}
|
||
} catch (err) {
|
||
console.error('加载检查项目分类失败', err);
|
||
} finally {
|
||
dictLoading.value = false;
|
||
}
|
||
}
|
||
|
||
/** 关键词过滤后的分类列表 */
|
||
const filteredCategoryList = computed(() => {
|
||
if (!dictSearchKey.value) return categoryList.value;
|
||
const key = dictSearchKey.value.toLowerCase();
|
||
return categoryList.value.map(cat => ({
|
||
...cat,
|
||
items: cat.items.filter(item => (item.name || '').toLowerCase().includes(key))
|
||
})).filter(cat => cat.items.length > 0);
|
||
});
|
||
|
||
// ====== 合计 ======
|
||
// Bug #384修复: 如果选中了检查方法,使用套餐价格;否则使用部位价格
|
||
const totalAmountCalc = computed(() => {
|
||
const total = selectedItems.value.reduce((sum, item) => {
|
||
const effectivePrice = item.selectedMethod?.packagePrice || item.price;
|
||
return sum + (effectivePrice * (item.quantity || 1));
|
||
}, 0);
|
||
return total.toFixed(2);
|
||
});
|
||
|
||
// 监听已选项:自动更新申检部位
|
||
watch(selectedItems, () => {
|
||
form.inspectionArea = selectedItems.value.map(i => i.name).join('+');
|
||
form.isCharged = selectedItems.value.length > 0 ? 1 : 0;
|
||
}, { deep: true });
|
||
|
||
// 监听患者变化
|
||
watch(() => props.patientInfo, (newVal) => {
|
||
if (newVal?.encounterId) {
|
||
initPatientForm(newVal);
|
||
getList();
|
||
}
|
||
}, { immediate: true, deep: true });
|
||
|
||
watch(() => props.activeTab, async (val) => {
|
||
if (val === 'examination') {
|
||
getList();
|
||
// 切换到检查页签时,重新获取临床诊断(确保与诊断页签同步)
|
||
if (props.patientInfo?.encounterId) {
|
||
await loadClinicalDiag();
|
||
}
|
||
}
|
||
});
|
||
|
||
function initPatientForm(patient) {
|
||
form.patientName = patient.patientName || '';
|
||
// 就诊卡号应取值于 identifierNo,而非 busNo(busNo 是病历号)
|
||
form.medicalrecordNumber = patient.identifierNo || patient.visitNo || '';
|
||
form.patientId = patient.patientId || '';
|
||
form.visitNo = patient.visitNo || '';
|
||
form.applyDeptCode = userStore.orgName || patient.organizationName || '';
|
||
form.applyDocCode = userStore.nickName || '';
|
||
}
|
||
|
||
// 加载临床诊断:获取患者主诊断并填充到临床诊断字段
|
||
async function loadClinicalDiag() {
|
||
if (!props.patientInfo?.encounterId) return;
|
||
try {
|
||
const res = await getEncounterDiagnosis(props.patientInfo.encounterId);
|
||
const diagnoses = res.data || res.rows || res;
|
||
if (Array.isArray(diagnoses) && diagnoses.length > 0) {
|
||
// Bug #380, #381 修复: 主诊断字段名为 maindiseFlag (后端 DiagnosisQueryDto 定义)
|
||
const mainDiag = diagnoses.find(d => d.maindiseFlag === 1 || d.maindiseFlag === '1');
|
||
// 如果有主诊断使用主诊断,否则使用第一个诊断
|
||
const targetDiag = mainDiag || diagnoses[0];
|
||
// 优先使用 diagnosisName,其次是 conditionName 或 name
|
||
form.clinicalDiag = targetDiag.diagnosisName || targetDiag.conditionName || targetDiag.name || '';
|
||
} else {
|
||
// 如果没有诊断,清空临床诊断字段
|
||
form.clinicalDiag = '';
|
||
}
|
||
} catch (err) {
|
||
console.error('加载临床诊断失败', err);
|
||
// 获取失败时不阻断用户操作,保持字段为空
|
||
}
|
||
}
|
||
|
||
// ====== 申请单 CRUD ======
|
||
function getList() {
|
||
loading.value = true;
|
||
request({
|
||
url: '/exam/apply/list',
|
||
method: 'get',
|
||
// 默认只展示本次就诊(encounterId)产生的检查申请单
|
||
params: { encounterId: props.patientInfo?.encounterId || '' }
|
||
}).then(res => {
|
||
applicationList.value = res.rows || res.data || [];
|
||
}).catch(err => console.error('获取申请单列表失败', err))
|
||
.finally(() => { loading.value = false; });
|
||
}
|
||
|
||
function handleAdd() {
|
||
formRef.value?.resetFields();
|
||
Object.assign(form, {
|
||
applyNo: '',
|
||
patientId: props.patientInfo?.patientId || '',
|
||
visitNo: props.patientInfo?.visitNo || '',
|
||
// 保留患者姓名和就诊卡号,不应重置为空
|
||
patientName: props.patientInfo?.patientName || '',
|
||
medicalrecordNumber: props.patientInfo?.identifierNo || '',
|
||
applyDeptCode: userStore.orgName || '',
|
||
performDeptCode: '',
|
||
applyDocCode: userStore.nickName || '',
|
||
applyTime: new Date().toISOString().split('T')[0] + ' 12:00:00',
|
||
natureofCost: '自费医疗',
|
||
clinicDesc: '', contraindication: '', medicalHistorySummary: '',
|
||
purposeofInspection: '', inspectionArea: '', inspectionMethod: '',
|
||
applyRemark: '', clinicalDiag: '', purposeDesc: '',
|
||
isUrgent: 0, pregnancyState: 0, allergyDesc: '',
|
||
applyStatus: 0, isCharged: 0, isRefunded: 0, isExecuted: 0,
|
||
examTypeCode: '',
|
||
selectedMethodDisplay: '' // Bug #384修复: 重置检查方法显示
|
||
});
|
||
selectedItems.value = [];
|
||
resetCategoryChecked();
|
||
activeDetailTab.value = 'applyForm';
|
||
// 自动加载临床诊断
|
||
loadClinicalDiag();
|
||
}
|
||
|
||
function handleSave() {
|
||
formRef.value.validate(valid => {
|
||
if (!valid) return;
|
||
if (selectedItems.value.length === 0) {
|
||
ElMessage.warning('请至少选择一个检查明细项目');
|
||
return;
|
||
}
|
||
// 从已选项目推导检查类型编码(取第一个项目的 checkType,如 CT / ECG / GI)
|
||
const firstCheckType = selectedItems.value[0]?.checkType || 'unknown';
|
||
form.examTypeCode = firstCheckType;
|
||
|
||
// 如果有选中的检查方法,更新表单中的检查方法字段(取第一个选中项目的检查方法)
|
||
const firstItemWithMethod = selectedItems.value.find(item => item.selectedMethod);
|
||
if (firstItemWithMethod?.selectedMethod) {
|
||
form.inspectionMethod = firstItemWithMethod.selectedMethod.name;
|
||
}
|
||
|
||
const payload = {
|
||
...form,
|
||
encounterId: props.patientInfo?.encounterId || null,
|
||
patientIdNum: props.patientInfo?.patientId || null,
|
||
items: selectedItems.value.map((item, index) => ({
|
||
itemCode: String(item.id),
|
||
itemName: item.name,
|
||
bodyPartCode: item.checkType || 'unknown',
|
||
// Bug #384修复: 如果选中了检查方法且有套餐价格,使用套餐价格;否则使用部位价格
|
||
itemFee: item.selectedMethod?.packagePrice || item.price,
|
||
performDeptCode: form.performDeptCode || '',
|
||
itemStatus: 0,
|
||
itemSeq: index + 1,
|
||
// 检查方法信息
|
||
checkMethodId: item.selectedMethod?.id || null,
|
||
checkMethodName: item.selectedMethod?.name || null,
|
||
checkMethodCode: item.selectedMethod?.code || null,
|
||
checkMethodPackageName: item.selectedMethod?.packageName || null // Bug #384修复: 保存套餐名称
|
||
}))
|
||
};
|
||
request({
|
||
url: '/exam/apply',
|
||
method: payload.applyNo ? 'put' : 'post',
|
||
data: payload
|
||
}).then(res => {
|
||
ElMessage.success('保存成功');
|
||
getList();
|
||
if (res.data) form.applyNo = res.data;
|
||
// 通知父组件刷新医嘱列表
|
||
emit('saved');
|
||
});
|
||
});
|
||
}
|
||
|
||
function handleRowClick(row) {
|
||
Object.assign(form, row);
|
||
form.selectedMethodDisplay = ''; // Bug #384修复: 先清空,后面根据回充数据更新
|
||
selectedItems.value = [];
|
||
activeDetailTab.value = 'applyForm';
|
||
request({ url: `/exam/apply/${row.applyNo}`, method: 'get' }).then(async res => {
|
||
const d = res.data || res;
|
||
if (d.data) Object.assign(form, d.data);
|
||
if (d.items && Array.isArray(d.items)) {
|
||
try {
|
||
// 为每个项目加载检查方法
|
||
const itemsWithMethods = await Promise.all(d.items.map(async m => {
|
||
const item = {
|
||
id: m.itemCode, name: m.itemName,
|
||
price: m.itemFee || 0, quantity: 1,
|
||
serviceFee: 0, unit: '次',
|
||
applyPart: m.itemName,
|
||
checkType: m.bodyPartCode || '',
|
||
nationalCode: '', checked: true,
|
||
methods: [],
|
||
selectedMethod: null,
|
||
expanded: false // Bug #384修复: 添加展开状态
|
||
};
|
||
// 加载该项目的检查方法
|
||
if (m.bodyPartCode) {
|
||
try {
|
||
const methodRes = await searchCheckMethod({ checkType: m.bodyPartCode });
|
||
// Bug #384修复: 正确解析 API 返回结构
|
||
let methodData = methodRes?.data?.data || methodRes?.data || methodRes?.rows || methodRes;
|
||
if (!Array.isArray(methodData) && methodRes?.data && Array.isArray(methodRes.data.data)) {
|
||
methodData = methodRes.data.data;
|
||
}
|
||
if (Array.isArray(methodData)) {
|
||
item.methods = methodData.map(md => ({
|
||
id: md.id,
|
||
name: md.name,
|
||
code: md.code,
|
||
price: m.itemFee || 0, // fallback 到已保存的价格
|
||
packageName: md.packageName || '',
|
||
packagePrice: md.packagePrice || null, // Bug #384修复: 套餐价格
|
||
serviceFee: md.serviceFee || null
|
||
}));
|
||
// 如果有已保存的检查方法信息,尝试匹配
|
||
if (m.checkMethodId) {
|
||
item.selectedMethod = item.methods.find(md => md.id === m.checkMethodId) || null;
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('加载检查方法失败', err);
|
||
// 单个项目加载失败不影响其他项目,继续返回 item
|
||
}
|
||
}
|
||
return item;
|
||
}));
|
||
selectedItems.value = itemsWithMethods;
|
||
syncCategoryChecked();
|
||
// Bug #384修复: 回充后更新检查方法显示
|
||
updateMethodDisplay();
|
||
// 修复【#408】:加载申请单详情后自动切换到检查明细页签,确保已加载的明细数据可见
|
||
activeDetailTab.value = 'applyDetail';
|
||
} catch (err) {
|
||
console.error('加载申请单详情失败', err);
|
||
ElMessage.error('加载申请单详情失败');
|
||
}
|
||
}
|
||
}).catch(err => {
|
||
console.error('获取申请单详情失败', err);
|
||
ElMessage.error('获取申请单详情失败');
|
||
});
|
||
}
|
||
|
||
function handlePrint(row) { ElMessage.info('打印申请单:' + row.applyNo); }
|
||
|
||
function handleDelete(row) {
|
||
ElMessageBox.confirm('确认删除该检查申请单吗?', '警告', { type: 'warning' }).then(() => {
|
||
request({ url: `/exam/apply/${row.applyNo}`, method: 'delete' }).then(() => {
|
||
ElMessage.success('删除成功');
|
||
getList();
|
||
if (form.applyNo === row.applyNo) handleAdd();
|
||
});
|
||
});
|
||
}
|
||
|
||
// ====== 勾选逻辑 ======
|
||
async function handleItemSelect(checked, item, cat) {
|
||
if (checked) {
|
||
// Bug #384修复: 检查方法表的 checkType 字段关联的是检查类型的 name(中文名称,如"心电图")
|
||
const effectiveCheckType = cat?.typeName || cat?.categoryName || '';
|
||
|
||
// 查询该检查类型对应的检查方法
|
||
let methods = [];
|
||
try {
|
||
if (effectiveCheckType) {
|
||
const res = await searchCheckMethod({ checkType: effectiveCheckType });
|
||
// Bug #384修复: API 返回结构可能是 {data: {data: Array}} 或 {data: Array}
|
||
let data = res?.data?.data || res?.data || res?.rows || res;
|
||
if (!Array.isArray(data) && res?.data && Array.isArray(res.data.data)) {
|
||
data = res.data.data;
|
||
}
|
||
if (Array.isArray(data)) {
|
||
methods = data.map(m => ({
|
||
id: m.id,
|
||
name: m.name,
|
||
code: m.code,
|
||
price: m.price || item.price, // fallback 到项目价格
|
||
packageName: m.packageName || '',
|
||
packagePrice: m.packagePrice || null, // Bug #384修复: 套餐价格
|
||
serviceFee: m.serviceFee || null // Bug #384修复: 服务费
|
||
}));
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('加载检查方法失败', err);
|
||
}
|
||
|
||
if (selectedItems.value.length > 0) {
|
||
const currentCategory = selectedItems.value[0].checkType;
|
||
const newCategory = cat.typeCode || '';
|
||
if (currentCategory !== newCategory) {
|
||
ElMessage.warning('一个检查单不能同时选择多个项目类型的检查项目');
|
||
item.checked = false;
|
||
return;
|
||
}
|
||
}
|
||
|
||
selectedItems.value.push({
|
||
id: item.id, name: item.name,
|
||
price: item.price, quantity: 1,
|
||
serviceFee: item.serviceFee || 0,
|
||
unit: item.unit || '次',
|
||
applyPart: item.name,
|
||
checkType: effectiveCheckType, // Bug #384修复: 使用有效的 checkType
|
||
nationalCode: item.nationalCode || '',
|
||
checked: true,
|
||
methods: methods,
|
||
selectedMethod: null,
|
||
expanded: false // Bug #384修复: 新增展开状态,默认不展开
|
||
});
|
||
|
||
// 自动回填执行科室:按检查项目类型 → 检查类型管理里配置的执行科室
|
||
if (selectedItems.value.length === 1 && cat?.performDeptName) {
|
||
form.performDeptCode = cat.performDeptName;
|
||
} else if (!form.performDeptCode && cat?.performDeptName) {
|
||
form.performDeptCode = cat.performDeptName;
|
||
}
|
||
|
||
// 如果有且仅有一个检查方法,自动选中并更新显示
|
||
if (methods.length === 1) {
|
||
const lastIdx = selectedItems.value.length - 1;
|
||
selectedItems.value[lastIdx].selectedMethod = methods[0];
|
||
updateMethodDisplay(); // Bug #384修复: 联动更新显示
|
||
}
|
||
} else {
|
||
const idx = selectedItems.value.findIndex(s => s.id === item.id);
|
||
if (idx > -1) selectedItems.value.splice(idx, 1);
|
||
|
||
if (selectedItems.value.length === 0) {
|
||
form.performDeptCode = '';
|
||
form.examTypeCode = '';
|
||
}
|
||
}
|
||
// Bug #382 修复:移除自动切换页签逻辑,保持当前页签状态
|
||
}
|
||
|
||
// Bug #384修复: 展开/收起项目卡片
|
||
function toggleItemExpand(item) {
|
||
item.expanded = !item.expanded;
|
||
}
|
||
|
||
// Bug #384修复: 勾选框选择检查方法(单选逻辑)
|
||
function selectMethodCheckbox(checked, item, method) {
|
||
if (checked) {
|
||
item.selectedMethod = method;
|
||
} else {
|
||
item.selectedMethod = null;
|
||
}
|
||
// 联动更新表单检查方法显示字段
|
||
updateMethodDisplay();
|
||
}
|
||
|
||
// Bug #384修复: 更新检查方法显示字段(联动)
|
||
function updateMethodDisplay() {
|
||
// 找到第一个有选中检查方法的项目
|
||
const itemWithMethod = selectedItems.value.find(item => item.selectedMethod);
|
||
if (itemWithMethod?.selectedMethod) {
|
||
form.selectedMethodDisplay = itemWithMethod.selectedMethod.name; // 显示检查方法名称,不显示套餐名称
|
||
} else {
|
||
form.selectedMethodDisplay = '';
|
||
}
|
||
}
|
||
|
||
// 选择检查方法
|
||
function selectMethod(item, method) {
|
||
if (item.selectedMethod?.id === method.id) {
|
||
item.selectedMethod = null;
|
||
} else {
|
||
item.selectedMethod = method;
|
||
}
|
||
// Bug #384修复: 联动更新表单检查方法显示字段
|
||
updateMethodDisplay();
|
||
}
|
||
|
||
function handleRemoveItem(idx, item) {
|
||
selectedItems.value.splice(idx, 1);
|
||
// 取消对应 category 中的 checkbox
|
||
for (const cat of categoryList.value) {
|
||
const found = cat.items.find(x => x.id === item.id);
|
||
if (found) { found.checked = false; break; }
|
||
}
|
||
|
||
if (selectedItems.value.length === 0) {
|
||
form.performDeptCode = '';
|
||
form.examTypeCode = '';
|
||
form.selectedMethodDisplay = ''; // Bug #384修复: 清空检查方法显示
|
||
} else {
|
||
// Bug #384修复: 移除后重新计算检查方法显示
|
||
updateMethodDisplay();
|
||
}
|
||
}
|
||
|
||
function resetCategoryChecked() {
|
||
for (const cat of categoryList.value)
|
||
for (const item of cat.items) item.checked = false;
|
||
}
|
||
|
||
function syncCategoryChecked() {
|
||
resetCategoryChecked();
|
||
const ids = new Set(selectedItems.value.map(s => s.id));
|
||
for (const cat of categoryList.value)
|
||
for (const item of cat.items)
|
||
if (ids.has(item.id)) item.checked = true;
|
||
}
|
||
|
||
defineExpose({ getList });
|
||
</script>
|
||
|
||
<style scoped>
|
||
.exam-app-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
padding: 8px;
|
||
height: 100%;
|
||
background: #f0f2f5;
|
||
}
|
||
|
||
/* 顶部申请单列表 */
|
||
.top-section {
|
||
background: #fff;
|
||
border-radius: 4px;
|
||
padding: 10px 12px;
|
||
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||
}
|
||
.section-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
}
|
||
.section-title {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
.header-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
/* 底部区域:左表单 + 右分类 */
|
||
.bottom-section {
|
||
display: flex;
|
||
gap: 10px;
|
||
flex: 1;
|
||
min-height: 0;
|
||
}
|
||
|
||
/* 左:表单面板 */
|
||
.form-panel {
|
||
flex: 1;
|
||
background: #fff;
|
||
border-radius: 4px;
|
||
padding: 10px 12px;
|
||
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||
overflow-y: auto;
|
||
min-width: 0;
|
||
}
|
||
.form-tabs :deep(.el-tabs__header) {
|
||
margin-bottom: 10px;
|
||
}
|
||
.apply-form :deep(.el-form-item) {
|
||
margin-bottom: 10px;
|
||
}
|
||
.apply-form :deep(.el-form-item__label) {
|
||
font-size: 12px;
|
||
}
|
||
.total-row {
|
||
margin-top: 8px;
|
||
text-align: right;
|
||
font-size: 13px;
|
||
color: #606266;
|
||
}
|
||
.total-amount {
|
||
font-size: 15px;
|
||
font-weight: bold;
|
||
color: #f56c6c;
|
||
margin-left: 4px;
|
||
}
|
||
|
||
/* 右:分类面板 */
|
||
.category-panel {
|
||
width: 380px;
|
||
flex-shrink: 0;
|
||
background: #fff;
|
||
border-radius: 4px;
|
||
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
||
padding: 10px 12px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
.panel-top {
|
||
display: flex;
|
||
gap: 12px;
|
||
flex: 1;
|
||
min-height: 0;
|
||
}
|
||
.category-left {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.panel-label {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
margin-bottom: 6px;
|
||
}
|
||
.search-input {
|
||
margin-bottom: 8px;
|
||
}
|
||
.collapse-scroll {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
}
|
||
.empty-hint {
|
||
color: #909399;
|
||
font-size: 12px;
|
||
text-align: center;
|
||
padding: 20px 0;
|
||
}
|
||
|
||
/* 检查项目分类折叠 */
|
||
.cat-title {
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
color: #303133;
|
||
}
|
||
.item-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 3px 4px;
|
||
}
|
||
.item-row:hover {
|
||
background: #f5f7fa;
|
||
}
|
||
.item-checkbox {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
.item-checkbox :deep(.el-checkbox__label) {
|
||
font-size: 12px;
|
||
color: #303133;
|
||
}
|
||
.item-price {
|
||
font-size: 12px;
|
||
color: #1890FF;
|
||
flex-shrink: 0;
|
||
margin-left: 6px;
|
||
}
|
||
|
||
/* 已选择 tags */
|
||
.selected-panel {
|
||
width: 140px; /* Bug #384修复: 加宽以适应展开内容 */
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.selected-tags {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
.selected-tag {
|
||
max-width: 100%;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.empty-selected {
|
||
color: #c0c4cc;
|
||
font-size: 12px;
|
||
}
|
||
|
||
/* Bug #384修复: 已选择项目卡片(可展开) */
|
||
.selected-item-card {
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: #F5F5F5;
|
||
border-radius: 4px;
|
||
border: 1px solid #e4e7ed;
|
||
}
|
||
|
||
.selected-item-card .card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 8px 10px;
|
||
cursor: pointer;
|
||
gap: 4px;
|
||
}
|
||
|
||
.selected-item-card .card-header:hover {
|
||
background: #E6F7FF;
|
||
}
|
||
|
||
.card-name {
|
||
flex: 1;
|
||
font-size: 12px;
|
||
color: #303133;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.card-price {
|
||
font-size: 12px;
|
||
color: #1890FF;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.expand-icon {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
transition: transform 0.2s;
|
||
}
|
||
|
||
.expand-icon.expanded {
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
/* Bug #384修复: 检查方法勾选框列表 */
|
||
.method-list {
|
||
padding: 6px 10px;
|
||
background: #fff;
|
||
border-top: 1px solid #e4e7ed;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.method-option {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.method-option :deep(.el-checkbox__label) {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
width: 100%;
|
||
}
|
||
|
||
.method-option .method-name {
|
||
font-size: 11px;
|
||
color: #606266;
|
||
}
|
||
|
||
.method-option .method-price {
|
||
font-size: 11px;
|
||
color: #e6a23c;
|
||
font-weight: 500;
|
||
margin-left: 8px;
|
||
}
|
||
|
||
/* 折叠组件细节 */
|
||
:deep(.el-collapse) {
|
||
border: none;
|
||
}
|
||
:deep(.el-collapse-item__header) {
|
||
font-size: 13px;
|
||
padding: 6px 0;
|
||
height: auto;
|
||
line-height: 1.5;
|
||
}
|
||
:deep(.el-collapse-item__content) {
|
||
padding-bottom: 4px;
|
||
}
|
||
:deep(.el-collapse-item__wrap) {
|
||
border: none;
|
||
}
|
||
</style>
|