/** * 需要 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.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 || {});