├── 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 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 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 | > ![image](https://github.com/qq282126990/musicApp/blob/mpa/images/androidAPK.png) 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 | 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 | 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 | 14 | 15 | 33 | 34 | 94 | -------------------------------------------------------------------------------- /src/base/search-list/search-list.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 25 | 26 | 55 | 56 | 108 | -------------------------------------------------------------------------------- /src/components/user-favorite-list/user-favorite-list.vue: -------------------------------------------------------------------------------- 1 | 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 | 10 | 11 | 103 | 104 | 119 | -------------------------------------------------------------------------------- /src/base/search-box/search-box.vue: -------------------------------------------------------------------------------- 1 | 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 | 21 | 22 | 104 | 105 | 149 | -------------------------------------------------------------------------------- /src/base/scroll/scroll.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 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 | 19 | 20 | 103 | 104 | 105 | 111 | 112 | 162 | -------------------------------------------------------------------------------- /src/base/confirm/confirm.vue: -------------------------------------------------------------------------------- 1 | 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 | 19 | 20 | 121 | 122 | 166 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 111 | 112 | 187 | -------------------------------------------------------------------------------- /src/base/login-input/login-input.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | --------------------------------------------------------------------------------