效果

原生的JavaScript+原生table
原生JavaScript


null


公共数据

const list = [
  {
    id: "a1",
    title: "第一列",
    list: [
      {
        id: "a11",
        parentId: "a1",
        title: "第二列",
        list: [
          { id: "a111", parentId: "a11", title: "第三列第一行" },
          { id: "a112", parentId: "a11", title: "第三列第二行" },
          { id: "a113", parentId: "a11", title: "第三列第三行" },
        ],
      },
    ],
  },
  {
    id: "a2",
    title: "第一列",
    list: [
      {
        id: "a21",
        parentId: "a2",
        title: "第二列",
        list: [
          { id: "a211", parentId: "a21", title: "第三列第一行" },
          { id: "a212", parentId: "a21", title: "第三列第二行" },
          { id: "a213", parentId: "a21", title: "第三列第三行" },
        ],
      },
      {
        id: "a22",
        parentId: "a2",
        title: "第二列",
        list: [
          { id: "a221", parentId: "a22", title: "第三列第一行" },
          { id: "a222", parentId: "a22", title: "第三列第二行" },
        ],
      },
    ],
  },
  {
    id: "a3",
    title: "第一列",
    list: [
      {
        id: "a31",
        parentId: "a3",
        title: "第二列",
        list: [{ id: "a311", parentId: "a31", title: "第三列第一行" }],
      },
    ],
  },
];

纯原生

Style

body {
  margin: 0px;
  display: flex;
  justify-content: center;
}
.merged-table {
  border-collapse: collapse;
  margin: 20px;
  width: 95%;
}
.merged-table td,
.merged-table th {
  border: 1px solid #ddd;
  padding: 8px;
  text-align: left;
}
.merged-table th {
  background-color: #f2f2f2;
  font-weight: bold;
}
.no-data {
  text-align: center;
  color: #999;
  padding: 20px;
}

JavaScript

function createMergedTable(data, container, options = {}) {
  // 清除容器原有内容
  container.innerHTML = "";

  // 合并配置
  const config = {
    classNames: ["merged-table", ...(options.classNames || [])],
    headers: options.headers || null,
    emptyText: options.emptyText || "暂无数据",
  };

  // 计算行合并数
  function computeRowspan(node) {
    if (!node.list || node.list.length === 0) {
      node.rowspan = 1;
      return 1;
    }

    let total = 0;

    for (const child of node.list) total += computeRowspan(child);
    node.rowspan = total;
    return total;
  }

  // 收集所有叶子节点路径
  function collectLeaves(node, chain = [], result = []) {
    const newChain = [...chain, node];

    if (!node.list || node.list.length === 0) {
      result.push(newChain);
      return result;
    }
    for (const child of node.list) collectLeaves(child, newChain, result);
    return result;
  }

  // 创建表头
  function createHeader(table, depth) {
    const thead = document.createElement("thead");
    const tr = document.createElement("tr");
    const headers =
      config.headers ||
      Array.from({ length: depth }, (_, i) => `层级${i + 1}`).concat([
        "明细项",
      ]);

    headers.forEach((text) => {
      const th = document.createElement("th");
      th.textContent = text;
      tr.appendChild(th);
    });
    thead.appendChild(tr);
    table.appendChild(thead);
  }

  // 创建表格主体
  function createTableBody(leaves) {
    const table = document.createElement("table");

    table.className = config.classNames.join(" ");

    // 空数据处理
    if (leaves.length === 0) {
      const tr = document.createElement("tr");
      const td = document.createElement("td");

      td.className = "no-data";
      td.colSpan = config.headers?.length || 3;
      td.textContent = config.emptyText;
      tr.appendChild(td);
      table.appendChild(tr);
      return table;
    }

    // 确定列数
    const columnCount = leaves[0].length;
    const mergeColumnCount = Math.max(0, columnCount - 1);

    // 创建表头
    createHeader(table, mergeColumnCount);

    // 初始化列跟踪器
    const trackers = Array(mergeColumnCount)
      .fill()
      .map(() => ({}));
    // 创建数据行
    const tbody = document.createElement("tbody");

    leaves.forEach((chain) => {
      const tr = document.createElement("tr");

      // 处理需要合并的列
      for (let i = 0; i < mergeColumnCount; i++) {
        const node = chain[i];

        if (!trackers[i][node.id]) {
          const td = document.createElement("td");

          td.textContent = node.title;
          td.rowSpan = node.rowspan;
          tr.appendChild(td);
          trackers[i][node.id] = node.rowspan - 1;
        } else {
          trackers[i][node.id]--;
          if (trackers[i][node.id] === 0) delete trackers[i][node.id];
        }
      }

      // 添加最后一列(不合并)
      const lastTd = document.createElement("td");

      lastTd.textContent = chain[chain.length - 1].title;
      tr.appendChild(lastTd);
      tbody.appendChild(tr);
    });
    table.appendChild(tbody);
    return table;
  }

  // 执行主流程
  try {
    // 预处理数据
    data.forEach((node) => computeRowspan(node));

    const leaves = data.flatMap((node) => collectLeaves(node));
    // 生成表格
    const table = createTableBody(leaves);

    container.appendChild(table);
  } catch (error) {
    console.error("表格生成失败:", error);
    container.innerHTML = `<div class="error">表格生成失败: ${error.message}</div>`;
  }
}

// 使用示例
createMergedTable(list, document.body, {
  headers: ["一级分类", "二级分类", "具体项目"],
  classNames: ["custom-table"],
  emptyText: "没有找到数据",
});

vue+原生table

敬请期待…

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐