229 lines
6.2 KiB
Vue
229 lines
6.2 KiB
Vue
<template>
|
||
<div class="exam-apply">
|
||
<div class="exam-layout">
|
||
<!-- 左侧:检查项目分类与项目树 -->
|
||
<div class="tree-section">
|
||
<el-tree
|
||
ref="itemTreeRef"
|
||
:data="itemTreeData"
|
||
node-key="id"
|
||
show-checkbox
|
||
:check-strictly="true"
|
||
:default-expand-all="false"
|
||
:props="itemTreeProps"
|
||
@check="handleTreeCheck"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 右侧:已选项目与明细 -->
|
||
<div class="selected-section">
|
||
<div class="section-title">已选择</div>
|
||
<div class="selected-tags">
|
||
<el-tag
|
||
v-for="group in selectedGroups"
|
||
:key="group.id"
|
||
closable
|
||
size="default"
|
||
show-overflow-tooltip
|
||
:title="group.fullName"
|
||
@close="removeGroup(group.id)"
|
||
>
|
||
{{ group.displayName }}
|
||
</el-tag>
|
||
</div>
|
||
|
||
<el-collapse v-model="activePanels" class="details-collapse">
|
||
<el-collapse-item
|
||
v-for="group in selectedGroups"
|
||
:key="group.id"
|
||
: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>
|
||
</el-collapse-item>
|
||
</el-collapse>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed } from 'vue';
|
||
import { ElMessage } from 'element-plus';
|
||
import type { TreeNode } from 'element-plus/lib/components/tree/src/tree.type';
|
||
|
||
// --------------------
|
||
// 数据结构定义(简化示例)
|
||
// --------------------
|
||
interface Method {
|
||
id: number;
|
||
name: string;
|
||
}
|
||
interface Group {
|
||
id: number;
|
||
name: string; // 原始名称,可能带 “套餐” 前缀
|
||
methods: Method[];
|
||
}
|
||
|
||
/**
|
||
* 1️⃣ 读取树形数据(这里假设已经通过接口获取并赋值)
|
||
* itemTreeData 的每个节点对应一个检查项目(Group),
|
||
* 其 children 为检查方法(Method)。
|
||
*/
|
||
const itemTreeData = ref<TreeNode[]>([]);
|
||
|
||
/**
|
||
* Tree 组件的属性映射
|
||
*/
|
||
const itemTreeProps = {
|
||
label: 'name',
|
||
children: 'children',
|
||
};
|
||
|
||
/**
|
||
* 已选中的项目(Group)集合
|
||
* 结构:
|
||
* id, displayName(去掉 “套餐” 前缀的名称),fullName(原始完整名称,用于 tooltip),
|
||
* methods(所有可选方法),checkedMethods(已勾选的方法 id 列表)
|
||
*/
|
||
const selectedGroups = ref<Array<{
|
||
id: number;
|
||
displayName: string;
|
||
fullName: string;
|
||
methods: Method[];
|
||
checkedMethods: number[];
|
||
}>>([]);
|
||
|
||
/**
|
||
* 当前展开的 Collapse 面板(使用 group.id)
|
||
*/
|
||
const activePanels = ref<number[]>([]);
|
||
|
||
/**
|
||
* 处理树节点勾选
|
||
*
|
||
* 需求:
|
||
* - 勾选项目节点时,只把项目本身加入 selectedGroups,**不**自动勾选其下的检查方法。
|
||
* - 取消勾选时,移除对应的项目及其所有方法的选中状态。
|
||
*
|
||
* 通过 `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();
|
||
</script>
|
||
|
||
<style scoped>
|
||
.exam-apply {
|
||
padding: 20px;
|
||
}
|
||
.exam-layout {
|
||
display: flex;
|
||
}
|
||
.tree-section {
|
||
width: 30%;
|
||
margin-right: 20px;
|
||
}
|
||
.selected-section {
|
||
flex: 1;
|
||
}
|
||
.selected-tags {
|
||
margin-bottom: 10px;
|
||
}
|
||
.method-container {
|
||
padding: 10px;
|
||
}
|
||
.collapse-title {
|
||
font-weight: 500;
|
||
}
|
||
</style>
|