refactor: 重构代码结构和类型定义,优化类型安全性和代码可维护性
- 添加类型定义文件和类型引用 - 删除废弃的页面模块和导出文件 - 新增聚合管理模块和插件系统 - 修复类型错误和潜在运行时问题 - 更新API基础URL和配置 - 优化组件类型定义和事件处理 - 重构数据源接口和实现 - 完善文档和开发进度记录
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
// 全局布局组件 - 包含左侧菜单和头部
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Layout, Menu, Typography, Avatar, Dropdown, Badge, Space } from 'antd';
|
||||
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,
|
||||
@@ -8,226 +7,290 @@ import {
|
||||
UserOutlined,
|
||||
TruckOutlined,
|
||||
AlertOutlined,
|
||||
AuditOutlined,
|
||||
DollarOutlined,
|
||||
BarChartOutlined,
|
||||
SettingOutlined,
|
||||
TeamOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
BellOutlined,
|
||||
DownOutlined,
|
||||
ShopOutlined,
|
||||
ScheduleOutlined,
|
||||
WalletOutlined,
|
||||
SafetyCertificateOutlined,
|
||||
GlobalOutlined,
|
||||
TrophyOutlined,
|
||||
RobotOutlined,
|
||||
AppstoreOutlined,
|
||||
LineChartOutlined,
|
||||
SafetyOutlined,
|
||||
CrownOutlined,
|
||||
SwapOutlined,
|
||||
ThunderboltOutlined,
|
||||
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 = MenuProps['items'][number];
|
||||
type MenuItem = Required<MenuProps>['items'][number];
|
||||
|
||||
// 菜单项配置
|
||||
const menuItems: MenuItem[] = [
|
||||
// 核心业务
|
||||
const ALL_MENU_ITEMS: MenuItem[] = [
|
||||
{
|
||||
key: 'core',
|
||||
key: '/dashboard',
|
||||
icon: <DashboardOutlined />,
|
||||
label: <Link to="/dashboard">数据概览</Link>,
|
||||
requiredPermission: null,
|
||||
},
|
||||
{
|
||||
key: 'aggregation',
|
||||
icon: <ClusterOutlined />,
|
||||
label: '聚合管理',
|
||||
children: [
|
||||
{ key: '/dashboard/aggregation/products', label: <Link to="/dashboard/aggregation/products">聚合商品</Link> },
|
||||
{ key: '/dashboard/aggregation/orders', label: <Link to="/dashboard/aggregation/orders">聚合订单</Link> },
|
||||
{ key: '/dashboard/aggregation/inventory', label: <Link to="/dashboard/aggregation/inventory">聚合库存</Link> },
|
||||
{ key: '/dashboard/aggregation/customers', label: <Link to="/dashboard/aggregation/customers">聚合客户</Link> },
|
||||
{ key: '/dashboard/aggregation/authorization', label: <Link to="/dashboard/aggregation/authorization">授权管理</Link> },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'ai-operations',
|
||||
icon: <RobotOutlined />,
|
||||
label: 'AI运营中心',
|
||||
requiredFeature: FEATURES.AI_OPERATIONS,
|
||||
children: [
|
||||
{ key: '/dashboard/operation-agent', label: <Link to="/dashboard/operation-agent">运营Agent</Link> },
|
||||
{ key: '/dashboard/auto-pilot', label: <Link to="/dashboard/auto-pilot">自动驾驶</Link> },
|
||||
{ key: '/dashboard/task-center', label: <Link to="/dashboard/task-center">任务中心</Link> },
|
||||
{ key: '/dashboard/ai-decision-log', label: <Link to="/dashboard/ai-decision-log">AI决策日志</Link> },
|
||||
{ key: '/dashboard/strategy-marketplace', label: <Link to="/dashboard/strategy-marketplace">策略市场</Link> },
|
||||
{ key: '/dashboard/auto-product-selection', label: <Link to="/dashboard/auto-product-selection">自动选品</Link> },
|
||||
{ key: '/dashboard/auto-execution', label: <Link to="/dashboard/auto-execution">自动执行</Link> },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'product-cycle',
|
||||
icon: <ShoppingOutlined />,
|
||||
label: '核心业务',
|
||||
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>,
|
||||
},
|
||||
{ key: '/dashboard/product', label: <Link to="/dashboard/product">商品列表</Link>, requiredPermission: PERMISSIONS.PRODUCT_READ },
|
||||
{ key: '/dashboard/product/publish', label: <Link to="/dashboard/product/publish">商品发布</Link>, requiredPermission: PERMISSIONS.PRODUCT_WRITE },
|
||||
{ key: '/dashboard/product/cross-platform', label: <Link to="/dashboard/product/cross-platform">跨平台管理</Link>, requiredFeature: FEATURES.MULTI_SHOP },
|
||||
{ key: '/dashboard/product/ai-pricing', label: <Link to="/dashboard/product/ai-pricing">AI定价</Link>, requiredFeature: FEATURES.AUTO_PRICING },
|
||||
{ key: '/dashboard/product/profit-monitor', label: <Link to="/dashboard/product/profit-monitor">利润监控</Link> },
|
||||
{ key: '/dashboard/inventory', label: <Link to="/dashboard/inventory">库存管理</Link>, requiredPermission: PERMISSIONS.INVENTORY_READ },
|
||||
{ key: '/dashboard/inventory/forecast', label: <Link to="/dashboard/inventory/forecast">库存预测</Link> },
|
||||
{ key: '/dashboard/inventory/warehouses', label: <Link to="/dashboard/inventory/warehouses">仓库管理</Link> },
|
||||
],
|
||||
},
|
||||
// 商户与贸易
|
||||
{
|
||||
key: 'merchant',
|
||||
icon: <ShopOutlined />,
|
||||
label: '商户与贸易',
|
||||
key: 'order-cycle',
|
||||
icon: <FileTextOutlined />,
|
||||
label: '订单闭环',
|
||||
children: [
|
||||
{
|
||||
key: '/Merchant',
|
||||
label: <Link to="/Merchant">商户管理</Link>,
|
||||
},
|
||||
{
|
||||
key: '/B2B',
|
||||
label: <Link to="/B2B">B2B贸易</Link>,
|
||||
},
|
||||
{ key: '/dashboard/orders', label: <Link to="/dashboard/orders">订单列表</Link>, requiredPermission: PERMISSIONS.ORDER_READ },
|
||||
{ key: '/dashboard/orders/exception', label: <Link to="/dashboard/orders/exception">异常订单</Link> },
|
||||
{ key: '/dashboard/orders/aggregation', label: <Link to="/dashboard/orders/aggregation">订单聚合</Link> },
|
||||
{ key: '/dashboard/after-sales', label: <Link to="/dashboard/after-sales">售后服务</Link> },
|
||||
{ key: '/dashboard/after-sales/refund', label: <Link to="/dashboard/after-sales/refund">退款处理</Link> },
|
||||
{ key: '/dashboard/after-sales/customer-service', label: <Link to="/dashboard/after-sales/customer-service">客服工单</Link> },
|
||||
],
|
||||
},
|
||||
// 营销与增长
|
||||
{
|
||||
key: 'marketing',
|
||||
icon: <BarChartOutlined />,
|
||||
label: '营销与增长',
|
||||
key: 'logistics-cycle',
|
||||
icon: <TruckOutlined />,
|
||||
label: '物流闭环',
|
||||
requiredPermission: PERMISSIONS.INVENTORY_READ,
|
||||
children: [
|
||||
{
|
||||
key: '/Ad',
|
||||
label: <Link to="/Ad">广告管理</Link>,
|
||||
},
|
||||
{
|
||||
key: '/DynamicPricing',
|
||||
label: <Link to="/DynamicPricing">动态定价</Link>,
|
||||
},
|
||||
{
|
||||
key: '/StrategyMarketplace',
|
||||
label: <Link to="/StrategyMarketplace">策略市场</Link>,
|
||||
},
|
||||
{ key: '/dashboard/logistics', label: <Link to="/dashboard/logistics">物流查询</Link> },
|
||||
{ key: '/dashboard/logistics/track', label: <Link to="/dashboard/logistics/track">物流追踪</Link> },
|
||||
{ key: '/dashboard/logistics/freight-calc', label: <Link to="/dashboard/logistics/freight-calc">运费计算</Link> },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'finance-cycle',
|
||||
icon: <WalletOutlined />,
|
||||
label: '财务闭环',
|
||||
requiredPermission: PERMISSIONS.FINANCE_READ,
|
||||
children: [
|
||||
{ key: '/dashboard/finance', label: <Link to="/dashboard/finance">财务概览</Link> },
|
||||
{ key: '/dashboard/finance/transactions', label: <Link to="/dashboard/finance/transactions">交易记录</Link> },
|
||||
{ key: '/dashboard/finance/reconciliation', label: <Link to="/dashboard/finance/reconciliation">财务对账</Link> },
|
||||
{ key: '/dashboard/user-asset', label: <Link to="/dashboard/user-asset">用户资产</Link> },
|
||||
{ key: '/dashboard/user-asset/member-level', label: <Link to="/dashboard/user-asset/member-level">会员等级</Link> },
|
||||
{ key: '/dashboard/user-asset/points', label: <Link to="/dashboard/user-asset/points">积分管理</Link> },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'marketing-cycle',
|
||||
icon: <LineChartOutlined />,
|
||||
label: '营销闭环',
|
||||
children: [
|
||||
{ key: '/dashboard/ad', label: <Link to="/dashboard/ad">广告管理</Link> },
|
||||
{ key: '/dashboard/ad/auto-adjustment', label: <Link to="/dashboard/ad/auto-adjustment">自动调价</Link>, requiredFeature: FEATURES.AUTO_PRICING },
|
||||
{ key: '/dashboard/ad/ai-optimization', label: <Link to="/dashboard/ad/ai-optimization">AI优化</Link>, requiredFeature: FEATURES.AI_OPERATIONS },
|
||||
{ key: '/dashboard/marketing/competitors', label: <Link to="/dashboard/marketing/competitors">竞品分析</Link> },
|
||||
{ key: '/dashboard/dynamic-pricing', label: <Link to="/dashboard/dynamic-pricing">动态定价</Link>, requiredFeature: FEATURES.AUTO_PRICING },
|
||||
{ key: '/dashboard/ab-test', label: <Link to="/dashboard/ab-test">AB测试</Link> },
|
||||
{ key: '/dashboard/ab-test/results', label: <Link to="/dashboard/ab-test/results">测试结果</Link> },
|
||||
],
|
||||
},
|
||||
// 数据分析
|
||||
{
|
||||
key: 'analytics',
|
||||
icon: <GlobalOutlined />,
|
||||
icon: <AppstoreOutlined />,
|
||||
label: '数据分析',
|
||||
children: [
|
||||
{
|
||||
key: '/Analytics',
|
||||
label: <Link to="/Analytics">数据分析</Link>,
|
||||
},
|
||||
{
|
||||
key: '/Leaderboard',
|
||||
label: <Link to="/Leaderboard">收益排行榜</Link>,
|
||||
},
|
||||
{
|
||||
key: '/ArbitrageMonitor',
|
||||
label: <Link to="/ArbitrageMonitor">套利监控</Link>,
|
||||
},
|
||||
{ key: '/dashboard/analytics', label: <Link to="/dashboard/analytics">数据分析</Link> },
|
||||
{ key: '/dashboard/reports', label: <Link to="/dashboard/reports">报表中心</Link> },
|
||||
{ key: '/dashboard/multi-shop-report', label: <Link to="/dashboard/multi-shop-report">多店报表</Link>, requiredFeature: FEATURES.MULTI_SHOP },
|
||||
{ key: '/dashboard/leaderboard', label: <Link to="/dashboard/leaderboard">收益排行</Link> },
|
||||
],
|
||||
},
|
||||
// 智能运营
|
||||
{
|
||||
key: 'ai',
|
||||
icon: <RobotOutlined />,
|
||||
label: '智能运营',
|
||||
key: 'b2b-trade',
|
||||
icon: <ShopOutlined />,
|
||||
label: 'B2B贸易',
|
||||
requiredFeature: FEATURES.B2B_TRADE,
|
||||
children: [
|
||||
{
|
||||
key: '/AutoPilot',
|
||||
label: <Link to="/AutoPilot">AI托管</Link>,
|
||||
},
|
||||
{
|
||||
key: '/TaskCenter',
|
||||
label: <Link to="/TaskCenter">任务中心</Link>,
|
||||
},
|
||||
{ key: '/dashboard/merchant', label: <Link to="/dashboard/merchant">商户管理</Link> },
|
||||
{ key: '/dashboard/merchant/orders', label: <Link to="/dashboard/merchant/orders">商户订单</Link> },
|
||||
{ key: '/dashboard/b2b', label: <Link to="/dashboard/b2b">B2B贸易</Link> },
|
||||
{ key: '/dashboard/b2b-trade', label: <Link to="/dashboard/b2b-trade">B2B交易</Link> },
|
||||
{ key: '/dashboard/suppliers', label: <Link to="/dashboard/suppliers">供应商</Link> },
|
||||
],
|
||||
},
|
||||
// 合规与安全
|
||||
{
|
||||
key: 'compliance',
|
||||
icon: <SafetyCertificateOutlined />,
|
||||
label: '合规与安全',
|
||||
key: 'independent-site',
|
||||
icon: <GlobalOutlined />,
|
||||
label: '独立站',
|
||||
requiredFeature: FEATURES.INDEPENDENT_SITE,
|
||||
children: [
|
||||
{
|
||||
key: '/Compliance',
|
||||
label: <Link to="/Compliance">合规管理</Link>,
|
||||
},
|
||||
{
|
||||
key: '/Blacklist',
|
||||
label: <Link to="/Blacklist">黑名单管理</Link>,
|
||||
},
|
||||
{ key: '/dashboard/independent-site', label: <Link to="/dashboard/independent-site">站点列表</Link> },
|
||||
{ key: '/dashboard/independent-site/create', label: <Link to="/dashboard/independent-site/create">对接外部站点</Link> },
|
||||
{ key: '/dashboard/independent-site/builder', label: <Link to="/dashboard/independent-site/builder">自建站点</Link> },
|
||||
{ key: '/dashboard/independent-site/templates', label: <Link to="/dashboard/independent-site/templates">网站模板</Link> },
|
||||
{ key: '/dashboard/independent-site/domains', label: <Link to="/dashboard/independent-site/domains">域名管理</Link> },
|
||||
],
|
||||
},
|
||||
// 财务管理
|
||||
{
|
||||
key: '/Finance',
|
||||
icon: <WalletOutlined />,
|
||||
label: <Link to="/Finance">财务管理</Link>,
|
||||
},
|
||||
// 系统管理
|
||||
{
|
||||
key: 'system',
|
||||
icon: <SettingOutlined />,
|
||||
label: '系统管理',
|
||||
key: 'risk-compliance',
|
||||
icon: <SafetyOutlined />,
|
||||
label: '风控合规',
|
||||
children: [
|
||||
{
|
||||
key: '/User',
|
||||
label: <Link to="/User">用户管理</Link>,
|
||||
},
|
||||
{
|
||||
key: '/Role',
|
||||
label: <Link to="/Role">角色管理</Link>,
|
||||
},
|
||||
{
|
||||
key: '/Settings',
|
||||
label: <Link to="/Settings">系统设置</Link>,
|
||||
},
|
||||
{ key: '/dashboard/compliance', label: <Link to="/dashboard/compliance">合规概览</Link> },
|
||||
{ key: '/dashboard/compliance/certificates', label: <Link to="/dashboard/compliance/certificates">证书管理</Link> },
|
||||
{ key: '/dashboard/compliance/check', label: <Link to="/dashboard/compliance/check">合规检查</Link> },
|
||||
{ key: '/dashboard/blacklist', label: <Link to="/dashboard/blacklist">黑名单</Link> },
|
||||
{ key: '/dashboard/arbitrage-monitor', label: <Link to="/dashboard/arbitrage-monitor">套利监控</Link> },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// 用户菜单项
|
||||
const userMenuItems: MenuItem[] = [
|
||||
{
|
||||
key: 'profile',
|
||||
label: '个人中心',
|
||||
},
|
||||
{
|
||||
key: 'settings',
|
||||
label: '账号设置',
|
||||
},
|
||||
{
|
||||
key: 'divider-1',
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
key: 'logout',
|
||||
label: '退出登录',
|
||||
icon: <SettingOutlined />,
|
||||
label: '系统设置',
|
||||
children: [
|
||||
{ key: '/dashboard/settings', label: <Link to="/dashboard/settings">设置概览</Link> },
|
||||
{ key: '/dashboard/settings/profile', label: <Link to="/dashboard/settings/profile">个人设置</Link> },
|
||||
{ key: '/dashboard/settings/users', label: <Link to="/dashboard/settings/users">用户管理</Link>, requiredPermission: PERMISSIONS.USER_MANAGE },
|
||||
{ key: '/dashboard/settings/system', label: <Link to="/dashboard/settings/system">系统配置</Link>, requiredPermission: PERMISSIONS.SYSTEM_CONFIG },
|
||||
{ key: '/dashboard/role', label: <Link to="/dashboard/role">角色管理</Link>, requiredPermission: PERMISSIONS.USER_MANAGE },
|
||||
{ key: '/dashboard/settings/subscription', label: <Link to="/dashboard/settings/subscription">订阅管理</Link> },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const MainLayout: React.FC = () => {
|
||||
const MainLayoutContent: React.FC = () => {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [selectedKeys, setSelectedKeys] = useState<string[]>(['/']);
|
||||
const { currentUser, setCurrentUser, hasPermission, hasFeature, getPlanLabel, isPaidUser } = useUser();
|
||||
|
||||
// 根据当前路径设置选中的菜单项
|
||||
useEffect(() => {
|
||||
const pathname = location.pathname;
|
||||
const findKey = (items: MenuItem[]): string | undefined => {
|
||||
for (const item of items) {
|
||||
if (item && 'key' in item && typeof item.key === 'string') {
|
||||
if (item.key.startsWith('/') && pathname.startsWith(item.key)) {
|
||||
return item.key;
|
||||
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<MenuItem> & { 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 found = findKey(item.children as MenuItem[]);
|
||||
if (found) return found;
|
||||
const filteredChildren = filterMenuItems(item.children);
|
||||
if (filteredChildren.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return stripCustomProps({ ...item, children: filteredChildren });
|
||||
}
|
||||
return stripCustomProps(item);
|
||||
})
|
||||
.filter((item): item is NonNullable<MenuItem> => 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
if (item && 'children' in item && Array.isArray(item.children)) {
|
||||
findMatch(item.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const matchedKey = findKey(menuItems);
|
||||
if (matchedKey) {
|
||||
setSelectedKeys([matchedKey]);
|
||||
}
|
||||
}, [location.pathname]);
|
||||
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();
|
||||
|
||||
// 从 localStorage 读取菜单折叠状态
|
||||
useEffect(() => {
|
||||
const savedCollapsed = localStorage.getItem('sidebar_collapsed');
|
||||
if (savedCollapsed !== null) {
|
||||
@@ -235,7 +298,6 @@ const MainLayout: React.FC = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 保存菜单折叠状态到 localStorage
|
||||
const handleCollapse = (value: boolean) => {
|
||||
setCollapsed(value);
|
||||
localStorage.setItem('sidebar_collapsed', String(value));
|
||||
@@ -243,77 +305,166 @@ const MainLayout: React.FC = () => {
|
||||
|
||||
const handleUserMenuClick = ({ key }: { key: string }) => {
|
||||
if (key === 'logout') {
|
||||
// 处理登出逻辑
|
||||
localStorage.removeItem('token');
|
||||
navigate('/Auth/LoginPage');
|
||||
navigate('/auth/login');
|
||||
} else if (key === 'profile') {
|
||||
navigate('/Settings/ProfileSettings');
|
||||
navigate('/dashboard/settings/profile');
|
||||
} else if (key === 'settings') {
|
||||
navigate('/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: <UserOutlined />,
|
||||
label: '个人中心',
|
||||
},
|
||||
{
|
||||
key: 'subscription',
|
||||
icon: <CrownOutlined />,
|
||||
label: '订阅管理',
|
||||
},
|
||||
{
|
||||
key: 'settings',
|
||||
icon: <SettingOutlined />,
|
||||
label: '账号设置',
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{
|
||||
key: 'logout',
|
||||
icon: <AlertOutlined />,
|
||||
label: '退出登录',
|
||||
},
|
||||
];
|
||||
|
||||
const planColors: Record<string, string> = {
|
||||
free: 'default',
|
||||
basic: 'blue',
|
||||
pro: 'gold',
|
||||
enterprise: 'purple',
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout style={{ minHeight: '100vh' }}>
|
||||
{/* 左侧菜单栏 */}
|
||||
<Sider
|
||||
trigger={null}
|
||||
collapsible
|
||||
collapsed={collapsed}
|
||||
collapsedWidth={80}
|
||||
width={220}
|
||||
width={collapsed ? 80 : 200}
|
||||
style={{
|
||||
background: '#001529',
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
height: '100vh',
|
||||
zIndex: 100,
|
||||
boxShadow: '2px 0 8px rgba(0, 0, 0, 0.15)',
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
{/* 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>
|
||||
<div style={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
height: '64px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: '#002140',
|
||||
borderBottom: '1px solid rgba(255, 255, 255, 0.1)',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{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',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
flex: '1 1 auto',
|
||||
minHeight: 0,
|
||||
overflow: 'auto',
|
||||
overflowX: 'hidden',
|
||||
}}
|
||||
className="custom-scrollbar"
|
||||
>
|
||||
<style>{`
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 2px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 2px;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
`}</style>
|
||||
<Menu
|
||||
theme="dark"
|
||||
mode="inline"
|
||||
selectedKeys={selectedKeys}
|
||||
defaultOpenKeys={openKeys}
|
||||
items={menuItems}
|
||||
style={{
|
||||
borderRight: 0,
|
||||
paddingTop: '8px',
|
||||
height: 'auto',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: '48px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderTop: '1px solid rgba(255, 255, 255, 0.1)',
|
||||
flexShrink: 0,
|
||||
cursor: 'pointer',
|
||||
background: 'rgba(255, 255, 255, 0.05)',
|
||||
}}
|
||||
onClick={() => handleCollapse(!collapsed)}
|
||||
>
|
||||
{collapsed ? (
|
||||
<MenuUnfoldOutlined style={{ fontSize: '16px', color: '#fff' }} />
|
||||
) : (
|
||||
<MenuFoldOutlined style={{ fontSize: '16px', color: '#fff' }} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Sider>
|
||||
|
||||
{/* 右侧内容区域 */}
|
||||
<Layout
|
||||
style={{
|
||||
marginLeft: collapsed ? 80 : 220,
|
||||
marginLeft: collapsed ? 80 : 200,
|
||||
transition: 'all 0.2s',
|
||||
}}
|
||||
>
|
||||
{/* 顶部Header */}
|
||||
<Header
|
||||
style={{
|
||||
background: '#fff',
|
||||
@@ -327,53 +478,59 @@ const MainLayout: React.FC = () => {
|
||||
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 style={{ fontSize: '16px', fontWeight: 500, color: '#333' }}>
|
||||
Crawlful Hub 管理后台
|
||||
</div>
|
||||
|
||||
{/* 右侧:通知和用户 */}
|
||||
<Space size={24}>
|
||||
{/* 通知图标 */}
|
||||
<Space size={16}>
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: MOCK_USERS.map(user => ({
|
||||
key: user.id,
|
||||
label: (
|
||||
<Space>
|
||||
<span>{user.name}</span>
|
||||
<Tag color={ROLE_CONFIG[user.role].color}>{ROLE_CONFIG[user.role].label}</Tag>
|
||||
<Tag color={planColors[user.subscription?.plan || 'free']}>{getPlanLabel()}</Tag>
|
||||
</Space>
|
||||
),
|
||||
})),
|
||||
onClick: ({ key }) => handleSwitchUser(key),
|
||||
}}
|
||||
placement="bottomRight"
|
||||
>
|
||||
<Button size="small" icon={<SwapOutlined />}>
|
||||
切换用户
|
||||
</Button>
|
||||
</Dropdown>
|
||||
|
||||
<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' }} />
|
||||
</>
|
||||
)}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
|
||||
<Text strong style={{ fontSize: '14px' }}>{currentUser.name}</Text>
|
||||
<Space size={4}>
|
||||
<Tag color={ROLE_CONFIG[currentUser.role].color} style={{ margin: 0, fontSize: '10px', padding: '0 4px' }}>
|
||||
{ROLE_CONFIG[currentUser.role].label}
|
||||
</Tag>
|
||||
<Tag color={planColors[currentUser.subscription?.plan || 'free']} style={{ margin: 0, fontSize: '10px', padding: '0 4px' }}>
|
||||
{getPlanLabel()}
|
||||
</Tag>
|
||||
</Space>
|
||||
</div>
|
||||
<DownOutlined style={{ fontSize: '12px', color: '#999' }} />
|
||||
</Space>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
</Header>
|
||||
|
||||
{/* 主内容区域 */}
|
||||
<Content
|
||||
style={{
|
||||
margin: '24px',
|
||||
@@ -391,7 +548,7 @@ const MainLayout: React.FC = () => {
|
||||
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.06)',
|
||||
}}
|
||||
>
|
||||
<Outlet />
|
||||
<Outlet context={{ currentUser, hasPermission, hasFeature, isPaidUser }} />
|
||||
</div>
|
||||
</Content>
|
||||
</Layout>
|
||||
@@ -399,4 +556,12 @@ const MainLayout: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const MainLayout: React.FC = () => {
|
||||
return (
|
||||
<UserProvider>
|
||||
<MainLayoutContent />
|
||||
</UserProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainLayout;
|
||||
|
||||
Reference in New Issue
Block a user