import './libs/jquery'; import { ElDialog, ElSelect, ElOption, ElButton, ElMessageBox, ElLoading, ElMessage, } from 'element-plus'; import {createApp, createVNode} from 'vue'; import {Router} from "vue-router" import {printerClient} from './libs/printerClient'; import PrinterFloatingBall from './component/PrinterFloatingBall.vue'; import { hiprint, defaultElementTypeProvider, print, print2, usePaper, useScale, useDataSource, } from './hooks/useHiprint'; export {hiprint, defaultElementTypeProvider, print, print2, usePaper, useScale, useDataSource}; export {default as fontSize} from './font-size'; export {default as scale} from './scale'; export {default as zIndex} from './z-index'; export {default as panel} from './panel'; export {default as hiprintVue} from './hiprint.vue'; export {default as defaultPrintElement} from './store/defaultPrintElement'; export {getPrintTemplate}; import {getPrintTemplate} from './libs/index.js'; import axios from "@/libs/api.request.js" export function guid(): string { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (t) { var e = (16 * Math.random()) | 0; return ('x' == t ? e : (3 & e) | 8).toString(16); }); } // 定义类型 interface HiprintFunParams { code: string; params?: any; data?: any; isCustom?: boolean; returnHtml?: boolean; } interface SilentHiprintParams { code: string; params?: any; data?: any; } // fetchAndRender 及相关类型已抽到 libs/fetchAndRender.ts(纯逻辑,不依赖 UI 库,桌面/移动共用) export {fetchAndRender} from './libs/fetchAndRender'; export type {FetchAndRenderParams, FetchAndRenderResult} from './libs/fetchAndRender'; import {fetchAndRender} from './libs/fetchAndRender'; interface CustomWindow extends Window { silentHiprint: typeof silentHiprint; } export function cLog(string: string, isError: boolean = false): void { if (isError) { console.error( '%cyhHiprint:%c', 'font-size: 16px;font-weight: bold;color: #00ffff', 'font-size: 16px;font-weight: bold;color: #ccccc', string ); } else { console.log( '%cyhHiprint%c ' + string, 'font-size: 18px;font-weight: bold;color: #61AFEF', 'font-size: 12px;color: #999' ); } } /** * 浏览器打印($hiprint) * 弹出新窗口渲染 HTML,调用 window.print() 触发浏览器原生打印 */ const hiprintFun = async ({ code, params, data, isCustom, returnHtml, }: HiprintFunParams): Promise => { const height: number = document.documentElement.clientHeight; const width: number = 1200; const left: number = (document.documentElement.clientWidth - 1200) / 2; // 转换数组 let paramData: any = params; if (!Array.isArray(params)) { paramData = [params]; } let url: string = '/hiprint/#/preview?code=' + encodeURIComponent(code); if (params) { url += `¶ms=${encodeURIComponent(JSON.stringify(paramData))}`; } if (data) { url += `&data=${encodeURIComponent(JSON.stringify(data))}`; } if (isCustom) { url += `&isCustom=${isCustom ? '1' : '0'}`; } if (returnHtml) { url += `&returnHtml=${returnHtml ? '1' : '0'}`; } const windowOpen = window.open( url, 'hiprintWindow', `height=${height}, width=${width}, top=20, left=${left}, toolbar=no, menubar=no, scrollbars=no, resizable=no, location=no, status=no` ); return new Promise((resolve, reject) => { if (!windowOpen) { reject('预览窗口渲染失败!'); return; } function printHTMLHandler(e: MessageEvent): void { const {type, data} = e.data as any; if (type === 'sendPrintHTML' && data) { resolve(data); } window.removeEventListener('message', printHTMLHandler); } window.addEventListener('message', printHTMLHandler); }); }; /** * 静默渲染($silentHiprint) * 仅渲染 HTML 字符串返回,不触发打印,供业务自定义使用 */ const silentHiprint = async ({ code, params = {}, data, }: SilentHiprintParams): Promise => { if (!code || !data) { ElMessageBox.alert('请正确的传递模板编号和打印数据', '提示'); return false; } let loading: any = null; try { loading = ElLoading.service({ text: '正在获取打印模板', }); const result = await fetchAndRender({code, params, data, onAlert: (msg) => ElMessageBox.alert(msg, '提示')}); if (result && result.html) { return result.html; } ElMessageBox.alert('打印内容渲染失败', '提示'); } catch (error) { console.error(error); } finally { if (loading) { loading.close(); loading = null; } } return false; }; (window as unknown as CustomWindow).silentHiprint = silentHiprint; interface InstallOptions { router?: Router | undefined; pinia?: any; isAdmin?: boolean; /** 打印服务(WebSocket)地址,默认 'ws://localhost:17891';集中部署填 nginx 反代的 ws 地址 */ printerHost?: string; /** 是否显示右下角打印机配置悬浮球(连接状态/选择打印机/重连),默认 false */ enablePrinterSetting?: boolean; } /** * 服务端打印($serverPrint) * 获取数据 + 渲染 HTML → 通过原生 WebSocket(经 nginx 反代)发送到 yh-printer-service 静默打印 * 全局常驻 ws 连接(install 时建立),前端弹框选打印机 */ const createServerPrint = () => { return async (code: string, params: any = {}): Promise => { let loading: any = null; try { loading = ElLoading.service({ text: '正在获取打印模板', }); // 1. 兼容历史调用:从 params 取出 data(支持 params.data 嵌套传递) const data = params.data; const fetchParams = {...params}; delete fetchParams.data; // 2. 获取数据 + 渲染 HTML const result = await fetchAndRender({code, params: fetchParams, data, onAlert: (msg) => ElMessageBox.alert(msg, '提示')}); if (!result) { loading?.close(); return false; } if (!result.hasData) { loading?.close(); ElMessageBox.alert('数据源没有数据,打印将取消', '提示'); return false; } // 2. 校验打印服务连接(printerClient 单例,install 时已 connect) if (printerClient.status.value !== 'connected') { // 未连接时尝试等一下重连;仍未连接则提示 loading?.close(); ElMessageBox.alert('打印服务未连接,请检查打印客户端是否运行', '提示'); return false; } // 3. 取打印机列表(单例响应式数据,已含轮询刷新);为空则主动拉一次 let printers = printerClient.printers.value; if (printers.length === 0) { printers = (await printerClient.getPrinters()) || []; } if (printers.length === 0) { loading?.close(); ElMessageBox.alert('未获取到打印机列表', '提示'); return false; } loading.close(); loading = null; // 4. 选择打印机:开启「使用默认打印机」则跳过选择弹框,直接用当前选中打印机;否则弹框选择 const useDefault = localStorage.getItem('hiprint-use-default-printer') === '1'; let selectedPrinter: string | undefined; if (useDefault) { // 直接用悬浮球里选中的打印机(单例已自动选中系统默认打印机,用户也可切换) selectedPrinter = printerClient.selectedPrinter.value || undefined; } else { const picked = await showPrinterSelectionDialog(printers); if (!picked) { return false; } selectedPrinter = picked; } // 5. 发送打印任务(走 printerClient 单例) loading = ElLoading.service({ text: '正在发送打印任务', }); const printRes = await printerClient.printHtml({ html: result.html, printerName: selectedPrinter, copies: 1, title: result.name, pageSize: result.pageSize, }); loading?.close(); if (printRes?.success) { ElMessage.success('打印任务已发送到打印机'); return true; } else { ElMessageBox.alert('打印失败: ' + (printRes?.message || '未知错误'), '提示'); return false; } } catch (error) { if (loading) { loading.close(); } console.error(error); ElMessageBox.alert('服务端打印出错: ' + (error as Error).message, '提示'); return false; } }; }; /** 服务端打印($serverPrint 的实例,可直接 import 调用) */ const serverPrint = createServerPrint(); /** * 显示打印机选择对话框,返回选中的打印机名(取消则返回 false) */ const showPrinterSelectionDialog = (printerList: any[]): Promise => { return new Promise((resolve) => { // 默认选中复用单例 selectedPrinter(已含 lastSelectedPrinter 校验 + fallback 默认/第一个) const lastSelectedPrinter = printerClient.selectedPrinter.value || ''; const mountPoint = document.createElement('div'); document.body.appendChild(mountPoint); const PrinterSelectorComponent = { components: { ElDialog, ElSelect, ElOption, ElButton, }, data() { return { visible: true, selectedPrinter: lastSelectedPrinter, printerList: printerList, }; }, methods: { handleConfirm(this: any) { if (!this.selectedPrinter) { ElMessageBox.alert('请选择打印机', '提示'); return; } // 通过单例持久化(写 lastSelectedPrinter),与悬浮球共享选中状态 printerClient.setSelectedPrinter(this.selectedPrinter); this.handleClose(this.selectedPrinter); }, handleClose(this: any, result: string | false) { this.visible = false; app.unmount(); document.body.removeChild(mountPoint); resolve(result); }, }, render() { return createVNode( ElDialog, { modelValue: this.visible, title: '选择打印机', width: '500px', onClose: () => this.handleClose(false), onClosed: () => this.handleClose(false), }, { default: () => [ createVNode( ElSelect, { modelValue: this.selectedPrinter, 'onUpdate:modelValue': (val: string) => { this.selectedPrinter = val; }, placeholder: '请选择打印机', style: 'width: 100%; margin-bottom: 15px;', }, { default: () => this.printerList.map((printer: any) => createVNode(ElOption, { key: printer.name, label: printer.name, value: printer.name, }) ), } ), ], footer: () => [ createVNode('span', {class: 'dialog-footer'}, [ createVNode( ElButton, { onClick: () => this.handleClose(false), }, { default: () => '取消', } ), createVNode( ElButton, { type: 'primary', onClick: this.handleConfirm, }, { default: () => '打印', } ), ]), ], } ); }, }; const app = createApp(PrinterSelectorComponent); app.mount(mountPoint); }); }; // ===== 打印服务 WebSocket 连接管理 ===== // 已迁移到 libs/printerClient.ts 的 printerClient 单例(install 时 connect), // 悬浮球 / showPrinterSelectionDialog / serverPrint 共享同一实例。 export default { install( app: any, config?: InstallOptions ): void { const printerHost = config?.printerHost || 'ws://localhost:17891'; // 建立全局常驻 WebSocket 连接(printerClient 单例,悬浮球/serverPrint 共享) if (typeof window !== 'undefined') { printerClient.connect(printerHost); } app.provide('$hiprint', hiprintFun); app.provide('$silentHiprint', silentHiprint); app.provide('$serverPrint', serverPrint); const isNew = app.config.globalProperties.$isNew; if(isNew) { const {$addInitTask:addInitTask,$router:router} = app.config.globalProperties addInitTask(async () => { if (!router || !router.hasRoute('Index')) return; router.addRoute('Index', { path: 'hiprint/designer', name: '打印设计器', meta: { icon: 'md-planet', title: '打印设计器', }, component: () => import('./HiprintDesigner.vue'), }); router.addRoute({ path: '/preview', name: 'printView', meta: { icon: 'md-planet', title: 'printView', }, component: () => import('./hiprintPreview.vue'), }); }) } else { const {router} = config if (router) { router.addRoute('Index', { path: 'hiprint/designer', name: '打印设计器', meta: { icon: 'md-planet', title: '打印设计器', }, component: () => import('./HiprintDesigner.vue'), }); router.addRoute({ path: '/preview', name: 'printView', meta: { icon: 'md-planet', title: 'printView', }, component: () => import('./hiprintPreview.vue'), }); } else { if (!router) { cLog('没有传递 router 对象,所以无法将路由添加到应用中', true); } } } // 悬浮球打印机配置组件(常驻挂载一次) if (config?.enablePrinterSetting && typeof document !== 'undefined') { const mountPoint = document.createElement('div'); document.body.appendChild(mountPoint); createApp(PrinterFloatingBall).mount(mountPoint); } }, }; // 具名导出(可直接 import 调用,不必走 inject) export {hiprintFun, silentHiprint, serverPrint};