Share.vue 28.5 KB
Newer Older
LiangLiu's avatar
LiangLiu committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup>
import { ref, onMounted, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import topMenu from '../components/TopBar.vue'
import Loading from '../components/Loading.vue'
import {
    isLoading,
    selectedTaskId,
    getCurrentForm,
    setCurrentImagePreview,
    setCurrentAudioPreview,
    getTemplateFileUrl,
    isCreationAreaExpanded,
    switchToCreateView,
    showAlert,
LiangLiu's avatar
LiangLiu committed
17
18
    login,
    copyPrompt
LiangLiu's avatar
LiangLiu committed
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
} from '../utils/other'

const { t } = useI18n()
const route = useRoute()
const router = useRouter()

const shareId = computed(() => route.params.shareId)
const shareData = ref(null)
const error = ref(null)
const videoUrl = ref('')
const inputUrls = ref({})
const showDetails = ref(false)
const videoLoading = ref(false)
const videoError = ref(false)

// 获取分享数据
const fetchShareData = async () => {
    try {
        const response = await fetch(`/api/v1/share/${shareId.value}`)

        if (!response.ok) {
            throw new Error('分享不存在或已过期')
        }

        const data = await response.json()
        shareData.value = data

        // 设置视频URL
        if (data.output_video_url) {
            videoUrl.value = data.output_video_url
        }

        // 设置输入素材URL
        if (data.input_urls) {
            inputUrls.value = data.input_urls
            console.log('设置输入素材URL:', data.input_urls)
        }
    } catch (err) {
        error.value = err.message
        console.error('获取分享数据失败:', err)
    }
}

// 获取分享标题
const getShareTitle = () => {
    if (shareData.value?.share_type === 'task') {
        // 获取用户名,如果没有则显示默认文本
        const username = shareData.value?.username || '用户'
        return `${username}${t('userGeneratedVideo')}`
    }
    return t('templateVideo')
}

// 获取分享描述
const getShareDescription = () => {
    return t('description')
}

// 获取分享按钮文本
const getShareButtonText = () => {
    switch (shareData.value?.share_type) {
        case 'template':
            return t('useTemplate')
        default:
            return t('createSimilar')
    }
}

// 视频事件处理
const onVideoLoadStart = () => {
    videoLoading.value = true
    videoError.value = false
}

const onVideoCanPlay = () => {
    videoLoading.value = false
    videoError.value = false
}

const onVideoError = () => {
    videoLoading.value = false
    videoError.value = true
}

// 获取图片素材
const getImageMaterials = () => {
    if (!inputUrls.value) return []
    const imageMaterials = Object.entries(inputUrls.value).filter(([name, url]) =>
        name.includes('image') && url
    )
    console.log('图片素材:', imageMaterials)
    return imageMaterials
}

// 获取音频素材
const getAudioMaterials = () => {
    if (!inputUrls.value) return []
    const audioMaterials = Object.entries(inputUrls.value).filter(([name, url]) =>
        name.includes('audio') && url
    )
    console.log('音频素材:', audioMaterials)
    return audioMaterials
}

// 处理图片加载错误
const handleImageError = (event, inputName, url) => {
    console.log('图片加载失败:', inputName, url)
    console.log('错误详情:', event)
    console.log('图片元素:', event.target)

    // 尝试移除crossorigin属性重新加载
    const img = event.target
    if (img.crossOrigin) {
        console.log('尝试移除crossorigin属性重新加载')
        img.crossOrigin = null
        img.src = url + '?retry=' + Date.now()
    }
}

// 做同款功能
const createSimilar = async () => {
    const token = localStorage.getItem('accessToken')
    if (!token) {
        // 未登录,跳转到登录页面,并保存分享ID
        localStorage.setItem('shareData', JSON.stringify({ shareId: shareId.value }))
        login()
        return
    }

    if (!shareData.value) {
        showAlert('分享数据不完整', 'danger')
        return
    }

    console.log('使用分享数据:', shareData.value)

    try {
        // 先设置任务类型
        selectedTaskId.value = shareData.value.task_type

        // 获取当前表单
        const currentForm = getCurrentForm()

        // 设置表单数据
        currentForm.prompt = shareData.value.prompt || ''
        currentForm.negative_prompt = shareData.value.negative_prompt || ''
        currentForm.seed = 42 // 默认种子
        currentForm.model_cls = shareData.value.model_cls || ''
        currentForm.stage = shareData.value.stage || 'single_stage'

        // 如果有输入图片,先设置URL,延迟加载文件
        if (shareData.value.inputs && shareData.value.inputs.input_image) {
            let imageUrl
            if (shareData.value.share_type === 'template') {
                // 对于模板,使用模板文件URL
                imageUrl = getTemplateFileUrl(shareData.value.inputs.input_image, 'images')
            } else {
                // 对于任务,使用分享数据中的URL
                imageUrl = shareData.value.input_urls?.input_image || shareData.value.input_urls?.[Object.keys(shareData.value.input_urls).find(key => key.includes('image'))]
            }

            if (imageUrl) {
                currentForm.imageUrl = imageUrl
                setCurrentImagePreview(imageUrl) // 直接使用URL作为预览
                console.log('分享输入图片:', imageUrl)

                // 异步加载图片文件(不阻塞UI)
                setTimeout(async () => {
                    try {
                        const imageResponse = await fetch(imageUrl)
                        if (imageResponse.ok) {
                            const blob = await imageResponse.blob()
                            const filename = shareData.value.inputs.input_image
                            const file = new File([blob], filename, { type: blob.type })
                            currentForm.imageFile = file
                            console.log('分享图片文件已加载')
                        }
                    } catch (error) {
                        console.warn('Failed to load share image file:', error)
                    }
                }, 100)
            }
        }

        // 如果有输入音频,先设置URL,延迟加载文件
        if (shareData.value.inputs && shareData.value.inputs.input_audio) {
            let audioUrl
            if (shareData.value.share_type === 'template') {
                // 对于模板,使用模板文件URL
                audioUrl = getTemplateFileUrl(shareData.value.inputs.input_audio, 'audios')
            } else {
                // 对于任务,使用分享数据中的URL
                audioUrl = shareData.value.input_urls?.input_audio || shareData.value.input_urls?.[Object.keys(shareData.value.input_urls).find(key => key.includes('audio'))]
            }

            if (audioUrl) {
                currentForm.audioUrl = audioUrl
                setCurrentAudioPreview(audioUrl) // 直接使用URL作为预览
                console.log('分享输入音频:', audioUrl)

                // 异步加载音频文件(不阻塞UI)
                setTimeout(async () => {
                    try {
                        const audioResponse = await fetch(audioUrl)
                        if (audioResponse.ok) {
                            const blob = await audioResponse.blob()
                            const filename = shareData.value.inputs.input_audio

                            // 根据文件扩展名确定正确的MIME类型
                            let mimeType = blob.type
                            if (!mimeType || mimeType === 'application/octet-stream') {
                                const ext = filename.toLowerCase().split('.').pop()
                                const mimeTypes = {
                                    'mp3': 'audio/mpeg',
                                    'wav': 'audio/wav',
                                    'mp4': 'audio/mp4',
                                    'aac': 'audio/aac',
                                    'ogg': 'audio/ogg',
                                    'm4a': 'audio/mp4'
                                }
                                mimeType = mimeTypes[ext] || 'audio/mpeg'
                            }

                            const file = new File([blob], filename, { type: mimeType })
                            currentForm.audioFile = file
                            console.log('分享音频文件已加载')
                            // 使用FileReader生成data URL,与正常上传保持一致
                            const reader = new FileReader()
                            reader.onload = (e) => {
                                setCurrentAudioPreview(e.target.result)
                                console.log('分享音频预览已设置:', e.target.result.substring(0, 50) + '...')
                            }
                            reader.readAsDataURL(file)
                        }
                    } catch (error) {
                        console.warn('Failed to load share audio file:', error)
                    }
                }, 100)
            }
        }

        // 切换到创建视图
        isCreationAreaExpanded.value = true
        switchToCreateView()

        showAlert(`已应用分享数据`, 'success')
    } catch (error) {
        console.error('应用分享数据失败:', error)
        showAlert(`应用分享数据失败: ${error.message}`, 'danger')
    }
}

onMounted(async () => {
    await fetchShareData()
    isLoading.value = false
})
</script>

<template>
LiangLiu's avatar
LiangLiu committed
278
279
    <!-- Apple 极简风格分享页面 -->
    <div class="min-h-screen w-full bg-[#f5f5f7] dark:bg-[#000000]">
LiangLiu's avatar
LiangLiu committed
280
281
282
283
        <!-- TopBar -->
        <topMenu />

        <!-- 主要内容区域 -->
LiangLiu's avatar
LiangLiu committed
284
285
286
287
288
289
        <div class="w-full min-h-[calc(100vh-80px)] overflow-y-auto main-scrollbar">
            <!-- 错误状态 - Apple 风格 -->
            <div v-if="error" class="flex items-center justify-center min-h-[60vh] px-6">
                <div class="text-center max-w-md">
                    <div class="inline-flex items-center justify-center w-20 h-20 bg-red-500/10 dark:bg-red-400/10 rounded-3xl mb-6">
                        <i class="fas fa-exclamation-triangle text-3xl text-red-500 dark:text-red-400"></i>
LiangLiu's avatar
LiangLiu committed
290
                    </div>
LiangLiu's avatar
LiangLiu committed
291
292
293
294
295
296
                    <h2 class="text-2xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] mb-4 tracking-tight">{{ t('shareNotFound') }}</h2>
                    <p class="text-base text-[#86868b] dark:text-[#98989d] mb-8 tracking-tight">{{ error }}</p>
                    <button @click="router.push('/')"
                            class="inline-flex items-center justify-center gap-2 px-8 py-3 bg-[color:var(--brand-primary)] dark:bg-[color:var(--brand-primary-light)] text-white rounded-full text-[15px] font-semibold tracking-tight transition-all duration-200 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">
                        <i class="fas fa-home text-sm"></i>
                        <span>{{ t('backToHome') }}</span>
LiangLiu's avatar
LiangLiu committed
297
298
299
300
                    </button>
                </div>
            </div>

LiangLiu's avatar
LiangLiu committed
301
302
            <!-- 分享内容 - Apple 风格 -->
            <div v-else-if="shareData" class="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-16 w-full max-w-7xl mx-auto px-6 sm:px-8 lg:px-12 py-12 lg:py-16 items-center">
LiangLiu's avatar
LiangLiu committed
303
                <!-- 左侧视频区域 -->
LiangLiu's avatar
LiangLiu committed
304
305
306
307
308
309
310
                <div class="flex justify-center items-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)] relative">
                        <!-- 视频加载占位符 - Apple 风格 -->
                        <div v-if="!videoUrl" class="w-full h-full flex flex-col items-center justify-center bg-[#f5f5f7] dark:bg-[#1c1c1e]">
                            <div class="relative w-12 h-12 mb-6">
                                <div class="absolute inset-0 rounded-full border-2 border-black/8 dark:border-white/8"></div>
                                <div class="absolute inset-0 rounded-full border-2 border-transparent border-t-[color:var(--brand-primary)] dark:border-t-[color:var(--brand-primary-light)] animate-spin"></div>
LiangLiu's avatar
LiangLiu committed
311
                            </div>
LiangLiu's avatar
LiangLiu committed
312
                            <p class="text-sm font-medium text-[#86868b] dark:text-[#98989d] tracking-tight">{{ t('loadingVideo') }}...</p>
LiangLiu's avatar
LiangLiu committed
313
314
315
316
317
318
                        </div>

                        <!-- 视频播放器 -->
                        <video
                            v-if="videoUrl"
                            :src="videoUrl"
LiangLiu's avatar
LiangLiu committed
319
                            class="w-full h-full object-contain"
LiangLiu's avatar
LiangLiu committed
320
321
322
323
324
325
326
327
328
329
                            controls
                            autoplay
                            loop
                            preload="metadata"
                            @loadstart="onVideoLoadStart"
                            @canplay="onVideoCanPlay"
                            @error="onVideoError">
                            {{ t('browserNotSupported') }}
                        </video>

LiangLiu's avatar
LiangLiu committed
330
331
332
333
334
335
                        <!-- 视频错误状态 - Apple 风格 -->
                        <div v-if="videoError" class="w-full h-full flex flex-col items-center justify-center bg-[#fef2f2] dark:bg-[#2c1b1b]">
                            <div class="w-16 h-16 rounded-full bg-red-500/10 dark:bg-red-400/10 flex items-center justify-center mb-4">
                                <i class="fas fa-exclamation-triangle text-3xl text-red-500 dark:text-red-400"></i>
                            </div>
                            <p class="text-sm font-medium text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight">{{ t('videoNotAvailable') }}</p>
LiangLiu's avatar
LiangLiu committed
336
337
338
339
                        </div>
                    </div>
                </div>

LiangLiu's avatar
LiangLiu committed
340
341
342
343
344
                <!-- 右侧信息区域 - Apple 风格 -->
                <div class="flex items-center justify-center">
                    <div class="w-full max-w-[500px]">
                        <!-- 标题 - Apple 风格 -->
                        <h1 class="text-4xl sm:text-5xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] mb-4 tracking-tight leading-tight">
LiangLiu's avatar
LiangLiu committed
345
346
347
                            {{ getShareTitle() }}
                        </h1>

LiangLiu's avatar
LiangLiu committed
348
349
                        <!-- 描述 - Apple 风格 -->
                        <p class="text-lg text-[#86868b] dark:text-[#98989d] mb-8 leading-relaxed tracking-tight">
LiangLiu's avatar
LiangLiu committed
350
351
352
                            {{ getShareDescription() }}
                        </p>

LiangLiu's avatar
LiangLiu committed
353
354
355
356
357
358
359
                        <!-- 特性列表 - Apple 风格 -->
                        <div class="grid grid-cols-1 gap-3 mb-8">
                            <div class="flex items-center gap-3 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-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)]">
                                <div class="w-10 h-10 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-rocket text-base text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                </div>
                                <span class="text-sm font-medium text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight">{{ t('latestAIModel') }}</span>
LiangLiu's avatar
LiangLiu committed
360
                            </div>
LiangLiu's avatar
LiangLiu committed
361
362
363
364
365
                            <div class="flex items-center gap-3 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-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)]">
                                <div class="w-10 h-10 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-bolt text-base text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                </div>
                                <span class="text-sm font-medium text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight">{{ t('oneClickReplication') }}</span>
LiangLiu's avatar
LiangLiu committed
366
                            </div>
LiangLiu's avatar
LiangLiu committed
367
368
369
370
371
                            <div class="flex items-center gap-3 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-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)]">
                                <div class="w-10 h-10 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-user-cog text-base text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                </div>
                                <span class="text-sm font-medium text-[#1d1d1f] dark:text-[#f5f5f7] tracking-tight">{{ t('customizableCharacter') }}</span>
LiangLiu's avatar
LiangLiu committed
372
373
374
                            </div>
                        </div>

LiangLiu's avatar
LiangLiu committed
375
376
377
378
379
380
                        <!-- 操作按钮 - Apple 风格 -->
                        <div class="space-y-3 mb-8">
                            <button @click="createSimilar"
                                    class="w-full rounded-full bg-[color:var(--brand-primary)] dark:bg-[color:var(--brand-primary-light)] border-0 px-8 py-3.5 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>{{ getShareButtonText() }}</span>
LiangLiu's avatar
LiangLiu committed
381
382
383
                            </button>

                            <!-- 详细信息按钮 -->
LiangLiu's avatar
LiangLiu committed
384
385
386
387
                            <button @click="showDetails = !showDetails"
                                    class="w-full rounded-full bg-white dark:bg-[#3a3a3c] border border-black/8 dark:border-white/8 px-8 py-3 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
388
389
390
                            </button>
                        </div>

LiangLiu's avatar
LiangLiu committed
391
392
393
394
395
396
397
398
399
400
                        <!-- 技术信息 - Apple 风格 -->
                        <div class="text-center pt-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
401
402
403
404
405
                        </div>
                    </div>
                </div>
            </div>

LiangLiu's avatar
LiangLiu committed
406
407
408
409
410
411
412
            <!-- 详细信息面板 - Apple 风格 -->
            <div v-if="showDetails && shareData" class="w-full bg-white dark:bg-[#1c1c1e] border-t border-black/8 dark:border-white/8 py-16">
                <div class="max-w-6xl mx-auto px-6 sm:px-8 lg:px-12">
                    <!-- 输入素材标题 - Apple 风格 -->
                    <h2 class="text-2xl sm:text-3xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] flex items-center justify-center gap-3 mb-10 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
413
414
                    </h2>

LiangLiu's avatar
LiangLiu committed
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
                    <!-- 三个并列的分块卡片 - 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>
                            </div>
                            <!-- 卡片内容 -->
                            <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">
                                        <img :src="url" :alt="inputName"
                                             class="w-full h-auto object-contain"
LiangLiu's avatar
LiangLiu committed
432
433
434
435
                                             @load="console.log('图片加载成功:', inputName, url)"
                                             @error="handleImageError($event, inputName, url)">
                                    </div>
                                </div>
LiangLiu's avatar
LiangLiu committed
436
437
438
439
                                <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
440
441
442
                            </div>
                        </div>

LiangLiu's avatar
LiangLiu committed
443
444
445
446
447
448
449
                        <!-- 音频卡片 - 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>
LiangLiu's avatar
LiangLiu committed
450
451
                                </div>
                            </div>
LiangLiu's avatar
LiangLiu committed
452
453
454
455
456
457
458
459
460
461
462
                            <!-- 卡片内容 -->
                            <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">
                                        <audio :src="url" controls class="w-full rounded-xl"></audio>
                                    </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>
                                </div>
LiangLiu's avatar
LiangLiu committed
463
464
465
                            </div>
                        </div>

LiangLiu's avatar
LiangLiu committed
466
467
468
469
470
471
472
473
474
475
476
477
478
479
                        <!-- 提示词卡片 - 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>
                                <button v-if="shareData.prompt"
                                        @click="copyPrompt(shareData.prompt)"
                                        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('copy')">
                                    <i class="fas fa-copy text-xs"></i>
                                </button>
LiangLiu's avatar
LiangLiu committed
480
                            </div>
LiangLiu's avatar
LiangLiu committed
481
482
483
484
485
486
487
488
489
                            <!-- 卡片内容 -->
                            <div class="p-6 min-h-[200px]">
                                <div v-if="shareData.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">{{ shareData.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
490
491
492
493
494
495
496
497
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

LiangLiu's avatar
LiangLiu committed
498
499
    <!-- 全局路由跳转Loading覆盖层 - Apple 风格 -->
    <div v-show="isLoading" class="fixed inset-0 bg-[#f5f5f7] dark:bg-[#000000] flex items-center justify-center z-[9999]">
LiangLiu's avatar
LiangLiu committed
500
501
502
503
504
        <Loading />
    </div>
</template>

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