Repository: tgxhx/vue-music
Branch: master
Commit: 51fb6c4d80f8
Files: 62
Total size: 150.8 KB
Directory structure:
gitextract_afk0_ghf/
├── .babelrc
├── .editorconfig
├── .gitignore
├── .postcssrc.js
├── README.md
├── build/
│ ├── build.js
│ ├── check-versions.js
│ ├── dev-client.js
│ ├── dev-server.js
│ ├── utils.js
│ ├── vue-loader.conf.js
│ ├── webpack.base.conf.js
│ ├── webpack.dev.conf.js
│ └── webpack.prod.conf.js
├── config/
│ ├── dev.env.js
│ ├── index.js
│ └── prod.env.js
├── index.html
├── package.json
├── screen/
│ └── 1.txt
├── src/
│ ├── App.vue
│ ├── assets/
│ │ ├── css/
│ │ │ ├── base.scss
│ │ │ ├── common.css
│ │ │ └── reset.css
│ │ ├── js/
│ │ │ ├── api.js
│ │ │ ├── filters.js
│ │ │ └── setStyle.js
│ │ └── login/
│ │ └── login.json
│ ├── components/
│ │ ├── AudiioNav.vue
│ │ ├── Comment.vue
│ │ ├── Cover.vue
│ │ ├── Header.vue
│ │ ├── Hello.vue
│ │ ├── ListTitle.vue
│ │ ├── PlayList.vue
│ │ ├── Player.vue
│ │ └── Root.vue
│ ├── main.js
│ ├── page/
│ │ ├── Index/
│ │ │ ├── Index.vue
│ │ │ ├── Radio.vue
│ │ │ ├── Recommend.vue
│ │ │ ├── SongList.vue
│ │ │ └── Top.vue
│ │ ├── PlayListDetail.vue
│ │ ├── home/
│ │ │ └── Home.vue
│ │ └── search/
│ │ ├── Album.vue
│ │ ├── Artist.vue
│ │ ├── ListItem.vue
│ │ ├── Search.vue
│ │ ├── Song.vue
│ │ └── list.vue
│ ├── router/
│ │ └── index.js
│ └── store/
│ ├── actions.js
│ ├── getters.js
│ ├── index.js
│ ├── mutation-type.js
│ └── mutations.js
└── static/
├── .gitkeep
└── iconfont/
├── MaterialIcons-Regular.ijmap
├── README.md
├── codepoints
└── material-icons.css
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": [
["env", { "modules": false }],
"stage-2"
],
"plugins": ["transform-runtime"],
"comments": false,
"env": {
"test": {
"presets": ["env", "stage-2"],
"plugins": [ "istanbul" ]
}
}
}
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules/
dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea/
.history
.vscode
package-lock.json
================================================
FILE: .postcssrc.js
================================================
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
"plugins": {
// to edit target browsers: use "browserlist" field in package.json
"autoprefixer": {}
}
}
================================================
FILE: README.md
================================================
# vue版网易云音乐
> api:ap使用的是一个开源的nodejs封装的网易云音乐api,[地址](https://binaryify.github.io/NeteaseCloudMusicApi/#/?id=%e5%ae%89%e8%a3%85)
> [预览地址](http://39.108.14.248/music)(国外的服务器,估计有点慢)
## 使用步骤
``` bash
# 下载
git clone git@github.com:tgxhx/vue-music.git
# 安装依赖
npm install
# 运行开发环境
npm run dev
# 构建生产环境
npm run build
```
## 技术栈
+ vue2:基础框架
+ vue-router2:路由跳转
+ vuex:全局数据管理
+ es6:采用部分es6特性,大大简化了写法
+ webpack:vue-cli基于webpack,修改了部分配置
+ axios:基于Promise的http库,用来请求数据
+ scss:写起css来十分方便,需要安装sass-loader,另外使用自定义其中的自定义函数配合rem来适配移动端设备简直不要太方便
+ flex:弹性布局,在移动端兼容性较好,写各种布局非常方便
+ [vue-material](https://github.com/vuematerial/vue-material):一个Android上material design风格的组件库
## 说明
1. 这个项目大小组件大概二十几个,目前还未完工,以后还会继续更新,目标是还原整个网易云音乐(虽然不知道什么时候能填完坑o(╯□╰)o)。
2. 关于路由,刚开始设计路由的时候想了很多,由于一开始的目标就是网易云音乐的所有内容,所以设计了三级路由,/root/index/recommed,大概这样,[点击查看](https://github.com/tgxhx/vue-music/blob/e43c4975e42b93b6ccf5d8609687879a589d4d8f/src/router/index.js)。
3. 关于体会,写到这个项目也算是对vue全家桶比较熟了,体会就是,不论是简单的复杂的项目,只要分割成一个个组件再拼起来,也没什么难的,组件之间通信又有vuex,也是非常简单的,总之就是熟能生巧。
4. 关于难点,对于自己来说,audio标签以前没有接触过,有点麻烦,不过还算有[相关文档](http://caibaojian.com/html5-audio.html);歌词滚动,参考[这里](http://www.brafox.com/post/2015/HTML5/js%E8%A7%A3%E6%9E%90lrc%E6%AD%8C%E8%AF%8D-%E5%88%B6%E4%BD%9C%E6%BB%9A%E5%8A%A8%E6%AD%8C%E8%AF%8D.html),部分歌曲还是有点问题,待解决;播放器背景图,这个当时考虑了很久,因为网易云音乐的播放器背景图就是当前歌曲的专辑图,还是模糊效果,最后实现方式是在当前组件的二级div设置当前歌曲的背景图,大小覆盖父元素,为防止第一次播放或者切歌的时候没有图,在组件的根元素也设置一张背景默认图,具体可以[点击查看](https://github.com/tgxhx/vue-music/blob/771218f2929de6b64a34c62597777a4fc0b6ed6e/src/components/Player.vue);当前歌曲在播放列表中的索引获取,通过es5中数组的findIndex方法,找到当前歌曲的id在数组中的位置,具体可以[点击查看](https://github.com/tgxhx/vue-music/blob/e43c4975e42b93b6ccf5d8609687879a589d4d8f/src/components/PlayList.vue)。
5. 关于打包后的资源路径,比如要放在二级目录www.xxx.com/music,那么修改config/index.js中的assetsPublicPath: '/music/'即可。
6. 关于移动设备适配,我使用的是js动态设置html元素font-size的方式,页面元素使用rem,这样可达到在不同设备下显示内容基本一样的效果,具体可以[点击](https://segmentfault.com/a/1190000008721148),使用方式比如width:100px;可以写成width:pr(100)。
7. 关于图标,使用的是淘宝的iconfont字体图标方案,相比于使用图片图标有许多优势,比如任意缩放、改变颜色、响应式适配设备等等,具体可以参考[这篇文章](https://www.w3cplus.com/css3/icon-fonts.html)。
## 功能
- [x] 首页歌单推荐
- [x] 歌单详情
- [x] 播放器
- [x] 播放暂停
- [x] 播放模式切歌(顺序和随机)
- [ ] 歌词滚动(待修改)
- [x] 歌曲拖动
- [x] 播放器底部背景
- [x] 播放底栏
- [x] 底栏歌词同步
- [x] 播放列表
- [x] 歌曲评论
- [x] 搜索推荐
- [x] 搜索分类
## 部分截图




================================================
FILE: build/build.js
================================================
require('./check-versions')()
process.env.NODE_ENV = 'production'
var ora = require('ora')
var rm = require('rimraf')
var path = require('path')
var chalk = require('chalk')
var webpack = require('webpack')
var config = require('../config')
var webpackConfig = require('./webpack.prod.conf')
var spinner = ora('building for production...')
spinner.start()
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
webpack(webpackConfig, function (err, stats) {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n')
console.log(chalk.cyan(' Build complete.\n'))
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
))
})
})
================================================
FILE: build/check-versions.js
================================================
var chalk = require('chalk')
var semver = require('semver')
var packageConfig = require('../package.json')
var shell = require('shelljs')
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
var versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
},
]
if (shell.which('npm')) {
versionRequirements.push({
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
})
}
module.exports = function () {
var warnings = []
for (var i = 0; i < versionRequirements.length; i++) {
var mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
if (warnings.length) {
console.log('')
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
for (var i = 0; i < warnings.length; i++) {
var warning = warnings[i]
console.log(' ' + warning)
}
console.log()
process.exit(1)
}
}
================================================
FILE: build/dev-client.js
================================================
/* eslint-disable */
require('eventsource-polyfill')
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
hotClient.subscribe(function (event) {
if (event.action === 'reload') {
window.location.reload()
}
})
================================================
FILE: build/dev-server.js
================================================
require('./check-versions')()
var config = require('../config')
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
}
var opn = require('opn')
var path = require('path')
var express = require('express')
var webpack = require('webpack')
var proxyMiddleware = require('http-proxy-middleware')
var webpackConfig = require('./webpack.dev.conf')
// default port where dev server listens for incoming traffic
var port = process.env.PORT || config.dev.port
// automatically open browser, if not set will be false
var autoOpenBrowser = !!config.dev.autoOpenBrowser
// Define HTTP proxies to your custom API backend
// https://github.com/chimurai/http-proxy-middleware
var proxyTable = config.dev.proxyTable
var app = express()
var compiler = webpack(webpackConfig)
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
quiet: true
})
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
log: () => {}
})
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})
// proxy api requests
Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context]
if (typeof options === 'string') {
options = { target: options }
}
app.use(proxyMiddleware(options.filter || context, options))
})
// handle fallback for HTML5 history API
app.use(require('connect-history-api-fallback')())
// serve webpack bundle output
app.use(devMiddleware)
// enable hot-reload and state-preserving
// compilation error display
app.use(hotMiddleware)
// serve pure static assets
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
app.use(staticPath, express.static('./static'))
var uri = 'http://localhost:' + port
var _resolve
var readyPromise = new Promise(resolve => {
_resolve = resolve
})
console.log('> Starting dev server...')
devMiddleware.waitUntilValid(() => {
console.log('> Listening at ' + uri + '\n')
// when env is testing, don't need open it
if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
opn(uri)
}
_resolve()
})
var server = app.listen(port)
module.exports = {
ready: readyPromise,
close: () => {
server.close()
}
}
================================================
FILE: build/utils.js
================================================
var path = require('path')
var config = require('../config')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
exports.assetsPath = function (_path) {
var assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}
exports.cssLoaders = function (options) {
options = options || {}
var cssLoader = {
loader: 'css-loader',
options: {
minimize: process.env.NODE_ENV === 'production',
sourceMap: options.sourceMap
}
}
// generate loader string to be used with extract text plugin
function generateLoaders (loader, loaderOptions) {
var loaders = [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
var output = []
var loaders = exports.cssLoaders(options)
for (var extension in loaders) {
var loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
================================================
FILE: build/vue-loader.conf.js
================================================
var utils = require('./utils')
var config = require('../config')
var isProduction = process.env.NODE_ENV === 'production'
module.exports = {
loaders: utils.cssLoaders({
sourceMap: isProduction
? config.build.productionSourceMap
: config.dev.cssSourceMap,
extract: isProduction
})
}
================================================
FILE: build/webpack.base.conf.js
================================================
var path = require('path')
var utils = require('./utils')
var config = require('../config')
var vueLoaderConfig = require('./vue-loader.conf')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
entry: {
app: './src/main.js'
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
'assets': resolve('src/assets')
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
exclude: resolve('/node_modules/'),
use: {
loader: 'babel-loader',
query: {
presets: ['es2015']
}
}
// include: [resolve('src'), resolve('test')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
},
{
test: /\.s[a|c]ss$/,
loader: 'style-loader!css-loader!sass-loader'
}
]
}
}
================================================
FILE: build/webpack.dev.conf.js
================================================
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
// add hot-reload related code to entry chunks
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})
module.exports = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
},
// cheap-module-eval-source-map is faster for development
devtool: '#cheap-module-eval-source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': config.dev.env
}),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
new FriendlyErrorsPlugin()
]
})
================================================
FILE: build/webpack.prod.conf.js
================================================
var path = require('path')
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var CopyWebpackPlugin = require('copy-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
var UglifyJsPlugin = require('uglifyjs-webpack-plugin')
var env = config.build.env
var webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
/* new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: true
}), */
new UglifyJsPlugin({
test: /\.js($|\?)/i
}),
// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css')
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin({
cssProcessorOptions: {
safe: true
}
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
}),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
if (config.build.productionGzip) {
var CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig
================================================
FILE: config/dev.env.js
================================================
var merge = require('webpack-merge')
var prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})
================================================
FILE: config/index.js
================================================
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')
module.exports = {
build: {
env: require('./prod.env'),
index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/music/',
productionSourceMap: true,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
},
dev: {
env: require('./dev.env'),
port: 8080,
autoOpenBrowser: true,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
cssSourceMap: false
}
}
================================================
FILE: config/prod.env.js
================================================
module.exports = {
NODE_ENV: '"production"'
}
================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>vue-music</title>
<script>
!function (n) {
var e = n.document,
t = e.documentElement,
i = 750,
d = i / 100,
o = "orientationchange" in n ? "orientationchange" : "resize",
a = function () {
var n = t.clientWidth || 320;
n > 750 && (n = 750);
t.style.fontSize = n / d + "px"
};
e.addEventListener && (n.addEventListener(o, a, !1), e.addEventListener("DOMContentLoaded", a, !1))
}(window);
</script>
<!--<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic">-->
<!--<link rel="stylesheet" href="//fonts.googleapis.com/icon?family=Material+Icons">-->
<link rel="stylesheet" href="//at.alicdn.com/t/font_2d9gf9alx3v26gvi.css">
<link rel="stylesheet" href="./static/iconfont/material-icons.css">
<style>
body {
font-size:.32rem;
}
</style>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
================================================
FILE: package.json
================================================
{
"name": "vue-music",
"version": "1.0.0",
"description": "A Vue.js project",
"author": "tgxh2020 <tgxh2020@gmail.com>",
"private": true,
"scripts": {
"dev": "node build/dev-server.js",
"start": "node build/dev-server.js",
"build": "node build/build.js"
},
"dependencies": {
"animate.css": "^3.5.2",
"axios": "^0.16.2",
"better-scroll": "^0.2.4",
"grade-js": "^1.0.10",
"muse-ui": "^2.0.3",
"node-sass": "^4.7.2",
"sass-loader": "^6.0.5",
"vue": "2.4.4",
"vue-awesome-swiper": "^2.4.2",
"vue-material": "^0.7.1",
"vue-router": "^2.3.1",
"vuex": "^2.3.1"
},
"devDependencies": {
"autoprefixer": "^6.7.2",
"babel-core": "^6.22.1",
"babel-generator": "^6.26.0",
"babel-loader": "^6.2.10",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-preset-env": "^1.3.2",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-2": "^6.22.0",
"babel-register": "^6.22.0",
"chalk": "^1.1.3",
"connect-history-api-fallback": "^1.3.0",
"copy-webpack-plugin": "^4.0.1",
"css-loader": "^0.28.0",
"eventsource-polyfill": "^0.9.6",
"express": "^4.14.1",
"extract-text-webpack-plugin": "^2.0.0",
"file-loader": "^0.11.1",
"friendly-errors-webpack-plugin": "^1.1.3",
"html-webpack-plugin": "^2.28.0",
"http-proxy-middleware": "^0.17.3",
"opn": "^4.0.2",
"optimize-css-assets-webpack-plugin": "^1.3.0",
"ora": "^1.2.0",
"rimraf": "^2.6.0",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"uglifyjs-webpack-plugin": "^1.1.0",
"url-loader": "^0.5.8",
"vue-loader": "^12.1.0",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "2.4.4",
"webpack": "^2.6.1",
"webpack-bundle-analyzer": "^2.2.1",
"webpack-dev-middleware": "^1.10.0",
"webpack-hot-middleware": "^2.18.0",
"webpack-merge": "^4.1.0"
},
"engines": {
"node": ">= 4.0.0",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}
================================================
FILE: screen/1.txt
================================================
http://wx4.sinaimg.cn/large/7b9a6229ly1fhwlyxi2mrj20ad0icn43.jpg
http://wx4.sinaimg.cn/large/7b9a6229ly1fhwlz0lmr2j20ac0idmzv.jpg
http://wx2.sinaimg.cn/large/7b9a6229ly1fhwlz4rv98j20ac0ic78c.jpg
http://wx1.sinaimg.cn/mw690/7b9a6229ly1fhwlz76gf8j20ac0idq4u.jpg
http://wx1.sinaimg.cn/mw690/7b9a6229ly1fhwlzckmt6j20ac0idtdf.jpg
http://wx2.sinaimg.cn/mw690/7b9a6229ly1fhwlzevul9j20ac0ic76k.jpg
http://wx3.sinaimg.cn/mw690/7b9a6229ly1fhwlzgb1uvj20ad0id0tp.jpg
http://wx1.sinaimg.cn/mw690/7b9a6229ly1fhwlzlcu5ej20ac0idq6w.jpg
================================================
FILE: src/App.vue
================================================
<template>
<div id="app">
<router-view></router-view>
<player></player>
<audio-nav></audio-nav>
<play-list></play-list>
<cover></cover>
<comment></comment>
</div>
</template>
<script type="text/ecmascript-6">
import AudioNav from './components/AudiioNav'
import Player from './components/Player'
import PlayList from './components/PlayList.vue'
import Cover from './components/Cover.vue'
import Comment from './components/Comment.vue'
export default {
data() {
return {}
},
components: {
AudioNav,Player,PlayList,Cover,Comment
}
}
</script>
<style>
#app {
}
</style>
================================================
FILE: src/assets/css/base.scss
================================================
@mixin ell() {
overflow: hidden;
-ms-text-overflow: ellipsis;
text-overflow: ellipsis;
white-space: nowrap;
}
$baseColor: #d33a31;
@function pr($px, $base-font-size: 50px) {
@if (unitless($px)) {
//@warn "Assuming #{$px} to be in pixels, attempting to convert it into pixels for you";
@return pr($px + 0px); // That may fail.
} @else if (unit($px) == rem) {
@return $px;
}
@return ($px / $base-font-size) * 1rem;
}
@font-face {
font-family: 'iconfont'; /* project id 311056 */
src: url('//at.alicdn.com/t/font_e3r3kwh70n8hyqfr.eot');
src: url('//at.alicdn.com/t/font_e3r3kwh70n8hyqfr.eot?#iefix') format('embedded-opentype'),
url('//at.alicdn.com/t/font_e3r3kwh70n8hyqfr.woff') format('woff'),
url('//at.alicdn.com/t/font_e3r3kwh70n8hyqfr.ttf') format('truetype'),
url('//at.alicdn.com/t/font_e3r3kwh70n8hyqfr.svg#iconfont') format('svg');
}
@mixin wh($w) {
width:$w;
height:$w;
}
@mixin ava($w) {
width:$w;
height:$w;
img {
width:100%;
height:100%;
}
}
@mixin dfcc() {
display: flex;
justify-content: center;
align-items: center;
}
@mixin overhide($n) {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: $n;
-webkit-box-orient: vertical;
}
//1px
@mixin _prefixDpr($selector, $position: 'top', $pseudo: 'before', $dpr: '2') {
@media only screen and (-webkit-min-device-pixel-ratio:$dpr) {
@if $dpr == '1.5' {
#{$selector}:#{$pseudo} {
-webkit-transform: scaleY(.7);
transform: scaleY(.7);
@if $position == 'top' {
-webkit-transform-origin: left top;
} @else if $position == 'bottom' {
-webkit-transform-origin: left bottom;
}
}
} @else if $dpr == '2' {
#{$selector}:#{$pseudo} {
-webkit-transform: scaleY(.5);
transform: scaleY(.5);
@if $position == 'top' {
-webkit-transform-origin: left top;
} @else if $position == 'bottom' {
-webkit-transform-origin: left bottom;
}
}
} @else if $dpr == '3' {
#{$selector}:#{$pseudo} {
-webkit-transform: scaleY(.3);
transform: scaleY(.3);
@if $position == 'top' {
-webkit-transform-origin: left top;
} @else if $position == 'bottom' {
-webkit-transform-origin: left bottom;
}
}
}
}
}
@mixin onepx($selector, $position: 'top',$pseudo: 'before', $color: #666) {
#{$selector}:#{$pseudo} {
content: ' ';
display: block;
border-top: 1px solid $color;
position: absolute;
left: 0;
right: 0;
}
#{$selector} {
position: relative;
&:#{$pseudo} {
@if #{$position} == 'top'{
top: 0;
} @else if #{$position} == 'bottom' {
bottom: 0;
}
}
}
@include _prefixDpr($selector, $position, $pseudo, '1.5');
@include _prefixDpr($selector, $position, $pseudo, '2');
@include _prefixDpr($selector, $position, $pseudo, '3');
}
================================================
FILE: src/assets/css/common.css
================================================
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
ul:not(.md-list)>li+li {
margin-top:0 !important;
}
================================================
FILE: src/assets/css/reset.css
================================================
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height:1;
font-size:0.32rem;
font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "WenQuanYi Micro Hei", "Microsoft Yahei", sans-serif;
}
ol, ul {
list-style: none;
}
a {
text-decoration: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
.clearfix:after, .clearfix:before {
display: table;
content: ''
}
.clearfix:after {
clear: both;
}
.clearfix {
*zoom: 1;
}
.fl {
float: left;
}
.fr {
float: right;
}
================================================
FILE: src/assets/js/api.js
================================================
import axios from 'axios'
const url = 'http://localhost:3000'
export default url
================================================
FILE: src/assets/js/filters.js
================================================
/**
* Created by 12 on 2017/6/11.
*/
const filters = {
//歌单播放数
playCount(value) {
if (value < 10000) {
return value
} else {
value = value + ''
return value.slice(0, value.length - 4)
}
}
}
export default filters
================================================
FILE: src/assets/js/setStyle.js
================================================
/**
* Created by 12 on 2017/6/11.
*/
function setStyle(obj,json) {
for (var i in json) {
obj.style[i] = json[i]
}
}
================================================
FILE: src/assets/login/login.json
================================================
{
"loginType": 1,
"code": 200,
"account": {
"id": 46909653,
"userName": "0_wo460520803@163.com",
"type": 0,
"status": 0,
"whitelistAuthority": 0,
"createTime": 0,
"salt": "",
"tokenVersion": 0,
"ban": 0,
"baoyueVersion": -2,
"donateVersion": 0,
"vipType": 0,
"viptypeVersion": 0,
"anonimousUser": false
}
}
================================================
FILE: src/components/AudiioNav.vue
================================================
<template>
<div class="audio-nav" v-if="JSON.stringify(curPlayMusic) !== '{}'">
<div class="music-wrap" @click="showPlayer">
<div class="music-img">
<img :src="curPlayMusic.detail.al.picUrl" alt="">
</div>
<div class="music-con">
<p class="music-name" v-if="curPlayMusic">{{curPlayMusic.detail.name}}</p>
<p class="music-lyric">{{curLyric}}</p>
</div>
</div>
<div class="music-btn">
<div class="nav-btn-play">
<i class="iconfont icon-ttpodicon" v-if="!playing" @click="play"></i>
<i class="iconfont icon-zanting" v-else @click="pause"></i>
</div>
<div class="nav-btn-list">
<i class="iconfont icon-liebiao" @click="showPlayList"></i>
</div>
</div>
</div>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState} from 'vuex'
export default {
data() {
return {
}
},
computed: {
...mapState([
'curPlayMusic', 'playing','curLyric'
])
},
mounted() {
this.$nextTick(() => {
})
},
methods: {
play() {
//audio标签在其他组件中,故手动获取dom操作
document.getElementById('music').play()
this.$store.dispatch('switchPlaying', true)
},
pause() {
document.getElementById('music').pause()
this.$store.dispatch('switchPlaying', false)
},
//显示播放器
showPlayer() {
this.$store.state.showPlayer = true
},
//显示播放列表
showPlayList() {
this.$store.dispatch('showPlayList', true)
}
},
filters: {},
watch: {
url(val, old) {
if (val == null) {
alert('播放失败,该歌曲需要收费')
}
}
}
}
</script>
<style lang="scss" type="text/scss">
@import '../assets/css/base';
.audio-nav {
display: flex;
justify-content: space-between;
align-items: center;
position: fixed;
left: 0;
bottom: 0;
right: 0;
height: pr(45);
box-shadow: 0 -2px 8px #ccc;
padding: pr(6);
background-color: #fff;
z-index: 1000;
.music-wrap {
display: flex;
flex:1;
.music-img {
width: pr(33);
img {
@include wh(100%);
}
}
.music-con {
display: flex;
flex-direction: column;
justify-content: center;
margin-left: pr(10);
p {
&:first-of-type {
font-size: pr(12);
}
&:nth-of-type(2) {
margin-top: pr(5);
font-size: pr(10);
color: #666;
height:pr(10);
@include ell;
}
}
}
}
.music-btn {
display: flex;
align-items: center;
.nav-btn-play {
display: flex;
justify-content: center;
align-items: center;
@include wh(pr(24));
border: 1px solid #666;
border-radius: 50%;
i {
font-size: pr(12);
}
}
.nav-btn-list {
margin-left: pr(15);
i {
font-size: pr(18);
}
}
}
}
</style>
================================================
FILE: src/components/Comment.vue
================================================
<template>
<div
class="comment"
:class="{show: showComment}"
v-if="JSON.stringify(curPlayMusic) !== '{}'">
<md-toolbar class="md-dense" :class="{show: showComment}">
<md-button class="md-icon-button">
<md-icon @click.native="hideComment">arrow_back</md-icon>
</md-button>
<h2 class="md-title" style="flex: 1">评论<span v-if="total">({{total}})</span></h2>
<md-button class="md-icon-button">
</md-button>
</md-toolbar>
<div class="comment-wrap" ref="comment">
<div class="music-info">
<div class="music-pic">
<img :src="curPlayMusic.detail.al.picUrl" alt="" v-if="JSON.stringify(curPlayMusic) !== '{}'">
</div>
<div class="music-name">
<p
v-if="JSON.stringify(curPlayMusic) !== '{}'">{{curPlayMusic.detail.name}}</p>
<p>
<span
v-for="(item,index) in curPlayMusic.detail.ar"
>{{item.name}}
<span v-if="index !== curPlayMusic.detail.ar.length - 1">/
</span>
</span>
</p>
</div>
</div>
<div class="wonderful-comments" v-show="!showLoading">
<div class="comment-title">
精彩评论
</div>
<ul class="comment-content">
<li v-for="(item,index) in hotComments">
<div class="comment-avatar">
<div><img :src="item.user.avatarUrl" alt=""></div>
</div>
<div class="comment-body">
<div class="comment-body-title">
<p>{{item.user.nickname}} <br/><span>2017年7月7日</span></p>
<p>{{item.likedCount}} <i class="iconfont icon-dianzan"></i></p>
</div>
<div class="comment-body-content">
<p>{{item.content}}</p>
</div>
</div>
</li>
</ul>
</div>
<div class="recent-comments" v-show="!showLoading">
<div class="comment-title">
最新评论
</div>
<ul class="comment-content">
<li v-for="(item,index) in comments">
<div class="comment-avatar">
<div><img :src="item.user.avatarUrl" alt=""></div>
</div>
<div class="comment-body">
<div class="comment-body-title">
<p>{{item.user.nickname}} <br/><span>2017年7月7日</span></p>
<p>{{item.likedCount}} <i class="iconfont icon-dianzan"></i></p>
</div>
<div class="comment-body-content">
<p>{{item.content}}</p>
</div>
</div>
</li>
</ul>
</div>
<div class="comment-loading" v-show="showLoading">
<md-spinner :md-size="40" md-indeterminate></md-spinner>
</div>
<div class="comment-b-loading" v-show="bottomLoading">
<md-spinner :md-size="30" md-indeterminate></md-spinner>
</div>
</div>
</div>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState} from 'vuex'
import url from '../assets/js/api'
export default {
data() {
return {
hotComments: [],
comments: [],
showLoading: false,
bottomLoading: false,
offset: 1,
total: '' //评论总数
}
},
computed: {
...mapState([
'commentId', 'showComment', 'curPlayMusic'
])
},
mounted() {
this.$nextTick(() => {
})
},
methods: {
//热门评论
fetHotComment(id) {
axios.get(`${url}/comment/music?id=${id}&limit=0`).then(res => {
this.hotComments = res.data.hotComments
this.showLoading = false //获取评论后隐藏loading
this.total = res.data.total
})
},
//获取更多评论
fetComment(id, offset) {
axios.get(`${url}/comment/music?id=${id}&limit=20&offset=${offset}`).then(res => {
this.comments = res.data.comments
this.bottomLoading = false
})
},
changeId() {
this.$store.dispatch('commentId', 23212)
},
//隐藏评论组件
hideComment() {
this.$store.state.showComment = false
}
},
filters: {},
watch: {
//监听评论id,改变时重新获取新歌曲评论
commentId(val, old) {
this.$store.state.showComment = true
this.showLoading = true
this.fetHotComment(val)
this.fetComment(val,1)
},
offset(val, old) {
this.fetComment(commentId, val)
},
}
}
</script>
<style lang="scss" type="text/scss">
@import '../assets/css/base';
.comment {
position: fixed;
left: 0;
bottom: -2000px;
right: 0;
background-color: #fff;
z-index: 1004;
transform: translate(0, 100%);
&.show {
position: fixed;
top: 0;
bottom: 0;
transform: translate(0, 0);
}
.md-toolbar {
position: absolute;
left: 0;
top: 0;
right: 0;
}
.comment-wrap {
height:100%;
padding-top:pr(48);
overflow-y: auto;
}
.music-info {
display: flex;
align-items: center;
height: pr(81);
padding: 0 pr(8);
.music-pic {
@include wh(pr(65));
img {
}
}
.music-name {
flex: 1;
margin-left: pr(10);
p {
&:nth-child(2) {
margin-top: pr(6);
font-size: pr(12);
color: #626262;
}
}
}
}
.comment-title {
display: flex;
align-items: center;
height: pr(24);
padding-left: pr(10);
color: #626262;
font-size: pr(12);
background-color: #f0f0f0;
}
.comment-content {
li {
display: flex;
padding-top: pr(10);
}
.comment-avatar {
display: flex;
justify-content: center;
width: pr(46);
div {
@include wh(pr(28));
border-radius: 50%;
overflow: hidden;
}
}
.comment-body {
flex: 1;
.comment-body-title {
display: flex;
justify-content: space-between;
padding-right: pr(10);
p {
&:nth-of-type(1) {
color: #616262;
font-size: pr(12);
line-height: 1.3;
span {
color: #acacac;
font-size: pr(10);
}
}
&:nth-of-type(2) {
margin-top: pr(3);
color: #616261;
font-size: pr(12);
}
}
}
.comment-body-content {
padding: pr(10);
padding-left: 0;
border-bottom: 1px solid #e0e0e0;
p {
font-size: pr(14);
line-height: 1.5;
color: #333;
}
}
}
}
.comment-loading {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-40%, -50%);
}
.comment-b-loading {
display: flex;
height:pr(40);
justify-content: center;
}
}
</style>
================================================
FILE: src/components/Cover.vue
================================================
<template>
<div class="bg-cover" :class="{hide:!showCover}" @touchmove.prevent @click="hideCover">
</div>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState} from 'vuex'
export default {
data() {
return {}
},
computed: {
...mapState([
'showCover' //控制是否添加hide class,隐藏或显示
])
},
mounted() {
this.$nextTick(() => {
})
},
methods: {
hideCover() {
this.$store.dispatch('showPlayList', false)
}
},
filters: {},
}
</script>
<style lang="scss" type="text/scss">
@import '../assets/css/base';
.bg-cover {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
opacity: 1;
z-index:1002;
background-color: rgba(0, 0, 0, .5);
transition: all .5s;
&.hide {
position: static;
opacity: 0;
}
}
</style>
================================================
FILE: src/components/Header.vue
================================================
<template>
<div>
</div>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState} from 'vuex'
export default {
data() {
return {}
},
mounted() {
this.(() => {
})
},
methods: {},
filters: {},
computed: {
...mapState([])
}
}
</script>
<style lang="scss" type="text/scss">
@import '../../assets/css/common';
</style>
================================================
FILE: src/components/Hello.vue
================================================
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<h2>Essential Links</h2>
<ul>
<li><a href="https://vuejs.org" target="_blank">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank">Forum</a></li>
<li><a href="https://gitter.im/vuejs/vue" target="_blank">Gitter Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank">Twitter</a></li>
<br>
<li><a href="http://vuejs-templates.github.io/webpack/" target="_blank">Docs for This Template</a></li>
</ul>
<h2>Ecosystem</h2>
<ul>
<li><a href="http://router.vuejs.org/" target="_blank">vue-router</a></li>
<li><a href="http://vuex.vuejs.org/" target="_blank">vuex</a></li>
<li><a href="http://vue-loader.vuejs.org/" target="_blank">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
import {mapState} from 'vuex'
import axios from 'axios'
export default {
name: 'hello',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
},
mounted() {
this.$nextTick(() => {
console.log(this.url)
this.getTest()
})
},
methods: {
getTest() {
axios.get(`${this.url}/lyric?id=347230`).then(res => {
console.log(1)
})
}
},
computed: {
...mapState([
'url'
])
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
================================================
FILE: src/components/ListTitle.vue
================================================
<template>
<h3 class="list-title">{{title}} <i class="iconfont icon-jiantou"></i></h3>
</template>
<script type="text/ecmascript-6">
export default {
props: ['title']
}
</script>
<style lang="scss" type="text/scss">
@import '../assets/css/base';
.list-title {
margin:pr(13) 0;
border-left:pr(3) solid $baseColor;
text-indent: pr(10);
font-size:pr(14);
.iconfont {
position: relative;
top:pr(-1);
height:100%;
font-size:pr(12);
}
}
</style>
================================================
FILE: src/components/PlayList.vue
================================================
<template>
<div class="play-list" :class="{showlist:showPlayList}">
<div class="play-list-title">
播放列表({{playList.length}}){{curMusicIndex}}
</div>
<div class="play-list-content">
<ul>
<li class="play-list-item" v-for="(item,index) in playList" :key="index">
<div
class="song-name"
:class="{current: item.id === curId}"
@click="playMusicFromList(item.id)">
<i class="iconfont icon-iconfonthuodonggonggao"></i>{{item.name}} -
<span v-for="(value,i) in item.artist">{{value.name}}
<span v-if="i !== item.artist.length - 1"> / </span>
</span>
</div>
<div class="song-del"><i class="iconfont icon-shanchu"></i></div>
</li>
</ul>
</div>
</div>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState,mapGetters} from 'vuex'
import url from '../assets/js/api'
export default {
data() {
return {}
},
computed: {
...mapState([
'showPlayList', 'playList','curPlayMusic','curMusicIndex'
]),
...mapGetters([
'curId'
]),
//当前播放歌曲在播放列表中的索引
curMusicIndexComputed() {
if (this.playList.length > 0) {
return this.playList.findIndex((ele) => {
return ele.id === this.curId
})
} else {
return 0
}
}
},
mounted() {
this.$nextTick(() => {
this.$store.dispatch('curMusicIndex', this.curMusicIndexComputed)
})
},
methods: {
hideCover() {
this.$store.dispatch('showPlayList', false)
},
//点击获取歌曲播放信息
playMusicFromList(id) {
function getUrl() {
return axios.get(`${url}/music/url?id=${id}`)
}
function getDetail() {
return axios.get(`${url}/song/detail?ids=${id}`)
}
function getLyric() {
return axios.get(`${url}/lyric?id=${id}`)
}
axios.all([getUrl(), getDetail(), getLyric()])
.then(axios.spread((res1, res2, res3) => {
const arr = [res1, res2, res3]
this.$store.dispatch('curPlayMusic', arr)
this.$store.dispatch('switchPlaying', true)
}))
}
},
filters: {},
watch: {
//监听当前歌曲在播放列表中的索引,改变就重新获取歌曲信息
curMusicIndex(val, old) {
this.playMusicFromList(this.playList[val].id)
console.log(this.playList[val].id)
}
}
}
</script>
<style lang="scss" type="text/scss">
@import '../assets/css/base';
.play-list {
position: fixed;
left: 0;
bottom: 0;
right: 0;
height: pr(394);
background-color: #fff;
z-index: 1003;
transform: translate(0, 100%);
&.showlist {
transition: all .3s;
transform: translate(0, 0);
}
.play-list-title {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 pr(10);
height: pr(40);
font-size: pr(14);
border-bottom: 1px solid #e0e0e0;
}
.play-list-content {
height:pr(354);
overflow-y: auto;
ul {
}
.play-list-item {
display: flex;
justify-content: space-between;
align-items: center;
height: pr(42);
padding: 0 pr(10);
border-bottom: 1px solid #e0e0e0;
.song-name {
flex: 1;
font-size: pr(14);
line-height: 1.5;
margin-right: pr(10);
@include ell;
.iconfont {
display: none;
}
&.current {
color:$baseColor;
.iconfont {
display: inline;
margin-top:pr(2);
margin-right:pr(5);
font-size:pr(14);
}
span {
color:$baseColor;
}
}
span {
color: #909090;
font-size: pr(10);
}
}
.song-del {
.iconfont {
font-size: pr(18);
}
}
}
}
}
</style>
================================================
FILE: src/components/Player.vue
================================================
<template>
<div class="player animated fadeIn"
:class="{show: showPlayer}"
@touchmove="moveProgress">
<audio :src="curPlayMusic.url"
id="music"
autoplay="autoplay"
ref="audio"
@timeupdate="timeProgress"
@ended="ended"
@play="startPlay"></audio>
<div class="player-wrap">
<md-toolbar class="md-dense player-header">
<md-button class="md-icon-button">
<md-icon @click.native="back">arrow_back</md-icon>
</md-button>
<h2 class="md-title" style="flex: 1" v-if="JSON.stringify(curPlayMusic) !== '{}'">
{{curPlayMusic.detail.name}}<br/>
<span
v-for="(item,index) in curPlayMusic.detail.ar" v-if="curPlayMusic">{{item.name}}
<span v-if="index !== curPlayMusic.detail.ar.length - 1">/ </span>
</span></h2>
<md-button class="md-icon-button">
<md-icon>share</md-icon>
</md-button>
</md-toolbar>
<div class="player-panel1" v-show="showPanel" @click="showPanelF">
<div class="player-line"></div>
<div class="player-needle"><img src="../assets/images/player-needle.png" alt=""></div>
<div class="record-cover">
<div class="record-bg rotate-bg animated slideInRight">
<div class="music-bg">
<!--判断curPlayMusic不为空-->
<img :src="curPlayMusic.detail.al.picUrl" alt="" v-if="JSON.stringify(curPlayMusic) !== '{}'">
</div>
</div>
</div>
<div class="player-bar">
<div class="bar-item"><i class="iconfont icon-like"></i></div>
<div class="bar-item"><i class="iconfont icon-download"></i></div>
<div class="bar-item" @click.stop="openComment(curPlayMusic.detail.id)"
v-if="JSON.stringify(curPlayMusic) !== '{}'"><i class="iconfont icon-pinglun"></i><span
class="comment-total">{{11111 | commentTotalFormat}}</span></div>
<div class="bar-item"><i class="iconfont icon-more1170511easyiconnet"></i></div>
</div>
</div>
<div class="player-panel2" v-show="!showPanel" @click="showPanelF">
<ul ref="lyric_wrap" class="lyric-wrap" v-if="lyric" :style="`transform: translateY(${marginTop}px)`">
<li v-for="(value,key) in lyric">{{value}}</li>
</ul>
<ul v-else class="lyric-wrap">
<li>暂无歌词</li>
</ul>
</div>
<div class="player-ctrl">
<div class="progress">
<span>{{currentTime | timeFormat}}</span>
<div class="progress-bar" ref="progress" @click.stop="touchProgress"
@touchend.stop="moveProgressEnd">
<div class="cur-progress" :style="`width:${curProgress}`"></div>
<div class="idot" :style="`left:${curIdot}`" @touchstart.stop="isMove = true"></div>
</div>
<span>{{allTime | timeFormat}}</span>
</div>
<div class="player-conctrl">
<div class="play-mode" @click="switchMode">
<i class="iconfont"
:class="{
'icon-shunxubofang': mode === 1,
'icon-suijibofang': mode === 2,
'icon-danquxunhuan': mode === 3}
"></i>
</div>
<div class="prev" @click="prevMusic"><i class="iconfont icon-shangyishou1"></i></div>
<div class="ctrl">
<i class="iconfont icon-iconfontplay" v-if="!playing" @click="play"></i>
<i class="iconfont icon-zanting" v-else @click="pause"></i>
</div>
<div class="next" @click="nextMusic"><i class="iconfont icon-xiayishou1"></i></div>
<div class="play-list-btn"><i class="iconfont icon-liebiao" @click="showPlayList"></i></div>
</div>
</div>
</div>
<div class="player-bg" :style="`backgroundImage:url(${curPlayMusic.detail.al.picUrl || bg})`"
v-if="JSON.stringify(curPlayMusic) !== '{}'"></div>
</div>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState} from 'vuex'
import {mapGetters} from 'vuex'
export default {
data() {
return {
curProgress: '0',
timer: null,
currentTime: 0,
allTime: 0,
curIdot: 0,
parsed: {}, //临时哈希表,保存播放进度和偏移位置
marginTop: 0,
showPanel: true,
mode: 1, //播放模式,1-顺序,2-随机,3-循环
bg: '../assets/images/bg.jpg',
isMove: false //
}
},
computed: {
...mapState([
'playing', 'showPlayer', 'curPlayMusic', 'curMusicIndex', 'playList'
]),
/*...mapGetters([
'lyric'
]),*/
lyric() {
if (this.curPlayMusic.lrc !== undefined) {
return this.parseLrc(this.curPlayMusic.lrc.lyric) || null
} else {
// debugger
return false
}
},
//随机播放随机id
randomId() {
let length = this.playList.length
let id
do {
id = Math.round(Math.random() * length)
} while (id === this.curMusicIndex)
return id
}
},
mounted() {
this.$nextTick(() => {
})
},
methods: {
//播放
play() {
this.$refs.audio.play()
// this.timeProgress()
this.$store.dispatch('switchPlaying', true)
// console.log(this.$store.state.playing)
},
// 暂停
pause() {
this.$refs.audio.pause()
// clearInterval(this.timer)
this.$store.dispatch('switchPlaying', false)
// console.log(this.$store.state.playing)
},
//audio的timeupdate事件触发
timeProgress() {
//拖动时不执行
if (this.isMove) return
/*this.timer = setInterval(() => {*/
// const audioid = document.getElementById('music')
const audioid = this.$refs.audio
var cur = (audioid.currentTime / audioid.duration) * this.$refs.progress.offsetWidth
this.curProgress = cur + 'px'
this.curIdot = (cur - 13) + 'px'
this.currentTime = audioid.currentTime
this.allTime = audioid.duration
/*}, 1000)*/
this.updateLyric(audioid)
},
//歌词滚动
updateLyric(audioid) {
let text_temp;
let data = this.parsed
if (!data) return;
let currentTime = Math.round(audioid.currentTime)
let lrc = data[currentTime];
if (!lrc)return;
let text = lrc.text
if (text != text_temp) {
// locationLrc(lrc);
document.querySelectorAll('.lyric-wrap .on').forEach((v, i) => {
v.classList.remove('on')
})
let num = document.querySelector('.lyric-wrap').querySelectorAll('li:nth-child(' + (lrc.index + 1) + ')')[0]
num.classList.add('on')
this.$store.dispatch('showCurLyric', num.innerText)
let top = Math.min(0, -lrc.top);
this.marginTop = top
/*console.log(this.marginTop)
console.log(lrc.index + 1)*/
text_temp = text;
}
},
//audio触发play事件,开始播放
startPlay() {
if (this.lyric.length <= 0) {
return
}
// console.log('start')
this.$store.dispatch('switchPlaying', true)
this.setParsed()
},
//audio触发ended事件,播放完毕
ended() {
this.$store.dispatch('switchPlaying', false)
this.nextMusic()
},
//保存歌词索引内容偏移位置
setParsed() {
let i = 0
if (this.lyric.length <= 0) {
return
}
//用数组保存lyric的键并排序
var arr = []
for (let k in this.lyric) {
/*this.parsed[k] = {
index: i++,
text: this.lyric[k],
top: (i * 30) * 0.9
}*/
arr.push(parseInt(k))
}
//循环数组来为parsed赋值
arr.sort((a, b) => a - b).forEach((v, i) => {
this.parsed[v] = {
index: i++,
text: this.lyric[v],
top: (i * 30) * 1
}
})
/*console.log(arr.sort((a, b) => a - b))
console.log(this.parsed)*/
// console.log(this.parsed)
},
//隐藏播放器
back() {
this.$store.state.showPlayer = false
},
//解析歌词
parseLrc(lrc) {
var lyrics = lrc.split("\n");
var lrcObj = {};
for (var i = 0; i < lyrics.length; i++) {
var lyric = decodeURIComponent(lyrics[i]);
var timeReg = /\[\d*:\d*((\.|\:)\d*)*\]/g;
var timeRegExpArr = lyric.match(timeReg);
if (!timeRegExpArr)continue;
var clause = lyric.replace(timeReg, '');
for (var k = 0, h = timeRegExpArr.length; k < h; k++) {
var t = timeRegExpArr[k];
var min = Number(String(t.match(/\[\d*/i)).slice(1)),
sec = Number(String(t.match(/\:\d*/i)).slice(1));
var time = min * 60 + sec;
lrcObj[time] = clause;
}
}
return lrcObj;
},
//切换两个面板
showPanelF() {
this.showPanel = !this.showPanel
},
//显示播放列表
showPlayList() {
this.$store.dispatch('showPlayList', true)
},
//打开评论
openComment(id) {
this.$store.dispatch('commentId', id)
this.$store.state.showComment = true
},
//下一首
nextMusic() {
if (this.mode === 1) {
this.$store.dispatch('curMusicIndex', this.curMusicIndex + 1)
} else if (this.mode === 2) {
this.$store.dispatch('curMusicIndex', this.randomId)
}
},
//上一首
prevMusic() {
if (this.mode === 1) {
!this.curMusicIndex
? this.$store.dispatch('curMusicIndex', 0)
: this.$store.dispatch('curMusicIndex', this.curMusicIndex - 1)
} else if (this.mode === 2) {
this.$store.dispatch('curMusicIndex', this.randomId)
}
},
//切换播放模式
switchMode() {
this.mode < 3 ? this.mode++ : this.mode = 1
},
//拖动进度条
moveProgress(e) {
if (this.isMove) {
const pro = this.$refs.progress
const curWidth = (e.touches[0].clientX - pro.offsetLeft)
this.curProgress = curWidth - 5 + 'px'
this.curIdot = curWidth - 5 + 'px'
console.log(curTime)
}
},
//拖动进度条完毕
moveProgressEnd(e) {
this.isMove = false
const pro = this.$refs.progress
const curTime = (e.changedTouches[0].clientX - pro.offsetLeft) * pro.offsetWidth / this.allTime
this.$refs.audio.currentTime = curTime
},
//点击进度条更改播放进度
touchProgress(e) {
const curTime = (e.clientX - this.$refs.progress.offsetLeft) * this.$refs.progress.offsetWidth / this.allTime
console.log(curTime)
this.$refs.audio.currentTime = curTime
}
},
filters: {
//播放时间格式化
timeFormat(value) {
let min = parseInt(value / 60)
let sec = parseInt(value % 60)
min = min < 10 ? '0' + min : min
sec = sec < 10 ? '0' + sec : sec
return min + ':' + sec
},
//评论总数大于999时显示999+
commentTotalFormat(value) {
if (parseInt(value) >= 1000) {
return '999+'
} else {
return value
}
}
},
watch: {}
}
</script>
<style lang="scss" type="text/scss">
@import '../assets/css/base';
.player {
position: fixed;
left: 0;
bottom: -2000px;
right: 0;background: url('../assets/images/bg.jpg');
background-color: #fff;
z-index: 1001;
&.show {
top: 0;
bottom: 0;
}
&.animated {
-webkit-animation-duration: .1s;
-moz-animation-duration: .1s;
-o-animation-duration: .1s;
animation-duration: .1s;
}
.player-wrap {
z-index: 1002;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
}
.player-header {
height: pr(56);
background-color: transparent !important;
h2 {
line-height: 1.1;
font-size: pr(16) !important;
span {
color: #ccc;
font-size: pr(12);
}
}
}
.player-panel1 {
position: absolute;
left: 0;
right: 0;
top: pr(56);
bottom: pr(110);
/*display: none;*/
.player-line {
height: 1px;
background-image: -webkit-linear-gradient(
to right,
rgba(0, 0, 0, 0),
rgba(255, 255, 255, .3),
rgba(0, 0, 0, 0)
);
background-image: linear-gradient(
to right,
rgba(0, 0, 0, 0),
rgba(255, 255, 255, .3),
rgba(0, 0, 0, 0)
);
}
.player-needle {
position: absolute;
z-index: 1;
top: -1px;
left: 46%;
height: pr(140);
img {
height: 100%;
}
}
.record-cover {
@include wh(pr(250));
display: flex;
justify-content: center;
align-items: center;
position: absolute;
left: 50%;
top: pr(80);
border-radius: 50%;
transform: translate(-50%, 0);
background-color: rgba(255, 255, 255, .2);
.record-bg {
@include wh(pr(246));
display: flex;
justify-content: center;
align-items: center;
background-image: url("../assets/images/record-bg.png");
background-size: 100% 100%;
&.rotate-bg {
animation: rotatebg 20s linear infinite;
}
@keyframes rotatebg {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(180deg);
}
100% {
transform: rotate(360deg);
}
}
.music-bg {
@include wh(pr(168));
border-radius: 50%;
overflow: hidden;
img {
@include wh(100%);
}
}
}
}
.player-bar {
position: absolute;
left: 50%;
bottom: pr(10);
transform: translate(-50%, 0);
display: flex;
width: 70%;
justify-content: space-around;
color: #fff;
.bar-item:nth-child(3) {
position: relative;
.comment-total {
position: absolute;
top: pr(-5);
right: pr(-12);
font-size: pr(8);
width: pr(21.5);
height: pr(10);
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, .1);
}
}
.iconfont {
font-size: pr(18);
&:nth-child(2) {
font-size: pr(20);
}
}
}
}
.player-panel2 {
position: absolute;
left: 0;
right: 0;
top: pr(56);
bottom: pr(110);
color: #fff;
overflow: hidden;
ul {
padding-top: 65%;
transition: all .8s;
li {
font-size: pr(14);
/*padding: 5px 0;*/
line-height: 30px;
text-align: center;
color: #ccc;
&.on {
color: #fff;
}
}
}
}
.player-ctrl {
position: absolute;
left: 0;
bottom: 0;
right: 0;
height: pr(110);
color: #fff;
background-color: transparent;
.progress {
display: flex;
align-items: center;
padding: 0 pr(19);
margin-top: pr(15);
span {
font-size: pr(8);
}
.progress-bar {
flex: 1;
margin: 0 pr(5);
height: pr(2);
background-color: #9f9f9f;
.cur-progress {
width: 50%;
height: 100%;
background-color: $baseColor;
}
.idot {
position: relative;
@include wh(13px);
border-radius: 50%;
background-color: #fff;
top: pr(-8);
}
}
}
.player-conctrl {
margin-top: pr(22);
padding: 0 pr(15);
display: flex;
align-items: center;
justify-content: space-around;
> div i {
font-size: pr(20);
}
.ctrl {
display: flex;
justify-content: center;
align-items: center;
@include wh(pr(40));
border: 1px solid #fff;
border-radius: 50%;
i {
margin-top: pr(3);
margin-left: pr(3);
font-size: pr(18);
&.icon-zanting {
margin-top: 0;
margin-left: 0;
}
}
}
.play-list-btn {
}
}
}
.player-bg {
position: absolute;
left: -30%;
top: -30%;
width: 180%;
height: 180%;
z-index: 1001;
background-image: url("http://p1.music.126.net/y1JN20Yevr4e8j75QeWtQA==/1771313232347170.jpg");
background-repeat: no-repeat;
background-size: 100% 100%;
filter: blur(70px) brightness(70%);
}
</style>
================================================
FILE: src/components/Root.vue
================================================
<template>
<div class="root">
<md-toolbar class="md-dense root">
<md-button class="md-icon-button">
<md-icon>menu</md-icon>
</md-button>
<h2 class="md-title" style="flex: 1">
<i class="iconfont"
:class="{
'icon-yinyue': i === 0,
'icon-wangyiyunyinlezizhi': i === 1,
'icon-shejiao': i === 2,
'active': i === cur
}"
@click="switchTab(i)"
v-for="(n,i) in 3"></i>
</h2>
<md-button class="md-icon-button">
<router-link to="/search">
<md-icon>search</md-icon>
</router-link>
</md-button>
</md-toolbar>
<transition :name="transitionName">
<keep-alive :include="/Test/">
<router-view class="root-child-view"></router-view>
</keep-alive>
</transition>
</div>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState} from 'vuex'
export default {
data() {
return {
cur: 1,
transitionName: ''
}
},
computed: {
...mapState([])
},
mounted() {
this.$nextTick(() => {
})
},
methods: {
//切换标签,当前高亮
switchTab(index) {
this.cur = index
switch (index) {
case 0:
this.$router.push({path: '/root/home'})
break
case 1:
this.$router.push({path: '/root/index'})
break
}
}
},
filters: {},
watch: {
//路由切换动画
$route(to,from) {
const arr = ['home','index','social']
const toIndex = arr.indexOf(to.path.split('/')[3])
const fromIndex = arr.indexOf(from.path.split('/')[3])
this.transitionName = toIndex < fromIndex ? 'root-slide-right':'root-slide-left'
}
}
}
</script>
<style lang="scss" type="text/scss">
@import '../assets/css/base';
.md-toolbar.root {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
a {
color: rgba(255, 255, 255, .87) !important;
}
.md-title {
display: flex;
justify-content: center;
.iconfont {
color: #e58983;
&.active {
color: #fff;
}
&:not(:last-of-type) {
margin-right: pr(15);
}
&:nth-of-type(1) {
font-size: pr(20);
}
&:nth-of-type(2) {
font-size: pr(26);
}
&:nth-of-type(3) {
font-size: pr(24);
}
}
}
}
.root-child-view {
transition: all .3s cubic-bezier(.55, 0, .1, 1);
overflow: auto;
}
.root-slide-left-enter, .root-slide-right-leave-active {
opacity: 0;
-webkit-transform: translate(7.5rem, 0);
transform: translate(7.5rem, 0);
transition-delay: .3s;
-webkit-transition-delay: .3s;
}
.root-slide-left-leave-active, .root-slide-right-enter {
opacity: 0;
-webkit-transform: translate(-7.5rem, 0);
transform: translate(-7.5rem, 0);
transition-delay: .3s;
-webkit-transition-delay: .3s;
}
.root-slide-enter-active {
-webkit-transition: all .3s ease;
transition: all .3s ease;
}
.root-slide-leave-active {
-webkit-transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
</style>
================================================
FILE: src/main.js
================================================
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
import VueMaterial from 'vue-material'
import 'vue-material/dist/vue-material.css'
import VueAwesomeSwiper from 'vue-awesome-swiper'
Vue.use(VueAwesomeSwiper)
Vue.use(VueMaterial)
Vue.material.registerTheme({
default: {
primary: {
color: 'red',
hue: 700
}
}
})
import './assets/css/reset.css'
import './assets/css/common.css'
import 'animate.css'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})
================================================
FILE: src/page/Index/Index.vue
================================================
<template>
<div class="index">
<md-button-toggle md-single class="index">
<md-button class="md-toggle" @click.native="indexTab('recommend')">个性推荐</md-button>
<md-button @click.native="indexTab('songlist')">歌单</md-button>
<md-button @click.native="indexTab('radio')">主播电台</md-button>
<md-button @click.native="indexTab('top')">排行榜</md-button>
</md-button-toggle>
<transition :name="transitionName">
<keep-alive>
<router-view class="index-child-view"></router-view>
</keep-alive>
</transition>
</div>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState} from 'vuex'
// import Recommend from 'Recommend.vue'
export default {
data() {
return {
transitionName: ''
}
},
computed: {
...mapState([])
},
mounted() {
this.$nextTick(() => {
})
},
methods: {
// 切换顶部标签
indexTab(tab) {
this.$router.push({path: `/root/index/${tab}`})
}
},
components: {
// Recommend
},
filters: {},
watch: {
$route(to, from) {
// 获取path的最后路由,比较在arr中的索引位置,
const arr = ['recommend','songlist','radio','top']
const toIndex = arr.indexOf(to.path.split('/').pop())
const fromIndex = arr.indexOf(from.path.split('/').pop())
this.transitionName = toIndex < fromIndex ? 'slide-right' : 'slide-left'
console.log(this.transitionName)
// 取到路由的最后
}
}
}
</script>
<style lang="scss" type="text/scss">
@import '../../assets/css/base';
.index-tab {
display: flex;
li {
display: flex;
justify-content: center;
align-items: center;
margin-top: 0 !important;
height: pr(35);
flex: 1;
a:not(.md-button) {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: pr(14);
color: #666;
border-bottom: 2px solid #fff;
&:hover {
color: $baseColor;
text-decoration: none;
border-bottom-color: $baseColor;
}
}
}
}
.md-button-toggle.index {
position: fixed;
top:pr(48);
left:0;
right:0;
z-index:1000;
}
.md-theme-default.md-button-toggle {
display: flex;
.md-button {
flex: 1;
border-bottom: 3px solid #fff;
background-color: #fff;
}
.md-toggle {
color: $baseColor;
background-color: #fff !important;
border-bottom: 3px solid $baseColor;
}
}
/* 上面是为了保证滑动的时候不出现抖动情况 */
.index-child-view {
position: absolute;
left:0;
top: pr(84);
/*height: 100%;*/
width: 100%;
transition: all .3s cubic-bezier(.55,0,.1,1);
background-color: #fff;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
/* 当child-view的内容过多时会撑开child-view使得内部能够滚动 */
.slide-left-enter, .slide-right-leave-active {
opacity: 0;
-webkit-transform: translate(7.5rem, 0);
transform: translate(7.5rem, 0);
transition-delay: .3s;
-webkit-transition-delay: .3s;
}
.slide-left-leave-active, .slide-right-enter {
opacity: 0;
-webkit-transform: translate(-7.5rem, 0);
transform: translate(-7.5rem, 0);
transition-delay: .3s;
-webkit-transition-delay: .3s;
}
.slide-enter-active {
-webkit-transition: all .3s ease;
transition: all .3s ease;
}
.slide-leave-active {
-webkit-transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
</style>
================================================
FILE: src/page/Index/Radio.vue
================================================
<template>
<div>
radio
</div>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState} from 'vuex'
export default {
data() {
return {}
},
mounted() {
this.$nextTick(() => {
})
},
methods: {},
filters: {},
computed: {
...mapState([])
}
}
</script>
<style lang="scss" type="text/scss">
@import '../../assets/css/base';
</style>
================================================
FILE: src/page/Index/Recommend.vue
================================================
<template>
<div>
<swiper :options="swiperOption">
<swiper-slide v-for="(item,index) in banners" :key="index">
<img :src="item.pic" alt="">
</swiper-slide>
<div class="swiper-pagination" slot="pagination"></div>
</swiper>
<div class="today-recommend">
<div class="recommend-item animated pulse">
<div class="circle-b">
<div class="circle-t">
<i class="iconfont icon-501"></i>
</div>
</div>
<div class="item-name">私人FM</div>
</div>
<div class="recommend-item animated pulse">
<div class="circle-b">
<i class="iconfont icon-rili"></i>
</div>
<div class="item-name">每日歌曲推荐</div>
</div>
<div class="recommend-item animated pulse">
<div class="circle-b">
<div class="circle-t">
<i class="iconfont icon-paixing"></i>
</div>
</div>
<div class="item-name">云音乐热歌榜</div>
</div>
</div>
<div class="reco-list">
<list-title :title="'推荐歌单'"></list-title>
<ul class="recl-list3">
<li class="reco-list-item3" v-for="(item,index) in recommendList" @click="toSongListDetail(item.id, item)">
<div><img :src="item.picUrl" alt=""></div>
<p>{{item.name }}</p>
</li>
</ul>
</div>
<div class="reco-list">
<list-title :title="'独家放送'"></list-title>
<ul class="recl-list2">
<li class="reco-list-item2" v-for="(item,index) in dujiaList">
<div>
<img :src="item.sPicUrl" alt="" v-if="index < 2">
<img :src="item.picUrl" alt="" v-else>
</div>
<p>{{item.name }}</p>
</li>
</ul>
</div>
</div>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState} from 'vuex'
import url from '../../assets/js/api'
import ListTitle from '@/components/ListTitle'
export default {
data() {
return {
swiperOption: {
pagination: '.swiper-pagination',
paginationClickable: true,
autoplay: 3000,
loop: true
},
banners: [],
recommendList: [],
dujiaList: []
}
},
computed: {
...mapState([
'url'
])
},
created() {
this.fetchBanner()
this.fetchRecommendList()
this.fetchDujia()
},
mounted() {
this.$nextTick(() => {
})
},
methods: {
//获取banner图
fetchBanner() {
axios.get(`${url}/banner`).then(res => {
this.banners = res.data.banners
})
},
//获取推荐歌单
fetchRecommendList() {
axios.get(`${url}/personalized`).then(res => {
this.recommendList = res.data.result
})
},
//独家放送
fetchDujia() {
axios.get(`${url}/personalized/privatecontent`).then(res => {
this.dujiaList = res.data.result
this.dujiaList = this.dujiaList.reverse()
})
},
//点击获取歌单详情
toSongListDetail(id, obj) {
this.$router.push({path: '/playlistdetail/' + id})
this.$store.dispatch('curPlaylistDetail',obj)
}
},
filters: {},
components: {
ListTitle
}
}
</script>
<style lang="scss" type="text/scss">
@import '../../assets/css/base';
.swiper-container {
height: pr(146);
.swiper-slide {
img {
@include wh(100%);
background-repeat: no-repeat;
background-size: 100% 100%;
}
}
.swiper-pagination-bullet {
background-color: #fff;
opacity: .5;
}
.swiper-pagination-bullet-active {
background: $baseColor;
opacity: 1;
}
}
.today-recommend {
display: flex;
align-items: center;
height: pr(93);
border-bottom: 1px solid #eee;
.recommend-item {
@include dfcc;
flex-direction: column;
flex: 1;
.circle-b {
@include dfcc;
@include wh(pr(95/2));
border-radius: 50%;
border: 1px solid $baseColor;
.iconfont {
font-size: pr(24);
color: $baseColor;
}
}
}
.item-name {
margin-top: pr(8);
font-size: pr(12);
}
}
.reco-list {
.recl-list3 {
display: flex;
flex-wrap: wrap;
.reco-list-item3 {
width: pr(248/2);
margin-bottom: pr(10);
margin-right: pr(1);
&:nth-child(3n) {
margin-right: 0;
}
div {
height: pr(248/2);
img {
@include wh(100%)
}
}
p {
padding: pr(5);
font-size: pr(12);
line-height: 1.6;
@include overhide(2)
}
}
}
.recl-list2 {
display: flex;
flex-wrap: wrap;
.reco-list-item2 {
width: pr(374/2);
margin-bottom: pr(10);
margin-right: pr(1);
&:nth-child(2n) {
margin-right: 0;
}
&:nth-child(3n) {
width: 100%;
}
div {
height: pr(248/2);
img {
@include wh(100%)
}
}
p {
padding: pr(5);
font-size: pr(12);
line-height: 1.6;
@include overhide(2)
}
}
}
}
</style>
================================================
FILE: src/page/Index/SongList.vue
================================================
<template>
<div>
songlist
</div>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState} from 'vuex'
export default {
data() {
return {
}
},
mounted() {
this.$nextTick(() => {
})
},
methods: {
},
filters: {
},
computed: {
...mapState([
])
}
}
</script>
<style lang="scss" type="text/scss">
@import '../../assets/css/base';
</style>
================================================
FILE: src/page/Index/Top.vue
================================================
<template>
<div>
top
</div>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState} from 'vuex'
export default {
data() {
return {}
},
mounted() {
this.$nextTick(() => {
})
},
methods: {},
filters: {},
computed: {
...mapState([])
}
}
</script>
<style lang="scss" type="text/scss">
@import '../../assets/css/base';
</style>
================================================
FILE: src/page/PlayListDetail.vue
================================================
<template>
<div id="playlist_detail" ref="playlistdetail"><!-- v-if="JSON.stringify(playlist) !== '{}'"-->
<div>
<md-toolbar class="md-dense songlist">
<md-button class="md-icon-button">
<md-icon @click.native="back">arrow_back</md-icon>
</md-button>
<h2 class="md-title" style="flex: 1">歌单</h2>
<md-button class="md-icon-button">
<!--<md-icon @click.native="toHome">search</md-icon>-->
</md-button>
</md-toolbar>
<div class="songlist-bg-wrap gradient-wrap">
<div class="songlist-info">
<div class="songlist-img">
<img class="gradient-img" :src="curPlaylistDetail.picUrl || playlist.creator.backgroundUrl" alt="">
<div class="play-views" v-if="playlist.playCount"><i
class="iconfont icon-erjiline"></i>{{playlist.playCount | playCount}}万
</div>
</div>
<div class="songlist-title">
<h3>{{curPlaylistDetail.name || playlist.name}}</h3>
<div class="songlist-avatar">
<div class="songlist-avatar-img">
<img :src="playlist.creator.avatarUrl" alt="" v-if="JSON.stringify(playlist) !== '{}'">
<img src="../assets/images/playlist-avatar.png" alt="" v-else>
</div>
<span v-if="JSON.stringify(playlist) !== '{}'">{{playlist.creator.nickname}} </span><i
class="iconfont icon-jiantou"></i>
</div>
</div>
</div>
<div class="songlist-operating">
<div class="songlist-op-item"><i
class="iconfont icon-wenjianjia"></i><span>{{playlist.subscribedCount || '收藏'}}</span>
</div>
<div class="songlist-op-item"><i
class="iconfont icon-pinglun"></i><span>{{playlist.commentCount || '评论'}}</span>
</div>
<div class="songlist-op-item"><i
class="iconfont icon-icglobaltitlebar48iconfontshare"></i><span>{{playlist.shareCount || '分享'}}</span></div>
<div class="songlist-op-item"><i class="iconfont icon-download"></i><span>下载</span></div>
</div>
<div class="songlist-bg" ref="songlist_bg"
:style="`backgroundImage: url(${this.curPlaylistDetail.picUrl});`"></div>
</div>
<div class="song-list">
<div class="play-bar">
<div class="play-bar-btn"><i
class="iconfont icon-iconfont31"></i>播放全部<span
v-if="JSON.stringify(playlist) !== '{}'">(共{{playlist.tracks.length}}首)</span></div>
</div>
<div class="song-item" v-for="(item,index) in playlist.tracks">
<div class="song-index">{{index + 1}}</div>
<div class="song-info" @click="playMusic(item.id)">
<p class="song-name">{{item.name}}<span class="song-desc" v-show="item.alia[0]">{{item.alia[0]}}</span></p>
<p class="song-singer"><span v-for="(itemname,index) in item.ar">{{itemname.name}}/</span> - {{item.al.name}}
</p>
</div>
<div class="song-option">
<i class="iconfont icon-more1170511easyiconnet"></i>
</div>
</div>
</div>
<div class="playlist-loading" v-if="loading">
<md-spinner :md-size="40" md-indeterminate></md-spinner>
</div>
</div>
</div>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState} from 'vuex'
import url from '../assets/js/api'
import Grade from 'grade-js'
import filters from '../assets/js/filters'
export default {
data() {
return {
playlist: {},
playListPanel: [], //存放播放列表
loading: true
}
},
computed: {
...mapState([
'curPlaylistDetail'
]),
},
created() {
this.fetchSongList()
},
mounted() {
this.$nextTick(() => {
})
},
methods: {
// 歌单详情
fetchSongList() {
axios.get(`${url}/playlist/detail?id=${this.$route.params.id}`).then(res => {
//获取完毕隐藏loading
this.loading = false
this.playlist = res.data.playlist
let songObj
//添加当前歌单到播放列表面板
this.playlist.tracks.forEach((val) => {
songObj = {
id: val.id,
name: val.name,
artist: val.ar
}
this.playListPanel.push(songObj);
})
// console.log(this.playListPanel)
}).catch(err => {
console.error(err)
})
},
back() {
this.$router.go(-1)
},
playMusic(id) {
this.$store.state.showPlayer = true
function getUrl() {
return axios.get(`${url}/music/url?id=${id}`)
}
function getDetail() {
return axios.get(`${url}/song/detail?ids=${id}`)
}
function getLyric() {
return axios.get(`${url}/lyric?id=${id}`)
}
//获取歌曲信息,并统一存入vuex中
axios.all([getUrl(), getDetail(), getLyric()])
.then(axios.spread((res1, res2, res3) => {
const arr = [res1, res2, res3]
this.$store.dispatch('curPlayMusic', arr)
this.$store.dispatch('switchPlaying', true)
this.$store.dispatch('playList', this.playListPanel)
// console.log(this.playListPanel)
}))
},
},
//歌单播放次数过滤器
filters: filters,
}
</script>
<style lang="scss" type="text/scss">
@import '../assets/css/base';
.md-toolbar.songlist {
position: fixed;
left: 0;
top: 0;
right: 0;
background-color: rgba(255, 255, 255, 0.05);
z-index: 1;
h2 {
font-size: pr(14);
}
}
.songlist-bg-wrap {
position: relative;
height: pr(220);
background-color: rgba(64, 46, 32, 0.58);
padding: pr(50) pr(20) pr(10);
color: #fff;
overflow: hidden;
.songlist-info {
display: flex;
.songlist-img {
position: relative;
@include wh(pr(116));
img {
@include wh(100%)
}
.play-views {
display: flex;
align-items: center;
position: absolute;
right: pr(5);
top: pr(5);
font-size: pr(10);
i {
margin-right: pr(3);
font-size: pr(10);
}
}
}
.songlist-title {
flex: 1;
margin-left: pr(20);
padding-top: pr(10);
h3 {
line-height: 1.4;
font-size: pr(14);
}
.songlist-avatar {
display: flex;
align-items: center;
margin-top: pr(25);
.songlist-avatar-img {
overflow: hidden;
border-radius: 50%;
@include ava(pr(24))
}
span {
margin-left: pr(10);
font-size: pr(12);
}
.iconfont {
font-size: pr(10);
}
}
}
}
.songlist-operating {
display: flex;
align-items: center;
margin-top: pr(5);
height: pr(48);
.songlist-op-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
i {
font-size: pr(16);
}
span {
margin-top: pr(3);
font-size: pr(12);
}
}
}
.songlist-bg {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: -1;
background-size: 100% 100%;
background-repeat: no-repeat;
filter: blur(35px)
}
}
.song-list {
$border_b: #e6e6e6;
$c888: #888;
.play-bar {
display: flex;
height: pr(46);
padding: 0 pr(15);
align-items: center;
border-bottom: 1px solid $border_b;
.play-bar-btn {
font-size: pr(14);
.iconfont {
font-size: pr(18);
margin-right: pr(10);
}
span {
margin-left: pr(5);
font-size: pr(12);
color: $c888;
}
}
}
.song-item {
display: flex;
height: pr(50);
border-bottom: 1px solid $border_b;
.song-index {
display: flex;
justify-content: center;
align-items: center;
width: pr(45);
}
.song-info {
display: flex;
flex-direction: column;
justify-content: center;
flex: 1;
overflow: hidden;
.song-name {
line-height: 1.2;
font-size: pr(14);
@include ell;
.song-desc {
color: $c888;
font-size: pr(12);
}
}
.song-singer {
line-height: 1.2;
margin-top: pr(3);
color: $c888;
font-size: pr(10);
@include ell;
}
}
.song-option {
display: flex;
justify-content: center;
align-items: center;
width: pr(45);
color: $c888;
}
}
}
.playlist-loading {
text-align: center;
margin-top:pr(80);
}
</style>
================================================
FILE: src/page/home/Home.vue
================================================
<template>
<div>
home
</div>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState} from 'vuex'
export default {
data() {
return {}
},
mounted() {
this.$nextTick(() => {
})
},
methods: {},
filters: {},
computed: {
...mapState([])
}
}
</script>
<style lang="scss" type="text/scss">
@import '../../assets/css/base';
</style>
================================================
FILE: src/page/search/Album.vue
================================================
<template>
<ul id="search-albums">
<list-item :item="album" v-for="(album,index) in albums" :key="index"></list-item>
</ul>
</template>
<script type="text/ecmascript-6" >
import axios from 'axios'
import {mapState} from 'vuex'
import url from '../../assets/js/api'
import ListItem from './ListItem'
export default {
data() {
return {
word: '',
albums: []
}
},
props: {
searchKey: {
type: String,
required: true
}
},
computed: {
...mapState([])
},
mounted() {
this.$nextTick(() => {
this.getAlbum(this.searchKey)
})
},
methods: {
//专辑组件
getAlbum(key) {
axios.get(`${url}/search?keywords=${key}&type=10`).then(res => {
this.albums = res.data.result.albums
})
}
},
filters: {},
components: {ListItem},
watch: {
searchKey(val, old) {
this.getAlbum(val)
}
}
}
</script>
<style lang="scss" type="text/scss">
@import '../../assets/css/base';
</style>
================================================
FILE: src/page/search/Artist.vue
================================================
<template>
<ul id="search-artist"
@touchstart="touchStart"
@touchmove="touchMove">
<li class="search-artist-item" v-for="(item,index) in artists">
<div class="s-artist-avatar">
<img :src="item.picUrl" alt="">
</div>
<div class="s-artist-name">
{{item.name}}<span v-if="item.alias.length > 0">({{item.alias[0]}})</span>
</div>
</li>
</ul>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState} from 'vuex'
import url from '../../assets/js/api'
export default {
data() {
return {
word: '',
artists: []
}
},
props: {
searchKey: {
type: String,
required: true
}
},
computed: {
...mapState([])
},
mounted() {
this.$nextTick(() => {
this.getArtist(this.searchKey)
})
},
methods: {
//艺术家信息
getArtist(key) {
axios.get(`${url}/search?keywords=${key}&type=100`).then(res => {
this.artists = res.data.result.artists
})
}
},
filters: {},
watch: {
searchKey(val, old) {
this.getArtist(val)
}
}
}
</script>
<style lang="scss" type="text/scss">
@import '../../assets/css/base';
.search-artist-item {
display: flex;
align-items: center;
padding: pr(3) pr(6);
border-bottom: 1px solid #e0e0e0;
.s-artist-avatar {
@include ava(pr(50))
}
.s-artist-name {
flex: 1;
padding-left: pr(10);
font-size: pr(14);
span {
color: #888;
}
}
}
</style>
================================================
FILE: src/page/search/ListItem.vue
================================================
<template>
<li class="search-album-item">
<div class="s-album-avatar">
<img :src="item.picUrl" alt="">
</div>
<div class="s-album-avatar-bg">
<img src="../../assets/images/search-artist-bg.png" alt="">
</div>
<div class="s-album-name">
<p>{{item.name}}</p>
<p>{{item.name || null}}</p>
</div>
</li>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState} from 'vuex'
export default {
data() {
return {}
},
props: {
item: {
type: Object,
required: true
}
},
computed: {
...mapState([])
},
mounted() {
this.$nextTick(() => {
})
},
methods: {
},
filters: {},
}
</script>
<style lang="scss" type="text/scss">
@import '../../assets/css/base';
.search-album-item {
display: flex;
align-items: center;
padding: pr(3) pr(6);
border-bottom: 1px solid #e0e0e0;
.s-album-avatar {
@include ava(pr(50))
}
.s-album-avatar-bg {
height: pr(46);
img {
height: 100%;
}
}
.s-album-name {
flex: 1;
overflow: hidden;
padding-left: pr(10);
p {
@include ell;
line-height:1.2;
&:first-of-type {
font-size: pr(14);
}
&:nth-of-type(2) {
margin-top: pr(8);
font-size: pr(10);
color: #666;
}
}
}
}
</style>
================================================
FILE: src/page/search/Search.vue
================================================
<template>
<div id="search">
<md-toolbar class="md-dense search">
<md-button class="md-icon-button">
<md-icon @click.native="back">arrow_back</md-icon>
</md-button>
<input type="text"
placeholder="搜索音乐、歌手、歌词、用户"
@keydown="searchData"
@focus="showSuggest = searchKey !== '' ? true: false"
v-model="searchKey">
<i class="iconfont icon-search" @click="toChild"></i>
</md-toolbar>
<div class="hot-search" :style="{height:windowHeight + 'px'}">
<p>热门搜索</p>
<ul class="hot-search-list">
<li v-for="(item,index) in hotSearch" @click="searchHotKey(item)">{{item}}</li>
</ul>
</div>
<md-button-toggle md-single class="search-button-toggle" v-if="showSearchType">
<md-button class="md-toggle" @click.native="searchTab('song')">单曲</md-button>
<md-button @click.native="searchTab('artist')">歌手</md-button>
<md-button @click.native="searchTab('album')">专辑</md-button>
<md-button @click.native="searchTab('list')">歌单</md-button>
</md-button-toggle>
<div class="search-suggest" v-show="showSuggest">
<div class="search-key" @click="toChild">搜索“{{searchKey}}”</div>
<ul v-if="suggestSongs.length !== 0">
<li v-for="(item,index) in suggestSongs"
@click="playMusicFromSearch(item.id)"><i
class="iconfont icon-sousuo"></i>{{item.name}} - {{item.artists[0].name}}
</li>
</ul>
</div>
<transition :name="transitionName">
<keep-alive>
<router-view
class="search-child-view"
:search-key="searchKeyProps"
></router-view>
<!--通过props传递搜索词给搜索结果组件-->
</keep-alive>
</transition>
</div>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState} from 'vuex'
import url from '../../assets/js/api'
export default {
data() {
return {
transitionName: '',
showSuggest: false,
searchKey: '',
searchKeyProps: '',
suggestSongs: [],
searching: false,
showSearchType: false,
hotSearch: ['跨界歌王', '老大', '暧昧', '告白气球', '演员', '薛之谦', '因你', '张碧晨', '杨宗纬', '凉凉'],
windowHeight: 0
}
},
computed: {
...mapState([])
},
mounted() {
this.$nextTick(() => {
this.windowHeight = document.documentElement.clientHeight
})
},
methods: {
searchTab(tab) {
this.$router.replace({path: `/search/${tab}`})
},
//按键的时候搜索显示推荐内容
searchData(e) {
if (e.keyCode === 13) {
this.toChild()
} else {
axios.get(`${url}/search/suggest?keywords=${this.searchKey}`).then(res => {
this.showSuggest = true
if (res.data.code === 200) {
this.suggestSongs = res.data.result.songs
} else if (res.data.code === 400) {
this.suggestSongs = []
this.showSuggest = false
}
})
}
},
//下拉搜索推荐点击播放
playMusicFromSearch(id) {
function getUrl() {
return axios.get(`${url}/music/url?id=${id}`)
}
function getDetail() {
return axios.get(`${url}/song/detail?ids=${id}`)
}
function getLyric() {
return axios.get(`${url}/lyric?id=${id}`)
}
axios.all([getUrl(), getDetail(), getLyric()])
.then(axios.spread((res1, res2, res3) => {
const arr = [res1, res2, res3]
this.$store.dispatch('curPlayMusic', arr)
this.$store.state.showPlayer = true
}))
},
//跳转到歌曲搜索结果页,由于通过props传递了搜索词,song组件会加载时就获取信息
toChild() {
this.$router.push({path: '/search/song'})
this.searchKeyProps = this.searchKey
this.showSuggest = false
this.showSearchType = true
},
//点击伪热搜词也会跳转到结果页
searchHotKey(key) {
this.searchKey = key
this.toChild()
},
back() {
this.$router.go(-1)
}
},
filters: {},
watch: {
$route(to, from) {
const arr = ['song', 'artist', 'album', 'list']
const toIndex = arr.indexOf(to.path.split('/').pop())
const fromIndex = arr.indexOf(from.path.split('/').pop())
this.transitionName = toIndex < fromIndex ? 'search-slide-right' : 'search-slide-left'
}
}
}
</script>
<style lang="scss" type="text/scss">
@import '../../assets/css/base';
#search {
> ul {
min-height:pr(400);
}
.md-toolbar {
position: fixed;
left: 0;
top: 0;
right: 0;
z-index: 1000;
input {
flex: 1;
margin-right: pr(5);
height: pr(30);
background-color: transparent;
border: none;
padding-bottom: 0;
border-bottom: 1px solid #fff;
color: #fff;
&::-webkit-input-placeholder {
color: #e07770;
}
&:focus {
outline: none;
}
}
.iconfont {
display: inline-block;
font-size: pr(20)
}
}
.hot-search {
box-sizing: border-box;
padding: pr(100) pr(10);
p {
font-size: pr(12);
color: #666;
margin-bottom: pr(11);
}
.hot-search-list {
display: flex;
flex-wrap: wrap;
li {
padding: pr(7) pr(11);
margin: pr(0) pr(8) pr(8) 0;
border: 1px solid #dcdcdc;
border-radius: pr(25);
font-size: pr(14);
}
}
}
.md-button-toggle.search-button-toggle {
position: fixed;
top: pr(48);
left: 0;
right: 0;
display: flex;
z-index: 1000;
.md-button {
flex: 1;
font-size: pr(13);
border-bottom: 3px solid #fff;
background-color: #fff;
}
.md-toggle {
color: $baseColor;
background-color: #fff !important;
border-bottom: 3px solid $baseColor;
}
}
.search-suggest {
position: fixed;
z-index: 1001;
top: pr(48);
left: pr(15);
right: pr(33);
background-color: #f3f3f3;
box-shadow: 3px 3px 10px #ccc, -1px 3px 10px #ccc;
transition: all .3s;
.search-key {
padding: pr(15) pr(13);
color: #679dc5;
}
ul {
li {
padding: pr(15) pr(13);
border-top: 1px solid #e5e5e5;
color: #666;
font-size: pr(14);
@include ell;
.iconfont {
margin-right: pr(8);
}
}
}
}
.search-child-view {
position: absolute;
top: pr(86.4);
left: 0;
width: 100%;
transition: all .3s cubic-bezier(.55, 0, .1, 1);
background-color: #f2f4f5;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
.search-slide-left-enter, .search-slide-right-leave-active {
opacity: 0;
-webkit-transform: translate(7.5rem, 0);
transform: translate(7.5rem, 0);
transition-delay: .3s;
-webkit-transition-delay: .3s;
}
.search-slide-left-leave-active, .search-slide-right-enter {
opacity: 0;
-webkit-transform: translate(-7.5rem, 0);
transform: translate(-7.5rem, 0);
transition-delay: .3s;
-webkit-transition-delay: .3s;
}
.search-slide-enter-active {
-webkit-transition: all .3s ease;
transition: all .3s ease;
}
.search-slide-leave-active {
-webkit-transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
}
</style>
================================================
FILE: src/page/search/Song.vue
================================================
<template>
<ul id="search-song">
<li class="search-song-item" v-for="(item,index) in songs">
<div class="search-song-info" @click="playMusicFromSearch(item.id)">
<p class="search-song-name">{{item.name}}</p>
<p class="search-song-artist"><span v-for="(v,i) in item.artists">{{v.name}}<span v-show="i !== item.artists.length - 1">/</span></span> - {{item.album.name}}</p>
</div>
<div class="search-song-btn">
<i class="iconfont icon-more1170511easyiconnet"></i>
</div>
</li>
</ul>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState} from 'vuex'
import url from '../../assets/js/api'
export default {
data() {
return {
word: '',
songs: []
}
},
props:{
searchKey: {
type: String,
required: true
}
},
computed: {
...mapState([])
},
mounted() {
this.$nextTick(() => {
this.getSong(this.searchKey)
})
},
methods: {
//点击播放
getSong(key) {
axios.get(`${url}/search?keywords=${key}`).then(res => {
this.songs = res.data.result.songs
})
},
playMusicFromSearch(id) {
function getUrl() {
return axios.get(`${url}/music/url?id=${id}`)
}
function getDetail() {
return axios.get(`${url}/song/detail?ids=${id}`)
}
function getLyric() {
return axios.get(`${url}/lyric?id=${id}`)
}
axios.all([getUrl(), getDetail(), getLyric()])
.then(axios.spread((res1, res2, res3) => {
const arr = [res1, res2, res3]
this.$store.dispatch('curPlayMusic', arr)
this.$store.state.showPlayer = true
}))
}
},
filters: {
//高亮搜索词
lightWord(value) {
console.log(this.searchKey)
const length = this.searchKey.length
const index = value.indexOf(this.searchKey)
return value.substr(index, 2)
}
},
watch: {
searchKey(val, old) {
this.word = val
this.getSong(val)
}
}
}
</script>
<style lang="scss" type="text/scss">
@import '../../assets/css/base';
#search-song {
.search-song-item {
display: flex;
align-items: center;
padding:pr(15) pr(7);
border-bottom:1px solid #dee0e1;
.search-song-info {
flex:1;
}
.search-song-name {
font-size:pr(14);
@include ell;
}
.search-song-artist {
margin-top:pr(10);
font-size:pr(12);
@include ell;
}
.iconfont {
color:#888;
}
}
}
</style>
================================================
FILE: src/page/search/list.vue
================================================
<template>
<ul id="search-list">
<li class="search-album-item"
v-for="(item,index) in list"
@click="toSongListDetail(item.id, item)">
<div class="s-album-avatar">
<img :src="item.coverImgUrl" alt="">
</div>
<div class="s-album-name">
<p>{{item.name}}</p>
<p>{{item.trackCount}}首 by {{item.creator.nickname}} 播放 {{item.playCount | playCount}}次</p>
</div>
</li>
</ul>
</template>
<script type="text/ecmascript-6">
import axios from 'axios'
import {mapState} from 'vuex'
import url from '../../assets/js/api'
export default {
data() {
return {
word: '',
list:[]
}
},
props: {
searchKey: {
type: String,
required: true
}
},
computed: {
...mapState([])
},
mounted() {
this.$nextTick(() => {
this.getList(this.searchKey)
})
},
methods: {
//获取歌单信息
getList(key) {
axios.get(`${url}/search?keywords=${key}&type=1000`).then(res => {
this.list = res.data.result.playlists
})
},
//跳转到歌单详情页
toSongListDetail(id, obj) {
this.$router.push({path: '/playlistdetail/' + id})
this.$store.dispatch('curPlaylistDetail',obj)
}
},
filters: {
playCount(value) {
if (value < 100000) {
return value
} else {
return (value / 10000).toFixed(1) + '万'
}
}
},
watch: {
searchKey(val, old) {
this.getList(val)
}
}
}
</script>
<style lang="scss" type="text/scss">
@import '../../assets/css/base';
</style>
================================================
FILE: src/router/index.js
================================================
import Vue from 'vue'
import Router from 'vue-router'
import Root from '@/components/Root'
import Index from '../page/index/Index'
import Recommend from '../page/index/Recommend'
import SongList from '../page/index/SongList'
import Radio from '../page/index/Radio'
import Top from '../page/index/Top'
import Home from '../page/home/Home'
import PlayListDetail from '../page/PlayListDetail'
import Search from '../page/search/Search'
import Song from '../page/search/Song'
import Artist from '../page/search/Artist'
import Album from '../page/search/Album'
import List from '../page/search/list.vue'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
redirect: '/root/index'
},
{
path: '/root',
name: 'Root',
component: Root,
children: [
{
path: 'index',
name: 'Test',
component: Index,
redirect: 'index/recommend',
children: [
{
path: 'recommend',
name: 'recommend',
component: Recommend
},
{
path: 'songlist',
component: SongList
},
{
path: 'radio',
component: Radio
},
{
path: 'top',
component: Top
}
]
},
{
path: 'home',
component: Home
},
]
},
{
path: '/playlistdetail/:id',
component: PlayListDetail
},
{
path: '/search',
component: Search,
redirect: '',
children: [
{
path: 'song',
component: Song
},
{
path: 'artist',
component: Artist
},
{
path: 'album',
component: Album
},
{
path: 'list',
component: List
}
]
}
]
})
================================================
FILE: src/store/actions.js
================================================
/**
* Created by 12 on 2017/6/8.
*/
import * as types from './mutation-type'
export default {
curPlaylistDetail({commit}, obj) {
commit(types.CUR_PLAYLIST_INFO, obj)
},
curPlayMusic({commit}, arr) {
commit(types.CUR_PLAY_MUSIC, arr)
},
switchPlaying({commit}, status) {
commit(types.SWITCH_PLAYING, status)
},
showCurLyric({commit}, lyric) {
commit(types.SHOW_CUR_LYRIC, lyric)
},
showPlayList({commit}, status) {
commit(types.SHOW_PLAY_LIST, status)
},
playList({commit}, arr) {
commit(types.PLAY_LIST, arr)
},
commentId({commit}, id) {
commit(types.COMMENT_ID, id)
},
curMusicIndex({commit}, index) {
commit(types.CUR_MUSIC_INDEX, index)
}
}
================================================
FILE: src/store/getters.js
================================================
/**
* Created by 12 on 2017/6/14.
*/
export default {
curId(state) {
return state.curPlayMusic.detail ? state.curPlayMusic.detail.id : null
}
}
================================================
FILE: src/store/index.js
================================================
import Vue from 'vue'
import Vuex from 'vuex'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
Vue.use(Vuex)
const state = {
curPlaylistDetail: {},
curPlayMusic: {},
playing: false,
showPlayer: false,
curLyric: '',
showPlayList: false,
playList: [],
showCover: false,
commentId: null,
showComment: false,
curMusicIndex: 0
}
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
================================================
FILE: src/store/mutation-type.js
================================================
//歌单页当前歌单上面图片标题信息
export const CUR_PLAYLIST_INFO= 'CUR_PLAYLIST_INFO'
//当前播放的歌曲信息:歌名,歌手,专辑,歌词,url,封面
export const CUR_PLAY_MUSIC= 'CUR_PLAY_MUSIC'
//切换播放状态
export const SWITCH_PLAYING = 'SWITCH_PLAYING'
//播放导航显示的歌词
export const SHOW_CUR_LYRIC = 'SHOW_CUR_LYRIC'
//控制显示播放列表
export const SHOW_PLAY_LIST = 'SHOW_PLAY_LIST'
//存储播放列表
export const PLAY_LIST = 'PLAY_LIST'
//当前歌曲评论id
export const COMMENT_ID = 'COMMENT_ID'
//当前歌曲在播放列表的索引
export const CUR_MUSIC_INDEX = 'CUR_MUSIC_INDEX'
================================================
FILE: src/store/mutations.js
================================================
/**
* Created by 12 on 2017/6/8.
*/
import * as types from './mutation-type'
export default {
[types.CUR_PLAYLIST_INFO](state, obj) {
state.curPlaylistDetail = obj
},
[types.CUR_PLAY_MUSIC](state, arr) {
const obj = {
url: arr[0].data.data[0].url,
detail: arr[1].data.songs[0],
lrc: arr[2].data.lrc
}
state.curPlayMusic = obj
},
[types.SWITCH_PLAYING](state, status) {
state.playing = status
},
[types.SHOW_CUR_LYRIC](state, lyric) {
state.curLyric = lyric
},
[types.SHOW_PLAY_LIST](state, status) {
state.showPlayList = status
state.showCover = status
},
[types.PLAY_LIST](state, arr) {
state.playList = arr
},
[types.COMMENT_ID](state, id) {
state.commentId = id
},
[types.CUR_MUSIC_INDEX](state, index) {
state.curMusicIndex = index
}
}
================================================
FILE: static/.gitkeep
================================================
================================================
FILE: static/iconfont/MaterialIcons-Regular.ijmap
================================================
{"icons":{"e84d":{"name":"3d Rotation"},"eb3b":{"name":"Ac Unit"},"e190":{"name":"Access Alarm"},"e191":{"name":"Access Alarms"},"e192":{"name":"Access Time"},"e84e":{"name":"Accessibility"},"e914":{"name":"Accessible"},"e84f":{"name":"Account Balance"},"e850":{"name":"Account Balance Wallet"},"e851":{"name":"Account Box"},"e853":{"name":"Account Circle"},"e60e":{"name":"Adb"},"e145":{"name":"Add"},"e439":{"name":"Add A Photo"},"e193":{"name":"Add Alarm"},"e003":{"name":"Add Alert"},"e146":{"name":"Add Box"},"e147":{"name":"Add Circle"},"e148":{"name":"Add Circle Outline"},"e567":{"name":"Add Location"},"e854":{"name":"Add Shopping Cart"},"e39d":{"name":"Add To Photos"},"e05c":{"name":"Add To Queue"},"e39e":{"name":"Adjust"},"e630":{"name":"Airline Seat Flat"},"e631":{"name":"Airline Seat Flat Angled"},"e632":{"name":"Airline Seat Individual Suite"},"e633":{"name":"Airline Seat Legroom Extra"},"e634":{"name":"Airline Seat Legroom Normal"},"e635":{"name":"Airline Seat Legroom Reduced"},"e636":{"name":"Airline Seat Recline Extra"},"e637":{"name":"Airline Seat Recline Normal"},"e195":{"name":"Airplanemode Active"},"e194":{"name":"Airplanemode Inactive"},"e055":{"name":"Airplay"},"eb3c":{"name":"Airport Shuttle"},"e855":{"name":"Alarm"},"e856":{"name":"Alarm Add"},"e857":{"name":"Alarm Off"},"e858":{"name":"Alarm On"},"e019":{"name":"Album"},"eb3d":{"name":"All Inclusive"},"e90b":{"name":"All Out"},"e859":{"name":"Android"},"e85a":{"name":"Announcement"},"e5c3":{"name":"Apps"},"e149":{"name":"Archive"},"e5c4":{"name":"Arrow Back"},"e5db":{"name":"Arrow Downward"},"e5c5":{"name":"Arrow Drop Down"},"e5c6":{"name":"Arrow Drop Down Circle"},"e5c7":{"name":"Arrow Drop Up"},"e5c8":{"name":"Arrow Forward"},"e5d8":{"name":"Arrow Upward"},"e060":{"name":"Art Track"},"e85b":{"name":"Aspect Ratio"},"e85c":{"name":"Assessment"},"e85d":{"name":"Assignment"},"e85e":{"name":"Assignment Ind"},"e85f":{"name":"Assignment Late"},"e860":{"name":"Assignment Return"},"e861":{"name":"Assignment Returned"},"e862":{"name":"Assignment Turned In"},"e39f":{"name":"Assistant"},"e3a0":{"name":"Assistant Photo"},"e226":{"name":"Attach File"},"e227":{"name":"Attach Money"},"e2bc":{"name":"Attachment"},"e3a1":{"name":"Audiotrack"},"e863":{"name":"Autorenew"},"e01b":{"name":"Av Timer"},"e14a":{"name":"Backspace"},"e864":{"name":"Backup"},"e19c":{"name":"Battery Alert"},"e1a3":{"name":"Battery Charging Full"},"e1a4":{"name":"Battery Full"},"e1a5":{"name":"Battery Std"},"e1a6":{"name":"Battery Unknown"},"eb3e":{"name":"Beach Access"},"e52d":{"name":"Beenhere"},"e14b":{"name":"Block"},"e1a7":{"name":"Bluetooth"},"e60f":{"name":"Bluetooth Audio"},"e1a8":{"name":"Bluetooth Connected"},"e1a9":{"name":"Bluetooth Disabled"},"e1aa":{"name":"Bluetooth Searching"},"e3a2":{"name":"Blur Circular"},"e3a3":{"name":"Blur Linear"},"e3a4":{"name":"Blur Off"},"e3a5":{"name":"Blur On"},"e865":{"name":"Book"},"e866":{"name":"Bookmark"},"e867":{"name":"Bookmark Border"},"e228":{"name":"Border All"},"e229":{"name":"Border Bottom"},"e22a":{"name":"Border Clear"},"e22b":{"name":"Border Color"},"e22c":{"name":"Border Horizontal"},"e22d":{"name":"Border Inner"},"e22e":{"name":"Border Left"},"e22f":{"name":"Border Outer"},"e230":{"name":"Border Right"},"e231":{"name":"Border Style"},"e232":{"name":"Border Top"},"e233":{"name":"Border Vertical"},"e06b":{"name":"Branding Watermark"},"e3a6":{"name":"Brightness 1"},"e3a7":{"name":"Brightness 2"},"e3a8":{"name":"Brightness 3"},"e3a9":{"name":"Brightness 4"},"e3aa":{"name":"Brightness 5"},"e3ab":{"name":"Brightness 6"},"e3ac":{"name":"Brightness 7"},"e1ab":{"name":"Brightness Auto"},"e1ac":{"name":"Brightness High"},"e1ad":{"name":"Brightness Low"},"e1ae":{"name":"Brightness Medium"},"e3ad":{"name":"Broken Image"},"e3ae":{"name":"Brush"},"e6dd":{"name":"Bubble Chart"},"e868":{"name":"Bug Report"},"e869":{"name":"Build"},"e43c":{"name":"Burst Mode"},"e0af":{"name":"Business"},"eb3f":{"name":"Business Center"},"e86a":{"name":"Cached"},"e7e9":{"name":"Cake"},"e0b0":{"name":"Call"},"e0b1":{"name":"Call End"},"e0b2":{"name":"Call Made"},"e0b3":{"name":"Call Merge"},"e0b4":{"name":"Call Missed"},"e0e4":{"name":"Call Missed Outgoing"},"e0b5":{"name":"Call Received"},"e0b6":{"name":"Call Split"},"e06c":{"name":"Call To Action"},"e3af":{"name":"Camera"},"e3b0":{"name":"Camera Alt"},"e8fc":{"name":"Camera Enhance"},"e3b1":{"name":"Camera Front"},"e3b2":{"name":"Camera Rear"},"e3b3":{"name":"Camera Roll"},"e5c9":{"name":"Cancel"},"e8f6":{"name":"Card Giftcard"},"e8f7":{"name":"Card Membership"},"e8f8":{"name":"Card Travel"},"eb40":{"name":"Casino"},"e307":{"name":"Cast"},"e308":{"name":"Cast Connected"},"e3b4":{"name":"Center Focus Strong"},"e3b5":{"name":"Center Focus Weak"},"e86b":{"name":"Change History"},"e0b7":{"name":"Chat"},"e0ca":{"name":"Chat Bubble"},"e0cb":{"name":"Chat Bubble Outline"},"e5ca":{"name":"Check"},"e834":{"name":"Check Box"},"e835":{"name":"Check Box Outline Blank"},"e86c":{"name":"Check Circle"},"e5cb":{"name":"Chevron Left"},"e5cc":{"name":"Chevron Right"},"eb41":{"name":"Child Care"},"eb42":{"name":"Child Friendly"},"e86d":{"name":"Chrome Reader Mode"},"e86e":{"name":"Class"},"e14c":{"name":"Clear"},"e0b8":{"name":"Clear All"},"e5cd":{"name":"Close"},"e01c":{"name":"Closed Caption"},"e2bd":{"name":"Cloud"},"e2be":{"name":"Cloud Circle"},"e2bf":{"name":"Cloud Done"},"e2c0":{"name":"Cloud Download"},"e2c1":{"name":"Cloud Off"},"e2c2":{"name":"Cloud Queue"},"e2c3":{"name":"Cloud Upload"},"e86f":{"name":"Code"},"e3b6":{"name":"Collections"},"e431":{"name":"Collections Bookmark"},"e3b7":{"name":"Color Lens"},"e3b8":{"name":"Colorize"},"e0b9":{"name":"Comment"},"e3b9":{"name":"Compare"},"e915":{"name":"Compare Arrows"},"e30a":{"name":"Computer"},"e638":{"name":"Confirmation Number"},"e0d0":{"name":"Contact Mail"},"e0cf":{"name":"Contact Phone"},"e0ba":{"name":"Contacts"},"e14d":{"name":"Content Copy"},"e14e":{"name":"Content Cut"},"e14f":{"name":"Content Paste"},"e3ba":{"name":"Control Point"},"e3bb":{"name":"Control Point Duplicate"},"e90c":{"name":"Copyright"},"e150":{"name":"Create"},"e2cc":{"name":"Create New Folder"},"e870":{"name":"Credit Card"},"e3be":{"name":"Crop"},"e3bc":{"name":"Crop 16 9"},"e3bd":{"name":"Crop 3 2"},"e3bf":{"name":"Crop 5 4"},"e3c0":{"name":"Crop 7 5"},"e3c1":{"name":"Crop Din"},"e3c2":{"name":"Crop Free"},"e3c3":{"name":"Crop Landscape"},"e3c4":{"name":"Crop Original"},"e3c5":{"name":"Crop Portrait"},"e437":{"name":"Crop Rotate"},"e3c6":{"name":"Crop Square"},"e871":{"name":"Dashboard"},"e1af":{"name":"Data Usage"},"e916":{"name":"Date Range"},"e3c7":{"name":"Dehaze"},"e872":{"name":"Delete"},"e92b":{"name":"Delete Forever"},"e16c":{"name":"Delete Sweep"},"e873":{"name":"Description"},"e30b":{"name":"Desktop Mac"},"e30c":{"name":"Desktop Windows"},"e3c8":{"name":"Details"},"e30d":{"name":"Developer Board"},"e1b0":{"name":"Developer Mode"},"e335":{"name":"Device Hub"},"e1b1":{"name":"Devices"},"e337":{"name":"Devices Other"},"e0bb":{"name":"Dialer Sip"},"e0bc":{"name":"Dialpad"},"e52e":{"name":"Directions"},"e52f":{"name":"Directions Bike"},"e532":{"name":"Directions Boat"},"e530":{"name":"Directions Bus"},"e531":{"name":"Directions Car"},"e534":{"name":"Directions Railway"},"e566":{"name":"Directions Run"},"e533":{"name":"Directions Subway"},"e535":{"name":"Directions Transit"},"e536":{"name":"Directions Walk"},"e610":{"name":"Disc Full"},"e875":{"name":"Dns"},"e612":{"name":"Do Not Disturb"},"e611":{"name":"Do Not Disturb Alt"},"e643":{"name":"Do Not Disturb Off"},"e644":{"name":"Do Not Disturb On"},"e30e":{"name":"Dock"},"e7ee":{"name":"Domain"},"e876":{"name":"Done"},"e877":{"name":"Done All"},"e917":{"name":"Donut Large"},"e918":{"name":"Donut Small"},"e151":{"name":"Drafts"},"e25d":{"name":"Drag Handle"},"e613":{"name":"Drive Eta"},"e1b2":{"name":"Dvr"},"e3c9":{"name":"Edit"},"e568":{"name":"Edit Location"},"e8fb":{"name":"Eject"},"e0be":{"name":"Email"},"e63f":{"name":"Enhanced Encryption"},"e01d":{"name":"Equalizer"},"e000":{"name":"Error"},"e001":{"name":"Error Outline"},"e926":{"name":"Euro Symbol"},"e56d":{"name":"Ev Station"},"e878":{"name":"Event"},"e614":{"name":"Event Available"},"e615":{"name":"Event Busy"},"e616":{"name":"Event Note"},"e903":{"name":"Event Seat"},"e879":{"name":"Exit To App"},"e5ce":{"name":"Expand Less"},"e5cf":{"name":"Expand More"},"e01e":{"name":"Explicit"},"e87a":{"name":"Explore"},"e3ca":{"name":"Exposure"},"e3cb":{"name":"Exposure Neg 1"},"e3cc":{"name":"Exposure Neg 2"},"e3cd":{"name":"Exposure Plus 1"},"e3ce":{"name":"Exposure Plus 2"},"e3cf":{"name":"Exposure Zero"},"e87b":{"name":"Extension"},"e87c":{"name":"Face"},"e01f":{"name":"Fast Forward"},"e020":{"name":"Fast Rewind"},"e87d":{"name":"Favorite"},"e87e":{"name":"Favorite Border"},"e06d":{"name":"Featured Play List"},"e06e":{"name":"Featured Video"},"e87f":{"name":"Feedback"},"e05d":{"name":"Fiber Dvr"},"e061":{"name":"Fiber Manual Record"},"e05e":{"name":"Fiber New"},"e06a":{"name":"Fiber Pin"},"e062":{"name":"Fiber Smart Record"},"e2c4":{"name":"File Download"},"e2c6":{"name":"File Upload"},"e3d3":{"name":"Filter"},"e3d0":{"name":"Filter 1"},"e3d1":{"name":"Filter 2"},"e3d2":{"name":"Filter 3"},"e3d4":{"name":"Filter 4"},"e3d5":{"name":"Filter 5"},"e3d6":{"name":"Filter 6"},"e3d7":{"name":"Filter 7"},"e3d8":{"name":"Filter 8"},"e3d9":{"name":"Filter 9"},"e3da":{"name":"Filter 9 Plus"},"e3db":{"name":"Filter B And W"},"e3dc":{"name":"Filter Center Focus"},"e3dd":{"name":"Filter Drama"},"e3de":{"name":"Filter Frames"},"e3df":{"name":"Filter Hdr"},"e152":{"name":"Filter List"},"e3e0":{"name":"Filter None"},"e3e2":{"name":"Filter Tilt Shift"},"e3e3":{"name":"Filter Vintage"},"e880":{"name":"Find In Page"},"e881":{"name":"Find Replace"},"e90d":{"name":"Fingerprint"},"e5dc":{"name":"First Page"},"eb43":{"name":"Fitness Center"},"e153":{"name":"Flag"},"e3e4":{"name":"Flare"},"e3e5":{"name":"Flash Auto"},"e3e6":{"name":"Flash Off"},"e3e7":{"name":"Flash On"},"e539":{"name":"Flight"},"e904":{"name":"Flight Land"},"e905":{"name":"Flight Takeoff"},"e3e8":{"name":"Flip"},"e882":{"name":"Flip To Back"},"e883":{"name":"Flip To Front"},"e2c7":{"name":"Folder"},"e2c8":{"name":"Folder Open"},"e2c9":{"name":"Folder Shared"},"e617":{"name":"Folder Special"},"e167":{"name":"Font Download"},"e234":{"name":"Format Align Center"},"e235":{"name":"Format Align Justify"},"e236":{"name":"Format Align Left"},"e237":{"name":"Format Align Right"},"e238":{"name":"Format Bold"},"e239":{"name":"Format Clear"},"e23a":{"name":"Format Color Fill"},"e23b":{"name":"Format Color Reset"},"e23c":{"name":"Format Color Text"},"e23d":{"name":"Format Indent Decrease"},"e23e":{"name":"Format Indent Increase"},"e23f":{"name":"Format Italic"},"e240":{"name":"Format Line Spacing"},"e241":{"name":"Format List Bulleted"},"e242":{"name":"Format List Numbered"},"e243":{"name":"Format Paint"},"e244":{"name":"Format Quote"},"e25e":{"name":"Format Shapes"},"e245":{"name":"Format Size"},"e246":{"name":"Format Strikethrough"},"e247":{"name":"Format Textdirection L To R"},"e248":{"name":"Format Textdirection R To L"},"e249":{"name":"Format Underlined"},"e0bf":{"name":"Forum"},"e154":{"name":"Forward"},"e056":{"name":"Forward 10"},"e057":{"name":"Forward 30"},"e058":{"name":"Forward 5"},"eb44":{"name":"Free Breakfast"},"e5d0":{"name":"Fullscreen"},"e5d1":{"name":"Fullscreen Exit"},"e24a":{"name":"Functions"},"e927":{"name":"G Translate"},"e30f":{"name":"Gamepad"},"e021":{"name":"Games"},"e90e":{"name":"Gavel"},"e155":{"name":"Gesture"},"e884":{"name":"Get App"},"e908":{"name":"Gif"},"eb45":{"name":"Golf Course"},"e1b3":{"name":"Gps Fixed"},"e1b4":{"name":"Gps Not Fixed"},"e1b5":{"name":"Gps Off"},"e885":{"name":"Grade"},"e3e9":{"name":"Gradient"},"e3ea":{"name":"Grain"},"e1b8":{"name":"Graphic Eq"},"e3eb":{"name":"Grid Off"},"e3ec":{"name":"Grid On"},"e7ef":{"name":"Group"},"e7f0":{"name":"Group Add"},"e886":{"name":"Group Work"},"e052":{"name":"Hd"},"e3ed":{"name":"Hdr Off"},"e3ee":{"name":"Hdr On"},"e3f1":{"name":"Hdr Strong"},"e3f2":{"name":"Hdr Weak"},"e310":{"name":"Headset"},"e311":{"name":"Headset Mic"},"e3f3":{"name":"Healing"},"e023":{"name":"Hearing"},"e887":{"name":"Help"},"e8fd":{"name":"Help Outline"},"e024":{"name":"High Quality"},"e25f":{"name":"Highlight"},"e888":{"name":"Highlight Off"},"e889":{"name":"History"},"e88a":{"name":"Home"},"eb46":{"name":"Hot Tub"},"e53a":{"name":"Hotel"},"e88b":{"name":"Hourglass Empty"},"e88c":{"name":"Hourglass Full"},"e902":{"name":"Http"},"e88d":{"name":"Https"},"e3f4":{"name":"Image"},"e3f5":{"name":"Image Aspect Ratio"},"e0e0":{"name":"Import Contacts"},"e0c3":{"name":"Import Export"},"e912":{"name":"Important Devices"},"e156":{"name":"Inbox"},"e909":{"name":"Indeterminate Check Box"},"e88e":{"name":"Info"},"e88f":{"name":"Info Outline"},"e890":{"name":"Input"},"e24b":{"name":"Insert Chart"},"e24c":{"name":"Insert Comment"},"e24d":{"name":"Insert Drive File"},"e24e":{"name":"Insert Emoticon"},"e24f":{"name":"Insert Invitation"},"e250":{"name":"Insert Link"},"e251":{"name":"Insert Photo"},"e891":{"name":"Invert Colors"},"e0c4":{"name":"Invert Colors Off"},"e3f6":{"name":"Iso"},"e312":{"name":"Keyboard"},"e313":{"name":"Keyboard Arrow Down"},"e314":{"name":"Keyboard Arrow Left"},"e315":{"name":"Keyboard Arrow Right"},"e316":{"name":"Keyboard Arrow Up"},"e317":{"name":"Keyboard Backspace"},"e318":{"name":"Keyboard Capslock"},"e31a":{"name":"Keyboard Hide"},"e31b":{"name":"Keyboard Return"},"e31c":{"name":"Keyboard Tab"},"e31d":{"name":"Keyboard Voice"},"eb47":{"name":"Kitchen"},"e892":{"name":"Label"},"e893":{"name":"Label Outline"},"e3f7":{"name":"Landscape"},"e894":{"name":"Language"},"e31e":{"name":"Laptop"},"e31f":{"name":"Laptop Chromebook"},"e320":{"name":"Laptop Mac"},"e321":{"name":"Laptop Windows"},"e5dd":{"name":"Last Page"},"e895":{"name":"Launch"},"e53b":{"name":"Layers"},"e53c":{"name":"Layers Clear"},"e3f8":{"name":"Leak Add"},"e3f9":{"name":"Leak Remove"},"e3fa":{"name":"Lens"},"e02e":{"name":"Library Add"},"e02f":{"name":"Library Books"},"e030":{"name":"Library Music"},"e90f":{"name":"Lightbulb Outline"},"e919":{"name":"Line Style"},"e91a":{"name":"Line Weight"},"e260":{"name":"Linear Scale"},"e157":{"name":"Link"},"e438":{"name":"Linked Camera"},"e896":{"name":"List"},"e0c6":{"name":"Live Help"},"e639":{"name":"Live Tv"},"e53f":{"name":"Local Activity"},"e53d":{"name":"Local Airport"},"e53e":{"name":"Local Atm"},"e540":{"name":"Local Bar"},"e541":{"name":"Local Cafe"},"e542":{"name":"Local Car Wash"},"e543":{"name":"Local Convenience Store"},"e556":{"name":"Local Dining"},"e544":{"name":"Local Drink"},"e545":{"name":"Local Florist"},"e546":{"name":"Local Gas Station"},"e547":{"name":"Local Grocery Store"},"e548":{"name":"Local Hospital"},"e549":{"name":"Local Hotel"},"e54a":{"name":"Local Laundry Service"},"e54b":{"name":"Local Library"},"e54c":{"name":"Local Mall"},"e54d":{"name":"Local Movies"},"e54e":{"name":"Local Offer"},"e54f":{"name":"Local Parking"},"e550":{"name":"Local Pharmacy"},"e551":{"name":"Local Phone"},"e552":{"name":"Local Pizza"},"e553":{"name":"Local Play"},"e554":{"name":"Local Post Office"},"e555":{"name":"Local Printshop"},"e557":{"name":"Local See"},"e558":{"name":"Local Shipping"},"e559":{"name":"Local Taxi"},"e7f1":{"name":"Location City"},"e1b6":{"name":"Location Disabled"},"e0c7":{"name":"Location Off"},"e0c8":{"name":"Location On"},"e1b7":{"name":"Location Searching"},"e897":{"name":"Lock"},"e898":{"name":"Lock Open"},"e899":{"name":"Lock Outline"},"e3fc":{"name":"Looks"},"e3fb":{"name":"Looks 3"},"e3fd":{"name":"Looks 4"},"e3fe":{"name":"Looks 5"},"e3ff":{"name":"Looks 6"},"e400":{"name":"Looks One"},"e401":{"name":"Looks Two"},"e028":{"name":"Loop"},"e402":{"name":"Loupe"},"e16d":{"name":"Low Priority"},"e89a":{"name":"Loyalty"},"e158":{"name":"Mail"},"e0e1":{"name":"Mail Outline"},"e55b":{"name":"Map"},"e159":{"name":"Markunread"},"e89b":{"name":"Markunread Mailbox"},"e322":{"name":"Memory"},"e5d2":{"name":"Menu"},"e252":{"name":"Merge Type"},"e0c9":{"name":"Message"},"e029":{"name":"Mic"},"e02a":{"name":"Mic None"},"e02b":{"name":"Mic Off"},"e618":{"name":"Mms"},"e253":{"name":"Mode Comment"},"e254":{"name":"Mode Edit"},"e263":{"name":"Monetization On"},"e25c":{"name":"Money Off"},"e403":{"name":"Monochrome Photos"},"e7f2":{"name":"Mood"},"e7f3":{"name":"Mood Bad"},"e619":{"name":"More"},"e5d3":{"name":"More Horiz"},"e5d4":{"name":"More Vert"},"e91b":{"name":"Motorcycle"},"e323":{"name":"Mouse"},"e168":{"name":"Move To Inbox"},"e02c":{"name":"Movie"},"e404":{"name":"Movie Creation"},"e43a":{"name":"Movie Filter"},"e6df":{"name":"Multiline Chart"},"e405":{"name":"Music Note"},"e063":{"name":"Music Video"},"e55c":{"name":"My Location"},"e406":{"name":"Nature"},"e407":{"name":"Nature People"},"e408":{"name":"Navigate Before"},"e409":{"name":"Navigate Next"},"e55d":{"name":"Navigation"},"e569":{"name":"Near Me"},"e1b9":{"name":"Network Cell"},"e640":{"name":"Network Check"},"e61a":{"name":"Network Locked"},"e1ba":{"name":"Network Wifi"},"e031":{"name":"New Releases"},"e16a":{"name":"Next Week"},"e1bb":{"name":"Nfc"},"e641":{"name":"No Encryption"},"e0cc":{"name":"No Sim"},"e033":{"name":"Not Interested"},"e06f":{"name":"Note"},"e89c":{"name":"Note Add"},"e7f4":{"name":"Notifications"},"e7f7":{"name":"Notifications Active"},"e7f5":{"name":"Notifications None"},"e7f6":{"name":"Notifications Off"},"e7f8":{"name":"Notifications Paused"},"e90a":{"name":"Offline Pin"},"e63a":{"name":"Ondemand Video"},"e91c":{"name":"Opacity"},"e89d":{"name":"Open In Browser"},"e89e":{"name":"Open In New"},"e89f":{"name":"Open With"},"e7f9":{"name":"Pages"},"e8a0":{"name":"Pageview"},"e40a":{"name":"Palette"},"e925":{"name":"Pan Tool"},"e40b":{"name":"Panorama"},"e40c":{"name":"Panorama Fish Eye"},"e40d":{"name":"Panorama Horizontal"},"e40e":{"name":"Panorama Vertical"},"e40f":{"name":"Panorama Wide Angle"},"e7fa":{"name":"Party Mode"},"e034":{"name":"Pause"},"e035":{"name":"Pause Circle Filled"},"e036":{"name":"Pause Circle Outline"},"e8a1":{"name":"Payment"},"e7fb":{"name":"People"},"e7fc":{"name":"People Outline"},"e8a2":{"name":"Perm Camera Mic"},"e8a3":{"name":"Perm Contact Calendar"},"e8a4":{"name":"Perm Data Setting"},"e8a5":{"name":"Perm Device Information"},"e8a6":{"name":"Perm Identity"},"e8a7":{"name":"Perm Media"},"e8a8":{"name":"Perm Phone Msg"},"e8a9":{"name":"Perm Scan Wifi"},"e7fd":{"name":"Person"},"e7fe":{"name":"Person Add"},"e7ff":{"name":"Person Outline"},"e55a":{"name":"Person Pin"},"e56a":{"name":"Person Pin Circle"},"e63b":{"name":"Personal Video"},"e91d":{"name":"Pets"},"e0cd":{"name":"Phone"},"e324":{"name":"Phone Android"},"e61b":{"name":"Phone Bluetooth Speaker"},"e61c":{"name":"Phone Forwarded"},"e61d":{"name":"Phone In Talk"},"e325":{"name":"Phone Iphone"},"e61e":{"name":"Phone Locked"},"e61f":{"name":"Phone Missed"},"e620":{"name":"Phone Paused"},"e326":{"name":"Phonelink"},"e0db":{"name":"Phonelink Erase"},"e0dc":{"name":"Phonelink Lock"},"e327":{"name":"Phonelink Off"},"e0dd":{"name":"Phonelink Ring"},"e0de":{"name":"Phonelink Setup"},"e410":{"name":"Photo"},"e411":{"name":"Photo Album"},"e412":{"name":"Photo Camera"},"e43b":{"name":"Photo Filter"},"e413":{"name":"Photo Library"},"e432":{"name":"Photo Size Select Actual"},"e433":{"name":"Photo Size Select Large"},"e434":{"name":"Photo Size Select Small"},"e415":{"name":"Picture As Pdf"},"e8aa":{"name":"Picture In Picture"},"e911":{"name":"Picture In Picture Alt"},"e6c4":{"name":"Pie Chart"},"e6c5":{"name":"Pie Chart Outlined"},"e55e":{"name":"Pin Drop"},"e55f":{"name":"Place"},"e037":{"name":"Play Arrow"},"e038":{"name":"Play Circle Filled"},"e039":{"name":"Play Circle Outline"},"e906":{"name":"Play For Work"},"e03b":{"name":"Playlist Add"},"e065":{"name":"Playlist Add Check"},"e05f":{"name":"Playlist Play"},"e800":{"name":"Plus One"},"e801":{"name":"Poll"},"e8ab":{"name":"Polymer"},"eb48":{"name":"Pool"},"e0ce":{"name":"Portable Wifi Off"},"e416":{"name":"Portrait"},"e63c":{"name":"Power"},"e336":{"name":"Power Input"},"e8ac":{"name":"Power Settings New"},"e91e":{"name":"Pregnant Woman"},"e0df":{"name":"Present To All"},"e8ad":{"name":"Print"},"e645":{"name":"Priority High"},"e80b":{"name":"Public"},"e255":{"name":"Publish"},"e8ae":{"name":"Query Builder"},"e8af":{"name":"Question Answer"},"e03c":{"name":"Queue"},"e03d":{"name":"Queue Music"},"e066":{"name":"Queue Play Next"},"e03e":{"name":"Radio"},"e837":{"name":"Radio Button Checked"},"e836":{"name":"Radio Button Unchecked"},"e560":{"name":"Rate Review"},"e8b0":{"name":"Receipt"},"e03f":{"name":"Recent Actors"},"e91f":{"name":"Record Voice Over"},"e8b1":{"name":"Redeem"},"e15a":{"name":"Redo"},"e5d5":{"name":"Refresh"},"e15b":{"name":"Remove"},"e15c":{"name":"Remove Circle"},"e15d":{"name":"Remove Circle Outline"},"e067":{"name":"Remove From Queue"},"e417":{"name":"Remove Red Eye"},"e928":{"name":"Remove Shopping Cart"},"e8fe":{"name":"Reorder"},"e040":{"name":"Repeat"},"e041":{"name":"Repeat One"},"e042":{"name":"Replay"},"e059":{"name":"Replay 10"},"e05a":{"name":"Replay 30"},"e05b":{"name":"Replay 5"},"e15e":{"name":"Reply"},"e15f":{"name":"Reply All"},"e160":{"name":"Report"},"e8b2":{"name":"Report Problem"},"e56c":{"name":"Restaurant"},"e561":{"name":"Restaurant Menu"},"e8b3":{"name":"Restore"},"e929":{"name":"Restore Page"},"e0d1":{"name":"Ring Volume"},"e8b4":{"name":"Room"},"eb49":{"name":"Room Service"},"e418":{"name":"Rotate 90 Degrees Ccw"},"e419":{"name":"Rotate Left"},"e41a":{"name":"Rotate Right"},"e920":{"name":"Rounded Corner"},"e328":{"name":"Router"},"e921":{"name":"Rowing"},"e0e5":{"name":"Rss Feed"},"e642":{"name":"Rv Hookup"},"e562":{"name":"Satellite"},"e161":{"name":"Save"},"e329":{"name":"Scanner"},"e8b5":{"name":"Schedule"},"e80c":{"name":"School"},"e1be":{"name":"Screen Lock Landscape"},"e1bf":{"name":"Screen Lock Portrait"},"e1c0":{"name":"Screen Lock Rotation"},"e1c1":{"name":"Screen Rotation"},"e0e2":{"name":"Screen Share"},"e623":{"name":"Sd Card"},"e1c2":{"name":"Sd Storage"},"e8b6":{"name":"Search"},"e32a":{"name":"Security"},"e162":{"name":"Select All"},"e163":{"name":"Send"},"e811":{"name":"Sentiment Dissatisfied"},"e812":{"name":"Sentiment Neutral"},"e813":{"name":"Sentiment Satisfied"},"e814":{"name":"Sentiment Very Dissatisfied"},"e815":{"name":"Sentiment Very Satisfied"},"e8b8":{"name":"Settings"},"e8b9":{"name":"Settings Applications"},"e8ba":{"name":"Settings Backup Restore"},"e8bb":{"name":"Settings Bluetooth"},"e8bd":{"name":"Settings Brightness"},"e8bc":{"name":"Settings Cell"},"e8be":{"name":"Settings Ethernet"},"e8bf":{"name":"Settings Input Antenna"},"e8c0":{"name":"Settings Input Component"},"e8c1":{"name":"Settings Input Composite"},"e8c2":{"name":"Settings Input Hdmi"},"e8c3":{"name":"Settings Input Svideo"},"e8c4":{"name":"Settings Overscan"},"e8c5":{"name":"Settings Phone"},"e8c6":{"name":"Settings Power"},"e8c7":{"name":"Settings Remote"},"e1c3":{"name":"Settings System Daydream"},"e8c8":{"name":"Settings Voice"},"e80d":{"name":"Share"},"e8c9":{"name":"Shop"},"e8ca":{"name":"Shop Two"},"e8cb":{"name":"Shopping Basket"},"e8cc":{"name":"Shopping Cart"},"e261":{"name":"Short Text"},"e6e1":{"name":"Show Chart"},"e043":{"name":"Shuffle"},"e1c8":{"name":"Signal Cellular 4 Bar"},"e1cd":{"name":"Signal Cellular Connected No Internet 4 Bar"},"e1ce":{"name":"Signal Cellular No Sim"},"e1cf":{"name":"Signal Cellular Null"},"e1d0":{"name":"Signal Cellular Off"},"e1d8":{"name":"Signal Wifi 4 Bar"},"e1d9":{"name":"Signal Wifi 4 Bar Lock"},"e1da":{"name":"Signal Wifi Off"},"e32b":{"name":"Sim Card"},"e624":{"name":"Sim Card Alert"},"e044":{"name":"Skip Next"},"e045":{"name":"Skip Previous"},"e41b":{"name":"Slideshow"},"e068":{"name":"Slow Motion Video"},"e32c":{"name":"Smartphone"},"eb4a":{"name":"Smoke Free"},"eb4b":{"name":"Smoking Rooms"},"e625":{"name":"Sms"},"e626":{"name":"Sms Failed"},"e046":{"name":"Snooze"},"e164":{"name":"Sort"},"e053":{"name":"Sort By Alpha"},"eb4c":{"name":"Spa"},"e256":{"name":"Space Bar"},"e32d":{"name":"Speaker"},"e32e":{"name":"Speaker Group"},"e8cd":{"name":"Speaker Notes"},"e92a":{"name":"Speaker Notes Off"},"e0d2":{"name":"Speaker Phone"},"e8ce":{"name":"Spellcheck"},"e838":{"name":"Star"},"e83a":{"name":"Star Border"},"e839":{"name":"Star Half"},"e8d0":{"name":"Stars"},"e0d3":{"name":"Stay Current Landscape"},"e0d4":{"name":"Stay Current Portrait"},"e0d5":{"name":"Stay Primary Landscape"},"e0d6":{"name":"Stay Primary Portrait"},"e047":{"name":"Stop"},"e0e3":{"name":"Stop Screen Share"},"e1db":{"name":"Storage"},"e8d1":{"name":"Store"},"e563":{"name":"Store Mall Directory"},"e41c":{"name":"Straighten"},"e56e":{"name":"Streetview"},"e257":{"name":"Strikethrough S"},"e41d":{"name":"Style"},"e5d9":{"name":"Subdirectory Arrow Left"},"e5da":{"name":"Subdirectory Arrow Right"},"e8d2":{"name":"Subject"},"e064":{"name":"Subscriptions"},"e048":{"name":"Subtitles"},"e56f":{"name":"Subway"},"e8d3":{"name":"Supervisor Account"},"e049":{"name":"Surround Sound"},"e0d7":{"name":"Swap Calls"},"e8d4":{"name":"Swap Horiz"},"e8d5":{"name":"Swap Vert"},"e8d6":{"name":"Swap Vertical Circle"},"e41e":{"name":"Switch Camera"},"e41f":{"name":"Switch Video"},"e627":{"name":"Sync"},"e628":{"name":"Sync Disabled"},"e629":{"name":"Sync Problem"},"e62a":{"name":"System Update"},"e8d7":{"name":"System Update Alt"},"e8d8":{"name":"Tab"},"e8d9":{"name":"Tab Unselected"},"e32f":{"name":"Tablet"},"e330":{"name":"Tablet Android"},"e331":{"name":"Tablet Mac"},"e420":{"name":"Tag Faces"},"e62b":{"name":"Tap And Play"},"e564":{"name":"Terrain"},"e262":{"name":"Text Fields"},"e165":{"name":"Text Format"},"e0d8":{"name":"Textsms"},"e421":{"name":"Texture"},"e8da":{"name":"Theaters"},"e8db":{"name":"Thumb Down"},"e8dc":{"name":"Thumb Up"},"e8dd":{"name":"Thumbs Up Down"},"e62c":{"name":"Time To Leave"},"e422":{"name":"Timelapse"},"e922":{"name":"Timeline"},"e425":{"name":"Timer"},"e423":{"name":"Timer 10"},"e424":{"name":"Timer 3"},"e426":{"name":"Timer Off"},"e264":{"name":"Title"},"e8de":{"name":"Toc"},"e8df":{"name":"Today"},"e8e0":{"name":"Toll"},"e427":{"name":"Tonality"},"e913":{"name":"Touch App"},"e332":{"name":"Toys"},"e8e1":{"name":"Track Changes"},"e565":{"name":"Traffic"},"e570":{"name":"Train"},"e571":{"name":"Tram"},"e572":{"name":"Transfer Within A Station"},"e428":{"name":"Transform"},"e8e2":{"name":"Translate"},"e8e3":{"name":"Trending Down"},"e8e4":{"name":"Trending Flat"},"e8e5":{"name":"Trending Up"},"e429":{"name":"Tune"},"e8e6":{"name":"Turned In"},"e8e7":{"name":"Turned In Not"},"e333":{"name":"Tv"},"e169":{"name":"Unarchive"},"e166":{"name":"Undo"},"e5d6":{"name":"Unfold Less"},"e5d7":{"name":"Unfold More"},"e923":{"name":"Update"},"e1e0":{"name":"Usb"},"e8e8":{"name":"Verified User"},"e258":{"name":"Vertical Align Bottom"},"e259":{"name":"Vertical Align Center"},"e25a":{"name":"Vertical Align Top"},"e62d":{"name":"Vibration"},"e070":{"name":"Video Call"},"e071":{"name":"Video Label"},"e04a":{"name":"Video Library"},"e04b":{"name":"Videocam"},"e04c":{"name":"Videocam Off"},"e338":{"name":"Videogame Asset"},"e8e9":{"name":"View Agenda"},"e8ea":{"name":"View Array"},"e8eb":{"name":"View Carousel"},"e8ec":{"name":"View Column"},"e42a":{"name":"View Comfy"},"e42b":{"name":"View Compact"},"e8ed":{"name":"View Day"},"e8ee":{"name":"View Headline"},"e8ef":{"name":"View List"},"e8f0":{"name":"View Module"},"e8f1":{"name":"View Quilt"},"e8f2":{"name":"View Stream"},"e8f3":{"name":"View Week"},"e435":{"name":"Vignette"},"e8f4":{"name":"Visibility"},"e8f5":{"name":"Visibility Off"},"e62e":{"name":"Voice Chat"},"e0d9":{"name":"Voicemail"},"e04d":{"name":"Volume Down"},"e04e":{"name":"Volume Mute"},"e04f":{"name":"Volume Off"},"e050":{"name":"Volume Up"},"e0da":{"name":"Vpn Key"},"e62f":{"name":"Vpn Lock"},"e1bc":{"name":"Wallpaper"},"e002":{"name":"Warning"},"e334":{"name":"Watch"},"e924":{"name":"Watch Later"},"e42c":{"name":"Wb Auto"},"e42d":{"name":"Wb Cloudy"},"e42e":{"name":"Wb Incandescent"},"e436":{"name":"Wb Iridescent"},"e430":{"name":"Wb Sunny"},"e63d":{"name":"Wc"},"e051":{"name":"Web"},"e069":{"name":"Web Asset"},"e16b":{"name":"Weekend"},"e80e":{"name":"Whatshot"},"e1bd":{"name":"Widgets"},"e63e":{"name":"Wifi"},"e1e1":{"name":"Wifi Lock"},"e1e2":{"name":"Wifi Tethering"},"e8f9":{"name":"Work"},"e25b":{"name":"Wrap Text"},"e8fa":{"name":"Youtube Searched For"},"e8ff":{"name":"Zoom In"},"e900":{"name":"Zoom Out"},"e56b":{"name":"Zoom Out Map"}}}
================================================
FILE: static/iconfont/README.md
================================================
The recommended way to use the Material Icons font is by linking to the web font hosted on Google Fonts:
```html
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet">
```
Read more in our full usage guide:
http://google.github.io/material-design-icons/#icon-font-for-the-web
================================================
FILE: static/iconfont/codepoints
================================================
3d_rotation e84d
ac_unit eb3b
access_alarm e190
access_alarms e191
access_time e192
accessibility e84e
accessible e914
account_balance e84f
account_balance_wallet e850
account_box e851
account_circle e853
adb e60e
add e145
add_a_photo e439
add_alarm e193
add_alert e003
add_box e146
add_circle e147
add_circle_outline e148
add_location e567
add_shopping_cart e854
add_to_photos e39d
add_to_queue e05c
adjust e39e
airline_seat_flat e630
airline_seat_flat_angled e631
airline_seat_individual_suite e632
airline_seat_legroom_extra e633
airline_seat_legroom_normal e634
airline_seat_legroom_reduced e635
airline_seat_recline_extra e636
airline_seat_recline_normal e637
airplanemode_active e195
airplanemode_inactive e194
airplay e055
airport_shuttle eb3c
alarm e855
alarm_add e856
alarm_off e857
alarm_on e858
album e019
all_inclusive eb3d
all_out e90b
android e859
announcement e85a
apps e5c3
archive e149
arrow_back e5c4
arrow_downward e5db
arrow_drop_down e5c5
arrow_drop_down_circle e5c6
arrow_drop_up e5c7
arrow_forward e5c8
arrow_upward e5d8
art_track e060
aspect_ratio e85b
assessment e85c
assignment e85d
assignment_ind e85e
assignment_late e85f
assignment_return e860
assignment_returned e861
assignment_turned_in e862
assistant e39f
assistant_photo e3a0
attach_file e226
attach_money e227
attachment e2bc
audiotrack e3a1
autorenew e863
av_timer e01b
backspace e14a
backup e864
battery_alert e19c
battery_charging_full e1a3
battery_full e1a4
battery_std e1a5
battery_unknown e1a6
beach_access eb3e
beenhere e52d
block e14b
bluetooth e1a7
bluetooth_audio e60f
bluetooth_connected e1a8
bluetooth_disabled e1a9
bluetooth_searching e1aa
blur_circular e3a2
blur_linear e3a3
blur_off e3a4
blur_on e3a5
book e865
bookmark e866
bookmark_border e867
border_all e228
border_bottom e229
border_clear e22a
border_color e22b
border_horizontal e22c
border_inner e22d
border_left e22e
border_outer e22f
border_right e230
border_style e231
border_top e232
border_vertical e233
branding_watermark e06b
brightness_1 e3a6
brightness_2 e3a7
brightness_3 e3a8
brightness_4 e3a9
brightness_5 e3aa
brightness_6 e3ab
brightness_7 e3ac
brightness_auto e1ab
brightness_high e1ac
brightness_low e1ad
brightness_medium e1ae
broken_image e3ad
brush e3ae
bubble_chart e6dd
bug_report e868
build e869
burst_mode e43c
business e0af
business_center eb3f
cached e86a
cake e7e9
call e0b0
call_end e0b1
call_made e0b2
call_merge e0b3
call_missed e0b4
call_missed_outgoing e0e4
call_received e0b5
call_split e0b6
call_to_action e06c
camera e3af
camera_alt e3b0
camera_enhance e8fc
camera_front e3b1
camera_rear e3b2
camera_roll e3b3
cancel e5c9
card_giftcard e8f6
card_membership e8f7
card_travel e8f8
casino eb40
cast e307
cast_connected e308
center_focus_strong e3b4
center_focus_weak e3b5
change_history e86b
chat e0b7
chat_bubble e0ca
chat_bubble_outline e0cb
check e5ca
check_box e834
check_box_outline_blank e835
check_circle e86c
chevron_left e5cb
chevron_right e5cc
child_care eb41
child_friendly eb42
chrome_reader_mode e86d
class e86e
clear e14c
clear_all e0b8
close e5cd
closed_caption e01c
cloud e2bd
cloud_circle e2be
cloud_done e2bf
cloud_download e2c0
cloud_off e2c1
cloud_queue e2c2
cloud_upload e2c3
code e86f
collections e3b6
collections_bookmark e431
color_lens e3b7
colorize e3b8
comment e0b9
compare e3b9
compare_arrows e915
computer e30a
confirmation_number e638
contact_mail e0d0
contact_phone e0cf
contacts e0ba
content_copy e14d
content_cut e14e
content_paste e14f
control_point e3ba
control_point_duplicate e3bb
copyright e90c
create e150
create_new_folder e2cc
credit_card e870
crop e3be
crop_16_9 e3bc
crop_3_2 e3bd
crop_5_4 e3bf
crop_7_5 e3c0
crop_din e3c1
crop_free e3c2
crop_landscape e3c3
crop_original e3c4
crop_portrait e3c5
crop_rotate e437
crop_square e3c6
dashboard e871
data_usage e1af
date_range e916
dehaze e3c7
delete e872
delete_forever e92b
delete_sweep e16c
description e873
desktop_mac e30b
desktop_windows e30c
details e3c8
developer_board e30d
developer_mode e1b0
device_hub e335
devices e1b1
devices_other e337
dialer_sip e0bb
dialpad e0bc
directions e52e
directions_bike e52f
directions_boat e532
directions_bus e530
directions_car e531
directions_railway e534
directions_run e566
directions_subway e533
directions_transit e535
directions_walk e536
disc_full e610
dns e875
do_not_disturb e612
do_not_disturb_alt e611
do_not_disturb_off e643
do_not_disturb_on e644
dock e30e
domain e7ee
done e876
done_all e877
donut_large e917
donut_small e918
drafts e151
drag_handle e25d
drive_eta e613
dvr e1b2
edit e3c9
edit_location e568
eject e8fb
email e0be
enhanced_encryption e63f
equalizer e01d
error e000
error_outline e001
euro_symbol e926
ev_station e56d
event e878
event_available e614
event_busy e615
event_note e616
event_seat e903
exit_to_app e879
expand_less e5ce
expand_more e5cf
explicit e01e
explore e87a
exposure e3ca
exposure_neg_1 e3cb
exposure_neg_2 e3cc
exposure_plus_1 e3cd
exposure_plus_2 e3ce
exposure_zero e3cf
extension e87b
face e87c
fast_forward e01f
fast_rewind e020
favorite e87d
favorite_border e87e
featured_play_list e06d
featured_video e06e
feedback e87f
fiber_dvr e05d
fiber_manual_record e061
fiber_new e05e
fiber_pin e06a
fiber_smart_record e062
file_download e2c4
file_upload e2c6
filter e3d3
filter_1 e3d0
filter_2 e3d1
filter_3 e3d2
filter_4 e3d4
filter_5 e3d5
filter_6 e3d6
filter_7 e3d7
filter_8 e3d8
filter_9 e3d9
filter_9_plus e3da
filter_b_and_w e3db
filter_center_focus e3dc
filter_drama e3dd
filter_frames e3de
filter_hdr e3df
filter_list e152
filter_none e3e0
filter_tilt_shift e3e2
filter_vintage e3e3
find_in_page e880
find_replace e881
fingerprint e90d
first_page e5dc
fitness_center eb43
flag e153
flare e3e4
flash_auto e3e5
flash_off e3e6
flash_on e3e7
flight e539
flight_land e904
flight_takeoff e905
flip e3e8
flip_to_back e882
flip_to_front e883
folder e2c7
folder_open e2c8
folder_shared e2c9
folder_special e617
font_download e167
format_align_center e234
format_align_justify e235
format_align_left e236
format_align_right e237
format_bold e238
format_clear e239
format_color_fill e23a
format_color_reset e23b
format_color_text e23c
format_indent_decrease e23d
format_indent_increase e23e
format_italic e23f
format_line_spacing e240
format_list_bulleted e241
format_list_numbered e242
format_paint e243
format_quote e244
format_shapes e25e
format_size e245
format_strikethrough e246
format_textdirection_l_to_r e247
format_textdirection_r_to_l e248
format_underlined e249
forum e0bf
forward e154
forward_10 e056
forward_30 e057
forward_5 e058
free_breakfast eb44
fullscreen e5d0
fullscreen_exit e5d1
functions e24a
g_translate e927
gamepad e30f
games e021
gavel e90e
gesture e155
get_app e884
gif e908
golf_course eb45
gps_fixed e1b3
gps_not_fixed e1b4
gps_off e1b5
grade e885
gradient e3e9
grain e3ea
graphic_eq e1b8
grid_off e3eb
grid_on e3ec
group e7ef
group_add e7f0
group_work e886
hd e052
hdr_off e3ed
hdr_on e3ee
hdr_strong e3f1
hdr_weak e3f2
headset e310
headset_mic e311
healing e3f3
hearing e023
help e887
help_outline e8fd
high_quality e024
highlight e25f
highlight_off e888
history e889
home e88a
hot_tub eb46
hotel e53a
hourglass_empty e88b
hourglass_full e88c
http e902
https e88d
image e3f4
image_aspect_ratio e3f5
import_contacts e0e0
import_export e0c3
important_devices e912
inbox e156
indeterminate_check_box e909
info e88e
info_outline e88f
input e890
insert_chart e24b
insert_comment e24c
insert_drive_file e24d
insert_emoticon e24e
insert_invitation e24f
insert_link e250
insert_photo e251
invert_colors e891
invert_colors_off e0c4
iso e3f6
keyboard e312
keyboard_arrow_down e313
keyboard_arrow_left e314
keyboard_arrow_right e315
keyboard_arrow_up e316
keyboard_backspace e317
keyboard_capslock e318
keyboard_hide e31a
keyboard_return e31b
keyboard_tab e31c
keyboard_voice e31d
kitchen eb47
label e892
label_outline e893
landscape e3f7
language e894
laptop e31e
laptop_chromebook e31f
laptop_mac e320
laptop_windows e321
last_page e5dd
launch e895
layers e53b
layers_clear e53c
leak_add e3f8
leak_remove e3f9
lens e3fa
library_add e02e
library_books e02f
library_music e030
lightbulb_outline e90f
line_style e919
line_weight e91a
linear_scale e260
link e157
linked_camera e438
list e896
live_help e0c6
live_tv e639
local_activity e53f
local_airport e53d
local_atm e53e
local_bar e540
local_cafe e541
local_car_wash e542
local_convenience_store e543
local_dining e556
local_drink e544
local_florist e545
local_gas_station e546
local_grocery_store e547
local_hospital e548
local_hotel e549
local_laundry_service e54a
local_library e54b
local_mall e54c
local_movies e54d
local_offer e54e
local_parking e54f
local_pharmacy e550
local_phone e551
local_pizza e552
local_play e553
local_post_office e554
local_printshop e555
local_see e557
local_shipping e558
local_taxi e559
location_city e7f1
location_disabled e1b6
location_off e0c7
location_on e0c8
location_searching e1b7
lock e897
lock_open e898
lock_outline e899
looks e3fc
looks_3 e3fb
looks_4 e3fd
looks_5 e3fe
looks_6 e3ff
looks_one e400
looks_two e401
loop e028
loupe e402
low_priority e16d
loyalty e89a
mail e158
mail_outline e0e1
map e55b
markunread e159
markunread_mailbox e89b
memory e322
menu e5d2
merge_type e252
message e0c9
mic e029
mic_none e02a
mic_off e02b
mms e618
mode_comment e253
mode_edit e254
monetization_on e263
money_off e25c
monochrome_photos e403
mood e7f2
mood_bad e7f3
more e619
more_horiz e5d3
more_vert e5d4
motorcycle e91b
mouse e323
move_to_inbox e168
movie e02c
movie_creation e404
movie_filter e43a
multiline_chart e6df
music_note e405
music_video e063
my_location e55c
nature e406
nature_people e407
navigate_before e408
navigate_next e409
navigation e55d
near_me e569
network_cell e1b9
network_check e640
network_locked e61a
network_wifi e1ba
new_releases e031
next_week e16a
nfc e1bb
no_encryption e641
no_sim e0cc
not_interested e033
note e06f
note_add e89c
notifications e7f4
notifications_active e7f7
notifications_none e7f5
notifications_off e7f6
notifications_paused e7f8
offline_pin e90a
ondemand_video e63a
opacity e91c
open_in_browser e89d
open_in_new e89e
open_with e89f
pages e7f9
pageview e8a0
palette e40a
pan_tool e925
panorama e40b
panorama_fish_eye e40c
panorama_horizontal e40d
panorama_vertical e40e
panorama_wide_angle e40f
party_mode e7fa
pause e034
pause_circle_filled e035
pause_circle_outline e036
payment e8a1
people e7fb
people_outline e7fc
perm_camera_mic e8a2
perm_contact_calendar e8a3
perm_data_setting e8a4
perm_device_information e8a5
perm_identity e8a6
perm_media e8a7
perm_phone_msg e8a8
perm_scan_wifi e8a9
person e7fd
person_add e7fe
person_outline e7ff
person_pin e55a
person_pin_circle e56a
personal_video e63b
pets e91d
phone e0cd
phone_android e324
phone_bluetooth_speaker e61b
phone_forwarded e61c
phone_in_talk e61d
phone_iphone e325
phone_locked e61e
phone_missed e61f
phone_paused e620
phonelink e326
phonelink_erase e0db
phonelink_lock e0dc
phonelink_off e327
phonelink_ring e0dd
phonelink_setup e0de
photo e410
photo_album e411
photo_camera e412
photo_filter e43b
photo_library e413
photo_size_select_actual e432
photo_size_select_large e433
photo_size_select_small e434
picture_as_pdf e415
picture_in_picture e8aa
picture_in_picture_alt e911
pie_chart e6c4
pie_chart_outlined e6c5
pin_drop e55e
place e55f
play_arrow e037
play_circle_filled e038
play_circle_outline e039
play_for_work e906
playlist_add e03b
playlist_add_check e065
playlist_play e05f
plus_one e800
poll e801
polymer e8ab
pool eb48
portable_wifi_off e0ce
portrait e416
power e63c
power_input e336
power_settings_new e8ac
pregnant_woman e91e
present_to_all e0df
print e8ad
priority_high e645
public e80b
publish e255
query_builder e8ae
question_answer e8af
queue e03c
queue_music e03d
queue_play_next e066
radio e03e
radio_button_checked e837
radio_button_unchecked e836
rate_review e560
receipt e8b0
recent_actors e03f
record_voice_over e91f
redeem e8b1
redo e15a
refresh e5d5
remove e15b
remove_circle e15c
remove_circle_outline e15d
remove_from_queue e067
remove_red_eye e417
remove_shopping_cart e928
reorder e8fe
repeat e040
repeat_one e041
replay e042
replay_10 e059
replay_30 e05a
replay_5 e05b
reply e15e
reply_all e15f
report e160
report_problem e8b2
restaurant e56c
restaurant_menu e561
restore e8b3
restore_page e929
ring_volume e0d1
room e8b4
room_service eb49
rotate_90_degrees_ccw e418
rotate_left e419
rotate_right e41a
rounded_corner e920
router e328
rowing e921
rss_feed e0e5
rv_hookup e642
satellite e562
save e161
scanner e329
schedule e8b5
school e80c
screen_lock_landscape e1be
screen_lock_portrait e1bf
screen_lock_rotation e1c0
screen_rotation e1c1
screen_share e0e2
sd_card e623
sd_storage e1c2
search e8b6
security e32a
select_all e162
send e163
sentiment_dissatisfied e811
sentiment_neutral e812
sentiment_satisfied e813
sentiment_very_dissatisfied e814
sentiment_very_satisfied e815
settings e8b8
settings_applications e8b9
settings_backup_restore e8ba
settings_bluetooth e8bb
settings_brightness e8bd
settings_cell e8bc
settings_ethernet e8be
settings_input_antenna e8bf
settings_input_component e8c0
settings_input_composite e8c1
settings_input_hdmi e8c2
settings_input_svideo e8c3
settings_overscan e8c4
settings_phone e8c5
settings_power e8c6
settings_remote e8c7
settings_system_daydream e1c3
settings_voice e8c8
share e80d
shop e8c9
shop_two e8ca
shopping_basket e8cb
shopping_cart e8cc
short_text e261
show_chart e6e1
shuffle e043
signal_cellular_4_bar e1c8
signal_cellular_connected_no_internet_4_bar e1cd
signal_cellular_no_sim e1ce
signal_cellular_null e1cf
signal_cellular_off e1d0
signal_wifi_4_bar e1d8
signal_wifi_4_bar_lock e1d9
signal_wifi_off e1da
sim_card e32b
sim_card_alert e624
skip_next e044
skip_previous e045
slideshow e41b
slow_motion_video e068
smartphone e32c
smoke_free eb4a
smoking_rooms eb4b
sms e625
sms_failed e626
snooze e046
sort e164
sort_by_alpha e053
spa eb4c
space_bar e256
speaker e32d
speaker_group e32e
speaker_notes e8cd
speaker_notes_off e92a
speaker_phone e0d2
spellcheck e8ce
star e838
star_border e83a
star_half e839
stars e8d0
stay_current_landscape e0d3
stay_current_portrait e0d4
stay_primary_landscape e0d5
stay_primary_portrait e0d6
stop e047
stop_screen_share e0e3
storage e1db
store e8d1
store_mall_directory e563
straighten e41c
streetview e56e
strikethrough_s e257
style e41d
subdirectory_arrow_left e5d9
subdirectory_arrow_right e5da
subject e8d2
subscriptions e064
subtitles e048
subway e56f
supervisor_account e8d3
surround_sound e049
swap_calls e0d7
swap_horiz e8d4
swap_vert e8d5
swap_vertical_circle e8d6
switch_camera e41e
switch_video e41f
sync e627
sync_disabled e628
sync_problem e629
system_update e62a
system_update_alt e8d7
tab e8d8
tab_unselected e8d9
tablet e32f
tablet_android e330
tablet_mac e331
tag_faces e420
tap_and_play e62b
terrain e564
text_fields e262
text_format e165
textsms e0d8
texture e421
theaters e8da
thumb_down e8db
thumb_up e8dc
thumbs_up_down e8dd
time_to_leave e62c
timelapse e422
timeline e922
timer e425
timer_10 e423
timer_3 e424
timer_off e426
title e264
toc e8de
today e8df
toll e8e0
tonality e427
touch_app e913
toys e332
track_changes e8e1
traffic e565
train e570
tram e571
transfer_within_a_station e572
transform e428
translate e8e2
trending_down e8e3
trending_flat e8e4
trending_up e8e5
tune e429
turned_in e8e6
turned_in_not e8e7
tv e333
unarchive e169
undo e166
unfold_less e5d6
unfold_more e5d7
update e923
usb e1e0
verified_user e8e8
vertical_align_bottom e258
vertical_align_center e259
vertical_align_top e25a
vibration e62d
video_call e070
video_label e071
video_library e04a
videocam e04b
videocam_off e04c
videogame_asset e338
view_agenda e8e9
view_array e8ea
view_carousel e8eb
view_column e8ec
view_comfy e42a
view_compact e42b
view_day e8ed
view_headline e8ee
view_list e8ef
view_module e8f0
view_quilt e8f1
view_stream e8f2
view_week e8f3
vignette e435
visibility e8f4
visibility_off e8f5
voice_chat e62e
voicemail e0d9
volume_down e04d
volume_mute e04e
volume_off e04f
volume_up e050
vpn_key e0da
vpn_lock e62f
wallpaper e1bc
warning e002
watch e334
watch_later e924
wb_auto e42c
wb_cloudy e42d
wb_incandescent e42e
wb_iridescent e436
wb_sunny e430
wc e63d
web e051
web_asset e069
weekend e16b
whatshot e80e
widgets e1bd
wifi e63e
wifi_lock e1e1
wifi_tethering e1e2
work e8f9
wrap_text e25b
youtube_searched_for e8fa
zoom_in e8ff
zoom_out e900
zoom_out_map e56b
================================================
FILE: static/iconfont/material-icons.css
================================================
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(MaterialIcons-Regular.eot); /* For IE6-8 */
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url(MaterialIcons-Regular.woff2) format('woff2'),
url(MaterialIcons-Regular.woff) format('woff'),
url(MaterialIcons-Regular.ttf) format('truetype');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px; /* Preferred icon size */
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
/* Support for IE. */
font-feature-settings: 'liga';
}
gitextract_afk0_ghf/
├── .babelrc
├── .editorconfig
├── .gitignore
├── .postcssrc.js
├── README.md
├── build/
│ ├── build.js
│ ├── check-versions.js
│ ├── dev-client.js
│ ├── dev-server.js
│ ├── utils.js
│ ├── vue-loader.conf.js
│ ├── webpack.base.conf.js
│ ├── webpack.dev.conf.js
│ └── webpack.prod.conf.js
├── config/
│ ├── dev.env.js
│ ├── index.js
│ └── prod.env.js
├── index.html
├── package.json
├── screen/
│ └── 1.txt
├── src/
│ ├── App.vue
│ ├── assets/
│ │ ├── css/
│ │ │ ├── base.scss
│ │ │ ├── common.css
│ │ │ └── reset.css
│ │ ├── js/
│ │ │ ├── api.js
│ │ │ ├── filters.js
│ │ │ └── setStyle.js
│ │ └── login/
│ │ └── login.json
│ ├── components/
│ │ ├── AudiioNav.vue
│ │ ├── Comment.vue
│ │ ├── Cover.vue
│ │ ├── Header.vue
│ │ ├── Hello.vue
│ │ ├── ListTitle.vue
│ │ ├── PlayList.vue
│ │ ├── Player.vue
│ │ └── Root.vue
│ ├── main.js
│ ├── page/
│ │ ├── Index/
│ │ │ ├── Index.vue
│ │ │ ├── Radio.vue
│ │ │ ├── Recommend.vue
│ │ │ ├── SongList.vue
│ │ │ └── Top.vue
│ │ ├── PlayListDetail.vue
│ │ ├── home/
│ │ │ └── Home.vue
│ │ └── search/
│ │ ├── Album.vue
│ │ ├── Artist.vue
│ │ ├── ListItem.vue
│ │ ├── Search.vue
│ │ ├── Song.vue
│ │ └── list.vue
│ ├── router/
│ │ └── index.js
│ └── store/
│ ├── actions.js
│ ├── getters.js
│ ├── index.js
│ ├── mutation-type.js
│ └── mutations.js
└── static/
├── .gitkeep
└── iconfont/
├── MaterialIcons-Regular.ijmap
├── README.md
├── codepoints
└── material-icons.css
SYMBOL INDEX (30 symbols across 9 files)
FILE: build/check-versions.js
function exec (line 5) | function exec (cmd) {
FILE: build/utils.js
function generateLoaders (line 24) | function generateLoaders (loader, loaderOptions) {
FILE: build/webpack.base.conf.js
function resolve (line 6) | function resolve (dir) {
FILE: src/assets/js/filters.js
method playCount (line 7) | playCount(value) {
FILE: src/assets/js/setStyle.js
function setStyle (line 4) | function setStyle(obj,json) {
FILE: src/store/actions.js
method curPlaylistDetail (line 7) | curPlaylistDetail({commit}, obj) {
method curPlayMusic (line 10) | curPlayMusic({commit}, arr) {
method switchPlaying (line 13) | switchPlaying({commit}, status) {
method showCurLyric (line 16) | showCurLyric({commit}, lyric) {
method showPlayList (line 19) | showPlayList({commit}, status) {
method playList (line 22) | playList({commit}, arr) {
method commentId (line 25) | commentId({commit}, id) {
method curMusicIndex (line 28) | curMusicIndex({commit}, index) {
FILE: src/store/getters.js
method curId (line 6) | curId(state) {
FILE: src/store/mutation-type.js
constant CUR_PLAYLIST_INFO (line 2) | const CUR_PLAYLIST_INFO= 'CUR_PLAYLIST_INFO'
constant CUR_PLAY_MUSIC (line 4) | const CUR_PLAY_MUSIC= 'CUR_PLAY_MUSIC'
constant SWITCH_PLAYING (line 6) | const SWITCH_PLAYING = 'SWITCH_PLAYING'
constant SHOW_CUR_LYRIC (line 8) | const SHOW_CUR_LYRIC = 'SHOW_CUR_LYRIC'
constant SHOW_PLAY_LIST (line 10) | const SHOW_PLAY_LIST = 'SHOW_PLAY_LIST'
constant PLAY_LIST (line 12) | const PLAY_LIST = 'PLAY_LIST'
constant COMMENT_ID (line 14) | const COMMENT_ID = 'COMMENT_ID'
constant CUR_MUSIC_INDEX (line 16) | const CUR_MUSIC_INDEX = 'CUR_MUSIC_INDEX'
FILE: src/store/mutations.js
method [types.CUR_PLAYLIST_INFO] (line 7) | [types.CUR_PLAYLIST_INFO](state, obj) {
method [types.CUR_PLAY_MUSIC] (line 10) | [types.CUR_PLAY_MUSIC](state, arr) {
method [types.SWITCH_PLAYING] (line 18) | [types.SWITCH_PLAYING](state, status) {
method [types.SHOW_CUR_LYRIC] (line 21) | [types.SHOW_CUR_LYRIC](state, lyric) {
method [types.SHOW_PLAY_LIST] (line 24) | [types.SHOW_PLAY_LIST](state, status) {
method [types.PLAY_LIST] (line 28) | [types.PLAY_LIST](state, arr) {
method [types.COMMENT_ID] (line 31) | [types.COMMENT_ID](state, id) {
method [types.CUR_MUSIC_INDEX] (line 34) | [types.CUR_MUSIC_INDEX](state, index) {
Condensed preview — 62 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (173K chars).
[
{
"path": ".babelrc",
"chars": 234,
"preview": "{\n \"presets\": [\n [\"env\", { \"modules\": false }],\n \"stage-2\"\n ],\n \"plugins\": [\"transform-runtime\"],\n \"comments\":"
},
{
"path": ".editorconfig",
"chars": 147,
"preview": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_"
},
{
"path": ".gitignore",
"chars": 119,
"preview": ".DS_Store\nnode_modules/\ndist/\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.idea/\n.history\n.vscode\npackage-lock.json\n"
},
{
"path": ".postcssrc.js",
"chars": 196,
"preview": "// https://github.com/michael-ciniawsky/postcss-load-config\n\nmodule.exports = {\n \"plugins\": {\n // to edit target bro"
},
{
"path": "README.md",
"chars": 2840,
"preview": "# vue版网易云音乐\n\n> api:ap使用的是一个开源的nodejs封装的网易云音乐api,[地址](https://binaryify.github.io/NeteaseCloudMusicApi/#/?id=%e5%ae%89%e8"
},
{
"path": "build/build.js",
"chars": 953,
"preview": "require('./check-versions')()\n\nprocess.env.NODE_ENV = 'production'\n\nvar ora = require('ora')\nvar rm = require('rimraf')\n"
},
{
"path": "build/check-versions.js",
"chars": 1257,
"preview": "var chalk = require('chalk')\nvar semver = require('semver')\nvar packageConfig = require('../package.json')\nvar shell = r"
},
{
"path": "build/dev-client.js",
"chars": 245,
"preview": "/* eslint-disable */\nrequire('eventsource-polyfill')\nvar hotClient = require('webpack-hot-middleware/client?noInfo=true&"
},
{
"path": "build/dev-server.js",
"chars": 2444,
"preview": "require('./check-versions')()\n\nvar config = require('../config')\nif (!process.env.NODE_ENV) {\n process.env.NODE_ENV = J"
},
{
"path": "build/utils.js",
"chars": 1949,
"preview": "var path = require('path')\nvar config = require('../config')\nvar ExtractTextPlugin = require('extract-text-webpack-plugi"
},
{
"path": "build/vue-loader.conf.js",
"chars": 307,
"preview": "var utils = require('./utils')\nvar config = require('../config')\nvar isProduction = process.env.NODE_ENV === 'production"
},
{
"path": "build/webpack.base.conf.js",
"chars": 1582,
"preview": "var path = require('path')\nvar utils = require('./utils')\nvar config = require('../config')\nvar vueLoaderConfig = requir"
},
{
"path": "build/webpack.dev.conf.js",
"chars": 1225,
"preview": "var utils = require('./utils')\nvar webpack = require('webpack')\nvar config = require('../config')\nvar merge = require('w"
},
{
"path": "build/webpack.prod.conf.js",
"chars": 3865,
"preview": "var path = require('path')\nvar utils = require('./utils')\nvar webpack = require('webpack')\nvar config = require('../conf"
},
{
"path": "config/dev.env.js",
"chars": 139,
"preview": "var merge = require('webpack-merge')\nvar prodEnv = require('./prod.env')\n\nmodule.exports = merge(prodEnv, {\n NODE_ENV: "
},
{
"path": "config/index.js",
"chars": 1443,
"preview": "// see http://vuejs-templates.github.io/webpack for documentation.\nvar path = require('path')\n\nmodule.exports = {\n buil"
},
{
"path": "config/prod.env.js",
"chars": 48,
"preview": "module.exports = {\n NODE_ENV: '\"production\"'\n}\n"
},
{
"path": "index.html",
"chars": 1240,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initia"
},
{
"path": "package.json",
"chars": 2040,
"preview": "{\n \"name\": \"vue-music\",\n \"version\": \"1.0.0\",\n \"description\": \"A Vue.js project\",\n \"author\": \"tgxh2020 <tgxh2020@gmai"
},
{
"path": "screen/1.txt",
"chars": 520,
"preview": "http://wx4.sinaimg.cn/large/7b9a6229ly1fhwlyxi2mrj20ad0icn43.jpg\nhttp://wx4.sinaimg.cn/large/7b9a6229ly1fhwlz0lmr2j20ac0"
},
{
"path": "src/App.vue",
"chars": 644,
"preview": "<template>\n <div id=\"app\">\n <router-view></router-view>\n <player></player>\n <audio-nav></audio-nav>\n <play-"
},
{
"path": "src/assets/css/base.scss",
"chars": 2984,
"preview": "@mixin ell() {\n overflow: hidden;\n -ms-text-overflow: ellipsis;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n$"
},
{
"path": "src/assets/css/common.css",
"chars": 161,
"preview": ".icon {\n width: 1em; height: 1em;\n vertical-align: -0.15em;\n fill: currentColor;\n overflow: hidden;\n}\n\nul:not(.md-li"
},
{
"path": "src/assets/css/reset.css",
"chars": 1481,
"preview": "/* http://meyerweb.com/eric/tools/css/reset/\n v2.0 | 20110126\n License: none (public domain)\n*/\n\nhtml, body, div, sp"
},
{
"path": "src/assets/js/api.js",
"chars": 83,
"preview": "import axios from 'axios'\n\nconst url = 'http://localhost:3000'\n\nexport default url\n"
},
{
"path": "src/assets/js/filters.js",
"chars": 254,
"preview": "/**\n * Created by 12 on 2017/6/11.\n */\n\nconst filters = {\n //歌单播放数\n playCount(value) {\n if (value < 10000) {\n "
},
{
"path": "src/assets/js/setStyle.js",
"chars": 126,
"preview": "/**\n * Created by 12 on 2017/6/11.\n */\nfunction setStyle(obj,json) {\n for (var i in json) {\n obj.style[i] = json[i]\n"
},
{
"path": "src/assets/login/login.json",
"chars": 371,
"preview": "{\n \"loginType\": 1,\n \"code\": 200,\n \"account\": {\n \"id\": 46909653,\n \"userName\": \"0_wo460520803@163.com\",\n \"type"
},
{
"path": "src/components/AudiioNav.vue",
"chars": 3119,
"preview": "<template>\n <div class=\"audio-nav\" v-if=\"JSON.stringify(curPlayMusic) !== '{}'\">\n <div class=\"music-wrap\" @click=\"sh"
},
{
"path": "src/components/Comment.vue",
"chars": 7080,
"preview": "<template>\n <div\n class=\"comment\"\n :class=\"{show: showComment}\"\n v-if=\"JSON.stringify(curPlayMusic) !== '{}'\">"
},
{
"path": "src/components/Cover.vue",
"chars": 900,
"preview": "<template>\n <div class=\"bg-cover\" :class=\"{hide:!showCover}\" @touchmove.prevent @click=\"hideCover\">\n </div>\n</template"
},
{
"path": "src/components/Header.vue",
"chars": 428,
"preview": "<template>\n <div>\n\n </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n import axios from 'axios'\n import {ma"
},
{
"path": "src/components/Hello.vue",
"chars": 1747,
"preview": "<template>\n <div class=\"hello\">\n <h1>{{ msg }}</h1>\n <h2>Essential Links</h2>\n <ul>\n <li><a href=\"https:/"
},
{
"path": "src/components/ListTitle.vue",
"chars": 507,
"preview": "<template>\n <h3 class=\"list-title\">{{title}} <i class=\"iconfont icon-jiantou\"></i></h3>\n</template>\n\n<script type=\"text"
},
{
"path": "src/components/PlayList.vue",
"chars": 4118,
"preview": "<template>\n <div class=\"play-list\" :class=\"{showlist:showPlayList}\">\n <div class=\"play-list-title\">\n 播放列表({{pla"
},
{
"path": "src/components/Player.vue",
"chars": 16760,
"preview": "<template>\n <div class=\"player animated fadeIn\"\n :class=\"{show: showPlayer}\"\n @touchmove=\"moveProgress\">\n "
},
{
"path": "src/components/Root.vue",
"chars": 3355,
"preview": "<template>\n <div class=\"root\">\n <md-toolbar class=\"md-dense root\">\n <md-button class=\"md-icon-button\">\n "
},
{
"path": "src/main.js",
"chars": 771,
"preview": "// The Vue build version to load with the `import` command\n// (runtime-only or standalone) has been set in webpack.base."
},
{
"path": "src/page/Index/Index.vue",
"chars": 3606,
"preview": "<template>\n <div class=\"index\">\n <md-button-toggle md-single class=\"index\">\n <md-button class=\"md-toggle\" @cli"
},
{
"path": "src/page/Index/Radio.vue",
"chars": 446,
"preview": "<template>\n <div>\n radio\n </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n import axios from 'axios'\n "
},
{
"path": "src/page/Index/Recommend.vue",
"chars": 5328,
"preview": "<template>\n <div>\n <swiper :options=\"swiperOption\">\n <swiper-slide v-for=\"(item,index) in banners\" :key=\"index\""
},
{
"path": "src/page/Index/SongList.vue",
"chars": 469,
"preview": "<template>\n <div>\n songlist\n </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n import axios from 'axios'\n im"
},
{
"path": "src/page/Index/Top.vue",
"chars": 444,
"preview": "<template>\n <div>\n top\n </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n import axios from 'axios'\n i"
},
{
"path": "src/page/PlayListDetail.vue",
"chars": 9098,
"preview": "<template>\n <div id=\"playlist_detail\" ref=\"playlistdetail\"><!-- v-if=\"JSON.stringify(playlist) !== '{}'\"-->\n <div>\n "
},
{
"path": "src/page/home/Home.vue",
"chars": 445,
"preview": "<template>\n <div>\n home\n </div>\n</template>\n\n<script type=\"text/ecmascript-6\">\n import axios from 'axios'\n "
},
{
"path": "src/page/search/Album.vue",
"chars": 1073,
"preview": "<template>\n <ul id=\"search-albums\">\n <list-item :item=\"album\" v-for=\"(album,index) in albums\" :key=\"index\"></list-it"
},
{
"path": "src/page/search/Artist.vue",
"chars": 1615,
"preview": "<template>\n <ul id=\"search-artist\"\n @touchstart=\"touchStart\"\n @touchmove=\"touchMove\">\n <li class=\"search-a"
},
{
"path": "src/page/search/ListItem.vue",
"chars": 1537,
"preview": "<template>\n <li class=\"search-album-item\">\n <div class=\"s-album-avatar\">\n <img :src=\"item.picUrl\" alt=\"\">\n <"
},
{
"path": "src/page/search/Search.vue",
"chars": 7661,
"preview": "<template>\n <div id=\"search\">\n <md-toolbar class=\"md-dense search\">\n <md-button class=\"md-icon-button\">\n "
},
{
"path": "src/page/search/Song.vue",
"chars": 2768,
"preview": "<template>\n <ul id=\"search-song\">\n <li class=\"search-song-item\" v-for=\"(item,index) in songs\">\n <div clas"
},
{
"path": "src/page/search/list.vue",
"chars": 1660,
"preview": "<template>\n <ul id=\"search-list\">\n <li class=\"search-album-item\"\n v-for=\"(item,index) in list\"\n @click"
},
{
"path": "src/router/index.js",
"chars": 1945,
"preview": "import Vue from 'vue'\nimport Router from 'vue-router'\nimport Root from '@/components/Root'\n\nimport Index from '../page/i"
},
{
"path": "src/store/actions.js",
"chars": 711,
"preview": "/**\n * Created by 12 on 2017/6/8.\n */\nimport * as types from './mutation-type'\n\nexport default {\n curPlaylistDetail({co"
},
{
"path": "src/store/getters.js",
"chars": 155,
"preview": "/**\n * Created by 12 on 2017/6/14.\n */\n\nexport default {\n curId(state) {\n return state.curPlayMusic.detail ? state.c"
},
{
"path": "src/store/index.js",
"chars": 478,
"preview": "import Vue from 'vue'\nimport Vuex from 'vuex'\nimport mutations from './mutations'\nimport actions from './actions'\nimport"
},
{
"path": "src/store/mutation-type.js",
"chars": 480,
"preview": "//歌单页当前歌单上面图片标题信息\nexport const CUR_PLAYLIST_INFO= 'CUR_PLAYLIST_INFO'\n//当前播放的歌曲信息:歌名,歌手,专辑,歌词,url,封面\nexport const CUR_PL"
},
{
"path": "src/store/mutations.js",
"chars": 837,
"preview": "/**\n * Created by 12 on 2017/6/8.\n */\nimport * as types from './mutation-type'\n\nexport default {\n [types.CUR_PLAYLIST_I"
},
{
"path": "static/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "static/iconfont/MaterialIcons-Regular.ijmap",
"chars": 28416,
"preview": "{\"icons\":{\"e84d\":{\"name\":\"3d Rotation\"},\"eb3b\":{\"name\":\"Ac Unit\"},\"e190\":{\"name\":\"Access Alarm\"},\"e191\":{\"name\":\"Access "
},
{
"path": "static/iconfont/README.md",
"chars": 316,
"preview": "The recommended way to use the Material Icons font is by linking to the web font hosted on Google Fonts:\n\n```html\n<link "
},
{
"path": "static/iconfont/codepoints",
"chars": 16289,
"preview": "3d_rotation e84d\nac_unit eb3b\naccess_alarm e190\naccess_alarms e191\naccess_time e192\naccessibility e84e\naccessible e914\na"
},
{
"path": "static/iconfont/material-icons.css",
"chars": 970,
"preview": "@font-face {\n font-family: 'Material Icons';\n font-style: normal;\n font-weight: 400;\n src: url(MaterialIcons-Regular"
}
]
About this extraction
This page contains the full source code of the tgxhx/vue-music GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 62 files (150.8 KB), approximately 47.4k tokens, and a symbol index with 30 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.