You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从对象数组生成甘特图样式的HTML表格?

如何实现甘特图样式的HTML表格(修复行元素位置错误问题)

我有一个项目数据数组,想要生成甘特图样式的HTML表格。我写了一段代码,但发现行元素被添加到错误的位置,请问有没有其他实现方法?


原有JavaScript代码

// 提取唯一的年份和月份
var years = [...new Set(dataArray.map(item => item.StartYear))];
var months = [...new Set(dataArray.map(item => item.StartMonth).concat(dataArray.map(item => item.EndMonth)))];

// 排序年份和月份
years.sort();
months.sort();

// 动态创建表格
var tableHTML = '<table>';
tableHTML += '<tr><th>Year</th>';
years.forEach(year => tableHTML += `<th colspan="${months.length}">${year}</th>`);
tableHTML += '</tr>';
tableHTML += '<tr><td>Month</td>';
months.forEach(month => tableHTML += `<td>${month}</td>`);
tableHTML += '</tr>';

dataArray.forEach(item => {
  tableHTML += `<tr><td>${item.Project}</td>`;
  years.forEach(year => {
    months.forEach(month => {
      var isInProjectRange = (year === item.StartYear && month >= item.StartMonth && month <= item.EndMonth) ||
        (year === item.EndYear && month <= item.EndMonth);
      tableHTML += `<td>${isInProjectRange ? 'YES' : ''}</td>`;
    });
  });
  tableHTML += '</tr>';
});

tableHTML += '</table>';

// 将表格添加到id为'table-container'的div中
document.getElementById('table-container').innerHTML = tableHTML;

原有HTML代码

<div id="table-container"></div>

<script>
var dataArray = [
    {'Project': 'Project 1', 'StartYear': '2023', 'EndYear': '2023', 'StartMonth': 'Nov', 'EndMonth': 'Dec', 'Owner':'Tom'},
    {'Project': 'Project 1.1', 'StartYear': '2023', 'EndYear': '2023', 'StartMonth': 'Nov', 'EndMonth': 'Dec', 'Owner':'Tom'},
    {'Project': 'Project 2', 'StartYear': '2024', 'EndYear': '2024', 'StartMonth': 'Jan', 'EndMonth': 'May', 'Owner':'Tom'},
    {'Project': 'Project 2.1', 'StartYear': '2024', 'EndYear': '2024', 'StartMonth': 'Jan', 'EndMonth': 'Feb', 'Owner':'Tom'},
    {'Project': 'Project 2.2', 'StartYear': '2024', 'EndYear': '2024', 'StartMonth': 'Feb', 'EndMonth': 'Mar', 'Owner':'Tom'},
    {'Project': 'Project 2.3', 'StartYear': '2024', 'EndYear': '2024', 'StartMonth': 'Mar', 'EndMonth': 'May', 'Owner':'Tom'}
     ];
</script>

问题分析

原有代码存在两个核心问题导致行元素位置错误:

  1. 月份排序逻辑错误:直接用字符串排序会导致月份按字母顺序排列(比如Jan会排在Nov前面),不符合实际时间顺序;且每个年份会渲染所有月份的列,导致2023年的列中出现不属于该年份的Jan等月份,项目对应的单元格位置错位。
  2. 年份与月份的对应关系错误:每个年份应该只显示该年份实际存在的月份,而非所有月份,否则会出现大量空列且布局混乱。

修正后的实现代码

// 定义月份的正确时间顺序,用于排序和范围判断
const monthOrder = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

// 按年份分组,整理每个年份对应的有效月份
const yearMonthMap = {};
dataArray.forEach(item => {
  // 初始化年份对应的月份集合
  if (!yearMonthMap[item.StartYear]) yearMonthMap[item.StartYear] = new Set();
  if (item.StartYear !== item.EndYear && !yearMonthMap[item.EndYear]) yearMonthMap[item.EndYear] = new Set();

  // 补充项目覆盖的所有月份(包括中间月份)
  if (item.StartYear === item.EndYear) {
    const startIdx = monthOrder.indexOf(item.StartMonth);
    const endIdx = monthOrder.indexOf(item.EndMonth);
    for (let i = startIdx; i <= endIdx; i++) {
      yearMonthMap[item.StartYear].add(monthOrder[i]);
    }
  } else {
    // 跨年份的项目,补充开始年份的剩余月份和结束年份的前置月份
    const startIdx = monthOrder.indexOf(item.StartMonth);
    for (let i = startIdx; i < monthOrder.length; i++) {
      yearMonthMap[item.StartYear].add(monthOrder[i]);
    }
    const endIdx = monthOrder.indexOf(item.EndMonth);
    for (let i = 0; i <= endIdx; i++) {
      yearMonthMap[item.EndYear].add(monthOrder[i]);
    }
  }
});

// 对年份排序
const sortedYears = Object.keys(yearMonthMap).sort();

// 为每个年份的月份按时间顺序排序
sortedYears.forEach(year => {
  yearMonthMap[year] = Array.from(yearMonthMap[year]).sort((a, b) => monthOrder.indexOf(a) - monthOrder.indexOf(b));
});

// 动态生成表格(添加基础样式优化视觉)
let tableHTML = '<table border="1" cellpadding="8" cellspacing="0" style="border-collapse:collapse;">';
// 表头:年份行
tableHTML += '<tr><th style="background:#f0f0f0;">项目</th>';
sortedYears.forEach(year => {
  tableHTML += `<th colspan="${yearMonthMap[year].length}" style="background:#f0f0f0;">${year}</th>`;
});
tableHTML += '</tr>';
// 表头:月份行
tableHTML += '<tr><td style="background:#f5f5f5;">月份</td>';
sortedYears.forEach(year => {
  yearMonthMap[year].forEach(month => {
    tableHTML += `<td style="background:#f5f5f5;">${month}</td>`;
  });
});
tableHTML += '</tr>';

// 生成项目数据行
dataArray.forEach(item => {
  tableHTML += `<tr><td>${item.Project}</td>`;
  sortedYears.forEach(year => {
    yearMonthMap[year].forEach(month => {
      let isInRange = false;
      // 判断当前年月是否在项目时间范围内
      if (year === item.StartYear && year === item.EndYear) {
        const monthIdx = monthOrder.indexOf(month);
        const startIdx = monthOrder.indexOf(item.StartMonth);
        const endIdx = monthOrder.indexOf(item.EndMonth);
        isInRange = monthIdx >= startIdx && monthIdx <= endIdx;
      } else if (year === item.StartYear) {
        const monthIdx = monthOrder.indexOf(month);
        const startIdx = monthOrder.indexOf(item.StartMonth);
        isInRange = monthIdx >= startIdx;
      } else if (year === item.EndYear) {
        const monthIdx = monthOrder.indexOf(month);
        const endIdx = monthOrder.indexOf(item.EndMonth);
        isInRange = monthIdx <= endIdx;
      }
      // 用背景色块标记项目覆盖的月份,更贴近甘特图样式
      tableHTML += `<td>${isInRange ? '<div style="width:100%;height:24px;background:#4CAF50;border-radius:4px;"></div>' : ''}</td>`;
    });
  });
  tableHTML += '</tr>';
});

tableHTML += '</table>';

// 将表格插入页面容器
document.getElementById('table-container').innerHTML = tableHTML;

改进点说明

  1. 正确的月份排序:通过monthOrder数组确保月份按实际时间顺序排列。
  2. 年份-月份精准分组:每个年份只渲染该年份下项目覆盖的月份,避免空列和位置错位。
  3. 完善的范围判断:处理同一年份、跨年份的项目时间范围,确保单元格标记准确。
  4. 视觉优化:用背景色块替代文字标记,更符合甘特图的直观视觉效果,同时添加基础表格样式提升可读性。

内容的提问来源于stack exchange,提问作者grabrep gg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 14:39:52