├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .postcssrc.js ├── LICENSE ├── 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 └── test.env.js ├── index.html ├── package-lock.json ├── package.json ├── src ├── App.vue ├── api │ └── index.js ├── assets │ ├── avatars.jpeg │ ├── favicon.ico │ ├── header-bg.jpg │ ├── qrcode.jpg │ ├── quote-l.svg │ └── quote-r.svg ├── components │ ├── about.vue │ ├── archives.vue │ ├── article.vue │ ├── common │ │ ├── background.vue │ │ ├── footerBar.vue │ │ ├── headerBar.vue │ │ ├── navBar.vue │ │ ├── qrcode.vue │ │ ├── scrollBar.vue │ │ └── selfInfo.vue │ ├── index.vue │ ├── tagArticle.vue │ └── tags.vue ├── main.js ├── router │ └── index.js ├── style │ ├── archives-style.less │ ├── md.css │ └── style.less └── util │ ├── eventBus.js │ └── util.js ├── static └── .gitkeep └── test ├── e2e ├── custom-assertions │ └── elementCount.js ├── nightwatch.conf.js ├── runner.js └── specs │ └── test.js └── unit ├── .eslintrc ├── jest.conf.js ├── setup.js └── specs └── HelloWorld.spec.js /.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 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.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 | /test/unit/coverage/ 6 | -------------------------------------------------------------------------------- /.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: ['vue'], 20 | // add your custom rules here 21 | rules: { 22 | // allow async-await 23 | 'generator-star-spacing': 'off', 24 | // allow debugger during development 25 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 26 | 'space-before-function-paren': [0] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.css linguist-language=vue -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | /test/unit/coverage/ 8 | /test/e2e/reports/ 9 | selenium-debug.log 10 | 11 | # Editor directories and files 12 | .idea 13 | .vscode 14 | *.suo 15 | *.ntvs* 16 | *.njsproj 17 | *.sln 18 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Yinkai Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 我的博客 2 | 3 | 一直以来都想用自己的技术做一些个人项目, 之前的[博客](https://chenyinkai.github.io/)是基于 `hexo` 搭建, 感觉啥都是别人帮你写好了, 于是便决定自己搞一个, 断断续续地弄了一段时间, 也总算是完成了个简单版本并部署上线。 4 | 5 | [博客线上地址](http://www.cykspace.com) 6 | 7 | 博客地址:[cykspace](https://github.com/chenyinkai/cykspace) 8 | 9 | 博客后台:[cykspace-node](https://github.com/chenyinkai/cykspace-node) 10 | 11 | 如果觉得还行的话, 欢迎给个 `star` 哈. 感谢~~ 12 | 13 | > 由于不会设计, 博客主题是模仿 [hexo-theme-next](https://github.com/iissnan/hexo-theme-next) 的布局样式, 感谢作者的开源分享. 14 | 15 | ## 相关技术栈 16 | 17 | + vue + vue-router + less + axios 18 | + node + koa2 + koa-router 19 | + mysql + sequelize 20 | + nginx + pm2 21 | 22 | ## 未来可能加入 23 | 24 | + ssr 25 | + 后台管理 26 | + 评论 27 | + 网易云音乐 28 | + 页面数据可视化统计 29 | + ... 30 | 31 | ## 测试 32 | 33 | > clone 34 | 35 | ```bash 36 | git clone git@github.com:chenyinkai/cykspace.git 37 | ``` 38 | 39 | > install 40 | 41 | ```bash 42 | cd cykspace 43 | 44 | npm install 45 | ``` 46 | 47 | > dev 48 | 49 | ```bash 50 | npm run dev # 打开 http://localhost:8081/ 51 | ``` 52 | 53 | > build 54 | 55 | ```bash 56 | npm run build 57 | ``` 58 | 59 | 如果在测试期间遇到什么问题, 可留言给我, 我会及时回复, 感谢~~ 60 | -------------------------------------------------------------------------------- /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/chenyinkai/cykspace/8c9863e8096f4ea99df88a137b390c083fa2b861/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 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass'), 64 | stylus: generateLoaders('stylus'), 65 | styl: generateLoaders('stylus') 66 | } 67 | } 68 | 69 | // Generate loaders for standalone style files (outside of .vue) 70 | exports.styleLoaders = function (options) { 71 | const output = [] 72 | const loaders = exports.cssLoaders(options) 73 | 74 | for (const extension in loaders) { 75 | const loader = loaders[extension] 76 | output.push({ 77 | test: new RegExp('\\.' + extension + '$'), 78 | use: loader 79 | }) 80 | } 81 | 82 | return output 83 | } 84 | 85 | exports.createNotifierCallback = () => { 86 | const notifier = require('node-notifier') 87 | 88 | return (severity, errors) => { 89 | if (severity !== 'error') return 90 | 91 | const error = errors[0] 92 | const filename = error.file && error.file.split('!').pop() 93 | 94 | notifier.notify({ 95 | title: packageConfig.name, 96 | message: severity + ': ' + error.name, 97 | subtitle: filename || '', 98 | icon: path.join(__dirname, 'logo.png') 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /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 | } 40 | }, 41 | module: { 42 | rules: [ 43 | ...(config.dev.useEslint ? [createLintingRule()] : []), 44 | { 45 | test: /\.vue$/, 46 | loader: 'vue-loader', 47 | options: vueLoaderConfig 48 | }, 49 | { 50 | test: /\.js$/, 51 | loader: 'babel-loader', 52 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 53 | }, 54 | { 55 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 56 | loader: 'url-loader', 57 | options: { 58 | limit: 10000, 59 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 60 | } 61 | }, 62 | { 63 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 64 | loader: 'url-loader', 65 | options: { 66 | limit: 10000, 67 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 68 | } 69 | }, 70 | { 71 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 72 | loader: 'url-loader', 73 | options: { 74 | limit: 10000, 75 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 76 | } 77 | } 78 | ] 79 | }, 80 | node: { 81 | // prevent webpack from injecting useless setImmediate polyfill because Vue 82 | // source contains it (although only uses it if it's native). 83 | setImmediate: false, 84 | // prevent webpack from injecting mocks to Node native modules 85 | // that does not make sense for the client 86 | dgram: 'empty', 87 | fs: 'empty', 88 | net: 'empty', 89 | tls: 'empty', 90 | child_process: 'empty' 91 | }, 92 | externals: { 93 | 'vue': 'Vue', 94 | 'vue-router': 'VueRouter', 95 | 'axios': 'axios' 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /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 | }), 60 | // copy custom static assets 61 | new CopyWebpackPlugin([ 62 | { 63 | from: path.resolve(__dirname, '../static'), 64 | to: config.dev.assetsSubDirectory, 65 | ignore: ['.*'] 66 | } 67 | ]) 68 | ] 69 | }) 70 | 71 | module.exports = new Promise((resolve, reject) => { 72 | portfinder.basePort = process.env.PORT || config.dev.port 73 | portfinder.getPort((err, port) => { 74 | if (err) { 75 | reject(err) 76 | } else { 77 | // publish the new Port, necessary for e2e tests 78 | process.env.PORT = port 79 | // add port to devServer config 80 | devWebpackConfig.devServer.port = port 81 | 82 | // Add FriendlyErrorsPlugin 83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 84 | compilationSuccessInfo: { 85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 86 | }, 87 | onErrors: config.dev.notifyOnErrors 88 | ? utils.createNotifierCallback() 89 | : undefined 90 | })) 91 | 92 | resolve(devWebpackConfig) 93 | } 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /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 = process.env.NODE_ENV === 'testing' 15 | ? require('../config/test.env') 16 | : require('../config/prod.env') 17 | 18 | const webpackConfig = merge(baseWebpackConfig, { 19 | module: { 20 | rules: utils.styleLoaders({ 21 | sourceMap: config.build.productionSourceMap, 22 | extract: true, 23 | usePostCSS: true 24 | }) 25 | }, 26 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 27 | output: { 28 | path: config.build.assetsRoot, 29 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 30 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 31 | }, 32 | plugins: [ 33 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 34 | new webpack.DefinePlugin({ 35 | 'process.env': env 36 | }), 37 | new UglifyJsPlugin({ 38 | uglifyOptions: { 39 | compress: { 40 | warnings: false 41 | } 42 | }, 43 | sourceMap: config.build.productionSourceMap, 44 | parallel: true 45 | }), 46 | // extract css into its own file 47 | new ExtractTextPlugin({ 48 | filename: utils.assetsPath('css/[name].[contenthash].css'), 49 | // Setting the following option to `false` will not extract CSS from codesplit chunks. 50 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. 51 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 52 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 53 | allChunks: true, 54 | }), 55 | // Compress extracted CSS. We are using this plugin so that possible 56 | // duplicated CSS from different components can be deduped. 57 | new OptimizeCSSPlugin({ 58 | cssProcessorOptions: config.build.productionSourceMap 59 | ? { safe: true, map: { inline: false } } 60 | : { safe: true } 61 | }), 62 | // generate dist index.html with correct asset hash for caching. 63 | // you can customize output by editing /index.html 64 | // see https://github.com/ampedandwired/html-webpack-plugin 65 | new HtmlWebpackPlugin({ 66 | filename: process.env.NODE_ENV === 'testing' 67 | ? 'index.html' 68 | : config.build.index, 69 | template: 'index.html', 70 | inject: true, 71 | minify: { 72 | removeComments: true, 73 | collapseWhitespace: true, 74 | removeAttributeQuotes: true 75 | // more options: 76 | // https://github.com/kangax/html-minifier#options-quick-reference 77 | }, 78 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 79 | chunksSortMode: 'dependency' 80 | }), 81 | // keep module.id stable when vendor modules does not change 82 | new webpack.HashedModuleIdsPlugin(), 83 | // enable scope hoisting 84 | new webpack.optimize.ModuleConcatenationPlugin(), 85 | // split vendor js into its own file 86 | new webpack.optimize.CommonsChunkPlugin({ 87 | name: 'vendor', 88 | minChunks (module) { 89 | // any required modules inside node_modules are extracted to vendor 90 | return ( 91 | module.resource && 92 | /\.js$/.test(module.resource) && 93 | module.resource.indexOf( 94 | path.join(__dirname, '../node_modules') 95 | ) === 0 96 | ) 97 | } 98 | }), 99 | // extract webpack runtime and module manifest to its own file in order to 100 | // prevent vendor hash from being updated whenever app bundle is updated 101 | new webpack.optimize.CommonsChunkPlugin({ 102 | name: 'manifest', 103 | minChunks: Infinity 104 | }), 105 | // This instance extracts shared chunks from code splitted chunks and bundles them 106 | // in a separate chunk, similar to the vendor chunk 107 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 108 | new webpack.optimize.CommonsChunkPlugin({ 109 | name: 'app', 110 | async: 'vendor-async', 111 | children: true, 112 | minChunks: 3 113 | }), 114 | 115 | // copy custom static assets 116 | new CopyWebpackPlugin([ 117 | { 118 | from: path.resolve(__dirname, '../static'), 119 | to: config.build.assetsSubDirectory, 120 | ignore: ['.*'] 121 | } 122 | ]) 123 | ] 124 | }) 125 | 126 | if (config.build.productionGzip) { 127 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 128 | 129 | webpackConfig.plugins.push( 130 | new CompressionWebpackPlugin({ 131 | asset: '[path].gz[query]', 132 | algorithm: 'gzip', 133 | test: new RegExp( 134 | '\\.(' + 135 | config.build.productionGzipExtensions.join('|') + 136 | ')$' 137 | ), 138 | threshold: 10240, 139 | minRatio: 0.8 140 | }) 141 | ) 142 | } 143 | 144 | if (config.build.bundleAnalyzerReport) { 145 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 146 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 147 | } 148 | 149 | module.exports = webpackConfig 150 | -------------------------------------------------------------------------------- /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: 8081, // 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: false, 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 | -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const devEnv = require('./dev.env') 4 | 5 | module.exports = merge(devEnv, { 6 | NODE_ENV: '"testing"' 7 | }) 8 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 博客-善良的乌贼 9 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cykspace", 3 | "version": "1.0.0", 4 | "description": "cykspace code", 5 | "author": "chenyinkai <287696322@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 | "unit": "jest --config test/unit/jest.conf.js --coverage", 11 | "e2e": "node test/e2e/runner.js", 12 | "test": "npm run unit && npm run e2e", 13 | "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs", 14 | "build": "node build/build.js" 15 | }, 16 | "dependencies": { 17 | "@fortawesome/fontawesome": "^1.1.7", 18 | "@fortawesome/fontawesome-free-brands": "^5.0.12", 19 | "@fortawesome/fontawesome-free-solid": "^5.0.10", 20 | "@fortawesome/vue-fontawesome": "0.0.22", 21 | "axios": "^0.18.0", 22 | "normalize.css": "^8.0.0", 23 | "nprogress": "^0.2.0", 24 | "vue": "^2.5.2", 25 | "vue-router": "^3.0.1" 26 | }, 27 | "devDependencies": { 28 | "autoprefixer": "^7.1.2", 29 | "babel-core": "^6.22.1", 30 | "babel-eslint": "^8.2.1", 31 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 32 | "babel-jest": "^21.0.2", 33 | "babel-loader": "^7.1.1", 34 | "babel-plugin-dynamic-import-node": "^1.2.0", 35 | "babel-plugin-syntax-jsx": "^6.18.0", 36 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", 37 | "babel-plugin-transform-runtime": "^6.22.0", 38 | "babel-plugin-transform-vue-jsx": "^3.5.0", 39 | "babel-preset-env": "^1.3.2", 40 | "babel-preset-stage-2": "^6.22.0", 41 | "babel-register": "^6.22.0", 42 | "chalk": "^2.0.1", 43 | "chromedriver": "^2.27.2", 44 | "copy-webpack-plugin": "^4.0.1", 45 | "cross-spawn": "^5.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 | "jest": "^22.0.4", 61 | "jest-serializer-vue": "^0.3.0", 62 | "less": "^3.0.2", 63 | "less-loader": "^4.1.0", 64 | "nightwatch": "^0.9.12", 65 | "node-notifier": "^5.1.2", 66 | "optimize-css-assets-webpack-plugin": "^3.2.0", 67 | "ora": "^1.2.0", 68 | "portfinder": "^1.0.13", 69 | "postcss-import": "^11.0.0", 70 | "postcss-loader": "^2.0.8", 71 | "postcss-url": "^7.2.1", 72 | "rimraf": "^2.6.0", 73 | "selenium-server": "^3.0.1", 74 | "semver": "^5.3.0", 75 | "shelljs": "^0.7.6", 76 | "uglifyjs-webpack-plugin": "^1.1.1", 77 | "url-loader": "^0.5.8", 78 | "vue-jest": "^1.0.2", 79 | "vue-loader": "^13.3.0", 80 | "vue-style-loader": "^3.0.1", 81 | "vue-template-compiler": "^2.5.2", 82 | "webpack": "^3.10.0", 83 | "webpack-bundle-analyzer": "^2.9.0", 84 | "webpack-dev-server": "^2.9.7", 85 | "webpack-merge": "^4.1.0" 86 | }, 87 | "engines": { 88 | "node": ">= 6.0.0", 89 | "npm": ">= 3.0.0" 90 | }, 91 | "browserslist": [ 92 | "> 1%", 93 | "last 2 versions", 94 | "not ie <= 8" 95 | ] 96 | } 97 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 32 | 33 | 41 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import NProgress from 'nprogress' 3 | 4 | const baseUrl = process.env.NODE_ENV === 'production' ? 'https://www.cykspace.com/v1/' : 'https://www.cykspace.com/v1/' 5 | 6 | axios.interceptors.request.use( 7 | function (config) { 8 | NProgress.start() 9 | return config 10 | }, 11 | function (error) { 12 | return Promise.reject(error) 13 | } 14 | ) 15 | 16 | axios.interceptors.response.use( 17 | function (response) { 18 | NProgress.done() 19 | return response 20 | }, 21 | function (error) { 22 | return Promise.reject(error) 23 | } 24 | ) 25 | 26 | // 获取所有的文章 27 | export const getArticleAll = (params = {}) => axios.get(`${baseUrl}articles`, { 28 | params 29 | }) 30 | 31 | // 获取文章详情 32 | export const getDetail = (postId) => axios.get(`${baseUrl}details?postId=${postId}`) 33 | 34 | // 获取所有的标签 35 | export const getTagsAll = () => axios.get(`${baseUrl}tags`) 36 | 37 | // 获取标签下所有的文章 38 | export const getTagArticle = (tagId) => axios.get(`${baseUrl}tags?tagId=${tagId}`) 39 | 40 | // 文章阅读次数 41 | export const readCountIncrease = (postId) => axios.post(`${baseUrl}readNum`, { 42 | postId: postId 43 | }) 44 | -------------------------------------------------------------------------------- /src/assets/avatars.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenyinkai/cykspace/8c9863e8096f4ea99df88a137b390c083fa2b861/src/assets/avatars.jpeg -------------------------------------------------------------------------------- /src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenyinkai/cykspace/8c9863e8096f4ea99df88a137b390c083fa2b861/src/assets/favicon.ico -------------------------------------------------------------------------------- /src/assets/header-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenyinkai/cykspace/8c9863e8096f4ea99df88a137b390c083fa2b861/src/assets/header-bg.jpg -------------------------------------------------------------------------------- /src/assets/qrcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenyinkai/cykspace/8c9863e8096f4ea99df88a137b390c083fa2b861/src/assets/qrcode.jpg -------------------------------------------------------------------------------- /src/assets/quote-l.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/quote-r.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/about.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 71 | 72 | 143 | -------------------------------------------------------------------------------- /src/components/archives.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 58 | 59 | 62 | -------------------------------------------------------------------------------- /src/components/article.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 34 | 35 | 53 | -------------------------------------------------------------------------------- /src/components/common/background.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenyinkai/cykspace/8c9863e8096f4ea99df88a137b390c083fa2b861/src/components/common/background.vue -------------------------------------------------------------------------------- /src/components/common/footerBar.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 34 | 35 | 53 | -------------------------------------------------------------------------------- /src/components/common/headerBar.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 62 | 63 | 181 | -------------------------------------------------------------------------------- /src/components/common/navBar.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 58 | 59 | 117 | -------------------------------------------------------------------------------- /src/components/common/qrcode.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | 20 | 32 | -------------------------------------------------------------------------------- /src/components/common/scrollBar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 67 | 68 | 111 | -------------------------------------------------------------------------------- /src/components/common/selfInfo.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 19 | -------------------------------------------------------------------------------- /src/components/index.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 105 | 106 | 236 | -------------------------------------------------------------------------------- /src/components/tagArticle.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 58 | 59 | 66 | -------------------------------------------------------------------------------- /src/components/tags.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 44 | 45 | 81 | -------------------------------------------------------------------------------- /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 './style/style.less' 7 | 8 | Vue.config.productionTip = false 9 | 10 | /* eslint-disable no-new */ 11 | new Vue({ 12 | el: '#app', 13 | router, 14 | components: { App }, 15 | template: '' 16 | }) 17 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | // 加载优化 -- 按需加载,cdn 2 | // import Vue from 'vue' 3 | import Router from 'vue-router' 4 | // import about from '@/components/about' 5 | // import archives from '@/components/archives' 6 | // import index from '@/components/index' 7 | // import article from '@/components/article' 8 | // import tags from '@/components/tags' 9 | // import tagArticle from '@/components/tagArticle' 10 | const about = r => require.ensure([], () => r(require('@/components/about'))) 11 | const archives = r => require.ensure([], () => r(require('@/components/archives'))) 12 | const index = r => require.ensure([], () => r(require('@/components/index'))) 13 | const article = r => require.ensure([], () => r(require('@/components/article'))) 14 | const tags = r => require.ensure([], () => r(require('@/components/tags'))) 15 | const tagArticle = r => require.ensure([], () => r(require('@/components/tagArticle'))) 16 | 17 | // Vue.use(Router) 18 | 19 | export default new Router({ 20 | mode: 'history', 21 | routes: [ 22 | { 23 | path: '/', 24 | name: 'index', 25 | component: index 26 | }, 27 | { 28 | path: '/article/:id', 29 | name: 'article', 30 | component: article 31 | }, 32 | { 33 | path: '/archives', 34 | name: 'archives', 35 | component: archives 36 | }, 37 | { 38 | path: '/about', 39 | name: 'about', 40 | component: about 41 | }, 42 | { 43 | path: '/tags', 44 | name: 'tags', 45 | component: tags 46 | }, 47 | { 48 | path: '/tags/:id', 49 | name: 'tagArticle', 50 | component: tagArticle 51 | } 52 | ] 53 | }) 54 | -------------------------------------------------------------------------------- /src/style/archives-style.less: -------------------------------------------------------------------------------- 1 | .content { 2 | min-height: 320px; 3 | .posts-expand { 4 | padding-top: 40px; 5 | } 6 | .posts-collapse { 7 | position: relative; 8 | margin-left: 55px; 9 | top: -10px; 10 | opacity: 0; 11 | animation: down 0.5s 0.5s linear; 12 | animation-fill-mode: both; 13 | &::after { 14 | content: ' '; 15 | position: absolute; 16 | top: 20px; 17 | left: 0; 18 | margin-left: -2px; 19 | width: 4px; 20 | height: 100%; 21 | background: #f5f5f5; 22 | z-index: -1; 23 | } 24 | } 25 | .archive-move-on { 26 | position: absolute; 27 | top: 11px; 28 | left: 0; 29 | margin-left: -6px; 30 | width: 10px; 31 | height: 10px; 32 | opacity: 0.5; 33 | background: #555; 34 | border: 1px solid #fff; 35 | border-radius: 50%; 36 | } 37 | .archive-page-counter { 38 | position: relative; 39 | top: 3px; 40 | left: 20px; 41 | line-height: 2; 42 | } 43 | .collection-title { 44 | position: relative; 45 | margin: 60px 0; 46 | line-height: 2; 47 | &::before { 48 | content: ' '; 49 | position: absolute; 50 | left: 0; 51 | top: 50%; 52 | margin-left: -4px; 53 | margin-top: -4px; 54 | width: 8px; 55 | height: 8px; 56 | background: #bbb; 57 | border-radius: 50%; 58 | } 59 | } 60 | .archive-year { 61 | margin-left: 20px; 62 | font-size: 20px; 63 | margin-left: 20px; 64 | } 65 | .post { 66 | margin: 30px 0; 67 | } 68 | .post-header { 69 | position: relative; 70 | transition: border .2s ease-in-out; 71 | border-bottom: 1px dashed #ccc; 72 | line-height: 2; 73 | &::before { 74 | content: ' '; 75 | position: absolute; 76 | left: 0; 77 | top: 12px; 78 | width: 6px; 79 | height: 6px; 80 | margin-left: -4px; 81 | background: #bbb; 82 | border-radius: 50%; 83 | border: 1px solid #fff; 84 | transition: background .2s ease-in-out; 85 | } 86 | &:hover { 87 | border-bottom-color: #666; 88 | &::before { 89 | background: #666; 90 | } 91 | } 92 | } 93 | .post-meta { 94 | position: absolute; 95 | font-size: 12px; 96 | left: 20px; 97 | top: 5px; 98 | } 99 | .post-title { 100 | margin: 20px 0 15px 60px; 101 | font-size: 16px; 102 | font-weight: normal; 103 | line-height: inherit; 104 | a { 105 | color: #666; 106 | border-bottom: none; 107 | } 108 | } 109 | } 110 | 111 | @media screen and(max-width: 767px) { 112 | .content { 113 | .posts-collapse { 114 | margin: 0 20px; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/style/md.css: -------------------------------------------------------------------------------- 1 | .markdown-body { 2 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; 3 | font-size: 16px; 4 | line-height: 1.5; 5 | word-wrap: break-word; 6 | } 7 | 8 | .markdown-body::before { 9 | display: table; 10 | content: ''; 11 | } 12 | 13 | .markdown-body::after { 14 | display: table; 15 | clear: both; 16 | content: ''; 17 | } 18 | 19 | .markdown-body>*:first-child { 20 | margin-top: 0 !important; 21 | } 22 | 23 | .markdown-body>*:last-child { 24 | margin-bottom: 0 !important; 25 | } 26 | 27 | .markdown-body a:not([href]) { 28 | color: inherit; 29 | text-decoration: none; 30 | } 31 | 32 | .markdown-body .absent { 33 | color: #cb2431; 34 | } 35 | 36 | .markdown-body .anchor { 37 | float: left; 38 | padding-right: 4px; 39 | margin-left: -20px; 40 | line-height: 1; 41 | } 42 | 43 | .markdown-body .anchor:focus { 44 | outline: none; 45 | } 46 | 47 | .markdown-body p, 48 | .markdown-body blockquote, 49 | .markdown-body ul, 50 | .markdown-body ol, 51 | .markdown-body dl, 52 | .markdown-body table, 53 | .markdown-body pre { 54 | margin-top: 0; 55 | margin-bottom: 16px; 56 | } 57 | 58 | .markdown-body hr { 59 | height: 0.25em; 60 | padding: 0; 61 | margin: 24px 0; 62 | background-color: #e1e4e8; 63 | border: 0; 64 | } 65 | 66 | .markdown-body blockquote { 67 | padding: 0 1em; 68 | color: #6a737d; 69 | border-left: 0.25em solid #dfe2e5; 70 | margin-left: 0; 71 | } 72 | 73 | .markdown-body blockquote> :first-child { 74 | margin-top: 0; 75 | } 76 | 77 | .markdown-body blockquote> :last-child { 78 | margin-bottom: 0; 79 | } 80 | 81 | .markdown-body kbd { 82 | display: inline-block; 83 | padding: 3px 5px; 84 | font-size: 11px; 85 | line-height: 10px; 86 | color: #444d56; 87 | vertical-align: middle; 88 | background-color: #fafbfc; 89 | border: solid 1px #c6cbd1; 90 | border-bottom-color: #959da5; 91 | border-radius: 3px; 92 | box-shadow: inset 0 -1px 0 #959da5; 93 | } 94 | 95 | .markdown-body h1, 96 | .markdown-body h2, 97 | .markdown-body h3, 98 | .markdown-body h4, 99 | .markdown-body h5, 100 | .markdown-body h6 { 101 | margin-top: 24px; 102 | margin-bottom: 16px; 103 | font-weight: 600; 104 | line-height: 1.25; 105 | } 106 | 107 | .markdown-body h1 .octicon-link, 108 | .markdown-body h2 .octicon-link, 109 | .markdown-body h3 .octicon-link, 110 | .markdown-body h4 .octicon-link, 111 | .markdown-body h5 .octicon-link, 112 | .markdown-body h6 .octicon-link { 113 | color: #1b1f23; 114 | vertical-align: middle; 115 | visibility: hidden; 116 | } 117 | 118 | .markdown-body h1:hover .anchor, 119 | .markdown-body h2:hover .anchor, 120 | .markdown-body h3:hover .anchor, 121 | .markdown-body h4:hover .anchor, 122 | .markdown-body h5:hover .anchor, 123 | .markdown-body h6:hover .anchor { 124 | text-decoration: none; 125 | } 126 | 127 | .markdown-body h1:hover .anchor .octicon-link, 128 | .markdown-body h2:hover .anchor .octicon-link, 129 | .markdown-body h3:hover .anchor .octicon-link, 130 | .markdown-body h4:hover .anchor .octicon-link, 131 | .markdown-body h5:hover .anchor .octicon-link, 132 | .markdown-body h6:hover .anchor .octicon-link { 133 | visibility: visible; 134 | } 135 | 136 | .markdown-body h1 tt, 137 | .markdown-body h1 code, 138 | .markdown-body h2 tt, 139 | .markdown-body h2 code, 140 | .markdown-body h3 tt, 141 | .markdown-body h3 code, 142 | .markdown-body h4 tt, 143 | .markdown-body h4 code, 144 | .markdown-body h5 tt, 145 | .markdown-body h5 code, 146 | .markdown-body h6 tt, 147 | .markdown-body h6 code { 148 | font-size: inherit; 149 | } 150 | 151 | .markdown-body h1 { 152 | padding-bottom: 0.3em; 153 | font-size: 2em; 154 | border-bottom: 1px solid #eaecef; 155 | } 156 | 157 | .markdown-body h2 { 158 | padding-bottom: 0.3em; 159 | font-size: 1.5em; 160 | border-bottom: 1px solid #eaecef; 161 | } 162 | 163 | .markdown-body h3 { 164 | font-size: 1.25em; 165 | } 166 | 167 | .markdown-body h4 { 168 | font-size: 1em; 169 | } 170 | 171 | .markdown-body h5 { 172 | font-size: 0.875em; 173 | } 174 | 175 | .markdown-body h6 { 176 | font-size: 0.85em; 177 | color: #6a737d; 178 | } 179 | 180 | .markdown-body ul, 181 | .markdown-body ol { 182 | padding-left: 2em; 183 | padding-left: 0; 184 | } 185 | 186 | .markdown-body ul.no-list, 187 | .markdown-body ol.no-list { 188 | padding: 0; 189 | list-style-type: none; 190 | } 191 | 192 | .markdown-body ul ul, 193 | .markdown-body ul ol, 194 | .markdown-body ol ol, 195 | .markdown-body ol ul { 196 | margin-top: 0; 197 | margin-bottom: 0; 198 | } 199 | 200 | .markdown-body a { 201 | color: #0366d6; 202 | } 203 | 204 | .markdown-body a:hover { 205 | text-decoration: underline; 206 | } 207 | 208 | .markdown-body li { 209 | word-wrap: break-all; 210 | list-style: disc; 211 | margin-left: 2em; 212 | } 213 | 214 | .markdown-body li>p { 215 | margin-top: 16px; 216 | } 217 | 218 | .markdown-body li+li { 219 | margin-top: 0.25em; 220 | } 221 | 222 | .markdown-body dl { 223 | padding: 0; 224 | } 225 | 226 | .markdown-body dl dt { 227 | padding: 0; 228 | margin-top: 16px; 229 | font-size: 1em; 230 | font-style: italic; 231 | font-weight: 600; 232 | } 233 | 234 | .markdown-body dl dd { 235 | padding: 0 16px; 236 | margin-bottom: 16px; 237 | } 238 | 239 | .markdown-body table { 240 | display: block; 241 | width: 100%; 242 | overflow: auto; 243 | } 244 | 245 | .markdown-body table th { 246 | font-weight: 600; 247 | } 248 | 249 | .markdown-body table th, 250 | .markdown-body table td { 251 | padding: 6px 13px; 252 | border: 1px solid #dfe2e5; 253 | } 254 | 255 | .markdown-body table tr { 256 | background-color: #fff; 257 | border-top: 1px solid #c6cbd1; 258 | } 259 | 260 | .markdown-body table tr:nth-child(2n) { 261 | background-color: #f6f8fa; 262 | } 263 | 264 | .markdown-body table img { 265 | background-color: transparent; 266 | } 267 | 268 | .markdown-body img { 269 | max-width: 100%; 270 | box-sizing: content-box; 271 | background-color: #fff; 272 | } 273 | 274 | .markdown-body img[align='right'] { 275 | padding-left: 20px; 276 | } 277 | 278 | .markdown-body img[align='left'] { 279 | padding-right: 20px; 280 | } 281 | 282 | .markdown-body .emoji { 283 | max-width: none; 284 | vertical-align: text-top; 285 | background-color: transparent; 286 | } 287 | 288 | .markdown-body span.frame { 289 | display: block; 290 | overflow: hidden; 291 | } 292 | 293 | .markdown-body span.frame>span { 294 | display: block; 295 | float: left; 296 | width: auto; 297 | padding: 7px; 298 | margin: 13px 0 0; 299 | overflow: hidden; 300 | border: 1px solid #dfe2e5; 301 | } 302 | 303 | .markdown-body span.frame span img { 304 | display: block; 305 | float: left; 306 | } 307 | 308 | .markdown-body span.frame span span { 309 | display: block; 310 | padding: 5px 0 0; 311 | clear: both; 312 | color: #24292e; 313 | } 314 | 315 | .markdown-body span.align-center { 316 | display: block; 317 | overflow: hidden; 318 | clear: both; 319 | } 320 | 321 | .markdown-body span.align-center>span { 322 | display: block; 323 | margin: 13px auto 0; 324 | overflow: hidden; 325 | text-align: center; 326 | } 327 | 328 | .markdown-body span.align-center span img { 329 | margin: 0 auto; 330 | text-align: center; 331 | } 332 | 333 | .markdown-body span.align-right { 334 | display: block; 335 | overflow: hidden; 336 | clear: both; 337 | } 338 | 339 | .markdown-body span.align-right>span { 340 | display: block; 341 | margin: 13px 0 0; 342 | overflow: hidden; 343 | text-align: right; 344 | } 345 | 346 | .markdown-body span.align-right span img { 347 | margin: 0; 348 | text-align: right; 349 | } 350 | 351 | .markdown-body span.float-left { 352 | display: block; 353 | float: left; 354 | margin-right: 13px; 355 | overflow: hidden; 356 | } 357 | 358 | .markdown-body span.float-left span { 359 | margin: 13px 0 0; 360 | } 361 | 362 | .markdown-body span.float-right { 363 | display: block; 364 | float: right; 365 | margin-left: 13px; 366 | overflow: hidden; 367 | } 368 | 369 | .markdown-body span.float-right>span { 370 | display: block; 371 | margin: 13px auto 0; 372 | overflow: hidden; 373 | text-align: right; 374 | } 375 | 376 | .markdown-body code, 377 | .markdown-body tt { 378 | padding: 0.2em 0.4em; 379 | margin: 0; 380 | font-size: 85%; 381 | background-color: rgba(27, 31, 35, 0.05); 382 | border-radius: 3px; 383 | } 384 | 385 | .markdown-body code br, 386 | .markdown-body tt br { 387 | display: none; 388 | } 389 | 390 | .markdown-body del code { 391 | text-decoration: inherit; 392 | } 393 | 394 | .markdown-body pre { 395 | word-wrap: normal; 396 | } 397 | 398 | .markdown-body pre>code { 399 | padding: 0; 400 | margin: 0; 401 | font-size: 100%; 402 | word-break: normal; 403 | white-space: pre; 404 | background: transparent; 405 | border: 0; 406 | } 407 | 408 | .markdown-body .highlight { 409 | margin-bottom: 16px; 410 | } 411 | 412 | .markdown-body .highlight pre { 413 | margin-bottom: 0; 414 | word-break: normal; 415 | } 416 | 417 | .markdown-body .highlight pre, 418 | .markdown-body pre { 419 | padding: 16px; 420 | overflow: auto; 421 | font-size: 85%; 422 | line-height: 1.45; 423 | background-color: #f6f8fa; 424 | border-radius: 3px; 425 | } 426 | 427 | .markdown-body pre code, 428 | .markdown-body pre tt { 429 | display: inline; 430 | max-width: auto; 431 | padding: 0; 432 | margin: 0; 433 | overflow: visible; 434 | line-height: inherit; 435 | word-wrap: normal; 436 | background-color: transparent; 437 | border: 0; 438 | } 439 | 440 | .markdown-body .csv-data td, 441 | .markdown-body .csv-data th { 442 | padding: 5px; 443 | overflow: hidden; 444 | font-size: 12px; 445 | line-height: 1; 446 | text-align: left; 447 | white-space: nowrap; 448 | } 449 | 450 | .markdown-body .csv-data .blob-num { 451 | padding: 10px 8px 9px; 452 | text-align: right; 453 | background: #fff; 454 | border: 0; 455 | } 456 | 457 | .markdown-body .csv-data tr { 458 | border-top: 0; 459 | } 460 | 461 | .markdown-body .csv-data th { 462 | font-weight: 600; 463 | background: #f6f8fa; 464 | border-top: 0; 465 | } 466 | 467 | /* 468 | github.com style (c) Vasily Polovnyov 469 | */ 470 | 471 | .hljs, 472 | .hljs-tag, 473 | .css .hljs-rules, 474 | .css .hljs-value, 475 | .css .hljs-function .hljs-preprocessor, 476 | .hljs-pragma { 477 | color: inherit; 478 | } 479 | 480 | .hljs-variable, 481 | .hljs-params, 482 | .hljs-class .hljs-title { 483 | color: inherit; 484 | } 485 | 486 | .hljs { 487 | display: block; 488 | overflow-x: auto; 489 | padding: 0.5em; 490 | color: #333; 491 | background: #f8f8f8; 492 | } 493 | 494 | .hljs-comment, 495 | .hljs-quote { 496 | color: #998; 497 | font-style: italic; 498 | } 499 | 500 | .hljs-keyword, 501 | .hljs-selector-tag, 502 | .hljs-subst { 503 | color: #d73a49; 504 | font-weight: bold; 505 | } 506 | 507 | .hljs-class { 508 | color: #6f42c1; 509 | } 510 | 511 | .hljs-number, 512 | .hljs-literal, 513 | .hljs-variable, 514 | .hljs-template-variable, 515 | .hljs-tag .hljs-attr { 516 | color: #005cc5; 517 | } 518 | 519 | .lang-html .hljs-keyword { 520 | color: #22863a; 521 | } 522 | 523 | .lang-css .hljs-keyword { 524 | color: #d73a49; 525 | } 526 | 527 | .lang-js .hljs-keyword { 528 | color: #d73a49; 529 | } 530 | 531 | .hljs-string, 532 | .hljs-doctag { 533 | color: #d14; 534 | } 535 | 536 | .hljs-title, 537 | .hljs-section, 538 | .hljs-selector-id { 539 | color: #900; 540 | font-weight: bold; 541 | } 542 | 543 | .hljs-subst { 544 | font-weight: normal; 545 | } 546 | 547 | .hljs-type, 548 | .hljs-class .hljs-title { 549 | color: #458; 550 | font-weight: bold; 551 | } 552 | 553 | .hljs-tag, 554 | .hljs-name, 555 | .hljs-attribute { 556 | color: #005cc5; 557 | font-weight: normal; 558 | } 559 | 560 | .hljs-regexp, 561 | .hljs-link { 562 | color: #009926; 563 | } 564 | 565 | .hljs-symbol, 566 | .hljs-bullet { 567 | color: #990073; 568 | } 569 | 570 | .hljs-built_in, 571 | .hljs-builtin-name { 572 | color: #0086b3; 573 | } 574 | 575 | .hljs-meta { 576 | color: #999; 577 | font-weight: bold; 578 | } 579 | 580 | .hljs-deletion { 581 | background: #fdd; 582 | } 583 | 584 | .hljs-addition { 585 | background: #dfd; 586 | } 587 | 588 | .hljs-emphasis { 589 | font-style: italic; 590 | } 591 | 592 | .hljs-strong { 593 | font-weight: bold; 594 | } 595 | -------------------------------------------------------------------------------- /src/style/style.less: -------------------------------------------------------------------------------- 1 | @import 'normalize.css'; 2 | @import './md.css'; 3 | @import 'nprogress/nprogress.css'; 4 | * { 5 | box-sizing: border-box; 6 | } 7 | body { 8 | font-size: 14px; 9 | color: #555; 10 | } 11 | a { 12 | text-decoration: none; 13 | color: #555; 14 | } 15 | 16 | li { 17 | list-style: none; 18 | } 19 | 20 | // 向下的动画 21 | @keyframes down { 22 | to { 23 | top: 0; 24 | opacity: 1; 25 | } 26 | } 27 | 28 | // 中间内容部分距离底部的距离 29 | main { 30 | padding-bottom: 100px; 31 | } 32 | .main-inner { 33 | width: 700px; 34 | height: auto; 35 | margin: 0 auto; 36 | min-width: 350px; 37 | } 38 | .post-body { 39 | font-family: 'Roboto Slab', 'Roboto Slab', 'PingFang SC', 'Microsoft YaHei', 40 | sans-serif; 41 | color: #555; 42 | } 43 | @media screen and(max-width:768px) { 44 | .main-inner { 45 | width: 94%; 46 | } 47 | } 48 | 49 | code { 50 | padding: 0.2em 0.4em; 51 | margin: 0; 52 | font-size: 85%; 53 | background-color: rgba(27, 31, 35, 0.05); 54 | border-radius: 3px; 55 | } 56 | -------------------------------------------------------------------------------- /src/util/eventBus.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | export default new Vue() 3 | -------------------------------------------------------------------------------- /src/util/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 时间戳转时间格式 3 | * 4 | * @param {date, type} 时间戳, 格式类型 5 | * @return {date} 返回 yyyy-mm-dd 或者 mm-dd 6 | */ 7 | export const formatTime = (date, type) => { 8 | if (type === 'yyyy-mm-dd') { 9 | return `${new Date(date).getFullYear()}-${(new Date(date).getMonth() + 1) < 10 ? ('0' + (new Date(date).getMonth() + 1)) : (new Date(date).getMonth() + 1)}-${(new Date(date).getDate()) < 10 ? ('0' + new Date(date).getDate()) : new Date(date).getDate()}` 10 | } else { 11 | return `${(new Date(date).getMonth() + 1) < 10 ? ('0' + (new Date(date).getMonth() + 1)) : (new Date(date).getMonth() + 1)}-${(new Date(date).getDate()) < 10 ? ('0' + new Date(date).getDate()) : new Date(date).getDate()}` 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenyinkai/cykspace/8c9863e8096f4ea99df88a137b390c083fa2b861/static/.gitkeep -------------------------------------------------------------------------------- /test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // The assertion name is the filename. 3 | // Example usage: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // For more information on custom assertions see: 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | 10 | exports.assertion = function (selector, count) { 11 | this.message = 'Testing if element <' + selector + '> has count: ' + count 12 | this.expected = count 13 | this.pass = function (val) { 14 | return val === this.expected 15 | } 16 | this.value = function (res) { 17 | return res.value 18 | } 19 | this.command = function (cb) { 20 | var self = this 21 | return this.api.execute(function (selector) { 22 | return document.querySelectorAll(selector).length 23 | }, [selector], function (res) { 24 | cb.call(self, res) 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/gettingstarted#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: require('selenium-server').path, 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true 35 | } 36 | }, 37 | 38 | firefox: { 39 | desiredCapabilities: { 40 | browserName: 'firefox', 41 | javascriptEnabled: true, 42 | acceptSslCerts: true 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing' 3 | 4 | const webpack = require('webpack') 5 | const DevServer = require('webpack-dev-server') 6 | 7 | const webpackConfig = require('../../build/webpack.prod.conf') 8 | const devConfigPromise = require('../../build/webpack.dev.conf') 9 | 10 | let server 11 | 12 | devConfigPromise.then(devConfig => { 13 | const devServerOptions = devConfig.devServer 14 | const compiler = webpack(webpackConfig) 15 | server = new DevServer(compiler, devServerOptions) 16 | const port = devServerOptions.port 17 | const host = devServerOptions.host 18 | return server.listen(port, host) 19 | }) 20 | .then(() => { 21 | // 2. run the nightwatch test suite against it 22 | // to run in additional browsers: 23 | // 1. add an entry in test/e2e/nightwatch.conf.js under "test_settings" 24 | // 2. add it to the --env flag below 25 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 26 | // For more information on Nightwatch's config file, see 27 | // http://nightwatchjs.org/guide#settings-file 28 | let opts = process.argv.slice(2) 29 | if (opts.indexOf('--config') === -1) { 30 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) 31 | } 32 | if (opts.indexOf('--env') === -1) { 33 | opts = opts.concat(['--env', 'chrome']) 34 | } 35 | 36 | const spawn = require('cross-spawn') 37 | const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) 38 | 39 | runner.on('exit', function (code) { 40 | server.close() 41 | process.exit(code) 42 | }) 43 | 44 | runner.on('error', function (err) { 45 | server.close() 46 | throw err 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function (browser) { 6 | // automatically uses dev Server port from /config.index.js 7 | // default: http://localhost:8080 8 | // see nightwatch.conf.js 9 | const devServer = browser.globals.devServerURL 10 | 11 | browser 12 | .url(devServer) 13 | .waitForElementVisible('#app', 5000) 14 | .assert.elementPresent('.hello') 15 | .assert.containsText('h1', 'Welcome to Your Vue.js App') 16 | .assert.elementCount('img', 1) 17 | .end() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "globals": { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/unit/jest.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | rootDir: path.resolve(__dirname, '../../'), 5 | moduleFileExtensions: [ 6 | 'js', 7 | 'json', 8 | 'vue' 9 | ], 10 | moduleNameMapper: { 11 | '^@/(.*)$': '/src/$1' 12 | }, 13 | transform: { 14 | '^.+\\.js$': '/node_modules/babel-jest', 15 | '.*\\.(vue)$': '/node_modules/vue-jest' 16 | }, 17 | testPathIgnorePatterns: [ 18 | '/test/e2e' 19 | ], 20 | snapshotSerializers: ['/node_modules/jest-serializer-vue'], 21 | setupFiles: ['/test/unit/setup'], 22 | mapCoverage: true, 23 | coverageDirectory: '/test/unit/coverage', 24 | collectCoverageFrom: [ 25 | 'src/**/*.{js,vue}', 26 | '!src/main.js', 27 | '!src/router/index.js', 28 | '!**/node_modules/**' 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /test/unit/setup.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.config.productionTip = false 4 | -------------------------------------------------------------------------------- /test/unit/specs/HelloWorld.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import HelloWorld from '@/components/HelloWorld' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('should render correct contents', () => { 6 | const Constructor = Vue.extend(HelloWorld) 7 | const vm = new Constructor().$mount() 8 | expect(vm.$el.querySelector('.hello h1').textContent) 9 | .toEqual('Welcome to Your Vue.js App') 10 | }) 11 | }) 12 | --------------------------------------------------------------------------------