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

        <!-- 主要内容区域 -->
        <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">
LiangLiu's avatar
LiangLiu committed
287
                <div class="text-center max-w-md">
LiangLiu's avatar
LiangLiu committed
288
289
                    <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
                    <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>
LiangLiu's avatar
LiangLiu committed
293
                    <button @click="router.push('/')"
LiangLiu's avatar
LiangLiu committed
294
295
                            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>
LiangLiu's avatar
LiangLiu committed
296
                        <span>{{ t('backToHome') }}</span>
LiangLiu's avatar
LiangLiu committed
297
298
299
300
                    </button>
                </div>
            </div>

LiangLiu's avatar
LiangLiu committed
301
302
303
304
305
            <!-- 分享内容 - 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">
                <!-- 左侧视频区域 -->
                <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">
LiangLiu's avatar
LiangLiu committed
306
307
308
309
310
                        <!-- 视频加载占位符 - 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
                        <!-- 特性列表 - 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>
LiangLiu's avatar
LiangLiu committed
358
                                </div>
LiangLiu's avatar
LiangLiu committed
359
                                <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
                            <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>
LiangLiu's avatar
LiangLiu committed
364
                                </div>
LiangLiu's avatar
LiangLiu committed
365
                                <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
                            <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>
LiangLiu's avatar
LiangLiu committed
370
                                </div>
LiangLiu's avatar
LiangLiu committed
371
                                <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
                        <!-- 操作按钮 - Apple 风格 -->
                        <div class="space-y-3 mb-8">
LiangLiu's avatar
LiangLiu committed
377
                            <button @click="createSimilar"
LiangLiu's avatar
LiangLiu committed
378
                                    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">
LiangLiu's avatar
LiangLiu committed
379
380
                                <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
                            <button @click="showDetails = !showDetails"
LiangLiu's avatar
LiangLiu committed
385
                                    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">
LiangLiu's avatar
LiangLiu committed
386
387
                                <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
                        <!-- 技术信息 - Apple 风格 - 响应式 -->
LiangLiu's avatar
LiangLiu committed
392
                        <div class="text-center pt-4 sm:pt-6 border-t border-black/8 dark:border-white/8">
LiangLiu's avatar
LiangLiu committed
393
394
395
                            <a href="https://github.com/ModelTC/LightX2V"
                               target="_blank"
                               rel="noopener noreferrer"
LiangLiu's avatar
LiangLiu committed
396
397
                               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>
LiangLiu's avatar
LiangLiu committed
398
                                <span>{{ t('poweredByLightX2V') }}</span>
LiangLiu's avatar
LiangLiu committed
399
                                <i class="fas fa-external-link-alt text-xs"></i>
LiangLiu's avatar
LiangLiu committed
400
                            </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
            <!-- 详细信息面板 - 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">
LiangLiu's avatar
LiangLiu committed
411
412
                        <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
                    <!-- 三个并列的分块卡片 - Apple 风格 -->
                    <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
LiangLiu's avatar
LiangLiu committed
417
418
                        <!-- 图片卡片 - 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)]">
LiangLiu's avatar
LiangLiu committed
419
420
421
422
423
                            <!-- 卡片头部 -->
                            <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>
LiangLiu's avatar
LiangLiu committed
424
425
                                </div>
                            </div>
LiangLiu's avatar
LiangLiu committed
426
427
                            <!-- 卡片内容 -->
                            <div class="p-6 min-h-[200px]">
LiangLiu's avatar
LiangLiu committed
428
429
430
431
                                <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
                        <!-- 音频卡片 - Apple 风格 -->
LiangLiu's avatar
LiangLiu committed
444
                        <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)]">
LiangLiu's avatar
LiangLiu committed
445
446
447
448
449
                            <!-- 卡片头部 -->
                            <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
                            <!-- 卡片内容 -->
                            <div class="p-6 min-h-[200px]">
LiangLiu's avatar
LiangLiu committed
454
455
456
457
458
459
460
461
462
                                <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
                        <!-- 提示词卡片 - Apple 风格 -->
LiangLiu's avatar
LiangLiu committed
467
                        <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)]">
LiangLiu's avatar
LiangLiu committed
468
469
470
471
472
                            <!-- 卡片头部 -->
                            <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>
LiangLiu's avatar
LiangLiu committed
473
474
475
                                </div>
                                <button v-if="shareData.prompt"
                                        @click="copyPrompt(shareData.prompt)"
LiangLiu's avatar
LiangLiu committed
476
                                        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
477
                                        :title="t('copy')">
LiangLiu's avatar
LiangLiu committed
478
                                    <i class="fas fa-copy text-xs"></i>
LiangLiu's avatar
LiangLiu committed
479
                                </button>
LiangLiu's avatar
LiangLiu committed
480
                            </div>
LiangLiu's avatar
LiangLiu committed
481
482
                            <!-- 卡片内容 -->
                            <div class="p-6 min-h-[200px]">
LiangLiu's avatar
LiangLiu committed
483
484
485
486
487
488
489
                                <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
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
LiangLiu's avatar
LiangLiu committed
496
    </div>
LiangLiu's avatar
LiangLiu committed
497

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

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