@develop-plugins/excel-preview
Version:
一个基于 Vue3 的 Excel 预览和编辑组件,支持导入导出 Excel 文件,以及在线编辑表格数据。
707 lines (706 loc) • 22.6 kB
JavaScript
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
};