Browse Source

client

master
朱奕帆 4 years ago
parent
commit
a40b8a3e26
122 changed files with 23061 additions and 0 deletions
  1. +0
    -0
      img-folder/.keep
  2. BIN
      img-folder/2.png
  3. BIN
      img-folder/3.png
  4. BIN
      img-folder/4.png
  5. BIN
      img-folder/5.png
  6. BIN
      img-folder/6.png
  7. BIN
      img-folder/屏幕截图 2021-01-17 111737.png
  8. +18
    -0
      music-client/.babelrc
  9. +9
    -0
      music-client/.editorconfig
  10. +5
    -0
      music-client/.eslintignore
  11. +29
    -0
      music-client/.eslintrc.js
  12. +0
    -0
      music-client/.keep
  13. +10
    -0
      music-client/.postcssrc.js
  14. +30
    -0
      music-client/README.md
  15. +41
    -0
      music-client/build/build.js
  16. +54
    -0
      music-client/build/check-versions.js
  17. +101
    -0
      music-client/build/utils.js
  18. +22
    -0
      music-client/build/vue-loader.conf.js
  19. +92
    -0
      music-client/build/webpack.base.conf.js
  20. +96
    -0
      music-client/build/webpack.dev.conf.js
  21. +149
    -0
      music-client/build/webpack.prod.conf.js
  22. +7
    -0
      music-client/config/dev.env.js
  23. +76
    -0
      music-client/config/index.js
  24. +4
    -0
      music-client/config/prod.env.js
  25. +7
    -0
      music-client/config/test.env.js
  26. +11
    -0
      music-client/index.html
  27. +17544
    -0
      music-client/package-lock.json
  28. +91
    -0
      music-client/package.json
  29. +36
    -0
      music-client/src/App.vue
  30. +67
    -0
      music-client/src/api/http.js
  31. +74
    -0
      music-client/src/api/index.js
  32. +16
    -0
      music-client/src/assets/css/404.scss
  33. +55
    -0
      music-client/src/assets/css/album-content.scss
  34. +11
    -0
      music-client/src/assets/css/app.scss
  35. +74
    -0
      music-client/src/assets/css/comment.scss
  36. +67
    -0
      music-client/src/assets/css/content-list.scss
  37. +32
    -0
      music-client/src/assets/css/global.scss
  38. +22
    -0
      music-client/src/assets/css/home.scss
  39. +43
    -0
      music-client/src/assets/css/index.scss
  40. +41
    -0
      music-client/src/assets/css/info.scss
  41. +28
    -0
      music-client/src/assets/css/login-in.scss
  42. +15
    -0
      music-client/src/assets/css/login-logo.scss
  43. +54
    -0
      music-client/src/assets/css/lyric.scss
  44. +69
    -0
      music-client/src/assets/css/my-music.scss
  45. +115
    -0
      music-client/src/assets/css/play-bar.scss
  46. +41
    -0
      music-client/src/assets/css/scroll-top.scss
  47. +5
    -0
      music-client/src/assets/css/search-song-Lists.scss
  48. +3
    -0
      music-client/src/assets/css/search-songs.scss
  49. +27
    -0
      music-client/src/assets/css/search.scss
  50. +60
    -0
      music-client/src/assets/css/setting.scss
  51. +27
    -0
      music-client/src/assets/css/sign-up.scss
  52. +68
    -0
      music-client/src/assets/css/singer-album.scss
  53. +38
    -0
      music-client/src/assets/css/singer.scss
  54. +3
    -0
      music-client/src/assets/css/song-audio.scss
  55. +88
    -0
      music-client/src/assets/css/song-list-album.scss
  56. +34
    -0
      music-client/src/assets/css/song-list.scss
  57. +8
    -0
      music-client/src/assets/css/swiper.scss
  58. +65
    -0
      music-client/src/assets/css/the-aside.scss
  59. +12
    -0
      music-client/src/assets/css/the-footer.scss
  60. +143
    -0
      music-client/src/assets/css/the-header.scss
  61. +21
    -0
      music-client/src/assets/css/upload.scss
  62. +60
    -0
      music-client/src/assets/css/var.scss
  63. +138
    -0
      music-client/src/assets/data/form.js
  64. +26
    -0
      music-client/src/assets/data/header.js
  65. +8
    -0
      music-client/src/assets/data/singer.js
  66. +11
    -0
      music-client/src/assets/data/songList.js
  67. +15
    -0
      music-client/src/assets/data/swiper.js
  68. BIN
      music-client/src/assets/img/swiper/1.jpg
  69. BIN
      music-client/src/assets/img/swiper/2.jpg
  70. BIN
      music-client/src/assets/img/swiper/3.jpg
  71. BIN
      music-client/src/assets/img/swiper/4.jpg
  72. BIN
      music-client/src/assets/img/swiper/5.jpg
  73. BIN
      music-client/src/assets/img/swiper/6.jpg
  74. BIN
      music-client/src/assets/img/swiper/7.jpg
  75. BIN
      music-client/src/assets/img/swiper/8.jpg
  76. BIN
      music-client/src/assets/img/tubiao.jpg
  77. BIN
      music-client/src/assets/img/user.jpg
  78. +1
    -0
      music-client/src/assets/js/iconfont.js
  79. +1
    -0
      music-client/src/assets/js/iconfont1.js
  80. +1
    -0
      music-client/src/assets/js/iconfont2.js
  81. +1
    -0
      music-client/src/assets/js/iconfont3.js
  82. +43
    -0
      music-client/src/components/AlbumContent.vue
  83. +153
    -0
      music-client/src/components/Comment.vue
  84. +38
    -0
      music-client/src/components/ContentList.vue
  85. +143
    -0
      music-client/src/components/Info.vue
  86. +16
    -0
      music-client/src/components/LoginLogo.vue
  87. +396
    -0
      music-client/src/components/PlayBar.vue
  88. +19
    -0
      music-client/src/components/ScrollTop.vue
  89. +79
    -0
      music-client/src/components/SongAudio.vue
  90. +28
    -0
      music-client/src/components/Swiper.vue
  91. +104
    -0
      music-client/src/components/TheAside.vue
  92. +14
    -0
      music-client/src/components/TheFooter.vue
  93. +119
    -0
      music-client/src/components/TheHeader.vue
  94. +62
    -0
      music-client/src/components/Upload.vue
  95. +45
    -0
      music-client/src/components/search/SearchSongLists.vue
  96. +30
    -0
      music-client/src/components/search/SearchSongs.vue
  97. +24
    -0
      music-client/src/main.js
  98. +109
    -0
      music-client/src/mixins/index.js
  99. +54
    -0
      music-client/src/pages/Home.vue
  100. +93
    -0
      music-client/src/pages/LoginIn.vue

+ 0
- 0
img-folder/.keep View File


BIN
img-folder/2.png View File

Before After
Width: 738  |  Height: 259  |  Size: 30 KiB

BIN
img-folder/3.png View File

Before After
Width: 720  |  Height: 360  |  Size: 41 KiB

BIN
img-folder/4.png View File

Before After
Width: 1138  |  Height: 250  |  Size: 52 KiB

BIN
img-folder/5.png View File

Before After
Width: 906  |  Height: 256  |  Size: 47 KiB

BIN
img-folder/6.png View File

Before After
Width: 1219  |  Height: 259  |  Size: 59 KiB

BIN
img-folder/屏幕截图 2021-01-17 111737.png View File

Before After
Width: 1239  |  Height: 514  |  Size: 71 KiB

+ 18
- 0
music-client/.babelrc View File

@ -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"]
}
}
}

+ 9
- 0
music-client/.editorconfig View File

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

+ 5
- 0
music-client/.eslintignore View File

@ -0,0 +1,5 @@
/build/
/config/
/dist/
/*.js
/test/unit/coverage/

+ 29
- 0
music-client/.eslintrc.js View File

@ -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
music-client/.keep View File


+ 10
- 0
music-client/.postcssrc.js View File

@ -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": {}
}
}

+ 30
- 0
music-client/README.md View File

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

+ 41
- 0
music-client/build/build.js View File

@ -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'
))
})
})

+ 54
- 0
music-client/build/check-versions.js View File

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

+ 101
- 0
music-client/build/utils.js View File

@ -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')
})
}
}

+ 22
- 0
music-client/build/vue-loader.conf.js View File

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

+ 92
- 0
music-client/build/webpack.base.conf.js View File

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

+ 96
- 0
music-client/build/webpack.dev.conf.js View File

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

+ 149
- 0
music-client/build/webpack.prod.conf.js View File

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

+ 7
- 0
music-client/config/dev.env.js View File

@ -0,0 +1,7 @@
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})

+ 76
- 0
music-client/config/index.js View File

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

+ 4
- 0
music-client/config/prod.env.js View File

@ -0,0 +1,4 @@
'use strict'
module.exports = {
NODE_ENV: '"production"'
}

+ 7
- 0
music-client/config/test.env.js View File

@ -0,0 +1,7 @@
'use strict'
const merge = require('webpack-merge')
const devEnv = require('./dev.env')
module.exports = merge(devEnv, {
NODE_ENV: '"testing"'
})

+ 11
- 0
music-client/index.html View File

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

+ 17544
- 0
music-client/package-lock.json
File diff suppressed because it is too large
View File


+ 91
- 0
music-client/package.json View File

@ -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"
]
}

+ 36
- 0
music-client/src/App.vue View File

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

+ 67
- 0
music-client/src/api/http.js View File

@ -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);
})
});
}

+ 74
- 0
music-client/src/api/index.js View File

@ -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}`);

+ 16
- 0
music-client/src/assets/css/404.scss View File

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

+ 55
- 0
music-client/src/assets/css/album-content.scss View File

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

+ 11
- 0
music-client/src/assets/css/app.scss View File

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

+ 74
- 0
music-client/src/assets/css/comment.scss View File

@ -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);
}

+ 67
- 0
music-client/src/assets/css/content-list.scss View File

@ -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));
}

+ 32
- 0
music-client/src/assets/css/global.scss View File

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

+ 22
- 0
music-client/src/assets/css/home.scss View File

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

+ 43
- 0
music-client/src/assets/css/index.scss View File

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

+ 41
- 0
music-client/src/assets/css/info.scss View File

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

+ 28
- 0
music-client/src/assets/css/login-in.scss View File

@ -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%;
}
}
}

+ 15
- 0
music-client/src/assets/css/login-logo.scss View File

@ -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);
}
}

+ 54
- 0
music-client/src/assets/css/lyric.scss View File

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

+ 69
- 0
music-client/src/assets/css/my-music.scss View File

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

+ 115
- 0
music-client/src/assets/css/play-bar.scss View File

@ -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);
}

+ 41
- 0
music-client/src/assets/css/scroll-top.scss View File

@ -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);
}

+ 5
- 0
music-client/src/assets/css/search-song-Lists.scss View File

@ -0,0 +1,5 @@
.search-song-Lists{
min-height: 300px;
margin-top: 50px;
}

+ 3
- 0
music-client/src/assets/css/search-songs.scss View File

@ -0,0 +1,3 @@
.search-songs {
min-height: 300px;
}

+ 27
- 0
music-client/src/assets/css/search.scss View File

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

+ 60
- 0
music-client/src/assets/css/setting.scss View File

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

+ 27
- 0
music-client/src/assets/css/sign-up.scss View File

@ -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%;
}
}
}

+ 68
- 0
music-client/src/assets/css/singer-album.scss View File

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

+ 38
- 0
music-client/src/assets/css/singer.scss View File

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

+ 3
- 0
music-client/src/assets/css/song-audio.scss View File

@ -0,0 +1,3 @@
audio {
display: none;
}

+ 88
- 0
music-client/src/assets/css/song-list-album.scss View File

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

+ 34
- 0
music-client/src/assets/css/song-list.scss View File

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

+ 8
- 0
music-client/src/assets/css/swiper.scss View File

@ -0,0 +1,8 @@
.swiper {
width: 90%;
margin: auto;
margin-top: 40px;
img {
width: 100%;
}
}

+ 65
- 0
music-client/src/assets/css/the-aside.scss View File

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

+ 12
- 0
music-client/src/assets/css/the-footer.scss View File

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

+ 143
- 0
music-client/src/assets/css/the-header.scss View File

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

+ 21
- 0
music-client/src/assets/css/upload.scss View File

@ -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);
}
}

+ 60
- 0
music-client/src/assets/css/var.scss View File

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

+ 138
- 0
music-client/src/assets/data/form.js View File

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

+ 26
- 0
music-client/src/assets/data/header.js View File

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

+ 8
- 0
music-client/src/assets/data/singer.js View File

@ -0,0 +1,8 @@
const singerStyle = [
{name: '全部歌手',type: '-1'},
{name: '男歌手',type: '1'},
{name: '女歌手',type: '0'},
{name: '组合歌手',type: '2'}
]
export {singerStyle}

+ 11
- 0
music-client/src/assets/data/songList.js View File

@ -0,0 +1,11 @@
const songStyle = [
{name:'全部歌单'},
{name:'华语'},
{name:'粤语'},
{name:'欧美'},
{name:'日韩'},
{name:'轻音乐'},
{name:'BGM'},
{name:'乐器'}
]
export {songStyle}

+ 15
- 0
music-client/src/assets/data/swiper.js View File

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

BIN
music-client/src/assets/img/swiper/1.jpg View File

Before After
Width: 640  |  Height: 360  |  Size: 23 KiB

BIN
music-client/src/assets/img/swiper/2.jpg View File

Before After
Width: 640  |  Height: 400  |  Size: 44 KiB

BIN
music-client/src/assets/img/swiper/3.jpg View File

Before After
Width: 640  |  Height: 426  |  Size: 58 KiB

BIN
music-client/src/assets/img/swiper/4.jpg View File

Before After
Width: 640  |  Height: 425  |  Size: 87 KiB

BIN
music-client/src/assets/img/swiper/5.jpg View File

Before After
Width: 640  |  Height: 426  |  Size: 45 KiB

BIN
music-client/src/assets/img/swiper/6.jpg View File

Before After
Width: 640  |  Height: 426  |  Size: 127 KiB

BIN
music-client/src/assets/img/swiper/7.jpg View File

Before After
Width: 640  |  Height: 426  |  Size: 63 KiB

BIN
music-client/src/assets/img/swiper/8.jpg View File

Before After
Width: 640  |  Height: 382  |  Size: 69 KiB

BIN
music-client/src/assets/img/tubiao.jpg View File

Before After
Width: 2000  |  Height: 2000  |  Size: 151 KiB

BIN
music-client/src/assets/img/user.jpg View File

Before After
Width: 640  |  Height: 640  |  Size: 59 KiB

+ 1
- 0
music-client/src/assets/js/iconfont.js
File diff suppressed because it is too large
View File


+ 1
- 0
music-client/src/assets/js/iconfont1.js View File

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

+ 1
- 0
music-client/src/assets/js/iconfont2.js View File

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

+ 1
- 0
music-client/src/assets/js/iconfont3.js View File

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

+ 43
- 0
music-client/src/components/AlbumContent.vue View File

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

+ 153
- 0
music-client/src/components/Comment.vue View File

@ -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' //01
],
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>

+ 38
- 0
music-client/src/components/ContentList.vue View File

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

+ 143
- 0
music-client/src/components/Info.vue View File

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

+ 16
- 0
music-client/src/components/LoginLogo.vue View File

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

+ 396
- 0
music-client/src/components/PlayBar.vue View File

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

+ 19
- 0
music-client/src/components/ScrollTop.vue View File

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

+ 79
- 0
music-client/src/components/SongAudio.vue View File

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

+ 28
- 0
music-client/src/components/Swiper.vue View File

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

+ 104
- 0
music-client/src/components/TheAside.vue View File

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

+ 14
- 0
music-client/src/components/TheFooter.vue View File

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

+ 119
- 0
music-client/src/components/TheHeader.vue View File

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

+ 62
- 0
music-client/src/components/Upload.vue View File

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

+ 45
- 0
music-client/src/components/search/SearchSongLists.vue View File

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

+ 30
- 0
music-client/src/components/search/SearchSongs.vue View File

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

+ 24
- 0
music-client/src/main.js View File

@ -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/>'
})

+ 109
- 0
music-client/src/mixins/index.js View File

@ -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);
}
}
}

+ 54
- 0
music-client/src/pages/Home.vue View File

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

+ 93
- 0
music-client/src/pages/LoginIn.vue View File

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

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save