mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 13:02:49 +08:00
Implements the NetEase-parity login features for the Kugou provider now that
QR login works:
- getDailyRecommendSongs (每日推荐), getUserPlaylists (我的歌单), and a real
getRecommendPlaylists (推荐歌单, was a stub) ported from the reference API.
- mapKugouSong now extracts cover art per endpoint (sizable_cover / cover /
trans_param.union_cover, resolving the {size} template) — Kugou songs had no
artwork before.
- Fix the playlist-song shape (combined "歌手 - 歌名" in `name`, mixsongid as the
audio id) so opened playlists show real titles instead of 未知歌曲.
- New defensive playlist mappers keyed on global_collection_id (the only id
getPlaylistSongs can open); dedup user playlists in case the list endpoint
ignores pagination; firstStr() so an empty-string field can't mask a real one.
Frontend wires Kugou as a third home-discovery source (Source type, store
caches/auth, availableSources, fetchHomeData, Home FM card + source tabs,
SourceTabs label, persisted-tab whitelist). SourceTabs now highlights the
fallback-corrected source so the active tab shows when a logged-out source was
persisted (newly possible with 3 sources).
Adds kugou.test.ts coverage for the new mappers, the cover/empty-string and
playlist-shape handling, and id openability.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
96 lines
2.0 KiB
Vue
96 lines
2.0 KiB
Vue
<template>
|
|
<div v-if="sources.length > 0" class="source-tabs">
|
|
<template v-if="sources.length >= 2">
|
|
<button
|
|
v-for="src in sources"
|
|
:key="src"
|
|
type="button"
|
|
class="source-tab"
|
|
:class="{ active: src === modelValue }"
|
|
@click="$emit('update:modelValue', src)"
|
|
>
|
|
{{ LABELS[src] }}
|
|
</button>
|
|
</template>
|
|
<span v-else class="source-tab-label" :title="`数据来自${LABELS[sources[0]]}`">
|
|
{{ LABELS[sources[0]] }}
|
|
</span>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { Source } from '../stores/player.js';
|
|
|
|
const LABELS: Record<Source, string> = {
|
|
netease: '网易云',
|
|
qq: 'QQ',
|
|
kugou: '酷狗',
|
|
};
|
|
|
|
defineProps<{
|
|
modelValue: Source;
|
|
sources: Source[];
|
|
}>();
|
|
|
|
defineEmits<{
|
|
'update:modelValue': [value: Source];
|
|
}>();
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.source-tabs {
|
|
display: inline-flex;
|
|
gap: 4px;
|
|
margin-left: 12px;
|
|
align-items: center;
|
|
}
|
|
|
|
.source-tab {
|
|
padding: 4px 10px;
|
|
min-height: 28px;
|
|
font-size: var(--fs-sm);
|
|
font-weight: var(--fw-medium);
|
|
color: var(--text-secondary);
|
|
background: transparent;
|
|
border: none;
|
|
border-radius: var(--radius-sm);
|
|
cursor: pointer;
|
|
transition: color var(--transition-fast), background var(--transition-fast);
|
|
|
|
&:hover {
|
|
color: var(--text-primary);
|
|
background: var(--hover-bg);
|
|
}
|
|
|
|
&.active {
|
|
color: var(--color-primary);
|
|
background: var(--color-primary-12);
|
|
font-weight: var(--fw-semi);
|
|
}
|
|
}
|
|
|
|
// Single-source mode: not interactive, but tells the user which platform
|
|
// they're looking at instead of leaving them guessing.
|
|
.source-tab-label {
|
|
padding: 4px 10px;
|
|
font-size: var(--fs-xs);
|
|
font-weight: var(--fw-medium);
|
|
color: var(--text-tertiary);
|
|
background: var(--hover-bg);
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.source-tabs {
|
|
margin-left: 8px;
|
|
gap: 2px;
|
|
}
|
|
|
|
.source-tab {
|
|
padding: 6px 10px;
|
|
min-height: 36px; // larger touch target on mobile
|
|
font-size: var(--fs-xs);
|
|
}
|
|
}
|
|
</style>
|