diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index a6f34fe..0000000 --- a/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -dist -out -.gitignore diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index 387378b..0000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,137 +0,0 @@ -/* eslint-env node */ -require('@rushstack/eslint-patch/modern-module-resolution'); - -module.exports = { - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - '@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-underscore-dangle': 'off', - 'no-nested-ternary': 'off', - 'no-console': 'off', - 'no-await-in-loop': '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: { - 'max-classes-per-file': 'off', - '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' - } - } - ] -}; diff --git a/.github/ISSUE_TEMPLATE/bug-report.zh-CN.yml b/.github/ISSUE_TEMPLATE/bug-report.zh-CN.yml index 717e651..464b493 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.zh-CN.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.zh-CN.yml @@ -1,6 +1,6 @@ name: 反馈 Bug description: 通过 github 模板进行 Bug 反馈。 -title: "描述问题的标题" +title: '描述问题的标题' body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index f92d028..eecc553 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: true contact_links: - - name: - url: - about: \ No newline at end of file + - name: + url: + about: diff --git a/.github/ISSUE_TEMPLATE/feature-report.zh-CN.yml b/.github/ISSUE_TEMPLATE/feature-report.zh-CN.yml index 09e97f0..923247f 100644 --- a/.github/ISSUE_TEMPLATE/feature-report.zh-CN.yml +++ b/.github/ISSUE_TEMPLATE/feature-report.zh-CN.yml @@ -1,6 +1,6 @@ name: 反馈新功能 description: 通过 github 模板进行新功能反馈。 -title: "描述问题的标题" +title: '描述问题的标题' body: - type: markdown attributes: diff --git a/.github/issue-shoot.md b/.github/issue-shoot.md index 1d541e8..da19b91 100644 --- a/.github/issue-shoot.md +++ b/.github/issue-shoot.md @@ -1,4 +1,5 @@ ## IssueShoot + - 预估时长: {{ .duration }} - 期望完成时间: {{ .deadline }} - 开发难度: {{ .level }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 030db6b..1792305 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,11 +8,11 @@ on: jobs: release: runs-on: ${{ matrix.os }} - + strategy: matrix: os: [macos-latest, windows-latest, ubuntu-latest] - + steps: - name: Check out Git repository uses: actions/checkout@v4 @@ -85,4 +85,4 @@ jobs: draft: false prerelease: false env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b48b47e..548ce75 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -16,7 +16,7 @@ jobs: uses: actions/setup-node@v3 with: node-version: '18' - + - name: 创建环境变量文件 run: | echo "VITE_API=${{ secrets.VITE_API }}" > .env.production.local @@ -36,7 +36,7 @@ jobs: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SERVER_USERNAME }} key: ${{ secrets.DEPLOY_KEY }} - source: "out/renderer/*" + source: 'out/renderer/*' target: ${{ secrets.DEPLOY_PATH }} strip_components: 2 @@ -48,4 +48,4 @@ jobs: key: ${{ secrets.DEPLOY_KEY }} script: | cd ${{ secrets.DEPLOY_PATH }} - echo "部署完成于 $(date)" \ No newline at end of file + echo "部署完成于 $(date)" diff --git a/.prettierrc.yaml b/.prettierrc.yaml deleted file mode 100644 index b7283f3..0000000 --- a/.prettierrc.yaml +++ /dev/null @@ -1,5 +0,0 @@ -singleQuote: true -semi: true -printWidth: 100 -trailingComma: none -endOfLine: auto diff --git a/CHANGELOG.md b/CHANGELOG.md index 5981bd4..a8f9dbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,29 +1,32 @@ # 更新日志 ## v4.8.2 + ### 🎨 优化 + - 重新设计pc端歌词页面Mini播放栏 - 添加清除歌曲自定义解析功能 ### 🐛 Bug 修复 + - 修复歌曲单独解析失败问题 - 修复歌词页面加入歌单抽屉被遮挡问题 - ## v4.8.1 ### 🐛 Bug 修复 + - 修复无法快捷键调整问题 ### 🎨 优化 + - 优化音乐资源解析 - 去除无用代码,优化加载速度 - - ## v4.8.0 ### ✨ 新功能 + - 增强移动端播放页面效果,添加播放模式选择,添加横屏模式,添加播放列表功能 ([81b61e4](https://github.com/algerkong/AlgerMusicPlayer/commit/81b61e4)),([0d89e15](https://github.com/algerkong/AlgerMusicPlayer/commit/0d89e15)),([9345805](https://github.com/algerkong/AlgerMusicPlayer/commit/9345805)) - 优化移动端界面动画效果,播放栏,返回效果等一系列功能 - 添加下载管理页面, 引入文件类型检测库以支持多种音频格式,支持自定义文件名格式和下载路径配置 ([3ac3159](https://github.com/algerkong/AlgerMusicPlayer/commit/3ac3159)),([b203077](https://github.com/algerkong/AlgerMusicPlayer/commit/b203077)) @@ -34,7 +37,8 @@ - 添加歌词时间矫正功能,支持增加和减少矫正时间 ([c975344](https://github.com/algerkong/AlgerMusicPlayer/commit/c975344)) ### 🐛 Bug 修复 -- 修复音频初始化音量问题,完善翻译 ([#320](https://github.com/algerkong/AlgerMusicPlayer/pull/320)) 感谢[Qumo](https://github.com/Hellodwadawd12312312)的pr + +- 修复音频初始化音量问题,完善翻译 ([#320](https://github.com/algerkong/AlgerMusicPlayer/pull/320)) 感谢[Qumo](https://github.com/Hellodwadawd12312312)的pr - 重构每日推荐数据加载逻辑,提取为独立函数并优化用户状态判断 ([5e704a1](https://github.com/algerkong/AlgerMusicPlayer/commit/5e704a1)) - 修复刷新后第一次播放出现的无法播放问题 ([6f1909a](https://github.com/algerkong/AlgerMusicPlayer/commit/6f1909a)) - 修复更多设置弹窗被歌词窗口遮挡问题,并优化为互斥弹窗,优化样式 ([62e5166](https://github.com/algerkong/AlgerMusicPlayer/commit/62e5166)) @@ -42,8 +46,8 @@ - 修复音频服务相关问题 ([090103b](https://github.com/algerkong/AlgerMusicPlayer/commit/090103b)),([5ee60d7](https://github.com/algerkong/AlgerMusicPlayer/commit/5ee60d7)) - 修复播放栏无法控制隐藏问题 ([d227ac8](https://github.com/algerkong/AlgerMusicPlayer/commit/d227ac8)) - ### 🎨 优化 + - 优化歌曲列表组件布局([fabcf28](https://github.com/algerkong/AlgerMusicPlayer/commit/fabcf28)) - 重构播放控制逻辑,添加播放进度恢复功能并清理无用代码 ([b9c38d2](https://github.com/algerkong/AlgerMusicPlayer/commit/b9c38d2)) - 优化提示组件,支持位置和图标显示选项 ([155bdf2](https://github.com/algerkong/AlgerMusicPlayer/commit/155bdf2)) @@ -53,7 +57,9 @@ - 代码优化 ## 赞赏支持☕️ + [赞赏列表](http://donate.alger.fun/) + diff --git a/DEV.md b/DEV.md index 0b98daf..836903f 100644 --- a/DEV.md +++ b/DEV.md @@ -105,18 +105,18 @@ AlgerMusicPlayer/ 渲染进程是基于 Vue 3 的前端应用,负责 UI 渲染和用户交互。 - **components/**: 包含各种 UI 组件 -  - **common/**: 通用组件 -  - **home/**: 首页相关组件 -  - **lyric/**: 歌词显示组件 -  - **settings/**: 设置界面组件 -  - **MusicList.vue**: 音乐列表组件 -  - **MvPlayer.vue**: MV 播放器 -  - **EQControl.vue**: 均衡器控制 -  - **...**: 其他组件 +   - **common/**: 通用组件 +   - **home/**: 首页相关组件 +   - **lyric/**: 歌词显示组件 +   - **settings/**: 设置界面组件 +   - **MusicList.vue**: 音乐列表组件 +   - **MvPlayer.vue**: MV 播放器 +   - **EQControl.vue**: 均衡器控制 +   - **...**: 其他组件 - **store/**: Pinia 状态管理 -  - **modules/**: 各功能模块的状态管理 -  - **index.ts**: 状态管理入口 +   - **modules/**: 各功能模块的状态管理 +   - **index.ts**: 状态管理入口 - **views/**: 页面视图组件 @@ -142,25 +142,28 @@ AlgerMusicPlayer/ - 避免使用枚举,使用 const 对象代替 - 使用 tailwind 实现响应式设计 - ### 如何启动? + 安装依赖(最好使用node18+): + ``` npm install ``` - #### 桌面端开发 + 启动桌面端开发: + ``` npm run dev ``` - #### 网页端开发 -如果只启动网页端开发,需要自己部署服务 netease-cloud-music-api + +如果只启动网页端开发,需要自己部署服务 netease-cloud-music-api 需要复制一份 `.env.development.local` 到 `src/renderer` 下 + ``` # .env.development.local @@ -171,6 +174,7 @@ VITE_API_MUSIC = *** ``` 启动web端开发: + ``` npm run dev:web ``` @@ -178,15 +182,17 @@ npm run dev:web ### 打包 打包桌面端: + ``` npm run build:win ``` + 打包后的文件在 /dist 下 - - 打包网页端: + ``` npm run build ``` -打包后的文件在 /out/renderer 下 \ No newline at end of file + +打包后的文件在 /out/renderer 下 diff --git a/README.md b/README.md index 9969597..274392f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -

🎵 Alger Music Player

@@ -23,10 +22,10 @@ Featured|HelloGitHub
- [项目下安装以及常用问题文档](https://www.yuque.com/alger-pfg5q/ip4f1a/bmgmfmghnhgwghkm?singleDoc#) 主要功能如下 + - 🎵 音乐推荐 - 🔐 网易云账号登录与同步 - 📝 功能 @@ -41,7 +40,6 @@ - 迷你模式 - 状态栏控制 - 多语言支持 - - 🎼 音乐功能 - 支持歌单、MV、专辑等完整音乐服务 - 音乐资源解析(基于 @unblockneteasemusic/server) @@ -56,12 +54,15 @@ - 全平台适配(Desktop & Web & Mobile Web & Android<测试> & ios<后续>) ## 项目简介 - 一个第三方音乐播放器、本地服务、桌面歌词、音乐下载、最高音质 + +一个第三方音乐播放器、本地服务、桌面歌词、音乐下载、最高音质 ## 预览地址 + [http://music.alger.fun/](http://music.alger.fun/) ## 软件截图 + ![首页白](./docs/image.png) ![首页黑](./docs/image3.png) ![歌词](./docs/image6.png) @@ -70,31 +71,31 @@ ![音乐远程控制](./docs/image5.png) ## 项目启动 + ```bash npm install npm run dev ``` + ## 开发文档 + 点击这里[开发文档](./DEV.md) - - - ## 赞赏☕️ + [赞赏列表](http://donate.alger.fun/) -| 微信赞赏 | 支付宝赞赏 | +| 微信赞赏 | 支付宝赞赏 | | :--------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------: | | WeChat QRcode
喝点咖啡继续干 | Wechat QRcode
来包辣条吧~ | - ## 项目统计 + [![Stargazers over time](https://starchart.cc/algerkong/AlgerMusicPlayer.svg?variant=adaptive)](https://starchart.cc/algerkong/AlgerMusicPlayer) -![Alt](https://repobeats.axiom.co/api/embed/c4d01b3632e241c90cdec9508dfde86a7f54c9f5.svg "Repobeats analytics image") - - +![Alt](https://repobeats.axiom.co/api/embed/c4d01b3632e241c90cdec9508dfde86a7f54c9f5.svg 'Repobeats analytics image') ## 欢迎提Issues ## 声明 + 本软件仅用于学习交流,禁止用于商业用途,否则后果自负。 希望大家还是要多多支持官方正版,此软件仅用作开发教学。 diff --git a/electron.vite.config.ts b/electron.vite.config.ts index fb2e11a..7371e47 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -39,7 +39,7 @@ export default defineConfig({ ], publicDir: resolve('resources'), server: { - host: '0.0.0.0', + host: '0.0.0.0' } } }); diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..4a363ce --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,249 @@ +import js from '@eslint/js'; +import typescript from '@typescript-eslint/eslint-plugin'; +import typescriptParser from '@typescript-eslint/parser'; +import vue from 'eslint-plugin-vue'; +import vueParser from 'vue-eslint-parser'; +import prettier from 'eslint-plugin-prettier'; +import simpleImportSort from 'eslint-plugin-simple-import-sort'; +import vueScopedCss from 'eslint-plugin-vue-scoped-css'; +import globals from 'globals'; + +export default [ + // 忽略文件配置 + { + ignores: ['node_modules/**', 'dist/**', 'out/**', '.gitignore'] + }, + + // 基础 JavaScript 配置 + js.configs.recommended, + + // JavaScript 文件配置 + { + files: ['**/*.js', '**/*.cjs', '**/*.mjs'], + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + globals: { + ...globals.node, + ...globals.browser + } + }, + rules: { + 'no-console': 'off', + 'no-undef': 'error' + } + }, + + // TypeScript 文件配置 + { + files: ['**/*.ts', '**/*.tsx'], + languageOptions: { + parser: typescriptParser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + allowImportExportEverywhere: true, + ecmaFeatures: { + jsx: true + } + }, + globals: { + ...globals.node, + ...globals.browser, + // Vue 3 特定全局变量 + defineProps: 'readonly', + defineEmits: 'readonly', + // TypeScript 全局类型 + NodeJS: 'readonly', + ScrollBehavior: 'readonly' + } + }, + plugins: { + '@typescript-eslint': typescript, + 'simple-import-sort': simpleImportSort + }, + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + argsIgnorePattern: + '^_|^e$|^event$|^error$|^data$|^callback$|^args$|^value$|^loading$|^width$|^height$|^showPlaylist$|^id$|^enabledSources$|^progress$|^status$|^success$|^filePath$|^locale$|^channel$|^listener$|^url$|^songId$|^delta$|^item$|^err$|^gradient$|^theme$', + varsIgnorePattern: + '^_|^e$|^event$|^error$|^data$|^callback$|^args$|^NONE$|^TIME$|^SONGS$|^PLAYLIST_END$|^c$|^l$|^Window$|^key$|^color$', + ignoreRestSiblings: true + } + ], + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/ban-types': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + 'simple-import-sort/imports': 'error', + 'simple-import-sort/exports': 'error', + 'no-console': 'off', + 'no-unused-vars': [ + 'warn', + { + argsIgnorePattern: + '^_|^e$|^event$|^error$|^data$|^callback$|^args$|^value$|^loading$|^width$|^height$|^showPlaylist$|^id$|^enabledSources$|^progress$|^status$|^success$|^filePath$|^locale$|^channel$|^listener$|^url$|^songId$|^delta$|^item$|^err$|^gradient$|^theme$', + varsIgnorePattern: + '^_|^e$|^event$|^error$|^data$|^callback$|^args$|^NONE$|^TIME$|^SONGS$|^PLAYLIST_END$|^c$|^l$|^Window$|^key$|^color$', + ignoreRestSiblings: true + } + ], + 'no-use-before-define': 'off', + 'max-classes-per-file': 'off', + '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' + } + }, + + // Vue 文件配置 + { + files: ['**/*.vue'], + languageOptions: { + parser: vueParser, + parserOptions: { + parser: typescriptParser, + ecmaVersion: 'latest', + sourceType: 'module', + allowImportExportEverywhere: true + }, + globals: { + ...globals.browser, + // Vue 3 特定全局变量 + defineProps: 'readonly', + defineEmits: 'readonly', + // Vue 3 Composition API (如果使用了 unplugin-auto-import) + ref: 'readonly', + reactive: 'readonly', + computed: 'readonly', + watch: 'readonly', + watchEffect: 'readonly', + onMounted: 'readonly', + onUnmounted: 'readonly', + onBeforeUnmount: 'readonly', + nextTick: 'readonly', + inject: 'readonly', + provide: 'readonly', + // Naive UI (如果使用了 unplugin-auto-import) + useDialog: 'readonly', + useMessage: 'readonly', + // TypeScript 全局类型 + NodeJS: 'readonly', + ScrollBehavior: 'readonly' + } + }, + plugins: { + vue, + '@typescript-eslint': typescript, + prettier, + 'simple-import-sort': simpleImportSort, + 'vue-scoped-css': vueScopedCss + }, + rules: { + // Vue 3 推荐规则 + 'vue/no-unused-vars': 'error', + 'vue/no-unused-components': 'error', + 'vue/no-multiple-template-root': 'off', + 'vue/no-v-model-argument': 'off', + 'vue/require-default-prop': 'off', + 'vue/multi-word-component-names': 'off', + 'vue/component-name-in-template-casing': ['error', 'kebab-case'], + 'vue/no-reserved-props': 'off', + 'vue/no-v-html': 'off', + 'vue/first-attribute-linebreak': 'off', + 'vue-scoped-css/enforce-style-type': [ + 'error', + { + allows: ['scoped'] + } + ], + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + 'prettier/prettier': 'error', + 'simple-import-sort/imports': 'error', + 'simple-import-sort/exports': 'error' + } + }, + + // TypeScript 类型定义文件配置 + { + files: ['**/*.d.ts'], + rules: { + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-empty-interface': 'off' + } + }, + + // JavaScript 第三方库文件配置 + { + files: ['**/assets/**/*.js', '**/vendor/**/*.js', '**/lib/**/*.js'], + rules: { + 'no-unused-vars': 'off', + 'no-redeclare': 'off', + 'no-self-assign': 'off', + 'no-undef': 'off' + } + }, + + // 通用规则 + { + files: ['**/*.js', '**/*.ts', '**/*.vue'], + rules: { + 'no-console': 'off', + 'no-underscore-dangle': 'off', + 'no-nested-ternary': 'off', + 'no-await-in-loop': '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', + 'class-methods-use-this': 'off', + 'no-case-declarations': 'off', + 'no-unused-vars': [ + 'warn', + { + argsIgnorePattern: + '^_|^e$|^event$|^error$|^data$|^callback$|^args$|^value$|^loading$|^width$|^height$|^showPlaylist$|^id$|^enabledSources$|^progress$|^status$|^success$|^filePath$|^locale$|^channel$|^listener$|^url$|^songId$|^delta$|^item$|^err$|^gradient$|^theme$', + varsIgnorePattern: + '^_|^e$|^event$|^error$|^data$|^callback$|^args$|^NONE$|^TIME$|^SONGS$|^PLAYLIST_END$|^c$|^l$|^Window$|^key$|^color$', + ignoreRestSiblings: true + } + ] + } + } +]; diff --git a/package.json b/package.json index 5756f12..682baac 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "prepare": "husky", "format": "prettier --write .", - "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix", + "lint": "eslint . --fix", "typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false", "typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false", "typecheck": "npm run typecheck:node && npm run typecheck:web", @@ -44,6 +44,7 @@ "@electron-toolkit/eslint-config": "^2.1.0", "@electron-toolkit/eslint-config-ts": "^3.1.0", "@electron-toolkit/tsconfig": "^1.0.1", + "@eslint/js": "^9.31.0", "@rushstack/eslint-patch": "^1.10.3", "@tailwindcss/postcss7-compat": "^2.2.4", "@types/howler": "^2.2.12", @@ -66,12 +67,13 @@ "electron-builder": "^25.1.8", "electron-vite": "^3.1.0", "eslint": "^9.0.0", - "eslint-config-prettier": "^10.1.2", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-simple-import-sort": "^12.0.0", - "eslint-plugin-vue": "^10.0.0", - "eslint-plugin-vue-scoped-css": "^2.9.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-prettier": "^5.5.3", + "eslint-plugin-simple-import-sort": "^12.1.1", + "eslint-plugin-vue": "^10.3.0", + "eslint-plugin-vue-scoped-css": "^2.11.0", + "globals": "^16.3.0", "howler": "^2.2.4", "lodash": "^4.17.21", "marked": "^15.0.4", @@ -79,7 +81,7 @@ "pinia": "^3.0.1", "pinyin-match": "^1.2.6", "postcss": "^8.5.3", - "prettier": "^3.3.2", + "prettier": "^3.6.2", "remixicon": "^4.6.0", "sass": "^1.86.0", "tailwindcss": "^3.4.17", diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..13864ff --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,10 @@ +/** + * @type {import('prettier').Config} + */ +module.exports = { + singleQuote: true, + semi: true, + printWidth: 100, + trailingComma: 'none', + endOfLine: 'auto' +}; diff --git a/resources/html/remote-control.html b/resources/html/remote-control.html index 59c2e7a..27edbd4 100644 --- a/resources/html/remote-control.html +++ b/resources/html/remote-control.html @@ -1,486 +1,505 @@ - + - - - - AlgerMusicPlayer 远程控制 - - - -
-

AlgerMusicPlayer 远程控制

-
-
-
-
- 封面 -
-

未在播放

-

--

-
未播放
+ .status-bar { + text-align: center; + padding: 8px 16px; + font-size: 14px; + color: var(--text-secondary); + background-color: var(--background-color); + border-top: 1px solid var(--medium-gray); + position: sticky; + bottom: 0; + } + + .status-message { + display: inline-block; + transition: opacity 0.3s ease; + } + + .status-message.fade { + opacity: 0; + } + + .refresh-button { + color: var(--primary-color); + background: none; + border: none; + font-size: 14px; + cursor: pointer; + padding: 0; + margin-left: 8px; + } + + @media (max-width: 350px) { + .controls, + .extra-controls { + gap: 8px; + } + + .btn { + padding: 12px 0; + font-size: 12px; + } + + .btn svg { + width: 20px; + height: 20px; + margin-bottom: 6px; + } + } + + + +
+

AlgerMusicPlayer 远程控制

+
+ +
+
+
+ 封面 +
+

未在播放

+

--

+
未播放
+
+
+
+ +
+
+ + + +
+ +
+ + +
+
+ +
+
+ +
-
-
- - - -
- -
- - -
+
+ 准备就绪
-
-
- - -
-
-
+ - - \ No newline at end of file + + + diff --git a/src/i18n/lang/en-US/comp.ts b/src/i18n/lang/en-US/comp.ts index 36a4b54..f0bfb55 100644 --- a/src/i18n/lang/en-US/comp.ts +++ b/src/i18n/lang/en-US/comp.ts @@ -174,7 +174,8 @@ export default { noTasks: 'No import tasks', clearTasks: 'Clear Tasks', clearTasksConfirmTitle: 'Confirm Clear', - clearTasksConfirmContent: 'Are you sure you want to clear all import task records? This action cannot be undone.', + clearTasksConfirmContent: + 'Are you sure you want to clear all import task records? This action cannot be undone.', confirm: 'Confirm', cancel: 'Cancel', clearTasksSuccess: 'Task list cleared', diff --git a/src/i18n/lang/en-US/player.ts b/src/i18n/lang/en-US/player.ts index b87144f..279d367 100644 --- a/src/i18n/lang/en-US/player.ts +++ b/src/i18n/lang/en-US/player.ts @@ -63,7 +63,7 @@ export default { favorite: 'Favorite {name}', unFavorite: 'Unfavorite {name}', playbackSpeed: 'Playback Speed', - advancedControls: 'Advanced Controls', + advancedControls: 'Advanced Controls' }, eq: { title: 'Equalizer', @@ -117,6 +117,7 @@ export default { cleared: 'Playlist cleared', empty: 'Playlist is empty', clearConfirmTitle: 'Clear Playlist', - clearConfirmContent: 'This will clear all songs in the playlist and stop the current playback. Continue?' + clearConfirmContent: + 'This will clear all songs in the playlist and stop the current playback. Continue?' } }; diff --git a/src/i18n/lang/en-US/settings.ts b/src/i18n/lang/en-US/settings.ts index 04f2e62..97601b8 100644 --- a/src/i18n/lang/en-US/settings.ts +++ b/src/i18n/lang/en-US/settings.ts @@ -66,11 +66,13 @@ export default { configureMusicSources: 'Configure Sources', selectedMusicSources: 'Selected sources:', noMusicSources: 'No sources selected', - gdmusicInfo: 'GD Music Station intelligently resolves music from multiple platforms automatically', + gdmusicInfo: + 'GD Music Station intelligently resolves music from multiple platforms automatically', autoPlay: 'Auto Play', autoPlayDesc: 'Auto resume playback when reopening the app', - showStatusBar: "Show Status Bar", - showStatusBarContent: "You can display the music control function in your mac status bar (effective after a restart)" + showStatusBar: 'Show Status Bar', + showStatusBarContent: + 'You can display the music control function in your mac status bar (effective after a restart)' }, application: { closeAction: 'Close Action', @@ -232,9 +234,9 @@ export default { 'spotify-green': 'Spotify Green', 'apple-blue': 'Apple Blue', 'youtube-red': 'YouTube Red', - 'orange': 'Vibrant Orange', - 'purple': 'Mystic Purple', - 'pink': 'Cherry Pink' + orange: 'Vibrant Orange', + purple: 'Mystic Purple', + pink: 'Cherry Pink' }, tooltips: { openColorPicker: 'Open Color Picker', @@ -280,6 +282,6 @@ export default { addIp: 'Add IP', emptyListHint: 'Empty list means allow all IPs', saveSuccess: 'Remote control settings saved', - accessInfo: 'Remote control access address:', + accessInfo: 'Remote control access address:' } }; diff --git a/src/i18n/lang/en-US/songItem.ts b/src/i18n/lang/en-US/songItem.ts index c84e39a..969b776 100644 --- a/src/i18n/lang/en-US/songItem.ts +++ b/src/i18n/lang/en-US/songItem.ts @@ -8,7 +8,7 @@ export default { unfavorite: 'Unlike', removeFromPlaylist: 'Remove from Playlist', dislike: 'Dislike', - undislike: 'Undislike', + undislike: 'Undislike' }, message: { downloading: 'Downloading, please wait...', @@ -18,7 +18,7 @@ export default { getUrlFailed: 'Failed to get music download URL, please check if logged in' }, dialog: { - dislike:{ + dislike: { title: 'Dislike', content: 'Are you sure you want to dislike this song?', positiveText: 'Dislike', diff --git a/src/i18n/lang/en-US/user.ts b/src/i18n/lang/en-US/user.ts index 90bf1d8..e07970f 100644 --- a/src/i18n/lang/en-US/user.ts +++ b/src/i18n/lang/en-US/user.ts @@ -20,14 +20,14 @@ export default { noFollowings: 'No Followings', loadMore: 'Load More', noSignature: 'This guy is lazy, nothing left', - userFollowsTitle: '\'s Followings', + userFollowsTitle: "'s Followings", myFollowsTitle: 'My Followings' }, follower: { title: 'Follower List', noFollowers: 'No Followers', loadMore: 'Load More', - userFollowersTitle: '\'s Followers', + userFollowersTitle: "'s Followers", myFollowersTitle: 'My Followers' }, detail: { @@ -38,7 +38,7 @@ export default { artist: 'Artist', noSignature: 'This guy is lazy, nothing left', invalidUserId: 'Invalid User ID', - noRecordPermission: '{name} doesn\'t let you see your listening history' + noRecordPermission: "{name} doesn't let you see your listening history" }, message: { loadFailed: 'Failed to load user page', diff --git a/src/i18n/lang/zh-CN/player.ts b/src/i18n/lang/zh-CN/player.ts index 3b194c2..c7b4497 100644 --- a/src/i18n/lang/zh-CN/player.ts +++ b/src/i18n/lang/zh-CN/player.ts @@ -64,7 +64,7 @@ export default { unFavorite: '已取消收藏{name}', miniPlayBar: '迷你播放栏', playbackSpeed: '播放速度', - advancedControls: '更多设置s', + advancedControls: '更多设置s' }, eq: { title: '均衡器', diff --git a/src/i18n/lang/zh-CN/settings.ts b/src/i18n/lang/zh-CN/settings.ts index a932398..a5684c4 100644 --- a/src/i18n/lang/zh-CN/settings.ts +++ b/src/i18n/lang/zh-CN/settings.ts @@ -70,7 +70,7 @@ export default { autoPlay: '自动播放', autoPlayDesc: '重新打开应用时是否自动继续播放', showStatusBar: '是否显示状态栏控制功能', - showStatusBarContent: '可以在您的mac状态栏显示音乐控制功能(重启后生效)', + showStatusBarContent: '可以在您的mac状态栏显示音乐控制功能(重启后生效)' }, application: { closeAction: '关闭行为', @@ -232,9 +232,9 @@ export default { 'spotify-green': 'Spotify 绿', 'apple-blue': '苹果蓝', 'youtube-red': 'YouTube 红', - 'orange': '活力橙', - 'purple': '神秘紫', - 'pink': '樱花粉' + orange: '活力橙', + purple: '神秘紫', + pink: '樱花粉' }, tooltips: { openColorPicker: '打开色板', @@ -280,6 +280,6 @@ export default { addIp: '添加IP', emptyListHint: '空列表表示允许所有IP访问', saveSuccess: '远程控制设置已保存', - accessInfo: '远程控制访问地址:', + accessInfo: '远程控制访问地址:' } }; diff --git a/src/i18n/lang/zh-CN/songItem.ts b/src/i18n/lang/zh-CN/songItem.ts index 9e71328..c6e98f4 100644 --- a/src/i18n/lang/zh-CN/songItem.ts +++ b/src/i18n/lang/zh-CN/songItem.ts @@ -8,7 +8,7 @@ export default { unfavorite: '取消喜欢', removeFromPlaylist: '从歌单中删除', dislike: '不喜欢', - undislike: '取消不喜欢', + undislike: '取消不喜欢' }, message: { downloading: '正在下载中,请稍候...', diff --git a/src/i18n/lang/zh-Hant/artist.ts b/src/i18n/lang/zh-Hant/artist.ts index a99c6da..e9461d1 100644 --- a/src/i18n/lang/zh-Hant/artist.ts +++ b/src/i18n/lang/zh-Hant/artist.ts @@ -2,4 +2,4 @@ export default { hotSongs: '熱門歌曲', albums: '專輯', description: '藝人介紹' -}; \ No newline at end of file +}; diff --git a/src/i18n/lang/zh-Hant/common.ts b/src/i18n/lang/zh-Hant/common.ts index 3082b72..ed44bee 100644 --- a/src/i18n/lang/zh-Hant/common.ts +++ b/src/i18n/lang/zh-Hant/common.ts @@ -53,4 +53,4 @@ export default { play: '播放', favorite: '收藏' } -}; \ No newline at end of file +}; diff --git a/src/i18n/lang/zh-Hant/comp.ts b/src/i18n/lang/zh-Hant/comp.ts index de165fc..6c72474 100644 --- a/src/i18n/lang/zh-Hant/comp.ts +++ b/src/i18n/lang/zh-Hant/comp.ts @@ -187,4 +187,4 @@ export default { mv: 'MV', home: '首頁', search: '搜尋' -}; \ No newline at end of file +}; diff --git a/src/i18n/lang/zh-Hant/donation.ts b/src/i18n/lang/zh-Hant/donation.ts index efc894f..00e4489 100644 --- a/src/i18n/lang/zh-Hant/donation.ts +++ b/src/i18n/lang/zh-Hant/donation.ts @@ -5,4 +5,4 @@ export default { toDonateList: '請我喝杯咖啡', noMessage: '暫無留言', title: '捐贈列表' -}; \ No newline at end of file +}; diff --git a/src/i18n/lang/zh-Hant/download.ts b/src/i18n/lang/zh-Hant/download.ts index 05b458c..0b310a6 100644 --- a/src/i18n/lang/zh-Hant/download.ts +++ b/src/i18n/lang/zh-Hant/download.ts @@ -84,4 +84,4 @@ export default { albumName: '專輯名' } } -}; \ No newline at end of file +}; diff --git a/src/i18n/lang/zh-Hant/favorite.ts b/src/i18n/lang/zh-Hant/favorite.ts index fc89f13..4441c40 100644 --- a/src/i18n/lang/zh-Hant/favorite.ts +++ b/src/i18n/lang/zh-Hant/favorite.ts @@ -10,4 +10,4 @@ export default { selectSongsFirst: '請先選擇要下載的歌曲', descending: '降', ascending: '升' -}; \ No newline at end of file +}; diff --git a/src/i18n/lang/zh-Hant/history.ts b/src/i18n/lang/zh-Hant/history.ts index 79cddb9..5904ca1 100644 --- a/src/i18n/lang/zh-Hant/history.ts +++ b/src/i18n/lang/zh-Hant/history.ts @@ -2,4 +2,4 @@ export default { title: '播放歷史', playCount: '{count}', getHistoryFailed: '取得歷史記錄失敗' -}; \ No newline at end of file +}; diff --git a/src/i18n/lang/zh-Hant/index.ts b/src/i18n/lang/zh-Hant/index.ts index 07c725f..3619c66 100644 --- a/src/i18n/lang/zh-Hant/index.ts +++ b/src/i18n/lang/zh-Hant/index.ts @@ -26,4 +26,4 @@ export default { download, comp, artist -}; \ No newline at end of file +}; diff --git a/src/i18n/lang/zh-Hant/login.ts b/src/i18n/lang/zh-Hant/login.ts index 7eea218..7c8867e 100644 --- a/src/i18n/lang/zh-Hant/login.ts +++ b/src/i18n/lang/zh-Hant/login.ts @@ -19,4 +19,4 @@ export default { loadError: '載入登入資訊時出錯', qrCheckError: '檢查二維碼狀態時出錯' } -}; \ No newline at end of file +}; diff --git a/src/i18n/lang/zh-Hant/player.ts b/src/i18n/lang/zh-Hant/player.ts index 2f66ef9..5e3ae78 100644 --- a/src/i18n/lang/zh-Hant/player.ts +++ b/src/i18n/lang/zh-Hant/player.ts @@ -64,7 +64,7 @@ export default { unFavorite: '已取消收藏{name}', miniPlayBar: '迷你播放列', playbackSpeed: '播放速度', - advancedControls: '更多設定s', + advancedControls: '更多設定s' }, eq: { title: '等化器', @@ -120,4 +120,4 @@ export default { clearConfirmTitle: '清空播放清單', clearConfirmContent: '這將清空所有播放清單中的歌曲並停止目前播放。是否繼續?' } -}; \ No newline at end of file +}; diff --git a/src/i18n/lang/zh-Hant/search.ts b/src/i18n/lang/zh-Hant/search.ts index 24ea84b..93b7f3b 100644 --- a/src/i18n/lang/zh-Hant/search.ts +++ b/src/i18n/lang/zh-Hant/search.ts @@ -24,4 +24,4 @@ export default { mv: 'MV', bilibili: 'B站' } -}; \ No newline at end of file +}; diff --git a/src/i18n/lang/zh-Hant/settings.ts b/src/i18n/lang/zh-Hant/settings.ts index 15b9710..6d7d877 100644 --- a/src/i18n/lang/zh-Hant/settings.ts +++ b/src/i18n/lang/zh-Hant/settings.ts @@ -70,7 +70,7 @@ export default { autoPlay: '自動播放', autoPlayDesc: '重新開啟應用程式時是否自動繼續播放', showStatusBar: '是否顯示狀態列控制功能', - showStatusBarContent: '可以在您的mac狀態列顯示音樂控制功能(重啟後生效)', + showStatusBarContent: '可以在您的mac狀態列顯示音樂控制功能(重啟後生效)' }, application: { closeAction: '關閉行為', @@ -212,9 +212,9 @@ export default { 'spotify-green': 'Spotify 綠', 'apple-blue': '蘋果藍', 'youtube-red': 'YouTube 紅', - 'orange': '活力橙', - 'purple': '神秘紫', - 'pink': '櫻花粉' + orange: '活力橙', + purple: '神秘紫', + pink: '櫻花粉' }, tooltips: { openColorPicker: '開啟色板', @@ -222,4 +222,4 @@ export default { }, placeholder: '#1db954' } -}; \ No newline at end of file +}; diff --git a/src/i18n/lang/zh-Hant/songItem.ts b/src/i18n/lang/zh-Hant/songItem.ts index 7680537..81e7da5 100644 --- a/src/i18n/lang/zh-Hant/songItem.ts +++ b/src/i18n/lang/zh-Hant/songItem.ts @@ -8,7 +8,7 @@ export default { unfavorite: '取消喜歡', removeFromPlaylist: '從播放清單中刪除', dislike: '不喜歡', - undislike: '取消不喜歡', + undislike: '取消不喜歡' }, message: { downloading: '正在下載中,請稍候...', @@ -25,4 +25,4 @@ export default { negativeText: '取消' } } -}; \ No newline at end of file +}; diff --git a/src/i18n/lang/zh-Hant/user.ts b/src/i18n/lang/zh-Hant/user.ts index 4cd795d..61aeaac 100644 --- a/src/i18n/lang/zh-Hant/user.ts +++ b/src/i18n/lang/zh-Hant/user.ts @@ -45,4 +45,4 @@ export default { deleteSuccess: '刪除成功', deleteFailed: '刪除失敗' } -}; \ No newline at end of file +}; diff --git a/src/locale/zh-CN.json b/src/locale/zh-CN.json index a5563da..87f105b 100644 --- a/src/locale/zh-CN.json +++ b/src/locale/zh-CN.json @@ -55,4 +55,4 @@ "mobileUnavailable": "此设置仅在移动端可用" } } -} \ No newline at end of file +} diff --git a/src/main/index.ts b/src/main/index.ts index 0e4f2f7..0a523a6 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -13,15 +13,13 @@ import { initializeShortcuts, registerShortcuts } from './modules/shortcuts'; import { initializeTray, updateCurrentSong, updatePlayState, updateTrayMenu } from './modules/tray'; import { setupUpdateHandlers } from './modules/update'; import { createMainWindow, initializeWindowManager, setAppQuitting } from './modules/window'; -import { startMusicApi } from './server'; import { initWindowSizeManager } from './modules/window-size'; +import { startMusicApi } from './server'; // 导入所有图标 const iconPath = join(__dirname, '../../resources'); const icon = nativeImage.createFromPath( - process.platform === 'darwin' - ? join(iconPath, 'icon.icns') - : join(iconPath, 'icon.png') + process.platform === 'darwin' ? join(iconPath, 'icon.icns') : join(iconPath, 'icon.png') ); let mainWindow: Electron.BrowserWindow; diff --git a/src/main/lyric.ts b/src/main/lyric.ts index e43c098..7a0da80 100644 --- a/src/main/lyric.ts +++ b/src/main/lyric.ts @@ -122,7 +122,7 @@ const createWin = () => { } }); - lyricWindow.on('blur', () => lyricWindow && lyricWindow.setMaximizable(false)) + lyricWindow.on('blur', () => lyricWindow && lyricWindow.setMaximizable(false)); return lyricWindow; }; diff --git a/src/main/modules/fileManager.ts b/src/main/modules/fileManager.ts index e1fb433..b7e74ed 100644 --- a/src/main/modules/fileManager.ts +++ b/src/main/modules/fileManager.ts @@ -1,14 +1,14 @@ import axios from 'axios'; import { app, dialog, ipcMain, Notification, protocol, shell } from 'electron'; import Store from 'electron-store'; +import { fileTypeFromFile } from 'file-type'; import * as fs from 'fs'; import * as http from 'http'; import * as https from 'https'; -import * as NodeID3 from 'node-id3'; -import * as path from 'path'; -import * as os from 'os'; import * as mm from 'music-metadata'; -import { fileTypeFromFile } from 'file-type'; +import * as NodeID3 from 'node-id3'; +import * as os from 'os'; +import * as path from 'path'; import { getStore } from './config'; @@ -42,18 +42,18 @@ export function initializeFileManager() { // 注册本地文件协议 protocol.registerFileProtocol('local', (request, callback) => { try { - let url = request.url; + const url = request.url; // local://C:/Users/xxx.mp3 let filePath = decodeURIComponent(url.replace('local:///', '')); - + // 兼容 local:///C:/Users/xxx.mp3 这种情况 if (/^\/[a-zA-Z]:\//.test(filePath)) { filePath = filePath.slice(1); } - + // 还原为系统路径格式 filePath = path.normalize(filePath); - + // 检查文件是否存在 if (!fs.existsSync(filePath)) { console.error('File not found:', filePath); @@ -128,13 +128,13 @@ export function initializeFileManager() { ipcMain.handle('get-downloads-path', () => { return app.getPath('downloads'); }); - + // 获取存储的配置值 ipcMain.handle('get-store-value', (_, key) => { const store = new Store(); return store.get(key); }); - + // 设置存储的配置值 ipcMain.on('set-store-value', (_, key, value) => { const store = new Store(); @@ -189,7 +189,8 @@ export function initializeFileManager() { const validEntriesPromises = await Promise.all( entriesArray.map(async ([path, info]) => { try { - const exists = await fs.promises.access(path) + const exists = await fs.promises + .access(path) .then(() => true) .catch(() => false); return exists ? info : null; @@ -202,7 +203,7 @@ export function initializeFileManager() { // 过滤有效的歌曲并排序 const validSongs = validEntriesPromises - .filter(song => song !== null) + .filter((song) => song !== null) .sort((a, b) => (b.downloadTime || 0) - (a.downloadTime || 0)); // 更新存储,移除不存在的文件记录 @@ -376,9 +377,9 @@ async function downloadMusic( const downloadPath = (configStore.get('set.downloadPath') as string) || app.getPath('downloads'); const apiPort = configStore.get('set.musicApiPort') || 30488; - + // 获取文件名格式设置 - const nameFormat = + const nameFormat = (configStore.get('set.downloadNameFormat') as string) || '{songName} - {artistName}'; // 根据格式创建文件名 @@ -388,7 +389,7 @@ async function downloadMusic( const artistName = songInfo.ar?.map((a: any) => a.name).join('/') || '未知艺术家'; const songName = songInfo.name || filename; const albumName = songInfo.al?.name || '未知专辑'; - + // 应用自定义格式 formattedFilename = nameFormat .replace(/\{songName\}/g, songName) @@ -401,12 +402,12 @@ async function downloadMusic( // 创建临时文件路径 (在系统临时目录中创建) const tempDir = path.join(os.tmpdir(), 'AlgerMusicPlayerTemp'); - + // 确保临时目录存在 if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir, { recursive: true }); } - + tempFilePath = path.join(tempDir, `${Date.now()}_${sanitizedFilename}.tmp`); // 先获取文件大小 @@ -460,7 +461,7 @@ async function downloadMusic( // 检测文件类型 let fileExtension = ''; - + try { // 首先尝试使用file-type库检测 const fileType = await fileTypeFromFile(tempFilePath); @@ -475,26 +476,28 @@ async function downloadMusic( const formatInfo = metadata.format; const container = formatInfo.container || ''; const codec = formatInfo.codec || ''; - + // 音频格式映射表 const formatMap = { - 'mp3': ['MPEG', 'MP3', 'mp3'], - 'aac': ['AAC'], - 'flac': ['FLAC'], - 'ogg': ['Ogg', 'Vorbis'], - 'wav': ['WAV', 'PCM'], - 'm4a': ['M4A', 'MP4'] + mp3: ['MPEG', 'MP3', 'mp3'], + aac: ['AAC'], + flac: ['FLAC'], + ogg: ['Ogg', 'Vorbis'], + wav: ['WAV', 'PCM'], + m4a: ['M4A', 'MP4'] }; - + // 查找匹配的格式 - const format = Object.entries(formatMap).find(([_, keywords]) => - keywords.some(keyword => container.includes(keyword) || codec.includes(keyword)) + const format = Object.entries(formatMap).find(([_, keywords]) => + keywords.some((keyword) => container.includes(keyword) || codec.includes(keyword)) ); - + // 设置文件扩展名,如果没找到则默认为mp3 fileExtension = format ? `.${format[0]}` : '.mp3'; - - console.log(`music-metadata检测结果: 容器:${container}, 编码:${codec}, 扩展名: ${fileExtension}`); + + console.log( + `music-metadata检测结果: 容器:${container}, 编码:${codec}, 扩展名: ${fileExtension}` + ); } else { // 两种方法都失败,使用传入的type或默认mp3 fileExtension = type ? `.${type}` : '.mp3'; @@ -593,7 +596,7 @@ async function downloadMusic( try { // 在写入ID3标签前,先移除可能存在的旧标签 NodeID3.removeTags(finalFilePath); - + const tags = { title: filename, artist: artistNames, @@ -676,7 +679,7 @@ async function downloadMusic( const notificationId = `download-${finalFilePath}`; if (!sentNotifications.has(notificationId)) { sentNotifications.set(notificationId, true); - + // 发送桌面通知 try { const artistNames = @@ -687,13 +690,13 @@ async function downloadMusic( body: `${songInfo?.name || filename} - ${artistNames}`, silent: false }); - + notification.on('click', () => { shell.showItemInFolder(finalFilePath); }); - + notification.show(); - + // 60秒后清理通知记录,释放内存 setTimeout(() => { sentNotifications.delete(notificationId); @@ -722,7 +725,7 @@ async function downloadMusic( if (writer) { writer.end(); } - + // 清理临时文件 if (tempFilePath && fs.existsSync(tempFilePath)) { try { @@ -731,7 +734,7 @@ async function downloadMusic( console.error('Failed to delete temporary file:', e); } } - + // 清理未完成的最终文件 if (finalFilePath && fs.existsSync(finalFilePath)) { try { diff --git a/src/main/modules/remoteControl.ts b/src/main/modules/remoteControl.ts index 72180ee..495926a 100644 --- a/src/main/modules/remoteControl.ts +++ b/src/main/modules/remoteControl.ts @@ -1,10 +1,11 @@ +import cors from 'cors'; import { ipcMain } from 'electron'; import express from 'express'; -import cors from 'cors'; -import os from 'os'; -import { getStore } from './config'; -import path from 'path'; import fs from 'fs'; +import os from 'os'; +import path from 'path'; + +import { getStore } from './config'; // 定义远程控制相关接口 export interface RemoteControlConfig { @@ -72,9 +73,9 @@ export function initializeRemoteControl(mainWindow: Electron.BrowserWindow) { if (server) { stopServer(); } - + store.set('remoteControl', newConfig); - + if (newConfig.enabled) { startServer(newConfig); } @@ -105,16 +106,16 @@ function startServer(config: RemoteControlConfig) { } app = express(); - + // 跨域配置 app.use(cors()); app.use(express.json()); - + // IP 过滤中间件 app.use((req, res, next) => { const clientIp = req.ip || req.socket.remoteAddress || ''; const cleanIp = clientIp.replace(/^::ffff:/, ''); // 移除IPv6前缀 - console.log('config',config) + console.log('config', config); if (config.allowedIps.length === 0 || config.allowedIps.includes(cleanIp)) { next(); } else { @@ -216,7 +217,7 @@ function setupRoutes(app: express.Application) { const isDev = process.env.NODE_ENV === 'development'; const htmlPath = path.join(process.cwd(), 'resources', 'html', 'remote-control.html'); const finalPath = isDev ? htmlPath : path.join(resourcesPath, 'html', 'remote-control.html'); - + if (fs.existsSync(finalPath)) { res.sendFile(finalPath); } else { @@ -228,4 +229,4 @@ function setupRoutes(app: express.Application) { res.status(500).send('加载远程控制界面失败'); } }); -} \ No newline at end of file +} diff --git a/src/main/modules/tray.ts b/src/main/modules/tray.ts index 0401e62..136542b 100644 --- a/src/main/modules/tray.ts +++ b/src/main/modules/tray.ts @@ -329,7 +329,7 @@ export function updateTrayMenu(mainWindow: BrowserWindow) { // 初始化状态栏Tray function initializeStatusBarTray(mainWindow: BrowserWindow) { - const store = getStore() + const store = getStore(); if (process.platform !== 'darwin' || !store.get('set.showTopAction')) return; const iconSize = getProperIconSize(); diff --git a/src/main/modules/window-size.ts b/src/main/modules/window-size.ts index 2801049..5a6246f 100644 --- a/src/main/modules/window-size.ts +++ b/src/main/modules/window-size.ts @@ -31,8 +31,6 @@ export interface WindowState { isMaximized: boolean; } - - /** * 窗口大小管理器 * 负责保存、恢复和维护窗口大小状态 @@ -42,12 +40,12 @@ class WindowSizeManager { private mainWindow: BrowserWindow | null = null; private savedState: WindowState | null = null; private isInitialized: boolean = false; - + constructor() { this.store = store; // 初始化时不做与screen相关的操作,等app ready后再初始化 } - + /** * 初始化窗口大小管理器 * 必须在app ready后调用 @@ -57,17 +55,17 @@ class WindowSizeManager { console.warn('WindowSizeManager.initialize() 必须在 app ready 之后调用!'); return; } - + if (this.isInitialized) { return; } - + this.initMinimumWindowSize(); this.setupIPCHandlers(); this.isInitialized = true; console.log('窗口大小管理器初始化完成'); } - + /** * 设置主窗口引用 */ @@ -75,19 +73,19 @@ class WindowSizeManager { if (!this.isInitialized) { this.initialize(); } - + this.mainWindow = win; - + // 读取保存的状态 this.savedState = this.getWindowState(); - + // 监听重要事件 this.setupEventListeners(win); - + // 立即保存初始状态 this.saveWindowState(win); } - + /** * 初始化最小窗口尺寸 */ @@ -96,14 +94,17 @@ class WindowSizeManager { console.warn('不能在 app ready 之前访问 screen 模块'); return; } - + try { const { width: workAreaWidth, height: workAreaHeight } = screen.getPrimaryDisplay().workArea; - + // 根据工作区大小设置合理的最小尺寸 MIN_WIDTH = Math.min(Math.round(DEFAULT_MAIN_WIDTH * 0.5), Math.round(workAreaWidth * 0.3)); - MIN_HEIGHT = Math.min(Math.round(DEFAULT_MAIN_HEIGHT * 0.5), Math.round(workAreaHeight * 0.3)); - + MIN_HEIGHT = Math.min( + Math.round(DEFAULT_MAIN_HEIGHT * 0.5), + Math.round(workAreaHeight * 0.3) + ); + console.log(`设置最小窗口尺寸: ${MIN_WIDTH}x${MIN_HEIGHT}`); } catch (error) { console.error('初始化最小窗口尺寸失败:', error); @@ -112,7 +113,7 @@ class WindowSizeManager { MIN_HEIGHT = Math.round(DEFAULT_MAIN_HEIGHT * 0.5); } } - + /** * 设置事件监听器 */ @@ -123,46 +124,46 @@ class WindowSizeManager { this.saveWindowState(win); } }); - + // 监听窗口移动事件 win.on('move', () => { if (!win.isDestroyed() && !win.isMinimized()) { this.saveWindowState(win); } }); - + // 监听窗口最大化事件 win.on('maximize', () => { if (!win.isDestroyed()) { this.saveWindowState(win); } }); - + // 监听窗口从最大化恢复事件 win.on('unmaximize', () => { if (!win.isDestroyed()) { this.saveWindowState(win); } }); - + // 监听窗口关闭事件,确保保存最终状态 win.on('close', () => { if (!win.isDestroyed()) { this.saveWindowState(win); } }); - + // 在页面加载完成后确保窗口大小正确 win.webContents.on('did-finish-load', () => { this.enforceCorrectSize(win); }); - + // 在窗口准备好显示时确保尺寸正确 win.on('ready-to-show', () => { this.enforceCorrectSize(win); }); } - + /** * 强制应用正确的窗口大小 */ @@ -170,30 +171,36 @@ class WindowSizeManager { if (!this.savedState || win.isMaximized() || win.isMinimized() || win.isDestroyed()) { return; } - + const [currentWidth, currentHeight] = win.getSize(); - - if (Math.abs(currentWidth - this.savedState.width) > 2 || - Math.abs(currentHeight - this.savedState.height) > 2) { - console.log(`强制调整窗口大小: 当前=${currentWidth}x${currentHeight}, 目标=${this.savedState.width}x${this.savedState.height}`); - + + if ( + Math.abs(currentWidth - this.savedState.width) > 2 || + Math.abs(currentHeight - this.savedState.height) > 2 + ) { + console.log( + `强制调整窗口大小: 当前=${currentWidth}x${currentHeight}, 目标=${this.savedState.width}x${this.savedState.height}` + ); + // 临时禁用minimum size限制 const [minWidth, minHeight] = win.getMinimumSize(); win.setMinimumSize(1, 1); - + // 强制设置正确大小 win.setSize(this.savedState.width, this.savedState.height, false); - + // 恢复原始minimum size win.setMinimumSize(minWidth, minHeight); - + // 验证尺寸设置是否成功 const [newWidth, newHeight] = win.getSize(); console.log(`调整后窗口大小: ${newWidth}x${newHeight}`); - + // 如果调整后的大小仍然与目标不一致,尝试再次调整 - if (Math.abs(newWidth - this.savedState.width) > 1 || - Math.abs(newHeight - this.savedState.height) > 1) { + if ( + Math.abs(newWidth - this.savedState.width) > 1 || + Math.abs(newHeight - this.savedState.height) > 1 + ) { console.log(`窗口大小调整后仍不一致,将再次尝试调整`); setTimeout(() => { if (!win.isDestroyed() && !win.isMaximized() && !win.isMinimized()) { @@ -201,12 +208,12 @@ class WindowSizeManager { } }, 50); } - + // // 开始尺寸强制执行 // this.startSizeEnforcement(win); } } - + /** * 开启尺寸强制执行定时器 */ @@ -216,15 +223,15 @@ class WindowSizeManager { // clearInterval(this.enforceTimer); // this.enforceTimer = null; // } - + // this.enforceCount = 0; - + // // 创建新的定时器,每50ms检查一次窗口大小 // this.enforceTimer = setInterval(() => { - // if (this.enforceCount >= this.MAX_ENFORCE_COUNT || - // !this.savedState || - // win.isDestroyed() || - // win.isMaximized() || + // if (this.enforceCount >= this.MAX_ENFORCE_COUNT || + // !this.savedState || + // win.isDestroyed() || + // win.isMaximized() || // win.isMinimized()) { // // 达到最大检查次数或不需要检查,清除定时器 // if (this.enforceTimer) { @@ -233,35 +240,35 @@ class WindowSizeManager { // } // return; // } - + // const [currentWidth, currentHeight] = win.getSize(); - - // if (Math.abs(currentWidth - this.savedState.width) > 2 || + + // if (Math.abs(currentWidth - this.savedState.width) > 2 || // Math.abs(currentHeight - this.savedState.height) > 2) { // console.log(`[定时检查] 强制调整窗口大小: 当前=${currentWidth}x${currentHeight}, 目标=${this.savedState.width}x${this.savedState.height}`); - + // // 临时禁用minimum size限制 // const [minWidth, minHeight] = win.getMinimumSize(); // win.setMinimumSize(1, 1); - + // // 强制设置正确大小 // win.setSize(this.savedState.width, this.savedState.height, false); - + // // 恢复原始minimum size // win.setMinimumSize(minWidth, minHeight); - + // // 验证尺寸设置是否成功 // const [newWidth, newHeight] = win.getSize(); - // if (Math.abs(newWidth - this.savedState.width) <= 1 && + // if (Math.abs(newWidth - this.savedState.width) <= 1 && // Math.abs(newHeight - this.savedState.height) <= 1) { // console.log(`窗口大小已成功调整为目标尺寸: ${newWidth}x${newHeight}`); // } // } - + // this.enforceCount++; // }, 50); // } - + /** * 获取窗口创建选项 */ @@ -270,10 +277,10 @@ class WindowSizeManager { if (!this.isInitialized && app.isReady()) { this.initialize(); } - + // 读取保存的状态 const savedState = this.getWindowState(); - + // 准备选项 const options: Electron.BrowserWindowConstructorOptions = { width: savedState?.width || DEFAULT_MAIN_WIDTH, @@ -287,7 +294,7 @@ class WindowSizeManager { contextIsolation: true } }; - + // 如果有保存的位置,且位置有效,则使用该位置 if (savedState?.x !== undefined && savedState?.y !== undefined && app.isReady()) { if (this.isPositionVisible(savedState.x, savedState.y)) { @@ -295,49 +302,57 @@ class WindowSizeManager { options.y = savedState.y; } } - - console.log(`窗口创建选项: 大小=${options.width}x${options.height}, 位置=(${options.x}, ${options.y})`); - + + console.log( + `窗口创建选项: 大小=${options.width}x${options.height}, 位置=(${options.x}, ${options.y})` + ); + return options; } - + /** * 应用窗口初始状态 * 在窗口创建后调用 */ applyInitialState(win: BrowserWindow): void { const savedState = this.getWindowState(); - + if (!savedState) { win.center(); return; } - + // 如果需要最大化,直接最大化 if (savedState.isMaximized) { console.log('应用已保存的最大化状态'); win.maximize(); - } + } // 如果位置无效,则居中显示 - else if (!app.isReady() || savedState.x === undefined || savedState.y === undefined || - !this.isPositionVisible(savedState.x, savedState.y)) { + else if ( + !app.isReady() || + savedState.x === undefined || + savedState.y === undefined || + !this.isPositionVisible(savedState.x, savedState.y) + ) { console.log('保存的位置无效,窗口居中显示'); win.center(); } } - + /** * 保存窗口状态 */ saveWindowState(win: BrowserWindow): WindowState { // 如果窗口已销毁,则返回之前的状态或默认状态 - console.log('win.isDestroyed()',win.isDestroyed()) + console.log('win.isDestroyed()', win.isDestroyed()); if (win.isDestroyed()) { - return this.savedState || { - width: DEFAULT_MAIN_WIDTH, - height: DEFAULT_MAIN_HEIGHT, - isMaximized: false - }; + return ( + this.savedState || { + width: DEFAULT_MAIN_WIDTH, + height: DEFAULT_MAIN_HEIGHT, + isMaximized: false + } + ); } // 检查是否是mini模式窗口(根据窗口大小判断) @@ -352,9 +367,10 @@ class WindowSizeManager { // 由于 Electron 的限制,最大化状态下 getBounds() 可能不准确 // 所以我们尽量保留之前保存的非最大化时的大小 const currentBounds = win.getBounds(); - const previousSize = this.savedState && !this.savedState.isMaximized - ? { width: this.savedState.width, height: this.savedState.height } - : { width: currentBounds.width, height: currentBounds.height }; + const previousSize = + this.savedState && !this.savedState.isMaximized + ? { width: this.savedState.width, height: this.savedState.height } + : { width: currentBounds.width, height: currentBounds.height }; state = { width: previousSize.width, @@ -363,19 +379,18 @@ class WindowSizeManager { y: currentBounds.y, isMaximized: true }; - console.log('state IsMaximized',state) - - } - else if (win.isMinimized()) { + console.log('state IsMaximized', state); + } else if (win.isMinimized()) { // 最小化状态下不保存窗口大小,因为可能不准确 - console.log('state IsMinimized',this.savedState) - return this.savedState || { - width: DEFAULT_MAIN_WIDTH, - height: DEFAULT_MAIN_HEIGHT, - isMaximized: false - }; - } - else { + console.log('state IsMinimized', this.savedState); + return ( + this.savedState || { + width: DEFAULT_MAIN_WIDTH, + height: DEFAULT_MAIN_HEIGHT, + isMaximized: false + } + ); + } else { // 正常状态下保存当前大小和位置 const [width, height] = win.getSize(); const [x, y] = win.getPosition(); @@ -387,7 +402,7 @@ class WindowSizeManager { y, isMaximized: false }; - console.log('state IsNormal',state) + console.log('state IsNormal', state); } // 如果是mini模式,不保存到持久化存储,只返回状态用于内存中的恢复 @@ -402,11 +417,11 @@ class WindowSizeManager { // 更新内部状态 this.savedState = state; - console.log('state',state) + console.log('state', state); return state; } - + /** * 获取保存的窗口状态 */ @@ -432,8 +447,6 @@ class WindowSizeManager { return validatedState; } - - /** * 检查位置是否在可见屏幕范围内 */ @@ -441,18 +454,13 @@ class WindowSizeManager { if (!app.isReady()) { return false; } - + try { const displays = screen.getAllDisplays(); - + for (const display of displays) { const { x: screenX, y: screenY, width, height } = display.workArea; - if ( - x >= screenX && - x < screenX + width && - y >= screenY && - y < screenY + height - ) { + if (x >= screenX && x < screenX + width && y >= screenY && y < screenY + height) { return true; } } @@ -460,10 +468,10 @@ class WindowSizeManager { console.error('检查位置可见性失败:', error); return false; } - + return false; } - + /** * 计算适合当前缩放比的缩放因子 */ @@ -472,14 +480,14 @@ class WindowSizeManager { if (!app.isReady()) { return 1; } - + try { // 获取系统的缩放因子 const { scaleFactor } = screen.getPrimaryDisplay(); - + // 缩放因子默认为1 let zoomFactor = 1; - + // 只在高DPI情况下调整 if (scaleFactor > 1) { // 自定义逻辑来根据不同的缩放比例进行调整 @@ -500,38 +508,40 @@ class WindowSizeManager { zoomFactor = 1; } } - + // 获取用户的自定义缩放设置(如果有) const userZoomFactor = this.store.get('set.contentZoomFactor') as number | undefined; if (userZoomFactor) { zoomFactor = userZoomFactor; } - + return zoomFactor; } catch (error) { console.error('计算内容缩放因子失败:', error); return 1; } } - + /** * 应用页面内容缩放 */ applyContentZoom(win: BrowserWindow): void { const zoomFactor = this.calculateContentZoomFactor(); win.webContents.setZoomFactor(zoomFactor); - + if (app.isReady()) { try { - console.log(`应用页面缩放因子: ${zoomFactor}, 系统缩放比: ${screen.getPrimaryDisplay().scaleFactor}`); + console.log( + `应用页面缩放因子: ${zoomFactor}, 系统缩放比: ${screen.getPrimaryDisplay().scaleFactor}` + ); } catch (error) { - console.log(`应用页面缩放因子: ${zoomFactor}`); + console.error('获取系统缩放比失败:', error); } } else { console.log(`应用页面缩放因子: ${zoomFactor}`); } } - + /** * 初始化IPC消息处理程序 */ @@ -541,25 +551,25 @@ class WindowSizeManager { console.log('IPC处理程序已注册,跳过重复注册'); return; } - + console.log('注册窗口大小相关的IPC处理程序'); - + // 标记为已注册 ipcHandlersRegistered = true; - + // 安全地移除已存在的处理程序(如果有) const removeHandlerSafely = (channel: string) => { try { ipcMain.removeHandler(channel); } catch (error) { - // 忽略错误,处理程序可能不存在 + console.warn(`移除IPC处理程序 ${channel} 时出错:`, error); } }; - + // 为需要使用handle方法的通道先移除已有处理程序 removeHandlerSafely('get-content-zoom'); removeHandlerSafely('get-system-scale-factor'); - + // 注册新的处理程序 ipcMain.on('set-content-zoom', (event, zoomFactor) => { const win = BrowserWindow.fromWebContents(event.sender); @@ -568,7 +578,7 @@ class WindowSizeManager { this.store.set('set.contentZoomFactor', zoomFactor); } }); - + ipcMain.handle('get-content-zoom', (event) => { const win = BrowserWindow.fromWebContents(event.sender); if (win && !win.isDestroyed()) { @@ -576,12 +586,12 @@ class WindowSizeManager { } return 1; }); - + ipcMain.handle('get-system-scale-factor', () => { if (!app.isReady()) { return 1; } - + try { return screen.getPrimaryDisplay().scaleFactor; } catch (error) { @@ -589,7 +599,7 @@ class WindowSizeManager { return 1; } }); - + ipcMain.on('reset-content-zoom', (event) => { const win = BrowserWindow.fromWebContents(event.sender); if (win && !win.isDestroyed()) { @@ -597,25 +607,25 @@ class WindowSizeManager { this.applyContentZoom(win); } }); - + ipcMain.on('resize-window', (event, width, height) => { const win = BrowserWindow.fromWebContents(event.sender); if (win && !win.isDestroyed()) { console.log(`接收到调整窗口大小请求: ${width}x${height}`); - + // 确保尺寸不小于最小值 const adjustedWidth = Math.max(width, MIN_WIDTH); const adjustedHeight = Math.max(height, MIN_HEIGHT); - + // 设置窗口的大小 win.setSize(adjustedWidth, adjustedHeight); console.log(`窗口大小已调整为: ${adjustedWidth}x${adjustedHeight}`); - + // 保存窗口状态 this.saveWindowState(win); } }); - + ipcMain.on('resize-mini-window', (event, showPlaylist) => { const win = BrowserWindow.fromWebContents(event.sender); if (win && !win.isDestroyed()) { @@ -632,7 +642,7 @@ class WindowSizeManager { } } }); - + // 只在app ready后设置显示器变化监听 if (app.isReady()) { // 监听显示器变化事件 @@ -642,13 +652,13 @@ class WindowSizeManager { if (changedMetrics.includes('scaleFactor')) { this.applyContentZoom(this.mainWindow); } - + // 重新初始化最小尺寸 this.initMinimumWindowSize(); } }); } - + // 监听 store 中的缩放设置变化 this.store.onDidChange('set.contentZoomFactor', () => { if (this.mainWindow && !this.mainWindow.isDestroyed()) { @@ -712,4 +722,3 @@ export const initWindowSizeHandlers = (mainWindow: BrowserWindow | null): void = export const calculateMinimumWindowSize = (): { minWidth: number; minHeight: number } => { return { minWidth: MIN_WIDTH, minHeight: MIN_HEIGHT }; }; - diff --git a/src/main/modules/window.ts b/src/main/modules/window.ts index 66492cb..f32f305 100644 --- a/src/main/modules/window.ts +++ b/src/main/modules/window.ts @@ -1,18 +1,28 @@ import { is } from '@electron-toolkit/utils'; -import { app, BrowserWindow, nativeImage, globalShortcut, ipcMain, screen, session, shell } from 'electron'; +import { + app, + BrowserWindow, + globalShortcut, + ipcMain, + nativeImage, + screen, + session, + shell +} from 'electron'; import Store from 'electron-store'; import { join } from 'path'; + import { - DEFAULT_MAIN_WIDTH, - DEFAULT_MAIN_HEIGHT, - DEFAULT_MINI_WIDTH, - DEFAULT_MINI_HEIGHT, applyContentZoom, - saveWindowState, applyInitialState, - initWindowSizeHandlers, + DEFAULT_MAIN_HEIGHT, + DEFAULT_MAIN_WIDTH, + DEFAULT_MINI_HEIGHT, + DEFAULT_MINI_WIDTH, getWindowOptions, getWindowState, + initWindowSizeHandlers, + saveWindowState, WindowState } from './window-size'; @@ -68,34 +78,32 @@ function setThumbarButtons(window: BrowserWindow) { window.setThumbarButtons([ { tooltip: 'prev', - icon: nativeImage - .createFromPath(join(app.getAppPath(), 'resources/icons', 'prev.png')), + icon: nativeImage.createFromPath(join(app.getAppPath(), 'resources/icons', 'prev.png')), click() { window.webContents.send('global-shortcut', 'prevPlay'); - }, + } }, { tooltip: isPlaying ? 'pause' : 'play', - icon: nativeImage - .createFromPath(join(app.getAppPath(), 'resources/icons', isPlaying ? 'pause.png' : 'play.png')), + icon: nativeImage.createFromPath( + join(app.getAppPath(), 'resources/icons', isPlaying ? 'pause.png' : 'play.png') + ), click() { window.webContents.send('global-shortcut', 'togglePlay'); - }, + } }, { tooltip: 'next', - icon: nativeImage - .createFromPath(join(app.getAppPath(), 'resources/icons', 'next.png')), + icon: nativeImage.createFromPath(join(app.getAppPath(), 'resources/icons', 'next.png')), click() { window.webContents.send('global-shortcut', 'nextPlay'); - }, + } } ]); } - /** * 初始化窗口管理相关的IPC监听 */ @@ -159,7 +167,11 @@ export function initializeWindowManager() { win.setMaximumSize(DEFAULT_MINI_WIDTH, DEFAULT_MINI_HEIGHT); win.setSize(DEFAULT_MINI_WIDTH, DEFAULT_MINI_HEIGHT, false); // 禁用动画 // 将迷你窗口放在工作区的右上角,留出一些边距 - win.setPosition(screenX + screenWidth - DEFAULT_MINI_WIDTH - 20, display.workArea.y + 20, false); + win.setPosition( + screenX + screenWidth - DEFAULT_MINI_WIDTH - 20, + display.workArea.y + 20, + false + ); win.setAlwaysOnTop(true); win.setSkipTaskbar(false); win.setResizable(false); @@ -186,7 +198,10 @@ export function initializeWindowManager() { console.log('从迷你模式恢复,使用保存的状态:', JSON.stringify(preMiniModeState)); // 设置适当的最小尺寸 - win.setMinimumSize(Math.max(DEFAULT_MAIN_WIDTH * 0.5, 600), Math.max(DEFAULT_MAIN_HEIGHT * 0.5, 400)); + win.setMinimumSize( + Math.max(DEFAULT_MAIN_WIDTH * 0.5, 600), + Math.max(DEFAULT_MAIN_HEIGHT * 0.5, 400) + ); // 恢复窗口状态 win.setAlwaysOnTop(false); @@ -223,9 +238,13 @@ export function initializeWindowManager() { if (!win.isDestroyed() && !win.isMaximized() && !win.isMinimized()) { // 再次验证窗口大小 const [width, height] = win.getSize(); - if (Math.abs(width - preMiniModeState.width) > 2 || - Math.abs(height - preMiniModeState.height) > 2) { - console.log(`恢复后窗口大小不一致,再次调整: 当前=${width}x${height}, 目标=${preMiniModeState.width}x${preMiniModeState.height}`); + if ( + Math.abs(width - preMiniModeState.width) > 2 || + Math.abs(height - preMiniModeState.height) > 2 + ) { + console.log( + `恢复后窗口大小不一致,再次调整: 当前=${width}x${height}, 目标=${preMiniModeState.width}x${preMiniModeState.height}` + ); win.setSize(preMiniModeState.width, preMiniModeState.height, false); } } @@ -234,7 +253,6 @@ export function initializeWindowManager() { } }); - ipcMain.on('update-play-state', (_, playing: boolean) => { isPlaying = playing; if (mainWindowInstance) { @@ -279,14 +297,16 @@ export function createMainWindow(icon: Electron.NativeImage): BrowserWindow { webSecurity: false }; - console.log(`创建窗口,使用选项: ${JSON.stringify({ - width: options.width, - height: options.height, - x: options.x, - y: options.y, - minWidth: options.minWidth, - minHeight: options.minHeight - })}`); + console.log( + `创建窗口,使用选项: ${JSON.stringify({ + width: options.width, + height: options.height, + x: options.x, + y: options.y, + minWidth: options.minWidth, + minHeight: options.minHeight + })}` + ); // 创建窗口 const mainWindow = new BrowserWindow(options); @@ -340,9 +360,13 @@ export function createMainWindow(icon: Electron.NativeImage): BrowserWindow { if (!mainWindow.isDestroyed() && !mainWindow.isMaximized()) { const [currentWidth, currentHeight] = mainWindow.getSize(); if (savedState && !savedState.isMaximized) { - if (Math.abs(currentWidth - savedState.width) > 2 || - Math.abs(currentHeight - savedState.height) > 2) { - console.log(`窗口大小不匹配,再次调整: 当前=${currentWidth}x${currentHeight}, 目标=${savedState.width}x${savedState.height}`); + if ( + Math.abs(currentWidth - savedState.width) > 2 || + Math.abs(currentHeight - savedState.height) > 2 + ) { + console.log( + `窗口大小不匹配,再次调整: 当前=${currentWidth}x${currentHeight}, 目标=${savedState.width}x${savedState.height}` + ); mainWindow.setSize(savedState.width, savedState.height, false); } } @@ -371,7 +395,6 @@ export function createMainWindow(icon: Electron.NativeImage): BrowserWindow { initWindowSizeHandlers(mainWindow); - // 保存主窗口引用 mainWindowInstance = mainWindow; diff --git a/src/main/server.ts b/src/main/server.ts index cfd4d14..a9a0488 100644 --- a/src/main/server.ts +++ b/src/main/server.ts @@ -5,7 +5,7 @@ import server from 'netease-cloud-music-api-alger/server'; import os from 'os'; import path from 'path'; -import { unblockMusic, type Platform } from './unblockMusic'; +import { type Platform, unblockMusic } from './unblockMusic'; const store = new Store(); if (!fs.existsSync(path.resolve(os.tmpdir(), 'anonymous_token'))) { diff --git a/src/main/unblockMusic.ts b/src/main/unblockMusic.ts index 2e4dcb6..32d57cd 100644 --- a/src/main/unblockMusic.ts +++ b/src/main/unblockMusic.ts @@ -45,33 +45,33 @@ function ensureDataStructure(data: any): any { album: { name: '' } }; } - + // 确保name字段存在 if (data.name === undefined || data.name === null) { data.name = ''; } - + // 确保artists字段存在且为数组 if (!data.artists || !Array.isArray(data.artists)) { data.artists = data.ar && Array.isArray(data.ar) ? data.ar : []; } - + // 确保artists中的每个元素都有name属性 if (data.artists.length > 0) { - data.artists = data.artists.map(artist => { + data.artists = data.artists.map((artist) => { return artist ? { name: artist.name || '' } : { name: '' }; }); } - + // 确保album对象存在并有name属性 if (!data.album || typeof data.album !== 'object') { data.album = data.al && typeof data.al === 'object' ? data.al : { name: '' }; } - + if (!data.album.name) { data.album.name = ''; } - + return data; } @@ -89,15 +89,14 @@ const unblockMusic = async ( retryCount = 1, enabledPlatforms?: Platform[] ): Promise => { - // 过滤 enabledPlatforms,确保只包含 ALL_PLATFORMS 中存在的平台 - const filteredPlatforms = enabledPlatforms - ? enabledPlatforms.filter(platform => ALL_PLATFORMS.includes(platform)) + const filteredPlatforms = enabledPlatforms + ? enabledPlatforms.filter((platform) => ALL_PLATFORMS.includes(platform)) : ALL_PLATFORMS; - + // 处理歌曲数据,确保数据结构完整 const processedSongData = ensureDataStructure(songData); - + const retry = async (attempt: number): Promise => { try { const data = await match(parseInt(String(id), 10), filteredPlatforms, processedSongData); diff --git a/src/preload/index.ts b/src/preload/index.ts index 3cce699..c47ac7e 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -16,7 +16,8 @@ const api = { openLyric: () => ipcRenderer.send('open-lyric'), sendLyric: (data) => ipcRenderer.send('send-lyric', data), sendSong: (data) => ipcRenderer.send('update-current-song', data), - unblockMusic: (id, data, enabledSources) => ipcRenderer.invoke('unblock-music', id, data, enabledSources), + unblockMusic: (id, data, enabledSources) => + ipcRenderer.invoke('unblock-music', id, data, enabledSources), // 歌词窗口关闭事件 onLyricWindowClosed: (callback: () => void) => { ipcRenderer.on('lyric-window-closed', () => callback()); diff --git a/src/renderer/App.vue b/src/renderer/App.vue index 6951781..0257395 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -19,17 +19,16 @@ import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; import TrafficWarningDrawer from '@/components/TrafficWarningDrawer.vue'; - import homeRouter from '@/router/home'; import { useMenuStore } from '@/store/modules/menu'; import { usePlayerStore } from '@/store/modules/player'; import { useSettingsStore } from '@/store/modules/settings'; import { isElectron, isLyricWindow } from '@/utils'; -import { initAudioListeners } from './hooks/MusicHook'; +import { initAudioListeners, initMusicHook } from './hooks/MusicHook'; +import { audioService } from './services/audioService'; import { isMobile } from './utils'; import { useAppShortcuts } from './utils/appShortcuts'; -import { audioService } from './services/audioService'; const { locale } = useI18n(); const settingsStore = useSettingsStore(); @@ -114,6 +113,9 @@ onMounted(async () => { } // 先初始化播放状态 await playerStore.initializePlayState(); + // 初始化 MusicHook,注入 playerStore + initMusicHook(playerStore); + // 如果有正在播放的音乐,则初始化音频监听器 if (playerStore.playMusic && playerStore.playMusic.id) { // 使用 nextTick 确保 DOM 更新后再初始化 diff --git a/src/renderer/api/bilibili.ts b/src/renderer/api/bilibili.ts index 90fb550..acb963a 100644 --- a/src/renderer/api/bilibili.ts +++ b/src/renderer/api/bilibili.ts @@ -153,11 +153,8 @@ export const getBilibiliAudioUrl = async (bvid: string, cid: number): Promise => { +export const searchAndGetBilibiliAudioUrl = async (keyword: string): Promise => { try { // 搜索B站视频,取第一页第一个结果 const res = await searchBilibili({ keyword, page: 1, pagesize: 1 }); @@ -180,4 +177,4 @@ export const searchAndGetBilibiliAudioUrl = async ( console.error('根据名称搜索B站音频URL失败:', error); throw error; } -} \ No newline at end of file +}; diff --git a/src/renderer/api/gdmusic.ts b/src/renderer/api/gdmusic.ts index dd82aea..40b6834 100644 --- a/src/renderer/api/gdmusic.ts +++ b/src/renderer/api/gdmusic.ts @@ -1,4 +1,5 @@ import axios from 'axios'; + import type { MusicSourceType } from '@/type/music'; /** @@ -19,8 +20,8 @@ export interface ParsedMusicResult { params: { id: number; type: string; - } - } + }; + }; } /** @@ -32,8 +33,8 @@ export interface ParsedMusicResult { * @returns 解析后的音乐URL及相关信息 */ export const parseFromGDMusic = async ( - id: number, - data: any, + id: number, + data: any, quality: string = '999', timeout: number = 15000 ): Promise => { @@ -53,32 +54,31 @@ export const parseFromGDMusic = async ( console.error('GD音乐台解析:歌曲数据为空'); throw new Error('歌曲数据为空'); } - + const songName = data.name || ''; let artistNames = ''; - + // 处理不同的艺术家字段结构 if (data.artists && Array.isArray(data.artists)) { - artistNames = data.artists.map(artist => artist.name).join(' '); + artistNames = data.artists.map((artist) => artist.name).join(' '); } else if (data.ar && Array.isArray(data.ar)) { - artistNames = data.ar.map(artist => artist.name).join(' '); + artistNames = data.ar.map((artist) => artist.name).join(' '); } else if (data.artist) { artistNames = typeof data.artist === 'string' ? data.artist : ''; } - + const searchQuery = `${songName} ${artistNames}`.trim(); - + if (!searchQuery || searchQuery.length < 2) { console.error('GD音乐台解析:搜索查询过短', { name: songName, artists: artistNames }); throw new Error('搜索查询过短'); } - + // 所有可用的音乐源 netease、joox、tidal - const allSources = ['joox', 'tidal', 'netease' - ] as MusicSourceType[]; - + const allSources = ['joox', 'tidal', 'netease'] as MusicSourceType[]; + console.log('GD音乐台开始搜索:', searchQuery); - + // 依次尝试所有音源 for (const source of allSources) { try { @@ -109,7 +109,7 @@ export const parseFromGDMusic = async ( continue; } } - + console.log('GD音乐台所有音源均解析失败'); return null; })(), @@ -142,32 +142,32 @@ const baseUrl = 'https://music-api.gdstudio.xyz/api.php'; * @returns 音乐URL结果 */ async function searchAndGetUrl( - source: MusicSourceType, - searchQuery: string, + source: MusicSourceType, + searchQuery: string, quality: string ): Promise { // 1. 搜索歌曲 const searchUrl = `${baseUrl}?types=search&source=${source}&name=${encodeURIComponent(searchQuery)}&count=1&pages=1`; console.log(`GD音乐台尝试音源 ${source} 搜索:`, searchUrl); - + const searchResponse = await axios.get(searchUrl, { timeout: 5000 }); - + if (searchResponse.data && Array.isArray(searchResponse.data) && searchResponse.data.length > 0) { const firstResult = searchResponse.data[0]; if (!firstResult || !firstResult.id) { console.log(`GD音乐台 ${source} 搜索结果无效`); return null; } - + const trackId = firstResult.id; const trackSource = firstResult.source || source; - + // 2. 获取歌曲URL const songUrl = `${baseUrl}?types=url&source=${trackSource}&id=${trackId}&br=${quality}`; console.log(`GD音乐台尝试获取 ${trackSource} 歌曲URL:`, songUrl); - + const songResponse = await axios.get(songUrl, { timeout: 5000 }); - + if (songResponse.data && songResponse.data.url) { return { url: songResponse.data.url, @@ -183,4 +183,4 @@ async function searchAndGetUrl( console.log(`GD音乐台 ${source} 搜索结果为空`); return null; } -} \ No newline at end of file +} diff --git a/src/renderer/api/music.ts b/src/renderer/api/music.ts index 8848dfa..8daee30 100644 --- a/src/renderer/api/music.ts +++ b/src/renderer/api/music.ts @@ -1,13 +1,15 @@ +import { cloneDeep } from 'lodash'; + import { musicDB } from '@/hooks/MusicHook'; import { useSettingsStore, useUserStore } from '@/store'; import type { ILyric } from '@/type/lyric'; +import type { SongResult } from '@/type/music'; import { isElectron } from '@/utils'; import request from '@/utils/request'; import requestMusic from '@/utils/request_music'; -import { cloneDeep } from 'lodash'; -import { parseFromGDMusic } from './gdmusic'; -import type { SongResult } from '@/type/music'; + import { searchAndGetBilibiliAudioUrl } from './bilibili'; +import { parseFromGDMusic } from './gdmusic'; const { addData, getData, deleteData } = musicDB; @@ -89,12 +91,13 @@ export const getMusicLrc = async (id: number) => { */ const getBilibiliAudio = async (data: SongResult) => { const songName = data?.name || ''; - const artistName = Array.isArray(data?.ar) && data.ar.length > 0 && data.ar[0]?.name ? data.ar[0].name : ''; + const artistName = + Array.isArray(data?.ar) && data.ar.length > 0 && data.ar[0]?.name ? data.ar[0].name : ''; const albumName = data?.al && typeof data.al === 'object' && data.al?.name ? data.al.name : ''; - + const searchQuery = [songName, artistName, albumName].filter(Boolean).join(' ').trim(); console.log('开始搜索bilibili音频:', searchQuery); - + const url = await searchAndGetBilibiliAudioUrl(searchQuery); return { data: { @@ -131,7 +134,7 @@ const getGDMusicAudio = async (id: number, data: SongResult) => { * @returns 解析结果 */ const getUnblockMusicAudio = (id: number, data: SongResult, sources: any[]) => { - const filteredSources = sources.filter(source => source !== 'gdmusic'); + const filteredSources = sources.filter((source) => source !== 'gdmusic'); console.log(`使用unblockMusic解析,音源:`, filteredSources); return window.api.unblockMusic(id, cloneDeep(data), cloneDeep(filteredSources)); }; @@ -144,17 +147,17 @@ const getUnblockMusicAudio = (id: number, data: SongResult, sources: any[]) => { */ export const getParsingMusicUrl = async (id: number, data: SongResult) => { const settingStore = useSettingsStore(); - + // 如果禁用了音乐解析功能,则直接返回空结果 if (!settingStore.setData.enableMusicUnblock) { return Promise.resolve({ data: { code: 404, message: '音乐解析功能已禁用' } }); } - + // 1. 确定使用的音源列表(自定义或全局) const songId = String(id); const savedSourceStr = localStorage.getItem(`song_source_${songId}`); let musicSources: any[] = []; - + try { if (savedSourceStr) { // 使用自定义音源 @@ -172,14 +175,14 @@ export const getParsingMusicUrl = async (id: number, data: SongResult) => { console.error('解析音源设置失败,使用全局设置', e); musicSources = settingStore.setData.enabledMusicSources || []; } - + // 2. 按优先级解析 - + // 2.1 Bilibili解析(优先级最高) if (musicSources.includes('bilibili')) { return await getBilibiliAudio(data); } - + // 2.2 GD音乐台解析 if (musicSources.includes('gdmusic')) { const gdResult = await getGDMusicAudio(id, data); @@ -187,12 +190,12 @@ export const getParsingMusicUrl = async (id: number, data: SongResult) => { // GD解析失败,继续下一步 console.log('GD音乐台解析失败,尝试使用其他音源'); } - console.log('musicSources',musicSources) + console.log('musicSources', musicSources); // 2.3 使用unblockMusic解析其他音源 if (isElectron && musicSources.length > 0) { return getUnblockMusicAudio(id, data, musicSources); } - + // 3. 后备方案:使用API请求 console.log('无可用音源或不在Electron环境中,使用API请求'); return requestMusic.get('/music', { params: { id } }); diff --git a/src/renderer/api/playlist.ts b/src/renderer/api/playlist.ts index 9de168b..a707f63 100644 --- a/src/renderer/api/playlist.ts +++ b/src/renderer/api/playlist.ts @@ -24,4 +24,4 @@ export function getImportTaskStatus(id: string | number) { method: 'get', params: { id } }); -} \ No newline at end of file +} diff --git a/src/renderer/api/user.ts b/src/renderer/api/user.ts index 2fda64f..9217828 100644 --- a/src/renderer/api/user.ts +++ b/src/renderer/api/user.ts @@ -14,7 +14,6 @@ export function getUserPlaylist(uid: number, limit: number = 30, offset: number // 播放历史 // /user/record?uid=32953014&type=1 export function getUserRecord(uid: number, type: number = 0) { - return request.get('/user/record', { params: { uid, type }, noRetry: true diff --git a/src/renderer/assets/css/base.css b/src/renderer/assets/css/base.css index cfead38..dd1a6bd 100644 --- a/src/renderer/assets/css/base.css +++ b/src/renderer/assets/css/base.css @@ -10,7 +10,7 @@ body { border-radius: 0.5rem !important; overflow: hidden !important; } -.n-popover:has(.transparent-popover ) { +.n-popover:has(.transparent-popover) { background-color: transparent !important; padding: 0 !important; } diff --git a/src/renderer/assets/icon/iconfont.js b/src/renderer/assets/icon/iconfont.js index a50eb22..e87a871 100644 --- a/src/renderer/assets/icon/iconfont.js +++ b/src/renderer/assets/icon/iconfont.js @@ -1,4 +1,4 @@ -(window._iconfont_svg_string_2685283 = +((window._iconfont_svg_string_2685283 = ''), (function (a) { var c = (c = document.getElementsByTagName('script'))[c.length - 1]; @@ -23,10 +23,10 @@ console && console.log(c); } } - (o = function () { + ((o = function () { let c; let l = document.createElement('div'); - (l.innerHTML = a._iconfont_svg_string_2685283), + ((l.innerHTML = a._iconfont_svg_string_2685283), (l = l.getElementsByTagName('svg')[0]) && (l.setAttribute('aria-hidden', 'true'), (l.style.position = 'absolute'), @@ -34,13 +34,13 @@ (l.style.height = 0), (l.style.overflow = 'hidden'), (l = l), - (c = document.body).firstChild ? d(l, c.firstChild) : c.appendChild(l)); + (c = document.body).firstChild ? d(l, c.firstChild) : c.appendChild(l))); }), document.addEventListener ? ~['complete', 'loaded', 'interactive'].indexOf(document.readyState) ? setTimeout(o, 0) : ((i = function () { - document.removeEventListener('DOMContentLoaded', i, !1), o(); + (document.removeEventListener('DOMContentLoaded', i, !1), o()); }), document.addEventListener('DOMContentLoaded', i, !1)) : document.attachEvent && @@ -50,7 +50,7 @@ e(), (h.onreadystatechange = function () { h.readyState == 'complete' && ((h.onreadystatechange = null), p()); - })); + }))); } function p() { s || ((s = !0), t()); @@ -58,9 +58,9 @@ function e() { try { h.documentElement.doScroll('left'); - } catch (c) { + } catch { return void setTimeout(e, 50); } p(); } - })(window); + })(window)); diff --git a/src/renderer/components/EQControl.vue b/src/renderer/components/EQControl.vue index c64ad79..3874332 100644 --- a/src/renderer/components/EQControl.vue +++ b/src/renderer/components/EQControl.vue @@ -1,7 +1,8 @@ \ No newline at end of file + diff --git a/src/renderer/components/settings/ProxySettings.vue b/src/renderer/components/settings/ProxySettings.vue index c00a869..78a96fd 100644 --- a/src/renderer/components/settings/ProxySettings.vue +++ b/src/renderer/components/settings/ProxySettings.vue @@ -46,10 +46,10 @@ \ No newline at end of file + diff --git a/src/renderer/components/settings/ServerSetting.vue b/src/renderer/components/settings/ServerSetting.vue index 653b288..4334867 100644 --- a/src/renderer/components/settings/ServerSetting.vue +++ b/src/renderer/components/settings/ServerSetting.vue @@ -24,13 +24,20 @@
-
- - + +
-

{{ t('settings.remoteControl.accessInfo') }}

- - http://localhost:{{ remoteControlConfig.port }}/ - + http://localhost:{{ remoteControlConfig.port }}/
- - http://{{ ip }}:{{ remoteControlConfig.port }}/ - + http://{{ ip }}:{{ remoteControlConfig.port }}/
@@ -99,10 +98,10 @@ + + +} + diff --git a/src/renderer/views/history/index.vue b/src/renderer/views/history/index.vue index 23f8887..b384d48 100644 --- a/src/renderer/views/history/index.vue +++ b/src/renderer/views/history/index.vue @@ -32,7 +32,7 @@
微信赞赏