feat: 使用banana模型生成分镜图片,修复数据库列类型问题
- 修改RealAIService.submitTextToImageTask使用nano-banana/nano-banana-hd模型 - 支持根据hdMode参数选择模型(标准/高清) - 修复数据库列类型:将result_url等字段改为TEXT类型以支持Base64图片 - 添加数据库修复SQL脚本(fix_database_columns.sql, update_database_schema.sql) - 改进StoryboardVideoService的错误处理和空值检查 - 添加GlobalExceptionHandler全局异常处理 - 优化图片URL提取逻辑,支持url和b64_json两种格式 - 改进响应格式验证,确保data字段不为空
This commit is contained in:
@@ -130,7 +130,25 @@
|
||||
<el-col v-for="item in filteredItems" :key="item.id" :xs="24" :sm="12" :md="8" :lg="6">
|
||||
<el-card class="work-card" :class="{ selected: selectedIds.has(item.id) }" shadow="hover">
|
||||
<div class="thumb" @click="multiSelect ? toggleSelect(item.id) : openDetail(item)">
|
||||
<img :src="item.cover" :alt="item.title" />
|
||||
<!-- 如果是视频类型且有视频URL,使用video元素显示首帧 -->
|
||||
<video
|
||||
v-if="item.type === 'video' && item.resultUrl"
|
||||
:src="item.resultUrl"
|
||||
class="work-thumbnail-video"
|
||||
muted
|
||||
preload="metadata"
|
||||
@loadedmetadata="onVideoLoaded"
|
||||
></video>
|
||||
<!-- 如果有封面图(thumbnailUrl),使用图片 -->
|
||||
<img
|
||||
v-else-if="item.cover && item.cover !== item.resultUrl"
|
||||
:src="item.cover"
|
||||
:alt="item.title"
|
||||
/>
|
||||
<!-- 否则使用默认占位符 -->
|
||||
<div v-else class="work-placeholder">
|
||||
<el-icon><VideoPlay /></el-icon>
|
||||
</div>
|
||||
|
||||
<div class="checker" v-if="multiSelect">
|
||||
<el-checkbox :model-value="selectedIds.has(item.id)" @change="() => toggleSelect(item.id)" />
|
||||
@@ -191,7 +209,7 @@
|
||||
<video
|
||||
v-if="selectedItem.type === 'video'"
|
||||
class="detail-video"
|
||||
:src="selectedItem.cover"
|
||||
:src="selectedItem.resultUrl || selectedItem.cover"
|
||||
:poster="selectedItem.cover"
|
||||
controls
|
||||
>
|
||||
@@ -314,7 +332,7 @@
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Star, User, Compass, Document, VideoPlay, Picture, Film, Bell, Setting, Search } from '@element-plus/icons-vue'
|
||||
import { Star, User, Compass, Document, VideoPlay, Picture, Film, Bell, Setting, Search, MoreFilled } from '@element-plus/icons-vue'
|
||||
import { getMyWorks } from '@/api/userWorks'
|
||||
|
||||
const router = useRouter()
|
||||
@@ -341,6 +359,28 @@ const loading = ref(false)
|
||||
const hasMore = ref(true)
|
||||
const items = ref([])
|
||||
|
||||
// 将后端返回的UserWork数据转换为前端需要的格式
|
||||
const transformWorkData = (work) => {
|
||||
return {
|
||||
id: work.id?.toString() || work.taskId || '',
|
||||
title: work.title || work.prompt || '未命名作品',
|
||||
cover: work.thumbnailUrl || work.resultUrl || '/images/backgrounds/welcome.jpg',
|
||||
resultUrl: work.resultUrl || '',
|
||||
type: work.workType === 'TEXT_TO_VIDEO' || work.workType === 'IMAGE_TO_VIDEO' ? 'video' : 'image',
|
||||
category: work.workType === 'TEXT_TO_VIDEO' ? '文生视频' : work.workType === 'IMAGE_TO_VIDEO' ? '图生视频' : '未知',
|
||||
sizeText: work.fileSize || '未知大小',
|
||||
createTime: work.createdAt ? new Date(work.createdAt).toLocaleString('zh-CN') : '',
|
||||
date: work.createdAt ? new Date(work.createdAt).toLocaleDateString('zh-CN') : '',
|
||||
description: work.description || work.prompt || '',
|
||||
prompt: work.prompt || '',
|
||||
duration: work.duration || '',
|
||||
aspectRatio: work.aspectRatio || '',
|
||||
quality: work.quality || '',
|
||||
status: work.status || 'COMPLETED',
|
||||
overlayText: work.prompt || ''
|
||||
}
|
||||
}
|
||||
|
||||
const loadList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -352,8 +392,11 @@ const loadList = async () => {
|
||||
if (response.data.success) {
|
||||
const data = response.data.data || []
|
||||
|
||||
// 转换数据格式
|
||||
const transformedData = data.map(transformWorkData)
|
||||
|
||||
if (page.value === 1) items.value = []
|
||||
items.value = items.value.concat(data)
|
||||
items.value = items.value.concat(transformedData)
|
||||
hasMore.value = data.length === pageSize.value
|
||||
} else {
|
||||
throw new Error(response.data.message || '获取作品列表失败')
|
||||
@@ -545,6 +588,17 @@ const resetFilters = () => {
|
||||
ElMessage.success('筛选器已重置')
|
||||
}
|
||||
|
||||
// 视频加载元数据后,跳转到第一帧(但不播放)
|
||||
const onVideoLoaded = (event) => {
|
||||
const video = event.target
|
||||
if (video && video.duration) {
|
||||
// 跳转到第一帧(0秒)
|
||||
video.currentTime = 0.1
|
||||
// 确保视频不播放
|
||||
video.pause()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadList()
|
||||
})
|
||||
@@ -799,6 +853,28 @@ onMounted(() => {
|
||||
.work-card { margin-bottom: 14px; }
|
||||
.thumb { position: relative; width: 100%; padding-top: 56.25%; overflow: hidden; border-radius: 6px; cursor: pointer; }
|
||||
.thumb img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; }
|
||||
.work-thumbnail-video {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
pointer-events: none;
|
||||
}
|
||||
.work-placeholder {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
font-size: 24px;
|
||||
}
|
||||
.checker { position: absolute; left: 6px; top: 6px; }
|
||||
.actions { position: absolute; right: 6px; top: 6px; display: flex; gap: 4px; opacity: 0; transition: opacity .2s ease; }
|
||||
.thumb:hover .actions { opacity: 1; }
|
||||
|
||||
Reference in New Issue
Block a user