├── 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 |
3 | .vm-account 账户
4 |
5 |
9 |
11 |
--------------------------------------------------------------------------------
/src/views/firends/index.vue:
--------------------------------------------------------------------------------
1 | // 朋友相关
2 |
3 | .vm-firends 朋友
4 |
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 |
3 | .vm-video 视频
4 |
5 |
12 |
14 |
--------------------------------------------------------------------------------
/src/views/find/station/index.vue:
--------------------------------------------------------------------------------
1 |
2 | .staion staion
3 |
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 |
2 |
3 |
4 |
5 |
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 |
3 | .vm-login
4 | .login-content
5 | router-link.btn-login(to="/login/phone") 手机号登陆
6 | router-link.btn-login(to="/login/email") 邮箱登陆
7 | router-view
8 |
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 |
2 | .s-singer(v-if="list && list.id", :class="size")
3 | img.avatar(v-imgsize="{url: list.img1v1Url, w: 150}")
4 | .name
5 | .detail {{list.name}}
6 |
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 |
2 | .loading(:class="{'absolute' : absolute}")
3 | .c(:class="{size, 'vertical' : vertical}", :style="{height: height}")
4 | img(src="./../../assets/images/loading.gif")
5 | p {{name}}
6 |
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 |
3 | router-link.s-dj(v-if="list && list.id", :to="{path: '/main/dj', query: {id: list.id}}")
4 | .img
5 | img(v-imgsize="list.picUrl")
6 | .right-info
7 | .name {{list.name}}
8 | .disc
9 | .user {{list.dj.nickname}}
10 |
11 |
23 |
24 |
79 |
--------------------------------------------------------------------------------
/src/components/musicsiderlist/index.vue:
--------------------------------------------------------------------------------
1 | // play页面呼起的列表内容
2 |
3 | .music-sider-lists
4 | transition(name="fade")
5 | .mask(@click="close")
6 | transition(name="sider-top")
7 | .sider-content
8 | MusicList(v-for="(item, index) in musicPlayLists" :index="index" :name="item.name", :singer="item.ar", :id="item.id", :list="item", :playSheet="playSheet(item.id)", @play="playMusic")
9 |
10 |
51 |
80 |
--------------------------------------------------------------------------------
/src/views/find/search/sheet/index.vue:
--------------------------------------------------------------------------------
1 | // 搜索的歌单显示效果
2 |
3 | router-link.s-sheet(v-if="list && list.name", :to="{path: '/main/listdetail', query: {id: list.id}}")
4 | img(v-imgsize="list.coverImgUrl")
5 | .right-info
6 | .name {{list.name}}
7 | .disc
8 | .count {{list.trackCount}} 首音乐
9 | .user(v-if="list.creator && list.creator.nickname") by {{list.creator.nickname}}
10 | .playCount 播放 {{ list.playCount | parseNumber }}次
11 |
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 |
3 | .cp-nav
4 | router-link(v-for="item in mainRt" :to="item.url" class="model-router" :class="isActiveRouter(item.url) ? 'acitve' : '' ")
5 | i(:class="item.icon")
6 | span {{item.name}}
7 |
8 |
54 |
91 |
--------------------------------------------------------------------------------
/src/views/Main.vue:
--------------------------------------------------------------------------------
1 | // 主页 只是个包含底部菜单以及模块内容
2 |
3 | .vm-main
4 | audio(id="myAudio"
5 | ref="myAudio"
6 | :src="musicPlayingList && musicPlayingList.id ? 'http://music.163.com/song/media/outer/url?id=' + musicPlayingList.id + '.mp3' : ''")
7 | router-link.fix-music-btn.icon-menu.easy-click(v-show="isPlayRouter", to="/main/play")
8 | Nav
9 | transition(:name="$route.meta.transition")
10 | keep-alive
11 | router-view(v-if="$route.meta.keepAlive" class="model-view" :class="{'isFull': $route.meta.isFull}")
12 | transition(:name="$route.meta.transition")
13 | router-view(v-if="!$route.meta.keepAlive" class="model-view" :class="{'isFull': $route.meta.isFull}")
14 |
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 |
2 | .s-user(v-if="list && list.userId")
3 | img.avatar(v-imgsize="{url: list.avatarUrl, w: 150}")
4 | .right-info
5 | .detail
6 | .name-info
7 | .name {{list.nickname}}
8 | i(:class="list.gender === 1 ? 'icon-menu nan' : 'icon-quill nv'")
9 | .disc {{list.signature}}
10 | .follow
11 | .f-name.followed(v-if="list.followed") 已关注
12 | .f-name(v-else) + 关注
13 |
14 |
26 |
104 |
--------------------------------------------------------------------------------
/src/views/find/search/album/index.vue:
--------------------------------------------------------------------------------
1 | // 搜索的歌单显示效果
2 |
3 | router-link.s-album(v-if="list && list.name", :to="{path: '/main/listdetail', query: {id: list.id, type: 'album'}}")
4 | .img
5 | img(v-imgsize="list.picUrl")
6 | .right-info
7 | .name {{list.name}}
8 | .disc
9 | .user {{getArtists}}
10 | .push-t {{getDate}}
11 |
12 |
37 |
38 |
104 |
--------------------------------------------------------------------------------
/src/components/commonpage/index.vue:
--------------------------------------------------------------------------------
1 |
2 | .common-page
3 | .auto-header(:class="{'bg': bg}")
4 | .bg(v-if="bg" :style="headerStyle")
5 | .bg-content
6 | .left
7 | slot(v-if="custBack" name="header-l")
8 | i.icon-menu(v-else @click="back")
9 | .title {{title}}
10 | .right
11 | slot(name="header-r")
12 | .auto-body
13 | slot(name="content")
14 |
15 |
52 |
128 |
--------------------------------------------------------------------------------
/src/components/minesheet/index.vue:
--------------------------------------------------------------------------------
1 |
2 | .mine-sheet
3 | .sheet-title(@click="toggoleList")
4 | i.icon-menu
5 | span.name 我创建的歌单
6 | i.icon-menu
7 | .sheet-content(v-if="showSheetList")
8 | .sheet-img.love
9 | img(src="http://www.daiwei.org/vue/bg/657952152722629515.jpg")
10 | .sheet-detail
11 | .detail-info
12 | .sheet-name 我喜欢的音乐
13 | .sheet-desc 共12首
14 | i.icon-menu
15 |
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 |
3 | .music-daily
4 | CommonPage(title="每日推荐")
5 | i.icon-menu(slot="header-r")
6 | .content(slot="content")
7 | .banner
8 | .date {{new Date().getDate()}}
9 | img(src="https://s2.music.126.net/style/web2/img/recmd_daily.jpg")
10 | .lists(v-if="recommendList.length")
11 | SheetList(v-for="(item, index) in recommendList" :key="index" :name="item.name", :index="index", :singer="item.artists", :avatar="item.album.picUrl", :id="item.id", :playSheet="playSheet(item.id)", :list="item", @play="play")
12 | Loading(v-else :absolute="true")
13 |
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 |
3 | .vm-find
4 | .fix-top
5 | // 搜索区域
6 | .serch-area
7 | i.left.icon-pencil.easy-click
8 | // 控制区域
9 | .input(@click="openSearch")
10 | i.icon-menu
11 | span 搜索
12 | .nav-bar
13 | router-link(to="/main/find/recommend" class="nav-bar-router") 个性推荐
14 | router-link(to="/main/find/station" class="nav-bar-router") 主播电台
15 | // 内容中心区域
16 | .find-content
17 | router-view
18 |
19 |
31 |
122 |
--------------------------------------------------------------------------------
/src/views/find/sheet/sheet-avatar/index.vue:
--------------------------------------------------------------------------------
1 |
2 | .sheet-avatar(@click="hideSheetAvatar")
3 | .blur(:style="{backgroundImage: 'url(' + imageSrc + ')'}")
4 | .fix-content
5 | i.icon-menu.easy-click(@click="hideSheetAvatar")
6 | .avatar-content
7 | img(:src="imageSrc")
8 | .detail-content
9 | .title {{avatarData.name}}
10 | .tips
11 | label 标签
12 | span(v-for="item in avatarData.tags") {{item}}
13 | .disc(v-html="trim(avatarData.description)")
14 |
15 |
42 |
110 |
--------------------------------------------------------------------------------
/src/components/lrc/index.vue:
--------------------------------------------------------------------------------
1 |
2 | .lrc-info(ref="lrc")
3 | .lrc-c(v-if="hasLrc === 1")
4 | .lrc-list(v-for="(item, index) in lrcs", :class="{active: activeIndex === index}") {{item.lrc}}
5 | .lrc-tip(v-if="hasLrc === 2") 纯音乐,请欣赏
6 | .lrc-tip(v-if="hasLrc === 0") 歌词加载中, 请稍等...
7 | .lrc-tip(v-if="hasLrc === 3") 暂无歌词
8 |
9 |
103 |
132 |
--------------------------------------------------------------------------------
/src/views/login/sublogin/index.vue:
--------------------------------------------------------------------------------
1 |
2 | .sub-login.auto-content
3 | Commonpage
4 | .content-slot(slot="content")
5 | .text-input
6 | i.icon-mobile
7 | input(v-if="type==='phone'" type="number" placeholder="手机号" v-model="phone")
8 | input(v-if="type==='email'" type="text" placeholder="邮箱" v-model="email")
9 | .text-input
10 | i.icon-mobile
11 | input(type="password" placeholder="密码" @keyup.enter="subLoginIn" v-model="password")
12 | input.login-in(type="button" @click="subLoginIn" value="登录")
13 |
14 |
78 |
119 |
--------------------------------------------------------------------------------
/src/components/musiclist/index.vue:
--------------------------------------------------------------------------------
1 | // 歌单详情列表
2 |
3 | .music-list(@click="saveAddPlay(index)")
4 | .music-index
5 | span.index(v-if="!playSheet") {{index + 1}}
6 | i.icon-menu(v-else)
7 | .music-info
8 | .music-detail
9 | .name(:class="{active: playSheet}") {{name}}
10 | .singer {{getSinger}}
11 | .music-conf
12 | .mv.icon-menu
13 | .set.icon-menu
14 |
15 |
76 |
77 |
153 |
--------------------------------------------------------------------------------
/src/views/find/search/searchingPanel/index.vue:
--------------------------------------------------------------------------------
1 | // 搜索面板
2 |
3 | .search-panel
4 | .search-lists-a
5 | .a-name 热门搜索
6 | .search-lists(v-if="hots.length")
7 | .list(v-for="item in hots", @click="searchItem(item.first)")
8 | .name {{item.first}}
9 | .tip(v-if="item.iconType === 1") 热
10 | Loading(v-else)
11 | .search-lists-a
12 | .a-name.config
13 | .name 搜索历史
14 | i.icon-menu.easy-click(@click="deleteHistory")
15 | .search-lists(v-if="history.length")
16 | .list(v-for="list in history", @click="searchItem(list)")
17 | .name {{list}}
18 | .none-list(v-else) 暂无搜索记录
19 |
20 |
72 |
73 |
128 |
--------------------------------------------------------------------------------
/src/components/sheetlist/index.vue:
--------------------------------------------------------------------------------
1 | // 通用音乐歌单列表组件
2 |
3 | .sheet-list(:id="id", @click="play(index)")
4 | .music-avatar
5 | img.avatar(v-imgsize="{url: avatar, w: 100}")
6 | .music-play.icon-menu(v-if="playSheet")
7 | .music-detail
8 | .name(:class="{active: playSheet}") {{name}}
9 | .singer {{getSinger}}
10 | .music-conf
11 | .mv.icon-menu
12 | .set.icon-menu
13 |
14 |
79 |
80 |
156 |
--------------------------------------------------------------------------------
/src/components/groupsheet/index.vue:
--------------------------------------------------------------------------------
1 | // 这是首页推荐歌单这种模版 封装成组件
2 |
3 | .group-sheet
4 | .title
5 | span {{sheetName}}
6 | i.icon-circle-right
7 | .content(v-if="sheetList.length")
8 | router-link.sheet-list(v-for="(item, index) in sheetList" v-if="index < 6", :to="{path: '/main/listdetail', query: {id: item.id}}")
9 | .sheet-image
10 | img(:src="item.picUrl")
11 | .high-quality(v-if="item.highQuality")
12 | i.icon-menu
13 | .tips
14 | i.icon-menu
15 | span {{item.playCount | parseNumber}}
16 | .sheet-name {{item.name}}
17 | Loading(v-else)
18 |
19 |
67 |
68 |
150 |
--------------------------------------------------------------------------------
/src/components/touchbar/index.vue:
--------------------------------------------------------------------------------
1 |
2 | .touch-bar
3 | slot.left-sider(name="left-sider")
4 | .bar
5 | .progress(ref="bar")
6 | .current(ref="current", :style="{background: color}")
7 | .range(ref="range" :class="type === 'progress' ? 'music' : ''"
8 | @touchstart="rangeTouchStart"
9 | @touchmove="rangeTouchMove"
10 | @touchend="rangeTouchEnd")
11 | .icon(v-if="type === 'progress'", :style="{background: color}")
12 | slot.right-sider(name="right-sider")
13 |
14 |
85 |
141 |
--------------------------------------------------------------------------------
/src/components/scroll/index.vue:
--------------------------------------------------------------------------------
1 |
2 | .bt-scroll
3 | slot(name="scroll-header")
4 | .wrapper(ref="wrapper", style="{background: background}")
5 | .content
6 | slot(name="scroll-content")
7 | .pullup-wrapper(v-if="needPullUp")
8 | .before-trigger(v-if="!isPullUpLoad")
9 | Loading(:vertical="false", height="60px")
10 |
11 |
127 |
170 |
--------------------------------------------------------------------------------
/src/views/find/recommend/index.vue:
--------------------------------------------------------------------------------
1 |
2 | .recommend
3 | Scroll(ref="scroll")
4 | .scroll-content(slot="scroll-content")
5 | // SWIPER
6 | .sider
7 | swiper(v-if="banners.length" class="main-sider" :options="swiperOption")
8 | swiper-slide(v-for="item in banners")
9 | img(:src="item.imageUrl" @click="bannerClick(item)")
10 | .swiper-pagination(slot="pagination")
11 | // 四个大的按钮
12 | .vm-lists
13 | router-link(class="list-entry" to="/")
14 | img(src="./../../../assets/images/r1.jpg")
15 | span.entry-name 私人FM
16 | router-link(class="list-entry" to="/main/daily")
17 | img(src="./../../../assets/images/r2.jpg")
18 | span.entry-name 每日推荐
19 | router-link(class="list-entry" to="/main/sheet")
20 | img(src="./../../../assets/images/r3.jpg")
21 | span.entry-name 歌单
22 | router-link(class="list-entry" to="/main/rank")
23 | img(src="./../../../assets/images/r4.jpg")
24 | span.entry-name 排行榜
25 | GroupSheet(api="RECOMMEND_SHEET_LISTS")
26 |
27 |
95 |
160 |
--------------------------------------------------------------------------------
/src/views/find/sheet/sheet-type/index.vue:
--------------------------------------------------------------------------------
1 |
2 | .sheet-type
3 | CommonPage(title="筛选歌单", :custBack="true")
4 | .header(slot="header-l")
5 | span(@click="selectCat()") 取消
6 | .content(slot="content")
7 | .cats-info(v-if="showCats")
8 | .all
9 | .all-cat(:class="cat === '全部歌单' ? 'active' : ''", @click="selectCat('全部歌单')") 全部歌单
10 | .lists(v-for="item in catsList")
11 | .name {{item.typename}}
12 | .detail
13 | .cat-name(v-for="list in item.list", :class="{'active' : cat === list.name, 'hot': list.hot}", @click="selectCat(list.name)") {{list.name}}
14 | Loading(v-else)
15 |
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 |
2 | .rank
3 | CommonPage(title="排行榜")
4 | .rank-content(slot="content")
5 | .main-content(v-if="official && official.length")
6 | .title 云音乐官方榜
7 | .lists-rank
8 | router-link.list(v-for="item in official", :to="{path: '/main/listdetail', query: {id: item.id}}")
9 | .img-info
10 | img(:src="item.coverImgUrl")
11 | .tips {{item.updateFrequency}}
12 | .rank-three
13 | .list-three(v-for="(list, index) in item.tracks") {{index + 1}}. {{list.first}} - {{list.second}}
14 | .title 全球榜
15 | .global-rank(v-if="unOfficial && unOfficial.length")
16 | router-link.sheet-list(v-for="item in unOfficial" :to="{path: '/main/listdetail', query: {id: item.id}}")
17 | .sheet-image
18 | img(:src="item.coverImgUrl")
19 | .high-quality(v-if="item.highQuality")
20 | i.icon-menu
21 | .tips
22 | span {{item.updateFrequency}}
23 | .sheet-name {{item.name}}
24 | Loading(v-else)
25 |
26 |
55 |
183 |
--------------------------------------------------------------------------------
/src/views/mine/index.vue:
--------------------------------------------------------------------------------
1 | // 我的模块
2 |
3 | .vm-mine
4 | Scroll
5 | .scroll-header(slot="scroll-header")
6 | i.cloud.icon-menu.easy-click
7 | span.title 我的
8 | .right
9 | .scroll-content(slot="scroll-content")
10 | // 用户填充头部的内容,防止content被头部遮挡
11 | .fill-top
12 | // 内容部分
13 | .fill-content
14 | .user-tip
15 | .tip-content
16 | .blur(v-if="!isIos")
17 | .detail-content
18 | .detail
19 | img(src="http://www.daiwei.org/vue/bg/657952152722629515.jpg")
20 | span.name 未曾遗忘的青春
21 | .tip
22 | .vip
23 | span.btn 会员中心
24 | .list-entry
25 | li
26 | i.icon-menu
27 | .entry-detail
28 | .list-name 本地音乐
29 | .list-count 11
30 | i.icon-menu
31 | li
32 | i.icon-menu
33 | .entry-detail
34 | .list-name 最近播放
35 | .list-count
36 | i.icon-menu
37 | li
38 | i.icon-menu
39 | .entry-detail
40 | .list-name 我的电台
41 | .list-count 22
42 | i.icon-menu
43 | li
44 | i.icon-menu
45 | .entry-detail
46 | .list-name 我的收藏
47 | .list-count 333
48 | i.icon-menu
49 | MineSheet()
50 |
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 |
3 | .sheet
4 | CommonPage(title="歌单", bg="#fff")
5 | .content(slot="content")
6 | Scroll(@pullingUp="getSheetListMore", :needPullUp="true")
7 | .scroll-content(slot="scroll-content")
8 | router-link.hight-sheet(to="/main/hightsheet" v-if="hightSheet.name")
9 | .blur(:style="{backgroundImage: 'url(' + $mutils.changeImageSize(hightSheet.coverImgUrl, 200) + ')'}")
10 | .content
11 | img(v-imgsize="hightSheet.coverImgUrl")
12 | .detail
13 | .title
14 | i.icon-menu.rank
15 | span 精品歌单
16 | i.icon-menu
17 | .name {{hightSheet.name}}
18 | .disc {{hightSheet.copywriter}}
19 | .sheet-lists(v-if="hightSheet.name")
20 | .filter
21 | .select
22 | .name(@click="showSelect = true") {{cat}}
23 | i.icon-menu
24 | .select-list
25 | li(@click="getSheetType('华语')") 华语
26 | li(@click="getSheetType('电子')") 电子
27 | li(@click="getSheetType('乡村')") 乡村
28 | .lists(v-if="sheets")
29 | router-link.sheet-list(:to="{path: '/main/listdetail', query: {id: item.id}}" v-for="item in sheets" :key="item.id")
30 | .image-list
31 | .tips {{item.playCount | parseNumber}}
32 | .user {{item.creator.nickname}}
33 | img(v-imgsize="item.coverImgUrl")
34 | .disc {{item.name}}
35 | transition(name="sider-top")
36 | SheetType(class="sheet-t" v-if="showSelect" :cat="cat", @selectCat="getSheetType")
37 |
38 |
122 |
289 |
--------------------------------------------------------------------------------
/public/img/icons/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
150 |
--------------------------------------------------------------------------------
/src/views/find/play/index.vue:
--------------------------------------------------------------------------------
1 |
2 | .play(v-if="musicPlayingDetail && musicPlayingDetail.id")
3 | .blur(:class="{'draken': isShowLrc}" :style="{backgroundImage: 'url(' + $mutils.changeImageSize(musicPlayingDetail.al.picUrl, 300) + ')'}")
4 | .auto-header
5 | .left
6 | i.icon-menu(@click="back")
7 | .title
8 | .name {{musicPlayingDetail.name}}
9 | .user
10 | span.user-name {{getSinger(musicPlayingDetail.ar)}}
11 | i.icon-menu
12 | // 分享
13 | .right
14 | i.icon-menu
15 | .content
16 | .lrc(v-if="isShowLrc" @click="toggleType")
17 | .vol
18 | TouthBar(@setProgress="setVol"
19 | color="rgba(77, 77, 77, 0.2)"
20 | :progress="audioVol")
21 | .left-sider(slot="left-sider")
22 | i.icon-menu
23 | .right-sider(slot="right-sider")
24 | i.icon-menu
25 | .lrc-area(ref="lrcarea")
26 | .full-lrc
27 | Lrc(:songId="musicPlayingDetail.id", :currentT="currentTime")
28 | .cd(v-else @click="toggleType")
29 | .cd-area
30 | .cd-image-wp(id="cdwp")
31 | img.cd-image(id="cd" v-imgsize="musicPlayingDetail.al.picUrl", :class="{'rotate': isPlaying}")
32 | .mc-conf
33 | i.icon-menu.collect
34 | i.icon-menu.download
35 | i.icon-menu.commen
36 | i.icon-menu.set
37 | .music-conf
38 | .music-progress
39 | TouthBar(@setProgress="setProgress"
40 | :color="musicColor"
41 | :progress="percent"
42 | type='progress'
43 | @setPercent="setPercent")
44 | .left(slot="left-sider")
45 | span {{currentTime | parseMusicTime}}
46 | .right(slot="right-sider")
47 | span {{durationTime | parseMusicTime}}
48 | .music-play-set
49 | .play-type.icon-menu.easy-click(@click="music.setPlayType")
50 | .play-set
51 | .play-index.icon-menu.easy-click(@click="playPrev")
52 | .play-puase.easy-click(:class="isPlaying ? 'icon-menu' : 'icon-office'"
53 | @click="playPause")
54 | .play-index.icon-menu.easy-click(@click="playNext")
55 | .play-lists.icon-menu.easy-click(@click="showSider = true")
56 | MusicSiderList(v-if="showSider", @close="showSider = false")
57 |
58 |
183 |
376 |
--------------------------------------------------------------------------------
/src/views/find/sheet/sheet-detail/index.vue:
--------------------------------------------------------------------------------
1 | // 这是歌单的详情页面 包含歌单的一系列操作
2 |
3 | .sheet-detail(v-if="!isEmptyDetail")
4 | CommonPage(title="歌单", :bg="imageSrc")
5 | .content(slot="content")
6 | .scroll-main
7 | .content-main
8 | .blur(:style="{backgroundImage: 'url(' + imageSrc + ')'}")
9 | .detail-main(v-if="isAlbum")
10 | .sheet-avatar(@click="showSheetAvatar")
11 | img.avatar(v-imgsize="detail.blurPicUrl")
12 | .info i
13 | .sheet-disc
14 | .name {{detail.name}}
15 | .user
16 | span {{detail.artists[0].name}}
17 | i.icon-menu
18 | .detail-main(v-else)
19 | .sheet-avatar(@click="showSheetAvatar")
20 | img.avatar(v-imgsize="detail.coverImgUrl")
21 | .tips {{detail.playCount | parseNumber}}
22 | .high-quality(v-if="detail.highQuality")
23 | i.icon-menu
24 | .info i
25 | .sheet-disc
26 | .name {{detail.name}}
27 | .user
28 | img(v-imgsize="{url: detail.creator.avatarUrl, w: 120}")
29 | span {{detail.creator.nickname}}
30 | i.icon-menu
31 | .detail-conf
32 | .conf-list(v-if="!isAlbum")
33 | i.icon-menu
34 | span.disc {{detail.commentCount}}
35 | .conf-list(v-else)
36 | i.icon-menu
37 | span.disc {{detail.info.commentCount}}
38 | .conf-list(v-if="!isAlbum")
39 | i.icon-menu
40 | span.disc {{detail.shareCount}}
41 | .conf-list(v-else)
42 | i.icon-menu
43 | span.disc {{detail.info.shareCount}}
44 | .conf-list
45 | i.icon-menu
46 | span.disc 下载
47 | .conf-list
48 | i.icon-menu
49 | span.disc 多选
50 | .content-lists-info
51 | .list-title
52 | .play
53 | i.icon-menu
54 | span.label 播放全部
55 | span.count (共{{detail.trackCount}}首)
56 | .collect
57 | span(v-if="!detail.subscribed" @click="subscribe") + 收藏 ({{detail.subscribedCount | parseNumber}})
58 | span(v-else @click="subscribe") 已收藏
59 | .list-content
60 | MusicList(v-for="(item, index) in tracks"
61 | :index="index"
62 | :key="index"
63 | :name="item.name"
64 | :singer="item.ar"
65 | :id="item.id"
66 | :list="item"
67 | :playSheet="playSheet(item.id)"
68 | @play="playMusic")
69 | transition(name="fade")
70 | SheetAvatar(v-if="showAvatar", @hideSheetAvatar="hideSheetAvatar" :avatarData="detail")
71 |
72 |
73 |
195 |
397 |
--------------------------------------------------------------------------------
/src/views/find/search/index.vue:
--------------------------------------------------------------------------------
1 | // 搜索
2 |
3 | .search
4 | .header
5 | .input-area
6 | i.icon-menu
7 | input(ref="searchRef", v-model="keywords", type="search")
8 | .span.easy-click(@click="back") 取消
9 | .content
10 | SearchingPanel(v-if="showRecommend", @search="searchItem")
11 | .result-info(v-else)
12 | .result-type
13 | .type(v-for="item in type" :class="{'active': currentType === item.code}" @click="selectType(item.code)") {{item.name}}
14 | .result-content
15 | Loading(v-if="isLoad")
16 | .result-detail(v-else-if="isEmptyResult")
17 | .title-tip(v-if="currentType === 0 && result.songs && result.songs.length") 单曲
18 | MusicList(v-if="result.songs && result.songs.length"
19 | v-for="(song, index) in result.songs"
20 | :index="index" :name="song.name"
21 | :singer="song.artists"
22 | :id="song.id"
23 | :list="song"
24 | :playSheet="playSheet(song.id)"
25 | @play="playMusic")
26 |
27 | .title-tip(v-if="currentType === 0 && result.playlists && result.playlists.length") 歌单
28 | SSheet(v-if="result.playlists && result.playlists.length" v-for="sheet in result.playlists"
29 | :list="sheet")
30 |
31 | //- .title-tip(v-if="currentType === 0 && result.albums && result.albums.length") 单曲
32 | SAlbum(v-if="result.albums && currentType !== 0 && result.albums.length" v-for="album in result.albums"
33 | :list="album")
34 |
35 | SUser(v-if="result.userprofiles && currentType !== 0 && result.userprofiles.length" v-for="user in result.userprofiles"
36 | :list="user")
37 |
38 | .title-tip(v-if="currentType === 0 && result.artists && result.artists.length") 歌手
39 | SSinger(v-if="result.artists && result.artists.length" v-for="singer in result.artists"
40 | :list="singer", :size="currentType === 0 ? 'big' : ''")
41 | // 电台
42 | SDj(v-if="result.djRadios && result.djRadios.length" v-for="radio in result.djRadios"
43 | :list="radio")
44 | .result-detail(v-else)
45 | .none 暂无数据
46 |
47 |
295 |
296 |
426 |
--------------------------------------------------------------------------------