import { useEffect, useState } from 'react'; import { Download, Eye, Search } from 'lucide-react'; import { adminApi, fileDownloadUrl, type ReportMaterialBatch, type ReportTask } from '@/api/adminApi'; import { Breadcrumb, Button, CarrierTag, DateRangeInput, Input, Modal, Pagination, Select, Table, Tag, Textarea, type DateRangeValue, type TableColumn, } from '@/components/ui'; import { formatDateTime } from '@/utils/dateTime'; const statusLabels: Record = { completed: '生成完成', partial_failed: '部分生成', failed: '生成失败', generating: '生成中', pending: '未报备', waiting_material: '资料待补充', reporting: '报备中', approved: '报备通过', rejected: '报备失败', abandoned: '已放弃', }; export function AdminReportBatchesPage() { const [items, setItems] = useState([]); const [keyword, setKeyword] = useState(''); const [dateRange, setDateRange] = useState({}); const [page, setPage] = useState(1); const [total, setTotal] = useState(0); const [detail, setDetail] = useState(); const [tasks, setTasks] = useState([]); const [selected, setSelected] = useState>(new Set()); const [nextStatus, setNextStatus] = useState('reporting'); const [reason, setReason] = useState(''); const [error, setError] = useState(''); const pageSize = 20; function load(target = page) { adminApi .listReportMaterialBatches({ keyword: keyword.trim() || undefined, startAt: dateRange.start, endAt: dateRange.end, page: target, pageSize, }) .then((result) => { setItems(result.items); setTotal(result.total); setError(''); }) .catch((failure: Error) => setError(failure.message || '报备批次加载失败')); } useEffect(() => { load(page); }, [page]); async function openBatch(batch: ReportMaterialBatch) { try { const [batchDetail, taskPage] = await Promise.all([ adminApi.getReportMaterialBatch(batch.id), adminApi.listReportMaterialBatchTasks(batch.id, { page: 1, pageSize: 100 }), ]); setDetail(batchDetail); setTasks(taskPage.items); setSelected(new Set()); setError(''); } catch (failure) { setError(failure instanceof Error ? failure.message : '批次明细加载失败'); } } async function saveStatuses() { const chosen = tasks.filter((task) => selected.has(task.id)); if (!chosen.length) return; try { await adminApi.changeReportTaskStatuses({ items: chosen.map((task) => ({ signatureId: task.signatureId, channelId: task.channelId, carrier: task.carrier ?? undefined, reportType: task.reportType, drainageItemId: task.drainageItemId ?? undefined, status: nextStatus, })), reason: reason.trim() || undefined, sourceEntry: 'report_task', }); if (detail) await openBatch(detail); } catch (failure) { setError(failure instanceof Error ? failure.message : '批量状态修改失败'); } } async function exportOne(task: ReportTask) { try { const blob = await adminApi.exportSingleReportMaterial({ reportType: task.reportType, signatureId: task.signatureId, channelId: task.channelId, carrier: task.carrier ?? undefined, drainageItemId: task.drainageItemId ?? undefined, batchItemId: task.exportItems?.[0]?.batchItem.id, }); const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; anchor.download = `${task.signature?.name ?? '签名'}-${task.channel?.name ?? '通道'}.xlsx`; anchor.click(); URL.revokeObjectURL(url); } catch (failure) { setError(failure instanceof Error ? failure.message : '单条资料导出失败'); } } const columns: Array> = [ { key: 'batchNo', title: '报备批次号', render: (item) => {item.batchNo} }, { key: 'time', title: '生成时间', render: (item) => formatDateTime(item.createdAt) }, { key: 'count', title: '明细进度', render: (item) => `${item.successCount}/${item.reportTotal}` }, { key: 'channels', title: '通道/文件', render: (item) => `${item.channelCount}个通道 · ${item.fileCount}份文件` }, { key: 'status', title: '生成状态', render: (item) => ( {statusLabels[item.status] ?? item.status} ), }, { key: 'files', title: '文件', render: (item) => (
{item.exportFiles.map((file) => file.fileObjectId ? ( {file.fileName} ) : null, )}
), }, { key: 'actions', title: '操作', align: 'right', render: (item) => ( ), }, ]; const taskColumns: Array> = [ { key: 'select', title: '', width: '44px', render: (task) => ( setSelected((current) => { const next = new Set(current); if (next.has(task.id)) next.delete(task.id); else next.add(task.id); return next; }) } type="checkbox" /> ), }, { key: 'target', title: '企业/应用/签名', render: (task) => (
{task.signature?.name ?? '-'}
{task.signature?.tenant?.name ?? '-'} · {task.signature?.application?.name ?? '-'}
), }, { key: 'channel', title: '通道/运营商', render: (task) => (
{task.channel?.name ?? '-'} {task.carrier ? (
) : null}
), }, { key: 'version', title: '资料版本', render: (task) => `V${task.exportItems?.[0]?.batchItem.materialVersion ?? '-'}`, }, { key: 'status', title: '报备状态', render: (task) => ( {statusLabels[task.status] ?? task.status} ), }, { key: 'actions', title: '操作', align: 'right', render: (task) => task.reportType !== 'drainage' ? ( ) : ( '-' ), }, ]; return (

报备批次

查看已生成批次、下载通道文件,并在批次内批量处理通道报备明细。

{error ?

{error}

: null}
setKeyword(event.target.value)} value={keyword} />
= total} onNext={() => setPage((value) => value + 1)} onPageChange={setPage} onPrevious={() => setPage((value) => Math.max(1, value - 1))} page={page} previousDisabled={page <= 1} total={total} totalPages={Math.max(1, Math.ceil(total / pageSize))} /> {detail ? ( setDetail(undefined)}>关闭} onClose={() => setDetail(undefined)} open size="xl" title={`批次明细 · ${detail.batchNo}`} >
共 {tasks.length} 条通道明细,已选 {selected.size} 条