Skip to content

Commit 7ddb5fa

Browse files
committed
✨ feat: 新增本地歌单相关功能
1 parent c53262c commit 7ddb5fa

8 files changed

Lines changed: 543 additions & 77 deletions

File tree

src/components/Layout/Menu.vue

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@
1818

1919
<script setup lang="ts">
2020
import { usePlayerController } from "@/core/player/PlayerController";
21-
import { useDataStore, useMusicStore, useSettingStore, useStatusStore } from "@/stores";
21+
import {
22+
useDataStore,
23+
useLocalStore,
24+
useMusicStore,
25+
useSettingStore,
26+
useStatusStore,
27+
} from "@/stores";
2228
import type { CoverType } from "@/types/main";
2329
import { isLogin } from "@/utils/auth";
2430
import { isElectron } from "@/utils/env";
@@ -34,11 +40,13 @@ import {
3440
NButton,
3541
NEllipsis,
3642
NText,
43+
NPopselect,
3744
} from "naive-ui";
3845
import { RouterLink, useRouter } from "vue-router";
3946
4047
const router = useRouter();
4148
const dataStore = useDataStore();
49+
const localStore = useLocalStore();
4250
const musicStore = useMusicStore();
4351
const statusStore = useStatusStore();
4452
const settingStore = useSettingStore();
@@ -47,6 +55,7 @@ const player = usePlayerController();
4755
// 菜单数据
4856
const menuRef = ref<MenuInst | null>(null);
4957
const menuActiveKey = ref<string | number>((router.currentRoute.value.name as string) || "home");
58+
const playlistMode = ref<"online" | "local">("online");
5059
5160
// 菜单内容
5261
const menuOptions = computed<MenuOption[] | MenuGroupOption[]>(() => {
@@ -166,14 +175,38 @@ const menuOptions = computed<MenuOption[] | MenuGroupOption[]>(() => {
166175
key: "divider-two",
167176
type: "divider",
168177
},
169-
// 创建的歌单
170178
{
171179
key: "user-playlists",
172180
show: !settingStore.sidebarHide.hideUserPlaylists,
173181
icon: statusStore.menuCollapsed ? renderIcon("PlaylistAdd") : undefined,
174182
label: () =>
175183
h("div", { class: "user-list" }, [
176-
h(NText, { depth: 3 }, () => ["创建的歌单"]),
184+
h(NText, { depth: 3 }, () =>
185+
playlistMode.value === "online" ? "创建的歌单" : "本地歌单",
186+
),
187+
h(
188+
NPopselect,
189+
{
190+
options: [
191+
{ label: "在线歌单", value: "online" },
192+
{ label: "本地歌单", value: "local" },
193+
],
194+
value: playlistMode.value,
195+
trigger: "click",
196+
onUpdateValue: (value: "online" | "local") => {
197+
playlistMode.value = value;
198+
},
199+
},
200+
() =>
201+
h(NButton, {
202+
type: "tertiary",
203+
round: true,
204+
strong: true,
205+
secondary: true,
206+
renderIcon: renderIcon("Menu"),
207+
onClick: (e: Event) => e.stopPropagation(),
208+
}),
209+
),
177210
h(NButton, {
178211
type: "tertiary",
179212
round: true,
@@ -182,11 +215,14 @@ const menuOptions = computed<MenuOption[] | MenuGroupOption[]>(() => {
182215
renderIcon: renderIcon("Add"),
183216
onclick: (event: Event) => {
184217
event.stopPropagation();
185-
openCreatePlaylist();
218+
openCreatePlaylist(playlistMode.value === "local");
186219
},
187220
}),
188221
]),
189-
children: [...createPlaylist.value],
222+
children:
223+
playlistMode.value === "online"
224+
? [...createPlaylist.value]
225+
: [...localPlaylistMenu.value],
190226
},
191227
// 收藏的歌单
192228
{
@@ -249,6 +285,27 @@ const likedPlaylist = computed<MenuOption[]>(() => {
249285
return renderPlaylist(list, settingStore.menuShowCover);
250286
});
251287
288+
// 本地歌单菜单
289+
const localPlaylistMenu = computed<MenuOption[]>(() => {
290+
const playlists = localStore.localPlaylists;
291+
if (!playlists || playlists.length === 0) return [];
292+
return playlists.map((playlist) => ({
293+
key: `local-${playlist.id}`,
294+
label: () =>
295+
settingStore.menuShowCover
296+
? h("div", { class: "pl-cover" }, [
297+
h(NAvatar, {
298+
src: playlist.cover || "/images/album.jpg?asset",
299+
fallbackSrc: "/images/album.jpg?asset",
300+
lazy: true,
301+
}),
302+
h(NEllipsis, null, () => playlist.name),
303+
])
304+
: h(NEllipsis, null, () => playlist.name),
305+
icon: settingStore.menuShowCover ? undefined : renderIcon("PlayList"),
306+
}));
307+
});
308+
252309
// 渲染菜单路由
253310
const renderMenuLabel = (option: MenuOption) => {
254311
// 路由链接
@@ -283,6 +340,13 @@ const menuUpdate = (key: string, item: MenuOption) => {
283340
name: "playlist",
284341
query: { id: item.key },
285342
});
343+
} else if (typeof key === "string" && key.startsWith("local-")) {
344+
// 本地歌单
345+
const localId = key.replace("local-", "");
346+
router.push({
347+
name: "playlist",
348+
query: { id: localId },
349+
});
286350
} else {
287351
switch (key) {
288352
case "like-songs":
@@ -334,8 +398,18 @@ const checkMenuItem = () => {
334398
const isUserPlaylist = dataStore.userLikeData.playlists.some(
335399
(playlist) => playlist?.id === playlistId,
336400
);
337-
if (playlistId) menuActiveKey.value = isUserPlaylist ? Number(playlistId) : "home";
338-
menuRef.value?.showOption(playlistId);
401+
// 是否为本地歌单
402+
const isLocalPlaylist = playlistId.toString().length === 16;
403+
if (!playlistId) menuActiveKey.value = "home";
404+
if (isUserPlaylist) {
405+
menuActiveKey.value = Number(playlistId);
406+
menuRef.value?.showOption(playlistId);
407+
} else if (isLocalPlaylist) {
408+
menuActiveKey.value = `local-${playlistId}`;
409+
menuRef.value?.showOption(`local-${playlistId}`);
410+
} else {
411+
menuActiveKey.value = "home";
412+
}
339413
break;
340414
}
341415
default:
@@ -401,11 +475,11 @@ watch(
401475
.user-list {
402476
display: flex;
403477
align-items: center;
478+
gap: 8px;
404479
.n-text {
405480
font-size: 0.93em;
406481
}
407482
.n-button {
408-
margin-left: 12px;
409483
--n-height: 22px;
410484
--n-padding: 0 12px;
411485
--n-icon-size: 12px;

src/components/Modal/CreatePlaylist.vue

Lines changed: 70 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,54 @@
11
<template>
22
<div class="create-playlist">
3-
<n-tabs v-model:value="playlistType" type="segment" animated>
4-
<n-tab-pane :disabled="isLogin() !== 1" name="online" tab="在线歌单">
5-
<n-form ref="onlineFormRef" :model="onlineFormData" :rules="onlineFormRules">
6-
<n-form-item label="歌单名称" path="name">
7-
<n-input v-model:value="onlineFormData.name" placeholder="请输入歌单名称" />
8-
</n-form-item>
9-
<n-form-item label="歌单类型" path="type">
10-
<n-select v-model:value="onlineFormData.type" :options="onlinePlaylistType" />
11-
</n-form-item>
12-
<n-form-item label="设为隐私歌单" path="privacy" label-placement="left">
13-
<n-switch v-model:value="onlineFormData.privacy" />
14-
</n-form-item>
15-
</n-form>
16-
</n-tab-pane>
17-
<n-tab-pane name="local" tab="本地歌单">
18-
<n-empty description="暂未实现" />
19-
</n-tab-pane>
20-
</n-tabs>
3+
<!-- 在线歌单表单 -->
4+
<template v-if="!isLocal">
5+
<n-form ref="onlineFormRef" :model="onlineFormData" :rules="onlineFormRules">
6+
<n-form-item label="歌单名称" path="name">
7+
<n-input v-model:value="onlineFormData.name" placeholder="请输入歌单名称" />
8+
</n-form-item>
9+
<n-form-item label="歌单类型" path="type">
10+
<n-select v-model:value="onlineFormData.type" :options="onlinePlaylistType" />
11+
</n-form-item>
12+
<n-form-item label="设为隐私歌单" path="privacy" label-placement="left">
13+
<n-switch v-model:value="onlineFormData.privacy" />
14+
</n-form-item>
15+
</n-form>
16+
</template>
17+
<!-- 本地歌单表单 -->
18+
<template v-else>
19+
<n-form ref="localFormRef" :model="localFormData" :rules="localFormRules">
20+
<n-form-item label="歌单名称" path="name">
21+
<n-input v-model:value="localFormData.name" placeholder="请输入歌单名称" />
22+
</n-form-item>
23+
<n-form-item label="歌单描述" path="description">
24+
<n-input
25+
v-model:value="localFormData.description"
26+
type="textarea"
27+
placeholder="请输入歌单描述(选填)"
28+
:autosize="{ minRows: 2, maxRows: 4 }"
29+
/>
30+
</n-form-item>
31+
</n-form>
32+
</template>
2133
<n-button class="create" type="primary" @click="toCreatePlaylist"> 新建 </n-button>
2234
</div>
2335
</template>
2436

2537
<script setup lang="ts">
2638
import type { FormInst, FormRules, SelectOption } from "naive-ui";
27-
import { useDataStore } from "@/stores";
39+
import { useDataStore, useLocalStore } from "@/stores";
2840
import { textRule } from "@/utils/rules";
2941
import { debounce } from "lodash-es";
3042
import { createPlaylist } from "@/api/playlist";
31-
import { isLogin, updateUserLikePlaylist } from "@/utils/auth";
43+
import { updateUserLikePlaylist } from "@/utils/auth";
44+
45+
const props = withDefaults(
46+
defineProps<{
47+
/** 是否为本地歌单模式 */
48+
isLocal?: boolean;
49+
}>(),
50+
{ isLocal: false },
51+
);
3252
3353
const emit = defineEmits<{ close: [] }>();
3454
@@ -39,16 +59,24 @@ interface OnlineFormType {
3959
privacy?: boolean;
4060
}
4161
42-
const dataStore = useDataStore();
62+
interface LocalFormType {
63+
name: string;
64+
description?: string;
65+
}
4366
44-
// 歌单类别
45-
const playlistType = ref<"online" | "local">(isLogin() === 1 ? "online" : "local");
67+
const dataStore = useDataStore();
68+
const localStore = useLocalStore();
4669
4770
// 在线歌单数据
4871
const onlineFormRef = ref<FormInst | null>(null);
4972
const onlineFormData = ref<OnlineFormType>({ name: "", type: "NORMAL", privacy: false });
5073
const onlineFormRules: FormRules = { name: textRule };
5174
75+
// 本地歌单数据
76+
const localFormRef = ref<FormInst | null>(null);
77+
const localFormData = ref<LocalFormType>({ name: "", description: "" });
78+
const localFormRules: FormRules = { name: textRule };
79+
5280
// 在线歌单类型
5381
const onlinePlaylistType: SelectOption[] = [
5482
{
@@ -71,10 +99,9 @@ const onlinePlaylistType: SelectOption[] = [
7199
const toCreatePlaylist = debounce(
72100
async (e: MouseEvent) => {
73101
e.preventDefault();
74-
if (playlistType.value === "online") {
75-
// 是否输入
102+
if (!props.isLocal) {
103+
// 在线歌单
76104
await onlineFormRef.value?.validate((errors) => errors);
77-
// 新建歌单
78105
const result = await createPlaylist(
79106
onlineFormData.value.name,
80107
onlineFormData.value.privacy,
@@ -90,6 +117,23 @@ const toCreatePlaylist = debounce(
90117
} else {
91118
window.$message.error(result.message || "新建歌单失败,请重试");
92119
}
120+
} else {
121+
// 本地歌单
122+
try {
123+
await localFormRef.value?.validate();
124+
await localStore.createLocalPlaylist(
125+
localFormData.value.name,
126+
localFormData.value.description,
127+
);
128+
emit("close");
129+
window.$message.success("新建本地歌单成功");
130+
} catch (error) {
131+
if (error) {
132+
// 验证失败,不做处理
133+
return;
134+
}
135+
window.$message.error("新建本地歌单失败,请重试");
136+
}
93137
}
94138
},
95139
300,

0 commit comments

Comments
 (0)