feat(PreferencePanel, usePreferences): add PreferencePanel component and integrate preference settings

This commit is contained in:
Astrian Zheng 2025-05-28 12:34:14 +10:00
parent e245afd709
commit 885a7dabab
Signed by: Astrian
SSH Key Fingerprint: SHA256:rVnhx3DAKjujCwWE13aDl7uV6+9U1MvydLkNRXJrBiA
3 changed files with 166 additions and 4 deletions

View File

@ -1,13 +1,23 @@
<script setup lang="ts"> <script setup lang="ts">
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import Player from './components/Player.vue' import Player from './components/Player.vue'
import PreferencePanel from './components/PreferencePanel.vue'
import { ref } from 'vue'
import LeftArrowIcon from './assets/icons/leftarrow.vue' import LeftArrowIcon from './assets/icons/leftarrow.vue'
// import SearchIcon from './assets/icons/search.vue' // import SearchIcon from './assets/icons/search.vue'
import CorgIcon from './assets/icons/corg.vue' import CorgIcon from './assets/icons/corg.vue'
import { watch } from 'vue'
const presentPreferencePanel = ref(true)
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
watch(() => presentPreferencePanel, (value) => {
console.log(value)
})
</script> </script>
<template> <template>
@ -54,7 +64,8 @@ const router = useRouter()
</button> --> </button> -->
<button <button
class="text-white w-9 h-9 bg-neutral-800/80 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center"> class="text-white w-9 h-9 bg-neutral-800/80 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center"
@click="presentPreferencePanel = true">
<CorgIcon :size="4" /> <CorgIcon :size="4" />
</button> </button>
@ -64,5 +75,6 @@ const router = useRouter()
</div> </div>
<RouterView /> <RouterView />
</div> </div>
<PreferencePanel :present="presentPreferencePanel" @dismiss="presentPreferencePanel = false" />
</div> </div>
</template> </template>

View File

@ -0,0 +1,144 @@
<script lang="ts" setup>
import { watch } from 'vue';
import XIcon from '../assets/icons/x.vue'
import { usePreferences } from '../stores/usePreferences'
const preferences = usePreferences()
const props = defineProps<{
present: boolean
}>()
defineEmits<{
(e: 'dismiss'): void
}>()
watch(() => props.present, (value) => console.log(value))
</script>
<template>
<Transition name="modal">
<div v-if="present"
class="bg-black/30 w-screen h-screen absolute top-0 left-0 z-30 flex justify-center items-center select-none"
@click="$emit('dismiss')">
<div
class="bg-neutral-900/80 shadow-[0_0_16px_0_rgba(0,0,0,0.5)] backdrop-blur-2xl border border-[#ffffff39] rounded-lg w-[60rem] h-3/4 relative overflow-scroll modal-content"
@click.stop>
<div
class="flex justify-between items-center p-8 sticky top-0 bg-gradient-to-b from-neutral-900 to-neutral-900/0 z-10">
<div class="text-white text-2xl font-semibold">偏好设置</div>
<button
class="text-white w-9 h-9 bg-neutral-800/80 border border-[#ffffff39] rounded-full text-center backdrop-blur-3xl flex justify-center items-center transition-all duration-200 hover:bg-neutral-700/80"
@click="$emit('dismiss')">
<XIcon :size="4" />
</button>
</div>
<div class="flex flex-col gap-4">
<div>
<div class="px-8">
<div class="text-white/50 text-sm ml-6">播放间</div>
<ul class="border border-[#ffffff39] rounded-lg backdrop-blur-lg mt-2 overflow-hidden">
<li class="odd:bg-neutral-300/5">
<button
class="flex justify-between items-center px-6 py-4 w-full text-left hover:bg-neutral-300/10 transition-all"
@click="preferences.displayTimeLeft = !preferences.displayTimeLeft">
<div class="flex flex-col">
<div class="text-base text-white">播放进度条右侧显示剩余时间</div>
<div class="text-sm text-white/80">而非歌曲总时长</div>
</div>
<div>
<div class="text-sky-500 text-lg" v-if="preferences.displayTimeLeft">开启</div>
<div class="text-neutral-500 text-lg" v-else>关闭</div>
</div>
</button>
</li>
<li class="odd:bg-neutral-300/5">
<button
class="flex justify-between items-center px-6 py-4 w-full text-left hover:bg-neutral-300/10 transition-all"
@click="preferences.presentLyrics = !preferences.presentLyrics">
<div class="flex flex-col">
<div class="text-base text-white">显示滚动歌词文本</div>
<div class="text-sm text-white/80">当歌词文本可用时</div>
</div>
<div>
<div class="text-sky-500 text-lg" v-if="preferences.presentLyrics">开启</div>
<div class="text-neutral-500 text-lg" v-else>关闭</div>
</div>
</button>
</li>
</ul>
</div>
</div>
<div>
<div class="px-8">
<div class="text-white/50 text-sm ml-6">行为</div>
<ul class="border border-[#ffffff39] rounded-lg backdrop-blur-lg mt-2 overflow-hidden">
<li class="odd:bg-neutral-300/5">
<button
class="flex justify-between items-center px-6 py-4 w-full text-left hover:bg-neutral-300/10 transition-all"
@click="preferences.autoRedirect = !preferences.autoRedirect">
<div class="flex flex-col">
<div class="text-base text-white">自动重定向</div>
<div class="text-sm text-white/80">当尝试访问塞壬唱片官网时重定向到 MSR Mod</div>
</div>
<div>
<div class="text-sky-500 text-lg" v-if="preferences.autoRedirect">开启</div>
<div class="text-neutral-500 text-lg" v-else>关闭</div>
</div>
</button>
</li>
</ul>
<div class="text-white/50 text-sm ml-6 mt-2">即使此项目关闭随时都可以通过点按 MSR Mod 扩展图标启动 MSR Mod</div>
</div>
</div>
<div>
<div class="px-8">
<div class="text-white/50 text-sm ml-6">关于</div>
<ul class="border border-[#ffffff39] rounded-lg backdrop-blur-lg mt-2 overflow-hidden">
<li class="odd:bg-neutral-300/5">
<button
class="flex justify-between items-center px-6 py-4 w-full text-left hover:bg-neutral-300/10 transition-all">
<div class="flex flex-col">
<div class="text-base text-white">MSR Mod</div>
<div class="text-sm text-white/80">版本号 0.0.1</div>
</div>
</button>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</Transition>
</template>
<style scoped>
.modal-enter-active,
.modal-leave-active {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.modal-enter-from,
.modal-leave-to {
opacity: 0;
}
.modal-enter-active .modal-content,
.modal-leave-active .modal-content {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.modal-enter-from .modal-content,
.modal-leave-to .modal-content {
opacity: 0;
transform: scale(0.95) translateY(1rem);
}
</style>

View File

@ -11,6 +11,7 @@ declare global {
export const usePreferences = defineStore('preferences', () => { export const usePreferences = defineStore('preferences', () => {
const displayTimeLeft = ref<boolean>(false) const displayTimeLeft = ref<boolean>(false)
const presentLyrics = ref<boolean>(false) const presentLyrics = ref<boolean>(false)
const autoRedirect = ref<boolean>(true)
const isLoaded = ref(false) const isLoaded = ref(false)
const storageType = ref<'chrome' | 'localStorage' | 'memory'>('chrome') const storageType = ref<'chrome' | 'localStorage' | 'memory'>('chrome')
@ -18,7 +19,8 @@ export const usePreferences = defineStore('preferences', () => {
// 默认偏好设置 // 默认偏好设置
const defaultPreferences = { const defaultPreferences = {
displayTimeLeft: false, displayTimeLeft: false,
presentLyrics: false presentLyrics: false,
autoRedirect: true
} }
// 检测可用的 API // 检测可用的 API
@ -140,7 +142,8 @@ export const usePreferences = defineStore('preferences', () => {
const savePreferences = async () => { const savePreferences = async () => {
const preferences = { const preferences = {
displayTimeLeft: displayTimeLeft.value, displayTimeLeft: displayTimeLeft.value,
presentLyrics: presentLyrics.value presentLyrics: presentLyrics.value,
autoRedirect: autoRedirect.value
} }
await setStoredValue('preferences', preferences) await setStoredValue('preferences', preferences)
} }
@ -151,16 +154,18 @@ export const usePreferences = defineStore('preferences', () => {
const preferences = await getPreferences() const preferences = await getPreferences()
displayTimeLeft.value = preferences.displayTimeLeft displayTimeLeft.value = preferences.displayTimeLeft
presentLyrics.value = preferences.presentLyrics presentLyrics.value = preferences.presentLyrics
autoRedirect.value = preferences.autoRedirect
isLoaded.value = true isLoaded.value = true
} catch (error) { } catch (error) {
displayTimeLeft.value = false displayTimeLeft.value = false
presentLyrics.value = false presentLyrics.value = false
autoRedirect.value = true
isLoaded.value = true isLoaded.value = true
} }
} }
// 监听变化并保存 // 监听变化并保存
watch([displayTimeLeft, presentLyrics], async () => { watch([displayTimeLeft, presentLyrics, autoRedirect], async () => {
if (isLoaded.value) { if (isLoaded.value) {
try { try {
await savePreferences() await savePreferences()
@ -176,6 +181,7 @@ export const usePreferences = defineStore('preferences', () => {
return { return {
displayTimeLeft, displayTimeLeft,
presentLyrics, presentLyrics,
autoRedirect,
isLoaded, isLoaded,
storageType, storageType,
initializePreferences, initializePreferences,