202 lines
5.9 KiB
Vue
202 lines
5.9 KiB
Vue
/**
|
||
* 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<PrintDotPrinter[]> {
|
||
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<string, unknown> = {
|
||
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 || '';
|
||
}
|