mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-09 10:50:50 +08:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88e1c2cd81 | ||
|
|
18853d401f | ||
|
|
6b27116584 | ||
|
|
283a123590 | ||
|
|
1597fbf108 | ||
|
|
3ba85f34ed | ||
|
|
daa8e7514d | ||
|
|
aeb7f0361d | ||
|
|
16aeaf2948 | ||
|
|
09ccd9f2a6 | ||
|
|
679089eda9 | ||
|
|
f2eba9a6d0 | ||
|
|
306215669d | ||
|
|
b7a58a0073 | ||
|
|
8fb382e21f | ||
|
|
c08c2cbf19 | ||
|
|
d1f5c8af84 | ||
|
|
d5ba218b10 | ||
|
|
e489ab46b5 | ||
|
|
9b3019d04b | ||
|
|
1e213388c1 | ||
|
|
67ef4d7221 | ||
|
|
62a504e7d3 | ||
|
|
386db7384d | ||
|
|
2cc03cb080 | ||
|
|
7891bf45fd | ||
|
|
2f339b1373 | ||
|
|
749a2a69c4 | ||
|
|
5b97010b32 | ||
|
|
694dff425b |
@@ -1,4 +0,0 @@
|
||||
node_modules
|
||||
dist
|
||||
out
|
||||
.gitignore
|
||||
137
.eslintrc.cjs
137
.eslintrc.cjs
@@ -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'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
2
.github/ISSUE_TEMPLATE/bug-report.zh-CN.yml
vendored
2
.github/ISSUE_TEMPLATE/bug-report.zh-CN.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: 反馈 Bug
|
||||
description: 通过 github 模板进行 Bug 反馈。
|
||||
title: "描述问题的标题"
|
||||
title: '描述问题的标题'
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/config.yml
vendored
6
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name:
|
||||
url:
|
||||
about:
|
||||
- name:
|
||||
url:
|
||||
about:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: 反馈新功能
|
||||
description: 通过 github 模板进行新功能反馈。
|
||||
title: "描述问题的标题"
|
||||
title: '描述问题的标题'
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
1
.github/issue-shoot.md
vendored
1
.github/issue-shoot.md
vendored
@@ -1,4 +1,5 @@
|
||||
## IssueShoot
|
||||
|
||||
- 预估时长: {{ .duration }}
|
||||
- 期望完成时间: {{ .deadline }}
|
||||
- 开发难度: {{ .level }}
|
||||
|
||||
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -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 }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
6
.github/workflows/deploy.yml
vendored
6
.github/workflows/deploy.yml
vendored
@@ -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)"
|
||||
echo "部署完成于 $(date)"
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
singleQuote: true
|
||||
semi: true
|
||||
printWidth: 100
|
||||
trailingComma: none
|
||||
endOfLine: auto
|
||||
67
CHANGELOG.md
67
CHANGELOG.md
@@ -1,58 +1,39 @@
|
||||
# 更新日志
|
||||
|
||||
## v4.8.1
|
||||
> 如果更新遇到问题,请前往 <a href="http://donate.alger.fun/download" target="_blank">下载 AlgerMusicPlayer</a>
|
||||
|
||||
> 请我喝咖啡(支持作者) ☕️ <a href="http://donate.alger.fun/donate" target="_blank" style="color: red; font-weight: bold;">赏你</a>
|
||||
|
||||
> 帮我点个 star <a href="https://github.com/algerkong/AlgerMusicPlayer" target="_blank">github star</a>
|
||||
|
||||
> 微信公众号 微信搜索 <span style="font-weight: bold;">AlgerMusic</span>
|
||||
|
||||
> QQ频道 AlgerMusic <a href="https://pd.qq.com/s/cs056n33q?b=5" target="_blank">加入频道</a>
|
||||
|
||||
### 🐛 Bug 修复
|
||||
- 修复无法快捷键调整问题
|
||||
|
||||
### 🎨 优化
|
||||
- 优化音乐资源解析
|
||||
- 去除无用代码,优化加载速度
|
||||
|
||||
|
||||
|
||||
## v4.8.0
|
||||
## v4.9.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))
|
||||
- 新增歌单导入功能 ([edd393c](https://github.com/algerkong/AlgerMusicPlayer/commit/edd393c))
|
||||
- 列表添加多选下载功能,支持批量选择和下载音乐 ([1221101](https://github.com/algerkong/AlgerMusicPlayer/commit/1221101)),([8988cdb](https://github.com/algerkong/AlgerMusicPlayer/commit/8988cdb)),([21b2fc0](https://github.com/algerkong/AlgerMusicPlayer/commit/21b2fc0))
|
||||
- Windows添加任务栏缩略图播放控制按钮 ([9bec67e](https://github.com/algerkong/AlgerMusicPlayer/commit/9bec67e)),([58ab990](https://github.com/algerkong/AlgerMusicPlayer/commit/58ab990)) 感谢[HE Cai](https://github.com/hecai84)的pr
|
||||
- 添加主窗口自适应大小功能,页面缩放功能,支持缩放因子的调整和重置 ([6170047](https://github.com/algerkong/AlgerMusicPlayer/commit/6170047)), ([e46df8a](https://github.com/algerkong/AlgerMusicPlayer/commit/e46df8a))
|
||||
- 添加歌词时间矫正功能,支持增加和减少矫正时间 ([c975344](https://github.com/algerkong/AlgerMusicPlayer/commit/c975344))
|
||||
|
||||
- 重新设计pc端歌词页面Mini播放栏
|
||||
- 添加清除歌曲自定义解析功能
|
||||
- 添加Cookie登录功能及自动获取等相关管理设置 ([16aeaf2](https://github.com/algerkong/AlgerMusicPlayer/commit/16aeaf2)) - 支持通过Cookie方式登录,提供更便捷的登录体验
|
||||
- 添加UID登录功能,优化登录流程 ([daa8e75](https://github.com/algerkong/AlgerMusicPlayer/commit/daa8e75)) - 新增用户ID直接登录方式
|
||||
- 添加主题根据系统切换功能 ([d5ba218](https://github.com/algerkong/AlgerMusicPlayer/commit/d5ba218)) - 支持跟随系统主题自动切换明暗模式
|
||||
- 桌面歌词添加主题颜色面板组件 ([d1f5c8a](https://github.com/algerkong/AlgerMusicPlayer/commit/d1f5c8a)) - 为桌面歌词提供丰富的主题颜色自定义选项
|
||||
- 增强播放速度控制,添加滑块控制并改善播放安全性 ([8fb382e](https://github.com/algerkong/AlgerMusicPlayer/commit/8fb382e)) 感谢[Qumo](https://github.com/Hellodwadawd12312312)的pr
|
||||
- 添加日语和韩语国际化支持,并优化语言相关代码 ([3062156](https://github.com/algerkong/AlgerMusicPlayer/commit/3062156))
|
||||
- 添加繁体中文本地化支持 ([2cc03cb](https://github.com/algerkong/AlgerMusicPlayer/commit/2cc03cb)) 感谢[dongguacute](https://github.com/dongguacute)的pr
|
||||
- 播放速度设置弹窗标题添加速度显示 ([aeb7f03](https://github.com/algerkong/AlgerMusicPlayer/commit/aeb7f03))
|
||||
|
||||
### 🐛 Bug 修复
|
||||
- 修复音频初始化音量问题,完善翻译 ([#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))
|
||||
- 修复设置页面动画速度滑块样式和文本错误 ([e5adb8a](https://github.com/algerkong/AlgerMusicPlayer/commit/e5adb8a))
|
||||
- 修复音频服务相关问题 ([090103b](https://github.com/algerkong/AlgerMusicPlayer/commit/090103b)),([5ee60d7](https://github.com/algerkong/AlgerMusicPlayer/commit/5ee60d7))
|
||||
- 修复播放栏无法控制隐藏问题 ([d227ac8](https://github.com/algerkong/AlgerMusicPlayer/commit/d227ac8))
|
||||
|
||||
- 修复mac快捷键关闭窗口报错的问题 ([67ef4d7](https://github.com/algerkong/AlgerMusicPlayer/commit/67ef4d7))
|
||||
- 修复mini窗口恢复时导致的应用窗口变小问题 ([9b3019d](https://github.com/algerkong/AlgerMusicPlayer/commit/9b3019d))
|
||||
- 修复歌单列表页面翻页类型问题 ([e489ab4](https://github.com/algerkong/AlgerMusicPlayer/commit/e489ab4))
|
||||
- 修复歌曲初始化问题 ([b7a58a0](https://github.com/algerkong/AlgerMusicPlayer/commit/b7a58a0))
|
||||
- 修复音量调整不同步的问题 ([679089e](https://github.com/algerkong/AlgerMusicPlayer/commit/679089e))
|
||||
- 修复菜单显示不全的问题,添加滚动条 ([09ccd9f](https://github.com/algerkong/AlgerMusicPlayer/commit/09ccd9f))
|
||||
|
||||
### 🎨 优化
|
||||
- 优化歌曲列表组件布局([fabcf28](https://github.com/algerkong/AlgerMusicPlayer/commit/fabcf28))
|
||||
- 重构播放控制逻辑,添加播放进度恢复功能并清理无用代码 ([b9c38d2](https://github.com/algerkong/AlgerMusicPlayer/commit/b9c38d2))
|
||||
- 优化提示组件,支持位置和图标显示选项 ([155bdf2](https://github.com/algerkong/AlgerMusicPlayer/commit/155bdf2))
|
||||
- 添加mini播放栏鼠标滚轮调整音量,并优化音量滑块数字不展示问题 ([5c72785](https://github.com/algerkong/AlgerMusicPlayer/commit/5c72785))
|
||||
- 优化收藏和历史列表组件,添加加载状态管理和动画效果 ([5070a08](https://github.com/algerkong/AlgerMusicPlayer/commit/5070a08))
|
||||
- 翻译优化
|
||||
- 代码优化
|
||||
|
||||
- 更新 eslint 和 prettier 配置,格式化代码 ([c08c2cb](https://github.com/algerkong/AlgerMusicPlayer/commit/c08c2cb))
|
||||
- 优化类型处理和登录功能 ([3ba85f3](https://github.com/algerkong/AlgerMusicPlayer/commit/3ba85f3))
|
||||
- 优化Cookie相关文字描述 ([1597fbf](https://github.com/algerkong/AlgerMusicPlayer/commit/1597fbf))
|
||||
|
||||
## 赞赏支持☕️
|
||||
|
||||
[赞赏列表](http://donate.alger.fun/)
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th style="text-align:center">微信赞赏</th>
|
||||
|
||||
40
DEV.md
40
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 下
|
||||
|
||||
打包后的文件在 /out/renderer 下
|
||||
|
||||
25
README.md
25
README.md
@@ -1,4 +1,3 @@
|
||||
|
||||
<h2 align="center">🎵 Alger Music Player</h2>
|
||||
<div align="center">
|
||||
<div align="center">
|
||||
@@ -23,10 +22,10 @@
|
||||
<a href="https://hellogithub.com/repository/607b849c598d48e08fe38789d156ebdc" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=607b849c598d48e08fe38789d156ebdc&claim_uid=ObuMXUfeHBmk9TI&theme=neutral" alt="Featured|HelloGitHub" width="160" height="32" /></a>
|
||||
</div>
|
||||
|
||||
|
||||
[项目下安装以及常用问题文档](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/)
|
||||
|
||||
## 软件截图
|
||||
|
||||

|
||||

|
||||

|
||||
@@ -70,31 +71,31 @@
|
||||

|
||||
|
||||
## 项目启动
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## 开发文档
|
||||
|
||||
点击这里[开发文档](./DEV.md)
|
||||
|
||||
|
||||
|
||||
|
||||
## 赞赏☕️
|
||||
|
||||
[赞赏列表](http://donate.alger.fun/)
|
||||
| 微信赞赏 | 支付宝赞赏 |
|
||||
| 微信赞赏 | 支付宝赞赏 |
|
||||
| :--------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------: |
|
||||
| <img src="https://github.com/algerkong/algerkong/blob/main/wechat.jpg?raw=true" alt="WeChat QRcode" width=200> <br><small>喝点咖啡继续干</small> | <img src="https://github.com/algerkong/algerkong/blob/main/alipay.jpg?raw=true" alt="Wechat QRcode" width=200> <br><small>来包辣条吧~</small> |
|
||||
|
||||
|
||||
## 项目统计
|
||||
|
||||
[](https://starchart.cc/algerkong/AlgerMusicPlayer)
|
||||

|
||||
|
||||
|
||||

|
||||
|
||||
## 欢迎提Issues
|
||||
|
||||
## 声明
|
||||
|
||||
本软件仅用于学习交流,禁止用于商业用途,否则后果自负。
|
||||
希望大家还是要多多支持官方正版,此软件仅用作开发教学。
|
||||
|
||||
@@ -18,7 +18,8 @@ export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve('src/renderer'),
|
||||
'@renderer': resolve('src/renderer')
|
||||
'@renderer': resolve('src/renderer'),
|
||||
'@i18n': resolve('src/i18n')
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
@@ -39,7 +40,7 @@ export default defineConfig({
|
||||
],
|
||||
publicDir: resolve('resources'),
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
host: '0.0.0.0'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
237
eslint.config.mjs
Normal file
237
eslint.config.mjs
Normal file
@@ -0,0 +1,237 @@
|
||||
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': [
|
||||
'error',
|
||||
// we are only using this rule to check for unused arguments since TS
|
||||
// catches unused variables but not args.
|
||||
{ varsIgnorePattern: '.*', args: 'none' }
|
||||
],
|
||||
'@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': [
|
||||
'error',
|
||||
// we are only using this rule to check for unused arguments since TS
|
||||
// catches unused variables but not args.
|
||||
{ varsIgnorePattern: '.*', args: 'none' }
|
||||
],
|
||||
'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': [
|
||||
'error',
|
||||
// we are only using this rule to check for unused arguments since TS
|
||||
// catches unused variables but not args.
|
||||
{ varsIgnorePattern: '.*', args: 'none' }
|
||||
]
|
||||
}
|
||||
}
|
||||
];
|
||||
20
package.json
20
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "AlgerMusicPlayer",
|
||||
"version": "4.8.1",
|
||||
"version": "4.9.0",
|
||||
"description": "Alger Music Player",
|
||||
"author": "Alger <algerkc@qq.com>",
|
||||
"main": "./out/main/index.js",
|
||||
@@ -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",
|
||||
|
||||
10
prettier.config.js
Normal file
10
prettier.config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* @type {import('prettier').Config}
|
||||
*/
|
||||
module.exports = {
|
||||
singleQuote: true,
|
||||
semi: true,
|
||||
printWidth: 100,
|
||||
trailingComma: 'none',
|
||||
endOfLine: 'auto'
|
||||
};
|
||||
@@ -1,486 +1,505 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>AlgerMusicPlayer 远程控制</title>
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #007AFF;
|
||||
--secondary-color: #5AC8FA;
|
||||
--success-color: #4CD964;
|
||||
--danger-color: #FF3B30;
|
||||
--warning-color: #FF9500;
|
||||
--light-gray: #F2F2F7;
|
||||
--medium-gray: #E5E5EA;
|
||||
--dark-gray: #8E8E93;
|
||||
--text-color: #000000;
|
||||
--text-secondary: #6C6C6C;
|
||||
--background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<title>AlgerMusicPlayer 远程控制</title>
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #0A84FF;
|
||||
--secondary-color: #64D2FF;
|
||||
--success-color: #30D158;
|
||||
--danger-color: #FF453A;
|
||||
--warning-color: #FF9F0A;
|
||||
--light-gray: #1C1C1E;
|
||||
--medium-gray: #2C2C2E;
|
||||
--dark-gray: #8E8E93;
|
||||
--text-color: #FFFFFF;
|
||||
--text-secondary: #AEAEB2;
|
||||
--background-color: #000000;
|
||||
--primary-color: #007aff;
|
||||
--secondary-color: #5ac8fa;
|
||||
--success-color: #4cd964;
|
||||
--danger-color: #ff3b30;
|
||||
--warning-color: #ff9500;
|
||||
--light-gray: #f2f2f7;
|
||||
--medium-gray: #e5e5ea;
|
||||
--dark-gray: #8e8e93;
|
||||
--text-color: #000000;
|
||||
--text-secondary: #6c6c6c;
|
||||
--background-color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text-color);
|
||||
background-color: var(--light-gray);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background-color: var(--background-color);
|
||||
padding: 12px 16px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid var(--medium-gray);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 540px;
|
||||
margin: 0 auto;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--background-color);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.song-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.song-cover {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||
background-color: var(--medium-gray);
|
||||
}
|
||||
|
||||
.song-details {
|
||||
margin-left: 16px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.song-details h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.song-details p {
|
||||
margin: 4px 0 0;
|
||||
font-size: 15px;
|
||||
color: var(--text-secondary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.play-state {
|
||||
font-size: 14px;
|
||||
color: var(--primary-color);
|
||||
margin-top: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.play-state::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.playing .play-state::before {
|
||||
background-color: var(--success-color);
|
||||
}
|
||||
|
||||
.paused .play-state::before {
|
||||
background-color: var(--warning-color);
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.extra-controls {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--background-color);
|
||||
color: var(--primary-color);
|
||||
border: 1px solid var(--medium-gray);
|
||||
padding: 16px 0;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: scale(0.97);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.btn::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--primary-color);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.btn:active::before {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.btn svg {
|
||||
margin-bottom: 8px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
fill: var(--primary-color);
|
||||
}
|
||||
|
||||
.btn-play svg {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.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;
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--primary-color: #0a84ff;
|
||||
--secondary-color: #64d2ff;
|
||||
--success-color: #30d158;
|
||||
--danger-color: #ff453a;
|
||||
--warning-color: #ff9f0a;
|
||||
--light-gray: #1c1c1e;
|
||||
--medium-gray: #2c2c2e;
|
||||
--dark-gray: #8e8e93;
|
||||
--text-color: #ffffff;
|
||||
--text-secondary: #aeaeb2;
|
||||
--background-color: #000000;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text-color);
|
||||
background-color: var(--light-gray);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background-color: var(--background-color);
|
||||
padding: 12px 16px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid var(--medium-gray);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 540px;
|
||||
margin: 0 auto;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--background-color);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.song-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.song-cover {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
background-color: var(--medium-gray);
|
||||
}
|
||||
|
||||
.song-details {
|
||||
margin-left: 16px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.song-details h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.song-details p {
|
||||
margin: 4px 0 0;
|
||||
font-size: 15px;
|
||||
color: var(--text-secondary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.play-state {
|
||||
font-size: 14px;
|
||||
color: var(--primary-color);
|
||||
margin-top: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.play-state::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.playing .play-state::before {
|
||||
background-color: var(--success-color);
|
||||
}
|
||||
|
||||
.paused .play-state::before {
|
||||
background-color: var(--warning-color);
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.extra-controls {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 0;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--background-color);
|
||||
color: var(--primary-color);
|
||||
border: 1px solid var(--medium-gray);
|
||||
padding: 16px 0;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.btn:active {
|
||||
transform: scale(0.97);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.btn::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--primary-color);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.btn:active::before {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.btn svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-bottom: 8px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
fill: var(--primary-color);
|
||||
}
|
||||
|
||||
.btn-play svg {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>AlgerMusicPlayer 远程控制</h1>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="card" id="songInfoCard">
|
||||
<div class="song-info">
|
||||
<img id="songCover" class="song-cover" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%238E8E93'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 14.5c-2.49 0-4.5-2.01-4.5-4.5S9.51 7.5 12 7.5s4.5 2.01 4.5 4.5-2.01 4.5-4.5 4.5zm0-5.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1z'/%3E%3C/svg%3E" alt="封面">
|
||||
<div class="song-details">
|
||||
<h2 id="songTitle">未在播放</h2>
|
||||
<p id="songArtist">--</p>
|
||||
<div class="play-state" id="playState">未播放</div>
|
||||
.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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>AlgerMusicPlayer 远程控制</h1>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="card" id="songInfoCard">
|
||||
<div class="song-info">
|
||||
<img
|
||||
id="songCover"
|
||||
class="song-cover"
|
||||
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%238E8E93'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 14.5c-2.49 0-4.5-2.01-4.5-4.5S9.51 7.5 12 7.5s4.5 2.01 4.5 4.5-2.01 4.5-4.5 4.5zm0-5.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1z'/%3E%3C/svg%3E"
|
||||
alt="封面"
|
||||
/>
|
||||
<div class="song-details">
|
||||
<h2 id="songTitle">未在播放</h2>
|
||||
<p id="songArtist">--</p>
|
||||
<div class="play-state" id="playState">未播放</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="controls">
|
||||
<button id="prevBtn" class="btn">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M6 6h2v12H6zm3.5 6l8.5 6V6z" />
|
||||
</svg>
|
||||
上一首
|
||||
</button>
|
||||
<button id="playBtn" class="btn btn-play">
|
||||
<svg id="playIcon" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M8 5v14l11-7z" />
|
||||
</svg>
|
||||
播放/暂停
|
||||
</button>
|
||||
<button id="nextBtn" class="btn">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z" />
|
||||
</svg>
|
||||
下一首
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="extra-controls">
|
||||
<button id="volumeDownBtn" class="btn">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z"
|
||||
/>
|
||||
</svg>
|
||||
音量-
|
||||
</button>
|
||||
<button id="volumeUpBtn" class="btn">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"
|
||||
/>
|
||||
</svg>
|
||||
音量+
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="extra-controls">
|
||||
<button id="favoriteBtn" class="btn">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
|
||||
/>
|
||||
</svg>
|
||||
收藏
|
||||
</button>
|
||||
<button id="refreshBtn" class="btn">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"
|
||||
/>
|
||||
</svg>
|
||||
刷新
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="controls">
|
||||
<button id="prevBtn" class="btn">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M6 6h2v12H6zm3.5 6l8.5 6V6z"/>
|
||||
</svg>
|
||||
上一首
|
||||
</button>
|
||||
<button id="playBtn" class="btn btn-play">
|
||||
<svg id="playIcon" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M8 5v14l11-7z"/>
|
||||
</svg>
|
||||
播放/暂停
|
||||
</button>
|
||||
<button id="nextBtn" class="btn">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"/>
|
||||
</svg>
|
||||
下一首
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="extra-controls">
|
||||
<button id="volumeDownBtn" class="btn">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z"/>
|
||||
</svg>
|
||||
音量-
|
||||
</button>
|
||||
<button id="volumeUpBtn" class="btn">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
|
||||
</svg>
|
||||
音量+
|
||||
</button>
|
||||
</div>
|
||||
<div class="status-bar">
|
||||
<span id="status" class="status-message">准备就绪</span>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="extra-controls">
|
||||
<button id="favoriteBtn" class="btn">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
|
||||
</svg>
|
||||
收藏
|
||||
</button>
|
||||
<button id="refreshBtn" class="btn">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
|
||||
</svg>
|
||||
刷新
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// 页面加载完成后执行
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 获取DOM元素
|
||||
const songInfoCard = document.getElementById('songInfoCard');
|
||||
const songTitle = document.getElementById('songTitle');
|
||||
const songArtist = document.getElementById('songArtist');
|
||||
const songCover = document.getElementById('songCover');
|
||||
const playState = document.getElementById('playState');
|
||||
const playBtn = document.getElementById('playBtn');
|
||||
const playIcon = document.getElementById('playIcon');
|
||||
const prevBtn = document.getElementById('prevBtn');
|
||||
const nextBtn = document.getElementById('nextBtn');
|
||||
const favoriteBtn = document.getElementById('favoriteBtn');
|
||||
const volumeUpBtn = document.getElementById('volumeUpBtn');
|
||||
const volumeDownBtn = document.getElementById('volumeDownBtn');
|
||||
const refreshBtn = document.getElementById('refreshBtn');
|
||||
const status = document.getElementById('status');
|
||||
|
||||
<div class="status-bar">
|
||||
<span id="status" class="status-message">准备就绪</span>
|
||||
</div>
|
||||
let isPlaying = false;
|
||||
|
||||
<script>
|
||||
// 页面加载完成后执行
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 获取DOM元素
|
||||
const songInfoCard = document.getElementById('songInfoCard');
|
||||
const songTitle = document.getElementById('songTitle');
|
||||
const songArtist = document.getElementById('songArtist');
|
||||
const songCover = document.getElementById('songCover');
|
||||
const playState = document.getElementById('playState');
|
||||
const playBtn = document.getElementById('playBtn');
|
||||
const playIcon = document.getElementById('playIcon');
|
||||
const prevBtn = document.getElementById('prevBtn');
|
||||
const nextBtn = document.getElementById('nextBtn');
|
||||
const favoriteBtn = document.getElementById('favoriteBtn');
|
||||
const volumeUpBtn = document.getElementById('volumeUpBtn');
|
||||
const volumeDownBtn = document.getElementById('volumeDownBtn');
|
||||
const refreshBtn = document.getElementById('refreshBtn');
|
||||
const status = document.getElementById('status');
|
||||
|
||||
let isPlaying = false;
|
||||
|
||||
// 显示状态消息并淡出
|
||||
function showStatus(message, autoClear = true) {
|
||||
status.textContent = message;
|
||||
status.classList.remove('fade');
|
||||
|
||||
if (autoClear) {
|
||||
setTimeout(() => {
|
||||
status.classList.add('fade');
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新播放/暂停图标
|
||||
function updatePlayIcon() {
|
||||
if (isPlaying) {
|
||||
playIcon.innerHTML = '<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>';
|
||||
} else {
|
||||
playIcon.innerHTML = '<path d="M8 5v14l11-7z"/>';
|
||||
}
|
||||
}
|
||||
|
||||
// 更新状态的函数
|
||||
async function updateStatus() {
|
||||
try {
|
||||
showStatus('获取播放状态...', false);
|
||||
|
||||
const response = await fetch('/api/status');
|
||||
const data = await response.json();
|
||||
|
||||
// 更新播放状态
|
||||
isPlaying = data.isPlaying;
|
||||
updatePlayIcon();
|
||||
|
||||
// 更新UI
|
||||
if (data.currentSong) {
|
||||
songTitle.textContent = data.currentSong.name || '未知歌曲';
|
||||
|
||||
if (data.currentSong.ar && data.currentSong.ar.length) {
|
||||
songArtist.textContent = data.currentSong.ar.map(a => a.name).join(', ');
|
||||
} else if (data.currentSong.artists && data.currentSong.artists.length) {
|
||||
songArtist.textContent = data.currentSong.artists.map(a => a.name).join(', ');
|
||||
} else {
|
||||
songArtist.textContent = '未知艺术家';
|
||||
}
|
||||
|
||||
if (data.currentSong.picUrl) {
|
||||
songCover.src = data.currentSong.picUrl;
|
||||
}
|
||||
} else {
|
||||
songTitle.textContent = '未在播放';
|
||||
songArtist.textContent = '--';
|
||||
songCover.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%238E8E93'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 14.5c-2.49 0-4.5-2.01-4.5-4.5S9.51 7.5 12 7.5s4.5 2.01 4.5 4.5-2.01 4.5-4.5 4.5zm0-5.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1z'/%3E%3C/svg%3E";
|
||||
// 显示状态消息并淡出
|
||||
function showStatus(message, autoClear = true) {
|
||||
status.textContent = message;
|
||||
status.classList.remove('fade');
|
||||
|
||||
if (autoClear) {
|
||||
setTimeout(() => {
|
||||
status.classList.add('fade');
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// 更新播放状态
|
||||
playState.textContent = isPlaying ? '正在播放' : '已暂停';
|
||||
songInfoCard.className = isPlaying ? 'card playing' : 'card paused';
|
||||
|
||||
showStatus('已更新', true);
|
||||
} catch (error) {
|
||||
console.error('获取状态失败:', error);
|
||||
showStatus('获取状态失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 发送命令的函数
|
||||
async function sendCommand(endpoint) {
|
||||
try {
|
||||
showStatus('发送命令中...', false);
|
||||
const response = await fetch('/api/' + endpoint, { method: 'POST' });
|
||||
const data = await response.json();
|
||||
|
||||
showStatus(data.message || '命令已发送');
|
||||
|
||||
// 稍等后更新状态
|
||||
setTimeout(updateStatus, 500);
|
||||
} catch (error) {
|
||||
console.error('发送命令失败:', error);
|
||||
showStatus('发送命令失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定按钮事件
|
||||
playBtn.addEventListener('click', () => sendCommand('toggle-play'));
|
||||
prevBtn.addEventListener('click', () => sendCommand('prev'));
|
||||
nextBtn.addEventListener('click', () => sendCommand('next'));
|
||||
favoriteBtn.addEventListener('click', () => sendCommand('toggle-favorite'));
|
||||
volumeUpBtn.addEventListener('click', () => sendCommand('volume-up'));
|
||||
volumeDownBtn.addEventListener('click', () => sendCommand('volume-down'));
|
||||
refreshBtn.addEventListener('click', updateStatus);
|
||||
|
||||
// 初始加载状态
|
||||
updateStatus();
|
||||
|
||||
// 每1秒更新一次状态
|
||||
setInterval(updateStatus, 1000);
|
||||
|
||||
// 添加触摸反馈
|
||||
const buttons = document.querySelectorAll('.btn');
|
||||
buttons.forEach(btn => {
|
||||
btn.addEventListener('touchstart', function() {
|
||||
this.style.transform = 'scale(0.97)';
|
||||
this.style.opacity = '0.7';
|
||||
});
|
||||
|
||||
btn.addEventListener('touchend', function() {
|
||||
this.style.transform = 'scale(1)';
|
||||
this.style.opacity = '1';
|
||||
});
|
||||
});
|
||||
|
||||
// 检测深色模式变化
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
||||
// 更新播放/暂停图标
|
||||
function updatePlayIcon() {
|
||||
if (isPlaying) {
|
||||
playIcon.innerHTML = '<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>';
|
||||
} else {
|
||||
playIcon.innerHTML = '<path d="M8 5v14l11-7z"/>';
|
||||
}
|
||||
}
|
||||
|
||||
// 更新状态的函数
|
||||
async function updateStatus() {
|
||||
try {
|
||||
showStatus('获取播放状态...', false);
|
||||
|
||||
const response = await fetch('/api/status');
|
||||
const data = await response.json();
|
||||
|
||||
// 更新播放状态
|
||||
isPlaying = data.isPlaying;
|
||||
updatePlayIcon();
|
||||
|
||||
// 更新UI
|
||||
if (data.currentSong) {
|
||||
songTitle.textContent = data.currentSong.name || '未知歌曲';
|
||||
|
||||
if (data.currentSong.ar && data.currentSong.ar.length) {
|
||||
songArtist.textContent = data.currentSong.ar.map((a) => a.name).join(', ');
|
||||
} else if (data.currentSong.artists && data.currentSong.artists.length) {
|
||||
songArtist.textContent = data.currentSong.artists.map((a) => a.name).join(', ');
|
||||
} else {
|
||||
songArtist.textContent = '未知艺术家';
|
||||
}
|
||||
|
||||
if (data.currentSong.picUrl) {
|
||||
songCover.src = data.currentSong.picUrl;
|
||||
}
|
||||
} else {
|
||||
songTitle.textContent = '未在播放';
|
||||
songArtist.textContent = '--';
|
||||
songCover.src =
|
||||
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%238E8E93'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 14.5c-2.49 0-4.5-2.01-4.5-4.5S9.51 7.5 12 7.5s4.5 2.01 4.5 4.5-2.01 4.5-4.5 4.5zm0-5.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1z'/%3E%3C/svg%3E";
|
||||
}
|
||||
|
||||
// 更新播放状态
|
||||
playState.textContent = isPlaying ? '正在播放' : '已暂停';
|
||||
songInfoCard.className = isPlaying ? 'card playing' : 'card paused';
|
||||
|
||||
showStatus('已更新', true);
|
||||
} catch (error) {
|
||||
console.error('获取状态失败:', error);
|
||||
showStatus('获取状态失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 发送命令的函数
|
||||
async function sendCommand(endpoint) {
|
||||
try {
|
||||
showStatus('发送命令中...', false);
|
||||
const response = await fetch('/api/' + endpoint, { method: 'POST' });
|
||||
const data = await response.json();
|
||||
|
||||
showStatus(data.message || '命令已发送');
|
||||
|
||||
// 稍等后更新状态
|
||||
setTimeout(updateStatus, 500);
|
||||
} catch (error) {
|
||||
console.error('发送命令失败:', error);
|
||||
showStatus('发送命令失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定按钮事件
|
||||
playBtn.addEventListener('click', () => sendCommand('toggle-play'));
|
||||
prevBtn.addEventListener('click', () => sendCommand('prev'));
|
||||
nextBtn.addEventListener('click', () => sendCommand('next'));
|
||||
favoriteBtn.addEventListener('click', () => sendCommand('toggle-favorite'));
|
||||
volumeUpBtn.addEventListener('click', () => sendCommand('volume-up'));
|
||||
volumeDownBtn.addEventListener('click', () => sendCommand('volume-down'));
|
||||
refreshBtn.addEventListener('click', updateStatus);
|
||||
|
||||
// 初始加载状态
|
||||
updateStatus();
|
||||
|
||||
// 每1秒更新一次状态
|
||||
setInterval(updateStatus, 1000);
|
||||
|
||||
// 添加触摸反馈
|
||||
const buttons = document.querySelectorAll('.btn');
|
||||
buttons.forEach((btn) => {
|
||||
btn.addEventListener('touchstart', function () {
|
||||
this.style.transform = 'scale(0.97)';
|
||||
this.style.opacity = '0.7';
|
||||
});
|
||||
|
||||
btn.addEventListener('touchend', function () {
|
||||
this.style.transform = 'scale(1)';
|
||||
this.style.opacity = '1';
|
||||
});
|
||||
});
|
||||
|
||||
// 检测深色模式变化
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
||||
updateStatus();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -60,7 +60,7 @@ export default {
|
||||
wechatQR: 'Wechat QR code',
|
||||
coffeeDesc: 'A cup of coffee, a support',
|
||||
coffeeDescLinkText: 'View more',
|
||||
qqGroup: 'QQ group: algermusic',
|
||||
groupText: 'Wechat Public Account: AlgerMusic',
|
||||
messages: {
|
||||
copySuccess: 'Copied to clipboard'
|
||||
},
|
||||
@@ -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',
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import artist from './artist';
|
||||
import common from './common';
|
||||
import comp from './comp';
|
||||
import donation from './donation';
|
||||
import download from './download';
|
||||
import favorite from './favorite';
|
||||
import history from './history';
|
||||
import login from './login';
|
||||
import player from './player';
|
||||
import search from './search';
|
||||
import settings from './settings';
|
||||
import songItem from './songItem';
|
||||
import user from './user';
|
||||
|
||||
export default {
|
||||
common,
|
||||
donation,
|
||||
favorite,
|
||||
history,
|
||||
login,
|
||||
player,
|
||||
search,
|
||||
settings,
|
||||
songItem,
|
||||
user,
|
||||
download,
|
||||
comp,
|
||||
artist
|
||||
};
|
||||
@@ -1,22 +1,62 @@
|
||||
export default {
|
||||
title: {
|
||||
qr: 'QR Code Login',
|
||||
phone: 'Phone Login'
|
||||
phone: 'Phone Login',
|
||||
cookie: 'Cookie Login',
|
||||
uid: 'UID Login'
|
||||
},
|
||||
qrTip: 'Scan with NetEase Cloud Music APP',
|
||||
phoneTip: 'Login with NetEase Cloud account',
|
||||
tokenTip: 'Enter a valid NetEase Cloud Music Cookie to login',
|
||||
uidTip: 'Enter User ID for quick login',
|
||||
placeholder: {
|
||||
phone: 'Phone Number',
|
||||
password: 'Password'
|
||||
password: 'Password',
|
||||
cookie: 'Please enter NetEase Cloud Music Cookie (token)',
|
||||
uid: 'Please enter User ID (UID)'
|
||||
},
|
||||
button: {
|
||||
login: 'Login',
|
||||
switchToQr: 'QR Code Login',
|
||||
switchToPhone: 'Phone Login'
|
||||
switchToPhone: 'Phone Login',
|
||||
switchToToken: 'Use Cookie Login',
|
||||
switchToUid: 'UID Login',
|
||||
backToQr: 'Back to QR Code Login',
|
||||
cookieLogin: 'Cookie Login',
|
||||
autoGetCookie: 'Auto Get Cookie',
|
||||
refresh: 'Click to Refresh',
|
||||
refreshing: 'Refreshing...',
|
||||
refreshQr: 'Refresh QR Code'
|
||||
},
|
||||
message: {
|
||||
loginSuccess: 'Login successful',
|
||||
loginFailed: 'Login failed',
|
||||
tokenLoginSuccess: 'Cookie login successful',
|
||||
uidLoginSuccess: 'UID login successful',
|
||||
loadError: 'Error loading login information',
|
||||
qrCheckError: 'Error checking QR code status'
|
||||
}
|
||||
qrCheckError: 'Error checking QR code status',
|
||||
tokenRequired: 'Please enter Cookie',
|
||||
tokenInvalid: 'Invalid Cookie, please check and try again',
|
||||
uidRequired: 'Please enter User ID',
|
||||
uidInvalid: 'Invalid User ID or user does not exist',
|
||||
uidLoginFailed: 'UID login failed, please check if User ID is correct',
|
||||
phoneRequired: 'Please enter phone number',
|
||||
passwordRequired: 'Please enter password',
|
||||
phoneLoginFailed: 'Phone login failed, please check if phone number and password are correct',
|
||||
autoGetCookieSuccess: 'Auto get Cookie successful',
|
||||
autoGetCookieFailed: 'Auto get Cookie failed',
|
||||
autoGetCookieTip: 'Will open NetEase Cloud Music login page, please complete login and close the window',
|
||||
qrCheckFailed: 'Failed to check QR code status, please refresh and try again',
|
||||
qrLoading: 'Loading QR code...',
|
||||
qrExpired: 'QR code has expired, please click to refresh',
|
||||
qrExpiredShort: 'QR code expired',
|
||||
qrExpiredWarning: 'QR code has expired, please click to refresh for a new one',
|
||||
qrScanned: 'QR code scanned, please confirm login on your phone',
|
||||
qrScannedShort: 'Scanned',
|
||||
qrScannedInfo: 'QR code scanned, please confirm login on your phone',
|
||||
qrConfirmed: 'Login successful, redirecting...',
|
||||
qrGenerating: 'Generating QR code...'
|
||||
},
|
||||
qrTitle: 'NetEase Cloud Music QR Code Login',
|
||||
uidWarning: 'Note: UID login is only for viewing user public information and cannot access features that require login permissions.'
|
||||
};
|
||||
|
||||
@@ -38,7 +38,8 @@ export default {
|
||||
failed: 'Reparse failed',
|
||||
warning: 'Please select a music source',
|
||||
bilibiliNotSupported: 'Bilibili videos do not support reparsing',
|
||||
processing: 'Processing...'
|
||||
processing: 'Processing...',
|
||||
clear: 'Clear Custom Source'
|
||||
},
|
||||
playBar: {
|
||||
expand: 'Expand Lyrics',
|
||||
@@ -62,7 +63,7 @@ export default {
|
||||
favorite: 'Favorite {name}',
|
||||
unFavorite: 'Unfavorite {name}',
|
||||
playbackSpeed: 'Playback Speed',
|
||||
advancedControls: 'Advanced Controls',
|
||||
advancedControls: 'Advanced Controls'
|
||||
},
|
||||
eq: {
|
||||
title: 'Equalizer',
|
||||
@@ -116,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?'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -15,8 +15,18 @@ export default {
|
||||
basic: {
|
||||
themeMode: 'Theme Mode',
|
||||
themeModeDesc: 'Switch between light/dark theme',
|
||||
autoTheme: 'Follow System',
|
||||
manualTheme: 'Manual Switch',
|
||||
language: 'Language Settings',
|
||||
languageDesc: 'Change display language',
|
||||
tokenManagement: 'Cookie Management',
|
||||
tokenManagementDesc: 'Manage NetEase Cloud Music login Cookie',
|
||||
tokenStatus: 'Current Cookie Status',
|
||||
tokenSet: 'Set',
|
||||
tokenNotSet: 'Not Set',
|
||||
setToken: 'Set Cookie',
|
||||
modifyToken: 'Modify Cookie',
|
||||
clearToken: 'Clear Cookie',
|
||||
font: 'Font Settings',
|
||||
fontDesc: 'Select fonts, prioritize fonts in order',
|
||||
fontScope: {
|
||||
@@ -64,11 +74,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',
|
||||
@@ -181,6 +193,7 @@ export default {
|
||||
hideLyrics: 'Hide Lyrics',
|
||||
hidePlayBar: 'Hide Play Bar',
|
||||
hideMiniPlayBar: 'Hide Mini Play Bar',
|
||||
showMiniPlayBar: 'Show Mini Play Bar',
|
||||
backgroundTheme: 'Background Theme',
|
||||
themeOptions: {
|
||||
default: 'Default',
|
||||
@@ -220,6 +233,26 @@ export default {
|
||||
lyricLines: 'Lyric Lines',
|
||||
mobileUnavailable: 'This setting is only available on mobile devices'
|
||||
},
|
||||
themeColor: {
|
||||
title: 'Lyric Theme Color',
|
||||
presetColors: 'Preset Colors',
|
||||
customColor: 'Custom Color',
|
||||
preview: 'Preview',
|
||||
previewText: 'Lyric Effect',
|
||||
colorNames: {
|
||||
'spotify-green': 'Spotify Green',
|
||||
'apple-blue': 'Apple Blue',
|
||||
'youtube-red': 'YouTube Red',
|
||||
orange: 'Vibrant Orange',
|
||||
purple: 'Mystic Purple',
|
||||
pink: 'Cherry Pink'
|
||||
},
|
||||
tooltips: {
|
||||
openColorPicker: 'Open Color Picker',
|
||||
closeColorPicker: 'Close Color Picker'
|
||||
},
|
||||
placeholder: '#1db954'
|
||||
},
|
||||
shortcutSettings: {
|
||||
title: 'Shortcut Settings',
|
||||
shortcut: 'Shortcut',
|
||||
@@ -258,6 +291,34 @@ 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:'
|
||||
},
|
||||
cookie: {
|
||||
title: 'Cookie Settings',
|
||||
description: 'Please enter NetEase Cloud Music Cookie:',
|
||||
placeholder: 'Please paste the complete Cookie...',
|
||||
help: {
|
||||
format: 'Cookie usually starts with "MUSIC_U="',
|
||||
source: 'Can be obtained from browser developer tools network requests',
|
||||
storage: 'Cookie will be automatically saved to local storage after setting'
|
||||
},
|
||||
action: {
|
||||
save: 'Save Cookie',
|
||||
paste: 'Paste',
|
||||
clear: 'Clear'
|
||||
},
|
||||
validation: {
|
||||
required: 'Please enter Cookie',
|
||||
format: 'Cookie format may be incorrect, please check if it contains MUSIC_U'
|
||||
},
|
||||
message: {
|
||||
saveSuccess: 'Cookie saved successfully',
|
||||
saveError: 'Failed to save Cookie',
|
||||
pasteSuccess: 'Pasted successfully',
|
||||
pasteError: 'Paste failed, please copy manually'
|
||||
},
|
||||
info: {
|
||||
length: 'Current length: {length} characters'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
38
src/i18n/lang/ja-JP/README.md
Normal file
38
src/i18n/lang/ja-JP/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# 日本語翻訳 (Japanese Translation)
|
||||
|
||||
このディレクトリには、AlgerMusicPlayerの日本語翻訳ファイルが含まれています。
|
||||
|
||||
## ファイル構成
|
||||
|
||||
- `artist.ts` - アーティスト関連の翻訳
|
||||
- `common.ts` - 共通の翻訳(ボタン、メッセージなど)
|
||||
- `comp.ts` - コンポーネント関連の翻訳
|
||||
- `donation.ts` - 寄付関連の翻訳
|
||||
- `download.ts` - ダウンロード管理の翻訳
|
||||
- `favorite.ts` - お気に入り機能の翻訳
|
||||
- `history.ts` - 履歴機能の翻訳
|
||||
- `login.ts` - ログイン関連の翻訳
|
||||
- `player.ts` - プレイヤー機能の翻訳
|
||||
- `search.ts` - 検索機能の翻訳
|
||||
- `settings.ts` - 設定画面の翻訳
|
||||
- `songItem.ts` - 楽曲アイテムの翻訳
|
||||
- `user.ts` - ユーザー関連の翻訳
|
||||
- `index.ts` - すべての翻訳をエクスポートするメインファイル
|
||||
|
||||
## 使用方法
|
||||
|
||||
アプリケーション内で言語を日本語に切り替えるには:
|
||||
|
||||
1. 設定画面を開く
|
||||
2. 「言語設定」セクションを見つける
|
||||
3. ドロップダウンメニューから「日本語」を選択
|
||||
|
||||
## 翻訳の改善
|
||||
|
||||
翻訳の改善や修正がある場合は、該当するファイルを編集してプルリクエストを送信してください。
|
||||
|
||||
## 注意事項
|
||||
|
||||
- すべての翻訳キーは中国語版と英語版に対応しています
|
||||
- 新しい機能が追加された場合は、対応する日本語翻訳も追加する必要があります
|
||||
- 文字化けを避けるため、ファイルはUTF-8エンコーディングで保存してください
|
||||
5
src/i18n/lang/ja-JP/artist.ts
Normal file
5
src/i18n/lang/ja-JP/artist.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
hotSongs: '人気楽曲',
|
||||
albums: 'アルバム',
|
||||
description: 'アーティスト紹介'
|
||||
};
|
||||
56
src/i18n/lang/ja-JP/common.ts
Normal file
56
src/i18n/lang/ja-JP/common.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
export default {
|
||||
play: '再生',
|
||||
next: '次の曲',
|
||||
previous: '前の曲',
|
||||
volume: '音量',
|
||||
settings: '設定',
|
||||
search: '検索',
|
||||
loading: '読み込み中...',
|
||||
loadingMore: 'さらに読み込み中...',
|
||||
alipay: 'Alipay',
|
||||
wechat: 'WeChat Pay',
|
||||
on: 'オン',
|
||||
off: 'オフ',
|
||||
show: '表示',
|
||||
hide: '非表示',
|
||||
confirm: '確認',
|
||||
cancel: 'キャンセル',
|
||||
configure: '設定',
|
||||
open: '開く',
|
||||
modify: '変更',
|
||||
success: '操作成功',
|
||||
error: '操作失敗',
|
||||
warning: '警告',
|
||||
info: 'お知らせ',
|
||||
save: '保存',
|
||||
delete: '削除',
|
||||
refresh: '更新',
|
||||
retry: '再試行',
|
||||
reset: 'リセット',
|
||||
back: '戻る',
|
||||
copySuccess: 'クリップボードにコピーしました',
|
||||
copyFailed: 'コピーに失敗しました',
|
||||
validation: {
|
||||
required: 'この項目は必須です',
|
||||
invalidInput: '無効な入力です',
|
||||
selectRequired: 'オプションを選択してください',
|
||||
numberRange: '{min}から{max}の間の数値を入力してください'
|
||||
},
|
||||
viewMore: 'もっと見る',
|
||||
noMore: 'これ以上ありません',
|
||||
selectAll: '全選択',
|
||||
expand: '展開',
|
||||
collapse: '折りたたみ',
|
||||
songCount: '{count}曲',
|
||||
language: '言語',
|
||||
tray: {
|
||||
show: '表示',
|
||||
quit: '終了',
|
||||
playPause: '再生/一時停止',
|
||||
prev: '前の曲',
|
||||
next: '次の曲',
|
||||
pause: '一時停止',
|
||||
play: '再生',
|
||||
favorite: 'お気に入り'
|
||||
}
|
||||
};
|
||||
190
src/i18n/lang/ja-JP/comp.ts
Normal file
190
src/i18n/lang/ja-JP/comp.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
export default {
|
||||
installApp: {
|
||||
description: 'アプリをインストールして、より良い体験を',
|
||||
noPrompt: '今後表示しない',
|
||||
install: '今すぐインストール',
|
||||
cancel: '後でインストール',
|
||||
download: 'ダウンロード',
|
||||
downloadFailed: 'ダウンロード失敗',
|
||||
downloadComplete: 'ダウンロード完了',
|
||||
downloadProblem: 'ダウンロードに問題がありますか?',
|
||||
downloadProblemLinkText: '最新版をダウンロード'
|
||||
},
|
||||
playlistDrawer: {
|
||||
title: 'プレイリストに追加',
|
||||
createPlaylist: '新しいプレイリストを作成',
|
||||
cancelCreate: '作成をキャンセル',
|
||||
create: '作成',
|
||||
playlistName: 'プレイリスト名',
|
||||
privatePlaylist: 'プライベートプレイリスト',
|
||||
publicPlaylist: 'パブリックプレイリスト',
|
||||
createSuccess: 'プレイリストの作成に成功しました',
|
||||
createFailed: 'プレイリストの作成に失敗しました',
|
||||
addSuccess: '楽曲の追加に成功しました',
|
||||
addFailed: '楽曲の追加に失敗しました',
|
||||
private: 'プライベート',
|
||||
public: 'パブリック',
|
||||
count: '曲',
|
||||
loginFirst: 'まずログインしてください',
|
||||
getPlaylistFailed: 'プレイリストの取得に失敗しました',
|
||||
inputPlaylistName: 'プレイリスト名を入力してください'
|
||||
},
|
||||
update: {
|
||||
title: '新しいバージョンが見つかりました',
|
||||
currentVersion: '現在のバージョン',
|
||||
cancel: '後で更新',
|
||||
prepareDownload: 'ダウンロード準備中...',
|
||||
downloading: 'ダウンロード中...',
|
||||
nowUpdate: '今すぐ更新',
|
||||
downloadFailed: 'ダウンロードに失敗しました。再試行するか手動でダウンロードしてください',
|
||||
startFailed: 'ダウンロードの開始に失敗しました。再試行するか手動でダウンロードしてください',
|
||||
noDownloadUrl: '現在のシステムに適したインストールパッケージが見つかりません。手動でダウンロードしてください',
|
||||
installConfirmTitle: '更新をインストール',
|
||||
installConfirmContent: 'アプリを閉じて更新をインストールしますか?',
|
||||
manualInstallTip: 'アプリを閉じた後にインストーラーが正常に起動しない場合は、ダウンロードフォルダでファイルを見つけて手動で開いてください。',
|
||||
yesInstall: '今すぐインストール',
|
||||
noThanks: '後でインストール',
|
||||
fileLocation: 'ファイルの場所',
|
||||
copy: 'パスをコピー',
|
||||
copySuccess: 'パスをクリップボードにコピーしました',
|
||||
copyFailed: 'コピーに失敗しました',
|
||||
backgroundDownload: 'バックグラウンドダウンロード'
|
||||
},
|
||||
coffee: {
|
||||
title: 'コーヒーをおごる',
|
||||
alipay: 'Alipay',
|
||||
wechat: 'WeChat Pay',
|
||||
alipayQR: 'Alipay QRコード',
|
||||
wechatQR: 'WeChat QRコード',
|
||||
coffeeDesc: '一杯のコーヒー、一つのサポート',
|
||||
coffeeDescLinkText: 'もっと見る',
|
||||
groupText: '微信公众号:AlgerMusic',
|
||||
messages: {
|
||||
copySuccess: 'クリップボードにコピーしました'
|
||||
},
|
||||
donateList: 'コーヒーをおごる'
|
||||
},
|
||||
playlistType: {
|
||||
title: 'プレイリストカテゴリ',
|
||||
showAll: 'すべて表示',
|
||||
hide: '一部を非表示'
|
||||
},
|
||||
recommendAlbum: {
|
||||
title: '最新アルバム'
|
||||
},
|
||||
recommendSinger: {
|
||||
title: '毎日のおすすめ',
|
||||
songlist: '毎日のおすすめリスト'
|
||||
},
|
||||
recommendSonglist: {
|
||||
title: '今週の人気音楽'
|
||||
},
|
||||
searchBar: {
|
||||
login: 'ログイン',
|
||||
toLogin: 'ログインへ',
|
||||
logout: 'ログアウト',
|
||||
set: '設定',
|
||||
theme: 'テーマ',
|
||||
restart: '再起動',
|
||||
refresh: '更新',
|
||||
currentVersion: '現在のバージョン',
|
||||
searchPlaceholder: '何かを検索してみましょう...',
|
||||
zoom: 'ページズーム',
|
||||
zoom100: '標準ズーム100%',
|
||||
resetZoom: 'クリックしてズームをリセット',
|
||||
zoomDefault: '標準ズーム'
|
||||
},
|
||||
titleBar: {
|
||||
closeTitle: '閉じる方法を選択してください',
|
||||
minimizeToTray: 'トレイに最小化',
|
||||
exitApp: 'アプリを終了',
|
||||
rememberChoice: '選択を記憶する',
|
||||
closeApp: 'アプリを閉じる'
|
||||
},
|
||||
userPlayList: {
|
||||
title: '{name}のよく聞く音楽'
|
||||
},
|
||||
musicList: {
|
||||
searchSongs: '楽曲を検索',
|
||||
noSearchResults: '関連する楽曲が見つかりませんでした',
|
||||
switchToNormal: 'デフォルトレイアウトに切り替え',
|
||||
switchToCompact: 'コンパクトレイアウトに切り替え',
|
||||
playAll: 'すべて再生',
|
||||
collect: 'お気に入り',
|
||||
collectSuccess: 'お気に入りに追加しました',
|
||||
cancelCollectSuccess: 'お気に入りから削除しました',
|
||||
operationFailed: '操作に失敗しました',
|
||||
cancelCollect: 'お気に入りから削除',
|
||||
addToPlaylist: 'プレイリストに追加',
|
||||
addToPlaylistSuccess: 'プレイリストに追加しました',
|
||||
songsAlreadyInPlaylist: '楽曲は既にプレイリストに存在します'
|
||||
},
|
||||
playlist: {
|
||||
import: {
|
||||
button: 'プレイリストインポート',
|
||||
title: 'プレイリストインポート',
|
||||
description: 'メタデータ/テキスト/リンクの3つの方法でプレイリストをインポートできます',
|
||||
linkTab: 'リンクインポート',
|
||||
textTab: 'テキストインポート',
|
||||
localTab: 'メタデータインポート',
|
||||
linkPlaceholder: 'プレイリストのリンクを入力してください(1行に1つ)',
|
||||
textPlaceholder: '楽曲情報を入力してください。形式:楽曲名 アーティスト名',
|
||||
localPlaceholder: 'JSON形式の楽曲メタデータを入力してください',
|
||||
linkTips: 'サポートされているリンクソース:',
|
||||
linkTip1: 'プレイリストをWeChat/Weibo/QQでシェアした後、リンクをコピー',
|
||||
linkTip2: 'プレイリスト/個人ページのリンクを直接コピー',
|
||||
linkTip3: '記事のリンクを直接コピー',
|
||||
textTips: '楽曲情報を入力してください(1行に1曲)',
|
||||
textFormat: '形式:楽曲名 アーティスト名',
|
||||
localTips: '楽曲メタデータを追加してください',
|
||||
localFormat: '形式例:',
|
||||
songNamePlaceholder: '楽曲名',
|
||||
artistNamePlaceholder: 'アーティスト名',
|
||||
albumNamePlaceholder: 'アルバム名',
|
||||
addSongButton: '楽曲を追加',
|
||||
addLinkButton: 'リンクを追加',
|
||||
importToStarPlaylist: 'お気に入りの音楽にインポート',
|
||||
playlistNamePlaceholder: 'プレイリスト名を入力してください',
|
||||
importButton: 'インポート開始',
|
||||
emptyLinkWarning: 'プレイリストのリンクを入力してください',
|
||||
emptyTextWarning: '楽曲情報を入力してください',
|
||||
emptyLocalWarning: '楽曲メタデータを入力してください',
|
||||
invalidJsonFormat: 'JSON形式が正しくありません',
|
||||
importSuccess: 'インポートタスクの作成に成功しました',
|
||||
importFailed: 'インポートに失敗しました',
|
||||
importStatus: 'インポート状況',
|
||||
refresh: '更新',
|
||||
taskId: 'タスクID',
|
||||
status: 'ステータス',
|
||||
successCount: '成功数',
|
||||
failReason: '失敗理由',
|
||||
unknownError: '不明なエラー',
|
||||
statusPending: '処理待ち',
|
||||
statusProcessing: '処理中',
|
||||
statusSuccess: 'インポート成功',
|
||||
statusFailed: 'インポート失敗',
|
||||
statusUnknown: '不明なステータス',
|
||||
taskList: 'タスクリスト',
|
||||
taskListTitle: 'インポートタスクリスト',
|
||||
action: '操作',
|
||||
select: '選択',
|
||||
fetchTaskListFailed: 'タスクリストの取得に失敗しました',
|
||||
noTasks: 'インポートタスクがありません',
|
||||
clearTasks: 'タスクをクリア',
|
||||
clearTasksConfirmTitle: 'クリア確認',
|
||||
clearTasksConfirmContent: 'すべてのインポートタスク記録をクリアしますか?この操作は元に戻せません。',
|
||||
confirm: '確認',
|
||||
cancel: 'キャンセル',
|
||||
clearTasksSuccess: 'タスクリストをクリアしました',
|
||||
clearTasksFailed: 'タスクリストのクリアに失敗しました'
|
||||
}
|
||||
},
|
||||
settings: '設定',
|
||||
user: 'ユーザー',
|
||||
toplist: 'ランキング',
|
||||
history: 'お気に入り履歴',
|
||||
list: 'プレイリスト',
|
||||
mv: 'MV',
|
||||
home: 'ホーム',
|
||||
search: '検索'
|
||||
};
|
||||
8
src/i18n/lang/ja-JP/donation.ts
Normal file
8
src/i18n/lang/ja-JP/donation.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
description: 'あなたの寄付は開発・保守作業をサポートするために使用され、サーバー保守、ドメイン更新などが含まれます。',
|
||||
message: 'メッセージを残す際は、メールアドレスやGitHubユーザー名を記載してください。',
|
||||
refresh: 'リストを更新',
|
||||
toDonateList: 'コーヒーをおごる',
|
||||
noMessage: 'メッセージがありません',
|
||||
title: '寄付リスト'
|
||||
};
|
||||
87
src/i18n/lang/ja-JP/download.ts
Normal file
87
src/i18n/lang/ja-JP/download.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
export default {
|
||||
title: 'ダウンロード管理',
|
||||
localMusic: 'ローカル音楽',
|
||||
count: '合計{count}曲',
|
||||
clearAll: '記録をクリア',
|
||||
settings: '設定',
|
||||
tabs: {
|
||||
downloading: 'ダウンロード中',
|
||||
downloaded: 'ダウンロード済み'
|
||||
},
|
||||
empty: {
|
||||
noTasks: 'ダウンロードタスクがありません',
|
||||
noDownloaded: 'ダウンロード済みの楽曲がありません'
|
||||
},
|
||||
progress: {
|
||||
total: '全体の進行状況: {progress}%'
|
||||
},
|
||||
status: {
|
||||
downloading: 'ダウンロード中',
|
||||
completed: '完了',
|
||||
failed: '失敗',
|
||||
unknown: '不明'
|
||||
},
|
||||
artist: {
|
||||
unknown: '不明なアーティスト'
|
||||
},
|
||||
delete: {
|
||||
title: '削除確認',
|
||||
message: '楽曲「{filename}」を削除しますか?この操作は元に戻せません。',
|
||||
confirm: '削除確認',
|
||||
cancel: 'キャンセル',
|
||||
success: '削除成功',
|
||||
failed: '削除失敗',
|
||||
fileNotFound: 'ファイルが存在しないか移動されました。記録から削除しました',
|
||||
recordRemoved: 'ファイルの削除に失敗しましたが、記録から削除しました'
|
||||
},
|
||||
clear: {
|
||||
title: 'ダウンロード記録をクリア',
|
||||
message: 'すべてのダウンロード記録をクリアしますか?この操作はダウンロード済みの音楽ファイルを削除しませんが、すべての記録をクリアします。',
|
||||
confirm: 'クリア確認',
|
||||
cancel: 'キャンセル',
|
||||
success: 'ダウンロード記録をクリアしました'
|
||||
},
|
||||
message: {
|
||||
downloadComplete: '{filename}のダウンロードが完了しました',
|
||||
downloadFailed: '{filename}のダウンロードに失敗しました: {error}'
|
||||
},
|
||||
loading: '読み込み中...',
|
||||
playStarted: '再生開始: {name}',
|
||||
playFailed: '再生失敗: {name}',
|
||||
path: {
|
||||
copied: 'パスをクリップボードにコピーしました',
|
||||
copyFailed: 'パスのコピーに失敗しました'
|
||||
},
|
||||
settingsPanel: {
|
||||
title: 'ダウンロード設定',
|
||||
path: 'ダウンロード場所',
|
||||
pathDesc: '音楽ファイルのダウンロード保存場所を設定',
|
||||
pathPlaceholder: 'ダウンロードパスを選択してください',
|
||||
noPathSelected: 'まずダウンロードパスを選択してください',
|
||||
select: 'フォルダを選択',
|
||||
open: 'フォルダを開く',
|
||||
fileFormat: 'ファイル名形式',
|
||||
fileFormatDesc: '音楽ダウンロード時のファイル命名形式を設定',
|
||||
customFormat: 'カスタム形式',
|
||||
separator: '区切り文字',
|
||||
separators: {
|
||||
dash: 'スペース-スペース',
|
||||
underscore: 'アンダースコア',
|
||||
space: 'スペース'
|
||||
},
|
||||
dragToArrange: 'ドラッグで並び替えまたは矢印ボタンで順序を調整:',
|
||||
formatVariables: '使用可能な変数',
|
||||
preview: 'プレビュー効果:',
|
||||
saveSuccess: 'ダウンロード設定を保存しました',
|
||||
presets: {
|
||||
songArtist: '楽曲名 - アーティスト名',
|
||||
artistSong: 'アーティスト名 - 楽曲名',
|
||||
songOnly: '楽曲名のみ'
|
||||
},
|
||||
components: {
|
||||
songName: '楽曲名',
|
||||
artistName: 'アーティスト名',
|
||||
albumName: 'アルバム名'
|
||||
}
|
||||
}
|
||||
};
|
||||
13
src/i18n/lang/ja-JP/favorite.ts
Normal file
13
src/i18n/lang/ja-JP/favorite.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export default {
|
||||
title: 'お気に入り',
|
||||
count: '合計{count}曲',
|
||||
batchDownload: '一括ダウンロード',
|
||||
download: 'ダウンロード ({count})',
|
||||
emptyTip: 'まだお気に入りの楽曲がありません',
|
||||
downloadSuccess: 'ダウンロード完了',
|
||||
downloadFailed: 'ダウンロード失敗',
|
||||
downloading: 'ダウンロード中です。しばらくお待ちください...',
|
||||
selectSongsFirst: 'まずダウンロードする楽曲を選択してください',
|
||||
descending: '降順',
|
||||
ascending: '昇順'
|
||||
};
|
||||
5
src/i18n/lang/ja-JP/history.ts
Normal file
5
src/i18n/lang/ja-JP/history.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
title: '再生履歴',
|
||||
playCount: '{count}',
|
||||
getHistoryFailed: '履歴の取得に失敗しました'
|
||||
};
|
||||
48
src/i18n/lang/ja-JP/login.ts
Normal file
48
src/i18n/lang/ja-JP/login.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
export default {
|
||||
title: {
|
||||
qr: 'QRコードログイン',
|
||||
phone: '電話番号ログイン',
|
||||
cookie: 'Cookieログイン',
|
||||
uid: 'UIDログイン'
|
||||
},
|
||||
qrTip: 'NetEase Cloudアプリでログイン',
|
||||
phoneTip: 'NetEase Cloudアカウントでログイン',
|
||||
tokenTip: '有効なNetEase Cloud MusicのCookieを入力してログイン',
|
||||
uidTip: 'ユーザーIDを入力してクイックログイン',
|
||||
placeholder: {
|
||||
phone: '電話番号',
|
||||
password: 'パスワード',
|
||||
cookie: 'NetEase Cloud MusicのCookie(token)を入力してください',
|
||||
uid: 'ユーザーID(UID)を入力してください'
|
||||
},
|
||||
button: {
|
||||
login: 'ログイン',
|
||||
switchToQr: 'QRコードログイン',
|
||||
switchToPhone: '電話番号ログイン',
|
||||
switchToToken: 'Cookieログインを使用',
|
||||
switchToUid: 'UIDログイン',
|
||||
backToQr: 'QRコードログインに戻る',
|
||||
cookieLogin: 'Cookieログイン',
|
||||
autoGetCookie: 'Cookie自動取得',
|
||||
refresh: 'クリックしてリフレッシュ',
|
||||
refreshing: 'リフレッシュ中...',
|
||||
refreshQr: 'QRコードをリフレッシュ'
|
||||
},
|
||||
message: {
|
||||
loginSuccess: 'ログイン成功',
|
||||
tokenLoginSuccess: 'Cookieログイン成功',
|
||||
uidLoginSuccess: 'UIDログイン成功',
|
||||
loadError: 'ログイン情報の読み込み中にエラーが発生しました',
|
||||
qrCheckError: 'QRコードの状態確認中にエラーが発生しました',
|
||||
tokenRequired: 'Cookieを入力してください',
|
||||
tokenInvalid: 'Cookieが無効です。確認して再試行してください',
|
||||
uidRequired: 'ユーザーIDを入力してください',
|
||||
uidInvalid: 'ユーザーIDが無効またはユーザーが存在しません',
|
||||
uidLoginFailed: 'UIDログインに失敗しました。ユーザーIDが正しいか確認してください',
|
||||
autoGetCookieSuccess: 'Cookie自動取得成功',
|
||||
autoGetCookieFailed: 'Cookie自動取得失敗',
|
||||
autoGetCookieTip: 'NetEase Cloud Musicのログインページを開きます。ログイン完了後、ウィンドウを閉じてください'
|
||||
},
|
||||
qrTitle: 'NetEase Cloud Music QRコードログイン',
|
||||
uidWarning: '注意:UIDログインはユーザーの公開情報を表示するためのみ使用でき、ログイン権限が必要な機能にはアクセスできません。'
|
||||
};
|
||||
123
src/i18n/lang/ja-JP/player.ts
Normal file
123
src/i18n/lang/ja-JP/player.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
export default {
|
||||
nowPlaying: '再生中',
|
||||
playlist: 'プレイリスト',
|
||||
lyrics: '歌詞',
|
||||
previous: '前へ',
|
||||
play: '再生',
|
||||
pause: '一時停止',
|
||||
next: '次へ',
|
||||
volumeUp: '音量を上げる',
|
||||
volumeDown: '音量を下げる',
|
||||
mute: 'ミュート',
|
||||
unmute: 'ミュート解除',
|
||||
songNum: '楽曲総数:{num}',
|
||||
addCorrection: '{num}秒早める',
|
||||
subtractCorrection: '{num}秒遅らせる',
|
||||
playFailed: '現在の楽曲の再生に失敗しました。次の曲を再生します',
|
||||
playMode: {
|
||||
sequence: '順次再生',
|
||||
loop: 'リピート再生',
|
||||
random: 'ランダム再生'
|
||||
},
|
||||
fullscreen: {
|
||||
enter: 'フルスクリーン',
|
||||
exit: 'フルスクリーン終了'
|
||||
},
|
||||
close: '閉じる',
|
||||
modeHint: {
|
||||
single: 'リピート再生',
|
||||
list: '自動で次の曲を再生'
|
||||
},
|
||||
lrc: {
|
||||
noLrc: '歌詞がありません。お楽しみください'
|
||||
},
|
||||
reparse: {
|
||||
title: '解析音源を選択',
|
||||
desc: '音源をクリックして直接解析します。次回この楽曲を再生する際は選択した音源を使用します',
|
||||
success: '再解析成功',
|
||||
failed: '再解析失敗',
|
||||
warning: '音源を選択してください',
|
||||
bilibiliNotSupported: 'Bilibili動画は再解析をサポートしていません',
|
||||
processing: '解析中...',
|
||||
clear: 'カスタム音源をクリア'
|
||||
},
|
||||
playBar: {
|
||||
expand: '歌詞を展開',
|
||||
collapse: '歌詞を折りたたみ',
|
||||
like: 'いいね',
|
||||
lyric: '歌詞',
|
||||
noSongPlaying: '再生中の楽曲がありません',
|
||||
eq: 'イコライザー',
|
||||
playList: 'プレイリスト',
|
||||
reparse: '再解析',
|
||||
playMode: {
|
||||
sequence: '順次再生',
|
||||
loop: 'ループ再生',
|
||||
random: 'ランダム再生'
|
||||
},
|
||||
play: '再生開始',
|
||||
pause: '再生一時停止',
|
||||
prev: '前の曲',
|
||||
next: '次の曲',
|
||||
volume: '音量',
|
||||
favorite: '{name}をお気に入りに追加しました',
|
||||
unFavorite: '{name}をお気に入りから削除しました',
|
||||
miniPlayBar: 'ミニ再生バー',
|
||||
playbackSpeed: '再生速度',
|
||||
advancedControls: 'その他の設定'
|
||||
},
|
||||
eq: {
|
||||
title: 'イコライザー',
|
||||
reset: 'リセット',
|
||||
on: 'オン',
|
||||
off: 'オフ',
|
||||
bass: '低音',
|
||||
midrange: '中音',
|
||||
treble: '高音',
|
||||
presets: {
|
||||
flat: 'フラット',
|
||||
pop: 'ポップ',
|
||||
rock: 'ロック',
|
||||
classical: 'クラシック',
|
||||
jazz: 'ジャズ',
|
||||
electronic: 'エレクトロニック',
|
||||
hiphop: 'ヒップホップ',
|
||||
rb: 'R&B',
|
||||
metal: 'メタル',
|
||||
vocal: 'ボーカル',
|
||||
dance: 'ダンス',
|
||||
acoustic: 'アコースティック',
|
||||
custom: 'カスタム'
|
||||
}
|
||||
},
|
||||
// タイマー機能関連
|
||||
sleepTimer: {
|
||||
title: 'スリープタイマー',
|
||||
cancel: 'タイマーをキャンセル',
|
||||
timeMode: '時間で停止',
|
||||
songsMode: '楽曲数で停止',
|
||||
playlistEnd: 'プレイリスト終了後に停止',
|
||||
afterPlaylist: 'プレイリスト終了後に停止',
|
||||
activeUntilEnd: 'リスト終了まで再生',
|
||||
minutes: '分',
|
||||
hours: '時間',
|
||||
songs: '曲',
|
||||
set: '設定',
|
||||
timerSetSuccess: '{minutes}分後に停止するよう設定しました',
|
||||
songsSetSuccess: '{songs}曲再生後に停止するよう設定しました',
|
||||
playlistEndSetSuccess: 'プレイリスト終了後に停止するよう設定しました',
|
||||
timerCancelled: 'スリープタイマーをキャンセルしました',
|
||||
timerEnded: 'スリープタイマーが作動しました',
|
||||
playbackStopped: '音楽再生を停止しました',
|
||||
minutesRemaining: '残り{minutes}分',
|
||||
songsRemaining: '残り{count}曲'
|
||||
},
|
||||
playList: {
|
||||
clearAll: 'プレイリストをクリア',
|
||||
alreadyEmpty: 'プレイリストは既に空です',
|
||||
cleared: 'プレイリストをクリアしました',
|
||||
empty: 'プレイリストが空です',
|
||||
clearConfirmTitle: 'プレイリストをクリア',
|
||||
clearConfirmContent: 'これによりプレイリスト内のすべての楽曲がクリアされ、現在の再生が停止されます。続行しますか?'
|
||||
}
|
||||
};
|
||||
27
src/i18n/lang/ja-JP/search.ts
Normal file
27
src/i18n/lang/ja-JP/search.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export default {
|
||||
title: {
|
||||
hotSearch: '人気検索リスト',
|
||||
searchList: '検索リスト',
|
||||
searchHistory: '検索履歴'
|
||||
},
|
||||
button: {
|
||||
clear: 'クリア',
|
||||
back: '戻る',
|
||||
playAll: 'リストを再生'
|
||||
},
|
||||
loading: {
|
||||
more: '読み込み中...',
|
||||
failed: '検索に失敗しました'
|
||||
},
|
||||
noMore: 'これ以上ありません',
|
||||
error: {
|
||||
searchFailed: '検索に失敗しました'
|
||||
},
|
||||
search: {
|
||||
single: '楽曲',
|
||||
album: 'アルバム',
|
||||
playlist: 'プレイリスト',
|
||||
mv: 'MV',
|
||||
bilibili: 'Bilibili'
|
||||
}
|
||||
};
|
||||
322
src/i18n/lang/ja-JP/settings.ts
Normal file
322
src/i18n/lang/ja-JP/settings.ts
Normal file
@@ -0,0 +1,322 @@
|
||||
export default {
|
||||
theme: 'テーマ',
|
||||
language: '言語',
|
||||
regard: 'について',
|
||||
logout: 'ログアウト',
|
||||
sections: {
|
||||
basic: '基本設定',
|
||||
playback: '再生設定',
|
||||
application: 'アプリケーション設定',
|
||||
network: 'ネットワーク設定',
|
||||
system: 'システム管理',
|
||||
donation: '寄付サポート',
|
||||
regard: 'について'
|
||||
},
|
||||
basic: {
|
||||
themeMode: 'テーマモード',
|
||||
themeModeDesc: 'ライト/ダークテーマの切り替え',
|
||||
autoTheme: 'システムに従う',
|
||||
manualTheme: '手動切り替え',
|
||||
language: '言語設定',
|
||||
languageDesc: '表示言語を切り替え',
|
||||
tokenManagement: 'Cookie管理',
|
||||
tokenManagementDesc: 'NetEase Cloud MusicログインCookieを管理',
|
||||
tokenStatus: '現在のCookieステータス',
|
||||
tokenSet: '設定済み',
|
||||
tokenNotSet: '未設定',
|
||||
setToken: 'Cookieを設定',
|
||||
modifyToken: 'Cookieを変更',
|
||||
clearToken: 'Cookieをクリア',
|
||||
font: 'フォント設定',
|
||||
fontDesc: 'フォントを選択します。前に配置されたフォントが優先されます',
|
||||
fontScope: {
|
||||
global: 'グローバル',
|
||||
lyric: '歌詞のみ'
|
||||
},
|
||||
animation: 'アニメーション速度',
|
||||
animationDesc: 'アニメーションを有効にするかどうか',
|
||||
animationSpeed: {
|
||||
slow: '非常に遅い',
|
||||
normal: '通常',
|
||||
fast: '非常に速い'
|
||||
},
|
||||
fontPreview: {
|
||||
title: 'フォントプレビュー',
|
||||
chinese: '中国語',
|
||||
english: 'English',
|
||||
japanese: '日本語',
|
||||
korean: '韓国語',
|
||||
chineseText: '静夜思 床前明月光 疑是地上霜',
|
||||
englishText: 'The quick brown fox jumps over the lazy dog',
|
||||
japaneseText: 'あいうえお かきくけこ さしすせそ',
|
||||
koreanText: '가나다라마 바사아자차 카타파하'
|
||||
}
|
||||
},
|
||||
playback: {
|
||||
quality: '音質設定',
|
||||
qualityDesc: '音楽再生の音質を選択(NetEase Cloud VIP)',
|
||||
qualityOptions: {
|
||||
standard: '標準',
|
||||
higher: '高音質',
|
||||
exhigh: '超高音質',
|
||||
lossless: 'ロスレス',
|
||||
hires: 'Hi-Res',
|
||||
jyeffect: 'HD サラウンド',
|
||||
sky: 'イマーシブサラウンド',
|
||||
dolby: 'Dolby Atmos',
|
||||
jymaster: '超高解像度マスター'
|
||||
},
|
||||
musicSources: '音源設定',
|
||||
musicSourcesDesc: '音楽解析に使用する音源プラットフォームを選択',
|
||||
musicSourcesWarning: '少なくとも1つの音源プラットフォームを選択する必要があります',
|
||||
musicUnblockEnable: '音楽解析を有効にする',
|
||||
musicUnblockEnableDesc: '有効にすると、再生できない音楽の解析を試みます',
|
||||
configureMusicSources: '音源を設定',
|
||||
selectedMusicSources: '選択された音源:',
|
||||
noMusicSources: '音源が選択されていません',
|
||||
gdmusicInfo: 'GD音楽台は複数のプラットフォーム音源を自動解析し、最適な結果を自動選択できます',
|
||||
autoPlay: '自動再生',
|
||||
autoPlayDesc: 'アプリを再起動した際に自動的に再生を継続するかどうか',
|
||||
showStatusBar: 'ステータスバーコントロール機能を表示するかどうか',
|
||||
showStatusBarContent: 'Macのステータスバーに音楽コントロール機能を表示できます(再起動後に有効)'
|
||||
},
|
||||
application: {
|
||||
closeAction: '閉じる動作',
|
||||
closeActionDesc: 'ウィンドウを閉じる際の動作を選択',
|
||||
closeOptions: {
|
||||
ask: '毎回確認',
|
||||
minimize: 'トレイに最小化',
|
||||
close: '直接終了'
|
||||
},
|
||||
shortcut: 'ショートカット設定',
|
||||
shortcutDesc: 'グローバルショートカットをカスタマイズ',
|
||||
download: 'ダウンロード管理',
|
||||
downloadDesc: 'ダウンロードリストボタンを常に表示するかどうか',
|
||||
unlimitedDownload: '無制限ダウンロード',
|
||||
unlimitedDownloadDesc: '有効にすると音楽を無制限でダウンロードします(ダウンロード失敗の可能性があります)。デフォルトは300曲制限',
|
||||
downloadPath: 'ダウンロードディレクトリ',
|
||||
downloadPathDesc: '音楽ファイルのダウンロード場所を選択',
|
||||
remoteControl: 'リモートコントロール',
|
||||
remoteControlDesc: 'リモートコントロール機能を設定'
|
||||
},
|
||||
network: {
|
||||
apiPort: '音楽APIポート',
|
||||
apiPortDesc: '変更後はアプリの再起動が必要です',
|
||||
proxy: 'プロキシ設定',
|
||||
proxyDesc: '音楽にアクセスできない場合はプロキシを有効にできます',
|
||||
proxyHost: 'プロキシアドレス',
|
||||
proxyHostPlaceholder: 'プロキシアドレスを入力してください',
|
||||
proxyPort: 'プロキシポート',
|
||||
proxyPortPlaceholder: 'プロキシポートを入力してください',
|
||||
realIP: 'realIP設定',
|
||||
realIPDesc: '制限により、このプロジェクトは海外での使用が制限されます。realIPパラメータを使用して国内IPを渡すことで解決できます',
|
||||
messages: {
|
||||
proxySuccess: 'プロキシ設定を保存しました。アプリ再起動後に有効になります',
|
||||
proxyError: '入力が正しいかどうか確認してください',
|
||||
realIPSuccess: '実IPアドレス設定を保存しました',
|
||||
realIPError: '有効なIPアドレスを入力してください'
|
||||
}
|
||||
},
|
||||
system: {
|
||||
cache: 'キャッシュ管理',
|
||||
cacheDesc: 'キャッシュをクリア',
|
||||
cacheClearTitle: 'クリアするキャッシュタイプを選択してください:',
|
||||
cacheTypes: {
|
||||
history: {
|
||||
label: '再生履歴',
|
||||
description: '再生した楽曲の記録をクリア'
|
||||
},
|
||||
favorite: {
|
||||
label: 'お気に入り記録',
|
||||
description: 'ローカルのお気に入り楽曲記録をクリア(クラウドのお気に入りには影響しません)'
|
||||
},
|
||||
user: {
|
||||
label: 'ユーザーデータ',
|
||||
description: 'ログイン情報とユーザー関連データをクリア'
|
||||
},
|
||||
settings: {
|
||||
label: 'アプリ設定',
|
||||
description: 'アプリのすべてのカスタム設定をクリア'
|
||||
},
|
||||
downloads: {
|
||||
label: 'ダウンロード記録',
|
||||
description: 'ダウンロード履歴をクリア(ダウンロード済みファイルは削除されません)'
|
||||
},
|
||||
resources: {
|
||||
label: '音楽リソース',
|
||||
description: '読み込み済みの音楽ファイル、歌詞などのリソースキャッシュをクリア'
|
||||
},
|
||||
lyrics: {
|
||||
label: '歌詞リソース',
|
||||
description: '読み込み済みの歌詞リソースキャッシュをクリア'
|
||||
}
|
||||
},
|
||||
restart: '再起動',
|
||||
restartDesc: 'アプリを再起動',
|
||||
messages: {
|
||||
clearSuccess: 'クリア成功。一部の設定は再起動後に有効になります'
|
||||
}
|
||||
},
|
||||
about: {
|
||||
version: 'バージョン',
|
||||
checkUpdate: '更新を確認',
|
||||
checking: '確認中...',
|
||||
latest: '現在最新バージョンです',
|
||||
hasUpdate: '新しいバージョンが見つかりました',
|
||||
gotoUpdate: '更新へ',
|
||||
gotoGithub: 'Githubへ',
|
||||
author: '作者',
|
||||
authorDesc: 'algerkong スターを付けてください🌟',
|
||||
messages: {
|
||||
checkError: '更新確認に失敗しました。後でもう一度お試しください'
|
||||
}
|
||||
},
|
||||
validation: {
|
||||
selectProxyProtocol: 'プロキシプロトコルを選択してください',
|
||||
proxyHost: 'プロキシアドレスを入力してください',
|
||||
portNumber: '有効なポート番号を入力してください(1-65535)'
|
||||
},
|
||||
lyricSettings: {
|
||||
title: '歌詞設定',
|
||||
tabs: {
|
||||
display: '表示',
|
||||
interface: 'インターフェース',
|
||||
typography: 'テキスト',
|
||||
mobile: 'モバイル'
|
||||
},
|
||||
pureMode: 'ピュアモード',
|
||||
hideCover: 'カバーを非表示',
|
||||
centerDisplay: '中央表示',
|
||||
showTranslation: '翻訳を表示',
|
||||
hideLyrics: '歌詞を非表示',
|
||||
hidePlayBar: '再生バーを非表示',
|
||||
hideMiniPlayBar: 'ミニ再生バーを非表示',
|
||||
showMiniPlayBar: 'ミニ再生バーを表示',
|
||||
backgroundTheme: '背景テーマ',
|
||||
themeOptions: {
|
||||
default: 'デフォルト',
|
||||
light: 'ライト',
|
||||
dark: 'ダーク'
|
||||
},
|
||||
fontSize: 'フォントサイズ',
|
||||
fontSizeMarks: {
|
||||
small: '小',
|
||||
medium: '中',
|
||||
large: '大'
|
||||
},
|
||||
letterSpacing: '文字間隔',
|
||||
letterSpacingMarks: {
|
||||
compact: 'コンパクト',
|
||||
default: 'デフォルト',
|
||||
loose: 'ゆったり'
|
||||
},
|
||||
lineHeight: '行の高さ',
|
||||
lineHeightMarks: {
|
||||
compact: 'コンパクト',
|
||||
default: 'デフォルト',
|
||||
loose: 'ゆったり'
|
||||
},
|
||||
mobileLayout: 'モバイルレイアウト',
|
||||
layoutOptions: {
|
||||
default: 'デフォルト',
|
||||
ios: 'iOSスタイル',
|
||||
android: 'Androidスタイル'
|
||||
},
|
||||
mobileCoverStyle: 'カバースタイル',
|
||||
coverOptions: {
|
||||
record: 'レコード',
|
||||
square: '正方形',
|
||||
full: 'フルスクリーン'
|
||||
},
|
||||
lyricLines: '歌詞行数',
|
||||
mobileUnavailable: 'この設定はモバイルでのみ利用可能です'
|
||||
},
|
||||
themeColor: {
|
||||
title: '歌詞テーマカラー',
|
||||
presetColors: 'プリセットカラー',
|
||||
customColor: 'カスタムカラー',
|
||||
preview: 'プレビュー効果',
|
||||
previewText: '歌詞効果',
|
||||
colorNames: {
|
||||
'spotify-green': 'Spotify グリーン',
|
||||
'apple-blue': 'Apple ブルー',
|
||||
'youtube-red': 'YouTube レッド',
|
||||
orange: 'バイタルオレンジ',
|
||||
purple: 'ミステリアスパープル',
|
||||
pink: 'サクラピンク'
|
||||
},
|
||||
tooltips: {
|
||||
openColorPicker: 'カラーパレットを開く',
|
||||
closeColorPicker: 'カラーパレットを閉じる'
|
||||
},
|
||||
placeholder: '#1db954'
|
||||
},
|
||||
shortcutSettings: {
|
||||
title: 'ショートカット設定',
|
||||
shortcut: 'ショートカット',
|
||||
shortcutDesc: 'ショートカットをカスタマイズ',
|
||||
shortcutConflict: 'ショートカットの競合',
|
||||
inputPlaceholder: 'クリックしてショートカットを入力',
|
||||
resetShortcuts: 'デフォルトに戻す',
|
||||
disableAll: 'すべて無効',
|
||||
enableAll: 'すべて有効',
|
||||
togglePlay: '再生/一時停止',
|
||||
prevPlay: '前の曲',
|
||||
nextPlay: '次の曲',
|
||||
volumeUp: '音量を上げる',
|
||||
volumeDown: '音量を下げる',
|
||||
toggleFavorite: 'お気に入り/お気に入り解除',
|
||||
toggleWindow: 'ウィンドウ表示/非表示',
|
||||
scopeGlobal: 'グローバル',
|
||||
scopeApp: 'アプリ内',
|
||||
enabled: '有効',
|
||||
disabled: '無効',
|
||||
messages: {
|
||||
resetSuccess: 'デフォルトのショートカットに戻しました。保存を忘れずに',
|
||||
conflict: '競合するショートカットがあります。再設定してください',
|
||||
saveSuccess: 'ショートカット設定を保存しました',
|
||||
saveError: 'ショートカットの保存に失敗しました。再試行してください',
|
||||
cancelEdit: '変更をキャンセルしました',
|
||||
disableAll: 'すべてのショートカットを無効にしました。保存を忘れずに',
|
||||
enableAll: 'すべてのショートカットを有効にしました。保存を忘れずに'
|
||||
}
|
||||
},
|
||||
remoteControl: {
|
||||
title: 'リモートコントロール',
|
||||
enable: 'リモートコントロールを有効にする',
|
||||
port: 'サービスポート',
|
||||
allowedIps: '許可されたIPアドレス',
|
||||
addIp: 'IPを追加',
|
||||
emptyListHint: '空のリストはすべてのIPアクセスを許可することを意味します',
|
||||
saveSuccess: 'リモートコントロール設定を保存しました',
|
||||
accessInfo: 'リモートコントロールアクセスアドレス:'
|
||||
},
|
||||
cookie: {
|
||||
title: 'Cookie設定',
|
||||
description: 'NetEase Cloud MusicのCookieを入力してください:',
|
||||
placeholder: '完全なCookieを貼り付けてください...',
|
||||
help: {
|
||||
format: 'Cookieは通常「MUSIC_U=」で始まります',
|
||||
source: 'ブラウザの開発者ツールのネットワークリクエストから取得できます',
|
||||
storage: 'Cookie設定後、自動的にローカルストレージに保存されます'
|
||||
},
|
||||
action: {
|
||||
save: 'Cookieを保存',
|
||||
paste: '貼り付け',
|
||||
clear: 'クリア'
|
||||
},
|
||||
validation: {
|
||||
required: 'Cookieを入力してください',
|
||||
format: 'Cookie形式が正しくない可能性があります。MUSIC_Uが含まれているか確認してください'
|
||||
},
|
||||
message: {
|
||||
saveSuccess: 'Cookieの保存に成功しました',
|
||||
saveError: 'Cookieの保存に失敗しました',
|
||||
pasteSuccess: '貼り付けに成功しました',
|
||||
pasteError: '貼り付けに失敗しました。手動でコピーしてください'
|
||||
},
|
||||
info: {
|
||||
length: '現在の長さ:{length} 文字'
|
||||
}
|
||||
}
|
||||
};
|
||||
28
src/i18n/lang/ja-JP/songItem.ts
Normal file
28
src/i18n/lang/ja-JP/songItem.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export default {
|
||||
menu: {
|
||||
play: '再生',
|
||||
playNext: '次に再生',
|
||||
download: '楽曲をダウンロード',
|
||||
addToPlaylist: 'プレイリストに追加',
|
||||
favorite: 'いいね',
|
||||
unfavorite: 'いいね解除',
|
||||
removeFromPlaylist: 'プレイリストから削除',
|
||||
dislike: '嫌い',
|
||||
undislike: '嫌い解除'
|
||||
},
|
||||
message: {
|
||||
downloading: 'ダウンロード中です。しばらくお待ちください...',
|
||||
downloadFailed: 'ダウンロードに失敗しました',
|
||||
downloadQueued: 'ダウンロードキューに追加しました',
|
||||
addedToNextPlay: '次の再生に追加しました',
|
||||
getUrlFailed: '音楽ダウンロードアドレスの取得に失敗しました。ログインしているか確認してください'
|
||||
},
|
||||
dialog: {
|
||||
dislike: {
|
||||
title: 'お知らせ!',
|
||||
content: 'この楽曲を嫌いにしますか?再度アクセスすると毎日のおすすめから除外されます。',
|
||||
positiveText: '嫌い',
|
||||
negativeText: 'キャンセル'
|
||||
}
|
||||
}
|
||||
};
|
||||
48
src/i18n/lang/ja-JP/user.ts
Normal file
48
src/i18n/lang/ja-JP/user.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
export default {
|
||||
profile: {
|
||||
followers: 'フォロワー',
|
||||
following: 'フォロー中',
|
||||
level: 'レベル'
|
||||
},
|
||||
playlist: {
|
||||
created: '作成したプレイリスト',
|
||||
mine: '私が作成した',
|
||||
trackCount: '{count}曲',
|
||||
playCount: '{count}回再生'
|
||||
},
|
||||
ranking: {
|
||||
title: '聴取ランキング',
|
||||
playCount: '{count}回'
|
||||
},
|
||||
follow: {
|
||||
title: 'フォローリスト',
|
||||
viewPlaylist: 'プレイリストを見る',
|
||||
noFollowings: 'フォローがありません',
|
||||
loadMore: 'さらに読み込み',
|
||||
noSignature: 'この人は怠け者で、何も残していません',
|
||||
userFollowsTitle: 'のフォロー',
|
||||
myFollowsTitle: '私のフォロー'
|
||||
},
|
||||
follower: {
|
||||
title: 'フォロワーリスト',
|
||||
noFollowers: 'フォロワーがいません',
|
||||
loadMore: 'さらに読み込み',
|
||||
userFollowersTitle: 'のフォロワー',
|
||||
myFollowersTitle: '私のフォロワー'
|
||||
},
|
||||
detail: {
|
||||
playlists: 'プレイリスト',
|
||||
records: '聴取ランキング',
|
||||
noPlaylists: 'プレイリストがありません',
|
||||
noRecords: '聴取記録がありません',
|
||||
artist: 'アーティスト',
|
||||
noSignature: 'この人は怠け者で、何も残していません',
|
||||
invalidUserId: '無効なユーザーID',
|
||||
noRecordPermission: '{name}は聴取ランキングを見せてくれません'
|
||||
},
|
||||
message: {
|
||||
loadFailed: 'ユーザーページの読み込みに失敗しました',
|
||||
deleteSuccess: '削除成功',
|
||||
deleteFailed: '削除失敗'
|
||||
}
|
||||
};
|
||||
5
src/i18n/lang/ko-KR/artist.ts
Normal file
5
src/i18n/lang/ko-KR/artist.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
hotSongs: '인기 곡',
|
||||
albums: '앨범',
|
||||
description: '아티스트 소개'
|
||||
};
|
||||
56
src/i18n/lang/ko-KR/common.ts
Normal file
56
src/i18n/lang/ko-KR/common.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
export default {
|
||||
play: '재생',
|
||||
next: '다음 곡',
|
||||
previous: '이전 곡',
|
||||
volume: '볼륨',
|
||||
settings: '설정',
|
||||
search: '검색',
|
||||
loading: '로딩 중...',
|
||||
loadingMore: '더 불러오기...',
|
||||
alipay: '알리페이',
|
||||
wechat: '위챗 페이',
|
||||
on: '켜기',
|
||||
off: '끄기',
|
||||
show: '표시',
|
||||
hide: '숨기기',
|
||||
confirm: '확인',
|
||||
cancel: '취소',
|
||||
configure: '구성',
|
||||
open: '열기',
|
||||
modify: '수정',
|
||||
success: '작업 성공',
|
||||
error: '작업 실패',
|
||||
warning: '경고',
|
||||
info: '알림',
|
||||
save: '저장',
|
||||
delete: '삭제',
|
||||
refresh: '새로고침',
|
||||
retry: '다시 시도',
|
||||
reset: '재설정',
|
||||
back: '뒤로',
|
||||
copySuccess: '클립보드에 복사됨',
|
||||
copyFailed: '복사 실패',
|
||||
validation: {
|
||||
required: '이 항목은 필수입니다',
|
||||
invalidInput: '잘못된 입력',
|
||||
selectRequired: '옵션을 선택해주세요',
|
||||
numberRange: '{min}에서 {max} 사이의 숫자를 입력해주세요'
|
||||
},
|
||||
viewMore: '더 보기',
|
||||
noMore: '더 이상 없음',
|
||||
selectAll: '전체 선택',
|
||||
expand: '펼치기',
|
||||
collapse: '접기',
|
||||
songCount: '{count}곡',
|
||||
language: '언어',
|
||||
tray: {
|
||||
show: '표시',
|
||||
quit: '종료',
|
||||
playPause: '재생/일시정지',
|
||||
prev: '이전 곡',
|
||||
next: '다음 곡',
|
||||
pause: '일시정지',
|
||||
play: '재생',
|
||||
favorite: '즐겨찾기'
|
||||
}
|
||||
};
|
||||
190
src/i18n/lang/ko-KR/comp.ts
Normal file
190
src/i18n/lang/ko-KR/comp.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
export default {
|
||||
installApp: {
|
||||
description: '앱을 설치하여 더 나은 경험을 얻으세요',
|
||||
noPrompt: '다시 묻지 않기',
|
||||
install: '지금 설치',
|
||||
cancel: '나중에 설치',
|
||||
download: '다운로드',
|
||||
downloadFailed: '다운로드 실패',
|
||||
downloadComplete: '다운로드 완료',
|
||||
downloadProblem: '다운로드에 문제가 있나요?',
|
||||
downloadProblemLinkText: '최신 버전 다운로드'
|
||||
},
|
||||
playlistDrawer: {
|
||||
title: '플레이리스트에 추가',
|
||||
createPlaylist: '새 플레이리스트 만들기',
|
||||
cancelCreate: '만들기 취소',
|
||||
create: '만들기',
|
||||
playlistName: '플레이리스트 이름',
|
||||
privatePlaylist: '비공개 플레이리스트',
|
||||
publicPlaylist: '공개 플레이리스트',
|
||||
createSuccess: '플레이리스트 생성 성공',
|
||||
createFailed: '플레이리스트 생성 실패',
|
||||
addSuccess: '곡 추가 성공',
|
||||
addFailed: '곡 추가 실패',
|
||||
private: '비공개',
|
||||
public: '공개',
|
||||
count: '곡',
|
||||
loginFirst: '먼저 로그인해주세요',
|
||||
getPlaylistFailed: '플레이리스트 가져오기 실패',
|
||||
inputPlaylistName: '플레이리스트 이름을 입력해주세요'
|
||||
},
|
||||
update: {
|
||||
title: '새 버전 발견',
|
||||
currentVersion: '현재 버전',
|
||||
cancel: '나중에 업데이트',
|
||||
prepareDownload: '다운로드 준비 중...',
|
||||
downloading: '다운로드 중...',
|
||||
nowUpdate: '지금 업데이트',
|
||||
downloadFailed: '다운로드 실패, 다시 시도하거나 수동으로 다운로드해주세요',
|
||||
startFailed: '다운로드 시작 실패, 다시 시도하거나 수동으로 다운로드해주세요',
|
||||
noDownloadUrl: '현재 시스템에 적합한 설치 패키지를 찾을 수 없습니다. 수동으로 다운로드해주세요',
|
||||
installConfirmTitle: '업데이트 설치',
|
||||
installConfirmContent: '앱을 닫고 업데이트를 설치하시겠습니까?',
|
||||
manualInstallTip: '앱을 닫은 후 설치 프로그램이 정상적으로 나타나지 않으면 다운로드 폴더에서 파일을 찾아 수동으로 열어주세요.',
|
||||
yesInstall: '지금 설치',
|
||||
noThanks: '나중에 설치',
|
||||
fileLocation: '파일 위치',
|
||||
copy: '경로 복사',
|
||||
copySuccess: '경로가 클립보드에 복사됨',
|
||||
copyFailed: '복사 실패',
|
||||
backgroundDownload: '백그라운드 다운로드'
|
||||
},
|
||||
coffee: {
|
||||
title: '커피 한 잔 사주세요',
|
||||
alipay: '알리페이',
|
||||
wechat: '위챗 페이',
|
||||
alipayQR: '알리페이 결제 QR코드',
|
||||
wechatQR: '위챗 결제 QR코드',
|
||||
coffeeDesc: '커피 한 잔, 하나의 지원',
|
||||
coffeeDescLinkText: '더 보기',
|
||||
groupText: '微信公众号:AlgerMusic',
|
||||
messages: {
|
||||
copySuccess: '클립보드에 복사됨'
|
||||
},
|
||||
donateList: '커피 한 잔 사주세요'
|
||||
},
|
||||
playlistType: {
|
||||
title: '플레이리스트 분류',
|
||||
showAll: '모두 표시',
|
||||
hide: '일부 숨기기'
|
||||
},
|
||||
recommendAlbum: {
|
||||
title: '최신 앨범'
|
||||
},
|
||||
recommendSinger: {
|
||||
title: '일일 추천',
|
||||
songlist: '일일 추천 목록'
|
||||
},
|
||||
recommendSonglist: {
|
||||
title: '이번 주 인기 음악'
|
||||
},
|
||||
searchBar: {
|
||||
login: '로그인',
|
||||
toLogin: '로그인하기',
|
||||
logout: '로그아웃',
|
||||
set: '설정',
|
||||
theme: '테마',
|
||||
restart: '재시작',
|
||||
refresh: '새로고침',
|
||||
currentVersion: '현재 버전',
|
||||
searchPlaceholder: '검색해보세요...',
|
||||
zoom: '페이지 확대/축소',
|
||||
zoom100: '표준 확대/축소 100%',
|
||||
resetZoom: '클릭하여 확대/축소 재설정',
|
||||
zoomDefault: '표준 확대/축소'
|
||||
},
|
||||
titleBar: {
|
||||
closeTitle: '닫기 방법을 선택해주세요',
|
||||
minimizeToTray: '트레이로 최소화',
|
||||
exitApp: '앱 종료',
|
||||
rememberChoice: '선택 기억하기',
|
||||
closeApp: '앱 닫기'
|
||||
},
|
||||
userPlayList: {
|
||||
title: '{name}의 자주 듣는 음악'
|
||||
},
|
||||
musicList: {
|
||||
searchSongs: '곡 검색',
|
||||
noSearchResults: '관련 곡을 찾을 수 없습니다',
|
||||
switchToNormal: '기본 레이아웃으로 전환',
|
||||
switchToCompact: '컴팩트 레이아웃으로 전환',
|
||||
playAll: '모두 재생',
|
||||
collect: '수집',
|
||||
collectSuccess: '수집 성공',
|
||||
cancelCollectSuccess: '수집 취소 성공',
|
||||
operationFailed: '작업 실패',
|
||||
cancelCollect: '수집 취소',
|
||||
addToPlaylist: '재생 목록에 추가',
|
||||
addToPlaylistSuccess: '재생 목록에 추가 성공',
|
||||
songsAlreadyInPlaylist: '곡이 이미 재생 목록에 있습니다'
|
||||
},
|
||||
playlist: {
|
||||
import: {
|
||||
button: '플레이리스트 가져오기',
|
||||
title: '플레이리스트 가져오기',
|
||||
description: '메타데이터/텍스트/링크 세 가지 방법으로 플레이리스트 가져오기 지원',
|
||||
linkTab: '링크 가져오기',
|
||||
textTab: '텍스트 가져오기',
|
||||
localTab: '메타데이터 가져오기',
|
||||
linkPlaceholder: '플레이리스트 링크를 입력하세요. 한 줄에 하나씩',
|
||||
textPlaceholder: '곡 정보를 입력하세요. 형식: 곡명 가수명',
|
||||
localPlaceholder: 'JSON 형식의 곡 메타데이터를 입력하세요',
|
||||
linkTips: '지원되는 링크 소스:',
|
||||
linkTip1: '플레이리스트를 위챗/웨이보/QQ로 공유한 후 링크 복사',
|
||||
linkTip2: '플레이리스트/개인 홈페이지 링크 직접 복사',
|
||||
linkTip3: '기사 링크 직접 복사',
|
||||
textTips: '곡 정보를 입력하세요. 한 줄에 한 곡씩',
|
||||
textFormat: '형식: 곡명 가수명',
|
||||
localTips: '곡 메타데이터를 추가해주세요',
|
||||
localFormat: '형식 예시:',
|
||||
songNamePlaceholder: '곡명',
|
||||
artistNamePlaceholder: '아티스트명',
|
||||
albumNamePlaceholder: '앨범명',
|
||||
addSongButton: '곡 추가',
|
||||
addLinkButton: '링크 추가',
|
||||
importToStarPlaylist: '내가 좋아하는 음악으로 가져오기',
|
||||
playlistNamePlaceholder: '플레이리스트 이름을 입력하세요',
|
||||
importButton: '가져오기 시작',
|
||||
emptyLinkWarning: '플레이리스트 링크를 입력해주세요',
|
||||
emptyTextWarning: '곡 정보를 입력해주세요',
|
||||
emptyLocalWarning: '곡 메타데이터를 입력해주세요',
|
||||
invalidJsonFormat: 'JSON 형식이 올바르지 않습니다',
|
||||
importSuccess: '가져오기 작업 생성 성공',
|
||||
importFailed: '가져오기 실패',
|
||||
importStatus: '가져오기 상태',
|
||||
refresh: '새로고침',
|
||||
taskId: '작업 ID',
|
||||
status: '상태',
|
||||
successCount: '성공 수',
|
||||
failReason: '실패 이유',
|
||||
unknownError: '알 수 없는 오류',
|
||||
statusPending: '처리 대기 중',
|
||||
statusProcessing: '처리 중',
|
||||
statusSuccess: '가져오기 성공',
|
||||
statusFailed: '가져오기 실패',
|
||||
statusUnknown: '알 수 없는 상태',
|
||||
taskList: '작업 목록',
|
||||
taskListTitle: '가져오기 작업 목록',
|
||||
action: '작업',
|
||||
select: '선택',
|
||||
fetchTaskListFailed: '작업 목록 가져오기 실패',
|
||||
noTasks: '가져오기 작업이 없습니다',
|
||||
clearTasks: '작업 지우기',
|
||||
clearTasksConfirmTitle: '지우기 확인',
|
||||
clearTasksConfirmContent: '모든 가져오기 작업 기록을 지우시겠습니까? 이 작업은 되돌릴 수 없습니다.',
|
||||
confirm: '확인',
|
||||
cancel: '취소',
|
||||
clearTasksSuccess: '작업 목록이 지워졌습니다',
|
||||
clearTasksFailed: '작업 목록 지우기 실패'
|
||||
}
|
||||
},
|
||||
settings: '설정',
|
||||
user: '사용자',
|
||||
toplist: '순위',
|
||||
history: '수집 기록',
|
||||
list: '플레이리스트',
|
||||
mv: 'MV',
|
||||
home: '홈',
|
||||
search: '검색'
|
||||
};
|
||||
8
src/i18n/lang/ko-KR/donation.ts
Normal file
8
src/i18n/lang/ko-KR/donation.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
description: '귀하의 기부는 서버 유지보수, 도메인 갱신 등을 포함한 개발 및 유지보수 작업을 지원하는 데 사용됩니다.',
|
||||
message: '메시지를 남길 때 이메일이나 GitHub 이름을 남겨주세요.',
|
||||
refresh: '목록 새로고침',
|
||||
toDonateList: '커피 한 잔 사주세요',
|
||||
noMessage: '메시지가 없습니다',
|
||||
title: '기부 목록'
|
||||
};
|
||||
87
src/i18n/lang/ko-KR/download.ts
Normal file
87
src/i18n/lang/ko-KR/download.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
export default {
|
||||
title: '다운로드 관리',
|
||||
localMusic: '로컬 음악',
|
||||
count: '총 {count}곡',
|
||||
clearAll: '기록 지우기',
|
||||
settings: '설정',
|
||||
tabs: {
|
||||
downloading: '다운로드 중',
|
||||
downloaded: '다운로드 완료'
|
||||
},
|
||||
empty: {
|
||||
noTasks: '다운로드 작업이 없습니다',
|
||||
noDownloaded: '다운로드된 곡이 없습니다'
|
||||
},
|
||||
progress: {
|
||||
total: '전체 진행률: {progress}%'
|
||||
},
|
||||
status: {
|
||||
downloading: '다운로드 중',
|
||||
completed: '완료',
|
||||
failed: '실패',
|
||||
unknown: '알 수 없음'
|
||||
},
|
||||
artist: {
|
||||
unknown: '알 수 없는 가수'
|
||||
},
|
||||
delete: {
|
||||
title: '삭제 확인',
|
||||
message: '곡 "{filename}"을(를) 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.',
|
||||
confirm: '삭제 확인',
|
||||
cancel: '취소',
|
||||
success: '삭제 성공',
|
||||
failed: '삭제 실패',
|
||||
fileNotFound: '파일이 존재하지 않거나 이동되었습니다. 기록에서 제거되었습니다',
|
||||
recordRemoved: '파일 삭제 실패, 하지만 기록에서 제거되었습니다'
|
||||
},
|
||||
clear: {
|
||||
title: '다운로드 기록 지우기',
|
||||
message: '모든 다운로드 기록을 지우시겠습니까? 이 작업은 다운로드된 음악 파일을 삭제하지 않지만 모든 기록을 지웁니다.',
|
||||
confirm: '지우기 확인',
|
||||
cancel: '취소',
|
||||
success: '다운로드 기록이 지워졌습니다'
|
||||
},
|
||||
message: {
|
||||
downloadComplete: '{filename} 다운로드 완료',
|
||||
downloadFailed: '{filename} 다운로드 실패: {error}'
|
||||
},
|
||||
loading: '로딩 중...',
|
||||
playStarted: '재생 시작: {name}',
|
||||
playFailed: '재생 실패: {name}',
|
||||
path: {
|
||||
copied: '경로가 클립보드에 복사됨',
|
||||
copyFailed: '경로 복사 실패'
|
||||
},
|
||||
settingsPanel: {
|
||||
title: '다운로드 설정',
|
||||
path: '다운로드 위치',
|
||||
pathDesc: '음악 파일 다운로드 저장 위치 설정',
|
||||
pathPlaceholder: '다운로드 경로를 선택해주세요',
|
||||
noPathSelected: '먼저 다운로드 경로를 선택해주세요',
|
||||
select: '폴더 선택',
|
||||
open: '폴더 열기',
|
||||
fileFormat: '파일명 형식',
|
||||
fileFormatDesc: '음악 다운로드 시 파일 이름 형식 설정',
|
||||
customFormat: '사용자 정의 형식',
|
||||
separator: '구분자',
|
||||
separators: {
|
||||
dash: '공백-공백',
|
||||
underscore: '밑줄',
|
||||
space: '공백'
|
||||
},
|
||||
dragToArrange: '드래그하여 정렬하거나 화살표 버튼을 사용하여 순서 조정:',
|
||||
formatVariables: '사용 가능한 변수',
|
||||
preview: '미리보기 효과:',
|
||||
saveSuccess: '다운로드 설정이 저장됨',
|
||||
presets: {
|
||||
songArtist: '곡명 - 가수명',
|
||||
artistSong: '가수명 - 곡명',
|
||||
songOnly: '곡명만'
|
||||
},
|
||||
components: {
|
||||
songName: '곡명',
|
||||
artistName: '가수명',
|
||||
albumName: '앨범명'
|
||||
}
|
||||
}
|
||||
};
|
||||
13
src/i18n/lang/ko-KR/favorite.ts
Normal file
13
src/i18n/lang/ko-KR/favorite.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export default {
|
||||
title: '내 수집',
|
||||
count: '총 {count}곡',
|
||||
batchDownload: '일괄 다운로드',
|
||||
download: '다운로드 ({count})',
|
||||
emptyTip: '아직 수집한 곡이 없습니다',
|
||||
downloadSuccess: '다운로드 완료',
|
||||
downloadFailed: '다운로드 실패',
|
||||
downloading: '다운로드 중입니다. 잠시만 기다려주세요...',
|
||||
selectSongsFirst: '먼저 다운로드할 곡을 선택해주세요',
|
||||
descending: '내림차순',
|
||||
ascending: '오름차순'
|
||||
};
|
||||
5
src/i18n/lang/ko-KR/history.ts
Normal file
5
src/i18n/lang/ko-KR/history.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
title: '재생 기록',
|
||||
playCount: '{count}',
|
||||
getHistoryFailed: '기록 가져오기 실패'
|
||||
};
|
||||
48
src/i18n/lang/ko-KR/login.ts
Normal file
48
src/i18n/lang/ko-KR/login.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
export default {
|
||||
title: {
|
||||
qr: 'QR코드 로그인',
|
||||
phone: '휴대폰 번호 로그인',
|
||||
cookie: 'Cookie 로그인',
|
||||
uid: 'UID 로그인'
|
||||
},
|
||||
qrTip: '넷이즈 클라우드 뮤직 앱으로 QR코드를 스캔하여 로그인',
|
||||
phoneTip: '넷이즈 클라우드 계정으로 로그인',
|
||||
tokenTip: '유효한 넷이즈 클라우드 뮤직 Cookie을 입력하여 로그인',
|
||||
uidTip: '사용자 ID를 입력하여 빠른 로그인',
|
||||
placeholder: {
|
||||
phone: '휴대폰 번호',
|
||||
password: '비밀번호',
|
||||
cookie: '넷이즈 클라우드 뮤직 Cookie(token)을 입력하세요',
|
||||
uid: '사용자 ID(UID)를 입력하세요'
|
||||
},
|
||||
button: {
|
||||
login: '로그인',
|
||||
switchToQr: 'QR코드 로그인',
|
||||
switchToPhone: '휴대폰 번호 로그인',
|
||||
switchToToken: 'Cookie 로그인 사용',
|
||||
switchToUid: 'UID 로그인',
|
||||
backToQr: 'QR코드 로그인으로 돌아가기',
|
||||
cookieLogin: 'Cookie 로그인',
|
||||
autoGetCookie: 'Cookie 자동 가져오기',
|
||||
refresh: '새로고침',
|
||||
refreshing: '새로고침 중...',
|
||||
refreshQr: 'QR코드 새로고침'
|
||||
},
|
||||
message: {
|
||||
loginSuccess: '로그인 성공',
|
||||
tokenLoginSuccess: 'Cookie 로그인 성공',
|
||||
uidLoginSuccess: 'UID 로그인 성공',
|
||||
loadError: '로그인 정보 로드 중 오류 발생',
|
||||
qrCheckError: 'QR코드 상태 확인 중 오류 발생',
|
||||
tokenRequired: 'Cookie을 입력하세요',
|
||||
tokenInvalid: 'Cookie이 유효하지 않습니다. 확인 후 다시 시도하세요',
|
||||
uidRequired: '사용자 ID를 입력하세요',
|
||||
uidInvalid: '사용자 ID가 유효하지 않거나 사용자가 존재하지 않습니다',
|
||||
uidLoginFailed: 'UID 로그인에 실패했습니다. 사용자 ID가 올바른지 확인하세요',
|
||||
autoGetCookieSuccess: 'Cookie 자동 가져오기 성공',
|
||||
autoGetCookieFailed: 'Cookie 자동 가져오기 실패',
|
||||
autoGetCookieTip: '넷이즈 클라우드 뮤직 로그인 페이지를 열겠습니다. 로그인 완료 후 창을 닫아주세요'
|
||||
},
|
||||
qrTitle: '넷이즈 클라우드 뮤직 QR코드 로그인',
|
||||
uidWarning: '주의: UID 로그인은 사용자 공개 정보를 확인하는 데만 사용할 수 있으며, 로그인 권한이 필요한 기능에 액세스할 수 없습니다.'
|
||||
};
|
||||
122
src/i18n/lang/ko-KR/player.ts
Normal file
122
src/i18n/lang/ko-KR/player.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
export default {
|
||||
nowPlaying: '현재 재생 중',
|
||||
playlist: '재생 목록',
|
||||
lyrics: '가사',
|
||||
previous: '이전',
|
||||
play: '재생',
|
||||
pause: '일시정지',
|
||||
next: '다음',
|
||||
volumeUp: '볼륨 증가',
|
||||
volumeDown: '볼륨 감소',
|
||||
mute: '음소거',
|
||||
unmute: '음소거 해제',
|
||||
songNum: '총 곡 수: {num}',
|
||||
addCorrection: '{num}초 앞당기기',
|
||||
subtractCorrection: '{num}초 지연',
|
||||
playFailed: '현재 곡 재생 실패, 다음 곡 재생',
|
||||
playMode: {
|
||||
sequence: '순차 재생',
|
||||
loop: '한 곡 반복',
|
||||
random: '랜덤 재생'
|
||||
},
|
||||
fullscreen: {
|
||||
enter: '전체화면',
|
||||
exit: '전체화면 종료'
|
||||
},
|
||||
close: '닫기',
|
||||
modeHint: {
|
||||
single: '한 곡 반복',
|
||||
list: '자동으로 다음 곡 재생'
|
||||
},
|
||||
lrc: {
|
||||
noLrc: '가사가 없습니다. 음악을 감상해주세요'
|
||||
},
|
||||
reparse: {
|
||||
title: '음원 선택',
|
||||
desc: '음원을 클릭하여 직접 분석하세요. 다음에 이 곡을 재생할 때 선택한 음원을 사용합니다',
|
||||
success: '재분석 성공',
|
||||
failed: '재분석 실패',
|
||||
warning: '음원을 선택해주세요',
|
||||
bilibiliNotSupported: 'B站 비디오는 재분석을 지원하지 않습니다',
|
||||
processing: '분석 중...',
|
||||
clear: '사용자 정의 음원 지우기'
|
||||
},
|
||||
playBar: {
|
||||
expand: '가사 펼치기',
|
||||
collapse: '가사 접기',
|
||||
like: '좋아요',
|
||||
lyric: '가사',
|
||||
noSongPlaying: '재생 중인 곡이 없습니다',
|
||||
eq: '이퀄라이저',
|
||||
playList: '재생 목록',
|
||||
reparse: '재분석',
|
||||
playMode: {
|
||||
sequence: '순차 재생',
|
||||
loop: '반복 재생',
|
||||
random: '랜덤 재생'
|
||||
},
|
||||
play: '재생 시작',
|
||||
pause: '재생 일시정지',
|
||||
prev: '이전 곡',
|
||||
next: '다음 곡',
|
||||
volume: '볼륨',
|
||||
favorite: '{name} 즐겨찾기 추가됨',
|
||||
unFavorite: '{name} 즐겨찾기 해제됨',
|
||||
miniPlayBar: '미니 재생바',
|
||||
playbackSpeed: '재생 속도',
|
||||
advancedControls: '고급 설정'
|
||||
},
|
||||
eq: {
|
||||
title: '이퀄라이저',
|
||||
reset: '재설정',
|
||||
on: '켜기',
|
||||
off: '끄기',
|
||||
bass: '저음',
|
||||
midrange: '중음',
|
||||
treble: '고음',
|
||||
presets: {
|
||||
flat: '플랫',
|
||||
pop: '팝',
|
||||
rock: '록',
|
||||
classical: '클래식',
|
||||
jazz: '재즈',
|
||||
electronic: '일렉트로닉',
|
||||
hiphop: '힙합',
|
||||
rb: 'R&B',
|
||||
metal: '메탈',
|
||||
vocal: '보컬',
|
||||
dance: '댄스',
|
||||
acoustic: '어쿠스틱',
|
||||
custom: '사용자 정의'
|
||||
}
|
||||
},
|
||||
sleepTimer: {
|
||||
title: '타이머 종료',
|
||||
cancel: '타이머 취소',
|
||||
timeMode: '시간으로 종료',
|
||||
songsMode: '곡 수로 종료',
|
||||
playlistEnd: '재생 목록 완료 후 종료',
|
||||
afterPlaylist: '재생 목록 완료 후 종료',
|
||||
activeUntilEnd: '목록 끝까지 재생',
|
||||
minutes: '분',
|
||||
hours: '시간',
|
||||
songs: '곡',
|
||||
set: '설정',
|
||||
timerSetSuccess: '{minutes}분 후 종료로 설정됨',
|
||||
songsSetSuccess: '{songs}곡 재생 후 종료로 설정됨',
|
||||
playlistEndSetSuccess: '재생 목록 완료 후 종료로 설정됨',
|
||||
timerCancelled: '타이머 종료 취소됨',
|
||||
timerEnded: '타이머 종료 실행됨',
|
||||
playbackStopped: '음악 재생이 중지됨',
|
||||
minutesRemaining: '남은 시간 {minutes}분',
|
||||
songsRemaining: '남은 곡 수 {count}곡'
|
||||
},
|
||||
playList: {
|
||||
clearAll: '재생 목록 비우기',
|
||||
alreadyEmpty: '재생 목록이 이미 비어있습니다',
|
||||
cleared: '재생 목록이 비워졌습니다',
|
||||
empty: '재생 목록이 비어있습니다',
|
||||
clearConfirmTitle: '재생 목록 비우기',
|
||||
clearConfirmContent: '재생 목록의 모든 곡을 삭제하고 현재 재생을 중지합니다. 계속하시겠습니까?'
|
||||
}
|
||||
};
|
||||
27
src/i18n/lang/ko-KR/search.ts
Normal file
27
src/i18n/lang/ko-KR/search.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export default {
|
||||
title: {
|
||||
hotSearch: '인기 검색',
|
||||
searchList: '검색 목록',
|
||||
searchHistory: '검색 기록'
|
||||
},
|
||||
button: {
|
||||
clear: '지우기',
|
||||
back: '뒤로',
|
||||
playAll: '재생 목록'
|
||||
},
|
||||
loading: {
|
||||
more: '로딩 중...',
|
||||
failed: '검색 실패'
|
||||
},
|
||||
noMore: '더 이상 없음',
|
||||
error: {
|
||||
searchFailed: '검색 실패'
|
||||
},
|
||||
search: {
|
||||
single: '단일곡',
|
||||
album: '앨범',
|
||||
playlist: '플레이리스트',
|
||||
mv: 'MV',
|
||||
bilibili: 'B站'
|
||||
}
|
||||
};
|
||||
322
src/i18n/lang/ko-KR/settings.ts
Normal file
322
src/i18n/lang/ko-KR/settings.ts
Normal file
@@ -0,0 +1,322 @@
|
||||
export default {
|
||||
theme: '테마',
|
||||
language: '언어',
|
||||
regard: '정보',
|
||||
logout: '로그아웃',
|
||||
sections: {
|
||||
basic: '기본 설정',
|
||||
playback: '재생 설정',
|
||||
application: '애플리케이션 설정',
|
||||
network: '네트워크 설정',
|
||||
system: '시스템 관리',
|
||||
donation: '후원 지원',
|
||||
regard: '정보'
|
||||
},
|
||||
basic: {
|
||||
themeMode: '테마 모드',
|
||||
themeModeDesc: '낮/밤 테마 전환',
|
||||
autoTheme: '시스템 따라가기',
|
||||
manualTheme: '수동 전환',
|
||||
language: '언어 설정',
|
||||
languageDesc: '표시 언어 전환',
|
||||
tokenManagement: 'Cookie 관리',
|
||||
tokenManagementDesc: '넷이즈 클라우드 뮤직 로그인 Cookie 관리',
|
||||
tokenStatus: '현재 Cookie 상태',
|
||||
tokenSet: '설정됨',
|
||||
tokenNotSet: '설정되지 않음',
|
||||
setToken: 'Cookie 설정',
|
||||
modifyToken: 'Cookie 수정',
|
||||
clearToken: 'Cookie 지우기',
|
||||
font: '폰트 설정',
|
||||
fontDesc: '폰트 선택, 앞에 있는 폰트를 우선 사용',
|
||||
fontScope: {
|
||||
global: '전역',
|
||||
lyric: '가사만'
|
||||
},
|
||||
animation: '애니메이션 속도',
|
||||
animationDesc: '애니메이션 활성화 여부',
|
||||
animationSpeed: {
|
||||
slow: '매우 느림',
|
||||
normal: '보통',
|
||||
fast: '매우 빠름'
|
||||
},
|
||||
fontPreview: {
|
||||
title: '폰트 미리보기',
|
||||
chinese: '中文',
|
||||
english: 'English',
|
||||
japanese: '日本語',
|
||||
korean: '한국어',
|
||||
chineseText: '静夜思 床前明月光 疑是地上霜',
|
||||
englishText: 'The quick brown fox jumps over the lazy dog',
|
||||
japaneseText: 'あいうえお かきくけこ さしすせそ',
|
||||
koreanText: '가나다라마 바사아자차 카타파하'
|
||||
}
|
||||
},
|
||||
playback: {
|
||||
quality: '음질 설정',
|
||||
qualityDesc: '음악 재생 음질 선택 (넷이즈 클라우드 VIP)',
|
||||
qualityOptions: {
|
||||
standard: '표준',
|
||||
higher: '높음',
|
||||
exhigh: '매우 높음',
|
||||
lossless: '무손실',
|
||||
hires: 'Hi-Res',
|
||||
jyeffect: 'HD 서라운드',
|
||||
sky: '몰입형 서라운드',
|
||||
dolby: '돌비 애트모스',
|
||||
jymaster: '초고화질 마스터'
|
||||
},
|
||||
musicSources: '음원 설정',
|
||||
musicSourcesDesc: '음악 해석에 사용할 음원 플랫폼 선택',
|
||||
musicSourcesWarning: '최소 하나의 음원 플랫폼을 선택해야 합니다',
|
||||
musicUnblockEnable: '음악 해석 활성화',
|
||||
musicUnblockEnableDesc: '활성화하면 재생할 수 없는 음악을 해석하려고 시도합니다',
|
||||
configureMusicSources: '음원 구성',
|
||||
selectedMusicSources: '선택된 음원:',
|
||||
noMusicSources: '음원이 선택되지 않음',
|
||||
gdmusicInfo: 'GD 뮤직은 여러 플랫폼 음원을 자동으로 해석하고 최적의 결과를 자동 선택합니다',
|
||||
autoPlay: '자동 재생',
|
||||
autoPlayDesc: '앱을 다시 열 때 자동으로 재생을 계속할지 여부',
|
||||
showStatusBar: '상태바 제어 기능 표시 여부',
|
||||
showStatusBarContent: 'Mac 상태바에 음악 제어 기능을 표시할 수 있습니다 (재시작 후 적용)'
|
||||
},
|
||||
application: {
|
||||
closeAction: '닫기 동작',
|
||||
closeActionDesc: '창을 닫을 때의 동작 선택',
|
||||
closeOptions: {
|
||||
ask: '매번 묻기',
|
||||
minimize: '트레이로 최소화',
|
||||
close: '직접 종료'
|
||||
},
|
||||
shortcut: '단축키 설정',
|
||||
shortcutDesc: '전역 단축키 사용자 정의',
|
||||
download: '다운로드 관리',
|
||||
downloadDesc: '다운로드 목록 버튼을 항상 표시할지 여부',
|
||||
unlimitedDownload: '무제한 다운로드',
|
||||
unlimitedDownloadDesc: '활성화하면 음악을 무제한으로 다운로드합니다 (다운로드 실패가 발생할 수 있음), 기본 제한 300곡',
|
||||
downloadPath: '다운로드 디렉토리',
|
||||
downloadPathDesc: '음악 파일의 다운로드 위치 선택',
|
||||
remoteControl: '원격 제어',
|
||||
remoteControlDesc: '원격 제어 기능 설정'
|
||||
},
|
||||
network: {
|
||||
apiPort: '음악 API 포트',
|
||||
apiPortDesc: '수정 후 앱을 재시작해야 합니다',
|
||||
proxy: '프록시 설정',
|
||||
proxyDesc: '음악에 액세스할 수 없을 때 프록시를 활성화할 수 있습니다',
|
||||
proxyHost: '프록시 주소',
|
||||
proxyHostPlaceholder: '프록시 주소를 입력하세요',
|
||||
proxyPort: '프록시 포트',
|
||||
proxyPortPlaceholder: '프록시 포트를 입력하세요',
|
||||
realIP: 'realIP 설정',
|
||||
realIPDesc: '제한으로 인해 이 프로젝트는 해외에서 사용할 때 제한을 받을 수 있으며, realIP 매개변수를 사용하여 국내 IP를 전달하여 해결할 수 있습니다',
|
||||
messages: {
|
||||
proxySuccess: '프록시 설정이 저장되었습니다. 앱을 재시작한 후 적용됩니다',
|
||||
proxyError: '입력이 올바른지 확인하세요',
|
||||
realIPSuccess: '실제 IP 설정이 저장되었습니다',
|
||||
realIPError: '유효한 IP 주소를 입력하세요'
|
||||
}
|
||||
},
|
||||
system: {
|
||||
cache: '캐시 관리',
|
||||
cacheDesc: '캐시 지우기',
|
||||
cacheClearTitle: '지울 캐시 유형을 선택하세요:',
|
||||
cacheTypes: {
|
||||
history: {
|
||||
label: '재생 기록',
|
||||
description: '재생한 곡 기록 지우기'
|
||||
},
|
||||
favorite: {
|
||||
label: '즐겨찾기 기록',
|
||||
description: '로컬 즐겨찾기 곡 기록 지우기 (클라우드 즐겨찾기에는 영향 없음)'
|
||||
},
|
||||
user: {
|
||||
label: '사용자 데이터',
|
||||
description: '로그인 정보 및 사용자 관련 데이터 지우기'
|
||||
},
|
||||
settings: {
|
||||
label: '앱 설정',
|
||||
description: '앱의 모든 사용자 정의 설정 지우기'
|
||||
},
|
||||
downloads: {
|
||||
label: '다운로드 기록',
|
||||
description: '다운로드 기록 지우기 (다운로드된 파일은 삭제되지 않음)'
|
||||
},
|
||||
resources: {
|
||||
label: '음악 리소스',
|
||||
description: '로드된 음악 파일, 가사 등 리소스 캐시 지우기'
|
||||
},
|
||||
lyrics: {
|
||||
label: '가사 리소스',
|
||||
description: '로드된 가사 리소스 캐시 지우기'
|
||||
}
|
||||
},
|
||||
restart: '재시작',
|
||||
restartDesc: '앱 재시작',
|
||||
messages: {
|
||||
clearSuccess: '지우기 성공, 일부 설정은 재시작 후 적용됩니다'
|
||||
}
|
||||
},
|
||||
about: {
|
||||
version: '버전',
|
||||
checkUpdate: '업데이트 확인',
|
||||
checking: '확인 중...',
|
||||
latest: '현재 최신 버전입니다',
|
||||
hasUpdate: '새 버전 발견',
|
||||
gotoUpdate: '업데이트하러 가기',
|
||||
gotoGithub: 'Github로 이동',
|
||||
author: '작성자',
|
||||
authorDesc: 'algerkong 별점🌟 부탁드려요',
|
||||
messages: {
|
||||
checkError: '업데이트 확인 실패, 나중에 다시 시도하세요'
|
||||
}
|
||||
},
|
||||
validation: {
|
||||
selectProxyProtocol: '프록시 프로토콜을 선택하세요',
|
||||
proxyHost: '프록시 주소를 입력하세요',
|
||||
portNumber: '유효한 포트 번호를 입력하세요 (1-65535)'
|
||||
},
|
||||
lyricSettings: {
|
||||
title: '가사 설정',
|
||||
tabs: {
|
||||
display: '표시',
|
||||
interface: '인터페이스',
|
||||
typography: '텍스트',
|
||||
mobile: '모바일'
|
||||
},
|
||||
pureMode: '순수 모드',
|
||||
hideCover: '커버 숨기기',
|
||||
centerDisplay: '중앙 표시',
|
||||
showTranslation: '번역 표시',
|
||||
hideLyrics: '가사 숨기기',
|
||||
hidePlayBar: '재생바 숨기기',
|
||||
hideMiniPlayBar: '미니 재생바 숨기기',
|
||||
showMiniPlayBar: '미니 재생바 표시',
|
||||
backgroundTheme: '배경 테마',
|
||||
themeOptions: {
|
||||
default: '기본',
|
||||
light: '밝음',
|
||||
dark: '어둠'
|
||||
},
|
||||
fontSize: '폰트 크기',
|
||||
fontSizeMarks: {
|
||||
small: '작음',
|
||||
medium: '중간',
|
||||
large: '큼'
|
||||
},
|
||||
letterSpacing: '글자 간격',
|
||||
letterSpacingMarks: {
|
||||
compact: '좁음',
|
||||
default: '기본',
|
||||
loose: '넓음'
|
||||
},
|
||||
lineHeight: '줄 높이',
|
||||
lineHeightMarks: {
|
||||
compact: '좁음',
|
||||
default: '기본',
|
||||
loose: '넓음'
|
||||
},
|
||||
mobileLayout: '모바일 레이아웃',
|
||||
layoutOptions: {
|
||||
default: '기본',
|
||||
ios: 'iOS 스타일',
|
||||
android: '안드로이드 스타일'
|
||||
},
|
||||
mobileCoverStyle: '커버 스타일',
|
||||
coverOptions: {
|
||||
record: '레코드',
|
||||
square: '정사각형',
|
||||
full: '전체화면'
|
||||
},
|
||||
lyricLines: '가사 줄 수',
|
||||
mobileUnavailable: '이 설정은 모바일에서만 사용 가능합니다'
|
||||
},
|
||||
themeColor: {
|
||||
title: '가사 테마 색상',
|
||||
presetColors: '미리 설정된 색상',
|
||||
customColor: '사용자 정의 색상',
|
||||
preview: '미리보기 효과',
|
||||
previewText: '가사 효과',
|
||||
colorNames: {
|
||||
'spotify-green': 'Spotify 그린',
|
||||
'apple-blue': '애플 블루',
|
||||
'youtube-red': 'YouTube 레드',
|
||||
orange: '활력 오렌지',
|
||||
purple: '신비 퍼플',
|
||||
pink: '벚꽃 핑크'
|
||||
},
|
||||
tooltips: {
|
||||
openColorPicker: '색상 선택기 열기',
|
||||
closeColorPicker: '색상 선택기 닫기'
|
||||
},
|
||||
placeholder: '#1db954'
|
||||
},
|
||||
shortcutSettings: {
|
||||
title: '단축키 설정',
|
||||
shortcut: '단축키',
|
||||
shortcutDesc: '단축키 사용자 정의',
|
||||
shortcutConflict: '단축키 충돌',
|
||||
inputPlaceholder: '클릭하여 단축키 입력',
|
||||
resetShortcuts: '기본값 복원',
|
||||
disableAll: '모두 비활성화',
|
||||
enableAll: '모두 활성화',
|
||||
togglePlay: '재생/일시정지',
|
||||
prevPlay: '이전 곡',
|
||||
nextPlay: '다음 곡',
|
||||
volumeUp: '볼륨 증가',
|
||||
volumeDown: '볼륨 감소',
|
||||
toggleFavorite: '즐겨찾기/즐겨찾기 취소',
|
||||
toggleWindow: '창 표시/숨기기',
|
||||
scopeGlobal: '전역',
|
||||
scopeApp: '앱 내',
|
||||
enabled: '활성화',
|
||||
disabled: '비활성화',
|
||||
messages: {
|
||||
resetSuccess: '기본 단축키로 복원되었습니다. 저장을 잊지 마세요',
|
||||
conflict: '충돌하는 단축키가 있습니다. 다시 설정하세요',
|
||||
saveSuccess: '단축키 설정이 저장되었습니다',
|
||||
saveError: '단축키 저장 실패, 다시 시도하세요',
|
||||
cancelEdit: '수정이 취소되었습니다',
|
||||
disableAll: '모든 단축키가 비활성화되었습니다. 저장을 잊지 마세요',
|
||||
enableAll: '모든 단축키가 활성화되었습니다. 저장을 잊지 마세요'
|
||||
}
|
||||
},
|
||||
remoteControl: {
|
||||
title: '원격 제어',
|
||||
enable: '원격 제어 활성화',
|
||||
port: '서비스 포트',
|
||||
allowedIps: '허용된 IP 주소',
|
||||
addIp: 'IP 추가',
|
||||
emptyListHint: '빈 목록은 모든 IP 액세스를 허용함을 의미합니다',
|
||||
saveSuccess: '원격 제어 설정이 저장되었습니다',
|
||||
accessInfo: '원격 제어 액세스 주소:'
|
||||
},
|
||||
cookie: {
|
||||
title: 'Cookie 설정',
|
||||
description: '넷이즈 클라우드 뮤직의 Cookie를 입력하세요:',
|
||||
placeholder: '완전한 Cookie를 붙여넣으세요...',
|
||||
help: {
|
||||
format: 'Cookie는 일반적으로 "MUSIC_U="로 시작합니다',
|
||||
source: '브라우저 개발자 도구의 네트워크 요청에서 얻을 수 있습니다',
|
||||
storage: 'Cookie 설정 후 자동으로 로컬 저장소에 저장됩니다'
|
||||
},
|
||||
action: {
|
||||
save: 'Cookie 저장',
|
||||
paste: '붙여넣기',
|
||||
clear: '지우기'
|
||||
},
|
||||
validation: {
|
||||
required: 'Cookie를 입력하세요',
|
||||
format: 'Cookie 형식이 올바르지 않을 수 있습니다. MUSIC_U가 포함되어 있는지 확인하세요'
|
||||
},
|
||||
message: {
|
||||
saveSuccess: 'Cookie 저장 성공',
|
||||
saveError: 'Cookie 저장 실패',
|
||||
pasteSuccess: '붙여넣기 성공',
|
||||
pasteError: '붙여넣기 실패, 수동으로 복사하세요'
|
||||
},
|
||||
info: {
|
||||
length: '현재 길이: {length} 문자'
|
||||
}
|
||||
}
|
||||
};
|
||||
28
src/i18n/lang/ko-KR/songItem.ts
Normal file
28
src/i18n/lang/ko-KR/songItem.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export default {
|
||||
menu: {
|
||||
play: '재생',
|
||||
playNext: '다음에 재생',
|
||||
download: '곡 다운로드',
|
||||
addToPlaylist: '플레이리스트에 추가',
|
||||
favorite: '좋아요',
|
||||
unfavorite: '좋아요 취소',
|
||||
removeFromPlaylist: '플레이리스트에서 삭제',
|
||||
dislike: '싫어요',
|
||||
undislike: '싫어요 취소'
|
||||
},
|
||||
message: {
|
||||
downloading: '다운로드 중입니다. 잠시 기다려주세요...',
|
||||
downloadFailed: '다운로드 실패',
|
||||
downloadQueued: '다운로드 대기열에 추가됨',
|
||||
addedToNextPlay: '다음 재생에 추가됨',
|
||||
getUrlFailed: '음악 다운로드 주소 가져오기 실패, 로그인 상태를 확인하세요'
|
||||
},
|
||||
dialog: {
|
||||
dislike: {
|
||||
title: '알림!',
|
||||
content: '이 곡을 싫어한다고 확인하시겠습니까? 다시 들어가면 일일 추천에서 제외됩니다.',
|
||||
positiveText: '싫어요',
|
||||
negativeText: '취소'
|
||||
}
|
||||
}
|
||||
};
|
||||
48
src/i18n/lang/ko-KR/user.ts
Normal file
48
src/i18n/lang/ko-KR/user.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
export default {
|
||||
profile: {
|
||||
followers: '팔로워',
|
||||
following: '팔로잉',
|
||||
level: '레벨'
|
||||
},
|
||||
playlist: {
|
||||
created: '생성한 플레이리스트',
|
||||
mine: '내가 만든',
|
||||
trackCount: '{count}곡',
|
||||
playCount: '{count}회 재생'
|
||||
},
|
||||
ranking: {
|
||||
title: '음악 청취 순위',
|
||||
playCount: '{count}회'
|
||||
},
|
||||
follow: {
|
||||
title: '팔로잉 목록',
|
||||
viewPlaylist: '플레이리스트 보기',
|
||||
noFollowings: '팔로잉이 없습니다',
|
||||
loadMore: '더 보기',
|
||||
noSignature: '이 사람은 게을러서 아무것도 남기지 않았습니다',
|
||||
userFollowsTitle: '의 팔로잉',
|
||||
myFollowsTitle: '내 팔로잉'
|
||||
},
|
||||
follower: {
|
||||
title: '팔로워 목록',
|
||||
noFollowers: '팔로워가 없습니다',
|
||||
loadMore: '더 보기',
|
||||
userFollowersTitle: '의 팔로워',
|
||||
myFollowersTitle: '내 팔로워'
|
||||
},
|
||||
detail: {
|
||||
playlists: '플레이리스트',
|
||||
records: '음악 청취 순위',
|
||||
noPlaylists: '플레이리스트가 없습니다',
|
||||
noRecords: '음악 청취 기록이 없습니다',
|
||||
artist: '아티스트',
|
||||
noSignature: '이 사람은 게을러서 아무것도 남기지 않았습니다',
|
||||
invalidUserId: '사용자 ID가 유효하지 않습니다',
|
||||
noRecordPermission: '{name}님이 음악 청취 순위를 보지 못하게 했습니다'
|
||||
},
|
||||
message: {
|
||||
loadFailed: '사용자 페이지 로드 실패',
|
||||
deleteSuccess: '삭제 성공',
|
||||
deleteFailed: '삭제 실패'
|
||||
}
|
||||
};
|
||||
@@ -58,7 +58,7 @@ export default {
|
||||
wechatQR: '微信收款码',
|
||||
coffeeDesc: '一杯咖啡,一份支持',
|
||||
coffeeDescLinkText: '查看更多',
|
||||
qqGroup: 'QQ频道:algermusic',
|
||||
groupText: '微信公众号:AlgerMusic',
|
||||
messages: {
|
||||
copySuccess: '已复制到剪贴板'
|
||||
},
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import artist from './artist';
|
||||
import common from './common';
|
||||
import comp from './comp';
|
||||
import donation from './donation';
|
||||
import download from './download';
|
||||
import favorite from './favorite';
|
||||
import history from './history';
|
||||
import login from './login';
|
||||
import player from './player';
|
||||
import search from './search';
|
||||
import settings from './settings';
|
||||
import songItem from './songItem';
|
||||
import user from './user';
|
||||
|
||||
export default {
|
||||
common,
|
||||
donation,
|
||||
favorite,
|
||||
history,
|
||||
login,
|
||||
player,
|
||||
search,
|
||||
settings,
|
||||
songItem,
|
||||
user,
|
||||
download,
|
||||
comp,
|
||||
artist
|
||||
};
|
||||
@@ -1,22 +1,62 @@
|
||||
export default {
|
||||
title: {
|
||||
qr: '扫码登录',
|
||||
phone: '手机号登录'
|
||||
phone: '手机号登录',
|
||||
cookie: 'Cookie登录',
|
||||
uid: 'UID登录'
|
||||
},
|
||||
qrTip: '使用网易云APP扫码登录',
|
||||
phoneTip: '使用网易云账号登录',
|
||||
tokenTip: '输入有效的网易云音乐Cookie即可登录',
|
||||
uidTip: '输入用户ID快速登录',
|
||||
placeholder: {
|
||||
phone: '手机号',
|
||||
password: '密码'
|
||||
password: '密码',
|
||||
cookie: '请输入网易云音乐Cookie(token)',
|
||||
uid: '请输入用户ID(UID)'
|
||||
},
|
||||
button: {
|
||||
login: '登录',
|
||||
switchToQr: '扫码登录',
|
||||
switchToPhone: '手机号登录'
|
||||
switchToPhone: '手机号登录',
|
||||
switchToToken: '使用Cookie登录',
|
||||
switchToUid: 'UID登录',
|
||||
backToQr: '返回二维码登录',
|
||||
cookieLogin: 'Cookie登录',
|
||||
autoGetCookie: '自动获取Cookie',
|
||||
refresh: '点击刷新',
|
||||
refreshing: '刷新中...',
|
||||
refreshQr: '刷新二维码'
|
||||
},
|
||||
message: {
|
||||
loginSuccess: '登录成功',
|
||||
loginFailed: '登录失败',
|
||||
tokenLoginSuccess: 'Cookie登录成功',
|
||||
uidLoginSuccess: 'UID登录成功',
|
||||
loadError: '加载登录信息时出错',
|
||||
qrCheckError: '检查二维码状态时出错'
|
||||
}
|
||||
qrCheckError: '检查二维码状态时出错',
|
||||
tokenRequired: '请输入Cookie',
|
||||
tokenInvalid: 'Cookie无效,请检查后重试',
|
||||
uidRequired: '请输入用户ID',
|
||||
uidInvalid: '用户ID无效或用户不存在',
|
||||
uidLoginFailed: 'UID登录失败,请检查用户ID是否正确',
|
||||
phoneRequired: '请输入手机号',
|
||||
passwordRequired: '请输入密码',
|
||||
phoneLoginFailed: '手机号登录失败,请检查手机号和密码是否正确',
|
||||
autoGetCookieSuccess: '自动获取Cookie成功',
|
||||
autoGetCookieFailed: '自动获取Cookie失败',
|
||||
autoGetCookieTip: '将打开网易云音乐登录页面,请完成登录后关闭窗口',
|
||||
qrCheckFailed: '检查二维码状态失败,请刷新重试',
|
||||
qrLoading: '正在加载二维码...',
|
||||
qrExpired: '二维码已过期,请点击刷新',
|
||||
qrExpiredShort: '二维码已过期',
|
||||
qrExpiredWarning: '二维码已过期,请点击刷新获取新的二维码',
|
||||
qrScanned: '已扫码,请在手机上确认登录',
|
||||
qrScannedShort: '已扫码',
|
||||
qrScannedInfo: '已扫码,请在手机上确认登录',
|
||||
qrConfirmed: '登录成功,正在跳转...',
|
||||
qrGenerating: '正在生成二维码...'
|
||||
},
|
||||
qrTitle: '扫码登录网易云音乐',
|
||||
uidWarning: '注意:UID登录仅用于查看用户公开信息,无法访问需要登录权限的功能'
|
||||
};
|
||||
|
||||
@@ -38,7 +38,8 @@ export default {
|
||||
failed: '重新解析失败',
|
||||
warning: '请选择一个音源',
|
||||
bilibiliNotSupported: 'B站视频不支持重新解析',
|
||||
processing: '解析中...'
|
||||
processing: '解析中...',
|
||||
clear: '清除自定义音源'
|
||||
},
|
||||
playBar: {
|
||||
expand: '展开歌词',
|
||||
@@ -63,7 +64,7 @@ export default {
|
||||
unFavorite: '已取消收藏{name}',
|
||||
miniPlayBar: '迷你播放栏',
|
||||
playbackSpeed: '播放速度',
|
||||
advancedControls: '更多设置s',
|
||||
advancedControls: '更多设置s'
|
||||
},
|
||||
eq: {
|
||||
title: '均衡器',
|
||||
|
||||
@@ -15,8 +15,18 @@ export default {
|
||||
basic: {
|
||||
themeMode: '主题模式',
|
||||
themeModeDesc: '切换日间/夜间主题',
|
||||
autoTheme: '跟随系统',
|
||||
manualTheme: '手动切换',
|
||||
language: '语言设置',
|
||||
languageDesc: '切换显示语言',
|
||||
tokenManagement: 'Cookie管理',
|
||||
tokenManagementDesc: '管理网易云音乐登录Cookie',
|
||||
tokenStatus: '当前Cookie状态',
|
||||
tokenSet: '已设置',
|
||||
tokenNotSet: '未设置',
|
||||
setToken: '设置Cookie',
|
||||
modifyToken: '修改Cookie',
|
||||
clearToken: '清除Cookie',
|
||||
font: '字体设置',
|
||||
fontDesc: '选择字体,优先使用排在前面的字体',
|
||||
fontScope: {
|
||||
@@ -68,7 +78,7 @@ export default {
|
||||
autoPlay: '自动播放',
|
||||
autoPlayDesc: '重新打开应用时是否自动继续播放',
|
||||
showStatusBar: '是否显示状态栏控制功能',
|
||||
showStatusBarContent: '可以在您的mac状态栏显示音乐控制功能(重启后生效)',
|
||||
showStatusBarContent: '可以在您的mac状态栏显示音乐控制功能(重启后生效)'
|
||||
},
|
||||
application: {
|
||||
closeAction: '关闭行为',
|
||||
@@ -181,6 +191,7 @@ export default {
|
||||
hideLyrics: '隐藏歌词',
|
||||
hidePlayBar: '隐藏播放栏',
|
||||
hideMiniPlayBar: '隐藏迷你播放栏',
|
||||
showMiniPlayBar: '显示迷你播放栏',
|
||||
backgroundTheme: '背景主题',
|
||||
themeOptions: {
|
||||
default: '默认',
|
||||
@@ -220,6 +231,26 @@ export default {
|
||||
lyricLines: '歌词行数',
|
||||
mobileUnavailable: '此设置仅在移动端可用'
|
||||
},
|
||||
themeColor: {
|
||||
title: '歌词主题色',
|
||||
presetColors: '预设颜色',
|
||||
customColor: '自定义颜色',
|
||||
preview: '预览效果',
|
||||
previewText: '歌词效果',
|
||||
colorNames: {
|
||||
'spotify-green': 'Spotify 绿',
|
||||
'apple-blue': '苹果蓝',
|
||||
'youtube-red': 'YouTube 红',
|
||||
orange: '活力橙',
|
||||
purple: '神秘紫',
|
||||
pink: '樱花粉'
|
||||
},
|
||||
tooltips: {
|
||||
openColorPicker: '打开色板',
|
||||
closeColorPicker: '关闭色板'
|
||||
},
|
||||
placeholder: '#1db954'
|
||||
},
|
||||
shortcutSettings: {
|
||||
title: '快捷键设置',
|
||||
shortcut: '快捷键',
|
||||
@@ -258,6 +289,34 @@ export default {
|
||||
addIp: '添加IP',
|
||||
emptyListHint: '空列表表示允许所有IP访问',
|
||||
saveSuccess: '远程控制设置已保存',
|
||||
accessInfo: '远程控制访问地址:',
|
||||
accessInfo: '远程控制访问地址:'
|
||||
},
|
||||
cookie: {
|
||||
title: 'Cookie设置',
|
||||
description: '请输入网易云音乐的Cookie:',
|
||||
placeholder: '请粘贴完整的Cookie...',
|
||||
help: {
|
||||
format: 'Cookie通常以 "MUSIC_U=" 开头',
|
||||
source: '可以从浏览器开发者工具的网络请求中获取',
|
||||
storage: 'Cookie设置后将自动保存到本地存储'
|
||||
},
|
||||
action: {
|
||||
save: '保存Cookie',
|
||||
paste: '粘贴',
|
||||
clear: '清空'
|
||||
},
|
||||
validation: {
|
||||
required: '请输入Cookie',
|
||||
format: 'Cookie格式可能不正确,请检查是否包含MUSIC_U'
|
||||
},
|
||||
message: {
|
||||
saveSuccess: 'Cookie保存成功',
|
||||
saveError: 'Cookie保存失败',
|
||||
pasteSuccess: '粘贴成功',
|
||||
pasteError: '粘贴失败,请手动复制'
|
||||
},
|
||||
info: {
|
||||
length: '当前长度:{length} 字符'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ export default {
|
||||
unfavorite: '取消喜欢',
|
||||
removeFromPlaylist: '从歌单中删除',
|
||||
dislike: '不喜欢',
|
||||
undislike: '取消不喜欢',
|
||||
undislike: '取消不喜欢'
|
||||
},
|
||||
message: {
|
||||
downloading: '正在下载中,请稍候...',
|
||||
|
||||
5
src/i18n/lang/zh-Hant/artist.ts
Normal file
5
src/i18n/lang/zh-Hant/artist.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
hotSongs: '熱門歌曲',
|
||||
albums: '專輯',
|
||||
description: '藝人介紹'
|
||||
};
|
||||
56
src/i18n/lang/zh-Hant/common.ts
Normal file
56
src/i18n/lang/zh-Hant/common.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
export default {
|
||||
play: '播放',
|
||||
next: '下一首',
|
||||
previous: '上一首',
|
||||
volume: '音量',
|
||||
settings: '設定',
|
||||
search: '搜尋',
|
||||
loading: '載入中...',
|
||||
loadingMore: '載入更多...',
|
||||
alipay: '支付寶',
|
||||
wechat: '微信支付',
|
||||
on: '開啟',
|
||||
off: '關閉',
|
||||
show: '顯示',
|
||||
hide: '隱藏',
|
||||
confirm: '確認',
|
||||
cancel: '取消',
|
||||
configure: '設定',
|
||||
open: '開啟',
|
||||
modify: '修改',
|
||||
success: '操作成功',
|
||||
error: '操作失敗',
|
||||
warning: '警告',
|
||||
info: '提示',
|
||||
save: '儲存',
|
||||
delete: '刪除',
|
||||
refresh: '重新整理',
|
||||
retry: '重試',
|
||||
reset: '重設',
|
||||
back: '返回',
|
||||
copySuccess: '已複製到剪貼簿',
|
||||
copyFailed: '複製失敗',
|
||||
validation: {
|
||||
required: '此項為必填',
|
||||
invalidInput: '輸入無效',
|
||||
selectRequired: '請選擇一個選項',
|
||||
numberRange: '請輸入 {min} 到 {max} 之間的數字'
|
||||
},
|
||||
viewMore: '查看更多',
|
||||
noMore: '沒有更多了',
|
||||
selectAll: '全選',
|
||||
expand: '展開',
|
||||
collapse: '收合',
|
||||
songCount: '{count}首',
|
||||
language: '語言',
|
||||
tray: {
|
||||
show: '顯示',
|
||||
quit: '退出',
|
||||
playPause: '播放/暫停',
|
||||
prev: '上一首',
|
||||
next: '下一首',
|
||||
pause: '暫停',
|
||||
play: '播放',
|
||||
favorite: '收藏'
|
||||
}
|
||||
};
|
||||
190
src/i18n/lang/zh-Hant/comp.ts
Normal file
190
src/i18n/lang/zh-Hant/comp.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
export default {
|
||||
installApp: {
|
||||
description: '安裝應用程式,獲得更好的體驗',
|
||||
noPrompt: '不再提示',
|
||||
install: '立即安裝',
|
||||
cancel: '暫不安裝',
|
||||
download: '下載',
|
||||
downloadFailed: '下載失敗',
|
||||
downloadComplete: '下載完成',
|
||||
downloadProblem: '下載遇到問題?去',
|
||||
downloadProblemLinkText: '下載最新版本'
|
||||
},
|
||||
playlistDrawer: {
|
||||
title: '新增至播放清單',
|
||||
createPlaylist: '建立新播放清單',
|
||||
cancelCreate: '取消建立',
|
||||
create: '建立',
|
||||
playlistName: '播放清單名稱',
|
||||
privatePlaylist: '私人播放清單',
|
||||
publicPlaylist: '公開播放清單',
|
||||
createSuccess: '播放清單建立成功',
|
||||
createFailed: '播放清單建立失敗',
|
||||
addSuccess: '歌曲新增成功',
|
||||
addFailed: '歌曲新增失敗',
|
||||
private: '私人',
|
||||
public: '公開',
|
||||
count: '首歌曲',
|
||||
loginFirst: '請先登入',
|
||||
getPlaylistFailed: '取得播放清單失敗',
|
||||
inputPlaylistName: '請輸入播放清單名稱'
|
||||
},
|
||||
update: {
|
||||
title: '發現新版本',
|
||||
currentVersion: '目前版本',
|
||||
cancel: '暫不更新',
|
||||
prepareDownload: '準備下載...',
|
||||
downloading: '下載中...',
|
||||
nowUpdate: '立即更新',
|
||||
downloadFailed: '下載失敗,請重試或手動下載',
|
||||
startFailed: '啟動下載失敗,請重試或手動下載',
|
||||
noDownloadUrl: '未找到適合目前系統的安裝包,請手動下載',
|
||||
installConfirmTitle: '安裝更新',
|
||||
installConfirmContent: '是否關閉應用程式並安裝更新?',
|
||||
manualInstallTip: '如果關閉應用程式後沒有正常彈出安裝程式,請至下載資料夾尋找檔案並手動開啟。',
|
||||
yesInstall: '立即安裝',
|
||||
noThanks: '稍後安裝',
|
||||
fileLocation: '檔案位置',
|
||||
copy: '複製路徑',
|
||||
copySuccess: '路徑已複製到剪貼簿',
|
||||
copyFailed: '複製失敗',
|
||||
backgroundDownload: '背景下載'
|
||||
},
|
||||
coffee: {
|
||||
title: '請我喝杯咖啡',
|
||||
alipay: '支付寶',
|
||||
wechat: '微信支付',
|
||||
alipayQR: '支付寶收款碼',
|
||||
wechatQR: '微信收款碼',
|
||||
coffeeDesc: '一杯咖啡,一份支持',
|
||||
coffeeDescLinkText: '查看更多',
|
||||
groupText: '微信公众号:AlgerMusic',
|
||||
messages: {
|
||||
copySuccess: '已複製到剪貼簿'
|
||||
},
|
||||
donateList: '請我喝杯咖啡'
|
||||
},
|
||||
playlistType: {
|
||||
title: '播放清單分類',
|
||||
showAll: '顯示全部',
|
||||
hide: '隱藏部分'
|
||||
},
|
||||
recommendAlbum: {
|
||||
title: '最新專輯'
|
||||
},
|
||||
recommendSinger: {
|
||||
title: '每日推薦',
|
||||
songlist: '每日推薦清單'
|
||||
},
|
||||
recommendSonglist: {
|
||||
title: '本週最熱音樂'
|
||||
},
|
||||
searchBar: {
|
||||
login: '登入',
|
||||
toLogin: '去登入',
|
||||
logout: '登出',
|
||||
set: '設定',
|
||||
theme: '主題',
|
||||
restart: '重新啟動',
|
||||
refresh: '重新整理',
|
||||
currentVersion: '目前版本',
|
||||
searchPlaceholder: '搜尋點什麼吧...',
|
||||
zoom: '頁面縮放',
|
||||
zoom100: '標準縮放100%',
|
||||
resetZoom: '點擊重設縮放',
|
||||
zoomDefault: '標準縮放'
|
||||
},
|
||||
titleBar: {
|
||||
closeTitle: '請選擇關閉方式',
|
||||
minimizeToTray: '最小化到系統匣',
|
||||
exitApp: '退出應用程式',
|
||||
rememberChoice: '記住我的選擇',
|
||||
closeApp: '關閉應用程式'
|
||||
},
|
||||
userPlayList: {
|
||||
title: '{name}的常聽'
|
||||
},
|
||||
musicList: {
|
||||
searchSongs: '搜尋歌曲',
|
||||
noSearchResults: '沒有找到相關歌曲',
|
||||
switchToNormal: '切換到預設版面',
|
||||
switchToCompact: '切換到緊湊版面',
|
||||
playAll: '播放全部',
|
||||
collect: '收藏',
|
||||
collectSuccess: '收藏成功',
|
||||
cancelCollectSuccess: '取消收藏成功',
|
||||
operationFailed: '操作失敗',
|
||||
cancelCollect: '取消收藏',
|
||||
addToPlaylist: '新增至播放清單',
|
||||
addToPlaylistSuccess: '新增至播放清單成功',
|
||||
songsAlreadyInPlaylist: '歌曲已存在於播放清單中'
|
||||
},
|
||||
playlist: {
|
||||
import: {
|
||||
button: '播放清單匯入',
|
||||
title: '播放清單匯入',
|
||||
description: '支援透過元資料/文字/連結三種方式匯入播放清單',
|
||||
linkTab: '連結匯入',
|
||||
textTab: '文字匯入',
|
||||
localTab: '元資料匯入',
|
||||
linkPlaceholder: '請輸入播放清單連結,每行一個',
|
||||
textPlaceholder: '請輸入歌曲資訊,格式為:歌曲名 歌手名',
|
||||
localPlaceholder: '請輸入JSON格式的歌曲元資料',
|
||||
linkTips: '支援的連結來源:',
|
||||
linkTip1: '將播放清單分享到微信/微博/QQ後複製連結',
|
||||
linkTip2: '直接複製播放清單/個人主頁連結',
|
||||
linkTip3: '直接複製文章連結',
|
||||
textTips: '請輸入歌曲資訊,每行一首歌',
|
||||
textFormat: '格式:歌曲名 歌手名',
|
||||
localTips: '請新增歌曲元資料',
|
||||
localFormat: '格式範例:',
|
||||
songNamePlaceholder: '歌曲名稱',
|
||||
artistNamePlaceholder: '藝人名稱',
|
||||
albumNamePlaceholder: '專輯名稱',
|
||||
addSongButton: '新增歌曲',
|
||||
addLinkButton: '新增連結',
|
||||
importToStarPlaylist: '匯入到我喜歡的音樂',
|
||||
playlistNamePlaceholder: '請輸入播放清單名稱',
|
||||
importButton: '開始匯入',
|
||||
emptyLinkWarning: '請輸入播放清單連結',
|
||||
emptyTextWarning: '請輸入歌曲資訊',
|
||||
emptyLocalWarning: '請輸入歌曲元資料',
|
||||
invalidJsonFormat: 'JSON格式不正確',
|
||||
importSuccess: '匯入任務建立成功',
|
||||
importFailed: '匯入失敗',
|
||||
importStatus: '匯入狀態',
|
||||
refresh: '重新整理',
|
||||
taskId: '任務ID',
|
||||
status: '狀態',
|
||||
successCount: '成功數量',
|
||||
failReason: '失敗原因',
|
||||
unknownError: '未知錯誤',
|
||||
statusPending: '等待處理',
|
||||
statusProcessing: '處理中',
|
||||
statusSuccess: '匯入成功',
|
||||
statusFailed: '匯入失敗',
|
||||
statusUnknown: '未知狀態',
|
||||
taskList: '任務清單',
|
||||
taskListTitle: '匯入任務清單',
|
||||
action: '操作',
|
||||
select: '選擇',
|
||||
fetchTaskListFailed: '取得任務清單失敗',
|
||||
noTasks: '暫無匯入任務',
|
||||
clearTasks: '清除任務',
|
||||
clearTasksConfirmTitle: '確認清除',
|
||||
clearTasksConfirmContent: '確定要清除所有匯入任務記錄嗎?此操作不可恢復。',
|
||||
confirm: '確認',
|
||||
cancel: '取消',
|
||||
clearTasksSuccess: '任務清單已清除',
|
||||
clearTasksFailed: '清除任務清單失敗'
|
||||
}
|
||||
},
|
||||
settings: '設定',
|
||||
user: '使用者',
|
||||
toplist: '排行榜',
|
||||
history: '收藏歷史',
|
||||
list: '播放清單',
|
||||
mv: 'MV',
|
||||
home: '首頁',
|
||||
search: '搜尋'
|
||||
};
|
||||
8
src/i18n/lang/zh-Hant/donation.ts
Normal file
8
src/i18n/lang/zh-Hant/donation.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
description: '您的捐贈將用於支持開發和維護工作,包括但不限於伺服器維護、域名續費等。',
|
||||
message: '留言時可留下您的電子郵件或 github 名稱。',
|
||||
refresh: '重新整理列表',
|
||||
toDonateList: '請我喝杯咖啡',
|
||||
noMessage: '暫無留言',
|
||||
title: '捐贈列表'
|
||||
};
|
||||
87
src/i18n/lang/zh-Hant/download.ts
Normal file
87
src/i18n/lang/zh-Hant/download.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
export default {
|
||||
title: '下載管理',
|
||||
localMusic: '本機音樂',
|
||||
count: '共 {count} 首歌曲',
|
||||
clearAll: '清空記錄',
|
||||
settings: '設定',
|
||||
tabs: {
|
||||
downloading: '下載中',
|
||||
downloaded: '已下載'
|
||||
},
|
||||
empty: {
|
||||
noTasks: '暫無下載任務',
|
||||
noDownloaded: '暫無已下載歌曲'
|
||||
},
|
||||
progress: {
|
||||
total: '總進度: {progress}%'
|
||||
},
|
||||
status: {
|
||||
downloading: '下載中',
|
||||
completed: '已完成',
|
||||
failed: '失敗',
|
||||
unknown: '未知'
|
||||
},
|
||||
artist: {
|
||||
unknown: '未知歌手'
|
||||
},
|
||||
delete: {
|
||||
title: '刪除確認',
|
||||
message: '確定要刪除歌曲 "{filename}" 嗎?此操作不可恢復。',
|
||||
confirm: '確定刪除',
|
||||
cancel: '取消',
|
||||
success: '刪除成功',
|
||||
failed: '刪除失敗',
|
||||
fileNotFound: '檔案不存在或已被移動,已從記錄中移除',
|
||||
recordRemoved: '檔案刪除失敗,但已從記錄中移除'
|
||||
},
|
||||
clear: {
|
||||
title: '清空下載記錄',
|
||||
message: '確定要清空所有下載記錄嗎?此操作不會刪除已下載的音樂檔案,但將清空所有記錄。',
|
||||
confirm: '確定清空',
|
||||
cancel: '取消',
|
||||
success: '下載記錄已清空'
|
||||
},
|
||||
message: {
|
||||
downloadComplete: '{filename} 下載完成',
|
||||
downloadFailed: '{filename} 下載失敗: {error}'
|
||||
},
|
||||
loading: '載入中...',
|
||||
playStarted: '開始播放: {name}',
|
||||
playFailed: '播放失敗: {name}',
|
||||
path: {
|
||||
copied: '路徑已複製到剪貼簿',
|
||||
copyFailed: '複製路徑失敗'
|
||||
},
|
||||
settingsPanel: {
|
||||
title: '下載設定',
|
||||
path: '下載位置',
|
||||
pathDesc: '設定音樂檔案下載儲存的位置',
|
||||
pathPlaceholder: '請選擇下載路徑',
|
||||
noPathSelected: '請先選擇下載路徑',
|
||||
select: '選擇資料夾',
|
||||
open: '開啟資料夾',
|
||||
fileFormat: '檔名格式',
|
||||
fileFormatDesc: '設定下載音樂時的檔案命名格式',
|
||||
customFormat: '自訂格式',
|
||||
separator: '分隔符號',
|
||||
separators: {
|
||||
dash: '空格-空格',
|
||||
underscore: '底線',
|
||||
space: '空格'
|
||||
},
|
||||
dragToArrange: '拖曳排序或使用箭頭按鈕調整順序:',
|
||||
formatVariables: '可用變數',
|
||||
preview: '預覽效果:',
|
||||
saveSuccess: '下載設定已儲存',
|
||||
presets: {
|
||||
songArtist: '歌曲名 - 歌手名',
|
||||
artistSong: '歌手名 - 歌曲名',
|
||||
songOnly: '僅歌曲名'
|
||||
},
|
||||
components: {
|
||||
songName: '歌曲名',
|
||||
artistName: '歌手名',
|
||||
albumName: '專輯名'
|
||||
}
|
||||
}
|
||||
};
|
||||
13
src/i18n/lang/zh-Hant/favorite.ts
Normal file
13
src/i18n/lang/zh-Hant/favorite.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export default {
|
||||
title: '我的收藏',
|
||||
count: '共 {count} 首',
|
||||
batchDownload: '批次下載',
|
||||
download: '下載 ({count})',
|
||||
emptyTip: '還沒有收藏歌曲',
|
||||
downloadSuccess: '下載完成',
|
||||
downloadFailed: '下載失敗',
|
||||
downloading: '正在下載中,請稍候...',
|
||||
selectSongsFirst: '請先選擇要下載的歌曲',
|
||||
descending: '降',
|
||||
ascending: '升'
|
||||
};
|
||||
5
src/i18n/lang/zh-Hant/history.ts
Normal file
5
src/i18n/lang/zh-Hant/history.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
title: '播放歷史',
|
||||
playCount: '{count}',
|
||||
getHistoryFailed: '取得歷史記錄失敗'
|
||||
};
|
||||
48
src/i18n/lang/zh-Hant/login.ts
Normal file
48
src/i18n/lang/zh-Hant/login.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
export default {
|
||||
title: {
|
||||
qr: '掃碼登入',
|
||||
phone: '手機號登入',
|
||||
cookie: 'Cookie登入',
|
||||
uid: 'UID登入'
|
||||
},
|
||||
qrTip: '使用網易雲APP掃碼登入',
|
||||
phoneTip: '使用網易雲帳號登入',
|
||||
tokenTip: '輸入有效的網易雲音樂Cookie即可登入',
|
||||
uidTip: '輸入使用者ID快速登入',
|
||||
placeholder: {
|
||||
phone: '手機號',
|
||||
password: '密碼',
|
||||
cookie: '請輸入網易雲音樂Cookie(token)',
|
||||
uid: '請輸入使用者ID(UID)'
|
||||
},
|
||||
button: {
|
||||
login: '登入',
|
||||
switchToQr: '掃碼登入',
|
||||
switchToPhone: '手機號登入',
|
||||
switchToToken: '使用Cookie登入',
|
||||
switchToUid: 'UID登入',
|
||||
backToQr: '返回二維碼登入',
|
||||
cookieLogin: 'Cookie登入',
|
||||
autoGetCookie: '自動取得Cookie',
|
||||
refresh: '點擊刷新',
|
||||
refreshing: '刷新中...',
|
||||
refreshQr: '刷新二維碼'
|
||||
},
|
||||
message: {
|
||||
loginSuccess: '登入成功',
|
||||
tokenLoginSuccess: 'Cookie登入成功',
|
||||
uidLoginSuccess: 'UID登入成功',
|
||||
loadError: '載入登入資訊時出錯',
|
||||
qrCheckError: '檢查二維碼狀態時出錯',
|
||||
tokenRequired: '請輸入Cookie',
|
||||
tokenInvalid: 'Cookie無效,請檢查後重試',
|
||||
uidRequired: '請輸入使用者ID',
|
||||
uidInvalid: '使用者ID無效或使用者不存在',
|
||||
uidLoginFailed: 'UID登入失敗,請檢查使用者ID是否正確',
|
||||
autoGetCookieSuccess: '自動取得Cookie成功',
|
||||
autoGetCookieFailed: '自動取得Cookie失敗',
|
||||
autoGetCookieTip: '將開啟網易雲音樂登入頁面,請完成登入後關閉視窗'
|
||||
},
|
||||
qrTitle: '掃碼登入網易雲音樂',
|
||||
uidWarning: '注意:UID登入僅用於查看使用者公開資訊,無法訪問需要登入權限的功能'
|
||||
};
|
||||
123
src/i18n/lang/zh-Hant/player.ts
Normal file
123
src/i18n/lang/zh-Hant/player.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
export default {
|
||||
nowPlaying: '正在播放',
|
||||
playlist: '播放清單',
|
||||
lyrics: '歌詞',
|
||||
previous: '上一個',
|
||||
play: '播放',
|
||||
pause: '暫停',
|
||||
next: '下一個',
|
||||
volumeUp: '音量增加',
|
||||
volumeDown: '音量減少',
|
||||
mute: '靜音',
|
||||
unmute: '取消靜音',
|
||||
songNum: '歌曲總數:{num}',
|
||||
addCorrection: '提前 {num} 秒',
|
||||
subtractCorrection: '延遲 {num} 秒',
|
||||
playFailed: '目前歌曲播放失敗,播放下一首',
|
||||
playMode: {
|
||||
sequence: '順序播放',
|
||||
loop: '單曲循環',
|
||||
random: '隨機播放'
|
||||
},
|
||||
fullscreen: {
|
||||
enter: '全螢幕',
|
||||
exit: '退出全螢幕'
|
||||
},
|
||||
close: '關閉',
|
||||
modeHint: {
|
||||
single: '單曲循環',
|
||||
list: '自動播放下一個'
|
||||
},
|
||||
lrc: {
|
||||
noLrc: '暫無歌詞, 請欣賞'
|
||||
},
|
||||
reparse: {
|
||||
title: '選擇解析音源',
|
||||
desc: '點擊音源直接進行解析,下次播放此歌曲時將使用所選音源',
|
||||
success: '重新解析成功',
|
||||
failed: '重新解析失敗',
|
||||
warning: '請選擇一個音源',
|
||||
bilibiliNotSupported: 'B站影片不支援重新解析',
|
||||
processing: '解析中...',
|
||||
clear: '清除自訂音源'
|
||||
},
|
||||
playBar: {
|
||||
expand: '展開歌詞',
|
||||
collapse: '收合歌詞',
|
||||
like: '喜歡',
|
||||
lyric: '歌詞',
|
||||
noSongPlaying: '沒有正在播放的歌曲',
|
||||
eq: '等化器',
|
||||
playList: '播放清單',
|
||||
reparse: '重新解析',
|
||||
playMode: {
|
||||
sequence: '順序播放',
|
||||
loop: '循環播放',
|
||||
random: '隨機播放'
|
||||
},
|
||||
play: '開始播放',
|
||||
pause: '暫停播放',
|
||||
prev: '上一首',
|
||||
next: '下一首',
|
||||
volume: '音量',
|
||||
favorite: '已收藏{name}',
|
||||
unFavorite: '已取消收藏{name}',
|
||||
miniPlayBar: '迷你播放列',
|
||||
playbackSpeed: '播放速度',
|
||||
advancedControls: '更多設定s'
|
||||
},
|
||||
eq: {
|
||||
title: '等化器',
|
||||
reset: '重設',
|
||||
on: '開啟',
|
||||
off: '關閉',
|
||||
bass: '低音',
|
||||
midrange: '中音',
|
||||
treble: '高音',
|
||||
presets: {
|
||||
flat: '平坦',
|
||||
pop: '流行',
|
||||
rock: '搖滾',
|
||||
classical: '古典',
|
||||
jazz: '爵士',
|
||||
electronic: '電子',
|
||||
hiphop: '嘻哈',
|
||||
rb: 'R&B',
|
||||
metal: '金屬',
|
||||
vocal: '人聲',
|
||||
dance: '舞曲',
|
||||
acoustic: '原聲',
|
||||
custom: '自訂'
|
||||
}
|
||||
},
|
||||
// 定時關閉功能相關
|
||||
sleepTimer: {
|
||||
title: '定時關閉',
|
||||
cancel: '取消定時',
|
||||
timeMode: '按時間關閉',
|
||||
songsMode: '按歌曲數關閉',
|
||||
playlistEnd: '播放完清單後關閉',
|
||||
afterPlaylist: '播放完清單後關閉',
|
||||
activeUntilEnd: '播放至清單結束',
|
||||
minutes: '分鐘',
|
||||
hours: '小時',
|
||||
songs: '首歌',
|
||||
set: '設定',
|
||||
timerSetSuccess: '已設定{minutes}分鐘後關閉',
|
||||
songsSetSuccess: '已設定播放{songs}首歌後關閉',
|
||||
playlistEndSetSuccess: '已設定播放完清單後關閉',
|
||||
timerCancelled: '已取消定時關閉',
|
||||
timerEnded: '定時關閉已觸發',
|
||||
playbackStopped: '音樂播放已停止',
|
||||
minutesRemaining: '剩餘{minutes}分鐘',
|
||||
songsRemaining: '剩餘{count}首歌'
|
||||
},
|
||||
playList: {
|
||||
clearAll: '清空播放清單',
|
||||
alreadyEmpty: '播放清單已經為空',
|
||||
cleared: '已清空播放清單',
|
||||
empty: '播放清單為空',
|
||||
clearConfirmTitle: '清空播放清單',
|
||||
clearConfirmContent: '這將清空所有播放清單中的歌曲並停止目前播放。是否繼續?'
|
||||
}
|
||||
};
|
||||
27
src/i18n/lang/zh-Hant/search.ts
Normal file
27
src/i18n/lang/zh-Hant/search.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export default {
|
||||
title: {
|
||||
hotSearch: '熱搜列表',
|
||||
searchList: '搜尋列表',
|
||||
searchHistory: '搜尋歷史'
|
||||
},
|
||||
button: {
|
||||
clear: '清空',
|
||||
back: '返回',
|
||||
playAll: '播放列表'
|
||||
},
|
||||
loading: {
|
||||
more: '載入中...',
|
||||
failed: '搜尋失敗'
|
||||
},
|
||||
noMore: '沒有更多了',
|
||||
error: {
|
||||
searchFailed: '搜尋失敗'
|
||||
},
|
||||
search: {
|
||||
single: '單曲',
|
||||
album: '專輯',
|
||||
playlist: '歌單',
|
||||
mv: 'MV',
|
||||
bilibili: 'B站'
|
||||
}
|
||||
};
|
||||
262
src/i18n/lang/zh-Hant/settings.ts
Normal file
262
src/i18n/lang/zh-Hant/settings.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
export default {
|
||||
theme: '主題',
|
||||
language: '語言',
|
||||
regard: '關於',
|
||||
logout: '登出',
|
||||
sections: {
|
||||
basic: '基礎設定',
|
||||
playback: '播放設定',
|
||||
application: '應用程式設定',
|
||||
network: '網路設定',
|
||||
system: '系統管理',
|
||||
donation: '捐贈支持',
|
||||
regard: '關於'
|
||||
},
|
||||
basic: {
|
||||
themeMode: '主題模式',
|
||||
themeModeDesc: '切換日間/夜間主題',
|
||||
autoTheme: '跟隨系統',
|
||||
manualTheme: '手動切換',
|
||||
language: '語言設定',
|
||||
languageDesc: '切換顯示語言',
|
||||
tokenManagement: 'Cookie管理',
|
||||
tokenManagementDesc: '管理網易雲音樂登入Cookie',
|
||||
tokenStatus: '目前Cookie狀態',
|
||||
tokenSet: '已設定',
|
||||
tokenNotSet: '未設定',
|
||||
setCookie: '設定Cookie',
|
||||
modifyToken: '修改Cookie',
|
||||
clearToken: '清除Cookie',
|
||||
font: '字體設定',
|
||||
fontDesc: '選擇字體,優先使用排在前面的字體',
|
||||
fontScope: {
|
||||
global: '全域',
|
||||
lyric: '僅歌詞'
|
||||
},
|
||||
animation: '動畫速度',
|
||||
animationDesc: '是否開起動畫',
|
||||
animationSpeed: {
|
||||
slow: '極慢',
|
||||
normal: '正常',
|
||||
fast: '極快'
|
||||
},
|
||||
fontPreview: {
|
||||
title: '字體預覽',
|
||||
chinese: '中文',
|
||||
english: 'English',
|
||||
japanese: '日本語',
|
||||
korean: '한국어',
|
||||
chineseText: '靜夜思 床前明月光 疑是地上霜',
|
||||
englishText: 'The quick brown fox jumps over the lazy dog',
|
||||
japaneseText: 'あいうえお かきくけこ さしすせそ',
|
||||
koreanText: '가나다라마 바사아자차 카타파하'
|
||||
}
|
||||
},
|
||||
playback: {
|
||||
quality: '音質設定',
|
||||
qualityDesc: '選擇音樂播放音質(網易云VIP)',
|
||||
qualityOptions: {
|
||||
standard: '標準',
|
||||
higher: '較高',
|
||||
exhigh: '極高',
|
||||
lossless: '無損',
|
||||
hires: 'Hi-Res',
|
||||
jyeffect: '高清環繞聲',
|
||||
sky: '沉浸環繞聲',
|
||||
dolby: '杜比全景聲',
|
||||
jymaster: '超清母帶'
|
||||
},
|
||||
musicSources: '音源設定',
|
||||
musicSourcesDesc: '選擇音樂解析使用的音源平台',
|
||||
musicSourcesWarning: '至少需要選擇一個音源平台',
|
||||
musicUnblockEnable: '啟用音樂解析',
|
||||
musicUnblockEnableDesc: '開啟後將嘗試解析無法播放的音樂',
|
||||
configureMusicSources: '設定音源',
|
||||
selectedMusicSources: '已選音源:',
|
||||
noMusicSources: '未選擇音源',
|
||||
gdmusicInfo: 'GD音樂台可自動解析多個平台音源,自動選擇最佳結果',
|
||||
autoPlay: '自動播放',
|
||||
autoPlayDesc: '重新開啟應用程式時是否自動繼續播放',
|
||||
showStatusBar: '是否顯示狀態列控制功能',
|
||||
showStatusBarContent: '可以在您的mac狀態列顯示音樂控制功能(重啟後生效)'
|
||||
},
|
||||
application: {
|
||||
closeAction: '關閉行為',
|
||||
closeActionDesc: '選擇關閉視窗時的行為',
|
||||
closeOptions: {
|
||||
ask: '每次詢問',
|
||||
minimize: '最小化到系統匣',
|
||||
close: '直接退出'
|
||||
},
|
||||
shortcut: '快捷鍵設定',
|
||||
shortcutDesc: '自訂全域快捷鍵',
|
||||
download: '下載管理',
|
||||
downloadDesc: '是否始終顯示下載清單按鈕',
|
||||
unlimitedDownload: '無限制下載',
|
||||
unlimitedDownloadDesc: '開啟後將無限制下載音樂(可能出現下載失敗的情況), 預設限制 300 首',
|
||||
downloadPath: '下載目錄',
|
||||
downloadPathDesc: '選擇音樂檔案的下載位置',
|
||||
remoteControl: '遠端控制',
|
||||
remoteControlDesc: '設定遠端控制功能'
|
||||
},
|
||||
network: {
|
||||
apiPort: '音樂API連接埠',
|
||||
apiPortDesc: '修改後需要重啟應用程式',
|
||||
proxy: '代理設定',
|
||||
proxyDesc: '無法存取音樂時可以開啟代理',
|
||||
proxyHost: '代理位址',
|
||||
proxyHostPlaceholder: '請輸入代理位址',
|
||||
proxyPort: '代理連接埠',
|
||||
proxyPortPlaceholder: '請輸入代理連接埠',
|
||||
realIP: 'realIP設定',
|
||||
realIPDesc: '由於限制,此項目在國外使用會受到限制可使用realIP參數,傳進國內IP解決',
|
||||
messages: {
|
||||
proxySuccess: '代理設定已儲存,重啟應用程式後生效',
|
||||
proxyError: '請檢查輸入是否正確',
|
||||
realIPSuccess: '真實IP設定已儲存',
|
||||
realIPError: '請輸入有效的IP位址'
|
||||
}
|
||||
},
|
||||
system: {
|
||||
cache: '快取管理',
|
||||
cacheDesc: '清除快取',
|
||||
cacheClearTitle: '請選擇要清除的快取類型:',
|
||||
cacheTypes: {
|
||||
history: {
|
||||
label: '播放歷史',
|
||||
description: '清除播放過的歌曲記錄'
|
||||
},
|
||||
favorite: {
|
||||
label: '收藏記錄',
|
||||
description: '清除本機收藏的歌曲記錄(不會影響雲端收藏)'
|
||||
},
|
||||
user: {
|
||||
label: '使用者資料',
|
||||
description: '清除登入資訊和使用者相關資料'
|
||||
},
|
||||
settings: {
|
||||
label: '應用程式設定',
|
||||
description: '清除應用程式的所有自訂設定'
|
||||
},
|
||||
downloads: {
|
||||
label: '下載記錄',
|
||||
description: '清除下載歷史記錄(不會刪除已下載的檔案)'
|
||||
},
|
||||
resources: {
|
||||
label: '音樂資源',
|
||||
description: '清除已載入的音樂檔案、歌詞等資源快取'
|
||||
},
|
||||
lyrics: {
|
||||
label: '歌詞資源',
|
||||
description: '清除已載入的歌詞資源快取'
|
||||
}
|
||||
},
|
||||
restart: '重新啟動',
|
||||
restartDesc: '重新啟動應用程式',
|
||||
messages: {
|
||||
clearSuccess: '清除成功,部分設定在重啟後生效'
|
||||
}
|
||||
},
|
||||
about: {
|
||||
version: '版本',
|
||||
checkUpdate: '檢查更新',
|
||||
checking: '檢查中...',
|
||||
latest: '目前已是最新版本',
|
||||
hasUpdate: '發現新版本',
|
||||
gotoUpdate: '前往更新',
|
||||
gotoGithub: '前往 Github',
|
||||
author: '作者',
|
||||
authorDesc: 'algerkong 點個star🌟呗',
|
||||
messages: {
|
||||
checkError: '檢查更新失敗,請稍後重試'
|
||||
}
|
||||
},
|
||||
validation: {
|
||||
selectProxyProtocol: '請選擇代理協議',
|
||||
proxyHost: '請輸入代理位址',
|
||||
portNumber: '請輸入有效的連接埠號(1-65535)'
|
||||
},
|
||||
lyricSettings: {
|
||||
title: '歌詞設定',
|
||||
tabs: {
|
||||
display: '顯示',
|
||||
interface: '介面',
|
||||
typography: '文字',
|
||||
mobile: '行動端'
|
||||
},
|
||||
pureMode: '純淨模式',
|
||||
hideCover: '隱藏封面',
|
||||
centerDisplay: '置中顯示',
|
||||
showTranslation: '顯示翻譯',
|
||||
hideLyrics: '隱藏歌詞',
|
||||
hidePlayBar: '隱藏播放列',
|
||||
hideMiniPlayBar: '隱藏迷你播放列',
|
||||
showMiniPlayBar: '顯示迷你播放列',
|
||||
backgroundTheme: '背景主題',
|
||||
themeOptions: {
|
||||
default: '預設',
|
||||
light: '亮色',
|
||||
dark: '暗色'
|
||||
},
|
||||
fontSize: '字體大小',
|
||||
fontSizeMarks: {
|
||||
small: '小',
|
||||
medium: '中',
|
||||
large: '大'
|
||||
},
|
||||
letterSpacing: '字間距',
|
||||
letterSpacingMarks: {
|
||||
compact: '緊湊',
|
||||
default: '預設',
|
||||
loose: '寬鬆'
|
||||
}
|
||||
},
|
||||
themeColor: {
|
||||
title: '歌詞主題色',
|
||||
presetColors: '預設顏色',
|
||||
customColor: '自訂顏色',
|
||||
preview: '預覽效果',
|
||||
previewText: '歌詞效果',
|
||||
colorNames: {
|
||||
'spotify-green': 'Spotify 綠',
|
||||
'apple-blue': '蘋果藍',
|
||||
'youtube-red': 'YouTube 紅',
|
||||
orange: '活力橙',
|
||||
purple: '神秘紫',
|
||||
pink: '櫻花粉'
|
||||
},
|
||||
tooltips: {
|
||||
openColorPicker: '開啟色板',
|
||||
closeColorPicker: '關閉色板'
|
||||
},
|
||||
placeholder: '#1db954'
|
||||
},
|
||||
cookie: {
|
||||
title: 'Cookie設定',
|
||||
description: '請輸入網易雲音樂的Cookie:',
|
||||
placeholder: '請貼上完整的Cookie...',
|
||||
help: {
|
||||
format: 'Cookie通常以 "MUSIC_U=" 開頭',
|
||||
source: '可以從瀏覽器開發者工具的網路請求中取得',
|
||||
storage: 'Cookie設定後將自動儲存到本機儲存'
|
||||
},
|
||||
action: {
|
||||
save: '儲存Cookie',
|
||||
paste: '貼上',
|
||||
clear: '清空'
|
||||
},
|
||||
validation: {
|
||||
required: '請輸入Cookie',
|
||||
format: 'Cookie格式可能不正確,請檢查是否包含MUSIC_U'
|
||||
},
|
||||
message: {
|
||||
saveSuccess: 'Cookie儲存成功',
|
||||
saveError: 'Cookie儲存失敗',
|
||||
pasteSuccess: '貼上成功',
|
||||
pasteError: '貼上失敗,請手動複製'
|
||||
},
|
||||
info: {
|
||||
length: '目前長度:{length} 字元'
|
||||
}
|
||||
}
|
||||
};
|
||||
28
src/i18n/lang/zh-Hant/songItem.ts
Normal file
28
src/i18n/lang/zh-Hant/songItem.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export default {
|
||||
menu: {
|
||||
play: '播放',
|
||||
playNext: '下一首播放',
|
||||
download: '下載歌曲',
|
||||
addToPlaylist: '新增至播放清單',
|
||||
favorite: '喜歡',
|
||||
unfavorite: '取消喜歡',
|
||||
removeFromPlaylist: '從播放清單中刪除',
|
||||
dislike: '不喜歡',
|
||||
undislike: '取消不喜歡'
|
||||
},
|
||||
message: {
|
||||
downloading: '正在下載中,請稍候...',
|
||||
downloadFailed: '下載失敗',
|
||||
downloadQueued: '已加入下載佇列',
|
||||
addedToNextPlay: '已新增至下一首播放',
|
||||
getUrlFailed: '取得音樂下載位址失敗,請檢查是否登入'
|
||||
},
|
||||
dialog: {
|
||||
dislike: {
|
||||
title: '提示!',
|
||||
content: '確認不喜歡這首歌嗎?再次進入將從每日推薦中排除。',
|
||||
positiveText: '不喜歡',
|
||||
negativeText: '取消'
|
||||
}
|
||||
}
|
||||
};
|
||||
48
src/i18n/lang/zh-Hant/user.ts
Normal file
48
src/i18n/lang/zh-Hant/user.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
export default {
|
||||
profile: {
|
||||
followers: '粉絲',
|
||||
following: '關注',
|
||||
level: '等級'
|
||||
},
|
||||
playlist: {
|
||||
created: '建立的歌單',
|
||||
mine: '我建立的',
|
||||
trackCount: '{count}首',
|
||||
playCount: '播放{count}次'
|
||||
},
|
||||
ranking: {
|
||||
title: '聽歌排行',
|
||||
playCount: '{count}次'
|
||||
},
|
||||
follow: {
|
||||
title: '關注列表',
|
||||
viewPlaylist: '查看歌單',
|
||||
noFollowings: '暫無關注',
|
||||
loadMore: '載入更多',
|
||||
noSignature: '這個傢伙很懶,什麼都沒留下',
|
||||
userFollowsTitle: '的關注',
|
||||
myFollowsTitle: '我的關注'
|
||||
},
|
||||
follower: {
|
||||
title: '粉絲列表',
|
||||
noFollowers: '暫無粉絲',
|
||||
loadMore: '載入更多',
|
||||
userFollowersTitle: '的粉絲',
|
||||
myFollowersTitle: '我的粉絲'
|
||||
},
|
||||
detail: {
|
||||
playlists: '歌單',
|
||||
records: '聽歌排行',
|
||||
noPlaylists: '暫無歌單',
|
||||
noRecords: '暫無聽歌記錄',
|
||||
artist: '歌手',
|
||||
noSignature: '這個人很懶,什麼都沒留下',
|
||||
invalidUserId: '使用者ID無效',
|
||||
noRecordPermission: '{name}不讓你聽歌排行'
|
||||
},
|
||||
message: {
|
||||
loadFailed: '載入使用者頁面失敗',
|
||||
deleteSuccess: '刪除成功',
|
||||
deleteFailed: '刪除失敗'
|
||||
}
|
||||
};
|
||||
25
src/i18n/languages.ts
Normal file
25
src/i18n/languages.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// 语言配置文件 - 集中管理语言相关的配置
|
||||
|
||||
// 语言显示名称映射
|
||||
export const LANGUAGE_DISPLAY_NAMES: Record<string, string> = {
|
||||
'zh-CN': '简体中文',
|
||||
'zh-Hant': '繁體中文',
|
||||
'en-US': 'English',
|
||||
'ja-JP': '日本語',
|
||||
'ko-KR': '한국어'
|
||||
};
|
||||
|
||||
// 默认语言
|
||||
export const DEFAULT_LANGUAGE = 'zh-CN';
|
||||
|
||||
// 回退语言
|
||||
export const FALLBACK_LANGUAGE = 'en-US';
|
||||
|
||||
// 语言排序优先级(用于在UI中的显示顺序)
|
||||
export const LANGUAGE_PRIORITY: Record<string, number> = {
|
||||
'zh-CN': 1,
|
||||
'zh-Hant': 2,
|
||||
'en-US': 3,
|
||||
'ja-JP': 4,
|
||||
'ko-KR': 5
|
||||
};
|
||||
@@ -1,17 +1,15 @@
|
||||
import enUS from './lang/en-US';
|
||||
import zhCN from './lang/zh-CN';
|
||||
import { DEFAULT_LANGUAGE } from './languages';
|
||||
import { buildLanguageMessages } from './utils';
|
||||
|
||||
const messages = {
|
||||
'zh-CN': zhCN,
|
||||
'en-US': enUS
|
||||
} as const;
|
||||
// 使用工具函数构建语言消息对象
|
||||
const messages = buildLanguageMessages();
|
||||
|
||||
type Language = keyof typeof messages;
|
||||
|
||||
// 为主进程提供一个简单的 i18n 实现
|
||||
const mainI18n = {
|
||||
global: {
|
||||
currentLocale: 'zh-CN' as Language,
|
||||
currentLocale: DEFAULT_LANGUAGE as Language,
|
||||
get locale() {
|
||||
return this.currentLocale;
|
||||
},
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { createI18n } from 'vue-i18n';
|
||||
|
||||
import enUS from './lang/en-US';
|
||||
import zhCN from './lang/zh-CN';
|
||||
import { DEFAULT_LANGUAGE, FALLBACK_LANGUAGE } from './languages';
|
||||
import { buildLanguageMessages } from './utils';
|
||||
|
||||
const messages = {
|
||||
'zh-CN': zhCN,
|
||||
'en-US': enUS
|
||||
};
|
||||
// 使用工具函数构建语言消息对象
|
||||
const messages = buildLanguageMessages();
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'zh-CN',
|
||||
fallbackLocale: 'en-US',
|
||||
locale: DEFAULT_LANGUAGE,
|
||||
fallbackLocale: FALLBACK_LANGUAGE,
|
||||
messages,
|
||||
globalInjection: true,
|
||||
silentTranslationWarn: true,
|
||||
|
||||
60
src/i18n/utils.ts
Normal file
60
src/i18n/utils.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
// 自动导入所有语言的所有翻译文件
|
||||
const allLangModules = import.meta.glob('./lang/**/*.ts', { eager: true });
|
||||
|
||||
// 构建语言消息对象
|
||||
export const buildLanguageMessages = () => {
|
||||
const messages: Record<string, Record<string, any>> = {};
|
||||
|
||||
Object.entries(allLangModules).forEach(([path, module]) => {
|
||||
// 解析路径,例如 './lang/zh-CN/common.ts' -> { lang: 'zh-CN', module: 'common' }
|
||||
const match = path.match(/\.\/lang\/([^/]+)\/([^/]+)\.ts$/);
|
||||
if (match) {
|
||||
const [, langCode, moduleName] = match;
|
||||
|
||||
// 跳过 index 文件
|
||||
if (moduleName !== 'index') {
|
||||
if (!messages[langCode]) {
|
||||
messages[langCode] = {};
|
||||
}
|
||||
messages[langCode][moduleName] = (module as any).default;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return messages;
|
||||
};
|
||||
|
||||
// 获取所有支持的语言
|
||||
export const getSupportedLanguages = (): string[] => {
|
||||
const messages = buildLanguageMessages();
|
||||
return Object.keys(messages);
|
||||
};
|
||||
|
||||
export const isLanguageSupported = (lang: string): boolean => {
|
||||
return getSupportedLanguages().includes(lang);
|
||||
};
|
||||
|
||||
import { LANGUAGE_DISPLAY_NAMES, LANGUAGE_PRIORITY } from './languages';
|
||||
|
||||
// 获取语言显示名称的映射
|
||||
export const getLanguageDisplayNames = (): Record<string, string> => {
|
||||
return LANGUAGE_DISPLAY_NAMES;
|
||||
};
|
||||
|
||||
// 生成语言选项数组,用于下拉选择等组件
|
||||
export const getLanguageOptions = () => {
|
||||
const supportedLanguages = getSupportedLanguages();
|
||||
const displayNames = getLanguageDisplayNames();
|
||||
|
||||
// 按优先级排序
|
||||
const sortedLanguages = supportedLanguages.sort((a, b) => {
|
||||
const priorityA = LANGUAGE_PRIORITY[a] || 999;
|
||||
const priorityB = LANGUAGE_PRIORITY[b] || 999;
|
||||
return priorityA - priorityB;
|
||||
});
|
||||
|
||||
return sortedLanguages.map((lang) => ({
|
||||
label: displayNames[lang] || lang,
|
||||
value: lang
|
||||
}));
|
||||
};
|
||||
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"settings": {
|
||||
"lyricSettings": {
|
||||
"title": "歌词设置",
|
||||
"tabs": {
|
||||
"display": "显示",
|
||||
"interface": "界面",
|
||||
"typography": "文字",
|
||||
"mobile": "移动端"
|
||||
},
|
||||
"pureMode": "纯净模式",
|
||||
"hideCover": "隐藏封面",
|
||||
"centerDisplay": "居中显示",
|
||||
"showTranslation": "显示翻译",
|
||||
"hideLyrics": "隐藏歌词",
|
||||
"hidePlayBar": "隐藏播放栏",
|
||||
"hideMiniPlayBar": "隐藏迷你播放栏",
|
||||
"backgroundTheme": "背景主题",
|
||||
"themeOptions": {
|
||||
"default": "默认",
|
||||
"light": "亮色",
|
||||
"dark": "暗色"
|
||||
},
|
||||
"fontSize": "字体大小",
|
||||
"fontSizeMarks": {
|
||||
"small": "小",
|
||||
"medium": "中",
|
||||
"large": "大"
|
||||
},
|
||||
"letterSpacing": "字间距",
|
||||
"letterSpacingMarks": {
|
||||
"compact": "紧凑",
|
||||
"default": "默认",
|
||||
"loose": "宽松"
|
||||
},
|
||||
"lineHeight": "行高",
|
||||
"lineHeightMarks": {
|
||||
"compact": "紧凑",
|
||||
"default": "默认",
|
||||
"loose": "宽松"
|
||||
},
|
||||
"mobileLayout": "移动端布局",
|
||||
"layoutOptions": {
|
||||
"default": "默认",
|
||||
"ios": "iOS风格",
|
||||
"android": "安卓风格"
|
||||
},
|
||||
"mobileCoverStyle": "封面样式",
|
||||
"coverOptions": {
|
||||
"record": "唱片",
|
||||
"square": "方形",
|
||||
"full": "全屏"
|
||||
},
|
||||
"lyricLines": "歌词行数",
|
||||
"mobileUnavailable": "此设置仅在移动端可用"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,20 +8,19 @@ import { loadLyricWindow } from './lyric';
|
||||
import { initializeConfig } from './modules/config';
|
||||
import { initializeFileManager } from './modules/fileManager';
|
||||
import { initializeFonts } from './modules/fonts';
|
||||
import { initializeLoginWindow } from './modules/loginWindow';
|
||||
import { initializeRemoteControl } from './modules/remoteControl';
|
||||
import { initializeShortcuts, registerShortcuts } from './modules/shortcuts';
|
||||
import { initializeTray, updateCurrentSong, updatePlayState, updateTrayMenu } from './modules/tray';
|
||||
import { setupUpdateHandlers } from './modules/update';
|
||||
import { createMainWindow, initializeWindowManager } from './modules/window';
|
||||
import { startMusicApi } from './server';
|
||||
import { createMainWindow, initializeWindowManager, setAppQuitting } from './modules/window';
|
||||
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;
|
||||
@@ -43,6 +42,8 @@ function initialize() {
|
||||
initializeWindowManager();
|
||||
// 初始化字体管理
|
||||
initializeFonts();
|
||||
// 初始化登录窗口
|
||||
initializeLoginWindow();
|
||||
|
||||
// 创建主窗口
|
||||
mainWindow = createMainWindow(icon);
|
||||
@@ -137,6 +138,12 @@ if (!isSingleInstance) {
|
||||
}
|
||||
});
|
||||
|
||||
// 应用即将退出时的处理
|
||||
app.on('before-quit', () => {
|
||||
// 设置退出标志
|
||||
setAppQuitting(true);
|
||||
});
|
||||
|
||||
// 重启应用
|
||||
ipcMain.on('restart', () => {
|
||||
app.relaunch();
|
||||
|
||||
@@ -122,7 +122,7 @@ const createWin = () => {
|
||||
}
|
||||
});
|
||||
|
||||
lyricWindow.on('blur', () => lyricWindow && lyricWindow.setMaximizable(false))
|
||||
lyricWindow.on('blur', () => lyricWindow && lyricWindow.setMaximizable(false));
|
||||
|
||||
return lyricWindow;
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
104
src/main/modules/loginWindow.ts
Normal file
104
src/main/modules/loginWindow.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { BrowserWindow, ipcMain, session } from 'electron';
|
||||
import { join } from 'path';
|
||||
|
||||
import i18n from '../../i18n/main';
|
||||
|
||||
let loginWindow: BrowserWindow | null = null;
|
||||
|
||||
const loginUrl = 'https://music.163.com/#/login/';
|
||||
const loginTitle = i18n.global.t('login.qrTitle');
|
||||
|
||||
/**
|
||||
* 打开登录窗口获取Cookie
|
||||
*/
|
||||
const openLoginWindow = async (mainWin: BrowserWindow) => {
|
||||
let loginTimer: NodeJS.Timeout;
|
||||
|
||||
// 如果登录窗口已存在,则聚焦并返回
|
||||
if (loginWindow && !loginWindow.isDestroyed()) {
|
||||
loginWindow.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const loginSession = session.fromPartition('persist:login');
|
||||
|
||||
// 清除 Cookie
|
||||
await loginSession.clearStorageData({
|
||||
storages: ['cookies', 'localstorage']
|
||||
});
|
||||
|
||||
loginWindow = new BrowserWindow({
|
||||
parent: mainWin,
|
||||
title: loginTitle,
|
||||
width: 1280,
|
||||
height: 800,
|
||||
center: true,
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
session: loginSession,
|
||||
sandbox: false,
|
||||
webSecurity: false,
|
||||
preload: join(__dirname, '../../preload/index.js')
|
||||
}
|
||||
});
|
||||
|
||||
// 打开网易云登录页面
|
||||
loginWindow.loadURL(loginUrl);
|
||||
|
||||
// 阻止新窗口创建
|
||||
loginWindow.webContents.setWindowOpenHandler(() => {
|
||||
return { action: 'deny' };
|
||||
});
|
||||
|
||||
// 检查是否登录
|
||||
const checkLogin = async () => {
|
||||
try {
|
||||
if (!loginWindow || loginWindow.isDestroyed()) {
|
||||
if (loginTimer) clearInterval(loginTimer);
|
||||
return;
|
||||
}
|
||||
|
||||
const MUSIC_U = await loginSession.cookies.get({
|
||||
name: 'MUSIC_U'
|
||||
});
|
||||
|
||||
if (MUSIC_U && MUSIC_U?.length > 0) {
|
||||
if (loginTimer) clearInterval(loginTimer);
|
||||
const value = `MUSIC_U=${MUSIC_U[0].value};`;
|
||||
|
||||
mainWin?.webContents.send('send-cookies', value);
|
||||
|
||||
// 关闭登录窗口
|
||||
loginWindow.destroy();
|
||||
loginWindow = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查登录状态失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 循环检查登录状态
|
||||
loginWindow.webContents.once('did-finish-load', () => {
|
||||
loginWindow?.show();
|
||||
loginTimer = setInterval(checkLogin, 500);
|
||||
|
||||
loginWindow?.on('closed', () => {
|
||||
if (loginTimer) clearInterval(loginTimer);
|
||||
loginWindow = null;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化登录窗口相关的IPC监听
|
||||
*/
|
||||
export function initializeLoginWindow() {
|
||||
ipcMain.on('open-login', (event) => {
|
||||
const mainWin = BrowserWindow.fromWebContents(event.sender);
|
||||
if (mainWin) {
|
||||
openLoginWindow(mainWin);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default openLoginWindow;
|
||||
@@ -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('加载远程控制界面失败');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ import {
|
||||
} from 'electron';
|
||||
import { join } from 'path';
|
||||
|
||||
import type { Language } from '../../i18n/main';
|
||||
import i18n from '../../i18n/main';
|
||||
import { getLanguageOptions } from '../../i18n/utils';
|
||||
import { getStore } from './config';
|
||||
|
||||
// 歌曲信息接口定义
|
||||
@@ -33,10 +33,8 @@ let songTitleTray: Tray | null = null;
|
||||
let isPlaying = false;
|
||||
let currentSong: SongInfo | null = null;
|
||||
|
||||
const LANGUAGES: { label: string; value: Language }[] = [
|
||||
{ label: '简体中文', value: 'zh-CN' },
|
||||
{ label: 'English', value: 'en-US' }
|
||||
];
|
||||
// 使用自动导入的语言选项
|
||||
const LANGUAGES = getLanguageOptions();
|
||||
|
||||
// 更新播放状态
|
||||
export function updatePlayState(playing: boolean) {
|
||||
@@ -328,7 +326,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();
|
||||
|
||||
@@ -40,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后调用
|
||||
@@ -55,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('窗口大小管理器初始化完成');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置主窗口引用
|
||||
*/
|
||||
@@ -73,19 +73,19 @@ class WindowSizeManager {
|
||||
if (!this.isInitialized) {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
|
||||
this.mainWindow = win;
|
||||
|
||||
|
||||
// 读取保存的状态
|
||||
this.savedState = this.getWindowState();
|
||||
|
||||
|
||||
// 监听重要事件
|
||||
this.setupEventListeners(win);
|
||||
|
||||
|
||||
// 立即保存初始状态
|
||||
this.saveWindowState(win);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 初始化最小窗口尺寸
|
||||
*/
|
||||
@@ -94,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);
|
||||
@@ -110,7 +113,7 @@ class WindowSizeManager {
|
||||
MIN_HEIGHT = Math.round(DEFAULT_MAIN_HEIGHT * 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置事件监听器
|
||||
*/
|
||||
@@ -121,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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 强制应用正确的窗口大小
|
||||
*/
|
||||
@@ -168,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()) {
|
||||
@@ -199,12 +208,12 @@ class WindowSizeManager {
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
|
||||
|
||||
// // 开始尺寸强制执行
|
||||
// this.startSizeEnforcement(win);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 开启尺寸强制执行定时器
|
||||
*/
|
||||
@@ -214,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) {
|
||||
@@ -231,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);
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* 获取窗口创建选项
|
||||
*/
|
||||
@@ -268,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,
|
||||
@@ -285,7 +294,7 @@ class WindowSizeManager {
|
||||
contextIsolation: true
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 如果有保存的位置,且位置有效,则使用该位置
|
||||
if (savedState?.x !== undefined && savedState?.y !== undefined && app.isReady()) {
|
||||
if (this.isPositionVisible(savedState.x, savedState.y)) {
|
||||
@@ -293,63 +302,76 @@ 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模式窗口(根据窗口大小判断)
|
||||
const [currentWidth, currentHeight] = win.getSize();
|
||||
const isMiniMode = currentWidth === DEFAULT_MINI_WIDTH && currentHeight === DEFAULT_MINI_HEIGHT;
|
||||
|
||||
const isMaximized = win.isMaximized();
|
||||
let state: WindowState;
|
||||
|
||||
|
||||
if (isMaximized) {
|
||||
// 如果窗口处于最大化状态,保存最大化标志
|
||||
// 由于 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,
|
||||
height: previousSize.height,
|
||||
@@ -357,23 +379,22 @@ 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();
|
||||
|
||||
|
||||
state = {
|
||||
width,
|
||||
height,
|
||||
@@ -381,31 +402,37 @@ class WindowSizeManager {
|
||||
y,
|
||||
isMaximized: false
|
||||
};
|
||||
console.log('state IsNormal',state)
|
||||
console.log('state IsNormal', state);
|
||||
}
|
||||
|
||||
|
||||
// 如果是mini模式,不保存到持久化存储,只返回状态用于内存中的恢复
|
||||
if (isMiniMode) {
|
||||
console.log('检测到mini模式窗口,不保存到持久化存储');
|
||||
return state;
|
||||
}
|
||||
|
||||
// 保存状态到存储
|
||||
this.store.set(WINDOW_STATE_KEY, state);
|
||||
console.log(`已保存窗口状态: ${JSON.stringify(state)}`);
|
||||
|
||||
|
||||
// 更新内部状态
|
||||
this.savedState = state;
|
||||
console.log('state',state)
|
||||
|
||||
console.log('state', state);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取保存的窗口状态
|
||||
*/
|
||||
getWindowState(): WindowState | null {
|
||||
const state = this.store.get(WINDOW_STATE_KEY) as WindowState | undefined;
|
||||
|
||||
|
||||
if (!state) {
|
||||
console.log('未找到保存的窗口状态,将使用默认值');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// 验证尺寸,确保不小于最小值
|
||||
const validatedState: WindowState = {
|
||||
width: Math.max(MIN_WIDTH, state.width || DEFAULT_MAIN_WIDTH),
|
||||
@@ -414,12 +441,12 @@ class WindowSizeManager {
|
||||
y: state.y,
|
||||
isMaximized: !!state.isMaximized
|
||||
};
|
||||
|
||||
|
||||
console.log(`读取保存的窗口状态: ${JSON.stringify(validatedState)}`);
|
||||
|
||||
|
||||
return validatedState;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查位置是否在可见屏幕范围内
|
||||
*/
|
||||
@@ -427,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;
|
||||
}
|
||||
}
|
||||
@@ -446,10 +468,10 @@ class WindowSizeManager {
|
||||
console.error('检查位置可见性失败:', error);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 计算适合当前缩放比的缩放因子
|
||||
*/
|
||||
@@ -458,14 +480,14 @@ class WindowSizeManager {
|
||||
if (!app.isReady()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// 获取系统的缩放因子
|
||||
const { scaleFactor } = screen.getPrimaryDisplay();
|
||||
|
||||
|
||||
// 缩放因子默认为1
|
||||
let zoomFactor = 1;
|
||||
|
||||
|
||||
// 只在高DPI情况下调整
|
||||
if (scaleFactor > 1) {
|
||||
// 自定义逻辑来根据不同的缩放比例进行调整
|
||||
@@ -486,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消息处理程序
|
||||
*/
|
||||
@@ -527,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);
|
||||
@@ -554,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()) {
|
||||
@@ -562,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) {
|
||||
@@ -575,7 +599,7 @@ class WindowSizeManager {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ipcMain.on('reset-content-zoom', (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
if (win && !win.isDestroyed()) {
|
||||
@@ -583,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()) {
|
||||
@@ -618,7 +642,7 @@ class WindowSizeManager {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 只在app ready后设置显示器变化监听
|
||||
if (app.isReady()) {
|
||||
// 监听显示器变化事件
|
||||
@@ -628,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()) {
|
||||
@@ -697,4 +721,4 @@ export const initWindowSizeHandlers = (mainWindow: BrowserWindow | null): void =
|
||||
|
||||
export const calculateMinimumWindowSize = (): { minWidth: number; minHeight: number } => {
|
||||
return { minWidth: MIN_WIDTH, minHeight: MIN_HEIGHT };
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -21,6 +31,7 @@ const store = new Store();
|
||||
// 保存主窗口引用,以便在 activate 事件中使用
|
||||
let mainWindowInstance: BrowserWindow | null = null;
|
||||
let isPlaying = false;
|
||||
let isAppQuitting = false;
|
||||
// 保存迷你模式前的窗口状态
|
||||
let preMiniModeState: WindowState = {
|
||||
width: DEFAULT_MAIN_WIDTH,
|
||||
@@ -30,6 +41,13 @@ let preMiniModeState: WindowState = {
|
||||
isMaximized: false
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置应用退出状态
|
||||
*/
|
||||
export function setAppQuitting(quitting: boolean) {
|
||||
isAppQuitting = quitting;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化代理设置
|
||||
*/
|
||||
@@ -60,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监听
|
||||
*/
|
||||
@@ -117,8 +133,13 @@ export function initializeWindowManager() {
|
||||
ipcMain.on('close-window', (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
if (win) {
|
||||
win.destroy();
|
||||
app.quit();
|
||||
// 在 macOS 上,关闭窗口不应该退出应用,而是隐藏窗口
|
||||
if (process.platform === 'darwin') {
|
||||
win.hide();
|
||||
} else {
|
||||
win.destroy();
|
||||
app.quit();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -146,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);
|
||||
@@ -173,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);
|
||||
@@ -210,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);
|
||||
}
|
||||
}
|
||||
@@ -221,7 +253,6 @@ export function initializeWindowManager() {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ipcMain.on('update-play-state', (_, playing: boolean) => {
|
||||
isPlaying = playing;
|
||||
if (mainWindowInstance) {
|
||||
@@ -266,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);
|
||||
@@ -294,6 +327,20 @@ export function createMainWindow(icon: Electron.NativeImage): BrowserWindow {
|
||||
setThumbarButtons(mainWindow);
|
||||
});
|
||||
|
||||
// 处理窗口关闭事件
|
||||
mainWindow.on('close', (event) => {
|
||||
// 在 macOS 上,阻止默认的关闭行为,改为隐藏窗口
|
||||
if (process.platform === 'darwin') {
|
||||
// 检查是否是应用正在退出
|
||||
if (!isAppQuitting) {
|
||||
event.preventDefault();
|
||||
mainWindow.hide();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 在其他平台上,或者应用正在退出时,允许正常关闭
|
||||
});
|
||||
|
||||
mainWindow.on('ready-to-show', () => {
|
||||
const [width, height] = mainWindow.getSize();
|
||||
console.log(`窗口显示前的大小: ${width}x${height}`);
|
||||
@@ -313,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);
|
||||
}
|
||||
}
|
||||
@@ -344,7 +395,6 @@ export function createMainWindow(icon: Electron.NativeImage): BrowserWindow {
|
||||
|
||||
initWindowSizeHandlers(mainWindow);
|
||||
|
||||
|
||||
// 保存主窗口引用
|
||||
mainWindowInstance = mainWindow;
|
||||
|
||||
|
||||
@@ -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'))) {
|
||||
|
||||
@@ -25,5 +25,7 @@
|
||||
"enableMusicUnblock": true,
|
||||
"enabledMusicSources": ["migu", "kugou", "pyncmd", "bilibili"],
|
||||
"showTopAction": false,
|
||||
"contentZoomFactor": 1
|
||||
"contentZoomFactor": 1,
|
||||
"autoTheme": false,
|
||||
"manualTheme": "light"
|
||||
}
|
||||
|
||||
@@ -32,6 +32,49 @@ interface UnblockResult {
|
||||
// 所有可用平台
|
||||
export const ALL_PLATFORMS: Platform[] = ['migu', 'kugou', 'pyncmd', 'bilibili'];
|
||||
|
||||
/**
|
||||
* 确保对象数据结构完整,处理null或undefined的情况
|
||||
* @param data 需要处理的数据对象
|
||||
*/
|
||||
function ensureDataStructure(data: any): any {
|
||||
// 如果数据本身为空,则返回一个基本结构
|
||||
if (!data) {
|
||||
return {
|
||||
name: '',
|
||||
artists: [],
|
||||
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) => {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 音乐解析函数
|
||||
* @param id 歌曲ID
|
||||
@@ -47,15 +90,16 @@ const unblockMusic = async (
|
||||
enabledPlatforms?: Platform[]
|
||||
): Promise<UnblockResult> => {
|
||||
// 过滤 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;
|
||||
|
||||
songData.album = songData.album || songData.al;
|
||||
songData.artists = songData.artists || songData.ar;
|
||||
|
||||
// 处理歌曲数据,确保数据结构完整
|
||||
const processedSongData = ensureDataStructure(songData);
|
||||
|
||||
const retry = async (attempt: number): Promise<UnblockResult> => {
|
||||
try {
|
||||
const data = await match(parseInt(String(id), 10), filteredPlatforms, songData);
|
||||
const data = await match(parseInt(String(id), 10), filteredPlatforms, processedSongData);
|
||||
const result: UnblockResult = {
|
||||
data: {
|
||||
data,
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
@@ -112,8 +111,11 @@ onMounted(async () => {
|
||||
if (isLyricWindow.value) {
|
||||
return;
|
||||
}
|
||||
// 先初始化播放状态
|
||||
// 初始化 MusicHook,注入 playerStore
|
||||
initMusicHook(playerStore);
|
||||
// 初始化播放状态
|
||||
await playerStore.initializePlayState();
|
||||
|
||||
// 如果有正在播放的音乐,则初始化音频监听器
|
||||
if (playerStore.playMusic && playerStore.playMusic.id) {
|
||||
// 使用 nextTick 确保 DOM 更新后再初始化
|
||||
|
||||
@@ -153,11 +153,8 @@ export const getBilibiliAudioUrl = async (bvid: string, cid: number): Promise<st
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 根据音乐名称搜索并直接返回音频URL
|
||||
export const searchAndGetBilibiliAudioUrl = async (
|
||||
keyword: string
|
||||
): Promise<string> => {
|
||||
export const searchAndGetBilibiliAudioUrl = async (keyword: string): Promise<string> => {
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import type { MusicSourceType } from '@/type/music';
|
||||
|
||||
import type { MusicSourceType } from '@/types/music';
|
||||
|
||||
/**
|
||||
* GD音乐台解析服务
|
||||
@@ -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<ParsedMusicResult | null> => {
|
||||
@@ -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<GDMusicUrlResult | null> {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { IData } from '@/type';
|
||||
import { IAlbumNew } from '@/type/album';
|
||||
import { IDayRecommend } from '@/type/day_recommend';
|
||||
import { IRecommendMusic } from '@/type/music';
|
||||
import { IPlayListSort } from '@/type/playlist';
|
||||
import { IHotSearch, ISearchKeyword } from '@/type/search';
|
||||
import { IHotSinger } from '@/type/singer';
|
||||
import { IData } from '@/types';
|
||||
import { IAlbumNew } from '@/types/album';
|
||||
import { IDayRecommend } from '@/types/day_recommend';
|
||||
import { IRecommendMusic } from '@/types/music';
|
||||
import { IPlayListSort } from '@/types/playlist';
|
||||
import { IHotSearch, ISearchKeyword } from '@/types/search';
|
||||
import { IHotSinger } from '@/types/singer';
|
||||
import request from '@/utils/request';
|
||||
|
||||
interface IHotSingerParams {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IList } from '@/type/list';
|
||||
import type { IListDetail } from '@/type/listDetail';
|
||||
import { IList } from '@/types/list';
|
||||
import type { IListDetail } from '@/types/listDetail';
|
||||
import request from '@/utils/request';
|
||||
|
||||
interface IListByTagParams {
|
||||
|
||||
@@ -44,3 +44,11 @@ export function loginByCellphone(phone: string, password: string) {
|
||||
password
|
||||
});
|
||||
}
|
||||
|
||||
// UID登录 - 通过用户ID获取用户信息
|
||||
// /user/detail
|
||||
export function loginByUid(uid: string | number) {
|
||||
return request.get('/user/detail', {
|
||||
params: { uid }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 { ILyric } from '@/types/lyric';
|
||||
import type { SongResult } from '@/types/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;
|
||||
|
||||
@@ -82,73 +84,120 @@ export const getMusicLrc = async (id: number) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 从Bilibili获取音频URL
|
||||
* @param data 歌曲数据
|
||||
* @returns 解析结果
|
||||
*/
|
||||
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 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: {
|
||||
code: 200,
|
||||
message: 'success',
|
||||
data: { url }
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 从GD音乐台获取音频URL
|
||||
* @param id 歌曲ID
|
||||
* @param data 歌曲数据
|
||||
* @returns 解析结果,失败时返回null
|
||||
*/
|
||||
const getGDMusicAudio = async (id: number, data: SongResult) => {
|
||||
try {
|
||||
const gdResult = await parseFromGDMusic(id, data, '999');
|
||||
if (gdResult) {
|
||||
return gdResult;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('GD音乐台解析失败:', error);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 使用unblockMusic解析音频URL
|
||||
* @param id 歌曲ID
|
||||
* @param data 歌曲数据
|
||||
* @param sources 音源列表
|
||||
* @returns 解析结果
|
||||
*/
|
||||
const getUnblockMusicAudio = (id: number, data: SongResult, sources: any[]) => {
|
||||
const filteredSources = sources.filter((source) => source !== 'gdmusic');
|
||||
console.log(`使用unblockMusic解析,音源:`, filteredSources);
|
||||
return window.api.unblockMusic(id, cloneDeep(data), cloneDeep(filteredSources));
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取解析后的音乐URL
|
||||
* @param id 歌曲ID
|
||||
* @param data 歌曲数据
|
||||
* @returns 解析结果
|
||||
*/
|
||||
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 savedSource = localStorage.getItem(`song_source_${songId}`);
|
||||
let enabledSources: any[] = [];
|
||||
|
||||
// 如果有歌曲自定义音源,使用自定义音源
|
||||
if (savedSource) {
|
||||
try {
|
||||
enabledSources = JSON.parse(savedSource);
|
||||
console.log(`使用歌曲 ${id} 自定义音源:`, enabledSources);
|
||||
if(enabledSources.includes('bilibili')){
|
||||
// 构建搜索关键词,依次判断歌曲名称、歌手名称和专辑名称是否存在
|
||||
const songName = data?.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 name = [songName, artistName, albumName].filter(Boolean).join(' ').trim();
|
||||
console.log('开始搜索bilibili音频', name);
|
||||
return {
|
||||
data: {
|
||||
code: 200,
|
||||
message: 'success',
|
||||
data: {
|
||||
url: await searchAndGetBilibiliAudioUrl(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
const savedSourceStr = localStorage.getItem(`song_source_${songId}`);
|
||||
let musicSources: any[] = [];
|
||||
|
||||
try {
|
||||
if (savedSourceStr) {
|
||||
// 使用自定义音源
|
||||
musicSources = JSON.parse(savedSourceStr);
|
||||
console.log(`使用歌曲 ${id} 自定义音源:`, musicSources);
|
||||
} else {
|
||||
// 使用全局音源设置
|
||||
musicSources = settingStore.setData.enabledMusicSources || [];
|
||||
console.log(`使用全局音源设置:`, musicSources);
|
||||
if (isElectron && musicSources.length > 0) {
|
||||
return getUnblockMusicAudio(id, data, musicSources);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('e',e)
|
||||
console.error('解析自定义音源失败, 使用全局设置', e);
|
||||
enabledSources = settingStore.setData.enabledMusicSources || [];
|
||||
}
|
||||
} else {
|
||||
// 没有自定义音源,使用全局音源设置
|
||||
enabledSources = settingStore.setData.enabledMusicSources || [];
|
||||
} catch (e) {
|
||||
console.error('解析音源设置失败,使用全局设置', e);
|
||||
musicSources = settingStore.setData.enabledMusicSources || [];
|
||||
}
|
||||
|
||||
// 检查是否选择了GD音乐台解析
|
||||
|
||||
if (enabledSources.includes('gdmusic')) {
|
||||
// 获取音质设置并转换为GD音乐台格式
|
||||
try {
|
||||
const gdResult = await parseFromGDMusic(id, data, '999');
|
||||
if (gdResult) {
|
||||
return gdResult;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('GD音乐台解析失败:', error);
|
||||
}
|
||||
|
||||
console.log('GD音乐台所有音源均解析失败,尝试使用unblockMusic');
|
||||
|
||||
// 2. 按优先级解析
|
||||
|
||||
// 2.1 Bilibili解析(优先级最高)
|
||||
if (musicSources.includes('bilibili')) {
|
||||
return await getBilibiliAudio(data);
|
||||
}
|
||||
|
||||
// 如果GD音乐台解析失败或者未启用,尝试使用unblockMusic
|
||||
if (isElectron) {
|
||||
const filteredSources = enabledSources.filter(source => source !== 'gdmusic');
|
||||
return window.api.unblockMusic(id, cloneDeep(data), cloneDeep(filteredSources));
|
||||
|
||||
// 2.2 GD音乐台解析
|
||||
if (musicSources.includes('gdmusic')) {
|
||||
const gdResult = await getGDMusicAudio(id, data);
|
||||
if (gdResult) return gdResult;
|
||||
// GD解析失败,继续下一步
|
||||
console.log('GD音乐台解析失败,尝试使用其他音源');
|
||||
}
|
||||
|
||||
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<any>('/music', { params: { id } });
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IData } from '@/type';
|
||||
import { IMvUrlData } from '@/type/mv';
|
||||
import { IData } from '@/types';
|
||||
import { IMvUrlData } from '@/types/mv';
|
||||
import request from '@/utils/request';
|
||||
|
||||
interface MvParams {
|
||||
|
||||
@@ -24,4 +24,4 @@ export function getImportTaskStatus(id: string | number) {
|
||||
method: 'get',
|
||||
params: { id }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { IUserDetail, IUserFollow } from '@/type/user';
|
||||
import type { IUserDetail, IUserFollow } from '@/types/user';
|
||||
import request from '@/utils/request';
|
||||
|
||||
// /user/detail
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -41,9 +41,9 @@
|
||||
<div class="mt-4">
|
||||
<p
|
||||
class="text-sm text-gray-700 dark:text-gray-200 text-center cursor-pointer hover:text-green-500"
|
||||
@click="copyQQ"
|
||||
@click="copyText"
|
||||
>
|
||||
{{ t('comp.coffee.qqGroup') }}
|
||||
{{ t('comp.coffee.groupText') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
@@ -70,8 +70,8 @@ import wechat from '@/assets/wechat.png';
|
||||
const { t } = useI18n();
|
||||
|
||||
const message = useMessage();
|
||||
const copyQQ = () => {
|
||||
navigator.clipboard.writeText('algermusic');
|
||||
const copyText = () => {
|
||||
navigator.clipboard.writeText('AlgerMusic');
|
||||
message.success(t('common.copySuccess'));
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user