feat: 支持收藏歌曲

This commit is contained in:
qier222 2022-04-05 02:30:07 +08:00
parent bbcf1f9340
commit bbd5299341
No known key found for this signature in database
GPG key ID: 9C85007ED905F14D
7 changed files with 127 additions and 49 deletions

View file

@ -120,3 +120,23 @@ export function fetchLyric(
params, params,
}) })
} }
export interface LikeATrackParams {
id: number
like: boolean
}
export interface LikeATrackResponse {
code: number
playlistId: number
songs: Track[]
}
export function likeATrack(params: LikeATrackParams) {
return request({
url: '/like',
method: 'post',
params: {
...params,
timestamp: Date.now(),
},
})
}

View file

@ -2,7 +2,7 @@ import request from '@/utils/request'
export enum UserApiNames { export enum UserApiNames {
FETCH_USER_ACCOUNT = 'fetchUserAccount', FETCH_USER_ACCOUNT = 'fetchUserAccount',
FETCH_USER_LIKED_SONGS_IDS = 'fetchUserLikedSongsIDs', FETCH_USER_LIKED_TRACKS_IDS = 'fetchUserLikedTracksIDs',
FETCH_USER_PLAYLISTS = 'fetchUserPlaylists', FETCH_USER_PLAYLISTS = 'fetchUserPlaylists',
} }
@ -113,17 +113,17 @@ export function fetchUserPlaylists(
}) })
} }
export interface FetchUserLikedSongsIDsParams { export interface FetchUserLikedTracksIDsParams {
uid: number uid: number
} }
export interface FetchUserLikedSongsIDsResponse { export interface FetchUserLikedTracksIDsResponse {
code: number code: number
checkPoint: number checkPoint: number
ids: number[] ids: number[]
} }
export function fetchUserLikedSongsIDs( export function fetchUserLikedTracksIDs(
params: FetchUserLikedSongsIDsParams params: FetchUserLikedTracksIDsParams
): Promise<FetchUserLikedSongsIDsResponse> { ): Promise<FetchUserLikedTracksIDsResponse> {
return request({ return request({
url: '/likelist', url: '/likelist',
method: 'get', method: 'get',

View file

@ -2,8 +2,9 @@ import ArtistInline from '@/components/ArtistsInline'
import IconButton from '@/components/IconButton' import IconButton from '@/components/IconButton'
import Slider from '@/components/Slider' import Slider from '@/components/Slider'
import SvgIcon from '@/components/SvgIcon' import SvgIcon from '@/components/SvgIcon'
import useUser from '@/hooks/useUser' import useUserLikedTracksIDs, {
import useUserLikedSongsIDs from '@/hooks/useUserLikedSongsIDs' useMutationLikeATrack,
} from '@/hooks/useUserLikedTracksIDs'
import { player } from '@/store' import { player } from '@/store'
import { resizeImage } from '@/utils/common' import { resizeImage } from '@/utils/common'
import { State as PlayerState, Mode as PlayerMode } from '@/utils/player' import { State as PlayerState, Mode as PlayerMode } from '@/utils/player'
@ -18,10 +19,8 @@ const PlayingTrack = () => {
) )
// Liked songs ids // Liked songs ids
const { data: user } = useUser() const { data: userLikedSongs } = useUserLikedTracksIDs()
const { data: userLikedSongs } = useUserLikedSongsIDs({ const mutationLikeATrack = useMutationLikeATrack()
uid: user?.account?.id ?? 0,
})
const toAlbum = () => { const toAlbum = () => {
const id = track?.al?.id const id = track?.al?.id
@ -65,7 +64,9 @@ const PlayingTrack = () => {
</div> </div>
</div> </div>
<IconButton onClick={() => toast('施工中...')}> <IconButton
onClick={() => track?.id && mutationLikeATrack.mutate(track.id)}
>
<SvgIcon <SvgIcon
className='h-5 w-5 text-black dark:text-white' className='h-5 w-5 text-black dark:text-white'
name={ name={

View file

@ -2,8 +2,9 @@ import { memo } from 'react'
import ArtistInline from '@/components/ArtistsInline' import ArtistInline from '@/components/ArtistsInline'
import Skeleton from '@/components/Skeleton' import Skeleton from '@/components/Skeleton'
import SvgIcon from '@/components/SvgIcon' import SvgIcon from '@/components/SvgIcon'
import useUser from '@/hooks/useUser' import useUserLikedTracksIDs, {
import useUserLikedSongsIDs from '@/hooks/useUserLikedSongsIDs' useMutationLikeATrack,
} from '@/hooks/useUserLikedTracksIDs'
import { player } from '@/store' import { player } from '@/store'
import { formatDuration } from '@/utils/common' import { formatDuration } from '@/utils/common'
import { State as PlayerState } from '@/utils/player' import { State as PlayerState } from '@/utils/player'
@ -65,6 +66,8 @@ const Track = memo(
[track.alia, track.tns] [track.alia, track.tns]
) )
const mutationLikeATrack = useMutationLikeATrack()
return ( return (
<div <div
onClick={e => onClick(e, track.id)} onClick={e => onClick(e, track.id)}
@ -163,6 +166,7 @@ const Track = memo(
{/* Like button */} {/* Like button */}
{!isSkeleton && ( {!isSkeleton && (
<button <button
onClick={() => track?.id && mutationLikeATrack.mutate(track.id)}
className={classNames( className={classNames(
'mr-5 cursor-default transition duration-300 hover:scale-[1.2]', 'mr-5 cursor-default transition duration-300 hover:scale-[1.2]',
isLiked isLiked
@ -213,10 +217,7 @@ const TracksAlbum = ({
const skeletonTracks: Track[] = new Array(1).fill({}) const skeletonTracks: Track[] = new Array(1).fill({})
// Liked songs ids // Liked songs ids
const { data: user } = useUser() const { data: userLikedSongs } = useUserLikedTracksIDs()
const { data: userLikedSongs } = useUserLikedSongsIDs({
uid: user?.account?.id ?? 0,
})
const handleClick = useCallback( const handleClick = useCallback(
(e: React.MouseEvent<HTMLElement>, trackID: number) => { (e: React.MouseEvent<HTMLElement>, trackID: number) => {

View file

@ -4,7 +4,9 @@ import ArtistInline from '@/components/ArtistsInline'
import Skeleton from '@/components/Skeleton' import Skeleton from '@/components/Skeleton'
import SvgIcon from '@/components/SvgIcon' import SvgIcon from '@/components/SvgIcon'
import useUser from '@/hooks/useUser' import useUser from '@/hooks/useUser'
import useUserLikedSongsIDs from '@/hooks/useUserLikedSongsIDs' import useUserLikedTracksIDs, {
useMutationLikeATrack,
} from '@/hooks/useUserLikedTracksIDs'
import { formatDuration, resizeImage } from '@/utils/common' import { formatDuration, resizeImage } from '@/utils/common'
import { player } from '@/store' import { player } from '@/store'
@ -26,6 +28,7 @@ const Track = memo(
() => track.tns?.at(0) ?? track.alia?.at(0), () => track.tns?.at(0) ?? track.alia?.at(0),
[track.alia, track.tns] [track.alia, track.tns]
) )
const mutationLikeATrack = useMutationLikeATrack()
return ( return (
<div <div
@ -129,6 +132,7 @@ const Track = memo(
{/* Like button */} {/* Like button */}
{!isSkeleton && ( {!isSkeleton && (
<button <button
onClick={() => track?.id && mutationLikeATrack.mutate(track.id)}
className={classNames( className={classNames(
'mr-5 cursor-default transition duration-300 hover:scale-[1.2]', 'mr-5 cursor-default transition duration-300 hover:scale-[1.2]',
!isLiked && 'text-gray-600 opacity-0 dark:text-gray-400', !isLiked && 'text-gray-600 opacity-0 dark:text-gray-400',
@ -181,10 +185,7 @@ const TracksList = memo(
const skeletonTracks: Track[] = new Array(12).fill({}) const skeletonTracks: Track[] = new Array(12).fill({})
// Liked songs ids // Liked songs ids
const { data: user } = useUser() const { data: userLikedSongs } = useUserLikedTracksIDs()
const { data: userLikedSongs } = useUserLikedSongsIDs({
uid: user?.account?.id ?? 0,
})
const handleClick = (e: React.MouseEvent<HTMLElement>, trackID: number) => { const handleClick = (e: React.MouseEvent<HTMLElement>, trackID: number) => {
if (e.detail === 2) onTrackDoubleClick?.(trackID) if (e.detail === 2) onTrackDoubleClick?.(trackID)

View file

@ -1,25 +0,0 @@
import type {
FetchUserLikedSongsIDsParams,
FetchUserLikedSongsIDsResponse,
} from '@/api/user'
import { UserApiNames, fetchUserLikedSongsIDs } from '@/api/user'
export default function useUserLikedSongsIDs(
params: FetchUserLikedSongsIDsParams
) {
return useQuery(
[UserApiNames.FETCH_USER_LIKED_SONGS_IDS, params],
() => fetchUserLikedSongsIDs(params),
{
enabled: !!(params.uid && params.uid !== 0),
refetchOnWindowFocus: true,
placeholderData: (): FetchUserLikedSongsIDsResponse | undefined =>
window.ipcRenderer?.sendSync('getApiCacheSync', {
api: 'likelist',
query: {
uid: params.uid,
},
}),
}
)
}

View file

@ -0,0 +1,80 @@
import type { FetchUserLikedTracksIDsResponse } from '@/api/user'
import { UserApiNames, fetchUserLikedTracksIDs } from '@/api/user'
import { likeATrack } from '@/api/track'
import useUser from './useUser'
import { useQueryClient } from 'react-query'
export default function useUserLikedTracksIDs() {
const { data: user } = useUser()
const uid = user?.account?.id ?? 0
return useQuery(
[UserApiNames.FETCH_USER_LIKED_TRACKS_IDS, uid],
() => fetchUserLikedTracksIDs({ uid }),
{
enabled: !!(uid && uid !== 0),
refetchOnWindowFocus: true,
placeholderData: (): FetchUserLikedTracksIDsResponse | undefined =>
window.ipcRenderer?.sendSync('getApiCacheSync', {
api: 'likelist',
query: {
uid,
},
}),
}
)
}
export const useMutationLikeATrack = () => {
const queryClient = useQueryClient()
const { data: user } = useUser()
const { data: userLikedSongs } = useUserLikedTracksIDs()
const uid = user?.account?.id ?? 0
const key = [UserApiNames.FETCH_USER_LIKED_TRACKS_IDS, uid]
return useMutation(
(trackID: number) => {
if (!trackID || userLikedSongs?.ids === undefined) {
throw new Error('trackID is required or userLikedSongs is undefined')
}
return likeATrack({
id: trackID,
like: !userLikedSongs.ids.includes(trackID),
})
},
{
onMutate: async trackID => {
// 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 likedSongs = old as FetchUserLikedTracksIDsResponse
const ids = likedSongs.ids
const newIds = ids.includes(trackID)
? ids.filter(id => id !== trackID)
: [...ids, trackID]
console.log(trackID, ids.includes(trackID), ids, newIds)
return {
...likedSongs,
ids: newIds,
}
})
// 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)
},
// Always refetch after error or success:
onSettled: () => {
queryClient.invalidateQueries(key)
},
}
)
}