From c9efca77873e234554e8a4fc7bca72680dce98c7 Mon Sep 17 00:00:00 2001 From: ibiz_zhf <1204297681@qq.com> Date: Mon, 8 Dec 2025 22:30:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A1=A5=E5=85=85=E5=AF=BC=E5=87=BAExc?= =?UTF-8?q?el=E5=B7=A5=E5=85=B7=E7=B1=BB=E5=AF=B9=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 + package.json | 4 +- src/ibiz-vue3.ts | 1 + src/util/xlsx-util/file-saver.d.ts | 1 + src/util/xlsx-util/xlsx-util.ts | 210 +++++++++++++++++++++++++++++ 5 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 src/util/xlsx-util/file-saver.d.ts create mode 100644 src/util/xlsx-util/xlsx-util.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 40bc92e8d36..b5d8f1288c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ ## [Unreleased] +### Added + +- 补充导出Excel工具类对象 + ### Change - 优化日历,标题栏,数据看板,分割容器样式,规范其样式变量, diff --git a/package.json b/package.json index 3a79c57ae22..800782f5d9a 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,9 @@ "vue-router": "^4.2.5", "qr-code-styling": "^1.8.3", "vuedraggable": "^4.1.0", - "vue-qrcode-reader": "5.5.11" + "vue-qrcode-reader": "5.5.11", + "file-saver": "^2.0.5", + "xlsx": "^0.18.5" }, "devDependencies": { "@commitlint/cli": "^18.4.1", diff --git a/src/ibiz-vue3.ts b/src/ibiz-vue3.ts index f5d7aa81a88..145b2022f06 100644 --- a/src/ibiz-vue3.ts +++ b/src/ibiz-vue3.ts @@ -47,6 +47,7 @@ import './style/index.scss'; export default { install: (v: App): void => { ibiz.i18n = iBizI18n; + ibiz.util.getExcelUtil = () => import('./util/xlsx-util/xlsx-util'); // vue 浏览器搭载平台 const browserPlatformProvider = new VueBrowserPlatformProvider(); const dingTalkPlatformProvider = new DingTalkPlatformProvider(); diff --git a/src/util/xlsx-util/file-saver.d.ts b/src/util/xlsx-util/file-saver.d.ts new file mode 100644 index 00000000000..da458fd4262 --- /dev/null +++ b/src/util/xlsx-util/file-saver.d.ts @@ -0,0 +1 @@ +declare module 'file-saver'; diff --git a/src/util/xlsx-util/xlsx-util.ts b/src/util/xlsx-util/xlsx-util.ts new file mode 100644 index 00000000000..80437f02811 --- /dev/null +++ b/src/util/xlsx-util/xlsx-util.ts @@ -0,0 +1,210 @@ +import { saveAs } from 'file-saver'; +import * as XLSX from 'xlsx'; +import { BookType } from 'xlsx'; + +function dateNum( + v: number | boolean | Date | string, + date1904: boolean = false, +) { + if (date1904) (v as number) += 1462; + const epoch = Date.parse(v as string); + return ( + (epoch - (new Date(Date.UTC(1899, 11, 30)) as unknown as number)) / + (24 * 60 * 60 * 1000) + ); +} + +function sheetFromArrayOfArrays(data: IData[]) { + const ws: IData = {}; + 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; + const cell: { + v: number | boolean | Date | string; + t?: string; + z?: string; + } = { + v: data[R][C], + }; + // eslint-disable-next-line no-continue + 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 = dateNum(cell.v); + } else cell.t = 's'; + + ws[cellRef] = cell; + } + } + if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range); + return ws; +} + +class Workbook { + public SheetNames: string[] = []; + + public Sheets: IData = {}; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function s2ab(s: any) { + const buf = new ArrayBuffer(s.length); + const view = new Uint8Array(buf); + // eslint-disable-next-line no-bitwise + for (let i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xff; + return buf; +} + +/** + * @description 导出excel文件 + * @export + * @param {{ + * multiHeader: []; + * header: string[]; + * data: string[][]; + * filename: string; + * merges: []; + * autoWidth: boolean; + * bookType: BookType; + * }} { + * multiHeader = [], + * header, + * data, + * filename, + * merges = [], + * autoWidth = true, + * bookType = 'xlsx', + * } + */ +export function exportJsonToExcel({ + multiHeader = [], + header, + data, + filename, + merges = [], + autoWidth = true, + bookType = 'xlsx', +}: { + multiHeader: []; + header: string[]; + data: string[][]; + filename: string; + merges: []; + autoWidth: boolean; + bookType: BookType; +}): void { + /* original data */ + filename = filename || 'excel-list'; + data = [...data]; + data.unshift(header); + for (let i = multiHeader.length - 1; i > -1; i--) { + data.unshift(multiHeader[i]); + } + const wsName = 'SheetJS'; + const wb = new Workbook(); + const ws = sheetFromArrayOfArrays(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, + }; + } + if (val.toString().charCodeAt(0) > 255) { + /* 再判断是否为中文 */ + return { + wch: val.toString().length * 2, + }; + } + return { + wch: val.toString().length, + }; + }), + ); + /* 以第一行为初始值 */ + 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; + } + + /* add worksheet to workbook */ + wb.SheetNames.push(wsName); + wb.Sheets[wsName] = ws; + + const wbOut = XLSX.write(wb, { + bookType, + bookSST: false, + type: 'binary', + }); + saveAs( + new Blob([s2ab(wbOut)], { + type: 'application/octet-stream', + }), + `${filename}.${bookType}`, + ); +} + +/** + * @description 读取excel文件 + * @export + * @param {File} file + * @param {number} sheetIndex + * @returns {*} {Promise} + */ +export async function readExcelFile( + file: File, + sheetIndex: number, +): Promise { + const readFile = (_file: File) => { + return new Promise(resolve => { + const reader = new FileReader(); + reader.readAsBinaryString(_file); + reader.onload = ev => { + resolve(ev.target?.result); + }; + }); + }; + let data = await readFile(file); + const workbook: XLSX.WorkBook = XLSX.read(data, { type: 'binary' }); + const worksheet: XLSX.WorkSheet = + workbook.Sheets[workbook.SheetNames[sheetIndex]]; + data = XLSX.utils.sheet_to_json(worksheet); + return data as IData[]; +} -- Gitee