mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-24 16:27:23 +08:00
✨ feat: 应用更新在内部更新 自动打开安装包
This commit is contained in:
@@ -7,6 +7,7 @@ import { initializeConfig } from './modules/config';
|
|||||||
import { initializeFileManager } from './modules/fileManager';
|
import { initializeFileManager } from './modules/fileManager';
|
||||||
import { initializeShortcuts, registerShortcuts } from './modules/shortcuts';
|
import { initializeShortcuts, registerShortcuts } from './modules/shortcuts';
|
||||||
import { initializeTray } from './modules/tray';
|
import { initializeTray } from './modules/tray';
|
||||||
|
import { setupUpdateHandlers } from './modules/update';
|
||||||
import { createMainWindow, initializeWindowManager } from './modules/window';
|
import { createMainWindow, initializeWindowManager } from './modules/window';
|
||||||
import { startMusicApi } from './server';
|
import { startMusicApi } from './server';
|
||||||
|
|
||||||
@@ -45,6 +46,9 @@ function initialize() {
|
|||||||
|
|
||||||
// 初始化快捷键
|
// 初始化快捷键
|
||||||
initializeShortcuts(mainWindow);
|
initializeShortcuts(mainWindow);
|
||||||
|
|
||||||
|
// 初始化更新处理程序
|
||||||
|
setupUpdateHandlers(mainWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用程序准备就绪时的处理
|
// 应用程序准备就绪时的处理
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import { exec } from 'child_process';
|
||||||
|
import { app, BrowserWindow, ipcMain } from 'electron';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export function setupUpdateHandlers(_mainWindow: BrowserWindow) {
|
||||||
|
ipcMain.on('start-download', async (event, url: string) => {
|
||||||
|
try {
|
||||||
|
const response = await axios({
|
||||||
|
url,
|
||||||
|
method: 'GET',
|
||||||
|
responseType: 'stream',
|
||||||
|
onDownloadProgress: (progressEvent: { loaded: number; total?: number }) => {
|
||||||
|
if (!progressEvent.total) return;
|
||||||
|
const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100);
|
||||||
|
const downloaded = (progressEvent.loaded / 1024 / 1024).toFixed(2);
|
||||||
|
const total = (progressEvent.total / 1024 / 1024).toFixed(2);
|
||||||
|
event.sender.send('download-progress', percent, `已下载 ${downloaded}MB / ${total}MB`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileName = url.split('/').pop() || 'update.exe';
|
||||||
|
const downloadPath = path.join(app.getPath('downloads'), fileName);
|
||||||
|
|
||||||
|
// 创建写入流
|
||||||
|
const writer = fs.createWriteStream(downloadPath);
|
||||||
|
|
||||||
|
// 将响应流写入文件
|
||||||
|
response.data.pipe(writer);
|
||||||
|
|
||||||
|
// 处理写入完成
|
||||||
|
writer.on('finish', () => {
|
||||||
|
event.sender.send('download-complete', true, downloadPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理写入错误
|
||||||
|
writer.on('error', (error) => {
|
||||||
|
console.error('Write file error:', error);
|
||||||
|
event.sender.send('download-complete', false, '');
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Download failed:', error);
|
||||||
|
event.sender.send('download-complete', false, '');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('install-update', (_event, filePath: string) => {
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
console.error('Installation file not found:', filePath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { platform } = process;
|
||||||
|
|
||||||
|
// 关闭当前应用
|
||||||
|
app.quit();
|
||||||
|
|
||||||
|
// 根据不同平台执行安装
|
||||||
|
if (platform === 'win32') {
|
||||||
|
exec(`"${filePath}"`, (error) => {
|
||||||
|
if (error) {
|
||||||
|
console.error('Error starting installer:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (platform === 'darwin') {
|
||||||
|
// 挂载 DMG 文件
|
||||||
|
exec(`open "${filePath}"`, (error) => {
|
||||||
|
if (error) {
|
||||||
|
console.error('Error opening DMG:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (platform === 'linux') {
|
||||||
|
const ext = path.extname(filePath);
|
||||||
|
if (ext === '.AppImage') {
|
||||||
|
exec(`chmod +x "${filePath}" && "${filePath}"`, (error) => {
|
||||||
|
if (error) {
|
||||||
|
console.error('Error running AppImage:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (ext === '.deb') {
|
||||||
|
exec(`pkexec dpkg -i "${filePath}"`, (error) => {
|
||||||
|
if (error) {
|
||||||
|
console.error('Error installing deb package:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
Vendored
+4
@@ -13,6 +13,10 @@ declare global {
|
|||||||
miniTray: () => void;
|
miniTray: () => void;
|
||||||
restart: () => void;
|
restart: () => void;
|
||||||
unblockMusic: (id: number, data: any) => Promise<any>;
|
unblockMusic: (id: number, data: any) => Promise<any>;
|
||||||
|
startDownload: (url: string) => void;
|
||||||
|
onDownloadProgress: (callback: (progress: number, status: string) => void) => void;
|
||||||
|
onDownloadComplete: (callback: (success: boolean, filePath: string) => void) => void;
|
||||||
|
removeDownloadListeners: () => void;
|
||||||
invoke: (channel: string, ...args: any[]) => Promise<any>;
|
invoke: (channel: string, ...args: any[]) => Promise<any>;
|
||||||
};
|
};
|
||||||
$message: any;
|
$message: any;
|
||||||
|
|||||||
@@ -12,6 +12,18 @@ const api = {
|
|||||||
openLyric: () => ipcRenderer.send('open-lyric'),
|
openLyric: () => ipcRenderer.send('open-lyric'),
|
||||||
sendLyric: (data) => ipcRenderer.send('send-lyric', data),
|
sendLyric: (data) => ipcRenderer.send('send-lyric', data),
|
||||||
unblockMusic: (id) => ipcRenderer.invoke('unblock-music', id),
|
unblockMusic: (id) => ipcRenderer.invoke('unblock-music', id),
|
||||||
|
// 更新相关
|
||||||
|
startDownload: (url: string) => ipcRenderer.send('start-download', url),
|
||||||
|
onDownloadProgress: (callback: (progress: number, status: string) => void) => {
|
||||||
|
ipcRenderer.on('download-progress', (_event, progress, status) => callback(progress, status));
|
||||||
|
},
|
||||||
|
onDownloadComplete: (callback: (success: boolean, filePath: string) => void) => {
|
||||||
|
ipcRenderer.on('download-complete', (_event, success, filePath) => callback(success, filePath));
|
||||||
|
},
|
||||||
|
removeDownloadListeners: () => {
|
||||||
|
ipcRenderer.removeAllListeners('download-progress');
|
||||||
|
ipcRenderer.removeAllListeners('download-complete');
|
||||||
|
},
|
||||||
// 歌词缓存相关
|
// 歌词缓存相关
|
||||||
invoke: (channel: string, ...args: any[]) => {
|
invoke: (channel: string, ...args: any[]) => {
|
||||||
const validChannels = ['get-cached-lyric', 'cache-lyric', 'clear-lyric-cache'];
|
const validChannels = ['get-cached-lyric', 'cache-lyric', 'clear-lyric-cache'];
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
v-model:show="showModal"
|
v-model:show="showModal"
|
||||||
preset="dialog"
|
preset="dialog"
|
||||||
:show-icon="false"
|
:show-icon="false"
|
||||||
:mask-closable="true"
|
:mask-closable="!downloading"
|
||||||
|
:closable="!downloading"
|
||||||
class="update-app-modal"
|
class="update-app-modal"
|
||||||
style="width: 800px; max-width: 90vw"
|
style="width: 800px; max-width: 90vw"
|
||||||
>
|
>
|
||||||
@@ -15,7 +16,6 @@
|
|||||||
<div class="app-info">
|
<div class="app-info">
|
||||||
<h2 class="app-name">发现新版本 {{ updateInfo.latestVersion }}</h2>
|
<h2 class="app-name">发现新版本 {{ updateInfo.latestVersion }}</h2>
|
||||||
<p class="app-desc mb-2">当前版本 {{ updateInfo.currentVersion }}</p>
|
<p class="app-desc mb-2">当前版本 {{ updateInfo.currentVersion }}</p>
|
||||||
<n-checkbox v-model:checked="noPrompt">不再提示</n-checkbox>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="update-info">
|
<div class="update-info">
|
||||||
@@ -23,11 +23,35 @@
|
|||||||
<div class="update-body" v-html="parsedReleaseNotes"></div>
|
<div class="update-body" v-html="parsedReleaseNotes"></div>
|
||||||
</n-scrollbar>
|
</n-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-actions">
|
<div v-if="downloading" class="download-status mt-6">
|
||||||
<n-button class="cancel-btn" @click="closeModal">暂不更新</n-button>
|
<div class="flex items-center justify-between mb-2">
|
||||||
<n-button type="primary" class="update-btn" @click="handleUpdate">立即更新</n-button>
|
<span class="text-sm text-gray-500">{{ downloadStatus }}</span>
|
||||||
|
<span class="text-sm font-medium">{{ downloadProgress }}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar-wrapper">
|
||||||
|
<div class="progress-bar" :style="{ width: `${downloadProgress}%` }"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-desc mt-4 text-center">
|
<div class="modal-actions" :class="{ 'mt-6': !downloading }">
|
||||||
|
<n-button
|
||||||
|
class="cancel-btn"
|
||||||
|
:disabled="downloading"
|
||||||
|
:loading="downloading"
|
||||||
|
@click="closeModal"
|
||||||
|
>
|
||||||
|
{{ '暂不更新' }}
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
type="primary"
|
||||||
|
class="update-btn"
|
||||||
|
:loading="downloading"
|
||||||
|
:disabled="downloading"
|
||||||
|
@click="handleUpdate"
|
||||||
|
>
|
||||||
|
{{ downloadBtnText }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
<div v-if="!downloading" class="modal-desc mt-4 text-center">
|
||||||
<p class="text-xs text-gray-400">
|
<p class="text-xs text-gray-400">
|
||||||
下载遇到问题?去
|
下载遇到问题?去
|
||||||
<a
|
<a
|
||||||
@@ -45,7 +69,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
import { computed, onMounted, ref, watch } from 'vue';
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||||
import { useStore } from 'vuex';
|
import { useStore } from 'vuex';
|
||||||
|
|
||||||
import { checkUpdate, UpdateResult } from '@/utils/update';
|
import { checkUpdate, UpdateResult } from '@/utils/update';
|
||||||
@@ -59,7 +83,6 @@ marked.setOptions({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const showModal = ref(false);
|
const showModal = ref(false);
|
||||||
const noPrompt = ref(false);
|
|
||||||
const updateInfo = ref<UpdateResult>({
|
const updateInfo = ref<UpdateResult>({
|
||||||
hasUpdate: false,
|
hasUpdate: false,
|
||||||
latestVersion: '',
|
latestVersion: '',
|
||||||
@@ -102,9 +125,6 @@ const parsedReleaseNotes = computed(() => {
|
|||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
showModal.value = false;
|
showModal.value = false;
|
||||||
if (noPrompt.value) {
|
|
||||||
localStorage.setItem('updatePromptDismissed', 'true');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkForUpdates = async () => {
|
const checkForUpdates = async () => {
|
||||||
@@ -112,21 +132,54 @@ const checkForUpdates = async () => {
|
|||||||
const result = await checkUpdate(config.version);
|
const result = await checkUpdate(config.version);
|
||||||
if (result) {
|
if (result) {
|
||||||
updateInfo.value = result;
|
updateInfo.value = result;
|
||||||
if (localStorage.getItem('updatePromptDismissed') !== 'true') {
|
showModal.value = true;
|
||||||
showModal.value = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('检查更新失败:', error);
|
console.error('检查更新失败:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const downloading = ref(false);
|
||||||
|
const downloadProgress = ref(0);
|
||||||
|
const downloadStatus = ref('准备下载...');
|
||||||
|
const downloadBtnText = computed(() => {
|
||||||
|
if (downloading.value) return '下载中...';
|
||||||
|
return '立即更新';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理下载状态更新
|
||||||
|
const handleDownloadProgress = (_event: any, progress: number, status: string) => {
|
||||||
|
downloadProgress.value = progress;
|
||||||
|
downloadStatus.value = status;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理下载完成
|
||||||
|
const handleDownloadComplete = (_event: any, success: boolean, filePath: string) => {
|
||||||
|
downloading.value = false;
|
||||||
|
if (success) {
|
||||||
|
window.electron.ipcRenderer.send('install-update', filePath);
|
||||||
|
} else {
|
||||||
|
window.$message.error('下载失败,请重试或手动下载');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听下载事件
|
||||||
|
onMounted(() => {
|
||||||
|
checkForUpdates();
|
||||||
|
window.electron.ipcRenderer.on('download-progress', handleDownloadProgress);
|
||||||
|
window.electron.ipcRenderer.on('download-complete', handleDownloadComplete);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清理事件监听
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.electron.ipcRenderer.removeListener('download-progress', handleDownloadProgress);
|
||||||
|
window.electron.ipcRenderer.removeListener('download-complete', handleDownloadComplete);
|
||||||
|
});
|
||||||
|
|
||||||
const handleUpdate = async () => {
|
const handleUpdate = async () => {
|
||||||
const assets = updateInfo.value.releaseInfo?.assets || [];
|
const assets = updateInfo.value.releaseInfo?.assets || [];
|
||||||
const { platform } = window.electron.process;
|
const { platform } = window.electron.process;
|
||||||
const arch = window.electron.ipcRenderer.sendSync('get-arch');
|
const arch = window.electron.ipcRenderer.sendSync('get-arch');
|
||||||
console.log('arch', arch);
|
|
||||||
console.log('platform', platform);
|
|
||||||
const version = updateInfo.value.latestVersion;
|
const version = updateInfo.value.latestVersion;
|
||||||
const downUrls = {
|
const downUrls = {
|
||||||
win32: {
|
win32: {
|
||||||
@@ -170,16 +223,20 @@ const handleUpdate = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (downloadUrl) {
|
if (downloadUrl) {
|
||||||
window.open(`https://www.ghproxy.cn/${downloadUrl}`, '_blank');
|
try {
|
||||||
|
downloading.value = true;
|
||||||
|
downloadStatus.value = '准备下载...';
|
||||||
|
window.electron.ipcRenderer.send('start-download', downloadUrl);
|
||||||
|
} catch (error) {
|
||||||
|
downloading.value = false;
|
||||||
|
window.$message.error('启动下载失败,请重试或手动下载');
|
||||||
|
console.error('下载失败:', error);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 如果没有找到对应的安装包,跳转到 release 页面
|
window.$message.error('未找到适合当前系统的安装包,请手动下载');
|
||||||
window.open('https://github.com/algerkong/AlgerMusicPlayer/releases/latest', '_blank');
|
window.open('https://github.com/algerkong/AlgerMusicPlayer/releases/latest', '_blank');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
checkForUpdates();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -266,8 +323,18 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.download-status {
|
||||||
|
@apply p-2;
|
||||||
|
.progress-bar-wrapper {
|
||||||
|
@apply w-full h-2 bg-gray-100 dark:bg-gray-700 rounded-full overflow-hidden;
|
||||||
|
.progress-bar {
|
||||||
|
@apply h-full bg-green-500 rounded-full transition-all duration-300 ease-out;
|
||||||
|
box-shadow: 0 0 10px rgba(34, 197, 94, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.modal-actions {
|
.modal-actions {
|
||||||
@apply flex gap-4 mt-6;
|
@apply flex gap-4;
|
||||||
.n-button {
|
.n-button {
|
||||||
@apply flex-1 text-base py-2;
|
@apply flex-1 text-base py-2;
|
||||||
}
|
}
|
||||||
@@ -276,12 +343,18 @@ onMounted(() => {
|
|||||||
&:hover {
|
&:hover {
|
||||||
@apply bg-gray-700;
|
@apply bg-gray-700;
|
||||||
}
|
}
|
||||||
|
&:disabled {
|
||||||
|
@apply opacity-50 cursor-not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.update-btn {
|
.update-btn {
|
||||||
@apply bg-green-600 border-none;
|
@apply bg-green-600 border-none;
|
||||||
&:hover {
|
&:hover {
|
||||||
@apply bg-green-500;
|
@apply bg-green-500;
|
||||||
}
|
}
|
||||||
|
&:disabled {
|
||||||
|
@apply opacity-50 cursor-not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user