Fix Bug #550: fallback修复

This commit is contained in:
2026-05-27 05:41:56 +08:00
parent 97e9fb944c
commit 829b652568

View File

@@ -1,162 +1,228 @@
<template> <template>
<div class="exam-apply-container"> <div class="exam-apply">
<!-- 左侧检查项目分类 --> <div class="exam-layout">
<el-card class="category-panel" shadow="never"> <!-- 左侧检查项目分类与项目树 -->
<template #header>检查项目分类</template> <div class="tree-section">
<el-tree <el-tree
:data="categories" ref="itemTreeRef"
node-key="id" :data="itemTreeData"
highlight-current node-key="id"
@node-click="handleCategorySelect" show-checkbox
/> :check-strictly="true"
</el-card> :default-expand-all="false"
:props="itemTreeProps"
<!-- 中间检查项目列表 --> @check="handleTreeCheck"
<el-card class="item-panel" shadow="never"> />
<template #header>检查项目</template>
<div class="item-list">
<div v-for="item in currentItems" :key="item.id" class="item-row">
<el-checkbox
v-model="item.checked"
@change="handleItemCheck(item)"
/>
<el-tooltip :content="item.name" placement="top" :show-after="300">
<span class="item-name">{{ cleanName(item.name) }}</span>
</el-tooltip>
</div>
</div> </div>
</el-card>
<!-- 下方已选择区域 --> <!-- 右侧已选项目与明细 -->
<el-card class="selected-panel" shadow="never"> <div class="selected-section">
<template #header>已选择</template> <div class="section-title">已选择</div>
<div v-if="selectedItems.length === 0" class="empty-tip">暂无已选项目</div> <div class="selected-tags">
<el-collapse v-else v-model="activeCollapseNames" accordion> <el-tag
<el-collapse-item v-for="group in selectedGroups"
v-for="sel in selectedItems" :key="group.id"
:key="sel.id" closable
:name="sel.id" size="default"
> show-overflow-tooltip
<template #title> :title="group.fullName"
<el-tooltip :content="sel.name" placement="top" :show-after="300"> @close="removeGroup(group.id)"
<span class="collapse-title">{{ cleanName(sel.name) }}</span> >
</el-tooltip> {{ group.displayName }}
</template> </el-tag>
<div class="method-container"> </div>
<div v-for="method in sel.methods" :key="method.id" class="method-row">
<el-checkbox <el-collapse v-model="activePanels" class="details-collapse">
v-model="method.checked" <el-collapse-item
@change="handleMethodCheck(sel, method)" v-for="group in selectedGroups"
/> :key="group.id"
<span class="method-name">{{ method.name }}</span> :name="group.id"
>
<template #title>
<span class="collapse-title">{{ group.displayName }}</span>
</template>
<div class="method-container">
<el-checkbox-group v-model="group.checkedMethods">
<el-checkbox
v-for="method in group.methods"
:key="method.id"
:label="method.id"
@change="onMethodChange(group, method)"
>
{{ method.name }}
</el-checkbox>
</el-checkbox-group>
</div> </div>
</div> </el-collapse-item>
</el-collapse-item> </el-collapse>
</el-collapse> </div>
</el-card> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, computed } from 'vue'; import { ref, computed } from 'vue';
import { ElMessage } from 'element-plus';
import type { TreeNode } from 'element-plus/lib/components/tree/src/tree.type';
// 模拟分类数据 // --------------------
const categories = ref([ // 数据结构定义(简化示例)
{ id: 1, label: '彩超', children: [] }, // --------------------
{ id: 2, label: 'CT', children: [] } interface Method {
]); id: number;
name: string;
}
interface Group {
id: number;
name: string; // 原始名称,可能带 “套餐” 前缀
methods: Method[];
}
// 当前分类下的项目列表 /**
const currentItems = ref<any[]>([]); * 1⃣ 读取树形数据(这里假设已经通过接口获取并赋值)
* itemTreeData 的每个节点对应一个检查项目Group
* 其 children 为检查方法Method
*/
const itemTreeData = ref<TreeNode[]>([]);
// 已选择项目列表(严格遵循 项目 > 检查方法 层级) /**
const selectedItems = reactive<any[]>([]); * Tree 组件的属性映射
*/
const itemTreeProps = {
label: 'name',
children: 'children',
};
// 折叠面板激活状态,默认空数组表示全部收起 /**
const activeCollapseNames = ref<string[]>([]); * 已选中的项目Group集合
* 结构:
* id, displayName去掉 “套餐” 前缀的名称fullName原始完整名称用于 tooltip
* methods所有可选方法checkedMethods已勾选的方法 id 列表)
*/
const selectedGroups = ref<Array<{
id: number;
displayName: string;
fullName: string;
methods: Method[];
checkedMethods: number[];
}>>([]);
// 清理名称中的冗余“套餐”字样 /**
const cleanName = (name: string) => name.replace(/套餐/g, ''); * 当前展开的 Collapse 面板(使用 group.id
*/
const activePanels = ref<number[]>([]);
// 选择分类 /**
const handleCategorySelect = (node: any) => { * 处理树节点勾选
// 实际项目中此处应请求后端获取对应分类下的项目 *
currentItems.value = [ * 需求:
{ id: 'item_1', name: '128线排彩超套餐', checked: false, methods: [ * - 勾选项目节点时,只把项目本身加入 selectedGroups**不**自动勾选其下的检查方法。
{ id: 'm1', name: '常规检查', checked: false }, * - 取消勾选时,移除对应的项目及其所有方法的选中状态。
{ id: 'm2', name: '血管多普勒', checked: false } *
]} * 通过 `check-strictly` 已经阻止了父子自动关联,但在业务层仍需要把
* 勾选的节点(只保留叶子为方法的情况)过滤掉,只保留项目节点。
*/
function handleTreeCheck(
data: any,
checkedNodes: any[],
indeterminateNodes: any[]
) {
// 1. 过滤出真正的项目节点(即没有 children或 children 为方法列表的父节点)
const projectNodes = checkedNodes.filter((node: any) => {
// 项目节点的 children 为方法数组,且 node.type === 'group'(业务自行约定)
// 为了兼容后端未返回 type 字段,这里判断是否存在 children 即可。
return node.children && node.children.length > 0;
});
// 2. 重新构建 selectedGroups仅保留项目层级信息
const newGroups = projectNodes.map((node: any) => {
const originalName: string = node.name || '';
// 去掉 “套餐” 前缀(如果有),并去除首尾空格
const displayName = originalName.replace(/^套餐\s*/, '').trim();
// 已经存在的 group用于保留已勾选的方法
const existed = selectedGroups.value.find(g => g.id === node.id);
return {
id: node.id,
displayName,
fullName: originalName, // 用于 tooltip
methods: (node.children || []).map((m: any) => ({
id: m.id,
name: m.name,
})),
// 保留之前已勾选的方法(若之前已存在),否则空数组
checkedMethods: existed ? existed.checkedMethods : [],
};
});
selectedGroups.value = newGroups;
}
/**
* 移除已选项目
*/
function removeGroup(groupId: number) {
selectedGroups.value = selectedGroups.value.filter(g => g.id !== groupId);
}
/**
* 检查方法勾选变化
* - 只更新对应 group 的 checkedMethods。
* - 不会影响其他 group也不会触发项目自动勾选。
*/
function onMethodChange(group: any, method: any) {
// 方法的勾选状态已经由 v-model 自动同步到 group.checkedMethods
// 这里仅做业务校验(如需要限制同一项目只能选择一种方法等),
// 目前保持空实现以避免副作用。
}
/**
* 初始化(示例:从后端获取树形数据)
*/
async function loadTreeData() {
// 这里使用 mock 数据,实际请调用接口
const mockData = [
{
id: 1,
name: '套餐 彩超',
children: [
{ id: 101, name: '128线排' },
{ id: 102, name: '多普勒' },
],
},
{
id: 2,
name: '普通 X 光',
children: [{ id: 201, name: '胸片' }],
},
]; ];
}; itemTreeData.value = mockData;
}
// 处理项目勾选(解耦:仅更新项目状态,不联动方法) loadTreeData();
const handleItemCheck = (item: any) => {
if (item.checked) {
// 若未存在于已选列表,则添加并默认收起
if (!selectedItems.find(s => s.id === item.id)) {
selectedItems.push({
id: item.id,
name: item.name,
methods: item.methods.map((m: any) => ({ ...m, checked: false }))
});
}
} else {
// 取消勾选则从已选列表移除
const idx = selectedItems.findIndex(s => s.id === item.id);
if (idx > -1) selectedItems.splice(idx, 1);
}
};
// 处理检查方法勾选(独立解耦)
const handleMethodCheck = (parentItem: any, method: any) => {
// 仅更新当前方法状态,不影响父项目或其他方法
// 实际业务中可在此处触发费用计算或明细同步
};
</script> </script>
<style scoped> <style scoped>
.exam-apply-container { .exam-apply {
padding: 20px;
}
.exam-layout {
display: flex; display: flex;
flex-direction: column;
gap: 16px;
padding: 16px;
height: 100%;
} }
.tree-section {
.category-panel, .item-panel, .selected-panel { width: 30%;
margin-right: 20px;
}
.selected-section {
flex: 1; flex: 1;
min-height: 0;
} }
.selected-tags {
.item-list, .method-container { margin-bottom: 10px;
max-height: 300px;
overflow-y: auto;
} }
.method-container {
.item-row, .method-row { padding: 10px;
display: flex;
align-items: center;
padding: 8px 0;
border-bottom: 1px dashed #eee;
} }
.item-name, .method-name {
margin-left: 8px;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
}
.collapse-title { .collapse-title {
font-weight: 500; font-weight: 500;
color: #303133;
}
.empty-tip {
text-align: center;
color: #909399;
padding: 20px 0;
} }
</style> </style>