Files
exceljs_practice/scripts/excelJS_helper.js
2020-10-09 12:28:47 +08:00

453 lines
13 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 需要 ExcelJS 套件
* 提供一些 function可以把呼叫 ExcelJS 的方法,把 html 的 table 轉成 excel
*/
// let namespace = window.exhelper;
(function (ns) {
'use strict';
// create a self-reference
var _self = ns;
/**
* 以 0 為起始的行、列座標
* @param {Number} col 行座標
* @param {Number} row 列座標
*/
_self.CellCoordinate = function (col, row) {
this.col = col || 0;
this.row = row || 0;
}
/**
* 每格的資料
* @param {String} addr 位址 (A1, A2...)
* @param {Any} val 值
* @param {Object} style 格式
* @param {Number} type 型別 (預設 3 string)
*/
_self.CellData = function(addr, val, style, type){
this.address = addr || "";
this.value = val || "";
this.style = style || {};
this.type = type || 3;
}
/**
* 合併欄位的資料
* @param {Number} top
* @param {Number} left
* @param {Number} bottom
* @param {Number} right
*/
_self.MergedRange = function(top, left, bottom, right){
this.top = top || 0;
this.left = left || 0;
this.bottom = bottom || 0;
this.right = right || 0;
}
/**
* 傳入 CellCoordinate { col: 0, row: 0 } 回傳 A1
* @param {CellCoordinate} coordinate
* @return {String} 格位標記
*/
_self.parseCelltoTag = function (coordinate) {
if (parseInt(coordinate.col) < 0) {
console.log("傳入參數有誤Coordinate 的 col 要大於 0");
return;
}
return _self.getAlphabetByInt(parseInt(coordinate.col)) + (parseInt(coordinate.row) + 1);
}
/**
* 傳入數字,回傳對應的字母 ( 0=>A, 1=>B...)
* @param {Number} idx 要對應的數字
* @param {Number} alphabetBase 要用幾個字母去對應,預設 26最大 26
*/
_self.getAlphabetByInt = function (idx, alphabetBase) {
if (!alphabetBase)
alphabetBase = 26;
let alphabets = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// 在 base 內,一個字母就可以處理
if (idx < alphabetBase) {
return alphabets.charAt(idx);
}
let result = "";
let quotient = idx;
while (quotient >= alphabetBase) {
let mod = quotient % alphabetBase;
quotient = Math.floor(idx / alphabetBase);
result = alphabets.charAt(mod) + result;
}
// 最後一碼
result = alphabets.charAt(quotient - 1) + result;
return result;
}
/**
* 傳入字母,回傳對應的數值 ( A=>0, B=>2...)
* @param {String} a 要對應的字串
* @param {Number} alphabetBase 要用幾個字母去對應,預設 26最大 26
*/
_self.getIdxByAlphbet = function (a, alphabetBase) {
if (!alphabetBase)
alphabetBase = 26;
let alphabets = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// 全部轉大寫
a = a.toUpperCase();
let result = 0;
for (let i = 0; i < a.length; i++) {
// 如果是第二碼以上,就要加 1 再去乘
let modifier = (i == 0) ? 0 : 1;
result += (alphabets.indexOf(a.charAt(a.length - (i + 1))) + modifier) * Math.pow(alphabetBase, i);
}
return result;
}
/**
* 傳入格位代碼,回傳以 0 起始的座標物件
* @param {String} tag 格位代碼 (A1, A2...)
* @return {CellCoordinate} { col: 0, row: 0}
*/
_self.parseCelltoCoordinate = function (tag) {
let re = /([A-Za-z]+)(\d+)/;
let match = tag.match(re);
return new _self.CellCoordinate(_self.getIdxByAlphbet(match[1]), parseInt(match[2]) - 1);
}
/**
* 儲存 Blob 資料至下載檔案
* @param {String} fileName 檔案名稱(含副檔名)
* @param {object} byte 檔案內容 byte 陣列
*/
_self.saveBlob = function (fileName, byte) {
var blob = new Blob([byte], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
});
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
// for IE
window.navigator.msSaveOrOpenBlob(blob, fileName);
} else {
// for other browsers
var url = window.URL.createObjectURL(blob, {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
});
/*
var a = $("<a style='display: none;'/>");
a.attr("href", url);
a.attr("download", fileName);
$("body").append(a);
a[0].click();
window.URL.revokeObjectURL(url);
a.remove();
*/
var a = document.createElement("a");
a.style.display = "none";
a.setAttribute("href", url);
a.setAttribute("download", fileName);
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
a.remove();
}
}
/**
* 將 ExcelJS.Workbook 物件存成 excel 檔案,並開始下載
* @param {String} filename 檔案名稱含副檔名
* @param {object} Workbook 物件
*/
_self.saveXlsx = function (filename, wb) {
wb.xlsx.writeBuffer().then(
function (result) {
_self.saveBlob(filename, result);
},
function (err) {
console.log("error: ", err);
}
);
}
/**
* 傳入 table 物件,回傳 ExcelJS.Workbook 物件
* @param {object} elt table 物件
* @param {object} opt 屬性設定 => { sheet: "sheet 名稱 預設 Sheet1" }
* @return {object} ExcelJS.Workbook 物件
*/
_self.tableToBook = function (elt, opt) {
if (!opt)
opt = {};
// 沒有這個方法 => 可能是 jquery 物件,要轉成原始的 html 物件
if (!elt.getElementsByTagName)
elt = elt.get(0);
var wb = new ExcelJS.Workbook();
wb.creator = "gamapay";
wb.lastModifiedBy = "gamapay";
wb.created = new Date();
wb.modified = new Date();
let sheetName = opt.sheet || "Sheet1";
// Set workbook dates to 1904 date system
wb.properties.date1904 = true;
var ws = wb.addWorksheet(sheetName);
var rows = elt.getElementsByTagName("tr");
var mergedCells = new Array(rows.length);
for (let i = 0; i < rows.length; i++) {
// 取得格位資料
var cols = rows[i].querySelectorAll("th,td");
// 計算要右移幾格 (因為合併)
let shiftCell = 0;
for (let j = 0; j < cols.length; j++) {
let colElem = cols[j];
let colspan = colElem.getAttribute("colspan");
let rowspan = colElem.getAttribute("rowspan");
let colValue = colElem.innerHTML;
// 如果有值,就進行移除所有的 html tag 運算
if (colValue) {
colValue = colValue.replace(/<\/?[^>]+(>|$)/g, "");
}
// 初始化本列的合併格位陣列 (如果是合併列的話,可能已經有初始化了)
if (!mergedCells[i])
mergedCells[i] = [];
// 檢查目前的格位有沒有在合併欄位中,有的話就右移一格
while (mergedCells[i].indexOf(j + shiftCell) >= 0) {
shiftCell++;
}
// 目前的格位 (位移後)
let currCell = new _self.CellCoordinate(j + shiftCell, i);
// 轉成 tag
let currTag = _self.parseCelltoTag(currCell);
// 設定本格的值
ws.getCell(currTag).value = colValue;
// 是否有合併欄位
let hasMerge = false;
// 要合併欄位
let toCell = new _self.CellCoordinate(j + shiftCell, i);
if (colspan && colspan != 1) {
hasMerge = true;
toCell.col += parseInt(colspan) - 1;
}
if (rowspan && rowspan != 1) {
hasMerge = true;
toCell.row += parseInt(rowspan) - 1;
}
if (hasMerge) {
// 把合併的欄位加到陣列中
for (let idr = currCell.row; idr <= toCell.row; idr++) {
// 如果是列之間的合併格位,會遇到還沒有初始化,所以在這要初始化
if (!mergedCells[idr])
mergedCells[idr] = [];
for (let idx = currCell.col; idx <= toCell.col; idx++) {
// 將合併的欄位加到陣列中
mergedCells[idr].push(idx);
}
}
// 將要合併的欄位轉成 tag
let toTag = _self.parseCelltoTag(toCell);
// 進行合併
ws.mergeCells(currTag + ":" + toTag);
}
}
}
return wb;
}
/**
* 在指定位置插入指定行數的空行
* @param {Object} sheet Worksheet object
* @param {Number} pos 要插入的位置
* @param {Number} cnt 要插入幾行
*/
_self.insertRows = function (sheet, pos, cnt){
// 建立要合併的欄位
var merged = [];
for(let cell in sheet._merges){
let model = sheet._merges[cell].model;
let range = new _self.MergedRange(model.top, model.left, model.bottom, model.right);
merged.push(range);
// 清除 merge 資訊
sheet.unMergeCells(cell);
}
// 紀錄所有格位資料
let cells = [];
sheet.eachRow(function(row, rowNumber) {
row.eachCell(function(cell, colNumber){
let model = cell._value.model;
let cellData = new _self.CellData(model.address, model.value, model. style, model.type);
cells.push(cellData);
});
});
// 清空所有資料
sheet._rows = [];
sheet._columns = [];
for(let i = 0; i < cells.length; i++){
let cell = cells[i];
let cellCoordinate = _self.parseCelltoCoordinate(cell.address);
// col 原則上不會變
let newCol = cellCoordinate.col + 1;
let newRow = cellCoordinate.row + 1;
// row 依插入幾行而變
if(newRow >= pos){
newRow = newRow + cnt;
}
// 將原本的值寫入
sheet.getRow(newRow).getCell(newCol).value = cell.value;
}
// 重新 merge
for(let i = 0; i < merged.length; i++){
let range = merged[i];
// 如果插入的行在merge行的中間那就不進行merge
if(range.top > pos && range.bottom <= pos)
continue;
// 原本合併的行在插入行之下,下移指定行數
// ,在插入行之上,維持不變
if(range.top >= pos){
range.top += cnt;
range.bottom += cnt;
}
// 進行合併
sheet.mergeCells(range.top, range.left, range.bottom, range.right);
}
}
/**
* 為 worksheet 物件增加表頭
* @param {object} sheet ExcelJS.Worksheet 物件
* @param {String} title 表頭
*/
_self.addTitle = function (sheet, title) {
_self.insertRows(sheet, 1, 1);
// 插入表頭
sheet.mergeCells(1, 1, 1, sheet.columnCount);
let titleCell = sheet.getRow(1).getCell(1);
titleCell.value = title;
titleCell.alignment = { vertical: 'middle', horizontal: 'center' };
titleCell.font = { bold: true };
}
/**
* 合併兩個 sheet
* @param {object} fromSheet
* @param {object} toSheet
* @param {Number} rowSeperate
*/
_self.mergeSheets = function(fromSheet, toSheet, rowSeperate){
// 兩個 table 之間的空行數
let seperate = rowSeperate + 1;
// fromSheet 新的開始行
let startRow = toSheet.rowCount + 1 + rowSeperate;
for (let r = 1; r <= fromSheet.rowCount; r++) {
for (let c = 1; c <= fromSheet.columnCount; c++) {
let newRow = r + startRow;
toSheet.getRow(newRow).getCell(c).value = fromSheet.getRow(r).getCell(c).value;
}
}
// 建立要合併的欄位
var merged = [];
for (let cell in fromSheet._merges) {
let model = fromSheet._merges[cell].model;
let range = new _self.MergedRange(model.top, model.left, model.bottom, model.right);
merged.push(range);
}
for (let i = 0; i < merged.length; i++) {
let range = merged[i];
toSheet.mergeCells(range.top + startRow, range.left, range.bottom + startRow, range.right);
}
}
// reasign everything just incase
_self = ns;
})(window.exhelper = window.exhelper || {});