TemplateDetails.vue 34.8 KB
Newer Older
LiangLiu's avatar
LiangLiu committed
1
<script setup>
LiangLiu's avatar
LiangLiu committed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { showTemplateDetailModal,
        closeTemplateDetailModal,
        useTemplate,
        getTemplateFileUrl,
        onVideoLoaded,
        selectedTemplate,
        applyTemplateAudio,
        applyTemplateImage,
        applyTemplatePrompt,
        showImageZoom,
        copyPrompt,
        generateTemplateShareUrl,
        copyShareLink,
        shareTemplateToSocial,
         } from '../utils/other'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
LiangLiu's avatar
LiangLiu committed
19
import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue'
LiangLiu's avatar
LiangLiu committed
20
21
22
23
24
25
26
const { t, locale } = useI18n()
const route = useRoute()
const router = useRouter()

// 添加响应式变量
const showDetails = ref(false)

LiangLiu's avatar
LiangLiu committed
27
28
29
30
31
32
33
34
// 音频播放器相关
const audioElement = ref(null)
const isPlaying = ref(false)
const audioDuration = ref(0)
const currentTime = ref(0)
const isDragging = ref(false)
const currentAudioUrl = ref('')

LiangLiu's avatar
LiangLiu committed
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 获取图片素材
const getImageMaterials = () => {
    if (!selectedTemplate.value?.inputs?.input_image) return []
    return [['input_image', getTemplateFileUrl(selectedTemplate.value.inputs.input_image, 'images')]]
}

// 获取音频素材
const getAudioMaterials = () => {
    if (!selectedTemplate.value?.inputs?.input_audio) return []
    return [['input_audio', getTemplateFileUrl(selectedTemplate.value.inputs.input_audio, 'audios')]]
}

// 路由关闭功能
const closeWithRoute = () => {
    closeTemplateDetailModal()
LiangLiu's avatar
LiangLiu committed
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
    // 只有当前路由是模板详情页面时才进行路由跳转
    // 如果在其他页面(如 generate)打开的弹窗,关闭时保持在原页面
    if (route.path.startsWith('/template/')) {
        // 从模板详情路由进入的,返回到上一页或首页
        if (window.history.length > 1) {
            router.go(-1)
        } else {
            router.push('/')
        }
    }
    // 如果不是模板详情路由,不做任何路由跳转,保持在当前页面
}

// 滚动到生成区域(仅在 generate 页面)
const scrollToCreationArea = () => {
LiangLiu's avatar
LiangLiu committed
65
66
67
68
69
70
    const mainScrollable = document.querySelector('.main-scrollbar');
    if (mainScrollable) {
        mainScrollable.scrollTo({
            top: 0,
            behavior: 'smooth'
        });
LiangLiu's avatar
LiangLiu committed
71
    }
LiangLiu's avatar
LiangLiu committed
72

LiangLiu's avatar
LiangLiu committed
73
74
75
76
77
78
79
80
81
82
83
}

// 包装 useTemplate 函数,在 generate 页面时滚动到生成区域
const handleUseTemplate = async () => {
    await useTemplate(selectedTemplate.value)
    // 如果当前在 generate 页面,滚动到生成区域
    if (route.path === '/generate' || route.name === 'Generate') {
        // 等待 DOM 更新和展开动画完成
        setTimeout(() => {
            scrollToCreationArea()
        }, 300)
LiangLiu's avatar
LiangLiu committed
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
    }
}

// 键盘事件处理
const handleKeydown = (event) => {
    if (event.key === 'Escape' && showTemplateDetailModal.value) {
        closeWithRoute()
    }
}

// 生命周期钩子
onMounted(() => {
    document.addEventListener('keydown', handleKeydown)
})

onUnmounted(() => {
    document.removeEventListener('keydown', handleKeydown)
LiangLiu's avatar
LiangLiu committed
101
102
103
104
105
    // 清理音频资源
    const audio = getCurrentAudioElement()
    if (audio) {
        audio.pause()
    }
LiangLiu's avatar
LiangLiu committed
106
})
LiangLiu's avatar
LiangLiu committed
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194

// 格式化音频时间
const formatAudioTime = (seconds) => {
    if (!seconds || isNaN(seconds)) return '0:00'
    const mins = Math.floor(seconds / 60)
    const secs = Math.floor(seconds % 60)
    return `${mins}:${secs.toString().padStart(2, '0')}`
}

// 获取当前音频元素(处理可能是数组的情况)
const getCurrentAudioElement = () => {
    return Array.isArray(audioElement.value) ? audioElement.value[0] : audioElement.value
}

// 切换播放/暂停
const toggleAudioPlayback = () => {
    const audio = getCurrentAudioElement()
    if (!audio) return

    if (audio.paused) {
        audio.play().catch(error => {
            console.log('播放失败:', error)
        })
    } else {
        audio.pause()
    }
}

// 音频加载完成
const onAudioLoaded = () => {
    const audio = getCurrentAudioElement()
    if (audio) {
        audioDuration.value = audio.duration || 0
    }
}

// 时间更新
const onTimeUpdate = () => {
    const audio = getCurrentAudioElement()
    if (audio && !isDragging.value) {
        currentTime.value = audio.currentTime || 0
    }
}

// 进度条变化处理
const onProgressChange = (event) => {
    const audio = getCurrentAudioElement()
    if (audioDuration.value > 0 && audio && event.target) {
        const newTime = parseFloat(event.target.value)
        currentTime.value = newTime
        audio.currentTime = newTime
    }
}

// 进度条拖拽结束处理
const onProgressEnd = (event) => {
    const audio = getCurrentAudioElement()
    if (audio && audioDuration.value > 0 && event.target) {
        const newTime = parseFloat(event.target.value)
        audio.currentTime = newTime
        currentTime.value = newTime
    }
    isDragging.value = false
}

// 播放结束
const onAudioEnded = () => {
    isPlaying.value = false
    currentTime.value = 0
}

// 监听音频URL变化
watch(() => getAudioMaterials(), (newMaterials) => {
    if (newMaterials && newMaterials.length > 0) {
        currentAudioUrl.value = newMaterials[0][1]
        nextTick(() => {
            const audio = getCurrentAudioElement()
            if (audio) {
                audio.load()
            }
        })
    } else {
        currentAudioUrl.value = ''
        isPlaying.value = false
        currentTime.value = 0
        audioDuration.value = 0
    }
}, { immediate: true })
LiangLiu's avatar
LiangLiu committed
195
196
</script>
<template>
LiangLiu's avatar
LiangLiu committed
197
            <!-- 模板详情弹窗 - Apple 极简风格 -->
LiangLiu's avatar
LiangLiu committed
198
199
            <div v-cloak>
                <div v-if="showTemplateDetailModal"
LiangLiu's avatar
LiangLiu committed
200
                    class="fixed inset-0 bg-black/50 dark:bg-black/60 backdrop-blur-sm z-[60] flex items-center justify-center p-2 sm:p-1"
LiangLiu's avatar
LiangLiu committed
201
                    @click="closeWithRoute">
LiangLiu's avatar
LiangLiu committed
202
203
204
205
206
                    <div class="w-full h-full max-w-7xl max-h-[100vh] bg-white/95 dark:bg-[#1e1e1e]/95 backdrop-blur-[40px] backdrop-saturate-[180%] border border-black/10 dark:border-white/10 rounded-3xl shadow-[0_20px_60px_rgba(0,0,0,0.2)] dark:shadow-[0_20px_60px_rgba(0,0,0,0.6)] overflow-hidden flex flex-col" @click.stop>
                        <!-- 弹窗头部 - Apple 风格 -->
                        <div class="flex items-center justify-between px-8 py-5 border-b border-black/8 dark:border-white/8 bg-white/50 dark:bg-[#1e1e1e]/50 backdrop-blur-[20px]">
                            <h3 class="text-xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] flex items-center gap-3 tracking-tight">
                                <i class="fas fa-star text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
LiangLiu's avatar
LiangLiu committed
207
208
                                {{ t('templateDetail') }}
                            </h3>
LiangLiu's avatar
LiangLiu committed
209
210
211
212
213
                            <div class="flex items-center gap-2">
                                <button @click="closeWithRoute"
                                        class="w-9 h-9 flex items-center justify-center bg-white/80 dark:bg-[#2c2c2e]/80 border border-black/8 dark:border-white/8 text-[#86868b] dark:text-[#98989d] hover:text-red-500 dark:hover:text-red-400 hover:bg-white dark:hover:bg-[#3a3a3c] rounded-full transition-all duration-200 hover:scale-110 active:scale-100"
                                        :title="t('close')">
                                    <i class="fas fa-times text-sm"></i>
LiangLiu's avatar
LiangLiu committed
214
215
216
217
                                </button>
                            </div>
                        </div>

LiangLiu's avatar
LiangLiu committed
218
219
220
                        <!-- 主要内容区域 - Apple 风格 -->
                        <div class="flex-1 overflow-y-auto main-scrollbar">
                            <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12 p-8 lg:p-12">
LiangLiu's avatar
LiangLiu committed
221
                                <!-- 左侧视频区域 -->
LiangLiu's avatar
LiangLiu committed
222
223
                                <div class="flex items-center justify-center">
                                    <div class="w-full max-w-[400px] aspect-[9/16] bg-black dark:bg-[#000000] rounded-2xl overflow-hidden shadow-[0_8px_24px_rgba(0,0,0,0.15)] dark:shadow-[0_8px_24px_rgba(0,0,0,0.5)]">
LiangLiu's avatar
LiangLiu committed
224
225
226
227
228
                                        <!-- 视频播放器 -->
                                        <video
                                            v-if="selectedTemplate?.outputs?.output_video"
                                            :src="getTemplateFileUrl(selectedTemplate.outputs.output_video,'videos')"
                                            :poster="getTemplateFileUrl(selectedTemplate.inputs.input_image,'images')"
LiangLiu's avatar
LiangLiu committed
229
                                            class="w-full h-full object-contain"
LiangLiu's avatar
LiangLiu committed
230
231
232
                                            controls
                                            loop
                                            preload="metadata"
LiangLiu's avatar
LiangLiu committed
233
                                            @loadeddata="onVideoLoaded">
LiangLiu's avatar
LiangLiu committed
234
235
                                            {{ t('browserNotSupported') }}
                                        </video>
LiangLiu's avatar
LiangLiu committed
236
237
238
                                        <div v-else class="w-full h-full flex flex-col items-center justify-center bg-[#f5f5f7] dark:bg-[#1c1c1e]">
                                            <div class="w-16 h-16 rounded-full bg-black/5 dark:bg-white/5 flex items-center justify-center mb-4">
                                                <i class="fas fa-video text-3xl text-[#86868b] dark:text-[#98989d]"></i>
LiangLiu's avatar
LiangLiu committed
239
                                            </div>
LiangLiu's avatar
LiangLiu committed
240
                                            <p class="text-sm text-[#86868b] dark:text-[#98989d] tracking-tight">{{ t('videoNotAvailable') }}</p>
LiangLiu's avatar
LiangLiu committed
241
242
243
244
                                        </div>
                                    </div>
                                </div>

LiangLiu's avatar
LiangLiu committed
245
246
247
248
249
                                <!-- 右侧信息区域 - Apple 风格 -->
                                <div class="flex items-center justify-center">
                                    <div class="w-full max-w-[400px]">
                                        <!-- 标题 - Apple 风格 -->
                                        <h1 class="text-3xl sm:text-4xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] mb-4 tracking-tight">
LiangLiu's avatar
LiangLiu committed
250
251
252
                                            {{ t('template') }}
                                        </h1>

LiangLiu's avatar
LiangLiu committed
253
254
                                        <!-- 描述 - Apple 风格 -->
                                        <p class="text-sm sm:text-base text-[#86868b] dark:text-[#98989d] mb-8 tracking-tight">
LiangLiu's avatar
LiangLiu committed
255
256
257
                                            {{ t('templateDescription') }}
                                        </p>

LiangLiu's avatar
LiangLiu committed
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
                                        <!-- 快速操作 - Apple 风格 -->
                                        <div class="grid grid-cols-2 gap-2 mb-8">
                                            <button @click="applyTemplateImage(selectedTemplate)"
                                                    class="flex items-center gap-2 p-3 bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-xl transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-[color:var(--brand-primary)]/30 dark:hover:border-[color:var(--brand-primary-light)]/30 hover:shadow-[0_4px_12px_rgba(var(--brand-primary-rgb),0.15)] dark:hover:shadow-[0_4px_12px_rgba(var(--brand-primary-light-rgb),0.2)] active:scale-[0.98]">
                                                <div class="w-8 h-8 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 rounded-lg flex-shrink-0">
                                                    <i class="fas fa-image text-sm text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                                </div>
                                                <span class="text-xs font-medium text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight">{{ t('onlyUseImage') }}</span>
                                            </button>
                                            <button @click="applyTemplateAudio(selectedTemplate)"
                                                    class="flex items-center gap-2 p-3 bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-xl transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-[color:var(--brand-primary)]/30 dark:hover:border-[color:var(--brand-primary-light)]/30 hover:shadow-[0_4px_12px_rgba(var(--brand-primary-rgb),0.15)] dark:hover:shadow-[0_4px_12px_rgba(var(--brand-primary-light-rgb),0.2)] active:scale-[0.98]">
                                                <div class="w-8 h-8 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 rounded-lg flex-shrink-0">
                                                    <i class="fas fa-music text-sm text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                                </div>
                                                <span class="text-xs font-medium text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight">{{ t('onlyUseAudio') }}</span>
                                            </button>
LiangLiu's avatar
LiangLiu committed
274
275
                                        </div>

LiangLiu's avatar
LiangLiu committed
276
277
278
279
280
281
                                        <!-- 操作按钮 - Apple 风格 -->
                                        <div class="space-y-2.5">
                                            <button @click="handleUseTemplate"
                                                    class="w-full rounded-full bg-[color:var(--brand-primary)] dark:bg-[color:var(--brand-primary-light)] border-0 px-6 py-3 text-[15px] font-semibold text-white hover:scale-[1.02] hover:shadow-[0_8px_24px_rgba(var(--brand-primary-rgb),0.35)] dark:hover:shadow-[0_8px_24px_rgba(var(--brand-primary-light-rgb),0.4)] active:scale-100 transition-all duration-200 ease-out tracking-tight flex items-center justify-center gap-2">
                                                <i class="fas fa-magic text-sm"></i>
                                                <span>{{ t('useTemplate') }}</span>
LiangLiu's avatar
LiangLiu committed
282
283
                                            </button>

LiangLiu's avatar
LiangLiu committed
284
285
286
287
                                            <button @click="copyShareLink(selectedTemplate?.task_id, 'template')"
                                                    class="w-full rounded-full bg-white dark:bg-[#3a3a3c] border border-black/8 dark:border-white/8 px-6 py-2.5 text-[15px] font-medium text-[#1d1d1f] dark:text-[#f5f5f7] hover:bg-white/80 dark:hover:bg-[#3a3a3c]/80 hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_4px_12px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_4px_12px_rgba(0,0,0,0.3)] active:scale-[0.98] transition-all duration-200 tracking-tight flex items-center justify-center gap-2">
                                                <i class="fas fa-share-alt text-sm"></i>
                                                <span>{{ t('shareTemplate') }}</span>
LiangLiu's avatar
LiangLiu committed
288
289
                                            </button>

LiangLiu's avatar
LiangLiu committed
290
291
292
293
                                            <button @click="showDetails = !showDetails"
                                                    class="w-full rounded-full bg-white dark:bg-[#3a3a3c] border border-black/8 dark:border-white/8 px-6 py-2.5 text-[15px] font-medium text-[#1d1d1f] dark:text-[#f5f5f7] hover:bg-white/80 dark:hover:bg-[#3a3a3c]/80 hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_4px_12px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_4px_12px_rgba(0,0,0,0.3)] active:scale-[0.98] transition-all duration-200 tracking-tight flex items-center justify-center gap-2">
                                                <i :class="showDetails ? 'fas fa-chevron-up' : 'fas fa-info-circle'" class="text-sm"></i>
                                                <span>{{ showDetails ? t('hideDetails') : t('showDetails') }}</span>
LiangLiu's avatar
LiangLiu committed
294
295
296
                                            </button>
                                        </div>

LiangLiu's avatar
LiangLiu committed
297
298
299
300
301
302
303
304
305
306
                                        <!-- 技术信息 - Apple 风格 -->
                                        <div class="text-center pt-6 mt-6 border-t border-black/8 dark:border-white/8">
                                            <a href="https://github.com/ModelTC/LightX2V"
                                               target="_blank"
                                               rel="noopener noreferrer"
                                               class="inline-flex items-center gap-2 text-sm text-[#86868b] dark:text-[#98989d] hover:text-[color:var(--brand-primary)] dark:hover:text-[color:var(--brand-primary-light)] transition-colors tracking-tight">
                                                <i class="fab fa-github text-base"></i>
                                                <span>{{ t('poweredByLightX2V') }}</span>
                                                <i class="fas fa-external-link-alt text-xs"></i>
                                            </a>
LiangLiu's avatar
LiangLiu committed
307
308
309
310
311
                                        </div>
                                    </div>
                                </div>
                            </div>

LiangLiu's avatar
LiangLiu committed
312
313
314
315
316
317
318
                            <!-- 详细信息面板 - Apple 风格 -->
                            <div v-if="showDetails && selectedTemplate" class="bg-[#f5f5f7] dark:bg-[#1c1c1e] border-t border-black/8 dark:border-white/8 py-12">
                                <div class="max-w-6xl mx-auto px-8">
                                    <!-- 输入素材标题 - Apple 风格 -->
                                    <h2 class="text-2xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] flex items-center justify-center gap-3 mb-8 tracking-tight">
                                        <i class="fas fa-upload text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                        <span>{{ t('inputMaterials') }}</span>
LiangLiu's avatar
LiangLiu committed
319
320
                                    </h2>

LiangLiu's avatar
LiangLiu committed
321
322
323
324
325
326
327
328
329
330
                                    <!-- 三个并列的分块卡片 - Apple 风格 -->
                                    <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
                                        <!-- 图片卡片 - Apple 风格 -->
                                        <div class="bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-2xl overflow-hidden transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_8px_24px_rgba(0,0,0,0.3)]">
                                            <!-- 卡片头部 -->
                                            <div class="flex items-center justify-between px-5 py-4 bg-[color:var(--brand-primary)]/5 dark:bg-[color:var(--brand-primary-light)]/10 border-b border-black/8 dark:border-white/8">
                                                <div class="flex items-center gap-3">
                                                    <i class="fas fa-image text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                                    <h3 class="text-base font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight">{{ t('image') }}</h3>
                                                </div>
LiangLiu's avatar
LiangLiu committed
331
332
                                                <button v-if="selectedTemplate?.inputs?.input_image"
                                                        @click="applyTemplateImage(selectedTemplate)"
LiangLiu's avatar
LiangLiu committed
333
                                                        class="w-8 h-8 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 border border-[color:var(--brand-primary)]/20 dark:border-[color:var(--brand-primary-light)]/20 text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] rounded-lg transition-all duration-200 hover:scale-110 active:scale-100"
LiangLiu's avatar
LiangLiu committed
334
                                                        :title="t('applyImage')">
LiangLiu's avatar
LiangLiu committed
335
                                                    <i class="fas fa-magic text-xs"></i>
LiangLiu's avatar
LiangLiu committed
336
337
                                                </button>
                                            </div>
LiangLiu's avatar
LiangLiu committed
338
339
340
341
342
343
344
                                            <!-- 卡片内容 -->
                                            <div class="p-6 min-h-[200px]">
                                                <div v-if="getImageMaterials().length > 0">
                                                    <div v-for="[inputName, url] in getImageMaterials()" :key="inputName"
                                                         class="rounded-xl overflow-hidden border border-black/8 dark:border-white/8 cursor-pointer hover:border-[color:var(--brand-primary)]/50 dark:hover:border-[color:var(--brand-primary-light)]/50 transition-all duration-200"
                                                         @click="showImageZoom(url)">
                                                        <img :src="url" :alt="inputName" class="w-full h-auto object-contain">
LiangLiu's avatar
LiangLiu committed
345
346
                                                    </div>
                                                </div>
LiangLiu's avatar
LiangLiu committed
347
348
349
350
                                                <div v-else class="flex flex-col items-center justify-center h-[150px]">
                                                    <i class="fas fa-image text-3xl text-[#86868b]/30 dark:text-[#98989d]/30 mb-3"></i>
                                                    <p class="text-sm text-[#86868b] dark:text-[#98989d] tracking-tight">{{ t('noImage') }}</p>
                                                </div>
LiangLiu's avatar
LiangLiu committed
351
352
353
                                            </div>
                                        </div>

LiangLiu's avatar
LiangLiu committed
354
355
356
357
358
359
360
361
                                        <!-- 音频卡片 - Apple 风格 -->
                                        <div class="bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-2xl overflow-hidden transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_8px_24px_rgba(0,0,0,0.3)]">
                                            <!-- 卡片头部 -->
                                            <div class="flex items-center justify-between px-5 py-4 bg-[color:var(--brand-primary)]/5 dark:bg-[color:var(--brand-primary-light)]/10 border-b border-black/8 dark:border-white/8">
                                                <div class="flex items-center gap-3">
                                                    <i class="fas fa-music text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                                    <h3 class="text-base font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight">{{ t('audio') }}</h3>
                                                </div>
LiangLiu's avatar
LiangLiu committed
362
363
                                                <button v-if="selectedTemplate?.inputs?.input_audio"
                                                        @click="applyTemplateAudio(selectedTemplate)"
LiangLiu's avatar
LiangLiu committed
364
                                                        class="w-8 h-8 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 border border-[color:var(--brand-primary)]/20 dark:border-[color:var(--brand-primary-light)]/20 text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] rounded-lg transition-all duration-200 hover:scale-110 active:scale-100"
LiangLiu's avatar
LiangLiu committed
365
                                                        :title="t('applyAudio')">
LiangLiu's avatar
LiangLiu committed
366
                                                    <i class="fas fa-magic text-xs"></i>
LiangLiu's avatar
LiangLiu committed
367
368
                                                </button>
                                            </div>
LiangLiu's avatar
LiangLiu committed
369
370
371
372
                                            <!-- 卡片内容 -->
                                            <div class="p-6 min-h-[200px]">
                                                <div v-if="getAudioMaterials().length > 0" class="space-y-4">
                                                    <div v-for="[inputName, url] in getAudioMaterials()" :key="inputName">
LiangLiu's avatar
LiangLiu committed
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
                                                        <!-- 音频播放器卡片 - Apple 风格 -->
                                                        <div class="bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-xl transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_4px_12px_rgba(0,0,0,0.08)] dark:hover:shadow-[0_4px_12px_rgba(0,0,0,0.2)] w-full p-4">
                                                            <div class="relative flex items-center mb-3">
                                                                <!-- 头像容器 -->
                                                                <div class="relative mr-3 flex-shrink-0">
                                                                    <!-- 透明白色头像 -->
                                                                    <div class="w-12 h-12 rounded-full bg-white/40 dark:bg-white/20 border border-white/30 dark:border-white/20 transition-all duration-200"></div>
                                                                    <!-- 播放/暂停按钮 -->
                                                                    <button
                                                                        @click="toggleAudioPlayback"
                                                                        class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-8 h-8 bg-[color:var(--brand-primary)]/90 dark:bg-[color:var(--brand-primary-light)]/90 rounded-full flex items-center justify-center text-white cursor-pointer hover:scale-110 transition-all duration-200 z-20 shadow-[0_2px_8px_rgba(var(--brand-primary-rgb),0.3)] dark:shadow-[0_2px_8px_rgba(var(--brand-primary-light-rgb),0.4)]"
                                                                    >
                                                                        <i :class="isPlaying ? 'fas fa-pause' : 'fas fa-play'" class="text-xs ml-0.5"></i>
                                                                    </button>
                                                                </div>

                                                                <!-- 音频信息 -->
                                                                <div class="flex-1 min-w-0">
                                                                    <div class="text-sm font-medium text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight truncate">
                                                                        {{ t('audio') }}
                                                                    </div>
                                                                </div>

                                                                <!-- 音频时长 -->
                                                                <div class="text-xs font-medium text-[#86868b] dark:text-[#98989d] tracking-tight flex-shrink-0">
                                                                    {{ formatAudioTime(currentTime) }} / {{ formatAudioTime(audioDuration) }}
                                                                </div>
                                                            </div>

                                                            <!-- 进度条 -->
                                                            <div class="flex items-center gap-2" v-if="audioDuration > 0">
                                                                <input
                                                                    type="range"
                                                                    :min="0"
                                                                    :max="audioDuration"
                                                                    :value="currentTime"
                                                                    @input="onProgressChange"
                                                                    @change="onProgressChange"
                                                                    @mousedown="isDragging = true"
                                                                    @mouseup="onProgressEnd"
                                                                    @touchstart="isDragging = true"
                                                                    @touchend="onProgressEnd"
                                                                    class="flex-1 h-1 bg-black/6 dark:bg-white/15 rounded-full appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:bg-[color:var(--brand-primary)] dark:[&::-webkit-slider-thumb]:bg-[color:var(--brand-primary-light)] [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:cursor-pointer"
                                                                />
                                                            </div>
                                                        </div>

                                                        <!-- 隐藏的音频元素 -->
                                                        <audio
                                                            ref="audioElement"
                                                            :src="url"
                                                            @loadedmetadata="onAudioLoaded"
                                                            @timeupdate="onTimeUpdate"
                                                            @ended="onAudioEnded"
                                                            @play="isPlaying = true"
                                                            @pause="isPlaying = false"
                                                            class="hidden"
                                                        ></audio>
LiangLiu's avatar
LiangLiu committed
431
432
433
434
435
                                                    </div>
                                                </div>
                                                <div v-else class="flex flex-col items-center justify-center h-[150px]">
                                                    <i class="fas fa-music text-3xl text-[#86868b]/30 dark:text-[#98989d]/30 mb-3"></i>
                                                    <p class="text-sm text-[#86868b] dark:text-[#98989d] tracking-tight">{{ t('noAudio') }}</p>
LiangLiu's avatar
LiangLiu committed
436
437
438
439
                                                </div>
                                            </div>
                                        </div>

LiangLiu's avatar
LiangLiu committed
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
                                        <!-- 提示词卡片 - Apple 风格 -->
                                        <div class="bg-white/80 dark:bg-[#2c2c2e]/80 backdrop-blur-[20px] border border-black/8 dark:border-white/8 rounded-2xl overflow-hidden transition-all duration-200 hover:bg-white dark:hover:bg-[#3a3a3c] hover:border-black/12 dark:hover:border-white/12 hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)] dark:hover:shadow-[0_8px_24px_rgba(0,0,0,0.3)]">
                                            <!-- 卡片头部 -->
                                            <div class="flex items-center justify-between px-5 py-4 bg-[color:var(--brand-primary)]/5 dark:bg-[color:var(--brand-primary-light)]/10 border-b border-black/8 dark:border-white/8">
                                                <div class="flex items-center gap-3">
                                                    <i class="fas fa-file-alt text-lg text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                                    <h3 class="text-base font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight">{{ t('prompt') }}</h3>
                                                </div>
                                                <div class="flex items-center gap-1">
                                                    <button v-if="selectedTemplate?.params?.prompt"
                                                            @click="copyPrompt(selectedTemplate?.params?.prompt)"
                                                            class="w-8 h-8 flex items-center justify-center bg-[#86868b]/10 dark:bg-[#98989d]/15 border border-[#86868b]/20 dark:border-[#98989d]/20 text-[#86868b] dark:text-[#98989d] rounded-lg transition-all duration-200 hover:scale-110 active:scale-100"
                                                            :title="t('copy')">
                                                        <i class="fas fa-copy text-xs"></i>
                                                    </button>
                                                    <button v-if="selectedTemplate?.params?.prompt"
                                                            @click="applyTemplatePrompt(selectedTemplate)"
                                                            class="w-8 h-8 flex items-center justify-center bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 border border-[color:var(--brand-primary)]/20 dark:border-[color:var(--brand-primary-light)]/20 text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] rounded-lg transition-all duration-200 hover:scale-110 active:scale-100"
                                                            :title="t('applyPrompt')">
                                                        <i class="fas fa-magic text-xs"></i>
                                                    </button>
                                                </div>
LiangLiu's avatar
LiangLiu committed
462
                                            </div>
LiangLiu's avatar
LiangLiu committed
463
464
465
466
467
468
469
470
471
                                            <!-- 卡片内容 -->
                                            <div class="p-6 min-h-[200px]">
                                                <div v-if="selectedTemplate?.params?.prompt" class="bg-white/50 dark:bg-[#1e1e1e]/50 backdrop-blur-[10px] border border-black/6 dark:border-white/6 rounded-xl p-4">
                                                    <p class="text-sm text-[#1d1d1f] dark:text-[#f5f5f7] leading-relaxed tracking-tight break-words">{{ selectedTemplate.params.prompt }}</p>
                                                </div>
                                                <div v-else class="flex flex-col items-center justify-center h-[150px]">
                                                    <i class="fas fa-file-alt text-3xl text-[#86868b]/30 dark:text-[#98989d]/30 mb-3"></i>
                                                    <p class="text-sm text-[#86868b] dark:text-[#98989d] tracking-tight">{{ t('noPrompt') }}</p>
                                                </div>
LiangLiu's avatar
LiangLiu committed
472
473
474
475
476
477
478
479
480
481
482
483
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
</template>

<style scoped>
LiangLiu's avatar
LiangLiu committed
484
485
/* 所有样式已通过 Tailwind CSS 的 dark: 前缀在 template 中定义 */
/* Apple 风格极简黑白设计 */
LiangLiu's avatar
LiangLiu committed
486
</style>