mirror of
https://github.com/atdunbg/Nekosonic-Music.git
synced 2026-08-06 03:44:18 +08:00
- 详情页统一 DetailLayout 组件(常驻头部 + 独立滚动 + 紧凑态动画) - 歌曲/评论 tab 切换(懒加载 + 保活),专辑模糊搜索 - 皮肤系统 skins.ts(19 语义色 + 14 预设 + 自定义皮肤 + 壁纸) - 设置页皮肤模块(外观/主题色/皮肤编辑器/壁纸选择) - 可折叠侧边栏 + TopBar + 折叠态歌单浮层(高度不越 PlayerBar) - 首页扩展至 6 区块 + 通用卡片组件 + 虚拟歌曲列表 - audio.rs 拆为 8 模块,api.ts 拆为 api/ 目录,player store 拆为 4 store - 修复播放竞态、seek 暂停、评论提前加载、紧凑态卡死等
62 lines
2.1 KiB
Vue
62 lines
2.1 KiB
Vue
<template>
|
|
<div class="relative flex-shrink-0" ref="menuRef">
|
|
<button @click.stop="toggle" class="text-content-3 hover:text-content transition p-1 rounded-md hover:bg-subtle">
|
|
<IconEllipsis class="w-4 h-4 fill-current" />
|
|
</button>
|
|
<div v-if="open"
|
|
class="absolute right-0 top-full mt-1 bg-surface border border-line rounded-xl shadow-xl z-50 py-1 min-w-[120px]">
|
|
<button @click.stop="handleComment" class="w-full flex items-center gap-2 px-3 py-2 text-sm text-content-2 hover:bg-subtle hover:text-content transition">
|
|
<IconMessageSquare style="font-size: 14px" />
|
|
评论
|
|
</button>
|
|
<button @click.stop="handleShare" class="w-full flex items-center gap-2 px-3 py-2 text-sm text-content-2 hover:bg-subtle hover:text-content transition">
|
|
<IconShare2 style="font-size: 14px" />
|
|
分享
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onBeforeUnmount, onMounted } from 'vue';
|
|
import { useUiStore } from '../stores/ui';
|
|
import { showToast } from '../composables/useToast';
|
|
import IconEllipsis from '~icons/lucide/ellipsis';
|
|
import IconMessageSquare from '~icons/lucide/message-square';
|
|
import IconShare2 from '~icons/lucide/share-2';
|
|
|
|
const ui = useUiStore();
|
|
const props = defineProps<{ songId: number }>();
|
|
const open = ref(false);
|
|
const menuRef = ref<HTMLElement | null>(null);
|
|
|
|
function toggle() {
|
|
open.value = !open.value;
|
|
}
|
|
|
|
function handleComment() {
|
|
open.value = false;
|
|
ui.openCommentForSong(props.songId);
|
|
}
|
|
|
|
async function handleShare() {
|
|
open.value = false;
|
|
const url = `https://music.163.com/song?id=${props.songId}`;
|
|
try {
|
|
await navigator.clipboard.writeText(url);
|
|
showToast('链接已复制', 'success');
|
|
} catch {
|
|
showToast('复制失败', 'error');
|
|
}
|
|
}
|
|
|
|
function onClickOutside(e: MouseEvent) {
|
|
if (menuRef.value && !menuRef.value.contains(e.target as Node)) {
|
|
open.value = false;
|
|
}
|
|
}
|
|
|
|
onMounted(() => document.addEventListener('click', onClickOutside));
|
|
onBeforeUnmount(() => document.removeEventListener('click', onClickOutside));
|
|
</script>
|