下载页面卡主问题修复
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
|
||||||
|
// 通知进度更新
|
||||||
|
const updatedTask = this.taskManager.getTask(taskId);
|
||||||
|
this.progressManager.notifyProgressUpdate(taskId, updatedTask);
|
||||||
|
|
||||||
// 重新开始下载
|
// 重新开始下载执行
|
||||||
return this.downloadArtwork(task.artwork_id, { skipExisting: false });
|
this.downloadExecutor.resumeTask(taskId);
|
||||||
|
|
||||||
|
return { success: true, data: updatedTask };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 代理方法 - 历史记录管理
|
// 代理方法 - 历史记录管理
|
||||||
|
|||||||
+100
-11
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user