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 => ipcRenderer.invoke('desktop:get-version'), /** 发送系统通知;route 为点击通知后要跳转的前端路由 */ notify: (options: { title: string; body: string; route?: string }): Promise => ipcRenderer.invoke('desktop:notify', options), /** 设置未读角标数量(0 表示清除);Windows 角标图在此侧 canvas 生成 */ setBadge: (count: number): Promise => ipcRenderer.invoke('desktop:set-badge', { count, image: count > 0 && process.platform === 'win32' ? drawBadgeImage(count) : undefined, }), /** 任务栏闪烁提醒(窗口未聚焦时生效,聚焦后自动清除) */ flashFrame: (): Promise => ipcRenderer.invoke('desktop:flash-frame'), /** 读取开机自启状态 */ getAutoLaunch: (): Promise => ipcRenderer.invoke('desktop:get-auto-launch'), /** 设置开机自启,返回设置后的实际状态 */ setAutoLaunch: (enable: boolean): Promise => ipcRenderer.invoke('desktop:set-auto-launch', enable), /** 获取系统打印机列表(打印设置下拉用) */ getPrinters: (): Promise> => ipcRenderer.invoke('desktop:get-printers'), /** 静默打印:传入拼好的完整 HTML,主进程隐藏窗口出纸 */ printHTML: (options: { html: string; deviceName?: string; silent?: 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 => ipcRenderer.invoke('desktop:check-update'), /** 唤起并聚焦主窗口 */ focusWindow: (): Promise => ipcRenderer.invoke('desktop:focus-window'), /** 监听「通知点击跳转」事件:主进程通知被点击后回传目标路由,渲染层执行 router.push */ onNavigate: (callback: (route: string) => void): void => { ipcRenderer.on('desktop:navigate', (_event, route: string) => callback(route)); }, /** 监听更新事件(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;