Files
ele-HeiXiu/electron/main.ts
YoungestSongMo 8b697e775d feat: 去除构建时版控变量 + 窗口标题动态版控 + 退出登录保留缓存 (0.0.24)
版控体系重构(13 文件):
- 移除 build.mjs --personal/--enterprise 参数和 EDITION 环境变量
- 统一安装包文件名:船长-HeiXiu-{os}-{版本号}-Setup.{ext}
- 简化 package.json 构建脚本,移除 :enterprise 变体
- scripts/*.mjs 不再接受 edition 参数
- 移除 shared/constants/app.ts 的 parseEdition()

窗口标题动态版控:
- 新格式:船长-HeiXiu-{os}-{版本号}(登录前)/ ·{版控} 后缀(登录后)
- 链路:AppProvider(account_type) → useEffect → appRuntime IPC → main process
- 移除 updater.ts 更新检查 API 的 edition 查询参数

本地存储生命周期修正:
- 退出登录不再自动清空缓存(user_id 隔离,重新登录命中缓存)
- clearUserStorage 添加 isOpen() 竞态守卫
- 移除 closeDatabase() 避免退出→重新登录后数据库不可用
- 手动「清理缓存」按钮保留

文档:
- CHANGELOG.md 新增 0.0.24 条目
- TODO.md 新增已完成项
2026-06-22 13:36:42 +08:00

191 lines
6.4 KiB
TypeScript

import {app, BrowserWindow, ipcMain, dialog, session} 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} from '../shared/constants/app';
import {loadEnvFile} from './main/load-env';
import {initUpdater, registerUpdateIpcHandlers} from './main/updater';
import {setupAppMenu} from './main/menu';
import {initLogger, flushLogger, logger} from './main/logger';
import {registerLogIpcHandlers} from './main/ipc/log-ipc';
import {registerSafeStorageIpcHandlers} from './main/ipc/safe-storage-ipc';
import {registerStorageIpcHandlers} from './main/ipc/storage-ipc';
import {BIDIRECTIONAL, RENDERER_TO_MAIN} from '../shared/constants/ipc-channels';
createRequire(import.meta.url);
const __dirname = path.dirname(fileURLToPath(import.meta.url));
process.env.APP_ROOT = path.join(__dirname, '..');
// 加载 .env 文件中的环境变量到 process.env
// 必须在任何读取 process.env.UPDATE_API_URL 的模块之前调用
loadEnvFile();
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();
/** 基础标题(无版控后缀),登录后通过 IPC 追加 */
const WINDOW_TITLE = buildWindowTitle(currentPlatform);
// ============================================================
// 全局未捕获异常(主进程兜底记录)
// ============================================================
process.on('uncaughtException', (error) => {
try {
logger.error('app', 'Uncaught exception (main process)', error);
} catch {
/* logger 自身异常 — 最后防线 */
}
});
process.on('unhandledRejection', (reason) => {
try {
const error = reason instanceof Error ? reason : new Error(String(reason));
logger.error('app', 'Unhandled rejection (main process)', error);
} catch {
/* logger 自身异常 — 最后防线 */
}
});
// ============================================================
// 窗口引用
// ============================================================
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', () => {
flushLogger();
});
app.whenReady().then(() => {
initLogger();
registerLogIpcHandlers();
registerSafeStorageIpcHandlers();
registerStorageIpcHandlers();
setupAppMenu();
app.setName(WINDOW_TITLE);
registerUpdateIpcHandlers();
const fakeUserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36';
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
const url = details.url;
if (url.includes("rh-images-1252422369.cos.ap-beijing.myqcloud.com")) {
details.requestHeaders['User-Agent'] = fakeUserAgent;
}
callback({
requestHeaders: details.requestHeaders
});
});
// 文件对话框 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'],
});
});
// 窗口标题版控后缀(渲染进程登录/登出后调用)
ipcMain.on(RENDERER_TO_MAIN.SET_WINDOW_TITLE, (_event, edition: string | null) => {
if (!mainWindow) return;
const title = edition
? buildWindowTitle(currentPlatform, undefined, edition as 'personal' | 'enterprise')
: buildWindowTitle(currentPlatform);
mainWindow.setTitle(title);
});
// 始终打开主窗口
// 登录/注册由渲染进程内的 Modal 弹层处理,不再新开窗口
const win = createMainWindow();
initUpdater(win);
});