diff --git a/src/main/server.ts b/src/main/server.ts index e92f390..1c17f36 100644 --- a/src/main/server.ts +++ b/src/main/server.ts @@ -39,7 +39,7 @@ Object.entries(neteaseApi).forEach(([name, handler]) => { try { const result = await handler({ ...req.query, - cookie: `MUSIC_U=${req.cookies['MUSIC_U']}`, + cookie: req.cookies, }) setCache(name, result.body, req.query) diff --git a/src/renderer/api/album.ts b/src/renderer/api/album.ts index e580704..5338073 100644 --- a/src/renderer/api/album.ts +++ b/src/renderer/api/album.ts @@ -27,3 +27,23 @@ export function fetchAlbum( params: { ...params, ...otherParams }, }) } + +export interface LikeAAlbumParams { + t: 1 | 2 + id: number +} +export interface LikeAAlbumResponse { + code: number +} +export function likeAAlbum( + params: LikeAAlbumParams +): Promise { + return request({ + url: '/album/sub', + method: 'post', + params: { + ...params, + timestamp: Date.now(), + }, + }) +} diff --git a/src/renderer/api/playlist.ts b/src/renderer/api/playlist.ts index 8939edc..2fce625 100644 --- a/src/renderer/api/playlist.ts +++ b/src/renderer/api/playlist.ts @@ -4,6 +4,7 @@ export enum PlaylistApiNames { FETCH_PLAYLIST = 'fetchPlaylist', FETCH_RECOMMENDED_PLAYLISTS = 'fetchRecommendedPlaylists', FETCH_DAILY_RECOMMEND_PLAYLISTS = 'fetchDailyRecommendPlaylists', + LIKE_A_PLAYLIST = 'likeAPlaylist', } // 歌单详情 @@ -70,3 +71,23 @@ export function fetchDailyRecommendPlaylists(): Promise { + return request({ + url: '/playlist/subscribe', + method: 'post', + params: { + ...params, + timestamp: Date.now(), + }, + }) +} diff --git a/src/renderer/api/track.ts b/src/renderer/api/track.ts index cceee5b..6c57061 100644 --- a/src/renderer/api/track.ts +++ b/src/renderer/api/track.ts @@ -130,7 +130,9 @@ export interface LikeATrackResponse { playlistId: number songs: Track[] } -export function likeATrack(params: LikeATrackParams) { +export function likeATrack( + params: LikeATrackParams +): Promise { return request({ url: '/like', method: 'post', diff --git a/src/renderer/api/user.ts b/src/renderer/api/user.ts index 3314d0c..fde4359 100644 --- a/src/renderer/api/user.ts +++ b/src/renderer/api/user.ts @@ -164,7 +164,9 @@ export interface FetchUserAlbumsResponse { count: number data: Album[] } -export function fetchUserAlbums(params: FetchUserAlbumsParams) { +export function fetchUserAlbums( + params: FetchUserAlbumsParams +): Promise { return request({ url: '/album/sublist', method: 'get', diff --git a/src/renderer/components/Sidebar.tsx b/src/renderer/components/Sidebar.tsx index 52c8f7f..ad4d9b8 100644 --- a/src/renderer/components/Sidebar.tsx +++ b/src/renderer/components/Sidebar.tsx @@ -60,11 +60,7 @@ const PrimaryTabs = () => { } const Playlists = () => { - const { data: user } = useUser() - const { data: playlists } = useUserPlaylists({ - uid: user?.account?.id ?? 0, - offset: 0, - }) + const { data: playlists } = useUserPlaylists() return (
diff --git a/src/renderer/hooks/useUserAlbums.ts b/src/renderer/hooks/useUserAlbums.ts index 00d4df3..5f6cb5f 100644 --- a/src/renderer/hooks/useUserAlbums.ts +++ b/src/renderer/hooks/useUserAlbums.ts @@ -1,12 +1,16 @@ +import { likeAAlbum } from '@/api/album' import type { FetchUserAlbumsParams, FetchUserAlbumsResponse } from '@/api/user' import { UserApiNames, fetchUserAlbums } from '@/api/user' +import { useQueryClient } from 'react-query' +import useUser from './useUser' -export default function useUserAlbums(params: FetchUserAlbumsParams) { +export default function useUserAlbums(params: FetchUserAlbumsParams = {}) { + const { data: user } = useUser() return useQuery( - [UserApiNames.FETCH_USER_ALBUMS, params], + [UserApiNames.FETCH_USER_ALBUMS, user?.profile?.userId ?? 0], () => fetchUserAlbums(params), { - placeholderData: (): FetchUserAlbumsResponse => + placeholderData: (): FetchUserAlbumsResponse | undefined => window.ipcRenderer?.sendSync('getApiCacheSync', { api: 'album/sublist', query: params, @@ -14,3 +18,56 @@ export default function useUserAlbums(params: FetchUserAlbumsParams) { } ) } + +export const useMutationLikeAAlbum = () => { + const queryClient = useQueryClient() + const { data: user } = useUser() + const { data: userAlbums } = useUserAlbums({ limit: 2000 }) + const uid = user?.account?.id ?? 0 + const key = [UserApiNames.FETCH_USER_ALBUMS, uid] + + return useMutation( + async (album: Album) => { + if (!album.id || userAlbums?.data === undefined) { + throw new Error('album id is required or userAlbums is undefined') + } + const response = await likeAAlbum({ + id: album.id, + t: userAlbums?.data.findIndex(a => a.id === album.id) > -1 ? 2 : 1, + }) + if (response.code !== 200) throw new Error((response as any).msg) + return response + }, + { + onMutate: async album => { + // Cancel any outgoing refetches (so they don't overwrite our optimistic update) + await queryClient.cancelQueries(key) + + // Snapshot the previous value + const previousData = queryClient.getQueryData(key) + + // Optimistically update to the new value + queryClient.setQueryData(key, old => { + const userAlbums = old as FetchUserAlbumsResponse + const albums = userAlbums.data + const newAlbums = + albums.findIndex(a => a.id === album.id) > -1 + ? albums.filter(a => a.id !== album.id) + : [...albums, album] + return { + ...userAlbums, + data: newAlbums, + } + }) + + // Return a context object with the snapshotted value + return { previousData } + }, + // If the mutation fails, use the context returned from onMutate to roll back + onError: (err, trackID, context) => { + queryClient.setQueryData(key, (context as any).previousData) + toast((err as any).toString()) + }, + } + ) +} diff --git a/src/renderer/hooks/useUserLikedTracksIDs.ts b/src/renderer/hooks/useUserLikedTracksIDs.ts index 9b6c6c1..d3442de 100644 --- a/src/renderer/hooks/useUserLikedTracksIDs.ts +++ b/src/renderer/hooks/useUserLikedTracksIDs.ts @@ -33,14 +33,16 @@ export const useMutationLikeATrack = () => { const key = [UserApiNames.FETCH_USER_LIKED_TRACKS_IDS, uid] return useMutation( - (trackID: number) => { + async (trackID: number) => { if (!trackID || userLikedSongs?.ids === undefined) { throw new Error('trackID is required or userLikedSongs is undefined') } - return likeATrack({ + const response = await likeATrack({ id: trackID, like: !userLikedSongs.ids.includes(trackID), }) + if (response.code !== 200) throw new Error((response as any).msg) + return response }, { onMutate: async trackID => { @@ -57,7 +59,6 @@ export const useMutationLikeATrack = () => { const newIds = ids.includes(trackID) ? ids.filter(id => id !== trackID) : [...ids, trackID] - console.log(trackID, ids.includes(trackID), ids, newIds) return { ...likedSongs, ids: newIds, @@ -70,10 +71,7 @@ export const useMutationLikeATrack = () => { // If the mutation fails, use the context returned from onMutate to roll back onError: (err, trackID, context) => { queryClient.setQueryData(key, (context as any).previousData) - }, - // Always refetch after error or success: - onSettled: () => { - queryClient.invalidateQueries(key) + toast((err as any).toString()) }, } ) diff --git a/src/renderer/hooks/useUserPlaylists.ts b/src/renderer/hooks/useUserPlaylists.ts index a1b6c32..b722c2e 100644 --- a/src/renderer/hooks/useUserPlaylists.ts +++ b/src/renderer/hooks/useUserPlaylists.ts @@ -1,13 +1,25 @@ -import type { - FetchUserPlaylistsParams, - FetchUserPlaylistsResponse, -} from '@/api/user' +import { likeAPlaylist } from '@/api/playlist' +import type { FetchUserPlaylistsResponse } from '@/api/user' import { UserApiNames, fetchUserPlaylists } from '@/api/user' +import { useQueryClient } from 'react-query' +import useUser from './useUser' + +export default function useUserPlaylists() { + const { data: user } = useUser() + const uid = user?.profile?.userId ?? 0 + + const params = { + uid: uid, + offset: 0, + limit: 2000, + } -export default function useUserPlaylists(params: FetchUserPlaylistsParams) { return useQuery( - [UserApiNames.FETCH_USER_PLAYLISTS, params], + [UserApiNames.FETCH_USER_PLAYLISTS, uid], async () => { + if (!params.uid) { + throw new Error('请登录后再请求用户收藏的歌单') + } const data = await fetchUserPlaylists(params) return data }, @@ -27,3 +39,59 @@ export default function useUserPlaylists(params: FetchUserPlaylistsParams) { } ) } + +export const useMutationLikeAPlaylist = () => { + const queryClient = useQueryClient() + const { data: user } = useUser() + const { data: userPlaylists } = useUserPlaylists() + const uid = user?.account?.id ?? 0 + const key = [UserApiNames.FETCH_USER_PLAYLISTS, uid] + + return useMutation( + async (playlist: Playlist) => { + if (!playlist.id || userPlaylists?.playlist === undefined) { + throw new Error('playlist id is required or userPlaylists is undefined') + } + const response = await likeAPlaylist({ + id: playlist.id, + t: + userPlaylists.playlist.findIndex(p => p.id === playlist.id) > -1 + ? 2 + : 1, + }) + if (response.code !== 200) throw new Error((response as any).msg) + return response + }, + { + onMutate: async playlist => { + // Cancel any outgoing refetches (so they don't overwrite our optimistic update) + await queryClient.cancelQueries(key) + + // Snapshot the previous value + const previousData = queryClient.getQueryData(key) + + // Optimistically update to the new value + queryClient.setQueryData(key, old => { + const userPlaylists = old as FetchUserPlaylistsResponse + const playlists = userPlaylists.playlist + const newPlaylists = + playlists.findIndex(p => p.id === playlist.id) > -1 + ? playlists.filter(p => p.id !== playlist.id) + : [...playlists, playlist] + return { + ...userPlaylists, + playlist: newPlaylists, + } + }) + + // Return a context object with the snapshotted value + return { previousData } + }, + // If the mutation fails, use the context returned from onMutate to roll back + onError: (err, trackID, context) => { + queryClient.setQueryData(key, (context as any).previousData) + toast((err as any).toString()) + }, + } + ) +} diff --git a/src/renderer/pages/Album.tsx b/src/renderer/pages/Album.tsx index ebe9630..306d902 100644 --- a/src/renderer/pages/Album.tsx +++ b/src/renderer/pages/Album.tsx @@ -16,6 +16,8 @@ import { scrollToTop, } from '@/utils/common' import useTracks from '@/hooks/useTracks' +import useUserAlbums, { useMutationLikeAAlbum } from '@/hooks/useUserAlbums' +import useUser from '@/hooks/useUser' const PlayButton = ({ album, @@ -79,6 +81,13 @@ const Header = ({ const [isCoverError, setCoverError] = useState(false) + const { data: userAlbums } = useUserAlbums() + const isThisAlbumLiked = useMemo(() => { + if (!album) return false + return !!userAlbums?.data?.find(a => a.id === album.id) + }, [album, userAlbums?.data]) + const mutationLikeAAlbum = useMutationLikeAAlbum() + return ( <> {/* Header background */} @@ -189,11 +198,16 @@ const Header = ({ - + {!isThisPlaylistCreatedByCurrentUser && ( + + )}