diff --git a/src/renderer/src/components/ExecutionReportDialog.tsx b/src/renderer/src/components/ExecutionReportDialog.tsx index 562a8fa..6a3b45e 100644 --- a/src/renderer/src/components/ExecutionReportDialog.tsx +++ b/src/renderer/src/components/ExecutionReportDialog.tsx @@ -7,8 +7,10 @@ * - Error list (if any) */ -import React from 'react' +import React, { useRef } from 'react' import { CheckCircle, XCircle, SkipForward, Package, Loader2 } from 'lucide-react' +import FocusLock from 'react-focus-lock' +import { useDialogFocus } from '../hooks/useDialogFocus' interface CleanerProgress { message: string @@ -46,6 +48,34 @@ export const ExecutionReportDialog: React.FC = ({ progress = null, startTime = null }) => { + const dialogRef = useRef(null) + + // Setup focus management with custom escape key handling + const { focusLockProps } = useDialogFocus({ + isOpen, + dialogRef, + onClose + }) + + // Custom escape key handling - only close when NOT executing + React.useEffect(() => { + if (!isOpen) return + + const handleKeyDown = (event: KeyboardEvent) => { + // Only allow escape to close when not executing + if ((event.key === 'Escape' || event.keyCode === 27) && !isExecuting) { + event.preventDefault() + event.stopPropagation() + onClose() + } + } + + window.addEventListener('keydown', handleKeyDown) + return () => { + window.removeEventListener('keydown', handleKeyDown) + } + }, [isOpen, isExecuting, onClose]) + if (!isOpen) return null const hasErrors = errors.length > 0 @@ -91,188 +121,202 @@ export const ExecutionReportDialog: React.FC = ({ }, [showProgress, startTime, progress, now]) return ( -
+
e.stopPropagation()} - style={{ width: showProgress ? '560px' : '480px' }} + className="execution-report-overlay" + onClick={showProgress ? undefined : onClose} + role="dialog" + aria-modal="true" + aria-labelledby={ + showProgress ? 'execution-dialog-progress-title' : 'execution-dialog-report-title' + } > - {showProgress ? ( - // Progress View -
-
- -
-

正在执行清理...

-

{progress?.message || '处理中...'}

-
- ) : ( - // Result View -
-
- {dryRun ? ( - - ) : hasErrors ? ( - - ) : ( - - )} -
-

- {dryRun ? '预览执行报告' : hasErrors ? '执行完成 (有错误)' : '执行完成'} -

-

- {dryRun - ? '预览模式 - 未实际删除数据' - : hasErrors - ? '部分操作未能完成,请查看下方错误信息' - : '所有操作已成功完成'} -

-
- )} - -
+
e.stopPropagation()} + style={{ width: showProgress ? '560px' : '480px' }} + > {showProgress ? ( - // Progress View Content -
-
-
+ // Progress View +
+
+
- -
-
- 订单进度 - - {Math.min(progress!.currentOrderIndex, progress!.totalOrders)} /{' '} - {progress!.totalOrders} - -
- {progress!.totalMaterialsInOrder > 0 && ( -
- 物料进度 - - {progress!.currentMaterialIndex} / {progress!.totalMaterialsInOrder} - -
- )} -
- 总进度 - {Math.round(progress?.progress || 0)}% -
-
- - {progress?.currentOrderNumber && ( -
- - 当前订单: - {progress.currentOrderNumber} -
- )} - - {estimatedTime && ( -
-
- 预估还要 - {estimatedTime.remainingMinutes} 分钟 -
-
- 将会在 - {estimatedTime.formattedTime} - 执行完毕 -
-
- )} +

+ 正在执行清理... +

+

+ {progress?.message || '处理中...'} +

) : ( - // Result View Content - <> -
-
-
- + // Result View +
+
+ {dryRun ? ( + + ) : hasErrors ? ( + + ) : ( + + )} +
+

+ {dryRun ? '预览执行报告' : hasErrors ? '执行完成 (有错误)' : '执行完成'} +

+

+ {dryRun + ? '预览模式 - 未实际删除数据' + : hasErrors + ? '部分操作未能完成,请查看下方错误信息' + : '所有操作已成功完成'} +

+
+ )} + +
+ {showProgress ? ( + // Progress View Content +
+
+
+
+ +
+
+ 订单进度 + + {Math.min(progress!.currentOrderIndex, progress!.totalOrders)} /{' '} + {progress!.totalOrders} +
-
-
处理订单
-
{ordersProcessed}
+ {progress!.totalMaterialsInOrder > 0 && ( +
+ 物料进度 + + {progress!.currentMaterialIndex} / {progress!.totalMaterialsInOrder} + +
+ )} +
+ 总进度 + {Math.round(progress?.progress || 0)}%
-
-
- + {progress?.currentOrderNumber && ( +
+ + 当前订单: + {progress.currentOrderNumber}
-
-
{dryRun ? '拟删除物料' : '删除物料'}
-
{materialsDeleted}
-
-
+ )} -
-
- + {estimatedTime && ( +
+
+ 预估还要 + {estimatedTime.remainingMinutes} 分钟 +
+
+ 将会在 + {estimatedTime.formattedTime} + 执行完毕 +
-
-
跳过物料
-
{materialsSkipped}
+ )} +
+ ) : ( + // Result View Content + <> +
+
+
+ +
+
+
处理订单
+
{ordersProcessed}
+
+ +
+
+ +
+
+
{dryRun ? '拟删除物料' : '删除物料'}
+
{materialsDeleted}
+
+
+ +
+
+ +
+
+
跳过物料
+
{materialsSkipped}
+
+
+ + {hasErrors && ( +
+
+ +
+
+
错误数量
+
{errors.length}
+
+
+ )}
{hasErrors && ( -
-
- -
-
-
错误数量
-
{errors.length}
+
+
错误详情
+
+ {errors.map((error, index) => ( +
+ + {error} +
+ ))}
)} -
- {hasErrors && ( -
-
错误详情
-
- {errors.map((error, index) => ( -
- - {error} -
- ))} + {!hasErrors && !dryRun && ( +
+ + 操作已成功完成,数据已同步到 ERP 系统
-
- )} + )} - {!hasErrors && !dryRun && ( -
- - 操作已成功完成,数据已同步到 ERP 系统 -
- )} + {!hasErrors && dryRun && ( +
+ + 预览模式结束,数据未实际修改。确认无误后可正式执行。 +
+ )} + + )} +
- {!hasErrors && dryRun && ( -
- - 预览模式结束,数据未实际修改。确认无误后可正式执行。 -
- )} - - )} -
+
+ {!showProgress && ( + + )} +
-
- {!showProgress && ( - - )} -
- - +
-
+ ) } diff --git a/src/renderer/src/components/UserSelectionDialog.tsx b/src/renderer/src/components/UserSelectionDialog.tsx index 6298335..e62dd38 100644 --- a/src/renderer/src/components/UserSelectionDialog.tsx +++ b/src/renderer/src/components/UserSelectionDialog.tsx @@ -7,7 +7,9 @@ * - Return selected user info */ -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, useRef } from 'react' +import FocusLock from 'react-focus-lock' +import { useDialogFocus } from '../hooks/useDialogFocus' export interface UserInfo { id: number @@ -32,6 +34,14 @@ export const UserSelectionDialog: React.FC = ({ onCancel }) => { const [selectedUserId, setSelectedUserId] = useState(null) + const dialogRef = useRef(null) + + const { focusLockProps } = useDialogFocus({ + isOpen, + dialogRef, + onClose: onCancel, + initialFocusSelector: users.length > 0 ? '.user-item:first-child' : undefined + }) // Reset selection when dialog opens useEffect(() => { @@ -58,60 +68,68 @@ export const UserSelectionDialog: React.FC = ({ if (!isOpen) return null return ( -
-
-
-

选择用户

-

当前登录:{currentUsername}

-
+ +
+
+
+

+ 选择用户 +

+

当前登录:{currentUsername}

+
-
-
- {users.map((user) => ( -
setSelectedUserId(user.id)} - onDoubleClick={() => handleDoubleClick(user)} - > -
-
- {user.username} - - {user.userType} - -
- {user.createTime && ( +
+
+ {users.map((user) => ( +
setSelectedUserId(user.id)} + onDoubleClick={() => handleDoubleClick(user)} + > +
- - 创建于:{new Date(user.createTime).toLocaleString('zh-CN')} + {user.username} + + {user.userType}
- )} + {user.createTime && ( +
+ + 创建于:{new Date(user.createTime).toLocaleString('zh-CN')} + +
+ )} +
-
- ))} + ))} +
-
-
- - -
+
+ + +
-
双击用户可直接选择
+
双击用户可直接选择
+
-
+ ) }