feat: 构建系统重构 + VCHSM 架构迁移 + 右键菜单修复 + 尺寸参数统一
## 构建、打包与分发系统重构 (build/) - 构建系统收敛至 build/ 目录:build.mjs + electron-builder.js + 6 个脚本 - electron-builder 配置从 package.json 提取为 build/electron-builder.js - 新增 --edition (personal/enterprise) 和 --channel (stable/beta/alpha) 参数 - OSS 路径按渠道隔离 - 新增 pre-build-check.mjs:Git/CHANGELOG/版本/环境变量校验 - 新增 bump-version.mjs:版本号管理 + CHANGELOG 自动生成 - updater.ts 发送 channel/edition/deviceFingerprint - 新增 src/shared/utils/rollout.ts 灰度测试工具 - 新增 10 条 NPM 脚本 ## VCHSM 五层架构迁移 - 7 个业务模块 + ~500 处导入路径同步 ## 修复 - MediaCardContextMenu 改用共享 ContextMenu 组件 - ComboBox 尺寸参数显示统一 (size-format.ts) ## 文档 - UpdateA.md / build/README.md / README.md / .env.example 更新 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
100
electron/main.ts
100
electron/main.ts
@@ -9,10 +9,10 @@ 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 {initLogger, flushLogger, logger, setLogDirResolver} 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 {registerStorageIpcHandlers, setDataDirResolver} from './main/ipc/storage-ipc';
|
||||
import {registerCanvasIpcHandlers} from './main/ipc/canvas-ipc';
|
||||
import {BIDIRECTIONAL, RENDERER_TO_MAIN, MAIN_TO_RENDERER} from '../shared/constants/ipc-channels';
|
||||
|
||||
@@ -229,16 +229,17 @@ app.on('before-quit', () => {
|
||||
});
|
||||
|
||||
app.whenReady().then(() => {
|
||||
// ── 存储路径配置 ──
|
||||
// 修改以下两行即可将日志/数据存储到任意位置
|
||||
setLogDirResolver(() => path.join(app.getPath('userData'), 'logs'));
|
||||
setDataDirResolver(() => path.join(app.getPath('userData'), 'heixiu-data'));
|
||||
|
||||
initLogger();
|
||||
registerLogIpcHandlers();
|
||||
registerSafeStorageIpcHandlers();
|
||||
registerStorageIpcHandlers();
|
||||
registerCanvasIpcHandlers();
|
||||
|
||||
// 注册 local-media:// 自定义协议(用于全屏预览加载本地文件)
|
||||
// 支持两种 URL 格式:
|
||||
// 本地路径:local-media:///C:/Users/... (authority 为空,路径以 / 开头)
|
||||
// UNC 路径:local-media://192.168.110.250/share/... (authority 为服务器主机名)
|
||||
protocol.handle('local-media', (request) => {
|
||||
const urlObj = new URL(request.url);
|
||||
let filePath: string;
|
||||
@@ -264,7 +265,7 @@ app.whenReady().then(() => {
|
||||
// 否则会干扰 Chromium 内部的 file:// 协议处理,导致请求失败。
|
||||
const rangeHeader = request.headers.get('Range');
|
||||
if (rangeHeader) {
|
||||
return net.fetch(fileUrl, { headers: { Range: rangeHeader } });
|
||||
return net.fetch(fileUrl, {headers: {Range: rangeHeader}});
|
||||
}
|
||||
return net.fetch(fileUrl);
|
||||
});
|
||||
@@ -285,51 +286,64 @@ app.whenReady().then(() => {
|
||||
|
||||
// 文件对话框 IPC 处理(供渲染进程选择文件夹)
|
||||
ipcMain.handle(BIDIRECTIONAL.FILE_DIALOG, async (_event, options: Electron.OpenDialogOptions) => {
|
||||
const parentWindow = mainWindow && !mainWindow.isDestroyed() ? mainWindow : canvasWindow;
|
||||
if (!parentWindow) return {canceled: true, filePaths: []};
|
||||
return dialog.showOpenDialog(parentWindow, {
|
||||
title: options?.title || '选择文件夹',
|
||||
defaultPath: options?.defaultPath || app.getPath('home'),
|
||||
properties: options?.properties || ['openDirectory'],
|
||||
});
|
||||
try {
|
||||
const parentWindow = mainWindow && !mainWindow.isDestroyed() ? mainWindow : canvasWindow;
|
||||
if (!parentWindow) return {canceled: true, filePaths: []};
|
||||
return dialog.showOpenDialog(parentWindow, {
|
||||
title: options?.title || '选择文件夹',
|
||||
defaultPath: options?.defaultPath || app.getPath('home'),
|
||||
properties: options?.properties || ['openDirectory'],
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn('ipc', '文件对话框打开失败', err instanceof Error ? err : undefined);
|
||||
return {canceled: true, filePaths: []};
|
||||
}
|
||||
});
|
||||
|
||||
// 窗口标题版控后缀(渲染进程登录/登出后调用)
|
||||
ipcMain.on(RENDERER_TO_MAIN.SET_WINDOW_TITLE, (_event, edition: string | null) => {
|
||||
currentEdition = edition;
|
||||
if (!mainWindow) return;
|
||||
const title = edition
|
||||
? buildWindowTitle(currentPlatform, undefined, edition as 'personal' | 'enterprise')
|
||||
: buildWindowTitle(currentPlatform);
|
||||
mainWindow.setTitle(title);
|
||||
try {
|
||||
currentEdition = edition;
|
||||
if (!mainWindow) return;
|
||||
const title = edition
|
||||
? buildWindowTitle(currentPlatform, undefined, edition as 'personal' | 'enterprise')
|
||||
: buildWindowTitle(currentPlatform);
|
||||
mainWindow.setTitle(title);
|
||||
} catch (err) {
|
||||
logger.warn('ipc', '设置窗口标题失败', err instanceof Error ? err : undefined);
|
||||
}
|
||||
});
|
||||
|
||||
// 打开无限画布窗口(渲染进程请求 → 主进程创建新 BrowserWindow)
|
||||
ipcMain.on(RENDERER_TO_MAIN.OPEN_CANVAS_WINDOW, (_event, payload?: { projectPath?: string }) => {
|
||||
const existingAlive = canvasWindow && !canvasWindow.isDestroyed();
|
||||
logger.info('canvas-window', '收到 OPEN_CANVAS_WINDOW IPC', {
|
||||
projectPath: payload?.projectPath || '(未指定)',
|
||||
existingAlive,
|
||||
});
|
||||
try {
|
||||
const existingAlive = canvasWindow && !canvasWindow.isDestroyed();
|
||||
logger.info('canvas-window', '收到 OPEN_CANVAS_WINDOW IPC', {
|
||||
projectPath: payload?.projectPath || '(未指定)',
|
||||
existingAlive,
|
||||
});
|
||||
|
||||
if (existingAlive) {
|
||||
logger.info('canvas-window', '画布窗口已存在,聚焦现有窗口');
|
||||
canvasWindow?.focus();
|
||||
} else {
|
||||
logger.info('canvas-window', '画布窗口不存在或已销毁,创建新窗口');
|
||||
canvasWindow = createCanvasWindow();
|
||||
}
|
||||
// 画布页面加载完成后,发送初始化参数
|
||||
if (payload?.projectPath) {
|
||||
const readyHandler = () => {
|
||||
logger.info('canvas-window', '画布页面 did-finish-load,发送 CANVAS_READY', {
|
||||
projectPath: payload.projectPath,
|
||||
});
|
||||
canvasWindow?.webContents.send(MAIN_TO_RENDERER.CANVAS_READY, {
|
||||
projectPath: payload.projectPath,
|
||||
});
|
||||
};
|
||||
canvasWindow?.webContents.on('did-finish-load', readyHandler);
|
||||
if (existingAlive) {
|
||||
logger.info('canvas-window', '画布窗口已存在,聚焦现有窗口');
|
||||
canvasWindow?.focus();
|
||||
} else {
|
||||
logger.info('canvas-window', '画布窗口不存在或已销毁,创建新窗口');
|
||||
canvasWindow = createCanvasWindow();
|
||||
}
|
||||
// 画布页面加载完成后,发送初始化参数
|
||||
if (payload?.projectPath) {
|
||||
const readyHandler = () => {
|
||||
logger.info('canvas-window', '画布页面 did-finish-load,发送 CANVAS_READY', {
|
||||
projectPath: payload.projectPath,
|
||||
});
|
||||
canvasWindow?.webContents.send(MAIN_TO_RENDERER.CANVAS_READY, {
|
||||
projectPath: payload.projectPath,
|
||||
});
|
||||
};
|
||||
canvasWindow?.webContents.on('did-finish-load', readyHandler);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn('canvas-window', '打开画布窗口失败', err instanceof Error ? err : undefined);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user