├── src
├── api
│ ├── index.js
│ ├── home
│ │ ├── index.js
│ │ └── home.api.js
│ ├── player
│ │ ├── index.js
│ │ └── player.api.js
│ ├── config.js
│ ├── Api.js
│ └── http.js
├── common
│ ├── stylus
│ │ ├── index.styl
│ │ ├── base.styl
│ │ ├── variable.styl
│ │ ├── mixin.styl
│ │ ├── icon.styl
│ │ └── reset.styl
│ ├── image
│ │ └── default.png
│ └── js
│ │ ├── config.js
│ │ ├── singer.js
│ │ ├── uid.js
│ │ ├── jsonp.js
│ │ ├── dom.js
│ │ ├── util.js
│ │ ├── song.js
│ │ └── mixin.js
├── store
│ ├── config.js
│ ├── index.js
│ ├── state.js
│ ├── getters.js
│ ├── mutations.js
│ └── actions.js
├── views
│ ├── my.vue
│ ├── mv.vue
│ ├── Disc.vue
│ └── Home.vue
├── main.js
├── components-base
│ ├── scroll
│ │ ├── use-scroll.js
│ │ ├── scroll.vue
│ │ └── scrol.v2.vue
│ ├── slider
│ │ ├── use-slider.js
│ │ ├── slider.vue
│ │ └── slider.v2.vue
│ ├── loading
│ │ ├── directive.js
│ │ └── loading.vue
│ ├── mv
│ │ ├── mv.vue
│ │ └── mv-item.vue
│ ├── music-animation
│ │ └── music-animation.vue
│ └── progress-bar
│ │ └── progress-bar.vue
├── App.vue
├── router
│ └── index.js
└── components
│ ├── nav
│ └── nav.vue
│ ├── song-list
│ └── song-list.vue
│ ├── disc-list
│ └── disc-list.vue
│ └── m-header
│ └── m-header.vue
├── v2
├── .browserslistrc
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── img
│ │ └── icons
│ │ │ ├── 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
│ └── index.html
├── music.zip
├── src
│ ├── common
│ │ ├── stylus
│ │ │ ├── index.styl
│ │ │ ├── base.styl
│ │ │ ├── variable.styl
│ │ │ ├── mixin.styl
│ │ │ ├── icon.styl
│ │ │ └── reset.styl
│ │ ├── js
│ │ │ ├── config.js
│ │ │ ├── singer.js
│ │ │ ├── uid.js
│ │ │ ├── jsonp.js
│ │ │ ├── util.js
│ │ │ ├── dom.js
│ │ │ ├── song.js
│ │ │ └── mixin.js
│ │ └── image
│ │ │ └── default.png
│ ├── store
│ │ ├── config.js
│ │ ├── state.js
│ │ ├── index.js
│ │ ├── actions.js
│ │ ├── getters.js
│ │ └── mutations.js
│ ├── api
│ │ ├── disc.js
│ │ ├── song.js
│ │ ├── singer.js
│ │ ├── config.js
│ │ ├── mv.js
│ │ └── home.js
│ ├── views
│ │ ├── my.vue
│ │ ├── mv.vue
│ │ ├── Disc.vue
│ │ ├── singerHome.vue
│ │ ├── singer.vue
│ │ └── Home.vue
│ ├── App.vue
│ ├── main.js
│ ├── registerServiceWorker.js
│ ├── router
│ │ └── index.js
│ ├── components
│ │ ├── nav
│ │ │ └── nav.vue
│ │ ├── song-list
│ │ │ └── song-list.vue
│ │ ├── music-list
│ │ │ └── music-list.vue
│ │ ├── singer-music-list
│ │ │ └── singer-music-list.vue
│ │ ├── m-header
│ │ │ └── m-header.vue
│ │ └── player
│ │ │ └── player.vue
│ └── base
│ │ ├── mv
│ │ ├── mv.vue
│ │ └── mv-item.vue
│ │ ├── loading
│ │ └── loading.vue
│ │ ├── scroll
│ │ └── scroll.vue
│ │ ├── music-animation
│ │ └── music-animation.vue
│ │ ├── slider
│ │ └── slider.vue
│ │ ├── progress-bar
│ │ └── progress-bar.vue
│ │ └── listview
│ │ └── listview.vue
├── babel.config.js
├── .editorconfig
├── .gitignore
├── .eslintrc.js
├── vue.config.js
├── package.json
└── README.md
├── music.zip
├── public
├── favicon.ico
├── icons
│ ├── 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
└── index.html
├── babel.config.js
├── .gitignore
├── package.json
├── vue.config.js
└── README.md
/src/api/index.js:
--------------------------------------------------------------------------------
1 | export * from "./Api";
2 |
--------------------------------------------------------------------------------
/v2/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 |
--------------------------------------------------------------------------------
/src/api/home/index.js:
--------------------------------------------------------------------------------
1 | export * from './home.api';
2 |
--------------------------------------------------------------------------------
/v2/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/src/api/player/index.js:
--------------------------------------------------------------------------------
1 | export * from './player.api';
2 |
--------------------------------------------------------------------------------
/src/api/config.js:
--------------------------------------------------------------------------------
1 | export const HOST = 'http://47.93.242.149:3300'
2 |
--------------------------------------------------------------------------------
/music.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/music.zip
--------------------------------------------------------------------------------
/v2/music.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/v2/music.zip
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/common/stylus/index.styl:
--------------------------------------------------------------------------------
1 | @import "./reset.styl"
2 | @import "./base.styl"
3 | @import "./icon.styl"
--------------------------------------------------------------------------------
/v2/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/v2/public/favicon.ico
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/src/store/config.js:
--------------------------------------------------------------------------------
1 | export const playMode = {
2 | sequence: 0,
3 | loop: 1,
4 | random: 2
5 | }
6 |
--------------------------------------------------------------------------------
/v2/src/common/stylus/index.styl:
--------------------------------------------------------------------------------
1 | @import "./reset.styl"
2 | @import "./base.styl"
3 | @import "./icon.styl"
--------------------------------------------------------------------------------
/v2/src/store/config.js:
--------------------------------------------------------------------------------
1 | export const playMode = {
2 | sequence: 0,
3 | loop: 1,
4 | random: 2
5 | }
6 |
--------------------------------------------------------------------------------
/v2/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/src/common/image/default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/src/common/image/default.png
--------------------------------------------------------------------------------
/src/common/js/config.js:
--------------------------------------------------------------------------------
1 | export const playMode = {
2 | sequence: 0,
3 | loop: 1,
4 | random: 2
5 | }
6 |
--------------------------------------------------------------------------------
/v2/src/common/js/config.js:
--------------------------------------------------------------------------------
1 | export const playMode = {
2 | sequence: 0,
3 | loop: 1,
4 | random: 2
5 | }
6 |
--------------------------------------------------------------------------------
/v2/src/common/image/default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/v2/src/common/image/default.png
--------------------------------------------------------------------------------
/public/icons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/public/icons/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/icons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/public/icons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/icons/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/public/icons/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/icons/apple-touch-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/public/icons/apple-touch-icon-60x60.png
--------------------------------------------------------------------------------
/public/icons/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/public/icons/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/public/icons/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/public/icons/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/public/icons/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/public/icons/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/public/icons/apple-touch-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/public/icons/apple-touch-icon-180x180.png
--------------------------------------------------------------------------------
/v2/public/img/icons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/v2/public/img/icons/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/icons/msapplication-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/public/icons/msapplication-icon-144x144.png
--------------------------------------------------------------------------------
/v2/public/img/icons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/v2/public/img/icons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/v2/public/img/icons/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/v2/public/img/icons/android-chrome-512x512.png
--------------------------------------------------------------------------------
/v2/public/img/icons/apple-touch-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/v2/public/img/icons/apple-touch-icon-60x60.png
--------------------------------------------------------------------------------
/v2/public/img/icons/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/v2/public/img/icons/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/v2/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/v2/public/img/icons/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/v2/public/img/icons/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/v2/public/img/icons/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/v2/public/img/icons/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/v2/public/img/icons/apple-touch-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/v2/public/img/icons/apple-touch-icon-180x180.png
--------------------------------------------------------------------------------
/v2/public/img/icons/msapplication-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/0xA1b/Music-For-The-Poor/HEAD/v2/public/img/icons/msapplication-icon-144x144.png
--------------------------------------------------------------------------------
/v2/src/api/disc.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { BASE_URL } from './config'
3 | export function getSongList (id) {
4 | return axios({
5 | url: `${BASE_URL}/playlist?id=${id}`
6 | })
7 | }
8 |
--------------------------------------------------------------------------------
/v2/src/api/song.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { BASE_URL } from './config'
3 | export function getSongUrl (id, cid) {
4 | return axios({
5 | url: `${BASE_URL}/song/url?id=${id}&cid=${cid}&needPic=1`
6 | })
7 | }
8 |
--------------------------------------------------------------------------------
/v2/src/api/singer.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { BASE_URL } from './config'
3 | export function getSongList (name, page) {
4 | return axios({
5 | url: `${BASE_URL}/search?keyword=${name}&&pageNo=${page}`
6 | })
7 | }
8 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/v2/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/v2/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 |
--------------------------------------------------------------------------------
/src/api/Api.js:
--------------------------------------------------------------------------------
1 | import { HomeApi } from "./home";
2 | import { PlayerApi } from './player'
3 |
4 |
5 |
6 | const isSuccess = (res) => {
7 | return res && res.code === 1;
8 | };
9 |
10 | export const Api = {
11 | isSuccess: isSuccess,
12 | HomeApi: new HomeApi(),
13 | PlayerApi: new PlayerApi(),
14 | };
15 |
--------------------------------------------------------------------------------
/v2/.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 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'vuex'
2 | import state from './state'
3 | import mutations from './mutations'
4 | import * as getters from './getters'
5 | import * as actions from './actions'
6 |
7 |
8 | export default createStore({
9 | state,
10 | getters,
11 | mutations,
12 | actions,
13 | })
14 |
--------------------------------------------------------------------------------
/src/views/my.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 升级vue3中~(*^_^*)
4 |
5 |
6 |
9 |
10 |
17 |
--------------------------------------------------------------------------------
/v2/src/views/my.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 开发中ing~(*^_^*)
4 |
5 |
6 |
11 |
12 |
18 |
--------------------------------------------------------------------------------
/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: #fff
9 | color: $color-text
10 | touch-action: none
--------------------------------------------------------------------------------
/v2/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: #fff
9 | color: $color-text
10 | touch-action: none
--------------------------------------------------------------------------------
/src/store/state.js:
--------------------------------------------------------------------------------
1 | import { playMode } from '@/common/js/config'
2 | const state = {
3 | disc: {},
4 | playerIconColor: 'black',
5 | fullScreen: false,
6 | currentIndex: -1,
7 | playing: false,
8 | playlist: [],
9 | sequenceList: [],
10 | mode: playMode.sequence,
11 | searchBoxFocue: false
12 | }
13 | export default state
14 |
--------------------------------------------------------------------------------
/v2/src/store/state.js:
--------------------------------------------------------------------------------
1 | import { playMode } from '@/common/js/config'
2 | const state = {
3 | disc: {},
4 | playerIconColor: 'black',
5 | fullScreen: false,
6 | currentIndex: -1,
7 | playing: false,
8 | playlist: [],
9 | sequenceList: [],
10 | mode: playMode.sequence,
11 | searchBoxFocue: false
12 | }
13 | export default state
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | music/node_modules
4 | /dist
5 | /music
6 |
7 |
8 | # local env files
9 | .env.local
10 | .env.*.local
11 |
12 | # Log files
13 | npm-debug.log*
14 | yarn-debug.log*
15 | yarn-error.log*
16 | pnpm-debug.log*
17 |
18 | # Editor directories and files
19 | .idea
20 | .vscode
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 |
--------------------------------------------------------------------------------
/v2/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import state from './state'
4 | import * as actions from './actions'
5 | import * as getters from './getters'
6 | import mutations from './mutations'
7 | Vue.use(Vuex)
8 |
9 | export default new Vuex.Store({
10 | state: state,
11 | mutations: mutations,
12 | actions: actions,
13 | getters: getters
14 | })
15 |
--------------------------------------------------------------------------------
/v2/src/api/config.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | export const BASE_URL = 'http://175.24.131.201:3400'
4 |
5 | // 默认超时设置
6 | axios.defaults.timeout = 10000
7 |
8 | // http request 拦截器
9 | axios.interceptors.response.use(
10 | response => {
11 | // 一些统一code的返回处理
12 | return response
13 | },
14 | error => {
15 | return Promise.reject(error)
16 | }
17 | )
18 |
--------------------------------------------------------------------------------
/src/common/stylus/variable.styl:
--------------------------------------------------------------------------------
1 | // 颜色定义规范
2 | $color-background = #d43c33
3 | $color-highlight-background = #dd001b
4 | $color-theme = #333
5 |
6 | $color-sub-theme = #d93f30
7 | $color-text = #333
8 | $color-text-light = #444
9 |
10 |
11 | //字体定义规范
12 | $font-size-small-s = 10px
13 | $font-size-small = 12px
14 | $font-size-medium = 14px
15 | $font-size-medium-x = 16px
16 | $font-size-large = 18px
17 | $font-size-large-x = 22px
--------------------------------------------------------------------------------
/v2/src/common/stylus/variable.styl:
--------------------------------------------------------------------------------
1 | // 颜色定义规范
2 | $color-background = #d43c33
3 | $color-highlight-background = #dd001b
4 | $color-theme = #333
5 |
6 | $color-sub-theme = #d93f30
7 | $color-text = #333
8 | $color-text-light = #444
9 |
10 |
11 | //字体定义规范
12 | $font-size-small-s = 10px
13 | $font-size-small = 12px
14 | $font-size-medium = 14px
15 | $font-size-medium-x = 16px
16 | $font-size-large = 18px
17 | $font-size-large-x = 22px
--------------------------------------------------------------------------------
/v2/src/api/mv.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { BASE_URL } from './config'
3 | export function getMv (id) {
4 | return axios({
5 | url: `${BASE_URL}/mv`
6 | })
7 | }
8 | // 服务端逻辑 http://www.xbeibeix.com/api/bilibiliapi.php?url=https://www.bilibili.com/&aid=[视频AID]&cid=[视频CID]
9 | export function getVideoUrl (aid, cid) {
10 | return axios({
11 | url: `${BASE_URL}/video?aid=${aid}&&cid=${cid}`
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/v2/.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' ? 'error' : 'off',
12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
13 | 'vue/require-v-for-key': 'off'
14 | },
15 | parserOptions: {
16 | parser: 'babel-eslint'
17 | }
18 | }
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
--------------------------------------------------------------------------------
/v2/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
23 |
24 |
--------------------------------------------------------------------------------
/v2/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/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 | import store from './store'
5 | import lazyPlugin from 'vue3-lazy';
6 | import loadingDirective from '@/components-base/loading/directive'
7 |
8 | import '@/common/stylus/index.styl'
9 |
10 | const app = createApp(App)
11 |
12 | app.use(store)
13 | app.use(router)
14 | app.use(lazyPlugin, {
15 | loading: require('@/common/image/default.png'),
16 | error: ''
17 | })
18 | app.directive('loading', loadingDirective)
19 |
20 | app.mount('#app')
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/common/stylus/icon.styl:
--------------------------------------------------------------------------------
1 |
2 | // [class^="icon-"], [class*=" icon-"]
3 | // /* use !important to prevent issues with browser extensions that change fonts */
4 | // font-family: 'music-icon' !important
5 | // speak: none
6 | // font-style: normal
7 | // font-weight: normal
8 | // font-variant: normal
9 | // text-transform: none
10 | // line-height: 1
11 |
12 | // /* Better Font Rendering =========== */
13 | // -webkit-font-smoothing: antialiased
14 | // -moz-osx-font-smoothing: grayscale
15 |
16 |
17 | @import 'http://at.alicdn.com/t/font_1621469_al4hf3dkenk.css'
--------------------------------------------------------------------------------
/v2/src/common/stylus/icon.styl:
--------------------------------------------------------------------------------
1 |
2 | // [class^="icon-"], [class*=" icon-"]
3 | // /* use !important to prevent issues with browser extensions that change fonts */
4 | // font-family: 'music-icon' !important
5 | // speak: none
6 | // font-style: normal
7 | // font-weight: normal
8 | // font-variant: normal
9 | // text-transform: none
10 | // line-height: 1
11 |
12 | // /* Better Font Rendering =========== */
13 | // -webkit-font-smoothing: antialiased
14 | // -moz-osx-font-smoothing: grayscale
15 |
16 |
17 | @import 'http://at.alicdn.com/t/font_1621469_zfmqpho57t.css'
--------------------------------------------------------------------------------
/src/components-base/scroll/use-scroll.js:
--------------------------------------------------------------------------------
1 | import BScroll from '@better-scroll/core'
2 | import ObserveDOM from '@better-scroll/observe-dom'
3 | import {ref, onMounted, onUnmounted} from 'vue';
4 |
5 | BScroll.use(ObserveDOM) //使用响应式dom插件 检测内部变化自动refresh
6 |
7 | export default function useScroll(wrapperRef,options){
8 | const scroll = ref(null)
9 | onMounted(()=>{
10 | scroll.value = new BScroll(wrapperRef.value,{
11 | observeDOM: true,
12 | ...options
13 | })
14 | })
15 | onUnmounted(() => {
16 | scroll.value.destroy()
17 | })
18 | }
19 |
--------------------------------------------------------------------------------
/src/api/player/player.api.js:
--------------------------------------------------------------------------------
1 | import { Http } from '../http';
2 |
3 |
4 | export class PlayerApi extends Http {
5 |
6 | async getSongPlayUrl(songmids) {
7 | const res = await this.get(`/song/urls?id=${songmids}`)
8 | return res
9 | }
10 |
11 | async getMvList() {
12 | const res = await this.get(`/mv/list`)
13 | return res
14 | }
15 |
16 | async getMv(mvid) {
17 | const res = await this.get(`/mv/url?id=${mvid}`)
18 | return res
19 | }
20 |
21 | async getComments(id) {
22 | const res = await this.get(`/comment?id=${id}&biztype=5`)
23 | return res
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/common/js/jsonp.js:
--------------------------------------------------------------------------------
1 | import originJsonp from 'jsonp'
2 |
3 | export default function jsonp2 (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 |
--------------------------------------------------------------------------------
/v2/src/common/js/jsonp.js:
--------------------------------------------------------------------------------
1 | import originJsonp from 'jsonp'
2 |
3 | export default function jsonp2 (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 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
10 | FreeMusic😊
11 |
12 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/v2/src/api/home.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { BASE_URL } from './config'
3 | export function getNewSong () {
4 | return axios({
5 | url: `${BASE_URL}/new/songs`
6 | })
7 | }
8 | export function getDiscList () {
9 | return axios({
10 | url: `${BASE_URL}/recommend/playlist`
11 | })
12 | }
13 | export function getHotWords () {
14 | return axios({
15 | url: `${BASE_URL}/new/hotwords`
16 | })
17 | }
18 | /*
19 | 参数:
20 | keyword: 搜索关键词 必填
21 | type: 默认 song,支持:song, playlist, mv, singer, album, lyric
22 | pageno: 默认 1
23 | */
24 | export function getHotWordsSearch (keyword) {
25 | return axios({
26 | url: `${BASE_URL}/search?keyword=${keyword}`
27 | })
28 | }
29 |
--------------------------------------------------------------------------------
/v2/src/store/actions.js:
--------------------------------------------------------------------------------
1 | import { playMode } from '@/common/js/config'
2 | import { shuffle } from '@/common/js/util'
3 |
4 | export const selectPlay = function ({ commit, state }, { list, index }) {
5 | commit('SET_SEQUENCE_LIST', list)
6 | if (state.mode === playMode.random) {
7 | let randomList = shuffle(list)
8 | commit('SET_PLAYLIST', randomList)
9 | index = findIndex(randomList, list[index])
10 | } else {
11 | commit('SET_PLAYLIST', list)
12 | }
13 | commit('SET_CURRENT_INDEX', index)
14 | commit('SET_FULLSCREEN', true)
15 | commit('SET_PLAYING_STATE', true)
16 | }
17 |
18 | function findIndex (list, song) {
19 | return list.findIndex((item) => {
20 | return item.id === song.id
21 | })
22 | }
23 |
--------------------------------------------------------------------------------
/v2/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 | Music
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
23 |
33 |
--------------------------------------------------------------------------------
/v2/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 | import store from './store'
5 | // import fastclick from 'fastclick'
6 | import VueLazyload from 'vue-lazyload'
7 |
8 | import './registerServiceWorker'
9 |
10 | import '@/common/stylus/index.styl'
11 |
12 | // fastclick.attach(document.body)
13 |
14 | Vue.config.productionTip = false
15 |
16 | Vue.use(VueLazyload, {
17 | loading: require('@/common/image/default.png'),
18 | error: 'https://pcache.cmam.migu.cn/prod/cmam_music/storage_1/albummaterial/11004/000002/0127/7283/1100000000255111663/cffb35b03da540a78df6fc5dd50fd0d8_1372844695.jpg'
19 | })
20 |
21 | new Vue({
22 | router,
23 | store,
24 | render: h => h(App)
25 | }).$mount('#app')
26 |
--------------------------------------------------------------------------------
/src/api/home/home.api.js:
--------------------------------------------------------------------------------
1 | import { Http } from '../http';
2 |
3 |
4 | export class HomeApi extends Http {
5 |
6 | // 按分类推荐歌单
7 | async getRecommendList() {
8 | const res = await this.get('/recommend/playlist')
9 | return res
10 | }
11 |
12 | // 根据分类获取歌单
13 | async getNewSong() {
14 | const res = await this.get('/songlist/list?sort=2')
15 | return res
16 | }
17 |
18 | // 获取歌单详情
19 | async getSongList(id) {
20 | const res = await this.get(`/songlist?id=${id}`)
21 | return res
22 | }
23 |
24 |
25 | async getHotWordsSearch(key) {
26 | const res = await this.get(`/search?key=${key}`)
27 | return res
28 | }
29 |
30 | async getHotWords() {
31 | const res = await this.get('/search/hot')
32 | return res
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/store/getters.js:
--------------------------------------------------------------------------------
1 | export const disc = (state) => {
2 | return state.disc
3 | }
4 |
5 | export const playerIconColor = state => {
6 | return state.playerIconColor
7 | }
8 |
9 | export const fullScreen = state => {
10 | return state.fullScreen
11 | }
12 |
13 | export const currentIndex = state => state.currentIndex
14 |
15 | export const currentSong = (state) => {
16 | return state.playlist[state.currentIndex] || {}
17 | // 通过计算Index获取当前currentSong 修改index响应 类似计算属性
18 | // 如果没有url和封面信息 在player组件里请求后获取数据后 替换修改currentSong
19 | }
20 | export const playing = state => state.playing
21 |
22 | export const playlist = (state) => state.playlist
23 |
24 | export const mode = state => state.mode
25 |
26 | export const searchBoxFocue = state => state.searchBoxFocue
27 |
--------------------------------------------------------------------------------
/v2/src/store/getters.js:
--------------------------------------------------------------------------------
1 | export const disc = (state) => {
2 | return state.disc
3 | }
4 |
5 | export const playerIconColor = state => {
6 | return state.playerIconColor
7 | }
8 |
9 | export const fullScreen = state => {
10 | return state.fullScreen
11 | }
12 |
13 | export const currentIndex = state => state.currentIndex
14 |
15 | export const currentSong = (state) => {
16 | return state.playlist[state.currentIndex] || {}
17 | // 通过计算Index获取当前currentSong 修改index响应 类似计算属性
18 | // 如果没有url和封面信息 在player组件里请求后获取数据后 替换修改currentSong
19 | }
20 | export const playing = state => state.playing
21 |
22 | export const playlist = (state) => state.playlist
23 |
24 | export const mode = state => state.mode
25 |
26 | export const searchBoxFocue = state => state.searchBoxFocue
27 |
--------------------------------------------------------------------------------
/src/views/mv.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
29 |
30 |
39 |
--------------------------------------------------------------------------------
/v2/src/store/mutations.js:
--------------------------------------------------------------------------------
1 | const mutations = {
2 | SET_DISC (state, disc) {
3 | state.disc = disc
4 | },
5 | SET_PLAY_ICON_COLOR (state, color) {
6 | state.playerIconColor = color
7 | },
8 | SET_FULLSCREEN (state, fullState) {
9 | state.fullScreen = fullState
10 | },
11 | SET_CURRENT_INDEX (state, index) {
12 | state.currentIndex = index
13 | },
14 | SET_SEQUENCE_LIST (state, list) {
15 | state.sequenceList = list
16 | },
17 | SET_PLAY_MODE (state, mode) {
18 | state.mode = mode
19 | },
20 | SET_PLAYLIST (state, list) {
21 | state.playlist = list
22 | },
23 | SET_PLAYING_STATE (state, flag) {
24 | state.playing = flag
25 | },
26 | SET_SEARCHBOX_FOCUS (state, flag) {
27 | state.searchBoxFocue = flag
28 | }
29 | }
30 | export default mutations
31 |
--------------------------------------------------------------------------------
/v2/src/common/js/util.js:
--------------------------------------------------------------------------------
1 | function getRandomInt (min, max) {
2 | return Math.floor(Math.random() * (max - min + 1) + min)
3 | }
4 |
5 | export function shuffle (arr) {
6 | let _arr = arr.slice()
7 | for (let i = 0; i < _arr.length; i++) {
8 | let j = getRandomInt(0, i)
9 | let t = _arr[i]
10 | _arr[i] = _arr[j]
11 | _arr[j] = t
12 | }
13 | return _arr
14 | }
15 |
16 | export function debounce (func, delay) {
17 | let timer
18 | return function (...args) {
19 | if (timer) {
20 | clearTimeout(timer)
21 | }
22 | timer = setTimeout(() => {
23 | func.apply(this, args)
24 | }, delay)
25 | }
26 | }
27 | export function isWeiXin () {
28 | var ua = window.navigator.userAgent.toLowerCase()
29 | if (ua.match(/micromessenger/i) == 'micromessenger') { // eslint-disable-line
30 | return true
31 | } else {
32 | return false
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/v2/src/views/mv.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
30 |
31 |
41 |
--------------------------------------------------------------------------------
/src/components-base/slider/use-slider.js:
--------------------------------------------------------------------------------
1 | import BScroll from '@better-scroll/core'
2 | import Slide from '@better-scroll/slide'
3 | import {onMounted,ref,onUnmounted} from 'vue';
4 |
5 | BScroll.use(Slide)
6 |
7 | export default function useSlider(wrapperRef){
8 | const slider = ref(null)
9 | const currentPageIndex = ref(0)
10 |
11 | onMounted(() => {
12 | slider.value = new BScroll(wrapperRef.value,{
13 | scrollX: true,
14 | scrollY: false,
15 | slide: true,
16 | momentum: false,
17 | bounce: false,
18 | probeType: 3
19 | })
20 |
21 | slider.value.on('slideWillChange', (page) => {
22 | currentPageIndex.value = page.pageX
23 | })
24 | })
25 |
26 | onUnmounted(() => {
27 | slider.value.destroy()
28 | })
29 |
30 | return {
31 | slider,
32 | currentPageIndex
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "music",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build"
8 | },
9 | "dependencies": {
10 | "@better-scroll/core": "^2.4.2",
11 | "@better-scroll/observe-dom": "^2.4.2",
12 | "@better-scroll/slide": "^2.4.2",
13 | "axios": "^0.21.4",
14 | "core-js": "^3.6.5",
15 | "jsonp": "^0.2.1",
16 | "qs": "^6.10.1",
17 | "vue": "^3.0.0",
18 | "vue-router": "^4.0.0-0",
19 | "vue3-lazy": "^1.0.0-alpha.1",
20 | "vuex": "^4.0.0-0"
21 | },
22 | "devDependencies": {
23 | "@vue/cli-plugin-babel": "~4.5.0",
24 | "@vue/cli-plugin-router": "~4.5.0",
25 | "@vue/cli-plugin-vuex": "~4.5.0",
26 | "@vue/cli-service": "~4.5.0",
27 | "@vue/compiler-sfc": "^3.0.0",
28 | "style-resources-loader": "^1.4.1",
29 | "stylus": "^0.54.7",
30 | "stylus-loader": "^3.0.2"
31 | }
32 | }
--------------------------------------------------------------------------------
/src/components-base/scroll/scroll.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
48 |
49 |
52 |
--------------------------------------------------------------------------------
/src/store/mutations.js:
--------------------------------------------------------------------------------
1 | const mutations = {
2 | SET_DISC(state, disc) {
3 | state.disc = disc
4 | },
5 | SET_PLAY_ICON_COLOR(state, color) {
6 | state.playerIconColor = color
7 | },
8 | SET_FULLSCREEN(state, fullState) {
9 | state.fullScreen = fullState
10 | },
11 | SET_CURRENT_INDEX(state, index) {
12 | state.currentIndex = index
13 | },
14 | SAVE_CURRENT_SONG(state, item) {
15 | state.currentSong = item
16 | },
17 | SET_SEQUENCE_LIST(state, list) {
18 | state.sequenceList = list
19 | },
20 | SET_PLAY_MODE(state, mode) {
21 | state.mode = mode
22 | },
23 | SET_PLAYLIST(state, list) {
24 | state.playlist = list
25 | },
26 | SET_PLAYLIST_PUSH(state, item) {
27 | state.playlist.push(item)
28 | },
29 | SET_PLAYING_STATE(state, flag) {
30 | state.playing = flag
31 | },
32 | SET_SEARCHBOX_FOCUS(state, flag) {
33 | state.searchBoxFocue = flag
34 | },
35 | DELETE_SONG(state, index) {
36 |
37 | }
38 | }
39 | export default mutations
40 |
--------------------------------------------------------------------------------
/v2/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 | registered () {
14 | console.log('Service worker has been registered.')
15 | },
16 | cached () {
17 | console.log('Content has been cached for offline use.')
18 | },
19 | updatefound () {
20 | console.log('New content is downloading.')
21 | },
22 | updated () {
23 | console.log('New content is available; please refresh.')
24 | },
25 | offline () {
26 | console.log('No internet connection found. App is running in offline mode.')
27 | },
28 | error (error) {
29 | console.error('Error during service worker registration:', error)
30 | }
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/src/components-base/loading/directive.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import Loading from './loading'
3 |
4 | const loadingDirective = {
5 | mounted(el,binding) {
6 | const app = createApp()
7 | const instance = app.mount(document.createElement('div'))
8 |
9 | binding.value.title && instance.setTitle(binding.value.title)
10 | binding.value.color && instance.setColor(binding.value.color)
11 |
12 | el.instance = instance // 存一个
13 |
14 | if(binding.value.loading){
15 | append(el)
16 | }
17 | },
18 | updated(el,binding) {
19 | binding.value.title && el.instance.setTitle(binding.value.title)
20 | binding.value.color && el.instance.setColor(binding.value.color)
21 |
22 | if(binding.value.loading !== binding.oldValue.loading){
23 | binding.value.loading? append(el) : remove(el)
24 | }
25 | },
26 | }
27 |
28 | function append(el){
29 | el.appendChild(el.instance.$el)
30 | }
31 |
32 | function remove(el){
33 | el.removeChild(el.instance.$el)
34 | }
35 |
36 | export default loadingDirective
37 |
--------------------------------------------------------------------------------
/v2/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 | import Home from '../views/Home.vue'
4 | import Disc from '@/views/Disc.vue'
5 | import Mv from '@/views/mv.vue'
6 | import My from '@/views/my.vue'
7 | import Singer from '@/views/singer.vue'
8 | import singerHome from '@/views/singerHome.vue'
9 | Vue.use(VueRouter)
10 |
11 | const routes = [
12 | {
13 | path: '/',
14 | redirect: '/home'
15 | },
16 | {
17 | path: '/home',
18 | name: 'home',
19 | component: Home,
20 | children: [
21 | {
22 | path: ':id',
23 | component: Disc
24 | }
25 | ]
26 | },
27 | {
28 | path: '/mv',
29 | name: 'mv',
30 | component: Mv
31 | },
32 | {
33 | path: '/my',
34 | name: 'my',
35 | component: My
36 | },
37 | {
38 | path: '/singer',
39 | component: Singer,
40 | children: [
41 | {
42 | path: '/singer/singerHome',
43 | name: 'singerHome',
44 | component: singerHome
45 | }
46 | ]
47 | }
48 | ]
49 |
50 | const router = new VueRouter({
51 | routes
52 | })
53 |
54 | export default router
55 |
--------------------------------------------------------------------------------
/v2/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | publicPath: '/music/',
3 | outputDir: 'music',
4 | assetsDir: './static',
5 | // ...other vue-cli plugin options...
6 | pwa: {
7 | name: 'Music',
8 | themeColor: '#4DBA87',
9 | msTileColor: '#ffffff',
10 | appleMobileWebAppCapable: 'yes',
11 | appleMobileWebAppStatusBarStyle: 'white',
12 | iconPaths: {
13 | appleTouchIcon: 'public/img/icons/apple-touch-icon-152x152.png',
14 | maskIcon: 'public/img/icons/safari-pinned-tab.svg',
15 | msTileImage: 'public/img/icons/msapplication-icon-144x144.png'
16 | },
17 |
18 | // configure the workbox plugin
19 | workboxPluginMode: 'InjectManifest',
20 | workboxOptions: {
21 | // swSrc is required in InjectManifest mode.
22 | swSrc: 'src/registerServiceWorker.js'
23 | // ...other Workbox options...
24 | }
25 | },
26 | devServer: {
27 | open: process.platform === 'darwin',
28 | // host: '192.168.199.163',
29 | port: 8082,
30 | https: false,
31 | hotOnly: false,
32 | // proxy: {}, // 设置代理
33 | before: app => {}
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/v2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "music-for-the-poor",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "@babel/core": "^7.13.15",
12 | "@babel/preset-env": "^7.13.15",
13 | "axios": "^0.19.1",
14 | "better-scroll": "^1.15.2",
15 | "core-js": "^3.4.4",
16 | "fastclick": "^1.0.6",
17 | "jsonp": "^0.2.1",
18 | "register-service-worker": "^1.6.2",
19 | "vue": "^2.6.10",
20 | "vue-router": "^3.1.3",
21 | "vue-toasted": "^1.1.27",
22 | "vuex": "^3.1.2"
23 | },
24 | "devDependencies": {
25 | "@vue/cli-plugin-babel": "^4.1.0",
26 | "@vue/cli-plugin-eslint": "^4.1.0",
27 | "@vue/cli-plugin-pwa": "^4.1.0",
28 | "@vue/cli-service": "^4.1.0",
29 | "@vue/eslint-config-standard": "^4.0.0",
30 | "babel-eslint": "^10.0.3",
31 | "eslint": "^5.16.0",
32 | "eslint-plugin-vue": "^5.0.0",
33 | "stylus": "^0.54.7",
34 | "stylus-loader": "^3.0.2",
35 | "vue-lazyload": "^1.3.3",
36 | "vue-template-compiler": "^2.6.10"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
2 | import Home from '../views/Home.vue'
3 | import Disc from '../views/Disc.vue'
4 | import Mv from '../views/mv'
5 | import My from '../views/my'
6 | const routes = [
7 | {
8 | path: '/',
9 | redirect: '/home'
10 | },
11 | {
12 | path: '/home',
13 | name: 'Home',
14 | component: Home,
15 | children: [
16 | {
17 | path: '/home/disc/:id',
18 | name: 'disc',
19 | component: Disc
20 | }
21 | ]
22 | },
23 | {
24 | path: '/mv',
25 | name: 'mv',
26 | component: Mv
27 | },
28 | {
29 | path: '/my',
30 | name: 'my',
31 | component: My
32 | },
33 | // {
34 | // path: '/singer',
35 | // component: Singer,
36 | // children: [
37 | // // {
38 | // // path: '/singer/singerHome',
39 | // // name: 'singerHome',
40 | // // component: () => import('../views/singerHome.vue')
41 | // // }
42 | // ]
43 | // }
44 | ]
45 |
46 | const router = createRouter({
47 | history: createWebHistory(process.env.BASE_URL),
48 | routes
49 | })
50 |
51 | export default router
52 |
--------------------------------------------------------------------------------
/src/common/js/dom.js:
--------------------------------------------------------------------------------
1 | export function hasClass (el, className) {
2 | let reg = new RegExp('(^|\\s)' + className + '(\\s|$)')
3 | return reg.test(el.className)
4 | }
5 |
6 | export function addClass (el, className) {
7 | if (hasClass(el, className)) {
8 | return
9 | }
10 |
11 | let newClass = el.className.split(' ')
12 | newClass.push(className)
13 | el.className = newClass.join(' ')
14 | }
15 | /* 如果有val 则为设置属性值 */
16 | export function getData (el, name, val) {
17 | const prefix = 'data-'
18 | name = prefix + name
19 | if (val) {
20 | return el.setAttribute(name, val)
21 | } else {
22 | return el.getAttribute(name)
23 | }
24 | }
25 |
26 | let elementStyle = document.createElement('div').style
27 |
28 | let vendor = (() => {
29 | let transformNames = {
30 | webkit: 'webkitTransform',
31 | Moz: 'MozTransform',
32 | O: 'OTransform',
33 | ms: 'msTransform',
34 | standard: 'transform'
35 | }
36 |
37 | for (let key in transformNames) {
38 | if (elementStyle[transformNames[key]] !== undefined) {
39 | return key
40 | }
41 | }
42 |
43 | return false
44 | })()
45 |
46 | export function prefixStyle (style) {
47 | if (vendor === false) {
48 | return false
49 | }
50 |
51 | if (vendor === 'standard') {
52 | return style
53 | }
54 |
55 | return vendor + style.charAt(0).toUpperCase() + style.substr(1)
56 | }
57 |
--------------------------------------------------------------------------------
/v2/src/common/js/dom.js:
--------------------------------------------------------------------------------
1 | export function hasClass (el, className) {
2 | let reg = new RegExp('(^|\\s)' + className + '(\\s|$)')
3 | return reg.test(el.className)
4 | }
5 |
6 | export function addClass (el, className) {
7 | if (hasClass(el, className)) {
8 | return
9 | }
10 |
11 | let newClass = el.className.split(' ')
12 | newClass.push(className)
13 | el.className = newClass.join(' ')
14 | }
15 | /* 如果有val 则为设置属性值 */
16 | export function getData (el, name, val) {
17 | const prefix = 'data-'
18 | name = prefix + name
19 | if (val) {
20 | return el.setAttribute(name, val)
21 | } else {
22 | return el.getAttribute(name)
23 | }
24 | }
25 |
26 | let elementStyle = document.createElement('div').style
27 |
28 | let vendor = (() => {
29 | let transformNames = {
30 | webkit: 'webkitTransform',
31 | Moz: 'MozTransform',
32 | O: 'OTransform',
33 | ms: 'msTransform',
34 | standard: 'transform'
35 | }
36 |
37 | for (let key in transformNames) {
38 | if (elementStyle[transformNames[key]] !== undefined) {
39 | return key
40 | }
41 | }
42 |
43 | return false
44 | })()
45 |
46 | export function prefixStyle (style) {
47 | if (vendor === false) {
48 | return false
49 | }
50 |
51 | if (vendor === 'standard') {
52 | return style
53 | }
54 |
55 | return vendor + style.charAt(0).toUpperCase() + style.substr(1)
56 | }
57 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/v2/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 |
--------------------------------------------------------------------------------
/v2/src/components/nav/nav.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 首页
7 |
8 |
9 |
10 |
11 |
12 | 歌手
13 |
14 |
15 |
16 |
17 |
18 | MV
19 |
20 |
21 |
22 |
23 |
24 | 我的
25 |
26 |
27 |
28 |
29 |
30 |
35 |
61 |
--------------------------------------------------------------------------------
/v2/src/views/Disc.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
60 |
61 |
68 |
--------------------------------------------------------------------------------
/v2/src/components/song-list/song-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
39 |
59 |
--------------------------------------------------------------------------------
/src/store/actions.js:
--------------------------------------------------------------------------------
1 | import { playMode } from '@/common/js/config'
2 | import { shuffle } from '@/common/js/util'
3 |
4 | // export const selectPlay = function ({ commit, state }, { list, index }) {
5 | // commit('SET_SEQUENCE_LIST', list)
6 | // if (state.mode === playMode.random) {
7 | // let randomList = shuffle(list)
8 | // commit('SET_PLAYLIST', randomList)
9 | // index = findIndex(randomList, list[index])
10 | // } else {
11 | // commit('SET_PLAYLIST', list)
12 | // }
13 | // commit('SET_CURRENT_INDEX', index)
14 | // commit('SET_FULLSCREEN', true)
15 | // commit('SET_PLAYING_STATE', true)
16 | // }
17 |
18 | export const selectPlay = async function ({ commit, dispatch, state }, { item }) {
19 |
20 | let index = findIndex(state.playlist, item)
21 | if (index == -1) {
22 | // 不存在
23 | let list = JSON.parse(JSON.stringify(state.playlist))
24 | list.push(item)
25 | commit('SET_SEQUENCE_LIST', list)
26 | commit('SET_PLAYLIST', list)
27 | commit('SET_CURRENT_INDEX', list.length - 1)
28 | } else {
29 | commit('SET_CURRENT_INDEX', index)
30 | }
31 | commit('SET_PLAYING_STATE', true)
32 | commit('SET_FULLSCREEN', true)
33 |
34 | // commit('SET_SEQUENCE_LIST', list)
35 | // if (state.mode === playMode.random) {
36 | // let randomList = shuffle(list)
37 | // commit('SET_PLAYLIST', randomList)
38 | // index = findIndex(randomList, list[index])
39 | // } else {
40 | // commit('SET_PLAYLIST', list)
41 | // }
42 | // commit('SET_CURRENT_INDEX', index)
43 | // commit('SET_FULLSCREEN', true)
44 | // commit('SET_PLAYING_STATE', true)
45 | }
46 |
47 | function findIndex(list, song) {
48 | return list.findIndex((item) => {
49 | return item.songmid === song.songmid
50 | })
51 | }
52 |
--------------------------------------------------------------------------------
/src/views/Disc.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
58 |
59 |
68 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | publicPath: '/music/',
5 | outputDir: 'music',
6 | assetsDir: './static',
7 | // ...other vue-cli plugin options...
8 | pwa: {
9 | name: 'FreeMusic',
10 | themeColor: '#4DBA87',
11 | msTileColor: '#ffffff',
12 | appleMobileWebAppCapable: 'yes',
13 | appleMobileWebAppStatusBarStyle: 'white',
14 | iconPaths: {
15 | appleTouchIcon: 'public/img/icons/apple-touch-icon-152x152.png',
16 | maskIcon: 'public/img/icons/safari-pinned-tab.svg',
17 | msTileImage: 'public/img/icons/msapplication-icon-144x144.png'
18 | },
19 |
20 | // configure the workbox plugin
21 | workboxPluginMode: 'InjectManifest',
22 | workboxOptions: {
23 | // swSrc is required in InjectManifest mode.
24 | swSrc: 'src/registerServiceWorker.js'
25 | // ...other Workbox options...
26 | }
27 | },
28 | devServer: {
29 | open: process.platform === 'darwin',
30 | // host: '192.168.199.163',
31 | port: 8082,
32 | https: false,
33 | hotOnly: false,
34 | // proxy: {}, // 设置代理
35 | before: app => { }
36 | },
37 | // 配置使用stylus全局变量
38 | chainWebpack: config => {
39 | const types = ["vue-modules", "vue", "normal-modules", "normal"];
40 | types.forEach(type =>
41 | addStyleResource(config.module.rule("stylus").oneOf(type))
42 | );
43 | }
44 | }
45 |
46 |
47 | // 定义函数addStyleResource
48 |
49 | function addStyleResource(rule) {
50 | rule.use("style-resource")
51 | .loader("style-resources-loader")
52 | .options({
53 | patterns: [
54 | path.resolve(__dirname, "./src/common/stylus/variable.styl"),
55 | path.resolve(__dirname, "./src/common/stylus/mixin.styl"),
56 | ]
57 | //后面的路径改成你自己放公共stylus变量的路径
58 | });
59 | }
60 |
--------------------------------------------------------------------------------
/v2/src/base/mv/mv.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
15 |
16 |
17 |
50 |
75 |
--------------------------------------------------------------------------------
/src/common/js/util.js:
--------------------------------------------------------------------------------
1 | function getRandomInt(min, max) {
2 | return Math.floor(Math.random() * (max - min + 1) + min)
3 | }
4 |
5 | export function shuffle(arr) {
6 | let _arr = arr.slice()
7 | for (let i = 0; i < _arr.length; i++) {
8 | let j = getRandomInt(0, i)
9 | let t = _arr[i]
10 | _arr[i] = _arr[j]
11 | _arr[j] = t
12 | }
13 | return _arr
14 | }
15 |
16 | export function debounce(func, delay) {
17 | let timer
18 | return function (...args) {
19 | if (timer) {
20 | clearTimeout(timer)
21 | }
22 | timer = setTimeout(() => {
23 | func.apply(this, args)
24 | }, delay)
25 | }
26 | }
27 | export function isWeiXin() {
28 | var ua = window.navigator.userAgent.toLowerCase()
29 | if (ua.match(/micromessenger/i) == 'micromessenger') { // eslint-disable-line
30 | return true
31 | } else {
32 | return false
33 | }
34 | }
35 |
36 | export const isIphoneX = () => {
37 | if (/iphone/gi.test(window.navigator.userAgent)) {
38 | let x = (window.screen.width === 375 && window.screen.height === 812);
39 | let xsMax = (window.screen.width === 414 && window.screen.height === 896);
40 | let xR = (window.screen.width === 414 && window.screen.height === 896);
41 | if (x || xsMax || xR) {
42 | return true;
43 | } else {
44 | return false;
45 | }
46 | } else {
47 | return false
48 | }
49 | }
50 |
51 | export function IsPC() {
52 | var userAgentInfo = navigator.userAgent;
53 | var Agents = new Array("Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod");
54 | var flag = true;
55 | for (var v = 0; v < Agents.length; v++) {
56 | if (userAgentInfo.indexOf(Agents[v]) > 0) {
57 | flag = false;
58 | break;
59 | }
60 | }
61 | return flag;
62 | }
--------------------------------------------------------------------------------
/src/api/http.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import qs from 'qs';
3 | import { HOST } from './config';
4 |
5 |
6 | axios.interceptors.response.use(
7 | response => {
8 | if (response.data) {
9 | return response;
10 | }
11 | return Promise.reject(response);
12 | },
13 | error => {
14 | return Promise.reject(error.response);
15 | },
16 | );
17 |
18 | export class Http {
19 |
20 | async request(config) {
21 | let response;
22 |
23 | try {
24 | response = await axios(
25 | {
26 | ...config,
27 | url: HOST + config.url,
28 | }
29 | );
30 | return response.data;
31 | } catch (error) {
32 | console.log(error)
33 | }
34 | }
35 |
36 | async get(url, params) {
37 | const config = {
38 | method: 'GET',
39 | url,
40 | params,
41 | };
42 | return this.request(config);
43 | }
44 |
45 | async post(url, params) {
46 | const config = {
47 | method: 'POST',
48 | url,
49 | data: params,
50 | };
51 | return this.request(config);
52 | }
53 |
54 | formPost(url, params) {
55 | const config = {
56 | method: 'POST',
57 | url,
58 | data: qs.stringify(params),
59 | };
60 | return this.request(config);
61 | }
62 |
63 | patch(url, params) {
64 | const config = {
65 | method: 'PATCH',
66 | url,
67 | data: params,
68 | };
69 | return this.request(config);
70 | }
71 |
72 |
73 | put(url, params) {
74 | const config = {
75 | method: 'PUT',
76 | url,
77 | data: params,
78 | };
79 | return this.request(config);
80 | }
81 |
82 | delete(url, params) {
83 | const config = {
84 | method: 'DELETE',
85 | url,
86 | data: params,
87 | };
88 | return this.request(config);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/components-base/mv/mv.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
15 |
16 |
17 |
49 |
80 |
--------------------------------------------------------------------------------
/src/components/nav/nav.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 发现
7 |
8 |
9 |
15 |
16 |
17 |
18 | MV
19 |
20 |
21 |
22 |
23 |
24 | 电台
25 |
26 |
27 |
28 |
29 |
30 |
40 |
78 |
--------------------------------------------------------------------------------
/src/components/song-list/song-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
48 |
78 |
--------------------------------------------------------------------------------
/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, cid, singer, name, image, url }) {
7 | this.id = id
8 | this.cid = cid
9 | this.singer = singer
10 | this.name = name
11 | this.image = image
12 | this.url = url
13 | }
14 |
15 | // getLyric () {
16 | // if (this.lyric) {
17 | // return Promise.resolve(this.lyric)
18 | // }
19 |
20 | // return new Promise((resolve, reject) => {
21 | // getLyric(this.mid).then((res) => {
22 | // if (res.retcode === ERR_OK) {
23 | // this.lyric = Base64.decode(res.lyric)
24 | // resolve(this.lyric)
25 | // } else {
26 | // reject(new Error('no lyric'))
27 | // }
28 | // })
29 | // })
30 | // }
31 | }
32 |
33 | export function createSong (id, cid, singer, name, image, url) {
34 | return new Song({
35 | id: id,
36 | cid: cid,
37 | singer: singer,
38 | name: name,
39 | image: image,
40 | url: url
41 | })
42 | }
43 | export function filterSinger (singer) {
44 | let ret = []
45 | if (!singer) {
46 | return ''
47 | }
48 | singer.forEach((s) => {
49 | ret.push(s.name)
50 | })
51 | return ret.join('/')
52 | }
53 |
54 | // export function isValidMusic (musicData) {
55 | // return musicData.songid && musicData.albummid && (!musicData.pay || musicData.pay.payalbumprice === 0)
56 | // }
57 |
58 | // export function processSongsUrl (songs) {
59 | // if (!songs.length) {
60 | // return Promise.resolve(songs)
61 | // }
62 | // return getSongsUrl(songs).then((purlMap) => {
63 | // songs = songs.filter((song) => {
64 | // const purl = purlMap[song.mid]
65 | // if (purl) {
66 | // song.url = purl.indexOf('http') === -1 ? `http://dl.stream.qqmusic.qq.com/${purl}` : purl
67 | // return true
68 | // }
69 | // return false
70 | // })
71 | // return songs
72 | // })
73 | // }
74 |
--------------------------------------------------------------------------------
/v2/README.md:
--------------------------------------------------------------------------------
1 | # Music For The Poor
2 |
3 | ## 更新:
4 |
5 | --2020.09.17:Vue3已经进入RFC了,估计没几个月就要正式发布了,目前本项目正在使用vue3升级重构中~ 提前感受一波Vue3
6 |
7 | ## 简介:
8 | 打造精美音乐WebAppp,提供优雅的用户体验,且能听付费歌曲(比如周杰伦等),为祖国2020全面脱贫实现小康社会尽一份自己力量,打赢这场脱贫攻坚战。精准扶贫,让穷人也能听到好的音乐,让穷人省下一笔钱来脱贫,愿天下没有穷人!
9 |
10 | [在线体验地址](http://www.iamzfj.cn/music)
11 |
12 | 扫描二维码:
13 |
14 |
15 | 当然,以上是开玩笑的,主要还是造轮子玩。
16 |
17 | ## Build Setup
18 |
19 | ``` bash
20 | npm i //安装依赖
21 | npm run dev //开发
22 | npm run build //打包
23 | ```
24 | ### 项目介绍(为了减少gif大小,掉帧严重,优雅的交互体现不出来==)
25 |
26 | ### 一、首页
27 |
28 | 首页主要由顶部搜索和推荐歌单与推荐单曲构成,图片部分应用了懒加载。
29 |
30 | 
31 |
32 | ### 二、 音乐播放器
33 |
34 | 音乐播放器有两种形态,一种mini的小图标,在没有播放或暂停时为静止的icon,当有播放音乐时,为一个有节奏跳动的音律动效,点击后进去播放界面,在缓冲时,音频资源未ready时 ,进度条为一个小小的转圈的loadng,准备就绪,可以拖动进度条对音乐前进或后退,下一首上一首操作播放列表。
35 |
36 | 下一步接着要做的便是歌词同步~
37 |
38 | 
39 |
40 | ### 三、搜索
41 |
42 | 首页搜索功能
43 |
44 | 
45 |
46 | ### 四、歌手页面
47 |
48 | 歌手页面,左右可以滑动,当点击歌手时,前往歌手主页,歌手主页下有歌曲列表,上拉加载更多功能,
49 |
50 | 
51 |
52 | ### 一、Mv
53 |
54 | 此处解析了哔哩哔哩(bilibili)音乐区的推荐(演奏类),都是非常的棒的视频,同时费劲周折解析了B站视频的高清视频原地址(1080p),很棒的噢~
55 |
56 | 此处后期可以考虑扩展封装一个播放器组件,目前只是一个简单的播放基础功能。也是一个很有趣的事情哦~~ 欢迎大家来一起参与造轮子~
57 |
58 | 
59 |
60 | ### 五、我的
61 |
62 | 此处待开发...
63 |
64 | 
65 |
66 |
67 | ### 未来规划和展望
68 | 目前武汉新型肺炎爆发,适逢春节,国家号召宅在家里,口罩难求,怕是怕死懒得出门,一直在家里做该项目,进度飞快,这个项目的核心已经完成,但是还是有很多扩展的余地。关于未来的规划,我是这么安排的:
69 |
70 | - 音乐player播放器 增加歌词等扩展功能
71 | - 视频播放器
72 | - 完成收藏、播放历史功能
73 | - 实现MV模块 评论页更优雅的交互
74 | - 同时撰写拆解文章
75 | - “我的”待开发
76 | - 未来更多功能待补充...
77 |
78 | 这个项目长期维护,希望大家踊跃提issue和pr,把这个项目打造得更加完美,帮助到更多的Vue开发者。
79 |
80 | 最后的最后,万水千山总是情,给个star行不行(你回头也好找这个项目呀 (*^_^*))
81 |
82 | 
83 |
84 | # 声明
85 | 本项目代码仅用学习交流, 请勿商业使用。
86 |
--------------------------------------------------------------------------------
/v2/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, cid, singer, name, image, url }) {
7 | this.id = id
8 | this.cid = cid
9 | this.singer = singer
10 | this.name = name
11 | this.image = image
12 | this.url = url
13 | }
14 |
15 | // getLyric () {
16 | // if (this.lyric) {
17 | // return Promise.resolve(this.lyric)
18 | // }
19 |
20 | // return new Promise((resolve, reject) => {
21 | // getLyric(this.mid).then((res) => {
22 | // if (res.retcode === ERR_OK) {
23 | // this.lyric = Base64.decode(res.lyric)
24 | // resolve(this.lyric)
25 | // } else {
26 | // reject(new Error('no lyric'))
27 | // }
28 | // })
29 | // })
30 | // }
31 | }
32 |
33 | export function createSong (id, cid, singer, name, image, url) {
34 | return new Song({
35 | id: id,
36 | cid: cid,
37 | singer: singer,
38 | name: name,
39 | image: image,
40 | url: url
41 | })
42 | }
43 | export function filterSinger (singer) {
44 | let ret = []
45 | if (!singer) {
46 | return ''
47 | }
48 | singer.forEach((s) => {
49 | ret.push(s.name)
50 | })
51 | return ret.join('/')
52 | }
53 |
54 | // export function isValidMusic (musicData) {
55 | // return musicData.songid && musicData.albummid && (!musicData.pay || musicData.pay.payalbumprice === 0)
56 | // }
57 |
58 | // export function processSongsUrl (songs) {
59 | // if (!songs.length) {
60 | // return Promise.resolve(songs)
61 | // }
62 | // return getSongsUrl(songs).then((purlMap) => {
63 | // songs = songs.filter((song) => {
64 | // const purl = purlMap[song.mid]
65 | // if (purl) {
66 | // song.url = purl.indexOf('http') === -1 ? `http://dl.stream.qqmusic.qq.com/${purl}` : purl
67 | // return true
68 | // }
69 | // return false
70 | // })
71 | // return songs
72 | // })
73 | // }
74 |
--------------------------------------------------------------------------------
/v2/src/base/loading/loading.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
33 |
93 |
--------------------------------------------------------------------------------
/v2/src/views/singerHome.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
74 |
75 |
82 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Music For The Poor
2 |
3 | ## 2021.09.24 更新
4 | - 1.已经从vue2.x更新vue3.x,并基于vue3的composition Api 抽离了可复用逻辑,做完这个项目,既有了react hooks的灵活也有了vue的直观,Interesting~
5 | - 2.切换了音乐Api,修改了解析接口并加入了我自己的登录信息(尊贵的vip会员😄)以获取付费资源;
6 | - 3.一小部分部音乐获取到的播放歌曲依然有问题,tm的QQ音乐的规则老换,没那么多时间去研究,顶不住了😭... 等他规则变动不那么频繁了,回来看看;
7 | - 4.移除了没啥卵用的模块,添加了下载音乐资源的功能,改进了mv模版的交换体验,体验更棒啦;
8 |
9 | ## 2020.09.17更新
10 | - Vue3已经进入RFC了,估计没几个月就要正式发布了,目前本项目正在使用vue3升级重构中~,已重构部分 提前感受一波Vue3
11 |
12 | ## 简介:
13 | 打造精美音乐WebAppp,提供优雅的用户体验,且能听付费歌曲(比如周杰伦等),为祖国2020全面脱贫实现小康社会尽一份自己力量,打赢这场脱贫攻坚战。精准扶贫,让穷人也能听到好的音乐,让穷人省下一笔钱来脱贫,愿天下没有穷人!
14 |
15 | [在线体验地址](http://www.iamzfj.cn/music)
16 |
17 | 扫描二维码:
18 |
19 |
20 | 当然,以上是开玩笑的,主要还是造轮子玩。
21 |
22 | ## Build Setup
23 |
24 | ``` bash
25 | npm i //安装依赖
26 | npm run serve //开发
27 | npm run build //打包, 记得修改你的publicPath
28 | ```
29 | ### 项目介绍(为了减少gif大小,掉帧严重,优雅的交互体现不出来==)
30 |
31 | ### 一、首页
32 |
33 | 首页主要由顶部搜索和推荐歌单与推荐单曲构成,图片部分应用了懒加载。
34 |
35 | 
36 |
37 | ### 二、 音乐播放器
38 |
39 | 音乐播放器有两种形态,一种mini的小图标,在没有播放或暂停时为静止的icon,当有播放音乐时,为一个有节奏跳动的音律动效,点击后进去播放界面,在缓冲时,音频资源未ready时 ,进度条为一个小小的转圈的loadng,准备就绪,可以拖动进度条对音乐前进或后退,下一首上一首操作播放列表。
40 |
41 | 下一步接着要做的便是歌词同步~
42 |
43 | 
44 |
45 | ### 三、搜索
46 |
47 | 首页搜索功能
48 |
49 | 
50 |
51 | ### 四、歌手页面
52 |
53 | 歌手页面,左右可以滑动,当点击歌手时,前往歌手主页,歌手主页下有歌曲列表,上拉加载更多功能,
54 |
55 | 
56 |
57 | ### 一、Mv
58 |
59 | 此处解析了哔哩哔哩(bilibili)音乐区的推荐(演奏类),都是非常的棒的视频,同时费劲周折解析了B站视频的高清视频原地址(1080p),很棒的噢~
60 |
61 | 此处后期可以考虑扩展封装一个播放器组件,目前只是一个简单的播放基础功能。也是一个很有趣的事情哦~~ 欢迎大家来一起参与造轮子~
62 |
63 | 
64 |
65 | ### 五、我的
66 |
67 | 此处待开发...
68 |
69 | 
70 |
71 |
72 | ### 未来规划和展望
73 | 目前武汉新型肺炎爆发,适逢春节,国家号召宅在家里,口罩难求,怕是怕死懒得出门,一直在家里做该项目,进度飞快,这个项目的核心已经完成,但是还是有很多扩展的余地。关于未来的规划,我是这么安排的:
74 |
75 | - 音乐player播放器 增加歌词等扩展功能
76 | - 视频播放器
77 | - 完成收藏、播放历史功能
78 | - 实现MV模块 评论页更优雅的交互
79 | - 同时撰写拆解文章
80 | - “我的”待开发
81 | - 未来更多功能待补充...
82 |
83 | 这个项目长期维护,希望大家踊跃提issue和pr,把这个项目打造得更加完美,帮助到更多的Vue开发者。
84 |
85 | 最后的最后,万水千山总是情,给个star行不行(你回头也好找这个项目呀 (*^_^*))
86 |
87 | 
88 |
89 | # 声明
90 | 本项目代码仅用学习交流, 请勿商业使用。
91 |
--------------------------------------------------------------------------------
/v2/src/base/scroll/scroll.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
100 |
101 |
104 |
--------------------------------------------------------------------------------
/src/components-base/scroll/scrol.v2.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
100 |
101 |
104 |
--------------------------------------------------------------------------------
/v2/src/base/music-animation/music-animation.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
37 |
98 |
--------------------------------------------------------------------------------
/src/components-base/music-animation/music-animation.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
37 |
98 |
--------------------------------------------------------------------------------
/src/components-base/loading/loading.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
{{title}}
10 |
11 |
12 |
46 |
111 |
--------------------------------------------------------------------------------
/src/components-base/slider/slider.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
![]()
8 |
9 |
10 |
11 |
12 |
17 |
18 |
19 |
20 |
21 |
22 |
44 |
45 |
115 |
--------------------------------------------------------------------------------
/v2/src/views/singer.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
97 |
98 |
105 |
--------------------------------------------------------------------------------
/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 |
5 | export const playlistMixin = {
6 | computed: {
7 | ...mapGetters([
8 | 'playList'
9 | ])
10 | },
11 | mounted () {
12 | this.handlePlaylist(this.playList)
13 | },
14 | activated () {
15 | this.handlePlaylist(this.playList)
16 | },
17 | watch: {
18 | playList (newVal) {
19 | this.handlePlaylist(newVal)
20 | }
21 | },
22 | methods: {
23 | handlePlaylist () {
24 | throw new Error('component must implement handlePlaylist method')
25 | }
26 | }
27 | }
28 |
29 | export const playerMixin = {
30 | computed: {
31 | iconMode () {
32 | return this.mode === playMode.sequence ? 'icon-sequence' : this.mode === playMode.loop ? 'icon-loop' : 'icon-random'
33 | },
34 | ...mapGetters([
35 | 'sequenceList',
36 | 'playlist',
37 | 'currentSong',
38 | 'mode',
39 | 'favoriteList'
40 | ]),
41 | favoriteIcon () {
42 | return this.getFavoriteIcon(this.currentSong)
43 | }
44 | },
45 | methods: {
46 | changeMode () {
47 | const mode = (this.mode + 1) % 3
48 | this.setPlayMode(mode)
49 | let list = null
50 | if (mode === playMode.random) {
51 | list = shuffle(this.sequenceList)
52 | } else {
53 | list = this.sequenceList
54 | }
55 | this.resetCurrentIndex(list)
56 | this.setPlaylist(list)
57 | },
58 | resetCurrentIndex (list) {
59 | let index = list.findIndex((item) => {
60 | return item.id === this.currentSong.id
61 | })
62 | this.setCurrentIndex(index)
63 | },
64 | toggleFavorite (song) {
65 | if (this.isFavorite(song)) {
66 | this.deleteFavoriteList(song)
67 | } else {
68 | this.saveFavoriteList(song)
69 | }
70 | },
71 | getFavoriteIcon (song) {
72 | if (this.isFavorite(song)) {
73 | return 'icon-favorite'
74 | }
75 | return 'icon-not-favorite'
76 | },
77 | isFavorite (song) {
78 | const index = this.favoriteList.findIndex((item) => {
79 | return item.id === song.id
80 | })
81 | return index > -1
82 | },
83 | ...mapMutations({
84 | setPlayMode: 'SET_PLAY_MODE',
85 | setPlaylist: 'SET_PLAYLIST',
86 | setCurrentIndex: 'SET_CURRENT_INDEX',
87 | setPlayingState: 'SET_PLAYING_STATE'
88 | }),
89 | ...mapActions([
90 | 'saveFavoriteList',
91 | 'deleteFavoriteList'
92 | ])
93 | }
94 | }
95 |
96 | export const searchMixin = {
97 | data () {
98 | return {
99 | query: '',
100 | refreshDelay: 120
101 | }
102 | },
103 | computed: {
104 | ...mapGetters([
105 | 'searchHistory'
106 | ])
107 | },
108 | methods: {
109 | onQueryChange (query) {
110 | // 处理带空格的情况
111 | this.query = query.trim()
112 | },
113 | blurInput () {
114 | this.$refs.searchBox.blur()
115 | },
116 | addQuery (query) {
117 | this.$refs.searchBox.setQuery(query)
118 | },
119 | saveSearch () {
120 | this.saveSearchHistory(this.query)
121 | },
122 | ...mapActions([
123 | 'saveSearchHistory',
124 | 'deleteSearchHistory'
125 | ])
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/v2/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 |
5 | export const playlistMixin = {
6 | computed: {
7 | ...mapGetters([
8 | 'playList'
9 | ])
10 | },
11 | mounted () {
12 | this.handlePlaylist(this.playList)
13 | },
14 | activated () {
15 | this.handlePlaylist(this.playList)
16 | },
17 | watch: {
18 | playList (newVal) {
19 | this.handlePlaylist(newVal)
20 | }
21 | },
22 | methods: {
23 | handlePlaylist () {
24 | throw new Error('component must implement handlePlaylist method')
25 | }
26 | }
27 | }
28 |
29 | export const playerMixin = {
30 | computed: {
31 | iconMode () {
32 | return this.mode === playMode.sequence ? 'icon-sequence' : this.mode === playMode.loop ? 'icon-loop' : 'icon-random'
33 | },
34 | ...mapGetters([
35 | 'sequenceList',
36 | 'playlist',
37 | 'currentSong',
38 | 'mode',
39 | 'favoriteList'
40 | ]),
41 | favoriteIcon () {
42 | return this.getFavoriteIcon(this.currentSong)
43 | }
44 | },
45 | methods: {
46 | changeMode () {
47 | const mode = (this.mode + 1) % 3
48 | this.setPlayMode(mode)
49 | let list = null
50 | if (mode === playMode.random) {
51 | list = shuffle(this.sequenceList)
52 | } else {
53 | list = this.sequenceList
54 | }
55 | this.resetCurrentIndex(list)
56 | this.setPlaylist(list)
57 | },
58 | resetCurrentIndex (list) {
59 | let index = list.findIndex((item) => {
60 | return item.id === this.currentSong.id
61 | })
62 | this.setCurrentIndex(index)
63 | },
64 | toggleFavorite (song) {
65 | if (this.isFavorite(song)) {
66 | this.deleteFavoriteList(song)
67 | } else {
68 | this.saveFavoriteList(song)
69 | }
70 | },
71 | getFavoriteIcon (song) {
72 | if (this.isFavorite(song)) {
73 | return 'icon-favorite'
74 | }
75 | return 'icon-not-favorite'
76 | },
77 | isFavorite (song) {
78 | const index = this.favoriteList.findIndex((item) => {
79 | return item.id === song.id
80 | })
81 | return index > -1
82 | },
83 | ...mapMutations({
84 | setPlayMode: 'SET_PLAY_MODE',
85 | setPlaylist: 'SET_PLAYLIST',
86 | setCurrentIndex: 'SET_CURRENT_INDEX',
87 | setPlayingState: 'SET_PLAYING_STATE'
88 | }),
89 | ...mapActions([
90 | 'saveFavoriteList',
91 | 'deleteFavoriteList'
92 | ])
93 | }
94 | }
95 |
96 | export const searchMixin = {
97 | data () {
98 | return {
99 | query: '',
100 | refreshDelay: 120
101 | }
102 | },
103 | computed: {
104 | ...mapGetters([
105 | 'searchHistory'
106 | ])
107 | },
108 | methods: {
109 | onQueryChange (query) {
110 | // 处理带空格的情况
111 | this.query = query.trim()
112 | },
113 | blurInput () {
114 | this.$refs.searchBox.blur()
115 | },
116 | addQuery (query) {
117 | this.$refs.searchBox.setQuery(query)
118 | },
119 | saveSearch () {
120 | this.saveSearchHistory(this.query)
121 | },
122 | ...mapActions([
123 | 'saveSearchHistory',
124 | 'deleteSearchHistory'
125 | ])
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/components-base/slider/slider.v2.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
![]()
8 |
9 |
10 |
11 |
12 |
17 |
18 |
19 |
20 |
21 |
22 |
79 |
80 |
130 |
--------------------------------------------------------------------------------
/v2/src/base/slider/slider.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
111 |
112 |
154 |
--------------------------------------------------------------------------------
/v2/src/base/progress-bar/progress-bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 |
95 |
96 |
184 |
--------------------------------------------------------------------------------
/v2/src/base/mv/mv-item.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
20 |
21 |
22 | {{data.title}}
23 |
24 |
25 |
26 |
27 |
![]()
28 |
29 |
30 | {{data.owner.name}}
31 |
32 |
33 |
34 |
35 |
36 | {{data.stat.like}}
37 |
38 |
39 |
40 | {{data.stat.reply}}
41 |
42 |
43 |
44 |
45 |
46 |
122 |
185 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 最新
12 |
13 |
14 | -
15 |
16 | {{item.dissname}}
17 |
18 |
19 |
20 |
21 |
22 |
23 | 推荐歌单
24 |
25 |
26 | -
27 |
28 | {{item.title}}
29 |
30 |
31 |
32 |
33 | 开发仅作为学习,非商业营利性目的。
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
124 |
204 |
--------------------------------------------------------------------------------
/src/components-base/progress-bar/progress-bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 |
100 |
101 |
203 |
--------------------------------------------------------------------------------
/v2/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |

10 |
11 |
12 |

13 |
14 |
15 |

16 |
17 |
18 |
19 |
20 |
21 | 推荐歌单
22 |
23 |
24 | -
25 |
26 | {{item.name}}
27 | {{item.intro.name}}
28 |
29 |
30 |
31 |
32 |
33 | 新歌首发
34 |
35 |
36 | -
37 |
38 | {{item.musicName}}
39 | {{item.singerName}}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
134 |
186 |
--------------------------------------------------------------------------------
/v2/src/components/music-list/music-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
![]()
13 |
14 |
15 |
{{discInfo.name}}
16 |
{{discInfo.creator.name}} · 创建
17 |
播放数 · {{discInfo.playCount}}
18 |
19 |
20 |
21 |
22 |
23 |
24 |
26 |
27 |
播放全部
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
119 |
215 |
--------------------------------------------------------------------------------
/src/components/disc-list/disc-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
![]()
13 |
14 |
15 |
{{discInfo.dissname}}
16 |
{{discInfo.nickname}} · 创建
17 |
播放数 · {{discInfo.visitnum}}
18 |
19 |
20 |
21 |
22 |
23 |
24 |
26 |
27 |
播放全部
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
118 |
248 |
--------------------------------------------------------------------------------
/v2/src/base/listview/listview.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 | -
10 |
{{group.title}}
11 |
12 | -
13 |
14 | {{item.name}}
15 |
16 |
17 |
18 |
19 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
178 |
179 |
248 |
--------------------------------------------------------------------------------
/v2/src/components/singer-music-list/singer-music-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
<
5 |
{{info.name}}
6 |
7 |
24 |
34 |
35 |
播放全部
36 |
37 |
38 |
39 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
151 |
248 |
--------------------------------------------------------------------------------
/src/components-base/mv/mv-item.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
24 |
25 |
26 | {{data.title}}
27 |
28 |
29 |
30 |
31 |
![]()
32 |
33 |
34 | {{data.title}}
35 |
36 |
37 |
38 |
39 |
40 | {{data.star_cnt}}
41 |
42 |
43 |
44 | {{data.comment_cnt}}
45 |
46 |
47 |
48 |
49 |
50 |
64 |
65 |
66 |
67 |
168 |
328 |
--------------------------------------------------------------------------------
/src/components/m-header/m-header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 |
18 |
热搜排行
19 |
20 | -
21 | {{index + 1}}
22 |
23 | {{item.k}}
24 |
25 |
26 |
27 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
111 |
313 |
--------------------------------------------------------------------------------
/v2/src/components/m-header/m-header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 |
18 |
热搜排行
19 |
20 | -
21 | {{index + 1}}
22 |
23 | {{item}}
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
113 |
268 |
--------------------------------------------------------------------------------
/v2/src/components/player/player.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
{{currentSong.name}}
11 |
12 |
13 |
14 |
![]()
15 |
16 |
17 |
18 |
![]()
19 |
20 |
21 |
22 |
23 |
{{format(currentTime)}}
24 |
28 |
{{format(duration)}}
29 |
30 |
31 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
59 |
60 |
61 |
62 |
266 |
394 |
--------------------------------------------------------------------------------