├── .dockerignore
├── .gitignore
├── README.md
├── admin
├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .postcssrc.js
├── .travis.yml
├── LICENSE
├── README.md
├── build
│ ├── build.js
│ ├── check-versions.js
│ ├── logo.png
│ ├── utils.js
│ ├── vue-loader.conf.js
│ ├── webpack.base.conf.js
│ ├── webpack.dev.conf.js
│ └── webpack.prod.conf.js
├── config
│ ├── dev.env.js
│ ├── index.js
│ └── prod.env.js
├── favicon.ico
├── index.html
├── package.json
├── src
│ ├── App.vue
│ ├── api
│ │ ├── article.js
│ │ ├── categories.js
│ │ ├── login.js
│ │ ├── tags.js
│ │ └── user.js
│ ├── assets
│ │ └── defaultAvatar.png
│ ├── components
│ │ ├── Breadcrumb
│ │ │ └── index.vue
│ │ ├── Hamburger
│ │ │ └── index.vue
│ │ ├── Markdown
│ │ │ ├── coverGithubCss.css
│ │ │ ├── index.vue
│ │ │ └── style.css
│ │ └── SvgIcon
│ │ │ └── index.vue
│ ├── icons
│ │ ├── index.js
│ │ ├── svg
│ │ │ ├── chart.svg
│ │ │ ├── edit.svg
│ │ │ ├── example.svg
│ │ │ ├── eye.svg
│ │ │ ├── form.svg
│ │ │ ├── link.svg
│ │ │ ├── list.svg
│ │ │ ├── nested.svg
│ │ │ ├── password.svg
│ │ │ ├── star.svg
│ │ │ ├── table.svg
│ │ │ ├── tree.svg
│ │ │ └── user.svg
│ │ └── svgo.yml
│ ├── main.js
│ ├── permission.js
│ ├── router
│ │ └── index.js
│ ├── store
│ │ ├── index.js
│ │ └── modules
│ │ │ ├── app.js
│ │ │ ├── article.js
│ │ │ ├── tags.js
│ │ │ └── user.js
│ ├── styles
│ │ ├── element-ui.scss
│ │ ├── index.scss
│ │ ├── mixin.scss
│ │ ├── sidebar.scss
│ │ ├── transition.scss
│ │ └── variables.scss
│ ├── utils
│ │ ├── formatTimestamp.js
│ │ ├── index.js
│ │ └── request.js
│ └── views
│ │ ├── article
│ │ ├── components
│ │ │ ├── createAndUpdate.vue
│ │ │ └── editor.vue
│ │ ├── create.vue
│ │ ├── detail.vue
│ │ └── list.vue
│ │ ├── categories
│ │ ├── categories.vue
│ │ └── compoents
│ │ │ └── editCategories.vue
│ │ ├── dashboard
│ │ └── index.vue
│ │ ├── layout
│ │ ├── Layout.vue
│ │ └── components
│ │ │ ├── AppMain.vue
│ │ │ ├── Navbar.vue
│ │ │ ├── Sidebar
│ │ │ ├── Item.vue
│ │ │ ├── Link.vue
│ │ │ ├── SidebarItem.vue
│ │ │ └── index.vue
│ │ │ └── index.js
│ │ ├── login
│ │ └── index.vue
│ │ ├── tags
│ │ ├── components
│ │ │ └── editEdit.vue
│ │ └── tags.vue
│ │ └── user
│ │ └── list.vue
└── static
│ └── .gitkeep
├── docker-compose.yml
├── docker
├── admin
│ ├── Dockerfile
│ └── nginx.conf
├── mongodb
│ ├── initdb.d
│ │ └── initdb.js
│ └── mongod.conf
├── server
│ └── Dockerfile
└── web
│ ├── Dockerfile
│ └── nginx.conf
├── server
├── .autod.conf.js
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .travis.yml
├── README.md
├── app
│ ├── common
│ │ ├── common.js
│ │ └── user.js
│ ├── controller
│ │ ├── article.js
│ │ ├── categories.js
│ │ ├── comment.js
│ │ ├── home.js
│ │ ├── images.js
│ │ ├── tags.js
│ │ └── user.js
│ ├── extend
│ │ └── helper.js
│ ├── middleware
│ │ └── jwt.js
│ ├── model
│ │ ├── article.js
│ │ ├── categories.js
│ │ ├── comment.js
│ │ ├── images.js
│ │ ├── tags.js
│ │ └── user.js
│ ├── router.js
│ └── service
│ │ ├── article.js
│ │ ├── categories.js
│ │ ├── comment.js
│ │ ├── images.js
│ │ ├── tags.js
│ │ └── user.js
├── appveyor.yml
├── config
│ ├── config.default.js
│ ├── config.local.js
│ ├── config.prod.js
│ └── plugin.js
├── package.json
└── test
│ └── app
│ └── controller
│ └── home.test.js
└── web
├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .postcssrc.js
├── README.md
├── build
├── build.js
├── check-versions.js
├── logo.png
├── utils.js
├── vue-loader.conf.js
├── webpack.base.conf.js
├── webpack.dev.conf.js
└── webpack.prod.conf.js
├── config
├── dev.env.js
├── index.js
└── prod.env.js
├── favicon.ico
├── index.html
├── jsconfig.json
├── package.json
├── src
├── App.vue
├── api
│ ├── article.js
│ ├── category.js
│ ├── comment.js
│ ├── github.js
│ ├── tags.js
│ └── user.js
├── assets
│ ├── about.jpeg
│ ├── activation.jpeg
│ ├── active_mail_logo.png
│ ├── bg.png
│ ├── login.jpeg
│ ├── logo.png
│ ├── payment
│ │ ├── alipay.png
│ │ └── weixin.png
│ ├── profile.jpeg
│ └── signup.jpeg
├── components
│ ├── Comment
│ │ ├── comment.vue
│ │ ├── commentatorInfo.vue
│ │ └── index.vue
│ ├── Markdown
│ │ ├── cover.css
│ │ ├── index.vue
│ │ └── style.css
│ ├── NoData
│ │ └── index.vue
│ ├── PaymentCode
│ │ └── index.vue
│ ├── Skeleton
│ │ └── index.vue
│ ├── Spin
│ │ └── index.vue
│ └── index.js
├── index.css
├── layout
│ ├── Footer.vue
│ ├── Header.vue
│ ├── MobileNavMenu.vue
│ ├── PcNavMenu.vue
│ ├── index.vue
│ ├── sider
│ │ ├── archives
│ │ │ └── index.vue
│ │ ├── categories
│ │ │ └── index.vue
│ │ ├── index.vue
│ │ ├── newestArticles
│ │ │ └── index.vue
│ │ ├── profile
│ │ │ └── index.vue
│ │ └── style.scss
│ └── userInfo
│ │ ├── index.vue
│ │ ├── login.vue
│ │ └── signup.vue
├── main.js
├── router
│ └── index.js
├── store
│ ├── index.js
│ └── modules
│ │ ├── archives.js
│ │ ├── category.js
│ │ ├── detail.js
│ │ ├── home.js
│ │ ├── signup.js
│ │ └── user.js
├── utils
│ ├── filters.js
│ ├── formatTimestamp.js
│ ├── formatYearAndDate.js
│ └── request.js
└── views
│ ├── about
│ └── about.vue
│ ├── archives
│ ├── archiveTimeRange.vue
│ └── archives.vue
│ ├── articles
│ ├── components
│ │ └── item.vue
│ ├── detail.vue
│ ├── list.vue
│ └── searchResult.vue
│ ├── categories
│ ├── categories.vue
│ └── categoryItem.vue
│ └── tags
│ ├── components
│ └── shuffle.vue
│ ├── tags.vue
│ └── tagsItem.vue
├── test
├── e2e
│ ├── custom-assertions
│ │ └── elementCount.js
│ ├── nightwatch.conf.js
│ ├── runner.js
│ └── specs
│ │ └── test.js
└── unit
│ ├── .eslintrc
│ ├── index.js
│ ├── karma.conf.js
│ └── specs
│ └── HelloWorld.spec.js
└── theme
├── alert.css
├── aside.css
├── autocomplete.css
├── avatar.css
├── backtop.css
├── badge.css
├── base.css
├── breadcrumb-item.css
├── breadcrumb.css
├── button-group.css
├── button.css
├── calendar.css
├── card.css
├── carousel-item.css
├── carousel.css
├── cascader-panel.css
├── cascader.css
├── checkbox-button.css
├── checkbox-group.css
├── checkbox.css
├── col.css
├── collapse-item.css
├── collapse.css
├── color-picker.css
├── container.css
├── date-picker.css
├── dialog.css
├── display.css
├── divider.css
├── drawer.css
├── dropdown-item.css
├── dropdown-menu.css
├── dropdown.css
├── fonts
├── element-icons.ttf
└── element-icons.woff
├── footer.css
├── form-item.css
├── form.css
├── header.css
├── icon.css
├── image.css
├── index.css
├── infinite-scroll.css
├── infiniteScroll.css
├── input-number.css
├── input.css
├── link.css
├── loading.css
├── main.css
├── menu-item-group.css
├── menu-item.css
├── menu.css
├── message-box.css
├── message.css
├── notification.css
├── option-group.css
├── option.css
├── page-header.css
├── pagination.css
├── popconfirm.css
├── popover.css
├── popper.css
├── progress.css
├── radio-button.css
├── radio-group.css
├── radio.css
├── rate.css
├── reset.css
├── row.css
├── scrollbar.css
├── select-dropdown.css
├── select.css
├── slider.css
├── spinner.css
├── step.css
├── steps.css
├── submenu.css
├── switch.css
├── tab-pane.css
├── table-column.css
├── table.css
├── tabs.css
├── tag.css
├── time-picker.css
├── time-select.css
├── timeline-item.css
├── timeline.css
├── tooltip.css
├── transfer.css
├── tree.css
└── upload.css
/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | node_modules
3 | npm-debug.log
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | yarn-debug.log*
3 | yarn-error.log*
4 | package-lock.json
5 |
6 | # Editor directories and files
7 | .idea
8 | .vscode
9 | *.suo
10 | *.ntvs*
11 | *.njsproj
12 | *.sln
13 |
14 |
15 | logs/
16 | npm-debug.log
17 | yarn-error.log
18 | node_modules/
19 | coverage/
20 | .idea/
21 | run/
22 | .DS_Store
23 | *.sw*
24 | *.un~
25 | config/mongoConfig.js
26 | config/redisConfig.js
27 |
28 |
29 | .DS_Store
30 | .history
31 | node_modules/
32 | npm-debug.log*
33 | yarn-debug.log*
34 | yarn-error.log*
35 | test/unit/coverage/
36 | test/e2e/reports/
37 | selenium-debug.log
38 |
39 | # Editor directories and files
40 | .idea
41 | .vscode
42 | *.suo
43 | *.ntvs*
44 | *.njsproj
45 | *.sln
46 |
47 | dist
48 | .history
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 一键启动:```docker-compose up -d```
--------------------------------------------------------------------------------
/admin/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins":["transform-vue-jsx", "transform-runtime"]
12 | }
13 |
--------------------------------------------------------------------------------
/admin/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 4
8 | end_of_line = lf
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | insert_final_newline = false
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/admin/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | config/*.js
3 | src/assets
4 |
--------------------------------------------------------------------------------
/admin/.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 | 'space-before-function-paren': 0
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/admin/.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 |
--------------------------------------------------------------------------------
/admin/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js: stable
3 | script: npm run test
4 | notifications:
5 | email: false
6 |
--------------------------------------------------------------------------------
/admin/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-present PanJiaChen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/admin/README.md:
--------------------------------------------------------------------------------
1 | # AntVueBlogAdmin
2 |
3 | > Vue.js+Egg.js+Mongodb的前后端分离的个人博客后台管理。
4 |
5 | > 博客地址:[ANT](http://120.77.219.106/#/)
6 |
7 |
8 | ## 主要技术栈
9 | 后台管理:vue.js、vue-router、vuex
10 |
11 | 后端:Egg.js、Mongodb。[AntEggBlogService](https://github.com/antbaobao/AntEggBlogService)
12 | 前端:[AntVueBlogAdmin](https://github.com/antbaobao/AntVueBlogAdmin)
13 |
14 | ### 后台管理
15 | - 发布文章、存为草稿
16 | - 文章管理
17 | - 标签管理
18 | - 分类管理
19 | - 登录验证
20 |
21 |
22 | ## Setup
23 |
24 | 运行环境
25 | - node.js
26 | - mongoDB
27 |
28 | 克隆远程库
29 | ```bash
30 | git clone git@github.com:antbaobao/AntVueBlogAdmin.git
31 | ```
32 | 安装依赖
33 | ```bash
34 | cd AntVueBlogAdmin
35 | npm i
36 | ```
37 | 运行
38 |
39 | ```bash
40 | npm run dev
41 | ```
42 | 部署
43 |
44 | 部署流程可以参考[使用docker部署网站](https://github.com/antbaobao/AntBlog/issues/20)
45 |
46 | ### 效果展示
47 |
48 | 
49 | ### 目录
50 | ```
51 | │ .babelrc babel配置
52 | │ .editorconfig 编辑器配置
53 | │ .eslintignore eslint忽略
54 | │ .eslintrc.js eslintrc配置
55 | │ .gitignore git上传忽略
56 | │ .postcssrc.js
57 | │ Dockerfile docker 配置
58 | │ index.html 打包模板
59 | │ package.json
60 | │ README.md
61 | │
62 | ├─build
63 | ├─src
64 | │ │ main.js 项目入口
65 | │ │ App.vue 根组件
66 | │ │ index.css 全局样式
67 | │ │
68 | │ ├─api api 请求接口
69 | │ │
70 | │ ├─assets 外部引用文件
71 | │ │
72 | │ ├─components vue组件
73 | │ │
74 | │ ├─icons 图标
75 | │ │
76 | │ ├─router 路由
77 | │ │
78 | │ ├─styles 全局样式
79 | │ │
80 | │ ├─ layout 页面公共结构
81 | │ │
82 | │ ├─store vuex文件
83 | │ │
84 | │ ├─utils 工具函数
85 | │ │
86 | │ └─views 页面vue文件
87 | │
88 | ├─test
89 | └─static 静态文件
90 | ```
91 |
92 |
93 | ## [1.0] - 2019-6-20
94 | ### Added
95 | - 完成版本1.0
96 |
97 |
98 |
--------------------------------------------------------------------------------
/admin/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(
23 | stats.toString({
24 | colors: true,
25 | modules: false,
26 | children: false,
27 | chunks: false,
28 | chunkModules: false
29 | }) + '\n\n'
30 | )
31 |
32 | if (stats.hasErrors()) {
33 | console.log(chalk.red(' Build failed with errors.\n'))
34 | process.exit(1)
35 | }
36 |
37 | console.log(chalk.cyan(' Build complete.\n'))
38 | console.log(
39 | chalk.yellow(
40 | ' Tip: built files are meant to be served over an HTTP server.\n' +
41 | " Opening index.html over file:// won't work.\n"
42 | )
43 | )
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/admin/build/check-versions.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const chalk = require('chalk')
3 | const semver = require('semver')
4 | const packageConfig = require('../package.json')
5 | const shell = require('shelljs')
6 |
7 | function exec(cmd) {
8 | return require('child_process')
9 | .execSync(cmd)
10 | .toString()
11 | .trim()
12 | }
13 |
14 | const versionRequirements = [
15 | {
16 | name: 'node',
17 | currentVersion: semver.clean(process.version),
18 | versionRequirement: packageConfig.engines.node
19 | }
20 | ]
21 |
22 | if (shell.which('npm')) {
23 | versionRequirements.push({
24 | name: 'npm',
25 | currentVersion: exec('npm --version'),
26 | versionRequirement: packageConfig.engines.npm
27 | })
28 | }
29 |
30 | module.exports = function() {
31 | const warnings = []
32 |
33 | for (let i = 0; i < versionRequirements.length; i++) {
34 | const mod = versionRequirements[i]
35 |
36 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
37 | warnings.push(
38 | mod.name +
39 | ': ' +
40 | chalk.red(mod.currentVersion) +
41 | ' should be ' +
42 | chalk.green(mod.versionRequirement)
43 | )
44 | }
45 | }
46 |
47 | if (warnings.length) {
48 | console.log('')
49 | console.log(
50 | chalk.yellow(
51 | 'To use this template, you must update following to modules:'
52 | )
53 | )
54 | console.log()
55 |
56 | for (let i = 0; i < warnings.length; i++) {
57 | const warning = warnings[i]
58 | console.log(' ' + warning)
59 | }
60 |
61 | console.log()
62 | process.exit(1)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/admin/build/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/admin/build/logo.png
--------------------------------------------------------------------------------
/admin/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | //You can set the vue-loader configuration by yourself.
5 | }
6 |
--------------------------------------------------------------------------------
/admin/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 | GATEWAY : '"http://127.0.0.1:7001"',
8 | STATIC_DOMAIN : '"http://127.0.0.:7001"'
9 | })
10 |
--------------------------------------------------------------------------------
/admin/config/prod.env.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | NODE_ENV: '"production"',
3 | GATEWAY: '"http://localhost/api"',
4 | STATIC_DOMAIN: '"http://localhost/api"',
5 | };
6 |
--------------------------------------------------------------------------------
/admin/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/admin/favicon.ico
--------------------------------------------------------------------------------
/admin/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Ant Blog Admin
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/admin/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/admin/src/api/article.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function fetchArticle(params) {
4 | return request.get('/articles', params)
5 | }
6 |
7 | export function getArticleDetail(id) {
8 | return request.get(`/articles/${id}`)
9 | }
10 |
11 | export function createArticle(data) {
12 | return request.post('/articles', data)
13 | }
14 |
15 | export function updateArticle(id, data) {
16 | return request.put(`/articles/${id}`, data)
17 | }
18 |
19 | export function deleteArticle(id) {
20 | return request.delete(`/articles/${id}`)
21 | }
22 |
--------------------------------------------------------------------------------
/admin/src/api/categories.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function create(name) {
4 | return request.post('/categories', { name })
5 | }
6 |
7 | export function getCategories() {
8 | return request.get('/categories')
9 | }
10 |
11 | export function deleteCategory(id) {
12 | return request.delete(`/categories/${id}`)
13 | }
14 |
15 | export function update(id, name) {
16 | return request.put(`/categories/${id}`, { name })
17 | }
18 |
--------------------------------------------------------------------------------
/admin/src/api/login.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function login(email, password) {
4 | return request.post('/login', { email, password })
5 | }
6 |
7 | export function getInfo() {
8 | return request.get('/user')
9 | }
10 |
11 | export function logout() {
12 | return request({
13 | url: '/user/logout',
14 | method: 'post'
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/admin/src/api/tags.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function create(name) {
4 | return request.post('/tags', { name })
5 | }
6 |
7 | export function getList() {
8 | return request.get('/tags')
9 | }
10 |
11 | export function deleteTag(id) {
12 | return request.delete(`/tags/${id}`)
13 | }
14 |
15 | export function updateTag(id, name) {
16 | return request.put(`/tags/${id}`, { name })
17 | }
18 |
--------------------------------------------------------------------------------
/admin/src/api/user.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function update(id, name, password) {
4 | return request.put(`/user`, { name, password, id })
5 | }
6 |
7 | export function getUserList() {
8 | return request.get(`/users`)
9 | }
10 |
--------------------------------------------------------------------------------
/admin/src/assets/defaultAvatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/admin/src/assets/defaultAvatar.png
--------------------------------------------------------------------------------
/admin/src/components/Breadcrumb/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ item.meta.title }}
9 | {{item.meta.title}}
10 |
11 |
12 |
13 |
14 |
15 |
59 |
60 |
72 |
--------------------------------------------------------------------------------
/admin/src/components/Hamburger/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
30 |
31 |
32 |
33 |
48 |
49 |
63 |
--------------------------------------------------------------------------------
/admin/src/components/Markdown/coverGithubCss.css:
--------------------------------------------------------------------------------
1 | .markdown-body .highlight pre, .markdown-body pre {
2 | /*background: #272822;*/
3 | }
4 |
--------------------------------------------------------------------------------
/admin/src/components/Markdown/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
43 |
44 |
49 |
--------------------------------------------------------------------------------
/admin/src/components/Markdown/style.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Google Code style (c) Aahan Krish
4 |
5 | */
6 |
7 | .hljs {
8 | display: block;
9 | overflow-x: auto;
10 | padding: 0.5em;
11 | background: white;
12 | color: black;
13 | }
14 |
15 | .hljs-comment,
16 | .hljs-quote {
17 | color: #800;
18 | }
19 |
20 | .hljs-keyword,
21 | .hljs-selector-tag,
22 | .hljs-section,
23 | .hljs-title,
24 | .hljs-name {
25 | color: #008;
26 | }
27 |
28 | .hljs-variable,
29 | .hljs-template-variable {
30 | color: #660;
31 | }
32 |
33 | .hljs-string,
34 | .hljs-selector-attr,
35 | .hljs-selector-pseudo,
36 | .hljs-regexp {
37 | color: #080;
38 | }
39 |
40 | .hljs-literal,
41 | .hljs-symbol,
42 | .hljs-bullet,
43 | .hljs-meta,
44 | .hljs-number,
45 | .hljs-link {
46 | color: #066;
47 | }
48 |
49 | .hljs-title,
50 | .hljs-doctag,
51 | .hljs-type,
52 | .hljs-attr,
53 | .hljs-built_in,
54 | .hljs-builtin-name,
55 | .hljs-params {
56 | color: #606;
57 | }
58 |
59 | .hljs-attribute,
60 | .hljs-subst {
61 | color: #000;
62 | }
63 |
64 | .hljs-formula {
65 | background-color: #eee;
66 | font-style: italic;
67 | }
68 |
69 | .hljs-selector-id,
70 | .hljs-selector-class {
71 | color: #9B703F
72 | }
73 |
74 | .hljs-addition {
75 | background-color: #baeeba;
76 | }
77 |
78 | .hljs-deletion {
79 | background-color: #ffc8bd;
80 | }
81 |
82 | .hljs-doctag,
83 | .hljs-strong {
84 | font-weight: bold;
85 | }
86 |
87 | .hljs-emphasis {
88 | font-style: italic;
89 | }
90 |
--------------------------------------------------------------------------------
/admin/src/components/SvgIcon/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
34 |
35 |
44 |
--------------------------------------------------------------------------------
/admin/src/icons/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import SvgIcon from '@/components/SvgIcon' // svg组件
3 |
4 | // register globally
5 | Vue.component('svg-icon', SvgIcon)
6 |
7 | const requireAll = requireContext => requireContext.keys().map(requireContext)
8 | const req = require.context('./svg', false, /\.svg$/)
9 | requireAll(req)
10 |
--------------------------------------------------------------------------------
/admin/src/icons/svg/chart.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/src/icons/svg/edit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/src/icons/svg/example.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/src/icons/svg/eye.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/src/icons/svg/form.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/src/icons/svg/link.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/src/icons/svg/list.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/src/icons/svg/nested.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/src/icons/svg/password.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/src/icons/svg/star.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/src/icons/svg/table.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/src/icons/svg/tree.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/src/icons/svg/user.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/admin/src/icons/svgo.yml:
--------------------------------------------------------------------------------
1 | # replace default config
2 |
3 | # multipass: true
4 | # full: true
5 |
6 | plugins:
7 |
8 | # - name
9 | #
10 | # or:
11 | # - name: false
12 | # - name: true
13 | #
14 | # or:
15 | # - name:
16 | # param1: 1
17 | # param2: 2
18 |
19 | - removeAttrs:
20 | attrs:
21 | - 'fill'
22 | - 'fill-rule'
23 |
--------------------------------------------------------------------------------
/admin/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 |
3 | import "normalize.css/normalize.css"; // A modern alternative to CSS resets
4 |
5 | import ElementUI from "element-ui";
6 | import "element-ui/lib/theme-chalk/index.css";
7 | import locale from "element-ui/lib/locale/lang/en"; // lang i18n
8 |
9 | import "@/styles/index.scss"; // global css
10 |
11 | import App from "./App";
12 | import router from "./router";
13 | import store from "./store";
14 |
15 | import "@/icons"; // icon
16 | import "@/permission"; // permission control
17 |
18 | Vue.use(ElementUI, { locale });
19 |
20 | Vue.config.productionTip = false;
21 |
22 | // eslint-disable-next-line no-new
23 | new Vue({
24 | el: "#app",
25 | router,
26 | store,
27 | render: (h) => h(App),
28 | });
29 |
--------------------------------------------------------------------------------
/admin/src/permission.js:
--------------------------------------------------------------------------------
1 | import router from "./router";
2 | import store from "./store";
3 | import NProgress from "nprogress"; // Progress 进度条
4 | import "nprogress/nprogress.css"; // Progress 进度条样式
5 | import { Message } from "element-ui";
6 |
7 | const whiteList = ["/login"]; // 不重定向白名单
8 | router.beforeEach((to, from, next) => {
9 | NProgress.start();
10 | if (localStorage.getItem("token")) {
11 | if (to.path === "/login") {
12 | next({ path: "/" });
13 | NProgress.done(); // if current page is dashboard will not trigger afterEach hook, so manually handle it
14 | } else {
15 | if (!store.state.user.role) {
16 | store
17 | .dispatch("user/GetInfo")
18 | .then((res) => {
19 | // 拉取用户信息
20 | if (res.data.code === "error_003") {
21 | // token过期
22 | store.dispatch("user/FedLogOut").then(() => {
23 | Message.error("鉴权失败");
24 | next({ path: "/" });
25 | });
26 | } else {
27 | next();
28 | }
29 | })
30 | .catch((err) => {
31 | store.dispatch("user/FedLogOut").then(() => {
32 | Message.error(err || "鉴权失败");
33 | next({ path: "/" });
34 | });
35 | });
36 | } else {
37 | next();
38 | }
39 | }
40 | } else {
41 | if (whiteList.indexOf(to.path) !== -1) {
42 | next();
43 | } else {
44 | next(`/login?redirect=${to.path}`); // 否则全部重定向到登录页
45 | NProgress.done();
46 | }
47 | }
48 | });
49 |
50 | router.afterEach(() => {
51 | NProgress.done(); // 结束Progress
52 | });
53 |
--------------------------------------------------------------------------------
/admin/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import app from './modules/app'
4 | import user from './modules/user'
5 | import article from './modules/article'
6 |
7 | Vue.use(Vuex)
8 |
9 | const store = new Vuex.Store({
10 | modules: {
11 | app,
12 | user,
13 | article
14 | }
15 | })
16 |
17 | export default store
18 |
--------------------------------------------------------------------------------
/admin/src/store/modules/app.js:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie'
2 |
3 | const app = {
4 | namespaced: true,
5 | state: {
6 | sidebar: {
7 | opened: !+Cookies.get('sidebarStatus'),
8 | withoutAnimation: false
9 | },
10 | device: 'desktop'
11 | },
12 | mutations: {
13 | TOGGLE_SIDEBAR: state => {
14 | if (state.sidebar.opened) {
15 | Cookies.set('sidebarStatus', 1)
16 | } else {
17 | Cookies.set('sidebarStatus', 0)
18 | }
19 | state.sidebar.opened = !state.sidebar.opened
20 | state.sidebar.withoutAnimation = false
21 | },
22 | CLOSE_SIDEBAR: (state, withoutAnimation) => {
23 | Cookies.set('sidebarStatus', 1)
24 | state.sidebar.opened = false
25 | state.sidebar.withoutAnimation = withoutAnimation
26 | },
27 | TOGGLE_DEVICE: (state, device) => {
28 | state.device = device
29 | }
30 | },
31 | actions: {
32 | ToggleSideBar: ({ commit }) => {
33 | commit('TOGGLE_SIDEBAR')
34 | },
35 | CloseSideBar({ commit }, { withoutAnimation }) {
36 | commit('CLOSE_SIDEBAR', withoutAnimation)
37 | },
38 | ToggleDevice({ commit }, device) {
39 | commit('TOGGLE_DEVICE', device)
40 | }
41 | }
42 | }
43 |
44 | export default app
45 |
--------------------------------------------------------------------------------
/admin/src/store/modules/article.js:
--------------------------------------------------------------------------------
1 | import { fetchArticle } from '@/api/article'
2 |
3 | const article = {
4 | namespaced: true,
5 | state: {
6 | articleList: null,
7 | count: 0
8 | },
9 |
10 | mutations: {
11 | SET_ARTICLE_LIST: (state, articleList) => {
12 | state.articleList = articleList
13 | },
14 | SET_COUNT: (state, count) => {
15 | state.count = count
16 | }
17 | },
18 | actions: {
19 | // 获取用户信息
20 | FetchArticle({ commit, state }, condition) {
21 | return new Promise((resolve, reject) => {
22 | fetchArticle(condition)
23 | .then(response => {
24 | const data = response.data
25 | if (data.code) {
26 | reject(response)
27 | } else {
28 | commit('SET_ARTICLE_LIST', data.data.article)
29 | commit('SET_COUNT', data.data.count)
30 | resolve(response)
31 | }
32 | })
33 | .catch(error => {
34 | reject(error)
35 | })
36 | })
37 | }
38 | }
39 | }
40 |
41 | export default article
42 |
--------------------------------------------------------------------------------
/admin/src/store/modules/tags.js:
--------------------------------------------------------------------------------
1 | const tags = {
2 | namespaced: true
3 | }
4 |
5 | export default tags
6 |
--------------------------------------------------------------------------------
/admin/src/store/modules/user.js:
--------------------------------------------------------------------------------
1 | import { login, getInfo } from '@/api/login'
2 |
3 | const user = {
4 | namespaced: true,
5 | state: {
6 | name: '',
7 | avatar: '',
8 | role: '' // 普通权限都为0,目前角色只有admin 1
9 | },
10 | mutations: {
11 | SET_USER_INFO: (state, info) => {
12 | state.name = info.name
13 | state.avatar = info.avatar
14 | state.role = info.role
15 | },
16 | LOGOUT: (state) => {
17 | localStorage.removeItem('token')
18 | location.reload()
19 | }
20 | },
21 | actions: {
22 | // 登录
23 | Login({ commit }, userInfo) {
24 | const email = userInfo.email.trim()
25 | return new Promise((resolve, reject) => {
26 | login(email, userInfo.password)
27 | .then(response => {
28 | const data = response.data
29 | if (!data.code) {
30 | localStorage.setItem('token', data.data.token)
31 | }
32 | resolve(response)
33 | })
34 | .catch(error => {
35 | reject(error)
36 | })
37 | })
38 | },
39 |
40 | // 获取用户信息
41 | GetInfo({ commit, state }) {
42 | return new Promise((resolve, reject) => {
43 | getInfo()
44 | .then(response => {
45 | const data = response.data
46 | commit('SET_USER_INFO', data)
47 | resolve(response)
48 | })
49 | .catch(error => {
50 | reject(error)
51 | })
52 | })
53 | }
54 | }
55 | }
56 |
57 | export default user
58 |
--------------------------------------------------------------------------------
/admin/src/styles/element-ui.scss:
--------------------------------------------------------------------------------
1 | //to reset element-ui default css
2 | .el-upload {
3 | input[type="file"] {
4 | display: none !important;
5 | }
6 | }
7 |
8 | .el-upload__input {
9 | display: none;
10 | }
11 |
12 | //暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
13 | .el-dialog {
14 | transform: none;
15 | left: 0;
16 | position: relative;
17 | margin: 0 auto;
18 | }
19 |
20 | //element ui upload
21 | .upload-container {
22 | .el-upload {
23 | width: 100%;
24 | .el-upload-dragger {
25 | width: 100%;
26 | height: 200px;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/admin/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import './variables.scss';
2 | @import './mixin.scss';
3 | @import './transition.scss';
4 | @import './element-ui.scss';
5 | @import './sidebar.scss';
6 |
7 | body {
8 | height: 100%;
9 | -moz-osx-font-smoothing: grayscale;
10 | -webkit-font-smoothing: antialiased;
11 | text-rendering: optimizeLegibility;
12 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
13 | }
14 |
15 | label {
16 | font-weight: 700;
17 | }
18 |
19 | html {
20 | height: 100%;
21 | box-sizing: border-box;
22 | }
23 |
24 | #app{
25 | height: 100%;
26 | }
27 |
28 | *,
29 | *:before,
30 | *:after {
31 | box-sizing: inherit;
32 | }
33 |
34 | a,
35 | a:focus,
36 | a:hover {
37 | cursor: pointer;
38 | color: inherit;
39 | outline: none;
40 | text-decoration: none;
41 | }
42 |
43 | div:focus{
44 | outline: none;
45 | }
46 |
47 | a:focus,
48 | a:active {
49 | outline: none;
50 | }
51 |
52 | a,
53 | a:focus,
54 | a:hover {
55 | cursor: pointer;
56 | color: inherit;
57 | text-decoration: none;
58 | }
59 |
60 | .clearfix {
61 | &:after {
62 | visibility: hidden;
63 | display: block;
64 | font-size: 0;
65 | content: " ";
66 | clear: both;
67 | height: 0;
68 | }
69 | }
70 |
71 | //main-container全局样式
72 | .app-main{
73 | min-height: 100%
74 | }
75 |
76 | .app-container {
77 | padding: 20px;
78 | }
79 |
--------------------------------------------------------------------------------
/admin/src/styles/mixin.scss:
--------------------------------------------------------------------------------
1 | @mixin clearfix {
2 | &:after {
3 | content: "";
4 | display: table;
5 | clear: both;
6 | }
7 | }
8 |
9 | @mixin scrollBar {
10 | &::-webkit-scrollbar-track-piece {
11 | background: #d3dce6;
12 | }
13 | &::-webkit-scrollbar {
14 | width: 6px;
15 | }
16 | &::-webkit-scrollbar-thumb {
17 | background: #99a9bf;
18 | border-radius: 20px;
19 | }
20 | }
21 |
22 | @mixin relative {
23 | position: relative;
24 | width: 100%;
25 | height: 100%;
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/admin/src/styles/transition.scss:
--------------------------------------------------------------------------------
1 | //globl transition css
2 |
3 | /*fade*/
4 | .fade-enter-active,
5 | .fade-leave-active {
6 | transition: opacity 0.28s;
7 | }
8 |
9 | .fade-enter,
10 | .fade-leave-active {
11 | opacity: 0;
12 | }
13 |
14 | /*fade-transform*/
15 | .fade-transform-leave-active,
16 | .fade-transform-enter-active {
17 | transition: all .5s;
18 | }
19 | .fade-transform-enter {
20 | opacity: 0;
21 | transform: translateX(-30px);
22 | }
23 | .fade-transform-leave-to {
24 | opacity: 0;
25 | transform: translateX(30px);
26 | }
27 |
28 | /*fade*/
29 | .breadcrumb-enter-active,
30 | .breadcrumb-leave-active {
31 | transition: all .5s;
32 | }
33 |
34 | .breadcrumb-enter,
35 | .breadcrumb-leave-active {
36 | opacity: 0;
37 | transform: translateX(20px);
38 | }
39 |
40 | .breadcrumb-move {
41 | transition: all .5s;
42 | }
43 |
44 | .breadcrumb-leave-active {
45 | position: absolute;
46 | }
47 |
--------------------------------------------------------------------------------
/admin/src/styles/variables.scss:
--------------------------------------------------------------------------------
1 | //sidebar
2 | $menuBg:#304156;
3 | $subMenuBg:#1f2d3d;
4 | $menuHover:#001528;
5 |
--------------------------------------------------------------------------------
/admin/src/utils/formatTimestamp.js:
--------------------------------------------------------------------------------
1 | const add0 = (m) => {
2 | return m < 10 ? '0' + m : m
3 | }
4 | const format = (timestamps) => {
5 | let time = new Date(parseInt(timestamps) * 1000)
6 | let y = time.getFullYear()
7 | let m = time.getMonth() + 1
8 | let d = time.getDate()
9 | let h = time.getHours()
10 | let mm = time.getMinutes()
11 | let s = time.getSeconds()
12 | return y + '/' + add0(m) + '/' + add0(d) + ' ' + add0(h) + ':' + add0(mm) + ':' + add0(s)
13 | }
14 | export default format
15 |
--------------------------------------------------------------------------------
/admin/src/utils/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jiachenpan on 16/11/18.
3 | */
4 |
5 | export function parseTime(time, cFormat) {
6 | if (arguments.length === 0) {
7 | return null
8 | }
9 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
10 | let date
11 | if (typeof time === 'object') {
12 | date = time
13 | } else {
14 | if (('' + time).length === 10) time = parseInt(time) * 1000
15 | date = new Date(time)
16 | }
17 | const formatObj = {
18 | y: date.getFullYear(),
19 | m: date.getMonth() + 1,
20 | d: date.getDate(),
21 | h: date.getHours(),
22 | i: date.getMinutes(),
23 | s: date.getSeconds(),
24 | a: date.getDay()
25 | }
26 | const timeStr = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
27 | let value = formatObj[key]
28 | // Note: getDay() returns 0 on Sunday
29 | if (key === 'a') {
30 | return ['日', '一', '二', '三', '四', '五', '六'][value]
31 | }
32 | if (result.length > 0 && value < 10) {
33 | value = '0' + value
34 | }
35 | return value || 0
36 | })
37 | return timeStr
38 | }
39 |
40 | export function formatTime(time, option) {
41 | time = +time * 1000
42 | const d = new Date(time)
43 | const now = Date.now()
44 |
45 | const diff = (now - d) / 1000
46 |
47 | if (diff < 30) {
48 | return '刚刚'
49 | } else if (diff < 3600) {
50 | // less 1 hour
51 | return Math.ceil(diff / 60) + '分钟前'
52 | } else if (diff < 3600 * 24) {
53 | return Math.ceil(diff / 3600) + '小时前'
54 | } else if (diff < 3600 * 24 * 2) {
55 | return '1天前'
56 | }
57 | if (option) {
58 | return parseTime(time, option)
59 | } else {
60 | return (
61 | d.getMonth() +
62 | 1 +
63 | '月' +
64 | d.getDate() +
65 | '日' +
66 | d.getHours() +
67 | '时' +
68 | d.getMinutes() +
69 | '分'
70 | )
71 | }
72 | }
73 |
74 | export function isExternal(path) {
75 | return /^(https?:|mailto:|tel:)/.test(path)
76 | }
77 |
--------------------------------------------------------------------------------
/admin/src/utils/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import queryString from 'query-string'
3 |
4 | axios.defaults.timeout = 5000
5 |
6 | const HTTP_HEADER = {
7 | 'Content-Type': 'application/json'
8 | }
9 |
10 | class Request {
11 | async get(url, params) {
12 | url = params ? `${url}?${queryString.stringify(params)}` : `${url}`
13 |
14 | let config = {
15 | url: `${process.env.GATEWAY}${url}`,
16 | method: 'get',
17 | headers: { ...HTTP_HEADER }
18 | }
19 | if (localStorage.getItem('token')) {
20 | config.headers['authorization'] = `Bearer ${localStorage.getItem(
21 | 'token'
22 | )}`
23 | }
24 | const result = await axios(config)
25 | return result
26 | }
27 |
28 | async post(url, params) {
29 | let config = {
30 | method: 'post',
31 | url: process.env.GATEWAY + url,
32 | headers: HTTP_HEADER,
33 | data: JSON.stringify(params)
34 | }
35 |
36 | if (localStorage.getItem('token')) {
37 | config.headers['authorization'] = `Bearer ${localStorage.getItem(
38 | 'token'
39 | )}`
40 | }
41 |
42 | const result = await axios(config)
43 | return result
44 | }
45 |
46 | async put(url, params) {
47 | let config = {
48 | method: 'put',
49 | url: process.env.GATEWAY + url,
50 | headers: HTTP_HEADER,
51 | data: JSON.stringify(params)
52 | }
53 |
54 | if (localStorage.getItem('token')) {
55 | config.headers['authorization'] = `Bearer ${localStorage.getItem(
56 | 'token'
57 | )}`
58 | }
59 |
60 | const result = await axios(config)
61 | return result
62 | }
63 |
64 | async delete(url, params) {
65 | let config = {
66 | method: 'delete',
67 | url: process.env.GATEWAY + url,
68 | headers: HTTP_HEADER,
69 | data: JSON.stringify(params)
70 | }
71 |
72 | if (localStorage.getItem('token')) {
73 | config.headers['authorization'] = `Bearer ${localStorage.getItem(
74 | 'token'
75 | )}`
76 | }
77 |
78 | const result = await axios(config)
79 | return result
80 | }
81 | }
82 |
83 | const _ = new Request()
84 | export default _
85 |
--------------------------------------------------------------------------------
/admin/src/views/article/components/editor.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
31 |
--------------------------------------------------------------------------------
/admin/src/views/article/create.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
14 |
--------------------------------------------------------------------------------
/admin/src/views/categories/compoents/editCategories.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
15 |
16 |
17 |
18 |
41 |
42 |
44 |
--------------------------------------------------------------------------------
/admin/src/views/dashboard/index.vue:
--------------------------------------------------------------------------------
1 |
2 | Welcome
3 |
4 |
11 |
--------------------------------------------------------------------------------
/admin/src/views/layout/Layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
50 |
51 |
75 |
--------------------------------------------------------------------------------
/admin/src/views/layout/components/AppMain.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
14 |
15 |
22 |
--------------------------------------------------------------------------------
/admin/src/views/layout/components/Sidebar/Item.vue:
--------------------------------------------------------------------------------
1 |
30 |
--------------------------------------------------------------------------------
/admin/src/views/layout/components/Sidebar/Link.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
39 |
--------------------------------------------------------------------------------
/admin/src/views/layout/components/Sidebar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
18 |
19 |
20 |
21 |
22 |
42 |
--------------------------------------------------------------------------------
/admin/src/views/layout/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Navbar } from './Navbar'
2 | export { default as Sidebar } from './Sidebar'
3 | export { default as AppMain } from './AppMain'
4 |
--------------------------------------------------------------------------------
/admin/src/views/tags/components/editEdit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
38 |
39 |
41 |
--------------------------------------------------------------------------------
/admin/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/admin/static/.gitkeep
--------------------------------------------------------------------------------
/docker/admin/Dockerfile:
--------------------------------------------------------------------------------
1 | # node镜像
2 | FROM node:13.11.0 as build-stage
3 | RUN echo "-------------------- admin环境配置 --------------------"
4 | # 指定接下来的工作路径为/app - 类似于cd命令
5 | WORKDIR /app
6 | # 拷贝前端项目到app目录下
7 | COPY ./admin/package*.json ./
8 | # 安装依赖
9 | RUN npm install
10 | COPY ./admin ./
11 | # 打包 - 目的:丢到nginx下跑
12 | RUN npm run build
13 | # ======================== 上:npm打包 下:nginx运行 ========================
14 | # nginx镜像
15 | FROM nginx:1.15.3-alpine as production-stage
16 | # 移除nginx容器的default.conf文件、nginx配置文件
17 | RUN rm /etc/nginx/conf.d/default.conf
18 | RUN rm /etc/nginx/nginx.conf
19 | # 把主机的nginx.conf文件复制到nginx容器的/etc/nginx文件夹下
20 | COPY ./docker/admin/nginx.conf /etc/nginx/
21 | # 拷贝前端vue项目打包后生成的文件到nginx下运行
22 | COPY --from=build-stage /app/dist /usr/share/nginx/html
23 | # 暴露8000端口
24 | EXPOSE 3000
25 | # 注:CMD不同于RUN,CMD用于指定在容器启动时所要执行的命令,而RUN用于指定镜像构建时所要执行的命令。
26 | # RUN指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指定--no-cache参数,如:docker build --no-cache
27 | # 使用daemon off的方式将nginx运行在前台保证镜像不至于退出
28 | CMD ["nginx", "-g", "daemon off;"]
--------------------------------------------------------------------------------
/docker/admin/nginx.conf:
--------------------------------------------------------------------------------
1 | user nginx;
2 | worker_processes 1;
3 |
4 | error_log /var/log/nginx/error.log warn;
5 | pid /var/run/nginx.pid;
6 |
7 | events {
8 | worker_connections 1024;
9 | }
10 |
11 | http {
12 | include /etc/nginx/mime.types;
13 | default_type application/octet-stream;
14 |
15 | log_format main '$remote_addr - $remote_user [$time_local] "$request" '
16 | '$status $body_bytes_sent "$http_referer" '
17 | '"$http_user_agent" "$http_x_forwarded_for"';
18 |
19 | access_log /var/log/nginx/access.log main;
20 |
21 | sendfile on;
22 |
23 | keepalive_timeout 65;
24 |
25 | # include /etc/nginx/conf.d/*.conf;
26 | server {
27 | listen 3000;
28 | charset utf-8;
29 | server_name localhost;# 服务器地址或绑定域名
30 | # start ---------------------------------------------------------------------------------------------
31 |
32 | location / {
33 | root /usr/share/nginx/html;
34 | try_files $uri $uri/ /index.html;
35 | }
36 | # end ---------------------------------------------------------------------------------------------
37 | error_page 500 502 503 504 /50x.html;
38 | location = /50x.html {
39 | root /usr/share/nginx/html;
40 | }
41 | }
42 | gzip on;
43 | gzip_min_length 1k;
44 | gzip_buffers 4 16k;
45 | gzip_http_version 1.1;
46 | gzip_comp_level 9;
47 | gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/javascript application/json;
48 | gzip_disable "MSIE [1-6]\.";
49 | gzip_vary on;
50 | }
--------------------------------------------------------------------------------
/docker/mongodb/initdb.d/initdb.js:
--------------------------------------------------------------------------------
1 | db = db.getSiblingDB("admin");
2 | db.auth("root", "root");
3 | db = db.getSiblingDB("ant_blog");
4 | db.createUser({
5 | user: "ant",
6 | pwd: "wl123456",
7 | roles: [
8 | {
9 | role: "readWrite",
10 | db: "ant_blog",
11 | },
12 | ],
13 | });
14 |
--------------------------------------------------------------------------------
/docker/mongodb/mongod.conf:
--------------------------------------------------------------------------------
1 | # mongod.conf
2 |
3 | # for documentation of all options, see:
4 | # http://docs.mongodb.org/manual/reference/configuration-options/
5 |
6 | # Where and how to store data.
7 | storage:
8 | dbPath: /data/db
9 | journal:
10 | enabled: true
11 | # engine:
12 | # mmapv1:
13 | # wiredTiger:
14 |
15 | # where to write logging data.
16 | systemLog:
17 | destination: file
18 | logAppend: true
19 | path: /var/log/mongodb/mongod.log
20 |
21 | # network interfaces
22 | net:
23 | port: 27017
24 | bindIp: 127.0.0.1
25 |
26 |
27 | # how the process runs
28 | processManagement:
29 | timeZoneInfo: /usr/share/zoneinfo
30 |
31 | security:
32 | authorization: enabled
33 |
34 | #operationProfiling:
35 |
36 | #replication:
37 |
38 | #sharding:
39 |
40 | ## Enterprise-Only Options:
41 |
42 | #auditLog:
43 |
44 | #snmp:
--------------------------------------------------------------------------------
/docker/server/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:10-alpine
2 |
3 | RUN mkdir -p /app
4 |
5 | WORKDIR /app
6 |
7 | # add npm package
8 | COPY ./server/package.json ./package.json
9 |
10 | RUN npm i --registry=https://registry.npm.taobao.org
11 |
12 | # copy code
13 | COPY ./server ./
14 |
15 | EXPOSE 7001
16 |
17 | CMD npm start
--------------------------------------------------------------------------------
/docker/web/Dockerfile:
--------------------------------------------------------------------------------
1 | # node镜像
2 | FROM node:latest as build-stage
3 | RUN echo "-------------------- web环境配置 --------------------"
4 | # 指定接下来的工作路径为/app - 类似于cd命令
5 | WORKDIR /app
6 | # 拷贝前端项目到app目录下
7 | COPY ./web/package*.json ./
8 | # 安装依赖
9 | RUN npm install
10 | COPY ./web ./
11 | # 打包 - 目的:丢到nginx下跑
12 | RUN npm run build
13 | # ======================== 上:npm打包 下:nginx运行 ========================
14 | # nginx镜像
15 | FROM nginx:1.15.3-alpine as production-stage
16 | # 移除nginx容器的default.conf文件、nginx配置文件
17 | RUN rm /etc/nginx/conf.d/default.conf
18 | RUN rm /etc/nginx/nginx.conf
19 | # 把主机的nginx.conf文件复制到nginx容器的/etc/nginx文件夹下
20 | COPY ./docker/web/nginx.conf /etc/nginx/
21 | # 拷贝前端vue项目打包后生成的文件到nginx下运行
22 | COPY --from=build-stage /app/dist /usr/share/nginx/html
23 | # 暴露80端口
24 | EXPOSE 80
25 | # 注:CMD不同于RUN,CMD用于指定在容器启动时所要执行的命令,而RUN用于指定镜像构建时所要执行的命令。
26 | # RUN指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指定--no-cache参数,如:docker build --no-cache
27 | # 使用daemon off的方式将nginx运行在前台保证镜像不至于退出
28 | CMD ["nginx", "-g", "daemon off;"]
--------------------------------------------------------------------------------
/docker/web/nginx.conf:
--------------------------------------------------------------------------------
1 | user nginx;
2 | worker_processes 1;
3 |
4 | error_log /var/log/nginx/error.log warn;
5 | pid /var/run/nginx.pid;
6 |
7 | events {
8 | worker_connections 1024;
9 | }
10 |
11 | http {
12 | include /etc/nginx/mime.types;
13 | default_type application/octet-stream;
14 |
15 | log_format main '$remote_addr - $remote_user [$time_local] "$request" '
16 | '$status $body_bytes_sent "$http_referer" '
17 | '"$http_user_agent" "$http_x_forwarded_for"';
18 |
19 | access_log /var/log/nginx/access.log main;
20 |
21 | sendfile on;
22 |
23 | keepalive_timeout 65;
24 |
25 | # include /etc/nginx/conf.d/*.conf;
26 |
27 | server {
28 | listen 80;
29 | server_name localhost;
30 |
31 | #charset koi8-r;
32 | access_log /var/log/nginx/host.access.log main;
33 | error_log /var/log/nginx/error.log error;
34 |
35 | location / {
36 | root /usr/share/nginx/html;
37 | index index.html index.htm;
38 | try_files $uri $uri/ /index.html last;
39 | }
40 |
41 | location ^~/api/ {
42 | proxy_set_header Host $host;
43 | proxy_set_header X-Real-IP $remote_addr;
44 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
45 | proxy_set_header X-NginX-Proxy true;
46 | proxy_pass http://server:7001/;
47 | }
48 |
49 | location ^~/admin/ {
50 | proxy_set_header Host $host;
51 | proxy_set_header X-Real-IP $remote_addr;
52 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
53 | proxy_set_header X-NginX-Proxy true;
54 | proxy_pass http://admin:3000/;
55 | }
56 |
57 | #error_page 404 /404.html;
58 |
59 | # redirect server error pages to the static page /50x.html
60 | #
61 | error_page 500 502 503 504 /50x.html;
62 | location = /50x.html {
63 | root /usr/share/nginx/html;
64 | }
65 | }
66 | gzip on;
67 | gzip_min_length 1k;
68 | gzip_buffers 4 16k;
69 | gzip_http_version 1.1;
70 | gzip_comp_level 9;
71 | gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/javascript application/json;
72 | gzip_disable "MSIE [1-6]\.";
73 | gzip_vary on;
74 | }
--------------------------------------------------------------------------------
/server/.autod.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | write: true,
5 | prefix: '^',
6 | plugin: 'autod-egg',
7 | test: [
8 | 'test',
9 | 'benchmark',
10 | ],
11 | dep: [
12 | 'egg',
13 | 'egg-scripts',
14 | ],
15 | devdep: [
16 | 'egg-ci',
17 | 'egg-bin',
18 | 'egg-mock',
19 | 'autod',
20 | 'autod-egg',
21 | 'eslint',
22 | 'eslint-config-egg',
23 | 'webstorm-disable-index',
24 | ],
25 | exclude: [
26 | './test/fixtures',
27 | './dist',
28 | ],
29 | };
30 |
31 |
--------------------------------------------------------------------------------
/server/.eslintignore:
--------------------------------------------------------------------------------
1 | coverage
2 |
--------------------------------------------------------------------------------
/server/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: "eslint-config-egg",
3 | // add your custom rules here
4 | rules: {
5 | // allow async-await
6 | 'generator-star-spacing': 'off',
7 | // allow debugger during development
8 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
9 | 'space-before-function-paren': 0,
10 | 'array-bracket-spacing': 0,
11 | 'indent': ['error', 2]
12 | },
13 | "parser": "babel-eslint"
14 | }
15 |
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | logs/
2 | npm-debug.log
3 | yarn-error.log
4 | node_modules/
5 | package-lock.json
6 | yarn.lock
7 | coverage/
8 | .idea/
9 | run/
10 | .DS_Store
11 | *.sw*
12 | *.un~
13 | /config/mongoConfig.js
14 | /config/redisConfig.js
15 |
--------------------------------------------------------------------------------
/server/.travis.yml:
--------------------------------------------------------------------------------
1 |
2 | language: node_js
3 | node_js:
4 | - '8'
5 | before_install:
6 | - npm i npminstall -g
7 | install:
8 | - npminstall
9 | script:
10 | - npm run ci
11 | after_script:
12 | - npminstall codecov && codecov
13 |
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
1 | # AntEggBlogService
2 |
3 | > Vue.js+Egg.js+Mongodb的前后端分离的个人博客。
4 |
5 | > 博客地址:[ANT](http://120.77.219.106/#/)
6 |
7 | ## 主要技术栈
8 | * egg、mongoose、jwt鉴权、redis
9 |
10 | ## QuickStart
11 |
12 | 克隆远程库
13 | ```bash
14 | git clone git@github.com:antbaobao/AntEggBlogService.git
15 | ```
16 | 安装依赖
17 | ```bash
18 | cd AntEggBlogService
19 | npm i
20 | ```
21 | 运行
22 |
23 | 1. 安装redis并启动
24 | 关于redis的安装与运行可以参考[这里](https://github.com/antbaobao/AntBlog/issues/42)
25 | 2. 安装mongodb并启动
26 | 3. 开发
27 | ```bash
28 | npm run dev
29 | ```
30 | 访问http://localhost:7001/
31 |
32 | 部署
33 |
34 | ```bash
35 | sh DokcerBash.sh
36 | ```
37 |
38 |
39 | ## [1.0] - 2019-6-20
40 | ### Added
41 | - 完成版本1.0
42 |
--------------------------------------------------------------------------------
/server/app/common/common.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports.error_001 = [
3 | 'error_001',
4 | 'something error',
5 | ];
6 |
7 | module.exports.error_002 = [
8 | 'error_002',
9 | 'invalid parameter',
10 | ];
11 |
12 | module.exports.error_003 = [
13 | 'error_003',
14 | 'invalid signature',
15 | ];
16 |
17 | module.exports.error_004 = [
18 | 'error_004',
19 | 'no permission',
20 | ];
21 |
22 | module.exports.error_005 = [
23 | 'error_005',
24 | 'please login',
25 | ];
26 |
27 |
--------------------------------------------------------------------------------
/server/app/common/user.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports.user_001 = [
3 | 'user_001',
4 | 'user not exist',
5 | ];
6 |
7 | module.exports.user_002 = [
8 | 'user_002',
9 | 'user existed',
10 | ];
11 |
12 | module.exports.user_003 = [
13 | 'user_003',
14 | 'invalid auth code',
15 | ];
16 |
17 | module.exports.user_004 = [
18 | 'user_004',
19 | '此邮箱已经激活了,不要重复激活',
20 | ];
21 |
22 | module.exports.user_005 = [
23 | 'user_005',
24 | '激活邮箱有误',
25 | ];
26 |
27 | module.exports.user_006 = [
28 | 'user_006',
29 | 'code失效,请重新发送邮件激活',
30 | ];
31 |
32 | module.exports.user_007 = [
33 | 'user_007',
34 | '邮件已注册',
35 | ];
36 |
37 |
38 | module.exports.user_008 = [
39 | 'user_008',
40 | '邮箱未注册',
41 | ];
42 |
43 | module.exports.user_009 = [
44 | 'user_009',
45 | '邮箱未激活,请激活邮箱',
46 | ];
47 |
48 | module.exports.user_010 = [
49 | 'user_010',
50 | '邮箱或密码错误',
51 | ];
52 |
53 |
--------------------------------------------------------------------------------
/server/app/controller/comment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const struct = require('superstruct').struct;
3 | const Controller = require('egg').Controller;
4 | const { error_001, error_002 } = require('../common/common');
5 |
6 | class Comment extends Controller {
7 | // post comment
8 | async create() {
9 | const { ctx, service } = this;
10 | const validator = struct({
11 | article_id: 'string',
12 | commentator: 'string',
13 | content: 'string',
14 | reply_to_comment_id: 'string?',
15 | reply_to_user_id: 'string?',
16 | });
17 | const params = { ...ctx.request.body, commentator: ctx.id };
18 | try {
19 | validator(params);
20 | } catch (err) {
21 | this.logger(err);
22 | return ctx.helper.error(ctx, error_002[0], error_002[1]);
23 | }
24 |
25 | try {
26 | await service.comment.create(params);
27 | return ctx.helper.success(ctx);
28 | } catch (err) {
29 | return ctx.helper.error(ctx, error_001[0], error_001[1]);
30 | }
31 | }
32 |
33 | async getCommentByArticleId() {
34 | const { ctx, service } = this;
35 | const validator = struct({
36 | article_id: 'string',
37 | });
38 |
39 | try {
40 | validator(ctx.params);
41 | } catch (err) {
42 | return ctx.helper.error(ctx, error_002[0], error_002[1]);
43 | }
44 |
45 | try {
46 | const res = await service.comment.find(ctx.params);
47 | const subComments = [];
48 | for (const val of res) {
49 | if (val.reply_to_comment_id) {
50 | const cur = res.find(item => {
51 | return JSON.stringify(item._id) === JSON.stringify(val.reply_to_comment_id);
52 | });
53 | cur.sub_comments = cur.sub_comments || [];
54 | const user = await service.user.findById(val.reply_to_user_id);
55 | val.reply_to_user = user;
56 | cur.sub_comments.push(val);
57 | subComments.push(val);
58 | }
59 | }
60 | for (const val of subComments) {
61 | const index = res.findIndex(item => {
62 | return JSON.stringify(item._id) === JSON.stringify(val._id);
63 | });
64 | res.splice(index, 1);
65 | }
66 | return ctx.helper.success(ctx, res);
67 | } catch (err) {
68 | return ctx.helper.error(ctx, error_001[0], error_001[1]);
69 | }
70 | }
71 | }
72 |
73 | module.exports = Comment;
74 |
75 |
--------------------------------------------------------------------------------
/server/app/controller/home.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Controller = require('egg').Controller;
4 |
5 | class HomeController extends Controller {
6 | async index() {
7 | this.ctx.body = 'hi, egg';
8 | }
9 | }
10 |
11 | module.exports = HomeController;
12 |
--------------------------------------------------------------------------------
/server/app/controller/images.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const Controller = require('egg').Controller;
3 | const fs = require('fs');
4 | const path = require('path');
5 | const pump = require('mz-modules/pump');
6 | const { error_001 } = require('../common/common');
7 | class ImagesController extends Controller {
8 | async upload() {
9 | const { ctx, service } = this;
10 | const parts = ctx.multipart({ autoFields: true });
11 | const files = [];
12 | try {
13 | let stream;
14 | while ((stream = await parts()) != null) {
15 | const filename = stream.filename.toLowerCase();
16 | const timestamp = Date.now();
17 | const target = path.join(this.config.baseDir, 'app/public/img', `${timestamp}_${filename}`);
18 | const writeStream = fs.createWriteStream(target);
19 | await pump(stream, writeStream);
20 | files.push({ name: filename, url: `/public/img/${timestamp}_${filename}` });
21 | }
22 | const _files = await service.images.create(files);
23 | return ctx.helper.success(ctx, { files: _files });
24 | } catch (e) {
25 | console.log(e);
26 | return ctx.helper.error(ctx, error_001[0], error_001[1]);
27 | }
28 | }
29 | }
30 |
31 | module.exports = ImagesController;
32 |
--------------------------------------------------------------------------------
/server/app/extend/helper.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const nodemailer = require('nodemailer');
3 |
4 | // success
5 | exports.success = (ctx, result = null, message = 'Succeed') => {
6 | ctx.body = {
7 | code: 0,
8 | message,
9 | data: result,
10 | };
11 | };
12 |
13 | // error
14 | exports.error = (ctx, code, message) => {
15 | ctx.body = {
16 | code,
17 | message,
18 | };
19 | };
20 |
21 |
22 | // get clien ip
23 | exports.getIp = ctx => {
24 | return ctx.request.ip.replace(/::ffff:/, '');
25 | };
26 |
27 | exports.sendUserEmail = (ctx, toEmail) => {
28 | const expiraton = 1800;
29 | const code = require('crypto')
30 | .randomBytes(16)
31 | .toString('hex');
32 | ctx.app.redis.set(`${code}`, toEmail, 'EX', expiraton);
33 | const config_email = {
34 | host: 'smtp.qq.com',
35 | post: 25, // SMTP 端口
36 | // secureConnection: true, // 使用 SSL
37 | auth: {
38 | user: '2929712050@qq.com',
39 | // 这里密码不是qq密码,是你设置的smtp授权码
40 | pass: 'rdbbqlgolefhdecc',
41 | },
42 | };
43 | const transporter = nodemailer.createTransport(config_email);
44 | const html = '感谢您的注册,请点击下面的链接激活您的账号,如果链接在邮箱中打不开,您可以试试将其复制到浏览器地址栏中' + ctx.app.config.baseUrl + '/activation?code=' + code + '&account=' + toEmail + '
';
45 | const data = {
46 | from: '2929712050@qq.com', // 发件地址
47 | to: toEmail, // 收件人
48 | subject: '注册激活-汪乐的个人网站', // 标题
49 | // text: html // 标题 //text和html两者只支持一种
50 | html, // html 内容
51 | };
52 | transporter.sendMail(data, function (err, info) {
53 | if (err) {
54 | return (err);
55 | }
56 | return (info.response);
57 | });
58 | };
59 |
--------------------------------------------------------------------------------
/server/app/middleware/jwt.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const { error_005, error_001 } = require('../common/common');
3 |
4 | module.exports = () => {
5 | return async (ctx, next) => {
6 | const bearerToken = ctx.headers.authorization,
7 | token = bearerToken && bearerToken.replace('Bearer ', '');
8 | let userInfo = null;
9 | if (token) {
10 | try {
11 | userInfo = await ctx.app.redis.get(`${token}`);
12 | if (!userInfo) {
13 | ctx.body = {
14 | code: error_005[0],
15 | message: error_005[1],
16 | };
17 | ctx.status = 200;
18 | } else {
19 | userInfo = JSON.parse(userInfo);
20 | ctx.id = userInfo.id;
21 | await next();
22 | }
23 | } catch (err) {
24 | this.logger(err);
25 | ctx.body = {
26 | code: error_001[0],
27 | message: error_001[1],
28 | };
29 | ctx.status = 200;
30 | return;
31 | }
32 |
33 | } else {
34 | // 如果token为空,则代表客户没有登录
35 | ctx.body = {
36 | code: error_005[0],
37 | message: error_005[1],
38 | };
39 | ctx.status = 200;
40 | }
41 | };
42 | };
43 |
--------------------------------------------------------------------------------
/server/app/model/article.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = app => {
3 | const mongoose = app.mongoose;
4 | const Schema = mongoose.Schema;
5 |
6 | const ArticleSchema = new Schema({
7 | title: {
8 | type: String,
9 | required: true,
10 | },
11 | tag: {
12 | type: Schema.ObjectId,
13 | ref: 'Tags',
14 | required: true,
15 | },
16 | tag_detail: {
17 | type: Object,
18 | required: true,
19 | },
20 | category: {
21 | type: Schema.ObjectId,
22 | ref: 'Categories',
23 | required: true,
24 | },
25 | category_detail: {
26 | type: Object,
27 | required: true,
28 | },
29 | creator: {
30 | type: Schema.ObjectId,
31 | ref: 'Users',
32 | required: true,
33 | },
34 | htmlValue: {
35 | type: String,
36 | },
37 | markdownValue: {
38 | type: String,
39 | },
40 | publishAt: { // 发布日期
41 | type: Number,
42 | required: true,
43 | },
44 | publishStatus: {
45 | type: String,
46 | default: '1',
47 | enum: ['1', '2'], // '1'表示草稿,'2'表示发布
48 | },
49 | viewCount: {
50 | type: Number,
51 | default: 0,
52 | },
53 | createAt: {
54 | type: Number,
55 | default: Date.now,
56 | },
57 | updatedAt: {
58 | type: Number,
59 | },
60 | deletedAt: {
61 | type: Number,
62 | },
63 | status: { // 状态
64 | type: String,
65 | enum: ['0', '1', '2'], // 0存在 1更新,2 删除
66 | default: '0',
67 | },
68 | }, { versionKey: false });
69 |
70 | return mongoose.model('Article', ArticleSchema);
71 | };
72 |
--------------------------------------------------------------------------------
/server/app/model/categories.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = app => {
3 | const mongoose = app.mongoose;
4 | const Schema = mongoose.Schema;
5 | const CategoriesSchema = new Schema({
6 | name: {
7 | type: String,
8 | required: true,
9 | },
10 | createAt: {
11 | type: Number,
12 | default: Date.now,
13 | },
14 | updatedAt: {
15 | type: Number,
16 | },
17 | deletedAt: {
18 | type: Number,
19 | },
20 | status: { // 状态
21 | type: String,
22 | enum: ['0', '1', '2'], // 0存在 1更新,2 删除
23 | default: '0',
24 | },
25 | }, { versionKey: false });
26 | return mongoose.model('Categories', CategoriesSchema);
27 | };
28 |
--------------------------------------------------------------------------------
/server/app/model/comment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = app => {
3 | const mongoose = app.mongoose;
4 | const Schema = mongoose.Schema;
5 |
6 | const CommentSchema = new Schema({
7 | article_id: { // 文档id
8 | type: String,
9 | required: true,
10 | },
11 | commentator: { // 评论人id
12 | type: Schema.ObjectId,
13 | ref: 'Users',
14 | required: true,
15 | },
16 | content: { // 回复的内容
17 | type: String,
18 | required: true,
19 | },
20 | reply_to_comment_id: { // 标识回复的哪一条评论
21 | type: Schema.ObjectId,
22 | },
23 | reply_to_user_id: { // 标识回复的哪一个人
24 | type: Schema.ObjectId,
25 | ref: 'Users',
26 | },
27 | createAt: {
28 | type: Number,
29 | default: Date.now,
30 | },
31 | updatedAt: {
32 | type: Number,
33 | },
34 | deletedAt: {
35 | type: Number,
36 | },
37 | status: { // 状态
38 | type: String,
39 | enum: ['0', '1', '2'], // 0存在 1更新,2 删除
40 | default: '0',
41 | },
42 | }, { versionKey: false });
43 |
44 | return mongoose.model('Comments', CommentSchema);
45 | };
46 |
--------------------------------------------------------------------------------
/server/app/model/images.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = app => {
3 | const mongoose = app.mongoose;
4 | const Schema = mongoose.Schema;
5 |
6 | const ImagesSchema = new Schema({
7 | name: {
8 | type: String,
9 | required: true,
10 | },
11 | url: {
12 | type: String,
13 | required: true,
14 | },
15 | publishAt: { // 发布日期
16 | type: Number,
17 | required: true,
18 | },
19 | createAt: {
20 | type: Number,
21 | default: Date.now,
22 | },
23 | updatedAt: {
24 | type: Number,
25 | },
26 | deletedAt: {
27 | type: Number,
28 | },
29 | status: { // 状态
30 | type: String,
31 | enum: ['0', '1', '2'], // 0存在 1更新,2 删除
32 | default: '0',
33 | },
34 | }, { versionKey: false });
35 |
36 | return mongoose.model('Images', ImagesSchema);
37 | };
38 |
--------------------------------------------------------------------------------
/server/app/model/tags.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = app => {
3 | const mongoose = app.mongoose;
4 | const Schema = mongoose.Schema;
5 |
6 | const TagsSchema = new Schema({
7 | name: {
8 | type: String,
9 | required: true,
10 | },
11 | createAt: {
12 | type: Number,
13 | default: Date.now,
14 | },
15 | updatedAt: {
16 | type: Number,
17 | },
18 | deletedAt: {
19 | type: Number,
20 | },
21 | status: { // 状态
22 | type: String,
23 | enum: ['0', '1', '2'], // 0存在 1更新,2 删除
24 | default: '0',
25 | },
26 | }, { versionKey: false });
27 |
28 | return mongoose.model('Tags', TagsSchema);
29 | };
30 |
--------------------------------------------------------------------------------
/server/app/model/user.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const rand = require('csprng');
3 | const sha1 = require('sha1');
4 | module.exports = app => {
5 | const mongoose = app.mongoose;
6 | const Schema = mongoose.Schema;
7 |
8 | const UserSchema = new Schema({
9 | name: {
10 | type: String,
11 | required: true,
12 | },
13 | gender: {
14 | type: String,
15 | enum: ['male', 'female'], // 0存在 1更新,2 删除
16 | default: 'male',
17 | },
18 | email: {
19 | type: String,
20 | },
21 | password: {
22 | type: String,
23 | required: true,
24 | },
25 | salt: {
26 | type: String,
27 | default: '',
28 | },
29 | role: {
30 | type: Number,
31 | default: 1, // 0: 普通用户,1,管理员
32 | },
33 | activated: {
34 | type: String,
35 | enum: ['0', '1'], // 0待激活 1已激活
36 | default: '1',
37 | },
38 | createAt: {
39 | type: Number,
40 | default: Date.now,
41 | },
42 | updatedAt: {
43 | type: Number,
44 | },
45 | deletedAt: {
46 | type: Number,
47 | },
48 | status: { // 状态
49 | type: String,
50 | enum: ['0', '1', '2'], // 0存在 1更新,2 删除
51 | default: '0',
52 | },
53 | }, { versionKey: false });
54 | const User = mongoose.model('Users', UserSchema);
55 | initialize(User);
56 | return User;
57 | };
58 |
59 | function initialize(User) {
60 | User.find({}, (err, doc) => {
61 | if (err) {
62 | this.logger(err);
63 | console.log('initialize failed');
64 | } else if (!doc.length) {
65 | const salt = rand(160, 36);
66 | new User({
67 | activated: 1,
68 | email: '2929712050@qq.com',
69 | name: 'admin',
70 | password: sha1('admin' + salt),
71 | salt,
72 | }).save();
73 | } else {
74 | console.log('initialize successfully');
75 | }
76 | });
77 | }
78 |
79 |
--------------------------------------------------------------------------------
/server/app/router.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | module.exports = app => {
3 | const { router, controller } = app;
4 | const jwt = app.middleware.jwt();
5 | router.post('/login', controller.user.login);
6 | router.get('/', controller.home.index);
7 | router.post('/tags', jwt, controller.tags.create);
8 | router.get('/tags', controller.tags.get);
9 | router.get('/tags/:id', controller.tags.get);
10 | router.put('/tags/:id', jwt, controller.tags.findByIdAndUpdate);
11 | router.delete('/tags/:id', jwt, controller.tags.deleteTag);
12 | router.get('/user', jwt, controller.user.getUserInfo);
13 | router.put('/user', jwt, controller.user.editUserInfo);
14 | router.get('/users', jwt, controller.user.getUserList);
15 | router.post('/articles', jwt, controller.article.create);
16 | router.get('/articles', controller.article.getArticles);
17 | router.get('/articles/keywords', controller.article.findByKeyWords);
18 | router.get('/articles/:_id', controller.article.getArticle);
19 | router.put('/articles/:_id', jwt, controller.article.findByIdAndUpdate);
20 | router.delete('/articles/:id', jwt, controller.article.deleteArticle);
21 | router.get('/articles/tags/:tag', controller.article.findByTag);
22 | router.get('/articles/category/:category', controller.article.findByCategory);
23 | router.get('/archives', controller.article.archives);
24 | router.get('/archives/:timeline', controller.article.findByArchive);
25 | router.post('/img/upload', jwt, controller.images.upload); // upload
26 | router.post('/categories', jwt, controller.categories.create);
27 | router.get('/categories', controller.categories.getCategories);
28 | router.get('/categories/count', controller.categories.getAggregateCategories);
29 | router.put('/categories/:id', jwt, controller.categories.findByIdAndUpdate);
30 | router.delete('/categories/:id', jwt, controller.categories.deleteCategory);
31 | router.post('/comment/', jwt, controller.comment.create);
32 | router.get('/comment/:article_id', controller.comment.getCommentByArticleId);
33 | router.get('/authcode', controller.user.generateAuthCode);
34 | router.post('/signup', controller.user.signup);
35 | // router.get('/activation', controller.user.userActivation);
36 |
37 | };
38 |
--------------------------------------------------------------------------------
/server/app/service/categories.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const Service = require('egg').Service;
3 |
4 | class CategoriesService extends Service {
5 | async create(name) {
6 | return await this.ctx.model.Categories.create({ name });
7 | }
8 |
9 | async find() {
10 | return await this.ctx.model.Categories.find({ status: { $ne: '2' } });
11 | }
12 |
13 | async findById(id) {
14 | return await this.ctx.model.Categories.findById(id);
15 | }
16 |
17 | async findByIdAndUpdate(_id, content) {
18 | return await this.ctx.model.Categories.findByIdAndUpdate({ _id, status: { $ne: '2' } }, { $set: content });
19 | }
20 |
21 | async aggregateCategories() {
22 | return await this.ctx.model.Categories.aggregate([
23 | { $match: { status: { $ne: '2' } } },
24 | {
25 | $lookup:
26 | {
27 | from: 'articles',
28 | localField: '_id',
29 | foreignField: 'category',
30 | as: 'articles',
31 | },
32 | },
33 | {
34 | $unwind: {
35 | path: '$articles',
36 | // preserveNullAndEmptyArrays : true // 空的数组也拆分
37 | },
38 | },
39 | { $match: { 'articles.status': { $ne: '2' } } },
40 | { $group: { _id: '$_id', name: { $first: '$name' }, total: { $sum: 1 } } },
41 |
42 | ]);
43 | }
44 |
45 | }
46 |
47 | module.exports = CategoriesService;
48 |
--------------------------------------------------------------------------------
/server/app/service/comment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const Service = require('egg').Service;
3 | class CommentService extends Service {
4 | async create({ article_id, commentator, content, reply_to_comment_id, reply_to_user_id }) {
5 | return await this.ctx.model.Comment.create({ article_id, commentator, content, reply_to_comment_id, reply_to_user_id });
6 | }
7 |
8 | async find(condition) {
9 | return await this.ctx.model.Comment.find({ ...condition, status: { $ne: '2' } }).populate('commentator', 'name').lean()
10 | .exec();
11 | }
12 | }
13 |
14 | module.exports = CommentService;
15 |
--------------------------------------------------------------------------------
/server/app/service/images.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const Service = require('egg').Service;
3 |
4 | class ImgService extends Service {
5 | async create(data) {
6 | return await this.ctx.model.Images.collection.insertMany(data);
7 | }
8 | }
9 |
10 | module.exports = ImgService;
11 |
--------------------------------------------------------------------------------
/server/app/service/tags.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const Service = require('egg').Service;
3 | class TagsService extends Service {
4 | async create(name) {
5 | return await this.ctx.model.Tags.create({ name });
6 | }
7 |
8 | async find(condition) {
9 | return await this.ctx.model.Tags.find({ ...condition, status: { $ne: '2' } });
10 | }
11 |
12 | async findById(id) {
13 | return await this.ctx.model.Tags.findById(id);
14 | }
15 |
16 | async findByIdAndUpdate(_id, content) {
17 | return await this.ctx.model.Tags.findByIdAndUpdate({ _id, status: { $ne: '2' } }, { $set: content });
18 | }
19 | }
20 |
21 | module.exports = TagsService;
22 |
--------------------------------------------------------------------------------
/server/app/service/user.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const Service = require('egg').Service;
3 |
4 | class UserService extends Service {
5 | async create(info) {
6 | return await this.ctx.model.User.create(info);
7 | }
8 |
9 | async findOne(condition) {
10 | return await this.ctx.model.User.findOne(condition, {
11 | password: 0,
12 | salt: 0,
13 | activated: 0,
14 | });
15 | }
16 |
17 | async findDetail(condition) {
18 | return await this.ctx.model.User.findOne(condition);
19 | }
20 |
21 | async findById(_id) {
22 | return await this.ctx.model.User.findById({ _id }, {
23 | password: 0,
24 | salt: 0,
25 | activated: 0,
26 | });
27 | }
28 |
29 | async find() {
30 | return await this.ctx.model.User.find({}, {
31 | password: 0,
32 | salt: 0,
33 | activated: 0,
34 | });
35 | }
36 |
37 | async updateById(_id, data) {
38 | return await this.ctx.model.User.updateOne({ _id }, { $set: data });
39 | }
40 | }
41 |
42 |
43 | module.exports = UserService;
44 |
--------------------------------------------------------------------------------
/server/appveyor.yml:
--------------------------------------------------------------------------------
1 | environment:
2 | matrix:
3 | - nodejs_version: '8'
4 |
5 | install:
6 | - ps: Install-Product node $env:nodejs_version
7 | - npm i npminstall && node_modules\.bin\npminstall
8 |
9 | test_script:
10 | - node --version
11 | - npm --version
12 | - npm run test
13 |
14 | build: off
15 |
--------------------------------------------------------------------------------
/server/config/config.default.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const path = require('path');
3 | module.exports = appInfo => {
4 | const config = exports = {};
5 |
6 | // use for cookie sign key, should change to your own and keep security
7 | config.keys = appInfo.name + '_1540997103739_7974';
8 | config.static = {
9 | prefix: '/public/',
10 | dir: path.join(appInfo.baseDir, 'app/public'),
11 | };
12 |
13 | // add your config here
14 | config.middleware = [];
15 | // jwt
16 | config.jwt = {
17 | secret: 'my.secret.my.secret.my.secret.my.secret',
18 | };
19 | // config cors
20 | config.security = {
21 | csrf: {
22 | enable: false,
23 | ignoreJSON: false,
24 | },
25 | domainWhiteList: ['*'],
26 | };
27 | // config cors
28 | config.cors = {
29 | origin: '*',
30 | allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS',
31 | };
32 | // config session
33 | config.session = {
34 | key: 'SESSION_ID', // key名字
35 | maxAge: 1000 * 60 * 24,
36 | httpOnly: true,
37 | encrypt: true, // 加密
38 | renew: true, // 最大时间范围内,刷新,自动增加最大时间
39 | };
40 | return config;
41 | };
42 |
--------------------------------------------------------------------------------
/server/config/config.local.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | exports.mongoose = {
3 | client: {
4 | // url: `mongodb://localhost:27017/ant_blog`,
5 | url: `mongodb://ant:wl123456@localhost:27017/ant_blog`,
6 | options: {},
7 | },
8 | };
9 |
10 | // config redis
11 | exports.redis = {
12 | client: {
13 | port: 6379, // Redis port
14 | host: "localhost", // Redis host
15 | password: 123456,
16 | db: 0,
17 | },
18 | };
19 |
20 | // exports.baseUrl = 'http://182.254.168.74:7001';
21 | // exports.redirectActivationUrl = 'http://182.254.168.74/activation';
22 |
--------------------------------------------------------------------------------
/server/config/config.prod.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | exports.mongoose = {
3 | client: {
4 | // url: `mongodb://mongodb:27017/ant_blog`,
5 | url: `mongodb://ant:wl123456@mongodb:27017/ant_blog`, // user: 数据库的用户名 password: 密码.
6 | options: {},
7 | },
8 | };
9 |
10 | // config redis
11 | exports.redis = {
12 | client: {
13 | port: 6379, // Redis port
14 | host: "redis", // Redis host
15 | password: 123456,
16 | db: 0,
17 | },
18 | };
19 |
20 | // exports.baseUrl = 'http://182.254.168.74:7001';
21 | // exports.redirectActivationUrl = 'http://182.254.168.74/activation';
22 |
--------------------------------------------------------------------------------
/server/config/plugin.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // had enabled by egg
4 | // exports.static = true;
5 | exports.mongoose = {
6 | enable: true,
7 | package: 'egg-mongoose',
8 | };
9 |
10 | exports.cors = {
11 | enable: true,
12 | package: 'egg-cors',
13 | };
14 |
15 | exports.static = {
16 | enable: true,
17 | package: 'egg-static',
18 | };
19 |
20 | exports.redis = {
21 | enable: true,
22 | package: 'egg-redis',
23 | };
24 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ant-egg-blog-service",
3 | "version": "1.0.0",
4 | "description": "",
5 | "private": true,
6 | "dependencies": {
7 | "axios": "^0.18.0",
8 | "crypto": "^1.0.1",
9 | "csprng": "^0.1.2",
10 | "egg": "^2.2.1",
11 | "egg-cors": "^2.1.1",
12 | "egg-mongoose": "^3.2.0",
13 | "egg-redis": "^2.3.2",
14 | "egg-scripts": "^2.5.0",
15 | "egg-static": "^2.1.1",
16 | "nodemailer": "^6.2.1",
17 | "sha1": "^1.1.1",
18 | "superstruct": "^0.6.0",
19 | "svg-captcha": "^1.4.0"
20 | },
21 | "devDependencies": {
22 | "autod": "^3.0.1",
23 | "autod-egg": "^1.0.0",
24 | "egg-bin": "^4.3.5",
25 | "egg-ci": "^1.8.0",
26 | "egg-mock": "^3.14.0",
27 | "eslint": "^4.11.0",
28 | "eslint-config-egg": "^6.0.0",
29 | "webstorm-disable-index": "^1.2.0"
30 | },
31 | "engines": {
32 | "node": ">=8.9.0"
33 | },
34 | "scripts": {
35 | "start": "egg-scripts start --title=server",
36 | "stop": "egg-scripts stop --title=server",
37 | "dev": "egg-bin dev",
38 | "debug": "egg-bin debug",
39 | "test": "npm run lint -- --fix && npm run test-local",
40 | "test-local": "egg-bin test",
41 | "cov": "egg-bin cov",
42 | "lint": "eslint .",
43 | "ci": "npm run lint && npm run cov",
44 | "lint-fix": "eslint --fix --ext .js ./",
45 | "autod": "autod"
46 | },
47 | "ci": {
48 | "version": "8"
49 | },
50 | "repository": {
51 | "type": "git",
52 | "url": ""
53 | },
54 | "author": "ant",
55 | "license": "MIT"
56 | }
57 |
--------------------------------------------------------------------------------
/server/test/app/controller/home.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { app, assert } = require('egg-mock/bootstrap');
4 |
5 | describe('test/app/controller/home.test.js', () => {
6 |
7 | it('should assert', function* () {
8 | const pkg = require('../../../package.json');
9 | assert(app.config.keys.startsWith(pkg.name));
10 |
11 | // const ctx = app.mockContext({});
12 | // yield ctx.service.xx();
13 | });
14 |
15 | it('should GET /', () => {
16 | return app.httpRequest()
17 | .get('/')
18 | .expect('hi, egg')
19 | .expect(200);
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/web/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "modules": false,
7 | "targets": {
8 | "browsers": [
9 | "> 1%",
10 | "last 2 versions",
11 | "not ie <= 8"
12 | ]
13 | }
14 | }
15 | ],
16 | "stage-2"
17 | ],
18 | "plugins": [
19 | [
20 | "component",
21 | {
22 | "libraryName": "element-ui",
23 | "styleLibraryName": "~theme"
24 | }
25 | ],
26 | "transform-vue-jsx",
27 | "transform-runtime",
28 | ],
29 | "env": {
30 | "test": {
31 | "presets": [
32 | "env",
33 | "stage-2"
34 | ],
35 | "plugins": [
36 | "transform-vue-jsx",
37 | "istanbul"
38 | ]
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/web/.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 |
--------------------------------------------------------------------------------
/web/.eslintignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /config/
3 | /dist/
4 | /*.js
5 | /test/unit/coverage/
6 | /src/store
7 | /src/api
8 | /src/utils
9 | /src/main.js
10 |
--------------------------------------------------------------------------------
/web/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // https://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parserOptions: {
6 | parser: "babel-eslint"
7 | },
8 | env: {
9 | browser: true
10 | },
11 | extends: [
12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
14 | "plugin:vue/essential",
15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md
16 | "standard"
17 | ],
18 | // required to lint *.vue files
19 | plugins: ["vue"],
20 | // add your custom rules here
21 | rules: {
22 | // allow async-await
23 | "generator-star-spacing": "off",
24 | // allow debugger during development
25 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
26 | "space-before-function-paren": 0
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/web/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .history
3 | node_modules/
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 |
--------------------------------------------------------------------------------
/web/.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 |
--------------------------------------------------------------------------------
/web/README.md:
--------------------------------------------------------------------------------
1 | # AntVueBlogFront
2 |
3 | > Vue.js+Egg.js+Mongodb个人网站
4 |
5 | > 网站地址:[www.wangleant.com](http://www.wangleant.com)
6 |
7 | ## 主要技术栈
8 |
9 | * 前端:vue.js、vue-router、vuex [AntVueBlogFront](https://github.com/antbaobao/AntVueBlogFront)
10 | * 后端:Egg.js、Mongodb [AntEggBlogService](https://github.com/antbaobao/AntEggBlogService)
11 | * 后台管理: vue.js、vue-router、vuex [AntVueBlogAdmin](https://github.com/antbaobao/AntVueBlogAdmin)
12 |
13 | ## 博客功能
14 | ### 前台页面
15 | - 文档列表
16 | - 分类
17 | - 标签
18 | - 归档
19 | - 评论
20 | - 文章检索
21 |
22 | ### 后台管理
23 | - 发布文章、存为草稿
24 | - 文章管理
25 | - 标签管理
26 | - 分类管理
27 | - 登录验证
28 |
29 | ## 开始
30 |
31 | 运行环境
32 | - node.js
33 | - mongoDB
34 |
35 | 克隆远程库
36 | ```bash
37 | git clone git@github.com:antbaobao/AntVueBlogFront.git
38 | ```
39 | 安装依赖
40 | ```bash
41 | cd AntVueBlogFront
42 | npm i
43 | ```
44 | 运行
45 | ```bash
46 | npm run dev
47 | ```
48 |
49 | ### 目录
50 | ```
51 | │ .babelrc babel配置
52 | │ .editorconfig 编辑器配置
53 | │ .eslintignore eslint忽略
54 | │ .eslintrc.js eslintrc配置
55 | │ .gitignore git上传忽略
56 | │ .postcssrc.js
57 | │ Dockerfile docker 配置
58 | │ index.html 打包模板
59 | │ package.json
60 | │ README.md
61 | │
62 | ├─build
63 | ├─src
64 | │ │ main.js 项目入口
65 | │ │ App.vue 根组件
66 | │ │ index.css 全局样式
67 | │ │
68 | │ ├─api api 请求接口
69 | │ │
70 | │ ├─assets 外部引用文件
71 | │ │ ├─css
72 | │ │ └─js
73 | │ │
74 | │ ├─components vue组件
75 | │ │
76 | │ ├─ layout 页面公共结构
77 | │ │
78 | │ ├─store vuex文件
79 | │ │
80 | │ ├─utils 工具函数
81 | │ │
82 | │ └─views 页面vue文件
83 | │
84 | ├─test
85 | └─static 静态文件
86 | ```
87 |
88 | ## 部署
89 |
90 | 部署流程可以参考[使用docker部署网站](https://github.com/antbaobao/AntBlog/issues/20)
91 |
92 | ## 待开发
93 | 1. 准备开发注册登录权限管理系统
94 | 2. 新增"生活与我"模块,加入权限控制。
95 | 3. 准备用ts改造
96 | 4. 用微前端的架构把admin整合进来
97 | ## [1.2] - 2020-04-17
98 | ### 新增
99 | - 开发评论功能
100 | ## [1.1] - 2020-04-15
101 | ### 新增
102 | - 注册登录功能
103 |
104 | ## [1.0] - 2019-06-20
105 | ### 更新
106 | - 优化了页面样式
107 |
108 | ## [0.9] - 2019-05-31
109 | ### 更新
110 | - 优化了页面跳转
111 | - 优化了CSS的命名使用BEM命名规则
112 |
113 |
114 | ## [0.81] - 2019-05-15
115 | ### 新增
116 | - 新增了全文检索功能
117 |
118 |
--------------------------------------------------------------------------------
/web/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 |
--------------------------------------------------------------------------------
/web/build/check-versions.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const chalk = require('chalk')
3 | const semver = require('semver')
4 | const packageConfig = require('../package.json')
5 | const shell = require('shelljs')
6 |
7 | function exec (cmd) {
8 | return require('child_process').execSync(cmd).toString().trim()
9 | }
10 |
11 | const versionRequirements = [
12 | {
13 | name: 'node',
14 | currentVersion: semver.clean(process.version),
15 | versionRequirement: packageConfig.engines.node
16 | }
17 | ]
18 |
19 | if (shell.which('npm')) {
20 | versionRequirements.push({
21 | name: 'npm',
22 | currentVersion: exec('npm --version'),
23 | versionRequirement: packageConfig.engines.npm
24 | })
25 | }
26 |
27 | module.exports = function () {
28 | const warnings = []
29 |
30 | for (let i = 0; i < versionRequirements.length; i++) {
31 | const mod = versionRequirements[i]
32 |
33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
34 | warnings.push(mod.name + ': ' +
35 | chalk.red(mod.currentVersion) + ' should be ' +
36 | chalk.green(mod.versionRequirement)
37 | )
38 | }
39 | }
40 |
41 | if (warnings.length) {
42 | console.log('')
43 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
44 | console.log()
45 |
46 | for (let i = 0; i < warnings.length; i++) {
47 | const warning = warnings[i]
48 | console.log(' ' + warning)
49 | }
50 |
51 | console.log()
52 | process.exit(1)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/web/build/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/build/logo.png
--------------------------------------------------------------------------------
/web/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 |
--------------------------------------------------------------------------------
/web/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 | GATEWAY: '"http://localhost:7001"',
8 | STATIC_DOMAIN: '"http://localhost:7001"'
9 | });
10 |
--------------------------------------------------------------------------------
/web/config/prod.env.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | NODE_ENV: '"production"',
3 | GATEWAY: '"http://localhost/api"',
4 | STATIC_DOMAIN: '"http://localhost/api"'
5 | };
6 |
--------------------------------------------------------------------------------
/web/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/favicon.ico
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 汪乐的个人网站
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/web/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "paths": {
5 | "@/*": [
6 | "src/*"
7 | ]
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/web/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
--------------------------------------------------------------------------------
/web/src/api/article.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function fetchArticle(params) {
4 | return request.get('/articles', params)
5 | }
6 |
7 | export function getArticleDetail(id) {
8 | return request.get(`/articles/${id}`)
9 | }
10 |
11 | export function getArticlesByTag(id) {
12 | return request.get(`/articles/tags/${id}`)
13 | }
14 |
15 | export function getArticlesByKeywords(keywords = '', pageSize = 0, pageLimit = 0) {
16 | return request.get(`/articles/keywords?keywords=${keywords}&pageSize=${pageSize}&pageLimit=${pageLimit}`)
17 | }
18 |
19 | export function getArticlesByCategory(category, pageSize = 0, pageLimit = 0) {
20 | return request.get(`/articles/category/${category}?pageSize=${pageSize}&pageLimit=${pageLimit}`)
21 | }
22 |
23 | export function fetchArchives() {
24 | return request.get('/archives')
25 | }
26 |
27 | export function getArticlesByArchives({ pageSize = 0, pageLimit = 0, timeline }) {
28 | return request.get(`/archives/${timeline}?pageSize=${pageSize}&pageLimit=${pageLimit}`)
29 | }
30 |
--------------------------------------------------------------------------------
/web/src/api/category.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function getList() {
4 | return request.get('/categories')
5 | }
6 |
7 | export function countCategotres() {
8 | return request.get('/categories/count')
9 | }
10 |
--------------------------------------------------------------------------------
/web/src/api/comment.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 | /**
3 | * 发表评论
4 | * @param {*} param0
5 | */
6 | export function postComment({ article_id, content, reply_to_comment_id, reply_to_user_id }) {
7 | return request.post('/comment', { article_id, content, reply_to_comment_id, reply_to_user_id })
8 | }
9 | export function getCommentByArticleId(article_id) {
10 | return request.get(`/comment/${article_id}`)
11 | }
--------------------------------------------------------------------------------
/web/src/api/github.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function githubLogin() {
4 | return request.get('/github/user')
5 | }
6 | export function getUserInfo(code) {
7 | return request.get('/github/user', { code })
8 | }
9 |
--------------------------------------------------------------------------------
/web/src/api/tags.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function getList() {
4 | return request.get('/tags')
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/web/src/api/user.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function user(name, password) {
4 | return request.post('/user', { name, password })
5 | }
6 |
7 | export function getUserInfo() {
8 | return request.get('/user')
9 | }
10 |
11 | export function logout() {
12 | return request({
13 | url: '/user/logout',
14 | method: 'post'
15 | })
16 | }
17 |
18 | export function generateAuthCode() {
19 | return request.get('/authcode')
20 | }
21 |
22 | export function signup(info) {
23 | return request.post('/signup', info)
24 | }
25 |
26 | export function login(info) {
27 | return request.post('/login', info)
28 | }
29 |
--------------------------------------------------------------------------------
/web/src/assets/about.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/src/assets/about.jpeg
--------------------------------------------------------------------------------
/web/src/assets/activation.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/src/assets/activation.jpeg
--------------------------------------------------------------------------------
/web/src/assets/active_mail_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/src/assets/active_mail_logo.png
--------------------------------------------------------------------------------
/web/src/assets/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/src/assets/bg.png
--------------------------------------------------------------------------------
/web/src/assets/login.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/src/assets/login.jpeg
--------------------------------------------------------------------------------
/web/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/src/assets/logo.png
--------------------------------------------------------------------------------
/web/src/assets/payment/alipay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/src/assets/payment/alipay.png
--------------------------------------------------------------------------------
/web/src/assets/payment/weixin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/src/assets/payment/weixin.png
--------------------------------------------------------------------------------
/web/src/assets/profile.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/src/assets/profile.jpeg
--------------------------------------------------------------------------------
/web/src/assets/signup.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/src/assets/signup.jpeg
--------------------------------------------------------------------------------
/web/src/components/Comment/comment.vue:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 |
67 |
68 |
84 |
--------------------------------------------------------------------------------
/web/src/components/Comment/commentatorInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
39 |
40 |
60 |
--------------------------------------------------------------------------------
/web/src/components/Markdown/cover.css:
--------------------------------------------------------------------------------
1 | .markdown-body {
2 | color: #4F566B;
3 | font-family: Georgia, serif;
4 | line-height: 1.5;
5 | }
6 |
7 | .markdown-body pre {
8 | background-color: #f5f2f0;
9 | overflow: auto;
10 | }
11 |
12 | .markdown-body h1,
13 | .markdown-body h2,
14 | .markdown-body h3,
15 | .markdown-body h4,
16 | .markdown-body h5,
17 | .markdown-body h6 {
18 | color: #24272E;
19 | }
20 |
21 | .markdown-body code {
22 | display: inline-block;
23 | padding-left: 5px;
24 | padding-right: 5px;
25 | background-color: pink;
26 | border-radius: 5px;
27 | margin: auto 3px;
28 | }
29 |
--------------------------------------------------------------------------------
/web/src/components/Markdown/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
25 |
--------------------------------------------------------------------------------
/web/src/components/Markdown/style.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Google Code style (c) Aahan Krish
4 |
5 | */
6 |
7 | .hljs {
8 | display: block;
9 | overflow-x: auto;
10 | padding: 0.5em;
11 | background: white;
12 | color: black;
13 | }
14 |
15 | .hljs-comment,
16 | .hljs-quote {
17 | color: #800;
18 | }
19 |
20 | .hljs-keyword,
21 | .hljs-selector-tag,
22 | .hljs-section,
23 | .hljs-title,
24 | .hljs-name {
25 | color: #008;
26 | }
27 |
28 | .hljs-variable,
29 | .hljs-template-variable {
30 | color: #660;
31 | }
32 |
33 | .hljs-string,
34 | .hljs-selector-attr,
35 | .hljs-selector-pseudo,
36 | .hljs-regexp {
37 | color: #080;
38 | }
39 |
40 | .hljs-literal,
41 | .hljs-symbol,
42 | .hljs-bullet,
43 | .hljs-meta,
44 | .hljs-number,
45 | .hljs-link {
46 | color: #066;
47 | }
48 |
49 | .hljs-title,
50 | .hljs-doctag,
51 | .hljs-type,
52 | .hljs-attr,
53 | .hljs-built_in,
54 | .hljs-builtin-name,
55 | .hljs-params {
56 | color: #606;
57 | }
58 |
59 | .hljs-attribute,
60 | .hljs-subst {
61 | color: #000;
62 | }
63 |
64 | .hljs-formula {
65 | background-color: #eee;
66 | font-style: italic;
67 | }
68 |
69 | .hljs-selector-id,
70 | .hljs-selector-class {
71 | color: #9B703F
72 | }
73 |
74 | .hljs-addition {
75 | background-color: #baeeba;
76 | }
77 |
78 | .hljs-deletion {
79 | background-color: #ffc8bd;
80 | }
81 |
82 | .hljs-doctag,
83 | .hljs-strong {
84 | font-weight: bold;
85 | }
86 |
87 | .hljs-emphasis {
88 | font-style: italic;
89 | }
--------------------------------------------------------------------------------
/web/src/components/NoData/index.vue:
--------------------------------------------------------------------------------
1 |
2 | {{text}}
3 |
4 |
14 |
21 |
--------------------------------------------------------------------------------
/web/src/components/PaymentCode/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 赏
5 | ↑
6 | 打发点吧
7 |
8 |
9 |
![]()
10 |
![]()
11 |
12 |
13 |
14 |
33 |
34 |
66 |
--------------------------------------------------------------------------------
/web/src/components/Skeleton/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
47 |
--------------------------------------------------------------------------------
/web/src/components/Spin/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
23 |
24 |
33 |
--------------------------------------------------------------------------------
/web/src/components/index.js:
--------------------------------------------------------------------------------
1 | import CustomSpin from './Spin'
2 | import CustomNoData from './NoData'
3 | import CustomSkeleton from './Skeleton'
4 |
5 | const components = {
6 | install: function (Vue) {
7 | Vue.component('CustomSpin', CustomSpin)
8 | Vue.component('CustomNoData', CustomNoData)
9 | Vue.component('CustomSkeleton', CustomSkeleton)
10 | }
11 | }
12 |
13 | export default components
14 |
--------------------------------------------------------------------------------
/web/src/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | -webkit-font-smoothing: antialiased;
5 | }
6 |
7 | body {
8 | font-family: Georgia, serif;
9 | color: #4F566B;
10 | }
11 |
12 | a:hover {
13 | cursor: pointer;
14 | }
--------------------------------------------------------------------------------
/web/src/layout/Footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
17 |
--------------------------------------------------------------------------------
/web/src/layout/PcNavMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
28 |
29 |
56 |
87 |
--------------------------------------------------------------------------------
/web/src/layout/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
35 |
59 |
--------------------------------------------------------------------------------
/web/src/layout/sider/archives/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
28 |
63 |
--------------------------------------------------------------------------------
/web/src/layout/sider/categories/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
30 |
62 |
--------------------------------------------------------------------------------
/web/src/layout/sider/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
29 |
41 |
--------------------------------------------------------------------------------
/web/src/layout/sider/newestArticles/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
最新文章
4 |
5 | -
6 | »
7 | {{ item.title }}
8 |
9 |
10 |
11 |
12 |
13 |
28 |
54 |
--------------------------------------------------------------------------------
/web/src/layout/sider/profile/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
54 |
--------------------------------------------------------------------------------
/web/src/layout/sider/style.scss:
--------------------------------------------------------------------------------
1 | .sider-item-common {
2 | background: white;
3 | padding: 20px;
4 | margin-bottom: 20px;
5 | }
--------------------------------------------------------------------------------
/web/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 store from './store'
7 | import { Button, Input, Pagination, Dropdown, DropdownMenu, DropdownItem, Form, FormItem, Radio, RadioGroup, Dialog, Message } from 'element-ui'
8 | import 'font-awesome/scss/font-awesome.scss'
9 | import './index.css'
10 | import '../theme/index.css'
11 | import customCompoents from '@/components/index'
12 | Vue.prototype.$ELEMENT = { size: 'medium' }
13 | Vue.use(Button)
14 | Vue.use(Input)
15 | Vue.use(Pagination)
16 | Vue.use(Dropdown)
17 | Vue.use(DropdownMenu)
18 | Vue.use(DropdownItem)
19 | Vue.use(Form)
20 | Vue.use(FormItem)
21 | Vue.use(Radio)
22 | Vue.use(RadioGroup)
23 | Vue.use(Dialog)
24 | Vue.use(customCompoents)
25 | Vue.config.productionTip = false
26 | Vue.prototype.$message = Message;
27 | new Vue({
28 | el: '#app',
29 | router,
30 | store,
31 | components: { App },
32 | template: ''
33 | })
34 |
--------------------------------------------------------------------------------
/web/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 |
4 | Vue.use(Router)
5 | const router = new Router({
6 | mode: 'history',
7 | routes: [
8 | {
9 | path: '/',
10 | name: 'Home',
11 | redirect: '/',
12 | component: () => import('@/layout/index.vue'),
13 | children: [
14 | {
15 | path: '/',
16 | name: 'ArticlesList',
17 | component: () =>
18 | import('@/views/articles/list')
19 | },
20 | {
21 | path: '/article/detail/:id',
22 | name: 'Detail',
23 | component: () =>
24 | import('@/views/articles/detail')
25 | },
26 | {
27 | path: '/article/keywords',
28 | name: 'SearchResult',
29 | component: () =>
30 | import('@/views/articles/searchResult')
31 | },
32 | {
33 | path: '/about',
34 | name: 'AboutMe',
35 | component: () =>
36 | import('@/views/about/about')
37 | },
38 | {
39 | path: '/tags',
40 | name: 'Tags',
41 | component: () =>
42 | import('@/views/tags/tags')
43 | },
44 | {
45 | path: '/tags/:id',
46 | name: 'TagsItem',
47 | component: () =>
48 | import('@/views/tags/tagsItem')
49 | },
50 | {
51 | path: '/categories',
52 | name: 'Categories',
53 | component: () =>
54 | import('@/views/categories/categories')
55 | },
56 | {
57 | path: '/categories/:id',
58 | name: 'CategoryItem',
59 | component: () =>
60 | import('@/views/categories/categoryItem')
61 | },
62 | {
63 | path: '/archives',
64 | name: 'Archives',
65 | component: () =>
66 | import('@/views/archives/archives')
67 | },
68 | {
69 | path: '/archive/:timeline',
70 | name: 'ArchiveTimeline',
71 | component: () =>
72 | import('@/views/archives/archiveTimeRange')
73 | }
74 | ]
75 | }
76 | ]
77 | })
78 |
79 | export default router
80 |
--------------------------------------------------------------------------------
/web/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import home from './modules/home'
4 | import category from './modules/category'
5 | import detail from './modules/detail'
6 | import archives from './modules/archives'
7 | import signup from './modules/signup'
8 | import user from './modules/user'
9 |
10 | Vue.use(Vuex)
11 |
12 | export default new Vuex.Store({
13 | modules: {
14 | home,
15 | category,
16 | detail,
17 | archives,
18 | signup,
19 | user
20 | }
21 | })
22 |
23 |
--------------------------------------------------------------------------------
/web/src/store/modules/archives.js:
--------------------------------------------------------------------------------
1 | import { getArticlesByArchives } from '@/api/article'
2 | const category = {
3 | state: {
4 | archivesArticle: [],
5 | total: 0,
6 | pageSize: 0,
7 | pageLimit: 0,
8 | from: -1
9 | },
10 | mutations: {
11 | SAVE_ARCHIVES_ARTICLE: (state, data) => {
12 | const { articleList, total, pageSize, pageLimit } = data
13 | state.archivesArticle = articleList
14 | state.total = total
15 | state.pageSize = pageSize
16 | state.pageLimit = pageLimit
17 | },
18 | SAVE_ARCHIVES_FROM: (state, from) => {
19 | state.from = from
20 | }
21 | },
22 | actions: {
23 | GET_ARTICLE_BY_ARCHIVES: ({ commit, dispatch, state }, data) => {
24 | const { timeline, pageSize, pageLimit } = data
25 | return new Promise((resolve, reject) => {
26 | getArticlesByArchives(timeline, pageSize, pageLimit)
27 | .then(response => {
28 | const data = response.data
29 | if (data.code) {
30 | reject(response)
31 | } else {
32 | commit('SAVE_ARCHIVES_ARTICLE', {
33 | articleList: data.data.article,
34 | total: data.data.count,
35 | pageSize,
36 | pageLimit
37 | })
38 | resolve(response)
39 | }
40 | })
41 | .catch(error => {
42 | reject(error)
43 | })
44 | })
45 | },
46 | },
47 | }
48 |
49 | export default category
50 |
--------------------------------------------------------------------------------
/web/src/store/modules/detail.js:
--------------------------------------------------------------------------------
1 | import { getArticleDetail } from '@/api/article'
2 |
3 | const detail = {
4 | state: {
5 | detail: '',
6 | },
7 | mutations: {
8 | SAVE_ARTICLE_DETAIL: (state, detail) => {
9 | state.detail = detail
10 | },
11 | },
12 | actions: {
13 | FETCH_ARTICLE_DETAIL: ({ commit, dispatch, state }, id) => {
14 | return new Promise((resolve, reject) => {
15 | getArticleDetail(id)
16 | .then(response => {
17 | const data = response.data
18 | if (data.code) {
19 | reject(response)
20 | } else {
21 | commit('SAVE_ARTICLE_DETAIL', data.data)
22 | resolve(response)
23 | }
24 | })
25 | .catch(error => {
26 | reject(error)
27 | })
28 | })
29 | },
30 | },
31 | }
32 |
33 | export default detail
34 |
--------------------------------------------------------------------------------
/web/src/store/modules/signup.js:
--------------------------------------------------------------------------------
1 | // import { generateAuthCode } from '@/api/user'
2 |
3 | const signup = {
4 | state: {},
5 | mutations: {
6 | // SAVE_ARTICLE_DETAIL: (state, detail) => {
7 | // state.detail = detail
8 | // },
9 | },
10 | actions: {
11 | // GENERATE_AUTH_CODE: ({commit, dispatch, state}) => {
12 | // return new Promise((resolve, reject) => {
13 | // generateAuthCode()
14 | // .then(response => {
15 | // const data = response.data
16 | // if (data.code) {
17 | // reject(response)
18 | // } else {
19 | // resolve(response)
20 | // }
21 | // })
22 | // .catch(error => {
23 | // reject(error)
24 | // })
25 | // })
26 | // },
27 | },
28 | }
29 |
30 | export default signup
31 |
--------------------------------------------------------------------------------
/web/src/store/modules/user.js:
--------------------------------------------------------------------------------
1 | import { login, getUserInfo } from '@/api/user'
2 |
3 |
4 | const loginStore = {
5 | state: {
6 | userInfo: null
7 | },
8 | mutations: {
9 | SAVE_USER_INFO: (state, userInfo) => {
10 | state.userInfo = userInfo
11 | }
12 | },
13 | actions: {
14 | LOGIN: ({ commit, dispatch, state }, { email, password }) => {
15 | return new Promise((resolve, reject) => {
16 | login({ email, password })
17 | .then(response => {
18 | const data = response.data
19 | if (data.code === 0) {
20 | localStorage.setItem('token', data.data.token)
21 | resolve(response)
22 | } else {
23 | resolve(response)
24 | }
25 | })
26 | .catch(error => {
27 | reject(error)
28 | })
29 | }
30 | )
31 | },
32 | GET_USER_INFO: ({ commit }) => {
33 | return new Promise((resolve, reject) => {
34 | getUserInfo()
35 | .then(response => {
36 | const data = response.data
37 | if (data.code === 0) {
38 | commit('SAVE_USER_INFO', data.data)
39 | resolve(response)
40 | } else {
41 | resolve(response)
42 | }
43 | })
44 | .catch(error => {
45 | reject(error)
46 | })
47 | }
48 | )
49 | },
50 | },
51 |
52 | }
53 |
54 | export default loginStore
55 |
--------------------------------------------------------------------------------
/web/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 add0 = m => {
10 | return m < 10 ? '0' + m : m
11 | }
12 | const format = timestamps => {
13 | const time = new Date(parseInt(timestamps / 1000))
14 | const y = time.getFullYear()
15 | const m = time.getMonth() + 1
16 | const d = time.getDate()
17 | let week = time.getDay()
18 | switch (week) {
19 | case 0:
20 | week = '日'
21 | break
22 | case 1:
23 | week = '一'
24 | break
25 | case 2:
26 | week = '二'
27 | break
28 | case 3:
29 | week = '三'
30 | break
31 | case 4:
32 | week = '四'
33 | break
34 | case 5:
35 | week = '五'
36 | break
37 | case 6:
38 | week = '六'
39 | break
40 | }
41 | return `${y}年 ${add0(m)}月 ${add0(d)}日, 星期${week}`
42 | }
43 | const between = Date.now() / 1000 - Number(time / 1000)
44 | if (between < 3600) {
45 | return pluralize(~~(between / 60), ' minute') + 'ago'
46 | } else if (between < 86400) {
47 | return pluralize(~~(between / 3600), ' hour') + 'ago'
48 | } else {
49 | return format(time * 1000)
50 | // return pluralize(~~(between / 86400), ' day')
51 | }
52 | }
53 |
54 | function pluralize(time, label) {
55 | if (time === 1) {
56 | return time + label
57 | }
58 | return time + label + 's'
59 | }
60 |
--------------------------------------------------------------------------------
/web/src/utils/formatTimestamp.js:
--------------------------------------------------------------------------------
1 | const add0 = (m) => {
2 | return m < 10 ? '0' + m : m
3 | }
4 | const format = (timestamps, short = true) => {
5 | const time = new Date(parseInt(timestamps) * 1000)
6 | const y = time.getFullYear()
7 | const m = time.getMonth() + 1
8 | const d = time.getDate()
9 | const h = time.getHours()
10 | const mm = time.getMinutes()
11 | const s = time.getSeconds()
12 | if (short) {
13 | return `${y}-${add0(m)}-${add0(d)}`
14 | } else {
15 | return `${y}-${add0(m)}-${add0(d)} ${add0(h)}:${add0(mm)}:${add0(s)}`
16 | }
17 | }
18 | export default format
19 |
--------------------------------------------------------------------------------
/web/src/utils/formatYearAndDate.js:
--------------------------------------------------------------------------------
1 | const add0 = (m) => {
2 | return m < 10 ? '0' + m : m
3 | }
4 | const format = (timestamps) => {
5 | const time = new Date(parseInt(timestamps) * 1000)
6 | const y = time.getFullYear()
7 | const m = time.getMonth() + 1
8 | const d = time.getDate()
9 | return [ `${y}`, add0(m) + '-' + add0(d) ]
10 | }
11 | export default format
12 |
--------------------------------------------------------------------------------
/web/src/views/about/about.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
目前在工作中使用的技术栈主要包括:
6 |
react、react native、vue、nodejs、koa、egg、mongo
7 |
8 |
9 |
10 |
11 |
22 |
62 |
--------------------------------------------------------------------------------
/web/src/views/tags/components/shuffle.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
38 |
75 |
--------------------------------------------------------------------------------
/web/src/views/tags/tags.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
61 |
62 |
101 |
--------------------------------------------------------------------------------
/web/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 |
--------------------------------------------------------------------------------
/web/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 |
--------------------------------------------------------------------------------
/web/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 |
--------------------------------------------------------------------------------
/web/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 |
--------------------------------------------------------------------------------
/web/test/unit/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "globals": {
6 | "expect": true,
7 | "sinon": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/web/test/unit/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | Vue.config.productionTip = false
4 |
5 | // require all test files (files that ends with .spec.js)
6 | const testsContext = require.context('./specs', true, /\.spec$/)
7 | testsContext.keys().forEach(testsContext)
8 |
9 | // require all src files except main.js for coverage.
10 | // you can also change this to match only the subset of files that
11 | // you want coverage for.
12 | const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/)
13 | srcContext.keys().forEach(srcContext)
14 |
--------------------------------------------------------------------------------
/web/test/unit/karma.conf.js:
--------------------------------------------------------------------------------
1 | // This is a karma config file. For more details see
2 | // http://karma-runner.github.io/0.13/config/configuration-file.html
3 | // we are also using it with karma-webpack
4 | // https://github.com/webpack/karma-webpack
5 |
6 | var webpackConfig = require('../../build/webpack.test.conf')
7 |
8 | module.exports = function karmaConfig (config) {
9 | config.set({
10 | // to run in additional browsers:
11 | // 1. install corresponding karma launcher
12 | // http://karma-runner.github.io/0.13/config/browsers.html
13 | // 2. add it to the `browsers` array below.
14 | browsers: ['PhantomJS'],
15 | frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],
16 | reporters: ['spec', 'coverage'],
17 | files: ['./index.js'],
18 | preprocessors: {
19 | './index.js': ['webpack', 'sourcemap']
20 | },
21 | webpack: webpackConfig,
22 | webpackMiddleware: {
23 | noInfo: true
24 | },
25 | coverageReporter: {
26 | dir: './coverage',
27 | reporters: [
28 | { type: 'lcov', subdir: '.' },
29 | { type: 'text-summary' }
30 | ]
31 | }
32 | })
33 | }
34 |
--------------------------------------------------------------------------------
/web/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 | .to.equal('Welcome to Your Vue.js App')
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/web/theme/alert.css:
--------------------------------------------------------------------------------
1 | .el-alert{width:100%;padding:8px 16px;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:4px;position:relative;background-color:#FFF;overflow:hidden;opacity:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-transition:opacity .2s;transition:opacity .2s}.el-alert.is-light .el-alert__closebtn{color:#C0C4CC}.el-alert.is-dark .el-alert__closebtn,.el-alert.is-dark .el-alert__description{color:#FFF}.el-alert.is-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-alert--success.is-light{background-color:#f0f9eb;color:#67C23A}.el-alert--success.is-light .el-alert__description{color:#67C23A}.el-alert--success.is-dark{background-color:#67C23A;color:#FFF}.el-alert--info.is-light{background-color:#f4f4f5;color:#909399}.el-alert--info.is-dark{background-color:#909399;color:#FFF}.el-alert--info .el-alert__description{color:#909399}.el-alert--warning.is-light{background-color:#fdf6ec;color:#E6A23C}.el-alert--warning.is-light .el-alert__description{color:#E6A23C}.el-alert--warning.is-dark{background-color:#E6A23C;color:#FFF}.el-alert--error.is-light{background-color:#fef0f0;color:#F56C6C}.el-alert--error.is-light .el-alert__description{color:#F56C6C}.el-alert--error.is-dark{background-color:#F56C6C;color:#FFF}.el-alert__content{display:table-cell;padding:0 8px}.el-alert__icon{font-size:16px;width:16px}.el-alert__icon.is-big{font-size:28px;width:28px}.el-alert__title{font-size:13px;line-height:18px}.el-alert__title.is-bold{font-weight:700}.el-alert .el-alert__description{font-size:12px;margin:5px 0 0}.el-alert__closebtn{font-size:12px;opacity:1;position:absolute;top:12px;right:15px;cursor:pointer}.el-alert__closebtn.is-customed{font-style:normal;font-size:13px;top:9px}.el-alert-fade-enter,.el-alert-fade-leave-active{opacity:0}
--------------------------------------------------------------------------------
/web/theme/aside.css:
--------------------------------------------------------------------------------
1 | .el-aside{overflow:auto;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-flex-negative:0;flex-shrink:0}
--------------------------------------------------------------------------------
/web/theme/avatar.css:
--------------------------------------------------------------------------------
1 | .el-avatar{display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;overflow:hidden;color:#fff;background:#C0C4CC;width:40px;height:40px;line-height:40px;font-size:14px}.el-avatar>img{display:block;height:100%;vertical-align:middle}.el-avatar--circle{border-radius:50%}.el-avatar--square{border-radius:4px}.el-avatar--icon{font-size:18px}.el-avatar--large{width:40px;height:40px;line-height:40px}.el-avatar--medium{width:36px;height:36px;line-height:36px}.el-avatar--small{width:28px;height:28px;line-height:28px}
--------------------------------------------------------------------------------
/web/theme/backtop.css:
--------------------------------------------------------------------------------
1 | .el-backtop{position:fixed;background-color:#FFF;width:40px;height:40px;border-radius:50%;color:#006666;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;font-size:20px;-webkit-box-shadow:0 0 6px rgba(0,0,0,.12);box-shadow:0 0 6px rgba(0,0,0,.12);cursor:pointer;z-index:5}.el-backtop:hover{background-color:#F2F6FC}
--------------------------------------------------------------------------------
/web/theme/badge.css:
--------------------------------------------------------------------------------
1 | .el-badge{position:relative;vertical-align:middle;display:inline-block}.el-badge__content{background-color:#F56C6C;border-radius:10px;color:#FFF;display:inline-block;font-size:12px;height:18px;line-height:18px;padding:0 6px;text-align:center;white-space:nowrap;border:1px solid #FFF}.el-badge__content.is-fixed{position:absolute;top:0;right:10px;-webkit-transform:translateY(-50%) translateX(100%);transform:translateY(-50%) translateX(100%)}.el-badge__content.is-fixed.is-dot{right:5px}.el-badge__content.is-dot{height:8px;width:8px;padding:0;right:0;border-radius:50%}.el-badge__content--primary{background-color:#006666}.el-badge__content--success{background-color:#67C23A}.el-badge__content--warning{background-color:#E6A23C}.el-badge__content--info{background-color:#909399}.el-badge__content--danger{background-color:#F56C6C}
--------------------------------------------------------------------------------
/web/theme/breadcrumb-item.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/theme/breadcrumb-item.css
--------------------------------------------------------------------------------
/web/theme/breadcrumb.css:
--------------------------------------------------------------------------------
1 | .el-breadcrumb{font-size:14px;line-height:1}.el-breadcrumb::after,.el-breadcrumb::before{display:table;content:""}.el-breadcrumb::after{clear:both}.el-breadcrumb__separator{margin:0 9px;font-weight:700;color:#C0C4CC}.el-breadcrumb__separator[class*=icon]{margin:0 6px;font-weight:400}.el-breadcrumb__item{float:left}.el-breadcrumb__inner{color:#606266}.el-breadcrumb__inner a,.el-breadcrumb__inner.is-link{font-weight:700;text-decoration:none;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1);color:#303133}.el-breadcrumb__inner a:hover,.el-breadcrumb__inner.is-link:hover{color:#006666;cursor:pointer}.el-breadcrumb__item:last-child .el-breadcrumb__inner,.el-breadcrumb__item:last-child .el-breadcrumb__inner a,.el-breadcrumb__item:last-child .el-breadcrumb__inner a:hover,.el-breadcrumb__item:last-child .el-breadcrumb__inner:hover{font-weight:400;color:#606266;cursor:text}.el-breadcrumb__item:last-child .el-breadcrumb__separator{display:none}
--------------------------------------------------------------------------------
/web/theme/button-group.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/theme/button-group.css
--------------------------------------------------------------------------------
/web/theme/card.css:
--------------------------------------------------------------------------------
1 | .el-card{border-radius:4px;border:1px solid #EBEEF5;background-color:#FFF;overflow:hidden;color:#303133;-webkit-transition:.3s;transition:.3s}.el-card.is-always-shadow,.el-card.is-hover-shadow:focus,.el-card.is-hover-shadow:hover{-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-card__header{padding:18px 20px;border-bottom:1px solid #EBEEF5;-webkit-box-sizing:border-box;box-sizing:border-box}.el-card__body{padding:20px}
--------------------------------------------------------------------------------
/web/theme/carousel-item.css:
--------------------------------------------------------------------------------
1 | .el-carousel__item,.el-carousel__mask{position:absolute;height:100%;top:0;left:0}.el-carousel__item{width:100%;display:inline-block;overflow:hidden;z-index:0}.el-carousel__item.is-active{z-index:2}.el-carousel__item.is-animating{-webkit-transition:-webkit-transform .4s ease-in-out;transition:-webkit-transform .4s ease-in-out;transition:transform .4s ease-in-out;transition:transform .4s ease-in-out,-webkit-transform .4s ease-in-out}.el-carousel__item--card{width:50%;-webkit-transition:-webkit-transform .4s ease-in-out;transition:-webkit-transform .4s ease-in-out;transition:transform .4s ease-in-out;transition:transform .4s ease-in-out,-webkit-transform .4s ease-in-out}.el-carousel__item--card.is-in-stage{cursor:pointer;z-index:1}.el-carousel__item--card.is-in-stage.is-hover .el-carousel__mask,.el-carousel__item--card.is-in-stage:hover .el-carousel__mask{opacity:.12}.el-carousel__item--card.is-active{z-index:2}.el-carousel__mask{width:100%;background-color:#FFF;opacity:.24;-webkit-transition:.2s;transition:.2s}
--------------------------------------------------------------------------------
/web/theme/carousel.css:
--------------------------------------------------------------------------------
1 | .el-carousel{position:relative}.el-carousel--horizontal{overflow-x:hidden}.el-carousel--vertical{overflow-y:hidden}.el-carousel__container{position:relative;height:300px}.el-carousel__arrow{border:none;outline:0;padding:0;margin:0;height:36px;width:36px;cursor:pointer;-webkit-transition:.3s;transition:.3s;border-radius:50%;background-color:rgba(31,45,61,.11);color:#FFF;position:absolute;top:50%;z-index:10;-webkit-transform:translateY(-50%);transform:translateY(-50%);text-align:center;font-size:12px}.el-carousel__arrow--left{left:16px}.el-carousel__arrow--right{right:16px}.el-carousel__arrow:hover{background-color:rgba(31,45,61,.23)}.el-carousel__arrow i{cursor:pointer}.el-carousel__indicators{position:absolute;list-style:none;margin:0;padding:0;z-index:2}.el-carousel__indicators--horizontal{bottom:0;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.el-carousel__indicators--vertical{right:0;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.el-carousel__indicators--outside{bottom:26px;text-align:center;position:static;-webkit-transform:none;transform:none}.el-carousel__indicators--outside .el-carousel__indicator:hover button{opacity:.64}.el-carousel__indicators--outside button{background-color:#C0C4CC;opacity:.24}.el-carousel__indicators--labels{left:0;right:0;-webkit-transform:none;transform:none;text-align:center}.el-carousel__indicators--labels .el-carousel__button{height:auto;width:auto;padding:2px 18px;font-size:12px}.el-carousel__indicators--labels .el-carousel__indicator{padding:6px 4px}.el-carousel__indicator{background-color:transparent;cursor:pointer}.el-carousel__indicator:hover button{opacity:.72}.el-carousel__indicator--horizontal{display:inline-block;padding:12px 4px}.el-carousel__indicator--vertical{padding:4px 12px}.el-carousel__indicator--vertical .el-carousel__button{width:2px;height:15px}.el-carousel__indicator.is-active button{opacity:1}.el-carousel__button{display:block;opacity:.48;width:30px;height:2px;background-color:#FFF;border:none;outline:0;padding:0;margin:0;cursor:pointer;-webkit-transition:.3s;transition:.3s}.carousel-arrow-left-enter,.carousel-arrow-left-leave-active{-webkit-transform:translateY(-50%) translateX(-10px);transform:translateY(-50%) translateX(-10px);opacity:0}.carousel-arrow-right-enter,.carousel-arrow-right-leave-active{-webkit-transform:translateY(-50%) translateX(10px);transform:translateY(-50%) translateX(10px);opacity:0}
--------------------------------------------------------------------------------
/web/theme/checkbox-button.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/theme/checkbox-button.css
--------------------------------------------------------------------------------
/web/theme/checkbox-group.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/theme/checkbox-group.css
--------------------------------------------------------------------------------
/web/theme/collapse-item.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/theme/collapse-item.css
--------------------------------------------------------------------------------
/web/theme/container.css:
--------------------------------------------------------------------------------
1 | .el-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-preferred-size:auto;flex-basis:auto;-webkit-box-sizing:border-box;box-sizing:border-box;min-width:0}.el-container.is-vertical{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}
--------------------------------------------------------------------------------
/web/theme/dialog.css:
--------------------------------------------------------------------------------
1 | .v-modal-enter{-webkit-animation:v-modal-in .2s ease;animation:v-modal-in .2s ease}.v-modal-leave{-webkit-animation:v-modal-out .2s ease forwards;animation:v-modal-out .2s ease forwards}@-webkit-keyframes v-modal-in{0%{opacity:0}}@keyframes v-modal-in{0%{opacity:0}}@-webkit-keyframes v-modal-out{100%{opacity:0}}@keyframes v-modal-out{100%{opacity:0}}.v-modal{position:fixed;left:0;top:0;width:100%;height:100%;opacity:.5;background:#000}.el-popup-parent--hidden{overflow:hidden}.el-dialog{position:relative;margin:0 auto 50px;background:#FFF;border-radius:2px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,.3);box-shadow:0 1px 3px rgba(0,0,0,.3);-webkit-box-sizing:border-box;box-sizing:border-box;width:50%}.el-dialog.is-fullscreen{width:100%;margin-top:0;margin-bottom:0;height:100%;overflow:auto}.el-dialog__wrapper{position:fixed;top:0;right:0;bottom:0;left:0;overflow:auto;margin:0}.el-dialog__header{padding:20px 20px 10px}.el-dialog__headerbtn{position:absolute;top:20px;right:20px;padding:0;background:0 0;border:none;outline:0;cursor:pointer;font-size:16px}.el-dialog__headerbtn .el-dialog__close{color:#909399}.el-dialog__headerbtn:focus .el-dialog__close,.el-dialog__headerbtn:hover .el-dialog__close{color:#006666}.el-dialog__title{line-height:24px;font-size:18px;color:#303133}.el-dialog__body{padding:30px 20px;color:#606266;font-size:14px;word-break:break-all}.el-dialog__footer{padding:10px 20px 20px;text-align:right;-webkit-box-sizing:border-box;box-sizing:border-box}.el-dialog--center{text-align:center}.el-dialog--center .el-dialog__body{text-align:initial;padding:25px 25px 30px}.el-dialog--center .el-dialog__footer{text-align:inherit}.dialog-fade-enter-active{-webkit-animation:dialog-fade-in .3s;animation:dialog-fade-in .3s}.dialog-fade-leave-active{-webkit-animation:dialog-fade-out .3s;animation:dialog-fade-out .3s}@-webkit-keyframes dialog-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@keyframes dialog-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}100%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@-webkit-keyframes dialog-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}@keyframes dialog-fade-out{0%{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}100%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}
--------------------------------------------------------------------------------
/web/theme/display.css:
--------------------------------------------------------------------------------
1 | @media only screen and (max-width:767px){.hidden-xs-only{display:none!important}}@media only screen and (min-width:768px){.hidden-sm-and-up{display:none!important}}@media only screen and (min-width:768px) and (max-width:991px){.hidden-sm-only{display:none!important}}@media only screen and (max-width:991px){.hidden-sm-and-down{display:none!important}}@media only screen and (min-width:992px){.hidden-md-and-up{display:none!important}}@media only screen and (min-width:992px) and (max-width:1199px){.hidden-md-only{display:none!important}}@media only screen and (max-width:1199px){.hidden-md-and-down{display:none!important}}@media only screen and (min-width:1200px){.hidden-lg-and-up{display:none!important}}@media only screen and (min-width:1200px) and (max-width:1919px){.hidden-lg-only{display:none!important}}@media only screen and (max-width:1919px){.hidden-lg-and-down{display:none!important}}@media only screen and (min-width:1920px){.hidden-xl-only{display:none!important}}
--------------------------------------------------------------------------------
/web/theme/divider.css:
--------------------------------------------------------------------------------
1 | .el-divider{background-color:#DCDFE6;position:relative}.el-divider--horizontal{display:block;height:1px;width:100%;margin:24px 0}.el-divider--vertical{display:inline-block;width:1px;height:1em;margin:0 8px;vertical-align:middle;position:relative}.el-divider__text{position:absolute;background-color:#FFF;padding:0 20px;font-weight:500;color:#303133;font-size:14px}.el-divider__text.is-left{left:20px;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.el-divider__text.is-center{left:50%;-webkit-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%)}.el-divider__text.is-right{right:20px;-webkit-transform:translateY(-50%);transform:translateY(-50%)}
--------------------------------------------------------------------------------
/web/theme/dropdown-item.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/theme/dropdown-item.css
--------------------------------------------------------------------------------
/web/theme/dropdown-menu.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/theme/dropdown-menu.css
--------------------------------------------------------------------------------
/web/theme/fonts/element-icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/theme/fonts/element-icons.ttf
--------------------------------------------------------------------------------
/web/theme/fonts/element-icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/theme/fonts/element-icons.woff
--------------------------------------------------------------------------------
/web/theme/footer.css:
--------------------------------------------------------------------------------
1 | .el-footer{padding:0 20px;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-flex-negative:0;flex-shrink:0}
--------------------------------------------------------------------------------
/web/theme/form-item.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/theme/form-item.css
--------------------------------------------------------------------------------
/web/theme/header.css:
--------------------------------------------------------------------------------
1 | .el-header{padding:0 20px;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-flex-negative:0;flex-shrink:0}
--------------------------------------------------------------------------------
/web/theme/infinite-scroll.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/theme/infinite-scroll.css
--------------------------------------------------------------------------------
/web/theme/infiniteScroll.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/theme/infiniteScroll.css
--------------------------------------------------------------------------------
/web/theme/link.css:
--------------------------------------------------------------------------------
1 | .el-link{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;vertical-align:middle;position:relative;text-decoration:none;outline:0;cursor:pointer;padding:0;font-size:14px;font-weight:500}.el-link.is-underline:hover:after{content:"";position:absolute;left:0;right:0;height:0;bottom:0;border-bottom:1px solid #006666}.el-link.el-link--default:after,.el-link.el-link--primary.is-underline:hover:after,.el-link.el-link--primary:after{border-color:#006666}.el-link.is-disabled{cursor:not-allowed}.el-link [class*=el-icon-]+span{margin-left:5px}.el-link.el-link--default{color:#606266}.el-link.el-link--default:hover{color:#006666}.el-link.el-link--default.is-disabled{color:#C0C4CC}.el-link.el-link--primary{color:#006666}.el-link.el-link--primary:hover{color:rgb(51, 133, 133)}.el-link.el-link--primary.is-disabled{color:rgb(128, 179, 179)}.el-link.el-link--danger.is-underline:hover:after,.el-link.el-link--danger:after{border-color:#F56C6C}.el-link.el-link--danger{color:#F56C6C}.el-link.el-link--danger:hover{color:#f78989}.el-link.el-link--danger.is-disabled{color:#fab6b6}.el-link.el-link--success.is-underline:hover:after,.el-link.el-link--success:after{border-color:#67C23A}.el-link.el-link--success{color:#67C23A}.el-link.el-link--success:hover{color:#85ce61}.el-link.el-link--success.is-disabled{color:#b3e19d}.el-link.el-link--warning.is-underline:hover:after,.el-link.el-link--warning:after{border-color:#E6A23C}.el-link.el-link--warning{color:#E6A23C}.el-link.el-link--warning:hover{color:#ebb563}.el-link.el-link--warning.is-disabled{color:#f3d19e}.el-link.el-link--info.is-underline:hover:after,.el-link.el-link--info:after{border-color:#909399}.el-link.el-link--info{color:#909399}.el-link.el-link--info:hover{color:#a6a9ad}.el-link.el-link--info.is-disabled{color:#c8c9cc}
--------------------------------------------------------------------------------
/web/theme/loading.css:
--------------------------------------------------------------------------------
1 | .el-loading-parent--relative{position:relative!important}.el-loading-parent--hidden{overflow:hidden!important}.el-loading-mask{position:absolute;z-index:2000;background-color:rgba(255,255,255,.9);margin:0;top:0;right:0;bottom:0;left:0;-webkit-transition:opacity .3s;transition:opacity .3s}.el-loading-mask.is-fullscreen{position:fixed}.el-loading-mask.is-fullscreen .el-loading-spinner{margin-top:-25px}.el-loading-mask.is-fullscreen .el-loading-spinner .circular{height:50px;width:50px}.el-loading-spinner{top:50%;margin-top:-21px;width:100%;text-align:center;position:absolute}.el-loading-spinner .el-loading-text{color:#006666;margin:3px 0;font-size:14px}.el-loading-spinner .circular{height:42px;width:42px;-webkit-animation:loading-rotate 2s linear infinite;animation:loading-rotate 2s linear infinite}.el-loading-spinner .path{-webkit-animation:loading-dash 1.5s ease-in-out infinite;animation:loading-dash 1.5s ease-in-out infinite;stroke-dasharray:90,150;stroke-dashoffset:0;stroke-width:2;stroke:#006666;stroke-linecap:round}.el-loading-spinner i{color:#006666}.el-loading-fade-enter,.el-loading-fade-leave-active{opacity:0}@-webkit-keyframes loading-rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes loading-rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}100%{stroke-dasharray:90,150;stroke-dashoffset:-120px}}@keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}100%{stroke-dasharray:90,150;stroke-dashoffset:-120px}}
--------------------------------------------------------------------------------
/web/theme/main.css:
--------------------------------------------------------------------------------
1 | .el-main{display:block;-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-preferred-size:auto;flex-basis:auto;overflow:auto;-webkit-box-sizing:border-box;box-sizing:border-box;padding:20px}
--------------------------------------------------------------------------------
/web/theme/menu-item-group.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/theme/menu-item-group.css
--------------------------------------------------------------------------------
/web/theme/menu-item.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/theme/menu-item.css
--------------------------------------------------------------------------------
/web/theme/message.css:
--------------------------------------------------------------------------------
1 | .el-message__closeBtn:focus,.el-message__content:focus{outline-width:0}.el-message{min-width:380px;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:4px;border-width:1px;border-style:solid;border-color:#EBEEF5;position:fixed;left:50%;top:20px;-webkit-transform:translateX(-50%);transform:translateX(-50%);background-color:#edf2fc;-webkit-transition:opacity .3s,top .4s,-webkit-transform .4s;transition:opacity .3s,top .4s,-webkit-transform .4s;transition:opacity .3s,transform .4s,top .4s;transition:opacity .3s,transform .4s,top .4s,-webkit-transform .4s;overflow:hidden;padding:15px 15px 15px 20px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-message.is-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-message.is-closable .el-message__content{padding-right:16px}.el-message p{margin:0}.el-message--info .el-message__content{color:#909399}.el-message--success{background-color:#f0f9eb;border-color:#e1f3d8}.el-message--success .el-message__content{color:#67C23A}.el-message--warning{background-color:#fdf6ec;border-color:#faecd8}.el-message--warning .el-message__content{color:#E6A23C}.el-message--error{background-color:#fef0f0;border-color:#fde2e2}.el-message--error .el-message__content{color:#F56C6C}.el-message__icon{margin-right:10px}.el-message__content{padding:0;font-size:14px;line-height:1}.el-message__closeBtn{position:absolute;top:50%;right:15px;-webkit-transform:translateY(-50%);transform:translateY(-50%);cursor:pointer;color:#C0C4CC;font-size:16px}.el-message__closeBtn:hover{color:#909399}.el-message .el-icon-success{color:#67C23A}.el-message .el-icon-error{color:#F56C6C}.el-message .el-icon-info{color:#909399}.el-message .el-icon-warning{color:#E6A23C}.el-message-fade-enter,.el-message-fade-leave-active{opacity:0;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}
--------------------------------------------------------------------------------
/web/theme/notification.css:
--------------------------------------------------------------------------------
1 | .el-notification{display:-webkit-box;display:-ms-flexbox;display:flex;width:330px;padding:14px 26px 14px 13px;border-radius:8px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #EBEEF5;position:fixed;background-color:#FFF;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-transition:opacity .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;transition:opacity .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;transition:opacity .3s,transform .3s,left .3s,right .3s,top .4s,bottom .3s;transition:opacity .3s,transform .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;overflow:hidden}.el-notification.right{right:16px}.el-notification.left{left:16px}.el-notification__group{margin-left:13px;margin-right:8px}.el-notification__title{font-weight:700;font-size:16px;color:#303133;margin:0}.el-notification__content{font-size:14px;line-height:21px;margin:6px 0 0;color:#606266;text-align:justify}.el-notification__content p{margin:0}.el-notification__icon{height:24px;width:24px;font-size:24px}.el-notification__closeBtn{position:absolute;top:18px;right:15px;cursor:pointer;color:#909399;font-size:16px}.el-notification__closeBtn:hover{color:#606266}.el-notification .el-icon-success{color:#67C23A}.el-notification .el-icon-error{color:#F56C6C}.el-notification .el-icon-info{color:#909399}.el-notification .el-icon-warning{color:#E6A23C}.el-notification-fade-enter.right{right:0;-webkit-transform:translateX(100%);transform:translateX(100%)}.el-notification-fade-enter.left{left:0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}.el-notification-fade-leave-active{opacity:0}
--------------------------------------------------------------------------------
/web/theme/option-group.css:
--------------------------------------------------------------------------------
1 | .el-select-group{margin:0;padding:0}.el-select-group__wrap{position:relative;list-style:none;margin:0;padding:0}.el-select-group__wrap:not(:last-of-type){padding-bottom:24px}.el-select-group__wrap:not(:last-of-type)::after{content:'';position:absolute;display:block;left:20px;right:20px;bottom:12px;height:1px;background:#E4E7ED}.el-select-group__title{padding-left:20px;font-size:12px;color:#909399;line-height:30px}.el-select-group .el-select-dropdown__item{padding-left:20px}
--------------------------------------------------------------------------------
/web/theme/option.css:
--------------------------------------------------------------------------------
1 | .el-select-dropdown__item{font-size:14px;padding:0 20px;position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#606266;height:34px;line-height:34px;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer}.el-select-dropdown__item.is-disabled{color:#C0C4CC;cursor:not-allowed}.el-select-dropdown__item.is-disabled:hover{background-color:#FFF}.el-select-dropdown__item.hover,.el-select-dropdown__item:hover{background-color:#F5F7FA}.el-select-dropdown__item.selected{color:#006666;font-weight:700}
--------------------------------------------------------------------------------
/web/theme/page-header.css:
--------------------------------------------------------------------------------
1 | .el-page-header{display:-webkit-box;display:-ms-flexbox;display:flex;line-height:24px}.el-page-header__left{display:-webkit-box;display:-ms-flexbox;display:flex;cursor:pointer;margin-right:40px;position:relative}.el-page-header__left::after{content:"";position:absolute;width:1px;height:16px;right:-20px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);background-color:#DCDFE6}.el-page-header__left .el-icon-back{font-size:18px;margin-right:6px;-ms-flex-item-align:center;align-self:center}.el-page-header__title{font-size:14px;font-weight:500}.el-page-header__content{font-size:18px;color:#303133}
--------------------------------------------------------------------------------
/web/theme/popconfirm.css:
--------------------------------------------------------------------------------
1 | .el-popconfirm__main{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-popconfirm__icon{margin-right:5px}.el-popconfirm__action{text-align:right;margin:0}
--------------------------------------------------------------------------------
/web/theme/popover.css:
--------------------------------------------------------------------------------
1 | .el-popper .popper__arrow,.el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.el-popper .popper__arrow::after{content:" ";border-width:6px}.el-popper[x-placement^=top]{margin-bottom:12px}.el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#EBEEF5;border-bottom-width:0}.el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#FFF;border-bottom-width:0}.el-popper[x-placement^=bottom]{margin-top:12px}.el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#EBEEF5}.el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#FFF}.el-popper[x-placement^=right]{margin-left:12px}.el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#EBEEF5;border-left-width:0}.el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#FFF;border-left-width:0}.el-popper[x-placement^=left]{margin-right:12px}.el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#EBEEF5}.el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#FFF}.el-popover{position:absolute;background:#FFF;min-width:150px;border-radius:4px;border:1px solid #EBEEF5;padding:12px;z-index:2000;color:#606266;line-height:1.4;text-align:justify;font-size:14px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);word-break:break-all}.el-popover--plain{padding:18px 20px}.el-popover__title{color:#303133;font-size:16px;line-height:1;margin-bottom:12px}.el-popover:focus,.el-popover:focus:active,.el-popover__reference:focus:hover,.el-popover__reference:focus:not(.focusing){outline-width:0}
--------------------------------------------------------------------------------
/web/theme/popper.css:
--------------------------------------------------------------------------------
1 | .el-popper .popper__arrow,.el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.el-popper .popper__arrow::after{content:" ";border-width:6px}.el-popper[x-placement^=top]{margin-bottom:12px}.el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#EBEEF5;border-bottom-width:0}.el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#FFF;border-bottom-width:0}.el-popper[x-placement^=bottom]{margin-top:12px}.el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#EBEEF5}.el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#FFF}.el-popper[x-placement^=right]{margin-left:12px}.el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#EBEEF5;border-left-width:0}.el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#FFF;border-left-width:0}.el-popper[x-placement^=left]{margin-right:12px}.el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#EBEEF5}.el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#FFF}
--------------------------------------------------------------------------------
/web/theme/progress.css:
--------------------------------------------------------------------------------
1 | .el-progress{position:relative;line-height:1}.el-progress__text{font-size:14px;color:#606266;display:inline-block;vertical-align:middle;margin-left:10px;line-height:1}.el-progress__text i{vertical-align:middle;display:block}.el-progress--circle,.el-progress--dashboard{display:inline-block}.el-progress--circle .el-progress__text,.el-progress--dashboard .el-progress__text{position:absolute;top:50%;left:0;width:100%;text-align:center;margin:0;-webkit-transform:translate(0,-50%);transform:translate(0,-50%)}.el-progress--circle .el-progress__text i,.el-progress--dashboard .el-progress__text i{vertical-align:middle;display:inline-block}.el-progress--without-text .el-progress__text{display:none}.el-progress--without-text .el-progress-bar{padding-right:0;margin-right:0;display:block}.el-progress-bar,.el-progress-bar__inner::after,.el-progress-bar__innerText{display:inline-block;vertical-align:middle}.el-progress--text-inside .el-progress-bar{padding-right:0;margin-right:0}.el-progress.is-success .el-progress-bar__inner{background-color:#67C23A}.el-progress.is-success .el-progress__text{color:#67C23A}.el-progress.is-warning .el-progress-bar__inner{background-color:#E6A23C}.el-progress.is-warning .el-progress__text{color:#E6A23C}.el-progress.is-exception .el-progress-bar__inner{background-color:#F56C6C}.el-progress.is-exception .el-progress__text{color:#F56C6C}.el-progress-bar{padding-right:50px;width:100%;margin-right:-55px;-webkit-box-sizing:border-box;box-sizing:border-box}.el-progress-bar__outer{height:6px;border-radius:100px;background-color:#EBEEF5;overflow:hidden;position:relative;vertical-align:middle}.el-progress-bar__inner{position:absolute;left:0;top:0;height:100%;background-color:#006666;text-align:right;border-radius:100px;line-height:1;white-space:nowrap;-webkit-transition:width .6s ease;transition:width .6s ease}.el-progress-bar__inner::after{content:"";height:100%}.el-progress-bar__innerText{color:#FFF;font-size:12px;margin:0 5px}@-webkit-keyframes progress{0%{background-position:0 0}100%{background-position:32px 0}}@keyframes progress{0%{background-position:0 0}100%{background-position:32px 0}}
--------------------------------------------------------------------------------
/web/theme/radio-button.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";.el-radio-button,.el-radio-button__inner{display:inline-block;position:relative;outline:0}.el-radio-button__inner{line-height:1;white-space:nowrap;vertical-align:middle;background:#FFF;border:1px solid #DCDFE6;font-weight:500;border-left:0;color:#606266;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;cursor:pointer;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);padding:12px 20px;font-size:14px;border-radius:0}.el-radio-button__inner.is-round{padding:12px 20px}.el-radio-button__inner:hover{color:#006666}.el-radio-button__inner [class*=el-icon-]{line-height:.9}.el-radio-button__inner [class*=el-icon-]+span{margin-left:5px}.el-radio-button:first-child .el-radio-button__inner{border-left:1px solid #DCDFE6;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.el-radio-button__orig-radio{opacity:0;outline:0;position:absolute;z-index:-1}.el-radio-button__orig-radio:checked+.el-radio-button__inner{color:#FFF;background-color:#006666;border-color:#006666;-webkit-box-shadow:-1px 0 0 0 #006666;box-shadow:-1px 0 0 0 #006666}.el-radio-button__orig-radio:disabled+.el-radio-button__inner{color:#C0C4CC;cursor:not-allowed;background-image:none;background-color:#FFF;border-color:#EBEEF5;-webkit-box-shadow:none;box-shadow:none}.el-radio-button__orig-radio:disabled:checked+.el-radio-button__inner{background-color:#F2F6FC}.el-radio-button:last-child .el-radio-button__inner{border-radius:0 4px 4px 0}.el-radio-button:first-child:last-child .el-radio-button__inner{border-radius:4px}.el-radio-button--medium .el-radio-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.el-radio-button--medium .el-radio-button__inner.is-round{padding:10px 20px}.el-radio-button--small .el-radio-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.el-radio-button--small .el-radio-button__inner.is-round{padding:9px 15px}.el-radio-button--mini .el-radio-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.el-radio-button--mini .el-radio-button__inner.is-round{padding:7px 15px}.el-radio-button:focus:not(.is-focus):not(:active):not(.is-disabled){-webkit-box-shadow:0 0 2px 2px #006666;box-shadow:0 0 2px 2px #006666}
--------------------------------------------------------------------------------
/web/theme/radio-group.css:
--------------------------------------------------------------------------------
1 | .el-radio-group{display:inline-block;line-height:1;vertical-align:middle;font-size:0}
--------------------------------------------------------------------------------
/web/theme/rate.css:
--------------------------------------------------------------------------------
1 | .el-rate__icon,.el-rate__item{position:relative;display:inline-block}.el-rate{height:20px;line-height:1}.el-rate:active,.el-rate:focus{outline-width:0}.el-rate__item{font-size:0;vertical-align:middle}.el-rate__icon{font-size:18px;margin-right:6px;color:#C0C4CC;-webkit-transition:.3s;transition:.3s}.el-rate__decimal,.el-rate__icon .path2{position:absolute;top:0;left:0}.el-rate__icon.hover{-webkit-transform:scale(1.15);transform:scale(1.15)}.el-rate__decimal{display:inline-block;overflow:hidden}.el-rate__text{font-size:14px;vertical-align:middle}
--------------------------------------------------------------------------------
/web/theme/reset.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";body{font-family:"Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;font-weight:400;font-size:14px;color:#000;-webkit-font-smoothing:antialiased}a{color:#006666;text-decoration:none}a:focus,a:hover{color:rgb(51, 133, 133)}a:active{color:rgb(0, 92, 92)}h1,h2,h3,h4,h5,h6{color:#606266;font-weight:inherit}h1:first-child,h2:first-child,h3:first-child,h4:first-child,h5:first-child,h6:first-child,p:first-child{margin-top:0}h1:last-child,h2:last-child,h3:last-child,h4:last-child,h5:last-child,h6:last-child,p:last-child{margin-bottom:0}h1{font-size:20px}h2{font-size:18px}h3{font-size:16px}h4,h5,h6,p{font-size:inherit}p{line-height:1.8}sub,sup{font-size:13px}small{font-size:12px}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}
--------------------------------------------------------------------------------
/web/theme/row.css:
--------------------------------------------------------------------------------
1 | .el-row{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box}.el-row::after,.el-row::before{display:table;content:""}.el-row::after{clear:both}.el-row--flex{display:-webkit-box;display:-ms-flexbox;display:flex}.el-row--flex:after,.el-row--flex:before{display:none}.el-row--flex.is-justify-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-row--flex.is-justify-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.el-row--flex.is-justify-space-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.el-row--flex.is-justify-space-around{-ms-flex-pack:distribute;justify-content:space-around}.el-row--flex.is-align-middle{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-row--flex.is-align-bottom{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}
--------------------------------------------------------------------------------
/web/theme/scrollbar.css:
--------------------------------------------------------------------------------
1 | .el-scrollbar{overflow:hidden;position:relative}.el-scrollbar:active>.el-scrollbar__bar,.el-scrollbar:focus>.el-scrollbar__bar,.el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity 340ms ease-out;transition:opacity 340ms ease-out}.el-scrollbar__wrap{overflow:scroll;height:100%}.el-scrollbar__wrap--hidden-default{scrollbar-width:none}.el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(144,147,153,.3);-webkit-transition:.3s background-color;transition:.3s background-color}.el-scrollbar__thumb:hover{background-color:rgba(144,147,153,.5)}.el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity 120ms ease-out;transition:opacity 120ms ease-out}.el-scrollbar__bar.is-vertical{width:6px;top:2px}.el-scrollbar__bar.is-vertical>div{width:100%}.el-scrollbar__bar.is-horizontal{height:6px;left:2px}.el-scrollbar__bar.is-horizontal>div{height:100%}
--------------------------------------------------------------------------------
/web/theme/select-dropdown.css:
--------------------------------------------------------------------------------
1 | .el-popper .popper__arrow,.el-popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03));filter:drop-shadow(0 2px 12px rgba(0, 0, 0, .03))}.el-popper .popper__arrow::after{content:" ";border-width:6px}.el-popper[x-placement^=top]{margin-bottom:12px}.el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#EBEEF5;border-bottom-width:0}.el-popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-6px;border-top-color:#FFF;border-bottom-width:0}.el-popper[x-placement^=bottom]{margin-top:12px}.el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#EBEEF5}.el-popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#FFF}.el-popper[x-placement^=right]{margin-left:12px}.el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#EBEEF5;border-left-width:0}.el-popper[x-placement^=right] .popper__arrow::after{bottom:-6px;left:1px;border-right-color:#FFF;border-left-width:0}.el-popper[x-placement^=left]{margin-right:12px}.el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#EBEEF5}.el-popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#FFF}.el-select-dropdown{position:absolute;z-index:1001;border:1px solid #E4E7ED;border-radius:4px;background-color:#FFF;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-box-sizing:border-box;box-sizing:border-box;margin:5px 0}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected{color:#006666;background-color:#FFF}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover{background-color:#F5F7FA}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected::after{position:absolute;right:20px;font-family:element-icons;content:"\e6da";font-size:12px;font-weight:700;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.el-select-dropdown .el-scrollbar.is-empty .el-select-dropdown__list{padding:0}.el-select-dropdown__empty{padding:10px 0;margin:0;text-align:center;color:#999;font-size:14px}.el-select-dropdown__wrap{max-height:274px}.el-select-dropdown__list{list-style:none;padding:6px 0;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box}
--------------------------------------------------------------------------------
/web/theme/spinner.css:
--------------------------------------------------------------------------------
1 | .el-time-spinner{width:100%;white-space:nowrap}.el-spinner{display:inline-block;vertical-align:middle}.el-spinner-inner{-webkit-animation:rotate 2s linear infinite;animation:rotate 2s linear infinite;width:50px;height:50px}.el-spinner-inner .path{stroke:#ececec;stroke-linecap:round;-webkit-animation:dash 1.5s ease-in-out infinite;animation:dash 1.5s ease-in-out infinite}@-webkit-keyframes rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes rotate{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}100%{stroke-dasharray:90,150;stroke-dashoffset:-124}}@keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}100%{stroke-dasharray:90,150;stroke-dashoffset:-124}}
--------------------------------------------------------------------------------
/web/theme/steps.css:
--------------------------------------------------------------------------------
1 | .el-steps{display:-webkit-box;display:-ms-flexbox;display:flex}.el-steps--simple{padding:13px 8%;border-radius:4px;background:#F5F7FA}.el-steps--horizontal{white-space:nowrap}.el-steps--vertical{height:100%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-flow:column;flex-flow:column}
--------------------------------------------------------------------------------
/web/theme/submenu.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/theme/submenu.css
--------------------------------------------------------------------------------
/web/theme/switch.css:
--------------------------------------------------------------------------------
1 | .el-switch{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;position:relative;font-size:14px;line-height:20px;height:20px;vertical-align:middle}.el-switch.is-disabled .el-switch__core,.el-switch.is-disabled .el-switch__label{cursor:not-allowed}.el-switch__core,.el-switch__label{display:inline-block;cursor:pointer;vertical-align:middle}.el-switch__label{-webkit-transition:.2s;transition:.2s;height:20px;font-size:14px;font-weight:500;color:#303133}.el-switch__label.is-active{color:#006666}.el-switch__label--left{margin-right:10px}.el-switch__label--right{margin-left:10px}.el-switch__label *{line-height:1;font-size:14px;display:inline-block}.el-switch__input{position:absolute;width:0;height:0;opacity:0;margin:0}.el-switch__core{margin:0;position:relative;width:40px;height:20px;border:1px solid #DCDFE6;outline:0;border-radius:10px;-webkit-box-sizing:border-box;box-sizing:border-box;background:#DCDFE6;-webkit-transition:border-color .3s,background-color .3s;transition:border-color .3s,background-color .3s}.el-switch__core:after{content:"";position:absolute;top:1px;left:1px;border-radius:100%;-webkit-transition:all .3s;transition:all .3s;width:16px;height:16px;background-color:#FFF}.el-switch.is-checked .el-switch__core{border-color:#006666;background-color:#006666}.el-switch.is-checked .el-switch__core::after{left:100%;margin-left:-17px}.el-switch.is-disabled{opacity:.6}.el-switch--wide .el-switch__label.el-switch__label--left span{left:10px}.el-switch--wide .el-switch__label.el-switch__label--right span{right:10px}.el-switch .label-fade-enter,.el-switch .label-fade-leave-active{opacity:0}
--------------------------------------------------------------------------------
/web/theme/tab-pane.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wl05/AntVueBlogFront/d9be93c5eb7a94a33e93fb4bcc45642bd2e8301d/web/theme/tab-pane.css
--------------------------------------------------------------------------------
/web/theme/timeline-item.css:
--------------------------------------------------------------------------------
1 | .el-timeline-item{position:relative;padding-bottom:20px}.el-timeline-item__wrapper{position:relative;padding-left:28px;top:-3px}.el-timeline-item__tail{position:absolute;left:4px;height:100%;border-left:2px solid #E4E7ED}.el-timeline-item__icon{color:#FFF;font-size:13px}.el-timeline-item__node{position:absolute;background-color:#E4E7ED;border-radius:50%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-timeline-item__node--normal{left:-1px;width:12px;height:12px}.el-timeline-item__node--large{left:-2px;width:14px;height:14px}.el-timeline-item__node--primary{background-color:#006666}.el-timeline-item__node--success{background-color:#67C23A}.el-timeline-item__node--warning{background-color:#E6A23C}.el-timeline-item__node--danger{background-color:#F56C6C}.el-timeline-item__node--info{background-color:#909399}.el-timeline-item__dot{position:absolute;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-timeline-item__content{color:#303133}.el-timeline-item__timestamp{color:#909399;line-height:1;font-size:13px}.el-timeline-item__timestamp.is-top{margin-bottom:8px;padding-top:4px}.el-timeline-item__timestamp.is-bottom{margin-top:8px}
--------------------------------------------------------------------------------
/web/theme/timeline.css:
--------------------------------------------------------------------------------
1 | .el-timeline{margin:0;font-size:14px;list-style:none}.el-timeline .el-timeline-item:last-child .el-timeline-item__tail{display:none}
--------------------------------------------------------------------------------
/web/theme/tooltip.css:
--------------------------------------------------------------------------------
1 | .el-tooltip:focus:hover,.el-tooltip:focus:not(.focusing){outline-width:0}.el-tooltip__popper{position:absolute;border-radius:4px;padding:10px;z-index:2000;font-size:12px;line-height:1.2;min-width:10px;word-wrap:break-word}.el-tooltip__popper .popper__arrow,.el-tooltip__popper .popper__arrow::after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.el-tooltip__popper .popper__arrow{border-width:6px}.el-tooltip__popper .popper__arrow::after{content:" ";border-width:5px}.el-tooltip__popper[x-placement^=top]{margin-bottom:12px}.el-tooltip__popper[x-placement^=top] .popper__arrow{bottom:-6px;border-top-color:#303133;border-bottom-width:0}.el-tooltip__popper[x-placement^=top] .popper__arrow::after{bottom:1px;margin-left:-5px;border-top-color:#303133;border-bottom-width:0}.el-tooltip__popper[x-placement^=bottom]{margin-top:12px}.el-tooltip__popper[x-placement^=bottom] .popper__arrow{top:-6px;border-top-width:0;border-bottom-color:#303133}.el-tooltip__popper[x-placement^=bottom] .popper__arrow::after{top:1px;margin-left:-5px;border-top-width:0;border-bottom-color:#303133}.el-tooltip__popper[x-placement^=right]{margin-left:12px}.el-tooltip__popper[x-placement^=right] .popper__arrow{left:-6px;border-right-color:#303133;border-left-width:0}.el-tooltip__popper[x-placement^=right] .popper__arrow::after{bottom:-5px;left:1px;border-right-color:#303133;border-left-width:0}.el-tooltip__popper[x-placement^=left]{margin-right:12px}.el-tooltip__popper[x-placement^=left] .popper__arrow{right:-6px;border-right-width:0;border-left-color:#303133}.el-tooltip__popper[x-placement^=left] .popper__arrow::after{right:1px;bottom:-5px;margin-left:-5px;border-right-width:0;border-left-color:#303133}.el-tooltip__popper.is-dark{background:#303133;color:#FFF}.el-tooltip__popper.is-light{background:#FFF;border:1px solid #303133}.el-tooltip__popper.is-light[x-placement^=top] .popper__arrow{border-top-color:#303133}.el-tooltip__popper.is-light[x-placement^=top] .popper__arrow::after{border-top-color:#FFF}.el-tooltip__popper.is-light[x-placement^=bottom] .popper__arrow{border-bottom-color:#303133}.el-tooltip__popper.is-light[x-placement^=bottom] .popper__arrow::after{border-bottom-color:#FFF}.el-tooltip__popper.is-light[x-placement^=left] .popper__arrow{border-left-color:#303133}.el-tooltip__popper.is-light[x-placement^=left] .popper__arrow::after{border-left-color:#FFF}.el-tooltip__popper.is-light[x-placement^=right] .popper__arrow{border-right-color:#303133}.el-tooltip__popper.is-light[x-placement^=right] .popper__arrow::after{border-right-color:#FFF}
--------------------------------------------------------------------------------