Merge pull request #256 from algerkong/fix/downloadurl

fix: 修复并优化下载功能,重构添加 hook
This commit is contained in:
Alger
2025-05-22 22:15:58 +08:00
committed by GitHub
5 changed files with 280 additions and 236 deletions
@@ -17,7 +17,7 @@
> >
<n-drawer-content :title="t('download.title')" closable :native-scrollbar="false"> <n-drawer-content :title="t('download.title')" closable :native-scrollbar="false">
<div class="drawer-container"> <div class="drawer-container">
<n-tabs type="line" animated class="h-full"> <n-tabs type="line" animated class="h-full" v-model:value="tabName">
<!-- 下载列表 --> <!-- 下载列表 -->
<n-tab-pane name="downloading" :tab="t('download.tabs.downloading')" class="h-full"> <n-tab-pane name="downloading" :tab="t('download.tabs.downloading')" class="h-full">
<div class="download-list"> <div class="download-list">
@@ -249,7 +249,7 @@ interface DownloadedItem {
picUrl: string; picUrl: string;
ar: { name: string }[]; ar: { name: string }[];
} }
const tabName = ref('downloading');
const message = useMessage(); const message = useMessage();
// const playerStore = usePlayerStore(); // const playerStore = usePlayerStore();
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
@@ -540,6 +540,16 @@ onMounted(() => {
const handleDrawerClose = () => { const handleDrawerClose = () => {
settingsStore.showDownloadDrawer = false; settingsStore.showDownloadDrawer = false;
}; };
watch(
() => tabName.value,
(newVal) => {
if (newVal) {
refreshDownloadedList();
}
}
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
+2 -70
View File
@@ -144,6 +144,7 @@ import { usePlayerStore } from '@/store';
import type { SongResult } from '@/type/music'; import type { SongResult } from '@/type/music';
import { getImgUrl, isElectron } from '@/utils'; import { getImgUrl, isElectron } from '@/utils';
import { getImageBackground } from '@/utils/linearColor'; import { getImageBackground } from '@/utils/linearColor';
import { useDownload } from '@/hooks/useDownload';
const { t } = useI18n(); const { t } = useI18n();
@@ -191,8 +192,6 @@ const dropdownX = ref(0);
const dropdownY = ref(0); const dropdownY = ref(0);
const isHovering = ref(false); const isHovering = ref(false);
const isDownloading = ref(false);
const openPlaylistDrawer = inject<(songId: number | string) => void>('openPlaylistDrawer'); const openPlaylistDrawer = inject<(songId: number | string) => void>('openPlaylistDrawer');
const { navigateToArtist } = useArtist(); const { navigateToArtist } = useArtist();
@@ -377,74 +376,7 @@ const handleSelect = (key: string | number) => {
}; };
// 下载音乐 // 下载音乐
const downloadMusic = async () => { const { isDownloading, downloadMusic } = useDownload();
if (isDownloading.value) {
message.warning(t('songItem.message.downloading'));
return;
}
try {
isDownloading.value = true;
const data = (await getSongUrl(props.item.id as number, cloneDeep(props.item), true)) as any;
if (!data || !data.url) {
throw new Error(t('songItem.message.getUrlFailed'));
}
// 构建文件名
const artistNames = (props.item.ar || props.item.song?.artists)?.map((a) => a.name).join(',');
const filename = `${props.item.name} - ${artistNames}`;
console.log('props.item', props.item);
const songData = cloneDeep(props.item);
songData.ar = songData.ar || songData.song?.artists;
// 发送下载请求
window.electron.ipcRenderer.send('download-music', {
url: data.url,
type: data.type,
filename,
songInfo: {
...songData,
downloadTime: Date.now()
}
});
message.success(t('songItem.message.downloadQueued'));
// 监听下载完成事件
const handleDownloadComplete = (_, result) => {
if (result.filename === filename) {
isDownloading.value = false;
removeListeners();
}
};
// 监听下载错误事件
const handleDownloadError = (_, result) => {
if (result.filename === filename) {
isDownloading.value = false;
removeListeners();
}
};
// 移除监听器函数
const removeListeners = () => {
window.electron.ipcRenderer.removeListener('music-download-complete', handleDownloadComplete);
window.electron.ipcRenderer.removeListener('music-download-error', handleDownloadError);
};
// 添加事件监听器
window.electron.ipcRenderer.once('music-download-complete', handleDownloadComplete);
window.electron.ipcRenderer.once('music-download-error', handleDownloadError);
// 30秒后自动清理监听器(以防下载过程中出现未知错误)
setTimeout(removeListeners, 30000);
} catch (error: any) {
console.error('Download error:', error);
isDownloading.value = false;
message.error(error.message || t('songItem.message.downloadFailed'));
}
};
const emits = defineEmits(['play', 'select', 'remove-song']); const emits = defineEmits(['play', 'select', 'remove-song']);
const songImageRef = useTemplateRef('songImg'); const songImageRef = useTemplateRef('songImg');
+173
View File
@@ -0,0 +1,173 @@
import { cloneDeep } from 'lodash';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useMessage } from 'naive-ui';
import { getSongUrl } from '@/store/modules/player';
import type { SongResult } from '@/type/music';
export const useDownload = () => {
const { t } = useI18n();
const message = useMessage();
const isDownloading = ref(false);
/**
* 下载单首音乐
* @param song 歌曲信息
* @returns Promise<void>
*/
const downloadMusic = async (song: SongResult) => {
if (isDownloading.value) {
message.warning(t('songItem.message.downloading'));
return;
}
try {
isDownloading.value = true;
const musicUrl = (await getSongUrl(song.id as number, cloneDeep(song), true)) as any;
if (!musicUrl) {
throw new Error(t('songItem.message.getUrlFailed'));
}
// 构建文件名
const artistNames = (song.ar || song.song?.artists)?.map((a) => a.name).join(',');
const filename = `${song.name} - ${artistNames}`;
const songData = cloneDeep(song);
songData.ar = songData.ar || songData.song?.artists;
// 发送下载请求
window.electron.ipcRenderer.send('download-music', {
url: musicUrl,
filename,
songInfo: {
...songData,
downloadTime: Date.now()
}
});
message.success(t('songItem.message.downloadQueued'));
// 监听下载完成事件
const handleDownloadComplete = (_, result) => {
if (result.filename === filename) {
isDownloading.value = false;
removeListeners();
}
};
// 监听下载错误事件
const handleDownloadError = (_, result) => {
if (result.filename === filename) {
isDownloading.value = false;
removeListeners();
}
};
// 移除监听器函数
const removeListeners = () => {
window.electron.ipcRenderer.removeListener('music-download-complete', handleDownloadComplete);
window.electron.ipcRenderer.removeListener('music-download-error', handleDownloadError);
};
// 添加事件监听器
window.electron.ipcRenderer.once('music-download-complete', handleDownloadComplete);
window.electron.ipcRenderer.once('music-download-error', handleDownloadError);
// 30秒后自动清理监听器(以防下载过程中出现未知错误)
setTimeout(removeListeners, 30000);
} catch (error: any) {
console.error('Download error:', error);
isDownloading.value = false;
message.error(error.message || t('songItem.message.downloadFailed'));
}
};
/**
* 批量下载音乐
* @param songs 歌曲列表
* @returns Promise<void>
*/
const batchDownloadMusic = async (songs: SongResult[]) => {
if (isDownloading.value) {
message.warning(t('favorite.downloading'));
return;
}
if (songs.length === 0) {
message.warning(t('favorite.selectSongsFirst'));
return;
}
try {
isDownloading.value = true;
message.success(t('favorite.downloading'));
// 移除旧的监听器
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 {
failCount++;
}
// 当所有下载完成时
if (successCount + failCount === songs.length) {
isDownloading.value = false;
message.success(t('favorite.downloadSuccess'));
window.electron.ipcRenderer.removeAllListeners('music-download-complete');
}
});
// 并行获取所有歌曲的下载链接
const downloadUrls = await Promise.all(
songs.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++;
return;
}
const songData = cloneDeep(song);
const songInfo = {
...songData,
ar: songData.ar || songData.song?.artists,
downloadTime: Date.now()
};
window.electron.ipcRenderer.send('download-music', {
url,
filename: `${song.name} - ${(song.ar || song.song?.artists)?.map((a) => a.name).join(',')}`,
songInfo,
type
});
});
} catch (error) {
console.error('下载失败:', error);
isDownloading.value = false;
message.destroyAll();
message.error(t('favorite.downloadFailed'));
}
};
return {
isDownloading,
downloadMusic,
batchDownloadMusic
};
};
+66 -60
View File
@@ -81,70 +81,76 @@ export const getSongUrl = async (
songData: SongResult, songData: SongResult,
isDownloaded: boolean = false isDownloaded: boolean = false
) => { ) => {
if (songData.playMusicUrl) {
return songData.playMusicUrl;
}
if (songData.source === 'bilibili' && songData.bilibiliData) {
console.log('加载B站音频URL');
if (!songData.playMusicUrl && songData.bilibiliData.bvid && songData.bilibiliData.cid) {
try {
songData.playMusicUrl = await getBilibiliAudioUrl(
songData.bilibiliData.bvid,
songData.bilibiliData.cid
);
return songData.playMusicUrl;
} catch (error) {
console.error('重启后获取B站音频URL失败:', error);
return '';
}
}
return songData.playMusicUrl || '';
}
const numericId = typeof id === 'string' ? parseInt(id, 10) : id;
// 检查是否有自定义音源设置
const songId = String(id);
const savedSource = localStorage.getItem(`song_source_${songId}`);
// 如果有自定义音源设置,直接使用getParsingMusicUrl获取URL
if (savedSource && songData.source !== 'bilibili') {
try {
console.log(`使用自定义音源解析歌曲 ID: ${songId}`);
const res = await getParsingMusicUrl(numericId, cloneDeep(songData));
if (res && res.data && res.data.data && res.data.data.url) {
return res.data.data.url;
}
// 如果自定义音源解析失败,继续使用正常的获取流程
console.warn('自定义音源解析失败,使用默认音源');
} catch (error) {
console.error('error',error)
console.error('自定义音源解析出错:', error);
}
}
// 正常获取URL流程
const { data } = await getMusicUrl(numericId, isDownloaded);
let url = '';
let songDetail = null;
try { try {
if (data.data[0].freeTrialInfo || !data.data[0].url) { if (songData.playMusicUrl) {
const res = await getParsingMusicUrl(numericId, cloneDeep(songData)); return songData.playMusicUrl;
url = res.data.data.url;
songDetail = res.data.data;
} else {
songDetail = data.data[0] as any;
} }
if (songData.source === 'bilibili' && songData.bilibiliData) {
console.log('加载B站音频URL');
if (!songData.playMusicUrl && songData.bilibiliData.bvid && songData.bilibiliData.cid) {
try {
songData.playMusicUrl = await getBilibiliAudioUrl(
songData.bilibiliData.bvid,
songData.bilibiliData.cid
);
return songData.playMusicUrl;
} catch (error) {
console.error('重启后获取B站音频URL失败:', error);
return '';
}
}
return songData.playMusicUrl || '';
}
const numericId = typeof id === 'string' ? parseInt(id, 10) : id;
// 检查是否有自定义音源设置
const songId = String(id);
const savedSource = localStorage.getItem(`song_source_${songId}`);
// 如果有自定义音源设置,直接使用getParsingMusicUrl获取URL
if (savedSource && songData.source !== 'bilibili') {
try {
console.log(`使用自定义音源解析歌曲 ID: ${songId}`);
const res = await getParsingMusicUrl(numericId, cloneDeep(songData));
console.log('res',res)
if (res && res.data && res.data.data && res.data.data.url) {
return res.data.data.url;
}
// 如果自定义音源解析失败,继续使用正常的获取流程
console.warn('自定义音源解析失败,使用默认音源');
} catch (error) {
console.error('error',error)
console.error('自定义音源解析出错:', error);
}
}
// 正常获取URL流程
const { data } = await getMusicUrl(numericId, isDownloaded);
let url = '';
let songDetail = null;
try {
if (data.data[0].freeTrialInfo || !data.data[0].url) {
const res = await getParsingMusicUrl(numericId, cloneDeep(songData));
url = res.data.data.url;
songDetail = res.data.data;
} else {
songDetail = data.data[0] as any;
}
} catch (error) {
console.error('error', error);
url = data.data[0].url || '';
}
if (isDownloaded) {
return songDetail;
}
url = url || data.data[0].url;
return url;
} catch (error) { } catch (error) {
console.error('error', error); console.error('error',error)
url = data.data[0].url || ''; return null;
} }
if (isDownloaded) {
return songDetail;
}
url = url || data.data[0].url;
return url;
}; };
const parseTime = (timeString: string): number => { const parseTime = (timeString: string): number => {
+27 -104
View File
@@ -1,4 +1,4 @@
<template> <template>
<div v-if="isComponent ? favoriteSongs.length : true" class="favorite-page"> <div v-if="isComponent ? favoriteSongs.length : true" class="favorite-page">
<div class="favorite-header" :class="setAnimationClass('animate__fadeInLeft')"> <div class="favorite-header" :class="setAnimationClass('animate__fadeInLeft')">
<div class="favorite-header-left"> <div class="favorite-header-left">
@@ -104,18 +104,18 @@
<script setup lang="ts"> <script setup lang="ts">
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { useMessage } from 'naive-ui'; import { useMessage } from 'naive-ui';
import { computed, onMounted, ref, watch } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { getMusicDetail } from '@/api/music'; import { getMusicDetail } from '@/api/music';
import { getBilibiliProxyUrl, getBilibiliVideoDetail } from '@/api/bilibili'; import { getBilibiliProxyUrl, getBilibiliVideoDetail } from '@/api/bilibili';
import SongItem from '@/components/common/SongItem.vue'; import SongItem from '@/components/common/SongItem.vue';
import { getSongUrl } from '@/store/modules/player'; import { useDownload } from '@/hooks/useDownload';
import { usePlayerStore } from '@/store'; import { usePlayerStore } from '@/store';
import type { SongResult } from '@/type/music'; import type { SongResult } from '@/type/music';
import { isElectron, setAnimationClass, setAnimationDelay } from '@/utils'; import { isElectron, setAnimationClass, setAnimationDelay } from '@/utils';
const { t } = useI18n(); const { t } = useI18n();
const playerStore = usePlayerStore(); const playerStore = usePlayerStore();
@@ -127,9 +127,9 @@
const scrollbarRef = ref(); const scrollbarRef = ref();
// 多选相关 // 多选相关
const isSelecting = ref(false); const isSelecting = ref(false);
const selectedSongs = ref<number[]>([]); const selectedSongs = ref<number[]>([]);
const isDownloading = ref(false); const { isDownloading, batchDownloadMusic } = useDownload();
// 开始多选 // 开始多选
const startSelect = () => { const startSelect = () => {
@@ -153,89 +153,18 @@
}; };
// 批量下载 // 批量下载
const handleBatchDownload = async () => { const handleBatchDownload = async () => {
if (isDownloading.value) { // 获取选中歌曲的信息
message.warning(t('favorite.downloading')); const selectedSongsList = selectedSongs.value
return; .map((songId) => favoriteSongs.value.find((s) => s.id === songId))
} .filter((song) => song) as SongResult[];
if (selectedSongs.value.length === 0) { // 使用hook中的批量下载功能
message.warning(t('favorite.selectSongsFirst')); await batchDownloadMusic(selectedSongsList);
return;
} // 下载完成后取消选择
cancelSelect();
try { };
isDownloading.value = true;
message.success(t('favorite.downloading'));
// 移除旧的监听器
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 {
failCount++;
}
// 当所有下载完成时
if (successCount + failCount === selectedSongs.value.length) {
isDownloading.value = false;
message.success(t('favorite.downloadSuccess'));
cancelSelect();
}
});
// 获取选中歌曲的信息
const selectedSongsList = selectedSongs.value
.map((songId) => favoriteSongs.value.find((s) => s.id === songId))
.filter((song) => song) as SongResult[];
// 并行获取所有歌曲的下载链接
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++;
return;
}
const songData = cloneDeep(song);
const songInfo = {
...songData,
ar: songData.ar || songData.song?.artists,
downloadTime: Date.now()
};
console.log('songInfo', songInfo);
console.log('song', song);
window.electron.ipcRenderer.send('download-music', {
url,
filename: `${song.name} - ${(song.ar || song.song?.artists)?.map((a) => a.name).join(',')}`,
songInfo,
type
});
});
} catch (error) {
console.error('下载失败:', error);
isDownloading.value = false;
message.destroyAll();
message.error(t('favorite.downloadFailed'));
}
};
// 排序相关 // 排序相关
const isDescending = ref(true); // 默认倒序显示 const isDescending = ref(true); // 默认倒序显示
@@ -290,18 +219,12 @@
loading.value = true; loading.value = true;
try { try {
const currentIds = getCurrentPageIds(); const currentIds = getCurrentPageIds();
console.log('currentIds', currentIds);
// 分离网易云音乐ID和B站视频ID // 分离网易云音乐ID和B站视频ID
const musicIds = currentIds.filter((id) => typeof id === 'number') as number[]; const musicIds = currentIds.filter((id) => typeof id === 'number') as number[];
// B站ID可能是字符串格式(包含"--")或特定数字ID,如113911642789603 // B站ID可能是字符串格式(包含"--")或特定数字ID,如113911642789603
const bilibiliIds = currentIds.filter((id) => typeof id === 'string'); const bilibiliIds = currentIds.filter((id) => typeof id === 'string');
console.log('处理数据:', {
musicIds,
bilibiliIds
});
// 处理网易云音乐数据 // 处理网易云音乐数据
let neteaseSongs: SongResult[] = []; let neteaseSongs: SongResult[] = [];
if (musicIds.length > 0) { if (musicIds.length > 0) {