Twilioapp/src/pages/mobile/Recharge.tsx
2025-06-29 01:33:41 +08:00

497 lines
14 KiB
TypeScript

import { FC, useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { BillingService } from '../../services/billingService';
import { UserAccount, UserType, RechargeRecord } from '../../types/billing';
const MobileRecharge: FC = () => {
const navigate = useNavigate();
const billingService = BillingService.getInstance();
const [userAccount, setUserAccount] = useState<UserAccount | null>(null);
const [selectedAmount, setSelectedAmount] = useState<number>(0);
const [customAmount, setCustomAmount] = useState<string>('');
const [selectedPayment, setSelectedPayment] = useState<'wechat' | 'alipay' | 'card'>('wechat');
const [rechargeHistory, setRechargeHistory] = useState<RechargeRecord[]>([]);
const [isProcessing, setIsProcessing] = useState(false);
// 预设充值金额(分)
const presetAmounts = [
{ value: 5000, label: '¥50', bonus: 0, popular: false },
{ value: 10000, label: '¥100', bonus: 500, popular: true },
{ value: 20000, label: '¥200', bonus: 1500, popular: false },
{ value: 50000, label: '¥500', bonus: 5000, popular: false },
{ value: 100000, label: '¥1000', bonus: 15000, popular: false },
];
const paymentMethods = [
{ id: 'wechat', name: '微信支付', icon: '💚', color: '#07c160' },
{ id: 'alipay', name: '支付宝', icon: '💙', color: '#1677ff' },
{ id: 'card', name: '银行卡', icon: '💳', color: '#722ed1' },
];
useEffect(() => {
const account = billingService.getUserAccount();
setUserAccount(account);
// 模拟充值历史
const mockHistory: RechargeRecord[] = [
{
id: '1',
userId: account?.id || '1',
amount: 10000,
bonus: 500,
paymentMethod: 'wechat',
status: 'completed',
createdAt: new Date(Date.now() - 86400000), // 1天前
},
{
id: '2',
userId: account?.id || '1',
amount: 5000,
bonus: 0,
paymentMethod: 'alipay',
status: 'completed',
createdAt: new Date(Date.now() - 7 * 86400000), // 7天前
},
];
setRechargeHistory(mockHistory);
}, []);
const formatCurrency = (cents: number) => {
return (cents / 100).toFixed(2);
};
const handleAmountSelect = (amount: number) => {
setSelectedAmount(amount);
setCustomAmount('');
};
const handleCustomAmountChange = (value: string) => {
const numValue = parseFloat(value);
if (!isNaN(numValue) && numValue > 0) {
setSelectedAmount(Math.round(numValue * 100));
setCustomAmount(value);
} else {
setSelectedAmount(0);
setCustomAmount(value);
}
};
const getBonus = (amount: number) => {
const preset = presetAmounts.find(p => p.value === amount);
return preset?.bonus || 0;
};
const getTotalAmount = () => {
return selectedAmount + getBonus(selectedAmount);
};
const handleRecharge = async () => {
if (selectedAmount < 100) { // 最低1元
alert('充值金额不能少于1元');
return;
}
if (!userAccount) {
alert('用户账户信息未加载');
return;
}
setIsProcessing(true);
try {
// 模拟支付处理
await new Promise(resolve => setTimeout(resolve, 2000));
// 执行充值
const success = billingService.rechargeAccount(selectedAmount);
if (success) {
// 更新用户账户
setUserAccount(billingService.getUserAccount());
// 添加充值记录
const newRecord: RechargeRecord = {
id: Date.now().toString(),
userId: userAccount.id,
amount: selectedAmount,
bonus: getBonus(selectedAmount),
paymentMethod: selectedPayment,
status: 'completed',
createdAt: new Date(),
};
setRechargeHistory(prev => [newRecord, ...prev]);
alert(`充值成功!到账金额:¥${formatCurrency(getTotalAmount())}`);
setSelectedAmount(0);
setCustomAmount('');
} else {
alert('充值失败,请重试');
}
} catch (error) {
alert('充值失败,请重试');
} finally {
setIsProcessing(false);
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'completed':
return '#52c41a';
case 'pending':
return '#fa8c16';
case 'failed':
return '#ff4d4f';
default:
return '#d9d9d9';
}
};
const getStatusText = (status: string) => {
switch (status) {
case 'completed':
return '成功';
case 'pending':
return '处理中';
case 'failed':
return '失败';
default:
return '未知';
}
};
const styles = {
container: {
padding: '16px',
backgroundColor: '#f5f5f5',
minHeight: '100vh',
},
header: {
display: 'flex',
alignItems: 'center',
marginBottom: '20px',
},
backButton: {
background: 'none',
border: 'none',
fontSize: '24px',
cursor: 'pointer',
marginRight: '12px',
},
title: {
fontSize: '20px',
fontWeight: '600' as const,
color: '#333',
},
balanceCard: {
backgroundColor: 'white',
borderRadius: '16px',
padding: '20px',
marginBottom: '20px',
textAlign: 'center' as const,
boxShadow: '0 2px 12px rgba(0, 0, 0, 0.1)',
},
balanceLabel: {
fontSize: '14px',
color: '#666',
marginBottom: '8px',
},
balanceAmount: {
fontSize: '32px',
fontWeight: '700' as const,
color: '#1890ff',
marginBottom: '8px',
},
userType: {
fontSize: '12px',
color: '#999',
backgroundColor: '#f0f8ff',
padding: '4px 12px',
borderRadius: '12px',
display: 'inline-block',
},
section: {
backgroundColor: 'white',
borderRadius: '16px',
padding: '20px',
marginBottom: '20px',
boxShadow: '0 2px 12px rgba(0, 0, 0, 0.1)',
},
sectionTitle: {
fontSize: '18px',
fontWeight: '600' as const,
color: '#333',
marginBottom: '16px',
},
amountGrid: {
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
gap: '12px',
marginBottom: '16px',
},
amountCard: (selected: boolean, popular: boolean) => ({
padding: '16px',
borderRadius: '12px',
border: selected ? '2px solid #1890ff' : '2px solid transparent',
backgroundColor: selected ? '#e6f7ff' : '#fafafa',
cursor: 'pointer',
textAlign: 'center' as const,
position: 'relative' as const,
...(popular && {
borderColor: '#fa8c16',
backgroundColor: '#fff7e6',
}),
}),
popularBadge: {
position: 'absolute' as const,
top: '-8px',
right: '-8px',
backgroundColor: '#fa8c16',
color: 'white',
fontSize: '10px',
padding: '2px 6px',
borderRadius: '8px',
fontWeight: '500' as const,
},
amountValue: {
fontSize: '18px',
fontWeight: '600' as const,
color: '#333',
marginBottom: '4px',
},
bonusText: {
fontSize: '12px',
color: '#52c41a',
fontWeight: '500' as const,
},
customAmountInput: {
width: '100%',
padding: '12px',
borderRadius: '8px',
border: '1px solid #d9d9d9',
fontSize: '16px',
marginBottom: '16px',
},
paymentMethods: {
display: 'flex',
gap: '12px',
marginBottom: '20px',
},
paymentMethod: (selected: boolean, color: string) => ({
flex: 1,
padding: '16px',
borderRadius: '12px',
border: selected ? `2px solid ${color}` : '2px solid #f0f0f0',
backgroundColor: selected ? `${color}10` : 'white',
cursor: 'pointer',
textAlign: 'center' as const,
}),
paymentIcon: {
fontSize: '24px',
marginBottom: '8px',
},
paymentName: {
fontSize: '14px',
fontWeight: '500' as const,
color: '#333',
},
summaryCard: {
backgroundColor: '#f8f9fa',
borderRadius: '12px',
padding: '16px',
marginBottom: '20px',
},
summaryRow: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '8px',
},
summaryLabel: {
fontSize: '14px',
color: '#666',
},
summaryValue: {
fontSize: '14px',
fontWeight: '500' as const,
color: '#333',
},
totalRow: {
borderTop: '1px solid #e8e8e8',
paddingTop: '8px',
marginTop: '8px',
},
totalValue: {
fontSize: '18px',
fontWeight: '600' as const,
color: '#1890ff',
},
rechargeButton: {
width: '100%',
padding: '16px',
borderRadius: '12px',
border: 'none',
backgroundColor: selectedAmount > 0 && !isProcessing ? '#1890ff' : '#d9d9d9',
color: 'white',
fontSize: '16px',
fontWeight: '600' as const,
cursor: selectedAmount > 0 && !isProcessing ? 'pointer' : 'not-allowed',
marginBottom: '20px',
},
historyItem: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '16px 0',
borderBottom: '1px solid #f0f0f0',
},
historyLeft: {
flex: 1,
},
historyAmount: {
fontSize: '16px',
fontWeight: '600' as const,
color: '#333',
marginBottom: '4px',
},
historyDate: {
fontSize: '12px',
color: '#999',
},
historyStatus: (status: string) => ({
fontSize: '12px',
fontWeight: '500' as const,
color: getStatusColor(status),
backgroundColor: `${getStatusColor(status)}15`,
padding: '2px 8px',
borderRadius: '8px',
}),
};
return (
<div style={styles.container}>
{/* 头部 */}
<div style={styles.header}>
<button style={styles.backButton} onClick={() => navigate(-1)}>
</button>
<div style={styles.title}></div>
</div>
{/* 余额显示 */}
<div style={styles.balanceCard}>
<div style={styles.balanceLabel}></div>
<div style={styles.balanceAmount}>
¥{userAccount ? formatCurrency(userAccount.balance) : '0.00'}
</div>
<div style={styles.userType}>
{userAccount?.userType === UserType.ENTERPRISE ? '企业用户' : '个人用户'}
</div>
</div>
{/* 充值金额选择 */}
<div style={styles.section}>
<div style={styles.sectionTitle}></div>
<div style={styles.amountGrid}>
{presetAmounts.map((amount) => (
<div
key={amount.value}
style={styles.amountCard(selectedAmount === amount.value, amount.popular)}
onClick={() => handleAmountSelect(amount.value)}
>
{amount.popular && <div style={styles.popularBadge}></div>}
<div style={styles.amountValue}>{amount.label}</div>
{amount.bonus > 0 && (
<div style={styles.bonusText}>
¥{formatCurrency(amount.bonus)}
</div>
)}
</div>
))}
</div>
<input
type="number"
placeholder="自定义金额(元)"
style={styles.customAmountInput}
value={customAmount}
onChange={(e) => handleCustomAmountChange(e.target.value)}
/>
</div>
{/* 支付方式 */}
<div style={styles.section}>
<div style={styles.sectionTitle}></div>
<div style={styles.paymentMethods}>
{paymentMethods.map((method) => (
<div
key={method.id}
style={styles.paymentMethod(
selectedPayment === method.id,
method.color
)}
onClick={() => setSelectedPayment(method.id as any)}
>
<div style={styles.paymentIcon}>{method.icon}</div>
<div style={styles.paymentName}>{method.name}</div>
</div>
))}
</div>
</div>
{/* 费用明细 */}
{selectedAmount > 0 && (
<div style={styles.summaryCard}>
<div style={styles.summaryRow}>
<span style={styles.summaryLabel}></span>
<span style={styles.summaryValue}>¥{formatCurrency(selectedAmount)}</span>
</div>
{getBonus(selectedAmount) > 0 && (
<div style={styles.summaryRow}>
<span style={styles.summaryLabel}></span>
<span style={{...styles.summaryValue, color: '#52c41a'}}>
+¥{formatCurrency(getBonus(selectedAmount))}
</span>
</div>
)}
<div style={{...styles.summaryRow, ...styles.totalRow}}>
<span style={styles.summaryLabel}></span>
<span style={styles.totalValue}>¥{formatCurrency(getTotalAmount())}</span>
</div>
</div>
)}
{/* 充值按钮 */}
<button
style={styles.rechargeButton}
onClick={handleRecharge}
disabled={selectedAmount === 0 || isProcessing}
>
{isProcessing ? '处理中...' : `确认充值 ¥${formatCurrency(selectedAmount)}`}
</button>
{/* 充值历史 */}
<div style={styles.section}>
<div style={styles.sectionTitle}></div>
{rechargeHistory.map((record) => (
<div key={record.id} style={styles.historyItem}>
<div style={styles.historyLeft}>
<div style={styles.historyAmount}>
+¥{formatCurrency(record.amount + record.bonus)}
</div>
<div style={styles.historyDate}>
{record.createdAt.toLocaleDateString()} {record.createdAt.toLocaleTimeString()}
</div>
</div>
<div style={styles.historyStatus(record.status)}>
{getStatusText(record.status)}
</div>
</div>
))}
</div>
</div>
);
};
export default MobileRecharge;