树形结构渲染 + 选择(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,无需额外处理
相关推荐
叶梅树3 小时前
从零构建A股量化交易工具:基于Qlib的全栈系统指南
前端·后端·算法
巴博尔3 小时前
uniapp的IOS中首次进入,无网络问题
前端·javascript·ios·uni-app
焚 城3 小时前
UniApp 实现双语功能
javascript·vue.js·uni-app
Asthenia04124 小时前
技术复盘:从一次UAT环境CORS故障看配置冗余的危害与最佳实践
前端
csj504 小时前
前端基础之《React(1)—webpack简介》
前端·react
会写代码的饭桶4 小时前
Jenkins 实现 Vue 项目自动化构建与远程服务器部署
vue.js·自动化·jenkins
被巨款砸中4 小时前
前端 20 个零依赖浏览器原生 API 实战清单
前端·javascript·vue.js·web
文韬_武略4 小时前
web vue之状态管理Pinia
前端·javascript·vue.js
董世昌414 小时前
js怎样改变元素的内容、属性、样式?
开发语言·javascript·ecmascript