树形结构渲染 + 选择(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,无需额外处理
相关推荐
夏日不想说话2 分钟前
一文搞懂 AI 流式响应
前端·node.js·openai
顾安r1 小时前
11.14 脚本网页 青蛙过河
服务器·前端·python·游戏·html
阿奇__1 小时前
el-table有固定列时样式bug
vue.js·elementui·bug
LXA08091 小时前
在Vue 3项目中配置和使用SCSS
vue.js·rust·scss
不爱吃糖的程序媛1 小时前
Electron 智能文件分析器开发实战适配鸿蒙
前端·javascript·electron
Doro再努力1 小时前
2025_11_14洛谷【入门1】数据结构刷题小结
前端·数据结构·算法
IT_陈寒2 小时前
SpringBoot 3.2新特性实战:这5个隐藏技巧让你的应用性能飙升50%
前端·人工智能·后端
flashlight_hi2 小时前
LeetCode 分类刷题:3217. 从链表中移除在数组中存在的节点
javascript·数据结构·leetcode·链表
Java追光着2 小时前
React Native 自建 JS Bundle OTA 更新系统:从零到一的完整实现与踩坑记录
javascript·react native·react.js
努力往上爬de蜗牛2 小时前
react native 运行问题和调试 --持续更新
javascript·react native·react.js