/** * PrintDot 本地桥接器(Wails 客户端)WebSocket 协议封装。 * 默认地址 ws://127.0.0.1:1122/ws ,与 PrintDot-Client 中 Bridge 默认端口一致。 */ const LS_WS_URL = 'qhmes_print_dot_ws_url'; const LS_KEY = 'qhmes_print_dot_key'; export type PrintDotPrinter = { name: string; isDefault?: boolean }; export function getPrintDotBridgeConfig() { return { wsUrl: localStorage.getItem(LS_WS_URL) || 'ws://127.0.0.1:1122/ws', key: localStorage.getItem(LS_KEY) || '', }; } export function setPrintDotBridgeConfig(wsUrl: string, key: string) { localStorage.setItem(LS_WS_URL, (wsUrl || '').trim()); localStorage.setItem(LS_KEY, key ?? ''); } function buildWsUrl(wsUrl: string, key: string) { const base = (wsUrl || '').trim(); const k = (key || '').trim(); if (!k) { return base; } const sep = base.includes('?') ? '&' : '?'; return `${base}${sep}key=${encodeURIComponent(k)}`; } /** 连接后服务端会先推送 printer_list,本方法读取该列表后关闭连接 */ export function fetchPrintDotPrinters(timeoutMs = 8000): Promise { const { wsUrl, key } = getPrintDotBridgeConfig(); return new Promise((resolve, reject) => { let done = false; const ws = new WebSocket(buildWsUrl(wsUrl, key)); const timer = window.setTimeout(() => { if (done) { return; } done = true; try { ws.close(); } catch { // ignore } reject(new Error('连接 PrintDot 超时,请确认客户端已启动且 WebSocket 地址正确')); }, timeoutMs); const finish = (fn: () => void) => { if (done) { return; } done = true; window.clearTimeout(timer); try { ws.close(); } catch { // ignore } fn(); }; ws.onmessage = (ev) => { try { const data = JSON.parse(ev.data as string); if (data?.type === 'printer_list' && Array.isArray(data.data)) { const list = data.data .map((p: any) => ({ name: String(p?.name || '').trim(), isDefault: p?.isDefault === true, })) .filter((p: PrintDotPrinter) => !!p.name); finish(() => resolve(list)); } } catch { // ignore } }; ws.onerror = () => { finish(() => reject(new Error('无法连接 PrintDot WebSocket,请检查地址与客户端是否运行'))); }; }); } /** 将 PrintDot 返回的英文错误转为带处理步骤的提示(便于运维排查) */ function enhancePrintDotErrorMessage(raw: string): string { const m = String(raw || '').trim(); if (/SumatraPDF\.exe not found/i.test(m) || /SUMATRAPDF_PATH/i.test(m)) { return `${m}。本地处理:PrintDot 依赖 SumatraPDF 静默打印 PDF。请安装 Sumatra PDF 后任选其一:将 SumatraPDF.exe 放在 PrintDot 客户端 exe 同目录;或将 Sumatra 安装目录加入系统 PATH;或设置用户/系统环境变量 SUMATRAPDF_PATH 指向 SumatraPDF.exe 的完整路径,然后重启 PrintDot。`; } return m; } /** * 发送 PDF 打印任务(content 为 Base64 PDF,可为纯 Base64 或 data:application/pdf;base64, 前缀) * 连接建立后服务端可能先推送 printer_list,需忽略直至收到 status。 */ export function printDotSendPdf(params: { printer: string; pdfBase64: string; jobName?: string; copies?: number; timeoutMs?: number; }): Promise<{ ok: boolean; message: string }> { const { wsUrl, key } = getPrintDotBridgeConfig(); const timeout = params.timeoutMs ?? 180000; return new Promise((resolve, reject) => { let settled = false; const ws = new WebSocket(buildWsUrl(wsUrl, key)); const timer = window.setTimeout(() => { if (settled) { return; } settled = true; try { ws.close(); } catch { // ignore } reject(new Error('PrintDot 打印等待结果超时')); }, timeout); const finishOk = (ok: boolean, message: string) => { if (settled) { return; } settled = true; window.clearTimeout(timer); try { ws.close(); } catch { // ignore } resolve({ ok, message }); }; ws.onopen = () => { let content = String(params.pdfBase64 || '').trim(); if (content.startsWith('data:')) { const idx = content.indexOf(','); if (idx !== -1) { content = content.slice(idx + 1); } } const payload: Record = { printer: String(params.printer || '').trim(), content, job: { name: String(params.jobName || 'QH-MES').trim() || 'QH-MES', copies: Math.max(1, Number(params.copies) || 1), }, }; ws.send(JSON.stringify(payload)); }; ws.onmessage = (ev) => { try { const data = JSON.parse(ev.data as string); if (data?.type === 'printer_list') { return; } if (data?.status) { const ok = data.status === 'success'; const rawMsg = String(data.message || ''); finishOk(ok, ok ? rawMsg : enhancePrintDotErrorMessage(rawMsg)); } } catch { finishOk(false, 'PrintDot 返回无法解析'); } }; ws.onerror = () => { if (settled) { return; } settled = true; window.clearTimeout(timer); try { ws.close(); } catch { // ignore } reject(new Error('PrintDot WebSocket 错误')); }; }); } /** 将列表页的「系统默认」或空选择解析为桥接器返回的默认打印机名称 */ export function resolvePrintDotPrinterName(selectedValue: string, printers: PrintDotPrinter[]): string { const s = String(selectedValue || '').trim(); if (s && s !== '__system_default__') { return s; } const def = printers.find((p) => p.isDefault); return def?.name || printers[0]?.name || ''; }