mirror of
https://github.com/imsyy/SPlayer.git
synced 2025-11-25 03:14:57 +08:00
✨ feat: 完善本地目录管理
This commit is contained in:
1
src/assets/icons/FolderPlus.svg
Normal file
1
src/assets/icons/FolderPlus.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M13 19c0 .34.04.67.09 1H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h8a2 2 0 0 1 2 2v5.81c-.88-.51-1.9-.81-3-.81c-3.31 0-6 2.69-6 6m7-1v-3h-2v3h-3v2h3v3h2v-3h3v-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 279 B |
@@ -258,7 +258,8 @@ import type { DropdownOption } from "naive-ui";
|
||||
import { useStatusStore, useMusicStore, useDataStore } from "@/stores";
|
||||
import { isObject, entries, cloneDeep } from "lodash-es";
|
||||
import { openJumpArtist } from "@/utils/modal";
|
||||
import { formatNumber, isElectron, sortOptions } from "@/utils/helper";
|
||||
import { formatNumber, isElectron } from "@/utils/helper";
|
||||
import { sortOptions } from "@/utils/meta";
|
||||
import { toLikeSong } from "@/utils/auth";
|
||||
import { formatTimestamp, msToTime } from "@/utils/time";
|
||||
import SongListMenu from "@/components/Menu/SongListMenu.vue";
|
||||
|
||||
@@ -10,6 +10,36 @@
|
||||
</div>
|
||||
<n-switch class="set" v-model:value="settingStore.showLocalCover" :round="false" />
|
||||
</n-card>
|
||||
<n-card class="set-item" id="local-list-choose" content-style="flex-direction: column">
|
||||
<n-flex justify="space-between">
|
||||
<div class="label">
|
||||
<n-text class="name">本地歌曲目录</n-text>
|
||||
<n-text class="tip" :depth="3">可在此增删本地歌曲目录,歌曲增删实时同步</n-text>
|
||||
</div>
|
||||
<n-button strong secondary @click="changeLocalPath()">
|
||||
<template #icon>
|
||||
<SvgIcon name="Folder" />
|
||||
</template>
|
||||
更改
|
||||
</n-button>
|
||||
</n-flex>
|
||||
<n-collapse-transition :show="settingStore.localFilesPath.length > 0">
|
||||
<n-card
|
||||
v-for="(item, index) in settingStore.localFilesPath"
|
||||
:key="index"
|
||||
class="set-item"
|
||||
>
|
||||
<div class="label">
|
||||
<n-text class="name">{{ item }}</n-text>
|
||||
</div>
|
||||
<n-button strong secondary @click="changeLocalPath(index)">
|
||||
<template #icon>
|
||||
<SvgIcon name="Delete" />
|
||||
</template>
|
||||
</n-button>
|
||||
</n-card>
|
||||
</n-collapse-transition>
|
||||
</n-card>
|
||||
</div>
|
||||
<div class="set-list">
|
||||
<n-h3 prefix="bar"> 下载配置 </n-h3>
|
||||
@@ -89,6 +119,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSettingStore } from "@/stores";
|
||||
import { changeLocalPath } from "@/utils/helper";
|
||||
|
||||
const settingStore = useSettingStore();
|
||||
|
||||
@@ -98,3 +129,14 @@ const choosePath = async () => {
|
||||
if (path) settingStore.downloadPath = path;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#local-list-choose {
|
||||
.n-flex {
|
||||
width: 100%;
|
||||
}
|
||||
.n-collapse-transition {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { type ImageRenderToolbarProps, NTooltip, SelectOption } from "naive-ui";
|
||||
import type { SongType, UpdateLogType } from "@/types/main";
|
||||
import { NTooltip, SelectOption } from "naive-ui";
|
||||
import { h, VNode } from "vue";
|
||||
import { useClipboard } from "@vueuse/core";
|
||||
import { getCacheData } from "./cache";
|
||||
import { updateLog } from "@/api/other";
|
||||
import { isEmpty } from "lodash-es";
|
||||
import { convertToLocalTime } from "./time";
|
||||
import { useSettingStore } from "@/stores";
|
||||
import { marked } from "marked";
|
||||
import SvgIcon from "@/components/Global/SvgIcon.vue";
|
||||
|
||||
@@ -62,19 +63,6 @@ export const renderOption = ({ node, option }: { node: VNode; option: SelectOpti
|
||||
},
|
||||
);
|
||||
|
||||
// 排序选项
|
||||
export const sortOptions = {
|
||||
default: { name: "默认排序", show: "all", icon: renderIcon("Sort") },
|
||||
titleAZ: { name: "标题升序( A - Z )", show: "all", icon: renderIcon("SortAZ") },
|
||||
titleZA: { name: "标题降序( Z - A )", show: "all", icon: renderIcon("SortZA") },
|
||||
arAZ: { name: "歌手升序( A - Z )", show: "song", icon: renderIcon("SortAZ") },
|
||||
arZA: { name: "歌手降序( Z - A )", show: "song", icon: renderIcon("SortZA") },
|
||||
timeUp: { name: "时长升序", show: "all", icon: renderIcon("SortClockUp") },
|
||||
timeDown: { name: "时长降序", show: "all", icon: renderIcon("SortClockDown") },
|
||||
dateUp: { name: "日期升序", show: "radio", icon: renderIcon("SortDateUp") },
|
||||
dateDown: { name: "日期降序", show: "radio", icon: renderIcon("SortDateDown") },
|
||||
};
|
||||
|
||||
// 模糊搜索
|
||||
export const fuzzySearch = (keyword: string, data: SongType[]): SongType[] => {
|
||||
try {
|
||||
@@ -190,20 +178,6 @@ export const convertImageUrlToBlobUrl = async (imageUrl: string) => {
|
||||
return imageBlobURL;
|
||||
};
|
||||
|
||||
// 自定义图片工具栏
|
||||
export const renderToolbar = ({ nodes }: ImageRenderToolbarProps) => {
|
||||
return [
|
||||
nodes.prev,
|
||||
nodes.next,
|
||||
nodes.rotateCounterclockwise,
|
||||
nodes.rotateClockwise,
|
||||
nodes.resizeToOriginalSize,
|
||||
nodes.zoomOut,
|
||||
nodes.zoomIn,
|
||||
nodes.close,
|
||||
];
|
||||
};
|
||||
|
||||
// 复制文本
|
||||
export const copyData = async (text: any, message?: string) => {
|
||||
const { copy, copied, isSupported } = useClipboard({ legacy: true });
|
||||
@@ -281,3 +255,34 @@ export const getUpdateLog = async (): Promise<UpdateLogType[]> => {
|
||||
);
|
||||
return updateLogs;
|
||||
};
|
||||
|
||||
/**
|
||||
* 更改本地目录
|
||||
* @param delIndex 删除文件夹路径的索引
|
||||
*/
|
||||
export const changeLocalPath = async (delIndex?: number) => {
|
||||
try {
|
||||
if (!isElectron) return;
|
||||
const settingStore = useSettingStore();
|
||||
if (typeof delIndex === "number" && delIndex >= 0) {
|
||||
settingStore.localFilesPath.splice(delIndex, 1);
|
||||
} else {
|
||||
const selectedDir = await window.electron.ipcRenderer.invoke("choose-path");
|
||||
if (!selectedDir) return;
|
||||
// 检查是否为子文件夹
|
||||
const defaultMusicPath = await window.electron.ipcRenderer.invoke("get-default-dir", "music");
|
||||
const allPath = [defaultMusicPath, ...settingStore.localFilesPath];
|
||||
const isSubfolder = allPath.some((existingPath) => {
|
||||
return selectedDir.startsWith(existingPath);
|
||||
});
|
||||
if (!isSubfolder) {
|
||||
settingStore.localFilesPath.push(selectedDir);
|
||||
} else {
|
||||
window.$message.error("添加的目录与现有目录有重叠,请重新选择");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error changing local path:", error);
|
||||
window.$message.error("更改本地歌曲文件夹出错,请重试");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { SongLevelType } from "@/types/main";
|
||||
import type { ImageRenderToolbarProps } from "naive-ui";
|
||||
import { compact, findKey, keys, pick, takeWhile } from "lodash-es";
|
||||
import { renderIcon } from "./helper";
|
||||
|
||||
// 音质数据
|
||||
export const songLevelData = {
|
||||
@@ -58,3 +60,30 @@ export function getLevelsUpTo(level: string): Partial<typeof songLevelData> {
|
||||
// 过滤空值
|
||||
return pick(songLevelData, compact(resultKeys));
|
||||
}
|
||||
|
||||
// 排序选项
|
||||
export const sortOptions = {
|
||||
default: { name: "默认排序", show: "all", icon: renderIcon("Sort") },
|
||||
titleAZ: { name: "标题升序( A - Z )", show: "all", icon: renderIcon("SortAZ") },
|
||||
titleZA: { name: "标题降序( Z - A )", show: "all", icon: renderIcon("SortZA") },
|
||||
arAZ: { name: "歌手升序( A - Z )", show: "song", icon: renderIcon("SortAZ") },
|
||||
arZA: { name: "歌手降序( Z - A )", show: "song", icon: renderIcon("SortZA") },
|
||||
timeUp: { name: "时长升序", show: "all", icon: renderIcon("SortClockUp") },
|
||||
timeDown: { name: "时长降序", show: "all", icon: renderIcon("SortClockDown") },
|
||||
dateUp: { name: "日期升序", show: "radio", icon: renderIcon("SortDateUp") },
|
||||
dateDown: { name: "日期降序", show: "radio", icon: renderIcon("SortDateDown") },
|
||||
};
|
||||
|
||||
// 自定义图片工具栏
|
||||
export const renderToolbar = ({ nodes }: ImageRenderToolbarProps) => {
|
||||
return [
|
||||
nodes.prev,
|
||||
nodes.next,
|
||||
nodes.rotateCounterclockwise,
|
||||
nodes.rotateClockwise,
|
||||
nodes.resizeToOriginalSize,
|
||||
nodes.zoomOut,
|
||||
nodes.zoomIn,
|
||||
nodes.close,
|
||||
];
|
||||
};
|
||||
|
||||
@@ -140,7 +140,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { DropdownOption } from "naive-ui";
|
||||
import type { ArtistType } from "@/types/main";
|
||||
import { coverLoaded, renderIcon, renderToolbar } from "@/utils/helper";
|
||||
import { coverLoaded, renderIcon } from "@/utils/helper";
|
||||
import { renderToolbar } from "@/utils/meta";
|
||||
import { artistDetail } from "@/api/artist";
|
||||
import { formatArtistsList } from "@/utils/format";
|
||||
import { useDataStore, useSettingStore } from "@/stores";
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
<!-- 分类选择 -->
|
||||
<n-modal
|
||||
v-model:show="catChangeShow"
|
||||
:bordered="false"
|
||||
display-directive="show"
|
||||
style="width: 600px"
|
||||
preset="card"
|
||||
|
||||
@@ -165,7 +165,8 @@ import type { DropdownOption } from "naive-ui";
|
||||
import { songDetail } from "@/api/song";
|
||||
import { albumDetail } from "@/api/album";
|
||||
import { formatCoverList, formatSongsList } from "@/utils/format";
|
||||
import { coverLoaded, fuzzySearch, renderIcon, renderToolbar } from "@/utils/helper";
|
||||
import { coverLoaded, fuzzySearch, renderIcon } from "@/utils/helper";
|
||||
import { renderToolbar } from "@/utils/meta";
|
||||
import { useDataStore, useStatusStore } from "@/stores";
|
||||
import { debounce } from "lodash-es";
|
||||
import { formatTimestamp } from "@/utils/time";
|
||||
|
||||
@@ -172,7 +172,8 @@ import type { DropdownOption, MessageReactive } from "naive-ui";
|
||||
import { songDetail } from "@/api/song";
|
||||
import { playlistDetail, playlistAllSongs } from "@/api/playlist";
|
||||
import { formatCoverList, formatSongsList } from "@/utils/format";
|
||||
import { coverLoaded, formatNumber, fuzzySearch, renderIcon, renderToolbar } from "@/utils/helper";
|
||||
import { coverLoaded, formatNumber, fuzzySearch, renderIcon } from "@/utils/helper";
|
||||
import { renderToolbar } from "@/utils/meta";
|
||||
import { debounce, isObject } from "lodash-es";
|
||||
import { useDataStore, useStatusStore } from "@/stores";
|
||||
import { openBatchList, openUpdatePlaylist } from "@/utils/modal";
|
||||
|
||||
@@ -210,7 +210,8 @@ import type { DropdownOption, MessageReactive } from "naive-ui";
|
||||
import { songDetail } from "@/api/song";
|
||||
import { playlistDetail, playlistAllSongs, deletePlaylist } from "@/api/playlist";
|
||||
import { formatCoverList, formatSongsList } from "@/utils/format";
|
||||
import { coverLoaded, formatNumber, fuzzySearch, renderIcon, renderToolbar } from "@/utils/helper";
|
||||
import { coverLoaded, formatNumber, fuzzySearch, renderIcon } from "@/utils/helper";
|
||||
import { renderToolbar } from "@/utils/meta";
|
||||
import { isLogin, updateUserLikePlaylist } from "@/utils/auth";
|
||||
import { debounce } from "lodash-es";
|
||||
import { useDataStore, useStatusStore } from "@/stores";
|
||||
|
||||
@@ -172,7 +172,8 @@
|
||||
import type { CoverType, SongType } from "@/types/main";
|
||||
import type { DropdownOption, MessageReactive } from "naive-ui";
|
||||
import { formatCoverList, formatSongsList } from "@/utils/format";
|
||||
import { coverLoaded, fuzzySearch, renderIcon, renderToolbar } from "@/utils/helper";
|
||||
import { coverLoaded, fuzzySearch, renderIcon } from "@/utils/helper";
|
||||
import { renderToolbar } from "@/utils/meta";
|
||||
import { debounce } from "lodash-es";
|
||||
import { useDataStore, useStatusStore } from "@/stores";
|
||||
import { radioAllProgram, radioDetail } from "@/api/radio";
|
||||
|
||||
@@ -87,6 +87,51 @@
|
||||
</KeepAlive>
|
||||
</Transition>
|
||||
</RouterView>
|
||||
<!-- 目录管理 -->
|
||||
<n-modal
|
||||
v-model:show="localPathShow"
|
||||
:close-on-esc="false"
|
||||
:mask-closable="false"
|
||||
preset="card"
|
||||
title="目录管理"
|
||||
transform-origin="center"
|
||||
style="width: 600px"
|
||||
>
|
||||
<n-list class="local-list" hoverable clickable bordered>
|
||||
<template #header>
|
||||
<n-text>请选择本地音乐文件夹,将自动扫描您添加的目录,歌曲增删实时同步</n-text>
|
||||
</template>
|
||||
<n-list-item v-if="defaultMusicPath">
|
||||
<template #prefix>
|
||||
<SvgIcon :size="20" name="FolderMusic" />
|
||||
</template>
|
||||
<n-thing :title="defaultMusicPath" description="系统默认音乐文件夹,无法更改" />
|
||||
</n-list-item>
|
||||
<n-list-item v-for="(item, index) in settingStore.localFilesPath" :key="index">
|
||||
<template #prefix>
|
||||
<SvgIcon :size="20" name="Folder" />
|
||||
</template>
|
||||
<template #suffix>
|
||||
<n-button quaternary @click="changeLocalPath(index)">
|
||||
<template #icon>
|
||||
<SvgIcon :size="20" name="Delete" />
|
||||
</template>
|
||||
</n-button>
|
||||
</template>
|
||||
<n-thing :title="item" />
|
||||
</n-list-item>
|
||||
</n-list>
|
||||
<template #footer>
|
||||
<n-flex justify="center">
|
||||
<n-button class="add-path" strong secondary @click="changeLocalPath()">
|
||||
<template #icon>
|
||||
<SvgIcon name="FolderPlus" />
|
||||
</template>
|
||||
添加文件夹
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -96,7 +141,7 @@ import type { DropdownOption, MessageReactive } from "naive-ui";
|
||||
import { useLocalStore, useSettingStore } from "@/stores";
|
||||
import { formatSongsList } from "@/utils/format";
|
||||
import { uniqBy, flattenDeep, debounce } from "lodash-es";
|
||||
import { fuzzySearch, renderIcon } from "@/utils/helper";
|
||||
import { changeLocalPath, fuzzySearch, renderIcon } from "@/utils/helper";
|
||||
import { openBatchList } from "@/utils/modal";
|
||||
import player from "@/utils/player";
|
||||
|
||||
@@ -117,6 +162,10 @@ const localType = ref<string>((router.currentRoute.value?.name as string) || "lo
|
||||
const searchValue = ref<string>("");
|
||||
const searchData = ref<SongType[]>([]);
|
||||
|
||||
// 目录管理
|
||||
const defaultMusicPath = ref<string>("");
|
||||
const localPathShow = ref<boolean>(false);
|
||||
|
||||
// 列表数据
|
||||
const listData = computed<SongType[]>(() => {
|
||||
if (searchValue.value && searchData.value.length) return searchData.value;
|
||||
@@ -125,8 +174,8 @@ const listData = computed<SongType[]>(() => {
|
||||
|
||||
// 获取音乐文件夹
|
||||
const getMusicFolder = async (): Promise<string[]> => {
|
||||
const defaultPath = await window.electron.ipcRenderer.invoke("get-default-dir", "music");
|
||||
return [defaultPath, ...settingStore.localFilesPath];
|
||||
defaultMusicPath.value = await window.electron.ipcRenderer.invoke("get-default-dir", "music");
|
||||
return [defaultMusicPath.value, ...settingStore.localFilesPath];
|
||||
};
|
||||
|
||||
// 全部音乐大小
|
||||
@@ -140,6 +189,9 @@ const moreOptions = computed<DropdownOption[]>(() => [
|
||||
{
|
||||
label: "本地目录管理",
|
||||
key: "folder",
|
||||
props: {
|
||||
onClick: () => (localPathShow.value = true),
|
||||
},
|
||||
icon: renderIcon("FolderCog"),
|
||||
},
|
||||
{
|
||||
@@ -207,6 +259,13 @@ const listSearch = debounce((val: string) => {
|
||||
|
||||
localEventBus.on(() => getAllLocalMusic());
|
||||
|
||||
// 本地目录变化
|
||||
watch(
|
||||
() => settingStore.localFilesPath,
|
||||
async () => await getAllLocalMusic(),
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
onBeforeRouteUpdate((to) => {
|
||||
if (to.matched[0].name !== "local") return;
|
||||
localType.value = to.name as string;
|
||||
@@ -282,4 +341,17 @@ onMounted(getAllLocalMusic);
|
||||
max-height: calc((var(--layout-height) - 132) * 1px);
|
||||
}
|
||||
}
|
||||
.local-list {
|
||||
:deep(.n-list-item__prefix) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
:deep(.n-list-item__main) {
|
||||
.n-thing-main__description {
|
||||
font-size: 13px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<template>
|
||||
<div class="local-songs">
|
||||
<SongList :data="data" :loading="loading" :hidden-cover="!settingStore.showLocalCover" />
|
||||
<SongList
|
||||
:key="data?.length"
|
||||
:data="data"
|
||||
:loading="loading"
|
||||
:hidden-cover="!settingStore.showLocalCover"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user