feat(video): 集成可灵AI多图参考生视频生成服务

- 替换视频生成服务为可灵AI多图参考生视频API,支持1-4张多视角图片输入
- 调整图片拼接逻辑,生成横向长图传入即梦API备用
- 实现基于JWT认证的可灵API请求和轮询机制,支持高品质1:1正方形视频生成
- 在设计详情页新增视频展示区域及生成、重新生成和下载视频操作
- 更新后台系统配置,支持配置可灵AI Access Key和Secret Key
- 删除即梦视频相关配置及逻辑,所有视频生成功能切换到可灵AI实现
- 优化视频生成提示词,提升视频质感和展示效果
- 增加视频文件本地存储和路径管理,保证视频可访问和下载
- 前端增加视频生成状态管理和用户界面交互提示
- 后端添加PyJWT依赖,支持JWT认证流程
This commit is contained in:
2026-03-28 00:20:48 +08:00
parent 8f5a86418e
commit 1d94ec114a
9 changed files with 596 additions and 95 deletions

View File

@@ -98,6 +98,38 @@
</button>
</div>
</div>
<!-- 展示视频区域 -->
<div class="video-section" v-if="design.video_url || generatingVideo">
<h4 class="section-title">展示视频</h4>
<div v-if="generatingVideo" class="generating-state">
<el-icon class="loading-icon"><Loading /></el-icon>
<span>正在生成展示视频预计需要 2-5 分钟...</span>
</div>
<div v-else-if="design.video_url" class="video-wrapper">
<video
:key="design.video_url"
controls
preload="auto"
crossorigin="anonymous"
class="preview-video"
>
<source :src="design.video_url" type="video/mp4" />
您的浏览器不支持视频播放
</video>
<div class="video-actions">
<button class="model3d-action-btn" @click="handleDownloadVideo">
<el-icon><Download /></el-icon>
<span>下载视频</span>
</button>
<button class="model3d-action-btn" @click="handleRegenVideo">
<el-icon><RefreshRight /></el-icon>
<span>重新生成</span>
</button>
</div>
</div>
</div>
<div class="design-info">
<h4 class="info-title">设计详情</h4>
<div class="info-grid">
@@ -140,6 +172,15 @@
<el-icon><Platform /></el-icon>
<span>生成3D模型</span>
</button>
<!-- 生成展示视频按钮 -->
<button
v-if="!design.video_url && !generatingVideo"
class="action-btn video-btn"
@click="handleGenerateVideo"
>
<el-icon><VideoCameraFilled /></el-icon>
<span>生成展示视频</span>
</button>
<button class="action-btn secondary-btn" @click="goToUserCenter">
<el-icon><User /></el-icon>
<span>查看我的设计</span>
@@ -154,7 +195,7 @@ import { useRouter } from 'vue-router'
import { Loading, PictureFilled, ZoomIn, ZoomOut, RefreshRight, Download, User, Platform, VideoCameraFilled } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import type { Design } from '@/stores/design'
import { getDesignDownloadUrl, generate3DModelApi } from '@/api/design'
import { getDesignDownloadUrl, generate3DModelApi, generateVideoApi } from '@/api/design'
import request from '@/api/request'
const props = defineProps<{
@@ -179,6 +220,9 @@ const scale = ref(1)
// 3D 模型生成状态
const generating3D = ref(false)
// 视频生成状态
const generatingVideo = ref(false)
// 是否有多视角图片
const hasMultipleViews = computed(() => {
return props.design.images && props.design.images.length > 1
@@ -252,6 +296,56 @@ const handleRegen3D = async () => {
}
}
// 生成展示视频
const handleGenerateVideo = async () => {
generatingVideo.value = true
try {
const updated = await generateVideoApi(props.design.id)
if (updated.video_url) {
props.design.video_url = updated.video_url
}
ElMessage.success('展示视频生成成功!')
} catch (e: any) {
ElMessage.error(e?.response?.data?.detail || e?.detail || '视频生成失败,请重试')
} finally {
generatingVideo.value = false
}
}
// 重新生成展示视频
const handleRegenVideo = async () => {
generatingVideo.value = true
try {
const updated = await generateVideoApi(props.design.id, true)
if (updated.video_url) {
props.design.video_url = updated.video_url
}
ElMessage.success('展示视频重新生成成功!')
} catch (e: any) {
ElMessage.error(e?.response?.data?.detail || '视频重新生成失败')
} finally {
generatingVideo.value = false
}
}
// 下载展示视频
const handleDownloadVideo = async () => {
const videoUrl = props.design.video_url
if (!videoUrl) return
try {
const res = await fetch(videoUrl)
if (!res.ok) throw new Error('下载失败')
const blob = await res.blob()
const category = props.design.category?.name || '设计'
const subType = props.design.sub_type?.name || ''
const filename = `${category}${subType ? '-' + subType : ''}-展示视频-${props.design.id}.mp4`
_downloadBlob(blob, filename)
ElMessage.success('视频下载成功')
} catch {
ElMessage.error('视频下载失败')
}
}
// 获取图片URL
const toImageUrl = (url: string | null): string => {
if (!url) return ''
@@ -867,4 +961,43 @@ $text-light: #999999;
overflow: hidden;
background: $bg-color;
}
// 视频区域样式
.video-section {
background: #fff;
border-radius: 12px;
padding: 20px 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
}
.video-wrapper {
border-radius: 8px;
overflow: hidden;
background: #000;
}
.preview-video {
width: 100%;
max-height: 500px;
display: block;
}
.video-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 12px 0 0;
background: #fff;
}
.video-btn {
background: linear-gradient(135deg, #e040fb 0%, #7c4dff 100%) !important;
color: #fff !important;
border: none !important;
&:hover {
opacity: 0.9;
transform: translateY(-1px);
}
}
</style>