Compare commits
134 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccc59ea893 | ||
|
|
0b409f38d6 | ||
|
|
f9878ed88a | ||
|
|
e43e85480d | ||
|
|
b97170d1b2 | ||
|
|
b9aa1d574a | ||
|
|
dd7b06d7e5 | ||
|
|
ddafcfba10 | ||
|
|
da5b8c408a | ||
|
|
fb35d42fc4 | ||
|
|
dfd5d4c8b7 | ||
|
|
e5309cedee | ||
|
|
d335f57a1a | ||
|
|
c703d9c197 | ||
|
|
87a0ceb5b0 | ||
|
|
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 | ||
|
|
f728191a8f | ||
|
|
dfa8b51a53 | ||
|
|
b2c13121fd | ||
|
|
d28adb61a4 | ||
|
|
9a7d5a3834 | ||
|
|
2037798fbe | ||
|
|
85bd0ad015 | ||
|
|
e1557a51a3 | ||
|
|
1ecc6f136f | ||
|
|
53b3061b03 | ||
|
|
3d2f6a2330 | ||
|
|
3b1470f28f | ||
|
|
100268448a | ||
|
|
51f67bb2c2 | ||
|
|
7be126cf5f | ||
|
|
f2f5d3ac15 | ||
|
|
34c45e0105 | ||
|
|
f9333f5f78 |
@@ -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
|
|
||||||
|
|||||||
139
.eslintrc
@@ -1,139 +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-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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
138
.eslintrc.cjs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/* 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-underscore-dangle': 'off',
|
||||||
|
'no-nested-ternary': 'off',
|
||||||
|
'no-console': 'off',
|
||||||
|
'no-await-in-loop': 'off',
|
||||||
|
'no-continue': 'off',
|
||||||
|
'no-restricted-syntax': 'off',
|
||||||
|
'no-return-assign': 'off',
|
||||||
|
'no-unused-expressions': 'off',
|
||||||
|
'no-return-await': 'off',
|
||||||
|
'no-plusplus': 'off',
|
||||||
|
'no-param-reassign': 'off',
|
||||||
|
'no-shadow': 'off',
|
||||||
|
'guard-for-in': 'off',
|
||||||
|
'import/extensions': 'off',
|
||||||
|
'import/no-unresolved': 'off',
|
||||||
|
'import/no-extraneous-dependencies': 'off',
|
||||||
|
'import/prefer-default-export': 'off',
|
||||||
|
'import/first': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'vue/first-attribute-linebreak': 0,
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'no-unused-vars': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'no-use-before-define': 'off',
|
||||||
|
'@typescript-eslint/no-use-before-define': 'off',
|
||||||
|
'@typescript-eslint/ban-ts-comment': 'off',
|
||||||
|
'@typescript-eslint/ban-types': 'off',
|
||||||
|
'class-methods-use-this': 'off',
|
||||||
|
'simple-import-sort/imports': 'error',
|
||||||
|
'simple-import-sort/exports': 'error'
|
||||||
|
},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['*.vue'],
|
||||||
|
rules: {
|
||||||
|
'vue/component-name-in-template-casing': [2, 'kebab-case'],
|
||||||
|
'vue/require-default-prop': 0,
|
||||||
|
'vue/multi-word-component-names': 0,
|
||||||
|
'vue/no-reserved-props': 0,
|
||||||
|
'vue/no-v-html': 0,
|
||||||
|
'vue-scoped-css/enforce-style-type': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
allows: ['scoped']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
|
// 需要行尾分号
|
||||||
|
'prettier/prettier': ['error', { endOfLine: 'auto' }]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['*.ts', '*.tsx'],
|
||||||
|
rules: {
|
||||||
|
'max-classes-per-file': 'off',
|
||||||
|
'no-await-in-loop': 'off',
|
||||||
|
'dot-notation': 'off',
|
||||||
|
'constructor-super': 'off',
|
||||||
|
'getter-return': 'off',
|
||||||
|
'no-const-assign': 'off',
|
||||||
|
'no-dupe-args': 'off',
|
||||||
|
'no-dupe-class-members': 'off',
|
||||||
|
'no-dupe-keys': 'off',
|
||||||
|
'no-func-assign': 'off',
|
||||||
|
'no-import-assign': 'off',
|
||||||
|
'no-new-symbol': 'off',
|
||||||
|
'no-obj-calls': 'off',
|
||||||
|
'no-redeclare': 'off',
|
||||||
|
'no-setter-return': 'off',
|
||||||
|
'no-this-before-super': 'off',
|
||||||
|
'no-undef': 'off',
|
||||||
|
'no-unreachable': 'off',
|
||||||
|
'no-unsafe-negation': 'off',
|
||||||
|
'no-var': 'error',
|
||||||
|
'prefer-const': 'error',
|
||||||
|
'prefer-rest-params': 'error',
|
||||||
|
'prefer-spread': 'error',
|
||||||
|
'valid-typeof': 'off',
|
||||||
|
'consistent-return': 'off',
|
||||||
|
'no-promise-executor-return': 'off',
|
||||||
|
'prefer-promise-reject-errors': 'off',
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
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 }}
|
||||||
4
.gitignore
vendored
@@ -18,3 +18,7 @@ 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
|
||||||
17
CHANGELOG.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# 更新日志
|
||||||
|
|
||||||
|
## v4.0.0
|
||||||
|
|
||||||
|
### ✨ 新功能
|
||||||
|
- **音效均衡器 (EQ)**: 新增音效调节功能,支持低音、高音等实时调整,并提供流行、摇滚、古典等多种预设。
|
||||||
|
- **歌词体验升级**: 优化歌词窗口交互,滚动更流畅,同步更精准。
|
||||||
|
- **播放状态记忆**: 应用重启后自动恢复上次的播放进度和状态。
|
||||||
|
|
||||||
|
### 🔧 功能优化
|
||||||
|
- **音乐列表**: 优化列表加载速度和响应性能,大量歌曲也能快速加载。
|
||||||
|
- **随机播放修复**: 修复手动点击"下一首"按钮不随机的问题。
|
||||||
|
- **下载体验优化**: 改进网页版下载代理,提高下载速度和成功率。
|
||||||
|
|
||||||
|
### 🧹 其他改进
|
||||||
|
- **细节优化**: 改进按钮反馈、音量调节等交互细节,提升整体使用体验。
|
||||||
|
- 其他已知问题优化
|
||||||
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.
|
|
||||||
100
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移动端
|
||||||
@@ -16,10 +26,14 @@
|
|||||||
## 预览地址
|
## 预览地址
|
||||||
[http://mc.alger.fun/](http://mc.alger.fun/)
|
[http://mc.alger.fun/](http://mc.alger.fun/)
|
||||||
|
|
||||||
|
QQ群:789288579
|
||||||
|
|
||||||
## 软件截图
|
## 软件截图
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
@@ -28,69 +42,19 @@
|
|||||||
- TypeScript - JavaScript 的超集,添加了类型系统
|
- TypeScript - JavaScript 的超集,添加了类型系统
|
||||||
- Electron - 跨平台桌面应用开发框架
|
- Electron - 跨平台桌面应用开发框架
|
||||||
- Vite - 下一代前端构建工具
|
- Vite - 下一代前端构建工具
|
||||||
|
|
||||||
### UI 框架
|
|
||||||
- Naive UI - 基于 Vue 3 的组件库
|
- Naive UI - 基于 Vue 3 的组件库
|
||||||
|
|
||||||
### 项目特点
|
|
||||||
- 完整的类型支持(TypeScript)
|
|
||||||
- 模块化设计
|
|
||||||
- 自动化组件和 API 导入
|
|
||||||
- 多平台支持(Web、Desktop、Mobile Web)
|
|
||||||
- 构建优化(代码分割、压缩)
|
|
||||||
|
|
||||||
## 咖啡☕️
|
## 赞赏☕️
|
||||||
| 微信 | 支付宝 |
|
[赞赏列表](http://donate.alger.fun/)
|
||||||
|
| 微信赞赏 | 支付宝赞赏 |
|
||||||
| :--------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------: |
|
| :--------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------: |
|
||||||
| <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> <br><small>喝点咖啡继续干</small> | <img src="https://github.com/algerkong/algerkong/blob/main/alipay.jpg?raw=true" alt="Wechat QRcode" width=200> <br><small>来包辣条吧~</small> |
|
||||||
|
|
||||||
## 项目运行
|
|
||||||
```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
|
|
||||||
[](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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 限制只能启动一个应用
|
|
||||||
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,75 +0,0 @@
|
|||||||
const { BrowserWindow } = require('electron');
|
|
||||||
const path = require('path');
|
|
||||||
const config = require('./config');
|
|
||||||
|
|
||||||
let lyricWindow = null;
|
|
||||||
|
|
||||||
const createWin = () => {
|
|
||||||
lyricWindow = new BrowserWindow({
|
|
||||||
width: 800,
|
|
||||||
height: 300,
|
|
||||||
frame: false,
|
|
||||||
show: false,
|
|
||||||
transparent: true,
|
|
||||||
hasShadow: false,
|
|
||||||
webPreferences: {
|
|
||||||
nodeIntegration: false,
|
|
||||||
contextIsolation: true,
|
|
||||||
preload: `${__dirname}/preload.js`,
|
|
||||||
webSecurity: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadLyricWindow = (ipcMain) => {
|
|
||||||
ipcMain.on('open-lyric', () => {
|
|
||||||
if (lyricWindow) {
|
|
||||||
if (lyricWindow.isMinimized()) lyricWindow.restore();
|
|
||||||
lyricWindow.focus();
|
|
||||||
lyricWindow.show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
createWin();
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
lyricWindow.webContents.openDevTools({ mode: 'detach' });
|
|
||||||
lyricWindow.loadURL(`http://localhost:${config.development.lyricPort}/#/lyric`);
|
|
||||||
} else {
|
|
||||||
const distPath = path.resolve(__dirname, config.production.distPath);
|
|
||||||
lyricWindow.loadURL(`file://${distPath}/index.html#/lyric`);
|
|
||||||
}
|
|
||||||
|
|
||||||
lyricWindow.setMinimumSize(600, 200);
|
|
||||||
|
|
||||||
// 隐藏任务栏
|
|
||||||
lyricWindow.setSkipTaskbar(true);
|
|
||||||
|
|
||||||
lyricWindow.show();
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('send-lyric', (e, data) => {
|
|
||||||
if (lyricWindow) {
|
|
||||||
lyricWindow.webContents.send('receive-lyric', data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('top-lyric', (e, data) => {
|
|
||||||
lyricWindow.setAlwaysOnTop(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('close-lyric', () => {
|
|
||||||
lyricWindow.close();
|
|
||||||
lyricWindow = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('mouseenter-lyric', () => {
|
|
||||||
lyricWindow.setIgnoreMouseEvents(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('mouseleave-lyric', () => {
|
|
||||||
lyricWindow.setIgnoreMouseEvents(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
loadLyricWindow,
|
|
||||||
};
|
|
||||||
@@ -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;
|
|
||||||
81
index.html
@@ -1,81 +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>
|
|
||||||
<!-- 收款码图片预加载 -->
|
|
||||||
<link rel="preload" as="image" href="https://github.com/algerkong/algerkong/blob/main/alipay.jpg?raw=true" />
|
|
||||||
<link rel="preload" as="image" href="https://github.com/algerkong/algerkong/blob/main/wechat.jpg?raw=true" />
|
|
||||||
<script type="module" src="/src/main.ts"></script>
|
|
||||||
|
|
||||||
<!-- 结构化数据 -->
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "WebApplication",
|
|
||||||
"name": "网抑云音乐",
|
|
||||||
"applicationCategory": "MultimediaApplication",
|
|
||||||
"operatingSystem": "Web, Windows, MacOS",
|
|
||||||
"author": {
|
|
||||||
"@type": "Person",
|
|
||||||
"name": "AlgerKong",
|
|
||||||
"url": "https://github.com/algerkong"
|
|
||||||
},
|
|
||||||
"description": "一款免费的在线音乐播放器,支持在线播放、歌词显示、音乐下载等功能。",
|
|
||||||
"offers": {
|
|
||||||
"@type": "Offer",
|
|
||||||
"price": "0",
|
|
||||||
"priceCurrency": "CNY"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
167
package.json
@@ -1,66 +1,167 @@
|
|||||||
{
|
{
|
||||||
"name": "alger-music",
|
"name": "AlgerMusicPlayer",
|
||||||
"version": "2.1.0",
|
"version": "4.0.0",
|
||||||
"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",
|
||||||
|
"tunajs": "^1.0.15"
|
||||||
|
},
|
||||||
|
"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 |
39
src/App.vue
@@ -1,39 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="app-container" :class="{ mobile: isMobile }">
|
|
||||||
<n-config-provider :theme="darkTheme">
|
|
||||||
<n-dialog-provider>
|
|
||||||
<router-view></router-view>
|
|
||||||
</n-dialog-provider>
|
|
||||||
</n-config-provider>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { darkTheme } from 'naive-ui';
|
|
||||||
import { onMounted } from 'vue';
|
|
||||||
|
|
||||||
import store from '@/store';
|
|
||||||
|
|
||||||
import { isMobile } from './utils';
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
store.dispatch('initializeSettings');
|
|
||||||
});
|
|
||||||
</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,44 +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-6">
|
|
||||||
<div class="flex flex-col items-center gap-2">
|
|
||||||
<n-image :src="alipayQR" alt="支付宝收款码" class="w-32 h-32 rounded-lg" 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" preview-disabled />
|
|
||||||
<span class="text-sm text-gray-100">微信支付</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-popover>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { NButton, NImage, NPopover } from 'naive-ui';
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
alipayQR: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
wechatQR: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,299 +0,0 @@
|
|||||||
<template>
|
|
||||||
<n-drawer
|
|
||||||
:show="show"
|
|
||||||
:height="isMobile ? '100vh' : '80vh'"
|
|
||||||
placement="bottom"
|
|
||||||
block-scroll
|
|
||||||
mask-closable
|
|
||||||
:style="{ backgroundColor: 'transparent' }"
|
|
||||||
@mask-click="close"
|
|
||||||
>
|
|
||||||
<div class="music-page">
|
|
||||||
<div class="music-header h-12 flex items-center justify-between">
|
|
||||||
<n-ellipsis :line-clamp="1">
|
|
||||||
<div class="music-title">
|
|
||||||
{{ name }}
|
|
||||||
</div>
|
|
||||||
</n-ellipsis>
|
|
||||||
<div class="music-close">
|
|
||||||
<i class="icon iconfont icon-icon_error" @click="close"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="music-content">
|
|
||||||
<!-- 左侧歌单信息 -->
|
|
||||||
<div class="music-info">
|
|
||||||
<div class="music-cover">
|
|
||||||
<n-image
|
|
||||||
:src="getImgUrl(listInfo?.coverImgUrl, '300y300')"
|
|
||||||
class="cover-img"
|
|
||||||
preview-disabled
|
|
||||||
:class="setAnimationClass('animate__fadeIn')"
|
|
||||||
object-fit="cover"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="music-detail">
|
|
||||||
<div v-if="listInfo?.creator" class="creator-info">
|
|
||||||
<n-avatar round :size="24" :src="getImgUrl(listInfo.creator.avatarUrl, '50y50')" />
|
|
||||||
<span class="creator-name">{{ listInfo.creator.nickname }}</span>
|
|
||||||
</div>
|
|
||||||
<div v-if="listInfo?.description" class="music-desc">
|
|
||||||
<n-ellipsis :line-clamp="isMobile ? 3 : 10">
|
|
||||||
{{ listInfo.description }}
|
|
||||||
</n-ellipsis>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 右侧歌曲列表 -->
|
|
||||||
<div class="music-list-container">
|
|
||||||
<div v-loading="loading" class="music-list">
|
|
||||||
<n-scrollbar @scroll="handleScroll">
|
|
||||||
<div v-loading="loading || !songList.length" class="music-list-content">
|
|
||||||
<div
|
|
||||||
v-for="(item, index) in displayedSongs"
|
|
||||||
:key="item.id"
|
|
||||||
class="double-item"
|
|
||||||
:class="setAnimationClass('animate__bounceInUp')"
|
|
||||||
:style="getItemAnimationDelay(index)"
|
|
||||||
>
|
|
||||||
<song-item :item="formatDetail(item)" @play="handlePlay" />
|
|
||||||
</div>
|
|
||||||
<div v-if="isLoadingMore" class="loading-more">加载更多...</div>
|
|
||||||
<play-bottom />
|
|
||||||
</div>
|
|
||||||
</n-scrollbar>
|
|
||||||
</div>
|
|
||||||
<play-bottom />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-drawer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useStore } from 'vuex';
|
|
||||||
|
|
||||||
import { getMusicDetail } from '@/api/music';
|
|
||||||
import SongItem from '@/components/common/SongItem.vue';
|
|
||||||
import { getImgUrl, isMobile, setAnimationClass, setAnimationDelay } from '@/utils';
|
|
||||||
|
|
||||||
import PlayBottom from './common/PlayBottom.vue';
|
|
||||||
|
|
||||||
const store = useStore();
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
show: boolean;
|
|
||||||
name: string;
|
|
||||||
songList: any[];
|
|
||||||
loading?: boolean;
|
|
||||||
listInfo?: {
|
|
||||||
trackIds: { id: number }[];
|
|
||||||
[key: string]: any;
|
|
||||||
};
|
|
||||||
}>();
|
|
||||||
const emit = defineEmits(['update:show', 'update:loading']);
|
|
||||||
|
|
||||||
const page = ref(0);
|
|
||||||
const pageSize = 20;
|
|
||||||
const isLoadingMore = ref(false);
|
|
||||||
const displayedSongs = ref<any[]>([]);
|
|
||||||
|
|
||||||
// 计算总数
|
|
||||||
const total = computed(() => {
|
|
||||||
if (props.listInfo?.trackIds) {
|
|
||||||
return props.listInfo.trackIds.length;
|
|
||||||
}
|
|
||||||
return props.songList.length;
|
|
||||||
});
|
|
||||||
|
|
||||||
const formatDetail = computed(() => (detail: any) => {
|
|
||||||
const song = {
|
|
||||||
artists: detail.ar,
|
|
||||||
name: detail.al.name,
|
|
||||||
id: detail.al.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
detail.song = song;
|
|
||||||
detail.picUrl = detail.al.picUrl;
|
|
||||||
return detail;
|
|
||||||
});
|
|
||||||
|
|
||||||
const handlePlay = () => {
|
|
||||||
const tracks = props.songList || [];
|
|
||||||
store.commit(
|
|
||||||
'setPlayList',
|
|
||||||
tracks.map((item) => ({
|
|
||||||
...item,
|
|
||||||
picUrl: item.al.picUrl,
|
|
||||||
song: {
|
|
||||||
artists: item.ar,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const close = () => {
|
|
||||||
emit('update:show', false);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 优化加载更多歌曲的函数
|
|
||||||
const loadMoreSongs = async () => {
|
|
||||||
if (isLoadingMore.value || displayedSongs.value.length >= total.value) return;
|
|
||||||
|
|
||||||
isLoadingMore.value = true;
|
|
||||||
try {
|
|
||||||
if (props.listInfo?.trackIds) {
|
|
||||||
// 如果有 trackIds,需要分批请求歌曲详情
|
|
||||||
const start = page.value * pageSize;
|
|
||||||
const end = Math.min((page.value + 1) * pageSize, total.value);
|
|
||||||
const trackIds = props.listInfo.trackIds.slice(start, end).map((item) => item.id);
|
|
||||||
|
|
||||||
if (trackIds.length > 0) {
|
|
||||||
const { data } = await getMusicDetail(trackIds);
|
|
||||||
displayedSongs.value = [...displayedSongs.value, ...data.songs];
|
|
||||||
page.value++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 如果没有 trackIds,直接使用 songList 分页
|
|
||||||
const start = page.value * pageSize;
|
|
||||||
const end = Math.min((page.value + 1) * pageSize, props.songList.length);
|
|
||||||
const newSongs = props.songList.slice(start, end);
|
|
||||||
displayedSongs.value = [...displayedSongs.value, ...newSongs];
|
|
||||||
page.value++;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载歌曲失败:', error);
|
|
||||||
} finally {
|
|
||||||
isLoadingMore.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getItemAnimationDelay = (index: number) => {
|
|
||||||
const currentPageIndex = index % pageSize;
|
|
||||||
return setAnimationDelay(currentPageIndex, 20);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 修改滚动处理函数
|
|
||||||
const handleScroll = (e: Event) => {
|
|
||||||
const target = e.target as HTMLElement;
|
|
||||||
if (!target) return;
|
|
||||||
|
|
||||||
const { scrollTop, scrollHeight, clientHeight } = target;
|
|
||||||
if (scrollHeight - scrollTop - clientHeight < 100 && !isLoadingMore.value) {
|
|
||||||
loadMoreSongs();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 监听 songList 变化,重置分页状态
|
|
||||||
watch(
|
|
||||||
() => props.songList,
|
|
||||||
(newSongs) => {
|
|
||||||
page.value = 0;
|
|
||||||
displayedSongs.value = newSongs.slice(0, pageSize);
|
|
||||||
if (newSongs.length > pageSize) {
|
|
||||||
page.value = 1;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.music {
|
|
||||||
&-title {
|
|
||||||
@apply text-xl font-bold text-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-page {
|
|
||||||
@apply px-8 w-full h-full bg-black bg-opacity-75 rounded-t-2xl;
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
}
|
|
||||||
|
|
||||||
&-close {
|
|
||||||
@apply cursor-pointer text-white flex gap-2 items-center;
|
|
||||||
.icon {
|
|
||||||
@apply text-3xl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-content {
|
|
||||||
@apply flex h-[calc(100%-60px)];
|
|
||||||
}
|
|
||||||
|
|
||||||
&-info {
|
|
||||||
@apply w-[25%] flex-shrink-0 pr-8 flex flex-col;
|
|
||||||
|
|
||||||
.music-cover {
|
|
||||||
@apply w-full aspect-square rounded-lg overflow-hidden mb-4;
|
|
||||||
.cover-img {
|
|
||||||
@apply w-full h-full object-cover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.music-detail {
|
|
||||||
@apply flex flex-col flex-grow;
|
|
||||||
|
|
||||||
.creator-info {
|
|
||||||
@apply flex items-center mb-4;
|
|
||||||
.creator-name {
|
|
||||||
@apply ml-2 text-sm text-gray-300;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.music-desc {
|
|
||||||
@apply text-sm text-gray-400;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-list-container {
|
|
||||||
@apply flex-grow min-h-0 flex flex-col relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-list {
|
|
||||||
@apply flex-grow min-h-0;
|
|
||||||
|
|
||||||
:deep(.n-virtual-list__scroll) {
|
|
||||||
scrollbar-width: none;
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile {
|
|
||||||
.music-page {
|
|
||||||
@apply px-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.music-content {
|
|
||||||
@apply flex-col;
|
|
||||||
}
|
|
||||||
|
|
||||||
.music-info {
|
|
||||||
@apply w-full pr-0 mb-2 flex flex-row;
|
|
||||||
|
|
||||||
.music-cover {
|
|
||||||
@apply w-[100px] h-[100px] rounded-lg overflow-hidden mb-4;
|
|
||||||
}
|
|
||||||
.music-detail {
|
|
||||||
@apply flex-1 ml-4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-more {
|
|
||||||
@apply text-center text-white py-10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.double-list {
|
|
||||||
.double-item {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.song-item {
|
|
||||||
background-color: #191919;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,119 +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</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>
|
|
||||||
</n-modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInstall = async () => {
|
|
||||||
// 新页面打开
|
|
||||||
// 识别当前环境是 mac 还是 windows
|
|
||||||
// const os = navigator.platform;
|
|
||||||
// const isMac = os.includes('Mac');
|
|
||||||
// const isWindows = os.includes('Win');
|
|
||||||
// const urls = {
|
|
||||||
// mac: 'http://file.alger.fun/d/ali/%E8%BD%AF%E4%BB%B6/AlgerMusic/AlgerMusic.dmg',
|
|
||||||
// windows: 'http://file.alger.fun/d/ali/%E8%BD%AF%E4%BB%B6/AlgerMusic/AlgerMusic.exe',
|
|
||||||
// };
|
|
||||||
// // 根据操作系统选择下载链接
|
|
||||||
// let downloadUrl = '';
|
|
||||||
// if (isMac) {
|
|
||||||
// downloadUrl = urls.mac;
|
|
||||||
// } else if (isWindows) {
|
|
||||||
// downloadUrl = urls.windows;
|
|
||||||
// }
|
|
||||||
const downloadUrl = 'https://github.com/algerkong/AlgerMusicPlayer/releases';
|
|
||||||
if (downloadUrl) {
|
|
||||||
window.open(downloadUrl, '_blank');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// 如果是 electron 环境,不显示安装提示
|
|
||||||
if (isElectron.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否已经点击过"暂不安装"
|
|
||||||
const isDismissed = localStorage.getItem('installPromptDismissed') === 'true';
|
|
||||||
if (isDismissed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showModal.value = true;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.install-app-modal {
|
|
||||||
:deep(.n-modal) {
|
|
||||||
@apply max-w-sm;
|
|
||||||
}
|
|
||||||
.modal-content {
|
|
||||||
@apply p-4;
|
|
||||||
.modal-header {
|
|
||||||
@apply flex items-center mb-6;
|
|
||||||
.app-icon {
|
|
||||||
@apply w-16 h-16 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;
|
|
||||||
.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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,317 +0,0 @@
|
|||||||
import { computed, ref } from 'vue';
|
|
||||||
|
|
||||||
import { audioService } from '@/services/audioService';
|
|
||||||
import store from '@/store';
|
|
||||||
import type { ILyricText, SongResult } from '@/type/music';
|
|
||||||
|
|
||||||
const windowData = window as any;
|
|
||||||
|
|
||||||
export const isElectron = computed(() => !!windowData.electronAPI);
|
|
||||||
|
|
||||||
export const lrcArray = ref<ILyricText[]>([]); // 歌词数组
|
|
||||||
export const lrcTimeArray = ref<number[]>([]); // 歌词时间数组
|
|
||||||
export const nowTime = ref(0); // 当前播放时间
|
|
||||||
export const allTime = ref(0); // 总播放时间
|
|
||||||
export const nowIndex = ref(0); // 当前播放歌词
|
|
||||||
export const correctionTime = ref(0.4); // 歌词矫正时间Correction time
|
|
||||||
export const currentLrcProgress = ref(0); // 来存储当前歌词的进度
|
|
||||||
export const playMusic = computed(() => store.state.playMusic as SongResult); // 当前播放歌曲
|
|
||||||
export const sound = ref<Howl | null>(audioService.getCurrentSound());
|
|
||||||
|
|
||||||
document.onkeyup = (e) => {
|
|
||||||
switch (e.code) {
|
|
||||||
case 'Space':
|
|
||||||
if (store.state.play) {
|
|
||||||
store.commit('setPlayMusic', false);
|
|
||||||
audioService.getCurrentSound()?.pause();
|
|
||||||
} else {
|
|
||||||
store.commit('setPlayMusic', true);
|
|
||||||
audioService.getCurrentSound()?.play();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => store.state.playMusicUrl,
|
|
||||||
(newVal) => {
|
|
||||||
if (newVal) {
|
|
||||||
audioService.play(newVal);
|
|
||||||
sound.value = audioService.getCurrentSound();
|
|
||||||
audioServiceOn(audioService);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => store.state.playMusic,
|
|
||||||
() => {
|
|
||||||
nextTick(() => {
|
|
||||||
lrcArray.value = playMusic.value.lyric?.lrcArray || [];
|
|
||||||
lrcTimeArray.value = playMusic.value.lyric?.lrcTimeArray || [];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deep: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export const audioServiceOn = (audio: typeof audioService) => {
|
|
||||||
let interval: any = null;
|
|
||||||
|
|
||||||
// 监听播放
|
|
||||||
audio.onPlay(() => {
|
|
||||||
store.commit('setPlayMusic', true);
|
|
||||||
interval = setInterval(() => {
|
|
||||||
nowTime.value = sound.value?.seek() as number;
|
|
||||||
allTime.value = sound.value?.duration() as number;
|
|
||||||
const newIndex = getLrcIndex(nowTime.value);
|
|
||||||
if (newIndex !== nowIndex.value) {
|
|
||||||
nowIndex.value = newIndex;
|
|
||||||
currentLrcProgress.value = 0;
|
|
||||||
}
|
|
||||||
if (isElectron.value) {
|
|
||||||
sendLyricToWin();
|
|
||||||
}
|
|
||||||
}, 50);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听暂停
|
|
||||||
audio.onPause(() => {
|
|
||||||
store.commit('setPlayMusic', false);
|
|
||||||
clearInterval(interval);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听结束
|
|
||||||
audio.onEnd(() => {
|
|
||||||
handleEnded();
|
|
||||||
store.commit('nextPlay');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const play = () => {
|
|
||||||
audioService.getCurrentSound()?.play();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const pause = () => {
|
|
||||||
audioService.getCurrentSound()?.pause();
|
|
||||||
};
|
|
||||||
|
|
||||||
const isPlaying = computed(() => store.state.play as boolean);
|
|
||||||
|
|
||||||
// 增加矫正时间
|
|
||||||
export const addCorrectionTime = (time: number) => (correctionTime.value += time);
|
|
||||||
|
|
||||||
// 减少矫正时间
|
|
||||||
export const reduceCorrectionTime = (time: number) => (correctionTime.value -= time);
|
|
||||||
|
|
||||||
// 获取当前播放歌词
|
|
||||||
export const isCurrentLrc = (index: number, time: number): boolean => {
|
|
||||||
const currentTime = lrcTimeArray.value[index];
|
|
||||||
const nextTime = lrcTimeArray.value[index + 1];
|
|
||||||
const nowTime = time + correctionTime.value;
|
|
||||||
const isTrue = nowTime > currentTime && nowTime < nextTime;
|
|
||||||
return isTrue;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取当前播放歌词INDEX
|
|
||||||
export const getLrcIndex = (time: number): number => {
|
|
||||||
for (let i = 0; i < lrcTimeArray.value.length; i++) {
|
|
||||||
if (isCurrentLrc(i, time)) {
|
|
||||||
nowIndex.value = i;
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nowIndex.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取当前播放歌词进度
|
|
||||||
const currentLrcTiming = computed(() => {
|
|
||||||
const start = lrcTimeArray.value[nowIndex.value] || 0;
|
|
||||||
const end = lrcTimeArray.value[nowIndex.value + 1] || start + 1;
|
|
||||||
return { start, end };
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取歌词样式
|
|
||||||
export const getLrcStyle = (index: number) => {
|
|
||||||
if (index === nowIndex.value) {
|
|
||||||
return {
|
|
||||||
backgroundImage: `linear-gradient(to right, #ffffff ${currentLrcProgress.value}%, #ffffff8a ${currentLrcProgress.value}%)`,
|
|
||||||
backgroundClip: 'text',
|
|
||||||
WebkitBackgroundClip: 'text',
|
|
||||||
color: 'transparent',
|
|
||||||
transition: 'background-image 0.1s linear',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
// 播放进度
|
|
||||||
export const useLyricProgress = () => {
|
|
||||||
let animationFrameId: number | null = null;
|
|
||||||
|
|
||||||
const updateProgress = () => {
|
|
||||||
if (!isPlaying.value) return;
|
|
||||||
const currentSound = sound.value;
|
|
||||||
if (!currentSound) return;
|
|
||||||
|
|
||||||
const { start, end } = currentLrcTiming.value;
|
|
||||||
const duration = end - start;
|
|
||||||
const elapsed = (currentSound.seek() as number) - start;
|
|
||||||
currentLrcProgress.value = Math.min(Math.max((elapsed / duration) * 100, 0), 100);
|
|
||||||
|
|
||||||
animationFrameId = requestAnimationFrame(updateProgress);
|
|
||||||
};
|
|
||||||
|
|
||||||
const startProgressAnimation = () => {
|
|
||||||
if (!animationFrameId && isPlaying.value) {
|
|
||||||
updateProgress();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const stopProgressAnimation = () => {
|
|
||||||
if (animationFrameId) {
|
|
||||||
cancelAnimationFrame(animationFrameId);
|
|
||||||
animationFrameId = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(isPlaying, (newIsPlaying) => {
|
|
||||||
if (newIsPlaying) {
|
|
||||||
startProgressAnimation();
|
|
||||||
} else {
|
|
||||||
stopProgressAnimation();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (isPlaying.value) {
|
|
||||||
startProgressAnimation();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
stopProgressAnimation();
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
currentLrcProgress,
|
|
||||||
getLrcStyle,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// 设置当前播放时间
|
|
||||||
export const setAudioTime = (index: number) => {
|
|
||||||
const currentSound = sound.value;
|
|
||||||
if (!currentSound) return;
|
|
||||||
|
|
||||||
currentSound.seek(lrcTimeArray.value[index]);
|
|
||||||
currentSound.play();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取当前播放的歌词
|
|
||||||
export const getCurrentLrc = () => {
|
|
||||||
const index = getLrcIndex(nowTime.value);
|
|
||||||
return {
|
|
||||||
currentLrc: lrcArray.value[index],
|
|
||||||
nextLrc: lrcArray.value[index + 1],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取一句歌词播放时间几秒到几秒
|
|
||||||
export const getLrcTimeRange = (index: number) => ({
|
|
||||||
currentTime: lrcTimeArray.value[index],
|
|
||||||
nextTime: lrcTimeArray.value[index + 1],
|
|
||||||
});
|
|
||||||
|
|
||||||
// 监听歌词数组变化,当切换歌曲时重新初始化歌词窗口
|
|
||||||
watch(
|
|
||||||
() => lrcArray.value,
|
|
||||||
(newLrcArray) => {
|
|
||||||
if (newLrcArray.length > 0 && isElectron.value) {
|
|
||||||
// 重新初始化歌词数据
|
|
||||||
initLyricWindow();
|
|
||||||
// 发送当前状态
|
|
||||||
sendLyricToWin();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// 监听播放状态变化
|
|
||||||
watch(isPlaying, (newIsPlaying) => {
|
|
||||||
if (isElectron.value) {
|
|
||||||
sendLyricToWin(newIsPlaying);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 处理歌曲结束
|
|
||||||
export const handleEnded = () => {
|
|
||||||
if (isElectron.value) {
|
|
||||||
setTimeout(() => {
|
|
||||||
initLyricWindow();
|
|
||||||
sendLyricToWin();
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化歌词数据
|
|
||||||
export const initLyricWindow = () => {
|
|
||||||
if (!isElectron.value) return;
|
|
||||||
try {
|
|
||||||
if (lrcArray.value.length > 0) {
|
|
||||||
console.log('Initializing lyric window with data:', {
|
|
||||||
lrcArray: lrcArray.value,
|
|
||||||
lrcTimeArray: lrcTimeArray.value,
|
|
||||||
allTime: allTime.value,
|
|
||||||
});
|
|
||||||
|
|
||||||
const staticData = {
|
|
||||||
type: 'init',
|
|
||||||
lrcArray: lrcArray.value,
|
|
||||||
lrcTimeArray: lrcTimeArray.value,
|
|
||||||
allTime: allTime.value,
|
|
||||||
};
|
|
||||||
windowData.electronAPI.sendLyric(JSON.stringify(staticData));
|
|
||||||
} else {
|
|
||||||
console.log('No lyrics available for initialization');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error initializing lyric window:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 发送歌词更新数据
|
|
||||||
export const sendLyricToWin = (isPlay: boolean = true) => {
|
|
||||||
if (!isElectron.value) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (lrcArray.value.length > 0) {
|
|
||||||
const nowIndex = getLrcIndex(nowTime.value);
|
|
||||||
const updateData = {
|
|
||||||
type: 'update',
|
|
||||||
nowIndex,
|
|
||||||
nowTime: nowTime.value,
|
|
||||||
startCurrentTime: lrcTimeArray.value[nowIndex],
|
|
||||||
nextTime: lrcTimeArray.value[nowIndex + 1],
|
|
||||||
isPlay,
|
|
||||||
};
|
|
||||||
windowData.electronAPI.sendLyric(JSON.stringify(updateData));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error sending lyric update:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const openLyric = () => {
|
|
||||||
if (!isElectron.value) return;
|
|
||||||
console.log('Opening lyric window');
|
|
||||||
windowData.electronAPI.openLyric();
|
|
||||||
|
|
||||||
// 延迟一下初始化,确保窗口已经创建
|
|
||||||
setTimeout(() => {
|
|
||||||
console.log('Initializing lyric window after delay');
|
|
||||||
initLyricWindow();
|
|
||||||
sendLyricToWin();
|
|
||||||
}, 500);
|
|
||||||
};
|
|
||||||
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'
|
||||||
|
}
|
||||||
|
};
|
||||||
76
src/i18n/lang/en-US/player.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
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',
|
||||||
|
songNum: 'Song Number: {num}',
|
||||||
|
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',
|
||||||
|
noSongPlaying: 'No song playing',
|
||||||
|
eq: 'Equalizer',
|
||||||
|
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}'
|
||||||
|
},
|
||||||
|
eq: {
|
||||||
|
title: 'Equalizer',
|
||||||
|
reset: 'Reset',
|
||||||
|
on: 'On',
|
||||||
|
off: 'Off',
|
||||||
|
bass: 'Bass',
|
||||||
|
midrange: 'Midrange',
|
||||||
|
treble: 'Treble',
|
||||||
|
presets: {
|
||||||
|
flat: 'Flat',
|
||||||
|
pop: 'Pop',
|
||||||
|
rock: 'Rock',
|
||||||
|
classical: 'Classical',
|
||||||
|
jazz: 'Jazz',
|
||||||
|
electronic: 'Electronic',
|
||||||
|
hiphop: 'Hip-Hop',
|
||||||
|
rb: 'R&B',
|
||||||
|
metal: 'Metal',
|
||||||
|
vocal: 'Vocal',
|
||||||
|
dance: 'Dance',
|
||||||
|
acoustic: 'Acoustic',
|
||||||
|
custom: 'Custom'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
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: '检查二维码状态时出错'
|
||||||
|
}
|
||||||
|
};
|
||||||
76
src/i18n/lang/zh-CN/player.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
export default {
|
||||||
|
nowPlaying: '正在播放',
|
||||||
|
playlist: '播放列表',
|
||||||
|
lyrics: '歌词',
|
||||||
|
previous: '上一个',
|
||||||
|
play: '播放',
|
||||||
|
pause: '暂停',
|
||||||
|
next: '下一个',
|
||||||
|
volumeUp: '音量增加',
|
||||||
|
volumeDown: '音量减少',
|
||||||
|
mute: '静音',
|
||||||
|
unmute: '取消静音',
|
||||||
|
songNum: '歌曲总数:{num}',
|
||||||
|
playMode: {
|
||||||
|
sequence: '顺序播放',
|
||||||
|
loop: '循环播放',
|
||||||
|
random: '随机播放'
|
||||||
|
},
|
||||||
|
fullscreen: {
|
||||||
|
enter: '全屏',
|
||||||
|
exit: '退出全屏'
|
||||||
|
},
|
||||||
|
close: '关闭',
|
||||||
|
modeHint: {
|
||||||
|
single: '单曲循环',
|
||||||
|
list: '自动播放下一个'
|
||||||
|
},
|
||||||
|
lrc: {
|
||||||
|
noLrc: '暂无歌词, 请欣赏'
|
||||||
|
},
|
||||||
|
playBar: {
|
||||||
|
expand: '展开歌词',
|
||||||
|
collapse: '收起歌词',
|
||||||
|
like: '喜欢',
|
||||||
|
lyric: '歌词',
|
||||||
|
noSongPlaying: '没有正在播放的歌曲',
|
||||||
|
eq: '均衡器',
|
||||||
|
playList: '播放列表',
|
||||||
|
playMode: {
|
||||||
|
sequence: '顺序播放',
|
||||||
|
loop: '循环播放',
|
||||||
|
random: '随机播放'
|
||||||
|
},
|
||||||
|
play: '开始播放',
|
||||||
|
pause: '暂停播放',
|
||||||
|
prev: '上一首',
|
||||||
|
next: '下一首',
|
||||||
|
volume: '音量',
|
||||||
|
favorite: '已收藏{name}',
|
||||||
|
unFavorite: '已取消收藏{name}'
|
||||||
|
},
|
||||||
|
eq: {
|
||||||
|
title: '均衡器',
|
||||||
|
reset: '重置',
|
||||||
|
on: '开启',
|
||||||
|
off: '关闭',
|
||||||
|
bass: '低音',
|
||||||
|
midrange: '中音',
|
||||||
|
treble: '高音',
|
||||||
|
presets: {
|
||||||
|
flat: '平坦',
|
||||||
|
pop: '流行',
|
||||||
|
rock: '摇滚',
|
||||||
|
classical: '古典',
|
||||||
|
jazz: '爵士',
|
||||||
|
electronic: '电子',
|
||||||
|
hiphop: '嘻哈',
|
||||||
|
rb: 'R&B',
|
||||||
|
metal: '金属',
|
||||||
|
vocal: '人声',
|
||||||
|
dance: '舞曲',
|
||||||
|
acoustic: '原声',
|
||||||
|
custom: '自定义'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
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,15 +0,0 @@
|
|||||||
/* ./src/index.css */
|
|
||||||
|
|
||||||
/*! @import */
|
|
||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
.n-image img {
|
|
||||||
background-color: #111111;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-el {
|
|
||||||
@apply overflow-ellipsis overflow-hidden whitespace-nowrap;
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="layout-page">
|
|
||||||
<div 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">
|
|
||||||
<n-message-provider>
|
|
||||||
<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>
|
|
||||||
</n-message-provider>
|
|
||||||
</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>
|
|
||||||