├── static
└── .gitkeep
├── .eslintignore
├── config
├── prod.env.js
├── dev.env.js
└── index.js
├── src
├── common
│ ├── stylus
│ │ ├── index.styl
│ │ ├── base.styl
│ │ ├── mixin.styl
│ │ ├── variable.styl
│ │ ├── reset.styl
│ │ └── icon.styl
│ ├── js
│ │ ├── config.js
│ │ ├── singer.js
│ │ ├── uid.js
│ │ ├── util.js
│ │ ├── jsonp.js
│ │ ├── dom.js
│ │ ├── song.js
│ │ ├── cache.js
│ │ └── mixin.js
│ ├── image
│ │ └── default.png
│ └── fonts
│ │ ├── music-icon.eot
│ │ ├── music-icon.ttf
│ │ ├── music-icon.woff
│ │ └── music-icon.svg
├── base
│ ├── loading
│ │ ├── loading.gif
│ │ └── loading.vue
│ ├── song-list
│ │ ├── first@2x.png
│ │ ├── first@3x.png
│ │ ├── third@2x.png
│ │ ├── third@3x.png
│ │ ├── second@2x.png
│ │ ├── second@3x.png
│ │ └── song-list.vue
│ ├── no-result
│ │ ├── no-result.png
│ │ └── no-result.vue
│ ├── top-tip
│ │ └── top-tip.vue
│ ├── switches
│ │ └── switches.vue
│ ├── search-list
│ │ └── search-list.vue
│ ├── progress-circle
│ │ └── progress-circle.vue
│ ├── search-box
│ │ └── search-box.vue
│ ├── confirm
│ │ └── confirm.vue
│ ├── progress-bar
│ │ └── progress-bar.vue
│ ├── scroll
│ │ └── scroll.vue
│ ├── slider
│ │ └── slider.vue
│ └── listview
│ │ └── listview.vue
├── components
│ ├── m-header
│ │ ├── logo@2x.png
│ │ ├── logo@3x.png
│ │ └── m-header.vue
│ ├── tab
│ │ └── tab.vue
│ ├── disc
│ │ └── disc.vue
│ ├── singer-detail
│ │ └── singer-detail.vue
│ ├── top-list
│ │ └── top-list.vue
│ ├── singer
│ │ └── singer.vue
│ ├── rank
│ │ └── rank.vue
│ ├── recommend
│ │ └── recommend.vue
│ ├── search
│ │ └── search.vue
│ ├── user-center
│ │ └── user-center.vue
│ ├── suggest
│ │ └── suggest.vue
│ ├── add-song
│ │ └── add-song.vue
│ ├── music-list
│ │ └── music-list.vue
│ ├── playlist
│ │ └── playlist.vue
│ └── player
│ │ └── player.vue
├── api
│ ├── config.js
│ ├── rank.js
│ ├── singer.js
│ ├── search.js
│ ├── recommend.js
│ └── song.js
├── store
│ ├── state.js
│ ├── index.js
│ ├── mutation-types.js
│ ├── getters.js
│ ├── mutations.js
│ └── actions.js
├── App.vue
├── main.js
└── router
│ └── index.js
├── .editorconfig
├── .gitignore
├── .babelrc
├── .postcssrc.js
├── index.html
├── ecosystem.config.js
├── README.md
├── .eslintrc.js
├── LICENSE
├── package.json
└── prod.server.js
/static/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | config/*.js
3 |
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | NODE_ENV: '"production"'
4 | }
5 |
--------------------------------------------------------------------------------
/src/common/stylus/index.styl:
--------------------------------------------------------------------------------
1 | @import "./reset.styl"
2 | @import "./base.styl"
3 | @import "./icon.styl"
--------------------------------------------------------------------------------
/src/common/js/config.js:
--------------------------------------------------------------------------------
1 | export const playMode = {
2 | sequence: 0,
3 | loop: 1,
4 | random: 2
5 | }
6 |
--------------------------------------------------------------------------------
/src/base/loading/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liugezhou/liugezhou_music/HEAD/src/base/loading/loading.gif
--------------------------------------------------------------------------------
/src/common/image/default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liugezhou/liugezhou_music/HEAD/src/common/image/default.png
--------------------------------------------------------------------------------
/src/base/song-list/first@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liugezhou/liugezhou_music/HEAD/src/base/song-list/first@2x.png
--------------------------------------------------------------------------------
/src/base/song-list/first@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liugezhou/liugezhou_music/HEAD/src/base/song-list/first@3x.png
--------------------------------------------------------------------------------
/src/base/song-list/third@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liugezhou/liugezhou_music/HEAD/src/base/song-list/third@2x.png
--------------------------------------------------------------------------------
/src/base/song-list/third@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liugezhou/liugezhou_music/HEAD/src/base/song-list/third@3x.png
--------------------------------------------------------------------------------
/src/common/fonts/music-icon.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liugezhou/liugezhou_music/HEAD/src/common/fonts/music-icon.eot
--------------------------------------------------------------------------------
/src/common/fonts/music-icon.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liugezhou/liugezhou_music/HEAD/src/common/fonts/music-icon.ttf
--------------------------------------------------------------------------------
/src/base/no-result/no-result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liugezhou/liugezhou_music/HEAD/src/base/no-result/no-result.png
--------------------------------------------------------------------------------
/src/base/song-list/second@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liugezhou/liugezhou_music/HEAD/src/base/song-list/second@2x.png
--------------------------------------------------------------------------------
/src/base/song-list/second@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liugezhou/liugezhou_music/HEAD/src/base/song-list/second@3x.png
--------------------------------------------------------------------------------
/src/common/fonts/music-icon.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liugezhou/liugezhou_music/HEAD/src/common/fonts/music-icon.woff
--------------------------------------------------------------------------------
/src/components/m-header/logo@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liugezhou/liugezhou_music/HEAD/src/components/m-header/logo@2x.png
--------------------------------------------------------------------------------
/src/components/m-header/logo@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liugezhou/liugezhou_music/HEAD/src/components/m-header/logo@3x.png
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const prodEnv = require('./prod.env')
4 |
5 | module.exports = merge(prodEnv, {
6 | NODE_ENV: '"development"'
7 | })
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/src/common/js/singer.js:
--------------------------------------------------------------------------------
1 | export default class Singer {
2 | constructor({id, name}) {
3 | this.id = id
4 | this.name = name
5 | this.avatar = `https://y.gtimg.cn/music/photo_new/T001R300x300M000${id}.jpg?max_age=2592000`
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | /dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | package-lock.json
8 |
9 | # Editor directories and files
10 | .idea
11 | .vscode
12 | *.suo
13 | *.ntvs*
14 | *.njsproj
15 | *.sln
16 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-vue-jsx", "transform-runtime"]
12 | }
13 |
--------------------------------------------------------------------------------
/src/common/js/uid.js:
--------------------------------------------------------------------------------
1 | let _uid = ''
2 |
3 | export function getUid () {
4 | if (_uid) {
5 | return _uid
6 | }
7 | if (!_uid) {
8 | const t = (new Date()).getUTCMilliseconds()
9 | _uid = '' + Math.round(2147483647 * Math.random()) * t % 1e10
10 | }
11 | return _uid
12 | }
13 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | "postcss-import": {},
6 | "postcss-url": {},
7 | // to edit target browsers: use "browserslist" field in package.json
8 | "autoprefixer": {}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/api/config.js:
--------------------------------------------------------------------------------
1 | export const commonParams = {
2 | g_tk: 1928093487,
3 | inCharset: 'utf-8',
4 | outCharset: 'utf-8',
5 | notice: 0,
6 | format: 'jsonp'
7 | }
8 |
9 | export const options = {
10 | param: 'jsonpCallback',
11 | prefix: 'jp'
12 | }
13 |
14 | export const ERR_OK = 0
15 |
--------------------------------------------------------------------------------
/src/common/stylus/base.styl:
--------------------------------------------------------------------------------
1 | @import "variable.styl"
2 |
3 | body, html
4 | line-height: 1
5 | font-family: 'PingFang SC', 'STHeitiSC-Light', 'Helvetica-Light', arial, sans-serif, 'Droid Sans Fallback'
6 | user-select: none
7 | -webkit-tap-highlight-color: transparent
8 | background: $color-background
9 | color: $color-text
10 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 | vue-music
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/store/state.js:
--------------------------------------------------------------------------------
1 | import { playMode } from 'common/js/config'
2 | import { loadSearch, loadPlay, loadFavorite } from "common/js/cache"
3 | const state = {
4 | singer: {},
5 | playing: false,
6 | fullScreen: false,
7 | playlist: [],
8 | sequenceList: [],
9 | mode: playMode.sequence,
10 | currentIndex: -1,
11 | disc: {},
12 | toplist: {},
13 | searchHistory: loadSearch(),
14 | playHistory: loadPlay(),
15 | favoriteList: loadFavorite()
16 | }
17 |
18 | export default state
19 |
--------------------------------------------------------------------------------
/src/common/stylus/mixin.styl:
--------------------------------------------------------------------------------
1 | // 背景图片
2 | bg-image($url)
3 | background-image: url($url + "@2x.png")
4 | @media (-webkit-min-device-pixel-ratio: 3),(min-device-pixel-ratio: 3)
5 | background-image: url($url + "@3x.png")
6 |
7 | // 不换行
8 | no-wrap()
9 | text-overflow: ellipsis
10 | overflow: hidden
11 | white-space: nowrap
12 |
13 | // 扩展点击区域
14 | extend-click()
15 | position: relative
16 | &:before
17 | content: ''
18 | position: absolute
19 | top: -10px
20 | left: -10px
21 | right: -10px
22 | bottom: -10px
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import * as actions from './actions'
4 | import * as getters from './getters'
5 | import state from './state'
6 | import mutations from './mutations'
7 | import createLogger from 'vuex/dist/logger'
8 |
9 | Vue.use(Vuex)
10 | // 开发环境使用
11 | const debug = process.env.NODE_ENV !== 'production'
12 |
13 | export default new Vuex.Store({
14 | actions,
15 | getters,
16 | state,
17 | mutations,
18 | strict: debug,
19 | plugins: debug ? [createLogger()] : []
20 | })
21 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
25 |
26 |
29 |
--------------------------------------------------------------------------------
/src/base/loading/loading.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{title}}
5 |
6 |
7 |
17 |
28 |
29 |
--------------------------------------------------------------------------------
/src/common/js/util.js:
--------------------------------------------------------------------------------
1 | function getRandomInt(min, max) {
2 | return Math.floor(Math.random() * (max - min + 1) + min)
3 | }
4 | export function shuffle(arr){
5 | let _arr = arr.slice()
6 | for(let i = 0; i < _arr.length; i++){
7 | let j = getRandomInt(0, i)
8 | let t = _arr[i]
9 | _arr[i] = _arr[j]
10 | _arr[j] = t
11 | }
12 | return _arr
13 | }
14 |
15 | export function debounce(func, delay) {
16 | let timer
17 | return function(...args) {
18 | if(timer) {
19 | clearTimeout(timer)
20 | }
21 | timer = setTimeout( ()=> {
22 | func.apply(this, args)
23 | },delay)
24 | }
25 | }
--------------------------------------------------------------------------------
/src/common/stylus/variable.styl:
--------------------------------------------------------------------------------
1 | // 颜色定义规范
2 | $color-background = #222
3 | $color-background-d = rgba(0, 0, 0, 0.3)
4 | $color-highlight-background = #333
5 | $color-dialog-background = #666
6 | $color-theme = #ffcd32
7 | $color-theme-d = rgba(255, 205, 49, 0.5)
8 | $color-sub-theme = #d93f30
9 | $color-text = #fff
10 | $color-text-d = rgba(255, 255, 255, 0.3)
11 | $color-text-l = rgba(255, 255, 255, 0.5)
12 | $color-text-ll = rgba(255, 255, 255, 0.8)
13 |
14 | //字体定义规范
15 | $font-size-small-s = 10px
16 | $font-size-small = 12px
17 | $font-size-medium = 14px
18 | $font-size-medium-x = 16px
19 | $font-size-large = 18px
20 | $font-size-large-x = 22px
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill'
2 | import Vue from 'vue'
3 | import App from './App'
4 | import router from './router'
5 | import store from './store'
6 | import fastclick from 'fastclick'
7 | import VueLazyload from 'vue-lazyload'
8 |
9 | /* eslint-disable no-unused-vars */
10 | // import vConsole from 'vconsole'
11 |
12 | // Vue.config.productionTip = false
13 | import 'common/stylus/index.styl'
14 | Vue.use(VueLazyload, {
15 | loading: require('common/image/default.png')
16 | })
17 |
18 | fastclick.attach(document.body)
19 | /* eslint-disable no-new */
20 | new Vue({
21 | el: '#app',
22 | render: h => h(App),
23 | store,
24 | router
25 | })
26 |
--------------------------------------------------------------------------------
/src/common/js/jsonp.js:
--------------------------------------------------------------------------------
1 | import originJSONP from 'jsonp'
2 |
3 | export default function jsonp(url, data, option) {
4 | url += (url.indexOf('?') < 0 ? '?' : '&') + param(data)
5 |
6 | return new Promise((resolve, reject) => {
7 | originJSONP(url, option, (err, data) => {
8 | if (!err) {
9 | resolve(data)
10 | } else {
11 | reject(err)
12 | }
13 | })
14 | })
15 | }
16 |
17 | export function param (data) {
18 | let url = ''
19 | for (var k in data) {
20 | let value = data[k] !== undefined ? data[k] : ''
21 | url += '&' + k + '=' + encodeURIComponent(value)
22 | }
23 | return url ? url.substring(1) : ''
24 | }
25 |
--------------------------------------------------------------------------------
/src/store/mutation-types.js:
--------------------------------------------------------------------------------
1 | export const SET_SINGER = 'SET_SINGER'
2 |
3 | export const SET_PLAYING_STATE = 'SET_PLAYING_STATE'
4 |
5 | export const SET_FULLSCREEN = 'SET_FULLSCREEN'
6 |
7 | export const SET_PLAYLIST = 'SET_PLAYLIST'
8 |
9 | export const SET_SEQUENCELIST = 'SET_SEQUENCELIST'
10 |
11 | export const SET_MODE = 'SET_MODE'
12 |
13 | export const SET_CURRENTINDEX = 'SET_CURRENTINDEX'
14 |
15 | export const SET_DISC = 'SET_DISC'
16 |
17 | export const SET_TOPLIST = 'SET_TOPLIST'
18 |
19 | export const SET_SEARCH_HISTORY = 'SET_SEARCH_HISTORY'
20 |
21 | export const SET_PLAY_HISTORY = 'SET_PLAY_HISTORY'
22 |
23 | export const SET_FAVORITE_LIST = 'SET_FAVORITE_LIST'
24 |
--------------------------------------------------------------------------------
/src/api/rank.js:
--------------------------------------------------------------------------------
1 | import jsonp from 'common/js/jsonp'
2 | import {commonParams, options} from './config'
3 |
4 | export function getTopList() {
5 | const url = 'https://c.y.qq.com/v8/fcg-bin/fcg_myqq_toplist.fcg'
6 |
7 | const data = Object.assign({}, commonParams, {
8 | uin: 0,
9 | needNewCode: 1,
10 | platform: 'h5'
11 | })
12 | return jsonp(url, data, options)
13 | }
14 |
15 | export function getMusicList(topid) {
16 | const url = 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_toplist_cp.fcg'
17 |
18 | const data = Object.assign({}, commonParams, {
19 | topid,
20 | needNewCode: 1,
21 | uin: 0,
22 | tpl: 3,
23 | page: 'detail',
24 | type: 'top',
25 | platform: 'h5'
26 | })
27 |
28 | return jsonp(url, data, options)
29 | }
--------------------------------------------------------------------------------
/src/base/no-result/no-result.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{title}}
6 |
7 |
8 |
9 |
10 |
20 |
--------------------------------------------------------------------------------
/src/store/getters.js:
--------------------------------------------------------------------------------
1 | export const singer = state => state.singer
2 |
3 | export const playing = state => state.playing
4 |
5 | export const fullScreen = state => state.fullScreen
6 |
7 | export const playlist = state => state.playlist
8 |
9 | export const sequenceList = state => state.sequenceList
10 |
11 | export const mode = state => state.mode
12 |
13 | export const currentIndex = state => state.currentIndex
14 |
15 |
16 | export const currentSong = (state) => {
17 | return state.playlist[state.currentIndex] || {}
18 | }
19 |
20 | export const disc = state => state.disc
21 |
22 | export const toplist = state => state.toplist
23 |
24 | export const searchHistory = state => state.searchHistory
25 |
26 | export const playHistory = state => state.playHistory
27 |
28 | export const favoriteList = state => state.favoriteList
--------------------------------------------------------------------------------
/ecosystem.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | apps : [{
3 | name: 'VueMusic',
4 | script: 'prod.server.js',
5 |
6 | // Options reference: https://pm2.keymetrics.io/docs/usage/application-declaration/
7 | args: 'one two',
8 | instances: 1,
9 | autorestart: true,
10 | watch: false,
11 | max_memory_restart: '1G',
12 | env: {
13 | NODE_ENV: 'development'
14 | },
15 | env_production: {
16 | NODE_ENV: 'production'
17 | }
18 | }],
19 |
20 | deploy : {
21 | production : {
22 | "user" : 'root',
23 | "host" : ['47.104.85.127'],
24 | "ref" : 'origin/master',
25 | "repo" : 'git@github.com:liugezhou/liugezhou_music.git',
26 | "path" : '/www/vuemusic/production',
27 | 'post-deploy' : 'npm install && npm run build && pm2 reload ecosystem.config.js --env production'
28 | }
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/src/api/singer.js:
--------------------------------------------------------------------------------
1 | import jsonp from 'common/js/jsonp'
2 | import {commonParams, options} from './config'
3 |
4 | export function getSingerList() {
5 | const url = 'https://c.y.qq.com/v8/fcg-bin/v8.fcg'
6 |
7 | const data = Object.assign({}, commonParams, {
8 | channel: 'singer',
9 | page: 'list',
10 | key: 'all_all_all',
11 | pagesize: 100,
12 | pagenum: 1,
13 | hostUin: 0,
14 | needNewCode: 0,
15 | platform: 'yqq'
16 | })
17 |
18 | return jsonp(url, data, options)
19 | }
20 |
21 | export function getSingerDetail(singerId) {
22 | const url = 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg'
23 |
24 | const data = Object.assign({}, commonParams, {
25 | hostUin: 0,
26 | platform: 'yqq',
27 | needNewCode: 0,
28 | singermid: singerId,
29 | order: 'listen',
30 | begin: 0,
31 | num: 100,
32 | songstatus: 1
33 | })
34 | return jsonp(url, data, options)
35 | }
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # music
2 |
3 | 音乐播放器
4 |
5 | # 项目介绍
6 | > 本项目是通过对Vue的学习而进行开发的一个移动端网页。
7 | > 项目初始化使用vue-cli脚手架进行搭建。
8 | > 项目包含技术栈或者第三方工具:
9 | > + vue-cli
10 | > + vue-router
11 | > + fastclick
12 | > + vue-lazyload
13 | > + axios
14 | > + better-scroll
15 | > + jsonp
16 | > + Vuex
17 | > + transition
18 | > + vconsole
19 | > + Charles
20 | > 等知识点总结中。
21 |
22 |
23 | # 项目更新
24 | > 该项目的学习与代码使用分支进行开发.
25 | > 每个分支或代码编写后的思考总结文章同步更新至:[这里](https://www.liugezhou.online/categories/Vue2-0%E5%BC%80%E5%8F%91%E4%BC%81%E4%B8%9A%E7%BA%A7%E7%A7%BB%E5%8A%A8%E9%9F%B3%E4%B9%90APP/)
26 |
27 | > 
28 |
29 | # 代码说明
30 | > 分之开发,其中chapter1-chapter15分为十五个章节进行每节课的代码提交,optimize为项目完结后的代码修改、优化。
31 | > master分之为项目最终代码。
32 |
33 | # 说明
34 | > 项目代码为跟着黄老师课程的第一手代码,之后项目会有变化,本仓库不会保持及时更新,还是建议想学习本课程的道友去购买黄老师的课程进行观看学习。
35 | > 黄老师会对此课程进行不断打磨更新。
36 |
37 | > 请勿索要源码,本仓库只是记录我的学习过程。
38 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // https://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parserOptions: {
6 | parser: 'babel-eslint'
7 | },
8 | env: {
9 | browser: true,
10 | },
11 | extends: [
12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
14 | 'plugin:vue/essential',
15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md
16 | 'standard'
17 | ],
18 | // required to lint *.vue files
19 | plugins: [
20 | 'vue'
21 | ],
22 | // add your custom rules here
23 | rules: {
24 | // allow paren-less arrow functions
25 | 'arrow-parens': 0,
26 | // allow async-await
27 | 'generator-star-spacing': 0,
28 | // allow debugger during development
29 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
30 | 'eol-last': 0,
31 | 'space-before-function-paren': 0
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/base/top-tip/top-tip.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
36 |
37 |
--------------------------------------------------------------------------------
/src/common/js/dom.js:
--------------------------------------------------------------------------------
1 | export function hasClass(el, className) {
2 | return el.classList.contains(className)
3 | }
4 |
5 | export function addClass(el, className) {
6 | el.classList.add(className)
7 | }
8 |
9 | export function getData(el, name, val) {
10 | const prefix = 'data-'
11 | if (val) {
12 | return el.setAttribute(prefix + name, val)
13 | }
14 | return el.getAttribute(prefix + name)
15 | }
16 |
17 | let elementStyle = document.createElement('div').style
18 |
19 | let vendor = (() => {
20 | let transformNames = {
21 | webkit: 'webkitTransform',
22 | Moz: 'MozTransform',
23 | O: 'OTransform',
24 | ms: 'msTransform',
25 | standard: 'transform'
26 | }
27 |
28 | for (let key in transformNames) {
29 | if (elementStyle[transformNames[key]] !== undefined) {
30 | return key
31 | }
32 | }
33 |
34 | return false
35 | })()
36 |
37 | export function prefixStyle(style) {
38 | if (vendor === false) {
39 | return false
40 | }
41 |
42 | if (vendor === 'standard') {
43 | return style
44 | }
45 |
46 | return vendor + style.charAt(0).toUpperCase() + style.substr(1)
47 | }
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 liugezhou
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/components/tab/tab.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 推荐
5 |
6 |
7 | 歌手
8 |
9 |
10 | 排行
11 |
12 |
13 | 搜索
14 |
15 |
16 |
17 |
20 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/store/mutations.js:
--------------------------------------------------------------------------------
1 | import * as types from './mutation-types'
2 |
3 | const mutations = {
4 | [types.SET_SINGER](state, singer) {
5 | state.singer = singer
6 | },
7 | [types.SET_PLAYING_STATE](state, flag) {
8 | state.playing = flag
9 | },
10 | [types.SET_FULLSCREEN](state, flag) {
11 | state.fullScreen = flag
12 | },
13 | [types.SET_PLAYLIST](state, list) {
14 | state.playlist = list
15 | },
16 | [types.SET_SEQUENCELIST](state, list) {
17 | state.sequenceList = list
18 | },
19 | [types.SET_MODE](state, mode) {
20 | state.mode = mode
21 | },
22 | [types.SET_CURRENTINDEX](state, index) {
23 | state.currentIndex = index
24 | },
25 | [types.SET_DISC](state, disc) {
26 | state.disc = disc
27 | },
28 | [types.SET_TOPLIST](state, toplist) {
29 | state.toplist = toplist
30 | },
31 | [types.SET_SEARCH_HISTORY](state, history) {
32 | state.searchHistory = history
33 | },
34 | [types.SET_PLAY_HISTORY](state, playhistory) {
35 | state.playHistory = playhistory
36 | },
37 | [types.SET_FAVORITE_LIST](state, favoriteList) {
38 | state.favoriteList = favoriteList
39 | }
40 | }
41 |
42 | export default mutations
43 |
--------------------------------------------------------------------------------
/src/components/m-header/m-header.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
13 |
47 |
48 |
--------------------------------------------------------------------------------
/src/base/switches/switches.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | {{item.name}}
6 |
7 |
8 |
9 |
10 |
29 |
30 |
--------------------------------------------------------------------------------
/src/api/search.js:
--------------------------------------------------------------------------------
1 | import jsonp from 'common/js/jsonp'
2 | import {commonParams, options} from './config'
3 | import axios from 'axios'
4 |
5 | const debug = process.env.NODE_ENV !== 'production'
6 |
7 | export function getHotSearch() {
8 | const url = 'https://c.y.qq.com/splcloud/fcgi-bin/gethotkey.fcg'
9 |
10 | const data = Object.assign({}, commonParams, {
11 | _: 1567068162534,
12 | uin: 0,
13 | platform: 'h5',
14 | needNewCode: 1
15 | })
16 | return jsonp(url, data, options)
17 | }
18 |
19 | export function search(query, page, zhida, perpage) {
20 | // const url = debug ? '/api/search' : 'http://ustbhuangyi.com/music/api/search'
21 | const url = debug ? '/api/search' : 'http://music.liugezhou.online/api/search'
22 | const data = Object.assign({}, commonParams, {
23 | w: query,
24 | p: page,
25 | perpage,
26 | n: perpage,
27 | catZhida: zhida ? 1 : 0,
28 | zhidaqu: 1,
29 | t: 0,
30 | flag: 1,
31 | ie: 'utf-8',
32 | sem: 1,
33 | aggr: 0,
34 | remoteplace: 'txt.mqq.all',
35 | uin: 0,
36 | needNewCode: 1,
37 | platform: 'h5',
38 | format: 'json'
39 | })
40 |
41 | return axios.get(url, {
42 | params: data
43 | }).then((res) => {
44 | return Promise.resolve(res.data)
45 | })
46 | }
--------------------------------------------------------------------------------
/src/base/search-list/search-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{item}}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
32 |
33 |
--------------------------------------------------------------------------------
/src/base/progress-circle/progress-circle.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
10 |
11 |
12 |
36 |
37 |
--------------------------------------------------------------------------------
/src/common/stylus/reset.styl:
--------------------------------------------------------------------------------
1 | /**
2 | * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
3 | * http://cssreset.com
4 | */
5 | html, body, div, span, applet, object, iframe,
6 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
7 | a, abbr, acronym, address, big, cite, code,
8 | del, dfn, em, img, ins, kbd, q, s, samp,
9 | small, strike, strong, sub, sup, tt, var,
10 | b, u, i, center,
11 | dl, dt, dd, ol, ul, li,
12 | fieldset, form, label, legend,
13 | table, caption, tbody, tfoot, thead, tr, th, td,
14 | article, aside, canvas, details, embed,
15 | figure, figcaption, footer, header,
16 | menu, nav, output, ruby, section, summary,
17 | time, mark, audio, video, input
18 | margin: 0
19 | padding: 0
20 | border: 0
21 | font-size: 100%
22 | font-weight: normal
23 | vertical-align: baseline
24 |
25 | /* HTML5 display-role reset for older browsers */
26 | article, aside, details, figcaption, figure,
27 | footer, header, menu, nav, section
28 | display: block
29 |
30 | body
31 | line-height: 1
32 |
33 | blockquote, q
34 | quotes: none
35 |
36 | blockquote:before, blockquote:after,
37 | q:before, q:after
38 | content: none
39 |
40 | table
41 | border-collapse: collapse
42 | border-spacing: 0
43 |
44 | /* custom */
45 |
46 | a
47 | color: #7e8c8d
48 | -webkit-backface-visibility: hidden
49 | text-decoration: none
50 |
51 | li
52 | list-style: none
53 |
54 | body
55 | -webkit-text-size-adjust: none
56 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0)
57 |
--------------------------------------------------------------------------------
/src/api/recommend.js:
--------------------------------------------------------------------------------
1 | import jsonp from 'common/js/jsonp'
2 | import {commonParams, options} from './config'
3 | import axios from 'axios'
4 |
5 | const debug = process.env.NODE_ENV !== 'production'
6 | export function getRecommend() {
7 | const url = 'https://c.y.qq.com/musichall/fcgi-bin/fcg_yqqhomepagerecommend.fcg'
8 |
9 | const data = Object.assign({}, commonParams, {
10 | platform: 'h5',
11 | uin: 0,
12 | needNewCode: 1
13 | })
14 |
15 | return jsonp(url, data, options)
16 | }
17 |
18 | export function getDiscList() {
19 | const url = '/api/getDiscList'
20 | const data = Object.assign({}, commonParams, {
21 | platform: 'yqq',
22 | hostUin: 0,
23 | sin: 0,
24 | ein: 29,
25 | sortId: 5,
26 | needNewCode: 0,
27 | categoryId: 10000000,
28 | rnd: Math.random(),
29 | format: 'json'
30 | })
31 |
32 | return axios.get(url, {
33 | params: data
34 | }).then((res) => {
35 | return Promise.resolve(res.data)
36 | })
37 | }
38 |
39 | export function getSongList (disstid) {
40 | // const url = debug ? '/api/getCdInfo' : 'http://ustbhuangyi.com/music/api/getCdInfo'
41 | const url = debug ? '/api/getCdInfo' : 'http://music.liugezhou.online/api/getCdInfo'
42 |
43 | const data = Object.assign({}, commonParams, {
44 | disstid,
45 | type: 1,
46 | json: 1,
47 | utf8: 1,
48 | onlysong: 0,
49 | platform: 'yqq',
50 | hostUin: 0,
51 | needNewCode: 0
52 | })
53 |
54 | return axios.get(url, {
55 | params: data
56 | }).then((res) => {
57 | return Promise.resolve(res.data)
58 | })
59 | }
60 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 |
4 | const Recommend = () => import('components/recommend/recommend')
5 | const Rank = () => import('components/rank/rank')
6 | const Singer = () => import('components/singer/singer')
7 | const Search = () => import('components/search/search')
8 | const SingerDetail = () => import('components/singer-detail/singer-detail')
9 | const Disc = () => import('components/disc/disc')
10 | const TopList = () => import('components/top-list/top-list')
11 | const User = () => import('components/user-center/user-center')
12 |
13 | Vue.use(Router)
14 |
15 | export default new Router({
16 | routes: [
17 | {
18 | path: '/',
19 | redirect: '/recommend'
20 | },
21 | {
22 | path: '/recommend',
23 | component: Recommend,
24 | children: [
25 | {
26 | path: ':id',
27 | component: Disc
28 | }
29 | ]
30 | },
31 | {
32 | path: '/rank',
33 | component: Rank,
34 | children: [
35 | {
36 | path: ':id',
37 | component: TopList
38 | }
39 | ]
40 | },
41 | {
42 | path: '/singer',
43 | component: Singer,
44 | children: [
45 | {
46 | path: ':id',
47 | component: SingerDetail
48 | }
49 | ]
50 | },
51 | {
52 | path: '/search',
53 | component: Search,
54 | children: [
55 | {
56 | path: ':id',
57 | component: SingerDetail
58 | }
59 | ]
60 | },
61 | {
62 | path: '/user',
63 | component: User
64 | }
65 | ]
66 | })
67 |
--------------------------------------------------------------------------------
/src/base/search-box/search-box.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
41 |
42 |
--------------------------------------------------------------------------------
/src/components/disc/disc.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
63 |
70 |
71 |
--------------------------------------------------------------------------------
/src/components/singer-detail/singer-detail.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
64 |
71 |
72 |
--------------------------------------------------------------------------------
/src/components/top-list/top-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
66 |
67 |
--------------------------------------------------------------------------------
/src/common/stylus/icon.styl:
--------------------------------------------------------------------------------
1 | @font-face
2 | font-family: 'music-icon'
3 | src: url('../fonts/music-icon.eot?2qevqt')
4 | src: url('../fonts/music-icon.eot?2qevqt#iefix') format('embedded-opentype'),
5 | url('../fonts/music-icon.ttf?2qevqt') format('truetype'),
6 | url('../fonts/music-icon.woff?2qevqt') format('woff'),
7 | url('../fonts/music-icon.svg?2qevqt#music-icon') format('svg')
8 | font-weight: normal
9 | font-style: normal
10 |
11 | [class^="icon-"], [class*=" icon-"]
12 | /* use !important to prevent issues with browser extensions that change fonts */
13 | font-family: 'music-icon' !important
14 | speak: none
15 | font-style: normal
16 | font-weight: normal
17 | font-variant: normal
18 | text-transform: none
19 | line-height: 1
20 |
21 | /* Better Font Rendering =========== */
22 | -webkit-font-smoothing: antialiased
23 | -moz-osx-font-smoothing: grayscale
24 |
25 | .icon-ok:before
26 | content: "\e900"
27 |
28 | .icon-close:before
29 | content: "\e901"
30 |
31 | .icon-add:before
32 | content: "\e902"
33 |
34 | .icon-play-mini:before
35 | content: "\e903"
36 |
37 | .icon-playlist:before
38 | content: "\e904"
39 |
40 | .icon-music:before
41 | content: "\e905"
42 |
43 | .icon-search:before
44 | content: "\e906"
45 |
46 | .icon-clear:before
47 | content: "\e907"
48 |
49 | .icon-delete:before
50 | content: "\e908"
51 |
52 | .icon-favorite:before
53 | content: "\e909"
54 |
55 | .icon-not-favorite:before
56 | content: "\e90a"
57 |
58 | .icon-pause:before
59 | content: "\e90b"
60 |
61 | .icon-play:before
62 | content: "\e90c"
63 |
64 | .icon-prev:before
65 | content: "\e90d"
66 |
67 | .icon-loop:before
68 | content: "\e90e"
69 |
70 | .icon-sequence:before
71 | content: "\e90f"
72 |
73 | .icon-random:before
74 | content: "\e910"
75 |
76 | .icon-back:before
77 | content: "\e911"
78 |
79 | .icon-mine:before
80 | content: "\e912"
81 |
82 | .icon-next:before
83 | content: "\e913"
84 |
85 | .icon-dismiss:before
86 | content: "\e914"
87 |
88 | .icon-pause-mini:before
89 | content: "\e915"
90 |
--------------------------------------------------------------------------------
/src/common/js/song.js:
--------------------------------------------------------------------------------
1 | import { getLyric, getSongsUrl } from 'api/song'
2 | import { ERR_OK } from 'api/config'
3 | import { Base64 } from 'js-base64'
4 |
5 | export default class Song {
6 | constructor ({ id, mid, singer, name, album, duration, image, url }) {
7 | this.id = id
8 | this.mid = mid
9 | this.singer = singer
10 | this.name = name
11 | this.album = album
12 | this.duration = duration
13 | this.image = image
14 | this.filename = `C400${this.mid}.m4a`
15 | this.url = url
16 | }
17 |
18 | getLyric () {
19 | if (this.lyric) {
20 | return Promise.resolve(this.lyric)
21 | }
22 |
23 | return new Promise((resolve, reject) => {
24 | getLyric(this.mid).then((res) => {
25 | if (res.retcode === ERR_OK) {
26 | this.lyric = Base64.decode(res.lyric)
27 | resolve(this.lyric)
28 | } else {
29 | reject(new Error('no lyric'))
30 | }
31 | })
32 | })
33 | }
34 | }
35 |
36 | export function createSong (musicData) {
37 | return new Song({
38 | id: musicData.songid,
39 | mid: musicData.songmid,
40 | singer: filterSinger(musicData.singer),
41 | name: musicData.songname,
42 | album: musicData.albumname,
43 | duration: musicData.interval,
44 | image: `https://y.gtimg.cn/music/photo_new/T002R300x300M000${musicData.albummid}.jpg?max_age=2592000`,
45 | url: musicData.url
46 | })
47 | }
48 |
49 | export function filterSinger (singer) {
50 | let ret = []
51 | if (!singer) {
52 | return ''
53 | }
54 | singer.forEach((s) => {
55 | ret.push(s.name)
56 | })
57 | return ret.join('/')
58 | }
59 |
60 | export function isValidMusic (musicData) {
61 | return musicData.songid && musicData.albummid && (!musicData.pay || musicData.pay.payalbumprice === 0)
62 | }
63 |
64 | export function processSongsUrl (songs) {
65 | if (!songs.length) {
66 | return Promise.resolve(songs)
67 | }
68 | return getSongsUrl(songs).then((purlMap) => {
69 | songs = songs.filter((song) => {
70 | const purl = purlMap[song.mid]
71 | if (purl) {
72 | song.url = purl.indexOf('http') === -1 ? `http://dl.stream.qqmusic.qq.com/${purl}` : purl
73 | return true
74 | }
75 | return false
76 | })
77 | return songs
78 | })
79 | }
80 |
--------------------------------------------------------------------------------
/src/base/song-list/song-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
{{song.name}}
10 |
{{getDesc(song)}}
11 |
12 |
13 |
14 |
15 |
16 |
50 |
92 |
--------------------------------------------------------------------------------
/src/common/js/cache.js:
--------------------------------------------------------------------------------
1 | import storage from 'good-storage'
2 |
3 | const SEARCH_KEY='__search__'
4 | const SEARCH_MAX_LENGTH= 15
5 |
6 | const PLAY_KEY='__play__'
7 | const PLAY_MAX_LENGTH= 200
8 |
9 | const FAVORITE_KEY='__favorite__'
10 | const FAVORITE_MAX_LENGTH= 200
11 |
12 | function insertArray(arr, val, compare, maxLen) {
13 | const index = arr.findIndex(compare)
14 | if(Object.is(index, 0)) {
15 | return
16 | }
17 | if(index>0) {
18 | arr.splice(index, 1)
19 | }
20 | arr.unshift(val)
21 | if(maxLen && arr.length > maxLen) {
22 | arr.pop()
23 | }
24 | }
25 |
26 | function deleteFromArray(arr, compare) {
27 | const index = arr.findIndex(compare)
28 | if(index > -1) {
29 | arr.splice(index, 1)
30 | }
31 | }
32 |
33 | export function saveSearch(query) {
34 | let searches = storage.get(SEARCH_KEY, [])
35 | insertArray(searches, query, (item)=> {
36 | return item === query
37 | }, SEARCH_MAX_LENGTH)
38 | storage.set(SEARCH_KEY, searches)
39 | return searches
40 | }
41 |
42 | export function loadSearch() {
43 | return storage.get(SEARCH_KEY,[])
44 | }
45 |
46 | export function deleteSearch(query) {
47 | let searches = storage.get(SEARCH_KEY, [])
48 | deleteFromArray(searches, (item) => {
49 | return item === query
50 | })
51 | storage.set(SEARCH_KEY, searches)
52 | return searches
53 | }
54 |
55 | export function clearSearch() {
56 | storage.remove(SEARCH_KEY)
57 | return []
58 | }
59 |
60 | export function savePlay(song) {
61 | let songs = storage.get(PLAY_KEY,[])
62 | insertArray(songs, song , (item) => {
63 | return item.id ===song.id
64 | }, PLAY_MAX_LENGTH)
65 | storage.set(PLAY_KEY, songs)
66 | return songs
67 | }
68 |
69 | export function loadPlay() {
70 | return storage.get(PLAY_KEY,[])
71 | }
72 |
73 | export function saveFavorite(song) {
74 | let songs = storage.get(FAVORITE_KEY,[])
75 | insertArray(songs, song , (item) => {
76 | return song.id === item.id
77 | }, FAVORITE_MAX_LENGTH)
78 | storage.set(FAVORITE_KEY, songs)
79 | return songs
80 | }
81 |
82 | export function deleteFavorite(song) {
83 | let songs = storage.get(FAVORITE_KEY,[])
84 | deleteFromArray(songs, (item) => {
85 | return item.id === song.id
86 | })
87 | storage.set(FAVORITE_KEY, songs)
88 | return songs
89 | }
90 |
91 | export function loadFavorite() {
92 | return storage.get(FAVORITE_KEY,[])
93 | }
94 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // Template version: 1.3.1
3 | // see http://vuejs-templates.github.io/webpack for documentation.
4 |
5 | const path = require('path')
6 |
7 | module.exports = {
8 | dev: {
9 |
10 | // Paths
11 | assetsSubDirectory: 'static',
12 | assetsPublicPath: '/',
13 | proxyTable: {},
14 |
15 | // Various Dev Server settings
16 | host: '0.0.0.0', // can be overwritten by process.env.HOST
17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
18 | autoOpenBrowser: false,
19 | errorOverlay: true,
20 | notifyOnErrors: true,
21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
22 |
23 | // Use Eslint Loader?
24 | // If true, your code will be linted during bundling and
25 | // linting errors and warnings will be shown in the console.
26 | useEslint: true,
27 | // If true, eslint errors and warnings will also be shown in the error overlay
28 | // in the browser.
29 | showEslintErrorsInOverlay: false,
30 |
31 | /**
32 | * Source Maps
33 | */
34 |
35 | // https://webpack.js.org/configuration/devtool/#development
36 | devtool: 'cheap-module-eval-source-map',
37 |
38 | // If you have problems debugging vue-files in devtools,
39 | // set this to false - it *may* help
40 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
41 | cacheBusting: true,
42 |
43 | cssSourceMap: false
44 | },
45 |
46 | build: {
47 | // Template for index.html
48 | index: path.resolve(__dirname, '../dist/index.html'),
49 | env: require('./prod.env'),
50 | port: 9000,
51 | // Paths
52 | assetsRoot: path.resolve(__dirname, '../dist'),
53 | assetsSubDirectory: 'static',
54 | assetsPublicPath: '/',
55 |
56 | /**
57 | * Source Maps
58 | */
59 |
60 | productionSourceMap: true,
61 | // https://webpack.js.org/configuration/devtool/#production
62 | devtool: '#source-map',
63 |
64 | // Gzip off by default as many popular static hosts such as
65 | // Surge or Netlify already gzip all static assets for you.
66 | // Before setting to `true`, make sure to:
67 | // npm install --save-dev compression-webpack-plugin
68 | productionGzip: false,
69 | productionGzipExtensions: ['js', 'css'],
70 |
71 | // Run the build command with an extra argument to
72 | // View the bundle analyzer report after build finishes:
73 | // `npm run build --report`
74 | // Set to `true` or `false` to always turn it on or off
75 | bundleAnalyzerReport: process.env.npm_config_report
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "music",
3 | "version": "1.0.0",
4 | "description": "音乐播放器",
5 | "author": "sixWeek <825386039@qq.com>",
6 | "private": true,
7 | "scripts": {
8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
9 | "start": "npm run dev",
10 | "lint": "eslint --ext .js,.vue src",
11 | "build": "node build/build.js"
12 | },
13 | "dependencies": {
14 | "axios": ">=0.21.1",
15 | "babel-runtime": "^6.0.0",
16 | "better-scroll": "^1.6.0",
17 | "create-keyframe-animation": "^0.1.0",
18 | "fastclick": "^1.0.6",
19 | "good-storage": "^1.1.0",
20 | "js-base64": "^2.1.9",
21 | "jsonp": "0.2.1",
22 | "lyric-parser": "^1.0.1",
23 | "vue": "^2.5.2",
24 | "vue-lazyload": "1.0.3",
25 | "vue-router": "^3.0.1",
26 | "vuex": "^2.3.1"
27 | },
28 | "devDependencies": {
29 | "autoprefixer": "^7.1.2",
30 | "babel-core": "^6.22.1",
31 | "babel-eslint": "^8.2.1",
32 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
33 | "babel-loader": "^7.1.1",
34 | "babel-plugin-syntax-jsx": "^6.18.0",
35 | "babel-plugin-transform-runtime": "^6.22.0",
36 | "babel-plugin-transform-vue-jsx": "^3.5.0",
37 | "babel-polyfill": "^6.2.0",
38 | "babel-preset-env": "^1.3.2",
39 | "babel-preset-stage-2": "^6.22.0",
40 | "body-parser": "^1.18.2",
41 | "chalk": "^2.0.1",
42 | "copy-webpack-plugin": "^4.0.1",
43 | "css-loader": "^0.28.0",
44 | "eslint": "^4.15.0",
45 | "eslint-config-standard": "^10.2.1",
46 | "eslint-friendly-formatter": "^3.0.0",
47 | "eslint-loader": "^1.7.1",
48 | "eslint-plugin-import": "^2.7.0",
49 | "eslint-plugin-node": "^5.2.0",
50 | "eslint-plugin-promise": "^3.4.0",
51 | "eslint-plugin-standard": "^3.0.1",
52 | "eslint-plugin-vue": "^4.0.0",
53 | "express": "^4.17.1",
54 | "extract-text-webpack-plugin": "^3.0.0",
55 | "file-loader": "^1.1.4",
56 | "friendly-errors-webpack-plugin": "^1.6.1",
57 | "html-webpack-plugin": "^2.30.1",
58 | "node-notifier": ">=8.0.1",
59 | "optimize-css-assets-webpack-plugin": "^3.2.0",
60 | "ora": "^1.2.0",
61 | "portfinder": "^1.0.13",
62 | "postcss-import": "^11.0.0",
63 | "postcss-loader": "^2.0.8",
64 | "postcss-url": "^7.2.1",
65 | "rimraf": "^2.6.0",
66 | "semver": "^5.3.0",
67 | "shelljs": "^0.7.6",
68 | "stylus": "^0.54.5",
69 | "stylus-loader": "^2.1.1",
70 | "uglifyjs-webpack-plugin": "^1.1.1",
71 | "url-loader": "^0.5.8",
72 | "vue-loader": "^13.3.0",
73 | "vue-style-loader": "^3.0.1",
74 | "vue-template-compiler": "^2.5.2",
75 | "webpack": "^3.6.0",
76 | "webpack-bundle-analyzer": ">=3.3.2",
77 | "webpack-dev-server": ">=3.1.11",
78 | "webpack-merge": "^4.1.0",
79 | "vconsole": "^2.5.2"
80 | },
81 | "engines": {
82 | "node": ">= 6.0.0",
83 | "npm": ">= 3.0.0"
84 | },
85 | "browserslist": [
86 | "> 1%",
87 | "last 2 versions",
88 | "not ie <= 8"
89 | ]
90 | }
91 |
--------------------------------------------------------------------------------
/src/api/song.js:
--------------------------------------------------------------------------------
1 | import { commonParams } from './config'
2 | import { getUid } from 'common/js/uid'
3 | import axios from 'axios'
4 | import { ERR_OK } from 'api/config'
5 |
6 | const debug = process.env.NODE_ENV !== 'production'
7 |
8 | export function getLyric (mid) {
9 | // const url = debug ? '/api/lyric' : 'http://ustbhuangyi.com/music/api/lyric'
10 | const url = debug ? '/api/lyric' : 'http://music.liugezhou.online/api/lyric'
11 |
12 | const data = Object.assign({}, commonParams, {
13 | songmid: mid,
14 | platform: 'yqq',
15 | hostUin: 0,
16 | needNewCode: 0,
17 | categoryId: 10000000,
18 | pcachetime: +new Date(),
19 | format: 'json'
20 | })
21 |
22 | return axios.get(url, {
23 | params: data
24 | }).then((res) => {
25 | return Promise.resolve(res.data)
26 | })
27 | }
28 |
29 | export function getSongsUrl (songs) {
30 | // const url = debug ? '/api/getPurlUrl' : 'http://ustbhuangyi.com/music/api/getPurlUrl'
31 | const url = debug ? '/api/getPurlUrl' : 'http://music.liugezhou.online/api/getPurlUrl'
32 |
33 | let mids = []
34 | let types = []
35 |
36 | songs.forEach((song) => {
37 | mids.push(song.mid)
38 | types.push(0)
39 | })
40 |
41 | const urlMid = genUrlMid(mids, types)
42 |
43 | const data = Object.assign({}, commonParams, {
44 | g_tk: 5381,
45 | format: 'json',
46 | platform: 'h5',
47 | needNewCode: 1,
48 | uin: 0
49 | })
50 |
51 | return new Promise((resolve, reject) => {
52 | let tryTime = 3
53 |
54 | function request () {
55 | return axios.post(url, {
56 | comm: data,
57 | req_0: urlMid
58 | }).then((response) => {
59 | const res = response.data
60 | if (res.code === ERR_OK) {
61 | let urlMid = res.req_0
62 | if (urlMid && urlMid.code === ERR_OK) {
63 | const purlMap = {}
64 | urlMid.data.midurlinfo.forEach((item) => {
65 | if (item.purl) {
66 | purlMap[item.songmid] = item.purl
67 | }
68 | })
69 | if (Object.keys(purlMap).length > 0) {
70 | resolve(purlMap)
71 | } else {
72 | retry()
73 | }
74 | } else {
75 | retry()
76 | }
77 | } else {
78 | retry()
79 | }
80 | })
81 | }
82 |
83 | function retry () {
84 | if (--tryTime >= 0) {
85 | request()
86 | } else {
87 | reject(new Error('Can not get the songs url'))
88 | }
89 | }
90 |
91 | request()
92 | })
93 | }
94 |
95 | function genUrlMid (mids, types) {
96 | const guid = getUid()
97 | return {
98 | module: 'vkey.GetVkeyServer',
99 | method: 'CgiGetVkey',
100 | param: {
101 | guid,
102 | songmid: mids,
103 | songtype: types,
104 | uin: '0',
105 | loginflag: 0,
106 | platform: '23'
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/base/confirm/confirm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{text}}
7 |
8 |
{{cancelButtonText}}
9 |
{{confirmButtonText}}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
56 |
57 |
--------------------------------------------------------------------------------
/src/components/singer/singer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
98 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/src/common/js/mixin.js:
--------------------------------------------------------------------------------
1 | import { mapGetters, mapMutations, mapActions } from "vuex"
2 | import {playMode} from 'common/js/config'
3 | import { shuffle } from 'common/js/util'
4 | export const playlistMixin = {
5 | computed: {
6 | ...mapGetters([
7 | 'playlist'
8 | ])
9 | },
10 | mounted() {
11 | this.handlePlaylist(this.playlist)
12 | },
13 | activated() {
14 | this.handlePlaylist(this.playlist)
15 | },
16 | watch: {
17 | playlist(newVal) {
18 | this.handlePlaylist(newVal)
19 | }
20 | },
21 | methods: {
22 | handlePlaylist() {
23 | throw new Error('component must implement handlePlaylist method')
24 | }
25 | }
26 | }
27 | export const playerMixin = {
28 | computed: {
29 | iconMode() {
30 | return this.mode === playMode.sequence ? 'icon-sequence' : this.mode === playMode.loop ? 'icon-loop' : 'icon-random'
31 | },
32 | ...mapGetters([
33 | 'sequenceList',
34 | 'currentSong',
35 | 'playlist',
36 | 'mode',
37 | 'favoriteList'
38 | ])
39 | },
40 | methods: {
41 | changeMode() {
42 | const mode = (this.mode +1) % 3
43 | this.setMode(mode)
44 | let list = null
45 | if(mode === playMode.random){
46 | list = shuffle(this.sequenceList)
47 | }else {
48 | list = this.sequenceList
49 | }
50 | this.resetCurrentIndex(list)
51 | this.setPlayList(list)
52 | },
53 | resetCurrentIndex(list) {
54 | let index = list.findIndex((item) => {
55 | return item.id === this.currentSong.id
56 | })
57 | this.setCurrentIndex(index)
58 | },
59 | getFavoriteIcon(song){
60 | if(this.isFavorite(song)) {
61 | return 'icon-favorite'
62 | }
63 | return 'icon-not-favorite'
64 | },
65 | toggleFavorite(song) {
66 | if(this.isFavorite(song)) {
67 | this.deleteFavoriteList(song)
68 | }else{
69 | this.saveFavoriteList(song)
70 | }
71 | },
72 | isFavorite(song) {
73 | const index = this.favoriteList.findIndex( (item) => {
74 | return item.id === song.id
75 | })
76 | return index > -1
77 | },
78 | ...mapMutations({
79 | setPlayingState: 'SET_PLAYING_STATE',
80 | setCurrentIndex: 'SET_CURRENTINDEX',
81 | setMode: 'SET_MODE',
82 | setPlayList: 'SET_PLAYLIST'
83 | }),
84 | ...mapActions([
85 | 'saveFavoriteList',
86 | 'deleteFavoriteList'
87 | ])
88 | },
89 | }
90 |
91 | export const searchMixin = {
92 | data() {
93 | return {
94 | query:'',
95 | refreshDelay: 100
96 | }
97 | },
98 | computed: {
99 | ...mapGetters([
100 | 'searchHistory'
101 | ])
102 | },
103 | methods: {
104 | blurInput() {
105 | this.$refs.searchBox.blur()
106 | },
107 | saveSearch() {
108 | this.saveSearchHistory(this.query)
109 | },
110 | onQueryChange(query) {
111 | this.query = query
112 | },
113 | addQuery(query) {
114 | this.$refs.searchBox.setQuery(query)
115 | },
116 | ...mapActions([
117 | 'saveSearchHistory',
118 | 'deleteSearchHistory'
119 | ])
120 | }
121 | }
--------------------------------------------------------------------------------
/src/base/progress-bar/progress-bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
75 |
76 |
106 |
--------------------------------------------------------------------------------
/src/components/rank/rank.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {{index + 1}}
12 | {{song.songname}}--{{song.singername}}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
71 |
114 |
115 |
--------------------------------------------------------------------------------
/src/base/scroll/scroll.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
155 |
156 |
158 |
159 |
--------------------------------------------------------------------------------
/src/base/slider/slider.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
114 |
155 |
156 |
--------------------------------------------------------------------------------
/src/store/actions.js:
--------------------------------------------------------------------------------
1 | import * as types from './mutation-types'
2 | import { playMode } from 'common/js/config'
3 | import { shuffle } from 'common/js/util'
4 | import { saveSearch, deleteSearch, clearSearch, savePlay, saveFavorite, deleteFavorite} from 'common/js/cache'
5 | function findIndex(list, song) {
6 | return list.findIndex((item) => {
7 | return item.id === song.id
8 | })
9 | }
10 | export const selectPlay = function ({commit, state}, {list, index}) {
11 | commit(types.SET_SEQUENCELIST, list)
12 | if(state.mode === playMode.random) {
13 | let randomList = shuffle(list)
14 | commit(types.SET_PLAYLIST, randomList)
15 | index = findIndex(randomList, list[index])
16 | }else {
17 | commit(types.SET_PLAYLIST, list)
18 | }
19 | commit(types.SET_CURRENTINDEX, index)
20 | commit(types.SET_FULLSCREEN, true)
21 | commit(types.SET_PLAYING_STATE, true)
22 | }
23 |
24 | export const randomPlay = function({commit}, {list}){
25 | commit(types.SET_MODE, playMode.random)
26 | commit(types.SET_SEQUENCELIST, list)
27 | let randomList = shuffle(list)
28 | commit(types.SET_PLAYLIST, randomList)
29 | commit(types.SET_CURRENTINDEX, 0)
30 | commit(types.SET_FULLSCREEN, true)
31 | commit(types.SET_PLAYING_STATE, true)
32 | }
33 |
34 | export const insertSong = function({commit,state}, song) {
35 | let playlist = state.playlist.slice()
36 | let sequenceList = state.sequenceList.slice()
37 | let currentIndex = state.currentIndex
38 | // 记录当前歌曲
39 | let currentSong = playlist[currentIndex]
40 | // 查找当前列表中是否有待插入的歌曲并返回其索引
41 | let fpIndex = findIndex(playlist, song)
42 | // 因为是插入歌曲,所以索引+1
43 | currentIndex++
44 | // 插入这首歌到当前索引位置
45 | playlist.splice(currentIndex, 0, song)
46 | // 如果已经包含了这首歌
47 | if (fpIndex > -1) {
48 | // 如果当前插入的序号大于列表中的序号
49 | if (currentIndex > fpIndex) {
50 | playlist.splice(fpIndex, 1)
51 | currentIndex--
52 | } else {
53 | playlist.splice(fpIndex + 1, 1)
54 | }
55 | }
56 |
57 | let currentSIndex = findIndex(sequenceList, currentSong) + 1
58 |
59 | let fsIndex = findIndex(sequenceList, song)
60 |
61 | sequenceList.splice(currentSIndex, 0, song)
62 |
63 | if (fsIndex > -1) {
64 | if (currentSIndex > fsIndex) {
65 | sequenceList.splice(fsIndex, 1)
66 | } else {
67 | sequenceList.splice(fsIndex + 1, 1)
68 | }
69 | }
70 | commit(types.SET_PLAYLIST, playlist)
71 | commit(types.SET_SEQUENCELIST, sequenceList)
72 | commit(types.SET_CURRENTINDEX, currentIndex)
73 | commit(types.SET_FULLSCREEN, true)
74 | commit(types.SET_PLAYING_STATE, true)
75 | }
76 |
77 | export const saveSearchHistory = function ({commit}, query) {
78 | commit(types.SET_SEARCH_HISTORY, saveSearch(query))
79 | }
80 |
81 | export const deleteSearchHistory = function ( {commit}, query) {
82 | commit(types.SET_SEARCH_HISTORY, deleteSearch(query))
83 | }
84 |
85 | export const clearSearchHistory = function ( {commit}) {
86 | commit(types.SET_SEARCH_HISTORY, clearSearch())
87 | }
88 |
89 | export const deleteSong = function ( {commit, state}, song) {
90 | let playlist = state.playlist.slice()
91 | let sequenceList = state.sequenceList.slice()
92 | let currentIndex = state.currentIndex
93 | let pIndex = findIndex(playlist, song)
94 | playlist.splice(pIndex, 1)
95 | let sIndex = findIndex(sequenceList, song)
96 | sequenceList.splice(sIndex, 1)
97 | if (currentIndex > pIndex || currentIndex === playlist.length) {
98 | currentIndex--
99 | }
100 |
101 | commit(types.SET_PLAYLIST, playlist)
102 | commit(types.SET_SEQUENCELIST, sequenceList)
103 | commit(types.SET_CURRENTINDEX, currentIndex)
104 |
105 | const playingState = playlist.length > 0
106 | commit(types.SET_PLAYING_STATE, playingState)
107 |
108 | }
109 |
110 | export const deleteSongList = function( {commit} ) {
111 | commit(types.SET_PLAYLIST,[])
112 | commit(types.SET_SEQUENCELIST, [])
113 | commit(types.SET_CURRENTINDEX, -1)
114 | commit(types.SET_PLAYING_STATE, false)
115 | }
116 |
117 | export const savePlayHistory = function ({commit, state}, song) {
118 | commit(types.SET_PLAY_HISTORY,savePlay(song))
119 | }
120 |
121 | export const saveFavoriteList = function({commit}, song) {
122 | commit(types.SET_FAVORITE_LIST,saveFavorite(song))
123 | }
124 |
125 | export const deleteFavoriteList = function({commit}, song) {
126 | commit(types.SET_FAVORITE_LIST,deleteFavorite(song))
127 | }
--------------------------------------------------------------------------------
/prod.server.js:
--------------------------------------------------------------------------------
1 | var express = require('express')
2 | var compression = require('compression')
3 | var config = require('./config/index')
4 | var axios = require('axios')
5 | const bodyParser = require('body-parser')
6 |
7 | var port = process.env.PORT || config.build.port
8 |
9 | var app = express()
10 |
11 | var apiRoutes = express.Router()
12 |
13 | apiRoutes.get('/getDiscList', function (req, res) {
14 | var url = 'https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg'
15 | axios.get(url, {
16 | headers: {
17 | referer: 'https://c.y.qq.com/',
18 | host: 'c.y.qq.com'
19 | },
20 | params: req.query
21 | }).then((response) => {
22 | res.json(response.data)
23 | }).catch((e) => {
24 | console.log(e)
25 | })
26 | })
27 |
28 | apiRoutes.get('/getCdInfo', function (req, res) {
29 | var url = 'https://c.y.qq.com/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg'
30 | axios.get(url, {
31 | headers: {
32 | referer: 'https://c.y.qq.com/',
33 | host: 'c.y.qq.com'
34 | },
35 | params: req.query
36 | }).then((response) => {
37 | var ret = response.data
38 | if (typeof ret === 'string') {
39 | var reg = /^\w+\(({.+})\)$/
40 | var matches = ret.match(reg)
41 | if (matches) {
42 | ret = JSON.parse(matches[1])
43 | }
44 | }
45 | res.json(ret)
46 | }).catch((e) => {
47 | console.log(e)
48 | })
49 | })
50 |
51 | apiRoutes.get('/lyric', function (req, res) {
52 | var url = 'https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg'
53 |
54 | axios.get(url, {
55 | headers: {
56 | referer: 'https://c.y.qq.com/',
57 | host: 'c.y.qq.com'
58 | },
59 | params: req.query
60 | }).then((response) => {
61 | var ret = response.data
62 | if (typeof ret === 'string') {
63 | var reg = /^\w+\(({.+})\)$/
64 | var matches = response.data.match(reg)
65 | if (matches) {
66 | ret = JSON.parse(matches[1])
67 | }
68 | }
69 | res.json(ret)
70 | }).catch((e) => {
71 | console.log(e)
72 | })
73 | })
74 |
75 | app.post('/api/getPurlUrl', bodyParser.json(), function (req, res) {
76 | const url = 'https://u.y.qq.com/cgi-bin/musicu.fcg'
77 | axios.post(url, req.body, {
78 | headers: {
79 | referer: 'https://y.qq.com/',
80 | origin: 'https://y.qq.com',
81 | 'Content-type': 'application/x-www-form-urlencoded'
82 | }
83 | }).then((response) => {
84 | res.json(response.data)
85 | }).catch((e) => {
86 | console.log(e)
87 | })
88 | })
89 |
90 | apiRoutes.get('/search', function (req, res) {
91 | const url = 'https://c.y.qq.com/soso/fcgi-bin/search_for_qq_cp'
92 | axios.get(url, {
93 | headers: {
94 | referer: 'https://c.y.qq.com/',
95 | host: 'c.y.qq.com'
96 | },
97 | params: req.query
98 | }).then((response) => {
99 | res.json(response.data)
100 | }).catch((e) => {
101 | console.log(e)
102 | })
103 | })
104 |
105 | apiRoutes.get('/getTopBanner', function (req, res) {
106 | const url = 'https://u.y.qq.com/cgi-bin/musicu.fcg'
107 | const jumpPrefix = 'https://y.qq.com/n/yqq/album/'
108 |
109 | axios.get(url, {
110 | headers: {
111 | referer: 'https://u.y.qq.com/',
112 | host: 'u.y.qq.com'
113 | },
114 | params: req.query
115 | }).then((response) => {
116 | response = response.data
117 | if (response.code === 0) {
118 | const slider = []
119 | const content = response.focus.data && response.focus.data.content
120 | if (content) {
121 | for (let i = 0; i < content.length; i++) {
122 | const item = content[i]
123 | const sliderItem = {}
124 | sliderItem.id = item.id
125 | sliderItem.linkUrl = jumpPrefix + item.jump_info.url + '.html'
126 | sliderItem.picUrl = item.pic_info.url
127 | slider.push(sliderItem)
128 | }
129 | }
130 | res.json({
131 | code: 0,
132 | data: {
133 | slider
134 | }
135 | })
136 | } else {
137 | res.json(response)
138 | }
139 | }).catch((e) => {
140 | console.log(e)
141 | })
142 | })
143 |
144 | app.use('/api', apiRoutes)
145 |
146 | app.use(compression())
147 |
148 | app.use(express.static('./dist'))
149 |
150 | module.exports = app.listen(port, function (err) {
151 | if (err) {
152 | console.log(err)
153 | return
154 | }
155 | console.log('Listening at http://localhost:' + port + '\n')
156 | })
--------------------------------------------------------------------------------
/src/components/recommend/recommend.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
15 |
热门歌单推荐
16 |
17 |
18 |
19 |
20 |
21 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
100 |
150 |
151 |
--------------------------------------------------------------------------------
/src/components/search/search.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
热门搜索
11 |
12 |
13 | {{item.k}}
14 |
15 |
16 |
17 |
18 |
19 | 搜索历史
20 |
21 |
22 |
23 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
40 |
41 |
42 |
43 |
110 |
161 |
162 |
163 |
--------------------------------------------------------------------------------
/src/components/user-center/user-center.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | 随机播放全部
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
117 |
118 |
--------------------------------------------------------------------------------
/src/components/suggest/suggest.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
163 |
164 |
195 |
--------------------------------------------------------------------------------
/src/components/add-song/add-song.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 | 1首歌曲已经添加到播放列表
40 |
41 |
42 |
43 |
44 |
45 |
46 |
121 |
122 |
--------------------------------------------------------------------------------
/src/components/music-list/music-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 随机播放全部
12 |
13 |
14 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
142 |
230 |
--------------------------------------------------------------------------------
/src/base/listview/listview.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 | {{group.title}}
11 |
12 |
13 |
14 | {{item.name}}
15 |
16 |
17 |
18 |
19 |
27 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
164 |
165 |
231 |
232 |
--------------------------------------------------------------------------------
/src/components/playlist/playlist.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
15 |
16 | {{item.name}}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 添加歌曲到队列
30 |
31 |
32 |
33 | 关闭
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
130 |
131 |
--------------------------------------------------------------------------------
/src/components/player/player.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
{{playingLyric}}
32 |
33 |
34 |
35 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
{{format(currentTime)}}
53 |
56 |
{{format(currentSong.duration)}}
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
89 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
107 |
108 |
109 |
454 |
702 |
--------------------------------------------------------------------------------
/src/common/fonts/music-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Generated by IcoMoon
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------