- 将服务文件按功能分类到core、ai、analytics、security等目录 - 修复logger导入路径问题,统一使用相对路径 - 更新相关文件的导入路径引用 - 添加新的批量操作组件导出文件 - 修复dashboard页面中的类型错误 - 添加dotenv依赖到package.json
1014 lines
34 KiB
TypeScript
1014 lines
34 KiB
TypeScript
import React, { useState, useMemo } from 'react';
|
||
import {
|
||
Card, Row, Col, Switch, Tag, Typography, Space, Button, Modal,
|
||
Progress, Divider, Alert, Tabs, Table, message, Statistic,
|
||
Timeline, Badge, Tooltip, Radio, Empty, Steps, theme, ConfigProvider
|
||
} from 'antd';
|
||
import type { ColumnsType } from 'antd/es/table';
|
||
import {
|
||
CrownOutlined,
|
||
RobotOutlined,
|
||
ShopOutlined,
|
||
GlobalOutlined,
|
||
LineChartOutlined,
|
||
ApiOutlined,
|
||
CheckCircleOutlined,
|
||
CloseCircleOutlined,
|
||
ThunderboltOutlined,
|
||
StarOutlined,
|
||
ArrowUpOutlined,
|
||
ArrowDownOutlined,
|
||
InfoCircleOutlined,
|
||
GiftOutlined,
|
||
SafetyOutlined,
|
||
RocketOutlined,
|
||
TeamOutlined,
|
||
ClockCircleOutlined,
|
||
DollarOutlined,
|
||
CheckSquareOutlined,
|
||
CloseSquareOutlined,
|
||
RightOutlined,
|
||
} from '@ant-design/icons';
|
||
import { useUser, FEATURES, ROLE_CONFIG } from '@/contexts/UserContext';
|
||
|
||
const { Title, Text, Paragraph } = Typography;
|
||
|
||
interface FeatureConfig {
|
||
key: string;
|
||
name: string;
|
||
description: string;
|
||
icon: React.ReactNode;
|
||
requiredPlan: string[];
|
||
benefits: string[];
|
||
limitations?: string[];
|
||
popularity?: number;
|
||
category: 'core' | 'advanced' | 'enterprise';
|
||
}
|
||
|
||
const FEATURE_CONFIGS: FeatureConfig[] = [
|
||
{
|
||
key: FEATURES.AI_OPERATIONS,
|
||
name: 'AI运营中心',
|
||
description: '智能运营助手,自动化执行日常运营任务',
|
||
icon: <RobotOutlined style={{ fontSize: 24 }} />,
|
||
requiredPlan: ['basic', 'pro', 'enterprise'],
|
||
benefits: [
|
||
'自动选品推荐',
|
||
'智能定价策略',
|
||
'自动上下架',
|
||
'AI决策日志追踪',
|
||
'策略市场访问',
|
||
],
|
||
limitations: [
|
||
'免费版: 每日仅3次AI调用',
|
||
'基础版: 每日100次AI调用',
|
||
'专业版: 无限AI调用',
|
||
],
|
||
popularity: 95,
|
||
category: 'core',
|
||
},
|
||
{
|
||
key: FEATURES.AUTO_PRICING,
|
||
name: '自动定价',
|
||
description: '基于市场数据和竞争分析的智能定价系统',
|
||
icon: <ThunderboltOutlined style={{ fontSize: 24 }} />,
|
||
requiredPlan: ['pro', 'enterprise'],
|
||
benefits: [
|
||
'实时竞品价格监控',
|
||
'动态定价策略',
|
||
'利润率自动优化',
|
||
'价格预警通知',
|
||
],
|
||
limitations: [
|
||
'基础版: 仅查看定价建议',
|
||
'专业版: 自动执行定价',
|
||
],
|
||
popularity: 88,
|
||
category: 'advanced',
|
||
},
|
||
{
|
||
key: FEATURES.MULTI_SHOP,
|
||
name: '多店铺管理',
|
||
description: '统一管理多个电商平台的店铺',
|
||
icon: <ShopOutlined style={{ fontSize: 24 }} />,
|
||
requiredPlan: ['basic', 'pro', 'enterprise'],
|
||
benefits: [
|
||
'跨平台商品同步',
|
||
'统一库存管理',
|
||
'多店铺数据报表',
|
||
'批量操作支持',
|
||
],
|
||
limitations: [
|
||
'免费版: 仅支持1个店铺',
|
||
'基础版: 最多3个店铺',
|
||
'专业版: 最多10个店铺',
|
||
'企业版: 无限店铺',
|
||
],
|
||
popularity: 92,
|
||
category: 'core',
|
||
},
|
||
{
|
||
key: FEATURES.B2B_TRADE,
|
||
name: 'B2B贸易',
|
||
description: '企业级批发贸易管理功能',
|
||
icon: <ShopOutlined style={{ fontSize: 24 }} />,
|
||
requiredPlan: ['basic', 'pro', 'enterprise'],
|
||
benefits: [
|
||
'供应商管理',
|
||
'批量订单处理',
|
||
'B2B专属定价',
|
||
'合同管理',
|
||
],
|
||
popularity: 75,
|
||
category: 'core',
|
||
},
|
||
{
|
||
key: FEATURES.INDEPENDENT_SITE,
|
||
name: '独立站',
|
||
description: '创建和管理独立电商网站',
|
||
icon: <GlobalOutlined style={{ fontSize: 24 }} />,
|
||
requiredPlan: ['pro', 'enterprise'],
|
||
benefits: [
|
||
'自定义品牌站点',
|
||
'独立域名绑定',
|
||
'SEO优化工具',
|
||
'独立支付集成',
|
||
],
|
||
popularity: 65,
|
||
category: 'advanced',
|
||
},
|
||
{
|
||
key: FEATURES.ADVANCED_ANALYTICS,
|
||
name: '高级数据分析',
|
||
description: '深度数据分析和可视化报表',
|
||
icon: <LineChartOutlined style={{ fontSize: 24 }} />,
|
||
requiredPlan: ['pro', 'enterprise'],
|
||
benefits: [
|
||
'自定义报表',
|
||
'数据导出',
|
||
'趋势预测',
|
||
'竞品分析报告',
|
||
],
|
||
limitations: [
|
||
'基础版: 仅基础报表',
|
||
'专业版: 完整分析功能',
|
||
],
|
||
popularity: 80,
|
||
category: 'advanced',
|
||
},
|
||
{
|
||
key: FEATURES.API_ACCESS,
|
||
name: 'API访问',
|
||
description: '开放API接口,支持系统集成',
|
||
icon: <ApiOutlined style={{ fontSize: 24 }} />,
|
||
requiredPlan: ['enterprise'],
|
||
benefits: [
|
||
'RESTful API',
|
||
'Webhook支持',
|
||
'SDK下载',
|
||
'技术文档',
|
||
],
|
||
popularity: 55,
|
||
category: 'enterprise',
|
||
},
|
||
];
|
||
|
||
const PLAN_DETAILS = {
|
||
free: {
|
||
name: '免费版',
|
||
price: 0,
|
||
color: '#d9d9d9',
|
||
gradient: 'linear-gradient(135deg, #d9d9d9 0%, #bfbfbf 100%)',
|
||
features: ['基础商品管理', '订单管理', '基础报表'],
|
||
limitations: ['1个店铺', '每日3次AI调用', '无API访问'],
|
||
recommended: false,
|
||
popular: false,
|
||
icon: <ClockCircleOutlined />,
|
||
description: '适合个人用户和小规模卖家',
|
||
},
|
||
basic: {
|
||
name: '基础版',
|
||
price: 99,
|
||
color: '#1890ff',
|
||
gradient: 'linear-gradient(135deg, #1890ff 0%, #096dd9 100%)',
|
||
features: ['多店铺管理(3个)', 'AI运营(100次/日)', 'B2B贸易', '基础定价建议'],
|
||
limitations: ['无独立站', '无API访问'],
|
||
recommended: true,
|
||
popular: true,
|
||
icon: <RocketOutlined />,
|
||
description: '适合成长中的电商卖家',
|
||
},
|
||
pro: {
|
||
name: '专业版',
|
||
price: 299,
|
||
color: '#faad14',
|
||
gradient: 'linear-gradient(135deg, #faad14 0%, #d48806 100%)',
|
||
features: ['多店铺管理(10个)', '无限AI调用', '自动定价', '独立站', '高级分析'],
|
||
limitations: ['无API访问'],
|
||
recommended: false,
|
||
popular: false,
|
||
icon: <StarOutlined />,
|
||
description: '适合专业电商运营团队',
|
||
},
|
||
enterprise: {
|
||
name: '企业版',
|
||
price: 999,
|
||
color: '#722ed1',
|
||
gradient: 'linear-gradient(135deg, #722ed1 0%, #531dab 100%)',
|
||
features: ['无限店铺', '全部功能', 'API访问', '专属客服', '定制开发'],
|
||
limitations: [],
|
||
recommended: false,
|
||
popular: false,
|
||
icon: <CrownOutlined />,
|
||
description: '适合大型企业和定制化需求',
|
||
},
|
||
};
|
||
|
||
const USAGE_DATA = {
|
||
aiCalls: { used: 45, total: 100, plan: 'basic' },
|
||
shops: { used: 2, total: 3, plan: 'basic' },
|
||
storage: { used: 75, total: 100, unit: 'GB', plan: 'basic' },
|
||
};
|
||
|
||
interface PlanComparisonData {
|
||
key: string;
|
||
name: string;
|
||
icon: React.ReactNode;
|
||
description: string;
|
||
popularity: number;
|
||
category: 'core' | 'advanced' | 'enterprise';
|
||
free: boolean;
|
||
basic: boolean;
|
||
pro: boolean;
|
||
enterprise: boolean;
|
||
current: boolean;
|
||
}
|
||
|
||
const SubscriptionManage: React.FC = () => {
|
||
const { currentUser, hasFeature, isPaidUser, getPlanLabel } = useUser();
|
||
const [previewFeature, setPreviewFeature] = useState<FeatureConfig | null>(null);
|
||
const [simulatedPlan, setSimulatedPlan] = useState<string | null>(null);
|
||
const [billingCycle, setBillingCycle] = useState<'monthly' | 'yearly'>('monthly');
|
||
const [selectedTab, setSelectedTab] = useState('overview');
|
||
|
||
const currentPlan = currentUser.subscription?.plan || 'free';
|
||
|
||
const handleFeaturePreview = (feature: FeatureConfig) => {
|
||
setPreviewFeature(feature);
|
||
};
|
||
|
||
const handleSimulatePlan = (plan: string) => {
|
||
setSimulatedPlan(plan);
|
||
message.success(`已切换到${PLAN_DETAILS[plan as keyof typeof PLAN_DETAILS].name}预览模式`);
|
||
};
|
||
|
||
const handleUpgrade = (plan: string) => {
|
||
message.info(`升级到${PLAN_DETAILS[plan as keyof typeof PLAN_DETAILS].name}功能开发中`);
|
||
};
|
||
|
||
const isFeatureAvailable = (featureKey: string) => {
|
||
if (simulatedPlan) {
|
||
const feature = FEATURE_CONFIGS.find(f => f.key === featureKey);
|
||
return feature?.requiredPlan.includes(simulatedPlan) ?? false;
|
||
}
|
||
return hasFeature(featureKey);
|
||
};
|
||
|
||
const getDiscount = () => {
|
||
return billingCycle === 'yearly' ? 20 : 0;
|
||
};
|
||
|
||
const getAnnualPrice = (monthlyPrice: number) => {
|
||
const annualPrice = monthlyPrice * 12;
|
||
const discount = getDiscount();
|
||
return Math.round(annualPrice * (1 - discount / 100));
|
||
};
|
||
|
||
const getDisplayPrice = (price: number) => {
|
||
if (billingCycle === 'yearly') {
|
||
return Math.round(price * (1 - getDiscount() / 100));
|
||
}
|
||
return price;
|
||
};
|
||
|
||
const planComparisonData = useMemo((): PlanComparisonData[] => {
|
||
return FEATURE_CONFIGS.map(feature => ({
|
||
key: feature.key,
|
||
name: feature.name,
|
||
icon: feature.icon,
|
||
description: feature.description,
|
||
popularity: feature.popularity || 0,
|
||
category: feature.category,
|
||
free: feature.requiredPlan.includes('free'),
|
||
basic: feature.requiredPlan.includes('basic'),
|
||
pro: feature.requiredPlan.includes('pro'),
|
||
enterprise: feature.requiredPlan.includes('enterprise'),
|
||
current: isFeatureAvailable(feature.key),
|
||
}));
|
||
}, [simulatedPlan]);
|
||
|
||
const upgradePath = useMemo(() => {
|
||
const planOrder = ['free', 'basic', 'pro', 'enterprise'];
|
||
const currentIndex = planOrder.indexOf(currentPlan);
|
||
return planOrder.slice(currentIndex + 1);
|
||
}, [currentPlan]);
|
||
|
||
const renderPlanCard = (planKey: string, plan: any) => (
|
||
<Col xs={24} sm={12} lg={6} key={planKey}>
|
||
<Card
|
||
hoverable
|
||
style={{
|
||
height: '100%',
|
||
borderColor: currentPlan === planKey ? '#1890ff' : undefined,
|
||
borderWidth: currentPlan === planKey ? 2 : 1,
|
||
borderRadius: '12px',
|
||
transition: 'all 0.3s ease',
|
||
background: planKey === simulatedPlan ? 'linear-gradient(135deg, #e6f7ff 0%, #bae7ff 100%)' : undefined,
|
||
}}
|
||
bodyStyle={{ padding: '24px' }}
|
||
onClick={() => handleSimulatePlan(planKey)}
|
||
>
|
||
<div style={{ textAlign: 'center' }}>
|
||
<div style={{ marginBottom: '12px' }}>
|
||
<div style={{
|
||
fontSize: '32px',
|
||
color: plan.color,
|
||
marginBottom: '8px'
|
||
}}>
|
||
{plan.icon}
|
||
</div>
|
||
<Tag
|
||
color={plan.color}
|
||
style={{
|
||
fontSize: '14px',
|
||
padding: '4px 16px',
|
||
borderRadius: '20px',
|
||
fontWeight: 500
|
||
}}
|
||
>
|
||
{plan.name}
|
||
</Tag>
|
||
{plan.recommended && (
|
||
<Tag
|
||
color="gold"
|
||
style={{
|
||
marginLeft: '8px',
|
||
borderRadius: '20px',
|
||
fontWeight: 500
|
||
}}
|
||
>
|
||
推荐
|
||
</Tag>
|
||
)}
|
||
{currentPlan === planKey && (
|
||
<Tag
|
||
color="green"
|
||
style={{
|
||
marginLeft: '8px',
|
||
borderRadius: '20px',
|
||
fontWeight: 500
|
||
}}
|
||
>
|
||
当前套餐
|
||
</Tag>
|
||
)}
|
||
{simulatedPlan === planKey && currentPlan !== planKey && (
|
||
<Tag
|
||
color="blue"
|
||
style={{
|
||
marginLeft: '8px',
|
||
borderRadius: '20px',
|
||
fontWeight: 500
|
||
}}
|
||
>
|
||
预览中
|
||
</Tag>
|
||
)}
|
||
</div>
|
||
|
||
<div style={{ margin: '16px 0' }}>
|
||
{billingCycle === 'yearly' && planKey !== 'free' && (
|
||
<div style={{ marginBottom: '8px' }}>
|
||
<Text type="secondary" style={{ fontSize: '12px', textDecoration: 'line-through' }}>
|
||
¥{plan.price * 12}/年
|
||
</Text>
|
||
<Tag color="red" style={{ marginLeft: '8px', fontSize: '12px' }}>
|
||
省{getDiscount()}%
|
||
</Tag>
|
||
</div>
|
||
)}
|
||
<Title level={3} style={{ margin: 0, color: plan.color }}>
|
||
¥{getDisplayPrice(plan.price)}
|
||
<Text type="secondary" style={{ fontSize: '14px', fontWeight: 'normal' }}>
|
||
/{billingCycle === 'yearly' ? '年' : '月'}
|
||
</Text>
|
||
</Title>
|
||
{billingCycle === 'yearly' && planKey !== 'free' && (
|
||
<Text type="secondary" style={{ fontSize: '12px' }}>
|
||
平均 ¥{Math.round(getDisplayPrice(plan.price) / 12)}/月
|
||
</Text>
|
||
)}
|
||
</div>
|
||
|
||
<Paragraph
|
||
type="secondary"
|
||
style={{
|
||
fontSize: '13px',
|
||
marginBottom: '16px',
|
||
minHeight: '40px'
|
||
}}
|
||
>
|
||
{plan.description}
|
||
</Paragraph>
|
||
|
||
<Divider style={{ margin: '12px 0' }} />
|
||
|
||
<div style={{ textAlign: 'left', marginBottom: '16px' }}>
|
||
{plan.features.slice(0, 4).map((f: string, i: number) => (
|
||
<div key={i} style={{ marginBottom: '8px', fontSize: '13px' }}>
|
||
<CheckCircleOutlined style={{ color: '#52c41a', marginRight: '8px', fontSize: '14px' }} />
|
||
<Text>{f}</Text>
|
||
</div>
|
||
))}
|
||
{plan.features.length > 4 && (
|
||
<Text type="secondary" style={{ fontSize: '12px' }}>
|
||
+{plan.features.length - 4} 更多功能
|
||
</Text>
|
||
)}
|
||
</div>
|
||
|
||
{plan.limitations.length > 0 && (
|
||
<div style={{ textAlign: 'left', marginBottom: '16px' }}>
|
||
{plan.limitations.slice(0, 2).map((l: string, i: number) => (
|
||
<div key={i} style={{ marginBottom: '6px', fontSize: '12px' }}>
|
||
<CloseCircleOutlined style={{ color: '#d9d9d9', marginRight: '8px', fontSize: '12px' }} />
|
||
<Text type="secondary">{l}</Text>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
<Button
|
||
type={currentPlan === planKey ? 'default' : 'primary'}
|
||
block
|
||
size="large"
|
||
style={{
|
||
borderRadius: '8px',
|
||
height: '44px',
|
||
fontSize: '16px',
|
||
fontWeight: 500
|
||
}}
|
||
disabled={currentPlan === planKey}
|
||
onClick={() => currentPlan !== planKey && handleUpgrade(planKey)}
|
||
>
|
||
{currentPlan === planKey ? '当前套餐' : '立即升级'}
|
||
</Button>
|
||
</div>
|
||
</Card>
|
||
</Col>
|
||
);
|
||
|
||
const renderUsageOverview = () => (
|
||
<Row gutter={[16, 16]}>
|
||
<Col xs={24} md={12} lg={8}>
|
||
<Card>
|
||
<Statistic
|
||
title={
|
||
<Space>
|
||
<RobotOutlined style={{ color: '#1890ff' }} />
|
||
<span>AI调用次数</span>
|
||
</Space>
|
||
}
|
||
value={USAGE_DATA.aiCalls.used}
|
||
suffix={`/ ${USAGE_DATA.aiCalls.total}`}
|
||
valueStyle={{ color: '#1890ff' }}
|
||
/>
|
||
<Progress
|
||
percent={USAGE_DATA.aiCalls.used}
|
||
status={USAGE_DATA.aiCalls.used >= 80 ? 'exception' : 'normal'}
|
||
strokeColor={USAGE_DATA.aiCalls.used >= 80 ? '#ff4d4f' : '#1890ff'}
|
||
/>
|
||
<Text type="secondary" style={{ fontSize: '12px' }}>
|
||
剩余 {USAGE_DATA.aiCalls.total - USAGE_DATA.aiCalls.used} 次
|
||
</Text>
|
||
</Card>
|
||
</Col>
|
||
<Col xs={24} md={12} lg={8}>
|
||
<Card>
|
||
<Statistic
|
||
title={
|
||
<Space>
|
||
<ShopOutlined style={{ color: '#52c41a' }} />
|
||
<span>店铺数量</span>
|
||
</Space>
|
||
}
|
||
value={USAGE_DATA.shops.used}
|
||
suffix={`/ ${USAGE_DATA.shops.total}`}
|
||
valueStyle={{ color: '#52c41a' }}
|
||
/>
|
||
<Progress
|
||
percent={Math.round((USAGE_DATA.shops.used / USAGE_DATA.shops.total) * 100)}
|
||
status="normal"
|
||
strokeColor="#52c41a"
|
||
/>
|
||
<Text type="secondary" style={{ fontSize: '12px' }}>
|
||
剩余 {USAGE_DATA.shops.total - USAGE_DATA.shops.used} 个
|
||
</Text>
|
||
</Card>
|
||
</Col>
|
||
<Col xs={24} md={12} lg={8}>
|
||
<Card>
|
||
<Statistic
|
||
title={
|
||
<Space>
|
||
<DollarOutlined style={{ color: '#faad14' }} />
|
||
<span>存储空间</span>
|
||
</Space>
|
||
}
|
||
value={USAGE_DATA.storage.used}
|
||
suffix={`/ ${USAGE_DATA.storage.total} ${USAGE_DATA.storage.unit}`}
|
||
valueStyle={{ color: '#faad14' }}
|
||
/>
|
||
<Progress
|
||
percent={USAGE_DATA.storage.used}
|
||
status={USAGE_DATA.storage.used >= 80 ? 'exception' : 'normal'}
|
||
strokeColor={USAGE_DATA.storage.used >= 80 ? '#ff4d4f' : '#faad14'}
|
||
/>
|
||
<Text type="secondary" style={{ fontSize: '12px' }}>
|
||
剩余 {USAGE_DATA.storage.total - USAGE_DATA.storage.used} {USAGE_DATA.storage.unit}
|
||
</Text>
|
||
</Card>
|
||
</Col>
|
||
</Row>
|
||
);
|
||
|
||
const renderFeatureComparison = () => {
|
||
const columns: ColumnsType<PlanComparisonData> = [
|
||
{
|
||
title: '功能',
|
||
dataIndex: 'name',
|
||
key: 'name',
|
||
width: 200,
|
||
render: (name: string, record: PlanComparisonData) => (
|
||
<Space direction="vertical" size={0}>
|
||
<Space>
|
||
{record.icon}
|
||
<Text strong>{name}</Text>
|
||
</Space>
|
||
<Text type="secondary" style={{ fontSize: '12px' }}>
|
||
{record.description}
|
||
</Text>
|
||
<div>
|
||
<Tooltip title="用户使用热度">
|
||
<Progress
|
||
percent={record.popularity}
|
||
size="small"
|
||
showInfo={false}
|
||
strokeColor="#1890ff"
|
||
/>
|
||
</Tooltip>
|
||
<Text type="secondary" style={{ fontSize: '11px', marginLeft: '8px' }}>
|
||
{record.popularity}% 热度
|
||
</Text>
|
||
</div>
|
||
</Space>
|
||
),
|
||
},
|
||
{
|
||
title: '免费版',
|
||
key: 'free',
|
||
align: 'center' as const,
|
||
render: (_: any, record: PlanComparisonData) =>
|
||
record.free ?
|
||
<CheckCircleOutlined style={{ color: '#52c41a', fontSize: '18px' }} /> :
|
||
<CloseCircleOutlined style={{ color: '#d9d9d9', fontSize: '18px' }} />,
|
||
},
|
||
{
|
||
title: '基础版',
|
||
key: 'basic',
|
||
align: 'center' as const,
|
||
render: (_: any, record: PlanComparisonData) =>
|
||
record.basic ?
|
||
<CheckCircleOutlined style={{ color: '#52c41a', fontSize: '18px' }} /> :
|
||
<CloseCircleOutlined style={{ color: '#d9d9d9', fontSize: '18px' }} />,
|
||
},
|
||
{
|
||
title: '专业版',
|
||
key: 'pro',
|
||
align: 'center' as const,
|
||
render: (_: any, record: PlanComparisonData) =>
|
||
record.pro ?
|
||
<CheckCircleOutlined style={{ color: '#52c41a', fontSize: '18px' }} /> :
|
||
<CloseCircleOutlined style={{ color: '#d9d9d9', fontSize: '18px' }} />,
|
||
},
|
||
{
|
||
title: '企业版',
|
||
key: 'enterprise',
|
||
align: 'center' as const,
|
||
render: (_: any, record: PlanComparisonData) =>
|
||
record.enterprise ?
|
||
<CheckCircleOutlined style={{ color: '#52c41a', fontSize: '18px' }} /> :
|
||
<CloseCircleOutlined style={{ color: '#d9d9d9', fontSize: '18px' }} />,
|
||
},
|
||
{
|
||
title: '当前状态',
|
||
key: 'status',
|
||
align: 'center' as const,
|
||
render: (_: any, record: PlanComparisonData) => (
|
||
<Tag color={record.current ? 'green' : 'default'} style={{ borderRadius: '12px' }}>
|
||
{record.current ? '已开通' : '未开通'}
|
||
</Tag>
|
||
),
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'action',
|
||
width: 100,
|
||
align: 'center' as const,
|
||
render: (_: any, record: PlanComparisonData) => (
|
||
<Button
|
||
type="link"
|
||
size="small"
|
||
onClick={() => handleFeaturePreview(FEATURE_CONFIGS.find(f => f.key === record.key)!)}
|
||
>
|
||
详情
|
||
</Button>
|
||
),
|
||
},
|
||
];
|
||
|
||
return (
|
||
<Table
|
||
columns={columns}
|
||
dataSource={planComparisonData}
|
||
rowKey="key"
|
||
pagination={false}
|
||
size="middle"
|
||
rowClassName={(record) => record.current ? 'highlight-row' : ''}
|
||
/>
|
||
);
|
||
};
|
||
|
||
const renderUpgradePath = () => {
|
||
const steps = upgradePath.map((planKey, index) => ({
|
||
title: PLAN_DETAILS[planKey as keyof typeof PLAN_DETAILS].name,
|
||
description: PLAN_DETAILS[planKey as keyof typeof PLAN_DETAILS].description,
|
||
icon: PLAN_DETAILS[planKey as keyof typeof PLAN_DETAILS].icon,
|
||
}));
|
||
|
||
return (
|
||
<Card title="升级路径">
|
||
<Alert
|
||
message="升级建议"
|
||
description="根据您的使用情况,我们推荐您升级到更高级别的套餐以获得更多功能"
|
||
type="info"
|
||
showIcon
|
||
style={{ marginBottom: '24px' }}
|
||
/>
|
||
<Steps
|
||
current={0}
|
||
items={steps}
|
||
style={{ marginBottom: '24px' }}
|
||
/>
|
||
<Space direction="vertical" style={{ width: '100%' }}>
|
||
{upgradePath.map((planKey, index) => {
|
||
const plan = PLAN_DETAILS[planKey as keyof typeof PLAN_DETAILS];
|
||
const additionalFeatures = FEATURE_CONFIGS.filter(f =>
|
||
f.requiredPlan.includes(planKey) && !isFeatureAvailable(f.key)
|
||
);
|
||
return (
|
||
<Card
|
||
key={planKey}
|
||
size="small"
|
||
style={{
|
||
borderLeft: `4px solid ${plan.color}`,
|
||
marginBottom: '12px'
|
||
}}
|
||
>
|
||
<Row gutter={16} align="middle">
|
||
<Col flex="auto">
|
||
<Space direction="vertical" size={0}>
|
||
<Space>
|
||
<Text strong style={{ fontSize: '16px' }}>
|
||
{plan.name}
|
||
</Text>
|
||
<Tag color={plan.color} style={{ borderRadius: '12px' }}>
|
||
¥{getDisplayPrice(plan.price)}/{billingCycle === 'yearly' ? '年' : '月'}
|
||
</Tag>
|
||
</Space>
|
||
<Text type="secondary" style={{ fontSize: '13px' }}>
|
||
{plan.description}
|
||
</Text>
|
||
<div>
|
||
<Text type="secondary" style={{ fontSize: '12px' }}>
|
||
新增功能:
|
||
</Text>
|
||
<Space wrap style={{ marginLeft: '8px' }}>
|
||
{additionalFeatures.slice(0, 3).map(f => (
|
||
<Tag key={f.key} color="blue" style={{ fontSize: '11px' }}>
|
||
{f.name}
|
||
</Tag>
|
||
))}
|
||
{additionalFeatures.length > 3 && (
|
||
<Tag color="default" style={{ fontSize: '11px' }}>
|
||
+{additionalFeatures.length - 3}
|
||
</Tag>
|
||
)}
|
||
</Space>
|
||
</div>
|
||
</Space>
|
||
</Col>
|
||
<Col>
|
||
<Button
|
||
type="primary"
|
||
icon={<RightOutlined />}
|
||
onClick={() => handleUpgrade(planKey)}
|
||
>
|
||
升级
|
||
</Button>
|
||
</Col>
|
||
</Row>
|
||
</Card>
|
||
);
|
||
})}
|
||
</Space>
|
||
</Card>
|
||
);
|
||
};
|
||
|
||
return (
|
||
<div className="subscription-manage" style={{ padding: '24px', background: '#f5f5f5', minHeight: '100vh' }}>
|
||
<ConfigProvider
|
||
theme={{
|
||
token: {
|
||
colorPrimary: '#1890ff',
|
||
borderRadius: 8,
|
||
},
|
||
}}
|
||
>
|
||
<div className="page-header" style={{ marginBottom: '24px' }}>
|
||
<Row gutter={[16, 16]} align="middle">
|
||
<Col xs={24} md={16}>
|
||
<Space direction="vertical" size={4}>
|
||
<Title level={2} style={{ margin: 0 }}>
|
||
<CrownOutlined style={{ marginRight: '8px', color: '#faad14' }} />
|
||
订阅管理中心
|
||
</Title>
|
||
<Paragraph type="secondary" style={{ margin: 0 }}>
|
||
管理您的订阅计划和付费功能,预览不同套餐的功能差异
|
||
</Paragraph>
|
||
</Space>
|
||
</Col>
|
||
<Col xs={24} md={8} style={{ textAlign: 'right' }}>
|
||
<Space>
|
||
<Radio.Group
|
||
value={billingCycle}
|
||
onChange={(e) => setBillingCycle(e.target.value)}
|
||
size="large"
|
||
>
|
||
<Radio.Button value="monthly">月付</Radio.Button>
|
||
<Radio.Button value="yearly">
|
||
年付
|
||
{getDiscount() > 0 && (
|
||
<Tag color="red" style={{ marginLeft: '4px' }}>
|
||
省{getDiscount()}%
|
||
</Tag>
|
||
)}
|
||
</Radio.Button>
|
||
</Radio.Group>
|
||
</Space>
|
||
</Col>
|
||
</Row>
|
||
</div>
|
||
|
||
<Alert
|
||
message="预览模式"
|
||
description="点击下方套餐卡片可以预览不同套餐的功能权限,实际功能以当前订阅为准"
|
||
type="info"
|
||
showIcon
|
||
icon={<InfoCircleOutlined />}
|
||
closable
|
||
style={{ marginBottom: '24px', borderRadius: '8px' }}
|
||
/>
|
||
|
||
<Tabs
|
||
activeKey={selectedTab}
|
||
onChange={setSelectedTab}
|
||
size="large"
|
||
items={[
|
||
{
|
||
key: 'overview',
|
||
label: (
|
||
<span>
|
||
<DollarOutlined />
|
||
套餐概览
|
||
</span>
|
||
),
|
||
children: (
|
||
<>
|
||
<Row gutter={[16, 16]} style={{ marginBottom: '24px' }}>
|
||
{Object.entries(PLAN_DETAILS).map(([key, plan]) =>
|
||
renderPlanCard(key, plan)
|
||
)}
|
||
</Row>
|
||
|
||
<Card title="功能使用情况" style={{ marginBottom: '24px' }}>
|
||
{renderUsageOverview()}
|
||
</Card>
|
||
|
||
<Card title="当前订阅状态">
|
||
<Row gutter={24}>
|
||
<Col xs={24} md={8}>
|
||
<div style={{ textAlign: 'center', padding: '24px', background: '#fafafa', borderRadius: '8px' }}>
|
||
<Tag
|
||
color={PLAN_DETAILS[currentPlan as keyof typeof PLAN_DETAILS]?.color || 'default'}
|
||
style={{
|
||
fontSize: '16px',
|
||
padding: '8px 24px',
|
||
borderRadius: '20px',
|
||
marginBottom: '12px'
|
||
}}
|
||
>
|
||
{getPlanLabel()}
|
||
</Tag>
|
||
<Title level={4} style={{ marginTop: '12px', marginBottom: '8px' }}>当前套餐</Title>
|
||
<Text type="secondary" style={{ fontSize: '13px' }}>
|
||
{currentUser.subscription?.expiresAt ?
|
||
`到期时间: ${currentUser.subscription.expiresAt}` :
|
||
'永久有效'}
|
||
</Text>
|
||
</div>
|
||
</Col>
|
||
<Col xs={24} md={8}>
|
||
<div style={{ textAlign: 'center', padding: '24px', background: '#fafafa', borderRadius: '8px' }}>
|
||
<Title level={2} style={{ color: '#52c41a', marginBottom: '8px' }}>
|
||
{currentUser.subscription?.features.length || 0}
|
||
</Title>
|
||
<Text type="secondary">已开通功能</Text>
|
||
</div>
|
||
</Col>
|
||
<Col xs={24} md={8}>
|
||
<div style={{ textAlign: 'center', padding: '24px', background: '#fafafa', borderRadius: '8px' }}>
|
||
<Title level={2} style={{ color: '#1890ff', marginBottom: '8px' }}>
|
||
{FEATURE_CONFIGS.filter(f => !hasFeature(f.key)).length}
|
||
</Title>
|
||
<Text type="secondary">可升级功能</Text>
|
||
</div>
|
||
</Col>
|
||
</Row>
|
||
</Card>
|
||
</>
|
||
),
|
||
},
|
||
{
|
||
key: 'comparison',
|
||
label: (
|
||
<span>
|
||
<LineChartOutlined />
|
||
功能对比
|
||
</span>
|
||
),
|
||
children: (
|
||
<Card title="套餐功能对比表">
|
||
{renderFeatureComparison()}
|
||
</Card>
|
||
),
|
||
},
|
||
{
|
||
key: 'upgrade',
|
||
label: (
|
||
<span>
|
||
<RocketOutlined />
|
||
升级路径
|
||
</span>
|
||
),
|
||
children: renderUpgradePath(),
|
||
},
|
||
]}
|
||
/>
|
||
|
||
<Modal
|
||
title={
|
||
<Space>
|
||
{previewFeature?.icon}
|
||
<span>{previewFeature?.name}</span>
|
||
</Space>
|
||
}
|
||
open={!!previewFeature}
|
||
onCancel={() => setPreviewFeature(null)}
|
||
footer={[
|
||
<Button key="close" onClick={() => setPreviewFeature(null)}>
|
||
关闭
|
||
</Button>,
|
||
!isFeatureAvailable(previewFeature?.key || '') && (
|
||
<Button
|
||
key="upgrade"
|
||
type="primary"
|
||
icon={<RocketOutlined />}
|
||
onClick={() => {
|
||
setPreviewFeature(null);
|
||
setSelectedTab('upgrade');
|
||
}}
|
||
>
|
||
查看升级方案
|
||
</Button>
|
||
),
|
||
]}
|
||
width={700}
|
||
>
|
||
{previewFeature && (
|
||
<div>
|
||
<Paragraph style={{ fontSize: '15px', marginBottom: '24px' }}>
|
||
{previewFeature.description}
|
||
</Paragraph>
|
||
|
||
<Space direction="vertical" style={{ width: '100%', marginBottom: '24px' }}>
|
||
<Title level={5}>功能权益</Title>
|
||
{previewFeature.benefits.map((b: string, i: number) => (
|
||
<div key={i} style={{ marginBottom: '12px' }}>
|
||
<CheckCircleOutlined style={{ color: '#52c41a', marginRight: '12px', fontSize: '16px' }} />
|
||
<Text style={{ fontSize: '14px' }}>{b}</Text>
|
||
</div>
|
||
))}
|
||
</Space>
|
||
|
||
{previewFeature.limitations && (
|
||
<Space direction="vertical" style={{ width: '100%', marginBottom: '24px' }}>
|
||
<Title level={5}>版本限制</Title>
|
||
{previewFeature.limitations.map((l: string, i: number) => (
|
||
<div key={i} style={{ marginBottom: '8px' }}>
|
||
<Text type="secondary" style={{ fontSize: '13px' }}>
|
||
• {l}
|
||
</Text>
|
||
</div>
|
||
))}
|
||
</Space>
|
||
)}
|
||
|
||
<Divider />
|
||
|
||
<Space direction="vertical" style={{ width: '100%', marginBottom: '16px' }}>
|
||
<Title level={5}>所需套餐</Title>
|
||
<Space>
|
||
{previewFeature.requiredPlan.map((p: string) => (
|
||
<Tag
|
||
key={p}
|
||
color={PLAN_DETAILS[p as keyof typeof PLAN_DETAILS]?.color}
|
||
style={{
|
||
fontSize: '14px',
|
||
padding: '4px 12px',
|
||
borderRadius: '12px'
|
||
}}
|
||
>
|
||
{PLAN_DETAILS[p as keyof typeof PLAN_DETAILS]?.name}
|
||
</Tag>
|
||
))}
|
||
</Space>
|
||
</Space>
|
||
|
||
<div style={{
|
||
padding: '16px',
|
||
background: isFeatureAvailable(previewFeature.key) ? '#f6ffed' : '#fff7e6',
|
||
borderRadius: '8px',
|
||
border: `1px solid ${isFeatureAvailable(previewFeature.key) ? '#b7eb8f' : '#ffd591'}`
|
||
}}>
|
||
<Space>
|
||
{isFeatureAvailable(previewFeature.key) ? (
|
||
<CheckCircleOutlined style={{ color: '#52c41a', fontSize: '20px' }} />
|
||
) : (
|
||
<InfoCircleOutlined style={{ color: '#faad14', fontSize: '20px' }} />
|
||
)}
|
||
<Text
|
||
strong
|
||
style={{
|
||
color: isFeatureAvailable(previewFeature.key) ? '#52c41a' : '#faad14',
|
||
fontSize: '15px'
|
||
}}
|
||
>
|
||
{isFeatureAvailable(previewFeature.key) ? '✓ 您已拥有此功能' : '✗ 需要升级套餐'}
|
||
</Text>
|
||
</Space>
|
||
</div>
|
||
|
||
{previewFeature.popularity && (
|
||
<div style={{ marginTop: '16px' }}>
|
||
<Space>
|
||
<Text type="secondary">用户使用热度:</Text>
|
||
<Progress
|
||
percent={previewFeature.popularity}
|
||
size="small"
|
||
strokeColor="#1890ff"
|
||
format={(percent) => `${percent}%`}
|
||
/>
|
||
</Space>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
</Modal>
|
||
</ConfigProvider>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default SubscriptionManage; |