├── .babelrc
├── .dockerignore
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .eslintrc.js
├── .gitignore
├── .postcssrc.js
├── Dockerfile
├── README.md
├── build
├── build.js
├── check-versions.js
├── dev-client.js
├── dev-server.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
├── server
├── .env
├── .gitignore
├── api
│ └── index.js
├── app.js
├── lib
│ └── mongo.js
├── logs
│ ├── combined.outerr.log
│ ├── err.log
│ └── out.log
├── package.json
├── pm2.json
├── routes
│ ├── articleList.js
│ ├── createArticle.js
│ ├── createTag.js
│ ├── detail.js
│ ├── index.js
│ ├── login.js
│ ├── register.js
│ └── user.js
└── utils
│ ├── checkToken.js
│ └── createToken.js
├── src
├── App.vue
├── api
│ └── index.js
├── assets
│ ├── css
│ │ ├── _variables.styl
│ │ ├── default.css
│ │ ├── default0.css
│ │ ├── editor.styl
│ │ ├── highlight.css
│ │ ├── markdown.styl
│ │ ├── preview.styl
│ │ └── reset.css
│ ├── images
│ │ └── logo.png
│ ├── img
│ │ └── logo.png
│ └── js
│ │ ├── commen.js
│ │ └── marked.js
├── components
│ ├── Comment.vue
│ ├── Info.vue
│ ├── Item.vue
│ ├── ProgressBar.vue
│ ├── Spinner.vue
│ ├── header.vue
│ └── sidebar.vue
├── config
│ ├── common.config.js
│ ├── development.config.js
│ ├── index.js
│ └── production.config.js
├── main.js
├── router
│ ├── index.js
│ └── routes.js
├── store
│ ├── actions.js
│ ├── getters.js
│ ├── index.js
│ └── mutations.js
├── utils
│ ├── filters.js
│ ├── http.js
│ ├── index.js
│ ├── marked.js
│ ├── meta.js
│ ├── store.js
│ └── title.js
├── view
│ ├── index
│ │ ├── components
│ │ │ ├── ArticleListMView.vue
│ │ │ ├── ArticleListVIew.vue
│ │ │ ├── ArticleTagView.vue
│ │ │ ├── BannerListView.vue
│ │ │ └── allTagView.vue
│ │ └── indexView.vue
│ ├── notfound
│ │ └── NotFoundView.vue
│ └── user
│ │ ├── article
│ │ ├── create
│ │ │ └── IndexView.vue
│ │ ├── detail
│ │ │ └── IndexView.vue
│ │ ├── list
│ │ │ └── IndexView.vue
│ │ └── tag
│ │ │ └── IndexView.vue
│ │ ├── center
│ │ └── IndexView.vue
│ │ ├── changepass
│ │ └── IndexView.vue
│ │ ├── index
│ │ └── IndexView.vue
│ │ ├── login
│ │ └── IndexView.vue
│ │ └── register
│ │ └── IndexView.vue
└── views
│ ├── admin
│ ├── components
│ │ ├── article.vue
│ │ ├── editor.vue
│ │ └── tags.vue
│ └── index.vue
│ ├── front
│ ├── ArticleDetail.vue
│ ├── ArticleList.vue
│ ├── Index.vue
│ ├── Mine.vue
│ ├── Tags.vue
│ ├── TimeLine.vue
│ └── components
│ │ └── RelateSkill.vue
│ └── hello
│ └── HelloView.vue
├── static
├── .gitkeep
└── img
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ ├── 5.png
│ ├── 6.png
│ ├── 7.png
│ └── 8.png
└── 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 |
--------------------------------------------------------------------------------
/.dockerignore :
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hbbaly/vue-blog/b624636e9ca48174e0bb49ef28d87c3e26be8a60/.dockerignore
--------------------------------------------------------------------------------
/.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:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "vue-eslint-parser",
3 | "parserOptions": {
4 | "ecmaVersion": 8,
5 | "sourceType": "module",
6 | "allowImportExportEverywhere": false
7 | },
8 | "extends": [
9 | "standard",
10 | "plugin:vue/recommended"
11 | ],
12 | "plugins": [
13 | "jsdoc"
14 | ],
15 | "globals": {
16 | "vue": true
17 | },
18 | "env": {
19 | "browser": true,
20 | "es6": true
21 | },
22 | "rules": {
23 | "no-var": 2,
24 | "one-var": 1,
25 | "block-scoped-var": 0,
26 | "no-alert": 0,
27 | "no-console": 0,
28 | "no-const-assign": 2,
29 | "no-dupe-keys": 2,
30 | "no-dupe-args": 2,
31 | "no-eval": 1,
32 | "no-irregular-whitespace": 2,
33 | "no-script-url": 0,
34 | "eqeqeq": 2,
35 | "indent": [2, 2],
36 | "prefer-const": 0,
37 | "prefer-spread": 0,
38 | "space-after-keywords": [0, "always"]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/.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 | /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 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # /usr/src/nodejs/hello-docker/Dockerfile
2 | FROM node:10.0
3 |
4 | # 在容器中创建一个目录
5 | RUN mkdir -p /usr/src/nodejs/
6 |
7 | # 定位到容器的工作目录
8 | WORKDIR /usr/src/nodejs/
9 |
10 | # RUN/COPY 是分层的,package.json 提前,只要没修改,就不会重新安装包
11 | COPY package.json /usr/src/app/package.json
12 | RUN cd /usr/src/app/
13 | RUN npm i
14 |
15 | # 把当前目录下的所有文件拷贝到 Image 的 /usr/src/nodejs/ 目录下
16 | COPY . /usr/src/nodejs/
17 |
18 |
19 | EXPOSE 3000
20 | CMD npm start
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-blog
2 |
3 | >Vue2 + Vue-router + Axios + Vuex + Node + Express + MongoDB + Webpack 搭建博客
4 |
5 | blog样式已改进
6 |
7 | ## Run Setup
8 |
9 | 注意:先启动MongoDB
10 |
11 | ``` bash
12 | cd server
13 | npm i
14 | pm2 start pm2.json (启动服务端)
15 | ```
16 |
17 | ## Build Setup
18 |
19 | ``` bash
20 | # install dependencies(安装依赖)
21 | npm install
22 |
23 | # serve with hot reload at localhost:8080(试运行)
24 | npm run dev
25 |
26 | # build for production with minification(项目构建)
27 | npm run build
28 |
29 | # build for production and view the bundle analyzer report(打包分析报告)
30 | npm run build --report
31 | ```
32 | ## 已完成的功能
33 | - 首页轮播设置及获取
34 | - 文章列表分页获取
35 | - 文章详情
36 | - 登陆
37 | - 注册
38 | - 修改密码
39 | - 编辑文章
40 | - 创建文章
41 | - 创建标签
42 | - 修改标签
43 | - 删除标签
44 |
45 | ## 未完成功能
46 | - 使用vuex管理数据
47 | - 需要登陆状态的页面需要路由守卫
48 | - 删除某篇文章
49 | - 标签搜索文章页面
50 | - 首页完善各个页面入口
51 | - 待添加......
52 |
53 |
54 | ## 目录结构
55 |
56 | ```
57 | │ .babelrc
58 | │ .eslintignore
59 | │ .eslintrc.js
60 | │ .gitignore
61 | │ index.html
62 | │ package.json
63 | │ README.md
64 | │
65 | ├─build
66 | │ build.js
67 | │ check-versions.js
68 | │ dev-client.js
69 | │ dev-server.js
70 | │ utils.js
71 | │ webpack.base.conf.js
72 | │ webpack.dev.conf.js
73 | │ webpack.prod.conf.js
74 | │
75 | ├─config
76 | │ dev.env.js
77 | │ index.js
78 | │ prod.env.js
79 | │
80 | ├─server 后端文件夹
81 | │ │ .env
82 | │ │ app.js 后端入口
83 | │ │
84 | │ ├─api
85 | │ │ index.js 后端api
86 | │ │
87 | │ ├─lib
88 | │ │ mongo.js 数据库
89 | │ │
90 | │ ├─utils
91 | │ │ checkToken.js
92 | │ │ createToken.js
93 | │ │
94 | │ └─routes 后端路由
95 | │ articleList.js
96 | │ createArticle.js
97 | │ createTag.js
98 | │ detail.js
99 | │ index.js
100 | │ login.js
101 | | register.js
102 | | user.js
103 | │
104 | ├─src 前端文件夹
105 | │ │ App.vue
106 | │ │ main.js 前端入口
107 | │ │
108 | │ ├─api 前端api
109 | │ │ index.js
110 | │ │
111 | │ ├─assets
112 | │ │ ├─css
113 | │ │ │ reset.css
114 | │ │ │ default.css
115 | │ │ │ default0.css
116 | │ │ │ highlight.css
117 | │ │ │
118 | │ │ ├─img
119 | │ │ │
120 | │ │ └─js
121 | │ │ highlight.pack.js
122 | │ │ hljs.js
123 | │ │
124 | │ ├─components
125 | │ │ │ info.vue
126 | │ │
127 | │ |─config 前端请求配置
128 | │ │ common.config.js
129 | │ │ development.config.js
130 | │ │ index.js.vue
131 | │ │ production.config.js
132 | │ |─filter 过滤
133 | │ │
134 | | |
135 | │ |─mixins 混入
136 | │ │
137 | │ ├─routes 前端路由vue-router
138 | │ │ index.js
139 | │ │ routes.js
140 | │ │
141 | │ └─store 前端vuex
142 | │ │
143 | │ │
144 | │ │
145 | │ └─utils 公用函数
146 | │ │ http
147 | │ │ index
148 | │ │ store
149 | │ │
150 | │ └─view 前端页面
151 | │ │ ├─index 首页
152 | │ │ ├─notfound 404页面
153 | │ │ ├─user 个人中心相关页面
154 | │ │ │ ├─article
155 | │ │ │ │ create
156 | │ │ │ │ detail
157 | │ │ │ │ list
158 | │ │ │ │ tag
159 | │ │ │ ├─center
160 | │ │ │ ├─changepass
161 | │ │ │ ├─index
162 | │ │ │ ├─login
163 | │ │ │ ├─register
164 | ```
165 |
166 | ## 功能展示展示
167 | **没有太过在意样式,后期计划会进行样式大修改**
168 | ### 首页
169 | http://localhost:8080/
170 |
171 |
172 | ### 创建文章
173 | http://localhost:8080/article/create
174 |
175 |
176 | ### 文章列表
177 | http://localhost:8080/article/list
178 |
179 |
180 | ### 标签
181 | http://localhost:8080/article/tag
182 |
183 | ### 文章详情
184 | http://localhost:8080/user/article/detail/${_id}
185 |
186 | ### 登陆
187 | http://localhost:8080/article/tag
188 |
189 | ### 注册
190 | http://localhost:8080/article/tag
191 |
192 |
193 |
194 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).
195 | more info, contact with 1422699902@qq.com
196 |
--------------------------------------------------------------------------------
/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 | function exec (cmd) {
7 | return require('child_process').execSync(cmd).toString().trim()
8 | }
9 |
10 | const versionRequirements = [
11 | {
12 | name: 'node',
13 | currentVersion: semver.clean(process.version),
14 | versionRequirement: packageConfig.engines.node
15 | }
16 | ]
17 |
18 | if (shell.which('npm')) {
19 | versionRequirements.push({
20 | name: 'npm',
21 | currentVersion: exec('npm --version'),
22 | versionRequirement: packageConfig.engines.npm
23 | })
24 | }
25 |
26 | module.exports = function () {
27 | const warnings = []
28 |
29 | for (let i = 0; i < versionRequirements.length; i++) {
30 | const mod = versionRequirements[i]
31 |
32 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
33 | warnings.push(mod.name + ': ' +
34 | chalk.red(mod.currentVersion) + ' should be ' +
35 | chalk.green(mod.versionRequirement)
36 | )
37 | }
38 | }
39 |
40 | if (warnings.length) {
41 | console.log('')
42 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
43 | console.log()
44 | for (let i = 0; i < warnings.length; i++) {
45 | const warning = warnings[i]
46 | console.log(' ' + warning)
47 | }
48 | console.log()
49 | process.exit(1)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/build/dev-client.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | 'use strict'
3 | require('eventsource-polyfill')
4 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
5 |
6 | hotClient.subscribe(function (event) {
7 | if (event.action === 'reload') {
8 | window.location.reload()
9 | }
10 | })
11 |
--------------------------------------------------------------------------------
/build/dev-server.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | require('./check-versions')()
3 |
4 | const config = require('../config')
5 | if (!process.env.NODE_ENV) {
6 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
7 | }
8 |
9 | const opn = require('opn')
10 | const path = require('path')
11 | const express = require('express')
12 | const webpack = require('webpack')
13 | const proxyMiddleware = require('http-proxy-middleware')
14 | const webpackConfig = require('./webpack.dev.conf')
15 |
16 | // default port where dev server listens for incoming traffic
17 | const port = process.env.PORT || config.dev.port
18 | // automatically open browser, if not set will be false
19 | const autoOpenBrowser = !!config.dev.autoOpenBrowser
20 | // Define HTTP proxies to your custom API backend
21 | // https://github.com/chimurai/http-proxy-middleware
22 | const proxyTable = config.dev.proxyTable
23 |
24 | const app = express()
25 |
26 | /*// json数据接口
27 | const router = express.Router();
28 | const goodsData = require('./../mock/goods.json');
29 | router.get("/goods", function (req, res, next) {
30 | res.json(goodsData)
31 | })
32 | app.use(router);*/
33 |
34 | const compiler = webpack(webpackConfig)
35 |
36 | const devMiddleware = require('webpack-dev-middleware')(compiler, {
37 | publicPath: webpackConfig.output.publicPath,
38 | quiet: true
39 | })
40 |
41 | const hotMiddleware = require('webpack-hot-middleware')(compiler, {
42 | log: false,
43 | heartbeat: 2000
44 | })
45 | // force page reload when html-webpack-plugin template changes
46 | // currently disabled until this is resolved:
47 | // https://github.com/jantimon/html-webpack-plugin/issues/680
48 | // compiler.plugin('compilation', function (compilation) {
49 | // compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
50 | // hotMiddleware.publish({ action: 'reload' })
51 | // cb()
52 | // })
53 | // })
54 |
55 | // enable hot-reload and state-preserving
56 | // compilation error display
57 | app.use(hotMiddleware)
58 |
59 | // proxy api requests
60 | Object.keys(proxyTable).forEach(function (context) {
61 | const options = proxyTable[context]
62 | if (typeof options === 'string') {
63 | options = { target: options }
64 | }
65 | app.use(proxyMiddleware(options.filter || context, options))
66 | })
67 |
68 | // handle fallback for HTML5 history API
69 | app.use(require('connect-history-api-fallback')())
70 |
71 | // serve webpack bundle output
72 | app.use(devMiddleware)
73 |
74 | // serve pure static assets
75 | const staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
76 | app.use(staticPath, express.static('./static'))
77 |
78 | const uri = 'http://localhost:' + port
79 |
80 | var _resolve
81 | var _reject
82 | var readyPromise = new Promise((resolve, reject) => {
83 | _resolve = resolve
84 | _reject = reject
85 | })
86 |
87 | var server
88 | var portfinder = require('portfinder')
89 | portfinder.basePort = port
90 |
91 | console.log('> Starting dev server...')
92 | devMiddleware.waitUntilValid(() => {
93 | portfinder.getPort((err, port) => {
94 | if (err) {
95 | _reject(err)
96 | }
97 | process.env.PORT = port
98 | var uri = 'http://localhost:' + port
99 | console.log('> Listening at ' + uri + '\n')
100 | // when env is testing, don't need open it
101 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
102 | opn(uri)
103 | }
104 | server = app.listen(port)
105 | _resolve()
106 | })
107 | })
108 |
109 | module.exports = {
110 | ready: readyPromise,
111 | close: () => {
112 | server.close()
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/build/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hbbaly/vue-blog/b624636e9ca48174e0bb49ef28d87c3e26be8a60/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 | return path.posix.join(assetsSubDirectory, _path)
12 | }
13 |
14 | exports.cssLoaders = function (options) {
15 | options = options || {}
16 |
17 | const cssLoader = {
18 | loader: 'css-loader',
19 | options: {
20 | sourceMap: options.sourceMap
21 | }
22 | }
23 |
24 | const postcssLoader = {
25 | loader: 'postcss-loader',
26 | options: {
27 | sourceMap: options.sourceMap
28 | }
29 | }
30 |
31 | // generate loader string to be used with extract text plugin
32 | function generateLoaders (loader, loaderOptions) {
33 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
34 |
35 | if (loader) {
36 | loaders.push({
37 | loader: loader + '-loader',
38 | options: Object.assign({}, loaderOptions, {
39 | sourceMap: options.sourceMap
40 | })
41 | })
42 | }
43 |
44 | // Extract CSS when that option is specified
45 | // (which is the case during production build)
46 | if (options.extract) {
47 | return ExtractTextPlugin.extract({
48 | use: loaders,
49 | fallback: 'vue-style-loader'
50 | })
51 | } else {
52 | return ['vue-style-loader'].concat(loaders)
53 | }
54 | }
55 |
56 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
57 | return {
58 | css: generateLoaders(),
59 | postcss: generateLoaders(),
60 | less: generateLoaders('less'),
61 | sass: generateLoaders('sass', { indentedSyntax: true }),
62 | scss: generateLoaders('sass'),
63 | stylus: generateLoaders('stylus'),
64 | styl: generateLoaders('stylus')
65 | }
66 | }
67 |
68 | // Generate loaders for standalone style files (outside of .vue)
69 | exports.styleLoaders = function (options) {
70 | const output = []
71 | const loaders = exports.cssLoaders(options)
72 | for (const extension in loaders) {
73 | const loader = loaders[extension]
74 | output.push({
75 | test: new RegExp('\\.' + extension + '$'),
76 | use: loader
77 | })
78 | }
79 |
80 | return output
81 | }
82 |
83 | exports.createNotifierCallback = () => {
84 | const notifier = require('node-notifier')
85 |
86 | return (severity, errors) => {
87 | if (severity !== 'error') return
88 |
89 | const error = errors[0]
90 | const filename = error.file && error.file.split('!').pop()
91 |
92 | notifier.notify({
93 | title: packageConfig.name,
94 | message: severity + ': ' + error.name,
95 | subtitle: filename || '',
96 | icon: path.join(__dirname, 'logo.png')
97 | })
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/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 | }
93 |
--------------------------------------------------------------------------------
/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 | '/api':{
15 | target:'http://127.0.0.1:3010/api',
16 | changeOrigin:true,
17 | pathRewrite:{
18 | '^/api':''
19 | }
20 | }
21 | },
22 |
23 | // Various Dev Server settings
24 | host: 'localhost', // can be overwritten by process.env.HOST
25 | env: require('./dev.env'),
26 | port: process.env.PORT || 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
27 | autoOpenBrowser: false,
28 | errorOverlay: true,
29 | notifyOnErrors: true,
30 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
31 |
32 | // Use Eslint Loader?
33 | // If true, your code will be linted during bundling and
34 | // linting errors and warnings will be shown in the console.
35 | useEslint: true,
36 | // If true, eslint errors and warnings will also be shown in the error overlay
37 | // in the browser.
38 | showEslintErrorsInOverlay: false,
39 |
40 | /**
41 | * Source Maps
42 | */
43 |
44 | // https://webpack.js.org/configuration/devtool/#development
45 | devtool: 'cheap-module-eval-source-map',
46 |
47 | // If you have problems debugging vue-files in devtools,
48 | // set this to false - it *may* help
49 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
50 | cacheBusting: true,
51 |
52 | cssSourceMap: true
53 | },
54 |
55 | build: {
56 | // Template for index.html
57 | index: path.resolve(__dirname, '../dist/index.html'),
58 |
59 | // Paths
60 | assetsRoot: path.resolve(__dirname, '../dist'),
61 | assetsSubDirectory: 'static',
62 | assetsPublicPath: '/',
63 |
64 | /**
65 | * Source Maps
66 | */
67 |
68 | productionSourceMap: true,
69 | // https://webpack.js.org/configuration/devtool/#production
70 | devtool: '#source-map',
71 |
72 | // Gzip off by default as many popular static hosts such as
73 | // Surge or Netlify already gzip all static assets for you.
74 | // Before setting to `true`, make sure to:
75 | // npm install --save-dev compression-webpack-plugin
76 | productionGzip: false,
77 | productionGzipExtensions: ['js', 'css'],
78 | // Run the build command with an extra argument to
79 | // View the bundle analyzer report after build finishes:
80 | // `npm run build --report`
81 | // Set to `true` or `false` to always turn it on or off
82 | bundleAnalyzerReport: process.env.npm_config_report
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/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 | vue-blog
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spa",
3 | "version": "1.0.0",
4 | "description": "blog spa",
5 | "author": "hbbaly <1422699902@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 | "axios": "^0.18.0",
18 | "element-ui": "^2.12.0",
19 | "highlight.js": "^9.16.2",
20 | "marked": "^0.7.0",
21 | "qs": "^6.9.1",
22 | "simplemde": "^1.11.2",
23 | "vue": "^2.5.2",
24 | "vue-router": "^3.0.1",
25 | "vuex": "^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 | "nightwatch": "^0.9.12",
63 | "node-notifier": "^5.1.2",
64 | "optimize-css-assets-webpack-plugin": "^3.2.0",
65 | "ora": "^1.2.0",
66 | "portfinder": "^1.0.13",
67 | "postcss-import": "^11.0.0",
68 | "postcss-loader": "^2.0.8",
69 | "postcss-url": "^7.2.1",
70 | "rimraf": "^2.6.0",
71 | "selenium-server": "^3.0.1",
72 | "semver": "^5.3.0",
73 | "shelljs": "^0.7.6",
74 | "stylus": "^0.54.5",
75 | "stylus-loader": "^3.0.1",
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.6.0",
83 | "webpack-bundle-analyzer": "^2.9.0",
84 | "webpack-dev-server": "^2.9.1",
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 |
--------------------------------------------------------------------------------
/server/.env:
--------------------------------------------------------------------------------
1 | JWT_SECRET = hbb
2 |
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | npm-debug.log
2 | node_modules
3 | coverage
4 | logs
--------------------------------------------------------------------------------
/server/api/index.js:
--------------------------------------------------------------------------------
1 | const {Article, SetBannerList, User, Classify} = require('../lib/mongo')
2 |
3 | module.exports = {
4 | setBanner: (params) => {
5 | return SetBannerList.create(params).exec()
6 | },
7 | getBanner: (params) => {
8 | return SetBannerList.find().exec()
9 | },
10 |
11 | registerUser: (params) => {
12 | return User.create(params).exec()
13 | },
14 | getUserLogin: (params) => {
15 | const {name} = params
16 | return User.find({name}).exec()
17 | },
18 | changePass (params) {
19 | const {name} = params
20 | return User.update({name},{$set:params}).exec()
21 | },
22 |
23 | createArticle: (params) => {
24 | return Article.create(params).exec()
25 | },
26 | getArticle: (params) => {
27 | const {page, limit, type} = params
28 | let skip
29 | let data = type ? {type} : {}
30 | if (page && limit) {
31 | skip = (page -1) * limit
32 | return Promise.all([
33 | Article.find(data).addCreateAt().sort({_id:-1}).skip(skip * 1).limit(limit * 1).exec(),
34 | Article.count(data).exec()
35 | ])
36 | } else {
37 | return Article.find(data).addCreateAt().sort({_id: -1}).exec()
38 | }
39 | },
40 | // 获取文章详情
41 | getArticleDetail: (params) => {
42 | return Article.find(params).addCreateAt().sort({_id: -1}).exec()
43 | },
44 | updateArticle (params) {
45 | const { _id, title, content,type,contentToMark } = params
46 | return Article.update({_id},{$set:{title, type, content,contentToMark }}).exec()
47 | },
48 | setClassify (params) {
49 | return Classify.create(params).exec()
50 | },
51 | searchClassify (params = {}) {
52 | return Classify.find(params).exec()
53 | },
54 | updateClassify (_id, params) {
55 | return Classify.update({_id:_id},{$set:params}).exec()
56 | },
57 | // remove Classify
58 | removeClassify (params) {
59 | return Classify.remove(params).exec()
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const path = require('path');
3 | const app = express();
4 | const bodyParser = require('body-parser');
5 | const index = require('./routes/index')
6 | const register = require('./routes/register.js')
7 | const login = require('./routes/login')
8 | const user = require('./routes/user')
9 | const createArticle = require('./routes/createArticle')
10 | const articleList = require('./routes/articleList')
11 | const createTag = require('./routes/createTag')
12 | const detail = require('./routes/detail')
13 | app.use(bodyParser.urlencoded({ extended: true }));
14 | app.use(bodyParser.json());
15 | // 跨域
16 | app.all('*', function(req, res, next) {
17 | res.header("Access-Control-Allow-Origin", "*");
18 | res.header("Access-Control-Allow-Headers", "X-Requested-With,Content-Type");//预检请求使用
19 | res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");//预检请求使用
20 | next();
21 | });
22 | app.use('/api', index)
23 | app.use('/api', register)
24 | app.use('/api', login)
25 | app.use('/api', user)
26 | app.use('/api', createArticle)
27 | app.use('/api', articleList)
28 | app.use('/api', createTag)
29 | app.use('/api', detail)
30 | app.set('port', process.env.PORT || 3010);
31 | app.listen(app.get('port'), function() {
32 | console.log('Express server listening on port ' + app.get('port'));
33 | });
34 |
35 |
--------------------------------------------------------------------------------
/server/lib/mongo.js:
--------------------------------------------------------------------------------
1 | const Mongolass = require('mongolass');
2 | const mongolass = new Mongolass();
3 | mongolass.connect('mongodb://localhost:27017/test');
4 | const moment = require('moment');//时间格式化(前后台都可以用的npm包)
5 | const objectIdToTimestamp = require('objectid-to-timestamp');// 根据_id生成时间戳
6 | /*
7 | mongolass插件系统,语法:
8 | mongolass.plugin(插件名字,{
9 | before(方法)
10 | after(方法)
11 | })
12 | */
13 | mongolass.plugin('addCreateAt',{
14 | // 只要查询所有条件,那么一定会有最终结果
15 | afterFind:results => {
16 | results.forEach(item => {
17 | item.created_at =item.created_at = moment(objectIdToTimestamp(item._id)).format('YYYY-MM-DD HH:mm:ss');
18 | })
19 | return results
20 | },
21 | // 单个查询有可能是null,所以要加if
22 | afterFindOne: (result) => {
23 | if(result){
24 | result.created_at =result.created_at = moment(objectIdToTimestamp(result._id)).format('YYYY-MM-DD HH:mm:ss');
25 | }
26 | return result
27 | }
28 | })
29 | // 文章
30 | exports.Article = mongolass.model('article',{
31 | title: {type: 'string'},
32 | content: {type: 'string'},
33 | type: {type: 'string'},
34 | contentToMark: {type: 'string'},
35 | })
36 | exports.SetBannerList = mongolass.model('banner', {
37 | title: {type: 'string'},
38 | description: {type: 'string'},
39 | url: {type: 'string'},
40 | img: {type: 'string'},
41 | })
42 | // 用户集合
43 | exports.User = mongolass.model('User',{
44 | name: {type: 'string'},
45 | password:{type:'string'}
46 | })
47 | // 分类
48 | exports.Classify = mongolass.model('Classify',{
49 | type: {type:'string'}
50 | })
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "cross-env NODE_ENV=production node app.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "body-parser": "^1.17.1",
14 | "cross-env": "^5.2.0",
15 | "dotenv": "^8.0.0",
16 | "express": "^4.15.2",
17 | "jsonwebtoken": "^8.5.1",
18 | "moment": "^2.24.0",
19 | "mongolass": "^2.4.5",
20 | "objectid-to-timestamp": "^1.3.0",
21 | "sha1": "^1.1.1"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/server/pm2.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-blog",
3 | "script": "npm",
4 | "args": "start",
5 | "watch": [
6 | "dist"
7 | ],
8 | "ignore_watch": [
9 | "node_modules",
10 | "public",
11 | "logs"
12 | ],
13 | "watch_options": {
14 | "followSymlinks": false
15 | },
16 | "log_file": "./logs/combined.outerr.log",
17 | "out_file": "./logs/out.log",
18 | "err_file": "./logs/err.log"
19 | }
--------------------------------------------------------------------------------
/server/routes/articleList.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 | const api = require('../api/index')
4 | const checkToken = require('../utils/checkToken')
5 | router.post('/get/article',checkToken, (req, res, next) => {
6 | api.getArticle(req.body).then(response => {
7 | res.send({
8 | code: 200,
9 | message: 'ok',
10 | data: response[0],
11 | total: response[1]
12 | })
13 | }).catch(err => {
14 | res.send({
15 | code: -200,
16 | message: err.toString()
17 | })
18 | })
19 | })
20 | router.get('/get/type/article',checkToken, (req, res, next) => {
21 | api.getArticleByTag(req.body).then(response => {
22 | res.send({
23 | code: 200,
24 | message: 'ok',
25 | data: response[0],
26 | total: response[1]
27 | })
28 | }).catch(err => {
29 | res.send({
30 | code: -200,
31 | message: err.toString()
32 | })
33 | })
34 | })
35 | module.exports = router
--------------------------------------------------------------------------------
/server/routes/createArticle.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 | const checkToken = require('../utils/checkToken')
4 | const api = require('../api/index')
5 | router.post('/create/article', checkToken, (req, res, next) => {
6 | api.createArticle(req.body).then(response => {
7 | if (response.result.ok && response.result.n > 0){
8 | res.send({
9 | code: 200,
10 | message: '发布成功'
11 | });
12 | } else {
13 | res.send({
14 | code: -200,
15 | message: '发布失败'
16 | })
17 | }
18 | }).catch(err => {
19 | res.send({
20 | code: -200,
21 | message: err.toString()
22 | })
23 | })
24 | })
25 | module.exports = router
--------------------------------------------------------------------------------
/server/routes/createTag.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 | const api = require('../api/index')
4 | const checkToken = require('../utils/checkToken')
5 |
6 | router.post('/create/tag',checkToken, (req, res, next) => {
7 | api.searchClassify(req.body).then(resp => {
8 | if (resp.length) {
9 | //更新
10 | api.updateClassify(resp[0]._id, req.body).then(response => {
11 | if (response.result.ok && response.result.n > 0){
12 | res.send({
13 | code: 200,
14 | message: '更新成功'
15 | });
16 | } else {
17 | res.send({
18 | code: -200,
19 | message: '更新失败'
20 | })
21 | }
22 | }).catch(err => {
23 | res.send({
24 | code: -200,
25 | message: err.toString()
26 | })
27 | })
28 | } else {
29 | // 创建
30 | api.setClassify(req.body).then(response => {
31 | if (response.result.ok && response.result.n > 0){
32 | res.send({
33 | code: 200,
34 | message: '添加成功'
35 | });
36 | } else {
37 | res.send({
38 | code: -200,
39 | message: '添加失败'
40 | })
41 | }
42 | }).catch(err => {
43 | res.send({
44 | code: -200,
45 | message: err.toString()
46 | })
47 | })
48 | }
49 | })
50 | })
51 | router.post('/get/all/tag', (req, res, next) => {
52 | api.searchClassify(req.body).then(response => {
53 | if (response.length){
54 | res.send({
55 | code: 200,
56 | message: '成功',
57 | data: response
58 | });
59 | } else {
60 | res.send({
61 | code: -200,
62 | message: '获取失败'
63 | })
64 | }
65 | }).catch(err => {
66 | res.send({
67 | code: -200,
68 | message: err.toString()
69 | })
70 | })
71 | })
72 | router.post('/remove/tag',checkToken, (req, res, next) => {
73 | api.removeClassify(req.body).then(response => {
74 | if (response.result.ok && response.result.n > 0){
75 | res.send({
76 | code: 200,
77 | message: '删除成功'
78 | })
79 | } else {
80 | throw new Error('该分类不存在')
81 | }
82 | }).catch(err => {
83 | res.send({
84 | code: -200,
85 | message: err.toString()
86 | })
87 | })
88 | })
89 | module.exports = router
--------------------------------------------------------------------------------
/server/routes/detail.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 | const api = require('../api/index')
4 | router.post('/get/article/detail', (req, res, next) => {
5 | api.getArticleDetail(req.body).then(response => {
6 | if (response.length) {
7 | res.send({
8 | code: 200,
9 | message: 'ok',
10 | data: response[0]
11 | })
12 | } else {
13 | res.send({
14 | code: -200,
15 | message: '没有找到改文章',
16 | })
17 | }
18 | }).catch(err => {
19 | res.send({
20 | code: -200,
21 | message: err.toString()
22 | })
23 | })
24 | })
25 | module.exports = router
--------------------------------------------------------------------------------
/server/routes/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 | const api = require('../api/index')
4 | router.get('/get/topics', (req, res) => {
5 | api.getTopics().then(data => {
6 | res.send({
7 | code: 200,
8 | message: 'ok',
9 | data
10 | })
11 | })
12 | });
13 | // 设置banner
14 | router.post('/set/banner', (req, res, next) => {
15 | const data = req.body
16 | api.setBanner(data).then(response => {
17 | res.send({
18 | code: 200,
19 | message: 'ok',
20 | data: response
21 | })
22 | })
23 | })
24 | router.get('/get/banner',(req, res, next) => {
25 | api.getBanner().then(response => {
26 | res.send({
27 | code: 200,
28 | message: 'ok',
29 | data: response
30 | })
31 | })
32 | })
33 | module.exports = router
--------------------------------------------------------------------------------
/server/routes/login.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config('../.env')
2 | const express = require('express')
3 | const router = express.Router()
4 | const api = require('../api/index')
5 | const sha1 = require('sha1')
6 | const createToken = require('../utils/createToken')
7 | router.post('/user/login', (req, res, next) => {
8 | const {name, password} = req.body
9 | let encodePassword = sha1(password),
10 | user = {
11 | name,
12 | password: encodePassword
13 | }
14 |
15 | api.getUserLogin(user).then(response => {
16 | if (response && response.length > 0 && response[0].password === encodePassword) {
17 | res.send({
18 | // 登陆成功
19 | code:200,
20 | message: '登陆成功',
21 | data: {
22 | token: createToken(name),
23 | name: name
24 | }
25 | })
26 | } else {
27 | res.send({
28 | // 登陆成功
29 | code: -200,
30 | message: '用户名或密码错误',
31 | })
32 | }
33 | }).catch(err => {
34 | next(err)
35 | // 服务器发生错误(例如status:)
36 | return res.json({
37 | code:-200,
38 | message:err.toString()
39 | })
40 | })
41 | })
42 | module.exports = router
--------------------------------------------------------------------------------
/server/routes/register.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config('../.env')
2 | const express = require('express')
3 | const router = express.Router()
4 | const api = require('../api/index')
5 | const sha1 = require('sha1')
6 | const createToken = require('../utils/createToken')
7 | const checkToken = require('../utils/checkToken')
8 | router.post('/user/register', (req, res, next) => {
9 | const {name, password} = req.body
10 | let encodePassword = sha1(password),
11 | user = {
12 | name,
13 | password: encodePassword
14 | }
15 |
16 | api.registerUser(user).then(response => {
17 | res.send({
18 | // 创建用户成功
19 | code:200,
20 | message: 'ok',
21 | data: {
22 | token: createToken(name)
23 | }
24 | })
25 | }).catch(err => {
26 | if(err.message.match('E11000 duplicate key')){
27 | return res.json({
28 | code:-200,
29 | message:'用户名重复'
30 | })
31 | }
32 | // 服务器发生错误(例如status:)
33 | return res.send({
34 | code:-200,
35 | message:err.toString()
36 | })
37 | })
38 | })
39 | router.post('/change/pass', checkToken, (req, res, next) => {
40 | let encodePassword = sha1(password),
41 | user = {
42 | name,
43 | password: encodePassword
44 | }
45 | api.changePass(user).then(response => {
46 | if (response.result.ok && response.result.n > 0){
47 | res.send({
48 | code: 200,
49 | message: '修改成功'
50 | })
51 | } else {
52 | throw new Error('没有找到该用户');
53 | }
54 | }).catch(err => {
55 | return res.json({
56 | code:-200,
57 | message: err.toString()
58 | })
59 | })
60 | })
61 | module.exports = router
--------------------------------------------------------------------------------
/server/routes/user.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 | const checkToken = require('../utils/checkToken')
4 | router.post('/user/info', checkToken, (req, res, next) => {
5 | console.log(req.headers['authorization'], 'headers authorization')
6 | const {name} = req.body
7 | res.send({
8 | code: 200,
9 | message: '用户中心',
10 | data: {
11 | name
12 | }
13 | });
14 | })
15 | module.exports = router
--------------------------------------------------------------------------------
/server/utils/checkToken.js:
--------------------------------------------------------------------------------
1 | // 验证token中间件
2 | const jwt = require('jsonwebtoken')
3 | module.exports = function (req, res, next) {
4 | if(req.headers['authorization']){
5 | const token = req.headers['authorization'].split(' ')[1]
6 | const decoded = jwt.decode(token, process.env.JWT_SECRET)
7 | // 如果过期了就重新登录
8 | // 验证token也需要优化
9 | if(token&&decoded.exp<=Date.now()/1000){
10 | return res.send({
11 | code: 401,
12 | message:"授权已经过期,请重新登陆"
13 | })
14 | }
15 | }
16 | next();
17 | }
--------------------------------------------------------------------------------
/server/utils/createToken.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken')
2 | module.exports =function (name){
3 | const expiry = new Date();
4 | expiry.setDate(expiry.getDate()+7);//有效期设置为七天
5 | const token = jwt.sign({
6 | name:name,
7 | exp:parseInt(expiry.getTime()/1000)//除以1000以后表示的是秒数
8 | },process.env.JWT_SECRET)
9 | return token;
10 | }
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
18 |
--------------------------------------------------------------------------------
/src/api/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hbbaly/vue-blog/b624636e9ca48174e0bb49ef28d87c3e26be8a60/src/api/index.js
--------------------------------------------------------------------------------
/src/assets/css/_variables.styl:
--------------------------------------------------------------------------------
1 | /**
2 | * @Author: hbbaly
3 | * @Date: 2019-11-16 17:43:56
4 | * @GitHub: https://github.com/hbbaly
5 | * @Description: variables
6 | */
7 |
8 | /* 水平间距 */
9 | $row-smest= 5px;
10 | $row-sm= 10px;
11 | $row-base= 15px;
12 | $row-lg= 20px;
13 | $row-lger= 25px;
14 | $row-lgest= 30px;
15 |
16 | /* 垂直间距 */
17 | $col-smest= 5px;
18 | $col-sm= 10px;
19 | $col-base= 15px;
20 | $col-lg= 20px;
21 | $col-lger= 25px;
22 | $col-lgest= 30px;
23 | // colors
24 | $grey-bg = #fefefe
25 | $blue = #0288D1
26 | $orange = #409EFF
27 | $color = #fff
28 |
29 | // font-size=
30 | $fs-smest=12px;
31 | $fs-smer=14px;
32 | $fs-sm=16px;
33 | $fs-base=18px;
34 | $fs-lg=20px;
35 | $fs-lger=22px;
36 | $fs-lgest=24px;
--------------------------------------------------------------------------------
/src/assets/css/default.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Original highlight.js style (c) Ivan Sagalaev
4 |
5 | */
6 |
7 | .hljs {
8 | display: block;
9 | overflow-x: auto;
10 | padding: 0.5em;
11 | background: #F0F0F0;
12 | }
13 |
14 |
15 | /* Base color: saturation 0; */
16 |
17 | .hljs,
18 | .hljs-subst {
19 | color: #444;
20 | }
21 |
22 | .hljs-comment {
23 | color: #888888;
24 | }
25 |
26 | .hljs-keyword,
27 | .hljs-attribute,
28 | .hljs-selector-tag,
29 | .hljs-meta-keyword,
30 | .hljs-doctag,
31 | .hljs-name {
32 | font-weight: bold;
33 | }
34 |
35 |
36 | /* User color: hue: 0 */
37 |
38 | .hljs-type,
39 | .hljs-string,
40 | .hljs-number,
41 | .hljs-selector-id,
42 | .hljs-selector-class,
43 | .hljs-quote,
44 | .hljs-template-tag,
45 | .hljs-deletion {
46 | color: #880000;
47 | }
48 |
49 | .hljs-title,
50 | .hljs-section {
51 | color: #880000;
52 | font-weight: bold;
53 | }
54 |
55 | .hljs-regexp,
56 | .hljs-symbol,
57 | .hljs-variable,
58 | .hljs-template-variable,
59 | .hljs-link,
60 | .hljs-selector-attr,
61 | .hljs-selector-pseudo {
62 | color: #BC6060;
63 | }
64 |
65 |
66 | /* Language color: hue: 90; */
67 |
68 | .hljs-literal {
69 | color: #78A960;
70 | }
71 |
72 | .hljs-built_in,
73 | .hljs-bullet,
74 | .hljs-code,
75 | .hljs-addition {
76 | color: #397300;
77 | }
78 |
79 |
80 | /* Meta color: hue: 200 */
81 |
82 | .hljs-meta {
83 | color: #1f7199;
84 | }
85 |
86 | .hljs-meta-string {
87 | color: #4d99bf;
88 | }
89 |
90 |
91 | /* Misc effects */
92 |
93 | .hljs-emphasis {
94 | font-style: italic;
95 | }
96 |
97 | .hljs-strong {
98 | font-weight: bold;
99 | }
100 |
--------------------------------------------------------------------------------
/src/assets/css/default0.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Original highlight.js style (c) Ivan Sagalaev
4 |
5 | */
6 |
7 | .hljs {
8 | display: block;
9 | overflow-x: auto;
10 | padding: 0.5em;
11 | background: #F0F0F0;
12 | }
13 |
14 |
15 | /* Base color: saturation 0; */
16 |
17 | .hljs,
18 | .hljs-subst {
19 | color: #444;
20 | }
21 |
22 | .hljs-comment {
23 | color: #888888;
24 | }
25 |
26 | .hljs-keyword,
27 | .hljs-attribute,
28 | .hljs-selector-tag,
29 | .hljs-meta-keyword,
30 | .hljs-doctag,
31 | .hljs-name {
32 | font-weight: bold;
33 | }
34 |
35 |
36 | /* User color: hue: 0 */
37 |
38 | .hljs-type,
39 | .hljs-string,
40 | .hljs-number,
41 | .hljs-selector-id,
42 | .hljs-selector-class,
43 | .hljs-quote,
44 | .hljs-template-tag,
45 | .hljs-deletion {
46 | color: #880000;
47 | }
48 |
49 | .hljs-title,
50 | .hljs-section {
51 | color: #880000;
52 | font-weight: bold;
53 | }
54 |
55 | .hljs-regexp,
56 | .hljs-symbol,
57 | .hljs-variable,
58 | .hljs-template-variable,
59 | .hljs-link,
60 | .hljs-selector-attr,
61 | .hljs-selector-pseudo {
62 | color: #BC6060;
63 | }
64 |
65 |
66 | /* Language color: hue: 90; */
67 |
68 | .hljs-literal {
69 | color: #78A960;
70 | }
71 |
72 | .hljs-built_in,
73 | .hljs-bullet,
74 | .hljs-code,
75 | .hljs-addition {
76 | color: #397300;
77 | }
78 |
79 |
80 | /* Meta color: hue: 200 */
81 |
82 | .hljs-meta {
83 | color: #1f7199;
84 | }
85 |
86 | .hljs-meta-string {
87 | color: #4d99bf;
88 | }
89 |
90 |
91 | /* Misc effects */
92 |
93 | .hljs-emphasis {
94 | font-style: italic;
95 | }
96 |
97 | .hljs-strong {
98 | font-weight: bold;
99 | }
100 |
--------------------------------------------------------------------------------
/src/assets/css/editor.styl:
--------------------------------------------------------------------------------
1 | .gutter pre
2 | color #999
3 | pre
4 | padding 10px
5 | color: #525252
6 | background: #ddd
7 | .function .keyword,
8 | .constant
9 | color: #0092db
10 | .keyword,
11 | .attribute
12 | color: #e96900
13 | .number,
14 | .literal
15 | color: #AE81FF
16 | .tag,
17 | .tag .title,
18 | .change,
19 | .winutils,
20 | .flow,
21 | .lisp .title,
22 | .clojure .built_in,
23 | .nginx .title,
24 | .tex .special
25 | color: #2973b7
26 | .class .title
27 | color: #42b983
28 | .symbol,
29 | .symbol .string,
30 | .value,
31 | .regexp
32 | color: $blue
33 | .title
34 | color: #A6E22E
35 | .tag .value,
36 | .string,
37 | .subst,
38 | .haskell .type,
39 | .preprocessor,
40 | .ruby .class .parent,
41 | .built_in,
42 | .sql .aggregate,
43 | .django .template_tag,
44 | .django .variable,
45 | .smalltalk .class,
46 | .javadoc,
47 | .django .filter .argument,
48 | .smalltalk .localvars,
49 | .smalltalk .array,
50 | .attr_selector,
51 | .pseudo,
52 | .addition,
53 | .stream,
54 | .envvar,
55 | .apache .tag,
56 | .apache .cbracket,
57 | .tex .command,
58 | .prompt
59 | color: #0288D1
60 | .comment,
61 | .java .annotation,
62 | .python .decorator,
63 | .template_comment,
64 | .pi,
65 | .doctype,
66 | .deletion,
67 | .shebang,
68 | .apache .sqbracket,
69 | .tex .formula
70 | color: #b3b3b3
71 | .coffeescript .javascript,
72 | .javascript .xml,
73 | .tex .formula,
74 | .xml .javascript,
75 | .xml .vbscript,
76 | .xml .css,
77 | .xml .cdata
78 | opacity: 0.5
--------------------------------------------------------------------------------
/src/assets/css/highlight.css:
--------------------------------------------------------------------------------
1 | .hljs-comment,
2 | .hljs-quote {
3 | color: #8e908c;
4 | }
5 |
6 | .hljs-variable,
7 | .hljs-template-variable,
8 | .hljs-tag,
9 | .hljs-name,
10 | .hljs-selector-id,
11 | .hljs-selector-class,
12 | .hljs-regexp,
13 | .hljs-deletion {
14 | color: #c82829;
15 | }
16 |
17 | .hljs-number,
18 | .hljs-built_in,
19 | .hljs-builtin-name,
20 | .hljs-literal,
21 | .hljs-type,
22 | .hljs-params,
23 | .hljs-meta,
24 | .hljs-link {
25 | color: #f5871f;
26 | }
27 |
28 | .hljs-attribute {
29 | color: #eab700;
30 | }
31 |
32 | .hljs-string,
33 | .hljs-symbol,
34 | .hljs-bullet,
35 | .hljs-addition {
36 | color: #718c00;
37 | }
38 |
39 | .hljs-title,
40 | .hljs-section {
41 | color: #4271ae;
42 | }
43 |
44 | .hljs-keyword,
45 | .hljs-selector-tag {
46 | color: #8959a8;
47 | }
48 |
49 | .hljs {
50 | display: block;
51 | overflow-x: auto;
52 | background: white;
53 | color: #4d4d4c;
54 | padding: 0.5em;
55 | }
56 |
57 | .hljs-emphasis {
58 | font-style: italic;
59 | }
60 |
61 | .hljs-strong {
62 | font-weight: bold;
63 | }
--------------------------------------------------------------------------------
/src/assets/css/markdown.styl:
--------------------------------------------------------------------------------
1 | .markdown-body {
2 | -ms-text-size-adjust: 100%;
3 | -webkit-text-size-adjust: 100%;
4 | line-height: 1.5;
5 | color: #24292e;
6 | font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
7 | font-size: 16px;
8 | line-height: 1.5;
9 | word-wrap: break-word;
10 | }
11 |
12 | .markdown-body .pl-c {
13 | color: #969896;
14 | }
15 |
16 | .markdown-body .pl-c1,
17 | .markdown-body .pl-s .pl-v {
18 | color: #0086b3;
19 | }
20 |
21 | .markdown-body .pl-e,
22 | .markdown-body .pl-en {
23 | color: #795da3;
24 | }
25 |
26 | .markdown-body .pl-smi,
27 | .markdown-body .pl-s .pl-s1 {
28 | color: #333;
29 | }
30 |
31 | .markdown-body .pl-ent {
32 | color: #63a35c;
33 | }
34 |
35 | .markdown-body .pl-k {
36 | color: #a71d5d;
37 | }
38 |
39 | .markdown-body .pl-s,
40 | .markdown-body .pl-pds,
41 | .markdown-body .pl-s .pl-pse .pl-s1,
42 | .markdown-body .pl-sr,
43 | .markdown-body .pl-sr .pl-cce,
44 | .markdown-body .pl-sr .pl-sre,
45 | .markdown-body .pl-sr .pl-sra {
46 | color: #183691;
47 | }
48 |
49 | .markdown-body .pl-v,
50 | .markdown-body .pl-smw {
51 | color: #ed6a43;
52 | }
53 |
54 | .markdown-body .pl-bu {
55 | color: #b52a1d;
56 | }
57 |
58 | .markdown-body .pl-ii {
59 | color: #f8f8f8;
60 | background-color: #b52a1d;
61 | }
62 |
63 | .markdown-body .pl-c2 {
64 | color: #f8f8f8;
65 | background-color: #b52a1d;
66 | }
67 |
68 | .markdown-body .pl-c2::before {
69 | content: "^M";
70 | }
71 |
72 | .markdown-body .pl-sr .pl-cce {
73 | font-weight: bold;
74 | color: #63a35c;
75 | }
76 |
77 | .markdown-body .pl-ml {
78 | color: #693a17;
79 | }
80 |
81 | .markdown-body .pl-mh,
82 | .markdown-body .pl-mh .pl-en,
83 | .markdown-body .pl-ms {
84 | font-weight: bold;
85 | color: #1d3e81;
86 | }
87 |
88 | .markdown-body .pl-mq {
89 | color: #008080;
90 | }
91 |
92 | .markdown-body .pl-mi {
93 | font-style: italic;
94 | color: #333;
95 | }
96 |
97 | .markdown-body .pl-mb {
98 | font-weight: bold;
99 | color: #333;
100 | }
101 |
102 | .markdown-body .pl-md {
103 | color: #bd2c00;
104 | background-color: #ffecec;
105 | }
106 |
107 | .markdown-body .pl-mi1 {
108 | color: #55a532;
109 | background-color: #eaffea;
110 | }
111 |
112 | .markdown-body .pl-mc {
113 | color: #ef9700;
114 | background-color: #ffe3b4;
115 | }
116 |
117 | .markdown-body .pl-mi2 {
118 | color: #d8d8d8;
119 | background-color: #808080;
120 | }
121 |
122 | .markdown-body .pl-mdr {
123 | font-weight: bold;
124 | color: #795da3;
125 | }
126 |
127 | .markdown-body .pl-mo {
128 | color: #1d3e81;
129 | }
130 |
131 | .markdown-body .pl-ba {
132 | color: #595e62;
133 | }
134 |
135 | .markdown-body .pl-sg {
136 | color: #c0c0c0;
137 | }
138 |
139 | .markdown-body .pl-corl {
140 | text-decoration: underline;
141 | color: #183691;
142 | }
143 |
144 | .markdown-body .octicon {
145 | display: inline-block;
146 | vertical-align: text-top;
147 | fill: currentColor;
148 | }
149 |
150 | .markdown-body a {
151 | background-color: transparent;
152 | -webkit-text-decoration-skip: objects;
153 | }
154 |
155 | .markdown-body a:active,
156 | .markdown-body a:hover {
157 | outline-width: 0;
158 | }
159 |
160 | .markdown-body strong {
161 | font-weight: inherit;
162 | }
163 |
164 | .markdown-body strong {
165 | font-weight: bolder;
166 | }
167 |
168 | .markdown-body h1 {
169 | font-size: 2em;
170 | margin: 0.67em 0;
171 | }
172 |
173 | .markdown-body img {
174 | border-style: none;
175 | }
176 |
177 | .markdown-body svg:not(:root) {
178 | overflow: hidden;
179 | }
180 |
181 | .markdown-body code,
182 | .markdown-body kbd,
183 | .markdown-body pre {
184 | font-family: monospace, monospace;
185 | font-size: 1em;
186 | }
187 |
188 | .markdown-body hr {
189 | box-sizing: content-box;
190 | height: 0;
191 | overflow: visible;
192 | }
193 |
194 | .markdown-body input {
195 | font: inherit;
196 | margin: 0;
197 | }
198 |
199 | .markdown-body input {
200 | overflow: visible;
201 | }
202 |
203 | .markdown-body [type="checkbox"] {
204 | box-sizing: border-box;
205 | padding: 0;
206 | }
207 |
208 | .markdown-body * {
209 | box-sizing: border-box;
210 | }
211 |
212 | .markdown-body input {
213 | font-family: inherit;
214 | font-size: inherit;
215 | line-height: inherit;
216 | }
217 |
218 | .markdown-body a {
219 | color: #0366d6;
220 | text-decoration: none;
221 | }
222 |
223 | .markdown-body a:hover {
224 | text-decoration: underline;
225 | }
226 |
227 | .markdown-body strong {
228 | font-weight: 600;
229 | }
230 |
231 | .markdown-body hr {
232 | height: 0;
233 | margin: 15px 0;
234 | overflow: hidden;
235 | background: transparent;
236 | border: 0;
237 | border-bottom: 1px solid #dfe2e5;
238 | }
239 |
240 | .markdown-body hr::before {
241 | display: table;
242 | content: "";
243 | }
244 |
245 | .markdown-body hr::after {
246 | display: table;
247 | clear: both;
248 | content: "";
249 | }
250 |
251 | .markdown-body table {
252 | border-spacing: 0;
253 | border-collapse: collapse;
254 | }
255 |
256 | .markdown-body td,
257 | .markdown-body th {
258 | padding: 0;
259 | }
260 |
261 | .markdown-body h1,
262 | .markdown-body h2,
263 | .markdown-body h3,
264 | .markdown-body h4,
265 | .markdown-body h5,
266 | .markdown-body h6 {
267 | margin-top: 0;
268 | margin-bottom: 0;
269 | }
270 |
271 | .markdown-body h1 {
272 | font-size: 32px;
273 | font-weight: 600;
274 | }
275 |
276 | .markdown-body h2 {
277 | font-size: 24px;
278 | font-weight: 600;
279 | }
280 |
281 | .markdown-body h3 {
282 | font-size: 20px;
283 | font-weight: 600;
284 | }
285 |
286 | .markdown-body h4 {
287 | font-size: 16px;
288 | font-weight: 600;
289 | }
290 |
291 | .markdown-body h5 {
292 | font-size: 14px;
293 | font-weight: 600;
294 | }
295 |
296 | .markdown-body h6 {
297 | font-size: 12px;
298 | font-weight: 600;
299 | }
300 |
301 | .markdown-body p {
302 | margin-top: 0;
303 | margin-bottom: 10px;
304 | }
305 |
306 | .markdown-body blockquote {
307 | margin: 0;
308 | }
309 |
310 | .markdown-body ul,
311 | .markdown-body ol {
312 | padding-left: 0;
313 | margin-top: 0;
314 | margin-bottom: 0;
315 | }
316 |
317 | .markdown-body ol ol,
318 | .markdown-body ul ol {
319 | list-style-type: lower-roman;
320 | }
321 |
322 | .markdown-body ul ul ol,
323 | .markdown-body ul ol ol,
324 | .markdown-body ol ul ol,
325 | .markdown-body ol ol ol {
326 | list-style-type: lower-alpha;
327 | }
328 |
329 | .markdown-body dd {
330 | margin-left: 0;
331 | }
332 |
333 | .markdown-body code {
334 | font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
335 | font-size: 12px;
336 | }
337 |
338 | .markdown-body pre {
339 | margin-top: 0;
340 | margin-bottom: 0;
341 | font: 12px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
342 | }
343 |
344 | .markdown-body .octicon {
345 | vertical-align: text-bottom;
346 | }
347 |
348 | .markdown-body .pl-0 {
349 | padding-left: 0 !important;
350 | }
351 |
352 | .markdown-body .pl-1 {
353 | padding-left: 4px !important;
354 | }
355 |
356 | .markdown-body .pl-2 {
357 | padding-left: 8px !important;
358 | }
359 |
360 | .markdown-body .pl-3 {
361 | padding-left: 16px !important;
362 | }
363 |
364 | .markdown-body .pl-4 {
365 | padding-left: 24px !important;
366 | }
367 |
368 | .markdown-body .pl-5 {
369 | padding-left: 32px !important;
370 | }
371 |
372 | .markdown-body .pl-6 {
373 | padding-left: 40px !important;
374 | }
375 |
376 | .markdown-body::before {
377 | display: table;
378 | content: "";
379 | }
380 |
381 | .markdown-body::after {
382 | display: table;
383 | //clear: both;
384 | content: "";
385 | }
386 |
387 | .markdown-body>*:first-child {
388 | margin-top: 0 !important;
389 | }
390 |
391 | .markdown-body>*:last-child {
392 | margin-bottom: 0 !important;
393 | }
394 |
395 | .markdown-body a:not([href]) {
396 | color: inherit;
397 | text-decoration: none;
398 | }
399 |
400 | .markdown-body .anchor {
401 | float: left;
402 | padding-right: 4px;
403 | margin-left: -20px;
404 | line-height: 1;
405 | }
406 |
407 | .markdown-body .anchor:focus {
408 | outline: none;
409 | }
410 |
411 | .markdown-body p,
412 | .markdown-body blockquote,
413 | .markdown-body ul,
414 | .markdown-body ol,
415 | .markdown-body dl,
416 | .markdown-body table,
417 | .markdown-body pre {
418 | margin-top: 0;
419 | margin-bottom: 16px;
420 | }
421 |
422 | .markdown-body hr {
423 | height: 0.25em;
424 | padding: 0;
425 | margin: 24px 0;
426 | background-color: #e1e4e8;
427 | border: 0;
428 | }
429 |
430 | .markdown-body blockquote {
431 | padding: 0 1em;
432 | color: #6a737d;
433 | border-left: 0.25em solid #dfe2e5;
434 | }
435 |
436 | .markdown-body blockquote>:first-child {
437 | margin-top: 0;
438 | }
439 |
440 | .markdown-body blockquote>:last-child {
441 | margin-bottom: 0;
442 | }
443 |
444 | .markdown-body kbd {
445 | display: inline-block;
446 | padding: 3px 5px;
447 | font-size: 11px;
448 | line-height: 10px;
449 | color: #444d56;
450 | vertical-align: middle;
451 | background-color: #fafbfc;
452 | border: solid 1px #c6cbd1;
453 | border-bottom-color: #959da5;
454 | border-radius: 3px;
455 | box-shadow: inset 0 -1px 0 #959da5;
456 | }
457 |
458 | .markdown-body h1,
459 | .markdown-body h2,
460 | .markdown-body h3,
461 | .markdown-body h4,
462 | .markdown-body h5,
463 | .markdown-body h6 {
464 | margin-top: 24px;
465 | margin-bottom: 16px;
466 | font-weight: 600;
467 | line-height: 1.25;
468 | }
469 |
470 | .markdown-body h1 .octicon-link,
471 | .markdown-body h2 .octicon-link,
472 | .markdown-body h3 .octicon-link,
473 | .markdown-body h4 .octicon-link,
474 | .markdown-body h5 .octicon-link,
475 | .markdown-body h6 .octicon-link {
476 | color: #1b1f23;
477 | vertical-align: middle;
478 | visibility: hidden;
479 | }
480 |
481 | .markdown-body h1:hover .anchor,
482 | .markdown-body h2:hover .anchor,
483 | .markdown-body h3:hover .anchor,
484 | .markdown-body h4:hover .anchor,
485 | .markdown-body h5:hover .anchor,
486 | .markdown-body h6:hover .anchor {
487 | text-decoration: none;
488 | }
489 |
490 | .markdown-body h1:hover .anchor .octicon-link,
491 | .markdown-body h2:hover .anchor .octicon-link,
492 | .markdown-body h3:hover .anchor .octicon-link,
493 | .markdown-body h4:hover .anchor .octicon-link,
494 | .markdown-body h5:hover .anchor .octicon-link,
495 | .markdown-body h6:hover .anchor .octicon-link {
496 | visibility: visible;
497 | }
498 |
499 | .markdown-body h1 {
500 | padding-bottom: 0.3em;
501 | font-size: 2em;
502 | border-bottom: 1px solid #eaecef;
503 | }
504 |
505 | .markdown-body h2 {
506 | padding-bottom: 0.3em;
507 | font-size: 1.5em;
508 | border-bottom: 1px solid #eaecef;
509 | }
510 |
511 | .markdown-body h3 {
512 | font-size: 1.25em;
513 | }
514 |
515 | .markdown-body h4 {
516 | font-size: 1em;
517 | }
518 |
519 | .markdown-body h5 {
520 | font-size: 0.875em;
521 | }
522 |
523 | .markdown-body h6 {
524 | font-size: 0.85em;
525 | color: #6a737d;
526 | }
527 |
528 | .markdown-body ul,
529 | .markdown-body ol {
530 | padding-left: 2em;
531 | }
532 |
533 | .markdown-body ul ul,
534 | .markdown-body ul ol,
535 | .markdown-body ol ol,
536 | .markdown-body ol ul {
537 | margin-top: 0;
538 | margin-bottom: 0;
539 | }
540 |
541 | .markdown-body li>p {
542 | margin-top: 16px;
543 | }
544 |
545 | .markdown-body li+li {
546 | margin-top: 0.25em;
547 | }
548 |
549 | .markdown-body dl {
550 | padding: 0;
551 | }
552 |
553 | .markdown-body dl dt {
554 | padding: 0;
555 | margin-top: 16px;
556 | font-size: 1em;
557 | font-style: italic;
558 | font-weight: 600;
559 | }
560 |
561 | .markdown-body dl dd {
562 | padding: 0 16px;
563 | margin-bottom: 16px;
564 | }
565 |
566 | .markdown-body table {
567 | display: block;
568 | width: 100%;
569 | overflow: auto;
570 | }
571 |
572 | .markdown-body table th {
573 | font-weight: 600;
574 | }
575 |
576 | .markdown-body table th,
577 | .markdown-body table td {
578 | padding: 6px 13px;
579 | border: 1px solid #dfe2e5;
580 | }
581 |
582 | .markdown-body table tr {
583 | background-color: #fff;
584 | border-top: 1px solid #c6cbd1;
585 | }
586 |
587 | .markdown-body table tr:nth-child(2n) {
588 | background-color: #f6f8fa;
589 | }
590 |
591 | .markdown-body img {
592 | max-width: 100%;
593 | box-sizing: content-box;
594 | background-color: #fff;
595 | }
596 |
597 | .markdown-body code {
598 | padding: 0;
599 | padding-top: 0.2em;
600 | padding-bottom: 0.2em;
601 | margin: 0;
602 | font-size: 85%;
603 | background-color: rgba(27,31,35,0.05);
604 | border-radius: 3px;
605 | }
606 |
607 | .markdown-body code::before,
608 | .markdown-body code::after {
609 | letter-spacing: -0.2em;
610 | content: "\00a0";
611 | }
612 |
613 | .markdown-body pre {
614 | word-wrap: normal;
615 | }
616 |
617 | .markdown-body pre>code {
618 | padding: 0;
619 | margin: 0;
620 | font-size: 100%;
621 | word-break: normal;
622 | white-space: pre;
623 | background: transparent;
624 | border: 0;
625 | }
626 |
627 | .markdown-body .highlight {
628 | margin-bottom: 16px;
629 | }
630 |
631 | .markdown-body .highlight pre {
632 | margin-bottom: 0;
633 | word-break: normal;
634 | }
635 |
636 | .markdown-body .highlight pre,
637 | .markdown-body pre {
638 | padding: 16px;
639 | overflow: auto;
640 | font-size: 85%;
641 | line-height: 1.45;
642 | background-color: #f6f8fa;
643 | border-radius: 3px;
644 | }
645 |
646 | .markdown-body pre code {
647 | display: inline;
648 | max-width: auto;
649 | padding: 0;
650 | margin: 0;
651 | overflow: visible;
652 | line-height: inherit;
653 | word-wrap: normal;
654 | background-color: transparent;
655 | border: 0;
656 | }
657 |
658 | .markdown-body pre code::before,
659 | .markdown-body pre code::after {
660 | content: normal;
661 | }
662 |
663 | .markdown-body .full-commit .btn-outline:not(:disabled):hover {
664 | color: #005cc5;
665 | border-color: #005cc5;
666 | }
667 |
668 | .markdown-body kbd {
669 | display: inline-block;
670 | padding: 3px 5px;
671 | font: 11px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
672 | line-height: 10px;
673 | color: #444d56;
674 | vertical-align: middle;
675 | background-color: #fcfcfc;
676 | border: solid 1px #c6cbd1;
677 | border-bottom-color: #959da5;
678 | border-radius: 3px;
679 | box-shadow: inset 0 -1px 0 #959da5;
680 | }
681 |
682 | .markdown-body :checked+.radio-label {
683 | position: relative;
684 | z-index: 1;
685 | border-color: #0366d6;
686 | }
687 |
688 | .markdown-body .task-list-item {
689 | list-style-type: none;
690 | }
691 |
692 | .markdown-body .task-list-item+.task-list-item {
693 | margin-top: 3px;
694 | }
695 |
696 | .markdown-body .task-list-item input {
697 | margin: 0 0.2em 0.25em -1.6em;
698 | vertical-align: middle;
699 | }
700 |
701 | .markdown-body hr {
702 | border-bottom-color: #eee;
703 | }
--------------------------------------------------------------------------------
/src/assets/css/reset.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * ress.css • v1.1.2
3 | * MIT License
4 | * github.com/filipelinhares/ress
5 | */
6 |
7 | /* # =================================================================
8 | # Global selectors
9 | # ================================================================= */
10 |
11 | html {
12 | box-sizing: border-box;
13 | -webkit-text-size-adjust: 100%; /* iOS 8+ */
14 | }
15 | *{
16 | margin: 0;
17 | padding:0
18 | }
19 | *,
20 | ::before,
21 | ::after {
22 | box-sizing: inherit;
23 | }
24 |
25 | ::before,
26 | ::after {
27 | text-decoration: inherit; /* Inherit text-decoration and vertical align to ::before and ::after pseudo elements */
28 | vertical-align: inherit;
29 | }
30 |
31 | /* Remove margin, padding of all elements and set background-no-repeat as default */
32 | * {
33 | background-repeat: no-repeat; /* Set `background-repeat: no-repeat` to all elements */
34 | padding: 0; /* Reset `padding` and `margin` of all elements */
35 | margin: 0;
36 | }
37 |
38 | /* # =================================================================
39 | # General elements
40 | # ================================================================= */
41 |
42 | /* Add the correct display in iOS 4-7.*/
43 | audio:not([controls]) {
44 | display: none;
45 | height: 0;
46 | }
47 |
48 | hr {
49 | overflow: visible; /* Show the overflow in Edge and IE */
50 | }
51 |
52 | /*
53 | * Correct `block` display not defined for any HTML5 element in IE 8/9
54 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
55 | * and Firefox
56 | * Correct `block` display not defined for `main` in IE 11
57 | */
58 | article,
59 | aside,
60 | details,
61 | figcaption,
62 | figure,
63 | footer,
64 | header,
65 | main,
66 | menu,
67 | nav,
68 | section,
69 | summary {
70 | display: block;
71 | }
72 |
73 | summary {
74 | display: list-item; /* Add the correct display in all browsers */
75 | }
76 |
77 | small {
78 | font-size: 80%; /* Set font-size to 80% in `small` elements */
79 | }
80 |
81 | [hidden],
82 | template {
83 | display: none; /* Add the correct display in IE */
84 | }
85 |
86 | abbr[title] {
87 | border-bottom: 1px dotted; /* Add a bordered underline effect in all browsers */
88 | text-decoration: none; /* Remove text decoration in Firefox 40+ */
89 | }
90 |
91 | a {
92 | color: #000;
93 | text-decoration: none;
94 | background-color: transparent; /* Remove the gray background on active links in IE 10 */
95 | -webkit-text-decoration-skip: objects; /* Remove gaps in links underline in iOS 8+ and Safari 8+ */
96 | }
97 |
98 | a:active,
99 | a:hover {
100 | outline-width: 0; /* Remove the outline when hovering in all browsers */
101 | }
102 |
103 | code,
104 | kbd,
105 | pre,
106 | samp {
107 | font-family: monospace, monospace; /* Specify the font family of code elements */
108 | }
109 |
110 | b,
111 | strong {
112 | font-weight: bolder; /* Correct style set to `bold` in Edge 12+, Safari 6.2+, and Chrome 18+ */
113 | }
114 |
115 | dfn {
116 | font-style: italic; /* Address styling not present in Safari and Chrome */
117 | }
118 |
119 | /* Address styling not present in IE 8/9 */
120 | mark {
121 | background-color: #ff0;
122 | color: #000;
123 | }
124 |
125 | /* https://gist.github.com/unruthless/413930 */
126 | sub,
127 | sup {
128 | font-size: 75%;
129 | line-height: 0;
130 | position: relative;
131 | vertical-align: baseline;
132 | }
133 |
134 | sub {
135 | bottom: -0.25em;
136 | }
137 |
138 | sup {
139 | top: -0.5em;
140 | }
141 |
142 | /* # =================================================================
143 | # Forms
144 | # ================================================================= */
145 |
146 | input {
147 | border-radius: 0;
148 | }
149 |
150 | /* Apply cursor pointer to button elements */
151 | button,
152 | [type="button"],
153 | [type="reset"],
154 | [type="submit"],
155 | [role="button"] {
156 | cursor: pointer;
157 | }
158 |
159 | /* Replace pointer cursor in disabled elements */
160 | [disabled] {
161 | cursor: default;
162 | }
163 |
164 | [type="number"] {
165 | width: auto; /* Firefox 36+ */
166 | }
167 |
168 | [type="search"] {
169 | -webkit-appearance: textfield; /* Safari 8+ */
170 | }
171 |
172 | [type="search"]::-webkit-search-cancel-button,
173 | [type="search"]::-webkit-search-decoration {
174 | -webkit-appearance: none; /* Safari 8 */
175 | }
176 |
177 | textarea {
178 | overflow: auto; /* Internet Explorer 11+ */
179 | resize: vertical; /* Specify textarea resizability */
180 | }
181 |
182 | button,
183 | input,
184 | optgroup,
185 | select,
186 | textarea {
187 | font: inherit; /* Specify font inheritance of form elements */
188 | }
189 |
190 | optgroup {
191 | font-weight: bold; /* Restore the font weight unset by the previous rule. */
192 | }
193 |
194 | button {
195 | overflow: visible; /* Address `overflow` set to `hidden` in IE 8/9/10/11 */
196 | }
197 |
198 | /* Remove inner padding and border in Firefox 4+ */
199 | button::-moz-focus-inner,
200 | [type="button"]::-moz-focus-inner,
201 | [type="reset"]::-moz-focus-inner,
202 | [type="submit"]::-moz-focus-inner {
203 | border-style: 0;
204 | padding: 0;
205 | }
206 |
207 | /* Replace focus style removed in the border reset above */
208 | button:-moz-focusring,
209 | [type="button"]::-moz-focus-inner,
210 | [type="reset"]::-moz-focus-inner,
211 | [type="submit"]::-moz-focus-inner {
212 | outline: 1px dotted ButtonText;
213 | }
214 |
215 | button,
216 | html [type="button"], /* Prevent a WebKit bug where (2) destroys native `audio` and `video`controls in Android 4 */
217 | [type="reset"],
218 | [type="submit"] {
219 | -webkit-appearance: button; /* Correct the inability to style clickable types in iOS */
220 | }
221 |
222 | button,
223 | select {
224 | text-transform: none; /* Firefox 40+, Internet Explorer 11- */
225 | }
226 |
227 | /* Remove the default button styling in all browsers */
228 | button,
229 | input,
230 | select,
231 | textarea {
232 | background-color: transparent;
233 | border-style: none;
234 | color: inherit;
235 | }
236 |
237 | /* Style select like a standard input */
238 | select {
239 | -moz-appearance: none; /* Firefox 36+ */
240 | -webkit-appearance: none; /* Chrome 41+ */
241 | }
242 |
243 | select::-ms-expand {
244 | display: none; /* Internet Explorer 11+ */
245 | }
246 |
247 | select::-ms-value {
248 | color: currentColor; /* Internet Explorer 11+ */
249 | }
250 |
251 | legend {
252 | border: 0; /* Correct `color` not being inherited in IE 8/9/10/11 */
253 | color: inherit; /* Correct the color inheritance from `fieldset` elements in IE */
254 | display: table; /* Correct the text wrapping in Edge and IE */
255 | max-width: 100%; /* Correct the text wrapping in Edge and IE */
256 | white-space: normal; /* Correct the text wrapping in Edge and IE */
257 | }
258 |
259 | ::-webkit-file-upload-button {
260 | -webkit-appearance: button; /* Correct the inability to style clickable types in iOS and Safari */
261 | font: inherit; /* Change font properties to `inherit` in Chrome and Safari */
262 | }
263 |
264 | [type="search"] {
265 | -webkit-appearance: textfield; /* Correct the odd appearance in Chrome and Safari */
266 | outline-offset: -2px; /* Correct the outline style in Safari */
267 | }
268 |
269 | /* # =================================================================
270 | # Specify media element style
271 | # ================================================================= */
272 |
273 | img {
274 | border-style: none; /* Remove border when inside `a` element in IE 8/9/10 */
275 | }
276 |
277 | /* Add the correct vertical alignment in Chrome, Firefox, and Opera */
278 | progress {
279 | vertical-align: baseline;
280 | }
281 |
282 | svg:not(:root) {
283 | overflow: hidden; /* Internet Explorer 11- */
284 | }
285 |
286 | audio,
287 | canvas,
288 | progress,
289 | video {
290 | display: inline-block; /* Internet Explorer 11+, Windows Phone 8.1+ */
291 | }
292 |
293 | /* # =================================================================
294 | # Accessibility
295 | # ================================================================= */
296 |
297 | /* Hide content from screens but not screenreaders */
298 | @media screen {
299 | [hidden~="screen"] {
300 | display: inherit;
301 | }
302 | [hidden~="screen"]:not(:active):not(:focus):not(:target) {
303 | position: absolute !important;
304 | clip: rect(0 0 0 0) !important;
305 | }
306 | }
307 |
308 | /* Specify the progress cursor of updating elements */
309 | [aria-busy="true"] {
310 | cursor: progress;
311 | }
312 |
313 | /* Specify the pointer cursor of trigger elements */
314 | [aria-controls] {
315 | cursor: pointer;
316 | }
317 |
318 | /* Specify the unstyled cursor of disabled, not-editable, or otherwise inoperable elements */
319 | [aria-disabled] {
320 | cursor: default;
321 | }
322 |
323 | /* # =================================================================
324 | # Selection
325 | # ================================================================= */
326 |
327 | /* Specify text selection background color and omit drop shadow */
328 |
329 | ::-moz-selection {
330 | background-color: #b3d4fc; /* Required when declaring ::selection */
331 | color: #000;
332 | text-shadow: none;
333 | }
334 |
335 | ::selection {
336 | background-color: #b3d4fc; /* Required when declaring ::selection */
337 | color: #000;
338 | text-shadow: none;
339 | }
340 |
341 |
--------------------------------------------------------------------------------
/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hbbaly/vue-blog/b624636e9ca48174e0bb49ef28d87c3e26be8a60/src/assets/images/logo.png
--------------------------------------------------------------------------------
/src/assets/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hbbaly/vue-blog/b624636e9ca48174e0bb49ef28d87c3e26be8a60/src/assets/img/logo.png
--------------------------------------------------------------------------------
/src/assets/js/commen.js:
--------------------------------------------------------------------------------
1 | export function sub(obj,res){
2 | obj.btnText="提交";
3 | obj.editLoading = false;
4 |
5 | if(res.data.code==200){
6 | obj.$notify({
7 | title:'成功',
8 | message:'提交成功',
9 | type:'success'
10 | })
11 | }else if(res.data.code==401){
12 | this.$notify({
13 | title:'失败',
14 | message,
15 | type:'error'
16 | })
17 | setTimeout(()=>{
18 | this.$router.replace({path:'/login'})
19 | },500)
20 | return false//阻止继续执行
21 | // 需要优化
22 | }
23 | obj.formVisible = false;
24 | obj.getLists();
25 | }
26 |
--------------------------------------------------------------------------------
/src/assets/js/marked.js:
--------------------------------------------------------------------------------
1 |
2 | import highlight from 'highlight.js';
3 | import marked from 'marked';
4 | import 'highlight.js/styles/monokai-sublime.css'
5 | const languages = ['cpp', 'xml', 'bash', 'coffeescript', 'css', 'markdown', 'http', 'java', 'javascript', 'json', 'less', 'makefile', 'nginx', 'php', 'python', 'scss', 'sql', 'stylus'];
6 | highlight.registerLanguage('cpp', require('highlight.js/lib/languages/cpp'));
7 | highlight.registerLanguage('xml', require('highlight.js/lib/languages/xml'));
8 | highlight.registerLanguage('bash', require('highlight.js/lib/languages/bash'));
9 | highlight.registerLanguage('coffeescript', require('highlight.js/lib/languages/coffeescript'));
10 | highlight.registerLanguage('css', require('highlight.js/lib/languages/css'));
11 | highlight.registerLanguage('markdown', require('highlight.js/lib/languages/markdown'));
12 | highlight.registerLanguage('http', require('highlight.js/lib/languages/http'));
13 | highlight.registerLanguage('java', require('highlight.js/lib/languages/java'));
14 | highlight.registerLanguage('javascript', require('highlight.js/lib/languages/javascript'));
15 | highlight.registerLanguage('json', require('highlight.js/lib/languages/json'));
16 | highlight.registerLanguage('less', require('highlight.js/lib/languages/less'));
17 | highlight.registerLanguage('makefile', require('highlight.js/lib/languages/makefile'));
18 | highlight.registerLanguage('nginx', require('highlight.js/lib/languages/nginx'));
19 | highlight.registerLanguage('php', require('highlight.js/lib/languages/php'));
20 | highlight.registerLanguage('python', require('highlight.js/lib/languages/python'));
21 | highlight.registerLanguage('scss', require('highlight.js/lib/languages/scss'));
22 | highlight.registerLanguage('sql', require('highlight.js/lib/languages/sql'));
23 | highlight.registerLanguage('stylus', require('highlight.js/lib/languages/stylus'));
24 | highlight.configure({
25 | classPrefix: '', // don't append class prefix
26 | });
27 | // https://github.com/chjj/marked
28 | marked.setOptions({
29 | renderer: new marked.Renderer(),
30 | gfm: true,
31 | pedantic: false,
32 | sanitize: false,
33 | tables: true,
34 | breaks: true,
35 | smartLists: true,
36 | smartypants: true,
37 | highlight: function (code, lang) {
38 | if (!lang) {
39 | return;
40 | }
41 | if (!~languages.indexOf(lang)) {
42 | return highlight.highlightAuto(code).value;
43 | }
44 | return highlight.highlight(lang, code).value;
45 | },
46 | });
47 | export default marked;
48 |
--------------------------------------------------------------------------------
/src/components/Comment.vue:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
40 |
41 |
75 |
--------------------------------------------------------------------------------
/src/components/Info.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {{ btnTxt }}
31 | 重置
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
123 |
131 |
--------------------------------------------------------------------------------
/src/components/Item.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ item.score }}
4 |
5 |
6 | {{ item.title }}
7 | ({{ item.url | host }})
8 |
9 |
10 | {{ item.title }}
11 |
12 |
13 |
14 |
15 |
16 | by {{ item.by }}
17 |
18 |
19 | {{ item.time | timeAgo }} ago
20 |
21 |
24 |
25 | {{ item.type }}
26 |
27 |
28 |
29 |
41 |
42 |
68 |
--------------------------------------------------------------------------------
/src/components/ProgressBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
88 |
89 |
102 |
--------------------------------------------------------------------------------
/src/components/Spinner.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
16 |
17 |
55 |
--------------------------------------------------------------------------------
/src/components/header.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
57 |
--------------------------------------------------------------------------------
/src/components/sidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
55 |
--------------------------------------------------------------------------------
/src/config/common.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'APP_NAME': 'vue-blog',
3 | 'APP_DESC': ''
4 | }
5 |
--------------------------------------------------------------------------------
/src/config/development.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | SERVER: ''
3 | }
4 |
5 |
--------------------------------------------------------------------------------
/src/config/index.js:
--------------------------------------------------------------------------------
1 | import commonConfig from './common.config'
2 | import developmentConfig from './development.config'
3 | import productionConfig from './production.config'
4 |
5 | function buildConfig (type) {
6 | let envConfig = process.env.NODE_ENV === 'production' ? productionConfig : developmentConfig
7 | return Object.assign(commonConfig, envConfig)
8 | }
9 |
10 | export default buildConfig()
11 |
--------------------------------------------------------------------------------
/src/config/production.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | }
3 |
--------------------------------------------------------------------------------
/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 ElementUI from 'element-ui'
7 | import 'element-ui/lib/theme-chalk/index.css'
8 | import './assets/css/reset.css'
9 | // import ProgressBar from './components/ProgressBar.vue'
10 | Vue.use(ElementUI)
11 | Vue.config.productionTip = false
12 | // global progress bar
13 | // const bar = Vue.prototype.$bar = new Vue(ProgressBar).$mount()
14 | // document.body.appendChild(bar.$el)
15 | /* eslint-disable no-new */
16 | new Vue({
17 | el: '#app',
18 | router,
19 | components: { App },
20 | template: ''
21 | })
22 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | // route-level code splitting
4 | const Index = () => import('../views/front/Index.vue')
5 | const AdminView = () => import('../views/admin/index.vue')
6 | const ArticleDetail = () => import('../views/front/ArticleDetail.vue')
7 | const TimeLine = () => import('../views/front/TimeLine.vue')
8 | const Tags = () => import('../views/front/Tags.vue')
9 | const ArticleList = () => import('../views/front/ArticleList.vue')
10 | Vue.use(Router)
11 |
12 | export default new Router({
13 | routes: [
14 | {
15 | path: '/',
16 | name: 'Index',
17 | component: Index
18 | },
19 | {
20 | path: '/admin',
21 | name: 'Admin',
22 | component: AdminView
23 | },
24 | {
25 | path: '/article/:_id',
26 | name: 'ArticleDetail',
27 | component: ArticleDetail
28 | },
29 | {
30 | path: '/timeline',
31 | name: 'TimeLine',
32 | component: TimeLine
33 | },
34 | {
35 | path: '/tags',
36 | name: 'Tags',
37 | component: Tags
38 | },
39 | {
40 | path: '/:type/article',
41 | name: 'ArticleList',
42 | component: ArticleList
43 | }
44 | ]
45 | })
46 |
--------------------------------------------------------------------------------
/src/router/routes.js:
--------------------------------------------------------------------------------
1 | import Index from '@/view/index/IndexView'
2 | import Register from '@/view/user/register/IndexView'
3 | import Login from '@/view/user/login/IndexView'
4 | import User from '@/view/user/index/IndexView'
5 | import CreateArticle from '@/view/user/article/create/IndexView'
6 | import ArticleList from '@/view/user/article/list/IndexView'
7 | import Tag from '@/view/user/article/tag/IndexView'
8 | import ChangePass from '@/view/user/changepass/IndexView'
9 | import Detail from '@/view/user/article/detail/IndexView'
10 | import NotFound from '@/view/notfound/NotFoundView'
11 | export default [
12 | {
13 | path:'/',
14 | component:Index,
15 | },
16 | {
17 | path: '/user',
18 | component: User,
19 | children: [
20 | {
21 | path:'',redirect: {name:'ArticleCreate'}
22 | },
23 | {
24 | name: 'ArticleCreate',
25 | path: '/article/create',
26 | component: CreateArticle
27 | },
28 | {
29 | path: '/article/list',
30 | component: ArticleList
31 | },
32 | {
33 | path: '/article/tag',
34 | component: Tag
35 | },
36 | {
37 | path: '/settings/changepass',
38 | component: ChangePass
39 | }
40 | ]
41 | },
42 | {
43 | path: '/user/register',
44 | component: Register
45 | },
46 | {
47 | path: '/user/login',
48 | component: Login
49 | },
50 | {
51 | path: '/user/article/detail/:_id',
52 | component: Detail
53 | },
54 | {
55 | name: 'NotFound',
56 | path:'*',
57 | component: NotFound
58 | }
59 | ]
60 |
--------------------------------------------------------------------------------
/src/store/actions.js:
--------------------------------------------------------------------------------
1 | import API from '../api'
2 |
3 | export default {
4 | GET_TOPICS: ({
5 | commit
6 | }) => {
7 | return API.home
8 | .getTopics({page:1,limit:20,tab:'share'})
9 | .then((res) => {
10 | if (res.status !== -404 && res.data && res.data) {
11 | commit('GET_TOPICS', res.data.data)
12 | }
13 | }).catch(() => {
14 |
15 | })
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/src/store/getters.js:
--------------------------------------------------------------------------------
1 | export default {
2 | // ids of the items that should be currently displayed based on
3 | // current list type and current pagination
4 | topicList (state) {
5 | return state.topicList
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import actions from './actions'
4 | import mutations from './mutations'
5 | import getters from './getters'
6 |
7 | Vue.use(Vuex)
8 |
9 | export function createStore () {
10 | return new Vuex.Store({
11 | state: {
12 | topicList:[]
13 | },
14 | actions,
15 | mutations,
16 | getters
17 | })
18 | }
19 |
--------------------------------------------------------------------------------
/src/store/mutations.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | export default {
4 | GET_TOPICS: (state, list) => {
5 | state.topicList = list
6 | },
7 | }
8 |
--------------------------------------------------------------------------------
/src/utils/filters.js:
--------------------------------------------------------------------------------
1 | export function host (url) {
2 | const host = url.replace(/^https?:\/\//, '').replace(/\/.*$/, '')
3 | const parts = host.split('.').slice(-3)
4 | if (parts[0] === 'www') parts.shift()
5 | return parts.join('.')
6 | }
7 |
8 | export function timeAgo (time) {
9 | const between = Date.now() / 1000 - Number(time)
10 | if (between < 3600) {
11 | return pluralize(~~(between / 60), ' minute')
12 | } else if (between < 86400) {
13 | return pluralize(~~(between / 3600), ' hour')
14 | } else {
15 | return pluralize(~~(between / 86400), ' day')
16 | }
17 | }
18 |
19 | function pluralize (time, label) {
20 | if (time === 1) {
21 | return time + label
22 | }
23 | return time + label + 's'
24 | }
25 |
--------------------------------------------------------------------------------
/src/utils/http.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | import axios from 'axios'
3 | import qs from 'qs'
4 | import config from '../config/index'
5 | axios.interceptors.request.use(
6 | config => {
7 | // loading
8 | return config
9 | },
10 | error => {
11 | return Promise.reject(error)
12 | }
13 | )
14 |
15 | axios.interceptors.response.use(
16 | response => {
17 | return response
18 | },
19 | error => {
20 | return Promise.resolve(error.response)
21 | }
22 | )
23 |
24 | function checkStatus(response) {
25 | // loading
26 | // 如果http状态码正常,则直接返回数据
27 | if (
28 | response &&
29 | (response.status === 200 ||
30 | response.status === 304 ||
31 | response.status === 400)
32 | ) {
33 | if (response.data.code === 200) {
34 | return response
35 | // 如果不需要除了data之外的数据,可以直接 return response.data
36 | }
37 | return response
38 | }
39 | // 异常状态下,把错误信息返回去
40 | return {
41 | status: -404,
42 | msg: '网络异常'
43 | }
44 | }
45 |
46 | function checkCode(res) {
47 | // 如果code异常(这里已经包括网络错误,服务器错误,后端抛出的错误),可以弹出一个错误提示,告诉用户
48 | if (res.status === -404) {
49 | }
50 | if (res.data && !res.data.success) {
51 | }
52 | return res
53 | }
54 |
55 | export default {
56 | post(url, data) {
57 | return axios({
58 | method: 'post',
59 | baseURL: config.API_SERVER,
60 | url,
61 | data: qs.stringify(data),
62 | timeout: 10000,
63 | headers: {
64 | 'X-Requested-With': 'XMLHttpRequest',
65 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
66 | }
67 | })
68 | .then(response => {
69 | return checkStatus(response)
70 | })
71 | .then(res => {
72 | return checkCode(res)
73 | })
74 | },
75 | get(url, params) {
76 | return axios({
77 | method: 'get',
78 | baseURL: config.API_SERVER,
79 | url,
80 | params, // get 请求时带的参数
81 | timeout: 10000,
82 | headers: {
83 | 'X-Requested-With': 'XMLHttpRequest'
84 | }
85 | })
86 | .then(response => {
87 | return checkStatus(response)
88 | })
89 | .then(res => {
90 | return checkCode(res)
91 | })
92 | .catch(err => {
93 | if (err) {
94 | }
95 | })
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hbbaly/vue-blog/b624636e9ca48174e0bb49ef28d87c3e26be8a60/src/utils/index.js
--------------------------------------------------------------------------------
/src/utils/marked.js:
--------------------------------------------------------------------------------
1 | import highlight from 'highlight.js';
2 | import marked from 'marked';
3 | import 'highlight.js/styles/darcula.css'
4 | const languages = ['cpp', 'xml', 'bash', 'coffeescript', 'css', 'markdown', 'http', 'java', 'javascript', 'json', 'less', 'makefile', 'nginx', 'php', 'python', 'scss', 'sql', 'stylus'];
5 | highlight.registerLanguage('cpp', require('highlight.js/lib/languages/cpp'));
6 | highlight.registerLanguage('xml', require('highlight.js/lib/languages/xml'));
7 | highlight.registerLanguage('bash', require('highlight.js/lib/languages/bash'));
8 | highlight.registerLanguage('coffeescript', require('highlight.js/lib/languages/coffeescript'));
9 | highlight.registerLanguage('css', require('highlight.js/lib/languages/css'));
10 | highlight.registerLanguage('markdown', require('highlight.js/lib/languages/markdown'));
11 | highlight.registerLanguage('http', require('highlight.js/lib/languages/http'));
12 | highlight.registerLanguage('java', require('highlight.js/lib/languages/java'));
13 | highlight.registerLanguage('javascript', require('highlight.js/lib/languages/javascript'));
14 | highlight.registerLanguage('json', require('highlight.js/lib/languages/json'));
15 | highlight.registerLanguage('less', require('highlight.js/lib/languages/less'));
16 | highlight.registerLanguage('makefile', require('highlight.js/lib/languages/makefile'));
17 | highlight.registerLanguage('nginx', require('highlight.js/lib/languages/nginx'));
18 | highlight.registerLanguage('php', require('highlight.js/lib/languages/php'));
19 | highlight.registerLanguage('python', require('highlight.js/lib/languages/python'));
20 | highlight.registerLanguage('scss', require('highlight.js/lib/languages/scss'));
21 | highlight.registerLanguage('sql', require('highlight.js/lib/languages/sql'));
22 | highlight.registerLanguage('stylus', require('highlight.js/lib/languages/stylus'));
23 | highlight.configure({
24 | classPrefix: '', // don't append class prefix
25 | });
26 | // https://github.com/chjj/marked
27 | marked.setOptions({
28 | renderer: new marked.Renderer(),
29 | gfm: true,
30 | pedantic: false,
31 | sanitize: false,
32 | tables: true,
33 | breaks: true,
34 | smartLists: true,
35 | smartypants: true,
36 | highlight: function (code, lang) {
37 | if (!lang) {
38 | return;
39 | }
40 | if (!~languages.indexOf(lang)) {
41 | return highlight.highlightAuto(code).value;
42 | }
43 | return highlight.highlight(lang, code).value;
44 | },
45 | });
46 | export default marked;
47 |
--------------------------------------------------------------------------------
/src/utils/meta.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Page meta message operate
3 | */
4 | function getMeta (vm) {
5 | const {
6 | meta
7 | } = vm.$options
8 | return typeof meta === 'function'
9 | ? meta.call(vm)
10 | : meta
11 | }
12 |
13 | const serverMetaMixin = {
14 | created () {
15 | const meta = getMeta(this)
16 | if (meta) {
17 | let metaTags = []
18 |
19 | meta.forEach(item => {
20 | metaTags.push(``)
21 | })
22 |
23 | this.$ssrContext.meta = metaTags.join('\r\n')
24 | }
25 | }
26 | }
27 |
28 | const clientMetaMixin = {
29 | mounted () {
30 | // const title = getMeta(this)
31 | // if (title) {
32 | // document.title = title
33 | // }
34 | }
35 | }
36 |
37 | // 可以通过 `webpack.DefinePlugin` 注入 `VUE_ENV`
38 | export default process.env.VUE_ENV === 'server'
39 | ? serverMetaMixin
40 | : clientMetaMixin
41 |
--------------------------------------------------------------------------------
/src/utils/store.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 全局 store 操作方法
3 | *
4 | * store : sessionStorage 、 localStorage、cookie
5 | * params: key 值
6 | * type 是类型,sessionStorage 、 localStorage、cookie三选一
7 | * day 此参数只在type === cookie 的时候存在
8 | */
9 | function saveWithKey (type, params, val, day = 7) {
10 | if (type === 'localStorage') localStorage.setItem(params, val)
11 | if (type === 'sessionStorage') sessionStorage.setItem(params, val)
12 | }
13 |
14 | function getWithKey (type, params, day = 7) {
15 | if (type === 'localStorage') return localStorage.getItem(params)
16 | if (type === 'sessionStorage') return sessionStorage.getItem(params)
17 | }
18 |
19 | function clearWithKey (type, params) {
20 | if (type === 'localStorage') localStorage.removeItem(params)
21 | if (type === 'sessionStorage') sessionStorage.removeItem(params)
22 | }
23 | export default {
24 | saveWithKey,
25 | getWithKey,
26 | clearWithKey
27 | }
28 |
--------------------------------------------------------------------------------
/src/utils/title.js:
--------------------------------------------------------------------------------
1 | function getTitle (vm) {
2 | const { title } = vm.$options
3 | if (title) {
4 | return typeof title === 'function'
5 | ? title.call(vm)
6 | : title
7 | }
8 | }
9 |
10 | const serverTitleMixin = {
11 | created () {
12 | const title = getTitle(this)
13 | if (title) {
14 | this.$ssrContext.title = `${title}`
15 | }
16 | }
17 | }
18 |
19 | const clientTitleMixin = {
20 | mounted () {
21 | const title = getTitle(this)
22 | if (title) {
23 | document.title = `${title}`
24 | }
25 | }
26 | }
27 |
28 | export default process.env.VUE_ENV === 'server'
29 | ? serverTitleMixin
30 | : clientTitleMixin
31 |
--------------------------------------------------------------------------------
/src/view/index/components/ArticleListMView.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
49 |
--------------------------------------------------------------------------------
/src/view/index/components/ArticleListVIew.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{ item.title }}
7 |
9 | {{item.type}}
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
44 |
--------------------------------------------------------------------------------
/src/view/index/components/ArticleTagView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 热门文章
5 |
6 |
7 |
8 | {{item.title}}
9 |
10 |
11 |
12 |
13 |
29 |
76 |
--------------------------------------------------------------------------------
/src/view/index/components/BannerListView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
30 |
--------------------------------------------------------------------------------
/src/view/index/components/allTagView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 文章分类
5 |
6 |
7 |
8 | {{item}}
9 |
10 |
11 |
12 |
13 |
29 |
67 |
--------------------------------------------------------------------------------
/src/view/index/indexView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
15 |
16 |
17 |
29 |
32 |
33 |
34 |
161 |
222 |
--------------------------------------------------------------------------------
/src/view/notfound/NotFoundView.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
20 |
21 |
43 |
--------------------------------------------------------------------------------
/src/view/user/article/create/IndexView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
195 |
--------------------------------------------------------------------------------
/src/view/user/article/detail/IndexView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{ title }}
6 |
7 |
9 | {{tag}}
10 |
11 |
创建于: {{ time }}
12 |
13 |
14 |
15 |
16 |
17 |
56 |
--------------------------------------------------------------------------------
/src/view/user/article/list/IndexView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
13 |
14 |
18 |
19 |
23 |
24 |
28 |
29 |
33 |
34 | 查看
35 | 编辑
36 |
37 |
38 |
39 |
46 |
47 |
48 |
49 |
50 |
107 |
124 |
--------------------------------------------------------------------------------
/src/view/user/article/tag/IndexView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
14 |
15 |
16 |
17 |
18 | {{ btnText }}
19 |
26 | {{tag.classify}}
27 |
28 |
29 |
30 |
152 |
--------------------------------------------------------------------------------
/src/view/user/center/IndexView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 | 座右铭: 和的vbhvbdfvbsdhvbsj
12 |
13 |
14 | 退出登陆
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/view/user/changepass/IndexView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
39 |
47 |
--------------------------------------------------------------------------------
/src/view/user/index/IndexView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
74 |
83 |
84 |
--------------------------------------------------------------------------------
/src/view/user/login/IndexView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 登陆
24 | 注册
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
113 |
121 |
--------------------------------------------------------------------------------
/src/view/user/register/IndexView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
46 |
54 |
--------------------------------------------------------------------------------
/src/views/admin/components/article.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 | 文章列表
13 |
14 |
15 |
16 |
{{ item.title }}
17 |
{{ item.created_at }}
18 |
{{ item.type }}
19 |
21 |
22 |
23 |
24 |
25 |
26 |
61 |
121 |
--------------------------------------------------------------------------------
/src/views/admin/components/editor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
请开始您的写作
5 |
6 |
12 |
15 |
16 |
17 | {{ item.type }}
18 |
19 |
20 |
21 |
27 |
28 |
29 | 清除
30 | 发布
31 |
32 |
33 |
34 |
165 |
--------------------------------------------------------------------------------
/src/views/admin/components/tags.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
39 |
40 |
41 |
104 |
--------------------------------------------------------------------------------
/src/views/admin/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
54 |
--------------------------------------------------------------------------------
/src/views/front/ArticleDetail.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
70 |
81 |
--------------------------------------------------------------------------------
/src/views/front/ArticleList.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
{{ item.title }}
14 |
{{ item.created_at }}
15 |
17 |
18 |
19 |
20 |
21 |
22 |
64 |
101 |
--------------------------------------------------------------------------------
/src/views/front/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
hbbaly
11 |
前端工程师
12 |
13 |
14 |
15 |
16 |
20 | {{tag.type}}
21 |
22 |
23 |
24 |
25 |
26 |
27 | {{ item.title }}
28 | {{ item.created_at }}
29 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | {{ item.title }}
39 | {{ item.created_at }}
40 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
96 |
--------------------------------------------------------------------------------
/src/views/front/Mine.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
hbbaly
9 |
重庆
10 |
前端工程师
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
39 |
--------------------------------------------------------------------------------
/src/views/front/Tags.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{item.type}}
6 |
7 |
8 |
9 |
38 |
--------------------------------------------------------------------------------
/src/views/front/TimeLine.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {{ item.title }}
17 | hbbaly 提交于 {{ item.created_at }}
18 |
19 |
20 |
21 |
22 |
23 | {{ itm.title }}
24 | hbbaly 提交于 {{ itm.created_at }}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
38 |
39 |
40 |
41 |
161 |
--------------------------------------------------------------------------------
/src/views/front/components/RelateSkill.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
37 |
38 |
--------------------------------------------------------------------------------
/src/views/hello/HelloView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | hello world
5 |
6 |
7 |
18 |
--------------------------------------------------------------------------------
/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hbbaly/vue-blog/b624636e9ca48174e0bb49ef28d87c3e26be8a60/static/.gitkeep
--------------------------------------------------------------------------------
/static/img/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hbbaly/vue-blog/b624636e9ca48174e0bb49ef28d87c3e26be8a60/static/img/1.png
--------------------------------------------------------------------------------
/static/img/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hbbaly/vue-blog/b624636e9ca48174e0bb49ef28d87c3e26be8a60/static/img/2.png
--------------------------------------------------------------------------------
/static/img/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hbbaly/vue-blog/b624636e9ca48174e0bb49ef28d87c3e26be8a60/static/img/3.png
--------------------------------------------------------------------------------
/static/img/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hbbaly/vue-blog/b624636e9ca48174e0bb49ef28d87c3e26be8a60/static/img/4.png
--------------------------------------------------------------------------------
/static/img/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hbbaly/vue-blog/b624636e9ca48174e0bb49ef28d87c3e26be8a60/static/img/5.png
--------------------------------------------------------------------------------
/static/img/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hbbaly/vue-blog/b624636e9ca48174e0bb49ef28d87c3e26be8a60/static/img/6.png
--------------------------------------------------------------------------------
/static/img/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hbbaly/vue-blog/b624636e9ca48174e0bb49ef28d87c3e26be8a60/static/img/7.png
--------------------------------------------------------------------------------
/static/img/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hbbaly/vue-blog/b624636e9ca48174e0bb49ef28d87c3e26be8a60/static/img/8.png
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------