mirror of
https://github.com/GSManagerXZ/GameServerManager.git
synced 2026-06-20 17:26:02 +08:00
优化压缩任务错误提示,增强用户反馈和任务管理
This commit is contained in:
@@ -75,7 +75,7 @@ import { ImagePreview } from '@/components/ImagePreview'
|
||||
import { EncodingConfirmDialog } from '@/components/EncodingConfirmDialog'
|
||||
import { FileChangedDialog } from '@/components/FileChangedDialog'
|
||||
import PasteConflictDialog from '@/components/PasteConflictDialog'
|
||||
import { FileItem } from '@/types/file'
|
||||
import { FileItem, Task } from '@/types/file'
|
||||
import socketClient from '@/utils/socket'
|
||||
import { fileApiClient } from '@/utils/fileApi'
|
||||
import { isTextFile, isImageFile } from '@/utils/format'
|
||||
@@ -193,6 +193,7 @@ const FileManagerPage: React.FC = () => {
|
||||
const [searchResults, setSearchResults] = useState<FileItem[]>([])
|
||||
const [isSearching, setIsSearching] = useState(false)
|
||||
const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
||||
const previousActiveTaskCountRef = useRef(0)
|
||||
|
||||
// 历史记录
|
||||
const [history, setHistory] = useState<string[]>([])
|
||||
@@ -774,6 +775,7 @@ const FileManagerPage: React.FC = () => {
|
||||
|
||||
// 初始加载任务列表
|
||||
loadActiveTasks()
|
||||
loadTasks()
|
||||
|
||||
// 加载系统盘符
|
||||
loadDrives()
|
||||
@@ -783,7 +785,7 @@ const FileManagerPage: React.FC = () => {
|
||||
|
||||
// 预加载系统信息(用于右键菜单权限判断)
|
||||
fetchSystemInfo()
|
||||
}, [searchParams, setCurrentPath, loadFiles, fetchSystemInfo, loadFavorites])
|
||||
}, [searchParams, setCurrentPath, loadFiles, loadActiveTasks, loadTasks, fetchSystemInfo, loadFavorites])
|
||||
|
||||
// 当路径变化时更新选中的盘符
|
||||
useEffect(() => {
|
||||
@@ -795,23 +797,26 @@ const FileManagerPage: React.FC = () => {
|
||||
}
|
||||
}, [currentPath, drives, selectedDrive, findDriveForPath])
|
||||
|
||||
// 定期刷新活动任务
|
||||
// 定期刷新任务状态
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
if (activeTasks.length > 0) {
|
||||
if (taskDrawerVisible || activeTasks.length > 0) {
|
||||
loadActiveTasks()
|
||||
// 如果有任务完成,刷新文件列表
|
||||
const hasCompletedTasks = activeTasks.some(task =>
|
||||
task.status === 'completed' || task.status === 'failed'
|
||||
)
|
||||
if (hasCompletedTasks) {
|
||||
loadFiles(undefined, true) // 重置分页
|
||||
}
|
||||
loadTasks()
|
||||
}
|
||||
}, 2000) // 每2秒刷新一次
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [activeTasks, loadFiles])
|
||||
}, [activeTasks.length, taskDrawerVisible, loadActiveTasks, loadTasks])
|
||||
|
||||
useEffect(() => {
|
||||
if (previousActiveTaskCountRef.current > 0 && activeTasks.length === 0) {
|
||||
loadTasks()
|
||||
loadFiles(undefined, true)
|
||||
}
|
||||
|
||||
previousActiveTaskCountRef.current = activeTasks.length
|
||||
}, [activeTasks.length, loadFiles, loadTasks])
|
||||
|
||||
// 键盘快捷键
|
||||
useEffect(() => {
|
||||
@@ -1268,7 +1273,7 @@ const FileManagerPage: React.FC = () => {
|
||||
|
||||
const handleCompressConfirm = async (archiveName: string, format: string, compressionLevel: number) => {
|
||||
const filePaths = compressDialog.files.map(file => file.path)
|
||||
const success = await compressFiles(filePaths, archiveName, format)
|
||||
const success = await compressFiles(filePaths, archiveName, format, compressionLevel)
|
||||
if (success) {
|
||||
addNotification({
|
||||
type: 'success',
|
||||
@@ -1524,6 +1529,42 @@ const FileManagerPage: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const getTaskTypeText = (type: Task['type']) => {
|
||||
switch (type) {
|
||||
case 'compress':
|
||||
return '压缩'
|
||||
case 'extract':
|
||||
return '解压'
|
||||
case 'copy':
|
||||
return '复制'
|
||||
case 'move':
|
||||
return '移动'
|
||||
case 'download':
|
||||
return '下载'
|
||||
default:
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
const getTaskTargetText = (task: Task) => {
|
||||
const archivePath = typeof task.data?.archivePath === 'string' ? task.data.archivePath : ''
|
||||
const targetPath = typeof task.data?.targetPath === 'string' ? task.data.targetPath : ''
|
||||
|
||||
if ((task.type === 'compress' || task.type === 'extract') && archivePath) {
|
||||
return getBasename(archivePath)
|
||||
}
|
||||
|
||||
if (task.type === 'download' && targetPath) {
|
||||
return getBasename(targetPath)
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
const visibleTasks = [...tasks]
|
||||
.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
|
||||
.slice(0, 12)
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col bg-white dark:bg-gray-900">
|
||||
{/* 工具栏 */}
|
||||
@@ -2426,27 +2467,30 @@ const FileManagerPage: React.FC = () => {
|
||||
width={400}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{activeTasks.length === 0 ? (
|
||||
{visibleTasks.length === 0 ? (
|
||||
<Empty
|
||||
description="暂无活动任务"
|
||||
description="暂无任务"
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
/>
|
||||
) : (
|
||||
activeTasks.map((task) => (
|
||||
visibleTasks.map((task) => (
|
||||
<Card key={task.id} size="small" className="mb-2">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
{getTaskStatusIcon(task.status)}
|
||||
<span className="font-medium">
|
||||
{task.type === 'compress' ? '压缩' :
|
||||
task.type === 'extract' ? '解压' :
|
||||
task.type === 'copy' ? '复制' :
|
||||
task.type === 'move' ? '移动' :
|
||||
task.type === 'download' ? '下载' : task.type}
|
||||
</span>
|
||||
<span className="text-gray-500">
|
||||
{getTaskStatusText(task.status)}
|
||||
</span>
|
||||
<div>
|
||||
<div className="flex items-center space-x-2">
|
||||
{getTaskStatusIcon(task.status)}
|
||||
<span className="font-medium">
|
||||
{getTaskTypeText(task.type)}
|
||||
</span>
|
||||
<span className="text-gray-500">
|
||||
{getTaskStatusText(task.status)}
|
||||
</span>
|
||||
</div>
|
||||
{getTaskTargetText(task) && (
|
||||
<div className="text-xs text-gray-500 mt-1 break-all">
|
||||
{getTaskTargetText(task)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{(task.status === 'completed' || task.status === 'failed') && (
|
||||
<Button
|
||||
@@ -2509,7 +2553,7 @@ const FileManagerPage: React.FC = () => {
|
||||
))
|
||||
)}
|
||||
|
||||
{activeTasks.length > 0 && (
|
||||
{visibleTasks.length > 0 && (
|
||||
<div className="text-center pt-4">
|
||||
<Button
|
||||
type="primary"
|
||||
|
||||
@@ -73,7 +73,7 @@ interface FileStore {
|
||||
clearClipboard: () => void
|
||||
|
||||
// 压缩解压操作
|
||||
compressFiles: (filePaths: string[], archiveName: string, format?: string) => Promise<boolean>
|
||||
compressFiles: (filePaths: string[], archiveName: string, format?: string, compressionLevel?: number) => Promise<boolean>
|
||||
extractArchive: (archivePath: string) => Promise<boolean>
|
||||
|
||||
// 编辑器操作
|
||||
@@ -614,13 +614,17 @@ export const useFileStore = create<FileStore>((set, get) => ({
|
||||
},
|
||||
|
||||
// 压缩文件
|
||||
compressFiles: async (filePaths: string[], archiveName: string, format: string = 'zip') => {
|
||||
compressFiles: async (
|
||||
filePaths: string[],
|
||||
archiveName: string,
|
||||
format: string = 'zip',
|
||||
compressionLevel: number = 6
|
||||
) => {
|
||||
const { currentPath } = get()
|
||||
|
||||
try {
|
||||
const result = await fileApiClient.compressFiles(filePaths, currentPath, archiveName, format)
|
||||
// 立即刷新活动任务列表
|
||||
await get().loadActiveTasks()
|
||||
await fileApiClient.compressFiles(filePaths, currentPath, archiveName, format, compressionLevel)
|
||||
await Promise.all([get().loadActiveTasks(), get().loadTasks()])
|
||||
return true
|
||||
} catch (error: any) {
|
||||
set({ error: error.message || '压缩文件失败' })
|
||||
@@ -633,9 +637,8 @@ export const useFileStore = create<FileStore>((set, get) => ({
|
||||
const { currentPath } = get()
|
||||
|
||||
try {
|
||||
const result = await fileApiClient.extractArchive(archivePath, currentPath)
|
||||
// 立即刷新活动任务列表
|
||||
await get().loadActiveTasks()
|
||||
await fileApiClient.extractArchive(archivePath, currentPath)
|
||||
await Promise.all([get().loadActiveTasks(), get().loadTasks()])
|
||||
return true
|
||||
} catch (error: any) {
|
||||
set({ error: error.message || '解压文件失败' })
|
||||
|
||||
23
docs/压缩任务错误提示优化说明.md
Normal file
23
docs/压缩任务错误提示优化说明.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# 压缩任务错误提示优化说明
|
||||
|
||||
## 本次调整
|
||||
|
||||
针对文件管理器中的压缩任务,补充了以下优化:
|
||||
|
||||
- 压缩工具启动失败时,任务列表会直接显示更明确的中文错误原因
|
||||
- 当错误属于执行权限不足(`EACCES`)时,会提示为二进制文件添加执行权限,并附带工具路径与参考命令
|
||||
- 文件管理器任务抽屉不再只显示活动任务,同时会保留最近结束的任务,避免“失败太快导致列表为空”
|
||||
- 压缩弹窗中选择的压缩级别会实际传递到后端接口
|
||||
|
||||
## 典型错误提示
|
||||
|
||||
当 Linux 环境中的 `file_zip_linux_x64` 缺少执行权限时,任务列表会显示类似提示:
|
||||
|
||||
```text
|
||||
Zip-Tools 无法启动:压缩工具缺少执行权限,请为该文件添加可执行权限后重试。
|
||||
参考命令: chmod +x "/path/to/file_zip_linux_x64"
|
||||
```
|
||||
|
||||
## 使用效果
|
||||
|
||||
用户在收到“请查看任务列表”提示后,即使压缩任务瞬时失败,也可以在任务抽屉中直接看到失败记录和原因,不再需要优先查看后端日志才能判断问题。
|
||||
@@ -326,6 +326,30 @@ class ZipToolsManager {
|
||||
await this.download7z()
|
||||
}
|
||||
|
||||
private buildProcessStartError(
|
||||
toolName: string,
|
||||
toolPath: string,
|
||||
error: NodeJS.ErrnoException
|
||||
): Error {
|
||||
const resolvedToolPath = path.resolve(toolPath)
|
||||
|
||||
if (error.code === 'EACCES') {
|
||||
return new Error(
|
||||
`${toolName} 无法启动:压缩工具缺少执行权限,请为该文件添加可执行权限后重试。参考命令: chmod +x "${resolvedToolPath}"。工具路径: ${resolvedToolPath}`
|
||||
)
|
||||
}
|
||||
|
||||
if (error.code === 'ENOENT') {
|
||||
return new Error(
|
||||
`${toolName} 无法启动:未找到压缩工具文件,请检查文件是否存在或重新下载依赖。工具路径: ${resolvedToolPath}`
|
||||
)
|
||||
}
|
||||
|
||||
return new Error(
|
||||
`${toolName} 进程启动失败: ${error.message || '未知错误'}。工具路径: ${resolvedToolPath}`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 7z 子进程并等待完成
|
||||
* 退出码为 0 表示成功,非 0 抛出包含 stderr 的异常
|
||||
@@ -341,8 +365,8 @@ class ZipToolsManager {
|
||||
stderr += data.toString()
|
||||
})
|
||||
|
||||
child.on('error', (error: Error) => {
|
||||
reject(new Error(`7z 进程启动失败: ${error.message}`))
|
||||
child.on('error', (error: NodeJS.ErrnoException) => {
|
||||
reject(this.buildProcessStartError('7z', toolPath, error))
|
||||
})
|
||||
|
||||
child.on('close', (code: number | null) => {
|
||||
@@ -453,8 +477,8 @@ class ZipToolsManager {
|
||||
stderr += data.toString()
|
||||
})
|
||||
|
||||
child.on('error', (error: Error) => {
|
||||
reject(new Error(`Zip-Tools 进程启动失败: ${error.message}`))
|
||||
child.on('error', (error: NodeJS.ErrnoException) => {
|
||||
reject(this.buildProcessStartError('Zip-Tools', toolPath, error))
|
||||
})
|
||||
|
||||
child.on('close', (code: number | null) => {
|
||||
|
||||
Reference in New Issue
Block a user