树形结构渲染 + 选择(Vue3 + ElementPlus)

javascript 复制代码
后端数据
   ├─ 平铺数组  →  listToTree  →  deptTree
   └─ 树形数组  →  直接使用

<el-tree-select>
   ├─ v-model        = 选中id
   ├─ :data          = deptTree
   ├─ :props         = 字段映射
   └─ @change        = 递归拿节点 → 回显名称

1. 完整 mini Demo(可运行)

javascript 复制代码
<template>
  <el-form :model="form" label-width="80">
    <el-form-item label="部门" prop="deptId" :rules="[{ required: true }]">
      <el-tree-select
        v-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 步:先看清后端数据长啥样

① 平铺数组(最常见)

javascript 复制代码
[
  { id: 1, name: '总部',  parentId: 0 },
  { id: 11, name: '技术部', parentId: 1 },
  { id: 12, name: '财务部', parentId: 1 }
]

→ 需要 转树

② 已经是树

javascript 复制代码
[
  { id: 1, name: '总部', children: [
      { id: 11, name: '技术部', children: [] },
      { id: 12, name: '财务部', children: [] }
  ]}
]

→ 直接可用。

2. 第 2 步:平铺转树工具函数

javascript 复制代码
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;
}    

用法:

javascript 复制代码
const deptTree = listToTree(flatList);  // 得到 ElementPlus 直接能用的数组

3. 第 3 步:选对组件 & 记住 4 个核心属性

组件 属性 含义
<el-tree-select> v-model 绑定 选中值 (一般是 id
:data 树形数组
:props 字段映射 { label: 'name', value: 'id', children: 'children' }
@change 选中变化回调,参数就是 value
html 复制代码
<el-tree-select
  v-model="form.deptId"
  :data="deptTree"
  placeholder="请选择部门"
  check-strictly
  :props="{ label: 'name', value: 'id' }"
  clearable
  @change="onChange" />

4. 第 4 步:选中后拿"名称"或其他字段

组件只返回 id,要名称 → 递归找节点

javascript 复制代码
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 步:默认值 / 回显(编辑场景)

javascript 复制代码
// 新增:空值
form.deptId = null;

// 编辑:把后端返回的 id 丢进去即可
form.deptId = row.deptId;   // 组件会自动高亮对应节点

6. 第 6 步:校验 & 清空

场景 说明
必填 <el-form-item label="部门" prop="deptId" :rules="[{ required: true, message: '请选择' }]">
清空 组件自带 clearable,清空后 form.deptId = null,无需额外处理
相关推荐
q***577414 分钟前
WebSpoon9.0(KETTLE的WEB版本)编译 + tomcatdocker部署 + 远程调试教程
前端
Q***l68732 分钟前
Vue增强现实案例
前端·vue.js·ar
十里-41 分钟前
前端监控1-数据上报
前端·安全
初学者,亦行者1 小时前
DevUI微前端集成实战解析
前端·typescript
han_1 小时前
前端高频面试题之CSS篇(一)
前端·css·面试
b***74881 小时前
Vue开源
前端·javascript·vue.js
不知更鸟1 小时前
前端报错:快速解决Django接口404问题
前端·python·django
D***t1312 小时前
React图像处理案例
前端
万少2 小时前
我是如何使用 Trae IDE 完成《流碧卡片》项目的完整记录
前端·后端·ai编程
9***Y482 小时前
前端微服务
前端·微服务·架构