import React, { useState, useEffect, useMemo } from 'react'; import { Layout, Menu, Typography, Avatar, Dropdown, Badge, Space, Tag, Divider, Switch, message, Button } from 'antd'; import { DashboardOutlined, ShoppingOutlined, FileTextOutlined, UserOutlined, TruckOutlined, AlertOutlined, SettingOutlined, MenuFoldOutlined, MenuUnfoldOutlined, BellOutlined, DownOutlined, ShopOutlined, WalletOutlined, GlobalOutlined, RobotOutlined, AppstoreOutlined, LineChartOutlined, SafetyOutlined, CrownOutlined, SwapOutlined, ClusterOutlined, } from '@ant-design/icons'; import { Link, useLocation, useNavigate, Outlet } from 'react-router-dom'; import type { MenuProps } from 'antd'; import { UserProvider, useUser, MOCK_USERS, ROLE_CONFIG, FEATURES, PERMISSIONS, UserRole } from '@/contexts/UserContext'; const { Header, Sider, Content } = Layout; const { Title, Text } = Typography; type MenuItem = Required['items'][number]; const ALL_MENU_ITEMS: MenuItem[] = [ { key: '/dashboard', icon: , label: 数据概览, requiredPermission: null, }, { key: 'aggregation', icon: , label: '聚合管理', children: [ { key: '/dashboard/aggregation/products', label: 聚合商品 }, { key: '/dashboard/aggregation/orders', label: 聚合订单 }, { key: '/dashboard/aggregation/inventory', label: 聚合库存 }, { key: '/dashboard/aggregation/customers', label: 聚合客户 }, { key: '/dashboard/aggregation/authorization', label: 授权管理 }, ], }, { key: 'ai-operations', icon: , label: 'AI运营中心', requiredFeature: FEATURES.AI_OPERATIONS, children: [ { key: '/dashboard/operation-agent', label: 运营Agent }, { key: '/dashboard/auto-pilot', label: 自动驾驶 }, { key: '/dashboard/task-center', label: 任务中心 }, { key: '/dashboard/ai-decision-log', label: AI决策日志 }, { key: '/dashboard/strategy-marketplace', label: 策略市场 }, { key: '/dashboard/auto-product-selection', label: 自动选品 }, { key: '/dashboard/auto-execution', label: 自动执行 }, ], }, { key: 'product-cycle', icon: , label: '商品闭环', children: [ { key: '/dashboard/product', label: 商品列表, requiredPermission: PERMISSIONS.PRODUCT_READ }, { key: '/dashboard/product/publish', label: 商品发布, requiredPermission: PERMISSIONS.PRODUCT_WRITE }, { key: '/dashboard/product/cross-platform', label: 跨平台管理, requiredFeature: FEATURES.MULTI_SHOP }, { key: '/dashboard/product/ai-pricing', label: AI定价, requiredFeature: FEATURES.AUTO_PRICING }, { key: '/dashboard/product/profit-monitor', label: 利润监控 }, { key: '/dashboard/inventory', label: 库存管理, requiredPermission: PERMISSIONS.INVENTORY_READ }, { key: '/dashboard/inventory/forecast', label: 库存预测 }, { key: '/dashboard/inventory/warehouses', label: 仓库管理 }, ], }, { key: 'order-cycle', icon: , label: '订单闭环', children: [ { key: '/dashboard/orders', label: 订单列表, requiredPermission: PERMISSIONS.ORDER_READ }, { key: '/dashboard/orders/exception', label: 异常订单 }, { key: '/dashboard/orders/aggregation', label: 订单聚合 }, { key: '/dashboard/after-sales', label: 售后服务 }, { key: '/dashboard/after-sales/refund', label: 退款处理 }, { key: '/dashboard/after-sales/customer-service', label: 客服工单 }, ], }, { key: 'logistics-cycle', icon: , label: '物流闭环', requiredPermission: PERMISSIONS.INVENTORY_READ, children: [ { key: '/dashboard/logistics', label: 物流查询 }, { key: '/dashboard/logistics/track', label: 物流追踪 }, { key: '/dashboard/logistics/freight-calc', label: 运费计算 }, ], }, { key: 'finance-cycle', icon: , label: '财务闭环', requiredPermission: PERMISSIONS.FINANCE_READ, children: [ { key: '/dashboard/finance', label: 财务概览 }, { key: '/dashboard/finance/transactions', label: 交易记录 }, { key: '/dashboard/finance/reconciliation', label: 财务对账 }, { key: '/dashboard/user-asset', label: 用户资产 }, { key: '/dashboard/user-asset/member-level', label: 会员等级 }, { key: '/dashboard/user-asset/points', label: 积分管理 }, ], }, { key: 'marketing-cycle', icon: , label: '营销闭环', children: [ { key: '/dashboard/ad', label: 广告管理 }, { key: '/dashboard/ad/auto-adjustment', label: 自动调价, requiredFeature: FEATURES.AUTO_PRICING }, { key: '/dashboard/ad/ai-optimization', label: AI优化, requiredFeature: FEATURES.AI_OPERATIONS }, { key: '/dashboard/marketing/competitors', label: 竞品分析 }, { key: '/dashboard/dynamic-pricing', label: 动态定价, requiredFeature: FEATURES.AUTO_PRICING }, { key: '/dashboard/ab-test', label: AB测试 }, { key: '/dashboard/ab-test/results', label: 测试结果 }, ], }, { key: 'analytics', icon: , label: '数据分析', children: [ { key: '/dashboard/analytics', label: 数据分析 }, { key: '/dashboard/reports', label: 报表中心 }, { key: '/dashboard/multi-shop-report', label: 多店报表, requiredFeature: FEATURES.MULTI_SHOP }, { key: '/dashboard/leaderboard', label: 收益排行 }, ], }, { key: 'b2b-trade', icon: , label: 'B2B贸易', requiredFeature: FEATURES.B2B_TRADE, children: [ { key: '/dashboard/merchant', label: 商户管理 }, { key: '/dashboard/merchant/orders', label: 商户订单 }, { key: '/dashboard/b2b', label: B2B贸易 }, { key: '/dashboard/b2b-trade', label: B2B交易 }, { key: '/dashboard/suppliers', label: 供应商 }, ], }, { key: 'independent-site', icon: , label: '独立站', requiredFeature: FEATURES.INDEPENDENT_SITE, children: [ { key: '/dashboard/independent-site', label: 站点列表 }, { key: '/dashboard/independent-site/create', label: 对接外部站点 }, { key: '/dashboard/independent-site/builder', label: 自建站点 }, { key: '/dashboard/independent-site/templates', label: 网站模板 }, { key: '/dashboard/independent-site/domains', label: 域名管理 }, ], }, { key: 'risk-compliance', icon: , label: '风控合规', children: [ { key: '/dashboard/compliance', label: 合规概览 }, { key: '/dashboard/compliance/certificates', label: 证书管理 }, { key: '/dashboard/compliance/check', label: 合规检查 }, { key: '/dashboard/blacklist', label: 黑名单 }, { key: '/dashboard/arbitrage-monitor', label: 套利监控 }, ], }, { key: 'settings', icon: , label: '系统设置', children: [ { key: '/dashboard/settings', label: 设置概览 }, { key: '/dashboard/settings/profile', label: 个人设置 }, { key: '/dashboard/settings/users', label: 用户管理, requiredPermission: PERMISSIONS.USER_MANAGE }, { key: '/dashboard/settings/system', label: 系统配置, requiredPermission: PERMISSIONS.SYSTEM_CONFIG }, { key: '/dashboard/role', label: 角色管理, requiredPermission: PERMISSIONS.USER_MANAGE }, { key: '/dashboard/settings/subscription', label: 订阅管理 }, ], }, ]; const MainLayoutContent: React.FC = () => { const [collapsed, setCollapsed] = useState(false); const location = useLocation(); const navigate = useNavigate(); const { currentUser, setCurrentUser, hasPermission, hasFeature, getPlanLabel, isPaidUser } = useUser(); const menuItems = useMemo(() => { const stripCustomProps = (item: any): MenuItem => { const { requiredPermission, requiredFeature, ...rest } = item; return rest; }; const filterMenuItems = (items: MenuItem[]): MenuItem[] => { return items .filter((item): item is NonNullable & { key: string } => item != null && 'key' in item) .filter((item) => { const itemWithMeta = item as any; if (itemWithMeta.requiredFeature && !hasFeature(itemWithMeta.requiredFeature)) { return false; } if (itemWithMeta.requiredPermission && !hasPermission(itemWithMeta.requiredPermission)) { return false; } return true; }) .map((item) => { if ('children' in item && Array.isArray(item.children)) { const filteredChildren = filterMenuItems(item.children); if (filteredChildren.length === 0) { return null; } return stripCustomProps({ ...item, children: filteredChildren }); } return stripCustomProps(item); }) .filter((item): item is NonNullable => item !== null); }; return filterMenuItems(ALL_MENU_ITEMS); }, [hasPermission, hasFeature]); const getSelectedKeys = (): string[] => { const pathname = location.pathname; let bestMatch: string | null = null; let bestMatchLength = 0; const findMatch = (items: MenuItem[]) => { items.forEach((item) => { if (item && 'key' in item && typeof item.key === 'string') { const key = item.key; if (pathname === key || pathname.startsWith(key + '/')) { if (key.length > bestMatchLength) { bestMatch = key; bestMatchLength = key.length; } } } if (item && 'children' in item && Array.isArray(item.children)) { findMatch(item.children); } }); }; findMatch(menuItems); return bestMatch ? [bestMatch] : []; }; const getOpenKeys = (): string[] => { const pathname = location.pathname; const openKeys: string[] = []; const findParent = (items: MenuItem[], parentKey?: string) => { items.forEach((item) => { if (item && 'key' in item && typeof item.key === 'string') { const key = item.key; if (pathname === key || pathname.startsWith(key + '/')) { if (parentKey) { openKeys.push(parentKey); } } } if (item && 'children' in item && Array.isArray(item.children)) { const itemKey = 'key' in item ? item.key as string : undefined; findParent(item.children, itemKey); } }); }; findParent(menuItems); return [...new Set(openKeys)]; }; const selectedKeys = getSelectedKeys(); const openKeys = getOpenKeys(); useEffect(() => { const savedCollapsed = localStorage.getItem('sidebar_collapsed'); if (savedCollapsed !== null) { setCollapsed(savedCollapsed === 'true'); } }, []); const handleCollapse = (value: boolean) => { setCollapsed(value); localStorage.setItem('sidebar_collapsed', String(value)); }; const handleUserMenuClick = ({ key }: { key: string }) => { if (key === 'logout') { localStorage.removeItem('token'); navigate('/auth/login'); } else if (key === 'profile') { navigate('/dashboard/settings/profile'); } else if (key === 'settings') { navigate('/dashboard/settings'); } else if (key === 'subscription') { navigate('/dashboard/settings/subscription'); } }; const handleSwitchUser = (userId: string) => { const user = MOCK_USERS.find(u => u.id === userId); if (user) { setCurrentUser(user); message.success(`已切换到 ${user.name} (${ROLE_CONFIG[user.role].label})`); } }; const userMenuItems: MenuItem[] = [ { key: 'profile', icon: , label: '个人中心', }, { key: 'subscription', icon: , label: '订阅管理', }, { key: 'settings', icon: , label: '账号设置', }, { type: 'divider' }, { key: 'logout', icon: , label: '退出登录', }, ]; const planColors: Record = { free: 'default', basic: 'blue', pro: 'gold', enterprise: 'purple', }; return (
{collapsed ? (
C
) : ( Crawlful Hub )}
handleCollapse(!collapsed)} > {collapsed ? ( ) : ( )}
Crawlful Hub 管理后台
({ key: user.id, label: ( {user.name} {ROLE_CONFIG[user.role].label} {getPlanLabel()} ), })), onClick: ({ key }) => handleSwitchUser(key), }} placement="bottomRight" > } />
{currentUser.name} {ROLE_CONFIG[currentUser.role].label} {getPlanLabel()}
); }; const MainLayout: React.FC = () => { return ( ); }; export default MainLayout;