├── .gitignore ├── README.md ├── babel.config.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── api │ ├── album.js │ ├── comment.js │ ├── discover.js │ ├── login.js │ ├── message.js │ ├── music.js │ ├── playlist.js │ ├── radio.js │ ├── search.js │ ├── singer.js │ ├── songs.js │ ├── user.js │ └── video.js ├── assets │ ├── iconfont │ │ ├── iconfont.css │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 │ ├── images │ │ ├── 20171122191630_ff8fef.webp │ │ ├── cover_play.png │ │ ├── loading.gif │ │ ├── logo_dark.png │ │ ├── logo_light.png │ │ ├── singer_300.png │ │ ├── song_300.png │ │ ├── vinyl-arm.png │ │ ├── vinyl-record.png │ │ └── wave.gif │ └── style │ │ └── common.less ├── components │ ├── app-add-playlist.vue │ ├── app-banner.vue │ ├── app-control.vue │ ├── app-header.vue │ ├── app-icon.vue │ ├── app-login.vue │ ├── app-mask.vue │ ├── app-more-actions.vue │ ├── app-more.vue │ ├── app-play-order.vue │ ├── app-progress.vue │ ├── app-search-box.vue │ ├── app-sidebar.vue │ ├── app-sound.vue │ └── library │ │ ├── music-comment.vue │ │ ├── music-dialog.vue │ │ ├── music-list.vue │ │ ├── music-message-box.vue │ │ ├── music-message.vue │ │ ├── music-mv-list.vue │ │ ├── music-play-count.vue │ │ ├── music-playlist-description.vue │ │ ├── music-playlist-list.vue │ │ ├── music-skeleton.vue │ │ ├── music-tabs.vue │ │ ├── music-title-child.vue │ │ ├── music-title.vue │ │ └── music-video-list.vue ├── hooks │ ├── useDraggable.js │ ├── useNumberSwitch.js │ ├── useProgress.js │ └── useSearch.js ├── main.js ├── plugin │ └── index.js ├── router │ └── index.js ├── store │ ├── index.js │ └── modules │ │ ├── playlist.js │ │ ├── song.js │ │ ├── user.js │ │ └── video.js ├── utils │ ├── bus.js │ ├── cookie.js │ ├── history.js │ ├── historyPlay.js │ ├── message-box.js │ ├── message.js │ ├── request.js │ ├── tidy.js │ ├── usePosition.js │ └── user.js └── views │ ├── Album │ ├── components │ │ ├── album-comment.vue │ │ └── album-list.vue │ └── index.vue │ ├── Audio │ └── index.vue │ ├── Audition │ └── index.vue │ ├── Comment │ └── index.vue │ ├── Discover │ ├── components │ │ ├── discover-mv.vue │ │ ├── discover-new-playlist.vue │ │ └── discover-new-song.vue │ └── index.vue │ ├── Layout.vue │ ├── Like │ ├── components │ │ ├── like-album.vue │ │ ├── like-song.vue │ │ └── like-video.vue │ └── index.vue │ ├── Local │ └── index.vue │ ├── Lyrics │ ├── components │ │ ├── lyrics-bac.vue │ │ ├── lyrics-disc.vue │ │ ├── lyrics-header.vue │ │ ├── lyrics-progress.vue │ │ ├── lyrics-songs.vue │ │ └── lyrics-video-visible.vue │ └── index.vue │ ├── Message │ ├── MessageAt │ │ └── index.vue │ ├── MessageComment │ │ └── index.vue │ ├── MessagePrivate │ │ ├── components │ │ │ ├── private-content.vue │ │ │ └── private-list.vue │ │ └── index.vue │ ├── MessageSystem │ │ └── index.vue │ ├── components │ │ └── message-notice.vue │ └── index.vue │ ├── Music │ ├── MusicAlbum │ │ ├── components │ │ │ ├── album-all-list.vue │ │ │ └── album-new-list.vue │ │ └── index.vue │ ├── MusicPicked │ │ ├── components │ │ │ ├── picked-exclusive.vue │ │ │ ├── picked-program.vue │ │ │ └── picked-radar.vue │ │ └── index.vue │ ├── MusicPlaylist │ │ ├── components │ │ │ ├── playlist-filters.vue │ │ │ └── playlist-list.vue │ │ └── index.vue │ ├── MusicRadio │ │ ├── components │ │ │ ├── radio-anchor.vue │ │ │ ├── radio-banner.vue │ │ │ ├── radio-hot.vue │ │ │ ├── radio-personality.vue │ │ │ └── radio-program.vue │ │ └── index.vue │ ├── MusicRanking │ │ ├── components │ │ │ ├── ranking-artist.vue │ │ │ ├── ranking-feature-list.vue │ │ │ └── ranking-general-list.vue │ │ └── index.vue │ ├── MusicSinger │ │ ├── components │ │ │ ├── singer-filters.vue │ │ │ └── singer-list.vue │ │ └── index.vue │ └── index.vue │ ├── Player │ ├── components │ │ ├── player-comment.vue │ │ ├── player-detail.vue │ │ ├── player-play-video.vue │ │ └── player-related.vue │ └── index.vue │ ├── Playlist │ ├── components │ │ ├── playlist-comment.vue │ │ └── playlist-list.vue │ └── index.vue │ ├── Radio │ ├── RadioPlaylist │ │ └── index.vue │ └── index.vue │ ├── Recently │ ├── components │ │ ├── recently-song.vue │ │ └── recently-video.vue │ └── index.vue │ ├── Search │ ├── components │ │ ├── search-album.vue │ │ ├── search-multi-match.vue │ │ ├── search-playlist.vue │ │ ├── search-singer.vue │ │ ├── search-songs.vue │ │ └── search-video.vue │ └── index.vue │ ├── Singer │ ├── components │ │ ├── singer-album.vue │ │ ├── singer-description.vue │ │ ├── singer-select.vue │ │ ├── singer-songs.vue │ │ └── singer-video.vue │ └── index.vue │ ├── User │ ├── UserHome │ │ ├── components │ │ │ ├── user-description.vue │ │ │ ├── user-like.vue │ │ │ └── user-playlist.vue │ │ └── index.vue │ └── index.vue │ └── Video │ ├── MvList │ ├── components │ │ ├── mv-new.vue │ │ ├── mv-official.vue │ │ └── mv-ranking.vue │ └── index.vue │ ├── VideoList │ ├── components │ │ └── video-tabs.vue │ └── index.vue │ └── index.vue └── vue.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | 6 | 7 |

8 | 9 | ## Vue3 Music 10 | 11 | ### 项目简介 12 | 13 | 项目基于 Vue3 全家桶开发的 QQ 音乐播放器,项目界面模仿 QQ 音乐 Windows 客户端。 14 | 15 | 后端 API [Binaryify/NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi) 16 | 17 | 界面适配 PC+iPad;移动端未适配 18 | 19 | 如果觉得项目不错的话欢迎 star 20 | 21 | 项目线上地址[http://47.102.197.149](http://47.102.197.149) 22 | 23 | **注**: 24 | 25 | - 部分音乐由于版权问题无法播放 26 | - 频繁关/取关注歌手或用户会导致风控,解决方法是登录 PC 端网易云扫码验证解决 27 | - 雷达(电台)无法播放,请求接口提示无版权,有小伙伴有解决办法的欢迎 lssues 或 pull request 28 | - 评论区回复接口返回的数据太复杂,等有时间再做 29 | - 视频小窗目前只支持拖动位置,暂不支持暂停/播放/修改进度 30 | - **项目有任何问题或 bug 欢迎提出 lssues** 31 | 32 | ### 快速导航 33 | 34 | - [Vue3 Music](#Vue3Music) 35 | - [项目简介](#项目简介) 36 | - [快速上手](#快速上手) 37 | - [进度](#进度) 38 | - [技术栈](#技术栈) 39 | - [项目预览](#项目预览) 40 | - [如何贡献](#如何贡献) 41 | - [之后的方向](#之后的方向) 42 | - [鸣谢](#鸣谢) 43 | 44 | ### 快速上手 45 | 46 | 安装 47 | 48 | ``` 49 | $ git clone https://github.com/xyyfun/music.git 50 | $ cd vue-music 51 | $ npm install 52 | ``` 53 | 54 | 运行 55 | 56 | ``` 57 | $ npm run serve 58 | ``` 59 | 60 | 打包部署 61 | 62 | ``` 63 | $ npm run build 64 | ``` 65 | 66 | ### 进度 67 | 68 | - [x] 推荐页 69 | - [x] 音乐馆 70 | 71 | - [x] 精选 72 | - [x] 有声电台 73 | - [x] 排行 74 | - [x] 歌手 75 | - [x] 分类歌单 76 | - [x] 数字专辑 77 | 78 | - [x] 视频 79 | - [x] 视频/MV 播放 80 | - [ ] 雷达 81 | - [x] 我喜欢 82 | - [ ] 本地下载 83 | - [x] 最近播放 84 | - [ ] 试听列表 85 | - [x] 用户歌单 86 | - [x] 登录 87 | - [x] 用户 88 | 89 | - [x] 用户喜欢 90 | - [x] 用户歌单 91 | 92 | - [x] 歌词播放器 93 | - [x] 歌单详情 94 | - [x] 歌单/专辑/电台评论 95 | - [x] 播放列表 96 | - [x] 歌手详情 97 | - [x] 深色模式 98 | - [x] 搜索 99 | - [x] 通知 100 | - [x] 私信 101 | - [x] 评论 102 | - [x] @我 103 | - [x] 通知 104 | 105 | ### 技术栈 106 | 107 | - **_Vue3 全家桶_** 108 | - **_vueuse_** 第三方工具库 109 | - **_axios_** 请求工具 110 | - **_mitt_** 组件通信 111 | - **_vue-lazyload_** 图片懒加载 112 | - **_lodash_** 第三方工具库 113 | 114 | ### 项目预览 115 | 116 | 117 | | 浅色 | 深色 | 118 | |:------:|:------:| 119 | |![image](https://s1.ax1x.com/2023/07/11/pCfkkwV.png)|![image](https://s1.ax1x.com/2023/07/11/pCfkAoT.png)| 120 | |![image](https://s1.ax1x.com/2023/07/11/pCfk1w6.png)|![image](https://s1.ax1x.com/2023/07/11/pCfklex.png)| 121 | |![image](https://s1.ax1x.com/2023/07/11/pCfk400.png)|![image](https://s1.ax1x.com/2023/07/11/pCfkhmq.png)| 122 | |![image](https://s1.ax1x.com/2023/07/11/pCfkOXR.png)|![image](https://s1.ax1x.com/2023/07/11/pCfkLc9.png)| 123 | |![image](https://s1.ax1x.com/2023/07/11/pCfA1js.png)|![image](https://s1.ax1x.com/2023/07/11/pCfAlcj.png)| 124 | |![image](https://s1.ax1x.com/2023/07/11/pCfAJH0.png)|![image](https://s1.ax1x.com/2023/07/11/pCfAtEV.png)| 125 | |![image](https://s1.ax1x.com/2023/07/11/pCfAw34.png)|![image](https://s1.ax1x.com/2023/07/11/pCfAdCF.png)| 126 | |![image](https://s1.ax1x.com/2023/07/11/pCfECV0.png)|![image](https://s1.ax1x.com/2023/07/11/pCfEpbq.png)| 127 | |![image](https://s1.ax1x.com/2023/07/12/pCfVdX9.png)|![image](https://s1.ax1x.com/2023/07/12/pCfVa6J.png)| 128 | 129 | ### 如何贡献 130 | 131 | 非常欢迎您的加入![提一个 lssue](https://github.com/xyyfun/music/issues)或提交一个[pull request](https://github.com/xyyfun/music/pulls) 132 | 133 | ### 之后的方向 134 | 135 | - 适配深色模式(已完成) 136 | - 根据当前用户网络状况加载对应大小预览图 137 | - 视频小窗(已完成) 138 | - 回复评论&楼层评论 139 | - 电台播放 140 | - ... 141 | 142 | ### 鸣谢 143 | 144 | 感谢[Binaryify/NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi)提供接口服务 145 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "baseUrl": "./", 6 | "moduleResolution": "node", 7 | "paths": { 8 | "@/*": [ 9 | "src/*" 10 | ] 11 | }, 12 | "lib": [ 13 | "esnext", 14 | "dom", 15 | "dom.iterable", 16 | "scripthost" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-music", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@vueuse/core": "^9.13.0", 12 | "axios": "^1.3.4", 13 | "core-js": "^3.8.3", 14 | "less": "^4.1.3", 15 | "less-loader": "^11.1.0", 16 | "lodash": "^4.17.21", 17 | "mitt": "^3.0.0", 18 | "vue": "^3.2.13", 19 | "vue-lazyload": "^3.0.0-rc.2", 20 | "vue-router": "^4.1.6", 21 | "vuex": "^4.1.0" 22 | }, 23 | "devDependencies": { 24 | "@babel/core": "^7.12.16", 25 | "@babel/eslint-parser": "^7.12.16", 26 | "@vue/cli-plugin-babel": "~5.0.0", 27 | "@vue/cli-plugin-eslint": "~5.0.0", 28 | "@vue/cli-service": "~5.0.0", 29 | "eslint": "^7.32.0", 30 | "eslint-plugin-vue": "^8.0.3" 31 | }, 32 | "eslintConfig": { 33 | "root": true, 34 | "env": { 35 | "node": true 36 | }, 37 | "extends": [ 38 | "plugin:vue/vue3-essential", 39 | "eslint:recommended" 40 | ], 41 | "parserOptions": { 42 | "parser": "@babel/eslint-parser" 43 | }, 44 | "rules": {} 45 | }, 46 | "browserslist": [ 47 | "> 1%", 48 | "last 2 versions", 49 | "not dead", 50 | "not ie 11" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyyfun/music/cf68bb624057b7118817f1dad001d7d2b40b9915/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | QQ音乐-千万正版音乐海量无损曲库新歌热歌天天畅听的高品质音乐平台! 9 | 10 | 11 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/api/album.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | /** 4 | * @Date : 2023-03-26 20:21:56 5 | * @description : 获取新碟上架 6 | * @return {*} 7 | */ 8 | export const getNewAlbum = () => request({ url: '/album/newest', method: 'get' }); 9 | 10 | /** 11 | * @Date : 2023-03-26 20:03:10 12 | * @description : 获取新碟上架列表 13 | * @return {*} 14 | */ 15 | export const getTopAlbum = (area, offset) => 16 | request({ url: `/album/new?area=${area}&limit=50&offset=${(offset - 1) * 50}`, method: 'get' }); 17 | 18 | /** 19 | * @Date : 2023-04-03 21:27:06 20 | * @description : 获取专辑详情 21 | * @param {*} id: 22 | * @return {*} 23 | */ 24 | 25 | export const getAlbumData = id => { 26 | return request({ 27 | url: `/album?id=${id}`, 28 | }); 29 | }; 30 | 31 | /** 32 | * @Date : 2023-05-05 17:13:47 33 | * @description : 收藏/取消收藏专辑 34 | * @param {*} id: 35 | * @param {*} t: 36 | * @return {*} 37 | */ 38 | export const albumSubscribe = (id, t) => 39 | request({ url: `/album/sub?t=${t}&id=${id}×tamp=${Date.now()}` }); 40 | 41 | /** 42 | * @Date : 2023-05-05 17:30:43 43 | * @description : 获取用户收藏的专辑列表 44 | * @return {*} 45 | */ 46 | export const getUserCollectAlbum = () => request({ url: '/album/sublist' }); 47 | -------------------------------------------------------------------------------- /src/api/comment.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | /** 4 | * @Date : 2023-04-12 23:21:40 5 | * @description : 获取评论 6 | * @param {*} type:评论类型,0歌曲,1mv,2歌单,3专辑,4电台节目,5视频,6动态,7电台 7 | * @param {*} id: 资源 id 8 | * @param {*} pageNo: 当前页 9 | * @param {*} sortType: 排序方式, 1:按推荐排序, 2:按热度排序, 3:按时间排序 10 | * @return {*} 11 | */ 12 | export const getComment = (id, type, pageNo) => { 13 | return request({ 14 | url: `/comment/new?id=${id}&type=${type}&sortType=1&pageSize=20&pageNo=${pageNo}`, 15 | method: 'get', 16 | }); 17 | }; 18 | 19 | /** 20 | * @Date : 2023-04-22 15:16:17 21 | * @description : 获取热门评论 22 | * @param {*} id: 23 | * @param {*} type::评论类型,0歌曲,1mv,2歌单,3专辑,4电台节目,5视频,6动态,7电台 24 | * @param {*} offset: 分页 25 | * @return {*} 26 | */ 27 | export const getHotComment = (id, type, offset) => { 28 | return request({ 29 | url: `/comment/hot?id=${id}&type=${type}&limit=20&offset=${offset}`, 30 | }); 31 | }; 32 | 33 | /** 34 | * @Date : 2023-04-12 23:52:15 35 | * @description : 获取楼层评论 36 | * @param {*} id: 37 | * @param {*} parentCommentId: 38 | * @param {*} type: 39 | * @return {*} 40 | */ 41 | export const getFloorComment = (id, parentCommentId, type) => { 42 | return request({ 43 | url: `/comment/floor?parentCommentId=${parentCommentId}&id=${id}&type=${type}`, 44 | method: 'get', 45 | }); 46 | }; 47 | 48 | /** 49 | * @Date : 2023-04-23 16:15:33 50 | * @description : 获取歌曲评论 51 | * @param {*} id: 52 | * @param {*} offset: 53 | * @return {*} 54 | */ 55 | export const getSongComment = (id, offset) => { 56 | return request({ 57 | url: `/comment/music?id=${id}&limit=20&offset=${(offset - 1) * 20}`, 58 | }); 59 | }; 60 | 61 | /** 62 | * @Date : 2023-04-23 16:40:14 63 | * @description : 获取歌单评论 64 | * @param {*} id: 65 | * @param {*} offset: 66 | * @return {*} 67 | */ 68 | export const getPlaylistComment = (id, offset) => { 69 | return request({ 70 | url: `/comment/playlist?id=${id}&limit=20&offset=${(offset - 1) * 20}`, 71 | }); 72 | }; 73 | 74 | /** 75 | * @Date : 2023-04-23 17:14:35 76 | * @description : 获取专辑评论 77 | * @param {*} id: 78 | * @param {*} offset: 79 | * @return {*} 80 | */ 81 | export const getAlbumComment = (id, offset) => { 82 | return request({ 83 | url: `/comment/album?id=${id}&limit=20&offset=${(offset - 1) * 20}`, 84 | }); 85 | }; 86 | 87 | /** 88 | * @Date : 2023-04-23 18:34:36 89 | * @description : 获取视频评论 90 | * @param {*} id: 91 | * @param {*} offset: 92 | * @return {*} 93 | */ 94 | export const getVideoComment = (id, offset) => { 95 | return request({ 96 | url: `/comment/video?id=${id}&limit=20&offset=${(offset - 1) * 20}`, 97 | }); 98 | }; 99 | 100 | /** 101 | * @Date : 2023-04-23 18:41:12 102 | * @description : 获取mv评论 103 | * @param {*} id: 104 | * @param {*} offset: 105 | * @return {*} 106 | */ 107 | export const getMvComment = (id, offset) => { 108 | return request({ 109 | url: `/comment/mv?id=${id}&limit=20&offset=${(offset - 1) * 20}`, 110 | }); 111 | }; 112 | 113 | /** 114 | * @Date : 2023-05-06 10:44:20 115 | * @description : 给评论点赞 116 | * @param {*} id:资源id 117 | * @param {*} cid:评论id 118 | * @param {*} t:是否点赞 1 为点赞 ,0 为取消点赞 119 | * @param {*} type:评论类型,0歌曲,1mv,2歌单,3专辑,4电台节目,5视频,6动态,7电台 120 | * @return {*} 121 | */ 122 | export const commentLike = ({ id, cid, t, type }) => { 123 | return request({ url: `/comment/like?id=${id}&cid=${cid}&t=${t}&type=${type}` }); 124 | }; 125 | -------------------------------------------------------------------------------- /src/api/discover.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | /** 4 | * @Date : 2023-03-23 19:09:34 5 | * @description : 获取轮播图数据 6 | * @return {*} 7 | */ 8 | export const getBanner = () => request({ url: '/banner', method: 'get' }); 9 | 10 | /** 11 | * @Date : 2023-03-23 19:08:03 12 | * @description : 获取推荐歌单数据 13 | * @return {*} 14 | */ 15 | export const getRecommendPlaylist = () => { 16 | return request({ 17 | url: '/personalized?limit=10', 18 | method: 'get', 19 | }); 20 | }; 21 | 22 | /** 23 | * @Date : 2023-03-23 22:03:43 24 | * @description : 获取推荐新歌曲数据 25 | * @return {*} 26 | */ 27 | export const getRecommendNewSong = () => request({ url: '/personalized/newsong', method: 'get' }); 28 | 29 | /** 30 | * @Date : 2023-03-24 14:07:32 31 | * @description : 获取推荐MV数据 32 | * @return {*} 33 | */ 34 | export const getRecommendMV = () => request({ url: '/personalized/mv', method: 'get' }); 35 | -------------------------------------------------------------------------------- /src/api/login.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | /** 4 | * @Date : 2023-04-11 19:56:22 5 | * @description : 游客登录 6 | * @return {*} 7 | */ 8 | export const loginTourist = () => 9 | request({ url: `/register/anonimous?timestamp=${Date.now()}`, method: 'get' }); 10 | 11 | /** 12 | * @Date : 2023-04-12 14:27:49 13 | * @description : 生成二维码key 14 | * @return {*} 15 | */ 16 | export const QRkey = () => { 17 | return request({ 18 | url: `/login/qr/key?timestamp=${Date.now()}`, 19 | method: 'get', 20 | }); 21 | }; 22 | 23 | /** 24 | * @Date : 2023-04-12 14:29:10 25 | * @description : 根据key值生成二维码图片的 base64 和二维码信息 26 | * @param {*} key: 上方接口返回的key值 27 | * @return {*} 28 | */ 29 | export const QRbase = key => { 30 | return request({ 31 | url: `/login/qr/create?key=${key}&qrimg=true×tamp=${Date.now()}`, 32 | method: 'get', 33 | }); 34 | }; 35 | 36 | /** 37 | * @Date : 2023-04-12 14:33:02 38 | * @description : 查询二维码状态 39 | * @param {*} key:生成二维码key接口返回的key值 40 | * @return {*} 800 为二维码过期,801 为等待扫码,802 为待确认,803 为授权登录成功(803 状态码下会返回 cookies) 41 | */ 42 | export const QRstate = key => { 43 | return request({ 44 | url: `/login/qr/check?key=${key}&noCookie=true×tamp=${Date.now()}`, 45 | method: 'get', 46 | }); 47 | }; 48 | 49 | /** 50 | * @Date : 2023-04-12 18:07:00 51 | * @description : 退出登录 52 | * @return {*} 53 | */ 54 | export const logout = () => { 55 | return request({ 56 | url: `/logout?timestamp=${Date.now()}`, 57 | method: 'get', 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /src/api/message.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | /** 4 | * @Date : 2023-05-12 14:28:03 5 | * @description : 获取私人信息 6 | * @param {*} offset: 7 | * @return {*} 8 | */ 9 | export const getPrivateMessage = offset => { 10 | return request({ 11 | url: `/msg/private?limit=30&offset=${(offset - 1) * 30}×tamp=${Date.now()}`, 12 | }); 13 | }; 14 | 15 | /** 16 | * @Date : 2023-05-25 20:40:47 17 | * @description : 获取评论信息 18 | * @param {*} uid: 用户id (登录的用户id非评论者id) 19 | * @param {*} before:分页参数,取上一页最后一项的 time 获取下一页数据 20 | * @return {*} 21 | */ 22 | export const getCommentMessage = (uid, before = '') => { 23 | return request({ 24 | url: `/msg/comments?uid=${uid}&limit=30&before=${before}×tamp=${Date.now()}`, 25 | }); 26 | }; 27 | 28 | /** 29 | * @Date : 2023-05-25 21:39:32 30 | * @description : 获取at我的信息 31 | * @param {*} offset: 32 | * @return {*} 33 | */ 34 | export const getFormeMessage = offset => { 35 | return request({ 36 | url: `/msg/forwards?limit=10&offset=${(offset - 1) * 10}×tamp=${Date.now()}`, 37 | }); 38 | }; 39 | 40 | /** 41 | * @Date : 2023-05-25 22:29:49 42 | * @description : 获取通知信息 43 | * @return {*} 44 | */ 45 | export const getNoticeMessage = () => { 46 | return request({ 47 | url: `/msg/notices?limit=30×tamp=${Date.now()}`, 48 | }); 49 | }; 50 | 51 | /** 52 | * @Date : 2023-05-24 18:43:54 53 | * @description : 获取私信内容 54 | * @param {*} uid: 用户 id 55 | * @param {*} before:分页参数,取上一页最后一项的 time 获取下一页数据 56 | * @return {*} 57 | */ 58 | export const getPrivateContent = (uid, before = '') => { 59 | return request({ 60 | url: `/msg/private/history?uid=${uid}&limit=30&before=${before}×tamp=${Date.now()}`, 61 | }); 62 | }; 63 | 64 | /** 65 | * @Date : 2023-05-24 23:20:27 66 | * @description : 发送私信 67 | * @param {*} user_ids:用户id 68 | * @param {*} msg: 信息 69 | * @return {*} 70 | */ 71 | export const sendPrivate = (user_ids, msg) => { 72 | return request({ 73 | url: `/send/text?user_ids=${user_ids}&msg=${msg}×tamp=${Date.now()}`, 74 | }); 75 | }; 76 | -------------------------------------------------------------------------------- /src/api/music.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | /** 4 | * @Date : 2023-03-24 15:59:47 5 | * @description : 推荐节目 6 | * @return {*} 7 | */ 8 | export const getRecommendProgram = () => request({ url: '/program/recommend', method: 'get' }); 9 | 10 | /** 11 | * @Date : 2023-03-24 16:02:22 12 | * @description : 独家放送 13 | * @return {*} 14 | */ 15 | export const getExclusiveBroadcast = () => { 16 | return request({ 17 | url: '/personalized/privatecontent', 18 | method: 'get', 19 | }); 20 | }; 21 | 22 | /** 23 | * @Date : 2023-03-24 15:59:47 24 | * @description : 推荐电台 25 | * @return {*} 26 | */ 27 | export const getRecommendRadar = () => request({ url: '/personalized/djprogram', method: 'get' }); 28 | 29 | /** 30 | * @Date : 2023-03-25 13:57:24 31 | * @description : 获取所有榜单 32 | * @return {*} 33 | */ 34 | export const getAllList = () => request({ url: '/toplist/detail', method: 'get' }); 35 | 36 | /** 37 | * @Date : 2023-03-26 23:39:32 38 | * @description : 获取电台banner 39 | * @return {*} 40 | */ 41 | export const getRadioBanner = () => request({ url: '/dj/banner', method: 'get' }); 42 | 43 | /** 44 | * @Date : 2023-03-27 08:59:10 45 | * @description : 获取热门电台 46 | * @return {*} 47 | */ 48 | export const getRadioHot = () => request({ url: '/dj/hot?limit=10', method: 'get' }); 49 | 50 | /** 51 | * @Date : 2023-03-27 09:09:55 52 | * @description : 24小时节目榜 53 | * @return {*} 54 | */ 55 | export const getRadioProgram = () => 56 | request({ url: '/dj/program/toplist/hours?limit=10', method: 'get' }); 57 | 58 | /** 59 | * @Date : 2023-03-27 14:05:53 60 | * @description : 获取最热主播 61 | * @return {*} 62 | */ 63 | export const getRadioAnchor = () => request({ url: '/dj/toplist/popular?limit=30', method: 'get' }); 64 | 65 | /** 66 | * @Date : 2023-04-09 15:44:21 67 | * @description : 获取电台个性推荐 68 | * @return {*} 69 | */ 70 | export const getRadioPersonality = () => 71 | request({ url: '/dj/personalize/recommend', method: 'get' }); 72 | -------------------------------------------------------------------------------- /src/api/playlist.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | /** 4 | * @Date : 2023-03-27 23:32:00 5 | * @description : 获取歌单详情 6 | * @param {*} id: 7 | * @return {*} 8 | */ 9 | export const getPlaylistDetail = id => { 10 | return request({ 11 | url: `/playlist/detail?id=${id}`, 12 | method: 'get', 13 | }); 14 | }; 15 | 16 | /** 17 | * @Date : 2023-05-05 15:14:58 18 | * @description : 收藏/取消收藏歌单 19 | * @param {*} id:歌单id 20 | * @param {*} t: 类型,1:收藏,2:取消收藏 21 | * @return {*} 22 | */ 23 | export const playlistSubscribe = (id, t) => { 24 | return request({ 25 | url: `/playlist/subscribe?t=${t}&id=${id}×tamp=${Date.now()}`, 26 | }); 27 | }; 28 | 29 | /** 30 | * @Date : 2023-03-26 18:23:31 31 | * @description : 分类歌单 32 | * @param {*} before:分页参数,取上一页最后一个歌单的 updateTime 获取下一页数据 33 | * @param {*} cat:歌单分类 34 | * @return {*} 35 | */ 36 | export const getPlaylist = ({ before, cat }) => { 37 | return request({ 38 | url: `/top/playlist/highquality?limit=35&before=${before}&cat=${cat}`, 39 | method: 'get', 40 | }); 41 | }; 42 | 43 | /** 44 | * @Date : 2023-06-02 20:15:52 45 | * @description : 对歌单添加或删除歌曲 46 | * @param {*} op:从歌单增加单曲为 add, 删除为 del 47 | * @param {*} pid:歌单 id 48 | * @param {*} tracks:歌曲 id,可多个,用逗号隔开 49 | * @return {*} 50 | */ 51 | export const changPlaylist = (op, pid, tracks) => { 52 | return request({ 53 | url: `/playlist/tracks?op=${op}&pid=${pid}&tracks=${tracks}×tamp=${Date.now()}`, 54 | }); 55 | }; 56 | -------------------------------------------------------------------------------- /src/api/radio.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | /** 4 | * @Date : 2023-04-06 21:34:12 5 | * @description : 获取电台详情 6 | * @param {*} rid: 7 | * @return {*} 8 | */ 9 | export const djDetail = rid => { 10 | return request({ 11 | url: `/dj/detail?rid=${rid}`, 12 | method: 'get', 13 | }); 14 | }; 15 | 16 | /** 17 | * @Date : 2023-05-23 18:21:23 18 | * @description : 获取电台节目列表 19 | * @param {*} rid: 20 | * @param {*} offset: 21 | * @return {*} 22 | */ 23 | export const djList = (rid, offset) => { 24 | return request({ 25 | url: `/dj/program?rid=${rid}&limit=30&offset=${(offset - 1) * 30}`, 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /src/api/search.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | /** 4 | * @Date : 2023-04-04 23:18:12 5 | * @description : 搜索 6 | * @param {*} keywords:关键字 7 | * @param {*} type:搜索类型默认为1 1单曲10专辑100歌手1000歌单1002用户1004MV1006歌词1009电台1014视频1018综合 8 | * @return {*} 9 | */ 10 | export const search = (keywords, type, offset) => { 11 | return request({ 12 | url: `/cloudsearch?keywords=${keywords}&limit=50&type=${type}&offset=${ 13 | (offset - 1) * 50 14 | }×tamp=${Date.now()}`, 15 | method: 'get', 16 | }); 17 | }; 18 | 19 | /** 20 | * @Date : 2023-04-04 23:20:03 21 | * @description : 默认关键字 22 | * @return {*} 23 | */ 24 | export const defaultKeyword = () => { 25 | return request({ 26 | url: '/search/default', 27 | method: 'get', 28 | }); 29 | }; 30 | 31 | /** 32 | * @Date : 2023-04-06 18:06:39 33 | * @description : 获取热搜列表 34 | * @return {*} 35 | */ 36 | export const searchHot = () => { 37 | return request({ 38 | url: '/search/hot/detail', 39 | method: 'get', 40 | }); 41 | }; 42 | 43 | /** 44 | * @Date : 2023-04-05 20:34:11 45 | * @description : 搜索建议 46 | * @param {*} keyword: 47 | * @return {*} 48 | */ 49 | export const searchSuggest = keyword => { 50 | return request({ 51 | url: `/search/suggest?keywords=${keyword}×tamp=${Date.now()}`, 52 | method: 'get', 53 | }); 54 | }; 55 | 56 | /** 57 | * @Date : 2023-05-07 21:50:41 58 | * @description : 搜索多重匹配 59 | * @param {*} keywords: 60 | * @return {*} 61 | */ 62 | export const searchMultiMatch = keywords => { 63 | return request({ 64 | url: `/search/multimatch?keywords=${keywords}×tamp=${Date.now()}`, 65 | }); 66 | }; 67 | -------------------------------------------------------------------------------- /src/api/singer.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | /** 4 | * @Date : 2023-04-03 09:33:50 5 | * @description : 获得歌手信息 6 | * @param {*} id: 7 | * @return {*} 8 | */ 9 | export const getSingerDetail = id => { 10 | return request({ 11 | url: `/artist/detail?id=${id}`, 12 | method: 'get', 13 | }); 14 | }; 15 | 16 | /** 17 | * @Date : 2023-04-23 21:07:02 18 | * @description : 获取歌手热门50首歌曲 19 | * @param {*} id: 20 | * @return {*} 21 | */ 22 | export const getSingerHotSong = id => { 23 | return request({ 24 | url: `/artist/top/song?id=${id}`, 25 | }); 26 | }; 27 | 28 | /** 29 | * @Date : 2023-04-12 19:10:16 30 | * @description : 获取歌手所有歌曲 31 | * @param {*} id: 32 | * @param {*} offset: 33 | * @return {*} 34 | */ 35 | export const getSingerAllSongs = (id, offset) => { 36 | return request({ 37 | url: `/artist/songs?id=${id}&limit=50&offset=${(offset - 1) * 50}`, 38 | method: 'get', 39 | }); 40 | }; 41 | 42 | /** 43 | * @Date : 2023-04-03 14:57:52 44 | * @description : 获取歌手专辑 45 | * @param {*} id: 46 | * @param {*} num:获取数 47 | * @return {*} 48 | */ 49 | export const getSingerAlbum = (id, offset) => { 50 | return request({ 51 | url: `/artist/album?id=${id}&limit=30&offset=${(offset - 1) * 30}`, 52 | method: 'get', 53 | }); 54 | }; 55 | 56 | /** 57 | * @Date : 2023-04-03 20:26:19 58 | * @description : 获取歌手mv 59 | * @param {*} id: 60 | * @param {*} limit:请求数量 61 | * @return {*} 62 | */ 63 | export const getSingerMV = (id, offset) => { 64 | return request({ 65 | url: `/artist/mv?id=${id}&limit=30&offset=${(offset - 1) * 30}`, 66 | method: 'get', 67 | }); 68 | }; 69 | 70 | /** 71 | * @Date : 2023-03-26 14:12:13 72 | * @description : 歌手过滤 73 | * @param {Number} type: 1:男歌手 2:女歌手 3:乐队组合 74 | * @param {Number} area: 0:其他 7:华语 8:日本 16:韩国 96:欧美 75 | * @param {String} initial: 首字母索引 76 | * @param {Number} offset: 多少数据 77 | * @return {*} 78 | */ 79 | export const getSinger = ({ type, area, initial, offset }) => { 80 | return request({ 81 | url: `/artist/list?type=${type}&area=${area}&initial=${initial}&limit=60&offset=${ 82 | (offset - 1) * 60 83 | }`, 84 | method: 'get', 85 | }); 86 | }; 87 | 88 | /** 89 | * @Date : 2023-03-25 16:52:18 90 | * @description : 获取歌手榜 91 | * @return {*} 92 | */ 93 | export const getArtist = () => request({ url: '/toplist/artist', method: 'get' }); 94 | 95 | /** 96 | * @Date : 2023-05-25 23:06:07 97 | * @description : 关注歌手 98 | * @param {*} id: 99 | * @param {*} t:1 为收藏,其他为取消收藏 100 | * @return {*} 101 | */ 102 | export const followSinger = (id, t) => { 103 | return request({ 104 | url: `/artist/sub?id=${id}&t=${t}×tamp=${Date.now()}`, 105 | }); 106 | }; 107 | -------------------------------------------------------------------------------- /src/api/songs.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | /** 4 | * @Date : 2023-03-27 18:54:35 5 | * @description : 获取歌曲url 6 | * @param {*} id: 7 | * @return {*} 8 | */ 9 | export const getSongUrl = id => request({ url: `/song/url?id=${id}`, method: 'get' }); 10 | 11 | /** 12 | * @Date : 2023-03-27 18:55:32 13 | * @description : 获取歌曲详情 14 | * @param {*} id: 15 | * @return {*} 16 | */ 17 | export const getSongDetail = id => request({ url: `/song/detail?ids=${id}`, method: 'get' }); 18 | 19 | /** 20 | * @Date : 2023-03-27 19:02:43 21 | * @description : 获取歌曲歌词 22 | * @param {*} id: 23 | * @return {*} 24 | */ 25 | export const getSongLyric = id => request({ url: `/lyric?id=${id}`, method: 'get' }); 26 | 27 | /** 28 | * @Date : 2023-05-12 14:42:54 29 | * @description : 检测音乐是否可用 30 | * @param {*} id: 31 | * @return {*} 32 | */ 33 | export const checkMusci = id => request({ url: `/check/music?id=${id}` }); 34 | -------------------------------------------------------------------------------- /src/api/user.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | /** 4 | * @Date : 2023-05-12 16:36:55 5 | * @description : 获取登录状态 6 | * @return {*} 7 | */ 8 | export const getUserStatus = () => 9 | request({ url: `/login/status?timestamp=${Date.now()}`, method: 'post' }); 10 | 11 | /** 12 | * @Date : 2023-04-11 21:13:46 13 | * @description : 获取用户详情 14 | * @param {*} uid: 15 | * @return {*} 16 | */ 17 | export const getUserDetail = uid => { 18 | return request({ 19 | url: `/user/detail?uid=${uid}×tamp=${Date.now()}`, 20 | method: 'get', 21 | }); 22 | }; 23 | 24 | /** 25 | * @Date : 2023-04-12 17:27:59 26 | * @description : 获取用户账号信息 27 | * @return {*} 28 | */ 29 | export const getUserAccountInfo = () => { 30 | return request({ 31 | url: `/user/account?timestamp=${Date.now()}`, 32 | method: 'get', 33 | }); 34 | }; 35 | 36 | /** 37 | * @Date : 2023-04-12 17:39:22 38 | * @description : 获取用户等级信息,包含当前登录天数,听歌次数,下一等级需要的登录天数和听歌次数,当前等级进度 39 | * @return {*} 40 | */ 41 | export const getUserLevel = () => { 42 | return request({ 43 | url: '/user/level', 44 | method: 'get', 45 | }); 46 | }; 47 | 48 | /** 49 | * @Date : 2023-04-12 18:57:26 50 | * @description : 获取用户喜欢列表(数组歌曲id) 51 | * @param {*} uid: 52 | * @return {*} 53 | */ 54 | export const getUserLike = uid => { 55 | return request({ 56 | url: `/likelist?uid=${uid}×tamp=${Date.now()}`, 57 | method: 'get', 58 | }); 59 | }; 60 | 61 | /** 62 | * @Date : 2023-04-13 15:53:28 63 | * @description : 喜欢音乐 64 | * @param {*} id: 65 | * @param {*} bol: 是否喜欢 66 | * @return {*} 67 | */ 68 | export const like = (id, bol) => { 69 | return request({ 70 | url: `/like?id=${id}&like=${bol}×tamp=${Date.now()}`, 71 | method: 'get', 72 | }); 73 | }; 74 | 75 | /** 76 | * @Date : 2023-04-12 18:31:18 77 | * @description : 获取用户播放记录-歌曲 78 | * @param {*} uid: 79 | * @return {*} 80 | */ 81 | export const getUserRecordSong = () => 82 | request({ url: `/record/recent/song?timestamp=${Date.now()}` }); 83 | 84 | /** 85 | * @Date : 2023-05-05 14:18:41 86 | * @description : 获取用户播放记录-视频 87 | * @return {*} 88 | */ 89 | export const getUserRecordVideo = () => 90 | request({ url: `/record/recent/video?timestamp=${Date.now()}` }); 91 | 92 | /** 93 | * @Date : 2023-04-12 22:10:58 94 | * @description : 获取用户歌单 95 | * @param {*} uid: 96 | * @return {*} 97 | */ 98 | export const getUserPlaylist = uid => { 99 | return request({ 100 | url: `/user/playlist?uid=${uid}`, 101 | method: 'get', 102 | }); 103 | }; 104 | 105 | /** 106 | * @Date : 2023-05-25 18:39:39 107 | * @description : 关注用户 108 | * @param {*} id: 用户id 109 | * @param {*} t:1为关注,其他为取消关注 110 | * @return {*} 111 | */ 112 | export const followUser = (id, t) => { 113 | return request({ url: `/follow?id=${id}&t=${t}×tamp=${Date.now()}` }); 114 | }; 115 | 116 | /** 117 | * @Date : 2023-06-11 12:09:26 118 | * @description : 获取用户vip信息 119 | * @return {*} 120 | */ 121 | export const getUserVIPinfo = () => { 122 | return request({ url: `/vip/info?timestamp=${Date.now()}` }); 123 | }; 124 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyyfun/music/cf68bb624057b7118817f1dad001d7d2b40b9915/src/assets/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyyfun/music/cf68bb624057b7118817f1dad001d7d2b40b9915/src/assets/iconfont/iconfont.woff -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyyfun/music/cf68bb624057b7118817f1dad001d7d2b40b9915/src/assets/iconfont/iconfont.woff2 -------------------------------------------------------------------------------- /src/assets/images/20171122191630_ff8fef.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyyfun/music/cf68bb624057b7118817f1dad001d7d2b40b9915/src/assets/images/20171122191630_ff8fef.webp -------------------------------------------------------------------------------- /src/assets/images/cover_play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyyfun/music/cf68bb624057b7118817f1dad001d7d2b40b9915/src/assets/images/cover_play.png -------------------------------------------------------------------------------- /src/assets/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyyfun/music/cf68bb624057b7118817f1dad001d7d2b40b9915/src/assets/images/loading.gif -------------------------------------------------------------------------------- /src/assets/images/logo_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyyfun/music/cf68bb624057b7118817f1dad001d7d2b40b9915/src/assets/images/logo_dark.png -------------------------------------------------------------------------------- /src/assets/images/logo_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyyfun/music/cf68bb624057b7118817f1dad001d7d2b40b9915/src/assets/images/logo_light.png -------------------------------------------------------------------------------- /src/assets/images/singer_300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyyfun/music/cf68bb624057b7118817f1dad001d7d2b40b9915/src/assets/images/singer_300.png -------------------------------------------------------------------------------- /src/assets/images/song_300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyyfun/music/cf68bb624057b7118817f1dad001d7d2b40b9915/src/assets/images/song_300.png -------------------------------------------------------------------------------- /src/assets/images/vinyl-arm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyyfun/music/cf68bb624057b7118817f1dad001d7d2b40b9915/src/assets/images/vinyl-arm.png -------------------------------------------------------------------------------- /src/assets/images/vinyl-record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyyfun/music/cf68bb624057b7118817f1dad001d7d2b40b9915/src/assets/images/vinyl-record.png -------------------------------------------------------------------------------- /src/assets/images/wave.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xyyfun/music/cf68bb624057b7118817f1dad001d7d2b40b9915/src/assets/images/wave.gif -------------------------------------------------------------------------------- /src/components/app-add-playlist.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 48 | 49 | 79 | -------------------------------------------------------------------------------- /src/components/app-icon.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 37 | 38 | 60 | -------------------------------------------------------------------------------- /src/components/app-mask.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 29 | -------------------------------------------------------------------------------- /src/components/app-more.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 39 | 40 | 53 | -------------------------------------------------------------------------------- /src/components/app-play-order.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 39 | 40 | 79 | -------------------------------------------------------------------------------- /src/components/app-sound.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 53 | 54 | 116 | -------------------------------------------------------------------------------- /src/components/library/music-list.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 34 | 35 | 84 | -------------------------------------------------------------------------------- /src/components/library/music-message-box.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 61 | 62 | 138 | -------------------------------------------------------------------------------- /src/components/library/music-message.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 58 | 59 | 99 | -------------------------------------------------------------------------------- /src/components/library/music-mv-list.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 37 | 38 | 106 | -------------------------------------------------------------------------------- /src/components/library/music-play-count.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 24 | 25 | 58 | -------------------------------------------------------------------------------- /src/components/library/music-skeleton.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 32 | 33 | 71 | -------------------------------------------------------------------------------- /src/components/library/music-tabs.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 45 | 46 | 75 | -------------------------------------------------------------------------------- /src/components/library/music-title-child.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 12 | 23 | -------------------------------------------------------------------------------- /src/components/library/music-title.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | 17 | 23 | -------------------------------------------------------------------------------- /src/components/library/music-video-list.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 29 | 30 | 88 | -------------------------------------------------------------------------------- /src/hooks/useDraggable.js: -------------------------------------------------------------------------------- 1 | import { onMounted, onUnmounted, reactive } from 'vue'; 2 | 3 | /** 4 | * @Date : 2023-05-24 17:00:08 5 | * @description : 使用拖动 6 | * @param {*} el : 绑定元素 7 | * @return {*} 8 | */ 9 | export default el => { 10 | const style = reactive({ 11 | right: '84px', 12 | bottom: '100px', 13 | }); 14 | const _mousedown = event => { 15 | const _mousemove = e => { 16 | style.top = e.clientY - event.offsetY + 'px'; 17 | style.left = e.clientX - event.offsetX + 'px'; 18 | }; 19 | const _mouseup = () => { 20 | document.removeEventListener('mousemove', _mousemove); 21 | document.removeEventListener('mouseup', _mouseup); 22 | event.preventDefault(); 23 | }; 24 | document.addEventListener('mousemove', _mousemove); 25 | document.addEventListener('mouseup', _mouseup); 26 | }; 27 | onMounted(() => { 28 | if (el.value) el.value.addEventListener('mousedown', _mousedown); 29 | }); 30 | onUnmounted(() => { 31 | document.removeEventListener('mousedown', _mousedown); 32 | }); 33 | return { 34 | style, 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /src/hooks/useNumberSwitch.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | export default function (num) { 3 | const count = ref(null); 4 | const length = num.toString().length; // 数据长度 5 | if (length < 4) { 6 | count.value = num; 7 | } else if (length === 4) { 8 | // 千单位 9 | count.value = (num / 1000).toFixed(2) + '千'; 10 | } else if (length <= 8) { 11 | // 千-千万 12 | count.value = (num / 10000).toFixed(2) + '万'; 13 | } else { 14 | // 亿 15 | count.value = (num / 100000000).toFixed(2) + '亿'; 16 | } 17 | return count; 18 | } 19 | -------------------------------------------------------------------------------- /src/hooks/useProgress.js: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted } from 'vue'; 2 | 3 | /** 4 | * @Date : 2023-05-21 17:55:00 5 | * @description : 使用进度 6 | * @param {*} target: HTMLElement 7 | * @param {*} slot: HTMLElement 8 | * @param {*} params: 当前歌曲总秒数 9 | * @return {*} 10 | */ 11 | 12 | export default (target, slot, params) => { 13 | // x负责控制音乐进度 14 | // y负责控制音量 15 | const currentTime = ref(0); 16 | const sound = ref(0); 17 | function chnagProgress(e) { 18 | const width = slot.value.offsetWidth; 19 | const height = slot.value.offsetHeight; 20 | const left = slot.value.getBoundingClientRect().left; // 元素相对于body左边的距离 21 | const top = slot.value.getBoundingClientRect().top + height; // 元素相对于body顶部的距离 22 | // 点击的进度条百分比位置 = 点击位置/总长度 * 100 23 | let percentageX = ((e.clientX - left) / width) * 100; 24 | let percentageY = Math.floor(((top - e.clientY) / height) * 100); 25 | if (percentageX < 0) percentageX = 0; 26 | if (percentageX > 100) percentageX = 100; 27 | if (percentageY < 0) percentageY = 0; 28 | if (percentageY > 100) percentageY = 100; 29 | // 点击位置的音乐秒数 = 点击的进度条百分比位置 / 100 * 音乐总秒数 30 | currentTime.value = (percentageX / 100) * params.value; 31 | sound.value = percentageY; 32 | } 33 | // 点击事件 34 | const _click = e => chnagProgress(e); 35 | // 拖拽事件 36 | const _mousedown = event => { 37 | event.preventDefault(); 38 | // 鼠标移动 39 | const _mousemove = e => chnagProgress(e); 40 | // 鼠标抬起 41 | const _mouseup = () => { 42 | document.removeEventListener('mousemove', _mousemove); 43 | document.removeEventListener('mouseup', _mouseup); 44 | }; 45 | document.addEventListener('mousemove', _mousemove); 46 | document.addEventListener('mouseup', _mouseup); 47 | }; 48 | onMounted(() => { 49 | if (slot.value) slot.value.addEventListener('click', _click); 50 | if (target.value) target.value.addEventListener('mousedown', _mousedown); 51 | }); 52 | onUnmounted(() => { 53 | // 销毁 54 | document.removeEventListener('click', _click); 55 | document.removeEventListener('mousedown', _mousedown); 56 | }); 57 | return { currentTime, sound }; 58 | }; 59 | -------------------------------------------------------------------------------- /src/hooks/useSearch.js: -------------------------------------------------------------------------------- 1 | import { onMounted, ref } from 'vue'; 2 | import { search } from '@/api/search'; 3 | import { useDateFormat } from '@vueuse/core'; 4 | import useNumberSwitch from '@/hooks/useNumberSwitch'; 5 | 6 | const defaultType = { 7 | songs: 1, 8 | albums: 10, 9 | artists: 100, 10 | playlists: 1000, 11 | videos: 1014, 12 | }; 13 | 14 | /** 15 | * @Date : 2023-05-08 17:26:01 16 | * @description : 17 | * @param {*} keyword: 搜索内容 18 | * @param {*} type: 搜索类型(参考默认类型) 19 | * @param {*} offset: 偏移数 20 | * @return {*} 21 | */ 22 | export default function (keyword, type, offset, callback) { 23 | search(keyword, defaultType[type], offset).then(data => { 24 | const result = data.data.result[type] || []; 25 | if (type === 'songs') { 26 | callback && callback(result, data.data.result.songCount); 27 | } else if (type === 'albums') { 28 | result.forEach(e => (e.publishTime = useDateFormat(e.publishTime, 'YYYY-MM-DD'))); 29 | callback && callback(result, data.data.result.albumCount); 30 | } else if (type === 'artists') { 31 | callback && callback(result, data.data.result.artistCount); 32 | } else if (type === 'playlists') { 33 | result.forEach(e => (e.playCount = useNumberSwitch(e.playCount))); 34 | callback && callback(result, data.data.result.playlistCount); 35 | } else if (type === 'videos') { 36 | callback && callback(result, data.data.result.videoCount); 37 | } 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import router from '@/router'; 3 | import store from '@/store'; 4 | import './assets/style/common.less'; 5 | import './assets/iconfont/iconfont.css'; 6 | import plugin from './plugin'; 7 | import App from './App.vue'; 8 | 9 | createApp(App).use(router).use(store).use(plugin).mount('#app'); 10 | -------------------------------------------------------------------------------- /src/plugin/index.js: -------------------------------------------------------------------------------- 1 | import VueLazyload from 'vue-lazyload'; 2 | 3 | export default { 4 | install(app) { 5 | // 配置项 6 | app.use(VueLazyload, { 7 | preLoad: 1.3, 8 | error: require('../assets/images/singer_300.png'), 9 | loading: require('../assets/images/song_300.png'), 10 | attempt: 1, 11 | }); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex'; 2 | import song from './modules/song'; 3 | import user from './modules/user'; 4 | import playlist from './modules/playlist'; 5 | import video from './modules/video'; 6 | 7 | export default createStore({ 8 | modules: { song, user, playlist, video }, 9 | }); 10 | -------------------------------------------------------------------------------- /src/store/modules/playlist.js: -------------------------------------------------------------------------------- 1 | import { getPlaylistDetail } from '@/api/playlist'; 2 | import { getAlbumData } from '@/api/album'; 3 | import { like } from '@/api/user'; 4 | import { getUserLike, setUserLike } from '@/utils/user'; 5 | import { useDateFormat } from '@vueuse/core'; 6 | export default { 7 | // 开启命名空间 8 | namespaced: true, 9 | state: { 10 | songLists: [], // 歌单/专辑列表 11 | playlistDetail: {}, // 歌单/专辑详情 12 | subscribed: false, // 是否收藏该歌单/专辑 13 | }, 14 | mutations: { 15 | // 歌曲列表 16 | lists(state, lists) { 17 | const userLikeSongs = new Set(getUserLike()); 18 | lists.forEach((e, i) => { 19 | const str = useDateFormat(e.dt, 'mm:ss'); 20 | e.dt = str.value.replace(/\"/g, ''); 21 | // 判断该音乐是否为用户喜欢 22 | if (userLikeSongs.has(e.id)) { 23 | e.isLike = true; 24 | } else { 25 | e.isLike = false; 26 | } 27 | state.songLists.push(e); 28 | }); 29 | }, 30 | // 歌单详情处理 31 | playlistDetail(state, val) { 32 | const { 33 | description, 34 | creator: { nickname, avatarUrl, userId }, 35 | name, 36 | coverImgUrl: picUrl, 37 | updateTime, 38 | subscribedCount, 39 | shareCount, 40 | } = val; 41 | state.playlistDetail = { 42 | description, 43 | nickname, 44 | avatarUrl, 45 | userId, 46 | name, 47 | picUrl, 48 | updateTime, 49 | subscribedCount, 50 | shareCount, 51 | }; 52 | state.subscribed = val.subscribed; 53 | }, 54 | // 专辑详情处理 55 | albumDetail(state, val) { 56 | const { description, artists, name, picUrl, publishTime } = val; 57 | state.playlistDetail = { description, artists, name, picUrl, publishTime }; 58 | state.subscribed = val.info.liked; 59 | }, 60 | // 修改是否收藏歌单/专辑 61 | changCollect(state, val) { 62 | state.subscribed = val; 63 | }, 64 | // 添加喜欢的歌曲id 65 | addUserLikeID(state, id) { 66 | const result = getUserLike(); 67 | for (let i = 0; i < state.songLists.length; i++) { 68 | if (id === state.songLists[i].id) { 69 | state.songLists[i].isLike = true; 70 | break; 71 | } 72 | } 73 | setUserLike([id, ...result]); 74 | }, 75 | // 移除喜欢的歌曲id 76 | removeUserLikeID(state, id) { 77 | const result = getUserLike(); 78 | for (let i = 0; i < state.songLists.length; i++) { 79 | if (id === state.songLists[i].id) { 80 | state.songLists[i].isLike = false; 81 | break; 82 | } 83 | } 84 | for (let i = 0; i < result.length; i++) { 85 | if (id === result[i]) { 86 | result.splice(i, 1); 87 | break; 88 | } 89 | } 90 | setUserLike(result); 91 | }, 92 | // 移除数据 93 | clearData(state) { 94 | state.songLists = []; 95 | state.playlistDetail = {}; 96 | }, 97 | }, 98 | actions: { 99 | // 歌单详情 100 | getPlaylistDetail({ commit }, id) { 101 | commit('clearData'); 102 | getPlaylistDetail(id).then(data => { 103 | commit('lists', data.data.playlist.tracks); 104 | commit('playlistDetail', data.data.playlist); 105 | }); 106 | }, 107 | // 专辑详情 108 | getAlbumDetail({ commit }, id) { 109 | commit('clearData'); 110 | getAlbumData(id).then(data => { 111 | commit('lists', data.data.songs); 112 | commit('albumDetail', data.data.album); 113 | }); 114 | }, 115 | // 修改用户喜欢 116 | changUserLike({ commit }, { id, boolean }) { 117 | return new Promise((resolve, reject) => { 118 | like(id, boolean).then( 119 | () => { 120 | if (boolean) { 121 | commit('addUserLikeID', id); 122 | } else { 123 | commit('removeUserLikeID', id); 124 | } 125 | resolve('success'); 126 | }, 127 | error => { 128 | reject(error.response); 129 | } 130 | ); 131 | }); 132 | }, 133 | }, 134 | }; 135 | -------------------------------------------------------------------------------- /src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import { setUserLike, setUserInfo, getUserInfo } from '@/utils/user'; 2 | import { getUserStatus, getUserLike, getUserDetail, getUserVIPinfo } from '@/api/user'; 3 | export default { 4 | // 开启命名空间 5 | namespaced: true, 6 | state: { 7 | // 用户信息 8 | userInfo: getUserInfo() || {}, 9 | // 用户歌曲喜欢列表(歌单) 10 | usersongLike: null, 11 | // 用户VIP信息 12 | userVIPinfo: {}, 13 | // 0:等待状态返回 1:未登录 2:登录成功 14 | status: 0, 15 | // 是否显示登录面板 16 | isShowLoginPanel: false, 17 | }, 18 | mutations: { 19 | // 修改用户状态 20 | changUserStatus(state, val) { 21 | state.status = val; 22 | }, 23 | // 存储用户信息 24 | setInfo(state, val) { 25 | state.userInfo = val; 26 | }, 27 | // 存储用户喜欢列表 28 | changUsreSongLike(state, val) { 29 | state.usersongLike = val; 30 | }, 31 | // 存储用户vip数据 32 | setUserVIPinfo(state, val) { 33 | let obj = {}; 34 | const { associator, redplus } = val; 35 | const presentTime = Date.now(); 36 | if (associator['expireTime'] > presentTime) { 37 | if (redplus['expireTime'] > presentTime) { 38 | // svip用户 39 | obj = { ...redplus, isMember: true }; 40 | } else { 41 | // 普通会员用户 42 | obj = { ...associator, isMember: true }; 43 | } 44 | } else { 45 | // 过期会员用户或无会员用户 46 | obj = { ...associator, isMember: false }; 47 | } 48 | state.userVIPinfo = obj; 49 | }, 50 | // 修改登录面板状态 51 | changPanelStatus(state, val) { 52 | state.isShowLoginPanel = val; 53 | }, 54 | // 删除用户信息 55 | removeInfo(state) { 56 | state.userInfo = {}; 57 | state.usersongLike = null; 58 | state.userVIPinfo = {}; 59 | state.status = 0; 60 | state.isShowLoginPanel = false; 61 | }, 62 | }, 63 | actions: { 64 | // 用户登录状态 65 | userStatus({ commit }) { 66 | return new Promise((resolv, reject) => { 67 | getUserStatus().then(result => { 68 | if (result.data.data.profile) { 69 | const userId = result.data.data.profile.userId; 70 | commit('changUserStatus', 2); 71 | resolv(userId); 72 | } else { 73 | commit('changUserStatus', 1); 74 | reject(); 75 | } 76 | }); 77 | }); 78 | }, 79 | // 获取用户信息 80 | userInfo({ commit }, id) { 81 | return new Promise((resolv, reject) => { 82 | getUserDetail(id).then( 83 | data => { 84 | setUserInfo(data.data.profile); // 持久化存储 85 | commit('setInfo', data.data.profile); // 存储至vuex 86 | resolv(); 87 | }, 88 | () => { 89 | reject(); 90 | } 91 | ); 92 | }); 93 | }, 94 | // 获取用户喜欢(歌曲id数组集合) 95 | userLike({}, id) { 96 | getUserLike(id).then(data => { 97 | setUserLike(data.data.ids); 98 | }); 99 | }, 100 | // 获取用户vip信息 101 | userVIPinfo({ commit }) { 102 | getUserVIPinfo().then(result => { 103 | const { associator, redplus } = result.data.data; 104 | commit('setUserVIPinfo', { associator, redplus }); 105 | }); 106 | }, 107 | }, 108 | getters: { 109 | // 用户id 110 | userId(state) { 111 | return state.userInfo.userId || undefined; 112 | }, 113 | // 用户头像 114 | userAvatarUrl(state) { 115 | return state.userInfo.avatarUrl || require('@/assets/images/singer_300.png'); 116 | }, 117 | // 用户名字 118 | userName(state) { 119 | return state.userInfo.nickname || undefined; 120 | }, 121 | }, 122 | }; 123 | -------------------------------------------------------------------------------- /src/store/modules/video.js: -------------------------------------------------------------------------------- 1 | import { getVideoUrl, getVideoDetail, getMvUrl, getMvDetail } from '@/api/video'; 2 | import { useDateFormat } from '@vueuse/core'; 3 | export default { 4 | // 开启命名空间 5 | namespaced: true, 6 | state: { 7 | videoUrl: '', // 视频url 8 | detail: {}, // 详情 9 | creator: {}, // 创作者 10 | artists: [], // 艺人 11 | isPlay: false, // 视频是否可以播放 12 | totalDuration: 0, // 视频总秒数 13 | totalTime: '00:00', // 视频播放时间(分+秒) 14 | nowTime: '00:00', // 当前视频播放时间(分+秒) 15 | nowProgress: 0, // 当前进度条进度 16 | duration: 0, // 指定播放器到哪秒 17 | isCollect: false, // 是否收藏 18 | }, 19 | mutations: { 20 | // 视频播放地址 21 | VIDEOURL(state, val) { 22 | state.videoUrl = val; 23 | }, 24 | // 视频详情 25 | VIDEODETAIL(state, val) { 26 | state.detail = val; 27 | }, 28 | // 视频创作者 29 | videoCreator(state, val) { 30 | state.creator = val; 31 | }, 32 | // mv创作者 33 | mvCreator(state, val) { 34 | state.artists = val; 35 | }, 36 | ISPLAY(state, val) { 37 | state.isPlay = val; 38 | }, 39 | NOWTIME(state, val) { 40 | // state.playerTime = val; 41 | // 进度位置=当前播放事件秒/总时间秒*100 42 | state.nowProgress = val / state.totalDuration; 43 | const time = useDateFormat(val * 1000, 'mm:ss'); 44 | state.nowTime = time.value; 45 | }, 46 | // 视频初始化 47 | initial(state, { bol, time }) { 48 | state.isPlay = bol; 49 | state.totalDuration = time; 50 | const str = useDateFormat(time * 1000, 'mm:ss'); 51 | state.totalTime = str.value; 52 | }, 53 | // 修改进度 54 | DURATION(state, val) { 55 | state.duration = val; 56 | }, 57 | // 清空数据 58 | clearData(state) { 59 | state.videoUrl = ''; 60 | state.detail = {}; 61 | state.creator = {}; 62 | state.nowProgress = 0; 63 | state.totalDuration = 0; 64 | state.duration = 0; 65 | state.nowTime = '00:00'; 66 | state.totalTime = '00:00'; 67 | }, 68 | }, 69 | actions: { 70 | // 视频URL 71 | videoUrl({ commit }, id) { 72 | if (id) { 73 | getVideoUrl(id).then(data => { 74 | commit('VIDEOURL', data.data.urls[0].url); 75 | }); 76 | } 77 | }, 78 | // 视频详情 79 | videoDetail({ commit }, id) { 80 | if (id) { 81 | getVideoDetail(id).then(data => { 82 | data.data.data.publishTime = useDateFormat(data.data.data.publishTime, 'YYYY-MM-DD'); 83 | commit('VIDEODETAIL', data.data.data); 84 | commit('videoCreator', data.data.data.creator); 85 | }); 86 | } 87 | }, 88 | // mvUrl 89 | mvUrl({ commit }, { id, r }) { 90 | if (id) { 91 | getMvUrl(id, r).then(data => { 92 | commit('VIDEOURL', data.data.data.url); 93 | }); 94 | } 95 | }, 96 | // mv详情 97 | mvDetail({ commit }, id) { 98 | if (id) { 99 | getMvDetail(id).then(data => { 100 | data.data.data.description = data.data.data.desc; 101 | delete data.data.data.desc; 102 | commit('VIDEODETAIL', data.data.data); 103 | commit('mvCreator', data.data.data.artists); 104 | }); 105 | } 106 | }, 107 | }, 108 | getters: { 109 | // 视频标签 110 | videoGroup(state) { 111 | return state.detail.videoGroup || []; 112 | }, 113 | }, 114 | }; 115 | -------------------------------------------------------------------------------- /src/utils/bus.js: -------------------------------------------------------------------------------- 1 | import mitt from 'mitt'; 2 | 3 | const emitter = mitt(); 4 | 5 | export default emitter; 6 | -------------------------------------------------------------------------------- /src/utils/cookie.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Date : 2023-04-12 15:57:07 3 | * @description : 存储cookie 4 | * @param {*} val: cookie 5 | * @param {*} status:状态 0:游客 1:登录用户 6 | * @return {*} 7 | */ 8 | export const setCookie = (val, status) => { 9 | if (status) { 10 | const cookie = val.replace(/\s*/g, ''); 11 | // 获取MUSIC_U之后所有字符 12 | const str = cookie.substring(cookie.indexOf('MUSIC_U')); 13 | // 截取MUSIC_U至HTTPOnly间字符 14 | const MUSIC_U = str.substring(0, str.indexOf('HTTPOnly')); 15 | document.cookie = MUSIC_U; 16 | } else { 17 | const cookie = val.replace(/\s*/g, ''); 18 | const array = cookie.split(';;'); 19 | array.forEach(e => (document.cookie = e)); 20 | } 21 | }; 22 | 23 | /** 24 | * @Date : 2023-04-11 20:44:51 25 | * @description : 获取cookie 26 | * @return {*} 27 | */ 28 | export const getCookie = () => document.cookie; 29 | 30 | /** 31 | * @Date : 2023-04-12 15:59:25 32 | * @description : 移除cookie 33 | * @return {*} 34 | */ 35 | export const removeCookie = () => { 36 | const cookie = document.cookie.replace(/\s*/g, ''); 37 | const array = cookie.split(';'); 38 | const new_data = new Date(Date.now() - 24 * 60 * 60 * 1000); 39 | array.forEach(e => { 40 | document.cookie = `${e};Expires=${new_data.toUTCString()};Path=/`; 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /src/utils/history.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Date : 2023-05-09 23:11:13 3 | * @description : 设置历史搜索 4 | * @param {*} val: 5 | * @return {*} 6 | */ 7 | export const setHistorySearch = val => localStorage.setItem('historySearch', JSON.stringify(val)); 8 | 9 | /** 10 | * @Date : 2023-05-09 23:10:58 11 | * @description : 获取历史搜索 12 | * @return {*} 13 | */ 14 | export const getHistorySearch = () => { 15 | return JSON.parse(localStorage.getItem('historySearch')) || []; 16 | }; 17 | -------------------------------------------------------------------------------- /src/utils/historyPlay.js: -------------------------------------------------------------------------------- 1 | // 历史播放 2 | import { computed } from 'vue'; 3 | import { useStore } from 'vuex'; 4 | import { useDateFormat } from '@vueuse/core'; 5 | import message from './message'; 6 | export default function historyPlay() { 7 | const store = useStore(); 8 | const currentMusicID = computed(() => store.state.song.currentMusicID); 9 | const isPlay = computed(() => store.state.song.isPlay); 10 | const playerTime = computed(() => store.state.song.playerTime); 11 | // 当前播放音乐 12 | const currentlyPlayingMusic = () => { 13 | if (currentMusicID.value && isPlay.value) { 14 | localStorage.setItem( 15 | 'historyPlay', 16 | JSON.stringify({ 17 | currentMusicID: currentMusicID.value, 18 | playerTime: playerTime.value, 19 | new: true, 20 | }) 21 | ); 22 | } 23 | }; 24 | // 是否有历史播放 25 | const isHistoryPlay = () => { 26 | const historyPlay = JSON.parse(localStorage.getItem('historyPlay')); 27 | if (historyPlay && historyPlay.new) { 28 | const { currentMusicID, playerTime } = historyPlay; 29 | store 30 | .dispatch('song/getMusic', currentMusicID) 31 | .then(() => { 32 | message({ 33 | type: 'success', 34 | message: `上次播放到 ${ 35 | useDateFormat(playerTime * 1000, 'mm:ss').value 36 | },已为您继续播放`, 37 | }); 38 | store.commit('song/DURATION', playerTime); 39 | localStorage.removeItem('historyPlay'); 40 | }) 41 | .catch(err => {}); 42 | } 43 | }; 44 | // 监听页签关闭beforeunload 45 | window.addEventListener('beforeunload', () => currentlyPlayingMusic()); 46 | window.addEventListener('load', () => isHistoryPlay()); 47 | } 48 | -------------------------------------------------------------------------------- /src/utils/message-box.js: -------------------------------------------------------------------------------- 1 | import MusicMessageBox from '@/components/library/music-message-box'; 2 | import { render, createVNode } from 'vue'; 3 | 4 | const defaults = { 5 | title: '标题', 6 | message: '这是一段有关内容', 7 | type: null, // 图标 8 | isShowClose: true, // 是否显示右上角关闭按钮 9 | isShowCancel: false, // 是否显示取消按钮 10 | cancelButtonText: '取消', // 取消按钮的文本内容 11 | confirmButtonText: '确定', // 确定按钮的文本内容 12 | }; 13 | 14 | let mountNode = null; 15 | 16 | // 销毁/收尾 17 | const destroy = () => { 18 | render(null, mountNode); 19 | document.body.removeChild(mountNode); 20 | mountNode = null; 21 | }; 22 | 23 | export default (options = {}) => { 24 | const messageBoxProps = { 25 | ...defaults, 26 | ...options, 27 | }; 28 | return new Promise((resolve, reject) => { 29 | // 确定 30 | const submitCallback = () => { 31 | destroy(); 32 | resolve('confirm'); 33 | }; 34 | // 取消 35 | const cancelCallback = () => { 36 | destroy(); 37 | reject('close'); 38 | }; 39 | const app = createVNode(MusicMessageBox, { messageBoxProps, submitCallback, cancelCallback }); 40 | mountNode = document.createElement('div'); 41 | render(app, mountNode); // 渲染 42 | document.body.appendChild(mountNode); // 插入页面body 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /src/utils/message.js: -------------------------------------------------------------------------------- 1 | import { render, createVNode } from 'vue'; 2 | import MusicMessage from '@/components/library/music-message'; 3 | 4 | let timer = null; 5 | let mountNode = null; 6 | 7 | const messageDefaults = { 8 | duration: 3000, 9 | id: '', 10 | message: '', 11 | onClose: undefined, 12 | showClose: false, 13 | type: 'warn', 14 | offset: 16, 15 | zIndex: 0, 16 | repeatNum: 1, 17 | }; 18 | 19 | export default options => { 20 | const messageProps = { 21 | ...messageDefaults, 22 | ...options, 23 | }; 24 | //确保只存在一个弹框,如果前一个弹窗还在则移除 25 | if (mountNode) { 26 | document.body.removeChild(mountNode); 27 | mountNode = null; 28 | clearTimeout(timer); 29 | } 30 | const app = createVNode(MusicMessage, { messageProps }); 31 | mountNode = document.createElement('div'); 32 | render(app, mountNode); 33 | document.body.appendChild(mountNode); 34 | timer = setTimeout(() => { 35 | render(null, mountNode); 36 | document.body.removeChild(mountNode); 37 | mountNode = null; 38 | clearTimeout(timer); 39 | }, messageProps.duration + 500); 40 | }; 41 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import message from '@/utils/message'; 3 | 4 | // 创建axios实例 5 | const instance = axios.create({ 6 | // 基础路径 7 | baseURL: 'https://service-nsjbrcma-1317235276.gz.apigw.tencentcs.com/release/', 8 | // baseURL: 'http://localhost:3000/', 9 | /* 10 | *请求API为本地时将baseURL路径替换为http://localhost:3000/ 11 | !Vercel/腾讯云部署时请配置代理 12 | */ 13 | // 超时时间 14 | timeout: 5000, 15 | // 允许携带cookie 16 | withCredentials: true, 17 | }); 18 | // 请求拦截器 19 | instance.interceptors.request.use( 20 | config => { 21 | config.data = { 22 | cookie: document.cookie, 23 | }; 24 | config.method = 'post'; 25 | return config; 26 | }, 27 | error => { 28 | console.log(error); 29 | } 30 | ); 31 | 32 | // 响应拦截器 33 | instance.interceptors.response.use( 34 | response => { 35 | const { 36 | data: { code, message: msg }, 37 | } = response; 38 | if (code === 400) return Promise.reject('error'); 39 | if (code === -460) message({ type: 'error', message: msg }); 40 | return response; 41 | }, 42 | error => { 43 | if (!error.response) { 44 | return message({ type: 'error', message: '请求超时,请稍后再试!' }); 45 | } 46 | if (error.response.status === 400) { 47 | const code = error.response.data.code; 48 | if (code === -462) { 49 | message({ type: 'error', message: error.response.data.data.blockText }); 50 | } else if (code === -460) { 51 | message({ type: 'error', message: error.response.data.message }); 52 | } 53 | } 54 | return Promise.reject(error); 55 | } 56 | ); 57 | 58 | export default instance; 59 | -------------------------------------------------------------------------------- /src/utils/tidy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Date : 2023-04-01 20:49:34 3 | * @description : 歌词整理 4 | * @param {*} val: 5 | * @return {*} 6 | */ 7 | export default function tidy(val) { 8 | if (!val) return; 9 | return conversionLrc(val); 10 | function conversionLrc(val) { 11 | let arrLrc = val.split('\n'); 12 | let result = []; 13 | arrLrc.forEach(e => { 14 | let arr = e.split(']'); 15 | let objLrc = { 16 | time: seconds(arr[0].substring(1)), 17 | lyrics: arr[1], 18 | }; 19 | result.push(objLrc); 20 | }); 21 | return result; 22 | } 23 | 24 | function seconds(time) { 25 | let arr = time.split(':'); 26 | return arr[0] * 60 + +arr[1]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/usePosition.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Date : 2023-04-01 12:30:02 3 | * @description : 返回下一首或上一首的音乐id(列表循环) 4 | * @param {*} playlist: 播放队列 5 | * @param {*} currentMusicID: 当前播放音乐id 6 | * @param {*} option: 选项 上一首or下一首 true下一首 false上一首 7 | * @return {*} 8 | */ 9 | export const musicPositionLoop = (playlist, currentMusicID, option) => { 10 | const length = playlist.value.length; 11 | let nextID = null; 12 | for (let i = 0; i < length; i++) { 13 | if (playlist.value[i].id === currentMusicID.value) { 14 | if (option) { 15 | // 下一首 16 | if (length === i + 1) { 17 | nextID = playlist.value[0].id; 18 | } else { 19 | nextID = playlist.value[i + 1].id; 20 | } 21 | } else { 22 | // 上一首 23 | if (i) { 24 | nextID = playlist.value[i - 1].id; 25 | } else { 26 | nextID = playlist.value[length - 1].id; 27 | } 28 | } 29 | break; 30 | } 31 | } 32 | return nextID; 33 | }; 34 | 35 | /** 36 | * @Date : 2023-04-01 13:32:33 37 | * @description : 返回随机音乐id 38 | * @param {*} playlist: 播放队列 39 | * @param {*} currentMusicID: 当前播放音乐id 40 | * @return {*} 41 | */ 42 | export const musicPositionRandom = (playlist, currentMusicID) => { 43 | const length = playlist.value.length; 44 | let randomID = null; 45 | randomID = Math.floor(Math.random() * (length - 0)) + 0; 46 | for (let i = 0; i < length; i++) { 47 | if (playlist.value[i].id === currentMusicID.value) { 48 | if (i === randomID) { 49 | if (length === i + 1) { 50 | randomID = playlist.value[0].id; 51 | } else { 52 | randomID = playlist.value[i + 1].id; 53 | } 54 | } else { 55 | randomID = playlist.value[randomID].id; 56 | } 57 | break; 58 | } 59 | } 60 | return randomID; 61 | }; 62 | -------------------------------------------------------------------------------- /src/utils/user.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Date : 2023-04-12 17:49:46 3 | * @description : 设置/存储用户信息 4 | * @param {*} val: 5 | * @return {*} 6 | */ 7 | export const setUserInfo = (val = {}) => localStorage.setItem('userInfo', JSON.stringify(val)); 8 | 9 | /** 10 | * @Date : 2023-04-12 17:50:22 11 | * @description : 获取用户信息 12 | * @return {*} 13 | */ 14 | export const getUserInfo = () => { 15 | let userInfo = localStorage.getItem('userInfo'); 16 | return JSON.parse(userInfo); 17 | }; 18 | 19 | /** 20 | * @Date : 2023-04-19 15:10:18 21 | * @description : 设置用户喜欢 22 | * @param {*} val: 23 | * @return {*} 24 | */ 25 | export const setUserLike = (val = []) => localStorage.setItem('userLike', JSON.stringify(val)); 26 | 27 | /** 28 | * @Date : 2023-04-19 15:11:57 29 | * @description : 获取用户喜欢 30 | * @return {*} 31 | */ 32 | export const getUserLike = () => JSON.parse(localStorage.getItem('userLike')) || []; 33 | 34 | /** 35 | * @Date : 2023-04-12 17:50:44 36 | * @description : 移除用户信息 37 | * @return {*} 38 | */ 39 | export const removeUserInfo = () => localStorage.clear(); 40 | -------------------------------------------------------------------------------- /src/views/Album/components/album-comment.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/views/Album/components/album-list.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/views/Album/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 46 | 47 | 56 | -------------------------------------------------------------------------------- /src/views/Audio/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /src/views/Audition/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 23 | 24 | 33 | -------------------------------------------------------------------------------- /src/views/Comment/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 89 | 90 | 132 | -------------------------------------------------------------------------------- /src/views/Discover/components/discover-mv.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 38 | 39 | 76 | -------------------------------------------------------------------------------- /src/views/Discover/components/discover-new-playlist.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 27 | 28 | 35 | -------------------------------------------------------------------------------- /src/views/Discover/components/discover-new-song.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 43 | 44 | 116 | -------------------------------------------------------------------------------- /src/views/Discover/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 30 | 31 | 42 | -------------------------------------------------------------------------------- /src/views/Like/components/like-album.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/views/Like/components/like-song.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 40 | 41 | 58 | -------------------------------------------------------------------------------- /src/views/Like/components/like-video.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 42 | 110 | -------------------------------------------------------------------------------- /src/views/Like/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 38 | 39 | 48 | -------------------------------------------------------------------------------- /src/views/Local/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 23 | 24 | 33 | -------------------------------------------------------------------------------- /src/views/Lyrics/components/lyrics-bac.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 17 | 18 | 41 | -------------------------------------------------------------------------------- /src/views/Lyrics/components/lyrics-header.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 26 | 27 | 46 | -------------------------------------------------------------------------------- /src/views/Lyrics/components/lyrics-video-visible.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 75 | 76 | 91 | -------------------------------------------------------------------------------- /src/views/Lyrics/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | 30 | 73 | -------------------------------------------------------------------------------- /src/views/Message/MessageAt/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /src/views/Message/MessageComment/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /src/views/Message/MessagePrivate/components/private-list.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 53 | 54 | 121 | -------------------------------------------------------------------------------- /src/views/Message/MessagePrivate/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | 17 | 23 | -------------------------------------------------------------------------------- /src/views/Message/MessageSystem/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 44 | 45 | 81 | -------------------------------------------------------------------------------- /src/views/Message/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 32 | 33 | 59 | -------------------------------------------------------------------------------- /src/views/Music/MusicAlbum/components/album-all-list.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 74 | 75 | 108 | -------------------------------------------------------------------------------- /src/views/Music/MusicAlbum/components/album-new-list.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 27 | -------------------------------------------------------------------------------- /src/views/Music/MusicAlbum/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /src/views/Music/MusicPicked/components/picked-exclusive.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 35 | 36 | 64 | -------------------------------------------------------------------------------- /src/views/Music/MusicPicked/components/picked-program.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 31 | -------------------------------------------------------------------------------- /src/views/Music/MusicPicked/components/picked-radar.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 34 | 35 | 63 | -------------------------------------------------------------------------------- /src/views/Music/MusicPicked/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | -------------------------------------------------------------------------------- /src/views/Music/MusicPlaylist/components/playlist-filters.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 70 | 71 | 116 | -------------------------------------------------------------------------------- /src/views/Music/MusicPlaylist/components/playlist-list.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 77 | -------------------------------------------------------------------------------- /src/views/Music/MusicPlaylist/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /src/views/Music/MusicRadio/components/radio-anchor.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 33 | 34 | 81 | -------------------------------------------------------------------------------- /src/views/Music/MusicRadio/components/radio-banner.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 29 | 30 | 45 | -------------------------------------------------------------------------------- /src/views/Music/MusicRadio/components/radio-hot.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 27 | -------------------------------------------------------------------------------- /src/views/Music/MusicRadio/components/radio-personality.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/views/Music/MusicRadio/components/radio-program.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 44 | 45 | 97 | -------------------------------------------------------------------------------- /src/views/Music/MusicRadio/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 22 | -------------------------------------------------------------------------------- /src/views/Music/MusicRanking/components/ranking-artist.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/views/Music/MusicRanking/components/ranking-feature-list.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 24 | -------------------------------------------------------------------------------- /src/views/Music/MusicRanking/components/ranking-general-list.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 39 | 40 | 101 | -------------------------------------------------------------------------------- /src/views/Music/MusicRanking/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 33 | -------------------------------------------------------------------------------- /src/views/Music/MusicSinger/components/singer-filters.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 101 | 102 | 140 | -------------------------------------------------------------------------------- /src/views/Music/MusicSinger/components/singer-list.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 27 | 28 | 78 | -------------------------------------------------------------------------------- /src/views/Music/MusicSinger/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 61 | -------------------------------------------------------------------------------- /src/views/Music/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 35 | 36 | 60 | -------------------------------------------------------------------------------- /src/views/Player/components/player-comment.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 81 | -------------------------------------------------------------------------------- /src/views/Player/components/player-related.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 45 | -------------------------------------------------------------------------------- /src/views/Player/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 35 | 36 | 50 | -------------------------------------------------------------------------------- /src/views/Playlist/components/playlist-comment.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/views/Playlist/components/playlist-list.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/views/Playlist/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 49 | 50 | 59 | -------------------------------------------------------------------------------- /src/views/Radio/RadioPlaylist/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 25 | 26 | 35 | -------------------------------------------------------------------------------- /src/views/Radio/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/views/Recently/components/recently-song.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 25 | -------------------------------------------------------------------------------- /src/views/Recently/components/recently-video.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 27 | -------------------------------------------------------------------------------- /src/views/Recently/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 36 | 37 | 46 | -------------------------------------------------------------------------------- /src/views/Search/components/search-album.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 70 | 71 | 113 | -------------------------------------------------------------------------------- /src/views/Search/components/search-playlist.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 70 | 71 | 113 | -------------------------------------------------------------------------------- /src/views/Search/components/search-singer.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 61 | 62 | 103 | -------------------------------------------------------------------------------- /src/views/Search/components/search-songs.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 53 | -------------------------------------------------------------------------------- /src/views/Search/components/search-video.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 73 | 74 | 142 | -------------------------------------------------------------------------------- /src/views/Search/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 69 | 70 | 88 | -------------------------------------------------------------------------------- /src/views/Singer/components/singer-album.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 63 | 64 | 96 | -------------------------------------------------------------------------------- /src/views/Singer/components/singer-select.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/views/Singer/components/singer-songs.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 45 | -------------------------------------------------------------------------------- /src/views/Singer/components/singer-video.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 52 | 53 | 91 | -------------------------------------------------------------------------------- /src/views/Singer/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 49 | 50 | 62 | -------------------------------------------------------------------------------- /src/views/User/UserHome/components/user-like.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/views/User/UserHome/components/user-playlist.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/views/User/UserHome/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 30 | -------------------------------------------------------------------------------- /src/views/User/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 18 | 19 | 28 | -------------------------------------------------------------------------------- /src/views/Video/MvList/components/mv-new.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 49 | 50 | 83 | -------------------------------------------------------------------------------- /src/views/Video/MvList/components/mv-official.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | -------------------------------------------------------------------------------- /src/views/Video/MvList/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/views/Video/VideoList/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/views/Video/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 32 | 33 | 42 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('@vue/cli-service'); 2 | module.exports = defineConfig({ 3 | transpileDependencies: true, 4 | lintOnSave: false, 5 | }); 6 | --------------------------------------------------------------------------------