mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 21:12:49 +08:00
fix: address all known follow-up issues except NetEase batch precheck
Surface failures + tighten edges:
1. Toast for /play-song & /play-playlist failures. Backend now returns
{ok, message} (localized in Chinese to match the rest of the UI).
Store stashes a notification on ok=false; new Toast.vue mounted in
App.vue shows it for 3-5s then fades. Clicking the X dismisses
immediately. Sits above the player on desktop and above the mobile
tabbar on phones.
2. QQ collected playlists pagination. fcg_get_profile_order_asset.fcg
returns max 30 per call; we now loop using has_more / short-page
detection up to a 300-playlist hard cap. Single-call users (typical)
exit the loop on the first iteration so no extra requests.
3. getPlayableSongIds chunking. 100 mids per request keeps URL well
under 8KB; chunk-level errors are isolated so a transient blip on
one chunk doesn't poison the whole batch. Returns null only when
every chunk failed (caller falls back to sequential retry).
4. SourceTabs single-source mode now renders a small subdued "网易云"
or "QQ" label instead of vanishing entirely, so the user always
knows which platform's data they're looking at.
5. Hide the "我的歌单 N" count badge when N=0 — Home and Library no
longer show "我的歌单 0" with an empty grid.
6. Auth state change invalidates fetchHomeData cache. Previously, a
user who logged out as account A and into account B within 5
minutes would see A's playlists. Now we always re-check auth at
the top of fetchHomeData and bypass the TTL cache when authStatus
has changed.
Out of scope:
- NetEase analogous batch precheck (per request).
- 60s TS3 UDP idle disconnect — that's the bundled @honeybbq/teamspeak-
client UDP layer kicking when no server packet arrives in 60s. It's
baked in (constant `v=6e4`) and not exposed as an option, and root
cause is server-side or network-layer behavior we can't reach from
here.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
221079b89c
commit
59fb3ee3bd
8 files changed
+245
-76
No files matched your search
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<Transition name="toast">
|
||||
<div v-if="visible && current" class="toast" :class="`toast--${current.type}`">
|
||||
<Icon :icon="current.type === 'error' ? 'mdi:alert-circle' : 'mdi:information'" class="toast-icon" />
|
||||
<span class="toast-msg">{{ current.message }}</span>
|
||||
<button class="toast-close" @click="visible = false" aria-label="关闭">
|
||||
<Icon icon="mdi:close" />
|
||||
</button>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onUnmounted } from 'vue';
|
||||
import { Icon } from '@iconify/vue';
|
||||
import { usePlayerStore } from '../stores/player.js';
|
||||
|
||||
const store = usePlayerStore();
|
||||
const visible = ref(false);
|
||||
const current = ref<{ id: number; message: string; type: 'error' | 'info' } | null>(null);
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
watch(
|
||||
() => store.notification?.id,
|
||||
() => {
|
||||
if (!store.notification) return;
|
||||
current.value = store.notification;
|
||||
visible.value = true;
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
visible.value = false;
|
||||
}, current.value.type === 'error' ? 5000 : 3000);
|
||||
}
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) clearTimeout(timer);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.toast {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
bottom: calc(var(--player-height) + 24px);
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
max-width: min(90vw, 480px);
|
||||
background: var(--bg-card);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-dropdown);
|
||||
font-size: var(--fs-body);
|
||||
color: var(--text-primary);
|
||||
z-index: 1000;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
bottom: calc(var(--player-height) + 60px); // sit above mobile tabbar
|
||||
}
|
||||
}
|
||||
|
||||
.toast--error {
|
||||
border-color: var(--brand-netease);
|
||||
.toast-icon { color: var(--brand-netease); }
|
||||
}
|
||||
|
||||
.toast--info {
|
||||
border-color: var(--color-primary);
|
||||
.toast-icon { color: var(--color-primary); }
|
||||
}
|
||||
|
||||
.toast-icon {
|
||||
font-size: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toast-msg {
|
||||
flex: 1;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.toast-close {
|
||||
flex-shrink: 0;
|
||||
font-size: 18px;
|
||||
color: var(--text-tertiary);
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
&:hover { color: var(--text-primary); }
|
||||
}
|
||||
|
||||
.toast-enter-active,
|
||||
.toast-leave-active {
|
||||
transition: opacity var(--transition-fast), transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.toast-enter-from,
|
||||
.toast-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(-50%) translateY(8px);
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user