fix: improve retry feedback and deletion stability
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { AlertTriangle, BarChart3, CheckCircle2, Eye, RefreshCw, Search, TimerReset } from 'lucide-react';
|
||||
import { adminApi, type DownstreamDeliveryDashboard, type DownstreamDeliveryRecord, type EnterpriseApplication } from '@/api/adminApi';
|
||||
import { Breadcrumb, Button, DateRangeInput, Input, Modal, Pagination, Select, Tag, type DateRangeValue } from '@/components/ui';
|
||||
@@ -42,6 +42,17 @@ const attemptStatusLabel: Record<string, string> = {
|
||||
failed: '投递失败',
|
||||
};
|
||||
|
||||
type RequeueTarget =
|
||||
| { kind: 'single'; record: DownstreamDeliveryRecord }
|
||||
| { kind: 'batch'; ids: string[] };
|
||||
|
||||
type RequeueResult = {
|
||||
status: 'success' | 'partial' | 'failed';
|
||||
title: string;
|
||||
message: string;
|
||||
failures?: string[];
|
||||
};
|
||||
|
||||
function formatLocalDate(value: Date) {
|
||||
return `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, '0')}-${String(value.getDate()).padStart(2, '0')}`;
|
||||
}
|
||||
@@ -149,6 +160,10 @@ export function AdminDownstreamDeliveriesPage() {
|
||||
const [detail, setDetail] = useState<DownstreamDeliveryRecord | null>(null);
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||
const [dateRange, setDateRange] = useState<DateRangeValue>(recentSevenDays);
|
||||
const [requeueTarget, setRequeueTarget] = useState<RequeueTarget | null>(null);
|
||||
const [requeueBusy, setRequeueBusy] = useState(false);
|
||||
const [requeueResult, setRequeueResult] = useState<RequeueResult | null>(null);
|
||||
const requeueInFlightRef = useRef(false);
|
||||
|
||||
const loadData = useCallback(() => {
|
||||
setLoading(true);
|
||||
@@ -200,12 +215,61 @@ export function AdminDownstreamDeliveriesPage() {
|
||||
|
||||
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
||||
|
||||
const replayRecord = (record: DownstreamDeliveryRecord) => {
|
||||
const acknowledgedWarning = record.status === 'delivered' ? '该记录已收到客户端确认,' : '';
|
||||
if (!window.confirm(`${acknowledgedWarning}重投可能导致下游业务重复处理,确认继续吗?`)) return;
|
||||
adminApi.requeueDownstreamDelivery(record.id)
|
||||
.then(() => loadData())
|
||||
.catch((failure: Error) => setError(failure.message || '人工重投失败'));
|
||||
const openSingleRequeue = (record: DownstreamDeliveryRecord) => {
|
||||
setRequeueResult(null);
|
||||
setRequeueTarget({ kind: 'single', record });
|
||||
};
|
||||
|
||||
const openBatchRequeue = () => {
|
||||
if (selectedIds.length === 0) return;
|
||||
setRequeueResult(null);
|
||||
setRequeueTarget({ kind: 'batch', ids: [...selectedIds] });
|
||||
};
|
||||
|
||||
const closeRequeueDialog = () => {
|
||||
if (requeueBusy) return;
|
||||
setRequeueTarget(null);
|
||||
setRequeueResult(null);
|
||||
};
|
||||
|
||||
const confirmRequeue = async () => {
|
||||
if (!requeueTarget || requeueInFlightRef.current) return;
|
||||
requeueInFlightRef.current = true;
|
||||
setRequeueBusy(true);
|
||||
setRequeueResult(null);
|
||||
setError('');
|
||||
try {
|
||||
if (requeueTarget.kind === 'single') {
|
||||
const updated = await adminApi.requeueDownstreamDelivery(requeueTarget.record.id);
|
||||
setRequeueResult({
|
||||
status: 'success',
|
||||
title: '重投请求已受理',
|
||||
message: `${deliveryTypeLabel[updated.deliveryType] ?? updated.deliveryType}已进入人工重投流程,当前状态为“${deliveryStatusLabel(updated)}”,人工重投次数 ${updated.manualRetryCount} 次。`,
|
||||
});
|
||||
} else {
|
||||
const result = await adminApi.batchRequeueDownstreamDeliveries(requeueTarget.ids);
|
||||
const failures = result.results
|
||||
.filter((item) => item.status === 'failed')
|
||||
.map((item) => `${item.id}:${item.errorMessage || '重投失败'}`);
|
||||
setRequeueResult({
|
||||
status: result.failedCount === 0 ? 'success' : result.successCount > 0 ? 'partial' : 'failed',
|
||||
title: result.failedCount === 0 ? '批量重投已受理' : result.successCount > 0 ? '批量重投部分完成' : '批量重投失败',
|
||||
message: `共处理 ${result.total} 条,成功 ${result.successCount} 条,失败 ${result.failedCount} 条。`,
|
||||
failures,
|
||||
});
|
||||
setSelectedIds([]);
|
||||
}
|
||||
loadData();
|
||||
} catch (failure) {
|
||||
setRequeueResult({
|
||||
status: 'failed',
|
||||
title: '重投请求未确认成功',
|
||||
message: failure instanceof Error && failure.message ? failure.message : '人工重投失败,请先检查列表状态和系统日志,避免重复提交。',
|
||||
});
|
||||
} finally {
|
||||
requeueInFlightRef.current = false;
|
||||
setRequeueBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -400,16 +464,8 @@ export function AdminDownstreamDeliveriesPage() {
|
||||
</Button>
|
||||
<Button
|
||||
icon={<RefreshCw size={14} />}
|
||||
disabled={selectedIds.length === 0}
|
||||
onClick={() => {
|
||||
if (!window.confirm(`即将重投 ${selectedIds.length} 条记录,可能导致下游业务重复处理,确认继续吗?`)) return;
|
||||
adminApi.batchRequeueDownstreamDeliveries(selectedIds)
|
||||
.then((result) => {
|
||||
setError(result.failedCount > 0 ? `批量重投完成,成功 ${result.successCount} 条,失败 ${result.failedCount} 条` : '');
|
||||
loadData();
|
||||
})
|
||||
.catch((failure: Error) => setError(failure.message || '批量重投失败'));
|
||||
}}
|
||||
disabled={selectedIds.length === 0 || requeueBusy}
|
||||
onClick={openBatchRequeue}
|
||||
variant="secondary"
|
||||
>
|
||||
批量重投
|
||||
@@ -466,9 +522,9 @@ export function AdminDownstreamDeliveriesPage() {
|
||||
<div className="downstream-delivery-list__actions" role="cell">
|
||||
<Button icon={<Eye size={14} />} onClick={() => setDetail(record)} size="sm" variant="ghost">详情</Button>
|
||||
<Button
|
||||
disabled={!replayableStatuses.has(record.status)}
|
||||
disabled={!replayableStatuses.has(record.status) || requeueBusy}
|
||||
icon={<RefreshCw size={14} />}
|
||||
onClick={() => replayRecord(record)}
|
||||
onClick={() => openSingleRequeue(record)}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
>
|
||||
@@ -493,6 +549,53 @@ export function AdminDownstreamDeliveriesPage() {
|
||||
</div>
|
||||
|
||||
{detail ? <DeliveryDetailModal record={detail} onClose={() => setDetail(null)} /> : null}
|
||||
{requeueTarget ? (
|
||||
<Modal
|
||||
footer={requeueResult ? (
|
||||
<Button onClick={closeRequeueDialog}>关闭</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button disabled={requeueBusy} onClick={closeRequeueDialog} variant="ghost">取消</Button>
|
||||
<Button disabled={requeueBusy} icon={<RefreshCw size={15} />} onClick={() => void confirmRequeue()} variant="warning">
|
||||
{requeueBusy ? '重投处理中…' : requeueTarget.kind === 'single' ? '确认重投' : `确认重投 ${requeueTarget.ids.length} 条`}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
onClose={closeRequeueDialog}
|
||||
open
|
||||
title={requeueResult?.title ?? (requeueTarget.kind === 'single' ? '确认人工重投' : '确认批量人工重投')}
|
||||
>
|
||||
{requeueResult ? (
|
||||
<div className={`downstream-requeue-result downstream-requeue-result--${requeueResult.status}`} role={requeueResult.status === 'success' ? 'status' : 'alert'}>
|
||||
<span aria-hidden="true">
|
||||
{requeueResult.status === 'success' ? <CheckCircle2 size={28} /> : <AlertTriangle size={28} />}
|
||||
</span>
|
||||
<div>
|
||||
<p>{requeueResult.message}</p>
|
||||
{requeueResult.failures?.length ? (
|
||||
<ul>
|
||||
{requeueResult.failures.map((item, index) => <li key={`${index}-${item}`}>{item}</li>)}
|
||||
</ul>
|
||||
) : null}
|
||||
{requeueResult.status === 'failed' ? <small>请求结果不明确时,请先刷新并核对人工重投次数,避免重复投递。</small> : null}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="downstream-requeue-confirm">
|
||||
<AlertTriangle aria-hidden="true" size={24} />
|
||||
<div>
|
||||
<p>
|
||||
{requeueTarget.kind === 'single'
|
||||
? `${requeueTarget.record.status === 'delivered' ? '该记录已收到客户端确认。' : ''}确认重投这条${deliveryTypeLabel[requeueTarget.record.deliveryType] ?? requeueTarget.record.deliveryType}吗?`
|
||||
: `即将重投所选 ${requeueTarget.ids.length} 条下游投递记录。`}
|
||||
</p>
|
||||
{requeueTarget.kind === 'single' ? <small>消息 ID:{requeueTarget.record.messageId ?? '-'}</small> : null}
|
||||
<strong>重投可能导致下游业务重复处理,请确认客户侧能够正确去重。</strong>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
) : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user