feat: add progress indicator to cleaner execution report dialog
- Add CleanerProgress type for tracking execution progress - Implement progress calculation: (1 + i + j/Mᵢ)/(1+N) × 100 - Login complete: 1/(1+N) × 100 - Per material: (1 + orderIndex + materialIdx/totalMaterials)/(1+totalOrders) × 100 - Add cleaner.onProgress IPC event for real-time progress updates - Enhance ExecutionReportDialog with progress bar and status display - Update useCleaner hook with progress state management - Dialog opens immediately on execution start, showing progress then results
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import { ipcMain, webContents } from 'electron'
|
||||
import { ErpAuthService } from '../services/erp/erp-auth'
|
||||
import { CleanerService } from '../services/erp/cleaner'
|
||||
import { OrderNumberResolver } from '../services/erp/order-resolver'
|
||||
@@ -11,12 +11,38 @@ import { ErpConnectionError, ValidationError, DatabaseQueryError } from '../type
|
||||
import type {
|
||||
CleanerInput,
|
||||
CleanerResult,
|
||||
CleanerProgress,
|
||||
ExportResultItem,
|
||||
ExportResultResponse
|
||||
} from '../types/cleaner.types'
|
||||
|
||||
const log = createLogger('CleanerHandler')
|
||||
|
||||
function sendProgress(
|
||||
windowId: number,
|
||||
message: string,
|
||||
progress: number,
|
||||
extra?: Partial<CleanerProgress>
|
||||
): void {
|
||||
try {
|
||||
const progressData: CleanerProgress = {
|
||||
message,
|
||||
progress,
|
||||
currentOrderIndex: extra?.currentOrderIndex ?? 0,
|
||||
totalOrders: extra?.totalOrders ?? 0,
|
||||
currentMaterialIndex: extra?.currentMaterialIndex ?? 0,
|
||||
totalMaterialsInOrder: extra?.totalMaterialsInOrder ?? 0,
|
||||
currentOrderNumber: extra?.currentOrderNumber,
|
||||
phase: extra?.phase ?? 'processing'
|
||||
}
|
||||
webContents.getAllWebContents().forEach((wc) => {
|
||||
wc.send('cleaner:progress', progressData)
|
||||
})
|
||||
} catch (error) {
|
||||
log.warn('Failed to send progress event', { error })
|
||||
}
|
||||
}
|
||||
|
||||
async function getDatabaseService(): Promise<MySqlService | SqlServerService> {
|
||||
const dbType = process.env.DB_TYPE?.toLowerCase()
|
||||
|
||||
@@ -50,7 +76,9 @@ async function getDatabaseService(): Promise<MySqlService | SqlServerService> {
|
||||
export function registerCleanerHandlers(): void {
|
||||
ipcMain.handle(
|
||||
'cleaner:run',
|
||||
async (_event, input: CleanerInput): Promise<IpcResult<CleanerResult>> => {
|
||||
async (event, input: CleanerInput): Promise<IpcResult<CleanerResult>> => {
|
||||
const windowId = event.sender.id
|
||||
|
||||
return withErrorHandling(async () => {
|
||||
let authService: ErpAuthService | null = null
|
||||
let dbService: MySqlService | SqlServerService | null = null
|
||||
@@ -125,12 +153,25 @@ export function registerCleanerHandlers(): void {
|
||||
}
|
||||
log.info('Login successful')
|
||||
|
||||
// Send login complete progress
|
||||
const totalOrders = validOrderNumbers.length
|
||||
const loginProgress = (1 / (1 + totalOrders)) * 100
|
||||
sendProgress(windowId, 'ERP 登录成功', loginProgress, {
|
||||
phase: 'login',
|
||||
currentOrderIndex: 0,
|
||||
totalOrders,
|
||||
currentMaterialIndex: 0,
|
||||
totalMaterialsInOrder: 0
|
||||
})
|
||||
|
||||
const cleaner = new CleanerService(authService)
|
||||
|
||||
const modifiedInput: CleanerInput = {
|
||||
...input,
|
||||
orderNumbers: validOrderNumbers,
|
||||
onProgress: input.onProgress
|
||||
onProgress: (message, progress, extra) => {
|
||||
sendProgress(windowId, message, progress ?? 0, extra)
|
||||
}
|
||||
}
|
||||
|
||||
log.info('Starting cleaning', { orderCount: validOrderNumbers.length })
|
||||
@@ -140,6 +181,15 @@ export function registerCleanerHandlers(): void {
|
||||
result.errors = [...warnings, ...result.errors]
|
||||
}
|
||||
|
||||
// Send completion progress
|
||||
sendProgress(windowId, '清理完成', 100, {
|
||||
phase: 'complete',
|
||||
currentOrderIndex: totalOrders,
|
||||
totalOrders,
|
||||
currentMaterialIndex: 0,
|
||||
totalMaterialsInOrder: 0
|
||||
})
|
||||
|
||||
log.info('Cleaning completed', {
|
||||
processedCount: result.ordersProcessed,
|
||||
errorCount: result.errors.length
|
||||
|
||||
@@ -87,6 +87,8 @@ export class CleanerService {
|
||||
details: []
|
||||
}
|
||||
|
||||
const totalOrders = input.orderNumbers.length
|
||||
|
||||
// Create delete set for O(1) lookup
|
||||
const deleteSet = new Set(input.materialCodes)
|
||||
|
||||
@@ -100,14 +102,8 @@ export class CleanerService {
|
||||
await this.setupQueryInterface(workFrame)
|
||||
|
||||
// Process each order
|
||||
for (let i = 0; i < input.orderNumbers.length; i++) {
|
||||
for (let i = 0; i < totalOrders; i++) {
|
||||
const orderNumber = input.orderNumbers[i]
|
||||
const progress = ((i + 1) / input.orderNumbers.length) * 100
|
||||
|
||||
input.onProgress?.(
|
||||
`Processing order ${i + 1}/${input.orderNumbers.length}: ${orderNumber}`,
|
||||
progress
|
||||
)
|
||||
|
||||
try {
|
||||
const detail = await this.processOrder({
|
||||
@@ -115,7 +111,7 @@ export class CleanerService {
|
||||
popupPage,
|
||||
orderNumber,
|
||||
orderIndex: i,
|
||||
totalOrders: input.orderNumbers.length,
|
||||
totalOrders,
|
||||
deleteSet,
|
||||
dryRun: input.dryRun ?? this.dryRun,
|
||||
onProgress: input.onProgress
|
||||
@@ -212,7 +208,11 @@ export class CleanerService {
|
||||
totalOrders: number
|
||||
deleteSet: Set<string>
|
||||
dryRun: boolean
|
||||
onProgress?: (message: string, progress?: number) => void
|
||||
onProgress?: (
|
||||
message: string,
|
||||
progress?: number,
|
||||
extra?: Partial<import('../../types/cleaner.types').CleanerProgress>
|
||||
) => void
|
||||
}): Promise<OrderCleanDetail> {
|
||||
const {
|
||||
workFrame,
|
||||
@@ -280,6 +280,19 @@ export class CleanerService {
|
||||
const statusMatch = statusText.replace(/\n/g, '').match(/备料状态:(.+)$/)
|
||||
const detailStatus = statusMatch ? statusMatch[1].trim() : ''
|
||||
|
||||
// Send progress for order start
|
||||
onProgress?.(
|
||||
`开始处理订单 ${orderIndex + 1}/${totalOrders}: ${orderNumber}`,
|
||||
((1 + orderIndex) / (1 + totalOrders)) * 100,
|
||||
{
|
||||
currentOrderIndex: orderIndex + 1,
|
||||
totalOrders,
|
||||
currentMaterialIndex: 0,
|
||||
totalMaterialsInOrder: detailCount,
|
||||
currentOrderNumber: orderNumber
|
||||
}
|
||||
)
|
||||
|
||||
// Process based on status (Python lines 228-441)
|
||||
if (detailStatus === '审批通过' && detailCount > 0) {
|
||||
// Click modify button (Python line 235)
|
||||
@@ -319,10 +332,20 @@ export class CleanerService {
|
||||
const materialName = await this.getInputValue(childForm, /^材料名称/)
|
||||
const pendingQty = await this.getInputValue(childForm, /^累计待发数量$/)
|
||||
|
||||
// Report progress
|
||||
// Report progress using formula: (1 + i + j/Mᵢ) / (1 + N) × 100
|
||||
// where i = orderIndex (0-based), j = materialIdx (1-based), Mᵢ = detailCount, N = totalOrders
|
||||
const progress = ((1 + orderIndex + materialIdx / detailCount) / (1 + totalOrders)) * 100
|
||||
|
||||
onProgress?.(
|
||||
`Order ${orderNumber} - Material ${materialIdx}/${detailCount}: ${materialName}`,
|
||||
((orderIndex + materialIdx / detailCount) / totalOrders) * 100
|
||||
`订单 ${orderIndex + 1}/${totalOrders} - 物料 ${materialIdx}/${detailCount}: ${materialName}`,
|
||||
progress,
|
||||
{
|
||||
currentOrderIndex: orderIndex + 1,
|
||||
totalOrders,
|
||||
currentMaterialIndex: materialIdx,
|
||||
totalMaterialsInOrder: detailCount,
|
||||
currentOrderNumber: orderNumber
|
||||
}
|
||||
)
|
||||
|
||||
// Check if should delete (Python lines 284-406)
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
export type CleanerPhase = 'login' | 'processing' | 'complete'
|
||||
|
||||
export interface CleanerProgress {
|
||||
message: string
|
||||
progress: number
|
||||
currentOrderIndex: number
|
||||
totalOrders: number
|
||||
currentMaterialIndex: number
|
||||
totalMaterialsInOrder: number
|
||||
currentOrderNumber?: string
|
||||
phase: CleanerPhase
|
||||
}
|
||||
|
||||
export interface CleanerInput {
|
||||
orderNumbers: string[]
|
||||
materialCodes: string[]
|
||||
dryRun: boolean
|
||||
headless?: boolean
|
||||
onProgress?: (message: string, progress?: number) => void
|
||||
onProgress?: (message: string, progress?: number, extra?: Partial<CleanerProgress>) => void
|
||||
}
|
||||
|
||||
export interface CleanerResult {
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { ExtractorInput, ExtractorResult, ExtractionProgress } from './extr
|
||||
import type {
|
||||
CleanerInput,
|
||||
CleanerResult,
|
||||
CleanerProgress,
|
||||
ExportResultItem,
|
||||
ExportResultResponse
|
||||
} from './cleaner.types'
|
||||
@@ -108,6 +109,13 @@ export interface CleanerAPI {
|
||||
* @param items - Validation result items to export
|
||||
*/
|
||||
exportResults: (items: ExportResultItem[]) => Promise<ExportResultResponse>
|
||||
|
||||
/**
|
||||
* Subscribe to progress updates
|
||||
* @param callback - Callback function receiving progress data
|
||||
* @returns Unsubscribe function
|
||||
*/
|
||||
onProgress: (callback: (data: CleanerProgress) => void) => () => void
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user