diff --git a/README.md b/README.md index 39282d4..187d431 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,12 @@ [![npm downloads](https://img.shields.io/npm/dm/vxe-table-plugin-export-xlsx.svg?style=flat-square)](http://npm-stat.com/charts.html?package=vxe-table-plugin-export-xlsx) [![npm license](https://img.shields.io/github/license/mashape/apistatus.svg)](LICENSE) -基于 [vxe-table](https://www.npmjs.com/package/vxe-table) 的表格插件,支持导出 xlsx 格式 +基于 [vxe-table](https://www.npmjs.com/package/vxe-table) 的表格插件,支持导出 xlsx 格式,依赖 [exceljs](https://github.com/exceljs/exceljs) 库 ## Installing ```shell -npm install xe-utils vxe-table vxe-table-plugin-export-xlsx xlsx +npm install xe-utils vxe-table vxe-table-plugin-export-xlsx exceljs ``` ```javascript diff --git a/gulpfile.js b/gulpfile.js index 2ca2830..f69061c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -12,7 +12,7 @@ const tsconfig = require('./tsconfig.json') const exportModuleName = 'VXETablePluginExportXLSX' gulp.task('build_commonjs', function () { - return gulp.src(['index.d.ts', 'depend.ts', 'index.ts']) + return gulp.src(['depend.ts', 'index.ts']) .pipe(sourcemaps.init()) .pipe(ts(tsconfig.compilerOptions)) .pipe(babel({ @@ -27,7 +27,7 @@ gulp.task('build_commonjs', function () { }) gulp.task('build_umd', function () { - return gulp.src(['index.d.ts', 'depend.ts', 'index.ts']) + return gulp.src(['depend.ts', 'index.ts']) .pipe(ts(tsconfig.compilerOptions)) .pipe(replace(`import XEUtils from 'xe-utils/ctor';`, `import XEUtils from 'xe-utils';`)) .pipe(babel({ diff --git a/index.d.ts b/index.d.ts deleted file mode 100644 index bab8633..0000000 --- a/index.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -declare module 'xlsx' { - interface utils { - /* eslint-disable camelcase */ - book_new(): any; - json_to_sheet(list: any[], options: any): any; - sheet_to_csv(Sheet: any): any; - sheet_to_json(Sheet: any): any[]; - book_append_sheet(book: any, sheet: any, sheetName?: string): any; - } - interface XLSXMethods { - utils: utils; - read(result: any, options: any): any; - write(book: any, options: any): any; - } - const XLSX: XLSXMethods - export default XLSX -} diff --git a/index.ts b/index.ts index 30c5415..aea6d58 100644 --- a/index.ts +++ b/index.ts @@ -6,13 +6,19 @@ import { InterceptorExportParams, InterceptorImportParams, ColumnConfig, - TableExportConfig + TableExportConfig, + ColumnAlign } from 'vxe-table/lib/vxe-table' -import XLSX from 'xlsx' +import * as ExcelJS from 'exceljs' /* eslint-enable no-unused-vars */ +const defaultHeaderBackgroundColor = 'f8f8f9' +const defaultCellFontColor = '606266' +const defaultCellBorderStyle = 'thin' +const defaultCellBorderColor = 'e8eaec' + function getFooterCellValue ($table: Table, opts: TableExportConfig, rows: any[], column: ColumnConfig) { - const cellValue = rows[$table.$getColumnIndex(column)] + const cellValue = rows[$table.getVMColumnIndex(column)] return cellValue } @@ -66,25 +72,72 @@ function getValidColumn (column: ColumnConfig): ColumnConfig { return column } +function setExcelRowHeight (excelRow: ExcelJS.Row, height: number) { + if (height) { + excelRow.height = XEUtils.floor(height * 0.8, 1) + } +} + +function setExcelCellStyle (excelCell: ExcelJS.Cell, align?: ColumnAlign) { + excelCell.protection = { + locked: false + } + excelCell.alignment = { + vertical: 'middle', + horizontal: align || 'left' + } +} + +function getDefaultBorderStyle () { + return { + top: { + style: defaultCellBorderStyle, + color: { + argb: defaultCellBorderColor + } + }, + left: { + style: defaultCellBorderStyle, + color: { + argb: defaultCellBorderColor + } + }, + bottom: { + style: defaultCellBorderStyle, + color: { + argb: defaultCellBorderColor + } + }, + right: { + style: defaultCellBorderStyle, + color: { + argb: defaultCellBorderColor + } + } + } +} + function exportXLSX (params: InterceptorExportParams) { const msgKey = 'xlsx' const { $table, options, columns, colgroups, datas } = params - const { $vxe } = $table + const { $vxe, rowHeight, headerAlign: allHeaderAlign, align: allAlign, footerAlign: allFooterAlign } = $table const { modal, t } = $vxe - const { message, sheetName, isHeader, isFooter, isMerge, isColgroup, original } = options + const { message, sheetName, isHeader, isFooter, isMerge, isColgroup, original, useStyle, sheetMethod } = options const showMsg = message !== false const mergeCells = $table.getMergeCells() const colList: any[] = [] const footList: any[] = [] - const sheetCols: { wpx: number }[] = [] + const sheetCols: any[] = [] const sheetMerges: { s: { r: number, c: number }, e: { r: number, c: number } }[] = [] // 处理表头 if (isHeader) { const colHead: any = {} columns.forEach((column) => { - colHead[column.id] = original ? column.property : column.getTitle() + const { id, property, renderWidth } = column + colHead[id] = original ? property : column.getTitle() sheetCols.push({ - wpx: XEUtils.toInteger(column.renderWidth * 0.75) + key: id, + width: XEUtils.ceil(renderWidth / 8, 1) }) }) if (isColgroup && !original && colgroups) { @@ -150,21 +203,100 @@ function exportXLSX (params: InterceptorExportParams) { }) } const exportMethod = () => { - const book = XLSX.utils.book_new() - const list = (isHeader ? colList : []).concat(rowList).concat(footList) - const sheet = XLSX.utils.json_to_sheet(list.length ? list : [{}], { skipHeader: true }) - sheet['!cols'] = sheetCols - sheet['!merges'] = sheetMerges - // 转换数据 - XLSX.utils.book_append_sheet(book, sheet, sheetName) - const wbout = XLSX.write(book, { bookType: 'xlsx', bookSST: false, type: 'binary' }) - const blob = new Blob([toBuffer(wbout)], { type: 'application/octet-stream' }) - // 导出 xlsx - downloadFile(params, blob, options) - if (showMsg) { - modal.close(msgKey) - modal.message({ message: t('vxe.table.expSuccess'), status: 'success' }) + const workbook = new ExcelJS.Workbook() + const sheet = workbook.addWorksheet(sheetName) + workbook.creator = 'vxe-table' + sheet.columns = sheetCols + if (isHeader) { + sheet.addRows(colList).forEach(excelRow => { + if (useStyle) { + setExcelRowHeight(excelRow, rowHeight) + } + excelRow.eachCell(excelCell => { + const excelCol = sheet.getColumn(excelCell.col) + const column: any = $table.getColumnById(excelCol.key as string) + const { headerAlign, align } = column + setExcelCellStyle(excelCell, headerAlign || align || allHeaderAlign || allAlign) + if (useStyle) { + Object.assign(excelCell, { + font: { + bold: true, + color: { + argb: defaultCellFontColor + } + }, + fill: { + type: 'pattern', + pattern:'solid', + fgColor: { + argb: defaultHeaderBackgroundColor + } + }, + border: getDefaultBorderStyle() + }) + } + }) + }) } + sheet.addRows(rowList).forEach(excelRow => { + if (useStyle) { + setExcelRowHeight(excelRow, rowHeight) + } + excelRow.eachCell(excelCell => { + const excelCol = sheet.getColumn(excelCell.col) + const column: any = $table.getColumnById(excelCol.key as string) + const { align } = column + setExcelCellStyle(excelCell, align || allAlign) + if (useStyle) { + Object.assign(excelCell, { + font: { + color: { + argb: defaultCellFontColor + } + }, + border: getDefaultBorderStyle() + }) + } + }) + }) + if (isFooter) { + sheet.addRows(footList).forEach(excelRow => { + if (useStyle) { + setExcelRowHeight(excelRow, rowHeight) + } + excelRow.eachCell(excelCell => { + const excelCol = sheet.getColumn(excelCell.col) + const column: any = $table.getColumnById(excelCol.key as string) + const { footerAlign, align } = column + setExcelCellStyle(excelCell, footerAlign || align || allFooterAlign || allAlign) + if (useStyle) { + Object.assign(excelCell, { + font: { + color: { + argb: defaultCellFontColor + } + }, + border: getDefaultBorderStyle() + }) + } + }) + }) + } + if (useStyle && sheetMethod) { + sheetMethod({ options, workbook, worksheet: sheet }) + } + sheetMerges.forEach(({ s, e }) => { + sheet.mergeCells(s.r + 1, s.c + 1, e.r + 1, e.c + 1) + }) + workbook.xlsx.writeBuffer().then(buffer => { + var blob = new Blob([buffer], { type: 'application/octet-stream' }) + // 导出 xlsx + downloadFile(params, blob, options) + if (showMsg) { + modal.close(msgKey) + modal.message({ message: t('vxe.table.expSuccess'), status: 'success' }) + } + }) } if (showMsg) { modal.message({ id: msgKey, message: t('vxe.table.expLoading'), status: 'loading', duration: -1 }) @@ -209,14 +341,29 @@ declare module 'vxe-table/lib/vxe-table' { _importReject?: Function | null; } } +function importError (params: InterceptorImportParams) { + const { $table, options } = params + const { $vxe, _importReject } = $table + const showMsg = options.message !== false + const { modal, t } = $vxe + if (showMsg) { + modal.message({ message: t('vxe.error.impFields'), status: 'error' }) + } + if (_importReject) { + _importReject({ status: false }) + } +} function importXLSX (params: InterceptorImportParams) { const { $table, columns, options, file } = params - const { $vxe, _importResolve, _importReject } = $table + const { $vxe, _importResolve } = $table const { modal, t } = $vxe const showMsg = options.message !== false const fileReader = new FileReader() - fileReader.onload = (e: any) => { + fileReader.onerror = () => { + importError(params) + } + fileReader.onload = (evnt) => { const tableFields: string[] = [] columns.forEach((column) => { const field = column.property @@ -224,46 +371,57 @@ function importXLSX (params: InterceptorImportParams) { tableFields.push(field) } }) - const workbook = XLSX.read(e.target.result, { type: 'binary' }) - const rest = XLSX.utils.sheet_to_json(XEUtils.first(workbook.Sheets)) as any[] - const fields = rest ? XEUtils.keys(rest[0]) : [] - const list = rest - const status = checkImportData(tableFields, fields) - if (status) { - const records: any[] = list.map(item => { - const record: any = {} - tableFields.forEach(field => { - record[field] = XEUtils.isUndefined(item[field]) ? null : item[field] - }) - return record - }) - $table.createData(records) - .then((data: any[]) => { - let loadRest: Promise - if (options.mode === 'insert') { - loadRest = $table.insertAt(data, -1) - } else { - loadRest = $table.reloadData(data) - } - return loadRest.then(() => { - if (_importResolve) { - _importResolve({ status: true }) + const workbook = new ExcelJS.Workbook() + const readerTarget = evnt.target + if (readerTarget) { + workbook.xlsx.load(readerTarget.result as ArrayBuffer).then(wb => { + const firstSheet = wb.worksheets[0] + if (firstSheet) { + const sheetValues = firstSheet.getSheetValues() as string[][] + const fieldIndex = XEUtils.findIndexOf(sheetValues, (list) => list && list.length > 0) + const fields = sheetValues[fieldIndex] as string[] + const status = checkImportData(tableFields, fields) + if (status) { + const records = sheetValues.slice(fieldIndex).map(list => { + const item : any= {} + list.forEach((cellValue, cIndex) => { + item[fields[cIndex]] = cellValue + }) + const record: any = {} + tableFields.forEach(field => { + record[field] = XEUtils.isUndefined(item[field]) ? null : item[field] + }) + return record + }) + $table.createData(records) + .then((data: any[]) => { + let loadRest: Promise + if (options.mode === 'insert') { + loadRest = $table.insertAt(data, -1) + } else { + loadRest = $table.reloadData(data) + } + return loadRest.then(() => { + if (_importResolve) { + _importResolve({ status: true }) + } + }) + }) + if (showMsg) { + modal.message({ message: t('vxe.table.impSuccess', [records.length]), status: 'success' }) } - }) - }) - if (showMsg) { - modal.message({ message: t('vxe.table.impSuccess', [records.length]), status: 'success' }) - } + } else { + importError(params) + } + } else { + importError(params) + } + }) } else { - if (showMsg) { - modal.message({ message: t('vxe.error.impFields'), status: 'error' }) - } - if (_importReject) { - _importReject({ status: false }) - } + importError(params) } } - fileReader.readAsBinaryString(file) + fileReader.readAsArrayBuffer(file) } function handleImportEvent (params: InterceptorImportParams) { diff --git a/package.json b/package.json index d3d75eb..6439780 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vxe-table-plugin-export-xlsx", - "version": "2.0.9", + "version": "2.1.0-beta.1", "description": "基于 vxe-table 的表格插件,支持导出 xlsx 格式", "scripts": { "lib": "gulp build" @@ -33,6 +33,7 @@ "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.2", "eslint-plugin-typescript": "^0.14.0", + "exceljs": "^4.2.0", "gulp": "^4.0.2", "gulp-autoprefixer": "^7.0.1", "gulp-babel": "^8.0.0", @@ -49,11 +50,10 @@ "typescript": "^4.0.5", "vue": "^2.6.12", "vxe-table": "^2.10.0", - "xe-utils": "^3.0.4", - "xlsx": "^0.16.7" + "xe-utils": "^3.0.4" }, "peerDependencies": { - "vxe-table": ">= 2.9" + "vxe-table": ">= 2.10" }, "repository": { "type": "git",