├── .browserslistrc ├── .commitlintrc.js ├── .dockerignore ├── .electron-builder.config.js ├── .env.browser ├── .env.electron.build ├── .env.electron.dev ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── 新功能.md │ └── 问题反馈.md └── workflows │ └── deploy.yml ├── .gitignore ├── .gitmodules ├── .husky └── pre-commit ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .stylelintrc.js ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .yarn └── plugins │ └── @yarnpkg │ └── plugin-workspace-tools.cjs ├── .yarnrc.yml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── build ├── icons │ ├── 1024x1024.png │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ ├── icon.icns │ └── icon.ico └── logo.ai ├── cypress.json ├── docker-compose.yml ├── electron.vite.config.js ├── index.html ├── lerna.json ├── lyrics.html ├── migrate.md ├── mirrori.sh ├── nginx.conf ├── package.json ├── packages └── unblock │ ├── .dockerignore │ ├── .gitignore │ ├── .npmignore │ ├── Dockerfile │ ├── LICENSE │ ├── README.md │ ├── app.js │ ├── babel.config.js │ ├── bridge.js │ ├── ca.crt │ ├── docker-compose.yml │ ├── endpoint.worker.js │ ├── package.json │ ├── server.crt │ ├── server.key │ ├── src │ ├── app.js │ ├── bridge.js │ ├── browser │ │ ├── README.md │ │ ├── background.html │ │ ├── background.js │ │ ├── convert.js │ │ ├── crypto.js │ │ ├── inject.js │ │ ├── manifest.json │ │ ├── request.js │ │ └── script.js │ ├── cache.js │ ├── cli.js │ ├── crypto.js │ ├── hook.js │ ├── kwDES.js │ ├── provider │ │ ├── baidu.js │ │ ├── find.js │ │ ├── insure.js │ │ ├── joox.js │ │ ├── kugou.js │ │ ├── kuwo.js │ │ ├── match.js │ │ ├── migu.js │ │ ├── netease.js │ │ ├── qq.js │ │ ├── select.js │ │ ├── xiami.js │ │ └── youtube.js │ ├── request.js │ ├── server.js │ └── sni.js │ ├── target │ └── npmlist.json │ └── test │ └── api.test.js ├── postcss.config.js ├── public ├── audio-effect │ ├── AliceInBones.wav │ ├── BlackSunGarden.wav │ ├── Car radio close.wav │ ├── Car radio wide.wav │ ├── CastilloDeLosTresReyesDelMorro.wav │ ├── CliffOfTheDawn.wav │ ├── CornOnTheLeash.wav │ ├── Erres tube radio.C.wav │ ├── FatMansMisery.wav │ ├── FooToFly.wav │ ├── Fredman_Mono_Vintage30_SPARC.wav │ ├── Fredman_Mono_Vintage30_Solid.wav │ ├── Fredman_Mono_Vintage30_Tube.wav │ ├── GreedSacrifice.wav │ ├── GreenBasketCase.wav │ ├── IslaMujeresCave.wav │ ├── LawrenceWelkCave.wav │ ├── Mono_3DoorTonite_SPARC.wav │ ├── Mono_3DoorTonite_Solid.wav │ ├── Mono_3DoorTonite_Tube.wav │ ├── Mono_AlterYourEyes_SPARC.wav │ ├── Mono_AlterYourEyes_Solid.wav │ ├── Mono_AlterYourEyes_Tube.wav │ ├── Mono_Bushlyerine_SPARC.wav │ ├── Mono_Bushlyerine_Solid.wav │ ├── Mono_Bushlyerine_Tube.wav │ ├── Mono_TheSpringsArentAlright_SPARC.wav │ ├── Mono_TheSpringsArentAlright_Solid.wav │ ├── Mono_TheSpringsArentAlright_Tube.wav │ ├── NaumburgBandshell.wav │ ├── ScorpYouLikeAHurricane.wav │ ├── Small portable ambient.wav │ ├── Small portable.wav │ ├── Small speaker mono.wav │ ├── SpoonGarden.wav │ ├── StanleyParkCliffs.wav │ ├── SweetChildOfGun.wav │ ├── ToolPot.wav │ ├── Very small speaker mono.wav │ ├── WoodruffLane.wav │ └── iron box mono.wav ├── favicon.ico ├── icon.png ├── loading-list.svg ├── loading.html └── loading.svg ├── script ├── build.js ├── core │ ├── core.js │ └── index.js ├── deploy.sh ├── read.js ├── render.js ├── server-local.js └── theme.js ├── snapshots ├── 231216.jpg └── home.jpg ├── src ├── api │ ├── index.ts │ ├── search.ts │ └── userinfo.ts ├── app │ ├── app.less │ ├── app.tsx │ ├── index.css │ ├── index.ts │ └── plugin │ │ └── v-easy-components.ts ├── assets │ ├── imgs │ │ └── rank_me.png │ └── svg │ │ └── vinyl.svg ├── components-business │ ├── auto │ │ ├── auto.less │ │ └── auto.tsx │ ├── button │ │ ├── index.less │ │ └── index.tsx │ ├── fly │ │ ├── index.less │ │ └── index.tsx │ ├── offline │ │ └── offline.tsx │ ├── secondary-bar │ │ ├── index.less │ │ └── index.tsx │ ├── secondary-list │ │ ├── index.less │ │ └── index.tsx │ ├── song-list │ │ ├── card.vue │ │ ├── daily.less │ │ ├── daily.tsx │ │ ├── index.less │ │ └── index.tsx │ └── table │ │ ├── index.less │ │ └── index.tsx ├── components-global │ ├── icon │ │ ├── index.ts │ │ └── main │ │ │ ├── index.less │ │ │ └── index.tsx │ └── index.ts ├── components │ ├── MatchText.vue │ ├── dialog │ │ └── index.tsx │ ├── error-boundary │ │ ├── index.less │ │ └── index.tsx │ ├── image │ │ └── index.tsx │ ├── keyword │ │ └── keyword.tsx │ ├── loading │ │ ├── index.less │ │ └── index.tsx │ ├── more-then │ │ ├── index.less │ │ └── index.tsx │ ├── overflow-text │ │ ├── index.less │ │ └── index.tsx │ ├── process-bar │ │ ├── block.less │ │ ├── block.tsx │ │ ├── index.less │ │ └── index.tsx │ ├── schedule │ │ ├── index.less │ │ └── index.tsx │ ├── swiper │ │ ├── index.less │ │ └── index.tsx │ ├── teleport-layout │ │ ├── index.less │ │ └── index.tsx │ └── widgets │ │ ├── index.ts │ │ ├── list-header │ │ ├── index.less │ │ └── index.tsx │ │ ├── list-wrapper │ │ ├── index.less │ │ └── index.tsx │ │ ├── playlist-item-box │ │ ├── index.less │ │ └── index.tsx │ │ ├── playlist-item-cell │ │ ├── index.less │ │ └── index.tsx │ │ ├── playlist-item-info │ │ ├── index.less │ │ └── index.tsx │ │ └── radio-item │ │ ├── index.less │ │ └── index.tsx ├── config │ ├── build.ts │ └── style.config.less ├── constants │ ├── eventName.ts │ ├── index.ts │ └── vars.ts ├── electron │ ├── constants.ts │ ├── event │ │ ├── action-types.ts │ │ ├── index.ts │ │ ├── ipc-browser.ts │ │ ├── ipc-main │ │ │ ├── auto-download.ts │ │ │ ├── download.ts │ │ │ └── index.ts │ │ └── ipc-renderer │ │ │ └── index.ts │ ├── main.ts │ ├── pages │ │ ├── index.less │ │ └── index.ts │ ├── preload │ │ ├── index.ts │ │ ├── init.ts │ │ └── ipc.ts │ ├── service │ │ ├── index.ts │ │ └── service.ts │ ├── store │ │ └── index.ts │ ├── utils │ │ ├── index.ts │ │ ├── log.ts │ │ ├── setupDevtool.ts │ │ └── setupMenu.ts │ └── web │ │ └── event.ts ├── env.d.ts ├── global.d.ts ├── helpers │ └── index.ts ├── hooks │ ├── auth.ts │ ├── common.ts │ ├── hook.ts │ ├── http.ts │ ├── index.ts │ ├── message.ts │ └── path.ts ├── iconfont │ └── index.ts ├── interface │ ├── app.ts │ ├── audio-convolver.ts │ ├── http.ts │ ├── index.ts │ ├── router.ts │ ├── service │ │ ├── albums.ts │ │ ├── artists.ts │ │ ├── avatar.ts │ │ └── songs.ts │ └── utils.ts ├── layout │ ├── container.less │ ├── container.tsx │ ├── interface.ts │ ├── module.ts │ ├── music │ │ ├── music.less │ │ └── music.tsx │ ├── sage.ts │ ├── secondary │ │ ├── secondary.less │ │ └── secondary.tsx │ └── state.ts ├── main.ts ├── modules │ └── index.ts ├── pages │ ├── 404 │ │ └── view │ │ │ ├── index.less │ │ │ └── index.tsx │ ├── artist │ │ ├── api │ │ │ └── index.ts │ │ ├── children │ │ │ ├── albume.tsx │ │ │ ├── detail.tsx │ │ │ ├── mv.tsx │ │ │ └── similar.tsx │ │ ├── components │ │ │ ├── grid.less │ │ │ └── grid.tsx │ │ ├── index.ts │ │ ├── interface.ts │ │ ├── logic │ │ │ └── ap.ts │ │ ├── module.ts │ │ ├── sage.ts │ │ ├── state.ts │ │ └── view │ │ │ ├── index.less │ │ │ └── index.tsx │ ├── auth │ │ ├── api.ts │ │ ├── component │ │ │ ├── auth-view │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── button │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── input-field │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── link │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── mask-view │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ └── sidebar-auth │ │ │ │ ├── cell.tsx │ │ │ │ ├── float-box.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── style.less │ │ ├── constant.ts │ │ ├── hooks.ts │ │ ├── module.ts │ │ ├── sage.ts │ │ ├── state.ts │ │ ├── theme.ts │ │ └── views │ │ │ ├── email-login.tsx │ │ │ ├── index.tsx │ │ │ ├── phone-login.tsx │ │ │ ├── reset-pwd.tsx │ │ │ ├── signup.tsx │ │ │ └── sms-code.tsx │ ├── cloud │ │ ├── api │ │ │ └── cloud.ts │ │ ├── index.ts │ │ ├── interface.ts │ │ ├── module.ts │ │ ├── sage.ts │ │ ├── state.ts │ │ └── view │ │ │ ├── index.less │ │ │ └── index.tsx │ ├── download │ │ ├── children │ │ │ ├── mv.tsx │ │ │ ├── song.less │ │ │ └── song.tsx │ │ ├── index.ts │ │ ├── interface.ts │ │ ├── module.ts │ │ ├── sage.ts │ │ ├── state.ts │ │ └── view │ │ │ ├── index.less │ │ │ └── index.tsx │ ├── findmusic │ │ └── recommend │ │ │ ├── api.ts │ │ │ ├── components │ │ │ ├── banner.vue │ │ │ ├── dailysong.vue │ │ │ ├── newsong.vue │ │ │ └── recommendsong.vue │ │ │ └── index.vue │ ├── footer │ │ ├── components │ │ │ ├── effect │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── lyrics-desktop │ │ │ │ ├── browser-lyrics.tsx │ │ │ │ ├── electron-lyrics.less │ │ │ │ ├── electron-lyrics.tsx │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── lyrics-embed │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── music-controller │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── no-music.tsx │ │ │ └── volume-history │ │ │ │ ├── history.less │ │ │ │ ├── history.tsx │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ ├── index.ts │ │ ├── interface.ts │ │ ├── module.ts │ │ ├── sage.ts │ │ ├── state.ts │ │ └── view │ │ │ ├── index.less │ │ │ └── index.tsx │ ├── header │ │ ├── component │ │ │ ├── logo.less │ │ │ ├── logo.tsx │ │ │ ├── puah-shift.less │ │ │ ├── push-shift.tsx │ │ │ ├── search.less │ │ │ ├── search.tsx │ │ │ ├── search.vue │ │ │ ├── setting.less │ │ │ └── setting.tsx │ │ ├── interface.ts │ │ ├── module.ts │ │ ├── sage.ts │ │ ├── state.ts │ │ └── view │ │ │ ├── index.less │ │ │ └── index.tsx │ ├── index.ts │ ├── list │ │ ├── api │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── interface.ts │ │ ├── module.ts │ │ ├── sage.ts │ │ ├── state.ts │ │ └── view │ │ │ ├── index.less │ │ │ └── index.tsx │ ├── main │ │ ├── api │ │ │ └── get.ts │ │ ├── interface.ts │ │ ├── module.ts │ │ ├── sage.ts │ │ ├── state.ts │ │ └── view │ │ │ ├── index.less │ │ │ └── index.tsx │ ├── moments │ │ ├── index.ts │ │ ├── module.ts │ │ ├── sage.ts │ │ ├── state.ts │ │ └── view │ │ │ └── index.tsx │ ├── music │ │ ├── children │ │ │ ├── directory.tsx │ │ │ └── song.tsx │ │ ├── index.ts │ │ ├── interface.ts │ │ ├── module.ts │ │ ├── sage.ts │ │ ├── state.ts │ │ └── view │ │ │ ├── index.less │ │ │ └── index.tsx │ ├── news │ │ ├── children │ │ │ ├── artists │ │ │ │ ├── api │ │ │ │ │ └── index.ts │ │ │ │ ├── module.ts │ │ │ │ ├── sage.ts │ │ │ │ ├── state.ts │ │ │ │ └── view │ │ │ │ │ ├── filter.less │ │ │ │ │ ├── filter.tsx │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ ├── module.ts │ │ │ ├── recommend │ │ │ │ ├── api │ │ │ │ │ └── index.ts │ │ │ │ ├── module.ts │ │ │ │ ├── sage.ts │ │ │ │ ├── state.ts │ │ │ │ └── view │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ ├── song-list │ │ │ │ ├── api │ │ │ │ │ └── index.ts │ │ │ │ ├── module.ts │ │ │ │ ├── sage.ts │ │ │ │ ├── state.ts │ │ │ │ └── view │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.tsx │ │ │ └── top-list │ │ │ │ ├── api │ │ │ │ └── index.ts │ │ │ │ ├── module.ts │ │ │ │ ├── sage.ts │ │ │ │ ├── state.ts │ │ │ │ └── view │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ ├── index.ts │ │ ├── interface.ts │ │ └── view │ │ │ ├── index.less │ │ │ └── index.tsx │ ├── search │ │ ├── children │ │ │ ├── album.tsx │ │ │ ├── artist.tsx │ │ │ ├── lyrics.tsx │ │ │ ├── play-list.tsx │ │ │ └── song.tsx │ │ ├── index.ts │ │ ├── interface.ts │ │ ├── logic │ │ │ └── index.ts │ │ ├── module.ts │ │ ├── sage.ts │ │ ├── state.ts │ │ └── view │ │ │ ├── search.less │ │ │ └── search.tsx │ ├── setting │ │ ├── index.ts │ │ ├── interface.ts │ │ ├── module.ts │ │ ├── sage.ts │ │ ├── state.ts │ │ └── view │ │ │ ├── about.tsx │ │ │ ├── author.tsx │ │ │ ├── download.tsx │ │ │ ├── effect.tsx │ │ │ ├── index.less │ │ │ ├── index.tsx │ │ │ ├── source.tsx │ │ │ └── upgrade.tsx │ ├── sidebar │ │ ├── api │ │ │ └── get.ts │ │ └── view │ │ │ ├── index.less │ │ │ └── index.tsx │ ├── userinfo │ │ ├── api.ts │ │ ├── component │ │ │ ├── audiolist │ │ │ │ └── index.tsx │ │ │ ├── empty-list │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── playlist │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── upload-avatar │ │ │ │ ├── UploadWrapper.tsx │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── upload-btn │ │ │ │ └── index.tsx │ │ │ └── userinfo-widget │ │ │ │ ├── index.tsx │ │ │ │ └── widget │ │ │ │ ├── btn │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ │ ├── user-info │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ │ ├── user-statistic │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ │ └── user-tag │ │ │ │ ├── UserTag.tsx │ │ │ │ └── index.less │ │ ├── constant.ts │ │ ├── hooks.ts │ │ ├── module.ts │ │ ├── sage.ts │ │ ├── state.ts │ │ └── views │ │ │ ├── event-view.tsx │ │ │ ├── fans-view.tsx │ │ │ ├── follow-view │ │ │ ├── card.tsx │ │ │ ├── index.less │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── user-setting │ │ │ ├── area-select.tsx │ │ │ ├── birth-select.tsx │ │ │ ├── city.json │ │ │ ├── index.less │ │ │ ├── index.tsx │ │ │ ├── province.json │ │ │ └── sex-select.tsx │ └── video │ │ ├── children │ │ └── mv │ │ │ ├── module.ts │ │ │ ├── sage.ts │ │ │ ├── state.ts │ │ │ └── view │ │ │ └── index.tsx │ │ ├── index.ts │ │ ├── module.ts │ │ ├── sage.ts │ │ ├── state.ts │ │ └── view │ │ └── index.tsx ├── pinia │ ├── index.ts │ ├── recommend.ts │ └── search.ts ├── router │ ├── base.ts │ ├── content.ts │ ├── hook.ts │ ├── index.ts │ └── nav.ts ├── shared │ ├── audio.ts │ ├── jump-shared.ts │ ├── music-shared.ts │ ├── subscribe.less │ └── subscribe.tsx ├── store │ └── index.ts ├── tag │ ├── ID3Writer.ts │ ├── encoder.ts │ ├── signatures.ts │ ├── sizes.ts │ └── transform.ts ├── theme │ ├── color.less │ ├── color.ts │ ├── cover.less │ ├── global.less │ ├── index.css │ ├── index.less │ └── index.ts ├── types.d.ts └── utils │ ├── eventBus.ts │ ├── http.ts │ ├── index.ts │ ├── interceptors │ ├── auth.ts │ ├── format.ts │ ├── index.ts │ └── timeout.ts │ └── musicControl.ts ├── tailwind.config.js ├── tests ├── e2e │ ├── integration │ │ ├── history.spec.js │ │ ├── news.spec.js │ │ └── recommend.spec.js │ ├── plugins │ │ └── index.js │ └── support │ │ └── index.js └── unit │ ├── electron.spec.js │ └── example.spec.ts ├── tsconfig.json ├── vercel.json ├── vite.config.js ├── vue.config.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'body-max-line-length': [2, 'always', 150] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | *.log 4 | -------------------------------------------------------------------------------- /.env.browser: -------------------------------------------------------------------------------- 1 | VUE_APP_PLATFORM = browser 2 | VUE_APP_BUILD_BASE_URL = / 3 | -------------------------------------------------------------------------------- /.env.electron.build: -------------------------------------------------------------------------------- 1 | VUE_APP_PLATFORM = electron 2 | VUE_APP_NODE_ENV = production 3 | VUE_APP_PORTABLE_EXECUTABLE_DIR 4 | -------------------------------------------------------------------------------- /.env.electron.dev: -------------------------------------------------------------------------------- 1 | VUE_APP_PLATFORM = electron 2 | VUE_APP_NODE_ENV = development 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | packages/ 2 | cypress/ 3 | tests/e2e/ 4 | src/mp3 5 | .yarn/ 6 | build/ 7 | dist/ 8 | node_modules/ 9 | public/ 10 | dist-electron/ -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | **/*.js linguist-generated=true 2 | **/*.html linguist-generated=true 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/新功能.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 新功能 3 | about: 如果你的时间允许也可以参与进来一起做的更好 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 可以详细描述出新的功能,可以提出和你专业度相关的见解,我们将无比荣幸 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/问题反馈.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 问题反馈 3 | about: 谢谢反馈,我们将尽快解决问题 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **详细描述你的问题** 11 | 可以描述复现步骤,如果有必要可以携带上截图 12 | 13 | **系统版本** 14 | - 客户端/网页端: [e.g. 客户端] 15 | - 客户端版本(如果是) [e.g. 22] 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /dist-electron 5 | /cypress 6 | /tests/e2e/video 7 | /out 8 | 9 | 10 | # local env files 11 | .env.local 12 | .env.*.local 13 | debug.log 14 | 15 | # Log files 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | pnpm-debug.log* 20 | 21 | # Editor directories and files 22 | .idea 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | /storybook-static 29 | 30 | #Electron-builder output 31 | /src/stories 32 | /packages/api/dist 33 | 34 | .vercel 35 | 36 | .pnp.* 37 | .yarn/* 38 | !.yarn/patches 39 | !.yarn/plugins 40 | !.yarn/releases 41 | !.yarn/sdks 42 | !.yarn/versions 43 | 44 | /public/service 45 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "radishes-v2"] 2 | path = packages/api 3 | url = https://github.com/radishes-music/NeteaseCloudMusicApi.git 4 | branch = radishes 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | npx lint-staged 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | electron_mirror=https://npmmirror.com/mirrors/electron/ 2 | electron-builder-binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.md 3 | .prettierrc.js -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "arrowParens": "avoid" 6 | } 7 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['stylelint-prettier', 'stylelint-order'], 3 | extends: [ 4 | 'stylelint-config-standard', 5 | 'stylelint-config-prettier', 6 | 'stylelint-config-recess-order' 7 | ], 8 | rules: { 9 | 'prettier/prettier': true, 10 | 'font-family-no-missing-generic-family-keyword': null 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Program", 6 | "program": "${file}", 7 | "request": "launch", 8 | "skipFiles": ["/**"], 9 | "type": "pwa-node" 10 | }, 11 | { 12 | "name": "Electron: Main", 13 | "type": "node", 14 | "request": "launch", 15 | "protocol": "inspector", 16 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", 17 | "windows": { 18 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" 19 | }, 20 | "preLaunchTask": "electron-debug", 21 | "args": ["--remote-debugging-port=9223", "./dist_electron"], 22 | "outFiles": ["${workspaceFolder}/dist_electron/**/*.js"] 23 | }, 24 | { 25 | "name": "Electron: Renderer", 26 | "type": "chrome", 27 | "request": "attach", 28 | "port": 9223, 29 | "urlFilter": "http://localhost:*", 30 | "timeout": 30000, 31 | "webRoot": "${workspaceFolder}/src", 32 | "sourceMapPathOverrides": { 33 | "webpack:///./src/*": "${webRoot}/*" 34 | } 35 | } 36 | ], 37 | "compounds": [ 38 | { 39 | "name": "Electron: All", 40 | "configurations": ["Electron: Main", "Electron: Renderer"] 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "merge-conflict.autoNavigateNextConflict.enabled": true, 4 | "prettier.semi": false, 5 | "prettier.singleQuote": true, 6 | "eslint.enable": true, 7 | "editor.codeActionsOnSave": { 8 | "source.fixAll.eslint": "explicit" 9 | }, 10 | "eslint.validate": ["javascript", "typescript", "typescriptreact", "vue"], 11 | "typescript.tsdk": "node_modules\\typescript\\lib", 12 | "[less]": { 13 | "editor.formatOnSave": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "electron-debug", 8 | "type": "process", 9 | "command": "./node_modules/.bin/vue-cli-service", 10 | "windows": { 11 | "command": "./node_modules/.bin/vue-cli-service.cmd" 12 | }, 13 | "isBackground": true, 14 | "args": ["electron:serve", "--debug"], 15 | "problemMatcher": { 16 | "owner": "custom", 17 | "pattern": { 18 | "regexp": "" 19 | }, 20 | "background": { 21 | "beginsPattern": "Starting development server\\.\\.\\.", 22 | "endsPattern": "Not launching electron as debug argument was passed\\." 23 | } 24 | } 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | npmRegistryServer: "https://registry.npmmirror.com" 4 | 5 | plugins: 6 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs 7 | spec: "@yarnpkg/plugin-workspace-tools" 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 阶段一: 构建 2 | FROM node:18 AS build 3 | WORKDIR /app 4 | COPY . . 5 | RUN YARN_IGNORE_SCRIPTS=1 yarn 6 | RUN yarn build:web 7 | 8 | # 阶段二: 设置 Nginx 9 | FROM nginx:latest 10 | # 注意:你可能需要根据你的 Nginx 配置和构建的输出修改以下这行 11 | COPY --from=build /app/dist /usr/share/nginx/html 12 | COPY nginx.conf /etc/nginx/conf.d/default.conf 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present, Link 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/build/icons/1024x1024.png -------------------------------------------------------------------------------- /build/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/build/icons/128x128.png -------------------------------------------------------------------------------- /build/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/build/icons/16x16.png -------------------------------------------------------------------------------- /build/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/build/icons/24x24.png -------------------------------------------------------------------------------- /build/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/build/icons/256x256.png -------------------------------------------------------------------------------- /build/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/build/icons/32x32.png -------------------------------------------------------------------------------- /build/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/build/icons/48x48.png -------------------------------------------------------------------------------- /build/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/build/icons/512x512.png -------------------------------------------------------------------------------- /build/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/build/icons/64x64.png -------------------------------------------------------------------------------- /build/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/build/icons/icon.icns -------------------------------------------------------------------------------- /build/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/build/icons/icon.ico -------------------------------------------------------------------------------- /build/logo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/build/logo.ai -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:8080", 3 | "integrationFolder": "tests/e2e/integration", 4 | "pluginsFile": "tests/e2e/plugins/index.js", 5 | "supportFile": "tests/e2e/support/index.js", 6 | "videosFolder": "tests/e2e/video", 7 | "fixturesFolder": "tests/e2e/fixturesFolder", 8 | "screenshotsFolder": "tests/e2e/screenshotsFolder" 9 | } 10 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | nginx: 4 | build: # 这是你的 Nginx Dockerfile 所在的位置 5 | context: . 6 | dockerfile: Dockerfile 7 | depends_on: 8 | - node 9 | ports: 10 | - "1234:80" 11 | 12 | node: 13 | build: ./packages/api 14 | ports: 15 | - "32768:32768" 16 | -------------------------------------------------------------------------------- /electron.vite.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { defineConfig, externalizeDepsPlugin } from 'electron-vite' 3 | import WebViteConfig, { __VITE_RESOLVE__ } from './vite.config' 4 | 5 | const { resolve } = path 6 | 7 | // electron.vite.config.js 8 | export default defineConfig({ 9 | main: { 10 | envPrefix: 'VUE_APP_', 11 | build: { 12 | rollupOptions: { 13 | input: { 14 | index: resolve(__dirname, 'src/electron/main.ts') 15 | } 16 | } 17 | }, 18 | resolve: __VITE_RESOLVE__, 19 | plugins: [externalizeDepsPlugin()] 20 | }, 21 | 22 | preload: { 23 | envPrefix: 'VUE_APP_', 24 | build: { 25 | rollupOptions: { 26 | input: { 27 | index: resolve(__dirname, 'src/electron/preload/index.ts') 28 | } 29 | } 30 | }, 31 | plugins: [externalizeDepsPlugin()], 32 | resolve: __VITE_RESOLVE__ 33 | }, 34 | renderer: WebViteConfig 35 | }) 36 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | radishes-music 11 | 25 | 26 | 27 |
28 |
29 | 30 |
31 |
32 | 33 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "npmClient": "yarn", 6 | "useWorkspaces": true, 7 | "version": "2.0.2" 8 | } 9 | -------------------------------------------------------------------------------- /lyrics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | radishes-music 11 | 12 | 13 |
14 | 15 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /mirrori.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ 4 | export ELECTRON_BUILDE_RBINARIES_MIRROR=https://npmmirror.com/mirrors/electron-builder-binaries/ 5 | echo -e "\033[32m ===================================================== \033[0m" 6 | echo -e "\033[32m ==============Setup Mirror Successfully============== \033[0m" 7 | echo -e "\033[32m ===================================================== \033[0m" 8 | 9 | yarn install -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | location / { 4 | root /usr/share/nginx/html; 5 | try_files $uri $uri/ /index.html; 6 | } 7 | 8 | location /api/ { 9 | proxy_pass http://node:32768/; 10 | proxy_http_version 1.1; 11 | proxy_set_header Upgrade $http_upgrade; 12 | proxy_set_header Connection 'upgrade'; 13 | proxy_set_header Host $host; 14 | proxy_cache_bypass $http_upgrade; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/unblock/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .npmignore 3 | .gitignore 4 | .dockerignore 5 | 6 | LICENSE 7 | *.md 8 | 9 | node_modules 10 | npm-debug.log 11 | 12 | Dockerfile* 13 | *.yml 14 | 15 | src/browser/ 16 | ca.* 17 | *.worker.js -------------------------------------------------------------------------------- /packages/unblock/.npmignore: -------------------------------------------------------------------------------- 1 | .npmignore 2 | .gitignore 3 | .dockerignore 4 | 5 | Dockerfile* 6 | *.yml 7 | 8 | src/browser/ 9 | ca.* 10 | *.worker.js -------------------------------------------------------------------------------- /packages/unblock/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | RUN apk add --update nodejs npm --repository=http://dl-cdn.alpinelinux.org/alpine/latest-stable/main/ 3 | 4 | ENV NODE_ENV production 5 | 6 | WORKDIR /usr/src/app 7 | COPY package*.json ./ 8 | RUN npm install --production 9 | COPY . . 10 | 11 | EXPOSE 8080 8081 12 | 13 | ENTRYPOINT ["node", "app.js"] -------------------------------------------------------------------------------- /packages/unblock/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nzix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/unblock/app.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./src/app') 3 | -------------------------------------------------------------------------------- /packages/unblock/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', { targets: { node: 'current' } }]] 3 | } 4 | -------------------------------------------------------------------------------- /packages/unblock/bridge.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./src/bridge') 3 | -------------------------------------------------------------------------------- /packages/unblock/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDdzCCAl+gAwIBAgIJAKX8LdIETDklMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV 3 | BAYTAkNOMSQwIgYDVQQDDBtVbmJsb2NrTmV0ZWFzZU11c2ljIFJvb3QgQ0ExHTAb 4 | BgNVBAoMFEdpdEh1Yi5jb20gQG5vbmRhbmVlMB4XDTE5MDUxODE2MDU0NVoXDTI0 5 | MDUxNjE2MDU0NVowUjELMAkGA1UEBhMCQ04xJDAiBgNVBAMMG1VuYmxvY2tOZXRl 6 | YXNlTXVzaWMgUm9vdCBDQTEdMBsGA1UECgwUR2l0SHViLmNvbSBAbm9uZGFuZWUw 7 | ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD23K6Ti2TfLJToCmpCAVgE 8 | Xb8+qTMfrifCpnKlJ+hrL+4KI1j4vSqTOOatqmxGSXZdF/j2kJuI40YThaokcgYx 9 | GFcPcEftSCYGWy8o20u2hzTkkW3KW9wlsDRIXICFXVIsHeSDwz+aVSudkyJHjfaS 10 | aLNb5pPovE7MRj8tDbp55scaSqhEcOe3m1ZlwlCeeXvD7RLKr3xhBKbGEqlJAjFq 11 | RNGzuqylqyJVBLScNHC7Lcf4n6pKr1yPGOeLePOUrIwtj0ynHUcBfeMuCVCsIKL8 12 | vy/oNwlDrZaAMfu5QQslzEf87KY1QgtI6Ppii+tzbmVx1ZxnlaCKqiuwlgBoi/5r 13 | AgMBAAGjUDBOMB0GA1UdDgQWBBRDhbGjnXEUouE9wNFS2k9PtgYYjDAfBgNVHSME 14 | GDAWgBRDhbGjnXEUouE9wNFS2k9PtgYYjDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3 15 | DQEBCwUAA4IBAQDRUh5+JFLEALXQkhPfwrVf4sCXTwLMwVujTPo3NMbhpWiP4cnn 16 | XHGCD5V57bBwjeD6NSrczDIdnN9uTJyFmLNVFMZBguEIeZfLUJLJ6w1ZhfgciX1D 17 | 9djyyo6eclkHvi+aPZKfzgMmc5BvHcjyUyS5MzI23kUW6WXUDn3IDIUKrfaH9Mjc 18 | /d4DDZVKQCYrLoBL+XO7pEHUY0u9XZVYWEavQ5tSN8XY1SDrO0yGUpRWET0ltubE 19 | zV7W0LOhuoVCiemboc5H8+njBjCis8obAo1XMmDZzW189L9GPFxHNWlka+KlajZB 20 | tMo90PooZYEOw1rTUrzHb+VZY/tYIAAomGZ0 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /packages/unblock/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | unblockneteasemusic: 5 | image: nondanee/unblockneteasemusic 6 | environment: 7 | NODE_ENV: production 8 | ports: 9 | - 8080:8080 10 | -------------------------------------------------------------------------------- /packages/unblock/endpoint.worker.js: -------------------------------------------------------------------------------- 1 | addEventListener('fetch', event => { 2 | event.respondWith(handleRequest(event.request)) 3 | }) 4 | 5 | const pattern = /^\/package\/([0-9a-zA-Z_\-=]+)\/(\w+\.\w+)$/ 6 | 7 | const handleRequest = async request => { 8 | const notFound = new Response(null, { status: 404 }) 9 | const path = new URL(request.url).pathname 10 | const [matched, base64Url, fileName] = pattern.exec(path || '') || [] 11 | if (!matched) return notFound 12 | let url = base64Url.replace(/-/g, '+').replace(/_/g, '/') 13 | try { 14 | url = new URL(atob(url)) 15 | } catch (_) { 16 | url = null 17 | } 18 | if (!url) return notFound 19 | const headers = new Headers(request.headers) 20 | headers.set('host', url.host) 21 | headers.delete('cookie') 22 | const { method, body } = request 23 | return fetch(url, { method, headers, body }) 24 | } 25 | -------------------------------------------------------------------------------- /packages/unblock/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@radishes/unblock", 3 | "version": "2.0.1", 4 | "description": "Revive unavailable songs for Netease Cloud Music", 5 | "main": "src/provider/match.js", 6 | "bin": { 7 | "unblockneteasemusic": "app.js" 8 | }, 9 | "scripts": { 10 | "pkg": "pkg . --out-path=dist/", 11 | "test": "jest" 12 | }, 13 | "pkg": { 14 | "assets": [ 15 | "server.key", 16 | "server.crt" 17 | ] 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/nondanee/UnblockNeteaseMusic.git" 22 | }, 23 | "author": "nondanee", 24 | "license": "MIT", 25 | "publishConfig": { 26 | "access": "public" 27 | }, 28 | "devDependencies": { 29 | "@babel/core": "^7.12.9", 30 | "@babel/preset-env": "^7.12.7", 31 | "babel-jest": "^26.6.3", 32 | "jest": "^26.6.3" 33 | }, 34 | "dependencies": { 35 | "js-base64": "^3.7.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/unblock/src/browser/README.md: -------------------------------------------------------------------------------- 1 | # Web Extension Port 2 | 3 | For test 4 | 5 | ## Implementation 6 | 7 | - Convert node module to ES6 module which can be directly executed in Chrome 61+ without Babel 8 | - Rewrite crypto module (using CryptoJS) and request (using XMLHttpRequest) module for browser environment 9 | - Do matching in background and transfer result with chrome runtime communication 10 | - Inject content script for hijacking Netease Music Web Ajax response 11 | 12 | ## Build 13 | 14 | ``` 15 | $ node convert.js 16 | ``` 17 | 18 | ## Install 19 | 20 | Load unpacked extension in Developer mode 21 | 22 | ## Known Issue 23 | 24 | Audio resources from `kuwo`, `kugou` and `migu` are limited in http protocol only and hence can't load 25 | Most audio resources from `qq` don't support preflight request (OPTIONS) and make playbar buggy 26 | 27 | ## Reference 28 | 29 | - [brix/crypto-js](https://github.com/brix/crypto-js) 30 | - [travist/jsencrypt](https://github.com/travist/jsencrypt) 31 | - [JixunMoe/cuwcl4c](https://github.com/JixunMoe/cuwcl4c) -------------------------------------------------------------------------------- /packages/unblock/src/browser/background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /packages/unblock/src/browser/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "UnblockNeteaseMusic", 3 | "description": "For test (es6 only)", 4 | "version": "0.1", 5 | "background": { 6 | "page": "background.html" 7 | }, 8 | "content_scripts": [{ 9 | "js": ["script.js"], 10 | "matches": ["*://music.163.com/*"], 11 | "all_frames": true 12 | }], 13 | "web_accessible_resources": ["inject.js"], 14 | "externally_connectable": { 15 | "matches": ["*://music.163.com/*"] 16 | }, 17 | "manifest_version": 2, 18 | "permissions": ["*://*/*", "webRequest", "webRequestBlocking"], 19 | "content_security_policy": "script-src 'self' 'unsafe-eval' https://cdn.jsdelivr.net; object-src 'self'" 20 | } -------------------------------------------------------------------------------- /packages/unblock/src/browser/script.js: -------------------------------------------------------------------------------- 1 | ;(() => { 2 | let script = (document.head || document.documentElement).appendChild( 3 | document.createElement('script') 4 | ) 5 | script.src = chrome.extension.getURL('inject.js') 6 | script.onload = script.parentNode.removeChild(script) 7 | })() 8 | -------------------------------------------------------------------------------- /packages/unblock/src/cache.js: -------------------------------------------------------------------------------- 1 | const collector = (job, cycle) => 2 | setTimeout(() => { 3 | let keep = false 4 | Object.keys(job.cache || {}).forEach(key => { 5 | if (!job.cache[key]) return 6 | job.cache[key].expiration < Date.now() 7 | ? (job.cache[key] = null) 8 | : (keep = keep || true) 9 | }) 10 | keep ? collector(job, cycle) : (job.collector = null) 11 | }, cycle) 12 | 13 | module.exports = (job, parameter, live = 30 * 60 * 1000) => { 14 | const cache = job.cache ? job.cache : (job.cache = {}) 15 | if (!job.collector) job.collector = collector(job, live / 2) 16 | const key = 17 | parameter == null 18 | ? 'default' 19 | : typeof parameter === 'object' 20 | ? parameter.id || parameter.key || JSON.stringify(parameter) 21 | : parameter 22 | const done = (status, result) => 23 | (cache[key].execution = Promise[status](result)) 24 | if (!cache[key] || cache[key].expiration < Date.now()) 25 | cache[key] = { 26 | expiration: Date.now() + live, 27 | execution: job(parameter) 28 | .then(result => done('resolve', result)) 29 | .catch(result => done('reject', result)) 30 | } 31 | return cache[key].execution 32 | } 33 | -------------------------------------------------------------------------------- /packages/unblock/src/provider/insure.js: -------------------------------------------------------------------------------- 1 | const request = require('../request') 2 | const host = null // 'http://localhost:9000' 3 | 4 | module.exports = () => { 5 | const proxy = new Proxy(() => {}, { 6 | get: (target, property) => { 7 | target.route = (target.route || []).concat(property) 8 | return proxy 9 | }, 10 | apply: (target, _, payload) => { 11 | if (module.exports.disable || !host) return Promise.reject() 12 | const path = target.route.join('/') 13 | const query = 14 | typeof payload[0] === 'object' ? JSON.stringify(payload[0]) : payload[0] 15 | // if (path != 'qq/ticket') return Promise.reject() 16 | return request( 17 | 'GET', 18 | `${host}/${path}?${encodeURIComponent(query)}` 19 | ).then(response => response.body()) 20 | } 21 | }) 22 | return proxy 23 | } 24 | -------------------------------------------------------------------------------- /packages/unblock/src/provider/netease.js: -------------------------------------------------------------------------------- 1 | const cache = require('../cache') 2 | const crypto = require('../crypto') 3 | const request = require('../request') 4 | 5 | const search = info => { 6 | const url = 'http://music.163.com/api/album/' + info.album.id 7 | 8 | return request('GET', url) 9 | .then(response => response.body()) 10 | .then(body => { 11 | const jsonBody = JSON.parse( 12 | body.replace(/"dfsId":(\d+)/g, '"dfsId":"$1"') 13 | ) // for js precision 14 | const matched = jsonBody.album.songs.find(song => song.id === info.id) 15 | if (matched) 16 | return ( 17 | matched.hMusic.dfsId || matched.mMusic.dfsId || matched.lMusic.dfsId 18 | ) 19 | else return Promise.reject() 20 | }) 21 | } 22 | 23 | const track = id => { 24 | if (!id || id === '0') return Promise.reject() 25 | return crypto.uri.retrieve(id) 26 | } 27 | 28 | const check = info => cache(search, info).then(track) 29 | 30 | module.exports = { check } 31 | -------------------------------------------------------------------------------- /packages/unblock/src/provider/select.js: -------------------------------------------------------------------------------- 1 | module.exports = list => list[0] 2 | 3 | module.exports.ENABLE_FLAC = 4 | (process.env.ENABLE_FLAC || '').toLowerCase() === 'true' 5 | -------------------------------------------------------------------------------- /packages/unblock/target/npmlist.json: -------------------------------------------------------------------------------- 1 | {"name":"@radishes/unblock","version":"0.25.3"} -------------------------------------------------------------------------------- /packages/unblock/test/api.test.js: -------------------------------------------------------------------------------- 1 | const match = require('../src/provider/match') 2 | 3 | test('match id', () => { 4 | return match(1391673624) 5 | .then(data => { 6 | const url = data.url 7 | expect(url.includes('http')).toBe(true) 8 | }) 9 | .catch(e => { 10 | expect(e).toBe('未获取到任何播放源') 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /public/audio-effect/AliceInBones.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/AliceInBones.wav -------------------------------------------------------------------------------- /public/audio-effect/BlackSunGarden.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/BlackSunGarden.wav -------------------------------------------------------------------------------- /public/audio-effect/Car radio close.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Car radio close.wav -------------------------------------------------------------------------------- /public/audio-effect/Car radio wide.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Car radio wide.wav -------------------------------------------------------------------------------- /public/audio-effect/CastilloDeLosTresReyesDelMorro.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/CastilloDeLosTresReyesDelMorro.wav -------------------------------------------------------------------------------- /public/audio-effect/CliffOfTheDawn.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/CliffOfTheDawn.wav -------------------------------------------------------------------------------- /public/audio-effect/CornOnTheLeash.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/CornOnTheLeash.wav -------------------------------------------------------------------------------- /public/audio-effect/Erres tube radio.C.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Erres tube radio.C.wav -------------------------------------------------------------------------------- /public/audio-effect/FatMansMisery.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/FatMansMisery.wav -------------------------------------------------------------------------------- /public/audio-effect/FooToFly.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/FooToFly.wav -------------------------------------------------------------------------------- /public/audio-effect/Fredman_Mono_Vintage30_SPARC.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Fredman_Mono_Vintage30_SPARC.wav -------------------------------------------------------------------------------- /public/audio-effect/Fredman_Mono_Vintage30_Solid.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Fredman_Mono_Vintage30_Solid.wav -------------------------------------------------------------------------------- /public/audio-effect/Fredman_Mono_Vintage30_Tube.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Fredman_Mono_Vintage30_Tube.wav -------------------------------------------------------------------------------- /public/audio-effect/GreedSacrifice.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/GreedSacrifice.wav -------------------------------------------------------------------------------- /public/audio-effect/GreenBasketCase.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/GreenBasketCase.wav -------------------------------------------------------------------------------- /public/audio-effect/IslaMujeresCave.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/IslaMujeresCave.wav -------------------------------------------------------------------------------- /public/audio-effect/LawrenceWelkCave.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/LawrenceWelkCave.wav -------------------------------------------------------------------------------- /public/audio-effect/Mono_3DoorTonite_SPARC.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Mono_3DoorTonite_SPARC.wav -------------------------------------------------------------------------------- /public/audio-effect/Mono_3DoorTonite_Solid.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Mono_3DoorTonite_Solid.wav -------------------------------------------------------------------------------- /public/audio-effect/Mono_3DoorTonite_Tube.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Mono_3DoorTonite_Tube.wav -------------------------------------------------------------------------------- /public/audio-effect/Mono_AlterYourEyes_SPARC.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Mono_AlterYourEyes_SPARC.wav -------------------------------------------------------------------------------- /public/audio-effect/Mono_AlterYourEyes_Solid.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Mono_AlterYourEyes_Solid.wav -------------------------------------------------------------------------------- /public/audio-effect/Mono_AlterYourEyes_Tube.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Mono_AlterYourEyes_Tube.wav -------------------------------------------------------------------------------- /public/audio-effect/Mono_Bushlyerine_SPARC.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Mono_Bushlyerine_SPARC.wav -------------------------------------------------------------------------------- /public/audio-effect/Mono_Bushlyerine_Solid.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Mono_Bushlyerine_Solid.wav -------------------------------------------------------------------------------- /public/audio-effect/Mono_Bushlyerine_Tube.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Mono_Bushlyerine_Tube.wav -------------------------------------------------------------------------------- /public/audio-effect/Mono_TheSpringsArentAlright_SPARC.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Mono_TheSpringsArentAlright_SPARC.wav -------------------------------------------------------------------------------- /public/audio-effect/Mono_TheSpringsArentAlright_Solid.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Mono_TheSpringsArentAlright_Solid.wav -------------------------------------------------------------------------------- /public/audio-effect/Mono_TheSpringsArentAlright_Tube.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Mono_TheSpringsArentAlright_Tube.wav -------------------------------------------------------------------------------- /public/audio-effect/NaumburgBandshell.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/NaumburgBandshell.wav -------------------------------------------------------------------------------- /public/audio-effect/ScorpYouLikeAHurricane.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/ScorpYouLikeAHurricane.wav -------------------------------------------------------------------------------- /public/audio-effect/Small portable ambient.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Small portable ambient.wav -------------------------------------------------------------------------------- /public/audio-effect/Small portable.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Small portable.wav -------------------------------------------------------------------------------- /public/audio-effect/Small speaker mono.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Small speaker mono.wav -------------------------------------------------------------------------------- /public/audio-effect/SpoonGarden.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/SpoonGarden.wav -------------------------------------------------------------------------------- /public/audio-effect/StanleyParkCliffs.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/StanleyParkCliffs.wav -------------------------------------------------------------------------------- /public/audio-effect/SweetChildOfGun.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/SweetChildOfGun.wav -------------------------------------------------------------------------------- /public/audio-effect/ToolPot.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/ToolPot.wav -------------------------------------------------------------------------------- /public/audio-effect/Very small speaker mono.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/Very small speaker mono.wav -------------------------------------------------------------------------------- /public/audio-effect/WoodruffLane.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/WoodruffLane.wav -------------------------------------------------------------------------------- /public/audio-effect/iron box mono.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/audio-effect/iron box mono.wav -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/favicon.ico -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/public/icon.png -------------------------------------------------------------------------------- /public/loading.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /script/deploy.sh: -------------------------------------------------------------------------------- 1 | git clone https://$2 temp 2 | cp -r temp/.git $3/.git 3 | 4 | cd $3 5 | 6 | cp index.html 404.html 7 | 8 | git config user.name "Link" 9 | git config user.email "link19970507@gmail.com" 10 | 11 | git add . 12 | 13 | git commit -m "Github Actions auto builder at $(date +'%Y-%m-%d %H:%M:%S')" 14 | git branch -M main 15 | 16 | git push --quiet "https://$1@$2" main:main 17 | 18 | rm -rf temp 19 | 20 | echo "Successful" 21 | -------------------------------------------------------------------------------- /script/read.js: -------------------------------------------------------------------------------- 1 | const glob = require('glob') 2 | 3 | glob('public/**/*.wav', (err, paths) => { 4 | const r = paths 5 | .map(path => path.replace('public/audio-effect/', '')) 6 | .map(file => file.replace('.wav', '')) 7 | .map(file => `'${file}'`) 8 | .join(',') 9 | 10 | console.log(r) 11 | }) 12 | -------------------------------------------------------------------------------- /script/server-local.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const { createProxyMiddleware } = require('http-proxy-middleware') 3 | const chalk = require('chalk') 4 | 5 | const app = express() 6 | 7 | const options = { 8 | target: 'http://localhost:3000', 9 | pathRewrite: { 10 | '^/api': '' 11 | } 12 | } 13 | 14 | const apiProxy = createProxyMiddleware('/api', options) 15 | 16 | app.use('/api', apiProxy) 17 | app.listen(3001) 18 | 19 | console.log(chalk.green('listen 3001')) 20 | -------------------------------------------------------------------------------- /snapshots/231216.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/snapshots/231216.jpg -------------------------------------------------------------------------------- /snapshots/home.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/snapshots/home.jpg -------------------------------------------------------------------------------- /src/app/app.less: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #app { 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | width: 100%; 8 | height: 100%; 9 | overflow: hidden; 10 | font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, 11 | 'Helvetica Neue', Arial, sans-serif; 12 | } 13 | -------------------------------------------------------------------------------- /src/app/app.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | import { ErrorBoundary } from '@/components/error-boundary/index' 3 | import { Container } from '@/layout/container' 4 | 5 | import '@/theme/index' 6 | import './app.less' 7 | 8 | /* Global Setup */ 9 | export default defineComponent({ 10 | name: 'APP', 11 | setup() { 12 | return () => ( 13 | // @ts-expect-error 14 | 15 | 16 | 17 | ) 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /src/app/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /src/app/index.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './app' 3 | import store from '../store' 4 | import pinia from '@/pinia' 5 | import router from '../router' 6 | import { Components } from './plugin/v-easy-components' 7 | import GlobalComponent from '@/components-global/index' 8 | import { errorHandle } from '@/components/error-boundary/index' 9 | import { registerIPC } from '../electron/preload/ipc' 10 | import { isElectron } from '@/utils/index' 11 | import init from '@/electron/preload/init' 12 | import '@/iconfont/index' 13 | import './index.css' 14 | import { setupMainEvent } from '@/electron/web/event' 15 | 16 | import PhosphorIcons from '@phosphor-icons/vue' 17 | 18 | import 'vue-skeletor/dist/vue-skeletor.css' 19 | import { Skeletor } from 'vue-skeletor' 20 | 21 | const app = createApp(App) 22 | .use(store) 23 | .use(pinia) 24 | .use(router) 25 | // @ts-expect-error 26 | .use(Components.default) 27 | .use(GlobalComponent) 28 | .use(PhosphorIcons) 29 | .component(Skeletor.name, Skeletor) 30 | 31 | errorHandle(app) 32 | 33 | init() 34 | 35 | if (isElectron) { 36 | // console.log = electronAPI.info 37 | // console.warn = electronAPI.warn 38 | // console.error = electronAPI.error 39 | registerIPC(app) 40 | setupMainEvent() 41 | } 42 | 43 | app.mount('#app') 44 | -------------------------------------------------------------------------------- /src/app/plugin/v-easy-components.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line prettier/prettier 2 | export * as Components from 'v-easy-components' 3 | import 'v-easy-components/dist/theme-chalk/index.css' 4 | -------------------------------------------------------------------------------- /src/assets/imgs/rank_me.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/radishes-music/radishes/6ab8dfe71c0087926556c69f806afd0bf153e7de/src/assets/imgs/rank_me.png -------------------------------------------------------------------------------- /src/components-business/auto/auto.less: -------------------------------------------------------------------------------- 1 | .auto { 2 | position: fixed; 3 | right: 10px; 4 | bottom: 10px; 5 | z-index: 9999; 6 | min-width: 180px; 7 | overflow: hidden; 8 | background-color: white; 9 | border-radius: 4px; 10 | &-header { 11 | padding: 10px; 12 | color: white; 13 | user-select: none; 14 | background-color: var(--base-color); 15 | border-radius: 4px 4px 0 0; 16 | } 17 | &-body { 18 | padding: 10px; 19 | background-color: var(--background-darken); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components-business/button/index.less: -------------------------------------------------------------------------------- 1 | .play-all { 2 | display: flex; 3 | align-items: center; 4 | color: white; 5 | background-color: var(--base-color); 6 | span { 7 | margin-left: 10px; 8 | } 9 | &:focus, 10 | &:hover { 11 | color: white; 12 | background-color: var(--base-color-darken); 13 | border-color: var(--base-color); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components-business/button/index.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, PropType } from 'vue' 2 | import { Button } from 'ant-design-vue' 3 | import { noop } from '@/utils' 4 | import './index.less' 5 | 6 | export const PlayAll = defineComponent({ 7 | name: 'PlayAll', 8 | props: { 9 | onClick: { 10 | type: Function as PropType<() => void>, 11 | default: noop 12 | } 13 | }, 14 | emits: ['click'], 15 | setup(props, { emit }) { 16 | return () => ( 17 | 29 | ) 30 | } 31 | }) 32 | -------------------------------------------------------------------------------- /src/components-business/fly/index.less: -------------------------------------------------------------------------------- 1 | @import '~@/theme/index.less'; 2 | 3 | .fly { 4 | position: absolute; 5 | z-index: 9; 6 | opacity: 0.3; 7 | transform: translateX(0); 8 | &-content { 9 | width: 30px; 10 | height: 30px; 11 | border-radius: 50%; 12 | transform: translateY(0); 13 | .border(2px); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components-business/offline/offline.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, ref } from 'vue' 2 | import { Alert } from 'ant-design-vue' 3 | import { useOnline } from '@vueuse/core' 4 | 5 | export default defineComponent({ 6 | name: 'Offline', 7 | setup() { 8 | const online = useOnline() 9 | 10 | return () => { 11 | if (!online.value) { 12 | return ( 13 | 17 | ) 18 | } 19 | } 20 | } 21 | }) 22 | -------------------------------------------------------------------------------- /src/components-business/secondary-bar/index.less: -------------------------------------------------------------------------------- 1 | @prefix: ~'secondary'; 2 | @base-color: #383838; 3 | 4 | .@{prefix}-bar { 5 | padding: 8px 0; 6 | ul { 7 | margin: 0; 8 | } 9 | &-link { 10 | margin-right: 20px; 11 | color: @base-color; 12 | transition: color 0.2s; 13 | &:hover { 14 | color: darken(@base-color, 10%); 15 | } 16 | &--small { 17 | font-size: 14px; 18 | } 19 | &-active { 20 | font-weight: bold; 21 | &:hover { 22 | color: @base-color; 23 | } 24 | &--small { 25 | font-size: 16px; 26 | } 27 | &--default { 28 | font-size: 22px; 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components-business/song-list/daily.less: -------------------------------------------------------------------------------- 1 | .center() { 2 | position: absolute; 3 | top: 50%; 4 | left: 50%; 5 | transform: translate(-50%, -50%); 6 | } 7 | 8 | .daily-img { 9 | display: flex; 10 | align-items: center; 11 | justify-content: center; 12 | height: 100%; 13 | min-height: 166px; 14 | background-color: var(--base-color); 15 | border-radius: 4px; 16 | &-date { 17 | position: relative; 18 | > div { 19 | margin-top: 6px; 20 | font-size: 28px; 21 | color: white; 22 | .center(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components-business/song-list/daily.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, PropType } from 'vue' 2 | import { Image } from '@/components/image' 3 | import dayjs from 'dayjs' 4 | import './daily.less' 5 | 6 | const prefix = 'daily' 7 | 8 | export const DailyCard = defineComponent({ 9 | name: 'DailyCard', 10 | props: { 11 | src: { 12 | type: String as PropType, 13 | default: '' 14 | }, 15 | name: { 16 | type: String as PropType, 17 | default: '' 18 | } 19 | }, 20 | setup(props) { 21 | return () => ( 22 | <> 23 | {props.src ? ( 24 | 29 | ) : ( 30 |
31 |
32 | 33 |
{dayjs().date()}
34 |
35 |
36 | )} 37 | 38 | ) 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /src/components-business/song-list/index.less: -------------------------------------------------------------------------------- 1 | @prefix: ~'song'; 2 | 3 | .@{prefix}-list { 4 | padding: 10px 0; 5 | 6 | > ul { 7 | display: grid; 8 | // grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); 9 | grid-template-columns: 1fr 1fr 1fr 1fr; 10 | grid-gap: 20px; 11 | height: 100%; 12 | } 13 | &-container { 14 | position: relative; 15 | // min-height: 220px; 16 | // cursor: pointer; 17 | user-select: none; 18 | .song-pic { 19 | position: relative; 20 | width: 0; 21 | height: 0; 22 | padding-left: 100%; 23 | padding-bottom: 100%; 24 | .daily-img, 25 | .bg-img { 26 | position: absolute; 27 | left: 0; 28 | width: 100%; 29 | height: 100%; 30 | } 31 | &-count { 32 | position: absolute; 33 | top: 4px; 34 | right: 4px; 35 | color: white; 36 | background: rgba(0, 0, 0, 0.3); 37 | } 38 | } 39 | .song-title { 40 | height: 44px; 41 | margin-top: 10px; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components-business/table/index.less: -------------------------------------------------------------------------------- 1 | @prefix: ~'table'; 2 | 3 | .@{prefix} { 4 | height: 100%; 5 | overflow: auto; 6 | &-body { 7 | .ant-table-tbody > tr > td, 8 | .ant-table-thead > tr > th { 9 | padding: 12px 16px; 10 | } 11 | } 12 | &-pagination { 13 | display: flex; 14 | justify-content: center; 15 | padding: 10px 0; 16 | } 17 | &-row-lyrics { 18 | span { 19 | display: block; 20 | overflow: hidden; 21 | line-height: 1.8; 22 | text-overflow: ellipsis; 23 | white-space: nowrap; 24 | } 25 | } 26 | .row-music { 27 | cursor: pointer; 28 | } 29 | .no-copyright { 30 | color: #b1b1b1; 31 | cursor: pointer; 32 | } 33 | .pic-url { 34 | width: 80px; 35 | height: 80px; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components-global/icon/index.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue' 2 | import main from './main/index' 3 | 4 | main.install = (app: App) => { 5 | app.component(main.name, main) 6 | } 7 | 8 | export default main 9 | -------------------------------------------------------------------------------- /src/components-global/icon/main/index.less: -------------------------------------------------------------------------------- 1 | .icon { 2 | width: 24px; 3 | height: 24px; 4 | overflow: hidden; 5 | vertical-align: -0.15em; 6 | } 7 | -------------------------------------------------------------------------------- /src/components-global/index.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue' 2 | import Icon from './icon/index' 3 | 4 | export interface Component { 5 | name: string 6 | } 7 | 8 | export const components: Component[] = [Icon] 9 | 10 | const install = (app: App) => { 11 | components.forEach(component => { 12 | app.component(component.name, component) 13 | }) 14 | } 15 | 16 | export default { 17 | install 18 | } 19 | -------------------------------------------------------------------------------- /src/components/MatchText.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/components/dialog/index.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, createApp, VNodeTypes } from 'vue' 2 | import { Modal } from 'ant-design-vue' 3 | import { create } from '@/utils/index' 4 | 5 | interface Config { 6 | centered: boolean 7 | } 8 | 9 | export const Dialog = defineComponent({ 10 | name: 'Dialog', 11 | setup(props, { slots }) { 12 | return () => {slots.default && slots.default()} 13 | } 14 | }) 15 | 16 | export const instance = function (content: VNodeTypes, config: Config) { 17 | const app = createApp({ 18 | setup() { 19 | return () => {content} 20 | } 21 | }) 22 | create(app) 23 | } 24 | -------------------------------------------------------------------------------- /src/components/error-boundary/index.less: -------------------------------------------------------------------------------- 1 | @prefix: ~'error'; 2 | 3 | .@{prefix} { 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | z-index: 9; 8 | width: 100%; 9 | height: 100%; 10 | color: white; 11 | background-color: #131313b8; 12 | &-boundary { 13 | width: 940px; 14 | height: 520px; 15 | margin: 0 auto; 16 | 17 | &-title { 18 | color: #ed570f; 19 | } 20 | } 21 | &-close { 22 | position: absolute; 23 | top: 20px; 24 | right: 100px; 25 | display: flex; 26 | align-items: center; 27 | justify-content: center; 28 | width: 32px; 29 | height: 32px; 30 | cursor: pointer; 31 | border: 1px solid white; 32 | border-radius: 50%; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/keyword/keyword.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, PropType } from 'vue' 2 | 3 | export default defineComponent({ 4 | name: 'Keyword', 5 | props: { 6 | text: { 7 | type: String as PropType, 8 | default: '' 9 | }, 10 | keyword: { 11 | type: String as PropType, 12 | default: '' 13 | } 14 | }, 15 | setup(props) { 16 | return () => ( 17 | ${props.keyword}` 21 | )} 22 | > 23 | ) 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /src/components/loading/index.less: -------------------------------------------------------------------------------- 1 | .box-view { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | width: 100%; 6 | height: 100%; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/loading/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by buddy on 2021/2/23. 3 | */ 4 | import { defineComponent } from 'vue' 5 | import { Loading as VantLoading } from 'vant' 6 | import './index.less' 7 | 8 | export const Loading = defineComponent({ 9 | name: 'Loading', 10 | props: { 11 | size: String 12 | }, 13 | setup(props) { 14 | return function () { 15 | return ( 16 |
17 | 18 |
19 | ) 20 | } 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /src/components/more-then/index.less: -------------------------------------------------------------------------------- 1 | @prefix: ~'more-then'; 2 | 3 | .@{prefix} { 4 | display: inline-block; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/overflow-text/index.less: -------------------------------------------------------------------------------- 1 | .overflow-text { 2 | display: flex; 3 | width: 100%; 4 | .overflow-text__value { 5 | flex: 1; 6 | margin-right: 30px; 7 | overflow: hidden; 8 | text-overflow: ellipsis; 9 | white-space: nowrap; 10 | 11 | &.switchOn { 12 | white-space: normal; 13 | } 14 | } 15 | .overflow-text__switch { 16 | align-self: flex-start; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/overflow-text/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by buddy on 2021/2/25. 3 | */ 4 | import { defineComponent, reactive } from 'vue' 5 | import Icon from '@/components-global/icon/main' 6 | 7 | import './index.less' 8 | 9 | // TODO 执行操作,你说呢? 10 | export const OverflowText = defineComponent({ 11 | name: 'OverflowText', 12 | setup() { 13 | const state = reactive({ 14 | show: false 15 | }) 16 | 17 | return function (this: any) { 18 | return ( 19 |
20 |
21 | {this.$slots.default?.()} 22 |
23 |
{ 26 | state.show = !state.show 27 | }} 28 | > 29 | 34 |
35 |
36 | ) 37 | } 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /src/components/process-bar/block.less: -------------------------------------------------------------------------------- 1 | .buffer-block { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100%; 7 | overflow: hidden; 8 | > div { 9 | position: absolute; 10 | height: 100%; 11 | background-color: #d4d4d4; 12 | &:first-child { 13 | border-top-left-radius: 4px; 14 | border-bottom-left-radius: 4px; 15 | } 16 | &:last-child { 17 | border-top-right-radius: 4px; 18 | border-bottom-right-radius: 4px; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/process-bar/block.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, toRefs, PropType } from 'vue' 2 | import './block.less' 3 | 4 | export interface Block { 5 | left: number 6 | width: number 7 | } 8 | 9 | export const BufferBlock = defineComponent({ 10 | name: 'BufferBlock', 11 | props: { 12 | block: { 13 | type: Array as PropType, 14 | required: true 15 | } 16 | }, 17 | setup(props) { 18 | const { block } = toRefs(props) 19 | return () => ( 20 |
21 | {block.value?.map(b => ( 22 |
23 | ))} 24 |
25 | ) 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /src/components/schedule/index.less: -------------------------------------------------------------------------------- 1 | .schedule { 2 | position: relative; 3 | &-percentage { 4 | position: absolute; 5 | top: 0; 6 | left: 0; 7 | z-index: 4; 8 | width: 100%; 9 | height: 4px; 10 | overflow: hidden; 11 | > div { 12 | height: 100%; 13 | background-color: var(--success-color); 14 | transition: 15 | width 0.2s, 16 | opacity 2s; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/schedule/index.tsx: -------------------------------------------------------------------------------- 1 | import { ref, defineComponent, PropType, watchEffect } from 'vue' 2 | import './index.less' 3 | 4 | export const Schedule = defineComponent({ 5 | name: 'Schedule', 6 | props: { 7 | percentage: { 8 | type: Number as PropType, 9 | default: 0 10 | } 11 | }, 12 | setup(props, { slots }) { 13 | const opacity = ref(0) 14 | 15 | watchEffect(() => { 16 | if (props.percentage >= 100) { 17 | setTimeout(() => { 18 | opacity.value = 0 19 | }, 2000) 20 | } else { 21 | opacity.value = 1 22 | } 23 | }) 24 | 25 | return () => ( 26 |
27 |
28 |
34 |
35 | {slots.default && slots.default()} 36 |
37 | ) 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /src/components/teleport-layout/index.less: -------------------------------------------------------------------------------- 1 | .cover-container { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | z-index: -1; 6 | width: 100%; 7 | height: 100%; 8 | overflow: hidden; 9 | &-show { 10 | z-index: 3; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/widgets/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by buddy on 2021/2/26. 3 | */ 4 | 5 | export { RadioItem } from './radio-item' 6 | export { ListWrapper } from './list-wrapper' 7 | export { ListHeader } from './list-header' 8 | 9 | export { PlaylistContainer, PlaylistItemBox } from './playlist-item-box' 10 | -------------------------------------------------------------------------------- /src/components/widgets/list-header/index.less: -------------------------------------------------------------------------------- 1 | .list-header { 2 | display: flex; 3 | justify-content: space-between; 4 | padding-right: 20px; 5 | padding-left: 30px; 6 | margin-top: 30px; 7 | margin-bottom: 10px; 8 | font-size: 16px; 9 | font-weight: 600; 10 | 11 | &__title { 12 | font-weight: 600; 13 | } 14 | 15 | &__count { 16 | font-size: 12px; 17 | color: #777; 18 | } 19 | 20 | &__select { 21 | display: flex; 22 | height: fit-content; 23 | overflow: hidden; 24 | border-radius: 2px; 25 | } 26 | 27 | &__option { 28 | display: flex; 29 | align-items: center; 30 | justify-content: center; 31 | width: 24px; 32 | height: 20px; 33 | background: #f2f2f2; 34 | fill: #bfbfbf; 35 | &:not(:last-child) { 36 | margin-right: 2px; 37 | } 38 | 39 | &:hover { 40 | cursor: pointer; 41 | background: #e8e8e8; 42 | } 43 | &.active { 44 | background: #bfbfbf; 45 | fill: #fff; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/components/widgets/list-wrapper/index.less: -------------------------------------------------------------------------------- 1 | .list-wrapper { 2 | > div { 3 | &:nth-child(2n) { 4 | background: #fff; 5 | } 6 | &:nth-child(2n + 1) { 7 | background: #fafafa; 8 | } 9 | &:hover { 10 | background: #f2f2f3; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/widgets/list-wrapper/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by buddy on 2021/2/26. 3 | */ 4 | import { defineComponent } from 'vue' 5 | import './index.less' 6 | 7 | export const ListWrapper = defineComponent({ 8 | name: 'ListWrapper', 9 | setup() { 10 | return function (this: any) { 11 | return
{this.$slots.default?.()}
12 | } 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /src/components/widgets/playlist-item-cell/index.less: -------------------------------------------------------------------------------- 1 | /* no-empty-source */ 2 | -------------------------------------------------------------------------------- /src/components/widgets/playlist-item-cell/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by buddy on 2021/2/26. 3 | */ 4 | import { defineComponent } from 'vue' 5 | 6 | export const PlaylistItemCell = defineComponent({ 7 | name: 'PlaylistItemCell', 8 | setup() { 9 | return function () { 10 | return
PlaylistItemCell
11 | } 12 | } 13 | }) 14 | -------------------------------------------------------------------------------- /src/components/widgets/playlist-item-info/index.less: -------------------------------------------------------------------------------- 1 | /* no-empty-source */ 2 | -------------------------------------------------------------------------------- /src/components/widgets/playlist-item-info/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by buddy on 2021/2/26. 3 | */ 4 | import { defineComponent } from 'vue' 5 | 6 | export const PlaylistItemInfo = defineComponent({ 7 | name: 'PlaylistItemInfo', 8 | setup() { 9 | return function () { 10 | return
PlaylistItemInfo
11 | } 12 | } 13 | }) 14 | -------------------------------------------------------------------------------- /src/components/widgets/radio-item/index.less: -------------------------------------------------------------------------------- 1 | .radio-item { 2 | display: flex; 3 | justify-content: space-between; 4 | padding-top: 10px; 5 | padding-right: 20px; 6 | padding-bottom: 10px; 7 | padding-left: 30px; 8 | cursor: pointer; 9 | 10 | &__left { 11 | display: flex; 12 | align-items: center; 13 | & > div { 14 | margin-right: 8px; 15 | } 16 | } 17 | 18 | &__right { 19 | display: flex; 20 | align-items: center; 21 | justify-content: flex-end; 22 | > div { 23 | width: 100px; 24 | font-size: 12px; 25 | color: #777; 26 | } 27 | } 28 | 29 | &__img { 30 | display: block; 31 | overflow: hidden; 32 | border-radius: 4px; 33 | } 34 | 35 | &__tag { 36 | width: fit-content; 37 | padding: 0 4px; 38 | font-size: 10px; 39 | color: var(--base-color); 40 | border: 1px solid var(--base-color-lighten); 41 | border-radius: 2px; 42 | &:hover { 43 | border-color: var(--base-color-darken); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/components/widgets/radio-item/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by buddy on 2021/2/26. 3 | */ 4 | import { defineComponent } from 'vue' 5 | import { Image } from 'vant' 6 | 7 | import './index.less' 8 | import { overNum } from '@/utils' 9 | 10 | export const RadioItem = defineComponent({ 11 | name: 'RadioItem', 12 | props: ['info'], 13 | setup(props) { 14 | return function () { 15 | const { info = {} } = props 16 | 17 | return ( 18 |
19 |
20 |
21 | 27 |
28 | 29 |
{info.category}
30 |
31 |
32 |
节目{overNum(info.programCount)}
33 |
订阅{overNum(info.subCount)}
34 |
35 |
36 | ) 37 | } 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /src/config/build.ts: -------------------------------------------------------------------------------- 1 | export const enum Platform { 2 | BROWSER = 'browser', 3 | ELECTRON = 'electron' 4 | } 5 | -------------------------------------------------------------------------------- /src/config/style.config.less: -------------------------------------------------------------------------------- 1 | @md-width: 1024px; 2 | @md-height: 700px; 3 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | export enum PlayMode { 3 | Shuffle, 4 | SingleLoop, 5 | AllLoop 6 | } 7 | 8 | export * from './eventName' 9 | export * from './vars' 10 | -------------------------------------------------------------------------------- /src/constants/vars.ts: -------------------------------------------------------------------------------- 1 | export const enum Platform { 2 | BROWSER = 'browser', 3 | ELECTRON = 'electron' 4 | } 5 | 6 | export const isBrowser = import.meta.env.VUE_APP_PLATFORM === Platform.BROWSER 7 | 8 | export const isElectron = import.meta.env.VUE_APP_PLATFORM === Platform.ELECTRON 9 | -------------------------------------------------------------------------------- /src/electron/constants.ts: -------------------------------------------------------------------------------- 1 | import { app } from 'electron' 2 | import { join } from 'path' 3 | 4 | export const resolveFile = (filePath: string) => 5 | join(app.getAppPath(), 'dist-electron', filePath) 6 | 7 | export const resolveFileUrl = (filePath: string) => 8 | `file://${resolveFile(filePath)}` 9 | 10 | export const PreloadPath = resolveFile('preload/index.js') 11 | export const LyricsPathUrl = resolveFileUrl('/renderer/lyrics.html') 12 | export const AppPath = resolveFileUrl('/renderer/index.html') 13 | 14 | export const isDevelopment = process.env.NODE_ENV_ELECTRON_VITE !== 'production' 15 | -------------------------------------------------------------------------------- /src/electron/event/index.ts: -------------------------------------------------------------------------------- 1 | import { BrowserWindow } from 'electron' 2 | import { onIpcMainEvent } from './ipc-main/index' 3 | import autoDownload from './ipc-main/auto-download' 4 | 5 | export const eventInit = (win: BrowserWindow) => { 6 | onIpcMainEvent(win) 7 | autoDownload(win) 8 | } 9 | -------------------------------------------------------------------------------- /src/electron/event/ipc-browser.ts: -------------------------------------------------------------------------------- 1 | export const asyncIpc = () => { 2 | return Promise.resolve({ 3 | sendAsyncIpcRendererEvent: ipcRenderer.send, 4 | sendSyncIpcRendererEvent: ipcRenderer.sendSync 5 | }) 6 | } 7 | 8 | export const asyncIpcOrigin = () => { 9 | return Promise.resolve(ipcRenderer) 10 | } 11 | 12 | export const asyncShell = () => { 13 | return Promise.resolve(shell) 14 | } 15 | -------------------------------------------------------------------------------- /src/electron/pages/index.less: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | width: 100%; 4 | height: 100%; 5 | overflow: hidden; 6 | font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, 7 | 'Helvetica Neue', Arial, sans-serif; 8 | background-color: transparent; 9 | } 10 | 11 | #app { 12 | height: 100%; 13 | } 14 | -------------------------------------------------------------------------------- /src/electron/pages/index.ts: -------------------------------------------------------------------------------- 1 | // electron creates window entry, such as lyrics 2 | 3 | import { createApp } from 'vue' 4 | import App from '@/pages/footer/components/lyrics-desktop/electron-lyrics' 5 | import { Components } from '@/app/plugin/v-easy-components' 6 | import GlobalComponent from '@/components-global/index' 7 | import { errorHandle } from '@/components/error-boundary/index' 8 | import store from '@/store' 9 | import '@/iconfont/index' 10 | import './index.less' 11 | import '@/theme/index' 12 | 13 | const app = createApp(App).use(store).use(Components).use(GlobalComponent) 14 | 15 | // errorHandle(app) 16 | 17 | app.mount('#app') 18 | -------------------------------------------------------------------------------- /src/electron/service/index.ts: -------------------------------------------------------------------------------- 1 | export * from './service' 2 | -------------------------------------------------------------------------------- /src/electron/service/service.ts: -------------------------------------------------------------------------------- 1 | import portscanner from 'portscanner' 2 | import childProcess from 'child_process' 3 | import path from 'path' 4 | import { errorMain, warnMain } from '../utils/log' 5 | 6 | const PORT = [1 << 15, (1 << 16) - 1] 7 | 8 | export const findPort = () => { 9 | const [min, max] = PORT 10 | return portscanner.findAPortNotInUse(min, max, '127.0.0.1') 11 | } 12 | 13 | export const runService = () => { 14 | return findPort().then(n => { 15 | const filename = 16 | process.platform === 'win32' ? 'netease-api.exe' : 'netease-api' 17 | const port = String(n) 18 | const cwd = path.resolve(__dirname, '../../public/service') 19 | const service = childProcess.exec(filename, { 20 | cwd: cwd, 21 | env: { 22 | PORT: port 23 | } 24 | }) 25 | service.on('error', err => { 26 | errorMain('cwd', cwd, '\nservice err:', err.toString()) 27 | }) 28 | service.on('exit', (code, signal) => { 29 | warnMain('service exit', code, signal) 30 | }) 31 | return { 32 | service, 33 | port 34 | } 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /src/electron/store/index.ts: -------------------------------------------------------------------------------- 1 | import Store from 'electron-store' 2 | 3 | export interface DownloadKey { 4 | downloadPath: string 5 | upgrade: boolean 6 | servicePort: string 7 | [k: string]: unknown 8 | } 9 | 10 | const initStore: DownloadKey = { 11 | downloadPath: '', 12 | upgrade: true, 13 | servicePort: '' 14 | } 15 | 16 | const store = new Store() 17 | 18 | for (const k in initStore) { 19 | store.set(k, initStore[k]) 20 | } 21 | 22 | export default store 23 | -------------------------------------------------------------------------------- /src/electron/utils/index.ts: -------------------------------------------------------------------------------- 1 | // runtime electron 2 | 3 | import { userInfo } from 'os' 4 | import { normalize, join } from 'path' 5 | export * from 'fs' 6 | export { homedir } from 'os' 7 | import { app } from 'electron' 8 | 9 | export { normalize, join } 10 | 11 | export const getAppPath = () => { 12 | return __dirname || import.meta.env.PORTABLE_EXECUTABLE_DIR 13 | } 14 | 15 | export const getUserOS = () => { 16 | return userInfo() 17 | } 18 | -------------------------------------------------------------------------------- /src/electron/utils/log.ts: -------------------------------------------------------------------------------- 1 | import log from 'electron-log' 2 | import path from 'path' 3 | 4 | const execPath = 5 | path.dirname(process.execPath) || 6 | (import.meta.env.VUE_APP_PORTABLE_EXECUTABLE_DIR as string) 7 | 8 | export enum LogInfoType { 9 | MAIN = '[Main]', 10 | MAIN_ERROR = '[Main Error]', 11 | MAIN_WARN = '[Main Warn]' 12 | } 13 | 14 | log.transports.file.resolvePath = () => path.join(execPath, 'logs/main.log') 15 | 16 | const format = (arg: unknown) => JSON.stringify(arg) 17 | 18 | export const infoMain = (...args: unknown[]) => { 19 | log.info(...args.map(format)) 20 | } 21 | 22 | export const errorMain = (...args: unknown[]) => { 23 | log.error(...args.map(format)) 24 | } 25 | 26 | export const warnMain = (...args: unknown[]) => { 27 | log.warn(...args.map(format)) 28 | } 29 | 30 | Object.assign(console, log.functions) 31 | 32 | export default log 33 | -------------------------------------------------------------------------------- /src/electron/utils/setupDevtool.ts: -------------------------------------------------------------------------------- 1 | import { BrowserWindow, globalShortcut } from 'electron' 2 | 3 | export const setupDevtool = (win: BrowserWindow) => { 4 | globalShortcut.register('CommandOrControl+Alt+i', function () { 5 | if (win.webContents.isDevToolsOpened()) { 6 | win.webContents.closeDevTools() 7 | } else { 8 | win.webContents.openDevTools() 9 | } 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | interface ImportMetaEnv { 5 | readonly VUE_APP_PLATFORM: 'browser' | 'electron' 6 | readonly VUE_APP_BUILD_BASE_URL: string 7 | readonly VUE_APP_NODE_ENV: 'production' | 'development' 8 | // more env variables... 9 | } 10 | 11 | interface ImportMeta { 12 | readonly env: ImportMetaEnv 13 | } 14 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { IpcRenderer, Shell } from 'electron' 4 | import Os from 'node:os' 5 | import Path from 'node:path' 6 | 7 | declare global { 8 | declare const __filenamespace: string 9 | declare const __APP_VERSION__: string 10 | declare const __GIT_URL__: string 11 | 12 | declare const ipcRenderer: IpcRenderer 13 | declare const shell: Shell 14 | declare const electronAPI: { 15 | isMaximized: (cb: any) => any 16 | os: typeof Os 17 | path: typeof Path 18 | readPathMusic: any 19 | error(message?: any, ...optionalParams: any[]): void 20 | info(message?: any, ...optionalParams: any[]): void 21 | warn(message?: any, ...optionalParams: any[]): void 22 | } 23 | declare const process: promises 24 | } 25 | 26 | declare module '*.png' 27 | declare module '*.md' 28 | -------------------------------------------------------------------------------- /src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import { AUTH_MUTATIONS } from './../pages/auth/sage' 2 | import { AuthNameSpaced, HeaderNameSpaced } from '@/modules' 3 | import store from '@/store' 4 | import { HeaderMutations } from '@/interface' 5 | 6 | export const isLogin = () => store.getters['Auth/isLogin'] 7 | 8 | export const showLogin = () => 9 | store.commit(`${AuthNameSpaced}/${AUTH_MUTATIONS.SHOW_VIEW}`) 10 | 11 | export const hideAuth = () => { 12 | store.commit(`${AuthNameSpaced}/${AUTH_MUTATIONS.HIDE_VIEW}`) 13 | } 14 | 15 | export const setThemeColor = (color: string) => { 16 | store.commit(`${HeaderNameSpaced}/${HeaderMutations.SET_THEME_COLOR}`, color) 17 | } 18 | -------------------------------------------------------------------------------- /src/hooks/common.ts: -------------------------------------------------------------------------------- 1 | import { onActivated, onMounted, ref } from 'vue' 2 | 3 | export const useOnActivated = (fn: () => void) => { 4 | const isMounted = ref(false) 5 | 6 | onMounted(() => { 7 | isMounted.value = true 8 | }) 9 | 10 | onActivated(() => { 11 | if (isMounted.value) { 12 | fn() 13 | } 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /src/hooks/http.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from 'vue' 2 | 3 | export const useHttp = (fetchFn: Function): any => { 4 | const state = reactive({ 5 | loading: false 6 | }) 7 | 8 | const doFetch = async (...args: any[]) => { 9 | state.loading = true 10 | try { 11 | const res = await fetchFn(...args) 12 | state.loading = false 13 | return res 14 | } catch (e) { 15 | state.loading = false 16 | throw e 17 | } 18 | } 19 | 20 | return [state, doFetch] 21 | } 22 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hook' 2 | export * from './http' 3 | export * from './auth' 4 | export * from './message' 5 | export * from './path' 6 | export * from './common' 7 | import { useRoute, useRouter } from 'vue-router' 8 | import { useStore } from 'vuex' 9 | 10 | export { useStore, useRoute, useRouter } 11 | -------------------------------------------------------------------------------- /src/hooks/message.ts: -------------------------------------------------------------------------------- 1 | import { message } from 'ant-design-vue' 2 | import { VNodeTypes } from 'vue' 3 | 4 | export interface MessageConfig { 5 | key: string | number 6 | } 7 | 8 | message.config({ 9 | duration: 2 10 | }) 11 | const messageMap = new Map() 12 | 13 | export const suggested = (msg: VNodeTypes, config?: MessageConfig) => { 14 | const loading = messageMap.get(config?.key) 15 | if (loading) { 16 | return loading 17 | } 18 | const insMessage = message.loading({ 19 | ...config, 20 | content: msg, 21 | duration: 0 22 | }) 23 | if (config) { 24 | messageMap.set(config?.key, insMessage) 25 | } 26 | return insMessage 27 | } 28 | 29 | export const success = (msg: VNodeTypes, config?: MessageConfig) => { 30 | return message.success({ 31 | ...config, 32 | content: msg 33 | }) 34 | } 35 | 36 | export const warning = (msg: VNodeTypes, config?: MessageConfig) => { 37 | return message.warning({ 38 | ...config, 39 | content: msg 40 | }) 41 | } 42 | 43 | export const error = (msg: VNodeTypes, config?: MessageConfig) => { 44 | return message.error({ 45 | ...config, 46 | content: msg 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /src/hooks/path.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from '@/hooks/index' 2 | import { isLogin } from '@/helpers/index' 3 | import { useAuthView } from '@/hooks/index' 4 | 5 | export const useJumpSongList = () => { 6 | const router = useRouter() 7 | const authView = useAuthView() 8 | return (id: number) => { 9 | if (String(id) === '-1' && !isLogin()) { 10 | return authView(true) 11 | } 12 | router.push({ 13 | path: '/list/song/' + id 14 | }) 15 | } 16 | } 17 | 18 | export const useJumpAblumList = () => { 19 | const router = useRouter() 20 | 21 | return (id: number) => { 22 | router.push({ 23 | path: '/list/album/' + id 24 | }) 25 | } 26 | } 27 | 28 | export const useJumpArtist = () => { 29 | const router = useRouter() 30 | return (id: number) => { 31 | router.push({ 32 | path: '/artist/' + id + '/album' 33 | }) 34 | } 35 | } 36 | 37 | export const useJump = () => { 38 | const jumpSongList = useJumpSongList() 39 | const jumpArtist = useJumpArtist() 40 | const jumpAblum = useJumpAblumList() 41 | return { 42 | jumpAblum, 43 | jumpArtist, 44 | jumpSongList 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/iconfont/index.ts: -------------------------------------------------------------------------------- 1 | import $script from 'scriptjs' 2 | import { noop } from '@/utils/index' 3 | 4 | const ICONFONT_URL = 'font_2132275_j86516oavzq' 5 | 6 | // repair electron packaging '//' protocol problem 7 | $script(`https://at.alicdn.com/t/${ICONFONT_URL}.js`, noop) 8 | 9 | void (function () { 10 | if (document) { 11 | const iconfont = document.createElement('style') 12 | document.getElementsByTagName('head')[0].appendChild(iconfont) 13 | const iconfontSheet = document.styleSheets[document.styleSheets.length - 1] 14 | iconfontSheet.insertRule(` 15 | @font-face { 16 | font-family: 'iconfont'; 17 | src: url('https://at.alicdn.com/t/${ICONFONT_URL}.eot'); 18 | src: url('https://at.alicdn.com/t/${ICONFONT_URL}.eot?#iefix') format('embedded-opentype'), 19 | url('https://at.alicdn.com/t/${ICONFONT_URL}.woff2') format('woff2'), 20 | url('https://at.alicdn.com/t/${ICONFONT_URL}.woff') format('woff'), 21 | url('https://at.alicdn.com/t/${ICONFONT_URL}.ttf') format('truetype'), 22 | url('https://at.alicdn.com/t/${ICONFONT_URL}.svg#iconfont') format('svg'); 23 | }`) 24 | } 25 | })() 26 | -------------------------------------------------------------------------------- /src/interface/audio-convolver.ts: -------------------------------------------------------------------------------- 1 | export type ConvolutionFile = 2 | | '原唱' 3 | | 'AliceInBones' 4 | | 'BlackSunGarden' 5 | | 'Car radio close' 6 | | 'Car radio wide' 7 | | 'CastilloDeLosTresReyesDelMorro' 8 | | 'CliffOfTheDawn' 9 | | 'CornOnTheLeash' 10 | | 'Erres tube radio.C' 11 | | 'FatMansMisery' 12 | | 'FooToFly' 13 | | 'Fredman_Mono_Vintage30_Solid' 14 | | 'Fredman_Mono_Vintage30_SPARC' 15 | | 'Fredman_Mono_Vintage30_Tube' 16 | | 'GreedSacrifice' 17 | | 'GreenBasketCase' 18 | | 'iron box mono' 19 | | 'IslaMujeresCave' 20 | | 'LawrenceWelkCave' 21 | | 'Mono_3DoorTonite_Solid' 22 | | 'Mono_3DoorTonite_SPARC' 23 | | 'Mono_3DoorTonite_Tube' 24 | | 'Mono_AlterYourEyes_Solid' 25 | | 'Mono_AlterYourEyes_SPARC' 26 | | 'Mono_AlterYourEyes_Tube' 27 | | 'Mono_Bushlyerine_Solid' 28 | | 'Mono_Bushlyerine_SPARC' 29 | | 'Mono_Bushlyerine_Tube' 30 | | 'Mono_TheSpringsArentAlright_Solid' 31 | | 'Mono_TheSpringsArentAlright_SPARC' 32 | | 'Mono_TheSpringsArentAlright_Tube' 33 | | 'NaumburgBandshell' 34 | | 'ScorpYouLikeAHurricane' 35 | | 'Small portable ambient' 36 | | 'Small portable' 37 | | 'Small speaker mono' 38 | | 'SpoonGarden' 39 | | 'StanleyParkCliffs' 40 | | 'SweetChildOfGun' 41 | | 'ToolPot' 42 | | 'Very small speaker mono' 43 | | 'WoodruffLane' 44 | -------------------------------------------------------------------------------- /src/interface/http.ts: -------------------------------------------------------------------------------- 1 | export interface HttpGet { 2 | [key: string]: string 3 | } 4 | 5 | export interface HttpPost { 6 | [key: string]: string 7 | } 8 | 9 | export type PlaySource = 10 | | 'qq' 11 | | 'xiami' 12 | | 'baidu' 13 | | 'kugou' 14 | | 'kuwo' 15 | | 'migu' 16 | | 'joox' 17 | | 'youtube' 18 | -------------------------------------------------------------------------------- /src/interface/index.ts: -------------------------------------------------------------------------------- 1 | export * from './http' 2 | export * from './app' 3 | export * from './utils' 4 | export * from './service/albums' 5 | export * from './service/artists' 6 | export * from './service/songs' 7 | export * from './service/avatar' 8 | export * from './audio-convolver' 9 | export * from './router' 10 | export * from '@/layout/interface' 11 | export * from '@/pages/index' 12 | -------------------------------------------------------------------------------- /src/interface/router.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router' 2 | 3 | export type RouteMeta = Partial<{ 4 | name: string 5 | auth: boolean 6 | nonav: boolean 7 | browser: boolean 8 | electron: boolean 9 | canBeCollect: boolean 10 | }> 11 | 12 | export type ChildrenRouteMeta = Partial<{ 13 | name: string 14 | path: 15 | | string 16 | | { 17 | name: string 18 | query: { 19 | [x: string]: unknown 20 | } 21 | } 22 | }> 23 | 24 | export type CustomizeChildrenRouteRecordRaw = RouteRecordRaw & { 25 | meta?: ChildrenRouteMeta 26 | } 27 | 28 | export type CustomizeRouteRecordRaw = RouteRecordRaw & { 29 | meta?: RouteMeta 30 | children?: CustomizeChildrenRouteRecordRaw[] 31 | } 32 | -------------------------------------------------------------------------------- /src/interface/service/albums.ts: -------------------------------------------------------------------------------- 1 | import { Artists } from './artists' 2 | 3 | export interface Albums { 4 | id: number 5 | name: string 6 | size: number 7 | artist: Artists 8 | picId: string 9 | copyrightId: string 10 | blurPicUrl: string 11 | picUrl: string 12 | publishTime: number 13 | company: string 14 | } 15 | -------------------------------------------------------------------------------- /src/interface/service/artists.ts: -------------------------------------------------------------------------------- 1 | export interface Artists { 2 | id: number 3 | name: string 4 | alias: unknown[] 5 | img1v1Url: string 6 | picUrl: string 7 | } 8 | -------------------------------------------------------------------------------- /src/interface/service/avatar.ts: -------------------------------------------------------------------------------- 1 | export interface AvatarBase { 2 | nickname: string 3 | userId: number 4 | userType: number 5 | } 6 | 7 | export interface Avatar extends AvatarBase { 8 | avatarUrl: string 9 | city: number 10 | birthday: number 11 | backgroundUrl: string 12 | } 13 | 14 | export interface Creator extends AvatarBase { 15 | authStatus: number 16 | } 17 | -------------------------------------------------------------------------------- /src/interface/utils.ts: -------------------------------------------------------------------------------- 1 | // Get the type in the array object 2 | export type ArrayItem = T extends Array ? K : never 3 | 4 | // Combine two types 5 | export type Merage = P & T 6 | 7 | // Declare optional and non-optional parameters 8 | export type RequiredPartial = Merage, Partial> 9 | 10 | export type IParameters = T extends (...args: infer P) => any ? P : never 11 | export type IReturenTypes = T extends (...args: any[]) => infer P ? P : never 12 | export type ThenArg = T extends PromiseLike ? U : T 13 | export type IActionsReturn = T extends keyof A 14 | ? A[T] extends Function 15 | ? IReturenTypes 16 | : never 17 | : any 18 | export type PayloadType = T extends keyof A 19 | ? A[T] extends Function 20 | ? IParameters[1] 21 | : never 22 | : any 23 | export type FormatEnum = T extends keyof A 24 | ? A[T] extends Function 25 | ? T 26 | : A 27 | : never 28 | -------------------------------------------------------------------------------- /src/layout/container.less: -------------------------------------------------------------------------------- 1 | @import '~@/config/style.config.less'; 2 | 3 | .container { 4 | position: relative; 5 | display: grid; 6 | grid-template-rows: 64px minmax(360px, calc(100% - 120px)) 80px; 7 | grid-template-columns: 100%; 8 | width: 100%; 9 | height: 100%; 10 | box-shadow: -1px 2px 6px 2px #dcdcdc; 11 | transition: 12 | width 0.2s, 13 | height 0.2s, 14 | transform 0.2s; 15 | // transform: matrix(1, 0, 0, 1, 0, 0); 16 | will-change: transform; 17 | backface-visibility: hidden; 18 | &-offline { 19 | grid-template-rows: 60px 40px minmax(360px, calc(100% - 120px)) 80px; 20 | } 21 | &-sm { 22 | position: absolute; 23 | bottom: 0; 24 | grid-template-rows: 80px; 25 | width: 100%; 26 | height: 80px; 27 | transform: matrix(1, 0, 0, 1, 0, 0) !important; 28 | & .schedule { 29 | display: none; 30 | } 31 | } 32 | &-lg { 33 | transform: matrix(1, 0, 0, 1, 0, 0) !important; 34 | } 35 | &-draging { 36 | transition: none; 37 | } 38 | } 39 | 40 | .container.container-md { 41 | width: @md-width; 42 | height: @md-height; 43 | } 44 | 45 | .container.container-electron { 46 | width: 100% !important; 47 | max-width: none !important; 48 | height: 100%; 49 | box-shadow: none; 50 | } 51 | -------------------------------------------------------------------------------- /src/layout/interface.ts: -------------------------------------------------------------------------------- 1 | export const enum LayoutSize { 2 | SM = 'sm', 3 | MD = 'md', 4 | LG = 'lg' 5 | } 6 | 7 | export interface LayoutState { 8 | screenSize: LayoutSize 9 | rebackSize: LayoutSize 10 | } 11 | 12 | export const enum LayoutMutations { 13 | CHANGE_WINDOW_SIZE = 'CHANGE_WINDOW_SIZE' 14 | } 15 | -------------------------------------------------------------------------------- /src/layout/module.ts: -------------------------------------------------------------------------------- 1 | import { state } from './state' 2 | import { mutations } from './sage' 3 | import { uesModuleStore } from '@/hooks/index' 4 | import { LayoutState, LayoutMutations } from '@/interface' 5 | 6 | export const LayoutNameSpaced = 'Layout' 7 | 8 | export const useLayoutModule = () => { 9 | return uesModuleStore(LayoutNameSpaced) 10 | } 11 | 12 | export default { 13 | namespaced: true, 14 | state, 15 | mutations 16 | } 17 | -------------------------------------------------------------------------------- /src/layout/music/music.less: -------------------------------------------------------------------------------- 1 | .music-layout { 2 | display: flex; 3 | flex-direction: column; 4 | padding: 20px; 5 | &--title { 6 | display: flex; 7 | align-items: center; 8 | justify-content: space-between; 9 | font-size: 20px; 10 | font-weight: bold; 11 | } 12 | &--head { 13 | margin-bottom: 10px; 14 | } 15 | &--body { 16 | flex: 1; 17 | height: 0; 18 | overflow: auto; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/layout/music/music.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | import './music.less' 3 | 4 | export const MusicLayout = defineComponent({ 5 | name: 'MusicLayout', 6 | setup(_, { slots }) { 7 | return () => ( 8 |
9 |
{slots.title && slots.title()}
10 |
{slots.head && slots.head()}
11 |
{slots.body && slots.body()}
12 |
13 | ) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /src/layout/sage.ts: -------------------------------------------------------------------------------- 1 | import { MutationTree } from 'vuex' 2 | import { LayoutMutations, LayoutState, LayoutSize } from '@/interface' 3 | 4 | export const mutations: MutationTree = { 5 | [LayoutMutations.CHANGE_WINDOW_SIZE](state, size: LayoutSize) { 6 | state.rebackSize = state.screenSize 7 | state.screenSize = size 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/layout/secondary/secondary.less: -------------------------------------------------------------------------------- 1 | .secondary { 2 | height: 100%; 3 | overflow-x: hidden; 4 | overflow-y: auto; 5 | > div { 6 | &:first-child { 7 | .ant-skeleton-header .ant-skeleton-avatar-lg { 8 | width: 210px; 9 | height: 210px; 10 | } 11 | } 12 | &:last-child { 13 | margin-top: 40px; 14 | } 15 | } 16 | &-head { 17 | display: flex; 18 | align-items: flex-start; 19 | } 20 | &-body { 21 | margin-top: 20px; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/layout/secondary/secondary.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, PropType, inject, ref } from 'vue' 2 | import { Skeleton } from 'ant-design-vue' 3 | import './secondary.less' 4 | 5 | export const SecondaryLayout = defineComponent({ 6 | name: 'Secondary', 7 | props: { 8 | src: { 9 | type: String as PropType, 10 | default: '' 11 | } 12 | }, 13 | setup(props, { slots }) { 14 | const loading = inject('loading', ref(false)) 15 | 16 | return () => ( 17 |
18 | 31 |
{slots.head && slots.head()}
32 |
33 | 42 |
{slots.body && slots.body()}
43 |
44 |
45 | ) 46 | } 47 | }) 48 | -------------------------------------------------------------------------------- /src/layout/state.ts: -------------------------------------------------------------------------------- 1 | import { LayoutState, LayoutSize } from '@/interface' 2 | 3 | export const state: LayoutState = { 4 | screenSize: LayoutSize.MD, 5 | rebackSize: LayoutSize.MD 6 | } 7 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import './app/index' 2 | -------------------------------------------------------------------------------- /src/pages/404/view/index.less: -------------------------------------------------------------------------------- 1 | .not-found { 2 | text-align: center; 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/404/view/index.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | import './index.less' 3 | 4 | export const $404 = defineComponent({ 5 | name: '$404', 6 | setup() { 7 | return () => ( 8 |
9 | 10 |
11 | 12 | 去听会歌 13 | 14 |
15 |
16 | ) 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /src/pages/artist/api/index.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/utils/http' 2 | import { Artists, Artist, Album, Desc } from '@/interface/index' 3 | 4 | const artistCache = new Map() 5 | 6 | export const getArtist = async (id: string): Promise => { 7 | if (artistCache.has(id)) { 8 | return artistCache.get(id) 9 | } 10 | const data = await get<{ data: { artist: Artist } }>('/api/artist/detail', { 11 | id 12 | }) 13 | artistCache.set(id, data.data.artist) 14 | return data.data.artist 15 | } 16 | 17 | export const getArtistAlbums = async (id: string): Promise => { 18 | const data = await get<{ hotAlbums: Album[] }>('/api/artist/album', { 19 | id 20 | }) 21 | return data.hotAlbums 22 | } 23 | 24 | export const getArtistDesc = async ( 25 | id: string 26 | ): Promise<{ introduction: Desc[]; briefDesc: string }> => { 27 | const data = await get<{ introduction: Desc[]; briefDesc: string }>( 28 | '/api/artist/desc', 29 | { 30 | id 31 | } 32 | ) 33 | return data 34 | } 35 | 36 | export const getArtistSimi = async (id: string): Promise => { 37 | const data = await get<{ artists: Artists[] }>('/api/simi/artist', { 38 | id 39 | }) 40 | return data.artists 41 | } 42 | -------------------------------------------------------------------------------- /src/pages/artist/children/albume.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | import { Album } from '@/interface' 3 | import { Grid } from '../components/grid' 4 | import { parentAP } from '../logic/ap' 5 | import { Jump } from '@/shared/jump-shared' 6 | 7 | export const Albume = defineComponent({ 8 | name: 'ArtistAlbume', 9 | setup() { 10 | const { state } = parentAP() 11 | 12 | // Directly coming from search will not trigger the activated event 13 | const jump = new Jump() 14 | const handleClick = (item: Album) => { 15 | jump.albumList(item.id) 16 | } 17 | 18 | return () => ( 19 |
20 | 21 |
22 | ) 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /src/pages/artist/children/detail.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, onActivated } from 'vue' 2 | import { parentAP } from '../logic/ap' 3 | import { ArtistActions } from '@/interface' 4 | 5 | export const Desc = defineComponent({ 6 | name: 'ArtistDesc', 7 | setup() { 8 | const { state, route, useActions } = parentAP() 9 | 10 | onActivated(() => { 11 | useActions( 12 | ArtistActions.SET_ACTION_ARTIST_DESC, 13 | route.params.id as string 14 | ) 15 | }) 16 | 17 | return () => ( 18 |
19 |

{state.artist.name}简介

20 |
{state.briefDesc}
21 | {state.introduction.map(item => ( 22 | <> 23 |

{item.ti}

24 |
{ 26 | return `
${ment}
` 27 | })} 28 | >
29 | 30 | ))} 31 |
32 | ) 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /src/pages/artist/children/mv.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | 3 | export const Mv = defineComponent({ 4 | name: 'ArtistMv', 5 | setup() { 6 | return () =>
7 | } 8 | }) 9 | -------------------------------------------------------------------------------- /src/pages/artist/children/similar.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, onActivated } from 'vue' 2 | import { Grid } from '../components/grid' 3 | import { Artists, ArtistActions } from '@/interface/index' 4 | import { parentAP } from '../logic/ap' 5 | import { Jump } from '@/shared/jump-shared' 6 | 7 | export const Similar = defineComponent({ 8 | name: 'ArtistSimilar', 9 | setup() { 10 | const jump = new Jump() 11 | const { state, route, useActions } = parentAP() 12 | 13 | onActivated(() => { 14 | useActions( 15 | ArtistActions.SET_ACTION_ARTIST_SIMI, 16 | route.params.id as string 17 | ) 18 | }) 19 | 20 | const handleClick = (item: Artists) => { 21 | jump.artist(item.id) 22 | } 23 | 24 | return () => ( 25 |
26 | 27 |
28 | ) 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /src/pages/artist/components/grid.less: -------------------------------------------------------------------------------- 1 | .grid-contanier { 2 | display: grid; 3 | grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); 4 | grid-gap: 20px; 5 | li { 6 | cursor: pointer; 7 | div { 8 | margin-top: 10px; 9 | } 10 | } 11 | &-picurl { 12 | width: 180px; 13 | height: 180px; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/artist/components/grid.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, PropType } from 'vue' 2 | import { Image } from '@/components/image/index' 3 | import './grid.less' 4 | import { noop } from '@/utils' 5 | 6 | export const Grid = defineComponent({ 7 | name: 'Grid', 8 | props: { 9 | source: { 10 | type: Array as PropType, 11 | default: () => [] 12 | }, 13 | onClick: { 14 | type: Function as PropType<(value: any) => void>, 15 | default: noop 16 | } 17 | }, 18 | emits: ['click'], 19 | setup(props, { emit }) { 20 | const handleClick = (item: any) => { 21 | emit('click', item) 22 | } 23 | return () => ( 24 |
    25 | {props.source.map(item => ( 26 |
  • handleClick(item)}> 27 | 31 |
    {item.name}
    32 |
  • 33 | ))} 34 |
35 | ) 36 | } 37 | }) 38 | -------------------------------------------------------------------------------- /src/pages/artist/index.ts: -------------------------------------------------------------------------------- 1 | export { Albume } from './children/albume' 2 | export { Desc } from './children/detail' 3 | export { Mv } from './children/mv' 4 | export { Similar } from './children/similar' 5 | -------------------------------------------------------------------------------- /src/pages/artist/interface.ts: -------------------------------------------------------------------------------- 1 | import { Artists, Albums } from '@/interface/index' 2 | 3 | export interface Artist extends Artists { 4 | cover: string 5 | briefDesc: string 6 | albumSize: number 7 | musicSize: number 8 | mvSize: number 9 | } 10 | 11 | export interface Album extends Albums { 12 | blurPicUrl: string 13 | } 14 | 15 | export interface Desc { 16 | ti: string 17 | txt: string 18 | } 19 | 20 | export interface ArtistState { 21 | artist: Artist 22 | album: Album[] 23 | introduction: Desc[] 24 | briefDesc: string 25 | simi: Artists[] 26 | } 27 | 28 | export const enum ArtistMutations { 29 | SET_ARTIST_DETAIL = 'SET_ARTIST_DETAIL', 30 | SET_ARTIST_ALBUM = 'SET_ARTIST_ALBUM', 31 | SET_ARTIST_DESC = 'SET_ARTIST_DESC', 32 | SET_ARTIST_SIMI = 'SET_ARTIST_SIMI' 33 | } 34 | 35 | export const enum ArtistActions { 36 | SET_ACTION_ARTIST_DETAIL = 'SET_ACTION_ARTIST_DETAIL', 37 | SET_ACTION_ARTIST_ALBUM = 'SET_ACTION_ARTIST_ALBUM', 38 | SET_ACTION_ARTIST_DESC = 'SET_ACTION_ARTIST_DESC', 39 | SET_ACTION_ARTIST_SIMI = 'SET_ACTION_ARTIST_SIMI' 40 | } 41 | -------------------------------------------------------------------------------- /src/pages/artist/logic/ap.ts: -------------------------------------------------------------------------------- 1 | // ap = 穿甲弹头 2 | // The functions exported from this file can only be run in setup for reuse of logic codes 3 | import { useRoute, useRouter } from '@/hooks/index' 4 | import { useArtistModule } from '@/modules' 5 | 6 | export const parentAP = () => { 7 | const route = useRoute() 8 | const router = useRouter() 9 | const { useActions, useState } = useArtistModule() 10 | 11 | return { 12 | state: useState(), 13 | route: route, 14 | router: router, 15 | useActions 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/artist/module.ts: -------------------------------------------------------------------------------- 1 | import { actions, mutations } from './sage' 2 | import { state } from './state' 3 | import { uesModuleStore } from '@/hooks/index' 4 | import { ArtistState, ArtistActions, ArtistMutations } from '@/interface' 5 | 6 | export const ArtistNameSpaced = 'Artist' 7 | 8 | export const useArtistModule = () => { 9 | return uesModuleStore( 10 | ArtistNameSpaced 11 | ) 12 | } 13 | 14 | export default { 15 | namespaced: true, 16 | state, 17 | actions, 18 | mutations 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/artist/state.ts: -------------------------------------------------------------------------------- 1 | export const state = { 2 | artist: {}, 3 | album: [], 4 | introduction: [], 5 | briefDesc: '', 6 | simi: [] 7 | } 8 | -------------------------------------------------------------------------------- /src/pages/artist/view/index.less: -------------------------------------------------------------------------------- 1 | .artist { 2 | padding: 20px; 3 | &-coverimg { 4 | width: 210px; 5 | height: 210px; 6 | margin-right: 20px; 7 | } 8 | &-detail { 9 | &-authoring { 10 | display: flex; 11 | align-items: center; 12 | font-size: 13px; 13 | p { 14 | margin-right: 10px; 15 | } 16 | } 17 | } 18 | // children 19 | &-desc { 20 | margin-top: 20px; 21 | h2 { 22 | font-size: 16px; 23 | } 24 | div { 25 | margin-bottom: 20px; 26 | line-height: 2.5; 27 | text-indent: 32px; 28 | line-break: anywhere; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/pages/auth/component/auth-view/index.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, onMounted } from 'vue' 2 | import { MaskView } from '../mask-view' 3 | import './index.less' 4 | import { hideAuth } from '@/helpers' 5 | 6 | export const AuthView = defineComponent({ 7 | setup(props, { slots }) { 8 | onMounted(() => { 9 | window.history.pushState(null, 'RADISHES', location.href) 10 | window.addEventListener( 11 | 'popstate', 12 | () => { 13 | hideAuth() 14 | }, 15 | { 16 | once: true 17 | } 18 | ) 19 | }) 20 | 21 | // TODO 怎么操作呢?? 22 | return () => { 23 | return ( 24 | // @ts-ignore 25 | 26 |
27 | 34 |
{slots.default?.()}
35 |
36 |
37 | ) 38 | } 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /src/pages/auth/component/button/index.less: -------------------------------------------------------------------------------- 1 | @auth-theme-color: var(--base-color); 2 | 3 | .van-button { 4 | &.auth-button { 5 | height: auto; 6 | padding: 10px 0; 7 | font-size: 16px; 8 | color: white; 9 | background-color: @auth-theme-color; 10 | border: none; 11 | border-radius: 4px; 12 | &::before { 13 | position: absolute; 14 | top: 50%; 15 | left: 50%; 16 | width: 100%; 17 | height: 100%; 18 | content: ' '; 19 | background-color: #000; 20 | border: inherit; 21 | border-color: #000; 22 | border-radius: inherit; 23 | opacity: 0; 24 | -webkit-transform: translate(-50%, -50%); 25 | transform: translate(-50%, -50%); 26 | } 27 | &:hover { 28 | &::before { 29 | opacity: 0.05; 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/pages/auth/component/input-field/index.less: -------------------------------------------------------------------------------- 1 | .ra-inputfield { 2 | display: flex; 3 | align-items: center; 4 | 5 | input { 6 | width: 100%; 7 | height: 38px; 8 | padding: 0 6px 0 10px; 9 | font-size: 14px; 10 | border: none; 11 | &::-webkit-input-placeholder { 12 | font-size: 12px; 13 | font-weight: normal; 14 | color: #b8b8b8; 15 | } 16 | } 17 | 18 | svg { 19 | display: block; 20 | } 21 | 22 | &__box { 23 | flex: 1; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/pages/auth/component/link/index.less: -------------------------------------------------------------------------------- 1 | .link-auth__icon { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | width: 36px; 6 | height: 36px; 7 | border: 1px solid #c8c8c8; 8 | border-radius: 50%; 9 | } 10 | 11 | .link-line { 12 | color: #333; 13 | text-decoration: underline; 14 | &:hover { 15 | color: #333; 16 | text-decoration: underline; 17 | cursor: pointer; 18 | } 19 | } 20 | 21 | .link-light { 22 | color: #507daf; 23 | &:hover { 24 | color: #507daf; 25 | } 26 | } 27 | 28 | .link-normal { 29 | color: #a9a9a9; 30 | &:hover { 31 | color: #a9a9a9; 32 | } 33 | } 34 | 35 | .link-auth { 36 | display: flex; 37 | flex-direction: column; 38 | align-items: center; 39 | width: 36px; 40 | span { 41 | padding-top: 4px; 42 | color: #b8b8b8; 43 | word-break: keep-all; 44 | } 45 | &:hover { 46 | span { 47 | color: #333; 48 | } 49 | .link-auth__icon { 50 | background-color: #f5f5f5; 51 | } 52 | 53 | cursor: pointer; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/pages/auth/component/mask-view/index.less: -------------------------------------------------------------------------------- 1 | .absolutefill { 2 | position: fixed; 3 | top: 0; 4 | right: 0; 5 | bottom: 0; 6 | left: 0; 7 | } 8 | .mask-wrapper { 9 | &:extend(.absolutefill); 10 | 11 | z-index: 999; 12 | background: rgba(0, 0, 0, 0.5); 13 | } 14 | -------------------------------------------------------------------------------- /src/pages/auth/component/mask-view/index.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | import './index.less' 3 | 4 | export const MaskView = defineComponent({ 5 | name: 'MaskView', 6 | setup(props, { slots }) { 7 | return () => { 8 | return
{slots.default?.()}
9 | } 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /src/pages/auth/constant.ts: -------------------------------------------------------------------------------- 1 | export const AUTH_TYPE: Record = { 2 | PHONE_LOGIN: 0, 3 | EMAIL_LOGIN: 1, 4 | REGISTER: 2, 5 | RESET_PWD: 3, 6 | SMS_CODE: 4 7 | } 8 | 9 | export const TERMS = [ 10 | { 11 | name: '《服务条款》', 12 | link: 'https://st.music.163.com/official-terms/service' 13 | }, 14 | { 15 | name: '《隐私政策》', 16 | link: 'https://st.music.163.com/official-terms/privacy' 17 | }, 18 | { 19 | name: '《儿童隐私政策》', 20 | link: 'https://st.music.163.com/official-terms/children' 21 | } 22 | ] 23 | 24 | export const PROVIDER_AUTH_UTIL = 'authUtil' 25 | -------------------------------------------------------------------------------- /src/pages/auth/hooks.ts: -------------------------------------------------------------------------------- 1 | import { reactive, computed } from 'vue' 2 | 3 | export const useText = (defaultText = ''): any => { 4 | const state = reactive<{ text: string }>({ 5 | text: defaultText 6 | }) 7 | 8 | const setText = (text: string): void => { 9 | state.text = text 10 | } 11 | 12 | const isNullText = computed(() => state.text === '') 13 | 14 | return [state, setText, isNullText] 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/auth/module.ts: -------------------------------------------------------------------------------- 1 | import { mutations, getters } from './sage' 2 | import { state } from './state' 3 | 4 | export const AuthNameSpaced = 'Auth' 5 | 6 | export default { 7 | namespaced: true, 8 | state, 9 | mutations, 10 | getters 11 | } 12 | -------------------------------------------------------------------------------- /src/pages/auth/state.ts: -------------------------------------------------------------------------------- 1 | import { Song } from '@/interface' 2 | 3 | export interface AuthState { 4 | user: { 5 | loginType: number 6 | code?: number 7 | account: any 8 | token: string 9 | profile: any 10 | bindings: Array 11 | cookie: string 12 | [key: string]: any 13 | } | null 14 | show: boolean 15 | userInfoLoading: boolean 16 | playlist: Song[] 17 | } 18 | 19 | export interface AuthGetter { 20 | isLogin: boolean 21 | } 22 | 23 | export const state = { 24 | user: null, 25 | show: false, 26 | userInfoLoading: false, 27 | playlist: [] 28 | } 29 | -------------------------------------------------------------------------------- /src/pages/auth/theme.ts: -------------------------------------------------------------------------------- 1 | export const leakThemeColor = '#f29c9f' 2 | export const inputColor = '#b8b8b8' 3 | export const themeColor = '#d33a31' 4 | -------------------------------------------------------------------------------- /src/pages/cloud/api/cloud.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/utils/http' 2 | import { CloudList, Pagination } from '@/interface' 3 | 4 | export const getCloud = async ({ 5 | offset, 6 | limit 7 | }: Pagination): Promise => { 8 | const data = await get<{ data: CloudList[] }>('/api/user/cloud', { 9 | offset, 10 | limit 11 | }) 12 | return data.data 13 | } 14 | -------------------------------------------------------------------------------- /src/pages/cloud/index.ts: -------------------------------------------------------------------------------- 1 | export { Cloud } from './view/index' 2 | -------------------------------------------------------------------------------- /src/pages/cloud/interface.ts: -------------------------------------------------------------------------------- 1 | import { GlobalBase, Songs, Artists, Pagination } from '@/interface/index' 2 | 3 | export interface SimpleSong extends GlobalBase { 4 | ar: Artists 5 | al: unknown[] 6 | } 7 | 8 | export interface CloudList extends Partial { 9 | simpleSong: SimpleSong 10 | version: string 11 | fileName: string // .mp3 12 | songName: string 13 | addTime: number 14 | songId: number 15 | fileSize: number 16 | artist: string 17 | uid?: string 18 | } 19 | 20 | export interface CloudState { 21 | cloudList: CloudList[] 22 | pagination: Pagination 23 | } 24 | 25 | export const enum CloudActions { 26 | CLOUD_LIST_ACTION = 'CLOUD_LIST_ACTION' 27 | } 28 | 29 | export const enum CloudMutations { 30 | SET_CLOUD_LIST = 'SET_CLOUD_LIST', 31 | UNSHIFT_CLOUD_LIST = 'UNSHIFT_CLOUD_LIST', 32 | REMOVE_UNSHIFT_CLOUD_LIST = 'REMOVE_UNSHIFT_CLOUD_LIST' 33 | } 34 | -------------------------------------------------------------------------------- /src/pages/cloud/module.ts: -------------------------------------------------------------------------------- 1 | import { actions, mutations } from './sage' 2 | import { state } from './state' 3 | import { uesModuleStore } from '@/hooks/index' 4 | import { CloudState, CloudActions, CloudMutations } from '@/interface' 5 | 6 | export const CloudNameSpaced = 'Cloud' 7 | 8 | export const useCloudModule = () => { 9 | return uesModuleStore( 10 | CloudNameSpaced 11 | ) 12 | } 13 | 14 | export default { 15 | namespaced: true, 16 | state, 17 | actions, 18 | mutations 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/cloud/state.ts: -------------------------------------------------------------------------------- 1 | import { CloudState } from '@/interface' 2 | 3 | export const state: CloudState = { 4 | cloudList: [], 5 | pagination: { 6 | offset: 0, 7 | limit: 200 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/cloud/view/index.less: -------------------------------------------------------------------------------- 1 | .cloud { 2 | &-head { 3 | display: flex; 4 | align-items: center; 5 | margin-top: 10px; 6 | .upload-music-btn { 7 | margin-left: 10px; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/pages/download/children/mv.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | 3 | export const DownloadMv = defineComponent({ 4 | name: 'DownloadMv', 5 | setup() { 6 | return () =>
已下载MV
7 | } 8 | }) 9 | -------------------------------------------------------------------------------- /src/pages/download/children/song.less: -------------------------------------------------------------------------------- 1 | .download-song { 2 | margin-top: 10px; 3 | overflow: auto; 4 | &-head { 5 | display: flex; 6 | align-items: center; 7 | &--dir { 8 | font-size: 12px; 9 | button.v-easy-button { 10 | margin-left: 10px; 11 | } 12 | } 13 | > div { 14 | margin-left: 10px; 15 | } 16 | } 17 | &-body { 18 | margin-top: 20px; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/pages/download/index.ts: -------------------------------------------------------------------------------- 1 | export { Download } from './view/index' 2 | export { DownloadSong } from './children/song' 3 | export { DownloadMv } from './children/mv' 4 | -------------------------------------------------------------------------------- /src/pages/download/interface.ts: -------------------------------------------------------------------------------- 1 | import { SongsDetail } from '@/interface' 2 | 3 | export interface Downloaded extends SongsDetail { 4 | dlt: number 5 | } 6 | 7 | export interface DownloadState { 8 | downloaded: Downloaded[] 9 | downloadPath: string 10 | } 11 | 12 | export const enum DownloadActions { 13 | DOWNLOAD_MUSIC = 'DOWNLOAD_MUSIC' 14 | } 15 | 16 | export const enum DownloadMutations { 17 | SET_DOWNLOAD_MUSIC = 'SET_DOWNLOAD_MUSIC', 18 | SET_DOWNLOAD_PATH = 'SET_DOWNLOAD_PATH', 19 | REMOVE_DOWNLOAD_MUSIC = 'REMOVE_DOWNLOAD_MUSIC' 20 | } 21 | -------------------------------------------------------------------------------- /src/pages/download/module.ts: -------------------------------------------------------------------------------- 1 | import { actions, mutations } from './sage' 2 | import { state } from './state' 3 | import { uesModuleStore } from '@/hooks/index' 4 | import { DownloadState, DownloadActions, DownloadMutations } from '@/interface' 5 | 6 | export const DownloadNameSpaced = 'Download' 7 | 8 | export const useDownloadModule = () => { 9 | return uesModuleStore( 10 | DownloadNameSpaced 11 | ) 12 | } 13 | 14 | export default { 15 | namespaced: true, 16 | state, 17 | actions, 18 | mutations 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/download/state.ts: -------------------------------------------------------------------------------- 1 | import { DownloadState } from '@/interface' 2 | 3 | export const state: DownloadState = { 4 | downloaded: [], 5 | downloadPath: '' 6 | } 7 | -------------------------------------------------------------------------------- /src/pages/download/view/index.less: -------------------------------------------------------------------------------- 1 | .download { 2 | visibility: visible; 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/download/view/index.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | import { 3 | SecondaryBar, 4 | renderNavList 5 | } from '@/components-business/secondary-bar/index' 6 | import { navRouter } from '@/router/index' 7 | import { RouterView } from 'vue-router' 8 | import { MusicLayout } from '@/layout/music/music' 9 | import './index.less' 10 | 11 | export const Download = defineComponent({ 12 | name: 'Download', 13 | setup() { 14 | const nav = renderNavList(navRouter, Download.name) 15 | return () => ( 16 |
下载管理
, 19 | head: () => , 20 | body: () => 21 | }} 22 | /> 23 | ) 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /src/pages/findmusic/recommend/api.ts: -------------------------------------------------------------------------------- 1 | import { get } from '@/utils/http' 2 | import { Banners } from '@/interface' 3 | import { Song } from '@/interface/index' 4 | 5 | export const getBanner = async (type: number): Promise => { 6 | const data = await get<{ banners: Banners[] }>('/api/banner', { 7 | type 8 | }) 9 | return data.banners 10 | } 11 | 12 | export const getRecommendSongList = async (limit = 30): Promise => { 13 | const data = await get<{ result: Song[] }>('/api/personalized', { 14 | limit 15 | }) 16 | return data.result 17 | } 18 | 19 | export const getDailyRecommendSongList = async () => { 20 | const data = await get<{ recommend: Song[] }>( 21 | '/api/recommend/resource', 22 | { limit: 10 }, 23 | { 24 | auths: true 25 | } 26 | ) 27 | return data.recommend.map(item => ({ 28 | ...item, 29 | playCount: item.playcount 30 | })) 31 | } 32 | 33 | export const getRecommendNewsong = async (): Promise => { 34 | const data = await get('/api/personalized/newsong') 35 | return data.result 36 | } 37 | -------------------------------------------------------------------------------- /src/pages/findmusic/recommend/components/dailysong.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/pages/findmusic/recommend/components/recommendsong.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/pages/findmusic/recommend/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 20 | 21 |