├── .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 | ![](https://user-gold-cdn.xitu.io/2018/12/8/1678d0285a4304cf?w=2878&h=1628&f=png&s=470479) 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 | 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 | 14 | 15 | 59 | 60 | 72 | -------------------------------------------------------------------------------- /admin/src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 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 | 9 | 10 | 31 | -------------------------------------------------------------------------------- /admin/src/views/article/create.vue: -------------------------------------------------------------------------------- 1 | 6 | 14 | -------------------------------------------------------------------------------- /admin/src/views/categories/compoents/editCategories.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 41 | 42 | 44 | -------------------------------------------------------------------------------- /admin/src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 11 | -------------------------------------------------------------------------------- /admin/src/views/layout/Layout.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 50 | 51 | 75 | -------------------------------------------------------------------------------- /admin/src/views/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 22 | -------------------------------------------------------------------------------- /admin/src/views/layout/components/Sidebar/Item.vue: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /admin/src/views/layout/components/Sidebar/Link.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 39 | -------------------------------------------------------------------------------- /admin/src/views/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 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 | 21 | 22 | 67 | 68 | 84 | -------------------------------------------------------------------------------- /web/src/components/Comment/commentatorInfo.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 4 | 14 | 21 | -------------------------------------------------------------------------------- /web/src/components/PaymentCode/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 33 | 34 | 66 | -------------------------------------------------------------------------------- /web/src/components/Skeleton/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 47 | -------------------------------------------------------------------------------- /web/src/components/Spin/index.vue: -------------------------------------------------------------------------------- 1 | 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 | 4 | 9 | 17 | -------------------------------------------------------------------------------- /web/src/layout/PcNavMenu.vue: -------------------------------------------------------------------------------- 1 | 29 | 56 | 87 | -------------------------------------------------------------------------------- /web/src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 35 | 59 | -------------------------------------------------------------------------------- /web/src/layout/sider/archives/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 28 | 63 | -------------------------------------------------------------------------------- /web/src/layout/sider/categories/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 30 | 62 | -------------------------------------------------------------------------------- /web/src/layout/sider/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 29 | 41 | -------------------------------------------------------------------------------- /web/src/layout/sider/newestArticles/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 28 | 54 | -------------------------------------------------------------------------------- /web/src/layout/sider/profile/index.vue: -------------------------------------------------------------------------------- 1 | 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 | 11 | 22 | 62 | -------------------------------------------------------------------------------- /web/src/views/tags/components/shuffle.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 38 | 75 | -------------------------------------------------------------------------------- /web/src/views/tags/tags.vue: -------------------------------------------------------------------------------- 1 | 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} --------------------------------------------------------------------------------