- -
-
{{ t('home.textToVideo') }}
-
{{ t('home.imageToVideo') }}
-
{{ t('home.storyboardVideo') }}
-
+ +
+ +
+
{{ t('home.textToVideo') }}
+
{{ t('home.imageToVideo') }}
+
{{ t('home.storyboardVideo') }}
+
- -
+ +
@@ -80,7 +82,6 @@
@@ -107,6 +108,7 @@
+
@@ -159,20 +161,6 @@
{{ t('video.imageToVideo.noVideoUrl') }}
- - -
-
-
- - -
-
- - -
-
-
@@ -317,7 +305,7 @@ import { ref, reactive, onUnmounted, computed, onMounted, watch } from 'vue' import { useRouter } from 'vue-router' import { useI18n } from 'vue-i18n' -import { ElMessage, ElLoading } from 'element-plus' +import { ElMessage, ElMessageBox, ElLoading } from 'element-plus' import { useUserStore } from '@/stores/user' import { User, VideoCamera, Star, Setting, SwitchButton } from '@element-plus/icons-vue' import { imageToVideoApi } from '@/api/imageToVideo' @@ -351,7 +339,6 @@ const taskProgress = ref(0) const taskStatus = ref('') const stopPolling = ref(null) const showInProgress = ref(false) -const watermarkOption = ref('without') const optimizingPrompt = ref(false) // 优化提示词状态 const historyTasks = ref([]) // 历史记录 const playingVideos = ref({}) // 正在播放的视频 @@ -399,6 +386,11 @@ const goToStoryboard = () => { // 用户菜单相关方法 const toggleUserMenu = () => { + // 未登录时跳转到登录页面 + if (!isAuthenticated.value) { + router.push('/login') + return + } showUserMenu.value = !showUserMenu.value } @@ -655,11 +647,10 @@ const startPollingTask = () => { // 可以在这里跳转到结果页面或显示结果 console.log('任务完成:', taskData) }, - // 错误回调 + // 错误回调 - 静默处理,只在页面上显示失败状态,不弹窗 (error) => { inProgress.value = false taskStatus.value = 'FAILED' - ElMessage.error(t('video.imageToVideo.videoGenerateFailed') + error.message) console.error('任务失败:', error) } ) @@ -821,25 +812,47 @@ const retryTask = () => { // 投稿功能(已移除按钮,保留函数已删除) // 删除作品 -const deleteWork = () => { +const deleteWork = async () => { if (!currentTask.value) { ElMessage.error(t('video.imageToVideo.noWorkToDelete')) return } - // 确认删除 - ElMessage.confirm(t('video.imageToVideo.confirmDeleteWork'), t('video.imageToVideo.confirmDelete'), { - confirmButtonText: t('common.confirm'), - cancelButtonText: t('common.cancel'), - type: 'warning' - }).then(() => { - // 这里可以调用删除API - currentTask.value = null - taskStatus.value = '' - ElMessage.success(t('video.imageToVideo.workDeleted')) - }).catch(() => { - ElMessage.info(t('video.imageToVideo.deleteCancelled')) - }) + try { + // 确认删除 + await ElMessageBox.confirm( + t('video.imageToVideo.confirmDeleteWork'), + t('video.imageToVideo.confirmDelete'), + { + confirmButtonText: t('common.delete'), + cancelButtonText: t('common.cancel'), + type: 'warning' + } + ) + + // 调用删除 API + const taskId = currentTask.value.taskId + const response = await imageToVideoApi.deleteTask(taskId) + + if (response.data && response.data.success) { + // 重置状态 + currentTask.value = null + taskStatus.value = '' + inProgress.value = false + taskProgress.value = 0 + ElMessage.success(t('video.imageToVideo.workDeleted')) + + // 刷新历史记录 + loadHistory() + } else { + throw new Error(response.data?.message || t('video.imageToVideo.deleteFailed')) + } + } catch (error) { + if (error !== 'cancel' && error !== 'close') { + console.error('删除作品失败:', error) + ElMessage.error(error.message || t('video.imageToVideo.deleteFailed')) + } + } } // 处理历史记录URL @@ -998,19 +1011,19 @@ watch(() => userStore.isAuthenticated, (isAuth) => { // 恢复正在进行中的任务 const restoreProcessingTask = async () => { if (!userStore.isAuthenticated) { - return + return false } // 如果正在创建任务,跳过恢复逻辑 if (isCreatingTask.value) { console.log('[Task Restore] 跳过恢复:正在创建新任务') - return + return false } // 如果已经恢复过任务且当前有任务在进行中,跳过 if (hasRestoredTask.value && currentTask.value) { console.log('[Task Restore] 跳过恢复:已有任务在进行中') - return + return true } try { @@ -1044,30 +1057,77 @@ const restoreProcessingTask = async () => { if (work.duration) { duration.value = work.duration || '10' } - - inProgress.value = true - taskStatus.value = work.status || 'PROCESSING' - taskProgress.value = 50 // 初始进度设为50% - - console.log('恢复正在进行中的任务:', work.taskId, '状态:', work.status) - ElMessage.info(t('video.imageToVideo.resumingTask')) - // 标记已恢复任务 - hasRestoredTask.value = true + // 恢复首帧图片(从 thumbnailUrl 或结果中获取) + if (work.thumbnailUrl) { + firstFrameImage.value = processHistoryUrl(work.thumbnailUrl) + console.log('[Task Restore] 已恢复首帧图片:', work.thumbnailUrl) + } - // 开始轮询任务状态 - startPollingTask() + // 只有真正在进行中的任务才恢复和显示消息 + const workStatus = work.status || 'PROCESSING' + if (workStatus === 'PROCESSING' || workStatus === 'PENDING') { + inProgress.value = true + taskStatus.value = workStatus + taskProgress.value = 50 // 初始进度设为50% + + console.log('[Task Restored]', work.taskId, 'Status:', workStatus) + // 静默恢复,不显示弹窗 + + // 标记已恢复任务 + hasRestoredTask.value = true + + // 开始轮询任务状态 + startPollingTask() + return true + } else { + // 如果任务已失败或取消,不显示恢复消息,让 checkLastTaskStatus 处理 + console.log('[Task Skip]', work.taskId, 'Status:', workStatus, '- 不是进行中状态') + return false + } } } } catch (error) { - console.error('恢复任务失败:', error) + console.error('[Task Restore Error]', error) + } + return false +} + +// 检查最近一条任务的状态(如果失败则显示失败状态,但不恢复输入参数) +const checkLastTaskStatus = async () => { + if (!userStore.isAuthenticated) return + + try { + const response = await imageToVideoApi.getTasks(0, 1) + if (response.data && response.data.success && response.data.data && response.data.data.length > 0) { + const lastTask = response.data.data[0] + + // 只关注 FAILED 状态,显示失败UI但不恢复输入参数 + if (lastTask.status === 'FAILED') { + console.log('[Last Task Failed]', lastTask) + + currentTask.value = lastTask + taskStatus.value = 'FAILED' + // 不恢复输入参数,让用户可以自由创建新任务 + } + } + } catch (error) { + console.error('Check last task status error', error) } } -// 组件挂载时加载历史记录 -onMounted(() => { +onMounted(async () => { loadHistory() - restoreProcessingTask() + // 延迟恢复任务,避免与创建任务冲突 + setTimeout(async () => { + if (!isCreatingTask.value) { + const restored = await restoreProcessingTask() + // 如果没有恢复进行中的任务,则检查最近一条是否失败 + if (!restored) { + checkLastTaskStatus() + } + } + }, 500) }) // 组件卸载时清理资源 @@ -1244,7 +1304,36 @@ onUnmounted(() => { display: flex; flex-direction: column; gap: 32px; + overflow: hidden; +} + +/* 左侧面板内容区域(可滚动) */ +.left-panel-content { + flex: 1; overflow-y: auto; + display: flex; + flex-direction: column; + gap: 32px; + padding-right: 8px; +} + +/* 优化滚动条样式 */ +.left-panel-content::-webkit-scrollbar { + width: 8px; +} + +.left-panel-content::-webkit-scrollbar-track { + background: #0a0a0a; + border-radius: 4px; +} + +.left-panel-content::-webkit-scrollbar-thumb { + background: #3a3a3a; + border-radius: 4px; +} + +.left-panel-content::-webkit-scrollbar-thumb:hover { + background: #4a4a4a; } /* 创作模式标签 */ @@ -1289,11 +1378,14 @@ onUnmounted(() => { .image-upload-area { display: flex; align-items: center; + justify-content: center; gap: 16px; } .upload-box { - flex: 1; + flex: none; + width: 240px; + max-width: 100%; aspect-ratio: 1; background: #0a0a0a; border: 2px dashed #2a2a2a; @@ -1641,13 +1733,13 @@ onUnmounted(() => { .preview-placeholder { width: 100%; - min-height: 400px; + min-height: 200px; background: #1a1a1a; border-radius: 8px; display: flex; align-items: center; justify-content: center; - padding: 60px 40px; + padding: 40px 20px; } .placeholder-text { @@ -1799,7 +1891,8 @@ onUnmounted(() => { background: #1a1a1a; border: 2px solid #2a2a2a; border-radius: 12px; - min-height: 300px; + min-height: 200px; + max-height: 50vh; display: flex; align-items: center; justify-content: center; @@ -1922,43 +2015,6 @@ onUnmounted(() => { font-weight: 500; } -/* 水印选择覆盖层 */ -.watermark-overlay { - position: absolute; - bottom: 15px; - right: 15px; - background: rgba(0, 0, 0, 0.8); - border-radius: 8px; - padding: 12px; - backdrop-filter: blur(10px); -} - -.watermark-options { - display: flex; - flex-direction: column; - gap: 8px; -} - -.watermark-option { - display: flex; - align-items: center; - gap: 8px; -} - -.watermark-option input[type="radio"] { - width: 16px; - height: 16px; - cursor: pointer; - accent-color: #3b82f6; -} - -.watermark-option label { - font-size: 13px; - color: #e5e7eb; - cursor: pointer; - font-weight: 500; -} - /* 操作按钮区域 */ .result-actions { display: flex; diff --git a/demo/frontend/src/views/Login.vue b/demo/frontend/src/views/Login.vue index 897781d..76b3b07 100644 --- a/demo/frontend/src/views/Login.vue +++ b/demo/frontend/src/views/Login.vue @@ -18,6 +18,8 @@ + + @@ -26,6 +28,8 @@ + + @@ -56,6 +60,7 @@ placeholder="请输入验证码" class="code-input" @keyup.enter="handleLogin" + @input="filterCodeSpaces" >