mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-23 23:57:22 +08:00
✨ feat: 优化主入口代码 添加歌曲下载功能
This commit is contained in:
@@ -60,12 +60,10 @@ const copyQQ = () => {
|
||||
defineProps({
|
||||
alipayQR: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: alipay
|
||||
},
|
||||
wechatQR: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: wechat
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="song-item" :class="{ 'song-mini': mini, 'song-list': list }">
|
||||
<div class="song-item" :class="{ 'song-mini': mini, 'song-list': list }" @contextmenu.prevent="handleContextMenu">
|
||||
<n-image
|
||||
v-if="item.picUrl"
|
||||
ref="songImg"
|
||||
@@ -57,17 +57,30 @@
|
||||
<i v-else class="iconfont icon-playfill"></i>
|
||||
</div>
|
||||
</div>
|
||||
<n-dropdown
|
||||
v-if="isElectron"
|
||||
:show="showDropdown"
|
||||
:options="dropdownOptions"
|
||||
:x="dropdownX"
|
||||
:y="dropdownY"
|
||||
placement="bottom-start"
|
||||
@clickoutside="showDropdown = false"
|
||||
@select="handleSelect"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, useTemplateRef } from 'vue';
|
||||
import { computed, h, ref, useTemplateRef } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import type { MenuOption } from 'naive-ui';
|
||||
|
||||
import { audioService } from '@/services/audioService';
|
||||
import type { SongResult } from '@/type/music';
|
||||
import { getImgUrl } from '@/utils';
|
||||
import { getImgUrl, isElectron } from '@/utils';
|
||||
import { getImageBackground } from '@/utils/linearColor';
|
||||
import { getSongUrl } from '@/hooks/MusicListHook';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -84,22 +97,96 @@ const props = withDefaults(
|
||||
);
|
||||
|
||||
const store = useStore();
|
||||
const message = useMessage();
|
||||
|
||||
const play = computed(() => store.state.play as boolean);
|
||||
|
||||
const playMusic = computed(() => store.state.playMusic);
|
||||
|
||||
const playLoading = computed(
|
||||
() => playMusic.value.id === props.item.id && playMusic.value.playLoading
|
||||
);
|
||||
|
||||
// 判断是否为正在播放的音乐
|
||||
const isPlaying = computed(() => {
|
||||
return playMusic.value.id === props.item.id;
|
||||
});
|
||||
|
||||
const emits = defineEmits(['play']);
|
||||
const showDropdown = ref(false);
|
||||
const dropdownX = ref(0);
|
||||
const dropdownY = ref(0);
|
||||
|
||||
const isDownloading = ref(false);
|
||||
|
||||
const dropdownOptions = computed<MenuOption[]>(() => [
|
||||
{
|
||||
label: isDownloading.value ? '下载中...' : '下载 ' + props.item.name,
|
||||
key: 'download',
|
||||
icon: () => h('i', { class: 'iconfont ri-download-line' }),
|
||||
disabled: isDownloading.value
|
||||
}
|
||||
]);
|
||||
|
||||
const handleContextMenu = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
showDropdown.value = true;
|
||||
dropdownX.value = e.clientX;
|
||||
dropdownY.value = e.clientY;
|
||||
};
|
||||
|
||||
const handleSelect = (key: string | number) => {
|
||||
showDropdown.value = false;
|
||||
if (key === 'download') {
|
||||
downloadMusic();
|
||||
}
|
||||
};
|
||||
|
||||
// 下载音乐
|
||||
const downloadMusic = async () => {
|
||||
if (isDownloading.value) {
|
||||
message.warning('正在下载中,请稍候...');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
isDownloading.value = true;
|
||||
const loadingMessage = message.loading('正在下载中...', { duration: 0 });
|
||||
|
||||
const url = await getSongUrl(props.item.id);
|
||||
if (!url) {
|
||||
loadingMessage.destroy();
|
||||
message.error('获取音乐下载地址失败');
|
||||
isDownloading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 先移除可能存在的旧监听器
|
||||
window.electron.ipcRenderer.removeAllListeners('music-download-complete');
|
||||
|
||||
// 发送下载请求
|
||||
window.electron.ipcRenderer.send('download-music', {
|
||||
url,
|
||||
filename: `${props.item.name} - ${(props.item.ar || props.item.song?.artists)?.map(a => a.name).join(',')}`
|
||||
});
|
||||
|
||||
// 添加新的一次性监听器
|
||||
window.electron.ipcRenderer.once('music-download-complete', (_, result) => {
|
||||
isDownloading.value = false;
|
||||
loadingMessage.destroy();
|
||||
|
||||
if (result.success) {
|
||||
message.success('下载成功');
|
||||
} else if (result.canceled) {
|
||||
// 用户取消了保存
|
||||
message.info('已取消下载');
|
||||
} else {
|
||||
message.error(`下载失败: ${result.error}`);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
isDownloading.value = false;
|
||||
message.destroyAll();
|
||||
message.error('下载失败');
|
||||
}
|
||||
};
|
||||
|
||||
const emits = defineEmits(['play']);
|
||||
const songImageRef = useTemplateRef('songImg');
|
||||
|
||||
const imageLoad = async () => {
|
||||
@@ -207,6 +294,14 @@ const toggleFavorite = async (e: Event) => {
|
||||
@apply bg-green-500 border-green-500 text-white;
|
||||
}
|
||||
}
|
||||
|
||||
&-download {
|
||||
@apply mr-2 cursor-pointer;
|
||||
|
||||
.iconfont {
|
||||
@apply text-xl transition text-gray-500 dark:text-gray-400 hover:text-green-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { getImageLinearBackground } from '@/utils/linearColor';
|
||||
const musicHistory = useMusicHistory();
|
||||
|
||||
// 获取歌曲url
|
||||
const getSongUrl = async (id: number) => {
|
||||
export const getSongUrl = async (id: number) => {
|
||||
const { data } = await getMusicUrl(id);
|
||||
let url = '';
|
||||
try {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<!-- 底部音乐播放 -->
|
||||
<play-bar v-if="isPlay" />
|
||||
</div>
|
||||
<install-app-modal></install-app-modal>
|
||||
<install-app-modal v-if="!isElectron"></install-app-modal>
|
||||
<update-modal />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { MessageApi } from 'naive-ui';
|
||||
|
||||
/**
|
||||
* 选择目录
|
||||
* @param message MessageApi 实例
|
||||
* @returns Promise<string | undefined> 返回选择的目录路径,如果取消则返回 undefined
|
||||
*/
|
||||
export const selectDirectory = async (message: MessageApi): Promise<string | undefined> => {
|
||||
try {
|
||||
const result = await window.electron.ipcRenderer.invoke('select-directory');
|
||||
if (result.filePaths?.[0]) {
|
||||
return result.filePaths[0];
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('选择目录失败');
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开目录
|
||||
* @param path 要打开的目录路径
|
||||
* @param message MessageApi 实例
|
||||
* @param showTip 是否显示提示信息
|
||||
*/
|
||||
export const openDirectory = (path: string | undefined, message: MessageApi, showTip = true) => {
|
||||
if (path) {
|
||||
window.electron.ipcRenderer.send('open-directory', path);
|
||||
} else if (showTip) {
|
||||
message.info('目录不存在');
|
||||
}
|
||||
};
|
||||
@@ -55,6 +55,18 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="set-item" v-if="isElectron">
|
||||
<div>
|
||||
<div class="set-item-title">下载目录</div>
|
||||
<div class="set-item-content">
|
||||
{{ setData.downloadPath || '默认下载目录' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<n-button size="small" @click="openDownloadPath">打开目录</n-button>
|
||||
<n-button size="small" @click="selectDownloadPath">修改目录</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="set-item">
|
||||
<div>
|
||||
<div class="set-item-title">版本</div>
|
||||
@@ -97,7 +109,7 @@
|
||||
</Coffee>
|
||||
</div>
|
||||
<div>
|
||||
<n-button type="primary" @click="openAuthor">前往github</n-button>
|
||||
<n-button size="small" type="primary" @click="openAuthor"><i class="ri-github-line"></i>前往github</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="set-item" v-if="isElectron">
|
||||
@@ -118,6 +130,7 @@ import { useStore } from 'vuex';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { isElectron } from '@/utils';
|
||||
import { checkUpdate, UpdateResult } from '@/utils/update';
|
||||
import { selectDirectory, openDirectory } from '@/utils/fileOperation';
|
||||
import config from '../../../../package.json';
|
||||
import PlayBottom from '@/components/common/PlayBottom.vue';
|
||||
import Coffee from '@/components/Coffee.vue';
|
||||
@@ -176,6 +189,20 @@ const openReleasePage = () => {
|
||||
window.open(updateInfo.value.releaseInfo?.html_url || 'https://github.com/algerkong/AlgerMusicPlayer/releases/latest', '_blank');
|
||||
};
|
||||
|
||||
const selectDownloadPath = async () => {
|
||||
const path = await selectDirectory(message);
|
||||
if (path) {
|
||||
store.commit('setSetData', {
|
||||
...setData.value,
|
||||
downloadPath: path
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const openDownloadPath = () => {
|
||||
openDirectory(setData.value.downloadPath, message);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
checkForUpdates();
|
||||
});
|
||||
@@ -183,7 +210,7 @@ onMounted(() => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.set-page {
|
||||
@apply p-4 bg-light dark:bg-dark;
|
||||
@apply p-4 bg-light dark:bg-dark pb-20;
|
||||
}
|
||||
|
||||
.set-item {
|
||||
|
||||
Reference in New Issue
Block a user