下载页面卡主问题修复

This commit is contained in:
2025-08-28 11:37:03 +08:00
parent 514a2ce1a1
commit 687cf2943e
3 changed files with 171 additions and 14 deletions
+62
View File
@@ -25,6 +25,12 @@ class DownloadExecutor {
break; break;
} }
// 检查是否应该暂停
if (this.shouldPause(task.id)) {
console.log('任务已暂停,停止下载:', task.id);
break;
}
// 从图片对象中获取指定尺寸的URL // 从图片对象中获取指定尺寸的URL
const imageObj = images[index]; const imageObj = images[index];
let imageUrl; let imageUrl;
@@ -158,6 +164,12 @@ class DownloadExecutor {
break; break;
} }
// 检查是否应该暂停
if (this.shouldPause(task.id)) {
console.log('批量下载任务已暂停,停止下载:', task.id);
break;
}
const batch = artworkIds.slice(i, i + concurrent); const batch = artworkIds.slice(i, i + concurrent);
const batchPromises = batch.map(async artworkId => { const batchPromises = batch.map(async artworkId => {
try { try {
@@ -503,6 +515,56 @@ class DownloadExecutor {
const match = url.match(/\.([a-zA-Z0-9]+)(?:\?|$)/); const match = url.match(/\.([a-zA-Z0-9]+)(?:\?|$)/);
return match ? match[1] : 'jpg'; return match ? match[1] : 'jpg';
} }
/**
* 恢复暂停的任务
*/
async resumeTask(taskId) {
const task = this.taskManager.getTask(taskId);
if (!task) {
throw new Error('任务不存在');
}
if (task.status !== 'paused') {
throw new Error('任务状态不是暂停状态');
}
// 根据任务类型重新开始下载
if (task.type === 'artwork') {
// 重新获取作品信息和图片URL
const artworkResult = await this.downloadService.artworkService.getArtwork(task.artwork_id);
if (!artworkResult.success) {
throw new Error(`获取作品信息失败: ${artworkResult.error}`);
}
const imagesResult = await this.downloadService.artworkService.getArtworkImages(task.artwork_id, 'original');
if (!imagesResult.success) {
throw new Error(`获取图片URL失败: ${imagesResult.error}`);
}
const artwork = artworkResult.data;
const images = imagesResult.data.images;
const artworkDir = await this.fileManager.getArtworkDirectory(artwork);
// 重新开始下载
this.executeArtworkDownload(task, images, 'original', artworkDir, artwork);
} else if (task.type === 'batch' || task.type === 'artist') {
// 批量下载和作者下载的恢复逻辑
// 这里需要根据具体实现来恢复
console.log('恢复批量下载任务:', taskId);
// TODO: 实现批量下载的恢复逻辑
}
return { success: true };
}
/**
* 检查任务是否应该暂停
*/
shouldPause(taskId) {
const task = this.taskManager.getTask(taskId);
return task && task.status === 'paused';
}
} }
module.exports = DownloadExecutor; module.exports = DownloadExecutor;
+9 -3
View File
@@ -134,11 +134,17 @@ class DownloadService {
return { success: false, error: '任务状态不是暂停状态' }; return { success: false, error: '任务状态不是暂停状态' };
} }
// 更新任务状态为下载中
await this.taskManager.updateTask(taskId, { status: 'downloading' }); await this.taskManager.updateTask(taskId, { status: 'downloading' });
this.progressManager.notifyProgressUpdate(taskId, task);
// 重新开始下载 // 通知进度更新
return this.downloadArtwork(task.artwork_id, { skipExisting: false }); const updatedTask = this.taskManager.getTask(taskId);
this.progressManager.notifyProgressUpdate(taskId, updatedTask);
// 重新开始下载执行
this.downloadExecutor.resumeTask(taskId);
return { success: true, data: updatedTask };
} }
// 代理方法 - 历史记录管理 // 代理方法 - 历史记录管理
+100 -11
View File
@@ -62,7 +62,10 @@
<div class="task-header"> <div class="task-header">
<div class="task-info"> <div class="task-info">
<h3 class="task-title"> <h3 class="task-title">
{{ getTaskTitle(task) }} <a v-if="task.artwork_id" :href="`/artwork/${task.artwork_id}`" class="task-link">
{{ getTaskTitle(task) }}
</a>
<span v-else>{{ getTaskTitle(task) }}</span>
</h3> </h3>
<span class="task-status" :class="task.status"> <span class="task-status" :class="task.status">
{{ getStatusText(task.status) }} {{ getStatusText(task.status) }}
@@ -72,6 +75,12 @@
<button v-if="task.status === 'downloading'" @click="cancelTask(task.id)" class="btn btn-danger btn-sm"> <button v-if="task.status === 'downloading'" @click="cancelTask(task.id)" class="btn btn-danger btn-sm">
取消 取消
</button> </button>
<button v-if="task.status === 'paused'" @click="resumeTask(task.id)" class="btn btn-primary btn-sm">
恢复
</button>
<button v-if="task.status === 'paused'" @click="cancelTask(task.id)" class="btn btn-danger btn-sm">
删除
</button>
</div> </div>
</div> </div>
@@ -205,7 +214,7 @@ const history = ref<any[]>([]);
// SSE连接管理 // SSE连接管理
const sseConnections = ref<Map<string, () => void>>(new Map()); const sseConnections = ref<Map<string, () => void>>(new Map());
// 计算属性:显示活跃任务 // 计算属性:显示活跃任务和暂停任务
const activeTasks = computed(() => { const activeTasks = computed(() => {
return tasks.value.filter(task => return tasks.value.filter(task =>
['downloading', 'paused'].includes(task.status) ['downloading', 'paused'].includes(task.status)
@@ -298,9 +307,9 @@ const fetchTasks = async () => {
if (response.success) { if (response.success) {
tasks.value = response.data || []; tasks.value = response.data || [];
// 为活跃任务建立SSE连接 // 只为正在下载的任务建立SSE连接,避免为暂停任务建立连接
activeTasks.value.forEach(task => { activeTasks.value.forEach(task => {
if (!sseConnections.value.has(task.id)) { if (task.status === 'downloading' && !sseConnections.value.has(task.id)) {
startTaskStreaming(task.id); startTaskStreaming(task.id);
} }
}); });
@@ -337,6 +346,12 @@ const startTaskStreaming = (taskId: string) => {
console.log('开始SSE监听任务进度:', taskId); console.log('开始SSE监听任务进度:', taskId);
// 添加超时处理
const timeoutId = setTimeout(() => {
console.warn('SSE连接超时,关闭连接:', taskId);
stopTaskStreaming(taskId);
}, 30000); // 30秒超时
const closeConnection = downloadService.streamTaskProgress( const closeConnection = downloadService.streamTaskProgress(
taskId, taskId,
(task) => { (task) => {
@@ -348,25 +363,31 @@ const startTaskStreaming = (taskId: string) => {
total: task.total_files total: task.total_files
}); });
// 清除超时
clearTimeout(timeoutId);
// 更新任务状态 // 更新任务状态
const index = tasks.value.findIndex(t => t.id === taskId); const index = tasks.value.findIndex(t => t.id === taskId);
if (index !== -1) { if (index !== -1) {
tasks.value[index] = task; tasks.value[index] = task;
} }
// 如果任务完成,清理连接 // 如果任务完成或暂停,清理连接
if (['completed', 'failed', 'cancelled', 'partial'].includes(task.status)) { if (['completed', 'failed', 'cancelled', 'partial', 'paused'].includes(task.status)) {
console.log('任务完成,关闭SSE连接:', taskId); console.log('任务状态变更,关闭SSE连接:', taskId);
stopTaskStreaming(taskId); stopTaskStreaming(taskId);
// 延迟刷新历史记录 // 延迟刷新历史记录
setTimeout(() => { if (['completed', 'failed', 'cancelled', 'partial'].includes(task.status)) {
fetchHistory(); setTimeout(() => {
}, 1000); fetchHistory();
}, 1000);
}
} }
}, },
() => { () => {
console.log('SSE连接完成:', taskId); console.log('SSE连接完成:', taskId);
clearTimeout(timeoutId);
stopTaskStreaming(taskId); stopTaskStreaming(taskId);
} }
); );
@@ -382,11 +403,35 @@ const stopTaskStreaming = (taskId: string) => {
} }
}; };
// 管理SSE连接
const manageSSEConnections = () => {
// 清理不需要的连接
const currentTaskIds = new Set(activeTasks.value.map(task => task.id));
// 关闭已不存在的任务的连接
sseConnections.value.forEach((closeConnection, taskId) => {
if (!currentTaskIds.has(taskId)) {
console.log('清理已不存在的任务连接:', taskId);
closeConnection();
sseConnections.value.delete(taskId);
}
});
// 为正在下载的任务建立连接
activeTasks.value.forEach(task => {
if (task.status === 'downloading' && !sseConnections.value.has(task.id)) {
startTaskStreaming(task.id);
}
});
};
// 取消任务 // 取消任务
const cancelTask = async (taskId: string) => { const cancelTask = async (taskId: string) => {
try { try {
const response = await downloadService.cancelTask(taskId); const response = await downloadService.cancelTask(taskId);
if (response.success) { if (response.success) {
// 立即停止SSE连接
stopTaskStreaming(taskId);
await fetchTasks(); await fetchTasks();
} else { } else {
throw new Error(response.error || '取消任务失败'); throw new Error(response.error || '取消任务失败');
@@ -397,6 +442,23 @@ const cancelTask = async (taskId: string) => {
} }
}; };
// 恢复任务
const resumeTask = async (taskId: string) => {
try {
const response = await downloadService.resumeTask(taskId);
if (response.success) {
await fetchTasks();
// 重新管理SSE连接
manageSSEConnections();
} else {
throw new Error(response.error || '恢复任务失败');
}
} catch (err) {
error.value = err instanceof Error ? err.message : '恢复任务失败';
console.error('恢复任务失败:', err);
}
};
// 清理历史记录 // 清理历史记录
const cleanupHistory = async () => { const cleanupHistory = async () => {
if (confirm('确定要清理下载历史吗?这将保留最新的500条记录。')) { if (confirm('确定要清理下载历史吗?这将保留最新的500条记录。')) {
@@ -455,7 +517,16 @@ const cleanupSSEConnections = () => {
onMounted(async () => { onMounted(async () => {
loading.value = true; loading.value = true;
try { try {
await refreshData(); // 先获取数据,不阻塞页面渲染
await Promise.all([
fetchTasks(),
fetchHistory()
]);
// 数据加载完成后,异步管理SSE连接
setTimeout(() => {
manageSSEConnections();
}, 100);
} catch (err) { } catch (err) {
error.value = err instanceof Error ? err.message : '加载数据失败'; error.value = err instanceof Error ? err.message : '加载数据失败';
} finally { } finally {
@@ -662,6 +733,17 @@ onUnmounted(() => {
margin: 0 0 0.25rem 0; margin: 0 0 0.25rem 0;
} }
.task-title .task-link {
color: #3b82f6;
text-decoration: none;
transition: color 0.2s ease;
}
.task-title .task-link:hover {
color: #2563eb;
text-decoration: underline;
}
.task-status, .task-status,
.history-status { .history-status {
display: inline-block; display: inline-block;
@@ -707,6 +789,13 @@ onUnmounted(() => {
color: #d97706; color: #d97706;
} }
.task-actions {
display: flex;
gap: 0.5rem;
align-items: center;
flex-wrap: wrap;
}
.task-progress { .task-progress {
margin-bottom: 1rem; margin-bottom: 1rem;
} }