MediaTemplate.vue 40.5 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
    clearImageHistory,
    clearAudioHistory,
    templatePaginationInfo,
    templateCurrentPage,
    templatePageInput,
    showImageTemplates,
    showAudioTemplates,
    imageHistory,
    audioHistory,
    imageTemplates,
    audioTemplates,
    mediaModalTab,
    getImageHistory,
    getAudioHistory,
LiangLiu's avatar
LiangLiu committed
39
    isPageLoading
LiangLiu's avatar
LiangLiu committed
40
} from '../utils/other'
LiangLiu's avatar
LiangLiu committed
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141

// 格式化音频时长
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
142
143
144
145
</script>

<template>

LiangLiu's avatar
LiangLiu committed
146
                        <!-- 模板选择浮窗 - Apple 极简风格 -->
LiangLiu's avatar
LiangLiu committed
147
148
                        <div v-cloak>
                            <div v-if="showImageTemplates || showAudioTemplates"
LiangLiu's avatar
LiangLiu committed
149
                                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
150
                                @click="showImageTemplates = false; showAudioTemplates = false">
LiangLiu's avatar
LiangLiu committed
151
                                <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-8 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
152
                                    @click.stop>
LiangLiu's avatar
LiangLiu committed
153
154
155
                                    <!-- 浮窗头部 - 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
156
                                                <i v-if="showImageTemplates"
LiangLiu's avatar
LiangLiu committed
157
                                                    class="fas fa-image text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
LiangLiu's avatar
LiangLiu committed
158
                                                <i v-if="showAudioTemplates"
LiangLiu's avatar
LiangLiu committed
159
                                                    class="fas fa-music text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
LiangLiu's avatar
LiangLiu committed
160
161
162
                                                {{ showImageTemplates ? t('imageTemplates') : t('audioTemplates') }}
                                        </h3>
                                        <button @click="showImageTemplates = false; showAudioTemplates = false"
LiangLiu's avatar
LiangLiu committed
163
164
                                                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
165
166
167
                                        </button>
                                    </div>

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

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

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

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

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

                                                        <!-- 下一页按钮 -->
                                                        <button @click="goToTemplatePage(templateCurrentPage + 1)"
                                                            :disabled="templateCurrentPage >= templatePaginationInfo.total_pages"
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 >= templatePaginationInfo.total_pages }"
                                                            :title="t('nextPage')">
                                                            <span class="sr-only">{{ t('nextPage') }}</span>
LiangLiu's avatar
LiangLiu committed
269
                                                            <i class="fas fa-chevron-right text-xs mx-auto" aria-hidden="true"></i>
LiangLiu's avatar
LiangLiu committed
270
271
272
273
                                                        </button>
                                                    </nav>
                                                </div>
                                            </div>
LiangLiu's avatar
LiangLiu committed
274
                                         <div class="overflow-y-auto flex-1 max-h-[60vh] main-scrollbar pr-2 pt-2">
LiangLiu's avatar
LiangLiu committed
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
                                            <div class="space-y-4">
                                                <div v-if="isPageLoading" class="flex items-center justify-center">
                                                    <div class="inline-flex items-center gap-3 px-4 py-2 rounded-full bg-white/90 dark:bg-[#2c2c2e]/90 border border-black/8 dark:border-white/8 text-sm text-[#1d1d1f] dark:text-[#f5f5f7] shadow-[0_4px_16px_rgba(0,0,0,0.08)] dark:shadow-[0_4px_16px_rgba(0,0,0,0.35)]">
                                                        <i class="fas fa-spinner fa-spin text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                                        <span>{{ t('loading') }}</span>
                                                    </div>
                                                </div>
                                                <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">
                                                    <div v-for="template in imageTemplates" :key="template.filename"
                                                        @click="selectImageTemplate(template)"
                                                        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)]">
                                                            <img :src="getTemplateFileUrl(template.filename,'images')" :alt="template.filename"
                                                            class="w-full h-auto object-contain" preload="metadata">
                                                            <div
                                                                class="absolute inset-0 bg-black/50 dark:bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
                                                            <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">
LiangLiu's avatar
LiangLiu committed
296
                                                    <div
LiangLiu's avatar
LiangLiu committed
297
298
299
300
                                                        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>
                                                </div>
                                                    <p class="text-[#1d1d1f] dark:text-[#f5f5f7] text-lg font-medium tracking-tight">{{ t('noImageTemplates') }}</p>
LiangLiu's avatar
LiangLiu committed
301
302
                                                </div>
                                            </div>
LiangLiu's avatar
LiangLiu committed
303
                                         </div>
LiangLiu's avatar
LiangLiu committed
304
305
306

                                    </div>

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

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

                                                                                                <!-- 页码按钮 -->
                                                                                                <template v-for="page in getVisibleTemplatePages()" :key="page">
                                                                                                    <button v-if="page !== '...'" @click="goToTemplatePage(page)"
                                                                                                        :class="[
LiangLiu's avatar
LiangLiu committed
387
                                                                                                            '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
388
                                                                                                            page === templateCurrentPage
LiangLiu's avatar
LiangLiu committed
389
390
                                                                                                                ? '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
391
392
393
394
                                                                                                        ]"
                                                                                                        :aria-current="page === templateCurrentPage ? 'page' : undefined">
                                                                                                        {{ page }}
                                                                                                    </button>
LiangLiu's avatar
LiangLiu committed
395
                                                                                                    <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
396
397
398
399
400
                                                                                                </template>

                                                                                                <!-- 下一页按钮 -->
                                                                                                <button @click="goToTemplatePage(templateCurrentPage + 1)"
                                                                                                    :disabled="templateCurrentPage >= templatePaginationInfo.total_pages"
LiangLiu's avatar
LiangLiu committed
401
                                                                                                    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
402
403
404
                                                                                                    :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
405
                                                                                                    <i class="fas fa-chevron-right text-xs mx-auto" aria-hidden="true"></i>
LiangLiu's avatar
LiangLiu committed
406
407
408
409
                                                                                                </button>
                                                                                            </nav>
                                                                                        </div>
                                                                                    </div>
LiangLiu's avatar
LiangLiu committed
410
                                        <div class="overflow-y-auto flex-1 max-h-[60vh] main-scrollbar pr-2 pt-2">
LiangLiu's avatar
LiangLiu committed
411
412
413
414
415
                                            <div class="space-y-4">
                                                <div v-if="isPageLoading" class="flex items-center justify-center">
                                                    <div class="inline-flex items-center gap-3 px-4 py-2 rounded-full bg-white/90 dark:bg-[#2c2c2e]/90 border border-black/8 dark:border-white/8 text-sm text-[#1d1d1f] dark:text-[#f5f5f7] shadow-[0_4px_16px_rgba(0,0,0,0.08)] dark:shadow-[0_4px_16px_rgba(0,0,0,0.35)]">
                                                        <i class="fas fa-spinner fa-spin text-[color:var(--brand-primary)] dark:text-[color:var(--brand-primary-light)]"></i>
                                                        <span>{{ t('loading') }}</span>
LiangLiu's avatar
LiangLiu committed
416
                                                    </div>
LiangLiu's avatar
LiangLiu committed
417
418
419
420
421
422
423
424
                                                </div>
                                                <div v-if="audioTemplates.length > 0" class="space-y-3 px-1">
                                                    <div v-for="template in audioTemplates" :key="template.filename"
                                                        @click="selectAudioTemplate(template)"
                                                        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">
                                                            <div
                                                                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
425
                                                        </div>
LiangLiu's avatar
LiangLiu committed
426
427
428
429
430
431
432
433
                                                        <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>
                                                                </div>
LiangLiu's avatar
LiangLiu committed
434
                                                        </div>
LiangLiu's avatar
LiangLiu committed
435
436
437
438
439
440
441
442
443
444
                                                        <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'"
                                                                style="pointer-events: auto;">
                                                            <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>
                                                        </button>
                                                    </div>
LiangLiu's avatar
LiangLiu committed
445
                                                </div>
LiangLiu's avatar
LiangLiu committed
446
447
448
449
450
451
452
                                                <div v-else
                                                    class="flex flex-col items-center justify-center py-12 text-center">
                                                    <div
                                                        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>
                                                </div>
                                                <p class="text-[#1d1d1f] dark:text-[#f5f5f7] text-lg font-medium tracking-tight">目前暂无音频模板</p>
LiangLiu's avatar
LiangLiu committed
453
454
455
456
457
458
459
460
461
                                            </div>
                                            </div>
                                        </div>

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