feat(cleaner): add automatic retry mechanism for failed orders

- Add retry logic with max 2 attempts per failed order
- Track retry statistics (retriedOrders, successfulRetries)
- Generate detailed retry report section in execution reports
- Display retry metrics in ExecutionReportDialog UI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-03-13 15:59:10 +08:00
parent a25ffd75c5
commit 715dfb4d71
9 changed files with 325 additions and 21 deletions

View File

@@ -34,6 +34,9 @@ interface ExecutionReportDialogProps {
progress?: CleanerProgress | null
startTime?: number | null
triggerRef?: React.RefObject<HTMLElement | null>
// Retry-related props
retriedOrders?: number
successfulRetries?: number
}
export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
@@ -47,11 +50,14 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
isExecuting = false,
progress = null,
startTime = null,
triggerRef
triggerRef,
retriedOrders = 0,
successfulRetries = 0
}) => {
const [now, setNow] = React.useState(() => Date.now())
const hasErrors = errors.length > 0
const hasRetries = retriedOrders > 0
const showProgress = isExecuting && progress
const isProgressing = !!showProgress
@@ -259,6 +265,46 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
</div>
</div>
)}
{hasRetries && (
<>
<div className="bg-gray-50 rounded-lg p-3 flex items-center gap-3 border border-gray-200">
<div className="w-9 h-9 rounded-lg bg-purple-50 flex items-center justify-center flex-shrink-0">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-purple-600"
>
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
<path d="M3 3v5h5" />
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" />
<path d="M16 21h5v-5" />
</svg>
</div>
<div className="flex-1 min-w-0">
<div className="text-xs text-gray-600"></div>
<div className="text-xl font-semibold text-gray-900">{retriedOrders}</div>
</div>
</div>
<div className="bg-gray-50 rounded-lg p-3 flex items-center gap-3 border border-gray-200">
<div className="w-9 h-9 rounded-lg bg-emerald-50 flex items-center justify-center flex-shrink-0">
<CheckCircle size={20} className="text-emerald-600" />
</div>
<div className="flex-1 min-w-0">
<div className="text-xs text-gray-600"></div>
<div className="text-xl font-semibold text-gray-900">{successfulRetries}</div>
</div>
</div>
</>
)}
</div>
{hasErrors && (

View File

@@ -107,11 +107,7 @@ export function ConfirmDialog({
<Button variant="secondary" onClick={onCancel}>
{cancelText}
</Button>
<Button
data-autofocus="true"
variant={styles.buttonVariant}
onClick={onConfirm}
>
<Button data-autofocus="true" variant={styles.buttonVariant} onClick={onConfirm}>
{confirmText}
</Button>
</div>
@@ -125,14 +121,14 @@ export function ConfirmDialog({
*/
export function useConfirmDialog() {
const [config, setConfig] = useState<
(Omit<ConfirmDialogProps, 'isOpen' | 'onConfirm' | 'onCancel'> & {
resolve: (value: boolean) => void
}) | null>(null)
| (Omit<ConfirmDialogProps, 'isOpen' | 'onConfirm' | 'onCancel'> & {
resolve: (value: boolean) => void
})
| null
>(null)
const confirm = useCallback(
(
options: Omit<ConfirmDialogProps, 'isOpen' | 'onConfirm' | 'onCancel'>
): Promise<boolean> => {
(options: Omit<ConfirmDialogProps, 'isOpen' | 'onConfirm' | 'onCancel'>): Promise<boolean> => {
return new Promise<boolean>((resolve) => {
setConfig({
...options,

View File

@@ -65,6 +65,8 @@ export function useCleaner() {
materialsDeleted: number
materialsSkipped: number
errors: string[]
retriedOrders?: number
successfulRetries?: number
} | null>(null)
// Progress state
@@ -426,7 +428,9 @@ export function useCleaner() {
ordersProcessed: cleanerRunData.ordersProcessed,
materialsDeleted: cleanerRunData.materialsDeleted,
materialsSkipped: cleanerRunData.materialsSkipped,
errors: cleanerRunData.errors
errors: cleanerRunData.errors,
retriedOrders: cleanerRunData.retriedOrders,
successfulRetries: cleanerRunData.successfulRetries
})
} else {
throw new Error(response.error || '清理失败')

View File

@@ -239,7 +239,9 @@ export function useDialogFocus(options: UseDialogFocusOptions): UseDialogFocusRe
if (style.visibility === 'hidden') {
if (import.meta.env.DEV) {
console.warn('[useDialogFocus] Trigger element is visibility: hidden, cannot restore focus')
console.warn(
'[useDialogFocus] Trigger element is visibility: hidden, cannot restore focus'
)
}
return
}

View File

@@ -496,6 +496,8 @@ const CleanerPage: React.FC = () => {
progress={progress}
startTime={startTime}
triggerRef={executeButtonRef}
retriedOrders={reportData?.retriedOrders}
successfulRetries={reportData?.successfulRetries}
/>
{/* Confirmation Dialog */}