mirror of
https://github.com/GiriNeko/YesPlayMusic.git
synced 2025-12-17 13:48:02 +00:00
feat: 音乐库增加查看收藏的专辑/歌单/歌手功能
This commit is contained in:
parent
bbd5299341
commit
2e41001d02
6 changed files with 203 additions and 44 deletions
|
|
@ -4,6 +4,8 @@ export enum UserApiNames {
|
||||||
FETCH_USER_ACCOUNT = 'fetchUserAccount',
|
FETCH_USER_ACCOUNT = 'fetchUserAccount',
|
||||||
FETCH_USER_LIKED_TRACKS_IDS = 'fetchUserLikedTracksIDs',
|
FETCH_USER_LIKED_TRACKS_IDS = 'fetchUserLikedTracksIDs',
|
||||||
FETCH_USER_PLAYLISTS = 'fetchUserPlaylists',
|
FETCH_USER_PLAYLISTS = 'fetchUserPlaylists',
|
||||||
|
FETCH_USER_ALBUMS = 'fetchUserAlbums',
|
||||||
|
FETCH_USER_ARTIST = 'fetchUserArtists',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -99,7 +101,7 @@ export interface FetchUserPlaylistsParams {
|
||||||
}
|
}
|
||||||
export interface FetchUserPlaylistsResponse {
|
export interface FetchUserPlaylistsResponse {
|
||||||
code: number
|
code: number
|
||||||
more: false
|
more: boolean
|
||||||
version: string
|
version: string
|
||||||
playlist: Playlist[]
|
playlist: Playlist[]
|
||||||
}
|
}
|
||||||
|
|
@ -151,40 +153,44 @@ export function dailySignin(type = 0) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export interface FetchUserAlbumsParams {
|
||||||
* 获取收藏的专辑(需要登录)
|
offset?: number // default 0
|
||||||
* 说明 : 调用此接口可获取到用户收藏的专辑
|
limit?: number // default 25
|
||||||
* - limit : 返回数量 , 默认为 25
|
}
|
||||||
* - offset : 偏移数量,用于分页 , 如 :( 页数 -1)*25, 其中 25 为 limit 的值 , 默认为 0
|
export interface FetchUserAlbumsResponse {
|
||||||
* @param {Object} params
|
code: number
|
||||||
* @param {number} params.limit
|
hasMore: boolean
|
||||||
* @param {number=} params.offset
|
paidCount: number
|
||||||
*/
|
count: number
|
||||||
// export function likedAlbums(params) {
|
data: Album[]
|
||||||
// return request({
|
}
|
||||||
// url: '/album/sublist',
|
export function fetchUserAlbums(params: FetchUserAlbumsParams) {
|
||||||
// method: 'get',
|
return request({
|
||||||
// params: {
|
url: '/album/sublist',
|
||||||
// limit: params.limit,
|
method: 'get',
|
||||||
// timestamp: new Date().getTime(),
|
params: {
|
||||||
// },
|
...params,
|
||||||
// })
|
timestamp: new Date().getTime(),
|
||||||
// }
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
// 获取收藏的歌手
|
||||||
* 获取收藏的歌手(需要登录)
|
export interface FetchUserArtistsResponse {
|
||||||
* 说明 : 调用此接口可获取到用户收藏的歌手
|
code: number
|
||||||
*/
|
hasMore: boolean
|
||||||
// export function likedArtists(params) {
|
count: number
|
||||||
// return request({
|
data: Artist[]
|
||||||
// url: '/artist/sublist',
|
}
|
||||||
// method: 'get',
|
export function fetchUserArtists(): Promise<FetchUserArtistsResponse> {
|
||||||
// params: {
|
return request({
|
||||||
// limit: params.limit,
|
url: '/artist/sublist',
|
||||||
// timestamp: new Date().getTime(),
|
method: 'get',
|
||||||
// },
|
params: {
|
||||||
// })
|
timestamp: new Date().getTime(),
|
||||||
// }
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取收藏的MV(需要登录)
|
* 获取收藏的MV(需要登录)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ const Cover = ({
|
||||||
{showHover && (
|
{showHover && (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'absolute top-2 z-[-1] h-full w-full scale-x-[.92] scale-y-[.96] rounded-xl bg-cover opacity-0 blur-lg filter transition duration-300 group-hover:opacity-60',
|
'absolute top-2 z-[-1] h-full w-full scale-x-[.92] scale-y-[.96] bg-cover opacity-0 blur-lg filter transition duration-300 group-hover:opacity-60',
|
||||||
roundedClass
|
roundedClass
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,12 @@ const getSubtitleText = (
|
||||||
subtitle: Subtitle
|
subtitle: Subtitle
|
||||||
) => {
|
) => {
|
||||||
const nickname = 'creator' in item ? item.creator.nickname : 'someone'
|
const nickname = 'creator' in item ? item.creator.nickname : 'someone'
|
||||||
const artist = 'artist' in item ? item.artist.name : 'unknown'
|
const artist =
|
||||||
|
'artist' in item
|
||||||
|
? item.artist.name
|
||||||
|
: 'artists' in item
|
||||||
|
? item.artists?.[0]?.name
|
||||||
|
: 'unknown'
|
||||||
const copywriter = 'copywriter' in item ? item.copywriter : 'unknown'
|
const copywriter = 'copywriter' in item ? item.copywriter : 'unknown'
|
||||||
const releaseYear =
|
const releaseYear =
|
||||||
('publishTime' in item &&
|
('publishTime' in item &&
|
||||||
|
|
@ -125,10 +130,10 @@ const CoverRow = ({
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'grid gap-x-6 gap-y-7',
|
'grid',
|
||||||
className,
|
className,
|
||||||
!className &&
|
!className &&
|
||||||
'grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6'
|
'grid-cols-3 gap-x-6 gap-y-7 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{renderItems.map((item, index) => (
|
{renderItems.map((item, index) => (
|
||||||
|
|
@ -146,6 +151,7 @@ const CoverRow = ({
|
||||||
onClick={() => goTo(item.id)}
|
onClick={() => goTo(item.id)}
|
||||||
imageUrl={getImageUrl(item)}
|
imageUrl={getImageUrl(item)}
|
||||||
showPlayButton={true}
|
showPlayButton={true}
|
||||||
|
roundedClass={artists ? 'rounded-full' : 'rounded-xl'}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -163,9 +169,14 @@ const CoverRow = ({
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<span className='line-clamp-2 leading-tight '>
|
<span
|
||||||
|
className={classNames(
|
||||||
|
'line-clamp-2 leading-tight',
|
||||||
|
artists && 'mt-3 text-center'
|
||||||
|
)}
|
||||||
|
>
|
||||||
{/* Playlist private icon */}
|
{/* Playlist private icon */}
|
||||||
{(item as Playlist).privacy && (
|
{(item as Playlist).privacy === 10 && (
|
||||||
<SvgIcon
|
<SvgIcon
|
||||||
name='lock'
|
name='lock'
|
||||||
className='mr-1 mb-1 inline-block h-3 w-3 text-gray-300'
|
className='mr-1 mb-1 inline-block h-3 w-3 text-gray-300'
|
||||||
|
|
@ -197,9 +208,11 @@ const CoverRow = ({
|
||||||
PLACEHOLDER
|
PLACEHOLDER
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
) : (
|
) : (
|
||||||
<div className='flex text-[12px] text-gray-500 dark:text-gray-400'>
|
!artists && (
|
||||||
<span>{getSubtitleText(item, subtitle)}</span>
|
<div className='flex text-[12px] text-gray-500 dark:text-gray-400'>
|
||||||
</div>
|
<span>{getSubtitleText(item, subtitle)}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
16
src/renderer/hooks/useUserAlbums.ts
Normal file
16
src/renderer/hooks/useUserAlbums.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import type { FetchUserAlbumsParams, FetchUserAlbumsResponse } from '@/api/user'
|
||||||
|
import { UserApiNames, fetchUserAlbums } from '@/api/user'
|
||||||
|
|
||||||
|
export default function useUserAlbums(params: FetchUserAlbumsParams) {
|
||||||
|
return useQuery(
|
||||||
|
[UserApiNames.FETCH_USER_ALBUMS, params],
|
||||||
|
() => fetchUserAlbums(params),
|
||||||
|
{
|
||||||
|
placeholderData: (): FetchUserAlbumsResponse =>
|
||||||
|
window.ipcRenderer?.sendSync('getApiCacheSync', {
|
||||||
|
api: 'album/sublist',
|
||||||
|
query: params,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
11
src/renderer/hooks/useUserArtists.ts
Normal file
11
src/renderer/hooks/useUserArtists.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import type { FetchUserArtistsResponse } from '@/api/user'
|
||||||
|
import { UserApiNames, fetchUserArtists } from '@/api/user'
|
||||||
|
|
||||||
|
export default function useUserArtists() {
|
||||||
|
return useQuery([UserApiNames.FETCH_USER_ARTIST], fetchUserArtists, {
|
||||||
|
placeholderData: (): FetchUserArtistsResponse =>
|
||||||
|
window.ipcRenderer?.sendSync('getApiCacheSync', {
|
||||||
|
api: 'album/sublist',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
import CoverRow, { Subtitle } from '@/components/CoverRow'
|
||||||
import SvgIcon from '@/components/SvgIcon'
|
import SvgIcon from '@/components/SvgIcon'
|
||||||
|
import useUserAlbums from '@/hooks/useUserAlbums'
|
||||||
import useLyric from '@/hooks/useLyric'
|
import useLyric from '@/hooks/useLyric'
|
||||||
import usePlaylist from '@/hooks/usePlaylist'
|
import usePlaylist from '@/hooks/usePlaylist'
|
||||||
import useUser from '@/hooks/useUser'
|
import useUser from '@/hooks/useUser'
|
||||||
|
|
@ -6,6 +8,7 @@ import useUserPlaylists from '@/hooks/useUserPlaylists'
|
||||||
import { player } from '@/store'
|
import { player } from '@/store'
|
||||||
import { resizeImage } from '@/utils/common'
|
import { resizeImage } from '@/utils/common'
|
||||||
import { sample, chunk } from 'lodash-es'
|
import { sample, chunk } from 'lodash-es'
|
||||||
|
import useUserArtists from '@/hooks/useUserArtists'
|
||||||
|
|
||||||
const LikedTracksCard = ({ className }: { className?: string }) => {
|
const LikedTracksCard = ({ className }: { className?: string }) => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
@ -122,8 +125,116 @@ const OtherCard = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Playlists = () => {
|
||||||
|
const { data: user } = useUser()
|
||||||
|
|
||||||
|
const { data: playlists } = useUserPlaylists({
|
||||||
|
uid: user?.account?.id ?? 0,
|
||||||
|
offset: 0,
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<CoverRow
|
||||||
|
playlists={playlists?.playlist?.slice(1) ?? []}
|
||||||
|
subtitle={Subtitle.CREATOR}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Albums = () => {
|
||||||
|
const { data: albums } = useUserAlbums({
|
||||||
|
limit: 1000,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<CoverRow albums={albums?.data ?? []} subtitle={Subtitle.ARTIST} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Artists = () => {
|
||||||
|
const { data: artists } = useUserArtists()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<CoverRow artists={artists?.data ?? []} subtitle={Subtitle.ARTIST} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const MVs = () => {
|
||||||
|
return <div>施工中</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
const Podcasts = () => {
|
||||||
|
return <div>施工中</div>
|
||||||
|
}
|
||||||
|
interface TabsType {
|
||||||
|
playlist: string
|
||||||
|
album: string
|
||||||
|
artist: string
|
||||||
|
mv: string
|
||||||
|
podcast: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const TabHeader = ({
|
||||||
|
activeTab,
|
||||||
|
tabs,
|
||||||
|
setActiveTab,
|
||||||
|
}: {
|
||||||
|
activeTab: keyof TabsType
|
||||||
|
tabs: TabsType
|
||||||
|
setActiveTab: (tab: keyof TabsType) => void
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className='mt-10 flex text-lg'>
|
||||||
|
{Object.entries(tabs).map(([id, name]) => (
|
||||||
|
<div
|
||||||
|
key={id}
|
||||||
|
onClick={() => setActiveTab(id as keyof TabsType)}
|
||||||
|
className={classNames(
|
||||||
|
'btn-pressed-animation mr-3 rounded-lg px-3.5 py-1.5 font-medium',
|
||||||
|
activeTab === id
|
||||||
|
? 'bg-black/[.04]'
|
||||||
|
: 'btn-hover-animation after:bg-black/[.04] dark:after:bg-white/10'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const Tabs = () => {
|
const Tabs = () => {
|
||||||
return <div></div>
|
const tabs = {
|
||||||
|
playlist: '全部歌单',
|
||||||
|
album: '专辑',
|
||||||
|
artist: '艺人',
|
||||||
|
mv: 'MV',
|
||||||
|
podcast: '播客',
|
||||||
|
}
|
||||||
|
|
||||||
|
const [activeTab, setActiveTab] = useState<keyof TabsType>('playlist')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TabHeader
|
||||||
|
activeTab={activeTab}
|
||||||
|
tabs={tabs}
|
||||||
|
setActiveTab={setActiveTab}
|
||||||
|
/>
|
||||||
|
<div className='mt-6'>
|
||||||
|
{activeTab === 'playlist' && <Playlists />}
|
||||||
|
{activeTab === 'album' && <Albums />}
|
||||||
|
{activeTab === 'artist' && <Artists />}
|
||||||
|
{activeTab === 'mv' && <MVs />}
|
||||||
|
{activeTab === 'podcast' && <Podcasts />}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Library = () => {
|
const Library = () => {
|
||||||
|
|
@ -148,6 +259,8 @@ const Library = () => {
|
||||||
<OtherCard name='最近播放' icon='playlist' className='' />
|
<OtherCard name='最近播放' icon='playlist' className='' />
|
||||||
<OtherCard name='听歌排行' icon='music-library' className='' />
|
<OtherCard name='听歌排行' icon='music-library' className='' />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Tabs />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue