mirror of
https://github.com/Moe-Sakura/frontend.git
synced 2026-03-15 04:53:18 +08:00
feat: implement sound settings management in App and SettingsModal components
- Added sound settings functionality, allowing users to enable or disable sound effects. - Integrated sound initialization and synchronization with user settings in App.vue. - Updated SettingsModal.vue to include a dedicated sound settings card with toggle functionality. - Enhanced sound management in useSound.ts to support initialization and synchronization with settings store.
This commit is contained in:
14
src/App.vue
14
src/App.vue
@@ -62,7 +62,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineAsyncComponent, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { defineAsyncComponent, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import AnimatedBackground from '@/components/AnimatedBackground.vue'
|
||||
import { useSearchStore } from '@/stores/search'
|
||||
import { useUIStore } from '@/stores/ui'
|
||||
@@ -75,6 +75,7 @@ import {
|
||||
} from '@/utils/theme'
|
||||
import { scheduleIdleTask } from '@/composables/usePerformance'
|
||||
import { useBackgroundImage } from '@/composables/useBackgroundImage'
|
||||
import { initSoundFromSettings, syncSoundWithSettings } from '@/composables/useSound'
|
||||
|
||||
// 关键组件 - 同步加载
|
||||
import StatsCorner from '@/components/StatsCorner.vue'
|
||||
@@ -137,6 +138,9 @@ onMounted(async () => {
|
||||
// 初始化 UI Store(恢复持久化状态 + 会话状态)
|
||||
uiStore.init()
|
||||
|
||||
// 初始化音效设置
|
||||
initSoundFromSettings(settingsStore.settings.enableSound)
|
||||
|
||||
// URL hash 优先级最高 - 覆盖会话状态
|
||||
const hash = window.location.hash
|
||||
if (hash.startsWith('#atk-comment-')) {
|
||||
@@ -174,6 +178,14 @@ onUnmounted(() => {
|
||||
destroyBackground()
|
||||
})
|
||||
|
||||
// 监听音效设置变化
|
||||
watch(
|
||||
() => settingsStore.settings.enableSound,
|
||||
(enabled) => {
|
||||
syncSoundWithSettings(enabled)
|
||||
},
|
||||
)
|
||||
|
||||
// 设置相关函数
|
||||
function saveSettings(customApi: string, newCustomCSS: string) {
|
||||
// 保存自定义 API 到 search store
|
||||
|
||||
@@ -56,56 +56,37 @@
|
||||
<!-- 内容区域 -->
|
||||
<div class="flex-1 overflow-y-auto custom-scrollbar">
|
||||
<div class="max-w-3xl mx-auto px-4 sm:px-6 py-6 sm:py-8 space-y-6">
|
||||
<!-- 主题设置卡片 -->
|
||||
<div
|
||||
class="settings-card"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-violet-500 to-purple-500 flex items-center justify-center shadow-lg shadow-violet-500/30">
|
||||
<Palette :size="20" class="text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-lg font-bold text-gray-800 dark:text-white">外观主题</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-slate-400">选择亮色、暗色或跟随系统</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主题选项 -->
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<button
|
||||
v-for="option in themeOptions"
|
||||
:key="option.value"
|
||||
type="button"
|
||||
:class="[
|
||||
'flex flex-col items-center gap-2 p-4 rounded-xl transition-all duration-200',
|
||||
uiStore.themeMode === option.value
|
||||
? 'bg-[#ff1493]/10 border-2 border-[#ff1493] dark:border-[#ff69b4]'
|
||||
: 'bg-slate-50 dark:bg-slate-800/60 border-2 border-transparent hover:border-pink-200 dark:hover:border-pink-900'
|
||||
]"
|
||||
@click="handleThemeChange(option.value)"
|
||||
>
|
||||
<!-- 图标 -->
|
||||
<div
|
||||
:class="[
|
||||
'w-10 h-10 rounded-xl flex items-center justify-center transition-colors',
|
||||
uiStore.themeMode === option.value
|
||||
? 'bg-[#ff1493] text-white'
|
||||
: 'bg-gray-200 dark:bg-slate-700 text-gray-600 dark:text-slate-400'
|
||||
]"
|
||||
>
|
||||
<component :is="option.icon" :size="20" />
|
||||
<!-- 音效设置卡片 -->
|
||||
<div class="settings-card">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-pink-500 to-rose-500 flex items-center justify-center shadow-lg shadow-pink-500/30">
|
||||
<Volume2 :size="20" class="text-white" />
|
||||
</div>
|
||||
<!-- 标签 -->
|
||||
<div>
|
||||
<h2 class="text-lg font-bold text-gray-800 dark:text-white">音效</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-slate-400">界面交互音效</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 开关 -->
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
:aria-checked="localEnableSound"
|
||||
:class="[
|
||||
'relative inline-flex h-7 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none',
|
||||
localEnableSound
|
||||
? 'bg-[#ff1493]'
|
||||
: 'bg-gray-300 dark:bg-slate-600'
|
||||
]"
|
||||
@click="toggleSound"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
'text-sm font-medium',
|
||||
uiStore.themeMode === option.value
|
||||
? 'text-[#ff1493] dark:text-[#ff69b4]'
|
||||
: 'text-gray-700 dark:text-slate-300'
|
||||
'pointer-events-none inline-block h-6 w-6 transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out',
|
||||
localEnableSound ? 'translate-x-5' : 'translate-x-0'
|
||||
]"
|
||||
>
|
||||
{{ option.label }}
|
||||
</span>
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,13 +95,40 @@
|
||||
<div
|
||||
class="settings-card"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-cyan-500 to-blue-500 flex items-center justify-center shadow-lg shadow-cyan-500/30">
|
||||
<Server :size="20" class="text-white" />
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-cyan-500 to-blue-500 flex items-center justify-center shadow-lg shadow-cyan-500/30">
|
||||
<Server :size="20" class="text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-lg font-bold text-gray-800 dark:text-white">聚搜 API 后端</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-slate-400">选择或自定义 URL 地址</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-lg font-bold text-gray-800 dark:text-white">聚搜 API 后端</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-slate-400">选择或自定义 URL 地址</p>
|
||||
<!-- 部署后端 & 贡献 API 按钮 -->
|
||||
<div class="flex items-center gap-2">
|
||||
<a
|
||||
:href="deployUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium text-cyan-600 dark:text-cyan-400 bg-cyan-50 dark:bg-cyan-950/40 border border-cyan-200 dark:border-cyan-800/50 hover:bg-cyan-100 dark:hover:bg-cyan-950/60 active:scale-95 transition-all"
|
||||
@click="playTap"
|
||||
>
|
||||
<Github :size="14" />
|
||||
<span class="hidden sm:inline">部署后端</span>
|
||||
<span class="sm:hidden">部署</span>
|
||||
</a>
|
||||
<a
|
||||
:href="contributeUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium text-cyan-600 dark:text-cyan-400 bg-cyan-50 dark:bg-cyan-950/40 border border-cyan-200 dark:border-cyan-800/50 hover:bg-cyan-100 dark:hover:bg-cyan-950/60 active:scale-95 transition-all"
|
||||
@click="playTap"
|
||||
>
|
||||
<Plus :size="14" />
|
||||
<span class="hidden sm:inline">贡献 API</span>
|
||||
<span class="sm:hidden">贡献</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -191,7 +199,7 @@
|
||||
v-if="selectedApiOption === 'custom'"
|
||||
class="overflow-hidden"
|
||||
>
|
||||
<div class="mt-4 space-y-3">
|
||||
<div class="mt-4">
|
||||
<div class="relative">
|
||||
<LinkIcon :size="18" class="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400" />
|
||||
<input
|
||||
@@ -202,18 +210,6 @@
|
||||
@input="handleTyping"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-xs text-gray-500 dark:text-slate-400">
|
||||
<Github :size="14" />
|
||||
<span>部署后端:</span>
|
||||
<a
|
||||
href="https://github.com/Moe-Sakura/Wrangler-API"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-[#ff1493] dark:text-[#ff69b4] hover:underline"
|
||||
>
|
||||
Moe-Sakura/Wrangler-API
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
@@ -467,7 +463,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { playTap, playCelebration, playSelect, playType } from '@/composables/useSound'
|
||||
import { playTap, playCelebration, playSelect, playType, playToggleOn, playToggleOff } from '@/composables/useSound'
|
||||
|
||||
// Prism Editor
|
||||
import { PrismEditor } from 'vue-prism-editor'
|
||||
@@ -529,29 +525,14 @@ import {
|
||||
Check,
|
||||
Github,
|
||||
X,
|
||||
Palette,
|
||||
Sun,
|
||||
Moon,
|
||||
Monitor,
|
||||
Plus,
|
||||
Volume2,
|
||||
} from 'lucide-vue-next'
|
||||
import { useUIStore, type ThemeMode } from '@/stores/ui'
|
||||
import { useSettingsStore, DEFAULT_API_CONFIG } from '@/stores/settings'
|
||||
import apiData from '@/data/api.json'
|
||||
|
||||
const uiStore = useUIStore()
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
// 主题选项
|
||||
const themeOptions = [
|
||||
{ value: 'light' as ThemeMode, label: '亮色', icon: Sun },
|
||||
{ value: 'system' as ThemeMode, label: '系统', icon: Monitor },
|
||||
{ value: 'dark' as ThemeMode, label: '暗色', icon: Moon },
|
||||
]
|
||||
|
||||
function handleThemeChange(mode: ThemeMode) {
|
||||
playSelect()
|
||||
uiStore.setThemeMode(mode)
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
isOpen: boolean
|
||||
customApi: string
|
||||
@@ -563,46 +544,33 @@ const emit = defineEmits<{
|
||||
save: [customApi: string, customCSS: string]
|
||||
}>()
|
||||
|
||||
// API 服务器选项
|
||||
// API 服务器选项 - 从 JSON 读取
|
||||
const apiOptions = [
|
||||
{ value: 'cfapi', label: 'Cloudflare' },
|
||||
{ value: 'api', label: '香港 雨云' },
|
||||
{ value: 'gzapi', label: '广州 腾讯云' },
|
||||
{ value: 'usapi', label: '洛杉矶 CloudCone' },
|
||||
{ value: 'jpapi', label: '东京 ClawCloud' },
|
||||
{ value: 'deapi', label: '法兰克福 ClawCloud' },
|
||||
...apiData.servers.map(server => ({ value: server.key, label: server.label })),
|
||||
{ value: 'custom', label: '自定义' },
|
||||
]
|
||||
|
||||
// API URL 映射
|
||||
const apiUrls: Record<string, string> = {
|
||||
cfapi: 'https://cf.api.searchgal.homes',
|
||||
api: 'https://api.searchgal.homes',
|
||||
gzapi: 'https://gz.api.searchgal.homes',
|
||||
usapi: 'https://us.api.searchgal.homes',
|
||||
jpapi: 'https://jp.api.searchgal.homes',
|
||||
deapi: 'https://de.api.searchgal.homes',
|
||||
}
|
||||
// API URL 映射 - 从 JSON 读取
|
||||
const apiUrls: Record<string, string> = Object.fromEntries(
|
||||
apiData.servers.map(server => [server.key, server.url]),
|
||||
)
|
||||
|
||||
// 部署后端 & 贡献 API 的 URL
|
||||
const deployUrl = apiData.deployUrl
|
||||
const contributeUrl = apiData.contributeUrl
|
||||
|
||||
// 根据 URL 判断选中的选项
|
||||
function getOptionFromUrl(url: string): string {
|
||||
if (!url || url === apiUrls.cfapi) {
|
||||
return 'cfapi'
|
||||
// 空 URL 或匹配第一个服务器(默认)
|
||||
const defaultKey = apiData.servers[0]?.key || 'cfapi'
|
||||
if (!url || url === apiUrls[defaultKey]) {
|
||||
return defaultKey
|
||||
}
|
||||
if (url === apiUrls.api) {
|
||||
return 'api'
|
||||
}
|
||||
if (url === apiUrls.gzapi) {
|
||||
return 'gzapi'
|
||||
}
|
||||
if (url === apiUrls.usapi) {
|
||||
return 'usapi'
|
||||
}
|
||||
if (url === apiUrls.jpapi) {
|
||||
return 'jpapi'
|
||||
}
|
||||
if (url === apiUrls.deapi) {
|
||||
return 'deapi'
|
||||
// 遍历查找匹配的服务器
|
||||
for (const [key, serverUrl] of Object.entries(apiUrls)) {
|
||||
if (url === serverUrl) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
return 'custom'
|
||||
}
|
||||
@@ -700,6 +668,26 @@ const localAiTranslateModel = ref(settingsStore.settings.aiTranslateModel)
|
||||
const localBackgroundImageApiUrl = ref(settingsStore.settings.backgroundImageApiUrl)
|
||||
const localVideoParseApiUrl = ref(settingsStore.settings.videoParseApiUrl)
|
||||
|
||||
// 音效设置
|
||||
const localEnableSound = ref(settingsStore.settings.enableSound)
|
||||
|
||||
// 切换音效
|
||||
function toggleSound() {
|
||||
localEnableSound.value = !localEnableSound.value
|
||||
// 播放对应的开关音效
|
||||
if (localEnableSound.value) {
|
||||
// 临时启用音效来播放开启音
|
||||
settingsStore.updateSetting('enableSound', true)
|
||||
playToggleOn()
|
||||
} else {
|
||||
playToggleOff()
|
||||
// 延迟关闭,让关闭音效播放完
|
||||
setTimeout(() => {
|
||||
settingsStore.updateSetting('enableSound', false)
|
||||
}, 150)
|
||||
}
|
||||
}
|
||||
|
||||
// 计算最终的 API 地址
|
||||
const localCustomApi = computed(() => {
|
||||
if (selectedApiOption.value === 'custom') {
|
||||
@@ -750,6 +738,8 @@ watch(() => props.isOpen, (isOpen) => {
|
||||
localAiTranslateModel.value = settingsStore.settings.aiTranslateModel
|
||||
localBackgroundImageApiUrl.value = settingsStore.settings.backgroundImageApiUrl
|
||||
localVideoParseApiUrl.value = settingsStore.settings.videoParseApiUrl
|
||||
// 同步音效设置
|
||||
localEnableSound.value = settingsStore.settings.enableSound
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
|
||||
@@ -5,9 +5,19 @@
|
||||
|
||||
import { ref } from 'vue'
|
||||
|
||||
// 音效是否启用
|
||||
// 音效是否启用(初始值,会被 settings store 覆盖)
|
||||
const soundEnabled = ref(true)
|
||||
|
||||
// 初始化音效设置(从 settings store 同步)
|
||||
export function initSoundFromSettings(enabled: boolean): void {
|
||||
soundEnabled.value = enabled
|
||||
}
|
||||
|
||||
// 监听 settings 变化的函数(供外部调用)
|
||||
export function syncSoundWithSettings(enabled: boolean): void {
|
||||
soundEnabled.value = enabled
|
||||
}
|
||||
|
||||
// AudioContext 单例
|
||||
let audioContext: AudioContext | null = null
|
||||
|
||||
|
||||
31
src/data/api.json
Normal file
31
src/data/api.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"servers": [
|
||||
{
|
||||
"key": "cfapi",
|
||||
"label": "Cloudflare",
|
||||
"url": "https://cf.api.searchgal.homes"
|
||||
},
|
||||
{
|
||||
"key": "api",
|
||||
"label": "香港 雨云",
|
||||
"url": "https://api.searchgal.homes"
|
||||
},
|
||||
{
|
||||
"key": "gzapi",
|
||||
"label": "广州 腾讯云",
|
||||
"url": "https://gz.api.searchgal.homes"
|
||||
},
|
||||
{
|
||||
"key": "jpapi",
|
||||
"label": "东京 ClawCloud",
|
||||
"url": "https://jp.api.searchgal.homes"
|
||||
},
|
||||
{
|
||||
"key": "deapi",
|
||||
"label": "法兰克福 ClawCloud",
|
||||
"url": "https://de.api.searchgal.homes"
|
||||
}
|
||||
],
|
||||
"deployUrl": "https://github.com/Moe-Sakura/Wrangler-API",
|
||||
"contributeUrl": "https://github.com/Moe-Sakura/frontend/edit/dev/src/data/api.json"
|
||||
}
|
||||
@@ -12,6 +12,7 @@ export interface UserSettings {
|
||||
showPlatformIcons: boolean
|
||||
compactMode: boolean
|
||||
enableNotifications: boolean
|
||||
enableSound: boolean
|
||||
// API 高级配置
|
||||
vndbApiBaseUrl: string
|
||||
vndbImageProxyUrl: string
|
||||
@@ -44,6 +45,7 @@ const DEFAULT_SETTINGS: UserSettings = {
|
||||
showPlatformIcons: true,
|
||||
compactMode: false,
|
||||
enableNotifications: true,
|
||||
enableSound: true,
|
||||
// API 高级配置
|
||||
vndbApiBaseUrl: DEFAULT_API_CONFIG.vndbApiBaseUrl,
|
||||
vndbImageProxyUrl: DEFAULT_API_CONFIG.vndbImageProxyUrl,
|
||||
|
||||
Reference in New Issue
Block a user