Skip to content

Commit c3d3b2e

Browse files
committed
🐞 fix: 修复我喜欢列表数量 & 本地歌单无法删除歌曲
1 parent 439fa0f commit c3d3b2e

9 files changed

Lines changed: 125 additions & 36 deletions

File tree

electron/preload/index.d.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ declare global {
1414
export(data: any): Promise<{ success: boolean; path?: string; error?: string }>;
1515
import(): Promise<{ success: boolean; data?: any; error?: string }>;
1616
};
17-
log: {
18-
info(message: string, ...args: unknown[]): void;
19-
warn(message: string, ...args: unknown[]): void;
20-
error(message: string, ...args: unknown[]): void;
21-
debug(message: string, ...args: unknown[]): void;
22-
};
17+
};
18+
// logs
19+
logger: {
20+
info: (message: string, ...args: unknown[]) => void;
21+
warn: (message: string, ...args: unknown[]) => void;
22+
error: (message: string, ...args: unknown[]) => void;
23+
debug: (message: string, ...args: unknown[]) => void;
2324
};
2425
}
2526
}

electron/preload/index.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,17 @@ if (process.contextIsolated) {
1818
export: (data: any) => ipcRenderer.invoke("store-export", data),
1919
import: () => ipcRenderer.invoke("store-import"),
2020
},
21-
// Renderer logging API
22-
log: {
23-
info: (message: string, ...args: unknown[]) =>
24-
ipcRenderer.send("renderer-log", "info", message, args),
25-
warn: (message: string, ...args: unknown[]) =>
26-
ipcRenderer.send("renderer-log", "warn", message, args),
27-
error: (message: string, ...args: unknown[]) =>
28-
ipcRenderer.send("renderer-log", "error", message, args),
29-
debug: (message: string, ...args: unknown[]) =>
30-
ipcRenderer.send("renderer-log", "debug", message, args),
31-
},
21+
});
22+
// Expose logger API via preload
23+
contextBridge.exposeInMainWorld("logger", {
24+
info: (message: string, ...args: unknown[]) =>
25+
ipcRenderer.send("renderer-log", "info", message, args),
26+
warn: (message: string, ...args: unknown[]) =>
27+
ipcRenderer.send("renderer-log", "warn", message, args),
28+
error: (message: string, ...args: unknown[]) =>
29+
ipcRenderer.send("renderer-log", "error", message, args),
30+
debug: (message: string, ...args: unknown[]) =>
31+
ipcRenderer.send("renderer-log", "debug", message, args),
3232
});
3333
} catch (error) {
3434
console.error(error);

src/api/playlist.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import request from "@/utils/request";
22

3-
// 获取歌单详情
3+
/**
4+
* 获取歌单详情
5+
* @param {number} id - 歌单 id
6+
*/
47
export const playlistDetail = (id: number) => {
58
return request({
69
url: "/playlist/detail",
@@ -13,7 +16,12 @@ export const playlistDetail = (id: number) => {
1316
});
1417
};
1518

16-
// 获取歌单所有歌曲
19+
/**
20+
* 获取歌单所有歌曲
21+
* @param {number} id - 歌单 id
22+
* @param {number} [limit=50] - 返回数量,默认 50
23+
* @param {number} [offset=0] - 偏移数量,默认 0
24+
*/
1725
export const playlistAllSongs = (id: number, limit: number = 50, offset: number = 0) => {
1826
return request({
1927
url: "/playlist/track/all",

src/components/Layout/Menu.vue

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ const { isDesktop } = useMobile();
6262
// 菜单数据
6363
const menuRef = ref<MenuInst | null>(null);
6464
const menuActiveKey = ref<string | number>((router.currentRoute.value.name as string) || "home");
65-
const playlistMode = ref<"online" | "local">("online");
6665
6766
// 刷新私人漫游
6867
const handleRefreshFM = async (e: Event) => {
@@ -217,7 +216,7 @@ const menuOptions = computed<MenuOption[] | MenuGroupOption[]>(() => {
217216
label: () =>
218217
h("div", { class: "user-list" }, [
219218
h(NText, { depth: 3 }, () =>
220-
playlistMode.value === "online" ? "创建的歌单" : "本地歌单",
219+
statusStore.playlistMode === "online" ? "创建的歌单" : "本地歌单",
221220
),
222221
h(
223222
NPopselect,
@@ -226,10 +225,10 @@ const menuOptions = computed<MenuOption[] | MenuGroupOption[]>(() => {
226225
{ label: "在线歌单", value: "online" },
227226
{ label: "本地歌单", value: "local" },
228227
],
229-
value: playlistMode.value,
228+
value: statusStore.playlistMode,
230229
trigger: "click",
231230
onUpdateValue: (value: "online" | "local") => {
232-
playlistMode.value = value;
231+
statusStore.playlistMode = value;
233232
},
234233
},
235234
() =>
@@ -250,12 +249,12 @@ const menuOptions = computed<MenuOption[] | MenuGroupOption[]>(() => {
250249
renderIcon: renderIcon("Add"),
251250
onclick: (event: Event) => {
252251
event.stopPropagation();
253-
openCreatePlaylist(playlistMode.value === "local");
252+
openCreatePlaylist(statusStore.playlistMode === "local");
254253
},
255254
}),
256255
]),
257256
children:
258-
playlistMode.value === "online"
257+
statusStore.playlistMode === "online"
259258
? [...createPlaylist.value]
260259
: [...localPlaylistMenu.value],
261260
},

src/composables/useSongMenu.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,9 @@ export const useSongMenu = () => {
157157
const isLocal = !!song?.path;
158158
const isLoginNormal = isLogin() === 1;
159159
const isCurrent = statusStore.playIndex === index;
160-
const isUserPlaylist = !!playListId && userPlaylistsData.some((pl) => pl.id === playListId);
160+
const isLocalPlaylist = playListId?.toString().length === 16;
161+
const isUserPlaylist =
162+
(!!playListId && userPlaylistsData.some((pl) => pl.id === playListId)) || isLocalPlaylist;
161163
const isDownloading = dataStore.downloadingSongs.some((item) => item.song.id === song.id);
162164

163165
return [
@@ -294,7 +296,7 @@ export const useSongMenu = () => {
294296
show:
295297
settingStore.contextMenuOptions.deleteFromPlaylist &&
296298
isUserPlaylist &&
297-
isLoginNormal &&
299+
(isLocalPlaylist || isLoginNormal) &&
298300
!isCloud,
299301
props: {
300302
onClick: () => deleteSongs(playListId!, [song.id], () => emit("removeSong", [song.id])),

src/stores/status.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ interface StatusState {
149149
pointA: number | null;
150150
pointB: number | null;
151151
};
152+
/** 侧边栏歌单显示模式 */
153+
playlistMode: "online" | "local";
152154
}
153155

154156
export const useStatusStore = defineStore("status", {
@@ -224,6 +226,7 @@ export const useStatusStore = defineStore("status", {
224226
pointA: null,
225227
pointB: null,
226228
},
229+
playlistMode: "online",
227230
}),
228231
getters: {
229232
// 播放音量图标
@@ -431,6 +434,7 @@ export const useStatusStore = defineStore("status", {
431434
"developerMode",
432435
"themeBackgroundMode",
433436
"backgroundConfig",
437+
"playlistMode",
434438
],
435439
},
436440
});

src/types/global.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,12 @@ declare global {
2020
import: () => Promise<{ success: boolean; data?: any; error?: string }>;
2121
};
2222
};
23+
// logs
24+
logger: {
25+
info: (message: string, ...args: unknown[]) => void;
26+
warn: (message: string, ...args: unknown[]) => void;
27+
error: (message: string, ...args: unknown[]) => void;
28+
debug: (message: string, ...args: unknown[]) => void;
29+
};
2330
}
2431
}

src/utils/auth.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
} from "@/api/user";
1414
import { likeSong } from "@/api/song";
1515
import { formatCoverList, formatArtistsList, formatSongsList } from "@/utils/format";
16-
import { useDataStore, useMusicStore } from "@/stores";
16+
import { useDataStore, useMusicStore, useLocalStore } from "@/stores";
1717
import { logout, refreshLogin } from "@/api/login";
1818
import { debounce, isFunction, type DebouncedFunc } from "lodash-es";
1919
import { isBeforeSixAM } from "./time";
@@ -526,6 +526,22 @@ export const deleteSongs = async (pid: number, ids: number[], callback?: () => v
526526
positiveText: "删除",
527527
negativeText: "取消",
528528
onPositiveClick: async () => {
529+
// 本地歌单
530+
if (pid.toString().length === 16) {
531+
const localStore = useLocalStore();
532+
const success = await localStore.removeSongsFromLocalPlaylist(
533+
pid,
534+
ids.map((id) => id.toString()),
535+
);
536+
if (success) {
537+
if (isFunction(callback)) callback();
538+
window.$message.success("删除成功");
539+
} else {
540+
window.$message.error("删除失败");
541+
}
542+
return;
543+
}
544+
// 在线歌单
529545
const result = await playlistTracks(pid, ids, "del");
530546
if (result.status === 200) {
531547
if (result.body?.code !== 200) {

src/views/List/liked.vue

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
import type { DropdownOption } from "naive-ui";
4545
import { SongType } from "@/types/main";
4646
import { songDetail } from "@/api/song";
47-
import { playlistDetail } from "@/api/playlist";
47+
import { playlistDetail, playlistAllSongs } from "@/api/playlist";
4848
import { formatCoverList, formatSongsList } from "@/utils/format";
4949
import { renderIcon, copyData } from "@/utils/helper";
5050
import { isObject } from "lodash-es";
@@ -61,8 +61,16 @@ const dataStore = useDataStore();
6161
// 是否激活
6262
const isActivated = ref<boolean>(false);
6363
64-
const { detailData, listData, loading, getSongListHeight, setDetailData, setListData, setLoading } =
65-
useListDetail();
64+
const {
65+
detailData,
66+
listData,
67+
loading,
68+
getSongListHeight,
69+
setDetailData,
70+
setListData,
71+
appendListData,
72+
setLoading,
73+
} = useListDetail();
6674
const { searchValue, searchData, displayData, clearSearch, performSearch } =
6775
useListSearch(listData);
6876
const { listScrolling, handleListScroll, resetScroll } = useListScroll();
@@ -181,12 +189,21 @@ const loadPlaylistData = async (id: number, forceRefresh: boolean = false) => {
181189
setDetailData(formatCoverList(detail.playlist)[0]);
182190
// 获取全部 ID 顺序
183191
const serverIds: number[] = detail.privileges?.map((p: any) => p.id) || [];
184-
if (serverIds.length === 0) {
185-
setLoading(false);
186-
return;
192+
const trackCount = detail.playlist?.trackCount || 0;
193+
194+
// 如果 privileges 数量少于 trackCount,说明数据不完整,需要全量获取
195+
if (serverIds.length < trackCount && trackCount > 0) {
196+
console.log(`🔄 Liked songs incomplete (${serverIds.length}/${trackCount}), fetching all...`);
197+
await fetchAllSongs(id, trackCount);
198+
} else {
199+
if (serverIds.length === 0) {
200+
setLoading(false);
201+
return;
202+
}
203+
// 同步歌曲列表
204+
await syncSongList(serverIds, id);
187205
}
188-
// 同步歌曲列表
189-
await syncSongList(serverIds, id);
206+
190207
// 更新缓存
191208
if (currentRequestId.value === id && detailData.value) {
192209
dataStore.setLikeSongsList(detailData.value, listData.value);
@@ -200,6 +217,41 @@ const loadPlaylistData = async (id: number, forceRefresh: boolean = false) => {
200217
}
201218
};
202219
220+
/**
221+
* 全量获取歌曲列表
222+
* 当 privileges 数据不完整时调用
223+
*/
224+
const fetchAllSongs = async (id: number, total: number) => {
225+
const limit = 500;
226+
let offset = 0;
227+
const allSongs: SongType[] = [];
228+
229+
while (offset < total) {
230+
if (currentRequestId.value !== id) return;
231+
try {
232+
const result = await playlistAllSongs(id, limit, offset);
233+
if (currentRequestId.value !== id) return;
234+
const songs = formatSongsList(result.songs);
235+
allSongs.push(...songs);
236+
// 实时更新列表展示
237+
if (offset === 0) {
238+
setListData(songs);
239+
} else {
240+
appendListData(songs);
241+
}
242+
offset += limit;
243+
} catch (error) {
244+
console.error("Failed to fetch all songs:", error);
245+
break;
246+
}
247+
}
248+
249+
if (currentRequestId.value !== id) return;
250+
// 确保最终列表完整性
251+
setListData(allSongs);
252+
console.log(`✅ Fetched all ${allSongs.length} liked songs`);
253+
};
254+
203255
/**
204256
* 加载缓存
205257
*/

0 commit comments

Comments
 (0)