├── static
├── .gitkeep
├── fonts
│ ├── MaterialIcons-Regular.woff2
│ ├── MaterialIcons-Regular.ttf
│ └── MaterialIcons-Regular.woff
├── img
│ ├── default.jpg
│ ├── loading.png
│ ├── icons
│ │ ├── favicon.ico
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.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
│ └── default_avater.png
├── manifest.json
├── js
│ └── amfe-flexible.min.js
└── css
│ └── reset.css
├── .eslintignore
├── .fecsignore
├── images
├── web.jpg
└── androidAPK.png
├── src
├── assets
│ ├── logo.png
│ ├── styles
│ │ ├── global.styl
│ │ ├── variables.styl
│ │ └── material-icons.css
│ ├── sass
│ │ ├── remAdaptive.scss
│ │ └── variables.scss
│ └── svg
│ │ └── sentiment-very-satisfied.svg
├── common
│ ├── img
│ │ ├── cd_gold.png
│ │ ├── login-bg.jpg
│ │ ├── cd_ornament.png
│ │ ├── gexingdauntai.png
│ │ └── category-header-defalut.png
│ └── js
│ │ ├── config.js
│ │ ├── jsonp.js
│ │ ├── songSingle.js
│ │ ├── cookie.js
│ │ ├── newSongSpeed.js
│ │ ├── dom.js
│ │ ├── singer.js
│ │ ├── util.js
│ │ ├── totalDigitalAlbum.js
│ │ ├── home.js
│ │ └── cache.js
├── event-bus.js
├── entry-skeleton.js
├── pages
│ ├── mv
│ │ ├── entry-skeleton.js
│ │ ├── entry.js
│ │ ├── router.js
│ │ ├── index.html
│ │ └── Mv.vue
│ ├── home
│ │ ├── entry.js
│ │ ├── entry-skeleton.js
│ │ ├── index.html
│ │ └── router.js
│ ├── songsingle
│ │ ├── entry.js
│ │ ├── entry-skeleton.js
│ │ ├── router.js
│ │ ├── index.html
│ │ └── Songsingle.vue
│ ├── categoryzone
│ │ ├── entry-skeleton.js
│ │ ├── entry.js
│ │ ├── router.js
│ │ └── index.html
│ └── NotFound.vue
├── store
│ ├── modules
│ │ └── app-store.js
│ ├── index.js
│ ├── getters.js
│ ├── state.js
│ ├── mutation-types.js
│ └── mutations.js
├── api
│ ├── rank.js
│ ├── search.js
│ ├── singer.js
│ ├── login.js
│ ├── config.js
│ ├── featuredRadio.js
│ ├── categorySongList.js
│ ├── songAlbumMessage.js
│ ├── homeAjax.js
│ ├── mvList.js
│ ├── songListPlayUrl.js
│ └── newSongSpeed.js
├── base
│ ├── loading
│ │ └── loading.vue
│ ├── search-list
│ │ └── search-list.vue
│ ├── search-box
│ │ └── search-box.vue
│ ├── scroll
│ │ └── scroll.vue
│ ├── video
│ │ └── video.vue
│ ├── songListPlayAll
│ │ └── songListPlayAll.vue
│ ├── header-scroll
│ │ └── header-scroll.vue
│ ├── confirm
│ │ └── confirm.vue
│ ├── progress-bar
│ │ └── progress-bar.vue
│ └── login-input
│ │ └── login-input.vue
├── sw-register.js
├── components
│ ├── tab-router
│ │ └── tab-router.vue
│ ├── ProgressBar.vue
│ ├── recent-play
│ │ └── recent-play.vue
│ ├── user-favorite-list
│ │ └── user-favorite-list.vue
│ └── new-song-speed
│ │ └── new-song-speed.vue
├── app.js
├── router.js
└── App.vue
├── config
├── prod.env.js
├── dev.env.js
├── icon.js
├── theme.js
├── index.js
└── sw-precache.js
├── .gitignore
├── .idea
├── encodings.xml
├── misc.xml
├── vcs.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── modules.xml
├── musicAPP.iml
└── codeStyleSettings.xml
├── .editorconfig
├── .postcssrc.js
├── server
├── db.js
├── sqlMap.js
├── index.js
└── api
│ └── userApi.js
├── .fecsrc
├── .babelrc
├── .eslintrc.js
├── README.md
├── LICENSE
└── package.json
/static/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/fonts/MaterialIcons-Regular.woff2:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /config/
3 | /dist/
4 | /*.js
5 |
--------------------------------------------------------------------------------
/.fecsignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | static
4 | config/sw.tmpl.js
5 | *.index.html
--------------------------------------------------------------------------------
/images/web.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/images/web.jpg
--------------------------------------------------------------------------------
/images/androidAPK.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/images/androidAPK.png
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/static/img/default.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/static/img/default.jpg
--------------------------------------------------------------------------------
/static/img/loading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/static/img/loading.png
--------------------------------------------------------------------------------
/src/assets/styles/global.styl:
--------------------------------------------------------------------------------
1 | @import './material-icons.css';
2 | @import '~vuetify/src/stylus/main';
3 |
--------------------------------------------------------------------------------
/src/common/img/cd_gold.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/src/common/img/cd_gold.png
--------------------------------------------------------------------------------
/src/common/img/login-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/src/common/img/login-bg.jpg
--------------------------------------------------------------------------------
/static/img/icons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/static/img/icons/favicon.ico
--------------------------------------------------------------------------------
/src/common/img/cd_ornament.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/src/common/img/cd_ornament.png
--------------------------------------------------------------------------------
/static/img/default_avater.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/static/img/default_avater.png
--------------------------------------------------------------------------------
/src/common/img/gexingdauntai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/src/common/img/gexingdauntai.png
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file 生产环境相关配置文件
3 | */
4 |
5 | module.exports = {
6 | NODE_ENV: '"production"'
7 | };
8 |
--------------------------------------------------------------------------------
/static/img/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/static/img/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/static/img/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/static/img/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/src/common/js/config.js:
--------------------------------------------------------------------------------
1 | // 设置播放模式
2 | export const isPlayMode = {
3 | sequence: 0,
4 | loop: 1,
5 | random: 2
6 | };
7 |
--------------------------------------------------------------------------------
/src/event-bus.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file 事件总线
3 | */
4 |
5 | import Vue from 'vue';
6 |
7 | // 全局事件总线
8 | export default new Vue();
9 |
--------------------------------------------------------------------------------
/static/fonts/MaterialIcons-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/static/fonts/MaterialIcons-Regular.ttf
--------------------------------------------------------------------------------
/static/fonts/MaterialIcons-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/static/fonts/MaterialIcons-Regular.woff
--------------------------------------------------------------------------------
/src/common/img/category-header-defalut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/src/common/img/category-header-defalut.png
--------------------------------------------------------------------------------
/static/img/icons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/static/img/icons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/static/img/icons/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/static/img/icons/android-chrome-512x512.png
--------------------------------------------------------------------------------
/static/img/icons/apple-touch-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/static/img/icons/apple-touch-icon-60x60.png
--------------------------------------------------------------------------------
/static/img/icons/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/static/img/icons/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/.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 | patch.diff
9 |
--------------------------------------------------------------------------------
/static/img/icons/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/static/img/icons/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/static/img/icons/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/static/img/icons/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/static/img/icons/apple-touch-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qq282126990/musicApp/HEAD/static/img/icons/apple-touch-icon-180x180.png
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 4
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file 开发环境相关配置文件
3 | */
4 |
5 | var merge = require('webpack-merge');
6 | var prodEnv = require('./prod.env');
7 |
8 | module.exports = merge(prodEnv, {
9 | NODE_ENV: '"development"'
10 | });
11 |
--------------------------------------------------------------------------------
/src/entry-skeleton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file 页面骨架
3 | */
4 |
5 | import Vue from 'vue';
6 | import Skeleton from '@/pages/Skeleton';
7 |
8 | export default new Vue({
9 | components: {
10 | Skeleton
11 | },
12 | template: ''
13 | });
14 |
--------------------------------------------------------------------------------
/src/pages/mv/entry-skeleton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file 页面骨架
3 | */
4 |
5 | import Vue from 'vue';
6 | import Mv from './Mv.skeleton.vue';
7 |
8 | export default new Vue({
9 | components: {
10 | Mv
11 | },
12 | template: ''
13 | });
14 |
--------------------------------------------------------------------------------
/src/pages/home/entry.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file client entry
3 | */
4 |
5 | import 'babel-polyfill';
6 | import {createApp} from '@/app';
7 | import pageRouter from './router';
8 |
9 | const {app, router} = createApp(pageRouter);
10 |
11 | router.onReady(() => app.$mount('#app'));
12 |
--------------------------------------------------------------------------------
/src/pages/mv/entry.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file client entry
3 | */
4 |
5 | import 'babel-polyfill';
6 | import {createApp} from '@/app';
7 | import pageRouter from './router';
8 |
9 | const {app, router} = createApp(pageRouter);
10 |
11 | router.onReady(() => app.$mount('#app'));
12 |
--------------------------------------------------------------------------------
/src/pages/songsingle/entry.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file client entry
3 | */
4 |
5 | import 'babel-polyfill';
6 | import {createApp} from '@/app';
7 | import pageRouter from './router';
8 |
9 | const {app, router} = createApp(pageRouter);
10 |
11 | router.onReady(() => app.$mount('#app'));
12 |
--------------------------------------------------------------------------------
/src/pages/home/entry-skeleton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file 页面骨架
3 | */
4 |
5 | import Vue from 'vue';
6 | import HomeSkeleton from './Home.skeleton.vue';
7 |
8 | export default new Vue({
9 | components: {
10 | HomeSkeleton
11 | },
12 | template: ''
13 | });
14 |
--------------------------------------------------------------------------------
/src/pages/mv/router.js:
--------------------------------------------------------------------------------
1 | // import Mv from '@/pages/mv/Mv.vue';
2 | let Mv = () => import('@/pages/mv/Mv.vue');
3 |
4 | export default {
5 | routes: [
6 | {
7 | path: '/mv/:id',
8 | name: 'mv',
9 | component: Mv
10 | }
11 | ]
12 | };
13 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/pages/songsingle/entry-skeleton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file 页面骨架
3 | */
4 |
5 | import Vue from 'vue';
6 | import SongsingleSkeleton from './Songsingle.skeleton.vue';
7 |
8 | export default new Vue({
9 | components: {
10 | SongsingleSkeleton
11 | },
12 | template: ''
13 | });
14 |
--------------------------------------------------------------------------------
/src/pages/categoryzone/entry-skeleton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file 页面骨架
3 | */
4 |
5 | import Vue from 'vue';
6 | import CategoryzoneSkeleton from './Categoryzone.skeleton.vue';
7 |
8 | export default new Vue({
9 | components: {
10 | CategoryzoneSkeleton
11 | },
12 | template: ''
13 | });
14 |
--------------------------------------------------------------------------------
/src/assets/sass/remAdaptive.scss:
--------------------------------------------------------------------------------
1 | @function px2rem($px, $base-font-size: 75px) {
2 | @return ($px / $base-font-size) * 1rem;
3 | }
4 |
5 | @mixin font-dpr($font-size){
6 | font-size: $font-size;
7 | [data-dpr="2"] & {
8 | font-size: $font-size * 2;
9 | }
10 | [data-dpr="3"] & {
11 | font-size: $font-size * 3;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "火影音乐",
3 | "short_name": "火影音乐",
4 | "icons": [
5 | {
6 | "src": "/static/img/icons/apple-touch-icon-152x152.png",
7 | "sizes": "152x152",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "/",
12 | "background_color": "#ffffff",
13 | "display": "fullscreen"
14 | }
15 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file https://github.com/michael-ciniawsky/postcss-load-config
3 | * @author *__ author __*{% if: *__ email __* %}(*__ email __*){% /if %}
4 | */
5 |
6 | module.exports = {
7 | plugins: {
8 |
9 | // to edit target browsers: use "browserlist" field in package.json
10 | autoprefixer: {}
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/src/pages/categoryzone/entry.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file client entry
3 | * @author *__ author __*{% if: *__ email __* %}(*__ email __*){% /if %}
4 | */
5 |
6 | import 'babel-polyfill';
7 | import {createApp} from '@/app';
8 | import pageRouter from './router';
9 |
10 | const {app, router} = createApp(pageRouter);
11 |
12 | router.onReady(() => app.$mount('#app'));
13 |
--------------------------------------------------------------------------------
/src/store/modules/app-store.js:
--------------------------------------------------------------------------------
1 | // 组件状态
2 | import * as actions from '../actions';
3 | import * as getters from '../getters';
4 | import state from '../state';
5 | import mutations from '../mutations';
6 |
7 | export default {
8 | namespaced: true,
9 | /* eslint-disable */
10 | actions,
11 | mutations,
12 | state,
13 | getters
14 | };
15 |
--------------------------------------------------------------------------------
/src/pages/songsingle/router.js:
--------------------------------------------------------------------------------
1 | // import Songsingle from '@/pages/songsingle/Songsingle.vue';
2 | let Songsingle = () => import('@/pages/songsingle/Songsingle.vue');
3 |
4 | export default {
5 | routes: [
6 | {
7 | path: '/songsingle/:id',
8 | name: 'songsingle',
9 | component: Songsingle
10 | }
11 | ]
12 | };
13 |
--------------------------------------------------------------------------------
/server/db.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mysql: {
3 | host: 'qd53.my3w.com',
4 | user: 'qdm177',
5 | password: '39',
6 | database: 'qdm173_db',
7 | port: '3306',
8 | insecureAuth: true,
9 | headers: {'X-Requested-With': 'XMLHttpRequest'},
10 | requestHeader:{'Content-Type':'application/json'}
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/config/icon.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file icon 构建相关配置
3 | */
4 |
5 | 'use strict';
6 |
7 | const path = require('path');
8 |
9 | module.exports = {
10 |
11 | // 前缀
12 | prefix: 'svg-',
13 |
14 | // 用户自定义的svg文件夹
15 | svgDir: path.resolve(__dirname, '../src/assets/svg')
16 |
17 | // 项目中使用的fontawesome名
18 | // icons: [
19 | // 'envelope'
20 | // ]
21 | };
22 |
--------------------------------------------------------------------------------
/src/assets/styles/variables.styl:
--------------------------------------------------------------------------------
1 | $app-header-height = 52px
2 | $app-footer-height = 56px
3 |
4 | // app-sidebar 相关配置
5 | $app-sidebar-title-height = 75px
6 | $app-sidebar-nav-height = 45px
7 | $app-sidebar-width = 75
8 | $app-sidebar-left-icon-size = 20
9 |
10 | // app-mask 相关配置
11 | $app-mask-bgcolor = #000
12 | $app-mask-opacity = 0.5
13 | $app-mask-zindex = 999
14 | $app-mask-duration = .5s
15 |
--------------------------------------------------------------------------------
/src/pages/categoryzone/router.js:
--------------------------------------------------------------------------------
1 | // import Categoryzone from '@/pages/categoryzone/Categoryzone.vue';
2 | let Categoryzone = () => import('@/pages/categoryzone/Categoryzone.vue');
3 |
4 | export default {
5 | routes: [
6 | {
7 | path: '/categoryzone/:name/:id',
8 | name: 'categoryzone',
9 | component: Categoryzone
10 | }
11 | ]
12 | };
13 |
--------------------------------------------------------------------------------
/.fecsrc:
--------------------------------------------------------------------------------
1 | {
2 | "eslint": {
3 | "env": {
4 | "node": true,
5 | "browser": true
6 | },
7 |
8 | "globals": {
9 | "console": true,
10 | "require": true,
11 | "define": true
12 | },
13 |
14 | "rules": {
15 | "no-console": 0,
16 | "fecs-no-require": "off"
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/assets/svg/sentiment-very-satisfied.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.idea/musicAPP.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file store index
3 | * @author *__ author __*{% if: *__ email __* %}(*__ email __*){% /if %}
4 | */
5 |
6 | import Vue from 'vue';
7 | import Vuex from 'vuex';
8 | import appShell from './modules/app-shell';
9 | import asyncAjax from './modules/async-ajax.js';
10 | import appStore from './modules/app-store.js';
11 |
12 | Vue.use(Vuex);
13 |
14 | export default new Vuex.Store({
15 | modules: {
16 | appShell,
17 | asyncAjax,
18 | appStore
19 | }
20 | });
21 |
--------------------------------------------------------------------------------
/.idea/codeStyleSettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "modules": false
7 | }
8 | ],
9 | "stage-2"
10 | ],
11 | "plugins": [
12 | "transform-runtime"
13 | ],
14 | "comments": false,
15 | "env": {
16 | "test": {
17 | "presets": [
18 | "env",
19 | "stage-2"
20 | ],
21 | "plugins": [
22 | "istanbul"
23 | ]
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/static/js/amfe-flexible.min.js:
--------------------------------------------------------------------------------
1 | !function(e,t){function n(){t.body?t.body.style.fontSize=12*o+"px":t.addEventListener("DOMContentLoaded",n)}function d(){var e=i.clientWidth/10;i.style.fontSize=e+"px"}var i=t.documentElement,o=e.devicePixelRatio||1;if(n(),d(),e.addEventListener("resize",d),e.addEventListener("pageshow",function(e){e.persisted&&d()}),o>=2){var a=t.createElement("body"),s=t.createElement("div");s.style.border=".5px solid transparent",a.appendChild(s),i.appendChild(a),1===s.offsetHeight&&i.classList.add("hairlines"),i.removeChild(a)}}(window,document);
--------------------------------------------------------------------------------
/server/sqlMap.js:
--------------------------------------------------------------------------------
1 | const sqlMap = {
2 | user: {
3 | add: 'insert into userData(username, password) value (?,?)', // 添加用户
4 | update_uid: 'update userData set uid = ? where username = ?', // 更新uid
5 | update_favorite: 'update userData set favorite = ? where username = ?', // 更新用户喜欢列表
6 | update_playHistory: 'update userData set playHistory = ? where username = ?', // 更新用户最近收听列表
7 | select_name: 'select * from userData where username = ?', // 查询 用户名
8 | select_password: 'select * from userData where password = ?', // 查询 用户密码
9 | }
10 | };
11 | module.exports = sqlMap;
12 |
--------------------------------------------------------------------------------
/src/api/rank.js:
--------------------------------------------------------------------------------
1 | import jsonp from 'common/js/jsonp'
2 | import {commonParams, options} from './config'
3 |
4 | // 获取排行榜数据接口
5 | export function getRankingList() {
6 | const url = 'https://c.y.qq.com/v8/fcg-bin/fcg_myqq_toplist.fcg';
7 |
8 | const data = Object.assign({}, commonParams, {
9 | uin: 0,
10 | needNewCode: 1,
11 | platform: 'h5'
12 | });
13 |
14 | return jsonp(url, data, options);
15 | }
16 |
17 | // 获取排行榜歌曲数据接口
18 | export function getRankingSongList(topid) {
19 | const url = 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_toplist_cp.fcg';
20 |
21 | const data = Object.assign({}, commonParams, {
22 | topid: topid,
23 | needNewCode: 1,
24 | uin: 0,
25 | tpl: 3,
26 | page: 'detail',
27 | type: 'top',
28 | platform: 'h5'
29 | });
30 |
31 | return jsonp(url, data, options);
32 | }
33 |
--------------------------------------------------------------------------------
/src/common/js/jsonp.js:
--------------------------------------------------------------------------------
1 | import originJsonp from 'jsonp';
2 |
3 | export default function jsonp(url, data, option) {
4 | // 如果url连接中没有?就添加? 有就添加&
5 | url += (url.indexOf('?') < 0 ? '?' : '&') + param(data);
6 | // 返回请求
7 | return new Promise((resolve, reject) => {
8 | originJsonp(url, option, (error, data) => {
9 | if (!error) {
10 | resolve(data);
11 | }
12 | else {
13 | reject(error);
14 | }
15 | });
16 | });
17 | }
18 |
19 | // 为url 添加参数
20 | function param(data) {
21 | let url = '';
22 | // 遍历data参数
23 | for (var k in data) {
24 | // 保存data值
25 | let value = data[k] !== undefined ? data[k] : '';
26 | // 输出键 / 值
27 | url += `&${k}=${encodeURIComponent(value)}`;
28 | }
29 | return url ? url.substring(1) : '';
30 | }
31 |
--------------------------------------------------------------------------------
/src/api/search.js:
--------------------------------------------------------------------------------
1 | import jsonp from 'common/js/jsonp'
2 | import {commonParams, options} from './config'
3 |
4 | // 获取热门搜索接口
5 | export function getSearchHot() {
6 | const url = 'https://c.y.qq.com/splcloud/fcgi-bin/gethotkey.fcg';
7 |
8 | const data = Object.assign({}, commonParams, {
9 | uin: 0,
10 | needNewCode: 1,
11 | platform: 'h5'
12 | });
13 |
14 | return jsonp(url, data, options)
15 | }
16 |
17 | // 搜索歌曲接口
18 | export function searchSong(query, page, zhida, perpage) {
19 | const url = 'https://c.y.qq.com/soso/fcgi-bin/search_for_qq_cp';
20 |
21 | const data = Object.assign({}, commonParams, {
22 | w: query,
23 | p: page,
24 | perpage,
25 | n: perpage,
26 | catZhida: zhida ? 1 : 0,
27 | zhidaqu: 1,
28 | t: 0,
29 | flag: 1,
30 | ie: 'utf-8',
31 | sem: 1,
32 | aggr: 0,
33 | remoteplace: 'txt.mqq.all',
34 | uin: 0,
35 | needNewCode: 1,
36 | platform: 'h5'
37 | })
38 |
39 | return jsonp(url, data, options)
40 | }
41 |
--------------------------------------------------------------------------------
/src/api/singer.js:
--------------------------------------------------------------------------------
1 | import jsonp from 'common/js/jsonp'
2 | import {commonParams, options} from './config'
3 |
4 | // 获取歌手列表接口
5 | export function getSingerList () {
6 | const url = 'https://c.y.qq.com/v8/fcg-bin/v8.fcg';
7 |
8 | const data = Object.assign({}, commonParams, {
9 | channel: 'singer',
10 | page: 'list',
11 | key: 'all_all_all',
12 | pagesize: 100,
13 | pagenum: 1,
14 | hostUin: 0,
15 | needNewCode: 0,
16 | platform: 'yqq'
17 | });
18 |
19 | return jsonp(url, data, options)
20 | }
21 |
22 | // 歌手歌曲列表
23 | export function getSingerDetail (param) {
24 | const url = 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg';
25 |
26 | const data = Object.assign({}, commonParams, {
27 | uin: 1020989782,
28 | platform: 'h5page',
29 | needNewCode: 1,
30 | order: 'listen',
31 | from: 'h5',
32 | begin: 0
33 | }, param);
34 |
35 | return jsonp(url, data, options)
36 | }
37 |
--------------------------------------------------------------------------------
/src/assets/styles/material-icons.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "Material Icons";
3 | font-style: normal;
4 | font-weight: 400;
5 | src: local("Material Icons"),
6 | local("MaterialIcons-Regular"),
7 | url(/static/fonts/MaterialIcons-Regular.woff2) format('woff2'),
8 | url(/static/fonts/MaterialIcons-Regular.woff) format('woff'),
9 | url(/static/fonts/MaterialIcons-Regular.ttf) format('truetype');
10 | }
11 |
12 | .material-icons {
13 | display: inline-block;
14 | font: normal normal 24px/24px "Material Icons";
15 | text-transform: none;
16 | letter-spacing: normal;
17 | word-wrap: normal;
18 | white-space: nowrap;
19 | direction: ltr;
20 |
21 | /* Support for all WebKit browsers. */
22 | -webkit-font-smoothing: antialiased;
23 | /* Support for Safari and Chrome. */
24 | text-rendering: optimizeLegibility;
25 |
26 | /* Support for Firefox. */
27 | -moz-osx-font-smoothing: grayscale;
28 |
29 | /* Support for IE. */
30 | font-feature-settings: "liga";
31 | }
32 |
--------------------------------------------------------------------------------
/config/theme.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file 主题构建相关配置
3 | * @author *__ author __*{% if: *__ email __* %}(*__ email __*){% /if %}
4 | */
5 |
6 | // 定义主题列表
7 | const themeList = {
8 | // 定义主题名称
9 | myTheme: {
10 | themeColor: {
11 | primary: 'rgb(40, 116, 240)',
12 | accent: '$blue.accent-2',
13 | secondary: '$grey.darken-3',
14 | info: '$blue.base',
15 | warning: '$amber.base',
16 | error: '$red.accent-2',
17 | success: '$green.base'
18 | },
19 | materialDesign: {
20 | 'bg-color': '#fff',
21 | 'fg-color': '#000',
22 | 'text-color': '#000',
23 | 'primary-text-percent': .87,
24 | 'secondary-text-percent': .54,
25 | 'disabledORhints-text-percent': .38,
26 | 'divider-percent': .12,
27 | 'active-icon-percent': .54,
28 | 'inactive-icon-percent': .38
29 | }
30 | }
31 | };
32 |
33 | module.exports = {
34 | theme: themeList.myTheme // 和主题列表中的主题名称对应
35 | };
36 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | const userApi = require('./api/userApi');
2 | const fs = require('fs');
3 | const path = require('path');
4 | const bodyParser = require('body-parser');
5 | const express = require('express');
6 | const app = express();
7 | const cors = require('cors');
8 | const session = require('express-session');
9 |
10 | app.use(cors({
11 | methods:['GET','POST'],
12 | alloweHeaders:['Conten-Type', 'Authorization']
13 | }));
14 |
15 | // Use the session middleware
16 | app.use(session({
17 | ////这里的name值得是cookie的name,默认cookie的name是:connect.sid
18 | //name: 'hhw',
19 | secret: 'keyboard cat',
20 | cookie: ('name', 'value', { path: '/serverApi', httpOnly: true,secure: false, maxAge: 60000 }),
21 | //重新保存:强制会话保存即使是未修改的。默认为true但是得写上
22 | resave: true,
23 | //强制“未初始化”的会话保存到存储。
24 | saveUninitialized: true,
25 |
26 | }));
27 |
28 |
29 | app.use(bodyParser.json());
30 | app.use(bodyParser.urlencoded({extended: false}));
31 |
32 |
33 | // 后端api路由
34 | app.use('/serverApi', userApi);
35 | // 监听端口
36 | app.listen(3001);
37 | console.log('success listen at port.....');
38 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // https://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parser: 'babel-eslint',
6 | parserOptions: {
7 | sourceType: 'module'
8 | },
9 | env: {
10 | browser: true,
11 | },
12 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md
13 | extends: 'standard',
14 | // required to lint *.vue files
15 | plugins: [
16 | 'html'
17 | ],
18 | // add your custom rules here
19 | 'rules': {
20 | // allow paren-less arrow functions
21 | 'arrow-parens': 0,
22 | // allow async-await
23 | 'generator-star-spacing': 0,
24 | // allow debugger during development
25 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
26 | 'semi': ['error', 'always'],
27 | 'indent': 0,
28 | 'space-before-function-paren': 0,
29 | 'no-tabs': "off",
30 | 'padded-blocks': 0,
31 | 'no-multiple-empty-lines': [2, {'max': 2}], // 不允许多个空行
32 | 'brace-style': [2, 'stroustrup',{'allowSingleLine': true}],
33 | 'operator-linebreak': [2, 'before', {'overrides': {'?':'before', ':': 'before'}}]
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PWA版本音乐APP
2 |
3 | > 这是一个采用PWA与百度开发的PWA框架Lavas制作的webApp
4 |
5 | > 该APP引入了页面骨架屏与Service Worker离线缓存,让webApp接近了原生的体验
6 |
7 | > 当用户访问该网站第二次的时候还允许将站点添加至主屏幕,这个是 PWA 提供的一项重要功能。
8 |
9 | > 由于商业利益关系已屏蔽部分接口,改项目仅供学习使用
10 |
11 | ## 安装
12 |
13 | 手机APP安装点击一下链接获取安卓APK
14 | > https://gss0.bdstatic.com/9rkZbzqaKgQUohGko9WTAnF6hhy/assets/pwa/projects/1521211644728/lavas.app.lavas.HuoyinMusic.apk
15 |
16 | 手机扫码获取安卓APK
17 | > 
18 |
19 |
20 | ### 免责声明
21 | 本文章的所有内容,包括文字、图片、音频、视频、软件、程序、以及网页版式设计等均在网上搜集。
22 | 本文章提供的内容仅用于个人学习、研究或欣赏。我们不保证内容的正确性。通过使用本站内容随之而来的风险与本站无关
23 | 访问者可将本网站提供的内容或服务用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯本网站及相关权利人的合法权利。
24 | 本网站内容原作者如不愿意在本网站刊登内容,请及时通知本人,予以删除。
25 |
26 |
27 | **链接到本站**
28 | 任何网站都可以链接到本站的任何页面。
29 | 如果您需要在对少量内容进行引用,请务必在引用该内容的页面添加指向被引用页面的链接。
30 |
31 | **保证**
32 | 本网站不提供任何形式的保证。所有与使用本站相关的直接风险均由用户承担。本网站提供的所有代码均为实例,并不对性能、适用性、适销性或/及其他方面提供任何保证。
33 | 内容可能包含不准确性或错误。本人不对本网站及其内容的准确性进行保证。如果您发现本站点及其内容包含错误,请联系我们以便这些错误得到及时的更正:282126990@qq.com
34 |
35 | **您的行为**
36 |
37 | 当您使用本站点时,说明您已经同意并接受本页面的所有信息。
38 |
--------------------------------------------------------------------------------
/src/pages/NotFound.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | info
6 |
7 |
页面未找到
8 |
9 |
10 |
11 |
12 |
33 |
34 |
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) *__year__* *__ author __*
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/common/js/songSingle.js:
--------------------------------------------------------------------------------
1 | // 自定义定义歌单信息
2 | class Songsingle {
3 | constructor ({contentId, cover, rcmdtemplate, title, start, mid, publicTime}) {
4 | // 歌单id
5 | this.contentId = contentId;
6 | // 歌单图片
7 | this.cover = cover;
8 | // 歌单头部标题
9 | this.rcmdtemplate = rcmdtemplate;
10 | // 歌单标题
11 | this.title = title;
12 | // 状态设置是不是新碟跳转
13 | this.start = start;
14 | // 专辑mid
15 | this.mid = mid;
16 | // 专辑发行时间
17 | this.publicTime = publicTime;
18 | };
19 | };
20 |
21 | // 创建歌单信息
22 | export function createSongSingle (data) {
23 | return new Songsingle({
24 | // 歌单id
25 | contentId: data.content_id || data.dissid,
26 | // 歌单图片
27 | cover: data.cover || data.imgurl,
28 | // 歌单头部标题
29 | rcmdtemplate: data.rcmdtemplate || '歌单推荐',
30 | // 歌单标题
31 | title: data.title || data.dissname,
32 | // 状态设置是不是新碟跳转
33 | start: data.start || null,
34 | // 专辑mid
35 | mid: data.mid || null,
36 | // 专辑发行时间
37 | publicTime: data.publicTime || null
38 | });
39 | };
40 |
--------------------------------------------------------------------------------
/src/common/js/cookie.js:
--------------------------------------------------------------------------------
1 | // 获取cookies
2 | export function getCookie(key) {
3 | if (document.cookie.length > 0) {
4 | return decodeURIComponent(document.cookie.replace(new RegExp('(?:(?:^|.*;)\\s*' + encodeURIComponent(key).replace(/[-.+*]/g, '\\$&') + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1')) || null;
5 | }
6 | }
7 |
8 | // 设置cookies
9 | export function setCookie(sKey, sValue, vEnd, sPath, sDomain, bSecure) {
10 | // 判断有没有传入sKey 或者 没有传入其他值 就返回false
11 | if (!sKey || /^(?:expires|max-age|path|domain|secure)$/i.test(sKey)) {
12 | return false;
13 | }
14 |
15 | // 过期时间
16 | let sExpires = '';
17 |
18 | if (vEnd) {
19 | switch (vEnd.constructor) {
20 | case Number:
21 | sExpires = vEnd === Infinity ? '; expires=Tue, 19 Jan 2038 03:14:07 GMT' : '; max-age=' + vEnd;
22 | break;
23 | case String:
24 | sExpires = '; expires=' + vEnd;
25 | break;
26 | case Date:
27 | sExpires = '; expires=' + vEnd.toUTCString();
28 | break;
29 | }
30 | }
31 | document.cookie = encodeURIComponent(sKey) + '=' + encodeURIComponent(sValue) + sExpires + (sDomain ? '; domain=' + sDomain : '') + (sPath ? '; path=' + sPath : '') + (bSecure ? '; secure' : '');
32 | return true;
33 | }
34 |
--------------------------------------------------------------------------------
/src/api/login.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | // 用户登录接口
4 | export function getSelectUser (param) {
5 | const url = 'https://linfengzhuiyi.cn:5000/serverApi/selectUser';
6 |
7 | return axios.post(url, param).then((res) => {
8 | return Promise.resolve(res.data);
9 | });
10 | }
11 |
12 | // 用户注册接口
13 | export function getAddUser (param) {
14 | const url = 'http://119.29.97.214:3001/serverApi/addUser';
15 |
16 | return axios.post(url, param).then((res) => {
17 | return Promise.resolve(res.data);
18 | });
19 | }
20 |
21 | // 同步用户收藏歌曲
22 | export function getAddFavorite (param) {
23 | const url = 'http://119.29.97.214:3001/serverApi/addFavorite';
24 |
25 | return axios.post(url, param).then((res) => {
26 | return Promise.resolve(res.data);
27 | });
28 | }
29 |
30 | // 同步用户最近播放歌曲到数据库接口
31 | export function getAddPlayHistory (param) {
32 | const url = 'http://119.29.97.214:3001/serverApi/addPlayHistory';
33 |
34 | return axios.post(url, param).then((res) => {
35 | return Promise.resolve(res.data);
36 | });
37 | }
38 |
39 | // 获取该用户的uid判断是否在另一个地方登录接口
40 | export function getUserUid (param) {
41 | const url = 'http://119.29.97.214:3001/serverApi/getUserUid';
42 |
43 | return axios.post(url, param).then((res) => {
44 | return Promise.resolve(res.data);
45 | });
46 | }
47 |
--------------------------------------------------------------------------------
/src/api/config.js:
--------------------------------------------------------------------------------
1 | export const commonParams = {
2 | inCharset: 'utf-8',
3 | outCharset: 'utf-8',
4 | g_tk: setgtk(),
5 | format: 'jsonp',
6 | notice: 0
7 | };
8 |
9 | export const options = {
10 | param: 'jsonpCallback'
11 | };
12 |
13 | export const ERR_OK = 0;
14 |
15 | export const MusicuMessageData = `{"comm":{"ct":24},"category":{"method":"get_hot_category","param":{"qq":""},"module":"music.web_category_svr"},"recomPlaylist":{"method":"get_hot_recommend","param":{"async":1,"cmd":2},"module":"playlist.HotRecommendServer"},"playlist":{"method":"get_playlist_by_category","param":{"id":8,"curPage":1,"size":40,"order":5,"titleid":8},"module":"playlist.PlayListPlazaServer"},"new_song":{"module":"QQMusic.MusichallServer","method":"GetNewSong","param":{"type":2}},"new_album":{"module":"QQMusic.MusichallServer","method":"GetNewAlbum","param":{"type":0,"category":"-1","genre":0,"year":1,"company":-1,"sort":1,"start":0,"end":39}},"toplist":{"module":"music.web_toplist_svr","method":"get_toplist_index","param":{}},"focus":{"module":"QQMusic.MusichallServer","method":"GetFocus","param":{}}}`;
16 |
17 | export function setgtk() {
18 | let t = '10';
19 | let n = '5381';
20 | if (t) {
21 | for (var i = 0, o = t.length; o > i; ++i) {
22 | n += (n << 5) + t.charCodeAt(i);
23 | }
24 | }
25 |
26 | return 2147483647 & n;
27 | }
28 |
--------------------------------------------------------------------------------
/src/common/js/newSongSpeed.js:
--------------------------------------------------------------------------------
1 | // 新碟数据
2 | class NewAlbum {
3 | constructor({dissid, mid, title, singerName, publicTime, start}) {
4 | this.dissid = dissid; // 专辑id
5 | this.mid = mid; // 专辑mid
6 | this.cover = `https://y.gtimg.cn/music/photo_new/T002R300x300M000${mid}.jpg?max_age=2592000`;
7 | this.title = title; // 专辑名称
8 | this.singerName = singerName; // 歌手名,
9 | this.publicTime = publicTime; // 发行时间
10 | this.start = start;
11 | }
12 | }
13 |
14 | /**
15 | * 对新碟list数据做处理
16 | * @type {Array} list
17 | */
18 | export function initNewAlbumList(list) {
19 | let ret = [];
20 |
21 | let items = {};
22 |
23 | list.forEach((item) => {
24 | items = (new NewAlbum({
25 | dissid: item.album_id,
26 | mid: item.album_mid,
27 | title: item.album_name,
28 | singerName: filterSinger(item.singers),
29 | publicTime: item.public_time,
30 | start: 'newAlbum'
31 | }));
32 |
33 | ret.push(items);
34 | });
35 |
36 | return ret;
37 | }
38 |
39 | /*
40 | * singer是一个数组 过滤出singer的name
41 | * @type {String}
42 | * */
43 | export function filterSinger(singer) {
44 | let ret = [];
45 |
46 | if (!singer) {
47 | return '';
48 | }
49 |
50 | singer.forEach((s) => {
51 | ret.push(s.singer_name);
52 | });
53 |
54 | return ret.join('/');
55 | }
56 |
--------------------------------------------------------------------------------
/src/api/featuredRadio.js:
--------------------------------------------------------------------------------
1 | import jsonp from 'common/js/jsonp';
2 | import {commonParams, options} from 'api/config';
3 |
4 | /*
5 | * 普通电台歌曲接口
6 | * @param id 电台id
7 | * */
8 | export function getOrdinaryFeaturedRadio(param) {
9 | const url = 'https://u.y.qq.com/cgi-bin/musicu.fcg';
10 |
11 | const stringData = {
12 | 'songlist': {
13 | 'module': 'pf.radiosvr',
14 | 'method': 'GetRadiosonglist',
15 | 'param': {
16 | 'id': parseInt(param),
17 | 'firstplay': 1,
18 | 'num': 10
19 | }
20 | }
21 | };
22 |
23 | // assign将所有可枚举属性的值从一个或多个源对象复制到目标对象{}
24 | const message = Object.assign({}, commonParams, {
25 | loginUin: 0,
26 | hostUin: 0,
27 | platform: 'yqq',
28 | needNewCode: 0,
29 | data: JSON.stringify(stringData)
30 | });
31 |
32 | return jsonp(url, message);
33 | }
34 |
35 |
36 | /*
37 | * 个性电台歌曲接口
38 | * @param id 电台id
39 | * */
40 | export function getPersonalFeaturedRadio() {
41 | const url = 'https://c.y.qq.com/rcmusic2/fcgi-bin/fcg_guess_youlike_pc.fcg';
42 |
43 | // assign将所有可枚举属性的值从一个或多个源对象复制到目标对象{}
44 | const message = Object.assign({}, commonParams, {
45 | loginUin: 0,
46 | hostUin: 0,
47 | platform: 'yqq',
48 | needNewCode: 0,
49 | cid: 703,
50 | uin: 0
51 | });
52 |
53 | return jsonp(url, message, options);
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/src/store/getters.js:
--------------------------------------------------------------------------------
1 | /** 映射 **/
2 | // 保存当前播放的歌曲信息 saveCurrentSong
3 | // 获取当前播放的歌曲信息 getCurrentSong
4 | import {saveCurrentSong, getCurrentSong} from 'common/js/cache'
5 |
6 | /** 播放组件状态 **/
7 | // 歌曲列表
8 | export const songList = state => state.songList;
9 | // 顺序播放列表
10 | export const sequenceList = state => state.sequenceList;
11 | // 控制歌曲播放
12 | export const playing = state => state.playing;
13 | // 播放列表
14 | export const playList = state => state.playList;
15 | // 控制歌曲播放模式
16 | export const playMode = state => state.playMode;
17 | // 当前播放索引
18 | export const currentIndex = state => state.currentIndex;
19 | // 获取当前歌曲
20 | export const currentSong = (state) => {
21 | // 保存当前播放的歌曲信息
22 | saveCurrentSong(state.playList[state.currentIndex] || {});
23 | return getCurrentSong();
24 | };
25 | // 获取收藏列表
26 | export const favoriteList = state => state.favoriteList;
27 | // 控制播发器放大缩小
28 | export const fullScreen = state => state.fullScreen;
29 | // 获取播放历史
30 | export const playHistory = state => state.playHistory;
31 | /***************/
32 |
33 | /** 搜索框组件状态 **/
34 | // 获取搜索历史
35 | export const searchHistory = state => state.searchHistory;
36 | /***************/
37 |
38 | /** 排行榜组件状态 **/
39 | // 获取搜索历史
40 | export const rankingSongList = state => state.rankingSongList;
41 | /***************/
42 |
43 | /** 歌曲列表组件状态 **/
44 | // 获取搜索历史
45 | export const showMore = state => state.showMore;
46 | /***************/
47 |
48 | /** 歌手组件状态 **/
49 | // 获取歌手信息
50 | export const singerMessage = state => state.singerMessage;
51 | /***************/
52 |
--------------------------------------------------------------------------------
/src/api/categorySongList.js:
--------------------------------------------------------------------------------
1 | import jsonp from 'common/js/jsonp';
2 | import {commonParams, options} from 'api/config';
3 | import axios from 'axios';
4 |
5 | // 获取分类歌单导航
6 | export function getCategoryNavigation () {
7 | const url = 'https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_tag_conf.fcg';
8 |
9 | // assign将所有可枚举属性的值从一个或多个源对象复制到目标对象{}
10 | const data = Object.assign ({}, commonParams, {
11 | loginUin: 0,
12 | hostUin: 0,
13 | platform: 'yqq',
14 | needNewCode: 0
15 | });
16 |
17 | return jsonp (url, data, options);
18 | }
19 |
20 | /*
21 | * 获取分类歌单
22 | * categoryId // categoryId
23 | * sin // 开始位置
24 | * ein // 结束位置
25 | * */
26 | export function getSortSongData (param) {
27 | const url = '/api/sortSongData';
28 |
29 | const data = Object.assign ({}, commonParams, {
30 | jsonpCallback: 'getPlaylist',
31 | picmid: 1,
32 | rnd: Math.random (),
33 | loginUin: 0,
34 | hostUin: 0,
35 | inCharset: 'utf8',
36 | platform: 'yqq',
37 | needNewCode: 0,
38 | }, param);
39 |
40 | return axios.get (url, {
41 | params: data
42 | }).then ((res) => {
43 | let ret = res.data;
44 | // 如果data是字符串就转换成对象
45 | if (typeof ret === 'string') {
46 |
47 | if (ret.match (/MusicJsonCallback\(/g)) {
48 | ret = ret.replace (/MusicJsonCallback\(/g, '').slice (0, -1);
49 | }
50 | else {
51 | ret = ret.replace (/getPlaylist\(/g, '').slice (0, -1);
52 | }
53 |
54 | return Promise.resolve (JSON.parse (ret));
55 | }
56 | else {
57 | return Promise.resolve (ret);
58 | }
59 | });
60 | }
61 |
--------------------------------------------------------------------------------
/src/common/js/dom.js:
--------------------------------------------------------------------------------
1 | // 添加类
2 | export function addClass(el, className) {
3 | if (hasClass(el, className)) {
4 | return;
5 | }
6 |
7 | // 分割
8 | let newClass = el.className.split(' ');
9 | // 添加
10 | newClass.push(className);
11 | // 合并
12 | el.className = newClass.join(' ');
13 | }
14 |
15 | // 查找类
16 | export function hasClass(el, className) {
17 | let reg = new RegExp('(^|\\s)' + className + '(\\s|$)');
18 | return reg.test(el.className);
19 | }
20 |
21 | // 获取范围值的随机数
22 | export function getRandomNum(min, max) {
23 | let range = max - min;
24 | return (min + Math.floor(Math.random() * range));
25 | }
26 |
27 | // 封装浏览器样式适配
28 | let elementStyle = document.createElement('div').style;
29 |
30 | let vendor = (() => {
31 | let transformNames = {
32 | webkit: 'webkitTransform',
33 | Moz: 'MozTransform',
34 | O: 'OTransform',
35 | ms: 'msTransform',
36 | standard: 'tansform'
37 | };
38 |
39 | // 遍历transformNames
40 | for (let key in transformNames) {
41 | if (elementStyle[transformNames[key]] !== undefined) {
42 | return key;
43 | }
44 | }
45 |
46 | return false;
47 | })();
48 |
49 | // 暴露浏览器样式适配方法
50 | export function prefixStyle (style) {
51 | if (vendor === false) {
52 | return false;
53 | }
54 |
55 | if (vendor === 'standard') {
56 | return style;
57 | }
58 |
59 | return vendor + style.charAt(0).toUpperCase() + style.substr(1);
60 | }
61 |
62 | // 获取设置data-方法
63 | export function getData (el, name,val) {
64 | const prefix = 'data-';
65 | if (val) {
66 | return el.setAttribute(prefix + name, val);
67 | }
68 | return el.getAttribute(prefix + name);
69 | }
70 |
--------------------------------------------------------------------------------
/src/api/songAlbumMessage.js:
--------------------------------------------------------------------------------
1 | import {commonParams} from 'api/config';
2 | import axios from 'axios';
3 |
4 | /*
5 | * 获取歌单专辑信息
6 | * 该接口下拉加载时传入的参数
7 | * songBegin
8 | * song_begin: songBegin, // 最大条数
9 | * song_num: 15 // 一次加载15条数据
10 | * pic: 500
11 | * */
12 | export function getSongAlbumMessage (dissid, songBegin) {
13 | const url = '/api/getSongAlbumMessage';
14 |
15 | const data = Object.assign({}, commonParams, {
16 | disstid: dissid,
17 | song_begin: songBegin,
18 | uin: 0,
19 | platform: 'h5',
20 | needNewCode: 1,
21 | new_format: 1,
22 | pic: 500,
23 | type: 1,
24 | json: 1,
25 | utf8: 1,
26 | onlysong: 0,
27 | nosign: 1,
28 | song_num: 15,
29 | });
30 | return axios.get(url, {
31 | params: data
32 | }).then((res) => {
33 | let ret = res.data;
34 | // 如果data是字符串就转换成对象
35 | if (typeof ret === 'string') {
36 | ret = ret.replace(/jsonCallback\(/g, '').slice(0, -1);
37 |
38 | return Promise.resolve(JSON.parse(ret));
39 | }
40 | else {
41 | return Promise.resolve(ret);
42 | }
43 | });
44 | };
45 |
46 | /*
47 | * 歌单专辑收藏量
48 | * disstid // 专辑id
49 | * */
50 | export function getCollection (disstid) {
51 | const url = '/api/getCollection';
52 |
53 | const data = Object.assign({}, commonParams, {
54 | disstid,
55 | loginUin: 0,
56 | hostUin: 0,
57 | platform: 'yqq',
58 | needNewCode: 0,
59 | cid: 322,
60 | nocompress: 1
61 | });
62 |
63 | return axios.get(url, {
64 | params: data
65 | }).then((res) => {
66 | return Promise.resolve(res.data);
67 | });
68 | }
69 |
--------------------------------------------------------------------------------
/src/pages/mv/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 火影音乐
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | <% for (var jsFilePath of htmlWebpackPlugin.files.js) { %>
22 |
23 | <% } %>
24 | <% for (var cssFilePath of htmlWebpackPlugin.files.css) { %>
25 |
26 | <% } %>
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/base/loading/loading.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | audiotrack
5 |
6 |
7 |
{{loadingText}}
8 |
9 |
10 |
11 |
21 |
22 |
71 |
--------------------------------------------------------------------------------
/src/api/homeAjax.js:
--------------------------------------------------------------------------------
1 | import jsonp from 'common/js/jsonp';
2 | import {commonParams, options, MusicuMessageData} from 'api/config';
3 |
4 | // 主页数据接口
5 | export function getHomeMessage() {
6 | const url = 'https://u.y.qq.com/cgi-bin/musicu.fcg';
7 |
8 | // assign将所有可枚举属性的值从一个或多个源对象复制到目标对象{}
9 | const data = Object.assign({}, commonParams, {
10 | hostUin: 0,
11 | platform: 'yqq',
12 | needNewCode: 0,
13 | data: MusicuMessageData
14 | });
15 | return jsonp(url, data);
16 | }
17 |
18 | // 主页精选电台导航
19 | export function getHomeFeaturedRadio() {
20 | const url = 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_radiolist.fcg';
21 |
22 | // assign将所有可枚举属性的值从一个或多个源对象复制到目标对象{}
23 | const data = Object.assign({}, commonParams, {
24 | channel: 'radio',
25 | page: 'index',
26 | hostUin: 0,
27 | platform: 'yqq',
28 | needNewCode: 0,
29 | p: Math.random()
30 | });
31 | return jsonp(url, data, options);
32 | }
33 |
34 |
35 | // 主页歌单推荐刷新后的数据
36 | export function getReplaceHomeRecomPlaylist(param) {
37 | const url = 'https://u.y.qq.com/cgi-bin/musicu.fcg';
38 |
39 | const stringData = {
40 | "comm":{
41 | "ct":24
42 | },
43 | "playlist":{
44 | "method":"get_playlist_by_category",
45 | "param":{
46 | "id": param,
47 | "curPage":1,
48 | "size":7,
49 | "order":5,
50 | "titleid":param
51 | },
52 | "module":"playlist.PlayListPlazaServer"
53 | }
54 | };
55 |
56 |
57 |
58 | // assign将所有可枚举属性的值从一个或多个源对象复制到目标对象{}
59 | const data = Object.assign({}, commonParams, {
60 | loginUin: 0,
61 | hostUin: 0,
62 | platform: 'yqq',
63 | needNewCode: 0,
64 | data: JSON.stringify(stringData)
65 | });
66 | return jsonp(url, data);
67 | }
68 |
--------------------------------------------------------------------------------
/src/common/js/singer.js:
--------------------------------------------------------------------------------
1 | const HOT_SINGER_LEN = 10;
2 | const HOT_NAME = '热';
3 |
4 | export default class Singer {
5 | constructor ({id, mid, name}) {
6 | this.id = id;
7 | this.mid = mid;
8 | this.name = name;
9 | this.avatar = `https://y.gtimg.cn/music/photo_new/T001R500x500M000${mid}.jpg?max_age=2592000`;
10 | }
11 | }
12 |
13 | // 自定义歌手数据
14 | export function normalizeSinger (list) {
15 | let map = {
16 | // 创建热门数据
17 | hot: {
18 | title: HOT_NAME,
19 | items: []
20 | }
21 | };
22 |
23 | list.forEach((item, index) => {
24 | // 前10条数据为热门歌手
25 | if (index < HOT_SINGER_LEN) {
26 | map.hot.items.push(new Singer({
27 | name: item.Fsinger_name,
28 | id: item.Fsinger_id,
29 | mid: item.Fsinger_mid
30 | }));
31 | }
32 |
33 | const key = item.Findex;
34 |
35 | // 设置其他歌手数据
36 | if (!map[key]) {
37 | map[key] = {
38 | title: key,
39 | items: []
40 | }
41 | }
42 |
43 | map[key].items.push(new Singer({
44 | name: item.Fsinger_name,
45 | id: item.Fsinger_id,
46 | mid: item.Fsinger_mid
47 | }));
48 | });
49 |
50 | // 为了得到有序列表,我们需要处理 map
51 | let ret = [];
52 | let hot = [];
53 |
54 | // 获取多有歌手数据
55 | for (let key in map) {
56 | // 把数据按照a-z 热门分别插入不同的数组
57 | let val = map[key];
58 | if (val.title.match(/[a-zA-Z]/)) {
59 | ret.push(val)
60 | }
61 | // 热门放到hot数组
62 | else if (val.title === HOT_NAME) {
63 | hot.push(val)
64 | }
65 | }
66 |
67 | ret.sort((a, b) => {
68 | return a.title.charCodeAt(0) - b.title.charCodeAt(0)
69 | });
70 |
71 | // 把排序好的数组拼接到一开始的hot数组后面
72 | return hot.concat(ret);
73 | }
74 |
--------------------------------------------------------------------------------
/src/pages/home/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 火影音乐
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | <% for (var jsFilePath of htmlWebpackPlugin.files.js) { %>
22 |
23 | <% } %>
24 | <% for (var cssFilePath of htmlWebpackPlugin.files.css) { %>
25 |
26 | <% } %>
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/pages/songsingle/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 火影音乐
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | <% for (var jsFilePath of htmlWebpackPlugin.files.js) { %>
22 |
23 | <% } %>
24 | <% for (var cssFilePath of htmlWebpackPlugin.files.css) { %>
25 |
26 | <% } %>
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/pages/categoryzone/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 火影音乐
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | <% for (var jsFilePath of htmlWebpackPlugin.files.js) { %>
22 |
23 | <% } %>
24 | <% for (var cssFilePath of htmlWebpackPlugin.files.css) { %>
25 |
26 | <% } %>
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // see http://vuejs-templates.github.io/webpack for documentation.
4 | const path = require('path');
5 | const swPrecacheConfig = require('./sw-precache');
6 | const icon = require('./icon');
7 | const theme = require('./theme');
8 |
9 | module.exports = {
10 | icon: icon,
11 | theme: theme,
12 | swPrecache: swPrecacheConfig,
13 | build: {
14 | env: require('./prod.env'),
15 | port: 9000,
16 | index: path.resolve(__dirname, '../dist/index.html'),
17 | assetsRoot: path.resolve(__dirname, '../dist'),
18 | assetsSubDirectory: 'static',
19 | assetsPublicPath: '/',
20 | productionSourceMap: true,
21 |
22 | // Gzip off by default as many popular static hosts such as
23 | // Surge or Netlify already gzip all static assets for you.
24 | // Before setting to `true`, make sure to:
25 | // npm install --save-dev compression-webpack-plugin
26 | productionGzip: false,
27 | productionGzipExtensions: ['js', 'css'],
28 |
29 | // Run the build command with an extra argument to
30 | // View the bundle analyzer report after build finishes:
31 | // `npm run build --report`
32 | // Set to `true` or `false` to always turn it on or off
33 | bundleAnalyzerReport: process.env.npm_config_report
34 | },
35 | dev: {
36 | env: require('./dev.env'),
37 | port: 8088,
38 | autoOpenBrowser: true,
39 | assetsSubDirectory: 'static',
40 | assetsPublicPath: '/',
41 | proxyTable: {},
42 |
43 | // CSS Sourcemaps off by default because relative paths are "buggy"
44 | // with this option, according to the CSS-Loader README
45 | // (https://github.com/webpack/css-loader#sourcemaps)
46 | // In our experience, they generally work as expected,
47 | // just be aware of this issue when enabling this option.
48 | cssSourceMap: false
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/src/api/mvList.js:
--------------------------------------------------------------------------------
1 | import jsonp from 'common/js/jsonp';
2 | import {commonParams, options} from 'api/config';
3 |
4 | /*
5 | * 获取MV列表接口
6 | * type {String}
7 | * */
8 | export function getNewMvList (lan) {
9 | const url = 'https://c.y.qq.com/v8/fcg-bin/getmv_by_tag';
10 |
11 | // assign将所有可枚举属性的值从一个或多个源对象复制到目标对象{}
12 | const data = Object.assign({}, commonParams, {
13 | loginUin: 0,
14 | hostUin: 0,
15 | outCharset: 'GB2312',
16 | platform: 'yqq',
17 | needNewCode: 0,
18 | cmd: 'shoubo',
19 | lan: lan
20 | });
21 |
22 | return jsonp(url, data, options);
23 | }
24 |
25 | /*
26 | * 获取对应MV的信息接口
27 | * type {Object}
28 | * */
29 | export function getMvMessage (vid) {
30 | const url = 'https://u.y.qq.com/cgi-bin/musicu.fcg';
31 |
32 | let stringData = {
33 | "getMvInfo": {
34 | "module": "MvService.MvInfoProServer",
35 | "method": "GetMvInfoList",
36 | "param": {
37 | "vidlist": [`${vid}`]
38 | }
39 | }
40 | };
41 |
42 | // assign将所有可枚举属性的值从一个或多个源对象复制到目标对象{}
43 | const data = Object.assign({}, commonParams, {
44 | uin: 0,
45 | ct: 23,
46 | cv: 0,
47 | data: JSON.stringify(stringData)
48 | });
49 |
50 | return jsonp(url, data);
51 | }
52 |
53 | /*
54 | * 获取MV播放地址请求
55 | * type {Object}
56 | * */
57 | export function getMvPlayUrl (fileid) {
58 | const url = 'https://u.y.qq.com/cgi-bin/musicu.fcg';
59 |
60 | let stringData = {
61 | "getMvUrl": {
62 | "module": "Mv.MvDownloadUrlServer",
63 | "method": "GetMvUrls",
64 | "param": {
65 | "fileid": [`${fileid}`],
66 | "filetype": [-1]
67 | }
68 | }
69 | };
70 |
71 | // assign将所有可枚举属性的值从一个或多个源对象复制到目标对象{}
72 | const data = Object.assign({}, commonParams, {
73 | uin: 0,
74 | ct: 23,
75 | cv: 0,
76 | data: JSON.stringify(stringData)
77 | });
78 |
79 | return jsonp(url, data);
80 | }
81 |
--------------------------------------------------------------------------------
/src/sw-register.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file serviceworker register
3 | */
4 |
5 | // 注册的地址为 sw-precache-webpack-pulgin 生成的 service-worker.js 或者自己手动维护的 service worker 文件
6 | navigator.serviceWorker && navigator.serviceWorker.register('/service-worker.js').then(() => {
7 | navigator.serviceWorker.addEventListener('message', e => {
8 |
9 | // service-worker.js 如果更新成功会 postMessage 给页面,内容为 'sw.update'
10 | if (e.data === 'sw.update') {
11 | // 开发者这自定义处理函数,也可以使用默认提供的用户提示,引导用户刷新
12 | // 这里建议引导用户 reload 处理,详细查看项目具体文件
13 | // location.reload();
14 | location.reload();
15 |
16 | // let dom = document.createElement('div');
17 | // let themeColor = document.querySelector('meta[name=theme-color]');
18 | //
19 | // themeColor && (themeColor.content = '#000');
20 | //
21 | // /* eslint-disable max-len */
22 | // dom.innerHTML = `
23 | //
29 | //
30 | //
31 | //
32 | // 点击刷新
33 | //
34 | //
35 | // `;
36 | // /* eslint-enable max-len */
37 | //
38 | // document.body.appendChild(dom);
39 | // setTimeout(() => document.getElementById('app-refresh').className += ' app-refresh-show', 16);
40 | }
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/src/components/tab-router/tab-router.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{item.name}}
10 |
11 |
12 |
13 |
14 |
15 |
33 |
34 |
94 |
--------------------------------------------------------------------------------
/src/base/search-list/search-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{item}}
7 |
8 | clear
9 |
10 |
11 |
12 |
13 |
14 |
38 |
39 |
83 |
--------------------------------------------------------------------------------
/src/common/js/util.js:
--------------------------------------------------------------------------------
1 | // 设置随机播放列表数据
2 | function getRandomIndex (min, max) {
3 | return Math.floor(Math.random() * (max - min + 1) + min);
4 | }
5 |
6 | export function shuffle (arr) {
7 | // 拷贝一份数据
8 | let _arr = arr.slice();
9 | // 循环洗牌
10 | for (let i = 0; i < _arr.length; i++) {
11 | let randomArr = getRandomIndex(0, i);
12 | let initArr = _arr[i];
13 |
14 | _arr[i] = _arr[randomArr];
15 | _arr[randomArr] = initArr;
16 | }
17 | return _arr;
18 | }
19 |
20 | // 函数防抖
21 | export function debounce (func, delay) {
22 | let timer;
23 |
24 | return function (...args) {
25 | if (timer) {
26 | clearTimeout(timer);
27 | }
28 | timer = setTimeout(() => {
29 | func.apply(this, args);
30 | }, delay);
31 | };
32 | };
33 |
34 | /**
35 | * 计算播放量
36 | * @type {String} playNumber
37 | */
38 | export function computedPlayNumber (playNumber) {
39 | // 如果当前播放量是1万才进行计算
40 | if (playNumber > 1e4) {
41 | playNumber = (playNumber / 1e4).toFixed(1) + '万';
42 | }
43 | return playNumber;
44 | }
45 |
46 |
47 | /**
48 | * 下载文件方法
49 | * @param {String} url 目标文件地址
50 | * @param {String} filename 想要保存的文件名称
51 | */
52 | export function downloadFile (url, filename) {
53 | getBlob(url).then(blob => {
54 | saveAs(blob, filename);
55 | });
56 | }
57 |
58 | /**
59 | * 获取文件 blob
60 | * @param {String} url 目标文件地址
61 | * @return {Promise}
62 | */
63 | function getBlob (url) {
64 | return new Promise(resolve => {
65 | const xhr = new XMLHttpRequest();
66 |
67 | xhr.open('GET', url, true);
68 | xhr.responseType = 'blob';
69 | xhr.onload = () => {
70 | if (xhr.status === 200) {
71 | resolve(xhr.response);
72 | }
73 | };
74 |
75 | xhr.send();
76 | });
77 | }
78 |
79 | /**
80 | * 保存文件
81 | * @param {Blob} blob
82 | * @param {String} filename 想要保存的文件名称
83 | */
84 | function saveAs (blob, filename) {
85 | if (window.navigator.msSaveOrOpenBlob) {
86 | navigator.msSaveBlob(blob, filename);
87 | } else {
88 | const link = document.createElement('a');
89 | const body = document.querySelector('body');
90 |
91 | link.href = window.URL.createObjectURL(blob);
92 | link.download = filename;
93 |
94 | // fix Firefox
95 | link.style.display = 'none';
96 | body.appendChild(link);
97 |
98 | link.click();
99 | body.removeChild(link);
100 |
101 | window.URL.revokeObjectURL(link.href);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/components/ProgressBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
88 |
89 |
103 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "qqmusic",
3 | "version": "1.0.0",
4 | "description": "这是一个 pwa 项目",
5 | "author": "jianzhongmin <282126990@qq.com>",
6 | "private": true,
7 | "scripts": {
8 | "dev": "node build/dev-server.js",
9 | "lint": "fecs ./ --rule --type 'vue,js,css'",
10 | "start": "node build/dev-server.js",
11 | "build": "node build/build.js"
12 | },
13 | "dependencies": {
14 | "axios": "^0.17.1",
15 | "better-scroll": "^1.8.0",
16 | "body-parser": "^1.18.2",
17 | "fastclick": "^1.0.6",
18 | "file-saver": "^1.3.3",
19 | "iscroll": "5.1.0",
20 | "jsonp": "^0.2.1",
21 | "normalize.css": "^7.0.0",
22 | "vue": "^2.3.3",
23 | "vue-awesome": "^2.3.1",
24 | "vue-lazyload": "^1.1.4",
25 | "vue-router": "^2.3.1",
26 | "vuetify": "^0.17.7",
27 | "vuex": "^2.3.1"
28 | },
29 | "devDependencies": {
30 | "autoprefixer": "^6.7.2",
31 | "babel-core": "^6.22.1",
32 | "babel-loader": "^7.1.1",
33 | "babel-plugin-transform-runtime": "^6.22.0",
34 | "babel-polyfill": "^6.23.0",
35 | "babel-preset-env": "^1.3.2",
36 | "babel-preset-stage-2": "^6.22.0",
37 | "babel-register": "^6.22.0",
38 | "bootstrap-loader": "^2.2.0",
39 | "chalk": "^1.1.3",
40 | "connect-history-api-fallback": "^1.3.0",
41 | "copy-webpack-plugin": "^4.0.1",
42 | "create-keyframe-animation": "^0.1.0",
43 | "css-loader": "^0.28.0",
44 | "eventsource-polyfill": "^0.9.6",
45 | "express": "^4.14.1",
46 | "extract-text-webpack-plugin": "^3.0.0",
47 | "fecs": "^1.4.0",
48 | "file-loader": "^0.11.1",
49 | "friendly-errors-webpack-plugin": "^1.1.3",
50 | "good-storage": "^1.0.1",
51 | "html-webpack-plugin": "^2.28.0",
52 | "http-proxy-middleware": "^0.17.3",
53 | "js-base64": "^2.4.3",
54 | "loader-utils": "^1.1.0",
55 | "lyric-parser": "^1.0.1",
56 | "multipage-webpack-plugin": "^0.4.0",
57 | "node-sass": "^4.7.2",
58 | "opn": "^4.0.2",
59 | "optimize-css-assets-webpack-plugin": "^1.3.0",
60 | "ora": "^1.2.0",
61 | "rimraf": "^2.6.0",
62 | "sass-loader": "^6.0.6",
63 | "semver": "^5.3.0",
64 | "shelljs": "^0.7.6",
65 | "stylus": "^0.54.5",
66 | "stylus-loader": "^3.0.1",
67 | "sw-precache": "^5.1.1",
68 | "sw-precache-webpack-plugin": "^0.11.3",
69 | "sw-register-webpack-plugin": "^1.0.6",
70 | "url-loader": "^0.5.8",
71 | "vue-loader": "^12.1.0",
72 | "vue-skeleton-webpack-plugin": "^1.1.4",
73 | "vue-style-loader": "^3.0.1",
74 | "vue-template-compiler": "^2.3.3",
75 | "webpack": "^3.5.4",
76 | "webpack-bundle-analyzer": "^2.2.1",
77 | "webpack-dev-middleware": "^1.10.0",
78 | "webpack-hot-middleware": "^2.18.0",
79 | "webpack-merge": "^4.1.0",
80 | "webpack-node-externals": "^1.6.0",
81 | "vconsole": "^2.5.2"
82 | },
83 | "engines": {
84 | "node": ">= 4.0.0",
85 | "npm": ">= 2.0.0"
86 | },
87 | "browserslist": [
88 | "> 1%",
89 | "last 2 versions",
90 | "not ie <= 8"
91 | ]
92 | }
93 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuetify from 'vuetify';
3 | import VueLazyload from 'vue-lazyload';
4 | import App from './App.vue';
5 | import {createRouter} from './router.js';
6 | import store from './store';
7 | import Icon from 'vue-awesome/components/Icon.vue';
8 | import ProgressBar from '@/components/ProgressBar.vue';
9 |
10 | import '@/assets/styles/global.styl';
11 |
12 | // 全局的进度条,在组件中可通过 $loading 访问
13 | let loading = Vue.prototype.$loading = new Vue(ProgressBar).$mount();
14 | document.body.appendChild(loading.$el);
15 |
16 | // ui组件库
17 | Vue.use(Vuetify);
18 | // 图标库
19 | Vue.component('icon', Icon);
20 |
21 | // 懒加载图片
22 | Vue.use(VueLazyload, {
23 | error: require('../static/img/default.jpg'),
24 | loading: require('../static/img/default.jpg')
25 | });
26 |
27 |
28 | /* eslint-disable no-unused-vars */
29 | // import vConsole from 'vconsole';
30 |
31 | Vue.config.productionTip = false;
32 |
33 | Vue.mixin({
34 |
35 | // 当复用的路由组件参数发生变化时,例如/detail/1 => /detail/2
36 | beforeRouteUpdate(to, from, next) {
37 |
38 | // asyncData方法中包含异步数据请求
39 | let asyncData = this.$options.asyncData;
40 |
41 | if (asyncData) {
42 | loading.start();
43 | asyncData.call(this, {
44 | store: this.$store,
45 | route: to
46 | }).then(() => {
47 | loading.finish();
48 | next();
49 | }).catch(next);
50 | }
51 | else {
52 | next();
53 | }
54 | }
55 | });
56 |
57 | /* eslint-disable no-new */
58 |
59 | export function createApp(routerParams) {
60 | let router = createRouter(routerParams);
61 |
62 | // 此时异步组件已经加载完成
63 | router.beforeResolve((to, from, next) => {
64 | let matched = router.getMatchedComponents(to);
65 | let prevMatched = router.getMatchedComponents(from);
66 |
67 | // [a, b]
68 | // [a, b, c, d]
69 | // => [c, d]
70 | let diffed = false;
71 | let activated = matched.filter((c, i) => {
72 | let ret = diffed || (diffed = (prevMatched[i] !== c));
73 | return ret;
74 | });
75 |
76 | if (!activated.length) {
77 | return next();
78 | }
79 |
80 | loading.start();
81 | Promise.all(activated.map(c => {
82 |
83 | /**
84 | * 两种情况下执行asyncData:
85 | * 1. 非keep-alive组件每次都需要执行
86 | * 2. keep-alive组件首次执行,执行后添加标志
87 | */
88 | if (c.asyncData && (!c.asyncDataFetched || to.meta.notKeepAlive)) {
89 | return c.asyncData.call(c, {
90 | store,
91 | route: to
92 | }).then(() => {
93 | c.asyncDataFetched = true;
94 | });
95 | }
96 | })).then(() => {
97 | loading.finish();
98 | next();
99 | }).catch(next);
100 | });
101 |
102 | const app = new Vue({
103 | router,
104 | store,
105 | ...App
106 | });
107 | return {app, router, store};
108 | }
109 |
--------------------------------------------------------------------------------
/src/components/recent-play/recent-play.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
16 |
18 |
22 |
23 |
24 |
25 |
26 |
55 |
56 |
108 |
--------------------------------------------------------------------------------
/src/components/user-favorite-list/user-favorite-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
16 |
18 |
22 |
23 |
24 |
25 |
26 |
57 |
58 |
110 |
--------------------------------------------------------------------------------
/static/css/reset.css:
--------------------------------------------------------------------------------
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 | font-family: "hiragino sans gb", arial;
25 | }
26 |
27 | input{
28 | text-indent: 0;
29 | background: transparent;
30 | border: 0 none;
31 | resize:none;
32 | outline:none;
33 | -webkit-appearance:none;
34 | line-height: normal;
35 | }
36 |
37 | input, button:focus {
38 | outline: none;
39 | }
40 |
41 | /*禁用 Lavas app 的下拉刷新*/
42 | html {
43 | touch-action: none !important;
44 | }
45 |
46 | /* HTML5 display-role reset for older browsers */
47 | article, aside, details, figcaption, figure,
48 | footer, header, menu, nav, section {
49 | display: block;
50 | }
51 |
52 | body {
53 | line-height: 1;
54 | }
55 |
56 | blockquote, q {
57 | quotes: none;
58 | }
59 |
60 | blockquote:before, blockquote:after,
61 | q:before, q:after {
62 | content: none;
63 | }
64 |
65 | table {
66 | border-collapse: collapse;
67 | border-spacing: 0;
68 | }
69 |
70 | /* custom */
71 | a {
72 | color: #7e8c8d;
73 | text-decoration: none;
74 | -webkit-backface-visibility: hidden;
75 | }
76 |
77 | li {
78 | list-style: none;
79 | }
80 |
81 | ::-webkit-scrollbar {
82 | display: none;
83 | }
84 |
85 | ::-webkit-scrollbar-track-piece {
86 | background-color: rgba(0, 0, 0, 0.2);
87 | -webkit-border-radius: 6px;
88 | }
89 |
90 | ::-webkit-scrollbar-thumb:vertical {
91 | height: 5px;
92 | background-color: rgba(125, 125, 125, 0.7);
93 | -webkit-border-radius: 6px;
94 | }
95 |
96 | ::-webkit-scrollbar-thumb:horizontal {
97 | width: 5px;
98 | background-color: rgba(125, 125, 125, 0.7);
99 | -webkit-border-radius: 6px;
100 | }
101 |
102 | html, body {
103 | width: 100%;
104 | }
105 |
106 | body {
107 | -webkit-text-size-adjust: none;
108 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
109 | }
110 |
111 | /*设置 v-list样式*/
112 | .list__tile {
113 | width: 100% !important;
114 | height: auto !important;
115 | padding: 0 !important;
116 | }
117 |
118 | .list__tile--link:hover, .list__tile--highlighted {
119 | background: none !important;
120 | }
121 |
122 | .btn__content {
123 | padding: 0 !important;
124 | }
125 |
126 | .data-title .title-span {
127 | -webkit-box-orient: vertical;
128 | }
129 |
130 | .music-list-content .album-message .message .album-name {
131 | -webkit-box-orient: vertical;
132 | }
133 |
134 | .introduction .text .txt p{
135 | -webkit-box-orient: vertical;
136 | }
137 |
138 | .application--wrap{
139 | min-height: auto !important;
140 | }
141 |
142 | .input-group{
143 | flex-wrap: initial !important;
144 | }
145 |
--------------------------------------------------------------------------------
/src/pages/songsingle/Songsingle.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
103 |
104 |
119 |
--------------------------------------------------------------------------------
/src/base/search-box/search-box.vue:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 |
74 |
75 |
124 |
--------------------------------------------------------------------------------
/src/store/state.js:
--------------------------------------------------------------------------------
1 | import {isPlayMode} from 'common/js/config';
2 | // 加载所有的收藏歌曲 loadFavorite
3 | // 获取当前播放的歌曲信息 getPlayList
4 | // 获取当前播放索引 getCurrentIndex
5 | // 获取顺序播放列表 getSequenceList
6 | // 获取搜索历史 loadSearchHistory
7 | // 获取播放历史 loadPlayHistory
8 | import {loadFavorite, getPlayList, getCurrentIndex, getSequenceList, loadSearchHistory, loadPlayHistory, } from 'common/js/cache';
9 |
10 | /**
11 | * 状态管理
12 | */
13 | const state = {
14 | /**
15 | * 是否显示搜索层
16 | * @type {Boolean}
17 | */
18 | showSearch: false,
19 | /** *****************歌曲列表状态****************** **/
20 | /**
21 | * 是否显示更多按钮和信息
22 | * @type {Boolean}
23 | */
24 | showMore: [],
25 | /** *****************播放器状态****************** **/
26 | /**
27 | * 歌曲播放模式
28 | * @type {String}
29 | */
30 | playMode: isPlayMode.sequence,
31 | /**
32 | * 顺序播放列表
33 | * @type {Array}
34 | */
35 | sequenceList: getSequenceList(),
36 | /**
37 | * 获取当前收藏列表
38 | * @type {Array}
39 | */
40 | favoriteList: loadFavorite(),
41 | /**
42 | * 播放列表
43 | * @type {Array}
44 | */
45 | playList: getPlayList(),
46 | /**
47 | * 当前播放索引
48 | * @type {Number}
49 | */
50 | currentIndex: getCurrentIndex(),
51 | /**
52 | * 控制歌曲播放
53 | * @type {Boolean}
54 | */
55 | playing: false,
56 | /*
57 | * 控制播发器放大缩小
58 | * @type {Boolean}
59 | * */
60 | fullScreen: false,
61 | // 播放历史 读取缓存的初始值
62 | playHistory: loadPlayHistory(),
63 | /** *****************滚动组件状态****************** **/
64 | /**
65 | * 滚动的状态
66 | * 当 probeType 为 1 的时候,会非实时(屏幕滑动超过一定时间后)派发scroll 事件;
67 | * 当 probeType 为 2 的时候,会在屏幕滑动的过程中实时的派发 scroll 事件;
68 | * 当 probeType 为 3 的时候,不仅在屏幕滑动的过程中,而且在 momentum 滚动动画运行过程中实时派发 scroll 事件。
69 | * @type {Number}
70 | */
71 | probeType: 1,
72 | /**
73 | * 分发点击事件
74 | * @type {Boolean}
75 | */
76 | click: true,
77 | /**
78 | * 滚动组件外部传入的数据
79 | * @type {Array}
80 | */
81 | scrollData: null,
82 | /**
83 | * 设置scroll组件要不要监听滚动事件
84 | * @type {Boolean}
85 | */
86 | listenScroll: false,
87 | /**
88 | * 是否开启滚动到到底部刷新
89 | * @type {Boolean}
90 | */
91 | pullup: false,
92 | /*
93 | * 设置是否开启上拉加载
94 | * @type {Boolean}
95 | * */
96 | pullUpLoad: false,
97 | /**
98 | * 开始滚动
99 | * @type {Boolean}
100 | */
101 | beforeScroll: false,
102 | /**
103 | * 刷新延迟
104 | * @type {Number}
105 | */
106 | refreshDelay: 20,
107 | /**
108 | * 是否开启回弹效果
109 | * @type {Boolean}
110 | */
111 | bounce: false,
112 | /**
113 | * 回弹时间
114 | * @type {Number}
115 | */
116 | bounceTime: 300,
117 | /*********************************************/
118 | /** *****************搜索框组件状态****************** **/
119 | /*
120 | * 获取搜索历史
121 | * @type {Array}
122 | * */
123 | searchHistory: loadSearchHistory(),
124 | /*********************************************/
125 | /** *****************排行榜组件状态****************** **/
126 | /*
127 | * 排行榜歌单Id
128 | * @type {Array}
129 | * */
130 | rankingId: {},
131 | /*********************************************/
132 | /** *****************歌手组件状态****************** **/
133 | /*
134 | * 歌手信息
135 | * @type {Array}
136 | * */
137 | singerMessage: {},
138 | /*********************************************/
139 | /** *****************登录组件状态****************** **/
140 | /*
141 | * 是否显示登录组件
142 | * @type {Boolean}
143 | * */
144 | showLogin: false
145 | /*********************************************/
146 | };
147 |
148 | export default state;
149 |
--------------------------------------------------------------------------------
/src/common/js/totalDigitalAlbum.js:
--------------------------------------------------------------------------------
1 | // 正常专辑数据
2 | class DigitalAlbumList {
3 | constructor ({id, mid, albumName, singerName}) {
4 | this.id = id; // 专辑id
5 | this.mid = mid; // 专辑mid
6 | this.cover = `https://y.gtimg.cn/music/photo_new/T002R300x300M000${mid}.jpg?max_age=2592000`;
7 | this.albumName = albumName; // 专辑名称
8 | this.singerName = singerName; // 歌手名
9 | }
10 | }
11 |
12 | // 其他专辑数据
13 | class OtherDigitalAlbumList {
14 | constructor ({cover, tag, title, composer, commentCount, likeCount}) {
15 | this.cover = cover; // 图片
16 | this.tag = tag; // 标签
17 | this.title = title; // 文字
18 | this.composer = composer; // 歌手
19 | this.commentCount = commentCount; // 评论量
20 | this.likeCount = likeCount; // 点赞量
21 | }
22 | }
23 |
24 | // 其他专辑数据
25 | class DigitalAlbumMusListMessage {
26 | constructor ({cover, albumName, companyname, publictime, desc, songlist}) {
27 | this.cover = cover; // 图片
28 | this.albumName = albumName; // 专辑名称
29 | this.companyname = companyname; // 唱片公司名称
30 | this.publictime = publictime; // 发行时间
31 | this.desc = desc; // 专辑简介文字
32 | this.songlist = songlist; // 专辑歌曲列表
33 | }
34 | }
35 |
36 | // 创建正常数字专辑列表数据
37 | export function createDigitalAlbumList (list) {
38 | let ret = [];
39 |
40 | let items = {};
41 | list.forEach((item) => {
42 | items = (new DigitalAlbumList({
43 | id: item.album_id,
44 | mid: item.album_mid,
45 | albumName: item.album_name,
46 | singerName: filterSinger(item.singers)
47 | }));
48 |
49 | ret.push(items);
50 | });
51 |
52 | return ret;
53 | }
54 |
55 | // 创建其他数字专辑列表数据
56 | export function createOtherDigitalAlbumList (list) {
57 | let ret = [];
58 |
59 | let items = {};
60 | list.forEach((item) => {
61 | items = (new OtherDigitalAlbumList({
62 | cover: item.pic,
63 | tag: item.tag,
64 | title: item.title,
65 | composer: item.composer,
66 | commentCount: item.comment_count,
67 | likeCount: item.like_count
68 | }));
69 |
70 | ret.push(items);
71 | });
72 |
73 | return ret;
74 | }
75 |
76 | // 更多数字专辑
77 | export function createMoreAlbumList (list) {
78 | let ret = [];
79 |
80 | let items = {};
81 | list.forEach((item) => {
82 | items = (new OtherDigitalAlbumList({
83 | cover: item.pic,
84 | tag: item.tag,
85 | title: item.title,
86 | composer: item.composer,
87 | commentCount: item.comment_count,
88 | likeCount: item.like_count
89 | }));
90 |
91 | ret.push(items);
92 | });
93 |
94 | return ret;
95 | }
96 |
97 | // 创建数字专辑基础信息
98 | export function createAlbumSongTableMessage (data) {
99 | return new DigitalAlbumList({
100 | id: data.id,
101 | mid: data.mid,
102 | albumName: data.albumName,
103 | singerName: data.singerName
104 | });
105 | }
106 |
107 | // 创建数字专辑歌曲列表信息
108 | export function createDigitalAlbumSongListMessage (data) {
109 | return new DigitalAlbumMusListMessage({
110 | cover: data.headpiclist[0].picurl, // 图片
111 | albumName: data.album_name, // 专辑名称
112 | companyname: data.companyname, // 唱片公司名称
113 | publictime: data.publictime, // 发行时间
114 | desc: data.desc, // 专辑简介文字
115 | songlist: data.songlist // 专辑歌曲列表
116 | });
117 | }
118 |
119 | /*
120 | * 过滤出singer
121 | * @type {String}
122 | * */
123 | export function filterSinger (singer) {
124 | let ret = [];
125 |
126 | if (!singer) {
127 | return '';
128 | }
129 |
130 | singer.forEach((s) => {
131 | ret.push(s.name);
132 | });
133 |
134 | return ret.join('/');
135 | };
136 |
--------------------------------------------------------------------------------
/src/api/songListPlayUrl.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import {getCookie} from 'common/js/cookie';
3 | import {commonParams} from 'api/config';
4 | import jsonp from 'common/js/jsonp';
5 |
6 | /*
7 | * 获取歌曲列表播放地址
8 | * @param {String}
9 | * */
10 | export function getSongListPlayingUrl (param) {
11 | const url = '/api/getSongListPlayingUrl';
12 |
13 | return axios.post (url,
14 | createdSongListParam (param)
15 | ).then ((res) => {
16 | return Promise.resolve (res.data);
17 | });
18 | }
19 |
20 | /**
21 | * 处理歌曲列表 param
22 | * @param {Object}
23 | */
24 | function createdSongListParam (list) {
25 | let strMediaMid = [];
26 | let songtype = [];
27 | if (!list) {
28 | return;
29 | }
30 | list.forEach ((data) => {
31 | if (data) {
32 | strMediaMid.push (`${data.mid}`);
33 | songtype.push (0);
34 | }
35 | });
36 | return crackedUrl (strMediaMid, songtype);
37 | }
38 |
39 | // 获取歌曲播放地址接口
40 | function crackedUrl (strMediaMid, songtype) {
41 | const comm = Object.assign ({}, commonParams, {
42 | 'uin': 0,
43 | 'platform': 'h5',
44 | 'needNewCode': 1
45 | });
46 |
47 | return {
48 | comm,
49 | 'url_mid': {
50 | 'module': 'vkey.GetVkeyServer',
51 | 'method': 'CgiGetVkey',
52 | 'param': {
53 | 'guid': getCookie ('guid'),
54 | 'songmid': strMediaMid,
55 | 'songtype': songtype,
56 | 'uin': '1020989782',
57 | 'loginflag': 0,
58 | 'platform': '23'
59 | }
60 | }
61 | };
62 | };
63 |
64 | /*
65 | * 获取歌曲单曲播放地址
66 | * songmid // 歌曲mid
67 | * */
68 | // export function getSinglePlayingUrl (songmid) {
69 | // const jsonpCallback = `${"MusicJsonCallback" + (Math.random () + "").replace ("0.", "")}`;
70 | // const url = '/api/getSinglePlayingUrl';
71 | // const data = Object.assign ({}, commonParams, {
72 | // jsonpCallback: jsonpCallback,
73 | // callback: jsonpCallback,
74 | // format: 'json',
75 | // cid: 205361747,
76 | // platform: 'yqq',
77 | // loginUin: 282126990,
78 | // hostUin: 0,
79 | // needNewCode: 0,
80 | // uin: 282126990,
81 | // songmid: songmid,
82 | // filename: `C4001${songmid.slice (1)}.m4a`,
83 | // guid: getCookie('guid')
84 | // });
85 | // return axios.get (url, {
86 | // params: data
87 | // }).then ((res) => {
88 | // return Promise.resolve (res.data);
89 | // });
90 | // }
91 |
92 | export function getSinglePlayingUrl (songmid) {
93 | const url = 'https://c.y.qq.com/base/fcgi-bin/fcg_music_express_mobile3.fcg';
94 | const jsonpCallback = `${"MusicJsonCallback" + (Math.random () + "").replace ("0.", "")}`;
95 |
96 | // assign将所有可枚举属性的值从一个或多个源对象复制到目标对象{}
97 | const data = Object.assign({}, commonParams,{
98 | jsonpCallback: jsonpCallback,
99 | callback: jsonpCallback,
100 | format: 'json',
101 | cid: 205361747,
102 | platform: 'yqq',
103 | loginUin: 1559482835,
104 | hostUin: 0,
105 | needNewCode: 0,
106 | uin: 1559482835,
107 | songmid: songmid,
108 | filename: `C4001${songmid.slice (1)}.m4a`,
109 | guid: getCookie('guid')
110 | });
111 |
112 | return jsonp(url, data);
113 | }
114 |
115 | /*
116 | * 获取歌曲歌词
117 | * songmid // 歌曲mid
118 | * */
119 | export function getLyric (songmid) {
120 | const url = '/api/lyric';
121 |
122 | const data = Object.assign ({}, commonParams, {
123 | songmid: songmid,
124 | platform: 'yqq',
125 | hostUin: 0,
126 | needNewCode: 0,
127 | categoryId: 10000000,
128 | pcachetime: +new Date (),
129 | format: 'json'
130 | });
131 |
132 | return axios.get (url, {
133 | params: data
134 | }).then ((res) => {
135 | return Promise.resolve (res.data);
136 | });
137 | }
138 |
--------------------------------------------------------------------------------
/src/router.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file router
3 | * @author jianzhongmin(282126990@qq.com)
4 | */
5 |
6 | import Vue from 'vue';
7 | import Router from 'vue-router';
8 | import * as types from './store/mutation-types';
9 | import NotFound from '@/pages/NotFound.vue';
10 |
11 | Vue.use(Router);
12 |
13 | // 所有页面使用的路由path,由build/loaders/router-loader收集插入
14 | const allRoutes = [];
15 |
16 | /**
17 | * 根据allRoutes判断当前路由路径是否有效,包括动态路由
18 | *
19 | * @param {string} path 路由路径
20 | * @return {boolean} 是否是有效路由
21 | */
22 | function validateRoute (path) {
23 | return allRoutes.includes(path)
24 | || allRoutes.some(route => {
25 | // 生成路由路径对应的正则表达式 /detail/:id => /^\/detail\/[^\/]+\/?$/
26 | let routeRegex = new RegExp(`^${route.replace(/\/:[^\/]*/g, '/[^\/]+')}\/?$`);
27 | return routeRegex.test(path);
28 | });
29 | }
30 |
31 | export function createRouter ({routes = []}) {
32 |
33 | const router = new Router({
34 | mode: 'history',
35 | base: '/',
36 | routes: [
37 | ...routes,
38 | {
39 | path: '*',
40 | component: NotFound,
41 | beforeEnter(to, from, next) {
42 | if (validateRoute(to.fullPath)) { // 跳转到有效路由
43 | window.location.href = to.fullPath;
44 | return;
45 | }
46 | next(); // 继续展示404页面
47 | }
48 | }
49 | ]
50 | });
51 |
52 | /**
53 | * 切换动画名称
54 | *
55 | * @type {string}
56 | * @const
57 | */
58 | const SLIDE_LEFT = 'slide-left';
59 |
60 | /**
61 | * 切换动画名称
62 | *
63 | * @type {string}
64 | * @const
65 | */
66 | const SLIDE_RIGHT = 'slide-right';
67 |
68 | router.beforeEach((to, from, next) => {
69 | if (router.app.$store) {
70 | // 如果不需要切换动画,直接返回
71 | if (router.app.$store.state.appShell.needPageTransition) {
72 | // 判断当前是前进还是后退,添加不同的动画效果
73 | let pageTransitionName = isForward(to, from) ? SLIDE_LEFT : SLIDE_RIGHT;
74 | router.app.$store.commit(`appShell/${types.SET_PAGE_TRANSITION_NAME}`, {pageTransitionName});
75 | }
76 | }
77 | next();
78 | });
79 |
80 | return router;
81 |
82 | }
83 |
84 | /**
85 | * to 如果在这个列表中,始终采用从左到右的滑动效果,首页比较适合用这种方式
86 | *
87 | * @type {Array.}
88 | * @const
89 | */
90 | const ALWAYS_BACK_PAGE = ['my'];
91 |
92 | /**
93 | * to 如果在这个列表中,始终采用从右到左的滑动效果
94 | *
95 | * @type {Array.}
96 | * @const
97 | */
98 | const ALWAYS_FORWARD_PAGE = ['find', 'songsingle', 'rankingSongList', 'singerDetail'];
99 |
100 | /**
101 | * 历史记录,记录访问过的页面的 fullPath
102 | *
103 | * @type {Array.}
104 | * @const
105 | */
106 | const HISTORY_STACK = ['/home'];
107 |
108 | /**
109 | * 判断当前是否是前进,true 表示是前进,否则是回退
110 | *
111 | * @param {Object} to 目标 route
112 | * @param {Object} from 源 route
113 | * @return {boolean} 是否表示返回
114 | */
115 | function isForward (to, from) {
116 | let res = true;
117 |
118 | // to 如果在这个列表中,始终认为是后退
119 | if (to.name && ALWAYS_BACK_PAGE.indexOf(to.name) !== -1) {
120 |
121 | // 清空历史
122 | HISTORY_STACK.length = 0;
123 | res = false;
124 | }
125 | else if (from.name && ALWAYS_BACK_PAGE.indexOf(from.name) !== -1) {
126 |
127 | // 如果是从 ALWAYS_BACK_PAGE 过来的,那么永远都是前进
128 | HISTORY_STACK.push(to.fullPath);
129 | }
130 | else if (to.name && ALWAYS_FORWARD_PAGE.indexOf(to.name) !== -1) {
131 |
132 | // to 如果在这个列表中,始终认为是前进
133 | HISTORY_STACK.push(to.fullPath);
134 | }
135 | else {
136 |
137 | // 根据 fullPath 判断当前页面是否访问过,如果访问过,则属于返回
138 | let index = HISTORY_STACK.indexOf(to.fullPath);
139 | if (index !== -1) {
140 | HISTORY_STACK.length = index + 1;
141 | res = false;
142 | }
143 | else {
144 |
145 | // 将 to.fullPath 加到栈顶
146 | HISTORY_STACK.push(to.fullPath);
147 | }
148 | }
149 |
150 | return res;
151 | }
152 |
--------------------------------------------------------------------------------
/src/pages/home/router.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file router
3 | * @author jianzhongmin(282126990@qq.com)
4 | */
5 |
6 | // 定义切割点,异步加载路由组
7 | // 主页模块
8 | // let Home = () => import('@/pages/home/home.vue');
9 | // 发现模块
10 | let Find = () => import('./Find.vue');
11 | // 我的模块
12 | let My = () => import('./My.vue');
13 | import Home from '@/pages/home/home.vue';
14 | // import Find from '@/pages/home/Find.vue';
15 | // import My from '@/pages/home/My.vue';
16 |
17 | // 新歌速递模块
18 | import NewSongSpeed from 'components/new-song-speed/new-song-speed.vue';
19 | // let NewSongSpeed = () => import('components/new-song-speed/new-song-speed.vue');
20 |
21 | // 数字专辑音乐列表
22 | import DigitalAlbumMusicList from 'components/digital-album-music-list/digital-album-music-list.vue';
23 | // let DigitalAlbumMusicList = () => import('components/digital-album-music-list/digital-album-music-list.vue');
24 |
25 | // 分类歌单
26 | import CategorySongList from 'components/category-song-list/category-song-list.vue';
27 | // let CategorySongList = () => import('components/category-song-list/category-song-list.vue');
28 |
29 | // 我的喜欢组件
30 | import UserFavoriteList from 'components/user-favorite-list/user-favorite-list.vue';
31 | // let UserFavoriteList = () => import('components/user-favorite-list/user-favorite-list.vue');
32 |
33 | // 最近播放组件
34 | import RecentPlay from 'components/recent-play/recent-play.vue';
35 | // let RecentPlay = () => import('components/recent-play/recent-play.vue');
36 |
37 | // 排行榜组件
38 | import Ranking from 'components/ranking/ranking.vue';
39 | // let Ranking = () => import('components/ranking/ranking.vue');
40 |
41 | // 排行榜歌曲列表组件
42 | import RankingSongList from 'components/rankingSongList/rankingSongList.vue';
43 | // let RankingSongList = () => import('components/rankingSongList/rankingSongList.vue');
44 |
45 | // 歌手组件
46 | import Singer from 'components/singer/singer.vue';
47 | // let Singer = () => import('components/singer/singer.vue');
48 |
49 | // 歌手专辑组件
50 | import SingerDetail from 'components/singer-detail/singer-detail.vue';
51 | // let SingerDetail = () => import('components/singer-detail/singer-detail.vue');
52 |
53 | export default {
54 | routes: [
55 | {
56 | path: '/home',
57 | name: 'home',
58 | component: Home
59 | },
60 | {
61 | path: '/find',
62 | name: 'find',
63 | component: Find,
64 | alias: '/home/find'
65 | },
66 | {
67 | path: '/my',
68 | name: 'my',
69 | component: My,
70 | alias: '/home/my'
71 | },
72 | // 歌手模块
73 | {
74 | path: '/home/singer',
75 | name: 'singer',
76 | component: Singer
77 | },
78 | // 歌手专辑模块
79 | {
80 | path: '/home/singer/:id',
81 | name: 'singerDetail',
82 | component: SingerDetail
83 | },
84 | // 排行榜模块
85 | {
86 | path: '/home/ranking',
87 | name: 'ranking',
88 | component: Ranking,
89 | },
90 | // 排行榜歌曲列表组件
91 | {
92 | path: '/home/ranking/:id',
93 | name: 'rankingSongList',
94 | component: RankingSongList,
95 | },
96 | // 新歌速递模块
97 | {
98 | path: '/home/newSongSpeed',
99 | name: 'newSongSpeed',
100 | component: NewSongSpeed
101 | },
102 | // 数字专辑模块
103 | {
104 | path: '/home/newSongSpeed/digitalAlbum/:id',
105 | name: 'digitalAlbumMusicList',
106 | component: DigitalAlbumMusicList
107 | },
108 | // 分类歌单模块
109 | {
110 | path: '/home/categorySongList',
111 | name: 'categorySongList',
112 | component: CategorySongList,
113 | alias: '/home/homeRecommend'
114 | },
115 | // 我的喜欢模块
116 | {
117 | path: '/my/myFavorite',
118 | name: 'userFavoriteList',
119 | component: UserFavoriteList,
120 | alias: '/home/my/myFavorite'
121 | },
122 | // 最近播放模块
123 | {
124 | path: '/my/playHistory',
125 | name: 'recentPlay',
126 | component: RecentPlay,
127 | alias: '/home/my/playHistory'
128 | }
129 | ]
130 | };
131 |
--------------------------------------------------------------------------------
/src/pages/mv/Mv.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
{{getMvMessage.name}}
12 |
13 |
14 |
15 | perm_identity
16 | {{filterSinger(getMvMessage.singers)}}
17 |
18 |
19 |
20 |
21 |
22 |
104 |
105 |
149 |
--------------------------------------------------------------------------------
/src/base/scroll/scroll.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
127 |
128 |
130 |
--------------------------------------------------------------------------------
/src/store/mutation-types.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file app shell mutation types
3 | * @author jianzhongmin(282126990@qq.com)
4 | */
5 |
6 | export const ENABLE_PAGE_TRANSITION = 'ENABLE_PAGE_TRANSITION';
7 | export const DISABLE_PAGE_TRANSITION = 'DISABLE_PAGE_TRANSITION';
8 | export const SET_PAGE_SWITCHING = 'SET_PAGE_SWITCHING';
9 | export const SET_PAGE_TRANSITION_NAME = 'SET_PAGE_TRANSITION_NAME';
10 | export const SET_APP_HEADER = 'SET_APP_HEADER';
11 | export const SET_SIDEBAR_VISIBILITY = 'SET_SIDEBAR_VISIBILITY';
12 | export const SAVE_SCROLLTOP = 'SAVE_SCROLLTOP';
13 |
14 | // 主页状态
15 | export const SET_HOME_SLIDER = 'SET_HOME_SLIDER'; // 获取主页轮播图
16 | export const SET_HOME_RECOMMEND = 'SET_HOME_RECOMMEND'; // 获取主页热门推荐导航
17 | export const SET_HOME_ALBUM = 'SET_HOME_ALBUM'; // 获取主页最新专辑
18 | export const SET_NEW_MV_LIST = 'SET_NEW_MV_LIST'; // 获取MV列表
19 | export const SET_HOME_NEW_SONG_SPEED = 'SET_HOME_NEW_SONG_SPEED'; // 获取主页热门推荐导航
20 | export const SET_HOME_FEATERED_RADIO = 'SET_HOME_FEATERED_RADIO'; // 获取主页精选电台导航
21 | export const SET_SONG_ALBUM_MESSAGE = 'SET_SONG_ALBUM_MESSAGE'; // 获取歌单专辑信息
22 | export const SET_SHOW_SEARCH = 'SET_SHOW_SEARCH'; // 是否显示搜索框
23 |
24 | // 新歌速递模块状态
25 | export const SET_NEW_SONG_LIST = 'SET_NEW_SONG_LIST'; // 新歌速递模块数据
26 | export const SET_NEW_SONG_LIST_TITLE = 'SET_NEW_SONG_LIST_TITLE'; // 新歌速递模块点击内容标题
27 | export const SET_SWITCH_NEW_SONG_LIST_TITLE = 'SET_SWITCH_NEW_SONG_LIST_TITLE'; // 新歌速递模块点击内容标题 对应type的数据
28 | export const SET_NEW_ALBUM = 'SET_NEW_ALBUM'; // 获取新碟数据
29 | export const SET_MUSIC_DIGITAL_ALBUM = 'SET_MUSIC_DIGITAL_ALBUM'; // 获取全部数字专辑数据 音乐数字专辑相册
30 | export const SET_DIGITAL_MORE_ALBUM = 'SET_DIGITAL_MORE_ALBUM'; // 更多数字专辑数据
31 | export const SET_DIGITAL_ALBUM_SONG_LIST = 'SET_DIGITAL_ALBUM_SONG_LIST'; // 获取数字专辑歌曲列表
32 |
33 | // 精选电台
34 | export const SET_PERSONAL_FEATURED_RADIO_SONG_LIST = 'SET_PERSONAL_FEATURED_RADIO_SONG_LIST'; // 获取个性电台歌曲列表
35 | export const SET_ORDINARY_FEATURED_RADIO_SONG_LIST = 'SET_ORDINARY_FEATURED_RADIO_SONG_LIST'; // 获取普通电台歌曲列表
36 |
37 | // 分类歌单模块
38 | export const SET_CATEGORY_NAVIGATION = 'SET_CATEGORY_NAVIGATION'; // 获取分类歌单导航信息
39 | export const SET_SORT_SONG_DATA = 'SET_SORT_SONG_DATA'; // 获取分类歌单歌曲信息
40 |
41 | // 滚动组件状态
42 | export const SET_PROBETYPE = 'SET_PROBETYPE'; // 分发点击事件
43 | export const SET_CLICK = 'SET_CLICK'; // 分发点击事件
44 | export const SET_SCROLL_DATA = 'SET_SCROLL_DATA'; // 滚动组件外部传入的数据
45 | export const SET_LISTEN_SCROLL = 'SET_LISTEN_SCROLL'; // scroll 要不要监听滚动事件
46 | export const SET_PULLUP = 'SET_PULLUP'; // 是否开启滚动到到底部刷新
47 | export const SET_BEFORE_SCROLL = 'SET_BEFORE_SCROLL'; // 是否开启滚动
48 | export const SET_REFRESH_DELAY = 'SET_REFRESH_DELAY'; // 刷新延迟
49 | export const SET_BOUNCE = 'SET_BOUNCE'; // 是否开启回弹效果
50 | export const SET_BOUNCE_TIME = 'SET_BOUNCE_TIME'; // 回弹时间
51 |
52 | // 播放器组件状态
53 | export const SET_SONG_LIST = 'SET_SONG_LIST'; // 获取歌曲列表
54 | export const SET_PLAYLIST = 'SET_PLAYLIST'; // 获取播放列表
55 | export const SET_SEQUENCE_LIST = 'SET_SEQUENCE_LIST'; // 顺序播放列表
56 | export const SET_CURRENT_INDEX = 'SET_CURRENT_INDEX'; // 当前播放索引
57 | export const SET_PLAYING_STATE = 'SET_PLAYING_STATE'; // 控制歌曲播放
58 | export const SET_FULL_SCREEN = 'SET_FULL_SCREEN'; // 控制播发器放大缩小
59 | export const SET_PLAY_MODE = 'SET_PLAY_MODE'; // 设置歌曲播放模式
60 | export const SET_FAVORITE_LIST = 'SET_FAVORITE_LIST'; // 获取收藏歌曲列表
61 | export const SET_PULLUP_LOAD = 'SET_PULLUP_LOAD'; // 设置是否开启上拉加载
62 | export const SET_PLAY_HISTORY = 'SET_PLAY_HISTORY'; // 保存播放历史
63 |
64 | // 搜索组件状态
65 | export const SET_SEARCH_HOT = 'SET_SEARCH_HOT'; // 热门搜索数据
66 | export const SET_SEARCH_HISTORY = 'SET_SEARCH_HISTORY'; // 设置搜索历史
67 |
68 | // 排行榜组件状态
69 | export const SET_RANKING_LIST = 'SET_RANKING_LIST'; // 获取排行榜数据
70 | export const SET_RANKING_ID = 'SET_RANKING_ID'; // 排行榜歌单Id
71 | export const SET_RANKING_SONG_LIST = 'SET_RANKING_SONG_LIST'; // 排行榜歌单列表
72 |
73 | // 歌手组件状态
74 | export const SET_SINGGER_LIST = 'SET_SINGGER_LIST'; // 获取歌手列表
75 | export const SET_SINGGER_Message = 'SET_SINGGER_Message'; // 获取歌手信息
76 | export const SET_SINGGER_DETAIL = 'SET_SINGGER_DETAIL'; // 获取歌手歌曲信息
77 |
78 | // 歌曲列表状态
79 | export const SET_SHOW_MORE = 'SET_SHOW_MORE'; // 是否显示更多按钮
80 |
81 | // 登录组件状态
82 | export const SET_SHOW_LOGIN = 'SET_SHOW_LOGIN'; // 是否显示登录组件
83 | export const SET_SELECT_USER = 'SET_SELECT_USER'; // 获取用户是否登录成功
84 | export const SET_ADD_USER = 'SET_ADD_USER'; // 获取用户是否注册成功
85 | export const SET_USER_MESSAGE = 'SET_USER_MESSAGE'; // 获取用户信息
86 | export const SET_USER_UID = 'SET_USER_UID'; // 获取该用户的uid判断是否在另一个地方登录
87 |
88 | // MV组件状态
89 | export const SET_MV_MESSAGE = 'SET_MV_MESSAGE'; // 获取对应MV的信息接口
90 | export const SET_MV_PLAY_URL = 'SET_MV_PLAY_URL'; // 获取MV播放地址
91 |
--------------------------------------------------------------------------------
/config/sw-precache.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file sw-precache一些设置项,staticFileGlobs主要是设置具体需要缓存的文件
3 | * 在webpack.prod.conf.js 中被使用
4 | * 可使用参考链接
5 | * https://www.npmjs.com/package/sw-precache-webpack-plugin 具体的配置参数选择
6 | * https://github.com/GoogleChrome/sw-precache#handlefetch-boolean
7 | * https://metaquant.org/programing/sw-precache-guide.html
8 | *
9 | * @author *__ author __*{% if: *__ email __* %}(*__ email __*){% /if %}
10 | */
11 |
12 | module.exports = {
13 |
14 | /**
15 | * build 时的配置文件
16 | *
17 | * @type {Object}
18 | */
19 | build: {
20 | cacheId: 'sw-cache-qqmusic',
21 |
22 | filename: 'service-worker.js',
23 |
24 | /**
25 | * 需缓存的文件配置
26 | * 需动态缓存的放到runtimeCaching中处理
27 | *
28 | * @type {Array}
29 | */
30 | staticFileGlobs: [],
31 |
32 | /**
33 | * [mergeStaticsConfig description]
34 | *
35 | * @type {boolean}
36 | */
37 | mergeStaticsConfig: true,
38 |
39 | /**
40 | * 忽略跳过的文件
41 | *
42 | * @type {Array}
43 | */
44 | staticFileGlobsIgnorePatterns: [
45 | /\.map$/ // map文件不需要缓存
46 | ],
47 |
48 | /**
49 | * 需要省略掉的前缀名
50 | *
51 | * @type {string}
52 | */
53 | stripPrefix: 'dist/',
54 |
55 | /**
56 | * 当请求路径不在缓存里的返回,对于单页应用来说,入口点是一样的
57 | *
58 | * @type {string}
59 | */
60 | navigateFallback: '/index.html',
61 |
62 | /**
63 | * 白名单包含所有的.html (for HTML imports) 和
64 | * 路径中含’/data/’(for dynamically-loaded data).
65 | *
66 | * @type {Array}
67 | */
68 | navigateFallbackWhitelist: [/^(?!.*\.html$|\/data\/).*/],
69 |
70 | /**
71 | * 是否压缩,默认不压缩
72 | *
73 | * @type {boolean}
74 | */
75 | minify: true,
76 |
77 | // maximumFileSizeToCacheInBytes: 4194304, // 最大缓存大小
78 |
79 |
80 | /**
81 | * 生成service-worker.js的文件配置模板,不配置时采用默认的配置
82 | * 我们做了sw的更新策略,所以在原有模板基础做了相应的修改
83 | *
84 | * @type {string}
85 | */
86 | templateFilePath: 'config/sw.tmpl.js',
87 |
88 |
89 | /**
90 | * 是否 verbose
91 | *
92 | * @type {boolean}
93 | */
94 | verbose: true,
95 |
96 |
97 | /**
98 | * 需要根据路由动态处理的文件
99 | *
100 | * @type {Array}
101 | */
102 | runtimeCaching: [
103 | {
104 | urlPattern: /^https:\/\/c\.y\.qq\.com\/qzone/,
105 | handler: 'networkFirst'
106 | },
107 | {
108 | urlPattern: /^https:\/\/c\.y\.qq\.com\/3gmusic/,
109 | handler: 'networkFirst'
110 | },
111 | {
112 | urlPattern: /^https:\/\/c\.y\.qq\.com\/base/,
113 | handler: 'networkFirst'
114 | },
115 | {
116 | urlPattern: /^https:\/\/c\.y\.qq\.com\/lyric/,
117 | handler: 'networkFirst'
118 | },
119 | {
120 | urlPattern: /^https:\/\/c\.y\.qq\.com\/rcmusic2/,
121 | handler: 'networkFirst'
122 | },
123 | {
124 | urlPattern: /^https:\/\/c\.y\.qq\.com\/v8/,
125 | handler: 'networkFirst'
126 | },
127 | {
128 | urlPattern: /^https:\/\/c\.y\.qq\.com\/splcloud/,
129 | handler: 'networkFirst'
130 | },
131 | {
132 | urlPattern: /^https:\/\/c\.y\.qq\.com\/soso/,
133 | handler: 'networkFirst'
134 | },
135 | {
136 | urlPattern: /^https:\/\/u\.y\.qq\.com\/cgi-bin/,
137 | handler: 'networkFirst'
138 | },
139 |
140 | {
141 | urlPattern: /\/material-design-icon/,
142 | // 五种:caheOnly cacheFirst fastest networkFirst networkOnly
143 | handler: 'networkFirst'
144 | }
145 | // ,
146 | // 如果在staticFileGlobs中设置相同的缓存路径,可能导致此处不起作用
147 | // {
148 | // urlPattern: /\/fonts\//,
149 | // handler: 'networkFirst',
150 | // options: {
151 | // cache: {
152 | // maxEntries: 10,
153 | // name: 'fonts-cache'
154 | // }
155 | // }
156 | // }
157 | ]
158 | }
159 | };
160 |
--------------------------------------------------------------------------------
/src/base/video/video.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 |
18 | {{playing ? 'pause' : 'play_arrow'}}
19 |
20 |
21 |
22 |
23 |
24 |
119 |
120 |
156 |
--------------------------------------------------------------------------------
/src/api/newSongSpeed.js:
--------------------------------------------------------------------------------
1 | import jsonp from 'common/js/jsonp';
2 | import {commonParams, options} from 'api/config';
3 | import axios from 'axios';
4 |
5 | /*
6 | * 新歌速递模块点击内容标题对应type的数据
7 | * type // 标题类型
8 | * */
9 | export function getSwitchNewSongList(type) {
10 | const url = 'https://u.y.qq.com/cgi-bin/musicu.fcg';
11 |
12 | let stringData = {
13 | 'comm': {'ct': 24},
14 | 'new_song': {
15 | 'module': 'QQMusic.MusichallServer',
16 | 'method': 'GetNewSong',
17 | 'param': {
18 | 'type': type
19 | }
20 | }
21 | };
22 |
23 | // assign将所有可枚举属性的值从一个或多个源对象复制到目标对象{}
24 | const data = Object.assign({}, commonParams, {
25 | loginUin: 0,
26 | hostUin: 0,
27 | needNewCode: 0,
28 | platform: 'yqq',
29 | data: JSON.stringify(stringData)
30 | });
31 |
32 | return jsonp(url, data);
33 | }
34 |
35 | /*
36 | * 新碟接口
37 | * @param area id
38 | * sin 页数
39 | * */
40 | export function getNewAlbum(param) {
41 | const url = 'https://u.y.qq.com/cgi-bin/musicu.fcg';
42 |
43 | const _param = Object.assign({}, param, {
44 | 'company': -1,
45 | 'genre': -1,
46 | 'type': -1,
47 | 'year': -1,
48 | 'sort': 2,
49 | 'get_tags': 1,
50 | 'num': 20,
51 | 'click_albumid': 0
52 | });
53 |
54 | const data = {
55 | 'albumlib': {
56 | 'method': 'get_album_by_tags',
57 | 'param': _param,
58 | 'module': 'music.web_album_library'
59 | }
60 | };
61 |
62 | // assign将所有可枚举属性的值从一个或多个源对象复制到目标对象{}
63 | const message = Object.assign({}, commonParams, {
64 | loginUin: 0,
65 | hostUin: 0,
66 | platform: 'yqq',
67 | needNewCode: 0,
68 | data: JSON.stringify(data)
69 | });
70 |
71 | return jsonp(url, message);
72 | }
73 |
74 | /*
75 | * 新碟专辑歌曲列表接口
76 | * @param albummid = mid
77 | * */
78 | export function getNewAlbumSongList(albummid) {
79 | const url = 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_album_info_cp.fcg';
80 |
81 | // assign将所有可枚举属性的值从一个或多个源对象复制到目标对象{}
82 | const message = Object.assign({}, commonParams, {
83 | callback: 'albuminfoCallback',
84 | albummid: albummid,
85 | loginUin: 0,
86 | hostUin: 0,
87 | platform: 'yqq',
88 | needNewCode: 0
89 | });
90 |
91 | return jsonp(url, message, options);
92 | }
93 |
94 | /**
95 | * 获取全部数字专辑数据
96 | * */
97 | export function getTotalDigitalAlbum () {
98 | const url = '/api/getTotalDigitalAlbum';
99 |
100 | const comm = Object.assign({}, commonParams, {
101 | 'uin': 0,
102 | 'platform': 'h5',
103 | 'needNewCode': 1
104 | });
105 | const MusicHallDigitalAlbum = Object.assign({}, {
106 | 'module': 'mall.AlbumPgcMgrServer',
107 | 'method': 'GetMallIndexPage',
108 | 'param': {}
109 | });
110 | const moreAlbum = Object.assign({}, {
111 | 'module': 'mall.AlbumPgcMgrServer',
112 | 'method': 'GetMoreAlbums',
113 | 'param': {
114 | 'start': 0,
115 | 'count': 9
116 | }
117 | });
118 | let data = {
119 | comm,
120 | MusicHallDigitalAlbum,
121 | moreAlbum
122 | };
123 |
124 | return axios.post(url, data).then((res) => {
125 | return Promise.resolve(res.data);
126 | });
127 | }
128 |
129 | /*
130 | * 获取更多数字专辑数据
131 | * start // 开始位置
132 | * */
133 | export function getMoreAlbumList (start) {
134 | const url = '/api/getMoreAlbumList';
135 |
136 | const comm = Object.assign({}, commonParams, {
137 | 'uin': 0,
138 | 'platform': 'h5',
139 | 'needNewCode': 1
140 | });
141 | const moreAlbum = Object.assign({}, {
142 | 'module': 'mall.AlbumPgcMgrServer',
143 | 'method': 'GetMoreAlbums',
144 | 'param': {
145 | 'start': start,
146 | 'count': 9
147 | }
148 | });
149 |
150 | let data = {
151 | comm,
152 | moreAlbum
153 | };
154 |
155 | return axios.post(url, data).then((res) => {
156 | return Promise.resolve(res.data);
157 | });
158 | }
159 |
160 | /*
161 | * 获取数字专辑歌曲列表
162 | * id // 数字专辑id
163 | * */
164 | export function getDigitalAlbumSongList (id) {
165 | const url = 'https://c.y.qq.com/v8/fcg-bin/musicmall.fcg';
166 |
167 | // assign将所有可枚举属性的值从一个或多个源对象复制到目标对象{}
168 | const data = Object.assign({}, commonParams, {
169 | uin: 0,
170 | platform: 'h5',
171 | needNewCode: 1,
172 | albumid: id,
173 | cmd: 'get_base_sale_info',
174 | songlist: 1,
175 | desc: 1,
176 | singerinfo: 1,
177 | salecount: 1
178 | });
179 |
180 | return jsonp(url, data, options);
181 | }
182 |
--------------------------------------------------------------------------------
/src/base/songListPlayAll/songListPlayAll.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | play_circle_outline
7 |
全部播放
8 | 共{{totalSongNum}}首
9 |
10 |
11 |
12 |
13 | 下载
14 |
15 |
16 |
17 |
18 | 多选
19 |
20 |
21 |
22 |
23 |
79 |
80 |
153 |
--------------------------------------------------------------------------------
/src/common/js/home.js:
--------------------------------------------------------------------------------
1 | class HomeNewSongSpeed {
2 | constructor({status, name, id, albumName, singerName}) {
3 | this.status = status;
4 | this.name = name;
5 | this.id = id; // 专辑id
6 | this.cover = `https://y.gtimg.cn/music/photo_new/T002R300x300M000${id}.jpg?max_age=2592000`;
7 | this.albumName = albumName; // 专辑名称
8 | this.singerName = singerName; // 歌手名
9 | this.title = this.albumName + ' - ' + this.singerName;
10 | }
11 | };
12 |
13 | // 主页新歌速递模块数据
14 | export function createHomeNewSongSpeed(newSong, newAlbum) {
15 | let ret = [];
16 | let items = {};
17 |
18 | newSong.forEach((item) => {
19 |
20 | items = (new HomeNewSongSpeed({
21 | status: '歌单推荐',
22 | name: 'newSongSpeed',
23 | id: item.album.mid,
24 | albumName: item.album.name,
25 | singerName: filterSinger(item.singer)
26 | }));
27 |
28 | ret.push(items);
29 | });
30 |
31 | newAlbum.forEach((item) => {
32 |
33 | items = (new HomeNewSongSpeed({
34 | status: '数字专辑',
35 | name: 'digitalAlbum',
36 | id: item.album.mid,
37 | albumName: item.album.name,
38 | singerName: filterSinger(item.author)
39 | }));
40 |
41 | ret.push(items);
42 | });
43 |
44 | return ret;
45 | }
46 |
47 | /*
48 | * singer是一个数组 过滤出singer的name
49 | * @type {String}
50 | * */
51 | export function filterSinger(singer) {
52 | let ret = [];
53 |
54 | if (!singer) {
55 | return '';
56 | }
57 |
58 | singer.forEach((s) => {
59 | ret.push(s.name);
60 | });
61 |
62 | return ret.join('/');
63 | }
64 |
65 |
66 | // 创建主页歌单推荐刷新后的数据
67 | class ReplaceHomeRecomPlaylist {
68 | constructor({album_pic_mid, content_id, cover, creator, edge_mark, listen_num, pic_mid, rcmdcontent, rcmdtemplate, title, username}) {
69 | this.album_pic_mid = album_pic_mid;
70 | this.content_id = content_id;
71 | this.cover = cover;
72 | this.creator = creator;
73 | this.edge_mark = '';
74 | this.listen_num = listen_num;
75 | this.pic_mid = pic_mid;
76 | this.rcmdcontent = rcmdcontent;
77 | this.rcmdtemplate = rcmdtemplate;
78 | this.title = title;
79 | this.username = username;
80 | }
81 | };
82 |
83 | // 歌单推荐刷新后的数据
84 | export function createReplaceHomeRecomPlaylist(list) {
85 | let ret = [];
86 | let items = {};
87 |
88 | list.forEach((item) => {
89 | items = (new ReplaceHomeRecomPlaylist({
90 | album_pic_mid: item.album_pic_mid,
91 | content_id: item.tid,
92 | cover: item.cover_url_medium,
93 | creator: item.creator_uin,
94 | listen_num: item.access_num,
95 | pic_mid: item.pic_mid,
96 | rcmdcontent: `编辑推荐:${item.title}`,
97 | rcmdtemplate: "编辑推荐",
98 | title: item.title,
99 | username: item.creator_info.nick
100 | }));
101 |
102 | ret.push(items);
103 | });
104 |
105 | return ret;
106 | }
107 |
108 | // 最新专辑数据
109 | class NewAlbum {
110 | constructor({dissid, mid, title, singerName, publicTime, start}) {
111 | this.dissid = dissid; // 专辑id
112 | this.mid = mid; // 专辑mid
113 | this.cover = `https://y.gtimg.cn/music/photo_new/T002R300x300M000${mid}.jpg?max_age=2592000`;
114 | this.title = title; // 专辑名称
115 | this.singerName = singerName; // 歌手名,
116 | this.publicTime = publicTime; // 发行时间
117 | this.start = start;
118 | }
119 | }
120 |
121 | /**
122 | * 对最新专辑list数据做处理
123 | * @type {Array} list
124 | */
125 | export function createHomeNewAlbumList(list) {
126 | let ret = [];
127 |
128 | let items = {};
129 |
130 | list.forEach((item) => {
131 | items = (new NewAlbum({
132 | dissid: item.album.id,
133 | mid: item.album.mid,
134 | title: item.album.title,
135 | singerName: filterSinger(item.author),
136 | publicTime: item.album.time_public,
137 | start: 'newAlbum'
138 | }));
139 |
140 | ret.push(items);
141 | });
142 |
143 | return ret;
144 | }
145 |
146 |
147 |
148 | // MV列表数据
149 | class MV {
150 | constructor({vid, listen_num, cover,title, mvdesc, publicTime, start}) {
151 | this.vid = vid; // vid
152 | this.listen_num = listen_num; // 播放量
153 | this.cover = cover; // 图片
154 | this.title = title; // 专辑名称
155 | this.mvdesc = mvdesc; // mvdesc,
156 | this.publicTime = publicTime; // 发行时间
157 | this.start = start;
158 | }
159 | }
160 |
161 | /**
162 | * 对MV列表数据做处理
163 | * @type {Array} list
164 | */
165 | export function createHomeMVList(list) {
166 | let ret = [];
167 |
168 | let items = {};
169 |
170 | list.forEach((item) => {
171 | items = (new MV({
172 | vid: item.vid,
173 | listen_num: item.listennum,
174 | cover: item.picurl,
175 | title: item.mvtitle,
176 | mvdesc: item.mvdesc,
177 | publicTime: item.pub_date,
178 | start: 'mv'
179 | }));
180 |
181 | ret.push(items);
182 | });
183 |
184 | return ret;
185 | }
186 |
--------------------------------------------------------------------------------
/src/base/header-scroll/header-scroll.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
19 |
20 |
103 |
104 |
105 |
111 |
112 |
162 |
--------------------------------------------------------------------------------
/src/base/confirm/confirm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
提示
9 |
10 |
{{text}}
11 |
12 |
13 |
{{exitText}}
15 |
{{endBtnText}}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
80 |
81 |
186 |
--------------------------------------------------------------------------------
/src/store/mutations.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 更改状态 同步操作
3 | */
4 | import * as types from './mutation-types';
5 |
6 | const mutations = {
7 | /**
8 | * 设置是否显示搜索框
9 | * @type {Array}
10 | */
11 | [types.SET_SHOW_SEARCH](state, showSearch) {
12 | state.showSearch = showSearch;
13 | },
14 | /**
15 | * 是否显示更多按钮
16 | * @type {Array}
17 | */
18 | [types.SET_SHOW_MORE](state, showMore) {
19 | state.showMore = showMore;
20 | },
21 | /** *************播放组件状态********************** **/
22 | /**
23 | * 歌曲列表
24 | * @type {Array}
25 | */
26 | [types.SET_SONG_LIST](state, songList) {
27 | state.songList = songList;
28 | },
29 | /**
30 | * 顺序播放列表
31 | * @type {Array}
32 | */
33 | [types.SET_SEQUENCE_LIST](state, list) {
34 | state.sequenceList = list;
35 | },
36 | /**
37 | * 获取播放列表
38 | * @type {Array}
39 | */
40 | [types.SET_PLAYLIST](state, list) {
41 | state.playList = list;
42 | },
43 | /**
44 | * 当前播放索引
45 | * @type {Number}
46 | */
47 | [types.SET_CURRENT_INDEX](state, index) {
48 | state.currentIndex = index;
49 | },
50 | /**
51 | * 控制歌曲播放
52 | * @type {Boolean}
53 | */
54 | [types.SET_PLAYING_STATE](state, flag) {
55 | state.playing = flag;
56 | },
57 | /*
58 | * 控制播发器放大缩小
59 | * @type {Boolean}
60 | * */
61 | [types.SET_FULL_SCREEN](state, flag) {
62 | state.fullScreen = flag;
63 | },
64 | /**
65 | * 获取收藏歌曲列表
66 | * @type {Array}
67 | */
68 | [types.SET_FAVORITE_LIST](state, list) {
69 | state.favoriteList = list;
70 | },
71 | /*
72 | * 设置歌曲播放模式
73 | * @type {String}
74 | * */
75 | [types.SET_PLAY_MODE](state, playMode) {
76 | state.playMode = playMode;
77 | },
78 | /*
79 | * 播放历史
80 | * @type {String}
81 | * */
82 | [types.SET_PLAY_HISTORY](state, history) {
83 | state.playHistory = history
84 | },
85 | /*********************************************************/
86 | /** *************滚动组件的状态********************** **/
87 | /**
88 | * 滚动的状态
89 | * 当 probeType 为 1 的时候,会非实时(屏幕滑动超过一定时间后)派发scroll 事件;
90 | * 当 probeType 为 2 的时候,会在屏幕滑动的过程中实时的派发 scroll 事件;
91 | * 当 probeType 为 3 的时候,不仅在屏幕滑动的过程中,而且在 momentum 滚动动画运行过程中实时派发 scroll 事件。
92 | * @type {Number}
93 | */
94 | [types.SET_PROBETYPE](state, probeType) {
95 | state.probeType = probeType;
96 | },
97 | /**
98 | * 分发点击事件
99 | * @type {Boolean}
100 | */
101 | [types.SET_CLICK](state, click) {
102 | state.click = click;
103 | },
104 | /**
105 | * 外部传入的数据
106 | * @type {Array}
107 | */
108 | [types.SET_SCROLL_DATA](state, scrollData) {
109 | state.scrollData = scrollData;
110 | },
111 | /**
112 | * scroll 要不要监听滚动事件
113 | * @type {Boolean}
114 | */
115 | [types.SET_LISTEN_SCROLL](state, listenScroll) {
116 | state.listenScroll = listenScroll;
117 | },
118 | /**
119 | * 是否开启滚动到到底部刷新
120 | * @type {Boolean}
121 | */
122 | [types.SET_PULLUP](state, pullup) {
123 | state.pullup = pullup;
124 | },
125 | /*
126 | * 设置是否开启上拉加载
127 | * @type {Boolean}
128 | * */
129 | [types.SET_PULLUP_LOAD](state, pullUpLoad) {
130 | state.pullUpLoad = pullUpLoad;
131 | },
132 | /**
133 | * 开始滚动
134 | * @type {Boolean}
135 | */
136 | [types.SET_BEFORE_SCROLL](state, beforeScroll) {
137 | state.beforeScroll = beforeScroll;
138 | },
139 | /**
140 | * 刷新延迟
141 | * @type {Number}
142 | */
143 | [types.SET_REFRESH_DELAY](state, refreshDelay) {
144 | state.refreshDelay = refreshDelay;
145 | },
146 | /**
147 | * 是否开启回弹效果
148 | * @type {Boolean}
149 | */
150 | [types.SET_BOUNCE](state, bounce) {
151 | state.bounce = bounce;
152 | },
153 | /**
154 | * 回弹时间
155 | * @type {Number}
156 | */
157 | [types.SET_BOUNCE_TIME](state, bounceTime) {
158 | state.bounceTime = bounceTime;
159 | },
160 | /*********************************************************/
161 | /** *************搜索框组件状态********************** **/
162 | /**
163 | * 获取搜索历史
164 | * @type {Array}
165 | */
166 | [types.SET_SEARCH_HISTORY](state, history) {
167 | state.searchHistory = history
168 | },
169 | /*********************************************************/
170 | /** *************排行榜组件状态********************** **/
171 | /**
172 | * 排行榜歌单
173 | * @type {Array}
174 | */
175 | [types.SET_RANKING_ID](state, data) {
176 | state.rankingId = data
177 | },
178 | /*********************************************************/
179 | /** *************歌手组件状态********************** **/
180 | /**
181 | * 歌手信息
182 | * @type {Object}
183 | */
184 | [types.SET_SINGGER_Message](state, singerMessage) {
185 | state.singerMessage = singerMessage
186 | },
187 | /*********************************************************/
188 | /** *************登录组件组件状态********************** **/
189 | /**
190 | * 是否显示登录组件
191 | * @type {Array}
192 | */
193 | [types.SET_SHOW_LOGIN](state, flag) {
194 | state.showLogin = flag
195 | }
196 | /*********************************************************/
197 | };
198 |
199 | export default mutations;
200 |
--------------------------------------------------------------------------------
/src/assets/sass/variables.scss:
--------------------------------------------------------------------------------
1 | // AppHeader.vue
2 | $btn-color: #fff; // 头部按钮颜色
3 | $app-header-height: 88px; // 头部导航的高度
4 | $app-header-bgcolor: #c52d61; // 头部背景颜色 默认颜色 rgba(49, 194, 124, 0.95)
5 | $header-li-color: rgba(255, 255, 255, 0.8); // 头部文字颜色
6 |
7 | // search.vue
8 | $search-font-color: #fff; // 搜索框字体颜色
9 | $search-wrapper-bgcolor: #c52d61; // 搜索框背景颜色 默认颜色 rgba(49, 194, 124, 0.95)
10 |
11 | // homeSlider.vue
12 | $dotsBgColor: rgba(20, 23, 35, 0.95); // 轮播点背景颜色
13 | $dotActiveBgColor-color: #c52d61; // 轮播点激活时的颜色
14 |
15 | // tab-router.vue
16 | $textColor: #000; // icon标题颜色
17 | $IconColor: #c52d61; // icon 颜色
18 | $tab-router-bgcolor: #fff; // 导航块背景颜色 默认颜色 rgba(20, 22, 34, 0.95)
19 |
20 | // home.vue
21 | $list-title: #000; // 菜单列表标题颜色
22 | $list-data-title: #fff; // 新歌速递文字颜色
23 | $content-bgcolor: #fff; // 内容背景颜色 rgba(13, 12, 18, 0.95)
24 |
25 | // 图标默认颜色
26 | $icon-color: #fff;
27 |
28 | // mask-layer.vue 遮罩层背景色
29 | $mask-layer-bgcolor: rgba(0, 0, 0, .1);
30 |
31 | // music-list.vue
32 | $name-color: #fff; // 歌曲列表颜色
33 | $icon-fanhui1-copy: #fff; // 返回键颜色
34 | $title-color: #fff; // 标题颜色
35 | $list-bgcolor: #fff; // 歌曲列表背景颜色
36 | $error: rgba(246, 246, 246, 0.95); // 没有数据的时候文字显示的颜色
37 | $music-list-bgcolor: rgba(19, 21, 33, 1); // 外层背景颜色linear-gradient(to bottom, rgba(19, 21, 33, 1) 70%, #fff 40%)
38 | $filter-bgcolor: rgba(0, 0, 0, 1); // 模糊效果背景颜色
39 | $play-number-bgcolor: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(0, 0, 0, 0.4) 100%); // 播放量背景样式
40 |
41 | // song-list.vue
42 | $total-number-color: grey; // 总歌曲数量的字体颜色
43 | $play-color: #c52d61; // 播放按钮颜色
44 | $content-title-color: #000; // 内容标题颜色
45 | $content-text-color: grey; // 内容文字颜色
46 | $more-color: #b6b6b6; // 更多 icon颜色
47 | $none-more: #fff; // 没有更多背景颜色
48 |
49 | // loading.vue
50 | $bg-color: rgba(246, 204, 23, 0.95); // 背景颜色
51 | $audiotrack-color: #31c27c; // 音乐图标颜色
52 | $green-color: #31c27c; // 旋转圆圈颜色
53 |
54 | // player.vue
55 | $mini-player-color: #fff; //播放器最小化时的背景颜色 rgba(18, 21, 33, 0.95)
56 | $normal-player-bgcolor: #0e0d13; // 放大的播放器的背景颜色
57 | $text-name-color: #000; // 歌曲标题颜色
58 | $text-desc-color: #777; // 歌手颜色
59 | $play-color: #c52d61; // 播放按钮图标颜色
60 | $playing-lyric-color: #c52d61; // 小歌词默认颜色
61 | $no-play-list-color: #000; // 没有歌曲列表时显示
62 | $sing-name-color: #fff; // 歌手名颜色
63 | $time-color: #fff; // 播放时间颜色
64 | $lyric-text-color: rgba(255, 255, 255, 0.5); // 歌词颜色
65 | $lyric-text-current-color: #c52d61; // 歌词行激活的颜色
66 | $favorite_border: #9d9d9d; // 没有收藏按钮
67 | $favorite: #f06868; // 收藏按钮
68 | $kip-previous: #c52d61; // 回到上一个
69 | $skip-next: #c52d61; // 下一个
70 | $mode-color: rgba(157, 157, 157, 1); // 切换播放模式按钮颜色
71 |
72 | // playlist.vue
73 | $playlist-bgcolor: rgba(0, 0, 0, .3); // 播放列表外层背景颜色
74 | $list-wrapper-bgcolor: rgba(24, 30, 39, 0.7); // 内容背景颜色
75 | $list-header-bgcolor: #1b2129; // 列表头部背景颜色
76 | $list-header-play-mode: #fff; // 播放模式icon颜色
77 | $list-header-text: #fff; // 文本文字颜色
78 | $list-header-text-numer: #999; // 文本歌曲数量颜色
79 | $list-header-icon: #999; // 列表头部icon颜色
80 | $list-content-bgcolor: rgba(26, 31, 40, 0.9); // 列表内容背景颜色
81 | $list-close-bgcolor: rgba(26, 31, 40, 0.4); // 关闭按钮背景颜色
82 | $list-close-color: #fff; // 关闭按钮文字颜色
83 | $list-content-name-color: #fff; // 歌曲名字颜色
84 | $list-content-name-active-color: #c52d61; // 当前正在播放的歌曲的文字颜色
85 |
86 | //progress-bar.vue
87 | $bar-inner-color: #898989; // 播放器进度条颜色
88 | $progress-color: #c52d61; // 进度条进度颜色
89 |
90 | // category-song-list.vue
91 | $hot-recommend-bg-color: #fff; // 背景颜色 1e2326
92 | $title-color: #fff; // 标题颜色
93 | $hot-songs-title-color: #000; // hot-songs字体颜色
94 | $header-color: #c52d61; // 标题颜色 1b2023
95 | $look-more-color: #c52d61; // 查看更多字体颜色
96 | $content-name-color: #000; // 歌单标题
97 | $user-name-color: #999; // 发布人名字颜色
98 | $chosen-heade-select: #c52d61; // 精选歌单头部选择颜色
99 |
100 | // homeSlider-switch.vue
101 | $slider-switch-dots-bg: #fff; // 滑动切换头部的背景 1b2023
102 | $slider-switch-name-color: #000; // 标题颜色
103 | $dots-bg: #c52d61; // dots颜色
104 |
105 | // new-song-speed.vue
106 | $song-speed-header-color: #c52d61; // 歌单速递路由头部标题字体颜色
107 | $song-speed-header-router-link-active-color: #fff; // 歌单速递路由
108 |
109 | // new-song.vue
110 | $new-song-header-active-color: #c52d61; // 新歌头部导航激活颜色
111 |
112 | // digital-album.vue
113 | $list-text2-color: #808080; // 专辑歌手名颜色
114 |
115 | // digital-album-list.vue
116 | $special-topic-logo: #a4721b; // 数字专辑专题栏目 logo颜色
117 | $comment-like-color: #808080; // 评论和点赞数量颜色
118 |
119 | // digital-album-music-list.vue
120 | $digital-album-music-list-content: #222222; // 数字专辑歌曲列表背景颜色
121 | $digital-album-music-list-content-text: #fff; // 数字专辑歌曲列表文字颜色
122 | $digital-album-music-list-content-text-active: #ffce6b; // 数字专辑歌曲列表文字颜色
123 |
124 | // my.vue
125 | $router-nav-icon-color: #c52d61; // 我的页面导航icon颜色
126 | $customize-song-list-header: #999; // 用户歌单头部导航字体颜色
127 | $customize-text-color: #000; // 用户歌单字体颜色
128 | $created-song-list-icon-color: #4e4e4e; // 创建歌单按钮颜色
129 | // Categoryzone.vue
130 | $categoryzone-header-color: rgba(0, 0, 0, .1);
131 |
132 | // search.vue
133 | $search-box-wrapper-bgcolor: #f3f3f3; // 搜索层背景颜色
134 | $search-box-header-bgcolor: #c52d61; // 搜索框背景颜色
135 | $search-result-bgcolor: #F4F4F4;// 搜索结果背景颜色
136 | $search-prompt-bgcolor: #fff;// 搜索提示背景颜色
137 | $search-prompt-color: #c52d61;// 搜索提示字体颜色
138 | $hot-key-color: #999;// 热门搜索字体颜色
139 |
140 | // ranking.vue
141 | $ranking-content-bgcolor: #F3F3F3; // 排行榜背景颜色
142 |
143 | // singer.vue
144 | $singer-list-title: #818181; // 歌手标题颜色
145 |
146 | // more-button.vue
147 | $more-bottom-bgcolor: #e3e3e3; // 更多按钮背景颜色
148 |
149 | // login.vue
150 | $weChat-login-bgcolor: #c52d61; // 微信登录按钮背景颜色
151 | $qq-login-bgcolor: #02a7e4; // qq登录按钮背景颜色
152 |
--------------------------------------------------------------------------------
/src/base/progress-bar/progress-bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
20 |
121 |
122 |
166 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
12 |
13 |
14 |
20 |
21 |
28 |
29 |
30 |
36 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
111 |
112 |
187 |
--------------------------------------------------------------------------------
/src/base/login-input/login-input.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
44 |
45 |
46 |
47 |
108 |
109 |
212 |
--------------------------------------------------------------------------------
/server/api/userApi.js:
--------------------------------------------------------------------------------
1 | const models = require('../db');
2 | const express = require('express');
3 | const router = express.Router();
4 | const mysql = require('mysql');
5 | const uuid = require('node-uuid');
6 | const sql = require('../sqlMap.js');
7 |
8 | // 链接数据库
9 | // 使用createConnection创建链接
10 | const conn = mysql.createConnection(models.mysql);
11 |
12 | // let jsonWrite = function (res, ret) {
13 | // if (typeof ret === 'undefined') {
14 | // res.send({
15 | // code: -1
16 | // });
17 | // }
18 | // else {
19 | // res.send({
20 | // code: 0
21 | // });
22 | // }
23 | // };
24 |
25 | // 注册用户接口
26 | router.post('/addUser', (req, res) => {
27 | // 查找用户名
28 | const sql_name = sql.user.select_name;
29 | // 添加用户
30 | const sql_add = sql.user.add;
31 | const params = req.body;
32 |
33 | conn.query(sql_name, params.username, function (err, result) {
34 | if (err) {
35 | console.log(err);
36 | }
37 | if (result[0] === undefined) {
38 | conn.query(sql_add, [params.username, params.password], function (err, result) {
39 | if (err) {
40 | res.send({
41 | code: -1
42 | }); // //查询不出username,data返回-1
43 | }
44 | else {
45 | // 生成UID
46 | let uid = uuid.v1();
47 | uid = uid.replace(/\-/g, '');
48 |
49 | // 生成登录时间
50 | let date = new Date();
51 | // 生成当前时间
52 | let time = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`;
53 |
54 | // 添加uid
55 | conn.query(`update userData set uid = '${uid}' where username = '${params.username}'`);
56 |
57 | // 更新登录时间
58 | conn.query(`update userData set loginTime = '${time}' where username = '${params.username}'`);
59 |
60 | // 添加用户完成查找该用户
61 | conn.query(sql_name, params.username, function (err, result) {
62 | if (err) {
63 | console.log(err);
64 | }
65 | else {
66 | res.send({
67 | code: 0,
68 | data: result[0]
69 | });
70 | }
71 | });
72 | }
73 | })
74 | }
75 | else {
76 | res.send({
77 | code: -1
78 | }); //当前注册username与数据库重复时,data返回-1
79 | }
80 | });
81 | });
82 |
83 |
84 | // 用户登录接口
85 | router.post('/selectUser', (req, res) => {
86 | // 查找用户名
87 | const sql_name = sql.user.select_name;
88 | // 查找用户密码
89 | const sql_password = sql.user.select_password;
90 |
91 | const params = req.body;
92 |
93 | conn.query(sql_name, params.username, function (err, result) {
94 | if (err) {
95 | console.log(err);
96 | }
97 | if (result[0] === undefined) {
98 | res.send({
99 | code: -1
100 | }); // //查询不出username,data返回-1
101 | }
102 | else {
103 | // 生成UID
104 | let uid = uuid.v1();
105 | uid = uid.replace(/\-/g, '');
106 |
107 | // 生成登录时间
108 | let date = new Date();
109 | // 生成当前时间
110 | let time = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`;
111 |
112 | // 更新uid
113 | conn.query(`update userData set uid = '${uid}' where username = '${params.username}'`);
114 |
115 | // 更新登录时间
116 | conn.query(`update userData set loginTime = '${time}' where username = '${params.username}'`);
117 |
118 | // 用户登录
119 | conn.query(sql_password, params.password, function (err, result) {
120 | if (err) {
121 | console.log(err);
122 | }
123 | else {
124 | res.send({
125 | code: 0,
126 | data: result[0]
127 | });
128 | }
129 | });
130 | }
131 | })
132 | });
133 |
134 | // 同步用户收藏歌曲
135 | router.post('/addFavorite', (req, res) => {
136 | // 查找用户名
137 | const sql_name = sql.user.select_name;
138 | const params = req.body;
139 |
140 | conn.query(sql_name, params.username, function (err, result) {
141 | if (err) {
142 | console.log(err);
143 | }
144 | if (result[0] === undefined) {
145 | res.send({
146 | code: -1
147 | }); // //查询不出username,data返回-1
148 | }
149 | else {
150 | if (params.favorite) {
151 | let favorite = JSON.stringify(params.favorite);
152 | // 更新用户喜欢列表
153 | conn.query(`update userData set favorite = '${favorite}' where username = '${params.username}'`);
154 | }
155 | }
156 | })
157 | });
158 |
159 | // 同步用户最近播放歌曲
160 | router.post('/addPlayHistory', (req, res) => {
161 | // 查找用户名
162 | const sql_name = sql.user.select_name;
163 | const params = req.body;
164 |
165 | conn.query(sql_name, params.username, function (err, result) {
166 | if (err) {
167 | console.log(err);
168 | }
169 | if (result[0] === undefined) {
170 | res.send({
171 | code: -1
172 | }); // //查询不出username,data返回-1
173 | }
174 | else {
175 | if (params.playHistory) {
176 | let playHistory = JSON.stringify(params.playHistory);
177 | // 更新用户最近收听列表
178 | conn.query(`update userData set playHistory = '${playHistory}' where username = '${params.username}'`);
179 | }
180 | }
181 | })
182 | });
183 |
184 | // 获取用户uid
185 | router.post('/getUserUid', (req, res) => {
186 | // 查找用户名
187 | const params = req.body;
188 |
189 | conn.query(`select * from userData where username = '${params.username}'`, function (err, result) {
190 | if (err) {
191 | console.log(err);
192 | }
193 | if (result[0] === undefined) {
194 | res.send({
195 | code: -1
196 | }); // //查询不出username,data返回-1
197 | }
198 | else {
199 | res.send({
200 | code: 0,
201 | data: {
202 | uid: result[0].uid
203 | }
204 | });
205 | }
206 | })
207 | });
208 |
209 | module.exports = router;
210 |
--------------------------------------------------------------------------------
/src/components/new-song-speed/new-song-speed.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
130 |
131 |
132 |
208 |
--------------------------------------------------------------------------------
/src/common/js/cache.js:
--------------------------------------------------------------------------------
1 | import storage from 'good-storage';
2 |
3 | // key
4 | const SONG_SINGLE_KEY = '__songsingle__';
5 | const FAVORITE_KEY = '__favorite__';
6 | const NEW_SONG_SPEED_TITLE_KEY = '__newSongSpeedTitle__';
7 | const INIT_NEW_SONG_LIST_KEY = '__initNewSongList__';
8 | const CURRENT_SONG_KEY = '__currentSong__';
9 | const CURRENT_INDEX_KEY = '__currentIndex__';
10 | const PlAULIST_KEY = '__playList__';
11 | const PLAYURL_KEY = '__playUrl__';
12 | const SEQUENCE_LIST_KEY = '__sequenceList__';
13 | const SEARCH_HISTORY_KEY = '__searchHistory__';
14 | const PLAU_HISTORY_KEY = '__playHistory__';
15 | const USER_MESSAGE_KEY = '__usermessage__';
16 |
17 | // 收藏歌曲最大存储长度 200
18 | const FAVORITE_MAX_LENGTH = 200;
19 | // 搜索历史最大存储15条数据
20 | const SEARCH_HISTORY_MAX_LENGTH = 15;
21 | // 保存播放历史最大存储200条数据
22 | const PLAY_HISTORY_MAX_LENGTH = 200;
23 |
24 |
25 | // 保存主页选择对应歌单的数据到本地
26 | export function saveSongSingle (currentSong) {
27 | return storage.set(SONG_SINGLE_KEY, currentSong);
28 | }
29 |
30 | // 获取主页选择对应歌单的数据
31 | export function getSongSingle () {
32 | return storage.get(SONG_SINGLE_KEY, []);
33 | }
34 |
35 | // 保存主页新歌模块跳转对应的模块的标题
36 | export function saveNewSongSpeedTitle (title) {
37 | return storage.set(NEW_SONG_SPEED_TITLE_KEY, title);
38 | }
39 |
40 | // 获取主页新歌模块跳转对应的模块的标题
41 | export function getNewSongSpeedTitle () {
42 | return storage.get(NEW_SONG_SPEED_TITLE_KEY, '');
43 | }
44 |
45 | // 保存一开始的新歌数据
46 | export function saveInitNewSongList (data) {
47 | return storage.set(INIT_NEW_SONG_LIST_KEY, data);
48 | }
49 |
50 | // 获取一开始的新歌数据
51 | export function getInitNewSongList () {
52 | return storage.get(INIT_NEW_SONG_LIST_KEY, []);
53 | }
54 |
55 | // 保存当前播放的歌曲信息
56 | export function saveCurrentSong (currentSong) {
57 | return storage.set(CURRENT_SONG_KEY, currentSong);
58 | }
59 | // 获取当前播放的歌曲信息
60 | export function getCurrentSong () {
61 | return storage.get(CURRENT_SONG_KEY, []);
62 | }
63 |
64 | // 保存播放列表
65 | export function savePlayList (playlist) {
66 | return storage.set(PlAULIST_KEY, playlist);
67 | }
68 | // 获取当前播放的歌曲信息
69 | export function getPlayList () {
70 | return storage.get(PlAULIST_KEY, []);
71 | }
72 |
73 | // 保存当前播放索引
74 | export function saveCurrentIndex (index) {
75 | return storage.set(CURRENT_INDEX_KEY, index);
76 | }
77 | // 获取当前播放索引
78 | export function getCurrentIndex () {
79 | return storage.get(CURRENT_INDEX_KEY, []);
80 | }
81 |
82 | // 保存当前播放歌曲链接
83 | export function savePlayUrl (url) {
84 | return storage.set(PLAYURL_KEY, url);
85 | }
86 | // 获取当前播放歌曲链接
87 | export function getPlayUrl () {
88 | return storage.get(PLAYURL_KEY, []);
89 | }
90 |
91 | // 保存顺序播放列表
92 | export function saveSequenceList (url) {
93 | return storage.set(SEQUENCE_LIST_KEY, url);
94 | }
95 | // 获取顺序播放列表
96 | export function getSequenceList () {
97 | return storage.get(SEQUENCE_LIST_KEY, []);
98 | }
99 |
100 | // 删除重复数据,插入新增数据 compare数组的查找方法
101 | export function insertArray (arr, val, compare, maxLen) {
102 | const index = arr.findIndex(compare);
103 | // 如果只有一个数据符合就直接返回
104 | if (index === 0) {
105 | return;
106 | }
107 | // 删除第一个数据
108 | if (index > 0) {
109 | arr.splice(index, 1);
110 | }
111 | // 重新插入要保存的数据到第一个
112 | arr.unshift(val);
113 | // 如果存入的数据长度大于maxLen条那么就删除最后一个数据
114 | if (maxLen && arr.length > maxLen) {
115 | arr.pop();
116 | }
117 | }
118 |
119 | // 删除收藏歌曲方法 compare数组的查找方法
120 | function deleteFromArray (arr, compare) {
121 | // 寻找要删除的歌曲在数组中的index索引
122 | const index = arr.findIndex(compare);
123 | // 如果数组中有该歌词索引就删除他
124 | if (index > -1) {
125 | arr.splice(index, 1);
126 | }
127 | }
128 |
129 | // 保存收藏歌曲到本地
130 | export function saveFavorite (currentSong) {
131 | // 初始化数据
132 | let songs = storage.get(FAVORITE_KEY, []);
133 |
134 | // 删除重复的数据 插入新增数据
135 | insertArray(songs, currentSong, (item) => {
136 | return item.id === currentSong.id;
137 | }, FAVORITE_MAX_LENGTH);
138 |
139 | // 保存到本地
140 | storage.set(FAVORITE_KEY, songs);
141 | return songs;
142 | }
143 |
144 | // 覆盖收藏歌曲到本地
145 | export function coverUserMessage (list) {
146 | if (list.length > 0) {
147 | return storage.set(FAVORITE_KEY, list);
148 | }
149 | else {
150 | return storage.set(FAVORITE_KEY, [list]);
151 | }
152 | }
153 |
154 | // 删除收藏歌曲
155 | export function deleteFavorite (currentSong) {
156 | // 初始化数据
157 | let songs = storage.get(FAVORITE_KEY, []);
158 |
159 | deleteFromArray(songs, (item) => {
160 | return currentSong.id === item.id;
161 | });
162 | storage.set(FAVORITE_KEY, songs);
163 | return songs;
164 | }
165 |
166 | // 加载所有的收藏歌曲
167 | export function loadFavorite () {
168 | return storage.get(FAVORITE_KEY, []);
169 | }
170 |
171 | // 保存搜索历史
172 | export function saveSearchHistory (query) {
173 | let searchesHistory = storage.get(SEARCH_HISTORY_KEY, []);
174 | insertArray(searchesHistory, query, (item) => {
175 | return item === query;
176 | }, SEARCH_HISTORY_MAX_LENGTH);
177 | storage.set(SEARCH_HISTORY_KEY, searchesHistory);
178 | return searchesHistory;
179 | }
180 |
181 | // 删除搜索历史
182 | export function deleteSearchHistory (query) {
183 | // 获取当前的记录
184 | let searchesHistory = storage.get(SEARCH_HISTORY_KEY, []);
185 | // 删除记录
186 | deleteFromArray(searchesHistory, (item) => {
187 | return item === query;
188 | });
189 | // 保存记录
190 | storage.set(SEARCH_HISTORY_KEY, searchesHistory);
191 | return searchesHistory;
192 | }
193 |
194 | // 加载搜索历史
195 | export function loadSearchHistory () {
196 | return storage.get(SEARCH_HISTORY_KEY, []);
197 | }
198 |
199 | // 清除全部搜索历史
200 | export function clearSearchHistory () {
201 | storage.remove(SEARCH_HISTORY_KEY);
202 | return [];
203 | }
204 |
205 | // 保存播放历史
206 | export function savePlayHistory (song) {
207 | let songs = storage.get(PLAU_HISTORY_KEY, []);
208 | // 删除重复的数据 插入新增数据
209 | insertArray(songs, song, (item) => {
210 | return item.id === song.id
211 | }, PLAY_HISTORY_MAX_LENGTH);
212 | // 保存到本地
213 | storage.set(PLAU_HISTORY_KEY, songs);
214 | return songs
215 | }
216 |
217 | // 覆盖播放历史
218 | export function coverPlayHistory (list) {
219 | if (list.length > 0) {
220 | return storage.set(PLAU_HISTORY_KEY, list);
221 | }
222 | else {
223 | return storage.set(PLAU_HISTORY_KEY, [list]);
224 | }
225 | }
226 |
227 |
228 | // 获取播放历史的本地记录
229 | export function loadPlayHistory () {
230 | return storage.get(PLAU_HISTORY_KEY, [])
231 | }
232 |
233 | // 保存用户信息
234 | export function saveUserMessage (data) {
235 | return storage.set(USER_MESSAGE_KEY, data);
236 | }
237 | // 获取用户信息
238 | export function loadUserMessage () {
239 | return storage.get(USER_MESSAGE_KEY, [])
240 | }
241 |
242 | // 清除用户信息
243 | export function clearUserMessage () {
244 | storage.remove(USER_MESSAGE_KEY);
245 | return [];
246 | }
247 |
--------------------------------------------------------------------------------