最近實現了一個純前端下載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> - 輸出:

- 引入:
