基于JSON数据生成动态HTML表格的循环逻辑问题求助
解决动态多维度销售表格渲染问题
我明白你现在的困扰——要做一个按日期分组、展示多支付类型的动态表格,原代码只处理了cash这一种支付方式,而且多次遍历数据的方式不仅效率低,也没法支撑复杂的表格结构。咱们来调整代码,让它完全动态适配数据:
核心优化点
- 预构建数据映射:把原始数据转换成按「日期-门店」分组的结构,避免重复遍历数组
- 自动提取销售类型:从JSON数据里自动识别所有支付类型(比如
cash、swiggy、creditcard等),不用硬编码 - 优化表格结构:实现日期单元格合并,按销售类型拆分行,完善总计逻辑
修改后的完整代码
var data = [{ "billdate": "2018-08-01", "outlet": "S0001", "amount": 291589, "cash": 288276, "creditcard": 0, "coupon": 0, "paytm": 0, "credit": 0, "swiggy": 3313, "kb": 0, "bigbasket": 0 }, { "billdate": "2018-08-01", "outlet": "S0002", "amount": 58337, "cash": 56727, "creditcard": 0, "coupon": 0, "paytm": 0, "credit": 0, "swiggy": 1610, "kb": 0, "bigbasket": 0 }, { "billdate": "2018-08-01", "outlet": "S0009", "amount": 65970, "cash": 65970, "creditcard": 0, "coupon": 0, "paytm": 0, "credit": 0, "swiggy": 0, "kb": 0, "bigbasket": 0 }, { "billdate": "2018-08-02", "outlet": "S0001", "amount": 296125, "cash": 290480, "creditcard": 0, "coupon": 0, "paytm": 0, "credit": 0, "swiggy": 5645, "kb": 0, "bigbasket": 0 }, { "billdate": "2018-08-02", "outlet": "S0002", "amount": 56545, "cash": 55034, "creditcard": 0, "coupon": 0, "paytm": 0, "credit": 0, "swiggy": 1511, "kb": 0, "bigbasket": 0 }, { "billdate": "2018-08-02", "outlet": "S0009", "amount": 72213, "cash": 72213, "creditcard": 0, "coupon": 0, "paytm": 0, "credit": 0, "swiggy": 0, "kb": 0, "bigbasket": 0 } ] // 格式化数据,构建便捷的查询映射 let formatData = function(data) { const billdates = [...new Set(data.map(item => item.billdate))]; const outlets = [...new Set(data.map(item => item.outlet))]; // 提取所有销售类型(排除billdate、outlet、amount) const salesTypes = Object.keys(data[0]).filter(key => !['billdate', 'outlet', 'amount'].includes(key)); // 构建日期->门店->销售数据的映射,方便快速查找 const dateOutletMap = {}; data.forEach(item => { if (!dateOutletMap[item.billdate]) { dateOutletMap[item.billdate] = {}; } dateOutletMap[item.billdate][item.outlet] = { amount: item.amount, ...salesTypes.reduce((acc, type) => ({...acc, [type]: item[type]}), {}) }; }); return { billdates, outlets, salesTypes, dateOutletMap }; }; // 渲染表格核心函数 let renderTable = function(formattedData) { const { billdates, outlets, salesTypes, dateOutletMap } = formattedData; const tblContainer = document.getElementById("tblOlSalesSummary"); tblContainer.innerHTML = ""; // 清空容器 // 创建表格及基础样式 const table = document.createElement("table"); table.classList.add("table", "table-striped", "table-bordered", "table-hover"); // 构建表头 const thead = document.createElement("thead"); const headerRow = document.createElement("tr"); // 第一列:BillDate let th = document.createElement("th"); th.textContent = "BillDate"; th.classList.add("text-center"); headerRow.appendChild(th); // 第二列:Sales Type th = document.createElement("th"); th.textContent = "Sales Type"; th.classList.add("text-center"); headerRow.appendChild(th); // 门店列 outlets.forEach(outlet => { th = document.createElement("th"); th.textContent = outlet; th.classList.add("text-center"); headerRow.appendChild(th); }); // 总计列 th = document.createElement("th"); th.textContent = "Total"; th.classList.add("text-center"); headerRow.appendChild(th); thead.appendChild(headerRow); table.appendChild(thead); // 构建表体 const tbody = document.createElement("tbody"); let grandTotalAll = 0; // 全局总计 billdates.forEach(date => { let dateTotal = 0; // 当日所有销售类型总计 const dateRows = []; // 按销售类型生成行 salesTypes.forEach(type => { const row = document.createElement("tr"); let typeTotal = 0; // 当前销售类型的当日总计 // 日期单元格:只有第一行显示日期,合并单元格 if (type === salesTypes[0]) { const td = document.createElement("td"); td.textContent = date; td.rowSpan = salesTypes.length; // 跨所有销售类型行 td.classList.add("text-center"); row.appendChild(td); } // 销售类型列 const typeTd = document.createElement("td"); typeTd.textContent = type.charAt(0).toUpperCase() + type.slice(1); // 首字母大写 typeTd.classList.add("text-center"); row.appendChild(typeTd); // 各门店数据列 outlets.forEach(outlet => { const outletData = dateOutletMap[date][outlet] || {}; const value = outletData[type] || 0; typeTotal += value; const td = document.createElement("td"); td.textContent = value.toLocaleString('en-in'); td.classList.add("text-right"); row.appendChild(td); }); // 销售类型总计列 const totalTd = document.createElement("td"); totalTd.textContent = typeTotal.toLocaleString('en-in'); totalTd.classList.add("text-right"); row.appendChild(totalTd); dateTotal += typeTotal; dateRows.push(row); }); // 添加当日总计行 const dateTotalRow = document.createElement("tr"); dateTotalRow.classList.add("table-secondary"); // 合并日期和销售类型列 let td = document.createElement("td"); td.textContent = "Daily Total"; td.colSpan = 2; td.classList.add("text-center", "font-weight-bold"); dateTotalRow.appendChild(td); // 各门店当日总计(amount字段) outlets.forEach(outlet => { const outletAmount = dateOutletMap[date][outlet].amount; td = document.createElement("td"); td.textContent = outletAmount.toLocaleString('en-in'); td.classList.add("text-right"); dateTotalRow.appendChild(td); }); // 当日全局总计 td = document.createElement("td"); td.textContent = dateTotal.toLocaleString('en-in'); td.classList.add("text-right", "font-weight-bold"); dateTotalRow.appendChild(td); grandTotalAll += dateTotal; // 把当前日期的所有行添加到tbody dateRows.forEach(row => tbody.appendChild(row)); tbody.appendChild(dateTotalRow); }); // 添加全局总计行 const grandTotalRow = document.createElement("tr"); grandTotalRow.classList.add("table-dark"); let td = document.createElement("td"); td.textContent = "Grand Total"; td.colSpan = 2; td.classList.add("text-center", "font-weight-bold"); grandTotalRow.appendChild(td); // 各门店全局总计 outlets.forEach(outlet => { let outletGrandTotal = 0; billdates.forEach(date => { outletGrandTotal += dateOutletMap[date][outlet].amount; }); td = document.createElement("td"); td.textContent = outletGrandTotal.toLocaleString('en-in'); td.classList.add("text-right"); grandTotalRow.appendChild(td); }); // 全局总计值 td = document.createElement("td"); td.textContent = grandTotalAll.toLocaleString('en-in'); td.classList.add("text-right", "font-weight-bold"); grandTotalRow.appendChild(td); tbody.appendChild(grandTotalRow); table.appendChild(tbody); tblContainer.appendChild(table); } // 执行渲染 const formattedData = formatData(data); renderTable(formattedData);
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/css/bootstrap.min.css"> <div class="container mt-4"> <div class="row justify-content-center"> <table id="tblOlSalesSummary"></table> </div> </div>
关键代码解释
数据映射
dateOutletMap:
把原始数据转换成{日期: {门店: {各支付类型数据}}}的结构,这样在渲染时不用每次遍历整个数组,直接通过dateOutletMap[date][outlet][type]就能拿到对应值,提升性能。自动识别销售类型:
通过Object.keys(data[0]).filter(...)过滤掉非支付类型的字段,不管后端返回哪些新的支付方式,表格都能自动适配。单元格合并:
日期单元格使用rowSpan属性,跨当前日期下的所有销售类型行,让表格结构更清晰,避免重复显示日期。多层总计逻辑:
实现了「销售类型当日总计」→「门店当日总计」→「当日全局总计」→「门店全局总计」→「全局总计」的完整统计,完全贴合销售报表的需求。
这样修改后,表格就能完全动态渲染所有数据,不管后续数据库返回的日期、门店、支付类型如何变化,都能正确展示啦~
内容的提问来源于stack exchange,提问作者user10705797
相关产品推荐
相关产品推荐

