背景:vue项目中使用了handsontable做报表,表格比较复杂。后端返回表格数据,在前端整理成handsontable需要的数据格式后展示。涉及到了查询、根绝查询结果动态渲染表头、导出等功能。
因为当时搞了好久,所以这里详细记录下遇到的问题。

写了一个handsontable的小demo放在github上了 【传送门】,包含以上功能,项目启动后的访问地址:http://localhost:8080/#/hotTableComp
例图1


handson介绍

handsontable引用方式:cdn or npm or 访问本地静态文件
(当时由于项目打包的原因,打包后静态文件访问不到,所以就直接将handsontable.full.min.js和xlsx.core.min.js的包放在的服务器上,然后在index.html直接引用服务器上的地址,相当于挂在window对象中,在组件里就可以直接从window上取到使用了)。

  • cdn引用的方式,放在index.html文件中全局引入:
 <script src="https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.js"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.16.2/xlsx.core.min.js"></script>
  • 本地静态文件可以在Github上找到
  • npm安装的使用和前两种不同,还没有进行尝试,尝试之后更新
Handsontable配置说明

目录结构如下:
在这里插入图片描述

  • HandsonTable.js
    组件初始化时,实例化Handsontable
/**
 *  @description 生成报表,参数由父组件传入;
 *  @param {object} dataSource; 表格渲染的数据源;
 *  @param {array}  columns  列定义;
 *  @param {array}  colHeaders 表格头标签; 如果有合并表头的需求,失效,使用 nestedHeaders;
 *  @param {array}  nestedHeaders  合并表头时配置对象;
 *  @param {array}  mergeCells 合并行标题配置对象;
 */
renderDashboard(
  dataSource,
  columns,
  nestedHeaders,
  mergeCells,
  columnSummaryList
) {
  var hotElement = document.getElementById("handsonContainer");
  var hotElementContainer = hotElement.parentNode;
  // 设置setting对象;
  let hotSettings = JSON.parse(JSON.stringify(this.defaultSettings));
  hotSettings.data = dataSource; 
  hotSettings.columns = columns;
  hotSettings.mergeCells = mergeCells;
  hotSettings.nestedHeaders = nestedHeaders;
  hotSettings.columnSummary = columnSummaryList;
  hotSettings.readOnly = true;
  hotSettings.exportFile = true;
  hotSettings.rowHeaders = true;
  hotSettings.contextMenu = true;
  // 合并setting对象的个性化参数;
  if (!!this.customSettings) {
    hotSettings = Object.assign(hotSettings, this.customSettings);
  }

  // 初始化实例;
  var hot = new Handsontable(hotElement, hotSettings);
}

data中的默认设置:

defaultSettings: {
  licenseKey: "non-commercial-and-evaluation",
   stretchH: "all",
   width: "100%",
   autoWrapRow: true,
   height: 400,
   maxRows: 14,
   rowHeaders: true,
   columnSorting: false, //排序属性;
   // colHeaders: colHeaders || [],
   colWidths: 80,
   className: "htRight htMiddle",
   data: [],
   mergeCells: [],
   columns: [],
   nestedHeaders: [],
   columnSummary: []
},
  • hotTable.js
    nestedHeaders
    数组。实现自定义表头、合并表头等。
    要算好表格一共多少列,表头一共多少行。以第一张图片的为例,17列,4行。那么nestedHeaders数组应该长度为4,每一个元素中colspan和应为17:
// 合并表头;
nestedHeaders: [
	//第一行
   [
     {
       label: "2019年   9月  30日",
       colspan: 12
     },
     {
       label: "单位:万元",
       colspan: 4
     }
   ],
   //第二行
   [
     "",
     "",
     {
       label: "保险板块",
       colspan: 6
     },
     {
       label: "其中:代理费",
       colspan: 4
     },
     {
       label: "其中:经纪费",
       colspan: 4
     }
   ],
   //第三行
   [
     "",
     "",
     {
       label: "本月",
       colspan: 3
     },
     {
       label: "本年",
       colspan: 3
     },
     {
       label: "本月",
       colspan: 2
     },
     {
       label: "本年",
       colspan: 2
     },
     {
       label: "本月",
       colspan: 2
     },
     {
       label: "本年",
       colspan: 2
     },
     {
       label: "本月",
       colspan: 2
     },
     {
       label: "本年",
       colspan: 2
     }
   ],
   //第四行
   [
     "险类",
     "险种名称",
     "保费",
     "同比增长值",
     "同比增长率",
     "保费",
     "同比增长值",
     "同比增长率",
     "保费",
     "同比增长值",
     "保费",
     "同比增长值",
     "保费",
     "同比增长值",
     "保费",
     "同比增长值"
   ]
 ],

columns
每列对应字段,因为第一列是序号,所以一共写16列。
即columns为长度16的数组。

 // 定义列;
 columns: [
     {
       data: "ensureType",
       type: "text",
       width: 50,
       className: "htCenter htMiddle"
     },
     {
       data: "ensureName",
       type: "text",
       width: 110,
       className: "htCenter htMiddle"
     },
     {
       data: "monthFee",
       type: "numeric",
       numericFormat: {
         pattern: "0.00"
       }
     },
     {
       data: "monthIncreaseNum",
       type: "numeric",
       numericFormat: {
         pattern: "0.00"
       }
     },
     {
       data: "monthIncreaseRadio",
       type: "numeric",
       numericFormat: {
         pattern: "0.00"
       }
     },
     {
       data: "yearFee",
       type: "numeric",
       numericFormat: {
         pattern: "0.00"
       }
     },
     {
       data: "yearIncreaseNum",
       type: "numeric",
       numericFormat: {
         pattern: "0.00"
       }
     },
     {
       data: "yearIncreaseRadio",
       type: "numeric",
       numericFormat: {
         pattern: "0.00"
       }
     },
     {
       data: "agencyMonthFee",
       type: "numeric",
       numericFormat: {
         pattern: "0.00"
       }
     },
     {
       data: "agencyMonthIncreaseNum",
       type: "numeric",
       numericFormat: {
         pattern: "0.00"
       }
     },
     {
       data: "agencyYearFee",
       type: "numeric",
       numericFormat: {
         pattern: "0.00"
       }
     },
     {
       data: "agencyYearIncreaseNum",
       type: "numeric",
       numericFormat: {
         pattern: "0.00"
       }
     },
     {
       data: "manageMonthFee",
       type: "numeric",
       numericFormat: {
         pattern: "0.00"
       }
     },
     {
       data: "manageMonthIncreaseNum",
       type: "numeric",
       numericFormat: {
         pattern: "0.00"
       }
     },
     {
       data: "manageYearFee",
       type: "numeric",
       numericFormat: {
         pattern: "0.00"
       }
     },
     {
       data: "manageYearIncreaseNum",
       type: "numeric",
       numericFormat: {
         pattern: "0.00"
       }
     }
   ],

columnSummaryList
合计数据的配置对象集合,即进行行计算。例如(按序列来看)第1、2、5、6、7、8行相加为第9行,10、11、12行相加为第13行,9、13相加为第14行。(实际行数从0开始)
在这里插入图片描述

/**
 * @param 获取需要计算单元格配置;
 */
getColumnSummary() {
  let retSummaryCfg = [];
  let columnList = [2, 3, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15]; // 需要计算的列的索引的集合,从0开始算;
  for (var i = 0; i < columnList.length; i++) {
    var tempCfg1 = {
      destinationRow: 8,  //求和后存放“和”的行
      destinationColumn: columnList[i],  //目标的合并列
      type: "sum",
      forceNumeric: true,
      ranges: [
        [0, 1],  //被相加的行,相邻的可以放在一个数组中
        [4, 7]   //被相加的行 
      ]
    };
    retSummaryCfg.push(tempCfg1);
    var tempCfg2 = {
      destinationRow: 12, 
      destinationColumn: columnList[i],
      type: "sum",
      forceNumeric: true,
      ranges: [[9, 11]]
    };
    retSummaryCfg.push(tempCfg2);
    var tempCfg3 = {
      destinationRow: 13,  
      destinationColumn: columnList[i],
      type: "sum",
      forceNumeric: true,
      ranges: [
        [0, 1],
        [4, 7],
        [9, 11]
      ]
    };
    retSummaryCfg.push(tempCfg3);
  }

  return retSummaryCfg;  //最终返回合并的行的对象
}
  • exportTable.js
    导出表头
    由于直接导出当前表格时,表头无法导出,导出的是表格的内容部分,所以要在导出之前追加一个要导出的表头,格式如下:
    第一部分:写出表头汉字对应的内容,将要被合并的单元格置空。A1代表第1行A列对应的单元格,H4代表4行H列对应的单元格;
    第二部分:"!merges"接受一个数组,每个元素是要合并的单元格对象,例如:表头第一个要合并的是“2019年9月30日”那个单元格,那么应传入{ s: { c: 0, r: 0 }, e: { c: 11, r: 0 } },表示从0行0列开始,到0行11列结束。
head: {
  //第一部分
  A1: { v: "2019年   9月  30日" },
  B1: { v: "" },
  C1: { v: "" },
  D1: { v: "" },
  E1: { v: "" },
  F1: { v: "" },
  G1: { v: "" },
  H1: { v: "" },
  I1: { v: "" },
  J1: { v: "" },
  K1: { v: "" },
  L1: { v: "" },
  M1: { v: "单位:万元" },
  N1: { v: "" },
  O1: { v: "" },
  P1: { v: "" },

  A2: { v: "险类" },
  B2: { v: "险种名称" },
  C2: { v: "保险板块" },
  D2: { v: "" },
  E2: { v: "" },
  F2: { v: "" },
  G2: { v: "" },
  H2: { v: "" },
  I2: { v: "其中:保代" },
  J2: { v: "" },
  K2: { v: "" },
  L2: { v: "" },
  M2: { v: "其中:保经" },
  N2: { v: "" },
  O2: { v: "" },
  P2: { v: "" },

  A3: { v: "" },
  B3: { v: "" },
  C3: { v: "本月" },
  D3: { v: "" },
  E3: { v: "" },
  F3: { v: "本年" },
  G3: { v: "" },
  H3: { v: "" },
  I3: { v: "本月" },
  J3: { v: "" },
  K3: { v: "本年" },
  L3: { v: "" },
  M3: { v: "本月" },
  N3: { v: "" },
  O3: { v: "本年" },
  P3: { v: "" },

  A4: { v: "" },
  B4: { v: "" },
  C4: { v: "保费" },
  D4: { v: "同比增长值" },
  E4: { v: "同比增长率" },
  F4: { v: "保费" },
  G4: { v: "同比增长值" },
  H4: { v: "同比增长率" },
  I4: { v: "保费" },
  J4: { v: "同比增长值" },
  K4: { v: "保费" },
  L4: { v: "同比增长值" },
  M4: { v: "保费" },
  N4: { v: "同比增长值" },
  O4: { v: "保费" },
  P4: { v: "同比增长值" },

  //第二部分
  "!merges": [
    // s: start  e: end  c: col  r: row
    { s: { c: 0, r: 0 }, e: { c: 11, r: 0 } },
    { s: { c: 12, r: 0 }, e: { c: 15, r: 0 } },

    { s: { c: 0, r: 1 }, e: { c: 0, r: 3 } },
    { s: { c: 1, r: 1 }, e: { c: 1, r: 3 } },

    { s: { c: 2, r: 1 }, e: { c: 7, r: 1 } },
    { s: { c: 8, r: 1 }, e: { c: 11, r: 1 } },
    { s: { c: 12, r: 1 }, e: { c: 15, r: 1 } },

    { s: { c: 2, r: 2 }, e: { c: 4, r: 2 } },
    { s: { c: 5, r: 2 }, e: { c: 7, r: 2 } },
    { s: { c: 8, r: 2 }, e: { c: 9, r: 2 } },
    { s: { c: 10, r: 2 }, e: { c: 11, r: 2 } },
    { s: { c: 12, r: 2 }, e: { c: 13, r: 2 } },
    { s: { c: 14, r: 2 }, e: { c: 15, r: 2 } },

    { s: { c: 0, r: 4 }, e: { c: 0, r: 12 } },
    { s: { c: 0, r: 13 }, e: { c: 0, r: 15 } },
    { s: { c: 0, r: 16 }, e: { c: 1, r: 16 } },
    { s: { c: 0, r: 17 }, e: { c: 1, r: 17 } }
  ]
},

导出方法
这里用到了XLSX插件,xlsxUtils上面挂载了插件的方法。

//导出按钮触发
/*
 * @param {String} 默认显示的日期
 */
exportFun(date) {
  var wopts = { bookType: "xlsx", bookSST: false, type: "binary" };
  this.downloadExl(date);
},
downloadExl(date) {
  var data = xlsxUtils.format2Sheet(
    this.dataSource,
    0,
    this.rowHeaderNum,
    this.keyMap
  ); //偏移2行按keyMap顺序转换
  var dataKeys = Object.keys(data);

  for (var k in this.exportHead) {
    data[k] = this.exportHead[k]; //追加列头
  }
  data.A1.v = date.slice(0, 7);

  var wb = xlsxUtils.format2WB(
    data,
    undefined,
    undefined,
    "A1:" + dataKeys[dataKeys.length - 1]
  );
  this.saveAs(xlsxUtils.format2Blob(wb), `${this.tableTitle}.xlsx`);
},
saveAs(obj, fileName) {
  var tmpa = document.createElement("a");
  tmpa.download = fileName || "下载";
  tmpa.href = URL.createObjectURL(obj); //绑定a标签
  tmpa.click(); //模拟点击实现下载
  setTimeout(function() {
    //延时释放
    URL.revokeObjectURL(obj); //用URL.revokeObjectURL()来释放这个object URL
  }, 100);
}

最后

项目中也能找到这以下文件:

handsontable.full.min.js
xlsx.core.min.js
handsontable.js
xlsx.utils.min.js

Blob.js
Export2Excel.js

官网demo传送门,实例了一个Handontable对象。
在这里插入图片描述

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐