mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-05-17 10:27:30 +08:00
✨ feat: 添加下载列表显示功能 可播放已经下载的歌曲 添加清除缓存功能 修复下载文件类型问题
This commit is contained in:
@@ -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('下载失败');
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user