Files
xk-admin/apps/desktop/src/preload/index.ts
李琦 50fc55925f feat: 地区选择器改走接口、订单市价重算与资金流水、同步日志内容复用
region-address-picker 接 region-picker API;订单支持按进价重算 market_price、追踪页资金流水;MES/ERP/中台同步日志共用内容查看组件;地区管理支持 pid 下钻与拼音检索。
2026-10-15 10:01:20 +08:00

113 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { contextBridge, ipcRenderer } from 'electron';
/**
* 预加载脚本:通过 contextBridge 向渲染层暴露受控的 desktopAPI
* - 渲染层(web-antd)通过 window.desktopAPI 使用桌面能力;浏览器环境没有该对象,
* 前端封装层(apps/web-antd/src/util/desktop.ts)会自动 no-op 降级
* - sandbox 模式下本文件必须是 CJS 产物(electron-vite 已按包配置输出 CJS),
* 且只能使用 Electron 提供的受限 API(contextBridge / ipcRenderer 等)
*/
/**
* Windows 任务栏角标图:用 canvas 画「红底白字」数字返回 dataURL
* 为什么在 preload 画:主进程没有 DOM/canvas;preload 运行在渲染进程内有 DOM,
* 由主进程 setOverlayIcon 消费,这是 Windows 数字角标的社区标准做法
*/
function drawBadgeImage(count: number): string | undefined {
try {
const size = 32;
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
if (!ctx) return undefined;
// 红色圆底
ctx.beginPath();
ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2);
ctx.fillStyle = '#f5222d';
ctx.fill();
// 白色数字,超过 99 显示 99+
const text = count > 99 ? '99+' : String(count);
ctx.fillStyle = '#ffffff';
ctx.font = `bold ${text.length > 2 ? 13 : 18}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, size / 2, size / 2 + 1);
return canvas.toDataURL('image/png');
} catch {
// canvas 不可用时退化为无图(主进程会清除角标),不影响业务
return undefined;
}
}
/** 暴露给渲染层的桌面能力集合,字段语义与 web 侧 desktop.ts 封装一一对应 */
const desktopAPI = {
/** 渲染层判断是否运行在桌面端的唯一标记 */
isElectron: true as const,
/** 当前平台:win32 / darwin / linux */
platform: process.platform,
/** 获取桌面端版本号(关于/设置展示用) */
getVersion: (): Promise<string> => ipcRenderer.invoke('desktop:get-version'),
/** 发送系统通知;route 为点击通知后要跳转的前端路由 */
notify: (options: { title: string; body: string; route?: string }): Promise<boolean> =>
ipcRenderer.invoke('desktop:notify', options),
/** 设置未读角标数量(0 表示清除);Windows 角标图在此侧 canvas 生成 */
setBadge: (count: number): Promise<void> =>
ipcRenderer.invoke('desktop:set-badge', {
count,
image: count > 0 && process.platform === 'win32' ? drawBadgeImage(count) : undefined,
}),
/** 任务栏闪烁提醒(窗口未聚焦时生效,聚焦后自动清除) */
flashFrame: (): Promise<void> => ipcRenderer.invoke('desktop:flash-frame'),
/** 读取开机自启状态 */
getAutoLaunch: (): Promise<boolean> => ipcRenderer.invoke('desktop:get-auto-launch'),
/** 设置开机自启,返回设置后的实际状态 */
setAutoLaunch: (enable: boolean): Promise<boolean> =>
ipcRenderer.invoke('desktop:set-auto-launch', enable),
/** 获取系统打印机列表(打印设置下拉用) */
getPrinters: (): Promise<Array<{ name: string; displayName: string; isDefault: boolean }>> =>
ipcRenderer.invoke('desktop:get-printers'),
/** 静默打印:传入拼好的完整 HTML,主进程隐藏窗口出纸;可带 pageSize(微米)指定热敏纸等 */
printHTML: (options: {
html: string;
deviceName?: string;
silent?: boolean;
pageSize?: { width: number; height: number } | string;
preferCSSPageSize?: boolean;
}): Promise<{ ok: boolean; message?: string }> =>
ipcRenderer.invoke('desktop:print-html', options),
/** 读取桌面端设置(打印机名称、静默打印开关等) */
getSettings: (): Promise<{ printerName: string; silentPrint: boolean }> =>
ipcRenderer.invoke('desktop:get-settings'),
/** 增量更新桌面端设置,返回更新后的完整设置 */
setSettings: (
patch: Partial<{ printerName: string; silentPrint: boolean }>,
): Promise<{ printerName: string; silentPrint: boolean }> =>
ipcRenderer.invoke('desktop:set-settings', patch),
/** 手动检查更新(结果由主进程弹窗反馈) */
checkUpdate: (): Promise<void> => ipcRenderer.invoke('desktop:check-update'),
/** 刷新主窗口页面 */
reload: (): Promise<void> => ipcRenderer.invoke('desktop:reload'),
/** 关于弹窗 */
showAbout: (): Promise<void> => ipcRenderer.invoke('desktop:show-about'),
/** 唤起并聚焦主窗口 */
focusWindow: (): Promise<void> => ipcRenderer.invoke('desktop:focus-window'),
/** 监听「通知点击跳转」事件:主进程通知被点击后回传目标路由,渲染层执行 router.push */
onNavigate: (callback: (route: string) => void): void => {
ipcRenderer.on('desktop:navigate', (_event, route: string) => callback(route));
},
/** 监听托盘「切换账号」:主进程唤起窗口后发此事件,渲染层打开 SwitchAccountModal */
onOpenSwitchAccount: (callback: () => void): void => {
ipcRenderer.on('desktop:open-switch-account', () => callback());
},
/** 监听更新事件(available / progress / downloaded / not-available / error),渲染层做轻量提示 */
onUpdateEvent: (callback: (payload: { type: string; data?: unknown }) => void): void => {
ipcRenderer.on('desktop:update-event', (_event, payload) => callback(payload));
},
};
contextBridge.exposeInMainWorld('desktopAPI', desktopAPI);
/** 供 web 侧类型声明引用(apps/web-antd/src/types/desktop.d.ts 与此保持同步) */
export type DesktopAPI = typeof desktopAPI;