- 任务右键菜单:实现打开输出目录(inputs/outputs/.cache 结构)、
复制资源链接、重新编辑、再次生成、拉取结果、重新下载、收藏、删除记录
- 主进程下载:通过 Electron net 模块绕过 CORS,支持 CDN 媒体文件下载
- sharp 缩略图:替换 nativeImage,支持 4K/8K+ 大尺寸素材的流式处理
- IPC 精简:40+ 冗余通道 → 15 个分组通道,移除未使用的 preload 桥接方法
- 任务存储:新增收藏字段、schema 迁移、task-store 持久化
- 清理废弃模块:canvas-ipc / updater / request / pricing
refactor: IPC/Preload/Services 模块化拆分 — 单一职责 + 向后兼容
主要变更:
- shared/constants/ipc-channels.ts → ipc/ 模块 (base/storage/canvas/updater)
- electron/preload.ts → preload/ 模块 (base/safe-storage/file-storage/canvas)
- electron/main/ipc/canvas-ipc.ts → canvas/ 模块 (types/walk/sidecar/mime/thumbnail)
- electron/main/ipc/storage-ipc.ts → 提取 utils/ (path/download/thumbnail)
- electron/main/updater.ts → updater/ 模块 (provider/platform-updaters)
- src/services/request.ts → request/ 模块 (token/interceptors/methods)
- src/shared/utils/pricing.ts → pricing/ 模块 (types/helpers/format/calculate/fields)
- src/modules/home/center/views/ModelInputForm.tsx → 提取 utils + DependentFormItem
架构优化:
- 所有拆分均保持向后兼容(旧导入路径仍可用)
- TypeScript 编译通过 ✓
- 每个模块单一职责,平均 ~130 行
169 lines
6.6 KiB
TypeScript
169 lines
6.6 KiB
TypeScript
// ============================================================
|
||
// 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_CANVAS} from '@shared/constants/ipc';
|
||
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, refetch: refetchAnnouncements } = 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);
|
||
refetchAnnouncements();
|
||
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_CANVAS.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, refetchAnnouncements],
|
||
);
|
||
|
||
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}
|
||
/>
|
||
</>
|
||
);
|
||
}
|