99 lines
3.2 KiB
TypeScript
99 lines
3.2 KiB
TypeScript
import React, { useMemo } from 'react';
|
|
import { Avatar, Dropdown, Space } from 'antd';
|
|
import { LogoutOutlined, SettingOutlined, UserOutlined } 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';
|
|
|
|
const extractProjectId = (pathname: string) => {
|
|
const match = pathname.match(/^\/projects\/([^/]+)/);
|
|
return match?.[1] || '';
|
|
};
|
|
|
|
const Header: React.FC = () => {
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
const user = useAuthStore((state) => state.user);
|
|
const logoutMutation = useLogout();
|
|
const projectId = extractProjectId(location.pathname);
|
|
|
|
const navItems = useMemo(() => {
|
|
const projectNav = projectId
|
|
? [
|
|
{ key: 'assets', label: '素材库', path: `/projects/${projectId}/assets` },
|
|
{ key: 'titles', label: '标题库', path: `/projects/${projectId}/titles` },
|
|
{ key: 'voices', label: '配音库', path: `/projects/${projectId}/voices` },
|
|
{ key: 'generation', label: '视频剪辑', path: `/projects/${projectId}/generation` },
|
|
{ key: 'results', label: '成片库', path: `/projects/${projectId}/results` },
|
|
]
|
|
: [];
|
|
|
|
return [
|
|
{ key: 'home', label: '首页', path: '/workspaces' },
|
|
{ key: 'subscription', label: '订阅', path: '/subscription' },
|
|
...projectNav,
|
|
];
|
|
}, [projectId]);
|
|
|
|
const menuItems: MenuProps['items'] = [
|
|
{
|
|
key: 'profile',
|
|
icon: <UserOutlined />,
|
|
label: '个人设置',
|
|
onClick: () => navigate('/profile'),
|
|
},
|
|
{
|
|
key: 'settings',
|
|
icon: <SettingOutlined />,
|
|
label: '账号设置',
|
|
onClick: () => navigate('/profile/settings'),
|
|
},
|
|
{ type: 'divider' },
|
|
{
|
|
key: 'logout',
|
|
icon: <LogoutOutlined />,
|
|
label: '退出登录',
|
|
onClick: () => logoutMutation.mutateAsync(),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<header className="xx-top-nav">
|
|
<div className="xx-top-nav-inner">
|
|
<button className="xx-brand" type="button" onClick={() => navigate('/workspaces')}>
|
|
<span className="xx-logo">🦐</span>
|
|
<span>小虾自动剪辑</span>
|
|
</button>
|
|
|
|
<nav className="xx-nav-links">
|
|
{navItems.map((item) => {
|
|
const active = item.path === '/workspaces'
|
|
? location.pathname === '/' || location.pathname.startsWith('/workspaces')
|
|
: location.pathname.startsWith(item.path);
|
|
return (
|
|
<button
|
|
key={item.key}
|
|
className={active ? 'active' : ''}
|
|
type="button"
|
|
onClick={() => navigate(item.path)}
|
|
>
|
|
{item.label}
|
|
</button>
|
|
);
|
|
})}
|
|
</nav>
|
|
|
|
<Dropdown menu={{ items: menuItems }} placement="bottomRight">
|
|
<Space className="xx-user-menu">
|
|
<Avatar className="xx-avatar" icon={<UserOutlined />} />
|
|
<span>{user?.display_name || user?.username || '小虾用户'}</span>
|
|
</Space>
|
|
</Dropdown>
|
|
</div>
|
|
</header>
|
|
);
|
|
};
|
|
|
|
export default Header;
|