feat: add estimated completion time to cleaner progress dialog

- Display remaining time estimate (in minutes) when progress >= 5%
- Show estimated completion time in 12-hour format (上午/下午 HH:MM:SS)
- Update every second during execution
- Add startTime state tracking in useCleaner hook
This commit is contained in:
Misaka_Company
2026-03-05 14:17:34 +08:00
parent 49ac29e70c
commit 7d2cf934f8
3 changed files with 91 additions and 2 deletions

View File

@@ -31,6 +31,7 @@ interface ExecutionReportDialogProps {
dryRun?: boolean
isExecuting?: boolean
progress?: CleanerProgress | null
startTime?: number | null
}
export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
@@ -42,15 +43,55 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
errors = [],
dryRun = false,
isExecuting = false,
progress = null
progress = null,
startTime = null
}) => {
if (!isOpen) return null
const hasErrors = errors.length > 0
const showProgress = isExecuting && progress
const [now, setNow] = React.useState(Date.now())
React.useEffect(() => {
if (!showProgress || !startTime) return
const interval = setInterval(() => {
setNow(Date.now())
}, 1000)
return () => clearInterval(interval)
}, [showProgress, startTime])
const estimatedTime = React.useMemo(() => {
if (!showProgress || !startTime || !progress) return null
const currentProgress = progress.progress
if (currentProgress < 5) return null
const elapsedMs = now - startTime
const elapsedMinutes = elapsedMs / 60000
const totalEstimatedMinutes = elapsedMinutes / (currentProgress / 100)
const remainingMinutes = Math.max(0, totalEstimatedMinutes - elapsedMinutes)
const remainingRounded = Math.round(remainingMinutes)
const endTime = new Date(now + remainingMinutes * 60000)
const hours = endTime.getHours()
const minutes = endTime.getMinutes()
const seconds = endTime.getSeconds()
const period = hours >= 12 ? '下午' : '上午'
const displayHours = hours % 12 || 12
const formattedTime = `${period} ${displayHours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
return {
remainingMinutes: remainingRounded,
formattedTime
}
}, [showProgress, startTime, progress, now])
return (
<div className="execution-report-overlay" onClick={onClose}>
<div className="execution-report-overlay" onClick={showProgress ? undefined : onClose}>
<div
className="execution-report-dialog"
onClick={(e) => e.stopPropagation()}
@@ -130,6 +171,20 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
<span className="order-number">{progress.currentOrderNumber}</span>
</div>
)}
{estimatedTime && (
<div className="estimated-time-info">
<div className="estimated-time-item">
<span className="time-label"></span>
<span className="time-value">{estimatedTime.remainingMinutes} </span>
</div>
<div className="estimated-time-item">
<span className="time-label"></span>
<span className="time-value">{estimatedTime.formattedTime}</span>
<span className="time-label"></span>
</div>
</div>
)}
</div>
) : (
// Result View Content
@@ -443,6 +498,34 @@ export const ExecutionReportDialog: React.FC<ExecutionReportDialogProps> = ({
font-family: monospace;
}
.estimated-time-info {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 12px;
background: #f6ffed;
border-radius: 6px;
border: 1px solid #b7eb8f;
}
.estimated-time-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
}
.estimated-time-item .time-label {
color: #666;
}
.estimated-time-item .time-value {
font-size: 14px;
font-weight: 600;
color: #52c41a;
}
/* Result View Styles */
.stats-grid {
display: grid;

View File

@@ -67,6 +67,7 @@ export function useCleaner() {
// Progress state
const [progress, setProgress] = useState<CleanerProgress | null>(null)
const [startTime, setStartTime] = useState<number | null>(null)
// Execution settings state
const [headless, setHeadless] = useState(() => {
@@ -316,6 +317,7 @@ export function useCleaner() {
setIsRunning(true)
setIsExecuting(true)
setProgress(null)
setStartTime(Date.now())
setIsReportDialogOpen(true)
try {
@@ -355,6 +357,7 @@ export function useCleaner() {
setIsRunning(false)
setIsExecuting(false)
setProgress(null)
setStartTime(null)
}
}
@@ -430,6 +433,7 @@ export function useCleaner() {
cancelEdit,
handleAssignManagerOnSelect,
progress,
startTime,
handleValidation,
handleCheckboxToggle,
handleConfirmDeletion,

View File

@@ -57,6 +57,7 @@ const CleanerPage: React.FC = () => {
cancelEdit,
handleAssignManagerOnSelect,
progress,
startTime,
handleValidation,
handleCheckboxToggle,
handleConfirmDeletion,
@@ -485,6 +486,7 @@ const CleanerPage: React.FC = () => {
dryRun={dryRun}
isExecuting={isExecuting}
progress={progress}
startTime={startTime}
/>
</div>
)