Files
xiaoxia-saas/apps/web/src/components/layout/Header.tsx
T
DevOps Bot b86a9c2af2
CI/CD Pipeline / Deploy Staging (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Failing after 75h41m6s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 75h41m16s
chore: 统一代码格式 - black + prettier 全量格式化
2026-07-06 09:13:03 +08:00

141 lines
3.9 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;