[+] Playlist page
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
let { icon, children, ...rest } = $props();
|
let { icon, children, ...rest }: {
|
||||||
|
icon?: string
|
||||||
|
children?: any
|
||||||
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button class="hbox h-40px rounded-12px px-16px py-10px gap-8px mfg-on-primary-container mbg-inverse-primary m3-font-label-large" {...rest}>
|
<button class="hbox h-40px rounded-12px px-16px py-10px gap-8px mfg-on-primary-container mbg-inverse-primary m3-font-label-large" {...rest}>
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import IconButton from "../IconButton.svelte";
|
import IconButton from "../IconButton.svelte";
|
||||||
|
|
||||||
let { title, account, settings }: {
|
interface Icon {
|
||||||
|
icon: string
|
||||||
|
onclick: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
let { title, account, right }: {
|
||||||
title?: string
|
title?: string
|
||||||
account?: () => void
|
account?: () => void
|
||||||
settings?: () => void
|
right?: Icon[]
|
||||||
} = $props()
|
} = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -15,7 +20,11 @@
|
|||||||
<IconButton icon="i-material-symbols:arrow-back-rounded" onclick={() => history.back()} aria-label="Account" />
|
<IconButton icon="i-material-symbols:arrow-back-rounded" onclick={() => history.back()} aria-label="Account" />
|
||||||
{/if}
|
{/if}
|
||||||
<div class="m3-font-title-large flex-1">{title}</div>
|
<div class="m3-font-title-large flex-1">{title}</div>
|
||||||
{#if settings}
|
<!--{#if right}-->
|
||||||
<IconButton icon="i-material-symbols:settings-rounded" onclick={settings} aria-label="Settings" />
|
<!-- <IconButton icon="i-material-symbols:settings-rounded" onclick={right} aria-label="Settings" />-->
|
||||||
{/if}
|
<!-- {/if}-->
|
||||||
|
|
||||||
|
{#each right as item}
|
||||||
|
<IconButton icon={item.icon} onclick={item.onclick} />
|
||||||
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -35,7 +35,7 @@ function parsePlaylistRef(ref: string): number {
|
|||||||
/**
|
/**
|
||||||
* Get raw playlist data from cache or netease API.
|
* Get raw playlist data from cache or netease API.
|
||||||
*/
|
*/
|
||||||
const getPlaylistRaw = cached('playlists',
|
export const getPlaylistRaw = cached('playlists',
|
||||||
async (id: number) => {
|
async (id: number) => {
|
||||||
const pl = ((await ne.playlist_detail({ id })).body as any)
|
const pl = ((await ne.playlist_detail({ id })).body as any)
|
||||||
|
|
||||||
@@ -67,10 +67,10 @@ export const parseBrief = (songData: any): NeteaseSongBrief => ({
|
|||||||
/**
|
/**
|
||||||
* Get a list of songs from a playlist reference.
|
* Get a list of songs from a playlist reference.
|
||||||
*/
|
*/
|
||||||
export async function getSongsFromPlaylist(ref: string): Promise<any[]> {
|
export async function getSongsFromPlaylist(ref: string): Promise<{meta: any, songs: NeteaseSongBrief[]}> {
|
||||||
const playlistId = parsePlaylistRef(ref)
|
const playlistId = parsePlaylistRef(ref)
|
||||||
const plData = await getPlaylistRaw(playlistId)
|
const plData = await getPlaylistRaw(playlistId)
|
||||||
return plData.playlist.tracks.map(parseBrief)
|
return {meta: plData.playlist, songs: plData.playlist.tracks.map(parseBrief)}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NeteaseLyricsResponse { lrc: { lyric: string } }
|
interface NeteaseLyricsResponse { lrc: { lyric: string } }
|
||||||
@@ -85,3 +85,7 @@ export const getLyricsProcessed = cached('lyrics_processed',
|
|||||||
return aiParseLyrics(raw.lrc.lyric)
|
return aiParseLyrics(raw.lrc.lyric)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await getSongsFromPlaylist("13555799996")
|
||||||
|
await getSongsFromPlaylist("https://music.163.com/playlist?id=14348145982")
|
||||||
|
await getSongsFromPlaylist("https://music.163.com/playlist?id=14392963638")
|
||||||
|
await getSongsFromPlaylist("https://music.163.com/playlist?id=580208139")
|
||||||
|
|||||||
@@ -11,7 +11,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<AppBar account={() => alert('Account clicked')} settings={() => alert('Settings clicked')} />
|
<AppBar account={() => alert('Account clicked')} right={[
|
||||||
|
{icon: "i-material-symbols:settings-rounded", onclick: () => alert('Settings clicked')}
|
||||||
|
]} />
|
||||||
|
|
||||||
<div class="vbox gap-16px">
|
<div class="vbox gap-16px">
|
||||||
{#if data.last}
|
{#if data.last}
|
||||||
@@ -49,13 +51,13 @@
|
|||||||
<TitleHeader title="推荐歌单"/>
|
<TitleHeader title="推荐歌单"/>
|
||||||
<div class="p-content hbox gap-8px w-auto overflow-x-auto py-8px">
|
<div class="p-content hbox gap-8px w-auto overflow-x-auto py-8px">
|
||||||
{#each data.recPlaylists as playlist}
|
{#each data.recPlaylists as playlist}
|
||||||
<div class="vbox flex-shrink-0 p-8px gap-8px rounded-12px mbg-surface-container-high">
|
<a class="vbox flex-shrink-0 p-8px gap-8px rounded-12px mbg-surface-container-high" href="/playlist/{playlist.id}">
|
||||||
<img src="{playlist.coverImgUrl}" alt="" class="size-116px rounded-8px">
|
<img src="{playlist.coverImgUrl}" alt="" class="size-116px rounded-8px">
|
||||||
<div>
|
<div>
|
||||||
<div class="m3-font-title-small font-bold truncate">{playlist.name}</div>
|
<div class="m3-font-title-small font-bold truncate">{playlist.name}</div>
|
||||||
<div class="m3-font-body-small truncate">{playlist.creator.nickname} 创建</div>
|
<div class="m3-font-body-small truncate">{playlist.creator.nickname} 创建</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
import { getSongsFromPlaylist, listPlaylists } from "$lib/server/songs.ts";
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ params }) => ({
|
||||||
|
playlist: await getSongsFromPlaylist(params.slug)
|
||||||
|
})
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { PageProps } from "./$types"
|
||||||
|
import AppBar from "../../../components/appbar/AppBar.svelte";
|
||||||
|
import Button from "../../../components/Button.svelte";
|
||||||
|
|
||||||
|
let { data }: PageProps = $props()
|
||||||
|
|
||||||
|
let {meta, songs} = data.playlist
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AppBar title="歌单详情" right={[
|
||||||
|
{icon: "i-material-symbols:bookmark-add-outline-rounded", onclick: () => alert('Bookmark clicked')},
|
||||||
|
{icon: "i-material-symbols:more-vert", onclick: () => alert('More clicked')}
|
||||||
|
]} />
|
||||||
|
|
||||||
|
<div class="hbox px-16px py-8px gap-24px">
|
||||||
|
<img src="{meta.coverImgUrl}" alt="" class="size-128px rounded-16px">
|
||||||
|
<div class="vbox flex-1 py-8px self-stretch min-h-full">
|
||||||
|
<div class="m3-font-headline-small font-bold">{meta.name}</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="m3-font-body-small text-surface-variant">创建者: {meta.creator.nickname}</div>
|
||||||
|
<div class="m3-font-body-small text-surface-variant">歌曲数: {meta.trackCount}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button>开始练习</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user