0fcb77b991
CI Build & Deploy Pipeline / Staging E2E Tests (push) Failing after 2m50s
CI Build & Deploy Pipeline / Production Browser E2E (push) Failing after 1437h19m24s
CI Build & Deploy Pipeline / Build Production Web Image (push) Failing after 1437h19m26s
CI Build & Deploy Pipeline / Deploy Production (push) Failing after 1437h19m25s
CI Build & Deploy Pipeline / Build Production API Image (push) Failing after 1437h19m26s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1437h21m16s
CI Build & Deploy Pipeline / Build Staging API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Failing after 1437h51m29s
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
128 lines
3.8 KiB
TypeScript
128 lines
3.8 KiB
TypeScript
/**
|
|
* Phase 1 Header 重构
|
|
* 扁平化导航菜单 + 手机端汉堡菜单
|
|
*/
|
|
import React, { useState } from "react"
|
|
import { Avatar, Dropdown, Drawer, Space } from "antd"
|
|
import { LogoutOutlined, SettingOutlined, UserOutlined, MenuOutlined } from "@ant-design/icons"
|
|
import { useLocation, useNavigate } from "react-router-dom"
|
|
import { useAuthStore } from "@/store/authStore"
|
|
import { useLogout } from "@/hooks/useAuth"
|
|
import type { MenuProps } from "antd"
|
|
import { NAV_ITEMS } from "@/config/navigation"
|
|
import "./Header.css"
|
|
|
|
const Header: React.FC = () => {
|
|
const navigate = useNavigate()
|
|
const location = useLocation()
|
|
const user = useAuthStore((state) => state.user)
|
|
const logoutMutation = useLogout()
|
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
|
|
|
/** 用户下拉菜单 */
|
|
const menuItems: MenuProps["items"] = [
|
|
{
|
|
key: "profile",
|
|
icon: <UserOutlined />,
|
|
label: "个人设置",
|
|
onClick: () => navigate("/app/profile"),
|
|
},
|
|
{
|
|
key: "subscription",
|
|
icon: <SettingOutlined />,
|
|
label: "订阅管理",
|
|
onClick: () => navigate("/app/subscription"),
|
|
},
|
|
{ type: "divider" },
|
|
{
|
|
key: "logout",
|
|
icon: <LogoutOutlined />,
|
|
label: "退出登录",
|
|
onClick: () => logoutMutation.mutateAsync(),
|
|
},
|
|
]
|
|
|
|
/** 判断导航项是否激活 */
|
|
const isActive = (path: string) => {
|
|
// 首页特殊处理:/ 和 /app/dashboard 都算激活
|
|
if (path === "/app/dashboard") {
|
|
return (
|
|
location.pathname === "/" ||
|
|
location.pathname === "/app" ||
|
|
location.pathname === "/app/dashboard"
|
|
)
|
|
}
|
|
return location.pathname.startsWith(path)
|
|
}
|
|
|
|
return (
|
|
<header className="xx-top-nav">
|
|
<div className="xx-top-nav-inner">
|
|
<button className="xx-brand" type="button" onClick={() => navigate("/app/dashboard")}>
|
|
<span className="xx-logo">🦐</span>
|
|
<span className="xx-brand-text">小虾自动剪辑</span>
|
|
</button>
|
|
|
|
{/* 桌面端导航 */}
|
|
<nav className="xx-nav-links">
|
|
{NAV_ITEMS.map((item) => (
|
|
<button
|
|
key={item.key}
|
|
className={isActive(item.path) ? "active" : ""}
|
|
type="button"
|
|
onClick={() => navigate(item.path)}
|
|
>
|
|
{item.label}
|
|
</button>
|
|
))}
|
|
</nav>
|
|
|
|
<div className="xx-right-section">
|
|
{/* 手机端汉堡菜单按钮 */}
|
|
<button className="xx-hamburger" type="button" onClick={() => setMobileMenuOpen(true)}>
|
|
<MenuOutlined />
|
|
</button>
|
|
|
|
<Dropdown menu={{ items: menuItems }} placement="bottomRight">
|
|
<Space className="xx-user-menu">
|
|
<Avatar className="xx-avatar" icon={<UserOutlined />} />
|
|
<span className="xx-username">
|
|
{user?.display_name || user?.username || "小虾用户"}
|
|
</span>
|
|
</Space>
|
|
</Dropdown>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 手机端侧边抽屉导航 */}
|
|
<Drawer
|
|
title="导航菜单"
|
|
placement="left"
|
|
onClose={() => setMobileMenuOpen(false)}
|
|
open={mobileMenuOpen}
|
|
width={260}
|
|
className="xx-mobile-drawer"
|
|
>
|
|
<div className="xx-mobile-nav">
|
|
{NAV_ITEMS.map((item) => (
|
|
<button
|
|
key={item.key}
|
|
className={`xx-mobile-nav-item ${isActive(item.path) ? "active" : ""}`}
|
|
type="button"
|
|
onClick={() => {
|
|
navigate(item.path)
|
|
setMobileMenuOpen(false)
|
|
}}
|
|
>
|
|
<span className="xx-mobile-nav-icon">{item.icon}</span>
|
|
<span>{item.label}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</Drawer>
|
|
</header>
|
|
)
|
|
}
|
|
|
|
export default Header
|