树型结构,动态计算-实现表单合并【行、列】

  1. 想要实现的效果图

3. 具体代码实现如下

js 复制代码
<template>
  <div style="padding: 20px;">
    <table style="width: 100%; border-collapse: collapse;" border="1">
      <thead>
      <tr>
        <th :colspan="maxLevel" style="text-align: center;">活动类型</th>
        <th v-for="item in columns" style="text-align: center;">{{item.name}}</th>
      </tr>
      </thead>
      <tbody>
      <tr v-for="(row, index) in tableRows" :key="index">
        <!-- 渲染每个单元格 -->
        <template v-for="(cell, cellIndex) in row.cells" :key="cellIndex">
          <td style="text-align: center" v-if="cell.show" :rowspan="cell.rowspan" :colspan="cell.colspan">
            {{ cell.name }}
          </td>
        </template>
        <!-- 渲染人数 -->
        <td v-for="item in columns">{{ row[item.prop] }}</td>
      </tr>
      </tbody>
    </table>


  </div>
</template>

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

const columns = [
  {
    prop: "count",
    name: "活动人数",
  },
  {
    prop: "count1",
    name: "活动人数1",
  },
]

const maxLevel = ref(5);
// 模拟的树形数据
const treeData = ref([
  {
    name: "一级活动1",
    count: 1,
    count1: 2,
    children: [],
  },
  {
    name: "一级活动2",
    count: 0, // 一级节点本身不显示具体人数,由子节点决定行数
    children: [
      {
        name: "二级活动1",
        count: 1,
        children: [],
      },
      {
        name: "二级活动2",
        count: 0, // 二级节点本身不显示具体人数,由子节点决定行数
        children: [
          {
            name: "三级活动1",
            count: 1,
            children: [],
          },
          {
            name: "三级活动2",
            count: 4,
            count1: 5,
            children: [],
          },
          {
            name: "三级活动3",
            count: 1,
            children: [],
          }
        ],
      }
    ],
  },
  {
    name: "一级活动3",
    count: 1,
    children: [
      {
        name: "二级活动3",
        count: 1,
        children: [],
      },
      {
        name: "二级活动4",
        count: 1,
        children: [
          {
            name: "三级活动4",
            count: 1,
            children: [],
          },
        ],
      },
      {
        name: "二级活动5",
        count: 1,
        children: [
          {
            name: "三级活动5",
            count: 1,
            children: [
              {
                name: "四级活动1",
                count: 1,
                children: [],
              },
              {
                name: "四级活动2",
                count: 1,
                children: [
                  {
                    name: "五级活动1",
                    count: 1,
                    children: [],
                  },
                ],
              },
            ],
          },
        ],
      },
    ],
  },
]);



// 计算属性,用于生成表格行数据
const tableRows = computed(() => {
  const rows = [];

  // 递归函数,用于处理节点并生成行
  // 返回该节点及其子树所占用的行数
  const processNode = (node, level, cells) => {
    const isLeaf = node.children.length === 0;
    let totalRowspan = 0;
    debugger

    if (isLeaf) {
      // 叶子节点:创建新行
      const newRowCells = [...cells];
      newRowCells[level - 1] = {
        name: node.name,
        rowspan: 1,
        colspan: maxLevel.value - level + 1,
        show: true
      };
      // 填充之前的空单元格
      for (let i = 0; i < level - 1; i++) {
        if (!newRowCells[i]) {
          newRowCells[i] = { show: false };
        }
      }
      rows.push({
        ...node,
        cells: newRowCells,
      });
      return 1; // 叶子节点贡献1行
    } else {
      // 非叶子节点
      const currentCells = [...cells];
      // 为当前节点预留一个单元格位置,但暂不设置 rowspan 和 show
      currentCells[level - 1] = {
        name: node.name,
        // rowspan 和 show 将在处理完所有子节点后设置
        colspan: 1,
      };

      // 递归处理所有子节点
      for (const child of node.children) {
        const childRows = processNode(child, level + 1, currentCells);
        totalRowspan += childRows;
      }

      // 处理完所有子节点后,更新当前节点的单元格信息
      // 找到当前节点对应的行(第一个子节点所在的行)
      if (rows.length > 0) {
        const firstChildRowIndex = rows.length - totalRowspan;
        if (firstChildRowIndex >= 0) {
          rows[firstChildRowIndex].cells[level - 1] = {
            name: node.name,
            rowspan: totalRowspan,
            colspan: 1,
            show: true
          };
          // 确保之前的单元格也正确设置(如果被覆盖)
          for (let i = 0; i < level - 1; i++) {
            if (!rows[firstChildRowIndex].cells[i]) {
              rows[firstChildRowIndex].cells[i] = { show: false };
            }
          }
        }
      }

      return totalRowspan;
    }
  };

  // 遍历所有根节点
  treeData.value.forEach(node => {
    processNode(node, 1, []);
  });

  return rows;
});
</script>

<style scoped>
/* 可以在这里添加一些样式 */
</style>
相关推荐
拾光拾趣录26 分钟前
JavaScript 究竟怎么跑
前端·javascript
Aotman_28 分钟前
el-input 重写带图标密码框(点击小眼睛显示、隐藏密码)
前端·javascript·vue.js
神笔码农nice29 分钟前
Promise详解:Promise解决ajax回调嵌套问题
前端·javascript
程序员二师兄37 分钟前
记一次鸿蒙webview图片渲染失败的问题
前端·javascript·harmonyos
萌萌哒草头将军37 分钟前
字节也在用的 @tanstack/react-query 到底有多好用!🔥🔥🔥
前端·javascript·react.js
JohnYan1 小时前
工作笔记 - 改进的单例应用
javascript·设计模式·bun
鹧鸪yy1 小时前
从Token介绍到单点登录SSO
前端·javascript
一块plus1 小时前
创造 Solidity、提出 Web3 的他回来了!Gavin Wood 这次将带领波卡走向何处?
javascript·后端·面试
老虎06271 小时前
JavaWeb前端03(Ajax概念及在前端开发时应用)
前端·javascript·ajax
小鸡脚来咯2 小时前
react速成
前端·javascript·react.js