Files
video-gen/video-gen-app/src/pages/CreditsPage.tsx
T
2026-05-26 13:47:24 +08:00

141 lines
6.0 KiB
TypeScript

import React, { useEffect, useState, useRef } from 'react';
import {
Col, Row, Space, Table, Tag, Typography,
} from 'antd';
import {
WalletOutlined, ArrowUpOutlined, ArrowDownOutlined,
ThunderboltOutlined,
} from '@ant-design/icons';
import { getCredits } from '../api';
import { useAuthStore } from '../store/useAuthStore';
import { formatDate } from '../utils/formatDate';
import type { CreditRecord } from '../types';
const AnimatedNumber: React.FC<{ value: number; duration?: number }> = ({ value, duration = 1200 }) => {
const [display, setDisplay] = useState(0);
const ref = useRef<number>(0);
useEffect(() => {
const start = display;
const diff = value - start;
if (diff === 0) return;
const startTime = performance.now();
const animate = (now: number) => {
const elapsed = now - startTime;
const progress = Math.min(elapsed / duration, 1);
const eased = 1 - Math.pow(1 - progress, 3);
setDisplay(start + diff * eased);
if (progress < 1) ref.current = requestAnimationFrame(animate);
};
ref.current = requestAnimationFrame(animate);
return () => cancelAnimationFrame(ref.current);
}, [value]);
return <>{display.toLocaleString()}</>;
};
const CreditsPage: React.FC = () => {
const { user } = useAuthStore();
const [records, setRecords] = useState<CreditRecord[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetch = async () => {
setLoading(true);
const data = await getCredits();
setRecords(data.records);
setLoading(false);
};
fetch();
}, []);
const totalRecharge = records.filter((r) => r.type === 'recharge').reduce((sum, r) => sum + r.amount, 0);
const totalConsume = records.filter((r) => r.type === 'consume').reduce((sum, r) => sum + Math.abs(r.amount), 0);
const columns = [
{ title: '类型', dataIndex: 'type', key: 'type', width: 100,
render: (type: string) => (
<Tag color={type === 'recharge' ? 'green' : 'orange'} icon={type === 'recharge' ? <ArrowUpOutlined /> : <ArrowDownOutlined />}>
{type === 'recharge' ? '充值' : '消费'}
</Tag>
),
},
{ title: '积分变动', dataIndex: 'amount', key: 'amount', width: 120,
render: (amount: number) => (
<Typography.Text strong style={{ color: amount > 0 ? '#10b981' : '#ef4444', fontSize: 15 }}>
{amount > 0 ? '+' : ''}{amount}
</Typography.Text>
),
},
{ title: '说明', dataIndex: 'description', key: 'description' },
{ title: '时间', dataIndex: 'createdAt', key: 'createdAt', width: 160,
render: (d: string) => <span className="date-display" translate="no" style={{ color: '#94a3b8' }}>{formatDate(d)}</span>,
},
];
return (
<div>
{/* Balance cards */}
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
<Col xs={24} sm={8}>
<div className="animate-fadeInUp" style={{
borderRadius: 16, padding: 24,
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
position: 'relative', overflow: 'hidden',
}}>
<div style={{ position: 'absolute', right: -20, top: -20, width: 120, height: 120, borderRadius: '50%', background: 'rgba(255,255,255,0.1)' }} />
<div style={{ position: 'relative' }}>
<Space style={{ marginBottom: 10 }}>
<WalletOutlined style={{ color: 'rgba(255,255,255,0.8)', fontSize: 16 }} />
<span style={{ color: 'rgba(255,255,255,0.8)', fontSize: 14 }}>当前可用积分</span>
</Space>
<div style={{ color: '#fff', fontSize: 38, fontWeight: 800, lineHeight: 1 }}>
<AnimatedNumber value={user?.credits ?? 0} />
</div>
</div>
</div>
</Col>
<Col xs={12} sm={8}>
<div className="animate-fadeInUp" style={{
borderRadius: 16, padding: 24, background: '#fff', border: '1px solid #f0f0f5', animationDelay: '0.06s',
}}>
<Space style={{ marginBottom: 10 }}>
<div style={{ width: 32, height: 32, borderRadius: 8, background: 'rgba(16,185,129,0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<ArrowUpOutlined style={{ color: '#10b981', fontSize: 14 }} />
</div>
<span style={{ color: '#94a3b8', fontSize: 14 }}>累计获得</span>
</Space>
<div style={{ color: '#10b981', fontSize: 28, fontWeight: 800 }}><AnimatedNumber value={totalRecharge} /></div>
</div>
</Col>
<Col xs={12} sm={8}>
<div className="animate-fadeInUp" style={{
borderRadius: 16, padding: 24, background: '#fff', border: '1px solid #f0f0f5', animationDelay: '0.12s',
}}>
<Space style={{ marginBottom: 10 }}>
<div style={{ width: 32, height: 32, borderRadius: 8, background: 'rgba(239,68,68,0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<ArrowDownOutlined style={{ color: '#ef4444', fontSize: 14 }} />
</div>
<span style={{ color: '#94a3b8', fontSize: 14 }}>累计消耗</span>
</Space>
<div style={{ color: '#ef4444', fontSize: 28, fontWeight: 800 }}><AnimatedNumber value={totalConsume} /></div>
</div>
</Col>
</Row>
{/* Records */}
<div className="animate-fadeInUp" style={{ animationDelay: '0.15s' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
<ThunderboltOutlined style={{ color: '#6366f1', fontSize: 16 }} />
<Typography.Text strong style={{ fontSize: 16 }}>积分明细</Typography.Text>
</div>
<div style={{ borderRadius: 16, background: '#fff', border: '1px solid #f0f0f5', overflow: 'hidden' }}>
<Table columns={columns} dataSource={records} rowKey="id" loading={loading}
pagination={{ pageSize: 10, size: 'small' }}
scroll={{ x: 500 }} />
</div>
</div>
</div>
);
};
export default CreditsPage;