├── .dockerignore
├── .editorconfig
├── .env
├── .eslintignore
├── .eslintrc.cjs
├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .npmrc-example
├── .prettierignore
├── .prettierrc.yaml
├── .vscode
└── settings.json
├── Dockerfile
├── LICENSE.txt
├── README.md
├── auto-imports.d.ts
├── components.d.ts
├── docker-compose.yml
├── electron-builder.yml
├── electron.vite.config.mjs
├── electron
├── index.html
├── main
│ ├── index.js
│ ├── mainIpcMain.js
│ ├── startMainServer.js
│ ├── startNcmServer.js
│ └── utils
│ │ ├── checkUpdates.js
│ │ ├── createGlobalShortcut.js
│ │ ├── createSystemTray.js
│ │ ├── getNeteaseMusicUrl.js
│ │ ├── kwDES.js
│ │ └── readDirAsync.js
└── preload
│ └── index.mjs
├── index.html
├── nginx.conf
├── package.json
├── public
├── favicon.ico
├── favicon.png
├── font
│ ├── font.min.css
│ └── font_files
│ │ ├── HarmonyOS_Regular.a.woff2
│ │ ├── HarmonyOS_Regular.a0.woff2
│ │ ├── HarmonyOS_Regular.a1.woff2
│ │ ├── HarmonyOS_Regular.aa.woff2
│ │ ├── HarmonyOS_Regular.ab.woff2
│ │ ├── HarmonyOS_Regular.ac.woff2
│ │ ├── HarmonyOS_Regular.ad.woff2
│ │ ├── HarmonyOS_Regular.ae.woff2
│ │ ├── HarmonyOS_Regular.af.woff2
│ │ ├── HarmonyOS_Regular.ag.woff2
│ │ ├── HarmonyOS_Regular.ah.woff2
│ │ ├── HarmonyOS_Regular.ai.woff2
│ │ ├── HarmonyOS_Regular.aj.woff2
│ │ ├── HarmonyOS_Regular.ak.woff2
│ │ ├── HarmonyOS_Regular.al.woff2
│ │ ├── HarmonyOS_Regular.am.woff2
│ │ ├── HarmonyOS_Regular.an.woff2
│ │ ├── HarmonyOS_Regular.ao.woff2
│ │ ├── HarmonyOS_Regular.ap.woff2
│ │ ├── HarmonyOS_Regular.aq.woff2
│ │ ├── HarmonyOS_Regular.ar.woff2
│ │ ├── HarmonyOS_Regular.as.woff2
│ │ ├── HarmonyOS_Regular.at.woff2
│ │ ├── HarmonyOS_Regular.au.woff2
│ │ ├── HarmonyOS_Regular.av.woff2
│ │ ├── HarmonyOS_Regular.aw.woff2
│ │ ├── HarmonyOS_Regular.ax.woff2
│ │ ├── HarmonyOS_Regular.ay.woff2
│ │ ├── HarmonyOS_Regular.az.woff2
│ │ ├── HarmonyOS_Regular.b.woff2
│ │ ├── HarmonyOS_Regular.c.woff2
│ │ ├── HarmonyOS_Regular.d.woff2
│ │ ├── HarmonyOS_Regular.e.woff2
│ │ ├── HarmonyOS_Regular.f.woff2
│ │ ├── HarmonyOS_Regular.g.woff2
│ │ ├── HarmonyOS_Regular.h.woff2
│ │ ├── HarmonyOS_Regular.i.woff2
│ │ ├── HarmonyOS_Regular.j.woff2
│ │ ├── HarmonyOS_Regular.k.woff2
│ │ ├── HarmonyOS_Regular.l.woff2
│ │ ├── HarmonyOS_Regular.m.woff2
│ │ ├── HarmonyOS_Regular.n.woff2
│ │ ├── HarmonyOS_Regular.o.woff2
│ │ ├── HarmonyOS_Regular.p.woff2
│ │ ├── HarmonyOS_Regular.q.woff2
│ │ ├── HarmonyOS_Regular.r.woff2
│ │ ├── HarmonyOS_Regular.s.woff2
│ │ ├── HarmonyOS_Regular.t.woff2
│ │ ├── HarmonyOS_Regular.u.woff2
│ │ ├── HarmonyOS_Regular.v.woff2
│ │ ├── HarmonyOS_Regular.w.woff2
│ │ ├── HarmonyOS_Regular.x.woff2
│ │ ├── HarmonyOS_Regular.y.woff2
│ │ └── HarmonyOS_Regular.z.woff2
└── images
│ ├── icons
│ ├── afdian.jpg
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── favicon-16x16.png
│ ├── favicon-192x192.png
│ ├── favicon-256x256.png
│ ├── favicon-32x32.png
│ ├── favicon-512x512.png
│ ├── favicon-96x96.png
│ ├── favicon.ico
│ ├── favicon.png
│ ├── favicon.svg
│ ├── logo-dark.png
│ ├── logo-light.png
│ ├── mstile-150x150.png
│ ├── next-dark.png
│ ├── next-light.png
│ ├── open-dark.png
│ ├── open-light.png
│ ├── pause-dark.png
│ ├── pause-light.png
│ ├── play-dark.png
│ ├── play-light.png
│ ├── power-dark.png
│ ├── power-light.png
│ ├── prev-dark.png
│ ├── prev-light.png
│ ├── safari-pinned-tab.svg
│ ├── setting-dark.png
│ └── setting-light.png
│ └── pic
│ ├── album.jpg
│ ├── album.png
│ ├── artist.jpg
│ ├── avatar.jpg
│ ├── cover-2.jpg
│ ├── cover.jpg
│ ├── default.jpg
│ ├── hot-list.png
│ ├── like.jpg
│ ├── pl-1.jpg
│ ├── pl-2.jpg
│ ├── pl-3.jpg
│ ├── pl-4.jpg
│ ├── pointer.png
│ ├── radar-private.jpg
│ ├── radar-treasure.jpg
│ ├── record.png
│ ├── song.jpg
│ ├── video.png
│ └── vip.png
├── screenshots
├── TuneFree - 主页面.jpg
├── TuneFree - 发现页面.jpg
├── TuneFree - 播放页面.jpg
├── TuneFree - 本地音乐.jpg
├── TuneFree - 歌单页面.jpg
├── TuneFree - 评论页面.jpg
├── TuneFree-播放.png
├── TuneFree-首页.png
├── TuneFree.jpg
└── build.png
├── src
├── App.vue
├── api
│ ├── album.js
│ ├── artist.js
│ ├── cloud.js
│ ├── comment.js
│ ├── dj.js
│ ├── login.js
│ ├── other.js
│ ├── playlist.js
│ ├── recommend.js
│ ├── search.js
│ ├── song.js
│ ├── update.js
│ ├── user.js
│ └── video.js
├── assets
│ ├── emoji.json
│ ├── icon.json
│ ├── idMeta.json
│ └── themeColor.json
├── components
│ ├── Cover
│ │ ├── CoverDropdown.vue
│ │ ├── CoverPlayBtn.vue
│ │ ├── MainCover.vue
│ │ ├── SpecialCover.vue
│ │ └── SpecialCoverCard.vue
│ ├── Global
│ │ ├── MainLayout.vue
│ │ ├── Menu.vue
│ │ ├── Pagination.vue
│ │ ├── Playlist.vue
│ │ ├── Provider.vue
│ │ └── SvgIcon.vue
│ ├── List
│ │ ├── CommentList.vue
│ │ ├── SongList.vue
│ │ ├── SongListDrawer.vue
│ │ └── SongListDropdown.vue
│ ├── Modal
│ │ ├── AddPlaylist.vue
│ │ ├── CloudSongMatch.vue
│ │ ├── CreatePlaylist.vue
│ │ ├── DownloadSong.vue
│ │ ├── Login.vue
│ │ ├── LoginPhone.vue
│ │ ├── LoginQRCode.vue
│ │ ├── PlaylistUpdate.vue
│ │ └── UpCloudSong.vue
│ ├── Nav
│ │ ├── MainNav.vue
│ │ └── UserData.vue
│ ├── Player
│ │ ├── CountDown.vue
│ │ ├── FullPlayer.vue
│ │ ├── Lyric.vue
│ │ ├── MainControl.vue
│ │ ├── PlayerControl.vue
│ │ ├── PlayerCover.vue
│ │ ├── PrivateFm.vue
│ │ └── Spectrum.vue
│ ├── Search
│ │ ├── SearchDropdown.vue
│ │ ├── SearchHot.vue
│ │ ├── SearchInp.vue
│ │ └── SearchSuggestions.vue
│ └── WinDom
│ │ └── TitleBar.vue
├── main.js
├── router
│ ├── index.js
│ └── routes.js
├── stores
│ ├── index.js
│ ├── indexedDB.js
│ ├── musicData.js
│ ├── siteData.js
│ ├── siteSettings.js
│ └── siteStatus.js
├── style
│ ├── animate.scss
│ └── main.scss
├── utils
│ ├── Player.js
│ ├── auth.js
│ ├── base64.js
│ ├── color-utils.js
│ ├── cover-color.js
│ ├── debounce.js
│ ├── formRules.js
│ ├── formatData.js
│ ├── globalEvents.js
│ ├── globalShortcut.js
│ ├── helper.js
│ ├── parseLyric.js
│ ├── request.js
│ ├── throttle.js
│ ├── timeTools.js
│ └── userSignIn.js
└── views
│ ├── Artist
│ ├── albums.vue
│ ├── hot.vue
│ ├── index.vue
│ ├── songs.vue
│ └── videos.vue
│ ├── Cloud.vue
│ ├── Comment.vue
│ ├── DailySongs.vue
│ ├── DesktopLyric.vue
│ ├── Discover
│ ├── artists.vue
│ ├── index.vue
│ ├── new.vue
│ ├── playlists.vue
│ └── toplists.vue
│ ├── Dj
│ ├── index.vue
│ └── type.vue
│ ├── History.vue
│ ├── Home.vue
│ ├── Like
│ ├── albums.vue
│ ├── artists.vue
│ ├── djs.vue
│ ├── index.vue
│ ├── playlists.vue
│ └── videos.vue
│ ├── List
│ ├── album.vue
│ ├── dj.vue
│ └── playlist.vue
│ ├── Local
│ ├── albums.vue
│ ├── artists.vue
│ ├── index.vue
│ └── songs.vue
│ ├── Player.vue
│ ├── Search
│ ├── albums.vue
│ ├── artists.vue
│ ├── djs.vue
│ ├── index.vue
│ ├── playlists.vue
│ ├── songs.vue
│ └── videos.vue
│ ├── Setting
│ └── index.vue
│ ├── Song.vue
│ ├── State
│ ├── 403.vue
│ ├── 404.vue
│ └── 500.vue
│ └── Test.vue
└── vercel.json
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | Dockerfile*
4 | docker-compose*
5 | .dockerignore
6 | .git
7 | .github
8 | .gitignore
9 | README.md
10 | LICENSE
11 | .vscode
12 | dist
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # 根配置文件
2 | ## 编辑器在查找配置时会停止查找更高层次的配置文件
3 | root = true
4 |
5 | # 通配符,匹配所有文件
6 | [*]
7 | # 设置字符集为 UTF-8,确保文件中的文本使用 UTF-8 编码
8 | charset = utf-8
9 | # 使用空格作为缩进风格
10 | indent_style = space
11 | # 设置每个缩进级别的空格数量为 2
12 | indent_size = 2
13 | # 设置行尾换行符为LF(Line Feed)
14 | end_of_line = lf
15 | # 在文件的末尾插入一个新行
16 | insert_final_newline = true
17 | # 删除每一行末尾的尾随空格
18 | trim_trailing_whitespace = true
19 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | # 程序配置
2 | ## 程序名称
3 | MAIN_VITE_TITLE = "TuneFree"
4 | ## 程序主端口
5 | MAIN_VITE_MAIN_PORT = 7899
6 | ## 程序开发环境运行端口
7 | MAIN_VITE_DEV_PORT = 6499
8 |
9 | # 全局 API 配置
10 | ## API 运行地址
11 | MAIN_VITE_SERVER_HOST = 127.0.0.1
12 | ## API 运行端口
13 | MAIN_VITE_SERVER_PORT = 11451
14 | ## API 在线地址( 网址结尾不要加 / )
15 | ### 用于非客户端( 浏览器环境 )
16 | RENDERER_VITE_SERVER_URL = /api
17 |
18 | # 程序信息
19 | RENDERER_VITE_SITE_TITLE = "tunefree"
20 | RENDERER_VITE_SITE_ANTHOR = "是青旨啊"
21 | RENDERER_VITE_SITE_KEYWORDS = "tunefree,云音乐,播放器,在线音乐,在线播放器,音乐播放器"
22 | RENDERER_VITE_SITE_DES = "一个简约的在线音乐播放器,具有音乐搜索、播放、每日推荐、私人FM、歌词显示、歌曲评论、网易云登录与云盘等功能"
23 | RENDERER_VITE_SITE_URL = "https://tunefree.fun"
24 |
25 | # Cookie
26 | ## 咪咕音乐 Cookie
27 |
28 | # 公告配置
29 | ## 若无需公告,请将标题或内容任意一项设为空即可
30 | ## 公告类型
31 | RENDERER_VITE_ANN_TYPE = "info"
32 | ## 公告标题
33 | RENDERER_VITE_ANN_TITLE = ""
34 | ## 公告内容
35 | RENDERER_VITE_ANN_CONTENT = ""
36 | ## 公告时长(毫秒)不可超过 999999
37 | RENDERER_VITE_ANN_DURATION = 8000
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | out
4 | .gitignore
5 | auto-imports.d.ts
6 | components.d.ts
7 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | require("@rushstack/eslint-patch/modern-module-resolution");
3 |
4 | module.exports = {
5 | extends: [
6 | "eslint:recommended",
7 | "plugin:vue/vue3-recommended",
8 | "@electron-toolkit",
9 | "@vue/eslint-config-prettier",
10 | ],
11 | rules: {
12 | "vue/v-on-event-hyphenation": "off",
13 | "vue/require-default-prop": "off",
14 | "vue/multi-word-component-names": "off",
15 | "vue/attribute-hyphenation": "off",
16 | },
17 | ignorePatterns: [
18 | "node_modules/",
19 | "build/",
20 | "dist/",
21 | "out/",
22 | "components.d.ts",
23 | "auto-imports.d.ts",
24 | ],
25 | globals: {
26 | defineProps: true,
27 | defineEmits: true,
28 | withDefaults: true,
29 | h: true,
30 | vue: true,
31 | ref: true,
32 | reactive: true,
33 | computed: true,
34 | watch: true,
35 | provide: true,
36 | inject: true,
37 | defineComponent: true,
38 | onBeforeMount: true,
39 | onBeforeUnmount: true,
40 | onUnmounted: true,
41 | onMounted: true,
42 | nextTick: true,
43 | watchEffect: true,
44 | electron: true,
45 | $message: true,
46 | $dialog: true,
47 | $loadingBar: true,
48 | $changeLogin: true,
49 | $notification: true,
50 | $changeThemeColor: true,
51 | $canNotConnect: true,
52 | $refreshCloudCatch: true,
53 | $cleanAll: true,
54 | $player: true,
55 | },
56 | };
57 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | .DS_Store
12 | dist
13 | dist-ssr
14 | coverage
15 | *.local
16 | out
17 | .npmrc
18 |
19 |
20 | /cypress/videos/
21 | /cypress/screenshots/
22 |
23 | # Editor directories and files
24 | .vscode/*
25 | .vscode
26 | !.vscode/extensions.json
27 | .idea
28 | *.suo
29 | *.ntvs*
30 | *.njsproj
31 | *.sln
32 | *.sw?
33 |
--------------------------------------------------------------------------------
/.npmrc-example:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmmirror.com
2 | disturl=https://registry.npmmirror.com/-/binary/node
3 | # ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
4 | ELECTRON_MIRROR=https://registry.npmmirror.com/-/binary/electron/
5 | shamefully-hoist=true
6 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | out
2 | dist
3 | pnpm-lock.yaml
4 | LICENSE.md
5 | tsconfig.json
6 | tsconfig.*.json
7 | auto-imports.d.ts
8 | components.d.ts
9 |
--------------------------------------------------------------------------------
/.prettierrc.yaml:
--------------------------------------------------------------------------------
1 | # 是否使用单引号而不是双引号
2 | singleQuote: false
3 | # 是否在语句末尾使用分号
4 | semi: true
5 | # 每行的最大打印宽度
6 | printWidth: 100
7 | # 是否在对象和数组的末尾加上逗号
8 | trailingComma: all
9 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "githubPullRequests.ignoredPullRequestBranches": [
3 | "master"
4 | ]
5 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # build
2 | FROM node:18-alpine as builder
3 |
4 | RUN apk update && apk add --no-cache git
5 |
6 | WORKDIR /app
7 |
8 | COPY package*.json ./
9 |
10 | RUN npm install
11 |
12 | COPY . .
13 |
14 | RUN [ ! -e ".env" ] && cp .env.example .env || true
15 |
16 | RUN npm run build
17 |
18 | # nginx
19 | FROM nginx:1.25.3-alpine-slim as app
20 |
21 | COPY --from=builder /app/out/renderer /usr/share/nginx/html
22 |
23 | COPY --from=builder /app/nginx.conf /etc/nginx/conf.d/default.conf
24 |
25 | RUN apk add --no-cache npm
26 |
27 | RUN npm install -g NeteaseCloudMusicApi
28 |
29 | CMD nginx && npx NeteaseCloudMusicApi
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # TuneFree
3 |
4 |
教你一点礼仪,别他妈跑到别人作品下面瞎几把吹说TuneFree比你做的好,别他妈吹牛逼了,我也是二开的别人软件,没啥技术含量,别tm到处引战给我丢脸了
5 |
6 |
7 | > 随心所欲,音乐无界 - 音遇自由
8 |
9 | > [!IMPORTANT]
10 | >
11 | > 由于 [NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi) 项目已停止维护,由于接口状态的不可确定性,将无法保障功能的正常使用,本项目将会停止新功能的开发,进入无限期停更状态。本项目基于开源项目[SPlayer](https://github.com/imsyy/SPlayer)进行开发,故产生的一切责任都与SPlayer以及他的开发者无关!
12 | >
13 | > Tips:该文档大部分内容引用了SPlayer仓库中的README.md
14 |
15 | 
16 |
17 |
18 | ## 👀 Demo
19 |
20 | - [TuneFree网页端](https://pt.sayqz.com)
21 | - [TuneFree for Windows](https://sayqz.lanpv.com/iN56k1o4ms3i)
22 | - [TuneFree for Android](https://sayqz.lanpv.com/ipru71xs00qj)
23 |
24 | ## 🎉 功能
25 |
26 | - 🎵 **支持播放网易云音乐所有的付费资源**
27 | - 🆕 软件内检查更新
28 | - 🤳 独立Android客户端
29 | - ✨ 支持扫码登录
30 | - 📱 支持手机号登录
31 | - 📅 自动进行每日签到及云贝签到
32 | - 🎨 封面主题色自适应
33 | - 📁 本地歌曲管理及分类(建议先使用 [音乐标签](https://www.cnblogs.com/vinlxc/p/11347744.html) 进行匹配后再使用)
34 | - ⬇️ 下载歌曲(最高支持 超清母带)
35 | - ➕ 新建歌单及歌单编辑
36 | - ❤️ 收藏 / 取消收藏歌单或歌手
37 | - 🎶 每日推荐歌曲
38 | - 📻 私人 FM
39 | - ☁️ 云盘音乐上传
40 | - 📂 云盘内歌曲播放
41 | - 🔄 云盘内歌曲纠正
42 | - 🗑️ 云盘歌曲删除
43 | - 📝 支持逐字歌词
44 | - 🔄 歌词滚动以及歌词翻译
45 | - 📹 MV 与视频播放
46 | - 🎶 音乐频谱显示
47 | - ⏭️ 音乐渐入渐出
48 | - 🔄 支持 PWA
49 | - 💬 支持评论区及评论点赞
50 | - 🌓 明暗模式自动 / 手动切换
51 | - 📱 移动端基础适配
52 |
53 |
54 | ## 🖼️ 预览
55 |
56 | > 开发中,仅供参考
57 |
58 |
59 | 主页面
60 |
61 | 
62 |
63 |
64 |
65 |
66 | 播放页面
67 |
68 | 
69 |
70 |
71 |
72 |
73 | 发现页面
74 |
75 | 
76 |
77 |
78 |
79 |
80 | 歌单页面
81 |
82 | 
83 |
84 |
85 |
86 |
87 | 评论页面
88 |
89 | 
90 |
91 |
92 |
93 |
94 | 本地音乐
95 |
96 | 
97 |
98 |
99 |
100 | ## 📦️ 获取
101 | ### TuneFree
102 | 通常情况下,可以在蓝奏云中获取到TuneFree各版本安装包
103 | - [蓝奏云](https://sayqz.lanpv.com/s/tunefree)
104 |
105 | ## ⚙️ 本地部署
106 |
107 | 1. 本地部署需要用到 `Node.js`。可前往 [Node.js 官网](https://nodejs.org/zh-cn/) 下载安装包,请下载最新稳定版
108 | 2. 安装 pnpm、electron-builder
109 |
110 | ```bash
111 | npm install pnpm electron-builder -g
112 | ```
113 |
114 | 3. 克隆仓库并拉取至本地,此处不再赘述
115 | 4. 使用 `pnpm install` 安装项目依赖(若安装过程中遇到网络错误,请使用国内镜像源替代,此处不再赘述)
116 | 5. 复制 `/.env.example` 文件并重命名为 `/.env` 并修改配置
117 | 6. 打包客户端,请依据你的系统类型来选择,打包成功后,会输出安装包或可执行文件在 `/dist` 目录中,可自行安装
118 |
119 | | 命令 | 系统类型 |
120 | | ------------------ | -------- |
121 | | `pnpm build:win` | Windows |
122 | | `pnpm build:linux` | Linux |
123 | | `pnpm build:mac` | MacOS |
124 |
125 | ## 📢 免责声明
126 |
127 | 再次声明,本项目基于开源项目SPlayer进行二次开发,故产生的一切责任都与SPlayer以及他的开发者无关!,仅供个人学习研究使用,禁止用于商业及非法用途。同时,本项目开发者承诺 没有任何破解网易音乐等相关情况,只是使用了我自己会员账号的Cookie进行了请求,请求使用的链接为网易云音乐官方的API
128 |
129 |
130 | ## 📜 开源许可
131 | - 原项目基于[GNU Affero General Public License (AGPL-3.0)](https://www.gnu.org/licenses/agpl-3.0.html)许可进行开源
132 | - 以下是来自原项目的开源许可
133 | 1. **修改和分发:** 任何对本项目的修改和分发都必须基于 AGPL-3.0 进行,源代码必须一并提供
134 | 2. **派生作品:** 任何派生作品必须同样采用 AGPL-3.0,并在适当的地方注明原始项目的许可证
135 | 3. **注明原作者:** 在任何修改、派生作品或其他分发中,必须在适当的位置明确注明原作者及其贡献
136 | 4. **免责声明:** 根据 AGPL-3.0,本项目不提供任何明示或暗示的担保。请详细阅读 [GNU Affero General Public License (AGPL-3.0)](https://www.gnu.org/licenses/agpl-3.0.html) 以了解完整的免责声明内容
137 | 5. **社区参与:** 欢迎社区的参与和贡献,我们鼓励开发者一同改进和维护本项目
138 | 6. **许可证链接:** 请阅读 [GNU Affero General Public License (AGPL-3.0)](https://www.gnu.org/licenses/agpl-3.0.html) 了解更多详情
139 | - TuneFree将遵守该开源许可,感谢SPlayer这一优秀的项目!更多详细说明请前往[SPlayer](https://github.com/imsyy/SPlayer)仓库了解!
140 |
141 |
142 | ## ⭐ Star History
143 |
144 | [](https://star-history.com/#GSQZ/TuneFree&Date)
145 |
--------------------------------------------------------------------------------
/auto-imports.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* prettier-ignore */
3 | // @ts-nocheck
4 | // noinspection JSUnusedGlobalSymbols
5 | // Generated by unplugin-auto-import
6 | export {}
7 | declare global {
8 | const EffectScope: typeof import('vue')['EffectScope']
9 | const computed: typeof import('vue')['computed']
10 | const createApp: typeof import('vue')['createApp']
11 | const customRef: typeof import('vue')['customRef']
12 | const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
13 | const defineComponent: typeof import('vue')['defineComponent']
14 | const effectScope: typeof import('vue')['effectScope']
15 | const getCurrentInstance: typeof import('vue')['getCurrentInstance']
16 | const getCurrentScope: typeof import('vue')['getCurrentScope']
17 | const h: typeof import('vue')['h']
18 | const inject: typeof import('vue')['inject']
19 | const isProxy: typeof import('vue')['isProxy']
20 | const isReactive: typeof import('vue')['isReactive']
21 | const isReadonly: typeof import('vue')['isReadonly']
22 | const isRef: typeof import('vue')['isRef']
23 | const markRaw: typeof import('vue')['markRaw']
24 | const nextTick: typeof import('vue')['nextTick']
25 | const onActivated: typeof import('vue')['onActivated']
26 | const onBeforeMount: typeof import('vue')['onBeforeMount']
27 | const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
28 | const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
29 | const onDeactivated: typeof import('vue')['onDeactivated']
30 | const onErrorCaptured: typeof import('vue')['onErrorCaptured']
31 | const onMounted: typeof import('vue')['onMounted']
32 | const onRenderTracked: typeof import('vue')['onRenderTracked']
33 | const onRenderTriggered: typeof import('vue')['onRenderTriggered']
34 | const onScopeDispose: typeof import('vue')['onScopeDispose']
35 | const onServerPrefetch: typeof import('vue')['onServerPrefetch']
36 | const onUnmounted: typeof import('vue')['onUnmounted']
37 | const onUpdated: typeof import('vue')['onUpdated']
38 | const provide: typeof import('vue')['provide']
39 | const reactive: typeof import('vue')['reactive']
40 | const readonly: typeof import('vue')['readonly']
41 | const ref: typeof import('vue')['ref']
42 | const resolveComponent: typeof import('vue')['resolveComponent']
43 | const shallowReactive: typeof import('vue')['shallowReactive']
44 | const shallowReadonly: typeof import('vue')['shallowReadonly']
45 | const shallowRef: typeof import('vue')['shallowRef']
46 | const toRaw: typeof import('vue')['toRaw']
47 | const toRef: typeof import('vue')['toRef']
48 | const toRefs: typeof import('vue')['toRefs']
49 | const toValue: typeof import('vue')['toValue']
50 | const triggerRef: typeof import('vue')['triggerRef']
51 | const unref: typeof import('vue')['unref']
52 | const useAttrs: typeof import('vue')['useAttrs']
53 | const useCssModule: typeof import('vue')['useCssModule']
54 | const useCssVars: typeof import('vue')['useCssVars']
55 | const useDialog: typeof import('naive-ui')['useDialog']
56 | const useLoadingBar: typeof import('naive-ui')['useLoadingBar']
57 | const useMessage: typeof import('naive-ui')['useMessage']
58 | const useNotification: typeof import('naive-ui')['useNotification']
59 | const useSlots: typeof import('vue')['useSlots']
60 | const watch: typeof import('vue')['watch']
61 | const watchEffect: typeof import('vue')['watchEffect']
62 | const watchPostEffect: typeof import('vue')['watchPostEffect']
63 | const watchSyncEffect: typeof import('vue')['watchSyncEffect']
64 | }
65 | // for type re-export
66 | declare global {
67 | // @ts-ignore
68 | export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
69 | import('vue')
70 | }
71 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | SPlayer:
3 | build:
4 | context: .
5 | image: splayer
6 | container_name: SPlayer
7 | volumes:
8 | - /etc/localtime:/etc/localtime:ro
9 | - /etc/timezone:/etc/timezone:ro
10 | ports:
11 | - 7899:7899
12 | restart: always
13 |
--------------------------------------------------------------------------------
/electron-builder.yml:
--------------------------------------------------------------------------------
1 | # 应用程序的唯一标识符
2 | appId: com.sayqz.tunefree
3 | # 应用程序的产品名称
4 | productName: TuneFree
5 | copyright: Copyright © Sayqz 2024
6 | # 构建资源所在的目录
7 | directories:
8 | buildResources: build
9 | # 包含在最终应用程序构建中的文件列表,这里使用通配符 ! 表示排除不需要的文件
10 | files:
11 | - "!**/.vscode/*"
12 | - "!src/*"
13 | - "!electron.vite.config.{js,ts,mjs,cjs}"
14 | - "!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}"
15 | - "!{.env,.env.*,.npmrc,pnpm-lock.yaml}"
16 | # 哪些文件将不会被压缩,而是解压到构建目录
17 | asarUnpack:
18 | - public/**
19 | # Windows 平台配置
20 | win:
21 | # 可执行文件名
22 | executableName: TuneFree
23 | # 应用程序的图标文件路径
24 | icon: public/images/icons/favicon-512x512.png
25 | # 构建类型
26 | target: nsis
27 | # NSIS 安装器配置
28 | nsis:
29 | # 一键式安装程序还是辅助安装程序
30 | oneClick: false
31 | # 安装程序的生成名称
32 | artifactName: ${productName}-${version}-${arch}-setup.${ext}
33 | # 创建的桌面快捷方式名称
34 | shortcutName: ${productName}
35 | # 卸载时显示的名称
36 | uninstallDisplayName: ${productName}
37 | # 创建桌面图标
38 | createDesktopShortcut: always
39 | # 是否允许 UAC 提升权限
40 | allowElevation: true
41 | # 是否允许用户更改安装目录
42 | allowToChangeInstallationDirectory: true
43 | # 安装包图标
44 | installerIcon: public/images/icons/favicon.ico
45 | # 卸载命令图标
46 | uninstallerIcon: public/images/icons/favicon.ico
47 | # macOS 平台配置
48 | mac:
49 | # 可执行文件名
50 | executableName: TuneFree
51 | # 应用程序的图标文件路径
52 | icon: public/images/icons/favicon-512x512.png
53 | # 权限继承的文件路径
54 | entitlementsInherit: build/entitlements.mac.plist
55 | # 扩展信息,如权限描述
56 | extendInfo:
57 | - NSCameraUsageDescription: Application requests access to the device's camera.
58 | - NSMicrophoneUsageDescription: Application requests access to the device's microphone.
59 | - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
60 | - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
61 | # 是否启用应用程序的 Notarization(苹果的安全审核)
62 | notarize: false
63 | darkModeSupport: true
64 | category: public.app-category.music
65 | # macOS 平台的 DMG 配置
66 | dmg:
67 | # DMG 文件的生成名称
68 | artifactName: ${productName}-${version}.${ext}
69 | # Linux 平台配置
70 | linux:
71 | # 可执行文件名
72 | executableName: TuneFree
73 | # 应用程序的图标文件路径
74 | icon: public/images/icons/favicon-512x512.png
75 | # 构建类型
76 | target:
77 | - AppImage
78 | - deb
79 | - rpm
80 | - tar.gz
81 | # 维护者信息
82 | maintainer: imsyy.top
83 | # 应用程序类别
84 | category: Audio;Music
85 | artifactName: ${productName}-${version}-${arch}.${ext}
86 | # 是否在构建之前重新编译原生模块
87 | npmRebuild: false
88 |
--------------------------------------------------------------------------------
/electron/index.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/electron/index.html
--------------------------------------------------------------------------------
/electron/main/startMainServer.js:
--------------------------------------------------------------------------------
1 | import { join } from "path";
2 | import express from "express";
3 | import expressProxy from "express-http-proxy";
4 |
5 | /**
6 | * 启动主服务器
7 | * @returns {import('http').Server} HTTP 服务器实例
8 | */
9 | export const startMainServer = async () => {
10 | const { MAIN_VITE_MAIN_PORT, MAIN_VITE_SERVER_HOST, MAIN_VITE_SERVER_PORT } = import.meta.env;
11 | const port = MAIN_VITE_MAIN_PORT ?? 7899;
12 | const apiHost = `http://${MAIN_VITE_SERVER_HOST}:${MAIN_VITE_SERVER_PORT}`;
13 | const expressApp = express();
14 | // 代理
15 | expressApp.use("/", express.static(join(__dirname, "../renderer/")));
16 | expressApp.use("/api", expressProxy(apiHost));
17 | // 启动 Express 应用服务器,并监听指定端口
18 | return expressApp.listen(port, "127.0.0.1");
19 | };
20 |
--------------------------------------------------------------------------------
/electron/main/startNcmServer.js:
--------------------------------------------------------------------------------
1 | import netEaseApi from "NeteaseCloudMusicApi";
2 |
3 | /**
4 | * 启动网易云音乐 API 服务器
5 | *
6 | * @async
7 | * @param {Object} options - 服务器配置
8 | * @param {number} [options.port=11451] - 服务器端口
9 | * @param {string} [options.host="127.0.0.1"] - 服务器主机地址
10 | * @returns {Promise} 返回一个 Promise,在 API 服务器成功启动后 resolve
11 | */
12 | export const startNcmServer = async (
13 | options = {
14 | port: 11451,
15 | host: "127.0.0.1",
16 | },
17 | ) => {
18 | console.log(options);
19 | return await netEaseApi.serveNcmApi(options);
20 | };
21 |
--------------------------------------------------------------------------------
/electron/main/utils/checkUpdates.js:
--------------------------------------------------------------------------------
1 | import { is } from "@electron-toolkit/utils";
2 |
3 | // 导出一个空函数,或者根本不导出
4 | export const configureAutoUpdater = () => {
5 | if (is.dev) return false;
6 |
7 | // 自动更新代码已被移除
8 | console.log("自动更新已被禁用");
9 |
10 | // 这里可以包含应用程序启动时需要执行的其他初始化代码
11 | // ...
12 |
13 | return true;
14 | };
15 |
--------------------------------------------------------------------------------
/electron/main/utils/createGlobalShortcut.js:
--------------------------------------------------------------------------------
1 | import { globalShortcut } from "electron";
2 |
3 | /**
4 | * 注册全局快捷键
5 | * @param {BrowserWindow} win - 程序窗口
6 | */
7 | const createGlobalShortcut = (win) => {
8 |
9 | //全局播放控制
10 | globalShortcut.register("CmdOrCtrl+Alt+P", () => {
11 | win.webContents.send("playOrPause");
12 | });
13 |
14 | //全局下一首
15 | globalShortcut.register("CmdOrCtrl+Alt+Right", () => {
16 | win.webContents.send("playNextOrPrev", "next");
17 | });
18 |
19 | //全局上一首
20 | globalShortcut.register("CmdOrCtrl+Alt+Left", () => {
21 | win.webContents.send("playNextOrPrev", "prev");
22 | });
23 |
24 | // 刷新程序
25 | globalShortcut.register("CmdOrCtrl+Shift+R", () => {
26 | if (win && win.isFocused()) win?.reload();
27 | });
28 |
29 | // 打开开发者工具
30 | globalShortcut.register("CmdOrCtrl+Shift+I", () => {
31 | if (win && win.isFocused()) {
32 | win?.webContents.openDevTools({
33 | mode: "right",
34 | activate: true,
35 | });
36 | }
37 | });
38 | };
39 |
40 | export default createGlobalShortcut;
41 |
--------------------------------------------------------------------------------
/electron/main/utils/createSystemTray.js:
--------------------------------------------------------------------------------
1 | import { Tray, Menu, app, ipcMain, nativeImage, nativeTheme } from "electron";
2 | import packageJson from "/package.json";
3 | import { join } from "path";
4 |
5 | // 当前歌曲数据
6 | let playSongName = "当前暂无播放歌曲";
7 | let playSongState = false;
8 |
9 | /**
10 | * 创建系统托盘
11 | * @param {BrowserWindow} win - 程序窗口
12 | */
13 | const createSystemTray = (win) => {
14 | // 系统托盘
15 | const mainTray = new Tray(
16 | nativeImage
17 | .createFromPath(
18 | join(
19 | __dirname,
20 | process.platform === "win32"
21 | ? "../../public/images/icons/favicon.ico"
22 | : "../../public/images/icons/favicon-32x32.png",
23 | ),
24 | )
25 | .resize({
26 | height: 32,
27 | width: 32,
28 | }),
29 | );
30 | // 应用内菜单
31 | Menu.setApplicationMenu(createTrayMenu(win));
32 | // 默认名称
33 | win.setTitle(app.getName());
34 | mainTray.setTitle(app.getName());
35 | mainTray.setToolTip(app.getName());
36 | // 左键事件
37 | mainTray.on("click", () => win.show());
38 | // 托盘菜单
39 | mainTray.setContextMenu(createTrayMenu(win));
40 | // 系统主题改变
41 | nativeTheme.on("updated", () => {
42 | mainTray.setContextMenu(createTrayMenu(win));
43 | });
44 | // 播放歌曲改变
45 | ipcMain.on("songNameChange", (_, val) => {
46 | playSongName = val;
47 | win.setTitle(val);
48 | mainTray.setTitle(val);
49 | mainTray.setToolTip(val);
50 | mainTray.setContextMenu(createTrayMenu(win));
51 | });
52 | // 播放状态改变
53 | ipcMain.on("songStateChange", (_, val) => {
54 | playSongState = val;
55 | mainTray.setContextMenu(createTrayMenu(win));
56 | });
57 | };
58 |
59 | // 生成图标
60 | const createIcon = (name) => {
61 | // 系统是否为暗色
62 | const isDarkMode = nativeTheme.shouldUseDarkColors;
63 | // 返回图标
64 | return nativeImage
65 | .createFromPath(
66 | isDarkMode
67 | ? join(__dirname, `../../public/images/icons/${name}-dark.png`)
68 | : join(__dirname, `../../public/images/icons/${name}-light.png`),
69 | )
70 | .resize({ width: 16, height: 16 });
71 | };
72 | // 生成右键菜单
73 | const createTrayMenu = (win) => {
74 | // 返回菜单
75 | return Menu.buildFromTemplate([
76 | {
77 | label: `TuneFree ${packageJson.version}`,
78 | icon: createIcon("logo"),
79 | accelerator: "Esc",
80 | click() {
81 | win.webContents.send("closePlayer");
82 | },
83 | },
84 | {
85 | label: playSongName,
86 | icon: createIcon("open"),
87 | click() {
88 | win.show();
89 | win.focus();
90 | win.webContents.send("showPlayer");
91 | },
92 | },
93 | {
94 | type: "separator",
95 | },
96 | {
97 | label: "上一曲",
98 | icon: createIcon("prev"),
99 | accelerator: "CmdOrCtrl+Left",
100 | click: () => {
101 | win.webContents.send("playNextOrPrev", "prev");
102 | },
103 | },
104 | {
105 | label: playSongState ? "暂停" : "播放",
106 | icon: createIcon(playSongState ? "pause" : "play"),
107 | accelerator: "Space",
108 | click: () => {
109 | win.webContents.send("playOrPause");
110 | },
111 | },
112 | {
113 | label: "下一曲",
114 | icon: createIcon("next"),
115 | accelerator: "CmdOrCtrl+Right",
116 | click: () => {
117 | win.webContents.send("playNextOrPrev", "next");
118 | },
119 | },
120 | {
121 | type: "separator",
122 | },
123 | {
124 | label: "全局设置",
125 | icon: createIcon("setting"),
126 | click: () => {
127 | win.show();
128 | win.focus();
129 | win.webContents.send("open-setting");
130 | },
131 | },
132 | {
133 | type: "separator",
134 | },
135 | {
136 | label: "退出",
137 | icon: createIcon("power"),
138 | click: () => {
139 | win.close();
140 | app.isQuiting = true;
141 | app.quit();
142 | },
143 | },
144 | ]);
145 | };
146 |
147 | export default createSystemTray;
148 |
--------------------------------------------------------------------------------
/electron/preload/index.mjs:
--------------------------------------------------------------------------------
1 | import { contextBridge } from "electron";
2 | import { electronAPI } from "@electron-toolkit/preload";
3 |
4 | // 如果启用了上下文隔离,使用 `contextBridge` 将 Electron API 暴露给渲染进程
5 | if (process.contextIsolated) {
6 | try {
7 | // 使用 contextBridge 暴露 electronAPI 到渲染进程的全局对象中
8 | contextBridge.exposeInMainWorld("electron", electronAPI);
9 | } catch (error) {
10 | console.error(error);
11 | }
12 | } else {
13 | // 如果上下文隔离未启用,将 electronAPI 添加到 DOM 全局对象
14 | window.electron = electronAPI;
15 | }
16 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | %RENDERER_VITE_SITE_TITLE%
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | gzip on;
3 | listen 7899;
4 | listen [::]:7899;
5 | server_name localhost;
6 |
7 | location / {
8 | root /usr/share/nginx/html;
9 | index index.html;
10 | try_files $uri $uri/ /index.html;
11 | }
12 |
13 | location @rewrites {
14 | rewrite ^(.*)$ /index.html last;
15 | }
16 |
17 | location /api/ {
18 | proxy_buffers 16 32k;
19 | proxy_buffer_size 128k;
20 | proxy_busy_buffers_size 128k;
21 | proxy_set_header Host $host;
22 | proxy_set_header X-Real-IP $remote_addr;
23 | proxy_set_header X-Forwarded-For $remote_addr;
24 | proxy_set_header X-Forwarded-Host $remote_addr;
25 | proxy_set_header X-NginX-Proxy true;
26 | proxy_pass http://localhost:3000/;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tunefree",
3 | "version": "1.2.0",
4 | "description": "TuneFree - 音遇自由",
5 | "main": "./out/main/index.js",
6 | "author": "是青旨啊",
7 | "home": "https://tunefree.fun",
8 | "github": "https://github.com/GSQZ",
9 | "license": "AGPL-3.0",
10 | "license-file": "LICENSE",
11 | "engines": {
12 | "node": ">=18.16.0",
13 | "npm": ">=9.6.7",
14 | "pnpm": ">=8.14.0"
15 | },
16 | "type": "module",
17 | "scripts": {
18 | "format": "prettier --write .",
19 | "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix",
20 | "start": "electron-vite preview",
21 | "dev": "electron-vite dev --watch",
22 | "build": "electron-vite build",
23 | "postinstall": "electron-builder install-app-deps",
24 | "build:win": "npm run build && electron-builder --win --config --publish never",
25 | "build:win-arm64": "npm run build && electron-builder --win --arm64 --config --publish never",
26 | "build:mac": "npm run build && electron-builder --mac --config --publish never",
27 | "build:linux": "npm run build && electron-builder --linux --config --publish never",
28 | "build:linux-arm64": "npm run build && electron-builder --linux --arm64 --config --publish never"
29 | },
30 | "dependencies": {
31 | "@electron-toolkit/preload": "^3.0.0",
32 | "@electron-toolkit/utils": "^3.0.0",
33 | "@material/material-color-utilities": "^0.2.7",
34 | "NeteaseCloudMusicApi": "^4.23.1",
35 | "axios": "^1.7.9",
36 | "form-data": "^4.0.1",
37 | "colorthief": "^2.4.0",
38 | "electron-dl": "^3.5.1",
39 | "electron-localshortcut": "^3.2.1",
40 | "electron-store": "^8.1.0",
41 | "electron-updater": "^6.1.7",
42 | "express": "^4.18.2",
43 | "express-http-proxy": "^2.0.0",
44 | "howler": "^2.2.4",
45 | "js-cookie": "^3.0.5",
46 | "localforage": "^1.10.0",
47 | "music-metadata": "7.14.0",
48 | "node-taglib-sharp": "^5.2.3",
49 | "pinia": "^2.1.7",
50 | "pinia-plugin-persistedstate": "^3.2.1",
51 | "plyr": "^3.7.8",
52 | "screenfull": "^6.0.2",
53 | "vue-router": "^4.2.5",
54 | "vue-slider-component": "4.1.0-beta.7",
55 | "jszip": "3.10.1"
56 | },
57 | "devDependencies": {
58 | "@electron-toolkit/eslint-config": "^1.0.2",
59 | "@rushstack/eslint-patch": "^1.7.0",
60 | "@vitejs/plugin-vue": "^5.0.3",
61 | "@vue/eslint-config-prettier": "^9.0.0",
62 | "ajv": "^8.12.0",
63 | "electron": "^28.3.3",
64 | "electron-builder": "^24.9.1",
65 | "electron-log": "^5.0.3",
66 | "electron-vite": "^2.0.0",
67 | "eslint": "^8.56.0",
68 | "eslint-plugin-vue": "^9.20.1",
69 | "naive-ui": "^2.37.3",
70 | "prettier": "^3.2.4",
71 | "sass": "^1.70.0",
72 | "terser": "^5.27.0",
73 | "unplugin-auto-import": "^0.17.3",
74 | "unplugin-vue-components": "^0.26.0",
75 | "vite": "^5.0.12",
76 | "vite-plugin-compression": "^0.5.1",
77 | "vite-plugin-pwa": "^0.17.4",
78 | "vue": "^3.5.11",
79 | "jszip": "3.10.1"
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/favicon.ico
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/favicon.png
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.a.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.a.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.a0.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.a0.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.a1.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.a1.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.aa.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.aa.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.ab.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.ab.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.ac.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.ac.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.ad.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.ad.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.ae.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.ae.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.af.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.af.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.ag.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.ag.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.ah.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.ah.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.ai.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.ai.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.aj.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.aj.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.ak.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.ak.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.al.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.al.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.am.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.am.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.an.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.an.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.ao.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.ao.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.ap.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.ap.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.aq.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.aq.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.ar.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.ar.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.as.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.as.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.at.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.at.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.au.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.au.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.av.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.av.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.aw.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.aw.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.ax.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.ax.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.ay.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.ay.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.az.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.az.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.b.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.b.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.c.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.c.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.d.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.d.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.e.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.e.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.f.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.f.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.g.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.g.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.h.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.h.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.i.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.i.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.j.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.j.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.k.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.k.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.l.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.l.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.m.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.m.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.n.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.n.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.o.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.o.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.p.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.p.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.q.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.q.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.r.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.r.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.s.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.s.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.t.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.t.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.u.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.u.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.v.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.v.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.w.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.w.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.x.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.x.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.y.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.y.woff2
--------------------------------------------------------------------------------
/public/font/font_files/HarmonyOS_Regular.z.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/font/font_files/HarmonyOS_Regular.z.woff2
--------------------------------------------------------------------------------
/public/images/icons/afdian.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/afdian.jpg
--------------------------------------------------------------------------------
/public/images/icons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/images/icons/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/images/icons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/images/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/public/images/icons/favicon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/favicon-192x192.png
--------------------------------------------------------------------------------
/public/images/icons/favicon-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/favicon-256x256.png
--------------------------------------------------------------------------------
/public/images/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/public/images/icons/favicon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/favicon-512x512.png
--------------------------------------------------------------------------------
/public/images/icons/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/favicon-96x96.png
--------------------------------------------------------------------------------
/public/images/icons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/favicon.ico
--------------------------------------------------------------------------------
/public/images/icons/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/favicon.png
--------------------------------------------------------------------------------
/public/images/icons/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/public/images/icons/logo-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/logo-dark.png
--------------------------------------------------------------------------------
/public/images/icons/logo-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/logo-light.png
--------------------------------------------------------------------------------
/public/images/icons/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/mstile-150x150.png
--------------------------------------------------------------------------------
/public/images/icons/next-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/next-dark.png
--------------------------------------------------------------------------------
/public/images/icons/next-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/next-light.png
--------------------------------------------------------------------------------
/public/images/icons/open-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/open-dark.png
--------------------------------------------------------------------------------
/public/images/icons/open-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/open-light.png
--------------------------------------------------------------------------------
/public/images/icons/pause-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/pause-dark.png
--------------------------------------------------------------------------------
/public/images/icons/pause-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/pause-light.png
--------------------------------------------------------------------------------
/public/images/icons/play-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/play-dark.png
--------------------------------------------------------------------------------
/public/images/icons/play-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/play-light.png
--------------------------------------------------------------------------------
/public/images/icons/power-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/power-dark.png
--------------------------------------------------------------------------------
/public/images/icons/power-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/power-light.png
--------------------------------------------------------------------------------
/public/images/icons/prev-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/prev-dark.png
--------------------------------------------------------------------------------
/public/images/icons/prev-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/prev-light.png
--------------------------------------------------------------------------------
/public/images/icons/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/public/images/icons/setting-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/setting-dark.png
--------------------------------------------------------------------------------
/public/images/icons/setting-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/icons/setting-light.png
--------------------------------------------------------------------------------
/public/images/pic/album.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/album.jpg
--------------------------------------------------------------------------------
/public/images/pic/album.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/album.png
--------------------------------------------------------------------------------
/public/images/pic/artist.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/artist.jpg
--------------------------------------------------------------------------------
/public/images/pic/avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/avatar.jpg
--------------------------------------------------------------------------------
/public/images/pic/cover-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/cover-2.jpg
--------------------------------------------------------------------------------
/public/images/pic/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/cover.jpg
--------------------------------------------------------------------------------
/public/images/pic/default.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/default.jpg
--------------------------------------------------------------------------------
/public/images/pic/hot-list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/hot-list.png
--------------------------------------------------------------------------------
/public/images/pic/like.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/like.jpg
--------------------------------------------------------------------------------
/public/images/pic/pl-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/pl-1.jpg
--------------------------------------------------------------------------------
/public/images/pic/pl-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/pl-2.jpg
--------------------------------------------------------------------------------
/public/images/pic/pl-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/pl-3.jpg
--------------------------------------------------------------------------------
/public/images/pic/pl-4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/pl-4.jpg
--------------------------------------------------------------------------------
/public/images/pic/pointer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/pointer.png
--------------------------------------------------------------------------------
/public/images/pic/radar-private.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/radar-private.jpg
--------------------------------------------------------------------------------
/public/images/pic/radar-treasure.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/radar-treasure.jpg
--------------------------------------------------------------------------------
/public/images/pic/record.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/record.png
--------------------------------------------------------------------------------
/public/images/pic/song.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/song.jpg
--------------------------------------------------------------------------------
/public/images/pic/video.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/video.png
--------------------------------------------------------------------------------
/public/images/pic/vip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/public/images/pic/vip.png
--------------------------------------------------------------------------------
/screenshots/TuneFree - 主页面.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/screenshots/TuneFree - 主页面.jpg
--------------------------------------------------------------------------------
/screenshots/TuneFree - 发现页面.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/screenshots/TuneFree - 发现页面.jpg
--------------------------------------------------------------------------------
/screenshots/TuneFree - 播放页面.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/screenshots/TuneFree - 播放页面.jpg
--------------------------------------------------------------------------------
/screenshots/TuneFree - 本地音乐.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/screenshots/TuneFree - 本地音乐.jpg
--------------------------------------------------------------------------------
/screenshots/TuneFree - 歌单页面.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/screenshots/TuneFree - 歌单页面.jpg
--------------------------------------------------------------------------------
/screenshots/TuneFree - 评论页面.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/screenshots/TuneFree - 评论页面.jpg
--------------------------------------------------------------------------------
/screenshots/TuneFree-播放.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/screenshots/TuneFree-播放.png
--------------------------------------------------------------------------------
/screenshots/TuneFree-首页.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/screenshots/TuneFree-首页.png
--------------------------------------------------------------------------------
/screenshots/TuneFree.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/screenshots/TuneFree.jpg
--------------------------------------------------------------------------------
/screenshots/build.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GSQZ/TuneFree/6f1feb056098943fd26c65a2f1e5d3b03615916f/screenshots/build.png
--------------------------------------------------------------------------------
/src/api/album.js:
--------------------------------------------------------------------------------
1 | import axios from "@/utils/request";
2 |
3 | /**
4 | * 专辑部分
5 | */
6 |
7 | /**
8 | * 获取专辑内容
9 | * @param {number} id - 专辑id
10 | */
11 | export const getAlbumDetail = (id) => {
12 | return axios({
13 | method: "GET",
14 | url: "/album",
15 | params: {
16 | id,
17 | timestamp: new Date().getTime(),
18 | },
19 | });
20 | };
21 |
22 | /**
23 | * 收藏/取消收藏专辑
24 | * @param {number} t - 操作类型,1为收藏,2为取消收藏
25 | * @param {number} id - 专辑id
26 | */
27 | export const likeAlbum = (t, id) => {
28 | return axios({
29 | method: "GET",
30 | url: "/album/sub",
31 | params: {
32 | t,
33 | id,
34 | timestamp: new Date().getTime(),
35 | },
36 | });
37 | };
38 |
--------------------------------------------------------------------------------
/src/api/artist.js:
--------------------------------------------------------------------------------
1 | import axios from "@/utils/request";
2 |
3 | /**
4 | * 歌手部分
5 | */
6 |
7 | /**
8 | * 歌手分类列表
9 | * @param {number} type - 歌手类型(-1:全部 1:男歌手 2:女歌手 3:乐队)
10 | * @param {number} area - 歌手区域(-1:全部 7:华语 96:欧美 8:日本 16:韩国 0:其他)
11 | * @param {number} [limit=50] - 返回数量,默认 50
12 | * @param {number} [offset=0] - 偏移数量,默认 0
13 | * @param {number} initial - 首字母索引查找参数
14 | */
15 | export const getArtistList = (type = -1, area = -1, offset = 0, initial = -1, limit = 50) => {
16 | return axios({
17 | method: "GET",
18 | url: "/artist/list",
19 | params: {
20 | type,
21 | area,
22 | offset,
23 | initial,
24 | limit,
25 | },
26 | });
27 | };
28 |
29 | /**
30 | * 获取歌手详情
31 | * @param {number} id - 歌手id
32 | */
33 | export const getArtistDetail = (id) => {
34 | return axios({
35 | method: "GET",
36 | url: "/artist/detail",
37 | params: {
38 | id,
39 | },
40 | });
41 | };
42 |
43 | /**
44 | * 获取歌手部分信息和热门歌曲
45 | * @param {number} id - 歌手id
46 | */
47 | export const getArtistSongs = (id) => {
48 | return axios({
49 | method: "GET",
50 | url: "/artists",
51 | params: {
52 | id,
53 | timestamp: new Date().getTime(),
54 | },
55 | });
56 | };
57 |
58 | /**
59 | * 获取歌手全部歌曲
60 | * @param {number} id - 歌手id
61 | * @param {number} [limit=50] - 返回数量,默认50
62 | * @param {number} [offset=0] - 偏移数量,默认0
63 | * @param {string} order - hot: 热门, time: 时间
64 | */
65 | export const getArtistAllSongs = (id, limit = 50, offset = 0, order = "hot") => {
66 | return axios({
67 | method: "GET",
68 | url: "/artist/songs",
69 | params: {
70 | id,
71 | limit,
72 | offset,
73 | order,
74 | timestamp: new Date().getTime(),
75 | },
76 | });
77 | };
78 |
79 | /**
80 | * 获取歌手专辑
81 | * @param {number} id - 歌手id
82 | * @param {number} [limit=50] - 返回数量,默认50
83 | * @param {number} [offset=0] - 偏移数量,默认0
84 | */
85 | export const getArtistAblums = (id, limit = 50, offset = 0) => {
86 | return axios({
87 | method: "GET",
88 | url: "/artist/album",
89 | params: {
90 | id,
91 | limit,
92 | offset,
93 | },
94 | });
95 | };
96 |
97 | /**
98 | * 获取歌手视频
99 | * @param {number} id - 歌手id
100 | * @param {number} [limit=50] - 返回数量,默认50
101 | * @param {number} [offset=0] - 偏移数量,默认0
102 | */
103 | export const getArtistVideos = (id, limit = 50, offset = 0) => {
104 | return axios({
105 | method: "GET",
106 | url: "/artist/mv",
107 | params: {
108 | id,
109 | limit,
110 | offset,
111 | },
112 | });
113 | };
114 |
115 | /**
116 | * 收藏/取消收藏歌手
117 | * @param {number} t - 操作类型,1 为收藏,其他为取消收藏
118 | * @param {number} id - 歌手id
119 | */
120 | export const likeArtist = (t, id) => {
121 | return axios({
122 | method: "GET",
123 | url: "/artist/sub",
124 | params: {
125 | t,
126 | id,
127 | timestamp: new Date().getTime(),
128 | },
129 | });
130 | };
131 |
--------------------------------------------------------------------------------
/src/api/cloud.js:
--------------------------------------------------------------------------------
1 | import axios from "@/utils/request";
2 |
3 | /**
4 | * 云盘部分
5 | */
6 |
7 | /**
8 | * 获取云盘数据
9 | * @param {number} [limit=50] - 返回数量,默认 50
10 | * @param {number} [offset=0] - 偏移数量,默认 0
11 | */
12 | export const getUserCloud = (limit = 50, offset = 0) => {
13 | return axios({
14 | method: "GET",
15 | url: "/user/cloud",
16 | params: {
17 | limit,
18 | offset,
19 | timestamp: new Date().getTime(),
20 | },
21 | });
22 | };
23 |
24 | /**
25 | * 用户云盘歌曲删除
26 | * @param {string} id - 歌曲的id
27 | */
28 | export const setCloudDel = (id) => {
29 | return axios({
30 | method: "GET",
31 | url: "/user/cloud/del",
32 | params: {
33 | id,
34 | timestamp: new Date().getTime(),
35 | },
36 | });
37 | };
38 |
39 | /**
40 | * 云盘歌曲信息匹配纠正
41 | * @param {string} uid - 用户 id
42 | * @param {string} sid - 原歌曲 id
43 | * @param {string} asid - 要匹配的歌曲 id
44 | */
45 | export const setCloudMatch = (uid, sid, asid) => {
46 | return axios({
47 | method: "GET",
48 | url: "/cloud/match",
49 | params: {
50 | uid,
51 | sid,
52 | asid,
53 | timestamp: new Date().getTime(),
54 | },
55 | });
56 | };
57 |
58 | /**
59 | * 用户云盘上传
60 | * @param {File} file - 要上传的文件
61 | */
62 | export const upCloudSong = (file, onUploadProgress) => {
63 | const formData = new FormData();
64 | formData.append("songFile", file);
65 | return axios({
66 | url: "/cloud",
67 | method: "POST",
68 | hiddenBar: true,
69 | params: {
70 | timestamp: new Date().getTime(),
71 | },
72 | data: formData,
73 | headers: {
74 | "Content-Type": "multipart/form-data",
75 | },
76 | timeout: 200000,
77 | onUploadProgress,
78 | });
79 | };
80 |
--------------------------------------------------------------------------------
/src/api/comment.js:
--------------------------------------------------------------------------------
1 | import axios from "@/utils/request";
2 |
3 | /**
4 | * 评论部分
5 | */
6 |
7 | /**
8 | * 获取评论
9 | * @param {number} id - 对应资源的 id
10 | * @param {string} type - 对应资源的类型,0: 歌曲, 1: mv, 2: 歌单, 3: 专辑, 4: 电台节目, 5: 视频, 6: 动态, 7: 电台
11 | * @param {number} pageNo - 分页参数,第 N 页, 默认 1
12 | * @param {number} pageSize - 分页参数,每页多少条数据, 默认 20
13 | * @param {number} sortType - 排序方式, 1:按推荐排序, 2:按热度排序, 3:按时间排序
14 | * @param {number} cursor - 当 sortType 为 3 时且页数不是第一页时需传入, 值为上一条数据的 time
15 | */
16 | export const getComment = (id, type = 0, pageNo = 1, sortType = 3, pageSize = 20, cursor) => {
17 | return axios({
18 | method: "GET",
19 | url: "/comment/new",
20 | params: {
21 | id,
22 | type,
23 | pageNo,
24 | pageSize,
25 | sortType,
26 | cursor,
27 | timestamp: new Date().getTime(),
28 | },
29 | });
30 | };
31 |
32 | /**
33 | * 获取热门评论
34 | * @param {number} id - 对应资源的 id
35 | * @param {string} type - 对应资源的类型,0: 歌曲, 1: mv, 2: 歌单, 3: 专辑, 4: 电台节目, 5: 视频, 6: 动态, 7: 电台
36 | * @param {number} limit - 取出评论数量 , 默认为 20
37 | * @param {number} offset - 偏移数量 , 用于分页 , 如 :( 评论页数 -1)*20, 其中 20 为 limit 的值
38 | * @param {string} before - 分页参数,取上一页最后一项的 time 获取下一页数据(获取超过 5000 条评论的时候需要用到)
39 | */
40 | export const getHotComment = (id, type = 0, limit = 20, offset = 0, before) => {
41 | return axios({
42 | method: "GET",
43 | url: "/comment/hot",
44 | params: {
45 | id,
46 | type,
47 | limit,
48 | offset,
49 | before,
50 | timestamp: new Date().getTime(),
51 | },
52 | });
53 | };
54 |
55 | /**
56 | * 发送 / 删除评论
57 | * @param {number} id - 对应资源的id
58 | * @param {number} commentId - 回复的评论 id(回复评论时必填)
59 | * @param {string} content - 要发送的内容
60 | * @param {number} t - 是否点赞,0为删除,1为发送,2为回复
61 | * @param {number} [type=0] - 对应资源的类型,默认为0(歌曲)
62 | */
63 | export const sendComment = (id, commentId = null, content, t, type = 0) => {
64 | return axios({
65 | method: "GET",
66 | url: "/comment",
67 | params: {
68 | id,
69 | commentId,
70 | content,
71 | t,
72 | type,
73 | timestamp: new Date().getTime(),
74 | },
75 | });
76 | };
77 |
78 | /**
79 | * 评论点赞
80 | * @param {number} id - 对应资源的 id
81 | * @param {number} cid - 评论的 id
82 | * @param {number} t - 操作, 1 为点赞, 其他为取消点赞
83 | * @param {number} type - 资源类型 / 0: 歌曲 / 1: mv / 2: 歌单 / 3: 专辑 / 4: 电台节目 / 5: 视频 / 6: 动态 / 7: 电台
84 | */
85 | export const likeComment = (id, cid, t, type = 0) => {
86 | return axios({
87 | method: "GET",
88 | url: "/comment/like",
89 | params: {
90 | id,
91 | cid,
92 | t,
93 | type,
94 | timestamp: new Date().getTime(),
95 | },
96 | });
97 | };
98 |
--------------------------------------------------------------------------------
/src/api/dj.js:
--------------------------------------------------------------------------------
1 | import axios from "@/utils/request";
2 |
3 | /**
4 | * 电台部分
5 | */
6 |
7 | /**
8 | * 获取电台 - 分类
9 | */
10 | export const getDjCatelist = () => {
11 | return axios({
12 | method: "GET",
13 | url: "/dj/catelist",
14 | });
15 | };
16 |
17 | /**
18 | * 获取电台 - 推荐
19 | */
20 | export const getDjRecommend = () => {
21 | return axios({
22 | method: "GET",
23 | url: "/dj/recommend",
24 | });
25 | };
26 |
27 | /**
28 | * 电台个性推荐
29 | */
30 | export const getDjPersonalRec = () => {
31 | return axios({
32 | method: "GET",
33 | url: "/dj/personalize/recommend",
34 | });
35 | };
36 |
37 | /**
38 | * 获取电台 - 推荐类型
39 | */
40 | export const getDjCategoryRec = () => {
41 | return axios({
42 | method: "GET",
43 | url: "/dj/category/recommend",
44 | });
45 | };
46 |
47 | /**
48 | * 私人 DJ
49 | */
50 | export const getPrivateDj = () => {
51 | return axios({
52 | method: "GET",
53 | url: "/aidj/content/rcmd",
54 | });
55 | };
56 |
57 | /**
58 | * 电台 - 类别热门电台
59 | * @param {string} cateId - 类别 id
60 | * @param {number} [limit=50] - 返回数量,默认 50
61 | * @param {number} [offset=0] - 偏移数量,默认 0
62 | */
63 | export const getRadioHot = (cateId, limit = 50, offset = 0) => {
64 | return axios({
65 | method: "GET",
66 | url: "/dj/radio/hot",
67 | params: {
68 | cateId,
69 | limit,
70 | offset,
71 | },
72 | });
73 | };
74 |
75 | /**
76 | * 电台 - 分类推荐
77 | * @param {string} type - 类别 id
78 | */
79 | export const getRecType = (type) => {
80 | return axios({
81 | method: "GET",
82 | url: "/dj/recommend/type",
83 | params: {
84 | type,
85 | },
86 | });
87 | };
88 |
89 | /**
90 | * 电台 - 详情
91 | * @param {string} rid - 电台 的 id
92 | */
93 | export const getDjDetail = (rid) => {
94 | return axios({
95 | method: "GET",
96 | url: "/dj/detail",
97 | params: {
98 | rid,
99 | },
100 | });
101 | };
102 |
103 | /**
104 | * 电台 - 节目
105 | * @param {string} rid - 电台 的 id
106 | * @param {number} [limit=50] - 返回数量,默认 50
107 | * @param {number} [offset=0] - 偏移数量,默认 0
108 | */
109 | export const getDjProgram = (rid, limit = 50, offset = 0) => {
110 | return axios({
111 | method: "GET",
112 | url: "/dj/program",
113 | params: {
114 | rid,
115 | limit,
116 | offset,
117 | },
118 | });
119 | };
120 |
121 | /**
122 | * 电台 - 订阅
123 | * @param {number} rid - 电台 的 id
124 | * @param {number} t - 操作类型,1为收藏,0为取消收藏
125 | */
126 | export const likeDj = (rid, t) => {
127 | return axios({
128 | method: "GET",
129 | url: "/dj/sub",
130 | params: {
131 | rid,
132 | t,
133 | timestamp: new Date().getTime(),
134 | },
135 | });
136 | };
137 |
--------------------------------------------------------------------------------
/src/api/login.js:
--------------------------------------------------------------------------------
1 | import axios from "@/utils/request";
2 |
3 | /**
4 | * 登录部分
5 | */
6 |
7 | /**
8 | * 生成二维码 key
9 | */
10 | export const getQrKey = () => {
11 | return axios({
12 | method: "GET",
13 | noCookie: true,
14 | url: "/login/qr/key",
15 | params: {
16 | timestamp: new Date().getTime(),
17 | },
18 | });
19 | };
20 |
21 | /**
22 | * 生成二维码
23 | * @param {string} key 二维码key
24 | * @param {boolean} qrimg 是否生成二维码图片,默认为true
25 | */
26 | export const qrCreate = (key, qrimg = true) => {
27 | return axios({
28 | method: "GET",
29 | noCookie: true,
30 | url: "/login/qr/create",
31 | params: {
32 | key,
33 | qrimg,
34 | timestamp: new Date().getTime(),
35 | },
36 | });
37 | };
38 |
39 | /**
40 | * 检查二维码状态
41 | * @param {string} key 二维码key
42 | */
43 | export const checkQr = (key) => {
44 | return axios({
45 | method: "GET",
46 | noCookie: true,
47 | url: "/login/qr/check",
48 | params: {
49 | key,
50 | timestamp: new Date().getTime(),
51 | },
52 | });
53 | };
54 |
55 | /**
56 | * 登录
57 | * @param {string} code 授权码
58 | */
59 | export const toLogin = async (code) => {
60 | try {
61 | const response = await fetch(`https://auth.sayqz.com/?path=info&code=${code}`);
62 | const data = await response.json();
63 |
64 | if (data.code === 200 && data.data) {
65 | return {
66 | code: 200,
67 | cookie: data.data.netease_cookie
68 | };
69 | }
70 | return {
71 | code: data.code,
72 | message: data.message
73 | };
74 | } catch (error) {
75 | console.error('登录请求失败:', error);
76 | return {
77 | code: 500,
78 | message: '登录失败,请重试'
79 | };
80 | }
81 | };
82 |
83 | /**
84 | * 发送验证码
85 | * @param {string} phone 手机号码
86 | */
87 | export const sentCaptcha = (phone) => {
88 | return axios({
89 | method: "GET",
90 | noCookie: true,
91 | url: "/captcha/sent",
92 | params: {
93 | phone,
94 | timestamp: new Date().getTime(),
95 | },
96 | });
97 | };
98 |
99 | /**
100 | * 验证验证码是否正确
101 | * @param {string} phone 手机号码
102 | * @param {string} captcha 验证码
103 | */
104 | export const verifyCaptcha = (phone, captcha) => {
105 | return axios({
106 | method: "GET",
107 | noCookie: true,
108 | url: "/captcha/verify",
109 | params: {
110 | phone,
111 | captcha,
112 | timestamp: new Date().getTime(),
113 | },
114 | });
115 | };
116 |
117 | // 获取登录状态
118 | export const getLoginState = () => {
119 | return axios({
120 | method: "GET",
121 | url: "/login/status",
122 | params: {
123 | timestamp: new Date().getTime(),
124 | },
125 | });
126 | };
127 |
128 | // 刷新登录
129 | export const refreshLogin = () => {
130 | return axios({
131 | method: "GET",
132 | url: "/login/refresh",
133 | params: {
134 | timestamp: new Date().getTime(),
135 | },
136 | });
137 | };
138 |
139 | /**
140 | * 退出登录
141 | */
142 | export const logOut = () => {
143 | return axios({
144 | method: "GET",
145 | url: "/logout",
146 | params: {
147 | timestamp: new Date().getTime(),
148 | },
149 | });
150 | };
151 |
--------------------------------------------------------------------------------
/src/api/other.js:
--------------------------------------------------------------------------------
1 | import axios from "@/utils/request";
2 |
3 | /**
4 | * 杂项
5 | */
6 |
7 | /**
8 | * 获取轮播图
9 | * @param {number} type - 资源类型 / 0: pc / 1: android / 2: iphone / 3: ipad
10 | */
11 | export const getBanner = (type = 0) => {
12 | return axios({
13 | method: "GET",
14 | url: `/banner?type=${type}`,
15 | });
16 | };
17 |
18 | /**
19 | * 资源点赞 ( MV,电台,视频 )
20 | * @param {number} id - 资源 id
21 | * @param {number} t - 操作, 1 为点赞, 其他为取消点赞
22 | * @param {number} type - 资源类型 / 0: 歌曲 / 1: mv / 2: 歌单 / 3: 专辑 / 4: 电台节目 / 5: 视频 / 6: 动态 / 7: 电台
23 | */
24 | export const resourceLike = (id, t = 1, type = 0) => {
25 | return axios({
26 | method: "GET",
27 | url: "/resource/like",
28 | params: {
29 | id,
30 | t,
31 | type,
32 | },
33 | });
34 | };
35 |
--------------------------------------------------------------------------------
/src/api/recommend.js:
--------------------------------------------------------------------------------
1 | import axios from "@/utils/request";
2 | import idMeta from "@/assets/idMeta.json";
3 |
4 | /**
5 | * 推荐部分
6 | */
7 |
8 | /**
9 | * 每日推荐 - 需要登录
10 | * @param {string} [type] - 推荐类型,songs 日推歌曲 / resource 推荐歌单
11 | */
12 | export const getDailyRec = (type = "songs") => {
13 | return axios({
14 | method: "GET",
15 | url: `/recommend/${type}`,
16 | params: {
17 | timestamp: new Date().getTime(),
18 | },
19 | });
20 | };
21 |
22 | /**
23 | * 推荐内容
24 | * @param {string} [type] - 推荐类型,mv MV / newsong 新音乐 / djprogram 电台 / privatecontent 独家放送
25 | * @param {number} [limit=12] - 返回结果的数量,默认为 12
26 | */
27 | export const getPersonalized = (type, limit = 12) => {
28 | const url = type ? `/personalized/${type}` : "/personalized";
29 | return axios({
30 | method: "GET",
31 | url,
32 | params: {
33 | limit,
34 | },
35 | });
36 | };
37 |
38 | /**
39 | * 雷达歌单
40 | */
41 | export const getRadarPlaylist = async () => {
42 | const allRadar = idMeta.radarPlaylist.map((playlist) => {
43 | return axios({
44 | method: "GET",
45 | url: "/playlist/detail",
46 | params: { id: playlist.id },
47 | });
48 | });
49 | const result = await Promise.allSettled(allRadar);
50 | return result.map((res) => res?.value.playlist);
51 | };
52 |
53 | /**
54 | * 热门歌手列表
55 | * @param {number} [limit=6] - 要返回的歌手数量,默认为 6 个
56 | */
57 | export const getTopArtists = (limit = 6) => {
58 | return axios({
59 | method: "GET",
60 | url: "/top/artists",
61 | params: {
62 | limit,
63 | },
64 | });
65 | };
66 |
67 | /**
68 | * 新歌速递
69 | * @param {number} type - 全部:0 / 华语:7 / 欧美:96 / 韩国:16 / 日本:8
70 | */
71 | export const getNewSong = (type = 0) => {
72 | return axios({
73 | method: "GET",
74 | url: "/top/song",
75 | params: {
76 | type,
77 | },
78 | });
79 | };
80 |
81 | /**
82 | * 新碟上架 - 首页
83 | */
84 | export const getNewAlbum = () => {
85 | return axios({
86 | method: "GET",
87 | url: "/album/newest",
88 | });
89 | };
90 |
91 | /**
92 | * 新碟上架 - 全部
93 | * @param {string} [cat='ALL'] - ALL:全部 / ZH:华语 / EA:欧美 / KR:韩国 / JP:日本
94 | * @param {number} [limit=50] - 返回数量,默认50
95 | * @param {number} [offset=0] - 偏移数量,默认0
96 | */
97 | export const getAllNewAlbum = (area = "ALL", limit = 50, offset = 0) => {
98 | return axios({
99 | method: "GET",
100 | url: "/album/new",
101 | params: {
102 | area,
103 | limit,
104 | offset,
105 | },
106 | });
107 | };
108 |
109 | /**
110 | * 获取私人FM数据
111 | */
112 | export const getPersonalFm = () => {
113 | return axios({
114 | method: "GET",
115 | url: "/personal_fm",
116 | params: {
117 | timestamp: new Date().getTime(),
118 | },
119 | });
120 | };
121 |
122 | /**
123 | * 将指定歌曲加入垃圾桶
124 | * @param {number} id 歌曲ID
125 | */
126 | export const setFmToTrash = (id) => {
127 | return axios({
128 | method: "GET",
129 | url: "/fm_trash",
130 | params: {
131 | id,
132 | timestamp: new Date().getTime(),
133 | },
134 | });
135 | };
136 |
--------------------------------------------------------------------------------
/src/api/search.js:
--------------------------------------------------------------------------------
1 | import axios from "@/utils/request";
2 |
3 | /**
4 | * 搜索部分
5 | */
6 |
7 | /**
8 | * 热搜列表 - 详细
9 | */
10 | export const getSearchHot = () => {
11 | return axios({
12 | method: "GET",
13 | url: "/search/hot/detail",
14 | params: {
15 | timestamp: new Date().getTime(),
16 | },
17 | });
18 | };
19 |
20 | /**
21 | * 搜索建议
22 | * @param {string} keywords - 搜索关键词
23 | * @param {Boolean} mobile - 如果传入 true 则返回移动端数据
24 | */
25 | export const getSearchSuggest = (keywords, mobile = false) => {
26 | return axios({
27 | method: "GET",
28 | url: "/search/suggest",
29 | params: {
30 | keywords,
31 | ...(mobile && { type: "mobile" }),
32 | },
33 | });
34 | };
35 |
36 | /**
37 | * 默认搜索关键词
38 | */
39 | export const getSearchDefault = () => {
40 | return axios({
41 | method: "GET",
42 | url: "/search/default",
43 | params: {
44 | timestamp: new Date().getTime(),
45 | },
46 | });
47 | };
48 |
49 | /**
50 | * 搜索结果
51 | * @param {string} keywords - 搜索关键词
52 | * @param {number} [limit=30] - 返回数量,默认30
53 | * @param {number} [offset=0] - 偏移数量,默认0
54 | * @param {number} [type=1] - 可选参数,搜索类型。1表示单曲,10表示专辑,100表示歌手,1000表示歌单,1002表示用户,1004表示MV,1006表示歌词,1009表示电台,1014表示视频,1018表示综合,2000表示声音。默认为 1
55 | */
56 | export const getSearchRes = (keywords, limit = 50, offset = 0, type = 1) => {
57 | return axios({
58 | method: "GET",
59 | url: "/cloudsearch",
60 | params: {
61 | keywords,
62 | limit,
63 | offset,
64 | type,
65 | },
66 | });
67 | };
68 |
--------------------------------------------------------------------------------
/src/api/song.js:
--------------------------------------------------------------------------------
1 | import axios from "@/utils/request";
2 |
3 | /**
4 | * 歌曲部分
5 | */
6 |
7 | // 音质等级映射
8 | const LEVEL_MAP = {
9 | 128000: "standard", // 标准
10 | 320000: "higher", // 较高
11 | 400000: "exhigh", // 极高
12 | 900000: "lossless", // 无损
13 | 1200000: "hires", // Hi-Res
14 | 1500000: "jymaster", // 超清母带
15 | 2000000: "sky" // 沉浸环绕声
16 | };
17 |
18 | // 转换音质等级
19 | const convertLevel = (br) => {
20 | if (typeof br === 'string' && Object.values(LEVEL_MAP).includes(br)) {
21 | return br;
22 | }
23 | const brNumber = Number(br);
24 | // 找到最接近的等级
25 | const levels = Object.keys(LEVEL_MAP).map(Number);
26 | const closest = levels.reduce((prev, curr) => {
27 | return Math.abs(curr - brNumber) < Math.abs(prev - brNumber) ? curr : prev;
28 | });
29 | return LEVEL_MAP[closest] || "standard";
30 | };
31 |
32 | /**
33 | * 获取指定音乐的详情
34 | * @param {string} ids - 要获取详情的音乐ID,多个ID用逗号分隔
35 | */
36 | export const getSongDetail = (ids) => {
37 | const timestamp = new Date().getTime();
38 | return axios({
39 | method: "POST",
40 | url: `/song/detail?timestamp=${timestamp}`,
41 | data: {
42 | ids,
43 | },
44 | });
45 | };
46 |
47 | /**
48 | * 获取音乐 URL
49 | * @param {number} id - 要获取音乐的 ID。
50 | * @param {number|string} br - 音质等级,可以是数字码率或字符串等级名称
51 | */
52 | export const getSongUrl = (id, br = "hires") => {
53 | return axios({
54 | method: "GET",
55 | url: "https://auth.sayqz.com/?path=song/url",
56 | params: {
57 | id,
58 | level: convertLevel(br)
59 | },
60 | });
61 | };
62 |
63 | /**
64 | * 获取指定音乐的歌词
65 | * @param {number} id - 要获取歌词的音乐ID
66 | */
67 | export const getSongLyric = (id) => {
68 | return axios({
69 | method: "GET",
70 | url: "/lyric/new",
71 | params: {
72 | id,
73 | },
74 | });
75 | };
76 |
77 | /**
78 | * 获取客户端歌曲下载
79 | * @param {number} id - 要下载的音乐ID
80 | * @param {number|string} br - 音质等级,可以是数字码率或字符串等级名称
81 | */
82 | export const getSongDownload = async (id, br = "hires") => {
83 | try {
84 | const res = await axios({
85 | method: "GET",
86 | url: "https://auth.sayqz.com/?path=song/url",
87 | params: {
88 | id,
89 | level: convertLevel(br)
90 | },
91 | });
92 |
93 | if (res.code === 200 && res.data?.[0]) {
94 | const songData = res.data[0];
95 | // 只有当歌曲URL存在且状态码为200时才返回成功
96 | if (songData.url && songData.code === 200) {
97 | return {
98 | code: 200,
99 | data: {
100 | url: songData.url,
101 | size: songData.size,
102 | md5: songData.md5,
103 | type: songData.type,
104 | level: songData.level,
105 | br: songData.br
106 | }
107 | };
108 | }
109 | // 如果歌曲URL不存在或状态码不为200,返回错误信息
110 | return {
111 | code: songData.code || 404,
112 | message: songData.message || "歌曲暂时无法下载"
113 | };
114 | }
115 | return {
116 | code: res.code,
117 | message: res.message || "获取下载链接失败"
118 | };
119 | } catch (error) {
120 | console.error("获取下载链接失败:", error);
121 | return {
122 | code: 500,
123 | message: "获取下载链接失败,请重试"
124 | };
125 | }
126 | };
127 |
128 | /**
129 | * 听歌打卡
130 | * @param {number} id - 音乐ID
131 | * @param {number} sourceid - 来源ID
132 | */
133 | export const songScrobble = (id, sourceid = 0, time = 0) => {
134 | return axios({
135 | method: "GET",
136 | url: "/scrobble",
137 | params: {
138 | id,
139 | sourceid,
140 | time,
141 | timestamp: new Date().getTime(),
142 | },
143 | });
144 | };
145 |
--------------------------------------------------------------------------------
/src/api/update.js:
--------------------------------------------------------------------------------
1 | import axios from "@/utils/request";
2 |
3 | /**
4 | * 检查应用更新
5 | */
6 | export const checkUpdate = async () => {
7 | try {
8 | const response = await fetch('https://auth.sayqz.com/?path=app/info&platform=pc');
9 | const data = await response.json();
10 |
11 | if (data.code === 200 && data.data) {
12 | return {
13 | code: 200,
14 | data: {
15 | version: data.data.version,
16 | updateUrl: data.data.update_url,
17 | announcement: data.data.announcement,
18 | ads: data.data.ads
19 | }
20 | };
21 | }
22 | return {
23 | code: data.code,
24 | message: data.message
25 | };
26 | } catch (error) {
27 | console.error('检查更新失败:', error);
28 | return {
29 | code: 500,
30 | message: '检查更新失败,请重试'
31 | };
32 | }
33 | };
--------------------------------------------------------------------------------
/src/api/video.js:
--------------------------------------------------------------------------------
1 | import axios from "@/utils/request";
2 |
3 | /**
4 | * 视频
5 | */
6 |
7 | /**
8 | * 获取指定 MV 的详细信息
9 | * @param {number} mvid - MV ID
10 | */
11 | export const getVideoDetail = (mvid) => {
12 | return axios({
13 | method: "GET",
14 | url: "/mv/detail",
15 | params: {
16 | mvid,
17 | },
18 | });
19 | };
20 |
21 | /**
22 | * 获取指定 MV 的点赞转发评论数
23 | * @param {number} mvid - MV ID
24 | */
25 | export const getVideoInfo = (mvid) => {
26 | return axios({
27 | method: "GET",
28 | url: "/mv/detail/info",
29 | params: {
30 | mvid,
31 | timestamp: new Date().getTime(),
32 | },
33 | });
34 | };
35 |
36 | /**
37 | * 获取指定 MV 的播放地址
38 | * @param {number} id - 要查询的MV ID
39 | * @param {string} [r=null] - 分辨率。默认值为null
40 | */
41 | export const getVideoUrl = (id, r = null) => {
42 | return axios({
43 | method: "GET",
44 | hiddenBar: true,
45 | url: "/mv/url",
46 | params: {
47 | id,
48 | r,
49 | },
50 | });
51 | };
52 |
53 | /**
54 | * 获取与指定 MV 相似的 MV 列表
55 | * @param {number} mvid - 要查询的 MV ID
56 | */
57 | export const getSimiVideo = (mvid) => {
58 | return axios({
59 | method: "GET",
60 | url: "/simi/mv",
61 | params: {
62 | mvid,
63 | },
64 | });
65 | };
66 |
67 | /**
68 | * 收藏/取消收藏视频
69 | * @param {number} t - 操作类型,1为收藏,2为取消收藏
70 | * @param {number} id - 视频id
71 | */
72 | export const likeVideo = (t, id) => {
73 | return axios({
74 | method: "GET",
75 | url: "/video/sub",
76 | params: {
77 | t,
78 | id,
79 | timestamp: new Date().getTime(),
80 | },
81 | });
82 | };
83 |
84 | /**
85 | * 收藏/取消收藏 MV
86 | * @param {number} t - 操作类型,1为收藏,2为取消收藏
87 | * @param {number} mvid - MV id
88 | */
89 | export const likeMv = (t, mvid) => {
90 | return axios({
91 | method: "GET",
92 | url: "/mv/sub",
93 | params: {
94 | t,
95 | mvid,
96 | timestamp: new Date().getTime(),
97 | },
98 | });
99 | };
100 |
101 | /**
102 | * 全部 mv
103 | * @param {string} area - 地区,可选值为全部,内地,港台,欧美,日本,韩国,不填则为全部
104 | * @param {string} type - 类型,可选值为全部,官方版,原生,现场版,网易出品,不填则为全部
105 | * @param {string} order - 排序,可选值为上升最快,最热,最新,不填则为上升最快
106 | * @param {number} [limit=12] - 返回数量,默认12
107 | * @param {number} [offset=0] - 偏移数量,默认0
108 | */
109 | export const allMv = (area, type, order, limit = 12, offset = 0) => {
110 | return axios({
111 | method: "GET",
112 | url: "/mv/all",
113 | params: {
114 | area,
115 | type,
116 | order,
117 | limit,
118 | offset,
119 | },
120 | });
121 | };
122 |
--------------------------------------------------------------------------------
/src/assets/emoji.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "emoji": "😄",
4 | "emjName": "大笑"
5 | },
6 | {
7 | "emoji": "😊",
8 | "emjName": "可爱"
9 | },
10 | {
11 | "emoji": "😄",
12 | "emjName": "憨笑"
13 | },
14 | {
15 | "emoji": "😘",
16 | "emjName": "亲亲"
17 | },
18 | {
19 | "emoji": "😄",
20 | "emjName": "流泪"
21 | },
22 | {
23 | "emoji": "😊",
24 | "emjName": "亲"
25 | },
26 | {
27 | "emoji": "😐",
28 | "emjName": "呆"
29 | },
30 | {
31 | "emoji": "😢",
32 | "emjName": "哀伤"
33 | },
34 | {
35 | "emoji": "😁",
36 | "emjName": "呲牙"
37 | },
38 | {
39 | "emoji": "😝",
40 | "emjName": "吐舌"
41 | },
42 | {
43 | "emoji": "😄",
44 | "emjName": "撇嘴"
45 | },
46 | {
47 | "emoji": "😡",
48 | "emjName": "怒"
49 | },
50 | {
51 | "emoji": "😏",
52 | "emjName": "奸笑"
53 | },
54 | {
55 | "emoji": "😅",
56 | "emjName": "汗"
57 | },
58 | {
59 | "emoji": "😣",
60 | "emjName": "痛苦"
61 | },
62 | {
63 | "emoji": "😱",
64 | "emjName": "惶恐"
65 | },
66 | {
67 | "emoji": "🤒",
68 | "emjName": "生病"
69 | },
70 | {
71 | "emoji": "😷",
72 | "emjName": "口罩"
73 | },
74 | {
75 | "emoji": "😭",
76 | "emjName": "大哭"
77 | },
78 | {
79 | "emoji": "😵",
80 | "emjName": "晕"
81 | },
82 | {
83 | "emoji": "😡",
84 | "emjName": "发怒"
85 | },
86 | {
87 | "emoji": "😀",
88 | "emjName": "开心"
89 | },
90 | {
91 | "emoji": "😈",
92 | "emjName": "鬼脸"
93 | },
94 | {
95 | "emoji": "😠",
96 | "emjName": "皱眉"
97 | },
98 | {
99 | "emoji": "😷",
100 | "emjName": "流感"
101 | },
102 | {
103 | "emoji": "❤️",
104 | "emjName": "爱心"
105 | },
106 | {
107 | "emoji": "💔",
108 | "emjName": "心碎"
109 | },
110 | {
111 | "emoji": "💗",
112 | "emjName": "钟情"
113 | },
114 | {
115 | "emoji": "⭐",
116 | "emjName": "星星"
117 | },
118 | {
119 | "emoji": "💢",
120 | "emjName": "生气"
121 | },
122 | {
123 | "emoji": "💩",
124 | "emjName": "便便"
125 | },
126 | {
127 | "emoji": "💪",
128 | "emjName": "强"
129 | },
130 | {
131 | "emoji": "💤",
132 | "emjName": "弱"
133 | },
134 | {
135 | "emoji": "🙏",
136 | "emjName": "拜"
137 | },
138 | {
139 | "emoji": "🤝",
140 | "emjName": "牵手"
141 | },
142 | {
143 | "emoji": "💃",
144 | "emjName": "跳舞"
145 | },
146 | {
147 | "emoji": "🚫",
148 | "emjName": "禁止"
149 | },
150 | {
151 | "emoji": "👉",
152 | "emjName": "这边"
153 | },
154 | {
155 | "emoji": "😍",
156 | "emjName": "爱意"
157 | },
158 | {
159 | "emoji": "💏",
160 | "emjName": "示爱"
161 | },
162 | {
163 | "emoji": "💋",
164 | "emjName": "嘴唇"
165 | },
166 | {
167 | "emoji": "🐶",
168 | "emjName": "狗"
169 | },
170 | {
171 | "emoji": "😸",
172 | "emjName": "猫"
173 | },
174 | {
175 | "emoji": "🐷",
176 | "emjName": "猪"
177 | },
178 | {
179 | "emoji": "🐰",
180 | "emjName": "兔子"
181 | },
182 | {
183 | "emoji": "🐔",
184 | "emjName": "小鸡"
185 | },
186 | {
187 | "emoji": "🐓",
188 | "emjName": "公鸡"
189 | },
190 | {
191 | "emoji": "👻",
192 | "emjName": "幽灵"
193 | },
194 | {
195 | "emoji": "🎅",
196 | "emjName": "圣诞"
197 | },
198 | {
199 | "emoji": "👽",
200 | "emjName": "外星"
201 | },
202 | {
203 | "emoji": "💎",
204 | "emjName": "钻石"
205 | },
206 | {
207 | "emoji": "🎁",
208 | "emjName": "礼物"
209 | },
210 | {
211 | "emoji": "👦",
212 | "emjName": "男孩"
213 | },
214 | {
215 | "emoji": "👧",
216 | "emjName": "女孩"
217 | },
218 | {
219 | "emoji": "🍰",
220 | "emjName": "蛋糕"
221 | },
222 | {
223 | "emoji": "🔞",
224 | "emjName": "18"
225 | },
226 | {
227 | "emoji": "🔵",
228 | "emjName": "圈"
229 | },
230 | {
231 | "emoji": "❌",
232 | "emjName": "叉"
233 | },
234 | {
235 | "emoji": "😱",
236 | "emjName": "惊恐"
237 | },
238 | {
239 | "emoji": "😍",
240 | "emjName": "色"
241 | }
242 | ]
243 |
--------------------------------------------------------------------------------
/src/assets/idMeta.json:
--------------------------------------------------------------------------------
1 | {
2 | "radarPlaylist": [
3 | {
4 | "id": 3136952023,
5 | "name": "私人雷达"
6 | },
7 | {
8 | "id": 5320167908,
9 | "name": "时光雷达"
10 | },
11 | {
12 | "id": 5327906368,
13 | "name": "乐迷雷达"
14 | },
15 | {
16 | "id": 5362359247,
17 | "name": "宝藏雷达"
18 | },
19 | {
20 | "id": 5300458264,
21 | "name": "新歌雷达"
22 | },
23 | {
24 | "id": 5341776086,
25 | "name": "神秘雷达"
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/src/assets/themeColor.json:
--------------------------------------------------------------------------------
1 | {
2 | "red": {
3 | "name": "欢快派对",
4 | "label": "red",
5 | "primaryColor": "#fe7971",
6 | "primaryColorHover": "#F57B74",
7 | "primaryColorSuppl": "#F57B74",
8 | "primaryColorPressed": "#F64B41"
9 | },
10 | "orange": {
11 | "name": "柑橘桔梦",
12 | "label": "orange",
13 | "primaryColor": "#ff8c00",
14 | "primaryColorHover": "#ffa033",
15 | "primaryColorSuppl": "#ffa033",
16 | "primaryColorPressed": "#ff6b00"
17 | },
18 | "blue": {
19 | "name": "深海蓝梦",
20 | "label": "blue",
21 | "primaryColor": "#3b5998",
22 | "primaryColorHover": "#5475a3",
23 | "primaryColorSuppl": "#8b9dc3",
24 | "primaryColorPressed": "#2d4373"
25 | },
26 | "pink": {
27 | "name": "粉色梦幻",
28 | "label": "pink",
29 | "primaryColor": "#e91e63",
30 | "primaryColorHover": "#f06292",
31 | "primaryColorSuppl": "#f06292",
32 | "primaryColorPressed": "#c2185b"
33 | },
34 | "brown": {
35 | "name": "深棕林荫",
36 | "label": "brown",
37 | "primaryColor": "#795548",
38 | "primaryColorHover": "#8d6e63",
39 | "primaryColorSuppl": "#8d6e63",
40 | "primaryColorPressed": "#5d4037"
41 | },
42 | "indigo": {
43 | "name": "星空靛蓝",
44 | "label": "indigo",
45 | "primaryColor": "#3f51b5",
46 | "primaryColorHover": "#5c6bc0",
47 | "primaryColorSuppl": "#5c6bc0",
48 | "primaryColorPressed": "#3949ab"
49 | },
50 | "green": {
51 | "name": "生命绿洲",
52 | "label": "green",
53 | "primaryColor": "#2ecc71",
54 | "primaryColorHover": "#3ddc88",
55 | "primaryColorSuppl": "#3ddc88",
56 | "primaryColorPressed": "#27ae60"
57 | },
58 | "purple": {
59 | "name": "皇室紫梦",
60 | "label": "purple",
61 | "primaryColor": "#9c27b0",
62 | "primaryColorHover": "#ba68c8",
63 | "primaryColorSuppl": "#ba68c8",
64 | "primaryColorPressed": "#7b1fa2"
65 | },
66 | "yellow": {
67 | "name": "金色阳光",
68 | "label": "yellow",
69 | "primaryColor": "#FBC02D",
70 | "primaryColorHover": "#FFD54F",
71 | "primaryColorSuppl": "#FFD54F",
72 | "primaryColorPressed": "#FFC107"
73 | },
74 | "teal": {
75 | "name": "海洋碧绿",
76 | "label": "teal",
77 | "primaryColor": "#009688",
78 | "primaryColorHover": "#26a69a",
79 | "primaryColorSuppl": "#26a69a",
80 | "primaryColorPressed": "#00796b"
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/components/Cover/CoverDropdown.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 |
18 |
88 |
--------------------------------------------------------------------------------
/src/components/Cover/SpecialCover.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
21 |
22 |
23 |
24 |
32 |
33 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
62 |
63 |
64 |
65 |
123 |
124 |
148 |
--------------------------------------------------------------------------------
/src/components/Global/MainLayout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
33 |
34 |
51 |
--------------------------------------------------------------------------------
/src/components/Global/Pagination.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
57 |
58 |
65 |
--------------------------------------------------------------------------------
/src/components/Modal/CreatePlaylist.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
21 |
22 | 设为隐私歌单
23 |
24 |
25 | 取消
26 |
31 | 新建
32 |
33 |
34 |
35 |
36 |
37 |
38 |
78 |
--------------------------------------------------------------------------------
/src/components/Modal/LoginPhone.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
18 |
19 |
20 |
26 | {{ loading ? '登录中...' : '登录' }}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
78 |
79 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/src/components/Modal/PlaylistUpdate.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
15 |
16 |
17 |
18 |
19 |
31 |
32 |
33 |
40 |
41 |
42 |
43 |
44 | 取消
45 | 编辑
46 |
47 |
48 |
49 |
50 |
51 |
141 |
--------------------------------------------------------------------------------
/src/components/Player/CountDown.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
19 |
20 |
21 |
79 |
80 |
118 |
--------------------------------------------------------------------------------
/src/components/Search/SearchDropdown.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 |
17 |
109 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "vue";
2 | import { createPinia } from "pinia";
3 | import { checkPlatform } from "@/utils/helper";
4 | import App from "@/App.vue";
5 | import router from "@/router";
6 | import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
7 | import packageJson from "@/../package.json";
8 |
9 | // 全局样式
10 | import "@/style/main.scss";
11 | import "@/style/animate.scss";
12 |
13 | // 是否为 Electron
14 | const isElectron = checkPlatform.electron();
15 |
16 | // 根据设备类型动态添加
17 | const linkElement = document.createElement("link");
18 | linkElement.rel = "stylesheet";
19 | linkElement.href = isElectron
20 | ? `${import.meta.env.BASE_URL}font/font.min.css`
21 | : "https://s1.hdslb.com/bfs/static/jinkela/long/font/regular.css";
22 | document.head.appendChild(linkElement);
23 | document.body.classList.add(isElectron ? "electron" : null);
24 |
25 | // 程序重置
26 | window.$cleanAll = (tip = true) => {
27 | if (tip) {
28 | const isConfirmed = window.confirm(`确认要重置${isElectron ? "应用程序" : "该站点"}吗?`);
29 | if (!isConfirmed) return false;
30 | }
31 | // 清除 localStorage
32 | localStorage.clear();
33 | // 清除 IndexedDB 数据库
34 | indexedDB.deleteDatabase("filesDB");
35 | // 清除所有 Cookie
36 | document.cookie.split(";").forEach((cookie) => {
37 | var eqPos = cookie.indexOf("=");
38 | var name = eqPos > -1 ? cookie.substring(0, eqPos) : cookie;
39 | document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
40 | });
41 | // 清除缓存
42 | if (caches) {
43 | caches.keys().then((names) => {
44 | for (let name of names) caches.delete(name);
45 | });
46 | }
47 | return "已重置应用,请" + (isElectron ? "重启应用" : "刷新页面");
48 | };
49 |
50 | // 版权声明
51 | const logoText = import.meta.env.RENDERER_VITE_SITE_TITLE;
52 | const copyrightNotice = `\n\n版本: ${packageJson.version}\n作者: ${packageJson.author}\n作者主页: ${packageJson.home}\nGitHub: ${packageJson.github}`;
53 | console.info(
54 | `%c${logoText} %c ${copyrightNotice}`,
55 | "color:#f55e55;font-size:26px;font-weight:bold;",
56 | "font-size:16px",
57 | );
58 | console.info(
59 | "若站点出现异常,可尝试在下方输入 %c$cleanAll()%c 然后按回车来重置",
60 | "background: #eaeffd;color:#f55e55;padding: 4px 6px;border-radius:8px;",
61 | "background:unset;color:unset;",
62 | );
63 |
64 | // 挂载
65 | const app = createApp(App);
66 | // pinia
67 | const pinia = createPinia();
68 | pinia.use(piniaPluginPersistedstate);
69 | app.use(pinia);
70 | // router
71 | app.use(router);
72 | // app
73 | app.mount("#app");
74 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import { nextTick } from "vue";
2 | import { createRouter, createWebHashHistory } from "vue-router";
3 | import { checkPlatform } from "@/utils/helper";
4 | import { isLogin } from "@/utils/auth";
5 | import routes from "@/router/routes";
6 |
7 | // 基础配置
8 | const router = createRouter({
9 | history: createWebHashHistory(import.meta.env.BASE_URL),
10 | routes,
11 | });
12 |
13 | // 页面回顶
14 | const scrollToTop = () => {
15 | nextTick().then(() => {
16 | const mainLayout = document.getElementById("main-layout");
17 | mainLayout?.scrollIntoView({ behavior: "smooth" });
18 | });
19 | };
20 |
21 | // 路由守卫
22 | router.beforeEach((to, from, next) => {
23 | // 开始进度条
24 | if (to.path !== from.path) {
25 | if (typeof $loadingBar !== "undefined" && !checkPlatform.electron()) {
26 | $loadingBar.start();
27 | }
28 | }
29 | // 判断是否需要登录
30 | if (to.meta.needLogin) {
31 | if (isLogin()) {
32 | next();
33 | } else {
34 | $message.warning("请登录后使用");
35 | if (typeof $loadingBar !== "undefined" && !checkPlatform.electron()) {
36 | $loadingBar.error();
37 | }
38 | if (typeof $changeLogin !== "undefined") $changeLogin();
39 | }
40 | }
41 | // 是否为本地功能
42 | else if (to.meta.needLocal) {
43 | if (checkPlatform.electron()) {
44 | next();
45 | } else {
46 | $message.error("客户端独占功能");
47 | if (typeof $loadingBar !== "undefined" && !checkPlatform.electron()) {
48 | $loadingBar.error();
49 | }
50 | next("/403");
51 | }
52 | } else {
53 | next();
54 | }
55 | });
56 |
57 | router.afterEach(() => {
58 | // 结束进度条
59 | if (typeof $loadingBar !== "undefined" && !checkPlatform.electron()) $loadingBar.finish();
60 | // 页面回顶
61 | scrollToTop();
62 | });
63 |
64 | export default router;
65 |
--------------------------------------------------------------------------------
/src/stores/index.js:
--------------------------------------------------------------------------------
1 | // stores
2 | import useSiteDataStore from "@/stores/siteData";
3 | import useSiteStatusStore from "@/stores/siteStatus";
4 | import useSiteSettingsStore from "@/stores/siteSettings";
5 | import useMusicDataStore from "@/stores/musicData";
6 | import useIndexedDBStore from "@/stores/indexedDB";
7 |
8 | export const siteData = () => useSiteDataStore();
9 | export const siteStatus = () => useSiteStatusStore();
10 | export const siteSettings = () => useSiteSettingsStore();
11 | export const musicData = () => useMusicDataStore();
12 | export const indexedDBData = () => useIndexedDBStore();
13 |
--------------------------------------------------------------------------------
/src/stores/indexedDB.js:
--------------------------------------------------------------------------------
1 | // indexedDB
2 | import { defineStore } from "pinia";
3 | import localforage from "localforage";
4 |
5 | const useIndexedDBStore = defineStore("indexedDB", {
6 | state: () => ({
7 | filesDB: localforage.createInstance({
8 | name: "filesDB",
9 | }),
10 | usersDB: localforage.createInstance({
11 | name: "usersDB",
12 | }),
13 | }),
14 | getters: {},
15 | actions: {
16 | async setfilesDB(key, value) {
17 | try {
18 | await this.filesDB.setItem(key, value);
19 | } catch (error) {
20 | console.error("Error setting data in IndexedDB:", error);
21 | throw error;
22 | }
23 | },
24 | async getfilesDB(key) {
25 | try {
26 | const value = await this.filesDB.getItem(key);
27 | return value;
28 | } catch (error) {
29 | console.error("Error retrieving data from IndexedDB:", error);
30 | throw error;
31 | }
32 | },
33 | },
34 | });
35 |
36 | export default useIndexedDBStore;
37 |
--------------------------------------------------------------------------------
/src/stores/siteSettings.js:
--------------------------------------------------------------------------------
1 | // 站点设置
2 | import { defineStore } from "pinia";
3 |
4 | const useSiteSettingsStore = defineStore("siteSettings", {
5 | state: () => {
6 | return {
7 | // 基础配置
8 | closeTip: true, // 关闭软件提醒弹窗
9 | closeType: "hide", // 关闭方式 close 直接关闭 / hide 最小化到任务栏
10 | showTaskbarProgress: false, // 显示歌曲任务栏进度
11 | showSearchHistory: true, // 搜索历史
12 | autoSignIn: true, // 自动签到
13 | showGithub: true,
14 | showSider: true, // 显示侧边栏
15 | siderShowCover: false, // 侧边栏显示封面
16 | // 主题部分
17 | themeType: "dark",
18 | themeAuto: false,
19 | themeTypeName: "red",
20 | themeTypeData: {},
21 | themeAutoCover: true, // 主题色跟随封面
22 | themeAutoCoverType: "secondary",
23 | // 播放部分
24 | playCoverType: "cover", // 播放器样式
25 | songLevel: "exhigh", // 歌曲音质
26 | autoPlay: false, // 程序启动时自动播放
27 | songVolumeFade: true, // 歌曲渐入渐出
28 | useUnmServer: true, // 是否使用网易云解灰
29 | countDownShow: true, // 是否显示前奏等待
30 | bottomLyricShow: true, // 底栏歌词显示
31 | playerBackgroundType: "blur", // 播放器背景类别 animation 流动 / blur 模糊
32 | memorySeek: true, // 记忆上次播放位置
33 | playSearch: false, // 是否播放全部搜索结果
34 | showPlaylistCount: true, // 是否显示播放列表数量
35 | showSpectrums: false, // 是否显示音乐频谱
36 | useMusicCache: false, // 是否采用音乐缓存
37 | // 数量部分
38 | loadSize: 50, // 每页加载数量
39 | // 歌词部分
40 | lrcMousePause: false, // 鼠标移入歌词区域暂停滚动
41 | lyricsFontSize: 46, // 歌词大小
42 | lyricsBlur: false, // 歌词模糊
43 | showYrc: true, // 是否显示逐字歌词
44 | showYrcAnimation: true, // 是否显示逐字歌词动画
45 | lyricsPosition: "left", // 歌词位置
46 | lyricsBlock: "start", // 歌词滚动位置
47 | showTransl: true, // 是否显示歌词翻译
48 | showRoma: true, // 是否显示歌词音译
49 | // 下载部分
50 | downloadPath: null, // 默认下载路径
51 | downloadMeta: true, // 同时下载元信息
52 | downloadCover: true, // 同时下载封面
53 | downloadLyrics: true, // 同时下载歌词
54 | downloadLyricsToFile: true,
55 | downloadCoverToFile: true,
56 | };
57 | },
58 | getters: {},
59 | actions: {
60 | // 调整主题类别
61 | setThemeType(value) {
62 | this.themeType = value;
63 | this.themeAuto = false;
64 | $message.info(`已切换至${value === "light" ? "浅色" : "深色"}模式`, { showIcon: false });
65 | },
66 | },
67 | // 数据持久化
68 | persist: [
69 | {
70 | key: "siteSettings",
71 | storage: localStorage,
72 | },
73 | ],
74 | });
75 |
76 | export default useSiteSettingsStore;
77 |
--------------------------------------------------------------------------------
/src/stores/siteStatus.js:
--------------------------------------------------------------------------------
1 | // 站点状态
2 | import { defineStore } from "pinia";
3 |
4 | const useSiteStatusStore = defineStore("siteStatus", {
5 | state: () => {
6 | return {
7 | // 菜单折叠状态
8 | asideMenuCollapsed: false,
9 | // 搜索框状态
10 | searchInputFocus: false,
11 | // 是否展示播放控制条
12 | showPlayBar: true,
13 | // 播放状态
14 | playState: false,
15 | playLoading: false,
16 | playUseOtherSource: false,
17 | // 播放列表状态
18 | playListShow: false,
19 | // 全屏播放器状态
20 | showFullPlayer: false,
21 | // 播放器功能显示
22 | playerControlShow: true,
23 | controlTimeOut: null,
24 | // 实时播放进度
25 | playSeek: 0,
26 | // 是否下一首
27 | hasNextSong: false,
28 | // 封面主题
29 | coverTheme: {},
30 | coverBackground: null,
31 | // 纯净歌词模式
32 | pureLyricMode: false,
33 | // 音乐频谱数据
34 | spectrumsData: [],
35 | // 当前歌曲歌词播放索引
36 | playSongLyricIndex: -1,
37 | // 播放时长数据
38 | playTimeData: {
39 | currentTime: 0,
40 | duration: 0,
41 | bar: 0,
42 | played: "00:00",
43 | durationTime: "00:00",
44 | },
45 | // 默认倍速
46 | playRate: 1,
47 | // 默认音量
48 | playVolume: 0.7,
49 | // 静音前音量
50 | playVolumeMute: 0,
51 | // 当前播放索引
52 | playIndex: 0,
53 | // 当前模式
54 | // normal 正常 / fm 私人 FM / dj 电台
55 | playMode: "normal",
56 | // normal 顺序播放 / random 随机播放 / repeat 单曲循环
57 | playSongMode: "normal",
58 | // 是否为心动模式
59 | playHeartbeatMode: false,
60 | };
61 | },
62 | getters: {},
63 | actions: {},
64 | // 数据持久化
65 | persist: [
66 | {
67 | key: "siteStatus",
68 | storage: localStorage,
69 | paths: [
70 | "asideMenuCollapsed",
71 | "pureLyricMode",
72 | "playRate",
73 | "playVolume",
74 | "playVolumeMute",
75 | "playIndex",
76 | "playMode",
77 | "playSongMode",
78 | "playHeartbeatMode",
79 | "playTimeData",
80 | ],
81 | },
82 | ],
83 | });
84 |
85 | export default useSiteStatusStore;
86 |
--------------------------------------------------------------------------------
/src/style/animate.scss:
--------------------------------------------------------------------------------
1 | // fade
2 | .fade-enter-active,
3 | .fade-leave-active {
4 | transition: opacity 0.3s ease-in-out;
5 | }
6 |
7 | .fade-enter-from,
8 | .fade-leave-to {
9 | opacity: 0;
10 | }
11 |
12 | // fadeDown
13 | .fadeDown-enter-active,
14 | .fadeDown-leave-active {
15 | transition:
16 | opacity 0.3s ease,
17 | transform 0.3s ease;
18 | }
19 |
20 | .fadeDown-enter-from,
21 | .fadeDown-leave-to {
22 | opacity: 0;
23 | transform: translateY(-10px);
24 | }
25 |
26 | // router
27 | .router-enter-active,
28 | .router-leave-active {
29 | transition: all 0.2s ease;
30 | }
31 |
32 | .router-enter-from,
33 | .router-leave-to {
34 | opacity: 0;
35 | // transform: scale(0.98);
36 | transform: translateX(10px);
37 | }
38 |
39 | // shrink
40 | .shrink-enter-active,
41 | .shrink-leave-active {
42 | opacity: 1;
43 | transform: scale(1);
44 | transition:
45 | transform 0.3s,
46 | opacity 0.3s;
47 | }
48 | .shrink-enter-from,
49 | .shrink-leave-to {
50 | opacity: 0;
51 | transform: scale(0.4);
52 | }
53 |
54 | // up
55 | .up-enter-active,
56 | .up-leave-active {
57 | transform: translateY(0);
58 | transition: transform 0.5s cubic-bezier(0.65, 0.05, 0.36, 1);
59 | }
60 | .up-enter-from,
61 | .up-leave-to {
62 | transform: translateY(100%);
63 | }
64 |
65 | // left
66 | .left-enter-active,
67 | .left-leave-active {
68 | opacity: 1;
69 | transform: translateX(0);
70 | transition:
71 | transform 0.3s,
72 | opacity 0.3s;
73 | }
74 | .left-enter-from,
75 | .left-leave-to {
76 | transform: translateX(-100%);
77 | }
78 |
79 | @keyframes skeleton-loading {
80 | 0% {
81 | background-position: 100% 50%;
82 | }
83 |
84 | 100% {
85 | background-position: 0 50%;
86 | }
87 | }
88 |
89 | @keyframes coverRotate {
90 | 0% {
91 | transform: rotate(0deg) scale(1) translateX(0);
92 | }
93 | 50% {
94 | transform: rotate(180deg) scale(2) translateX(40%);
95 | }
96 | 100% {
97 | transform: rotate(360deg) scale(1) translateX(0);
98 | }
99 | }
100 |
101 | @keyframes playerCoverRotate {
102 | 0% {
103 | transform: rotate(0deg);
104 | }
105 | 100% {
106 | transform: rotate(360deg);
107 | }
108 | }
109 |
110 | @keyframes fade-spacing {
111 | 0% {
112 | opacity: 0;
113 | }
114 | 100% {
115 | opacity: 1;
116 | letter-spacing: 12px;
117 | }
118 | }
119 |
120 | @keyframes fade-down {
121 | 0% {
122 | opacity: 0;
123 | transform: translateY(10px);
124 | }
125 | 100% {
126 | opacity: 1;
127 | transform: translateY(0);
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/style/main.scss:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | user-select: none;
5 | -webkit-user-drag: none;
6 | }
7 |
8 | html,
9 | body,
10 | #app {
11 | width: 100vw;
12 | height: 100vh;
13 | font-family: "HarmonyOS_Regular", sans-serif !important;
14 | overflow: hidden;
15 | }
16 |
17 | // n-text
18 | .n-text {
19 | display: -webkit-box;
20 | overflow: hidden;
21 | word-break: break-all;
22 | -webkit-box-orient: vertical;
23 | -webkit-line-clamp: 1;
24 | }
25 |
26 | // n-image
27 | .n-image {
28 | .cover-loading {
29 | position: relative;
30 | display: flex;
31 | align-items: center;
32 | justify-content: center;
33 | width: 100%;
34 | height: 0;
35 | padding-bottom: 100%;
36 | background-color: var(--n-color);
37 | border-radius: 8px;
38 | .n-spin-body {
39 | position: absolute;
40 | height: 100%;
41 | top: 0;
42 | }
43 | &.mv {
44 | padding-bottom: 56%;
45 | }
46 | .loading-img {
47 | position: absolute;
48 | top: 0;
49 | width: 100%;
50 | height: 100%;
51 | opacity: 1 !important;
52 | }
53 | }
54 | }
55 |
56 | // n-scrollbar
57 | .n-scrollbar {
58 | .n-scrollbar-rail {
59 | right: 0 !important;
60 | z-index: 20;
61 | }
62 | }
63 |
64 | // n-message
65 | .n-message-container {
66 | bottom: 90px !important;
67 | .n-message-wrapper {
68 | .n-message {
69 | border-radius: 12px;
70 | }
71 | }
72 | }
73 |
74 | // n-dropdown
75 | .n-dropdown {
76 | --n-border-radius: 8px !important;
77 | }
78 |
79 | // n-notification
80 | .n-notification {
81 | --n-border-radius: 8px !important;
82 | }
83 |
84 | // n-skeleton
85 | .n-skeleton {
86 | background: linear-gradient(
87 | 90deg,
88 | var(--n-color-end) 25%,
89 | var(--n-color-start) 37%,
90 | var(--n-color-end) 63%
91 | );
92 | background-size: 400% 100%;
93 | animation: skeleton-loading 1.4s ease infinite;
94 | }
95 |
96 | // n-tabs
97 | .n-tabs {
98 | --n-tab-border-radius: 6px !important;
99 | .n-tabs-rail {
100 | position: relative;
101 | .n-tabs-tab-wrapper {
102 | .n-tabs-tab {
103 | &:hover {
104 | transition:
105 | color 0.3s,
106 | background-color 0.5s ease-in-out,
107 | box-shadow 0.3s;
108 | }
109 | }
110 | }
111 | }
112 | }
113 |
114 | // n-modal
115 | .n-modal {
116 | width: 60vw;
117 | max-width: 700px;
118 | min-width: min(24rem, 100vw);
119 | border-radius: 8px;
120 | }
121 | .n-modal-mask {
122 | backdrop-filter: blur(16px);
123 | }
124 |
125 | // n-result
126 | .n-result {
127 | min-height: calc(100vh - 178px);
128 | display: flex;
129 | flex-direction: column;
130 | justify-content: center;
131 | }
132 |
133 | // n-popover
134 | .n-popover {
135 | border-radius: 8px !important;
136 | backdrop-filter: blur(10px);
137 | }
138 |
139 | // n-back-top
140 | .n-back-top {
141 | transition:
142 | color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
143 | box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1),
144 | background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
145 | transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
146 | &:active {
147 | transform: scale(0.9);
148 | }
149 | }
150 |
151 | // n-data
152 | .n-data-table {
153 | --n-merged-th-color: transparent !important;
154 | --n-merged-td-color: transparent !important;
155 | --n-merged-border-color: transparent !important;
156 | --n-merged-th-color-hover: transparent !important;
157 | --n-merged-td-color-hover: transparent !important;
158 | --n-merged-td-color-striped: transparent !important;
159 | .n-data-table__pagination {
160 | margin-top: 40px;
161 | margin-bottom: 30px;
162 | justify-content: center;
163 | }
164 | }
165 |
166 | // n-image-preview
167 | .n-image-preview-container {
168 | .n-image-preview-overlay {
169 | backdrop-filter: blur(16px);
170 | }
171 | }
172 |
173 | // layout-toggle-bar
174 | .n-layout-toggle-bar {
175 | height: 44px !important;
176 | top: calc(50% - 22px) !important;
177 | .n-layout-toggle-bar__top,
178 | .n-layout-toggle-bar__bottom {
179 | height: 24px !important;
180 | }
181 | .n-layout-toggle-bar__bottom {
182 | top: 20px !important;
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/src/utils/auth.js:
--------------------------------------------------------------------------------
1 | import { logOut } from "@/api/login";
2 | import { siteData } from "@/stores";
3 | import Cookies from "js-cookie";
4 |
5 | /**
6 | * 将服务器返回的 Cookie 数据设置到浏览器的 Cookie 和本地存储中
7 | */
8 | export const setCookies = (cookieValue) => {
9 | const cookies = cookieValue.split(";;");
10 | cookies.map((cookie) => {
11 | document.cookie = cookie;
12 | const cookieKeyValue = cookie.split(";")[0].split("=");
13 | localStorage.setItem(`cookie-${cookieKeyValue[0]}`, cookieKeyValue[1]);
14 | });
15 | };
16 |
17 | /**
18 | * 获取指定 Cookie 值
19 | * @param {string} key - 要获取的 Cookie 键名
20 | * @returns {string|null} Cookie 值,如果不存在则返回 null
21 | */
22 | export const getCookie = (key) => {
23 | return Cookies.get(key) ?? localStorage.getItem(`cookie-${key}`);
24 | };
25 |
26 | /**
27 | * 移除指定 Cookie
28 | * @param {string} key - 要移除的 Cookie 键名
29 | */
30 | export const removeCookie = (key) => {
31 | Cookies.remove(key);
32 | localStorage.removeItem(`cookie-${key}`);
33 | };
34 |
35 | /**
36 | * 检查用户是否已登录
37 | * @returns {boolean} 如果用户已登录,则返回 true;否则返回 false
38 | */
39 | export const isLogin = () => {
40 | const data = siteData();
41 | const status = getCookie("MUSIC_U") === undefined || getCookie("MUSIC_U") === null;
42 | // 更改状态
43 | data.userLoginStatus = !status;
44 | return !status;
45 | };
46 |
47 | /**
48 | * 退出用户登录
49 | */
50 | export const toLogout = async (show = true) => {
51 | const data = siteData();
52 | // 去除 cookie
53 | await logOut();
54 | removeCookie("MUSIC_U");
55 | removeCookie("__csrf");
56 | sessionStorage.clear();
57 | // 去除用户信息
58 | data.userLoginStatus = false;
59 | data.userData = {};
60 | data.userLikeData = {
61 | songs: [],
62 | playlists: [],
63 | albums: [],
64 | mvs: [],
65 | };
66 | data.dailySongsData = {
67 | timestamp: null,
68 | data: [],
69 | };
70 | if (show) $message.success("成功退出登录");
71 | };
72 |
--------------------------------------------------------------------------------
/src/utils/base64.js:
--------------------------------------------------------------------------------
1 | // https://github.com/niklasvh/base64-arraybuffer/blob/master/src/index.ts
2 | // Copyright (c) 2012 Niklas von Hertzen Licensed under the MIT license.
3 |
4 | const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5 |
6 | // Use a lookup table to find the index.
7 | const lookup = typeof Uint8Array === "undefined" ? [] : new Uint8Array(256);
8 | for (let i = 0; i < chars.length; i++) {
9 | lookup[chars.charCodeAt(i)] = i;
10 | }
11 |
12 | export const encode = (arraybuffer) => {
13 | let bytes = new Uint8Array(arraybuffer),
14 | i,
15 | len = bytes.length,
16 | base64 = "";
17 |
18 | for (i = 0; i < len; i += 3) {
19 | base64 += chars[bytes[i] >> 2];
20 | base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
21 | base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
22 | base64 += chars[bytes[i + 2] & 63];
23 | }
24 |
25 | if (len % 3 === 2) {
26 | base64 = base64.substring(0, base64.length - 1) + "=";
27 | } else if (len % 3 === 1) {
28 | base64 = base64.substring(0, base64.length - 2) + "==";
29 | }
30 |
31 | return base64;
32 | };
33 |
34 | export const decode = (base64) => {
35 | let bufferLength = base64.length * 0.75,
36 | len = base64.length,
37 | i,
38 | p = 0,
39 | encoded1,
40 | encoded2,
41 | encoded3,
42 | encoded4;
43 |
44 | if (base64[base64.length - 1] === "=") {
45 | bufferLength--;
46 | if (base64[base64.length - 2] === "=") {
47 | bufferLength--;
48 | }
49 | }
50 |
51 | const arraybuffer = new ArrayBuffer(bufferLength),
52 | bytes = new Uint8Array(arraybuffer);
53 |
54 | for (i = 0; i < len; i += 4) {
55 | encoded1 = lookup[base64.charCodeAt(i)];
56 | encoded2 = lookup[base64.charCodeAt(i + 1)];
57 | encoded3 = lookup[base64.charCodeAt(i + 2)];
58 | encoded4 = lookup[base64.charCodeAt(i + 3)];
59 |
60 | bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
61 | bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
62 | bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
63 | }
64 |
65 | return arraybuffer;
66 | };
67 |
--------------------------------------------------------------------------------
/src/utils/debounce.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 防抖函数
3 | * @param {Function} func - 要进行防抖处理的函数
4 | * @param {number} delay - 延迟时间,单位毫秒
5 | * @param {string} errorMessage - 重复触发时的提醒消息
6 | * @returns {Function} - 返回一个新的函数,该函数在指定的时间间隔内最多只会执行一次
7 | */
8 | const debounce = (func, delay, errorMessage) => {
9 | let timerId;
10 | let lastCallTime = 0;
11 |
12 | // 返回一个新的函数
13 | return (...args) => {
14 | const currentTime = Date.now();
15 | const elapsed = currentTime - lastCallTime;
16 |
17 | // 如果两次触发时间间隔小于 delay,弹出提醒
18 | if (elapsed < delay && errorMessage) {
19 | $message.warning(errorMessage);
20 | return;
21 | }
22 |
23 | // 清除上一个定时器
24 | clearTimeout(timerId);
25 |
26 | // 设置新的定时器,在指定的延迟时间后执行函数
27 | timerId = setTimeout(() => {
28 | func.apply(this, args);
29 | lastCallTime = Date.now();
30 | }, delay);
31 | };
32 | };
33 |
34 | export default debounce;
35 |
--------------------------------------------------------------------------------
/src/utils/formRules.js:
--------------------------------------------------------------------------------
1 | // 表单规则
2 | const formRules = () => {
3 | return {
4 | // 普通验证
5 | textRule: {
6 | required: true,
7 | message: "请填写必要信息",
8 | trigger: "blur",
9 | },
10 | // 数字验证
11 | numberRule: {
12 | type: "number",
13 | required: true,
14 | message: "请填写必要信息",
15 | trigger: "blur",
16 | },
17 | // 邮箱验证
18 | emailRule: {
19 | required: true,
20 | validator(_, value) {
21 | if (!value) {
22 | return new Error("请输入电子邮箱");
23 | } else if (
24 | !/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
25 | value,
26 | )
27 | ) {
28 | return new Error("请输入正确的电子邮箱");
29 | }
30 | return true;
31 | },
32 | trigger: ["input", "blur"],
33 | },
34 | // 手机号验证
35 | mobileRule: {
36 | key: "phone",
37 | required: true,
38 | validator(_, value) {
39 | if (!value) {
40 | return new Error("请输入手机号码");
41 | } else if (!/^(?:(?:\+|00)86)?1[3-9]\d{9}$/.test(value)) {
42 | return new Error("请输入正确的手机号码");
43 | }
44 | return true;
45 | },
46 | trigger: ["input", "blur"],
47 | },
48 | };
49 | };
50 |
51 | export { formRules };
52 |
--------------------------------------------------------------------------------
/src/utils/globalEvents.js:
--------------------------------------------------------------------------------
1 | import { checkPlatform } from "@/utils/helper";
2 | import { playOrPause, changePlayIndex } from "@/utils/Player";
3 | import { siteStatus } from "@/stores";
4 |
5 | /**
6 | * 全局事件
7 | * @param {import('vue-router').Router} router - router
8 | */
9 | const globalEvents = (router) => {
10 | if (!checkPlatform.electron()) return false;
11 | // 显示播放器
12 | electron.ipcRenderer.on("showPlayer", () => {
13 | const status = siteStatus();
14 | if (status.playMode === "dj") return false;
15 | status.showFullPlayer = true;
16 | });
17 | // 关闭播放器
18 | electron.ipcRenderer.on("closePlayer", () => {
19 | const status = siteStatus();
20 | if (status.playMode === "dj") return false;
21 | status.showFullPlayer = false;
22 | });
23 | // 播放或暂停
24 | electron.ipcRenderer.on("playOrPause", () => {
25 | playOrPause();
26 | });
27 | // 上一曲或下一曲
28 | electron.ipcRenderer.on("playNextOrPrev", (_, val) => {
29 | changePlayIndex(val, true);
30 | });
31 | // 全局设置
32 | electron.ipcRenderer.on("open-setting", () => {
33 | if (router) router.push("/setting");
34 | const status = siteStatus();
35 | status.showFullPlayer = false;
36 | });
37 | };
38 |
39 | export default globalEvents;
40 |
--------------------------------------------------------------------------------
/src/utils/globalShortcut.js:
--------------------------------------------------------------------------------
1 | import { playOrPause, setVolume } from "@/utils/Player";
2 | import { siteStatus } from "@/stores";
3 |
4 | /**
5 | * 全局快捷键监听
6 | * @param {KeyboardEvent} e - 键盘事件对象
7 | * @param {import('vue-router').Router} router - router
8 | * @returns {boolean} - 如果事件对象不存在,则返回false
9 | */
10 | const globalShortcut = (e, router) => {
11 | if (!e) return false;
12 | e.preventDefault();
13 | e.stopPropagation();
14 |
15 | // 播放或暂停
16 | if (e.code === "Space") {
17 | if (e.target.tagName === "INPUT") return false;
18 | if (router.currentRoute.value.name === "videos-player") return false;
19 | playOrPause();
20 | }
21 |
22 | // 调整音量
23 | if (e.code === "ArrowUp" || e.code === "ArrowDown") {
24 | const status = siteStatus();
25 | const volume = status.playVolume;
26 | const delta = e.code === "ArrowUp" ? 0.1 : -0.1;
27 | const newVolume = Math.min(1, Math.max(0, volume + delta));
28 | setVolume(newVolume);
29 | status.playVolume = newVolume;
30 | }
31 | };
32 |
33 | export default globalShortcut;
34 |
--------------------------------------------------------------------------------
/src/utils/request.js:
--------------------------------------------------------------------------------
1 | import { checkPlatform } from "@/utils/helper";
2 | import { getCookie, isLogin } from "@/utils/auth";
3 | import axios from "axios";
4 |
5 | // 全局地址
6 | if (checkPlatform.electron()) {
7 | axios.defaults.baseURL = "/api";
8 | } else {
9 | axios.defaults.baseURL = import.meta.env["RENDERER_VITE_SERVER_URL"];
10 | }
11 |
12 | // 基础配置
13 | axios.defaults.timeout = 15000;
14 | axios.defaults.withCredentials = true;
15 |
16 | // 请求拦截
17 | axios.interceptors.request.use(
18 | (request) => {
19 | // 当请求的接口为特定地址时不附加cookie参数和realIP
20 | if (!request.url.startsWith('https://api.tunefree.fun/ncm/song/')) {
21 | if (!request.params) request.params = {};
22 | // 附加 cookie
23 | if (!request.noCookie && (isLogin() || getCookie("MUSIC_U") !== null)) {
24 | request.params.cookie = `MUSIC_U=${getCookie("MUSIC_U")}`;
25 | // 对于非 auth.sayqz.com 的请求,添加分号
26 | if (!request.url.startsWith('https://auth.sayqz.com')) {
27 | request.params.cookie += ';';
28 | }
29 | }
30 | // 附加 realIP
31 | if (!checkPlatform.electron()) request.params.realIP = "116.25.146.177";
32 | } else {
33 | // 对特定地址的请求,去除 cookie 和 realIP
34 | if (request.params && request.params.cookie) {
35 | delete request.params.cookie;
36 | }
37 | if (request.params && request.params.realIP) {
38 | delete request.params.realIP;
39 | }
40 | }
41 |
42 | // 去除 cookie
43 | if (request.noCookie) {
44 | request.params.noCookie = true;
45 | }
46 |
47 | // 发送请求
48 | return request;
49 | },
50 | (error) => {
51 | console.error("请求失败,请稍后重试");
52 | return Promise.reject(error);
53 | },
54 | );
55 |
56 | // 响应拦截
57 | axios.interceptors.response.use(
58 | (response) => {
59 | return response?.data;
60 | },
61 | (error) => {
62 | // 从错误对象中获取响应信息
63 | const response = error.response;
64 | // 断网处理
65 | if (!response) $canNotConnect(error);
66 | // 状态码处理
67 | switch (response?.status) {
68 | case 400:
69 | console.error("客户端错误:", response.status, response.statusText);
70 | // 执行客户端错误的处理逻辑
71 | break;
72 | case 401:
73 | console.error("未授权:", response.status, response.statusText);
74 | // 执行未授权的处理逻辑
75 | break;
76 | case 403:
77 | console.error("禁止访问:", response.status, response.statusText);
78 | // 执行禁止访问的处理逻辑
79 | break;
80 | case 404:
81 | console.error("未找到资源:", response.status, response.statusText);
82 | // 执行未找到资源的处理逻辑
83 | break;
84 | case 500:
85 | console.error("服务器错误:", response.status, response.statusText);
86 | // 执行服务器错误的处理逻辑
87 | break;
88 | default:
89 | // 处理其他状态码或错误条件
90 | console.error("未处理的错误:", error.message);
91 | }
92 | // 继续传递错误
93 | return Promise.reject(error);
94 | },
95 | );
96 |
97 | export default axios;
98 |
--------------------------------------------------------------------------------
/src/utils/throttle.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 节流函数
3 | * @param {Function} func - 要进行节流处理的函数
4 | * @param {number} delay - 延迟时间,单位毫秒
5 | * @param {string} errorMessage - 重复触发时的提醒消息
6 | * @returns {Function} - 返回一个新的函数,该函数在指定的时间间隔内最多只会执行一次
7 | */
8 | const throttle = (func, delay, errorMessage) => {
9 | let isThrottled = false;
10 |
11 | // 返回一个新的函数
12 | return (...args) => {
13 | if (!isThrottled) {
14 | isThrottled = true;
15 | func.apply(this, args);
16 | setTimeout(() => {
17 | isThrottled = false;
18 | }, delay);
19 | } else if (errorMessage) {
20 | $message.warning(errorMessage);
21 | }
22 | };
23 | };
24 |
25 | export default throttle;
26 |
--------------------------------------------------------------------------------
/src/utils/userSignIn.js:
--------------------------------------------------------------------------------
1 | import { userDailySignin } from "@/api/user";
2 | import { siteSettings } from "@/stores";
3 | import { isLogin } from "@/utils/auth";
4 |
5 | /**
6 | * 用户签到
7 | * https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1387
8 | * 云贝签到本质上就是 Android 客户端每日签到
9 | */
10 | const userSignIn = async () => {
11 | const settings = siteSettings();
12 | try {
13 | if (!isLogin()) return false;
14 | const today = new Date().toLocaleDateString();
15 | const lastSignInDate = sessionStorage.getItem("lastSignInDate");
16 | if (lastSignInDate !== today) {
17 | const result = await userDailySignin(1);
18 | console.log("签到结果:", result);
19 | sessionStorage.setItem("lastSignInDate", today);
20 | if (result.status === 400) {
21 | return console.log("重复签到");
22 | }
23 | $notification["success"]({
24 | content: "签到通知",
25 | meta: "🎉 每日签到成功",
26 | duration: 3000,
27 | });
28 | } else {
29 | console.log("今日已签到");
30 | }
31 | } catch (error) {
32 | if (error.request.status === 400) {
33 | console.log("重复签到");
34 | sessionStorage.setItem("lastSignInDate", new Date().toLocaleDateString());
35 | return false;
36 | }
37 | settings.autoSignIn = false;
38 | console.error("签到过程中发生错误:", error);
39 | $notification["error"]({
40 | content: "签到通知",
41 | meta: "签到过程中发生错误,已关闭自动签到,详细信息可查看控制台输出,请及时向开发者报告",
42 | duration: 8000,
43 | });
44 | }
45 | };
46 |
47 | export default userSignIn;
48 |
--------------------------------------------------------------------------------
/src/views/Artist/albums.vue:
--------------------------------------------------------------------------------
1 |
2 |
24 |
25 |
26 |
93 |
--------------------------------------------------------------------------------
/src/views/Artist/hot.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 热门歌曲
5 |
6 | 查看全部
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
63 |
64 |
85 |
--------------------------------------------------------------------------------
/src/views/Artist/songs.vue:
--------------------------------------------------------------------------------
1 |
2 |
24 |
25 |
26 |
95 |
--------------------------------------------------------------------------------
/src/views/Artist/videos.vue:
--------------------------------------------------------------------------------
1 |
2 |
24 |
25 |
26 |
100 |
--------------------------------------------------------------------------------
/src/views/DailySongs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
每日推荐
6 |
7 |
8 |
9 | 根据你的音乐口味 ·
10 | {{ showTime && updatedTime ? "更新于 " + updatedTime : "每日 6:00 更新" }}
11 |
12 |
13 |
14 |
15 |
16 |
26 |
27 |
28 |
29 |
30 |
31 | 播放全部
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
103 |
104 |
131 |
--------------------------------------------------------------------------------
/src/views/Discover/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 发现音乐
5 |
6 |
7 | 歌单
8 | 排行榜
9 | 歌手
10 | 最新音乐
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
45 |
46 |
59 |
--------------------------------------------------------------------------------
/src/views/Discover/toplists.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 官方榜
6 |
13 |
14 |
15 | 全球榜
16 |
17 |
18 |
19 |
20 |
21 |
59 |
--------------------------------------------------------------------------------
/src/views/Dj/type.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 返回全部
12 |
13 | {{ djName }}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
60 |
61 |
79 |
--------------------------------------------------------------------------------
/src/views/History.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 最近播放
6 | 共 {{ historyPlaylist?.length || 0 }} 首
7 |
8 |
9 |
10 |
20 |
21 |
22 | 最多展示 500 条播放历史
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
50 |
51 |
80 |
--------------------------------------------------------------------------------
/src/views/Like/albums.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
15 |
16 |
17 |
18 |
19 |
26 |
--------------------------------------------------------------------------------
/src/views/Like/artists.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
15 |
16 |
17 |
18 |
19 |
26 |
--------------------------------------------------------------------------------
/src/views/Like/djs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
15 |
16 |
17 |
18 |
19 |
26 |
--------------------------------------------------------------------------------
/src/views/Like/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
我的收藏
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | 张专辑
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 个歌单
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | 位歌手
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | 个视频
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | 个电台
45 |
46 |
47 |
48 |
49 | 专辑
50 | 歌单
51 | 歌手
52 | 视频
53 | 电台
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
92 |
93 |
130 |
--------------------------------------------------------------------------------
/src/views/Like/playlists.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 | {{ item }}
16 |
17 |
18 |
19 |
20 |
21 |
26 |
27 |
28 |
33 |
34 |
35 |
36 |
43 |
44 |
45 |
46 |
47 |
79 |
80 |
108 |
--------------------------------------------------------------------------------
/src/views/Like/videos.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
15 |
16 |
17 |
18 |
19 |
26 |
--------------------------------------------------------------------------------
/src/views/Local/artists.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | 未找到歌手
34 |
35 |
36 |
37 |
110 |
111 |
143 |
--------------------------------------------------------------------------------
/src/views/Local/songs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
18 |
--------------------------------------------------------------------------------
/src/views/Search/albums.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
106 |
--------------------------------------------------------------------------------
/src/views/Search/artists.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
89 |
--------------------------------------------------------------------------------
/src/views/Search/djs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
107 |
--------------------------------------------------------------------------------
/src/views/Search/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ searchKeywords }}
7 | 的相关搜索
8 |
9 |
10 |
11 | 单曲
12 | 歌手
13 | 专辑
14 | 歌单
15 | 视频
16 | 电台
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 参数不完整
30 |
31 | 返回上一页
32 |
33 |
34 |
35 |
36 |
67 |
68 |
87 |
--------------------------------------------------------------------------------
/src/views/Search/playlists.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
107 |
--------------------------------------------------------------------------------
/src/views/Search/songs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
107 |
--------------------------------------------------------------------------------
/src/views/Search/videos.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
107 |
--------------------------------------------------------------------------------
/src/views/Song.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 单曲页面 - 待完成
5 | {{ songDetail }}
6 |
7 |
8 |
9 |
46 |
--------------------------------------------------------------------------------
/src/views/State/403.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 返回上一级
5 |
6 |
7 |
8 |
9 |
14 |
--------------------------------------------------------------------------------
/src/views/State/404.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 返回上一级
5 |
6 |
7 |
8 |
9 |
14 |
--------------------------------------------------------------------------------
/src/views/State/500.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 重新载入
5 |
6 |
7 |
8 |
9 |
14 |
--------------------------------------------------------------------------------
/src/views/Test.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 测试页面
4 |
5 |
6 | {{ status.spectrumsData }}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
70 |
71 |
93 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "rewrites": [{ "source": "/:path*", "destination": "/index.html" }]
3 | }
4 |
--------------------------------------------------------------------------------