## 新增功能 ### 设置页面(Router + Modal 叠加) - HashRouter 路由系统,Electron file:// 协议兼容 - 设置页以居中 Modal 叠加首页,仅 X 按钮关闭 - 主题切换 Segmented 控件(与首页 Switch 共享 Context) - 大模型产物存储仓库路径配置(原生文件夹选择对话框) - 企业版额外:团队创作存储仓库路径 - 系统信息展示(平台/版本/版本类型/环境) - 版本更新检查(状态机:idle→checking→no-update/available→downloading→downloaded→error) ### JWT 认证体系 - 双 Token 机制:access_token + refresh_token - Axios 拦截器 401 自动刷新 + 并发请求去重队列 - 启动时 refresh_token 静默续期(自动登录) - 记住密码:Base64 编码存储,表单自动回填 - setTokenRefreshHandler 解耦模式:request.ts 不知晓 auth ### 自定义错误类型系统 - RefreshError 继承 RequestError,type = AUTH_EXPIRED - 全局拦截器捕获 RefreshError → 自动 clearToken + AUTH_REQUIRED - 任何位置 throw new RefreshError() 即可触发登录 Modal ### 公告卡片优化 - 长文本 Tooltip 悬浮预览 - 点击卡片弹出详情 Modal(全文保留换行) ### SettingsContext(类似 Vue Pinia) - 集中式设置状态管理 - localStorage 持久化:ele-heixiu-output-path / ele-heixiu-team-repo-path - useSettings() 全局消费 ## 架构变更 - App.tsx → HashRouter + AppRoutes + LoginPage 弹层 - Layout.tsx = NavBar + <Outlet /> 页面壳 - HomePage.tsx 从 App.tsx 提取 - NavBar 使用 useNavigate() 真实路由导航 - useUpdater 改为模块级单例 IPC 监听器(修复 MaxListenersExceededWarning) - electron/main.ts 新增 FILE_DIALOG IPC handler ## 依赖 - 新增 react-router-dom
138 lines
4.2 KiB
TypeScript
138 lines
4.2 KiB
TypeScript
import { app, BrowserWindow, ipcMain, dialog } from 'electron';
|
|
import { createRequire } from 'node:module';
|
|
import { fileURLToPath } from 'node:url';
|
|
import path from 'node:path';
|
|
|
|
import { getPlatform, isMacOS } from './main/utils/platform';
|
|
import { getWindowIconPath } from './main/utils/logo';
|
|
import { buildWindowTitle, parseEdition } from '../shared/constants/app';
|
|
import { initUpdater, registerUpdateIpcHandlers } from './main/updater';
|
|
import { setupAppMenu } from './main/menu';
|
|
import { BIDIRECTIONAL } from '../shared/constants/ipc-channels';
|
|
|
|
createRequire(import.meta.url);
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
process.env.APP_ROOT = path.join(__dirname, '..');
|
|
|
|
export const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL'];
|
|
export const MAIN_DIST = path.join(process.env.APP_ROOT, 'dist-electron');
|
|
export const RENDERER_DIST = path.join(process.env.APP_ROOT, 'dist');
|
|
|
|
process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL
|
|
? path.join(process.env.APP_ROOT, 'public')
|
|
: RENDERER_DIST;
|
|
|
|
// ---------- 平台 & 版本信息 ----------
|
|
const currentPlatform = getPlatform();
|
|
const currentEdition = parseEdition(process.env.EDITION);
|
|
const WINDOW_TITLE = buildWindowTitle(currentPlatform, currentEdition);
|
|
|
|
// ============================================================
|
|
// 窗口引用
|
|
// ============================================================
|
|
|
|
let mainWindow: BrowserWindow | null = null;
|
|
|
|
// ============================================================
|
|
// 主窗口
|
|
// ============================================================
|
|
|
|
function createMainWindow(): BrowserWindow {
|
|
const iconPath = getWindowIconPath(app.getAppPath());
|
|
|
|
mainWindow = new BrowserWindow({
|
|
title: WINDOW_TITLE,
|
|
icon: iconPath,
|
|
width: 1280,
|
|
height: 800,
|
|
minWidth: 960,
|
|
minHeight: 600,
|
|
show: false,
|
|
...(isMacOS() ? { titleBarStyle: 'hiddenInset' as const } : {}),
|
|
webPreferences: {
|
|
preload: path.join(__dirname, 'preload.mjs'),
|
|
contextIsolation: true,
|
|
nodeIntegration: false,
|
|
},
|
|
});
|
|
|
|
mainWindow.setTitle(WINDOW_TITLE);
|
|
|
|
if (VITE_DEV_SERVER_URL) {
|
|
mainWindow.webContents.openDevTools();
|
|
}
|
|
|
|
// macOS 全屏事件
|
|
if (isMacOS()) {
|
|
mainWindow.on('enter-full-screen', () =>
|
|
mainWindow?.webContents.send('window-fullscreen-changed', true),
|
|
);
|
|
mainWindow.on('leave-full-screen', () =>
|
|
mainWindow?.webContents.send('window-fullscreen-changed', false),
|
|
);
|
|
}
|
|
|
|
mainWindow.webContents.on('did-finish-load', () => {
|
|
mainWindow?.webContents.send('main-process-message', new Date().toLocaleString());
|
|
mainWindow?.webContents.send('platform-info', {
|
|
platform: currentPlatform,
|
|
edition: currentEdition,
|
|
});
|
|
});
|
|
|
|
// ready-to-show 后显示,避免白屏闪烁
|
|
mainWindow.once('ready-to-show', () => {
|
|
mainWindow?.show();
|
|
});
|
|
|
|
if (VITE_DEV_SERVER_URL) {
|
|
mainWindow.loadURL(VITE_DEV_SERVER_URL);
|
|
} else {
|
|
mainWindow.loadFile(path.join(RENDERER_DIST, 'index.html'));
|
|
}
|
|
|
|
return mainWindow;
|
|
}
|
|
|
|
// ============================================================
|
|
// 应用生命周期
|
|
// ============================================================
|
|
|
|
app.on('window-all-closed', () => {
|
|
if (!isMacOS()) {
|
|
app.quit();
|
|
}
|
|
});
|
|
|
|
app.on('activate', () => {
|
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|
createMainWindow();
|
|
}
|
|
});
|
|
|
|
app.on('before-quit', () => {
|
|
// 清理工作
|
|
});
|
|
|
|
app.whenReady().then(() => {
|
|
setupAppMenu();
|
|
app.setName(WINDOW_TITLE);
|
|
registerUpdateIpcHandlers();
|
|
|
|
// 文件对话框 IPC 处理(供渲染进程选择文件夹)
|
|
ipcMain.handle(BIDIRECTIONAL.FILE_DIALOG, async (_event, options: Electron.OpenDialogOptions) => {
|
|
if (!mainWindow) return { canceled: true, filePaths: [] };
|
|
return dialog.showOpenDialog(mainWindow, {
|
|
title: options?.title || '选择文件夹',
|
|
defaultPath: options?.defaultPath || app.getPath('home'),
|
|
properties: options?.properties || ['openDirectory'],
|
|
});
|
|
});
|
|
|
|
// 始终打开主窗口
|
|
// 登录/注册由渲染进程内的 Modal 弹层处理,不再新开窗口
|
|
const win = createMainWindow();
|
|
initUpdater(win);
|
|
});
|