AI代码生成工具、个人中心

This commit is contained in:
李琦
2026-08-13 19:04:28 +08:00
parent c45bcde94d
commit 318aeab092
84 changed files with 37648 additions and 56 deletions

View File

@@ -0,0 +1,121 @@
/**
* preload:通过 contextBridge 向渲染层暴露 window.desktopAPI
*
* 运行在 sandbox 沙箱中(只能使用 electron 受限模块 + DOM),
* 所有能力都走 ipcRenderer.invoke 白名单通道,渲染层拿不到任何 Node 能力。
* web 侧的类型声明见 apps/web-antd/src/types/desktop.d.ts(两边字段需保持同步)。
*/
import type { IpcRendererEvent } from 'electron';
import { contextBridge, ipcRenderer } from 'electron';
/**
* 用 canvas 绘制 Windows 任务栏角标覆盖图(红底白字数字),返回 dataURL
* 为什么在 preload 画:主进程没有 DOM canvas,nativeImage 也不能绘制文字;
* preload 跑在渲染进程里有完整 DOM 能力,画完把 dataURL 交给主进程 setOverlayIcon
*/
function drawBadgeDataURL(count: number): string {
const size = 32;
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
if (!ctx) return '';
const text = count > 99 ? '99+' : String(count);
ctx.beginPath();
ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2);
ctx.fillStyle = '#f5222d';
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = `bold ${text.length > 2 ? 13 : 18}px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, size / 2, size / 2 + 1);
return canvas.toDataURL('image/png');
}
/** 订阅主进程推送事件的通用封装,返回取消订阅函数(组件卸载时记得调用防止泄漏) */
function subscribe<T>(
channel: string,
callback: (payload: T) => void,
): () => void {
const listener = (_event: IpcRendererEvent, payload: T) => callback(payload);
ipcRenderer.on(channel, listener);
return () => ipcRenderer.removeListener(channel, listener);
}
const desktopAPI = {
/** 恒为 true,web 侧用它判断是否运行在 Electron 壳内 */
isElectron: true as const,
/** 当前平台:win32 / darwin / linux(渲染层做平台差异 UI 时用) */
platform: process.platform,
/** 应用版本号(package.json version,用于「关于」展示) */
getVersion: (): Promise<string> => ipcRenderer.invoke('desktop:get-version'),
/**
* 发系统通知;route 传入站内路由(如 /notice/list)时点击通知会唤起窗口并跳转
* flash 默认 true:窗口未聚焦时同时闪烁任务栏(mac 表现为 Dock 弹跳)
*/
notify: (options: {
title: string;
body?: string;
route?: string;
silent?: boolean;
flash?: boolean;
}): Promise<void> => ipcRenderer.invoke('desktop:notify', options),
/** 设置未读角标数,0 清除;mac 用 Dock 原生数字,Windows 用 canvas 画的覆盖图 */
setBadge: (count: number): Promise<void> => {
const dataUrl =
count > 0 && process.platform === 'win32' ? drawBadgeDataURL(count) : '';
return ipcRenderer.invoke('desktop:set-badge', count, dataUrl);
},
/** 手动控制任务栏闪烁(一般用不到,notify 已内置) */
flashFrame: (flag = true): Promise<void> =>
ipcRenderer.invoke('desktop:flash-frame', flag),
/** 读取开机自启状态 */
getAutoLaunch: (): Promise<boolean> =>
ipcRenderer.invoke('desktop:get-auto-launch'),
/** 设置开机自启(--hidden 静默启动进托盘) */
setAutoLaunch: (enabled: boolean): Promise<boolean> =>
ipcRenderer.invoke('desktop:set-auto-launch', enabled),
/** 枚举系统打印机列表 */
getPrinters: (): Promise<unknown[]> =>
ipcRenderer.invoke('desktop:get-printers'),
/**
* 打印一段完整 HTML:不传 deviceName 时读持久化的默认打印机;
* silent=true 静默打印,false 弹系统打印对话框
*/
printHTML: (options: {
html: string;
deviceName?: string;
silent?: boolean;
landscape?: boolean;
}): Promise<{ message?: string; success: boolean }> =>
ipcRenderer.invoke('desktop:print-html', options),
/** 读桌面端本地设置(如默认打印机) */
getSetting: (key: string): Promise<unknown> =>
ipcRenderer.invoke('desktop:get-setting', key),
/** 写桌面端本地设置 */
setSetting: (key: string, value: unknown): Promise<void> =>
ipcRenderer.invoke('desktop:set-setting', key, value),
/** 手动检查更新(托盘菜单也有同款入口) */
checkUpdate: (): Promise<void> => ipcRenderer.invoke('desktop:check-update'),
/** 更新下载完成后立即重启安装 */
quitAndInstall: (): Promise<void> =>
ipcRenderer.invoke('desktop:quit-and-install'),
/** 订阅「点击通知要求跳转路由」事件,web 侧收到后自行 router.push */
onNavigate: (callback: (route: string) => void): (() => void) =>
subscribe<string>('desktop:navigate', callback),
/** 订阅更新流程事件(checking / available / progress / downloaded 等),用于展示进度 */
onUpdateEvent: (
callback: (event: { payload?: unknown; type: string }) => void,
): (() => void) =>
subscribe<{ payload?: unknown; type: string }>(
'desktop:update-event',
callback,
),
};
contextBridge.exposeInMainWorld('desktopAPI', desktopAPI);
/** 导出类型供主进程侧引用(仅类型层面,运行时无依赖) */
export type DesktopAPI = typeof desktopAPI;