@ -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> |