import { useState, useEffect, useMemo, Layout, Menu, Typography, Avatar, Dropdown, Badge, Space, Tag, message, Button, DashboardOutlined, ShoppingOutlined, FileTextOutlined, UserOutlined, TruckOutlined, AlertOutlined, SettingOutlined, MenuFoldOutlined, MenuUnfoldOutlined, BellOutlined, DownOutlined, ShopOutlined, WalletOutlined, GlobalOutlined, RobotOutlined, AppstoreOutlined, LineChartOutlined, SafetyOutlined, CrownOutlined, SwapOutlined, Header, Sider, Content, Title, Text, Link, useLocation, useNavigate, Outlet, FC, } from '@/imports'; import type { MenuProps } from 'antd'; import { UserProvider, useUser, MOCK_USERS, ROLE_CONFIG, FEATURES, PERMISSIONS } from '@/contexts/UserContext'; import { LocaleProvider, useLocale } from '@/contexts/LocaleContext'; import { ThemeProvider } from '@/contexts/ThemeContext'; import ThemeSwitch from '@/components/ThemeSwitch'; import Breadcrumb from '@/components/Breadcrumb'; interface CustomMenuItem { key: string; icon?: React.ReactNode; label?: React.ReactNode; requiredPermission?: string | null; requiredFeature?: string | null; children?: CustomMenuItem[]; type?: 'divider' | 'group'; } type MenuItem = Required['items'][number]; const MainLayoutContent: FC = () => { const [collapsed, setCollapsed] = useState(false); const location = useLocation(); const navigate = useNavigate(); const { currentUser, setCurrentUser, hasPermission, hasFeature, getPlanLabel, isPaidUser } = useUser(); const { locale, setLocale, t } = useLocale(); const ALL_MENU_ITEMS = useMemo(() => [ { key: '/dashboard', icon: , label: {t('menu.dashboard')}, requiredPermission: null, }, { key: 'ai-operations', icon: , label: t('menu.aiOperations'), requiredFeature: FEATURES.AI_OPERATIONS, children: [ { key: '/dashboard/operation-agent-enhanced', label: {t('menu.operationAgent')} }, { key: '/dashboard/auto-pilot', label: {t('menu.autoPilot')} }, { key: '/dashboard/task-center', label: {t('menu.taskCenter')} }, { key: '/dashboard/ai-action-task-manager', label: {t('menu.taskManager')} }, { key: '/dashboard/ai-suggestion', label: {t('menu.aiSuggestion')} }, { key: '/dashboard/human-approval', label: {t('menu.humanApproval')} }, { key: '/dashboard/client-management', label: {t('menu.clientManagement')} }, { key: '/dashboard/execution-results', label: {t('menu.executionResults')} }, { key: '/dashboard/ai-decision-log', label: {t('menu.aiDecisionLog')} }, { key: '/dashboard/strategy-marketplace', label: {t('menu.strategyMarketplace')} }, { key: '/dashboard/auto-product-selection', label: {t('menu.autoProductSelection')} }, { key: '/dashboard/auto-execution', label: {t('menu.autoExecution')} }, { key: '/dashboard/workflow', label: {t('menu.workflowManagement')} }, ], }, { key: 'product-cycle', icon: , label: t('menu.productCycle'), children: [ { key: '/dashboard/product', label: {t('menu.productManagement')}, requiredPermission: PERMISSIONS.PRODUCT_READ }, { key: '/dashboard/product/publish', label: {t('menu.productPublish')}, requiredPermission: PERMISSIONS.PRODUCT_WRITE }, { key: '/dashboard/inventory', label: {t('menu.inventoryManagement')}, requiredPermission: PERMISSIONS.INVENTORY_READ }, { key: '/dashboard/product/ai-pricing', label: {t('menu.aiPricing')}, requiredFeature: FEATURES.AUTO_PRICING }, { key: '/dashboard/product/profit-monitor', label: {t('menu.profitMonitor')} }, ], }, { key: 'order-cycle', icon: , label: t('menu.orderCycle'), children: [ { key: '/dashboard/orders', label: {t('menu.orderManagement')}, requiredPermission: PERMISSIONS.ORDER_READ }, { key: '/dashboard/orders/exception', label: {t('menu.exceptionOrders')} }, { key: '/dashboard/after-sales', label: {t('menu.afterSales')} }, { key: '/dashboard/after-sales/refund', label: {t('menu.refundProcessing')} }, ], }, { key: 'logistics-cycle', icon: , label: t('menu.logisticsCycle'), requiredPermission: PERMISSIONS.INVENTORY_READ, children: [ { key: '/dashboard/logistics', label: {t('menu.logisticsQuery')} }, { key: '/dashboard/logistics/freight-calc', label: {t('menu.freightCalculation')} }, ], }, { key: 'finance-cycle', icon: , label: t('menu.financeCycle'), requiredPermission: PERMISSIONS.FINANCE_READ, children: [ { key: '/dashboard/finance', label: {t('menu.financeOverview')} }, { key: '/dashboard/finance/transactions', label: {t('menu.transactionRecords')} }, { key: '/dashboard/finance/reconciliation', label: {t('menu.financeReconciliation')} }, { key: '/dashboard/user-asset', label: {t('menu.userAssets')} }, ], }, { key: 'marketing-cycle', icon: , label: t('menu.marketingCycle'), children: [ { key: '/dashboard/ad', label: {t('menu.adManagement')} }, { key: '/dashboard/marketing/competitors', label: {t('menu.competitorAnalysis')} }, { key: '/dashboard/dynamic-pricing', label: {t('menu.dynamicPricing')}, requiredFeature: FEATURES.AUTO_PRICING }, { key: '/dashboard/ab-test', label: {t('menu.abTest')} }, ], }, { key: 'analytics', icon: , label: t('menu.analytics'), children: [ { key: '/dashboard/analytics', label: {t('menu.dataAnalysis')} }, { key: '/dashboard/analytics/dashboard', label: {t('menu.analyticsDashboard')} }, { key: '/dashboard/reports', label: {t('menu.reportCenter')} }, { key: '/dashboard/multi-shop-report', label: {t('menu.multiShopReport')}, requiredFeature: FEATURES.MULTI_SHOP }, { key: '/dashboard/leaderboard', label: {t('menu.leaderboard')} }, ], }, { key: 'b2b-trade', icon: , label: t('menu.b2bTrade'), requiredFeature: FEATURES.B2B_TRADE, children: [ { key: '/dashboard/merchant', label: {t('menu.merchantManagement')} }, { key: '/dashboard/b2b', label: {t('menu.b2bTradeManagement')} }, { key: '/dashboard/b2b/batch-order', label: {t('menu.batchOrder')} }, { key: '/dashboard/b2b/enterprise-quote', label: {t('menu.enterpriseQuote')} }, { key: '/dashboard/b2b/contract-manage', label: {t('menu.contractManagement')} }, { key: '/dashboard/procurement', label: {t('menu.procurementManagement')} }, { key: '/dashboard/suppliers', label: {t('menu.suppliers')} }, { key: '/dashboard/warehouse', label: {t('menu.warehouseInventory')} }, ], }, { key: 'independent-site', icon: , label: t('menu.independentSite'), requiredFeature: FEATURES.INDEPENDENT_SITE, children: [ { key: '/dashboard/independent-site', label: {t('menu.siteManagement')} }, { key: '/dashboard/independent-site/create', label: {t('menu.createSite')} }, { key: '/dashboard/independent-site/templates', label: {t('menu.siteTemplates')} }, { key: '/dashboard/independent-site/analytics', label: {t('menu.siteAnalytics')} }, { key: '/dashboard/independent-site/orders', label: {t('menu.independentSiteOrders')} }, { key: '/dashboard/independent-site/products', label: {t('menu.independentSiteProducts')} }, { key: '/dashboard/independent-site/domains', label: {t('menu.domainManagement')} }, ], }, { key: 'risk-compliance', icon: , label: t('menu.riskCompliance'), children: [ { key: '/dashboard/blacklist', label: {t('menu.riskMonitoring')} }, { key: '/dashboard/compliance', label: {t('menu.complianceCheck')} }, { key: '/dashboard/audit', label: {t('menu.auditLogs')} }, { key: '/dashboard/operation-logs', label: {t('menu.operationLogs')} }, { key: '/dashboard/governance', label: {t('menu.governanceCenter')} }, { key: '/dashboard/sovereignty', label: {t('menu.sovereigntyManagement')} }, { key: '/dashboard/compliance/certificates', label: {t('menu.certificateManagement')} }, ], }, { key: 'settings', icon: , label: t('menu.settings'), children: [ { key: '/dashboard/settings', label: {t('menu.settingsOverview')} }, { key: '/dashboard/settings/platform-auth', label: {t('menu.platformAuth')} }, { key: '/dashboard/settings/service-manager', label: {t('menu.serviceManager')} }, { key: '/dashboard/settings/subscription', label: {t('menu.subscription')} }, { key: '/dashboard/user', label: {t('menu.userManagement')} }, { key: '/dashboard/role', label: {t('menu.rolePermissions')} }, ], }, ], [t]); const menuItems = useMemo(() => { const stripCustomProps = (item: CustomMenuItem): MenuItem => { const { requiredPermission, requiredFeature, ...rest } = item; return rest as MenuItem; }; const filterMenuItems = (items: CustomMenuItem[]): MenuItem[] => { const result: MenuItem[] = []; items.forEach((item) => { if (item.requiredFeature && !hasFeature(item.requiredFeature)) { return; } if (item.requiredPermission && !hasPermission(item.requiredPermission)) { return; } if (item.children && Array.isArray(item.children)) { const filteredChildren = filterMenuItems(item.children); if (filteredChildren.length > 0) { result.push(stripCustomProps({ ...item, children: filteredChildren as CustomMenuItem[] })); } } else { result.push(stripCustomProps(item)); } }); return result; }; return filterMenuItems(ALL_MENU_ITEMS); }, [hasPermission, hasFeature, t]); 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 Array.from(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', }; // 如果当前路径不是以 /dashboard 开头,则只渲染 Outlet,避免布局嵌套 if (!location.pathname.startsWith('/dashboard')) { return ; } return (
{collapsed ? (
C
) : ( Crawlful Hub )}
handleCollapse(!collapsed)} > {collapsed ? ( ) : ( )}
{t('app.name')} {t('app.title')}
({ key: user.id, label: ( {user.name} {ROLE_CONFIG[user.role].label} {getPlanLabel()} ), })), onClick: ({ key }) => handleSwitchUser(key), }} placement="bottomRight" > setLocale(key), selectedKeys: [locale], }} placement="bottomRight" > } />
{currentUser.name} {ROLE_CONFIG[currentUser.role].label} {getPlanLabel()}
); }; const MainLayout: FC = () => { return ( ); }; export default MainLayout;