You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3树形表格实现求助:修复parent_id关联children的递归组件

树形表格递归组件实现修复方案

问题描述

需要实现树形表格,现有数据包含parent_id和待构建的children数组,数据库返回的数据结构为:{name: string, employee_id: number, subordinates: number[], boss_id: number}(数组形式对应该字段集合)。当前递归组件实现存在逻辑问题,需修复基于children数组的树形渲染逻辑。

现有代码问题分析

  1. Tree组件中treeData为模拟固定结构,未对接服务端扁平数据的树形转换逻辑;模板中绑定的items未定义,数据指向错误。
  2. TreeItem组件中,判断是否含子节点用了subordinates字段,但递归渲染时遍历的是model.children,两者未关联;直接修改props.model.children违反Vue单向数据流原则。

修复步骤

1. 数据转换:扁平数据转树形结构

先把服务端返回的扁平员工数据,通过boss_id与employee_id的关联,转换成带children的树形结构:

function convertToTree(flatEmployees) {
  const employeeMap = new Map();
  // 先将所有员工存入Map,方便快速查找
  flatEmployees.forEach(emp => {
    employeeMap.set(emp.employee_id, { ...emp, children: [] });
  });
  const tree = [];
  // 遍历匹配父节点,构建树形结构
  flatEmployees.forEach(emp => {
    if (emp.boss_id) {
      const boss = employeeMap.get(emp.boss_id);
      boss?.children.push(employeeMap.get(emp.employee_id));
    } else {
      // 无boss_id的为根节点
      tree.push(employeeMap.get(emp.employee_id));
    }
  });
  return tree;
}

2. 修复Tree组件

修改Tree组件,对接服务端数据并完成树形转换,修正模板数据绑定:

<script setup>
import { ref, watchEffect } from 'vue';
import TreeItem from './TreeItem.vue';

const treeData = ref([]);

// 模拟服务端数据请求,实际替换为接口调用
async function fetchDataFromServer() {
  const flatEmployees = [
    { name: '总经理', employee_id: 1, subordinates: [2,3], boss_id: null },
    { name: '部门经理A', employee_id: 2, subordinates: [4], boss_id: 1 },
    { name: '部门经理B', employee_id: 3, subordinates: [], boss_id: 1 },
    { name: '员工小甲', employee_id: 4, subordinates: [], boss_id: 2 },
  ];
  treeData.value = convertToTree(flatEmployees);
}

// 数据转换函数
function convertToTree(flatEmployees) {
  const employeeMap = new Map();
  flatEmployees.forEach(emp => {
    employeeMap.set(emp.employee_id, { ...emp, children: [] });
  });
  const tree = [];
  flatEmployees.forEach(emp => {
    if (emp.boss_id) {
      const boss = employeeMap.get(emp.boss_id);
      boss?.children.push(employeeMap.get(emp.employee_id));
    } else {
      tree.push(employeeMap.get(emp.employee_id));
    }
  });
  return tree;
}

watchEffect(fetchDataFromServer);
</script>

<template>
  <ul>
    <TreeItem 
      class="item" 
      v-for="item in treeData" 
      :key="item.employee_id"
      :model="item"
    ></TreeItem>
  </ul>
</template>

3. 修复TreeItem组件

统一子节点判断逻辑,遵循单向数据流,通过事件通知父组件修改数据:

<script setup>
import { ref, computed } from 'vue';

const props = defineProps({
  model: {
    type: Object,
    required: true
  }
});

const isOpen = ref(false);
// 基于转换后的children数组判断是否有子节点
const hasChildren = computed(() => {
  return props.model.children?.length > 0;
});

function toggle() {
  isOpen.value = !isOpen.value;
}

// 通过emit通知父组件添加子节点,避免直接修改props
function addChildren() {
  if (!hasChildren.value) {
    emit('add-children', props.model.employee_id);
  }
}

const emit = defineEmits(['add-children']);
</script>

<template>
  <li>
    <div 
      :class="{ bold: hasChildren }"
      @click="toggle"
      @dblclick="addChildren"
    >
      {{ model.name }}
      <span v-if="hasChildren">[{{ isOpen ? '-' : '+' }}]</span>
    </div>
    <ul v-show="isOpen" v-if="hasChildren">
      <TreeItem 
        class="item" 
        v-for="child in model.children" 
        :key="child.employee_id"
        :model="child"
        @add-children="addChildren"
      ></TreeItem>
    </ul>
  </li>
</template>

<style scoped>
.bold {
  font-weight: bold;
  cursor: pointer;
}
.item {
  margin-left: 16px;
}
</style>

关键修复点说明

  • 数据结构统一:通过Map快速匹配父节点,将扁平数据转换为带children的树形结构,解决parent_id与子节点的关联问题。
  • 单向数据流遵循:TreeItem组件不再直接修改props数据,通过emit事件通知父组件处理数据变更,符合Vue设计规范。
  • 渲染逻辑一致:判断子节点存在性与递归渲染的数据源统一为children数组,避免逻辑混乱。

内容的提问来源于stack exchange,提问作者sow cantli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 23:17:52