MediaTemplate.vue 40.7 KB
Newer Older
LiangLiu's avatar
LiangLiu committed
1
<script setup>
LiangLiu's avatar
LiangLiu committed
2
import { ref, computed, watch } from 'vue'
LiangLiu's avatar
LiangLiu committed
3
4
5
6
import { useI18n } from 'vue-i18n'

const { t } = useI18n()

LiangLiu's avatar
LiangLiu committed
7
8
9
// 音频播放状态管理
const playingAudioId = ref(null)
const audioDurations = ref({})
LiangLiu's avatar
LiangLiu committed
10
11
// 图像加载失败状态
const imageLoadFailed = ref({})
LiangLiu's avatar
LiangLiu committed
12

LiangLiu's avatar
LiangLiu committed
13
14
15
16
17
18
19
20
21
22
23
24
import {
    getTemplateFileUrl,
    getHistoryImageUrl,
    goToTemplatePage,
    jumpToTemplatePage,
    getVisibleTemplatePages,
    selectImageHistory,
    selectImageTemplate,
    selectAudioHistory,
    selectAudioTemplate,
    previewAudioHistory,
    previewAudioTemplate,
LiangLiu's avatar
LiangLiu committed
25
26
    stopAudioPlayback,
    setAudioStopCallback,
LiangLiu's avatar
LiangLiu committed
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
    clearImageHistory,
    clearAudioHistory,
    templatePaginationInfo,
    templateCurrentPage,
    templatePageInput,
    showImageTemplates,
    showAudioTemplates,
    imageHistory,
    audioHistory,
    imageTemplates,
    audioTemplates,
    mediaModalTab,
    getImageHistory,
    getAudioHistory,
} from '../utils/other'
LiangLiu's avatar
LiangLiu committed
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

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

// 获取音频时长
const getAudioDuration = async (url, id) => {
    if (audioDurations.value[id]) return audioDurations.value[id]

    return new Promise((resolve) => {
        const audio = new Audio()
        audio.addEventListener('loadedmetadata', () => {
            audioDurations.value[id] = audio.duration
            resolve(audio.duration)
        })
        audio.addEventListener('error', () => {
            resolve(0)
        })
        audio.src = url
    })
}

// 处理音频预览播放/停止
const handleAudioPreview = async (item, isTemplate = false) => {
    const id = isTemplate ? `template_${item.filename}` : `history_${item.filename}`
    const url = isTemplate ? getTemplateFileUrl(item.filename, 'audios') : item.url

    // 如果当前正在播放这个音频,则停止
    if (playingAudioId.value === id) {
        playingAudioId.value = null
        stopAudioPlayback() // 调用停止音频播放函数
        return
    }

    // 停止其他正在播放的音频
    playingAudioId.value = null
    stopAudioPlayback() // 先停止当前播放的音频

    // 播放新音频
    try {
        // 设置停止回调,当音频停止时更新UI状态
        setAudioStopCallback(() => {
            playingAudioId.value = null
        })

        if (isTemplate) {
            previewAudioTemplate(item)
        } else {
            previewAudioHistory({ url })
        }
        playingAudioId.value = id

        // 获取音频时长
        await getAudioDuration(url, id)
    } catch (error) {
        console.error('音频播放失败:', error)
    }
}

// 检查是否正在播放
const isPlaying = (item, isTemplate = false) => {
    const id = isTemplate ? `template_${item.filename}` : `history_${item.filename}`
    return playingAudioId.value === id
}

// 获取音频时长显示
const getDurationDisplay = (item, isTemplate = false) => {
    const id = isTemplate ? `template_${item.filename}` : `history_${item.filename}`
    return formatDuration(audioDurations.value[id])
}

LiangLiu's avatar
LiangLiu committed
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// 获取音频对应的图像URL
const getAudioImageUrl = (item, isTemplate = false) => {
    if (isTemplate) {
        // 对于模板,如果有 input_image 字段,获取对应的图像URL
        if (item.inputs && item.inputs.input_image) {
            return getTemplateFileUrl(item.inputs.input_image, 'images')
        }
        // 如果没有 input_image,尝试使用相同的 filename(可能在同一目录下)
        // 这里假设音频文件名和图像文件名可能相同或有关联
        return null
    } else {
        // 对于历史记录,可能没有对应的图像,返回 null
        return null
    }
}

// 检查是否有对应的图像
const hasAudioImage = (item, isTemplate = false) => {
    if (isTemplate) {
        return item.inputs && item.inputs.input_image
    }
    return false
}

LiangLiu's avatar
LiangLiu committed
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
// 预加载音频时长
const preloadAudioDurations = (items, isTemplate = false) => {
    items.forEach(item => {
        const id = isTemplate ? `template_${item.filename}` : `history_${item.filename}`
        const url = isTemplate ? getTemplateFileUrl(item.filename, 'audios') : item.url

        // 如果已经有时长数据,跳过
        if (audioDurations.value[id] || !url) return

        // 异步加载时长
        getAudioDuration(url, id)
    })
}

// 监听音频历史和模板列表变化,预加载时长
watch(audioHistory, (newHistory) => {
    if (newHistory && newHistory.length > 0) {
        preloadAudioDurations(newHistory, false)
    }
}, { immediate: true, deep: true })

watch(audioTemplates, (newTemplates) => {
    if (newTemplates && newTemplates.length > 0) {
        preloadAudioDurations(newTemplates, true)
    }
}, { immediate: true, deep: true })
LiangLiu's avatar
LiangLiu committed
167
168
169
170
</script>

<template>

LiangLiu's avatar
LiangLiu committed
171
                        <!-- 模板选择浮窗 - Apple 极简风格 -->
LiangLiu's avatar
LiangLiu committed
172
173
                        <div v-cloak>
                            <div v-if="showImageTemplates || showAudioTemplates"
LiangLiu's avatar
LiangLiu committed
174
                                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
175
                                @click="showImageTemplates = false; showAudioTemplates = false">
LiangLiu's avatar
LiangLiu committed
176
                                <div class="bg-white/95 dark:bg-[#1e1e1e]/95 backdrop-blur-[40px] backdrop-saturate-[180%] border border-black/10 dark:border-white/10 rounded-3xl px-6 sm:px-10 py-6 sm:py-8 max-w-4xl w-full h-[90vh] overflow-hidden shadow-[0_20px_60px_rgba(0,0,0,0.2)] dark:shadow-[0_20px_60px_rgba(0,0,0,0.6)] flex flex-col"
LiangLiu's avatar
LiangLiu committed
177
                                    @click.stop>
LiangLiu's avatar
LiangLiu committed
178
179
180
                                    <!-- 浮窗头部 - Apple 风格 -->
                                    <div class="flex items-center justify-between mb-8">
                                        <h3 class="text-2xl font-semibold text-[#1d1d1f] dark:text-[#f5f5f7] flex items-center gap-3 tracking-tight">
LiangLiu's avatar
LiangLiu committed
181
                                                <i v-if="showImageTemplates"
LiangLiu's avatar
LiangLiu committed
182
                                                    class="fas fa-image text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
LiangLiu's avatar
LiangLiu committed
183
                                                <i v-if="showAudioTemplates"
LiangLiu's avatar
LiangLiu committed
184
                                                    class="fas fa-music text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
LiangLiu's avatar
LiangLiu committed
185
186
187
                                                {{ showImageTemplates ? t('imageTemplates') : t('audioTemplates') }}
                                        </h3>
                                        <button @click="showImageTemplates = false; showAudioTemplates = false"
LiangLiu's avatar
LiangLiu committed
188
189
                                                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-[#1d1d1f] dark:hover:text-[#f5f5f7] hover:bg-white dark:hover:bg-[#3a3a3c] rounded-full transition-all duration-200 hover:scale-110 active:scale-100">
                                            <i class="fas fa-times text-base"></i>
LiangLiu's avatar
LiangLiu committed
190
191
192
                                        </button>
                                    </div>

LiangLiu's avatar
LiangLiu committed
193
194
                                    <!-- 标签页切换 - Apple 风格 -->
                                    <div class="flex gap-2 mb-8">
LiangLiu's avatar
LiangLiu committed
195
196
                                            <button
                                                @click="mediaModalTab = 'history'; showImageTemplates && getImageHistory(); showAudioTemplates && getAudioHistory()"
LiangLiu's avatar
LiangLiu committed
197
198
199
                                                class="px-5 py-2.5 text-sm font-medium rounded-full transition-all duration-200 tracking-tight" :class="mediaModalTab === 'history'
                                                    ? 'bg-[color:var(--brand-primary)] dark:bg-[color:var(--brand-primary-light)] text-white shadow-[0_4px_12px_rgba(var(--brand-primary-rgb),0.25)] dark:shadow-[0_4px_12px_rgba(var(--brand-primary-light-rgb),0.3)]'
                                                    : 'bg-white/80 dark:bg-[#2c2c2e]/80 border border-black/8 dark:border-white/8 text-[#86868b] dark:text-[#98989d] hover:bg-white dark:hover:bg-[#3a3a3c] hover:text-[#1d1d1f] dark:hover:text-[#f5f5f7]'">
LiangLiu's avatar
LiangLiu committed
200
201
202
203
                                            <i class="fas fa-history mr-2"></i>
                                                {{ t('history') }}
                                        </button>
                                        <button @click="mediaModalTab = 'templates'"
LiangLiu's avatar
LiangLiu committed
204
205
206
                                                class="px-5 py-2.5 text-sm font-medium rounded-full transition-all duration-200 tracking-tight" :class="mediaModalTab === 'templates'
                                                    ? 'bg-[color:var(--brand-primary)] dark:bg-[color:var(--brand-primary-light)] text-white shadow-[0_4px_12px_rgba(var(--brand-primary-rgb),0.25)] dark:shadow-[0_4px_12px_rgba(var(--brand-primary-light-rgb),0.3)]'
                                                    : 'bg-white/80 dark:bg-[#2c2c2e]/80 border border-black/8 dark:border-white/8 text-[#86868b] dark:text-[#98989d] hover:bg-white dark:hover:bg-[#3a3a3c] hover:text-[#1d1d1f] dark:hover:text-[#f5f5f7]'">
LiangLiu's avatar
LiangLiu committed
207
208
209
210
211
                                            <i class="fas fa-layer-group mr-2"></i>
                                                {{ t('templates') }}
                                        </button>
                                    </div>

LiangLiu's avatar
LiangLiu committed
212
                                    <!-- 图片历史记录 - Apple 风格 -->
LiangLiu's avatar
LiangLiu committed
213
                                          <div v-if="showImageTemplates && mediaModalTab === 'history'"
LiangLiu's avatar
LiangLiu committed
214
                                             class="overflow-y-auto flex-1 max-h-[60vh] main-scrollbar pr-6 pl-1">
LiangLiu's avatar
LiangLiu committed
215
216
217
                                            <div v-if="imageHistory.length === 0"
                                                class="flex flex-col items-center justify-center py-12 text-center">
                                                <div
LiangLiu's avatar
LiangLiu committed
218
219
                                                    class="w-16 h-16 bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 rounded-full flex items-center justify-center mb-4">
                                                <i class="fas fa-history text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] text-2xl"></i>
LiangLiu's avatar
LiangLiu committed
220
                                            </div>
LiangLiu's avatar
LiangLiu committed
221
222
                                                <p class="text-[#1d1d1f] dark:text-[#f5f5f7] text-lg font-medium mb-2 tracking-tight">{{ t('noHistoryRecords') }}</p>
                                                <p class="text-[#86868b] dark:text-[#98989d] text-sm tracking-tight">{{ t('imageHistoryAutoSave') }}</p>
LiangLiu's avatar
LiangLiu committed
223
                                        </div>
LiangLiu's avatar
LiangLiu committed
224
225
226
                                        <div v-else class="space-y-4 pt-2">
                                            <div class="flex items-center justify-between mb-6 px-1">
                                                    <span class="text-sm text-[#86868b] dark:text-[#98989d] tracking-tight">{{ t('total') }} {{ imageHistory.length }}
LiangLiu's avatar
LiangLiu committed
227
228
                                                        {{ t('records') }}</span>
                                                <button @click="clearImageHistory"
LiangLiu's avatar
LiangLiu committed
229
                                                        class="text-xs text-red-500 dark:text-red-400 hover:text-red-600 dark:hover:text-red-300 transition-colors flex items-center gap-1.5 tracking-tight"
LiangLiu's avatar
LiangLiu committed
230
231
232
233
234
                                                        :title="t('clearHistory')">
                                                    <i class="fas fa-trash"></i>
                                                        {{ t('clear') }}
                                                </button>
                                            </div>
LiangLiu's avatar
LiangLiu committed
235
                                            <div class="columns-2 md:columns-3 lg:columns-4 xl:columns-5 gap-4 px-1">
LiangLiu's avatar
LiangLiu committed
236
237
                                                <div v-for="(history, index) in imageHistory" :key="index"
                                                    @click="selectImageHistory(history)"
LiangLiu's avatar
LiangLiu committed
238
                                                    class="break-inside-avoid mb-4 relative group cursor-pointer rounded-2xl overflow-hidden border border-black/8 dark:border-white/8 hover:border-[color:var(--brand-primary)]/50 dark:hover:border-[color:var(--brand-primary-light)]/50 transition-all 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)]">
LiangLiu's avatar
LiangLiu committed
239
240
241
                                                        <img :src="getHistoryImageUrl(history)" :alt="history.filename"
                                                            class="w-full h-auto object-contain">
                                                        <div
LiangLiu's avatar
LiangLiu committed
242
                                                            class="absolute inset-0 bg-black/50 dark:bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
LiangLiu's avatar
LiangLiu committed
243
244
245
246
247
248
249
                                                        <i class="fas fa-check text-white text-xl"></i>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                    </div>

LiangLiu's avatar
LiangLiu committed
250
251
                                     <!-- 图片模板网格 - Apple 风格 -->
                                         <div v-if="showImageTemplates && mediaModalTab === 'templates'" class="pr-6 pl-1">
LiangLiu's avatar
LiangLiu committed
252

LiangLiu's avatar
LiangLiu committed
253
254
255
256
                                            <!-- 图片模板分页组件 - Apple 风格 -->
                                            <div v-if="templatePaginationInfo" class="mt-6">
                                                <div class="flex items-center justify-between text-xs mb-4">
                                                    <div class="flex items-center space-x-1 text-[#86868b] dark:text-[#98989d] tracking-tight">
LiangLiu's avatar
LiangLiu committed
257
258
259
260
                                                        <span>{{ templatePaginationInfo.total }} {{ t('records') }}</span>
                                                    </div>
                                                </div>
                                                <div v-if="templatePaginationInfo.total_pages > 1" class="flex justify-center">
LiangLiu's avatar
LiangLiu committed
261
                                                    <nav class="isolate inline-flex gap-1" aria-label="Pagination">
LiangLiu's avatar
LiangLiu committed
262
263
264
                                                        <!-- 上一页按钮 -->
                                                        <button @click="goToTemplatePage(templateCurrentPage - 1)"
                                                            :disabled="templateCurrentPage <= 1"
LiangLiu's avatar
LiangLiu committed
265
                                                            class="relative inline-flex items-center w-9 h-9 rounded-lg bg-white/80 dark:bg-[#2c2c2e]/80 border border-black/8 dark:border-white/8 text-[#86868b] dark:text-[#98989d] hover:bg-white dark:hover:bg-[#3a3a3c] hover:text-[#1d1d1f] dark:hover:text-[#f5f5f7] transition-all duration-200"
LiangLiu's avatar
LiangLiu committed
266
267
268
                                                            :class="{ 'opacity-50 cursor-not-allowed': templateCurrentPage <= 1 }"
                                                            :title="t('previousPage')">
                                                            <span class="sr-only">{{ t('previousPage') }}</span>
LiangLiu's avatar
LiangLiu committed
269
                                                            <i class="fas fa-chevron-left text-xs mx-auto" aria-hidden="true"></i>
LiangLiu's avatar
LiangLiu committed
270
271
272
273
274
275
                                                        </button>

                                                        <!-- 页码按钮 -->
                                                        <template v-for="page in getVisibleTemplatePages()" :key="page">
                                                            <button v-if="page !== '...'" @click="goToTemplatePage(page)"
                                                                :class="[
LiangLiu's avatar
LiangLiu committed
276
                                                                    'relative inline-flex items-center justify-center min-w-[36px] h-9 px-3 text-sm font-medium rounded-lg transition-all duration-200',
LiangLiu's avatar
LiangLiu committed
277
                                                                    page === templateCurrentPage
LiangLiu's avatar
LiangLiu committed
278
279
                                                                        ? 'bg-[color:var(--brand-primary)] dark:bg-[color:var(--brand-primary-light)] text-white shadow-[0_2px_8px_rgba(var(--brand-primary-rgb),0.25)] dark:shadow-[0_2px_8px_rgba(var(--brand-primary-light-rgb),0.3)]'
                                                                        : 'bg-white/80 dark:bg-[#2c2c2e]/80 border border-black/8 dark:border-white/8 text-[#86868b] dark:text-[#98989d] hover:bg-white dark:hover:bg-[#3a3a3c] hover:text-[#1d1d1f] dark:hover:text-[#f5f5f7]'
LiangLiu's avatar
LiangLiu committed
280
281
282
283
                                                                ]"
                                                                :aria-current="page === templateCurrentPage ? 'page' : undefined">
                                                                {{ page }}
                                                            </button>
LiangLiu's avatar
LiangLiu committed
284
                                                            <span v-else class="relative inline-flex items-center px-2 text-sm font-semibold text-[#86868b] dark:text-[#98989d]">...</span>
LiangLiu's avatar
LiangLiu committed
285
286
287
288
289
                                                        </template>

                                                        <!-- 下一页按钮 -->
                                                        <button @click="goToTemplatePage(templateCurrentPage + 1)"
                                                            :disabled="templateCurrentPage >= templatePaginationInfo.total_pages"
LiangLiu's avatar
LiangLiu committed
290
                                                            class="relative inline-flex items-center w-9 h-9 rounded-lg bg-white/80 dark:bg-[#2c2c2e]/80 border border-black/8 dark:border-white/8 text-[#86868b] dark:text-[#98989d] hover:bg-white dark:hover:bg-[#3a3a3c] hover:text-[#1d1d1f] dark:hover:text-[#f5f5f7] transition-all duration-200"
LiangLiu's avatar
LiangLiu committed
291
292
293
                                                            :class="{ 'opacity-50 cursor-not-allowed': templateCurrentPage >= templatePaginationInfo.total_pages }"
                                                            :title="t('nextPage')">
                                                            <span class="sr-only">{{ t('nextPage') }}</span>
LiangLiu's avatar
LiangLiu committed
294
                                                            <i class="fas fa-chevron-right text-xs mx-auto" aria-hidden="true"></i>
LiangLiu's avatar
LiangLiu committed
295
296
297
298
                                                        </button>
                                                    </nav>
                                                </div>
                                            </div>
LiangLiu's avatar
LiangLiu committed
299
300
                                         <div class="overflow-y-auto flex-1 max-h-[60vh] main-scrollbar pr-2 pt-2">
                                         <div v-if="imageTemplates.length > 0" class="columns-2 sm:columns-2 md:columns-3 lg:columns-4 xl:columns-5 gap-4 px-1">
LiangLiu's avatar
LiangLiu committed
301
302
                                            <div v-for="template in imageTemplates" :key="template.filename"
                                                @click="selectImageTemplate(template)"
LiangLiu's avatar
LiangLiu committed
303
                                                class="break-inside-avoid mb-4 relative group cursor-pointer rounded-2xl overflow-hidden border border-black/8 dark:border-white/8 hover:border-[color:var(--brand-primary)]/50 dark:hover:border-[color:var(--brand-primary-light)]/50 transition-all 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)]">
LiangLiu's avatar
LiangLiu committed
304
305
306
                                                    <img :src="getTemplateFileUrl(template.filename,'images')" :alt="template.filename"
                                                    class="w-full h-auto object-contain" preload="metadata">
                                                    <div
LiangLiu's avatar
LiangLiu committed
307
                                                        class="absolute inset-0 bg-black/50 dark:bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
LiangLiu's avatar
LiangLiu committed
308
309
310
311
312
313
314
                                                    <i class="fas fa-check text-white text-2xl"></i>
                                                </div>
                                            </div>
                                        </div>
                                            <div v-else
                                                class="flex flex-col items-center justify-center py-12 text-center">
                                                <div
LiangLiu's avatar
LiangLiu committed
315
316
                                                    class="w-16 h-16 bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 rounded-full flex items-center justify-center mb-4">
                                                <i class="fas fa-image text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] text-2xl"></i>
LiangLiu's avatar
LiangLiu committed
317
                                            </div>
LiangLiu's avatar
LiangLiu committed
318
                                                <p class="text-[#1d1d1f] dark:text-[#f5f5f7] text-lg font-medium tracking-tight">{{ t('noImageTemplates') }}</p>
LiangLiu's avatar
LiangLiu committed
319
320
321
322
323
                                        </div>
                                    </div>

                                    </div>

LiangLiu's avatar
LiangLiu committed
324
                                    <!-- 音频历史记录 - Apple 风格 -->
LiangLiu's avatar
LiangLiu committed
325
                                          <div v-if="showAudioTemplates && mediaModalTab === 'history'"
LiangLiu's avatar
LiangLiu committed
326
                                             class="overflow-y-auto flex-1 max-h-[60vh] main-scrollbar pr-6 pl-1">
LiangLiu's avatar
LiangLiu committed
327
328
329
                                            <div v-if="audioHistory.length === 0"
                                                class="flex flex-col items-center justify-center py-12 text-center">
                                                <div
LiangLiu's avatar
LiangLiu committed
330
331
                                                    class="w-16 h-16 bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 rounded-full flex items-center justify-center mb-4">
                                                <i class="fas fa-history text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] text-2xl"></i>
LiangLiu's avatar
LiangLiu committed
332
                                            </div>
LiangLiu's avatar
LiangLiu committed
333
334
                                                <p class="text-[#1d1d1f] dark:text-[#f5f5f7] text-lg font-medium mb-2 tracking-tight">{{ t('noHistoryRecords') }}</p>
                                                <p class="text-[#86868b] dark:text-[#98989d] text-sm tracking-tight">{{ t('audioHistoryAutoSave') }}</p>
LiangLiu's avatar
LiangLiu committed
335
                                        </div>
LiangLiu's avatar
LiangLiu committed
336
337
338
                                        <div v-else class="space-y-3 pt-2">
                                            <div class="flex items-center justify-between mb-6 px-1">
                                                    <span class="text-sm text-[#86868b] dark:text-[#98989d] tracking-tight">{{ t('total') }} {{ audioHistory.length }}
LiangLiu's avatar
LiangLiu committed
339
340
                                                        {{ t('records') }}</span>
                                                <button @click="clearAudioHistory"
LiangLiu's avatar
LiangLiu committed
341
                                                        class="text-xs text-red-500 dark:text-red-400 hover:text-red-600 dark:hover:text-red-300 transition-colors flex items-center gap-1.5 tracking-tight"
LiangLiu's avatar
LiangLiu committed
342
343
344
345
346
                                                        :title="t('clearHistory')">
                                                    <i class="fas fa-trash"></i>
                                                        {{ t('clear') }}
                                                </button>
                                            </div>
LiangLiu's avatar
LiangLiu committed
347
                                            <div class="space-y-3 px-1">
LiangLiu's avatar
LiangLiu committed
348
349
                                                <div v-for="(history, index) in audioHistory" :key="index"
                                                    @click="selectAudioHistory(history)"
LiangLiu's avatar
LiangLiu committed
350
                                                    class="flex items-center gap-4 p-4 rounded-2xl border border-black/8 dark:border-white/8 hover:border-[color:var(--brand-primary)]/50 dark:hover:border-[color:var(--brand-primary-light)]/50 transition-all cursor-pointer bg-white/80 dark:bg-[#2c2c2e]/80 hover:bg-white dark:hover:bg-[#3a3a3c] 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)] group">
LiangLiu's avatar
LiangLiu committed
351
                                                        <!-- 头像容器 - 如果有图像则显示图像,否则显示图标 -->
LiangLiu's avatar
LiangLiu committed
352
                                                        <div
LiangLiu's avatar
LiangLiu committed
353
354
355
356
357
358
359
360
                                                            class="w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0 overflow-hidden bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15">
                                                            <img v-if="getAudioImageUrl(history, false) && !imageLoadFailed[`history_${history.filename}`]"
                                                                :src="getAudioImageUrl(history, false)"
                                                                :alt="history.filename"
                                                                class="w-full h-full object-cover"
                                                                @error="imageLoadFailed[`history_${history.filename}`] = true">
                                                            <i v-else class="fas fa-music text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] text-xl"></i>
                                                        </div>
LiangLiu's avatar
LiangLiu committed
361
                                                    <div class="flex-1 min-w-0">
LiangLiu's avatar
LiangLiu committed
362
                                                            <div
LiangLiu's avatar
LiangLiu committed
363
                                                                class="text-[#1d1d1f] dark:text-[#f5f5f7] font-medium group-hover:text-[color:var(--brand-primary)] dark:group-hover:text-[color:var(--brand-primary-light)] transition-colors truncate tracking-tight">
LiangLiu's avatar
LiangLiu committed
364
                                                                {{ history.filename }}</div>
LiangLiu's avatar
LiangLiu committed
365
366
367
368
369
                                                            <div class="text-[#86868b] dark:text-[#98989d] text-sm flex items-center gap-2 tracking-tight">
                                                                <span>{{ t('audioFile') }}</span>
                                                                <span class="text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></span>
                                                                <span>{{ getDurationDisplay(history, false) }}</span>
                                                            </div>
LiangLiu's avatar
LiangLiu committed
370
                                                    </div>
LiangLiu's avatar
LiangLiu committed
371
372
373
374
375
                                                    <button @click.stop="handleAudioPreview(history, false)"
                                                            class="px-4 py-2 rounded-lg transition-all cursor-pointer relative z-10 flex items-center gap-2 flex-shrink-0 tracking-tight"
                                                            :class="isPlaying(history, false)
                                                                ? 'text-red-500 dark:text-red-400 hover:text-red-600 dark:hover:text-red-300'
                                                                : 'text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] hover:text-[color:var(--brand-primary)]/80 dark:hover:text-[color:var(--brand-primary-light)]/80'"
LiangLiu's avatar
LiangLiu committed
376
                                                            style="pointer-events: auto;">
LiangLiu's avatar
LiangLiu committed
377
378
                                                        <i :class="isPlaying(history, false) ? 'fas fa-stop' : 'fas fa-play'"></i>
                                                        <span class="text-sm font-medium">{{ isPlaying(history, false) ? t('stop') : t('preview') }}</span>
LiangLiu's avatar
LiangLiu committed
379
380
381
382
383
384
                                                    </button>
                                                </div>
                                            </div>
                                        </div>
                                    </div>

LiangLiu's avatar
LiangLiu committed
385
386
387
388
389
390
                                    <!-- 音频模板列表 - Apple 风格 -->
                                          <div v-if="showAudioTemplates && mediaModalTab === 'templates'" class="pr-6 pl-1">
                                                                                    <!-- 音频模板分页组件 - Apple 风格 -->
                                                                                    <div v-if="templatePaginationInfo" class="mt-6">
                                                                                        <div class="flex items-center justify-between text-xs mb-4">
                                                                                            <div class="flex items-center space-x-1 text-[#86868b] dark:text-[#98989d] tracking-tight">
LiangLiu's avatar
LiangLiu committed
391
392
393
394
                                                                                                <span>{{ templatePaginationInfo.total }} {{ t('records') }}</span>
                                                                                            </div>
                                                                                        </div>
                                                                                        <div v-if="templatePaginationInfo.total_pages > 1" class="flex justify-center">
LiangLiu's avatar
LiangLiu committed
395
                                                                                            <nav class="isolate inline-flex gap-1" aria-label="Pagination">
LiangLiu's avatar
LiangLiu committed
396
397
398
                                                                                                <!-- 上一页按钮 -->
                                                                                                <button @click="goToTemplatePage(templateCurrentPage - 1)"
                                                                                                    :disabled="templateCurrentPage <= 1"
LiangLiu's avatar
LiangLiu committed
399
                                                                                                    class="relative inline-flex items-center w-9 h-9 rounded-lg bg-white/80 dark:bg-[#2c2c2e]/80 border border-black/8 dark:border-white/8 text-[#86868b] dark:text-[#98989d] hover:bg-white dark:hover:bg-[#3a3a3c] hover:text-[#1d1d1f] dark:hover:text-[#f5f5f7] transition-all duration-200"
LiangLiu's avatar
LiangLiu committed
400
401
402
                                                                                                    :class="{ 'opacity-50 cursor-not-allowed': templateCurrentPage <= 1 }"
                                                                                                    :title="t('previousPage')">
                                                                                                    <span class="sr-only">{{ t('previousPage') }}</span>
LiangLiu's avatar
LiangLiu committed
403
                                                                                                    <i class="fas fa-chevron-left text-xs mx-auto" aria-hidden="true"></i>
LiangLiu's avatar
LiangLiu committed
404
405
406
407
408
409
                                                                                                </button>

                                                                                                <!-- 页码按钮 -->
                                                                                                <template v-for="page in getVisibleTemplatePages()" :key="page">
                                                                                                    <button v-if="page !== '...'" @click="goToTemplatePage(page)"
                                                                                                        :class="[
LiangLiu's avatar
LiangLiu committed
410
                                                                                                            'relative inline-flex items-center justify-center min-w-[36px] h-9 px-3 text-sm font-medium rounded-lg transition-all duration-200',
LiangLiu's avatar
LiangLiu committed
411
                                                                                                            page === templateCurrentPage
LiangLiu's avatar
LiangLiu committed
412
413
                                                                                                                ? 'bg-[color:var(--brand-primary)] dark:bg-[color:var(--brand-primary-light)] text-white shadow-[0_2px_8px_rgba(var(--brand-primary-rgb),0.25)] dark:shadow-[0_2px_8px_rgba(var(--brand-primary-light-rgb),0.3)]'
                                                                                                                : 'bg-white/80 dark:bg-[#2c2c2e]/80 border border-black/8 dark:border-white/8 text-[#86868b] dark:text-[#98989d] hover:bg-white dark:hover:bg-[#3a3a3c] hover:text-[#1d1d1f] dark:hover:text-[#f5f5f7]'
LiangLiu's avatar
LiangLiu committed
414
415
416
417
                                                                                                        ]"
                                                                                                        :aria-current="page === templateCurrentPage ? 'page' : undefined">
                                                                                                        {{ page }}
                                                                                                    </button>
LiangLiu's avatar
LiangLiu committed
418
                                                                                                    <span v-else class="relative inline-flex items-center px-2 text-sm font-semibold text-[#86868b] dark:text-[#98989d]">...</span>
LiangLiu's avatar
LiangLiu committed
419
420
421
422
423
                                                                                                </template>

                                                                                                <!-- 下一页按钮 -->
                                                                                                <button @click="goToTemplatePage(templateCurrentPage + 1)"
                                                                                                    :disabled="templateCurrentPage >= templatePaginationInfo.total_pages"
LiangLiu's avatar
LiangLiu committed
424
                                                                                                    class="relative inline-flex items-center w-9 h-9 rounded-lg bg-white/80 dark:bg-[#2c2c2e]/80 border border-black/8 dark:border-white/8 text-[#86868b] dark:text-[#98989d] hover:bg-white dark:hover:bg-[#3a3a3c] hover:text-[#1d1d1f] dark:hover:text-[#f5f5f7] transition-all duration-200"
LiangLiu's avatar
LiangLiu committed
425
426
427
                                                                                                    :class="{ 'opacity-50 cursor-not-allowed': templateCurrentPage >= templatePaginationInfo.total_pages }"
                                                                                                    :title="t('nextPage')">
                                                                                                    <span class="sr-only">{{ t('nextPage') }}</span>
LiangLiu's avatar
LiangLiu committed
428
                                                                                                    <i class="fas fa-chevron-right text-xs mx-auto" aria-hidden="true"></i>
LiangLiu's avatar
LiangLiu committed
429
430
431
432
                                                                                                </button>
                                                                                            </nav>
                                                                                        </div>
                                                                                    </div>
LiangLiu's avatar
LiangLiu committed
433
434
                                        <div class="overflow-y-auto flex-1 max-h-[60vh] main-scrollbar pr-2 pt-2">
                                        <div v-if="audioTemplates.length > 0" class="space-y-3 px-1">
LiangLiu's avatar
LiangLiu committed
435
436
                                            <div v-for="template in audioTemplates" :key="template.filename"
                                                @click="selectAudioTemplate(template)"
LiangLiu's avatar
LiangLiu committed
437
                                                class="flex items-center gap-4 p-4 rounded-2xl border border-black/8 dark:border-white/8 hover:border-[color:var(--brand-primary)]/50 dark:hover:border-[color:var(--brand-primary-light)]/50 transition-all cursor-pointer bg-white/80 dark:bg-[#2c2c2e]/80 hover:bg-white dark:hover:bg-[#3a3a3c] 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)] group">
LiangLiu's avatar
LiangLiu committed
438
                                                    <!-- 头像容器 - 如果有图像则显示图像,否则显示图标 -->
LiangLiu's avatar
LiangLiu committed
439
                                                    <div
LiangLiu's avatar
LiangLiu committed
440
441
442
443
444
445
446
447
                                                        class="w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0 overflow-hidden bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15">
                                                        <img v-if="hasAudioImage(template, true) && !imageLoadFailed[`template_${template.filename}`]"
                                                            :src="getAudioImageUrl(template, true)"
                                                            :alt="template.filename"
                                                            class="w-full h-full object-cover"
                                                            @error="imageLoadFailed[`template_${template.filename}`] = true">
                                                        <i v-else class="fas fa-music text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] text-xl"></i>
                                                    </div>
LiangLiu's avatar
LiangLiu committed
448
449
450
451
452
453
454
                                                <div class="flex-1 min-w-0">
                                                        <div class="text-[#1d1d1f] dark:text-[#f5f5f7] font-medium group-hover:text-[color:var(--brand-primary)] dark:group-hover:text-[color:var(--brand-primary-light)] transition-colors truncate tracking-tight">{{ template.filename }}
                                                        </div>
                                                        <div class="text-[#86868b] dark:text-[#98989d] text-sm flex items-center gap-2 tracking-tight">
                                                            <span>{{ t('audioTemplates') }}</span>
                                                            <span class="text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></span>
                                                            <span>{{ getDurationDisplay(template, true) }}</span>
LiangLiu's avatar
LiangLiu committed
455
456
                                                        </div>
                                                </div>
LiangLiu's avatar
LiangLiu committed
457
458
459
460
461
                                                <button @click.stop="handleAudioPreview(template, true)"
                                                        class="px-4 py-2 rounded-lg transition-all cursor-pointer relative z-10 flex items-center gap-2 flex-shrink-0 tracking-tight"
                                                        :class="isPlaying(template, true)
                                                            ? 'text-red-500 dark:text-red-400 hover:text-red-600 dark:hover:text-red-300'
                                                            : 'text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] hover:text-[color:var(--brand-primary)]/80 dark:hover:text-[color:var(--brand-primary-light)]/80'"
LiangLiu's avatar
LiangLiu committed
462
                                                        style="pointer-events: auto;">
LiangLiu's avatar
LiangLiu committed
463
464
                                                    <i :class="isPlaying(template, true) ? 'fas fa-stop' : 'fas fa-play'"></i>
                                                    <span class="text-sm font-medium">{{ isPlaying(template, true) ? t('stop')  : t('preview') }}</span>
LiangLiu's avatar
LiangLiu committed
465
466
467
468
469
470
                                                </button>
                                            </div>
                                        </div>
                                            <div v-else
                                                class="flex flex-col items-center justify-center py-12 text-center">
                                                <div
LiangLiu's avatar
LiangLiu committed
471
472
                                                    class="w-16 h-16 bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 rounded-full flex items-center justify-center mb-4">
                                                <i class="fas fa-music text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] text-2xl"></i>
LiangLiu's avatar
LiangLiu committed
473
                                            </div>
LiangLiu's avatar
LiangLiu committed
474
                                            <p class="text-[#1d1d1f] dark:text-[#f5f5f7] text-lg font-medium tracking-tight">目前暂无音频模板</p>
LiangLiu's avatar
LiangLiu committed
475
476
477
478
479
480
481
482
                                        </div>
                                        </div>

                                        </div>
                                </div>
                        </div>
                    </div>
</template>