feat: complete reporting and filing workflows
This commit is contained in:
@@ -1,22 +1,49 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { AlertTriangle, CheckCircle2, Download, FileSpreadsheet, Layers3, RefreshCw, ShieldCheck } from 'lucide-react';
|
||||
import { adminApi, fileDownloadUrl, type ReportMaterialBatchPreflight, type ReportMaterialBatchResult, type ReportMaterialPendingItem } from '@/api/adminApi';
|
||||
import { Breadcrumb, Button, Input, Modal, Select, Tag } from '@/components/ui';
|
||||
import { AlertTriangle, CheckCircle2, Download, Layers3, Search, ShieldCheck } from 'lucide-react';
|
||||
import {
|
||||
adminApi,
|
||||
fileDownloadUrl,
|
||||
type ReportMaterialBatch,
|
||||
type ReportMaterialBatchPreflight,
|
||||
type ReportMaterialBatchResult,
|
||||
type ReportMaterialPendingItem,
|
||||
} from '@/api/adminApi';
|
||||
import {
|
||||
Breadcrumb,
|
||||
Button,
|
||||
DateRangeInput,
|
||||
Input,
|
||||
Modal,
|
||||
Pagination,
|
||||
Select,
|
||||
Table,
|
||||
Tabs,
|
||||
Tag,
|
||||
type DateRangeValue,
|
||||
type TableColumn,
|
||||
} from '@/components/ui';
|
||||
import { formatDateTime } from '@/utils/dateTime';
|
||||
import { createUuid } from '@/utils/randomId';
|
||||
import { ReportMaterialImportModal } from './ReportMaterialImportModal';
|
||||
|
||||
type Batch = Record<string, unknown> & { id: string; batchNo?: string; status?: string; createdAt?: string; selectedCount?: number; channelCount?: number; exportFiles?: Array<Record<string, unknown>> };
|
||||
|
||||
const statusLabel: Record<string, string> = { completed: '生成完成', partial_failed: '部分资料待补充', failed: '生成失败', processing: '生成中' };
|
||||
const batchStatusLabels: Record<string, string> = {
|
||||
completed: '生成完成',
|
||||
partial_failed: '部分生成',
|
||||
failed: '生成失败',
|
||||
generating: '生成中',
|
||||
processing: '生成中',
|
||||
};
|
||||
|
||||
export function AdminReportMaterialsPage() {
|
||||
const [items, setItems] = useState<ReportMaterialPendingItem[]>([]);
|
||||
const [batches, setBatches] = useState<Batch[]>([]);
|
||||
const [activeTab, setActiveTab] = useState<'pending' | 'batches'>('pending');
|
||||
const [pendingData, setPendingData] = useState<{ items: ReportMaterialPendingItem[]; total: number }>({ items: [], total: 0 });
|
||||
const [batchData, setBatchData] = useState<{ items: ReportMaterialBatch[]; total: number }>({ items: [], total: 0 });
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
const [reportType, setReportType] = useState('all');
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [importOpen, setImportOpen] = useState(false);
|
||||
const [dateRange, setDateRange] = useState<DateRangeValue>({});
|
||||
const [pendingPage, setPendingPage] = useState(1);
|
||||
const [batchPage, setBatchPage] = useState(1);
|
||||
const pageSize = 20;
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [preflightBusy, setPreflightBusy] = useState(false);
|
||||
const [preflight, setPreflight] = useState<ReportMaterialBatchPreflight | null>(null);
|
||||
@@ -27,55 +54,216 @@ export function AdminReportMaterialsPage() {
|
||||
const [error, setError] = useState('');
|
||||
const [message, setMessage] = useState('');
|
||||
|
||||
function loadData() {
|
||||
Promise.all([adminApi.listPendingReportMaterials(reportType === 'all' ? {} : { reportType: reportType as 'signature' | 'drainage' }), adminApi.listReportMaterialBatches()])
|
||||
.then(async ([pendingItems, batchItems]) => {
|
||||
setItems(pendingItems); setBatches(batchItems as Batch[]); setError('');
|
||||
const eligibility = pendingItems.length ? await adminApi.preflightReportMaterialBatch({ items: pendingItems.map(toBatchItem) }) : null;
|
||||
const eligibilityMap = new Map((eligibility?.items ?? []).map((item) => [item.id, item]));
|
||||
setPoolEligibility(eligibilityMap);
|
||||
setSelected((current) => new Set([...current].filter((id) => eligibilityMap.get(id)?.eligible)));
|
||||
})
|
||||
.catch((failure: Error) => setError(failure.message || '待报备资料加载失败'));
|
||||
async function loadPending(
|
||||
page = pendingPage,
|
||||
filters: {
|
||||
keyword?: string;
|
||||
dateRange?: DateRangeValue;
|
||||
reportType?: string;
|
||||
} = {},
|
||||
) {
|
||||
const nextKeyword = filters.keyword ?? keyword;
|
||||
const nextDateRange = filters.dateRange ?? dateRange;
|
||||
const nextReportType = filters.reportType ?? reportType;
|
||||
try {
|
||||
const result = await adminApi.listPendingReportMaterials({
|
||||
reportType: nextReportType === 'all' ? undefined : nextReportType as 'signature' | 'drainage',
|
||||
keyword: nextKeyword.trim() || undefined,
|
||||
startAt: nextDateRange.start,
|
||||
endAt: nextDateRange.end,
|
||||
page,
|
||||
pageSize,
|
||||
});
|
||||
setPendingData({ items: result.items, total: result.total });
|
||||
const eligibility = result.items.length ? await adminApi.preflightReportMaterialBatch({ items: result.items.map(toBatchItem) }) : null;
|
||||
const eligibilityMap = new Map((eligibility?.items ?? []).map((item) => [item.id, item]));
|
||||
setPoolEligibility(eligibilityMap);
|
||||
setSelected((current) => new Set([...current].filter((id) => eligibilityMap.get(id)?.eligible)));
|
||||
setError('');
|
||||
} catch (failure) {
|
||||
setError(failure instanceof Error ? failure.message : '待生成资料加载失败');
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(loadData, [reportType]);
|
||||
const visibleItems = useMemo(() => items.filter((item) => !keyword.trim() || [item.name, item.detail, item.signatureName, item.tenant?.name, item.application?.name].some((value) => String(value ?? '').includes(keyword.trim()))), [items, keyword]);
|
||||
const eligibleVisibleItems = visibleItems.filter((item) => poolEligibility.get(item.id)?.eligible);
|
||||
const allSelected = eligibleVisibleItems.length > 0 && eligibleVisibleItems.every((item) => selected.has(item.id));
|
||||
async function loadBatches(
|
||||
page = batchPage,
|
||||
filters: {
|
||||
keyword?: string;
|
||||
dateRange?: DateRangeValue;
|
||||
} = {},
|
||||
) {
|
||||
const nextKeyword = filters.keyword ?? keyword;
|
||||
const nextDateRange = filters.dateRange ?? dateRange;
|
||||
try {
|
||||
const result = await adminApi.listReportMaterialBatches({
|
||||
keyword: nextKeyword.trim() || undefined,
|
||||
startAt: nextDateRange.start,
|
||||
endAt: nextDateRange.end,
|
||||
page,
|
||||
pageSize,
|
||||
});
|
||||
setBatchData({ items: result.items, total: result.total });
|
||||
setError('');
|
||||
} catch (failure) {
|
||||
setError(failure instanceof Error ? failure.message : '已生成批次加载失败');
|
||||
}
|
||||
}
|
||||
|
||||
function toggle(id: string) { if (!poolEligibility.get(id)?.eligible) return; setSelected((current) => { const next = new Set(current); if (next.has(id)) next.delete(id); else next.add(id); return next; }); }
|
||||
function loadActive() {
|
||||
if (activeTab === 'pending') void loadPending(pendingPage);
|
||||
else void loadBatches(batchPage);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadActive();
|
||||
}, [activeTab, pendingPage, batchPage, reportType]);
|
||||
|
||||
const eligibleItems = pendingData.items.filter((item) => poolEligibility.get(item.id)?.eligible);
|
||||
const allSelected = eligibleItems.length > 0 && eligibleItems.every((item) => selected.has(item.id));
|
||||
|
||||
function toggle(id: string) {
|
||||
if (!poolEligibility.get(id)?.eligible) return;
|
||||
setSelected((current) => {
|
||||
const next = new Set(current);
|
||||
if (next.has(id)) next.delete(id); else next.add(id);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
async function beginCreateBatch() {
|
||||
const chosen = items.filter((item) => selected.has(item.id));
|
||||
if (!chosen.length) { setError('请先选择具备报备资格的资料'); return; }
|
||||
setConfirmOpen(true); setPreflightBusy(true); setPreflight(null); setBatchResult(null); setError(''); setMessage('');
|
||||
const chosen = pendingData.items.filter((item) => selected.has(item.id));
|
||||
if (!chosen.length) {
|
||||
setError('请先选择具备报备资格的资料');
|
||||
return;
|
||||
}
|
||||
setConfirmOpen(true);
|
||||
setPreflightBusy(true);
|
||||
setPreflight(null);
|
||||
setBatchResult(null);
|
||||
setError('');
|
||||
setMessage('');
|
||||
setOperationKey(`report-batch:${createUuid()}`);
|
||||
try { setPreflight(await adminApi.preflightReportMaterialBatch({ items: chosen.map(toBatchItem) })); }
|
||||
catch (failure) { setError(failure instanceof Error ? failure.message : '报备资格预检失败'); }
|
||||
finally { setPreflightBusy(false); }
|
||||
try {
|
||||
setPreflight(await adminApi.preflightReportMaterialBatch({ items: chosen.map(toBatchItem) }));
|
||||
} catch (failure) {
|
||||
setError(failure instanceof Error ? failure.message : '报备资格预检失败');
|
||||
} finally {
|
||||
setPreflightBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function createBatch() {
|
||||
const chosen = items.filter((item) => selected.has(item.id));
|
||||
if (!chosen.length) { setError('请先选择要统一报备的资料'); return; }
|
||||
setBusy(true); setError(''); setMessage('');
|
||||
const chosen = pendingData.items.filter((item) => selected.has(item.id));
|
||||
if (!chosen.length) return;
|
||||
setBusy(true);
|
||||
setError('');
|
||||
try {
|
||||
const batch = await adminApi.createReportMaterialBatch({ idempotencyKey: operationKey, items: chosen.map((item) => ({ ...toBatchItem(item), materialVersion: item.materialVersion })) });
|
||||
setBatchResult(batch); setMessage(`批次 ${batch.batchNo} 已完成:成功 ${batch.result.successCount},跳过 ${batch.result.skippedCount},失败 ${batch.result.failedCount}`); setSelected(new Set()); loadData();
|
||||
} catch (failure) { setError(failure instanceof Error ? failure.message : '报备批次生成失败'); }
|
||||
finally { setBusy(false); }
|
||||
const batch = await adminApi.createReportMaterialBatch({
|
||||
idempotencyKey: operationKey,
|
||||
items: chosen.map((item) => ({ ...toBatchItem(item), materialVersion: item.materialVersion })),
|
||||
});
|
||||
setBatchResult(batch);
|
||||
setMessage(`批次 ${batch.batchNo} 已生成:成功 ${batch.result.successCount},跳过 ${batch.result.skippedCount},失败 ${batch.result.failedCount}`);
|
||||
setSelected(new Set());
|
||||
await Promise.all([loadPending(pendingPage), loadBatches(1)]);
|
||||
} catch (failure) {
|
||||
setError(failure instanceof Error ? failure.message : '报备批次生成失败');
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
const pendingColumns = useMemo<Array<TableColumn<ReportMaterialPendingItem>>>(() => [
|
||||
{
|
||||
key: 'select',
|
||||
title: '',
|
||||
width: '48px',
|
||||
render: (item) => {
|
||||
const eligible = poolEligibility.get(item.id)?.eligible;
|
||||
return <input aria-label={`选择${item.name}`} checked={selected.has(item.id)} disabled={!eligible} onChange={() => toggle(item.id)} type="checkbox" />;
|
||||
},
|
||||
},
|
||||
{ key: 'name', title: '资料', render: (item) => <div><strong>{item.name}</strong><div className="muted">{item.reportType === 'signature' ? '签名资料' : `引流信息 · ${item.signatureName ?? '-'}`} · {item.detail || '-'}</div></div> },
|
||||
{ key: 'tenant', title: '企业/应用', render: (item) => <div><strong>{item.tenant?.name ?? '-'}</strong><div className="muted">{item.application?.name ?? '未指定应用'}</div></div> },
|
||||
{ key: 'eligibility', title: '版本/资格', render: (item) => {
|
||||
const eligibility = poolEligibility.get(item.id);
|
||||
const eligible = eligibility?.eligible;
|
||||
return <div><Tag tone={eligible ? 'success' : 'warning'}>V{item.materialVersion} · {eligible ? `${eligibility.targets.filter((target) => target.eligible).length}个通道可生成` : '待补充'}</Tag>{!eligible ? <div className="muted">{eligibility?.blockedReasons[0] ?? '资格检查中'}</div> : null}</div>;
|
||||
} },
|
||||
{ key: 'changedAt', title: '资料变更时间', render: (item) => formatDateTime(item.changedAt) },
|
||||
], [poolEligibility, selected]);
|
||||
|
||||
const batchColumns = useMemo<Array<TableColumn<ReportMaterialBatch>>>(() => [
|
||||
{ key: 'batchNo', title: '报备批次号', render: (batch) => <strong>{batch.batchNo}</strong> },
|
||||
{ key: 'time', title: '生成时间', render: (batch) => formatDateTime(batch.createdAt) },
|
||||
{ key: 'reportTotal', title: '报备总数', render: (batch) => batch.reportTotal.toLocaleString('zh-CN') },
|
||||
{ key: 'successCount', title: '成功数', render: (batch) => batch.successCount.toLocaleString('zh-CN') },
|
||||
{ key: 'successRate', title: '成功率', render: (batch) => `${(batch.successRate * 100).toFixed(2)}%` },
|
||||
{ key: 'channels', title: '通道/文件', render: (batch) => `${batch.channelCount}个通道 · ${batch.fileCount}份文件` },
|
||||
{ key: 'status', title: '生成状态', render: (batch) => <Tag tone={batch.status === 'completed' ? 'success' : batch.status === 'failed' ? 'danger' : 'warning'}>{batchStatusLabels[batch.status] ?? batch.status}</Tag> },
|
||||
{ key: 'files', title: '报备文件', align: 'right', render: (batch) => <div className="table-actions">{batch.exportFiles.map((file) => file.fileObjectId ? <a href={fileDownloadUrl(file.fileObjectId)} key={file.id}><Download size={15} />{file.fileName}({file.rowCount}行)</a> : null)}</div> },
|
||||
], []);
|
||||
|
||||
const filter = <div className="surface report-material-filter">
|
||||
{activeTab === 'pending' ? <Select label="资料类型" onChange={(event) => { setReportType(event.target.value); setPendingPage(1); }} options={[{ label: '全部资料', value: 'all' }, { label: '签名资料', value: 'signature' }, { label: '引流信息', value: 'drainage' }]} value={reportType} /> : null}
|
||||
<Input label={activeTab === 'pending' ? '企业/应用/签名/站点' : '报备批次号'} onChange={(event) => setKeyword(event.target.value)} placeholder={activeTab === 'pending' ? '搜索待生成资料' : '搜索报备批次号'} value={keyword} />
|
||||
<DateRangeInput label={activeTab === 'pending' ? '资料变更时间' : '批次生成时间'} onChange={setDateRange} value={dateRange} />
|
||||
<Button icon={<Search size={16} />} onClick={() => {
|
||||
if (activeTab === 'pending') {
|
||||
setPendingPage(1);
|
||||
void loadPending(1);
|
||||
} else {
|
||||
setBatchPage(1);
|
||||
void loadBatches(1);
|
||||
}
|
||||
}}>查询</Button>
|
||||
<Button onClick={() => {
|
||||
setKeyword('');
|
||||
setDateRange({});
|
||||
if (activeTab === 'pending') {
|
||||
setReportType('all');
|
||||
setPendingPage(1);
|
||||
void loadPending(1, { keyword: '', dateRange: {}, reportType: 'all' });
|
||||
} else {
|
||||
setBatchPage(1);
|
||||
void loadBatches(1, { keyword: '', dateRange: {} });
|
||||
}
|
||||
}} variant="ghost">重置</Button>
|
||||
</div>;
|
||||
|
||||
return <section className="page-stack report-material-page">
|
||||
<div className="surface page-heading"><div><Breadcrumb items={['报备任务', '待报备资料']} /><h1>签名与引流资料报备工作台</h1><p>导入或业务修改的资料先进入待报备池;生成前会重新检查应用、路由、通道字段、资料版本和重复批次。</p></div><div className="page-heading-actions"><Button icon={<FileSpreadsheet size={16} />} onClick={() => setImportOpen(true)} variant="ghost">导入 WPS 表格</Button><Button disabled={busy || selected.size === 0} icon={<Layers3 size={16} />} onClick={() => void beginCreateBatch()}>{busy ? '生成中...' : `预检并生成(${selected.size})`}</Button></div></div>
|
||||
{error ? <p className="form-error">{error}</p> : null}{message ? <p className="form-success">{message}</p> : null}
|
||||
<div className="surface report-material-filter"><Select label="资料类型" onChange={(event) => setReportType(event.target.value)} options={[{ label: '全部资料', value: 'all' }, { label: '签名资料', value: 'signature' }, { label: '引流信息', value: 'drainage' }]} value={reportType} /><Input label="企业/应用/签名/站点" onChange={(event) => setKeyword(event.target.value)} placeholder="搜索待报备资料" value={keyword} /><Button icon={<RefreshCw size={15} />} onClick={loadData} variant="ghost">刷新</Button></div>
|
||||
<div className="surface report-material-pool"><div className="report-material-table-head"><input checked={allSelected} onChange={() => setSelected((current) => { const next = new Set(current); for (const item of visibleItems.filter((entry) => poolEligibility.get(entry.id)?.eligible)) allSelected ? next.delete(item.id) : next.add(item.id); return next; })} type="checkbox" /><span>资料</span><span>企业 / 应用</span><span>版本 / 资格</span><span>变更时间</span></div>{visibleItems.map((item) => { const eligibility = poolEligibility.get(item.id); const disabled = !eligibility?.eligible; return <label className={`report-material-row${disabled ? ' is-disabled' : ''}`} key={item.id}><input checked={selected.has(item.id)} disabled={disabled} onChange={() => toggle(item.id)} type="checkbox" /><span><strong>{item.name}</strong><small>{item.reportType === 'signature' ? '签名资料' : `引流信息 · 所属签名 ${item.signatureName ?? '-'}`}</small><em>{item.detail || '-'}</em></span><span><strong>{item.tenant?.name ?? '-'}</strong><small>{item.application?.name ?? '未指定应用'}</small></span><span><Tag tone={disabled ? 'warning' : 'success'}>V{item.materialVersion} · {disabled ? '待补充' : `${eligibility.targets.filter((target) => target.eligible).length} 通道可生成`}</Tag>{disabled ? <small title={eligibility?.blockedReasons.join(';')}>{eligibility?.blockedReasons[0] ?? '资格检查中'}</small> : null}</span><span>{formatDateTime(item.changedAt)}</span></label>; })}{visibleItems.length === 0 ? <div className="channel-report-empty">暂无符合条件的待报备资料</div> : null}</div>
|
||||
<div className="surface report-material-batches"><div className="channel-field-section-head"><div><h2>最近生成批次</h2><p>一个批次可按路由展开成多个通道文件,图片直接嵌入 XLSX。</p></div><Tag tone="neutral">{batches.length} 个批次</Tag></div>{batches.map((batch) => <article key={batch.id}><div><strong>{batch.batchNo ?? batch.id}</strong><span>{statusLabel[String(batch.status)] ?? batch.status}</span><small>{batch.createdAt ? formatDateTime(batch.createdAt) : '-'} · 选择 {batch.selectedCount ?? 0} 条 · {batch.channelCount ?? 0} 个通道</small></div><div>{(batch.exportFiles ?? []).map((file) => <a href={fileDownloadUrl(String(file.fileObjectId))} key={String(file.id)}><Download size={15} />{String(file.fileName ?? '下载报备文件')}({String(file.rowCount ?? 0)} 行)</a>)}</div></article>)}{batches.length === 0 ? <div className="channel-report-empty">尚未生成报备批次</div> : null}</div>
|
||||
{importOpen ? <ReportMaterialImportModal onClose={() => setImportOpen(false)} onCompleted={() => { setMessage('资料导入完成,已进入待报备池'); loadData(); }} /> : null}
|
||||
<div className="surface page-heading">
|
||||
<div><Breadcrumb items={['报备任务', '待生成报备批次']} /><h1>待生成报备批次</h1><p>审核通过的签名和引流资料先进入待生成池,运营选择资料后按应用路由为各通道生成批量报备文件。</p></div>
|
||||
{activeTab === 'pending' ? <Button disabled={busy || selected.size === 0} icon={<Layers3 size={16} />} onClick={() => void beginCreateBatch()}>{busy ? '生成中...' : `预检并生成(${selected.size})`}</Button> : null}
|
||||
</div>
|
||||
{error ? <p className="form-error">{error}</p> : null}
|
||||
{message ? <p className="form-success">{message}</p> : null}
|
||||
<Tabs
|
||||
onChange={(value) => {
|
||||
setActiveTab(value as 'pending' | 'batches');
|
||||
setKeyword('');
|
||||
setDateRange({});
|
||||
}}
|
||||
value={activeTab}
|
||||
items={[
|
||||
{
|
||||
label: `待生成资料(${pendingData.total})`,
|
||||
value: 'pending',
|
||||
content: <div className="page-stack">{filter}<div className="surface"><label className="table-actions"><input checked={allSelected} onChange={() => setSelected(allSelected ? new Set() : new Set(eligibleItems.map((item) => item.id)))} type="checkbox" />选择本页全部可生成资料</label><Table columns={pendingColumns} data={pendingData.items} emptyText="暂无符合条件的待生成资料" pagination={false} rowKey="id" /></div><Pagination nextDisabled={pendingPage * pageSize >= pendingData.total} onNext={() => setPendingPage((page) => page + 1)} onPageChange={setPendingPage} onPrevious={() => setPendingPage((page) => Math.max(1, page - 1))} page={pendingPage} previousDisabled={pendingPage <= 1} total={pendingData.total} totalPages={Math.max(1, Math.ceil(pendingData.total / pageSize))} /></div>,
|
||||
},
|
||||
{
|
||||
label: `已生成批次(${batchData.total})`,
|
||||
value: 'batches',
|
||||
content: <div className="page-stack">{filter}<div className="surface"><Table columns={batchColumns} data={batchData.items} emptyText="尚未生成报备批次" pagination={false} rowKey="id" /></div><Pagination nextDisabled={batchPage * pageSize >= batchData.total} onNext={() => setBatchPage((page) => page + 1)} onPageChange={setBatchPage} onPrevious={() => setBatchPage((page) => Math.max(1, page - 1))} page={batchPage} previousDisabled={batchPage <= 1} total={batchData.total} totalPages={Math.max(1, Math.ceil(batchData.total / pageSize))} /></div>,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Modal footer={batchResult ? <Button onClick={() => setConfirmOpen(false)}>关闭</Button> : <><Button disabled={busy} onClick={() => setConfirmOpen(false)} variant="ghost">取消</Button><Button disabled={preflightBusy || busy || !preflight?.eligible} icon={<ShieldCheck size={16} />} onClick={() => void createBatch()}>{busy ? '生成处理中…' : '确认生成'}</Button></>} onClose={() => { if (!busy) setConfirmOpen(false); }} open={confirmOpen} size="xl" title="报备生成资格预检">
|
||||
<div className="report-batch-preflight">{preflightBusy ? <p role="status">正在核对资料版本、应用路由、通道字段与历史批次…</p> : null}{preflight ? <><div className="report-batch-summary"><span><CheckCircle2 size={17} />可生成 {preflight.eligibleTargetCount} 个资料通道组合</span><span><AlertTriangle size={17} />跳过 {preflight.skippedTargetCount} 个组合</span></div>{preflight.items.map((item) => <article key={item.id}><div><strong>{item.name}</strong><small>{item.tenantName} · {item.applicationName} · V{item.materialVersion}</small></div>{item.targets.length ? <ul>{item.targets.map((target) => <li key={target.businessKey} className={target.eligible ? 'is-eligible' : 'is-blocked'}><span>{target.name} · {target.carrier}</span><small>{target.eligible ? '资格通过' : target.blockedReasons.join(';')}</small></li>)}</ul> : <p className="form-error">{item.blockedReasons.join(';')}</p>}</article>)}</> : null}{batchResult ? <div className="risk-action-result" role="status"><ShieldCheck size={20} /><div><strong>报备批次 {batchResult.batchNo} 已处理</strong><span>成功 {batchResult.result.successCount} · 跳过 {batchResult.result.skippedCount} · 失败 {batchResult.result.failedCount}</span><span>操作单号:{batchResult.operationId}{batchResult.replayed ? '(幂等重放)' : ''}</span></div></div> : null}{error ? <p className="form-error" role="alert">{error}</p> : null}</div>
|
||||
<div className="report-batch-preflight">
|
||||
{preflightBusy ? <p role="status">正在核对资料版本、应用路由、通道字段与历史批次…</p> : null}
|
||||
{preflight ? <><div className="report-batch-summary"><span><CheckCircle2 size={17} />可生成 {preflight.eligibleTargetCount} 个资料通道组合</span><span><AlertTriangle size={17} />跳过 {preflight.skippedTargetCount} 个组合</span></div>{preflight.items.map((item) => <article key={item.id}><div><strong>{item.name}</strong><small>{item.tenantName} · {item.applicationName} · V{item.materialVersion}</small></div>{item.targets.length ? <ul>{item.targets.map((target) => <li className={target.eligible ? 'is-eligible' : 'is-blocked'} key={target.businessKey}><span>{target.name} · {target.carrier}</span><small>{target.eligible ? '资格通过' : target.blockedReasons.join(';')}</small></li>)}</ul> : <p className="form-error">{item.blockedReasons.join(';')}</p>}</article>)}</> : null}
|
||||
{batchResult ? <div className="risk-action-result" role="status"><ShieldCheck size={20} /><div><strong>报备批次 {batchResult.batchNo} 已处理</strong><span>成功 {batchResult.result.successCount} · 跳过 {batchResult.result.skippedCount} · 失败 {batchResult.result.failedCount}</span><span>操作单号:{batchResult.operationId}{batchResult.replayed ? '(幂等重放)' : ''}</span></div></div> : null}
|
||||
</div>
|
||||
</Modal>
|
||||
</section>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user