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

@@ -0,0 +1,167 @@
// ============================================================
// NavBar — 自定义顶部导航栏(替代系统菜单)
//
// 逻辑:
// - 所有导航项默认全部显示(不再按登录状态隐藏)
// - 未登录时点击需登录的项 → 弹出提示"请先登录"并触发登录 Modal
// - 登录/注册 与 退出登录 仍按登录状态互斥显示
// ============================================================
import {useState, useCallback, useMemo} from 'react';
import {App} from 'antd';
import {useAppContext} from '@/app/providers/AppProvider';
import {useTheme} from '@/app/providers/ThemeProvider';
import {emit, EVENTS, safeIpcSend} from '@/shared/utils/bus';
import {RENDERER_TO_MAIN} from '@shared/constants/ipc-channels';
import {useAnnouncements} from '@/shared/hooks/use-announcements';
import {NavItem} from './NavItem';
import {AnnouncementDrawer} from '@/modules/announcement/views/AnnouncementDrawer';
import {personalLeft, personalRight, enterpriseLeft, enterpriseRight} from '../settings/nav-config';
import type {NavItemConfig} from '../model/types';
const NAVBAR_HEIGHT = 56;
export function NavBar() {
const {platform, edition, isLoggedIn, logout} = useAppContext();
const {isDark} = useTheme();
const {message} = App.useApp();
const [drawerOpen, setDrawerOpen] = useState(false);
// 公告轮询 + 未读缓存
const { announcements, unreadCount, loading: announcementsLoading, markAsRead } = useAnnouncements();
// 根据版本选择导航项
const {leftItems, rightItems} = useMemo(() => {
const isEnterprise = edition === 'enterprise';
return {
leftItems: isEnterprise ? enterpriseLeft : personalLeft,
rightItems: isEnterprise ? enterpriseRight : personalRight,
};
}, [edition]);
// 过滤规则:
// requireGuest → 仅未登录时可见(登录/注册)
// "退出登录" → 仅已登录时可见
// 其他所有项 → 始终可见,未登录点击时弹"请先登录"
const visibleLeft = useMemo(
() =>
leftItems.filter((item) => {
if (item.requireGuest && isLoggedIn) return false;
return !(item.key === 'logout' && !isLoggedIn);
}),
[leftItems, isLoggedIn],
);
const visibleRight: NavItemConfig[] = useMemo(
() =>
rightItems.filter((item) => {
if (item.requireGuest && isLoggedIn) return false;
return !(item.key === 'logout' && !isLoggedIn);
}),
[rightItems, isLoggedIn],
);
// 点击导航项
const handleNavClick = useCallback(
(item: NavItemConfig) => {
// 需要登录但未登录 → 提示并弹出登录 Modal
if (item.requireLogin && !isLoggedIn) {
message.warning('请先登录');
emit(EVENTS.SHOW_LOGIN);
return;
}
switch (item.action) {
case 'drawer':
if (item.key === 'announcements') {
setDrawerOpen(true);
markAsRead();
}
break;
case 'button':
if (item.key === 'logout') {
logout();
} else if (item.key === 'settings') {
emit(EVENTS.OPEN_SETTINGS);
} else if (item.key === 'auth') {
emit(EVENTS.SHOW_LOGIN);
}
break;
case 'external':
if (item.target) {
window.open(item.target, '_blank');
}
break;
case 'page':
// 项目管理 → 打开独立画布窗口(新 BrowserWindow非主窗口内叠加层
if (item.key === 'project-management') {
console.log('[NavBar] 🖼️ 点击"项目管理" → 发送 OPEN_CANVAS_WINDOW IPC');
safeIpcSend(RENDERER_TO_MAIN.OPEN_CANVAS_WINDOW);
return;
}
if (item.target) {
emit(EVENTS.OPEN_PAGE, {
target: item.target,
modalType: item.modalType ?? 'floating',
label: item.label,
});
}
break;
}
},
[isLoggedIn, logout, message, markAsRead],
);
const macPaddingLeft = platform === 'darwin' ? 80 : 12;
return (
<>
<div
className="navbar-drag-region flex items-center justify-between select-none"
style={{
height: NAVBAR_HEIGHT,
paddingLeft: macPaddingLeft,
paddingRight: 12,
background: isDark
? 'linear-gradient(180deg, #1E1B4B 0%, #1A1730 100%)'
: 'linear-gradient(180deg, #FFFFFF 0%, #F5F3FF 100%)',
borderBottom: `1px solid ${isDark ? '#2E2A4A' : '#E5E7EB'}`,
boxShadow: isDark ? '0 1px 4px rgba(0,0,0,0.3)' : '0 1px 4px rgba(99,102,241,0.06)',
}}
>
<div className="navbar-no-drag flex items-center gap-0.5">
{visibleLeft.map((item) => (
<NavItem
key={item.key}
item={item}
onClick={handleNavClick}
badgeCount={item.key === 'announcements' ? unreadCount : undefined}
/>
))}
</div>
<div className="navbar-no-drag flex items-center gap-0.5">
{visibleRight.map((item) => (
<NavItem
key={item.key}
item={item}
onClick={handleNavClick}
badgeCount={item.key === 'announcements' ? unreadCount : undefined}
/>
))}
</div>
</div>
<AnnouncementDrawer
open={drawerOpen}
onClose={() => setDrawerOpen(false)}
announcements={announcements}
loading={announcementsLoading}
/>
</>
);
}