树形结构渲染 + 选择(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,无需额外处理
相关推荐
0思必得043 分钟前
[Web自动化] Selenium处理动态网页
前端·爬虫·python·selenium·自动化
东东5161 小时前
智能社区管理系统的设计与实现ssm+vue
前端·javascript·vue.js·毕业设计·毕设
catino1 小时前
图片、文件的预览
前端·javascript
2501_920931703 小时前
React Native鸿蒙跨平台实现推箱子游戏,完成玩家移动与箱子推动,当所有箱子都被推到目标位置时,玩家获胜
javascript·react native·react.js·游戏·ecmascript·harmonyos
layman05283 小时前
webpack5 css-loader:从基础到原理
前端·css·webpack
半桔3 小时前
【前端小站】CSS 样式美学:从基础语法到界面精筑的实战宝典
前端·css·html
AI老李3 小时前
PostCSS完全指南:功能/配置/插件/SourceMap/AST/插件开发/自定义语法
前端·javascript·postcss
_OP_CHEN3 小时前
【前端开发之CSS】(一)初识 CSS:网页化妆术的终极指南,新手也能轻松拿捏页面美化!
前端·css·html·网页开发·样式表·界面美化
啊哈一半醒3 小时前
CSS 主流布局
前端·css·css布局·标准流 浮动 定位·flex grid 响应式布局
PHP武器库3 小时前
ULUI:不止于按钮和菜单,一个专注于“业务组件”的纯 CSS 框架
前端·css