mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
feat: add Vue.js WebUI — YesPlayMusic-inspired SPA with all pages and components
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
2a966e914c
commit
694e3c953b
25 files changed
+5388
-1
No files matched your search
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<div class="playlist-page">
|
||||
<div v-if="loading" class="loading">加载中...</div>
|
||||
|
||||
<template v-else-if="playlist">
|
||||
<!-- Hero Header -->
|
||||
<div class="playlist-hero">
|
||||
<CoverArt :url="playlist.coverUrl" :size="200" :radius="14" :show-shadow="true" />
|
||||
<div class="playlist-meta">
|
||||
<h1 class="playlist-title">{{ playlist.name }}</h1>
|
||||
<p class="playlist-desc">{{ playlist.description }}</p>
|
||||
<div class="playlist-stats">
|
||||
{{ playlist.songCount }} 首歌曲
|
||||
</div>
|
||||
<button class="play-all-btn" @click="playAll">
|
||||
<Icon icon="mdi:play" />
|
||||
播放全部
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Song List -->
|
||||
<div class="song-list">
|
||||
<SongCard
|
||||
v-for="(song, i) in songs"
|
||||
:key="song.id"
|
||||
:song="song"
|
||||
:index="i + 1"
|
||||
:active="store.currentSong?.id === song.id"
|
||||
@play="store.play(song.name, song.platform)"
|
||||
@add="store.addToQueue(song.name, song.platform)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { Icon } from '@iconify/vue';
|
||||
import axios from 'axios';
|
||||
import { usePlayerStore } from '../stores/player.js';
|
||||
import CoverArt from '../components/CoverArt.vue';
|
||||
import SongCard from '../components/SongCard.vue';
|
||||
|
||||
const store = usePlayerStore();
|
||||
const route = useRoute();
|
||||
|
||||
interface PlaylistDetail {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
coverUrl: string;
|
||||
songCount: number;
|
||||
}
|
||||
|
||||
const playlist = ref<PlaylistDetail | null>(null);
|
||||
const songs = ref<Array<{ id: string; name: string; artist: string; album: string; duration: number; coverUrl: string; platform: string }>>([]);
|
||||
const loading = ref(true);
|
||||
|
||||
async function playAll() {
|
||||
if (songs.value.length > 0) {
|
||||
const first = songs.value[0];
|
||||
await store.play(first.name, first.platform);
|
||||
for (let i = 1; i < songs.value.length; i++) {
|
||||
await store.addToQueue(songs.value[i].name, songs.value[i].platform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const id = route.params.id as string;
|
||||
const platform = (route.query.platform as string) || 'netease';
|
||||
|
||||
try {
|
||||
const res = await axios.get(`/api/music/playlist/${id}`, {
|
||||
params: { platform },
|
||||
});
|
||||
playlist.value = res.data.playlist;
|
||||
songs.value = res.data.songs;
|
||||
} catch {
|
||||
// Ignore if API not ready
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.playlist-hero {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
|
||||
.playlist-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.playlist-title {
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.playlist-desc {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.playlist-stats {
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.play-all-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 10px 28px;
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
border-radius: var(--radius-lg);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
width: fit-content;
|
||||
transition: transform var(--transition-fast);
|
||||
|
||||
&:hover { transform: scale(1.04); }
|
||||
&:active { transform: scale(0.96); }
|
||||
}
|
||||
|
||||
.song-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 60px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user