初始提交: Qihu-AIFlow v2.0.1 完整项目
- 前端 React+TypeScript (源码+构建产物) - 后端 Python FastAPI - 数据库迁移 001-015 - Docker部署配置 - 微信ClawBot API频道绑定 - 项目文档 README.md
This commit is contained in:
commit
78ff261b5a
199 files changed
+69126
No files matched your search
@@ -0,0 +1,662 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { paymentAPI } from '@/api';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import './PricingPage.css';
|
||||
|
||||
// ---- SVG Icons ----
|
||||
const IconCheck = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<polyline points="20 6 9 17 4 12" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
);
|
||||
const IconService = () => (
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
||||
<rect x="3" y="3" width="18" height="18" rx="3" stroke="currentColor" strokeWidth="1.8" />
|
||||
<line x1="9" y1="9" x2="15" y2="9" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
||||
<line x1="9" y1="13" x2="13" y2="13" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
||||
<line x1="9" y1="17" x2="11" y2="17" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
||||
</svg>
|
||||
);
|
||||
const IconTag = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5z" stroke="currentColor" strokeWidth="1.8" strokeLinejoin="round" />
|
||||
<path d="M2 17l10 5 10-5" stroke="currentColor" strokeWidth="1.8" strokeLinejoin="round" />
|
||||
<path d="M2 12l10 5 10-5" stroke="currentColor" strokeWidth="1.8" strokeLinejoin="round" />
|
||||
</svg>
|
||||
);
|
||||
const IconCoupon = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<rect x="2" y="5" width="20" height="14" rx="2" stroke="currentColor" strokeWidth="1.6" />
|
||||
<line x1="12" y1="5" x2="12" y2="19" stroke="currentColor" strokeWidth="1.6" strokeDasharray="2 2" />
|
||||
<circle cx="6" cy="12" r="3" stroke="currentColor" strokeWidth="1.2" />
|
||||
<text x="18" y="13.5" textAnchor="middle" fontSize="5" fontWeight="800" fill="currentColor">OFF</text>
|
||||
</svg>
|
||||
);
|
||||
const IconClock = () => (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none">
|
||||
<circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="1.6" />
|
||||
<polyline points="12 6 12 12 16 14" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
);
|
||||
const IconSparkles = () => (
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 2l1.5 5h5l-4 3 1.5 5-4-3-4 3 1.5-5-4-3h5L12 2z" stroke="currentColor" strokeWidth="1.6" strokeLinejoin="round" />
|
||||
</svg>
|
||||
);
|
||||
const IconAppCustom = () => (
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none">
|
||||
<rect x="3" y="3" width="7" height="7" rx="1.5" stroke="currentColor" strokeWidth="1.6" />
|
||||
<rect x="14" y="3" width="7" height="7" rx="1.5" stroke="currentColor" strokeWidth="1.6" />
|
||||
<rect x="3" y="14" width="7" height="7" rx="1.5" stroke="currentColor" strokeWidth="1.6" />
|
||||
<rect x="14" y="14" width="7" height="7" rx="1.5" stroke="currentColor" strokeWidth="1.6" />
|
||||
</svg>
|
||||
);
|
||||
const IconPlatform = () => (
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none">
|
||||
<rect x="2" y="9" width="20" height="13" rx="2" stroke="currentColor" strokeWidth="1.6" />
|
||||
<path d="M6 9V7a6 6 0 0112 0v2" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" />
|
||||
<circle cx="12" cy="16" r="2" stroke="currentColor" strokeWidth="1.4" />
|
||||
</svg>
|
||||
);
|
||||
const IconDataCustom = () => (
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none">
|
||||
<ellipse cx="12" cy="5" rx="9" ry="3" stroke="currentColor" strokeWidth="1.6" />
|
||||
<path d="M3 5v14c0 1.66 4.03 3 9 3s9-1.34 9-3V5" stroke="currentColor" strokeWidth="1.6" />
|
||||
<path d="M3 12c0 1.66 4.03 3 9 3s9-1.34 9-3" stroke="currentColor" strokeWidth="1.2" strokeDasharray="2 2" />
|
||||
</svg>
|
||||
);
|
||||
const IconPrivateProject = () => (
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M3 21h18" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" />
|
||||
<path d="M5 21V7l7-4 7 4v14" stroke="currentColor" strokeWidth="1.6" strokeLinejoin="round" />
|
||||
<path d="M9 14v7" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
|
||||
<path d="M15 11v10" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
|
||||
</svg>
|
||||
);
|
||||
const IconWechat = () => (
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M8 12a1.5 1.5 0 100-3 1.5 1.5 0 000 3z" fill="currentColor" />
|
||||
<path d="M15 10.5a1.5 1.5 0 100-3 1.5 1.5 0 000 3z" fill="currentColor" />
|
||||
<path d="M21 13.5c0 3.6-3.7 6.5-8.3 6.5-.8 0-1.5-.1-2.2-.2L7 21l1.3-3.2C6.5 16.5 5 15.1 5 13.5 5 9.9 8.7 7 13.3 7S21 9.9 21 13.5z" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" />
|
||||
</svg>
|
||||
);
|
||||
const IconAlipay = () => (
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none">
|
||||
<rect x="2" y="4" width="20" height="16" rx="3" stroke="currentColor" strokeWidth="1.8" />
|
||||
<path d="M8 14.5c1.5-2.5 3.5-4 5.5-4.5" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />
|
||||
<path d="M7 12l4-3.5" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />
|
||||
<circle cx="14" cy="14" r="1" fill="currentColor" />
|
||||
<text x="12" y="17" textAnchor="middle" fontSize="5" fontWeight="700" fill="currentColor">Alipay</text>
|
||||
</svg>
|
||||
);
|
||||
|
||||
function formatYuan(n: number): string {
|
||||
return n.toLocaleString('en-US');
|
||||
}
|
||||
|
||||
interface ServiceItem {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
type: string;
|
||||
price: number;
|
||||
features?: string[];
|
||||
assigned_agent_id?: string;
|
||||
}
|
||||
|
||||
interface CouponItem {
|
||||
id: string;
|
||||
code: string;
|
||||
type: string;
|
||||
value: number;
|
||||
min_amount: number;
|
||||
expire_at: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
const SERVICE_TYPE_CONFIG: Record<string, { label: string; icon: React.ReactNode; color: string; bg: string }> = {
|
||||
app_custom: { label: '应用定制', icon: <IconAppCustom />, color: '#4F46E5', bg: '#EEF2FF' },
|
||||
platform_proxy: { label: '平台代理', icon: <IconPlatform />, color: '#059669', bg: '#ECFDF5' },
|
||||
data_custom: { label: '数据定制', icon: <IconDataCustom />, color: '#D97706', bg: '#FFFBEB' },
|
||||
private_project: { label: '私有化项目', icon: <IconPrivateProject />, color: '#7C3AED', bg: '#F5F3FF' },
|
||||
};
|
||||
|
||||
const PricingPage: React.FC = () => {
|
||||
const { user } = useAuth();
|
||||
const [billingCycle, setBillingCycle] = useState<'month' | 'quarter' | 'year'>('month');
|
||||
const [plans, setPlans] = useState<any[]>([]);
|
||||
const [services, setServices] = useState<ServiceItem[]>([]);
|
||||
const [coupons, setCoupons] = useState<CouponItem[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [subscribeMsg, setSubscribeMsg] = useState('');
|
||||
|
||||
// Modal states
|
||||
const [showModal, setShowModal] = useState<string | null>(null); // service type or null
|
||||
const [appCustomType, setAppCustomType] = useState<string>('skill'); // skill/agent/mcp
|
||||
const [platformForm, setPlatformForm] = useState({ companyName: '', contactName: '', usage: '' });
|
||||
const [dataRequestForm, setDataRequestForm] = useState({ title: '', description: '', requirements: '' });
|
||||
const [privateProjectForm, setPrivateProjectForm] = useState({ title: '', description: '', requirements: '' });
|
||||
const [submitLoading, setSubmitLoading] = useState(false);
|
||||
|
||||
const currentPlanLevel = user?.membership_level ?? 0;
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const [plansRes, servicesRes, couponsRes] = await Promise.all([
|
||||
paymentAPI.getPlans(),
|
||||
paymentAPI.getServices(),
|
||||
paymentAPI.getCoupons(),
|
||||
]);
|
||||
if (plansRes.code === 0) setPlans(plansRes.data);
|
||||
if (servicesRes.code === 0) setServices(servicesRes.data);
|
||||
if (couponsRes.code === 0) setCoupons(couponsRes.data);
|
||||
} catch (e) {
|
||||
console.error('加载付费数据失败', e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, [loadData]);
|
||||
|
||||
const getPrice = (plan: any, cycle: typeof billingCycle) => {
|
||||
const base = Number(plan.price) || 0;
|
||||
switch (cycle) {
|
||||
case 'quarter': return Math.round(base * 2.7);
|
||||
case 'year': return Math.round(base * 10);
|
||||
default: return base;
|
||||
}
|
||||
};
|
||||
|
||||
const getCycleLabel = (cycle: typeof billingCycle) => {
|
||||
switch (cycle) { case 'month': return '/月'; case 'quarter': return '/季'; case 'year': return '/年'; }
|
||||
};
|
||||
|
||||
const handleSubscribe = async (plan: any) => {
|
||||
try {
|
||||
setSubscribeMsg('');
|
||||
const res = await paymentAPI.createOrder({
|
||||
item_id: plan.id,
|
||||
order_type: 'membership',
|
||||
});
|
||||
if (res.code === 0) {
|
||||
const payRes = await paymentAPI.payOrder(res.data.id);
|
||||
if (payRes.code === 0) {
|
||||
setSubscribeMsg('成功订阅 ' + plan.name + '!');
|
||||
await loadData();
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
setSubscribeMsg('订阅失败:' + (e.message || '未知错误'));
|
||||
}
|
||||
};
|
||||
|
||||
// 应用定制服务 - 提交
|
||||
const handleAppCustomSubmit = async (serviceId: string) => {
|
||||
try {
|
||||
setSubmitLoading(true);
|
||||
const res = await paymentAPI.createCustomOrder({
|
||||
service_id: serviceId,
|
||||
description: '应用定制服务 - ' + (appCustomType === 'skill' ? 'Skills' : appCustomType === 'agent' ? 'Agent' : 'MCP') + ' 类型',
|
||||
budget: 0,
|
||||
contact_info: '',
|
||||
});
|
||||
if (res.code === 0) {
|
||||
setSubscribeMsg('应用定制服务已提交!即将跳转到定制对话窗口...');
|
||||
setShowModal(null);
|
||||
// 跳转到聊天页面,带定制服务ID
|
||||
setTimeout(() => {
|
||||
window.location.hash = '#/chat?service_id=' + res.data.id;
|
||||
}, 1500);
|
||||
}
|
||||
} catch (e: any) {
|
||||
setSubscribeMsg('提交失败:' + (e.message || '未知错误'));
|
||||
} finally {
|
||||
setSubmitLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 平台代理申请 - 提交
|
||||
const handlePlatformProxySubmit = async (serviceId: string) => {
|
||||
if (!platformForm.companyName.trim()) {
|
||||
setSubscribeMsg('请填写公司名称');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setSubmitLoading(true);
|
||||
const res = await paymentAPI.createServiceRequest({
|
||||
service_id: serviceId,
|
||||
title: '平台代理申请 - ' + platformForm.companyName,
|
||||
description: '公司名称: ' + platformForm.companyName + '\n联系人: ' + platformForm.contactName + '\n用途说明: ' + platformForm.usage,
|
||||
requirements: {
|
||||
company_name: platformForm.companyName,
|
||||
contact_name: platformForm.contactName,
|
||||
usage: platformForm.usage,
|
||||
},
|
||||
});
|
||||
if (res.code === 0) {
|
||||
setSubscribeMsg('平台代理申请已成功提交!工单号: ' + res.data.id);
|
||||
setShowModal(null);
|
||||
setPlatformForm({ companyName: '', contactName: '', usage: '' });
|
||||
await loadData();
|
||||
}
|
||||
} catch (e: any) {
|
||||
setSubscribeMsg('提交失败:' + (e.message || '未知错误'));
|
||||
} finally {
|
||||
setSubmitLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 数据定制服务 - 提交
|
||||
const handleDataCustomSubmit = async (serviceId: string) => {
|
||||
if (!dataRequestForm.title.trim()) {
|
||||
setSubscribeMsg('请填写需求标题');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setSubmitLoading(true);
|
||||
const res = await paymentAPI.createServiceRequest({
|
||||
service_id: serviceId,
|
||||
title: dataRequestForm.title,
|
||||
description: dataRequestForm.description,
|
||||
requirements: { detail: dataRequestForm.requirements },
|
||||
});
|
||||
if (res.code === 0) {
|
||||
setSubscribeMsg('数据定制需求已成功提交!工单号: ' + res.data.id);
|
||||
setShowModal(null);
|
||||
setDataRequestForm({ title: '', description: '', requirements: '' });
|
||||
await loadData();
|
||||
}
|
||||
} catch (e: any) {
|
||||
setSubscribeMsg('提交失败:' + (e.message || '未知错误'));
|
||||
} finally {
|
||||
setSubmitLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 私有化项目服务 - 提交
|
||||
const handlePrivateProjectSubmit = async (serviceId: string) => {
|
||||
if (!privateProjectForm.title.trim()) {
|
||||
setSubscribeMsg('请填写咨询标题');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setSubmitLoading(true);
|
||||
const res = await paymentAPI.createServiceRequest({
|
||||
service_id: serviceId,
|
||||
title: privateProjectForm.title,
|
||||
description: privateProjectForm.description,
|
||||
requirements: { detail: privateProjectForm.requirements },
|
||||
});
|
||||
if (res.code === 0) {
|
||||
setSubscribeMsg('私有化项目咨询已成功提交!工单号: ' + res.data.id);
|
||||
setShowModal(null);
|
||||
setPrivateProjectForm({ title: '', description: '', requirements: '' });
|
||||
await loadData();
|
||||
}
|
||||
} catch (e: any) {
|
||||
setSubscribeMsg('提交失败:' + (e.message || '未知错误'));
|
||||
} finally {
|
||||
setSubmitLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getServiceAction = (svc: ServiceItem) => {
|
||||
switch (svc.type) {
|
||||
case 'app_custom':
|
||||
return { label: '前往定制', action: () => setShowModal('app_custom_' + svc.id) };
|
||||
case 'platform_proxy':
|
||||
return { label: '立即申请', action: () => setShowModal('platform_proxy_' + svc.id) };
|
||||
case 'data_custom':
|
||||
return { label: '提交需求', action: () => setShowModal('data_custom_' + svc.id) };
|
||||
case 'private_project':
|
||||
return { label: '咨询方案', action: () => setShowModal('private_project_' + svc.id) };
|
||||
default:
|
||||
return { label: '了解更多', action: () => {} };
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) return <div className="pricing-page"><p style={{textAlign:'center',marginTop:60}}>加载中...</p></div>;
|
||||
|
||||
return (
|
||||
<div className="pricing-page">
|
||||
<div className="pricing-header">
|
||||
<h1 className="pricing-title">订阅付费中心</h1>
|
||||
<p className="pricing-subtitle">选择最适合你的方案,解锁 AI 无限可能</p>
|
||||
</div>
|
||||
|
||||
<div className="pricing-cycle-tabs">
|
||||
{(['month', 'quarter', 'year'] as const).map((cycle) => (
|
||||
<button
|
||||
key={cycle}
|
||||
className={`pricing-cycle-tab ${billingCycle === cycle ? 'active' : ''}`}
|
||||
onClick={() => setBillingCycle(cycle)}
|
||||
>
|
||||
{cycle === 'month' ? '月付' : cycle === 'quarter' ? '季付' : '年付'}
|
||||
{cycle === 'year' && <span className="pricing-savings-badge">84折</span>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{subscribeMsg && <div className="pricing-subscribe-msg">{subscribeMsg}</div>}
|
||||
|
||||
<div className="pricing-plans-grid">
|
||||
{plans.map((plan: any, idx: number) => {
|
||||
const isCurrent = currentPlanLevel === plan.level;
|
||||
const price = getPrice(plan, billingCycle);
|
||||
const isPopular = idx === 1;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={plan.id}
|
||||
className={`pricing-plan-card ${isCurrent ? 'pricing-plan--current' : ''} ${isPopular ? 'pricing-plan--popular' : ''}`}
|
||||
>
|
||||
{isPopular && (
|
||||
<div className="pricing-popular-ribbon"><IconSparkles /><span>最受欢迎</span></div>
|
||||
)}
|
||||
{isCurrent && <div className="pricing-current-badge">当前方案</div>}
|
||||
<div className="pricing-plan-head">
|
||||
<span className="pricing-plan-name">{plan.name}</span>
|
||||
<div className="pricing-plan-price-row">
|
||||
<span className="pricing-plan-price">
|
||||
<span className="pricing-currency">$</span>{price === 0 ? '0' : formatYuan(price)}
|
||||
</span>
|
||||
<span className="pricing-plan-cycle">{getCycleLabel(billingCycle)}</span>
|
||||
</div>
|
||||
<p className="pricing-plan-tokens">日赠 {formatYuan(plan.daily_free_tokens)} Tokens</p>
|
||||
</div>
|
||||
<div className="pricing-plan-features">
|
||||
{(plan.features || []).map((feat: string, i: number) => (
|
||||
<div key={i} className="pricing-feature-item">
|
||||
<span className="pricing-feature-check"><IconCheck /></span>
|
||||
<span>{feat}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
className={`pricing-subscribe-btn ${isCurrent ? 'pricing-btn--current' : ''}`}
|
||||
onClick={() => !isCurrent && handleSubscribe(plan)}
|
||||
>
|
||||
{isCurrent ? '当前方案' : '立即订阅'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 增值服务 - 四大类 */}
|
||||
<div className="pricing-section">
|
||||
<div className="pricing-section-header">
|
||||
<IconService />
|
||||
<h2 className="pricing-section-title">增值服务</h2>
|
||||
</div>
|
||||
<div className="pricing-services-grid-v2">
|
||||
{services.map((svc) => {
|
||||
const config = SERVICE_TYPE_CONFIG[svc.type] || SERVICE_TYPE_CONFIG.app_custom;
|
||||
const actionInfo = getServiceAction(svc);
|
||||
return (
|
||||
<div key={svc.id} className="pricing-service-card-v2">
|
||||
<div className="pricing-service-card-v2-icon" style={{ background: config.bg, color: config.color }}>
|
||||
{config.icon}
|
||||
</div>
|
||||
<div className="pricing-service-card-v2-body">
|
||||
<span className="pricing-service-card-v2-tag" style={{ color: config.color, background: config.bg }}>
|
||||
{config.label}
|
||||
</span>
|
||||
<h4 className="pricing-service-card-v2-name">{svc.name}</h4>
|
||||
<p className="pricing-service-card-v2-desc">{svc.description}</p>
|
||||
{(svc.features && svc.features.length > 0) && (
|
||||
<div className="pricing-service-card-v2-features">
|
||||
{svc.features.map((feat: string, i: number) => (
|
||||
<span key={i} className="pricing-service-card-v2-feature-tag">{feat}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="pricing-service-card-v2-action">
|
||||
{svc.price > 0 ? (
|
||||
<span className="pricing-service-card-v2-price">{formatYuan(svc.price)} 元起</span>
|
||||
) : (
|
||||
<span className="pricing-service-card-v2-price">免费咨询</span>
|
||||
)}
|
||||
<button
|
||||
className="pricing-service-card-v2-btn"
|
||||
onClick={actionInfo.action}
|
||||
>
|
||||
{actionInfo.label}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{services.length === 0 && <p className="pricing-empty">暂无增值服务</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 优惠券 */}
|
||||
<div className="pricing-section">
|
||||
<div className="pricing-section-header">
|
||||
<IconCoupon />
|
||||
<h2 className="pricing-section-title">优惠券</h2>
|
||||
</div>
|
||||
<div className="pricing-coupons-row">
|
||||
{coupons.map((cp) => {
|
||||
const isPercent = cp.type === 'percent';
|
||||
return (
|
||||
<div key={cp.id} className="pricing-coupon-card">
|
||||
<div className="pricing-coupon-left">
|
||||
<span className="pricing-coupon-value">{isPercent ? `${cp.value}%` : `$${cp.value}`}</span>
|
||||
<span className="pricing-coupon-type">{isPercent ? '折扣券' : '立减券'}</span>
|
||||
</div>
|
||||
<div className="pricing-coupon-right">
|
||||
<div className="pricing-coupon-code">{cp.code}</div>
|
||||
<div className="pricing-coupon-desc">满 {formatYuan(cp.min_amount || 0)} 元可用</div>
|
||||
<div className="pricing-coupon-valid"><IconClock /><span>有效期至 {cp.expire_at || '--'}</span></div>
|
||||
<span className="pricing-coupon-status">{cp.status === 'active' ? '可使用' : cp.status}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{coupons.length === 0 && <p className="pricing-empty">暂无可用优惠券</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 支付方式 */}
|
||||
<div className="pricing-section">
|
||||
<div className="pricing-section-header">
|
||||
<IconTag />
|
||||
<h2 className="pricing-section-title">支付方式</h2>
|
||||
</div>
|
||||
<div className="pricing-payment-row">
|
||||
<div className="pricing-payment-method"><IconWechat /><span>微信支付</span></div>
|
||||
<div className="pricing-payment-method"><IconAlipay /><span>支付宝</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===== Modal - 应用定制服务 ===== */}
|
||||
{showModal && showModal.startsWith('app_custom_') && (
|
||||
<div className="pricing-modal-overlay" onClick={() => setShowModal(null)}>
|
||||
<div className="pricing-modal" onClick={e => e.stopPropagation()}>
|
||||
<h3 className="pricing-modal-title">应用定制服务</h3>
|
||||
<p className="pricing-modal-subtitle">请选择您需要的定制类型</p>
|
||||
<div className="pricing-modal-app-type-grid">
|
||||
{[
|
||||
{ type: 'skill', label: 'Skills', desc: '自定义技能开发,扩展AI能力边界' },
|
||||
{ type: 'agent', label: 'Agent', desc: '智能体定制,构建专属AI助手' },
|
||||
{ type: 'mcp', label: 'MCP', desc: 'MCP协议开发,连接外部数据源' },
|
||||
].map((item) => (
|
||||
<div
|
||||
key={item.type}
|
||||
className={`pricing-modal-app-type-card ${appCustomType === item.type ? 'active' : ''}`}
|
||||
onClick={() => setAppCustomType(item.type)}
|
||||
>
|
||||
<div className="pricing-modal-app-type-label">{item.label}</div>
|
||||
<div className="pricing-modal-app-type-desc">{item.desc}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="pricing-modal-actions">
|
||||
<button
|
||||
className="pricing-modal-btn-cancel"
|
||||
onClick={() => setShowModal(null)}
|
||||
>取消</button>
|
||||
<button
|
||||
className="pricing-modal-btn-confirm"
|
||||
disabled={submitLoading}
|
||||
onClick={() => handleAppCustomSubmit(showModal.replace('app_custom_', ''))}
|
||||
>
|
||||
{submitLoading ? '提交中...' : '确认并前往定制'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ===== Modal - 平台代理申请 ===== */}
|
||||
{showModal && showModal.startsWith('platform_proxy_') && (
|
||||
<div className="pricing-modal-overlay" onClick={() => setShowModal(null)}>
|
||||
<div className="pricing-modal" onClick={e => e.stopPropagation()}>
|
||||
<h3 className="pricing-modal-title">平台代理申请</h3>
|
||||
<p className="pricing-modal-subtitle">填写以下信息,提交代理申请</p>
|
||||
<div className="pricing-modal-form">
|
||||
<div className="pricing-modal-form-group">
|
||||
<label>公司名称 <span className="required">*</span></label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="请输入公司名称"
|
||||
value={platformForm.companyName}
|
||||
onChange={e => setPlatformForm({ ...platformForm, companyName: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="pricing-modal-form-group">
|
||||
<label>联系人</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="请输入联系人姓名"
|
||||
value={platformForm.contactName}
|
||||
onChange={e => setPlatformForm({ ...platformForm, contactName: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="pricing-modal-form-group">
|
||||
<label>用途说明</label>
|
||||
<textarea
|
||||
placeholder="请简要说明代理用途"
|
||||
rows={3}
|
||||
value={platformForm.usage}
|
||||
onChange={e => setPlatformForm({ ...platformForm, usage: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pricing-modal-actions">
|
||||
<button className="pricing-modal-btn-cancel" onClick={() => setShowModal(null)}>取消</button>
|
||||
<button
|
||||
className="pricing-modal-btn-confirm"
|
||||
disabled={submitLoading}
|
||||
onClick={() => handlePlatformProxySubmit(showModal.replace('platform_proxy_', ''))}
|
||||
>{submitLoading ? '提交中...' : '提交申请'}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ===== Modal - 数据定制服务 ===== */}
|
||||
{showModal && showModal.startsWith('data_custom_') && (
|
||||
<div className="pricing-modal-overlay" onClick={() => setShowModal(null)}>
|
||||
<div className="pricing-modal" onClick={e => e.stopPropagation()}>
|
||||
<h3 className="pricing-modal-title">数据定制服务</h3>
|
||||
<p className="pricing-modal-subtitle">请描述您的数据定制需求</p>
|
||||
<div className="pricing-modal-form">
|
||||
<div className="pricing-modal-form-group">
|
||||
<label>需求标题 <span className="required">*</span></label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="请输入需求标题"
|
||||
value={dataRequestForm.title}
|
||||
onChange={e => setDataRequestForm({ ...dataRequestForm, title: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="pricing-modal-form-group">
|
||||
<label>需求描述</label>
|
||||
<textarea
|
||||
placeholder="请描述您的数据定制需求详情"
|
||||
rows={3}
|
||||
value={dataRequestForm.description}
|
||||
onChange={e => setDataRequestForm({ ...dataRequestForm, description: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="pricing-modal-form-group">
|
||||
<label>具体要求</label>
|
||||
<textarea
|
||||
placeholder="请说明数据格式、数量、交付时间等具体要求"
|
||||
rows={2}
|
||||
value={dataRequestForm.requirements}
|
||||
onChange={e => setDataRequestForm({ ...dataRequestForm, requirements: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pricing-modal-actions">
|
||||
<button className="pricing-modal-btn-cancel" onClick={() => setShowModal(null)}>取消</button>
|
||||
<button
|
||||
className="pricing-modal-btn-confirm"
|
||||
disabled={submitLoading}
|
||||
onClick={() => handleDataCustomSubmit(showModal.replace('data_custom_', ''))}
|
||||
>{submitLoading ? '提交中...' : '提交需求'}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ===== Modal - 私有化项目服务 ===== */}
|
||||
{showModal && showModal.startsWith('private_project_') && (
|
||||
<div className="pricing-modal-overlay" onClick={() => setShowModal(null)}>
|
||||
<div className="pricing-modal" onClick={e => e.stopPropagation()}>
|
||||
<h3 className="pricing-modal-title">私有化项目服务</h3>
|
||||
<p className="pricing-modal-subtitle">请描述您的私有化部署需求,我们将为您提供专业方案</p>
|
||||
<div className="pricing-modal-form">
|
||||
<div className="pricing-modal-form-group">
|
||||
<label>项目标题 <span className="required">*</span></label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="请输入项目标题"
|
||||
value={privateProjectForm.title}
|
||||
onChange={e => setPrivateProjectForm({ ...privateProjectForm, title: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="pricing-modal-form-group">
|
||||
<label>项目描述</label>
|
||||
<textarea
|
||||
placeholder="请描述您的私有化部署需求"
|
||||
rows={3}
|
||||
value={privateProjectForm.description}
|
||||
onChange={e => setPrivateProjectForm({ ...privateProjectForm, description: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="pricing-modal-form-group">
|
||||
<label>具体要求</label>
|
||||
<textarea
|
||||
placeholder="请说明部署规模、安全要求、时间计划等"
|
||||
rows={2}
|
||||
value={privateProjectForm.requirements}
|
||||
onChange={e => setPrivateProjectForm({ ...privateProjectForm, requirements: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pricing-modal-actions">
|
||||
<button className="pricing-modal-btn-cancel" onClick={() => setShowModal(null)}>取消</button>
|
||||
<button
|
||||
className="pricing-modal-btn-confirm"
|
||||
disabled={submitLoading}
|
||||
onClick={() => handlePrivateProjectSubmit(showModal.replace('private_project_', ''))}
|
||||
>{submitLoading ? '提交中...' : '提交咨询'}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PricingPage;
|
||||
Reference in new issue
Block a user