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

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'
  });
};
相关推荐
e***877016 小时前
windows配置永久路由
android·前端·后端
u***276116 小时前
TypeScript 与后端开发Node.js
javascript·typescript·node.js
星空的资源小屋16 小时前
跨平台下载神器ArrowDL,一网打尽所有资源
javascript·笔记·django
Dorcas_FE17 小时前
【tips】动态el-form-item中校验的注意点
前端·javascript·vue.js
小小前端要继续努力17 小时前
前端新人怎么更快的融入工作
前端
八月ouc17 小时前
解密JavaScript模块化演进:从IIFE到ES Module,深入理解现代前端工程化基石
javascript·es6·模块化·cmd·commonjs·amd·iife
四岁爱上了她17 小时前
input输入框焦点的获取和隐藏div,一个自定义的下拉选择
前端·javascript·vue.js
fouryears_2341717 小时前
现代 Android 后台应用读取剪贴板最佳实践
android·前端·flutter·dart
boolean的主人17 小时前
mac电脑安装nvm
前端
用户19729591889117 小时前
WKWebView的重定向(objective_c)
前端·ios