├── src ├── components │ ├── navigator.vue │ │ └── index.vue │ ├── loading │ │ └── index.vue │ ├── musicsiderlist │ │ └── index.vue │ ├── nav │ │ └── index.vue │ ├── commonpage │ │ └── index.vue │ ├── minesheet │ │ └── index.vue │ ├── lrc │ │ └── index.vue │ ├── musiclist │ │ └── index.vue │ ├── sheetlist │ │ └── index.vue │ ├── groupsheet │ │ └── index.vue │ ├── touchbar │ │ └── index.vue │ └── scroll │ │ └── index.vue ├── config │ └── base.js ├── assets │ ├── logo.png │ ├── images │ │ ├── r1.jpg │ │ ├── r2.jpg │ │ ├── r3.jpg │ │ ├── r4.jpg │ │ ├── ablum.png │ │ └── loading.gif │ └── font-icon │ │ ├── fonts │ │ ├── icomoon.eot │ │ ├── icomoon.ttf │ │ └── icomoon.woff │ │ ├── Read Me.txt │ │ └── demo-files │ │ ├── demo.js │ │ └── demo.css ├── style │ ├── base.scss │ ├── view.scss │ ├── variables.scss │ ├── global.scss │ └── mixin.scss ├── store │ ├── modules │ │ ├── config.js │ │ ├── find.js │ │ ├── mine.js │ │ ├── login.js │ │ └── music.js │ ├── index.js │ └── mutation-types.js ├── views │ ├── account │ │ └── index.vue │ ├── firends │ │ └── index.vue │ ├── video │ │ └── index.vue │ ├── find │ │ ├── station │ │ │ └── index.vue │ │ ├── search │ │ │ ├── singer │ │ │ │ └── index.vue │ │ │ ├── radio │ │ │ │ └── index.vue │ │ │ ├── sheet │ │ │ │ └── index.vue │ │ │ ├── user │ │ │ │ └── index.vue │ │ │ ├── album │ │ │ │ └── index.vue │ │ │ ├── searchingPanel │ │ │ │ └── index.vue │ │ │ └── index.vue │ │ ├── daily │ │ │ └── index.vue │ │ ├── index.vue │ │ ├── sheet │ │ │ ├── sheet-avatar │ │ │ │ └── index.vue │ │ │ ├── sheet-type │ │ │ │ └── index.vue │ │ │ ├── index.vue │ │ │ └── sheet-detail │ │ │ │ └── index.vue │ │ ├── recommend │ │ │ └── index.vue │ │ ├── rank │ │ │ └── index.vue │ │ └── play │ │ │ └── index.vue │ ├── login │ │ ├── index.vue │ │ └── sublogin │ │ │ └── index.vue │ ├── Main.vue │ └── mine │ │ └── index.vue ├── App.vue ├── filter │ └── index.js ├── directive │ └── imgsize.js ├── registerServiceWorker.js ├── utils │ ├── http.js │ ├── index.js │ └── music.js ├── main.js ├── api │ └── index.js └── router.js ├── public ├── robots.txt ├── favicon.ico ├── img │ └── icons │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-150x150.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── msapplication-icon-144x144.png │ │ └── safari-pinned-tab.svg ├── manifest.json └── index.html ├── .env.development ├── .browserslistrc ├── .env.production ├── babel.config.js ├── postcss.config.js ├── .gitattributes ├── .gitignore ├── .eslintrc.js ├── README.md ├── package.json ├── vue.config.js └── config └── index.js /src/components/navigator.vue/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/config/base.js: -------------------------------------------------------------------------------- 1 | // let process.env.NODE_ENV -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | VUE_APP_MUSIC_URL=http://localhost:3000 -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | VUE_APP_MUSIC_URL=http://123.56.221.77/musicApi -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/src/assets/logo.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/assets/images/r1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/src/assets/images/r1.jpg -------------------------------------------------------------------------------- /src/assets/images/r2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/src/assets/images/r2.jpg -------------------------------------------------------------------------------- /src/assets/images/r3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/src/assets/images/r3.jpg -------------------------------------------------------------------------------- /src/assets/images/r4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/src/assets/images/r4.jpg -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=vue 2 | *.css linguist-language=vue 3 | *.html linguist-language=vue -------------------------------------------------------------------------------- /src/assets/images/ablum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/src/assets/images/ablum.png -------------------------------------------------------------------------------- /src/style/base.scss: -------------------------------------------------------------------------------- 1 | @import './mixin.scss'; 2 | @import './variables.scss'; 3 | @import './global.scss'; 4 | -------------------------------------------------------------------------------- /src/assets/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/src/assets/images/loading.gif -------------------------------------------------------------------------------- /public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /src/assets/font-icon/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/src/assets/font-icon/fonts/icomoon.eot -------------------------------------------------------------------------------- /src/assets/font-icon/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/src/assets/font-icon/fonts/icomoon.ttf -------------------------------------------------------------------------------- /src/assets/font-icon/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/src/assets/font-icon/fonts/icomoon.woff -------------------------------------------------------------------------------- /public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFmiss/vue-music/v2.0/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /src/store/modules/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 配置类的信息 3 | */ 4 | // import * as types from 'store/mutation-types' 5 | // import axios from 'utils/http' 6 | // import API from '@/api/index' -------------------------------------------------------------------------------- /src/views/account/index.vue: -------------------------------------------------------------------------------- 1 | // 账户相关 2 | 5 | 9 | 11 | -------------------------------------------------------------------------------- /src/views/firends/index.vue: -------------------------------------------------------------------------------- 1 | // 朋友相关 2 | 5 | 9 | 11 | -------------------------------------------------------------------------------- /src/style/view.scss: -------------------------------------------------------------------------------- 1 | // 这是覆盖样式的布局 2 | // 覆盖swiper 3 | .swiper-container-horizontal > .swiper-pagination-bullets .swiper-pagination-bullet{ 4 | margin: 0 3px; 5 | } 6 | .swiper-pagination-bullet-active { 7 | background: $primary_color !important; 8 | } -------------------------------------------------------------------------------- /src/views/video/index.vue: -------------------------------------------------------------------------------- 1 | // 视频模块 2 | 5 | 12 | 14 | -------------------------------------------------------------------------------- /src/views/find/station/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 12 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | '@vue/standard' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'off' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'off' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import Login from './modules/login' 4 | import Mine from './modules/mine' 5 | import Find from './modules/find' 6 | import Music from './modules/music' 7 | // import createLogger from 'vuex/dist/logger' 8 | 9 | Vue.use(Vuex) 10 | const store = new Vuex.Store({ 11 | // plugins: [createLogger()], 12 | modules: { 13 | Login, 14 | Mine, 15 | Find, 16 | Music 17 | } 18 | }) 19 | export default store 20 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 24 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-music", 3 | "short_name": "vue-music", 4 | "icons": [ 5 | { 6 | "src": "/img/icons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/img/icons/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": "/index.html", 17 | "display": "standalone", 18 | "background_color": "#000000", 19 | "theme_color": "#4DBA87" 20 | } 21 | -------------------------------------------------------------------------------- /src/filter/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /** 3 | * 返回听歌的用户数量 添加万 亿 单位的 4 | */ 5 | parseNumber: (value) => { 6 | return value >= 100000 ? (value >= 100000000 ? (value / 100000000).toFixed(1) + '亿' : Math.floor(value / 10000) + '万') : value 7 | }, 8 | 9 | /** 10 | * 返回倒计时时分秒 11 | */ 12 | parseMusicTime: (value) => { 13 | let t = Math.floor(value) 14 | let m = Math.floor(t / 60) 15 | let s = t % 60 16 | let strS = String(s).padStart(2, '0') 17 | let strM = String(m).padStart(2, '0') 18 | return strM + ' : ' + strS 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/store/modules/find.js: -------------------------------------------------------------------------------- 1 | import * as types from 'store/mutation-types' 2 | import axios from 'utils/http' 3 | import API from '@/api/index' 4 | let state = {} 5 | 6 | const getters = {} 7 | 8 | let actions = {} 9 | let mutations = {} 10 | 11 | /** 12 | * 获取首页Banner信息 13 | */ 14 | actions[types.BANNER_LISTS] = ({commit}) => { 15 | return new Promise((resolve, reject) => { 16 | axios.get(API.find.BANNER_LISTS).then(res => { 17 | resolve(res) 18 | }, err => { 19 | reject(err) 20 | }) 21 | }) 22 | } 23 | 24 | export default { 25 | state, 26 | getters, 27 | actions, 28 | mutations 29 | } 30 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue-music 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/store/modules/mine.js: -------------------------------------------------------------------------------- 1 | import * as types from 'store/mutation-types' 2 | import axios from 'utils/http' 3 | import API from '@/api/index' 4 | let state = {} 5 | 6 | const getters = {} 7 | 8 | let actions = {} 9 | let mutations = {} 10 | 11 | /** 12 | * 用户信息,歌单,收藏,mv,dj数量 13 | */ 14 | actions[types.MINE_AUTO_INFO] = ({commit}, uid) => { 15 | return new Promise((resolve, reject) => { 16 | axios.get(API.user.MINE_AUTO_INFO, { 17 | params: { 18 | uid 19 | } 20 | }).then(res => { 21 | resolve(res) 22 | }, err => { 23 | reject(err) 24 | }) 25 | }) 26 | } 27 | 28 | export default { 29 | state, 30 | getters, 31 | actions, 32 | mutations 33 | } 34 | -------------------------------------------------------------------------------- /src/directive/imgsize.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | /** 4 | * @description 用于动态设置返回的图片地址大小 只用于img标签 5 | * v-imgsize 6 | * 字符串 7 | * v-imgsize = "http://www......" 8 | * 对象写法 9 | * v-imgsize = "{url: 'http://......', w: 120}" 10 | */ 11 | Vue.directive('imgsize', { 12 | bind (el, binding, vnode) { 13 | if (typeof binding.value === 'object' && 14 | binding.value.w) { 15 | el.src = `${binding.value.url}?param=${binding.value.w}y${binding.value.w}` 16 | return 17 | } 18 | el.src = binding.value + '?param=300y300' 19 | }, 20 | update (el, binding) { 21 | let w = binding.value.w || 300 22 | let value = binding.value.w ? binding.value.url : binding.value 23 | el.src = `${value}?param=${w}y${w}` 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /src/assets/font-icon/Read Me.txt: -------------------------------------------------------------------------------- 1 | Open *demo.html* to see a list of all the glyphs in your font along with their codes/ligatures. 2 | 3 | To use the generated font in desktop programs, you can install the TTF font. In order to copy the character associated with each icon, refer to the text box at the bottom right corner of each glyph in demo.html. The character inside this text box may be invisible; but it can still be copied. See this guide for more info: https://icomoon.io/#docs/local-fonts 4 | 5 | You won't need any of the files located under the *demo-files* directory when including the generated font in your own projects. 6 | 7 | You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu → Manage Projects) to retrieve your icon selection. 8 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { register } from 'register-service-worker' 4 | 5 | if (process.env.NODE_ENV === 'production') { 6 | register(`${process.env.BASE_URL}service-worker.js`, { 7 | ready () { 8 | console.log( 9 | 'App is being served from cache by a service worker.\n' + 10 | 'For more details, visit https://goo.gl/AFskqB' 11 | ) 12 | }, 13 | cached () { 14 | console.log('Content has been cached for offline use.') 15 | }, 16 | updated () { 17 | console.log('New content is available; please refresh.') 18 | }, 19 | offline () { 20 | console.log('No internet connection found. App is running in offline mode.') 21 | }, 22 | error (error) { 23 | console.error('Error during service worker registration:', error) 24 | } 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | // 登陆相关 2 | 9 | 13 | 43 | -------------------------------------------------------------------------------- /src/utils/http.js: -------------------------------------------------------------------------------- 1 | import vueProject from '@/main.js' 2 | import axios from 'axios' 3 | // 全局的音乐播放api地址 4 | const MUSIC_API = process.env.VUE_APP_MUSIC_URL 5 | let http = axios.create({ 6 | timeout: 15000, 7 | withCredentials: true, 8 | baseURL: MUSIC_API, 9 | params: { 10 | ts: new Date().getTime() 11 | } 12 | }) 13 | 14 | /** 15 | * 请求拦截 16 | */ 17 | http.interceptors.request.use(function (config) { 18 | return config 19 | }, function (error) { 20 | return Promise.reject(error) 21 | }) 22 | 23 | /** 24 | * 响应拦截 25 | */ 26 | http.interceptors.response.use(res => { 27 | if (res.data.code === 200) { 28 | return Promise.resolve(res) 29 | } 30 | 31 | if ('success' in res.data) { 32 | return Promise.resolve(res) 33 | } 34 | let msg = res.data.msg ? res.data.msg : '数据请求报错' 35 | vueProject && vueProject.$msg && vueProject.$msg({text: msg, background: '#FF3366'}) 36 | return Promise.reject(res) 37 | }, error => { 38 | return Promise.reject(error) 39 | }) 40 | export default http 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 3.0都来了 学啥2.0 啊 🐶 2 | ## 代码已不维护,可移步 https://github.com/IFmiss/vue-website 3 | ## [demo地址](https://v2.daiwei.site/); 4 | 5 | ### vue-music 6 | 7 | 这是新的vue音乐播放器 UI 基本是仿照网易云音乐IOS系统的UI 8 | 9 | 新的代码才刚开始,不建议继续看以前[v1.0](https://github.com/IFmiss/vue-music/tree/v1.0)的代码,因为真的有待完善,所以我必须要写一个稍微靠谱一点的播放器,不能让你们和我一样踩同样的坑 10 | 11 | 最新的播放器会使用 [NeteaseCloudMusicApi](https://binaryify.github.io/NeteaseCloudMusicApi/#/)的API,有兴趣可以看看 12 | 13 | #### 项目安装 (保证vue-music和NeteaseCloudMusicApi为同级文件夹) 14 | - step 1. 将最新的vue-music代码拉到本地 15 | - step 2. 将NeteaseCloudMusicApi拉到本地 16 | - step 3. 启动 NeteaseCloudMusicApi (npm start) 默认 端口为3000 如果觉得麻烦可以直接访问 https://daiwei.site/netease 调用api 17 | - setp 4. 启动vue-music (npm run dev) 如果NeteaseCloudMusicApi是默认端口3000, 在vue-music项目中登陆之后基本可以跑通接口了 18 | 19 | #### vue-cli3 20 | 项目基于vue-cli3.0搭建的vue开发环境 21 | 22 | #### pug 23 | vue的模版编辑都是基于[pug](https://pug.bootcss.com/api/getting-started.html)的,使用这个模版引擎会使得编写template里的内容会很方便 24 | 25 | 项目刚开始做的,而且正在进行中 26 | 27 | 想看之前老版本的代码,请切换到[v1.0](https://github.com/IFmiss/vue-music/tree/v1.0)的代码 28 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import Http from './http' 2 | /** 3 | * 通用js 的一些代码 4 | */ 5 | export default { 6 | /** 7 | * 基于http.js二次封装 8 | * @param { Sring } url 请求地址 9 | * @param { Object } data 请求参数 10 | * @param { String } type 请求类型,get还是post 默认get 11 | * @return { Promise } 返回一个Promise resove会返回请求成功的数据,reject会提示错误 12 | */ 13 | fetchData (url, data = {}, type = 'get') { 14 | return new Promise((resolve, reject) => { 15 | if (type.toLowerCase() === 'get') { 16 | Http.get(url, { 17 | params: { 18 | ...data 19 | } 20 | }).then(res => { 21 | resolve(res) 22 | }, err => { 23 | reject(err) 24 | }) 25 | return 26 | } 27 | Http.post(url, data).then(res => { 28 | resolve(res) 29 | }, err => { 30 | reject(err) 31 | }) 32 | }) 33 | }, 34 | 35 | /** 36 | * 返回图片的不同宽度的地址 37 | */ 38 | changeImageSize (src, w = 300) { 39 | return `${src}?param=${w}y${w}` 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /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 | "dev": "vue-cli-service serve --mode development" 10 | }, 11 | "dependencies": { 12 | "amfe-flexible": "^2.2.1", 13 | "axios": "^0.18.0", 14 | "better-scroll": "^1.12.6", 15 | "d-js-utils": "^1.0.8", 16 | "node-vibrant": "^3.0.0", 17 | "pug": "^2.0.3", 18 | "pug-plain-loader": "^1.0.0", 19 | "register-service-worker": "^1.0.0", 20 | "vue": "^2.5.17", 21 | "vue-awesome-swiper": "^2.6.7", 22 | "vue-message": "^1.4.3", 23 | "vue-router": "^3.0.1", 24 | "vuex": "^3.0.1" 25 | }, 26 | "devDependencies": { 27 | "@vue/cli-plugin-babel": "^3.0.1", 28 | "@vue/cli-plugin-eslint": "^3.0.1", 29 | "@vue/cli-plugin-pwa": "^3.0.1", 30 | "@vue/cli-service": "^3.0.1", 31 | "@vue/eslint-config-standard": "^3.0.1", 32 | "node-sass": "^4.10.0", 33 | "sass-loader": "^7.0.1", 34 | "vue-template-compiler": "^2.5.17" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/assets/font-icon/demo-files/demo.js: -------------------------------------------------------------------------------- 1 | if (!('boxShadow' in document.body.style)) { 2 | document.body.setAttribute('class', 'noBoxShadow'); 3 | } 4 | 5 | document.body.addEventListener("click", function(e) { 6 | var target = e.target; 7 | if (target.tagName === "INPUT" && 8 | target.getAttribute('class').indexOf('liga') === -1) { 9 | target.select(); 10 | } 11 | }); 12 | 13 | (function() { 14 | var fontSize = document.getElementById('fontSize'), 15 | testDrive = document.getElementById('testDrive'), 16 | testText = document.getElementById('testText'); 17 | function updateTest() { 18 | testDrive.innerHTML = testText.value || String.fromCharCode(160); 19 | if (window.icomoonLiga) { 20 | window.icomoonLiga(testDrive); 21 | } 22 | } 23 | function updateSize() { 24 | testDrive.style.fontSize = fontSize.value + 'px'; 25 | } 26 | fontSize.addEventListener('change', updateSize, false); 27 | testText.addEventListener('input', updateTest, false); 28 | testText.addEventListener('change', updateTest, false); 29 | updateSize(); 30 | }()); 31 | -------------------------------------------------------------------------------- /src/style/variables.scss: -------------------------------------------------------------------------------- 1 | $text_color: #2c3e50; 2 | 3 | $text_gray_color: #373838; 4 | 5 | $text_active: #fff; 6 | 7 | $text_noactive: #e1e1e1; 8 | 9 | // $primary_color: #333; 10 | 11 | $primary_color: rgb(203, 40, 41); 12 | 13 | // 浅色主色 14 | $primary_color_s: #e75e4e; 15 | 16 | // 深色主色 17 | $primary_color_d: #C2473B; 18 | 19 | // 金色 20 | $gold_color: #ffcc99; 21 | 22 | // 按钮通用色 23 | $icon_color: #aaa; 24 | 25 | $icon_color_nav: #c1c1c1; 26 | 27 | // 边框通用色 28 | $border_color: #eee; 29 | 30 | $bg_gray: #f3f3f3; 31 | 32 | // 白色图标 33 | $icon-f: #fff; 34 | 35 | // 输入框提示颜色 36 | $placeholder_color: #ccc; 37 | 38 | $color_gray: #aaa; 39 | 40 | $color_gray_deep: #a1a1a1; 41 | 42 | $color_drak_slow: #999; 43 | 44 | $color_gray_slow: #bcbcbc; 45 | 46 | // 字体大小设置 47 | // 登录input输入框文字大小 48 | $f_auto_s: p2r(0.32rem); 49 | 50 | // 登陆按钮文字 / 头部title文字大小 51 | $f_auto_l: p2r(0.34rem); 52 | 53 | // 底部菜单按钮下的小文字,最小的类型 54 | $f_small_s: p2r(0.2rem); 55 | 56 | $f_small_x: p2r(0.24rem); 57 | 58 | // 歌单列表名字下方的描述信息 59 | $f_small_m: p2r(0.26rem); 60 | 61 | // mine 中的tip用户名字大小 62 | $f_small_l: p2r(0.28rem); 63 | 64 | 65 | // ======================= 高度宽度 66 | 67 | // 底部菜单高度 68 | $NAV_H: p2r(0.9rem); 69 | 70 | // 头部内容高度 71 | $HEADER_H: p2r(1.55rem); 72 | 73 | // 仅仅搜索头部的高度 74 | $HEADER_H_S: p2r(0.86rem); 75 | 76 | 77 | $auto_h: p2r(1rem); 78 | 79 | $auto_padding_l_r: p2r(0.15rem); 80 | $auto_padding_t_b: p2r(0.15rem); -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store/index' 5 | import './registerServiceWorker' 6 | import 'amfe-flexible' 7 | // 图标字体 8 | import '@/assets/font-icon/style.css' 9 | 10 | // 提示组件 11 | import Msg from 'vue-message' 12 | 13 | // 通用js组件 来自 d-js-utils 14 | import Dutils from 'd-js-utils' 15 | 16 | // 项目内的通用组件挂在 来自 utils/index.js 17 | import Mutils from 'utils' 18 | 19 | import VueAwesomeSwiper from 'vue-awesome-swiper' 20 | import 'swiper/dist/css/swiper.css' 21 | import 'style/view.scss' 22 | import filters from 'filter' 23 | 24 | // 引入指令 25 | import 'directive/imgsize' 26 | 27 | Vue.use(VueAwesomeSwiper) 28 | 29 | Vue.use(Msg, { 30 | text: 'Hello world', duration: 3000, background: 'rgba(7,8,9,0.8)' 31 | }) 32 | 33 | // 挂载 34 | Vue.prototype.$dutils = Dutils 35 | Vue.prototype.$mutils = Mutils 36 | // 手机横屏提示 37 | Vue.prototype.$dutils.device.checkLayoutOrientation() 38 | 39 | Vue.config.productionTip = false 40 | 41 | // 准备实例化 42 | let vueProject = new Vue({ 43 | router, 44 | store, 45 | render: h => h(App) 46 | }).$mount('#app') 47 | 48 | // 注册filter 49 | Object.keys(filters).forEach(key => { 50 | Vue.filter(key, filters[key]) 51 | }) 52 | 53 | // 初始化用户登陆状态校验 54 | store.dispatch('LOGIN_STATUS_SETTERS').then(() => { 55 | vueProject.$msg('登陆校验成功') 56 | }, () => { 57 | vueProject.$msg('登陆校验失败') 58 | }) 59 | 60 | export default vueProject 61 | -------------------------------------------------------------------------------- /src/views/find/search/singer/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 23 | 61 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 3 | function resolve (dir) { 4 | return path.join(__dirname, dir) 5 | } 6 | // vue 的配置 7 | module.exports = { 8 | // 部署应用时的基本 URL。用法和 webpack 本身的 output.publicPath 一致,但是 Vue CLI 在一些其他地方也需要用到这个值,所以请始终使用 baseUrl 而不要直接修改 webpack 的 output.publicPath 9 | baseUrl: process.env.NODE_ENV === 'production' 10 | ? '/vue-music/' 11 | : '/', 12 | // 当运行 vue-cli-service build 时生成的生产环境构建文件的目录。注意目标目录在构建之前会被清除 (构建时传入 --no-clean 可关闭该行为)。 13 | outputDir: 'dist', 14 | // 是否在开发环境下通过 eslint-loader 在每次保存时 lint 代码。这个值会在 @vue/cli-plugin-eslint 被安装之后生效。 15 | lintOnSave: 'error', 16 | productionSourceMap: false, 17 | css: { 18 | loaderOptions: { 19 | sass: { 20 | data: `@import "@/style/base.scss";` 21 | } 22 | } 23 | }, 24 | devServer: { 25 | proxy: '', 26 | port: '1111' 27 | }, 28 | chainWebpack: (config) => { 29 | // 配置alias 30 | config.resolve.alias 31 | .set('vue$', 'vue/dist/vue.esm.js') 32 | .set('@', resolve('src')) 33 | .set('assets', resolve('src/assets')) 34 | .set('components', resolve('src/components')) 35 | .set('config', resolve('src/config')) 36 | .set('style', resolve('src/style')) 37 | .set('utils', resolve('src/utils')) 38 | .set('views', resolve('src/views')) 39 | .set('store', resolve('src/store')) 40 | .set('api', resolve('src/api')) 41 | .set('filter', resolve('src/filter')) 42 | .set('directive', resolve('src/directive')) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | index: path.resolve(__dirname, '../dist/index.html'), 8 | assetsRoot: path.resolve(__dirname, '../dist'), 9 | assetsSubDirectory: 'static', 10 | assetsPublicPath: './', 11 | productionSourceMap: true, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'], 18 | // Run the build command with an extra argument to 19 | // View the bundle analyzer report after build finishes: 20 | // `npm run build --report` 21 | // Set to `true` or `false` to always turn it on or off 22 | bundleAnalyzerReport: process.env.npm_config_report 23 | }, 24 | dev: { 25 | env: require('./dev.env'), 26 | port: 8080, 27 | autoOpenBrowser: true, 28 | assetsSubDirectory: 'static', 29 | assetsPublicPath: '/', 30 | proxyTable: {}, 31 | // CSS Sourcemaps off by default because relative paths are "buggy" 32 | // with this option, according to the CSS-Loader README 33 | // (https://github.com/webpack/css-loader#sourcemaps) 34 | // In our experience, they generally work as expected, 35 | // just be aware of this issue when enabling this option. 36 | cssSourceMap: false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/components/loading/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 40 | 86 | -------------------------------------------------------------------------------- /src/style/global.scss: -------------------------------------------------------------------------------- 1 | a{ 2 | text-decoration: none; 3 | color: $text_color; 4 | &:hover, &:active, &:link{ 5 | color: $text_color; 6 | } 7 | } 8 | 9 | // 让按钮更舒服的点击 10 | .easy-click{ 11 | &::after{ 12 | position: absolute; 13 | content: ''; 14 | top: - p2r(0.2rem); 15 | left: - p2r(0.2rem); 16 | right: - p2r(0.2rem); 17 | bottom: - p2r(0.2rem); 18 | } 19 | } 20 | 21 | body { 22 | -webkit-tap-highlight-color:rgba(0,0,0,0); 23 | } 24 | 25 | ::-webkit-input-placeholder { 26 | color: $placeholder_color; 27 | } 28 | :-moz-placeholder { 29 | color: $placeholder_color; 30 | } 31 | ::-moz-placeholder { 32 | color: $placeholder_color; 33 | } 34 | :-ms-input-placeholder { 35 | color: $placeholder_color; 36 | } 37 | 38 | // 动画效果 39 | // sider-fade-left 40 | .fade-left-enter-to, 41 | .fade-left-leave-to{ 42 | transition: all .3s; 43 | } 44 | .fade-left-leave-to{ 45 | transform: translateX(15px); 46 | opacity: 0; 47 | } 48 | .fade-left-enter{ 49 | transform: translateX(15px); 50 | opacity: 0; 51 | } 52 | 53 | // sider-left 54 | .sider-left-enter-to, 55 | .sider-left-leave-to{ 56 | transition: all .3s; 57 | } 58 | .sider-left-leave-to{ 59 | transform: translateX(15px); 60 | } 61 | .sider-left-enter{ 62 | transform: translateX(15px); 63 | } 64 | 65 | // sider-top 66 | .sider-top-enter-to, 67 | .sider-top-leave-to{ 68 | transition: all .3s; 69 | } 70 | .sider-top-leave-to{ 71 | transform: translateY(120%); 72 | } 73 | .sider-top-enter{ 74 | transform: translateY(120%); 75 | } 76 | 77 | // fade效果 78 | .fade-enter-to, 79 | .fade-leave-to{ 80 | transition: all .3s; 81 | } 82 | .fade-leave-to{ 83 | opacity: 0; 84 | } 85 | .fade-enter{ 86 | opacity: 0; 87 | } 88 | 89 | // 旋转 90 | .rotate { 91 | animation: round 15s linear infinite; 92 | } 93 | 94 | @keyframes round { 95 | 100% { 96 | transform: rotate(1turn) 97 | } 98 | } -------------------------------------------------------------------------------- /src/views/find/search/radio/index.vue: -------------------------------------------------------------------------------- 1 | // 搜索的电台显示效果 2 | 11 | 23 | 24 | 79 | -------------------------------------------------------------------------------- /src/components/musicsiderlist/index.vue: -------------------------------------------------------------------------------- 1 | // play页面呼起的列表内容 2 | 10 | 51 | 80 | -------------------------------------------------------------------------------- /src/views/find/search/sheet/index.vue: -------------------------------------------------------------------------------- 1 | // 搜索的歌单显示效果 2 | 12 | 24 | 25 | 76 | -------------------------------------------------------------------------------- /src/store/modules/login.js: -------------------------------------------------------------------------------- 1 | import * as types from 'store/mutation-types' 2 | import axios from 'utils/http' 3 | import API from '@/api/index' 4 | import router from '@/router' 5 | 6 | let state = { 7 | [types.LOGIN_STATUS]: {} 8 | } 9 | 10 | const getters = { 11 | [types.LOGIN_STATUS_GETTERS]: state => state.LOGIN_STATUS 12 | } 13 | 14 | let actions = {} 15 | let mutations = {} 16 | 17 | /** 18 | * 登录获取用户信息 19 | * @param { commit } 执行mutations 20 | * @param { Object } data 参数 包括data.type (phone || email) 和 data.password 21 | * 手机----------------: 22 | * 必选参数 : phone: 手机号码 password: 密码 23 | * 接口地址 : /login/cellphone 24 | * 调用例子 : /login/cellphone?phone=xxx&password=yyy 25 | * 邮箱----------------: 26 | * 必选参数 : email: 163 网易邮箱 password: 密码 27 | * 接口地址 : /login 28 | * 调用例子 : /login?email=xxx@163.com&password=yyy 29 | */ 30 | actions[types.USER_LOGIN] = ({commit}, data) => { 31 | let url = data.type === 'phone' ? API.login.USER_LOGIN_PHONE : API.login.USER_LOGIN_EMAIL 32 | return new Promise((resolve, reject) => { 33 | axios.get(url, { 34 | params: { 35 | ...data 36 | } 37 | }).then(res => { 38 | resolve(res) 39 | }, err => { 40 | reject(err) 41 | }) 42 | }) 43 | } 44 | 45 | /** 46 | * 获取用户的登陆状态 47 | */ 48 | mutations[types.LOGIN_STATUS_SETTERS] = (state, data) => { 49 | state[types.LOGIN_STATUS] = data 50 | } 51 | 52 | actions[types.LOGIN_STATUS_SETTERS] = ({commit}, data) => { 53 | return new Promise((resolve, reject) => { 54 | axios.get(API.login.USER_LOGIN_STATUS).then(res => { 55 | if (!res.data.bindings[0] || res.data.bindings[0].expired) { 56 | commit(types.LOGIN_STATUS_SETTERS, {}) 57 | reject(new Error('登陆过期')) 58 | router.push('/login') 59 | return 60 | } 61 | commit(types.LOGIN_STATUS_SETTERS, res.data.profile) 62 | resolve(res) 63 | }, err => { 64 | commit(types.LOGIN_STATUS_SETTERS, {}) 65 | router.push('/login') 66 | reject(err) 67 | }) 68 | }) 69 | } 70 | 71 | export default { 72 | state, 73 | getters, 74 | actions, 75 | mutations 76 | } 77 | -------------------------------------------------------------------------------- /src/components/nav/index.vue: -------------------------------------------------------------------------------- 1 | // 底部固定菜单 2 | 8 | 54 | 91 | -------------------------------------------------------------------------------- /src/views/Main.vue: -------------------------------------------------------------------------------- 1 | // 主页 只是个包含底部菜单以及模块内容 2 | 15 | 56 | 99 | -------------------------------------------------------------------------------- /src/style/mixin.scss: -------------------------------------------------------------------------------- 1 | // 多少行限制超出省略 2 | // 整个元素的高度, 行数 3 | @mixin lineclamp($height, $count) { 4 | height: $height; 5 | line-height: $height / $count; 6 | overflow:hidden; 7 | text-overflow:ellipsis; 8 | word-wrap: break-word; 9 | display:-webkit-box; 10 | -webkit-box-orient:vertical; 11 | -webkit-line-clamp: $count; 12 | } 13 | 14 | // fixed布局铺面全屏 15 | @mixin fixedfull { 16 | position: fixed; 17 | right: 0; 18 | top: 0; 19 | left: 0; 20 | bottom: 0; 21 | } 22 | 23 | // 单行省略 24 | @mixin els { 25 | overflow:hidden; 26 | text-overflow:ellipsis; 27 | word-wrap: break-word; 28 | white-space: nowrap; 29 | } 30 | 31 | // 1像素的问题 32 | @mixin border-1px ($color: #eee, $type: bottom) { 33 | &:after { 34 | height: 1px; 35 | content: ''; 36 | width: 100%; 37 | background: $color; 38 | position: absolute; 39 | left: 0; 40 | right: 0; 41 | } 42 | @if ($type == top) { 43 | &:after { 44 | top: 0; 45 | } 46 | } @else { 47 | &::after{ 48 | bottom: 0; 49 | } 50 | } 51 | @media (-webkit-min-device-pixel-ratio: 1) { 52 | &:after { 53 | transform: scaleY(1); 54 | -webkit-transform: scaleY(1); 55 | } 56 | } 57 | @media (-webkit-min-device-pixel-ratio: 2) { 58 | &:after { 59 | transform: scaleY(0.5); 60 | -webkit-transform: scaleY(0.5); 61 | } 62 | } 63 | @media (-webkit-min-device-pixel-ratio: 3) { 64 | &:after { 65 | transform: scaleY(0.3333); 66 | -webkit-transform: scaleY(0.3333); 67 | } 68 | } 69 | } 70 | 71 | // amfe-flexible 定义宽度10rem是整个屏幕的宽度,是按照750 / 7.5算的,p2r是一种以宽度为7.5rem计算的方式 72 | // 虽然实际上会被转换,但是在编写和计算宽度的时候很方便 73 | @function p2r($rem) { 74 | @return $rem / 7.5 * 10 75 | }; 76 | 77 | // 渐变的效果 78 | @function lg($direction: 90deg, $colors...) { 79 | @return linear-gradient($direction, $colors); 80 | } 81 | 82 | // 添加blur模糊效果 83 | // 如果不是绝对定位,父元素需要设置相对定位 84 | // $blur 为模糊的数值 85 | // $height 区域的高度 86 | // $position 为位置 默认50% 87 | @mixin blur($blur, $height, $position: 50%, $scale: 1.5) { 88 | position: absolute; 89 | top: 0; 90 | left: 0; 91 | right: 0; 92 | background-repeat: no-repeat; 93 | background-size: cover; 94 | background-position: $position; 95 | -webkit-filter: blur($blur); 96 | filter: blur($blur); 97 | -webkit-transform: scale($scale); 98 | transform: scale($scale); 99 | overflow: hidden; 100 | &::before{ 101 | content: ''; 102 | position: absolute; 103 | left: 0; 104 | top: 0; 105 | right: 0; 106 | bottom: 0; 107 | z-index: 1; 108 | background: rgba(0,0,0,0.1); 109 | transition: all 0.3s; 110 | } 111 | @if ($height == auto) { 112 | bottom: 0; 113 | } 114 | @else { 115 | height: $height; 116 | } 117 | &.draken{ 118 | &::before{ 119 | background: rgba(0,0,0,0.3); 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /src/assets/font-icon/demo-files/demo.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 0; 3 | margin: 0; 4 | font-family: sans-serif; 5 | font-size: 1em; 6 | line-height: 1.5; 7 | color: #555; 8 | background: #fff; 9 | } 10 | h1 { 11 | font-size: 1.5em; 12 | font-weight: normal; 13 | } 14 | small { 15 | font-size: .66666667em; 16 | } 17 | a { 18 | color: #e74c3c; 19 | text-decoration: none; 20 | } 21 | a:hover, a:focus { 22 | box-shadow: 0 1px #e74c3c; 23 | } 24 | .bshadow0, input { 25 | box-shadow: inset 0 -2px #e7e7e7; 26 | } 27 | input:hover { 28 | box-shadow: inset 0 -2px #ccc; 29 | } 30 | input, fieldset { 31 | font-family: sans-serif; 32 | font-size: 1em; 33 | margin: 0; 34 | padding: 0; 35 | border: 0; 36 | } 37 | input { 38 | color: inherit; 39 | line-height: 1.5; 40 | height: 1.5em; 41 | padding: .25em 0; 42 | } 43 | input:focus { 44 | outline: none; 45 | box-shadow: inset 0 -2px #449fdb; 46 | } 47 | .glyph { 48 | font-size: 16px; 49 | width: 15em; 50 | padding-bottom: 1em; 51 | margin-right: 4em; 52 | margin-bottom: 1em; 53 | float: left; 54 | overflow: hidden; 55 | } 56 | .liga { 57 | width: 80%; 58 | width: calc(100% - 2.5em); 59 | } 60 | .talign-right { 61 | text-align: right; 62 | } 63 | .talign-center { 64 | text-align: center; 65 | } 66 | .bgc1 { 67 | background: #f1f1f1; 68 | } 69 | .fgc1 { 70 | color: #999; 71 | } 72 | .fgc0 { 73 | color: #000; 74 | } 75 | p { 76 | margin-top: 1em; 77 | margin-bottom: 1em; 78 | } 79 | .mvm { 80 | margin-top: .75em; 81 | margin-bottom: .75em; 82 | } 83 | .mtn { 84 | margin-top: 0; 85 | } 86 | .mtl, .mal { 87 | margin-top: 1.5em; 88 | } 89 | .mbl, .mal { 90 | margin-bottom: 1.5em; 91 | } 92 | .mal, .mhl { 93 | margin-left: 1.5em; 94 | margin-right: 1.5em; 95 | } 96 | .mhmm { 97 | margin-left: 1em; 98 | margin-right: 1em; 99 | } 100 | .mls { 101 | margin-left: .25em; 102 | } 103 | .ptl { 104 | padding-top: 1.5em; 105 | } 106 | .pbs, .pvs { 107 | padding-bottom: .25em; 108 | } 109 | .pvs, .pts { 110 | padding-top: .25em; 111 | } 112 | .unit { 113 | float: left; 114 | } 115 | .unitRight { 116 | float: right; 117 | } 118 | .size1of2 { 119 | width: 50%; 120 | } 121 | .size1of1 { 122 | width: 100%; 123 | } 124 | .clearfix:before, .clearfix:after { 125 | content: " "; 126 | display: table; 127 | } 128 | .clearfix:after { 129 | clear: both; 130 | } 131 | .hidden-true { 132 | display: none; 133 | } 134 | .textbox0 { 135 | width: 3em; 136 | background: #f1f1f1; 137 | padding: .25em .5em; 138 | line-height: 1.5; 139 | height: 1.5em; 140 | } 141 | #testDrive { 142 | display: block; 143 | padding-top: 24px; 144 | line-height: 1.5; 145 | } 146 | .fs0 { 147 | font-size: 16px; 148 | } 149 | .fs1 { 150 | font-size: 32px; 151 | } 152 | 153 | -------------------------------------------------------------------------------- /src/views/find/search/user/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 26 | 104 | -------------------------------------------------------------------------------- /src/views/find/search/album/index.vue: -------------------------------------------------------------------------------- 1 | // 搜索的歌单显示效果 2 | 12 | 37 | 38 | 104 | -------------------------------------------------------------------------------- /src/components/commonpage/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 52 | 128 | -------------------------------------------------------------------------------- /src/components/minesheet/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 31 | 116 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | const API = { 2 | /** 3 | * ==================== 4 | * 登录 5 | * ==================== 6 | */ 7 | login: { 8 | // 手机号 9 | USER_LOGIN_PHONE: `/login/cellphone`, 10 | // 邮箱 11 | USER_LOGIN_EMAIL: `/login`, 12 | // 登录刷新 可刷新登录状态 13 | USER_LOGIN_REFRESH: `/login/refresh`, 14 | // 调用此接口,可获取登录状态 15 | USER_LOGIN_STATUS: `/login/status` 16 | }, 17 | 18 | /** 19 | * ==================== 20 | * 用户相关接口 21 | * ==================== 22 | */ 23 | user: { 24 | // 获取用户信息,歌单,收藏,mv,dj数量 25 | MINE_AUTO_INFO: `/user/subcount` 26 | }, 27 | 28 | /** 29 | * ==================== 30 | * 发现页面 31 | * ==================== 32 | */ 33 | find: { 34 | // banner 幻灯片列表 35 | BANNER_LISTS: `/banner`, 36 | 37 | // 音乐搜索 38 | MUSIC_SEARCH: `/search`, 39 | 40 | // 最新音乐 41 | NEW_SONG_LISTS: `/personalized/newsong` 42 | }, 43 | 44 | /** 45 | * ==================== 46 | * 歌单 47 | * ==================== 48 | */ 49 | sheet: { 50 | // 获取推荐歌单 51 | RECOMMEND_SHEET_LISTS: `/personalized`, 52 | 53 | // 每日推荐歌曲 54 | RECOMMEND_SONGS_LISTS: `/recommend/songs`, 55 | 56 | // 获取精品歌单 57 | HIGHT_QUALITY_SHEET_LISTS: `/top/playlist/highquality`, 58 | 59 | // 调用此接口 , 可获取网友精选碟歌单 60 | // parmas: cat=华语 分类 61 | SHEET_LISTS: `/top/playlist`, 62 | 63 | // 获取歌单分类 64 | CAT_LISTS: `/playlist/catlist`, 65 | 66 | // 获取歌单详情 参数id为歌单id 67 | SHEET_DETAIL_LISTS: `/playlist/detail`, 68 | 69 | // 获取排行榜信息 70 | RANK_SHEET_LISTS: `/toplist/detail`, 71 | 72 | // 获取专辑列表信息 参数id 73 | SHEET_ALBUM_LISTS: `/album`, 74 | 75 | // 歌单收藏 | 取消收藏 76 | // t : 类型,1:收藏,2:取消收藏 id : 歌单 id 77 | SHEET_SUBSCRIBE: `/playlist/subscribe` 78 | }, 79 | 80 | /** 81 | * ==================== 82 | * 音乐详情 83 | * ==================== 84 | */ 85 | music: { 86 | // 获取歌词 87 | MUSIC_LRC: '/lyric', 88 | // 获取音乐详情 89 | MUSIC_DETAIL: '/song/detail', 90 | // 检查音乐是否可以播放 91 | CHECK_MUSIC: '/check/music' 92 | }, 93 | 94 | /** 95 | * ==================== 96 | * 搜索相关接口 97 | * ==================== 98 | */ 99 | search: { 100 | /** 101 | * 必选参数 : keywords : 关键词 102 | * 可选参数 : 103 | * limit : 返回数量 , 默认为 30 104 | * offset : 偏移数量,用于分页 , 如 : 如 :( 页数 -1)*30, 其中 30 为 limit 的值 , 默认为 0 105 | * type: 搜索类型;默认为 1 即单曲 , 取值意义 : 1: 单曲 10: 专辑 100: 歌手 1000: 歌单 1002: 用户 1004: MV 1006: 歌词 1009: 电台 106 | */ 107 | // 调用此接口 , 传入搜索关键词可获得搜索建议 , 搜索结果同时包含单曲 , 歌手 , 歌单 ,mv 信息 108 | SEARCH_SUGGEST: '/search/suggest', 109 | 110 | // 说明 : 调用此接口,可获取热门搜索列表 111 | SEARCH_HOT: '/search/hot', 112 | 113 | // 说明 : 调用此接口 , 传入搜索关键词可以搜索该音乐 / 专辑 / 歌手 / 歌单 / 用户 , 关键词可以多个 , 以空格隔开 , 如 " 周杰伦 搁浅 "( 不需要登录 ), 搜索获取的 mp3url 不能直接用 , 可通过 /song/url 接口传入歌曲 id 获取具体的播放链接 114 | SEARCH_MAIN: '/search' 115 | }, 116 | 117 | /** 118 | * ==================== 119 | * 本地存储相关的,item的名字 120 | * ==================== 121 | */ 122 | storage: { 123 | SEARCH_LISTS: 'searchLists' 124 | } 125 | } 126 | export default API 127 | -------------------------------------------------------------------------------- /src/views/find/daily/index.vue: -------------------------------------------------------------------------------- 1 | // 每日推荐音乐列表 2 | 14 | 86 | 114 | -------------------------------------------------------------------------------- /src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | // 登录 2 | export const USER_LOGIN = 'USER_LOGIN' 3 | // 设置用户信息 4 | export const LOGIN_STATUS = 'LOGIN_STATUS' 5 | export const LOGIN_STATUS_GETTERS = 'LOGIN_STATUS_GETTERS' 6 | export const LOGIN_STATUS_SETTERS = 'LOGIN_STATUS_SETTERS' 7 | 8 | // Mine 个人中心 获取用户信息,歌单,收藏,mv,dj数量 9 | export const MINE_AUTO_INFO = 'MINE_AUTO_INFO' 10 | 11 | // 获取banner轮播图信息 12 | export const BANNER_LISTS = 'BANNER_LISTS' 13 | 14 | /** 15 | * ================================ 16 | * ========== 播放相关 =========== 17 | * ================================ 18 | */ 19 | // 播放列表集合 20 | export const PLAY_MUSIC_LISTS = 'PLAY_MUSIC_LISTS' 21 | export const PLAY_MUSIC_LISTS_GETTERS = 'PLAY_MUSIC_LISTS_GETTERS' 22 | export const PLAY_MUSIC_LISTS_SETTERS = 'PLAY_MUSIC_LISTS_SETTERS' 23 | 24 | // 播放的索引信息 25 | export const PLAY_MUSIC_INDEX = 'PLAY_MUSIC_INDEX' 26 | export const PLAY_MUSIC_INDEX_GETTERS = 'PLAY_MUSIC_INDEX_GETTERS' 27 | export const PLAY_MUSIC_INDEX_SETTERS = 'PLAY_MUSIC_INDEX_SETTERS' 28 | 29 | // 播放的列表id 30 | export const PLAY_MUSIC_LISTS_ID = 'PLAY_MUSIC_LISTS_ID' 31 | export const PLAY_MUSIC_LISTS_ID_GETTERS = 'PLAY_MUSIC_LISTS_ID_GETTERS' 32 | export const PLAY_MUSIC_LISTS_ID_SETTERS = 'PLAY_MUSIC_LISTS_ID_SETTERS' 33 | 34 | // 音乐的播放状态是否在播放 35 | export const MUSIC_IS_PLAYING = 'MUSIC_IS_PLAYING' 36 | export const MUSIC_IS_PLAYING_GETTERS = 'MUSIC_IS_PLAYING_GETTERS' 37 | export const MUSIC_IS_PLAYING_SETTERS = 'MUSIC_IS_PLAYING_SETTERS' 38 | 39 | // 音乐播放的类型 40 | export const MUSIC_PLAY_TYPE = 'MUSIC_PLAY_TYPE' 41 | export const MUSIC_PLAY_TYPE_GETTERS = 'MUSIC_PLAY_TYPE_GETTERS' 42 | export const MUSIC_PLAY_TYPE_SETTERS = 'MUSIC_PLAY_TYPE_SETTERS' 43 | 44 | // 当前音乐播放的内容 45 | export const PLAY_MUSIC_LIST = 'PLAY_MUSIC_LIST' 46 | export const PLAY_MUSIC_LIST_SETTERS = 'PLAY_MUSIC_LIST_SETTERS' 47 | export const PLAY_MUSIC_LIST_GETTERS = 'PLAY_MUSIC_LIST_GETTERS' 48 | 49 | // 设置音频播放的时间 50 | export const MUSIC_CURRENT_TIME = 'MUSIC_CURRENT_TIME' 51 | export const MUSIC_CURRENT_TIME_GETTERS = 'MUSIC_CURRENT_TIME_GETTERS' 52 | export const MUSIC_CURRENT_TIME_SETTERS = 'MUSIC_CURRENT_TIME_SETTERS' 53 | 54 | // 设置音频的时长 55 | export const MUSIC_DURATION_TIME = 'MUSIC_DURATION_TIME' 56 | export const MUSIC_DURATION_TIME_GETTERS = 'MUSIC_DURATION_TIME_GETTERS' 57 | export const MUSIC_DURATION_TIME_SETTERS = 'MUSIC_DURATION_TIME_SETTERS' 58 | 59 | // 设置音频的音量大小 60 | export const MUSIC_VOL = 'MUSIC_VOL' 61 | export const MUSIC_VOL_GETTERS = 'MUSIC_VOL_GETTERS' 62 | export const MUSIC_VOL_SETTERS = 'MUSIC_VOL_SETTERS' 63 | 64 | // 正在播放的音乐的详细信息 65 | export const MUSIC_PLAYING_DETAIL = 'MUSIC_PLAYING_DETAIL' 66 | export const MUSIC_PLAYING_DETAIL_GETTERS = 'MUSIC_PLAYING_DETAIL_GETTERS' 67 | export const MUSIC_PLAYING_DETAIL_SETTERS = 'MUSIC_PLAYING_DETAIL_SETTERS' 68 | 69 | // 正在播放的音乐的专辑的近似颜色 70 | export const MUSIC_PLAYING_COLOR = 'MUSIC_PLAYING_COLOR' 71 | export const MUSIC_PLAYING_COLOR_GETTERS = 'MUSIC_PLAYING_COLOR_GETTERS' 72 | export const MUSIC_PLAYING_COLOR_SETTERS = 'MUSIC_PLAYING_COLOR_SETTERS' 73 | 74 | // 右上角菜单的显示隐藏 75 | export const MUSIC_SHOW_FIXED_MENU = 'MUSIC_SHOW_FIXED_MENU' 76 | export const MUSIC_SHOW_FIXED_MENU_GETTERS = 'MUSIC_SHOW_FIXED_MENU_GETTERS' 77 | export const MUSIC_SHOW_FIXED_MENU_SETTERS = 'MUSIC_SHOW_FIXED_MENU_SETTERS' 78 | -------------------------------------------------------------------------------- /src/views/find/index.vue: -------------------------------------------------------------------------------- 1 | // 发现模块 2 | 19 | 31 | 122 | -------------------------------------------------------------------------------- /src/views/find/sheet/sheet-avatar/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 42 | 110 | -------------------------------------------------------------------------------- /src/components/lrc/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 103 | 132 | -------------------------------------------------------------------------------- /src/views/login/sublogin/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 78 | 119 | -------------------------------------------------------------------------------- /src/components/musiclist/index.vue: -------------------------------------------------------------------------------- 1 | // 歌单详情列表 2 | 15 | 76 | 77 | 153 | -------------------------------------------------------------------------------- /src/views/find/search/searchingPanel/index.vue: -------------------------------------------------------------------------------- 1 | // 搜索面板 2 | 20 | 72 | 73 | 128 | -------------------------------------------------------------------------------- /src/components/sheetlist/index.vue: -------------------------------------------------------------------------------- 1 | // 通用音乐歌单列表组件 2 | 14 | 79 | 80 | 156 | -------------------------------------------------------------------------------- /src/components/groupsheet/index.vue: -------------------------------------------------------------------------------- 1 | // 这是首页推荐歌单这种模版 封装成组件 2 | 19 | 67 | 68 | 150 | -------------------------------------------------------------------------------- /src/components/touchbar/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 85 | 141 | -------------------------------------------------------------------------------- /src/components/scroll/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 127 | 170 | -------------------------------------------------------------------------------- /src/views/find/recommend/index.vue: -------------------------------------------------------------------------------- 1 | 27 | 95 | 160 | -------------------------------------------------------------------------------- /src/views/find/sheet/sheet-type/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 76 | 159 | -------------------------------------------------------------------------------- /src/store/modules/music.js: -------------------------------------------------------------------------------- 1 | import * as types from './../mutation-types' 2 | // import axios from 'utils/http' 3 | // import API from '@/api/index' 4 | let state = { 5 | // 播放的列表集合 6 | [types.PLAY_MUSIC_LISTS]: [], 7 | // 音乐播放的当前索引 8 | [types.PLAY_MUSIC_INDEX]: 0, 9 | // 音乐播放的播放集ID 10 | [types.PLAY_MUSIC_LISTS_ID]: null, 11 | // 音乐播放状态 12 | [types.MUSIC_IS_PLAYING]: false, 13 | // 音乐播放类型 auto loop random 14 | [types.MUSIC_PLAY_TYPE]: 'auto', 15 | // 当前音乐时长 16 | [types.MUSIC_CURRENT_TIME]: 0, 17 | // 音乐时长 18 | [types.MUSIC_DURATION_TIME]: 0, 19 | // 音量 20 | [types.MUSIC_VOL]: 100, 21 | // 音乐播放详细信息 22 | [types.MUSIC_PLAYING_DETAIL]: {}, 23 | // 音乐播放的颜色 24 | [types.MUSIC_PLAYING_COLOR]: 'rgb(203, 40, 41)', 25 | // 音乐左上角显示隐藏的控制 26 | [types.MUSIC_SHOW_FIXED_MENU]: true 27 | } 28 | 29 | let getters = { 30 | [types.PLAY_MUSIC_LISTS_GETTERS]: (state) => state[types.PLAY_MUSIC_LISTS], 31 | [types.PLAY_MUSIC_INDEX_GETTERS]: (state) => state[types.PLAY_MUSIC_INDEX], 32 | [types.PLAY_MUSIC_LISTS_ID_GETTERS]: (state) => state[types.PLAY_MUSIC_LISTS_ID], 33 | [types.MUSIC_IS_PLAYING_GETTERS]: (state) => state[types.MUSIC_IS_PLAYING], 34 | [types.MUSIC_PLAY_TYPE_GETTERS]: (state) => state[types.MUSIC_PLAY_TYPE], 35 | [types.MUSIC_CURRENT_TIME_GETTERS]: (state) => state[types.MUSIC_CURRENT_TIME], 36 | [types.MUSIC_DURATION_TIME_GETTERS]: (state) => state[types.MUSIC_DURATION_TIME], 37 | [types.MUSIC_VOL_GETTERS]: (state) => state[types.MUSIC_VOL], 38 | [types.MUSIC_PLAYING_DETAIL_GETTERS]: (state) => state[types.MUSIC_PLAYING_DETAIL], 39 | [types.MUSIC_PLAYING_COLOR_GETTERS]: (state) => state[types.MUSIC_PLAYING_COLOR], 40 | [types.MUSIC_SHOW_FIXED_MENU_GETTERS]: (state) => state[types.MUSIC_SHOW_FIXED_MENU] 41 | } 42 | 43 | const mutations = {} 44 | const actions = {} 45 | 46 | mutations[types.PLAY_MUSIC_LISTS_ID_SETTERS] = (state, id) => { 47 | state[types.PLAY_MUSIC_LISTS_ID] = id 48 | } 49 | 50 | /** 51 | * 添加播放列表,此时PLAY_MUSIC_LISTS_ID 也需要变化 52 | * Index也需要变化 53 | * data.lists 是播放的列表集合 54 | * data.index 是播放的列表索引 55 | * data.id 是播放的列表的id 56 | */ 57 | mutations[types.PLAY_MUSIC_LISTS_SETTERS] = (state, lists) => { 58 | state[types.PLAY_MUSIC_LISTS] = lists 59 | } 60 | 61 | actions[types.PLAY_MUSIC_LISTS_SETTERS] = ({commit}, data) => { 62 | // 不等才修改播放列表 63 | if (data.id !== state[types.PLAY_MUSIC_LISTS_ID]) { 64 | commit(types.PLAY_MUSIC_LISTS_SETTERS, data.lists) 65 | commit(types.PLAY_MUSIC_LISTS_ID_SETTERS, data.id) 66 | } 67 | if (data.index >= 0) { 68 | commit(types.PLAY_MUSIC_INDEX_SETTERS, Number(data.index)) 69 | // commit(types.PLAY_MUSIC_LIST_SETTERS, data.lists[data.index]) 70 | } 71 | } 72 | 73 | /** 74 | * 设置音频播放的状态 75 | * 用于全局控制音频按钮等样式的显示效果 76 | */ 77 | mutations[types.MUSIC_IS_PLAYING_SETTERS] = (state, isplay) => { 78 | state[types.MUSIC_IS_PLAYING] = isplay 79 | } 80 | 81 | actions[types.MUSIC_IS_PLAYING_SETTERS] = ({commit}, isplay) => { 82 | commit(types.MUSIC_IS_PLAYING_SETTERS, isplay) 83 | } 84 | 85 | /** 86 | * 用于控制音频的播放类型 87 | */ 88 | mutations[types.MUSIC_PLAY_TYPE_SETTERS] = (state, type) => { 89 | state[types.MUSIC_PLAY_TYPE] = type 90 | } 91 | actions[types.MUSIC_PLAY_TYPE_SETTERS] = ({commit}, type) => { 92 | commit(types.MUSIC_PLAY_TYPE_SETTERS, type) 93 | } 94 | 95 | /** 96 | * 用于设置播放的索引 97 | */ 98 | mutations[types.PLAY_MUSIC_INDEX_SETTERS] = (state, index) => { 99 | state[types.PLAY_MUSIC_INDEX] = index 100 | } 101 | actions[types.PLAY_MUSIC_INDEX_SETTERS] = ({commit}, index) => { 102 | commit(types.PLAY_MUSIC_INDEX_SETTERS, index) 103 | } 104 | 105 | /** 106 | * 用于设置播放的current时间 107 | */ 108 | mutations[types.MUSIC_CURRENT_TIME_SETTERS] = (state, time) => { 109 | state[types.MUSIC_CURRENT_TIME] = time 110 | } 111 | actions[types.MUSIC_CURRENT_TIME_SETTERS] = ({commit}, time) => { 112 | commit(types.MUSIC_CURRENT_TIME_SETTERS, time) 113 | } 114 | 115 | /** 116 | * 用于设置音频的duration时长 117 | */ 118 | mutations[types.MUSIC_DURATION_TIME_SETTERS] = (state, time) => { 119 | state[types.MUSIC_DURATION_TIME] = time 120 | } 121 | actions[types.MUSIC_DURATION_TIME_SETTERS] = ({commit}, time) => { 122 | commit(types.MUSIC_DURATION_TIME_SETTERS, time) 123 | } 124 | 125 | /** 126 | * 设置音频播放声音大小 127 | */ 128 | mutations[types.MUSIC_VOL_SETTERS] = (state, vol) => { 129 | state[types.MUSIC_VOL] = vol 130 | } 131 | actions[types.MUSIC_VOL_SETTERS] = ({commit}, vol) => { 132 | commit(types.MUSIC_VOL_SETTERS, vol) 133 | } 134 | 135 | /** 136 | * 音频的播放详情 137 | */ 138 | mutations[types.MUSIC_PLAYING_DETAIL_SETTERS] = (state, detail) => { 139 | state[types.MUSIC_PLAYING_DETAIL] = detail 140 | } 141 | actions[types.MUSIC_PLAYING_DETAIL_SETTERS] = ({commit}, detail) => { 142 | commit(types.MUSIC_PLAYING_DETAIL_SETTERS, detail) 143 | } 144 | 145 | /** 146 | * 音频专辑的相近颜色设定 147 | */ 148 | mutations[types.MUSIC_PLAYING_COLOR_SETTERS] = (state, color) => { 149 | state[types.MUSIC_PLAYING_COLOR] = color 150 | } 151 | actions[types.MUSIC_PLAYING_COLOR_SETTERS] = ({commit}, color) => { 152 | commit(types.MUSIC_PLAYING_COLOR_SETTERS, color) 153 | } 154 | 155 | /** 156 | * 页面固定右上角菜单设定 157 | */ 158 | mutations[types.MUSIC_SHOW_FIXED_MENU_SETTERS] = (state, status) => { 159 | state[types.MUSIC_SHOW_FIXED_MENU] = status 160 | } 161 | actions[types.MUSIC_SHOW_FIXED_MENU_SETTERS] = ({commit}, status) => { 162 | commit(types.MUSIC_SHOW_FIXED_MENU_SETTERS, status) 163 | } 164 | 165 | export default { 166 | state, 167 | getters, 168 | mutations, 169 | actions 170 | } 171 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | // route level code-splitting 4 | // this generates a separate chunk (login.[hash].js) for this route 5 | // which is lazy-loaded when the route is visited. 6 | const Main = () => import('./views/Main.vue') 7 | 8 | // 登陆模块 9 | const Login = () => import('./views/login') 10 | const LoginSub = () => import('./views/login/sublogin') 11 | 12 | // 首页五大类 13 | const Find = () => import('./views/find') 14 | const Account = () => import('./views/account') 15 | const Video = () => import('./views/video') 16 | const Mine = () => import('./views/mine') 17 | const Firends = () => import('./views/firends') 18 | 19 | // Find 20 | const Recommend = () => import('./views/find/recommend') 21 | const Station = () => import('./views/find/station') 22 | const Daily = () => import('./views/find/daily') 23 | const Sheet = () => import('./views/find/sheet') 24 | const SheetDetail = () => import('./views/find/sheet/sheet-detail') 25 | const Play = () => import('./views/find/play') 26 | const Rank = () => import('./views/find/rank') 27 | const Search = () => import('./views/find/search') 28 | 29 | Vue.use(Router) 30 | 31 | /** 32 | * 路由配置 33 | * meta对象下的属性意思 34 | * @param { String } transition 过渡的动画 fade-left 35 | * @param { Array } activeRouter 当前页面是属于四个菜单或者某个路由的关联操作,如果设置了/main/find 则打开该路由的话,底部菜单第一个按钮显示active状态 36 | * @param { Boolean } isFull 是否全屏显示不显示底部菜单 37 | * @param { Boolean } keepAlive 页面是否缓存 38 | * @param { Boolean } showFixedMenu 右上角是否显示播放的固定菜单入口 39 | */ 40 | const myRouter = new Router({ 41 | // mode: 'history', 42 | base: process.env.BASE_URL, 43 | routes: [ 44 | { 45 | path: '/', 46 | redirect: '/main' 47 | }, 48 | { 49 | path: '/main', 50 | name: 'home', 51 | redirect: '/main/find', 52 | component: Main, 53 | children: [ 54 | { 55 | path: '/main/find', 56 | name: 'find', 57 | redirect: '/main/find/recommend', 58 | component: Find, 59 | meta: { 60 | keepAlive: true 61 | }, 62 | // 推荐 63 | children: [ 64 | { 65 | path: '/main/find/recommend', 66 | name: 'findrecommend', 67 | component: Recommend, 68 | meta: { 69 | keepAlive: true 70 | } 71 | }, 72 | { 73 | path: '/main/find/station', 74 | name: 'findstaion', 75 | component: Station 76 | } 77 | ] 78 | }, 79 | // 个人中心 80 | { 81 | path: '/main/account', 82 | name: 'account', 83 | component: Account, 84 | meta: { 85 | keepAlive: true 86 | } 87 | }, 88 | // 视频 89 | { 90 | path: '/main/video', 91 | name: 'video', 92 | component: Video, 93 | meta: { 94 | keepAlive: true 95 | } 96 | }, 97 | // 我的 98 | { 99 | path: '/main/mine', 100 | name: 'mine', 101 | component: Mine, 102 | meta: { 103 | keepAlive: true 104 | } 105 | }, 106 | // 朋友 107 | { 108 | path: '/main/firends', 109 | name: 'firends', 110 | component: Firends, 111 | meta: { 112 | keepAlive: true 113 | } 114 | }, 115 | 116 | // 每日推荐 117 | { 118 | path: '/main/daily', 119 | name: 'daily', 120 | component: Daily, 121 | meta: { 122 | keepAlive: true, 123 | transition: 'fade-left', 124 | activeRouter: ['/main/find'] 125 | } 126 | }, 127 | 128 | // 歌单集合 129 | { 130 | path: '/main/sheet', 131 | name: 'sheet', 132 | component: Sheet, 133 | meta: { 134 | keepAlive: true, 135 | transition: 'fade-left', 136 | activeRouter: ['/main/find'] 137 | } 138 | }, 139 | // 歌单详情 140 | { 141 | path: '/main/listdetail', 142 | name: 'listdetail', 143 | component: SheetDetail, 144 | meta: { 145 | keepAlive: true, 146 | transition: 'fade-left', 147 | activeRouter: ['/main/find'] 148 | } 149 | }, 150 | 151 | // 播放页面 152 | { 153 | path: '/main/play', 154 | name: 'play', 155 | component: Play, 156 | meta: { 157 | keepAlive: true, 158 | transition: 'fade-left', 159 | isFull: true 160 | } 161 | }, 162 | // 排行榜页面 163 | { 164 | path: '/main/rank', 165 | name: 'rank', 166 | component: Rank, 167 | meta: { 168 | keepAlive: true, 169 | transition: 'fade-left' 170 | } 171 | }, 172 | // 搜索页面 173 | { 174 | path: '/main/search', 175 | name: 'search', 176 | component: Search, 177 | meta: { 178 | transition: 'fade-top', 179 | activeRouter: ['/main/find'], 180 | hideFixedMenu: true, 181 | keepAlive: true 182 | } 183 | } 184 | ] 185 | }, 186 | { 187 | path: '/login', 188 | name: 'login', 189 | component: Login, 190 | children: [ 191 | { 192 | path: '/login/:type', 193 | name: 'loginsub', 194 | component: LoginSub 195 | } 196 | ] 197 | } 198 | ] 199 | }) 200 | 201 | export default myRouter 202 | -------------------------------------------------------------------------------- /src/views/find/rank/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 55 | 183 | -------------------------------------------------------------------------------- /src/views/mine/index.vue: -------------------------------------------------------------------------------- 1 | // 我的模块 2 | 51 | 77 | 257 | -------------------------------------------------------------------------------- /src/utils/music.js: -------------------------------------------------------------------------------- 1 | import store from 'store' 2 | import route from '@/router' 3 | import vueProject from '@/main.js' 4 | import API from 'api' 5 | import Vibrant from 'node-vibrant' 6 | 7 | const Music = { 8 | audioEle: store.getters.AUDIO_ELE_GETTERS, 9 | 10 | /** 11 | * 播放上一首 下一首歌曲 12 | * @param { String } type 是下一首还是上一首 'next' 和 'prev' 两种 13 | */ 14 | playNextPrev (type) { 15 | let playType = store.getters.MUSIC_PLAY_TYPE_GETTERS 16 | let playIndex = store.getters.PLAY_MUSIC_INDEX_GETTERS 17 | let listCount = store.getters.PLAY_MUSIC_LISTS_GETTERS.length 18 | switch (playType) { 19 | case 'auto': 20 | case 'loop': 21 | // 下一曲 22 | if (type === 'next') { 23 | if (playIndex >= listCount - 1) { 24 | this.playIndex(0) 25 | return 26 | } 27 | this.playIndex(playIndex + 1) 28 | } 29 | // 上一曲 30 | if (type === 'prev') { 31 | if (playIndex <= 0) { 32 | this.playIndex(listCount - 1) 33 | return 34 | } 35 | this.playIndex(playIndex - 1) 36 | } 37 | break 38 | // 随机不区分上一曲下一曲 39 | case 'random': 40 | let index = Math.floor(Math.random() * listCount) 41 | this.playIndex(index) 42 | break 43 | } 44 | }, 45 | 46 | /** 47 | * 播放对应索引的歌曲 48 | * @param { Number } index 是点击第多少个播放 49 | */ 50 | async playIndex (index) { 51 | let playId = store.getters.PLAY_MUSIC_LISTS_GETTERS[index].id 52 | store.dispatch('PLAY_MUSIC_INDEX_SETTERS', index) 53 | await this.initMusic(playId) 54 | }, 55 | 56 | /** 57 | * 设置音乐的播放类型 58 | */ 59 | setPlayType () { 60 | const type = ['auto', 'loop', 'random'] 61 | let playType = store.getters.MUSIC_PLAY_TYPE_GETTERS 62 | // 获取索引 63 | let getTypeIndex 64 | for (let k in type) { 65 | if (type[k] === playType) { 66 | getTypeIndex = parseInt(k) 67 | } 68 | } 69 | 70 | let index 71 | if (getTypeIndex >= type.length - 1) { 72 | index = 0 73 | } else { 74 | index = getTypeIndex + 1 75 | } 76 | store.dispatch('MUSIC_PLAY_TYPE_SETTERS', type[index]) 77 | }, 78 | 79 | /** 80 | * 设置播放音频音量的大小 81 | */ 82 | setVol (vol) { 83 | this.audioEle.volume = vol / 100 84 | store.dispatch('MUSIC_VOL_SETTERS', vol) 85 | }, 86 | 87 | /** 88 | * 播放 89 | */ 90 | play () { 91 | this.audioEle.play() 92 | }, 93 | 94 | /** 95 | * 暂停 96 | */ 97 | pause () { 98 | this.audioEle.pause() 99 | }, 100 | 101 | /** 102 | * 播放暂停 103 | */ 104 | playPause () { 105 | if (this.audioEle.paused) { 106 | this.play() 107 | } else { 108 | this.pause() 109 | } 110 | }, 111 | 112 | /** 113 | * 解码音乐歌词 114 | */ 115 | parseLrc (lrc) { 116 | /* eslint-disable */ 117 | if (!lrc) return '' 118 | const lyrics = lrc.split('\n') 119 | let lrcObj = [] 120 | for (let i = 0; i < lyrics.length; i++) { 121 | // 解码 122 | const lyric = decodeURIComponent(lyrics[i]) 123 | const timeReg = /\[\d*:\d*((\.|\:)\d*)*\]/g 124 | const timeRegExpArr = lyric.match(timeReg) 125 | if (!timeRegExpArr) continue 126 | const clause = lyric.replace(timeReg, '') 127 | for (let k = 0, h = timeRegExpArr.length; k < h; k++) { 128 | const t = timeRegExpArr[k] 129 | let min = Number(String(t.match(/\[\d*/i)).slice(1)) 130 | let sec = Number(String(t.match(/\:\d*/i)).slice(1)) 131 | const time = min * 60 + sec 132 | // lrcObj[time] = clause 133 | lrcObj.push({ 134 | t: time, 135 | lrc: clause 136 | }) 137 | } 138 | } 139 | return lrcObj 140 | }, 141 | 142 | /** 143 | * 播放专辑歌曲的操作 144 | * @param { Object } data 相关数据 145 | * data.lists 播放的列表集合 146 | * data.index 播放的索引 147 | * data.id 播放的歌单id 148 | */ 149 | saveSheetList (data) { 150 | let {lists, index} = data 151 | store.dispatch('PLAY_MUSIC_LISTS_SETTERS', data) 152 | route.push({ 153 | path: '/main/play', 154 | query: { 155 | id: lists[index].id 156 | } 157 | }) 158 | }, 159 | 160 | /** 161 | * 初始化音频 162 | */ 163 | async initMusic (id) { 164 | // 动态设置url的qurey id 165 | if (location.href.includes('/main/play')) { 166 | route.replace({ 167 | path: '/main/play', 168 | query: { 169 | id 170 | } 171 | }) 172 | } 173 | 174 | // 判断是否可以播放音乐,可以则播放,否则提示无法播放且console 175 | this.checkMusic(id).then(async res => { 176 | // 此时这首歌可以播放 177 | // 请求数据 178 | let musicRes = await vueProject.$mutils.fetchData(API.music.MUSIC_DETAIL, { 179 | ids: id 180 | }) 181 | // 判断有没有播放列表集合,没有存储单曲作为歌曲列表集合 182 | if (!store.getters.PLAY_MUSIC_LISTS_GETTERS.length) { 183 | store.dispatch('PLAY_MUSIC_LISTS_SETTERS', { 184 | id: musicRes.data.songs[0].id, 185 | index: 0, 186 | lists: musicRes.data.songs 187 | }) 188 | } 189 | store.dispatch('MUSIC_PLAYING_DETAIL_SETTERS', musicRes.data.songs[0]) 190 | this.initMusicColor(musicRes.data.songs[0]) 191 | vueProject.$nextTick(() => { 192 | this.play() 193 | }) 194 | }, err => { 195 | vueProject.$msg({text: '暂无权限播放'}) 196 | // 需要重置index的值(通过当前播放的音乐id与lists的集合比对拿到索引至重新修改索引值) 197 | this.resetMusicIndex() 198 | route.go(-1) 199 | console.log(err) 200 | }) 201 | }, 202 | 203 | /** 204 | * 重置索引,当点击播放的音乐无效的时候 205 | */ 206 | resetMusicIndex () { 207 | let playingList = store.getters.MUSIC_PLAYING_DETAIL_GETTERS 208 | let playLists = store.getters.PLAY_MUSIC_LISTS_GETTERS 209 | if (vueProject.$dutils.exp.isEmptyObject(playingList) || !playLists.length) return 210 | let index = function () { 211 | for (let i = 0; i < playLists.length; i++) { 212 | if (playLists[i].id === playingList.id) return i 213 | } 214 | return 0 215 | } 216 | store.dispatch('PLAY_MUSIC_INDEX_SETTERS', Number(index())) 217 | }, 218 | 219 | // 获取图片的颜色以设置进度条的颜色 220 | initMusicColor (res) { 221 | // Vibrant 222 | let pic = res.al.picUrl 223 | Vibrant.from(pic).getPalette() 224 | .then((palette) => { 225 | // palette.DarkMuted 226 | store.dispatch( 227 | 'MUSIC_PLAYING_COLOR_SETTERS', 228 | `rgba(${palette.LightMuted.r}, ${palette.LightMuted.g}, ${palette.LightMuted.b}, 0.9)` 229 | ) 230 | }) 231 | }, 232 | 233 | async checkMusic (id) { 234 | return new Promise(async (resolve, reject) => { 235 | try { 236 | let res = await vueProject.$mutils.fetchData(API.music.CHECK_MUSIC, {id}) 237 | resolve(res) 238 | } catch (e) { 239 | reject(e) 240 | } 241 | }) 242 | }, 243 | 244 | /** 245 | * 音频的事件初始化 246 | */ 247 | initAudioEvent (ele) { 248 | // 存储 audioEle 249 | this.audioEle = ele 250 | // 播放事件 251 | ele.onplaying = () => { 252 | store.dispatch('MUSIC_IS_PLAYING_SETTERS', true) 253 | } 254 | 255 | // 暂停事件 256 | ele.onpause = () => { 257 | if (!this.audioEle.paused) return 258 | let cd = document.getElementById('cd') 259 | let cdwp = document.getElementById('cdwp') 260 | if (cd && cdwp) { 261 | let cdTransform = getComputedStyle(cd).transform 262 | let wpTransform = getComputedStyle(cdwp).transform 263 | cdwp.style.transform = wpTransform === 'none' ? cdTransform : wpTransform.concat(' ', cdTransform) 264 | } 265 | store.dispatch('MUSIC_IS_PLAYING_SETTERS', false) 266 | } 267 | 268 | // 播放结束事件 269 | ele.onended = () => { 270 | this.playNextPrev('next') 271 | } 272 | 273 | // 音频初始化获取数据 274 | ele.onloadedmetadata = (e) => { 275 | let dr = Math.floor(e.target.duration) 276 | store.dispatch('MUSIC_DURATION_TIME_SETTERS', dr) 277 | } 278 | 279 | // 截流 获取当前的播放进度 280 | ele.ontimeupdate = vueProject.$dutils.utils.throttle(function (e) { 281 | let arg = Array.from(arguments)[0] 282 | if (arg.target && arg.target.currentTime) { 283 | let ct = Math.floor(arg.target.currentTime) 284 | store.dispatch('MUSIC_CURRENT_TIME_SETTERS', ct) 285 | } 286 | }, 1000) 287 | } 288 | } 289 | 290 | export default Music 291 | -------------------------------------------------------------------------------- /src/views/find/sheet/index.vue: -------------------------------------------------------------------------------- 1 | // 歌单内容,显示歌单的页面 2 | 38 | 122 | 289 | -------------------------------------------------------------------------------- /public/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /src/views/find/play/index.vue: -------------------------------------------------------------------------------- 1 | 58 | 183 | 376 | -------------------------------------------------------------------------------- /src/views/find/sheet/sheet-detail/index.vue: -------------------------------------------------------------------------------- 1 | // 这是歌单的详情页面 包含歌单的一系列操作 2 | 72 | 73 | 195 | 397 | -------------------------------------------------------------------------------- /src/views/find/search/index.vue: -------------------------------------------------------------------------------- 1 | // 搜索 2 | 47 | 295 | 296 | 426 | --------------------------------------------------------------------------------