feat: 完善本地目录管理

This commit is contained in:
imsyy
2024-09-27 11:34:53 +08:00
parent 50374e173e
commit 0c82d6a096
13 changed files with 198 additions and 39 deletions

View 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

View File

@@ -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";

View File

@@ -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>

View File

@@ -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("更改本地歌曲文件夹出错,请重试");
}
};

View File

@@ -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,
];
};

View File

@@ -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";

View File

@@ -44,7 +44,6 @@
<!-- 分类选择 -->
<n-modal
v-model:show="catChangeShow"
:bordered="false"
display-directive="show"
style="width: 600px"
preset="card"

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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>

View File

@@ -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>