动态生成多层表头表格算法

js 复制代码
/**
 * 动态生成多层表头表格
 * @param {Object} options - 表格配置
 * @param {Array} options.headers - 表头配置数组
 * @param {Array} options.data - 表格数据数组
 * @param {string} options.containerId - 容器ID
 */
function generateMultiHeaderTable(options) {
  const { headers, data, containerId } = options;
  const container = document.getElementById(containerId);
  if (!container) return;

  // 1. 计算表头层级和每个单元格的合并属性
  const headerLevels = calculateHeaderLevels(headers);
  const processedHeaders = processHeaders(headers, headerLevels);

  // 2. 生成表头HTML
  const theadHtml = generateThead(processedHeaders, headerLevels);

  // 3. 生成表格内容HTML
  const tbodyHtml = generateTbody(data, processedHeaders);

  // 4. 组合成完整表格并渲染
  const tableHtml = `
    <table border="1" cellpadding="8" cellspacing="0">
      ${theadHtml}
      ${tbodyHtml}
    </table>
  `;
  container.innerHTML = tableHtml;
}

/**
 * 计算表头的最大层级
 */
function calculateHeaderLevels(headers) {
  let maxLevel = 1;
  
  function traverse(header, currentLevel) {
    if (currentLevel > maxLevel) {
      maxLevel = currentLevel;
    }
    if (header.children && header.children.length) {
      header.children.forEach(child => traverse(child, currentLevel + 1));
    }
  }
  
  headers.forEach(header => traverse(header, 1));
  return maxLevel;
}

/**
 * 处理表头,计算每个单元格的rowspan和colspan
 */
function processHeaders(headers, totalLevels) {
  const result = [];
  
  function traverse(headers, currentLevel, parent) {
    headers.forEach(header => {
      // 标记当前层级
      header.level = currentLevel;
      
      // 没有子项的单元格需要跨越多行
      if (!header.children || !header.children.length) {
        header.rowspan = totalLevels - currentLevel + 1;
        header.colspan = 1;
        result.push(header);
      } else {
        // 有子项的单元格只占当前行
        header.rowspan = 1;
        // 计算需要横跨的列数(子项总数量)
        header.colspan = countLeafNodes(header.children);
        result.push(header);
        
        // 递归处理子项
        traverse(header.children, currentLevel + 1, header);
      }
    });
  }
  
  traverse(headers, 1, null);
  return result;
}

/**
 * 计算叶子节点数量(用于确定colspan)
 */
function countLeafNodes(headers) {
  let count = 0;
  
  function traverse(headers) {
    headers.forEach(header => {
      if (!header.children || !header.children.length) {
        count++;
      } else {
        traverse(header.children);
      }
    });
  }
  
  traverse(headers);
  return count;
}

/**
 * 生成表头HTML
 */
function generateThead(headers, totalLevels) {
  let thead = '<thead>';
  
  // 为每个层级生成一行
  for (let level = 1; level <= totalLevels; level++) {
    const levelHeaders = headers.filter(header => header.level === level);
    thead += '<tr>';
    
    levelHeaders.forEach(header => {
      thead += `<th rowspan="${header.rowspan}" colspan="${header.colspan}">${header.name}</th>`;
    });
    
    thead += '</tr>';
  }
  
  thead += '</thead>';
  return thead;
}

/**
 * 生成表格内容HTML
 */
function generateTbody(data, headers) {
  // 获取所有叶子节点(最终列)
  const leafHeaders = headers.filter(header => !header.children || !header.children.length);
  
  let tbody = '<tbody>';
  
  data.forEach(row => {
    tbody += '<tr>';
    
    leafHeaders.forEach(header => {
      tbody += `<td>${row[header.key] !== undefined ? row[header.key] : ''}</td>`;
    });
    
    tbody += '</tr>';
  });
  
  tbody += '</tbody>';
  return tbody;
}

// ------------------------------
// 使用示例
// ------------------------------
// 1. 定义表头结构
const headers = [
  { 
    name: '日期', 
    key: 'date',
    // 没有children表示这是叶子节点
  },
  { 
    name: '产品A', 
    children: [
      { name: '销量', key: 'a_sales' },
      { name: '销售额', key: 'a_revenue' }
    ]
  },
  { 
    name: '产品B', 
    children: [
      { name: '销量', key: 'b_sales' },
      { name: '销售额', key: 'b_revenue' },
      { 
        name: '增长率', 
        children: [
          { name: '周环比', key: 'b_week_growth' },
          { name: '月环比', key: 'b_month_growth' }
        ]
      }
    ]
  },
  { 
    name: '总计', 
    key: 'total'
  }
];

// 2. 定义表格数据
const tableData = [
  {
    date: '2023-01-01',
    a_sales: 120,
    a_revenue: 6000,
    b_sales: 80,
    b_revenue: 4000,
    b_week_growth: '12%',
    b_month_growth: '8%',
    total: 10000
  },
  {
    date: '2023-01-02',
    a_sales: 150,
    a_revenue: 7500,
    b_sales: 95,
    b_revenue: 4750,
    b_week_growth: '8%',
    b_month_growth: '5%',
    total: 12250
  }
];

// 3. 生成表格
window.onload = () => {
  generateMultiHeaderTable({
    headers: headers,
    data: tableData,
    containerId: 'tableContainer'
  });
};
相关推荐
To_OC18 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
To_OC19 小时前
LC 17 电话号码的字母组合:我的回溯算法,就是从这道题开窍的
javascript·算法·leetcode
vipbic19 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy19 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志19 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble20 小时前
c端系统,其实更像一个信息展示平台
前端
你怎么知道我是队长21 小时前
JavaScript的变量和数据类型介绍
开发语言·javascript·ecmascript
李姆斯21 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜1 天前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司1 天前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能