├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── README.md
├── build
├── build.js
├── logo.png
├── utils.js
├── webpack.base.conf.js
├── webpack.dev.conf.js
└── webpack.prod.conf.js
├── dist
├── favicon.ico
├── icons
│ ├── music-120.png
│ ├── music-144.png
│ ├── music-152.png
│ ├── music-192.png
│ ├── music-256.png
│ ├── music-384.png
│ ├── music-48.png
│ └── music-512.png
├── index.html
├── manifest.json
└── static
│ ├── css
│ ├── app.4aae7266452030a68c992e7a644689e2.css
│ └── app.4aae7266452030a68c992e7a644689e2.css.map
│ ├── img
│ ├── default.9710131.png
│ ├── empty.4c4c2c3.png
│ └── loading.1f26c50.gif
│ └── js
│ ├── app.f2498bb9c13212717c50.js
│ ├── app.f2498bb9c13212717c50.js.map
│ ├── manifest.2ae2e69a05c33dfc65f8.js
│ ├── manifest.2ae2e69a05c33dfc65f8.js.map
│ ├── vendor.ebd99a3ce2f05a6feefe.js
│ └── vendor.ebd99a3ce2f05a6feefe.js.map
├── favicon.ico
├── index.html
├── package-lock.json
├── package.json
├── postcss.config.js
├── src
├── api
│ ├── config.js
│ ├── recommends.js
│ ├── search.js
│ ├── singers.js
│ ├── song.js
│ └── top-list.js
├── app.vue
├── assets
│ ├── icons
│ │ ├── back.svg
│ │ ├── calendar.svg
│ │ ├── clear.svg
│ │ ├── clock.svg
│ │ ├── comment.svg
│ │ ├── delete.svg
│ │ ├── disc.svg
│ │ ├── down.svg
│ │ ├── download.svg
│ │ ├── favorite.svg
│ │ ├── fm.svg
│ │ ├── headphones.svg
│ │ ├── like.svg
│ │ ├── logo.svg
│ │ ├── loop.svg
│ │ ├── menu.svg
│ │ ├── mini-pause.svg
│ │ ├── mini-pause2.svg
│ │ ├── mini-play.svg
│ │ ├── mini-play2.svg
│ │ ├── more.svg
│ │ ├── music.svg
│ │ ├── next.svg
│ │ ├── once.svg
│ │ ├── pause.svg
│ │ ├── play.svg
│ │ ├── playlist.svg
│ │ ├── prev.svg
│ │ ├── random.svg
│ │ ├── rank.svg
│ │ ├── repeat.svg
│ │ ├── search.svg
│ │ ├── unfavorite.svg
│ │ └── volume.svg
│ └── images
│ │ ├── default.png
│ │ ├── empty.png
│ │ └── loading.gif
├── components
│ ├── confirm.vue
│ ├── empty.vue
│ ├── loading.vue
│ ├── progress-bar.vue
│ ├── progress-circle.vue
│ ├── scroll.vue
│ ├── swiper.vue
│ ├── tab.vue
│ └── toast.vue
├── icon.vue
├── main.js
├── pages
│ ├── comment
│ │ ├── comment-item.vue
│ │ └── comment.vue
│ ├── entry
│ │ └── entry.vue
│ ├── header
│ │ └── header.vue
│ ├── music-list
│ │ ├── music-list.vue
│ │ └── song-list.vue
│ ├── player
│ │ └── player.vue
│ ├── playlist
│ │ └── playlist.vue
│ ├── recommends
│ │ ├── recommend-detail.vue
│ │ └── recommends.vue
│ ├── search
│ │ ├── search-box.vue
│ │ └── search.vue
│ ├── singers
│ │ ├── singer-detail.vue
│ │ └── singers.vue
│ ├── top-list
│ │ ├── top-list-detail.vue
│ │ └── top-list.vue
│ └── user
│ │ └── user.vue
├── route.js
├── services
│ ├── cache.js
│ ├── config.js
│ └── song.js
├── styles
│ ├── base.styl
│ ├── highlight.styl
│ ├── index.styl
│ ├── lib
│ │ └── variables-bootstrap.styl
│ ├── mixins.styl
│ ├── reset.styl
│ └── variables.styl
├── utils
│ ├── axios.js
│ ├── cache.js
│ ├── console.js
│ ├── directives.js
│ ├── filters.js
│ ├── index.js
│ └── mixins.js
└── vuex
│ ├── actions.js
│ ├── getters.js
│ ├── mutation-types.js
│ ├── mutations.js
│ ├── state.js
│ └── store.js
└── static
├── icons
├── music-120.png
├── music-144.png
├── music-152.png
├── music-192.png
├── music-256.png
├── music-384.png
├── music-48.png
└── music-512.png
└── manifest.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "env",
4 | "stage-2"
5 | ],
6 | "plugins": [
7 | [
8 | "transform-runtime"
9 | ]
10 | ]
11 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 | indent_style = space
16 | indent_size = 4
17 |
18 | [*.js]
19 | indent_style = space
20 | indent_size = 2
21 |
22 | [*.coffee]
23 | indent_style = space
24 | indent_size = 2
25 |
26 | [*.html]
27 | indent_style = space
28 | indent_size = 4
29 |
30 | [*.ejs]
31 | indent_style = space
32 | indent_size = 4
33 |
34 | [*.css]
35 | indent_style = space
36 | indent_size = 2
37 |
38 | [*.less]
39 | indent_style = space
40 | indent_size = 2
41 |
42 | [*.styl]
43 | indent_style = space
44 | indent_size = 2
45 |
46 | [*.vue]
47 | indent_style = space
48 | indent_size = 2
49 |
50 | [*.eslintrc]
51 | indent_style = space
52 | indent_size = 2
53 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /dist
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // https://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parserOptions: {
6 | parser: 'babel-eslint'
7 | },
8 | env: {
9 | browser: true,
10 | },
11 | extends: [
12 | 'plugin:vue/essential',
13 | 'standard'
14 | ],
15 | plugins: [
16 | 'vue'
17 | ],
18 | rules: {
19 | 'semi': [2, 'always'],
20 | 'generator-star-spacing': 0,
21 | 'space-before-function-paren': 0,
22 | 'prefer-promise-reject-errors': 0,
23 | 'eol-last': 0,
24 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependency directory
2 | node_modules
3 |
4 | # Logs
5 | logs
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-music-webapp 网易云音乐
2 |
3 | 技术栈: vue、vuex、better-sroll、vue-lazyload、webpack3
4 |
5 | ## 网易云音乐接口地址
6 | [网易云接口仓库地址](https://github.com/Binaryify/NeteaseCloudMusicApi)
7 |
8 | ## Live demo
9 | http://music.ipeihan.top
10 |
11 | 手机扫码,体验更加
12 |
13 | 
14 |
15 |
16 | ## Typescript 版
17 | Typescript 版请移步https://github.com/lpeihan/netease
18 |
19 | ## Usage
20 | ```shell
21 | # 开发环境
22 | npm run dev
23 |
24 | # 生产环境
25 | npm run build
26 | ```
27 |
28 | ## Finished
29 | * 推荐页面
30 |
31 | * 音乐排行
32 | * 歌手页表
33 | * 我的收藏
34 | * 最近播放
35 | * 搜索
36 | * 歌曲列表
37 | * 播放列表
38 | * 歌词展示
39 | * 评论展示
40 | * 切换播放模式
41 |
42 | ## TodoList
43 |
44 | * ~~加入 `pwa`,支持添加图标到桌面~~
45 | * ~~兼容`safari`以及部分不能播放音频的安卓手机~~
46 |
--------------------------------------------------------------------------------
/build/build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | process.env.NODE_ENV = 'production';
4 |
5 | const ora = require('ora');
6 | const rm = require('rimraf');
7 | const chalk = require('chalk');
8 | const webpack = require('webpack');
9 | const webpackConfig = require('./webpack.prod.conf');
10 | const { resolve } = require('./utils');
11 |
12 | const spinner = ora('building for production...');
13 | spinner.start();
14 |
15 | rm(resolve('dist'), err => {
16 | if (err) {
17 | throw err;
18 | }
19 | webpack(webpackConfig, (err, stats) => {
20 | spinner.stop();
21 | if (err) {
22 | throw err;
23 | }
24 | process.stdout.write(stats.toString({
25 | colors: true,
26 | modules: false,
27 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
28 | chunks: false,
29 | chunkModules: false
30 | }) + '\n\n');
31 |
32 | if (stats.hasErrors()) {
33 | console.log(chalk.red(' Build failed with errors.\n'));
34 | process.exit(1);
35 | }
36 |
37 | console.log(chalk.cyan(' Build complete.\n'));
38 | console.log(chalk.yellow(
39 | ' Tip: The build folder is ready to be deployed.\n' +
40 | ' You may serve it with a static server:\n'
41 | ));
42 | console.log(chalk.cyan(
43 | ' npm install -g serve\n' +
44 | ' serve -s dist\n'
45 | ));
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/build/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/build/logo.png
--------------------------------------------------------------------------------
/build/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
5 |
6 | const isProduction = process.env.NODE_ENV === 'production';
7 |
8 | exports.resolve = function(dir = '') {
9 | return path.join(__dirname, '..', dir);
10 | };
11 |
12 | exports.assetsPath = function(_path) {
13 | return path.posix.join('static', _path);
14 | };
15 |
16 | exports.cssLoader = function(loader) {
17 | const loaders = [
18 | {
19 | loader: 'css-loader',
20 | options: { sourceMap: true }
21 | },
22 | {
23 | loader: 'postcss-loader',
24 | options: { sourceMap: true }
25 | }
26 | ];
27 |
28 | if (loader) {
29 | loaders.push({
30 | loader: `${loader}-loader`,
31 | options: { sourceMap: true }
32 | });
33 | }
34 |
35 | if (isProduction) {
36 | return ExtractTextPlugin.extract({
37 | use: loaders,
38 | fallback: 'vue-style-loader'
39 | });
40 | } else {
41 | return ['vue-style-loader'].concat(loaders);
42 | }
43 | };
44 |
45 | exports.vueLoaderConf = {
46 | loaders: {
47 | css: exports.cssLoader(),
48 | stylus: exports.cssLoader('stylus')
49 | },
50 | cssSourceMap: isProduction
51 | };
52 |
--------------------------------------------------------------------------------
/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { resolve, assetsPath, cssLoader, vueLoaderConf } = require('./utils');
4 |
5 | module.exports = {
6 | context: resolve(),
7 | entry: {
8 | app: './src/main.js'
9 | },
10 | output: {
11 | path: resolve('dist'),
12 | filename: assetsPath('js/[name].js'),
13 | publicPath: '/'
14 | },
15 | resolve: {
16 | extensions: ['.js', '.vue', '.json'],
17 | alias: {
18 | 'vue$': 'vue/dist/vue.esm.js'
19 | }
20 | },
21 | module: {
22 | rules: [
23 | {
24 | test: /\.js$/,
25 | use: 'babel-loader',
26 | include: [
27 | resolve('src'),
28 | resolve('node_modules/_pinyin@2.8.3@pinyin')
29 | ]
30 | },
31 | {
32 | test: /\.vue$/,
33 | loader: 'vue-loader',
34 | options: vueLoaderConf
35 | },
36 | {
37 | test: /\.(js|vue)$/,
38 | loader: 'eslint-loader',
39 | enforce: 'pre',
40 | include: [resolve('src')],
41 | options: {
42 | emitWarning: true
43 | }
44 | },
45 | {
46 | test: /assets[\\/]+?\S+\.svg$/,
47 | use: 'svg-inline-loader'
48 | },
49 | {
50 | test: /\.css$/,
51 | use: cssLoader()
52 | },
53 | {
54 | test: /\.styl$/,
55 | use: cssLoader('stylus')
56 | },
57 | {
58 | test: /\.(png|jpe?g|gif)(\?.*)?$/,
59 | loader: 'url-loader',
60 | options: {
61 | limit: 1000,
62 | name: assetsPath('img/[name].[hash:7].[ext]')
63 | }
64 | },
65 | {
66 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
67 | loader: 'url-loader',
68 | options: {
69 | limit: 10000,
70 | name: assetsPath('media/[name].[hash:7].[ext]')
71 | }
72 | },
73 | {
74 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
75 | loader: 'url-loader',
76 | options: {
77 | limit: 10000,
78 | name: assetsPath('fonts/[name].[hash:7].[ext]')
79 | }
80 | }
81 | ]
82 | }
83 | };
84 |
--------------------------------------------------------------------------------
/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const webpack = require('webpack');
4 | const path = require('path');
5 | const merge = require('webpack-merge');
6 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
7 | const notifier = require('node-notifier');
8 | const HtmlWebpackPlugin = require('html-webpack-plugin');
9 |
10 | const packageConfig = require('../package.json');
11 | const baseWebpackConf = require('./webpack.base.conf');
12 | const {
13 | resolve
14 | } = require('./utils');
15 |
16 | const host = 'localhost';
17 | const port = 8302;
18 | const proxy = {};
19 |
20 | module.exports = merge(baseWebpackConf, {
21 | devServer: {
22 | host,
23 | port,
24 | proxy,
25 | hot: true,
26 | inline: true,
27 | open: true,
28 | compress: true,
29 | quiet: true,
30 | clientLogLevel: 'warning',
31 | contentBase: resolve('static'),
32 | overlay: {
33 | errors: true,
34 | warnings: false
35 | },
36 | historyApiFallback: {
37 | rewrites: [
38 | { from: /.*/, to: path.posix.join('/', 'index.html') }
39 | ]
40 | }
41 | },
42 | devtool: 'cheap-module-eval-source-map',
43 | plugins: [
44 | new webpack.DefinePlugin({
45 | 'process.env': {
46 | 'NODE_ENV': '"development"'
47 | }
48 | }),
49 | new HtmlWebpackPlugin({
50 | filename: 'index.html',
51 | template: 'index.html',
52 | title: packageConfig.name,
53 | inject: true,
54 | favicon: resolve('favicon.ico')
55 | }),
56 | new webpack.HotModuleReplacementPlugin(),
57 | new webpack.NamedModulesPlugin(),
58 | new FriendlyErrorsPlugin({
59 | compilationSuccessInfo: {
60 | messages: [
61 | `Your application is running here http://${host}:${port}`
62 | ]
63 | },
64 | onErrors: function(severity, errors) {
65 | if (severity !== 'error') {
66 | return;
67 | }
68 | const error = errors[0];
69 | const filename = error.file.split('!').pop();
70 | notifier.notify({
71 | title: packageConfig.name,
72 | message: severity + ': ' + error.name,
73 | subtitle: filename || '',
74 | icon: path.resolve(__dirname, 'logo.png')
75 | });
76 | }
77 | })
78 | ]
79 | });
80 |
--------------------------------------------------------------------------------
/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const webpack = require('webpack');
4 | const merge = require('webpack-merge');
5 | const HtmlWebpackPlugin = require('html-webpack-plugin');
6 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
7 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin');
8 | const CopyWebpackPlugin = require('copy-webpack-plugin');
9 |
10 | const packageConfig = require('../package.json');
11 | const baseWebpackConf = require('./webpack.base.conf');
12 | const {
13 | resolve,
14 | assetsPath
15 | } = require('./utils');
16 |
17 | module.exports = merge(baseWebpackConf, {
18 | output: {
19 | publicPath: '/',
20 | filename: assetsPath('js/[name].[chunkhash].js')
21 | },
22 | devtool: '#source-map',
23 | plugins: [
24 | new webpack.DefinePlugin({
25 | 'process.env': {
26 | 'NODE_ENV': '"production"'
27 | }
28 | }),
29 | new HtmlWebpackPlugin({
30 | filename: 'index.html',
31 | template: 'index.html',
32 | title: packageConfig.name,
33 | favicon: resolve('favicon.ico'),
34 | inject: true,
35 | minify: {
36 | removeComments: true,
37 | collapseWhitespace: true,
38 | removeAttributeQuotes: true
39 | },
40 | chunksSortMode: 'dependency'
41 | }),
42 | new webpack.optimize.UglifyJsPlugin({
43 | parallel: true,
44 | sourceMap: true,
45 | compress: {
46 | warnings: false
47 | }
48 | }),
49 | new ExtractTextPlugin({
50 | filename: assetsPath('css/[name].[contenthash].css'),
51 | allChunks: true
52 | }),
53 | new OptimizeCSSPlugin({
54 | cssProcessorOptions: { safe: true, map: { inline: false } }
55 | }),
56 | new webpack.HashedModuleIdsPlugin(),
57 | new webpack.optimize.CommonsChunkPlugin({
58 | name: 'vendor',
59 | minChunks: function(module) {
60 | return (
61 | module.resource &&
62 | /\.js$/.test(module.resource) &&
63 | module.resource.indexOf(resolve('node_modules')) === 0
64 | );
65 | }
66 | }),
67 | new webpack.optimize.CommonsChunkPlugin({
68 | name: 'manifest',
69 | minChunks: Infinity
70 | }),
71 | new CopyWebpackPlugin([
72 | {
73 | from: resolve('static'),
74 | to: ''
75 | }
76 | ])
77 | ]
78 | });
79 |
--------------------------------------------------------------------------------
/dist/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/dist/favicon.ico
--------------------------------------------------------------------------------
/dist/icons/music-120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/dist/icons/music-120.png
--------------------------------------------------------------------------------
/dist/icons/music-144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/dist/icons/music-144.png
--------------------------------------------------------------------------------
/dist/icons/music-152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/dist/icons/music-152.png
--------------------------------------------------------------------------------
/dist/icons/music-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/dist/icons/music-192.png
--------------------------------------------------------------------------------
/dist/icons/music-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/dist/icons/music-256.png
--------------------------------------------------------------------------------
/dist/icons/music-384.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/dist/icons/music-384.png
--------------------------------------------------------------------------------
/dist/icons/music-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/dist/icons/music-48.png
--------------------------------------------------------------------------------
/dist/icons/music-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/dist/icons/music-512.png
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
Netease
--------------------------------------------------------------------------------
/dist/manifest.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "name": "Netease",
4 | "short_name": "Netease",
5 | "icons": [{
6 | "src": "/icons/music-120.png",
7 | "sizes": "120x120",
8 | "type": "image/png"
9 | }, {
10 | "src": "/icons/music-144.png",
11 | "sizes": "144x144",
12 | "type": "image/png"
13 | }, {
14 | "src": "/icons/music-152.png",
15 | "sizes": "152x152",
16 | "type": "image/png"
17 | }, {
18 | "src": "/icons/music-192.png",
19 | "sizes": "192x192",
20 | "type": "image/png"
21 | }, {
22 | "src": "/icons/music-256.png",
23 | "sizes": "256x256",
24 | "type": "image/png"
25 | }, {
26 | "src": "/icons/music-384.png",
27 | "sizes": "384x384",
28 | "type": "image/png"
29 | }, {
30 | "src": "/icons/music-512.png",
31 | "sizes": "512x512",
32 | "type": "image/png"
33 | }],
34 | "start_url": "/",
35 | "background_color": "#f2f3f5",
36 | "display": "standalone",
37 | "theme_color": "#d44439"
38 | }
--------------------------------------------------------------------------------
/dist/static/img/default.9710131.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/dist/static/img/default.9710131.png
--------------------------------------------------------------------------------
/dist/static/img/empty.4c4c2c3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/dist/static/img/empty.4c4c2c3.png
--------------------------------------------------------------------------------
/dist/static/img/loading.1f26c50.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/dist/static/img/loading.1f26c50.gif
--------------------------------------------------------------------------------
/dist/static/js/manifest.2ae2e69a05c33dfc65f8.js:
--------------------------------------------------------------------------------
1 | !function(r){function n(e){if(o[e])return o[e].exports;var t=o[e]={i:e,l:!1,exports:{}};return r[e].call(t.exports,t,t.exports,n),t.l=!0,t.exports}var e=window.webpackJsonp;window.webpackJsonp=function(o,u,c){for(var f,i,p,a=0,l=[];a
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | <%= htmlWebpackPlugin.options.title %>
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Netease",
3 | "version": "1.0.0",
4 | "homepage": "https://lpeihan.github.io/vue-music-webapp",
5 | "description": "",
6 | "main": "index.js",
7 | "scripts": {
8 | "build": "node build/build.js",
9 | "dev": "webpack-dev-server --config build/webpack.dev.conf.js",
10 | "predeploy": "npm run build",
11 | "deploy": "gh-pages -d dist"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC",
16 | "devDependencies": {
17 | "autoprefixer": "^8.3.0",
18 | "babel-core": "^6.26.0",
19 | "babel-eslint": "^8.2.3",
20 | "babel-loader": "^7.1.4",
21 | "babel-plugin-transform-runtime": "^6.23.0",
22 | "babel-preset-env": "^1.6.1",
23 | "babel-preset-stage-2": "^6.24.1",
24 | "chalk": "^2.4.0",
25 | "copy-webpack-plugin": "^4.5.1",
26 | "css-loader": "^0.28.11",
27 | "eslint": "^4.19.1",
28 | "eslint-config-standard": "^11.0.0",
29 | "eslint-loader": "^2.0.0",
30 | "eslint-plugin-import": "^2.11.0",
31 | "eslint-plugin-node": "^6.0.1",
32 | "eslint-plugin-promise": "^3.7.0",
33 | "eslint-plugin-standard": "^3.0.1",
34 | "eslint-plugin-vue": "^4.5.0",
35 | "extract-text-webpack-plugin": "^3.0.2",
36 | "file-loader": "^1.1.11",
37 | "friendly-errors-webpack-plugin": "^1.7.0",
38 | "gh-pages": "^1.1.0",
39 | "html-webpack-plugin": "^3.2.0",
40 | "node-notifier": "^5.2.1",
41 | "optimize-css-assets-webpack-plugin": "^3.2.0",
42 | "ora": "^2.0.0",
43 | "postcss-loader": "^2.1.4",
44 | "rimraf": "^2.6.2",
45 | "stylus": "^0.54.5",
46 | "stylus-loader": "^3.0.2",
47 | "svg-inline-loader": "^0.8.0",
48 | "url-loader": "^1.0.1",
49 | "vconsole": "^3.2.0",
50 | "vue-lazyload": "^1.2.6",
51 | "vue-loader": "^14.2.2",
52 | "vue-style-loader": "^4.1.0",
53 | "vue-template-compiler": "^2.5.16",
54 | "webpack": "^3.8.1",
55 | "webpack-dev-server": "^2.9.7",
56 | "webpack-merge": "^4.1.2"
57 | },
58 | "dependencies": {
59 | "axios": "^0.18.0",
60 | "better-scroll": "^1.15.1",
61 | "create-keyframe-animation": "^0.1.0",
62 | "lyric-parser": "^1.0.1",
63 | "pinyin": "2.8.3",
64 | "vue": "^2.5.16",
65 | "vue-lazyload": "^1.2.3",
66 | "vue-router": "^3.0.1",
67 | "vue-swiper-component": "^2.1.0",
68 | "vuex": "^3.0.1"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('autoprefixer')
4 | ]
5 | };
6 |
--------------------------------------------------------------------------------
/src/api/config.js:
--------------------------------------------------------------------------------
1 | // export const host = 'http://120.79.162.149:3000';
2 |
3 | // export const host = 'http://192.168.31.170:3000';
4 |
5 | export const host = 'http://47.98.144.117:3000';
6 |
--------------------------------------------------------------------------------
/src/api/recommends.js:
--------------------------------------------------------------------------------
1 | import { host } from './config';
2 | import axios from 'axios';
3 |
4 | export function getBanners () {
5 | return axios.get(host + '/banner');
6 | }
7 |
8 | export function getRecommends() {
9 | return axios.get(host + '/personalized');
10 | }
11 |
12 | export function getRecommendDetail(id) {
13 | return axios.get(host + `/playlist/detail?id=${id}`);
14 | }
--------------------------------------------------------------------------------
/src/api/search.js:
--------------------------------------------------------------------------------
1 | import { host } from './config';
2 | import axios from 'axios';
3 |
4 | export function getSearchHot() {
5 | return axios.get(host + `/search/hot`);
6 | }
7 |
8 | export function getSearchSuggest(keywords) {
9 | return axios.get(host + `/search/suggest?keywords=${keywords}`);
10 | }
11 |
12 | export function getSearchSinger(keywords) {
13 | return axios.get(host + `/search?keywords=${keywords}&type=100&limit=1`);
14 | }
15 |
16 | export function getSearchSongs(keywords, offset = 0, limit = 20) {
17 | return axios.get(host + `/search?keywords=${keywords}&offset=${offset}&limit=${limit}`);
18 | }
19 |
20 | export function getSearchMusicList(keywords) {
21 | return axios.get(host + `/search?keywords=${keywords}&type=1000&limit=1`);
22 | }
23 |
24 | export function getSongDetail (id) {
25 | return axios.get(host + `/song/detail?ids=${id}`);
26 | }
27 |
--------------------------------------------------------------------------------
/src/api/singers.js:
--------------------------------------------------------------------------------
1 | import { host } from './config';
2 | import axios from 'axios';
3 |
4 | export function getSingers () {
5 | return axios.get(host + '/top/artists?limit=100');
6 | }
7 |
8 | export function getSingerDetail(id) {
9 | return axios.get(host + `/artists?id=${id}`);
10 | }
--------------------------------------------------------------------------------
/src/api/song.js:
--------------------------------------------------------------------------------
1 | import { host } from './config';
2 | import axios from 'axios';
3 |
4 | export function getSong(id) {
5 | return axios.get(host + `/song/url?id=${id}`);
6 | }
7 |
8 | export function getLyric (id) {
9 | return axios.get(host + `/lyric?id=${id}`);
10 | }
11 |
12 | export function getComments(id, offset = 0) {
13 | return axios.get(host + `/comment/music?id=${id}&offset=${offset}`);
14 | }
15 |
--------------------------------------------------------------------------------
/src/api/top-list.js:
--------------------------------------------------------------------------------
1 | import { host } from './config';
2 | import axios from 'axios';
3 |
4 | export function getTopList() {
5 | return axios.get(host + `/toplist/detail`);
6 | }
7 |
8 | export function getTopListDetail(id) {
9 | return axios.get(host + `/playlist/detail?id=${id}`);
10 | }
--------------------------------------------------------------------------------
/src/app.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
62 |
63 |
65 |
--------------------------------------------------------------------------------
/src/assets/icons/back.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/calendar.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/clear.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/clock.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/comment.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/delete.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/disc.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/down.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/download.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/favorite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/fm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/headphones.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/like.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/loop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/menu.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/mini-pause.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/mini-pause2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/mini-play.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/mini-play2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/more.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/music.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/once.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/pause.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/play.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/playlist.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/prev.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/random.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/rank.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/repeat.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/unfavorite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/volume.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/src/assets/images/default.png
--------------------------------------------------------------------------------
/src/assets/images/empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/src/assets/images/empty.png
--------------------------------------------------------------------------------
/src/assets/images/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/src/assets/images/loading.gif
--------------------------------------------------------------------------------
/src/components/confirm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{text}}
7 |
8 |
12 |
13 |
14 |
15 |
16 |
17 |
42 |
43 |
89 |
--------------------------------------------------------------------------------
/src/components/empty.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
好像什么东西也没有…
5 |
6 |
7 |
8 |
18 |
--------------------------------------------------------------------------------
/src/components/loading.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
{{title}}
5 |
6 |
7 |
8 |
18 |
19 |
30 |
--------------------------------------------------------------------------------
/src/components/progress-bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
70 |
71 |
100 |
--------------------------------------------------------------------------------
/src/components/progress-circle.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
51 |
52 |
80 |
--------------------------------------------------------------------------------
/src/components/scroll.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
86 |
87 |
91 |
--------------------------------------------------------------------------------
/src/components/swiper.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
132 |
133 |
166 |
--------------------------------------------------------------------------------
/src/components/tab.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{tab}}
6 |
7 |
15 |
16 |
31 |
32 |
33 |
34 |
104 |
105 |
154 |
--------------------------------------------------------------------------------
/src/components/toast.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{text}}
5 |
6 |
7 |
8 |
9 |
31 |
32 |
54 |
--------------------------------------------------------------------------------
/src/icon.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
52 |
53 |
65 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from './app';
3 | import router from './route';
4 | import Icon from './icon';
5 | import store from './vuex/store';
6 | import VueLazyload from 'vue-lazyload';
7 |
8 | /* eslint-disable no-unused-vars */
9 | // import VConsole from 'vconsole';
10 | import './utils/console';
11 |
12 | import './utils/axios';
13 | import './utils/directives';
14 | import './utils/filters';
15 | import './styles/index.styl';
16 |
17 | // const vConsole = new VConsole();
18 |
19 | Vue.component('icon', Icon);
20 |
21 | Vue.use(VueLazyload, {
22 | loading: require('./assets/images/default.png')
23 | });
24 |
25 | Vue.config.productionTip = false;
26 |
27 | /* eslint-disable no-new */
28 | new Vue({
29 | el: '#app',
30 | router,
31 | store,
32 | components: { App },
33 | template: ''
34 | });
35 |
--------------------------------------------------------------------------------
/src/pages/comment/comment-item.vue:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
20 |
30 |
31 |
--------------------------------------------------------------------------------
/src/pages/comment/comment.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
42 |
43 |
44 |
45 |
108 |
109 |
171 |
--------------------------------------------------------------------------------
/src/pages/entry/entry.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 | {{entry.text}}
8 |
9 |
10 |
11 |
12 |
38 |
39 |
68 |
--------------------------------------------------------------------------------
/src/pages/header/header.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
33 |
--------------------------------------------------------------------------------
/src/pages/music-list/music-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
{{title}}
10 |
11 |
12 |
13 |
14 |
21 |
22 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
109 |
110 |
198 |
--------------------------------------------------------------------------------
/src/pages/music-list/song-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 播放全部
6 | ({{songs.length}}首)
7 |
8 |
9 |
{{index + 1}}
10 |
{{song.name}}
11 |
{{song.singer}} - {{song.desc}}
12 |
13 |
14 |
15 |
16 |
17 |
32 |
33 |
81 |
--------------------------------------------------------------------------------
/src/pages/player/player.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
{{currentSong.name}}
11 |
{{currentSong.singer}}
12 |
13 |
14 |
15 |
23 |
24 |
25 |
29 | {{line.txt}}
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
47 |
48 |
49 |
50 |
51 |
52 |
{{currentTime | time}}
53 |
56 |
{{duration | time}}
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
{{currentSong.name}}
84 |
{{currentSong.singer}}
85 |
86 |
87 |
97 |
98 |
99 |
100 |
109 |
110 |
111 |
112 |
430 |
431 |
621 |
--------------------------------------------------------------------------------
/src/pages/playlist/playlist.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
15 |
16 |
17 | -
21 |
22 | {{item.name}}
23 | - {{item.singer}}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
117 |
118 |
188 |
--------------------------------------------------------------------------------
/src/pages/recommends/recommend-detail.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
58 |
--------------------------------------------------------------------------------
/src/pages/recommends/recommends.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
![]()
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 推荐歌单
20 |
21 |
22 |
28 |
29 |
![]()
30 |
31 |
32 | {{recommend.name}}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
113 |
114 |
172 |
--------------------------------------------------------------------------------
/src/pages/search/search-box.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
55 |
56 |
89 |
--------------------------------------------------------------------------------
/src/pages/search/search.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
15 |
热门搜索
16 |
17 | -
18 | {{hot.first}}
19 |
20 |
21 |
22 |
23 |
24 |
搜索历史
25 |
26 |
27 |
28 | -
29 |
{{history}}
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | - 搜索 "{{query}}"
41 | -
42 | {{suggest.name}}
43 |
44 |
45 |
46 |
47 |
54 |
55 | - 你可能感兴趣
56 | -
57 |
58 | 歌手:
59 |
60 |
61 | -
62 |
63 | 歌单:
64 |
65 |
66 | -
67 |
68 |
69 | -
70 |
71 |
72 |
73 | -
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
270 |
271 |
411 |
--------------------------------------------------------------------------------
/src/pages/singers/singer-detail.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
56 |
--------------------------------------------------------------------------------
/src/pages/singers/singers.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 | -
9 |
10 | {{group.title}}
11 | -
16 |
17 | {{item.name}}
18 |
19 |
20 |
21 |
22 |
27 | -
32 | {{group.title[0]}}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
188 |
189 |
245 |
--------------------------------------------------------------------------------
/src/pages/top-list/top-list-detail.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
52 |
--------------------------------------------------------------------------------
/src/pages/top-list/top-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
![coverImgUrl]()
7 |
8 |
9 |
10 |
11 | -
12 | {{`${index + 1}. ${song.first} - ${song.second}`}}
13 |
14 |
15 |
16 | - {{top.name}}
17 | - {{top.description}}
18 | - {{top.updateFrequency}}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
83 |
84 |
120 |
--------------------------------------------------------------------------------
/src/pages/user/user.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
72 |
73 |
113 |
--------------------------------------------------------------------------------
/src/route.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Router from 'vue-router';
3 | import RecommendDetail from './pages/recommends/recommend-detail';
4 | import SingerDetail from './pages/singers/singer-detail';
5 | import TopListDetail from './pages/top-list/top-list-detail';
6 | import Search from './pages/search/search';
7 | import User from './pages/user/user';
8 | import Comments from './pages/comment/comment';
9 |
10 | Vue.use(Router);
11 |
12 | export default new Router({
13 | mode: 'history',
14 | routes: [
15 | {
16 | path: '/recommends/:id',
17 | name: 'recommendsDetail',
18 | component: RecommendDetail
19 | },
20 | {
21 | path: '/singers/:id',
22 | name: 'singerDetail',
23 | component: SingerDetail
24 | },
25 | {
26 | path: '/top-list/:id',
27 | name: 'topListDetail',
28 | component: TopListDetail
29 | },
30 | {
31 | path: '/search',
32 | name: 'search',
33 | component: Search,
34 | children: [
35 | {
36 | path: 'singers/:id',
37 | component: SingerDetail
38 | },
39 | {
40 | path: 'recommends/:id',
41 | component: RecommendDetail
42 | }
43 | ]
44 | },
45 | {
46 | path: '/user',
47 | name: 'user',
48 | component: User
49 | },
50 | {
51 | path: '/music/comment/:id/full-screen',
52 | name: 'comment',
53 | component: Comments
54 | }
55 | ]
56 | });
57 |
--------------------------------------------------------------------------------
/src/services/cache.js:
--------------------------------------------------------------------------------
1 | import storage from '../utils/cache';
2 |
3 | const SEARCH_SHITORY = '__SEARCH_HISTORY__';
4 | const FAVORITE_LIST = '__FAVORITE_LIST__';
5 | const PLAY_HISTORY = '__PLAY_HISTORY__';
6 |
7 | export function loadSearchHistory() {
8 | return storage.getItem(SEARCH_SHITORY);
9 | }
10 |
11 | export function cacheSearchHistory(history) {
12 | return storage.setItem(SEARCH_SHITORY, history);
13 | }
14 |
15 | export function loadFavoriteList() {
16 | return storage.getItem(FAVORITE_LIST);
17 | }
18 |
19 | export function cacheFavoriteList(list) {
20 | return storage.setItem(FAVORITE_LIST, list);
21 | }
22 |
23 | export function loadPlayHistory() {
24 | return storage.getItem(PLAY_HISTORY);
25 | }
26 |
27 | export function cachePlayHistory(history) {
28 | return storage.setItem(PLAY_HISTORY, history);
29 | }
30 |
--------------------------------------------------------------------------------
/src/services/config.js:
--------------------------------------------------------------------------------
1 | export const mode = {
2 | loop: 0,
3 | random: 1,
4 | once: 2
5 | };
--------------------------------------------------------------------------------
/src/services/song.js:
--------------------------------------------------------------------------------
1 | class Song {
2 | constructor({id, singer, name, image, desc, alias}) {
3 | this.id = id;
4 | this.singer = singer;
5 | this.name = name;
6 | this.image = image;
7 | this.desc = desc;
8 | this.alias = alias;
9 | }
10 | }
11 |
12 | function singerName(names) {
13 | let name = [];
14 | name = names.map((item) => {
15 | return item.name;
16 | });
17 |
18 | return name.join('/');
19 | }
20 |
21 | export function createSong(song) {
22 | return new Song({
23 | id: song.id,
24 | name: `${song.name}`,
25 | singer: singerName(song.ar),
26 | image: song.al.picUrl,
27 | desc: `${song.al.name}`
28 | });
29 | }
30 |
31 | export function createSearchSong(song) {
32 | return new Song({
33 | id: song.id,
34 | name: song.name,
35 | singer: singerName(song.artists),
36 | alias: song.alias ? song.alias[0] : '',
37 | image: song.artists[0].img1v1Url
38 | });
39 | }
--------------------------------------------------------------------------------
/src/styles/base.styl:
--------------------------------------------------------------------------------
1 | @import "./variables.styl"
2 |
3 | body
4 | line-height: 1
5 | font-family: 'PingFang SC', 'STHeitiSC-Light', 'Helvetica-Light', arial, sans-serif, 'Droid Sans Fallback'
6 | user-select: none
7 | background: $color-background
8 | color: $color-text
--------------------------------------------------------------------------------
/src/styles/highlight.styl:
--------------------------------------------------------------------------------
1 | @import "./variables"
2 |
3 | .highlight-text
4 | color: $color-primary
--------------------------------------------------------------------------------
/src/styles/index.styl:
--------------------------------------------------------------------------------
1 | @import "./reset.styl"
2 | @import "./base.styl"
3 | @import "./highlight.styl"
--------------------------------------------------------------------------------
/src/styles/mixins.styl:
--------------------------------------------------------------------------------
1 | //
2 | // Mixins
3 | // --------------------------------------------------
4 |
5 | @import "./variables"
6 |
7 | size()
8 | if length(arguments) == 1
9 | width: arguments[0]
10 | height: arguments[0]
11 | else
12 | width: arguments[0]
13 | height: arguments[1]
14 |
15 | no-wrap()
16 | text-overflow: ellipsis
17 | overflow: hidden
18 | white-space: nowrap
19 |
20 | -pos(type, args)
21 | i = 0
22 | position: unquote(type)
23 | for j in (1..4)
24 | if length(args) > i
25 | {args[i]}: args[i + 1] is a 'unit' ? args[i += 1] : 0
26 | i += 1
27 |
28 | fixed()
29 | -pos('fixed', arguments)
30 |
31 | absolute()
32 | -pos('absolute', arguments)
33 |
34 | relative()
35 | -pos('relative', arguments)
36 |
37 |
38 | border-1px($color = $color-border, $left = 0, $right = 0)
39 | position: relative
40 | &::after
41 | content: ''
42 | absolute: left $left bottom 0 right $right
43 | border-top: 1px solid $color
44 | transform: scaleY(0.5)
45 |
46 | background-filter($blur = 80px, $scale = 1.5, $background = rgba(7, 17, 27, 0.2))
47 | absolute: top 0 left 0 right 0 bottom 0
48 | background-size: cover
49 | filter: blur($blur)
50 | transform: scale($scale)
51 | z-index: -1
52 | &::before
53 | content: ''
54 | absolute: top 0 left 0 right 0 bottom 0
55 | background: $background
56 |
--------------------------------------------------------------------------------
/src/styles/reset.styl:
--------------------------------------------------------------------------------
1 | /**
2 | * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
3 | * http://cssreset.com
4 | */
5 | html, body, div, span, applet, object, iframe,
6 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
7 | a, abbr, acronym, address, big, cite, code,
8 | del, dfn, em, img, ins, kbd, q, s, samp,
9 | small, strike, strong, sub, sup, tt, var,
10 | b, u, i, center,
11 | dl, dt, dd, ol, ul, li,
12 | fieldset, form, label, legend,
13 | table, caption, tbody, tfoot, thead, tr, th, td,
14 | article, aside, canvas, details, embed,
15 | figure, figcaption, footer, header,
16 | menu, nav, output, ruby, section, summary,
17 | time, mark, audio, video, input
18 | margin: 0
19 | padding: 0
20 | border: 0
21 | font-size: 100%
22 | font-weight: normal
23 | vertical-align: baseline
24 |
25 | /* HTML5 display-role reset for older browsers */
26 | article, aside, details, figcaption, figure,
27 | footer, header, menu, nav, section
28 | display: block
29 |
30 | body
31 | line-height: 1
32 |
33 | blockquote, q
34 | quotes: none
35 |
36 | blockquote:before, blockquote:after,
37 | q:before, q:after
38 | content: none
39 |
40 | table
41 | border-collapse: collapse
42 | border-spacing: 0
43 |
44 | /* custom */
45 |
46 | a
47 | color: #7e8c8d
48 | -webkit-backface-visibility: hidden
49 | text-decoration: none
50 |
51 | li
52 | list-style: none
53 |
54 | body
55 | -webkit-text-size-adjust: none
56 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0)
57 |
--------------------------------------------------------------------------------
/src/styles/variables.styl:
--------------------------------------------------------------------------------
1 | //
2 | // Variables
3 | // --------------------------------------------------
4 |
5 | $color-background = white
6 | $color-highlight-background = rgb(253, 108, 98)
7 | $color-theme = rgb(212, 68, 57)
8 | $color-sub-theme = rgb(240, 116, 107)
9 | $color-text = #2E3030
10 | $color-text-l = #757575
11 | $color-text-g = #c7c7c7
12 | $color-text-ll = rgba(255, 255, 255, 0.8)
13 | $color-border = #dddddd
14 | $color-overlay = rgba(7, 17, 27, 0.4)
15 |
16 | $white = #ffffff
17 | $color-primary = #337ab7
18 |
19 | $font-size-small-s = 10px
20 | $font-size-small = 12px
21 | $font-size-medium = 14px
22 | $font-size-medium-x = 16px
23 | $font-size-large = 18px
24 | $font-size-large-x = 24px
25 | $font-size-default = $font-size-medium-x
26 |
27 | $font-size-icon = 28px
--------------------------------------------------------------------------------
/src/utils/axios.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import axios from 'axios';
3 |
4 | Vue.prototype.$http = axios;
5 |
--------------------------------------------------------------------------------
/src/utils/cache.js:
--------------------------------------------------------------------------------
1 | function serialize(val) {
2 | return JSON.stringify(val);
3 | }
4 |
5 | function deserialize(val) {
6 | return JSON.parse(val);
7 | }
8 |
9 | const storage = {
10 | store: window.localStorage,
11 |
12 | getItem(key) {
13 | return deserialize(this.store.getItem(key));
14 | },
15 | setItem(key, val) {
16 | this.store.setItem(key, serialize(val));
17 |
18 | return val;
19 | },
20 | removeItem(key) {
21 | this.store.removeItem(key);
22 | }
23 | };
24 |
25 | export default storage;
--------------------------------------------------------------------------------
/src/utils/console.js:
--------------------------------------------------------------------------------
1 | export function loadScript(src, cb) {
2 | const script = document.createElement('script');
3 | script.type = 'text/javascript';
4 | script.src = src;
5 |
6 | let flag = false; // 防止 IE9/10 中执行两次
7 |
8 | script.onload = script.onreadystatechange = function() {
9 | if (
10 | flag === false &&
11 | (!this.readyState || this.readyState === 'complete')
12 | ) {
13 | flag = true;
14 | cb && cb();
15 | }
16 | };
17 |
18 | const s = document.getElementsByTagName('script')[0];
19 | s.parentNode.insertBefore(script, s);
20 | }
21 |
22 | let vconsole;
23 | let count = 0;
24 | const url =
25 | 'https://res.wx.qq.com/mmbizwap/zh_CN/htmledition/js/vconsole/3.0.0/vconsole.min.js';
26 |
27 | // 通过 url 唤醒
28 | if (/console/g.test(location.href)) {
29 | loadScript(url, function() {
30 | if (typeof vconsole === 'undefined') {
31 | /* eslint-disable */
32 | vconsole = new VConsole();
33 | }
34 | });
35 | }
36 |
37 | // 通过点击事件
38 | window.addEventListener('load', function() {
39 | const entry = document.querySelector('#vconsole-secret');
40 |
41 | // 点击 #vconsole-secret 的元素唤起
42 | entry &&
43 | entry.addEventListener('click', function() {
44 | count++;
45 |
46 | if (count > 5) {
47 | count = -10000;
48 | loadScript(url, function() {
49 | if (typeof vconsole === 'undefined') {
50 | /* eslint-disable */
51 | vconsole = new VConsole();
52 | }
53 | });
54 | }
55 | });
56 | });
57 |
--------------------------------------------------------------------------------
/src/utils/directives.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 |
3 | Vue.directive('focus', {
4 | inserted(el) {
5 | el.focus();
6 | }
7 | });
--------------------------------------------------------------------------------
/src/utils/filters.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 |
3 | import { leftpad } from './index';
4 |
5 | function fillZero(str) {
6 | return leftpad(str, 2, '0');
7 | }
8 |
9 | Vue.filter('date', (value) => {
10 | const date = new Date(parseInt(value, 10));
11 | return value ? `${date.getFullYear()}年${fillZero(date.getMonth() + 1)}月\
12 | ${fillZero(date.getDate())}日` : '';
13 | });
14 |
15 | Vue.filter('highlight', (value, key) => {
16 | const reg = new RegExp(key, 'g');
17 | return value.replace(reg, `${key}`);
18 | });
19 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export function leftpad(str, len, ch = ' ') {
2 | str = `${str}`;
3 |
4 | for (let i = str.length; i < len; i++) {
5 | str = ch + str;
6 | }
7 |
8 | return str;
9 | };
10 |
11 | export function shuffle(array) {
12 | const items = array.slice();
13 | let t, r, i;
14 |
15 | for (i = items.length - 1; i > 0; i--) {
16 | r = Math.round(Math.random() * i);
17 |
18 | t = items[i];
19 | items[i] = items[r];
20 | items[r] = t;
21 | }
22 |
23 | return items;
24 | }
25 |
26 | export function debounce(func, delay) {
27 | let timer;
28 |
29 | return function(...args) {
30 | if (timer) {
31 | clearTimeout(timer);
32 | }
33 |
34 | timer = setTimeout(() => {
35 | func.apply(this, args);
36 | }, delay);
37 | };
38 | }
--------------------------------------------------------------------------------
/src/utils/mixins.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import { mapGetters } from 'vuex';
3 |
4 | Vue.mixin({});
5 |
6 | const PLAYLIST = 'playlist';
7 |
8 | export const playlistMixin = {
9 | computed: {
10 | ...mapGetters([
11 | 'playlist'
12 | ])
13 | },
14 | methods: {
15 | appendBottom(playlist = this.playlist) {
16 | this.$nextTick(() => {
17 | const el = this.$refs.scroll.$el.children[0];
18 | const lastChild = el.lastChild;
19 |
20 | if (playlist.length && lastChild && lastChild.nodeName !== '#text' && lastChild.getAttribute(PLAYLIST) !== PLAYLIST) {
21 | const bottom = document.createElement('div');
22 | bottom.style.height = '60px';
23 | bottom.setAttribute(PLAYLIST, PLAYLIST);
24 |
25 | el.appendChild(bottom);
26 | this.$refs.scroll.refresh();
27 | } else if (!playlist.length && lastChild && lastChild.nodeName !== '#text' && lastChild.getAttribute(PLAYLIST) === PLAYLIST) {
28 | el.removeChild(lastChild);
29 | this.$refs.scroll.refresh();
30 | }
31 | });
32 | }
33 | },
34 | watch: {
35 | playlist(val) {
36 | this.appendBottom(val);
37 | }
38 | },
39 | mounted() {
40 | this.appendBottom(this.playlist);
41 | }
42 | };
43 |
44 | export const showMixin = {
45 | data() {
46 | return {
47 | show: false,
48 | name: 'default'
49 | };
50 | },
51 | methods: {
52 | open(name = this.name) {
53 | this.show = true;
54 | this.name = name;
55 | const hash = location.hash ? `&${name}` : `#${name}`;
56 | history.pushState({ page: name }, name, `${location.href}${hash}`);
57 | },
58 | back() {
59 | history.go(-1);
60 | },
61 | close() {
62 | if (location.hash.indexOf(this.name) === -1) {
63 | this.show = false;
64 | }
65 | }
66 | },
67 | created() {
68 | addEventListener('popstate', this.close);
69 | },
70 | beforeDestroy() {
71 | removeEventListener('popstate', this.close);
72 | }
73 | };
74 |
--------------------------------------------------------------------------------
/src/vuex/actions.js:
--------------------------------------------------------------------------------
1 | import * as types from './mutation-types';
2 | import { cacheSearchHistory, cacheFavoriteList, cachePlayHistory } from '../services/cache';
3 |
4 | const actions = {
5 | selectPlay({ commit, state }, { list, index }) {
6 | commit(types.SET_PLAYLIST, list);
7 | commit(types.SET_SEQUENCE_LIST, list);
8 | commit(types.SET_CURRENT_INDEX, index);
9 | commit(types.SET_FULL_SCREEN, true);
10 | commit(types.SET_PLAYING, true);
11 | },
12 |
13 | deleteSong({ commit, state, dispatch }, { song }) {
14 | const playlist = state.playlist.slice();
15 | const sequenceList = state.sequenceList.slice();
16 | let currentIndex = state.currentIndex;
17 |
18 | if (playlist.length === 1) {
19 | dispatch('clearPlaylist');
20 | return;
21 | }
22 |
23 | const pIndex = playlist.findIndex(item => item.id === song.id);
24 | const sIndex = sequenceList.findIndex(item => item.id === song.id);
25 |
26 | playlist.splice(pIndex, 1);
27 | sequenceList.splice(sIndex, 1);
28 |
29 | if (currentIndex > pIndex || currentIndex === playlist.length) {
30 | currentIndex--;
31 | }
32 |
33 | commit(types.SET_PLAYLIST, playlist);
34 | commit(types.SET_SEQUENCE_LIST, sequenceList);
35 | commit(types.SET_CURRENT_INDEX, currentIndex);
36 | },
37 |
38 | clearPlaylist({ commit }) {
39 | commit(types.SET_FULL_SCREEN, false);
40 | commit(types.SET_PLAYLIST, []);
41 | commit(types.SET_SEQUENCE_LIST, []);
42 | commit(types.SET_CURRENT_INDEX, -1);
43 | commit(types.SET_PLAYING, false);
44 | },
45 |
46 | insertSong({ commit, state }, song) {
47 | const playlist = state.playlist.slice();
48 | const sequenceList = state.sequenceList.slice();
49 |
50 | const fIndex = playlist.findIndex(item => {
51 | return item.id === song.id;
52 | });
53 |
54 | let index = 0;
55 |
56 | if (fIndex > -1) {
57 | index = fIndex;
58 | } else {
59 | playlist.unshift(song);
60 | sequenceList.unshift(song);
61 | }
62 |
63 | commit(types.SET_PLAYLIST, playlist);
64 | commit(types.SET_SEQUENCE_LIST, sequenceList);
65 | commit(types.SET_CURRENT_INDEX, index);
66 | commit(types.SET_FULL_SCREEN, true);
67 | commit(types.SET_PLAYING, true);
68 | },
69 |
70 | saveSearchHistory({ commit, state }, query) {
71 | const searchHistory = state.searchHistory.slice();
72 |
73 | const index = searchHistory.findIndex(item => item === query);
74 |
75 | if (index > -1) {
76 | searchHistory.splice(index, 1);
77 | }
78 |
79 | searchHistory.unshift(query);
80 |
81 | if (searchHistory.length > 10) {
82 | searchHistory.pop();
83 | }
84 |
85 | commit(types.SET_SEARCH_HISTORY, cacheSearchHistory(searchHistory));
86 | },
87 |
88 | deleteSearchHitory({ commit, state }, index) {
89 | const searchHistory = state.searchHistory.slice();
90 |
91 | searchHistory.splice(index, 1);
92 |
93 | commit(types.SET_SEARCH_HISTORY, cacheSearchHistory(searchHistory));
94 | },
95 |
96 | clearSearchHistory({ commit }) {
97 | commit(types.SET_SEARCH_HISTORY, cacheSearchHistory([]));
98 | },
99 |
100 | saveFavoriteList({ commit, state }, song) {
101 | const favoriteList = state.favoriteList.slice();
102 |
103 | const index = favoriteList.findIndex(item => item.id === song.id);
104 |
105 | if (index > -1) {
106 | favoriteList.splice(index, 1);
107 | }
108 |
109 | favoriteList.unshift(song);
110 |
111 | if (favoriteList.length > 100) {
112 | favoriteList.pop();
113 | }
114 |
115 | commit(types.SET_FAVORITE_LIST, cacheFavoriteList(favoriteList));
116 | },
117 |
118 | deleteFavoriteList({ commit, state }, song) {
119 | const favoriteList = state.favoriteList.slice();
120 |
121 | const index = favoriteList.findIndex(item => song.id === item.id);
122 |
123 | favoriteList.splice(index, 1);
124 | commit(types.SET_FAVORITE_LIST, cacheFavoriteList(favoriteList));
125 | },
126 |
127 | savePlayHistory({ commit, state }, song) {
128 | const playHistory = state.playHistory.slice();
129 |
130 | const index = playHistory.findIndex(item => item.id === song.id);
131 |
132 | if (index > -1) {
133 | playHistory.splice(index, 1);
134 | }
135 |
136 | playHistory.unshift(song);
137 |
138 | if (playHistory.length > 100) {
139 | playHistory.pop();
140 | }
141 |
142 | commit(types.SET_PLAY_HISTORY, cachePlayHistory(playHistory));
143 | }
144 | };
145 |
146 | export default actions;
147 |
--------------------------------------------------------------------------------
/src/vuex/getters.js:
--------------------------------------------------------------------------------
1 | const getters = {
2 | musicList: state => state.musicList,
3 |
4 | playlist: state => state.playlist,
5 |
6 | currentIndex: state => state.currentIndex,
7 |
8 | currentSong: state => state.playlist[state.currentIndex] || {},
9 |
10 | fullScreen: state => state.fullScreen,
11 |
12 | playing: state => state.playing,
13 |
14 | sequenceList: state => state.sequenceList,
15 |
16 | mode: state => state.mode,
17 |
18 | tabIndex: state => state.tabIndex,
19 |
20 | singer: state => state.singer,
21 |
22 | topList: state => state.topList,
23 |
24 | searchHistory: state => state.searchHistory,
25 |
26 | favoriteList: state => state.favoriteList,
27 |
28 | playHistory: state => state.playHistory
29 | };
30 |
31 | export default getters;
32 |
--------------------------------------------------------------------------------
/src/vuex/mutation-types.js:
--------------------------------------------------------------------------------
1 | export const SET_MUSIC_LIST = 'SET_MUSIC_LIST';
2 |
3 | export const SET_PLAYLIST = 'SET_PLAYLIST';
4 |
5 | export const SET_CURRENT_INDEX = 'SET_CURRENT_INDEX';
6 |
7 | export const SET_FULL_SCREEN = 'SET_FULL_SCREEN';
8 |
9 | export const SET_PLAYING = 'SET_PLAYING';
10 |
11 | export const SET_SEQUENCE_LIST = 'SET_SEQUENCE_LIST';
12 |
13 | export const SET_MODE = 'SET_MODE';
14 |
15 | export const SET_SINGER = 'SET_SINGER';
16 |
17 | export const SET_TOP_LIST = 'SET_TOP_LIST';
18 |
19 | export const SET_SEARCH_HISTORY = 'SET_SEARCH_HISTORY';
20 |
21 | export const SET_FAVORITE_LIST = 'SET_FAVORITE_LIST';
22 |
23 | export const SET_PLAY_HISTORY = 'SET_PLAY_HISTORY';
--------------------------------------------------------------------------------
/src/vuex/mutations.js:
--------------------------------------------------------------------------------
1 | import * as types from './mutation-types';
2 |
3 | const mutations = {
4 | [types.SET_MUSIC_LIST](state, musicList) {
5 | state.musicList = musicList;
6 | },
7 |
8 | [types.SET_PLAYLIST](state, playlist) {
9 | state.playlist = playlist;
10 | },
11 |
12 | [types.SET_CURRENT_INDEX](state, index) {
13 | state.currentIndex = index;
14 | },
15 |
16 | [types.SET_FULL_SCREEN](state, flag) {
17 | state.fullScreen = flag;
18 | },
19 |
20 | [types.SET_PLAYING](state, flag) {
21 | state.playing = flag;
22 | },
23 |
24 | [types.SET_SEQUENCE_LIST](state, list) {
25 | state.sequenceList = list;
26 | },
27 |
28 | [types.SET_MODE](state, mode) {
29 | state.mode = mode;
30 | },
31 |
32 | [types.SET_SINGER](state, singer) {
33 | state.singer = singer;
34 | },
35 |
36 | [types.SET_TOP_LIST](state, list) {
37 | state.topList = list;
38 | },
39 |
40 | [types.SET_SEARCH_HISTORY](state, history) {
41 | state.searchHistory = history;
42 | },
43 |
44 | [types.SET_FAVORITE_LIST](state, list) {
45 | state.favoriteList = list;
46 | },
47 |
48 | [types.SET_PLAY_HISTORY](state, history) {
49 | state.playHistory = history;
50 | }
51 | };
52 |
53 | export default mutations;
54 |
--------------------------------------------------------------------------------
/src/vuex/state.js:
--------------------------------------------------------------------------------
1 | import { mode } from '../services/config';
2 | import { loadSearchHistory, loadFavoriteList, loadPlayHistory } from '../services/cache';
3 |
4 | const state = {
5 | musicList: {},
6 |
7 | playlist: [],
8 |
9 | sequenceList: [],
10 |
11 | currentIndex: -1,
12 |
13 | fullScreen: false,
14 |
15 | playing: false,
16 |
17 | mode: mode.loop,
18 |
19 | singer: {},
20 |
21 | topList: {},
22 |
23 | searchHistory: loadSearchHistory() || [],
24 |
25 | favoriteList: loadFavoriteList() || [],
26 |
27 | playHistory: loadPlayHistory() || []
28 | };
29 |
30 | export default state;
31 |
--------------------------------------------------------------------------------
/src/vuex/store.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 | import state from './state';
4 | import getters from './getters';
5 | import mutations from './mutations';
6 | import actions from './actions';
7 |
8 | import createLogger from 'vuex/dist/logger';
9 |
10 | Vue.use(Vuex);
11 |
12 | export default new Vuex.Store({
13 | state,
14 | mutations,
15 | getters,
16 | actions,
17 |
18 | plugins: process.env.NODE_ENV !== 'production' ? [createLogger()] : []
19 | });
20 |
--------------------------------------------------------------------------------
/static/icons/music-120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/static/icons/music-120.png
--------------------------------------------------------------------------------
/static/icons/music-144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/static/icons/music-144.png
--------------------------------------------------------------------------------
/static/icons/music-152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/static/icons/music-152.png
--------------------------------------------------------------------------------
/static/icons/music-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/static/icons/music-192.png
--------------------------------------------------------------------------------
/static/icons/music-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/static/icons/music-256.png
--------------------------------------------------------------------------------
/static/icons/music-384.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/static/icons/music-384.png
--------------------------------------------------------------------------------
/static/icons/music-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/static/icons/music-48.png
--------------------------------------------------------------------------------
/static/icons/music-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lpeihan/vue-music-webapp/e058cf522d731c80881c93f3364335136203b33e/static/icons/music-512.png
--------------------------------------------------------------------------------
/static/manifest.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "name": "Netease",
4 | "short_name": "Netease",
5 | "icons": [{
6 | "src": "/icons/music-120.png",
7 | "sizes": "120x120",
8 | "type": "image/png"
9 | }, {
10 | "src": "/icons/music-144.png",
11 | "sizes": "144x144",
12 | "type": "image/png"
13 | }, {
14 | "src": "/icons/music-152.png",
15 | "sizes": "152x152",
16 | "type": "image/png"
17 | }, {
18 | "src": "/icons/music-192.png",
19 | "sizes": "192x192",
20 | "type": "image/png"
21 | }, {
22 | "src": "/icons/music-256.png",
23 | "sizes": "256x256",
24 | "type": "image/png"
25 | }, {
26 | "src": "/icons/music-384.png",
27 | "sizes": "384x384",
28 | "type": "image/png"
29 | }, {
30 | "src": "/icons/music-512.png",
31 | "sizes": "512x512",
32 | "type": "image/png"
33 | }],
34 | "start_url": "/",
35 | "background_color": "#f2f3f5",
36 | "display": "standalone",
37 | "theme_color": "#d44439"
38 | }
--------------------------------------------------------------------------------
{{currentSong.name}}
22 |{{currentSong.singer}}
23 |