UNPKG

@develop-plugins/excel-preview

Version:

一个基于 Vue3 的 Excel 预览和编辑组件,支持导入导出 Excel 文件,以及在线编辑表格数据。

707 lines (706 loc) 22.6 kB
import { openBlock as x, createElementBlock as k, normalizeClass as U, normalizeStyle as j, createElementVNode as A, toDisplayString as se, createCommentVNode as B, renderSlot as ve, inject as ge, ref as C, onMounted as ae, onUnmounted as pe, watch as re, nextTick as W, Fragment as be, renderList as we, withModifiers as Ce, withDirectives as xe, withKeys as ke, vModelText as Le, useCssVars as Te, provide as Re, shallowRef as Ee, effectScope as Ae, computed as Be, onBeforeUnmount as Me, createBlock as te, createVNode as ne, unref as Se, mergeProps as Ne, toRaw as ze } from "vue"; import Oe from "exceljs"; import { HotTable as $e } from "@handsontable/vue3"; import { registerAllModules as De } from "handsontable/registry"; import { registerLanguageDictionary as He, zhCN as Pe } from "handsontable/i18n"; import Fe from "@develop-plugins/x-message"; function Ie(t) { var u, f, r, c, m, v, g, d; const e = t.style, o = (e == null ? void 0 : e.font) || {}, l = (e == null ? void 0 : e.fill) || {}, n = (e == null ? void 0 : e.border) || {}, a = (e == null ? void 0 : e.alignment) || {}; return { font: { name: o.name || "Arial", size: o.size || 11, bold: o.bold || !1, italic: o.italic || !1, underline: o.underline || !1, color: { rgb: ((f = (u = o == null ? void 0 : o.color) == null ? void 0 : u.argb) == null ? void 0 : f.substring(2)) || "000000" } }, fgColor: { rgb: ((c = (r = l == null ? void 0 : l.fgColor) == null ? void 0 : r.argb) == null ? void 0 : c.substring(2)) || "FFFFFF" }, border: { top: { style: ((m = n.top) == null ? void 0 : m.style) || "thin" }, left: { style: ((v = n.left) == null ? void 0 : v.style) || "thin" }, bottom: { style: ((g = n.bottom) == null ? void 0 : g.style) || "thin" }, right: { style: ((d = n.right) == null ? void 0 : d.style) || "thin" } }, alignment: { vertical: (a == null ? void 0 : a.vertical) || "middle", horizontal: (a == null ? void 0 : a.horizontal) || "left", wrapText: (a == null ? void 0 : a.wrapText) || !0 } }; } function Ue(t) { const e = t._merges || {}, o = []; for (const [l, n] of Object.entries(e)) { const { top: a, left: u, bottom: f, right: r } = n; o.push({ row: a - 1, col: u - 1, rowspan: f - a + 1, colspan: r - u + 1 }); } return o; } function oe(t) { var l; return (((l = t.getPlugin("mergeCells").mergedCellsCollection) == null ? void 0 : l.mergedCells) || []).map((n) => ({ row: n.row, col: n.col, rowspan: n.rowspan, colspan: n.colspan })); } function je(t) { const e = t.rowCount, o = t.columnCount, l = Array.from({ length: e }, () => new Array(o).fill("")), n = Array.from({ length: e }, () => new Array(o).fill(null)); return t.eachRow((a, u) => { a.eachCell({ includeEmpty: !0 }, (f, r) => { const c = u - 1, m = r - 1; l[c][m] = f.text || ""; const v = Ie(f); n[c][m] = { ...v, ...n[c][m] // 保留合并单元格信息 }; }); }), { data: l, styles: n }; } function We(t, e = "表格导出.xlsx") { return new Promise((o) => { const l = new Blob([t], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }), n = window.URL.createObjectURL(l), a = document.createElement("a"); a.href = n, a.download = e, a.click(), window.URL.revokeObjectURL(n), o(); }); } function Xe(t, e, o, l) { var n, a, u, f; if (l != null && l.length) { t[e] ?? (t[e] = {}), (n = t[e]).styles ?? (n.styles = {}); for (const [r, c, m, v] of l) for (let g = r; g <= m; g++) { (a = t[e].styles)[g] ?? (a[g] = {}); for (let d = c; d <= v; d++) (u = t[e].styles[g])[d] ?? (u[d] = {}), (f = t[e].styles[g][d]).alignment ?? (f.alignment = {}), t[e].styles[g][d].alignment.horizontal = o; } } } function Ve(t, e, o) { var n; const l = t.items.find((a) => a.key === e); l && ((n = l.submenu) != null && n.items) && l.submenu.items.forEach((a) => { const u = a.callback; a.callback = function(...f) { o(a.key), u == null || u.apply(this, f); }; }); } function Je(t) { if (typeof queueMicrotask == "function") return queueMicrotask(t); if (typeof Promise == "function") return Promise.resolve().then(t); setTimeout(t, 0); } function le(t = 1e3) { return new Promise((e) => setTimeout(e, t)); } function qe(t = {}, e = []) { const o = []; for (const l of e) { const { data: n, styles: a, mergeList: u } = t[l] || {}; o.push({ sheetName: l, data: n, styles: a, mergeList: u }); } return o; } function Ke(t) { return new Promise((e, o) => { const l = new FileReader(); l.onload = () => e(l.result), l.onerror = o, l.readAsArrayBuffer(t); }); } function Ge(t, e, o) { return t.reduce((l, n) => { const { row: a, rowspan: u } = n, f = a + u, r = e + o; if (f <= e) return [...l, n]; if (a >= r) return [...l, { ...n, row: a - o }]; if (a < r && f > e) { const c = u - Math.min(r - a, u); if (c > 0) return [...l, { ...n, rowspan: c }]; } return l; }, []); } function Ye(t, e, o) { return !t || !t.length ? t : t.filter((l) => !(l.col >= e && l.col + l.colspan <= e + o)).map((l) => { const { col: n, colspan: a } = l; if (n >= e + o) return { ...l, col: n - o }; if (n < e && n + a > e) { const u = Math.min(n + a, e + o) - e; return { ...l, colspan: a - u }; } else if (n >= e && n < e + o) { const u = e, f = a - (e + o - n); return { ...l, col: u, colspan: f }; } return l; }); } function Ze(t, e) { t.addHook("afterCreateRow", (o, l) => { if (e) { if (e.styles) { const n = Array(l).fill([]); e.styles.splice(o, 0, ...n); } e.mergeList && (e.mergeList = e.mergeList.map((n) => { const { row: a, rowspan: u } = n; return a >= o ? { ...n, row: a + l } : a < o && a + u > o ? { ...n, rowspan: u + l } : n; })); } }); } function Qe(t, e) { t.addHook("afterRemoveRow", (o, l) => { e && (e.styles && e.styles.splice(o, l), e.mergeList && (e.mergeList = Ge(e.mergeList, o, l))); }); } function _e(t, e) { t.addHook("afterCreateCol", (o, l) => { e && (e.styles && e.styles.forEach((n) => { const a = Array(l).fill({}); n.splice(o, 0, ...a); }), e.mergeList && (e.mergeList = e.mergeList.map((n) => { const { col: a, colspan: u } = n; return a >= o ? { ...n, col: a + l } : a < o && a + u > o ? { ...n, colspan: u + l } : n; }))); }); } function et(t, e) { t.addHook("afterRemoveCol", (o, l) => { e && (e.styles && e.styles.forEach((n) => { n.splice(o, l); }), e.mergeList && (e.mergeList = Ye(e.mergeList, o, l))); }); } const I = { // 导出Excel请求类型 EXPORT: "export", // 导出成功响应类型 SUCCESS: "success", // 导出错误响应类型 ERROR: "error" }, D = (t, e) => { const o = t.__vccOpts || t; for (const [l, n] of e) o[l] = n; return o; }, tt = { key: 0, class: "loading-text" }, nt = { __name: "index", props: { text: { type: String, default: "" }, height: { type: String, default: "100px" }, loading: { type: Boolean, default: !1 }, customClass: { type: String, default: "" } }, setup(t) { return (e, o) => (x(), k("div", { class: U([t.customClass]) }, [ t.loading ? (x(), k("div", { key: 0, class: "x-loading-container", style: j({ height: t.height }) }, [ o[0] || (o[0] = A("div", { class: "spinner" }, null, -1)), t.text ? (x(), k("div", tt, se(t.text), 1)) : B("", !0) ], 4)) : ve(e.$slots, "default", { key: 1 }, void 0, !0) ], 2)); } }, ot = /* @__PURE__ */ D(nt, [["__scopeId", "data-v-0c034e83"]]), lt = { class: "toolbar" }, st = { class: "toolbar-buttons" }, at = ["disabled"], rt = ["disabled"], it = { __name: "Toolbar", props: { // 是否显示sheet showSheetTabs: { type: Boolean, default: !1 }, // 保存按钮是否禁用 saveBtnDisabled: { type: Boolean, default: !1 }, // 导出按钮是否禁用 exportBtnDisabled: { type: Boolean, default: !1 }, readonly: { type: Boolean, default: !1 } }, emits: ["export", "save", "sheet-change"], setup(t) { return (e, o) => (x(), k("div", lt, [ A("div", st, [ A("button", { disabled: t.exportBtnDisabled, class: "button", onClick: o[0] || (o[0] = (l) => e.$emit("export")) }, o[2] || (o[2] = [ A("span", null, "导出Excel", -1) ]), 8, at), t.readonly ? B("", !0) : (x(), k("button", { key: 0, disabled: t.saveBtnDisabled, class: "button", onClick: o[1] || (o[1] = (l) => e.$emit("save")) }, o[3] || (o[3] = [ A("span", null, "保存数据", -1) ]), 8, rt)) ]) ])); } }, ut = /* @__PURE__ */ D(it, [["__scopeId", "data-v-7e90ab60"]]), ct = ["onClick"], ft = ["title"], dt = ["onClick"], mt = { __name: "SheetTabs", props: { sheetTabList: { type: Array, required: !0 }, activeSheet: { type: String, required: !0 }, readonly: { type: Boolean, default: !1 } }, emits: ["sheet-change", "add-sheet", "delete-sheet", "edit-sheet"], setup(t, { emit: e }) { const o = t, l = ge("XMessage"), n = C(null), a = C(!1), u = C(null), f = C(null), r = e; function c(p) { o.sheetTabList.length <= 1 || r("delete-sheet", p); } const m = C(!1), v = C(""); function g() { m.value = !0, W(() => { f.value.focus(); }); } function d(p) { return /^[\u4e00-\u9fa5_a-zA-Z0-9-]+$/.test(p); } function S() { const p = v.value.trim(); if (!p) return; if (!d(p)) { l({ type: "warning", message: "工作表名称只能包含中文、英文、数字、下划线、中划线" }), m.value = !1, v.value = ""; return; } if (o.sheetTabList.includes(p)) { l({ type: "warning", message: "工作表已存在" }), m.value = !1, v.value = ""; return; } r("add-sheet", v.value), m.value = !1, v.value = ""; } function E() { if (!n.value) return; const { scrollWidth: p, clientWidth: L } = n.value; a.value = p > L; } function H() { u.value = F(E, 200), window.addEventListener("resize", u.value); } function P() { window.removeEventListener("resize", u.value); } function F(p, L) { let b = null; return function(...z) { b && clearTimeout(b), b = setTimeout(() => p.apply(this, z), L); }; } return ae(() => { H(), E(); }), pe(() => { P(); }), re( () => o.sheetTabList, () => { W(() => { E(); }); }, { deep: !0 } ), (p, L) => (x(), k("div", { ref_key: "tabsContainer", ref: n, class: "sheet-tabs" }, [ (x(!0), k(be, null, we(t.sheetTabList, (b) => (x(), k("div", { key: b, class: U(["sheet-tab", { active: t.activeSheet === b }]), onClick: () => p.$emit("sheet-change", b) }, [ A("span", { class: "tab-name", title: b }, se(b), 9, ft), t.sheetTabList.length !== 1 && !t.readonly ? (x(), k("span", { key: 0, class: "delete-icon", onClick: Ce((z) => c(b), ["stop"]) }, "×", 8, dt)) : B("", !0) ], 10, ct))), 128)), !m.value && !t.readonly ? (x(), k("div", { key: 0, class: U(["add-sheet", { "scroll-bar": a.value }]), onClick: g }, " + ", 2)) : B("", !0), m.value && !t.readonly ? xe((x(), k("input", { key: 1, ref_key: "InputRef", ref: f, "onUpdate:modelValue": L[0] || (L[0] = (b) => v.value = b), class: "sheet-input", type: "text", placeholder: "请输入", onBlur: S, onKeyup: ke(S, ["enter"]) }, null, 544)), [ [Le, v.value] ]) : B("", !0) ], 512)); } }, ht = /* @__PURE__ */ D(mt, [["__scopeId", "data-v-6484c524"]]), yt = { class: "excel-preview" }, N = "Sheet1", vt = /* @__PURE__ */ Object.assign({ name: "ExcelPreview" }, { __name: "index", props: { /** * 数据分为两种: * 1. 直接传入表格数据(Array) * 2. 传入excel文件(ArrayBuffer、Uint8Array) */ data: { type: [Uint8Array, ArrayBuffer, Array, Blob], default: () => { } }, // 表头下拉菜单 dropdownMenu: { type: [Boolean, Array, Object], default: !0 }, // 表格右键菜单 contextMenu: { type: [Boolean, Array, Object], default: !0 }, // 是否只读 readOnly: { type: Boolean, default: !1 }, // 是否显示标题行 rowHeaders: { type: Boolean, default: !0 }, // 是否显示标题列 colHeaders: { type: Boolean, default: !0 }, // 是否手动调整行高 manualRowResize: { type: Boolean, default: !0 }, // 手动手动调整列宽 manualColumnResize: { type: Boolean, default: !0 }, // 表格高度 width: { type: [String, Number], default: "100%" }, // 表格高度 height: { type: [String, Number], default: "400px" }, // 表格列宽度 colWidths: { type: [Number, Array, Function], default: 100 }, // 导入excel时保留的最小行数 minSpareRows: { type: Number, default: 0 }, // 导入excel时保留的最小列数 minSpareCols: { type: Number, default: 0 }, // 导出excel文件名 downloadFileName: { type: String, default: "export.xlsx" }, // 是否显示工具栏 showToolbar: { type: Boolean, default: !0 } }, emits: ["export", "save"], setup(t, { emit: e }) { Te((s) => ({ cd7efb0e: H.value })), Re("XMessage", Fe), He(Pe), De(); const o = e, l = t; let n = null; const a = C(null), u = C(null), f = C([]), r = Ee({}), c = C(N), m = C(!1), v = C(!1), g = C(!1), d = C([]), S = Ae(), E = new Worker(new URL("/assets/excel-export.worker-eDwnj8u_.js", import.meta.url), { type: "module" }), H = Be(() => { const s = a.value.$el.offsetHeight; return l.height.toString().includes("px") ? Number.parseInt(l.height) + s + "px" : l.height + s + "px"; }), P = { // 拦截右键菜单 afterContextMenuDefaultOptions(s) { Ve(s, "alignment", (i) => { const h = i.split(":").pop(), y = n.getSelected(); Xe(r.value, c.value, h, y); }); } }; function F() { const s = oe(n); r.value[c.value].mergeList = s || []; } function p(s) { if (r.value[s]) return; const i = Array.from({ length: 10 }, () => Array.from({ length: 10 }, () => "")); r.value = { ...r.value, [s]: { data: i, styles: [], mergeList: [] } }, c.value = s, d.value.push(s), M(i); } function L(s) { if (d.value.length <= 1) return; const i = d.value.filter((y) => y !== s); if (s === c.value) { const y = d.value.findIndex((R) => R === s), w = Math.max(0, y - 1); c.value = i[w]; } const { ...h } = r.value; d.value = i, r.value = h, delete r.value[s], M(r.value[c.value].data); } function b() { n && (v.value = !0, Je(() => { const s = oe(n); r.value[c.value].mergeList = s || []; const i = JSON.parse(JSON.stringify(r.value)), h = qe(i, d.value); v.value = !1, o("save", h); })); } function z(s, i, h, y, w, R, O) { var $, V, J, q, K, G, Y, Z, Q, _, ee; if (O.className && (i.classList = O.className), (J = (V = ($ = r.value[c.value]) == null ? void 0 : $.styles) == null ? void 0 : V[h]) != null && J[y]) { const T = r.value[c.value].styles[h][y]; (q = T.font) != null && q.bold && (i.style.fontWeight = "bold"), (K = T.font) != null && K.italic && (i.style.fontStyle = "italic"), (G = T.font) != null && G.underline && (i.style.textDecoration = "underline"), (Z = (Y = T.font) == null ? void 0 : Y.color) != null && Z.rgb && (i.style.color = `#${T.font.color.rgb}`), (Q = T.fgColor) != null && Q.rgb && (i.style.backgroundColor = `#${T.fgColor.rgb}`), T.alignment && (i.style.textAlign = ((_ = T.alignment) == null ? void 0 : _.horizontal) || "left", i.style.verticalAlign = ((ee = T.alignment) == null ? void 0 : ee.vertical) || "top"); } return i.innerHTML = R === null ? "" : R, i; } function M(s = []) { f.value = (s == null ? void 0 : s.length) > 0 ? s : Array.from({ length: 10 }, () => Array.from({ length: 10 }, () => "")), W(() => { var i; if (n) { (i = n == null ? void 0 : n.loadData) == null || i.call(n, f.value); const h = r.value[c.value].mergeList; n.updateSettings({ mergeCells: h }), n.render(); } }); } async function X(s) { const i = new Oe.Workbook(); await i.xlsx.load(s); const h = {}; i.eachSheet((w) => { const { data: R, styles: O } = je(w); if (R.length === 0) return; const $ = Ue(w); h[w.name] = { data: R, styles: O, mergeList: $ }, d.value.push(w.name); }); const y = i.worksheets[0].name; r.value = h, c.value = y, M(h[y].data); } async function ie() { m.value = !0, g.value = !0; try { const s = JSON.parse(JSON.stringify(r.value)), i = structuredClone(ze(d.value)); E.postMessage({ type: I.EXPORT, data: { sheetData: s, sheetTabList: i } }); } catch (s) { throw new Error("导出excel失败:" + s); } } function ue(s) { if (s === c.value) return; c.value = s; const i = r.value[s]; i && M(i.data); } function ce() { const s = Array.from({ length: 10 }, () => Array.from({ length: 10 }, () => "")); r.value = { [N]: { data: s, styles: [] } }, c.value = N, d.value = [N], M(s); } function fe() { E.onmessage = async (s) => { const { type: i, data: h, error: y, originType: w } = s.data; le().then(() => { if (i === I.SUCCESS && w === I.EXPORT) { const R = l.downloadFileName.endsWith(".xlsx") ? l.downloadFileName : `${l.downloadFileName}.xlsx`; We(h, R).then(o("export")); } g.value = !1, m.value = !1; }); }; } function de() { r.value = {}, c.value = N, d.value = [], f.value = []; } async function me(s) { var i, h; if (de(), m.value = !0, s instanceof Blob) { const y = await Ke(s); X(y); } else s instanceof Uint8Array || s instanceof ArrayBuffer ? X(s) : s instanceof Object && Array.isArray(s) && (d.value = s == null ? void 0 : s.map((y) => y.sheetName), r.value = s.reduce((y, w) => (y[w.sheetName] = { data: w.data, styles: w.styles, mergeList: w.mergeList }, y), {}), c.value = (i = d.value) == null ? void 0 : i[0], M((h = r.value[c.value]) == null ? void 0 : h.data)); le().then(() => { m.value = !1, f.value.length === 0 && ce(); }); } function he() { Ze(n, r.value[c.value]), Qe(n, r.value[c.value]), _e(n, r.value[c.value]), et(n, r.value[c.value]); } function ye() { var i; const s = (i = u.value) == null ? void 0 : i.hotInstance; s && (n = s, he()); } return ae(() => { fe(); }), Me(() => { E.terminate(), S.stop(); }), S.run(() => { re( () => l.data, (s) => { me(s); }, { immediate: !0 } ); }), (s, i) => (x(), k("div", yt, [ t.showToolbar ? (x(), te(ut, { key: 0, "export-btn-disabled": g.value, "save-btn-disabled": v.value, "show-sheet-tabs": Object.keys(r.value).length > 0, readonly: t.readOnly, onExport: ie, onSave: b }, null, 8, ["export-btn-disabled", "save-btn-disabled", "show-sheet-tabs", "readonly"])) : B("", !0), ne(ot, { "custom-class": "excel-preview-loading", style: j({ zIndex: m.value ? 9999 : -1, top: t.showToolbar ? "58px" : "0px" }), loading: !0 }, null, 8, ["style"]), ne(ht, { ref_key: "SheetTabsRef", ref: a, readonly: t.readOnly, "sheet-tab-list": d.value, "active-sheet": c.value, onDeleteSheet: L, onAddSheet: p, onSheetChange: ue }, null, 8, ["readonly", "sheet-tab-list", "active-sheet"]), A("div", { style: j({ height: t.height }), class: "ht-theme-main-dark-auto border" }, [ f.value.length > 0 ? (x(), te(Se($e), Ne({ key: 0, ref_key: "hot", ref: u, data: f.value, "license-key": "non-commercial-and-evaluation", "row-headers": t.rowHeaders, "col-headers": t.colHeaders, "manual-row-resize": t.manualRowResize, "manual-column-resize": t.manualColumnResize, "min-spare-rows": t.minSpareRows, "dropdown-menu": t.dropdownMenu, "min-spare-cols": t.minSpareCols, "context-menu": t.contextMenu, "drag-to-scroll": !0, "read-only": t.readOnly, height: t.height, width: t.width, language: "zh-CN", "col-widths": t.colWidths, "render-all-rows": !1, init: ye, renderer: z, settings: P, "after-merge-cells": F }, s.$attrs), null, 16, ["data", "row-headers", "col-headers", "manual-row-resize", "manual-column-resize", "min-spare-rows", "dropdown-menu", "min-spare-cols", "context-menu", "read-only", "height", "width", "col-widths"])) : B("", !0) ], 4) ])); } }), gt = /* @__PURE__ */ D(vt, [["__scopeId", "data-v-d0fb4907"]]), pt = (t) => (t.install = (e) => { e.component(t.name, t); }, t), Tt = pt(gt); export { Tt as XExcelPreviewInstall, gt as default };