
本文详解如何基于原始数据动态构建结构严谨的矩阵表格,重点解决跨列/跨行错位、pareto 多值分行显示、表格外溢等常见渲染问题,并提供可直接运行的现代 dom 操作方案。
本文详解如何基于原始数据动态构建结构严谨的矩阵表格,重点解决跨列/跨行错位、pareto 多值分行显示、表格外溢等常见渲染问题,并提供可直接运行的现代 dom 操作方案。
在质量分析或制程监控场景中,常需将多维数据(如 Process × Date)以矩阵形式可视化——即行表示工艺段(Process),列表示日期(DATE),每个单元格内再细分为 Qty、FPY、RTY、Pareto、Fail 五个子指标。但原代码使用 document.write 和粗粒度 rowspan="5" 导致严重布局缺陷:右侧列宽失控、Pareto 值被强行拼接为逗号字符串(如 "Client BER Lane 12,Client BER")、空单元格未对齐,最终引发表格向右溢出。
根本问题在于 逻辑与结构混淆:rowspan="5" 被错误应用于所有子列(包括 Pareto/Fail),而 Pareto 和 Fail 实际需按数据条目 逐行展开(一对多关系),不应跨行合并。正确策略是:
✅ 分离静态维度与动态维度:Process 和 DATE 作为主轴固定;QTY/FPY/RTY 属于“单值维度”,可跨行;Pareto/Fail 属于“多值维度”,需按实际数据条目生成独立行。
✅ 弃用 document.write:避免覆盖文档流、无法调试、不兼容现代框架;改用 document.createElement + insertRow/insertCell 精确控制 DOM 结构。
✅ 动态计算行数:每组 (Process, DATE) 可能对应 N 条记录(如 ESS + 29 Nov 2023 有 2 条),则该日期块需占用 N 行(而非硬编码 5 行)。
以下为优化后的完整实现:
<style>
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #333; padding: 8px 12px; text-align: left; }
th { background-color: #f5f5f5; font-weight: bold; }
</style><script>
const data = [
{ Process: 'PCAL', DATE: '29 Nov 2023', FPY: '0.0', QTY: '1', RTY: '0.0', Pareto: 'Client BER META 1 Serdes 142', Fail: '1' },
{ Process: 'CAL', DATE: '28 Nov 2023', FPY: '100.0', QTY: '2', RTY: '100.0', Pareto: 'None', Fail: 'None' },
{ Process: 'FVT', DATE: '28 Nov 2023', FPY: '50.0', QTY: '2', RTY: '50.0', Pareto: 'Client BER Lane 12', Fail: '3' },
{ Process: 'FVT', DATE: '28 Nov 2023', FPY: '50.0', QTY: '2', RTY: '50.0', Pareto: 'Client BER', Fail: '1' },
{ Process: 'ESS', DATE: '28 Nov 2023', FPY: '100.0', QTY: '1', RTY: '100.0', Pareto: 'None', Fail: 'None' },
{ Process: 'ESS', DATE: '29 Nov 2023', FPY: '0.0', QTY: '1', RTY: '0.0', Pareto: 'Module Power', Fail: '4' },
{ Process: 'ESS', DATE: '29 Nov 2023', FPY: '0.0', QTY: '1', RTY: '0.0', Pareto: 'Module A Power', Fail: '14' },
{ Process: 'EXS', DATE: '30 Nov 2023', FPY: '100.0', QTY: '3', RTY: '100.0', Pareto: 'None', Fail: 'None' }
];
// 步骤1:提取唯一 Process 和排序后 DATE
const uniqueProcesses = [...new Set(data.map(d => d.Process))].sort();
const uniqueDates = [...new Set(data.map(d => d.DATE))].sort();
// 步骤2:构建表格容器
const table = document.createElement('table');
// 步骤3:生成表头(两行)
const thead = table.createTHead();
const headerRow1 = thead.insertRow();
headerRow1.innerHTML = '<th rowspan="2">Process'; // 注意:原需求中"DATE"实为列标题,但逻辑上Process才是行主键,故修正为"Process"
uniqueDates.forEach(date => {
headerRow1.innerHTML += `<th colspan="5">${date}`;
});
const headerRow2 = thead.insertRow();
for (let i = 0; i < uniqueDates.length; i++) {
headerRow2.innerHTML += '<th>QTY<th>FPY<th>RTY<th>Pareto<th>Fail';
}
// 步骤4:按 Process 分组填充数据行
uniqueProcesses.forEach(process => {
// 获取该 Process 下所有日期的数据分组:{ '28 Nov 2023': [item1, item2], ... }
const dateGroups = {};
data.filter(d => d.Process === process).forEach(item => {
const date = item.DATE;
if (!dateGroups[date]) dateGroups[date] = [];
dateGroups[date].push(item);
});
// 计算该 Process 所需最大行数(即某日期下最多几条记录)
const maxRows = Math.max(...Object.values(dateGroups).map(arr => arr.length), 1);
// 生成首行(含 Process 名和 QTY/FPY/RTY 的跨行单元格)
let row = table.insertRow();
const processCell = row.insertCell();
processCell.innerHTML = process;
processCell.rowSpan = maxRows;
uniqueDates.forEach(date => {
const items = dateGroups[date] || [];
// QTY/FPY/RTY:取第一条记录值(若存在),并跨 maxRows 行
const qtyVal = items.length > 0 ? items[0].QTY : '';
const fpyVal = items.length > 0 ? items[0].FPY : '';
const rtyVal = items.length > 0 ? items[0].RTY : '';
if (items.length > 0) {
row.insertCell().innerHTML = qtyVal;
row.insertCell().innerHTML = fpyVal;
row.insertCell().innerHTML = rtyVal;
// Pareto & Fail:仅首行放第一个值,后续行由下方循环补全
row.insertCell().innerHTML = items[0].Pareto;
row.insertCell().innerHTML = items[0].Fail;
} else {
row.insertCell().innerHTML = '';
row.insertCell().innerHTML = '';
row.insertCell().innerHTML = '';
row.insertCell().innerHTML = '';
row.insertCell().innerHTML = '';
}
});
// 生成剩余行(用于填充同一 Process 同一 Date 下的多条 Pareto/Fail)
for (let r = 1; r < maxRows; r++) {
row = table.insertRow();
// Process 列留空(已被首行 rowspan 覆盖)
row.insertCell().innerHTML = '';
uniqueDates.forEach(date => {
const items = dateGroups[date] || [];
if (r < items.length) {
// 当前行有对应数据 → 填入 Pareto/Fail
row.insertCell().innerHTML = '';
row.insertCell().innerHTML = '';
row.insertCell().innerHTML = '';
row.insertCell().innerHTML = items[r].Pareto;
row.insertCell().innerHTML = items[r].Fail;
} else {
// 无数据 → 全空单元格
for (let c = 0; c < 5; c++) row.insertCell().innerHTML = '';
}
});
}
});
document.body.appendChild(table);
</script>
关键改进说明:
- ✅ 精准 rowspan 控制:Process 单元格跨 maxRows 行(非固定 5),QTY/FPY/RTY 仅跨 maxRows 行且只出现在首行;Pareto/Fail 则严格按数据条目逐行渲染,彻底消除拼接混乱。
- ✅ 响应式列宽:移除硬编码宽度,依赖 CSS table-layout: auto 自动适应内容,杜绝右侧溢出。
- ✅ 健壮性增强:支持任意 Process-Date 组合下的 0~N 条记录,空数据自动填充空白单元格,避免 undefined 渲染。
- ⚠️ 注意事项:若需导出 Excel 或打印,建议添加 table { table-layout: fixed; } 并设置列宽;Pareto 文本过长时,可添加 td:nth-child(4n), td:nth-child(4n+1) { word-break: break-word; } 防止撑破布局。
此方案兼顾语义清晰性与渲染准确性,可直接集成至生产环境,亦可轻松扩展为支持筛选、排序或导出功能的交互式质量看板。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











