MediaTemplate.vue 38.3 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
10
// 音频播放状态管理
const playingAudioId = ref(null)
const audioDurations = ref({})

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

// 格式化音频时长
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])
}

// 预加载音频时长
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
141
142
143
144
</script>

<template>

LiangLiu's avatar
LiangLiu committed
145
                        <!-- 模板选择浮窗 - Apple 极简风格 -->
LiangLiu's avatar
LiangLiu committed
146
147
                        <div v-cloak>
                            <div v-if="showImageTemplates || showAudioTemplates"
LiangLiu's avatar
LiangLiu committed
148
                                class="fixed inset-0 bg-black/50 dark:bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center"
LiangLiu's avatar
LiangLiu committed
149
                                @click="showImageTemplates = false; showAudioTemplates = false">
LiangLiu's avatar
LiangLiu committed
150
                                <div class="bg-white/95 dark:bg-[#1e1e1e]/95 backdrop-blur-[20px] backdrop-saturate-[180%] border border-black/8 dark:border-white/8 rounded-3xl px-10 py-8 max-w-4xl w-full mx-6 h-[90vh] overflow-hidden shadow-[0_8px_32px_rgba(0,0,0,0.12)] dark:shadow-[0_8px_32px_rgba(0,0,0,0.4)]"
LiangLiu's avatar
LiangLiu committed
151
                                    @click.stop>
LiangLiu's avatar
LiangLiu committed
152
153
154
                                    <!-- 浮窗头部 - 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
155
                                                <i v-if="showImageTemplates"
LiangLiu's avatar
LiangLiu committed
156
                                                    class="fas fa-image text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
LiangLiu's avatar
LiangLiu committed
157
                                                <i v-if="showAudioTemplates"
LiangLiu's avatar
LiangLiu committed
158
                                                    class="fas fa-music text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
LiangLiu's avatar
LiangLiu committed
159
160
161
                                                {{ showImageTemplates ? t('imageTemplates') : t('audioTemplates') }}
                                        </h3>
                                        <button @click="showImageTemplates = false; showAudioTemplates = false"
LiangLiu's avatar
LiangLiu committed
162
163
                                                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
164
165
166
                                        </button>
                                    </div>

LiangLiu's avatar
LiangLiu committed
167
168
                                    <!-- 标签页切换 - Apple 风格 -->
                                    <div class="flex gap-2 mb-8">
LiangLiu's avatar
LiangLiu committed
169
170
                                            <button
                                                @click="mediaModalTab = 'history'; showImageTemplates && getImageHistory(); showAudioTemplates && getAudioHistory()"
LiangLiu's avatar
LiangLiu committed
171
172
173
                                                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
174
175
176
177
                                            <i class="fas fa-history mr-2"></i>
                                                {{ t('history') }}
                                        </button>
                                        <button @click="mediaModalTab = 'templates'"
LiangLiu's avatar
LiangLiu committed
178
179
180
                                                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
181
182
183
184
185
                                            <i class="fas fa-layer-group mr-2"></i>
                                                {{ t('templates') }}
                                        </button>
                                    </div>

LiangLiu's avatar
LiangLiu committed
186
                                    <!-- 图片历史记录 - Apple 风格 -->
LiangLiu's avatar
LiangLiu committed
187
                                          <div v-if="showImageTemplates && mediaModalTab === 'history'"
LiangLiu's avatar
LiangLiu committed
188
                                             class="overflow-y-auto flex-1 max-h-[60vh] main-scrollbar pr-6 pl-1">
LiangLiu's avatar
LiangLiu committed
189
190
191
                                            <div v-if="imageHistory.length === 0"
                                                class="flex flex-col items-center justify-center py-12 text-center">
                                                <div
LiangLiu's avatar
LiangLiu committed
192
193
                                                    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
194
                                            </div>
LiangLiu's avatar
LiangLiu committed
195
196
                                                <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
197
                                        </div>
LiangLiu's avatar
LiangLiu committed
198
199
200
                                        <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
201
202
                                                        {{ t('records') }}</span>
                                                <button @click="clearImageHistory"
LiangLiu's avatar
LiangLiu committed
203
                                                        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
204
205
206
207
208
                                                        :title="t('clearHistory')">
                                                    <i class="fas fa-trash"></i>
                                                        {{ t('clear') }}
                                                </button>
                                            </div>
LiangLiu's avatar
LiangLiu committed
209
                                            <div class="columns-2 md:columns-3 lg:columns-4 xl:columns-5 gap-4 px-1">
LiangLiu's avatar
LiangLiu committed
210
211
                                                <div v-for="(history, index) in imageHistory" :key="index"
                                                    @click="selectImageHistory(history)"
LiangLiu's avatar
LiangLiu committed
212
                                                    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
213
214
215
                                                        <img :src="getHistoryImageUrl(history)" :alt="history.filename"
                                                            class="w-full h-auto object-contain">
                                                        <div
LiangLiu's avatar
LiangLiu committed
216
                                                            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
217
218
219
220
221
222
223
                                                        <i class="fas fa-check text-white text-xl"></i>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                    </div>

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

LiangLiu's avatar
LiangLiu committed
227
228
229
230
                                            <!-- 图片模板分页组件 - 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
231
232
233
234
                                                        <span>{{ templatePaginationInfo.total }} {{ t('records') }}</span>
                                                    </div>
                                                </div>
                                                <div v-if="templatePaginationInfo.total_pages > 1" class="flex justify-center">
LiangLiu's avatar
LiangLiu committed
235
                                                    <nav class="isolate inline-flex gap-1" aria-label="Pagination">
LiangLiu's avatar
LiangLiu committed
236
237
238
                                                        <!-- 上一页按钮 -->
                                                        <button @click="goToTemplatePage(templateCurrentPage - 1)"
                                                            :disabled="templateCurrentPage <= 1"
LiangLiu's avatar
LiangLiu committed
239
                                                            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
240
241
242
                                                            :class="{ 'opacity-50 cursor-not-allowed': templateCurrentPage <= 1 }"
                                                            :title="t('previousPage')">
                                                            <span class="sr-only">{{ t('previousPage') }}</span>
LiangLiu's avatar
LiangLiu committed
243
                                                            <i class="fas fa-chevron-left text-xs mx-auto" aria-hidden="true"></i>
LiangLiu's avatar
LiangLiu committed
244
245
246
247
248
249
                                                        </button>

                                                        <!-- 页码按钮 -->
                                                        <template v-for="page in getVisibleTemplatePages()" :key="page">
                                                            <button v-if="page !== '...'" @click="goToTemplatePage(page)"
                                                                :class="[
LiangLiu's avatar
LiangLiu committed
250
                                                                    '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
251
                                                                    page === templateCurrentPage
LiangLiu's avatar
LiangLiu committed
252
253
                                                                        ? '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
254
255
256
257
                                                                ]"
                                                                :aria-current="page === templateCurrentPage ? 'page' : undefined">
                                                                {{ page }}
                                                            </button>
LiangLiu's avatar
LiangLiu committed
258
                                                            <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
259
260
261
262
263
                                                        </template>

                                                        <!-- 下一页按钮 -->
                                                        <button @click="goToTemplatePage(templateCurrentPage + 1)"
                                                            :disabled="templateCurrentPage >= templatePaginationInfo.total_pages"
LiangLiu's avatar
LiangLiu committed
264
                                                            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
265
266
267
                                                            :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
268
                                                            <i class="fas fa-chevron-right text-xs mx-auto" aria-hidden="true"></i>
LiangLiu's avatar
LiangLiu committed
269
270
271
272
                                                        </button>
                                                    </nav>
                                                </div>
                                            </div>
LiangLiu's avatar
LiangLiu committed
273
274
                                         <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
275
276
                                            <div v-for="template in imageTemplates" :key="template.filename"
                                                @click="selectImageTemplate(template)"
LiangLiu's avatar
LiangLiu committed
277
                                                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
278
279
280
                                                    <img :src="getTemplateFileUrl(template.filename,'images')" :alt="template.filename"
                                                    class="w-full h-auto object-contain" preload="metadata">
                                                    <div
LiangLiu's avatar
LiangLiu committed
281
                                                        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
282
283
284
285
286
287
288
                                                    <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
289
290
                                                    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
291
                                            </div>
LiangLiu's avatar
LiangLiu committed
292
                                                <p class="text-[#1d1d1f] dark:text-[#f5f5f7] text-lg font-medium tracking-tight">{{ t('noImageTemplates') }}</p>
LiangLiu's avatar
LiangLiu committed
293
294
295
296
297
                                        </div>
                                    </div>

                                    </div>

LiangLiu's avatar
LiangLiu committed
298
                                    <!-- 音频历史记录 - Apple 风格 -->
LiangLiu's avatar
LiangLiu committed
299
                                          <div v-if="showAudioTemplates && mediaModalTab === 'history'"
LiangLiu's avatar
LiangLiu committed
300
                                             class="overflow-y-auto flex-1 max-h-[60vh] main-scrollbar pr-6 pl-1">
LiangLiu's avatar
LiangLiu committed
301
302
303
                                            <div v-if="audioHistory.length === 0"
                                                class="flex flex-col items-center justify-center py-12 text-center">
                                                <div
LiangLiu's avatar
LiangLiu committed
304
305
                                                    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
306
                                            </div>
LiangLiu's avatar
LiangLiu committed
307
308
                                                <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
309
                                        </div>
LiangLiu's avatar
LiangLiu committed
310
311
312
                                        <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
313
314
                                                        {{ t('records') }}</span>
                                                <button @click="clearAudioHistory"
LiangLiu's avatar
LiangLiu committed
315
                                                        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
316
317
318
319
320
                                                        :title="t('clearHistory')">
                                                    <i class="fas fa-trash"></i>
                                                        {{ t('clear') }}
                                                </button>
                                            </div>
LiangLiu's avatar
LiangLiu committed
321
                                            <div class="space-y-3 px-1">
LiangLiu's avatar
LiangLiu committed
322
323
                                                <div v-for="(history, index) in audioHistory" :key="index"
                                                    @click="selectAudioHistory(history)"
LiangLiu's avatar
LiangLiu committed
324
                                                    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
325
                                                        <div
LiangLiu's avatar
LiangLiu committed
326
327
                                                            class="w-12 h-12 bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 rounded-xl flex items-center justify-center flex-shrink-0">
                                                        <i class="fas fa-music text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] text-xl"></i>
LiangLiu's avatar
LiangLiu committed
328
                                                    </div>
LiangLiu's avatar
LiangLiu committed
329
                                                    <div class="flex-1 min-w-0">
LiangLiu's avatar
LiangLiu committed
330
                                                            <div
LiangLiu's avatar
LiangLiu committed
331
                                                                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
332
                                                                {{ history.filename }}</div>
LiangLiu's avatar
LiangLiu committed
333
334
335
336
337
                                                            <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
338
                                                    </div>
LiangLiu's avatar
LiangLiu committed
339
340
341
342
343
                                                    <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
344
                                                            style="pointer-events: auto;">
LiangLiu's avatar
LiangLiu committed
345
346
                                                        <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
347
348
349
350
351
352
                                                    </button>
                                                </div>
                                            </div>
                                        </div>
                                    </div>

LiangLiu's avatar
LiangLiu committed
353
354
355
356
357
358
                                    <!-- 音频模板列表 - 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
359
360
361
362
                                                                                                <span>{{ templatePaginationInfo.total }} {{ t('records') }}</span>
                                                                                            </div>
                                                                                        </div>
                                                                                        <div v-if="templatePaginationInfo.total_pages > 1" class="flex justify-center">
LiangLiu's avatar
LiangLiu committed
363
                                                                                            <nav class="isolate inline-flex gap-1" aria-label="Pagination">
LiangLiu's avatar
LiangLiu committed
364
365
366
                                                                                                <!-- 上一页按钮 -->
                                                                                                <button @click="goToTemplatePage(templateCurrentPage - 1)"
                                                                                                    :disabled="templateCurrentPage <= 1"
LiangLiu's avatar
LiangLiu committed
367
                                                                                                    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
368
369
370
                                                                                                    :class="{ 'opacity-50 cursor-not-allowed': templateCurrentPage <= 1 }"
                                                                                                    :title="t('previousPage')">
                                                                                                    <span class="sr-only">{{ t('previousPage') }}</span>
LiangLiu's avatar
LiangLiu committed
371
                                                                                                    <i class="fas fa-chevron-left text-xs mx-auto" aria-hidden="true"></i>
LiangLiu's avatar
LiangLiu committed
372
373
374
375
376
377
                                                                                                </button>

                                                                                                <!-- 页码按钮 -->
                                                                                                <template v-for="page in getVisibleTemplatePages()" :key="page">
                                                                                                    <button v-if="page !== '...'" @click="goToTemplatePage(page)"
                                                                                                        :class="[
LiangLiu's avatar
LiangLiu committed
378
                                                                                                            '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
379
                                                                                                            page === templateCurrentPage
LiangLiu's avatar
LiangLiu committed
380
381
                                                                                                                ? '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
382
383
384
385
                                                                                                        ]"
                                                                                                        :aria-current="page === templateCurrentPage ? 'page' : undefined">
                                                                                                        {{ page }}
                                                                                                    </button>
LiangLiu's avatar
LiangLiu committed
386
                                                                                                    <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
387
388
389
390
391
                                                                                                </template>

                                                                                                <!-- 下一页按钮 -->
                                                                                                <button @click="goToTemplatePage(templateCurrentPage + 1)"
                                                                                                    :disabled="templateCurrentPage >= templatePaginationInfo.total_pages"
LiangLiu's avatar
LiangLiu committed
392
                                                                                                    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
393
394
395
                                                                                                    :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
396
                                                                                                    <i class="fas fa-chevron-right text-xs mx-auto" aria-hidden="true"></i>
LiangLiu's avatar
LiangLiu committed
397
398
399
400
                                                                                                </button>
                                                                                            </nav>
                                                                                        </div>
                                                                                    </div>
LiangLiu's avatar
LiangLiu committed
401
402
                                        <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
403
404
                                            <div v-for="template in audioTemplates" :key="template.filename"
                                                @click="selectAudioTemplate(template)"
LiangLiu's avatar
LiangLiu committed
405
                                                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
406
                                                    <div
LiangLiu's avatar
LiangLiu committed
407
408
                                                        class="w-12 h-12 bg-[color:var(--brand-primary)]/10 dark:bg-[color:var(--brand-primary-light)]/15 rounded-xl flex items-center justify-center flex-shrink-0">
                                                    <i class="fas fa-music text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)] text-xl"></i>
LiangLiu's avatar
LiangLiu committed
409
                                                </div>
LiangLiu's avatar
LiangLiu committed
410
411
412
413
414
415
416
                                                <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
417
418
                                                        </div>
                                                </div>
LiangLiu's avatar
LiangLiu committed
419
420
421
422
423
                                                <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
424
                                                        style="pointer-events: auto;">
LiangLiu's avatar
LiangLiu committed
425
426
                                                    <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
427
428
429
430
431
432
                                                </button>
                                            </div>
                                        </div>
                                            <div v-else
                                                class="flex flex-col items-center justify-center py-12 text-center">
                                                <div
LiangLiu's avatar
LiangLiu committed
433
434
                                                    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
435
                                            </div>
LiangLiu's avatar
LiangLiu committed
436
                                            <p class="text-[#1d1d1f] dark:text-[#f5f5f7] text-lg font-medium tracking-tight">目前暂无音频模板</p>
LiangLiu's avatar
LiangLiu committed
437
438
439
440
441
442
443
444
                                        </div>
                                        </div>

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