- 订单双视图:商品订单/处方/挂号列表新增卡片视图(CardList)、视图切换组件 view-mode-switch、stat-islands 统计岛、constants 字典 - 工作台:新增工作日历 Widget、待办日历、即将到访预约、公告滚动 NoticeTicker、日历面板 CalendarPanel - 医生排班:新增排班 API、ScheduleDrawer 抽屉、schedule-calendar 组件、门店设置弹窗 - 日志与通知:新增排班变更日志页、排班变更通知视图 - 挂号提醒:新增挂号语音播报资源与 register-notify 工具 - 桌面端:新增 apps/desktop 壳及 desktop 工具方法 - 其他:处方/订单导出、聊天设置与 WebSocket 等小幅优化
103 lines
5.0 KiB
TypeScript
103 lines
5.0 KiB
TypeScript
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;
|