vue-print-next
Version:
This is a directive wrapper for printed, Simple, fast, convenient, light.
784 lines (783 loc) • 37.5 kB
JavaScript
var D = Object.defineProperty, F = Object.defineProperties;
var q = Object.getOwnPropertyDescriptors;
var S = Object.getOwnPropertySymbols;
var I = Object.prototype.hasOwnProperty, L = Object.prototype.propertyIsEnumerable;
var T = (l, e, t) => e in l ? D(l, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : l[e] = t, E = (l, e) => {
for (var t in e || (e = {}))
I.call(e, t) && T(l, t, e[t]);
if (S)
for (var t of S(e))
L.call(e, t) && T(l, t, e[t]);
return l;
}, z = (l, e) => F(l, q(e));
var H = (l, e) => {
var t = {};
for (var o in l)
I.call(l, o) && e.indexOf(o) < 0 && (t[o] = l[o]);
if (l != null && S)
for (var o of S(l))
e.indexOf(o) < 0 && L.call(l, o) && (t[o] = l[o]);
return t;
};
var k = (l, e, t) => T(l, typeof e != "symbol" ? e + "" : e, t);
const g = "[VuePrintNext]";
let N = 0;
const C = {
A0: { width: "841mm", height: "1189mm" },
A1: { width: "594mm", height: "841mm" },
A2: { width: "420mm", height: "594mm" },
A3: { width: "297mm", height: "420mm" },
A4: { width: "210mm", height: "297mm" },
A5: { width: "148mm", height: "210mm" },
A6: { width: "105mm", height: "148mm" },
A7: { width: "74mm", height: "105mm" },
A8: { width: "52mm", height: "74mm" },
Letter: { width: "215.9mm", height: "279.4mm" },
Legal: { width: "215.9mm", height: "355.6mm" },
Tabloid: { width: "279.4mm", height: "431.8mm" }
}, j = (l = 32) => `width: ${l}px; height: ${l}px; border: none; background: transparent; cursor: pointer; border-radius: 4px; display: flex; align-items: center; justify-content: center; transition: background-color 0.2s ease; padding: 0; outline: none;`, B = (l, e, t = 32, o = "") => {
const n = document.createElement("button");
return n.setAttribute("class", l), n.setAttribute("style", j(t)), n.innerHTML = e, o && n.setAttribute("title", o), n;
}, x = (l, e) => (l.addEventListener("mouseover", () => {
l.style.backgroundColor = e ? "#444" : "#f0f0f0";
}), l.addEventListener("mouseout", () => {
l.style.backgroundColor = "transparent";
}), l), w = {
close: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>',
zoomIn: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>',
zoomOut: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line></svg>',
darkMode: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>',
lightMode: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>',
fullscreen: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" y1="3" x2="14" y2="10"></line><line x1="3" y1="21" x2="10" y2="14"></line></svg>',
exitFullscreen: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="8 3 3 3 3 8"></polyline><polyline points="21 8 21 3 16 3"></polyline><polyline points="3 16 3 21 8 21"></polyline><polyline points="16 21 21 21 21 16"></polyline></svg>',
print: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 6px;"><polyline points="6 9 6 2 18 2 18 9"></polyline><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path><rect x="6" y="14" width="12" height="8"></rect></svg>',
paper: '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 6px;"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>'
}, b = (l, e) => {
var o;
if (typeof l == "number" || !isNaN(Number(l))) return `${l}${e}`;
const t = (o = l.toString().match(/[a-zA-Z]+/g)) == null ? void 0 : o[0];
return !t || !(t in C) ? `${l}mm` : l;
};
function O() {
try {
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent) && navigator.vendor === "Apple Computer, Inc.";
} catch (l) {
return !1;
}
}
function U(l) {
const { immediateEvents: e = ["load"], debug: t = !1 } = {}, o = HTMLIFrameElement.prototype.addEventListener;
return HTMLIFrameElement.prototype.addEventListener = function(n, r, i) {
var s;
if (o.call(this, n, r, i), !(!O() || typeof r != "function"))
try {
e.includes(n) && ((s = this.contentWindow) == null ? void 0 : s.document.readyState) === "complete" && (t && console.log(`[Safari Iframe Patch] Triggering "${n}" immediately`), this.contentWindow.setTimeout(() => {
r.call(this, new Event(n));
}));
} catch (a) {
console.warn("[Safari Iframe Patch] Error:", a);
}
}, function() {
HTMLIFrameElement.prototype.addEventListener = o, t && console.log("[Safari Iframe Patch] Restored original addEventListener");
};
}
class W {
constructor(e) {
// html 文档标准
k(this, "standards", {
strict: "strict",
loose: "loose",
html5: "html5"
});
// 打印窗口的 iframe id
k(this, "iframeId", "");
// 预览窗口的 body
k(this, "previewBody", null);
// 预览窗口的 关闭按钮
k(this, "close", null);
// 预览窗口的缩放比例
k(this, "scale", 1);
// 预览窗口是否为深色模式
k(this, "isDarkMode", !1);
// 预览窗口是否为全屏模式
k(this, "isFullscreen", !0);
// 默认为全屏模式
// 用户设置
k(this, "settings");
const v = e, {
vue: t,
standard: o = "html5",
zIndex: n = 20002,
previewTitle: r = "打印预览",
previewPrintBtnLabel: i = "打印",
preview: s = !1,
popTitle: a = document.title,
previewSize: c,
darkMode: h = !1,
windowMode: d = !1,
defaultScale: p = 1,
previewTools: f = { zoom: !0, theme: !0, fullscreen: !0 }
} = v, y = H(v, [
"vue",
"standard",
"zIndex",
"previewTitle",
"previewPrintBtnLabel",
"preview",
"popTitle",
"previewSize",
"darkMode",
"windowMode",
"defaultScale",
"previewTools"
]);
this.isDarkMode = h, this.isFullscreen = !d, this.scale = p, this.settings = z(E({
standard: o,
zIndex: n,
previewTitle: r,
previewPrintBtnLabel: i,
preview: s,
popTitle: a,
previewSize: c,
darkMode: h,
windowMode: d,
defaultScale: p,
previewTools: f
}, y), {
previewBeforeOpenCallback: () => {
var m;
return (m = e.previewBeforeOpenCallback) == null ? void 0 : m.call(e, t);
},
previewOpenCallback: () => {
var m;
return (m = e.previewOpenCallback) == null ? void 0 : m.call(e, t);
},
openCallback: () => {
var m;
return (m = e.openCallback) == null ? void 0 : m.call(e, t);
},
closeCallback: () => {
var m;
return (m = e.closeCallback) == null ? void 0 : m.call(e, t);
},
beforeOpenCallback: () => {
var m;
return (m = e.beforeOpenCallback) == null ? void 0 : m.call(e, t);
}
}), this.init();
}
init() {
this.iframeId = `printArea_${++N}`;
const { el: e, url: t, asyncUrl: o } = this.settings;
if (U(), e)
this.handlePrintWindow("");
else if (t)
this.handlePrintWindow(t);
else if (o)
o((n) => this.handlePrintWindow(n), this.settings.vue);
else
throw new Error(`${g}: Either "el", "url", or "asyncUrl" parameter must be provided in the settings.`);
}
handlePrintWindow(e) {
const t = this.getPrintWindow(e);
!e && this.write(t.doc), this.settings.preview ? this.previewIframeLoad() : this.print(t);
}
addEvent(e, t, o) {
e && e.addEventListener(t, o, !1);
}
previewIframeLoad() {
var o, n;
const e = document.getElementById("vue-print-next-previewBox");
if (!e) return;
const t = e.querySelector("iframe");
(n = (o = this.settings).previewBeforeOpenCallback) == null || n.call(o), this.addEvent(t, "load", () => {
var r, i;
this.previewBoxShow(), (i = (r = this.settings).previewOpenCallback) == null || i.call(r);
}), this.addEvent(e.querySelector(".previewBodyUtilPrintBtn"), "click", () => {
var r, i, s, a, c, h;
(i = (r = this.settings).beforeOpenCallback) == null || i.call(r), t != null && t.contentWindow ? this.waitForFontsLoaded(t.contentWindow).then(() => {
var d, p, f, y, v;
(p = (d = this.settings).openCallback) == null || p.call(d), (f = t.contentWindow) == null || f.print(), (v = (y = this.settings).closeCallback) == null || v.call(y);
}).catch(() => {
var d, p, f, y, v;
console.warn(`${g}: Font loading timeout in preview mode, proceeding with print`), (p = (d = this.settings).openCallback) == null || p.call(d), (f = t.contentWindow) == null || f.print(), (v = (y = this.settings).closeCallback) == null || v.call(y);
}) : ((a = (s = this.settings).openCallback) == null || a.call(s), (h = (c = this.settings).closeCallback) == null || h.call(c));
});
}
print(e) {
var r, i;
const t = document.getElementById(this.iframeId) || e.f, o = t == null ? void 0 : t.contentWindow;
if (!o) return;
const n = () => {
this.waitForFontsLoaded(o).then(() => {
const s = setTimeout(() => {
var a, c, h, d;
o.focus(), (c = (a = this.settings).openCallback) == null || c.call(a), o.print(), t.remove(), (d = (h = this.settings).closeCallback) == null || d.call(h), clearTimeout(s);
}, 100);
}).catch(() => {
console.warn(`${g}: Font loading timeout, proceeding with print`);
const s = setTimeout(() => {
var a, c, h, d;
o.focus(), (c = (a = this.settings).openCallback) == null || c.call(a), o.print(), t.remove(), (d = (h = this.settings).closeCallback) == null || d.call(h), clearTimeout(s);
}, 100);
});
};
(i = (r = this.settings).beforeOpenCallback) == null || i.call(r), this.addEvent(t, "load", n);
}
/**
* 获取打印需要隐藏的 css
* @private
*/
getNoPrintMediaStyle() {
const { noPrintSelector: e } = this.settings;
if (!e) return "";
const o = (Array.isArray(e) ? e : [e]).filter((n) => n.trim());
return o.length ? `${o.join(",")} { display: none; }` : (console.error(new TypeError(`${g}: The "noPrintSelector" must be a non-empty string or an array of strings.`)), "");
}
write(e) {
e.open(), e.write(
`${this.docType()}<html lang='zh'>${this.getHead()}${this.getBody()}</html>`
), e.close();
}
docType() {
if (this.settings.standard === this.standards.html5)
return "<!DOCTYPE html>";
const e = this.settings.standard === this.standards.loose ? " Transitional" : "", t = this.settings.standard === this.standards.loose ? "loose" : "strict";
return `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01${e}//EN" "http://www.w3.org/TR/html4/${t}.dtd">`;
}
/**
* 获取媒体打印独有样式
* @private
*/
getPrintMediaStyle() {
const { paperSize: e, orientation: t, customSize: o } = this.settings, n = "body {-webkit-print-color-adjust: exact; -moz-print-color-adjust: exact; -ms-print-color-adjust: exact; print-color-adjust: exact;}", r = this.settings.paperSize !== void 0 || this.settings.orientation !== void 0 || this.settings.customSize !== void 0;
let i = "";
if (r) {
const s = e || "A4", a = t || "portrait";
if (s === "custom" && o) {
const { width: c, height: h, unit: d = "mm" } = o, p = b(c, d), f = b(h, d);
i = a === "portrait" ? ` { size: ${p} ${f}; }` : ` { size: ${f} ${p}; }`;
} else if (s in C) {
const c = C[s];
i = a === "portrait" ? ` { size: ${c.width} ${c.height}; }` : ` { size: ${c.height} ${c.width}; }`;
} else {
const c = C.A4;
i = t === "portrait" ? ` { size: ${c.width} ${c.height}; }` : ` { size: ${c.height} ${c.width}; }`;
}
}
return `${n}${i}`;
}
getHead() {
const { extraHead: e = "", extraCss: t = "", popTitle: o } = this.settings, n = Array.from(document.querySelectorAll('link[href$=".css"]')).map((h) => `<link type="text/css" rel="stylesheet" href='${h.href}'>`).join(""), r = Array.from(document.querySelectorAll("style")).map((h) => h.outerHTML).join(""), i = t.split(",").filter((h) => h.trim()).map((h) => `<link type="text/css" rel="stylesheet" href='${h.trim()}'>`).join(""), s = this.getPrintMediaStyle(), a = this.getNoPrintMediaStyle();
return `<head><title>${o}</title>` + e + n + r + `<style type="text/css">${a}${s}</style>` + i + "</head>";
}
/**
* 获取打印区的 DOM
*/
getPrintAreaDom() {
const e = this.settings.el, t = typeof e == "string";
if (e instanceof HTMLElement) return [e];
if (!t)
throw new TypeError(`${g}: The "el" property should be either a string (CSS selector) or an HTMLElement, but received type "${typeof e}".`);
let o = Array.from(document.querySelectorAll(e));
if (!(o != null && o.length))
throw new Error(`${g}: No elements found matching the selector: "${e}".`);
return o;
}
getBody() {
const e = this.getPrintAreaDom(), t = document.createElement("div");
return e.forEach((o) => {
const n = o.cloneNode(!0);
this.removeScriptHandler(n), this.canvasToImgHandler(o, n), this.formDataHandler(o, n), t.appendChild(n);
}), `<body>${t.innerHTML}</body>`;
}
/**
* 移除 script 标签
* @param clonedElement
*/
removeScriptHandler(e) {
const t = Array.from(e.querySelectorAll("script"));
t.length !== 0 && t.forEach((o) => {
o.remove();
});
}
/**
* 复制 Canvas 内容到新的 Canvas 元素
* @param originalElement
* @param clonedElement
* @private
*/
canvasToImgHandler(e, t) {
try {
const o = Array.from(e.querySelectorAll("canvas")), n = Array.from(t.querySelectorAll("canvas"));
if (n.length === 0) return;
let r = [];
n.forEach((i, s) => {
const a = o[s];
if (!a) {
console.warn(`${g}: Canvas element mismatch, Canvas at index ${s} does not exist in the original element`);
return;
}
const c = i.parentNode;
if (!c) {
console.warn(`${g}: Canvas parent element does not exist`);
return;
}
try {
const h = a.toDataURL("image/png"), d = new Image();
d.className = "canvasImg", d.style.display = "block", d.src = h, r.push({ parent: c, newElement: d, oldElement: i });
} catch (h) {
console.error(`${g}: Error processing Canvas:`, h);
const d = document.createElement("div");
d.className = "canvas-error", d.style.cssText = "padding: 10px; border: 1px dashed #ff6b6b; color: #ff6b6b; text-align: center;", d.textContent = "Unable to load Canvas content, possibly due to cross-origin restrictions", r.push({ parent: c, newElement: d, oldElement: i });
}
}), r.forEach(({ parent: i, newElement: s, oldElement: a }) => {
i.replaceChild(s, a);
});
} catch (o) {
console.error(`${g}: Error occurred while processing Canvas elements:`, o);
}
}
/**
* 根据type去处理form表单
* @param originalElement
* @param clonedElement
* @private
*/
formDataHandler(e, t) {
try {
const o = Array.from(t.querySelectorAll("input,select,textarea"));
if (o.length === 0) return;
const n = Array.from(e.querySelectorAll("select"));
let r = -1;
o.forEach((i) => {
var s;
try {
const a = i.value;
switch ((s = i.getAttribute("type")) != null ? s : i.tagName.toLowerCase()) {
case "select":
if (r++, r < n.length) {
const d = n[r].selectedIndex, p = i.options;
p && p.length > d && d >= 0 ? p[d].setAttribute("selected", "selected") : p && p.length > 0 ? (p[0].setAttribute("selected", "selected"), console.warn(`${g}: Select element option index out of range, defaulting to first option`)) : console.warn(`${g}: Select element has no options`);
} else
console.warn(`${g}: Original select element not found at index ${r}`);
break;
case "textarea":
i.innerHTML = a, i.setAttribute("html", a);
break;
case "radio":
case "checkbox":
i.checked && i.setAttribute("checked", "checked");
break;
default:
i.value = a, i.setAttribute("value", a);
break;
}
} catch (a) {
console.error(`${g}: Error processing form element (${i.tagName}):`, a);
}
});
} catch (o) {
console.error(`${g}: Error occurred while processing form data:`, o);
}
}
// 生成并返回打印窗口的 iframe 和文档对
getPrintWindow(e) {
var n;
const t = this.createIframe(e), o = t.contentDocument || ((n = t.contentWindow) == null ? void 0 : n.document) || t.document;
if (!o)
throw new Error(`${g}: Unable to find the document object within the created iframe. Please ensure the iframe is correctly created and loaded.`);
return { f: t, win: t.contentWindow || t, doc: o };
}
// 显示预览窗口
previewBoxShow() {
const e = document.getElementById("vue-print-next-previewBox");
if (!e) return;
const t = document.querySelector("html");
t && (t.style.overflow = "hidden"), e.style.opacity = "0", e.style.display = "block", setTimeout(() => {
e.style.transition = "opacity 0.3s ease", e.style.opacity = "1", this.applyInitialSettings();
}, 10);
}
// 应用初始设置
applyInitialSettings() {
this.isDarkMode && (this.isDarkMode = !1, this.toggleTheme()), this.isFullscreen || (this.isFullscreen = !0, this.toggleFullscreen()), this.scale !== 1 && this.updatePreviewScale();
}
// 隐藏预览窗口
previewBoxHide() {
const e = document.getElementById("vue-print-next-previewBox");
e && (e.style.opacity = "0", setTimeout(() => {
var n;
const t = document.querySelector("html");
t && (t.style.overflow = "visible");
const o = e.querySelector(".paperContainer");
(n = o == null ? void 0 : o.querySelector("iframe")) == null || n.remove(), e.style.display = "none", this.resetPreviewState(e);
}, 300));
}
// 重置预览状态
resetPreviewState(e) {
const t = e.querySelector(".previewBodyUtilPrintBtn");
t && (t.style.backgroundColor = "#1890ff", t.style.boxShadow = "none"), this.scale = this.settings.defaultScale || 1, this.isDarkMode && this.toggleTheme(), this.isFullscreen && (this.isFullscreen = !1, e.style.width = "100%", e.style.height = "100%", e.style.top = "0", e.style.left = "0", e.style.borderRadius = "0");
}
// 创建或获取打印预览的框架
previewBox() {
var o;
let e = document.getElementById("vue-print-next-previewBox");
if (e) {
(o = e.querySelector("iframe")) == null || o.remove();
const n = e.querySelector(".previewHeader");
if (n) {
const s = this.createPreviewHeader();
e.replaceChild(s, n), this.close = s.querySelector(".previewClose");
}
const r = e.querySelector(".paperContainer");
r && this.setPaperSize(r);
const i = e.querySelector(".paperInfo");
return i && (i.innerHTML = w.paper + this.getPaperSizeText()), { close: this.close, previewBody: e.querySelector(".previewBody") };
}
e = document.createElement("div"), e.setAttribute("id", "vue-print-next-previewBox"), e.setAttribute(
"style",
"position: fixed; top: 0px; left: 0px; width: 100%; height: 100%; background: rgba(250, 250, 250, 0.98); display: none; z-index: " + this.settings.zIndex + '; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); transition: background-color 0.3s ease;'
), e.setAttribute("data-theme", "light");
const t = this.createPreviewHeader();
return e.appendChild(t), this.previewBody = this.createPreviewBody(), e.appendChild(this.previewBody), document.body.appendChild(e), { close: this.close, previewBody: this.previewBody };
}
// 创建预览框架的头部
createPreviewHeader() {
const e = document.createElement("div");
e.setAttribute("class", "previewHeader"), e.setAttribute("style", "padding: 12px 24px; background: #fff; border-bottom: 1px solid #eaeaea; display: flex; align-items: center; justify-content: space-between; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); transition: background-color 0.3s ease, border-color 0.3s ease;");
const t = document.createElement("div");
t.setAttribute("style", "font-size: 16px; font-weight: 500; color: #333; transition: color 0.3s ease;"), t.innerHTML = this.settings.previewTitle || "";
const o = document.createElement("div");
o.setAttribute("style", "display: flex; align-items: center; gap: 12px;");
const n = { zoom: !0, theme: !0, fullscreen: !0 };
let r;
if (this.settings.previewTools === !1)
r = { zoom: !1, theme: !1, fullscreen: !1 };
else if (typeof this.settings.previewTools == "object" && this.settings.previewTools !== null) {
const i = this.settings.previewTools;
r = {
zoom: i.zoom !== !1,
theme: i.theme !== !1,
fullscreen: i.fullscreen !== !1
};
} else
r = E({}, n);
if (r.zoom) {
const i = this.createZoomControls();
o.appendChild(i);
}
if (r.theme) {
const i = this.createThemeToggle();
o.appendChild(i);
}
if (r.fullscreen) {
const i = this.createFullscreenToggle();
o.appendChild(i);
}
return this.close = this.createCloseButton(), e.appendChild(t), e.appendChild(o), e.appendChild(this.close), e;
}
// 创建iframe元素
createIframe(e) {
const t = document.createElement("iframe");
if (t.id = this.iframeId, t.src = e || "about:blank", t.style.cssText = "position: fixed; left: -9999px; border: 0; width: 0; height: 0; opacity: 0;", !this.settings.preview)
document.body.appendChild(t);
else {
t.style.cssText = "border: 0px; width: 100%; height: 100%; background: white; transition: background-color 0.3s ease;";
const { close: o, previewBody: n } = this.previewBox(), r = n == null ? void 0 : n.querySelector(".paperContainer"), i = r == null ? void 0 : r.querySelector("div");
i == null || i.appendChild(t), this.updatePreviewScale(), this.addEvent(o, "click", this.previewBoxHide.bind(this)), this.addEvent(t, "load", this.applyIframeDarkMode.bind(this, t));
}
return t;
}
// 为iframe应用深色模式
applyIframeDarkMode(e) {
var t;
if (this.isDarkMode)
try {
const o = e.contentDocument || ((t = e.contentWindow) == null ? void 0 : t.document);
if (o) {
const n = o.createElement("style");
n.textContent = "body { color-scheme: dark; }", o.head.appendChild(n);
}
} catch (o) {
console.warn("[VuePrintNext] Failed to apply dark mode style to iframe content", o);
}
}
// 创建关闭按钮
createCloseButton() {
const e = document.createElement("div");
return e.setAttribute("class", "previewClose"), e.setAttribute("style", "width: 32px; height: 32px; cursor: pointer; display: flex; align-items: center; justify-content: center; border-radius: 4px; transition: background-color 0.2s ease, color 0.3s ease;"), e.innerHTML = w.close, x(e, this.isDarkMode), e;
}
// 创建缩放控制
createZoomControls() {
const e = document.createElement("div");
e.setAttribute("class", "zoomControls"), e.setAttribute("style", "display: flex; align-items: center; gap: 8px;");
const t = B("zoomOutBtn", w.zoomOut, 28);
x(t, this.isDarkMode), t.addEventListener("click", () => {
this.scale = Math.max(0.5, this.scale - 0.1), this.updatePreviewScale();
});
const o = document.createElement("span");
o.setAttribute("class", "zoomDisplay"), o.setAttribute("style", "font-size: 14px; min-width: 40px; text-align: center;"), o.textContent = "100%";
const n = B("zoomInBtn", w.zoomIn, 28);
x(n, this.isDarkMode), n.addEventListener("click", () => {
this.scale = Math.min(2, this.scale + 0.1), this.updatePreviewScale();
});
const r = document.createElement("button");
return r.setAttribute("class", "zoomResetBtn"), r.setAttribute("style", "font-size: 12px; padding: 2px 6px; border: none; background: transparent; cursor: pointer; border-radius: 4px; transition: background-color 0.2s ease;"), r.textContent = "重置", x(r, this.isDarkMode), r.addEventListener("click", () => {
this.scale = this.settings.defaultScale || 1, this.updatePreviewScale();
}), e.appendChild(t), e.appendChild(o), e.appendChild(n), e.appendChild(r), e;
}
// 更新预览缩放比例
updatePreviewScale() {
const e = document.getElementById("vue-print-next-previewBox");
if (!e) return;
const t = e.querySelector(".zoomDisplay");
t && (t.textContent = `${Math.round(this.scale * 100)}%`);
const o = e.querySelector(".paperContainer");
o && (o.style.transform = `scale(${this.scale})`, o.style.transformOrigin = "center top");
}
// 创建主题切换按钮
createThemeToggle() {
const e = B("themeToggle", w.darkMode, 32, "切换深色/浅色模式");
return x(e, this.isDarkMode), e.addEventListener("click", () => {
this.toggleTheme();
}), e;
}
// 切换深色/浅色主题
toggleTheme() {
var v, m, M, P, $;
this.isDarkMode = !this.isDarkMode;
const e = document.getElementById("vue-print-next-previewBox");
if (!e) return;
const t = this.isDarkMode ? {
theme: "dark",
background: "rgba(30, 30, 30, 0.98)",
headerBg: "#242424",
borderColor: "#333",
textColor: "#fff",
contentBg: "#1a1a1a",
pageInfoColor: "#ccc",
themeIcon: w.lightMode
} : {
theme: "light",
background: "rgba(250, 250, 250, 0.98)",
headerBg: "#fff",
borderColor: "#eaeaea",
textColor: "#333",
contentBg: "#f5f5f5",
pageInfoColor: "#666",
themeIcon: w.darkMode
};
e.setAttribute("data-theme", t.theme), e.style.backgroundColor = t.background;
const o = e.querySelector(".previewHeader");
o && (o.style.backgroundColor = t.headerBg, o.style.borderColor = t.borderColor);
const n = e.querySelector(".previewHeader > div:first-child");
n && (n.style.color = t.textColor), e.querySelectorAll(".previewHeader button, .previewHeader svg, .zoomDisplay, .zoomResetBtn").forEach((u) => {
u.style.color = t.textColor;
});
const i = e.querySelector(".previewBodyUtil");
i && (i.style.backgroundColor = t.headerBg, i.style.borderColor = t.borderColor);
const s = e.querySelector(".previewBody > div:last-child");
s && (s.style.backgroundColor = t.contentBg);
const a = e.querySelector(".pageInfo");
a && (a.style.color = t.pageInfoColor);
const c = e.querySelector(".themeToggle");
if (c) {
const u = c.cloneNode(!0);
u.innerHTML = t.themeIcon, (v = c.parentNode) == null || v.replaceChild(u, c), x(u, this.isDarkMode), u.addEventListener("click", () => {
this.toggleTheme();
});
}
const h = e.querySelector(".zoomInBtn");
if (h) {
const u = h.cloneNode(!0);
(m = h.parentNode) == null || m.replaceChild(u, h), x(u, this.isDarkMode), u.addEventListener("click", () => {
this.scale = Math.min(2, this.scale + 0.1), this.updatePreviewScale();
});
}
const d = e.querySelector(".zoomOutBtn");
if (d) {
const u = d.cloneNode(!0);
(M = d.parentNode) == null || M.replaceChild(u, d), x(u, this.isDarkMode), u.addEventListener("click", () => {
this.scale = Math.max(0.5, this.scale - 0.1), this.updatePreviewScale();
});
}
const p = e.querySelector(".zoomResetBtn");
if (p) {
const u = p.cloneNode(!0);
(P = p.parentNode) == null || P.replaceChild(u, p), x(u, this.isDarkMode), u.addEventListener("click", () => {
this.scale = this.settings.defaultScale || 1, this.updatePreviewScale();
});
}
const f = e.querySelector(".fullscreenToggle");
if (f) {
const u = f.cloneNode(!0);
u.innerHTML = this.isFullscreen ? w.fullscreen : w.exitFullscreen, ($ = f.parentNode) == null || $.replaceChild(u, f), x(u, this.isDarkMode), u.addEventListener("click", () => {
this.toggleFullscreen();
});
}
const y = e.querySelector(".previewBodyUtilPrintBtn");
y && (y.addEventListener("mouseover", () => {
y.style.backgroundColor = "#40a9ff", y.style.boxShadow = "0 2px 8px rgba(24, 144, 255, 0.3)";
}), y.addEventListener("mouseout", () => {
y.style.backgroundColor = "#1890ff", y.style.boxShadow = "none";
}));
}
// 创建全屏切换按钮
createFullscreenToggle() {
const e = B("fullscreenToggle", w.fullscreen, 32, "切换全屏/弹窗模式");
return x(e, this.isDarkMode), e.addEventListener("click", () => {
this.toggleFullscreen();
}), e;
}
// 切换全屏/弹窗模式
toggleFullscreen() {
this.isFullscreen = !this.isFullscreen;
const e = document.getElementById("vue-print-next-previewBox");
if (!e) return;
const t = this.isFullscreen ? {
width: "100%",
height: "100%",
top: "0",
left: "0",
borderRadius: "0",
boxShadow: "0 0 20px rgba(0, 0, 0, 0.1)",
icon: w.fullscreen
} : {
width: "80%",
height: "80%",
top: "10%",
left: "10%",
borderRadius: "8px",
boxShadow: "0 0 30px rgba(0, 0, 0, 0.2)",
icon: w.exitFullscreen
};
e.style.width = t.width, e.style.height = t.height, e.style.top = t.top, e.style.left = t.left, e.style.borderRadius = t.borderRadius, e.style.boxShadow = t.boxShadow;
const o = e.querySelector(".fullscreenToggle");
o && (o.innerHTML = t.icon);
}
// 获取纸张信息文本
getPaperSizeText() {
const { paperSize: e = "A4", orientation: t = "portrait", customSize: o, previewSize: n } = this.settings;
let r = "";
if (n) {
if (typeof n == "string")
r = n;
else if (n.width && n.height) {
const { width: i, height: s, unit: a = "mm" } = n, c = b(i, a), h = b(s, a);
r = `自定义 (${c} × ${h})`;
}
} else if (e === "custom" && o) {
const { width: i, height: s, unit: a = "mm" } = o, c = b(i, a), h = b(s, a);
r = `自定义 (${c} × ${h})`;
} else
r = e;
return `${r} - ${t === "portrait" ? "纵向" : "横向"}`;
}
// 创建预览主体
createPreviewBody() {
const e = document.createElement("div");
e.className = "previewBody", e.style.cssText = "display: flex; flex-direction: column; height: calc(100% - 56px);";
const t = document.createElement("div");
t.className = "previewBodyUtil", t.style.cssText = "height: 48px; background: #fff; position: relative; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; border-bottom: 1px solid #eaeaea; transition: background-color 0.3s ease, border-color 0.3s ease;";
const o = document.createElement("div");
o.className = "leftTools", o.style.cssText = "display: flex; align-items: center; gap: 16px;";
const n = document.createElement("button");
n.className = "previewBodyUtilPrintBtn", n.style.cssText = "padding: 8px 16px; font-size: 14px; color: white; cursor: pointer; background: #1890ff; border: none; border-radius: 4px; transition: all 0.3s ease; outline: none; font-weight: 500; display: flex; align-items: center; justify-content: center;", n.innerHTML = w.print + (this.settings.previewPrintBtnLabel || ""), n.addEventListener("mouseover", () => {
n.style.backgroundColor = "#40a9ff", n.style.boxShadow = "0 2px 8px rgba(24, 144, 255, 0.3)";
}), n.addEventListener("mouseout", () => {
n.style.backgroundColor = "#1890ff", n.style.boxShadow = "none";
}), o.appendChild(n);
const r = document.createElement("div");
r.className = "paperInfo", r.style.cssText = "font-size: 13px; color: #666; display: flex; align-items: center; transition: color 0.3s ease;", r.innerHTML = w.paper + this.getPaperSizeText(), o.appendChild(r), t.appendChild(o), e.appendChild(t);
const i = document.createElement("div");
i.className = "contentContainer", i.style.cssText = "flex: 1; padding: 24px; overflow: auto; background: #f5f5f5; display: flex; justify-content: center; transition: background-color 0.3s ease;";
const s = document.createElement("div");
s.className = "paperContainer", s.style.cssText = "background: white; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15); transition: box-shadow 0.3s ease; transform-origin: center top; margin-bottom: 24px;", this.setPaperSize(s);
const a = document.createElement("div");
return a.style.cssText = "width: 100%; height: 100%; overflow: hidden;", s.appendChild(a), i.appendChild(s), e.appendChild(i), e;
}
// 获取纸张尺寸
getPaperDimensions() {
const { paperSize: e = "A4", customSize: t, previewSize: o } = this.settings, n = C.A4;
let r = n.width, i = n.height;
if (o) {
if (typeof o == "string") {
if (o in C) {
const s = C[o];
r = s.width, i = s.height;
}
} else if (o.width && o.height) {
const { width: s, height: a, unit: c = "mm" } = o;
r = b(s, c), i = b(a, c);
}
} else if (e === "custom" && t) {
const { width: s, height: a, unit: c = "mm" } = t;
r = b(s, c), i = b(a, c);
} else if (e in C) {
const s = C[e];
r = s.width, i = s.height;
}
return { width: r, height: i };
}
// 设置纸张尺寸和方向
setPaperSize(e) {
const { orientation: t = "portrait" } = this.settings, { width: o, height: n } = this.getPaperDimensions();
t === "portrait" ? (e.style.width = o, e.style.height = n) : (e.style.width = n, e.style.height = o);
}
/**
* 等待字体加载完成
* @param targetWindow 目标窗口(iframe的contentWindow或当前窗口)
* @returns Promise<void>
*/
waitForFontsLoaded(e) {
return new Promise((t, o) => {
const r = Date.now();
if (e.document.fonts && typeof e.document.fonts.ready == "object")
Promise.race([
e.document.fonts.ready,
new Promise((i, s) => {
setTimeout(() => s(new Error("Font loading timeout")), 3e3);
})
]).then(() => {
t();
}).catch(() => {
o(new Error("Font loading timeout or failed"));
});
else {
const i = () => {
if (Date.now() - r >= 3e3) {
o(new Error("Font loading timeout"));
return;
}
e.document.readyState === "complete" ? setTimeout(() => t(), 200) : setTimeout(i, 50);
};
i();
}
});
}
}
const R = (l, e, t) => {
l.addEventListener ? l.addEventListener(e, t, !1) : l.attachEvent ? l.attachEvent("on" + e, t) : l["on" + e] = t;
}, A = {
directiveName: "print",
// vue3 指定挂载
mounted(l, e) {
let t, o = {};
R(l, "click", () => {
e.value ? typeof e.value == "string" ? t = e.value : typeof e.value == "object" && (t = e.value.el, o = e.value) : t = "body", new W(z(E({}, o), { el: t, vue: e.instance }));
});
},
// 兼容 Vue2 指令挂载
bind(l, e, t) {
e.instance = t.context, A.mounted(l, e);
}
}, Z = {
install(l) {
l.directive(A.directiveName, A);
}
};
export {
W as VuePrintNext,
Z as printPlugin,
A as vPrint
};