@ -0,0 +1,18 @@ | |||
{ | |||
"presets": [ | |||
["env", { | |||
"modules": false, | |||
"targets": { | |||
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"] | |||
} | |||
}], | |||
"stage-2" | |||
], | |||
"plugins": ["transform-vue-jsx", "transform-runtime"], | |||
"env": { | |||
"test": { | |||
"presets": ["env", "stage-2"], | |||
"plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"] | |||
} | |||
} | |||
} |
@ -0,0 +1,9 @@ | |||
root = true | |||
[*] | |||
charset = utf-8 | |||
indent_style = space | |||
indent_size = 2 | |||
end_of_line = lf | |||
insert_final_newline = true | |||
trim_trailing_whitespace = true |
@ -0,0 +1,5 @@ | |||
/build/ | |||
/config/ | |||
/dist/ | |||
/*.js | |||
/test/unit/coverage/ |
@ -0,0 +1,29 @@ | |||
// https://eslint.org/docs/user-guide/configuring | |||
module.exports = { | |||
root: true, | |||
parserOptions: { | |||
parser: 'babel-eslint' | |||
}, | |||
env: { | |||
browser: true, | |||
}, | |||
extends: [ | |||
// https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention | |||
// consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. | |||
'plugin:vue/essential', | |||
// https://github.com/standard/standard/blob/master/docs/RULES-en.md | |||
'standard' | |||
], | |||
// required to lint *.vue files | |||
plugins: [ | |||
'vue' | |||
], | |||
// add your custom rules here | |||
rules: { | |||
// allow async-await | |||
'generator-star-spacing': 'off', | |||
// allow debugger during development | |||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' | |||
} | |||
} |
@ -0,0 +1,10 @@ | |||
// https://github.com/michael-ciniawsky/postcss-load-config | |||
module.exports = { | |||
"plugins": { | |||
"postcss-import": {}, | |||
"postcss-url": {}, | |||
// to edit target browsers: use "browserslist" field in package.json | |||
"autoprefixer": {} | |||
} | |||
} |
@ -0,0 +1,30 @@ | |||
# music | |||
> A Vue.js project | |||
## Build Setup | |||
``` bash | |||
# install dependencies | |||
npm install | |||
# serve with hot reload at localhost:8080 | |||
npm run dev | |||
# build for production with minification | |||
npm run build | |||
# build for production and view the bundle analyzer report | |||
npm run build --report | |||
# run unit tests | |||
npm run unit | |||
# run e2e tests | |||
npm run e2e | |||
# run all tests | |||
npm test | |||
``` | |||
For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). |
@ -0,0 +1,41 @@ | |||
'use strict' | |||
require('./check-versions')() | |||
process.env.NODE_ENV = 'production' | |||
const ora = require('ora') | |||
const rm = require('rimraf') | |||
const path = require('path') | |||
const chalk = require('chalk') | |||
const webpack = require('webpack') | |||
const config = require('../config') | |||
const webpackConfig = require('./webpack.prod.conf') | |||
const spinner = ora('building for production...') | |||
spinner.start() | |||
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { | |||
if (err) throw err | |||
webpack(webpackConfig, (err, stats) => { | |||
spinner.stop() | |||
if (err) throw err | |||
process.stdout.write(stats.toString({ | |||
colors: true, | |||
modules: false, | |||
children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. | |||
chunks: false, | |||
chunkModules: false | |||
}) + '\n\n') | |||
if (stats.hasErrors()) { | |||
console.log(chalk.red(' Build failed with errors.\n')) | |||
process.exit(1) | |||
} | |||
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' | |||
)) | |||
}) | |||
}) |
@ -0,0 +1,54 @@ | |||
'use strict' | |||
const chalk = require('chalk') | |||
const semver = require('semver') | |||
const packageConfig = require('../package.json') | |||
const shell = require('shelljs') | |||
function exec (cmd) { | |||
return require('child_process').execSync(cmd).toString().trim() | |||
} | |||
const 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 () { | |||
const warnings = [] | |||
for (let i = 0; i < versionRequirements.length; i++) { | |||
const 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 (let i = 0; i < warnings.length; i++) { | |||
const warning = warnings[i] | |||
console.log(' ' + warning) | |||
} | |||
console.log() | |||
process.exit(1) | |||
} | |||
} |
@ -0,0 +1,101 @@ | |||
'use strict' | |||
const path = require('path') | |||
const config = require('../config') | |||
const ExtractTextPlugin = require('extract-text-webpack-plugin') | |||
const packageConfig = require('../package.json') | |||
exports.assetsPath = function (_path) { | |||
const assetsSubDirectory = process.env.NODE_ENV === 'production' | |||
? config.build.assetsSubDirectory | |||
: config.dev.assetsSubDirectory | |||
return path.posix.join(assetsSubDirectory, _path) | |||
} | |||
exports.cssLoaders = function (options) { | |||
options = options || {} | |||
const cssLoader = { | |||
loader: 'css-loader', | |||
options: { | |||
sourceMap: options.sourceMap | |||
} | |||
} | |||
const postcssLoader = { | |||
loader: 'postcss-loader', | |||
options: { | |||
sourceMap: options.sourceMap | |||
} | |||
} | |||
// generate loader string to be used with extract text plugin | |||
function generateLoaders (loader, loaderOptions) { | |||
const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [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) { | |||
const output = [] | |||
const loaders = exports.cssLoaders(options) | |||
for (const extension in loaders) { | |||
const loader = loaders[extension] | |||
output.push({ | |||
test: new RegExp('\\.' + extension + '$'), | |||
use: loader | |||
}) | |||
} | |||
return output | |||
} | |||
exports.createNotifierCallback = () => { | |||
const notifier = require('node-notifier') | |||
return (severity, errors) => { | |||
if (severity !== 'error') return | |||
const error = errors[0] | |||
const filename = error.file && error.file.split('!').pop() | |||
notifier.notify({ | |||
title: packageConfig.name, | |||
message: severity + ': ' + error.name, | |||
subtitle: filename || '', | |||
icon: path.join(__dirname, 'logo.png') | |||
}) | |||
} | |||
} |
@ -0,0 +1,22 @@ | |||
'use strict' | |||
const utils = require('./utils') | |||
const config = require('../config') | |||
const isProduction = process.env.NODE_ENV === 'production' | |||
const sourceMapEnabled = isProduction | |||
? config.build.productionSourceMap | |||
: config.dev.cssSourceMap | |||
module.exports = { | |||
loaders: utils.cssLoaders({ | |||
sourceMap: sourceMapEnabled, | |||
extract: isProduction | |||
}), | |||
cssSourceMap: sourceMapEnabled, | |||
cacheBusting: config.dev.cacheBusting, | |||
transformToRequire: { | |||
video: ['src', 'poster'], | |||
source: 'src', | |||
img: 'src', | |||
image: 'xlink:href' | |||
} | |||
} |
@ -0,0 +1,92 @@ | |||
'use strict' | |||
const path = require('path') | |||
const utils = require('./utils') | |||
const config = require('../config') | |||
const vueLoaderConfig = require('./vue-loader.conf') | |||
function resolve(dir) { | |||
return path.join(__dirname, '..', dir) | |||
} | |||
const createLintingRule = () => ({ | |||
test: /\.(js|vue)$/, | |||
loader: 'eslint-loader', | |||
enforce: 'pre', | |||
include: [resolve('src'), resolve('test')], | |||
options: { | |||
formatter: require('eslint-friendly-formatter'), | |||
emitWarning: !config.dev.showEslintErrorsInOverlay | |||
} | |||
}) | |||
module.exports = { | |||
context: path.resolve(__dirname, '../'), | |||
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'), | |||
} | |||
}, | |||
module: { | |||
rules: [ | |||
// ...(config.dev.useEslint ? [createLintingRule()] : []), | |||
{ | |||
test: /\.vue$/, | |||
loader: 'vue-loader', | |||
options: vueLoaderConfig | |||
}, | |||
{ | |||
test: /\.js$/, | |||
loader: 'babel-loader', | |||
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] | |||
}, | |||
{ | |||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, | |||
loader: 'url-loader', | |||
options: { | |||
limit: 10000, | |||
name: utils.assetsPath('img/[name].[hash:7].[ext]') | |||
} | |||
}, | |||
{ | |||
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, | |||
loader: 'url-loader', | |||
options: { | |||
limit: 10000, | |||
name: utils.assetsPath('media/[name].[hash:7].[ext]') | |||
} | |||
}, | |||
{ | |||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, | |||
loader: 'url-loader', | |||
options: { | |||
limit: 10000, | |||
name: utils.assetsPath('fonts/[name].[hash:7].[ext]') | |||
} | |||
} | |||
] | |||
}, | |||
node: { | |||
// prevent webpack from injecting useless setImmediate polyfill because Vue | |||
// source contains it (although only uses it if it's native). | |||
setImmediate: false, | |||
// prevent webpack from injecting mocks to Node native modules | |||
// that does not make sense for the client | |||
dgram: 'empty', | |||
fs: 'empty', | |||
net: 'empty', | |||
tls: 'empty', | |||
child_process: 'empty' | |||
} | |||
} |
@ -0,0 +1,96 @@ | |||
'use strict' | |||
const utils = require('./utils') | |||
const webpack = require('webpack') | |||
const config = require('../config') | |||
const merge = require('webpack-merge') | |||
const path = require('path') | |||
const baseWebpackConfig = require('./webpack.base.conf') | |||
const CopyWebpackPlugin = require('copy-webpack-plugin') | |||
const HtmlWebpackPlugin = require('html-webpack-plugin') | |||
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') | |||
const portfinder = require('portfinder') | |||
const HOST = process.env.HOST | |||
const PORT = process.env.PORT && Number(process.env.PORT) | |||
const devWebpackConfig = merge(baseWebpackConfig, { | |||
module: { | |||
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) | |||
}, | |||
// cheap-module-eval-source-map is faster for development | |||
devtool: config.dev.devtool, | |||
// these devServer options should be customized in /config/index.js | |||
devServer: { | |||
clientLogLevel: 'warning', | |||
historyApiFallback: { | |||
rewrites: [ | |||
{ from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, | |||
], | |||
}, | |||
hot: true, | |||
contentBase: false, // since we use CopyWebpackPlugin. | |||
compress: true, | |||
host: HOST || config.dev.host, | |||
port: PORT || config.dev.port, | |||
open: config.dev.autoOpenBrowser, | |||
overlay: config.dev.errorOverlay | |||
? { warnings: false, errors: true } | |||
: false, | |||
publicPath: config.dev.assetsPublicPath, | |||
proxy: config.dev.proxyTable, | |||
quiet: true, // necessary for FriendlyErrorsPlugin | |||
disableHostCheck: true, | |||
watchOptions: { | |||
poll: config.dev.poll, | |||
} | |||
}, | |||
plugins: [ | |||
new webpack.DefinePlugin({ | |||
'process.env': require('../config/dev.env') | |||
}), | |||
new webpack.HotModuleReplacementPlugin(), | |||
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. | |||
new webpack.NoEmitOnErrorsPlugin(), | |||
// https://github.com/ampedandwired/html-webpack-plugin | |||
new HtmlWebpackPlugin({ | |||
filename: 'index.html', | |||
template: 'index.html', | |||
inject: true | |||
}), | |||
// copy custom static assets | |||
new CopyWebpackPlugin([ | |||
{ | |||
from: path.resolve(__dirname, '../static'), | |||
to: config.dev.assetsSubDirectory, | |||
ignore: ['.*'] | |||
} | |||
]) | |||
] | |||
}) | |||
module.exports = new Promise((resolve, reject) => { | |||
portfinder.basePort = process.env.PORT || config.dev.port | |||
portfinder.getPort((err, port) => { | |||
if (err) { | |||
reject(err) | |||
} else { | |||
// publish the new Port, necessary for e2e tests | |||
process.env.PORT = port | |||
// add port to devServer config | |||
devWebpackConfig.devServer.port = port | |||
// Add FriendlyErrorsPlugin | |||
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ | |||
compilationSuccessInfo: { | |||
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], | |||
}, | |||
onErrors: config.dev.notifyOnErrors | |||
? utils.createNotifierCallback() | |||
: undefined | |||
})) | |||
resolve(devWebpackConfig) | |||
} | |||
}) | |||
}) |
@ -0,0 +1,149 @@ | |||
'use strict' | |||
const path = require('path') | |||
const utils = require('./utils') | |||
const webpack = require('webpack') | |||
const config = require('../config') | |||
const merge = require('webpack-merge') | |||
const baseWebpackConfig = require('./webpack.base.conf') | |||
const CopyWebpackPlugin = require('copy-webpack-plugin') | |||
const HtmlWebpackPlugin = require('html-webpack-plugin') | |||
const ExtractTextPlugin = require('extract-text-webpack-plugin') | |||
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') | |||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin') | |||
const env = process.env.NODE_ENV === 'testing' | |||
? require('../config/test.env') | |||
: require('../config/prod.env') | |||
const webpackConfig = merge(baseWebpackConfig, { | |||
module: { | |||
rules: utils.styleLoaders({ | |||
sourceMap: config.build.productionSourceMap, | |||
extract: true, | |||
usePostCSS: true | |||
}) | |||
}, | |||
devtool: config.build.productionSourceMap ? config.build.devtool : 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 UglifyJsPlugin({ | |||
uglifyOptions: { | |||
compress: { | |||
warnings: false | |||
} | |||
}, | |||
sourceMap: config.build.productionSourceMap, | |||
parallel: true | |||
}), | |||
// extract css into its own file | |||
new ExtractTextPlugin({ | |||
filename: utils.assetsPath('css/[name].[contenthash].css'), | |||
// Setting the following option to `false` will not extract CSS from codesplit chunks. | |||
// Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. | |||
// It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, | |||
// increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 | |||
allChunks: true, | |||
}), | |||
// Compress extracted CSS. We are using this plugin so that possible | |||
// duplicated CSS from different components can be deduped. | |||
new OptimizeCSSPlugin({ | |||
cssProcessorOptions: config.build.productionSourceMap | |||
? { safe: true, map: { inline: false } } | |||
: { 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: process.env.NODE_ENV === 'testing' | |||
? 'index.html' | |||
: 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' | |||
}), | |||
// keep module.id stable when vendor modules does not change | |||
new webpack.HashedModuleIdsPlugin(), | |||
// enable scope hoisting | |||
new webpack.optimize.ModuleConcatenationPlugin(), | |||
// split vendor js into its own file | |||
new webpack.optimize.CommonsChunkPlugin({ | |||
name: 'vendor', | |||
minChunks (module) { | |||
// 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', | |||
minChunks: Infinity | |||
}), | |||
// This instance extracts shared chunks from code splitted chunks and bundles them | |||
// in a separate chunk, similar to the vendor chunk | |||
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk | |||
new webpack.optimize.CommonsChunkPlugin({ | |||
name: 'app', | |||
async: 'vendor-async', | |||
children: true, | |||
minChunks: 3 | |||
}), | |||
// copy custom static assets | |||
new CopyWebpackPlugin([ | |||
{ | |||
from: path.resolve(__dirname, '../static'), | |||
to: config.build.assetsSubDirectory, | |||
ignore: ['.*'] | |||
} | |||
]) | |||
] | |||
}) | |||
if (config.build.productionGzip) { | |||
const 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) { | |||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin | |||
webpackConfig.plugins.push(new BundleAnalyzerPlugin()) | |||
} | |||
module.exports = webpackConfig |
@ -0,0 +1,7 @@ | |||
'use strict' | |||
const merge = require('webpack-merge') | |||
const prodEnv = require('./prod.env') | |||
module.exports = merge(prodEnv, { | |||
NODE_ENV: '"development"' | |||
}) |
@ -0,0 +1,76 @@ | |||
'use strict' | |||
// Template version: 1.3.1 | |||
// see http://vuejs-templates.github.io/webpack for documentation. | |||
const path = require('path') | |||
module.exports = { | |||
dev: { | |||
// Paths | |||
assetsSubDirectory: 'static', | |||
assetsPublicPath: '/', | |||
proxyTable: {}, | |||
// Various Dev Server settings | |||
host: 'localhost', // can be overwritten by process.env.HOST | |||
port: 8081, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined | |||
autoOpenBrowser: false, | |||
errorOverlay: true, | |||
notifyOnErrors: true, | |||
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- | |||
// Use Eslint Loader? | |||
// If true, your code will be linted during bundling and | |||
// linting errors and warnings will be shown in the console. | |||
useEslint: false, | |||
// If true, eslint errors and warnings will also be shown in the error overlay | |||
// in the browser. | |||
showEslintErrorsInOverlay: false, | |||
/** | |||
* Source Maps | |||
*/ | |||
// https://webpack.js.org/configuration/devtool/#development | |||
devtool: 'cheap-module-eval-source-map', | |||
// If you have problems debugging vue-files in devtools, | |||
// set this to false - it *may* help | |||
// https://vue-loader.vuejs.org/en/options.html#cachebusting | |||
cacheBusting: true, | |||
cssSourceMap: true | |||
}, | |||
build: { | |||
// Template for index.html | |||
index: path.resolve(__dirname, '../dist/index.html'), | |||
// Paths | |||
assetsRoot: path.resolve(__dirname, '../dist'), | |||
assetsSubDirectory: 'static', | |||
assetsPublicPath: '/', | |||
/** | |||
* Source Maps | |||
*/ | |||
productionSourceMap: true, | |||
// https://webpack.js.org/configuration/devtool/#production | |||
devtool: '#source-map', | |||
// 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 | |||
} | |||
} |
@ -0,0 +1,4 @@ | |||
'use strict' | |||
module.exports = { | |||
NODE_ENV: '"production"' | |||
} |
@ -0,0 +1,7 @@ | |||
'use strict' | |||
const merge = require('webpack-merge') | |||
const devEnv = require('./dev.env') | |||
module.exports = merge(devEnv, { | |||
NODE_ENV: '"testing"' | |||
}) |
@ -0,0 +1,11 @@ | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
<meta charset="utf-8"> | |||
<meta name="viewport" content="width=device-width,initial-scale=1.0"> | |||
<title>音乐</title> | |||
</head> | |||
<body> | |||
<div id="app"></div> | |||
</body> | |||
</html> |
@ -0,0 +1,91 @@ | |||
{ | |||
"name": "music", | |||
"version": "1.0.0", | |||
"private": true, | |||
"scripts": { | |||
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", | |||
"start": "npm run dev", | |||
"unit": "jest --config test/unit/jest.conf.js --coverage", | |||
"e2e": "node test/e2e/runner.js", | |||
"test": "npm run unit && npm run e2e", | |||
"lint": "eslint --ext .js,.vue src test/unit test/e2e/specs", | |||
"build": "node build/build.js" | |||
}, | |||
"dependencies": { | |||
"axios": "^0.18.0", | |||
"element-ui": "^2.4.11", | |||
"vue": "^2.6.11", | |||
"vue-router": "^3.0.7", | |||
"vuex": "^3.0.1" | |||
}, | |||
"devDependencies": { | |||
"autoprefixer": "^7.1.2", | |||
"babel-core": "^6.22.1", | |||
"babel-eslint": "^8.2.1", | |||
"babel-helper-vue-jsx-merge-props": "^2.0.3", | |||
"babel-jest": "^21.0.2", | |||
"babel-loader": "^7.1.1", | |||
"babel-plugin-dynamic-import-node": "^1.2.0", | |||
"babel-plugin-syntax-jsx": "^6.18.0", | |||
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", | |||
"babel-plugin-transform-runtime": "^6.22.0", | |||
"babel-plugin-transform-vue-jsx": "^3.5.0", | |||
"babel-preset-env": "^1.3.2", | |||
"babel-preset-stage-2": "^6.22.0", | |||
"babel-register": "^6.22.0", | |||
"chalk": "^2.0.1", | |||
"chromedriver": "^2.27.2", | |||
"copy-webpack-plugin": "^4.0.1", | |||
"cross-spawn": "^5.0.1", | |||
"css-loader": "^0.28.11", | |||
"eslint": "^4.15.0", | |||
"eslint-config-standard": "^10.2.1", | |||
"eslint-friendly-formatter": "^3.0.0", | |||
"eslint-loader": "^1.7.1", | |||
"eslint-plugin-import": "^2.7.0", | |||
"eslint-plugin-node": "^5.2.0", | |||
"eslint-plugin-promise": "^3.4.0", | |||
"eslint-plugin-standard": "^3.0.1", | |||
"eslint-plugin-vue": "^4.0.0", | |||
"extract-text-webpack-plugin": "^3.0.0", | |||
"file-loader": "^1.1.4", | |||
"friendly-errors-webpack-plugin": "^1.6.1", | |||
"html-webpack-plugin": "^2.30.1", | |||
"jest": "^22.0.4", | |||
"jest-serializer-vue": "^0.3.0", | |||
"nightwatch": "^0.9.12", | |||
"node-notifier": "^5.1.2", | |||
"node-sass": "^4.14.0", | |||
"optimize-css-assets-webpack-plugin": "^3.2.0", | |||
"ora": "^1.2.0", | |||
"portfinder": "^1.0.13", | |||
"postcss-import": "^11.0.0", | |||
"postcss-loader": "^2.0.8", | |||
"postcss-url": "^7.2.1", | |||
"rimraf": "^2.6.0", | |||
"sass-loader": "^7.3.1", | |||
"selenium-server": "^3.0.1", | |||
"semver": "^5.3.0", | |||
"shelljs": "^0.7.6", | |||
"uglifyjs-webpack-plugin": "^1.1.1", | |||
"url-loader": "^0.5.8", | |||
"vue-jest": "^1.0.2", | |||
"vue-loader": "^13.3.0", | |||
"vue-router": "^3.0.7", | |||
"vue-style-loader": "^3.0.1", | |||
"vue-template-compiler": "^2.5.2", | |||
"webpack": "^3.6.0", | |||
"webpack-bundle-analyzer": "^3.6.0", | |||
"webpack-dev-server": "^2.9.1", | |||
"webpack-merge": "^4.1.0" | |||
}, | |||
"engines": { | |||
"node": ">= 6.0.0", | |||
"npm": ">= 3.0.0" | |||
}, | |||
"browserslist": [ | |||
"> 1%", | |||
"last 2 versions", | |||
"not ie <= 8" | |||
] | |||
} |
@ -0,0 +1,36 @@ | |||
<template> | |||
<div id="app"> | |||
<the-header /> | |||
<router-view class="music-content"/> | |||
<song-audio/> | |||
<the-aside/> | |||
<play-bar/> | |||
<scroll-top/> | |||
<the-footer/> | |||
</div> | |||
</template> | |||
<script> | |||
import TheHeader from './components/TheHeader'; | |||
import ScrollTop from './components/ScrollTop'; | |||
import TheFooter from './components/TheFooter'; | |||
import SongAudio from './components/SongAudio'; | |||
import TheAside from './components/TheAside'; | |||
import PlayBar from './components/PlayBar'; | |||
export default { | |||
name: 'App', | |||
components: { | |||
TheHeader, | |||
ScrollTop, | |||
TheFooter, | |||
SongAudio, | |||
TheAside, | |||
PlayBar | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import './assets/css/app.scss'; | |||
</style> |
@ -0,0 +1,67 @@ | |||
import axios from 'axios'; | |||
import router from '../router'; | |||
axios.defaults.timeout = 5000; //超市时间是5秒 | |||
axios.defaults.withCredentials = true; //允许跨域 | |||
//Content-Type 响应头 | |||
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'; | |||
//基础url | |||
axios.defaults.baseURL = "http://localhost:8888"; | |||
//响应拦截器 | |||
axios.interceptors.response.use( | |||
response => { | |||
//如果reponse里面的status是200,说明访问到接口了,否则错误 | |||
if(response.status == 200){ | |||
return Promise.resolve(response); | |||
}else{ | |||
return Promise.reject(response); | |||
} | |||
}, | |||
error => { | |||
if(error.response.status){ | |||
switch(error.response.status){ | |||
case 401: //未登录 | |||
router.replace({ | |||
path:'/', | |||
query:{ | |||
redirect: router.currentRoute.fullPath | |||
} | |||
}); | |||
break; | |||
case 404: //没找到 | |||
break; | |||
} | |||
return Promise.reject(error.response); | |||
} | |||
} | |||
); | |||
/** | |||
* 封装get方法 | |||
*/ | |||
export function get(url,params={}){ | |||
return new Promise((resolve,reject) => { | |||
axios.get(url,{params:params}) | |||
.then(response =>{ | |||
resolve(response.data); | |||
}) | |||
.catch(err =>{ | |||
reject(err); | |||
}) | |||
}); | |||
} | |||
/** | |||
* 封装post方法 | |||
*/ | |||
export function post(url,data={}){ | |||
return new Promise((resolve,reject) => { | |||
axios.post(url,data) | |||
.then(response =>{ | |||
resolve(response.data); | |||
}) | |||
.catch(err =>{ | |||
reject(err); | |||
}) | |||
}); | |||
} |
@ -0,0 +1,74 @@ | |||
import Axios from "axios"; | |||
import {get,post} from "./http"; | |||
//============歌手相关================ | |||
//查询歌手 | |||
export const getAllSinger =() => get(`singer/allSinger`); | |||
//根据性别查询歌手 | |||
export const getSingerOfSex = (sex) => get(`singer/singerOfSex?sex=${sex}`); | |||
//============歌曲相关================ | |||
//根据歌手id查询歌曲 | |||
export const songOfSingerId =(id) => get(`song/singer/detail?singerId=${id}`); | |||
//根据歌曲id查询歌曲对象 | |||
export const songOfSongId =(id) => get(`song/detail?songId=${id}`); | |||
//根据歌手名字模糊查询歌曲 | |||
export const likeSongOfName =(keywords) => get(`song/likeSongOfName?songName=${keywords}`); | |||
//============歌单相关================ | |||
//查询歌单 | |||
export const getAllSongList =() => get(`songList/allSongList`); | |||
//返回标题包含文字的歌单列表 | |||
export const getSongListOfLikeTitle = (keywords) => get(`songList/likeTitle?title=${keywords}`); | |||
//根据风格模糊查询歌单列表 | |||
export const getSongListOfLikeStyle = (style) => get(`songList/likeStyle?style=${style}`); | |||
//============歌单的歌曲相关============ | |||
//根据歌单id查询歌曲列表 | |||
export const listSongDetail = (songListId) => get(`listSong/detail?songListId=${songListId}`); | |||
//============用户相关================ | |||
//查询用户 | |||
export const getAllConsumer =() => get(`consumer/allConsumer`); | |||
//注册 | |||
export const SignUp =(params) => post(`/consumer/add`,params); | |||
//登录 | |||
export const loginIn =(params) => post(`/consumer/login`,params); | |||
//根据用户id查询该用户的详细信息 | |||
export const getUserOfId =(id) => get(`/consumer/selectByPrimaryKey?id=${id}`); | |||
//更新用户信息 | |||
export const updateUserMsg =(params) => post(`/consumer/update`,params); | |||
//下载音乐 | |||
export const download = (url) => Axios({ | |||
method: 'get', | |||
url: url, | |||
responseType: 'blob' | |||
}); | |||
//===========评价====================== | |||
//提交评分 | |||
export const setRank =(params) => post(`/rank/add`,params); | |||
//获取指定歌单的平均分 | |||
export const getRankOfSongListId = (songListId) => get(`/rank?songListId=${songListId}`); | |||
//===========评论====================== | |||
//提交评论 | |||
export const setComment =(params) => post(`/comment/add`,params); | |||
//点赞 | |||
export const setLike =(params) => post(`/comment/like`,params); | |||
//返回当前歌单或歌曲的评论列表 | |||
export const getAllComment = (type,id) => { | |||
if(type == 0){ //歌曲 | |||
return get(`/comment/commentOfSongId?songId=${id}`); | |||
}else{ //歌单 | |||
return get(`/comment/commentOfSongListId?songListId=${id}`); | |||
} | |||
} | |||
//===============收藏=================== | |||
//新增收藏 | |||
export const setCollect =(params) => post(`/collect/add`,params); | |||
//指定用户的收藏列表 | |||
export const getCollectOfUserId = (userId) => get(`/collect/collectOfUserId?userId=${userId}`); |
@ -0,0 +1,16 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
.error-page { | |||
@include layout(center); | |||
width: 100%; | |||
height: 100%; | |||
padding: 11rem 0; | |||
box-sizing: border-box; | |||
.error-code { | |||
font-size: 250px; | |||
font-weight: bolder; | |||
color: $theme-color; | |||
} | |||
} |
@ -0,0 +1,55 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
.content { | |||
background-color: $color-white; | |||
border-radius: $border-radius-songlist; | |||
padding: 20px 40px; | |||
min-width: 700px; | |||
.title { | |||
text-align: center; | |||
} | |||
> ul { | |||
width: 100%; | |||
padding-bottom: 50px; | |||
> li { | |||
border-bottom: 1px solid rgba(0, 0, 0, 0.1); | |||
display: block; | |||
height: 50px; | |||
line-height: 50px; | |||
text-indent: 20px; | |||
cursor: pointer; | |||
} | |||
} | |||
} | |||
.song-item { | |||
@include layout; | |||
white-space: nowrap; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
.item-index { | |||
width: 5%; | |||
} | |||
.item-title { | |||
width: 30%; | |||
} | |||
.item-name { | |||
width: 25%; | |||
} | |||
.item-intro { | |||
width: 40%; | |||
} | |||
} | |||
.is-play { | |||
color: $color-blue-active; | |||
font-weight: bold; | |||
} | |||
.icon { | |||
@include icon(1.3em, $color-blue-active); | |||
vertical-align: -0.3em; | |||
right: 5px; | |||
} |
@ -0,0 +1,11 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
#app { | |||
background-color: $theme-background-color; | |||
@include layout(flex-start, stretch, column); | |||
} | |||
.music-content { | |||
flex: 1; | |||
} |
@ -0,0 +1,74 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
/*评论*/ | |||
.comment { | |||
h2 { | |||
margin-bottom: 20px; | |||
text-align: center; | |||
height: 50px; | |||
line-height: 50px; | |||
border-bottom: 1px solid $color-black; | |||
} | |||
.comment-msg { | |||
display: flex; | |||
.comment-img { | |||
width: 70px; | |||
img { | |||
width: 100%; | |||
} | |||
} | |||
.comment-input { | |||
margin-left: 10px; | |||
flex: 1; | |||
} | |||
} | |||
.sub-btn { | |||
margin-top: 10px; | |||
margin-left: 90%; | |||
} | |||
} | |||
/*热门评论*/ | |||
.popular { | |||
width: 100%; | |||
> li { | |||
border-bottom: solid 1px rgba(0, 0, 0, 0.1); | |||
padding: 15px 0; | |||
display: flex; | |||
.popular-img { | |||
width: 50px; | |||
img { | |||
width: 100%; | |||
} | |||
} | |||
.popular-msg { | |||
padding: 0 20px; | |||
flex: 1; | |||
li { | |||
width: 100%; | |||
} | |||
.name { | |||
font-size: 1rem; | |||
} | |||
.time { | |||
font-size: 0.6rem; | |||
color: rgba(0, 0, 0, 0.5); | |||
} | |||
.content { | |||
font-size: 1rem; | |||
} | |||
} | |||
.up { | |||
width: 50px; | |||
line-height: 60px; | |||
} | |||
} | |||
} | |||
.icon { | |||
@include icon(1em); | |||
} |
@ -0,0 +1,67 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
.content-list { | |||
min-height: 500px; | |||
padding: 0 20px; | |||
.section-content { | |||
@include layout(flex-start, stretch, row, wrap); | |||
} | |||
} | |||
.content-item { | |||
width: 18%; | |||
margin: 20px 1%; | |||
overflow: hidden; | |||
border-radius: 4px; | |||
@include box-shadow(0 0 5px 1px rgba(0, 0, 0, 0.1)); | |||
position: relative; | |||
&:hover { | |||
@include box-shadow(0 0 5px 2px rgba(0, 0, 0, 0.3)); | |||
} | |||
&:hover .item-img { | |||
transform: scale(1.1); | |||
} | |||
.item-name { | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
display: -webkit-box; | |||
-webkit-box-orient: vertical; | |||
-webkit-line-clamp: 2; | |||
margin: 10px 8px; | |||
} | |||
} | |||
.item-img { | |||
width: 100%; | |||
transition: all 0.4s ease; | |||
} | |||
.kuo, | |||
.mask { | |||
width: 100%; | |||
padding-bottom: 100%; | |||
height: 0; | |||
overflow: hidden; | |||
} | |||
.mask { | |||
position: absolute; | |||
top: 0; | |||
background-color: rgba(52, 47, 41, 0.4); | |||
transition: all 0.3s ease-in-out; | |||
opacity: 0; | |||
@include layout(center); | |||
> .icon { | |||
position: absolute; | |||
top: 40%; | |||
} | |||
&:hover { | |||
opacity: 1; | |||
cursor: pointer; | |||
} | |||
} | |||
.icon { | |||
@include icon(2em, rgba(240, 240, 240, 1)); | |||
} |
@ -0,0 +1,32 @@ | |||
@import "var.scss"; | |||
// 上下左右居中 | |||
@mixin layout( | |||
$justify-content: flex-start, | |||
$align-items: stretch, | |||
$flex-direction: row, | |||
$flex-wrap: nowrap | |||
) { | |||
display: flex; | |||
justify-content: $justify-content; | |||
align-items: $align-items; | |||
flex-direction: $flex-direction; | |||
flex-wrap: $flex-wrap; | |||
} | |||
// 图标 | |||
@mixin icon($size: 1.5em, $color: $color-black) { | |||
width: $size; | |||
height: $size; | |||
font-size: $size; | |||
color: $color; | |||
fill: currentColor; | |||
overflow: hidden; | |||
position: relative; | |||
} | |||
@mixin box-shadow($box-shadow) { | |||
-webkit-box-shadow: $box-shadow; | |||
-moz-box-shadow: $box-shadow; | |||
box-shadow: $box-shadow; | |||
} |
@ -0,0 +1,22 @@ | |||
@import "var.scss"; | |||
.home { | |||
margin-top: $header-height - 10px; | |||
.section { | |||
width: 100%; | |||
margin-top: 20px; | |||
padding: $content-padding; | |||
background-color: $color-white; | |||
box-sizing: border-box; | |||
.section-title { | |||
height: 60px; | |||
line-height: 60px; | |||
padding-top: 10px; | |||
font-size: 28px; | |||
font-weight: 500; | |||
text-align: center; | |||
color: $color-black; | |||
box-sizing: border-box; | |||
} | |||
} | |||
} |
@ -0,0 +1,43 @@ | |||
@import "var.scss"; | |||
* { | |||
padding: 0; | |||
margin: 0; | |||
} | |||
ul, | |||
li { | |||
list-style: none; | |||
display: inline-block; | |||
} | |||
html { | |||
min-width: 1200px; | |||
font-size: 14px; | |||
} | |||
div { | |||
box-sizing: content-box; | |||
} | |||
/*表单*/ | |||
.el-select-dropdown__item { | |||
display: block !important; | |||
} | |||
/*轮播图*/ | |||
.el-scrollbar__view, | |||
.el-select-dropdown__list { | |||
width: 100% !important; | |||
text-align: center; | |||
} | |||
.el-carousel__indicators, | |||
.el-carousel__indicators--outside { | |||
display: inline-block; | |||
margin-left: 500px !important; | |||
} | |||
.el-slider__runway { | |||
background-color: $color-blue !important; | |||
} |
@ -0,0 +1,41 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
.info { | |||
padding-bottom: 30px; | |||
.title { | |||
height: 50px; | |||
line-height: 50px; | |||
padding-left: 20px; | |||
font-size: 20px; | |||
font-weight: 600; | |||
color: $color-black; | |||
} | |||
hr { | |||
width: 98%; | |||
} | |||
.personal { | |||
padding-right: 50px; | |||
padding-top: 40px; | |||
} | |||
.btn { | |||
width: 100%; | |||
height: 40px; | |||
margin-left: 40px; | |||
@include layout(center, center); | |||
cursor: pointer; | |||
div { | |||
display: inline-block; | |||
width: 100px; | |||
height: 30px; | |||
line-height: 30px; | |||
text-align: center; | |||
background-color: $color-blue-active; | |||
margin: 0 20px; | |||
color: $color-white; | |||
} | |||
} | |||
} |
@ -0,0 +1,28 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
.login { | |||
position: absolute; | |||
margin-left: 850px; | |||
width: 300px; | |||
height: 210px; | |||
top: $header-height + 60px; | |||
padding: 30px 50px; | |||
border-radius: 10px; | |||
background-color: $color-white; | |||
.login-head { | |||
text-align: center; | |||
margin-bottom: 10px; | |||
font-size: 20px; | |||
font-weight: 600; | |||
} | |||
.login-btn { | |||
@include layout(space-between); | |||
button { | |||
display: block; | |||
width: 50%; | |||
} | |||
} | |||
} |
@ -0,0 +1,15 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
.login-logo { | |||
background-color: $color-blue-light; | |||
height: 100vh; | |||
width: 50vw; | |||
min-width: 650px; | |||
overflow: hidden; | |||
@include layout(center, center); | |||
.icon { | |||
@include icon(6.5em, $color-blue-dark); | |||
transform: rotate(-30deg); | |||
} | |||
} |
@ -0,0 +1,54 @@ | |||
@import "var.scss"; | |||
.song-lyric { | |||
margin: auto; | |||
margin-top: $header-height + 20px; | |||
width: 700px; | |||
background-color: $color-white; | |||
border-radius: 12px; | |||
padding: 0 20px 50px 20px; | |||
font-family: $font-family; | |||
.lyric-title { | |||
text-align: center; | |||
height: 60px; | |||
line-height: 60px; | |||
border-bottom: 2px solid $color-black; | |||
} | |||
.has-lyric { | |||
font-size: 18px; | |||
padding: 30px 0; | |||
width: 100%; | |||
min-height: 170px; | |||
text-align: center; | |||
li { | |||
width: 100%; | |||
height: 40px; | |||
line-height: 40px; | |||
} | |||
} | |||
.no-lyric { | |||
margin: 200px 0; | |||
width: 100%; | |||
text-align: center; | |||
span { | |||
font-size: 18px; | |||
text-align: center; | |||
} | |||
} | |||
} | |||
.lyric-fade-enter, | |||
.lyric-fade-leave-to { | |||
transform: translateX(30px); | |||
opacity: 0; | |||
} | |||
.lyric-fade-enter-active, | |||
.lyric-fade-leave-active { | |||
transition: all 0.3s ease; | |||
} |
@ -0,0 +1,69 @@ | |||
@import "var.scss"; | |||
.my-music { | |||
margin-top: $header-height; | |||
padding-top: 150px; | |||
background-color: $theme-background-color; | |||
&::before { | |||
/*背景*/ | |||
content: ""; | |||
background-color: $theme-color; | |||
position: absolute; | |||
top: 0; | |||
width: 100%; | |||
height: $header-height + 150px; | |||
} | |||
} | |||
/*左侧*/ | |||
.album-slide { | |||
float: left; | |||
width: 400px; | |||
// 图片 | |||
.album-img { | |||
height: 200px; | |||
width: 200px; | |||
display: inline-block; | |||
position: relative; | |||
top: -100px; | |||
left: 50px; | |||
border-radius: 50%; | |||
overflow: hidden; | |||
border: 5px solid $color-white; | |||
img { | |||
width: 100%; | |||
} | |||
} | |||
/*信息*/ | |||
.album-info { | |||
color: $color-black; | |||
width: 500px; | |||
font-size: 20px; | |||
font-weight: 500; | |||
margin-top: -100px; | |||
margin-left: 100px; | |||
padding: 30px 30px; | |||
li { | |||
width: 100%; | |||
line-height: 40px; | |||
} | |||
} | |||
} | |||
/*歌单内容*/ | |||
.album-content { | |||
margin-left: 300px; | |||
padding: 40px 100px; | |||
/*歌单题目*/ | |||
.album-title { | |||
font-size: 30px; | |||
font-weight: 600; | |||
} | |||
.songs-body { | |||
margin-top: 50px; | |||
} | |||
} |
@ -0,0 +1,115 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
.play-bar { | |||
position: fixed; | |||
bottom: 0; | |||
width: 100%; | |||
transition: all 0.5s; | |||
@include box-shadow($box-shadow); | |||
.item-up { | |||
position: absolute; | |||
bottom: $play-bar-height + 10px; | |||
left: 20px; | |||
cursor: pointer; | |||
} | |||
.kongjian { | |||
@include layout(center, center); | |||
bottom: 0; | |||
height: $play-bar-height; | |||
width: 100%; | |||
min-width: 1000px; | |||
background-color: $theme-play-bar-color; | |||
.item { | |||
position: relative; | |||
width: 80px; | |||
height: 50px; | |||
line-height: 60px; | |||
text-align: center; | |||
cursor: pointer; | |||
.icon.active { | |||
color: red !important; | |||
} | |||
.volume { | |||
position: absolute; | |||
display: none; | |||
height: 100px; | |||
top: -($play-bar-height + 50px); | |||
right: 22px; | |||
} | |||
.show-volume { | |||
display: block; | |||
} | |||
} | |||
.item-img { | |||
width: $play-bar-height; | |||
height: $play-bar-height; | |||
img { | |||
width: 100%; | |||
} | |||
} | |||
.playing-speed { | |||
// 进度条 | |||
height: 50px; | |||
width: 900px; | |||
@include layout(center, center); | |||
.current-time, | |||
.left-time { | |||
width: 70px; | |||
text-align: center; | |||
font-size: 13px; | |||
color: $color-black; | |||
font-weight: 500; | |||
top: -10px; | |||
} | |||
.progress-box { | |||
flex: 1; | |||
.item-song-title { | |||
@include layout(space-between); | |||
height: 20px; | |||
line-height: 10px; | |||
} | |||
.progress { | |||
width: 100%; | |||
background: $color-blue-shallow; | |||
height: 6px; | |||
.bg { | |||
height: 100%; | |||
.cur-progress { | |||
height: 100%; | |||
background: $color-blue-active; | |||
} | |||
} | |||
.idot { | |||
width: 16px; | |||
height: 16px; | |||
position: relative; | |||
border-radius: 50%; | |||
background-color: $color-black; | |||
top: -11px; | |||
vertical-align: middle; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
.turn { | |||
transform: rotate(180deg); | |||
} | |||
.show { | |||
bottom: -($play-bar-height); | |||
} | |||
.icon { | |||
@include icon(1.2em, $color-black); | |||
} |
@ -0,0 +1,41 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
.scroll-top { | |||
position: fixed; | |||
width: 50px; | |||
height: 30px; | |||
right: 10px; | |||
bottom: 80px; | |||
padding-top: 20px; | |||
text-align: center; | |||
background-color: $color-white; | |||
border-radius: 20%; | |||
overflow: hidden; | |||
@include box-shadow(0 0 4px 3px rgba(0, 0, 0, 0.2)); | |||
&:hover:before { | |||
top: 50%; | |||
} | |||
&:hover .box-in { | |||
visibility: hidden; | |||
} | |||
&:before { | |||
content: "回到顶部"; | |||
position: absolute; | |||
font-weight: bold; | |||
width: 30px; | |||
top: -50%; | |||
left: 50%; | |||
transform: translate(-50%, -50%); | |||
} | |||
} | |||
.box-in { | |||
visibility: visible; | |||
display: inline-block; | |||
height: 15px; | |||
width: 15px; | |||
border: 1px solid $color-black; | |||
border-color: $color-black transparent transparent $color-black; | |||
transform: rotate(45deg); | |||
} |
@ -0,0 +1,5 @@ | |||
.search-song-Lists{ | |||
min-height: 300px; | |||
margin-top: 50px; | |||
} |
@ -0,0 +1,3 @@ | |||
.search-songs { | |||
min-height: 300px; | |||
} |
@ -0,0 +1,27 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
.search{ | |||
margin: auto; | |||
margin-top: $header-height + 20px; | |||
background-color: $color-white; | |||
border-radius: 12px; | |||
width: 900px; | |||
position: relative; | |||
} | |||
.searchList-nav { | |||
@include layout(space-around); | |||
margin-top: 20px; | |||
font-size: 1.5rem; | |||
color: $color-black; | |||
span { | |||
line-height: 50px; | |||
cursor: pointer; | |||
} | |||
} | |||
.isActive { | |||
font-weight: 600; | |||
border-bottom:5px solid $color-black; | |||
} |
@ -0,0 +1,60 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
.setting { | |||
margin: 30px 10%; | |||
margin-top: $header-height + 20px; | |||
min-height: 65vh; | |||
@include layout; | |||
background-color: $color-white; | |||
border-radius: 12px; | |||
} | |||
/* 左侧导航栏 */ | |||
.leftCol { | |||
margin-top: 10px; | |||
padding: 0 20px; | |||
box-sizing: border-box; | |||
width: 200px; | |||
.settingsMainHeader { | |||
height: 60px; | |||
line-height: 60px; | |||
font-size: 22px; | |||
font-weight: 500; | |||
} | |||
} | |||
.setting-aside { | |||
width: 100%; | |||
li { | |||
display: block; | |||
height: 40px; | |||
line-height: 40px; | |||
font-size: 18px; | |||
padding: 0 10px; | |||
box-sizing: border-box; | |||
border-radius: 5px; | |||
margin-right: 2px; | |||
cursor: pointer; | |||
&:hover { | |||
background-color: $color-blue-active; | |||
color: $color-white; | |||
} | |||
&:active { | |||
background-color: $color-blue-shallow; | |||
} | |||
} | |||
} | |||
.activeColor { | |||
background-color: $color-blue-shallow !important; | |||
color: $color-white; | |||
} | |||
/* 右边内容 */ | |||
.contentCol { | |||
flex: 1; | |||
padding-top: 20px; | |||
} |
@ -0,0 +1,27 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
.signUp { | |||
position: absolute; | |||
top: $header-height + 20px; | |||
background-color: $color-white; | |||
border-radius: 10px; | |||
width: 350px; | |||
margin-left: 1200px; | |||
padding: 30px 30px; | |||
.signUp-head { | |||
text-align: center; | |||
margin-bottom: 10px; | |||
font-size: 20px; | |||
font-weight: 600; | |||
} | |||
.login-btn { | |||
@include layout(space-between); | |||
button { | |||
display: block; | |||
width: 50%; | |||
} | |||
} | |||
} |
@ -0,0 +1,68 @@ | |||
@import "var.scss"; | |||
.singer-album { | |||
margin-top: $header-height; | |||
padding-top: 150px; | |||
background-color: $theme-background-color; | |||
&::before { | |||
/*背景*/ | |||
content: ""; | |||
background-color: $theme-color; | |||
position: absolute; | |||
top: 0; | |||
width: 100%; | |||
height: $header-height + 150px; | |||
} | |||
} | |||
/*左*/ | |||
.album-slide { | |||
float: left; | |||
width: 400px; | |||
.singer-img { | |||
position: relative; | |||
display: inline-block; | |||
height: 300px; | |||
width: 300px; | |||
top: -100px; | |||
left: 50px; | |||
border-radius: 10%; | |||
overflow: hidden; | |||
border: 5px solid $color-white; | |||
background-color: $color-white; | |||
img { | |||
width: 100%; | |||
} | |||
} | |||
.info { | |||
color: $color-black; | |||
font-size: 20px; | |||
font-weight: 500; | |||
margin-top: -80px; | |||
padding: 30px 40px 30px 60px; | |||
li { | |||
width: 100%; | |||
height: 40px; | |||
} | |||
} | |||
} | |||
/*右*/ | |||
.album-content { | |||
margin-left: 300px; | |||
padding: 30px 100px; | |||
.intro { | |||
font-size: 20px; | |||
span { | |||
color: rgba(0, 0, 0, 0.5); | |||
} | |||
} | |||
.content { | |||
margin-top: 50px; | |||
} | |||
} | |||
@ -0,0 +1,38 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
div, | |||
ul, | |||
li { | |||
box-sizing: border-box; | |||
} | |||
.singer { | |||
margin: 30px 10%; | |||
margin-top: $header-height + 20px; | |||
padding-bottom: 50px; | |||
background-color: $color-white; | |||
.singer-header { | |||
width: 100%; | |||
padding: 0 40px; | |||
li { | |||
display: inline-block; | |||
line-height: 40px; | |||
margin: 40px 20px 15px 20px; | |||
font-size: 20px; | |||
font-weight: 400; | |||
color: $color-grey; | |||
border-bottom: none; | |||
cursor: pointer; | |||
} | |||
li.active { | |||
color: $color-black; | |||
font-weight: 600; | |||
border-bottom: 4px solid $color-black; | |||
} | |||
} | |||
} | |||
.pagination { | |||
@include layout; | |||
} |
@ -0,0 +1,3 @@ | |||
audio { | |||
display: none; | |||
} |
@ -0,0 +1,88 @@ | |||
@import "var.scss"; | |||
/*歌单背景*/ | |||
.song-list-album { | |||
margin-top: $header-height; | |||
padding-top: 150px; | |||
background-color: $theme-background-color; | |||
&::before { | |||
/*背景*/ | |||
content: ""; | |||
background-color: $theme-color; | |||
position: absolute; | |||
top: 0; | |||
width: 100%; | |||
height: $header-height + 150px; | |||
} | |||
} | |||
/*歌单左侧*/ | |||
.album-slide { | |||
float: left; | |||
width: 400px; | |||
/*歌单图像*/ | |||
.album-img { | |||
position: relative; | |||
display: inline-block; | |||
height: 300px; | |||
width: 300px; | |||
top: -100px; | |||
left: 50px; | |||
border-radius: 10%; | |||
overflow: hidden; | |||
border: 5px solid $color-white; | |||
background-color: $color-white; | |||
img { | |||
width: 100%; | |||
} | |||
} | |||
/*歌单信息*/ | |||
.album-info { | |||
color: $color-black; | |||
font-size: 20px; | |||
font-weight: 500; | |||
margin-top: -80px; | |||
padding: 30px 40px 30px 60px; | |||
span { | |||
color: rgba(0, 0, 0, 0.5); | |||
} | |||
} | |||
} | |||
/*歌单内容*/ | |||
.album-content { | |||
margin-left: 300px; | |||
padding: 40px 100px; | |||
/*歌单题目*/ | |||
.album-title { | |||
font-size: 30px; | |||
font-weight: 600; | |||
} | |||
/*歌单打分*/ | |||
.album-score { | |||
display: flex; | |||
align-items: center; | |||
margin: 50px; | |||
> div { | |||
margin-left: 100px; | |||
} | |||
> span { | |||
font-size: 60px; | |||
} | |||
h3 { | |||
margin: 10px 0; | |||
} | |||
} | |||
/*歌曲列表*/ | |||
.songs-body { | |||
background-color: $color-white; | |||
border-radius: 12px; | |||
padding: 0 40px 50px 40px; | |||
min-width: 700px; | |||
} | |||
} |
@ -0,0 +1,34 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
.song-list { | |||
margin: 30px 150px; | |||
margin-top: $header-height + 20px; | |||
padding-bottom: 50px; | |||
min-width: 800px; | |||
background-color: $color-white; | |||
} | |||
.song-list-header { | |||
width: 100%; | |||
padding: 0 40px; | |||
li { | |||
display: inline-block; | |||
line-height: 40px; | |||
margin: 40px 20px 15px 20px; | |||
font-size: 20px; | |||
font-weight: 400; | |||
color: $color-grey; | |||
border-bottom: none; | |||
cursor: pointer; | |||
} | |||
li.active { | |||
color: $color-black; | |||
font-weight: 600; | |||
border-bottom: 4px solid $color-black; | |||
} | |||
} | |||
.pagination { | |||
@include layout; | |||
} |
@ -0,0 +1,8 @@ | |||
.swiper { | |||
width: 90%; | |||
margin: auto; | |||
margin-top: 40px; | |||
img { | |||
width: 100%; | |||
} | |||
} |
@ -0,0 +1,65 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
.slide-fade-enter-active { | |||
transition: all .3s ease; | |||
} | |||
.slide-fade-leave-active { | |||
transition: all .2s ease; | |||
} | |||
.slide-fade-enter, .slide-fade-leave-to { | |||
transform: translateX(10px); | |||
opacity: 0; | |||
} | |||
.the-aside { | |||
font-size: 14px; | |||
width: 250px; | |||
height: 370px; | |||
position: fixed; | |||
right: 0; | |||
top: 150px; | |||
z-index: 99; | |||
background-color: $color-white; | |||
@include box-shadow(1px 1px 10px rgba(0, 0, 0, 0.4)); | |||
border: 1px solid rgba(0, 0, 0, 0.5); | |||
border-top-left-radius: 5px; | |||
border-bottom-left-radius: 5px; | |||
overflow: hidden; | |||
} | |||
.title { | |||
padding-left: 20px; | |||
margin: 10px 0; | |||
box-sizing: border-box; | |||
} | |||
.menus { | |||
background-color: $color-white; | |||
width: 100%; | |||
height: calc(100% - 19px); | |||
cursor: pointer; | |||
z-index: 100; | |||
overflow: scroll; | |||
white-space: nowrap; | |||
li { | |||
display: block; | |||
width: 100%; | |||
height: 40px; | |||
line-height: 40px; | |||
padding-left: 20px; | |||
box-sizing: border-box; | |||
border-bottom: solid 1px rgba(0, 0, 0, 0.2); | |||
&:hover{ | |||
background-color: $color-blue; | |||
color: $color-white; | |||
} | |||
} | |||
} | |||
.is-play{ | |||
color: $color-blue-active; | |||
font-weight: bold; | |||
} |
@ -0,0 +1,12 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
.the-footer { | |||
width: 100%; | |||
height: $footer-height; | |||
background-color: $theme-footer-color; | |||
@include layout(center, center, column); | |||
p { | |||
height: 30px; | |||
} | |||
} |
@ -0,0 +1,143 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
.the-header { | |||
position: fixed; | |||
@include layout; | |||
width: 100%; | |||
height: $header-height; | |||
line-height: $header-height; | |||
padding: $header-padding; | |||
margin: $header-margin; | |||
background-color: $theme-header-color; | |||
@include box-shadow($box-shadow); | |||
box-sizing: border-box; | |||
z-index: 100; | |||
} | |||
.header-logo { | |||
margin: $header-logo-margin; | |||
font-size: $font-size-logo; | |||
font-weight: bold; | |||
white-space: nowrap; | |||
cursor: pointer; | |||
.icon { | |||
@include icon(($header-height / 3) * 2, $color-black); | |||
vertical-align: middle; | |||
} | |||
} | |||
/*nav*/ | |||
.navbar { | |||
height: $header-height; | |||
white-space: nowrap; | |||
li { | |||
margin: $header-nav-margin; | |||
padding: $header-nav-padding; | |||
font-size: $font-size-header; | |||
color: $color-grey; | |||
text-align: center; | |||
border-bottom: none; | |||
box-sizing: border-box; | |||
cursor: pointer; | |||
} | |||
} | |||
/*搜索*/ | |||
.header-search { | |||
@include layout; | |||
border-radius: $header-search-radius; | |||
overflow: hidden; | |||
input { | |||
height: $header-search-height; | |||
width: $header-search-width; | |||
font-size: $font-size-default; | |||
border: 0; | |||
text-indent: 10px; | |||
background-color: $color-light-grey; | |||
&:focus { | |||
outline: none; | |||
} | |||
} | |||
.search-btn { | |||
@include layout(center, center); | |||
background-color: $color-blue-active; | |||
width: $header-search-btn-width; | |||
height: $header-search-btn-height; | |||
cursor: pointer; | |||
.icon { | |||
@include icon(1.2em, $color-white); | |||
} | |||
} | |||
} | |||
/*用户*/ | |||
.header-right { | |||
position: relative; | |||
@include layout(center, center); | |||
#user { | |||
overflow: hidden; | |||
width: $header-user-width; | |||
height: $header-user-height; | |||
border-radius: $header-user-radius; | |||
margin: $header-user-margin; | |||
cursor: pointer; | |||
img { | |||
width: 100%; | |||
} | |||
} | |||
} | |||
.menu { | |||
display: none; | |||
line-height: 0px; | |||
position: absolute; | |||
background-color: $color-white; | |||
@include box-shadow(1px 1px 10px rgba(0, 0, 0, 0.4)); | |||
width: $header-menu-width; | |||
padding: $header-menu-padding; | |||
border-radius: $header-menu-radius; | |||
top: $header-height + 10px; | |||
right: -20px; | |||
z-index: 5; | |||
text-align: center; | |||
cursor: pointer; | |||
li { | |||
display: inline-block; | |||
width: 100%; | |||
height: 40px; | |||
line-height: 40px; | |||
&:hover { | |||
background-color: $theme-color; | |||
color: $color-white; | |||
} | |||
} | |||
:nth-child(1):before { | |||
content: " "; | |||
display: block; /*独占一行*/ | |||
position: absolute; | |||
right: 45px; | |||
top: -5px; /*果断的露出上半部分*/ | |||
width: 10px; | |||
height: 10px; | |||
background-color: $color-white; | |||
/*一个正方形倾斜四十五度就是三角了但是要把下半部分藏起来*/ | |||
transform: rotate(45deg); | |||
} | |||
// :nth-child(1):hover:before { | |||
// background-color: $theme-color; | |||
// } | |||
} | |||
.show { | |||
display: block; | |||
} | |||
.active { | |||
color: $theme-color !important; | |||
border-bottom: 5px solid $theme-color !important; | |||
} |
@ -0,0 +1,21 @@ | |||
@import "var.scss"; | |||
@import "global.scss"; | |||
.upload { | |||
width: 100%; | |||
.title { | |||
height: 50px; | |||
line-height: 50px; | |||
padding-left: 20px; | |||
font-size: 20px; | |||
font-weight: 600; | |||
color: $color-black; | |||
} | |||
hr { | |||
width: 98%; | |||
} | |||
.section { | |||
height: 400px; | |||
@include layout(center, center); | |||
} | |||
} |
@ -0,0 +1,60 @@ | |||
//Colors | |||
$color-white: #ffffff; | |||
$color-light-white: #fefefe; | |||
$color-grey: #67757f; | |||
$color-light-grey: #ebeef0; | |||
$color-black: #000000; | |||
$color-blue-shallow: #a9dbf9; | |||
$color-blue: #95d2f6; | |||
$color-blue-active: #30a4fc; | |||
$color-blue-light: #2aa3ef; | |||
$color-blue-dark: #2796dd; | |||
// Theme Global Color | |||
$theme-color: $color-blue; | |||
$theme-background-color: #e6ecf0; | |||
$theme-header-color: $color-light-white; | |||
$theme-footer-color: $theme-background-color; | |||
$theme-play-bar-color: $color-light-white; | |||
// Fonts | |||
$font-family: Lato, Helvetica Neue For Number, -apple-system, BlinkMacSystemFont, | |||
Segoe UI, Roboto, PingFang SC, Hiragino Sans GB, Microsoft YaHei, | |||
Helvetica Neue, Helvetica, Arial, sans-serif; | |||
$font-size-default: 14px; | |||
$font-size-logo: 26px; | |||
$font-size-header: 18px; | |||
// header | |||
$header-height: 70px; | |||
$header-padding: 0 80px; | |||
$header-margin: 0; | |||
$header-logo-width: 150px; | |||
$header-logo-margin: 0 10px; | |||
$header-nav-padding: 0 10px; | |||
$header-nav-margin: 0 10px; | |||
$header-search-radius: 5px; | |||
$header-search-height: ($header-height / 2); | |||
$header-search-width: 270px; | |||
$header-search-btn-height: ($header-height / 2); | |||
$header-search-btn-width: 60px; | |||
$header-user-height: ($header-height / 3) * 2; | |||
$header-user-width: ($header-height / 3) * 2; | |||
$header-user-margin: 0 25px; | |||
$header-user-radius: 50%; | |||
$header-menu-width: 150px; | |||
$header-menu-padding: 7px 0px; | |||
$header-menu-radius: 4px; | |||
// page | |||
$content-padding: 0 120px 50px 120px; | |||
$border-radius-songlist: 12px; | |||
// footer | |||
$footer-height: 180px; | |||
// play-bar | |||
$play-bar-height: 60px; | |||
// style | |||
$box-shadow: 0 0 10px rgba(0, 0, 0, 0.4); |
@ -0,0 +1,138 @@ | |||
// 匹配规则 | |||
const rules = { | |||
username: [ | |||
{ required: true, trigger: 'blur' } | |||
], | |||
password: [ | |||
{ required: true, trigger: 'blur' } | |||
], | |||
sex: [ | |||
{ required: true, message: '请选择性别', trigger: 'change' } | |||
], | |||
phoneNum: [ | |||
{ essage: '请选择日期', trigger: 'blur' } | |||
], | |||
email: [ | |||
{ message: '请输入邮箱地址', trigger: 'blur' }, | |||
{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] } | |||
], | |||
birth: [ | |||
{ required: true, message: '请选择日期', trigger: 'change' } | |||
], | |||
introduction: [ | |||
{ message: '请输入介绍', trigger: 'blur' } | |||
], | |||
location: [ | |||
{ message: '请输入地区', trigger: 'change' } | |||
] | |||
} | |||
// 地区选择 | |||
const cities = [{ | |||
value: '北京', | |||
label: '北京' | |||
}, { | |||
value: '天津', | |||
label: '天津' | |||
}, { | |||
value: '河北', | |||
label: '河北' | |||
}, { | |||
value: '山西', | |||
label: '山西' | |||
}, { | |||
value: '内蒙古', | |||
label: '内蒙古' | |||
}, { | |||
value: '辽宁', | |||
label: '辽宁' | |||
}, { | |||
value: '吉林', | |||
label: '吉林' | |||
}, { | |||
value: '黑龙江', | |||
label: '黑龙江' | |||
}, { | |||
value: '上海', | |||
label: '上海' | |||
}, { | |||
value: '江苏', | |||
label: '江苏' | |||
}, { | |||
value: '浙江', | |||
label: '浙江' | |||
}, { | |||
value: '安徽', | |||
label: '安徽' | |||
}, { | |||
value: '福建', | |||
label: '福建' | |||
}, { | |||
value: '江西', | |||
label: '江西' | |||
}, { | |||
value: '山东', | |||
label: '山东' | |||
}, { | |||
value: '河南', | |||
label: '河南' | |||
}, { | |||
value: '湖北', | |||
label: '湖北' | |||
}, { | |||
value: '湖南', | |||
label: '湖南' | |||
}, { | |||
value: '广东', | |||
label: '广东' | |||
}, { | |||
value: '广西', | |||
label: '广西' | |||
}, { | |||
value: '海南', | |||
label: '海南' | |||
}, { | |||
value: '重庆', | |||
label: '重庆' | |||
}, { | |||
value: '四川', | |||
label: '四川' | |||
}, { | |||
value: '贵州', | |||
label: '贵州' | |||
}, { | |||
value: '云南', | |||
label: '云南' | |||
}, { | |||
value: '西藏', | |||
label: '西藏' | |||
}, { | |||
value: '陕西', | |||
label: '陕西' | |||
}, { | |||
value: '甘肃', | |||
label: '甘肃' | |||
}, { | |||
value: '青海', | |||
label: '青海' | |||
}, { | |||
value: '宁夏', | |||
label: '宁夏' | |||
}, { | |||
value: '新疆', | |||
label: '新疆' | |||
}, { | |||
value: '香港', | |||
label: '香港' | |||
}, { | |||
value: '澳门', | |||
label: '澳门' | |||
}, { | |||
value: '台湾', | |||
label: '台湾' | |||
}] | |||
export { | |||
rules, | |||
cities | |||
} |
@ -0,0 +1,26 @@ | |||
//左侧导航栏 | |||
const navMsg = [ | |||
{name: '首页',path: '/'}, | |||
{name: '歌单',path: '/song-list'}, | |||
{name: '歌手',path: '/singer'}, | |||
{name: '我的音乐',path: '/my-music'} | |||
] | |||
//右侧导航栏 | |||
const loginMsg = [ | |||
{name: '登录',path: '/login-in'}, | |||
{name: '注册',path: '/sign-up'} | |||
] | |||
//用户下拉菜单 | |||
const menuList = [ | |||
{name: '设置',path: '/setting'}, | |||
{name: '退出',path: 0} | |||
] | |||
export { | |||
navMsg, | |||
loginMsg, | |||
menuList | |||
} |
@ -0,0 +1,8 @@ | |||
const singerStyle = [ | |||
{name: '全部歌手',type: '-1'}, | |||
{name: '男歌手',type: '1'}, | |||
{name: '女歌手',type: '0'}, | |||
{name: '组合歌手',type: '2'} | |||
] | |||
export {singerStyle} |
@ -0,0 +1,11 @@ | |||
const songStyle = [ | |||
{name:'全部歌单'}, | |||
{name:'华语'}, | |||
{name:'粤语'}, | |||
{name:'欧美'}, | |||
{name:'日韩'}, | |||
{name:'轻音乐'}, | |||
{name:'BGM'}, | |||
{name:'乐器'} | |||
] | |||
export {songStyle} |
@ -0,0 +1,15 @@ | |||
//轮播图的数据 | |||
const swiperList = [ | |||
{picImg: require('@/assets/img/swiper/1.jpg')}, | |||
{picImg: require('@/assets/img/swiper/2.jpg')}, | |||
{picImg: require('@/assets/img/swiper/3.jpg')}, | |||
{picImg: require('@/assets/img/swiper/4.jpg')}, | |||
{picImg: require('@/assets/img/swiper/5.jpg')}, | |||
{picImg: require('@/assets/img/swiper/6.jpg')}, | |||
{picImg: require('@/assets/img/swiper/7.jpg')}, | |||
{picImg: require('@/assets/img/swiper/8.jpg')} | |||
] | |||
export { | |||
swiperList | |||
} |
@ -0,0 +1 @@ | |||
!function(d){var t,n='<svg><symbol id="icon-liebiao" viewBox="0 0 1024 1024"><path d="M892.928 128q28.672 0 48.64 19.968t19.968 48.64l0 52.224q0 28.672-19.968 48.64t-48.64 19.968l-759.808 0q-28.672 0-48.64-19.968t-19.968-48.64l0-52.224q0-28.672 19.968-48.64t48.64-19.968l759.808 0zM892.928 448.512q28.672 0 48.64 19.968t19.968 48.64l0 52.224q0 28.672-19.968 48.64t-48.64 19.968l-759.808 0q-28.672 0-48.64-19.968t-19.968-48.64l0-52.224q0-28.672 19.968-48.64t48.64-19.968l759.808 0zM892.928 769.024q28.672 0 48.64 19.968t19.968 48.64l0 52.224q0 28.672-19.968 48.64t-48.64 19.968l-759.808 0q-28.672 0-48.64-19.968t-19.968-48.64l0-52.224q0-28.672 19.968-48.64t48.64-19.968l759.808 0z" ></path></symbol><symbol id="icon-xiazai" viewBox="0 0 1024 1024"><path d="M819.203 405.649c0-169.66-137.541-307.19-307.201-307.19s-307.195 137.53-307.195 307.19c-113.105 0-204.8 91.69-204.8 204.801s91.695 204.801 204.8 204.801h102.4V733.33h-102.4c-67.755 0-122.88-55.12-122.88-122.88 0-67.761 55.125-122.881 122.88-122.881h81.92v-81.92c0-124.22 101.055-225.28 225.275-225.28 124.221 0 225.281 101.06 225.281 225.28v81.92h81.92c67.76 0 122.871 55.12 122.871 122.881 0 67.76-55.111 122.88-122.871 122.88h-102.4v81.921h102.4c113.09 0 204.791-91.69 204.791-204.801s-91.701-204.801-204.791-204.801z" fill="#040000" ></path><path d="M511.393 925.541l221.281-238.02-64.441-60-110.79 119.22V410.22h-92.16v336.47L354.488 627.521l-64.431 60z" fill="#040000" ></path></symbol></svg>',e=(t=document.getElementsByTagName("script"))[t.length-1].getAttribute("data-injectcss");if(e&&!d.__iconfont__svg__cssinject__){d.__iconfont__svg__cssinject__=!0;try{document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>")}catch(t){console&&console.log(t)}}!function(t){if(document.addEventListener)if(~["complete","loaded","interactive"].indexOf(document.readyState))setTimeout(t,0);else{var e=function(){document.removeEventListener("DOMContentLoaded",e,!1),t()};document.addEventListener("DOMContentLoaded",e,!1)}else document.attachEvent&&(n=t,i=d.document,o=!1,l=function(){o||(o=!0,n())},(c=function(){try{i.documentElement.doScroll("left")}catch(t){return void setTimeout(c,50)}l()})(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,l())});var n,i,o,l,c}(function(){var t,e;(t=document.createElement("div")).innerHTML=n,n=null,(e=t.getElementsByTagName("svg")[0])&&(e.setAttribute("aria-hidden","true"),e.style.position="absolute",e.style.width=0,e.style.height=0,e.style.overflow="hidden",function(t,e){e.firstChild?function(t,e){e.parentNode.insertBefore(t,e)}(t,e.firstChild):e.appendChild(t)}(e,document.body))})}(window); |
@ -0,0 +1 @@ | |||
!function(d){var e,a='<svg><symbol id="icon-shanchu" viewBox="0 0 1024 1024"><path d="M517.59 21.609c-100.299 0-181.703 79.514-185.167 179.34H98.603c-25.979 0-47.235 21.099-47.235 47.236 0 25.98 21.099 47.237 47.236 47.237h52.117v528.416c0 99.196 67.233 180.285 150.37 180.285h423.55c82.98 0 150.37-80.616 150.37-180.285V295.737h47.236c25.98 0 47.236-21.1 47.236-47.237 0-25.98-21.099-47.236-47.236-47.236H702.441C699.449 101.124 617.888 21.61 517.59 21.61z m-96.677 179.34c3.464-51.172 45.19-90.85 96.834-90.85s93.37 39.835 96.362 90.85H420.913z m-119.98 714.842c-29.444 0-61.88-37.789-61.88-91.953V295.737h547.311V824.31c0 54.007-32.436 91.954-61.88 91.954H300.933v-0.473z m0 0" ></path><path d="M364.387 802.267c21.57 0 39.363-21.571 39.363-48.653V476.022c0-27.082-17.635-48.654-39.363-48.654-21.572 0-39.364 21.572-39.364 48.654v277.592c0 26.924 17.32 48.653 39.364 48.653z m142.496 0c21.571 0 39.363-21.571 39.363-48.653V476.022c0-27.082-17.635-48.654-39.363-48.654-21.571 0-39.364 21.572-39.364 48.654v277.592c0 26.924 17.793 48.653 39.364 48.653z m149.896 0c21.571 0 39.364-21.571 39.364-48.653V476.022c0-27.082-17.635-48.654-39.364-48.654-21.571 0-39.363 21.572-39.363 48.654v277.592c0 26.924 17.162 48.653 39.363 48.653z m0 0" ></path></symbol><symbol id="icon-yinliang" viewBox="0 0 1024 1024"><path d="M545.962667 208.042667a66.679467 66.679467 0 0 0-67.413334 1.877333L232.106667 364.885333H150.186667c-36.522667 0-66.389333 29.696-66.389334 66.389334v239.786666c0 36.522667 29.696 66.389333 66.389334 66.389334h81.92l246.613333 154.965333c10.752 6.826667 23.04 10.069333 35.328 10.069333 11.093333 0 22.186667-2.730667 32.085333-8.362666a66.286933 66.286933 0 0 0 34.304-58.026667v-570.026667a66.781867 66.781867 0 0 0-34.474666-58.026666zM512 832.682667l-251.904-158.378667c-5.461333-3.413333-11.776-5.290667-18.090667-5.290667H152.064V433.152h89.941333c6.485333 0 12.8-1.877333 18.090667-5.290667L512 269.482667v563.2zM806.741333 551.082667c0-76.629333-41.130667-147.797333-107.52-185.856-16.384-9.386667-37.205333-3.754667-46.592 12.629333-9.386667 16.384-3.754667 37.205333 12.629334 46.592a146.432 146.432 0 0 1 0 253.610667c-16.384 9.386667-22.016 30.208-12.629334 46.592a34.1504 34.1504 0 0 0 46.762667 12.458666c66.218667-38.229333 107.349333-109.397333 107.349333-186.026666z" ></path><path d="M784.896 239.957333c-16.384-9.386667-37.205333-3.754667-46.592 12.458667s-3.754667 37.205333 12.458667 46.592c89.770667 51.882667 145.578667 148.48 145.578666 252.074667s-55.808 200.192-145.578666 252.074666c-16.384 9.386667-21.845333 30.378667-12.458667 46.592 6.314667 10.922667 17.749333 17.066667 29.525333 17.066667 5.802667 0 11.605333-1.536 17.066667-4.608a360.226133 360.226133 0 0 0 179.712-311.125333c0-128-68.778667-247.125333-179.712-311.125334z" ></path></symbol></svg>',t=(e=document.getElementsByTagName("script"))[e.length-1].getAttribute("data-injectcss");if(t&&!d.__iconfont__svg__cssinject__){d.__iconfont__svg__cssinject__=!0;try{document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>")}catch(e){console&&console.log(e)}}!function(e){if(document.addEventListener)if(~["complete","loaded","interactive"].indexOf(document.readyState))setTimeout(e,0);else{var t=function(){document.removeEventListener("DOMContentLoaded",t,!1),e()};document.addEventListener("DOMContentLoaded",t,!1)}else document.attachEvent&&(c=e,o=d.document,i=!1,(a=function(){try{o.documentElement.doScroll("left")}catch(e){return void setTimeout(a,50)}n()})(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,n())});function n(){i||(i=!0,c())}var c,o,i,a}(function(){var e,t,n,c,o,i;(e=document.createElement("div")).innerHTML=a,a=null,(t=e.getElementsByTagName("svg")[0])&&(t.setAttribute("aria-hidden","true"),t.style.position="absolute",t.style.width=0,t.style.height=0,t.style.overflow="hidden",n=t,(c=document.body).firstChild?(o=n,(i=c.firstChild).parentNode.insertBefore(o,i)):c.appendChild(n))})}(window); |
@ -0,0 +1 @@ | |||
!function(l){var e,a='<svg><symbol id="icon-yinliangjingyinheix" viewBox="0 0 1024 1024"><path d="M337.834667 341.333333l147.2-120.021333A42.666667 42.666667 0 0 1 554.666667 254.378667v516.053333a42.666667 42.666667 0 0 1-69.632 33.066667L336.810667 682.666667H213.333333a85.333333 85.333333 0 0 1-85.333333-85.333334v-170.666666a85.333333 85.333333 0 0 1 85.333333-85.333334h124.501334z m433.792 110.293334l60.330666-60.373334a42.666667 42.666667 0 0 1 60.330667 60.373334L832 511.957333l60.330667 60.330667a42.666667 42.666667 0 1 1-60.330667 60.330667l-60.330667-60.330667-60.330666 60.330667a42.666667 42.666667 0 0 1-60.373334-60.330667l60.373334-60.330667-60.373334-60.330666a42.666667 42.666667 0 0 1 60.373334-60.373334l60.330666 60.373334z" fill="#303030" ></path></symbol><symbol id="icon-yinliang1" viewBox="0 0 1024 1024"><path d="M737.43945313 700.73138428c-12.9776001-12.9776001-12.9776001-30.03387451-1e-8-43.01147461 83.05664063-81.9442749 83.05664063-210.97869873 0-292.92297363-12.9776001-12.9776001-12.9776001-30.03387451 0-43.01147461s30.77545166-12.9776001 43.75305177 0c52.28118896 51.91040039 83.05664063 116.42761231 83.05664061 185.39428711s-30.77545166 137.93334961-83.05664062 185.02349853c-4.44946289 4.44946289-12.9776001 8.52813721-21.87652587 8.52813721-8.89892578 8.89892578-17.42706299 4.44946289-21.87652589 0z m-87.50610352-47.09014892c-8.89892578 0-17.42706299-4.44946289-21.87652588-8.52813721-12.9776001-12.9776001-12.9776001-30.03387451 0-43.01147461 52.65197754-51.53961182 52.65197754-129.03442383 0-180.94482422-12.9776001-12.9776001-12.9776001-30.03387451 0-43.01147461s30.77545166-12.9776001 43.75305176 0c78.60717773 73.04534912 78.60717773 189.47296143 0 262.51831055-8.89892578 8.52813721-13.34838867 12.9776001-21.87652588 12.9776001zM487.89874268 209.80731201c39.30358887-30.03387451 70.07904052-12.9776001 70.07904052 34.4833374V782.67565918c0 47.4609375-30.77545166 60.4385376-65.62957763 30.03387451l-157.58514405-129.03442382H247.256958c-48.20251465 0-87.50610352-38.93280029-87.5061035-86.02294922V420.78601074c0-47.4609375 39.30358887-86.02294922 87.50610351-86.02294921H330.31359863l157.58514405-124.95574952z" ></path></symbol><symbol id="icon-shezhi" viewBox="0 0 1024 1024"><path d="M880.9984 512c0-52.39808 32.90112-96.79872 79.0016-114.49856a459.2896 459.2896 0 0 0-50.2016-121.29792c-45.19936 20.10112-99.79904 11.89888-136.80128-25.20064-37.00224-37.00224-45.19936-91.6992-25.09824-136.80128a461.55264 461.55264 0 0 0-121.40032-50.2016c-17.80224 46.10048-62.19776 79.0016-114.49856 79.0016-52.39808 0-96.79872-32.90112-114.49856-79.0016a459.2896 459.2896 0 0 0-121.29792 50.2016c20.10112 45.19936 11.89888 99.79904-25.09824 136.80128s-91.6992 45.19936-136.80128 25.20064a456.43264 456.43264 0 0 0-50.29888 121.29792C110.10048 415.29856 143.0016 459.6992 143.0016 512c0 52.39808-32.90112 96.79872-79.0016 114.49856a459.2896 459.2896 0 0 0 50.2016 121.29792c45.19936-20.10112 99.79904-11.89888 136.80128 25.20064 37.00224 37.00224 45.19936 91.6992 25.09824 136.80128a461.55264 461.55264 0 0 0 121.40032 50.2016c17.80224-46.10048 62.19776-79.0016 114.49856-79.0016 52.39808 0 96.79872 32.90112 114.49856 79.0016a459.2896 459.2896 0 0 0 121.29792-50.2016c-20.10112-45.19936-11.89888-99.79904 25.09824-136.80128s91.6992-45.19936 136.80128-25.20064a456.43264 456.43264 0 0 0 50.29888-121.29792c-46.09536-17.69984-78.99648-62.10048-78.99648-114.49856zM512 634.99776c-67.90144 0-122.99776-55.10144-122.99776-122.99776S444.09856 389.00224 512 389.00224 634.99776 444.09856 634.99776 512 579.90144 634.99776 512 634.99776z" fill="#333333" ></path></symbol></svg>',t=(e=document.getElementsByTagName("script"))[e.length-1].getAttribute("data-injectcss");if(t&&!l.__iconfont__svg__cssinject__){l.__iconfont__svg__cssinject__=!0;try{document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>")}catch(e){console&&console.log(e)}}!function(e){if(document.addEventListener)if(~["complete","loaded","interactive"].indexOf(document.readyState))setTimeout(e,0);else{var t=function(){document.removeEventListener("DOMContentLoaded",t,!1),e()};document.addEventListener("DOMContentLoaded",t,!1)}else document.attachEvent&&(i=e,o=l.document,c=!1,(a=function(){try{o.documentElement.doScroll("left")}catch(e){return void setTimeout(a,50)}n()})(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,n())});function n(){c||(c=!0,i())}var i,o,c,a}(function(){var e,t,n,i,o,c;(e=document.createElement("div")).innerHTML=a,a=null,(t=e.getElementsByTagName("svg")[0])&&(t.setAttribute("aria-hidden","true"),t.style.position="absolute",t.style.width=0,t.style.height=0,t.style.overflow="hidden",n=t,(i=document.body).firstChild?(o=n,(c=i.firstChild).parentNode.insertBefore(o,c)):i.appendChild(n))})}(window); |
@ -0,0 +1,43 @@ | |||
<template> | |||
<div class="content"> | |||
<h1 class="title"> | |||
<slot name="title"></slot> | |||
<hr/> | |||
</h1> | |||
<ul> | |||
<li> | |||
<div class="song-item"> | |||
<span class="item-index"></span> | |||
<span class="item-title">歌曲名</span> | |||
<span class="item-name">歌手</span> | |||
<span class="item-intro">专辑</span> | |||
</div> | |||
</li> | |||
<li v-for="(item,index) in songList" :key="index"> | |||
<div class="song-item" @click="toplay(item.id,item.url,item.pic,index,item.name,item.lyric)"> | |||
<span class="item-index"> | |||
{{index + 1}} | |||
</span> | |||
<span class="item-title">{{replaceFName(item.name)}}</span> | |||
<span class="item-name">{{replaceLName(item.name)}}</span> | |||
<span class="item-intro">{{item.introduction}}</span> | |||
</div> | |||
</li> | |||
</ul> | |||
</div> | |||
</template> | |||
<script> | |||
import {mapGetters} from "vuex"; | |||
import {mixin} from "../mixins"; | |||
export default { | |||
name: 'album-content', | |||
mixins: [mixin], | |||
props:[ | |||
'songList' | |||
] | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import '../assets/css/album-content.scss'; | |||
</style> |
@ -0,0 +1,153 @@ | |||
<template> | |||
<div> | |||
<div class="comment"> | |||
<h2>评论</h2> | |||
<div class="comment-msg"> | |||
<div class="comment-img"> | |||
<img :src="attachImageUrl(avator)"> | |||
</div> | |||
<el-input class="comment-input" type="textarea" :rows="2" placeholder="请输入内容" v-model="textarea"> | |||
</el-input> | |||
</div> | |||
<el-button type="primary" class="sub-btn" @click="postComment">评论</el-button> | |||
</div> | |||
<p>精彩评论:共{{commentList.length}}条评论</p> | |||
<ul class="popular" v-for="(item,index) in commentList" :key="index"> | |||
<li> | |||
<div class="popular-img"> | |||
<img :src="attachImageUrl(userPic[index])"> | |||
</div> | |||
<div class="popular-msg"> | |||
<ul> | |||
<li class="name">{{userName[index]}}</li> | |||
<li class="time">{{item.createTime}}</li> | |||
<li class="content">{{item.content}}</li> | |||
</ul> | |||
</div> | |||
<div class="up" ref="up" @click="postUp(item.id,item.up,index)"> | |||
<svg class="icon"> | |||
<use xlink:href="#icon-zan"></use> | |||
</svg> | |||
{{item.up}} | |||
</div> | |||
</li> | |||
</ul> | |||
</div> | |||
</template> | |||
<script> | |||
import {mixin} from '../mixins'; | |||
import {mapGetters} from 'vuex'; | |||
import {setComment,setLike,getAllComment,getUserOfId} from '../api/index'; | |||
export default { | |||
name: 'comment', | |||
mixins: [mixin], | |||
props: [ | |||
'playId', //歌曲或歌单id | |||
'type' //0歌曲、1歌单 | |||
], | |||
computed:{ | |||
...mapGetters([ | |||
'id', //歌曲或歌单id | |||
'loginIn', //用户是否已登录 | |||
'userId', //当前登录用户id | |||
'avator', //当前登录用户头像 | |||
]) | |||
}, | |||
data(){ | |||
return{ | |||
textarea: '', //存放输入的评论内容 | |||
commentList: [], //存放评论列表 | |||
userPic: [], //用户的头像 | |||
userName: [], //用户的昵称 | |||
} | |||
}, | |||
mounted(){ | |||
this.getComment(); | |||
}, | |||
methods: { | |||
//提交评论 | |||
postComment(){ | |||
if(this.loginIn){ | |||
let params = new URLSearchParams(); | |||
if(this.type == 0){ | |||
params.append('songId',this.playId); | |||
}else{ | |||
params.append('songListId',this.playId); | |||
} | |||
params.append('userId',this.userId); | |||
params.append('type',this.type); | |||
params.append('content',this.textarea); | |||
setComment(params) | |||
.then(res => { | |||
if(res.code == 1){ | |||
this.notify('评论成功','success'); | |||
this.textarea=''; | |||
this.getComment(); | |||
}else{ | |||
this.notify('评论失败','error'); | |||
} | |||
}) | |||
.catch(err =>{ | |||
this.notify('评论失败','error'); | |||
}) | |||
}else{ | |||
this.rank = null; | |||
this.notify('请先登录','warning'); | |||
} | |||
}, | |||
//评论列表 | |||
getComment(){ | |||
getAllComment(this.type,this.playId) | |||
.then(res => { | |||
this.commentList = res; | |||
for(let item of res){ | |||
this.getUsers(item.userId); | |||
} | |||
}) | |||
.catch(err =>{ | |||
this.notify('评论加载失败','error'); | |||
}) | |||
}, | |||
//获取用户的头像和昵称 | |||
getUsers(id){ | |||
getUserOfId(id) | |||
.then(res => { | |||
this.userPic.push(res.avator); | |||
this.userName.push(res.username); | |||
}) | |||
.catch(err =>{ | |||
this.notify('出错了','error'); | |||
}) | |||
}, | |||
//给某一个评论点赞 | |||
postUp(id,up,index){ | |||
if(this.loginIn){ | |||
let params = new URLSearchParams(); | |||
params.append('id',id); | |||
params.append('up',up+1); | |||
setLike(params) | |||
.then(res => { | |||
if(res.code == 1){ | |||
this.$refs.up[index].children[0].style.color = '#2796cd'; | |||
this.getComment(); | |||
}else{ | |||
this.notify('点赞失败','error'); | |||
} | |||
}) | |||
.catch(err =>{ | |||
this.notify('点赞失败','error'); | |||
}) | |||
}else{ | |||
this.rank = null; | |||
this.notify('请先登录','warning'); | |||
} | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import '../assets/css/comment.scss'; | |||
</style> |
@ -0,0 +1,38 @@ | |||
<template> | |||
<div class="content-list"> | |||
<ul class="section-content"> | |||
<li class="content-item" v-for="(item,index) in contentList" :key="index"> | |||
<div class="kuo" @click="goAlbum(item,item.name)"> | |||
<img class="item-img" :src="attachImageUrl(item.pic)"> | |||
<div class="mask"> | |||
<svg class="icon"> | |||
<use xlink:href="#icon-bofang"></use> | |||
</svg> | |||
</div> | |||
</div> | |||
<p class="item-name">{{item.name||item.title}}</p> | |||
</li> | |||
</ul> | |||
</div> | |||
</template> | |||
<script> | |||
import {mixin} from '../mixins'; | |||
export default { | |||
name: 'content-list', | |||
mixins: [mixin], | |||
props: ['contentList'], | |||
methods: { | |||
goAlbum(item,type){ | |||
this.$store.commit("setTempList",item); | |||
if(type){ //歌手 | |||
this.$router.push({path:`singer-album/${item.id}`}); | |||
}else{ //歌单 | |||
this.$router.push({path:`song-list-album/${item.id}`}); | |||
} | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import '../assets/css/content-list.scss'; | |||
</style> |
@ -0,0 +1,143 @@ | |||
<template> | |||
<div> | |||
<div class="info"> | |||
<div class="title"> | |||
<span>编辑个人资料</span> | |||
</div> | |||
<hr/> | |||
<div class="personal"> | |||
<el-form :model="registerForm" ref="registerForm" label-width="70px" class="demo-ruleForm" :rules="rules"> | |||
<el-form-item prop="username" label="用户名"> | |||
<el-input v-model="registerForm.username" placeholder="用户名"></el-input> | |||
</el-form-item> | |||
<el-form-item prop="password" label="密码"> | |||
<el-input type="password" v-model="registerForm.password" placeholder="密码"></el-input> | |||
</el-form-item> | |||
<el-form-item prop="sex" label="性别"> | |||
<el-radio-group v-model="registerForm.sex"> | |||
<el-radio :label="0">女</el-radio> | |||
<el-radio :label="1">男</el-radio> | |||
</el-radio-group> | |||
</el-form-item> | |||
<el-form-item prop="phoneNum" label="手机"> | |||
<el-input v-model="registerForm.phoneNum" placeholder="手机"></el-input> | |||
</el-form-item> | |||
<el-form-item prop="email" label="邮箱"> | |||
<el-input v-model="registerForm.email" placeholder="邮箱"></el-input> | |||
</el-form-item> | |||
<el-form-item prop="birth" label="生日"> | |||
<el-date-picker type='date' :editable="false" v-model="registerForm.birth" placeholder="选择日期" style="width: 100%;"></el-date-picker> | |||
</el-form-item> | |||
<el-form-item prop="introduction" label="签名"> | |||
<el-input v-model="registerForm.introduction" placeholder="签名"></el-input> | |||
</el-form-item> | |||
<el-form-item prop="location" label="地区"> | |||
<el-select v-model="registerForm.location" placeholder="地区" style="width: 100%;"> | |||
<el-option v-for=" item in cities" :key="item.value" :label="item.label" :value="item.value"></el-option> | |||
</el-select> | |||
</el-form-item> | |||
</el-form> | |||
<div class="btn"> | |||
<div @click="saveMsg">保存</div> | |||
<div @click="goback(-1)">取消</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import {mapGetters} from 'vuex' | |||
import {rules,cities} from '../assets/data/form' | |||
import {mixin} from '../mixins' | |||
import {getUserOfId,updateUserMsg} from '../api/index' | |||
export default { | |||
name: 'info', | |||
mixins: [mixin], | |||
data() { | |||
return { | |||
registerForm: { | |||
username: '', //用户名 | |||
password: '', //密码 | |||
sex: '', //性别 | |||
phoneNum: '', //手机 | |||
email: '', //邮箱 | |||
birth: '', //生日 | |||
introduction: '', //签名 | |||
location: '' //地区 | |||
}, | |||
cities: [], //所有的地区--省 | |||
rules: {} //表单提交的规则 | |||
} | |||
}, | |||
computed:{ | |||
...mapGetters([ | |||
'userId' | |||
]) | |||
}, | |||
created() { | |||
this.rules = rules; | |||
this.cities = cities; | |||
}, | |||
mounted(){ | |||
this.getMsg(this.userId); | |||
}, | |||
methods:{ | |||
getMsg(userId){ | |||
getUserOfId(userId) | |||
.then(res =>{ | |||
this.registerForm.username = res.username; | |||
this.registerForm.password = res.password; | |||
this.registerForm.sex = res.sex; | |||
this.registerForm.phoneNum = res.phoneNum; | |||
this.registerForm.email = res.email; | |||
this.registerForm.birth = res.birth; | |||
this.registerForm.introduction = res.introduction; | |||
this.registerForm.location = res.location; | |||
}) | |||
.catch(err => { | |||
console.log(err); | |||
}) | |||
}, | |||
saveMsg(){ | |||
let _this = this; | |||
let d = new Date(this.registerForm.birth); | |||
let datetime = d.getFullYear() + '-' +(d.getMonth() + 1) +'-' + d.getDate(); | |||
let params = new URLSearchParams(); | |||
params.append('id',this.userId); | |||
params.append('username',this.registerForm.username); | |||
params.append('password',this.registerForm.password); | |||
params.append('sex',this.registerForm.sex); | |||
params.append('phoneNum',this.registerForm.phoneNum); | |||
params.append('email',this.registerForm.email); | |||
params.append('birth',datetime); | |||
params.append('introduction',this.registerForm.introduction); | |||
params.append('location',this.registerForm.location); | |||
updateUserMsg(params) | |||
.then(res => { | |||
if(res.code == 1){ | |||
_this.$store.commit('setUsername',this.registerForm.username); | |||
_this.notify('修改成功','success'); | |||
setTimeout(function(){ | |||
_this.$router.push({path: '/'}); | |||
},2000); | |||
}else{ | |||
_this.notify('修改失败','error'); | |||
} | |||
}) | |||
.catch(err => { | |||
_this.notify('修改失败','error'); | |||
}) | |||
}, | |||
goback(index){ | |||
this.$router.go(index); | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import '../assets/css/info.scss'; | |||
</style> |
@ -0,0 +1,16 @@ | |||
<template> | |||
<div class="login-logo"> | |||
<svg class="icon"> | |||
<use xlink:href="#icon-erji"></use> | |||
</svg> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'login-logo' | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import '../assets/css/login-logo.scss'; | |||
</style> |
@ -0,0 +1,396 @@ | |||
<template> | |||
<div class="play-bar" :class="{show:!toggle}"> | |||
<div @click="toggle=!toggle" class="item-up" :class="{turn: toggle}"> | |||
<svg class="icon"> | |||
<use xlink:href="#icon-jiantou-xia-cuxiantiao"></use> | |||
</svg> | |||
</div> | |||
<div class="kongjian"> | |||
<!-- 上一首 --> | |||
<div class="item" @click="prev"> | |||
<svg class="icon"> | |||
<use xlink:href="#icon-ziyuanldpi"></use> | |||
</svg> | |||
</div> | |||
<!-- 播放 --> | |||
<div class="item" @click="togglePlay"> | |||
<svg class="icon"> | |||
<use :xlink:href="playButtonUrl"></use> | |||
</svg> | |||
</div> | |||
<!-- 下一首 --> | |||
<div class="item" @click="next"> | |||
<svg class="icon"> | |||
<use xlink:href="#icon-ziyuanldpi1"></use> | |||
</svg> | |||
</div> | |||
<!-- 歌曲图片 --> | |||
<div class="item-img" @click="toLyric"> | |||
<img :src="picUrl"/> | |||
</div> | |||
<!-- 播放进度 --> | |||
<div class="playing-speed"> | |||
<!-- 播放开始时间 --> | |||
<div class="current-time">{{nowTime}}</div> | |||
<div class="progress-box"> | |||
<div class="item-song-title"> | |||
<div>{{this.title}}</div> | |||
<div>{{this.artist}}</div> | |||
</div> | |||
<div ref="progress" class="progress" @mousemove="mousemove"> | |||
<!-- 进度条 --> | |||
<div ref="bg" class="bg" @click="updatemove"> | |||
<div ref="curProgress" class="cur-progress" :style="{width:curLength+'%'}"></div> | |||
</div> | |||
<!-- 拖动的点点 --> | |||
<div ref="idot" class="idot" :style="{left:curLength+'%'}" @mousedown="mousedown" @mouseup="mouseup"></div> | |||
</div> | |||
</div> | |||
<!-- 播放结束时间 --> | |||
<div class="left-time">{{songTime}}</div> | |||
<!-- 音量 --> | |||
<div class="item item-volume"> | |||
<svg v-if="volume == 0" class="icon"> | |||
<use xlink:href="#icon-yinliangjingyinheix"></use> | |||
</svg> | |||
<svg v-else class="icon"> | |||
<use xlink:href="#icon-yinliang1"></use> | |||
</svg> | |||
<el-slider class="volume" v-model="volume" :vertical="true"></el-slider> | |||
</div> | |||
<!-- 收藏 --> | |||
<div class="item" @click="collection"> | |||
<svg :class="{active:isActive}" class="icon"> | |||
<use xlink:href="#icon-xihuan-shi"></use> | |||
</svg> | |||
</div> | |||
<!-- 下载 --> | |||
<div class="item" @click="download"> | |||
<svg class="icon"> | |||
<use xlink:href="#icon-xiazai"></use> | |||
</svg> | |||
</div> | |||
<!-- 当前播放的歌曲列表 --> | |||
<div class="item" @click="changeAside"> | |||
<svg class="icon"> | |||
<use xlink:href="#icon-liebiao"></use> | |||
</svg> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import {mapGetters} from 'vuex'; | |||
import { download,setCollect,getCollectOfUserId } from '../api/index'; | |||
export default { | |||
name: 'play-bar', | |||
data(){ | |||
return { | |||
nowTime: '00:00', //当前播放进度的时间 | |||
songTime: '00:00', //当前歌曲总时间 | |||
curLength: 0, //进度条的位置 | |||
progressLength: 0, //进度条的总长度 | |||
mouseStartX: 0, //拖拽开始位置 | |||
tag: false, //拖拽开始结束的标志,当开始拖拽,它的值才会变成true | |||
volume: 50, //音量,默认一半 | |||
toggle: true //显示隐藏播放器页面 | |||
} | |||
}, | |||
computed: { | |||
...mapGetters([ | |||
'id', //歌曲id | |||
'url', //歌曲地址 | |||
'isPlay', //播放状态 | |||
'playButtonUrl', //播放状态的图标 | |||
'picUrl', //正在播放的音乐的图片 | |||
'title', //歌名 | |||
'artist', //歌手名 | |||
'duration', //音乐时长 | |||
'curTime', //当前音乐的播放位置 | |||
'showAside', //是否显示播放中的歌曲列表 | |||
'listIndex', //当前歌曲在歌单中的位置 | |||
'listOfSongs', //当前歌单列表 | |||
'autoNext', //自动播放下一首 | |||
'loginIn', //用户是否已登录 | |||
'userId', //当前登录用户的id | |||
'isActive', //当前播放的歌曲是否已收藏 | |||
]) | |||
}, | |||
watch:{ | |||
//切换播放状态的图标 | |||
isPlay() { | |||
if(this.isPlay){ | |||
this.$store.commit('setPlayButtonUrl', '#icon-zanting'); | |||
}else{ | |||
this.$store.commit('setPlayButtonUrl', '#icon-bofang'); | |||
} | |||
}, | |||
curTime(){ | |||
this.nowTime = this.formatSeconds(this.curTime); | |||
this.songTime = this.formatSeconds(this.duration); | |||
this.curLength = (this.curTime / this.duration) *100; | |||
}, | |||
//音量变化 | |||
volume(){ | |||
this.$store.commit('setVolume',this.volume / 100); | |||
}, | |||
//自动播放下一首 | |||
autoNext(){ | |||
this.next(); | |||
} | |||
}, | |||
mounted(){ | |||
this.progressLength = this.$refs.progress.getBoundingClientRect().width; | |||
document.querySelector('.item-volume').addEventListener('click',function(e){ | |||
document.querySelector('.volume').classList.add('show-volume'); | |||
e.stopPropagation(); | |||
},false); | |||
document.querySelector('.volume').addEventListener('click',function(e){ | |||
e.stopPropagation(); | |||
},false); | |||
document.addEventListener('click',function(){ | |||
document.querySelector('.volume').classList.remove('show-volume'); | |||
},false); | |||
}, | |||
methods: { | |||
//提示信息 | |||
notify(title,type) { | |||
this.$notify({ | |||
title: title, | |||
type: type | |||
}) | |||
}, | |||
//控制音乐播放、暂停 | |||
togglePlay() { | |||
if(this.isPlay){ | |||
this.$store.commit('setIsPlay', false); | |||
}else{ | |||
this.$store.commit('setIsPlay', true); | |||
} | |||
}, | |||
//解析时间 | |||
formatSeconds(value){ | |||
let theTime = parseInt(value); | |||
let result = ''; //返回值 | |||
let hour = parseInt(theTime / 3600); //小时 | |||
let minute = parseInt((theTime / 60) % 60); //分钟 | |||
let second = parseInt(theTime % 60); //秒 | |||
if(hour > 0){ | |||
if(hour < 10){ | |||
result = '0' + hour + ":"; | |||
}else{ | |||
result = hour + ":"; | |||
} | |||
} | |||
if(minute > 0){ | |||
if(minute < 10){ | |||
result += "0" + minute + ":"; | |||
}else{ | |||
result += minute + ":"; | |||
} | |||
}else{ | |||
result += "00:"; | |||
} | |||
if(second > 0){ | |||
if(second < 10){ | |||
result += "0" + second; | |||
}else{ | |||
result += second; | |||
} | |||
}else{ | |||
result += "00"; | |||
} | |||
return result; | |||
}, | |||
//拖拽开始 | |||
mousedown(e){ | |||
this.mouseStartX = e.clientX; | |||
this.tag = true; | |||
}, | |||
//拖拽结束 | |||
mouseup(){ | |||
this.tag = false; | |||
}, | |||
//拖拽中 | |||
mousemove(e){ | |||
if(!this.duration){ | |||
return false; | |||
} | |||
if(this.tag){ | |||
let movementX = e.clientX - this.mouseStartX; //点点移动的距离 | |||
let curLength = this.$refs.curProgress.getBoundingClientRect().width; | |||
let newPercent = ((movementX+curLength)/this.progressLength)*100; | |||
if(newPercent>100){ | |||
newPercent = 100; | |||
} | |||
this.curLength = newPercent; | |||
this.mouseStartX = e.clientX; | |||
this.changeTime(newPercent); | |||
} | |||
}, | |||
//更改歌曲进度 | |||
changeTime(percent){ | |||
let newCurTime = (percent*0.01)* this.duration; | |||
this.$store.commit('setChangeTime',newCurTime); | |||
}, | |||
//点击播放条切换播放进度 | |||
updatemove(e){ | |||
if(!this.tag){ | |||
//进度条的左侧坐标 | |||
let curLength = this.$refs.bg.offsetLeft; | |||
let newPercent = ((e.clientX - curLength) / this.progressLength) * 100; | |||
if(newPercent>100){ | |||
newPercent = 100; | |||
}else if(newPercent<0){ | |||
newPercent = 0; | |||
} | |||
this.curLength = newPercent; | |||
this.changeTime(newPercent); | |||
} | |||
}, | |||
//显示播放中的歌曲列表 | |||
changeAside(){ | |||
this.$store.commit('setShowAside',true); | |||
}, | |||
//上一首 | |||
prev(){ | |||
if(this.listIndex != -1 && this.listOfSongs.length > 1){ //当前处于不可能状态或者只有只有一首音乐的时候不执行) | |||
if(this.listIndex > 0){ //不是第一首音乐 | |||
this.$store.commit('setListIndex',this.listIndex - 1); //直接返回上一首 | |||
}else{ //当前是第一首音乐 | |||
this.$store.commit('setListIndex',this.listOfSongs.length - 1); //切换到倒数第一首 | |||
} | |||
this.toplay(this.listOfSongs[this.listIndex].url); | |||
} | |||
}, | |||
//下一首 | |||
next(){ | |||
if(this.listIndex != -1 && this.listOfSongs.length > 1){ //当前处于不可能状态或者只有只有一首音乐的时候不执行) | |||
if(this.listIndex < this.listOfSongs.length - 1){ //不是最后一首音乐 | |||
this.$store.commit('setListIndex',this.listIndex + 1); //直接返回下一首 | |||
}else{ //当前是最后一首音乐 | |||
this.$store.commit('setListIndex',0); //切换到第一首 | |||
} | |||
this.toplay(this.listOfSongs[this.listIndex].url); | |||
} | |||
}, | |||
//播放音乐 | |||
toplay: function(url){ | |||
if(url && url != this.url){ | |||
this.$store.commit('setId',this.listOfSongs[this.listIndex].id); | |||
this.$store.commit('setUrl',this.$store.state.configure.HOST+url); | |||
this.$store.commit('setPicUrl',this.$store.state.configure.HOST+this.listOfSongs[this.listIndex].pic); | |||
this.$store.commit('setTitle',this.replaceFName(this.listOfSongs[this.listIndex].name)); | |||
this.$store.commit('setArtist',this.replaceLName(this.listOfSongs[this.listIndex].name)); | |||
this.$store.commit('setLyric',this.parseLyric(this.listOfSongs[this.listIndex].lyric)); | |||
this.$store.commit('setIsActive',false); | |||
if(this.loginIn){ | |||
getCollectOfUserId(this.userId) | |||
.then(res =>{ | |||
for(let item of res){ | |||
if(item.songId == id){ | |||
this.$store.commit('setIsActive',true); | |||
break; | |||
} | |||
} | |||
}) | |||
} | |||
} | |||
}, | |||
//获取名字前半部分--歌手名 | |||
replaceLName(str){ | |||
let arr = str.split('-'); | |||
return arr[0]; | |||
}, | |||
//获取名字后半部分--歌名 | |||
replaceFName(str){ | |||
let arr = str.split('-'); | |||
return arr[1]; | |||
}, | |||
//解析歌词 | |||
parseLyric(text){ | |||
let lines = text.split("\n"); //将歌词按行分解成数组 | |||
let pattern = /\[\d{2}:\d{2}.(\d{3}|\d{2})\]/g; //时间格式的正则表达式 | |||
let result = []; //返回值 | |||
//对于歌词格式不对的直接返回 | |||
if(!(/\[.+\]/.test(text))){ | |||
return [[0,text]] | |||
} | |||
//去掉前面格式不正确的行 | |||
while(!pattern.test(lines[0])){ | |||
lines = lines.slice(1); | |||
} | |||
//遍历每一行,形成一个每行带着俩元素的数组,第一个元素是以秒为计算单位的时间,第二个元素是歌词 | |||
for(let item of lines){ | |||
let time = item.match(pattern); //存前面的时间段 | |||
let value = item.replace(pattern,'');//存后面的歌词 | |||
for(let item1 of time){ | |||
let t = item1.slice(1,-1).split(":"); //取出时间,换算成数组 | |||
if(value!=''){ | |||
result.push([parseInt(t[0],10)*60 + parseFloat(t[1]),value]); | |||
} | |||
} | |||
} | |||
//按照第一个元素--时间--排序 | |||
result.sort(function(a,b){ | |||
return a[0] - b[0]; | |||
}); | |||
return result; | |||
}, | |||
//转向歌词页面 | |||
toLyric(){ | |||
this.$router.push({path: `/lyric`}); | |||
}, | |||
//下载音乐 | |||
download() { | |||
download(this.url) | |||
.then(res=>{ | |||
let content = res.data; | |||
let eleLink = document.createElement('a'); | |||
eleLink.download = `${this.artist}-${this.title}.mp3`; | |||
eleLink.style.display = 'none'; | |||
//把字符内容转换成blob地址 | |||
let blob = new Blob([content]); | |||
eleLink.href = URL.createObjectURL(blob); | |||
//把链接地址加到document里 | |||
document.body.appendChild(eleLink); | |||
//触发点击 | |||
eleLink.click(); | |||
//然后移除掉这个新加的控件 | |||
document.body.removeChild(eleLink); | |||
}) | |||
.catch(err =>{ | |||
console.log(err); | |||
}) | |||
}, | |||
//收藏 | |||
collection() { | |||
if(this.loginIn){ | |||
var params = new URLSearchParams(); | |||
params.append('userId',this.userId); | |||
params.append('type',0); | |||
params.append('songId',this.id); | |||
setCollect(params) | |||
.then(res =>{ | |||
if(res.code == 1){ | |||
this.$store.commit('setIsActive',true); | |||
this.notify('收藏成功','success'); | |||
}else if(res.code == 2){ | |||
this.notify('已收藏','warning'); | |||
}else{ | |||
this.notify('收藏失败','error'); | |||
} | |||
}) | |||
}else{ | |||
this.notify('请先登录','warning'); | |||
} | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import '../assets/css/play-bar.scss'; | |||
</style> |
@ -0,0 +1,19 @@ | |||
<template> | |||
<div class="scroll-top" @click="returnTop"> | |||
<div class="box-in"></div> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'scroll-top', | |||
methods: { | |||
returnTop() { | |||
document.documentElement.scrollTop = document.body.scrollTop = 0; | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import '../assets/css/scroll-top.scss'; | |||
</style> |
@ -0,0 +1,79 @@ | |||
<template> | |||
<div class="song-audio"> | |||
<audio ref="player" | |||
:src="url" | |||
controls = "controls" | |||
preload = "true" | |||
@canplay="startPlay" | |||
@ended="ended" | |||
@timeupdate="timeupdate" | |||
></audio> | |||
</div> | |||
</template> | |||
<script> | |||
import {mapGetters} from 'vuex'; | |||
export default { | |||
name: 'song-audio', | |||
computed: { | |||
...mapGetters([ | |||
'id', //歌曲id | |||
'url', //歌曲地址 | |||
'isPlay', //播放状态 | |||
'listOfSongs', //当前歌曲列表 | |||
'curTime', //当前音乐的播放位置 | |||
'changeTime', //指定播放时刻 | |||
'autoNext', //用于自动触发播放下一首 | |||
'volume' //音量 | |||
]) | |||
}, | |||
watch:{ | |||
//监听播放还是暂停 | |||
isPlay(){ | |||
this.togglePlay(); | |||
}, | |||
//跳转到指定播放时刻 | |||
changeTime(){ | |||
this.$refs.player.currentTime = this.changeTime; | |||
}, | |||
//改变音量 | |||
volume(val){ | |||
this.$refs.player.volume = val; | |||
} | |||
}, | |||
methods:{ | |||
//获取链接后准备播放 | |||
startPlay(){ | |||
let player = this.$refs.player; | |||
this.$store.commit('setDuration',player.duration); | |||
//开始播放 | |||
player.play(); | |||
this.$store.commit('setIsPlay',true); | |||
}, | |||
//播放完成之后触发 | |||
ended(){ | |||
this.$store.commit('setIsPlay',false); | |||
this.$store.commit('setCurTime',0); | |||
this.$store.commit('setAutoNext',!this.autoNext); | |||
}, | |||
//开始、暂停 | |||
togglePlay() { | |||
let player = this.$refs.player; | |||
if(this.isPlay){ | |||
player.play(); | |||
}else{ | |||
player.pause(); | |||
} | |||
}, | |||
//音乐播放时记录音乐的播放位置 | |||
timeupdate(){ | |||
this.$store.commit('setCurTime',this.$refs.player.currentTime); | |||
} | |||
} | |||
} | |||
</script> | |||
<style> | |||
.song-audio { | |||
display: none; | |||
} | |||
</style> |
@ -0,0 +1,28 @@ | |||
<template> | |||
<div class="swiper"> | |||
<el-carousel :interval="4000" type="card" height="280px"> | |||
<el-carousel-item v-for="(item,index) in swiperList" :key="index"> | |||
<img :src="item.picImg" /> | |||
</el-carousel-item> | |||
</el-carousel> | |||
</div> | |||
</template> | |||
<script> | |||
import {swiperList} from "../assets/data/swiper"; | |||
export default { | |||
name: "swiper", | |||
data() { | |||
return { | |||
swiperList: [] | |||
} | |||
}, | |||
created () { | |||
this.swiperList = swiperList; | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import '../assets/css/swiper.scss'; | |||
</style> |
@ -0,0 +1,104 @@ | |||
<template> | |||
<transition name="slide-fade"> | |||
<div class="the-aside" v-if="showAside"> | |||
<h2 class="title">播放列表</h2> | |||
<ul class="menus"> | |||
<li v-for="(item,index) in listOfSongs" :key="index" :class="{'is-play': id==item.id}" | |||
@click="toplay(item.id,item.url,item.pic,item.index,item.name,item.lyric)"> | |||
{{replaceFName(item.name)}} | |||
</li> | |||
</ul> | |||
</div> | |||
</transition> | |||
</template> | |||
<script> | |||
import {mapGetters} from 'vuex'; | |||
import { getCollectOfUserId } from '../api/index'; | |||
export default { | |||
name: 'the-aside', | |||
computed: { | |||
...mapGetters([ | |||
'showAside', //是否显示播放中的歌曲列表 | |||
'listOfSongs', //当前歌曲列表 | |||
'id', //播放中的音乐id | |||
'loginIn', //用户是否已登录 | |||
'userId', //当前登录用户的id | |||
'isActive', //当前播放的歌曲是否已收藏 | |||
]) | |||
}, | |||
mounted(){ | |||
let _this = this; | |||
document.addEventListener('click',function(){ | |||
_this.$store.commit('setShowAside',false) | |||
},true); | |||
}, | |||
methods:{ | |||
//获取名字前半部分--歌手名 | |||
replaceLName(str){ | |||
let arr = str.split('-'); | |||
return arr[0]; | |||
}, | |||
//获取名字后半部分--歌名 | |||
replaceFName(str){ | |||
let arr = str.split('-'); | |||
return arr[1]; | |||
}, | |||
//播放 | |||
toplay: function(id,url,pic,index,name,lyric){ | |||
this.$store.commit('setId',id); | |||
this.$store.commit('setUrl',this.$store.state.configure.HOST+url); | |||
this.$store.commit('setPicUrl',this.$store.state.configure.HOST+pic); | |||
this.$store.commit('setListIndex',index); | |||
this.$store.commit('setTitle',this.replaceFName(name)); | |||
this.$store.commit('setArtist',this.replaceLName(name)); | |||
this.$store.commit('setLyric',this.parseLyric(lyric)); | |||
this.$store.commit('setIsActive',false); | |||
if(this.loginIn){ | |||
getCollectOfUserId(this.userId) | |||
.then(res =>{ | |||
for(let item of res){ | |||
if(item.songId == id){ | |||
this.$store.commit('setIsActive',true); | |||
break; | |||
} | |||
} | |||
}) | |||
} | |||
}, | |||
//解析歌词 | |||
parseLyric(text){ | |||
let lines = text.split("\n"); //将歌词按行分解成数组 | |||
let pattern = /\[\d{2}:\d{2}.(\d{3}|\d{2})\]/g; //时间格式的正则表达式 | |||
let result = []; //返回值 | |||
//对于歌词格式不对的直接返回 | |||
if(!(/\[.+\]/.test(text))){ | |||
return [[0,text]] | |||
} | |||
//去掉前面格式不正确的行 | |||
while(!pattern.test(lines[0])){ | |||
lines = lines.slice(1); | |||
} | |||
//遍历每一行,形成一个每行带着俩元素的数组,第一个元素是以秒为计算单位的时间,第二个元素是歌词 | |||
for(let item of lines){ | |||
let time = item.match(pattern); //存前面的时间段 | |||
let value = item.replace(pattern,'');//存后面的歌词 | |||
for(let item1 of time){ | |||
let t = item1.slice(1,-1).split(":"); //取出时间,换算成数组 | |||
if(value!=''){ | |||
result.push([parseInt(t[0],10)*60 + parseFloat(t[1]),value]); | |||
} | |||
} | |||
} | |||
//按照第一个元素--时间--排序 | |||
result.sort(function(a,b){ | |||
return a[0] - b[0]; | |||
}); | |||
return result; | |||
}, | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import '../assets/css/the-aside.scss'; | |||
</style> |
@ -0,0 +1,14 @@ | |||
<template> | |||
<div class="the-footer"> | |||
<p>关于 | 帮助 |条款 |反馈</p> | |||
<p>Copyright ©2020</p> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'the-footer' | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import '../assets/css/the-footer.scss'; | |||
</style> |
@ -0,0 +1,119 @@ | |||
<template> | |||
<div class="the-header"> | |||
<div class="header-logo" @click="goHome"> | |||
<svg class="icon"> | |||
<use xlink:href = "#icon-erji"></use> | |||
</svg> | |||
<span>music</span> | |||
</div> | |||
<ul class="navbar"> | |||
<li :class="{active: item.name == activeName}" v-for="item in navMsg" :key="item.path" @click="goPage(item.path,item.name)"> | |||
{{item.name}} | |||
</li> | |||
<li> | |||
<div class="header-search"> | |||
<input type="text" placeholder="搜索音乐" @keyup.enter="goSearch()" v-model="keywords"> | |||
<div class="search-btn" @click="goSearch()"> | |||
<svg class="icon"> | |||
<use xlink:href = "#icon-sousuo"></use> | |||
</svg> | |||
</div> | |||
</div> | |||
</li> | |||
<li v-show="!loginIn" :class="{active: item.name == activeName}" v-for="item in loginMsg" :key="item.path" @click="goPage(item.path,item.name)"> | |||
{{item.name}} | |||
</li> | |||
</ul> | |||
<div class="header-right" v-show="loginIn"> | |||
<div id='user'> | |||
<img :src='attachImageUrl(avator)'> | |||
</div> | |||
<ul class="menu"> | |||
<li v-for="(item,index) in menuList" :key="index" @click="goMenuList(item.path)">{{item.name}}</li> | |||
</ul> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import {mapGetters} from 'vuex'; | |||
import {navMsg,loginMsg,menuList} from '../assets/data/header'; | |||
export default { | |||
name: 'the-header', | |||
data() { | |||
return { | |||
navMsg: [], //左侧导航栏 | |||
keywords: '', //搜索关键字 | |||
loginMsg: [], //右侧导航栏 | |||
menuList: [], //用户下拉菜单 | |||
} | |||
}, | |||
computed:{ | |||
...mapGetters([ | |||
'activeName', | |||
'loginIn', | |||
'avator' | |||
]) | |||
}, | |||
created() { | |||
this.navMsg = navMsg; | |||
this.loginMsg = loginMsg; | |||
this.menuList = menuList; | |||
}, | |||
mounted(){ | |||
document.querySelector('#user').addEventListener('click',function(e){ | |||
document.querySelector('.menu').classList.add("show"); | |||
e.stopPropagation() //关键在于阻止冒泡 | |||
},false); | |||
document.querySelector('.menu').addEventListener('click',function(e){ | |||
e.stopPropagation() //点击菜单内部时,阻止时间冒泡,这样,点击内部时,菜单不会关闭 | |||
},false); | |||
document.addEventListener('click',function(){ | |||
document.querySelector('.menu').classList.remove('show'); | |||
},false); | |||
}, | |||
methods: { | |||
//提示信息 | |||
notify(title,type) { | |||
this.$notify({ | |||
title: title, | |||
type: type | |||
}) | |||
}, | |||
goHome() { | |||
this.$router.push({path: "/"}); | |||
}, | |||
goPage(path,name) { | |||
if(!this.loginIn && path=='/my-music'){ | |||
this.notify('请先登录','warning'); | |||
}else{ | |||
this.$store.commit('setActiveName',name); | |||
this.$router.push({path: path}); | |||
} | |||
}, | |||
goSearch(){ | |||
this.$router.push({path:'/search',query:{keywords: this.keywords}}) | |||
}, | |||
//获取图片地址 | |||
attachImageUrl (srcUrl) { | |||
return srcUrl? this.$store.state.configure.HOST+srcUrl : '../assets/img/user.jpg'; | |||
}, | |||
goMenuList(path){ | |||
if(path == 0){ | |||
this.$store.commit('setLoginIn',false); | |||
this.$store.commit('setIsActive',false); | |||
this.$router.go(0); | |||
}else{ | |||
this.$router.push({path:path}); | |||
} | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import '../assets/css/the-header.scss'; | |||
</style> |
@ -0,0 +1,62 @@ | |||
<template> | |||
<div class="upload"> | |||
<p class="title">修改头像</p> | |||
<hr/> | |||
<div class="section"> | |||
<el-upload drag :action="uploadUrl()" :show-file-list="false" :on-success="handleAvatorSuccess" | |||
:before-upload="beforeAvatorUpload"> | |||
<i class="el-icon-upload"></i> | |||
<div> | |||
将文件拖到此处,或<span style="color:blue">修改头像</span> | |||
</div> | |||
<div slot="tip">只能上传jpg/png文件,且不能超过10MB</div> | |||
</el-upload> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import {mapGetters} from 'vuex' | |||
import {mixin} from '../mixins' | |||
export default { | |||
name: 'upload', | |||
mixins: [mixin], | |||
computed:{ | |||
...mapGetters([ | |||
'userId' | |||
]) | |||
}, | |||
methods:{ | |||
//上传地址 | |||
uploadUrl(){ | |||
return `${this.$store.state.configure.HOST}/consumer/updateConsumerPic?id=${this.userId}` | |||
}, | |||
//上传成功 | |||
handleAvatorSuccess(res,file){ | |||
if(res.code == 1){ | |||
this.$store.commit('setAvator',res.avator); | |||
this.notify('修改成功','success'); | |||
}else{ | |||
this.notify('修改失败','error'); | |||
} | |||
}, | |||
//上传之前的校验 | |||
beforeAvatorUpload(file){ | |||
const isJPG = file.type=='image/jpeg'; | |||
const isLt10M = file.size /1024/1024<10; | |||
if(!isJPG){ | |||
this.notify('上传头像图片只能是JPG格式','error'); | |||
return false; | |||
} | |||
if(!isLt10M){ | |||
this.notify('上传头像图片不能大于10MB','error'); | |||
return false; | |||
} | |||
return true; | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import '../assets/css/upload.scss'; | |||
</style> |
@ -0,0 +1,45 @@ | |||
<template> | |||
<div class="search-song-Lists"> | |||
<content-list :contentList="albumDatas"></content-list> | |||
</div> | |||
</template> | |||
<script> | |||
import ContentList from '../ContentList'; | |||
import {getSongListOfLikeTitle} from '../../api/index'; | |||
import {mixin} from "../../mixins"; | |||
export default { | |||
name: 'search-song-lists', | |||
components:{ | |||
ContentList | |||
}, | |||
data(){ | |||
return{ | |||
albumDatas: [] | |||
} | |||
}, | |||
mounted(){ | |||
this.getSearchList(); | |||
}, | |||
methods:{ | |||
getSearchList(){ | |||
if(!this.$route.query.keywords){ | |||
this.notify('您输入的内容为空','warning') | |||
}else{ | |||
getSongListOfLikeTitle(this.$route.query.keywords) | |||
.then(res =>{ | |||
if(res){ | |||
this.albumDatas = res | |||
}else{ | |||
this.notify('暂无该歌曲内容','warning') | |||
} | |||
}) | |||
} | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import '../../assets/css/search-song-lists.scss'; | |||
</style> |
@ -0,0 +1,30 @@ | |||
<template> | |||
<div class="search-songs"> | |||
<album-content :songList="listOfSongs"></album-content> | |||
</div> | |||
</template> | |||
<script> | |||
import {mapGetters} from "vuex"; | |||
import {mixin} from "../../mixins"; | |||
import AlbumContent from "../AlbumContent"; | |||
export default { | |||
name: 'search-songs', | |||
components:{ | |||
AlbumContent | |||
}, | |||
mixins: [mixin], | |||
computed:{ | |||
...mapGetters([ | |||
'listOfSongs' | |||
]) | |||
}, | |||
mounted() { | |||
this.getSong(); | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import '../../assets/css/search-songs.scss'; | |||
</style> |
@ -0,0 +1,24 @@ | |||
import Vue from 'vue' | |||
import App from './App' | |||
import router from './router' | |||
import store from './store/index' | |||
import './assets/css/index.scss' | |||
import ElementUI from 'element-ui' | |||
import 'element-ui/lib/theme-chalk/index.css' | |||
import '@/assets/js/iconfont.js' | |||
import '@/assets/js/iconfont1.js' | |||
import '@/assets/js/iconfont2.js' | |||
import '@/assets/js/iconfont3.js' | |||
Vue.use(ElementUI) | |||
Vue.config.productionTip = false | |||
/* eslint-disable no-new */ | |||
new Vue({ | |||
el: '#app', | |||
router, | |||
store, | |||
components: { App }, | |||
template: '<App/>' | |||
}) |
@ -0,0 +1,109 @@ | |||
import {mapGetters} from 'vuex'; | |||
import {likeSongOfName,getCollectOfUserId } from '../api/index'; | |||
export const mixin = { | |||
computed: { | |||
...mapGetters([ | |||
'loginIn', //用户是否已登录 | |||
'userId', //当前登录用户的id | |||
]) | |||
}, | |||
methods: { | |||
//提示信息 | |||
notify(title,type) { | |||
this.$notify({ | |||
title: title, | |||
type: type | |||
}) | |||
}, | |||
//获取图片地址 | |||
attachImageUrl (srcUrl) { | |||
return srcUrl? this.$store.state.configure.HOST+srcUrl : this.$store.state.configure.HOST+'/img/user.jpg'; | |||
}, | |||
//根据歌手名字模糊查询歌曲 | |||
getSong() { | |||
if(!this.$route.query.keywords){ | |||
this.$store.commit('setListOfSongs',[]); | |||
this.notify('您输入的内容为空','warning'); | |||
}else{ | |||
likeSongOfName(this.$route.query.keywords).then(res => { | |||
if(!res.length){ | |||
this.$store.commit('setListOfSongs',[]); | |||
this.notify('系统暂无符合条件的歌曲','warning'); | |||
}else{ | |||
this.$store.commit('setListOfSongs',res); | |||
} | |||
}).catch(err => { | |||
console.log(err) | |||
}) | |||
} | |||
}, | |||
//获取名字前半部分--歌手名 | |||
replaceLName(str){ | |||
let arr = str.split('-'); | |||
return arr[0]; | |||
}, | |||
//获取名字后半部分--歌名 | |||
replaceFName(str){ | |||
let arr = str.split('-'); | |||
return arr[1]; | |||
}, | |||
//播放 | |||
toplay: function(id,url,pic,index,name,lyric){ | |||
this.$store.commit('setId',id); | |||
this.$store.commit('setUrl',this.$store.state.configure.HOST+url); | |||
this.$store.commit('setPicUrl',this.$store.state.configure.HOST+pic); | |||
this.$store.commit('setListIndex',index); | |||
this.$store.commit('setTitle',this.replaceFName(name)); | |||
this.$store.commit('setArtist',this.replaceLName(name)); | |||
this.$store.commit('setLyric',this.parseLyric(lyric)); | |||
this.$store.commit('setIsActive',false); | |||
if(this.loginIn){ | |||
getCollectOfUserId(this.userId) | |||
.then(res =>{ | |||
for(let item of res){ | |||
if(item.songId == id){ | |||
this.$store.commit('setIsActive',true); | |||
break; | |||
} | |||
} | |||
}) | |||
} | |||
}, | |||
//解析歌词 | |||
parseLyric(text){ | |||
let lines = text.split("\n"); //将歌词按行分解成数组 | |||
let pattern = /\[\d{2}:\d{2}.(\d{3}|\d{2})\]/g; //时间格式的正则表达式 | |||
let result = []; //返回值 | |||
//对于歌词格式不对的直接返回 | |||
if(!(/\[.+\]/.test(text))){ | |||
return [[0,text]] | |||
} | |||
//去掉前面格式不正确的行 | |||
while(!pattern.test(lines[0])){ | |||
lines = lines.slice(1); | |||
} | |||
//遍历每一行,形成一个每行带着俩元素的数组,第一个元素是以秒为计算单位的时间,第二个元素是歌词 | |||
for(let item of lines){ | |||
let time = item.match(pattern); //存前面的时间段 | |||
let value = item.replace(pattern,'');//存后面的歌词 | |||
for(let item1 of time){ | |||
let t = item1.slice(1,-1).split(":"); //取出时间,换算成数组 | |||
if(value!=''){ | |||
result.push([parseInt(t[0],10)*60 + parseFloat(t[1]),value]); | |||
} | |||
} | |||
} | |||
//按照第一个元素--时间--排序 | |||
result.sort(function(a,b){ | |||
return a[0] - b[0]; | |||
}); | |||
return result; | |||
}, | |||
//获取生日 | |||
attachBirth(val){ | |||
return val.substr(0,10); | |||
} | |||
} | |||
} |
@ -0,0 +1,54 @@ | |||
<template> | |||
<div class="home"> | |||
<swiper /> | |||
<div class="section" v-for="(item,index) in songsList" :key="index"> | |||
<div class="section-title">{{item.name}}</div> | |||
<content-list :contentList="item.list"></content-list> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import Swiper from "../components/Swiper"; | |||
import contentList from '../components/ContentList'; | |||
import {getAllSinger,getAllSongList} from '../api/index'; | |||
export default { | |||
name: 'home', | |||
components: { | |||
Swiper, | |||
contentList | |||
}, | |||
data () { | |||
return { | |||
songsList: [ | |||
{name:"歌单",list: []}, | |||
{name:"歌手",list: []} | |||
] | |||
} | |||
}, | |||
created () { | |||
this.getSongList(); | |||
this.getSinger(); | |||
}, | |||
methods: { | |||
getSongList(){ //获取前十条歌单 | |||
getAllSongList().then((res) => { | |||
this.songsList[0].list = res.slice(0,10); | |||
}).catch((err) => { | |||
console.log(err); | |||
}) | |||
}, | |||
getSinger(){ //获取前十名歌手 | |||
getAllSinger().then((res) => { | |||
this.songsList[1].list = res.slice(0,10); | |||
}).catch((err) => { | |||
console.log(err); | |||
}) | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import '../assets/css/home.scss'; | |||
</style> |
@ -0,0 +1,93 @@ | |||
<template> | |||
<div> | |||
<loginLogo/> | |||
<div class="signUp"> | |||
<div class="signUp-head"> | |||
<span>帐号登录</span> | |||
</div> | |||
<el-form :model="loginForm" ref="loginForm" label-width="70px" class="demo-ruleForm" :rules="rules"> | |||
<el-form-item prop="username" label="用户名"> | |||
<el-input v-model="loginForm.username" placeholder="用户名"></el-input> | |||
</el-form-item> | |||
<el-form-item prop="password" label="密码"> | |||
<el-input type="password" v-model="loginForm.password" placeholder="密码"></el-input> | |||
</el-form-item> | |||
<div class="login-btn"> | |||
<el-button @click="goSignUp">注册</el-button> | |||
<el-button type="primary" @click="handleLoginIn">登录</el-button> | |||
</div> | |||
</el-form> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import loginLogo from '../components/LoginLogo' | |||
import {mixin} from '../mixins' | |||
import {loginIn} from '../api/index' | |||
export default { | |||
name: 'login-in', | |||
mixins: [mixin], | |||
components: { | |||
loginLogo | |||
}, | |||
data() { | |||
return { | |||
loginForm: { | |||
username: '', //用户名 | |||
password: '', //密码 | |||
}, | |||
rules: { | |||
username: [ | |||
{ required: true, trigger: 'blur',message: '请输入用户名' } | |||
], | |||
password: [ | |||
{ required: true, trigger: 'blur',message: '请输入密码' } | |||
] | |||
} | |||
} | |||
}, | |||
mounted() { | |||
this.changeIndex('登录'); | |||
}, | |||
methods:{ | |||
handleLoginIn(){ | |||
let _this = this; | |||
let params = new URLSearchParams(); | |||
params.append('username',this.loginForm.username); | |||
params.append('password',this.loginForm.password); | |||
loginIn(params) | |||
.then(res => { | |||
if(res.code == 1){ | |||
_this.notify('登录成功','success'); | |||
_this.$store.commit('setLoginIn',true); | |||
_this.$store.commit('setUserId',res.userMsg.id); | |||
_this.$store.commit('setUsername',res.userMsg.username); | |||
_this.$store.commit('setAvator',res.userMsg.avator); | |||
setTimeout(function(){ | |||
_this.changeIndex('首页'); | |||
_this.$router.push({path: '/'}); | |||
},2000); | |||
}else{ | |||
_this.notify('用户名或密码错误','error'); | |||
} | |||
}) | |||
.catch(err => { | |||
_this.notify('用户名或密码错误','error'); | |||
}) | |||
}, | |||
goSignUp(){ | |||
this.changeIndex('注册'); | |||
this.$router.push({path: '/sign-up'}); | |||
}, | |||
changeIndex(value){ | |||
this.$store.commit('setActiveName',value); | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import '../assets/css/sign-up.scss'; | |||
</style> |