树形结构渲染 + 选择(Vue3 + ElementPlus)
后端数据├─ 平铺数组 → listToTree → deptTree└─ 树形数组 → 直接使用<el-tree-select>├─ v-model = 选中id├─ :data = deptTree├─ :props = 字段映射└─ @change = 递归拿节点 → 回显名称
1. 完整 mini Demo(可运行)
<template><el-form :model="form" label-width="80"><el-form-item label="部门" prop="deptId" :rules="[{ required: true }]"><el-tree-selectv-model="form.deptId":data="deptTree"placeholder="请选择部门"check-strictly:props="{ label: 'name', value: 'id' }"clearable@change="onChange" /></el-form-item></el-form>
</template><script setup lang="ts">
import { ref } from 'vue';// 模拟平铺数据
const flat = [{ id: 1, name: '总部', parentId: 0 },{ id: 11, name: '技术部', parentId: 1 },{ id: 12, name: '财务部', parentId: 1 }
];// 工具:转树
const listToTree = (list: any[], root = 0, key = 'id', parentKey = 'parentId') => {const map: any = {};list.forEach(item => map[item[key]] = item);const tree: any[] = [];list.forEach(item => {const parent = map[item[parentKey]];if (parent) (parent.children ||= []).push(item);else if (item[parentKey] === root) tree.push(item);});return tree;
};const deptTree = ref(listToTree(flat));
const form = ref({ deptId: null as number | null, deptName: '' });const findNodeDeep = (tree: any[] | undefined, id: number): any | undefined => {if (!tree) return;for (const node of tree) {if (node.id === id) return node;const found = findNodeDeep(node.children, id);if (found) return found;}
};const onChange = (id: number) => {const node = findNodeDeep(deptTree.value, id);form.value.deptName = node?.name ?? '';
};
</script>
1. 第 1 步:先看清后端数据长啥样
① 平铺数组(最常见)
[{ id: 1, name: '总部', parentId: 0 },{ id: 11, name: '技术部', parentId: 1 },{ id: 12, name: '财务部', parentId: 1 }
]
→ 需要 转树。
② 已经是树
[{ id: 1, name: '总部', children: [{ id: 11, name: '技术部', children: [] },{ id: 12, name: '财务部', children: [] }]}
]
→ 直接可用。
2. 第 2 步:平铺转树工具函数
function listToTree(list: any[], root = 0, key = 'id', parentKey = 'parentId', childKey = 'children') {const map: any = {};list.forEach(item => map[item[key]] = item);const tree: any[] = [];list.forEach(item => {const parent = map[item[parentKey]];if (parent) {(parent[childKey] ||= []).push(item);} else if (item[parentKey] === root) {tree.push(item);}});return tree;
}
用法:
const deptTree = listToTree(flatList); // 得到 ElementPlus 直接能用的数组
3. 第 3 步:选对组件 & 记住 4 个核心属性
组件 | 属性 | 含义 |
---|---|---|
<el-tree-select> | v-model | 绑定 选中值(一般是 id ) |
:data | 树形数组 | |
:props | 字段映射 { label: 'name', value: 'id', children: 'children' } | |
@change | 选中变化回调,参数就是 value |
<el-tree-selectv-model="form.deptId":data="deptTree"placeholder="请选择部门"check-strictly:props="{ label: 'name', value: 'id' }"clearable@change="onChange" />
4. 第 4 步:选中后拿“名称”或其他字段
组件只返回 id
,要名称 → 递归找节点。
function findNodeDeep(tree: any[] | undefined, id: number): any | undefined {if (!tree) return;for (const node of tree) {if (node.id === id) return node;const found = findNodeDeep(node.children, id);if (found) return found;}
}const onChange = (id: number) => {const node = findNodeDeep(deptTree, id);form.deptName = node?.name ?? ''; // 回显名称form.deptId = id; // 保存 id
};
5. 第 5 步:默认值 / 回显(编辑场景)
// 新增:空值
form.deptId = null;// 编辑:把后端返回的 id 丢进去即可
form.deptId = row.deptId; // 组件会自动高亮对应节点
6. 第 6 步:校验 & 清空
场景 | 说明 |
---|---|
必填 | <el-form-item label="部门" prop="deptId" :rules="[{ required: true, message: '请选择' }]"> |
清空 | 组件自带 clearable ,清空后 form.deptId = null ,无需额外处理 |