UNPKG

wangbd-myui

Version:

MY前端基础框架核心组件库

439 lines (421 loc) 12.9 kB
/** * Excel 文件读写模块 * @module $ui/utils/excel */ import XLSX from 'xlsx'; function setAutoWidth(ws, data) { /* set worksheet max width per col */ const colWidth = data.map(row => row.map(val => { /* if null/undefined */ if (val === null) { return {wch: 10}; /* if chinese */ } else if (val.toString().charCodeAt(0) > 255) { return {wch: val.toString().length * 2}; } else { return {wch: val.toString().length}; } })) /* start in the first row */ const 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; } function jsonToArray(key, jsonData) { return jsonData.map(v => key.map(j => { return v[j] })); } // fix data,return string function fixData(data) { let o = '' let l = 0 const w = 10240 for (; l < data.byteLength / w; ++l) o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w, l * w + w))) o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w))) return o } // get head from excel file,return array function getHeaderRow(sheet) { const headers = [] const range = XLSX.utils.decode_range(sheet['!ref']) let C const R = range.s.r /* start in the first row */ for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */ const cell = sheet[XLSX.utils.encode_cell({c: C, r: R})] /* find the cell in the first row */ let hdr = 'UNKNOWN ' + C // <-- replace with your desired default if (cell && cell.t) hdr = XLSX.utils.format_cell(cell) headers.push(hdr) } return headers } /** * 导出文件 * @param {array} key 列标识数组 * @param {array} title 列文本数组 * @param {array} data 行数据 * @param {string} fileName 导出文件名,不含扩展名 * @param {boolean} autoWidth 是否设置自动列宽度 * @return {Promise<any>} */ export const write = ({key, data, title, fileName, autoWidth}) => { return new Promise(resolve => { const wb = XLSX.utils.book_new(); const arr = jsonToArray(key, data); arr.unshift(title); const ws = XLSX.utils.aoa_to_sheet(arr); if (autoWidth) { setAutoWidth(ws, arr); } XLSX.utils.book_append_sheet(wb, ws, fileName); XLSX.writeFile(wb, fileName + '.xlsx'); resolve() }) } // 获取table的行数据 function addTableRow(obj, arr) { Array.prototype.forEach.call(obj.rows, r => { const row = []; Array.prototype.forEach.call(r.cells, c => { row.push(c.innerText + '\x0a') }) arr.push(row) }) return arr; } // 判断是el-table的表格还是 table标签,并获取行数据 function getTableData(table) { if(!table) { throw new Error('表格类型错误') } let arr = []; const headerEl = table.querySelector('.el-table__header'); const bodyEl = table.querySelector('.el-table__body'); if(headerEl && bodyEl) { arr = addTableRow(headerEl, arr) arr = addTableRow(bodyEl, arr) } else if(table instanceof HTMLElement && table.tagName === 'TABLE') { arr = addTableRow(table, arr) } else { throw new Error('表格类型错误') } return arr; } /** * * @param {Array|Object} tables 表格数组或者对象, 必须是table标签或者el-table的 $el * @param {Array|String} sheetNames 表名数组或字符串 * @param {String} fileName 文件名 * @param {autoWidth} autoWidth = true */ export const writeFromTable = ({tables, sheetNames, fileName, autoWidth = true}) => { return new Promise((resolve) => { const wb = XLSX.utils.book_new(); // 可以传数组(生成多表excel文件)或对象(单表excel文件) if(tables instanceof Array && sheetNames instanceof Array) { tables.forEach((table, idx) => { const arr = getTableData(table); const ws = XLSX.utils.aoa_to_sheet(arr) if(autoWidth) { setAutoWidth(ws, arr) } XLSX.utils.book_append_sheet(wb, ws, sheetNames[idx]) }) } else { const arr = getTableData(tables); const ws = XLSX.utils.aoa_to_sheet(arr) if(autoWidth) { setAutoWidth(ws, arr) } XLSX.utils.book_append_sheet(wb, ws, sheetNames) } XLSX.writeFile(wb, fileName + '.xlsx'); resolve() }) } /** * 文件转换成JSON数据 * @param {Object|String} data 文件流 * @param {String} type 数据类型 array 或 base64 * @return {{header: Array, results: any[] | * | any[][]}} */ export const read = (data, type) => { /* if type == 'base64' must fix data first */ let workbook if (type === 'base64') { const fixedData = fixData(data) workbook = XLSX.read(btoa(fixedData), {type: 'base64'}) } else { workbook = XLSX.read(data, {type: type}); } const firstSheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[firstSheetName]; const header = getHeaderRow(worksheet); const results = XLSX.utils.sheet_to_json(worksheet); return {header, results}; } /** * 读取文件 * @param {Object} file 文件框响应的文件对象 * @param {object} options 参数选项,可侦听回调函数,如:onloadstart / onprogress / onerror / onload * @return {Promise<any>} */ export function reader(file, options = {}) { const reader = new FileReader() reader.readAsArrayBuffer(file) return new Promise((resolve, reject) => { const opt = { ...options, onerror(e) { options.onerror && options.onerror(e) reject(e) }, onload(e) { const reuslt = e.target.result options.onload && options.onload(reuslt) resolve(reuslt) } } Object.keys(opt).forEach(key => { reader[key] = opt[key] }) }) } // ============== 以下为复杂表头输出函数集合 ============================= const getHeader = function(headers, excelHeader, deep, perOffset) { let offset = 0; let cur = excelHeader[deep]; if (!cur) { cur = excelHeader[deep] = []; } pushUndefined(cur, perOffset - cur.length); for (let i = 0; i < headers.length; i++) { const head = headers[i]; cur.push(head.name); // head.hasOwnProperty('child') if (head.child && Array.isArray(head.child) && head.child.length > 0) { const childOffset = getHeader( head.child, excelHeader, deep + 1, cur.length - 1 ); pushNull(cur, childOffset - 1); offset += childOffset; } else { offset++; } } return offset; }; const pushUndefined = function(arr, count) { for (let i = 0; i < count; i++) { arr.push(undefined); } }; const pushNull = function(arr, count) { for (let i = 0; i < count; i++) { arr.push(null); } }; const fillNull = function(arr) { const max = Math.max(...arr.map(a => a.length)); arr.filter(e => e.length < max).forEach(e => pushNull(e, max - e.length)); }; const extractData = function(selectionData, revealList) { const headerList = []; flat(revealList, headerList); // 结果集 const result = []; selectionData.forEach(row => { const rowData = []; headerList.forEach(prop => { let value = null; if (typeof prop === 'function') { value = prop(row); } else { value = row[prop]; } value = value === null || value === undefined ? '' : value; rowData.push(value); }); result.push(rowData); }); return result; }; const flat = function(revealList, result) { revealList.forEach(e => { if (e.child) { flat(e.child, result); } else if (e.exeFun) { result.push(e.exeFun); } else if (e.prop) { result.push(e.prop); } }); }; const doMerges = function(arr) { // 要么横向合并 要么纵向合并 const deep = arr.length; const merges = []; for (let y = 0; y < deep; y++) { // 先处理横向合并 const row = arr[y]; let colSpan = 0; for (let x = 0; x < row.length; x++) { if (row[x] === null) { colSpan++; if (x + 1 === row.length && (colSpan > 0 && x > colSpan)) { merges.push({ s: { r: y, c: x - colSpan }, e: { r: y, c: x } }); } } else if (colSpan > 0 && x > colSpan) { merges.push({ s: { r: y, c: x - colSpan - 1 }, e: { r: y, c: x - 1 } }); colSpan = 0; } else { colSpan = 0; } } } // 再处理纵向合并 const colLength = arr[0].length; for (let x = 0; x < colLength; x++) { let rowSpan = 0; for (let y = 0; y < deep; y++) { if (arr[y][x] != null) { rowSpan = 0; } else { rowSpan++; } } if (rowSpan > 0) { merges.push({ s: { r: deep - rowSpan - 1, c: x }, e: { r: deep - 1, c: x } }); } } return merges; }; const aoaToSheet = function(data) { const ws = {}; const range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 } }; for (let R = 0; R !== data.length; ++R) { for (let 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; /// 这里生成cell的时候,使用上面定义的默认样式 const cell = { v: data[R][C], s: { font: { name: '宋体', sz: 11, color: { auto: 1 } }, border: { color: { auto: 1 } }, alignment: { /// 自动换行 wrapText: 1, // 居中 horizontal: 'center', vertical: 'center', indent: 0 } } }; if (cell.v == null) continue; const cellRef = 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 = this.dateNum(cell.v); // } else cell.t = 's'; ws[cellRef] = cell; } } if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range); return ws; }; const s2ab = function(s) { const buf = new ArrayBuffer(s.length); const view = new Uint8Array(buf); for (let i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xff; return buf; }; const openDownloadXLSXDialog = function(url, saveName) { if (typeof url === 'object' && url instanceof Blob) { url = URL.createObjectURL(url); // 创建blob地址 } const aLink = document.createElement('a'); aLink.href = url; aLink.download = saveName || ''; // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效 let event; if (window.MouseEvent) event = new MouseEvent('click'); else { event = document.createEvent('MouseEvents'); event.initMouseEvent( 'click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null ); } aLink.dispatchEvent(event); }; /** * 读取文件 * @param {String} docName 输出文件名称 * @param {Array} rowData 行数据 * @param {Array} colData 表头列数据 {name: '表头名', prop: '字段名', child: []} * cloData = [ { name: '姓名', prop: 'name' }, { name: '专业技能', child: [ { name: '前端', child: [ { name: 'JavaScript', prop: 'js' } ] } ] } ] */ const complexTableExport = function(docName, rowData, colData) { const sheetName = docName; // '多级表头excel' const excelHeader = []; // [[], []]; // , [] getHeader(colData, excelHeader, 0, 0); fillNull(excelHeader); const merges = doMerges(excelHeader); const dataList = extractData(rowData, colData); excelHeader.push(...dataList); const ws = aoaToSheet(excelHeader); ws['!merges'] = merges; const workbook = { SheetNames: [sheetName], Sheets: {} }; workbook.Sheets[sheetName] = ws; // excel样式 const wopts = { bookType: 'xlsx', bookSST: true, type: 'binary', cellStyles: true }; const wbout = XLSX.write(workbook, wopts); const blob = new Blob([s2ab(wbout)], { type: 'application/octet-stream' }); openDownloadXLSXDialog(blob, sheetName + '.xlsx'); }; // =========================================== export default { write, writeFromTable, read, reader, complexTableExport }