最近实现了一个纯前端下载Excel,并可以修改Excel样式的功能。由于实现过程比较曲折,没搜到较完整的示例,英文文档看起来也比较吃力,所以这里分享一个完整的示例。下面是两种方法分别介绍纯前端实现不修改样式和修改样式时导出Excel的方法:
- 使用SheetJS/js-xlsx(https://github.com/SheetJS/js-xlsx#input-type)导出Excel表格。
- 优点:简单。
- 缺点:免费版不支持修改表格样式。
- 安装:npm install xlsx。
- 性能:经测试,导出30多列,几百条数据的表格比较快;上千条大概需要等待3-5秒。
- 补充:支持很多种类的数据解析和导出,这里仅涉及导入二维数组,导出xlsx。持续更新中,最新的更新日期是2019年8月。
- 实现:
- 引入:
import XLSX from 'xlsx'
- 输入(数据源):
const sheetDatas = [ ['序号, '姓名', '性别'], [1, 'Lily', '女'], [2, 'John', '男'], [3, 'Mary', '女'] ]
- 调用方法:
const wb = XLSX.utils.book_new() // 创建一个工作簿 const ws = XLSX.utils.aoa_to_sheet(sheetDatas) // 使用二维数组创建一个工作表对象 XLSX.utils.book_append_sheet(wb, ws, '人员信息') // 向工作簿追加一个工作表 XLSX.writeFile(wb, `人员信息${moment().format('YYYYMMDD')}.xlsx`) // 写入文件
- 输出(Excel):
- 引入:
- 使用protobi/js-xlsx(https://github.com/protobi/js-xlsx)导出Excel表格。
- 优点:可修改表格样式(如字体、合并单元格、颜色等)。
- 缺点:比较复杂。
- 安装:npm install xlsx-style --save。
- 性能:经测试,导出30多列,几百条数据的表格比较快;上千条大概需要等待3-5秒。
- 补充:基于SheetJS/js-xlsx开发(注意:安装依赖的时候不需要装SheetJS/js-xlsx),开发停留在2017年,后面没有更新,没有维护,SheetJS/js-xlsx中的新方法不支持。
- 实现:
- 引入:
import XLSXStyle from 'js-xlsx'
- 我是在Vue中引入的这个依赖,引入之后会报错,解决方法:
- 修改源码:修改xlsx-style/dist/cpexcel.js文件中的第807行
var cpt = require('./cpt' + 'able');
var cpt = cptable;
- 修改webpack配置,与entry同一级新增一个字段,如图:
- 修改源码:修改xlsx-style/dist/cpexcel.js文件中的第807行
- 我是在Vue中引入的这个依赖,引入之后会报错,解决方法:
- 输入:
const sheetTitle = ["序号", "姓名", "性别"] // 表格第一行的标题
const sheetDatas = [ // 表格内容[1, 'Lily', '女'], [2, 'John', '男'], [3, 'Mary', '女'] ]
- 完整代码(在Vue中的实现):
<template> <div> <Button type="primary" @click='handleClick'> <slot></slot> </Button> </div> </template> <script> import moment from 'moment' import XLSXStyle from 'js-xlsx' export default { data() { return { sheetTitle: ["序号", "姓名", "性别"], sheetDatas: [ [1,'Lily','女'], [2,'John','男'], [3,'Mary','女'] ] } }, methods: { handleClick() { this.downloadExl(this.sheetDatas) }, downloadExl(sourceData) { const wopts = { bookType: 'xlsx', bookSST: true, type: 'binary', cellHeadStyles: true }; // excel表格样式 let fontBold = { font: { sz: 10, bold: true, // 粗字体 color: { rgb: "000000" }, name: "Times New Roman" } } let fontThin = { font: { sz: 10, bold: false, color: { rgb: "000000" }, name: "Times New Roman" } } let alignmentCenter = { alignment: { horizontal: 'center' } } // 居中对齐 let alignmentLeft = { alignment: { horizontal: 'left' } } let borderStyle = { // 边框粗细和颜色 border: { right: { style: 'thin', color: { rgb: "000000" } }, bottom: { style: 'thin', color: { rgb: "000000" } }, } } let cellBaseStyle = Object.assign({}, { fill: { bgColor: { indexed: 64 }, fgColor: { rgb: "FFFFFF" } }, }, borderStyle, alignmentCenter) let cellHeadStyle = Object.assign({}, cellBaseStyle, fontBold) let cellBodyStyle = Object.assign({}, cellBaseStyle, fontThin) // 数据 let tmpdata = sourceData[0], keyMap = [], outputPos1 = [], outputPos2 = [], noteStyle = {}, titleStyle = {}, cols = Object.keys(this.sheetDatas[0]).length if(sourceData[0] instanceof Array) sourceData.unshift({}) for (let k in tmpdata) { keyMap.push(k) sourceData[0][k] = k } tmpdata = [];//用来保存转换好的sourceData sourceData.map((v, i) => keyMap.map((k, j) => Object.assign({}, { v: v[k], position: (j > 25 ? this.getCharCol(j) : String.fromCharCode(65 + j)) + (i + 1) }))).reduce((prev, next) => prev.concat(next)).forEach((v, i) => tmpdata[v.position] = { v: v.v, s: cellBodyStyle }); outputPos1 = Object.keys(tmpdata) // 第一部分数据区域 for(let i=0; i<cols; i++) { let len = this.sheetDatas.length, letter = i > 25 ? this.getCharCol(i) : String.fromCharCode(65 + i), // A~AI noteObj = {}, titleObj = {} outputPos2.push(letter+(this.sheetDatas.length+2)) // 第二部分数据区域,用于添加备注行 // 第一行标题样式 titleObj = { [letter+'1']: { v: this.sheetTitle[i], s: cellHeadStyle } } Object.assign(titleStyle, titleObj) // 最后一行备注信息样式 if(i == 0) { // 第一个cell noteObj = { [letter+(len+1)]: { v: '备注:数据生成于' + this.sheetTime, t: 's', s: Object.assign({}, cellBodyStyle, alignmentLeft) }, } } else if(i == (cols-1)) { // 最后一个cell noteObj = { [letter+(len+1)]: { s: borderStyle } } } else { noteObj = { [letter+(len+1)]: { s: { border: { bottom: { style: 'thin', color: { rgb: "000000" } }, } } } } } Object.assign(noteStyle, noteObj) } let outputPos = [...outputPos1, ...outputPos2] //设置区域,比如表格从A1到D10 let tmpdataStyle = { '!merges': [{ s: {c: 0, r: this.sheetDatas.length}, e: {c: cols-1, r: this.sheetDatas.length} }], '!cols': [ //设置列宽 {wpx: 45}, /*a*/ {wpx: 85}, /*b*/ {wpx: 85} ] }; let tmpdataAll = Object.assign({}, tmpdata, tmpdataStyle, noteStyle, titleStyle) let tmpWB = { SheetNames: ['人员信息'], //保存的表标题 Sheets: { '人员信息': Object.assign({}, tmpdataAll, //内容 { '!ref': outputPos[0] + ':' + outputPos[outputPos.length-1] //设置填充区域 }) } }; let tmpDown = new Blob([this.s2ab(XLSXStyle.write(tmpWB, { bookType: 'xlsx', bookSST: false, type: 'binary' } //定义导出的格式类型 ))], { type: "" }); this.saveAs(tmpDown, `人员信息${moment().format('YYYYMMDD')}` + '.' + (wopts.bookType == "biff2" ? "xlsx" : wopts.bookType)); }, saveAs(obj, fileName) { let tmpa = document.createElement("a"); tmpa.download = fileName; tmpa.href = URL.createObjectURL(obj); tmpa.click(); setTimeout(function () { URL.revokeObjectURL(obj); }, 100); }, s2ab(s) { let buf = new ArrayBuffer(s.length); let view = new Uint8Array(buf); for (let i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; return buf; }, getCharCol(n) { let temCol = '', s = '', m = 0 while (n > 0) { m = n % 26 + 1 s = String.fromCharCode(m + 64) + s n = (n - m) / 26 } return s } }, mounted() {} } </script>
- 输出:
- 引入:
来源:oschina
链接:https://my.oschina.net/u/4282334/blog/3403954