Files
app/wei-ai-demo/pages/TopUp.tsx
2026-01-28 19:10:19 +08:00

176 lines
9.1 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 } from 'react';
import { ChevronLeft, Zap, CreditCard, Check, ShieldCheck, Gem } from 'lucide-react';
interface TopUpProps {
onBack: () => void;
}
const RECHARGE_OPTIONS = [
{ id: 1, points: 60, price: '¥6.00', bonus: null, tag: null },
{ id: 2, points: 300, price: '¥30.00', bonus: '+15', tag: null },
{ id: 3, points: 680, price: '¥68.00', bonus: '+50', tag: '热销' },
{ id: 4, points: 1280, price: '¥128.00', bonus: '+120', tag: null },
{ id: 5, points: 3280, price: '¥328.00', bonus: '+350', tag: '超值' },
{ id: 6, points: 6480, price: '¥648.00', bonus: '+800', tag: null },
];
const PAYMENT_METHODS = [
{ id: 'alipay', name: '支付宝', icon: '支' },
{ id: 'wechat', name: '微信支付', icon: '微' },
];
const TopUp: React.FC<TopUpProps> = ({ onBack }) => {
const [selectedOption, setSelectedOption] = useState<number>(3);
const [paymentMethod, setPaymentMethod] = useState('alipay');
const [isProcessing, setIsProcessing] = useState(false);
const handlePay = () => {
setIsProcessing(true);
setTimeout(() => {
setIsProcessing(false);
alert('模拟支付成功!积分已到账。');
onBack();
}, 1500);
};
return (
<div className="fixed inset-0 z-[60] bg-[#0F1014] flex flex-col h-full overflow-hidden animate-in slide-in-from-bottom-10 duration-300 fade-in">
{/* Header */}
<div className="relative z-20 pt-safe-top px-4 py-3 flex items-center justify-between border-b border-white/5 bg-[#0F1014]/80 backdrop-blur-md">
<button onClick={onBack} className="p-2 -ml-2 text-white/70 hover:text-white active:scale-95 transition-transform">
<ChevronLeft size={24} />
</button>
<h1 className="text-base font-bold text-white tracking-wider"></h1>
<div className="w-8"></div> {/* Spacer */}
</div>
<div className="flex-1 overflow-y-auto no-scrollbar pb-32">
{/* Current Balance Card */}
<div className="mx-4 mt-6 mb-8 relative h-32 rounded-2xl overflow-hidden shadow-[0_10px_30px_rgba(139,92,246,0.15)] group">
<div className="absolute inset-0 bg-gradient-to-br from-[#1C1F26] via-[#2D3039] to-[#1C1F26]"></div>
{/* Neon Accents */}
<div className="absolute top-0 right-0 w-32 h-32 bg-[#8B5CF6]/20 blur-[50px] rounded-full"></div>
<div className="absolute bottom-0 left-0 w-24 h-24 bg-[#F43F5E]/10 blur-[40px] rounded-full"></div>
<div className="relative z-10 p-6 flex flex-col justify-between h-full">
<div className="flex items-center gap-2 opacity-70">
<Gem size={14} className="text-[#8B5CF6]" />
<span className="text-xs font-bold text-[#E2E8F0] tracking-widest uppercase"></span>
</div>
<div className="flex items-end gap-3">
<span className="text-4xl font-bold text-white font-mono tracking-tighter drop-shadow-lg">2,450</span>
<span className="text-sm font-medium text-[#8B5CF6] mb-1.5"></span>
</div>
</div>
{/* Decorative Pattern */}
<div className="absolute right-4 top-1/2 -translate-y-1/2 opacity-5 pointer-events-none">
<Zap size={100} />
</div>
</div>
{/* Recharge Options Grid */}
<div className="px-4">
<h3 className="text-xs font-bold text-[#64748B] uppercase tracking-widest mb-4 flex items-center gap-2">
<Zap size={12} />
</h3>
<div className="grid grid-cols-2 gap-3">
{RECHARGE_OPTIONS.map((opt) => {
const isSelected = selectedOption === opt.id;
return (
<button
key={opt.id}
onClick={() => setSelectedOption(opt.id)}
className={`relative p-4 rounded-xl border flex flex-col items-start transition-all duration-300 active:scale-[0.98] ${
isSelected
? 'bg-[#8B5CF6]/10 border-[#8B5CF6] shadow-[0_0_15px_rgba(139,92,246,0.15)]'
: 'bg-[#1C1F26]/60 border-white/5 hover:border-white/10'
}`}
>
{opt.tag && (
<div className={`absolute -top-2.5 -right-2 px-2 py-0.5 rounded text-[9px] font-bold tracking-wider uppercase border shadow-sm ${
opt.tag === '热销'
? 'bg-[#F43F5E] text-white border-[#F43F5E]'
: 'bg-[#F59E0B] text-black border-[#F59E0B]'
}`}>
{opt.tag}
</div>
)}
<div className="flex items-center gap-1.5 mb-1">
<Zap size={14} className={isSelected ? 'text-[#8B5CF6]' : 'text-[#64748B]'} fill={isSelected ? "currentColor" : "none"} />
<span className={`text-lg font-bold font-mono ${isSelected ? 'text-white' : 'text-[#E2E8F0]'}`}>
{opt.points}
</span>
</div>
{opt.bonus && (
<span className="text-[10px] text-[#10B981] font-mono mb-2 block">
{opt.bonus}
</span>
)}
<span className={`text-sm font-medium mt-auto ${isSelected ? 'text-[#8B5CF6]' : 'text-[#94A3B8]'}`}>
{opt.price}
</span>
</button>
);
})}
</div>
</div>
{/* Payment Method (Visual Only) */}
<div className="px-4 mt-8">
<h3 className="text-xs font-bold text-[#64748B] uppercase tracking-widest mb-3"></h3>
<div className="space-y-2">
{PAYMENT_METHODS.map(method => (
<button
key={method.id}
onClick={() => setPaymentMethod(method.id)}
className={`w-full flex items-center justify-between p-3 rounded-xl border transition-all ${
paymentMethod === method.id
? 'bg-[#1C1F26] border-[#8B5CF6]/50'
: 'bg-transparent border-white/5 opacity-60'
}`}
>
<div className="flex items-center gap-3">
<div className={`w-8 h-8 rounded flex items-center justify-center font-bold text-white ${method.id === 'alipay' ? 'bg-[#1677FF]' : 'bg-[#07C160]'}`}>
{method.icon}
</div>
<span className="text-sm text-white">{method.name}</span>
</div>
{paymentMethod === method.id && <Check size={16} className="text-[#8B5CF6]" />}
</button>
))}
</div>
</div>
{/* Terms */}
<div className="px-6 mt-8 mb-4 flex items-start gap-2 text-[10px] text-[#64748B] leading-tight">
<ShieldCheck size={12} className="shrink-0 mt-0.5" />
<p>退</p>
</div>
</div>
{/* Bottom Action Bar */}
<div className="absolute bottom-0 left-0 w-full p-4 bg-[#0F1014]/90 backdrop-blur-xl border-t border-white/5 z-30">
<button
onClick={handlePay}
disabled={isProcessing}
className="w-full h-12 rounded-xl bg-gradient-to-r from-[#8B5CF6] to-[#6366f1] text-white font-bold tracking-wide shadow-[0_0_20px_rgba(139,92,246,0.3)] active:scale-[0.98] transition-transform flex items-center justify-center gap-2 disabled:opacity-70 disabled:cursor-not-allowed"
>
{isProcessing ? (
<span className="animate-pulse">...</span>
) : (
<>
<CreditCard size={18} />
{RECHARGE_OPTIONS.find(o => o.id === selectedOption)?.price}
</>
)}
</button>
</div>
</div>
);
};
export default TopUp;