import { useEffect, useState, useCallback } from 'react'; import { teamApi } from '../lib/api'; import type { AdminRecord } from '../types'; import { showToast } from '../components/Toast'; import { DatePicker } from '../components/DatePicker'; import { RecordDetailModal } from '../components/RecordDetailModal'; import styles from './RecordsPage.module.css'; export function TeamRecordsPage() { const [records, setRecords] = useState([]); const [detailRecord, setDetailRecord] = useState(null); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [search, setSearch] = useState(''); const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); const [loading, setLoading] = useState(true); const pageSize = 20; const fetchRecords = useCallback(async () => { setLoading(true); try { const { data } = await teamApi.getRecords({ page, page_size: pageSize, search, start_date: startDate || undefined, end_date: endDate || undefined, }); setRecords(data.results); setTotal(data.total); } catch { showToast('加载消费记录失败'); } finally { setLoading(false); } }, [page, search, startDate, endDate]); useEffect(() => { fetchRecords(); }, [fetchRecords]); const handleSearch = () => { setPage(1); fetchRecords(); }; const handleExportCSV = async () => { try { const { data } = await teamApi.getRecords({ page: 1, page_size: 10000, search, start_date: startDate || undefined, end_date: endDate || undefined, }); const header = '任务ID,提交时间,完成时间,耗时,用户名,模型,视频时长(秒),模式,比例,分辨率,消费秒数,Tokens,费用(元),种子值,状态,提示词,失败原因,原始错误,参考素材数\n'; const rows = data.results.map((r) => { const esc = (s: string) => s.replace(/"/g, '""').replace(/^[=+\-@]/, "'$&"); const modeLabel = r.mode === 'universal' ? '全能参考' : '首尾帧'; const modelLabel = r.model === 'seedance_2.0_fast' ? 'AirDrama Fast' : 'AirDrama'; const statusLabel = { queued: '排队中', processing: '生成中', completed: '已完成', failed: '失败' }[r.status]; const elapsed = r.completed_at ? Math.round((new Date(r.completed_at).getTime() - new Date(r.created_at).getTime()) / 1000) + '秒' : ''; const completedAt = r.completed_at ? new Date(r.completed_at).toLocaleString('zh-CN') : ''; const refCount = (r.reference_urls || []).length; const resolutionLabel = r.resolution ? r.resolution.toUpperCase() : ''; return `"${r.ark_task_id || ''}","${new Date(r.created_at).toLocaleString('zh-CN')}","${completedAt}","${elapsed}","${r.username}","${modelLabel}","${r.duration ?? ''}","${modeLabel}","${r.aspect_ratio || ''}","${resolutionLabel}","${r.seconds_consumed}","${r.tokens_consumed || 0}","${(r.cost_amount || 0).toFixed(2)}","${r.seed != null && r.seed !== -1 ? r.seed : ''}","${statusLabel}","${esc(r.prompt || '')}","${esc(r.error_message || '')}","${esc(r.raw_error || '')}","${refCount}"`; }).join('\n'); const blob = new Blob(['\uFEFF' + header + rows], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `团队消费记录_${new Date().toISOString().slice(0, 10)}.csv`; a.click(); URL.revokeObjectURL(url); showToast('导出成功'); } catch { showToast('导出失败'); } }; const totalPages = Math.ceil(total / pageSize); const statusMap: Record = { queued: '排队中', processing: '生成中', completed: '已完成', failed: '失败' }; const formatElapsed = (r: AdminRecord) => { if (!r.completed_at) return '-'; const ms = new Date(r.completed_at).getTime() - new Date(r.created_at).getTime(); if (ms < 0) return '-'; const totalSec = Math.round(ms / 1000); if (totalSec < 60) return `${totalSec}秒`; const min = Math.floor(totalSec / 60); const sec = totalSec % 60; return `${min}分${sec > 0 ? sec + '秒' : ''}`; }; return (<>

消费记录

setSearch(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSearch()} /> ~
{loading ? ( Array.from({ length: 5 }).map((_, i) => ( {Array.from({ length: 10 }).map((_, j) => ( ))} )) ) : records.length === 0 ? ( ) : ( records.map((r) => ( setDetailRecord(r)} style={{ cursor: 'pointer' }}> )) )}
时间 耗时 用户名 消费秒数 Tokens 费用 视频描述 模型 模式 状态
暂无记录
{new Date(r.created_at).toLocaleString('zh-CN')} {formatElapsed(r)} {r.username} {r.seconds_consumed.toLocaleString()}s {(r.tokens_consumed || 0).toLocaleString()} ¥{(r.cost_amount || 0).toFixed(2)} {r.prompt ? (r.prompt.slice(0, 40) + (r.prompt.length > 40 ? '...' : '')) : '-'} {r.model === 'seedance_2.0_fast' ? 'AirDrama Fast' : 'AirDrama'} {r.mode === 'universal' ? '全能参考' : '首尾帧'} {statusMap[r.status]} {r.status === 'failed' && r.error_message && ( {r.error_message} )}
{totalPages > 1 && (
共 {total} 条
{Array.from({ length: Math.min(totalPages, 5) }, (_, i) => { let p: number; if (totalPages <= 5) p = i + 1; else if (page <= 3) p = i + 1; else if (page >= totalPages - 2) p = totalPages - 4 + i; else p = page - 2 + i; return ( ); })}
)}
{detailRecord && ( setDetailRecord(null)} /> )} ); }