refactor: 将下载逻辑提取到useDownload hook中

This commit is contained in:
alger
2025-05-22 20:58:47 +08:00
parent 258828ffbd
commit ca51020602
4 changed files with 269 additions and 235 deletions

View File

@@ -144,6 +144,7 @@ import { usePlayerStore } from '@/store';
import type { SongResult } from '@/type/music';
import { getImgUrl, isElectron } from '@/utils';
import { getImageBackground } from '@/utils/linearColor';
import { useDownload } from '@/hooks/useDownload';
const { t } = useI18n();
@@ -191,8 +192,6 @@ const dropdownX = ref(0);
const dropdownY = ref(0);
const isHovering = ref(false);
const isDownloading = ref(false);
const openPlaylistDrawer = inject<(songId: number | string) => void>('openPlaylistDrawer');
const { navigateToArtist } = useArtist();
@@ -344,7 +343,7 @@ const handleMenuClick = (e: MouseEvent) => {
const handleSelect = (key: string | number) => {
showDropdown.value = false;
if (key === 'download') {
downloadMusic();
downloadMusic(props.item);
} else if (key === 'playNext') {
handlePlayNext();
} else if (key === 'addToPlaylist') {
@@ -359,74 +358,7 @@ const handleSelect = (key: string | number) => {
};
// 下载音乐
const downloadMusic = async () => {
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 { isDownloading, downloadMusic } = useDownload();
const emits = defineEmits(['play', 'select', 'remove-song']);
const songImageRef = useTemplateRef('songImg');

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
};
};

View File

@@ -81,70 +81,76 @@ export const getSongUrl = async (
songData: SongResult,
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 {
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;
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));
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) {
console.error('error', error);
url = data.data[0].url || '';
console.error('error',error)
return null;
}
if (isDownloaded) {
return songDetail;
}
url = url || data.data[0].url;
return url;
};
const parseTime = (timeString: string): number => {

View File

@@ -1,4 +1,4 @@
<template>
<template>
<div v-if="isComponent ? favoriteSongs.length : true" class="favorite-page">
<div class="favorite-header" :class="setAnimationClass('animate__fadeInLeft')">
<div class="favorite-header-left">
@@ -104,18 +104,18 @@
<script setup lang="ts">
import { cloneDeep } from 'lodash';
import { useMessage } from 'naive-ui';
import { computed, onMounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { useMessage } from 'naive-ui';
import { computed, onMounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { getMusicDetail } from '@/api/music';
import { getBilibiliProxyUrl, getBilibiliVideoDetail } from '@/api/bilibili';
import SongItem from '@/components/common/SongItem.vue';
import { getSongUrl } from '@/store/modules/player';
import { usePlayerStore } from '@/store';
import type { SongResult } from '@/type/music';
import { isElectron, setAnimationClass, setAnimationDelay } from '@/utils';
import { getMusicDetail } from '@/api/music';
import { getBilibiliProxyUrl, getBilibiliVideoDetail } from '@/api/bilibili';
import SongItem from '@/components/common/SongItem.vue';
import { useDownload } from '@/hooks/useDownload';
import { usePlayerStore } from '@/store';
import type { SongResult } from '@/type/music';
import { isElectron, setAnimationClass, setAnimationDelay } from '@/utils';
const { t } = useI18n();
const playerStore = usePlayerStore();
@@ -127,9 +127,9 @@
const scrollbarRef = ref();
// 多选相关
const isSelecting = ref(false);
const selectedSongs = ref<number[]>([]);
const isDownloading = ref(false);
const isSelecting = ref(false);
const selectedSongs = ref<number[]>([]);
const { isDownloading, batchDownloadMusic } = useDownload();
// 开始多选
const startSelect = () => {
@@ -153,89 +153,18 @@
};
// 批量下载
const handleBatchDownload = async () => {
if (isDownloading.value) {
message.warning(t('favorite.downloading'));
return;
}
if (selectedSongs.value.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 === 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 handleBatchDownload = async () => {
// 获取选中歌曲的信息
const selectedSongsList = selectedSongs.value
.map((songId) => favoriteSongs.value.find((s) => s.id === songId))
.filter((song) => song) as SongResult[];
// 使用hook中的批量下载功能
await batchDownloadMusic(selectedSongsList);
// 下载完成后取消选择
cancelSelect();
};
// 排序相关
const isDescending = ref(true); // 默认倒序显示
@@ -290,18 +219,12 @@
loading.value = true;
try {
const currentIds = getCurrentPageIds();
console.log('currentIds', currentIds);
// 分离网易云音乐ID和B站视频ID
const musicIds = currentIds.filter((id) => typeof id === 'number') as number[];
// B站ID可能是字符串格式包含"--"或特定数字ID如113911642789603
const bilibiliIds = currentIds.filter((id) => typeof id === 'string');
console.log('处理数据:', {
musicIds,
bilibiliIds
});
// 处理网易云音乐数据
let neteaseSongs: SongResult[] = [];
if (musicIds.length > 0) {