mirror of
https://github.com/atdunbg/Nekosonic-Music.git
synced 2026-08-06 11:54:18 +08:00
新功能: - 亮色主题:新增浅色外观模式,7种主题色各有对应亮色变体 - 封面主色背景:漫游抽屉自动提取封面主色,PlayerBar跟随继承 - 发现页重做:多类型搜索(歌曲/歌手/专辑)+搜索建议+搜索历史 - 漫游页重做:进入即播放,布局改为封面+歌名+播放/下一首/减少推荐 - 减少推荐:FM模式下可标记不推荐歌曲或歌手 - 列表风格统一:播放指示器跳动动画+hover播放图标+图标统一使用Lucide 修复: - 专辑页艺术家过多时窗口缩小竖排,改为自动换行 - FM播放时退出登录后首页仍可点击下一首 - 本地音乐播放时缓冲进度条未重置 - 亮色主题下多处文字不可见 - 退出FM模式时状态未正确清理 - 暗色模式下关闭抽屉时PlayerBar闪烁亮色(改用opacity过渡) - player.ts tickInterval双变量状态不同步,统一为clearTick/setTick 变更: - 移除播放列表按钮数字角标 - 主页卡片标题固定白色不随主题变化 - 全项目空catch块格式统一 - 清理冗余注释和代码
82 lines
2.5 KiB
Vue
82 lines
2.5 KiB
Vue
<template>
|
|
<div class="relative" ref="container">
|
|
<button
|
|
@click="toggle"
|
|
class="flex items-center justify-between bg-subtle border border-line rounded-lg px-3 py-1.5 text-sm text-content outline-none transition min-w-[140px] max-w-[320px] hover:border-content-3 focus:border-accent focus:shadow-[0_0_0_2px_var(--c-accent-dim)]"
|
|
:class="{ 'border-accent shadow-[0_0_0_2px_var(--c-accent-dim)]': isOpen }"
|
|
>
|
|
<span class="truncate">{{ currentLabel }}</span>
|
|
<IconChevronDown style="font-size: 12px" class="transition-transform flex-shrink-0 ml-2" :class="{ 'rotate-180': isOpen }" />
|
|
</button>
|
|
<Transition name="dropdown">
|
|
<div v-if="isOpen" class="absolute right-0 top-full mt-1 bg-surface border border-line rounded-lg shadow-xl z-50 py-1 min-w-full max-w-[360px] overflow-hidden">
|
|
<button
|
|
v-for="(label, key) in options"
|
|
:key="key"
|
|
@click="select(key)"
|
|
class="w-full text-left px-3 py-2 text-sm transition flex items-center justify-between gap-2"
|
|
:class="modelValue === key ? 'bg-accent-dim text-accent-text' : 'text-content-2 hover:bg-subtle hover:text-content'"
|
|
>
|
|
<span class="truncate">{{ label }}</span>
|
|
<IconCheck v-if="modelValue === key" style="font-size: 14px" />
|
|
</button>
|
|
</div>
|
|
</Transition>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
|
|
import IconChevronDown from '~icons/lucide/chevron-down';
|
|
import IconCheck from '~icons/lucide/check';
|
|
|
|
const props = defineProps<{
|
|
modelValue: string;
|
|
options: Record<string, string>;
|
|
}>();
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: string];
|
|
}>();
|
|
|
|
const isOpen = ref(false);
|
|
const container = ref<HTMLElement | null>(null);
|
|
|
|
const currentLabel = computed(() => props.options[props.modelValue] || '');
|
|
|
|
function toggle() {
|
|
isOpen.value = !isOpen.value;
|
|
}
|
|
|
|
function select(key: string) {
|
|
emit('update:modelValue', key);
|
|
isOpen.value = false;
|
|
}
|
|
|
|
function onClickOutside(e: MouseEvent) {
|
|
if (container.value && !container.value.contains(e.target as Node)) {
|
|
isOpen.value = false;
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
document.addEventListener('click', onClickOutside);
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
document.removeEventListener('click', onClickOutside);
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.dropdown-enter-active,
|
|
.dropdown-leave-active {
|
|
transition: all 0.15s ease;
|
|
}
|
|
.dropdown-enter-from,
|
|
.dropdown-leave-to {
|
|
opacity: 0;
|
|
transform: translateY(-4px);
|
|
}
|
|
</style>
|