基于 parentId 的树形 el-table 配置方案详解

161 阅读4分钟

引言

在管理后台开发中,树形结构数据的展示是一个常见需求。Element UI 的 el-table 组件提供了强大的树形表格功能,通过 tree-props 配置可以轻松实现。本文将详细介绍如何基于 parentId 字段构建树形表格,包括数据预处理、表格列定义和树形渲染逻辑。

一、数据预处理:构建树形结构

1.1 数据结构设计

假设我们有一个部门数据,每个部门包含以下字段:

  • id: 部门ID
  • parentId: 父部门ID(根节点的 parentId 为 null 或 0
  • name: 部门名称
  • orderNum: 排序字段
  • 其他业务字段...

1.2 树形结构构建算法

我们需要将扁平数据转换为树形结构,以下是实现代码:

javascript
	/**

	 * 将扁平数据转换为树形结构

	 * @param {Array} data 原始数据

	 * @param {String} idKey 主键字段名

	 * @param {String} parentKey 父键字段名

	 * @param {String} childrenKey 子节点字段名

	 * @returns {Array} 树形结构数据

	 */

	function buildTree(data, idKey = 'id', parentKey = 'parentDefId', childrenKey = 'children') {

	  const map = {};

	  const tree = [];

	  

	  // 首先将所有数据存入map,键为id

	  data.forEach(item => {

	    map[item[idKey]] = { ...item, [childrenKey]: [] };

	  });

	  

	  // 遍历map,构建树形结构

	  Object.keys(map).forEach(id => {

	    const node = map[id];

	    const parentId = node[parentKey];

	    

	    if (parentId === null || parentId === undefined || parentId === 0) {

	      // 根节点

	      tree.push(node);

	    } else {

	      // 子节点,添加到父节点的children中

	      if (map[parentId]) {

	        map[parentId][childrenKey].push(node);

	      }

	    }

	  });

	  

	  return tree;

	}

1.3 排序处理

如果需要按 orderNum 排序,可以修改算法:

javascript
	function buildSortedTree(data, idKey = 'id', parentKey = 'parentDefId', childrenKey = 'children') {

	  const map = {};

	  const tree = [];

	  

	  // 第一步:将所有数据存入map

	  data.forEach(item => {

	    map[item[idKey]] = { ...item, [childrenKey]: [] };

	  });

	  

	  // 第二步:构建树形结构

	  Object.keys(map).forEach(id => {

	    const node = map[id];

	    const parentId = node[parentKey];

	    

	    if (parentId === null || parentId === undefined || parentId === 0) {

	      tree.push(node);

	    } else {

	      if (map[parentId]) {

	        map[parentId][childrenKey].push(node);

	      }

	    }

	  });

	  

	  // 第三步:递归排序

	  function sortTree(nodes) {

	    if (!nodes || nodes.length === 0) return;

	    

	    // 对当前层级排序

	    nodes.sort((a, b) => (a.orderNum || 0) - (b.orderNum || 0));

	    

	    // 递归排序子节点

	    nodes.forEach(node => {

	      if (node[childrenKey] && node[childrenKey].length > 0) {

	        sortTree(node[childrenKey]);

	      }

	    });

	  }

	  

	  sortTree(tree);

	  return tree;

	}

二、el-table 树形表格配置

2.1 基本表格配置

vue
	<template>

	  <el-table

	    :data="tableData"

	    row-key="id"

	    border

	    default-expand-all

	    :tree-props="{children: 'children', hasChildren: 'hasChildren'}"

	    style="width: 100%">

	    <el-table-column prop="name" label="部门名称" width="180"></el-table-column>

	    <el-table-column prop="orderNum" label="排序" width="100"></el-table-column>

	    <el-table-column prop="description" label="描述"></el-table-column>

	  </el-table>

	</template>

	 

	<script>

	export default {

	  data() {

	    return {

	      flatData: [

	        { id: 1, parentDefId: null, name: '总公司', orderNum: 1, description: '总公司描述' },

	        { id: 2, parentDefId: 1, name: '技术部', orderNum: 1, description: '技术部描述' },

	        { id: 3, parentDefId: 1, name: '市场部', orderNum: 2, description: '市场部描述' },

	        { id: 4, parentDefId: 2, name: '前端组', orderNum: 1, description: '前端组描述' },

	        { id: 5, parentDefId: 2, name: '后端组', orderNum: 2, description: '后端组描述' },

	      ],

	      tableData: []

	    }

	  },

	  created() {

	    this.tableData = this.buildSortedTree(this.flatData);

	  },

	  methods: {

	    buildSortedTree(data) {

	      // 使用前面定义的buildSortedTree方法

	      return buildSortedTree(data);

	    }

	  }

	}

	</script>

2.2 关键配置说明

  1. row-key: 必须设置为唯一标识字段(通常是id)

  2. tree-props:

    • children: 指定子节点字段名(默认为children)
    • hasChildren: 指定是否有子节点的字段名(可选)
  3. default-expand-all: 是否默认展开所有节点

三、高级功能实现

3.1 懒加载子节点

对于大数据量的树形表格,可以使用懒加载方式:

vue
	<template>

	  <el-table

	    :data="tableData"

	    row-key="id"

	    border

	    lazy

	    :load="loadChildren"

	    :tree-props="{children: 'children', hasChildren: 'hasChildren', isLeaf: 'leaf'}"

	    style="width: 100%">

	    <!-- 列定义 -->

	  </el-table>

	</template>

	 

	<script>

	export default {

	  data() {

	    return {

	      tableData: [

	        { id: 1, parentDefId: null, name: '总公司', hasChildren: true },

	        { id: 2, parentDefId: 1, name: '技术部', hasChildren: true },

	        { id: 3, parentDefId: 1, name: '市场部', hasChildren: false, leaf: true }

	      ]

	    }

	  },

	  methods: {

	    async loadChildren(tree, treeNode, resolve) {

	      // 模拟API请求

	      setTimeout(() => {

	        const children = [

	          { id: 4, parentDefId: 2, name: '前端组', hasChildren: false, leaf: true },

	          { id: 5, parentDefId: 2, name: '后端组', hasChildren: false, leaf: true }

	        ];

	        resolve(children);

	      }, 1000);

	    }

	  }

	}

	</script>

3.2 自定义展开/折叠图标

vue
	<el-table

	  <!-- 其他配置 -->

	  :indent="20">

	  <template #default="{row, $index}">

	    <span v-if="row.children && row.children.length" 

	          @click="toggleExpand(row)"

	          style="cursor: pointer; margin-right: 5px;">

	      <i :class="row._expanded ? 'el-icon-arrow-down' : 'el-icon-arrow-right'"></i>

	    </span>

	    <span v-else style="margin-right: 20px;"></span>

	    {{ row.name }}

	  </template>

	</el-table>

	 

	<script>

	export default {

	  methods: {

	    toggleExpand(row) {

	      // 这里需要手动维护_expanded状态

	      // 实际项目中建议使用状态管理或直接操作DOM

	      this.$set(row, '_expanded', !row._expanded);

	      // 可能需要手动触发重新渲染

	    }

	  }

	}

	</script>

四、完整示例代码

vue
	<template>

	  <div class="tree-table-container">

	    <el-table

	      :data="tableData"

	      row-key="id"

	      border

	      default-expand-all

	      :tree-props="{children: 'children', hasChildren: 'hasChildren'}"

	      style="width: 100%">

	      <el-table-column label="部门名称" width="180">

	        <template #default="{row}">

	          <span :style="{paddingLeft: (row.level * 20) + 'px'}">

	            <i v-if="row.children && row.children.length" 

	               class="el-icon-folder" 

	               style="color: #f0ad4e; margin-right: 5px;"></i>

	            <i v-else class="el-icon-document" style="color: #5bc0de; margin-right: 5px;"></i>

	            {{ row.name }}

	          </span>

	        </template>

	      </el-table-column>

	      <el-table-column prop="orderNum" label="排序" width="100" align="center"></el-table-column>

	      <el-table-column prop="description" label="描述"></el-table-column>

	      <el-table-column label="操作" width="150" align="center">

	        <template #default="{row}">

	          <el-button size="mini" @click="handleAddChild(row)">添加子部门</el-button>

	        </template>

	      </el-table-column>

	    </el-table>

	  </div>

	</template>

	 

	<script>

	function buildTreeWithLevel(data, idKey = 'id', parentKey = 'parentDefId', childrenKey = 'children', level = 0) {

	  const map = {};

	  const tree = [];

	  

	  data.forEach(item => {

	    map[item[idKey]] = { ...item, [childrenKey]: [], level };

	  });

	  

	  Object.keys(map).forEach(id => {

	    const node = map[id];

	    const parentId = node[parentKey];

	    

	    if (parentId === null || parentId === undefined || parentId === 0) {

	      tree.push(node);

	    } else {

	      if (map[parentId]) {

	        // 设置子节点的level为父节点level+1

	        const childNode = { ...node, level: map[parentId].level + 1 };

	        map[parentId][childrenKey].push(childNode);

	      }

	    }

	  });

	  

	  return tree;

	}

	 

	export default {

	  data() {

	    return {

	      flatData: [

	        { id: 1, parentDefId: null, name: '总公司', orderNum: 1, description: '总公司描述' },

	        { id: 2, parentDefId: 1, name: '技术部', orderNum: 1, description: '技术部描述' },

	        { id: 3, parentDefId: 1, name: '市场部', orderNum: 2, description: '市场部描述' },

	        { id: 4, parentDefId: 2, name: '前端组', orderNum: 1, description: '前端组描述' },

	        { id: 5, parentDefId: 2, name: '后端组', orderNum: 2, description: '后端组描述' },

	      ],

	      tableData: []

	    }

	  },

	  created() {

	    this.tableData = buildTreeWithLevel(this.flatData);

	  },

	  methods: {

	    handleAddChild(parentRow) {

	      const newId = Date.now(); // 简单生成ID,实际项目应使用更可靠的方案

	      const newChild = {

	        id: newId,

	        parentDefId: parentRow.id,

	        name: '新部门',

	        orderNum: parentRow.children.length + 1,

	        description: '新部门描述',

	        level: parentRow.level + 1

	      };

	      

	      // 实际项目中应该调用API添加,这里简单模拟

	      this.flatData.push(newChild);

	      

	      // 重新构建树

	      this.tableData = buildTreeWithLevel(this.flatData);

	      

	      this.$message.success(`成功添加子部门: ${newChild.name}`);

	    }

	  }

	}

	</script>

	 

	<style scoped>

	.tree-table-container {

	  padding: 20px;

	}

	</style>

五、总结与优化建议

  1. 数据预处理:将扁平数据转换为树形结构是关键步骤
  2. 树形配置:正确配置 row-key 和 tree-props 是树形表格的基础
  3. 性能优化:对于大数据量考虑使用懒加载