Files
makemd/dashboard/src/pages/Settings/SubscriptionManage.tsx
wurenzhi 2748456d8a refactor(services): 重构服务文件结构,将服务按功能分类到不同目录
- 将服务文件按功能分类到core、ai、analytics、security等目录
- 修复logger导入路径问题,统一使用相对路径
- 更新相关文件的导入路径引用
- 添加新的批量操作组件导出文件
- 修复dashboard页面中的类型错误
- 添加dotenv依赖到package.json
2026-03-25 13:46:26 +08:00

1014 lines
34 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;