新增PrintDot桥接功能,支持本地打印机连接和配置,优化打印模板设计,允许多页表格重复显示,改进打印预览和设计器界面,确保用户体验流畅。
This commit is contained in:
201
jeecgboot-vue3/src/views/print/template/utils/printDotBridge.ts
Normal file
201
jeecgboot-vue3/src/views/print/template/utils/printDotBridge.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* 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 || '';
|
||||
}
|
||||
Reference in New Issue
Block a user