Files
xk-admin/apps/desktop/src/preload/index.ts
李琦 beba5e8feb feat: 订单双视图、工作台日历组件、医生排班管理与挂号语音提醒
- 订单双视图:商品订单/处方/挂号列表新增卡片视图(CardList)、视图切换组件 view-mode-switch、stat-islands 统计岛、constants 字典
- 工作台:新增工作日历 Widget、待办日历、即将到访预约、公告滚动 NoticeTicker、日历面板 CalendarPanel
- 医生排班:新增排班 API、ScheduleDrawer 抽屉、schedule-calendar 组件、门店设置弹窗
- 日志与通知:新增排班变更日志页、排班变更通知视图
- 挂号提醒:新增挂号语音播报资源与 register-notify 工具
- 桌面端:新增 apps/desktop 壳及 desktop 工具方法
- 其他:处方/订单导出、聊天设置与 WebSocket 等小幅优化
2026-08-14 17:46:50 +08:00

103 lines
5.0 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 提供的受限 APIcontextBridge / ipcRenderer 等)
*/
/**
* Windows 任务栏角标图:用 canvas 画「红底白字」数字返回 dataURL
* 为什么在 preload 画:主进程没有 DOM/canvaspreload 运行在渲染进程内有 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;