feat: 初始化船长·HeiXiu 桌面应用项目(错误是svg的问题)
Electron + React 19 + TypeScript + Ant Design 6 + Tailwind CSS 4 【核心架构】 - Electron 主进程(窗口管理、平台检测、图标适配) - Vite 构建链(双产物 dist/ + dist-electron/) - 共享层 shared/(类型、常量、工具函数) - IPC 通信框架 + 安全守卫 【主题系统】 - 蓝紫渐变主题(亮色/暗色双模式) - Ant Design ConfigProvider 适配(lightAlgorithm / darkAlgorithm) - Tailwind CSS 4 @theme 指令 + CSS 变量 - 设计令牌三处同步(tokens.ts / globals.css / antd-theme.ts) 【导航栏】 - 自定义 H5 导航栏替代系统菜单 - 个人版/企业版双布局 - 未登录拦截 + 事件总线驱动登录弹窗 【登录注册】 - Modal 弹层(Portal)+ Tabs 切换 - 登录/注册表单动态字段渲染 - 记住密码 / 自动登录 / 用户协议 【更新系统】 - 自定义 API 版本检查(替代 electron-updater generic provider) - 手动下载 + SHA512 校验 + spawn NSIS 安装 - 渲染进程更新状态机 Hook(useUpdater) - 检查更新按钮 + 下载进度展示 【构建工具链】 - build.mjs 构建总管(--win/--mac/--linux --personal/--enterprise --build/--clean/--sign/--upload/--publish) - scripts/ 自动化脚本(clean / generate-update-info / upload-oss / publish-release) - electron-builder NSIS 安装器(可选路径、多版本打包) - update-info.json 自动生成(fileSize/sha512/changelog/releaseDate) 【代码质量】 - ESLint + TypeScript strict + Prettier - husky + lint-staged(提交前自动检查) - commitlint(规范提交信息) - Playwright E2E + Vitest 单元测试框架 - rollup-plugin-visualizer 打包体积分析 【文档】 - README.md(快速开始 + 目录结构 + 命令速查) - UpdateA.md(发布手册:API/签名/发版流程/数据库) - CHANGELOG.md(更新日志) - .env.example(构建环境变量模板)
This commit is contained in:
135
src/components/navbar/NavBar.tsx
Normal file
135
src/components/navbar/NavBar.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
// ============================================================
|
||||
// NavBar — 自定义顶部导航栏(替代系统菜单)
|
||||
//
|
||||
// 逻辑:
|
||||
// - 所有导航项默认全部显示(不再按登录状态隐藏)
|
||||
// - 未登录时点击需登录的项 → 弹出提示"请先登录"并触发登录 Modal
|
||||
// - 登录/注册 与 退出登录 仍按登录状态互斥显示
|
||||
// ============================================================
|
||||
|
||||
import { useState, useCallback, useMemo } from 'react';
|
||||
import { App } from 'antd';
|
||||
|
||||
import { useAppContext } from '@/contexts/app-context';
|
||||
import { useTheme } from '@/hooks/use-theme';
|
||||
import { emit, EVENTS } from '@/utils/event-bus.ts';
|
||||
import { NavItem } from './NavItem';
|
||||
import { AnnouncementDrawer } from './AnnouncementDrawer';
|
||||
import { personalLeft, personalRight, enterpriseLeft, enterpriseRight } from './nav-config';
|
||||
import type { NavItemConfig } from './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 { 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 = 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);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'button':
|
||||
if (item.key === 'logout') {
|
||||
logout();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'external':
|
||||
if (item.target) {
|
||||
window.open(item.target, '_blank');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'navigate':
|
||||
case 'spa':
|
||||
if (item.key === 'auth') {
|
||||
emit(EVENTS.SHOW_LOGIN);
|
||||
} else {
|
||||
console.log('[NavBar] 导航至:', item.target);
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
[isLoggedIn, logout, message],
|
||||
);
|
||||
|
||||
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} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="navbar-no-drag flex items-center gap-0.5">
|
||||
{visibleRight.map((item) => (
|
||||
<NavItem key={item.key} item={item} onClick={handleNavClick} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AnnouncementDrawer open={drawerOpen} onClose={() => setDrawerOpen(false)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user