Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
891d70f3ed | ||
|
|
37b5908ddc | ||
|
|
ead017e4b1 | ||
|
|
da2a32e420 | ||
|
|
5fc6edba20 | ||
|
|
1ef47b8f1d | ||
|
|
cdaab19afa | ||
|
|
6a0b03cfe1 | ||
|
|
d449930a02 | ||
|
|
820509dbea | ||
|
|
1493ab7317 | ||
|
|
c6ca63ee11 | ||
|
|
4fa1295b84 | ||
|
|
174428b386 | ||
|
|
599b0251af | ||
|
|
25c2180247 | ||
|
|
a6ff0e7f5c | ||
|
|
2e06711600 | ||
|
|
80770d6c75 | ||
|
|
1e068df2ad | ||
|
|
4172ff9fc6 | ||
|
|
83a7df9fe8 | ||
|
|
ba95dc11fe | ||
|
|
93829acdab | ||
|
|
1f0f35dd51 | ||
|
|
be94d6ff8e | ||
|
|
1bdb8fcb4a | ||
|
|
914e043502 | ||
|
|
dfa175b8b2 | ||
|
|
a94e0efba5 | ||
|
|
fb0831f2eb | ||
|
|
573023600a | ||
|
|
041aad31b4 | ||
|
|
f652932d71 | ||
|
|
7efeb9492b | ||
|
|
055536eb5c | ||
|
|
14852fc8d3 | ||
|
|
9866e772df | ||
|
|
87ca0836b1 | ||
|
|
fa07c5a40c | ||
|
|
5dbfea240b | ||
|
|
c1344393c3 | ||
|
|
426888f77c | ||
|
|
45cbc15c0f | ||
|
|
072025a543 | ||
|
|
c6427aa3e1 | ||
|
|
632cdb1239 | ||
|
|
8ffe472605 | ||
|
|
8e86d378d0 | ||
|
|
744fd53fb1 | ||
|
|
3c64473dbb | ||
|
|
e70fed37da | ||
|
|
b749854c5e | ||
|
|
d9210cc50a | ||
|
|
f186d34885 | ||
|
|
ba992b7c33 | ||
|
|
24d7c839c7 | ||
|
|
a4f3df80c9 | ||
|
|
866fec6ee3 | ||
|
|
8f7d6fbb8d | ||
|
|
62e26cae7d | ||
|
|
ddb814da10 | ||
|
|
e266ea8ef8 | ||
|
|
a894954641 | ||
|
|
f640ab9969 | ||
|
|
9eb17fd978 | ||
|
|
020aca7384 | ||
|
|
fcc47dc0ff | ||
|
|
17ce268da6 | ||
|
|
43c64b1b43 | ||
|
|
11ced6b418 | ||
|
|
3d3992154a | ||
|
|
81e7b67c7f | ||
|
|
d7e94a342b | ||
|
|
46f8067577 | ||
|
|
1dc7d0ceca | ||
|
|
ba64631a17 | ||
|
|
cdb9524f04 | ||
|
|
5213aa13c5 | ||
|
|
d870d0198f | ||
|
|
976a9afd2f | ||
|
|
018218a5bf | ||
|
|
38a9d6ed31 | ||
|
|
8dab799939 | ||
|
|
1ddbe6f24e | ||
|
|
4d5bcba6c7 | ||
|
|
f833306b60 | ||
|
|
4d92ed9963 | ||
|
|
a22285156a | ||
|
|
d1029f16d6 | ||
|
|
4908555635 | ||
|
|
750cf7a484 | ||
|
|
a334743f6f | ||
|
|
14747cac10 | ||
|
|
cc239aeaba | ||
|
|
eeda296589 | ||
|
|
edb7ea201c | ||
|
|
17d20fa299 | ||
|
|
f8d421c9b1 | ||
|
|
dfdf02a17f | ||
|
|
abdb2bcd50 |
@@ -1,13 +1,4 @@
|
|||||||
snapshot*
|
|
||||||
dist
|
|
||||||
lib
|
|
||||||
es
|
|
||||||
esm
|
|
||||||
node_modules
|
node_modules
|
||||||
static
|
dist
|
||||||
cypress
|
out
|
||||||
script/test/cypress
|
.gitignore
|
||||||
_site
|
|
||||||
temp*
|
|
||||||
static/
|
|
||||||
!.prettierrc.js
|
|
||||||
|
|||||||
140
.eslintrc
@@ -1,140 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"eslint-config-airbnb-base",
|
|
||||||
"@vue/typescript/recommended",
|
|
||||||
"plugin:vue/vue3-recommended",
|
|
||||||
"plugin:vue-scoped-css/base",
|
|
||||||
"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": {
|
|
||||||
"no-nested-ternary": "off",
|
|
||||||
"no-console": "off",
|
|
||||||
"no-continue": "off",
|
|
||||||
"no-restricted-syntax": "off",
|
|
||||||
"no-return-assign": "off",
|
|
||||||
"no-unused-expressions": "off",
|
|
||||||
"no-return-await": "off",
|
|
||||||
"no-plusplus": "off",
|
|
||||||
"no-param-reassign": "off",
|
|
||||||
"no-shadow": "off",
|
|
||||||
"guard-for-in": "off",
|
|
||||||
"import/extensions": "off",
|
|
||||||
"import/no-unresolved": "off",
|
|
||||||
"import/no-extraneous-dependencies": "off",
|
|
||||||
"import/prefer-default-export": "off",
|
|
||||||
"import/first": "off", // https://github.com/vuejs/vue-eslint-parser/issues/58
|
|
||||||
"@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", // 因为AxiosCancel必须实例化而能静态化所以加的规则,如果有办法解决可以取消
|
|
||||||
"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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"*.ts",
|
|
||||||
"*.tsx"
|
|
||||||
], // https://github.com/typescript-eslint eslint-recommended
|
|
||||||
"rules": {
|
|
||||||
"constructor-super": "off", // ts(2335) & ts(2377)
|
|
||||||
"getter-return": "off", // ts(2378)
|
|
||||||
"no-const-assign": "off", // ts(2588)
|
|
||||||
"no-dupe-args": "off", // ts(2300)
|
|
||||||
"no-dupe-class-members": "off", // ts(2393) & ts(2300)
|
|
||||||
"no-dupe-keys": "off", // ts(1117)
|
|
||||||
"no-func-assign": "off", // ts(2539)
|
|
||||||
"no-import-assign": "off", // ts(2539) & ts(2540)
|
|
||||||
"no-new-symbol": "off", // ts(2588)
|
|
||||||
"no-obj-calls": "off", // ts(2349)
|
|
||||||
"no-redeclare": "off", // ts(2451)
|
|
||||||
"no-setter-return": "off", // ts(2408)
|
|
||||||
"no-this-before-super": "off", // ts(2376)
|
|
||||||
"no-undef": "off", // ts(2304)
|
|
||||||
"no-unreachable": "off", // ts(7027)
|
|
||||||
"no-unsafe-negation": "off", // ts(2365) & ts(2360) & ts(2358)
|
|
||||||
"no-var": "error", // ts transpiles let/const to var, so no need for vars any more
|
|
||||||
"prefer-const": "error", // ts provides better types with const
|
|
||||||
"prefer-rest-params": "error", // ts provides better types with rest args over arguments
|
|
||||||
"prefer-spread": "error", // ts transpiles spread to apply, so no need for manual apply
|
|
||||||
"valid-typeof": "off", // ts(2367)
|
|
||||||
"consistent-return": "off",
|
|
||||||
"no-promise-executor-return": "off",
|
|
||||||
"prefer-promise-reject-errors": "off"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
137
.eslintrc.cjs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
require('@rushstack/eslint-patch/modern-module-resolution');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'eslint-config-airbnb-base',
|
||||||
|
'@vue/typescript/recommended',
|
||||||
|
'plugin:vue/vue3-recommended',
|
||||||
|
'plugin:vue-scoped-css/base',
|
||||||
|
'@electron-toolkit',
|
||||||
|
'@electron-toolkit/eslint-config-ts/eslint-recommended',
|
||||||
|
'plugin:prettier/recommended'
|
||||||
|
],
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
node: true,
|
||||||
|
jest: true,
|
||||||
|
es6: true
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
defineProps: 'readonly',
|
||||||
|
defineEmits: 'readonly'
|
||||||
|
},
|
||||||
|
plugins: ['vue', '@typescript-eslint', 'simple-import-sort'],
|
||||||
|
parserOptions: {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
sourceType: 'module',
|
||||||
|
allowImportExportEverywhere: true,
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
'import/extensions': ['.js', '.jsx', '.ts', '.tsx']
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'vue/require-default-prop': 'off',
|
||||||
|
'vue/multi-word-component-names': 'off',
|
||||||
|
'no-nested-ternary': 'off',
|
||||||
|
'no-console': 'off',
|
||||||
|
'no-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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
87
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
name: Build and Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
# MacOS Build
|
||||||
|
- name: Build MacOS
|
||||||
|
if: matrix.os == 'macos-latest'
|
||||||
|
run: |
|
||||||
|
export ELECTRON_BUILDER_EXTRA_ARGS="--universal"
|
||||||
|
npm run build:mac
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
CSC_IDENTITY_AUTO_DISCOVERY: false
|
||||||
|
DEBUG: electron-builder
|
||||||
|
|
||||||
|
# Windows Build
|
||||||
|
- name: Build Windows
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
run: npm run build:win
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
# Linux Build
|
||||||
|
- name: Build Linux
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf
|
||||||
|
npm run build:linux
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
# Get version from tag
|
||||||
|
- name: Get version from tag
|
||||||
|
id: get_version
|
||||||
|
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
# Read release notes
|
||||||
|
- name: Read release notes
|
||||||
|
id: release_notes
|
||||||
|
run: |
|
||||||
|
NOTES=$(awk "/## \[v${{ env.VERSION }}\]/{p=1;print;next} /## \[v/{p=0}p" CHANGELOG.md)
|
||||||
|
echo "NOTES<<EOF" >> $GITHUB_ENV
|
||||||
|
echo "$NOTES" >> $GITHUB_ENV
|
||||||
|
echo "EOF" >> $GITHUB_ENV
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
# Upload artifacts
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
dist/*.dmg
|
||||||
|
dist/*.exe
|
||||||
|
dist/*.deb
|
||||||
|
dist/*.AppImage
|
||||||
|
dist/latest*.yml
|
||||||
|
dist/*.blockmap
|
||||||
|
body: ${{ env.NOTES }}
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
6
.gitignore
vendored
@@ -17,4 +17,8 @@ dist.zip
|
|||||||
|
|
||||||
bun.lockb
|
bun.lockb
|
||||||
|
|
||||||
.env.*.local
|
.env.*.local
|
||||||
|
|
||||||
|
out
|
||||||
|
|
||||||
|
.cursorrules
|
||||||
2
.npmrc
@@ -1,2 +0,0 @@
|
|||||||
electron_mirror=https://npmmirror.com/mirrors/electron/
|
|
||||||
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
|
|
||||||
6
.prettierignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
out
|
||||||
|
dist
|
||||||
|
pnpm-lock.yaml
|
||||||
|
LICENSE.md
|
||||||
|
tsconfig.json
|
||||||
|
tsconfig.*.json
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
// 一行最多 120 字符..
|
|
||||||
printWidth: 120,
|
|
||||||
// 使用 2 个空格缩进
|
|
||||||
tabWidth: 2,
|
|
||||||
// 不使用缩进符,而使用空格
|
|
||||||
useTabs: false,
|
|
||||||
// 行尾需要有分号
|
|
||||||
semi: true,
|
|
||||||
// 使用单引号
|
|
||||||
singleQuote: true,
|
|
||||||
// 对象的 key 仅在必要时用引号
|
|
||||||
quoteProps: 'as-needed',
|
|
||||||
// jsx 不使用单引号,而使用双引号
|
|
||||||
jsxSingleQuote: false,
|
|
||||||
// 末尾需要有逗号
|
|
||||||
trailingComma: 'all',
|
|
||||||
// 大括号内的首尾需要空格
|
|
||||||
bracketSpacing: true,
|
|
||||||
// jsx 标签的反尖括号需要换行
|
|
||||||
jsxBracketSameLine: false,
|
|
||||||
// 箭头函数,只有一个参数的时候,也需要括号
|
|
||||||
arrowParens: 'always',
|
|
||||||
// 每个文件格式化的范围是文件的全部内容
|
|
||||||
rangeStart: 0,
|
|
||||||
rangeEnd: Infinity,
|
|
||||||
// 不需要写文件开头的 @prettier
|
|
||||||
requirePragma: false,
|
|
||||||
// 不需要自动在文件开头插入 @prettier
|
|
||||||
insertPragma: false,
|
|
||||||
// 使用默认的折行标准
|
|
||||||
proseWrap: 'preserve',
|
|
||||||
// 根据显示样式决定 html 要不要折行
|
|
||||||
htmlWhitespaceSensitivity: 'css',
|
|
||||||
// vue 文件中的 script 和 style 内不用缩进
|
|
||||||
vueIndentScriptAndStyle: false,
|
|
||||||
// 换行符使用 lf
|
|
||||||
endOfLine: 'lf',
|
|
||||||
};
|
|
||||||
5
.prettierrc.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
singleQuote: true
|
||||||
|
semi: true
|
||||||
|
printWidth: 100
|
||||||
|
trailingComma: none
|
||||||
|
endOfLine: auto
|
||||||
9
CHANGELOG.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# 更新日志
|
||||||
|
|
||||||
|
## v3.9.3
|
||||||
|
|
||||||
|
### ✨ 新功能
|
||||||
|
- 实现国际化(i18n)功能
|
||||||
|
- 增加动态代理节点获取和缓存机制
|
||||||
|
- 优化更新检查逻辑,增加多个代理源支持
|
||||||
|
- 修改捐赠列表 API
|
||||||
201
LICENSE
@@ -1,201 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
88
README.md
@@ -1,14 +1,24 @@
|
|||||||
# Alger Music Player
|
# Alger Music Player
|
||||||
主要功能如下
|
主要功能如下
|
||||||
|
- 🎵 音乐推荐
|
||||||
- 音乐推荐
|
- 🔐 网易云账号登录与同步
|
||||||
- 音乐播放
|
- 📝 功能
|
||||||
- 网易云登录
|
- 播放历史记录
|
||||||
- 播放历史
|
- 歌曲收藏管理
|
||||||
- 桌面歌词
|
- 自定义快捷键配置
|
||||||
- 歌单 mv 搜索 专辑等功能
|
- 🎨 界面与交互
|
||||||
- 识别无法播放歌曲 并代理播放
|
- 沉浸式歌词显示(点击左下角封面进入)
|
||||||
- 可听周杰伦(搜索专辑)
|
- 独立桌面歌词窗口
|
||||||
|
- 明暗主题切换
|
||||||
|
- 🎼 音乐功能
|
||||||
|
- 支持歌单、MV、专辑等完整音乐服务
|
||||||
|
- 灰色音乐资源解析(基于 @unblockneteasemusic/server)
|
||||||
|
- 高品质音乐试听(需网易云VIP)
|
||||||
|
- 音乐文件下载(支持右键下载和批量下载)
|
||||||
|
- 🚀 技术特性
|
||||||
|
- 本地化服务,无需依赖在线API (基于 netease-cloud-music-api)
|
||||||
|
- 自动更新检测
|
||||||
|
- 全平台适配(Desktop & Web & Mobile Web)
|
||||||
|
|
||||||
## 项目简介
|
## 项目简介
|
||||||
一个基于 electron typescript vue3 的桌面音乐播放器 适配 web端 桌面端 web移动端
|
一个基于 electron typescript vue3 的桌面音乐播放器 适配 web端 桌面端 web移动端
|
||||||
@@ -19,9 +29,11 @@
|
|||||||
QQ群:789288579
|
QQ群:789288579
|
||||||
|
|
||||||
## 软件截图
|
## 软件截图
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
@@ -30,65 +42,15 @@ QQ群:789288579
|
|||||||
- TypeScript - JavaScript 的超集,添加了类型系统
|
- TypeScript - JavaScript 的超集,添加了类型系统
|
||||||
- Electron - 跨平台桌面应用开发框架
|
- Electron - 跨平台桌面应用开发框架
|
||||||
- Vite - 下一代前端构建工具
|
- Vite - 下一代前端构建工具
|
||||||
|
|
||||||
### UI 框架
|
|
||||||
- Naive UI - 基于 Vue 3 的组件库
|
- Naive UI - 基于 Vue 3 的组件库
|
||||||
|
|
||||||
### 项目特点
|
|
||||||
- 完整的类型支持(TypeScript)
|
|
||||||
- 模块化设计
|
|
||||||
- 自动化组件和 API 导入
|
|
||||||
- 多平台支持(Web、Desktop、Mobile Web)
|
|
||||||
- 构建优化(代码分割、压缩)
|
|
||||||
|
|
||||||
## 咖啡☕️
|
## 咖啡☕️
|
||||||
|
|
||||||
| 微信 | 支付宝 |
|
| 微信 | 支付宝 |
|
||||||
| :--------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------: |
|
| :--------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------: |
|
||||||
| <img src="https://github.com/algerkong/algerkong/blob/main/wechat.jpg?raw=true" alt="WeChat QRcode" width=200> | <img src="https://github.com/algerkong/algerkong/blob/main/alipay.jpg?raw=true" alt="Wechat QRcode" width=200> |
|
| <img src="https://github.com/algerkong/algerkong/blob/main/wechat.jpg?raw=true" alt="WeChat QRcode" width=200> | <img src="https://github.com/algerkong/algerkong/blob/main/alipay.jpg?raw=true" alt="Wechat QRcode" width=200> |
|
||||||
|
|
||||||
## 项目运行
|
|
||||||
```bash
|
|
||||||
# 安装依赖
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# 运行项目 web
|
|
||||||
npm run dev
|
|
||||||
|
|
||||||
# 运行项目 electron
|
|
||||||
npm run start
|
|
||||||
|
|
||||||
# 打包项目 web
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
# 打包项目 electron
|
|
||||||
npm run win ...
|
|
||||||
# 具体看 package.json
|
|
||||||
```
|
|
||||||
#### 注意
|
|
||||||
- 本地运行需要配置 .env.development 文件
|
|
||||||
- 打包需要配置 .env.production 文件
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# .env.development
|
|
||||||
VITE_API_LOCAL = /api
|
|
||||||
VITE_API_MUSIC_PROXY = /music
|
|
||||||
VITE_API_PROXY_MUSIC = /music_proxy
|
|
||||||
|
|
||||||
# 你的接口地址 (必填)
|
|
||||||
VITE_API = ***
|
|
||||||
# 音乐po接口地址
|
|
||||||
VITE_API_MUSIC = ***
|
|
||||||
VITE_API_PROXY = ***
|
|
||||||
|
|
||||||
|
|
||||||
# .env.production
|
|
||||||
# 你的接口地址 (必填)
|
|
||||||
VITE_API = ***
|
|
||||||
# 音乐po接口地址
|
|
||||||
VITE_API_MUSIC = ***
|
|
||||||
# 代理地址
|
|
||||||
VITE_API_PROXY = ***
|
|
||||||
```
|
|
||||||
|
|
||||||
## Stargazers over time
|
## Stargazers over time
|
||||||
[](https://starchart.cc/algerkong/AlgerMusicPlayer)
|
[](https://starchart.cc/algerkong/AlgerMusicPlayer)
|
||||||
|
|||||||
148
app.js
@@ -1,148 +0,0 @@
|
|||||||
const { app, BrowserWindow, ipcMain, Tray, Menu, globalShortcut, nativeImage } = require('electron');
|
|
||||||
const path = require('path');
|
|
||||||
const Store = require('electron-store');
|
|
||||||
const setJson = require('./electron/set.json');
|
|
||||||
const { loadLyricWindow } = require('./electron/lyric');
|
|
||||||
const config = require('./electron/config');
|
|
||||||
|
|
||||||
let mainWin = null;
|
|
||||||
function createWindow() {
|
|
||||||
mainWin = new BrowserWindow({
|
|
||||||
width: 1200,
|
|
||||||
height: 780,
|
|
||||||
frame: false,
|
|
||||||
webPreferences: {
|
|
||||||
nodeIntegration: false,
|
|
||||||
contextIsolation: true,
|
|
||||||
preload: path.join(__dirname, '/electron/preload.js'),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const win = mainWin;
|
|
||||||
win.setMinimumSize(1200, 780);
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
win.webContents.openDevTools({ mode: 'detach' });
|
|
||||||
win.loadURL(`http://localhost:${config.development.mainPort}/`);
|
|
||||||
} else {
|
|
||||||
win.loadURL(`file://${__dirname}/dist/index.html`);
|
|
||||||
}
|
|
||||||
const image = nativeImage
|
|
||||||
.createFromPath(path.join(__dirname, 'public/icon_16x16.png'))
|
|
||||||
.resize({ width: 16, height: 16 });
|
|
||||||
const tray = new Tray(image);
|
|
||||||
|
|
||||||
// 创建一个上下文菜单
|
|
||||||
const contextMenu = Menu.buildFromTemplate([
|
|
||||||
{
|
|
||||||
label: '显示',
|
|
||||||
click: () => {
|
|
||||||
win.show();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '退出',
|
|
||||||
click: () => {
|
|
||||||
win.destroy();
|
|
||||||
app.quit();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 设置系统托盘图标的上下文菜单
|
|
||||||
tray.setContextMenu(contextMenu);
|
|
||||||
|
|
||||||
// 当系统托盘图标被点击时,切换窗口的显示/隐藏
|
|
||||||
tray.on('click', () => {
|
|
||||||
if (win.isVisible()) {
|
|
||||||
win.hide();
|
|
||||||
} else {
|
|
||||||
win.show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const set = store.get('set');
|
|
||||||
// store.set('set', setJson)
|
|
||||||
|
|
||||||
if (!set) {
|
|
||||||
store.set('set', setJson);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadLyricWindow(ipcMain, mainWin);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 限制只能启动一个应用
|
|
||||||
const gotTheLock = app.requestSingleInstanceLock();
|
|
||||||
if (!gotTheLock) {
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
app.whenReady().then(createWindow);
|
|
||||||
|
|
||||||
app.on('ready', () => {
|
|
||||||
globalShortcut.register('CommandOrControl+Alt+Shift+M', () => {
|
|
||||||
if (mainWin.isVisible()) {
|
|
||||||
mainWin.hide();
|
|
||||||
} else {
|
|
||||||
mainWin.show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
|
||||||
if (process.platform !== 'darwin') {
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.on('will-quit', () => {
|
|
||||||
globalShortcut.unregisterAll();
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('minimize-window', (event) => {
|
|
||||||
const win = BrowserWindow.fromWebContents(event.sender);
|
|
||||||
win.minimize();
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('maximize-window', (event) => {
|
|
||||||
const win = BrowserWindow.fromWebContents(event.sender);
|
|
||||||
if (win.isMaximized()) {
|
|
||||||
win.unmaximize();
|
|
||||||
} else {
|
|
||||||
win.maximize();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('close-window', (event) => {
|
|
||||||
const win = BrowserWindow.fromWebContents(event.sender);
|
|
||||||
win.destroy();
|
|
||||||
app.quit();
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('drag-start', (event) => {
|
|
||||||
const win = BrowserWindow.fromWebContents(event.sender);
|
|
||||||
win.webContents.beginFrameSubscription((frameBuffer) => {
|
|
||||||
event.reply('frame-buffer', frameBuffer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('mini-tray', (event) => {
|
|
||||||
const win = BrowserWindow.fromWebContents(event.sender);
|
|
||||||
win.hide();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 重启
|
|
||||||
ipcMain.on('restart', () => {
|
|
||||||
app.relaunch();
|
|
||||||
app.exit(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
const store = new Store();
|
|
||||||
|
|
||||||
// 定义ipcRenderer监听事件
|
|
||||||
ipcMain.on('setStore', (_, key, value) => {
|
|
||||||
store.set(key, value);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('getStore', (_, key) => {
|
|
||||||
const value = store.get(key);
|
|
||||||
_.returnValue = value || '';
|
|
||||||
});
|
|
||||||
139
auto-imports.d.ts
vendored
@@ -6,70 +6,85 @@
|
|||||||
// biome-ignore lint: disable
|
// biome-ignore lint: disable
|
||||||
export {}
|
export {}
|
||||||
declare global {
|
declare global {
|
||||||
const EffectScope: typeof import('vue')['EffectScope']
|
const EffectScope: (typeof import('vue'))['EffectScope'];
|
||||||
const computed: typeof import('vue')['computed']
|
const computed: (typeof import('vue'))['computed'];
|
||||||
const createApp: typeof import('vue')['createApp']
|
const createApp: (typeof import('vue'))['createApp'];
|
||||||
const customRef: typeof import('vue')['customRef']
|
const customRef: (typeof import('vue'))['customRef'];
|
||||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
const defineAsyncComponent: (typeof import('vue'))['defineAsyncComponent'];
|
||||||
const defineComponent: typeof import('vue')['defineComponent']
|
const defineComponent: (typeof import('vue'))['defineComponent'];
|
||||||
const effectScope: typeof import('vue')['effectScope']
|
const effectScope: (typeof import('vue'))['effectScope'];
|
||||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
const getCurrentInstance: (typeof import('vue'))['getCurrentInstance'];
|
||||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
const getCurrentScope: (typeof import('vue'))['getCurrentScope'];
|
||||||
const h: typeof import('vue')['h']
|
const h: (typeof import('vue'))['h'];
|
||||||
const inject: typeof import('vue')['inject']
|
const inject: (typeof import('vue'))['inject'];
|
||||||
const isProxy: typeof import('vue')['isProxy']
|
const isProxy: (typeof import('vue'))['isProxy'];
|
||||||
const isReactive: typeof import('vue')['isReactive']
|
const isReactive: (typeof import('vue'))['isReactive'];
|
||||||
const isReadonly: typeof import('vue')['isReadonly']
|
const isReadonly: (typeof import('vue'))['isReadonly'];
|
||||||
const isRef: typeof import('vue')['isRef']
|
const isRef: (typeof import('vue'))['isRef'];
|
||||||
const markRaw: typeof import('vue')['markRaw']
|
const markRaw: (typeof import('vue'))['markRaw'];
|
||||||
const nextTick: typeof import('vue')['nextTick']
|
const nextTick: (typeof import('vue'))['nextTick'];
|
||||||
const onActivated: typeof import('vue')['onActivated']
|
const onActivated: (typeof import('vue'))['onActivated'];
|
||||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
const onBeforeMount: (typeof import('vue'))['onBeforeMount'];
|
||||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
const onBeforeUnmount: (typeof import('vue'))['onBeforeUnmount'];
|
||||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
const onBeforeUpdate: (typeof import('vue'))['onBeforeUpdate'];
|
||||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
const onDeactivated: (typeof import('vue'))['onDeactivated'];
|
||||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
const onErrorCaptured: (typeof import('vue'))['onErrorCaptured'];
|
||||||
const onMounted: typeof import('vue')['onMounted']
|
const onMounted: (typeof import('vue'))['onMounted'];
|
||||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
const onRenderTracked: (typeof import('vue'))['onRenderTracked'];
|
||||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
const onRenderTriggered: (typeof import('vue'))['onRenderTriggered'];
|
||||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
const onScopeDispose: (typeof import('vue'))['onScopeDispose'];
|
||||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
const onServerPrefetch: (typeof import('vue'))['onServerPrefetch'];
|
||||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
const onUnmounted: (typeof import('vue'))['onUnmounted'];
|
||||||
const onUpdated: typeof import('vue')['onUpdated']
|
const onUpdated: (typeof import('vue'))['onUpdated'];
|
||||||
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
const onWatcherCleanup: (typeof import('vue'))['onWatcherCleanup'];
|
||||||
const provide: typeof import('vue')['provide']
|
const provide: (typeof import('vue'))['provide'];
|
||||||
const reactive: typeof import('vue')['reactive']
|
const reactive: (typeof import('vue'))['reactive'];
|
||||||
const readonly: typeof import('vue')['readonly']
|
const readonly: (typeof import('vue'))['readonly'];
|
||||||
const ref: typeof import('vue')['ref']
|
const ref: (typeof import('vue'))['ref'];
|
||||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
const resolveComponent: (typeof import('vue'))['resolveComponent'];
|
||||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
const shallowReactive: (typeof import('vue'))['shallowReactive'];
|
||||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
const shallowReadonly: (typeof import('vue'))['shallowReadonly'];
|
||||||
const shallowRef: typeof import('vue')['shallowRef']
|
const shallowRef: (typeof import('vue'))['shallowRef'];
|
||||||
const toRaw: typeof import('vue')['toRaw']
|
const toRaw: (typeof import('vue'))['toRaw'];
|
||||||
const toRef: typeof import('vue')['toRef']
|
const toRef: (typeof import('vue'))['toRef'];
|
||||||
const toRefs: typeof import('vue')['toRefs']
|
const toRefs: (typeof import('vue'))['toRefs'];
|
||||||
const toValue: typeof import('vue')['toValue']
|
const toValue: (typeof import('vue'))['toValue'];
|
||||||
const triggerRef: typeof import('vue')['triggerRef']
|
const triggerRef: (typeof import('vue'))['triggerRef'];
|
||||||
const unref: typeof import('vue')['unref']
|
const unref: (typeof import('vue'))['unref'];
|
||||||
const useAttrs: typeof import('vue')['useAttrs']
|
const useAttrs: (typeof import('vue'))['useAttrs'];
|
||||||
const useCssModule: typeof import('vue')['useCssModule']
|
const useCssModule: (typeof import('vue'))['useCssModule'];
|
||||||
const useCssVars: typeof import('vue')['useCssVars']
|
const useCssVars: (typeof import('vue'))['useCssVars'];
|
||||||
const useDialog: typeof import('naive-ui')['useDialog']
|
const useDialog: (typeof import('naive-ui'))['useDialog'];
|
||||||
const useId: typeof import('vue')['useId']
|
const useId: (typeof import('vue'))['useId'];
|
||||||
const useLoadingBar: typeof import('naive-ui')['useLoadingBar']
|
const useLoadingBar: (typeof import('naive-ui'))['useLoadingBar'];
|
||||||
const useMessage: typeof import('naive-ui')['useMessage']
|
const useMessage: (typeof import('naive-ui'))['useMessage'];
|
||||||
const useModel: typeof import('vue')['useModel']
|
const useModel: (typeof import('vue'))['useModel'];
|
||||||
const useNotification: typeof import('naive-ui')['useNotification']
|
const useNotification: (typeof import('naive-ui'))['useNotification'];
|
||||||
const useSlots: typeof import('vue')['useSlots']
|
const useSlots: (typeof import('vue'))['useSlots'];
|
||||||
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
const useTemplateRef: (typeof import('vue'))['useTemplateRef'];
|
||||||
const watch: typeof import('vue')['watch']
|
const watch: (typeof import('vue'))['watch'];
|
||||||
const watchEffect: typeof import('vue')['watchEffect']
|
const watchEffect: (typeof import('vue'))['watchEffect'];
|
||||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
const watchPostEffect: (typeof import('vue'))['watchPostEffect'];
|
||||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
const watchSyncEffect: (typeof import('vue'))['watchSyncEffect'];
|
||||||
}
|
}
|
||||||
// for type re-export
|
// for type re-export
|
||||||
declare global {
|
declare global {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
export type {
|
||||||
import('vue')
|
Component,
|
||||||
|
ComponentPublicInstance,
|
||||||
|
ComputedRef,
|
||||||
|
DirectiveBinding,
|
||||||
|
ExtractDefaultPropTypes,
|
||||||
|
ExtractPropTypes,
|
||||||
|
ExtractPublicPropTypes,
|
||||||
|
InjectionKey,
|
||||||
|
PropType,
|
||||||
|
Ref,
|
||||||
|
MaybeRef,
|
||||||
|
MaybeRefOrGetter,
|
||||||
|
VNode,
|
||||||
|
WritableComputedRef
|
||||||
|
} from 'vue';
|
||||||
|
import('vue');
|
||||||
}
|
}
|
||||||
|
|||||||
20
build/entitlements.mac.plist
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.server</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.files.user-selected.read-write</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.files.downloads.read-write</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
build/icon.icns
Normal file
BIN
build/icon.ico
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
build/icon.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
13
build/installer.nsh
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# 设置 Windows 7 兼容性
|
||||||
|
ManifestDPIAware true
|
||||||
|
ManifestSupportedOS all
|
||||||
|
|
||||||
|
!macro customInit
|
||||||
|
# 检查系统版本
|
||||||
|
${If} ${AtLeastWin7}
|
||||||
|
# Windows 7 或更高版本
|
||||||
|
${Else}
|
||||||
|
MessageBox MB_OK|MB_ICONSTOP "此应用程序需要 Windows 7 或更高版本。"
|
||||||
|
Abort
|
||||||
|
${EndIf}
|
||||||
|
!macroend
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
{
|
|
||||||
"appId": "com.alger.music",
|
|
||||||
"productName": "AlgerMusic",
|
|
||||||
"artifactName": "${productName}_${version}_${arch}.${ext}",
|
|
||||||
"directories": {
|
|
||||||
"output": "dist_electron/mac"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"dist/**/*",
|
|
||||||
"package.json",
|
|
||||||
"app.js",
|
|
||||||
"electron/**/*",
|
|
||||||
"**/*",
|
|
||||||
"public/**/*",
|
|
||||||
"node_modules/**/*"
|
|
||||||
],
|
|
||||||
"mac": {
|
|
||||||
"icon": "public/icon.icns",
|
|
||||||
"target": [
|
|
||||||
{
|
|
||||||
"target": "dmg",
|
|
||||||
"arch": ["x64", "arm64"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"category": "public.app-category.music",
|
|
||||||
"darkModeSupport": true
|
|
||||||
},
|
|
||||||
"dmg": {
|
|
||||||
"title": "${productName} ${version}",
|
|
||||||
"icon": "public/icon.icns",
|
|
||||||
"contents": [
|
|
||||||
{
|
|
||||||
"x": 410,
|
|
||||||
"y": 150,
|
|
||||||
"type": "link",
|
|
||||||
"path": "/Applications"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"x": 130,
|
|
||||||
"y": 150,
|
|
||||||
"type": "file"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"window": {
|
|
||||||
"width": 540,
|
|
||||||
"height": 380
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"appId": "com.alger.music",
|
|
||||||
"productName": "AlgerMusic",
|
|
||||||
"artifactName": "${productName}_${version}_Setup_x86.${ext}",
|
|
||||||
"directories": {
|
|
||||||
"output": "dist_electron/win-x86"
|
|
||||||
},
|
|
||||||
"files": ["dist/**/*", "package.json", "app.js", "electron/**/*"],
|
|
||||||
"win": {
|
|
||||||
"icon": "public/icon.png",
|
|
||||||
"target": [
|
|
||||||
{
|
|
||||||
"target": "nsis",
|
|
||||||
"arch": ["ia32"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"extraFiles": [
|
|
||||||
{
|
|
||||||
"from": "installer/installer.nsh",
|
|
||||||
"to": "$INSTDIR"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nsis": {
|
|
||||||
"oneClick": false,
|
|
||||||
"language": "2052",
|
|
||||||
"allowToChangeInstallationDirectory": true,
|
|
||||||
"differentialPackage": true,
|
|
||||||
"shortcutName": "Alger Music"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"appId": "com.alger.music",
|
|
||||||
"productName": "AlgerMusic",
|
|
||||||
"artifactName": "${productName}_${version}_Setup_x64.${ext}",
|
|
||||||
"directories": {
|
|
||||||
"output": "dist_electron/win-x64"
|
|
||||||
},
|
|
||||||
"files": ["dist/**/*", "package.json", "app.js", "electron/**/*"],
|
|
||||||
"win": {
|
|
||||||
"icon": "public/icon.png",
|
|
||||||
"target": [
|
|
||||||
{
|
|
||||||
"target": "nsis",
|
|
||||||
"arch": ["x64"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"extraFiles": [
|
|
||||||
{
|
|
||||||
"from": "installer/installer.nsh",
|
|
||||||
"to": "$INSTDIR"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nsis": {
|
|
||||||
"oneClick": false,
|
|
||||||
"language": "2052",
|
|
||||||
"allowToChangeInstallationDirectory": true,
|
|
||||||
"differentialPackage": true,
|
|
||||||
"shortcutName": "Alger Music"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"appId": "com.alger.music",
|
|
||||||
"productName": "AlgerMusic",
|
|
||||||
"artifactName": "${productName}_${version}_Setup_arm64.${ext}",
|
|
||||||
"directories": {
|
|
||||||
"output": "dist_electron/win-arm64"
|
|
||||||
},
|
|
||||||
"files": ["dist/**/*", "package.json", "app.js", "electron/**/*", "!node_modules/**/*"],
|
|
||||||
"win": {
|
|
||||||
"icon": "public/icon.png",
|
|
||||||
"target": [
|
|
||||||
{
|
|
||||||
"target": "nsis",
|
|
||||||
"arch": ["arm64"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"extraFiles": [
|
|
||||||
{
|
|
||||||
"from": "installer/installer.nsh",
|
|
||||||
"to": "$INSTDIR"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nsis": {
|
|
||||||
"oneClick": false,
|
|
||||||
"language": "2052",
|
|
||||||
"allowToChangeInstallationDirectory": true,
|
|
||||||
"differentialPackage": true,
|
|
||||||
"shortcutName": "Alger Music"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
21
components.d.ts
vendored
@@ -2,19 +2,13 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
// Generated by unplugin-vue-components
|
// Generated by unplugin-vue-components
|
||||||
// Read more: https://github.com/vuejs/core/pull/3399
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
export {}
|
export {};
|
||||||
|
|
||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
Coffee: typeof import('./src/components/Coffee.vue')['default']
|
|
||||||
InstallAppModal: typeof import('./src/components/common/InstallAppModal.vue')['default']
|
|
||||||
MPop: typeof import('./src/components/common/MPop.vue')['default']
|
|
||||||
MusicList: typeof import('./src/components/MusicList.vue')['default']
|
|
||||||
MvPlayer: typeof import('./src/components/MvPlayer.vue')['default']
|
|
||||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||||
NButton: typeof import('naive-ui')['NButton']
|
NButton: typeof import('naive-ui')['NButton']
|
||||||
NButtonGroup: typeof import('naive-ui')['NButtonGroup']
|
|
||||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||||
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
||||||
@@ -24,27 +18,16 @@ declare module 'vue' {
|
|||||||
NEmpty: typeof import('naive-ui')['NEmpty']
|
NEmpty: typeof import('naive-ui')['NEmpty']
|
||||||
NImage: typeof import('naive-ui')['NImage']
|
NImage: typeof import('naive-ui')['NImage']
|
||||||
NInput: typeof import('naive-ui')['NInput']
|
NInput: typeof import('naive-ui')['NInput']
|
||||||
|
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||||
NLayout: typeof import('naive-ui')['NLayout']
|
NLayout: typeof import('naive-ui')['NLayout']
|
||||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||||
NModal: typeof import('naive-ui')['NModal']
|
NModal: typeof import('naive-ui')['NModal']
|
||||||
NPagination: typeof import('naive-ui')['NPagination']
|
|
||||||
NPopover: typeof import('naive-ui')['NPopover']
|
NPopover: typeof import('naive-ui')['NPopover']
|
||||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||||
NSlider: typeof import('naive-ui')['NSlider']
|
NSlider: typeof import('naive-ui')['NSlider']
|
||||||
NSpin: typeof import('naive-ui')['NSpin']
|
NSpin: typeof import('naive-ui')['NSpin']
|
||||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
|
||||||
NVirtualList: typeof import('naive-ui')['NVirtualList']
|
|
||||||
PlayBottom: typeof import('./src/components/common/PlayBottom.vue')['default']
|
|
||||||
PlayListsItem: typeof import('./src/components/common/PlayListsItem.vue')['default']
|
|
||||||
PlaylistType: typeof import('./src/components/PlaylistType.vue')['default']
|
|
||||||
PlayVideo: typeof import('./src/components/common/PlayVideo.vue')['default']
|
|
||||||
RecommendAlbum: typeof import('./src/components/RecommendAlbum.vue')['default']
|
|
||||||
RecommendSinger: typeof import('./src/components/RecommendSinger.vue')['default']
|
|
||||||
RecommendSonglist: typeof import('./src/components/RecommendSonglist.vue')['default']
|
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
SearchItem: typeof import('./src/components/common/SearchItem.vue')['default']
|
|
||||||
SongItem: typeof import('./src/components/common/SongItem.vue')['default']
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
dev-app-update.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
provider: generic
|
||||||
|
url: https://example.com/auto-updates
|
||||||
|
updaterCacheDirName: electron-lan-file-updater
|
||||||
BIN
docs/image.png
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
docs/image1.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
docs/image2.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
docs/image3.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
docs/image4.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 902 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 237 KiB |
|
Before Width: | Height: | Size: 478 KiB |
|
Before Width: | Height: | Size: 2.1 MiB |
|
Before Width: | Height: | Size: 2.6 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 502 KiB |
45
electron-builder.yml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
appId: com.electron.app
|
||||||
|
productName: electron-lan-file
|
||||||
|
directories:
|
||||||
|
buildResources: build
|
||||||
|
files:
|
||||||
|
- '!**/.vscode/*'
|
||||||
|
- '!src/*'
|
||||||
|
- '!electron.vite.config.{js,ts,mjs,cjs}'
|
||||||
|
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
|
||||||
|
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
|
||||||
|
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
|
||||||
|
asarUnpack:
|
||||||
|
- resources/**
|
||||||
|
win:
|
||||||
|
executableName: electron-lan-file
|
||||||
|
nsis:
|
||||||
|
artifactName: ${name}-${version}-setup.${ext}
|
||||||
|
shortcutName: ${productName}
|
||||||
|
uninstallDisplayName: ${productName}
|
||||||
|
createDesktopShortcut: always
|
||||||
|
mac:
|
||||||
|
entitlementsInherit: build/entitlements.mac.plist
|
||||||
|
extendInfo:
|
||||||
|
- NSCameraUsageDescription: Application requests access to the device's camera.
|
||||||
|
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
|
||||||
|
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||||
|
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||||
|
notarize: false
|
||||||
|
dmg:
|
||||||
|
artifactName: ${name}-${version}.${ext}
|
||||||
|
linux:
|
||||||
|
target:
|
||||||
|
- AppImage
|
||||||
|
- snap
|
||||||
|
- deb
|
||||||
|
maintainer: electronjs.org
|
||||||
|
category: Utility
|
||||||
|
appImage:
|
||||||
|
artifactName: ${name}-${version}.${ext}
|
||||||
|
npmRebuild: false
|
||||||
|
publish:
|
||||||
|
provider: generic
|
||||||
|
url: https://example.com/auto-updates
|
||||||
|
electronDownload:
|
||||||
|
mirror: https://npmmirror.com/mirrors/electron/
|
||||||
60
electron.vite.config.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
import { defineConfig, externalizeDepsPlugin } from 'electron-vite';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import AutoImport from 'unplugin-auto-import/vite';
|
||||||
|
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
|
||||||
|
import Components from 'unplugin-vue-components/vite';
|
||||||
|
import viteCompression from 'vite-plugin-compression';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
main: {
|
||||||
|
plugins: [externalizeDepsPlugin()]
|
||||||
|
},
|
||||||
|
preload: {
|
||||||
|
plugins: [externalizeDepsPlugin()]
|
||||||
|
},
|
||||||
|
renderer: {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': resolve('src/renderer'),
|
||||||
|
'@renderer': resolve('src/renderer')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
viteCompression(),
|
||||||
|
// VueDevTools(),
|
||||||
|
AutoImport({
|
||||||
|
imports: [
|
||||||
|
'vue',
|
||||||
|
{
|
||||||
|
'naive-ui': ['useDialog', 'useMessage', 'useNotification', 'useLoadingBar']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
Components({
|
||||||
|
resolvers: [NaiveUiResolver()]
|
||||||
|
})
|
||||||
|
],
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
// with options
|
||||||
|
[process.env.VITE_API_LOCAL as string]: {
|
||||||
|
target: process.env.VITE_API,
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(new RegExp(`^${process.env.VITE_API_LOCAL}`), '')
|
||||||
|
},
|
||||||
|
[process.env.VITE_API_MUSIC_PROXY as string]: {
|
||||||
|
target: process.env.VITE_API_MUSIC,
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(new RegExp(`^${process.env.VITE_API_MUSIC_PROXY}`), '')
|
||||||
|
},
|
||||||
|
[process.env.VITE_API_PROXY_MUSIC as string]: {
|
||||||
|
target: process.env.VITE_API_PROXY,
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(new RegExp(`^${process.env.VITE_API_PROXY_MUSIC}`), '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
// 开发环境配置
|
|
||||||
development: {
|
|
||||||
mainPort: 4488,
|
|
||||||
lyricPort: 4488,
|
|
||||||
},
|
|
||||||
// 生产环境配置
|
|
||||||
production: {
|
|
||||||
distPath: '../dist',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
const { contextBridge, ipcRenderer } = require('electron');
|
|
||||||
|
|
||||||
// 主进程通信
|
|
||||||
contextBridge.exposeInMainWorld('electronAPI', {
|
|
||||||
minimize: () => ipcRenderer.send('minimize-window'),
|
|
||||||
maximize: () => ipcRenderer.send('maximize-window'),
|
|
||||||
close: () => ipcRenderer.send('close-window'),
|
|
||||||
dragStart: (data) => ipcRenderer.send('drag-start', data),
|
|
||||||
miniTray: () => ipcRenderer.send('mini-tray'),
|
|
||||||
restart: () => ipcRenderer.send('restart'),
|
|
||||||
openLyric: () => ipcRenderer.send('open-lyric'),
|
|
||||||
sendLyric: (data) => ipcRenderer.send('send-lyric', data),
|
|
||||||
});
|
|
||||||
|
|
||||||
// 存储相关
|
|
||||||
contextBridge.exposeInMainWorld('electron', {
|
|
||||||
ipcRenderer: {
|
|
||||||
setStoreValue: (key, value) => ipcRenderer.send('setStore', key, value),
|
|
||||||
getStoreValue: (key) => ipcRenderer.sendSync('getStore', key),
|
|
||||||
on: (channel, func) => {
|
|
||||||
ipcRenderer.on(channel, (event, ...args) => func(...args));
|
|
||||||
},
|
|
||||||
once: (channel, func) => {
|
|
||||||
ipcRenderer.once(channel, (event, ...args) => func(...args));
|
|
||||||
},
|
|
||||||
send: (channel, data) => {
|
|
||||||
ipcRenderer.send(channel, data);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"isProxy": false,
|
|
||||||
"noAnimate": false,
|
|
||||||
"animationSpeed": 1,
|
|
||||||
"author": "Alger",
|
|
||||||
"authorUrl": "https://github.com/algerkong"
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
const { app, BrowserWindow } = require('electron');
|
|
||||||
const axios = require('axios');
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const AdmZip = require('adm-zip');
|
|
||||||
|
|
||||||
class Updater {
|
|
||||||
constructor(mainWindow) {
|
|
||||||
this.mainWindow = mainWindow;
|
|
||||||
this.updateUrl = 'http://your-server.com/update'; // 更新服务器地址
|
|
||||||
this.version = app.getVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查更新
|
|
||||||
async checkForUpdates() {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${this.updateUrl}/check`, {
|
|
||||||
params: {
|
|
||||||
version: this.version,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.data.hasUpdate) {
|
|
||||||
await this.downloadUpdate(response.data.downloadUrl);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('检查更新失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 下载更新
|
|
||||||
async downloadUpdate(downloadUrl) {
|
|
||||||
try {
|
|
||||||
const response = await axios({
|
|
||||||
url: downloadUrl,
|
|
||||||
method: 'GET',
|
|
||||||
responseType: 'arraybuffer',
|
|
||||||
});
|
|
||||||
|
|
||||||
const tempPath = path.join(app.getPath('temp'), 'update.zip');
|
|
||||||
fs.writeFileSync(tempPath, response.data);
|
|
||||||
|
|
||||||
await this.extractUpdate(tempPath);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('下载更新失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解压更新
|
|
||||||
async extractUpdate(zipPath) {
|
|
||||||
try {
|
|
||||||
const zip = new AdmZip(zipPath);
|
|
||||||
const targetPath = path.join(__dirname, '../dist'); // 前端文件目录
|
|
||||||
|
|
||||||
// 解压文件
|
|
||||||
zip.extractAllTo(targetPath, true);
|
|
||||||
|
|
||||||
// 删除临时文件
|
|
||||||
fs.unlinkSync(zipPath);
|
|
||||||
|
|
||||||
// 刷新页面
|
|
||||||
this.mainWindow.webContents.reload();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('解压更新失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Updater;
|
|
||||||
57
index.html
@@ -1,57 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<link rel="icon" href="/favicon.ico" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
|
||||||
|
|
||||||
<!-- SEO 元数据 -->
|
|
||||||
<title>网抑云音乐 | AlgerKong | AlgerMusicPlayer</title>
|
|
||||||
<meta name="description"
|
|
||||||
content="AlgerMusicPlayer 网抑云音乐 基于 网易云音乐API 的一款免费的在线音乐播放器,支持在线播放、歌词显示、音乐下载等功能。提供海量音乐资源,让您随时随地享受音乐。" />
|
|
||||||
<meta name="keywords" content="AlgerMusic, AlgerMusicPlayer, 网抑云, 音乐播放器, 在线音乐, 免费音乐, 歌词显示, 音乐下载, AlgerKong, 网易云音乐" />
|
|
||||||
|
|
||||||
<!-- 作者信息 -->
|
|
||||||
<meta name="author" content="AlgerKong" />
|
|
||||||
<meta name="author-url" content="https://github.com/algerkong" />
|
|
||||||
|
|
||||||
<!-- PWA 相关 -->
|
|
||||||
<link rel="manifest" href="/manifest.json" />
|
|
||||||
<meta name="theme-color" content="#000000" />
|
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
|
||||||
<meta name="apple-mobile-web-app-title" content="网抑云音乐" />
|
|
||||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
||||||
|
|
||||||
<!-- 资源预加载 -->
|
|
||||||
<link rel="preload" href="/icon/iconfont.css" as="style" />
|
|
||||||
<link rel="preload" href="/css/animate.css" as="style" />
|
|
||||||
<link rel="preload" href="/css/base.css" as="style" />
|
|
||||||
|
|
||||||
<!-- 样式表 -->
|
|
||||||
<link rel="stylesheet" href="/icon/iconfont.css" />
|
|
||||||
<link rel="stylesheet" href="/css/animate.css" />
|
|
||||||
<link rel="stylesheet" href="/css/base.css" />
|
|
||||||
<script defer src="https://cn.vercount.one/js"></script>
|
|
||||||
|
|
||||||
<!-- 动画配置 -->
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--animate-delay: 0.5s;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
|
|
||||||
<div style="display: none;">
|
|
||||||
Total Page View <span id="vercount_value_page_pv">Loading</span>
|
|
||||||
Total Visits <span id="vercount_value_site_pv">Loading</span>
|
|
||||||
Site Total Visitors <span id="vercount_value_site_uv">Loading</span>
|
|
||||||
</div>
|
|
||||||
<script type="module" src="/src/main.ts"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
164
package.json
@@ -1,66 +1,166 @@
|
|||||||
{
|
{
|
||||||
"name": "alger-music",
|
"name": "AlgerMusicPlayer",
|
||||||
"version": "2.4.0",
|
"version": "3.9.3",
|
||||||
"description": "这是一个用于音乐播放的应用程序。",
|
"description": "Alger Music Player",
|
||||||
"author": "Alger <algerkc@qq.com>",
|
"author": "Alger <algerkc@qq.com>",
|
||||||
"main": "app.js",
|
"main": "./out/main/index.js",
|
||||||
|
"homepage": "https://github.com/algerkong/AlgerMusicPlayer",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"format": "prettier --write .",
|
||||||
"build": "cross-env NODE_ENV=production vite build",
|
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix",
|
||||||
"serve": "vite preview",
|
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
|
||||||
"start": "cross-env NODE_ENV=development electron .",
|
"typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false",
|
||||||
"lint": "eslint --ext .vue,.js,.jsx,.ts,.tsx ./ --max-warnings 0",
|
"typecheck": "npm run typecheck:node && npm run typecheck:web",
|
||||||
"b:win:x64": "cross-env NODE_ENV=production electron-builder --config ./build/win64.json",
|
"start": "electron-vite preview",
|
||||||
"b:win:x86": "cross-env NODE_ENV=production electron-builder --config ./build/win32.json",
|
"dev": "electron-vite dev",
|
||||||
"b:win:arm": "cross-env NODE_ENV=production electron-builder --config ./build/winarm64.json",
|
"build": "npm run typecheck && electron-vite build",
|
||||||
"b:mac": "cross-env NODE_ENV=production npm run build && electron-builder --config ./build/mac.json",
|
"postinstall": "electron-builder install-app-deps",
|
||||||
"b:win": "cross-env NODE_ENV=production npm run build && npm run b:win:x64 && npm run b:win:x86 && npm run b:win:arm"
|
"build:unpack": "npm run build && electron-builder --dir",
|
||||||
|
"build:win": "npm run build && electron-builder --win",
|
||||||
|
"build:mac": "npm run build && electron-builder --mac",
|
||||||
|
"build:linux": "npm run build && electron-builder --linux"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/howler": "^2.2.12",
|
"@electron-toolkit/preload": "^3.0.0",
|
||||||
|
"@electron-toolkit/utils": "^3.0.0",
|
||||||
|
"@unblockneteasemusic/server": "^0.27.8-patch.1",
|
||||||
"electron-store": "^8.1.0",
|
"electron-store": "^8.1.0",
|
||||||
"howler": "^2.2.4"
|
"electron-updater": "^6.1.7",
|
||||||
|
"font-list": "^1.5.1",
|
||||||
|
"netease-cloud-music-api-alger": "^4.25.0",
|
||||||
|
"vue-i18n": "9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@electron-toolkit/eslint-config": "^1.0.2",
|
||||||
|
"@electron-toolkit/eslint-config-ts": "^2.0.0",
|
||||||
|
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||||
|
"@rushstack/eslint-patch": "^1.10.3",
|
||||||
"@tailwindcss/postcss7-compat": "^2.2.4",
|
"@tailwindcss/postcss7-compat": "^2.2.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
"@types/howler": "^2.2.12",
|
||||||
"@typescript-eslint/parser": "^6.21.0",
|
"@types/node": "^20.14.8",
|
||||||
"@vitejs/plugin-vue": "^5.1.3",
|
"@types/tinycolor2": "^1.4.6",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
|
"@typescript-eslint/parser": "^7.0.0",
|
||||||
|
"@vitejs/plugin-vue": "^5.0.5",
|
||||||
"@vue/compiler-sfc": "^3.5.0",
|
"@vue/compiler-sfc": "^3.5.0",
|
||||||
|
"@vue/eslint-config-prettier": "^9.0.0",
|
||||||
"@vue/eslint-config-typescript": "^13.0.0",
|
"@vue/eslint-config-typescript": "^13.0.0",
|
||||||
"@vue/runtime-core": "^3.5.0",
|
"@vue/runtime-core": "^3.5.0",
|
||||||
"@vueuse/core": "^11.0.3",
|
"@vueuse/core": "^11.0.3",
|
||||||
"@vueuse/electron": "^11.0.3",
|
"@vueuse/electron": "^11.0.3",
|
||||||
|
"animate.css": "^4.1.1",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"electron": "^32.2.7",
|
"electron": "^34.0.0",
|
||||||
"electron-builder": "^25.0.5",
|
"electron-builder": "^25.1.8",
|
||||||
"eslint": "^8.56.0",
|
"electron-vite": "^2.3.0",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-airbnb-base": "^15.0.0",
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-simple-import-sort": "^12.0.0",
|
"eslint-plugin-simple-import-sort": "^12.0.0",
|
||||||
"eslint-plugin-vue": "^9.21.1",
|
"eslint-plugin-vue": "^9.26.0",
|
||||||
"eslint-plugin-vue-scoped-css": "^2.7.2",
|
"eslint-plugin-vue-scoped-css": "^2.7.2",
|
||||||
|
"howler": "^2.2.4",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"naive-ui": "^2.39.0",
|
"marked": "^15.0.4",
|
||||||
|
"naive-ui": "^2.41.0",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.2",
|
||||||
"remixicon": "^4.2.0",
|
"remixicon": "^4.2.0",
|
||||||
"sass": "^1.82.0",
|
"sass": "^1.83.4",
|
||||||
"tailwindcss": "^3.4.15",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "^5.5.4",
|
"tinycolor2": "^1.6.0",
|
||||||
|
"typescript": "^5.5.2",
|
||||||
"unplugin-auto-import": "^0.18.2",
|
"unplugin-auto-import": "^0.18.2",
|
||||||
"unplugin-vue-components": "^0.27.4",
|
"unplugin-vue-components": "^0.27.4",
|
||||||
"vfonts": "^0.1.0",
|
"vfonts": "^0.1.0",
|
||||||
"vite": "^5.4.3",
|
"vite": "^5.3.1",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-vue-devtools": "7.4.0",
|
"vite-plugin-vue-devtools": "7.4.0",
|
||||||
"vue": "^3.5.0",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.4.3",
|
"vue-router": "^4.5.0",
|
||||||
"vue-tsc": "^2.1.4",
|
"vue-tsc": "^2.0.22",
|
||||||
"vuex": "^4.1.0"
|
"vuex": "^4.1.0"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"appId": "com.alger.music",
|
||||||
|
"productName": "AlgerMusicPlayer",
|
||||||
|
"publish": [
|
||||||
|
{
|
||||||
|
"provider": "github",
|
||||||
|
"owner": "algerkong",
|
||||||
|
"repo": "AlgerMusicPlayer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mac": {
|
||||||
|
"icon": "resources/icon.icns",
|
||||||
|
"target": [
|
||||||
|
{
|
||||||
|
"target": "dmg",
|
||||||
|
"arch": [
|
||||||
|
"universal"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"artifactName": "${productName}-${version}-mac-${arch}.${ext}",
|
||||||
|
"darkModeSupport": true,
|
||||||
|
"hardenedRuntime": false,
|
||||||
|
"gatekeeperAssess": false,
|
||||||
|
"entitlements": "build/entitlements.mac.plist",
|
||||||
|
"entitlementsInherit": "build/entitlements.mac.plist",
|
||||||
|
"notarize": false,
|
||||||
|
"identity": null,
|
||||||
|
"type": "distribution",
|
||||||
|
"binaries": [
|
||||||
|
"Contents/MacOS/AlgerMusicPlayer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"win": {
|
||||||
|
"icon": "resources/favicon.ico",
|
||||||
|
"target": [
|
||||||
|
{
|
||||||
|
"target": "nsis",
|
||||||
|
"arch": [
|
||||||
|
"x64",
|
||||||
|
"ia32"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"artifactName": "${productName}-${version}-win-${arch}.${ext}",
|
||||||
|
"requestedExecutionLevel": "asInvoker"
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"icon": "resources/icon.png",
|
||||||
|
"target": [
|
||||||
|
{
|
||||||
|
"target": "AppImage",
|
||||||
|
"arch": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": "deb",
|
||||||
|
"arch": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"artifactName": "${productName}-${version}-linux-${arch}.${ext}",
|
||||||
|
"category": "Audio",
|
||||||
|
"maintainer": "Alger <algerkc@qq.com>"
|
||||||
|
},
|
||||||
|
"nsis": {
|
||||||
|
"oneClick": false,
|
||||||
|
"allowToChangeInstallationDirectory": true,
|
||||||
|
"installerIcon": "resources/favicon.ico",
|
||||||
|
"uninstallerIcon": "resources/favicon.ico",
|
||||||
|
"createDesktopShortcut": true,
|
||||||
|
"createStartMenuShortcut": true,
|
||||||
|
"shortcutName": "AlgerMusicPlayer",
|
||||||
|
"include": "build/installer.nsh"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {}
|
||||||
},
|
}
|
||||||
}
|
};
|
||||||
|
|||||||
7
public/css/animate.css
vendored
@@ -1,7 +0,0 @@
|
|||||||
body{
|
|
||||||
background-color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.n-popover:has(.music-play){
|
|
||||||
border-radius: 1.5rem !important;
|
|
||||||
}
|
|
||||||
BIN
public/icon1.png
|
Before Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 626 B After Width: | Height: | Size: 626 B |
49
src/App.vue
@@ -1,49 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="app-container" :class="{ mobile: isMobile, noElectron: !isElectron }">
|
|
||||||
<n-config-provider :theme="darkTheme">
|
|
||||||
<n-dialog-provider>
|
|
||||||
<n-message-provider>
|
|
||||||
<router-view></router-view>
|
|
||||||
</n-message-provider>
|
|
||||||
</n-dialog-provider>
|
|
||||||
</n-config-provider>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { darkTheme } from 'naive-ui';
|
|
||||||
import { onMounted } from 'vue';
|
|
||||||
|
|
||||||
import { isElectron } from '@/hooks/MusicHook';
|
|
||||||
import homeRouter from '@/router/home';
|
|
||||||
import store from '@/store';
|
|
||||||
|
|
||||||
import { isMobile } from './utils';
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
store.dispatch('initializeSettings');
|
|
||||||
if (isMobile.value) {
|
|
||||||
store.commit(
|
|
||||||
'setMenus',
|
|
||||||
homeRouter.filter((item) => item.meta.isMobile),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.app-container {
|
|
||||||
@apply h-full w-full;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile {
|
|
||||||
.text-base {
|
|
||||||
font-size: 14px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.html:has(.mobile) {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { ILyric } from '@/type/lyric';
|
|
||||||
import { IPlayMusicUrl } from '@/type/music';
|
|
||||||
import request from '@/utils/request';
|
|
||||||
import requestMusic from '@/utils/request_music';
|
|
||||||
// 根据音乐Id获取音乐播放URl
|
|
||||||
export const getMusicUrl = (id: number) => {
|
|
||||||
return request.get<IPlayMusicUrl>('/song/url', { params: { id } });
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取歌曲详情
|
|
||||||
export const getMusicDetail = (ids: Array<number>) => {
|
|
||||||
return request.get('/song/detail', { params: { ids: ids.join(',') } });
|
|
||||||
};
|
|
||||||
|
|
||||||
// 根据音乐Id获取音乐歌词
|
|
||||||
export const getMusicLrc = (id: number) => {
|
|
||||||
return request.get<ILyric>('/lyric', { params: { id } });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getParsingMusicUrl = (id: number) => {
|
|
||||||
return requestMusic.get<any>('/music', { params: { id } });
|
|
||||||
};
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="relative inline-block">
|
|
||||||
<n-popover trigger="hover" placement="top" :show-arrow="true" :raw="true" :delay="100">
|
|
||||||
<template #trigger>
|
|
||||||
<slot>
|
|
||||||
<n-button
|
|
||||||
quaternary
|
|
||||||
class="inline-flex items-center gap-2 px-4 py-2 transition-all duration-300 hover:-translate-y-0.5"
|
|
||||||
>
|
|
||||||
请我喝咖啡
|
|
||||||
</n-button>
|
|
||||||
</slot>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="p-6 bg-black rounded-lg shadow-lg">
|
|
||||||
<div class="flex gap-10">
|
|
||||||
<div class="flex flex-col items-center gap-2">
|
|
||||||
<n-image :src="alipayQR" alt="支付宝收款码" class="w-32 h-32 rounded-lg cursor-none" preview-disabled />
|
|
||||||
<span class="text-sm text-gray-100">支付宝</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col items-center gap-2">
|
|
||||||
<n-image :src="wechatQR" alt="微信收款码" class="w-32 h-32 rounded-lg cursor-none" preview-disabled />
|
|
||||||
<span class="text-sm text-gray-100">微信支付</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-4">
|
|
||||||
<p class="text-sm text-gray-100 text-center cursor-pointer hover:text-green-500" @click="copyQQ">
|
|
||||||
QQ群:789288579
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-popover>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { NButton, NImage, NPopover } from 'naive-ui';
|
|
||||||
|
|
||||||
const message = useMessage();
|
|
||||||
const copyQQ = () => {
|
|
||||||
navigator.clipboard.writeText('789288579');
|
|
||||||
message.success('已复制到剪贴板');
|
|
||||||
};
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
alipayQR: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
wechatQR: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
<template>
|
|
||||||
<n-modal v-model:show="showModal" preset="dialog" :show-icon="false" :mask-closable="true" class="install-app-modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<div class="app-icon">
|
|
||||||
<img src="@/assets/logo.png" alt="App Icon" />
|
|
||||||
</div>
|
|
||||||
<div class="app-info">
|
|
||||||
<h2 class="app-name">Alger Music Player {{ config.version }}</h2>
|
|
||||||
<p class="app-desc mb-2">在桌面安装应用,获得更好的体验</p>
|
|
||||||
<n-checkbox v-model:checked="noPrompt">不再提示</n-checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-actions">
|
|
||||||
<n-button class="cancel-btn" @click="closeModal">暂不安装</n-button>
|
|
||||||
<n-button type="primary" class="install-btn" @click="handleInstall">立即安装</n-button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-desc mt-4 text-center">
|
|
||||||
<p class="text-xs text-gray-400">
|
|
||||||
下载遇到问题?去
|
|
||||||
<a class="text-green-500" target="_blank" href="https://github.com/algerkong/AlgerMusicPlayer/releases"
|
|
||||||
>GitHub</a
|
|
||||||
>
|
|
||||||
下载最新版本
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import config from '@/../package.json';
|
|
||||||
import { isMobile } from '@/utils';
|
|
||||||
|
|
||||||
const showModal = ref(false);
|
|
||||||
const isElectron = ref((window as any).electron !== undefined);
|
|
||||||
const noPrompt = ref(false);
|
|
||||||
|
|
||||||
const closeModal = () => {
|
|
||||||
showModal.value = false;
|
|
||||||
if (noPrompt.value) {
|
|
||||||
localStorage.setItem('installPromptDismissed', 'true');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// 如果是 electron 环境,不显示安装提示
|
|
||||||
if (isElectron.value || isMobile.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否已经点击过"暂不安装"
|
|
||||||
const isDismissed = localStorage.getItem('installPromptDismissed') === 'true';
|
|
||||||
if (isDismissed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showModal.value = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleInstall = async (): Promise<void> => {
|
|
||||||
const { userAgent } = navigator;
|
|
||||||
console.log('userAgent', userAgent);
|
|
||||||
const isMac: boolean = userAgent.includes('Mac');
|
|
||||||
const isWindows: boolean = userAgent.includes('Win');
|
|
||||||
const isARM: boolean = userAgent.includes('ARM') || userAgent.includes('arm') || userAgent.includes('OS X');
|
|
||||||
const isX64: boolean = userAgent.includes('x86_64') || userAgent.includes('Win64') || userAgent.includes('WOW64');
|
|
||||||
const isX86: boolean =
|
|
||||||
!isX64 && (userAgent.includes('i686') || userAgent.includes('i386') || userAgent.includes('Win32'));
|
|
||||||
|
|
||||||
const getDownloadUrl = (os: string, arch: string): string => {
|
|
||||||
const version = config.version as string;
|
|
||||||
const setup = os !== 'mac' ? 'Setup_' : '';
|
|
||||||
return `https://gh.llkk.cc/https://github.com/algerkong/AlgerMusicPlayer/releases/download/${version}/AlgerMusic_${version}_${setup}${arch}.${os === 'mac' ? 'dmg' : 'exe'}`;
|
|
||||||
};
|
|
||||||
const osType: string | null = isMac ? 'mac' : isWindows ? 'windows' : null;
|
|
||||||
const archType: string | null = isARM ? 'arm64' : isX64 ? 'x64' : isX86 ? 'x86' : null;
|
|
||||||
|
|
||||||
const downloadUrl: string | null = osType && archType ? getDownloadUrl(osType, archType) : null;
|
|
||||||
|
|
||||||
window.open(downloadUrl || 'https://github.com/algerkong/AlgerMusicPlayer/releases', '_blank');
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.install-app-modal {
|
|
||||||
:deep(.n-modal) {
|
|
||||||
@apply max-w-sm;
|
|
||||||
}
|
|
||||||
.modal-content {
|
|
||||||
@apply p-4 pb-0;
|
|
||||||
.modal-header {
|
|
||||||
@apply flex items-center mb-6;
|
|
||||||
.app-icon {
|
|
||||||
@apply w-20 h-20 mr-4 rounded-2xl overflow-hidden;
|
|
||||||
img {
|
|
||||||
@apply w-full h-full object-cover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.app-info {
|
|
||||||
@apply flex-1;
|
|
||||||
.app-name {
|
|
||||||
@apply text-xl font-bold mb-1;
|
|
||||||
}
|
|
||||||
.app-desc {
|
|
||||||
@apply text-sm text-gray-400;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.modal-actions {
|
|
||||||
@apply flex gap-3 mt-4;
|
|
||||||
.n-button {
|
|
||||||
@apply flex-1;
|
|
||||||
}
|
|
||||||
.cancel-btn {
|
|
||||||
@apply bg-gray-800 text-gray-300 border-none;
|
|
||||||
&:hover {
|
|
||||||
@apply bg-gray-700;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.install-btn {
|
|
||||||
@apply bg-green-600 border-none;
|
|
||||||
&:hover {
|
|
||||||
@apply bg-green-500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { setAnimationClass } from '@/utils';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
showPop: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
showClose: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const musicFullClass = computed(() => {
|
|
||||||
if (props.showPop) {
|
|
||||||
return setAnimationClass('animate__fadeInUp');
|
|
||||||
}
|
|
||||||
return setAnimationClass('animate__fadeOutDown');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-show="props.showPop" class="pop-page" :class="musicFullClass">
|
|
||||||
<i v-if="props.showClose" class="iconfont icon-icon_error close"></i>
|
|
||||||
<img src="http://code.myalger.top/2000*2000.jpg,f054f0,0f2255" />
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.pop-page {
|
|
||||||
height: 800px;
|
|
||||||
@apply absolute top-4 left-0 w-full;
|
|
||||||
background-color: #000000f0;
|
|
||||||
.close {
|
|
||||||
@apply absolute top-4 right-4 cursor-pointer text-white text-3xl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
<template>
|
|
||||||
<n-drawer :show="show" height="100vh" placement="bottom" :z-index="999999999">
|
|
||||||
<div class="mv-detail">
|
|
||||||
<video :src="url" controls autoplay></video>
|
|
||||||
<div class="mv-detail-title">
|
|
||||||
<div class="title">{{ title }}</div>
|
|
||||||
<button @click="close">
|
|
||||||
<i class="iconfont icon-xiasanjiaoxing"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-drawer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useStore } from 'vuex';
|
|
||||||
|
|
||||||
import { audioService } from '@/services/audioService';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
show: boolean;
|
|
||||||
title: string;
|
|
||||||
url: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const store = useStore();
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.show,
|
|
||||||
(val) => {
|
|
||||||
if (val) {
|
|
||||||
store.commit('setIsPlay', false);
|
|
||||||
store.commit('setPlayMusic', false);
|
|
||||||
audioService.getCurrentSound()?.pause();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:show']);
|
|
||||||
|
|
||||||
const close = () => {
|
|
||||||
emit('update:show', false);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.mv-detail {
|
|
||||||
@apply w-full h-full bg-black relative;
|
|
||||||
|
|
||||||
&-title {
|
|
||||||
@apply absolute w-full left-0 flex justify-between h-16 px-6 py-2 text-xl font-bold items-center z-50 transition-all duration-300 ease-in-out -top-24;
|
|
||||||
background: linear-gradient(0, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 100%);
|
|
||||||
button .icon-xiasanjiaoxing {
|
|
||||||
@apply text-3xl;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
@apply text-green-400;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
video {
|
|
||||||
@apply w-full h-full;
|
|
||||||
}
|
|
||||||
video:hover + .mv-detail-title {
|
|
||||||
@apply top-0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mv-detail-title:hover {
|
|
||||||
@apply top-0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="song-item" :class="{ 'song-mini': mini, 'song-list': list }">
|
|
||||||
<n-image
|
|
||||||
v-if="item.picUrl"
|
|
||||||
ref="songImg"
|
|
||||||
:src="getImgUrl(item.picUrl, '40y40')"
|
|
||||||
class="song-item-img"
|
|
||||||
preview-disabled
|
|
||||||
:img-props="{
|
|
||||||
crossorigin: 'anonymous',
|
|
||||||
}"
|
|
||||||
@load="imageLoad"
|
|
||||||
/>
|
|
||||||
<div class="song-item-content">
|
|
||||||
<div v-if="list" class="song-item-content-wrapper">
|
|
||||||
<n-ellipsis class="song-item-content-title text-ellipsis" line-clamp="1">{{ item.name }}</n-ellipsis>
|
|
||||||
<div class="song-item-content-divider">-</div>
|
|
||||||
<n-ellipsis class="song-item-content-name text-ellipsis" line-clamp="1">
|
|
||||||
<span v-for="(artists, artistsindex) in item.ar || item.song.artists" :key="artistsindex"
|
|
||||||
>{{ artists.name }}{{ artistsindex < (item.ar || item.song.artists).length - 1 ? ' / ' : '' }}</span
|
|
||||||
>
|
|
||||||
</n-ellipsis>
|
|
||||||
</div>
|
|
||||||
<template v-else>
|
|
||||||
<div class="song-item-content-title">
|
|
||||||
<n-ellipsis class="text-ellipsis" line-clamp="1">{{ item.name }}</n-ellipsis>
|
|
||||||
</div>
|
|
||||||
<div class="song-item-content-name">
|
|
||||||
<n-ellipsis class="text-ellipsis" line-clamp="1">
|
|
||||||
<span v-for="(artists, artistsindex) in item.ar || item.song.artists" :key="artistsindex"
|
|
||||||
>{{ artists.name }}{{ artistsindex < (item.ar || item.song.artists).length - 1 ? ' / ' : '' }}</span
|
|
||||||
>
|
|
||||||
</n-ellipsis>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="song-item-operating" :class="{ 'song-item-operating-list': list }">
|
|
||||||
<div v-if="favorite" class="song-item-operating-like">
|
|
||||||
<i class="iconfont icon-likefill" :class="{ 'like-active': isFavorite }" @click.stop="toggleFavorite"></i>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="song-item-operating-play bg-black animate__animated"
|
|
||||||
:class="{ 'bg-green-600': isPlaying, animate__flipInY: playLoading }"
|
|
||||||
@click="playMusicEvent(item)"
|
|
||||||
>
|
|
||||||
<i v-if="isPlaying && play" class="iconfont icon-stop"></i>
|
|
||||||
<i v-else class="iconfont icon-playfill"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { computed, useTemplateRef } from 'vue';
|
|
||||||
import { useStore } from 'vuex';
|
|
||||||
|
|
||||||
import { audioService } from '@/services/audioService';
|
|
||||||
import type { SongResult } from '@/type/music';
|
|
||||||
import { getImgUrl } from '@/utils';
|
|
||||||
import { getImageBackground } from '@/utils/linearColor';
|
|
||||||
|
|
||||||
const props = withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
item: SongResult;
|
|
||||||
mini?: boolean;
|
|
||||||
list?: boolean;
|
|
||||||
favorite?: boolean;
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
mini: false,
|
|
||||||
list: false,
|
|
||||||
favorite: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const store = useStore();
|
|
||||||
|
|
||||||
const play = computed(() => store.state.play as boolean);
|
|
||||||
|
|
||||||
const playMusic = computed(() => store.state.playMusic);
|
|
||||||
|
|
||||||
const playLoading = computed(() => playMusic.value.id === props.item.id && playMusic.value.playLoading);
|
|
||||||
|
|
||||||
// 判断是否为正在播放的音乐
|
|
||||||
const isPlaying = computed(() => {
|
|
||||||
return playMusic.value.id === props.item.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
const emits = defineEmits(['play']);
|
|
||||||
|
|
||||||
const songImageRef = useTemplateRef('songImg');
|
|
||||||
|
|
||||||
const imageLoad = async () => {
|
|
||||||
if (!songImageRef.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { backgroundColor } = await getImageBackground(
|
|
||||||
(songImageRef.value as any).imageRef as unknown as HTMLImageElement,
|
|
||||||
);
|
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
|
||||||
props.item.backgroundColor = backgroundColor;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 播放音乐 设置音乐详情 打开音乐底栏
|
|
||||||
const playMusicEvent = async (item: SongResult) => {
|
|
||||||
if (playMusic.value.id === item.id) {
|
|
||||||
if (play.value) {
|
|
||||||
store.commit('setPlayMusic', false);
|
|
||||||
audioService.getCurrentSound()?.pause();
|
|
||||||
} else {
|
|
||||||
store.commit('setPlayMusic', true);
|
|
||||||
audioService.getCurrentSound()?.play();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await store.commit('setPlay', item);
|
|
||||||
store.commit('setIsPlay', true);
|
|
||||||
emits('play', item);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 判断是否已收藏
|
|
||||||
const isFavorite = computed(() => {
|
|
||||||
return store.state.favoriteList.includes(props.item.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 切换收藏状态
|
|
||||||
const toggleFavorite = async (e: Event) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (isFavorite.value) {
|
|
||||||
store.commit('removeFromFavorite', props.item.id);
|
|
||||||
} else {
|
|
||||||
store.commit('addToFavorite', props.item.id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
// 配置文字不可选中
|
|
||||||
.text-ellipsis {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.song-item {
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
@apply rounded-3xl p-3 flex items-center hover:bg-gray-800 transition;
|
|
||||||
&-img {
|
|
||||||
@apply w-12 h-12 rounded-2xl mr-4;
|
|
||||||
}
|
|
||||||
&-content {
|
|
||||||
@apply flex-1;
|
|
||||||
&-title {
|
|
||||||
@apply text-base text-white;
|
|
||||||
}
|
|
||||||
&-name {
|
|
||||||
@apply text-xs;
|
|
||||||
@apply text-gray-400;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&-operating {
|
|
||||||
@apply flex items-center rounded-full border border-gray-700 ml-4;
|
|
||||||
background-color: #0d0d0d;
|
|
||||||
.iconfont {
|
|
||||||
@apply text-xl;
|
|
||||||
}
|
|
||||||
.icon-likefill {
|
|
||||||
color: #868686;
|
|
||||||
@apply text-xl hover:text-red-600 transition;
|
|
||||||
}
|
|
||||||
&-like {
|
|
||||||
@apply mr-2 cursor-pointer ml-4;
|
|
||||||
}
|
|
||||||
.like-active {
|
|
||||||
@apply text-red-600;
|
|
||||||
}
|
|
||||||
&-play {
|
|
||||||
@apply cursor-pointer border border-gray-500 rounded-full w-10 h-10 flex justify-center items-center hover:bg-green-600 transition;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.song-mini {
|
|
||||||
@apply p-2 rounded-2xl;
|
|
||||||
.song-item {
|
|
||||||
@apply p-0;
|
|
||||||
&-img {
|
|
||||||
@apply w-10 h-10 mr-2;
|
|
||||||
}
|
|
||||||
&-content {
|
|
||||||
@apply flex-1;
|
|
||||||
&-title {
|
|
||||||
@apply text-sm;
|
|
||||||
}
|
|
||||||
&-name {
|
|
||||||
@apply text-xs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&-operating {
|
|
||||||
@apply pl-2;
|
|
||||||
.iconfont {
|
|
||||||
@apply text-base;
|
|
||||||
}
|
|
||||||
&-like {
|
|
||||||
@apply mr-1 ml-1;
|
|
||||||
}
|
|
||||||
&-play {
|
|
||||||
@apply w-8 h-8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.song-list {
|
|
||||||
@apply p-2 rounded-lg hover:bg-gray-800/50 border border-gray-800/50 mb-2;
|
|
||||||
.song-item-img {
|
|
||||||
@apply w-10 h-10 rounded-lg mr-3;
|
|
||||||
}
|
|
||||||
.song-item-content {
|
|
||||||
@apply flex items-center flex-1;
|
|
||||||
&-wrapper {
|
|
||||||
@apply flex items-center flex-1 text-sm;
|
|
||||||
}
|
|
||||||
&-title {
|
|
||||||
@apply text-white flex-shrink-0 max-w-[45%];
|
|
||||||
}
|
|
||||||
&-divider {
|
|
||||||
@apply mx-2 text-gray-400;
|
|
||||||
}
|
|
||||||
&-name {
|
|
||||||
@apply text-gray-400 flex-1 min-w-0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.song-item-operating {
|
|
||||||
@apply flex items-center gap-2;
|
|
||||||
&-like {
|
|
||||||
@apply cursor-pointer hover:scale-110 transition-transform;
|
|
||||||
.iconfont {
|
|
||||||
@apply text-base text-gray-400 hover:text-red-500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&-play {
|
|
||||||
@apply w-7 h-7 cursor-pointer hover:scale-110 transition-transform;
|
|
||||||
.iconfont {
|
|
||||||
@apply text-base;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
10
src/electron.d.ts
vendored
@@ -1,10 +0,0 @@
|
|||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
electronAPI: {
|
|
||||||
minimize: () => void;
|
|
||||||
maximize: () => void;
|
|
||||||
close: () => void;
|
|
||||||
dragStart: () => void;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
5
src/i18n/lang/en-US/artist.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export default {
|
||||||
|
hotSongs: 'Hot Songs',
|
||||||
|
albums: 'Albums',
|
||||||
|
description: 'Artist Introduction'
|
||||||
|
};
|
||||||
47
src/i18n/lang/en-US/common.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
export default {
|
||||||
|
play: 'Play',
|
||||||
|
next: 'Next',
|
||||||
|
previous: 'Previous',
|
||||||
|
volume: 'Volume',
|
||||||
|
settings: 'Settings',
|
||||||
|
search: 'Search',
|
||||||
|
loading: 'Loading...',
|
||||||
|
loadingMore: 'Loading more...',
|
||||||
|
alipay: 'Alipay',
|
||||||
|
wechat: 'WeChat Pay',
|
||||||
|
on: 'On',
|
||||||
|
off: 'Off',
|
||||||
|
show: 'Show',
|
||||||
|
hide: 'Hide',
|
||||||
|
confirm: 'Confirm',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
configure: 'Configure',
|
||||||
|
open: 'Open',
|
||||||
|
modify: 'Modify',
|
||||||
|
success: 'Operation Successful',
|
||||||
|
error: 'Operation Failed',
|
||||||
|
warning: 'Warning',
|
||||||
|
info: 'Info',
|
||||||
|
save: 'Save',
|
||||||
|
delete: 'Delete',
|
||||||
|
refresh: 'Refresh',
|
||||||
|
retry: 'Retry',
|
||||||
|
validation: {
|
||||||
|
required: 'This field is required',
|
||||||
|
invalidInput: 'Invalid input',
|
||||||
|
selectRequired: 'Please select an option',
|
||||||
|
numberRange: 'Please enter a number between {min} and {max}',
|
||||||
|
ipAddress: 'Please enter a valid IP address',
|
||||||
|
portNumber: 'Please enter a valid port number (1-65535)'
|
||||||
|
},
|
||||||
|
viewMore: 'View More',
|
||||||
|
noMore: 'No more',
|
||||||
|
expand: 'Expand',
|
||||||
|
collapse: 'Collapse',
|
||||||
|
songCount: '{count} songs',
|
||||||
|
tray: {
|
||||||
|
show: 'Show',
|
||||||
|
quit: 'Quit'
|
||||||
|
},
|
||||||
|
language: 'Language'
|
||||||
|
};
|
||||||
89
src/i18n/lang/en-US/comp.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
export default {
|
||||||
|
installApp: {
|
||||||
|
description: 'Install the application on the desktop for a better experience',
|
||||||
|
noPrompt: 'Do not prompt again',
|
||||||
|
install: 'Install now',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
download: 'Download',
|
||||||
|
downloadFailed: 'Download failed',
|
||||||
|
downloadComplete: 'Download complete',
|
||||||
|
downloadProblem: 'Download problem? Go to',
|
||||||
|
downloadProblemLinkText: 'Download the latest version'
|
||||||
|
},
|
||||||
|
playlistDrawer: {
|
||||||
|
title: 'Add to playlist',
|
||||||
|
createPlaylist: 'Create new playlist',
|
||||||
|
cancelCreate: 'Cancel create',
|
||||||
|
create: 'Create',
|
||||||
|
playlistName: 'Playlist name',
|
||||||
|
privatePlaylist: 'Private playlist',
|
||||||
|
publicPlaylist: 'Public playlist',
|
||||||
|
createSuccess: 'Playlist created successfully',
|
||||||
|
createFailed: 'Playlist creation failed',
|
||||||
|
addSuccess: 'Song added successfully',
|
||||||
|
addFailed: 'Song addition failed',
|
||||||
|
private: 'Private',
|
||||||
|
public: 'Public',
|
||||||
|
count: 'songs',
|
||||||
|
loginFirst: 'Please login first',
|
||||||
|
getPlaylistFailed: 'Get playlist failed',
|
||||||
|
inputPlaylistName: 'Please enter the playlist name'
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
title: 'New version found',
|
||||||
|
currentVersion: 'Current version',
|
||||||
|
cancel: 'Do not update',
|
||||||
|
prepareDownload: 'Preparing to download...',
|
||||||
|
downloading: 'Downloading...',
|
||||||
|
nowUpdate: 'Update now',
|
||||||
|
downloadFailed: 'Download failed, please try again or download manually',
|
||||||
|
startFailed: 'Start download failed, please try again or download manually',
|
||||||
|
noDownloadUrl:
|
||||||
|
'No suitable installation package found for the current system, please download manually'
|
||||||
|
},
|
||||||
|
coffee: {
|
||||||
|
title: 'Buy me a coffee',
|
||||||
|
alipay: 'Alipay',
|
||||||
|
wechat: 'Wechat',
|
||||||
|
alipayQR: 'Alipay QR code',
|
||||||
|
wechatQR: 'Wechat QR code',
|
||||||
|
coffeeDesc: 'A cup of coffee, a support',
|
||||||
|
coffeeDescLinkText: 'View more',
|
||||||
|
qqGroup: 'QQ group: 789288579',
|
||||||
|
messages: {
|
||||||
|
copySuccess: 'Copied to clipboard'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
playlistType: {
|
||||||
|
title: 'Playlist Category',
|
||||||
|
showAll: 'Show all',
|
||||||
|
hide: 'Hide some'
|
||||||
|
},
|
||||||
|
recommendAlbum: {
|
||||||
|
title: 'Latest Album'
|
||||||
|
},
|
||||||
|
recommendSinger: {
|
||||||
|
title: 'Daily Recommendation',
|
||||||
|
songlist: 'Daily Recommendation List'
|
||||||
|
},
|
||||||
|
recommendSonglist: {
|
||||||
|
title: 'Weekly Hot Music'
|
||||||
|
},
|
||||||
|
searchBar: {
|
||||||
|
login: 'Login',
|
||||||
|
toLogin: 'To Login',
|
||||||
|
logout: 'Logout',
|
||||||
|
set: 'Set',
|
||||||
|
theme: 'Theme',
|
||||||
|
restart: 'Restart',
|
||||||
|
refresh: 'Refresh',
|
||||||
|
currentVersion: 'Current Version',
|
||||||
|
searchPlaceholder: 'Search for something...'
|
||||||
|
},
|
||||||
|
titleBar: {
|
||||||
|
closeTitle: 'Choose how to close',
|
||||||
|
minimizeToTray: 'Minimize to Tray',
|
||||||
|
exitApp: 'Exit App',
|
||||||
|
rememberChoice: 'Remember my choice'
|
||||||
|
}
|
||||||
|
};
|
||||||
6
src/i18n/lang/en-US/donation.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
description:
|
||||||
|
'Your donation will be used to support development and maintenance work, including but not limited to server maintenance, domain name renewal, etc.',
|
||||||
|
message: 'You can leave your email or github name when leaving a message.',
|
||||||
|
refresh: 'Refresh List'
|
||||||
|
};
|
||||||
36
src/i18n/lang/en-US/download.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
export default {
|
||||||
|
title: 'Download Manager',
|
||||||
|
localMusic: 'Local Music',
|
||||||
|
tabs: {
|
||||||
|
downloading: 'Downloading',
|
||||||
|
downloaded: 'Downloaded'
|
||||||
|
},
|
||||||
|
empty: {
|
||||||
|
noTasks: 'No download tasks',
|
||||||
|
noDownloaded: 'No downloaded songs'
|
||||||
|
},
|
||||||
|
progress: {
|
||||||
|
total: 'Total Progress: {progress}%'
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
downloading: 'Downloading',
|
||||||
|
completed: 'Completed',
|
||||||
|
failed: 'Failed',
|
||||||
|
unknown: 'Unknown'
|
||||||
|
},
|
||||||
|
artist: {
|
||||||
|
unknown: 'Unknown Artist'
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
title: 'Delete Confirmation',
|
||||||
|
message: 'Are you sure you want to delete "{filename}"? This action cannot be undone.',
|
||||||
|
confirm: 'Delete',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
success: 'Successfully deleted',
|
||||||
|
failed: 'Failed to delete'
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
downloadComplete: '{filename} download completed',
|
||||||
|
downloadFailed: '{filename} download failed: {error}'
|
||||||
|
}
|
||||||
|
};
|
||||||
15
src/i18n/lang/en-US/favorite.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export default {
|
||||||
|
title: 'Favorites',
|
||||||
|
count: 'Total {count}',
|
||||||
|
batchDownload: 'Batch Download',
|
||||||
|
selectAll: 'All',
|
||||||
|
download: 'Download ({count})',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
emptyTip: 'No favorite songs yet',
|
||||||
|
viewMore: 'View More',
|
||||||
|
noMore: 'No more',
|
||||||
|
downloadSuccess: 'Download completed',
|
||||||
|
downloadFailed: 'Download failed',
|
||||||
|
downloading: 'Downloading, please wait...',
|
||||||
|
selectSongsFirst: 'Please select songs to download first'
|
||||||
|
};
|
||||||
5
src/i18n/lang/en-US/history.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export default {
|
||||||
|
title: 'Play History',
|
||||||
|
playCount: '{count}',
|
||||||
|
getHistoryFailed: 'Failed to get play history'
|
||||||
|
};
|
||||||
29
src/i18n/lang/en-US/index.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
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
|
||||||
|
};
|
||||||
22
src/i18n/lang/en-US/login.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
export default {
|
||||||
|
title: {
|
||||||
|
qr: 'QR Code Login',
|
||||||
|
phone: 'Phone Login'
|
||||||
|
},
|
||||||
|
qrTip: 'Scan with NetEase Cloud Music APP',
|
||||||
|
phoneTip: 'Login with NetEase Cloud account',
|
||||||
|
placeholder: {
|
||||||
|
phone: 'Phone Number',
|
||||||
|
password: 'Password'
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
login: 'Login',
|
||||||
|
switchToQr: 'QR Code Login',
|
||||||
|
switchToPhone: 'Phone Login'
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
loginSuccess: 'Login successful',
|
||||||
|
loadError: 'Error loading login information',
|
||||||
|
qrCheckError: 'Error checking QR code status'
|
||||||
|
}
|
||||||
|
};
|
||||||
49
src/i18n/lang/en-US/player.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
export default {
|
||||||
|
nowPlaying: 'Now Playing',
|
||||||
|
playlist: 'Playlist',
|
||||||
|
lyrics: 'Lyrics',
|
||||||
|
previous: 'Previous',
|
||||||
|
play: 'Play',
|
||||||
|
pause: 'Pause',
|
||||||
|
next: 'Next',
|
||||||
|
volumeUp: 'Volume Up',
|
||||||
|
volumeDown: 'Volume Down',
|
||||||
|
mute: 'Mute',
|
||||||
|
unmute: 'Unmute',
|
||||||
|
playMode: {
|
||||||
|
sequence: 'Sequence',
|
||||||
|
loop: 'Loop',
|
||||||
|
random: 'Random'
|
||||||
|
},
|
||||||
|
fullscreen: {
|
||||||
|
enter: 'Enter Fullscreen',
|
||||||
|
exit: 'Exit Fullscreen'
|
||||||
|
},
|
||||||
|
close: 'Close',
|
||||||
|
modeHint: {
|
||||||
|
single: 'Single',
|
||||||
|
list: 'Next'
|
||||||
|
},
|
||||||
|
lrc: {
|
||||||
|
noLrc: 'No lyrics, please enjoy'
|
||||||
|
},
|
||||||
|
playBar: {
|
||||||
|
expand: 'Expand Lyrics',
|
||||||
|
collapse: 'Collapse Lyrics',
|
||||||
|
like: 'Like',
|
||||||
|
lyric: 'Lyric',
|
||||||
|
playList: 'Play List',
|
||||||
|
playMode: {
|
||||||
|
sequence: 'Sequence',
|
||||||
|
loop: 'Loop',
|
||||||
|
random: 'Random'
|
||||||
|
},
|
||||||
|
play: 'Play',
|
||||||
|
pause: 'Pause',
|
||||||
|
prev: 'Previous',
|
||||||
|
next: 'Next',
|
||||||
|
volume: 'Volume',
|
||||||
|
favorite: 'Favorite {name}',
|
||||||
|
unFavorite: 'Unfavorite {name}'
|
||||||
|
}
|
||||||
|
};
|
||||||
19
src/i18n/lang/en-US/search.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
export default {
|
||||||
|
title: {
|
||||||
|
hotSearch: 'Hot Search',
|
||||||
|
searchList: 'Search Results',
|
||||||
|
searchHistory: 'Search History'
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
clear: 'Clear',
|
||||||
|
back: 'Back'
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
more: 'Loading...',
|
||||||
|
failed: 'Search failed'
|
||||||
|
},
|
||||||
|
noMore: 'No more results',
|
||||||
|
error: {
|
||||||
|
searchFailed: 'Search failed'
|
||||||
|
}
|
||||||
|
};
|
||||||
208
src/i18n/lang/en-US/settings.ts
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
export default {
|
||||||
|
theme: 'Theme',
|
||||||
|
language: 'Language',
|
||||||
|
regard: 'About',
|
||||||
|
logout: 'Logout',
|
||||||
|
sections: {
|
||||||
|
basic: 'Basic Settings',
|
||||||
|
playback: 'Playback Settings',
|
||||||
|
application: 'Application Settings',
|
||||||
|
network: 'Network Settings',
|
||||||
|
system: 'System Management',
|
||||||
|
donation: 'Donation',
|
||||||
|
regard: 'About'
|
||||||
|
},
|
||||||
|
basic: {
|
||||||
|
themeMode: 'Theme Mode',
|
||||||
|
themeModeDesc: 'Switch between light/dark theme',
|
||||||
|
language: 'Language Settings',
|
||||||
|
languageDesc: 'Change display language',
|
||||||
|
font: 'Font Settings',
|
||||||
|
fontDesc: 'Select fonts, prioritize fonts in order',
|
||||||
|
fontScope: {
|
||||||
|
global: 'Global',
|
||||||
|
lyric: 'Lyrics Only'
|
||||||
|
},
|
||||||
|
animation: 'Animation Speed',
|
||||||
|
animationDesc: 'Enable/disable animations',
|
||||||
|
animationSpeed: {
|
||||||
|
slow: 'Very Slow',
|
||||||
|
normal: 'Normal',
|
||||||
|
fast: 'Very Fast'
|
||||||
|
},
|
||||||
|
fontPreview: {
|
||||||
|
title: 'Font Preview',
|
||||||
|
chinese: 'Chinese',
|
||||||
|
english: 'English',
|
||||||
|
japanese: 'Japanese',
|
||||||
|
korean: 'Korean',
|
||||||
|
chineseText: '静夜思 床前明月光 疑是地上霜',
|
||||||
|
englishText: 'The quick brown fox jumps over the lazy dog',
|
||||||
|
japaneseText: 'あいうえお かきくけこ さしすせそ',
|
||||||
|
koreanText: '가나다라마 바사아자차 카타파하'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
playback: {
|
||||||
|
quality: 'Audio Quality',
|
||||||
|
qualityDesc: 'Select music playback quality (VIP)',
|
||||||
|
qualityOptions: {
|
||||||
|
standard: 'Standard',
|
||||||
|
higher: 'Higher',
|
||||||
|
exhigh: 'Extreme',
|
||||||
|
lossless: 'Lossless',
|
||||||
|
hires: 'Hi-Res',
|
||||||
|
jyeffect: 'HD Surround',
|
||||||
|
sky: 'Immersive',
|
||||||
|
dolby: 'Dolby Atmos',
|
||||||
|
jymaster: 'Master'
|
||||||
|
},
|
||||||
|
autoPlay: 'Auto Play',
|
||||||
|
autoPlayDesc: 'Auto resume playback when reopening the app'
|
||||||
|
},
|
||||||
|
application: {
|
||||||
|
closeAction: 'Close Action',
|
||||||
|
closeActionDesc: 'Choose action when closing window',
|
||||||
|
closeOptions: {
|
||||||
|
ask: 'Ask Every Time',
|
||||||
|
minimize: 'Minimize to Tray',
|
||||||
|
close: 'Exit Directly'
|
||||||
|
},
|
||||||
|
shortcut: 'Shortcut Settings',
|
||||||
|
shortcutDesc: 'Customize global shortcuts',
|
||||||
|
download: 'Download Management',
|
||||||
|
downloadDesc: 'Always show download list button',
|
||||||
|
downloadPath: 'Download Directory',
|
||||||
|
downloadPathDesc: 'Choose download location for music files'
|
||||||
|
},
|
||||||
|
network: {
|
||||||
|
apiPort: 'Music API Port',
|
||||||
|
apiPortDesc: 'Restart required after modification',
|
||||||
|
proxy: 'Proxy Settings',
|
||||||
|
proxyDesc: 'Enable proxy when unable to access music',
|
||||||
|
proxyHost: 'Proxy Host',
|
||||||
|
proxyHostPlaceholder: 'Enter proxy host',
|
||||||
|
proxyPort: 'Proxy Port',
|
||||||
|
proxyPortPlaceholder: 'Enter proxy port',
|
||||||
|
realIP: 'RealIP Settings',
|
||||||
|
realIPDesc: 'Use realIP parameter with mainland China IP to resolve access restrictions abroad',
|
||||||
|
messages: {
|
||||||
|
proxySuccess: 'Proxy settings saved, restart required to take effect',
|
||||||
|
proxyError: 'Please check your input',
|
||||||
|
realIPSuccess: 'RealIP settings saved',
|
||||||
|
realIPError: 'Please enter a valid IP address'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
system: {
|
||||||
|
cache: 'Cache Management',
|
||||||
|
cacheDesc: 'Clear cache',
|
||||||
|
cacheClearTitle: 'Select cache types to clear:',
|
||||||
|
cacheTypes: {
|
||||||
|
history: {
|
||||||
|
label: 'Play History',
|
||||||
|
description: 'Clear played song records'
|
||||||
|
},
|
||||||
|
favorite: {
|
||||||
|
label: 'Favorites',
|
||||||
|
description: 'Clear local favorite songs (cloud favorites not affected)'
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
label: 'User Data',
|
||||||
|
description: 'Clear login info and user-related data'
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
label: 'App Settings',
|
||||||
|
description: 'Clear all custom app settings'
|
||||||
|
},
|
||||||
|
downloads: {
|
||||||
|
label: 'Download History',
|
||||||
|
description: 'Clear download history (downloaded files not affected)'
|
||||||
|
},
|
||||||
|
resources: {
|
||||||
|
label: 'Music Resources',
|
||||||
|
description: 'Clear cached music files, lyrics and other resources'
|
||||||
|
},
|
||||||
|
lyrics: {
|
||||||
|
label: 'Lyrics Resources',
|
||||||
|
description: 'Clear cached lyrics resources'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
restart: 'Restart',
|
||||||
|
restartDesc: 'Restart application',
|
||||||
|
messages: {
|
||||||
|
clearSuccess: 'Cache cleared successfully, some settings will take effect after restart'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
about: {
|
||||||
|
version: 'Version',
|
||||||
|
checkUpdate: 'Check for Updates',
|
||||||
|
checking: 'Checking...',
|
||||||
|
latest: 'Already latest version',
|
||||||
|
hasUpdate: 'New version available',
|
||||||
|
gotoUpdate: 'Go to Update',
|
||||||
|
gotoGithub: 'Go to Github',
|
||||||
|
author: 'Author',
|
||||||
|
authorDesc: 'algerkong Give a star🌟',
|
||||||
|
messages: {
|
||||||
|
checkError: 'Failed to check for updates, please try again later'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validation: {
|
||||||
|
selectProxyProtocol: 'Please select proxy protocol',
|
||||||
|
proxyHost: 'Please enter proxy host',
|
||||||
|
portNumber: 'Please enter a valid port number (1-65535)'
|
||||||
|
},
|
||||||
|
lyricSettings: {
|
||||||
|
title: 'Lyric Settings',
|
||||||
|
pureMode: 'Pure Mode',
|
||||||
|
hideCover: 'Hide Cover',
|
||||||
|
centerDisplay: 'Center Display',
|
||||||
|
showTranslation: 'Show Translation',
|
||||||
|
hidePlayBar: 'Hide Play Bar',
|
||||||
|
fontSize: 'Font Size',
|
||||||
|
letterSpacing: 'Letter Spacing',
|
||||||
|
lineHeight: 'Line Height',
|
||||||
|
backgroundTheme: 'Background Theme',
|
||||||
|
fontSizeMarks: {
|
||||||
|
small: 'Small',
|
||||||
|
medium: 'Medium',
|
||||||
|
large: 'Large'
|
||||||
|
},
|
||||||
|
letterSpacingMarks: {
|
||||||
|
compact: 'Compact',
|
||||||
|
default: 'Default',
|
||||||
|
loose: 'Loose'
|
||||||
|
},
|
||||||
|
lineHeightMarks: {
|
||||||
|
compact: 'Compact',
|
||||||
|
default: 'Default',
|
||||||
|
loose: 'Loose'
|
||||||
|
},
|
||||||
|
themeOptions: {
|
||||||
|
default: 'Default',
|
||||||
|
light: 'Light',
|
||||||
|
dark: 'Dark'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shortcutSettings: {
|
||||||
|
title: 'Shortcut Settings',
|
||||||
|
shortcut: 'Shortcut',
|
||||||
|
shortcutDesc: 'Customize global shortcuts',
|
||||||
|
shortcutConflict: 'Shortcut Conflict',
|
||||||
|
inputPlaceholder: 'Click to input shortcut',
|
||||||
|
resetShortcuts: 'Reset',
|
||||||
|
togglePlay: 'Play/Pause',
|
||||||
|
prevPlay: 'Previous',
|
||||||
|
nextPlay: 'Next',
|
||||||
|
volumeUp: 'Volume Up',
|
||||||
|
volumeDown: 'Volume Down',
|
||||||
|
toggleFavorite: 'Favorite/Unfavorite',
|
||||||
|
toggleWindow: 'Show/Hide Window',
|
||||||
|
messages: {
|
||||||
|
resetSuccess: 'Shortcuts reset successfully, please save',
|
||||||
|
conflict: 'Shortcut conflict, please reset',
|
||||||
|
saveSuccess: 'Shortcuts saved successfully',
|
||||||
|
saveError: 'Failed to save shortcuts',
|
||||||
|
cancelEdit: 'Edit cancelled'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
18
src/i18n/lang/en-US/songItem.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export default {
|
||||||
|
menu: {
|
||||||
|
play: 'Play',
|
||||||
|
playNext: 'Play Next',
|
||||||
|
download: 'Download',
|
||||||
|
addToPlaylist: 'Add to Playlist',
|
||||||
|
favorite: 'Like',
|
||||||
|
unfavorite: 'Unlike',
|
||||||
|
removeFromPlaylist: 'Remove from Playlist'
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
downloading: 'Downloading, please wait...',
|
||||||
|
downloadFailed: 'Download failed',
|
||||||
|
downloadQueued: 'Added to download queue',
|
||||||
|
addedToNextPlay: 'Added to play next',
|
||||||
|
getUrlFailed: 'Failed to get music download URL'
|
||||||
|
}
|
||||||
|
};
|
||||||
21
src/i18n/lang/en-US/user.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
export default {
|
||||||
|
profile: {
|
||||||
|
followers: 'Followers',
|
||||||
|
following: 'Following',
|
||||||
|
level: 'Level'
|
||||||
|
},
|
||||||
|
playlist: {
|
||||||
|
created: 'Created Playlists',
|
||||||
|
trackCount: '{count} tracks',
|
||||||
|
playCount: 'Played {count} times'
|
||||||
|
},
|
||||||
|
ranking: {
|
||||||
|
title: 'Listening History',
|
||||||
|
playCount: '{count} times'
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
loadFailed: 'Failed to load user page',
|
||||||
|
deleteSuccess: 'Successfully deleted',
|
||||||
|
deleteFailed: 'Failed to delete'
|
||||||
|
}
|
||||||
|
};
|
||||||
5
src/i18n/lang/zh-CN/artist.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export default {
|
||||||
|
hotSongs: '热门歌曲',
|
||||||
|
albums: '专辑',
|
||||||
|
description: '艺人介绍'
|
||||||
|
};
|
||||||
46
src/i18n/lang/zh-CN/common.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
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: '重试',
|
||||||
|
validation: {
|
||||||
|
required: '此项是必填的',
|
||||||
|
invalidInput: '输入无效',
|
||||||
|
selectRequired: '请选择一个选项',
|
||||||
|
numberRange: '请输入 {min} 到 {max} 之间的数字'
|
||||||
|
},
|
||||||
|
viewMore: '查看更多',
|
||||||
|
noMore: '没有更多了',
|
||||||
|
selectAll: '全选',
|
||||||
|
expand: '展开',
|
||||||
|
collapse: '收起',
|
||||||
|
songCount: '{count}首',
|
||||||
|
language: '语言',
|
||||||
|
tray: {
|
||||||
|
show: '显示',
|
||||||
|
quit: '退出'
|
||||||
|
}
|
||||||
|
};
|
||||||
88
src/i18n/lang/zh-CN/comp.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
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: '未找到适合当前系统的安装包,请手动下载'
|
||||||
|
},
|
||||||
|
coffee: {
|
||||||
|
title: '请我喝咖啡',
|
||||||
|
alipay: '支付宝',
|
||||||
|
wechat: '微信支付',
|
||||||
|
alipayQR: '支付宝收款码',
|
||||||
|
wechatQR: '微信收款码',
|
||||||
|
coffeeDesc: '一杯咖啡,一份支持',
|
||||||
|
coffeeDescLinkText: '查看更多',
|
||||||
|
qqGroup: 'QQ群:789288579',
|
||||||
|
messages: {
|
||||||
|
copySuccess: '已复制到剪贴板'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
playlistType: {
|
||||||
|
title: '歌单分类',
|
||||||
|
showAll: '显示全部',
|
||||||
|
hide: '隐藏一些'
|
||||||
|
},
|
||||||
|
recommendAlbum: {
|
||||||
|
title: '最新专辑'
|
||||||
|
},
|
||||||
|
recommendSinger: {
|
||||||
|
title: '每日推荐',
|
||||||
|
songlist: '每日推荐列表'
|
||||||
|
},
|
||||||
|
recommendSonglist: {
|
||||||
|
title: '本周最热音乐'
|
||||||
|
},
|
||||||
|
searchBar: {
|
||||||
|
login: '登录',
|
||||||
|
toLogin: '去登录',
|
||||||
|
logout: '退出登录',
|
||||||
|
set: '设置',
|
||||||
|
theme: '主题',
|
||||||
|
restart: '重启',
|
||||||
|
refresh: '刷新',
|
||||||
|
currentVersion: '当前版本',
|
||||||
|
searchPlaceholder: '搜索点什么吧...'
|
||||||
|
},
|
||||||
|
titleBar: {
|
||||||
|
closeTitle: '请选择关闭方式',
|
||||||
|
minimizeToTray: '最小化到托盘',
|
||||||
|
exitApp: '退出应用',
|
||||||
|
rememberChoice: '记住我的选择'
|
||||||
|
}
|
||||||
|
};
|
||||||
5
src/i18n/lang/zh-CN/donation.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export default {
|
||||||
|
description: '您的捐赠将用于支持开发和维护工作,包括但不限于服务器维护、域名续费等。',
|
||||||
|
message: '留言时可留下您的邮箱或 github名称。',
|
||||||
|
refresh: '刷新列表'
|
||||||
|
};
|
||||||
36
src/i18n/lang/zh-CN/download.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
export default {
|
||||||
|
title: '下载管理',
|
||||||
|
localMusic: '本地音乐',
|
||||||
|
tabs: {
|
||||||
|
downloading: '下载中',
|
||||||
|
downloaded: '已下载'
|
||||||
|
},
|
||||||
|
empty: {
|
||||||
|
noTasks: '暂无下载任务',
|
||||||
|
noDownloaded: '暂无已下载歌曲'
|
||||||
|
},
|
||||||
|
progress: {
|
||||||
|
total: '总进度: {progress}%'
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
downloading: '下载中',
|
||||||
|
completed: '已完成',
|
||||||
|
failed: '失败',
|
||||||
|
unknown: '未知'
|
||||||
|
},
|
||||||
|
artist: {
|
||||||
|
unknown: '未知歌手'
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
title: '删除确认',
|
||||||
|
message: '确定要删除歌曲 "{filename}" 吗?此操作不可恢复。',
|
||||||
|
confirm: '确定删除',
|
||||||
|
cancel: '取消',
|
||||||
|
success: '删除成功',
|
||||||
|
failed: '删除失败'
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
downloadComplete: '{filename} 下载完成',
|
||||||
|
downloadFailed: '{filename} 下载失败: {error}'
|
||||||
|
}
|
||||||
|
};
|
||||||
11
src/i18n/lang/zh-CN/favorite.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export default {
|
||||||
|
title: '我的收藏',
|
||||||
|
count: '共 {count} 首',
|
||||||
|
batchDownload: '批量下载',
|
||||||
|
download: '下载 ({count})',
|
||||||
|
emptyTip: '还没有收藏歌曲',
|
||||||
|
downloadSuccess: '下载完成',
|
||||||
|
downloadFailed: '下载失败',
|
||||||
|
downloading: '正在下载中,请稍候...',
|
||||||
|
selectSongsFirst: '请先选择要下载的歌曲'
|
||||||
|
};
|
||||||
5
src/i18n/lang/zh-CN/history.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export default {
|
||||||
|
title: '播放历史',
|
||||||
|
playCount: '{count}',
|
||||||
|
getHistoryFailed: '获取历史记录失败'
|
||||||
|
};
|
||||||
29
src/i18n/lang/zh-CN/index.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
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
|
||||||
|
};
|
||||||
22
src/i18n/lang/zh-CN/login.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
export default {
|
||||||
|
title: {
|
||||||
|
qr: '扫码登录',
|
||||||
|
phone: '手机号登录'
|
||||||
|
},
|
||||||
|
qrTip: '使用网易云APP扫码登录',
|
||||||
|
phoneTip: '使用网易云账号登录',
|
||||||
|
placeholder: {
|
||||||
|
phone: '手机号',
|
||||||
|
password: '密码'
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
login: '登录',
|
||||||
|
switchToQr: '扫码登录',
|
||||||
|
switchToPhone: '手机号登录'
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
loginSuccess: '登录成功',
|
||||||
|
loadError: '加载登录信息时出错',
|
||||||
|
qrCheckError: '检查二维码状态时出错'
|
||||||
|
}
|
||||||
|
};
|
||||||
49
src/i18n/lang/zh-CN/player.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
export default {
|
||||||
|
nowPlaying: '正在播放',
|
||||||
|
playlist: '播放列表',
|
||||||
|
lyrics: '歌词',
|
||||||
|
previous: '上一个',
|
||||||
|
play: '播放',
|
||||||
|
pause: '暂停',
|
||||||
|
next: '下一个',
|
||||||
|
volumeUp: '音量增加',
|
||||||
|
volumeDown: '音量减少',
|
||||||
|
mute: '静音',
|
||||||
|
unmute: '取消静音',
|
||||||
|
playMode: {
|
||||||
|
sequence: '顺序播放',
|
||||||
|
loop: '循环播放',
|
||||||
|
random: '随机播放'
|
||||||
|
},
|
||||||
|
fullscreen: {
|
||||||
|
enter: '全屏',
|
||||||
|
exit: '退出全屏'
|
||||||
|
},
|
||||||
|
close: '关闭',
|
||||||
|
modeHint: {
|
||||||
|
single: '单曲循环',
|
||||||
|
list: '自动播放下一个'
|
||||||
|
},
|
||||||
|
lrc: {
|
||||||
|
noLrc: '暂无歌词, 请欣赏'
|
||||||
|
},
|
||||||
|
playBar: {
|
||||||
|
expand: '展开歌词',
|
||||||
|
collapse: '收起歌词',
|
||||||
|
like: '喜欢',
|
||||||
|
lyric: '歌词',
|
||||||
|
playList: '播放列表',
|
||||||
|
playMode: {
|
||||||
|
sequence: '顺序播放',
|
||||||
|
loop: '循环播放',
|
||||||
|
random: '随机播放'
|
||||||
|
},
|
||||||
|
play: '开始播放',
|
||||||
|
pause: '暂停播放',
|
||||||
|
prev: '上一首',
|
||||||
|
next: '下一首',
|
||||||
|
volume: '音量',
|
||||||
|
favorite: '已收藏{name}',
|
||||||
|
unFavorite: '已取消收藏{name}'
|
||||||
|
}
|
||||||
|
};
|
||||||
19
src/i18n/lang/zh-CN/search.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
export default {
|
||||||
|
title: {
|
||||||
|
hotSearch: '热搜列表',
|
||||||
|
searchList: '搜索列表',
|
||||||
|
searchHistory: '搜索历史'
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
clear: '清空',
|
||||||
|
back: '返回'
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
more: '加载中...',
|
||||||
|
failed: '搜索失败'
|
||||||
|
},
|
||||||
|
noMore: '没有更多了',
|
||||||
|
error: {
|
||||||
|
searchFailed: '搜索失败'
|
||||||
|
}
|
||||||
|
};
|
||||||
208
src/i18n/lang/zh-CN/settings.ts
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
export default {
|
||||||
|
theme: '主题',
|
||||||
|
language: '语言',
|
||||||
|
regard: '关于',
|
||||||
|
logout: '退出登录',
|
||||||
|
sections: {
|
||||||
|
basic: '基础设置',
|
||||||
|
playback: '播放设置',
|
||||||
|
application: '应用设置',
|
||||||
|
network: '网络设置',
|
||||||
|
system: '系统管理',
|
||||||
|
donation: '捐赠支持',
|
||||||
|
regard: '关于'
|
||||||
|
},
|
||||||
|
basic: {
|
||||||
|
themeMode: '主题模式',
|
||||||
|
themeModeDesc: '切换日间/夜间主题',
|
||||||
|
language: '语言设置',
|
||||||
|
languageDesc: '切换显示语言',
|
||||||
|
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: '超清母带'
|
||||||
|
},
|
||||||
|
autoPlay: '自动播放',
|
||||||
|
autoPlayDesc: '重新打开应用时是否自动继续播放'
|
||||||
|
},
|
||||||
|
application: {
|
||||||
|
closeAction: '关闭行为',
|
||||||
|
closeActionDesc: '选择关闭窗口时的行为',
|
||||||
|
closeOptions: {
|
||||||
|
ask: '每次询问',
|
||||||
|
minimize: '最小化到托盘',
|
||||||
|
close: '直接退出'
|
||||||
|
},
|
||||||
|
shortcut: '快捷键设置',
|
||||||
|
shortcutDesc: '自定义全局快捷键',
|
||||||
|
download: '下载管理',
|
||||||
|
downloadDesc: '是否始终显示下载列表按钮',
|
||||||
|
downloadPath: '下载目录',
|
||||||
|
downloadPathDesc: '选择音乐文件的下载位置'
|
||||||
|
},
|
||||||
|
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: '页面设置',
|
||||||
|
pureMode: '纯净模式',
|
||||||
|
hideCover: '隐藏封面',
|
||||||
|
centerDisplay: '居中显示',
|
||||||
|
showTranslation: '显示翻译',
|
||||||
|
hidePlayBar: '隐藏播放栏',
|
||||||
|
fontSize: '字体大小',
|
||||||
|
fontSizeMarks: {
|
||||||
|
small: '小',
|
||||||
|
medium: '中',
|
||||||
|
large: '大'
|
||||||
|
},
|
||||||
|
letterSpacing: '文字间距',
|
||||||
|
letterSpacingMarks: {
|
||||||
|
compact: '紧凑',
|
||||||
|
default: '默认',
|
||||||
|
loose: '宽松'
|
||||||
|
},
|
||||||
|
lineHeight: '行高',
|
||||||
|
lineHeightMarks: {
|
||||||
|
compact: '紧凑',
|
||||||
|
default: '默认',
|
||||||
|
loose: '宽松'
|
||||||
|
},
|
||||||
|
backgroundTheme: '背景主题',
|
||||||
|
themeOptions: {
|
||||||
|
default: '默认',
|
||||||
|
light: '亮色',
|
||||||
|
dark: '暗色'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shortcutSettings: {
|
||||||
|
title: '快捷键设置',
|
||||||
|
shortcut: '快捷键',
|
||||||
|
shortcutDesc: '自定义快捷键',
|
||||||
|
shortcutConflict: '快捷键冲突',
|
||||||
|
inputPlaceholder: '点击输入快捷键',
|
||||||
|
resetShortcuts: '恢复默认',
|
||||||
|
togglePlay: '播放/暂停',
|
||||||
|
prevPlay: '上一首',
|
||||||
|
nextPlay: '下一首',
|
||||||
|
volumeUp: '音量增加',
|
||||||
|
volumeDown: '音量减少',
|
||||||
|
toggleFavorite: '收藏/取消收藏',
|
||||||
|
toggleWindow: '显示/隐藏窗口',
|
||||||
|
messages: {
|
||||||
|
resetSuccess: '已恢复默认快捷键,请记得保存',
|
||||||
|
conflict: '存在冲突的快捷键,请重新设置',
|
||||||
|
saveSuccess: '快捷键设置已保存',
|
||||||
|
saveError: '保存快捷键失败,请重试',
|
||||||
|
cancelEdit: '已取消修改'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
18
src/i18n/lang/zh-CN/songItem.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export default {
|
||||||
|
menu: {
|
||||||
|
play: '播放',
|
||||||
|
playNext: '下一首播放',
|
||||||
|
download: '下载歌曲',
|
||||||
|
addToPlaylist: '添加到歌单',
|
||||||
|
favorite: '喜欢',
|
||||||
|
unfavorite: '取消喜欢',
|
||||||
|
removeFromPlaylist: '从歌单中删除'
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
downloading: '正在下载中,请稍候...',
|
||||||
|
downloadFailed: '下载失败',
|
||||||
|
downloadQueued: '已加入下载队列',
|
||||||
|
addedToNextPlay: '已添加到下一首播放',
|
||||||
|
getUrlFailed: '获取音乐下载地址失败'
|
||||||
|
}
|
||||||
|
};
|
||||||
21
src/i18n/lang/zh-CN/user.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
export default {
|
||||||
|
profile: {
|
||||||
|
followers: '粉丝',
|
||||||
|
following: '关注',
|
||||||
|
level: '等级'
|
||||||
|
},
|
||||||
|
playlist: {
|
||||||
|
created: '创建的歌单',
|
||||||
|
trackCount: '{count}首',
|
||||||
|
playCount: '播放{count}次'
|
||||||
|
},
|
||||||
|
ranking: {
|
||||||
|
title: '听歌排行',
|
||||||
|
playCount: '{count}次'
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
loadFailed: '加载用户页面失败',
|
||||||
|
deleteSuccess: '删除成功',
|
||||||
|
deleteFailed: '删除失败'
|
||||||
|
}
|
||||||
|
};
|
||||||
38
src/i18n/main.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import enUS from './lang/en-US';
|
||||||
|
import zhCN from './lang/zh-CN';
|
||||||
|
|
||||||
|
const messages = {
|
||||||
|
'zh-CN': zhCN,
|
||||||
|
'en-US': enUS
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type Language = keyof typeof messages;
|
||||||
|
|
||||||
|
// 为主进程提供一个简单的 i18n 实现
|
||||||
|
const mainI18n = {
|
||||||
|
global: {
|
||||||
|
currentLocale: 'zh-CN' as Language,
|
||||||
|
get locale() {
|
||||||
|
return this.currentLocale;
|
||||||
|
},
|
||||||
|
set locale(value: Language) {
|
||||||
|
this.currentLocale = value;
|
||||||
|
},
|
||||||
|
t(key: string) {
|
||||||
|
const keys = key.split('.');
|
||||||
|
let current: any = messages[this.currentLocale];
|
||||||
|
for (const k of keys) {
|
||||||
|
if (current[k] === undefined) {
|
||||||
|
// 如果找不到翻译,返回键名
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
current = current[k];
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
},
|
||||||
|
messages
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export type { Language };
|
||||||
|
export default mainI18n;
|
||||||
21
src/i18n/renderer.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { createI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import enUS from './lang/en-US';
|
||||||
|
import zhCN from './lang/zh-CN';
|
||||||
|
|
||||||
|
const messages = {
|
||||||
|
'zh-CN': zhCN,
|
||||||
|
'en-US': enUS
|
||||||
|
};
|
||||||
|
|
||||||
|
const i18n = createI18n({
|
||||||
|
legacy: false,
|
||||||
|
locale: 'zh-CN',
|
||||||
|
fallbackLocale: 'en-US',
|
||||||
|
messages,
|
||||||
|
globalInjection: true,
|
||||||
|
silentTranslationWarn: true,
|
||||||
|
silentFallbackWarn: true
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
/* ./src/index.css */
|
|
||||||
|
|
||||||
/*! @import */
|
|
||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
.n-image img {
|
|
||||||
background-color: #111111;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.n-slider-handle-indicator--top {
|
|
||||||
@apply bg-transparent text-[#ffffffdd] text-2xl px-2 py-1 shadow-none mb-0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-el {
|
|
||||||
@apply overflow-ellipsis overflow-hidden whitespace-nowrap;
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="layout-page">
|
|
||||||
<div id="layout-main" class="layout-main" :style="{ background: backgroundColor }">
|
|
||||||
<title-bar v-if="isElectron" />
|
|
||||||
<div class="layout-main-page" :class="isElectron ? '' : 'pt-6'">
|
|
||||||
<!-- 侧边菜单栏 -->
|
|
||||||
<app-menu v-if="!isMobile" class="menu" :menus="menus" />
|
|
||||||
<div class="main">
|
|
||||||
<!-- 搜索栏 -->
|
|
||||||
<search-bar />
|
|
||||||
<!-- 主页面路由 -->
|
|
||||||
<div class="main-content" :native-scrollbar="false">
|
|
||||||
<router-view
|
|
||||||
v-slot="{ Component }"
|
|
||||||
class="main-page"
|
|
||||||
:class="route.meta.noScroll && !isMobile ? 'pr-3' : ''"
|
|
||||||
>
|
|
||||||
<keep-alive :include="keepAliveInclude">
|
|
||||||
<component :is="Component" />
|
|
||||||
</keep-alive>
|
|
||||||
</router-view>
|
|
||||||
</div>
|
|
||||||
<play-bottom height="5rem" />
|
|
||||||
<app-menu v-if="isMobile" class="menu" :menus="menus" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 底部音乐播放 -->
|
|
||||||
<play-bar v-if="isPlay" />
|
|
||||||
</div>
|
|
||||||
<install-app-modal></install-app-modal>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
import { useStore } from 'vuex';
|
|
||||||
|
|
||||||
import InstallAppModal from '@/components/common/InstallAppModal.vue';
|
|
||||||
import PlayBottom from '@/components/common/PlayBottom.vue';
|
|
||||||
import { isElectron } from '@/hooks/MusicHook';
|
|
||||||
import homeRouter from '@/router/home';
|
|
||||||
import { isMobile } from '@/utils';
|
|
||||||
|
|
||||||
const keepAliveInclude = computed(() =>
|
|
||||||
homeRouter
|
|
||||||
.filter((item) => {
|
|
||||||
return item.meta.keepAlive;
|
|
||||||
})
|
|
||||||
.map((item) => {
|
|
||||||
// return item.name;
|
|
||||||
// 首字母大写
|
|
||||||
return item.name.charAt(0).toUpperCase() + item.name.slice(1);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const AppMenu = defineAsyncComponent(() => import('./components/AppMenu.vue'));
|
|
||||||
const PlayBar = defineAsyncComponent(() => import('./components/PlayBar.vue'));
|
|
||||||
const SearchBar = defineAsyncComponent(() => import('./components/SearchBar.vue'));
|
|
||||||
const TitleBar = defineAsyncComponent(() => import('./components/TitleBar.vue'));
|
|
||||||
|
|
||||||
const store = useStore();
|
|
||||||
|
|
||||||
const isPlay = computed(() => store.state.isPlay as boolean);
|
|
||||||
const { menus } = store.state;
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const backgroundColor = ref('#000');
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.layout-page {
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
@apply flex justify-center items-center overflow-hidden bg-black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-main {
|
|
||||||
@apply text-white shadow-xl flex flex-col relative transition-all;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
&-page {
|
|
||||||
@apply flex flex-1 overflow-hidden;
|
|
||||||
}
|
|
||||||
.main {
|
|
||||||
@apply flex-1 box-border flex flex-col overflow-hidden;
|
|
||||||
height: 100%;
|
|
||||||
&-content {
|
|
||||||
@apply box-border flex-1 overflow-hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// :deep(.n-scrollbar-content) {
|
|
||||||
// @apply pr-3;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile {
|
|
||||||
.layout-main {
|
|
||||||
&-page {
|
|
||||||
@apply pt-4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<!-- menu -->
|
|
||||||
<div class="app-menu">
|
|
||||||
<div class="app-menu-header">
|
|
||||||
<div class="app-menu-logo">
|
|
||||||
<img src="/icon.png" class="w-9 h-9" alt="logo" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="app-menu-list">
|
|
||||||
<div v-for="(item, index) in menus" :key="item.path" class="app-menu-item">
|
|
||||||
<router-link class="app-menu-item-link" :to="item.path">
|
|
||||||
<i class="iconfont app-menu-item-icon" :style="iconStyle(index)" :class="item.meta.icon"></i>
|
|
||||||
<span v-if="isText" class="app-menu-item-text ml-3">{{ item.meta.title }}</span>
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
isText: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
type: String,
|
|
||||||
default: '26px',
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
type: String,
|
|
||||||
default: '#aaa',
|
|
||||||
},
|
|
||||||
selectColor: {
|
|
||||||
type: String,
|
|
||||||
default: '#10B981',
|
|
||||||
},
|
|
||||||
menus: {
|
|
||||||
type: Array as any,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const path = ref(route.path);
|
|
||||||
watch(
|
|
||||||
() => route.path,
|
|
||||||
async (newParams) => {
|
|
||||||
path.value = newParams;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const iconStyle = (index: number) => {
|
|
||||||
const style = {
|
|
||||||
fontSize: props.size,
|
|
||||||
color: path.value === props.menus[index].path ? props.selectColor : props.color,
|
|
||||||
};
|
|
||||||
return style;
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.app-menu {
|
|
||||||
@apply flex-col items-center justify-center px-6;
|
|
||||||
max-width: 100px;
|
|
||||||
}
|
|
||||||
.app-menu-item-link,
|
|
||||||
.app-menu-header {
|
|
||||||
@apply flex items-center justify-center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-menu-item-link {
|
|
||||||
@apply mb-6 mt-6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-menu-item-icon:hover {
|
|
||||||
color: #10b981 !important;
|
|
||||||
transform: scale(1.05);
|
|
||||||
transition: 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile {
|
|
||||||
.app-menu {
|
|
||||||
max-width: 100%;
|
|
||||||
width: 100vw;
|
|
||||||
position: relative;
|
|
||||||
z-index: 999999;
|
|
||||||
background-color: #000;
|
|
||||||
&-header {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
&-list {
|
|
||||||
@apply flex justify-between;
|
|
||||||
}
|
|
||||||
&-item {
|
|
||||||
&-link {
|
|
||||||
@apply my-4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,305 +0,0 @@
|
|||||||
<template>
|
|
||||||
<n-drawer
|
|
||||||
:show="musicFull"
|
|
||||||
height="100%"
|
|
||||||
placement="bottom"
|
|
||||||
:style="{ background: currentBackground || background }"
|
|
||||||
:to="`#layout-main`"
|
|
||||||
>
|
|
||||||
<div id="drawer-target">
|
|
||||||
<div class="drawer-back"></div>
|
|
||||||
<div class="music-img">
|
|
||||||
<n-image ref="PicImgRef" :src="getImgUrl(playMusic?.picUrl, '300y300')" class="img" lazy preview-disabled />
|
|
||||||
<div>
|
|
||||||
<div class="music-content-name">{{ playMusic.name }}</div>
|
|
||||||
<div class="music-content-singer">
|
|
||||||
<span v-for="(item, index) in playMusic.ar || playMusic.song.artists" :key="index">
|
|
||||||
{{ item.name }}{{ index < (playMusic.ar || playMusic.song.artists).length - 1 ? ' / ' : '' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="music-content">
|
|
||||||
<n-layout
|
|
||||||
ref="lrcSider"
|
|
||||||
class="music-lrc"
|
|
||||||
style="height: 60vh"
|
|
||||||
:native-scrollbar="false"
|
|
||||||
@mouseover="mouseOverLayout"
|
|
||||||
@mouseleave="mouseLeaveLayout"
|
|
||||||
>
|
|
||||||
<div ref="lrcContainer">
|
|
||||||
<div
|
|
||||||
v-for="(item, index) in lrcArray"
|
|
||||||
:id="`music-lrc-text-${index}`"
|
|
||||||
:key="index"
|
|
||||||
class="music-lrc-text"
|
|
||||||
:class="{ 'now-text': index === nowIndex, 'hover-text': item.text }"
|
|
||||||
@click="setAudioTime(index)"
|
|
||||||
>
|
|
||||||
<span :style="getLrcStyle(index)">{{ item.text }}</span>
|
|
||||||
<div class="music-lrc-text-tr">{{ item.trText }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-layout>
|
|
||||||
<!-- 时间矫正 -->
|
|
||||||
<!-- <div class="music-content-time">
|
|
||||||
<n-button @click="reduceCorrectionTime(0.2)">-</n-button>
|
|
||||||
<n-button @click="addCorrectionTime(0.2)">+</n-button>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-drawer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useDebounceFn } from '@vueuse/core';
|
|
||||||
import { onBeforeUnmount, ref, watch } from 'vue';
|
|
||||||
|
|
||||||
import { lrcArray, nowIndex, playMusic, setAudioTime, useLyricProgress } from '@/hooks/MusicHook';
|
|
||||||
import { getImgUrl } from '@/utils';
|
|
||||||
import { animateGradient, getHoverBackgroundColor, getTextColors } from '@/utils/linearColor';
|
|
||||||
|
|
||||||
// 定义 refs
|
|
||||||
const lrcSider = ref<any>(null);
|
|
||||||
const isMouse = ref(false);
|
|
||||||
const lrcContainer = ref<HTMLElement | null>(null);
|
|
||||||
const currentBackground = ref('');
|
|
||||||
const animationFrame = ref<number | null>(null);
|
|
||||||
const isDark = ref(false);
|
|
||||||
|
|
||||||
// 初始化 textColors
|
|
||||||
const textColors = ref(getTextColors());
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
musicFull: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
background: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 歌词滚动方法
|
|
||||||
const lrcScroll = (behavior = 'smooth') => {
|
|
||||||
const nowEl = document.querySelector(`#music-lrc-text-${nowIndex.value}`);
|
|
||||||
if (props.musicFull && !isMouse.value && nowEl && lrcContainer.value) {
|
|
||||||
const containerRect = lrcContainer.value.getBoundingClientRect();
|
|
||||||
const nowElRect = nowEl.getBoundingClientRect();
|
|
||||||
const relativeTop = nowElRect.top - containerRect.top;
|
|
||||||
const scrollTop = relativeTop - lrcSider.value.$el.getBoundingClientRect().height / 2;
|
|
||||||
lrcSider.value.scrollTo({ top: scrollTop, behavior });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const debouncedLrcScroll = useDebounceFn(lrcScroll, 200);
|
|
||||||
|
|
||||||
const mouseOverLayout = () => {
|
|
||||||
isMouse.value = true;
|
|
||||||
};
|
|
||||||
const mouseLeaveLayout = () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
isMouse.value = false;
|
|
||||||
lrcScroll();
|
|
||||||
}, 2000);
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(nowIndex, () => {
|
|
||||||
debouncedLrcScroll();
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.musicFull,
|
|
||||||
() => {
|
|
||||||
if (props.musicFull) {
|
|
||||||
nextTick(() => {
|
|
||||||
lrcScroll('instant');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// 监听背景变化
|
|
||||||
watch(
|
|
||||||
() => props.background,
|
|
||||||
(newBg) => {
|
|
||||||
if (!newBg) {
|
|
||||||
textColors.value = getTextColors();
|
|
||||||
document.documentElement.style.setProperty('--hover-bg-color', getHoverBackgroundColor(false));
|
|
||||||
document.documentElement.style.setProperty('--text-color-primary', textColors.value.primary);
|
|
||||||
document.documentElement.style.setProperty('--text-color-active', textColors.value.active);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentBackground.value) {
|
|
||||||
if (animationFrame.value) {
|
|
||||||
cancelAnimationFrame(animationFrame.value);
|
|
||||||
}
|
|
||||||
animationFrame.value = animateGradient(currentBackground.value, newBg, (gradient) => {
|
|
||||||
currentBackground.value = gradient;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
currentBackground.value = newBg;
|
|
||||||
}
|
|
||||||
|
|
||||||
textColors.value = getTextColors(newBg);
|
|
||||||
isDark.value = textColors.value.active === '#000000';
|
|
||||||
|
|
||||||
document.documentElement.style.setProperty('--hover-bg-color', getHoverBackgroundColor(isDark.value));
|
|
||||||
document.documentElement.style.setProperty('--text-color-primary', textColors.value.primary);
|
|
||||||
document.documentElement.style.setProperty('--text-color-active', textColors.value.active);
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
// 修改 useLyricProgress 的使用方式
|
|
||||||
const { getLrcStyle: originalLrcStyle } = useLyricProgress();
|
|
||||||
|
|
||||||
// 修改 getLrcStyle 函数
|
|
||||||
const getLrcStyle = (index: number) => {
|
|
||||||
const colors = textColors.value || getTextColors;
|
|
||||||
const originalStyle = originalLrcStyle(index);
|
|
||||||
|
|
||||||
if (index === nowIndex.value) {
|
|
||||||
// 当前播放的歌词,使用渐变效果
|
|
||||||
return {
|
|
||||||
...originalStyle,
|
|
||||||
backgroundImage: originalStyle.backgroundImage
|
|
||||||
?.replace(/#ffffff/g, colors.active)
|
|
||||||
.replace(/#ffffff8a/g, `${colors.primary}`),
|
|
||||||
backgroundClip: 'text',
|
|
||||||
WebkitBackgroundClip: 'text',
|
|
||||||
color: 'transparent',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 非当前播放的歌词,使用普通颜色
|
|
||||||
return {
|
|
||||||
color: colors.primary,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// 组件卸载时清理动画
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
if (animationFrame.value) {
|
|
||||||
cancelAnimationFrame(animationFrame.value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
lrcScroll,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
@keyframes round {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.drawer-back {
|
|
||||||
@apply absolute bg-cover bg-center;
|
|
||||||
z-index: -1;
|
|
||||||
width: 200%;
|
|
||||||
height: 200%;
|
|
||||||
top: -50%;
|
|
||||||
left: -50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer-back.paused {
|
|
||||||
animation-play-state: paused;
|
|
||||||
}
|
|
||||||
|
|
||||||
#drawer-target {
|
|
||||||
@apply top-0 left-0 absolute overflow-hidden rounded px-24 flex items-center justify-center w-full h-full pb-8;
|
|
||||||
animation-duration: 300ms;
|
|
||||||
|
|
||||||
.music-img {
|
|
||||||
@apply flex-1 flex justify-center mr-16 flex-col;
|
|
||||||
max-width: 360px;
|
|
||||||
max-height: 360px;
|
|
||||||
.img {
|
|
||||||
@apply rounded-xl w-full h-full shadow-2xl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.music-content {
|
|
||||||
@apply flex flex-col justify-center items-center relative;
|
|
||||||
|
|
||||||
&-name {
|
|
||||||
@apply font-bold text-xl pb-1 pt-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-singer {
|
|
||||||
@apply text-base;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.music-content-time {
|
|
||||||
display: none;
|
|
||||||
@apply flex justify-center items-center;
|
|
||||||
}
|
|
||||||
.music-lrc {
|
|
||||||
background-color: inherit;
|
|
||||||
width: 500px;
|
|
||||||
height: 550px;
|
|
||||||
mask-image: linear-gradient(to bottom, transparent 0%, black 10%, black 90%, transparent 100%);
|
|
||||||
&-text {
|
|
||||||
@apply text-2xl cursor-pointer font-bold px-2 py-4;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
background-color: transparent;
|
|
||||||
|
|
||||||
span {
|
|
||||||
background-clip: text !important;
|
|
||||||
-webkit-background-clip: text !important;
|
|
||||||
padding-right: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-tr {
|
|
||||||
@apply font-normal;
|
|
||||||
opacity: 0.7;
|
|
||||||
color: var(--text-color-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover-text {
|
|
||||||
&:hover {
|
|
||||||
@apply font-bold opacity-100 rounded-xl;
|
|
||||||
background-color: var(--hover-bg-color);
|
|
||||||
|
|
||||||
span {
|
|
||||||
color: var(--text-color-active) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile {
|
|
||||||
#drawer-target {
|
|
||||||
@apply flex-col p-4 pt-8 justify-start;
|
|
||||||
.music-img {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.music-lrc {
|
|
||||||
height: calc(100vh - 260px) !important;
|
|
||||||
width: 100vw;
|
|
||||||
span {
|
|
||||||
padding-right: 0px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.music-lrc-text {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.music-drawer {
|
|
||||||
transition: none; // 移除之前的过渡效果,现在使用 JS 动画
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,266 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="search-box flex">
|
|
||||||
<div class="search-box-input flex-1">
|
|
||||||
<n-input
|
|
||||||
v-model:value="searchValue"
|
|
||||||
size="medium"
|
|
||||||
round
|
|
||||||
:placeholder="hotSearchKeyword"
|
|
||||||
class="border border-gray-600"
|
|
||||||
@keydown.enter="search"
|
|
||||||
>
|
|
||||||
<template #prefix>
|
|
||||||
<i class="iconfont icon-search"></i>
|
|
||||||
</template>
|
|
||||||
<template #suffix>
|
|
||||||
<n-dropdown trigger="hover" :options="searchTypeOptions" @select="selectSearchType">
|
|
||||||
<div class="w-20 px-3 flex justify-between items-center">
|
|
||||||
<div>{{ searchTypeOptions.find((item) => item.key === store.state.searchType)?.label }}</div>
|
|
||||||
<i class="iconfont icon-xiasanjiaoxing"></i>
|
|
||||||
</div>
|
|
||||||
</n-dropdown>
|
|
||||||
</template>
|
|
||||||
</n-input>
|
|
||||||
</div>
|
|
||||||
<n-popover trigger="hover" placement="bottom" :show-arrow="false" raw>
|
|
||||||
<template #trigger>
|
|
||||||
<div class="user-box">
|
|
||||||
<n-avatar
|
|
||||||
v-if="store.state.user"
|
|
||||||
class="ml-2 cursor-pointer"
|
|
||||||
circle
|
|
||||||
size="medium"
|
|
||||||
:src="getImgUrl(store.state.user.avatarUrl)"
|
|
||||||
@click="selectItem('user')"
|
|
||||||
/>
|
|
||||||
<div v-else class="mx-2 rounded-full cursor-pointer text-sm" @click="toLogin">登录</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div class="user-popover">
|
|
||||||
<div v-if="store.state.user" class="user-header" @click="selectItem('user')">
|
|
||||||
<n-avatar circle size="small" :src="getImgUrl(store.state.user?.avatarUrl)" />
|
|
||||||
<span class="username">{{ store.state.user?.nickname || 'Theodore' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="menu-items">
|
|
||||||
<div v-if="!store.state.user" class="menu-item" @click="toLogin">
|
|
||||||
<i class="iconfont ri-login-box-line"></i>
|
|
||||||
<span>去登录</span>
|
|
||||||
</div>
|
|
||||||
<div class="menu-item" @click="selectItem('set')">
|
|
||||||
<i class="iconfont ri-settings-3-line"></i>
|
|
||||||
<span>设置</span>
|
|
||||||
</div>
|
|
||||||
<div class="menu-item" @click="toGithubRelease">
|
|
||||||
<i class="iconfont ri-refresh-line"></i>
|
|
||||||
<span>当前版本</span>
|
|
||||||
<span class="download-btn">{{ config.version }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-popover>
|
|
||||||
|
|
||||||
<coffee :alipay-q-r="alipay" :wechat-q-r="wechat">
|
|
||||||
<div class="github" @click="toGithub">
|
|
||||||
<i class="ri-github-fill"></i>
|
|
||||||
</div>
|
|
||||||
</coffee>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
import { useStore } from 'vuex';
|
|
||||||
|
|
||||||
import config from '@/../package.json';
|
|
||||||
import { getSearchKeyword } from '@/api/home';
|
|
||||||
import { getUserDetail, logout } from '@/api/login';
|
|
||||||
import alipay from '@/assets/alipay.png';
|
|
||||||
import wechat from '@/assets/wechat.png';
|
|
||||||
import Coffee from '@/components/Coffee.vue';
|
|
||||||
import { SEARCH_TYPES, USER_SET_OPTIONS } from '@/const/bar-const';
|
|
||||||
import { getImgUrl } from '@/utils';
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const store = useStore();
|
|
||||||
const userSetOptions = ref(USER_SET_OPTIONS);
|
|
||||||
|
|
||||||
// 推荐热搜词
|
|
||||||
const hotSearchKeyword = ref('搜索点什么吧...');
|
|
||||||
const hotSearchValue = ref('');
|
|
||||||
const loadHotSearchKeyword = async () => {
|
|
||||||
const { data } = await getSearchKeyword();
|
|
||||||
hotSearchKeyword.value = data.data.showKeyword;
|
|
||||||
hotSearchValue.value = data.data.realkeyword;
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadPage = async () => {
|
|
||||||
const token = localStorage.getItem('token');
|
|
||||||
if (!token) return;
|
|
||||||
const { data } = await getUserDetail();
|
|
||||||
store.state.user = data.profile;
|
|
||||||
localStorage.setItem('user', JSON.stringify(data.profile));
|
|
||||||
};
|
|
||||||
|
|
||||||
loadPage();
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
if (store.state.user) {
|
|
||||||
userSetOptions.value = USER_SET_OPTIONS;
|
|
||||||
} else {
|
|
||||||
userSetOptions.value = USER_SET_OPTIONS.filter((item) => item.key !== 'logout');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const toLogin = () => {
|
|
||||||
router.push('/login');
|
|
||||||
};
|
|
||||||
|
|
||||||
// 页面初始化
|
|
||||||
onMounted(() => {
|
|
||||||
loadHotSearchKeyword();
|
|
||||||
loadPage();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 搜索词
|
|
||||||
const searchValue = ref('');
|
|
||||||
const search = () => {
|
|
||||||
const { value } = searchValue;
|
|
||||||
if (value === '') {
|
|
||||||
searchValue.value = hotSearchValue.value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (router.currentRoute.value.path === '/search') {
|
|
||||||
store.state.searchValue = value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
router.push({
|
|
||||||
path: '/search',
|
|
||||||
query: {
|
|
||||||
keyword: value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectSearchType = (key: number) => {
|
|
||||||
store.state.searchType = key;
|
|
||||||
};
|
|
||||||
|
|
||||||
const searchTypeOptions = ref(SEARCH_TYPES);
|
|
||||||
|
|
||||||
const selectItem = async (key: string) => {
|
|
||||||
// switch 判断
|
|
||||||
switch (key) {
|
|
||||||
case 'logout':
|
|
||||||
logout().then(() => {
|
|
||||||
store.state.user = null;
|
|
||||||
localStorage.clear();
|
|
||||||
router.push('/login');
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'login':
|
|
||||||
router.push('/login');
|
|
||||||
break;
|
|
||||||
case 'set':
|
|
||||||
router.push('/set');
|
|
||||||
break;
|
|
||||||
case 'user':
|
|
||||||
router.push('/user');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const toGithub = () => {
|
|
||||||
window.open('https://github.com/algerkong/AlgerMusicPlayer', '_blank');
|
|
||||||
};
|
|
||||||
|
|
||||||
const toGithubRelease = () => {
|
|
||||||
window.open('https://github.com/algerkong/AlgerMusicPlayer/releases', '_blank');
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.user-box {
|
|
||||||
@apply ml-4 flex text-lg justify-center items-center rounded-full border border-gray-600 hover:border-gray-400 transition-colors duration-200;
|
|
||||||
background: #1a1a1a;
|
|
||||||
}
|
|
||||||
.search-box {
|
|
||||||
@apply pb-4 pr-4;
|
|
||||||
}
|
|
||||||
.search-box-input {
|
|
||||||
@apply relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile {
|
|
||||||
.search-box {
|
|
||||||
@apply pl-4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.github {
|
|
||||||
@apply cursor-pointer text-gray-100 hover:text-gray-400 text-xl ml-4 rounded-full border border-gray-600 flex justify-center items-center px-2 h-full;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-popover {
|
|
||||||
@apply min-w-[280px] p-0 rounded-xl overflow-hidden;
|
|
||||||
background: #2c2c2c;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
||||||
|
|
||||||
.user-header {
|
|
||||||
@apply flex items-center gap-2 p-3;
|
|
||||||
border-bottom: 1px solid #3a3a3a;
|
|
||||||
|
|
||||||
.username {
|
|
||||||
@apply text-sm font-medium text-gray-200;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-items {
|
|
||||||
@apply py-1;
|
|
||||||
|
|
||||||
.menu-item {
|
|
||||||
@apply flex items-center px-3 py-2 text-sm cursor-pointer;
|
|
||||||
@apply text-gray-300;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #3a3a3a;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
@apply mr-1 text-lg text-gray-400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shortcut {
|
|
||||||
@apply ml-auto text-xs text-gray-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-btn {
|
|
||||||
@apply ml-auto px-2 py-0.5 text-xs rounded;
|
|
||||||
background: #4a4a4a;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.zoom-controls {
|
|
||||||
@apply ml-auto flex items-center gap-2;
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
.zoom-btn {
|
|
||||||
@apply px-2 py-0.5 text-sm rounded cursor-pointer;
|
|
||||||
background: #3a3a3a;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #4a4a4a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
span:not(.zoom-btn) {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||