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

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'
  });
};
相关推荐
hywel6 小时前
一开始只是想整理下书签,结果做成了一个 AI 插件 😂
前端
傅里叶6 小时前
SchedulerBinding 的三个Frame回调
前端·flutter
小小前端_我自坚强6 小时前
React Hooks 使用详解
前端·react.js·redux
java水泥工6 小时前
基于Echarts+HTML5可视化数据大屏展示-车辆综合管控平台
前端·echarts·html5·大屏模版
aklry6 小时前
elpis之学习总结
前端·vue.js
笔尖的记忆6 小时前
【前端架构和框架】react中Scheduler调度原理
前端·面试
_advance6 小时前
我是怎么把 JavaScript 的 this 和箭头函数彻底搞明白的——个人学习心得
前端
右子6 小时前
React 编程的优雅艺术:从设计到实现
前端·react.js·mobx
清灵xmf7 小时前
npm install --legacy-peer-deps:它到底做了什么,什么时候该用?
前端·npm·node.js