2026-03-19 01:39:34 +08:00
|
|
|
// 全局布局组件 - 包含左侧菜单和头部
|
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
import { Layout, Menu, Typography, Avatar, Dropdown, Badge, Space } from 'antd';
|
|
|
|
|
import {
|
|
|
|
|
DashboardOutlined,
|
|
|
|
|
ShoppingOutlined,
|
|
|
|
|
FileTextOutlined,
|
|
|
|
|
UserOutlined,
|
|
|
|
|
TruckOutlined,
|
|
|
|
|
AlertOutlined,
|
|
|
|
|
AuditOutlined,
|
|
|
|
|
DollarOutlined,
|
|
|
|
|
BarChartOutlined,
|
|
|
|
|
SettingOutlined,
|
|
|
|
|
TeamOutlined,
|
|
|
|
|
MenuFoldOutlined,
|
|
|
|
|
MenuUnfoldOutlined,
|
|
|
|
|
BellOutlined,
|
|
|
|
|
DownOutlined,
|
|
|
|
|
ShopOutlined,
|
|
|
|
|
ScheduleOutlined,
|
|
|
|
|
WalletOutlined,
|
|
|
|
|
SafetyCertificateOutlined,
|
|
|
|
|
GlobalOutlined,
|
|
|
|
|
TrophyOutlined,
|
2026-03-19 14:19:01 +08:00
|
|
|
RobotOutlined,
|
|
|
|
|
SwapOutlined,
|
|
|
|
|
ThunderboltOutlined,
|
2026-03-19 01:39:34 +08:00
|
|
|
} from '@ant-design/icons';
|
|
|
|
|
import { Link, useLocation, history, Outlet } from 'umi';
|
|
|
|
|
|
|
|
|
|
const { Header, Sider, Content } = Layout;
|
|
|
|
|
const { Title, Text } = Typography;
|
|
|
|
|
|
|
|
|
|
// 菜单项配置
|
|
|
|
|
const menuItems = [
|
2026-03-19 14:19:01 +08:00
|
|
|
// 核心业务
|
2026-03-19 01:39:34 +08:00
|
|
|
{
|
2026-03-19 14:19:01 +08:00
|
|
|
key: 'core',
|
2026-03-19 01:39:34 +08:00
|
|
|
icon: <ShoppingOutlined />,
|
2026-03-19 14:19:01 +08:00
|
|
|
label: '核心业务',
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
key: '/Product',
|
|
|
|
|
label: <Link to="/Product">商品管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: '/Orders',
|
|
|
|
|
label: <Link to="/Orders">订单管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: '/Logistics',
|
|
|
|
|
label: <Link to="/Logistics">物流管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: '/AfterSales',
|
|
|
|
|
label: <Link to="/AfterSales">售后服务</Link>,
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-03-19 01:39:34 +08:00
|
|
|
},
|
2026-03-19 14:19:01 +08:00
|
|
|
// 商户与贸易
|
2026-03-19 01:39:34 +08:00
|
|
|
{
|
2026-03-19 14:19:01 +08:00
|
|
|
key: 'merchant',
|
2026-03-19 01:39:34 +08:00
|
|
|
icon: <ShopOutlined />,
|
2026-03-19 14:19:01 +08:00
|
|
|
label: '商户与贸易',
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
key: '/Merchant',
|
|
|
|
|
label: <Link to="/Merchant">商户管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: '/B2B',
|
|
|
|
|
label: <Link to="/B2B">B2B贸易</Link>,
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-03-19 01:39:34 +08:00
|
|
|
},
|
2026-03-19 14:19:01 +08:00
|
|
|
// 营销与增长
|
2026-03-19 01:39:34 +08:00
|
|
|
{
|
2026-03-19 14:19:01 +08:00
|
|
|
key: 'marketing',
|
2026-03-19 01:39:34 +08:00
|
|
|
icon: <BarChartOutlined />,
|
2026-03-19 14:19:01 +08:00
|
|
|
label: '营销与增长',
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
key: '/Ad',
|
|
|
|
|
label: <Link to="/Ad">广告管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: '/DynamicPricing',
|
|
|
|
|
label: <Link to="/DynamicPricing">动态定价</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: '/StrategyMarketplace',
|
|
|
|
|
label: <Link to="/StrategyMarketplace">策略市场</Link>,
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-03-19 01:39:34 +08:00
|
|
|
},
|
2026-03-19 14:19:01 +08:00
|
|
|
// 数据分析
|
2026-03-19 01:39:34 +08:00
|
|
|
{
|
2026-03-19 14:19:01 +08:00
|
|
|
key: 'analytics',
|
2026-03-19 01:39:34 +08:00
|
|
|
icon: <GlobalOutlined />,
|
2026-03-19 14:19:01 +08:00
|
|
|
label: '数据分析',
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
key: '/Analytics',
|
|
|
|
|
label: <Link to="/Analytics">数据分析</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: '/Leaderboard',
|
|
|
|
|
label: <Link to="/Leaderboard">收益排行榜</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: '/ArbitrageMonitor',
|
|
|
|
|
label: <Link to="/ArbitrageMonitor">套利监控</Link>,
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-03-19 01:39:34 +08:00
|
|
|
},
|
2026-03-19 14:19:01 +08:00
|
|
|
// 智能运营
|
2026-03-19 01:39:34 +08:00
|
|
|
{
|
2026-03-19 14:19:01 +08:00
|
|
|
key: 'ai',
|
|
|
|
|
icon: <RobotOutlined />,
|
|
|
|
|
label: '智能运营',
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
key: '/AutoPilot',
|
|
|
|
|
label: <Link to="/AutoPilot">AI托管</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: '/TaskCenter',
|
|
|
|
|
label: <Link to="/TaskCenter">任务中心</Link>,
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-03-19 01:39:34 +08:00
|
|
|
},
|
2026-03-19 14:19:01 +08:00
|
|
|
// 合规与安全
|
2026-03-19 01:39:34 +08:00
|
|
|
{
|
2026-03-19 14:19:01 +08:00
|
|
|
key: 'compliance',
|
|
|
|
|
icon: <SafetyCertificateOutlined />,
|
|
|
|
|
label: '合规与安全',
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
key: '/Compliance',
|
|
|
|
|
label: <Link to="/Compliance">合规管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: '/Blacklist',
|
|
|
|
|
label: <Link to="/Blacklist">黑名单管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-03-19 01:39:34 +08:00
|
|
|
},
|
2026-03-19 14:19:01 +08:00
|
|
|
// 财务管理
|
2026-03-19 01:39:34 +08:00
|
|
|
{
|
|
|
|
|
key: '/Finance',
|
|
|
|
|
icon: <WalletOutlined />,
|
|
|
|
|
label: <Link to="/Finance">财务管理</Link>,
|
|
|
|
|
},
|
2026-03-19 14:19:01 +08:00
|
|
|
// 系统管理
|
2026-03-19 01:39:34 +08:00
|
|
|
{
|
2026-03-19 14:19:01 +08:00
|
|
|
key: 'system',
|
2026-03-19 01:39:34 +08:00
|
|
|
icon: <SettingOutlined />,
|
2026-03-19 14:19:01 +08:00
|
|
|
label: '系统管理',
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
key: '/User',
|
|
|
|
|
label: <Link to="/User">用户管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: '/Role',
|
|
|
|
|
label: <Link to="/Role">角色管理</Link>,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: '/Settings',
|
|
|
|
|
label: <Link to="/Settings">系统设置</Link>,
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-03-19 01:39:34 +08:00
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// 用户菜单项
|
|
|
|
|
const userMenuItems = [
|
|
|
|
|
{
|
|
|
|
|
key: 'profile',
|
|
|
|
|
label: '个人中心',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'settings',
|
|
|
|
|
label: '账号设置',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
type: 'divider',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'logout',
|
|
|
|
|
label: '退出登录',
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const MainLayout: React.FC = () => {
|
|
|
|
|
const location = useLocation();
|
|
|
|
|
const [collapsed, setCollapsed] = useState(false);
|
|
|
|
|
const [selectedKeys, setSelectedKeys] = useState<string[]>(['/']);
|
|
|
|
|
|
|
|
|
|
// 根据当前路径设置选中的菜单项
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const pathname = location.pathname;
|
|
|
|
|
// 找到匹配的菜单项
|
2026-03-19 14:19:01 +08:00
|
|
|
let matchedKey: string | undefined;
|
|
|
|
|
|
|
|
|
|
// 递归查找匹配的菜单项
|
|
|
|
|
const findMatchedItem = (items: any[]) => {
|
|
|
|
|
for (const item of items) {
|
|
|
|
|
if (item.children) {
|
|
|
|
|
// 检查子菜单项
|
|
|
|
|
for (const child of item.children) {
|
|
|
|
|
if (child.key === '/') {
|
|
|
|
|
if (pathname === '/') {
|
|
|
|
|
matchedKey = child.key;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
} else if (pathname.startsWith(child.key as string)) {
|
|
|
|
|
matchedKey = child.key;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 递归检查子菜单
|
|
|
|
|
if (findMatchedItem(item.children)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 检查当前菜单项
|
|
|
|
|
if (item.key === '/') {
|
|
|
|
|
if (pathname === '/') {
|
|
|
|
|
matchedKey = item.key;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
} else if (pathname.startsWith(item.key as string)) {
|
|
|
|
|
matchedKey = item.key;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-19 01:39:34 +08:00
|
|
|
}
|
2026-03-19 14:19:01 +08:00
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
findMatchedItem(menuItems);
|
|
|
|
|
|
|
|
|
|
if (matchedKey) {
|
|
|
|
|
setSelectedKeys([matchedKey]);
|
2026-03-19 01:39:34 +08:00
|
|
|
}
|
|
|
|
|
}, [location.pathname]);
|
|
|
|
|
|
|
|
|
|
// 从 localStorage 读取菜单折叠状态
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const savedCollapsed = localStorage.getItem('sidebar_collapsed');
|
|
|
|
|
if (savedCollapsed !== null) {
|
|
|
|
|
setCollapsed(savedCollapsed === 'true');
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// 保存菜单折叠状态到 localStorage
|
|
|
|
|
const handleCollapse = (value: boolean) => {
|
|
|
|
|
setCollapsed(value);
|
|
|
|
|
localStorage.setItem('sidebar_collapsed', String(value));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleUserMenuClick = ({ key }: { key: string }) => {
|
|
|
|
|
if (key === 'logout') {
|
|
|
|
|
// 处理登出逻辑
|
|
|
|
|
localStorage.removeItem('token');
|
|
|
|
|
history.push('/Auth/LoginPage');
|
|
|
|
|
} else if (key === 'profile') {
|
|
|
|
|
history.push('/Settings/ProfileSettings');
|
|
|
|
|
} else if (key === 'settings') {
|
|
|
|
|
history.push('/Settings');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Layout style={{ minHeight: '100vh' }}>
|
|
|
|
|
{/* 左侧菜单栏 */}
|
|
|
|
|
<Sider
|
|
|
|
|
trigger={null}
|
|
|
|
|
collapsible
|
|
|
|
|
collapsed={collapsed}
|
|
|
|
|
collapsedWidth={80}
|
|
|
|
|
width={220}
|
|
|
|
|
style={{
|
|
|
|
|
background: '#001529',
|
|
|
|
|
position: 'fixed',
|
|
|
|
|
left: 0,
|
|
|
|
|
top: 0,
|
|
|
|
|
bottom: 0,
|
|
|
|
|
zIndex: 100,
|
|
|
|
|
boxShadow: '2px 0 8px rgba(0, 0, 0, 0.15)',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{/* Logo 区域 */}
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
height: '64px',
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
justifyContent: collapsed ? 'center' : 'flex-start',
|
|
|
|
|
padding: collapsed ? '0' : '0 24px',
|
|
|
|
|
background: '#002140',
|
|
|
|
|
borderBottom: '1px solid rgba(255, 255, 255, 0.1)',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{collapsed ? (
|
|
|
|
|
<div style={{ color: '#fff', fontSize: '20px', fontWeight: 'bold' }}>C</div>
|
|
|
|
|
) : (
|
|
|
|
|
<Title level={4} style={{ color: '#fff', margin: 0, whiteSpace: 'nowrap' }}>
|
|
|
|
|
Crawlful Hub
|
|
|
|
|
</Title>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 菜单 */}
|
|
|
|
|
<Menu
|
|
|
|
|
theme="dark"
|
|
|
|
|
mode="inline"
|
|
|
|
|
selectedKeys={selectedKeys}
|
|
|
|
|
items={menuItems}
|
|
|
|
|
style={{
|
|
|
|
|
borderRight: 0,
|
|
|
|
|
paddingTop: '8px',
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</Sider>
|
|
|
|
|
|
|
|
|
|
{/* 右侧内容区域 */}
|
|
|
|
|
<Layout
|
|
|
|
|
style={{
|
|
|
|
|
marginLeft: collapsed ? 80 : 220,
|
|
|
|
|
transition: 'all 0.2s',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{/* 顶部Header */}
|
|
|
|
|
<Header
|
|
|
|
|
style={{
|
|
|
|
|
background: '#fff',
|
|
|
|
|
padding: '0 24px',
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
|
boxShadow: '0 1px 4px rgba(0, 21, 41, 0.08)',
|
|
|
|
|
position: 'sticky',
|
|
|
|
|
top: 0,
|
|
|
|
|
zIndex: 99,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{/* 左侧:折叠按钮 */}
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
padding: '8px',
|
|
|
|
|
borderRadius: '4px',
|
|
|
|
|
transition: 'background 0.3s',
|
|
|
|
|
}}
|
|
|
|
|
onClick={() => handleCollapse(!collapsed)}
|
|
|
|
|
onMouseEnter={(e) => {
|
|
|
|
|
e.currentTarget.style.background = '#f0f0f0';
|
|
|
|
|
}}
|
|
|
|
|
onMouseLeave={(e) => {
|
|
|
|
|
e.currentTarget.style.background = 'transparent';
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{collapsed ? <MenuUnfoldOutlined style={{ fontSize: '18px' }} /> : <MenuFoldOutlined style={{ fontSize: '18px' }} />}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 右侧:通知和用户 */}
|
|
|
|
|
<Space size={24}>
|
|
|
|
|
{/* 通知图标 */}
|
|
|
|
|
<Badge count={5} size="small">
|
|
|
|
|
<BellOutlined style={{ fontSize: '18px', cursor: 'pointer', color: '#666' }} />
|
|
|
|
|
</Badge>
|
|
|
|
|
|
|
|
|
|
{/* 用户下拉菜单 */}
|
|
|
|
|
<Dropdown
|
|
|
|
|
menu={{ items: userMenuItems, onClick: handleUserMenuClick }}
|
|
|
|
|
placement="bottomRight"
|
|
|
|
|
>
|
|
|
|
|
<Space style={{ cursor: 'pointer' }}>
|
|
|
|
|
<Avatar style={{ backgroundColor: '#1890ff' }} icon={<UserOutlined />} />
|
|
|
|
|
{!collapsed && (
|
|
|
|
|
<>
|
|
|
|
|
<Text strong>管理员</Text>
|
|
|
|
|
<DownOutlined style={{ fontSize: '12px', color: '#999' }} />
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</Space>
|
|
|
|
|
</Dropdown>
|
|
|
|
|
</Space>
|
|
|
|
|
</Header>
|
|
|
|
|
|
|
|
|
|
{/* 主内容区域 */}
|
|
|
|
|
<Content
|
|
|
|
|
style={{
|
|
|
|
|
margin: '24px',
|
|
|
|
|
padding: '24px',
|
|
|
|
|
background: '#f0f2f5',
|
|
|
|
|
minHeight: 280,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
background: '#fff',
|
|
|
|
|
padding: '24px',
|
|
|
|
|
borderRadius: '8px',
|
|
|
|
|
minHeight: 'calc(100vh - 184px)',
|
|
|
|
|
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.06)',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Outlet />
|
|
|
|
|
</div>
|
|
|
|
|
</Content>
|
|
|
|
|
</Layout>
|
|
|
|
|
</Layout>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default MainLayout;
|