Files
AIGC/demo/frontend/src/views/TextToVideo.vue

793 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="text-to-video-page">
<!-- 左侧导航栏 -->
<aside class="sidebar">
<div class="logo">logo</div>
<nav class="nav-menu">
<div class="nav-item" @click="goToProfile">
<el-icon><User /></el-icon>
<span>个人主页</span>
</div>
<div class="nav-item" @click="goToSubscription">
<el-icon><Compass /></el-icon>
<span>会员订阅</span>
</div>
<div class="nav-item" @click="goToMyWorks">
<el-icon><Document /></el-icon>
<span>我的作品</span>
</div>
<div class="nav-divider"></div>
<div class="nav-item active">
<el-icon><VideoPlay /></el-icon>
<span>文生视频</span>
</div>
<div class="nav-item" @click="goToImageToVideo">
<el-icon><Picture /></el-icon>
<span>图生视频</span>
</div>
<div class="nav-item storyboard-item" @click="goToStoryboardVideo">
<el-icon><Film /></el-icon>
<span>分镜视频</span>
<el-tag size="small" type="primary" class="sora-tag">Sora2.0</el-tag>
</div>
</nav>
</aside>
<!-- 主内容区域 -->
<main class="main-content">
<!-- 顶部用户信息卡片 -->
<div class="user-info-card">
<div class="user-avatar">
<div class="avatar-placeholder">||</div>
</div>
<div class="user-details">
<div class="username">mingzi_FBx7foZYDS7inLQb</div>
<div class="profile-prompt">还没有设置个人简介,点击填写</div>
<div class="user-id">ID 2994509784706419</div>
</div>
<div class="edit-profile-btn">
<el-button type="primary">编辑资料</el-button>
</div>
</div>
<!-- 已发布作品区域 -->
<div class="published-works">
<div class="works-tabs">
<div class="tab active">已发布</div>
</div>
<div class="works-grid">
<div class="work-item" v-for="(work, index) in publishedWorks" :key="work.id" @click="openDetail(work)">
<div class="work-thumbnail">
<img :src="work.cover" :alt="work.title" />
<div class="work-overlay">
<div class="overlay-text">{{ work.text }}</div>
</div>
<!-- 鼠标悬停时显示的做同款按钮 -->
<div class="hover-create-btn" @click.stop="goToCreate(work)">
<el-button type="primary" size="small" round>
<el-icon><VideoPlay /></el-icon>
做同款
</el-button>
</div>
</div>
<div class="work-info">
<div class="work-title">{{ work.title }}</div>
<div class="work-meta">{{ work.id }} · {{ work.size }}</div>
</div>
<div class="work-actions" v-if="index === 0">
<el-button type="primary" class="create-similar-btn" @click.stop="goToCreate(work)">做同款</el-button>
</div>
<div class="work-director" v-else>
<span>DIRECTED BY VANNOCENT</span>
</div>
</div>
</div>
</div>
</main>
<!-- 作品详情模态框 -->
<el-dialog
v-model="detailDialogVisible"
:title="selectedItem?.title"
width="60%"
class="detail-dialog"
:modal="true"
:close-on-click-modal="true"
:close-on-press-escape="true"
@close="handleClose"
>
<div class="detail-content">
<div class="detail-left">
<div class="video-player">
<img :src="selectedItem?.cover" :alt="selectedItem?.title" class="video-thumbnail" />
<div class="play-overlay">
<div class="play-button"></div>
</div>
</div>
</div>
<div class="detail-right">
<div class="metadata-section">
<div class="metadata-item">
<span class="label">作品 ID</span>
<span class="value">{{ selectedItem?.id }}</span>
</div>
<div class="metadata-item">
<span class="label">文件大小</span>
<span class="value">{{ selectedItem?.size }}</span>
</div>
<div class="metadata-item">
<span class="label">创建时间</span>
<span class="value">{{ selectedItem?.createTime }}</span>
</div>
<div class="metadata-item">
<span class="label">分类</span>
<span class="value">{{ selectedItem?.category }}</span>
</div>
</div>
<div class="description-section">
<h3 class="section-title">描述</h3>
<p class="description-text">{{ getDescription(selectedItem) }}</p>
</div>
<!-- 操作按钮 -->
<div class="action-section">
<button class="create-similar-btn" @click="createSimilar">
做同款
</button>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElIcon, ElButton, ElTag, ElMessage, ElDialog } from 'element-plus'
import { User, Document, VideoPlay, Picture, Film, Compass } from '@element-plus/icons-vue'
const router = useRouter()
// 模态框状态
const detailDialogVisible = ref(false)
const selectedItem = ref(null)
// 已发布作品数据
const publishedWorks = ref([
{
id: '2995000000001',
title: '文生视频作品 #1',
cover: '/images/backgrounds/welcome.jpg',
text: 'What Does it Mean To You',
size: '9 MB',
category: '文生视频',
createTime: '2025/01/15 14:30'
},
{
id: '2995000000002',
title: '文生视频作品 #2',
cover: '/images/backgrounds/welcome.jpg',
text: 'What Does it Mean To You',
size: '9 MB',
category: '文生视频',
createTime: '2025/01/14 16:45'
},
{
id: '2995000000003',
title: '文生视频作品 #3',
cover: '/images/backgrounds/welcome.jpg',
text: 'What Does it Mean To You',
size: '9 MB',
category: '文生视频',
createTime: '2025/01/13 09:20'
}
])
// 导航函数
const goToProfile = () => {
router.push('/profile')
}
const goToSubscription = () => {
router.push('/subscription')
}
const goToMyWorks = () => {
router.push('/works')
}
const goToImageToVideo = () => {
router.push('/image-to-video/create')
}
const goToStoryboardVideo = () => {
router.push('/storyboard-video/create')
}
const goToCreate = (work) => {
// 跳转到文生视频创作页面
router.push('/text-to-video/create')
}
// 模态框相关函数
const openDetail = (work) => {
selectedItem.value = work
detailDialogVisible.value = true
}
const handleClose = () => {
detailDialogVisible.value = false
selectedItem.value = null
}
const getDescription = (item) => {
if (!item) return ''
return `这是一个${item.category}作品,展现了"What Does it Mean To You"的主题。作品通过AI技术生成具有独特的视觉风格和创意表达。`
}
const createSimilar = () => {
// 关闭模态框并跳转到创作页面
handleClose()
router.push('/text-to-video/create')
}
onMounted(() => {
// 页面初始化
})
</script>
<style scoped>
.text-to-video-page {
display: flex;
height: 100vh;
background: #0a0a0a;
color: #fff;
margin: 0;
padding: 0;
}
/* 左侧导航栏 */
.sidebar {
width: 280px !important;
background: #1a1a1a !important;
padding: 24px 0 !important;
border-right: 1px solid #1a1a1a !important;
flex-shrink: 0 !important;
z-index: 100 !important;
display: block !important;
position: relative !important;
}
.logo {
padding: 0 24px 32px;
font-size: 20px;
font-weight: 500;
color: white;
}
.nav-menu {
padding: 0 24px;
}
.nav-item {
display: flex;
align-items: center;
padding: 14px 18px;
margin-bottom: 4px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.nav-item:hover {
background: #2a2a2a;
}
.nav-item.active {
background: #1e3a8a;
}
.nav-item .el-icon {
margin-right: 14px;
font-size: 20px;
}
.nav-item span {
font-size: 15px;
flex: 1;
}
.nav-divider {
height: 1px;
background: #333;
margin: 16px 0;
}
.sora-tag {
margin-left: 8px;
}
/* 分镜视频特殊样式 */
.storyboard-item {
position: relative;
}
.storyboard-item .sora-tag {
background: linear-gradient(135deg, #667eea, #764ba2) !important;
border: none !important;
color: #fff !important;
font-weight: 700 !important;
font-size: 11px !important;
padding: 2px 8px !important;
border-radius: 12px !important;
box-shadow: 0 2px 6px rgba(102, 126, 234, 0.3) !important;
animation: pulse-glow 2s ease-in-out infinite alternate;
}
@keyframes pulse-glow {
0% {
box-shadow: 0 2px 6px rgba(102, 126, 234, 0.3);
}
100% {
box-shadow: 0 2px 12px rgba(102, 126, 234, 0.6);
}
}
/* 主内容区域 */
.main-content {
flex: 1;
padding: 24px;
display: flex;
flex-direction: column;
gap: 24px;
}
/* 用户信息卡片 */
.user-info-card {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 12px;
padding: 24px;
display: flex;
align-items: center;
gap: 20px;
}
.user-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
background: #000;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid #333;
}
.avatar-placeholder {
color: #fff;
font-size: 24px;
font-weight: bold;
letter-spacing: 2px;
}
.user-details {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.username {
font-size: 18px;
font-weight: 600;
color: #fff;
}
.profile-prompt {
font-size: 14px;
color: #9ca3af;
}
.user-id {
font-size: 12px;
color: #6b7280;
}
.edit-profile-btn {
margin-left: auto;
}
/* 已发布作品区域 */
.published-works {
display: flex;
flex-direction: column;
gap: 20px;
}
.works-tabs {
display: flex;
gap: 24px;
}
.tab {
padding: 8px 0;
color: #9ca3af;
cursor: pointer;
position: relative;
}
.tab.active {
color: #fff;
}
.tab.active::after {
content: '';
position: absolute;
bottom: -8px;
left: 0;
right: 0;
height: 2px;
background: #3b82f6;
}
.works-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.work-item {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 12px;
overflow: hidden;
transition: all 0.2s;
cursor: pointer;
}
.work-item:hover {
border-color: #3b82f6;
transform: translateY(-2px);
}
.work-thumbnail {
position: relative;
aspect-ratio: 16/9;
overflow: hidden;
}
.work-thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* 鼠标悬停时显示的做同款按钮 */
.hover-create-btn {
position: absolute;
right: 8px;
bottom: 8px;
opacity: 0;
transform: translateY(10px);
transition: all 0.3s ease;
z-index: 10;
}
.work-thumbnail:hover .hover-create-btn {
opacity: 1;
transform: translateY(0);
}
.hover-create-btn .el-button {
background: rgba(64, 158, 255, 0.9);
border: none;
backdrop-filter: blur(8px);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
}
.hover-create-btn .el-button:hover {
background: rgba(64, 158, 255, 1);
transform: scale(1.05);
}
.work-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.8));
padding: 20px;
}
.overlay-text {
font-size: 16px;
font-weight: 600;
color: #fff;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.8);
}
.work-info {
padding: 16px;
display: flex;
flex-direction: column;
gap: 8px;
}
.work-title {
font-size: 16px;
font-weight: 600;
color: #fff;
}
.work-meta {
font-size: 12px;
color: #9ca3af;
}
.work-actions {
padding: 0 16px 16px;
opacity: 0;
transition: opacity 0.2s ease;
}
.work-item:hover .work-actions {
opacity: 1;
}
.create-similar-btn {
width: 100%;
}
.work-director {
padding: 0 16px 16px;
text-align: center;
}
.work-director span {
font-size: 12px;
color: #6b7280;
font-style: italic;
}
/* 响应式设计 */
@media (max-width: 1024px) {
.sidebar {
width: 260px;
}
.works-grid {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
}
@media (max-width: 768px) {
.text-to-video-page {
flex-direction: column;
}
.sidebar {
width: 100%;
height: auto;
}
.nav-menu {
flex-direction: row;
overflow-x: auto;
padding: 0 16px;
}
.nav-item {
white-space: nowrap;
}
.works-grid {
grid-template-columns: 1fr;
}
}
/* 模态框样式 */
:deep(.detail-dialog .el-dialog) {
background: #0a0a0a !important;
border-radius: 12px;
border: 1px solid #333 !important;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.8);
}
:deep(.detail-dialog .el-dialog__wrapper) {
background-color: rgba(0, 0, 0, 0.8) !important;
}
:deep(.detail-dialog .el-dialog__header) {
background: #0a0a0a !important;
padding: 16px 20px;
border-bottom: 1px solid #333;
}
:deep(.detail-dialog .el-dialog__title) {
color: #fff !important;
font-size: 18px;
font-weight: 600;
}
:deep(.detail-dialog .el-dialog__headerbtn) {
color: #fff !important;
}
:deep(.detail-dialog .el-dialog__body) {
background: #0a0a0a !important;
padding: 0 !important;
}
:deep(.detail-dialog .el-overlay) {
background-color: rgba(0, 0, 0, 0.8) !important;
}
/* 全局覆盖Element Plus默认样式 */
:deep(.el-dialog) {
background: #0a0a0a !important;
border: 1px solid #333 !important;
}
:deep(.el-dialog__wrapper) {
background-color: rgba(0, 0, 0, 0.8) !important;
}
:deep(.el-dialog__header) {
background: #0a0a0a !important;
}
:deep(.el-dialog__body) {
background: #0a0a0a !important;
}
:deep(.el-overlay) {
background-color: rgba(0, 0, 0, 0.8) !important;
}
.detail-content {
display: flex;
height: 50vh;
background: #0a0a0a;
}
.detail-left {
flex: 1;
padding: 20px;
display: flex;
align-items: center;
justify-content: center;
}
.video-player {
position: relative;
width: 100%;
max-width: 400px;
aspect-ratio: 16/9;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
}
.video-thumbnail {
width: 100%;
height: 100%;
object-fit: cover;
}
.play-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.2s ease;
}
.video-player:hover .play-overlay {
opacity: 1;
}
.play-button {
width: 60px;
height: 60px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.9);
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: #000;
font-weight: bold;
}
.detail-right {
flex: 1;
padding: 20px;
background: #0a0a0a;
display: flex;
flex-direction: column;
gap: 20px;
}
.metadata-section {
display: flex;
flex-direction: column;
gap: 12px;
}
.metadata-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #2a2a2a;
}
.metadata-item:last-child {
border-bottom: none;
}
.label {
font-size: 14px;
color: #9ca3af;
font-weight: 500;
}
.value {
font-size: 14px;
color: #fff;
font-weight: 600;
}
.description-section {
flex: 1;
}
.section-title {
font-size: 16px;
color: #fff;
font-weight: 600;
margin-bottom: 12px;
}
.description-text {
font-size: 14px;
color: #d1d5db;
line-height: 1.6;
margin: 0;
}
.action-section {
margin-top: auto;
}
.create-similar-btn {
width: 100%;
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
color: #fff;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
}
.create-similar-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
}
</style>