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:
2026-06-29 18:41:07 +08:00
parent e0d91335c7
commit 337d6c1205
187 changed files with 5252 additions and 1702 deletions

View File

@@ -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);
}
});