├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .postcssrc.js
├── README.md
├── build
├── build.js
├── check-versions.js
├── logo.png
├── utils.js
├── vue-loader.conf.js
├── webpack.base.conf.js
├── webpack.dev.conf.js
└── webpack.prod.conf.js
├── config
├── dev.env.js
├── index.js
└── prod.env.js
├── cursor.cur
├── favicon.ico
├── favicon.png
├── index.html
├── package-lock.json
├── package.json
├── src
├── App.vue
├── api
│ ├── search.js
│ └── song.js
├── assets
│ ├── bg.jpg
│ ├── bgvideo480.mp4
│ ├── cursor.cur
│ ├── head.jpg
│ ├── siyecao1.jpg
│ ├── siyecao2.jpg
│ ├── siyecao3.jpg
│ └── userbg.jpg
├── base
│ ├── blog-box
│ │ └── blog-box.vue
│ ├── board
│ │ └── board.vue
│ ├── player
│ │ └── player.vue
│ ├── progress-bar
│ │ └── progress-bar.vue
│ └── tags
│ │ └── tags.vue
├── blogs
│ ├── blog.md
│ ├── imgs
│ │ ├── image-20200818160039299.png
│ │ ├── image-20200818163913139-1598429442173.png
│ │ ├── image-20200818163913139.png
│ │ ├── image-20200819143501237.png
│ │ ├── image-20200820222252811.png
│ │ ├── image-20200822165552890-1598429509454.png
│ │ ├── image-20200822165552890.png
│ │ ├── image-20200822172328629.png
│ │ ├── image-20200822172553110.png
│ │ ├── image-20200822174649258.png
│ │ ├── image-20200824161718758.png
│ │ ├── image-20200826154211160.png
│ │ ├── image-20200826154240558.png
│ │ ├── image-20200826213007574.png
│ │ ├── image-20200826213021445.png
│ │ ├── image-20200826213037228.png
│ │ └── image-20200826213524137.png
│ ├── test.md
│ └── 博客主页开发日志.md
├── common
│ ├── css
│ │ └── index.css
│ └── js
│ │ ├── cache.js
│ │ ├── config.js
│ │ ├── lyric.js
│ │ ├── song.js
│ │ └── util.js
├── components
│ ├── bg
│ │ └── bg.vue
│ ├── bottom
│ │ └── bottom.vue
│ ├── center-content
│ │ └── center-content.vue
│ ├── center
│ │ └── center.vue
│ ├── left-content
│ │ └── left-content.vue
│ ├── m-header
│ │ └── m-header.vue
│ ├── main-content
│ │ └── main-content.vue
│ ├── md-view
│ │ └── md-view.vue
│ ├── music-box
│ │ └── music-box.vue
│ ├── right-content
│ │ └── right-content.vue
│ ├── search-box
│ │ └── search-box.vue
│ ├── tab
│ │ └── tab.vue
│ └── user-info
│ │ └── user-info.vue
├── main.js
└── router
│ └── index.js
└── static
└── .gitkeep
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-vue-jsx", "transform-runtime"]
12 | }
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /config/
3 | /dist/
4 | /*.js
5 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // https://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parserOptions: {
6 | parser: 'babel-eslint'
7 | },
8 | env: {
9 | browser: true,
10 | },
11 | extends: [
12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
14 | 'plugin:vue/essential',
15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md
16 | 'standard'
17 | ],
18 | // required to lint *.vue files
19 | plugins: [
20 | 'vue'
21 | ],
22 | // add your custom rules here
23 | rules: {
24 | // allow async-await
25 | 'generator-star-spacing': 'off',
26 | // allow debugger during development
27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | /dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Editor directories and files
9 | .idea
10 | .vscode
11 | *.suo
12 | *.ntvs*
13 | *.njsproj
14 | *.sln
15 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | "postcss-import": {},
6 | "postcss-url": {},
7 | // to edit target browsers: use "browserslist" field in package.json
8 | "autoprefixer": {}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-blog
2 | 一个基于Vue的个人博客,为了初学Vue和记录笔记而打造,后端还在学习开发中。
3 |
4 | ## 博客地址
5 | 四叶草的博客:www.luckyclover.top
6 |
7 | 觉得不错的点个star吧
8 |
9 | ## 小白教程
10 |
11 | 0. 安装[Node.js](https://nodejs.org/en/)
12 |
13 | 1. 下载代码,vscode打开根目录,终端执行
14 |
15 | ```
16 | npm install //下载项目依赖
17 | ```
18 |
19 | 2. 本地调试(退出 Ctrl + C)
20 |
21 | ```
22 | npm start
23 | ```
24 |
25 | 3. 打包
26 |
27 |
28 | 终端运行
29 |
30 | ```
31 | npm run build
32 | ```
33 |
34 | 4. 部署
35 |
36 | 打开生成的dist目录,复制里面的内容到你的网站上即可
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/build/build.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | require('./check-versions')()
3 |
4 | process.env.NODE_ENV = 'production'
5 |
6 | const ora = require('ora')
7 | const rm = require('rimraf')
8 | const path = require('path')
9 | const chalk = require('chalk')
10 | const webpack = require('webpack')
11 | const config = require('../config')
12 | const webpackConfig = require('./webpack.prod.conf')
13 |
14 | const spinner = ora('building for production...')
15 | spinner.start()
16 |
17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
18 | if (err) throw err
19 | webpack(webpackConfig, (err, stats) => {
20 | spinner.stop()
21 | if (err) throw err
22 | process.stdout.write(stats.toString({
23 | colors: true,
24 | modules: false,
25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
26 | chunks: false,
27 | chunkModules: false
28 | }) + '\n\n')
29 |
30 | if (stats.hasErrors()) {
31 | console.log(chalk.red(' Build failed with errors.\n'))
32 | process.exit(1)
33 | }
34 |
35 | console.log(chalk.cyan(' Build complete.\n'))
36 | console.log(chalk.yellow(
37 | ' Tip: built files are meant to be served over an HTTP server.\n' +
38 | ' Opening index.html over file:// won\'t work.\n'
39 | ))
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/build/check-versions.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const chalk = require('chalk')
3 | const semver = require('semver')
4 | const packageConfig = require('../package.json')
5 | const shell = require('shelljs')
6 |
7 | function exec (cmd) {
8 | return require('child_process').execSync(cmd).toString().trim()
9 | }
10 |
11 | const versionRequirements = [
12 | {
13 | name: 'node',
14 | currentVersion: semver.clean(process.version),
15 | versionRequirement: packageConfig.engines.node
16 | }
17 | ]
18 |
19 | if (shell.which('npm')) {
20 | versionRequirements.push({
21 | name: 'npm',
22 | currentVersion: exec('npm --version'),
23 | versionRequirement: packageConfig.engines.npm
24 | })
25 | }
26 |
27 | module.exports = function () {
28 | const warnings = []
29 |
30 | for (let i = 0; i < versionRequirements.length; i++) {
31 | const mod = versionRequirements[i]
32 |
33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
34 | warnings.push(mod.name + ': ' +
35 | chalk.red(mod.currentVersion) + ' should be ' +
36 | chalk.green(mod.versionRequirement)
37 | )
38 | }
39 | }
40 |
41 | if (warnings.length) {
42 | console.log('')
43 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
44 | console.log()
45 |
46 | for (let i = 0; i < warnings.length; i++) {
47 | const warning = warnings[i]
48 | console.log(' ' + warning)
49 | }
50 |
51 | console.log()
52 | process.exit(1)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/build/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/build/logo.png
--------------------------------------------------------------------------------
/build/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const config = require('../config')
4 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
5 | const packageConfig = require('../package.json')
6 |
7 | exports.assetsPath = function (_path) {
8 | const assetsSubDirectory = process.env.NODE_ENV === 'production'
9 | ? config.build.assetsSubDirectory
10 | : config.dev.assetsSubDirectory
11 |
12 | return path.posix.join(assetsSubDirectory, _path)
13 | }
14 |
15 | exports.cssLoaders = function (options) {
16 | options = options || {}
17 |
18 | const cssLoader = {
19 | loader: 'css-loader',
20 | options: {
21 | sourceMap: options.sourceMap
22 | }
23 | }
24 |
25 | const postcssLoader = {
26 | loader: 'postcss-loader',
27 | options: {
28 | sourceMap: options.sourceMap
29 | }
30 | }
31 |
32 | // generate loader string to be used with extract text plugin
33 | function generateLoaders (loader, loaderOptions) {
34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
35 |
36 | if (loader) {
37 | loaders.push({
38 | loader: loader + '-loader',
39 | options: Object.assign({}, loaderOptions, {
40 | sourceMap: options.sourceMap
41 | })
42 | })
43 | }
44 |
45 | // Extract CSS when that option is specified
46 | // (which is the case during production build)
47 | if (options.extract) {
48 | return ExtractTextPlugin.extract({
49 | use: loaders,
50 | publicPath: '../../',
51 | fallback: 'vue-style-loader'
52 | })
53 | } else {
54 | return ['vue-style-loader'].concat(loaders)
55 | }
56 | }
57 |
58 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
59 | return {
60 | css: generateLoaders(),
61 | postcss: generateLoaders(),
62 | less: generateLoaders('less'),
63 | sass: generateLoaders('sass', { indentedSyntax: true }),
64 | scss: generateLoaders('sass'),
65 | stylus: generateLoaders('stylus'),
66 | styl: generateLoaders('stylus')
67 | }
68 | }
69 |
70 | // Generate loaders for standalone style files (outside of .vue)
71 | exports.styleLoaders = function (options) {
72 | const output = []
73 | const loaders = exports.cssLoaders(options)
74 |
75 | for (const extension in loaders) {
76 | const loader = loaders[extension]
77 | output.push({
78 | test: new RegExp('\\.' + extension + '$'),
79 | use: loader
80 | })
81 | }
82 |
83 | return output
84 | }
85 |
86 | exports.createNotifierCallback = () => {
87 | const notifier = require('node-notifier')
88 |
89 | return (severity, errors) => {
90 | if (severity !== 'error') return
91 |
92 | const error = errors[0]
93 | const filename = error.file && error.file.split('!').pop()
94 |
95 | notifier.notify({
96 | title: packageConfig.name,
97 | message: severity + ': ' + error.name,
98 | subtitle: filename || '',
99 | icon: path.join(__dirname, 'logo.png')
100 | })
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const utils = require('./utils')
3 | const config = require('../config')
4 | const isProduction = process.env.NODE_ENV === 'production'
5 | const sourceMapEnabled = isProduction
6 | ? config.build.productionSourceMap
7 | : config.dev.cssSourceMap
8 |
9 | module.exports = {
10 | loaders: utils.cssLoaders({
11 | sourceMap: sourceMapEnabled,
12 | extract: isProduction
13 | }),
14 | cssSourceMap: sourceMapEnabled,
15 | cacheBusting: config.dev.cacheBusting,
16 | transformToRequire: {
17 | video: ['src', 'poster'],
18 | source: 'src',
19 | img: 'src',
20 | image: 'xlink:href'
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const config = require('../config')
5 | const vueLoaderConfig = require('./vue-loader.conf')
6 |
7 | function resolve (dir) {
8 | return path.join(__dirname, '..', dir)
9 | }
10 |
11 | const createLintingRule = () => ({
12 | test: /\.(js|vue)$/,
13 | loader: 'eslint-loader',
14 | enforce: 'pre',
15 | include: [resolve('src'), resolve('test')],
16 | options: {
17 | formatter: require('eslint-friendly-formatter'),
18 | emitWarning: !config.dev.showEslintErrorsInOverlay
19 | }
20 | })
21 |
22 | module.exports = {
23 | context: path.resolve(__dirname, '../'),
24 | entry: {
25 | app: './src/main.js'
26 | },
27 | output: {
28 | path: config.build.assetsRoot,
29 | filename: '[name].js',
30 | publicPath: process.env.NODE_ENV === 'production'
31 | ? config.build.assetsPublicPath
32 | : config.dev.assetsPublicPath
33 | },
34 | resolve: {
35 | extensions: ['.js', '.vue', '.json'],
36 | alias: {
37 | 'vue$': 'vue/dist/vue.esm.js',
38 | '@': resolve('src'),
39 | 'common': resolve('src/common'),
40 | 'cpnts': resolve('src/components'),
41 | 'base': resolve('src/base'),
42 | 'api': resolve('src/api'),
43 | 'blogs': resolve('src/blogs')
44 | }
45 | },
46 | module: {
47 | rules: [
48 | ...(config.dev.useEslint ? [createLintingRule()] : []),
49 | {
50 | test: /\.vue$/,
51 | loader: 'vue-loader',
52 | options: vueLoaderConfig
53 | },
54 | {
55 | test: /\.js$/,
56 | loader: 'babel-loader',
57 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
58 | },
59 | {
60 | test: /\.(png|jpe?g|gif|svg|cur)(\?.*)?$/,
61 | loader: 'url-loader',
62 | options: {
63 | limit: 10000,
64 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
65 | }
66 | },
67 | {
68 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
69 | loader: 'url-loader',
70 | options: {
71 | limit: 10000,
72 | name: utils.assetsPath('media/[name].[hash:7].[ext]')
73 | }
74 | },
75 | {
76 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
77 | loader: 'url-loader',
78 | options: {
79 | limit: 10000,
80 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
81 | }
82 | },
83 | {
84 | test: /\.md$/,
85 | use: [
86 | {
87 | loader: "html-loader"
88 | },
89 | {
90 | loader: "markdown-loader",
91 | options: {}
92 | }
93 | ]
94 | }
95 | ]
96 | },
97 | node: {
98 | // prevent webpack from injecting useless setImmediate polyfill because Vue
99 | // source contains it (although only uses it if it's native).
100 | setImmediate: false,
101 | // prevent webpack from injecting mocks to Node native modules
102 | // that does not make sense for the client
103 | dgram: 'empty',
104 | fs: 'empty',
105 | net: 'empty',
106 | tls: 'empty',
107 | child_process: 'empty'
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const utils = require('./utils')
3 | const webpack = require('webpack')
4 | const config = require('../config')
5 | const merge = require('webpack-merge')
6 | const path = require('path')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 | const CopyWebpackPlugin = require('copy-webpack-plugin')
9 | const HtmlWebpackPlugin = require('html-webpack-plugin')
10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
11 | const portfinder = require('portfinder')
12 |
13 | const HOST = process.env.HOST
14 | const PORT = process.env.PORT && Number(process.env.PORT)
15 |
16 | const devWebpackConfig = merge(baseWebpackConfig, {
17 | module: {
18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
19 | },
20 | // cheap-module-eval-source-map is faster for development
21 | devtool: config.dev.devtool,
22 |
23 | // these devServer options should be customized in /config/index.js
24 | devServer: {
25 | clientLogLevel: 'warning',
26 | historyApiFallback: {
27 | rewrites: [
28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
29 | ],
30 | },
31 | hot: true,
32 | contentBase: false, // since we use CopyWebpackPlugin.
33 | compress: true,
34 | host: HOST || config.dev.host,
35 | port: PORT || config.dev.port,
36 | open: config.dev.autoOpenBrowser,
37 | overlay: config.dev.errorOverlay
38 | ? { warnings: false, errors: true }
39 | : false,
40 | publicPath: config.dev.assetsPublicPath,
41 | proxy: config.dev.proxyTable,
42 | quiet: true, // necessary for FriendlyErrorsPlugin
43 | watchOptions: {
44 | poll: config.dev.poll,
45 | }
46 | },
47 | plugins: [
48 | new webpack.DefinePlugin({
49 | 'process.env': require('../config/dev.env')
50 | }),
51 | new webpack.HotModuleReplacementPlugin(),
52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
53 | new webpack.NoEmitOnErrorsPlugin(),
54 | // https://github.com/ampedandwired/html-webpack-plugin
55 | new HtmlWebpackPlugin({
56 | filename: 'index.html',
57 | template: 'index.html',
58 | inject: true,
59 | favicon: path.resolve('favicon.png')
60 | }),
61 | // copy custom static assets
62 | new CopyWebpackPlugin([
63 | {
64 | from: path.resolve(__dirname, '../static'),
65 | to: config.dev.assetsSubDirectory,
66 | ignore: ['.*']
67 | }
68 | ])
69 | ]
70 | })
71 |
72 | module.exports = new Promise((resolve, reject) => {
73 | portfinder.basePort = process.env.PORT || config.dev.port
74 | portfinder.getPort((err, port) => {
75 | if (err) {
76 | reject(err)
77 | } else {
78 | // publish the new Port, necessary for e2e tests
79 | process.env.PORT = port
80 | // add port to devServer config
81 | devWebpackConfig.devServer.port = port
82 |
83 | // Add FriendlyErrorsPlugin
84 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
85 | compilationSuccessInfo: {
86 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
87 | },
88 | onErrors: config.dev.notifyOnErrors
89 | ? utils.createNotifierCallback()
90 | : undefined
91 | }))
92 |
93 | resolve(devWebpackConfig)
94 | }
95 | })
96 | })
97 |
--------------------------------------------------------------------------------
/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const webpack = require('webpack')
5 | const config = require('../config')
6 | const merge = require('webpack-merge')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 | const CopyWebpackPlugin = require('copy-webpack-plugin')
9 | const HtmlWebpackPlugin = require('html-webpack-plugin')
10 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
13 |
14 | const env = require('../config/prod.env')
15 |
16 | const webpackConfig = merge(baseWebpackConfig, {
17 | module: {
18 | rules: utils.styleLoaders({
19 | sourceMap: config.build.productionSourceMap,
20 | extract: true,
21 | usePostCSS: true
22 | })
23 | },
24 | devtool: config.build.productionSourceMap ? config.build.devtool : false,
25 | output: {
26 | path: config.build.assetsRoot,
27 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
28 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
29 | },
30 | plugins: [
31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
32 | new webpack.DefinePlugin({
33 | 'process.env': env
34 | }),
35 | new UglifyJsPlugin({
36 | uglifyOptions: {
37 | compress: {
38 | warnings: false
39 | }
40 | },
41 | sourceMap: config.build.productionSourceMap,
42 | parallel: true
43 | }),
44 | // extract css into its own file
45 | new ExtractTextPlugin({
46 | filename: utils.assetsPath('css/[name].[contenthash].css'),
47 | // Setting the following option to `false` will not extract CSS from codesplit chunks.
48 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
49 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
50 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
51 | allChunks: true,
52 | }),
53 | // Compress extracted CSS. We are using this plugin so that possible
54 | // duplicated CSS from different components can be deduped.
55 | new OptimizeCSSPlugin({
56 | cssProcessorOptions: config.build.productionSourceMap
57 | ? { safe: true, map: { inline: false } }
58 | : { safe: true }
59 | }),
60 | // generate dist index.html with correct asset hash for caching.
61 | // you can customize output by editing /index.html
62 | // see https://github.com/ampedandwired/html-webpack-plugin
63 | new HtmlWebpackPlugin({
64 | filename: config.build.index,
65 | template: 'index.html',
66 | inject: true,
67 | favicon: path.resolve('favicon.png'),
68 | minify: {
69 | removeComments: true,
70 | collapseWhitespace: true,
71 | removeAttributeQuotes: true
72 | // more options:
73 | // https://github.com/kangax/html-minifier#options-quick-reference
74 | },
75 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
76 | chunksSortMode: 'dependency'
77 | }),
78 | // keep module.id stable when vendor modules does not change
79 | new webpack.HashedModuleIdsPlugin(),
80 | // enable scope hoisting
81 | new webpack.optimize.ModuleConcatenationPlugin(),
82 | // split vendor js into its own file
83 | new webpack.optimize.CommonsChunkPlugin({
84 | name: 'vendor',
85 | minChunks (module) {
86 | // any required modules inside node_modules are extracted to vendor
87 | return (
88 | module.resource &&
89 | /\.js$/.test(module.resource) &&
90 | module.resource.indexOf(
91 | path.join(__dirname, '../node_modules')
92 | ) === 0
93 | )
94 | }
95 | }),
96 | // extract webpack runtime and module manifest to its own file in order to
97 | // prevent vendor hash from being updated whenever app bundle is updated
98 | new webpack.optimize.CommonsChunkPlugin({
99 | name: 'manifest',
100 | minChunks: Infinity
101 | }),
102 | // This instance extracts shared chunks from code splitted chunks and bundles them
103 | // in a separate chunk, similar to the vendor chunk
104 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
105 | new webpack.optimize.CommonsChunkPlugin({
106 | name: 'app',
107 | async: 'vendor-async',
108 | children: true,
109 | minChunks: 3
110 | }),
111 |
112 | // copy custom static assets
113 | new CopyWebpackPlugin([
114 | {
115 | from: path.resolve(__dirname, '../static'),
116 | to: config.build.assetsSubDirectory,
117 | ignore: ['.*']
118 | }
119 | ])
120 | ]
121 | })
122 |
123 | if (config.build.productionGzip) {
124 | const CompressionWebpackPlugin = require('compression-webpack-plugin')
125 |
126 | webpackConfig.plugins.push(
127 | new CompressionWebpackPlugin({
128 | asset: '[path].gz[query]',
129 | algorithm: 'gzip',
130 | test: new RegExp(
131 | '\\.(' +
132 | config.build.productionGzipExtensions.join('|') +
133 | ')$'
134 | ),
135 | threshold: 10240,
136 | minRatio: 0.8
137 | })
138 | )
139 | }
140 |
141 | if (config.build.bundleAnalyzerReport) {
142 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
143 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
144 | }
145 |
146 | module.exports = webpackConfig
147 |
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const prodEnv = require('./prod.env')
4 |
5 | module.exports = merge(prodEnv, {
6 | NODE_ENV: '"development"'
7 | })
8 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // Template version: 1.3.1
3 | // see http://vuejs-templates.github.io/webpack for documentation.
4 |
5 | const path = require('path')
6 |
7 | module.exports = {
8 | dev: {
9 |
10 | // Paths
11 | assetsSubDirectory: 'static',
12 | assetsPublicPath: '/',
13 | proxyTable: {},
14 |
15 | // Various Dev Server settings
16 | host: 'localhost', // can be overwritten by process.env.HOST
17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
18 | autoOpenBrowser: false,
19 | errorOverlay: true,
20 | notifyOnErrors: true,
21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
22 |
23 | // Use Eslint Loader?
24 | // If true, your code will be linted during bundling and
25 | // linting errors and warnings will be shown in the console.
26 | useEslint: true,
27 | // If true, eslint errors and warnings will also be shown in the error overlay
28 | // in the browser.
29 | showEslintErrorsInOverlay: false,
30 |
31 | /**
32 | * Source Maps
33 | */
34 |
35 | // https://webpack.js.org/configuration/devtool/#development
36 | devtool: 'cheap-module-eval-source-map',
37 |
38 | // If you have problems debugging vue-files in devtools,
39 | // set this to false - it *may* help
40 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
41 | cacheBusting: true,
42 |
43 | cssSourceMap: true
44 | },
45 |
46 | build: {
47 | // Template for index.html
48 | index: path.resolve(__dirname, '../dist/index.html'),
49 |
50 | // Paths
51 | assetsRoot: path.resolve(__dirname, '../dist'),
52 | assetsSubDirectory: 'static',
53 | assetsPublicPath: '/',
54 |
55 | /**
56 | * Source Maps
57 | */
58 |
59 | productionSourceMap: true,
60 | // https://webpack.js.org/configuration/devtool/#production
61 | devtool: '#source-map',
62 |
63 | // Gzip off by default as many popular static hosts such as
64 | // Surge or Netlify already gzip all static assets for you.
65 | // Before setting to `true`, make sure to:
66 | // npm install --save-dev compression-webpack-plugin
67 | productionGzip: false,
68 | productionGzipExtensions: ['js', 'css'],
69 |
70 | // Run the build command with an extra argument to
71 | // View the bundle analyzer report after build finishes:
72 | // `npm run build --report`
73 | // Set to `true` or `false` to always turn it on or off
74 | bundleAnalyzerReport: process.env.npm_config_report
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | NODE_ENV: '"production"'
4 | }
5 |
--------------------------------------------------------------------------------
/cursor.cur:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/cursor.cur
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/favicon.ico
--------------------------------------------------------------------------------
/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/favicon.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 四叶草的博客
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-blog",
3 | "version": "1.0.0",
4 | "description": "第一个博客主页",
5 | "author": "iCodek <790477428@qq.com>",
6 | "private": true,
7 | "scripts": {
8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
9 | "start": "npm run dev",
10 | "lint": "eslint --ext .js,.vue src",
11 | "build": "node build/build.js"
12 | },
13 | "dependencies": {
14 | "@better-scroll/core": "^2.0.0-alpha.4",
15 | "axios": "^0.18.0",
16 | "babel-runtime": "^6.26.0",
17 | "better-scroll": "^2.0.0-beta.10",
18 | "create-keyframe-animation": "0.1.0",
19 | "fastclick": "^1.0.6",
20 | "good-storage": "^1.0.1",
21 | "lyric-parser": "^1.0.1",
22 | "pinyin": "^2.8.3",
23 | "vue": "^2.5.2",
24 | "vue-lazyload": "^1.2.3",
25 | "vue-router": "^3.0.1",
26 | "vuex": "^3.0.1",
27 | "vuescroll": "4.16.1",
28 | "mavon-editor": "2.9.0",
29 | "markdown-loader": "5.1.0",
30 | "html-loader": "1.3.0",
31 | "highlight.js": "10.1.2"
32 | },
33 | "devDependencies": {
34 | "autoprefixer": "^7.1.2",
35 | "babel-core": "^6.22.1",
36 | "babel-eslint": "^8.2.1",
37 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
38 | "babel-loader": "^7.1.1",
39 | "babel-plugin-syntax-jsx": "^6.18.0",
40 | "babel-plugin-transform-runtime": "^6.22.0",
41 | "babel-plugin-transform-vue-jsx": "^3.5.0",
42 | "babel-preset-env": "^1.3.2",
43 | "babel-preset-stage-2": "^6.22.0",
44 | "chalk": "^2.0.1",
45 | "copy-webpack-plugin": "^4.0.1",
46 | "css-loader": "^0.28.0",
47 | "eslint": "^4.15.0",
48 | "eslint-config-standard": "^10.2.1",
49 | "eslint-friendly-formatter": "^3.0.0",
50 | "eslint-loader": "^1.7.1",
51 | "eslint-plugin-import": "^2.7.0",
52 | "eslint-plugin-node": "^5.2.0",
53 | "eslint-plugin-promise": "^3.4.0",
54 | "eslint-plugin-standard": "^3.0.1",
55 | "eslint-plugin-vue": "^4.0.0",
56 | "extract-text-webpack-plugin": "^3.0.0",
57 | "file-loader": "^1.1.4",
58 | "friendly-errors-webpack-plugin": "^1.6.1",
59 | "html-webpack-plugin": "^2.30.1",
60 | "node-notifier": "^5.1.2",
61 | "node-sass": "^4.9.0",
62 | "optimize-css-assets-webpack-plugin": "^3.2.0",
63 | "ora": "^1.2.0",
64 | "portfinder": "^1.0.13",
65 | "postcss-import": "^11.0.0",
66 | "postcss-loader": "^2.0.8",
67 | "postcss-url": "^7.2.1",
68 | "rimraf": "^2.6.0",
69 | "semver": "^5.3.0",
70 | "shelljs": "^0.7.6",
71 | "sass-loader": "^7.0.1",
72 | "uglifyjs-webpack-plugin": "^1.1.1",
73 | "url-loader": "^0.5.8",
74 | "vue-loader": "^13.3.0",
75 | "vue-style-loader": "^3.0.1",
76 | "vue-template-compiler": "^2.5.2",
77 | "webpack": "^3.6.0",
78 | "webpack-bundle-analyzer": "^2.9.0",
79 | "webpack-dev-server": "^2.9.1",
80 | "webpack-merge": "^4.1.0"
81 | },
82 | "engines": {
83 | "node": ">= 6.0.0",
84 | "npm": ">= 3.0.0"
85 | },
86 | "browserslist": [
87 | "> 1%",
88 | "last 2 versions",
89 | "not ie <= 8"
90 | ]
91 | }
92 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
24 |
25 |
35 |
--------------------------------------------------------------------------------
/src/api/search.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import {HOST} from 'common/js/config'
3 |
4 | export function getSearchSinger (name) {
5 | const url = HOST + `/search?keywords=${name}&type=100`
6 |
7 | return axios.get(url)
8 | }
9 |
10 | export function getSearchSongs (name, page) {
11 | const url = HOST + `/search?keywords=${name}&offset=${page}`
12 |
13 | return axios.get(url)
14 | }
15 |
16 | export function getSearchSuggest (name) {
17 | const url = HOST + `/search/suggest?keywords=${name}`
18 |
19 | return axios.get(url)
20 | }
21 |
22 | export function getSongDetail (id) {
23 | const url = HOST + `/song/detail?ids=${id}`
24 |
25 | return axios.get(url)
26 | }
27 |
28 | export function getSearchHot (id) {
29 | const url = HOST + `/search/hot`
30 |
31 | return axios.get(url)
32 | }
33 |
--------------------------------------------------------------------------------
/src/api/song.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import {HOST} from 'common/js/config'
3 |
4 | export function getSong (id) {
5 | const url = HOST + `/song/url?id=${id}`
6 |
7 | return axios.get(url)
8 | }
9 |
10 | export function getLyric (id) {
11 | const url = HOST + `/lyric?id=${id}`
12 |
13 | return axios.get(url)
14 | }
15 |
16 | export function getSongDetail (id) {
17 | const url = HOST + `/song/detail?ids=${id}`
18 |
19 | return axios.get(url)
20 | }
21 |
--------------------------------------------------------------------------------
/src/assets/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/assets/bg.jpg
--------------------------------------------------------------------------------
/src/assets/bgvideo480.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/assets/bgvideo480.mp4
--------------------------------------------------------------------------------
/src/assets/cursor.cur:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/assets/cursor.cur
--------------------------------------------------------------------------------
/src/assets/head.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/assets/head.jpg
--------------------------------------------------------------------------------
/src/assets/siyecao1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/assets/siyecao1.jpg
--------------------------------------------------------------------------------
/src/assets/siyecao2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/assets/siyecao2.jpg
--------------------------------------------------------------------------------
/src/assets/siyecao3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/assets/siyecao3.jpg
--------------------------------------------------------------------------------
/src/assets/userbg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/assets/userbg.jpg
--------------------------------------------------------------------------------
/src/base/blog-box/blog-box.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{blogSummary.title}}
6 |
38 |
39 |
40 |
41 |
73 |
213 |
--------------------------------------------------------------------------------
/src/base/board/board.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{title}}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
31 |
71 |
--------------------------------------------------------------------------------
/src/base/player/player.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
![]()
9 |
10 |
13 |
16 |
17 |
18 |
19 |
20 |
23 | -
24 | {{lyr}}
25 |
26 |
27 |
28 |
29 |
30 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
362 |
597 |
--------------------------------------------------------------------------------
/src/base/progress-bar/progress-bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{parseInt(scale*100)}}
9 |
10 |
11 |
12 |
13 |
14 |
116 |
194 |
--------------------------------------------------------------------------------
/src/base/tags/tags.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{tag.name+' ('+tag.number+')'}}
6 |
7 |
8 |
9 |
10 |
11 |
47 |
48 |
78 |
--------------------------------------------------------------------------------
/src/blogs/blog.md:
--------------------------------------------------------------------------------
1 | # 博客主页开发日志
2 |
3 | >## 0. 开篇
4 | >
5 | >今天818,帮弟弟买手机又被狗东坑了一手,真是气啊。言归正传,这是第一篇markdown博客,主要目的呢,是为了熟悉下markdown语法和记录下博客的开发日志,也是第一次边学边做笔记,当初没用现成的wordpress和hexo,就是为了自己实现一下动态博客。开始吧~
6 | >
7 | >GitHub地址在此:https://github.com/iCodek/vue-blog
8 |
9 | # 1.网站大体样式
10 |
11 | ## 1.1 先创建Vue项目
12 |
13 | ```
14 | vue init webpack vue-blog
15 | ```
16 |
17 | 改下结构,加了下面三个目录
18 |
19 | 
20 |
21 | - api 前后端交互
22 | - base 基本组件
23 | - common 用到的css,js方法
24 |
25 |
26 |
27 | ## 1.2 编写样式
28 |
29 | 主要样子呢 打算照着[小游的博客主页](https://xiaoyou66.com/)画瓢
30 |
31 | 
32 |
33 | 观察了下,路由的位置都在中间的绿色区域,那就先把其他的组件位置固定下。
34 |
35 | # 2.各种组件
36 |
37 | ## 2.1 m-header组件
38 |
39 | 创建好了脚手架,突然发现body默认是*margin:* *8px;*晕,在index.html修改下style
40 |
41 | search-box组件用到了icon-font 使用方式是在style中导入(全局可用)忽略了scope
42 |
43 | ```js
44 | @import url("../../common/css/iconfont/iconfont.css");
45 | ```
46 |
47 | 分类标题用的弹性布局,也是第一次搞懂了,只要设置父元素display: flex; 在设置子元素的flex大小,是成比例缩放的。其他参数可以[参见](https://www.runoob.com/w3cnote/flex-grammar.html)。
48 |
49 | 还有首页的居中,设置父元素没有高度,然后子元素设置margin即可。
50 |
51 | ------
52 |
53 | 分割线表示第二天,做了挺多的,主要是遇到了好多坑,来总结一下
54 |
55 | 上面的icon导入方式是黑白的图标,换了一种svg的方式,首先把iconfont的图标加入自己的项目,然后生成自己的代码,例如
56 |
57 | ```
58 | //at.alicdn.com/t/font_2014472_qa5ruvao5kb.js
59 | ```
60 |
61 | 然后在index.html引入
62 |
63 | ```html
64 |
65 | ```
66 |
67 | 然后就不用像上面import了,直接
68 |
69 | ```html
70 |
73 | ```
74 |
75 | 就能用了,#后面是图标的代码(自己项目里的)
76 |
77 | ## 2.2 content组件
78 |
79 | 中间主要有三列,用了弹性布局
80 |
81 | 父级元素
82 |
83 | ```css
84 |
85 |
86 |
87 | ```
88 |
89 | 样式
90 |
91 | ```css
92 | display: flex;
93 | ```
94 |
95 | 再把中间的元素设置样式
96 |
97 | ```css
98 | flex: 1;
99 | ```
100 |
101 | 另外 为了适配移动端,左右两边当宽度小于500不显示,响应式css代码
102 |
103 | ```css
104 | @media screen and (max-width:500px){
105 | display: none;
106 | }
107 | ```
108 |
109 | 默认左右两边的容器直接撑到底下了,查了下父元素要设置**align-items**
110 |
111 |
112 |
113 | 大致框架为
114 |
115 |
116 |
117 | ## 2.3 user-info组件
118 |
119 | 这里的布局都是用的flex,熟能生巧了。
120 |
121 | 主要难(坑)点
122 |
123 | - 头像要设置hover时变大,那只能是absolute定位,所以在头像div外面套了一个relative定位的div,在把里面的头像div弄成absolute。这样变大也不会影像下面的元素位置。绝对定位想要居中得用left top 50%和transform: translate(-50%, -50%)
124 |
125 | - 头像hover旋转要注意transition里是all,居中定位的transform: translate(-50%, -50%)要保留,所以hover里是transform: translate(-50%, -50%) rotate(360deg); 注意这里还有顺序要求,不然就不转了(不知道为什么)
126 |
127 | ```css
128 | .userheadwrap {
129 | position: relative;
130 | .userhead {
131 | left: 50%;
132 | position: absolute;
133 | width: 90px;
134 | height: 90px;
135 | border-radius: 100%;
136 | border: 4px solid hsla(0,0%,100%,.4);
137 | background-color: red;
138 | background: url('../../assets/siyecao1.jpg') center center no-repeat;
139 | background-size: cover;
140 | transition: all 0.4s;
141 | transform: translate(-50%, -50%);
142 | &:hover {
143 | width: 120px;
144 | height: 120px;
145 | transform: translate(-50%, -50%) rotate(360deg);
146 | }
147 | }
148 | }
149 | ```
150 |
151 | - 下面的联系方式图标想要一个hover显示提示框的效果这里要用三个伪类,首先在after伪类里面弄好圆角框和动画时间,然后隐藏。接下来在:hover::after里面显示就可以了,下面的三角形其实是一个css画的before
152 |
153 | ```css
154 | ::after {
155 | content: attr(data-title); //取到data-title属性的值
156 | background:#000;
157 | width: 80px;
158 | height: 35px;
159 | line-height: 35px;
160 | border-radius: 10px;
161 | font-size: 16px;
162 | color: #fff;
163 | position: absolute;
164 | bottom: 58px;
165 | left: 0px;
166 | transition: all 0.4s;
167 | opacity: 0;
168 | }
169 | ```
170 |
171 | ```css
172 | :hover::after {
173 | opacity: 1;
174 | }
175 | ```
176 |
177 | ```css
178 | :before { //三角形
179 | content: "";
180 | position: absolute;
181 | bottom: 50px;
182 | left: 34px;
183 | width: 0;
184 | height: 0;
185 | border-top: 8px solid #000;
186 | border-left: 6px solid transparent;
187 | border-right: 6px solid transparent;
188 | transition: all 0.4s;
189 | opacity: 0;
190 | }
191 | :hover::before{
192 | opacity: 1;
193 | }
194 | ```
195 |
196 | ## 2.4 music-box组件
197 |
198 | 这绝对称得上博客页面最难的地方,用上了keyframes动画,height是auto的动画,vuescroll插件,自己画svg,父子组件通信,Promise.all的使用,li里的span不换行,一个一个讲。
199 |
200 | 
201 |
202 | - keyframes 实现旋转动画,首先定义rotate
203 |
204 | ```css
205 | @keyframes rotate {
206 | 0% {
207 | transform: rotate(0);
208 | }
209 | 100% {
210 | transform: rotate(360deg);
211 | }
212 | }
213 | ```
214 |
215 | 再在css里定义animation
216 |
217 | ```css
218 | &.play {
219 | animation: rotate 10s linear infinite;
220 | }
221 | &.pause {
222 | animation-play-state: paused;
223 | }
224 | ```
225 |
226 | 即可实现封面无限旋转
227 |
228 | - height是auto不固定的div无法实现height动画,可以利用max-height
229 |
230 | ```css
231 | .scrollboxhide {
232 | max-height: 0 !important;
233 | margin-bottom: 0px !important;
234 | }
235 | .scrollbox {
236 | max-height: 250px;
237 | margin-bottom: 8px;
238 | transition: all 0.5s;
239 | }
240 | ```
241 |
242 | - vuescroll插件使用,现在main.js里
243 |
244 | ```js
245 | import vuescroll from 'vuescroll'
246 | Vue.use(vuescroll)
247 |
248 | Vue.prototype.$vuescrollConfig = {
249 | vuescroll: {
250 | mode: 'native', // 选择一个模式, native 或者 slide(pc&app)
251 | sizeStrategy: 'percent', // 如果父容器不是固定高度,请设置为 number , 否则保持默认的percent即可
252 | detectResize: true // 是否检测内容尺寸发生变化
253 | },
254 | scrollPanel: {
255 | initialScrollY: false, // 只要组件mounted之后自动滚动的距离。 例如 100 or 10%
256 | initialScrollX: false,
257 | scrollingX: false, // 是否启用 x 或者 y 方向上的滚动
258 | scrollingY: true,
259 | speed: 100, // 多长时间内完成一次滚动。 数值越小滚动的速度越快
260 | easing: undefined, // 滚动动画 参数通animation
261 | verticalNativeBarPos: 'right'// 原生滚动条的位置
262 | },
263 | rail: { // 轨道
264 | background: '#c3c3c3', // 轨道的背景色
265 | opacity: 0,
266 | size: '6px',
267 | specifyBorderRadius: false, // 是否指定轨道的 borderRadius, 如果不那么将会自动设置
268 | gutterOfEnds: null,
269 | gutterOfSide: '0px', // 轨道距 x 和 y 轴两端的距离
270 | keepShow: false // 是否即使 bar 不存在的情况下也保持显示
271 | },
272 | bar: {
273 | showDelay: 1000, // 在鼠标离开容器后多长时间隐藏滚动条
274 | onlyShowBarOnScroll: false, // 当页面滚动时显示
275 | keepShow: false, // 是否一直显示
276 | background: '#c3c3c3',
277 | opacity: 1,
278 | hoverStyle: false,
279 | specifyBorderRadius: false,
280 | minSize: false,
281 | size: '6px',
282 | disable: false // 是否禁用滚动条
283 | }, // 在这里设置全局默认配置
284 | name: 'vuescroll' // 在这里自定义组件名字,默认是vueScroll
285 | }
286 | ```
287 |
288 | 然后使用
289 |
290 | ```js
291 |
292 |
293 |
294 |
295 | -
296 |
299 |
302 |
303 | {{index + 1}}
304 | {{song.name}}
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 | ```
313 |
314 | - svg可以在iconfont自己下个差不多的,再上svg在线编辑网站改下就好了,覆盖掉原来自己项目里的svg
315 |
316 | - 父子组件通信
317 |
318 | 父调用子:
319 |
320 | 父中组件
321 |
322 | ```js
323 |
324 | ```
325 |
326 | 调用
327 |
328 | ```js
329 | toPlay (item, e) {
330 | this.$refs.player.setPlay(item)
331 | }
332 | ```
333 |
334 | 子调用父:
335 |
336 | 子中调用
337 |
338 | ```js
339 | shouqi (e) {
340 | this.spread = false
341 | this.$emit('callshouqi')
342 | }
343 | ```
344 |
345 | 父亲中用到的组件用@callshouqi="shouqilist"
346 |
347 | ```js
348 |
349 | ```
350 |
351 | 父中函数
352 |
353 | ```js
354 | shouqilist () {
355 | this.spread = false
356 | this.scrollClass = 'scrollbox scrollboxhide'
357 | }
358 | ```
359 |
360 | - Promise.all
361 |
362 | 在处理for中的异步方法中,每个异步方法都有then,相当于两个异步,必须等所有异步执行完再赋值songlist
363 |
364 | ```js
365 | _getSongList () {
366 | const songlist = []
367 | const pros = []
368 | this.songs.forEach((id, index) => {
369 | pros.push(getSongDetail(id).then((res) => {
370 | if (res.status === ERR_OK) {
371 | songlist.push({'id': res.data.songs[0].id, 'name': res.data.songs[0].name, 'pic': res.data.songs[0].al.picUrl, 'index': index})
372 | }
373 | }))
374 | })
375 | Promise.all(pros).then(() => {
376 | songlist.sort((a, b) => a.index - b.index)
377 | this.songlist = songlist
378 | })
379 | }
380 | ```
381 |
382 | Promise.all([p1,p2,...])接受Promise列表的传入参数,所以先创建一个for训练里面的异步列表,再传入Promise.all
383 |
384 | - li里的span不换行
385 |
386 | ```html
387 |
388 |
389 | {{index + 1}}
390 | {{song.name}}
391 |
392 |
393 | ```
394 |
395 | 外面再套一个span,设置 white-space: nowrap; 和设置 li overflow: hidden;
396 |
397 | 播放组件做了做了一天半,效果如图
398 |
399 | 
400 |
401 | 真是苦难重重啊。
402 |
403 | - 布局
404 |
405 | 绿色整块display:flex; 图像大小固定,图片右边整块flex:1,然后也是flex布局,列向排列,主要是为了随着底部部分变化,歌词都可以撑满蓝色的区域,底部(进度条+图标)也是flex布局,进度条flex:1,可以随着屏幕变化而变。
406 |
407 | - 播放进度条(注意点:document的事件绑定document.onmousemove = function() {} 可以用docume.onmousemove =null**清空绑定**
408 |
409 | 但是使用document.addEventListener('mousemove',f)绑定的要用document.removeEventListener('mousemove',f)**解除该绑定**
410 |
411 | - 点击事件
412 |
413 | ```js
414 | progressClick (e) {
415 | let rect = this.$refs.progressBar.getBoundingClientRect()
416 | let offsetWidth = this._isMobile() ? e.touches[0].clientX - rect.left : e.clientX - rect.left //手机和电脑clientX不一样
417 | let barWidth = rect.right - rect.left
418 | let percent = offsetWidth / barWidth
419 | this.currentTime = this.duration * percent
420 | this.$refs.audio.currentTime = this.duration * percent
421 | }
422 | ```
423 |
424 | - pc端+移动端拖动事件
425 |
426 | ```js
427 | progressTouchStart (e) {
428 | this.touch.move = true
429 | let rect = this.$refs.progressBar.getBoundingClientRect()
430 | let barWidth = rect.right - rect.left
431 | let offsetWidth = this._isMobile() ? e.touches[0].clientX - rect.left : e.clientX - rect.left
432 | let percent = offsetWidth / barWidth
433 | if (this._isMobile()) { //手机端触碰直接跳到进度
434 | this.currentTime = this.duration * percent
435 | this.$refs.audio.currentTime = this.duration * percent
436 | }
437 | this.touch.rect = rect //PC端保存以下dom偏离位置
438 | let $this = this
439 | if (!this._isMobile()) { //PC端和手机端的鼠标移动事件和鼠标按起事件不一样
440 | document.onmousemove = function (e) {
441 | $this.progressTouchMove(e)
442 | }
443 | document.onmouseup = function (e) {
444 | document.onmousemove = null
445 | document.onmouseup = null
446 | $this.progressTouchEnd(e)
447 | }
448 | } else {
449 | document.ontouchmove = function (e) {
450 | $this.progressTouchMove(e)
451 | }
452 | document.ontouchend = function (e) {
453 | document.ontouchmove = null
454 | document.ontouchend = null
455 | $this.progressTouchEnd(e)
456 | }
457 | }
458 | },
459 | progressTouchMove (e) {
460 | if (!this.touch.move) {
461 | return
462 | }
463 | let endX = this._isMobile() ? e.touches[0].clientX : e.clientX
464 | let percent = (endX - this.touch.rect.left) / (this.touch.rect.right - this.touch.rect.left)
465 | this.currentTime = this.duration * Math.min(Math.max(0, percent), 1) //移一次设置一次进度条但是不改变事件
466 | },
467 | progressTouchEnd (e) {
468 | this.touch.move = false
469 | this.$refs.audio.currentTime = this.currentTime //移完了改变播放时间
470 | }
471 | ```
472 |
473 | - 歌词移动
474 |
475 | 歌词没有点击事件,比上面简单一点
476 |
477 | ```js
478 | lyricTouchStart (e) {
479 | this.songLyric.move = true
480 | this.songLyric.startY = this._isMobile() ? e.touches[0].clientY : e.clientY
481 | this.songLyric.marginTop = this.$refs.ul.style.marginTop
482 | let dom = document.querySelector('ul')
483 | let height = window.getComputedStyle(dom).height
484 | this.songLyric.height = height
485 | let $this = this
486 | if (!this._isMobile()) {
487 | document.onmousemove = function (e) {
488 | $this.lyricTouchMove(e)
489 | }
490 | document.onmouseup = function (e) {
491 | document.onmousemove = null
492 | document.onmouseup = null
493 | $this.lyricTouchEnd()
494 | }
495 | } else {
496 | document.ontouchmove = function (e) {
497 | $this.lyricTouchMove(e)
498 | }
499 | document.ontouchend = function (e) {
500 | document.ontouchmove = null
501 | document.ontouchend = null
502 | $this.lyricTouchEnd()
503 | }
504 | }
505 | },
506 | lyricTouchMove (e) {
507 | if (!this.songLyric.move) return
508 | let endY = this._isMobile() ? e.touches[0].clientY : e.clientY
509 | this.transitionOn = false
510 | let top = Math.min(parseInt(this.songLyric.marginTop) + endY - this.songLyric.startY, 25)
511 | let bottom = Math.max(-parseInt(this.songLyric.height) + 45, top)
512 | this.$refs.ul.style.marginTop = bottom + 'px'
513 | },
514 | lyricTouchEnd (e) {
515 | this.songLyric.move = false
516 | this.transitionOn = true
517 | }
518 | ```
519 |
520 | - 音量悬浮显示音量bar
521 |
522 | - 注意点:悬浮显示需要在鼠标悬浮在和进度条部分都要显示,所以干脆把这两个放在一个div里,初始父div位置就在图标上,hover时,把子div就是bar的height高度还原正常,这样鼠标移动到bar就可以继续hover显示
523 |
524 | ```css
525 | .volume {
526 | position: relative;
527 | display: inline-block;
528 | height: 30px;
529 | &:hover > .volume_wrap {
530 | height: 54px;
531 | }
532 | }
533 | ```
534 |
535 | 音量调整条 比上面两个简单
536 |
537 | ```js
538 | volumeClick (e) {
539 | let rect = this.$refs.vlmpgs.getBoundingClientRect()
540 | let offsetHeight = rect.bottom - e.clientY
541 | let barHeight = rect.bottom - rect.top
542 | this.volume = Math.min(Math.max(offsetHeight / barHeight, 0), 1)
543 | },
544 | volumeMoveStart (e) {
545 | let rect = this.$refs.vlmpgs.getBoundingClientRect()
546 | let $this = this
547 | document.onmousemove = function (e) {
548 | let offsetHeight = rect.bottom - e.clientY
549 | let barHeight = rect.bottom - rect.top
550 |
551 | $this.volume = Math.min(Math.max(offsetHeight / barHeight, 0), 1)
552 | }
553 | document.onmouseup = function (e) {
554 | document.onmousemove = null
555 | document.onmouseup = null
556 | }
557 | }
558 | ```
559 |
560 |
561 |
562 | - 歌词高亮
563 |
564 | 使用了background-clip,思路绝对定位(ul会动)一个div,ul放在这个div里面,设置div css
565 |
566 | ```css
567 | .mask {
568 | position: absolute;
569 | width: 100%;
570 | height: 100%;
571 | color: transparent;
572 | -webkit-background-clip: text;
573 | background-image: linear-gradient(0deg, rgba(121, 121, 121, 0.8) 33%, white 45%, white 64%, rgba(121, 121, 121, 0.8) 72%);;
574 | }
575 | ```
576 |
577 | 相当于歌词透明,背景是,背景透过透明的字就是-webkit-background-clip: text;的效果
578 |
579 | - 获取内联样式
580 |
581 | ```js
582 | this.$refs.ul.style.marginTop
583 | ```
584 |
585 | - 获取即时样式
586 |
587 | ```js
588 | let dom = document.querySelector('ul')
589 | let height = window.getComputedStyle(dom).height
590 | ```
591 |
592 | - 事件e的位置
593 |
594 | #### 一、clientX、clientY
595 |
596 | 点击位置距离当前body可视区域的x,y坐标
597 |
598 | #### 二、pageX、pageY
599 |
600 | 对于整个页面来说,包括了被卷去的body部分的长度
601 |
602 | #### 三、screenX、screenY
603 |
604 | 点击位置距离当前电脑屏幕的x,y坐标
605 |
606 | #### 四、offsetX、offsetY
607 |
608 | 相对于带有定位的父盒子的x,y坐标
609 |
610 | #### 五、x、y
611 |
612 | 和screenX、screenY一样
613 |
614 | 音乐盒又细化了两天 现在来继续总结下
615 |
616 | 加了搜索功能,花功夫主要在一些逻辑bug和动画上
617 |
618 | - 输入框停止输入300毫秒后搜索,用到了截流函数
619 |
620 | ```js
621 | // 截流函数
622 | export function debounce (func, delay) {
623 | let timer
624 | return function (...args) {
625 | if (timer) {
626 | clearTimeout(timer)
627 | }
628 | timer = setTimeout(() => {
629 | func.apply(this, args)
630 | }, delay)
631 | }
632 | }
633 | ```
634 |
635 | 然后在mounted方法中绑定watch调用的函数
636 |
637 | ```js
638 | mounted () {
639 | this.$watch('query', debounce((newQuery) => {
640 | if (newQuery) {
641 | let searchResults = []
642 | getSearchSongs(newQuery, 0).then((res) => {
643 | if (res.status === ERR_OK) {
644 | let songs = res.data.result.songs
645 | if (songs.length > 0) {
646 | songs.forEach((song, index) => {
647 | searchResults.push({'id': song.id, 'name': song.name, 'pic': undefined, 'index': index, 'duration': song.duration, 'singerName': this.singerName(song.artists), 'source': 'Search', 'islike': false})
648 | })
649 | this.searchResults = searchResults
650 | }
651 | }
652 | })
653 | }
654 | }, 300))
655 | }
656 | ```
657 |
658 | - 注意下.className:hover(无空格)和.className :hover(:前面有空格)的区别
659 |
660 | - 列表布局
661 |
662 | 注意歌名过长会隐藏掉,然后歌手名字长度是不固定的,局部方式
663 |
664 | ```css
665 | .wrap {
666 | // white-space: nowrap;
667 | width: 300px;
668 | overflow: hidden;
669 | display: flex;
670 | height: 36px;
671 | .index {
672 | text-align: center;
673 | width: 26px;
674 | }
675 | .name {
676 | flex: 1;
677 | }
678 | .singer {
679 | float: right;
680 | font-size: 16px;
681 | opacity: 0.6;
682 | line-height: 36px;
683 | margin: 1px;
684 | }
685 | .duration {
686 | float: right;
687 | margin: 2px;
688 | width: 28px;
689 | font-size: 14px;
690 | opacity: 0.3;
691 | }
692 | ```
693 |
694 | - 播放器组件用了很多vue动画,[参考](https://cn.vuejs.org/v2/guide/transitions.html#CSS-%E5%8A%A8%E7%94%BB)
695 |
696 | - svg的title
697 |
698 | ```html
699 |
703 | ```
704 |
705 | - 注册全局filter(main.js)
706 |
707 | ```js
708 | Vue.filter('format', (interval) => {
709 | interval = interval | 0
710 | let minute = interval / 60 | 0
711 | let second = interval % 60
712 | if (second < 10) {
713 | second = '0' + second
714 | }
715 | return minute + ':' + second
716 | })
717 | ```
718 |
719 | - 递归深拷贝对象
720 |
721 | ```js
722 | export function copySong (obj) {
723 | if (typeof obj !== 'object') return obj
724 | let newobj = {}
725 | for (var attr in obj) {
726 | newobj[attr] = copySong(obj[attr])
727 | }
728 | return newobj
729 | }
730 | ```
731 |
732 | - 图片src没有的时候,解决出现的边框
733 |
734 | ```css
735 | img[src=""],img:not([src]){
736 | opacity:0;
737 | }
738 | ```
739 |
740 | - input里面的placeholder字体
741 |
742 | ```css
743 | input::-webkit-input-placeholder {
744 | color: #ccc;
745 | }
746 | ```
747 |
748 | ## 2.5 blog-box组件
749 |
750 | - 背景图片API
751 |
752 | ```html
753 |
754 | ```
755 |
756 | index是父组件传入的props,为了图片不重复(设置cookie达到不重复,第一次登录需要刷新)
757 |
758 | # 3.骚操作
759 |
760 | ## 3.1 主题透明度
761 |
762 | 为了调节全局的透明的,使用拖拽进度条调节css的var()
763 |
764 | - 进度条组件
765 |
766 | ```css
767 |
768 |
769 |
770 |
771 |
772 |
773 |
774 | {{parseInt(scale*100)}}
775 |
776 |
777 |
778 |
779 |
780 |
849 |
930 |
931 | ```
932 |
933 | 使用
934 |
935 | ```html
936 |
937 | ```
938 |
939 | - 全局透明度,index.html定义
940 |
941 | ```css
942 | :root {
943 | --color: rgba(255, 255, 255, 1);
944 | --opacity: 1;
945 | }
946 | ```
947 |
948 | 在需要改变的类样式中
949 |
950 | ```css
951 | background-color: var(--color);
952 | opacity: var(--opacity);
953 | ```
954 |
955 | 观察进度条变化变化,调节透明度
956 |
957 | ```js
958 | watch: {
959 | transparent (newVal) {
960 | if (newVal >= 0 && newVal <= 100) {
961 | let root = document.querySelector(':root')
962 | let value = '--color: ' + 'rgba(255, 255, 255, ' + (1 - newVal / 100) + ');'
963 | let opacity = '--opacity: ' + (1 - newVal / 100)
964 | root.setAttribute('style', value + opacity)
965 | }
966 | }
967 | }
968 | ```
969 |
970 | ------
971 |
972 | ## 3.2 打乱动画
973 |
974 | 主要在这两部分有shuffle动画,参考了Vue官网。
975 |
976 | - 引入shuffle函数(本来自己写的,结果动画不支持自己写的)
977 |
978 | ```js
979 | import _ from 'lodash/lodash'
980 | ```
981 |
982 | - 创建彩虹色函数和打乱函数
983 |
984 | ```js
985 | methods: {
986 | rainBow () {
987 | this.color = rainbowColor(this.tags.length, 15, 255)
988 | },
989 | shuffle () {
990 | this.tags = _.shuffle(this.tags)
991 | }
992 | },
993 | ```
994 |
995 | - 初始化颜色
996 |
997 | ```js
998 | mounted () {
999 | this.rainBow()
1000 | }
1001 | ```
1002 |
1003 | - 使用transition-group组件和绑定shuffle函数
1004 |
1005 | ```html
1006 |
1007 |
1008 |
1009 | {{tag.name+' ('+tag.number+')'}}
1010 |
1011 |
1012 |
1013 | ```
1014 |
1015 | - 添加css
1016 |
1017 | ```css
1018 | .cell-move {
1019 | transition: transform 1s;
1020 | }
1021 | ```
1022 |
1023 | ## 3.3 board组件
1024 |
1025 | 
1026 |
1027 | 这是一个基础组件,预留了slot
1028 |
1029 | ```html
1030 |
1031 |
1032 |
1033 | {{title}}
1034 |
1035 |
1036 |
1037 |
1038 |
1039 |
1040 | ```
1041 |
1042 | 注意点:
1043 |
1044 | ```html
1045 | 原本是{{content}}
,
1046 | ```
1047 |
1048 | 但是这样不支持content里面有换行,模板字符串也不行,只能用v-html,传入的
1049 |
1050 | content需要使用
1051 |
1052 | ```
1053 | 空格:
1054 | 换行:
1055 | ```
1056 |
1057 | 但是这样content不受组件内部css影响,需要在index.html的css设置
1058 |
1059 | # 4.markdown显示
1060 |
1061 | ## 4.1 md-view组件
1062 |
1063 | - package.json添加依赖dependencies
1064 |
1065 | ```json
1066 | "mavon-editor": "2.9.0",
1067 | "markdown-loader": "5.1.0",
1068 | "html-loader": "1.3.0"
1069 | ```
1070 |
1071 | - main.js全局注册
1072 |
1073 | ```js
1074 | import mavonEditor from 'mavon-editor'
1075 | import 'mavon-editor/dist/css/index.css'
1076 | Vue.use(mavonEditor)
1077 | ```
1078 |
1079 | - 引入博客的md文件,并绑定给组件
1080 |
1081 | ```js
1082 | import Blog from 'blogs/博客主页开发日志.md'
1083 | export default {
1084 | components: {},
1085 | props: {},
1086 | data () {
1087 | return {
1088 | value: Blog
1089 | }
1090 | },
1091 | watch: {},
1092 | computed: {
1093 | _isMobile () {
1094 | let flag = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)
1095 | return flag
1096 | }
1097 | },
1098 | methods: {},
1099 | created () {},
1100 | mounted () {
1101 | this.$refs.md.$nextTick(() => {
1102 | setTimeout(() => {
1103 | let blocks = this.$el.querySelectorAll('pre code')
1104 | blocks.forEach((block) => hljs.highlightBlock(block))
1105 | }, 1000)
1106 | })
1107 | }
1108 | }
1109 | ```
1110 |
1111 | - 使用组件
1112 |
1113 | ```html
1114 |
1115 | ```
1116 |
1117 | # 5.保存喜欢的音乐
1118 |
1119 | - 新建js/cache.js
1120 |
1121 | ```js
1122 | import storage from 'good-storage'
1123 |
1124 | const FAVORITE_KEY = '__favorite__'
1125 | const FAVORITE_MAX_LENGTH = 200
1126 |
1127 | function insertArray (arr, val, compare, maxLen) {
1128 | const index = arr.findIndex(compare)
1129 | if (index === 0) {
1130 | return
1131 | }
1132 | if (index > 0) {
1133 | arr.splice(index, 1)
1134 | }
1135 | arr.unshift(val)
1136 | if (maxLen && arr.length > maxLen) {
1137 | arr.pop()
1138 | }
1139 | }
1140 |
1141 | function deleteFromArray (arr, compare) {
1142 | const index = arr.findIndex(compare)
1143 | if (index > -1) {
1144 | arr.splice(index, 1)
1145 | }
1146 | }
1147 |
1148 | export function saveFavorite (song) {
1149 | let songs = storage.get(FAVORITE_KEY, [])
1150 | insertArray(songs, song, (item) => {
1151 | return song === item
1152 | }, FAVORITE_MAX_LENGTH)
1153 | storage.set(FAVORITE_KEY, songs)
1154 | return songs
1155 | }
1156 |
1157 | export function deleteFavorite (song) {
1158 | let songs = storage.get(FAVORITE_KEY, [])
1159 | deleteFromArray(songs, (item) => {
1160 | return song.id === item.id
1161 | })
1162 | storage.set(FAVORITE_KEY, songs)
1163 | return songs
1164 | }
1165 |
1166 | export function loadFavorite () {
1167 | return storage.get(FAVORITE_KEY, [])
1168 | }
1169 | ```
1170 |
1171 | - 在music-box组件引入
1172 |
1173 | ```js
1174 | import {saveFavorite, deleteFavorite, loadFavorite} from 'common/js/cache'
1175 | ```
1176 |
1177 | - 使用
1178 |
1179 | ```js
1180 | like (item) {
1181 | if (this.songs.includes(item.id)) return
1182 | let copy = copySong(item)
1183 | copy.source = 'Normal'
1184 | copy.index = this.songs.length
1185 | this.songs.push(copy.id)
1186 | this.songlist.push(copy)
1187 | this.playindex = copy.index
1188 | saveFavorite(copy.id)
1189 | },
1190 | unlike (item) {
1191 | let id = this.songs.indexOf(item.id)
1192 | if (id !== -1) {
1193 | this.songlist.splice(id, 1)
1194 | this.songs.splice(id, 1)
1195 | }
1196 | this.playindex = -1
1197 | this.songlist.forEach((item, index) => {
1198 | item.index = index
1199 | })
1200 | deleteFavorite(item.id)
1201 | },
1202 | ```
1203 |
1204 | # 6.代码高亮
1205 |
1206 | - 添加依赖
1207 |
1208 | ```json
1209 | "highlight.js": "10.1.2"
1210 | ```
1211 |
1212 | - md-view组件中引入
1213 |
1214 | ```js
1215 | import hljs from 'highlight.js'
1216 | ```
1217 |
1218 | 设置props,(组件自带的高亮有点问题,强制关掉,但是codeStyle='atom-one-dark'是为了引入hljs的css样式
1219 |
1220 | ```js
1221 |
1222 | ```
1223 |
1224 | - 加载后设置延时引入,在md-view的mounted方法添加
1225 |
1226 | ```js
1227 | mounted () {
1228 | this.$refs.md.$nextTick(() => {
1229 | setTimeout(() => {
1230 | let blocks = this.$el.querySelectorAll('pre code')
1231 | blocks.forEach((block) => hljs.highlightBlock(block))
1232 | }, 1000)
1233 | })
1234 | }
1235 | ```
1236 |
1237 | this.$refs.md.$nextTick是在md组件渲染完成后执行,设置了延时是为了打开博客时不卡,因为是循环替换样式
1238 |
1239 |
1240 | 注意
1241 |
1242 | ```css
1243 | 'rgba(0, 0, 0, calc(var(--opacity)*2))'
1244 | ```
1245 |
1246 | 是calc与var结合的写法
1247 |
1248 | - 修改代码背景色和默认色
1249 |
1250 | ```css
1251 | .markdown-body .highlight pre,
1252 | .markdown-body pre {
1253 | background-color: rgba(0, 0, 0, calc(var(--opacity)*2)) !important;
1254 | color: #fff;
1255 | }
1256 | .v-note-wrapper, .v-note-op, .v-show-content{
1257 | background-color: var(--color) !important;
1258 | }
1259 | .v-note-navigation-wrapper {
1260 | right: 12px !important;
1261 | bottom: auto !important;
1262 | height: auto !important;
1263 | border: none !important;
1264 | width: 225px !important;
1265 | background-color: rgba(255,255,255,0.4) !important;
1266 | opacity: var(--opacity);
1267 | }
1268 | .v-show-content {
1269 | height: 1200px !important;
1270 | }
1271 | .v-note-wrapper .v-note-panel .v-note-show .v-show-content.scroll-style-border-radius::-webkit-scrollbar, .v-note-wrapper .v-note-panel .v-note-show .v-show-content-html.scroll-style-border-radius::-webkit-scrollbar {
1272 | width: 12px !important;
1273 | background-color: var(--color) !important;
1274 | }
1275 | ```
1276 |
1277 |
--------------------------------------------------------------------------------
/src/blogs/imgs/image-20200818160039299.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/blogs/imgs/image-20200818160039299.png
--------------------------------------------------------------------------------
/src/blogs/imgs/image-20200818163913139-1598429442173.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/blogs/imgs/image-20200818163913139-1598429442173.png
--------------------------------------------------------------------------------
/src/blogs/imgs/image-20200818163913139.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/blogs/imgs/image-20200818163913139.png
--------------------------------------------------------------------------------
/src/blogs/imgs/image-20200819143501237.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/blogs/imgs/image-20200819143501237.png
--------------------------------------------------------------------------------
/src/blogs/imgs/image-20200820222252811.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/blogs/imgs/image-20200820222252811.png
--------------------------------------------------------------------------------
/src/blogs/imgs/image-20200822165552890-1598429509454.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/blogs/imgs/image-20200822165552890-1598429509454.png
--------------------------------------------------------------------------------
/src/blogs/imgs/image-20200822165552890.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/blogs/imgs/image-20200822165552890.png
--------------------------------------------------------------------------------
/src/blogs/imgs/image-20200822172328629.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/blogs/imgs/image-20200822172328629.png
--------------------------------------------------------------------------------
/src/blogs/imgs/image-20200822172553110.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/blogs/imgs/image-20200822172553110.png
--------------------------------------------------------------------------------
/src/blogs/imgs/image-20200822174649258.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/blogs/imgs/image-20200822174649258.png
--------------------------------------------------------------------------------
/src/blogs/imgs/image-20200824161718758.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/blogs/imgs/image-20200824161718758.png
--------------------------------------------------------------------------------
/src/blogs/imgs/image-20200826154211160.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/blogs/imgs/image-20200826154211160.png
--------------------------------------------------------------------------------
/src/blogs/imgs/image-20200826154240558.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/blogs/imgs/image-20200826154240558.png
--------------------------------------------------------------------------------
/src/blogs/imgs/image-20200826213007574.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/blogs/imgs/image-20200826213007574.png
--------------------------------------------------------------------------------
/src/blogs/imgs/image-20200826213021445.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/blogs/imgs/image-20200826213021445.png
--------------------------------------------------------------------------------
/src/blogs/imgs/image-20200826213037228.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/blogs/imgs/image-20200826213037228.png
--------------------------------------------------------------------------------
/src/blogs/imgs/image-20200826213524137.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/blogs/imgs/image-20200826213524137.png
--------------------------------------------------------------------------------
/src/blogs/test.md:
--------------------------------------------------------------------------------
1 | # 1 标题1
2 |
3 | # 2 标题2
4 |
5 | ## 2.1 二级
6 |
7 | ## 2.2 二级
8 |
9 | 对方对方的
10 |
11 | ```js
12 | like (item) {
13 | if (this.songs.includes(item.id)) return
14 | let copy = copySong(item)
15 | copy.source = 'Normal'
16 | copy.index = this.songs.length
17 | this.songs.push(copy.id)
18 | this.songlist.push(copy)
19 | this.playindex = copy.index
20 | saveFavorite(copy.id)
21 | },
22 | unlike (item) {
23 | let id = this.songs.indexOf(item.id)
24 | if (id !== -1) {
25 | this.songlist.splice(id, 1)
26 | this.songs.splice(id, 1)
27 | }
28 | this.playindex = -1
29 | this.songlist.forEach((item, index) => {
30 | item.index = index
31 | })
32 | deleteFavorite(item.id)
33 | },
34 | ```
35 |
36 |
37 |
38 | # 3 标题3
39 |
40 | # 4 标题4
41 |
42 | # 5 标题5
43 |
44 | # 7 标题5
45 |
46 | # 8 标题5
47 |
48 | # 9 标题5
49 |
50 | # 10 标题5
51 |
52 | # 11 标题6
53 |
54 | # 12 标题6
55 |
56 | # 13 标题6
57 |
58 | # 14 标题6
59 |
60 | # 15 标题6
--------------------------------------------------------------------------------
/src/blogs/博客主页开发日志.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/src/blogs/博客主页开发日志.md
--------------------------------------------------------------------------------
/src/common/css/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | cursor: url('../../assets/cursor.cur'), default;
3 | }
4 | .icon {
5 | width: 1em; height: 1em;
6 | vertical-align: -0.15em;
7 | fill: currentColor;
8 | overflow: hidden;
9 | font-size: 40px;
10 | }
11 | * {
12 | margin: 0;
13 | padding: 0;
14 | }
15 | :root {
16 | --color: rgba(255, 255, 255, 1);
17 | --opacity: 1;
18 | --margin: 6;
19 | }
20 | a {
21 | text-decoration: none;
22 | color: rgb(60, 136, 236);
23 | }
24 |
--------------------------------------------------------------------------------
/src/common/js/cache.js:
--------------------------------------------------------------------------------
1 | import storage from 'good-storage'
2 |
3 | const FAVORITE_KEY = '__favorite__'
4 | const FAVORITE_MAX_LENGTH = 200
5 |
6 | function insertArray (arr, val, compare, maxLen) {
7 | const index = arr.findIndex(compare)
8 | if (index === 0) {
9 | return
10 | }
11 | if (index > 0) {
12 | arr.splice(index, 1)
13 | }
14 | arr.unshift(val)
15 | if (maxLen && arr.length > maxLen) {
16 | arr.pop()
17 | }
18 | }
19 |
20 | function deleteFromArray (arr, compare) {
21 | const index = arr.findIndex(compare)
22 | if (index > -1) {
23 | arr.splice(index, 1)
24 | }
25 | }
26 |
27 | export function saveFavorite (song) {
28 | let songs = storage.get(FAVORITE_KEY, [])
29 | insertArray(songs, song, (item) => {
30 | return song === item
31 | }, FAVORITE_MAX_LENGTH)
32 | storage.set(FAVORITE_KEY, songs)
33 | return songs
34 | }
35 |
36 | export function deleteFavorite (song) {
37 | let songs = storage.get(FAVORITE_KEY, [])
38 | deleteFromArray(songs, (item) => {
39 | return song.id === item.id
40 | })
41 | storage.set(FAVORITE_KEY, songs)
42 | return songs
43 | }
44 |
45 | export function loadFavorite () {
46 | return storage.get(FAVORITE_KEY, [])
47 | }
48 |
--------------------------------------------------------------------------------
/src/common/js/config.js:
--------------------------------------------------------------------------------
1 | export const HOST = 'https://www.luckyclover.top/api'
2 | export const ERR_OK = 200
3 |
4 | export const playMode = {
5 | sequence: 0,
6 | loop: 1,
7 | random: 2
8 | }
9 |
--------------------------------------------------------------------------------
/src/common/js/lyric.js:
--------------------------------------------------------------------------------
1 | export function parseLyric (data) {
2 | const lyricList = []
3 | const lyricTime = []
4 | let array = data.split('\n')
5 | let timeReg = /\[(\d*:\d*\.\d*)\]/
6 | for (let element of array) {
7 | if (element.indexOf(']') === -1) continue
8 | let lyr = element.split(']')[1]
9 | let res = timeReg.exec(element)
10 | if (res == null || lyr.length <= 1) continue
11 | let timeStr = res[1]
12 | let min = parseInt(timeStr.split(':')[0]) * 60
13 | let sec = parseFloat(timeStr.split(':')[1])
14 | let time = +Number(min + sec).toFixed(2)
15 | lyricTime.push(time)
16 | lyricList.push(lyr)
17 | }
18 | // console.log([lyricList, lyricTime])
19 | return [lyricList, lyricTime]
20 | }
21 |
22 | export function curLyricIndex (lyricTime, curTime) {
23 | let id = 0
24 | let i = 0
25 | for (let element of lyricTime) {
26 | id = i
27 | if (element > (curTime + 0.2)) {
28 | // console.log(element, curTime, id - 1)
29 | break
30 | }
31 | id += 1
32 | i++
33 | }
34 | if (id <= 0) return 0
35 | return id - 1
36 | }
37 |
--------------------------------------------------------------------------------
/src/common/js/song.js:
--------------------------------------------------------------------------------
1 | export function copySong (obj) {
2 | if (typeof obj !== 'object') return obj
3 | let newobj = {}
4 | for (var attr in obj) {
5 | newobj[attr] = copySong(obj[attr])
6 | }
7 | return newobj
8 | }
9 |
--------------------------------------------------------------------------------
/src/common/js/util.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash/lodash'
2 | // 截流函数
3 | export function debounce (func, delay) {
4 | let timer
5 | return function (...args) {
6 | if (timer) {
7 | clearTimeout(timer)
8 | }
9 | timer = setTimeout(() => {
10 | func.apply(this, args)
11 | }, delay)
12 | }
13 | }
14 | // 排序数组
15 | export function sortList (n) {
16 | let foo = []
17 | for (let i = 0; i < n; i++) {
18 | foo.push(i)
19 | }
20 | return foo
21 | }
22 |
23 | // 彩虹色
24 | export function rainbowColor (len, min, max) {
25 | if (min < 0 || max > 255) return
26 | let arr = sortList(len)
27 | let bgc = new Array(len).fill('rgba(')
28 | // let color = arr.map((a) => Math.floor(255 * (a + 1) / len))
29 | let color = arr.map((a) => Math.floor((max - min) * a / (len - 1)) + min)
30 | for (let i = 0; i < 3; i++) {
31 | let shu = _.shuffle(arr)
32 | for (let j = 0; j < shu.length; j++) {
33 | bgc[j] += color[shu[j]] + ','
34 | if (i === 2) {
35 | bgc[j] += '0.8)'
36 | }
37 | }
38 | }
39 | return bgc
40 | }
41 |
--------------------------------------------------------------------------------
/src/components/bg/bg.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
28 |
42 |
--------------------------------------------------------------------------------
/src/components/bottom/bottom.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 本站已上线: {{time}}
4 |
5 |
6 |
7 |
8 |
48 |
61 |
--------------------------------------------------------------------------------
/src/components/center-content/center-content.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
30 |
38 |
--------------------------------------------------------------------------------
/src/components/center/center.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
34 |
47 |
--------------------------------------------------------------------------------
/src/components/left-content/left-content.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
28 |
37 |
--------------------------------------------------------------------------------
/src/components/m-header/m-header.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
41 |
60 |
--------------------------------------------------------------------------------
/src/components/main-content/main-content.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
55 |
61 |
--------------------------------------------------------------------------------
/src/components/md-view/md-view.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
37 |
38 |
71 |
--------------------------------------------------------------------------------
/src/components/music-box/music-box.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 音乐盒
5 |
6 |
7 |
10 |
11 |
15 |
19 |
20 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | -
33 |
36 |
39 |
40 |
{{index + 1}}
41 |
{{song.name}}
42 |
{{song.duration / 1000 | format}}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | -
52 |
55 |
58 |
59 |
{{index + 1}}
60 |
{{song.name}}
61 |
{{song.singerName}}
62 |
{{song.duration / 1000 | format}}
63 |
64 |
65 |
66 |
67 |
68 |
69 |
72 |
73 | 音乐盒里空空如也
74 |
赶快去添加喜欢的歌曲吧
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
249 |
498 |
--------------------------------------------------------------------------------
/src/components/right-content/right-content.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
32 |
41 |
--------------------------------------------------------------------------------
/src/components/search-box/search-box.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
43 |
90 |
--------------------------------------------------------------------------------
/src/components/tab/tab.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 | 首页
8 |
9 |
10 |
11 | {{title.name}}
12 | {{title.count}}
13 |
14 |
15 |
16 |
17 |
18 |
63 |
140 |
--------------------------------------------------------------------------------
/src/components/user-info/user-info.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
{{user.name}}
8 |
9 |
10 |
11 | {{chinese(val)}}
12 |
13 |
14 | {{key}}
15 |
16 |
17 |
18 |
55 |
56 |
57 |
58 |
101 |
208 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 | import App from './App'
5 | import router from './router'
6 | import vuescroll from 'vuescroll'
7 | import mavonEditor from 'mavon-editor'
8 | import 'mavon-editor/dist/css/index.css'
9 |
10 | Vue.use(mavonEditor)
11 | Vue.use(vuescroll)
12 |
13 | Vue.prototype.$vuescrollConfig = {
14 | vuescroll: {
15 | mode: 'native', // 选择一个模式, native 或者 slide(pc&app)
16 | sizeStrategy: 'percent', // 如果父容器不是固定高度,请设置为 number , 否则保持默认的percent即可
17 | detectResize: true // 是否检测内容尺寸发生变化
18 | },
19 | scrollPanel: {
20 | initialScrollY: false, // 只要组件mounted之后自动滚动的距离。 例如 100 or 10%
21 | initialScrollX: false,
22 | scrollingX: false, // 是否启用 x 或者 y 方向上的滚动
23 | scrollingY: true,
24 | speed: 100, // 多长时间内完成一次滚动。 数值越小滚动的速度越快
25 | easing: undefined, // 滚动动画 参数通animation
26 | verticalNativeBarPos: 'right'// 原生滚动条的位置
27 | },
28 | rail: { // 轨道
29 | background: '#c3c3c3', // 轨道的背景色
30 | opacity: 0,
31 | size: '6px',
32 | specifyBorderRadius: false, // 是否指定轨道的 borderRadius, 如果不那么将会自动设置
33 | gutterOfEnds: null,
34 | gutterOfSide: '0px', // 轨道距 x 和 y 轴两端的距离
35 | keepShow: false // 是否即使 bar 不存在的情况下也保持显示
36 | },
37 | bar: {
38 | showDelay: 1000, // 在鼠标离开容器后多长时间隐藏滚动条
39 | onlyShowBarOnScroll: false, // 当页面滚动时显示
40 | keepShow: false, // 是否一直显示
41 | background: '#c3c3c3',
42 | opacity: 1,
43 | hoverStyle: false,
44 | specifyBorderRadius: false,
45 | minSize: false,
46 | size: '6px',
47 | disable: false // 是否禁用滚动条
48 | }, // 在这里设置全局默认配置
49 | name: 'vuescroll' // 在这里自定义组件名字,默认是vueScroll
50 | }
51 |
52 | Vue.config.productionTip = false
53 |
54 | Vue.filter('format', (interval) => {
55 | interval = interval | 0
56 | let minute = interval / 60 | 0
57 | let second = interval % 60
58 | if (second < 10) {
59 | second = '0' + second
60 | }
61 | return minute + ':' + second
62 | })
63 | /* eslint-disable no-new */
64 | new Vue({
65 | el: '#app',
66 | router,
67 | components: { App },
68 | template: ''
69 | })
70 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 |
4 | const MainContent = (resolve) => {
5 | import('cpnts/main-content/main-content').then((module) => {
6 | resolve(module)
7 | })
8 | }
9 | const MdView = (resolve) => {
10 | import('cpnts/md-view/md-view').then((module) => {
11 | resolve(module)
12 | })
13 | }
14 | Vue.use(Router)
15 |
16 | export default new Router({
17 | mode: 'history',
18 | routes: [
19 | // {
20 | // path: '/',
21 | // name: 'HelloWorld',
22 | // component: HelloWorld
23 | // }
24 | {
25 | path: '/',
26 | name: 'MainContent',
27 | component: MainContent
28 | },
29 | {
30 | path: '/',
31 | name: 'MdView',
32 | component: MdView
33 | }
34 | ]
35 | })
36 |
--------------------------------------------------------------------------------
/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iCodek/vue-blog/27998e1559ee7813c9ffc6560dc41f4f76b4c953/static/.gitkeep
--------------------------------------------------------------------------------