![]()
@@ -382,6 +389,7 @@ const firstFrameImage = ref('')
const lastFrameImage = ref('')
const firstFrameFile = ref(null)
const lastFrameFile = ref(null)
+const isDraggingFirstFrame = ref(false)
// 任务状态
const currentTask = ref(null)
@@ -544,6 +552,34 @@ const uploadFirstFrame = () => {
input.click()
}
+// 处理拖拽上传首帧图片
+const handleDropFirstFrame = (e) => {
+ isDraggingFirstFrame.value = false
+ const files = e.dataTransfer.files
+ if (files.length === 0) return
+
+ const file = files[0]
+ if (!file.type.startsWith('image/')) {
+ ElMessage.error(t('video.imageToVideo.invalidImageFile'))
+ return
+ }
+
+ const maxFileSize = 100 * 1024 * 1024 // 100MB
+ if (file.size > maxFileSize) {
+ ElMessage.error(t('video.imageToVideo.fileSizeLimit'))
+ return
+ }
+
+ firstFrameFile.value = file
+ console.log('[Drop] 文件已设置:', file.name, file.size, 'bytes')
+ const reader = new FileReader()
+ reader.onload = (e) => {
+ firstFrameImage.value = e.target.result
+ console.log('[Drop] 图片预览已设置,base64长度:', e.target.result.length)
+ }
+ reader.readAsDataURL(file)
+}
+
const uploadLastFrame = () => {
const input = document.createElement('input')
input.type = 'file'
@@ -1021,25 +1057,13 @@ const downloadVideo = async () => {
try {
ElMessage.info('正在准备下载...')
-
- // 获取视频文件
- const response = await fetch(currentTask.value.resultUrl)
- const blob = await response.blob()
-
- // 创建下载链接
- const url = window.URL.createObjectURL(blob)
- const link = document.createElement('a')
- link.href = url
- link.download = `video_${currentTask.value.taskId || Date.now()}.mp4`
- document.body.appendChild(link)
- link.click()
- document.body.removeChild(link)
- window.URL.revokeObjectURL(url)
-
- ElMessage.success(t('video.imageToVideo.startDownload'))
+ const { downloadVideo: downloadVideoUtil } = await import('@/utils/download')
+ const success = await downloadVideoUtil(currentTask.value.resultUrl, currentTask.value.taskId || Date.now())
+ if (success) {
+ ElMessage.success(t('video.imageToVideo.startDownload'))
+ }
} catch (error) {
console.error('下载失败:', error)
- // 备用方案:直接打开链接
window.open(currentTask.value.resultUrl, '_blank')
}
}
@@ -1054,20 +1078,11 @@ const downloadHistoryVideo = async (task) => {
try {
ElMessage.info('正在准备下载...')
const videoUrl = processHistoryUrl(task.resultUrl)
-
- const response = await fetch(videoUrl)
- const blob = await response.blob()
-
- const url = window.URL.createObjectURL(blob)
- const link = document.createElement('a')
- link.href = url
- link.download = `video_${task.taskId || Date.now()}.mp4`
- document.body.appendChild(link)
- link.click()
- document.body.removeChild(link)
- window.URL.revokeObjectURL(url)
-
- ElMessage.success(t('video.imageToVideo.startDownload'))
+ const { downloadVideo: downloadVideoUtil } = await import('@/utils/download')
+ const success = await downloadVideoUtil(videoUrl, task.taskId || Date.now())
+ if (success) {
+ ElMessage.success(t('video.imageToVideo.startDownload'))
+ }
} catch (error) {
console.error('下载失败:', error)
window.open(processHistoryUrl(task.resultUrl), '_blank')
@@ -1186,14 +1201,14 @@ const loadHistory = async () => {
}
try {
- // 请求更多条数以确保能筛选出足够的任务
- const response = await imageToVideoApi.getTasks(0, 50)
+ // 请求全部已完成的任务,不限制数量
+ const response = await imageToVideoApi.getTasks(0, 1000)
console.log('历史记录API响应:', response.data)
if (response.data && response.data.success) {
// 只显示已完成的任务,不显示失败的任务
const tasks = (response.data.data || []).filter(task =>
task.status === 'COMPLETED'
- ).slice(0, 10)
+ )
// 处理URL,确保相对路径正确
historyTasks.value = tasks.map(task => ({
@@ -1502,7 +1517,7 @@ onMounted(async () => {
// 注意:firstFrameFile 为 null,用户需要重新上传或点击生成时会提示
}
- ElMessage.success(t('video.imageToVideo.historyParamsFilled') || '已填充历史参数')
+ // 静默填充,不显示弹窗提示(减少干扰)
// 清除URL中的query参数,避免刷新页面重复填充
router.replace({ path: route.path })
}
@@ -1788,6 +1803,12 @@ onUnmounted(() => {
background: #1a1a1a;
}
+.upload-box.drag-over {
+ border-color: #3b82f6;
+ background: #1a2a3a;
+ border-style: solid;
+}
+
.upload-box.optional {
opacity: 0.7;
}
@@ -2583,6 +2604,30 @@ onUnmounted(() => {
color: #9ca3af;
}
+.failed-reason {
+ margin-top: 16px;
+ padding: 12px 16px;
+ background: rgba(239, 68, 68, 0.1);
+ border-radius: 8px;
+ border: 1px solid rgba(239, 68, 68, 0.2);
+ text-align: left;
+ max-width: 400px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.failed-reason .reason-label {
+ color: #ef4444;
+ font-weight: 500;
+ font-size: 13px;
+}
+
+.failed-reason .reason-text {
+ color: #f87171;
+ font-size: 13px;
+ word-break: break-word;
+}
+
/* 其他状态 */
.status-placeholder {
width: 100%;
diff --git a/demo/frontend/src/views/Login.vue b/demo/frontend/src/views/Login.vue
index 1705ef7..edafbb8 100644
--- a/demo/frontend/src/views/Login.vue
+++ b/demo/frontend/src/views/Login.vue
@@ -10,7 +10,7 @@
欢迎来到
- VidFlow
+ Vionow
diff --git a/demo/frontend/src/views/MyWorks.vue b/demo/frontend/src/views/MyWorks.vue
index 3226df9..3c233c5 100644
--- a/demo/frontend/src/views/MyWorks.vue
+++ b/demo/frontend/src/views/MyWorks.vue
@@ -220,10 +220,10 @@
{{ item.title }}
{{ item.date || t('profile.unknown') }} · {{ item.id }}
-
+
{{ formatQuality(item.quality) }}
- · {{ item.sizeText }}
+ · {{ item.sizeText }}