feat: 订单双视图、工作台日历组件、医生排班管理与挂号语音提醒

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

View File

@@ -0,0 +1,122 @@
/**
* 桌面端(Electron)能力封装
* 桌面端由 apps/desktop 的 preload 脚本注入 window.desktopAPI;
* 浏览器环境没有该对象,本封装的所有方法自动 no-op 降级,保证网页端行为完全不变。
* 业务代码禁止直接访问 window.desktopAPI,统一走本文件,便于类型收敛与降级兜底。
*/
/** 系统打印机信息(Electron getPrintersAsync 的子集) */
export interface DesktopPrinter {
name: string;
displayName: string;
isDefault: boolean;
}
/** 桌面端本地设置(与 apps/desktop 主进程 settings.ts 保持同步) */
export interface DesktopSettings {
/** 静默打印使用的打印机名称,空串表示系统默认打印机 */
printerName: string;
/** 是否启用静默打印 */
silentPrint: boolean;
}
/** 自动更新事件(主进程 updater 转发,渲染层做轻量提示) */
export interface DesktopUpdateEvent {
type: 'available' | 'downloaded' | 'error' | 'not-available' | 'progress';
data?: unknown;
}
/** 判断当前是否运行在 Electron 桌面端 */
export function isElectron(): boolean {
return typeof window !== 'undefined' && !!window.desktopAPI?.isElectron;
}
/** 取桥接 API:浏览器环境返回 undefined,调用方全部可选链降级 */
function api() {
return typeof window === 'undefined' ? undefined : window.desktopAPI;
}
// onNavigate / onUpdateEvent 的桥接层每调用一次就会新增一个 ipc 监听,
// 而布局组件会随登录/登出反复挂载。这里用「模块级单次桥接 + 可替换回调」避免监听器堆积
let navigateCallback: ((route: string) => void) | null = null;
let navigateBridged = false;
let updateCallback: ((payload: DesktopUpdateEvent) => void) | null = null;
let updateBridged = false;
export const desktop = {
/** 发送系统通知;route 为点击通知后要跳转的前端路由(不传则仅唤起窗口) */
notify(options: { body: string; route?: string; title: string }): void {
api()?.notify(options);
},
/** 设置未读角标数量,0 表示清除(mac Dock 数字 / Windows 任务栏覆盖图标) */
setBadge(count: number): void {
api()?.setBadge(count);
},
/** 任务栏闪烁提醒(窗口未聚焦时生效,聚焦后自动清除) */
flashFrame(): void {
api()?.flashFrame();
},
/** 读取开机自启状态(浏览器环境恒为 false) */
async getAutoLaunch(): Promise<boolean> {
return (await api()?.getAutoLaunch()) ?? false;
},
/** 设置开机自启,返回设置后的实际状态 */
async setAutoLaunch(enable: boolean): Promise<boolean> {
return (await api()?.setAutoLaunch(enable)) ?? false;
},
/** 获取系统打印机列表(浏览器环境返回空数组) */
async getPrinters(): Promise<DesktopPrinter[]> {
return (await api()?.getPrinters()) ?? [];
},
/** 静默打印:传入拼好的完整 HTML,由主进程隐藏窗口出纸 */
async printHTML(options: {
deviceName?: string;
html: string;
silent?: boolean;
}): Promise<{ message?: string; ok: boolean }> {
return (
(await api()?.printHTML(options)) ?? {
ok: false,
message: '当前环境不支持桌面打印',
}
);
},
/** 读取桌面端设置 */
async getSettings(): Promise<DesktopSettings> {
return (await api()?.getSettings()) ?? { printerName: '', silentPrint: false };
},
/** 增量更新桌面端设置,返回更新后的完整设置 */
async setSettings(patch: Partial<DesktopSettings>): Promise<DesktopSettings> {
return (
(await api()?.setSettings(patch)) ?? { printerName: '', silentPrint: false }
);
},
/** 手动检查更新(结果由主进程弹窗反馈) */
checkUpdate(): void {
api()?.checkUpdate();
},
/** 唤起并聚焦主窗口 */
focusWindow(): void {
api()?.focusWindow();
},
/** 监听系统通知点击后的路由跳转请求(重复调用只替换回调,不会堆积监听器) */
onNavigate(callback: (route: string) => void): void {
navigateCallback = callback;
if (!navigateBridged && api()) {
navigateBridged = true;
api()!.onNavigate((route) => navigateCallback?.(route));
}
},
/** 解绑路由跳转回调:布局卸载(登出)时调用,避免残留系统通知被点击后对已登出会话执行跳转 */
offNavigate(): void {
navigateCallback = null;
},
/** 监听自动更新事件(重复调用只替换回调,不会堆积监听器) */
onUpdateEvent(callback: (payload: DesktopUpdateEvent) => void): void {
updateCallback = callback;
if (!updateBridged && api()) {
updateBridged = true;
api()!.onUpdateEvent((payload) => updateCallback?.(payload as DesktopUpdateEvent));
}
},
};