import React, { useState, useEffect, useCallback } from 'react'; import { adminAPI } from '@/api'; const SVGIcon: React.FC<{ children: React.ReactNode; size?: number }> = ({ children, size = 16 }) => ( {children} ); const levelColors: Record = { 0: { bg: 'var(--color-bg-tertiary)', accent: 'var(--color-text-secondary)', label: '免费版' }, 1: { bg: 'rgba(99,102,241,0.1)', accent: '#818cf8', label: '基础版' }, 2: { bg: 'rgba(34,197,94,0.1)', accent: '#22c55e', label: '专业版' }, 3: { bg: 'rgba(245,158,11,0.1)', accent: '#f59e0b', label: '企业版' }, }; const Toggle: React.FC<{ checked: boolean; onChange: (v: boolean) => void }> = ({ checked, onChange }) => ( ); interface Plan { id: string; name: string; level: number; duration_type: string; price: number; daily_free_tokens: number; features: string[]; free_app_ids: string[]; free_model_ids: string[]; is_active: boolean; max_agents: number; max_workflows: number; max_knowledge_bases: number; max_storage_mb: number; api_calls_per_day: number; api_calls_per_min: number; can_publish_apps: boolean; can_create_api_keys: boolean; can_use_workflow: boolean; can_use_mcp: boolean; can_use_code_exec: boolean; can_use_privacy_mode: boolean; can_use_dag: boolean; allowed_model_tiers: string[]; permissions: Record; } interface UserMembership { user_id: string; username: string; email: string; membership_plan_id: string; membership_plan_name: string; membership_level: number; membership_expire_at: string; auto_renew: boolean; member_status: string; } interface GlobalConfig { id: string; key: string; value: any; type: string; description: string; } const Membership: React.FC = () => { const [activeTab, setActiveTab] = useState<'plans' | 'benefits' | 'users' | 'configs' | 'stats'>('plans'); const [plans, setPlans] = useState([]); const [loading, setLoading] = useState(false); const [editingPlan, setEditingPlan] = useState(null); const [showModal, setShowModal] = useState(false); const [showAssignModal, setShowAssignModal] = useState(false); const [assignUserId, setAssignUserId] = useState(''); const [assignPlanId, setAssignPlanId] = useState(''); const [assignDays, setAssignDays] = useState(30); // User memberships const [userMemberships, setUserMemberships] = useState([]); const [userTotal, setUserTotal] = useState(0); const [userPage, setUserPage] = useState(1); // Configs const [configs, setConfigs] = useState([]); // Stats const [stats, setStats] = useState(null); const fetchPlans = useCallback(async () => { setLoading(true); try { const res = await adminAPI.getPlans(); setPlans(Array.isArray(res.data) ? res.data : (Array.isArray(res) ? res : [])); } catch (e) { console.error('fetchPlans:', e); } setLoading(false); }, []); const fetchUserMemberships = useCallback(async () => { try { const res = await adminAPI.getUserMemberships(userPage); const d = res.data || res; setUserMemberships(d?.items || []); setUserTotal(d?.total || 0); } catch (e) { console.error('fetchUserMemberships:', e); } }, [userPage]); const fetchConfigs = useCallback(async () => { try { const res = await adminAPI.getMembershipConfigs(); setConfigs(Array.isArray(res.data) ? res.data : (Array.isArray(res) ? res : [])); } catch (e) { console.error('fetchConfigs:', e); } }, []); const fetchStats = useCallback(async () => { try { const res = await adminAPI.getMembershipStats(); setStats(res.data || res); } catch (e) { console.error('fetchStats:', e); } }, []); useEffect(() => { fetchPlans(); }, [fetchPlans]); useEffect(() => { if (activeTab === 'users') fetchUserMemberships(); if (activeTab === 'configs') fetchConfigs(); if (activeTab === 'stats') fetchStats(); }, [activeTab, fetchUserMemberships, fetchConfigs, fetchStats]); // ===================== Plan Edit Form ===================== const emptyForm: Plan = { id: '', name: '', level: 0, duration_type: 'month', price: 0, daily_free_tokens: 0, features: [], free_app_ids: [], free_model_ids: [], is_active: true, max_agents: 5, max_workflows: 3, max_knowledge_bases: 2, max_storage_mb: 1024, api_calls_per_day: 100, api_calls_per_min: 10, can_publish_apps: false, can_create_api_keys: false, can_use_workflow: false, can_use_mcp: false, can_use_code_exec: false, can_use_privacy_mode: false, can_use_dag: false, allowed_model_tiers: ['free'], permissions: {}, }; const [form, setForm] = useState(emptyForm); const [newFeature, setNewFeature] = useState(''); const handleEdit = (plan: Plan) => { setEditingPlan(plan); setForm({ ...plan }); setShowModal(true); }; const handleCreate = () => { setEditingPlan(null); setForm({ ...emptyForm }); setShowModal(true); }; const handleSave = async () => { try { const params = new URLSearchParams(); Object.keys(form).forEach(k => { if (k === 'id' || k === 'created_at' || k === 'updated_at') return; const val = (form as any)[k]; if (val === null || val === undefined) return; if (typeof val === 'boolean') params.append(k, String(val)); else if (Array.isArray(val)) params.append(k, JSON.stringify(val)); else if (typeof val === 'object') params.append(k, JSON.stringify(val)); else params.append(k, String(val)); }); if (editingPlan) { await adminAPI.updatePlan(editingPlan.id, `?${params.toString()}`); } else { await adminAPI.addPlan(`?${params.toString()}`); } setShowModal(false); fetchPlans(); } catch (e) { console.error('handleSave:', e); alert('保存失败: ' + (e as any)?.message); } }; const handleDelete = async (id: string) => { if (!confirm('确定删除此会员计划?')) return; try { await adminAPI.deletePlan(id); fetchPlans(); } catch (e) { alert('删除失败: ' + (e as any)?.message); } }; const addFeature = () => { if (newFeature.trim()) { setForm({ ...form, features: [...form.features, newFeature.trim()] }); setNewFeature(''); } }; const toggleModelTier = (tier: string) => { setForm(prev => ({ ...prev, allowed_model_tiers: prev.allowed_model_tiers.includes(tier) ? prev.allowed_model_tiers.filter(t => t !== tier) : [...prev.allowed_model_tiers, tier], })); }; // ===================== Assign Plan ===================== const handleAssignPlan = async () => { if (!assignUserId || !assignPlanId) { alert('请填写用户ID和选择计划'); return; } try { await adminAPI.assignPlan(assignUserId, assignPlanId, assignDays); setShowAssignModal(false); setAssignUserId(''); setAssignPlanId(''); setAssignDays(30); fetchUserMemberships(); } catch (e) { alert('分配失败: ' + (e as any)?.message); } }; const handleRevoke = async (userId: string, username: string) => { if (!confirm(`确定撤销 ${username} 的会员?`)) return; try { await adminAPI.revokeMembership(userId); fetchUserMemberships(); } catch (e) { alert('撤销失败: ' + (e as any)?.message); } }; const handleUpdateConfig = async (key: string, value: any) => { try { const params = new URLSearchParams(); params.append('value', String(value)); await adminAPI.updateMembershipConfig(key, `?${params.toString()}`); fetchConfigs(); } catch (e) { alert('更新失败: ' + (e as any)?.message); } }; // ===================== Render ===================== const tabs = [ { key: 'plans', label: '会员计划', icon: <> }, { key: 'benefits', label: '权益对比', icon: <> }, { key: 'users', label: '用户会员', icon: <> }, { key: 'configs', label: '全局配置', icon: <> }, { key: 'stats', label: '统计概览', icon: <> }, ]; const inputStyle: React.CSSProperties = { background: 'var(--color-bg-secondary)', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-md)', padding: '8px 12px', color: 'var(--color-text)', fontSize: 'var(--text-sm)', width: '100%', outline: 'none', }; const labelStyle: React.CSSProperties = { fontSize: 'var(--text-xs)', color: 'var(--color-text-tertiary)', fontWeight: 600, marginBottom: 4, display: 'block', }; const sectionStyle: React.CSSProperties = { padding: 'var(--space-lg)', borderBottom: '1px solid var(--color-border)', }; return (

会员订阅体系管理

{activeTab === 'plans' && ( )}
{/* Tabs */}
{tabs.map(tab => ( ))}
{/* ===================== Plans Tab ===================== */} {activeTab === 'plans' && (
{plans.map((plan) => { const colors = levelColors[plan.level] || levelColors[0]; return (
{colors.label} · Level {plan.level}

{plan.name}

{/* Price */}
价格
¥{plan.price}
每日Token
{plan.daily_free_tokens?.toLocaleString()}
{/* Quotas */}
{[ { label: 'Agent', value: plan.max_agents }, { label: '工作流', value: plan.max_workflows }, { label: '知识库', value: plan.max_knowledge_bases }, { label: '存储(MB)', value: plan.max_storage_mb }, { label: 'API/日', value: plan.api_calls_per_day }, { label: 'API/分', value: plan.api_calls_per_min }, ].map((item, i) => (
{item.label}
{item.value >= 999 ? '∞' : item.value?.toLocaleString()}
))}
{/* Permissions */}
{[ { key: 'can_publish_apps', label: '发布应用' }, { key: 'can_create_api_keys', label: 'API密钥' }, { key: 'can_use_workflow', label: '工作流' }, { key: 'can_use_mcp', label: 'MCP' }, { key: 'can_use_code_exec', label: '代码执行' }, { key: 'can_use_privacy_mode', label: '隐私模式' }, { key: 'can_use_dag', label: 'DAG' }, ].map(perm => { const enabled = (plan as any)[perm.key]; return enabled ? ( ✓ {perm.label} ) : ( ✗ {perm.label} ); })}
{/* Features */} {plan.features && plan.features.length > 0 && (
会员权益
    {plan.features.map((f, i) => (
  • {f}
  • ))}
)}
); })}
)} {/* ===================== Benefits Comparison Tab ===================== */} {activeTab === 'benefits' && (
{plans.map(p => { const c = levelColors[p.level] || levelColors[0]; return ; })} {[ { key: 'price', label: '月价格(元)', type: 'number' }, { key: 'daily_free_tokens', label: '每日免费Token', type: 'number' }, { key: 'max_agents', label: '最大Agent数', type: 'number' }, { key: 'max_workflows', label: '最大工作流数', type: 'number' }, { key: 'max_knowledge_bases', label: '最大知识库数', type: 'number' }, { key: 'max_storage_mb', label: '存储配额(MB)', type: 'number' }, { key: 'api_calls_per_day', label: '每日API限额', type: 'number' }, { key: 'api_calls_per_min', label: '每分钟API限额', type: 'number' }, { key: 'can_publish_apps', label: '发布应用到市场', type: 'boolean' }, { key: 'can_create_api_keys', label: '创建开发者API密钥', type: 'boolean' }, { key: 'can_use_workflow', label: '工作流编排', type: 'boolean' }, { key: 'can_use_mcp', label: 'MCP工具', type: 'boolean' }, { key: 'can_use_code_exec', label: '代码执行', type: 'boolean' }, { key: 'can_use_privacy_mode', label: '隐私模式', type: 'boolean' }, { key: 'can_use_dag', label: 'DAG多Agent', type: 'boolean' }, { key: 'allowed_model_tiers', label: '可用模型层级', type: 'list' }, ].map(row => ( {plans.map(p => { const val = (p as any)[row.key]; let display; if (row.type === 'boolean') { display = val ? ✓ : ✗; } else if (row.type === 'number') { display = val >= 999 ? '不限' : (val?.toLocaleString() || '0'); } else if (row.type === 'list') { display = Array.isArray(val) ? val.join(', ') : '-'; } else { display = String(val ?? '-'); } return ; })} ))}
权益项 {p.name}
{row.label}{display}
)} {/* ===================== Users Tab ===================== */} {activeTab === 'users' && (
{userMemberships.length === 0 ? ( ) : userMemberships.map(u => ( ))}
用户名邮箱会员计划等级 到期时间自动续费状态操作
暂无数据
{u.username} {u.email || '-'} {u.membership_plan_name} Level {u.membership_level} {u.membership_expire_at ? new Date(u.membership_expire_at).toLocaleString('zh-CN') : '-'} {u.auto_renew ? '✓' : '✗'} {u.member_status === 'active' ? '活跃' : u.member_status === 'expired' ? '已过期' : '免费'} {u.membership_level > 0 && ( )}
{userTotal > 20 && (
第 {userPage} 页
)}
)} {/* ===================== Configs Tab ===================== */} {activeTab === 'configs' && (

全局会员配置

{configs.map(cfg => (
{cfg.description}
{cfg.key}
{cfg.type === 'boolean' ? ( handleUpdateConfig(cfg.key, v)} /> ) : cfg.type === 'number' ? ( handleUpdateConfig(cfg.key, e.target.value)} style={{ ...inputStyle, width: 80 }} /> ) : ( handleUpdateConfig(cfg.key, e.target.value)} style={{ ...inputStyle, width: 200 }} /> )}
))}
)} {/* ===================== Stats Tab ===================== */} {activeTab === 'stats' && stats && (
{[ { label: '总用户数', value: stats.total_users, color: '#818cf8' }, { label: '活跃会员', value: stats.active_members, color: '#22c55e' }, { label: '已过期', value: stats.expired_members, color: '#ef4444' }, { label: '免费用户', value: stats.free_users, color: 'var(--color-text-secondary)' }, { label: '自动续费', value: stats.auto_renew_count, color: '#f59e0b' }, { label: '今日新增', value: stats.today_new_subs, color: '#06b6d4' }, ].map((s, i) => (
{s.value?.toLocaleString()}
{s.label}
))}

各计划订阅统计

{stats.plan_stats?.map((ps: any) => ( ))}
计划名称等级用户数状态
{ps.plan_name} Level {ps.level} {ps.user_count} {ps.is_active ? 启用 : 停用}
)} {/* ===================== Edit/Create Plan Modal ===================== */} {showModal && (
setShowModal(false)}>
e.stopPropagation()}>

{editingPlan ? `编辑 ${editingPlan.name}` : '新建会员计划'}

{/* Basic Info */}
基础信息
setForm({ ...form, name: e.target.value })} />
setForm({ ...form, level: Number(e.target.value) })} />
setForm({ ...form, price: Number(e.target.value) })} />
setForm({ ...form, daily_free_tokens: Number(e.target.value) })} />
{/* Quotas */}
配额限制
setForm({ ...form, max_agents: Number(e.target.value) })} />
setForm({ ...form, max_workflows: Number(e.target.value) })} />
setForm({ ...form, max_knowledge_bases: Number(e.target.value) })} />
setForm({ ...form, max_storage_mb: Number(e.target.value) })} />
setForm({ ...form, api_calls_per_day: Number(e.target.value) })} />
setForm({ ...form, api_calls_per_min: Number(e.target.value) })} />
{/* Permissions */}
功能权限
{[ { key: 'can_publish_apps', label: '发布应用到市场' }, { key: 'can_create_api_keys', label: '创建开发者API密钥' }, { key: 'can_use_workflow', label: '使用工作流编排' }, { key: 'can_use_mcp', label: '使用MCP工具' }, { key: 'can_use_code_exec', label: '使用代码执行' }, { key: 'can_use_privacy_mode', label: '使用隐私模式' }, { key: 'can_use_dag', label: '使用DAG多Agent' }, ].map(perm => ( ))}
{['free', 'standard', 'premium'].map(tier => ( ))}
{/* Features */}
会员权益标签
{form.features.map((f, i) => ( {f} ))}
setNewFeature(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); addFeature(); } }} placeholder="输入权益名称,回车添加" />
{/* Actions */}
)} {/* ===================== Assign Plan Modal ===================== */} {showAssignModal && (
setShowAssignModal(false)}>
e.stopPropagation()}>

分配会员计划

setAssignUserId(e.target.value)} placeholder="输入用户ID" />
setAssignDays(Number(e.target.value))} />
)}
); }; export default Membership;