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

基于JSON数据生成动态HTML表格的循环逻辑问题求助

解决动态多维度销售表格渲染问题

我明白你现在的困扰——要做一个按日期分组、展示多支付类型的动态表格,原代码只处理了cash这一种支付方式,而且多次遍历数据的方式不仅效率低,也没法支撑复杂的表格结构。咱们来调整代码,让它完全动态适配数据:

核心优化点

  1. 预构建数据映射:把原始数据转换成按「日期-门店」分组的结构,避免重复遍历数组
  2. 自动提取销售类型:从JSON数据里自动识别所有支付类型(比如cash、swiggy、creditcard等),不用硬编码
  3. 优化表格结构:实现日期单元格合并,按销售类型拆分行,完善总计逻辑

修改后的完整代码

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>

关键代码解释

  1. 数据映射dateOutletMap:
    把原始数据转换成{日期: {门店: {各支付类型数据}}}的结构,这样在渲染时不用每次遍历整个数组,直接通过dateOutletMap[date][outlet][type]就能拿到对应值,提升性能。

  2. 自动识别销售类型:
    通过Object.keys(data[0]).filter(...)过滤掉非支付类型的字段,不管后端返回哪些新的支付方式,表格都能自动适配。

  3. 单元格合并:
    日期单元格使用rowSpan属性,跨当前日期下的所有销售类型行,让表格结构更清晰,避免重复显示日期。

  4. 多层总计逻辑:
    实现了「销售类型当日总计」→「门店当日总计」→「当日全局总计」→「门店全局总计」→「全局总计」的完整统计,完全贴合销售报表的需求。

这样修改后,表格就能完全动态渲染所有数据,不管后续数据库返回的日期、门店、支付类型如何变化,都能正确展示啦~

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:28:59