初始提交: Qihu-AIFlow v2.0.1 完整项目

- 前端 React+TypeScript (源码+构建产物)
- 后端 Python FastAPI
- 数据库迁移 001-015
- Docker部署配置
- 微信ClawBot API频道绑定
- 项目文档 README.md
This commit is contained in:
qyyfw committed 2026-07-02 14:40:46 +08:00
commit 78ff261b5a
199 files changed
+69126

No files matched your search

+662
View File
@@ -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;