Files
xk-admin/apps/desktop/src/preload/index.ts

103 lines
5.0 KiB
TypeScript
Raw Normal View History

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,主进程隐藏窗口出纸 */
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<void> => ipcRenderer.invoke('desktop:check-update'),
/** 唤起并聚焦主窗口 */
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));
},
/** 监听更新事件(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;