diff --git a/package.json b/package.json index 38d5e65..e2686c5 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "@unblockneteasemusic/server": "^0.27.8-patch.1", "electron-store": "^8.1.0", "electron-updater": "^6.1.7", - "netease-cloud-music-api-alger": "^4.25.0" }, "devDependencies": { diff --git a/src/main/index.ts b/src/main/index.ts index e0e4497..870597c 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,8 +1,14 @@ -import { electronApp, is, optimizer } from '@electron-toolkit/utils'; -import { app, BrowserWindow, globalShortcut, ipcMain, Menu, nativeImage, shell, Tray } from 'electron'; -import Store from 'electron-store'; +import { electronApp, optimizer } from '@electron-toolkit/utils'; +import { app, globalShortcut, ipcMain, nativeImage } from 'electron'; import { join } from 'path'; -import set from './set.json'; + +import { loadLyricWindow } from './lyric'; +import { startMusicApi } from './server'; +import { initializeFileManager } from './modules/fileManager'; +import { initializeTray } from './modules/tray'; +import { createMainWindow, initializeWindowManager } from './modules/window'; +import { initializeConfig } from './modules/config'; + // 导入所有图标 const iconPath = join(__dirname, '../../resources'); const icon = nativeImage.createFromPath( @@ -13,109 +19,50 @@ const icon = nativeImage.createFromPath( : join(iconPath, 'icon.png') ); -import { loadLyricWindow } from './lyric'; -import { startMusicApi } from './server'; +let mainWindow: Electron.BrowserWindow; -let mainWindow: BrowserWindow; -function createWindow(): void { - startMusicApi(); - // Create the browser window. - mainWindow = new BrowserWindow({ - width: 1200, - height: 780, - show: false, - frame: false, - autoHideMenuBar: true, - icon, - webPreferences: { - preload: join(__dirname, '../preload/index.js'), - sandbox: false, - contextIsolation: true - } - }); - - mainWindow.setMinimumSize(1200, 780); - - mainWindow.on('ready-to-show', () => { - mainWindow.show(); - }); - - mainWindow.webContents.setWindowOpenHandler((details) => { - shell.openExternal(details.url); - return { action: 'deny' }; - }); - - // HMR for renderer base on electron-vite cli. - // Load the remote URL for development or the local html file for production. - if (is.dev && process.env.ELECTRON_RENDERER_URL) { - mainWindow.webContents.openDevTools({ mode: 'detach' }); - mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL); - } else { - mainWindow.loadFile(join(__dirname, '../renderer/index.html')); - } +// 初始化应用 +function initialize() { + // 初始化各个模块 + initializeConfig(); + initializeFileManager(); - // 创建托盘图标 - const trayIcon = nativeImage.createFromPath(join(iconPath, 'icon_16x16.png')).resize({ width: 16, height: 16 }); - const tray = new Tray(trayIcon); - - // 创建一个上下文菜单 - const contextMenu = Menu.buildFromTemplate([ - { - label: '显示', - click: () => { - mainWindow.show(); - }, - }, - { - label: '退出', - click: () => { - mainWindow.destroy(); - app.quit(); - }, - }, - ]); - - // 设置系统托盘图标的上下文菜单 - tray.setContextMenu(contextMenu); - - // 当系统托盘图标被点击时,切换窗口的显示/隐藏 - tray.on('click', () => { - if (mainWindow.isVisible()) { - mainWindow.hide(); - } else { - mainWindow.show(); - } - }); - + // 创建主窗口 + mainWindow = createMainWindow(icon); + + // 初始化窗口管理 + initializeWindowManager(mainWindow); + + // 初始化托盘 + initializeTray(iconPath, mainWindow); + + // 启动音乐API + startMusicApi(); + + // 加载歌词窗口 loadLyricWindow(ipcMain, mainWindow); } -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. +// 应用程序准备就绪时的处理 app.whenReady().then(() => { - // Set app user model id for windows + // 设置应用ID electronApp.setAppUserModelId('com.alger.music'); - // Default open or close DevTools by F12 in development - // and ignore CommandOrControl + R in production. - // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils + // 监听窗口创建事件 app.on('browser-window-created', (_, window) => { optimizer.watchWindowShortcuts(window); }); - // IPC test - ipcMain.on('ping', () => console.log('pong')); - - createWindow(); + // 初始化应用 + initialize(); + // macOS 激活应用时的处理 app.on('activate', function () { - // On macOS it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (BrowserWindow.getAllWindows().length === 0) createWindow(); + if (mainWindow === null) initialize(); }); }); +// 应用程序准备就绪后的快捷键设置 app.on('ready', () => { globalShortcut.register('CommandOrControl+Alt+Shift+M', () => { if (mainWindow.isVisible()) { @@ -126,84 +73,20 @@ app.on('ready', () => { }); }); -// Quit when all windows are closed, except on macOS. There, it's common -// for applications and their menu bar to stay active until the user quits -// explicitly with Cmd + Q. +// 所有窗口关闭时的处理 app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); -ipcMain.on('minimize-window', (event) => { - const win = BrowserWindow.fromWebContents(event.sender); - if (win) { - win.minimize(); - } -}); - -ipcMain.on('maximize-window', (event) => { - const win = BrowserWindow.fromWebContents(event.sender); - if (win) { - if (win.isMaximized()) { - win.unmaximize(); - } else { - win.maximize(); - } - } -}); - -ipcMain.on('close-window', (event) => { - const win = BrowserWindow.fromWebContents(event.sender); - if (win) { - win.destroy(); - app.quit(); - } -}); - -ipcMain.on('drag-start', (event) => { - const win = BrowserWindow.fromWebContents(event.sender); - if (win) { - win.webContents.beginFrameSubscription((frameBuffer) => { - event.reply('frame-buffer', frameBuffer); - }); - } -}); - -ipcMain.on('mini-tray', (event) => { - const win = BrowserWindow.fromWebContents(event.sender); - if (win) { - win.hide(); - } -}); - -// 重启 +// 重启应用 ipcMain.on('restart', () => { app.relaunch(); app.exit(0); }); -const store = new Store({ - name: 'config', // 配置文件名 - defaults: { - set: set - } -}); - -// 定义ipcRenderer监听事件 -ipcMain.on('set-store-value', (_, key, value) => { - store.set(key, value); -}); - -ipcMain.on('get-store-value', (_, key) => { - const value = store.get(key); - _.returnValue = value || ''; -}); - -// 添加 IPC 处理程序 +// 获取系统架构信息 ipcMain.on('get-arch', (event) => { event.returnValue = process.arch; }); - -// In this file you can include the rest of your app"s specific main process -// code. You can also put them in separate files and require them here. diff --git a/src/main/modules/config.ts b/src/main/modules/config.ts new file mode 100644 index 0000000..d5ed62f --- /dev/null +++ b/src/main/modules/config.ts @@ -0,0 +1,42 @@ +import { app, ipcMain } from 'electron'; +import Store from 'electron-store'; +import set from '../set.json'; + +interface StoreType { + set: { + isProxy: boolean; + noAnimate: boolean; + animationSpeed: number; + author: string; + authorUrl: string; + musicApiPort: number; + }; +} + +let store: Store; + +/** + * 初始化配置管理 + */ +export function initializeConfig() { + store = new Store({ + name: 'config', + defaults: { + set: set + } + }); + + store.get('set.downloadPath') || store.set('set.downloadPath', app.getPath('downloads')); + + // 定义ipcRenderer监听事件 + ipcMain.on('set-store-value', (_, key, value) => { + store.set(key, value); + }); + + ipcMain.on('get-store-value', (_, key) => { + const value = store.get(key); + _.returnValue = value || ''; + }); + + return store; +} \ No newline at end of file diff --git a/src/main/modules/fileManager.ts b/src/main/modules/fileManager.ts new file mode 100644 index 0000000..4b7a93a --- /dev/null +++ b/src/main/modules/fileManager.ts @@ -0,0 +1,69 @@ +import { app, dialog, shell, ipcMain } from 'electron'; +import Store from 'electron-store'; +import * as fs from 'fs'; +import * as path from 'path'; +import axios from 'axios'; + +/** + * 初始化文件管理相关的IPC监听 + */ +export function initializeFileManager() { + // 通用的选择目录处理 + ipcMain.handle('select-directory', async () => { + const result = await dialog.showOpenDialog({ + properties: ['openDirectory'], + title: '选择目录' + }); + return result; + }); + + // 通用的打开目录处理 + ipcMain.on('open-directory', (_, path) => { + shell.openPath(path); + }); + + // 下载音乐处理 + ipcMain.on('download-music', downloadMusic); +} + +/** + * 下载音乐功能 + */ +async function downloadMusic(event: Electron.IpcMainEvent, { url, filename }: { url: string; filename: string }) { + try { + const store = new Store(); + const downloadPath = store.get('set.downloadPath') as string || app.getPath('downloads'); + + // 直接使用配置的下载路径 + const filePath = path.join(downloadPath, `${filename}.mp3`); + + // 检查文件是否已存在,如果存在则添加序号 + let finalFilePath = filePath; + let counter = 1; + while (fs.existsSync(finalFilePath)) { + const ext = path.extname(filePath); + const nameWithoutExt = filePath.slice(0, -ext.length); + finalFilePath = `${nameWithoutExt} (${counter})${ext}`; + counter++; + } + + const response = await axios({ + url, + method: 'GET', + responseType: 'stream' + }); + + const writer = fs.createWriteStream(finalFilePath); + response.data.pipe(writer); + + writer.on('finish', () => { + event.reply('music-download-complete', { success: true, path: finalFilePath }); + }); + + writer.on('error', (err) => { + event.reply('music-download-complete', { success: false, error: err.message }); + }); + } catch (error: any) { + event.reply('music-download-complete', { success: false, error: error.message }); + } +} \ No newline at end of file diff --git a/src/main/modules/tray.ts b/src/main/modules/tray.ts new file mode 100644 index 0000000..e847874 --- /dev/null +++ b/src/main/modules/tray.ts @@ -0,0 +1,43 @@ +import { app, Menu, nativeImage, Tray, BrowserWindow } from 'electron'; +import { join } from 'path'; + +let tray: Tray | null = null; + +/** + * 初始化系统托盘 + */ +export function initializeTray(iconPath: string, mainWindow: BrowserWindow) { + const trayIcon = nativeImage.createFromPath(join(iconPath, 'icon_16x16.png')).resize({ width: 16, height: 16 }); + tray = new Tray(trayIcon); + + // 创建一个上下文菜单 + const contextMenu = Menu.buildFromTemplate([ + { + label: '显示', + click: () => { + mainWindow.show(); + }, + }, + { + label: '退出', + click: () => { + mainWindow.destroy(); + app.quit(); + }, + }, + ]); + + // 设置系统托盘图标的上下文菜单 + tray.setContextMenu(contextMenu); + + // 当系统托盘图标被点击时,切换窗口的显示/隐藏 + tray.on('click', () => { + if (mainWindow.isVisible()) { + mainWindow.hide(); + } else { + mainWindow.show(); + } + }); + + return tray; +} \ No newline at end of file diff --git a/src/main/modules/window.ts b/src/main/modules/window.ts new file mode 100644 index 0000000..85306a1 --- /dev/null +++ b/src/main/modules/window.ts @@ -0,0 +1,81 @@ +import { BrowserWindow, shell, ipcMain } from 'electron'; +import { is } from '@electron-toolkit/utils'; +import { join } from 'path'; + +/** + * 初始化窗口管理相关的IPC监听 + */ +export function initializeWindowManager(mainWindow: BrowserWindow) { + ipcMain.on('minimize-window', (event) => { + const win = BrowserWindow.fromWebContents(event.sender); + if (win) { + win.minimize(); + } + }); + + ipcMain.on('maximize-window', (event) => { + const win = BrowserWindow.fromWebContents(event.sender); + if (win) { + if (win.isMaximized()) { + win.unmaximize(); + } else { + win.maximize(); + } + } + }); + + ipcMain.on('close-window', (event) => { + const win = BrowserWindow.fromWebContents(event.sender); + if (win) { + win.destroy(); + } + }); + + ipcMain.on('mini-tray', (event) => { + const win = BrowserWindow.fromWebContents(event.sender); + if (win) { + win.hide(); + } + }); +} + +/** + * 创建主窗口 + */ +export function createMainWindow(icon: Electron.NativeImage): BrowserWindow { + const mainWindow = new BrowserWindow({ + width: 1200, + height: 780, + show: false, + frame: false, + autoHideMenuBar: true, + icon, + webPreferences: { + preload: join(__dirname, '../preload/index.js'), + sandbox: false, + contextIsolation: true + } + }); + + mainWindow.setMinimumSize(1200, 780); + + mainWindow.on('ready-to-show', () => { + mainWindow.show(); + }); + + mainWindow.webContents.setWindowOpenHandler((details) => { + shell.openExternal(details.url); + return { action: 'deny' }; + }); + + // HMR for renderer base on electron-vite cli. + // Load the remote URL for development or the local html file for production. + if (is.dev && process.env.ELECTRON_RENDERER_URL) { + mainWindow.webContents.openDevTools({ mode: 'detach' }); + mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL); + } else { + mainWindow.loadFile(join(__dirname, '../renderer/index.html')); + } + + return mainWindow; +} \ No newline at end of file diff --git a/src/renderer/components/Coffee.vue b/src/renderer/components/Coffee.vue index e2a307f..f15488b 100644 --- a/src/renderer/components/Coffee.vue +++ b/src/renderer/components/Coffee.vue @@ -60,12 +60,10 @@ const copyQQ = () => { defineProps({ alipayQR: { type: String, - required: true, default: alipay }, wechatQR: { type: String, - required: true, default: wechat } }); diff --git a/src/renderer/components/common/SongItem.vue b/src/renderer/components/common/SongItem.vue index e96d96a..212f96b 100644 --- a/src/renderer/components/common/SongItem.vue +++ b/src/renderer/components/common/SongItem.vue @@ -1,5 +1,5 @@