├── .autod.conf.js ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .travis.yml ├── README.md ├── app.js ├── app ├── controller │ ├── admin.js │ ├── client.js │ ├── login.js │ └── page.js ├── extend │ └── helper.js ├── middleware │ └── auth.js ├── model │ ├── Article.js │ ├── Category.js │ ├── Tag.js │ └── User.js ├── public │ ├── admin │ │ └── src │ │ │ ├── .babelrc │ │ │ ├── .editorconfig │ │ │ ├── .eslintignore │ │ │ ├── .eslintrc.js │ │ │ ├── .gitattributes │ │ │ ├── .gitignore │ │ │ ├── .postcssrc.js │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── build │ │ │ ├── build.js │ │ │ ├── check-versions.js │ │ │ ├── logo.png │ │ │ ├── utils.js │ │ │ ├── vue-loader.conf.js │ │ │ ├── webpack.base.conf.js │ │ │ ├── webpack.dev.conf.js │ │ │ └── webpack.prod.conf.js │ │ │ ├── config │ │ │ ├── dev.env.js │ │ │ ├── index.js │ │ │ └── prod.env.js │ │ │ ├── deploy │ │ │ └── preview.sh │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src │ │ │ ├── App.vue │ │ │ ├── assets │ │ │ │ ├── library │ │ │ │ │ └── font-awesome-4.7.0 │ │ │ │ │ │ ├── HELP-US-OUT.txt │ │ │ │ │ │ ├── css │ │ │ │ │ │ ├── font-awesome.css │ │ │ │ │ │ └── font-awesome.min.css │ │ │ │ │ │ ├── fonts │ │ │ │ │ │ ├── FontAwesome.otf │ │ │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ │ │ └── fontawesome-webfont.woff2 │ │ │ │ │ │ ├── less │ │ │ │ │ │ ├── animated.less │ │ │ │ │ │ ├── bordered-pulled.less │ │ │ │ │ │ ├── core.less │ │ │ │ │ │ ├── fixed-width.less │ │ │ │ │ │ ├── font-awesome.less │ │ │ │ │ │ ├── icons.less │ │ │ │ │ │ ├── larger.less │ │ │ │ │ │ ├── list.less │ │ │ │ │ │ ├── mixins.less │ │ │ │ │ │ ├── path.less │ │ │ │ │ │ ├── rotated-flipped.less │ │ │ │ │ │ ├── screen-reader.less │ │ │ │ │ │ ├── stacked.less │ │ │ │ │ │ └── variables.less │ │ │ │ │ │ └── scss │ │ │ │ │ │ ├── _animated.scss │ │ │ │ │ │ ├── _bordered-pulled.scss │ │ │ │ │ │ ├── _core.scss │ │ │ │ │ │ ├── _fixed-width.scss │ │ │ │ │ │ ├── _icons.scss │ │ │ │ │ │ ├── _larger.scss │ │ │ │ │ │ ├── _list.scss │ │ │ │ │ │ ├── _mixins.scss │ │ │ │ │ │ ├── _path.scss │ │ │ │ │ │ ├── _rotated-flipped.scss │ │ │ │ │ │ ├── _screen-reader.scss │ │ │ │ │ │ ├── _stacked.scss │ │ │ │ │ │ ├── _variables.scss │ │ │ │ │ │ └── font-awesome.scss │ │ │ │ └── style │ │ │ │ │ ├── animate │ │ │ │ │ └── vue-transition.scss │ │ │ │ │ ├── fixed │ │ │ │ │ ├── base.scss │ │ │ │ │ └── element.scss │ │ │ │ │ ├── public-class.scss │ │ │ │ │ ├── public.scss │ │ │ │ │ ├── theme │ │ │ │ │ ├── d2 │ │ │ │ │ │ ├── index.scss │ │ │ │ │ │ └── setting.scss │ │ │ │ │ ├── list.js │ │ │ │ │ ├── register.scss │ │ │ │ │ ├── star │ │ │ │ │ │ ├── index.scss │ │ │ │ │ │ └── setting.scss │ │ │ │ │ ├── theme-base.scss │ │ │ │ │ ├── theme.scss │ │ │ │ │ └── violet │ │ │ │ │ │ ├── index.scss │ │ │ │ │ │ └── setting.scss │ │ │ │ │ └── unit │ │ │ │ │ └── _color.scss │ │ │ ├── components │ │ │ │ ├── common │ │ │ │ │ └── articleForm │ │ │ │ │ │ └── index.vue │ │ │ │ ├── core │ │ │ │ │ ├── d2-container │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ ├── d2-container-full-bs.vue │ │ │ │ │ │ │ └── d2-container-full.vue │ │ │ │ │ │ └── index.vue │ │ │ │ │ ├── d2-icon │ │ │ │ │ │ └── index.vue │ │ │ │ │ ├── d2-layout-main │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ ├── -full-screen │ │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ │ ├── -github │ │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ │ ├── -help │ │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ │ ├── -menu-header │ │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ │ ├── -menu-item │ │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ │ ├── -menu-side │ │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ │ ├── -menu-sub │ │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ │ ├── -theme │ │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ │ ├── -user │ │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ │ └── mixin │ │ │ │ │ │ │ │ └── menu.js │ │ │ │ │ │ └── index.vue │ │ │ │ │ ├── d2-multiple-page-control │ │ │ │ │ │ └── index.vue │ │ │ │ │ ├── d2-page-cover │ │ │ │ │ │ └── index.vue │ │ │ │ │ ├── d2-theme-list │ │ │ │ │ │ └── index.vue │ │ │ │ │ └── register.js │ │ │ │ └── index.js │ │ │ ├── libs │ │ │ │ ├── db.js │ │ │ │ └── util.js │ │ │ ├── main.js │ │ │ ├── menu │ │ │ │ └── index.js │ │ │ ├── pages │ │ │ │ └── core │ │ │ │ │ ├── 404 │ │ │ │ │ └── index.vue │ │ │ │ │ ├── create │ │ │ │ │ ├── articleList │ │ │ │ │ │ ├── draft.vue │ │ │ │ │ │ ├── garbage.vue │ │ │ │ │ │ └── index.vue │ │ │ │ │ ├── editArticle │ │ │ │ │ │ └── index.vue │ │ │ │ │ ├── index │ │ │ │ │ │ └── index.vue │ │ │ │ │ ├── newArticle │ │ │ │ │ │ └── index.vue │ │ │ │ │ ├── newCategory │ │ │ │ │ │ └── index.vue │ │ │ │ │ └── newTag │ │ │ │ │ │ └── index.vue │ │ │ │ │ ├── index │ │ │ │ │ └── index.vue │ │ │ │ │ └── login │ │ │ │ │ ├── config │ │ │ │ │ ├── bubble.js │ │ │ │ │ ├── default.js │ │ │ │ │ ├── nasa.js │ │ │ │ │ └── snow.js │ │ │ │ │ ├── index.vue │ │ │ │ │ └── style.scss │ │ │ ├── plugin │ │ │ │ └── axios │ │ │ │ │ └── index.js │ │ │ ├── router │ │ │ │ ├── index.js │ │ │ │ └── routes.js │ │ │ └── store │ │ │ │ ├── index.js │ │ │ │ └── modules │ │ │ │ └── d2admin.js │ │ │ └── static │ │ │ ├── icon.ico │ │ │ └── image │ │ │ ├── bg │ │ │ └── star-squashed.jpg │ │ │ ├── icon │ │ │ ├── 500 │ │ │ │ ├── d2admin.png │ │ │ │ └── setting.png │ │ │ └── github │ │ │ │ └── forkme@2x.png │ │ │ ├── login-code.png │ │ │ ├── me │ │ │ ├── qq.jpg │ │ │ ├── qqq.png │ │ │ └── we.jpg │ │ │ ├── page │ │ │ └── 404 │ │ │ │ └── cover@2x.png │ │ │ └── theme │ │ │ ├── d2 │ │ │ ├── logo │ │ │ │ ├── all.png │ │ │ │ ├── icon-only.png │ │ │ │ ├── logo3-s.png │ │ │ │ └── logo3.png │ │ │ └── preview@2x.png │ │ │ ├── star │ │ │ ├── logo │ │ │ │ ├── all.png │ │ │ │ └── icon-only.png │ │ │ └── preview@2x.png │ │ │ └── violet │ │ │ ├── logo │ │ │ ├── all.png │ │ │ └── icon-only.png │ │ │ └── preview@2x.png │ └── client │ │ └── src │ │ ├── .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 │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ ├── css │ │ │ │ ├── common.styl │ │ │ │ ├── icon.styl │ │ │ │ ├── index.styl │ │ │ │ └── reset.styl │ │ │ └── js │ │ │ │ ├── api.js │ │ │ │ └── utils.js │ │ ├── components │ │ │ ├── 404 │ │ │ │ └── 404.vue │ │ │ ├── ArticleList │ │ │ │ └── ArticleList.vue │ │ │ ├── AticleDetail │ │ │ │ └── AticleDetail.vue │ │ │ ├── Header │ │ │ │ └── Header.vue │ │ │ ├── Index │ │ │ │ └── Index.vue │ │ │ ├── LeftSide │ │ │ │ └── LeftSide.vue │ │ │ ├── SearchList │ │ │ │ └── SearchList.vue │ │ │ └── TagCloud │ │ │ │ └── TagCloud.vue │ │ ├── main.js │ │ ├── plugins │ │ │ └── axios.js │ │ ├── router │ │ │ └── index.js │ │ └── store │ │ │ └── index.js │ │ └── static │ │ ├── .gitkeep │ │ ├── logo.png │ │ ├── logo1.png │ │ ├── logo2.png │ │ ├── logo3.png │ │ └── qqq.jpg ├── router.js └── service │ ├── admin.js │ ├── client.js │ └── login.js ├── appveyor.yml ├── config ├── config.default.js └── plugin.js ├── package.json └── test └── app └── controller └── home.test.js /.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 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-egg", 3 | "rules": { 4 | "strict": 0, 5 | "linebreak-style": 0, 6 | "semi": 0, 7 | "comma-dangle": 0, 8 | "object-shorthand": 0, 9 | "prefer-const": 0 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=javascript 2 | *.css linguist-language=javascript 3 | *.scss linguist-language=javascript 4 | *.html linguist-language=javascript 5 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '8' 5 | install: 6 | - npm i npminstall && npminstall 7 | script: 8 | - npm run ci 9 | after_script: 10 | - npminstall codecov && codecov 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## W-Blog 3 | 4 | W-Blog是一个基于vue和node的小小小博客 5 | 前端用vue,后端用egg.js 6 | 7 | ## 快速入门 8 | 9 | ### 技术栈 10 | 11 | - 前端: 12 | - 用户端:[vue](https://cn.vuejs.org/)、[iview](https://www.iviewui.com) 13 | - admin端:[vue](https://cn.vuejs.org/)、[d2admin](https://github.com/d2-projects/d2-admin)、[element](http://element-cn.eleme.io/#/zh-CN) 14 | - 后端: [egg.js](https://eggjs.org/zh-cn/)、[mongodb](https://www.mongodb.com/) 15 | 16 | ### 功能特性 17 | 18 | - 轻量级Markdown编辑器,图片上传七牛 19 | - 支持标签、分类、搜索草稿箱等功能 20 | - 标签云 21 | ### 线上地址 22 | [煌哥哥的博客](http://blog.wadejs.cn/) 23 | ### 图片演示 24 | #### 前台 25 | - 首页浏览 26 | ![1.gif](https://user-gold-cdn.xitu.io/2018/8/15/1653cab4e30730e0?w=1836&h=931&f=gif&s=1793276) 27 | - 文章详情浏览及目录导航 28 | ![2.gif](https://user-gold-cdn.xitu.io/2018/8/15/1653cab443526800?w=1836&h=931&f=gif&s=1255416) 29 | - 可根据分类和标签搜索文章 30 | ![3.gif](https://user-gold-cdn.xitu.io/2018/8/15/1653cab444149bbb?w=1836&h=931&f=gif&s=2470094) 31 | - 输入关键词搜索 32 | ![4.gif](https://user-gold-cdn.xitu.io/2018/8/15/1653cab4458bf3c8?w=1836&h=931&f=gif&s=568162) 33 | - 标签云及搜索 34 | ![5.gif](https://user-gold-cdn.xitu.io/2018/8/15/1653cab445cc9fa5?w=1836&h=931&f=gif&s=1807408) 35 | #### 后台 36 | - 后台登录 37 | ![6.gif](https://user-gold-cdn.xitu.io/2018/8/15/1653cab44755f4fc?w=1836&h=931&f=gif&s=106266) 38 | - 文章列表 39 | ![7.gif](https://user-gold-cdn.xitu.io/2018/8/15/1653cab44b1b3a17?w=1836&h=931&f=gif&s=1504343) 40 | - 文章搜索 41 | ![8.gif](https://user-gold-cdn.xitu.io/2018/8/15/1653cab476ca1ac9?w=1836&h=931&f=gif&s=516696) 42 | - 文章编辑 43 | ![9.gif](https://user-gold-cdn.xitu.io/2018/8/15/1653cab483ccca2f?w=1836&h=931&f=gif&s=849836) 44 | - 增加分类 45 | ![10.gif](https://user-gold-cdn.xitu.io/2018/8/15/1653cab498fd116e?w=1836&h=931&f=gif&s=594627) 46 | - 增加标签 47 | ![11.gif](https://user-gold-cdn.xitu.io/2018/8/15/1653cab49eb1062c?w=1836&h=931&f=gif&s=630573) 48 | - 标签修改(分类一样) 49 | ![12.gif](https://user-gold-cdn.xitu.io/2018/8/15/1653cab4a8d82c53?w=1836&h=931&f=gif&s=780030) 50 | - 文章修改 51 | ![13.gif](https://user-gold-cdn.xitu.io/2018/8/15/1653cab4b1353700?w=1836&h=931&f=gif&s=447485) 52 | - 文章删除,支持垃圾箱草稿箱 53 | ![14.gif](https://user-gold-cdn.xitu.io/2018/8/15/1653cab4d87200f5?w=1836&h=931&f=gif&s=1534909) 54 | 55 | ### 目录结构 56 | 57 | ``` 58 | │ .autod.conf.js 59 | │ .eslintignore 60 | │ .eslintrc 61 | │ .gitignore 62 | │ .travis.yml 63 | │ app.js // 项目启动前执行,比如写入管理员 64 | │ appveyor.yml 65 | │ package.json 66 | │ README.md 67 | │ 68 | ├─app 69 | │ │ router.js // 服务端路由 70 | │ │ 71 | │ ├─controller 72 | │ │ admin.js // 后台相关controller 73 | │ │ client.js // 前台相关controller 74 | │ │ login.js // 登录相关controller 75 | │ │ page.js // 页面相关controller 76 | │ │ 77 | │ ├─extend 78 | │ │ helper.js 79 | │ │ 80 | │ ├─middleware 81 | │ │ auth.js // 登录验证中间件 82 | │ │ 83 | │ ├─model 84 | │ │ Article.js // 文章model 85 | │ │ Category.js // 分类model 86 | │ │ Tag.js // 标签model 87 | │ │ User.js // 用户model 88 | │ │ 89 | │ ├─public 90 | │ │ │ 91 | │ │ ├─admin // admin端 92 | │ │ │ ├─dist // 打包生成后的目录 93 | │ │ │ └─src // admin端源文件 94 | │ │ │ 95 | │ │ └─client // 用户端 96 | │ │ ├─dist // 打包生成后的目录 97 | │ │ └─src // 用户端源文件 98 | │ │ 99 | │ └─service // service部分用来执行具体的操作 100 | │ admin.js 101 | │ client.js 102 | │ login.js 103 | │ 104 | ├─config 105 | │ config.default.js // 项目配置相关 106 | │ plugin.js 107 | │ 108 | └─test // 测试相关 109 | └─app 110 | └─controller 111 | home.test.js 112 | ``` 113 | 114 | ### 全局配置 115 | 116 | ```javascript 117 | module.exports = appInfo => { 118 | return { 119 | keys: appInfo.name + '_1432030565447_3632', 120 | mongoose: { 121 | clients: { 122 | blog: { 123 | url: 'mongodb://127.0.0.1/blog', 124 | options: { 125 | user: 'test', // 数据库的用户名 126 | pass: 'test' // 数据库的密码 127 | }, 128 | } 129 | } 130 | }, 131 | // 初始化管理员信息 132 | user: { 133 | userName: 'admin', 134 | password: 'admin', 135 | }, 136 | session: { 137 | maxAge: 3600 * 1000, 138 | }, 139 | jwt: { 140 | cert: 'jsonwebtoken' // jwt秘钥 141 | }, 142 | /** 143 | * markdown编辑器的图片上传用的是七牛存储 144 | * 所以需要配置七牛的key 145 | */ 146 | qiniu: { // 这里填写你七牛的Access Key和Secret Key 147 | ak: '', 148 | sk: '' 149 | } 150 | } 151 | }; 152 | ``` 153 | 154 | 155 | ### 本地运行 156 | 157 | 安装[MongoDB](https://www.mongodb.com/download-center?jmp=nav#community)数据库和[Node.js](https://nodejs.org/en/)环境。 158 | 159 | ``` bash 160 | # 安装服务端依赖 161 | npm install 162 | # 开启mongodb 163 | mongod --dbpath '你数据库的目录' # --auth 如果开启密码,要加上这个命令 164 | # 运行服务 165 | npm run dev 166 | 167 | # 进入前台目录 168 | cd ./app/public/client/src 169 | # 安装前台依赖 170 | npm install 171 | # 运行前台项目 172 | npm run dev 173 | 174 | # 进入后台目录 175 | cd ./app/public/admin/src 176 | # 安装后台依赖 177 | npm install 178 | # 运行后台项目 179 | npm run dev 180 | 181 | # 即可通过 http://127.0.0.1:8080访问 182 | # 开发阶段直接通过webpack的devserver访问 183 | # 代理请求已经配置好,可在config下配置proxyTable更改 184 | ``` 185 | 186 | ### 打包 187 | 188 | ```bash 189 | # 在前台和后台目录分别 190 | npm run build 191 | # 在项目根目录 192 | npm install --production 193 | # 启动 194 | npm start 195 | # 打包后可以通过 196 | # http://127.0.0.1:7001/ 和 http://127.0.0.1:7001/admin 来访问前台和后台 197 | ``` 198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | app.beforeStart(async () => { 3 | // 应用会等待这个函数执行完成才启动 4 | const db = app.mongooseDB.get('blog') 5 | let adminInfo = app.config.user 6 | let admin = await app.model.User.find({ userName: adminInfo.userName }) 7 | if (admin.length === 0) { 8 | // 初始化管理员 9 | await app.model.User.create(adminInfo) 10 | console.log('管理员已初始化') 11 | } 12 | db.once('open', () => { 13 | console.log('数据库连接成功'); 14 | }) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /app/controller/client.js: -------------------------------------------------------------------------------- 1 | const Controller = require('egg').Controller 2 | class BlogController extends Controller { 3 | /** 4 | * 获取文章列表 5 | * 如果没传keyword,返回所有文章 6 | * 传入keywork返回根据keyword在标题和内容搜索的结果 7 | */ 8 | async getArticleList() { 9 | const { ctx } = this 10 | let { page = 1, keyword = '' } = ctx.query 11 | let resMsg = { 12 | errcode: 0, 13 | data: {}, 14 | msg: 'success' 15 | } 16 | let articleList = await ctx.service.client.getAllArticleById(page, keyword) 17 | resMsg.data = { 18 | list: articleList.list, 19 | count: articleList.count 20 | } 21 | ctx.body = resMsg 22 | } 23 | /** 24 | * 根据分类id查询文章 25 | */ 26 | async searchByCategory() { 27 | const { ctx } = this 28 | let { page = 1, id = '' } = ctx.query 29 | let resMsg = { 30 | errcode: 0, 31 | data: {}, 32 | msg: 'success' 33 | } 34 | let articleList = await ctx.service.client.searchByCategory(page, id) 35 | resMsg.data = { 36 | list: articleList.list, 37 | count: articleList.count 38 | } 39 | ctx.body = resMsg 40 | } 41 | /** 42 | * 根据标签id查询文章 43 | */ 44 | async searchByTag() { 45 | const { ctx } = this 46 | let { page = 1, id = '' } = ctx.query 47 | let resMsg = { 48 | errcode: 0, 49 | data: {}, 50 | msg: 'success' 51 | } 52 | let articleList = await ctx.service.client.searchByTag(page, id) 53 | resMsg.data = { 54 | list: articleList.list, 55 | count: articleList.count 56 | } 57 | ctx.body = resMsg 58 | } 59 | /** 60 | * 根据文章id查询文章内容 61 | */ 62 | async getArticleDetail() { 63 | const { ctx } = this 64 | let articleId = ctx.request.body.id 65 | let resMsg = { 66 | errcode: 0, 67 | data: {}, 68 | msg: 'success' 69 | } 70 | let articleDetail = await ctx.service.client.getArticleDetailByArticleId(articleId) 71 | resMsg.data = articleDetail[0] 72 | ctx.body = resMsg 73 | } 74 | /** 75 | * 获取所有分类和标签及其数量 76 | */ 77 | async getTagsAndCategories() { 78 | const { ctx } = this 79 | let resMsg = { 80 | errcode: 0, 81 | data: {}, 82 | msg: 'success' 83 | } 84 | let [ categoriesCount, tagsCount ] = await Promise.all([ 85 | ctx.service.client.getCategoriesCount(), 86 | ctx.service.client.getTagsCount() 87 | ]) 88 | resMsg.data = { 89 | categoriesCount, 90 | tagsCount 91 | } 92 | ctx.body = resMsg 93 | } 94 | } 95 | 96 | module.exports = BlogController 97 | -------------------------------------------------------------------------------- /app/controller/login.js: -------------------------------------------------------------------------------- 1 | const Controller = require('egg').Controller 2 | class LoginController extends Controller { 3 | // 获取验证码 4 | async getCaptcha() { 5 | const { ctx } = this 6 | const captcha = ctx.service.login.genCaptcha() 7 | // 把生成的验证码文本信息(如:t8ec),存入session,以待验证 8 | ctx.session.code = captcha.text 9 | // 将生成的验证码svg图片返回给前端 10 | ctx.body = captcha.data 11 | } 12 | // 登录 13 | async login() { 14 | const { ctx } = this 15 | const { username, password, code } = ctx.request.body 16 | let resMsg = { 17 | errcode: 0, 18 | data: {}, 19 | msg: '登录成功' 20 | } 21 | let isCaptchaVali = ctx.service.login.checkCaptcha(code) 22 | if (!isCaptchaVali) { 23 | resMsg.errcode = 1 24 | resMsg.msg = '验证码错误' 25 | ctx.body = resMsg 26 | return 27 | } 28 | // 验证码正确则继续登录操作 29 | const userData = await ctx.service.login.login({ username, password }) 30 | if (!userData) { 31 | resMsg.errcode = 2 32 | resMsg.msg = '用户名或密码错误' 33 | ctx.body = resMsg 34 | return 35 | } 36 | resMsg.token = userData.token 37 | resMsg.data = { 38 | username: userData.user.userName, 39 | uid: userData.user._id 40 | } 41 | ctx.body = resMsg 42 | } 43 | } 44 | 45 | module.exports = LoginController 46 | -------------------------------------------------------------------------------- /app/controller/page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Controller = require('egg').Controller; 4 | const fs = require('fs') 5 | const util = require('util') 6 | const path = require('path') 7 | const readFilePromise = util.promisify(fs.readFile) 8 | class PageController extends Controller { 9 | /** 10 | * 打包后可以通过 11 | * http://rootUrl/ 和 http://rootUrl/admin 来访问前台可后台 12 | * 在开发阶段直接通过webpack的devserver来访问,接口通过proxyTable来代理 13 | */ 14 | async index() { 15 | const { ctx } = this 16 | ctx.response.type = 'html' 17 | let page = await readFilePromise(path.resolve(__dirname, '../public/client/dist/index.html')) 18 | ctx.body = page 19 | } 20 | async admin() { 21 | const { ctx } = this 22 | ctx.response.type = 'html' 23 | let page = await readFilePromise(path.resolve(__dirname, '../public/admin/dist/index.html')) 24 | ctx.body = page 25 | } 26 | } 27 | 28 | module.exports = PageController; 29 | 30 | -------------------------------------------------------------------------------- /app/extend/helper.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | } 3 | -------------------------------------------------------------------------------- /app/middleware/auth.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken') 2 | module.exports = () => { 3 | return async function auth(ctx, next) { 4 | try { 5 | let decode = jwt.verify(ctx.get('Authorization'), ctx.app.config.jwt.cert) 6 | ctx.userId = decode.id 7 | } catch (err) { 8 | console.log(err) 9 | ctx.status = 401 10 | ctx.body = { 11 | errcode: 1, 12 | msg: '授权失败,请重新登录' 13 | } 14 | return 15 | } 16 | await next() // 这里因为next之后的操作是异步的所以需要加 await 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /app/model/Article.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const mongoose = app.mongoose; 3 | const Schema = mongoose.Schema; 4 | const conn = app.mongooseDB.get('blog') 5 | const ArticleSchema = new Schema({ 6 | title: { type: String, required: true }, 7 | categoryId: { 8 | type: Schema.Types.ObjectId, 9 | ref: 'Category' 10 | }, 11 | tagId: [ 12 | { 13 | type: Schema.Types.ObjectId, 14 | ref: 'Tag' 15 | } 16 | ], 17 | userId: { 18 | type: Schema.Types.ObjectId, 19 | ref: 'User' 20 | }, 21 | content: { 22 | type: String 23 | }, 24 | html: { 25 | type: String 26 | }, 27 | createTime: { 28 | type: String 29 | // 因为mongodb存入date类型会有早8个小时的问题,所以这里用String 30 | }, 31 | updateTime: { 32 | type: String 33 | // 因为mongodb存入date类型会有早8个小时的问题,所以这里用String 34 | }, 35 | status: { 36 | type: Number 37 | /** 38 | * 0: 已发表 39 | * 1: 草稿 40 | * 2: 已删除 41 | */ 42 | } 43 | }); 44 | return conn.model('Article', ArticleSchema); 45 | } 46 | -------------------------------------------------------------------------------- /app/model/Category.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const mongoose = app.mongoose; 3 | const Schema = mongoose.Schema; 4 | const conn = app.mongooseDB.get('blog') 5 | 6 | const CategorySchema = new Schema({ 7 | categoryName: { type: String }, 8 | // count: { type: Number }, 9 | userId: { 10 | type: Schema.Types.ObjectId, 11 | ref: 'User' 12 | } 13 | }); 14 | 15 | return conn.model('Category', CategorySchema); 16 | } 17 | -------------------------------------------------------------------------------- /app/model/Tag.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const mongoose = app.mongoose; 3 | const Schema = mongoose.Schema; 4 | const conn = app.mongooseDB.get('blog') 5 | 6 | const TagSchema = new Schema({ 7 | tagName: { type: String }, 8 | // count: { type: Number }, 9 | userId: { 10 | type: Schema.Types.ObjectId, 11 | ref: 'User' 12 | } 13 | }); 14 | 15 | return conn.model('Tag', TagSchema); 16 | } 17 | -------------------------------------------------------------------------------- /app/model/User.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const mongoose = app.mongoose; 3 | const Schema = mongoose.Schema; 4 | const conn = app.mongooseDB.get('blog') 5 | 6 | const UserSchema = new Schema({ 7 | userName: { type: String }, 8 | password: { type: String } 9 | }); 10 | 11 | return conn.model('User', UserSchema); 12 | } 13 | -------------------------------------------------------------------------------- /app/public/admin/src/.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 | -------------------------------------------------------------------------------- /app/public/admin/src/.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 | -------------------------------------------------------------------------------- /app/public/admin/src/.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | -------------------------------------------------------------------------------- /app/public/admin/src/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 13 | extends: 'standard', 14 | // required to lint *.vue files 15 | plugins: [ 16 | 'html' 17 | ], 18 | // add your custom rules here 19 | rules: { 20 | // allow async-await 21 | 'generator-star-spacing': 'off', 22 | // allow debugger during development 23 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 24 | 'space-before-function-paren': 0 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/public/admin/src/.gitattributes: -------------------------------------------------------------------------------- 1 | *.css linguist-language=JavaScript -------------------------------------------------------------------------------- /app/public/admin/src/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /app/public/admin/src/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | "postcss-import": {}, 7 | "autoprefixer": {} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /app/public/admin/src/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 李杨 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 | -------------------------------------------------------------------------------- /app/public/admin/src/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/README.md -------------------------------------------------------------------------------- /app/public/admin/src/build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // 检查依赖版本 3 | require('./check-versions')() 4 | 5 | // 设置 NODE_ENV 6 | process.env.NODE_ENV = 'production' 7 | 8 | // loading模块 https://www.npmjs.com/package/ora 9 | const ora = require('ora') 10 | // 删除模块 https://www.npmjs.com/package/rimraf 11 | const rm = require('rimraf') 12 | // path 13 | const path = require('path') 14 | // 输出彩色的文字 https://www.npmjs.com/package/chalk 15 | const chalk = require('chalk') 16 | // webpack 17 | const webpack = require('webpack') 18 | // 设置 这个文件里只用到了 config.build 19 | const config = require('../config') 20 | // webpack 生产环境的核心配置文件 21 | const webpackConfig = require('./webpack.prod.conf') 22 | 23 | // 定义一个 loading 并开始 24 | const spinner = ora('building for production...') 25 | spinner.start() 26 | 27 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 28 | if (err) throw err 29 | webpack(webpackConfig, (err, stats) => { 30 | spinner.stop() 31 | if (err) throw err 32 | process.stdout.write(stats.toString({ 33 | colors: true, 34 | modules: false, 35 | children: false, // if you are using ts-loader, setting this to true will make tyescript errors show up during build 36 | chunks: false, 37 | chunkModules: false 38 | }) + '\n\n') 39 | 40 | if (stats.hasErrors()) { 41 | console.log(chalk.red(' Build failed with errors.\n')) 42 | process.exit(1) 43 | } 44 | 45 | console.log(chalk.cyan(' Build complete.\n')) 46 | console.log(chalk.yellow( 47 | ' Tip: built files are meant to be served over an HTTP server.\n' + 48 | ' Opening index.html over file:// won\'t work.\n' 49 | )) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /app/public/admin/src/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 | -------------------------------------------------------------------------------- /app/public/admin/src/build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/build/logo.png -------------------------------------------------------------------------------- /app/public/admin/src/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass'), 64 | stylus: generateLoaders('stylus'), 65 | styl: generateLoaders('stylus') 66 | } 67 | } 68 | 69 | // Generate loaders for standalone style files (outside of .vue) 70 | exports.styleLoaders = function (options) { 71 | const output = [] 72 | const loaders = exports.cssLoaders(options) 73 | 74 | for (const extension in loaders) { 75 | const loader = loaders[extension] 76 | output.push({ 77 | test: new RegExp('\\.' + extension + '$'), 78 | use: loader 79 | }) 80 | } 81 | 82 | return output 83 | } 84 | 85 | exports.createNotifierCallback = () => { 86 | const notifier = require('node-notifier') 87 | 88 | return (severity, errors) => { 89 | if (severity !== 'error') return 90 | 91 | const error = errors[0] 92 | const filename = error.file && error.file.split('!').pop() 93 | 94 | notifier.notify({ 95 | title: packageConfig.name, 96 | message: severity + ': ' + error.name, 97 | subtitle: filename || '', 98 | icon: path.join(__dirname, 'logo.png') 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/public/admin/src/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: { 11 | ...utils.cssLoaders({ 12 | sourceMap: sourceMapEnabled, 13 | extract: isProduction 14 | }) 15 | }, 16 | cssSourceMap: sourceMapEnabled, 17 | cacheBusting: config.dev.cacheBusting, 18 | transformToRequire: { 19 | video: ['src', 'poster'], 20 | source: 'src', 21 | img: 'src', 22 | image: 'xlink:href' 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/public/admin/src/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const vueLoaderConfig = require('./vue-loader.conf') 7 | 8 | function resolve (dir) { 9 | return path.join(__dirname, '..', dir) 10 | } 11 | 12 | const createLintingRule = () => ({ 13 | test: /\.(js|vue)$/, 14 | loader: 'eslint-loader', 15 | enforce: 'pre', 16 | include: [resolve('src'), resolve('test')], 17 | options: { 18 | formatter: require('eslint-friendly-formatter'), 19 | emitWarning: !config.dev.showEslintErrorsInOverlay 20 | } 21 | }) 22 | 23 | module.exports = { 24 | context: path.resolve(__dirname, '../'), 25 | entry: { 26 | app: ['babel-polyfill', './src/main.js'] 27 | }, 28 | output: { 29 | path: config.build.assetsRoot, 30 | filename: '[name].js', 31 | publicPath: process.env.NODE_ENV === 'production' 32 | ? config.build.assetsPublicPath 33 | : config.dev.assetsPublicPath 34 | }, 35 | resolve: { 36 | extensions: ['.js', '.vue', '.json'], 37 | alias: { 38 | 'vue$': 'vue/dist/vue.esm.js', 39 | '@': resolve('src'), 40 | } 41 | }, 42 | module: { 43 | rules: [ 44 | ...(config.dev.useEslint ? [createLintingRule()] : []), 45 | { 46 | test: /\.vue$/, 47 | loader: 'vue-loader', 48 | options: vueLoaderConfig 49 | }, 50 | { 51 | test: /\.js$/, 52 | loader: 'babel-loader', 53 | include: [resolve('src'), resolve('test')] 54 | }, 55 | { 56 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 57 | loader: 'url-loader', 58 | exclude: [resolve('src/assets/icons/svg')], 59 | options: { 60 | limit: 10000, 61 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 62 | } 63 | }, 64 | { 65 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 66 | loader: 'url-loader', 67 | options: { 68 | limit: 10000, 69 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 70 | } 71 | }, 72 | { 73 | test: /\.scss$/, 74 | loaders: ["style", "css", "sass"] 75 | }, 76 | { 77 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 78 | loader: 'url-loader', 79 | options: { 80 | limit: 10000, 81 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 82 | } 83 | } 84 | ] 85 | }, 86 | node: { 87 | // prevent webpack from injecting useless setImmediate polyfill because Vue 88 | // source contains it (although only uses it if it's native). 89 | setImmediate: false, 90 | // prevent webpack from injecting mocks to Node native modules 91 | // that does not make sense for the client 92 | dgram: 'empty', 93 | fs: 'empty', 94 | net: 'empty', 95 | tls: 'empty', 96 | child_process: 'empty' 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/public/admin/src/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const baseWebpackConfig = require('./webpack.base.conf') 7 | const HtmlWebpackPlugin = require('html-webpack-plugin') 8 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 9 | const portfinder = require('portfinder') 10 | 11 | const HOST = process.env.HOST 12 | const PORT = process.env.PORT && Number(process.env.PORT) 13 | 14 | const devWebpackConfig = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 17 | }, 18 | // cheap-module-eval-source-map is faster for development 19 | devtool: config.dev.devtool, 20 | 21 | // these devServer options should be customized in /config/index.js 22 | devServer: { 23 | clientLogLevel: 'warning', 24 | historyApiFallback: true, 25 | hot: true, 26 | compress: true, 27 | host: HOST || config.dev.host, 28 | port: PORT || config.dev.port, 29 | open: config.dev.autoOpenBrowser, 30 | overlay: config.dev.errorOverlay 31 | ? { warnings: false, errors: true } 32 | : false, 33 | publicPath: config.dev.assetsPublicPath, 34 | proxy: config.dev.proxyTable, 35 | quiet: true, // necessary for FriendlyErrorsPlugin 36 | watchOptions: { 37 | poll: config.dev.poll, 38 | } 39 | }, 40 | plugins: [ 41 | new webpack.DefinePlugin({ 42 | 'process.env': require('../config/dev.env') 43 | }), 44 | new webpack.HotModuleReplacementPlugin(), 45 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 46 | new webpack.NoEmitOnErrorsPlugin(), 47 | // https://github.com/ampedandwired/html-webpack-plugin 48 | new HtmlWebpackPlugin({ 49 | filename: 'index.html', 50 | template: 'index.html', 51 | inject: true 52 | }), 53 | ] 54 | }) 55 | 56 | module.exports = new Promise((resolve, reject) => { 57 | portfinder.basePort = process.env.PORT || config.dev.port 58 | portfinder.getPort((err, port) => { 59 | if (err) { 60 | reject(err) 61 | } else { 62 | // publish the new Port, necessary for e2e tests 63 | process.env.PORT = port 64 | // add port to devServer config 65 | devWebpackConfig.devServer.port = port 66 | 67 | // Add FriendlyErrorsPlugin 68 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 69 | compilationSuccessInfo: { 70 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 71 | }, 72 | onErrors: config.dev.notifyOnErrors 73 | ? utils.createNotifierCallback() 74 | : undefined 75 | })) 76 | 77 | resolve(devWebpackConfig) 78 | } 79 | }) 80 | }) 81 | -------------------------------------------------------------------------------- /app/public/admin/src/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /app/public/admin/src/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.2.7 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: { 14 | '/api': { 15 | target: 'http://127.0.0.1:7001', 16 | changeOrigin: true, 17 | pathRewrite: { 18 | '^/api': '/' 19 | } 20 | } 21 | }, 22 | 23 | // Various Dev Server settings 24 | host: 'localhost', // can be overwritten by process.env.HOST 25 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 26 | autoOpenBrowser: true, 27 | errorOverlay: true, 28 | notifyOnErrors: true, 29 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 30 | 31 | // Use Eslint Loader? 32 | // If true, your code will be linted during bundling and 33 | // linting errors and warnings will be shown in the console. 34 | useEslint: true, 35 | // If true, eslint errors and warnings will also be shown in the error overlay 36 | // in the browser. 37 | showEslintErrorsInOverlay: false, 38 | 39 | /** 40 | * Source Maps 41 | */ 42 | 43 | // https://webpack.js.org/configuration/devtool/#development 44 | devtool: 'eval-source-map', 45 | 46 | // If you have problems debugging vue-files in devtools, 47 | // set this to false - it *may* help 48 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 49 | cacheBusting: true, 50 | 51 | // CSS Sourcemaps off by default because relative paths are "buggy" 52 | // with this option, according to the CSS-Loader README 53 | // (https://github.com/webpack/css-loader#sourcemaps) 54 | // In our experience, they generally work as expected, 55 | // just be aware of this issue when enabling this option. 56 | cssSourceMap: false, 57 | }, 58 | 59 | build: { 60 | // Template for index.html 61 | index: path.resolve(__dirname, '../../dist/index.html'), 62 | // index: path.resolve(__dirname, '../dist/index.html'), 63 | 64 | // Paths 65 | // assetsRoot: path.resolve(__dirname, '../dist'), 66 | assetsRoot: path.resolve(__dirname, '../../dist'), 67 | assetsSubDirectory: 'static', 68 | // 请根据你的站点地址修改这里 69 | // assetsPublicPath: '/d2-admin-start-kit-preview/', 70 | assetsPublicPath: '/public/admin/dist/', 71 | /** 72 | * Source Maps 73 | */ 74 | 75 | productionSourceMap: false, 76 | // https://webpack.js.org/configuration/devtool/#production 77 | devtool: '#source-map', 78 | 79 | // Gzip off by default as many popular static hosts such as 80 | // Surge or Netlify already gzip all static assets for you. 81 | // Before setting to `true`, make sure to: 82 | // npm install --save-dev compression-webpack-plugin 83 | productionGzip: false, 84 | productionGzipExtensions: ['js', 'css'], 85 | 86 | // Run the build command with an extra argument to 87 | // View the bundle analyzer report after build finishes: 88 | // `npm run build --report` 89 | // Set to `true` or `false` to always turn it on or off 90 | bundleAnalyzerReport: process.env.npm_config_report 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/public/admin/src/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /app/public/admin/src/deploy/preview.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 确保脚本抛出遇到的错误 4 | set -e 5 | 6 | # 生成静态文件 7 | npm run build 8 | 9 | # 进入生成的文件夹 10 | cd dist 11 | 12 | # 如果是发布到自定义域名 13 | # echo 'www.example.com' > CNAME 14 | 15 | git init 16 | git add -A 17 | git commit -m 'deploy' 18 | 19 | # 如果发布到 https://.github.io 20 | # git push -f git@github.com:/.github.io.git master 21 | 22 | # 如果发布到 https://.github.io/ 23 | git push -f git@gitee.com:fairyever/d2-admin-start-kit-preview.git master 24 | 25 | cd - 26 | 27 | echo "publish to https://fairyever.gitee.io/d2-admin-start-kit-preview/#/index" -------------------------------------------------------------------------------- /app/public/admin/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d2-admin-start-kit", 3 | "version": "1.1.4", 4 | "description": "A management system framework based on element", 5 | "author": "李杨 <1711467488@qq.com>", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "lint": "eslint --fix --ext .js,.vue src", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.17.1", 15 | "babel-polyfill": "^6.26.0", 16 | "better-scroll": "^1.12.1", 17 | "dayjs": "^1.7.2", 18 | "element-ui": "^2.0.11", 19 | "js-cookie": "^2.2.0", 20 | "lodash.uniqueid": "^4.0.1", 21 | "lowdb": "^1.0.0", 22 | "mavon-editor": "^2.6.15", 23 | "moment": "^2.22.2", 24 | "particles.js": "^2.0.0", 25 | "qiniu-js": "^2.4.0", 26 | "screenfull": "^3.3.2", 27 | "vue": "^2.5.2", 28 | "vue-router": "^3.0.1", 29 | "vuex": "^3.0.1" 30 | }, 31 | "devDependencies": { 32 | "autoprefixer": "^7.1.2", 33 | "babel-core": "^6.22.1", 34 | "babel-eslint": "^7.1.1", 35 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 36 | "babel-loader": "^7.1.1", 37 | "babel-plugin-syntax-jsx": "^6.18.0", 38 | "babel-plugin-transform-runtime": "^6.22.0", 39 | "babel-plugin-transform-vue-jsx": "^3.5.0", 40 | "babel-preset-env": "^1.3.2", 41 | "babel-preset-stage-2": "^6.22.0", 42 | "chalk": "^2.0.1", 43 | "copy-webpack-plugin": "^4.0.1", 44 | "css-loader": "^0.28.0", 45 | "eslint": "^3.19.0", 46 | "eslint-config-standard": "^10.2.1", 47 | "eslint-friendly-formatter": "^3.0.0", 48 | "eslint-loader": "^1.7.1", 49 | "eslint-plugin-html": "^3.0.0", 50 | "eslint-plugin-import": "^2.7.0", 51 | "eslint-plugin-node": "^5.2.0", 52 | "eslint-plugin-promise": "^3.4.0", 53 | "eslint-plugin-standard": "^3.0.1", 54 | "extract-text-webpack-plugin": "^3.0.0", 55 | "file-loader": "^1.1.4", 56 | "friendly-errors-webpack-plugin": "^1.6.1", 57 | "html-webpack-plugin": "^2.30.1", 58 | "node-notifier": "^5.1.2", 59 | "node-sass": "^4.7.2", 60 | "optimize-css-assets-webpack-plugin": "^3.2.0", 61 | "ora": "^1.2.0", 62 | "portfinder": "^1.0.13", 63 | "postcss-import": "^11.0.0", 64 | "postcss-loader": "^2.0.8", 65 | "rimraf": "^2.6.0", 66 | "sass-loader": "^6.0.6", 67 | "semver": "^5.5.0", 68 | "shelljs": "^0.7.6", 69 | "stylus": "^0.54.5", 70 | "stylus-loader": "^3.0.2", 71 | "uglifyjs-webpack-plugin": "^1.1.1", 72 | "url-loader": "^0.5.8", 73 | "vue-loader": "^13.3.0", 74 | "vue-style-loader": "^3.0.1", 75 | "vue-template-compiler": "^2.5.2", 76 | "webpack": "^3.6.0", 77 | "webpack-bundle-analyzer": "^2.9.0", 78 | "webpack-dev-server": "^2.9.1", 79 | "webpack-merge": "^4.1.0" 80 | }, 81 | "engines": { 82 | "node": ">= 4.0.0", 83 | "npm": ">= 3.0.0" 84 | }, 85 | "browserslist": [ 86 | "> 1%", 87 | "last 2 versions", 88 | "not ie <= 8" 89 | ] 90 | } 91 | -------------------------------------------------------------------------------- /app/public/admin/src/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/HELP-US-OUT.txt: -------------------------------------------------------------------------------- 1 | I hope you love Font Awesome. If you've found it useful, please do me a favor and check out my latest project, 2 | Fort Awesome (https://fortawesome.com). It makes it easy to put the perfect icons on your website. Choose from our awesome, 3 | comprehensive icon sets or copy and paste your own. 4 | 5 | Please. Check it out. 6 | 7 | -Dave Gandy 8 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/src/assets/library/font-awesome-4.7.0/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/src/assets/library/font-awesome-4.7.0/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/src/assets/library/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/src/assets/library/font-awesome-4.7.0/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/src/assets/library/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/less/animated.less: -------------------------------------------------------------------------------- 1 | // Animated Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .@{fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .@{fa-css-prefix}-pull-left { float: left; } 11 | .@{fa-css-prefix}-pull-right { float: right; } 12 | 13 | .@{fa-css-prefix} { 14 | &.@{fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.@{fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .@{fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables.less"; 7 | @import "mixins.less"; 8 | @import "path.less"; 9 | @import "core.less"; 10 | @import "larger.less"; 11 | @import "fixed-width.less"; 12 | @import "list.less"; 13 | @import "bordered-pulled.less"; 14 | @import "animated.less"; 15 | @import "rotated-flipped.less"; 16 | @import "stacked.less"; 17 | @import "icons.less"; 18 | @import "screen-reader.less"; 19 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: (-@fa-li-width + (4em / 14)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | .fa-icon-rotate(@degrees, @rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})"; 16 | -webkit-transform: rotate(@degrees); 17 | -ms-transform: rotate(@degrees); 18 | transform: rotate(@degrees); 19 | } 20 | 21 | .fa-icon-flip(@horiz, @vert, @rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)"; 23 | -webkit-transform: scale(@horiz, @vert); 24 | -ms-transform: scale(@horiz, @vert); 25 | transform: scale(@horiz, @vert); 26 | } 27 | 28 | 29 | // Only display content to screen readers. A la Bootstrap 4. 30 | // 31 | // See: http://a11yproject.com/posts/how-to-hide-content/ 32 | 33 | .sr-only() { 34 | position: absolute; 35 | width: 1px; 36 | height: 1px; 37 | padding: 0; 38 | margin: -1px; 39 | overflow: hidden; 40 | clip: rect(0,0,0,0); 41 | border: 0; 42 | } 43 | 44 | // Use in conjunction with .sr-only to only display content when it's focused. 45 | // 46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 47 | // 48 | // Credit: HTML5 Boilerplate 49 | 50 | .sr-only-focusable() { 51 | &:active, 52 | &:focus { 53 | position: static; 54 | width: auto; 55 | height: auto; 56 | margin: 0; 57 | overflow: visible; 58 | clip: auto; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'), 9 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 10 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 11 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .@{fa-css-prefix}-rotate-90, 15 | :root .@{fa-css-prefix}-rotate-180, 16 | :root .@{fa-css-prefix}-rotate-270, 17 | :root .@{fa-css-prefix}-flip-horizontal, 18 | :root .@{fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/less/screen-reader.less: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { .sr-only(); } 5 | .sr-only-focusable { .sr-only-focusable(); } 6 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/scss/_animated.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .#{$fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .#{$fa-css-prefix}-pull-left { float: left; } 11 | .#{$fa-css-prefix}-pull-right { float: right; } 12 | 13 | .#{$fa-css-prefix} { 14 | &.#{$fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.#{$fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .#{$fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | @mixin fa-icon-rotate($degrees, $rotation) { 15 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})"; 16 | -webkit-transform: rotate($degrees); 17 | -ms-transform: rotate($degrees); 18 | transform: rotate($degrees); 19 | } 20 | 21 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 22 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)"; 23 | -webkit-transform: scale($horiz, $vert); 24 | -ms-transform: scale($horiz, $vert); 25 | transform: scale($horiz, $vert); 26 | } 27 | 28 | 29 | // Only display content to screen readers. A la Bootstrap 4. 30 | // 31 | // See: http://a11yproject.com/posts/how-to-hide-content/ 32 | 33 | @mixin sr-only { 34 | position: absolute; 35 | width: 1px; 36 | height: 1px; 37 | padding: 0; 38 | margin: -1px; 39 | overflow: hidden; 40 | clip: rect(0,0,0,0); 41 | border: 0; 42 | } 43 | 44 | // Use in conjunction with .sr-only to only display content when it's focused. 45 | // 46 | // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 47 | // 48 | // Credit: HTML5 Boilerplate 49 | 50 | @mixin sr-only-focusable { 51 | &:active, 52 | &:focus { 53 | position: static; 54 | width: auto; 55 | height: auto; 56 | margin: 0; 57 | overflow: visible; 58 | clip: auto; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'), 9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .#{$fa-css-prefix}-rotate-90, 15 | :root .#{$fa-css-prefix}-rotate-180, 16 | :root .#{$fa-css-prefix}-rotate-270, 17 | :root .#{$fa-css-prefix}-flip-horizontal, 18 | :root .#{$fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/scss/_screen-reader.scss: -------------------------------------------------------------------------------- 1 | // Screen Readers 2 | // ------------------------- 3 | 4 | .sr-only { @include sr-only(); } 5 | .sr-only-focusable { @include sr-only-focusable(); } 6 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; } 21 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/library/font-awesome-4.7.0/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "animated"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | @import "screen-reader"; 19 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/style/animate/vue-transition.scss: -------------------------------------------------------------------------------- 1 | // 过渡动画 横向渐变 2 | .fade-transverse-leave-active, 3 | .fade-transverse-enter-active { 4 | transition: all .5s; 5 | } 6 | .fade-transverse-enter { 7 | opacity: 0; 8 | transform: translateX(-30px); 9 | } 10 | .fade-transverse-leave-to { 11 | opacity: 0; 12 | transform: translateX(30px); 13 | } 14 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/style/fixed/base.scss: -------------------------------------------------------------------------------- 1 | // 优化显示 2 | 3 | html, body { 4 | margin: 0px; 5 | height: 100%; 6 | font-family: "PingFang SC", "Arial", "Microsoft YaHei", "黑体", "宋体", sans-serif; 7 | #app { 8 | position: absolute; 9 | top: 0px; 10 | bottom: 0px; 11 | left: 0px; 12 | right: 0px; 13 | overflow: hidden; 14 | a { 15 | text-decoration: none; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/style/fixed/element.scss: -------------------------------------------------------------------------------- 1 | // element 样式补丁 2 | 3 | .el-card { 4 | &.is-always-shadow { 5 | box-shadow: 0 0 8px 0 rgba(232,237,250,.6), 0 2px 4px 0 rgba(232,237,250,.5); 6 | } 7 | &.is-hover-shadow { 8 | &:hover { 9 | box-shadow: 0 0 8px 0 rgba(232,237,250,.6), 0 2px 4px 0 rgba(232,237,250,.5); 10 | } 11 | } 12 | } 13 | 14 | .el-menu--horizontal { 15 | border-bottom: none !important; 16 | } -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/style/public-class.scss: -------------------------------------------------------------------------------- 1 | @import 'public'; 2 | 3 | // 补丁 base 4 | @import '~@/assets/style/fixed/base.scss'; 5 | // 补丁 element 6 | @import '~@/assets/style/fixed/element.scss'; 7 | 8 | // 动画 9 | @import '~@/assets/style/animate/vue-transition.scss'; 10 | 11 | // 在这里写公用的class 12 | // 注意 这个文件里只写class 13 | // mixin等内容请在 public.scss 里书写 14 | 15 | // 文字相关 16 | .#{$prefix}-text-center { 17 | text-align: center; 18 | } 19 | 20 | // 浮动相关 21 | .#{$prefix}-fl { 22 | float: left; 23 | } 24 | .#{$prefix}-fr { 25 | float: right; 26 | } 27 | .#{$prefix}-clearfix:before, 28 | .#{$prefix}-clearfix:after { 29 | display: table; 30 | content: ""; 31 | } 32 | .#{$prefix}-clearfix:after { 33 | clear: both 34 | } 35 | 36 | // 边距相关 37 | $sizes: (0, 10, 15, 20); 38 | 39 | @for $index from 1 to 5 { 40 | .#{$prefix}-m-#{nth($sizes, $index)} { margin: #{nth($sizes, $index)}px !important; } 41 | .#{$prefix}-mt-#{nth($sizes, $index)} { margin-top: #{nth($sizes, $index)}px !important; } 42 | .#{$prefix}-mr-#{nth($sizes, $index)} { margin-right: #{nth($sizes, $index)}px !important; } 43 | .#{$prefix}-mb-#{nth($sizes, $index)} { margin-bottom: #{nth($sizes, $index)}px !important; } 44 | .#{$prefix}-ml-#{nth($sizes, $index)} { margin-left: #{nth($sizes, $index)}px !important; } 45 | 46 | .#{$prefix}-p-#{nth($sizes, $index)} { padding: #{nth($sizes, $index)}px !important; } 47 | .#{$prefix}-pt-#{nth($sizes, $index)} { padding-top: #{nth($sizes, $index)}px !important; } 48 | .#{$prefix}-pr-#{nth($sizes, $index)} { padding-right: #{nth($sizes, $index)}px !important; } 49 | .#{$prefix}-pb-#{nth($sizes, $index)} { padding-bottom: #{nth($sizes, $index)}px !important; } 50 | .#{$prefix}-pl-#{nth($sizes, $index)} { padding-left: #{nth($sizes, $index)}px !important; } 51 | } 52 | 53 | // 快速使用 54 | 55 | .#{$prefix}-m { margin: 20px !important; } 56 | .#{$prefix}-mt { margin-top: 20px !important; } 57 | .#{$prefix}-mr { margin-right: 20px !important; } 58 | .#{$prefix}-mb { margin-bottom: 20px !important; } 59 | .#{$prefix}-ml { margin-left: 20px !important; } 60 | 61 | .#{$prefix}-p { padding: 20px !important; } 62 | .#{$prefix}-pt { padding-top: 20px !important; } 63 | .#{$prefix}-pr { padding-right: 20px !important; } 64 | .#{$prefix}-pb { padding-bottom: 20px !important; } 65 | .#{$prefix}-pl { padding-left: 20px !important; } 66 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/style/public.scss: -------------------------------------------------------------------------------- 1 | @import '~@/assets/style/unit/_color.scss'; 2 | 3 | // 工具类名统一前缀 4 | $prefix: d2; 5 | 6 | // 禁止用户选中 鼠标变为手形 7 | %unable-select { 8 | user-select: none; 9 | cursor: pointer; 10 | } 11 | 12 | %card { 13 | border: 1px solid #dddee1; 14 | border-color: #e9eaec; 15 | background: #fff; 16 | border-radius: 4px; 17 | font-size: 14px; 18 | position: relative; 19 | } -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/style/theme/d2/index.scss: -------------------------------------------------------------------------------- 1 | @import './setting.scss'; 2 | @import '../theme.scss'; 3 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/style/theme/d2/setting.scss: -------------------------------------------------------------------------------- 1 | // 主题名称 2 | $theme-name: 'd2'; 3 | // 主题背景颜色 4 | $theme-bg-color: #ebf1f6; 5 | // 主题背景图片遮罩 6 | $theme-bg-mask: rgba(#000, 0); 7 | 8 | // container组件 9 | $theme-container-background-color: rgba(#FFF, 1); 10 | $theme-container-header-footer-background-color: #FFF; 11 | $theme-container-border-inner: 1px solid #cfd7e5; 12 | $theme-container-border-outer: 1px solid #cfd7e5; 13 | 14 | $theme-multiple-page-control-color: $color-text-normal; 15 | $theme-multiple-page-control-color-active: #2f74ff; 16 | $theme-multiple-page-control-nav-prev-color: #cfd7e5; 17 | $theme-multiple-page-control-nav-next-color: #cfd7e5; 18 | $theme-multiple-page-control-border-color: #cfd7e5; 19 | $theme-multiple-page-control-border-color-active: #FFF; 20 | $theme-multiple-page-control-background-color: rgba(#000, .03); 21 | $theme-multiple-page-control-background-color-active: #FFF; 22 | 23 | // 顶栏和侧边栏中展开的菜单 hover 状态下 24 | $theme-menu-item-color-hover: #293849; 25 | $theme-menu-item-background-color-hover: #ecf5ff; 26 | 27 | // 顶栏上的文字颜色 28 | $theme-header-item-color: $color-text-normal; 29 | $theme-header-item-background-color: transparent; 30 | // 顶栏上的项目在 hover 时 31 | $theme-header-item-color-hover: #2f74ff; 32 | $theme-header-item-background-color-hover: rgba(#FFF, .5); 33 | // 顶栏上的项目在 focus 时 34 | $theme-header-item-color-focus: #2f74ff; 35 | $theme-header-item-background-color-focus: rgba(#FFF, .5); 36 | // 顶栏上的项目在 active 时 37 | $theme-header-item-color-active: #2f74ff; 38 | $theme-header-item-background-color-active: rgba(#FFF, .5); 39 | 40 | // 侧边栏上的文字颜色 41 | $theme-aside-item-color: $color-text-normal; 42 | $theme-aside-item-background-color: transparent; 43 | // 侧边栏上的项目在 hover 时 44 | $theme-aside-item-color-hover: #2f74ff; 45 | $theme-aside-item-background-color-hover: rgba(#FFF, .5); 46 | // 侧边栏上的项目在 focus 时 47 | $theme-aside-item-color-focus: #2f74ff; 48 | $theme-aside-item-background-color-focus: rgba(#FFF, .5); 49 | // 侧边栏上的项目在 active 时 50 | $theme-aside-item-color-active: #2f74ff; 51 | $theme-aside-item-background-color-active: rgba(#FFF, .5); 52 | 53 | // 侧边栏菜单为空的时候显示的元素 54 | $theme-aside-menu-empty-icon-color: $color-text-normal; 55 | $theme-aside-menu-empty-text-color: $color-text-normal; 56 | $theme-aside-menu-empty-background-color: rgba(#000, .03); 57 | $theme-aside-menu-empty-icon-color-hover: $color-text-main; 58 | $theme-aside-menu-empty-text-color-hover: $color-text-main; 59 | $theme-aside-menu-empty-background-color-hover: rgba(#000, .05); -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/style/theme/list.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | title: 'd2admin 经典', 4 | name: 'd2', 5 | preview: 'static/image/theme/d2/preview@2x.png' 6 | }, 7 | { 8 | title: '紫罗兰', 9 | name: 'violet', 10 | preview: 'static/image/theme/violet/preview@2x.png' 11 | }, 12 | { 13 | title: '流星', 14 | name: 'star', 15 | backgroundImage: 'static/image/bg/star-squashed.jpg', 16 | preview: 'static/image/theme/star/preview@2x.png' 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/style/theme/register.scss: -------------------------------------------------------------------------------- 1 | @import '~@/assets/style/public.scss'; 2 | @import '~@/assets/style/theme/theme-base.scss'; 3 | 4 | @import '~@/assets/style/theme/d2/index.scss'; 5 | @import '~@/assets/style/theme/star/index.scss'; 6 | @import '~@/assets/style/theme/violet/index.scss'; -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/style/theme/star/index.scss: -------------------------------------------------------------------------------- 1 | @import './setting.scss'; 2 | @import '../theme.scss'; 3 | -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/style/theme/star/setting.scss: -------------------------------------------------------------------------------- 1 | // 主题名称 2 | $theme-name: 'star'; 3 | // 主题背景颜色 4 | $theme-bg-color: #EFF4F8; 5 | // 主题背景图片遮罩 6 | $theme-bg-mask: rgba(#000, .3); 7 | 8 | // container组件 9 | $theme-container-background-color: rgba(#FFF, .9); 10 | $theme-container-header-footer-background-color: #FFF; 11 | $theme-container-border-inner: 1px solid $color-border-1; 12 | $theme-container-border-outer: 1px solid #114450; 13 | 14 | $theme-multiple-page-control-color: #FFF; 15 | $theme-multiple-page-control-color-active: $color-text-normal; 16 | $theme-multiple-page-control-nav-prev-color: #FFF; 17 | $theme-multiple-page-control-nav-next-color: #FFF; 18 | $theme-multiple-page-control-border-color: #114450; 19 | $theme-multiple-page-control-border-color-active: #FFF; 20 | $theme-multiple-page-control-background-color: rgba(#FFF, .5); 21 | $theme-multiple-page-control-background-color-active: #FFF; 22 | 23 | // 顶栏和侧边栏中展开的菜单 hover 状态下 24 | $theme-menu-item-color-hover: #293849; 25 | $theme-menu-item-background-color-hover: #ecf5ff; 26 | 27 | // 顶栏上的文字颜色 28 | $theme-header-item-color: #FFF; 29 | $theme-header-item-background-color: transparent; 30 | // 顶栏上的项目在 hover 时 31 | $theme-header-item-color-hover: #FFF; 32 | $theme-header-item-background-color-hover: rgba(#000, .2); 33 | // 顶栏上的项目在 focus 时 34 | $theme-header-item-color-focus: #FFF; 35 | $theme-header-item-background-color-focus: rgba(#000, .2); 36 | // 顶栏上的项目在 active 时 37 | $theme-header-item-color-active: #FFF; 38 | $theme-header-item-background-color-active: rgba(#000, .3); 39 | 40 | // 侧边栏上的文字颜色 41 | $theme-aside-item-color: #FFF; 42 | $theme-aside-item-background-color: transparent; 43 | // 侧边栏上的项目在 hover 时 44 | $theme-aside-item-color-hover: #FFF; 45 | $theme-aside-item-background-color-hover: rgba(#000, .2); 46 | // 侧边栏上的项目在 focus 时 47 | $theme-aside-item-color-focus: #FFF; 48 | $theme-aside-item-background-color-focus: rgba(#000, .2); 49 | // 侧边栏上的项目在 active 时 50 | $theme-aside-item-color-active: #FFF; 51 | $theme-aside-item-background-color-active: rgba(#000, .3); 52 | 53 | // 侧边栏菜单为空的时候显示的元素 54 | $theme-aside-menu-empty-icon-color: #FFF; 55 | $theme-aside-menu-empty-text-color: #FFF; 56 | $theme-aside-menu-empty-background-color: rgba(#FFF, .2); 57 | $theme-aside-menu-empty-icon-color-hover: #FFF; 58 | $theme-aside-menu-empty-text-color-hover: #FFF; 59 | $theme-aside-menu-empty-background-color-hover: rgba(#FFF, .3); -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/style/theme/violet/index.scss: -------------------------------------------------------------------------------- 1 | @import './setting.scss'; 2 | @import '../theme.scss'; 3 | 4 | .theme-#{$theme-name} { 5 | .d2-layout-main-group { 6 | background: #bc00e3; 7 | background: linear-gradient(120deg, #bc00e3 0%, #4EFFFB 100%); 8 | } 9 | } -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/style/theme/violet/setting.scss: -------------------------------------------------------------------------------- 1 | // 主题名称 2 | $theme-name: 'violet'; 3 | // 主题背景颜色 4 | $theme-bg-color: #000; 5 | // 主题背景图片遮罩 6 | $theme-bg-mask: rgba(#000, 0); 7 | 8 | // container组件 9 | $theme-container-background-color: #FFF; 10 | $theme-container-header-footer-background-color: #FFF; 11 | $theme-container-border-inner: 1px solid $color-border-2; 12 | $theme-container-border-outer: 1px solid #8C40E2; 13 | 14 | $theme-multiple-page-control-color: #FFF; 15 | $theme-multiple-page-control-color-active: $color-text-normal; 16 | $theme-multiple-page-control-nav-prev-color: #FFF; 17 | $theme-multiple-page-control-nav-next-color: #FFF; 18 | $theme-multiple-page-control-border-color: #8C40E2; 19 | $theme-multiple-page-control-border-color-active: #FFF; 20 | $theme-multiple-page-control-background-color: rgba(#FFF, .3); 21 | $theme-multiple-page-control-background-color-active: #FFF; 22 | 23 | // 顶栏和侧边栏中展开的菜单 hover 状态下 24 | $theme-menu-item-color-hover: #293849; 25 | $theme-menu-item-background-color-hover: #ecf5ff; 26 | 27 | // 顶栏上的文字颜色 28 | $theme-header-item-color: #FFF; 29 | $theme-header-item-background-color: transparent; 30 | // 顶栏上的项目在 hover 时 31 | $theme-header-item-color-hover: #FFF; 32 | $theme-header-item-background-color-hover: linear-gradient(-180deg, rgba(255,255,255,0.18) 0%, rgba(255,255,255,0.12) 100%); 33 | // 顶栏上的项目在 focus 时 34 | $theme-header-item-color-focus: #FFF; 35 | $theme-header-item-background-color-focus: linear-gradient(-180deg, rgba(255,255,255,0.18) 0%, rgba(255,255,255,0.12) 100%); 36 | // 顶栏上的项目在 active 时 37 | $theme-header-item-color-active: #FFF; 38 | $theme-header-item-background-color-active: linear-gradient(-180deg, rgba(255,255,255,0.18) 0%, rgba(255,255,255,0.12) 100%); 39 | 40 | // 侧边栏上的文字颜色 41 | $theme-aside-item-color: #FFF; 42 | $theme-aside-item-background-color: transparent; 43 | // 侧边栏上的项目在 hover 时 44 | $theme-aside-item-color-hover: #FFF; 45 | $theme-aside-item-background-color-hover: linear-gradient(90deg, rgba(255,255,255,0.28) 0%, rgba(255,255,255,0.00) 100%); 46 | // 侧边栏上的项目在 focus 时 47 | $theme-aside-item-color-focus: #FFF; 48 | $theme-aside-item-background-color-focus: linear-gradient(90deg, rgba(255,255,255,0.28) 0%, rgba(255,255,255,0.00) 100%); 49 | // 侧边栏上的项目在 active 时 50 | $theme-aside-item-color-active: #FFF; 51 | $theme-aside-item-background-color-active: linear-gradient(90deg, rgba(255,255,255,0.28) 0%, rgba(255,255,255,0.00) 100%); 52 | 53 | // 侧边栏菜单为空的时候显示的元素 54 | $theme-aside-menu-empty-icon-color: #FFF; 55 | $theme-aside-menu-empty-text-color: #FFF; 56 | $theme-aside-menu-empty-background-color: rgba(#000, .1); 57 | $theme-aside-menu-empty-icon-color-hover: #FFF; 58 | $theme-aside-menu-empty-text-color-hover: #FFF; 59 | $theme-aside-menu-empty-background-color-hover: rgba(#000, .15); -------------------------------------------------------------------------------- /app/public/admin/src/src/assets/style/unit/_color.scss: -------------------------------------------------------------------------------- 1 | // 主色 2 | $color-primary: #409EFF; 3 | 4 | // 辅助色 5 | $color-info: #909399; 6 | $color-success: #67C23A; 7 | $color-warning: #E6A23C; 8 | $color-danger: #F56C6C; 9 | 10 | // 文字 11 | $color-text-main: #303133; 12 | $color-text-normal: #606266; 13 | $color-text-sub: #909399; 14 | $color-text-placehoder: #C0C4CC; 15 | 16 | // 边框 17 | $color-border-1: #DCDFE6; 18 | $color-border-2: #E4E7ED; 19 | $color-border-3: #EBEEF5; 20 | $color-border-4: #F2F6FC; 21 | 22 | // 背景 23 | $color-bg: #f8f8f9; -------------------------------------------------------------------------------- /app/public/admin/src/src/components/core/d2-container/components/d2-container-full-bs.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 52 | -------------------------------------------------------------------------------- /app/public/admin/src/src/components/core/d2-container/components/d2-container-full.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /app/public/admin/src/src/components/core/d2-container/index.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 95 | 96 | 116 | -------------------------------------------------------------------------------- /app/public/admin/src/src/components/core/d2-icon/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | -------------------------------------------------------------------------------- /app/public/admin/src/src/components/core/d2-layout-main/components/-full-screen/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 28 | -------------------------------------------------------------------------------- /app/public/admin/src/src/components/core/d2-layout-main/components/-github/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 28 | 29 | 30 | 36 | -------------------------------------------------------------------------------- /app/public/admin/src/src/components/core/d2-layout-main/components/-help/index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 35 | 36 | 43 | -------------------------------------------------------------------------------- /app/public/admin/src/src/components/core/d2-layout-main/components/-menu-header/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 33 | -------------------------------------------------------------------------------- /app/public/admin/src/src/components/core/d2-layout-main/components/-menu-item/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 26 | -------------------------------------------------------------------------------- /app/public/admin/src/src/components/core/d2-layout-main/components/-menu-side/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 101 | -------------------------------------------------------------------------------- /app/public/admin/src/src/components/core/d2-layout-main/components/-menu-sub/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 38 | -------------------------------------------------------------------------------- /app/public/admin/src/src/components/core/d2-layout-main/components/-theme/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 23 | -------------------------------------------------------------------------------- /app/public/admin/src/src/components/core/d2-layout-main/components/-user/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 51 | 57 | -------------------------------------------------------------------------------- /app/public/admin/src/src/components/core/d2-layout-main/components/mixin/menu.js: -------------------------------------------------------------------------------- 1 | export default { 2 | methods: { 3 | handleMenuSelect (index, indexPath) { 4 | if (/^d2-menu-empty-\d+$/.test(index)) { 5 | this.$message('功能正在开发') 6 | } else { 7 | this.$router.push({ 8 | path: index 9 | }) 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/public/admin/src/src/components/core/d2-layout-main/index.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 104 | 105 | 109 | -------------------------------------------------------------------------------- /app/public/admin/src/src/components/core/d2-page-cover/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 34 | 35 | 67 | -------------------------------------------------------------------------------- /app/public/admin/src/src/components/core/d2-theme-list/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 48 | 49 | 59 | -------------------------------------------------------------------------------- /app/public/admin/src/src/components/core/register.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import d2Container from '@/components/core/d2-container' 4 | import d2MultiplePageControl from '@/components/core/d2-multiple-page-control' 5 | 6 | Vue.component('d2-container', d2Container) 7 | Vue.component('d2-multiple-page-control', d2MultiplePageControl) 8 | 9 | Vue.component('d2-icon', () => import('@/components/core/d2-icon')) 10 | Vue.component('d2-theme-list', () => import('@/components/core/d2-theme-list')) 11 | Vue.component('d2-page-cover', () => import('@/components/core/d2-page-cover')) 12 | -------------------------------------------------------------------------------- /app/public/admin/src/src/components/index.js: -------------------------------------------------------------------------------- 1 | // 核心组件 2 | import './core/register' 3 | -------------------------------------------------------------------------------- /app/public/admin/src/src/libs/db.js: -------------------------------------------------------------------------------- 1 | import low from 'lowdb' 2 | import LocalStorage from 'lowdb/adapters/LocalStorage' 3 | 4 | const adapter = new LocalStorage('d2admin') 5 | const db = low(adapter) 6 | 7 | db.defaults({ 8 | themeActiveName: [], 9 | pageOpenedList: [], 10 | updateNotify: [], 11 | username: [] 12 | }) 13 | .write() 14 | 15 | export default db 16 | -------------------------------------------------------------------------------- /app/public/admin/src/src/libs/util.js: -------------------------------------------------------------------------------- 1 | // 插件 2 | import Cookies from 'js-cookie' 3 | import axios from 'axios' 4 | import semver from 'semver' 5 | import dayjs from 'dayjs' 6 | 7 | // 获取项目信息 8 | import packJson from '../../package.json' 9 | 10 | let util = {} 11 | 12 | /** 13 | * @description 得到现在的用户 14 | */ 15 | util.uuid = function () { 16 | return Cookies.get('uuid') 17 | } 18 | 19 | /** 20 | * @description 更新标题 21 | * @param {string} title 标题 22 | */ 23 | util.title = function (title) { 24 | const prefix = 'wadejs' 25 | window.document.title = `${prefix}${title ? ` | ${title}` : ''}` 26 | } 27 | 28 | /** 29 | * @description 在每次打开新页面的时候调用 打开一个新的 tab 30 | * @param {object} vm vue 31 | * @param {string} name route name 32 | * @param {object} argu arguments 33 | * @param {object} query query object 34 | */ 35 | util.openNewPage = function (vm, name, argu, query) { 36 | // 已经打开的页面 37 | let pageOpenedList = vm.$store.state.d2admin.pageOpenedList 38 | // 判断此页面是否已经打开 并且记录位置 39 | let pageOpendIndex = 0 40 | const pageOpend = pageOpenedList.find((page, index) => { 41 | const same = page.name === name 42 | pageOpendIndex = same ? index : pageOpendIndex 43 | return same 44 | }) 45 | if (pageOpend) { 46 | // 页面以前打开过 但是新的页面可能 name 一样,参数不一样 47 | vm.$store.commit('d2adminPageOpenedListUpdateItem', { index: pageOpendIndex, argu, query }) 48 | } else { 49 | // 页面以前没有打开过 50 | const tagPool = vm.$store.state.d2admin.tagPool 51 | let tag = tagPool.find(t => t.name === name) 52 | if (tag) { 53 | vm.$store.commit('d2adminTagIncreate', { tag, argu, query }) 54 | } 55 | } 56 | vm.$store.commit('d2adminPageCurrentSet', name) 57 | } 58 | 59 | /** 60 | * @description 判断是否在其内 61 | * @param {*} ele element 62 | * @param {array} targetArr array 63 | */ 64 | util.isOneOf = function (ele, targetArr) { 65 | if (targetArr.indexOf(ele) >= 0) { 66 | return true 67 | } else { 68 | return false 69 | } 70 | } 71 | 72 | /** 73 | * @description 检查版本更新 74 | * @param {object} vm vue 75 | */ 76 | // TODO: 添加 跳过此版本 选项 77 | util.checkUpdate = function (vm) { 78 | axios.get('https://api.github.com/repos/FairyEver/d2-admin/releases/latest') 79 | .then(res => { 80 | let version = res.tag_name 81 | const update = semver.lt(packJson.version, version) 82 | if (vm.$env === 'development' && update && vm.$store.state.d2admin.updateNotify) { 83 | vm.$nextTick(() => { 84 | vm.$notify({ 85 | title: `D2Admin 新版本 ${res.name}`, 86 | duration: 0, 87 | dangerouslyUseHTMLString: true, 88 | message: ` 89 |

当前版本: ${packJson.version}

90 |

${dayjs(res.created_at).format('YYYY年M月D日')}更新 版本号: ${res.tag_name}

91 |

此信息只在开发环境提示

92 |

93 | 98 | 99 | 详细信息 100 | 101 |

102 | `.trim() 103 | }) 104 | }) 105 | } 106 | vm.$store.commit('d2adminUpdateSet', update) 107 | vm.$store.commit('d2adminReleasesSet', res) 108 | }) 109 | .catch(err => { 110 | console.log('checkUpdate error', err) 111 | }) 112 | } 113 | 114 | export default util 115 | -------------------------------------------------------------------------------- /app/public/admin/src/src/main.js: -------------------------------------------------------------------------------- 1 | // polyfill 2 | import 'babel-polyfill' 3 | 4 | // Vue 5 | import Vue from 'vue' 6 | import App from './App' 7 | 8 | // 工具 9 | // import util from '@/libs/util' 10 | 11 | // vuex 12 | import store from '@/store/index' 13 | 14 | // 路由 15 | import router from './router' 16 | // 框架内的路由 17 | import { frameInRoutes } from '@/router/routes' 18 | 19 | // ElementUI 20 | import ElementUI from 'element-ui' 21 | import 'element-ui/lib/theme-chalk/index.css' 22 | 23 | // font-awesome 24 | import '@/assets/library/font-awesome-4.7.0/css/font-awesome.min.css' 25 | 26 | // 全屏控制 27 | import screenfull from 'screenfull' 28 | 29 | // 全局注册的组件 30 | import '@/components' 31 | 32 | // 异步请求库 33 | import '@/plugin/axios' 34 | 35 | // 打包的设置 用户获取路径 36 | import buildConfig from '../config/index' 37 | 38 | // markdown插件 39 | import mavonEditor from 'mavon-editor' 40 | import 'mavon-editor/dist/css/index.css' 41 | 42 | Vue.use(mavonEditor) 43 | Vue.use(ElementUI) 44 | 45 | Vue.config.productionTip = false 46 | 47 | Vue.prototype.$env = process.env.NODE_ENV 48 | 49 | Vue.prototype.$assetsPublicPath = process.env.NODE_ENV === 'development' ? buildConfig.dev.assetsPublicPath : buildConfig.build.assetsPublicPath 50 | 51 | /* eslint-disable no-new */ 52 | new Vue({ 53 | el: '#app', 54 | store, 55 | router, 56 | template: '', 57 | components: { App }, 58 | created () { 59 | // 处理路由 得到每一级的路由设置 60 | this.getAllTagFromRoutes() 61 | }, 62 | mounted () { 63 | // DB -> store 加载用户名 64 | this.$store.commit('d2adminUsernameLoad') 65 | // DB -> store 加载主题 66 | this.$store.commit('d2adminThemeLoad') 67 | // DB -> store 数据库加载上次退出时的多页列表 68 | this.$store.commit('d2adminPageOpenedListLoad') 69 | // 检测退出全屏 70 | if (screenfull.enabled) { 71 | screenfull.on('change', () => { 72 | if (!screenfull.isFullscreen) { 73 | this.$store.commit('d2adminFullScreenSet', false) 74 | } 75 | }) 76 | } 77 | }, 78 | methods: { 79 | /** 80 | * 处理路由 得到每一级的路由设置 81 | */ 82 | getAllTagFromRoutes () { 83 | // 所有加载在主框架内的页面 84 | const tagPool = [] 85 | const push = function (routes) { 86 | routes.forEach(route => { 87 | if (route.children) { 88 | push(route.children) 89 | } else { 90 | const { meta, name, path } = route 91 | tagPool.push({ meta, name, path }) 92 | } 93 | }) 94 | } 95 | push(frameInRoutes) 96 | this.$store.commit('d2adminTagPoolSet', tagPool) 97 | } 98 | } 99 | }) 100 | -------------------------------------------------------------------------------- /app/public/admin/src/src/menu/index.js: -------------------------------------------------------------------------------- 1 | const create = { 2 | path: '/core/create', 3 | title: '博客管理', 4 | icon: 'cog', 5 | children: (pre => [ 6 | { path: `${pre}index`, title: '博客管理首页', icon: 'pencil' }, 7 | { path: `${pre}newArticle`, title: '创建文章', icon: 'code' }, 8 | // { path: `${pre}editArticle`, title: '编辑文章', icon: 'code' }, 9 | { path: `${pre}newCategory`, title: '创建分类', icon: 'cubes' }, 10 | { path: `${pre}newTag`, title: '创建标签', icon: 'tags' }, 11 | { 12 | // path: `${pre}articleList`, 13 | title: '文章管理', 14 | icon: 'file', 15 | children: [ 16 | { path: `${pre}articleList`, title: '全部文章', icon: 'th-large' }, 17 | { path: `${pre}articleList/draft`, title: '草稿箱', icon: 'save' }, 18 | { path: `${pre}articleList/garbage`, title: '垃圾箱', icon: 'remove' } 19 | ] 20 | } 21 | ])('/core/create/') 22 | } 23 | 24 | // 菜单 侧边栏 25 | export const side = [ 26 | create 27 | ] 28 | 29 | // 菜单 顶栏 30 | export default [ 31 | { 32 | path: '/index', 33 | title: '首页', 34 | icon: 'home' 35 | }, 36 | create 37 | ] 38 | -------------------------------------------------------------------------------- /app/public/admin/src/src/pages/core/404/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 25 | -------------------------------------------------------------------------------- /app/public/admin/src/src/pages/core/create/articleList/index.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 175 | 176 | -------------------------------------------------------------------------------- /app/public/admin/src/src/pages/core/create/editArticle/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 41 | 42 | -------------------------------------------------------------------------------- /app/public/admin/src/src/pages/core/create/index/index.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 175 | 176 | -------------------------------------------------------------------------------- /app/public/admin/src/src/pages/core/create/newArticle/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 25 | 26 | -------------------------------------------------------------------------------- /app/public/admin/src/src/pages/core/index/index.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /app/public/admin/src/src/pages/core/login/config/bubble.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | export default { 4 | "particles": { 5 | "number": { 6 | "value": 6, 7 | "density": { 8 | "enable": true, 9 | "value_area": 800 10 | } 11 | }, 12 | "color": { 13 | "value": "#444" 14 | }, 15 | "shape": { 16 | "type": "polygon", 17 | "stroke": { 18 | "width": 0, 19 | "color": "#000" 20 | }, 21 | "polygon": { 22 | "nb_sides": 6 23 | }, 24 | "image": { 25 | "src": "img/github.svg", 26 | "width": 100, 27 | "height": 100 28 | } 29 | }, 30 | "opacity": { 31 | "value": 0.3, 32 | "random": true, 33 | "anim": { 34 | "enable": false, 35 | "speed": 1, 36 | "opacity_min": 0.1, 37 | "sync": false 38 | } 39 | }, 40 | "size": { 41 | "value": 160, 42 | "random": false, 43 | "anim": { 44 | "enable": true, 45 | "speed": 10, 46 | "size_min": 40, 47 | "sync": false 48 | } 49 | }, 50 | "line_linked": { 51 | "enable": false, 52 | "distance": 200, 53 | "color": "#ffffff", 54 | "opacity": 1, 55 | "width": 2 56 | }, 57 | "move": { 58 | "enable": true, 59 | "speed": 8, 60 | "direction": "none", 61 | "random": false, 62 | "straight": false, 63 | "out_mode": "out", 64 | "bounce": false, 65 | "attract": { 66 | "enable": false, 67 | "rotateX": 600, 68 | "rotateY": 1200 69 | } 70 | } 71 | }, 72 | "interactivity": { 73 | "detect_on": "canvas", 74 | "events": { 75 | "onhover": { 76 | "enable": false, 77 | "mode": "grab" 78 | }, 79 | "onclick": { 80 | "enable": false, 81 | "mode": "push" 82 | }, 83 | "resize": true 84 | }, 85 | "modes": { 86 | "grab": { 87 | "distance": 400, 88 | "line_linked": { 89 | "opacity": 1 90 | } 91 | }, 92 | "bubble": { 93 | "distance": 400, 94 | "size": 40, 95 | "duration": 2, 96 | "opacity": 8, 97 | "speed": 3 98 | }, 99 | "repulse": { 100 | "distance": 200, 101 | "duration": 0.4 102 | }, 103 | "push": { 104 | "particles_nb": 4 105 | }, 106 | "remove": { 107 | "particles_nb": 2 108 | } 109 | } 110 | }, 111 | "retina_detect": true 112 | } 113 | -------------------------------------------------------------------------------- /app/public/admin/src/src/pages/core/login/config/default.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | export default { 4 | "particles": { 5 | "number": { 6 | "value": 80, 7 | "density": { 8 | "enable": true, 9 | "value_area": 800 10 | } 11 | }, 12 | "color": { 13 | "value": "#DCDFE6" 14 | }, 15 | "shape": { 16 | "type": "circle", 17 | "stroke": { 18 | "width": 0, 19 | "color": "#000000" 20 | }, 21 | "polygon": { 22 | "nb_sides": 5 23 | }, 24 | "image": { 25 | "src": "img/github.svg", 26 | "width": 100, 27 | "height": 100 28 | } 29 | }, 30 | "opacity": { 31 | "value": 1, 32 | "random": false, 33 | "anim": { 34 | "enable": false, 35 | "speed": 1, 36 | "opacity_min": 0.1, 37 | "sync": false 38 | } 39 | }, 40 | "size": { 41 | "value": 4, 42 | "random": true, 43 | "anim": { 44 | "enable": false, 45 | "speed": 40, 46 | "size_min": 0.1, 47 | "sync": false 48 | } 49 | }, 50 | "line_linked": { 51 | "enable": true, 52 | "distance": 150, 53 | "color": "#DCDFE6", 54 | "opacity": 0.4, 55 | "width": 2 56 | }, 57 | "move": { 58 | "enable": true, 59 | "speed": 6, 60 | "direction": "none", 61 | "random": false, 62 | "straight": false, 63 | "out_mode": "out", 64 | "bounce": false, 65 | "attract": { 66 | "enable": false, 67 | "rotateX": 600, 68 | "rotateY": 1200 69 | } 70 | } 71 | }, 72 | "interactivity": { 73 | "detect_on": "canvas", 74 | "events": { 75 | "onhover": { 76 | "enable": true, 77 | "mode": "repulse" 78 | }, 79 | "onclick": { 80 | "enable": true, 81 | "mode": "push" 82 | }, 83 | "resize": true 84 | }, 85 | "modes": { 86 | "grab": { 87 | "distance": 400, 88 | "line_linked": { 89 | "opacity": 1 90 | } 91 | }, 92 | "bubble": { 93 | "distance": 400, 94 | "size": 40, 95 | "duration": 2, 96 | "opacity": 8, 97 | "speed": 3 98 | }, 99 | "repulse": { 100 | "distance": 200, 101 | "duration": 0.4 102 | }, 103 | "push": { 104 | "particles_nb": 4 105 | }, 106 | "remove": { 107 | "particles_nb": 2 108 | } 109 | } 110 | }, 111 | "retina_detect": true 112 | } 113 | -------------------------------------------------------------------------------- /app/public/admin/src/src/pages/core/login/config/nasa.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | export default { 4 | "particles": { 5 | "number": { 6 | "value": 160, 7 | "density": { 8 | "enable": true, 9 | "value_area": 800 10 | } 11 | }, 12 | "color": { 13 | "value": "#ffffff" 14 | }, 15 | "shape": { 16 | "type": "circle", 17 | "stroke": { 18 | "width": 0, 19 | "color": "#000000" 20 | }, 21 | "polygon": { 22 | "nb_sides": 5 23 | }, 24 | "image": { 25 | "src": "img/github.svg", 26 | "width": 100, 27 | "height": 100 28 | } 29 | }, 30 | "opacity": { 31 | "value": 1, 32 | "random": true, 33 | "anim": { 34 | "enable": true, 35 | "speed": 1, 36 | "opacity_min": 0, 37 | "sync": false 38 | } 39 | }, 40 | "size": { 41 | "value": 3, 42 | "random": true, 43 | "anim": { 44 | "enable": false, 45 | "speed": 4, 46 | "size_min": 0.3, 47 | "sync": false 48 | } 49 | }, 50 | "line_linked": { 51 | "enable": false, 52 | "distance": 150, 53 | "color": "#ffffff", 54 | "opacity": 0.4, 55 | "width": 1 56 | }, 57 | "move": { 58 | "enable": true, 59 | "speed": 1, 60 | "direction": "none", 61 | "random": true, 62 | "straight": false, 63 | "out_mode": "out", 64 | "bounce": false, 65 | "attract": { 66 | "enable": false, 67 | "rotateX": 600, 68 | "rotateY": 600 69 | } 70 | } 71 | }, 72 | "interactivity": { 73 | "detect_on": "canvas", 74 | "events": { 75 | "onhover": { 76 | "enable": true, 77 | "mode": "bubble" 78 | }, 79 | "onclick": { 80 | "enable": true, 81 | "mode": "repulse" 82 | }, 83 | "resize": true 84 | }, 85 | "modes": { 86 | "grab": { 87 | "distance": 400, 88 | "line_linked": { 89 | "opacity": 1 90 | } 91 | }, 92 | "bubble": { 93 | "distance": 250, 94 | "size": 0, 95 | "duration": 2, 96 | "opacity": 0, 97 | "speed": 3 98 | }, 99 | "repulse": { 100 | "distance": 400, 101 | "duration": 0.4 102 | }, 103 | "push": { 104 | "particles_nb": 4 105 | }, 106 | "remove": { 107 | "particles_nb": 2 108 | } 109 | } 110 | }, 111 | "retina_detect": true 112 | } 113 | -------------------------------------------------------------------------------- /app/public/admin/src/src/pages/core/login/config/snow.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | export default { 4 | "particles": { 5 | "number": { 6 | "value": 400, 7 | "density": { 8 | "enable": true, 9 | "value_area": 800 10 | } 11 | }, 12 | "color": { 13 | "value": "#fff" 14 | }, 15 | "shape": { 16 | "type": "circle", 17 | "stroke": { 18 | "width": 0, 19 | "color": "#000000" 20 | }, 21 | "polygon": { 22 | "nb_sides": 5 23 | }, 24 | "image": { 25 | "src": "img/github.svg", 26 | "width": 100, 27 | "height": 100 28 | } 29 | }, 30 | "opacity": { 31 | "value": 0.5, 32 | "random": true, 33 | "anim": { 34 | "enable": false, 35 | "speed": 1, 36 | "opacity_min": 0.1, 37 | "sync": false 38 | } 39 | }, 40 | "size": { 41 | "value": 10, 42 | "random": true, 43 | "anim": { 44 | "enable": false, 45 | "speed": 40, 46 | "size_min": 0.1, 47 | "sync": false 48 | } 49 | }, 50 | "line_linked": { 51 | "enable": false, 52 | "distance": 500, 53 | "color": "#ffffff", 54 | "opacity": 0.4, 55 | "width": 2 56 | }, 57 | "move": { 58 | "enable": true, 59 | "speed": 6, 60 | "direction": "bottom", 61 | "random": false, 62 | "straight": false, 63 | "out_mode": "out", 64 | "bounce": false, 65 | "attract": { 66 | "enable": false, 67 | "rotateX": 600, 68 | "rotateY": 1200 69 | } 70 | } 71 | }, 72 | "interactivity": { 73 | "detect_on": "canvas", 74 | "events": { 75 | "onhover": { 76 | "enable": true, 77 | "mode": "bubble" 78 | }, 79 | "onclick": { 80 | "enable": true, 81 | "mode": "repulse" 82 | }, 83 | "resize": true 84 | }, 85 | "modes": { 86 | "grab": { 87 | "distance": 400, 88 | "line_linked": { 89 | "opacity": 0.5 90 | } 91 | }, 92 | "bubble": { 93 | "distance": 400, 94 | "size": 4, 95 | "duration": 0.3, 96 | "opacity": 1, 97 | "speed": 3 98 | }, 99 | "repulse": { 100 | "distance": 200, 101 | "duration": 0.4 102 | }, 103 | "push": { 104 | "particles_nb": 4 105 | }, 106 | "remove": { 107 | "particles_nb": 2 108 | } 109 | } 110 | }, 111 | "retina_detect": true 112 | } 113 | -------------------------------------------------------------------------------- /app/public/admin/src/src/pages/core/login/index.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 146 | 147 | 156 | 165 | 166 | -------------------------------------------------------------------------------- /app/public/admin/src/src/pages/core/login/style.scss: -------------------------------------------------------------------------------- 1 | @import '~@/assets/style/public.scss'; 2 | .login-page { 3 | background-color: #EDF4FA; 4 | height: 100%; 5 | position: relative; 6 | // 层 7 | .layer { 8 | position: absolute; 9 | height: 100%; 10 | width: 100%; 11 | &.flex-center { 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | flex-direction: column; 16 | } 17 | } 18 | // 背景 19 | .bg { 20 | canvas { 21 | display: block; 22 | margin: 0px; 23 | padding: 0px; 24 | } 25 | } 26 | // logo 27 | .logo-group { 28 | margin-top: -75px - 70px; 29 | position: relative; 30 | top: 75px; 31 | img { 32 | height: 140px; 33 | } 34 | } 35 | // 登陆表单 36 | .form-group { 37 | width: 300px; 38 | // 重新设置卡片阴影 39 | .el-card { 40 | box-shadow: 0 0 8px 0 rgba(232,237,250,.6), 0 2px 4px 0 rgba(232,237,250,.5); 41 | .el-card__body { 42 | padding-top: 70px; 43 | } 44 | } 45 | // 登陆按钮 46 | .button-login { 47 | width: 100%; 48 | } 49 | // 输入框左边的图表区域缩窄 50 | .el-input-group__prepend { 51 | padding: 0px 14px; 52 | } 53 | .login-code { 54 | height: 40px - 2px; 55 | display: block; 56 | margin: 0px -20px; 57 | border-top-right-radius: 2px; 58 | border-bottom-right-radius: 2px; 59 | } 60 | } 61 | // 帮助按钮 62 | .button-help { 63 | width: 300px; 64 | margin-top: 20px; 65 | } 66 | } -------------------------------------------------------------------------------- /app/public/admin/src/src/plugin/axios/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { Message } from 'element-ui' 3 | import axios from 'axios' 4 | import Cookies from 'js-cookie' 5 | if (process.env.NODE_ENV === 'development') { 6 | axios.defaults.baseURL = '/api/' 7 | } 8 | axios.defaults.headers.Authorization = localStorage['token'] || null 9 | axios.interceptors.request.use(function (config) { 10 | // Do something before request is sent 11 | config.headers['x-csrf-token'] = Cookies.get('csrfToken') 12 | return config 13 | }, function (error) { 14 | // Do something with request error 15 | return Promise.reject(error) 16 | }) 17 | // 在这里对返回的数据进行处理 18 | // 在这里添加你自己的逻辑 19 | axios.interceptors.response.use(res => { 20 | if (res.data.code !== undefined) { 21 | if (res.data.code !== 0) { 22 | Message.error(res.data.msg) 23 | return Promise.reject(res.data.msg) 24 | } else { 25 | return res.data.data 26 | } 27 | } else { 28 | return res.data 29 | } 30 | }, error => { 31 | if (error.response.status === 401) { 32 | location.href = location.protocol + '//' + location.host + location.pathname + '#/login' 33 | } else { 34 | return Promise.reject(error) 35 | } 36 | }) 37 | Vue.prototype.$axios = axios 38 | -------------------------------------------------------------------------------- /app/public/admin/src/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | 4 | import util from '@/libs/util.js' 5 | 6 | // 路由数据 7 | import routes from './routes' 8 | 9 | Vue.use(VueRouter) 10 | 11 | let router = new VueRouter({ routes }) 12 | 13 | /** 14 | * 路由拦截 15 | * 权限验证 16 | */ 17 | router.beforeEach((to, from, next) => { 18 | // 验证当前路由所有的匹配中是否需要有登陆验证的 19 | if (to.matched.some(r => r.meta.requiresAuth)) { 20 | // 这里暂时将cookie里是否存有token作为验证是否登陆的条件 21 | // 请根据自身业务需要修改 22 | if (localStorage['token']) { 23 | next() 24 | } else { 25 | // 没有登陆的时候跳转到登陆界面 26 | next({ 27 | name: 'login' 28 | }) 29 | } 30 | } else { 31 | // 不需要身份校验 直接通过 32 | next() 33 | } 34 | next() 35 | }) 36 | 37 | router.afterEach(to => { 38 | // 需要的信息 39 | const app = router.app 40 | const { name, params, query } = to 41 | // 多页控制 打开新的页面 42 | util.openNewPage(app, name, params, query) 43 | // 更改标题 44 | util.title(to.meta.title) 45 | }) 46 | 47 | export default router 48 | -------------------------------------------------------------------------------- /app/public/admin/src/src/router/routes.js: -------------------------------------------------------------------------------- 1 | const meta = { requiresAuth: true } 2 | 3 | /** 4 | * 在主框架内显示 5 | */ 6 | const frameIn = [ 7 | // 首页 8 | { 9 | path: '/', 10 | redirect: { name: 'index' }, 11 | component: () => import('@/components/core/d2-layout-main'), 12 | children: [ 13 | { 14 | path: 'index', 15 | name: 'index', 16 | meta, 17 | component: () => import('@/pages/core/index') 18 | } 19 | ] 20 | }, 21 | { 22 | path: '/core/create', 23 | name: 'core-create', 24 | meta, 25 | redirect: { name: 'core-create-index' }, 26 | component: () => import('@/components/core/d2-layout-main'), 27 | children: (pre => [ 28 | { path: 'index', name: `${pre}index`, component: () => import('@/pages/core/create/index'), meta: { ...meta, title: '博客管理首页' } }, 29 | { path: 'newArticle', name: `${pre}newArticle`, component: () => import('@/pages/core/create/newArticle'), meta: { ...meta, title: '创建文章', alive: true } }, 30 | { path: 'editArticle/:id', name: `${pre}editArticle`, component: () => import('@/pages/core/create/editArticle'), meta: { ...meta, title: '编辑文章' } }, 31 | { path: 'newCategory', name: `${pre}newCategory`, component: () => import('@/pages/core/create/newCategory'), meta: { ...meta, title: '创建分类' } }, 32 | { path: 'newTag', name: `${pre}newTag`, component: () => import('@/pages/core/create/newTag'), meta: { ...meta, title: '创建标签' } }, 33 | { path: 'articleList', name: `${pre}articleList`, component: () => import('@/pages/core/create/articleList'), meta: { ...meta, title: '全部文章' } }, 34 | { path: 'articleList/draft', name: `${pre}articleList-draft`, component: () => import('@/pages/core/create/articleList/draft.vue'), meta: { ...meta, title: '草稿箱' } }, 35 | { path: 'articleList/garbage', name: `${pre}articleList-garbage`, component: () => import('@/pages/core/create/articleList/garbage.vue'), meta: { ...meta, title: '垃圾箱' } } 36 | ])('core-create-') 37 | } 38 | ] 39 | 40 | /** 41 | * 错误页面 42 | */ 43 | const errorPage = [ 44 | // 404 45 | { 46 | path: '*', 47 | name: '404', 48 | component: () => import('@/pages/core/404') 49 | } 50 | ] 51 | 52 | /** 53 | * 在主框架之外显示 54 | */ 55 | const frameOut = [ 56 | // 登陆 57 | { 58 | path: '/login', 59 | name: 'login', 60 | component: () => import('@/pages/core/login') 61 | } 62 | ] 63 | 64 | // 导出需要显示菜单的 65 | export const frameInRoutes = frameIn 66 | 67 | // 重新组织后导出 68 | export default [ 69 | ...frameIn, 70 | ...frameOut, 71 | ...errorPage 72 | ] 73 | -------------------------------------------------------------------------------- /app/public/admin/src/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import d2admin from './modules/d2admin' 5 | 6 | Vue.use(Vuex) 7 | 8 | export default new Vuex.Store({ 9 | modules: { 10 | d2admin 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /app/public/admin/src/static/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/icon.ico -------------------------------------------------------------------------------- /app/public/admin/src/static/image/bg/star-squashed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/bg/star-squashed.jpg -------------------------------------------------------------------------------- /app/public/admin/src/static/image/icon/500/d2admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/icon/500/d2admin.png -------------------------------------------------------------------------------- /app/public/admin/src/static/image/icon/500/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/icon/500/setting.png -------------------------------------------------------------------------------- /app/public/admin/src/static/image/icon/github/forkme@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/icon/github/forkme@2x.png -------------------------------------------------------------------------------- /app/public/admin/src/static/image/login-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/login-code.png -------------------------------------------------------------------------------- /app/public/admin/src/static/image/me/qq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/me/qq.jpg -------------------------------------------------------------------------------- /app/public/admin/src/static/image/me/qqq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/me/qqq.png -------------------------------------------------------------------------------- /app/public/admin/src/static/image/me/we.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/me/we.jpg -------------------------------------------------------------------------------- /app/public/admin/src/static/image/page/404/cover@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/page/404/cover@2x.png -------------------------------------------------------------------------------- /app/public/admin/src/static/image/theme/d2/logo/all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/theme/d2/logo/all.png -------------------------------------------------------------------------------- /app/public/admin/src/static/image/theme/d2/logo/icon-only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/theme/d2/logo/icon-only.png -------------------------------------------------------------------------------- /app/public/admin/src/static/image/theme/d2/logo/logo3-s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/theme/d2/logo/logo3-s.png -------------------------------------------------------------------------------- /app/public/admin/src/static/image/theme/d2/logo/logo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/theme/d2/logo/logo3.png -------------------------------------------------------------------------------- /app/public/admin/src/static/image/theme/d2/preview@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/theme/d2/preview@2x.png -------------------------------------------------------------------------------- /app/public/admin/src/static/image/theme/star/logo/all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/theme/star/logo/all.png -------------------------------------------------------------------------------- /app/public/admin/src/static/image/theme/star/logo/icon-only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/theme/star/logo/icon-only.png -------------------------------------------------------------------------------- /app/public/admin/src/static/image/theme/star/preview@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/theme/star/preview@2x.png -------------------------------------------------------------------------------- /app/public/admin/src/static/image/theme/violet/logo/all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/theme/violet/logo/all.png -------------------------------------------------------------------------------- /app/public/admin/src/static/image/theme/violet/logo/icon-only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/theme/violet/logo/icon-only.png -------------------------------------------------------------------------------- /app/public/admin/src/static/image/theme/violet/preview@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/admin/src/static/image/theme/violet/preview@2x.png -------------------------------------------------------------------------------- /app/public/client/src/.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": [ 12 | "transform-vue-jsx", 13 | "transform-runtime" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /app/public/client/src/.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 | -------------------------------------------------------------------------------- /app/public/client/src/.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | /src/vendor -------------------------------------------------------------------------------- /app/public/client/src/.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 | 'vue/no-parsing-error': 0, 29 | 'space-before-function-paren': 0 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/public/client/src/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /app/public/client/src/.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 | -------------------------------------------------------------------------------- /app/public/client/src/README.md: -------------------------------------------------------------------------------- 1 | # blog 2 | 3 | > blog 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | ``` 20 | 21 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 22 | -------------------------------------------------------------------------------- /app/public/client/src/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 | -------------------------------------------------------------------------------- /app/public/client/src/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 | -------------------------------------------------------------------------------- /app/public/client/src/build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/client/src/build/logo.png -------------------------------------------------------------------------------- /app/public/client/src/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass'), 64 | stylus: generateLoaders('stylus'), 65 | styl: generateLoaders('stylus') 66 | } 67 | } 68 | 69 | // Generate loaders for standalone style files (outside of .vue) 70 | exports.styleLoaders = function (options) { 71 | const output = [] 72 | const loaders = exports.cssLoaders(options) 73 | 74 | for (const extension in loaders) { 75 | const loader = loaders[extension] 76 | output.push({ 77 | test: new RegExp('\\.' + extension + '$'), 78 | use: loader 79 | }) 80 | } 81 | 82 | return output 83 | } 84 | 85 | exports.createNotifierCallback = () => { 86 | const notifier = require('node-notifier') 87 | 88 | return (severity, errors) => { 89 | if (severity !== 'error') return 90 | 91 | const error = errors[0] 92 | const filename = error.file && error.file.split('!').pop() 93 | 94 | notifier.notify({ 95 | title: packageConfig.name, 96 | message: severity + ': ' + error.name, 97 | subtitle: filename || '', 98 | icon: path.join(__dirname, 'logo.png') 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/public/client/src/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 | -------------------------------------------------------------------------------- /app/public/client/src/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | const createLintingRule = () => ({ 12 | test: /\.(js|vue)$/, 13 | loader: 'eslint-loader', 14 | enforce: 'pre', 15 | include: [resolve('src'), resolve('test')], 16 | options: { 17 | formatter: require('eslint-friendly-formatter'), 18 | emitWarning: !config.dev.showEslintErrorsInOverlay 19 | } 20 | }) 21 | 22 | module.exports = { 23 | context: path.resolve(__dirname, '../'), 24 | entry: { 25 | app: './src/main.js' 26 | }, 27 | output: { 28 | path: config.build.assetsRoot, 29 | filename: '[name].js', 30 | publicPath: process.env.NODE_ENV === 'production' 31 | ? config.build.assetsPublicPath 32 | : config.dev.assetsPublicPath 33 | }, 34 | resolve: { 35 | extensions: ['.js', '.vue', '.json'], 36 | alias: { 37 | 'vue$': 'vue/dist/vue.esm.js', 38 | '@': resolve('src'), 39 | } 40 | }, 41 | module: { 42 | rules: [ 43 | ...(config.dev.useEslint ? [createLintingRule()] : []), 44 | { 45 | test: /\.vue$/, 46 | loader: 'vue-loader', 47 | options: vueLoaderConfig 48 | }, 49 | { 50 | test: /\.js$/, 51 | loader: 'babel-loader', 52 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 53 | }, 54 | { 55 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 56 | loader: 'url-loader', 57 | options: { 58 | limit: 10000, 59 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 60 | } 61 | }, 62 | { 63 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 64 | loader: 'url-loader', 65 | options: { 66 | limit: 10000, 67 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 68 | } 69 | }, 70 | { 71 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 72 | loader: 'url-loader', 73 | options: { 74 | limit: 10000, 75 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 76 | } 77 | } 78 | ] 79 | }, 80 | node: { 81 | // prevent webpack from injecting useless setImmediate polyfill because Vue 82 | // source contains it (although only uses it if it's native). 83 | setImmediate: false, 84 | // prevent webpack from injecting mocks to Node native modules 85 | // that does not make sense for the client 86 | dgram: 'empty', 87 | fs: 'empty', 88 | net: 'empty', 89 | tls: 'empty', 90 | child_process: 'empty' 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/public/client/src/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const path = require('path') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 11 | const portfinder = require('portfinder') 12 | 13 | const HOST = process.env.HOST 14 | const PORT = process.env.PORT && Number(process.env.PORT) 15 | 16 | const devWebpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 19 | }, 20 | // cheap-module-eval-source-map is faster for development 21 | devtool: config.dev.devtool, 22 | 23 | // these devServer options should be customized in /config/index.js 24 | devServer: { 25 | clientLogLevel: 'warning', 26 | historyApiFallback: { 27 | rewrites: [ 28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, 29 | ], 30 | }, 31 | hot: true, 32 | contentBase: false, // since we use CopyWebpackPlugin. 33 | compress: true, 34 | host: HOST || config.dev.host, 35 | port: PORT || config.dev.port, 36 | open: config.dev.autoOpenBrowser, 37 | overlay: config.dev.errorOverlay 38 | ? { warnings: false, errors: true } 39 | : false, 40 | publicPath: config.dev.assetsPublicPath, 41 | proxy: config.dev.proxyTable, 42 | quiet: true, // necessary for FriendlyErrorsPlugin 43 | watchOptions: { 44 | poll: config.dev.poll, 45 | } 46 | }, 47 | plugins: [ 48 | new webpack.DefinePlugin({ 49 | 'process.env': require('../config/dev.env') 50 | }), 51 | new webpack.HotModuleReplacementPlugin(), 52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 53 | new webpack.NoEmitOnErrorsPlugin(), 54 | // https://github.com/ampedandwired/html-webpack-plugin 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'index.html', 58 | inject: true 59 | }), 60 | // copy custom static assets 61 | new CopyWebpackPlugin([ 62 | { 63 | from: path.resolve(__dirname, '../static'), 64 | to: config.dev.assetsSubDirectory, 65 | ignore: ['.*'] 66 | } 67 | ]) 68 | ] 69 | }) 70 | 71 | module.exports = new Promise((resolve, reject) => { 72 | portfinder.basePort = process.env.PORT || config.dev.port 73 | portfinder.getPort((err, port) => { 74 | if (err) { 75 | reject(err) 76 | } else { 77 | // publish the new Port, necessary for e2e tests 78 | process.env.PORT = port 79 | // add port to devServer config 80 | devWebpackConfig.devServer.port = port 81 | 82 | // Add FriendlyErrorsPlugin 83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 84 | compilationSuccessInfo: { 85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 86 | }, 87 | onErrors: config.dev.notifyOnErrors 88 | ? utils.createNotifierCallback() 89 | : undefined 90 | })) 91 | 92 | resolve(devWebpackConfig) 93 | } 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /app/public/client/src/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /app/public/client/src/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: { 14 | '/api': { 15 | target: 'http://127.0.0.1:7001/', 16 | changeOrigin: true, 17 | pathRewrite: { 18 | '^/api': '/' 19 | } 20 | } 21 | }, 22 | 23 | // Various Dev Server settings 24 | host: 'localhost', // can be overwritten by process.env.HOST 25 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 26 | autoOpenBrowser: false, 27 | errorOverlay: true, 28 | notifyOnErrors: true, 29 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 30 | 31 | // Use Eslint Loader? 32 | // If true, your code will be linted during bundling and 33 | // linting errors and warnings will be shown in the console. 34 | useEslint: true, 35 | // If true, eslint errors and warnings will also be shown in the error overlay 36 | // in the browser. 37 | showEslintErrorsInOverlay: false, 38 | 39 | /** 40 | * Source Maps 41 | */ 42 | 43 | // https://webpack.js.org/configuration/devtool/#development 44 | devtool: 'cheap-module-eval-source-map', 45 | 46 | // If you have problems debugging vue-files in devtools, 47 | // set this to false - it *may* help 48 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 49 | cacheBusting: true, 50 | 51 | cssSourceMap: true 52 | }, 53 | 54 | build: { 55 | // Template for index.html 56 | // index: path.resolve(__dirname, '../dist/index.html'), 57 | index: path.resolve(__dirname, '../../dist/index.html'), 58 | 59 | // Paths 60 | assetsRoot: path.resolve(__dirname, '../../dist'), 61 | assetsSubDirectory: 'static', 62 | assetsPublicPath: '/public/client/dist/', 63 | 64 | /** 65 | * Source Maps 66 | */ 67 | 68 | productionSourceMap: false, 69 | // https://webpack.js.org/configuration/devtool/#production 70 | devtool: '#source-map', 71 | 72 | // Gzip off by default as many popular static hosts such as 73 | // Surge or Netlify already gzip all static assets for you. 74 | // Before setting to `true`, make sure to: 75 | // npm install --save-dev compression-webpack-plugin 76 | productionGzip: false, 77 | productionGzipExtensions: ['js', 'css'], 78 | 79 | // Run the build command with an extra argument to 80 | // View the bundle analyzer report after build finishes: 81 | // `npm run build --report` 82 | // Set to `true` or `false` to always turn it on or off 83 | bundleAnalyzerReport: process.env.npm_config_report 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/public/client/src/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /app/public/client/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog", 3 | "version": "1.0.0", 4 | "description": "blog", 5 | "author": "wadejs", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "lint": "eslint --ext .js,.vue src", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.18.0", 15 | "iview": "^3.0.0", 16 | "js-cookie": "^2.2.0", 17 | "mavon-editor": "^2.6.15", 18 | "moment": "^2.22.2", 19 | "vue": "^2.5.2", 20 | "vue-router": "^3.0.1", 21 | "vuewordcloud": "^18.7.9", 22 | "vuex": "^3.0.1" 23 | }, 24 | "devDependencies": { 25 | "autoprefixer": "^7.1.2", 26 | "babel-core": "^6.22.1", 27 | "babel-eslint": "^8.2.1", 28 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 29 | "babel-loader": "^7.1.1", 30 | "babel-plugin-import": "^1.8.0", 31 | "babel-plugin-syntax-jsx": "^6.18.0", 32 | "babel-plugin-transform-runtime": "^6.22.0", 33 | "babel-plugin-transform-vue-jsx": "^3.5.0", 34 | "babel-preset-env": "^1.3.2", 35 | "babel-preset-stage-2": "^6.22.0", 36 | "chalk": "^2.0.1", 37 | "copy-webpack-plugin": "^4.0.1", 38 | "css-loader": "^0.28.0", 39 | "eslint": "^4.15.0", 40 | "eslint-config-standard": "^10.2.1", 41 | "eslint-friendly-formatter": "^3.0.0", 42 | "eslint-loader": "^1.7.1", 43 | "eslint-plugin-import": "^2.7.0", 44 | "eslint-plugin-node": "^5.2.0", 45 | "eslint-plugin-promise": "^3.4.0", 46 | "eslint-plugin-standard": "^3.0.1", 47 | "eslint-plugin-vue": "^4.0.0", 48 | "extract-text-webpack-plugin": "^3.0.0", 49 | "file-loader": "^1.1.4", 50 | "friendly-errors-webpack-plugin": "^1.6.1", 51 | "html-webpack-plugin": "^2.30.1", 52 | "node-notifier": "^5.1.2", 53 | "optimize-css-assets-webpack-plugin": "^3.2.0", 54 | "ora": "^1.2.0", 55 | "portfinder": "^1.0.13", 56 | "postcss-import": "^11.0.0", 57 | "postcss-loader": "^2.0.8", 58 | "postcss-url": "^7.2.1", 59 | "rimraf": "^2.6.0", 60 | "semver": "^5.3.0", 61 | "shelljs": "^0.7.6", 62 | "stylus": "^0.54.5", 63 | "stylus-loader": "^3.0.2", 64 | "uglifyjs-webpack-plugin": "^1.1.1", 65 | "url-loader": "^0.5.8", 66 | "vue-loader": "^13.3.0", 67 | "vue-style-loader": "^3.0.1", 68 | "vue-template-compiler": "^2.5.2", 69 | "webpack": "^3.6.0", 70 | "webpack-bundle-analyzer": "^2.9.0", 71 | "webpack-dev-server": "^2.9.1", 72 | "webpack-merge": "^4.1.0" 73 | }, 74 | "engines": { 75 | "node": ">= 6.0.0", 76 | "npm": ">= 3.0.0" 77 | }, 78 | "browserslist": [ 79 | "> 1%", 80 | "last 2 versions", 81 | "not ie <= 8" 82 | ] 83 | } 84 | -------------------------------------------------------------------------------- /app/public/client/src/src/App.vue: -------------------------------------------------------------------------------- 1 | 16 | 32 | 70 | -------------------------------------------------------------------------------- /app/public/client/src/src/assets/css/common.styl: -------------------------------------------------------------------------------- 1 | body 2 | background rgb(238, 238, 238) -------------------------------------------------------------------------------- /app/public/client/src/src/assets/css/icon.styl: -------------------------------------------------------------------------------- 1 | @font-face 2 | font-family: 'iconfont'; /* project id 793491 */ 3 | src: url('//at.alicdn.com/t/font_793491_ai0rw13n477.eot'); 4 | src: url('//at.alicdn.com/t/font_793491_ai0rw13n477.eot?#iefix') format('embedded-opentype'), 5 | url('//at.alicdn.com/t/font_793491_ai0rw13n477.woff') format('woff'), 6 | url('//at.alicdn.com/t/font_793491_ai0rw13n477.ttf') format('truetype'), 7 | url('//at.alicdn.com/t/font_793491_ai0rw13n477.svg#iconfont') format('svg'); 8 | .iconfont 9 | font-family: "iconfont" !important; 10 | font-size: 16px;font-style:normal; 11 | -webkit-font-smoothing: antialiased; 12 | -webkit-text-stroke-width: 0.2px; 13 | -moz-osx-font-smoothing: grayscale; -------------------------------------------------------------------------------- /app/public/client/src/src/assets/css/index.styl: -------------------------------------------------------------------------------- 1 | @import './reset' 2 | @import './common' 3 | @import './icon' -------------------------------------------------------------------------------- /app/public/client/src/src/assets/css/reset.styl: -------------------------------------------------------------------------------- 1 | /** 2 | * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) 3 | * http://cssreset.com 4 | */ 5 | html, body, div, span, applet, object, iframe, 6 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 7 | a, abbr, acronym, address, big, cite, code, 8 | del, dfn, em, img, ins, kbd, q, s, samp, 9 | small, strike, strong, sub, sup, tt, var, 10 | b, u, i, center, 11 | dl, dt, dd, ol, ul, li, 12 | fieldset, form, label, legend, 13 | table, caption, tbody, tfoot, thead, tr, th, td, 14 | article, aside, canvas, details, embed, 15 | figure, figcaption, footer, header, 16 | menu, nav, output, ruby, section, summary, 17 | time, mark, audio, video, input 18 | margin: 0 19 | padding: 0 20 | border: 0 21 | font-size: 100% 22 | font-weight: normal 23 | vertical-align: baseline 24 | 25 | /* HTML5 display-role reset for older browsers */ 26 | article, aside, details, figcaption, figure, 27 | footer, header, menu, nav, section 28 | display: block 29 | 30 | body 31 | line-height: 1 32 | 33 | blockquote, q 34 | quotes: none 35 | 36 | blockquote:before, blockquote:after, 37 | q:before, q:after 38 | content: none 39 | 40 | table 41 | border-collapse: collapse 42 | border-spacing: 0 43 | 44 | /* custom */ 45 | 46 | a 47 | color: #7e8c8d 48 | -webkit-backface-visibility: hidden 49 | text-decoration: none 50 | 51 | li 52 | list-style: none 53 | 54 | body 55 | -webkit-text-size-adjust: none 56 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0) 57 | -------------------------------------------------------------------------------- /app/public/client/src/src/assets/js/api.js: -------------------------------------------------------------------------------- 1 | import axios from '../../plugins/axios' 2 | let baseUrl = '/' 3 | 4 | function getArticleDetail(options) { 5 | return axios.post(baseUrl + 'c/getArticleDetail', options) 6 | } 7 | 8 | function getArticleList(options) { 9 | return axios.get(baseUrl + 'c/getArticleList', options) 10 | } 11 | 12 | function searchByCategory(options) { 13 | return axios.get(baseUrl + 'c/searchByCategory', options) 14 | } 15 | 16 | function searchByTag(options) { 17 | return axios.get(baseUrl + 'c/searchByTag', options) 18 | } 19 | 20 | function getTagsAndCategories(options) { 21 | return axios.get(baseUrl + 'c/getTagsAndCategories', options) 22 | } 23 | 24 | export { 25 | getArticleDetail, 26 | getArticleList, 27 | searchByCategory, 28 | searchByTag, 29 | getTagsAndCategories 30 | } 31 | -------------------------------------------------------------------------------- /app/public/client/src/src/assets/js/utils.js: -------------------------------------------------------------------------------- 1 | function genToc(selector, el) { 2 | let tocs = document.querySelector(selector).children 3 | let reg = new RegExp('[H]\\d') 4 | let list = document.createDocumentFragment() 5 | let style = document.createElement('style') 6 | style.innerHTML = ` 7 | h1 {cursor: pointer;margin-bottom: 10px;} 8 | h2 {cursor: pointer; padding-left: 10px;margin-bottom: 5px;} 9 | h3 {cursor: pointer; padding-left: 20px;margin-bottom: 5px;} 10 | h4 {cursor: pointer; padding-left: 30px;margin-bottom: 5px;} 11 | h5 {cursor: pointer; padding-left: 40px;margin-bottom: 5px;} 12 | h6 {cursor: pointer; padding-left: 50px;margin-bottom: 5px;} 13 | h1:hover {text-decoration: underline;} 14 | h2:hover {text-decoration: underline;} 15 | h3:hover {text-decoration: underline;} 16 | h4:hover {text-decoration: underline;} 17 | h5:hover {text-decoration: underline;} 18 | h6:hover {text-decoration: underline;} 19 | ` 20 | for (let index = 0; index < tocs.length; index++) { 21 | const item = tocs[index] 22 | if (reg.test(item.nodeName)) { 23 | list.appendChild(item.cloneNode(true)) 24 | } 25 | } 26 | document.querySelector(el).appendChild(style) 27 | document.querySelector(el).appendChild(list) 28 | document.querySelector(el).addEventListener('click', function (e) { 29 | if (reg.test(e.target.nodeName)) { 30 | let id = e.target.children[0].id 31 | document.getElementById(id).scrollIntoView({ 32 | behavior: 'smooth', 33 | block: 'start' 34 | }) 35 | } 36 | }) 37 | } 38 | 39 | export { 40 | genToc // 生成目录 41 | } 42 | -------------------------------------------------------------------------------- /app/public/client/src/src/components/404/404.vue: -------------------------------------------------------------------------------- 1 | 12 | 20 | 33 | -------------------------------------------------------------------------------- /app/public/client/src/src/components/ArticleList/ArticleList.vue: -------------------------------------------------------------------------------- 1 | 24 | 86 | 130 | 145 | -------------------------------------------------------------------------------- /app/public/client/src/src/components/AticleDetail/AticleDetail.vue: -------------------------------------------------------------------------------- 1 | 20 | 75 | 117 | -------------------------------------------------------------------------------- /app/public/client/src/src/components/Header/Header.vue: -------------------------------------------------------------------------------- 1 | 65 | 81 | 111 | -------------------------------------------------------------------------------- /app/public/client/src/src/components/Index/Index.vue: -------------------------------------------------------------------------------- 1 | 12 | 53 | 55 | -------------------------------------------------------------------------------- /app/public/client/src/src/components/LeftSide/LeftSide.vue: -------------------------------------------------------------------------------- 1 | 29 | 51 | 103 | -------------------------------------------------------------------------------- /app/public/client/src/src/components/SearchList/SearchList.vue: -------------------------------------------------------------------------------- 1 | 12 | 102 | 104 | -------------------------------------------------------------------------------- /app/public/client/src/src/components/TagCloud/TagCloud.vue: -------------------------------------------------------------------------------- 1 | 28 | 78 | 82 | -------------------------------------------------------------------------------- /app/public/client/src/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 iView from 'iview' 7 | import store from './store' 8 | import 'iview/dist/styles/iview.css' 9 | import '@/assets/css/index.styl' 10 | import './plugins/axios' 11 | // 打包的设置 用户获取路径 12 | import buildConfig from '../config/index' 13 | Vue.prototype.$assetsPublicPath = process.env.NODE_ENV === 'development' ? buildConfig.dev.assetsPublicPath : buildConfig.build.assetsPublicPath 14 | Vue.config.productionTip = false 15 | Vue.use(iView) 16 | /* eslint-disable no-new */ 17 | new Vue({ 18 | el: '#app', 19 | router, 20 | store, 21 | components: { App }, 22 | template: '' 23 | }) 24 | -------------------------------------------------------------------------------- /app/public/client/src/src/plugins/axios.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import axios from 'axios' 3 | import Cookies from 'js-cookie' 4 | if (process.env.NODE_ENV === 'development') { 5 | axios.defaults.baseURL = '/api/' 6 | } 7 | axios.defaults.headers.Authorization = localStorage['token'] || null 8 | axios.interceptors.request.use(function (config) { 9 | // Do something before request is sent 10 | config.headers['x-csrf-token'] = Cookies.get('csrfToken') 11 | return config 12 | }, function (error) { 13 | // Do something with request error 14 | return Promise.reject(error) 15 | }) 16 | // 在这里对返回的数据进行处理 17 | // 在这里添加你自己的逻辑 18 | axios.interceptors.response.use(res => { 19 | if (res.data.code !== undefined) { 20 | if (res.data.code !== 0) { 21 | console.log(res.data.msg) 22 | return Promise.reject(res.data.msg) 23 | } else { 24 | return res.data.data 25 | } 26 | } else { 27 | return res.data 28 | } 29 | }, error => { 30 | if (error.response.status === 401) { 31 | console.log(this) 32 | } else { 33 | return Promise.reject(error) 34 | } 35 | }) 36 | Vue.prototype.$axios = axios 37 | export default axios 38 | -------------------------------------------------------------------------------- /app/public/client/src/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Index from '@/components/Index/Index' 4 | import SearchList from '@/components/SearchList/SearchList' 5 | import AticleDetail from '@/components/AticleDetail/AticleDetail' 6 | import TagCloud from '@/components/TagCloud/TagCloud' 7 | import P404 from '@/components/404/404' 8 | 9 | Vue.use(Router) 10 | export default new Router({ 11 | routes: [ 12 | { 13 | path: '/', 14 | name: 'Index', 15 | component: Index 16 | }, 17 | { 18 | /** 19 | * @param type 0 为 关键字搜索, 1 为 分类搜索, 2 为标签搜索 20 | * @param condition 搜索的关键字 或者 分类/标签的id 21 | * **/ 22 | path: '/SearchList/:type/:condition?', 23 | name: 'SearchList', 24 | component: SearchList 25 | }, 26 | { 27 | /** 28 | * @param id 文章id 29 | * **/ 30 | path: '/AticleDetail/:id', 31 | name: 'AticleDetail', 32 | component: AticleDetail 33 | }, 34 | { 35 | path: '/TagCloud', 36 | name: 'TagCloud', 37 | component: TagCloud 38 | }, 39 | { 40 | path: '*', 41 | name: 'P404', 42 | component: P404 43 | } 44 | ] 45 | }) 46 | -------------------------------------------------------------------------------- /app/public/client/src/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | const store = new Vuex.Store({ 7 | }) 8 | 9 | export default store 10 | -------------------------------------------------------------------------------- /app/public/client/src/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/client/src/static/.gitkeep -------------------------------------------------------------------------------- /app/public/client/src/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/client/src/static/logo.png -------------------------------------------------------------------------------- /app/public/client/src/static/logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/client/src/static/logo1.png -------------------------------------------------------------------------------- /app/public/client/src/static/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/client/src/static/logo2.png -------------------------------------------------------------------------------- /app/public/client/src/static/logo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/client/src/static/logo3.png -------------------------------------------------------------------------------- /app/public/client/src/static/qqq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadejs/W-Blog/c03c0f6ab75b80b76f8d1a9dfecff663478ec5a6/app/public/client/src/static/qqq.jpg -------------------------------------------------------------------------------- /app/router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {Egg.Application} app - egg application 5 | */ 6 | module.exports = app => { 7 | const { router, controller } = app; 8 | // 后台授权中间件 9 | const auth = app.middleware.auth() 10 | // 首页 11 | router.get('/', controller.page.index); 12 | router.get('/admin', controller.page.admin); 13 | // 获取验证码 14 | router.get('/getCaptcha', controller.login.getCaptcha); 15 | // 登录 16 | router.post('/login', controller.login.login); 17 | // 获取所有文章列表 18 | router.get('/getArticleList', auth, controller.admin.getArticleList); 19 | // 编辑文章页获取用户的标签和分类数据 20 | router.get('/getArticleOptions', auth, controller.admin.getArticleOptions); 21 | // 获取文章的内容 22 | router.post('/getArticleDetail', auth, controller.admin.getArticleDetail); 23 | // 创建或更新文章,如果有文章id就更新文章,否则新建文章 24 | router.post('/postArticle', auth, controller.admin.postArticle); 25 | // 删除文章 26 | router.post('/delArticle', auth, controller.admin.delArticle); 27 | // 批量删除文章 28 | router.post('/delArticleBatch', auth, controller.admin.delArticleBatch); 29 | // 恢复文章 30 | router.post('/recoveryArticle', auth, controller.admin.recoveryArticle); 31 | // 批量恢复文章 32 | router.post('/recoveryArticleBatch', auth, controller.admin.recoveryArticleBatch); 33 | // 获取分类列表 34 | router.get('/getCategoryList', auth, controller.admin.getCategoryList); 35 | // 修改分类信息 36 | router.post('/modifyCategory', auth, controller.admin.modifyCategory); 37 | // 删除分类,如果提交一个id字符串,删除该分类;如果提交一个分类的数组,则删除该数组匹配的所有分类 38 | router.post('/delCategory', auth, controller.admin.delCategory); 39 | // 创建分类 40 | router.post('/createCategory', auth, controller.admin.createCategory); 41 | // 获取标签列表 42 | router.get('/getTagList', auth, controller.admin.getTagList); 43 | // 修改标签信息 44 | router.post('/modifyTag', auth, controller.admin.modifyTag); 45 | // 删除标签,如果提交一个id字符串,删除该标签;如果提交一个标签的数组,则删除该数组匹配的所有标签 46 | router.post('/delTag', auth, controller.admin.delTag); 47 | // 创建标签 48 | router.post('/createTag', auth, controller.admin.createTag); 49 | // 获取七牛token 50 | router.get('/getQiniuToken', auth, controller.admin.getQiniuToken); 51 | 52 | /* 前台路由 */ 53 | // 获取所有文章列表,如果有传keyword,则根据keyword搜索 54 | router.get('/c/getArticleList', controller.client.getArticleList); 55 | // 根据分类搜索 56 | router.get('/c/searchByCategory', controller.client.searchByCategory); 57 | // 根据标签搜索 58 | router.get('/c/searchByTag', controller.client.searchByTag); 59 | // 获取文章的详细信息 60 | router.post('/c/getArticleDetail', controller.client.getArticleDetail); 61 | // 获取所有分类和标签及其数量 62 | router.get('/c/getTagsAndCategories', controller.client.getTagsAndCategories); 63 | 64 | }; 65 | -------------------------------------------------------------------------------- /app/service/client.js: -------------------------------------------------------------------------------- 1 | const Service = require('egg').Service; 2 | class BlogService extends Service { 3 | 4 | async getAllArticleById(page, keyword) { 5 | const { ctx } = this 6 | const reg = new RegExp(keyword, 'i') 7 | let [ list, count ] = await Promise.all([ 8 | ctx.model.Article.find({ status: 0, $or: [{ title: { $regex: reg } }, { content: { $regex: reg } }] }, { html: 0 }) 9 | .populate([{ path: 'tagId', select: 'tagName' }, { path: 'categoryId', select: 'categoryName' }]).limit(10) 10 | .skip((page - 1) * 10), 11 | ctx.model.Article.find({ status: 0, $or: [{ title: { $regex: reg } }, { content: { $regex: reg } }] }).count() 12 | ]) 13 | // 截出预览部分 14 | list.map(item => { 15 | item.content = item.content.split('')[0] 16 | return item 17 | }) 18 | return { 19 | list, 20 | count 21 | } 22 | } 23 | 24 | async searchByCategory(page, id) { 25 | const { ctx } = this 26 | // let list = await ctx.model.Article.find({ categoryId: id }) 27 | let [ list, count ] = await Promise.all([ 28 | ctx.model.Article.find({ status: 0, categoryId: id }, { html: 0 }).populate([{ path: 'tagId', select: 'tagName' }, { path: 'categoryId', select: 'categoryName' }]).limit(10) 29 | .skip((page - 1) * 10), 30 | ctx.model.Article.find({ status: 0, categoryId: id }, { html: 0 }).count() 31 | ]) 32 | // 截出预览部分 33 | list.map(item => { 34 | item.content = item.content.split('')[0] 35 | return item 36 | }) 37 | return { 38 | list, 39 | count 40 | } 41 | } 42 | 43 | async searchByTag(page, id) { 44 | const { ctx } = this 45 | let [ list, count ] = await Promise.all([ 46 | ctx.model.Article.find({ status: 0, tagId: id }, { html: 0 }).populate([{ path: 'tagId', select: 'tagName' }, { path: 'categoryId', select: 'categoryName' }]).limit(10) 47 | .skip((page - 1) * 10), 48 | ctx.model.Article.find({ status: 0, tagId: id }, { html: 0 }).count() 49 | ]) 50 | // 截出预览部分 51 | list.map(item => { 52 | item.content = item.content.split('')[0] 53 | return item 54 | }) 55 | return { 56 | list, 57 | count 58 | } 59 | } 60 | 61 | async getArticleDetailByArticleId(id) { 62 | const { ctx } = this 63 | return await ctx.model.Article.find({ _id: id }, { html: 0 }).populate([{ path: 'tagId', select: 'tagName' }, { path: 'categoryId', select: 'categoryName' }]) 64 | } 65 | 66 | // 获取分类及数量 67 | async getCategoriesCount() { 68 | const { ctx } = this 69 | let categories = await ctx.model.Category.find({}, { __v: 0, userId: 0 }) 70 | let res = [] 71 | for (let index = 0; index < categories.length; index++) { 72 | let item = categories[index] 73 | let count = await ctx.model.Article.find({ status: 0, categoryId: item._id }).count() 74 | res.push({ 75 | count: count, 76 | categoryName: item.categoryName, 77 | categoryId: item._id 78 | }) 79 | } 80 | return res 81 | } 82 | 83 | // 获取标签及数量 84 | async getTagsCount() { 85 | const { ctx } = this 86 | let tags = await ctx.model.Tag.find({}, { __v: 0, userId: 0 }) 87 | let res = [] 88 | for (let index = 0; index < tags.length; index++) { 89 | let item = tags[index] 90 | let count = await ctx.model.Article.find({ status: 0, tagId: item._id }).count() 91 | res.push({ 92 | count: count, 93 | tagName: item.tagName, 94 | tagId: item._id 95 | }) 96 | } 97 | return res 98 | } 99 | 100 | } 101 | 102 | module.exports = BlogService 103 | -------------------------------------------------------------------------------- /app/service/login.js: -------------------------------------------------------------------------------- 1 | const Service = require('egg').Service; 2 | const jwt = require('jsonwebtoken') 3 | const svgCaptcha = require('svg-captcha') 4 | class LoginService extends Service { 5 | // 生成验证码 6 | genCaptcha() { 7 | return svgCaptcha.create({ 8 | width: 85, 9 | height: 38 10 | }) 11 | } 12 | // 检查验证码是否正确 13 | checkCaptcha(code) { 14 | const { ctx } = this 15 | code = code.toLowerCase() 16 | let sessCode = ctx.session.code ? ctx.session.code.toLowerCase() : null // 拿到之前存在session中的验证码 17 | // 进行验证 18 | if (code === sessCode) { 19 | // 成功后验证码作废 20 | ctx.session.code = null 21 | } 22 | return code === sessCode 23 | } 24 | // 登录操作 25 | async login({ username, password }) { 26 | const { ctx, app } = this 27 | const userData = await ctx.model.User.find({ 28 | userName: username, 29 | password: password 30 | }, { password: 0, __v: 0 }) 31 | // 找不到则返回false 32 | if (userData.length === 0) { 33 | return false 34 | } 35 | // 找到则以用户id生成token 36 | const token = jwt.sign({ 37 | id: userData[0]._id 38 | }, app.config.jwt.cert, { 39 | expiresIn: '10h' // token过期时间 40 | }) 41 | return { 42 | user: userData[0], 43 | token: token 44 | } 45 | } 46 | } 47 | 48 | module.exports = LoginService 49 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/config.default.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = appInfo => { 4 | return { 5 | keys: appInfo.name + '_153332185447_3632', 6 | mongoose: { 7 | clients: { 8 | blog: { 9 | url: 'mongodb://127.0.0.1/blogtest', 10 | options: { 11 | // user: 'test', // 数据库账号 12 | // pass: 'test' // 数据库密码 13 | }, 14 | } 15 | } 16 | }, 17 | user: { // 初始化管理员的账号 18 | userName: 'admin', 19 | password: 'admin', 20 | }, 21 | session: { 22 | maxAge: 3600 * 1000, 23 | }, 24 | jwt: { 25 | cert: 'huanggegehaoshuai' // jwt秘钥 26 | }, 27 | qiniu: { // 这里填写你七牛的Access Key和Secret Key 28 | ak: '', 29 | sk: '' 30 | } 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /config/plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // had enabled by egg 4 | // exports.static = true; 5 | module.exports = { 6 | mongoose: { 7 | enable: true, 8 | package: 'egg-mongoose' 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "private": true, 6 | "dependencies": { 7 | "egg": "^2.2.1", 8 | "egg-mongoose": "^3.1.0", 9 | "egg-scripts": "^2.5.0", 10 | "jsonwebtoken": "^8.3.0", 11 | "qiniu": "^7.2.1", 12 | "svg-captcha": "^1.3.11" 13 | }, 14 | "devDependencies": { 15 | "autod": "^3.0.1", 16 | "autod-egg": "^1.0.0", 17 | "egg-bin": "^4.3.5", 18 | "egg-ci": "^1.8.0", 19 | "egg-mock": "^3.14.0", 20 | "eslint": "^4.11.0", 21 | "eslint-config-egg": "^6.0.0", 22 | "webstorm-disable-index": "^1.2.0" 23 | }, 24 | "engines": { 25 | "node": ">=8.9.0" 26 | }, 27 | "scripts": { 28 | "start": "egg-scripts start --daemon --title=egg-server-demo", 29 | "stop": "egg-scripts stop --title=egg-server-demo", 30 | "dev": "egg-bin dev", 31 | "debug": "egg-bin debug", 32 | "test": "npm run lint -- --fix && npm run test-local", 33 | "test-local": "egg-bin test", 34 | "cov": "egg-bin cov", 35 | "lint": "eslint .", 36 | "ci": "npm run lint && npm run cov", 37 | "autod": "autod" 38 | }, 39 | "ci": { 40 | "version": "8" 41 | }, 42 | "repository": { 43 | "type": "git", 44 | "url": "" 45 | }, 46 | "author": "wade", 47 | "license": "MIT" 48 | } 49 | -------------------------------------------------------------------------------- /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, egasdfg') 19 | .expect(200); 20 | }); 21 | }); 22 | --------------------------------------------------------------------------------