在表格展示数据时,经常会遇到首列内容对应多行相同数据的情况,比如同一个分类下有多个子项,此时首列的分类名称会重复显示多次,既占用空间又影响阅读体验,我们可以通过多种方式解决这个问题。

使用HTML rowspan属性手动合并
如果表格数据是静态的,或者我们可以提前知道重复内容的行数,可以直接使用HTML的<td>标签的rowspan属性来合并单元格。rowspan的值表示当前单元格要合并的行数,合并后其他被覆盖的行就不需要再写对应的<td>标签了。
下面是一个静态表格的示例,同一个部门下有多个员工,部门列需要合并:
<table border="1" cellpadding="10" cellspacing="0">
<thead>
<tr>
<th>部门</th>
<th>员工姓名</th>
<th>职位</th>
</tr>
</thead>
<tbody>
<tr>
<!-- 部门列合并3行 -->
<td rowspan="3">技术部</td>
<td>张三</td>
<td>前端工程师</td>
</tr>
<tr>
<!-- 被合并的行不需要再写部门列的td -->
<td>李四</td>
<td>后端工程师</td>
</tr>
<tr>
<td>王五</td>
<td>测试工程师</td>
</tr>
<tr>
<td rowspan="2">产品部</td>
<td>赵六</td>
<td>产品经理</td>
</tr>
<tr>
<td>孙七</td>
<td>交互设计师</td>
</tr>
</tbody>
</table>
前端动态渲染时处理重复数据
如果表格数据是通过接口动态获取的,我们需要先对数据进行处理,找出首列重复的内容,计算每组的行数,再生成对应的rowspan值。核心思路是先对数据按首列字段分组,然后遍历分组后的数据生成表格行。
下面以JavaScript渲染动态表格为例,假设接口返回的员工数据如下:
// 接口返回的原始数据,同一个deptName对应多条员工记录
const tableData = [
{ deptName: '技术部', empName: '张三', job: '前端工程师' },
{ deptName: '技术部', empName: '李四', job: '后端工程师' },
{ deptName: '技术部', empName: '王五', job: '测试工程师' },
{ deptName: '产品部', empName: '赵六', job: '产品经理' },
{ deptName: '产品部', empName: '孙七', job: '交互设计师' },
{ deptName: '运营部', empName: '周八', job: '运营专员' }
];
接下来处理数据,给每条数据添加rowspan信息:
// 处理数据,添加rowspan标识
function processTableData(data, firstColKey) {
const result = [];
// 先按首列字段分组
const groupMap = {};
data.forEach(item => {
const key = item[firstColKey];
if (!groupMap[key]) {
groupMap[key] = [];
}
groupMap[key].push(item);
});
// 遍历分组,给每组的第一条数据添加rowspan,其余的不添加
Object.keys(groupMap).forEach(key => {
const group = groupMap[key];
group.forEach((item, index) => {
if (index === 0) {
// 第一组数据的rowspan为当前分组的长度
result.push({ ...item, rowSpan: group.length });
} else {
result.push({ ...item, rowSpan: 0 });
}
});
});
return result;
}
const processedData = processTableData(tableData, 'deptName');
console.log(processedData);
然后渲染表格时,根据rowSpan的值判断是否添加rowspan属性,rowSpan为0的话就不渲染该列的<td>:
// 渲染表格的示例代码
function renderTable(data) {
const tbody = document.querySelector('#dynamicTable tbody');
tbody.innerHTML = '';
data.forEach(item => {
const tr = document.createElement('tr');
// 处理首列
if (item.rowSpan > 0) {
const td = document.createElement('td');
td.textContent = item.deptName;
td.setAttribute('rowspan', item.rowSpan);
tr.appendChild(td);
}
// 处理其他列
const nameTd = document.createElement('td');
nameTd.textContent = item.empName;
tr.appendChild(nameTd);
const jobTd = document.createElement('td');
jobTd.textContent = item.job;
tr.appendChild(jobTd);
tbody.appendChild(tr);
});
}
// 假设页面有一个id为dynamicTable的表格
renderTable(processedData);
后端返回数据时预先分组
如果前端处理逻辑比较复杂,也可以让后端在返回数据时直接按首列字段分组,返回结构化的数据,这样前端渲染时不需要做额外的分组处理,直接遍历分组数据即可。
后端可以返回如下格式的数据:
{
"code": 0,
"data": [
{
"deptName": "技术部",
"rowSpan": 3,
"empList": [
{ "empName": "张三", "job": "前端工程师" },
{ "empName": "李四", "job": "后端工程师" },
{ "empName": "王五", "job": "测试工程师" }
]
},
{
"deptName": "产品部",
"rowSpan": 2,
"empList": [
{ "empName": "赵六", "job": "产品经理" },
{ "empName": "孙七", "job": "交互设计师" }
]
},
{
"deptName": "运营部",
"rowSpan": 1,
"empList": [
{ "empName": "周八", "job": "运营专员" }
]
}
]
}
前端拿到这种格式的数据后,渲染逻辑会更简单:
function renderGroupTable(groupData) {
const tbody = document.querySelector('#groupTable tbody');
tbody.innerHTML = '';
groupData.forEach(group => {
group.empList.forEach((emp, index) => {
const tr = document.createElement('tr');
// 首条员工记录添加合并的td
if (index === 0) {
const td = document.createElement('td');
td.textContent = group.deptName;
td.setAttribute('rowspan', group.rowSpan);
tr.appendChild(td);
}
const nameTd = document.createElement('td');
nameTd.textContent = emp.empName;
tr.appendChild(nameTd);
const jobTd = document.createElement('td');
jobTd.textContent = emp.job;
tr.appendChild(jobTd);
tbody.appendChild(tr);
});
});
}
注意事项
- 使用rowspan合并单元格时,要确保合并的行数是正确的,否则会导致表格结构错乱。
- 如果表格有排序功能,排序后首列的重复分组会发生变化,需要重新计算rowspan的值,或者禁用首列的排序。
- 如果表格支持动态增删行,增删后也需要重新处理首列的合并逻辑,避免显示异常。
- 当首列内容可能为空时,需要额外处理空值的分组逻辑,避免空值被合并到正常内容中。