feat: app 端 ui 设计完成

This commit is contained in:
liqupan
2026-01-28 19:10:19 +08:00
commit a4e7898e94
149 changed files with 11302 additions and 0 deletions

176
wei-ai-demo/pages/TopUp.tsx Normal file
View File

@@ -0,0 +1,176 @@
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;