├── .nojekyll ├── README.md ├── _coverpage.md ├── _sidebar.md ├── art.md ├── assets ├── docsify.min.js ├── emoji.min.js ├── itheima-cli.gif ├── prism-bash.js ├── prism-java.js ├── search.js ├── theme-buble.css ├── theme-vue.css └── zoom-image.js ├── chart.md ├── create-model.md ├── deploy.md ├── donate.md ├── error.md ├── eslint.md ├── faq.md ├── getting-started.md ├── i18n.md ├── icon.md ├── img ├── banner.png ├── different-router.jpeg ├── heima-admin-dashboard.png └── logo.png ├── index.html ├── java-art.md ├── java-err.md ├── java-flux.md ├── java-jpa.md ├── java-jwt.md ├── java-logs.md ├── java-new-api.md ├── java-permission.md ├── java-spring-boot2.md ├── layout.md ├── lazy-loading.md ├── mock-api.md ├── nav-permission.md ├── new-page.md ├── package.json ├── page-points.md ├── permission.md ├── router-and-nav.md ├── server.md ├── style.md └── theme.md /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itheima2017/vue-element-admin-doc-itheima/2b1383efc61279f6d627c7baf581c0ecebbfc3f6/.nojekyll -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](http://or45inefq.bkt.clouddn.com/logo-heima150.png ':no-zoom') 2 | 3 | # **黑马Admin** 商用后台模板 4 | 5 | [![vue](https://img.shields.io/badge/vue-2.5.9-brightgreen.svg ':no-zoom')](https://github.com/vuejs/vue) 6 | [![element-ui](https://img.shields.io/badge/element--ui-2.0.5-brightgreen.svg ':no-zoom')](https://github.com/ElemeFE/element) 7 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg ':no-zoom')](https://github.com/itheima2017/vue-element-admin-itheima/blob/master/LICENSE) 8 | [![GitHub release](https://img.shields.io/github/release/itheima2017/vue-element-admin-itheima.svg ':no-zoom')](https://github.com/itheima2017/vue-element-admin-itheima/releases) 9 | [![GitHub stars](https://img.shields.io/github/stars/itheima2017/vue-element-admin-itheima.svg?style=social&label=Stars ':no-zoom')](https://github.com/itheima2017/vue-element-admin-itheima) 10 | 11 | ## 背景&目标 12 | 13 | 提供一套标准的前后端分离模板,这样每次开发新管理项目,有个基础轮子可供使用。 14 | 15 | * [在线演示](http://itheimaadmin.itcast.cn/preview) 16 | * 代码 17 | * [前端 Vue](https://github.com/itheima2017/vue-element-admin-itheima) 18 | * [后端 Java](https://github.com/itheima2017/vue-element-admin-api-java-itheima) 19 | * 文档 20 | * [API 接口文档](http://itheimaadmin.itcast.cn/book/api/_book/) 21 | * [使用帮助文档](http://itheimaadmin.itcast.cn/book/help/) 22 | 23 | ## 功能截图 24 | 25 | ![后台面板](http://oflimcy5e.bkt.clouddn.com/ducafecat_2018-05-22-17-51-42.png) 26 | 27 | ## 功能 28 | 29 | * [x] 用户面板 30 | * [x] 用户登录 31 | * [x] 用户注销 32 | * [x] 用户菜单权限 33 | * [x] 用户页面权限点 34 | * [x] 当前面包靴 35 | * [x] 多语言支持 36 | * [x] 全屏显示 37 | * [x] 修改样式主色调 38 | * [x] 界面缓存 39 | * [x] 导航标签 40 | * [x] 错误页面-401 41 | * [x] 错误页面-404 42 | * [x] 前端错误日志 43 | * [x] 用户管理 44 | * [x] 菜单管理 45 | * [x] 权限管理 46 | * [x] 日志管理 47 | * [ ] api鉴权 48 | 49 | ## 技术栈 50 | 51 | * 前端 vue 52 | 53 | * vue 2.5++ 54 | * elementUI 2.2.2 55 | * vuex 56 | * axios 57 | * vue-router 58 | * vue-i18n 59 | 60 | * 后端 java 61 | 62 | * Spring boot 2.0 63 | * Spring Security 64 | * Spring Data JPA 65 | * auth0 jwt 66 | * MySQL 67 | 68 | ## 环境要求 69 | 70 | * 前端 71 | * node 8.++ 72 | * npm 5.++ 73 | 74 | * 后端 75 | * jdk 8.++ 76 | * maven 3.++ 77 | 78 | ### 设计参考 79 | 80 | * [ant-design/ant-design-pro](https://github.com/ant-design/ant-design-pro) 81 | * [PanJiaChen/vueAdmin-template](https://github.com/PanJiaChen/vueAdmin-template) 82 | 83 | ### 版权 84 | 85 | [MIT](https://opensource.org/licenses/MIT) license. 86 | 87 | @传智研究院-研发部 88 | 89 | 江苏传智播客教育科技股份有限公司  版权所有 Copyright 2006-2018, All Rights Reserved 90 | -------------------------------------------------------------------------------- /_coverpage.md: -------------------------------------------------------------------------------- 1 | ![logo](http://or45inefq.bkt.clouddn.com/logo-heima150.png ':no-zoom') 2 | 3 |

**黑马Admin** 商用后台模板

4 | 5 | *

标准功能 =》 登录、面板、用户、权限、权限点、日志、i18n

6 | *

前端架构 vue spa

7 | *

后端架构 Java SpringBoot2

8 | *

接口规范 restful api

9 | 10 | [GitHub](https://github.com/itheima2017/vue-element-admin-itheima ':target=_blank') 11 | [开始了解](README.md) 12 | 13 | ![color](#f4f5f5) 14 | -------------------------------------------------------------------------------- /_sidebar.md: -------------------------------------------------------------------------------- 1 | * [简介](README.md) 2 | * 快速开始 3 | * [1. 下载&运行](getting-started.md) 4 | * [2. 权限说明](permission.md) 5 | * [3. 新增模块](create-model.md) 6 | * [4. 新增菜单](nav-permission.md) 7 | * [5. 新增页面](new-page.md) 8 | * [6. 新增权限点](page-points.md) 9 | * [7. 新增 Rest API](java-new-api.md) 10 | * [8. 和服务端进行交互](server.md) 11 | * [9. 构建和发布](deploy.md) 12 | * 进阶 13 | * vue 前端 14 | * [项目结构](art.md) 15 | * [布局](layout.md) 16 | * [路由和菜单](router-and-nav.md) 17 | * [样式](style.md) 18 | * [图标](icon.md) 19 | * [自定义主题](theme.md) 20 | * [国际化](i18n.md) 21 | * [路由懒加载](lazy-loading.md) 22 | * [图表](chart.md) 23 | * [错误处理](error.md) 24 | * [mock 数据](mock-api.md) 25 | * [eslint 语法检查](eslint.md) 26 | * java 后端 27 | * [代码架构](java-art.md) 28 | * [权限控制](java-permission.md) 29 | * [Webflux 响应式编程](java-flux.md) 30 | * [JWT 令牌](java-jwt.md) 31 | * [JPA 配置](java-jpa.md) 32 | * [SpringBoot2 说明](java-spring-boot2.md) 33 | * [日志处理](java-logs.md) 34 | * [错误处理](java-err.md) 35 | * [更新日志](https://github.com/itheima2017/vue-element-admin-itheima/releases) 36 | * [常见问题](https://github.com/itheima2017/vue-element-admin-itheima/issues) 37 | * [传智研究院](http://research.itcast.cn/) 38 | -------------------------------------------------------------------------------- /art.md: -------------------------------------------------------------------------------- 1 | # 项目结构 2 | 3 | ## 后端 4 | ```bash 5 | ├── README.md 6 | ├── db | 初始化数据库文件目录 7 | │   ├── _init.sql 8 | │   └── init.sql | 初始化数据库文件 9 | ├── pom.xml | Maven配置文件 10 | ├── src 11 | │   └── main 12 | │   ├── java 13 | │   │   └── itcast 14 | │   │   └── research 15 | │   │   ├── VueElementAdminApiApplication.java | Springboot启动类 16 | │   │   ├── config | 项目相关配置 17 | │   │   │   └── startup | 自定义启动类 18 | │   │   ├── controller | Api控制器层 19 | │   │   │   ├── other | 控制器-其他 20 | │   │   │   ├── permission | 控制器-权限相关 21 | │   │   │   └── user | 控制器-用户相关 22 | │   │   ├── entity | 实体 23 | │   │   │   ├── other | 实体-其他 24 | │   │   │   ├── permission | 实体-权限相关 25 | │   │   │   └── user | 实体-用户相关 26 | │   │   ├── exception | 异常处理 27 | │   │   ├── handler | 切面及身份验证处理器 28 | │   │   ├── repository | JPA数据操作接口 29 | │   │   │   ├── other | 数据操作接口-其他 30 | │   │   │   ├── permission | 数据操作接口-权限相关 31 | │   │   │   └── user | 数据操作接口-用户相关 32 | │   │   ├── service | 服务层 33 | │   │   │   ├── other | 服务-其他 34 | │   │   │   ├── permission | 服务-权限相关 35 | │   │   │   └── user | 服务-用户相关 36 | │   │   └── util | 工具包 37 | │   └── resources | 资源文件 38 | │   ├── _application.yml 39 | │   └── application.yml | Springboot配置文件 40 | ``` 41 | 42 | ## 前端 43 | 44 | ```bash 45 | ├── assets | 资源 46 | ├── build | webpack编译配置 47 | ├── config | 全局变量 48 | ├── src | 源码 49 | │   ├── api | 数据请求 50 | │   ├── assets | 资源 51 | │   ├── components | 组件 52 | │   ├── mixins | mixins 53 | │   ├── filters | vue filter 54 | │   ├── icons | 图标 55 | │   ├── lang | 多语言 56 | │   ├── router | 路由 57 | │   ├── store | 数据 58 | │   ├── styles | 样式 59 | │   ├── utils | 工具函数库 60 | │   ├── module-dashboard | 框架程序 61 | │   │   ├── assets 62 | │   │   ├── components 63 | │   │   ├── pages 64 | │   │   ├── router 65 | │   │   └── store 66 | │   ├── module-example | 示例程序 67 | │   │   ├── assets 68 | │   │   ├── components 69 | │   │   ├── pages 70 | │   │   ├── router 71 | │   │   └── store 72 | │   ├── App.vue | app 73 | │   ├── main.js | 主引导 74 | │   └── errorLog.js | vue全局错误捕捉 75 | ├── dist | 编译发布目录 76 | ├── README.md 77 | ├── index.html | 页面模板 78 | ├── package.json | npn包配置 79 | ├── static 80 | └── test | 测试 81 | ├── e2e 82 | └── unit 83 | ``` 84 | 85 | * 创建模块目录 86 | 87 | > 每个业务模块独立目录,互不干扰 88 | > 89 | > 我们简化了每次构建的操作,执行 `npm` 命令 90 | 91 | ```bash 92 | > module=example npm run art:create 93 | ``` 94 | 95 | 自动创建模块目录 96 | 97 | ```bash 98 | │   ├── module-example // 业务模块目录 99 | │   │   ├── assets // 静态资源 img font svg icon 100 | │   │   ├── components // 组件 vue 101 | │   │   ├── pages // 页面 view 102 | │   │   ├── router // 路由控制 vue-router 103 | │   │   └── store // 数据管理 vuex 104 | ``` 105 | -------------------------------------------------------------------------------- /assets/docsify.min.js: -------------------------------------------------------------------------------- 1 | !function(){function e(e){var t=Object.create(null);return function(n){var r=i(n)?n:JSON.stringify(n);return t[r]||(t[r]=e(n))}}var t=e(function(e){return e.replace(/([A-Z])/g,function(e){return"-"+e.toLowerCase()})}),n=Object.prototype.hasOwnProperty,r=Object.assign||function(e){for(var t=arguments,r=1;r=i.length)r(n);else if("function"==typeof t)if(2===t.length)t(n,function(t){n=t,o(e+1)});else{var a=t(n);n=void 0===a?n:a,o(e+1)}else o(e+1)};o(0)}var l=!0,c=l&&document.body.clientWidth<=600,u=l&&window.history&&window.history.pushState&&window.history.replaceState&&!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/),h={};function p(e,t){if(void 0===t&&(t=!1),"string"==typeof e){if(void 0!==window.Vue)return m(e);e=t?m(e):h[e]||(h[e]=m(e))}return e}var d=l&&document,g=l&&d.body,f=l&&d.head;function m(e,t){return t?e.querySelector(t):d.querySelector(e)}function v(e,t){return[].slice.call(t?e.querySelectorAll(t):d.querySelectorAll(e))}function b(e,t){return e=d.createElement(e),t&&(e.innerHTML=t),e}function y(e,t){return e.appendChild(t)}function k(e,t){return e.insertBefore(t,e.children[0])}function w(e,t,n){o(t)?window.addEventListener(e,t):e.addEventListener(t,n)}function x(e,t,n){o(t)?window.removeEventListener(e,t):e.removeEventListener(t,n)}function _(e,t,n){e&&e.classList[n?t:"toggle"](n||t)}var S=Object.freeze({getNode:p,$:d,body:g,head:f,find:m,findAll:v,create:b,appendTo:y,before:k,on:w,off:x,toggleClass:_,style:function(e){y(f,b("style",e))}});function C(e,t){return void 0===t&&(t=""),e&&e.length?(e.forEach(function(e){t+='
  • '+e.title+"
  • ",e.children&&(t+='
  • ")}),t):""}function L(e,t){return'

    '+t.slice(5).trim()+"

    "}var E,A;function $(e){var t,n=e.loaded,r=e.total,i=e.step;!E&&function(){var e=b("div");e.classList.add("progress"),y(g,e),E=e}(),t=i?(t=parseInt(E.style.width||0,10)+i)>80?80:t:Math.floor(n/r*100),E.style.opacity=1,E.style.width=t>=95?"100%":t+"%",t>=95&&(clearTimeout(A),A=setTimeout(function(e){E.style.opacity=0,E.style.width="0%"},200))}var T={};function P(e,t,r){void 0===t&&(t=!1),void 0===r&&(r={});var i=new XMLHttpRequest,o=function(){i.addEventListener.apply(i,arguments)},s=T[e];if(s)return{then:function(e){return e(s.content,s.opt)},abort:a};i.open("GET",e);for(var l in r)n.call(r,l)&&i.setRequestHeader(l,r[l]);return i.send(),{then:function(n,r){if(void 0===r&&(r=a),t){var s=setInterval(function(e){return $({step:Math.floor(5*Math.random()+1)})},500);o("progress",$),o("loadend",function(e){$(e),clearInterval(s)})}o("error",r),o("load",function(t){var a=t.target;if(a.status>=400)r(a);else{var o=T[e]={content:a.response,opt:{updatedAt:i.getResponseHeader("last-modified")}};n(o.content,o.opt)}})},abort:function(e){return 4!==i.readyState&&i.abort()}}}function F(e,t){e.innerHTML=e.innerHTML.replace(/var\(\s*--theme-color.*?\)/g,t)}var O=/([^{]*?)\w(?=\})/g,M={YYYY:"getFullYear",YY:"getYear",MM:function(e){return e.getMonth()+1},DD:"getDate",HH:"getHours",mm:"getMinutes",ss:"getSeconds"};var N="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function j(e,t){return e(t={exports:{}},t.exports),t.exports}var q=j(function(e,t){(function(){var t={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:p,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:p,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,def:/^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:p,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};t.bullet=/(?:[*+-]|\d+\.)/,t.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/,t.item=l(t.item,"gm")(/bull/g,t.bullet)(),t.list=l(t.list)(/bull/g,t.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+t.def.source+")")(),t.blockquote=l(t.blockquote)("def",t.def)(),t._tag="(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b",t.html=l(t.html)("comment",//)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/])*?>/)(/tag/g,t._tag)(),t.paragraph=l(t.paragraph)("hr",t.hr)("heading",t.heading)("lheading",t.lheading)("blockquote",t.blockquote)("tag","<"+t._tag)("def",t.def)(),t.normal=d({},t),t.gfm=d({},t.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/}),t.gfm.paragraph=l(t.paragraph)("(?!","(?!"+t.gfm.fences.source.replace("\\1","\\2")+"|"+t.list.source.replace("\\1","\\3")+"|")(),t.tables=d({},t.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/});function n(e){this.tokens=[],this.tokens.links={},this.options=e||g.defaults,this.rules=t.normal,this.options.gfm&&(this.options.tables?this.rules=t.tables:this.rules=t.gfm)}n.rules=t,n.lex=function(e,t){return new n(t).lex(e)},n.prototype.lex=function(e){return e=e.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n"),this.token(e,!0)},n.prototype.token=function(e,n,r){var i,a,o,s,l,c,u,h,p;for(e=e.replace(/^ +$/gm,"");e;)if((o=this.rules.newline.exec(e))&&(e=e.substring(o[0].length),o[0].length>1&&this.tokens.push({type:"space"})),o=this.rules.code.exec(e))e=e.substring(o[0].length),o=o[0].replace(/^ {4}/gm,""),this.tokens.push({type:"code",text:this.options.pedantic?o:o.replace(/\n+$/,"")});else if(o=this.rules.fences.exec(e))e=e.substring(o[0].length),this.tokens.push({type:"code",lang:o[2],text:o[3]||""});else if(o=this.rules.heading.exec(e))e=e.substring(o[0].length),this.tokens.push({type:"heading",depth:o[1].length,text:o[2]});else if(n&&(o=this.rules.nptable.exec(e))){for(e=e.substring(o[0].length),c={type:"table",header:o[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:o[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:o[3].replace(/\n$/,"").split("\n")},h=0;h ?/gm,""),this.token(o,n,!0),this.tokens.push({type:"blockquote_end"});else if(o=this.rules.list.exec(e)){for(e=e.substring(o[0].length),s=o[2],this.tokens.push({type:"list_start",ordered:s.length>1}),i=!1,p=(o=o[0].match(this.rules.item)).length,h=0;h1&&l.length>1||(e=o.slice(h+1).join("\n")+e,h=p-1)),a=i||/\n\n(?!\s*$)/.test(c),h!==p-1&&(i="\n"===c.charAt(c.length-1),a||(a=i)),this.tokens.push({type:a?"loose_item_start":"list_item_start"}),this.token(c,!1,r),this.tokens.push({type:"list_item_end"});this.tokens.push({type:"list_end"})}else if(o=this.rules.html.exec(e))e=e.substring(o[0].length),this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:!this.options.sanitizer&&("pre"===o[1]||"script"===o[1]||"style"===o[1]),text:o[0]});else if(!r&&n&&(o=this.rules.def.exec(e)))e=e.substring(o[0].length),this.tokens.links[o[1].toLowerCase()]={href:o[2],title:o[3]};else if(n&&(o=this.rules.table.exec(e))){for(e=e.substring(o[0].length),c={type:"table",header:o[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:o[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:o[3].replace(/(?: *\| *)?\n$/,"").split("\n")},h=0;h])/,autolink:/^<([^ <>]+(@|:\/)[^ <>]+)>/,url:p,tag:/^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^<'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)([\s\S]*?[^`])\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:p,text:/^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/,r.link=l(r.link)("inside",r._inside)("href",r._href)(),r.reflink=l(r.reflink)("inside",r._inside)(),r.normal=d({},r),r.pedantic=d({},r.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/}),r.gfm=d({},r.normal,{escape:l(r.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:l(r.text)("]|","~]|")("|","|https?://|")()}),r.breaks=d({},r.gfm,{br:l(r.br)("{2,}","*")(),text:l(r.gfm.text)("{2,}","*")()});function i(e,t){if(this.options=t||g.defaults,this.links=e,this.rules=r.normal,this.renderer=this.options.renderer||new a,this.renderer.options=this.options,!this.links)throw new Error("Tokens array requires a `links` property.");this.options.gfm?this.options.breaks?this.rules=r.breaks:this.rules=r.gfm:this.options.pedantic&&(this.rules=r.pedantic)}i.rules=r,i.output=function(e,t,n){return new i(t,n).output(e)},i.prototype.output=function(e){for(var t,n,r,i,a="";e;)if(i=this.rules.escape.exec(e))e=e.substring(i[0].length),a+=i[1];else if(i=this.rules.autolink.exec(e))e=e.substring(i[0].length),"@"===i[2]?(n=s(":"===i[1].charAt(6)?this.mangle(i[1].substring(7)):this.mangle(i[1])),r=this.mangle("mailto:")+n):r=n=s(i[1]),a+=this.renderer.link(r,null,n);else if(this.inLink||!(i=this.rules.url.exec(e))){if(i=this.rules.tag.exec(e))!this.inLink&&/^/i.test(i[0])&&(this.inLink=!1),e=e.substring(i[0].length),a+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(i[0]):s(i[0]):i[0];else if(i=this.rules.link.exec(e))e=e.substring(i[0].length),this.inLink=!0,a+=this.outputLink(i,{href:i[2],title:i[3]}),this.inLink=!1;else if((i=this.rules.reflink.exec(e))||(i=this.rules.nolink.exec(e))){if(e=e.substring(i[0].length),t=(i[2]||i[1]).replace(/\s+/g," "),!(t=this.links[t.toLowerCase()])||!t.href){a+=i[0].charAt(0),e=i[0].substring(1)+e;continue}this.inLink=!0,a+=this.outputLink(i,t),this.inLink=!1}else if(i=this.rules.strong.exec(e))e=e.substring(i[0].length),a+=this.renderer.strong(this.output(i[2]||i[1]));else if(i=this.rules.em.exec(e))e=e.substring(i[0].length),a+=this.renderer.em(this.output(i[2]||i[1]));else if(i=this.rules.code.exec(e))e=e.substring(i[0].length),a+=this.renderer.codespan(s(i[2].trim(),!0));else if(i=this.rules.br.exec(e))e=e.substring(i[0].length),a+=this.renderer.br();else if(i=this.rules.del.exec(e))e=e.substring(i[0].length),a+=this.renderer.del(this.output(i[1]));else if(i=this.rules.text.exec(e))e=e.substring(i[0].length),a+=this.renderer.text(s(this.smartypants(i[0])));else if(e)throw new Error("Infinite loop on byte: "+e.charCodeAt(0))}else e=e.substring(i[0].length),r=n=s(i[1]),a+=this.renderer.link(r,null,n);return a},i.prototype.outputLink=function(e,t){var n=s(t.href),r=t.title?s(t.title):null;return"!"!==e[0].charAt(0)?this.renderer.link(n,r,this.output(e[1])):this.renderer.image(n,r,s(e[1]))},i.prototype.smartypants=function(e){return this.options.smartypants?e.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…"):e},i.prototype.mangle=function(e){if(!this.options.mangle)return e;for(var t,n="",r=e.length,i=0;i.5&&(t="x"+t.toString(16)),n+="&#"+t+";";return n};function a(e){this.options=e||{}}a.prototype.code=function(e,t,n){if(this.options.highlight){var r=this.options.highlight(e,t);null!=r&&r!==e&&(n=!0,e=r)}return t?'
    '+(n?e:s(e,!0))+"\n
    \n":"
    "+(n?e:s(e,!0))+"\n
    "},a.prototype.blockquote=function(e){return"
    \n"+e+"
    \n"},a.prototype.html=function(e){return e},a.prototype.heading=function(e,t,n){return"'+e+"\n"},a.prototype.hr=function(){return this.options.xhtml?"
    \n":"
    \n"},a.prototype.list=function(e,t){var n=t?"ol":"ul";return"<"+n+">\n"+e+"\n"},a.prototype.listitem=function(e){return"
  • "+e+"
  • \n"},a.prototype.paragraph=function(e){return"

    "+e+"

    \n"},a.prototype.table=function(e,t){return"\n\n"+e+"\n\n"+t+"\n
    \n"},a.prototype.tablerow=function(e){return"\n"+e+"\n"},a.prototype.tablecell=function(e,t){var n=t.header?"th":"td";return(t.align?"<"+n+' style="text-align:'+t.align+'">':"<"+n+">")+e+"\n"},a.prototype.strong=function(e){return""+e+""},a.prototype.em=function(e){return""+e+""},a.prototype.codespan=function(e){return""+e+""},a.prototype.br=function(){return this.options.xhtml?"
    ":"
    "},a.prototype.del=function(e){return""+e+""},a.prototype.link=function(e,t,n){if(this.options.sanitize){try{var r=decodeURIComponent((i=e,i.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi,function(e,t){return"colon"===(t=t.toLowerCase())?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""}))).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return n}if(0===r.indexOf("javascript:")||0===r.indexOf("vbscript:")||0===r.indexOf("data:"))return n}var i;this.options.baseUrl&&!h.test(e)&&(e=c(this.options.baseUrl,e));var a='
    "},a.prototype.image=function(e,t,n){this.options.baseUrl&&!h.test(e)&&(e=c(this.options.baseUrl,e));var r=''+n+'":">"},a.prototype.text=function(e){return e};function o(e){this.tokens=[],this.token=null,this.options=e||g.defaults,this.options.renderer=this.options.renderer||new a,this.renderer=this.options.renderer,this.renderer.options=this.options}o.parse=function(e,t,n){return new o(t,n).parse(e)},o.prototype.parse=function(e){this.inline=new i(e.links,this.options,this.renderer),this.tokens=e.reverse();for(var t="";this.next();)t+=this.tok();return t},o.prototype.next=function(){return this.token=this.tokens.pop()},o.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0},o.prototype.parseText=function(){for(var e=this.token.text;"text"===this.peek().type;)e+="\n"+this.next().text;return this.inline.output(e)},o.prototype.tok=function(){switch(this.token.type){case"space":return"";case"hr":return this.renderer.hr();case"heading":return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text);case"code":return this.renderer.code(this.token.text,this.token.lang,this.token.escaped);case"table":var e,t,n,r,i="",a="";for(n="",e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'")}function l(e,t){return e=e.source,t=t||"",function n(r,i){return r?(i=(i=i.source||i).replace(/(^|[^\[])\^/g,"$1"),e=e.replace(r,i),n):new RegExp(e,t)}}function c(e,t){return u[" "+e]||(/^[^:]+:\/*[^/]*$/.test(e)?u[" "+e]=e+"/":u[" "+e]=e.replace(/[^/]*$/,"")),e=u[" "+e],"//"===t.slice(0,2)?e.replace(/:[\s\S]*/,":")+t:"/"===t.charAt(0)?e.replace(/(:\/*[^/]*)[\s\S]*/,"$1")+t:e+t}var u={},h=/^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;function p(){}p.exec=p;function d(e){for(var t,n,r=arguments,i=1;iAn error occurred:

    "+s(e.message+"",!0)+"
    ";throw e}}g.options=g.setOptions=function(e){return d(g.defaults,e),g},g.defaults={gfm:!0,tables:!0,breaks:!1,pedantic:!1,sanitize:!1,sanitizer:null,mangle:!0,smartLists:!1,silent:!1,highlight:null,langPrefix:"lang-",smartypants:!1,headerPrefix:"",renderer:new a,xhtml:!1,baseUrl:null},g.Parser=o,g.parser=o.parse,g.Renderer=a,g.Lexer=n,g.lexer=n.lex,g.InlineLexer=i,g.inlineLexer=i.output,g.parse=g,e.exports=g}).call(function(){return this||("undefined"!=typeof window?window:N)}())}),R=j(function(e){var t="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},n=function(){var e=/\blang(?:uage)?-(\w+)\b/i,n=0,r=t.Prism={manual:t.Prism&&t.Prism.manual,disableWorkerMessageHandler:t.Prism&&t.Prism.disableWorkerMessageHandler,util:{encode:function(e){return e instanceof i?new i(e.type,r.util.encode(e.content),e.alias):"Array"===r.util.type(e)?e.map(r.util.encode):e.replace(/&/g,"&").replace(/e.length)return;if(!(w instanceof l)){p.lastIndex=0;var x=1;if(!(A=p.exec(w))&&f&&y!=t.length-1){if(p.lastIndex=k,!(A=p.exec(e)))break;for(var _=A.index+(g?A[1].length:0),S=A.index+A[0].length,C=y,L=k,E=t.length;C=(L+=t[C].length)&&(++y,k=L);if(t[y]instanceof l||t[C-1].greedy)continue;x=C-y,w=e.slice(k,L),A.index-=k}if(A){g&&(m=A[1].length);S=(_=A.index+m)+(A=A[0].slice(m)).length;var A,$=w.slice(0,_),T=w.slice(S),P=[y,x];$&&(++y,k+=$.length,P.push($));var F=new l(c,d?r.tokenize(A,d):A,v,A,f);if(P.push(F),T&&P.push(T),Array.prototype.splice.apply(t,P),1!=x&&r.matchGrammar(e,t,n,y,k,!0,c),o)break}else if(o)break}}}}},tokenize:function(e,t,n){var i=[e],a=t.rest;if(a){for(var o in a)t[o]=a[o];delete t.rest}return r.matchGrammar(e,i,t,0,0,!1),i},hooks:{all:{},add:function(e,t){var n=r.hooks.all;n[e]=n[e]||[],n[e].push(t)},run:function(e,t){var n=r.hooks.all[e];if(n&&n.length)for(var i,a=0;i=n[a++];)i(t)}}},i=r.Token=function(e,t,n,r,i){this.type=e,this.content=t,this.alias=n,this.length=0|(r||"").length,this.greedy=!!i};if(i.stringify=function(e,t,n){if("string"==typeof e)return e;if("Array"===r.util.type(e))return e.map(function(n){return i.stringify(n,t,e)}).join("");var a={type:e.type,content:i.stringify(e.content,t,n),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:n};if(e.alias){var o="Array"===r.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(a.classes,o)}r.hooks.run("wrap",a);var s=Object.keys(a.attributes).map(function(e){return e+'="'+(a.attributes[e]||"").replace(/"/g,""")+'"'}).join(" ");return"<"+a.tag+' class="'+a.classes.join(" ")+'"'+(s?" "+s:"")+">"+a.content+""},!t.document)return t.addEventListener?(r.disableWorkerMessageHandler||t.addEventListener("message",function(e){var n=JSON.parse(e.data),i=n.language,a=n.code,o=n.immediateClose;t.postMessage(r.highlight(a,r.languages[i],i)),o&&t.close()},!1),t.Prism):t.Prism;var a=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return a&&(r.filename=a.src,r.manual||a.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(r.highlightAll):window.setTimeout(r.highlightAll,16):document.addEventListener("DOMContentLoaded",r.highlightAll))),t.Prism}();e.exports&&(e.exports=n),void 0!==N&&(N.Prism=n),n.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype://i,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+)/i,inside:{punctuation:[/^=/,{pattern:/(^|[^\\])["']/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},n.languages.markup.tag.inside["attr-value"].inside.entity=n.languages.markup.entity,n.hooks.add("wrap",function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))}),n.languages.xml=n.languages.markup,n.languages.html=n.languages.markup,n.languages.mathml=n.languages.markup,n.languages.svg=n.languages.markup,n.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(?:;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^{}\s][^{};]*?(?=\s*\{)/,string:{pattern:/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},property:/[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,important:/\B!important\b/i,function:/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},n.languages.css.atrule.inside.rest=n.util.clone(n.languages.css),n.languages.markup&&(n.languages.insertBefore("markup","tag",{style:{pattern:/()[\s\S]*?(?=<\/style>)/i,lookbehind:!0,inside:n.languages.css,alias:"language-css",greedy:!0}}),n.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:n.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:n.languages.css}},alias:"language-css"}},n.languages.markup.tag)),n.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/},n.languages.javascript=n.languages.extend("clike",{keyword:/\b(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(?:0[xX][\dA-Fa-f]+|0[bB][01]+|0[oO][0-7]+|\d*\.?\d+(?:[Ee][+-]?\d+)?|NaN|Infinity)\b/,function:/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*\()/i,operator:/-[-=]?|\+[+=]?|!=?=?|<>?>?=?|=(?:==?|>)?|&[&=]?|\|[|=]?|\*\*?=?|\/=?|~|\^=?|%=?|\?|\.{3}/}),n.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[[^\]\r\n]+]|\\.|[^/\\\[\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0,greedy:!0},"function-variable":{pattern:/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=\s*(?:function\b|(?:\([^()]*\)|[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/i,alias:"function"}}),n.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\[\s\S]|[^\\`])*`/,greedy:!0,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:n.languages.javascript}},string:/[\s\S]+/}}}),n.languages.markup&&n.languages.insertBefore("markup","tag",{script:{pattern:/()[\s\S]*?(?=<\/script>)/i,lookbehind:!0,inside:n.languages.javascript,alias:"language-javascript",greedy:!0}}),n.languages.js=n.languages.javascript,"undefined"!=typeof self&&self.Prism&&self.document&&document.querySelector&&(self.Prism.fileHighlight=function(){var e={js:"javascript",py:"python",rb:"ruby",ps1:"powershell",psm1:"powershell",sh:"bash",bat:"batch",h:"c",tex:"latex"};Array.prototype.slice.call(document.querySelectorAll("pre[data-src]")).forEach(function(t){for(var r,i=t.getAttribute("data-src"),a=t,o=/\blang(?:uage)?-(?!\*)(\w+)\b/i;a&&!o.test(a.className);)a=a.parentNode;if(a&&(r=(t.className.match(o)||[,""])[1]),!r){var s=(i.match(/\.(\w+)$/)||[,""])[1];r=e[s]||s}var l=document.createElement("code");l.className="language-"+r,t.textContent="",l.textContent="Loading…",t.appendChild(l);var c=new XMLHttpRequest;c.open("GET",i,!0),c.onreadystatechange=function(){4==c.readyState&&(c.status<400&&c.responseText?(l.textContent=c.responseText,n.highlightElement(l)):c.status>=400?l.textContent="✖ Error "+c.status+" while fetching file: "+c.statusText:l.textContent="✖ Error: File does not exist or is empty")},c.send(null)})},document.addEventListener("DOMContentLoaded",self.Prism.fileHighlight))});function H(e,t){var n=[],r={};return e.forEach(function(e){var i=e.level||1,a=i-1;i>t||(r[a]?r[a].children=(r[a].children||[]).concat(e):n.push(e),r[i]=e)}),n}var z={},I=/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g;function B(e){return e.toLowerCase()}function U(e){if("string"!=typeof e)return"";var t=e.trim().replace(/[A-Z]+/g,B).replace(/<[^>\d]+>/g,"").replace(I,"").replace(/\s/g,"-").replace(/-+/g,"-").replace(/^(\d)/,"_$1"),r=z[t];return r=n.call(z,t)?r+1:0,z[t]=r,r&&(t=t+"-"+r),t}U.clear=function(){z={}};function D(e,t){return''+t+''}var Y=decodeURIComponent,W=encodeURIComponent;function G(e){var t={};return(e=e.trim().replace(/^(\?|#|&)/,""))?(e.split("&").forEach(function(e){var n=e.replace(/\+/g," ").split("=");t[n[0]]=n[1]&&Y(n[1])}),t):t}function X(e,t){void 0===t&&(t=[]);var n=[];for(var r in e)t.indexOf(r)>-1||n.push(e[r]?(W(r)+"="+W(e[r])).toLowerCase():W(r));return n.length?"?"+n.join("&"):""}var Q=e(function(e){return/(:|(\/{2}))/g.test(e)}),V=e(function(e){return/\/$/g.test(e)?e:(e=e.match(/(\S*\/)[^/]+$/))?e[1]:""}),Z=e(function(e){return e.replace(/^\/+/,"/").replace(/([^:])\/{2,}/g,"$1/")});function J(){for(var e=[],t=arguments.length;t--;)e[t]=arguments[t];return Z(e.join("/"))}var K=e(function(e){return e.replace("#","?id=")}),ee={};function te(e){void 0===e&&(e="");var t={};return e&&(e=e.replace(/:([\w-]+)=?([\w-]+)?/g,function(e,n,r){return t[n]=r&&r.replace(/"/g,"")||!0,""}).trim()),{str:e,config:t}}var ne={markdown:function(e){return{url:e}},iframe:function(e,t){return{code:'"}},video:function(e,t){return{code:'"}},audio:function(e,t){return{code:'"}},code:function(e,t){var n=e.match(/\.(\w+)$/);return"md"===(n=t||n&&n[1])&&(n="markdown"),{url:e,lang:n}}},re=function(t,n){this.config=t,this.router=n,this.cacheTree={},this.toc=[],this.linkTarget=t.externalLinkTarget||"_blank",this.contentBase=n.getBasePath();var a,s=this._initRenderer(),c=t.markdown||{};o(c)?a=c(q,s):(q.setOptions(r(c,{renderer:r(s,c.renderer)})),a=q),this._marked=a,this.compile=e(function(e){var n="";if(!e)return e;n=i(e)?a(e):a.parser(e),n=t.noEmoji?n:(r=n,r.replace(/<(pre|template|code)[^>]*?>[\s\S]+?<\/(pre|template|code)>/g,function(e){return e.replace(/:/g,"__colon__")}).replace(/:(\w+?):/gi,l&&window.emojify||D).replace(/__colon__/g,":"));var r;return U.clear(),n})};re.prototype.compileEmbed=function(e,t){var n,r=te(t),i=r.str,a=r.config;if(t=i,a.include){Q(e)||(e=J(this.contentBase,V(this.router.getCurrentPath()),e));var o;if(a.type&&(o=ne[a.type]))(n=o.call(this,e,t)).type=a.type;else{var s="code";/\.(md|markdown)/.test(e)?s="markdown":/\.html?/.test(e)?s="iframe":/\.(mp4|ogg)/.test(e)?s="video":/\.mp3/.test(e)&&(s="audio"),(n=ne[s].call(this,e,t)).type=s}return n}},re.prototype._matchNotCompileLink=function(e){for(var t=this.config.noCompileLinks||[],n=0;n
    '+e+""},a.code=e.code=function(e,t){void 0===t&&(t=""),e=e.replace(/@DOCSIFY_QM@/g,"`");return'
    '+R.highlight(e,R.languages[t]||R.languages.markup)+"
    "},a.link=e.link=function(e,r,a){void 0===r&&(r="");var o="",s=te(r),l=s.str,c=s.config;return r=l,/:|(\/{2})/.test(e)||i._matchNotCompileLink(e)||c.ignore?o+=' target="'+t+'"':(e===i.config.homepage&&(e="README"),e=n.toURL(e,null,n.getCurrentPath())),c.target&&(o+=" target="+c.target),c.disabled&&(o+=" disabled",e="javascript:void(0)"),r&&(o+=' title="'+r+'"'),'"+a+""},a.paragraph=e.paragraph=function(e){return/^!>/.test(e)?L("tip",e):/^\?>/.test(e)?L("warn",e):"

    "+e+"

    "},a.image=e.image=function(e,t,i){var a=e,o="",s=te(t);return t=s.str,s.config["no-zoom"]&&(o+=" data-no-zoom"),t&&(o+=' title="'+t+'"'),Q(e)||(a=J(r,V(n.getCurrentPath()),e)),''+i+'"};var o=/^\[([ x])\] +/;return a.listitem=e.listitem=function(e){var t=o.exec(e);return t&&(e=e.replace(o,'")),""+e+"\n"},e.origin=a,e},re.prototype.sidebar=function(e,t){var n=this.router.getCurrentPath(),r="";if(e)r=(r=this.compile(e))&&r.match(/]*>([\s\S]+)<\/ul>/g)[0];else{var i=this.cacheTree[n]||H(this.toc,t);r=C(i,"
      "),this.cacheTree[n]=i}return r},re.prototype.subSidebar=function(e){if(e){var t=this.router.getCurrentPath(),n=this.cacheTree,r=this.toc;r[0]&&r[0].ignoreAllSubs&&r.splice(0),r[0]&&1===r[0].level&&r.shift();for(var i=0;i')}this.toc=[]},re.prototype.article=function(e){return this.compile(e)},re.prototype.cover=function(e){var t=this.toc.slice(),n=this.compile(e);return this.toc=t.slice(),n};var ie=d.title;function ae(){var e=p("section.cover");if(e){var t=e.getBoundingClientRect().height;window.pageYOffset>=t||e.classList.contains("hidden")?_(g,"add","sticky"):_(g,"remove","sticky")}}function oe(e,t,n,r){var i,a=v(t=p(t),"a"),o=decodeURI(e.toURL(e.getCurrentPath()));return a.sort(function(e,t){return t.href.length-e.href.length}).forEach(function(e){var t=e.getAttribute("href"),r=n?e.parentNode:e;0!==o.indexOf(t)||i?_(r,"remove","active"):(i=e,_(r,"add","active"))}),r&&(d.title=i?i.innerText+" - "+ie:ie),i}var se=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.duration=t.duration||1e3,this.ease=t.easing||this._defaultEase,this.start=t.start,this.end=t.end,this.frame=null,this.next=null,this.isRunning=!1,this.events={},this.direction=this.startthis.end&&e>=this.next}[this.direction]}},{key:"_defaultEase",value:function(e,t,n,r){return(e/=r/2)<1?n/2*e*e+t:-n/2*(--e*(e-2)-1)+t}}]),e}(),ce={},ue=!1,he=null,pe=!0,de=0;function ge(e){if(pe){for(var t,n=p(".sidebar"),r=v(".anchor"),i=m(n,".sidebar-nav"),a=m(n,"li.active"),o=document.documentElement,s=(o&&o.scrollTop||document.body.scrollTop)-de,l=0,c=r.length;ls){t||(t=u);break}t=u}if(t){var h=ce[fe(e,t.getAttribute("data-id"))];if(h&&h!==a&&(a&&a.classList.remove("active"),h.classList.add("active"),a=h,!ue&&g.classList.contains("sticky"))){var d=n.clientHeight,f=a.offsetTop+a.clientHeight+40,b=f-0=i.scrollTop&&f<=i.scrollTop+d?i.scrollTop:b?0:f-d;n.scrollTop=y}}}}function fe(e,t){return e+"?id="+t}function me(e,t){if(t){var n=m("#"+t);n&&(r=n,he&&he.stop(),pe=!1,he=new le({start:window.pageYOffset,end:r.getBoundingClientRect().top+window.pageYOffset,duration:500}).on("tick",function(e){return window.scrollTo(0,e)}).on("done",function(){pe=!0,he=null}).begin());var r,i=ce[fe(e,t)],a=m(p(".sidebar"),"li.active");a&&a.classList.remove("active"),i&&i.classList.add("active")}}var ve=d.scrollingElement||d.documentElement;var be={};function ye(e,t){var n=e.compiler,i=e.raw;void 0===i&&(i="");var a,o=e.fetch;if(a=be[i])return t(a);var s=n._marked,l=s.lexer(i),c=[],u=s.InlineLexer.rules.link,h=l.links;l.forEach(function(e,t){"paragraph"===e.type&&(e.text=e.text.replace(new RegExp(u.source,"g"),function(e,r,i,a){var o=n.compileEmbed(i,a);return o?("markdown"!==o.type&&"code"!==o.type||c.push({index:t,embed:o}),o.code):e}))});var p=0;!function e(t,n){var r=t.step;void 0===r&&(r=0);var i=t.embedTokens,a=t.compile,o=t.fetch,s=i[r];if(!s)return n({});P(s.embed.url).then(function(t){var l;t&&("markdown"===s.embed.type?l=a.lexer(t):"code"===s.embed.type&&(l=a.lexer("```"+s.embed.lang+"\n"+t.replace(/`/g,"@DOCSIFY_QM@")+"\n```\n"))),n({token:s,embedToken:l}),e({step:++r,compile:a,embedTokens:i,fetch:o},n)})}({compile:s,embedTokens:c,fetch:o},function(e){var n=e.embedToken,a=e.token;if(a){var o=a.index+p;r(h,n.links),l=l.slice(0,o).concat(n,l.slice(o+1)),p+=n.length-1}else be[i]=l.concat(),l.links=be[i].links=h,t(l)})}function ke(){var e=v(".markdown-section>script").filter(function(e){return!/template/.test(e.type)})[0];if(!e)return!1;var t=e.innerText.trim();if(!t)return!1;setTimeout(function(e){window.__EXECUTE_RESULT__=new Function(t)()},0)}function we(e,t,n){return t="function"==typeof n?n(t):"string"==typeof n?function(e){var t=[],n=0;return e.replace(O,function(r,i,a){t.push(e.substring(n,a-1)),n=a+=r.length+1,t.push(function(e){return("00"+("string"==typeof M[r]?e[M[r]]():M[r](e))).slice(-r.length)})}),n!==e.length&&t.push(e.substring(n)),function(e){for(var n="",r=0,i=e||new Date;r'):"")),t.coverpage&&(a+='
      \x3c!--cover--\x3e
      '),a+=function(e){var t='';return(c?t+"
      ":"
      "+t)+'
      \x3c!--main--\x3e
      '}(t),e._renderTo(i,a,!0)):e.rendered=!0;var s;t.mergeNavbar&&c?o=m(".sidebar"):(r.classList.add("app-nav"),t.repo||r.classList.add("no-badge")),t.loadNavbar&&k(o,r),t.themeColor&&(d.head.appendChild(b("div",(u=t.themeColor,"")).firstElementChild),function(e){if(!(window.CSS&&window.CSS.supports&&window.CSS.supports("(--v:red)"))){var t=v("style:not(.inserted),link");[].forEach.call(t,function(t){if("STYLE"===t.nodeName)F(t,e);else if("LINK"===t.nodeName){var n=t.getAttribute("href");if(!/\.css$/.test(n))return;P(n).then(function(t){var n=b("style",t);f.appendChild(n),F(n,e)})}})}}(t.themeColor));var u;e._updateRender(),_(g,"ready")}var Se={};var Ce=function(e){this.config=e};Ce.prototype.getBasePath=function(){return this.config.basePath},Ce.prototype.getFile=function(e,t){void 0===e&&(e=this.getCurrentPath());var n=this.config,r=this.getBasePath(),i="string"==typeof n.ext?n.ext:".md";e=n.alias?function e(t,n,r){var i=Object.keys(n).filter(function(e){return(Se[e]||(Se[e]=new RegExp("^"+e+"$"))).test(t)&&t!==r})[0];return i?e(t.replace(Se[i],n[i]),n,t):t}(e,n.alias):e,a=e,o=i;var a,o;return e=(e=new RegExp("\\.("+o.replace(/^\./,"")+"|html)$","g").test(a)?a:/\/$/g.test(a)?a+"README"+o:""+a+o)==="/README"+i?n.homepage||e:e,e=Q(e)?e:J(r,e),t&&(e=e.replace(new RegExp("^"+r),"")),e},Ce.prototype.onchange=function(e){void 0===e&&(e=a),e()},Ce.prototype.getCurrentPath=function(){},Ce.prototype.normalize=function(){},Ce.prototype.parse=function(){},Ce.prototype.toURL=function(e,t,n){var i=n&&"#"===e[0],a=this.parse(K(e));if(a.query=r({},a.query,t),e=(e=a.path+X(a.query)).replace(/\.md(\?)|\.md$/,"$1"),i){var o=n.indexOf("?");e=(o>0?n.substr(0,o):n)+e}return Z("/"+e)};function Le(e){var t=location.href.indexOf("#");location.replace(location.href.slice(0,t>=0?t:0)+"#"+e)}var Ee=function(e){function t(t){e.call(this,t),this.mode="hash"}return e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t,t.prototype.getBasePath=function(){var e=window.location.pathname||"",t=this.config.basePath;return/^(\/|https?:)/g.test(t)?t:Z(e+"/"+t)},t.prototype.getCurrentPath=function(){var e=location.href,t=e.indexOf("#");return-1===t?"":e.slice(t+1)},t.prototype.onchange=function(e){void 0===e&&(e=a),w("hashchange",e)},t.prototype.normalize=function(){var e=this.getCurrentPath();if("/"===(e=K(e)).charAt(0))return Le(e);Le("/"+e)},t.prototype.parse=function(e){void 0===e&&(e=location.href);var t="",n=e.indexOf("#");n>=0&&(e=e.slice(n+1));var r=e.indexOf("?");return r>=0&&(t=e.slice(r+1),e=e.slice(0,r)),{path:e,file:this.getFile(e,!0),query:G(t)}},t.prototype.toURL=function(t,n,r){return"#"+e.prototype.toURL.call(this,t,n,r)},t}(Ce),Ae=function(e){function t(t){e.call(this,t),this.mode="history"}return e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t,t.prototype.getCurrentPath=function(){var e=this.getBasePath(),t=window.location.pathname;return e&&0===t.indexOf(e)&&(t=t.slice(e.length)),(t||"/")+window.location.search+window.location.hash},t.prototype.onchange=function(e){void 0===e&&(e=a),w("click",function(t){var n="A"===t.target.tagName?t.target:t.target.parentNode;if("A"===n.tagName&&!/_blank/.test(n.target)){t.preventDefault();var r=n.href;window.history.pushState({key:r},"",r),e()}}),w("popstate",e)},t.prototype.parse=function(e){void 0===e&&(e=location.href);var t="",n=e.indexOf("?");n>=0&&(t=e.slice(n+1),e=e.slice(0,n));var r=J(location.origin),i=e.indexOf(r);return i>-1&&(e=e.slice(i+r.length)),{path:e,file:this.getFile(e),query:G(t)}},t}(Ce);var $e={};function Te(e){e.router.normalize(),e.route=e.router.parse(),g.setAttribute("data-page",e.route.file)}function Pe(e){!function(e){var t=function(e){return g.classList.toggle("close")};w(e=p(e),"click",function(e){e.stopPropagation(),t()}),c&&w(g,"click",function(e){return g.classList.contains("close")&&t()})}("button.sidebar-toggle",e.router),t=".sidebar",e.router,w(t=p(t),"click",function(e){var t=e.target;"A"===t.nodeName&&t.nextSibling&&t.nextSibling.classList.contains("app-sub-sidebar")&&_(t.parentNode,"collapse")});var t;e.config.coverpage?!c&&w("scroll",ae):g.classList.add("sticky")}function Fe(e,t,n,r,i,a){e=a?e:e.replace(/\/$/,""),(e=V(e))&&P(i.router.getFile(e+n)+t,!1,i.config.requestHeaders).then(r,function(a){return Fe(e,t,n,r,i)})}var Oe=Object.freeze({cached:e,hyphenate:t,hasOwn:n,merge:r,isPrimitive:i,noop:a,isFn:o,inBrowser:l,isMobile:c,supportsPushState:u,parseQuery:G,stringifyQuery:X,isAbsolutePath:Q,getParentPath:V,cleanPath:Z,getPath:J,replaceSlug:K});function Me(){this._init()}var Ne=Me.prototype;Ne._init=function(){this.config=function(){var e=r({el:"#app",repo:"",maxLevel:6,subMaxLevel:0,loadSidebar:null,loadNavbar:null,homepage:"README.md",coverpage:"",basePath:"",auto2top:!1,name:"",themeColor:"",nameLink:window.location.pathname,autoHeader:!1,executeScript:null,noEmoji:!1,ga:"",ext:".md",mergeNavbar:!1,formatUpdated:"",externalLinkTarget:"_blank",routerMode:"hash",noCompileLinks:[]},window.$docsify),a=document.currentScript||[].slice.call(document.getElementsByTagName("script")).filter(function(e){return/docsify\./.test(e.src)})[0];if(a){for(var o in e)if(n.call(e,o)){var s=a.getAttribute("data-"+t(o));i(s)&&(e[o]=""===s||s)}!0===e.loadSidebar&&(e.loadSidebar="_sidebar"+e.ext),!0===e.loadNavbar&&(e.loadNavbar="_navbar"+e.ext),!0===e.coverpage&&(e.coverpage="_coverpage"+e.ext),!0===e.repo&&(e.repo=""),!0===e.name&&(e.name="")}return window.$docsify=e,e}(),(e=this)._hooks={},e._lifecycle={},["init","mounted","beforeEach","afterEach","doneEach","ready"].forEach(function(t){var n=e._hooks[t]=[];e._lifecycle[t]=function(e){return n.push(e)}});var e;[].concat((a=this).config.plugins).forEach(function(e){return o(e)&&e(a._lifecycle,a)});var a;s(this,"init"),function(e){var t,n=e.config;t="history"===(n.routerMode||"hash")&&u?new Ae(n):new Ee(n),e.router=t,Te(e),$e=e.route,t.onchange(function(t){Te(e),e._updateRender(),$e.path!==e.route.path?(e.$fetch(),$e=e.route):e.$resetEvents()})}(this),_e(this),Pe(this),function(e){var t=e.config.loadSidebar;if(e.rendered){var n=oe(e.router,".sidebar-nav",!0,!0);t&&n&&(n.parentNode.innerHTML+=window.__SUB_SIDEBAR__),e._bindEventOnRendered(n),e.$resetEvents(),s(e,"doneEach"),s(e,"ready")}else e.$fetch(function(t){return s(e,"ready")})}(this),s(this,"mounted")};Ne.route={};(je=Ne)._renderTo=function(e,t,n){var r=p(e);r&&(r[n?"outerHTML":"innerHTML"]=t)},je._renderSidebar=function(e){var t=this.config,n=t.maxLevel,r=t.subMaxLevel,i=t.loadSidebar;this._renderTo(".sidebar-nav",this.compiler.sidebar(e,n));var a=oe(this.router,".sidebar-nav",!0,!0);i&&a?a.parentNode.innerHTML+=this.compiler.subSidebar(r)||"":this.compiler.subSidebar(),this._bindEventOnRendered(a)},je._bindEventOnRendered=function(e){var t=this.config,n=t.autoHeader,r=t.auto2top;if(function(e){var t=m(".cover.show");de=t?t.offsetHeight:0;for(var n=p(".sidebar"),r=v(n,"li"),i=0,a=r.length;i([^<]*?)

      $');if(i){if("color"===i[2])n.style.background=i[1]+(i[3]||"");else{var a=i[1];_(n,"add","has-mask"),Q(i[1])||(a=J(this.router.getBasePath(),i[1])),n.style.backgroundImage="url("+a+")",n.style.backgroundSize="cover",n.style.backgroundPosition="center center"}r=r.replace(i[0],"")}this._renderTo(".cover-main",r),ae()}else _(n,"remove","show")},je._updateRender=function(){!function(e){var t=p(".app-name-link"),n=e.config.nameLink,r=e.route.path;if(t)if(i(e.config.nameLink))t.setAttribute("href",n);else if("object"==typeof n){var a=Object.keys(n).filter(function(e){return r.indexOf(e)>-1})[0];t.setAttribute("href",n[a])}}(this)};var je;!function(e){var t,n=function(e,n,r){return t&&t.abort&&t.abort(),t=P(e,!0,r)};e._loadSideAndNav=function(e,t,n,r){var i=this;return function(){if(!n)return r();Fe(e,t,n,function(e){i._renderSidebar(e),r()},i,!0)}},e._fetch=function(e){var t=this;void 0===e&&(e=a);var r=this.route,i=r.path,o=X(r.query,["id"]),s=this.config,l=s.loadNavbar,c=s.requestHeaders,u=s.loadSidebar,h=this.router.getFile(i),p=n(h+o,0,c);this.isHTML=/\.html$/g.test(h),p.then(function(n,r){return t._renderMain(n,r,t._loadSideAndNav(i,o,u,e))},function(n){t._fetchFallbackPage(h,o,e)||t._fetch404(h,o,e)}),l&&Fe(i,o,l,function(e){return t._renderNav(e)},this,!0)},e._fetchCover=function(){var e=this,t=this.config,n=t.coverpage,r=t.requestHeaders,i=this.route.query,a=V(this.route.path);if(n){var o=null,s=this.route.path;if("string"==typeof n)"/"===s&&(o=n);else if(Array.isArray(n))o=n.indexOf(s)>-1&&"_coverpage";else{var l=n[s];o=!0===l?"_coverpage":l}var c=Boolean(o)&&this.config.onlyCover;return o?(o=this.router.getFile(a+o),this.coverIsHTML=/\.html$/g.test(o),P(o+X(i,["id"]),!1,r).then(function(t){return e._renderCover(t,c)})):this._renderCover(null,c),c}},e.$fetch=function(e){var t=this;void 0===e&&(e=a);var n=function(){s(t,"doneEach"),e()};this._fetchCover()?n():this._fetch(function(){t.$resetEvents(),n()})},e._fetchFallbackPage=function(e,t,r){var i=this;void 0===r&&(r=a);var o=this.config,s=o.requestHeaders,l=o.fallbackLanguages,c=o.loadSidebar;if(!l)return!1;var u=e.split("/")[1];if(-1===l.indexOf(u))return!1;var h=e.replace(new RegExp("^/"+u),"");return n(h+t,0,s).then(function(n,a){return i._renderMain(n,a,i._loadSideAndNav(e,t,c,r))},function(){return i._fetch404(e,t,r)}),!0},e._fetch404=function(e,t,r){var i=this;void 0===r&&(r=a);var o=this.config,s=o.loadSidebar,l=o.requestHeaders,c=o.notFoundPage,u=this._loadSideAndNav(e,t,s,r);if(c){var h=function(e,t){var n,r,i=t.notFoundPage,a="_404"+(t.ext||".md");switch(typeof i){case"boolean":r=a;break;case"string":r=i;break;case"object":r=(n=Object.keys(i).sort(function(e,t){return t.length-e.length}).find(function(t){return e.match(new RegExp("^"+t))}))&&i[n]||a}return r}(e,this.config);return n(this.router.getFile(h),0,l).then(function(e,t){return i._renderMain(e,t,u)},function(){return i._renderMain(null,{},u)}),!0}return this._renderMain(null,{},u),!1}}(Ne),Ne.$resetEvents=function(){me(this.route.path,this.route.query.id),this.config.loadNavbar&&oe(this.router,"nav")};window.Docsify={util:Oe,dom:S,get:P,slugify:U},window.DocsifyCompiler=re,window.marked=q,window.Prism=R,Me.version="4.6.10",function(e){var t=document.readyState;if("complete"===t||"interactive"===t)return setTimeout(e,0);document.addEventListener("DOMContentLoaded",e)}(function(e){return new Me})}(); -------------------------------------------------------------------------------- /assets/emoji.min.js: -------------------------------------------------------------------------------- 1 | !function(){var e=["+1","100","1234","8ball","a","ab","abc","abcd","accept","aerial_tramway","airplane","alarm_clock","alien","ambulance","anchor","angel","anger","angry","anguished","ant","apple","aquarius","aries","arrow_backward","arrow_double_down","arrow_double_up","arrow_down","arrow_down_small","arrow_forward","arrow_heading_down","arrow_heading_up","arrow_left","arrow_lower_left","arrow_lower_right","arrow_right","arrow_right_hook","arrow_up","arrow_up_down","arrow_up_small","arrow_upper_left","arrow_upper_right","arrows_clockwise","arrows_counterclockwise","art","articulated_lorry","astonished","athletic_shoe","atm","b","baby","baby_bottle","baby_chick","baby_symbol","back","baggage_claim","balloon","ballot_box_with_check","bamboo","banana","bangbang","bank","bar_chart","barber","baseball","basketball","bath","bathtub","battery","bear","bee","beer","beers","beetle","beginner","bell","bento","bicyclist","bike","bikini","bird","birthday","black_circle","black_joker","black_large_square","black_medium_small_square","black_medium_square","black_nib","black_small_square","black_square_button","blossom","blowfish","blue_book","blue_car","blue_heart","blush","boar","boat","bomb","book","bookmark","bookmark_tabs","books","boom","boot","bouquet","bow","bowling","bowtie","boy","bread","bride_with_veil","bridge_at_night","briefcase","broken_heart","bug","bulb","bullettrain_front","bullettrain_side","bus","busstop","bust_in_silhouette","busts_in_silhouette","cactus","cake","calendar","calling","camel","camera","cancer","candy","capital_abcd","capricorn","car","card_index","carousel_horse","cat","cat2","cd","chart","chart_with_downwards_trend","chart_with_upwards_trend","checkered_flag","cherries","cherry_blossom","chestnut","chicken","children_crossing","chocolate_bar","christmas_tree","church","cinema","circus_tent","city_sunrise","city_sunset","cl","clap","clapper","clipboard","clock1","clock10","clock1030","clock11","clock1130","clock12","clock1230","clock130","clock2","clock230","clock3","clock330","clock4","clock430","clock5","clock530","clock6","clock630","clock7","clock730","clock8","clock830","clock9","clock930","closed_book","closed_lock_with_key","closed_umbrella","cloud","clubs","cn","cocktail","coffee","cold_sweat","collision","computer","confetti_ball","confounded","confused","congratulations","construction","construction_worker","convenience_store","cookie","cool","cop","copyright","corn","couple","couple_with_heart","couplekiss","cow","cow2","credit_card","crescent_moon","crocodile","crossed_flags","crown","cry","crying_cat_face","crystal_ball","cupid","curly_loop","currency_exchange","curry","custard","customs","cyclone","dancer","dancers","dango","dart","dash","date","de","deciduous_tree","department_store","diamond_shape_with_a_dot_inside","diamonds","disappointed","disappointed_relieved","dizzy","dizzy_face","do_not_litter","dog","dog2","dollar","dolls","dolphin","door","doughnut","dragon","dragon_face","dress","dromedary_camel","droplet","dvd","e-mail","ear","ear_of_rice","earth_africa","earth_americas","earth_asia","egg","eggplant","eight","eight_pointed_black_star","eight_spoked_asterisk","electric_plug","elephant","email","end","envelope","envelope_with_arrow","es","euro","european_castle","european_post_office","evergreen_tree","exclamation","expressionless","eyeglasses","eyes","facepunch","factory","fallen_leaf","family","fast_forward","fax","fearful","feelsgood","feet","ferris_wheel","file_folder","finnadie","fire","fire_engine","fireworks","first_quarter_moon","first_quarter_moon_with_face","fish","fish_cake","fishing_pole_and_fish","fist","five","flags","flashlight","flipper","floppy_disk","flower_playing_cards","flushed","foggy","football","footprints","fork_and_knife","fountain","four","four_leaf_clover","fr","free","fried_shrimp","fries","frog","frowning","fu","fuelpump","full_moon","full_moon_with_face","game_die","gb","gem","gemini","ghost","gift","gift_heart","girl","globe_with_meridians","goat","goberserk","godmode","golf","grapes","green_apple","green_book","green_heart","grey_exclamation","grey_question","grimacing","grin","grinning","guardsman","guitar","gun","haircut","hamburger","hammer","hamster","hand","handbag","hankey","hash","hatched_chick","hatching_chick","headphones","hear_no_evil","heart","heart_decoration","heart_eyes","heart_eyes_cat","heartbeat","heartpulse","hearts","heavy_check_mark","heavy_division_sign","heavy_dollar_sign","heavy_exclamation_mark","heavy_minus_sign","heavy_multiplication_x","heavy_plus_sign","helicopter","herb","hibiscus","high_brightness","high_heel","hocho","honey_pot","honeybee","horse","horse_racing","hospital","hotel","hotsprings","hourglass","hourglass_flowing_sand","house","house_with_garden","hurtrealbad","hushed","ice_cream","icecream","id","ideograph_advantage","imp","inbox_tray","incoming_envelope","information_desk_person","information_source","innocent","interrobang","iphone","it","izakaya_lantern","jack_o_lantern","japan","japanese_castle","japanese_goblin","japanese_ogre","jeans","joy","joy_cat","jp","key","keycap_ten","kimono","kiss","kissing","kissing_cat","kissing_closed_eyes","kissing_heart","kissing_smiling_eyes","koala","koko","kr","lantern","large_blue_circle","large_blue_diamond","large_orange_diamond","last_quarter_moon","last_quarter_moon_with_face","laughing","leaves","ledger","left_luggage","left_right_arrow","leftwards_arrow_with_hook","lemon","leo","leopard","libra","light_rail","link","lips","lipstick","lock","lock_with_ink_pen","lollipop","loop","loud_sound","loudspeaker","love_hotel","love_letter","low_brightness","m","mag","mag_right","mahjong","mailbox","mailbox_closed","mailbox_with_mail","mailbox_with_no_mail","man","man_with_gua_pi_mao","man_with_turban","mans_shoe","maple_leaf","mask","massage","meat_on_bone","mega","melon","memo","mens","metal","metro","microphone","microscope","milky_way","minibus","minidisc","mobile_phone_off","money_with_wings","moneybag","monkey","monkey_face","monorail","moon","mortar_board","mount_fuji","mountain_bicyclist","mountain_cableway","mountain_railway","mouse","mouse2","movie_camera","moyai","muscle","mushroom","musical_keyboard","musical_note","musical_score","mute","nail_care","name_badge","neckbeard","necktie","negative_squared_cross_mark","neutral_face","new","new_moon","new_moon_with_face","newspaper","ng","night_with_stars","nine","no_bell","no_bicycles","no_entry","no_entry_sign","no_good","no_mobile_phones","no_mouth","no_pedestrians","no_smoking","non-potable_water","nose","notebook","notebook_with_decorative_cover","notes","nut_and_bolt","o","o2","ocean","octocat","octopus","oden","office","ok","ok_hand","ok_woman","older_man","older_woman","on","oncoming_automobile","oncoming_bus","oncoming_police_car","oncoming_taxi","one","open_book","open_file_folder","open_hands","open_mouth","ophiuchus","orange_book","outbox_tray","ox","package","page_facing_up","page_with_curl","pager","palm_tree","panda_face","paperclip","parking","part_alternation_mark","partly_sunny","passport_control","paw_prints","peach","pear","pencil","pencil2","penguin","pensive","performing_arts","persevere","person_frowning","person_with_blond_hair","person_with_pouting_face","phone","pig","pig2","pig_nose","pill","pineapple","pisces","pizza","point_down","point_left","point_right","point_up","point_up_2","police_car","poodle","poop","post_office","postal_horn","postbox","potable_water","pouch","poultry_leg","pound","pouting_cat","pray","princess","punch","purple_heart","purse","pushpin","put_litter_in_its_place","question","rabbit","rabbit2","racehorse","radio","radio_button","rage","rage1","rage2","rage3","rage4","railway_car","rainbow","raised_hand","raised_hands","raising_hand","ram","ramen","rat","recycle","red_car","red_circle","registered","relaxed","relieved","repeat","repeat_one","restroom","revolving_hearts","rewind","ribbon","rice","rice_ball","rice_cracker","rice_scene","ring","rocket","roller_coaster","rooster","rose","rotating_light","round_pushpin","rowboat","ru","rugby_football","runner","running","running_shirt_with_sash","sa","sagittarius","sailboat","sake","sandal","santa","satellite","satisfied","saxophone","school","school_satchel","scissors","scorpius","scream","scream_cat","scroll","seat","secret","see_no_evil","seedling","seven","shaved_ice","sheep","shell","ship","shipit","shirt","shit","shoe","shower","signal_strength","six","six_pointed_star","ski","skull","sleeping","sleepy","slot_machine","small_blue_diamond","small_orange_diamond","small_red_triangle","small_red_triangle_down","smile","smile_cat","smiley","smiley_cat","smiling_imp","smirk","smirk_cat","smoking","snail","snake","snowboarder","snowflake","snowman","sob","soccer","soon","sos","sound","space_invader","spades","spaghetti","sparkle","sparkler","sparkles","sparkling_heart","speak_no_evil","speaker","speech_balloon","speedboat","squirrel","star","star2","stars","station","statue_of_liberty","steam_locomotive","stew","straight_ruler","strawberry","stuck_out_tongue","stuck_out_tongue_closed_eyes","stuck_out_tongue_winking_eye","sun_with_face","sunflower","sunglasses","sunny","sunrise","sunrise_over_mountains","surfer","sushi","suspect","suspension_railway","sweat","sweat_drops","sweat_smile","sweet_potato","swimmer","symbols","syringe","tada","tanabata_tree","tangerine","taurus","taxi","tea","telephone","telephone_receiver","telescope","tennis","tent","thought_balloon","three","thumbsdown","thumbsup","ticket","tiger","tiger2","tired_face","tm","toilet","tokyo_tower","tomato","tongue","top","tophat","tractor","traffic_light","train","train2","tram","triangular_flag_on_post","triangular_ruler","trident","triumph","trolleybus","trollface","trophy","tropical_drink","tropical_fish","truck","trumpet","tshirt","tulip","turtle","tv","twisted_rightwards_arrows","two","two_hearts","two_men_holding_hands","two_women_holding_hands","u5272","u5408","u55b6","u6307","u6708","u6709","u6e80","u7121","u7533","u7981","u7a7a","uk","umbrella","unamused","underage","unlock","up","us","v","vertical_traffic_light","vhs","vibration_mode","video_camera","video_game","violin","virgo","volcano","vs","walking","waning_crescent_moon","waning_gibbous_moon","warning","watch","water_buffalo","watermelon","wave","wavy_dash","waxing_crescent_moon","waxing_gibbous_moon","wc","weary","wedding","whale","whale2","wheelchair","white_check_mark","white_circle","white_flower","white_large_square","white_medium_small_square","white_medium_square","white_small_square","white_square_button","wind_chime","wine_glass","wink","wolf","woman","womans_clothes","womans_hat","womens","worried","wrench","x","yellow_heart","yen","yum","zap","zero","zzz"];window.emojify=function(a,o){return-1===e.indexOf(o)?a:''+o+''}}(); -------------------------------------------------------------------------------- /assets/itheima-cli.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itheima2017/vue-element-admin-doc-itheima/2b1383efc61279f6d627c7baf581c0ecebbfc3f6/assets/itheima-cli.gif -------------------------------------------------------------------------------- /assets/prism-bash.js: -------------------------------------------------------------------------------- 1 | (function(Prism) { 2 | var insideString = { 3 | variable: [ 4 | // Arithmetic Environment 5 | { 6 | pattern: /\$?\(\([\s\S]+?\)\)/, 7 | inside: { 8 | // If there is a $ sign at the beginning highlight $(( and )) as variable 9 | variable: [{ 10 | pattern: /(^\$\(\([\s\S]+)\)\)/, 11 | lookbehind: true 12 | }, 13 | /^\$\(\(/ 14 | ], 15 | number: /\b0x[\dA-Fa-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee]-?\d+)?/, 16 | // Operators according to https://www.gnu.org/software/bash/manual/bashref.html#Shell-Arithmetic 17 | operator: /--?|-=|\+\+?|\+=|!=?|~|\*\*?|\*=|\/=?|%=?|<<=?|>>=?|<=?|>=?|==?|&&?|&=|\^=?|\|\|?|\|=|\?|:/, 18 | // If there is no $ sign at the beginning highlight (( and )) as punctuation 19 | punctuation: /\(\(?|\)\)?|,|;/ 20 | } 21 | }, 22 | // Command Substitution 23 | { 24 | pattern: /\$\([^)]+\)|`[^`]+`/, 25 | greedy: true, 26 | inside: { 27 | variable: /^\$\(|^`|\)$|`$/ 28 | } 29 | }, 30 | /\$(?:[\w#?*!@]+|\{[^}]+\})/i 31 | ] 32 | }; 33 | 34 | Prism.languages.bash = { 35 | 'shebang': { 36 | pattern: /^#!\s*\/bin\/bash|^#!\s*\/bin\/sh/, 37 | alias: 'important' 38 | }, 39 | 'comment': { 40 | pattern: /(^|[^"{\\])#.*/, 41 | lookbehind: true 42 | }, 43 | 'string': [ 44 | //Support for Here-Documents https://en.wikipedia.org/wiki/Here_document 45 | { 46 | pattern: /((?:^|[^<])<<\s*)["']?(\w+?)["']?\s*\r?\n(?:[\s\S])*?\r?\n\2/, 47 | lookbehind: true, 48 | greedy: true, 49 | inside: insideString 50 | }, 51 | { 52 | pattern: /(["'])(?:\\[\s\S]|\$\([^)]+\)|`[^`]+`|(?!\1)[^\\])*\1/, 53 | greedy: true, 54 | inside: insideString 55 | } 56 | ], 57 | 'variable': insideString.variable, 58 | // Originally based on http://ss64.com/bash/ 59 | 'function': { 60 | pattern: /(^|[\s;|&])(?:alias|apropos|apt-get|aptitude|aspell|awk|basename|bash|bc|bg|builtin|bzip2|cal|cat|cd|cfdisk|chgrp|chmod|chown|chroot|chkconfig|cksum|clear|cmp|comm|command|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|enable|env|ethtool|eval|exec|expand|expect|export|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|getopts|git|grep|groupadd|groupdel|groupmod|groups|gzip|hash|head|help|hg|history|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|jobs|join|kill|killall|less|link|ln|locate|logname|logout|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|make|man|mkdir|mkfifo|mkisofs|mknod|more|most|mount|mtools|mtr|mv|mmv|nano|netstat|nice|nl|nohup|notify-send|npm|nslookup|open|op|passwd|paste|pathchk|ping|pkill|popd|pr|printcap|printenv|printf|ps|pushd|pv|pwd|quota|quotacheck|quotactl|ram|rar|rcp|read|readarray|readonly|reboot|rename|renice|remsync|rev|rm|rmdir|rsync|screen|scp|sdiff|sed|seq|service|sftp|shift|shopt|shutdown|sleep|slocate|sort|source|split|ssh|stat|strace|su|sudo|sum|suspend|sync|tail|tar|tee|test|time|timeout|times|touch|top|traceroute|trap|tr|tsort|tty|type|ulimit|umask|umount|unalias|uname|unexpand|uniq|units|unrar|unshar|uptime|useradd|userdel|usermod|users|uuencode|uudecode|v|vdir|vi|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yes|zip)(?=$|[\s;|&])/, 61 | lookbehind: true 62 | }, 63 | 'keyword': { 64 | pattern: /(^|[\s;|&])(?:let|:|\.|if|then|else|elif|fi|for|break|continue|while|in|case|function|select|do|done|until|echo|exit|return|set|declare)(?=$|[\s;|&])/, 65 | lookbehind: true 66 | }, 67 | 'boolean': { 68 | pattern: /(^|[\s;|&])(?:true|false)(?=$|[\s;|&])/, 69 | lookbehind: true 70 | }, 71 | 'operator': /&&?|\|\|?|==?|!=?|<<>|<=?|>=?|=~/, 72 | 'punctuation': /\$?\(\(?|\)\)?|\.\.|[{}[\];]/ 73 | }; 74 | 75 | var inside = insideString.variable[1].inside; 76 | inside.string = Prism.languages.bash.string; 77 | inside['function'] = Prism.languages.bash['function']; 78 | inside.keyword = Prism.languages.bash.keyword; 79 | inside.boolean = Prism.languages.bash.boolean; 80 | inside.operator = Prism.languages.bash.operator; 81 | inside.punctuation = Prism.languages.bash.punctuation; 82 | 83 | Prism.languages.shell = Prism.languages.bash; 84 | })(Prism); 85 | -------------------------------------------------------------------------------- /assets/prism-java.js: -------------------------------------------------------------------------------- 1 | Prism.languages.java = Prism.languages.extend('clike', { 2 | 'keyword': /\b(?:abstract|continue|for|new|switch|assert|default|goto|package|synchronized|boolean|do|if|private|this|break|double|implements|protected|throw|byte|else|import|public|throws|case|enum|instanceof|return|transient|catch|extends|int|short|try|char|final|interface|static|void|class|finally|long|strictfp|volatile|const|float|native|super|while)\b/, 3 | 'number': /\b0b[01]+\b|\b0x[\da-f]*\.?[\da-fp-]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?[df]?/i, 4 | 'operator': { 5 | pattern: /(^|[^.])(?:\+[+=]?|-[-=]?|!=?|<>?>?=?|==?|&[&=]?|\|[|=]?|\*=?|\/=?|%=?|\^=?|[?:~])/m, 6 | lookbehind: true 7 | } 8 | }); 9 | 10 | Prism.languages.insertBefore('java','function', { 11 | 'annotation': { 12 | alias: 'punctuation', 13 | pattern: /(^|[^.])@\w+/, 14 | lookbehind: true 15 | } 16 | }); 17 | 18 | Prism.languages.insertBefore('java', 'class-name', { 19 | 'generics': { 20 | pattern: /<\s*\w+(?:\.\w+)?(?:\s*,\s*\w+(?:\.\w+)?)*>/i, 21 | alias: 'function', 22 | inside: { 23 | keyword: Prism.languages.java.keyword, 24 | punctuation: /[<>(),.:]/ 25 | } 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /assets/search.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var INDEXS = {}; 3 | var helper; 4 | 5 | function escapeHtml(string) { 6 | var entityMap = { 7 | '&': '&', 8 | '<': '<', 9 | '>': '>', 10 | '"': '"', 11 | '\'': ''', 12 | '/': '/' 13 | }; 14 | 15 | return String(string).replace(/[&<>"'/]/g, function (s) { return entityMap[s]; }) 16 | } 17 | 18 | function getAllPaths(router) { 19 | var paths = []; 20 | 21 | helper.dom.findAll('a:not([data-nosearch])').forEach(function (node) { 22 | var href = node.href; 23 | var originHref = node.getAttribute('href'); 24 | var path = router.parse(href).path; 25 | 26 | if ( 27 | path && 28 | paths.indexOf(path) === -1 && 29 | !Docsify.util.isAbsolutePath(originHref) 30 | ) { 31 | paths.push(path); 32 | } 33 | }); 34 | 35 | return paths 36 | } 37 | 38 | function saveData(maxAge) { 39 | localStorage.setItem('docsify.search.expires', Date.now() + maxAge); 40 | localStorage.setItem('docsify.search.index', JSON.stringify(INDEXS)); 41 | } 42 | 43 | function genIndex(path, content, router, depth) { 44 | if ( content === void 0 ) content = ''; 45 | 46 | var tokens = window.marked.lexer(content); 47 | var slugify = window.Docsify.slugify; 48 | var index = {}; 49 | var slug; 50 | 51 | tokens.forEach(function (token) { 52 | if (token.type === 'heading' && token.depth <= depth) { 53 | slug = router.toURL(path, {id: slugify(token.text)}); 54 | index[slug] = {slug: slug, title: token.text, body: ''}; 55 | } else { 56 | if (!slug) { 57 | return 58 | } 59 | if (!index[slug]) { 60 | index[slug] = {slug: slug, title: '', body: ''}; 61 | } else if (index[slug].body) { 62 | index[slug].body += '\n' + (token.text || ''); 63 | } else { 64 | index[slug].body = token.text; 65 | } 66 | } 67 | }); 68 | slugify.clear(); 69 | return index 70 | } 71 | 72 | /** 73 | * @param {String} query 74 | * @returns {Array} 75 | */ 76 | function search(query) { 77 | var matchingResults = []; 78 | var data = []; 79 | Object.keys(INDEXS).forEach(function (key) { 80 | data = data.concat(Object.keys(INDEXS[key]).map(function (page) { return INDEXS[key][page]; })); 81 | }); 82 | 83 | query = query.trim(); 84 | var keywords = query.split(/[\s\-,\\/]+/); 85 | if (keywords.length !== 1) { 86 | keywords = [].concat(query, keywords); 87 | } 88 | 89 | var loop = function ( i ) { 90 | var post = data[i]; 91 | var isMatch = false; 92 | var resultStr = ''; 93 | var postTitle = post.title && post.title.trim(); 94 | var postContent = post.body && post.body.trim(); 95 | var postUrl = post.slug || ''; 96 | 97 | if (postTitle && postContent) { 98 | keywords.forEach(function (keyword) { 99 | // From https://github.com/sindresorhus/escape-string-regexp 100 | var regEx = new RegExp( 101 | keyword.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'), 102 | 'gi' 103 | ); 104 | var indexTitle = -1; 105 | var indexContent = -1; 106 | 107 | indexTitle = postTitle && postTitle.search(regEx); 108 | indexContent = postContent && postContent.search(regEx); 109 | 110 | if (indexTitle < 0 && indexContent < 0) { 111 | isMatch = false; 112 | } else { 113 | isMatch = true; 114 | if (indexContent < 0) { 115 | indexContent = 0; 116 | } 117 | 118 | var start = 0; 119 | var end = 0; 120 | 121 | start = indexContent < 11 ? 0 : indexContent - 10; 122 | end = start === 0 ? 70 : indexContent + keyword.length + 60; 123 | 124 | if (end > postContent.length) { 125 | end = postContent.length; 126 | } 127 | 128 | var matchContent = 129 | '...' + 130 | escapeHtml(postContent) 131 | .substring(start, end) 132 | .replace(regEx, ("" + keyword + "")) + 133 | '...'; 134 | 135 | resultStr += matchContent; 136 | } 137 | }); 138 | 139 | if (isMatch) { 140 | var matchingPost = { 141 | title: escapeHtml(postTitle), 142 | content: resultStr, 143 | url: postUrl 144 | }; 145 | 146 | matchingResults.push(matchingPost); 147 | } 148 | } 149 | }; 150 | 151 | for (var i = 0; i < data.length; i++) loop( i ); 152 | 153 | return matchingResults 154 | } 155 | 156 | function init$1(config, vm) { 157 | helper = Docsify; 158 | 159 | var isAuto = config.paths === 'auto'; 160 | var isExpired = localStorage.getItem('docsify.search.expires') < Date.now(); 161 | 162 | INDEXS = JSON.parse(localStorage.getItem('docsify.search.index')); 163 | 164 | if (isExpired) { 165 | INDEXS = {}; 166 | } else if (!isAuto) { 167 | return 168 | } 169 | 170 | var paths = isAuto ? getAllPaths(vm.router) : config.paths; 171 | var len = paths.length; 172 | var count = 0; 173 | 174 | paths.forEach(function (path) { 175 | if (INDEXS[path]) { 176 | return count++ 177 | } 178 | 179 | helper 180 | .get(vm.router.getFile(path), false, vm.config.requestHeaders) 181 | .then(function (result) { 182 | INDEXS[path] = genIndex(path, result, vm.router, config.depth); 183 | len === ++count && saveData(config.maxAge); 184 | }); 185 | }); 186 | } 187 | 188 | var NO_DATA_TEXT = ''; 189 | 190 | function style() { 191 | var code = "\n.sidebar {\n padding-top: 0;\n}\n\n.search {\n margin-bottom: 20px;\n padding: 6px;\n border-bottom: 1px solid #eee;\n}\n\n.search .input-wrap {\n display: flex;\n align-items: center;\n}\n\n.search .results-panel {\n display: none;\n}\n\n.search .results-panel.show {\n display: block;\n}\n\n.search input {\n outline: none;\n border: none;\n width: 100%;\n padding: 0 7px;\n line-height: 36px;\n font-size: 14px;\n}\n\n.search input::-webkit-search-decoration,\n.search input::-webkit-search-cancel-button,\n.search input {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n.search .clear-button {\n width: 36px;\n text-align: right;\n display: none;\n}\n\n.search .clear-button.show {\n display: block;\n}\n\n.search .clear-button svg {\n transform: scale(.5);\n}\n\n.search h2 {\n font-size: 17px;\n margin: 10px 0;\n}\n\n.search a {\n text-decoration: none;\n color: inherit;\n}\n\n.search .matching-post {\n border-bottom: 1px solid #eee;\n}\n\n.search .matching-post:last-child {\n border-bottom: 0;\n}\n\n.search p {\n font-size: 14px;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n\n.search p.empty {\n text-align: center;\n}"; 192 | 193 | Docsify.dom.style(code); 194 | } 195 | 196 | function tpl(opts, defaultValue) { 197 | if ( defaultValue === void 0 ) defaultValue = ''; 198 | 199 | var html = 200 | "
      \n \n
      \n \n \n \n \n \n
      \n
      \n
      \n "; 201 | var el = Docsify.dom.create('div', html); 202 | var aside = Docsify.dom.find('aside'); 203 | 204 | Docsify.dom.toggleClass(el, 'search'); 205 | Docsify.dom.before(aside, el); 206 | } 207 | 208 | function doSearch(value) { 209 | var $search = Docsify.dom.find('div.search'); 210 | var $panel = Docsify.dom.find($search, '.results-panel'); 211 | var $clearBtn = Docsify.dom.find($search, '.clear-button'); 212 | 213 | if (!value) { 214 | $panel.classList.remove('show'); 215 | $clearBtn.classList.remove('show'); 216 | $panel.innerHTML = ''; 217 | return 218 | } 219 | var matchs = search(value); 220 | 221 | var html = ''; 222 | matchs.forEach(function (post) { 223 | html += ""; 224 | }); 225 | 226 | $panel.classList.add('show'); 227 | $clearBtn.classList.add('show'); 228 | $panel.innerHTML = html || ("

      " + NO_DATA_TEXT + "

      "); 229 | } 230 | 231 | function bindEvents() { 232 | var $search = Docsify.dom.find('div.search'); 233 | var $input = Docsify.dom.find($search, 'input'); 234 | var $inputWrap = Docsify.dom.find($search, '.input-wrap'); 235 | 236 | var timeId; 237 | // Prevent to Fold sidebar 238 | Docsify.dom.on( 239 | $search, 240 | 'click', 241 | function (e) { return e.target.tagName !== 'A' && e.stopPropagation(); } 242 | ); 243 | Docsify.dom.on($input, 'input', function (e) { 244 | clearTimeout(timeId); 245 | timeId = setTimeout(function (_) { return doSearch(e.target.value.trim()); }, 100); 246 | }); 247 | Docsify.dom.on($inputWrap, 'click', function (e) { 248 | // Click input outside 249 | if (e.target.tagName !== 'INPUT') { 250 | $input.value = ''; 251 | doSearch(); 252 | } 253 | }); 254 | } 255 | 256 | function updatePlaceholder(text, path) { 257 | var $input = Docsify.dom.getNode('.search input[type="search"]'); 258 | 259 | if (!$input) { 260 | return 261 | } 262 | if (typeof text === 'string') { 263 | $input.placeholder = text; 264 | } else { 265 | var match = Object.keys(text).filter(function (key) { return path.indexOf(key) > -1; })[0]; 266 | $input.placeholder = text[match]; 267 | } 268 | } 269 | 270 | function updateNoData(text, path) { 271 | if (typeof text === 'string') { 272 | NO_DATA_TEXT = text; 273 | } else { 274 | var match = Object.keys(text).filter(function (key) { return path.indexOf(key) > -1; })[0]; 275 | NO_DATA_TEXT = text[match]; 276 | } 277 | } 278 | 279 | function init(opts, vm) { 280 | var keywords = vm.router.parse().query.s; 281 | 282 | style(); 283 | tpl(opts, keywords); 284 | bindEvents(); 285 | keywords && setTimeout(function (_) { return doSearch(keywords); }, 500); 286 | } 287 | 288 | function update(opts, vm) { 289 | updatePlaceholder(opts.placeholder, vm.route.path); 290 | updateNoData(opts.noData, vm.route.path); 291 | } 292 | 293 | var CONFIG = { 294 | placeholder: 'Type to search', 295 | noData: 'No Results!', 296 | paths: 'auto', 297 | depth: 2, 298 | maxAge: 86400000 // 1 day 299 | }; 300 | 301 | var install = function (hook, vm) { 302 | var util = Docsify.util; 303 | var opts = vm.config.search || CONFIG; 304 | 305 | if (Array.isArray(opts)) { 306 | CONFIG.paths = opts; 307 | } else if (typeof opts === 'object') { 308 | CONFIG.paths = Array.isArray(opts.paths) ? opts.paths : 'auto'; 309 | CONFIG.maxAge = util.isPrimitive(opts.maxAge) ? opts.maxAge : CONFIG.maxAge; 310 | CONFIG.placeholder = opts.placeholder || CONFIG.placeholder; 311 | CONFIG.noData = opts.noData || CONFIG.noData; 312 | CONFIG.depth = opts.depth || CONFIG.depth; 313 | } 314 | 315 | var isAuto = CONFIG.paths === 'auto'; 316 | 317 | hook.mounted(function (_) { 318 | init(CONFIG, vm); 319 | !isAuto && init$1(CONFIG, vm); 320 | }); 321 | hook.doneEach(function (_) { 322 | update(CONFIG, vm); 323 | isAuto && init$1(CONFIG, vm); 324 | }); 325 | }; 326 | 327 | $docsify.plugins = [].concat(install, $docsify.plugins); 328 | 329 | }()); -------------------------------------------------------------------------------- /assets/theme-buble.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Inconsolata|Inconsolata-Bold"); 2 | * { 3 | -webkit-font-smoothing: antialiased; 4 | -webkit-overflow-scrolling: touch; 5 | -webkit-tap-highlight-color: rgba(0,0,0,0); 6 | -webkit-text-size-adjust: none; 7 | -webkit-touch-callout: none; 8 | -webkit-box-sizing: border-box; 9 | box-sizing: border-box; 10 | } 11 | body:not(.ready) { 12 | overflow: hidden; 13 | } 14 | body:not(.ready) [data-cloak], 15 | body:not(.ready) .app-nav, 16 | body:not(.ready) > nav { 17 | display: none; 18 | } 19 | div#app { 20 | font-size: 30px; 21 | font-weight: lighter; 22 | margin: 40vh auto; 23 | text-align: center; 24 | } 25 | div#app:empty::before { 26 | content: 'Loading...'; 27 | } 28 | .emoji { 29 | height: 1.2rem; 30 | vertical-align: middle; 31 | } 32 | .progress { 33 | background-color: var(--theme-color, #0074d9); 34 | height: 2px; 35 | left: 0px; 36 | position: fixed; 37 | right: 0px; 38 | top: 0px; 39 | -webkit-transition: width 0.2s, opacity 0.4s; 40 | transition: width 0.2s, opacity 0.4s; 41 | width: 0%; 42 | z-index: 999999; 43 | } 44 | .search a:hover { 45 | color: var(--theme-color, #0074d9); 46 | } 47 | .search .search-keyword { 48 | color: var(--theme-color, #0074d9); 49 | font-style: normal; 50 | font-weight: bold; 51 | } 52 | html, 53 | body { 54 | height: 100%; 55 | } 56 | body { 57 | -moz-osx-font-smoothing: grayscale; 58 | -webkit-font-smoothing: antialiased; 59 | color: #34495e; 60 | font-family: 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif; 61 | font-size: 15px; 62 | letter-spacing: 0; 63 | margin: 0; 64 | overflow-x: hidden; 65 | } 66 | img { 67 | max-width: 100%; 68 | } 69 | a[disabled] { 70 | cursor: not-allowed; 71 | opacity: 0.6; 72 | } 73 | kbd { 74 | border: solid 1px #ccc; 75 | border-radius: 3px; 76 | display: inline-block; 77 | font-size: 12px !important; 78 | line-height: 12px; 79 | margin-bottom: 3px; 80 | padding: 3px 5px; 81 | vertical-align: middle; 82 | } 83 | .task-list-item { 84 | list-style-type: none; 85 | } 86 | li input[type='checkbox'] { 87 | margin: 0 0.2em 0.25em -1.6em; 88 | vertical-align: middle; 89 | } 90 | .app-nav { 91 | margin: 25px 60px 0 0; 92 | position: absolute; 93 | right: 0; 94 | text-align: right; 95 | z-index: 10; 96 | /* navbar dropdown */ 97 | } 98 | .app-nav.no-badge { 99 | margin-right: 25px; 100 | } 101 | .app-nav p { 102 | margin: 0; 103 | } 104 | .app-nav > a { 105 | margin: 0 1rem; 106 | padding: 5px 0; 107 | } 108 | .app-nav ul, 109 | .app-nav li { 110 | display: inline-block; 111 | list-style: none; 112 | margin: 0; 113 | } 114 | .app-nav a { 115 | color: inherit; 116 | font-size: 16px; 117 | text-decoration: none; 118 | -webkit-transition: color 0.3s; 119 | transition: color 0.3s; 120 | } 121 | .app-nav a:hover { 122 | color: var(--theme-color, #0074d9); 123 | } 124 | .app-nav a.active { 125 | border-bottom: 2px solid var(--theme-color, #0074d9); 126 | color: var(--theme-color, #0074d9); 127 | } 128 | .app-nav li { 129 | display: inline-block; 130 | margin: 0 1rem; 131 | padding: 5px 0; 132 | position: relative; 133 | } 134 | .app-nav li ul { 135 | background-color: #fff; 136 | border: 1px solid #ddd; 137 | border-bottom-color: #ccc; 138 | border-radius: 4px; 139 | -webkit-box-sizing: border-box; 140 | box-sizing: border-box; 141 | display: none; 142 | max-height: calc(100vh - 61px); 143 | overflow-y: auto; 144 | padding: 10px 0; 145 | position: absolute; 146 | right: -15px; 147 | text-align: left; 148 | top: 100%; 149 | white-space: nowrap; 150 | } 151 | .app-nav li ul li { 152 | display: block; 153 | font-size: 14px; 154 | line-height: 1rem; 155 | margin: 0; 156 | margin: 8px 14px; 157 | white-space: nowrap; 158 | } 159 | .app-nav li ul a { 160 | display: block; 161 | font-size: inherit; 162 | margin: 0; 163 | padding: 0; 164 | } 165 | .app-nav li ul a.active { 166 | border-bottom: 0; 167 | } 168 | .app-nav li:hover ul { 169 | display: block; 170 | } 171 | .github-corner { 172 | border-bottom: 0; 173 | position: fixed; 174 | right: 0; 175 | text-decoration: none; 176 | top: 0; 177 | z-index: 1; 178 | } 179 | .github-corner:hover .octo-arm { 180 | -webkit-animation: octocat-wave 560ms ease-in-out; 181 | animation: octocat-wave 560ms ease-in-out; 182 | } 183 | .github-corner svg { 184 | color: #fff; 185 | fill: var(--theme-color, #0074d9); 186 | height: 80px; 187 | width: 80px; 188 | } 189 | main { 190 | display: block; 191 | position: relative; 192 | width: 100vw; 193 | height: 100%; 194 | z-index: 0; 195 | } 196 | main.hidden { 197 | display: none; 198 | } 199 | .anchor { 200 | display: inline-block; 201 | text-decoration: none; 202 | -webkit-transition: all 0.3s; 203 | transition: all 0.3s; 204 | } 205 | .anchor span { 206 | color: #34495e; 207 | } 208 | .anchor:hover { 209 | text-decoration: underline; 210 | } 211 | .sidebar { 212 | border-right: 1px solid rgba(0,0,0,0.07); 213 | overflow-y: auto; 214 | padding: 40px 0 0; 215 | position: absolute; 216 | top: 0; 217 | bottom: 0; 218 | left: 0; 219 | -webkit-transition: -webkit-transform 250ms ease-out; 220 | transition: -webkit-transform 250ms ease-out; 221 | transition: transform 250ms ease-out; 222 | transition: transform 250ms ease-out, -webkit-transform 250ms ease-out; 223 | width: 16rem; 224 | z-index: 20; 225 | } 226 | .sidebar > h1 { 227 | margin: 0 auto 1rem; 228 | font-size: 1.5rem; 229 | font-weight: 300; 230 | text-align: center; 231 | } 232 | .sidebar > h1 a { 233 | color: inherit; 234 | text-decoration: none; 235 | } 236 | .sidebar > h1 .app-nav { 237 | display: block; 238 | position: static; 239 | } 240 | .sidebar .sidebar-nav { 241 | line-height: 2em; 242 | padding-bottom: 40px; 243 | } 244 | .sidebar li.collapse .app-sub-sidebar { 245 | display: none; 246 | } 247 | .sidebar ul { 248 | margin: 0; 249 | padding: 0; 250 | } 251 | .sidebar li > p { 252 | font-weight: 700; 253 | margin: 0; 254 | } 255 | .sidebar ul, 256 | .sidebar ul li { 257 | list-style: none; 258 | } 259 | .sidebar ul li a { 260 | border-bottom: none; 261 | display: block; 262 | } 263 | .sidebar ul li ul { 264 | padding-left: 20px; 265 | } 266 | .sidebar::-webkit-scrollbar { 267 | width: 4px; 268 | } 269 | .sidebar::-webkit-scrollbar-thumb { 270 | background: transparent; 271 | border-radius: 4px; 272 | } 273 | .sidebar:hover::-webkit-scrollbar-thumb { 274 | background: rgba(136,136,136,0.4); 275 | } 276 | .sidebar:hover::-webkit-scrollbar-track { 277 | background: rgba(136,136,136,0.1); 278 | } 279 | .sidebar-toggle { 280 | background-color: transparent; 281 | background-color: rgba(255,255,255,0.8); 282 | border: 0; 283 | outline: none; 284 | padding: 10px; 285 | position: absolute; 286 | bottom: 0; 287 | left: 0; 288 | text-align: center; 289 | -webkit-transition: opacity 0.3s; 290 | transition: opacity 0.3s; 291 | width: 0rem; 292 | z-index: 30; 293 | } 294 | .sidebar-toggle .sidebar-toggle-button:hover { 295 | opacity: 0.4; 296 | } 297 | .sidebar-toggle span { 298 | background-color: var(--theme-color, #0074d9); 299 | display: block; 300 | margin-bottom: 4px; 301 | width: 16px; 302 | height: 2px; 303 | } 304 | body.sticky .sidebar, 305 | body.sticky .sidebar-toggle { 306 | position: fixed; 307 | } 308 | .content { 309 | padding-top: 60px; 310 | position: absolute; 311 | top: 0; 312 | right: 0; 313 | bottom: 0; 314 | left: 16rem; 315 | -webkit-transition: left 250ms ease; 316 | transition: left 250ms ease; 317 | } 318 | .markdown-section { 319 | margin: 0 auto; 320 | max-width: 800px; 321 | padding: 30px 15px 40px 15px; 322 | position: relative; 323 | } 324 | .markdown-section > * { 325 | -webkit-box-sizing: border-box; 326 | box-sizing: border-box; 327 | font-size: inherit; 328 | } 329 | .markdown-section > :first-child { 330 | margin-top: 0 !important; 331 | } 332 | .markdown-section hr { 333 | border: none; 334 | border-bottom: 1px solid #eee; 335 | margin: 2em 0; 336 | } 337 | .markdown-section iframe { 338 | border: 1px solid #eee; 339 | } 340 | .markdown-section table { 341 | border-collapse: collapse; 342 | border-spacing: 0; 343 | display: block; 344 | margin-bottom: 1rem; 345 | overflow: auto; 346 | width: 100%; 347 | } 348 | .markdown-section th { 349 | border: 1px solid #ddd; 350 | font-weight: bold; 351 | padding: 6px 13px; 352 | } 353 | .markdown-section td { 354 | border: 1px solid #ddd; 355 | padding: 6px 13px; 356 | } 357 | .markdown-section tr { 358 | border-top: 1px solid #ccc; 359 | } 360 | .markdown-section tr:nth-child(2n) { 361 | background-color: #f8f8f8; 362 | } 363 | .markdown-section p.tip { 364 | background-color: #f8f8f8; 365 | border-bottom-right-radius: 2px; 366 | border-left: 4px solid #f66; 367 | border-top-right-radius: 2px; 368 | margin: 2em 0; 369 | padding: 12px 24px 12px 30px; 370 | position: relative; 371 | } 372 | .markdown-section p.tip:before { 373 | background-color: #f66; 374 | border-radius: 100%; 375 | color: #fff; 376 | content: '!'; 377 | font-family: 'Dosis', 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif; 378 | font-size: 14px; 379 | font-weight: bold; 380 | left: -12px; 381 | line-height: 20px; 382 | position: absolute; 383 | height: 20px; 384 | width: 20px; 385 | text-align: center; 386 | top: 14px; 387 | } 388 | .markdown-section p.tip code { 389 | background-color: #efefef; 390 | } 391 | .markdown-section p.tip em { 392 | color: #34495e; 393 | } 394 | .markdown-section p.warn { 395 | background: rgba(0,116,217,0.1); 396 | border-radius: 2px; 397 | padding: 1rem; 398 | } 399 | body.close .sidebar { 400 | -webkit-transform: translateX(-16rem); 401 | transform: translateX(-16rem); 402 | } 403 | body.close .sidebar-toggle { 404 | width: auto; 405 | } 406 | body.close .content { 407 | left: 0; 408 | } 409 | @media print { 410 | .github-corner, 411 | .sidebar-toggle, 412 | .sidebar, 413 | .app-nav { 414 | display: none; 415 | } 416 | } 417 | @media screen and (max-width: 768px) { 418 | .github-corner, 419 | .sidebar-toggle, 420 | .sidebar { 421 | position: fixed; 422 | } 423 | .app-nav { 424 | margin-top: 16px; 425 | } 426 | .app-nav li ul { 427 | top: 30px; 428 | } 429 | main { 430 | height: auto; 431 | overflow-x: hidden; 432 | } 433 | .sidebar { 434 | left: -16rem; 435 | -webkit-transition: -webkit-transform 250ms ease-out; 436 | transition: -webkit-transform 250ms ease-out; 437 | transition: transform 250ms ease-out; 438 | transition: transform 250ms ease-out, -webkit-transform 250ms ease-out; 439 | } 440 | .content { 441 | left: 0; 442 | max-width: 100vw; 443 | position: static; 444 | padding-top: 20px; 445 | -webkit-transition: -webkit-transform 250ms ease; 446 | transition: -webkit-transform 250ms ease; 447 | transition: transform 250ms ease; 448 | transition: transform 250ms ease, -webkit-transform 250ms ease; 449 | } 450 | .app-nav, 451 | .github-corner { 452 | -webkit-transition: -webkit-transform 250ms ease-out; 453 | transition: -webkit-transform 250ms ease-out; 454 | transition: transform 250ms ease-out; 455 | transition: transform 250ms ease-out, -webkit-transform 250ms ease-out; 456 | } 457 | .sidebar-toggle { 458 | background-color: transparent; 459 | width: auto; 460 | padding: 30px 30px 10px 10px; 461 | } 462 | body.close .sidebar { 463 | -webkit-transform: translateX(16rem); 464 | transform: translateX(16rem); 465 | } 466 | body.close .sidebar-toggle { 467 | background-color: rgba(255,255,255,0.8); 468 | -webkit-transition: 1s background-color; 469 | transition: 1s background-color; 470 | width: 0rem; 471 | padding: 10px; 472 | } 473 | body.close .content { 474 | -webkit-transform: translateX(16rem); 475 | transform: translateX(16rem); 476 | } 477 | body.close .app-nav, 478 | body.close .github-corner { 479 | display: none; 480 | } 481 | .github-corner:hover .octo-arm { 482 | -webkit-animation: none; 483 | animation: none; 484 | } 485 | .github-corner .octo-arm { 486 | -webkit-animation: octocat-wave 560ms ease-in-out; 487 | animation: octocat-wave 560ms ease-in-out; 488 | } 489 | } 490 | @-webkit-keyframes octocat-wave { 491 | 0%, 100% { 492 | -webkit-transform: rotate(0); 493 | transform: rotate(0); 494 | } 495 | 20%, 60% { 496 | -webkit-transform: rotate(-25deg); 497 | transform: rotate(-25deg); 498 | } 499 | 40%, 80% { 500 | -webkit-transform: rotate(10deg); 501 | transform: rotate(10deg); 502 | } 503 | } 504 | @keyframes octocat-wave { 505 | 0%, 100% { 506 | -webkit-transform: rotate(0); 507 | transform: rotate(0); 508 | } 509 | 20%, 60% { 510 | -webkit-transform: rotate(-25deg); 511 | transform: rotate(-25deg); 512 | } 513 | 40%, 80% { 514 | -webkit-transform: rotate(10deg); 515 | transform: rotate(10deg); 516 | } 517 | } 518 | section.cover { 519 | -webkit-box-align: center; 520 | -ms-flex-align: center; 521 | align-items: center; 522 | background-position: center center; 523 | background-repeat: no-repeat; 524 | background-size: cover; 525 | height: 100vh; 526 | display: none; 527 | } 528 | section.cover.show { 529 | display: -webkit-box; 530 | display: -ms-flexbox; 531 | display: flex; 532 | } 533 | section.cover.has-mask .mask { 534 | background-color: #fff; 535 | opacity: 0.8; 536 | position: absolute; 537 | top: 0; 538 | height: 100%; 539 | width: 100%; 540 | } 541 | section.cover .cover-main { 542 | -webkit-box-flex: 1; 543 | -ms-flex: 1; 544 | flex: 1; 545 | margin: -20px 16px 0; 546 | text-align: center; 547 | z-index: 1; 548 | } 549 | section.cover a { 550 | color: inherit; 551 | text-decoration: none; 552 | } 553 | section.cover a:hover { 554 | text-decoration: none; 555 | } 556 | section.cover p { 557 | line-height: 1.5rem; 558 | margin: 1em 0; 559 | } 560 | section.cover h1 { 561 | color: inherit; 562 | font-size: 2.5rem; 563 | font-weight: 300; 564 | margin: 0.625rem 0 2.5rem; 565 | position: relative; 566 | text-align: center; 567 | } 568 | section.cover h1 a { 569 | display: block; 570 | } 571 | section.cover h1 small { 572 | bottom: -0.4375rem; 573 | font-size: 1rem; 574 | position: absolute; 575 | } 576 | section.cover blockquote { 577 | font-size: 1.5rem; 578 | text-align: center; 579 | } 580 | section.cover ul { 581 | line-height: 1.8; 582 | list-style-type: none; 583 | margin: 1em auto; 584 | max-width: 500px; 585 | padding: 0; 586 | } 587 | section.cover .cover-main > p:last-child a { 588 | border-color: var(--theme-color, #0074d9); 589 | border-radius: 2rem; 590 | border-style: solid; 591 | border-width: 1px; 592 | -webkit-box-sizing: border-box; 593 | box-sizing: border-box; 594 | color: var(--theme-color, #0074d9); 595 | display: inline-block; 596 | font-size: 1.05rem; 597 | letter-spacing: 0.1rem; 598 | margin: 0.5rem 1rem; 599 | padding: 0.75em 2rem; 600 | text-decoration: none; 601 | -webkit-transition: all 0.15s ease; 602 | transition: all 0.15s ease; 603 | } 604 | section.cover .cover-main > p:last-child a:last-child { 605 | background-color: var(--theme-color, #0074d9); 606 | color: #fff; 607 | } 608 | section.cover .cover-main > p:last-child a:last-child:hover { 609 | color: inherit; 610 | opacity: 0.8; 611 | } 612 | section.cover .cover-main > p:last-child a:hover { 613 | color: inherit; 614 | } 615 | section.cover blockquote > p > a { 616 | border-bottom: 2px solid var(--theme-color, #0074d9); 617 | -webkit-transition: color 0.3s; 618 | transition: color 0.3s; 619 | } 620 | section.cover blockquote > p > a:hover { 621 | color: var(--theme-color, #0074d9); 622 | } 623 | /* sidebar */ 624 | .sidebar { 625 | color: #364149; 626 | background-color: #fff; 627 | } 628 | .sidebar a { 629 | color: #666; 630 | text-decoration: none; 631 | } 632 | .sidebar li { 633 | list-style: none; 634 | margin: 0; 635 | padding: 0.2em 0 0.2em 1rem; 636 | } 637 | .sidebar ul li ul { 638 | padding: 0; 639 | } 640 | .sidebar li.active { 641 | background-color: #eee; 642 | } 643 | .sidebar li.active a { 644 | color: #333; 645 | } 646 | .markdown-section h1, 647 | .markdown-section h2, 648 | .markdown-section h3, 649 | .markdown-section h4, 650 | .markdown-section strong { 651 | color: #333; 652 | font-weight: 400; 653 | } 654 | .markdown-section a { 655 | color: var(--theme-color, #0074d9); 656 | font-weight: 400; 657 | } 658 | .markdown-section p, 659 | .markdown-section ul, 660 | .markdown-section ol { 661 | line-height: 1.6rem; 662 | margin: 0 0 1em 0; 663 | word-spacing: 0.05rem; 664 | } 665 | .markdown-section h1 { 666 | font-size: 2rem; 667 | font-weight: 500; 668 | margin: 0 0 1rem; 669 | } 670 | .markdown-section h2 { 671 | font-size: 1.8rem; 672 | font-weight: 400; 673 | margin: 0 0 1rem 0; 674 | padding: 1rem 0 0 0; 675 | } 676 | .markdown-section h3 { 677 | font-size: 1.5rem; 678 | margin: 52px 0 1.2rem; 679 | } 680 | .markdown-section h4 { 681 | font-size: 1.25rem; 682 | } 683 | .markdown-section h5 { 684 | font-size: 1rem; 685 | } 686 | .markdown-section h6 { 687 | color: #777; 688 | font-size: 1rem; 689 | } 690 | .markdown-section figure, 691 | .markdown-section p, 692 | .markdown-section ul, 693 | .markdown-section ol { 694 | margin: 1.2em 0; 695 | } 696 | .markdown-section ul, 697 | .markdown-section ol { 698 | padding-left: 1.5rem; 699 | } 700 | .markdown-section li { 701 | line-height: 1.5; 702 | margin: 0; 703 | } 704 | .markdown-section blockquote { 705 | border-left: 4px solid var(--theme-color, #0074d9); 706 | color: #858585; 707 | margin: 2em 0; 708 | padding-left: 20px; 709 | } 710 | .markdown-section blockquote p { 711 | font-weight: 600; 712 | margin-left: 0; 713 | } 714 | .markdown-section iframe { 715 | margin: 1em 0; 716 | } 717 | .markdown-section em { 718 | color: #7f8c8d; 719 | } 720 | .markdown-section code { 721 | background-color: #f9f9f9; 722 | border-radius: 3px; 723 | font-family: Inconsolata; 724 | padding: 0.2em 0.4rem; 725 | white-space: nowrap; 726 | } 727 | .markdown-section pre { 728 | background-color: #f9f9f9; 729 | border-left: 2px solid #eee; 730 | font-family: Inconsolata; 731 | font-size: 16px; 732 | margin: 0 0 1em 0; 733 | padding: 8px; 734 | padding: 0 10px 12px 0; 735 | overflow: auto; 736 | word-wrap: normal; 737 | } 738 | /* code highlight */ 739 | .token.cdata, 740 | .token.comment, 741 | .token.doctype, 742 | .token.prolog { 743 | color: #93a1a1 /* base1 */; 744 | } 745 | .token.punctuation { 746 | color: #586e75 /* base01 */; 747 | } 748 | .namespace { 749 | opacity: 0.7; 750 | } 751 | .token.property, 752 | .token.tag, 753 | .token.boolean, 754 | .token.number, 755 | .token.constant, 756 | .token.symbol, 757 | .token.deleted { 758 | color: #268bd2 /* blue */; 759 | } 760 | .token.selector, 761 | .token.attr-name, 762 | .token.string, 763 | .token.char, 764 | .token.builtin, 765 | .token.url, 766 | .token.inserted { 767 | color: #2aa198 /* cyan */; 768 | } 769 | .token.entity { 770 | color: #657b83 /* base00 */; 771 | background: #eee8d5 /* base2 */; 772 | } 773 | .token.atrule, 774 | .token.attr-value, 775 | .token.keyword { 776 | color: #a11 /* green */; 777 | } 778 | .token.function { 779 | color: #b58900 /* yellow */; 780 | } 781 | .token.regex, 782 | .token.important, 783 | .token.variable { 784 | color: #cb4b16 /* orange */; 785 | } 786 | .token.important, 787 | .token.bold { 788 | font-weight: bold; 789 | } 790 | .token.italic { 791 | font-style: italic; 792 | } 793 | .token.entity { 794 | cursor: help; 795 | } 796 | .markdown-section pre > code { 797 | background-color: #f8f8f8; 798 | border-radius: 2px; 799 | display: block; 800 | font-family: Inconsolata; 801 | line-height: 1.1rem; 802 | max-width: inherit; 803 | overflow: inherit; 804 | padding: 20px 0.8em 20px; 805 | position: relative; 806 | white-space: inherit; 807 | } 808 | .markdown-section code::after, 809 | .markdown-section code::before { 810 | letter-spacing: 0.05rem; 811 | } 812 | code .token { 813 | -webkit-font-smoothing: initial; 814 | -moz-osx-font-smoothing: initial; 815 | min-height: 1.5rem; 816 | } -------------------------------------------------------------------------------- /assets/theme-vue.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Roboto+Mono|Source+Sans+Pro:300,400,600");*{-webkit-font-smoothing:antialiased;-webkit-overflow-scrolling:touch;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:none;-webkit-touch-callout:none;box-sizing:border-box}body:not(.ready){overflow:hidden}body:not(.ready) .app-nav,body:not(.ready)>nav,body:not(.ready) [data-cloak]{display:none}div#app{font-size:30px;font-weight:lighter;margin:40vh auto;text-align:center}div#app:empty:before{content:"Loading..."}.emoji{height:1.2rem;vertical-align:middle}.progress{background-color:var(--theme-color,#42b983);height:2px;left:0;position:fixed;right:0;top:0;transition:width .2s,opacity .4s;width:0;z-index:5}.search .search-keyword,.search a:hover{color:var(--theme-color,#42b983)}.search .search-keyword{font-style:normal;font-weight:700}body,html{height:100%}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;color:#34495e;font-family:Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:15px;letter-spacing:0;margin:0;overflow-x:hidden}img{max-width:100%}a[disabled]{cursor:not-allowed;opacity:.6}kbd{border:1px solid #ccc;border-radius:3px;display:inline-block;font-size:12px!important;line-height:12px;margin-bottom:3px;padding:3px 5px;vertical-align:middle}.task-list-item{list-style-type:none}li input[type=checkbox]{margin:0 .2em .25em -1.6em;vertical-align:middle}.app-nav{margin:25px 60px 0 0;position:absolute;right:0;text-align:right;z-index:2}.app-nav.no-badge{margin-right:25px}.app-nav p{margin:0}.app-nav>a{margin:0 1rem;padding:5px 0}.app-nav li,.app-nav ul{display:inline-block;list-style:none;margin:0}.app-nav a{color:inherit;font-size:16px;text-decoration:none;transition:color .3s}.app-nav a.active,.app-nav a:hover{color:var(--theme-color,#42b983)}.app-nav a.active{border-bottom:2px solid var(--theme-color,#42b983)}.app-nav li{display:inline-block;margin:0 1rem;padding:5px 0;position:relative}.app-nav li ul{background-color:#fff;border:1px solid #ddd;border-bottom-color:#ccc;border-radius:4px;box-sizing:border-box;display:none;max-height:calc(100vh - 61px);overflow-y:auto;padding:10px 0;position:absolute;right:-15px;text-align:left;top:100%;white-space:nowrap}.app-nav li ul li{display:block;font-size:14px;line-height:1rem;margin:0;margin:8px 14px;white-space:nowrap}.app-nav li ul a{display:block;font-size:inherit;margin:0;padding:0}.app-nav li ul a.active{border-bottom:0}.app-nav li:hover ul{display:block}.github-corner{border-bottom:0;position:fixed;right:0;text-decoration:none;top:0;z-index:1}.github-corner:hover .octo-arm{animation:a .56s ease-in-out}.github-corner svg{color:#fff;fill:var(--theme-color,#42b983);height:80px;width:80px}main{display:block;position:relative;width:100vw;height:100%;z-index:0}main.hidden{display:none}.anchor{display:inline-block;text-decoration:none;transition:all .3s}.anchor span{color:#34495e}.anchor:hover{text-decoration:underline}.sidebar{border-right:1px solid rgba(0,0,0,.07);overflow-y:auto;padding:40px 0 0;position:absolute;top:0;bottom:0;left:0;transition:transform .25s ease-out;width:300px;z-index:3}.sidebar>h1{margin:0 auto 1rem;font-size:1.5rem;font-weight:300;text-align:center}.sidebar>h1 a{color:inherit;text-decoration:none}.sidebar>h1 .app-nav{display:block;position:static}.sidebar .sidebar-nav{line-height:2em;padding-bottom:40px}.sidebar li.collapse .app-sub-sidebar{display:none}.sidebar ul{margin:0;padding:0}.sidebar li>p{font-weight:700;margin:0}.sidebar ul,.sidebar ul li{list-style:none}.sidebar ul li a{border-bottom:none;display:block}.sidebar ul li ul{padding-left:20px}.sidebar::-webkit-scrollbar{width:4px}.sidebar::-webkit-scrollbar-thumb{background:transparent;border-radius:4px}.sidebar:hover::-webkit-scrollbar-thumb{background:hsla(0,0%,53%,.4)}.sidebar:hover::-webkit-scrollbar-track{background:hsla(0,0%,53%,.1)}.sidebar-toggle{background-color:transparent;background-color:hsla(0,0%,100%,.8);border:0;outline:none;padding:10px;position:absolute;bottom:0;left:0;text-align:center;transition:opacity .3s;width:284px;z-index:4}.sidebar-toggle .sidebar-toggle-button:hover{opacity:.4}.sidebar-toggle span{background-color:var(--theme-color,#42b983);display:block;margin-bottom:4px;width:16px;height:2px}body.sticky .sidebar,body.sticky .sidebar-toggle{position:fixed}.content{padding-top:60px;position:absolute;top:0;right:0;bottom:0;left:300px;transition:left .25s ease}.markdown-section{margin:0 auto;max-width:800px;padding:30px 15px 40px;position:relative}.markdown-section>*{box-sizing:border-box;font-size:inherit}.markdown-section>:first-child{margin-top:0!important}.markdown-section hr{border:none;border-bottom:1px solid #eee;margin:2em 0}.markdown-section iframe{border:1px solid #eee}.markdown-section table{border-collapse:collapse;border-spacing:0;display:block;margin-bottom:1rem;overflow:auto;width:100%}.markdown-section th{font-weight:700}.markdown-section td,.markdown-section th{border:1px solid #ddd;padding:6px 13px}.markdown-section tr{border-top:1px solid #ccc}.markdown-section p.tip,.markdown-section tr:nth-child(2n){background-color:#f8f8f8}.markdown-section p.tip{border-bottom-right-radius:2px;border-left:4px solid #f66;border-top-right-radius:2px;margin:2em 0;padding:12px 24px 12px 30px;position:relative}.markdown-section p.tip:before{background-color:#f66;border-radius:100%;color:#fff;content:"!";font-family:Dosis,Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:14px;font-weight:700;left:-12px;line-height:20px;position:absolute;height:20px;width:20px;text-align:center;top:14px}.markdown-section p.tip code{background-color:#efefef}.markdown-section p.tip em{color:#34495e}.markdown-section p.warn{background:rgba(66,185,131,.1);border-radius:2px;padding:1rem}body.close .sidebar{transform:translateX(-300px)}body.close .sidebar-toggle{width:auto}body.close .content{left:0}@media print{.app-nav,.github-corner,.sidebar,.sidebar-toggle{display:none}}@media screen and (max-width:768px){.github-corner,.sidebar,.sidebar-toggle{position:fixed}.app-nav{margin-top:16px}.app-nav li ul{top:30px}main{height:auto;overflow-x:hidden}.sidebar{left:-300px;transition:transform .25s ease-out}.content{left:0;max-width:100vw;position:static;padding-top:20px;transition:transform .25s ease}.app-nav,.github-corner{transition:transform .25s ease-out}.sidebar-toggle{background-color:transparent;width:auto;padding:30px 30px 10px 10px}body.close .sidebar{transform:translateX(300px)}body.close .sidebar-toggle{background-color:hsla(0,0%,100%,.8);transition:background-color 1s;width:284px;padding:10px}body.close .content{transform:translateX(300px)}body.close .app-nav,body.close .github-corner{display:none}.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:a .56s ease-in-out}}@keyframes a{0%,to{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}section.cover{-ms-flex-align:center;align-items:center;background-position:50%;background-repeat:no-repeat;background-size:cover;height:100vh;display:none}section.cover.show{display:-ms-flexbox;display:flex}section.cover.has-mask .mask{background-color:#fff;opacity:.8;position:absolute;top:0;height:100%;width:100%}section.cover .cover-main{-ms-flex:1;flex:1;margin:-20px 16px 0;text-align:center;z-index:1}section.cover a{color:inherit}section.cover a,section.cover a:hover{text-decoration:none}section.cover p{line-height:1.5rem;margin:1em 0}section.cover h1{color:inherit;font-size:2.5rem;font-weight:300;margin:.625rem 0 2.5rem;position:relative;text-align:center}section.cover h1 a{display:block}section.cover h1 small{bottom:-.4375rem;font-size:1rem;position:absolute}section.cover blockquote{font-size:1.5rem;text-align:center}section.cover ul{line-height:1.8;list-style-type:none;margin:1em auto;max-width:500px;padding:0}section.cover .cover-main>p:last-child a{border:1px solid var(--theme-color,#42b983);border-radius:2rem;box-sizing:border-box;color:var(--theme-color,#42b983);display:inline-block;font-size:1.05rem;letter-spacing:.1rem;margin:.5rem 1rem;padding:.75em 2rem;text-decoration:none;transition:all .15s ease}section.cover .cover-main>p:last-child a:last-child{background-color:var(--theme-color,#42b983);color:#fff}section.cover .cover-main>p:last-child a:last-child:hover{color:inherit;opacity:.8}section.cover .cover-main>p:last-child a:hover{color:inherit}section.cover blockquote>p>a{border-bottom:2px solid var(--theme-color,#42b983);transition:color .3s}section.cover blockquote>p>a:hover{color:var(--theme-color,#42b983)}.sidebar,body{background-color:#fff}.sidebar{color:#364149}.sidebar li{margin:6px 0 6px 15px}.sidebar ul li a{color:#505d6b;font-size:14px;font-weight:400;overflow:hidden;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.sidebar ul li a:hover{text-decoration:underline}.sidebar ul li ul{padding:0}.sidebar ul li.active>a{border-right:2px solid;color:var(--theme-color,#42b983);font-weight:600}.app-sub-sidebar li:before{content:"-";padding-right:4px;float:left}.markdown-section h1,.markdown-section h2,.markdown-section h3,.markdown-section h4,.markdown-section strong{color:#2c3e50;font-weight:600}.markdown-section a{color:var(--theme-color,#42b983);font-weight:600}.markdown-section h1{font-size:2rem;margin:0 0 1rem}.markdown-section h2{font-size:1.75rem;margin:45px 0 .8rem}.markdown-section h3{font-size:1.5rem;margin:40px 0 .6rem}.markdown-section h4{font-size:1.25rem}.markdown-section h5{font-size:1rem}.markdown-section h6{color:#777;font-size:1rem}.markdown-section figure,.markdown-section p{margin:1.2em 0}.markdown-section ol,.markdown-section p,.markdown-section ul{line-height:1.6rem;word-spacing:.05rem}.markdown-section ol,.markdown-section ul{padding-left:1.5rem}.markdown-section blockquote{border-left:4px solid var(--theme-color,#42b983);color:#858585;margin:2em 0;padding-left:20px}.markdown-section blockquote p{font-weight:600;margin-left:0}.markdown-section iframe{margin:1em 0}.markdown-section em{color:#7f8c8d}.markdown-section code{border-radius:2px;color:#e96900;font-size:.8rem;margin:0 2px;padding:3px 5px;white-space:pre-wrap}.markdown-section code,.markdown-section pre{background-color:#f8f8f8;font-family:Roboto Mono,Monaco,courier,monospace}.markdown-section pre{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;line-height:1.5rem;margin:1.2em 0;overflow:auto;padding:0 1.4rem;position:relative;word-wrap:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8e908c}.token.namespace{opacity:.7}.token.boolean,.token.number{color:#c76b29}.token.punctuation{color:#525252}.token.property{color:#c08b30}.token.tag{color:#2973b7}.token.string{color:var(--theme-color,#42b983)}.token.selector{color:#6679cc}.token.attr-name{color:#2973b7}.language-css .token.string,.style .token.string,.token.entity,.token.url{color:#22a2c9}.token.attr-value,.token.control,.token.directive,.token.unit{color:var(--theme-color,#42b983)}.token.keyword{color:#e96900}.token.atrule,.token.regex,.token.statement{color:#22a2c9}.token.placeholder,.token.variable{color:#3d8fd1}.token.deleted{text-decoration:line-through}.token.inserted{border-bottom:1px dotted #202746;text-decoration:none}.token.italic{font-style:italic}.token.bold,.token.important{font-weight:700}.token.important{color:#c94922}.token.entity{cursor:help}.markdown-section pre>code{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;background-color:#f8f8f8;border-radius:2px;color:#525252;display:block;font-family:Roboto Mono,Monaco,courier,monospace;font-size:.8rem;line-height:inherit;margin:0 2px;max-width:inherit;overflow:inherit;padding:2.2em 5px;white-space:inherit}.markdown-section code:after,.markdown-section code:before{letter-spacing:.05rem}code .token{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;min-height:1.5rem}pre:after{color:#ccc;content:attr(data-lang);font-size:.6rem;font-weight:600;height:15px;line-height:15px;padding:5px 10px 0;position:absolute;right:0;text-align:right;top:0} -------------------------------------------------------------------------------- /assets/zoom-image.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | /* 3 | * medium-zoom v0.4.0 4 | * Medium zoom on your images in vanilla JavaScript 5 | * Copyright 2018 Francois Chalifour 6 | * https://github.com/francoischalifour/medium-zoom 7 | * MIT License 8 | */var _extends=Object.assign||function(a){for(var b,c=1;cw.scrollOffset&&o(150);}},u=function(a){-1 具体实例可参照 `@/model-dashboard/components/` 文件下几个 chart 文件 77 | 78 | ## 其它 79 | 80 | 当然社区里的其它图表如 [d3](https://github.com/d3/d3) , [Chart.js](https://github.com/chartjs/Chart.js) , [chartist-js](https://github.com/gionkunz/chartist-js) 等封装方法都是大同小异差不多的,这里就不再展开了。 81 | -------------------------------------------------------------------------------- /create-model.md: -------------------------------------------------------------------------------- 1 | # 新增模块 2 | 3 | ## 使用 npm 快捷命令创建 4 | 5 | ```bash 6 | >> itheima moduleAdd example 7 | 8 | `example` 是新模块的名字 9 | 10 | 自动创建这些目录和文件 11 | │ ├── module-example | example模块主目录 12 | │ │ ├── assets | 资源 13 | │ │ ├── components | 组件 14 | │ │ ├── pages | 页面 15 | │   │ │ └── index.vue | 示例 16 | │ │ ├── router | 路由 17 | │   │ │ └── index.js | 示例 18 | │ │ └── store | 数据 19 | │   │ └── app.js | 示例 20 | ``` 21 | 22 | * 每个模块所有的素材、页面、组件、路由、数据,都是独立的,方便大型项目管理, 23 | * 在实际项目中会有很多子业务项目,它们之间的关系是平行的、低耦合、互不依赖。 24 | 25 | ## 注册模块 26 | 27 | * 编辑 `src/main.js` 28 | 29 | ```js 30 | ... 31 | /* 32 | * 注册 - 业务模块 33 | */ 34 | import dashboard from '@/module-dashboard/' // 面板 35 | import example from '@/module-example/' // 刚新添加的 example 36 | Vue.use(dashboard, store) 37 | Vue.use(example, store) 38 | ... 39 | ``` 40 | 41 | > 使用 `Vue.use` 方式注册 42 | > 43 | > 参数 `store` 表示注册状态管理 44 | -------------------------------------------------------------------------------- /deploy.md: -------------------------------------------------------------------------------- 1 | # 构建和发布 2 | 3 | ## 关闭 mock 数据 4 | 5 | 打开 `/src/main.js` 注释 `mock` 6 | 7 | ```js 8 | ... 9 | // import './mock0' // simulation data 10 | ... 11 | ``` 12 | 13 | ## 构建 14 | 15 | 当项目开发完毕,只需要运行一行命令就可以打包你的应用: 16 | 17 | ```bash 18 | # 打包正式环境 19 | npm run build 20 | ``` 21 | 22 | ## 发布 23 | 24 | 对于发布来讲,只需要将最终生成的静态文件,也就是通常情况下 `dist` 文件夹的静态文件发布到你的 cdn 或者静态服务器即可,需要注意的是其中的 `index.html` 通常会是你后台服务的入口页面,在确定了 js 和 css 的静态之后可能需要改变页面的引入路径。 25 | ?> 部署是可能会发现资源路径不对 ,只需修改 `config/index.js` 文件资源路径即可。 26 | 27 | ```js 28 | assetsPublicPath: './' //请根据自己路径来配置更改 29 | ``` 30 | 31 | ### 前端路由与服务端的结合 32 | 33 | vue-element-admin 中,前端路由使用的是 `vue-router`,所以你可以选择两种方式:`browserHistory` 和 `hashHistory`。 34 | 35 | 两者的区别简单来说是对路由方式的处理不一样,`hashHistory` 是以 `#` 后面的路径进行处理,通过 [HTML 5 History](https://developer.mozilla.org/en-US/docs/Web/API/History_API) 进行前端路由管理,而 `browserHistory` 则是类似我们通常的页面访问路径,并没有 `#`,但要通过服务端的配置,能够访问指定的 url 都定向到当前页面,从而能够进行前端的路由管理。 36 | 37 | 本项目默认使用的是 `hashHistory` ,所以如果你的 url 里有 `#`,想去掉的话,需要切换为 `browserHistory`。修改 `src/router/index.js` 中的 mode 即可 38 | 39 | ```js 40 | export default new Router({ 41 | // mode: 'history', //后端支持可开 42 | }) 43 | ``` 44 | 45 | 如果你使用的是静态站点,那么使用 `browserHistory` 可能会无法访问你的应用,因为假设你访问 `http://localhost:9527/dashboard`,那么其实你的静态服务器并没有能够映射的文件,而使用 `hashHistory` 则不会有这个问题,因为它的页面路径是以 `#` 开始的,所有访问都在前端完成,如:`http://localhost:9527/#/dashboard/`。 46 | 47 | 不过如果你有对应的后台服务器,那么我们推荐采用 `browserHistory`,只需要在服务端做一个映射,比如: 48 | 49 | Apache 50 | 51 | ```bash 52 | 53 | RewriteEngine On 54 | RewriteBase / 55 | RewriteRule ^index\.html$ - [L] 56 | RewriteCond %{REQUEST_FILENAME} !-f 57 | RewriteCond %{REQUEST_FILENAME} !-d 58 | RewriteRule . /index.html [L] 59 | 60 | ``` 61 | 62 | nginx 63 | 64 | ```bash 65 | location / { 66 | try_files $uri $uri/ /index.html; 67 | } 68 | ``` 69 | 70 | ?> 更多配置请查看 [vue-router 文档](https://router.vuejs.org/zh-cn/essentials/history-mode.html) 71 | 72 | ## apache 73 | 74 | 1. 需要修改`router/index.js`中`new Router` 配置,加一个`base: '/vue/'`, 它指定应用的基路径,该应用是服务于`localhost/vue`路径下,所以必须加`base`配置,否则应用会展示 404 页面 75 | 2. 需要修改`config/index.js`中 build 下的`assetsPublicPath: '/vue/'`,如果用相对路径,chunk 文件会报错找不到。 76 | 3. 修改`httpd.conf`文件,开启 rewrite_module 功能。 77 | 78 | * `LoadModule rewrite_module libexec/apache2/mod_rewrite.so`,去掉前面的#。 79 | * 然后找到`AllowOverride None`的那行,把它改成`AllowOverride All`,来使`.htaccess`文件生效。 80 | 81 | 4. 在 apache 的`www/vue` 目录下新建`.htaccess`文件, 需要修改`RewriteRule` 为`/vue/index.html`, 否则刷新页面服务端会直接报 404 错误。 82 | 83 | .htaccess 文件内容 84 | 85 | ``` 86 | 87 | RewriteEngine On 88 | RewriteBase / 89 | RewriteRule ^index\.html$ - [L] 90 | RewriteCond %{REQUEST_FILENAME} !-f 91 | RewriteCond %{REQUEST_FILENAME} !-d 92 | RewriteRule . /vue/index.html [L] 93 | 94 | ``` 95 | 96 | ## nginx api 请求转发 97 | 98 | 编辑 `/usr/local/nginx/conf/nginx.conf` 99 | 100 | * 简单配置 101 | 102 | ```bash 103 | server { 104 | listen 80; 105 | server_name localhost; # 这里指定域名 106 | root /home/www/vue-web-site; 107 | # 匹配 api 路由的反向代理到API服务 108 | location ^~/api/ { 109 | rewrite ^/(.*)$ /$1 break; 110 | proxy_pass http://127.0.0.1:8080; 111 | } 112 | } 113 | ``` 114 | 115 | * 复杂情况例子 116 | 117 | ```bash 118 | upstream server-api{ 119 | # api 代理服务地址 120 | server 127.0.0.1:8080; 121 | } 122 | upstream server-resource{ 123 | # 静态资源 代理服务地址 124 | server 127.0.0.1:8090; 125 | } 126 | server { 127 | listen 80; 128 | server_name localhost; # 这里指定域名 129 | root /home/www/vue-web-site; 130 | # 匹配 api 路由的反向代理到API服务 131 | location ^~/api/ { 132 | rewrite ^/(.*)$ /$1 break; 133 | proxy_pass http://server-api; 134 | } 135 | # 假设这里验证码也在API服务中 136 | location ^~/captcha { 137 | rewrite ^/(.*)$ /$1 break; 138 | proxy_pass http://server-api; 139 | } 140 | # 假设你的图片资源全部在另外一个服务上面 141 | location ^~/img/ { 142 | rewrite ^/(.*)$ /$1 break; 143 | proxy_pass http://server-resource; 144 | } 145 | } 146 | ``` 147 | -------------------------------------------------------------------------------- /donate.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itheima2017/vue-element-admin-doc-itheima/2b1383efc61279f6d627c7baf581c0ecebbfc3f6/donate.md -------------------------------------------------------------------------------- /error.md: -------------------------------------------------------------------------------- 1 | # 错误处理 2 | 3 | ## 页面 4 | 5 | **404** 6 | 7 | 页面级的错误处理由 `vue-router` 统一处理,所有匹配不到正确路由的页面都会进 `404`页面。 8 | 9 | ```js 10 | { path: '*', redirect: '/404' } 11 | ``` 12 | 13 | **401** 14 | 15 | 在`@/src/router/index.js`做了权限控制,所有没有权限进入该路由的用户都会被重定向到 `401`页面。 16 | 17 | ## 请求 18 | 19 | 项目里所有的请求都会走`@/utils/request.js`里面创建的的 axios 实例,它统一做了错误处理,`@/src/utils/request.js`。 20 | 21 | 你可以在`service.interceptors.response` respone 拦截器之中根据自己的实际业务统一针对不同的状态码或者根据自定义 code 来做错误处理。如: 22 | 23 | ```js 24 | 25 | ``` 26 | 27 | 因为所以请求返回的是`promise`,所以你也可以自行`catch` 错误,做相对应的提示。 28 | 29 | ```js 30 | getInfo() 31 | .then(res => {}) 32 | .catch(err => { 33 | xxxx 34 | }) 35 | ``` 36 | 37 | ## 代码 38 | 39 | 本项目也做了代码层面的错误处理,如果你开启了`eslint`在编写代码的时候就回提示错误。 40 | 41 | 当然还有很多不能被`eslint`检查出来的错误,vue 也提供了全局错误处理钩子[errorHandler](https://vuejs.org/v2/api/#errorHandler),所以本项目也做了相对应的错误收集。 42 | -------------------------------------------------------------------------------- /eslint.md: -------------------------------------------------------------------------------- 1 | # ESLint 2 | 3 | 不管是多人合作还是个人项目,代码规范是很重要的。这样做不仅可以很大程度地避免基本语法错误,也保证了代码的可读性。 4 | 5 | ## 配置项 6 | 7 | 所有的配置文件都在 `.eslintrc.js` 中。本项目基本规范是依托于 vue 官方的 eslint 规则 [eslint-config-vue](https://github.com/vuejs/eslint-config-vue) 做了少许的修改。大家可以按照自己的需求进行定制化配置。 8 | 9 | 比如:我个人或者项目组习惯于使用两个空格,但你可能觉得四个空格更顺眼,你可以做如下修改。进入项目 `.eslintrc.js` 中,找到 `indent`,然后修改为 `4` 即可。 还有各种各样的配置信息,详情见 [ESLint 文档](https://eslint.org/docs/rules/)。 10 | 11 | ## 取消 ESLint 校验 12 | 13 | 如果你实在是不想使用 ESLint 校验,只要找到 `/build/webpack.base.conf.js` 文件。将 `useEslint: true` 设置为 `useEslint: false` 即可 14 | 15 | ## vscode 配置 ESLint 16 | 17 | 这所谓工欲善其事,必先利其器,个人推荐 eslint+vscode 来写 vue,绝对有种飞一般的感觉。 18 | 19 | 每次保存,vscode 就能标红不符合 eslint 规则的地方,同时还会做一些简单的自我修正。安装步骤如下: 20 | 21 | 首先安装 eslint 插件 22 | 23 | 安装并配置完成 ESLint 后,我们继续回到 VSCode 进行扩展设置,依次点击 文件 > 首选项 > 设置 打开 VSCode 配置文件,添加如下配置 24 | 25 | ```json 26 | "files.autoSave":"off", 27 | "eslint.validate": [ 28 | "javascript", 29 | "javascriptreact", 30 | "html", 31 | { "language": "vue", "autoFix": true } 32 | ], 33 | "eslint.options": { 34 | "plugins": ["html"] 35 | } 36 | ``` 37 | 38 | 这样每次保存的时候就可以根据根目录下.eslintrc.js 你配置的 eslint 规则来检查和做一些简单的 fix。每个人和团队都有自己的代码规范,统一就好了,去打造一份属于自己的 eslint 规则上传到 npm 吧,如饿了么团队的 [config](https://www.npmjs.com/package/eslint-config-elemefe),vue 的 [config](https://github.com/vuejs/eslint-config-vue)。 39 | 40 | [vscode 插件和配置推荐](https://github.com/varHarrie/Dawn-Blossoms/issues/10) 41 | 42 | ## 自动修复 43 | 44 | ```bash 45 | npm run lint -- --fix 46 | ``` 47 | -------------------------------------------------------------------------------- /faq.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itheima2017/vue-element-admin-doc-itheima/2b1383efc61279f6d627c7baf581c0ecebbfc3f6/faq.md -------------------------------------------------------------------------------- /getting-started.md: -------------------------------------------------------------------------------- 1 | # 下载&运行 2 | 3 | ## 环境要求 4 | 5 | * node >= 6.0.0 6 | * npm >= 3.0.0 7 | * java = 1.8 8 | * maven >= 3 9 | * mysql >= 5.3 10 | 11 | ## 方式一:脚手架快速配置 12 | 13 | * 1 安装脚手架 14 | 15 | ```bash 16 | npm install -g itheima-cli 17 | ``` 18 | 19 | > 需要 `node` 环境,请自行安装 `nodejs.org` 20 | 21 | 更多脚手架信息请移步 [itheima-cli](https://www.npmjs.com/package/itheima-cli) 22 | 23 | * 2 执行安装 24 | 25 | ```bash 26 | itheima init itheimaAdmin-template my-admin 27 | ``` 28 | 29 | 按提示 `enter` `enter` 就完成了,就这么简单。 30 | 31 | !> 环境必须配置好啊! 如:`maven` ,否者 `mvn` 命令执行不了。 32 | 33 | **安装录像:** 34 | 35 | ![安装录像](./assets/itheima-cli.gif) 36 | 37 | ## 方式二:下载源码,手动配置 38 | 39 | ?> 本项目是前后端分离,需要先启动后端服务,然后再运行前端项目。 40 | 41 | ### 1. java 后端 42 | 43 | #### 1.1 克隆项目 git 44 | 45 | ```bash 46 | git clone https://github.com/itheima2017/vue-element-admin-api-java-itheima.git 47 | ``` 48 | 49 | #### 1.2 新建数据库 50 | 51 | 登录mysql,创建名为 your-database-name 的数据库 52 | 53 | #### 1.3 修改默认配置 54 | 55 | ```bash 56 | #修改默认配置 57 | vim ./your-project/src/main/resources/application.yml 58 | 59 | #修改数据库配置 60 | spring: 61 | datasource: 62 | url: jdbc:mysql://localhost:3306/your-database-name?useUnicode=true&characterEncoding=utf8 63 | username: root #数据库账号 64 | password: root #数据库密码 65 | 66 | #修改端口 67 | server: 68 | port: 7999 69 | ``` 70 | 71 | #### 1.4 初始化数据库 72 | 73 | ```bash 74 | #进入数据库初始化文件目录 75 | cd ./your-project/db 76 | 77 | #运行初始化文件 78 | mysql -uroot -proot < init.sql 79 | ``` 80 | 81 | > mysql -u账号 -p密码 -D数据库名称 < sql文件路径 82 | 83 | #### 1.5 打包及运行 84 | 85 | ```bash 86 | #进入项目目录 87 | cd your-project 88 | 89 | #打包 90 | mvn clean package 91 | 92 | ``` 93 | 94 | #### 1.6 运行服务 95 | 96 | ```bash 97 | nohup ./your-project/target/vue-element-admin-api-1.0.0.jar 98 | ``` 99 | 100 | ### 2. VUE 前端 101 | 102 | #### 2.1 克隆项目 git 103 | 104 | ```bash 105 | # 克隆项目 106 | git clone https://github.com/itheima2017/vue-element-admin-itheima.git 107 | 108 | # 安装依赖 109 | npm install 110 | # 或者 用淘宝镜像加速 111 | npm install --registry=https://registry.npm.taobao.org 112 | ``` 113 | 114 | #### 2.2 修改 `API` 地址 115 | 116 | 编辑文件 `config/index.js` 117 | 118 | ```js 119 | ... 120 | proxyTable: { 121 | '/api': { 122 | target: 'http://localhost:7999', 123 | changeOrigin: true, 124 | pathRewrite: { 125 | '^/api': '' 126 | } 127 | } 128 | }, 129 | ... 130 | ``` 131 | 132 | > `target` 是请求的API地址 133 | 134 | #### 2.3 启动前端 135 | 136 | ```bash 137 | npm run dev 138 | ``` 139 | 140 | ![服务启动](http://oflimcy5e.bkt.clouddn.com/ducafecat_2018-05-23-10-24-59.png) 141 | 142 | 打开浏览器访问 http://localhost:8080 143 | 144 | ![后台面板](http://oflimcy5e.bkt.clouddn.com/ducafecat_2018-05-22-17-51-42.png) 145 | -------------------------------------------------------------------------------- /i18n.md: -------------------------------------------------------------------------------- 1 | # 国际化 2 | 3 | 本项目集成了国际化 i18n。 4 | 5 | ## vue-i18n 6 | 7 | 本项目国际化方案主要是依赖 [vue-i18n](https://github.com/kazupon/vue-i18n)而实现。 8 | 9 | ### 全局 lang 10 | 11 | ?> 代码地址: [@/lang](src/lang) 12 | 目前配置了英文和中文两种语言。 13 | 14 | 同时在 `@/lang/index.js` 中引入了 `element-ui`的语言包 15 | 16 | ### 异步 lang 17 | 18 | 有一些某些特定页面才需要的 lang,比如 `@/views/i18n` 页面 19 | 20 | ```js 21 | import local from './local' 22 | 23 | this.$i18n.mergeLocaleMessage('en', local.en) 24 | this.$i18n.mergeLocaleMessage('zh', local.zh) 25 | ``` 26 | 27 | ## 移除国际化 28 | 29 | 在 `src/main.js` 中移除 `import i18n from './lang'` 并且删除 `src/lang` 文件夹 30 | 31 | 并在 `src/layout/components/Levelbar`、`src/layout/components/SidebarItem`、`src/layout/components/TabsView` 等文件夹中 移除 `this.$t('route.xxxx')` 使用国际化的方式。 32 | -------------------------------------------------------------------------------- /icon.md: -------------------------------------------------------------------------------- 1 | # 图标 2 | 3 | 在 [iconfont.cn](http://iconfont.cn/) 上选择并生成自己的业务图标库,再进行使用。 4 | 5 | ## 生成图标库代码 6 | 7 | 首先,搜索并找到你需要的图标,将它采集到你的购物车里,在购物车里,你可以将选中的图标添加到项目中(没有的话,新建一个),后续生成的资源/代码都是以项目为维度的。 8 | 9 | 下载完成之后将下载好的 .svg 文件放入 `@/icons/svg` 文件夹下之后就会自动导入。 10 | 11 | * 使用方式 12 | 13 | ```js 14 | //icon-class 为 icon 的名字 15 | ``` 16 | 17 | ## 改变颜色 18 | 19 | `svg-icon` 默认会读取其父级的 color `fill: currentColor;` 20 | 21 | 你可以改变父级的`color`或者直接改变`fill`的颜色即可。 22 | -------------------------------------------------------------------------------- /img/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itheima2017/vue-element-admin-doc-itheima/2b1383efc61279f6d627c7baf581c0ecebbfc3f6/img/banner.png -------------------------------------------------------------------------------- /img/different-router.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itheima2017/vue-element-admin-doc-itheima/2b1383efc61279f6d627c7baf581c0ecebbfc3f6/img/different-router.jpeg -------------------------------------------------------------------------------- /img/heima-admin-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itheima2017/vue-element-admin-doc-itheima/2b1383efc61279f6d627c7baf581c0ecebbfc3f6/img/heima-admin-dashboard.png -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itheima2017/vue-element-admin-doc-itheima/2b1383efc61279f6d627c7baf581c0ecebbfc3f6/img/logo.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 黑马Admin - 帮助手册 6 | 7 | 8 | 9 | 10 | 11 | 12 | 30 | 31 | 32 |
      33 | 34 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /java-art.md: -------------------------------------------------------------------------------- 1 | # 代码架构 2 | 3 | 黑马Admin后端基于 SpringBoot2,使用Spring Security作为权限控制主体,并引入JWT实现基于token的无状态Http请求,使得后端可以灵活部署,无需考虑传统的session同步问题。ORM技术采用SpringData JPA,大大的简化了数据访问层的代码,使代码更加简单明了,其面向对象的设计,又较为贴合Java开发人员的编码习惯。在这个体系中,我们还引入了Webflux框架,它与SprinBoot2同时推出,在编码的方式上面,基本上可以无缝的结合原有的基于SpringBoot的开发的套路,因此移植成本较低。经测试,使用该框架将可以大大的增加后端服务的程序的吞吐量。 4 | 5 | ``` 6 | #当前架构 7 | ----------------------------------------------------- 8 | |接口服务 | 9 | | -------- ---------- --------- | 10 | | | 接口文档 | | 安全控制 | | 服务交互 | | 11 | | -------- ---------- --------- | 12 | ----------------------------------------------------- 13 | |业务系统 | 权限服务 | 14 | | ------- --------- ---------- | Security | 15 | | |用户管理 | |权限组管理| | 权限管理 | | JWT | 16 | | ------- --------- ---------- | | 17 | ----------------------------------------------------- 18 | | 数据管理 | 19 | | ------- ----- | 20 | | | Mysql | | 文件 | | 21 | | ------- ----- | 22 | ----------------------------------------------------- 23 | ``` 24 | 25 | ##### 模块介绍 26 | 27 | - 安全控制:基于Spring Security框架实现的后端安全模块。 28 | - 用户管理:提供用户信息维护相关的一系列curd操作。 29 | - 权限组管理:用于定义和管理权限组,并且通过它来建立用户与权限的关系。 30 | - 权限管理:提供权限维护相关的一系列curd操作。 31 | - 其他:待扩展 -------------------------------------------------------------------------------- /java-err.md: -------------------------------------------------------------------------------- 1 | # 错误处理 2 | 3 | 当服务器发生错误时,SpringBoot都会返回一个状态码以及一个错误页面,但是这种方式,只对浏览器端有效,而不是浏览器发送的请求不会生效。那么我们就需要自定义异常处理来解决这个问题。 4 | 5 | 首先,我们编写一个继承于Exception的CommonException异常类 6 | 7 | ```java 8 | package itcast.research.exception; 9 | 10 | public class CommonException extends Exception { 11 | public CommonException() { 12 | } 13 | 14 | public CommonException(String message) { 15 | super(message); 16 | } 17 | 18 | public CommonException(String message, Throwable throwable) { 19 | super(message, throwable); 20 | } 21 | 22 | public CommonException(Throwable throwable) { 23 | super(throwable); 24 | } 25 | } 26 | ``` 27 | 28 | 接着,我们编写一个handler类处理controller层抛出的异常: 29 | 30 | ```java 31 | package itcast.research.exception; 32 | 33 | import com.fasterxml.jackson.core.JsonProcessingException; 34 | import com.fasterxml.jackson.databind.ObjectMapper; 35 | import org.slf4j.Logger; 36 | import org.slf4j.LoggerFactory; 37 | import org.springframework.http.HttpStatus; 38 | import org.springframework.security.access.AccessDeniedException; 39 | import org.springframework.web.bind.annotation.ExceptionHandler; 40 | import org.springframework.web.bind.annotation.RestControllerAdvice; 41 | 42 | import javax.servlet.http.HttpServletResponse; 43 | 44 | @RestControllerAdvice 45 | public class ExceptionHandlers { 46 | private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandlers.class); 47 | 48 | @ExceptionHandler(Exception.class) 49 | public String serverExceptionHandler(HttpServletResponse httpServletResponse, Exception ex) { 50 | LOGGER.error(ex.getMessage(), ex); 51 | ObjectMapper om = new ObjectMapper(); 52 | httpServletResponse.setContentType("application/json;charset=utf-8"); 53 | int errCode = HttpStatus.INTERNAL_SERVER_ERROR.value(); 54 | if (ex.getClass() == AccessDeniedException.class) { 55 | errCode = HttpStatus.FORBIDDEN.value(); 56 | } 57 | httpServletResponse.setStatus(errCode); 58 | try { 59 | return om.writeValueAsString(new ReturnValue(errCode, ex.getMessage())); 60 | } catch (JsonProcessingException e) { 61 | return "{\"err_code\":500,\"message\":\"信息处理错误!\"}"; 62 | } 63 | } 64 | } 65 | 66 | ``` 67 | 68 | 这个类加上@RestControllerAdvice注解将会处理controller层抛出的对应的异常,这里我们处理controller抛出的CommonException自定义异常,并且将错误信息以json串的格式返回给用户。 69 | 70 | ```java 71 | // 使用代码片段 72 | @RequestMapping(name = "check添加用户", value = "/base/users", method = RequestMethod.POST, produces = "application/json;charset=utf-8") 73 | public Mono> save(@RequestBody String strBody) throws Exception { 74 | User user = formatInUser(strBody); 75 | if (VEAStringUtil.isBlank(user.getPassword()) || VEAStringUtil.isBlank(user.getUsername()) || VEAStringUtil.isBlank(user.getEmail())) { 76 | throw new CommonException("用户名或密码或邮箱不能为空!"); 77 | } 78 | userService.save(user); 79 | return Mono.just(formatOutUser(user)); 80 | } 81 | ``` 82 | 83 | SpringBoot默认的错误处理机制以及自定义异常来处理错误请求,这更有利于我们的开发,带给用户更佳的使用效果。 -------------------------------------------------------------------------------- /java-flux.md: -------------------------------------------------------------------------------- 1 | # Webflux响应式编程 2 | 3 | 响应式编程是一种基于数据流和变化传递的声明式的编程范式。用一个形象的例子来形容响应式编程,你会发现Excel简直就是一个响应式的典范。当你在某个单元格中使用了公式,其单元格中的值,假设是a1,a2,a3的汇总,那么这时候,只要a1,a2,a3中任意项的值被修改了,就会直接体现到这个汇总的单元格中。这个过程充分体现了变化传递,每一次的修改可以视为数据流,而设定的好的公式则是它的声明,这恰恰贴合了关于响应式编程的描述。 4 | 5 | Spring Webflux是随着Spring5推出的响应式Web框架。它以Reactor 库为基础,开发人员可以使用 WebFlux 创建高性能的 Web 应用和客户端。这个模块当中,包含了对响应式Http、服务器推送事件和 WebSocket 的客户端和服务器端的支持。对于后端开发来说,比较重要的就是服务器端的开发。在服务器端,Webflux在同样的响应式底层架构之上,提供了两种不同的代码编写方式,一种是在Spring MVC中使用注解的方式,另一种是基于lambda表达式的函数式编程模型。Webflux程序运行时,是需要容器支持的,它可以运行在支持非阻塞IO API的Servlet容器上,或者是其他异步运行时环境,如Netty等。 6 | 7 | 这里我们简单介绍一下如何运用注解的方式进行开发。 8 | 9 | ```java 10 | @RestController 11 | public class HelloController { 12 | @RequestMapping(value = "/hello") 13 | public Mono hello(){ 14 | return Mono.just("hello itheima"); 15 | } 16 | @RequestMapping(value = "/hellos") 17 | public Flux hellos(){ 18 | List hellos=new ArrayList<>(); 19 | hellos.add("hello itheima1"); 20 | hellos.add("hello itheima2"); 21 | return Flux.fromIterable(hellos); 22 | } 23 | } 24 | ``` 25 | 26 | 从上面的代码中,我们不难看出使用Webflux与Spring MVC的不同在于,在于使用了与响应式编程相关的Flux和Mono等,而不是简单的对象。在简单的示例程序中,两者之间并没有太大的区别。而对于复杂的应用,响应式编程和抗压能力将会给整个应用带来性能的提升。而Webflux模块提供的另外一种开发模式以及服务器推送实践和WebSocket并未在项目中使用,因此在这里暂时不进行讨论,如对此感兴趣,可以查阅相关文档[《Webflux框架》](https://docs.spring.io/spring-framework/docs/5.0.0.BUILD-SNAPSHOT/spring-framework-reference/html/web-reactive.html)。 27 | 28 | 通过少量的代码修改,我们即可运用Webflux来提升我们应用的性能。由于Spring框架的流行,WebFlux 会成为开发 Web 应用的重要趋势之一,为我们开发高性能 Web 应用带来了新的机会和挑战。 -------------------------------------------------------------------------------- /java-jpa.md: -------------------------------------------------------------------------------- 1 | # JPA 配置 2 | 3 | 在黑马Admin中,我们使用了SpringDataJPA来完成数据访问层的实现。而SpringBoot2中的JPA的配置与SpringBoot1有所区别。 4 | 5 | ```yaml 6 | #SpringBoot1配置方式 7 | spring : 8 | datasource : 9 | driverClassName : com.mysql.jdbc.Driver 10 | url : jdbc:mysql://ip:port/database?useUnicode=true&characterEncoding=utf8 11 | username : root 12 | password : 123456 13 | jpa : 14 | database : MySQL 15 | show-sql : true 16 | generate-ddl : true 17 | hibernate : 18 | ddl-auto : update 19 | naming : 20 | physical-strategy : org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 21 | open-in-view : true 22 | 23 | #SpringBoot2配置方式 24 | spring: 25 | datasource: 26 | driver-class-name: com.mysql.jdbc.Driver 27 | url: jdbc:mysql://ip:port/database?useUnicode=true&characterEncoding=utf8 28 | username: root 29 | password: 123456 30 | jpa: 31 | database: MySQL 32 | show-sql: true 33 | generate-ddl: true 34 | hibernate: 35 | ddl-auto: update 36 | naming: 37 | physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 38 | open-in-view: true 39 | database-platform: org.hibernate.dialect.MySQL5InnoDBDialect 40 | ``` 41 | 42 | 从上述代码中,可以看到两个版本中,关于JPA的配置基本一致,少数的一些地方有所区别。因此,在使用新版本时,遇到不支持的配置项或api,尽量到官网阅读新版本的文档,以便找到替换的方法或者不支持的原因,才能对项目进行正确的调整。 -------------------------------------------------------------------------------- /java-jwt.md: -------------------------------------------------------------------------------- 1 | # JWT 令牌 2 | 3 | JWT是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准([(RFC 7519](https://tools.ietf.org/html/rfc7519)).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。 4 | 5 | ##### JWT的构成 6 | 7 | - Header 8 | 9 | 通常由两部分组成:token的类型,即JWT和正在使用的散列算法,如HMAC SHA256或RSA。 10 | 11 | ```json 12 | { 13 | "alg": "HS256", 14 | "typ": "JWT" 15 | } 16 | ``` 17 | 18 | 然后,这个JSON被Base64Url编码,形成JWT的第一部分。 19 | 20 | - Payload 21 | 22 | 令牌的第二部分是包含声明的有效负载。 声明是关于实体(通常是用户)和其他元数据的声明。 有三种类型的声明:注册,公共声明和私有声明。 23 | 24 | - 注册 25 | 26 | 这些是一组预先定义的声明,它们不是强制性的,但推荐使用,以提供一组有用的,可互操作的声明。 其中一些是:iss(发行者),exp(到期时间),sub(主题),aud(受众)等。 27 | 28 | - 公共声明 29 | 30 | 这些可以由使用JWT的人员随意定义。 但为避免冲突,应在IANA JSON Web令牌注册表中定义它们,或将其定义为包含防冲突命名空间的URI。该部分信息可以在客户端解密,因此不建议添加敏感信息。 31 | 32 | - 私有声明 33 | 34 | 由提供者和消费者共同定义,因为这部分采用的是对称加密,也就意味着这部分信息是明文信息,因此不建议存放敏感信息。 35 | 36 | ```json 37 | //Payload示例 38 | { 39 | "sub": "1234567890", 40 | "name": "John Doe", 41 | "admin": true 42 | } 43 | ``` 44 | 45 | - Signature 46 | 47 | 要创建签名部分,必须采用编码头,编码有效载荷,秘钥,header指定的算法并签名。 48 | 49 | 例如,如果你想使用HMAC SHA256算法,签名将按照以下方式创建: 50 | 51 | ``` 52 | HMACSHA256( 53 | base64UrlEncode(header) + "." + 54 | base64UrlEncode(payload), 55 | secret) 56 | ``` 57 | 58 | 将这三部分,连接成一个完整的字符串,构成一个最终的JWT: 59 | 60 | ``` 61 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ 62 | ``` 63 | 64 | 秘钥是保存在服务器的,因此JWT的签发也是在服务器上完成的。在任何时候,都不要将秘钥泄露出去,否则你的验证机制将没有任何意义。 65 | 66 | ##### 如何应用 67 | 68 | ```java 69 | //token签发 70 | String username = String.valueOf(authentication.getPrincipal()); 71 | String token = Jwts.builder() 72 | .setSubject(username) 73 | .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)) 74 | .signWith(SignatureAlgorithm.HS256, JWTConstants.SECRET) 75 | .compact(); 76 | 77 | //token验证 78 | String token = request.getHeader(JWTConstants.AUTHORIZATION_HEADER); 79 | if (token != null) { 80 | String user = Jwts.parser() 81 | .setSigningKey(JWTConstants.SECRET) 82 | .parseClaimsJws(token.replace(JWTConstants.AUTHORIZATION_PRE, "")) 83 | .getBody() 84 | .getSubject(); 85 | if (user != null) { 86 | UserDetails userDetails = userDetailsService.loadUserByUsername(user); 87 | return new UsernamePasswordAuthenticationToken(userDetails.getUsername(), null, userDetails.getAuthorities()); 88 | } 89 | } 90 | ``` 91 | 92 | 上述代码,即JWT在Java中应用的方式之一。从代码中我们可以看出,当前签发的token使用HS256加密,在token中存放了username,用于验证时,通过username从上下文中获取当前用户的相关信息,从而完成token的校验操作。 93 | 94 | ##### 总结 95 | 96 | 因为json的通用,所以JWT可以进行跨语言支持。在payload中可以保存一些信息,因此可用于存放也一些业务相关的必要的非敏感信息。由于它不需要在后端保存会话信息,因此它易于扩展。但是在使用过程中,需要注意保护好秘钥信息,避免泄露。同时由于payload在客户端是可以解密的,因此请不要在payload中存放敏感信息,避免重要信息泄露。 -------------------------------------------------------------------------------- /java-logs.md: -------------------------------------------------------------------------------- 1 | # 日志处理 2 | 3 | 目前,项目中对api的访问日志进行了简单的记录,通过自定义控制器切面(ControllerInterceptor),环绕api方法进行日志记录。实现代码如下: 4 | 5 | ```java 6 | package itcast.research.handler; 7 | 8 | import itcast.research.entity.other.Log; 9 | import itcast.research.entity.user.User; 10 | import itcast.research.service.other.ILogService; 11 | import itcast.research.service.user.IUserService; 12 | import itcast.research.util.DBLogUtil; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.aspectj.lang.JoinPoint; 15 | import org.aspectj.lang.annotation.AfterReturning; 16 | import org.aspectj.lang.annotation.AfterThrowing; 17 | import org.aspectj.lang.annotation.Aspect; 18 | import org.aspectj.lang.annotation.Pointcut; 19 | import org.springframework.beans.factory.annotation.Autowired; 20 | import org.springframework.security.core.Authentication; 21 | import org.springframework.security.core.context.SecurityContextHolder; 22 | import org.springframework.stereotype.Component; 23 | import org.springframework.web.context.request.RequestContextHolder; 24 | import org.springframework.web.context.request.ServletRequestAttributes; 25 | 26 | import javax.servlet.http.HttpServletRequest; 27 | import java.util.Arrays; 28 | import java.util.Date; 29 | 30 | @Aspect 31 | @Component 32 | @Slf4j 33 | public class ControllerInterceptor { 34 | @Autowired 35 | private IUserService userService; 36 | @Autowired 37 | private ILogService logService; 38 | 39 | /** 40 | * 匹配controller包及其子包下的所有类的所有方法 41 | */ 42 | @Pointcut("execution(* itcast.research.controller..*.*(..))") 43 | public void executeService() { 44 | 45 | } 46 | 47 | /** 48 | * 方法执行成功通知 49 | * 50 | * @throws Throwable 51 | */ 52 | @AfterReturning(value = "executeService()") 53 | public void doAfter(JoinPoint jp) { 54 | saveLog(jp, true); 55 | } 56 | 57 | /** 58 | * 方法执行失败通知 59 | */ 60 | @AfterThrowing(value = "executeService()") 61 | public void afterThrowing(JoinPoint jp) { 62 | saveLog(jp, false); 63 | } 64 | 65 | /** 66 | * 保存日志 67 | * @param jp 方法信息 68 | * @param isSuc 操作结果标记 69 | */ 70 | public void saveLog(JoinPoint jp, boolean isSuc) { 71 | HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); 72 | String method = request.getMethod(); 73 | String uri = request.getRequestURI(); 74 | String body = Arrays.toString(jp.getArgs()); 75 | 76 | Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 77 | String email = (String) authentication.getPrincipal(); 78 | User user = userService.findByEmail(email); 79 | 80 | Log log = new Log(); 81 | log.setUser(user); 82 | log.setMethod(method); 83 | log.setUrl(uri); 84 | log.setOperationDate(new Date()); 85 | log.setRequestBody(body); 86 | log.setOperationResult(isSuc); 87 | DBLogUtil.getInstance().setLogService(logService).write(log); 88 | } 89 | } 90 | ``` 91 | 92 | 从上述的代码中,可以看到,使用@Pointcut、@AfterReturning、@AfterThrowing注解,我们可以轻松的构建环绕api方法的处理程序,通过这些环绕方法来达到日志收集的目的。未来,我们将会对日志进行详细的设计,丰富日志模块功能,为系统提供详尽的日志收集、存储和处理。 -------------------------------------------------------------------------------- /java-new-api.md: -------------------------------------------------------------------------------- 1 | # 新增 SpringBoot2 Rest Api 2 | 3 | 在黑马 Admin 中,新增一个 Rest Api 是非常方便的。通常我们会在 controller 包,创建一个名为\*Controller.java 的类文件,然后在该类当中定义所需添加的 rest api。具体步骤如下: 4 | 5 | * 第一步,在 itcast.research.controller 包下,创建名为 HelloController 的 Java 类。 6 | 7 | ```bash 8 | -itcast 9 | -research 10 | -controller 11 | -HelloController.java 12 | ``` 13 | 14 | * 第二步,打开 HelloController,开始编写相关代码。 15 | 16 | ```java 17 | package itcast.research.controller; 18 | 19 | import org.springframework.web.bind.annotation.RestController; 20 | 21 | @RestController 22 | public class HelloController { 23 | } 24 | ``` 25 | 26 | 在 HelloController 类头添加 @RestController 注解,将该控制器声明为 rest 控制器,该控制器中定义的 api 将以 json 方式返回信息。 27 | 28 | * 第三步,定义一个 api,例如/hello。 29 | 30 | ```java 31 | package itcast.research.controller; 32 | 33 | import org.springframework.web.bind.annotation.RequestMapping; 34 | import org.springframework.web.bind.annotation.RestController; 35 | 36 | @RestController 37 | public class HelloController { 38 | @RequestMapping(value = "/hello") 39 | public String hello(){ 40 | return "hello itheima"; 41 | } 42 | } 43 | ``` 44 | 45 | 添加名为 hello 的方法,并在方法上定义请求的链接"/hello",即 api 访问地址。 46 | 47 | * 第四步,验证。 48 | 49 | 为方便验证,我们暂时先将身份认证关闭,即接口访问时,无需验证身份。 50 | 51 | ```java 52 | //修改 itcast.research.config.WebSecurityConfig,并注释如下代码 53 | .anyRequest().authenticated() 54 | //注释后 55 | //.anyRequest().authenticated() 56 | ``` 57 | 58 | 运行项目,并使用 Postman 或者类似工具,对新建的接口进行访问。 59 | 60 | ```bash 61 | #这里使用 curl 62 | [root@ss]# curl http://localhost:7999/hello 63 | #返回信息 64 | hello itheima 65 | ``` 66 | 67 | 至此,我们已经完成了新增简单的 rest api 操作,更多的内容,请结合具体的业务,对数据操作层(Dao)以及服务层(Service)进行扩展。 68 | -------------------------------------------------------------------------------- /java-permission.md: -------------------------------------------------------------------------------- 1 | # 权限控制 2 | 3 | 在黑马Admin中,我们详细的设计了权限模块,目前权限分为3大类: 4 | 5 | - 菜单权限 6 | 7 | 主要用于约束页面菜单,路由等,其约束实现主要作用于前端,后端对此仅做信息的记录和查询操作。 8 | 9 | - 点权限 10 | 11 | 主要用于页面元素,如按钮,控件等等,其作用域同菜单权限 12 | 13 | - api权限 14 | 15 | 主要用于约束用户访问api,当用户访问未授权api,后端会返回未授权信息。 16 | 17 | 18 | 这里,我们主要介绍一下api权限。作为后端防御措施的一种,api权限可以有效的避免非授权用户对一些敏感接口进行访问和操作,从而达到接口保护的作用。接下来,将分步骤的分解api权限是如何设计以及实现。 19 | 20 | ##### 第一步 定义api权限 21 | 22 | 我们在编写接口时,都需要通过RequestMapping注解来完成接口访问地址等信息的编写。由于在项目启动时,我们会通过一个自定义启动类,利用Spring mvc提供的RequestMappingHandlerMapping获取项目中所有的 定义的api,并记录api的相关信息(URL,请求方法,接口名称)。因此,在编写api的RequestMapping时,需要为api定义一个易读的接口名称,这个名称将在为用户分配权限时使用。同时,按照约定,在定义接口名称时,所定义接口为敏感接口时,在名称前面增加check字样,用来标识这个接口在访问时,需要进行校验。 23 | 24 | ```java 25 | //RequestMapping 编写示例 26 | @RequestMapping( 27 | name = "check分页获取用户列表", //接口名称 28 | value = "/base/users", //接口url 29 | method = RequestMethod.GET, //请求方法 30 | produces = "application/json;charset=utf-8" 31 | ) 32 | 33 | //初始化 34 | List urlInfoList = PermissionUtil.getAllUrl(webApplicationContext); 35 | List saveList = new ArrayList<>(); 36 | for (PermissionUrlInfo info : urlInfoList) { 37 | Permission permission = new Permission(); 38 | permission.setName(info.getName()); 39 | permission.setType(PermissionConstants.PERMISSION_TYPE_API); 40 | PermissionApiExtend apiExtend = new PermissionApiExtend(); 41 | apiExtend.setApiMethod(info.getMethod()); 42 | apiExtend.setApiUrl(info.getUrl()); 43 | permission.setPermissionApiExtend(apiExtend); 44 | if (permission.getName().startsWith("check")) { 45 | permission.setName(permission.getName().replace("check", "")); 46 | apiExtend.setApiLevel(PermissionConstants.API_LEVEL_CHECK); 47 | } else { 48 | apiExtend.setApiLevel(PermissionConstants.API_LEVEL_UNCHECK); 49 | } 50 | saveList.add(permission); 51 | } 52 | permissionsService.saveApiPermissions(saveList); 53 | ``` 54 | 55 | 初始化操作,将所有的api接口写入到权限表中,以便在用户授权时使用。api权限的新增和信息更新无需手动添加,在项目启动时,会自动加载所有的变更。 56 | 57 | ##### 第二步 校验 58 | 59 | 在用户登录时,通过实现Spring Security提供的UserDetailsService,将已授予用户的权限放入上下文中。当用户访问接口时,Spring Security的验证模块会根据接口上定义的权限校验信息,对上下文中的用户信息的权限进行检查,只有通过时,才可进入接口具体的业务处理。 60 | 61 | ```java 62 | //加载权限到上下文 63 | @Service 64 | public class UserDetailsServiceImpl implements UserDetailsService { 65 | @Autowired 66 | private IUserService userService; 67 | 68 | @Override 69 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 70 | User user = userService.findByEmail(username); 71 | List authorities = new ArrayList<>(); 72 | if (user != null && user.getStatus() != EUserStatus.DISABLE.getCode()) { 73 | PermissionGroup permissionGroup = user.getPermissionGroup(); 74 | if (permissionGroup != null) { 75 | Set permissionSet = permissionGroup.getPermissions(); 76 | if (permissionSet != null) { 77 | for (Permission permission : permissionSet) { 78 | if (PermissionConstants.PERMISSION_TYPE_API == permission.getType()) { 79 | //此为API权限 80 | PermissionApiExtend permissionApiExtend = permission.getPermissionApiExtend(); 81 | GrantedAuthority grantedAuthority = new GrantedAuthorityImpl(permissionApiExtend.getApiMethod() + "|" + permissionApiExtend.getApiUrl()); 82 | authorities.add(grantedAuthority); 83 | } 84 | } 85 | } 86 | } 87 | return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), authorities); 88 | } 89 | return null; 90 | } 91 | } 92 | 93 | //声明权限检查 94 | @PreAuthorize("hasAuthority('GET|/base/users')") 95 | @RequestMapping(name = "check分页获取用户列表", value = "/base/users", method = RequestMethod.GET, produces = "application/json;charset=utf-8") 96 | public Mono> findAllByPage() throws Exception {} 97 | ``` 98 | 99 | 通过PreAuthorize注解,在进入方法前,进行权限校验,当该用户的上下文信息当中存在当前访问接口的权限,则进入方法继续执行。反之,直接返回未授权信息。 100 | 101 | 在上述权限内容的基础上,未来我们会对权限控制进行进一步的细化,做到用户与权限的细粒度控制。 -------------------------------------------------------------------------------- /java-spring-boot2.md: -------------------------------------------------------------------------------- 1 | # SpringBoot2 说明 2 | 3 | 本项目中使用了SpringBoot2,这个版本做了很多的变更。 4 | 5 | ##### 主要的变更 6 | 7 | - 基于Java8,支持Java9 8 | 9 | SpringBoot2完全基于Java8,对Java9做了一些支持。 10 | 11 | - 引入Webflux 12 | 13 | 它是一个新的响应式的Reactive Web框架,可以用来建立异步、非阻塞,以事件驱动的服务,并且扩展性很好,性能比之SpringMVC方式有了一定的提高。 14 | 15 | - 默认引入HikariCP 16 | 17 | 用于替代之前的tomcat-pool作为底层数据库连接池,相比tomcat-pool,HikariCP有更好的性能,能够有效的提高db的访问速度。 18 | 19 | - 为各种组件的响应式编程提供了自动配置,如Reactive Spring Data、Reactive Spring Security等 20 | 21 | ##### 依赖组件的升级 22 | 23 | - Tomcat升级至 8.5 24 | - Flyway升级至 5 25 | - Hibernate升级至 5.2 26 | - Thymeleaf升级至 3 27 | 28 | ##### 配置重定位 29 | 30 | 在一些配置上,SpringBoot2进行了修改,例如一些servlet相关的server.*属性重定位到了server.servlet前缀下: 31 | 32 | | Old property | New property | 33 | | ------------------------------ | -------------------------------------- | 34 | | `server.context-parameters.*` | `server.servlet.context-parameters.*` | 35 | | `server.context-path` | `server.servlet.context-path` | 36 | | `server.jsp.class-name` | `server.servlet.jsp.class-name` | 37 | | `server.jsp.init-parameters.*` | `server.servlet.jsp.init-parameters.*` | 38 | | `server.jsp.registered` | `server.servlet.jsp.registered` | 39 | | `server.servlet-path` | `server.servlet.path` | 40 | 41 | 更多的变化、配置重定位等,请查阅官方升级手册:https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide 42 | 43 | ##### 总结 44 | 45 | 实际上,升级或使用SpringBoot2不会像想象中的那样高风险,虽然它推出了很多的功能,但是对于原有功能的支持并没有抛弃。因此,在我们不使用Webflux的基础上,我们仅仅只需做一些依赖和配置上的调整就能继续将应用正常的运行起来。 -------------------------------------------------------------------------------- /layout.md: -------------------------------------------------------------------------------- 1 | # 布局 2 | 3 | 了解一个后台项目,先要了解它的基础布局。页面整体布局是一个产品最外层的框架结构,往往会包含导航、侧边栏、面包屑以及内容等。 4 | 5 | ## Layout 6 | 7 | > 对应代码目录: `/src/module-dashboard/pages/layout.vue` 8 | 9 | 大部分页面都是基于这个`layout`的,除了个别页面如 `login` , `404`, `401` 等页面没有使用该`layout`。如果你想在一个项目中有多种不同的`layout`也是很方便的,只要在一级路由那里选择不同的`layout`组件就行。 10 | 11 | ```js 12 | //no layout 13 | { 14 | path: '/401', 15 | component: _import('errorPage/401') 16 | } 17 | 18 | //has layout 19 | { 20 | path: '/documentation', 21 | //你可以选择不同的layout组件 22 | component: Layout, 23 | 24 | //这里开始对应的路由都会显示在app-main中 如上图所示 25 | children: [{ 26 | path: 'index', 27 | component: _import('documentation/index'), 28 | name: 'documentation' 29 | }] 30 | } 31 | ``` 32 | 33 | 这里使用了 vue-router [路由嵌套](https://router.vuejs.org/zh-cn/essentials/nested-routes.html), 所以一般情况下,你增加或者修改页面只会影响 `app-main`这个主体区域, 其它如侧边栏或者导航栏都不会发生变化。 34 | 35 | 当然你也可以一个项目里面使用多个不同的 `layout`,只要在你想作用的路由父级引用它就可以了。 36 | 37 | ## app-main 38 | 39 | > 对应代码目录: `/src/module-dashboard/components/layoutAppMain.vue` 40 | 41 | 这里在 `app-main` 外部包了一层 `keep-alive` 主要是为了缓存 ``的,配合页面的 `layoutTags` 标签导航使用,如不需要可自行 去除 `layoutTags`。 42 | 43 | ### router-view 44 | 45 | different router the same component vue。真实的业务场景中,这种情况很多。比如 46 | 47 | ![](img/different-router.jpeg) 48 | 49 | 我创建和编辑的页面使用的是同一个component,默认情况下当这两个页面切换时并不会触发vue的created或者mounted钩子,官方说你可以通过watch $route的变化来做处理,但其实说真的还是蛮麻烦的。后来发现其实可以简单的在 router-view上加上一个唯一的key,来保证路由切换时都会重新渲染触发钩子了。这样简单的多了。 50 | 51 | ```js 52 | 53 | 54 | computed: { 55 | key() { 56 | // 或者 :key="$route.path" 只要保证key唯一就可以了 57 | return this.$route.name !== undefined? this.$route.name + +new Date(): this.$route + +new Date() 58 | } 59 | } 60 | ``` 61 | 62 | > 可以声明 `editForm` 和 `createForm` 两个不同的 view 但引入同一个component。 63 | 64 | ```html 65 | //create.vue 66 | 69 | 72 | 73 | //edit.vue 74 | 77 | 80 | ``` 81 | -------------------------------------------------------------------------------- /lazy-loading.md: -------------------------------------------------------------------------------- 1 | # 路由懒加载 2 | 3 | 当打包构建应用时,Javascript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。 4 | 5 | 结合 Vue 的[异步组件](https://cn.vuejs.org/v2/guide/components.html#异步组件)和 Webpack 的[代码分割功能](https://doc.webpack-china.org/guides/code-splitting),轻松实现路由组件的懒加载。如: 6 | 7 | ```js 8 | const Foo = () => import('./Foo.vue') 9 | ``` 10 | 11 | ## 区分开发与生产环境 12 | 13 | 当你的项目页面越来越多之后,在开发环境之中使用 `lazy-loading` 会变得不太合适,每次更改代码触发热更新都会变得非常的慢。所以建议只在生成环境之中使用路由懒加载功能。 14 | 15 | 开发环境: 16 | 17 | ```js 18 | module.exports = file => require('@/views/' + file + '.vue').default // vue-loader at least v13.0.0+ 19 | ``` 20 | 21 | ?> 这里注意一下该写法只支持 `vue-loader at least v13.0.0+` 22 | 23 | 生产环境: 24 | 25 | ```js 26 | module.exports = file => () => import('@/views/' + file + '.vue') 27 | ``` 28 | 29 | ## 待改进 30 | 31 | 当然这样写会有一些副作用。由于 32 | 33 | > Every module that could potentially be requested on an import() call is included. For example, import(./locale/${language}.json) will cause every .json file in the ./locale directory to be bundled into the new chunk. At run time, when the variable language has been computed, any file like english.json or german.json will be available for consumption. 34 | 35 | `@/views/下的 .vue` 文件都会被打包。不管你是否被依赖。所以这样就产生了一个副作用,就是会多打包一些可能永远都用不到 js 代码。当然这只会增加 dist 文件的大小,但不会对线上代码产生任何的副作用。 36 | 37 | ?> 用户自己可以根据业务情况来衡量一下是否采用本方案,如果你的项目页面不超过几十个,本地开发热更新速度你还能接受的话,可以直接所有环境下都是用懒加载避免此副作用 38 | -------------------------------------------------------------------------------- /mock-api.md: -------------------------------------------------------------------------------- 1 | # Mock 数据 2 | 3 | Mock 数据是前端开发过程中必不可少的一环,是分离前后端开发的关键链路。通过预先跟服务器端约定好的接口,模拟请求数据甚至逻辑,能够让前端开发独立自主,不会被服务端的开发所阻塞。 4 | 5 | ## Swagger 6 | 7 | 我司项目中通常使用 [swagger](https://swagger.io/) 由后端来模拟数据。 8 | **swagger** 是一个 REST APIs 文档生成工具,可以跨平台从代码注释中自动生成,开源,支持大部分语言,社区好,总之非常不错,强烈推荐。 9 | 10 | ## Easy-mock 11 | 12 | 我们使用的是 [easy-mock](https://easy-mock.com/login) 来模拟数据。它是一个纯前端可视化,并且能快速生成 模拟数据 的持久化服务。非常的简单易用还能结合 `swagger` ,不管团队还是个人项目都值得一试。 13 | 14 | > 地址 `https://www.easy-mock.com/mock/5ab213e33666166110a94928/admin` 15 | 16 | ## Mockjs 17 | 18 | [mockjs](https://github.com/nuysoft/Mock) 生成的,它的原理是:拦截了所有的请求并代理到本地模拟数据,所以 network 中没有发出任何的请求发出。 19 | -------------------------------------------------------------------------------- /nav-permission.md: -------------------------------------------------------------------------------- 1 | # 新增菜单 2 | 3 | 假设已有模块目录 `/src/module-example` 4 | 5 | ## 第一步:编辑路由 6 | 7 | 打开刚才自动创建的 `/src/module-example/router/index.js` 8 | 9 | ```js 10 | import Layout from '@/module-dashboard/pages/layout' 11 | const _import = require('@/router/import_' + process.env.NODE_ENV) 12 | 13 | export default [ 14 | { 15 | path: '/my-admin', 16 | component: Layout, 17 | redirect: 'noredirect', 18 | name: 'my-admin', 19 | meta: { 20 | title: 'my-admin', 21 | icon: 'component' 22 | }, 23 | children: [ 24 | { 25 | path: 'index', 26 | component: _import('my-admin/pages/index'), 27 | name: 'my-admin-index', 28 | meta: {title: 'index'} 29 | } 30 | ] 31 | } 32 | ] 33 | ``` 34 | 35 | ?> 在 `children` 下维护你的模块页面路由 36 | 37 | ## 第二步:菜单管理 38 | 39 | 添加你的新页面 `代码` `标题` 40 | 41 | ?> 代码是全局唯一的 42 | 43 | ## 第三步:权限组授权 44 | 45 | 权限组授权中勾选需要的菜单项 46 | -------------------------------------------------------------------------------- /new-page.md: -------------------------------------------------------------------------------- 1 | # 新增页面 2 | 3 | ## 编写业务界面 4 | 5 | * 创建 `/src/module-example/pages/my.vue` 6 | 7 | ```html 8 | 13 | 14 | 29 | ``` 30 | 31 | > 注意文件名 `驼峰格式` `首字小写` 32 | > 33 | > 页面请放在目录 `/src/module-example/pages/` 34 | > 35 | > 组件请放在目录 `/src/module-example/components/` 36 | > 37 | > 页面路由请修改 `/src/module-example/router/index.js` 38 | 39 | ## 可选:`i18n` 多语言配置 40 | 41 | * 编辑 `/src/lang/zh.js` `/src/lang/zh.js` 42 | 43 | ```js 44 | export default { 45 | route: { 46 | ... 47 | my: '我的' 48 | }, 49 | ... 50 | ``` 51 | 52 | > `router` 是菜单导航显示的标题,对应 `meta: {title: 'my'}` 中的 `title` 属性 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-element-admin-doc-itheima", 3 | "version": "1.0.0", 4 | "description": "![logo](./img/logo.png ':no-zoom')", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "docsify serve" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://itheima2017@github.com/itheima2017/vue-element-admin-doc-itheima.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/itheima2017/vue-element-admin-doc-itheima/issues" 17 | }, 18 | "homepage": "https://github.com/itheima2017/vue-element-admin-doc-itheima#readme" 19 | } 20 | -------------------------------------------------------------------------------- /page-points.md: -------------------------------------------------------------------------------- 1 | # 新增权限点 2 | 3 | ## 添加权限点 4 | 5 | ## 授权权限点 6 | 7 | ## 检查权限点 8 | 9 | * 第一步:导入 `hasPermissionPoint` 检查函数 10 | 11 | ```js 12 | import { hasPermissionPoint } from '@/utils/permission' 13 | ``` 14 | 15 | * 第二步:检查操作 16 | 17 | ```js 18 | let hasList = hasPermissionPoint('base-users-list') 19 | console.log(hasList) 20 | ``` 21 | 22 | > `hasList` 将会以 `bool` 形式返回是否有权限点 23 | 24 | ?> 对于呈现类组件可以结合 `v-if` 指令来控制 25 | -------------------------------------------------------------------------------- /permission.md: -------------------------------------------------------------------------------- 1 | # 权限设计 2 | 3 | 其实权限问题,可以设计的很复杂,我们通过研究考虑以轻量级的方式开始项目为基线。 4 | 5 | 权限控制分为三个方面 `菜单授权` `页面权限点` `api鉴权`: 6 | 7 | | 名称 | 说明 | 8 | | ---------- | ------------------------------------ | 9 | | 菜单授权 | 客户端,用户可以使用的菜单、路由 | 10 | | 页面权限点 | 客户端,用户在单个页面可以使用的功能 | 11 | | api 鉴权 | 服务端,过滤掉没有授权的 api 请求 | 12 | -------------------------------------------------------------------------------- /router-and-nav.md: -------------------------------------------------------------------------------- 1 | # 路由和菜单 2 | 3 | 路由和菜单是组织起一个后台应用的关键骨架。 4 | 5 | 本项目侧边栏和路由是绑定在一起的,所以你只有在 `@/router/index.js` 下面配置对应的路由,侧边栏就能动态的生成了。大大减轻了手动编辑侧边栏的工作量。当然这样就需要在配置路由的时候遵循很多的约定。 6 | 7 | ## 配置项 8 | 9 | 首先我们了解一些本项目配置路由时提供了哪些配置项。 10 | 11 | ```js 12 | //当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面(默认 false) 13 | hidden: true 14 | 15 | //当设置 noredirect 的时候该路由不会在面包屑导航中出现 16 | redirect: noredirect 17 | 18 | //当设置 true 的时候永远会显示根菜单,不设置的情况下只有当子路由个数大于一个时才会显示根菜单 19 | //当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式。只有一个时会将那个子路由当做根路由 20 | alwaysShow: true 21 | 22 | name:'router-name' //设定路由的名字,一定要填写不然 使用 时会出现各种问题 23 | meta : { 24 | roles: ['admin','editor'] //设置该路由进入的权限,支持多个权限叠加 25 | title: 'title' //设置该路由在侧边栏和面包屑中展示的名字 26 | icon: 'svg-name' //设置该路由的图标 27 | noCache: true //如果设置为true ,则不会被 缓存(默认 false) 28 | } 29 | ``` 30 | 31 | ?> 路由中的 `name` 和组件对象中的 `name` 必须一致,否者缓存失败 32 | 33 | ** 示例: ** 34 | 35 | ```js 36 | { 37 | path: '/permission', 38 | component: Layout, 39 | redirect: '/permission/index', 40 | hidden: true, 41 | alwaysShow: true, 42 | meta: { roles: ['admin','edior'] }, // you can set roles in root nav 43 | children: [{ 44 | path: 'index', 45 | component: _import('permission/index'), 46 | name: 'permission', 47 | meta: { 48 | title: 'permission', 49 | icon: 'lock', 50 | role: ['admin','editor'], // or you can only set roles in sub nav 51 | noCache: true 52 | } 53 | }] 54 | } 55 | ``` 56 | 57 | ## 路由 58 | 59 | 这里的路由分为两种,`constantRouterMap` 和 `asyncRouterMap`。 60 | 61 | **constantRouterMap** 代表那些不需要动态判断权限的路由,如登录页,404,等通用页面。 62 | 63 | **asyncRouterMap** 代表那些需求动态判断权限并通过 `addRouters` 动态添加的页面。 64 | 65 | > 这里所有的组件使用自定义方法 `_import` 引入,具体介绍见[路由懒加载](lazy-loading) 66 | > 67 | > 如果你想了解更多关于 browserHistory 和 hashHistory,请参看 [构建和发布](deploy)。 68 | 69 | 其它的配置和 [vue-router](https://router.vuejs.org/zh-cn/) 官方并没有区别,自行查看文档。 70 | 71 | ?> **注意事项:**如果这里有一个需要非常注意的地方就是404页面一定要最后加载,如果放在 constantRouterMap 一同声明了404,后面的所以页面都会被拦截到404,详细的问题见 [addRoutes when you've got a wildcard route for 404s does not work](https://github.com/vuejs/vue-router/issues/1176) 72 | 73 | ## 侧边栏 74 | 75 | 本项目侧边栏主要基于 `element-ui` 的 `el-menu` 改造。 76 | 77 | 前面也介绍了,侧边栏是通过读取路由并结合权限判断而动态生成的,而且还需要支持路由无限嵌套,所以这里还使用到了递归组件。 78 | 79 | > 代码地址: `/src/module-dashboard/components/layoutSidebar.vue` 80 | 81 | 这里同时也改造了 `element-ui` 默认侧边栏不少的样式,所有的css都可以在 `/src/styles/sidebar.scss` 中找到,可以根据自己的需求进行修改。 82 | 83 | ?> 这里需要注意一下,一般侧边栏有两种形式,即有 `submenu` 和 直接 `el-menu-item`。 一个嵌套子菜单,一个则是直接一个链接。 84 | 85 | ?> 在 `Sidebar` 中已经做了判断,当你一个路由下面的 `children` 声明的路由大于1个时,自动会变成嵌套的模式。如果子路由正好等于一个就会默认将子路由作为根路由显示在侧边栏中,若不想这样,可以通过设置在根路由中设置`alwaysShow: true`来取消这一特性。如: 86 | 87 | ```js 88 | // no submenu, because children.length===1 89 | { 90 | path: '/icon', 91 | component: Layout, 92 | children: [{ 93 | path: 'index', 94 | component: _import('svg-icons/index'), 95 | name: 'icons', 96 | meta: { title: 'icons', icon: 'icon' } 97 | }] 98 | }, 99 | 100 | // has submenu, because children.length>=1 101 | { 102 | path: '/components', 103 | component: Layout, 104 | name: 'component-demo', 105 | meta: { 106 | title: 'components', 107 | icon: 'component' 108 | }, 109 | children: [ 110 | { path: 'tinymce', component: _import('components-demo/tinymce'), name: 'tinymce-demo', meta: { title: 'tinymce' }}, 111 | { path: 'markdown', component: _import('components-demo/markdown'), name: 'markdown-demo', meta: { title: 'markdown' }}, 112 | ] 113 | } 114 | ``` 115 | 116 | ### 点击侧边栏 刷新当前路由 117 | 118 | 在用 spa(单页面应用) 这种开发模式的之前,用户每次点击侧边栏都会重新请求这个页面,用户渐渐养成了点击侧边栏当前路由来刷新 view 的习惯。但现在 spa 就不一样了,用户点击当前高亮的路由并不会刷新 view,因为 vue-router 会拦截你的路由,它判断你的url并没有任何变化,所以它不会触发任何钩子或者是view的变化。[issue](https://github.com/vuejs/vue-router/issues/296) 地址,社区也对该问题展开了激烈讨论。 119 | 120 | 尤大本来也说要增加一个方法来强刷 view,但后来他又改变了心意/(ㄒoㄒ)/~~。但需求就摆在这里,我们该怎么办呢?他说了不改变current URL 就不会触发任何东西,那我可不可以强行触发你的hook呢?上有政策, 下有对策我们变着花来hack。方法也很简单,通过不断改变 url的 query 来触发view的变化。我们监听侧边栏每个 link 的 click 事件,每次点击都给 router push 一个不一样的query 来确保会重新刷新 view。 121 | 122 | ```js 123 | clickLink(path) { 124 | this.$router.push({ 125 | path, 126 | query: { 127 | t: +new Date() //保证每次点击路由的query项都是不一样的,确保会重新刷新view 128 | } 129 | }) 130 | } 131 | ``` 132 | 133 | ps:不要忘了在 `router-view` 加上一个特定唯一的 `key`,如 ``, 134 | 但这也有一个弊端就是 url 后面有一个很难看的 `query` 后缀如 `xxx.com/article/list?t=1496832345025` 135 | 136 | ## 面包屑 137 | 138 | 本项目中也封装了一个面包屑导航,它也是通过 `watch $route` 变化动态生成的。它和 menu 也一样,也可以通过之前那些配置项控制一些路由在面包屑中的展现。大家也可以结合自己的业务需求增改这些自定义属性。 139 | 140 | > 组件地址: `/src/components/Breadcrumb` 141 | 142 | ## 侧边栏滚动问题 143 | 144 | 之前版本的滚动都是用css来做处理的 145 | 146 | ```css 147 | overflow-y:scroll; 148 | 149 | ::-webkit-scrollbar {display:none} 150 | 151 | ``` 152 | 153 | 首先这样写会有兼容性问题,在火狐或者其它低版本游览器中都会比较不美观。其次在侧边栏收起的情况下,受限于 `element-ui`的 `menu` 组件的实现方式,不能使用该方式来处理。 154 | 155 | 所以现版本中使用了 js 来处理侧边栏滚动问题。封装了 滚动组件 `ScrollPane`。 156 | 157 | > 代码地址: `/src/components/ScrollPane` 158 | -------------------------------------------------------------------------------- /server.md: -------------------------------------------------------------------------------- 1 | # 和服务端进行交互 2 | 3 | ## 前端请求流程 4 | 5 | 在 黑马Admin 中,一个完整的前端 UI 交互到服务端处理流程是这样的: 6 | 7 | 1. UI 组件交互操作; 8 | 3. 调用统一管理的 api service 请求函数; 9 | 4. 使用封装的 request.js 发送请求; 10 | 5. 获取服务端返回; 11 | 7. 更新 data; 12 | 13 | 从上面的流程可以看出,为了方便管理维护,统一的请求处理都放在 `src/api` 文件夹中,并且一般按照 model 纬度进行拆分文件,如: 14 | 15 | ``` 16 | api/ 17 | frame.js 18 | menus.js 19 | users.js 20 | permissions.js 21 | ... 22 | ``` 23 | 24 | 其中,`src/utils/request.js` 是基于 [axios](https://github.com/axios/axios) 的封装,便于统一处理 POST,GET 等请求参数,请求头,以及错误提示信息等。具体可以参看 [request.js](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/utils/request.js)。 25 | 它封装了全局 `request拦截器`、`respone拦截器`、`统一的错误处理`、`统一做了超时,baseURL设置等` 26 | 27 | ### 一个请求 用户管理 的例子: 28 | 29 | ```js 30 | // api/article.js 31 | import {createAPI} from '@/utils/request' 32 | 33 | export const list = data => createAPI('/base/users', 'get', data) 34 | export const simple = data => createAPI('/base/users/simple', 'get', data) 35 | export const add = data => createAPI('/base/users', 'post', data) 36 | export const update = data => createAPI('/base/users', 'put', data) 37 | export const remove = data => createAPI('/base/users', 'delete', data) 38 | export const detail = data => createAPI('/base/users/:id', 'get', data) 39 | ``` 40 | 41 | ?> `createAPI` 常规方式提交 42 | 43 | ?> `createFormAPI` 表单方式提交 44 | 45 | ### 修改 API 基础地址 `BASE_API` 46 | 47 | * 编辑文件 `config/dev.cnv.js` 或者 `config/pro.cnv.js` 48 | 49 | ```js 50 | 'use strict' 51 | const merge = require('webpack-merge') 52 | const prodEnv = require('./prod.env') 53 | 54 | module.exports = merge(prodEnv, { 55 | NODE_ENV: '"development"', 56 | BASE_API: '"api"' 57 | }) 58 | ``` 59 | 60 | ?> `BASE_API` 就是基地址 61 | 62 | ### 是否启用代理 63 | 64 | * 编辑文件 `config/index.js` 65 | 66 | ```js 67 | ... 68 | module.exports = { 69 | dev: { 70 | // Paths 71 | assetsSubDirectory: 'static', 72 | assetsPublicPath: '/', 73 | proxyTable: { 74 | '/api': { 75 | target: 'https://www.easy-mock.com/mock/5ab213e33666166110a94928/admin', 76 | changeOrigin: true, 77 | pathRewrite: { 78 | '^/api': '' 79 | } 80 | } 81 | }, 82 | ... 83 | ``` 84 | 85 | > 关闭请设置 `proxyTable: {}` 86 | > 87 | > 代理配置中有重写路径的选项:`pathRewrite` 设置后 88 | > 89 | > `http://yourserver/api/user/12` 变成 `http://apiserver/user/12` 90 | -------------------------------------------------------------------------------- /style.md: -------------------------------------------------------------------------------- 1 | # scss 样式 2 | 3 | ## 安装依赖包 4 | 5 | ```bash 6 | npm install sass-loader node-sass --save-dev 7 | ``` 8 | 9 | ## 无需配置 10 | 11 | * vue脚手架已经为我们在 `webpack` 中写好了 12 | * 文件 `/build/utils.js` 13 | 14 | ```js 15 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 16 | return { 17 | css: generateLoaders(), 18 | postcss: generateLoaders(), 19 | less: generateLoaders('less'), 20 | sass: generateLoaders('sass', {indentedSyntax: true}), 21 | scss: generateLoaders('sass'), 22 | stylus: generateLoaders('stylus'), 23 | styl: generateLoaders('stylus') 24 | } 25 | ``` 26 | 27 | ## 使用 28 | 29 | ```html 30 | 33 | 34 | 37 | 38 | 47 | ``` 48 | -------------------------------------------------------------------------------- /theme.md: -------------------------------------------------------------------------------- 1 | # 更换主题 2 | 3 | 本项目基于 element-ui 默认视觉风格搭建了。如果对视觉风格有额外的要求可以按照[官方自定义主题指导](http://element-cn.eleme.io/#/zh-CN/component/custom-theme)。 4 | 5 | ## 样式覆盖 6 | 7 | element-ui 的通用样式变量可能无法满足所有定制需求,你可以覆盖默认的组件样式。由于 element-ui 的样式我们是在全局引入的,所以你想在某个 view 里面覆盖它的样式就不能加 scoped,但你又想只覆盖这个页面的 element 样式,你就可在它的父级加一个 class,以用命名空间来解决问题。 8 | 9 | ```css 10 | .aritle-page{ //你的命名空间 11 | .el-tag { //element-ui 元素 12 | margin-right: 0px; 13 | } 14 | } 15 | ``` 16 | 17 | 一些全局的 element-ui 样式修改可以在 `src/styles/element-ui.scss` 中进行设置。 18 | 19 | ## 动态换肤 20 | 21 | 本项目提供了两种动态换肤的功能,各有利弊,请结合个人需求自行选择。 22 | 23 | ### element-ui 官方文档页面 换肤方式 24 | 25 | element-ui 升级为 2.0 之后官方文档的右上角提供了动态换肤的功能,本项目也提供了改功能。代码地址:`src/components/ThemePicker` 26 | 27 | 简单说明一下它的原理: 28 | element-ui 2.0 版本之后所有的样式都是基于 SCSS 编写的,所有的颜色都是基于几个基础颜色变量来设置的,所以就不难实现动态换肤了,只要找到那几个颜色变量修改它就可以了。首先我们需要拿到通过 `package.json` 拿到 element-ui 的版本号,根据该版本号去请求相应的样式。拿到样式之后将样色,通过正则匹配和替换,将颜色变量替换成你需要的,之后动态添加 `style` 标签来覆盖原有的 css 样式。 29 | 30 | **使用** 31 | 32 | ?> PS:这里需要获取 element-ui 的版本号,从而锁定版本,以免将来 Element 升级时受到非兼容性更新的影响。 33 | 34 | ```js 35 | const version = require('element-ui/package.json').version 36 | 37 | const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css` 38 | this.getCSSString(url, chalkHandler, 'chalk') 39 | 40 | getCSSString(url, callback, variable) { 41 | const xhr = new XMLHttpRequest() 42 | xhr.onreadystatechange = () => { 43 | if (xhr.readyState === 4 && xhr.status === 200) { 44 | this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '') 45 | callback() 46 | } 47 | } 48 | xhr.open('GET', url) 49 | xhr.send() 50 | } 51 | ``` 52 | 53 | 之后在项目中引入 ThemePicker 组件即可 54 | 55 | ```js 56 | import ThemePicker from '@/components/ThemePicker' 57 | ``` 58 | 59 | * 优点 60 | * 无需准备多套主题,可以自由动态换肤 61 | * 缺点 62 | * 自定义不够,只支持基础颜色的切换 63 | 64 | ### 多套主题换肤 65 | 66 | 本方法就是最常见的换肤方式,本地存放多套主题,两者有不同的命名空间,如写两套主题,一套叫 day-theme ,一套叫 night-theme,night-theme 主题都在一个.night-theme 的命名空间下,我们动态的在 body 上 add .night-theme ; remove .night-theme。 67 | --------------------------------------------------------------------------------