100100 </n-button >
101101 </n-flex >
102102 </n-flex >
103-
104- <!-- 音质选择弹窗 -->
105- <n-modal
106- v-model:show =" showQualityModal"
107- preset =" card"
108- title =" 批量下载"
109- :closable =" false"
110- :mask-closable =" false"
111- style =" width : 500px "
112- >
113- <n-alert type =" warning" title =" 请知悉" closable style =" margin-top : 20px " >
114- 本软件仅支持从官方途径合法合规的下载歌曲,并用于学习研究用途。本功能将严格按照相应账户的权限来提供基础的下载功能
115- </n-alert >
116- <n-collapse
117- :default-expanded-names =" ['level', 'path']"
118- arrow-placement =" right"
119- style =" margin-top : 20px "
120- >
121- <n-collapse-item title =" 音质选择" name =" level" >
122- <n-radio-group v-model:value =" selectedQuality" name =" quality" >
123- <n-flex >
124- <n-radio v-for =" (item, index) in qualityOptions" :key =" index" :value =" item.value" >
125- <n-flex >
126- <n-text class =" name" >{{ item.label }}</n-text >
127- </n-flex >
128- </n-radio >
129- </n-flex >
130- </n-radio-group >
131- <n-text depth =" 3" style =" font-size : 12px ; margin-top : 10px ; display : block " >
132- 注意:如果歌曲没有对应的音质,将自动下载最高可用音质
133- </n-text >
134- </n-collapse-item >
135- <n-collapse-item v-if =" isElectron" title =" 下载路径" name =" path" >
136- <n-input-group >
137- <n-input :value =" downloadPath || '未配置下载目录'" disabled >
138- <template #prefix >
139- <SvgIcon name="Folder" />
140- </template >
141- </n-input >
142- <n-button type =" primary" strong secondary @click =" changeDownloadPath" >
143- <template #icon >
144- <SvgIcon name="Folder" />
145- </template >
146- </n-button >
147- <n-button type =" primary" strong secondary @click =" openSetting('local')" >
148- <template #icon >
149- <SvgIcon name="Settings" />
150- </template >
151- 更多设置
152- </n-button >
153- </n-input-group >
154- </n-collapse-item >
155- </n-collapse >
156-
157- <template #action >
158- <n-flex justify =" end" >
159- <n-button @click =" showQualityModal = false" >取消</n-button >
160- <n-button type =" primary" @click =" startBatchDownload" >确认下载</n-button >
161- </n-flex >
162- </template >
163- </n-modal >
164103 </div >
165104</template >
166105
167106<script setup lang="ts">
168107import type { DataTableColumns , DataTableRowKey } from " naive-ui" ;
169- import type { SongType , SongLevelType } from " @/types/main" ;
170- import { isArray , isObject , pick } from " lodash-es" ;
171- import { openPlaylistAdd , openSetting } from " @/utils/modal" ;
108+ import type { SongType } from " @/types/main" ;
109+ import { isArray , isObject } from " lodash-es" ;
110+ import { openPlaylistAdd } from " @/utils/modal" ;
172111import { deleteSongs } from " @/utils/auth" ;
173- import {
174- NInput ,
175- NInputNumber ,
176- NRadioGroup ,
177- NRadio ,
178- NCollapse ,
179- NCollapseItem ,
180- NInputGroup ,
181- NButton ,
182- NAlert ,
183- NText ,
184- NFlex ,
185- } from " naive-ui" ;
186- import { useLocalStore , useSettingStore , useDataStore } from " @/stores" ;
187- import { isElectron } from " @/utils/env" ;
188- import { songLevelData , getSongLevelsData } from " @/utils/meta" ;
189- import { downloadSong } from " @/utils/download" ;
112+ import { NInput , NInputNumber , NButton , NText , NFlex } from " naive-ui" ;
113+ import { useLocalStore } from " @/stores" ;
114+ import { openDownloadSongs } from " @/utils/modal" ;
190115
191116const localStore = useLocalStore ();
192- const settingStore = useSettingStore ();
193- const dataStore = useDataStore ();
194117
195118interface DataType {
196119 key? : number ;
@@ -217,23 +140,6 @@ const checkedRowKeys = ref<DataTableRowKey[]>([]);
217140const startRange = ref <number | null >(null );
218141const endRange = ref <number | null >(null );
219142
220- // 下载相关
221- const showQualityModal = ref (false );
222- const selectedQuality = ref <SongLevelType >(" h" );
223- const downloadPath = ref <string >(settingStore .downloadPath );
224-
225- // 音质选项
226- const qualityOptions = computed (() => {
227- // 批量下载时,默认显示所有常用音质选项
228- // 这里模拟一个包含所有常用音质的 level 对象
229- const levels = pick (songLevelData , [" l" , " m" , " h" , " sq" , " hr" , " je" , " sk" , " db" , " jm" ]);
230- return getSongLevelsData (levels ).map ((item ) => ({
231- label: item .name ,
232- value: item .value ,
233- level: item .level ,
234- }));
235- });
236-
237143// 表头数据
238144const columnsData = computed <DataTableColumns <DataType >>(() => [
239145 {
@@ -395,139 +301,11 @@ const handleDeleteLocalSongs = () => {
395301
396302// 批量下载处理
397303const handleBatchDownloadClick = () => {
398- if (settingStore .downloadPath ) downloadPath .value = settingStore .downloadPath ;
399- showQualityModal .value = true ;
400- };
401-
402- // 更改下载路径
403- const changeDownloadPath = async () => {
404- const path = await window .electron .ipcRenderer .invoke (" choose-path" );
405- if (path ) downloadPath .value = path ;
406- };
407-
408- const startBatchDownload = () => {
409- showQualityModal .value = false ;
410- executeBatchDownload (checkSongData .value );
411- };
412-
413- const executeBatchDownload = async (songs : SongType []) => {
414- if (! songs .length ) return ;
415-
416- // 重置状态
417- dataStore .batchDownload .isDownloading = true ;
418- dataStore .batchDownload .total = songs .length ;
419- dataStore .batchDownload .processed = 0 ;
420- dataStore .batchDownload .success = 0 ;
421- dataStore .batchDownload .percent = 0 ;
422- dataStore .batchDownload .transferred = " 0MB" ;
423- dataStore .batchDownload .totalSize = " 0MB" ;
424-
425- let failCount = 0 ;
426- const failedSongs: SongType [] = [];
427-
428- // 监听下载进度
429- const onProgress = (
430- _event : any ,
431- progress : { percent: number ; transferredBytes: number ; totalBytes: number },
432- ) => {
433- const { percent, transferredBytes, totalBytes } = progress ;
434- dataStore .batchDownload .percent = Number ((percent * 100 ).toFixed (0 ));
435- dataStore .batchDownload .transferred = (transferredBytes / 1024 / 1024 ).toFixed (2 ) + " MB" ;
436- dataStore .batchDownload .totalSize = (totalBytes / 1024 / 1024 ).toFixed (2 ) + " MB" ;
437- };
438-
439- if (isElectron ) {
440- window .electron .ipcRenderer .on (" download-progress" , onProgress );
441- }
442-
443- try {
444- for (const song of songs ) {
445- dataStore .batchDownload .currentSong = song .name ;
446- try {
447- const result = await downloadSong ({
448- song ,
449- quality: selectedQuality .value ,
450- downloadPath: downloadPath .value ,
451- skipIfExist: true ,
452- });
453-
454- if (result .success ) {
455- dataStore .batchDownload .success ++ ;
456- if (result .skipped ) {
457- window .$notification .create ({
458- title: " 已跳过重复文件" ,
459- content: ` ${song .name } 已存在 ` ,
460- duration: 2000 ,
461- });
462- }
463- if (! isElectron ) {
464- // Browser download delay
465- await new Promise ((resolve ) => setTimeout (resolve , 500 ));
466- }
467- } else {
468- console .error (` Failed to download song ${song .name }: ${result .message } ` );
469- failCount ++ ;
470- failedSongs .push (song );
471- }
472- } catch (err ) {
473- console .error (` Error downloading song ${song .name }: ` , err );
474- failCount ++ ;
475- failedSongs .push (song );
476- } finally {
477- dataStore .batchDownload .processed ++ ;
478- // Reset progress for next song
479- dataStore .batchDownload .percent = 0 ;
480- dataStore .batchDownload .transferred = " 0MB" ;
481- dataStore .batchDownload .totalSize = " 0MB" ;
482- }
483- }
484-
485- if (failCount > 0 ) {
486- window .$dialog .warning ({
487- title: " 下载完成,但有部分失败" ,
488- content : () =>
489- h (" div" , [
490- h (
491- " div" ,
492- { style: " margin-bottom: 10px" },
493- ` ${dataStore .batchDownload .success } 首下载成功,${failCount } 首下载失败。 ` ,
494- ),
495- h (
496- " div" ,
497- {
498- style:
499- " max-height: 200px; overflow-y: auto; background: rgba(0,0,0,0.05); padding: 8px; border-radius: 4px;" ,
500- },
501- [
502- h (" div" , { style: " font-weight: bold; margin-bottom: 4px" }, " 失败列表:" ),
503- ... failedSongs .map ((s ) =>
504- h (
505- " div" ,
506- { style: " font-size: 12px" },
507- ` ${s .name } - ${isArray (s .artists ) ? s .artists [0 ]?.name : s .artists || " 未知歌手" } ` ,
508- ),
509- ),
510- ],
511- ),
512- ]),
513- positiveText: " 重试失败歌曲" ,
514- negativeText: " 取消" ,
515- onPositiveClick : () => {
516- executeBatchDownload (failedSongs );
517- },
518- });
519- } else {
520- window .$message .success (` 批量下载完成,共 ${dataStore .batchDownload .success } 首 ` );
521- }
522- } catch (error ) {
523- console .error (" Batch download error:" , error );
524- window .$message .error (" 批量下载过程中出现错误" );
525- } finally {
526- if (isElectron ) {
527- window .electron .ipcRenderer .removeListener (" download-progress" , onProgress );
528- }
529- dataStore .batchDownload .isDownloading = false ;
304+ if (checkSongData .value .length === 0 ) {
305+ window .$message .warning (" 请选择要下载的歌曲" );
306+ return ;
530307 }
308+ openDownloadSongs (checkSongData .value );
531309};
532310 </script >
533311
0 commit comments