🌈 style: 优化代码格式化

This commit is contained in:
alger
2025-01-10 22:49:55 +08:00
parent ddb814da10
commit 62e26cae7d
40 changed files with 450 additions and 239 deletions

4
.eslintignore Normal file
View File

@@ -0,0 +1,4 @@
node_modules
dist
out
.gitignore

135
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,135 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution');
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'eslint-config-airbnb-base',
'@vue/typescript/recommended',
'plugin:vue/vue3-recommended',
'plugin:vue-scoped-css/base',
'@electron-toolkit',
'@electron-toolkit/eslint-config-ts/eslint-recommended',
'plugin:prettier/recommended'
],
env: {
browser: true,
node: true,
jest: true,
es6: true
},
globals: {
defineProps: 'readonly',
defineEmits: 'readonly'
},
plugins: ['vue', '@typescript-eslint', 'simple-import-sort'],
parserOptions: {
parser: '@typescript-eslint/parser',
sourceType: 'module',
allowImportExportEverywhere: true,
ecmaFeatures: {
jsx: true
}
},
settings: {
'import/extensions': ['.js', '.jsx', '.ts', '.tsx']
},
rules: {
'vue/require-default-prop': 'off',
'vue/multi-word-component-names': 'off',
'no-nested-ternary': 'off',
'no-console': 'off',
'no-continue': 'off',
'no-restricted-syntax': 'off',
'no-return-assign': 'off',
'no-unused-expressions': 'off',
'no-return-await': 'off',
'no-plusplus': 'off',
'no-param-reassign': 'off',
'no-shadow': 'off',
'guard-for-in': 'off',
'import/extensions': 'off',
'import/no-unresolved': 'off',
'import/no-extraneous-dependencies': 'off',
'import/prefer-default-export': 'off',
'import/first': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'vue/first-attribute-linebreak': 0,
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_'
}
],
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_'
}
],
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'class-methods-use-this': 'off',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error'
},
overrides: [
{
files: ['*.vue'],
rules: {
'vue/component-name-in-template-casing': [2, 'kebab-case'],
'vue/require-default-prop': 0,
'vue/multi-word-component-names': 0,
'vue/no-reserved-props': 0,
'vue/no-v-html': 0,
'vue-scoped-css/enforce-style-type': [
'error',
{
allows: ['scoped']
}
],
'@typescript-eslint/explicit-function-return-type': 'off',
// 需要行尾分号
'prettier/prettier': ['error', { endOfLine: 'auto' }]
}
},
{
files: ['*.ts', '*.tsx'],
rules: {
'no-await-in-loop': 'off',
'dot-notation': 'off',
'constructor-super': 'off',
'getter-return': 'off',
'no-const-assign': 'off',
'no-dupe-args': 'off',
'no-dupe-class-members': 'off',
'no-dupe-keys': 'off',
'no-func-assign': 'off',
'no-import-assign': 'off',
'no-new-symbol': 'off',
'no-obj-calls': 'off',
'no-redeclare': 'off',
'no-setter-return': 'off',
'no-this-before-super': 'off',
'no-undef': 'off',
'no-unreachable': 'off',
'no-unsafe-negation': 'off',
'no-var': 'error',
'prefer-const': 'error',
'prefer-rest-params': 'error',
'prefer-spread': 'error',
'valid-typeof': 'off',
'consistent-return': 'off',
'no-promise-executor-return': 'off',
'prefer-promise-reject-errors': 'off',
'@typescript-eslint/explicit-function-return-type': 'off'
}
}
]
};

6
.prettierignore Normal file
View File

@@ -0,0 +1,6 @@
out
dist
pnpm-lock.yaml
LICENSE.md
tsconfig.json
tsconfig.*.json

5
.prettierrc.yaml Normal file
View File

@@ -0,0 +1,5 @@
singleQuote: true
semi: true
printWidth: 100
trailingComma: none
endOfLine: auto

View File

@@ -3,11 +3,11 @@ import { app, globalShortcut, ipcMain, nativeImage } from 'electron';
import { join } from 'path'; import { join } from 'path';
import { loadLyricWindow } from './lyric'; import { loadLyricWindow } from './lyric';
import { startMusicApi } from './server'; import { initializeConfig } from './modules/config';
import { initializeFileManager } from './modules/fileManager'; import { initializeFileManager } from './modules/fileManager';
import { initializeTray } from './modules/tray'; import { initializeTray } from './modules/tray';
import { createMainWindow, initializeWindowManager } from './modules/window'; import { createMainWindow, initializeWindowManager } from './modules/window';
import { initializeConfig } from './modules/config'; import { startMusicApi } from './server';
// 导入所有图标 // 导入所有图标
const iconPath = join(__dirname, '../../resources'); const iconPath = join(__dirname, '../../resources');
@@ -15,8 +15,8 @@ const icon = nativeImage.createFromPath(
process.platform === 'darwin' process.platform === 'darwin'
? join(iconPath, 'icon.icns') ? join(iconPath, 'icon.icns')
: process.platform === 'win32' : process.platform === 'win32'
? join(iconPath, 'favicon.ico') ? join(iconPath, 'favicon.ico')
: join(iconPath, 'icon.png') : join(iconPath, 'icon.png')
); );
let mainWindow: Electron.BrowserWindow; let mainWindow: Electron.BrowserWindow;
@@ -26,19 +26,19 @@ function initialize() {
// 初始化各个模块 // 初始化各个模块
initializeConfig(); initializeConfig();
initializeFileManager(); initializeFileManager();
// 创建主窗口 // 创建主窗口
mainWindow = createMainWindow(icon); mainWindow = createMainWindow(icon);
// 初始化窗口管理 // 初始化窗口管理
initializeWindowManager(); initializeWindowManager();
// 初始化托盘 // 初始化托盘
initializeTray(iconPath, mainWindow); initializeTray(iconPath, mainWindow);
// 启动音乐API // 启动音乐API
startMusicApi(); startMusicApi();
// 加载歌词窗口 // 加载歌词窗口
loadLyricWindow(ipcMain, mainWindow); loadLyricWindow(ipcMain, mainWindow);
} }

View File

@@ -1,5 +1,6 @@
import { app, ipcMain } from 'electron'; import { app, ipcMain } from 'electron';
import Store from 'electron-store'; import Store from 'electron-store';
import set from '../set.json'; import set from '../set.json';
interface StoreType { interface StoreType {
@@ -22,7 +23,7 @@ export function initializeConfig() {
store = new Store<StoreType>({ store = new Store<StoreType>({
name: 'config', name: 'config',
defaults: { defaults: {
set: set set
} }
}); });
@@ -39,4 +40,4 @@ export function initializeConfig() {
}); });
return store; return store;
} }

View File

@@ -1,8 +1,8 @@
import { app, dialog, shell, ipcMain } from 'electron'; import axios from 'axios';
import { app, dialog, ipcMain, shell } from 'electron';
import Store from 'electron-store'; import Store from 'electron-store';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import axios from 'axios';
/** /**
* 初始化文件管理相关的IPC监听 * 初始化文件管理相关的IPC监听
@@ -29,11 +29,14 @@ export function initializeFileManager() {
/** /**
* 下载音乐功能 * 下载音乐功能
*/ */
async function downloadMusic(event: Electron.IpcMainEvent, { url, filename }: { url: string; filename: string }) { async function downloadMusic(
event: Electron.IpcMainEvent,
{ url, filename }: { url: string; filename: string }
) {
try { try {
const store = new Store(); const store = new Store();
const downloadPath = store.get('set.downloadPath') as string || app.getPath('downloads'); const downloadPath = (store.get('set.downloadPath') as string) || app.getPath('downloads');
// 直接使用配置的下载路径 // 直接使用配置的下载路径
const filePath = path.join(downloadPath, `${filename}.mp3`); const filePath = path.join(downloadPath, `${filename}.mp3`);
@@ -66,4 +69,4 @@ async function downloadMusic(event: Electron.IpcMainEvent, { url, filename }: {
} catch (error: any) { } catch (error: any) {
event.reply('music-download-complete', { success: false, error: error.message }); event.reply('music-download-complete', { success: false, error: error.message });
} }
} }

View File

@@ -1,4 +1,4 @@
import { app, Menu, nativeImage, Tray, BrowserWindow } from 'electron'; import { app, BrowserWindow, Menu, nativeImage, Tray } from 'electron';
import { join } from 'path'; import { join } from 'path';
let tray: Tray | null = null; let tray: Tray | null = null;
@@ -7,7 +7,9 @@ let tray: Tray | null = null;
* 初始化系统托盘 * 初始化系统托盘
*/ */
export function initializeTray(iconPath: string, mainWindow: BrowserWindow) { export function initializeTray(iconPath: string, mainWindow: BrowserWindow) {
const trayIcon = nativeImage.createFromPath(join(iconPath, 'icon_16x16.png')).resize({ width: 16, height: 16 }); const trayIcon = nativeImage
.createFromPath(join(iconPath, 'icon_16x16.png'))
.resize({ width: 16, height: 16 });
tray = new Tray(trayIcon); tray = new Tray(trayIcon);
// 创建一个上下文菜单 // 创建一个上下文菜单
@@ -16,15 +18,15 @@ export function initializeTray(iconPath: string, mainWindow: BrowserWindow) {
label: '显示', label: '显示',
click: () => { click: () => {
mainWindow.show(); mainWindow.show();
}, }
}, },
{ {
label: '退出', label: '退出',
click: () => { click: () => {
mainWindow.destroy(); mainWindow.destroy();
app.quit(); app.quit();
}, }
}, }
]); ]);
// 设置系统托盘图标的上下文菜单 // 设置系统托盘图标的上下文菜单
@@ -40,4 +42,4 @@ export function initializeTray(iconPath: string, mainWindow: BrowserWindow) {
}); });
return tray; return tray;
} }

View File

@@ -1,7 +1,7 @@
import { BrowserWindow, shell, ipcMain, app, session } from 'electron';
import { is } from '@electron-toolkit/utils'; import { is } from '@electron-toolkit/utils';
import { join } from 'path'; import { app, BrowserWindow, ipcMain, session, shell } from 'electron';
import Store from 'electron-store'; import Store from 'electron-store';
import { join } from 'path';
const store = new Store(); const store = new Store();
@@ -116,4 +116,4 @@ export function createMainWindow(icon: Electron.NativeImage): BrowserWindow {
} }
return mainWindow; return mainWindow;
} }

View File

@@ -1,6 +1,7 @@
import { ipcMain } from 'electron'; import { ipcMain } from 'electron';
import Store from 'electron-store'; import Store from 'electron-store';
import fs from 'fs'; import fs from 'fs';
import server from 'netease-cloud-music-api-alger/server';
import os from 'os'; import os from 'os';
import path from 'path'; import path from 'path';
@@ -16,9 +17,6 @@ ipcMain.handle('unblock-music', async (_, id, data) => {
return unblockMusic(id, data); return unblockMusic(id, data);
}); });
import server from 'netease-cloud-music-api-alger/server';
async function startMusicApi(): Promise<void> { async function startMusicApi(): Promise<void> {
console.log('MUSIC API STARTED'); console.log('MUSIC API STARTED');

View File

@@ -14,6 +14,6 @@ declare global {
restart: () => void; restart: () => void;
unblockMusic: (id: number, data: any) => Promise<any>; unblockMusic: (id: number, data: any) => Promise<any>;
}; };
$message:any $message: any;
} }
} }

View File

@@ -13,10 +13,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { darkTheme, lightTheme } from 'naive-ui'; import { darkTheme, lightTheme } from 'naive-ui';
import { onMounted } from 'vue'; import { onMounted } from 'vue';
import { isElectron } from '@/utils';
import homeRouter from '@/router/home'; import homeRouter from '@/router/home';
import store from '@/store'; import store from '@/store';
import { isElectron } from '@/utils';
import { isMobile } from './utils'; import { isMobile } from './utils';

View File

@@ -1,8 +1,8 @@
import store from '@/store';
import { ILyric } from '@/type/lyric'; import { ILyric } from '@/type/lyric';
import { isElectron } from '@/utils'; import { isElectron } from '@/utils';
import request from '@/utils/request'; import request from '@/utils/request';
import requestMusic from '@/utils/request_music'; import requestMusic from '@/utils/request_music';
import store from '@/store';
// 获取音乐音质详情 // 获取音乐音质详情
export const getMusicQualityDetail = (id: number) => { export const getMusicQualityDetail = (id: number) => {
@@ -11,22 +11,22 @@ export const getMusicQualityDetail = (id: number) => {
// 根据音乐Id获取音乐播放URl // 根据音乐Id获取音乐播放URl
export const getMusicUrl = async (id: number) => { export const getMusicUrl = async (id: number) => {
const res = await request.get('/song/download/url/v1', { const res = await request.get('/song/download/url/v1', {
params: { params: {
id, id,
level: store.state.setData.musicQuality || 'higher' level: store.state.setData.musicQuality || 'higher'
} }
}); });
if (res.data.data.url) { if (res.data.data.url) {
return {data:{ data:[{url:res.data.data.url}]}}; return { data: { data: [{ url: res.data.data.url }] } };
} }
return await request.get('/song/url/v1', { return await request.get('/song/url/v1', {
params: { params: {
id, id,
level: store.state.setData.musicQuality || 'higher' level: store.state.setData.musicQuality || 'higher'
} }
}); });
}; };

View File

@@ -49,8 +49,10 @@
<script setup> <script setup>
import { NButton, NImage, NPopover } from 'naive-ui'; import { NButton, NImage, NPopover } from 'naive-ui';
import alipay from '@/assets/alipay.png'; import alipay from '@/assets/alipay.png';
import wechat from '@/assets/wechat.png'; import wechat from '@/assets/wechat.png';
const message = useMessage(); const message = useMessage();
const copyQQ = () => { const copyQQ = () => {
navigator.clipboard.writeText('789288579'); navigator.clipboard.writeText('789288579');

View File

@@ -20,7 +20,7 @@
</div> </div>
</template> </template>
</div> </div>
<MusicList <music-list
v-model:show="showMusic" v-model:show="showMusic"
:name="albumName" :name="albumName"
:song-list="songList" :song-list="songList"
@@ -36,9 +36,9 @@ import { onMounted, ref } from 'vue';
import { getNewAlbum } from '@/api/home'; import { getNewAlbum } from '@/api/home';
import { getAlbum } from '@/api/list'; import { getAlbum } from '@/api/list';
import MusicList from '@/components/MusicList.vue';
import type { IAlbumNew } from '@/type/album'; import type { IAlbumNew } from '@/type/album';
import { getImgUrl, setAnimationClass, setAnimationDelay } from '@/utils'; import { getImgUrl, setAnimationClass, setAnimationDelay } from '@/utils';
import MusicList from '@/components/MusicList.vue';
const albumData = ref<IAlbumNew>(); const albumData = ref<IAlbumNew>();
const loadAlbumList = async () => { const loadAlbumList = async () => {

View File

@@ -74,11 +74,11 @@ import { onMounted, ref } from 'vue';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import { getDayRecommend, getHotSinger } from '@/api/home'; import { getDayRecommend, getHotSinger } from '@/api/home';
import MusicList from '@/components/MusicList.vue';
import router from '@/router'; import router from '@/router';
import { IDayRecommend } from '@/type/day_recommend'; import { IDayRecommend } from '@/type/day_recommend';
import type { IHotSinger } from '@/type/singer'; import type { IHotSinger } from '@/type/singer';
import { getImgUrl, setAnimationClass, setAnimationDelay, setBackgroundImg } from '@/utils'; import { getImgUrl, setAnimationClass, setAnimationDelay, setBackgroundImg } from '@/utils';
import MusicList from '@/components/MusicList.vue';
const store = useStore(); const store = useStore();

View File

@@ -39,10 +39,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { isElectron, isMobile } from '@/utils'; import { isElectron, isMobile } from '@/utils';
import config from '../../../../package.json';
import { getLatestReleaseInfo } from '@/utils/update'; import { getLatestReleaseInfo } from '@/utils/update';
import config from '../../../../package.json';
const showModal = ref(false); const showModal = ref(false);
const noPrompt = ref(false); const noPrompt = ref(false);
const releaseInfo = ref<any>(null); const releaseInfo = ref<any>(null);
@@ -77,36 +79,33 @@ const handleInstall = async (): Promise<void> => {
const isMac = userAgent.toLowerCase().includes('mac'); const isMac = userAgent.toLowerCase().includes('mac');
const isWindows = userAgent.toLowerCase().includes('win'); const isWindows = userAgent.toLowerCase().includes('win');
const isLinux = userAgent.toLowerCase().includes('linux'); const isLinux = userAgent.toLowerCase().includes('linux');
const isX64 = userAgent.includes('x86_64') || const isX64 =
userAgent.includes('Win64') || userAgent.includes('x86_64') || userAgent.includes('Win64') || userAgent.includes('WOW64');
userAgent.includes('WOW64');
let downloadUrl = ''; let downloadUrl = '';
// 根据平台和架构选择对应的安装包 // 根据平台和架构选择对应的安装包
if (isMac) { if (isMac) {
// macOS // macOS
const macAsset = assets.find(asset => const macAsset = assets.find((asset) => asset.name.includes('mac'));
asset.name.includes('mac')
);
downloadUrl = macAsset?.browser_download_url || ''; downloadUrl = macAsset?.browser_download_url || '';
} else if (isWindows) { } else if (isWindows) {
// Windows // Windows
let winAsset = assets.find(asset => let winAsset = assets.find(
asset.name.includes('win') && (asset) =>
(isX64 ? asset.name.includes('x64') : asset.name.includes('ia32')) asset.name.includes('win') &&
(isX64 ? asset.name.includes('x64') : asset.name.includes('ia32'))
); );
if(!winAsset){ if (!winAsset) {
winAsset = assets.find(asset => winAsset = assets.find((asset) => asset.name.includes('win.exe'));
asset.name.includes('win.exe')
);
} }
downloadUrl = winAsset?.browser_download_url || ''; downloadUrl = winAsset?.browser_download_url || '';
} else if (isLinux) { } else if (isLinux) {
// Linux // Linux
const linuxAsset = assets.find(asset => const linuxAsset = assets.find(
(asset.name.endsWith('.AppImage') || asset.name.endsWith('.deb')) && (asset) =>
asset.name.includes('x64') (asset.name.endsWith('.AppImage') || asset.name.endsWith('.deb')) &&
asset.name.includes('x64')
); );
downloadUrl = linuxAsset?.browser_download_url || ''; downloadUrl = linuxAsset?.browser_download_url || '';
} }

View File

@@ -15,7 +15,7 @@
<p class="search-item-artist">{{ item.desc }}</p> <p class="search-item-artist">{{ item.desc }}</p>
</div> </div>
<MusicList <music-list
v-if="['专辑', 'playlist'].includes(item.type)" v-if="['专辑', 'playlist'].includes(item.type)"
v-model:show="showPop" v-model:show="showPop"
:name="item.name" :name="item.name"
@@ -40,6 +40,7 @@ import MvPlayer from '@/components/MvPlayer.vue';
import { audioService } from '@/services/audioService'; import { audioService } from '@/services/audioService';
import { IMvItem } from '@/type/mv'; import { IMvItem } from '@/type/mv';
import { getImgUrl } from '@/utils'; import { getImgUrl } from '@/utils';
import MusicList from '../MusicList.vue'; import MusicList from '../MusicList.vue';
const props = defineProps<{ const props = defineProps<{

View File

@@ -1,5 +1,9 @@
<template> <template>
<div class="song-item" :class="{ 'song-mini': mini, 'song-list': list }" @contextmenu.prevent="handleContextMenu"> <div
class="song-item"
:class="{ 'song-mini': mini, 'song-list': list }"
@contextmenu.prevent="handleContextMenu"
>
<n-image <n-image
v-if="item.picUrl" v-if="item.picUrl"
ref="songImg" ref="songImg"
@@ -71,17 +75,17 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { cloneDeep } from 'lodash';
import type { MenuOption } from 'naive-ui';
import { useMessage } from 'naive-ui';
import { computed, h, ref, useTemplateRef } from 'vue'; import { computed, h, ref, useTemplateRef } from 'vue';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import { useMessage } from 'naive-ui';
import type { MenuOption } from 'naive-ui';
import { getSongUrl } from '@/hooks/MusicListHook';
import { audioService } from '@/services/audioService'; import { audioService } from '@/services/audioService';
import type { SongResult } from '@/type/music'; import type { SongResult } from '@/type/music';
import { getImgUrl, isElectron } from '@/utils'; import { getImgUrl, isElectron } from '@/utils';
import { getImageBackground } from '@/utils/linearColor'; import { getImageBackground } from '@/utils/linearColor';
import { getSongUrl } from '@/hooks/MusicListHook';
import { cloneDeep } from 'lodash';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@@ -117,7 +121,7 @@ const isDownloading = ref(false);
const dropdownOptions = computed<MenuOption[]>(() => [ const dropdownOptions = computed<MenuOption[]>(() => [
{ {
label: isDownloading.value ? '下载中...' : '下载 ' + props.item.name, label: isDownloading.value ? '下载中...' : `下载 ${props.item.name}`,
key: 'download', key: 'download',
icon: () => h('i', { class: 'iconfont ri-download-line' }), icon: () => h('i', { class: 'iconfont ri-download-line' }),
disabled: isDownloading.value disabled: isDownloading.value
@@ -148,7 +152,7 @@ const downloadMusic = async () => {
try { try {
isDownloading.value = true; isDownloading.value = true;
const loadingMessage = message.loading('正在下载中...', { duration: 0 }); const loadingMessage = message.loading('正在下载中...', { duration: 0 });
const url = await getSongUrl(props.item.id, cloneDeep(props.item)); const url = await getSongUrl(props.item.id, cloneDeep(props.item));
if (!url) { if (!url) {
loadingMessage.destroy(); loadingMessage.destroy();
@@ -156,21 +160,21 @@ const downloadMusic = async () => {
isDownloading.value = false; isDownloading.value = false;
return; return;
} }
// 先移除可能存在的旧监听器 // 先移除可能存在的旧监听器
window.electron.ipcRenderer.removeAllListeners('music-download-complete'); window.electron.ipcRenderer.removeAllListeners('music-download-complete');
// 发送下载请求 // 发送下载请求
window.electron.ipcRenderer.send('download-music', { window.electron.ipcRenderer.send('download-music', {
url, url,
filename: `${props.item.name} - ${(props.item.ar || props.item.song?.artists)?.map(a => a.name).join(',')}` filename: `${props.item.name} - ${(props.item.ar || props.item.song?.artists)?.map((a) => a.name).join(',')}`
}); });
// 添加新的一次性监听器 // 添加新的一次性监听器
window.electron.ipcRenderer.once('music-download-complete', (_, result) => { window.electron.ipcRenderer.once('music-download-complete', (_, result) => {
isDownloading.value = false; isDownloading.value = false;
loadingMessage.destroy(); loadingMessage.destroy();
if (result.success) { if (result.success) {
message.success('下载成功'); message.success('下载成功');
} else if (result.canceled) { } else if (result.canceled) {
@@ -298,7 +302,7 @@ const toggleFavorite = async (e: Event) => {
&-download { &-download {
@apply mr-2 cursor-pointer; @apply mr-2 cursor-pointer;
.iconfont { .iconfont {
@apply text-xl transition text-gray-500 dark:text-gray-400 hover:text-green-500; @apply text-xl transition text-gray-500 dark:text-gray-400 hover:text-green-500;
} }

View File

@@ -44,12 +44,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref, computed, watch } from 'vue';
import { marked } from 'marked'; import { marked } from 'marked';
import { checkUpdate, UpdateResult } from '@/utils/update'; import { computed, onMounted, ref, watch } from 'vue';
import config from '../../../../package.json';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import { checkUpdate, UpdateResult } from '@/utils/update';
import config from '../../../../package.json';
// 配置 marked // 配置 marked
marked.setOptions({ marked.setOptions({
breaks: true, // 支持 GitHub 风格的换行 breaks: true, // 支持 GitHub 风格的换行
@@ -65,24 +67,27 @@ const updateInfo = ref<UpdateResult>({
releaseInfo: null releaseInfo: null
}); });
const store = useStore() const store = useStore();
// 添加计算属性 // 添加计算属性
const showUpdateModalState = computed({ const showUpdateModalState = computed({
get: () => store.state.showUpdateModal, get: () => store.state.showUpdateModal,
set: (val) => store.commit('setShowUpdateModal', val) set: (val) => store.commit('setShowUpdateModal', val)
}) });
// 替换原来的 watch // 替换原来的 watch
watch(showUpdateModalState, (newVal) => { watch(showUpdateModalState, (newVal) => {
if (newVal) { if (newVal) {
showModal.value = true showModal.value = true;
} }
}) });
watch(() => showModal.value, (newVal) => { watch(
showUpdateModalState.value = newVal () => showModal.value,
}) (newVal) => {
showUpdateModalState.value = newVal;
}
);
// 解析 Markdown // 解析 Markdown
const parsedReleaseNotes = computed(() => { const parsedReleaseNotes = computed(() => {
@@ -116,52 +121,50 @@ const checkForUpdates = async () => {
} }
}; };
const handleUpdate = async () => { const handleUpdate = async () => {
const assets = updateInfo.value.releaseInfo?.assets || []; const assets = updateInfo.value.releaseInfo?.assets || [];
const platform = window.electron.process.platform; 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('arch', arch);
console.log('platform',platform) console.log('platform', platform);
const version = updateInfo.value.latestVersion const version = updateInfo.value.latestVersion;
const downUrls = { const downUrls = {
win32: { win32: {
all: `https://github.com/algerkong/AlgerMusicPlayer/releases/download/v${version}/AlgerMusicPlayer-${version}-win.exe`, all: `https://github.com/algerkong/AlgerMusicPlayer/releases/download/v${version}/AlgerMusicPlayer-${version}-win.exe`,
x64: `https://github.com/algerkong/AlgerMusicPlayer/releases/download/v${version}/AlgerMusicPlayer-${version}-win-x64.exe`, x64: `https://github.com/algerkong/AlgerMusicPlayer/releases/download/v${version}/AlgerMusicPlayer-${version}-win-x64.exe`,
ia32: `https://github.com/algerkong/AlgerMusicPlayer/releases/download/v${version}/AlgerMusicPlayer-${version}-win-ia32.exe`, ia32: `https://github.com/algerkong/AlgerMusicPlayer/releases/download/v${version}/AlgerMusicPlayer-${version}-win-ia32.exe`
}, },
darwin: { darwin: {
all: `https://github.com/algerkong/AlgerMusicPlayer/releases/download/v${version}AlgerMusicPlayer-${version}-mac-universal.dmg`, all: `https://github.com/algerkong/AlgerMusicPlayer/releases/download/v${version}AlgerMusicPlayer-${version}-mac-universal.dmg`
}, },
linux: { linux: {
AppImage: `https://github.com/algerkong/AlgerMusicPlayer/releases/download/v${version}/AlgerMusicPlayer-${version}-linux-x64.AppImage`, AppImage: `https://github.com/algerkong/AlgerMusicPlayer/releases/download/v${version}/AlgerMusicPlayer-${version}-linux-x64.AppImage`,
deb: `https://github.com/algerkong/AlgerMusicPlayer/releases/download/v${version}/AlgerMusicPlayer-${version}-linux-x64.deb`, deb: `https://github.com/algerkong/AlgerMusicPlayer/releases/download/v${version}/AlgerMusicPlayer-${version}-linux-x64.deb`
} }
} };
let downloadUrl = ''; let downloadUrl = '';
// 根据平台和架构选择对应的安装包 // 根据平台和架构选择对应的安装包
if (platform === 'darwin') { if (platform === 'darwin') {
// macOS // macOS
const macAsset = assets.find(asset => const macAsset = assets.find((asset) => asset.name.includes('mac'));
asset.name.includes('mac')
);
downloadUrl = macAsset?.browser_download_url || downUrls.darwin.all || ''; downloadUrl = macAsset?.browser_download_url || downUrls.darwin.all || '';
} else if (platform === 'win32') { } else if (platform === 'win32') {
// Windows // Windows
const winAsset = assets.find(asset => const winAsset = assets.find(
asset.name.includes('win') && (asset) =>
(arch === 'x64' ? asset.name.includes('x64') : asset.name.includes('ia32')) asset.name.includes('win') &&
(arch === 'x64' ? asset.name.includes('x64') : asset.name.includes('ia32'))
); );
downloadUrl = winAsset?.browser_download_url || downUrls.win32[arch] || downUrls.win32.all || ''; downloadUrl =
winAsset?.browser_download_url || downUrls.win32[arch] || downUrls.win32.all || '';
} else if (platform === 'linux') { } else if (platform === 'linux') {
// Linux // Linux
const linuxAsset = assets.find(asset => const linuxAsset = assets.find(
(asset.name.endsWith('.AppImage') || asset.name.endsWith('.deb')) && (asset) =>
asset.name.includes('x64') (asset.name.endsWith('.AppImage') || asset.name.endsWith('.deb')) &&
asset.name.includes('x64')
); );
downloadUrl = linuxAsset?.browser_download_url || downUrls.linux[arch] || ''; downloadUrl = linuxAsset?.browser_download_url || downUrls.linux[arch] || '';
} }
@@ -211,7 +214,7 @@ onMounted(() => {
} }
.update-body { .update-body {
@apply p-4 pt-2 text-gray-600 dark:text-gray-300 rounded-lg overflow-hidden; @apply p-4 pt-2 text-gray-600 dark:text-gray-300 rounded-lg overflow-hidden;
:deep(h1) { :deep(h1) {
@apply text-xl font-bold mb-3; @apply text-xl font-bold mb-3;
} }
@@ -253,7 +256,8 @@ onMounted(() => {
} }
:deep(table) { :deep(table) {
@apply w-full mb-3; @apply w-full mb-3;
th, td { th,
td {
@apply px-3 py-2 border border-gray-200 dark:border-gray-600; @apply px-3 py-2 border border-gray-200 dark:border-gray-600;
} }
th { th {
@@ -282,4 +286,4 @@ onMounted(() => {
} }
} }
} }
</style> </style>

View File

@@ -3,8 +3,8 @@ import { computed, ref } from 'vue';
import { audioService } from '@/services/audioService'; import { audioService } from '@/services/audioService';
import store from '@/store'; import store from '@/store';
import type { ILyricText, SongResult } from '@/type/music'; import type { ILyricText, SongResult } from '@/type/music';
import { getTextColors } from '@/utils/linearColor';
import { isElectron } from '@/utils'; import { isElectron } from '@/utils';
import { getTextColors } from '@/utils/linearColor';
const windowData = window as any; const windowData = window as any;
@@ -18,7 +18,7 @@ export const currentLrcProgress = ref(0); // 来存储当前歌词的进度
export const playMusic = computed(() => store.state.playMusic as SongResult); // 当前播放歌曲 export const playMusic = computed(() => store.state.playMusic as SongResult); // 当前播放歌曲
export const sound = ref<Howl | null>(audioService.getCurrentSound()); export const sound = ref<Howl | null>(audioService.getCurrentSound());
export const isLyricWindowOpen = ref(false); // 新增状态 export const isLyricWindowOpen = ref(false); // 新增状态
export const textColors = ref(getTextColors()); export const textColors = ref<any>(getTextColors());
document.onkeyup = (e) => { document.onkeyup = (e) => {
// 检查事件目标是否是输入框元素 // 检查事件目标是否是输入框元素

View File

@@ -1,4 +1,5 @@
import { Howl } from 'howler'; import { Howl } from 'howler';
import { cloneDeep } from 'lodash';
import { getMusicLrc, getMusicUrl, getParsingMusicUrl } from '@/api/music'; import { getMusicLrc, getMusicUrl, getParsingMusicUrl } from '@/api/music';
import { useMusicHistory } from '@/hooks/MusicHistoryHook'; import { useMusicHistory } from '@/hooks/MusicHistoryHook';
@@ -6,7 +7,6 @@ import { audioService } from '@/services/audioService';
import type { ILyric, ILyricText, SongResult } from '@/type/music'; import type { ILyric, ILyricText, SongResult } from '@/type/music';
import { getImgUrl } from '@/utils'; import { getImgUrl } from '@/utils';
import { getImageLinearBackground } from '@/utils/linearColor'; import { getImageLinearBackground } from '@/utils/linearColor';
import { cloneDeep } from 'lodash';
const musicHistory = useMusicHistory(); const musicHistory = useMusicHistory();

View File

@@ -28,7 +28,7 @@
<play-bar v-if="isPlay" :style="isMobile && store.state.musicFull ? 'bottom: 0;' : ''" /> <play-bar v-if="isPlay" :style="isMobile && store.state.musicFull ? 'bottom: 0;' : ''" />
</div> </div>
<install-app-modal v-if="!isElectron"></install-app-modal> <install-app-modal v-if="!isElectron"></install-app-modal>
<update-modal v-if="isElectron"/> <update-modal v-if="isElectron" />
</div> </div>
</template> </template>
@@ -39,9 +39,9 @@ import { useStore } from 'vuex';
import InstallAppModal from '@/components/common/InstallAppModal.vue'; import InstallAppModal from '@/components/common/InstallAppModal.vue';
import PlayBottom from '@/components/common/PlayBottom.vue'; import PlayBottom from '@/components/common/PlayBottom.vue';
import UpdateModal from '@/components/common/UpdateModal.vue';
import homeRouter from '@/router/home'; import homeRouter from '@/router/home';
import { isElectron, isMobile } from '@/utils'; import { isElectron, isMobile } from '@/utils';
import UpdateModal from '@/components/common/UpdateModal.vue';
const keepAliveInclude = computed(() => const keepAliveInclude = computed(() =>
homeRouter homeRouter

View File

@@ -116,11 +116,15 @@ const lrcScroll = (behavior = 'smooth') => {
const debouncedLrcScroll = useDebounceFn(lrcScroll, 200); const debouncedLrcScroll = useDebounceFn(lrcScroll, 200);
const mouseOverLayout = () => { const mouseOverLayout = () => {
if(isMobile.value) {return} if (isMobile.value) {
return;
}
isMouse.value = true; isMouse.value = true;
}; };
const mouseLeaveLayout = () => { const mouseLeaveLayout = () => {
if(isMobile.value) {return} if (isMobile.value) {
return;
}
setTimeout(() => { setTimeout(() => {
isMouse.value = false; isMouse.value = false;
lrcScroll(); lrcScroll();

View File

@@ -140,7 +140,7 @@
</n-popover> </n-popover>
</div> </div>
<!-- 播放音乐 --> <!-- 播放音乐 -->
<music-full ref="MusicFullRef" v-model:music-full="musicFullVisible" :background="background" /> <music-full ref="MusicFullRef" v-model:music-full="musicFullVisible" :background="background" />
</div> </div>
</template> </template>
@@ -159,7 +159,7 @@ import {
textColors textColors
} from '@/hooks/MusicHook'; } from '@/hooks/MusicHook';
import type { SongResult } from '@/type/music'; import type { SongResult } from '@/type/music';
import { getImgUrl, isMobile, secondToMinute, setAnimationClass, isElectron } from '@/utils'; import { getImgUrl, isElectron, isMobile, secondToMinute, setAnimationClass } from '@/utils';
import MusicFull from './MusicFull.vue'; import MusicFull from './MusicFull.vue';

View File

@@ -96,7 +96,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref, watchEffect, computed } from 'vue'; import { computed, onMounted, ref, watchEffect } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
@@ -240,7 +240,7 @@ const checkForUpdates = async () => {
const toGithubRelease = () => { const toGithubRelease = () => {
if (updateInfo.value.hasUpdate) { if (updateInfo.value.hasUpdate) {
store.commit('setShowUpdateModal', true) store.commit('setShowUpdateModal', true);
} else { } else {
window.open('https://github.com/algerkong/AlgerMusicPlayer/releases', '_blank'); window.open('https://github.com/algerkong/AlgerMusicPlayer/releases', '_blank');
} }
@@ -317,7 +317,7 @@ const toGithubRelease = () => {
.version-info { .version-info {
@apply ml-auto flex items-center; @apply ml-auto flex items-center;
.version-number { .version-number {
@apply text-xs px-2 py-0.5 rounded; @apply text-xs px-2 py-0.5 rounded;
@apply bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300; @apply bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300;

View File

@@ -36,6 +36,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import { isElectron } from '@/utils'; import { isElectron } from '@/utils';
const store = useStore(); const store = useStore();
@@ -56,7 +57,7 @@ const handleAction = (action: 'minimize' | 'close') => {
closeAction: action closeAction: action
}); });
} }
if (action === 'minimize') { if (action === 'minimize') {
window.api.miniTray(); window.api.miniTray();
} else { } else {
@@ -70,13 +71,13 @@ const close = () => {
return; return;
} }
const closeAction = store.state.setData.closeAction; const { closeAction } = store.state.setData;
if (closeAction === 'minimize') { if (closeAction === 'minimize') {
window.api.miniTray(); window.api.miniTray();
return; return;
} }
if (closeAction === 'close') { if (closeAction === 'close') {
window.api.close(); window.api.close();
return; return;

View File

@@ -1,12 +1,12 @@
import { createStore } from 'vuex'; import { createStore } from 'vuex';
import setData from '@/../main/set.json';
import { getLikedList, likeSong } from '@/api/music';
import { useMusicListHook } from '@/hooks/MusicListHook'; import { useMusicListHook } from '@/hooks/MusicListHook';
import homeRouter from '@/router/home'; import homeRouter from '@/router/home';
import type { SongResult } from '@/type/music'; import type { SongResult } from '@/type/music';
import { applyTheme, getCurrentTheme, ThemeType } from '@/utils/theme';
import { isElectron } from '@/utils'; import { isElectron } from '@/utils';
import { likeSong, getLikedList } from '@/api/music'; import { applyTheme, getCurrentTheme, ThemeType } from '@/utils/theme';
import setData from '@/../main/set.json'
// 默认设置 // 默认设置
const defaultSettings = setData; const defaultSettings = setData;
@@ -93,14 +93,18 @@ const mutations = {
// 'set', // 'set',
// JSON.parse(JSON.stringify(setData)) // JSON.parse(JSON.stringify(setData))
// ); // );
window.electron.ipcRenderer.send('set-store-value', 'set', JSON.parse(JSON.stringify(setData))); window.electron.ipcRenderer.send(
'set-store-value',
'set',
JSON.parse(JSON.stringify(setData))
);
} else { } else {
localStorage.setItem('appSettings', JSON.stringify(setData)); localStorage.setItem('appSettings', JSON.stringify(setData));
} }
}, },
async addToFavorite(state: State, songId: number) { async addToFavorite(state: State, songId: number) {
try { try {
state.user && localStorage.getItem('token') && await likeSong(songId, true); state.user && localStorage.getItem('token') && (await likeSong(songId, true));
if (!state.favoriteList.includes(songId)) { if (!state.favoriteList.includes(songId)) {
state.favoriteList = [songId, ...state.favoriteList]; state.favoriteList = [songId, ...state.favoriteList];
localStorage.setItem('favoriteList', JSON.stringify(state.favoriteList)); localStorage.setItem('favoriteList', JSON.stringify(state.favoriteList));
@@ -111,7 +115,7 @@ const mutations = {
}, },
async removeFromFavorite(state: State, songId: number) { async removeFromFavorite(state: State, songId: number) {
try { try {
state.user && localStorage.getItem('token') && await likeSong(songId, false); state.user && localStorage.getItem('token') && (await likeSong(songId, false));
state.favoriteList = state.favoriteList.filter((id) => id !== songId); state.favoriteList = state.favoriteList.filter((id) => id !== songId);
localStorage.setItem('favoriteList', JSON.stringify(state.favoriteList)); localStorage.setItem('favoriteList', JSON.stringify(state.favoriteList));
} catch (error) { } catch (error) {
@@ -127,7 +131,7 @@ const mutations = {
applyTheme(state.theme); applyTheme(state.theme);
}, },
setShowUpdateModal(state, value) { setShowUpdateModal(state, value) {
state.showUpdateModal = value state.showUpdateModal = value;
}, },
logout(state: State) { logout(state: State) {
state.user = null; state.user = null;
@@ -158,13 +162,13 @@ const actions = {
}, },
async initializeFavoriteList({ state }: { state: State }) { async initializeFavoriteList({ state }: { state: State }) {
try { try {
if(state.user && localStorage.getItem('token')){ if (state.user && localStorage.getItem('token')) {
const res = await getLikedList(); const res = await getLikedList();
if (res.data?.ids) { if (res.data?.ids) {
state.favoriteList = res.data.ids.reverse(); state.favoriteList = res.data.ids.reverse();
localStorage.setItem('favoriteList', JSON.stringify(state.favoriteList)); localStorage.setItem('favoriteList', JSON.stringify(state.favoriteList));
} }
}else{ } else {
const localFavoriteList = localStorage.getItem('favoriteList'); const localFavoriteList = localStorage.getItem('favoriteList');
if (localFavoriteList) { if (localFavoriteList) {
state.favoriteList = JSON.parse(localFavoriteList); state.favoriteList = JSON.parse(localFavoriteList);

View File

@@ -43,7 +43,7 @@ interface Profile {
createTime: number; createTime: number;
nickname: string; nickname: string;
avatarUrl: string; avatarUrl: string;
experts: Experts; experts: any;
expertTags?: any; expertTags?: any;
djStatus: number; djStatus: number;
accountStatus: number; accountStatus: number;
@@ -79,8 +79,6 @@ interface Profile {
newFollows: number; newFollows: number;
} }
interface Experts {}
interface UserPoint { interface UserPoint {
userId: number; userId: number;
balance: number; balance: number;

View File

@@ -19,4 +19,4 @@ declare global {
interface Window { interface Window {
api: IElectronAPI; api: IElectronAPI;
} }
} }

View File

@@ -29,4 +29,4 @@ export const openDirectory = (path: string | undefined, message: MessageApi, sho
} else if (showTip) { } else if (showTip) {
message.info('目录不存在'); message.info('目录不存在');
} }
}; };

View File

@@ -1,4 +1,5 @@
import { computed } from 'vue'; import { computed } from 'vue';
import store from '@/store'; import store from '@/store';
// 设置歌手背景图片 // 设置歌手背景图片
@@ -71,4 +72,4 @@ export const isMobile = computed(() => {
return !!flag; return !!flag;
}); });
export const isElectron = (window as any).electron !== undefined; export const isElectron = (window as any).electron !== undefined;

View File

@@ -1,26 +1,27 @@
import axios, { InternalAxiosRequestConfig } from 'axios'; import axios, { InternalAxiosRequestConfig } from 'axios';
import { isElectron } from '.'; import { createDiscreteApi } from 'naive-ui';
import store from '@/store'; import store from '@/store';
import { createDiscreteApi } from 'naive-ui'
import { isElectron } from '.';
const { notification } = createDiscreteApi( const { notification } = createDiscreteApi(['notification']);
['notification']
)
let setData: any = null; let setData: any = null;
const getSetData = ()=>{ const getSetData = () => {
if (window.electron) { if (window.electron) {
setData = window.electron.ipcRenderer.sendSync('get-store-value', 'set'); setData = window.electron.ipcRenderer.sendSync('get-store-value', 'set');
} }
} };
getSetData() getSetData();
// 扩展请求配置接口 // 扩展请求配置接口
interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig { interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
retryCount?: number; retryCount?: number;
} }
const baseURL = window.electron ? `http://127.0.0.1:${setData?.musicApiPort}` : import.meta.env.VITE_API; const baseURL = window.electron
? `http://127.0.0.1:${setData?.musicApiPort}`
: import.meta.env.VITE_API;
const request = axios.create({ const request = axios.create({
baseURL, baseURL,
@@ -46,23 +47,23 @@ request.interceptors.request.use(
if (config.method === 'get') { if (config.method === 'get') {
config.params = { config.params = {
...config.params, ...config.params,
timestamp: Date.now(), timestamp: Date.now()
}; };
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
if (token) { if (token) {
config.params.cookie = token + ' os=pc;'; config.params.cookie = `${token} os=pc;`;
}else{ } else {
config.params.cookie = 'os=pc;'; config.params.cookie = 'os=pc;';
} }
} }
if(isElectron){ if (isElectron) {
const proxyConfig = setData?.proxyConfig const proxyConfig = setData?.proxyConfig;
if (proxyConfig?.enable && ['http', 'https'].includes(proxyConfig?.protocol)) { if (proxyConfig?.enable && ['http', 'https'].includes(proxyConfig?.protocol)) {
config.params.proxy = `${proxyConfig.protocol}://${proxyConfig.host}:${proxyConfig.port}` config.params.proxy = `${proxyConfig.protocol}://${proxyConfig.host}:${proxyConfig.port}`;
} }
if(setData.enableRealIP && setData.realIP){ if (setData.enableRealIP && setData.realIP) {
config.params.realIP = setData.realIP config.params.realIP = setData.realIP;
} }
} }
@@ -80,7 +81,7 @@ request.interceptors.response.use(
return response; return response;
}, },
async (error) => { async (error) => {
console.log('error',error) console.log('error', error);
const config = error.config as CustomAxiosRequestConfig; const config = error.config as CustomAxiosRequestConfig;
// 如果没有配置,直接返回错误 // 如果没有配置,直接返回错误
@@ -102,7 +103,7 @@ request.interceptors.response.use(
meta: '请重新登录', meta: '请重新登录',
duration: 2500, duration: 2500,
keepAliveOnHover: true keepAliveOnHover: true
}) });
// 延迟重试 // 延迟重试
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY)); await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));

View File

@@ -1,6 +1,8 @@
import axios from 'axios';
import config from '../../../package.json';
import { useDateFormat } from '@vueuse/core'; import { useDateFormat } from '@vueuse/core';
import axios from 'axios';
import config from '../../../package.json';
interface GithubReleaseInfo { interface GithubReleaseInfo {
tag_name: string; tag_name: string;
body: string; body: string;
@@ -39,15 +41,15 @@ export const getLatestReleaseInfo = async (): Promise<GithubReleaseInfo | null>
const apiUrls = [ const apiUrls = [
// 原始地址 // 原始地址
'https://api.github.com/repos/algerkong/AlgerMusicPlayer/releases/latest', 'https://api.github.com/repos/algerkong/AlgerMusicPlayer/releases/latest',
// 使用 ghproxy.com 代理 // 使用 ghproxy.com 代理
'https://www.ghproxy.cn/https://raw.githubusercontent.com/algerkong/AlgerMusicPlayer/dev_electron/package.json', 'https://www.ghproxy.cn/https://raw.githubusercontent.com/algerkong/AlgerMusicPlayer/dev_electron/package.json'
// 使用 gitee 镜像(如果有的话) // 使用 gitee 镜像(如果有的话)
// 'https://gitee.com/api/v5/repos/[用户名]/AlgerMusicPlayer/releases/latest' // 'https://gitee.com/api/v5/repos/[用户名]/AlgerMusicPlayer/releases/latest'
]; ];
if (token) { if (token) {
headers['Authorization'] = `token ${token}`; headers['Authorization'] = `token ${token}`;
} }
for (const url of apiUrls) { for (const url of apiUrls) {
@@ -58,7 +60,11 @@ export const getLatestReleaseInfo = async (): Promise<GithubReleaseInfo | null>
// 如果是 package.json直接读取版本号 // 如果是 package.json直接读取版本号
return { return {
tag_name: response.data.version, tag_name: response.data.version,
body:(await axios.get('https://raw.githubusercontent.com/algerkong/AlgerMusicPlayer/dev_electron/CHANGELOG.md')).data, body: (
await axios.get(
'https://raw.githubusercontent.com/algerkong/AlgerMusicPlayer/dev_electron/CHANGELOG.md'
)
).data,
html_url: 'https://github.com/algerkong/AlgerMusicPlayer/releases/latest', html_url: 'https://github.com/algerkong/AlgerMusicPlayer/releases/latest',
assets: [] assets: []
} as unknown as GithubReleaseInfo; } as unknown as GithubReleaseInfo;
@@ -86,10 +92,12 @@ export const formatDate = (dateStr: string): string => {
/** /**
* 检查更新 * 检查更新
*/ */
export const checkUpdate = async (currentVersion: string = config.version): Promise<UpdateResult | null> => { export const checkUpdate = async (
currentVersion: string = config.version
): Promise<UpdateResult | null> => {
try { try {
const releaseInfo = await getLatestReleaseInfo(); const releaseInfo = await getLatestReleaseInfo();
console.log('releaseInfo',releaseInfo) console.log('releaseInfo', releaseInfo);
if (!releaseInfo) { if (!releaseInfo) {
return null; return null;
} }
@@ -98,8 +106,8 @@ export const checkUpdate = async (currentVersion: string = config.version): Prom
if (latestVersion === currentVersion) { if (latestVersion === currentVersion) {
return null; return null;
} }
console.log('latestVersion',latestVersion) console.log('latestVersion', latestVersion);
console.log('currentVersion',currentVersion) console.log('currentVersion', currentVersion);
return { return {
hasUpdate: true, hasUpdate: true,
@@ -109,7 +117,7 @@ export const checkUpdate = async (currentVersion: string = config.version): Prom
tag_name: latestVersion, tag_name: latestVersion,
body: `## 更新内容\n\n- 版本: ${latestVersion}\n${releaseInfo.body}`, body: `## 更新内容\n\n- 版本: ${latestVersion}\n${releaseInfo.body}`,
html_url: releaseInfo.html_url, html_url: releaseInfo.html_url,
assets: releaseInfo.assets.map(asset => ({ assets: releaseInfo.assets.map((asset) => ({
browser_download_url: asset.browser_download_url, browser_download_url: asset.browser_download_url,
name: asset.name name: asset.name
})) }))
@@ -119,4 +127,4 @@ export const checkUpdate = async (currentVersion: string = config.version): Prom
console.error('检查更新失败:', error); console.error('检查更新失败:', error);
return null; return null;
} }
}; };

View File

@@ -54,7 +54,7 @@ const scrollbarRef = ref();
// 无限滚动相关 // 无限滚动相关
const pageSize = 16; const pageSize = 16;
const currentPage = ref(1); const currentPage = ref(1);
const props = defineProps({ const props = defineProps({
isComponent: { isComponent: {
type: Boolean, type: Boolean,

View File

@@ -34,10 +34,10 @@ import { onMounted, ref } from 'vue';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
import { getMusicDetail } from '@/api/music'; import { getMusicDetail } from '@/api/music';
import SongItem from '@/components/common/SongItem.vue';
import { useMusicHistory } from '@/hooks/MusicHistoryHook'; import { useMusicHistory } from '@/hooks/MusicHistoryHook';
import type { SongResult } from '@/type/music'; import type { SongResult } from '@/type/music';
import { setAnimationClass, setAnimationDelay } from '@/utils'; import { setAnimationClass, setAnimationDelay } from '@/utils';
import SongItem from '@/components/common/SongItem.vue';
defineOptions({ defineOptions({
name: 'History' name: 'History'

View File

@@ -18,7 +18,12 @@
</n-scrollbar> </n-scrollbar>
</div> </div>
<!-- 歌单列表 --> <!-- 歌单列表 -->
<n-scrollbar class="recommend" style="height: calc(100% - 55px)" :size="100" @scroll="handleScroll"> <n-scrollbar
class="recommend"
style="height: calc(100% - 55px)"
:size="100"
@scroll="handleScroll"
>
<div v-loading="loading" class="recommend-list"> <div v-loading="loading" class="recommend-list">
<div <div
v-for="(item, index) in recommendList" v-for="(item, index) in recommendList"

View File

@@ -49,7 +49,7 @@
:class="setAnimationClass('animate__bounceInRight')" :class="setAnimationClass('animate__bounceInRight')"
:style="setAnimationDelay(index, 50)" :style="setAnimationDelay(index, 50)"
> >
<SearchItem :item="item" /> <search-item :item="item" />
</div> </div>
</template> </template>
</template> </template>
@@ -67,10 +67,10 @@ import { useStore } from 'vuex';
import { getHotSearch } from '@/api/home'; import { getHotSearch } from '@/api/home';
import { getSearch } from '@/api/search'; import { getSearch } from '@/api/search';
import SearchItem from '@/components/common/SearchItem.vue';
import SongItem from '@/components/common/SongItem.vue'; import SongItem from '@/components/common/SongItem.vue';
import type { IHotSearch } from '@/type/search'; import type { IHotSearch } from '@/type/search';
import { isMobile, setAnimationClass, setAnimationDelay } from '@/utils'; import { isMobile, setAnimationClass, setAnimationDelay } from '@/utils';
import SearchItem from '@/components/common/SearchItem.vue';
defineOptions({ defineOptions({
name: 'Search' name: 'Search'

View File

@@ -22,12 +22,10 @@
</div> </div>
<n-switch v-model:value="setData.isProxy" /> <n-switch v-model:value="setData.isProxy" />
</div> --> </div> -->
<div class="set-item" v-if="isElectron"> <div v-if="isElectron" class="set-item">
<div> <div>
<div class="set-item-title">音乐API端口</div> <div class="set-item-title">音乐API端口</div>
<div class="set-item-content"> <div class="set-item-content">修改后需要重启应用</div>
修改后需要重启应用
</div>
</div> </div>
<n-input-number v-model:value="setData.musicApiPort" /> <n-input-number v-model:value="setData.musicApiPort" />
</div> </div>
@@ -55,7 +53,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="set-item" v-if="isElectron"> <div v-if="isElectron" class="set-item">
<div> <div>
<div class="set-item-title">下载目录</div> <div class="set-item-title">下载目录</div>
<div class="set-item-content"> <div class="set-item-content">
@@ -101,15 +99,17 @@
@click="openAuthor" @click="openAuthor"
> >
<div> <div>
<Coffee> <coffee>
<div> <div>
<div class="set-item-title">作者</div> <div class="set-item-title">作者</div>
<div class="set-item-content">algerkong 点个star🌟</div> <div class="set-item-content">algerkong 点个star🌟</div>
</div> </div>
</Coffee> </coffee>
</div> </div>
<div> <div>
<n-button size="small" type="primary" @click="openAuthor"><i class="ri-github-line"></i>前往github</n-button> <n-button size="small" type="primary" @click="openAuthor"
><i class="ri-github-line"></i>前往github</n-button
>
</div> </div>
</div> </div>
<div class="set-item"> <div class="set-item">
@@ -133,7 +133,7 @@
style="width: 160px" style="width: 160px"
/> />
</div> </div>
<div class="set-item" v-if="isElectron"> <div v-if="isElectron" class="set-item">
<div> <div>
<div class="set-item-title">关闭行为</div> <div class="set-item-title">关闭行为</div>
<div class="set-item-content"> <div class="set-item-content">
@@ -151,14 +151,14 @@
/> />
</div> </div>
<div class="set-item" v-if="isElectron"> <div v-if="isElectron" class="set-item">
<div> <div>
<div class="set-item-title">重启</div> <div class="set-item-title">重启</div>
<div class="set-item-content">重启应用</div> <div class="set-item-content">重启应用</div>
</div> </div>
<n-button type="primary" @click="restartApp">重启</n-button> <n-button type="primary" @click="restartApp">重启</n-button>
</div> </div>
<div class="set-item" v-if="isElectron"> <div v-if="isElectron" class="set-item">
<div> <div>
<div class="set-item-title">代理设置</div> <div class="set-item-title">代理设置</div>
<div class="set-item-content">无法访问音乐时可以开启代理</div> <div class="set-item-content">无法访问音乐时可以开启代理</div>
@@ -171,10 +171,13 @@
<n-button size="small" @click="showProxyModal = true">配置</n-button> <n-button size="small" @click="showProxyModal = true">配置</n-button>
</div> </div>
</div> </div>
<div class="set-item" v-if="isElectron"> <div v-if="isElectron" class="set-item">
<div> <div>
<div class="set-item-title">realIP</div> <div class="set-item-title">realIP</div>
<div class="set-item-content">由于限制,此项目在国外使用会受到限制可使用realIP参数,传进国内IP解决,:116.25.146.177 即可解决</div> <div class="set-item-content">
由于限制,此项目在国外使用会受到限制可使用realIP参数,传进国内IP解决,:116.25.146.177
即可解决
</div>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<n-switch v-model:value="setData.enableRealIP"> <n-switch v-model:value="setData.enableRealIP">
@@ -185,23 +188,22 @@
v-if="setData.enableRealIP" v-if="setData.enableRealIP"
v-model:value="setData.realIP" v-model:value="setData.realIP"
placeholder="realIP" placeholder="realIP"
@blur="validateAndSaveRealIP"
style="width: 200px" style="width: 200px"
@blur="validateAndSaveRealIP"
/> />
</div> </div>
</div> </div>
</div> </div>
<PlayBottom/> <play-bottom />
<n-modal <n-modal
v-model:show="showProxyModal" v-model:show="showProxyModal"
preset="dialog" preset="dialog"
title="代理设置" title="代理设置"
positive-text="确认" positive-text="确认"
negative-text="取消" negative-text="取消"
:show-icon="false"
@positive-click="handleProxyConfirm" @positive-click="handleProxyConfirm"
@negative-click="showProxyModal = false" @negative-click="showProxyModal = false"
:show-icon="false"
> >
<n-form <n-form
ref="formRef" ref="formRef"
@@ -225,7 +227,12 @@
<n-input v-model:value="proxyForm.host" placeholder="请输入代理地址" /> <n-input v-model:value="proxyForm.host" placeholder="请输入代理地址" />
</n-form-item> </n-form-item>
<n-form-item label="代理端口" path="port"> <n-form-item label="代理端口" path="port">
<n-input-number v-model:value="proxyForm.port" placeholder="请输入代理端口" :min="1" :max="65535" /> <n-input-number
v-model:value="proxyForm.port"
placeholder="请输入代理端口"
:min="1"
:max="65535"
/>
</n-form-item> </n-form-item>
</n-form> </n-form>
</n-modal> </n-modal>
@@ -233,16 +240,18 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, onMounted, watch } from 'vue';
import { useStore } from 'vuex';
import { useMessage } from 'naive-ui';
import type { FormRules } from 'naive-ui'; import type { FormRules } from 'naive-ui';
import { isElectron } from '@/utils'; import { useMessage } from 'naive-ui';
import { checkUpdate, UpdateResult } from '@/utils/update'; import { computed, onMounted, ref, watch } from 'vue';
import { selectDirectory, openDirectory } from '@/utils/fileOperation'; import { useStore } from 'vuex';
import config from '../../../../package.json';
import PlayBottom from '@/components/common/PlayBottom.vue';
import Coffee from '@/components/Coffee.vue'; import Coffee from '@/components/Coffee.vue';
import PlayBottom from '@/components/common/PlayBottom.vue';
import { isElectron } from '@/utils';
import { openDirectory, selectDirectory } from '@/utils/fileOperation';
import { checkUpdate, UpdateResult } from '@/utils/update';
import config from '../../../../package.json';
const store = useStore(); const store = useStore();
const checking = ref(false); const checking = ref(false);
@@ -277,9 +286,13 @@ const setData = computed(() => {
return data; return data;
}); });
watch(() => setData.value, (newVal) => { watch(
store.commit('setSetData', newVal) () => setData.value,
}, { deep: true }); (newVal) => {
store.commit('setSetData', newVal);
},
{ deep: true }
);
const isDarkTheme = computed({ const isDarkTheme = computed({
get: () => store.state.theme === 'dark', get: () => store.state.theme === 'dark',
@@ -317,7 +330,7 @@ const checkForUpdates = async (isClick = false) => {
}; };
const openReleasePage = () => { const openReleasePage = () => {
store.commit('setShowUpdateModal', true) store.commit('setShowUpdateModal', true);
}; };
const selectDownloadPath = async () => { const selectDownloadPath = async () => {
@@ -355,7 +368,8 @@ const proxyRules: FormRules = {
validator: (_rule, value) => { validator: (_rule, value) => {
if (!value) return false; if (!value) return false;
// 简单的IP或域名验证 // 简单的IP或域名验证
const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$|^localhost$|^[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/; const ipRegex =
/^(\d{1,3}\.){3}\d{1,3}$|^localhost$|^[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/;
return ipRegex.test(value); return ipRegex.test(value);
} }
}, },
@@ -385,15 +399,19 @@ onMounted(() => {
}); });
// 监听代理配置变化 // 监听代理配置变化
watch(() => setData.value.proxyConfig, (newVal) => { watch(
if (newVal) { () => setData.value.proxyConfig,
proxyForm.value = { (newVal) => {
protocol: newVal.protocol || 'http', if (newVal) {
host: newVal.host || '127.0.0.1', proxyForm.value = {
port: newVal.port || 7890 protocol: newVal.protocol || 'http',
}; host: newVal.host || '127.0.0.1',
} port: newVal.port || 7890
}, { immediate: true, deep: true }); };
}
},
{ immediate: true, deep: true }
);
const handleProxyConfirm = async () => { const handleProxyConfirm = async () => {
try { try {
@@ -436,15 +454,18 @@ const validateAndSaveRealIP = () => {
}; };
// 监听enableRealIP变化当关闭时清空realIP // 监听enableRealIP变化当关闭时清空realIP
watch(() => setData.value.enableRealIP, (newVal) => { watch(
if (!newVal) { () => setData.value.enableRealIP,
store.commit('setSetData', { (newVal) => {
...setData.value, if (!newVal) {
realIP: '', store.commit('setSetData', {
enableRealIP: false ...setData.value,
}); realIP: '',
enableRealIP: false
});
}
} }
}); );
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -89,7 +89,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref, watch, onBeforeUnmount } from 'vue'; import { computed, onBeforeUnmount, ref, watch } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useStore } from 'vuex'; import { useStore } from 'vuex';
@@ -125,7 +125,7 @@ onBeforeUnmount(() => {
const loadPage = async () => { const loadPage = async () => {
if (!mounted.value || !user.value) return; if (!mounted.value || !user.value) return;
try { try {
infoLoading.value = true; infoLoading.value = true;
@@ -154,15 +154,19 @@ const loadPage = async () => {
}; };
// 监听用户状态变化 // 监听用户状态变化
watch(() => store.state.user, (newUser) => { watch(
if (!mounted.value) return; () => store.state.user,
(newUser) => {
if (!newUser) { if (!mounted.value) return;
router.push('/login');
} else { if (!newUser) {
loadPage(); router.push('/login');
} } else {
}, { immediate: true }); loadPage();
}
},
{ immediate: true }
);
// 展示歌单 // 展示歌单
const showPlaylist = async (id: number, name: string) => { const showPlaylist = async (id: number, name: string) => {