feat: 增加头,交互优化
This commit is contained in:
@@ -1,12 +1,8 @@
|
||||
<template>
|
||||
<router-view />
|
||||
<!-- <AppLayout>
|
||||
<router-view />
|
||||
</AppLayout> -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import AppLayout from '@/components/common/AppLayout.vue'
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -774,21 +774,21 @@ body {
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: var(--bg-primary);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
/* padding: var(--space-2) var(--space-3); */
|
||||
transition: background-color var(--transition-normal);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
/* @media (min-width: 768px) {
|
||||
.page-container {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
/* @media (min-width: 1024px) {
|
||||
.page-container {
|
||||
padding: var(--space-4) var(--space-5);
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
.content-wrapper {
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -236,6 +236,7 @@ const props = defineProps<{
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: boolean]
|
||||
'config-updated': []
|
||||
}>()
|
||||
|
||||
const visible = computed({
|
||||
@@ -567,6 +568,7 @@ const handleSubmit = async () => {
|
||||
|
||||
editDialogVisible.value = false
|
||||
loadConfigs()
|
||||
emit('config-updated')
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error.message || '操作失败')
|
||||
} finally {
|
||||
@@ -625,45 +627,84 @@ const handleQuickSetup = async () => {
|
||||
const apiKey = quickSetupApiKey.value.trim()
|
||||
|
||||
try {
|
||||
// 创建文本配置
|
||||
const textProvider = providerConfigs.text.find(p => p.id === 'chatfire')!
|
||||
await aiAPI.create({
|
||||
service_type: 'text',
|
||||
provider: 'chatfire',
|
||||
name: generateConfigName('chatfire', 'text'),
|
||||
base_url: baseUrl,
|
||||
api_key: apiKey,
|
||||
model: [textProvider.models[0]],
|
||||
priority: 0
|
||||
})
|
||||
// 加载所有类型的配置,检查是否已存在相同 baseUrl 的配置
|
||||
const [textConfigs, imageConfigs, videoConfigs] = await Promise.all([
|
||||
aiAPI.list('text'),
|
||||
aiAPI.list('image'),
|
||||
aiAPI.list('video')
|
||||
])
|
||||
|
||||
// 创建图片配置
|
||||
const imageProvider = providerConfigs.image.find(p => p.id === 'chatfire')!
|
||||
await aiAPI.create({
|
||||
service_type: 'image',
|
||||
provider: 'chatfire',
|
||||
name: generateConfigName('chatfire', 'image'),
|
||||
base_url: baseUrl,
|
||||
api_key: apiKey,
|
||||
model: [imageProvider.models[0]],
|
||||
priority: 0
|
||||
})
|
||||
const createdServices: string[] = []
|
||||
const skippedServices: string[] = []
|
||||
|
||||
// 创建视频配置
|
||||
const videoProvider = providerConfigs.video.find(p => p.id === 'chatfire')!
|
||||
await aiAPI.create({
|
||||
service_type: 'video',
|
||||
provider: 'chatfire',
|
||||
name: generateConfigName('chatfire', 'video'),
|
||||
base_url: baseUrl,
|
||||
api_key: apiKey,
|
||||
model: [videoProvider.models[0]],
|
||||
priority: 0
|
||||
})
|
||||
// 创建文本配置(如果不存在)
|
||||
const existingTextConfig = textConfigs.find(c => c.base_url === baseUrl)
|
||||
if (!existingTextConfig) {
|
||||
const textProvider = providerConfigs.text.find(p => p.id === 'chatfire')!
|
||||
await aiAPI.create({
|
||||
service_type: 'text',
|
||||
provider: 'chatfire',
|
||||
name: generateConfigName('chatfire', 'text'),
|
||||
base_url: baseUrl,
|
||||
api_key: apiKey,
|
||||
model: [textProvider.models[0]],
|
||||
priority: 0
|
||||
})
|
||||
createdServices.push('文本')
|
||||
} else {
|
||||
skippedServices.push('文本')
|
||||
}
|
||||
|
||||
// 创建图片配置(如果不存在)
|
||||
const existingImageConfig = imageConfigs.find(c => c.base_url === baseUrl)
|
||||
if (!existingImageConfig) {
|
||||
const imageProvider = providerConfigs.image.find(p => p.id === 'chatfire')!
|
||||
await aiAPI.create({
|
||||
service_type: 'image',
|
||||
provider: 'chatfire',
|
||||
name: generateConfigName('chatfire', 'image'),
|
||||
base_url: baseUrl,
|
||||
api_key: apiKey,
|
||||
model: [imageProvider.models[0]],
|
||||
priority: 0
|
||||
})
|
||||
createdServices.push('图片')
|
||||
} else {
|
||||
skippedServices.push('图片')
|
||||
}
|
||||
|
||||
// 创建视频配置(如果不存在)
|
||||
const existingVideoConfig = videoConfigs.find(c => c.base_url === baseUrl)
|
||||
if (!existingVideoConfig) {
|
||||
const videoProvider = providerConfigs.video.find(p => p.id === 'chatfire')!
|
||||
await aiAPI.create({
|
||||
service_type: 'video',
|
||||
provider: 'chatfire',
|
||||
name: generateConfigName('chatfire', 'video'),
|
||||
base_url: baseUrl,
|
||||
api_key: apiKey,
|
||||
model: [videoProvider.models[0]],
|
||||
priority: 0
|
||||
})
|
||||
createdServices.push('视频')
|
||||
} else {
|
||||
skippedServices.push('视频')
|
||||
}
|
||||
|
||||
// 显示结果消息
|
||||
if (createdServices.length > 0 && skippedServices.length > 0) {
|
||||
ElMessage.success(`已创建 ${createdServices.join('、')} 配置,${skippedServices.join('、')} 配置已存在`)
|
||||
} else if (createdServices.length > 0) {
|
||||
ElMessage.success(`一键配置成功!已创建 ${createdServices.join('、')} 服务配置`)
|
||||
} else {
|
||||
ElMessage.info('所有配置已存在,无需重复创建')
|
||||
}
|
||||
|
||||
ElMessage.success('一键配置成功!已创建文本、图片、视频三个服务配置')
|
||||
quickSetupVisible.value = false
|
||||
loadConfigs()
|
||||
if (createdServices.length > 0) {
|
||||
emit('config-updated')
|
||||
}
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error.message || '配置失败')
|
||||
} finally {
|
||||
|
||||
271
web/src/components/common/AppHeader.vue
Normal file
271
web/src/components/common/AppHeader.vue
Normal file
@@ -0,0 +1,271 @@
|
||||
<template>
|
||||
<div class="app-header-wrapper">
|
||||
<header class="app-header" :class="{ 'header-fixed': fixed }">
|
||||
<div class="header-content">
|
||||
<!-- Left section: Logo + Left slot -->
|
||||
<div class="header-left">
|
||||
<router-link v-if="showLogo" to="/" class="logo">
|
||||
<span class="logo-text">🎬 AI Drama</span>
|
||||
</router-link>
|
||||
<!-- Left slot for business content | 左侧插槽用于业务内容 -->
|
||||
<slot name="left" />
|
||||
</div>
|
||||
|
||||
<!-- Center section: Center slot -->
|
||||
<div class="header-center">
|
||||
<slot name="center" />
|
||||
</div>
|
||||
|
||||
<!-- Right section: Actions + Right slot -->
|
||||
<div class="header-right">
|
||||
|
||||
<!-- Language Switcher | 语言切换 -->
|
||||
<LanguageSwitcher v-if="showLanguage" />
|
||||
|
||||
<!-- Theme Toggle | 主题切换 -->
|
||||
<ThemeToggle v-if="showTheme" />
|
||||
|
||||
<!-- AI Config (Model Switch) | AI 配置(模型切换) -->
|
||||
<el-button v-if="showAIConfig" @click="handleOpenAIConfig" class="header-btn">
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span class="btn-text">{{ $t('drama.aiConfig') }}</span>
|
||||
</el-button>
|
||||
<!-- Right slot for business content (before actions) | 右侧插槽(在操作按钮前) -->
|
||||
<slot name="right" />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- AI Config Dialog | AI 配置对话框 -->
|
||||
<AIConfigDialog v-model="showConfigDialog" @config-updated="emit('config-updated')" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Setting } from '@element-plus/icons-vue'
|
||||
import ThemeToggle from './ThemeToggle.vue'
|
||||
import AIConfigDialog from './AIConfigDialog.vue'
|
||||
import LanguageSwitcher from '@/components/LanguageSwitcher.vue'
|
||||
|
||||
/**
|
||||
* AppHeader - Global application header component
|
||||
* 应用顶部头组件
|
||||
*
|
||||
* Features | 功能:
|
||||
* - Fixed position at top | 固定在顶部
|
||||
* - Model/Theme/Language switch | 模型/主题/语言切换
|
||||
* - Slots support for business content | 支持插槽放置业务内容
|
||||
*
|
||||
* Slots | 插槽:
|
||||
* - left: Content after logo | logo 右侧内容
|
||||
* - center: Center content | 中间内容
|
||||
* - right: Content before actions | 操作按钮左侧内容
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
/** Fixed position at top | 是否固定在顶部 */
|
||||
fixed?: boolean
|
||||
/** Show logo | 是否显示 logo */
|
||||
showLogo?: boolean
|
||||
/** Show language switcher | 是否显示语言切换 */
|
||||
showLanguage?: boolean
|
||||
/** Show theme toggle | 是否显示主题切换 */
|
||||
showTheme?: boolean
|
||||
/** Show AI config button | 是否显示 AI 配置按钮 */
|
||||
showAIConfig?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
fixed: true,
|
||||
showLogo: true,
|
||||
showLanguage: true,
|
||||
showTheme: true,
|
||||
showAIConfig: true
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'open-ai-config'): void
|
||||
(e: 'config-updated'): void
|
||||
}>()
|
||||
|
||||
// AI Config dialog state | AI 配置对话框状态
|
||||
const showConfigDialog = ref(false)
|
||||
|
||||
// Handle open AI config | 处理打开 AI 配置
|
||||
const handleOpenAIConfig = () => {
|
||||
showConfigDialog.value = true
|
||||
emit('open-ai-config')
|
||||
}
|
||||
|
||||
// Expose methods for external control | 暴露方法供外部控制
|
||||
defineExpose({
|
||||
openAIConfig: () => {
|
||||
showConfigDialog.value = true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-header {
|
||||
background: var(--bg-card);
|
||||
border-bottom: 1px solid var(--border-primary);
|
||||
backdrop-filter: blur(8px);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.app-header.header-fixed {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 var(--space-4);
|
||||
height: 70px;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
text-decoration: none;
|
||||
color: var(--text-primary);
|
||||
font-weight: 700;
|
||||
font-size: 1.125rem;
|
||||
transition: opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.logo:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
background: linear-gradient(135deg, var(--accent) 0%, #06b6d4 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
border-radius: var(--radius-lg);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.header-btn .btn-text {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
/* Dark mode adjustments | 深色模式适配 */
|
||||
.dark .app-header {
|
||||
background: rgba(26, 33, 41, 0.95);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Common Slot Styles / 插槽通用样式
|
||||
======================================== */
|
||||
|
||||
/* Back Button | 返回按钮 */
|
||||
:deep(.back-btn) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 8px 12px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
border-radius: var(--radius-md);
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
:deep(.back-btn:hover) {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
:deep(.back-btn .el-icon) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Page Title | 页面标题 */
|
||||
:deep(.page-title) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
:deep(.page-title h1),
|
||||
:deep(.header-title),
|
||||
:deep(.drama-title) {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
:deep(.page-title .subtitle) {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Episode Title | 章节标题 */
|
||||
:deep(.episode-title) {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Responsive | 响应式 */
|
||||
@media (max-width: 768px) {
|
||||
.header-content {
|
||||
padding: 0 var(--space-3);
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
:deep(.page-title h1),
|
||||
:deep(.header-title),
|
||||
:deep(.drama-title) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
:deep(.back-btn span) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -20,3 +20,4 @@ export { default as AIConfigDialog } from './AIConfigDialog.vue'
|
||||
|
||||
// Layout Components / 布局组件
|
||||
export { default as AppLayout } from './AppLayout.vue'
|
||||
export { default as AppHeader } from './AppHeader.vue'
|
||||
|
||||
@@ -3,13 +3,18 @@
|
||||
<div class="page-container">
|
||||
<div class="content-wrapper animate-fade-in">
|
||||
<!-- Header / 头部 -->
|
||||
<PageHeader
|
||||
title="创建新项目"
|
||||
subtitle="填写基本信息来创建你的短剧项目"
|
||||
:show-back="true"
|
||||
back-text="返回"
|
||||
:show-border="false"
|
||||
/>
|
||||
<AppHeader :fixed="false" :show-logo="false">
|
||||
<template #left>
|
||||
<el-button text @click="goBack" class="back-btn">
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
<span>返回</span>
|
||||
</el-button>
|
||||
<div class="page-title">
|
||||
<h1>创建新项目</h1>
|
||||
<span class="subtitle">填写基本信息来创建你的短剧项目</span>
|
||||
</div>
|
||||
</template>
|
||||
</AppHeader>
|
||||
|
||||
<!-- Form Card / 表单卡片 -->
|
||||
<div class="form-card">
|
||||
@@ -69,7 +74,7 @@ import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { ArrowLeft, Plus } from '@element-plus/icons-vue'
|
||||
import { dramaAPI } from '@/api/drama'
|
||||
import type { CreateDramaRequest } from '@/types/drama'
|
||||
import { PageHeader } from '@/components/common'
|
||||
import { AppHeader } from '@/components/common'
|
||||
|
||||
const router = useRouter()
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
@@ -3,114 +3,68 @@
|
||||
<!-- 短剧列表页面 - 使用现代简约设计重构 -->
|
||||
<div class="page-container">
|
||||
<div class="content-wrapper animate-fade-in">
|
||||
<!-- Page Header / 页面头部 -->
|
||||
<PageHeader
|
||||
:title="$t('drama.title')"
|
||||
:subtitle="$t('drama.totalProjects', { count: total })"
|
||||
>
|
||||
<template #actions>
|
||||
<LanguageSwitcher />
|
||||
<ThemeToggle />
|
||||
<el-button @click="showAIConfig = true" class="header-btn">
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span class="btn-text">{{ $t('drama.aiConfig') }}</span>
|
||||
</el-button>
|
||||
<!-- App Header / 应用头部 -->
|
||||
<AppHeader :fixed="false">
|
||||
<template #left>
|
||||
<div class="page-title">
|
||||
<h1>{{ $t('drama.title') }}</h1>
|
||||
<span class="subtitle">{{ $t('drama.totalProjects', { count: total }) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<el-button type="primary" @click="handleCreate" class="header-btn primary">
|
||||
<el-icon><Plus /></el-icon>
|
||||
<el-icon>
|
||||
<Plus />
|
||||
</el-icon>
|
||||
<span class="btn-text">{{ $t('drama.createNew') }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</PageHeader>
|
||||
</AppHeader>
|
||||
|
||||
<!-- Project Grid / 项目网格 -->
|
||||
<div v-loading="loading" class="projects-grid" :class="{ 'is-empty': !loading && dramas.length === 0 }">
|
||||
<!-- Empty state / 空状态 -->
|
||||
<EmptyState
|
||||
v-if="!loading && dramas.length === 0"
|
||||
:title="$t('drama.empty')"
|
||||
:description="$t('drama.emptyHint')"
|
||||
:icon="Film"
|
||||
>
|
||||
<EmptyState v-if="!loading && dramas.length === 0" :title="$t('drama.empty')"
|
||||
:description="$t('drama.emptyHint')" :icon="Film">
|
||||
<el-button type="primary" @click="handleCreate">
|
||||
<el-icon><Plus /></el-icon>
|
||||
<el-icon>
|
||||
<Plus />
|
||||
</el-icon>
|
||||
{{ $t('drama.createNew') }}
|
||||
</el-button>
|
||||
</EmptyState>
|
||||
|
||||
<!-- Project Cards / 项目卡片列表 -->
|
||||
<ProjectCard
|
||||
v-for="drama in dramas"
|
||||
:key="drama.id"
|
||||
:title="drama.title"
|
||||
:description="drama.description"
|
||||
:updated-at="drama.updated_at"
|
||||
:episode-count="drama.total_episodes || 0"
|
||||
@click="viewDrama(drama.id)"
|
||||
>
|
||||
<ProjectCard v-for="drama in dramas" :key="drama.id" :title="drama.title" :description="drama.description"
|
||||
:updated-at="drama.updated_at" :episode-count="drama.total_episodes || 0" @click="viewDrama(drama.id)">
|
||||
<template #actions>
|
||||
<ActionButton
|
||||
:icon="Edit"
|
||||
:tooltip="$t('common.edit')"
|
||||
@click="editDrama(drama.id)"
|
||||
/>
|
||||
<el-popconfirm
|
||||
:title="$t('drama.deleteConfirm')"
|
||||
:confirm-button-text="$t('common.confirm')"
|
||||
:cancel-button-text="$t('common.cancel')"
|
||||
@confirm="deleteDrama(drama.id)"
|
||||
>
|
||||
<ActionButton :icon="Edit" :tooltip="$t('common.edit')" @click="editDrama(drama.id)" />
|
||||
<el-popconfirm :title="$t('drama.deleteConfirm')" :confirm-button-text="$t('common.confirm')"
|
||||
:cancel-button-text="$t('common.cancel')" @confirm="deleteDrama(drama.id)">
|
||||
<template #reference>
|
||||
<el-button
|
||||
:icon="Delete"
|
||||
class="action-button danger"
|
||||
link
|
||||
/>
|
||||
<el-button :icon="Delete" class="action-button danger" link />
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</ProjectCard>
|
||||
</ProjectCard>
|
||||
</div>
|
||||
|
||||
<!-- Edit Dialog / 编辑对话框 -->
|
||||
<el-dialog
|
||||
v-model="editDialogVisible"
|
||||
:title="$t('drama.editProject')"
|
||||
width="520px"
|
||||
:close-on-click-modal="false"
|
||||
class="edit-dialog"
|
||||
>
|
||||
<el-form
|
||||
:model="editForm"
|
||||
label-position="top"
|
||||
v-loading="editLoading"
|
||||
class="edit-form"
|
||||
>
|
||||
<el-dialog v-model="editDialogVisible" :title="$t('drama.editProject')" width="520px"
|
||||
:close-on-click-modal="false" class="edit-dialog">
|
||||
<el-form :model="editForm" label-position="top" v-loading="editLoading" class="edit-form">
|
||||
<el-form-item :label="$t('drama.projectName')" required>
|
||||
<el-input
|
||||
v-model="editForm.title"
|
||||
:placeholder="$t('drama.projectNamePlaceholder')"
|
||||
size="large"
|
||||
/>
|
||||
<el-input v-model="editForm.title" :placeholder="$t('drama.projectNamePlaceholder')" size="large" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('drama.projectDesc')">
|
||||
<el-input
|
||||
v-model="editForm.description"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
:placeholder="$t('drama.projectDescPlaceholder')"
|
||||
resize="none"
|
||||
/>
|
||||
<el-input v-model="editForm.description" type="textarea" :rows="4"
|
||||
:placeholder="$t('drama.projectDescPlaceholder')" resize="none" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="editDialogVisible = false" size="large">{{ $t('common.cancel') }}</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="saveEdit"
|
||||
:loading="editLoading"
|
||||
size="large"
|
||||
>
|
||||
<el-button type="primary" @click="saveEdit" :loading="editLoading" size="large">
|
||||
{{ $t('common.save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -118,13 +72,8 @@
|
||||
</el-dialog>
|
||||
|
||||
<!-- Create Drama Dialog / 创建短剧弹窗 -->
|
||||
<CreateDramaDialog
|
||||
v-model="createDialogVisible"
|
||||
@created="loadDramas"
|
||||
/>
|
||||
<CreateDramaDialog v-model="createDialogVisible" @created="loadDramas" />
|
||||
|
||||
<!-- AI Config Dialog / AI配置弹窗 -->
|
||||
<AIConfigDialog v-model="showAIConfig" />
|
||||
</div>
|
||||
|
||||
<!-- Sticky Pagination / 吸底分页器 -->
|
||||
@@ -134,25 +83,13 @@
|
||||
<span class="pagination-total">{{ $t('drama.totalProjects', { count: total }) }}</span>
|
||||
</div>
|
||||
<div class="pagination-controls">
|
||||
<el-pagination
|
||||
v-model:current-page="queryParams.page"
|
||||
v-model:page-size="queryParams.page_size"
|
||||
:total="total"
|
||||
:page-sizes="[12, 24, 36, 48]"
|
||||
:pager-count="5"
|
||||
layout="prev, pager, next"
|
||||
@size-change="loadDramas"
|
||||
@current-change="loadDramas"
|
||||
/>
|
||||
<el-pagination v-model:current-page="queryParams.page" v-model:page-size="queryParams.page_size"
|
||||
:total="total" :page-sizes="[12, 24, 36, 48]" :pager-count="5" layout="prev, pager, next"
|
||||
@size-change="loadDramas" @current-change="loadDramas" />
|
||||
</div>
|
||||
<div class="pagination-size">
|
||||
<span class="size-label">{{ $t('common.perPage') }}</span>
|
||||
<el-select
|
||||
v-model="queryParams.page_size"
|
||||
size="small"
|
||||
class="size-select"
|
||||
@change="loadDramas"
|
||||
>
|
||||
<el-select v-model="queryParams.page_size" size="small" class="size-select" @change="loadDramas">
|
||||
<el-option :value="12" label="12" />
|
||||
<el-option :value="24" label="24" />
|
||||
<el-option :value="36" label="36" />
|
||||
@@ -168,19 +105,18 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
Plus,
|
||||
Film,
|
||||
Setting,
|
||||
Edit,
|
||||
View,
|
||||
import {
|
||||
Plus,
|
||||
Film,
|
||||
Setting,
|
||||
Edit,
|
||||
View,
|
||||
Delete,
|
||||
InfoFilled
|
||||
InfoFilled
|
||||
} from '@element-plus/icons-vue'
|
||||
import { dramaAPI } from '@/api/drama'
|
||||
import type { Drama, DramaListQuery } from '@/types/drama'
|
||||
import { PageHeader, ProjectCard, ThemeToggle, ActionButton, CreateDramaDialog, EmptyState, AIConfigDialog } from '@/components/common'
|
||||
import LanguageSwitcher from '@/components/LanguageSwitcher.vue'
|
||||
import { AppHeader, ProjectCard, ActionButton, CreateDramaDialog, EmptyState } from '@/components/common'
|
||||
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
@@ -194,7 +130,6 @@ const queryParams = ref<DramaListQuery>({
|
||||
|
||||
// Create dialog state / 创建弹窗状态
|
||||
const createDialogVisible = ref(false)
|
||||
const showAIConfig = ref(false)
|
||||
|
||||
// Load drama list / 加载短剧列表
|
||||
const loadDramas = async () => {
|
||||
@@ -248,7 +183,7 @@ const saveEdit = async () => {
|
||||
ElMessage.warning('请输入项目名称')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
editLoading.value = true
|
||||
try {
|
||||
await dramaAPI.update(editForm.value.id, {
|
||||
@@ -288,19 +223,19 @@ onMounted(() => {
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background: var(--bg-primary);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
/* padding: var(--space-2) var(--space-3); */
|
||||
transition: background var(--transition-normal);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.page-container {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
/* padding: var(--space-3) var(--space-4); */
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.page-container {
|
||||
padding: var(--space-4) var(--space-5);
|
||||
/* padding: var(--space-4) var(--space-5); */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,6 +244,28 @@ onMounted(() => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Page Title / 页面标题
|
||||
======================================== */
|
||||
.page-title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.page-title h1 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.page-title .subtitle {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Header Buttons / 头部按钮
|
||||
======================================== */
|
||||
@@ -332,7 +289,7 @@ onMounted(() => {
|
||||
.btn-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.header-btn {
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
@@ -342,7 +299,9 @@ onMounted(() => {
|
||||
Projects Grid / 项目网格 - 紧凑间距
|
||||
======================================== */
|
||||
.projects-grid {
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
/* grid-template-columns: repeat(2, 1fr); */
|
||||
gap: var(--space-2);
|
||||
margin-bottom: var(--space-4);
|
||||
@@ -386,6 +345,7 @@ onMounted(() => {
|
||||
Sticky Pagination / 吸底分页器
|
||||
======================================== */
|
||||
.pagination-sticky {
|
||||
/* padding: 12px; */
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
@@ -2,12 +2,18 @@
|
||||
<div class="page-container">
|
||||
<div class="content-wrapper animate-fade-in">
|
||||
<!-- Page Header / 页面头部 -->
|
||||
<PageHeader
|
||||
:title="drama?.title || ''"
|
||||
:subtitle="drama?.description || $t('drama.management.overview')"
|
||||
:show-back="true"
|
||||
:back-text="$t('common.back')"
|
||||
/>
|
||||
<AppHeader :fixed="false" :show-logo="false">
|
||||
<template #left>
|
||||
<el-button text @click="$router.back()" class="back-btn">
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
<span>{{ $t('common.back') }}</span>
|
||||
</el-button>
|
||||
<div class="page-title">
|
||||
<h1>{{ drama?.title || '' }}</h1>
|
||||
<span class="subtitle">{{ drama?.description || $t('drama.management.overview') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</AppHeader>
|
||||
|
||||
<!-- Tabs / 标签页 -->
|
||||
<div class="tabs-wrapper">
|
||||
@@ -60,15 +66,22 @@
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<el-card shadow="never" style="margin-top: 20px;">
|
||||
<el-card shadow="never" class="project-info-card">
|
||||
<template #header>
|
||||
<h3 class="card-title">{{ $t('drama.management.projectInfo') }}</h3>
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">{{ $t('drama.management.projectInfo') }}</h3>
|
||||
<el-tag :type="getStatusType(drama?.status)" size="small">{{ getStatusText(drama?.status) }}</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item :label="$t('drama.management.projectName')">{{ drama?.title }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('common.createdAt')">{{ formatDate(drama?.created_at) }}</el-descriptions-item>
|
||||
<el-descriptions :column="2" border class="project-descriptions">
|
||||
<el-descriptions-item :label="$t('drama.management.projectName')">
|
||||
<span class="info-value">{{ drama?.title }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('common.createdAt')">
|
||||
<span class="info-value">{{ formatDate(drama?.created_at) }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="$t('drama.management.projectDesc')" :span="2">
|
||||
{{ drama?.description || $t('drama.management.noDescription') }}
|
||||
<span class="info-desc">{{ drama?.description || $t('drama.management.noDescription') }}</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
@@ -250,7 +263,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ArrowLeft, Document, User, Picture, Plus } from '@element-plus/icons-vue'
|
||||
import { dramaAPI } from '@/api/drama'
|
||||
import type { Drama } from '@/types/drama'
|
||||
import { PageHeader, StatCard, EmptyState } from '@/components/common'
|
||||
import { AppHeader, StatCard, EmptyState } from '@/components/common'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@@ -543,19 +556,19 @@ onMounted(() => {
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background: var(--bg-primary);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
/* padding: var(--space-2) var(--space-3); */
|
||||
transition: background var(--transition-normal);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.page-container {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
/* padding: var(--space-3) var(--space-4); */
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.page-container {
|
||||
padding: var(--space-4) var(--space-5);
|
||||
/* padding: var(--space-4) var(--space-5); */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -762,6 +775,20 @@ onMounted(() => {
|
||||
border-color: var(--border-primary);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Project Info Card / 项目信息卡片
|
||||
======================================== */
|
||||
.project-info-card {
|
||||
margin-top: var(--space-5);
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
@@ -769,6 +796,30 @@ onMounted(() => {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.project-descriptions {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.project-descriptions .el-descriptions__label) {
|
||||
width: 120px;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
:deep(.project-descriptions .el-descriptions__content) {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.info-desc {
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.dark :deep(.el-dialog) {
|
||||
background: var(--bg-card);
|
||||
}
|
||||
|
||||
@@ -1,37 +1,34 @@
|
||||
<template>
|
||||
<div class="workflow-container">
|
||||
<div class="workflow-header">
|
||||
<div class="header-single-line">
|
||||
<div class="header-left-section">
|
||||
<el-button text @click="goBack" class="back-btn">
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
<span>{{ $t('dramaWorkflow.returnToList') }}</span>
|
||||
</el-button>
|
||||
<h2 class="drama-title">{{ drama?.title }}</h2>
|
||||
<el-tag :type="getStatusType(drama?.status)" size="small">{{ getStatusText(drama?.status) }}</el-tag>
|
||||
</div>
|
||||
|
||||
<AppHeader :fixed="false" :show-logo="false">
|
||||
<template #left>
|
||||
<el-button text @click="goBack" class="back-btn">
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
<span>{{ $t('dramaWorkflow.returnToList') }}</span>
|
||||
</el-button>
|
||||
<h2 class="drama-title">{{ drama?.title }}</h2>
|
||||
<el-tag :type="getStatusType(drama?.status)" size="small">{{ getStatusText(drama?.status) }}</el-tag>
|
||||
</template>
|
||||
<template #center>
|
||||
<!-- 步骤进度条 -->
|
||||
<div class="steps-inline">
|
||||
<div class="custom-steps">
|
||||
<div class="step-item" :class="{ active: currentStep >= 0, current: currentStep === 0 }">
|
||||
<div class="step-circle">1</div>
|
||||
<span class="step-text">{{ $t('dramaWorkflow.episodeScript', { number: currentEpisodeNumber }) }}</span>
|
||||
</div>
|
||||
<el-icon class="step-arrow"><ArrowRight /></el-icon>
|
||||
<div class="step-item" :class="{ active: currentStep >= 1, current: currentStep === 1 }">
|
||||
<div class="step-circle">2</div>
|
||||
<span class="step-text">{{ $t('dramaWorkflow.storyboardBreakdown') }}</span>
|
||||
</div>
|
||||
<el-icon class="step-arrow"><ArrowRight /></el-icon>
|
||||
<div class="step-item" :class="{ active: currentStep >= 2, current: currentStep === 2 }">
|
||||
<div class="step-circle">3</div>
|
||||
<span class="step-text">{{ $t('dramaWorkflow.characterImages') }}</span>
|
||||
</div>
|
||||
<div class="custom-steps">
|
||||
<div class="step-item" :class="{ active: currentStep >= 0, current: currentStep === 0 }">
|
||||
<div class="step-circle">1</div>
|
||||
<span class="step-text">{{ $t('dramaWorkflow.episodeScript', { number: currentEpisodeNumber }) }}</span>
|
||||
</div>
|
||||
<el-icon class="step-arrow"><ArrowRight /></el-icon>
|
||||
<div class="step-item" :class="{ active: currentStep >= 1, current: currentStep === 1 }">
|
||||
<div class="step-circle">2</div>
|
||||
<span class="step-text">{{ $t('dramaWorkflow.storyboardBreakdown') }}</span>
|
||||
</div>
|
||||
<el-icon class="step-arrow"><ArrowRight /></el-icon>
|
||||
<div class="step-item" :class="{ active: currentStep >= 2, current: currentStep === 2 }">
|
||||
<div class="step-circle">3</div>
|
||||
<span class="step-text">{{ $t('dramaWorkflow.characterImages') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</AppHeader>
|
||||
|
||||
<!-- 当前阶段内容区域 -->
|
||||
<div class="stage-area">
|
||||
@@ -567,6 +564,7 @@ import { generationAPI } from '@/api/generation'
|
||||
import { characterLibraryAPI } from '@/api/character-library'
|
||||
import request from '@/utils/request'
|
||||
import type { Drama, DramaStatus } from '@/types/drama'
|
||||
import { AppHeader } from '@/components/common'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -1,39 +1,38 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="content-wrapper animate-fade-in">
|
||||
<header class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<button class="back-btn" @click="$router.back()">
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
<span>{{ $t('workflow.backToProject') }}</span>
|
||||
</button>
|
||||
<div class="nav-divider"></div>
|
||||
<h1 class="header-title">{{ $t('workflow.episodeProduction', { number: episodeNumber }) }}</h1>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<div class="custom-steps">
|
||||
<div class="step-item" :class="{ active: currentStep >= 0, current: currentStep === 0 }">
|
||||
<div class="step-circle">1</div>
|
||||
<span class="step-text">{{ $t('workflow.steps.content') }}</span>
|
||||
</div>
|
||||
<el-icon class="step-arrow"><ArrowRight /></el-icon>
|
||||
<div class="step-item" :class="{ active: currentStep >= 1, current: currentStep === 1 }">
|
||||
<div class="step-circle">2</div>
|
||||
<span class="step-text">{{ $t('workflow.steps.generateImages') }}</span>
|
||||
</div>
|
||||
<el-icon class="step-arrow"><ArrowRight /></el-icon>
|
||||
<div class="step-item" :class="{ active: currentStep >= 2, current: currentStep === 2 }">
|
||||
<div class="step-circle">3</div>
|
||||
<span class="step-text">{{ $t('workflow.steps.splitStoryboard') }}</span>
|
||||
</div>
|
||||
<AppHeader :fixed="false" :show-logo="false">
|
||||
<template #left>
|
||||
<el-button text @click="$router.back()" class="back-btn">
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
<span>{{ $t('workflow.backToProject') }}</span>
|
||||
</el-button>
|
||||
<h1 class="header-title">{{ $t('workflow.episodeProduction', { number: episodeNumber }) }}</h1>
|
||||
</template>
|
||||
<template #center>
|
||||
<div class="custom-steps">
|
||||
<div class="step-item" :class="{ active: currentStep >= 0, current: currentStep === 0 }">
|
||||
<div class="step-circle">1</div>
|
||||
<span class="step-text">{{ $t('workflow.steps.content') }}</span>
|
||||
</div>
|
||||
<el-icon class="step-arrow"><ArrowRight /></el-icon>
|
||||
<div class="step-item" :class="{ active: currentStep >= 1, current: currentStep === 1 }">
|
||||
<div class="step-circle">2</div>
|
||||
<span class="step-text">{{ $t('workflow.steps.generateImages') }}</span>
|
||||
</div>
|
||||
<el-icon class="step-arrow"><ArrowRight /></el-icon>
|
||||
<div class="step-item" :class="{ active: currentStep >= 2, current: currentStep === 2 }">
|
||||
<div class="step-circle">3</div>
|
||||
<span class="step-text">{{ $t('workflow.steps.splitStoryboard') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<el-button :icon="Setting" circle @click="showModelConfigDialog" :title="$t('workflow.modelConfig')" />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
<template #right>
|
||||
<el-button :icon="Setting" @click="showModelConfigDialog" :title="$t('workflow.modelConfig')">
|
||||
图文配置
|
||||
</el-button>
|
||||
</template>
|
||||
</AppHeader>
|
||||
|
||||
<!-- 阶段 0: 章节内容 + 提取角色场景 -->
|
||||
<el-card v-show="currentStep === 0" shadow="never" class="stage-card stage-card-fullscreen">
|
||||
@@ -822,7 +821,7 @@ import { aiAPI } from '@/api/ai'
|
||||
import type { AIServiceConfig } from '@/types/ai'
|
||||
import { imageAPI } from '@/api/image'
|
||||
import type { Drama } from '@/types/drama'
|
||||
import PageHeader from '@/components/common/PageHeader.vue'
|
||||
import { AppHeader } from '@/components/common'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -1750,19 +1749,19 @@ onMounted(() => {
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background: var(--bg-primary);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
// padding: var(--space-2) var(--space-3);
|
||||
transition: background var(--transition-normal);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.page-container {
|
||||
padding: var(--space-3) var(--space-4);
|
||||
// padding: var(--space-3) var(--space-4);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.page-container {
|
||||
padding: var(--space-4) var(--space-5);
|
||||
// padding: var(--space-4) var(--space-5);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1841,7 +1840,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.workflow-card {
|
||||
margin-bottom: var(--space-4);
|
||||
margin: 12px;
|
||||
background: var(--bg-card);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-card);
|
||||
@@ -1915,7 +1914,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.stage-card {
|
||||
margin-bottom: 24px;
|
||||
margin: 12px;
|
||||
|
||||
&.stage-card-fullscreen {
|
||||
.stage-body-fullscreen {
|
||||
@@ -1950,14 +1949,13 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.stage-body {
|
||||
padding: 32px;
|
||||
background: var(--bg-card);
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
margin: 12px 0;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -1990,8 +1988,8 @@ onMounted(() => {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-primary);
|
||||
// border-radius: 8px;
|
||||
// border: 1px solid var(--border-primary);
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
@@ -2178,6 +2176,7 @@ onMounted(() => {
|
||||
|
||||
.character-image-list,
|
||||
.scene-image-list {
|
||||
padding: 5px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 16px;
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
<template>
|
||||
<div class="professional-editor">
|
||||
<!-- 顶部工具栏 -->
|
||||
<div class="editor-toolbar">
|
||||
<div class="toolbar-left">
|
||||
<el-button link @click="goBack" class="back-btn">
|
||||
<el-icon>
|
||||
<ArrowLeft />
|
||||
</el-icon>
|
||||
{{ $t('editor.backToEpisode') }}
|
||||
<AppHeader :fixed="false" :show-logo="false" @config-updated="loadVideoModels">
|
||||
<template #left>
|
||||
<el-button text @click="goBack" class="back-btn">
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
<span>{{ $t('editor.backToEpisode') }}</span>
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="episode-title">{{ drama?.title }} - {{ $t('editor.episode', { number: episodeNumber }) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="toolbar-right">
|
||||
<!-- <el-button :icon="Setting" circle @click="showSettings = true" /> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</AppHeader>
|
||||
|
||||
<!-- 主编辑区域 -->
|
||||
<div class="editor-main">
|
||||
@@ -906,6 +899,7 @@ import type { Asset } from '@/types/asset'
|
||||
import type { VideoMerge } from '@/api/videoMerge'
|
||||
import VideoTimelineEditor from '@/components/editor/VideoTimelineEditor.vue'
|
||||
import type { Drama, Episode, Storyboard } from '@/types/drama'
|
||||
import { AppHeader } from '@/components/common'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
Reference in New Issue
Block a user