feat: 添加下载列表显示功能 可播放已经下载的歌曲 添加清除缓存功能 修复下载文件类型问题

This commit is contained in:
alger
2025-01-12 16:04:03 +08:00
parent b749854c5e
commit e70fed37da
15 changed files with 1214 additions and 103 deletions
+34 -16
View File
@@ -5,7 +5,7 @@
<h2>我的收藏</h2>
<div class="favorite-count"> {{ favoriteList.length }} </div>
</div>
<div v-if="!isComponent" class="favorite-header-right">
<div v-if="!isComponent && isElectron" class="favorite-header-right">
<n-button
v-if="!isSelecting"
secondary
@@ -81,6 +81,7 @@
</template>
<script setup lang="ts">
import { cloneDeep } from 'lodash';
import { useMessage } from 'naive-ui';
import { computed, onMounted, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
@@ -90,7 +91,7 @@ import { getMusicDetail } from '@/api/music';
import SongItem from '@/components/common/SongItem.vue';
import { getSongUrl } from '@/hooks/MusicListHook';
import type { SongResult } from '@/type/music';
import { setAnimationClass, setAnimationDelay } from '@/utils';
import { isElectron, setAnimationClass, setAnimationDelay } from '@/utils';
const store = useStore();
const message = useMessage();
@@ -140,47 +141,64 @@ const handleBatchDownload = async () => {
try {
isDownloading.value = true;
const loadingMessage = message.loading('正在下载...', { duration: 0 });
let successCount = 0;
let failCount = 0;
message.success('开始下载...');
// 移除旧的监听器
window.electron.ipcRenderer.removeAllListeners('music-download-complete');
let successCount = 0;
let failCount = 0;
// 添加新的监听器
window.electron.ipcRenderer.on('music-download-complete', (_, result) => {
if (result.success) {
successCount++;
} else if (!result.canceled) {
} else {
failCount++;
}
// 当所有下载完成时
if (successCount + failCount === selectedSongs.value.length) {
isDownloading.value = false;
loadingMessage.destroy();
message.success(`下载完成:成功 ${successCount} 首,失败 ${failCount}`);
message.success(`下载完成`);
cancelSelect();
}
});
// 开始下载选中歌曲
for (const songId of selectedSongs.value) {
const song = favoriteSongs.value.find((s) => s.id === songId);
if (!song) continue;
// 获取选中歌曲的信息
const selectedSongsList = selectedSongs.value
.map((songId) => favoriteSongs.value.find((s) => s.id === songId))
.filter((song) => song) as SongResult[];
const url = await getSongUrl(song.id, song);
// 并行获取所有歌曲的下载链接
const downloadUrls = await Promise.all(
selectedSongsList.map(async (song) => {
try {
const data = (await getSongUrl(song.id, song, true)) as any;
return { song, ...data };
} catch (error) {
console.error(`获取歌曲 ${song.name} 下载链接失败:`, error);
return { song, url: null };
}
})
);
// 开始下载有效的链接
downloadUrls.forEach(({ song, url, type }) => {
if (!url) {
failCount++;
continue;
return;
}
window.electron.ipcRenderer.send('download-music', {
url,
filename: `${song.name} - ${(song.ar || song.song?.artists)?.map((a) => a.name).join(',')}`
filename: `${song.name} - ${(song.ar || song.song?.artists)?.map((a) => a.name).join(',')}`,
songInfo: cloneDeep(song),
type
});
}
});
} catch (error) {
console.error('下载失败:', error);
isDownloading.value = false;
message.destroyAll();
message.error('下载失败');
+1 -1
View File
@@ -14,7 +14,7 @@ defineOptions({
const message = useMessage();
const store = useStore();
const router = useRouter();
const isQr = ref(false);
const isQr = ref(true);
const qrUrl = ref<string>();
onMounted(() => {
+129 -1
View File
@@ -156,7 +156,22 @@
<div class="set-item-title">重启</div>
<div class="set-item-content">重启应用</div>
</div>
<n-button type="primary" @click="restartApp">重启</n-button>
<n-button type="primary" size="small" @click="restartApp">重启</n-button>
</div>
<!-- 缓存管理 -->
<!-- <n-card class="set-card" title="缓存管理">
<n-space vertical>
<n-button type="primary" @click="showClearCacheModal = true"> 清除缓存 </n-button>
</n-space>
</n-card> -->
<div v-if="isElectron" class="set-item">
<div>
<div class="set-item-title">缓存管理</div>
<div class="set-item-content">清除缓存</div>
</div>
<n-button type="primary" size="small" @click="showClearCacheModal = true">
清除缓存
</n-button>
</div>
<div v-if="isElectron" class="set-item">
<div>
@@ -210,6 +225,42 @@
</div>
<donation-list v-if="isDonationListVisible" />
</div>
<!-- 清除缓存弹窗 -->
<n-modal
v-model:show="showClearCacheModal"
preset="dialog"
title="清除缓存"
positive-text="确认"
negative-text="取消"
@positive-click="clearCache"
@negative-click="
() => {
selectedCacheTypes = [];
}
"
>
<n-space vertical>
<p>请选择要清除的缓存类型:</p>
<n-checkbox-group v-model:value="selectedCacheTypes">
<n-space vertical>
<n-checkbox
v-for="option in clearCacheOptions"
:key="option.key"
:value="option.key"
:label="option.label"
>
<template #default>
<div>
<div>{{ option.label }}</div>
<div class="text-gray-400 text-sm">{{ option.description }}</div>
</div>
</template>
</n-checkbox>
</n-space>
</n-checkbox-group>
</n-space>
</n-modal>
</div>
<play-bottom />
<n-modal
@@ -262,6 +313,7 @@ import { useMessage } from 'naive-ui';
import { computed, onMounted, ref, watch } from 'vue';
import { useStore } from 'vuex';
import localData from '@/../main/set.json';
import Coffee from '@/components/Coffee.vue';
import DonationList from '@/components/common/DonationList.vue';
import PlayBottom from '@/components/common/PlayBottom.vue';
@@ -491,6 +543,82 @@ const toggleDonationList = () => {
isDonationListVisible.value = !isDonationListVisible.value;
localStorage.setItem('donationListVisible', isDonationListVisible.value.toString());
};
// 清除缓存相关
const showClearCacheModal = ref(false);
const clearCacheOptions = ref([
{ label: '播放历史', key: 'history', description: '清除播放过的歌曲记录' },
{ label: '收藏记录', key: 'favorite', description: '清除本地收藏的歌曲记录(不会影响云端收藏)' },
{ label: '用户数据', key: 'user', description: '清除登录信息和用户相关数据' },
{ label: '应用设置', key: 'settings', description: '清除应用的所有自定义设置' },
{ label: '下载记录', key: 'downloads', description: '清除下载历史记录(不会删除已下载的文件)' },
{ label: '音乐资源', key: 'resources', description: '清除已加载的音乐文件、歌词等资源缓存' }
]);
const selectedCacheTypes = ref<string[]>([]);
const clearCache = async () => {
const clearTasks = selectedCacheTypes.value.map(async (type) => {
switch (type) {
case 'history':
localStorage.removeItem('musicHistory');
break;
case 'favorite':
localStorage.removeItem('favoriteList');
break;
case 'user':
localStorage.removeItem('user');
localStorage.removeItem('token');
store.commit('logout');
break;
case 'settings':
if (window.electron) {
window.electron.ipcRenderer.send('set-store-value', 'set', localData);
}
localStorage.removeItem('appSettings');
localStorage.removeItem('theme');
localStorage.removeItem('lyricData');
localStorage.removeItem('lyricFontSize');
localStorage.removeItem('playMode');
break;
case 'downloads':
if (window.electron) {
window.electron.ipcRenderer.send('clear-downloads-history');
}
break;
case 'resources':
// 清除音频资源缓存
if (window.electron) {
window.electron.ipcRenderer.send('clear-audio-cache');
}
// 清除歌词缓存
localStorage.removeItem('lyricCache');
// 清除音乐URL缓存
localStorage.removeItem('musicUrlCache');
// 清除图片缓存
if (window.caches) {
try {
const cache = await window.caches.open('music-images');
await cache.keys().then((keys) => {
keys.forEach((key) => {
cache.delete(key);
});
});
} catch (error) {
console.error('清除图片缓存失败:', error);
}
}
break;
default:
break;
}
});
await Promise.all(clearTasks);
message.success('清除成功,部分设置在重启后生效');
showClearCacheModal.value = false;
selectedCacheTypes.value = [];
};
</script>
<style lang="scss" scoped>
+44 -2
View File
@@ -123,8 +123,29 @@ onBeforeUnmount(() => {
mounted.value = false;
});
// 检查登录状态
const checkLoginStatus = () => {
const token = localStorage.getItem('token');
const userData = localStorage.getItem('user');
if (!token || !userData) {
router.push('/login');
return false;
}
// 如果store中没有用户数据,但localStorage中有,则恢复用户数据
if (!store.state.user && userData) {
store.state.user = JSON.parse(userData);
}
return true;
};
const loadPage = async () => {
if (!mounted.value || !user.value) return;
if (!mounted.value) return;
// 检查登录状态
if (!checkLoginStatus()) return;
try {
infoLoading.value = true;
@@ -144,8 +165,13 @@ const loadPage = async () => {
...item.song,
picUrl: item.song.al.picUrl
}));
} catch (error) {
} catch (error: any) {
console.error('加载用户页面失败:', error);
// 如果获取用户数据失败,可能是token过期
if (error.response?.status === 401) {
store.commit('logout');
router.push('/login');
}
} finally {
if (mounted.value) {
infoLoading.value = false;
@@ -153,6 +179,16 @@ const loadPage = async () => {
}
};
// 监听路由变化
watch(
() => router.currentRoute.value.path,
(newPath) => {
if (newPath === '/user') {
checkLoginStatus();
}
}
);
// 监听用户状态变化
watch(
() => store.state.user,
@@ -168,6 +204,12 @@ watch(
{ immediate: true }
);
// 页面挂载时检查登录状态
onMounted(() => {
checkLoginStatus();
loadPage();
});
// 展示歌单
const showPlaylist = async (id: number, name: string) => {
isShowList.value = true;