vue前端匯出複雜表頭Excel
阿新 • • 發佈:2020-12-10
vue前端匯出複雜表頭Excel
最近因為業務需求,需要做一個匯出複雜表頭為Excel的功能,需要要匯出的頁面如下:
欄位實在是太多了,截不完。。。這是一個二級表頭的
1、安裝
npm install file-saver xlsx -S
npm install script-loader -D
這是生成excel表格所需要的的依賴
2、在src目錄下建立一個vendor的資料夾,裡面存放Blob.js和Export2Excel2.js兩個JS檔案,這兩個包的具體內容:
- Blob.js
/* eslint-disable */ /* Blob.js * A Blob implementation. * 2014-05-27 * * By Eli Grey, http://eligrey.com * By Devin Samarin, https://github.com/eboyjr * License: X11/MIT * See LICENSE.md */ /*global self, unescape */ /*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, plusplus: true */ /*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */ (function (view) { "use strict"; view.URL = view.URL || view.webkitURL; if (view.Blob && view.URL) { try { new Blob; return; } catch (e) {} } // Internally we use a BlobBuilder implementation to base Blob off of // in order to support older browsers that only have BlobBuilder var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function (view) { var get_class = function (object) { return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1]; }, FakeBlobBuilder = function BlobBuilder() { this.data = []; }, FakeBlob = function Blob(data, type, encoding) { this.data = data; this.size = data.length; this.type = type; this.encoding = encoding; }, FBB_proto = FakeBlobBuilder.prototype, FB_proto = FakeBlob.prototype, FileReaderSync = view.FileReaderSync, FileException = function (type) { this.code = this[this.name = type]; }, file_ex_codes = ( "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR " + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR" ).split(" "), file_ex_code = file_ex_codes.length, real_URL = view.URL || view.webkitURL || view, real_create_object_URL = real_URL.createObjectURL, real_revoke_object_URL = real_URL.revokeObjectURL, URL = real_URL, btoa = view.btoa, atob = view.atob , ArrayBuffer = view.ArrayBuffer, Uint8Array = view.Uint8Array; FakeBlob.fake = FB_proto.fake = true; while (file_ex_code--) { FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1; } if (!real_URL.createObjectURL) { URL = view.URL = {}; } URL.createObjectURL = function (blob) { var type = blob.type, data_URI_header; if (type === null) { type = "application/octet-stream"; } if (blob instanceof FakeBlob) { data_URI_header = "data:" + type; if (blob.encoding === "base64") { return data_URI_header + ";base64," + blob.data; } else if (blob.encoding === "URI") { return data_URI_header + "," + decodeURIComponent(blob.data); } if (btoa) { return data_URI_header + ";base64," + btoa(blob.data); } else { return data_URI_header + "," + encodeURIComponent(blob.data); } } else if (real_create_object_URL) { return real_create_object_URL.call(real_URL, blob); } }; URL.revokeObjectURL = function (object_URL) { if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) { real_revoke_object_URL.call(real_URL, object_URL); } }; FBB_proto.append = function (data /*, endings*/ ) { var bb = this.data; // decode data to a binary string if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) { var str = "", buf = new Uint8Array(data), i = 0, buf_len = buf.length; for (; i < buf_len; i++) { str += String.fromCharCode(buf[i]); } bb.push(str); } else if (get_class(data) === "Blob" || get_class(data) === "File") { if (FileReaderSync) { var fr = new FileReaderSync; bb.push(fr.readAsBinaryString(data)); } else { // async FileReader won't work as BlobBuilder is sync throw new FileException("NOT_READABLE_ERR"); } } else if (data instanceof FakeBlob) { if (data.encoding === "base64" && atob) { bb.push(atob(data.data)); } else if (data.encoding === "URI") { bb.push(decodeURIComponent(data.data)); } else if (data.encoding === "raw") { bb.push(data.data); } } else { if (typeof data !== "string") { data += ""; // convert unsupported types to strings } // decode UTF-16 to binary string bb.push(unescape(encodeURIComponent(data))); } }; FBB_proto.getBlob = function (type) { if (!arguments.length) { type = null; } return new FakeBlob(this.data.join(""), type, "raw"); }; FBB_proto.toString = function () { return "[object BlobBuilder]"; }; FB_proto.slice = function (start, end, type) { var args = arguments.length; if (args < 3) { type = null; } return new FakeBlob( this.data.slice(start, args > 1 ? end : this.data.length), type, this.encoding ); }; FB_proto.toString = function () { return "[object Blob]"; }; FB_proto.close = function () { this.size = this.data.length = 0; }; return FakeBlobBuilder; }(view)); view.Blob = function Blob(blobParts, options) { var type = options ? (options.type || "") : ""; var builder = new BlobBuilder(); if (blobParts) { for (var i = 0, len = blobParts.length; i < len; i++) { builder.append(blobParts[i]); } } return builder.getBlob(type); }; }(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));
-
Export2Excel2.js
其實就是主要引用了Export2Excel2.js這個檔案裡面
export_json_to_excel
方法
/* eslint-disable */ import { saveAs } from 'file-saver' import XLSX from 'xlsx' function generateArray(table) { var out = []; var rows = table.querySelectorAll('tr'); var ranges = []; for (var R = 0; R < rows.length; ++R) { var outRow = []; var row = rows[R]; var columns = row.querySelectorAll('td'); for (var C = 0; C < columns.length; ++C) { var cell = columns[C]; var colspan = cell.getAttribute('colspan'); var rowspan = cell.getAttribute('rowspan'); var cellValue = cell.innerText; if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue; //Skip ranges ranges.forEach(function (range) { if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) { for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null); } }); //Handle Row Span if (rowspan || colspan) { rowspan = rowspan || 1; colspan = colspan || 1; ranges.push({ s: { r: R, c: outRow.length }, e: { r: R + rowspan - 1, c: outRow.length + colspan - 1 } }); }; //Handle Value outRow.push(cellValue !== "" ? cellValue : null); //Handle Colspan if (colspan) for (var k = 0; k < colspan - 1; ++k) outRow.push(null); } out.push(outRow); } return [out, ranges]; }; function datenum(v, date1904) { if (date1904) v += 1462; var epoch = Date.parse(v); return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000); } function sheet_from_array_of_arrays(data, opts) { var ws = {}; var range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 } }; for (var R = 0; R != data.length; ++R) { for (var C = 0; C != data[R].length; ++C) { if (range.s.r > R) range.s.r = R; if (range.s.c > C) range.s.c = C; if (range.e.r < R) range.e.r = R; if (range.e.c < C) range.e.c = C; var cell = { v: data[R][C] }; if (cell.v == null) continue; var cell_ref = XLSX.utils.encode_cell({ c: C, r: R }); if (typeof cell.v === 'number') cell.t = 'n'; else if (typeof cell.v === 'boolean') cell.t = 'b'; else if (cell.v instanceof Date) { cell.t = 'n'; cell.z = XLSX.SSF._table[14]; cell.v = datenum(cell.v); } else cell.t = 's'; ws[cell_ref] = cell; } } if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range); return ws; } function Workbook() { if (!(this instanceof Workbook)) return new Workbook(); this.SheetNames = []; this.Sheets = {}; } function s2ab(s) { var buf = new ArrayBuffer(s.length); var view = new Uint8Array(buf); for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; return buf; } export function export_table_to_excel(id) { var theTable = document.getElementById(id); var oo = generateArray(theTable); var ranges = oo[1]; /* original data */ var data = oo[0]; var ws_name = "SheetJS"; var wb = new Workbook(), ws = sheet_from_array_of_arrays(data); /* add ranges to worksheet */ // ws['!cols'] = ['apple', 'banan']; ws['!merges'] = ranges; /* add worksheet to workbook */ wb.SheetNames.push(ws_name); wb.Sheets[ws_name] = ws; var wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: false, type: 'binary' }); saveAs(new Blob([s2ab(wbout)], { type: "application/octet-stream" }), "test.xlsx") } // 對此方法進行修改,如下: export function export_json_to_excel({ multiHeader2 = [], // 第一行表頭 multiHeader = [], // 第二行表頭 header, // 第三行表頭 data, filename, //檔名 merges = [], // 合併 autoWidth = true, bookType = 'xlsx' } = {}) { /* original data */ filename = filename || '列表'; data = [...data] data.unshift(header); for (let i = multiHeader2.length - 1; i > -1; i--) { data.unshift(multiHeader2[i]) } for (let i = multiHeader.length - 1; i > -1; i--) { data.unshift(multiHeader[i]) } var ws_name = "SheetJS"; var wb = new Workbook(), ws = sheet_from_array_of_arrays(data); if (merges.length > 0) { if (!ws['!merges']) ws['!merges'] = []; merges.forEach(item => { ws['!merges'].push(XLSX.utils.decode_range(item)) }) } if (autoWidth) { /*設定worksheet每列的最大寬度*/ const colWidth = data.map(row => row.map(val => { /*先判斷是否為null/undefined*/ if (val == null) { return { 'wch': 10 }; } /*再判斷是否為中文*/ else if (val.toString().charCodeAt(0) > 255) { return { 'wch': val.toString().length * 2 }; } else { return { 'wch': val.toString().length }; } })) /*以第一行為初始值*/ let result = colWidth[0]; for (let i = 1; i < colWidth.length; i++) { for (let j = 0; j < colWidth[i].length; j++) { if (result[j]['wch'] < colWidth[i][j]['wch']) { result[j]['wch'] = colWidth[i][j]['wch']; } } } ws['!cols'] = result; } /* add worksheet to workbook */ wb.SheetNames.push(ws_name); wb.Sheets[ws_name] = ws; var wbout = XLSX.write(wb, { bookType: bookType, bookSST: false, type: 'binary' }); saveAs(new Blob([s2ab(wbout)], { type: "application/octet-stream" }), `${filename}.${bookType}`); }
3. 在你要匯出excel檔案的vue元件中新增兩個方法
handleExportMarketingList()
和formatJson
,再將匯出的按鈕新增一個點選事件,繫結handleExportMarketingList
<div style="margin-left: 20px;margin-top: 6px; margin-bottom: 6px; "> <el-button type="info" size="small" @click="handleExportMarketingList">Export</el-button> </div>
// 匯出複雜表頭
handleExportMarketingList() {
require.ensure([], () => {
const {export_json_to_excel} = require('@/vendor/Export2Excel2');
//multiHeader數組裡面新增的是一級表頭的名稱,要按順序新增每個表頭的的名稱,如果遇到橫向合併單元格的地方,合併了多少個單元格,就要留幾個空字串佔位
const multiHeader = [
[
'Project ID#', 'Project/Product Name', 'Customer', 'Product Type', 'Project Type', 'Resource Availability', 'System Owner',
'Architecture', 'Form Factor', 'Platform', 'When To Start', 'Project Complete Target', 'Project Total Expense', '', '', '',
'FY2022 (K$)--Q1 FCST', '', '', '', 'FY2022 (K$)--Q2 FCST', '', '', '', 'FY2022 (K$)--Q3 FCST', '', '', '', 'FY2022 (K$)--Q4 FCST',
'', '', '', 'FY2022 (K$)--FY2022 Outlook', '', '', '', 'Actual Spending Before FY2022', '', '', '', 'Spending After FY2022', '', '', '', 'Comment',
],
];
//tHeader數組裡面放的是二級表頭的名稱,也要按順序新增每個表頭的的名稱,新增的時候要注意,如果是一級表頭上下單元格合併地方,要用空字串補上佔位。
const tHeader = [
'', '', '', '', '', '', '', '', '', '', '', '', 'Material', 'NRE', 'Cert', 'TTL', 'Material', 'NRE', 'Cert', 'TTL',
'Material', 'NRE', 'Cert', 'TTL', 'Material', 'NRE', 'Cert', 'TTL', 'Material', 'NRE', 'Cert', 'TTL', 'Material', 'NRE', 'Cert', 'TTL',
'Material', 'NRE', 'Cert', 'TTL', 'Material', 'NRE', 'Cert', 'TTL', '',
];
//filterVal數組裡面放的是表頭title對應的欄位名稱
const filterVal = ['id', 'name', 'customer', 'product_type', 'project_type', 'resource_availability', 'system_owner',
'architecture', 'form_factor', 'platform', 'when_to_start', 'project_complete_target', 'total_material', 'total_nre',
'total_cert', 'total_ttl', 'q1_material', 'q1_nre', 'q1_cert', 'q1_ttl', 'q2_material', 'q2_nre', 'q2_cert', 'q2_ttl',
'q3_material', 'q3_nre', 'q3_cert', 'q3_ttl', 'q4_material', 'q4_nre', 'q4_cert', 'q4_ttl', 'outlook_material',
'outlook_nre', 'outlook_cert', 'outlook_ttl', 'actual_material', 'actual_nre', 'actual_cert', 'actual_ttl', 'spending_material',
'spending_nre', 'spending_cert', 'spending_ttl', 'comment',
];
// this.cash是後端返回來的陣列,用map方法遍歷,這裡你可以改你想要的資料,最後用return將每個欄位一次返回就好啦
const list = this.cash.map((item, key) => {
var product_type = '';
if (item.product_type === 0) {
this.product_type = 'Sustaining' || null
} else if (item.product_type === 1) {
this.product_type = 'NPI'
} else if (item.product_type === 2) {
this.product_type = 'Proposal'
} else if (item.product_type === 3) {
this.product_type = 'POC'
} else {
this.product_type = null
}
var project_type = '';
if (item.project_type === 0) {
this.project_type = 'Ongoing'
} else if (item.project_type === 1) {
this.project_type = 'Evaluating'
} else if (item.project_type === 2) {
this.project_type = 'Upcoming'
} else {
this.project_type = null
}
var resource_availability = '';
if (item.resource_availability === 0) {
this.resource_availability ='Staffed'
} else if (item.resource_availability === 1) {
this.resource_availability ='Un-staffed'
} else if (item.move_resource_availability === 2) {
this.resource_availability = 'Cancel'
} else if (item.move_resource_availability === 3) {
this.resource_availability = 'Done'
}else {
this.resource_availability = null
}
return {
id: item.id || null,
name: item.name || null,
customer: item.customer || null,
product_type: this.product_type || null,
project_type: this.project_type || null,
resource_availability: this.resource_availability || null,
system_owner: item.system_owner || null,
architecture: item.architecture || null,
form_factor: item.form_factor || null,
platform: item.platform || null,
when_to_start: item.when_to_start || null,
project_complete_target: item.project_complete_target || null,
total_material: item.total_material || null,
total_nre: item.total_nre || null,
total_cert: item.total_cert || null,
total_ttl: item.total_ttl || null,
q1_material: item.q1_material || null,
q1_nre: item.q1_nre || null,
q1_cert: item.q1_cert || null,
q1_ttl: item.q1_ttl || null,
q2_material: item.q2_material || null,
q2_nre: item.q2_nre || null,
q2_cert: item.q2_cert || null,
q2_ttl: item.q2_ttl || null,
q3_material: item.q3_material || null,
q3_nre: item.q3_nre || null,
q3_cert: item.q3_cert || null,
q3_ttl: item.q3_ttl || null,
q4_material: item.q4_material || null,
q4_nre: item.q4_nre || null,
q4_cert: item.q4_cert || null,
q4_ttl: item.q4_ttl || null,
outlook_material: item.outlook_material || null,
outlook_nre: item.outlook_nre || null,
outlook_cert: item.outlook_cert || null,
outlook_ttl: item.outlook_ttl || null,
actual_material: item.actual_material || null,
actual_nre: item.actual_nre || null,
actual_cert: item.actual_cert || null,
actual_ttl: item.actual_ttl || null,
spending_material: item.spending_material || null,
spending_nre: item.spending_nre || null,
spending_cert: item.spending_cert || null,
spending_ttl: item.spending_ttl || null,
comment: item.comment || null,
}
});
// merges是進行所有表頭的單元格合併,數組裡面的每個元素的填寫規則是:按從左往右的順序依次填寫,字母是指excel裡面的第幾列,數字表示第幾行,如果是上下的單元格合併,就是按從上往下填寫,比如'A1:A2'代表的是A列的第一個單元格和A列的第二個單元格合併,如果是左右的單元格合併,也是從左往右填寫,同理,
const merges = [
'A1:A2', 'B1:B2', 'C1:C2', 'D1:D2', 'E1:E2', 'F1:F2', 'G1:G2', 'H1:H2', 'I1:I2', 'J1:J2', 'K1:K2', 'L1:L2', 'M1:P1',
'Q1:T1', 'U1:X1', 'Y1:AB1', 'AC1:AF1', 'AG1:AJ1', 'AK1:AN1', 'AO1:AR1', 'AS1:AS2'
];
if(list) {
const data = this.formatJson(filterVal, list); // 生成json資料
export_json_to_excel({
multiHeader, // 這裡是第一行的表頭
header: tHeader, // 這裡是第二行的表頭
data,
filename: 'Cash plan', //這裡填的是匯出excel的名稱
merges,
});
} else {
alert("暫無無資料");
}
});
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => v[j]))
},
4. 匯出excel長這樣結束,撒花~~
一張圖截不下,只能截兩次了,大概就是這樣。。