Skip to content

Commit

Permalink
feat: 添加下载内容抽屉 (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
nailiable committed Dec 31, 2024
1 parent d5e1165 commit cb6accd
Show file tree
Hide file tree
Showing 22 changed files with 356 additions and 26 deletions.
4 changes: 2 additions & 2 deletions backend/controllers/music.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export class MusicControllerImpl implements MusicController {
public readonly song: SongService,
) {}

async proxyRequest(url: string): Promise<Response> {
return await fetch(url)
proxyRequest(url: string) {
return fetch(url)
}

async getVersion(): Promise<VersionResponse> {
Expand Down
2 changes: 1 addition & 1 deletion backend/services/song.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class SongService implements ISongService {

const [detailResponse, urlResponse] = await Promise.all([
Netease.song_detail({
ids: request.id as string,
ids: request.id.toString(),
cookie: request.useInternalCookieIfExist === true ? cookie.value : request.cookie,
}),
Netease.song_url({
Expand Down
1 change: 1 addition & 0 deletions frontend/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const themeOverridesRef = computed(() => {
<template>
<NConfigProvider inline-theme-disabled :locale="locale === 'zh-CN' ? zhCN : enUS" :theme-overrides="themeOverridesRef" :theme="isDark ? darkTheme : null">
<NMessageProvider placement="bottom" container-class="mb-40 md:mb-30">
<DownloadPanel />
<RouterView>
<template #default="{ Component }">
<Transition
Expand Down
12 changes: 12 additions & 0 deletions frontend/apis/use-chicken-soup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import axios from 'axios'

export function useChickenSoup() {
async function getIowenChickenSoup(): Promise<string> {
const result = await axios.get('https://www.iowen.cn/jitang/api')
return result.data.data.content.content
}

return {
getIowenChickenSoup,
}
}
6 changes: 6 additions & 0 deletions frontend/components/default-header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const { width: windowWidth } = useWindowSize()
const { toggleLocales } = useToggleLocale()
const { isElectron, platform } = useEnvironment()
const route = useRoute()
const downloadPanel = useDownloadPanel()
const headerRef = ref<HTMLElement | null>(null)
Expand Down Expand Up @@ -51,6 +52,11 @@ const leftButton = computed(() => [
text: t('nav.music-player'),
onClick: () => void 0,
},
{
icon: 'i-ph-arrow-down-duotone',
text: t('my.download-list'),
onClick: () => downloadPanel.toggle(),
},
])
const rightButton = computed(() => [
{
Expand Down
19 changes: 19 additions & 0 deletions frontend/components/download-panel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script setup lang="tsx">
const downloadStore = useDownloadStore()
const downloadPanel = useDownloadPanel()
</script>

<template>
<NDrawer v-model:show="downloadPanel.isOpen" width="50%">
<NDrawerContent :title="$t('my.download-list')">
<div w-full class="flex col gap-4 justify-center">
<div v-for="(belong, belongIndex) in downloadStore.belongs" :key="belongIndex" w-full flex="~ col gap-4">
<h3 font-size-lg>
{{ belong }}
</h3>
<TaskCard v-for="(task, taskIndex) in downloadStore.getBelongTasks(belong)" :key="taskIndex" :task="task" />
</div>
</div>
</NDrawerContent>
</NDrawer>
</template>
4 changes: 2 additions & 2 deletions frontend/components/form-label.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ defineProps<{

<template>
<div>
<h2 select-none text-lg font-bold mb-1>
<h3 select-none text-lg font-bold mb-1>
{{ label }}
</h2>
</h3>
<div select-none op-70>
{{ description }}
</div>
Expand Down
6 changes: 6 additions & 0 deletions frontend/components/mobile-footer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ const footerButtons = ref([
selected: computed(() => route.path === '/my'),
label: t('nav.personal'),
},
{
icon: 'i-ph-gear-duotone',
onClick: () => router.push('/setting'),
selected: computed(() => route.path === '/setting'),
label: t('nav.setting'),
},
])
</script>

Expand Down
14 changes: 13 additions & 1 deletion frontend/components/song-list-card.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,24 @@ const emit = defineEmits<{
const isLoaded = ref(false)
const cardRef = ref<HTMLImageElement | null>(null)
const { width: imageWidth } = useElementBounding(cardRef)
const { width: windowWidth } = useWindowSize()
const imageParamSize = computed<`${number}y${number}`>(() => {
if (windowWidth.value < 768)
return '200y200'
else if (windowWidth.value < 1024)
return '300y300'
else if (windowWidth.value < 1280)
return '400y400'
else
return '500y500'
})
</script>

<template>
<div ref="cardRef" class="song-list-card" mb-4 relative cursor-pointer @click="(e) => emit('click', e, props)">
<NSkeleton v-if="!isLoaded" w-full h-full rounded-lg :style="{ 'min-height': `${imageWidth}px` }" />
<img v-show="isLoaded" rounded-lg w-full h-full transition-all class="cover" :src="`${props.cover}?param=200y200`" alt="cover" @load="isLoaded = true">
<img v-show="isLoaded" rounded-lg w-full h-full transition-all class="cover" :src="`${props.cover}?${imageParamSize}`" alt="cover" @load="isLoaded = true">
<div
class="layer hidden! md:flex!" text="white center"
p3 select-none transition-all op-0 absolute top-0 left-0 w-full h-full scale-90 hover:scale-100
Expand Down
17 changes: 17 additions & 0 deletions frontend/components/task-card.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script setup lang="tsx">
import { DownloadListItem } from '~/stores/download'
defineProps<{
task: DownloadListItem
}>()
</script>

<template>
<div>
<div flex="~ justify-between">
<div>{{ task.name }}</div>
<div>{{ $t(`my.download-status.${task.status}`) }}</div>
</div>
<NProgress :percentage="Number(task.percentage.toFixed(2))" />
</div>
</template>
24 changes: 24 additions & 0 deletions frontend/composables/download-panel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { defineStore } from 'pinia'

export const useDownloadPanel = defineStore('download-panel', () => {
const isOpen = ref(false)

function open() {
isOpen.value = true
}

function close() {
isOpen.value = false
}

function toggle() {
isOpen.value = !isOpen.value
}

return {
isOpen,
open,
close,
toggle,
}
})
8 changes: 4 additions & 4 deletions frontend/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,19 @@ function handleArtistCardClick(e: MouseEvent, item: PersonalizedArtistsResponseR

<template>
<div>
<h1 pointer-events-none select-none font-extrabold text="size-3xl md:size-2xl" flex="~ items-center gap-2">
<h2 pointer-events-none select-none font-extrabold text="size-3xl md:size-2xl" flex="~ items-center gap-2">
<div i-ph-list-numbers-duotone hidden md:block />
{{ $t('home.popular-music-list') }}
</h1>
</h2>

<div columns-2 md:columns-4 lg:columns-5 xl:columns-6 gap-4 mt-5>
<SongListCard v-for="(item, index) in personalizedSongListResult" v-bind="item" :key="index" break-inside-avoid @click="handleSongListCardClick" />
</div>

<h1 mt-10 pointer-events-none select-none font-extrabold text="size-3xl md:size-2xl" flex="~ items-center gap-2">
<h2 mt-10 pointer-events-none select-none font-extrabold text="size-3xl md:size-2xl" flex="~ items-center gap-2">
<div i-ph-user-duotone hidden md:block />
{{ $t('home.top-artists') }}
</h1>
</h2>

<div grid="~ cols-2 sm:cols-3 md:cols-4 xl:cols-6" gap-4 mt-5>
<ArtistCard v-for="(item, index) in personalizedArtistsResult" v-bind="item" :key="index" @click="handleArtistCardClick" />
Expand Down
31 changes: 17 additions & 14 deletions frontend/pages/my.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const songLists = ref<Partial<UserSongListsResponse>>({
songLists: [],
})
const avatarIsLoaded = ref(false)
async function requestAccountInfo() {
await musicController.user.getCurrentAccount({ cookie: cookie.value })
.then(response => result.value = response)
Expand All @@ -35,33 +36,35 @@ async function requestAccountInfo() {
}
if (cookie.value)
requestAccountInfo()
const avatarIsLoaded = ref(false)
</script>

<script lang="tsx">
export default { name: 'My' }
</script>

<template>
<div flex="~ col md:row gap-5 md:gap-10" pt5>
<aside flex="~ md:col gap-5 items-center md:items-start" min-w-60>
<NSkeleton v-if="!avatarIsLoaded" transition-all size-20 md:size-60 rounded-full mb-4 />
<img v-show="avatarIsLoaded" transition-all size-20 md:size-60 rounded-full mb-4 :src="result.avatar" @load="avatarIsLoaded = true">
<div flex="~ col">
<!-- eslint-disable-next-line -->
<h1 font="size-lg md:size-2xl bold">{{ result.name }}</h1>
<!-- eslint-disable-next-line -->
<p op-60>{{ result.signature }}</p>
<div select-none flex="~ col gap-5 md:gap-10" pt5>
<div flex="~ col md:row md:items-center justify-between gap-2">
<!-- Left -->
<div flex="~ items-center gap-2">
<NSkeleton v-if="!avatarIsLoaded" transition-all size-18 md:size-15 rounded-full />
<img v-show="avatarIsLoaded" transition-all size-18 md:size-15 rounded-full :src="result.avatar" @load="avatarIsLoaded = true">
<div flex="~ col">
<h1 font="size-lg md:size-3xl bold">{{ result.name }}</h1>
<p op-60 block md:hidden>{{ result.signature }}</p>
</div>
</div>
</aside>
<!-- Right -->
<p op-60 hidden md:block>{{ result.signature }}</p>
</div>

<div flex="~ col gap-4" w-full>
<h2 font="size-lg md:size-2xl bold">
<h2 font="size-lg md:size-2xl bold" flex="~ items-center gap-2">
<div i-ph-list-numbers-duotone hidden md:block />
{{ $t('my.music-list') }}
</h2>

<div columns-2 md:columns-2 lg:columns-4 xl:columns-5 gap-4>
<div columns-2 md:columns-3 lg:columns-4 xl:columns-6 gap-4>
<SongListCard
v-for="(item, index) in songLists.songLists" v-bind="item" :key="index" break-inside-avoid
@click="$router.push(`/play-list/${item.id}`)"
Expand Down
20 changes: 19 additions & 1 deletion frontend/pages/play-list/[id].vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
<script setup lang="tsx">
import { SongListDetailResponse } from '#/song-list.protocol'
import { useMessage } from 'naive-ui'
import { useMusicController } from '~/apis/music'
defineOptions({ name: 'PlayListDetail' })
useHead({ title: '歌单详情 - 网易云音乐下崽器' })
const cookie = useLocalStorage('__naily:music-downloader-cookie__', undefined)
const route = useRoute()
const message = useMessage()
const { t } = useI18n()
const { id } = route.params as { id: string }
const musicController = useMusicController()
const songListDetail = ref<Partial<SongListDetailResponse>>({})
musicController.songList.getSongListDetail({ id, cookie: cookie.value }).then(response =>
songListDetail.value = response,
)
const downloadStore = useDownloadStore()
async function downloadAll() {
message.info(t('play-list-detail.item-action.start-download'))
await Promise.all(
(songListDetail.value.songs || []).map(async song =>
await downloadStore.addDownloadTask({
id: song.id,
name: song.name,
belongTo: songListDetail.value?.name || '未知歌单',
}),
),
)
await downloadStore.startAndCompressAll()
}
</script>

<template>
Expand All @@ -40,7 +58,7 @@ musicController.songList.getSongListDetail({ id, cookie: cookie.value }).then(re
<button mt-4 bg-red-5 transition-all color-white scale="hover:105 active:95" p="x4 y2" rounded-md>
{{ $t('play-list-detail.replace-current-play-list') }}
</button>
<button mt-4 transition-all scale="hover:105 active:95" p="x4 y2" rounded-md class="bg-gray/20">
<button mt-4 transition-all scale="hover:105 active:95" p="x4 y2" rounded-md class="bg-gray/20" @click="downloadAll">
{{ $t('play-list-detail.download-all') }}
</button>
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/pages/setting.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const { proxyRulesInput, setProxy, closeProxy } = useProxy()

<template>
<div min-h-screen flex="~ col gap-3">
<h1 font-size-10 font-bold>{{ $t('setting.title') }}</h1>
<h1 font-size-4xl font-bold pointer-events-none select-none>{{ $t('setting.title') }}</h1>
<section>
<FormLabel mb4 :label="$t('setting.server-manager')" :description="$t('setting.server-manager-desc')" />
<NDynamicInput v-model:value="settingStore.serverBackends" class="server-dynamic-input" @create="() => ({ name: '', url: '' })">
Expand Down
Loading

0 comments on commit cb6accd

Please sign in to comment.