Files
video-gen/video-gen-app/src/pages/CreditsPage.tsx
T
2026-08-14 15:23:46 +08:00

80 lines
4.7 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { Col, Empty, Pagination, Row, Spin, Table, Tag, Typography } from 'antd';
import { LockOutlined, TeamOutlined, WalletOutlined } from '@ant-design/icons';
import { getCreditBalances, getCredits } from '../api';
import { formatDatePrecise } from '../utils/formatDate';
const CreditsPage: React.FC = () => {
const [loading, setLoading] = useState(false);
const [summary, setSummary] = useState({ personalCredits: 0, teamAvailableCredits: 0, teamFrozenCredits: 0, credits: 0, nextExpiringCredits: 0, nextLastUsableAt: null as string | null });
const [balances, setBalances] = useState<any[]>([]);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(20);
useEffect(() => {
const load = async () => {
setLoading(true);
try {
const [creditData, balanceData] = await Promise.all([getCredits(1, 1), getCreditBalances(page, pageSize)]);
setSummary({
personalCredits: Number(creditData.personalCredits || 0),
teamAvailableCredits: Number(creditData.teamAvailableCredits || 0),
teamFrozenCredits: Number(creditData.teamFrozenCredits || 0),
credits: Number(creditData.credits || creditData.availableCredits || 0),
nextExpiringCredits: Number(creditData.nextExpiringCredits || 0),
nextLastUsableAt: creditData.nextLastUsableAt || null,
});
setBalances(balanceData || []);
} finally {
setLoading(false);
}
};
void load();
}, [page, pageSize]);
const cards = [
{ title: '个人可用积分', value: summary.personalCredits, icon: <WalletOutlined />, note: '个人积分可正常用于业务消费' },
{ title: '团队可用积分', value: summary.teamAvailableCredits, icon: <TeamOutlined />, note: '仅统计当前有效席位可消费额度' },
{ title: '团队冻结积分', value: summary.teamFrozenCredits, icon: <LockOutlined />, note: '团队禁用期间继续发放和过期,但不可消费' },
{ title: '总可消费积分', value: summary.credits, icon: <WalletOutlined />, note: '个人积分 + 当前可用团队积分' },
];
const columns = [
{ title: '资金域', dataIndex: 'creditScopeLabel', width: 110, render: (v: string, r: any) => <Tag color={r.creditScope === 'team' ? 'blue' : 'green'}>{v || (r.creditScope === 'team' ? '团队积分' : '个人积分')}</Tag> },
{ title: '积分等级', dataIndex: 'creditLevelLabel', width: 120, render: (v: string) => v || '-' },
{ title: '来源', dataIndex: 'sourceTypeLabel', width: 150, render: (v: string) => v || '-' },
{ title: '发放积分', dataIndex: 'grantAmount', width: 110, align: 'right' as const },
{ title: '剩余积分', dataIndex: 'unspentAmount', width: 110, align: 'right' as const, render: (v: number) => <Typography.Text strong>{Number(v || 0).toLocaleString()}</Typography.Text> },
{ title: '状态', dataIndex: 'statusLabel', width: 100, render: (v: string) => <Tag>{v || '其他状态'}</Tag> },
{ title: '最后可用时间', dataIndex: 'lastUsableAt', width: 190, render: (v: string) => v ? formatDatePrecise(v) : '-' },
];
return (
<Spin spinning={loading}>
<Row gutter={[16, 16]} style={{ marginBottom: 18 }}>
{cards.map((card) => (
<Col xs={24} sm={12} lg={6} key={card.title}>
<div style={{ background: '#fff', border: '1px solid #f0f0f5', borderRadius: 16, padding: 20, minHeight: 132 }}>
<div style={{ display: 'flex', gap: 8, color: '#64748b', alignItems: 'center' }}>{card.icon}<span>{card.title}</span></div>
<div style={{ fontSize: 30, fontWeight: 800, color: '#1e293b', margin: '10px 0 6px' }}>{card.value.toLocaleString()}</div>
<div style={{ fontSize: 12, color: '#94a3b8' }}>{card.note}</div>
</div>
</Col>
))}
</Row>
{summary.nextExpiringCredits > 0 && (
<div style={{ marginBottom: 16, padding: '10px 14px', borderRadius: 10, background: '#fffbe6', color: '#8c6d1f' }}>
最近将有 {summary.nextExpiringCredits.toLocaleString()} 积分到期,最后可用时间:{summary.nextLastUsableAt ? formatDatePrecise(summary.nextLastUsableAt) : '-'}
</div>
)}
<Typography.Title level={5}>个人积分批次</Typography.Title>
{balances.length === 0 ? <Empty description="暂无个人积分批次" /> : <Table rowKey="id" columns={columns} dataSource={balances} pagination={false} scroll={{ x: 900 }} />}
<div style={{ marginTop: 16, textAlign: 'right' }}>
<Pagination current={page} pageSize={pageSize} onChange={(p, ps) => { setPage(p); setPageSize(ps); }} showSizeChanger pageSizeOptions={[20, 50, 100]} />
</div>
</Spin>
);
};
export default CreditsPage;