mirror of
https://github.com/GiriNeko/YesPlayMusic.git
synced 2025-12-16 13:17:46 +00:00
feat: updates
This commit is contained in:
parent
f340a90117
commit
cec4c5909d
50 changed files with 1304 additions and 207 deletions
31
packages/web/pages/New/Artist/Artist.tsx
Normal file
31
packages/web/pages/New/Artist/Artist.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import useArtist from '@/web/api/hooks/useArtist'
|
||||
import { cx, css } from '@emotion/css'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import Header from './Header'
|
||||
import Popular from './Popular'
|
||||
import ArtistAlbum from './ArtistAlbums'
|
||||
import FansAlsoLike from './FansAlsoLike'
|
||||
|
||||
const Artist = () => {
|
||||
const params = useParams()
|
||||
|
||||
const { data: artist, isLoading: isLoadingArtist } = useArtist({
|
||||
id: Number(params.id) || 0,
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Header artist={artist?.artist} />
|
||||
|
||||
<div className='mt-10 mb-7.5 h-px w-full bg-white/20'></div>
|
||||
|
||||
<Popular tracks={artist?.hotSongs} />
|
||||
|
||||
<ArtistAlbum />
|
||||
|
||||
<FansAlsoLike />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Artist
|
||||
27
packages/web/pages/New/Artist/ArtistAlbums.tsx
Normal file
27
packages/web/pages/New/Artist/ArtistAlbums.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import useArtistAlbums from '@/web/api/hooks/useArtistAlbums'
|
||||
import CoverRow from '@/web/components/New/CoverRow'
|
||||
import { useMemo } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
const ArtistAlbum = () => {
|
||||
const params = useParams()
|
||||
|
||||
const { data: albumsRaw, isLoading: isLoadingAlbums } = useArtistAlbums({
|
||||
id: Number(params.id) || 0,
|
||||
limit: 1000,
|
||||
})
|
||||
|
||||
const albums = useMemo(() => albumsRaw?.hotAlbums, [albumsRaw?.hotAlbums])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='mb-4 mt-11 text-12 font-medium uppercase text-neutral-300'>
|
||||
Albums
|
||||
</div>
|
||||
|
||||
<CoverRow albums={albums?.slice(0, 12)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ArtistAlbum
|
||||
20
packages/web/pages/New/Artist/FansAlsoLike.tsx
Normal file
20
packages/web/pages/New/Artist/FansAlsoLike.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import ArtistRow from '@/web/components/New/ArtistRow'
|
||||
import useSimilarArtists from '@/web/api/hooks/useSimilarArtists'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
const FansAlsoLike = () => {
|
||||
const params = useParams()
|
||||
const { data: artists } = useSimilarArtists({ id: Number(params.id) || 0 })
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='mb-6 mt-10 text-12 font-medium uppercase text-neutral-300'>
|
||||
Fans Also Like
|
||||
</div>
|
||||
|
||||
<ArtistRow artists={artists?.artists?.slice(0, 5)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FansAlsoLike
|
||||
13
packages/web/pages/New/Artist/Header/Actions.tsx
Normal file
13
packages/web/pages/New/Artist/Header/Actions.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
const Actions = () => {
|
||||
return (
|
||||
<div className='mt-11 flex items-end justify-between lg:z-10 lg:mt-6'>
|
||||
<div className='flex items-end'>
|
||||
<button className='mr-2.5 h-14 w-14 rounded-full dark:bg-white/10'></button>
|
||||
<button className='h-14 w-14 rounded-full dark:bg-white/10'></button>
|
||||
</div>
|
||||
<button className='h-14 w-[125px] rounded-full dark:bg-brand-700 lg:w-[170px]'></button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Actions
|
||||
28
packages/web/pages/New/Artist/Header/ArtistInfo.tsx
Normal file
28
packages/web/pages/New/Artist/Header/ArtistInfo.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import useIsMobile from '@/web/hooks/useIsMobile'
|
||||
|
||||
const ArtistInfo = ({ artist }: { artist?: Artist }) => {
|
||||
const isMobile = useIsMobile()
|
||||
return (
|
||||
<div>
|
||||
<div className='text-28 font-semibold text-night-50 lg:text-32'>
|
||||
{artist?.name}
|
||||
</div>
|
||||
<div className='mt-2.5 text-24 font-medium text-night-400 lg:mt-6'>
|
||||
Artist
|
||||
</div>
|
||||
<div className='mt-1 text-12 font-medium text-night-400'>
|
||||
{artist?.musicSize} Tracks · {artist?.albumSize} Albums ·{' '}
|
||||
{artist?.mvSize} Videos
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
{!isMobile && (
|
||||
<div className='line-clamp-5 mt-6 text-14 font-bold text-night-400'>
|
||||
{artist?.briefDesc}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ArtistInfo
|
||||
26
packages/web/pages/New/Artist/Header/BlurBackground.tsx
Normal file
26
packages/web/pages/New/Artist/Header/BlurBackground.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { resizeImage } from '@/web/utils/common'
|
||||
import { cx, css } from '@emotion/css'
|
||||
import useIsMobile from '@/web/hooks/useIsMobile'
|
||||
|
||||
const BlurBackground = ({ cover }: { cover?: string }) => {
|
||||
const isMobile = useIsMobile()
|
||||
return isMobile || !cover ? (
|
||||
<></>
|
||||
) : (
|
||||
<img
|
||||
className={cx(
|
||||
'absolute z-0 object-cover opacity-70',
|
||||
css`
|
||||
top: -400px;
|
||||
left: -370px;
|
||||
width: 1572px;
|
||||
height: 528px;
|
||||
filter: blur(256px) saturate(1.2);
|
||||
`
|
||||
)}
|
||||
src={resizeImage(cover, 'sm')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default BlurBackground
|
||||
65
packages/web/pages/New/Artist/Header/Header.tsx
Normal file
65
packages/web/pages/New/Artist/Header/Header.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { resizeImage } from '@/web/utils/common'
|
||||
import { cx, css } from '@emotion/css'
|
||||
import Image from '@/web/components/New/Image'
|
||||
import { useMemo } from 'react'
|
||||
import { breakpoint as bp } from '@/web/utils/const'
|
||||
import BlurBackground from './BlurBackground'
|
||||
import ArtistInfo from './ArtistInfo'
|
||||
import Actions from './Actions'
|
||||
import LatestRelease from './LatestRelease'
|
||||
|
||||
const Header = ({ artist }: { artist?: Artist }) => {
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
'lg:grid lg:gap-10',
|
||||
css`
|
||||
grid-template-columns: auto 558px;
|
||||
grid-template-areas:
|
||||
'info cover'
|
||||
'info cover';
|
||||
`
|
||||
)}
|
||||
>
|
||||
<Image
|
||||
className={cx(
|
||||
'z-10 aspect-square lg:rounded-24',
|
||||
css`
|
||||
grid-area: cover;
|
||||
`
|
||||
)}
|
||||
src={resizeImage(artist?.img1v1Url || '', 'lg')}
|
||||
/>
|
||||
|
||||
<BlurBackground cover={artist?.img1v1Url} />
|
||||
|
||||
<div
|
||||
className={cx(
|
||||
'lg:flex lg:flex-col lg:justify-between',
|
||||
css`
|
||||
grid-area: info;
|
||||
`
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cx(
|
||||
'mx-2.5 rounded-48 bg-white/10 p-8 backdrop-blur-3xl lg:mx-0 lg:bg-transparent lg:p-0 lg:backdrop-blur-none',
|
||||
css`
|
||||
margin-top: -60px;
|
||||
${bp.lg} {
|
||||
margin-top: 0px;
|
||||
}
|
||||
`
|
||||
)}
|
||||
>
|
||||
<ArtistInfo artist={artist} />
|
||||
<Actions />
|
||||
</div>
|
||||
|
||||
<LatestRelease />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
||||
97
packages/web/pages/New/Artist/Header/LatestRelease.tsx
Normal file
97
packages/web/pages/New/Artist/Header/LatestRelease.tsx
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import { resizeImage } from '@/web/utils/common'
|
||||
import dayjs from 'dayjs'
|
||||
import { cx, css } from '@emotion/css'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import Image from '@/web/components/New/Image'
|
||||
import useArtistAlbums from '@/web/api/hooks/useArtistAlbums'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
const Album = () => {
|
||||
const params = useParams()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { data: albumsRaw, isLoading: isLoadingAlbums } = useArtistAlbums({
|
||||
id: Number(params.id) || 0,
|
||||
limit: 1000,
|
||||
})
|
||||
|
||||
const album = useMemo(() => albumsRaw?.hotAlbums?.[0], [albumsRaw?.hotAlbums])
|
||||
|
||||
if (!album) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => navigate(`/album/${album.id}`)}
|
||||
className='flex rounded-24 bg-white/10 p-2.5'
|
||||
>
|
||||
<Image
|
||||
src={resizeImage(album.picUrl, 'sm')}
|
||||
className={cx(
|
||||
'aspect-square',
|
||||
css`
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
border-radius: 16px;
|
||||
`
|
||||
)}
|
||||
/>
|
||||
<div className='flex-shrink-1 ml-2'>
|
||||
<div className='line-clamp-1 text-16 font-medium text-night-100'>
|
||||
{album.name}
|
||||
</div>
|
||||
<div className='mt-1 text-14 font-bold text-night-500'>
|
||||
{album.type}
|
||||
{album.size > 1 ? `· ${album.size} Tracks` : ''}
|
||||
</div>
|
||||
<div className='mt-1.5 text-12 font-medium text-night-500'>
|
||||
{dayjs(album?.publishTime || 0).format('MMM DD, YYYY')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Video = () => {
|
||||
return (
|
||||
<div className='mt-4 flex rounded-24 bg-white/10 p-2.5'>
|
||||
<Image
|
||||
src={resizeImage(
|
||||
'https://p1.music.126.net/am47BH30IGQit_L2vYaArg==/109951167502760845.jpg',
|
||||
'sm'
|
||||
)}
|
||||
className={cx(
|
||||
css`
|
||||
height: 60px;
|
||||
width: 106px;
|
||||
border-radius: 16px;
|
||||
`
|
||||
)}
|
||||
/>
|
||||
<div className='flex-shrink-1 ml-2'>
|
||||
<div className='line-clamp-2 text-16 font-medium text-night-100'>
|
||||
Swedish House Mafia & The Weeknd Live at C...
|
||||
</div>
|
||||
<div className='mt-1.5 text-12 font-medium text-night-500'>
|
||||
{dayjs().format('MMM DD, YYYY')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const LatestRelease = () => {
|
||||
return (
|
||||
<div className='mx-2.5 lg:mx-0'>
|
||||
<div className='mt-7 mb-3 text-14 font-bold text-neutral-300'>
|
||||
Latest Releases
|
||||
</div>
|
||||
|
||||
<Album />
|
||||
<Video />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LatestRelease
|
||||
3
packages/web/pages/New/Artist/Header/index.tsx
Normal file
3
packages/web/pages/New/Artist/Header/index.tsx
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import Header from './Header'
|
||||
|
||||
export default Header
|
||||
76
packages/web/pages/New/Artist/Popular.tsx
Normal file
76
packages/web/pages/New/Artist/Popular.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { resizeImage } from '@/web/utils/common'
|
||||
import { player } from '@/web/store'
|
||||
import { State as PlayerState } from '@/web/utils/player'
|
||||
import useTracks from '@/web/api/hooks/useTracks'
|
||||
import { css, cx } from '@emotion/css'
|
||||
import Image from '@/web/components/New/Image'
|
||||
|
||||
const Track = ({
|
||||
track,
|
||||
isPlaying,
|
||||
onPlay,
|
||||
}: {
|
||||
track?: Track
|
||||
isPlaying?: boolean
|
||||
onPlay: (id: number) => void
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className='flex items-center justify-between'
|
||||
onClick={e => {
|
||||
if (e.detail === 2 && track?.id) onPlay(track.id)
|
||||
}}
|
||||
>
|
||||
{/* Cover */}
|
||||
<Image
|
||||
className='mr-4 aspect-square h-14 w-14 flex-shrink-0 rounded-12'
|
||||
src={resizeImage(track?.al?.picUrl || '', 'sm')}
|
||||
animation={false}
|
||||
placeholder={false}
|
||||
/>
|
||||
|
||||
{/* Track info */}
|
||||
<div className='mr-3 flex-grow'>
|
||||
<div
|
||||
className={cx(
|
||||
'line-clamp-1 text-16 font-medium ',
|
||||
isPlaying
|
||||
? 'text-brand-700'
|
||||
: 'text-neutral-700 dark:text-neutral-200'
|
||||
)}
|
||||
>
|
||||
{track?.name}
|
||||
</div>
|
||||
<div className='line-clamp-1 mt-1 text-14 font-bold text-neutral-200 dark:text-neutral-700'>
|
||||
{track?.ar.map(a => a.name).join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Popular = ({ tracks }: { tracks?: Track[] }) => {
|
||||
const onPlay = (id: number) => {
|
||||
if (!tracks) return
|
||||
player.playAList(
|
||||
tracks.map(t => t.id),
|
||||
id
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='mb-4 text-12 font-medium uppercase text-neutral-300'>
|
||||
Popular
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-3 grid-rows-3 gap-4 overflow-hidden'>
|
||||
{tracks?.slice(0, 9)?.map(t => (
|
||||
<Track key={t.id} track={t} onPlay={onPlay} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Popular
|
||||
3
packages/web/pages/New/Artist/index.tsx
Normal file
3
packages/web/pages/New/Artist/index.tsx
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import Artist from './Artist'
|
||||
|
||||
export default Artist
|
||||
|
|
@ -16,6 +16,7 @@ import topbarBackground from '@/web/assets/images/topbar-background.png'
|
|||
const reactQueryOptions = {
|
||||
refetchOnWindowFocus: false,
|
||||
refetchInterval: 1000 * 60 * 60, // 1 hour
|
||||
refetchOnMount: false,
|
||||
}
|
||||
|
||||
const Recommend = () => {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { fetchTracksWithReactQuery } from '@/web/api/hooks/useTracks'
|
|||
import { useEffect, useState } from 'react'
|
||||
import { sampleSize } from 'lodash-es'
|
||||
import { FetchPlaylistResponse } from '@/shared/api/Playlists'
|
||||
import { useQuery } from 'react-query'
|
||||
|
||||
interface DiscoverAlbum {
|
||||
id: number
|
||||
|
|
@ -82,28 +83,31 @@ const getAlbumsFromAPI = async () => {
|
|||
}
|
||||
|
||||
const Discover = () => {
|
||||
const [albums, setAlbums] = useState<DiscoverAlbum[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
const get = async () => {
|
||||
const { data: albums } = useQuery(
|
||||
['DiscoveryAlbums'],
|
||||
async () => {
|
||||
const albumsInLocalStorageTime =
|
||||
localStorage.getItem('discoverAlbumsTime')
|
||||
if (
|
||||
!albumsInLocalStorageTime ||
|
||||
Date.now() - Number(albumsInLocalStorageTime) > 1000 * 60 * 60 * 2 // 2小时刷新一次
|
||||
) {
|
||||
setAlbums(await getAlbumsFromAPI())
|
||||
return await getAlbumsFromAPI()
|
||||
} else {
|
||||
setAlbums(JSON.parse(localStorage.getItem('discoverAlbums') || '[]'))
|
||||
return JSON.parse(localStorage.getItem('discoverAlbums') || '[]')
|
||||
}
|
||||
},
|
||||
{
|
||||
staleTime: 1000 * 60 * 60 * 2, // 2hr
|
||||
refetchOnWindowFocus: false,
|
||||
refetchInterval: 1000 * 60 * 60 * 2, // 2hr
|
||||
}
|
||||
get()
|
||||
}, [])
|
||||
)
|
||||
|
||||
return (
|
||||
<PageTransition disableEnterAnimation={true}>
|
||||
<div className='mx-2.5 pb-10 lg:mx-0 lg:pb-16'>
|
||||
<CoverWall albums={albums} />
|
||||
<CoverWall albums={albums || []} />
|
||||
</div>
|
||||
</PageTransition>
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue