mirror of
https://github.com/GiriNeko/YesPlayMusic.git
synced 2025-12-16 21:28:06 +00:00
feat: 支持收藏歌曲
This commit is contained in:
parent
bbcf1f9340
commit
bbd5299341
7 changed files with 127 additions and 49 deletions
|
|
@ -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(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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={
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
80
src/renderer/hooks/useUserLikedTracksIDs.ts
Normal file
80
src/renderer/hooks/useUserLikedTracksIDs.ts
Normal 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)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue