fix: corner-case audit — distinguish failures, recover from blips

Three small but real correctness fixes from auditing recent commits:

1. getPlayableSongIds returned an empty Set for both "endpoint failed"
   and "succeeded but all unplayable" — the caller couldn't tell which.
   Return Set | null now: null = error (fall through to sequential
   retry), empty Set = authoritative "all unplayable" (short-circuit
   to a clear message instead of wasting 20+ retries).

2. Web store: fetchHomeData unconditionally wrote lastFetchTime even
   when every fetch rejected (network blip, server down). That cached
   the failure for 5 minutes — user had to hard-reload to recover.
   Now only commit lastFetchTime if at least one auth-status call
   succeeded.

3. Settings profile section: if GET /profile failed, profileConfigs
   stayed undefined and the row showed "加载中..." forever. Track a
   per-bot error state and render an inline "加载失败 / 重试" link
   so the user can recover without page reload.

Out of scope but documented:
- ein=29 hardcoded in fetchCollectedPlaylists (no pagination yet —
  users with 30+ collected QQ playlists get truncated).
- /play-song single-failure UX (returns "Cannot play" message but
  frontend ignores; needs a global toast/notification primitive).
- NetEase has no analogous batch precheck (could surface same
  "click and wait silent" issue if user has region-restricted NetEase
  playlists).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
saopig1andClaude Opus 4.7 committed 2026-05-06 15:46:31 +08:00
1 parent 3d0aca52d0
commit 892d9f7959
4 files changed
+57 -15

No files matched your search

+24 -3
View File
@@ -414,7 +414,11 @@
<span class="profile-bot-name">{{ bot.name }}</span>
</button>
<div v-if="profileExpanded[bot.id]" class="profile-toggles">
<div v-if="!profileConfigs[bot.id]" class="profile-loading">加载中...</div>
<div v-if="profileLoadError[bot.id]" class="profile-loading profile-error">
{{ profileLoadError[bot.id] }}
<button class="btn-link" @click="loadProfileConfig(bot.id)">重试</button>
</div>
<div v-else-if="!profileConfigs[bot.id]" class="profile-loading">加载中...</div>
<label
v-else
v-for="t in PROFILE_TOGGLES"
@@ -771,14 +775,18 @@ const PROFILE_TOGGLES: ReadonlyArray<{
const profileConfigs = reactive<Record<string, ProfileConfig>>({});
const profileExpanded = reactive<Record<string, boolean>>({});
const profileLoadError = reactive<Record<string, string | null>>({});
async function loadProfileConfig(botId: string) {
if (profileConfigs[botId]) return;
profileLoadError[botId] = null;
try {
const res = await axios.get(`/api/player/${botId}/profile`);
profileConfigs[botId] = res.data;
} catch {
// bot not loaded or no profile manager yet
} catch (err: any) {
profileLoadError[botId] = err?.response?.status === 404
? '机器人未加载'
: '加载失败,请重试';
}
}
@@ -1299,6 +1307,19 @@ onUnmounted(() => {
text-align: center;
}
.profile-error {
color: var(--brand-netease); // re-uses red brand color for error state
.btn-link {
margin-left: 8px;
font-size: 13px;
color: var(--color-primary);
text-decoration: underline;
background: transparent;
cursor: pointer;
}
}
.profile-toggle {
display: flex;
align-items: center;