feat: polish reporting templates and shared controls

This commit is contained in:
hectorzhao
2026-07-15 16:31:49 +08:00
parent 28fb8e9038
commit a7a4e8d9f6
21 changed files with 488 additions and 119 deletions
+12 -2
View File
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from 'react';
import { Search } from 'lucide-react';
import { Download, Search } from 'lucide-react';
import { adminApi, type AdminChannel, type DailyQualityReport, type EnterpriseApplication, type TenantOption } from '@/api/adminApi';
import { Breadcrumb, Button, DateRangeInput, Pagination, Select, Tabs, Tag, type DateRangeValue } from '@/components/ui';
import { formatDateTime } from '@/utils/dateTime';
@@ -24,6 +24,7 @@ export function AdminQualityReportsPage() {
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [exporting, setExporting] = useState(false);
useEffect(() => {
Promise.all([adminApi.listTenants(), adminApi.listEnterpriseApplications(), adminApi.listChannels()])
@@ -54,6 +55,13 @@ export function AdminQualityReportsPage() {
}
}
async function exportData() {
setExporting(true); setError('');
try { downloadBlob(await adminApi.exportQualityReports({ dimensionType: dimension, dateFrom: dateRange.start, dateTo: dateRange.end, tenantId: dimension === 'channel' ? undefined : tenantId || undefined, applicationId: dimension === 'channel' ? undefined : applicationId || undefined, channelId: dimension === 'channel' ? channelId || undefined : undefined }), '发送质量报表.csv'); }
catch (failure) { setError(failure instanceof Error ? failure.message : '发送质量报表导出失败'); }
finally { setExporting(false); }
}
const availableApplications = useMemo(() => applications.filter((application) => !tenantId || application.tenantId === tenantId), [applications, tenantId]);
const totalPages = Math.max(1, Math.ceil(total / pageSize));
@@ -89,7 +97,7 @@ export function AdminQualityReportsPage() {
return (
<section className="page-stack">
<div className="page-heading"><div><Breadcrumb items={['报表对账', '发送质量报表']} /><h1></h1></div><Tag tone="info"> 5% · T-4T-1</Tag></div>
<div className="page-heading"><div><Breadcrumb items={['报表对账', '发送质量报表']} /><h1></h1></div><div className="page-actions"><Button disabled={exporting} icon={<Download size={16} />} onClick={() => void exportData()} size="sm" variant="secondary">{exporting ? '导出中...' : '导出报表'}</Button><Tag tone="info"> 5% · T-4T-1</Tag></div></div>
<Tabs value={dimension} onChange={changeDimension} items={(Object.keys(dimensionLabels) as QualityDimension[]).map((value) => ({ value, label: dimensionLabels[value], content: reportPanel }))} />
</section>
);
@@ -111,3 +119,5 @@ function defaultDateRange(): DateRangeValue {
function localDate(value: Date) {
return `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, '0')}-${String(value.getDate()).padStart(2, '0')}`;
}
function downloadBlob(blob: Blob, fileName: string) { const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; anchor.download = fileName; anchor.click(); URL.revokeObjectURL(url); }