├── .env.local ├── .env.production ├── .gitignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public ├── bg.jpg ├── favicon.ico └── index.html ├── src ├── App.vue ├── api │ ├── admin.js │ ├── login.js │ └── table.js ├── assets │ ├── 404_images │ │ ├── 404.png │ │ └── 404_cloud.png │ ├── iconfont │ │ ├── demo.css │ │ ├── iconfont.css │ │ ├── iconfont.eot │ │ ├── iconfont.js │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 │ └── logo.png ├── components │ ├── Breadcrumb │ │ └── index.vue │ ├── Hamburger │ │ └── index.vue │ └── SvgIcon │ │ └── index.vue ├── main.js ├── permisssion.js ├── router.js ├── store │ ├── getters.js │ ├── index.js │ ├── modules │ │ ├── admin.js │ │ ├── app.js │ │ └── user.js │ └── routePermission.js ├── styles │ ├── element-ui.scss │ ├── index.scss │ ├── normalize.scss │ ├── sidebar.scss │ ├── transition.scss │ └── variables.scss ├── utils │ ├── auth.js │ ├── index.js │ ├── request.js │ └── validate.js └── views │ ├── 404.vue │ ├── admin │ ├── manage-money.vue │ └── manage-users.vue │ ├── center │ └── index.vue │ ├── dashboard │ └── index.vue │ ├── display │ ├── fiction.vue │ ├── index.vue │ └── technology.vue │ ├── edit │ ├── index.vue │ └── test.js │ ├── layout │ ├── Layout.vue │ ├── components │ │ ├── AppMain.vue │ │ ├── Navbar.vue │ │ ├── Sidebar │ │ │ ├── SidebarItem.vue │ │ │ └── index.vue │ │ └── index.js │ └── mixin │ │ └── ResizeHandler.js │ ├── login │ └── index.vue │ ├── nested │ ├── menu1 │ │ ├── index.vue │ │ ├── menu1-1 │ │ │ └── index.vue │ │ ├── menu1-2 │ │ │ ├── index.vue │ │ │ ├── menu1-2-1 │ │ │ │ └── index.vue │ │ │ └── menu1-2-2 │ │ │ │ └── index.vue │ │ └── menu1-3 │ │ │ └── index.vue │ └── menu2 │ │ └── index.vue │ ├── register │ └── index.vue │ ├── table │ └── index.vue │ └── tree │ └── index.vue └── vue.config.js /.env.local: -------------------------------------------------------------------------------- 1 | VUE_APP_BASE_API=/nodejsapi 2 | VUE_APP_PUBLIC_URL=/ 3 | #VUE_LIFECIRCLE_EVENT=DEV -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | VUE_APP_BASE_API=/nodejsapi 2 | VUE_APP_PUBLIC_URL=http://vue.wtodd.wang 3 | VUE_LIFECIRCLE_EVENT=ANALYZE -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # Log files 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Editor directories and files 11 | .idea 12 | .vscode 13 | *.suo 14 | *.ntvs* 15 | *.njsproj 16 | *.sln 17 | *.sw* 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-cli3.0-vueadmin 2 | 技术栈主要使用vue-cli3.0+vue+elementUI+vuex+axios。
3 | 这是一个基于手摸手系列,vueadmin-template进行改造的版本----感谢作者风骚花裤衩。 4 | 5 | 由于是基于vue-cli3.0为基础进行的开发,所以同比vue-cli2会有区别:
6 | 1、项目的目录结构发生了变化,vue-cli3.0隐藏了webpack的配置文件,目录看起来非常的清爽简洁,在目标上追求0配置进行开发,将大部分时间用在开发上,避免在配置上浪费过多时间。但是个人风格配置无法避免,这里提供了一个vue.config.js进行项目的配置;

7 | 8 | 2、线上预览地址 登陆:admin 123456 9 | 10 | ##注意事项:

11 | 1、由于个人风格原因,该项目去掉了eslint限制,需要的同学可以自己增加;

12 | 2、为了跑通整个项目,这里我使用nodejs写了几个接口进行验证,包括token、userinfo、list,并且使用cors开放了跨域,需要的同学可以直接使用,无需代理;

13 | 3、后端使用的是express + mongodb,地址为: https://github.com/adouwt/nodejsAPI,用的是es6的语法写的后端服务,这个项目都在一直更新,如有需要,可以关注,私聊讨论。

14 | 4、可以直接注册用户,默认为dev角色,注册,admin登录进去,可以新建用户 15 | 如果这个项目对觉得OK,可以点击右上角的star啊 16 | 17 | ## 简单的描述下,从注册页面到注册成功跳转页面的数据流程 18 | ### views/register/index.vue 触发actions Register 19 | ### --> store/user.js的Register, 这个js调用了 api/login.js register方法 ,register方法返回一个request的promise对象 20 | ### --> utils/request.js promise resolve(或者reject)数据 给 store/user.js 21 | ### --> store/user.js register promise then(或者catch)方法接收到返回数据,然后 resolve(reject)response给 view/resgiter/index.vue 22 | ### --> view/resgiter/index.vue 接受到注册成功信息 23 | ### 其中,错误信息会在 request.js 进行拦截处理,在每次页面路由跳转时候,都会经过permission.js 24 | 25 | ### todo 26 | #### ~~新增一个接口 配置用户角色能访问的路由, 每次当用户登录成功,permission.js 拉取用户信息时,限制用户的访问路由,没有权限时强制跳转到一个没有权限的提示页面~~ 27 | #### ~~用户管理那里新增用户,修改密码,修改权限等功能~~ 28 | 29 | #### 接口访问权限 30 | ##### 接口访问时候需要接口校验,简单的传递,在参数里面校验是否是 role 31 | ### Project setup 32 | ``` 33 | npm install 34 | ``` 35 | 36 | ### Compiles and hot-reloads for development 37 | ``` 38 | npm run serve 39 | ``` 40 | 41 | ### Compiles and minifies for production 42 | ``` 43 | npm run build 44 | ``` 45 | 46 | ### Compiles and minifies for production to analyze the component percent 47 | ``` 48 | npm run analyze 49 | ``` 50 | 51 | 52 | 53 | #### 2019-5-31 54 | 默认关闭 开发环境的包文件分析,如果需要的话,可以修改.env.local VUE_LIFECIRCLE_EVENT 为DEV -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-admin-template", 3 | "version": "0.1.0", 4 | "private": true, 5 | "author": "Wtodd <1259709654@qq.com>", 6 | "scripts": { 7 | "serve": "vue-cli-service serve", 8 | "build": "cross-env NODE_ENV=production vue-cli-service build --mode production", 9 | "analyz": "cross-env NODE_ENV=production npm_config_report=true npm run build" 10 | }, 11 | "dependencies": { 12 | "@tinymce/tinymce-vue": "^2.0.0", 13 | "axios": "0.17.1", 14 | "cross-env": "^5.2.0", 15 | "element-ui": "2.3.4", 16 | "js-cookie": "2.2.0", 17 | "normalize.css": "7.0.0", 18 | "nprogress": "0.2.0", 19 | "vue": "^2.5.17", 20 | "vue-ajax-upload": "^0.1.7", 21 | "vue-router": "^3.0.1", 22 | "vue2-editor": "^2.6.6", 23 | "vuex": "^3.0.1" 24 | }, 25 | "devDependencies": { 26 | "@vue/cli-plugin-babel": "^3.0.0", 27 | "@vue/cli-service": "^3.5.3", 28 | "node-sass": "^4.9.3", 29 | "qiniu-webpack-plugin": "^0.4.2", 30 | "sass-loader": "^7.1.0", 31 | "svg-sprite-loader": "3.5.2", 32 | "vue-template-compiler": "^2.5.17", 33 | "webpack-bundle-analyzer": "^2.13.1" 34 | }, 35 | "postcss": { 36 | "plugins": { 37 | "autoprefixer": {} 38 | } 39 | }, 40 | "browserslist": [ 41 | "> 1%", 42 | "last 2 versions", 43 | "not ie <= 8" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /public/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adouwt/vue-cli3-admin-system/6cd999169d5fc0b8322487f5f2093f2ad6a0fb87/public/bg.jpg -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adouwt/vue-cli3-admin-system/6cd999169d5fc0b8322487f5f2093f2ad6a0fb87/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue-cli3 admin 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/api/admin.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getAllUser() { 4 | console.log(456798132) 5 | return request({ 6 | url: '/get/alluser', 7 | method: 'get', 8 | data: { 9 | page: page 10 | } 11 | }) 12 | } 13 | export function deleteOneUser(id) { 14 | return request({ 15 | url: '/post/deleteuser', 16 | method: 'post', 17 | data: { 18 | id: id 19 | } 20 | }) 21 | } 22 | 23 | export function GetAllUserFromPage(page, skip) { 24 | return request({ 25 | url: '/post/getUsersFromPage', 26 | method: 'post', 27 | data: { 28 | page: page, 29 | skip: skip 30 | } 31 | }) 32 | } 33 | 34 | export function updateSomeOneRole(id, roles) { 35 | return request({ 36 | url: '/post/updatesomerole', 37 | method: 'post', 38 | data: { 39 | id: id, 40 | roles: roles 41 | } 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /src/api/login.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function login(username, password) { 4 | return request({ 5 | url: '/post/login', 6 | method: 'post', 7 | data: { 8 | username, 9 | password 10 | } 11 | }) 12 | } 13 | // 像这样的结构不好,前端页面添加一个参数,这里也相应的添加 14 | export function register(username, password, type, roles, email, registerCode) { 15 | // console.log('register in login.js') 16 | return request({ 17 | url: '/post/register', 18 | method: 'post', 19 | data: { 20 | username, 21 | password, 22 | type: type, 23 | roles: roles, 24 | email: email, 25 | registerCode: registerCode 26 | } 27 | }) 28 | } 29 | 30 | // 管理员添加用户 31 | export function adminRegister(username, password, roles, age) { 32 | // console.log('register in login.js') 33 | return request({ 34 | url: '/post/adminRegister', 35 | method: 'post', 36 | data: { 37 | username, 38 | password, 39 | roles: roles, 40 | age: age 41 | } 42 | }) 43 | } 44 | 45 | 46 | 47 | export function getInfo(token) { 48 | return request({ 49 | url: '/get/user/info', 50 | method: 'get', 51 | params: { token } 52 | }) 53 | } 54 | 55 | export function logout(token) { 56 | return request({ 57 | url: '/post/logout', 58 | method: 'post', 59 | params: { token } 60 | }) 61 | } 62 | 63 | export function sendEmail(email) { 64 | return request({ 65 | url: '/post/sendEmailCode', 66 | method: 'post', 67 | data: { 68 | email 69 | } 70 | }) 71 | } 72 | 73 | export function uploadImg(_id, file) { 74 | return request({ 75 | url: '/post/uploadImg', 76 | method: 'post', 77 | data: { 78 | _id: _id, 79 | avatar_file: file 80 | } 81 | }) 82 | } 83 | -------------------------------------------------------------------------------- /src/api/table.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getList(params) { 4 | return request({ 5 | url: '/table/list', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /src/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adouwt/vue-cli3-admin-system/6cd999169d5fc0b8322487f5f2093f2ad6a0fb87/src/assets/404_images/404.png -------------------------------------------------------------------------------- /src/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adouwt/vue-cli3-admin-system/6cd999169d5fc0b8322487f5f2093f2ad6a0fb87/src/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /src/assets/iconfont/demo.css: -------------------------------------------------------------------------------- 1 | /* Logo 字体 */ 2 | @font-face { 3 | font-family: "iconfont logo"; 4 | src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834'); 5 | src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'), 6 | url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'), 7 | url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'), 8 | url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg'); 9 | } 10 | 11 | .logo { 12 | font-family: "iconfont logo"; 13 | font-size: 160px; 14 | font-style: normal; 15 | -webkit-font-smoothing: antialiased; 16 | -moz-osx-font-smoothing: grayscale; 17 | } 18 | 19 | /* tabs */ 20 | .nav-tabs { 21 | position: relative; 22 | } 23 | 24 | .nav-tabs .nav-more { 25 | position: absolute; 26 | right: 0; 27 | bottom: 0; 28 | height: 42px; 29 | line-height: 42px; 30 | color: #666; 31 | } 32 | 33 | #tabs { 34 | border-bottom: 1px solid #eee; 35 | } 36 | 37 | #tabs li { 38 | cursor: pointer; 39 | width: 100px; 40 | height: 40px; 41 | line-height: 40px; 42 | text-align: center; 43 | font-size: 16px; 44 | border-bottom: 2px solid transparent; 45 | position: relative; 46 | z-index: 1; 47 | margin-bottom: -1px; 48 | color: #666; 49 | } 50 | 51 | 52 | #tabs .active { 53 | border-bottom-color: #f00; 54 | color: #222; 55 | } 56 | 57 | .tab-container .content { 58 | display: none; 59 | } 60 | 61 | /* 页面布局 */ 62 | .main { 63 | padding: 30px 100px; 64 | width: 960px; 65 | margin: 0 auto; 66 | } 67 | 68 | .main .logo { 69 | color: #333; 70 | text-align: left; 71 | margin-bottom: 30px; 72 | line-height: 1; 73 | height: 110px; 74 | margin-top: -50px; 75 | overflow: hidden; 76 | *zoom: 1; 77 | } 78 | 79 | .main .logo a { 80 | font-size: 160px; 81 | color: #333; 82 | } 83 | 84 | .helps { 85 | margin-top: 40px; 86 | } 87 | 88 | .helps pre { 89 | padding: 20px; 90 | margin: 10px 0; 91 | border: solid 1px #e7e1cd; 92 | background-color: #fffdef; 93 | overflow: auto; 94 | } 95 | 96 | .icon_lists { 97 | width: 100% !important; 98 | overflow: hidden; 99 | *zoom: 1; 100 | } 101 | 102 | .icon_lists li { 103 | width: 100px; 104 | margin-bottom: 10px; 105 | margin-right: 20px; 106 | text-align: center; 107 | list-style: none !important; 108 | cursor: default; 109 | } 110 | 111 | .icon_lists li .code-name { 112 | line-height: 1.2; 113 | } 114 | 115 | .icon_lists .icon { 116 | display: block; 117 | height: 100px; 118 | line-height: 100px; 119 | font-size: 42px; 120 | margin: 10px auto; 121 | color: #333; 122 | -webkit-transition: font-size 0.25s linear, width 0.25s linear; 123 | -moz-transition: font-size 0.25s linear, width 0.25s linear; 124 | transition: font-size 0.25s linear, width 0.25s linear; 125 | } 126 | 127 | .icon_lists .icon:hover { 128 | font-size: 100px; 129 | } 130 | 131 | .icon_lists .svg-icon { 132 | /* 通过设置 font-size 来改变图标大小 */ 133 | width: 1em; 134 | /* 图标和文字相邻时,垂直对齐 */ 135 | vertical-align: -0.15em; 136 | /* 通过设置 color 来改变 SVG 的颜色/fill */ 137 | fill: currentColor; 138 | /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示 139 | normalize.css 中也包含这行 */ 140 | overflow: hidden; 141 | } 142 | 143 | .icon_lists li .name, 144 | .icon_lists li .code-name { 145 | color: #666; 146 | } 147 | 148 | /* markdown 样式 */ 149 | .markdown { 150 | color: #666; 151 | font-size: 14px; 152 | line-height: 1.8; 153 | } 154 | 155 | .highlight { 156 | line-height: 1.5; 157 | } 158 | 159 | .markdown img { 160 | vertical-align: middle; 161 | max-width: 100%; 162 | } 163 | 164 | .markdown h1 { 165 | color: #404040; 166 | font-weight: 500; 167 | line-height: 40px; 168 | margin-bottom: 24px; 169 | } 170 | 171 | .markdown h2, 172 | .markdown h3, 173 | .markdown h4, 174 | .markdown h5, 175 | .markdown h6 { 176 | color: #404040; 177 | margin: 1.6em 0 0.6em 0; 178 | font-weight: 500; 179 | clear: both; 180 | } 181 | 182 | .markdown h1 { 183 | font-size: 28px; 184 | } 185 | 186 | .markdown h2 { 187 | font-size: 22px; 188 | } 189 | 190 | .markdown h3 { 191 | font-size: 16px; 192 | } 193 | 194 | .markdown h4 { 195 | font-size: 14px; 196 | } 197 | 198 | .markdown h5 { 199 | font-size: 12px; 200 | } 201 | 202 | .markdown h6 { 203 | font-size: 12px; 204 | } 205 | 206 | .markdown hr { 207 | height: 1px; 208 | border: 0; 209 | background: #e9e9e9; 210 | margin: 16px 0; 211 | clear: both; 212 | } 213 | 214 | .markdown p { 215 | margin: 1em 0; 216 | } 217 | 218 | .markdown>p, 219 | .markdown>blockquote, 220 | .markdown>.highlight, 221 | .markdown>ol, 222 | .markdown>ul { 223 | width: 80%; 224 | } 225 | 226 | .markdown ul>li { 227 | list-style: circle; 228 | } 229 | 230 | .markdown>ul li, 231 | .markdown blockquote ul>li { 232 | margin-left: 20px; 233 | padding-left: 4px; 234 | } 235 | 236 | .markdown>ul li p, 237 | .markdown>ol li p { 238 | margin: 0.6em 0; 239 | } 240 | 241 | .markdown ol>li { 242 | list-style: decimal; 243 | } 244 | 245 | .markdown>ol li, 246 | .markdown blockquote ol>li { 247 | margin-left: 20px; 248 | padding-left: 4px; 249 | } 250 | 251 | .markdown code { 252 | margin: 0 3px; 253 | padding: 0 5px; 254 | background: #eee; 255 | border-radius: 3px; 256 | } 257 | 258 | .markdown strong, 259 | .markdown b { 260 | font-weight: 600; 261 | } 262 | 263 | .markdown>table { 264 | border-collapse: collapse; 265 | border-spacing: 0px; 266 | empty-cells: show; 267 | border: 1px solid #e9e9e9; 268 | width: 95%; 269 | margin-bottom: 24px; 270 | } 271 | 272 | .markdown>table th { 273 | white-space: nowrap; 274 | color: #333; 275 | font-weight: 600; 276 | } 277 | 278 | .markdown>table th, 279 | .markdown>table td { 280 | border: 1px solid #e9e9e9; 281 | padding: 8px 16px; 282 | text-align: left; 283 | } 284 | 285 | .markdown>table th { 286 | background: #F7F7F7; 287 | } 288 | 289 | .markdown blockquote { 290 | font-size: 90%; 291 | color: #999; 292 | border-left: 4px solid #e9e9e9; 293 | padding-left: 0.8em; 294 | margin: 1em 0; 295 | } 296 | 297 | .markdown blockquote p { 298 | margin: 0; 299 | } 300 | 301 | .markdown .anchor { 302 | opacity: 0; 303 | transition: opacity 0.3s ease; 304 | margin-left: 8px; 305 | } 306 | 307 | .markdown .waiting { 308 | color: #ccc; 309 | } 310 | 311 | .markdown h1:hover .anchor, 312 | .markdown h2:hover .anchor, 313 | .markdown h3:hover .anchor, 314 | .markdown h4:hover .anchor, 315 | .markdown h5:hover .anchor, 316 | .markdown h6:hover .anchor { 317 | opacity: 1; 318 | display: inline-block; 319 | } 320 | 321 | .markdown>br, 322 | .markdown>p>br { 323 | clear: both; 324 | } 325 | 326 | 327 | .hljs { 328 | display: block; 329 | background: white; 330 | padding: 0.5em; 331 | color: #333333; 332 | overflow-x: auto; 333 | } 334 | 335 | .hljs-comment, 336 | .hljs-meta { 337 | color: #969896; 338 | } 339 | 340 | .hljs-string, 341 | .hljs-variable, 342 | .hljs-template-variable, 343 | .hljs-strong, 344 | .hljs-emphasis, 345 | .hljs-quote { 346 | color: #df5000; 347 | } 348 | 349 | .hljs-keyword, 350 | .hljs-selector-tag, 351 | .hljs-type { 352 | color: #a71d5d; 353 | } 354 | 355 | .hljs-literal, 356 | .hljs-symbol, 357 | .hljs-bullet, 358 | .hljs-attribute { 359 | color: #0086b3; 360 | } 361 | 362 | .hljs-section, 363 | .hljs-name { 364 | color: #63a35c; 365 | } 366 | 367 | .hljs-tag { 368 | color: #333333; 369 | } 370 | 371 | .hljs-title, 372 | .hljs-attr, 373 | .hljs-selector-id, 374 | .hljs-selector-class, 375 | .hljs-selector-attr, 376 | .hljs-selector-pseudo { 377 | color: #795da3; 378 | } 379 | 380 | .hljs-addition { 381 | color: #55a532; 382 | background-color: #eaffea; 383 | } 384 | 385 | .hljs-deletion { 386 | color: #bd2c00; 387 | background-color: #ffecec; 388 | } 389 | 390 | .hljs-link { 391 | text-decoration: underline; 392 | } 393 | 394 | /* 代码高亮 */ 395 | /* PrismJS 1.15.0 396 | https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */ 397 | /** 398 | * prism.js default theme for JavaScript, CSS and HTML 399 | * Based on dabblet (http://dabblet.com) 400 | * @author Lea Verou 401 | */ 402 | code[class*="language-"], 403 | pre[class*="language-"] { 404 | color: black; 405 | background: none; 406 | text-shadow: 0 1px white; 407 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 408 | text-align: left; 409 | white-space: pre; 410 | word-spacing: normal; 411 | word-break: normal; 412 | word-wrap: normal; 413 | line-height: 1.5; 414 | 415 | -moz-tab-size: 4; 416 | -o-tab-size: 4; 417 | tab-size: 4; 418 | 419 | -webkit-hyphens: none; 420 | -moz-hyphens: none; 421 | -ms-hyphens: none; 422 | hyphens: none; 423 | } 424 | 425 | pre[class*="language-"]::-moz-selection, 426 | pre[class*="language-"] ::-moz-selection, 427 | code[class*="language-"]::-moz-selection, 428 | code[class*="language-"] ::-moz-selection { 429 | text-shadow: none; 430 | background: #b3d4fc; 431 | } 432 | 433 | pre[class*="language-"]::selection, 434 | pre[class*="language-"] ::selection, 435 | code[class*="language-"]::selection, 436 | code[class*="language-"] ::selection { 437 | text-shadow: none; 438 | background: #b3d4fc; 439 | } 440 | 441 | @media print { 442 | 443 | code[class*="language-"], 444 | pre[class*="language-"] { 445 | text-shadow: none; 446 | } 447 | } 448 | 449 | /* Code blocks */ 450 | pre[class*="language-"] { 451 | padding: 1em; 452 | margin: .5em 0; 453 | overflow: auto; 454 | } 455 | 456 | :not(pre)>code[class*="language-"], 457 | pre[class*="language-"] { 458 | background: #f5f2f0; 459 | } 460 | 461 | /* Inline code */ 462 | :not(pre)>code[class*="language-"] { 463 | padding: .1em; 464 | border-radius: .3em; 465 | white-space: normal; 466 | } 467 | 468 | .token.comment, 469 | .token.prolog, 470 | .token.doctype, 471 | .token.cdata { 472 | color: slategray; 473 | } 474 | 475 | .token.punctuation { 476 | color: #999; 477 | } 478 | 479 | .namespace { 480 | opacity: .7; 481 | } 482 | 483 | .token.property, 484 | .token.tag, 485 | .token.boolean, 486 | .token.number, 487 | .token.constant, 488 | .token.symbol, 489 | .token.deleted { 490 | color: #905; 491 | } 492 | 493 | .token.selector, 494 | .token.attr-name, 495 | .token.string, 496 | .token.char, 497 | .token.builtin, 498 | .token.inserted { 499 | color: #690; 500 | } 501 | 502 | .token.operator, 503 | .token.entity, 504 | .token.url, 505 | .language-css .token.string, 506 | .style .token.string { 507 | color: #9a6e3a; 508 | background: hsla(0, 0%, 100%, .5); 509 | } 510 | 511 | .token.atrule, 512 | .token.attr-value, 513 | .token.keyword { 514 | color: #07a; 515 | } 516 | 517 | .token.function, 518 | .token.class-name { 519 | color: #DD4A68; 520 | } 521 | 522 | .token.regex, 523 | .token.important, 524 | .token.variable { 525 | color: #e90; 526 | } 527 | 528 | .token.important, 529 | .token.bold { 530 | font-weight: bold; 531 | } 532 | 533 | .token.italic { 534 | font-style: italic; 535 | } 536 | 537 | .token.entity { 538 | cursor: help; 539 | } 540 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('iconfont.eot?t=1558080209229'); /* IE9 */ 3 | src: url('iconfont.eot?t=1558080209229#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), 5 | url('iconfont.woff?t=1558080209229') format('woff'), 6 | url('iconfont.ttf?t=1558080209229') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('iconfont.svg?t=1558080209229#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family: "iconfont" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-kaquanguanli:before { 19 | content: "\e616"; 20 | } 21 | 22 | .icon-huiyuan:before { 23 | content: "\e617"; 24 | } 25 | 26 | .icon-shangchengguanli:before { 27 | content: "\e618"; 28 | } 29 | 30 | .icon-zuzhiyushouquan:before { 31 | content: "\e619"; 32 | } 33 | 34 | .icon-jiaoyiguanli:before { 35 | content: "\e61a"; 36 | } 37 | 38 | .icon-huodongguanli:before { 39 | content: "\e61b"; 40 | } 41 | 42 | .icon-zhifuguanli:before { 43 | content: "\e61c"; 44 | } 45 | 46 | .icon-gongzhonghaopeizhi:before { 47 | content: "\e61d"; 48 | } 49 | 50 | .icon-neirongguanli:before { 51 | content: "\e61e"; 52 | } 53 | 54 | .icon-jichuxinxi:before { 55 | content: "\e61f"; 56 | } 57 | 58 | .icon-qudaoguanli:before { 59 | content: "\e620"; 60 | } 61 | 62 | .icon-duanxinpingtai:before { 63 | content: "\e621"; 64 | } 65 | 66 | .icon-tongdaoguanli:before { 67 | content: "\e622"; 68 | } 69 | 70 | .icon-yingxiaoduanxin:before { 71 | content: "\e623"; 72 | } 73 | 74 | .icon-duanxinyingxiao:before { 75 | content: "\e624"; 76 | } 77 | 78 | .icon-jichuziliao:before { 79 | content: "\e625"; 80 | } 81 | 82 | .icon-pingtaiguanli:before { 83 | content: "\e626"; 84 | } 85 | 86 | .icon-xiaochengxv:before { 87 | content: "\e627"; 88 | } 89 | 90 | .icon-zhuanxiangfenxi:before { 91 | content: "\e628"; 92 | } 93 | 94 | .icon-jiesuanxitong:before { 95 | content: "\e629"; 96 | } 97 | 98 | .icon-yingxiaotuiguang:before { 99 | content: "\e62a"; 100 | } 101 | 102 | .icon-huiyuanjifen:before { 103 | content: "\e62b"; 104 | } 105 | 106 | .icon-huiyuanbiaoqian:before { 107 | content: "\e62c"; 108 | } 109 | 110 | .icon-liebianguanli:before { 111 | content: "\e62d"; 112 | } 113 | 114 | .icon-yingxiaozhixing:before { 115 | content: "\e62e"; 116 | } 117 | 118 | .icon-jingzhunyingxiao:before { 119 | content: "\e62f"; 120 | } 121 | 122 | .icon-huiyuanquanyi:before { 123 | content: "\e630"; 124 | } 125 | 126 | .icon-wandayaochiri:before { 127 | content: "\e631"; 128 | } 129 | 130 | .icon-huiyuandengji:before { 131 | content: "\e632"; 132 | } 133 | 134 | .icon-huiyuanguanli:before { 135 | content: "\e633"; 136 | } 137 | 138 | .icon-yingxiaocehua:before { 139 | content: "\e634"; 140 | } 141 | 142 | .icon-gonggaoguanli:before { 143 | content: "\e635"; 144 | } 145 | 146 | .icon-shangpinguanli:before { 147 | content: "\e636"; 148 | } 149 | 150 | .icon-shanghuguanli:before { 151 | content: "\e637"; 152 | } 153 | 154 | .icon-huiyuandingyi:before { 155 | content: "\e638"; 156 | } 157 | 158 | .icon-huiyuanfenxi:before { 159 | content: "\e639"; 160 | } 161 | 162 | .icon-huiyuankashezhi:before { 163 | content: "\e63a"; 164 | } 165 | 166 | .icon-zengzhibiaoqian:before { 167 | content: "\e63b"; 168 | } 169 | 170 | .icon-jingyinggaikuang:before { 171 | content: "\e63c"; 172 | } 173 | 174 | .icon-waibuyonghuguanli:before { 175 | content: "\e63d"; 176 | } 177 | 178 | .icon-shezhi:before { 179 | content: "\e63e"; 180 | } 181 | 182 | .icon-dakaguanli:before { 183 | content: "\e63f"; 184 | } 185 | 186 | .icon-huiyuantuozhan:before { 187 | content: "\e640"; 188 | } 189 | 190 | .icon-libao:before { 191 | content: "\e641"; 192 | } 193 | 194 | .icon-shijianguanli:before { 195 | content: "\e642"; 196 | } 197 | 198 | .icon-qiandaohuodong:before { 199 | content: "\e643"; 200 | } 201 | 202 | .icon-shouye:before { 203 | content: "\e644"; 204 | } 205 | 206 | .icon-hetongguanli:before { 207 | content: "\e645"; 208 | } 209 | 210 | .icon-mendianruzhu:before { 211 | content: "\e646"; 212 | } 213 | 214 | .icon-choujiang:before { 215 | content: "\e647"; 216 | } 217 | 218 | .icon-wanpuxiaoer:before { 219 | content: "\e648"; 220 | } 221 | 222 | .icon-tingchechangguanli:before { 223 | content: "\e649"; 224 | } 225 | 226 | .icon-jiesuanguanli:before { 227 | content: "\e64a"; 228 | } 229 | 230 | .icon-mendianguanli:before { 231 | content: "\e64b"; 232 | } 233 | 234 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adouwt/vue-cli3-admin-system/6cd999169d5fc0b8322487f5f2093f2ad6a0fb87/src/assets/iconfont/iconfont.eot -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adouwt/vue-cli3-admin-system/6cd999169d5fc0b8322487f5f2093f2ad6a0fb87/src/assets/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adouwt/vue-cli3-admin-system/6cd999169d5fc0b8322487f5f2093f2ad6a0fb87/src/assets/iconfont/iconfont.woff -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adouwt/vue-cli3-admin-system/6cd999169d5fc0b8322487f5f2093f2ad6a0fb87/src/assets/iconfont/iconfont.woff2 -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adouwt/vue-cli3-admin-system/6cd999169d5fc0b8322487f5f2093f2ad6a0fb87/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/Breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 40 | 41 | 53 | -------------------------------------------------------------------------------- /src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 31 | 32 | 46 | -------------------------------------------------------------------------------- /src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 33 | 34 | 43 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App.vue"; 3 | import router from "./router"; 4 | import store from "./store"; 5 | // reset CSS 6 | import "normalize.css/normalize.css"; 7 | 8 | import ElementUI from "element-ui"; 9 | import "element-ui/lib/theme-chalk/index.css"; 10 | import "./assets/iconfont/iconfont.css" // 简化处理时候 只用element-UI 的图标库,项目大的时候 改成vue-awesome 11 | import '@/styles/index.scss' // global css 12 | import './permisssion' // permission control 13 | 14 | Vue.use(ElementUI); 15 | Vue.config.productionTip = false; 16 | 17 | new Vue({ 18 | router, 19 | store, 20 | render: h => h(App) 21 | }).$mount("#app"); 22 | -------------------------------------------------------------------------------- /src/permisssion.js: -------------------------------------------------------------------------------- 1 | import router from './router' 2 | import store from './store' 3 | import { Message } from 'element-ui' 4 | import NProgress from 'nprogress' // progress bar 5 | import 'nprogress/nprogress.css'// progress bar style 6 | import { getToken } from '@/utils/auth' // getToken from cookie 7 | 8 | NProgress.configure({ showSpinner: false })// NProgress Configuration 9 | 10 | // permission judge function 11 | function hasPermission(roles, permissionRoles) { 12 | if (roles.indexOf('admin') >= 0) { 13 | return true // 管理员直接通过 14 | } 15 | if (!permissionRoles) { 16 | return true // 如果没有权限限制 直接通过 17 | } 18 | return roles.some(role => permissionRoles.indexOf(role) >= 0) 19 | } 20 | 21 | const whiteList = ['/login', '/auth-redirect', '/register']// 没有限制登陆要求的页面 22 | 23 | router.beforeEach((to, from, next) => { 24 | NProgress.start() // start progress bar 25 | if (getToken()) { // 存在登陆信息 26 | if (to.path === '/login') { 27 | next({ path: '/' }) // 登陆 && 登陆界面 28 | NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it 29 | } else { 30 | // console.log(store.getters.roles.length) 31 | if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息 32 | store.dispatch('GetUserInfo').then(res => { // 拉取user_info 33 | const roles = res.data.roles // note: roles must be a array! such as: ['editor','develop'] 34 | store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表 35 | router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表 36 | next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record 37 | }) 38 | }).catch((err) => { 39 | console.log(err, 'reqwrqweerr2') 40 | store.dispatch('FedLogOut').then(() => { 41 | Message.error(err || 'Verification failed, please login again') 42 | next({ path: '/' }) 43 | }) 44 | }) 45 | } else { 46 | // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓ 47 | if (hasPermission(store.getters.roles, to.meta.roles)) { 48 | next() 49 | } else { 50 | next({ path: '/401', replace: true, query: { noGoBack: true }}) 51 | } 52 | // 可删 ↑ 53 | } 54 | } 55 | } else { 56 | /* has no token*/ 57 | if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入 58 | next() 59 | } else { 60 | next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页 61 | NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it 62 | } 63 | } 64 | }) 65 | 66 | router.afterEach(() => { 67 | NProgress.done() // finish progress bar 68 | }) 69 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | /* Layout */ 4 | import Layout from './views/layout/Layout' 5 | // 异步加载 按需加载 6 | const TableIndex = () => import(/* webpackChunkName: "TableIndex" */ '@/views/table/index') 7 | const TreeIndex = () => import(/* webpackChunkName: "TreeIndex" */ '@/views/tree/index') 8 | 9 | // 同步加载 按需加载 10 | // const TechnologyIndex = require(/* webpackChunkName: "TechnologyIndex" */ '@/views/display/technology') 11 | const TechnologyIndex = () => import(/* webpackChunkName: "TechnologyIndex" */ '@/views/display/technology') 12 | // const FictionIndex = require(/* webpackChunkName: "FictionIndex" */ '@/views/display/fiction') 13 | const FictionIndex = () => import(/* webpackChunkName: "FictionIndex" */ '@/views/display/fiction') 14 | 15 | const MyIndex = () => import(/* webpackChunkName: "MyIndex" */ '@/views/center/index') 16 | 17 | Vue.use(Router) 18 | // hidden: false, 控制是否在左侧导航显示,用作过滤 19 | 20 | export const asyncRouterMap = [ 21 | { 22 | path: '/admin/manage-users', 23 | component: Layout, 24 | meta: {roles: ['admin', 'boss']}, 25 | hidden: false, 26 | children: [{ 27 | path: '/admin/manage-users', 28 | name: 'ManageUsers', 29 | component: () => import('@/views/admin/manage-users'), 30 | meta: { title: 'manage-users', icon: 'icon-huiyuan' } 31 | }] 32 | }, 33 | { 34 | path: '/admin/manage-money', 35 | component: Layout, 36 | meta: {roles: ['admin', 'boss']}, 37 | hidden: false, 38 | children: [ 39 | { 40 | path: '/admin/manage-money', 41 | name: 'ManageMoney', 42 | component: () => import('@/views/admin/manage-money'), 43 | meta: { title: 'Manage-money', icon: 'icon-huiyuanjifen' } 44 | } 45 | ] 46 | }, 47 | { path: '*', redirect: '/404', hidden: true } 48 | ]; 49 | 50 | export const constantRouterMap = [ 51 | { path: '/login', component: () => import('@/views/login/index'), hidden: true, roles: 'all' }, 52 | { path: '/register', component: () => import('@/views/register/index'), hidden: true, roles: 'all' }, 53 | { 54 | path: '/', 55 | component: Layout, 56 | redirect: '/dashboard', 57 | name: 'Dashboard', 58 | hidden: true, 59 | roles: 'all', 60 | children: [{ 61 | path: 'dashboard', 62 | component: () => import('@/views/dashboard/index'), 63 | }] 64 | }, 65 | { 66 | path: '/example', 67 | component: Layout, 68 | redirect: '/example/table', 69 | name: 'Example', 70 | // meta: { title: 'Example', icon: 'example' }, 71 | meta: { title: 'Example', icon: 'icon-qudaoguanli' }, 72 | roles: 'dev', 73 | children: [ 74 | { 75 | path: 'table', 76 | name: 'Table', 77 | component: TableIndex, 78 | meta: { title: 'Table', icon: 'icon-shoujitianchong' } 79 | }, 80 | { 81 | path: 'tree', 82 | name: 'Tree', 83 | component: TreeIndex, 84 | // meta: { title: 'Tree', icon: 'tree' } 85 | meta: { title: 'Tree', icon: 'icon-gengduotianchong' } 86 | } 87 | /** 88 | * 多个子路由,某些子路由用户有权限,某些没有权限,因此role 要放在最内层的 的children里 89 | * 这里简化处理,暂不这样做 90 | * */ 91 | ] 92 | }, 93 | 94 | { 95 | path: '/form', 96 | component: Layout, 97 | children: [ 98 | { 99 | path: 'index', 100 | name: 'Edit', 101 | component: () => import('@/views/edit/index'), 102 | meta: { title: 'edit', icon: 'icon-huiyuandingyi' } 103 | } 104 | ] 105 | }, 106 | 107 | { 108 | path: '/nested', 109 | component: Layout, 110 | redirect: '/nested/menu1', 111 | name: 'nested', 112 | meta: { 113 | title: 'nested', 114 | icon: 'icon-shijianguanli' 115 | }, 116 | children: [ 117 | { 118 | path: 'menu1', 119 | component: () => import('@/views/nested/menu1/index'), // Parent router-view 120 | name: 'menu1', 121 | meta: { title: 'menu1' }, 122 | children: [ 123 | { 124 | path: 'menu1-1', 125 | component: () => import('@/views/nested/menu1/menu1-1'), 126 | name: 'menu1-1', 127 | meta: { title: 'menu1-1' } 128 | }, 129 | { 130 | path: 'menu1-2', 131 | component: () => import('@/views/nested/menu1/menu1-2'), 132 | name: 'menu1-2', 133 | meta: { title: 'menu1-2' }, 134 | children: [ 135 | { 136 | path: 'menu1-2-1', 137 | component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'), 138 | name: 'menu1-2-1', 139 | meta: { title: 'menu1-2-1' } 140 | }, 141 | { 142 | path: 'menu1-2-2', 143 | component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'), 144 | name: 'menu1-2-2', 145 | meta: { title: 'menu1-2-2' } 146 | } 147 | ] 148 | }, 149 | { 150 | path: 'menu1-3', 151 | component: () => import('@/views/nested/menu1/menu1-3'), 152 | name: 'menu1-3', 153 | meta: { title: 'menu1-3' } 154 | } 155 | ] 156 | }, 157 | { 158 | path: 'menu2', 159 | component: () => import('@/views/nested/menu2/index'), 160 | meta: { title: 'menu2' } 161 | } 162 | ] 163 | }, 164 | { 165 | path: '/display', 166 | component: Layout, 167 | redirect: '/display/tech', 168 | name: 'Display', 169 | // meta: { title: 'Example', icon: 'example' }, 170 | meta: { title: 'display', icon: 'icon-yingxiaocehua' }, 171 | role: 'dev', 172 | children: [ 173 | { 174 | path: 'technology', 175 | name: 'Technology', 176 | component: TechnologyIndex, 177 | meta: { title: 'technology', icon: 'icon-shoujitianchong' } 178 | }, 179 | { 180 | path: 'fiction', 181 | name: 'Fiction', 182 | component: FictionIndex, 183 | meta: { title: 'Fiction', icon: 'icon-gengduotianchong' } 184 | } 185 | ] 186 | }, 187 | { 188 | path: '/my', 189 | component: Layout, 190 | name: 'personalPage', 191 | hidden: true, // 左侧路由导航会遍历这个名称,如果没有,就不会显示在左侧导航表中 192 | children: [ 193 | { 194 | path: '', 195 | // name: '个人中心', 196 | component: MyIndex, 197 | // meta: { title: '个人中心', icon: 'icon-huiyuandingyi' } 198 | } 199 | ] 200 | }, 201 | { path: '/404', component: () => import('@/views/404'), hidden: true, role: 'all' }, 202 | ] 203 | 204 | export default new Router({ 205 | mode: "history", 206 | scrollBehavior: () => ({ y: 0 }), 207 | routes: constantRouterMap 208 | }); -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | sidebar: state => state.app.sidebar, 3 | device: state => state.app.device, 4 | token: state => state.user.token, 5 | avatar: state => state.user.avatar, 6 | name: state => state.user.name, 7 | roles: state => state.user.roles, 8 | _id: state => state.user._id, 9 | permission_routers: state => state.permission.routers, 10 | addRouters: state => state.permission.addRouters, 11 | } 12 | export default getters 13 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import app from './modules/app' 4 | import user from './modules/user' 5 | import admin from './modules/admin' 6 | import getters from './getters' 7 | import permission from './routePermission' 8 | 9 | Vue.use(Vuex) 10 | const store = new Vuex.Store({ 11 | modules: { 12 | permission, 13 | app, 14 | user, 15 | admin 16 | }, 17 | getters 18 | }) 19 | 20 | export default store 21 | -------------------------------------------------------------------------------- /src/store/modules/admin.js: -------------------------------------------------------------------------------- 1 | import { getAllUser, GetAllUserFromPage, deleteOneUser, updateSomeOneRole } from '@/api/admin' 2 | /** eslint disabled */ 3 | const admin = { 4 | state: { 5 | }, 6 | 7 | mutations: { 8 | }, 9 | 10 | actions: { 11 | // 获取所有用户 12 | GetAllUser({ commit }, {page}) { 13 | return new Promise((resolve, reject) => { 14 | getAllUser(page).then(response => { 15 | resolve(response) 16 | }).catch(error => { 17 | reject(error) 18 | }) 19 | }) 20 | }, 21 | // 获取所有用户(分页获取) 22 | GetAllUserFromPage({ commit }, {page, skip}) { 23 | return new Promise((resolve, reject) => { 24 | console.log(page, skip) 25 | GetAllUserFromPage(page, skip).then(response => { 26 | resolve(response) 27 | }).catch(error => { 28 | reject(error) 29 | }) 30 | }) 31 | }, 32 | // 删除用户 33 | DeleteOneUser ({ commit }, {id}) { 34 | return new Promise((resolve, reject) => { 35 | deleteOneUser(id).then(response => { 36 | resolve(response) 37 | }).catch(error => { 38 | reject(error) 39 | }) 40 | }) 41 | }, 42 | // 修改用户 43 | UpdateSomeOneRole ({ commit }, {id, roles}) { 44 | console.log(roles) 45 | return new Promise((resolve, reject) => { 46 | updateSomeOneRole(id, roles).then(response => { 47 | resolve(response) 48 | }).catch(error => { 49 | reject(error) 50 | }) 51 | }) 52 | }, 53 | } 54 | } 55 | 56 | export default admin 57 | -------------------------------------------------------------------------------- /src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const app = { 4 | state: { 5 | sidebar: { 6 | opened: !+Cookies.get('sidebarStatus'), 7 | withoutAnimation: false 8 | }, 9 | device: 'desktop' 10 | }, 11 | mutations: { 12 | TOGGLE_SIDEBAR: state => { 13 | if (state.sidebar.opened) { 14 | Cookies.set('sidebarStatus', 1) 15 | } else { 16 | Cookies.set('sidebarStatus', 0) 17 | } 18 | state.sidebar.opened = !state.sidebar.opened 19 | state.sidebar.withoutAnimation = false 20 | }, 21 | CLOSE_SIDEBAR: (state, withoutAnimation) => { 22 | Cookies.set('sidebarStatus', 1) 23 | state.sidebar.opened = false 24 | state.sidebar.withoutAnimation = withoutAnimation 25 | }, 26 | TOGGLE_DEVICE: (state, device) => { 27 | state.device = device 28 | } 29 | }, 30 | actions: { 31 | ToggleSideBar: ({ commit }) => { 32 | commit('TOGGLE_SIDEBAR') 33 | }, 34 | CloseSideBar({ commit }, { withoutAnimation }) { 35 | commit('CLOSE_SIDEBAR', withoutAnimation) 36 | }, 37 | ToggleDevice({ commit }, device) { 38 | commit('TOGGLE_DEVICE', device) 39 | } 40 | } 41 | } 42 | 43 | export default app 44 | -------------------------------------------------------------------------------- /src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import { adminRegister, register, login, logout, getInfo ,sendEmail, uploadImg} from '@/api/login' 2 | import { getToken, setToken, removeToken } from '@/utils/auth' 3 | /** eslint disabled */ 4 | const user = { 5 | state: { 6 | token: getToken(), 7 | name: '', 8 | avatar: '', 9 | roles: [], 10 | roleRouters: [] 11 | }, 12 | 13 | mutations: { 14 | SET_TOKEN: (state, token) => { 15 | state.token = token 16 | }, 17 | SET_NAME: (state, name) => { 18 | state.name = name 19 | }, 20 | SET_AVATAR: (state, avatar) => { 21 | state.avatar = avatar 22 | }, 23 | SET_ROLES: (state, roles) => { 24 | state.roles = roles 25 | }, 26 | SET_USERID: (state, _id) => { 27 | state._id = _id 28 | }, 29 | SET_ROLE_ROUTERS: (state, roleRouters) => { 30 | state.roleRouters = roleRouters 31 | } 32 | }, 33 | 34 | actions: { 35 | // 发送验证码 36 | SendEmail({ commit }, email) { 37 | console.log(email,'email') 38 | return new Promise((resolve, reject) => { 39 | sendEmail(email).then(response => { 40 | resolve(response) 41 | }).catch(error => { 42 | console.log(error) 43 | reject(error) 44 | }) 45 | }) 46 | }, 47 | // 注册 48 | Register({ commit }, userInfo) { 49 | const username = userInfo.username.trim() 50 | const roles = userInfo.roles 51 | const type = userInfo.type.trim() 52 | const email = userInfo.email 53 | const registerCode = userInfo.registerCode 54 | return new Promise((resolve, reject) => { 55 | register(username, userInfo.password, type, roles, email, registerCode).then(response => { 56 | // then 这里接收到的只要成功的提示,失败的情况已经在拦截器里面处理 57 | // console.log('actions', response) 58 | setToken(response.token) // 注册成功直接登陆 使用 59 | commit('SET_TOKEN', response.token) // 注册成功直接登陆 使用 60 | resolve(response) 61 | }).catch(error => { 62 | console.log(error) 63 | reject(error) 64 | }) 65 | }) 66 | }, 67 | // 管理员添加用户 68 | adminRegister({ commit }, userInfo) { 69 | const username = userInfo.username.trim() 70 | const roles = userInfo.roles 71 | const age = userInfo.age.trim() 72 | return new Promise((resolve, reject) => { 73 | // 修改成传递一个对象过来,在源头处修改,省得在很多地方逐个添加参数 74 | adminRegister(username, userInfo.password, roles, age).then(response => { 75 | // then 这里接收到的只要成功的提示,失败的情况已经在拦截器里面处理 76 | // console.log('actions', response) 77 | // setToken(response.token) // 注册成功直接登陆 使用 78 | // commit('SET_TOKEN', response.token) // 注册成功直接登陆 使用 79 | resolve(response) 80 | }).catch(error => { 81 | console.log(error) 82 | reject(error) 83 | }) 84 | }) 85 | }, 86 | // 登录 87 | Login({ commit }, userInfo) { 88 | const username = userInfo.username.trim() 89 | return new Promise((resolve, reject) => { 90 | login(username, userInfo.password).then(response => { 91 | // then 这里接收到的只要成功的提示,失败的情况已经在拦截器里面处理 92 | setToken(response.token) 93 | commit('SET_TOKEN', response.token) 94 | resolve(response) 95 | }).catch(error => { 96 | reject(error) 97 | }) 98 | }) 99 | }, 100 | 101 | // 获取用户信息 102 | GetUserInfo({ commit, state }) { 103 | return new Promise((resolve, reject) => { 104 | getInfo(state.token).then(response => { 105 | // console.log(response) 106 | if (response.data.expired) { 107 | reject('登录已经过期,请重新登录!') 108 | } 109 | const data = response.data 110 | if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组 111 | commit('SET_ROLES', data.roles) 112 | } else { 113 | reject('getInfo: roles must be a non-null array !') 114 | } 115 | commit('SET_NAME', data.name) 116 | commit('SET_AVATAR', data.avatar_url) 117 | commit('SET_USERID', data._id) 118 | resolve(response) 119 | }).catch(error => { 120 | // console.log(error, '115') 121 | reject(error) 122 | }) 123 | }) 124 | }, 125 | 126 | UploadImage({commit}, {_id, file}) { 127 | // todo 过滤没有登录 128 | console.log(file) 129 | return new Promise((resolve, reject) => { 130 | uploadImg(_id, file) 131 | .then(response => { 132 | commit('SET_USERID', _id) 133 | resolve(response) 134 | }) 135 | .catch(err => { 136 | reject(err) 137 | }) 138 | }) 139 | }, 140 | // 登出 141 | LogOut({ commit, state }) { 142 | return new Promise((resolve, reject) => { 143 | logout(state.token).then(() => { 144 | console.log('out') 145 | commit('SET_TOKEN', '') 146 | commit('SET_ROLE', '') 147 | removeToken() 148 | resolve() 149 | }).catch(error => { 150 | reject(error) 151 | }) 152 | }) 153 | }, 154 | 155 | // 前端 登出 156 | FedLogOut({ commit }) { 157 | return new Promise(resolve => { 158 | commit('SET_TOKEN', '') 159 | removeToken() 160 | resolve() 161 | }) 162 | } 163 | } 164 | } 165 | 166 | export default user 167 | -------------------------------------------------------------------------------- /src/store/routePermission.js: -------------------------------------------------------------------------------- 1 | import { asyncRouterMap, constantRouterMap } from '@/router' 2 | 3 | /** 4 | * 通过meta.role判断是否与当前用户权限匹配 5 | * @param roles 6 | * @param route 7 | */ 8 | function hasPermission(roles, route) { 9 | if (route.meta && route.meta.roles) { 10 | return roles.some(role => route.meta.roles.includes(role)) 11 | } else { 12 | return true 13 | } 14 | } 15 | 16 | /** 17 | * 递归过滤异步路由表,返回符合用户角色权限的路由表 18 | * @param routes asyncRouterMap 19 | * @param roles 20 | */ 21 | function filterAsyncRouter(routes, roles) { 22 | const res = [] 23 | 24 | routes.forEach(route => { 25 | const tmp = { ...route } 26 | if (hasPermission(roles, tmp)) { 27 | if (tmp.children) { 28 | tmp.children = filterAsyncRouter(tmp.children, roles) 29 | } 30 | res.push(tmp) 31 | } 32 | }) 33 | 34 | return res 35 | } 36 | 37 | const permission = { 38 | state: { 39 | routers: constantRouterMap, 40 | addRouters: [] 41 | }, 42 | mutations: { 43 | SET_ROUTERS: (state, routers) => { 44 | state.addRouters = routers 45 | state.routers = constantRouterMap.concat(routers) 46 | } 47 | }, 48 | actions: { 49 | GenerateRoutes({ commit }, data) { 50 | return new Promise(resolve => { 51 | const { roles } = data 52 | let accessedRouters 53 | if (roles.includes('admin')) { 54 | accessedRouters = asyncRouterMap 55 | } else { 56 | accessedRouters = filterAsyncRouter(asyncRouterMap, roles) 57 | } 58 | commit('SET_ROUTERS', accessedRouters) 59 | resolve() 60 | }) 61 | } 62 | } 63 | } 64 | 65 | export default permission 66 | -------------------------------------------------------------------------------- /src/styles/element-ui.scss: -------------------------------------------------------------------------------- 1 | //to reset element-ui default css 2 | .el-upload { 3 | input[type="file"] { 4 | display: none !important; 5 | } 6 | } 7 | 8 | .el-upload__input { 9 | display: none; 10 | } 11 | 12 | //暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461 13 | .el-dialog { 14 | transform: none; 15 | left: 0; 16 | position: relative; 17 | margin: 0 auto; 18 | } 19 | 20 | //element ui upload 21 | .upload-container { 22 | .el-upload { 23 | width: 100%; 24 | .el-upload-dragger { 25 | width: 100%; 26 | height: 200px; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './variables.scss'; 2 | @import './transition.scss'; 3 | @import './element-ui.scss'; 4 | @import './sidebar.scss'; 5 | @import './normalize.scss'; 6 | 7 | body { 8 | height: 100%; 9 | -moz-osx-font-smoothing: grayscale; 10 | -webkit-font-smoothing: antialiased; 11 | text-rendering: optimizeLegibility; 12 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; 13 | } 14 | 15 | label { 16 | font-weight: 700; 17 | } 18 | 19 | html { 20 | height: 100%; 21 | box-sizing: border-box; 22 | } 23 | 24 | #app{ 25 | height: 100%; 26 | } 27 | 28 | *, 29 | *:before, 30 | *:after { 31 | box-sizing: inherit; 32 | } 33 | 34 | a, 35 | a:focus, 36 | a:hover { 37 | cursor: pointer; 38 | color: inherit; 39 | outline: none; 40 | text-decoration: none; 41 | } 42 | 43 | div:focus{ 44 | outline: none; 45 | } 46 | 47 | a:focus, 48 | a:active { 49 | outline: none; 50 | } 51 | 52 | a, 53 | a:focus, 54 | a:hover { 55 | cursor: pointer; 56 | color: inherit; 57 | text-decoration: none; 58 | } 59 | 60 | .clearfix { 61 | &:after { 62 | visibility: hidden; 63 | display: block; 64 | font-size: 0; 65 | content: " "; 66 | clear: both; 67 | height: 0; 68 | } 69 | } 70 | 71 | //main-container全局样式 72 | .app-main{ 73 | min-height: 100% 74 | } 75 | 76 | .app-container { 77 | padding: 20px; 78 | } 79 | -------------------------------------------------------------------------------- /src/styles/normalize.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in iOS. 9 | */ 10 | 11 | html { 12 | line-height: 1.15; /* 1 */ 13 | -webkit-text-size-adjust: 100%; /* 2 */ 14 | } 15 | 16 | /* Sections 17 | ========================================================================== */ 18 | 19 | /** 20 | * Remove the margin in all browsers. 21 | */ 22 | 23 | body { 24 | margin: 0; 25 | } 26 | 27 | /** 28 | * Render the `main` element consistently in IE. 29 | */ 30 | 31 | main { 32 | display: block; 33 | } 34 | 35 | /** 36 | * Correct the font size and margin on `h1` elements within `section` and 37 | * `article` contexts in Chrome, Firefox, and Safari. 38 | */ 39 | 40 | h1 { 41 | font-size: 2em; 42 | margin: 0.67em 0; 43 | } 44 | 45 | /* Grouping content 46 | ========================================================================== */ 47 | 48 | /** 49 | * 1. Add the correct box sizing in Firefox. 50 | * 2. Show the overflow in Edge and IE. 51 | */ 52 | 53 | hr { 54 | box-sizing: content-box; /* 1 */ 55 | height: 0; /* 1 */ 56 | overflow: visible; /* 2 */ 57 | } 58 | 59 | /** 60 | * 1. Correct the inheritance and scaling of font size in all browsers. 61 | * 2. Correct the odd `em` font sizing in all browsers. 62 | */ 63 | 64 | pre { 65 | font-family: monospace, monospace; /* 1 */ 66 | font-size: 1em; /* 2 */ 67 | } 68 | 69 | /* Text-level semantics 70 | ========================================================================== */ 71 | 72 | /** 73 | * Remove the gray background on active links in IE 10. 74 | */ 75 | 76 | a { 77 | background-color: transparent; 78 | } 79 | 80 | /** 81 | * 1. Remove the bottom border in Chrome 57- 82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 83 | */ 84 | 85 | abbr[title] { 86 | border-bottom: none; /* 1 */ 87 | text-decoration: underline; /* 2 */ 88 | text-decoration: underline dotted; /* 2 */ 89 | } 90 | 91 | /** 92 | * Add the correct font weight in Chrome, Edge, and Safari. 93 | */ 94 | 95 | b, 96 | strong { 97 | font-weight: bolder; 98 | } 99 | 100 | /** 101 | * 1. Correct the inheritance and scaling of font size in all browsers. 102 | * 2. Correct the odd `em` font sizing in all browsers. 103 | */ 104 | 105 | code, 106 | kbd, 107 | samp { 108 | font-family: monospace, monospace; /* 1 */ 109 | font-size: 1em; /* 2 */ 110 | } 111 | 112 | /** 113 | * Add the correct font size in all browsers. 114 | */ 115 | 116 | small { 117 | font-size: 80%; 118 | } 119 | 120 | /** 121 | * Prevent `sub` and `sup` elements from affecting the line height in 122 | * all browsers. 123 | */ 124 | 125 | sub, 126 | sup { 127 | font-size: 75%; 128 | line-height: 0; 129 | position: relative; 130 | vertical-align: baseline; 131 | } 132 | 133 | sub { 134 | bottom: -0.25em; 135 | } 136 | 137 | sup { 138 | top: -0.5em; 139 | } 140 | 141 | /* Embedded content 142 | ========================================================================== */ 143 | 144 | /** 145 | * Remove the border on images inside links in IE 10. 146 | */ 147 | 148 | img { 149 | border-style: none; 150 | } 151 | 152 | /* Forms 153 | ========================================================================== */ 154 | 155 | /** 156 | * 1. Change the font styles in all browsers. 157 | * 2. Remove the margin in Firefox and Safari. 158 | */ 159 | 160 | button, 161 | input, 162 | optgroup, 163 | select, 164 | textarea { 165 | font-family: inherit; /* 1 */ 166 | font-size: 100%; /* 1 */ 167 | line-height: 1.15; /* 1 */ 168 | margin: 0; /* 2 */ 169 | } 170 | 171 | /** 172 | * Show the overflow in IE. 173 | * 1. Show the overflow in Edge. 174 | */ 175 | 176 | button, 177 | input { /* 1 */ 178 | overflow: visible; 179 | } 180 | 181 | /** 182 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 183 | * 1. Remove the inheritance of text transform in Firefox. 184 | */ 185 | 186 | button, 187 | select { /* 1 */ 188 | text-transform: none; 189 | } 190 | 191 | /** 192 | * Correct the inability to style clickable types in iOS and Safari. 193 | */ 194 | 195 | button, 196 | [type="button"], 197 | [type="reset"], 198 | [type="submit"] { 199 | -webkit-appearance: button; 200 | } 201 | 202 | /** 203 | * Remove the inner border and padding in Firefox. 204 | */ 205 | 206 | button::-moz-focus-inner, 207 | [type="button"]::-moz-focus-inner, 208 | [type="reset"]::-moz-focus-inner, 209 | [type="submit"]::-moz-focus-inner { 210 | border-style: none; 211 | padding: 0; 212 | } 213 | 214 | /** 215 | * Restore the focus styles unset by the previous rule. 216 | */ 217 | 218 | button:-moz-focusring, 219 | [type="button"]:-moz-focusring, 220 | [type="reset"]:-moz-focusring, 221 | [type="submit"]:-moz-focusring { 222 | outline: 1px dotted ButtonText; 223 | } 224 | 225 | /** 226 | * Correct the padding in Firefox. 227 | */ 228 | 229 | fieldset { 230 | padding: 0.35em 0.75em 0.625em; 231 | } 232 | 233 | /** 234 | * 1. Correct the text wrapping in Edge and IE. 235 | * 2. Correct the color inheritance from `fieldset` elements in IE. 236 | * 3. Remove the padding so developers are not caught out when they zero out 237 | * `fieldset` elements in all browsers. 238 | */ 239 | 240 | legend { 241 | box-sizing: border-box; /* 1 */ 242 | color: inherit; /* 2 */ 243 | display: table; /* 1 */ 244 | max-width: 100%; /* 1 */ 245 | padding: 0; /* 3 */ 246 | white-space: normal; /* 1 */ 247 | } 248 | 249 | /** 250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 251 | */ 252 | 253 | progress { 254 | vertical-align: baseline; 255 | } 256 | 257 | /** 258 | * Remove the default vertical scrollbar in IE 10+. 259 | */ 260 | 261 | textarea { 262 | overflow: auto; 263 | } 264 | 265 | /** 266 | * 1. Add the correct box sizing in IE 10. 267 | * 2. Remove the padding in IE 10. 268 | */ 269 | 270 | [type="checkbox"], 271 | [type="radio"] { 272 | box-sizing: border-box; /* 1 */ 273 | padding: 0; /* 2 */ 274 | } 275 | 276 | /** 277 | * Correct the cursor style of increment and decrement buttons in Chrome. 278 | */ 279 | 280 | [type="number"]::-webkit-inner-spin-button, 281 | [type="number"]::-webkit-outer-spin-button { 282 | height: auto; 283 | } 284 | 285 | /** 286 | * 1. Correct the odd appearance in Chrome and Safari. 287 | * 2. Correct the outline style in Safari. 288 | */ 289 | 290 | [type="search"] { 291 | -webkit-appearance: textfield; /* 1 */ 292 | outline-offset: -2px; /* 2 */ 293 | } 294 | 295 | /** 296 | * Remove the inner padding in Chrome and Safari on macOS. 297 | */ 298 | 299 | [type="search"]::-webkit-search-decoration { 300 | -webkit-appearance: none; 301 | } 302 | 303 | /** 304 | * 1. Correct the inability to style clickable types in iOS and Safari. 305 | * 2. Change font properties to `inherit` in Safari. 306 | */ 307 | 308 | ::-webkit-file-upload-button { 309 | -webkit-appearance: button; /* 1 */ 310 | font: inherit; /* 2 */ 311 | } 312 | 313 | /* Interactive 314 | ========================================================================== */ 315 | 316 | /* 317 | * Add the correct display in Edge, IE 10+, and Firefox. 318 | */ 319 | 320 | details { 321 | display: block; 322 | } 323 | 324 | /* 325 | * Add the correct display in all browsers. 326 | */ 327 | 328 | summary { 329 | display: list-item; 330 | } 331 | 332 | /* Misc 333 | ========================================================================== */ 334 | 335 | /** 336 | * Add the correct display in IE 10+. 337 | */ 338 | 339 | template { 340 | display: none; 341 | } 342 | 343 | /** 344 | * Add the correct display in IE 10. 345 | */ 346 | 347 | [hidden] { 348 | display: none; 349 | } 350 | -------------------------------------------------------------------------------- /src/styles/sidebar.scss: -------------------------------------------------------------------------------- 1 | #app { 2 | // 主体区域 3 | .main-container { 4 | min-height: 100%; 5 | transition: margin-left .28s; 6 | margin-left: 180px; 7 | position: relative; 8 | } 9 | // 侧边栏 10 | .sidebar-container { 11 | transition: width 0.28s; 12 | width: 180px !important; 13 | height: 100%; 14 | position: fixed; 15 | font-size: 0px; 16 | top: 0; 17 | bottom: 0; 18 | left: 0; 19 | z-index: 1001; 20 | overflow: hidden; 21 | //reset element-ui css 22 | .horizontal-collapse-transition { 23 | transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; 24 | } 25 | .scrollbar-wrapper { 26 | height: calc(100% + 15px); 27 | .el-scrollbar__view { 28 | height: 100%; 29 | } 30 | } 31 | .is-horizontal { 32 | display: none; 33 | } 34 | a { 35 | display: inline-block; 36 | width: 100%; 37 | overflow: hidden; 38 | } 39 | .svg-icon { 40 | margin-right: 16px; 41 | } 42 | .el-menu { 43 | border: none; 44 | height: 100%; 45 | width: 100% !important; 46 | } 47 | } 48 | .hideSidebar { 49 | .sidebar-container { 50 | width: 36px !important; 51 | } 52 | .main-container { 53 | margin-left: 36px; 54 | } 55 | .submenu-title-noDropdown { 56 | padding-left: 10px !important; 57 | position: relative; 58 | .el-tooltip { 59 | padding: 0 10px !important; 60 | } 61 | } 62 | .el-submenu { 63 | overflow: hidden; 64 | &>.el-submenu__title { 65 | padding-left: 10px !important; 66 | .el-submenu__icon-arrow { 67 | display: none; 68 | } 69 | } 70 | } 71 | .el-menu--collapse { 72 | .el-submenu { 73 | &>.el-submenu__title { 74 | &>span { 75 | height: 0; 76 | width: 0; 77 | overflow: hidden; 78 | visibility: hidden; 79 | display: inline-block; 80 | } 81 | } 82 | } 83 | } 84 | } 85 | .sidebar-container .nest-menu .el-submenu>.el-submenu__title, 86 | .sidebar-container .el-submenu .el-menu-item { 87 | min-width: 180px !important; 88 | background-color: $subMenuBg !important; 89 | &:hover { 90 | background-color: $menuHover !important; 91 | } 92 | } 93 | .el-menu--collapse .el-menu .el-submenu { 94 | min-width: 180px !important; 95 | } 96 | 97 | //适配移动端 98 | .mobile { 99 | .main-container { 100 | margin-left: 0px; 101 | } 102 | .sidebar-container { 103 | transition: transform .28s; 104 | width: 180px !important; 105 | } 106 | &.hideSidebar { 107 | .sidebar-container { 108 | transition-duration: 0.3s; 109 | transform: translate3d(-180px, 0, 0); 110 | } 111 | } 112 | } 113 | .withoutAnimation { 114 | .main-container, 115 | .sidebar-container { 116 | transition: none; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/styles/transition.scss: -------------------------------------------------------------------------------- 1 | //globl transition css 2 | 3 | /*fade*/ 4 | .fade-enter-active, 5 | .fade-leave-active { 6 | transition: opacity 0.28s; 7 | } 8 | 9 | .fade-enter, 10 | .fade-leave-active { 11 | opacity: 0; 12 | } 13 | 14 | /*fade*/ 15 | .breadcrumb-enter-active, 16 | .breadcrumb-leave-active { 17 | transition: all .5s; 18 | } 19 | 20 | .breadcrumb-enter, 21 | .breadcrumb-leave-active { 22 | opacity: 0; 23 | transform: translateX(20px); 24 | } 25 | 26 | .breadcrumb-move { 27 | transition: all .5s; 28 | } 29 | 30 | .breadcrumb-leave-active { 31 | position: absolute; 32 | } 33 | -------------------------------------------------------------------------------- /src/styles/variables.scss: -------------------------------------------------------------------------------- 1 | //sidebar 2 | $menuBg:#304156; 3 | $subMenuBg:#1f2d3d; 4 | $menuHover:#001528; 5 | -------------------------------------------------------------------------------- /src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const TokenKey = 'erlinger' 4 | const routeKey = 'route' 5 | /** 6 | * 将用户token存储 7 | */ 8 | export function getToken() { 9 | return Cookies.get(TokenKey) 10 | } 11 | 12 | export function setToken(token) { 13 | return Cookies.set(TokenKey, token) 14 | } 15 | 16 | export function removeToken() { 17 | return Cookies.remove(TokenKey) 18 | } 19 | /** 20 | * 将路由存储 21 | */ 22 | export function setRouteToken(route) { 23 | return Cookies.set(routeKey, route) 24 | } 25 | 26 | export function getRouteToken() { 27 | return Cookies.get(routeKey) 28 | } 29 | 30 | export function removeRouteToken() { 31 | return Cookies.remove(routeKey) 32 | } 33 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {*} time 4 | * @param {*} cFormat 5 | */ 6 | export function parseTime(time, cFormat) { 7 | if (arguments.length === 0) { 8 | return null 9 | } 10 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' 11 | let date 12 | if (typeof time === 'object') { 13 | date = time 14 | } else { 15 | if (('' + time).length === 10) time = parseInt(time) * 1000 16 | date = new Date(time) 17 | } 18 | const formatObj = { 19 | y: date.getFullYear(), 20 | m: date.getMonth() + 1, 21 | d: date.getDate(), 22 | h: date.getHours(), 23 | i: date.getMinutes(), 24 | s: date.getSeconds(), 25 | a: date.getDay() 26 | } 27 | const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { 28 | let value = formatObj[key] 29 | if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1] 30 | if (result.length > 0 && value < 10) { 31 | value = '0' + value 32 | } 33 | return value || 0 34 | }) 35 | return time_str 36 | } 37 | 38 | export function formatTime(time, option) { 39 | time = +time * 1000 40 | const d = new Date(time) 41 | const now = Date.now() 42 | 43 | const diff = (now - d) / 1000 44 | 45 | if (diff < 30) { 46 | return '刚刚' 47 | } else if (diff < 3600) { // less 1 hour 48 | return Math.ceil(diff / 60) + '分钟前' 49 | } else if (diff < 3600 * 24) { 50 | return Math.ceil(diff / 3600) + '小时前' 51 | } else if (diff < 3600 * 24 * 2) { 52 | return '1天前' 53 | } 54 | if (option) { 55 | return parseTime(time, option) 56 | } else { 57 | return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分' 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { Message, MessageBox } from 'element-ui' 3 | import store from '../store' 4 | import { getToken } from '@/utils/auth' 5 | 6 | let baseURL = process.env.NODE_ENV === 'production' ? 'http://vue.wtodd.wang:4000' : '/' 7 | /** eslint disabled */ 8 | // 创建axios实例 9 | const service = axios.create({ 10 | baseURL: process.env.VUE_APP_BASE_API, // api 的 base_url 11 | timeout: 20000 // 请求超时时间 12 | }) 13 | // request拦截器 14 | service.interceptors.request.use( 15 | config => { 16 | if (store.getters.token) { 17 | config.headers['w-token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 18 | } 19 | // console.log(config, 'config') // config 请求数据 20 | return config 21 | }, 22 | error => { 23 | // Do something with request error 24 | console.log(error) // for debug 25 | Promise.reject(error) 26 | } 27 | ) 28 | 29 | // response 拦截器 30 | service.interceptors.response.use( 31 | response => { 32 | /** 33 | * code为非20000是抛错 可结合自己业务进行修改 34 | */ 35 | // console.log(response, '拦截器 response') // // response 响应数据 36 | const res = response.data 37 | console.log(res) // 在这里已经对返回的参数 做了判断,如果是失败的信息,就弹框提示失败的信息 38 | // if (res.code !== 20000) { 39 | if (!res.success) { 40 | // 这里可以统一提示错误信息 41 | Message({ 42 | message: res.message, 43 | type: 'error' 44 | }) 45 | // 也可以针对具体的返回参数 提示不同的信息 46 | // 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了; 47 | if (res.code === 50008 || res.code === 50012 || res.code === 50014) { 48 | MessageBox.confirm( 49 | '你已被登出,可以取消继续留在该页面,或者重新登录', 50 | '确定登出', 51 | { 52 | confirmButtonText: '重新登录', 53 | cancelButtonText: '取消', 54 | type: 'warning' 55 | } 56 | ).then(() => { 57 | store.dispatch('FedLogOut').then(() => { 58 | location.reload() // 为了重新实例化vue-router对象 避免bug 59 | }) 60 | }) 61 | } 62 | return Promise.reject('error') 63 | } else { 64 | // console.log(response.data) 65 | return response.data 66 | } 67 | }, 68 | error => { 69 | console.log('err info' + error) // for debug 70 | Message({ 71 | message: error.message, 72 | type: 'error', 73 | duration: 20 * 1000 74 | }) 75 | return Promise.reject(error) 76 | } 77 | ) 78 | 79 | export default service 80 | -------------------------------------------------------------------------------- /src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {*} str 4 | */ 5 | export function isvalidUsername(str) { 6 | // const valid_map = ['admin', 'editor'] 7 | const valid_map = str 8 | return valid_map.indexOf(str.trim()) >= 0 9 | } 10 | 11 | /* 合法uri*/ 12 | export function validateURL(textval) { 13 | const urlregex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/ 14 | return urlregex.test(textval) 15 | } 16 | 17 | /* 小写字母*/ 18 | export function validateLowerCase(str) { 19 | const reg = /^[a-z]+$/ 20 | return reg.test(str) 21 | } 22 | 23 | /* 大写字母*/ 24 | export function validateUpperCase(str) { 25 | const reg = /^[A-Z]+$/ 26 | return reg.test(str) 27 | } 28 | 29 | /* 大小写字母*/ 30 | export function validatAlphabets(str) { 31 | const reg = /^[A-Za-z]+$/ 32 | return reg.test(str) 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/views/404.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 42 | 43 | 237 | -------------------------------------------------------------------------------- /src/views/admin/manage-money.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | 23 | 34 | -------------------------------------------------------------------------------- /src/views/admin/manage-users.vue: -------------------------------------------------------------------------------- 1 | 149 | 150 | 325 | 326 | 349 | -------------------------------------------------------------------------------- /src/views/center/index.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 83 | 84 | 123 | -------------------------------------------------------------------------------- /src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 38 | 39 | 50 | -------------------------------------------------------------------------------- /src/views/display/fiction.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 25 | 26 | 31 | -------------------------------------------------------------------------------- /src/views/display/index.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adouwt/vue-cli3-admin-system/6cd999169d5fc0b8322487f5f2093f2ad6a0fb87/src/views/display/index.vue -------------------------------------------------------------------------------- /src/views/display/technology.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 22 | 23 | 28 | -------------------------------------------------------------------------------- /src/views/edit/index.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 78 | 79 | 90 | 91 | -------------------------------------------------------------------------------- /src/views/edit/test.js: -------------------------------------------------------------------------------- 1 | // /* 2 | // * My97 DatePicker 4.8 3 | // * License: http://www.my97.net/license.asp 4 | // */ 5 | // var $dp, WdatePicker1, WdatePicker; 6 | 7 | // WdatePicker1 = (function () { 8 | // var l = { 9 | // $langList: [{ name: "en", charset: "UTF-8" }, 10 | // { name: "zh-cn", charset: "gb2312" }, 11 | // { name: "zh-tw", charset: "GBK" }], 12 | // $skinList: [{ name: "default", charset: "gb2312" }, 13 | // { name: "whyGreen", charset: "gb2312" }, 14 | // { name: "blue", charset: "gb2312" }, 15 | // { name: "green", charset: "gb2312" }, 16 | // { name: "simple", charset: "gb2312" }, 17 | // { name: "ext", charset: "gb2312" }, 18 | // { name: "blueFresh", charset: "gb2312" }, 19 | // { name: "twoer", charset: "gb2312" }, 20 | // { name: "YcloudRed", charset: "gb2312" }], 21 | // $wdate: true, 22 | // $crossFrame: true, 23 | // $preLoad: false, 24 | // $dpPath: "", 25 | // doubleCalendar: false, 26 | // enableKeyboard: true, 27 | // enableInputMask: true, 28 | // autoUpdateOnChanged: null, 29 | // weekMethod: "MSExcel", 30 | // position: {}, 31 | // lang: "auto", 32 | // skin: "default", 33 | // dateFmt: "yyyy-MM-dd", 34 | // realDateFmt: "yyyy-MM-dd", 35 | // realTimeFmt: "HH:mm:ss", 36 | // realFullFmt: "%Date %Time", 37 | // minDate: "0001-01-01 00:00:00", 38 | // maxDate: "9999-12-31 23:59:59", 39 | // minTime: "00:00:00", 40 | // maxTime: "23:59:59", 41 | // startDate: "", 42 | // alwaysUseStartDate: false, 43 | // yearOffset: 1911, 44 | // firstDayOfWeek: 0, 45 | // isShowWeek: false, 46 | // highLineWeekDay: true, 47 | // isShowClear: true, 48 | // isShowToday: true, 49 | // isShowOK: true, 50 | // isShowOthers: true, 51 | // readOnly: false, 52 | // errDealMode: 0, 53 | // autoPickDate: null, 54 | // qsEnabled: true, 55 | // autoShowQS: false, 56 | // hmsMenuCfg: { H: [1, 6], m: [5, 6], s: [15, 4] }, 57 | // opposite: false, 58 | 59 | // specialDates: null, specialDays: null, disabledDates: null, 60 | 61 | // disabledDays: null, onpicking: null, onpicked: null, onclearing: null, oncleared: null, ychanging: null, ychanged: null, Mchanging: null, Mchanged: null, dchanging: null, dchanged: null, Hchanging: null, Hchanged: null, mchanging: null, mchanged: null, schanging: null, schanged: null, eCont: null, vel: null, elProp: "", errMsg: "", quickSel: [], has: {}, getRealLang: function () { var d = l.$langList; for (var e = 0; e < d.length; e++) { if (d[e].name == this.lang) { return d[e] } } return d[0] } 62 | // }; 63 | // WdatePicker = g; 64 | // var n = window, i = { innerHTML: "" }, z = "document", B = "documentElement", H = "getElementsByTagName", E, u, h, f, D; 65 | // var v = navigator.appName; if (v == "Microsoft Internet Explorer") { h = true } else { if (v == "Opera") { D = true } else { f = true } } u = l.$dpPath || q(); if (l.$wdate) { m(u + "skin/WdatePicker.css") } E = n; 66 | // if (l.$crossFrame) { try { while (E.parent != E && E.parent[z][H]("frameset").length == 0) { E = E.parent } } catch (y) { } } if (!E.$dp) { E.$dp = { ff: f, ie: h, opera: D, status: 0, defMinDate: l.minDate, defMaxDate: l.maxDate } } b(); 67 | // if (l.$preLoad && $dp.status == 0) { k(n, "onload", function () { g(null, true) }) } if (!n[z].docMD) { k(n[z], "onmousedown", s, true); n[z].docMD = true } if (!E[z].docMD) { k(E[z], "onmousedown", s, true); E[z].docMD = true } k(n, "onunload", function () { if ($dp.dd) { r($dp.dd, "none") } }); 68 | // function b() { try { E[z], E.$dp = E.$dp || {} } catch (I) { E = n; $dp = $dp || {} } var w = { win: n, $: function (e) { return (typeof e == "string") ? n[z].getElementById(e) : e }, $D: function (J, e) { return this.$DV(this.$(J).value, e) }, $DV: function (J, e) { if (J != "") { this.dt = $dp.cal.splitDate(J, $dp.cal.dateFmt); 69 | // if (e) { for (var L in e) { if (this.dt[L] === undefined) { this.errMsg = "invalid property:" + L } else { this.dt[L] += e[L]; if (L == "M") { var M = e.M > 0 ? 1 : 0; var K = new Date(this.dt.y, this.dt.M, 0).getDate(); this.dt.d = Math.min(K + M, this.dt.d) } } } } if (this.dt.refresh()) { return this.dt } } return "" }, show: function () { var K = E[z].getElementsByTagName("div"), J = 100000; for (var e = 0; e < K.length; e++) { var L = parseInt(K[e].style.zIndex); 70 | // if (L > J) { J = L } } this.dd.style.zIndex = J + 2; r(this.dd, "block"); r(this.dd.firstChild, "") }, unbind: function (e) { e = this.$(e); if (e.initcfg) { t(e, "onclick", function () { g(e.initcfg) }); 71 | // t(e, "onfocus", function () { g(e.initcfg) }) } }, hide: function () { r(this.dd, "none") }, attachEvent: k }; for (var d in w) { E.$dp[d] = w[d] } $dp = E.$dp } function k(I, J, w, d) { if (I.addEventListener) { var e = J.replace(/on/, ""); w._ieEmuEventHandler = function (K) { return w(K) }; I.addEventListener(e, w._ieEmuEventHandler, d) } else { I.attachEvent(J, w) } } function t(w, I, e) { if (w.removeEventListener) { var d = I.replace(/on/, ""); e._ieEmuEventHandler = function (J) { return e(J) }; w.removeEventListener(d, e._ieEmuEventHandler, false) } else { w.detachEvent(I, e) } } function C(w, e, d) { if (typeof w != typeof e) { return false } if (typeof w == "object") { if (!d) { for (var I in w) { if (typeof e[I] == "undefined") { return false } if (!C(w[I], e[I], true)) { return false } } } return true } else { if (typeof w == "function" && typeof e == "function") { return w.toString() == e.toString() } else { return w == e } } } function q() { var I, w, d = n[z][H]("script"); for (var e = 0; e < d.length; e++) { I = d[e].getAttribute("src") || ""; I = I.substr(0, I.toLowerCase().indexOf("wdatepicker.js")); var w = I.lastIndexOf("/"); if (w > 0) { I = I.substring(0, w + 1) } if (I) { break } } return I } function m(w, I, J) { var d = n[z][H]("HEAD").item(0), e = n[z].createElement("link"); if (d) { e.href = w; e.rel = "stylesheet"; e.type = "text/css"; if (I) { e.title = I } if (J) { e.charset = J } d.appendChild(e) } } function p(I) { I = I || E; var L = 0, d = 0; while (I != E) { var N = I.parent[z][H]("iframe"); for (var J = 0; J < N.length; J++) { try { if (N[J].contentWindow == I) { var K = o(N[J]); L += K.left; d += K.top; break } } catch (M) { } } I = I.parent } return { leftM: L, topM: d } } function o(I, w) { if (I.getBoundingClientRect) { return I.getBoundingClientRect() } else { var J = { ROOT_TAG: /^body|html$/i, OP_SCROLL: /^(?:inline|table-row)$/i }; var e = false, M = null, P = I.offsetTop, K = I.offsetLeft, d = I.offsetWidth, O = I.offsetHeight; var L = I.offsetParent; if (L != I) { while (L) { K += L.offsetLeft; P += L.offsetTop; if (c(L, "position").toLowerCase() == "fixed") { e = true } else { if (L.tagName.toLowerCase() == "body") { M = L.ownerDocument.defaultView } } L = L.offsetParent } } L = I.parentNode; while (L.tagName && !J.ROOT_TAG.test(L.tagName)) { if (L.scrollTop || L.scrollLeft) { if (!J.OP_SCROLL.test(r(L))) { if (!D || L.style.overflow !== "visible") { K -= L.scrollLeft; P -= L.scrollTop } } } L = L.parentNode } if (!e) { var N = F(M); K -= N.left; P -= N.top } d += K; O += P; return { left: K, top: P, right: d, bottom: O } } } function x(e) { e = e || E; var J = e[z], I = (e.innerWidth) ? e.innerWidth : (J[B] && J[B].clientWidth) ? J[B].clientWidth : J.body.offsetWidth, d = (e.innerHeight) ? e.innerHeight : (J[B] && J[B].clientHeight) ? J[B].clientHeight : J.body.offsetHeight; return { width: I, height: d } } function F(e) { e = e || E; var J = e[z], d = J[B], I = J.body; J = (d && d.scrollTop != null && (d.scrollTop > I.scrollTop || d.scrollLeft > I.scrollLeft)) ? d : I; return { top: J.scrollTop, left: J.scrollLeft } } function s(d) { try { var w = d ? (d.srcElement || d.target) : null; if ($dp.cal && !$dp.eCont && $dp.dd && w != $dp.el && $dp.dd.style.display == "block") { $dp.cal.close() } } catch (d) { } } function A() { $dp.status = 2 } var G, j; function g(M, d) { if (!$dp) { return } b(); var J = {}; for (var L in M) { J[L] = M[L] } for (var L in l) { if (L.substring(0, 1) != "$" && J[L] === undefined) { J[L] = l[L] } } if (d) { if (!w()) { j = j || setInterval(function () { if (E[z].readyState == "complete") { clearInterval(j) } g(null, true) }, 50); return } if ($dp.status == 0) { $dp.status = 1; J.el = i; a(J, true) } else { return } } else { if (J.eCont) { J.eCont = $dp.$(J.eCont); J.el = i; J.autoPickDate = true; J.qsEnabled = false; a(J) } else { if (l.$preLoad && $dp.status != 2) { return } var I = N(); if (n.event === I || I) { J.srcEl = I.srcElement || I.target; I.cancelBubble = true } J.el = J.el = $dp.$(J.el || J.srcEl); if (J.el == null) { alert("WdatePicker:el is null") } try { if (!J.el || J.el.My97Mark === true || J.el.disabled || ($dp.dd && r($dp.dd) != "none" && $dp.dd.style.left != "-970px")) { if (J.el.My97Mark) { J.el.My97Mark = false } return } } catch (K) { } if (I && J.el.nodeType == 1 && !C(J.el.initcfg, M)) { $dp.unbind(J.el); J.el.initcfg = M } a(J) } } function w() { if (h && E != n && E[z].readyState != "complete") { return false } return true } function N() { if (f) { try { func = N.caller; while (func != null) { var O = func.arguments[0]; if (O && (O + "").indexOf("Event") >= 0) { return O } func = func.caller } } catch (P) { } return null } return event } } function c(e, d) { return e.currentStyle ? e.currentStyle[d] : document.defaultView.getComputedStyle(e, false)[d] } function r(e, d) { if (e) { if (d != null) { e.style.display = d } else { return c(e, "display") } } } function a(e, d) { var K = e.el ? e.el.nodeName : "INPUT"; if (d || e.eCont || new RegExp(/input|textarea|div|span|p|a/ig).test(K)) { e.elProp = K == "INPUT" ? "value" : "innerHTML" } else { return } if (e.lang == "auto") { e.lang = h ? navigator.browserLanguage.toLowerCase() : navigator.language.toLowerCase() } if (!e.eCont) { for (var J in e) { $dp[J] = e[J] } } if (!$dp.dd || e.eCont || ($dp.dd && (e.getRealLang().name != $dp.dd.lang || e.skin != $dp.dd.skin))) { if (e.eCont) { w(e.eCont, e) } else { $dp.dd = E[z].createElement("DIV"); $dp.dd.style.cssText = "position:absolute"; E[z].body.appendChild($dp.dd); w($dp.dd, e); if (d) { $dp.dd.style.left = $dp.dd.style.top = "-970px" } else { $dp.show(); I($dp) } } } else { if ($dp.cal) { $dp.show(); $dp.cal.init(); if (!$dp.eCont) { I($dp) } } } function w(V, P) { var O = E[z].domain, S = false, M = ''; V.innerHTML = M; var L = l.$langList, U = l.$skinList, T; try { T = V.lastChild.contentWindow[z] } catch (Q) { S = true; V.removeChild(V.lastChild); var N = E[z].createElement("iframe"); N.hideFocus = true; N.frameBorder = 0; N.scrolling = "no"; N.src = "javascript:(function(){var d=document;d.open();d.domain='" + O + "';})()"; V.appendChild(N); setTimeout(function () { T = V.lastChild.contentWindow[z]; R() }, 97); return } R(); function R() { var Y = P.getRealLang(); V.lang = Y.name; V.skin = P.skin; var X = [" 47 | 48 | 51 | -------------------------------------------------------------------------------- /src/views/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | 21 | 29 | -------------------------------------------------------------------------------- /src/views/layout/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 61 | 62 | 109 | 110 | -------------------------------------------------------------------------------- /src/views/layout/components/Sidebar/SidebarItem.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 90 | 91 | 96 | 97 | -------------------------------------------------------------------------------- /src/views/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 37 | -------------------------------------------------------------------------------- /src/views/layout/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navbar } from './Navbar' 2 | export { default as Sidebar } from './Sidebar' 3 | export { default as AppMain } from './AppMain' 4 | -------------------------------------------------------------------------------- /src/views/layout/mixin/ResizeHandler.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | const { body } = document 4 | const WIDTH = 1024 5 | const RATIO = 3 6 | 7 | export default { 8 | watch: { 9 | $route(route) { 10 | if (this.device === 'mobile' && this.sidebar.opened) { 11 | store.dispatch('CloseSideBar', { withoutAnimation: false }) 12 | } 13 | } 14 | }, 15 | beforeMount() { 16 | window.addEventListener('resize', this.resizeHandler) 17 | }, 18 | mounted() { 19 | const isMobile = this.isMobile() 20 | if (isMobile) { 21 | store.dispatch('ToggleDevice', 'mobile') 22 | store.dispatch('CloseSideBar', { withoutAnimation: true }) 23 | } 24 | }, 25 | methods: { 26 | isMobile() { 27 | const rect = body.getBoundingClientRect() 28 | return rect.width - RATIO < WIDTH 29 | }, 30 | resizeHandler() { 31 | if (!document.hidden) { 32 | const isMobile = this.isMobile() 33 | store.dispatch('ToggleDevice', isMobile ? 'mobile' : 'desktop') 34 | 35 | if (isMobile) { 36 | store.dispatch('CloseSideBar', { withoutAnimation: true }) 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 110 | 111 | 145 | 146 | 208 | -------------------------------------------------------------------------------- /src/views/nested/menu1/index.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-1/index.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-2/index.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-2/menu1-2-1/index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-2/menu1-2-2/index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-3/index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/nested/menu2/index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/register/index.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | 188 | 189 | 221 | 222 | 311 | -------------------------------------------------------------------------------- /src/views/table/index.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 73 | -------------------------------------------------------------------------------- /src/views/tree/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 71 | 72 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 2 | const path = require('path'); 3 | 4 | function resolve(dir) { 5 | return path.join(__dirname, dir) 6 | } 7 | module.exports = { 8 | // 项目部署的基础路径 9 | // 我们默认假设你的应用将会部署在域名的根部, 10 | // 比如 https://www.my-app.com/ 11 | // 如果你的应用时部署在一个子路径下,那么你需要在这里 12 | // 指定子路径。比如,如果你的应用部署在 13 | // https://www.foobar.com/my-app/ 14 | // 那么将这个值改为 `/my-app/` 15 | publicPath: process.env.VUE_APP_PUBLIC_URL, 16 | 17 | // 将构建好的文件输出到哪里 18 | outputDir: 'dist', 19 | 20 | // 放置静态资源的地方 (js/css/img/font/...) 21 | // assetsDir: '', 22 | 23 | // 是否在保存的时候使用 `eslint-loader` 进行检查。 24 | // 有效的值:`ture` | `false` | `"error"` 25 | // 当设置为 `"error"` 时,检查出的错误会触发编译失败。 26 | lintOnSave: true, 27 | 28 | // 使用带有浏览器内编译器的完整构建版本 29 | // 查阅 https://cn.vuejs.org/v2/guide/installation.html#运行时-编译器-vs-只包含运行时 30 | // compiler: false, 31 | 32 | // babel-loader 默认会跳过 node_modules 依赖。 33 | // 通过这个选项可以显式转译一个依赖。 34 | transpileDependencies: [/* string or regex */], 35 | 36 | // 是否为生产环境构建生成 source map? 37 | productionSourceMap: false, 38 | 39 | // 调整内部的 webpack 配置。 40 | // 查阅 https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli/webpack.md 41 | chainWebpack: () => { }, 42 | configureWebpack: () => { }, 43 | 44 | // CSS 相关选项 45 | css: { 46 | // 将组件内的 CSS 提取到一个单独的 CSS 文件 (只用在生产环境中) 47 | // 也可以是一个传递给 `extract-text-webpack-plugin` 的选项对象 48 | extract: true, 49 | 50 | // 是否开启 CSS source map? 51 | sourceMap: false, 52 | 53 | // 为预处理器的 loader 传递自定义选项。比如传递给 54 | // sass-loader 时,使用 `{ sass: { ... } }`。 55 | loaderOptions: {}, 56 | 57 | // 为所有的 CSS 及其预处理文件开启 CSS Modules。 58 | // 这个选项不会影响 `*.vue` 文件。 59 | modules: false 60 | }, 61 | 62 | // 在生产环境下为 Babel 和 TypeScript 使用 `thread-loader` 63 | // 在多核机器下会默认开启。 64 | parallel: require('os').cpus().length > 1, 65 | 66 | // PWA 插件的选项。 67 | // 查阅 https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli-plugin-pwa/README.md 68 | pwa: {}, 69 | 70 | // 配置 webpack-dev-server 行为。 71 | devServer: { 72 | open: process.platform === 'darwin', 73 | host: '0.0.0.0', 74 | port: 8080, 75 | https: false, 76 | hotOnly: false, 77 | open:true, 78 | // 查阅 https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli/cli-service.md#配置代理 79 | proxy: { 80 | '/nodejsapi': { 81 | // target: 'http://vue.wtodd.wang:4000', 82 | target: 'http://localhost:4000', 83 | changeOrigin: true, 84 | ws: true, 85 | pathRewrite: { 86 | '^/nodejsapi': '' 87 | } 88 | } 89 | }, 90 | before: app => { } 91 | }, 92 | 93 | configureWebpack: config => { 94 | if (process.env.NODE_ENV === 'production') { 95 | // 为生产环境修改配置... 96 | if(process.env.VUE_LIFECIRCLE_EVENT === 'ANALYZE'){ 97 | config.plugins.push( 98 | new BundleAnalyzerPlugin( 99 | { 100 | analyzerMode: 'server', 101 | analyzerHost: '127.0.0.1', 102 | analyzerPort: 8889, 103 | reportFilename: 'report.html', 104 | defaultSizes: 'parsed', 105 | openAnalyzer: true, 106 | generateStatsFile: false, 107 | statsFilename: 'stats.json', 108 | statsOptions: null, 109 | logLevel: 'info' 110 | } 111 | ) 112 | ); 113 | } 114 | 115 | } else { 116 | // 为开发环境修改配置... 117 | if(process.env.VUE_LIFECIRCLE_EVENT === 'DEV'){ 118 | config.plugins.push( 119 | new BundleAnalyzerPlugin( 120 | { 121 | analyzerMode: 'server', 122 | analyzerHost: '127.0.0.1', 123 | analyzerPort: 8888, 124 | reportFilename: 'report.html', 125 | defaultSizes: 'parsed', 126 | openAnalyzer: true, 127 | generateStatsFile: false, 128 | statsFilename: 'stats.json', 129 | statsOptions: null, 130 | logLevel: 'info' 131 | } 132 | ) 133 | ); 134 | } 135 | } 136 | 137 | }, 138 | 139 | // 第三方插件的选项 140 | pluginOptions: { 141 | 142 | } 143 | } --------------------------------------------------------------------------------