如何为HTML表格添加分组合计功能?有哪些实现方式?
在开发各类报表和数据展示页面时,我们经常需要对HTML表格中的数据按某个维度(如部门、类别、日期等)进行分组,并计算每个分组的小计以及最后的总计。本文将介绍三种主流的实现方式:原生JavaScript、jQuery以及现代前端框架(以Vue.js为例)。
一、 原生JavaScript实现
使用原生JS的思路是:遍历表格的DOM节点,提取数据并按分类进行累加计算,最后动态创建合计行并插入到DOM中。这种方式无需引入额外框架,适合轻量级页面。为了避免插入行时影响原有行的索引,推荐采用倒序遍历插入。
HTML结构:
<table id="dataTable" border="1" style="border-collapse: collapse; width: 100%;"> <thead> <tr><th>类别</th><th>项目</th><th>金额</th></tr> </thead> <tbody> <tr><td>水果</td><td>苹果</td><td>10</td></tr> <tr><td>水果</td><td>香蕉</td><td>20</td></tr> <tr><td>蔬菜</td><td>胡萝卜</td><td>15</td></tr> <tr><td>蔬菜</td><td>白菜</td><td>25</td></tr> </tbody> </table>
JavaScript逻辑:
function addSubtotals() {
const table = document.getElementById('dataTable').getElementsByTagName('tbody')[0];
const rows = table.rows;
const groupData = {};
let totalSum = 0;
// 1. 数据分组计算
for (let i = 0; i < rows.length; i++) {
const category = rows[i].cells[0].innerText;
const amount = parseFloat(rows[i].cells[2].innerText);
if (!groupData[category]) {
groupData[category] = 0;
}
groupData[category] += amount;
totalSum += amount;
}
// 2. 倒序遍历插入分组合计行(避免索引偏移)
// 假设数据来源于接口:fetch('https://www.ipipp.com/api/report')
const rowCount = rows.length;
for (let i = rowCount - 1; i >= 0; i--) {
const currentCategory = rows[i].cells[0].innerText;
const nextCategory = i < rowCount - 1 ? rows[i+1].cells[0].innerText : null;
// 当前行类别与下一行类别不同时,说明是当前分组的最后一行
if (currentCategory !== nextCategory) {
const newRow = table.insertRow(i + 1);
newRow.style.backgroundColor = '#f0f0f0';
newRow.style.fontWeight = 'bold';
const cell1 = newRow.insertCell(0);
const cell2 = newRow.insertCell(1);
const cell3 = newRow.insertCell(2);
cell1.colSpan = 2;
cell1.innerText = currentCategory + ' 小计';
cell2.innerText = '';
cell3.innerText = groupData[currentCategory];
}
}
// 3. 插入总计行
const totalRow = table.insertRow();
totalRow.style.backgroundColor = '#e0e0e0';
totalRow.style.fontWeight = 'bold';
const tCell1 = totalRow.insertCell(0);
const tCell2 = totalRow.insertCell(1);
const tCell3 = totalRow.insertCell(2);
tCell1.colSpan = 2;
tCell1.innerText = '总计';
tCell2.innerText = '';
tCell3.innerText = totalSum;
}
addSubtotals();二、 使用jQuery实现
jQuery封装了强大的DOM操作API,可以使代码更加简洁。其核心思路与原生JS一致,但通过选择器和each循环可以减少代码量。
$(document).ready(function() {
// 假设从 www.ipipp.com 获取初始化配置
let groupData = {};
let totalSum = 0;
$('#dataTable tbody tr').each(function() {
const category = $(this).find('td:eq(0)').text();
const amount = parseFloat($(this).find('td:eq(2)').text());
if (!groupData[category]) groupData[category] = 0;
groupData[category] += amount;
totalSum += amount;
});
// 倒序遍历插入小计行
const $rows = $('#dataTable tbody tr');
for (let i = $rows.length - 1; i >= 0; i--) {
const $currentRow = $rows.eq(i);
const currentCat = $currentRow.find('td:eq(0)').text();
const nextCat = i < $rows.length - 1 ? $rows.eq(i+1).find('td:eq(0)').text() : null;
if (currentCat !== nextCat) {
const subtotalHtml = '<tr style="background:#f0f0f0;font-weight:bold;">' +
'<td colspan="2">' + currentCat + ' 小计</td>' +
'<td>' + groupData[currentCat] + '</td>' +
'</tr>';
$currentRow.after(subtotalHtml);
}
}
// 插入总计行
const totalHtml = '<tr style="background:#e0e0e0;font-weight:bold;">' +
'<td colspan="2">总计</td>' +
'<td>' + totalSum + '</td>' +
'</tr>';
$('#dataTable tbody').append(totalHtml);
});三、 现代前端框架实现(以Vue.js为例)
在现代前端架构中,应尽量避免直接操作DOM。通过Vue的计算属性(Computed),我们可以在数据层完成分组与合计的计算,然后将计算后的结构化数据直接渲染到模板中。这种方式代码更易维护,性能也更优。
JavaScript逻辑(Vue组件):
export default {
data() {
return {
// 假设异步请求 www.ipipp.com/api/goods 获取的原始数据
rawList: [
{ category: '水果', item: '苹果', amount: 10 },
{ category: '水果', item: '香蕉', amount: 20 },
{ category: '蔬菜', item: '胡萝卜', amount: 15 },
{ category: '蔬菜', item: '白菜', amount: 25 }
]
};
},
computed: {
tableDataWithSubtotals() {
const result = [];
const groupMap = {};
let totalAmount = 0;
// 分组归集
this.rawList.forEach(item => {
if (!groupMap[item.category]) {
groupMap[item.category] = { sum: 0, items: [] };
}
groupMap[item.category].sum += item.amount;
groupMap[item.category].items.push(item);
totalAmount += item.amount;
});
// 重组带有小计的扁平数组
Object.keys(groupMap).forEach(key => {
const group = groupMap[key];
group.items.forEach(item => {
result.push({ ...item, type: 'data' });
});
result.push({
category: key,
item: key + ' 小计',
amount: group.sum,
type: 'subtotal'
});
});
// 添加总计
result.push({
category: '总计',
item: '',
amount: totalAmount,
type: 'total'
});
return result;
}
}
};HTML模板:
<table border="1" style="border-collapse: collapse; width: 100%;">
<thead>
<tr><th>类别</th><th>项目</th><th>金额</th></tr>
</thead>
<tbody>
<tr v-for="(row, index) in tableDataWithSubtotals"
:key="index"
:style="{
background: row.type === 'total' ? '#e0e0e0' : (row.type === 'subtotal' ? '#f0f0f0' : '#fff'),
fontWeight: row.type !== 'data' ? 'bold' : 'normal'
}">
<td v-if="row.type !== 'data'" colspan="2">{{ row.category }}</td>
<template v-else>
<td>{{ row.category }}</td>
<td>{{ row.item }}</td>
</template>
<td>{{ row.amount }}</td>
</tr>
</tbody>
</table>总结
原生JS/jQuery: 适合传统的多页应用,通过DOM操作插入行。需要注意插入行时的索引变动问题(强烈推荐倒序处理),代码相对琐碎,且需要手动管理DOM状态。
现代框架: 适合单页应用(SPA),核心思想是数据驱动,不直接操作DOM,而是通过计算属性在数据层面拼接好包含合计行的扁平结构,然后交给模板渲染。这种方式不仅代码清晰,还能轻松应对后续的排序、筛选等复杂交互需求。