├── .browserslistrc ├── .env.development ├── .env.production ├── .gitignore ├── LICENSE ├── README.md ├── babel.config.js ├── other ├── admin1.jpg ├── admin2.jpg └── skm.jpg ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── api │ ├── request.js │ └── user.js ├── assets │ ├── login │ │ ├── login-bg-1.jpg │ │ ├── login-bg-2.jpg │ │ ├── qq.svg │ │ ├── weibo.svg │ │ └── weixin.svg │ └── main │ │ ├── avatar.jpg │ │ ├── logo-dark.png │ │ ├── logo-light.png │ │ ├── logo-small.png │ │ ├── nav-theme-dark.svg │ │ └── nav-theme-light.svg ├── components │ ├── footer │ │ └── footer.vue │ ├── main │ │ ├── main.less │ │ └── main.vue │ └── message │ │ └── message.vue ├── config │ └── config.js ├── css │ └── public.less ├── main.js ├── mock │ ├── index.js │ └── mock.js ├── router │ ├── index.js │ └── permission.js ├── store │ ├── getters.js │ ├── index.js │ └── modules │ │ ├── permission.js │ │ ├── tagsView.js │ │ └── user.js ├── utils │ ├── permission.js │ └── util.js └── views │ ├── Dashboard │ ├── workplace.vue │ └── workplace1.vue │ ├── brief │ └── brief.vue │ ├── form │ ├── advanced_form.vue │ └── basic_form.vue │ ├── login │ └── login.vue │ ├── permission │ └── index.vue │ └── tmp.vue └── vue.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | ie 11 3 | last 2 versions 4 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # just a flag 2 | ENV = 'development' 3 | 4 | # base api 5 | VUE_APP_BASE_API = '/dev-api' 6 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # just a flag 2 | ENV = 'production' 3 | 4 | # base api 5 | VUE_APP_BASE_API = 'https://www.fastmock.site/mock/8b8187de5502cc6a522b78638621c2c4/HuiAdmin/' 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hui-Admin-Template 2 | ## 基础框架简洁版 3 | 4 | 1. 简述:当前是[码云 Hui-Admin-Pro](https://gitee.com/zengxiaohui2019/Hui-Admin-Pro) / 5 | [GitHub Hui-Admin-Pro](https://gitgub.com/zengxiaohui2019/Hui-Admin-Pro) 的母版,也就是基础Template版,只有基础组件和框架 6 | - Hui-Admin-Pro是基于[基础版 Hui-Admin-Template](https://github.com/zengxiaohui2019/Hui-Admin-Template) 增加更多复杂功能 7 | - [Hui-Admin-Pro 在线演示](https://github.zengxiaohui.com/Hui-Admin-Pro) 8 | - [Hui-Admin-Template 在线演示](https://github.zengxiaohui.com/Hui-Admin-Template) 9 | - 说明:本人是写这个框架是抱着学习态度,无意冲突商业大佬利益。 10 | - 兼容说明:初测兼容ie11 360极速模式 谷歌 火狐 其他未测;界面适配笔记本和日常PC;不适配平板和手机。 11 | 2. 主要技术: 12 | 13 | iview4.0 + vuecli3.0 14 | vue-router路由跳转 (层级嵌套适配、跳转前的拦截、跳转后页面自动滚到顶部) 15 | vuex管理 (导航高亮、多分页标签切换、根据用户动态菜单路由控制等) 16 | less flex弹性布局 17 | 18 | 3. 更新日志: 19 | 20 | 1.1.0 2019-10-03 01:10 21 | - 历时两天多1小时,每一行代码纯手打。 22 | - 第1天:VueCli3.0基本环境搭建,插件安装,观摩iview-admin-pro布局搭配,做了基本框架页面布局 23 | - 第2天:新增导航 多栏目切换 通知下拉功能 路由鉴权 24 | 1.1.1 2019-10-03 25 | - 新增主题风格切换、多页签、顶栏通顶功能,增加配置文件 26 | 1.1.2 2019-10-04 am 27 | - 新增刷新页面功能 28 | 2.1.0 2020-05-16 29 | - 更换登录接口为在线mock 30 | - 重构了动态路由权限校验,实现根据登录接口返回的权限集合,动态渲染菜单和加载路由 31 | - 新增 按钮组件级别权限控制 32 | 33 | ## 启动 34 | 35 | npm install 安装依赖包 36 | npm run dev 开发模式运行 37 | npm run build 打包 38 | 39 | ### 已安装插件 40 | 1. axios 0.19.0 // 接口请求控件 41 | 2. iview 3.5.1 42 | 3. mockjs 1.0.1-beta3 // 数据模拟控件 不用可删除 43 | 4. moment 2.24.0 // 时间处理控件 不用可删除 44 | 5. vue-router 3.0.3 45 | 6. vuex 3.0.1 46 | 7. less 3.0.4 47 | 8. babel-polyfill 6.26.0 // ie兼容必须 48 | 49 | ### 目录说明 50 | 51 | Hui-Admin-Template 52 | node_modules npm install 后安装的依赖包 53 | other md文档需要的图片 54 | public 公共目录 55 | src 核心业务目录 56 | api 接口请求封装 57 | request.js axios配置和请求返回拦截 58 | user.js 登录接口封装 59 | assets 图片 60 | main 主框架用到的图片 61 | components 组件 62 | main 主框架 63 | message 消息通知 64 | config 基础配置 65 | config.js 基础配置文件 66 | utils 工具 67 | permission.js 按扭级权限校验 68 | util.js 常用业务文件(判断是否为空 等) 69 | css css 70 | public.less 全局公共css 71 | router 路由配置 72 | permission.js 路由权限校验 73 | router.js 路由操作处理文件 74 | views 页面 75 | tmp.vue 模板页面 (已包含常用的vue生命周期 可删除) 76 | store vuex目录 77 | App.vue 主入口vue 78 | main.js 主入口js 79 | .env.development 开发环境 上下文配置 VUE_APP_BASE_API = '/dev-api' 80 | .env.production 生产环境 上下文配置 VUE_APP_BASE_API = '' 81 | babel.config.js babel配置文件 82 | package.json npm包管理文件 83 | README.md 说明文档 84 | vue.config.js 开发打包配置文件 85 | 86 | ### 配置说明 87 | 88 | 1. 基本配置 config->config.js 里面有详细的说明 89 | 90 | // 默认系统名称 91 | export const siteTitle = 'Hui-Admin-Pro 企业级中台前端解决方案' 92 | // 默认首页 93 | export const indexPage = 'workplace' 94 | 95 | // 主题风格 96 | export const themeData = { 97 | themeType: 'dark', // 主题风格配置 dark(经典酷黑) 或者 light(极简雅白) 默认dark经典酷黑 98 | isTabsShow: true, // 是否显示多页签 默认true 99 | headMaxWidth: false // 栏目头部是否通顶(最大宽度) 默认false 100 | } 101 | 102 | 2. 用户数据 在store/modules/user.js 103 | 104 | state: { 105 | // 用户信息数据 106 | userData: sessionStorage.getItem('userData') ? JSON.parse(sessionStorage.getItem('userData')) : {}, 107 | // 是否登陆 108 | isLogin: sessionStorage.getItem('userData') ? true : false, 109 | // 角色权限集合 110 | roles: [] 111 | } 112 | 113 | 3. 路由鉴权 (根据登录接口返回的权限集合 生产动态路由) 114 | 115 | // 一级栏目 116 | { 117 | path: "/form", 118 | name: "form", 119 | component: Main, 120 | meta: { 121 | hide: false, // 是否显示 122 | title: "Dashboard", // 显示文字 123 | icon: "md-speedometer", // 显示图标 124 | roleId: 8,// 权限id 125 | }, 126 | // 二级栏目 127 | children: [ 128 | { 129 | path: "/form/basic_form", 130 | name: "basic-form", 131 | meta: { 132 | hide: false, // 是否显示 133 | title: "基础表单", // 显示文字 134 | roleId: 9, // 权限 数组 135 | }, 136 | component: () => import("@/views/form/basic_form") 137 | } 138 | ] 139 | } 140 | 4. 路由权限测试 141 | 142 | 测试权限需要登录不同账户 143 | test账户 账号:test 密码:123456 144 | admin账户 账号:admin 密码:123456 145 | 146 | 说明:根据登录接口返回 147 | admin 拥有1-10的权限[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 148 | test 拥有6-10的权限[6, 7, 8, 9, 10] 149 | 150 | ### git地址 151 | 码云 https://gitee.com/zengxiaohui2019/Hui-Admin-Template 152 | 153 | GitHub https://github.com/zengxiaohui2019/Hui-Admin-Template 154 | 155 | ### 效果 156 | 157 | 在线演示:https://github.zengxiaohui.com/Hui-Admin-Template 158 | ![时尚酷黑主题](other/admin1.jpg) 159 | ![极简雅白主题](other/admin2.jpg) 160 | 161 | ### 支持作者 162 | 163 | 1. 本软件Hui-Admin-Template,是一个完全免费开源的产品,您可以任意使用,包括修订、发布、出售等。 164 | 2. 如果你觉得它给你的项目带来了帮助,提高了开发效率,您可以通过以下的方式来表示你的谢意! 165 | 3. 网站程序开发、管理系统、小程序开发请找我,WeiXin:badiweier 昵称:[曾小晖] 166 | 167 | 4. 使用 支付宝 或 微信 请我喝杯咖啡 168 | 169 | ![收款码](https://gitee.com/zengxiaohui2019/Hui-Admin-Pro/raw/master/other/skm.jpg) 170 | 171 | ### 免责申明 172 | 173 | 1. 本软件代码全为曾小晖本人手写,不存在侵犯任何第三方著作权。 174 | 2. 禁止使用本软件从事违法犯罪的事情,在使用时产生的任何法律刑事责任于本软件无关 (想不出来能做什么违法的事情,但还是要申明一下)。 175 | 3. 由于个人才识技术浅薄,在使用本软件造成的一切损失于本软件无关 (应该不存在,只是一个前端页面而已)。 176 | 4. 有问题可联系本人 (大前提:请先百度尝试解决后最终没有找到解决办法),可付费或免费获得支持。 177 | 178 | ### 项目使用图片版权说明 179 | 180 | 1. 本项目使用的图片来源网络,版权归原作者所有 181 | 2. 由于没有联系到原作者,若涉及版权问题,请联系本人,予以删除,谢谢 182 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /other/admin1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengxiaohui2019/Hui-Admin-Template/48a38f8acd2db67fc25ef73f051f30c6881e049e/other/admin1.jpg -------------------------------------------------------------------------------- /other/admin2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengxiaohui2019/Hui-Admin-Template/48a38f8acd2db67fc25ef73f051f30c6881e049e/other/admin2.jpg -------------------------------------------------------------------------------- /other/skm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengxiaohui2019/Hui-Admin-Template/48a38f8acd2db67fc25ef73f051f30c6881e049e/other/skm.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hui-admin-template", 3 | "version": "2.1.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vue-cli-service serve", 7 | "build": "vue-cli-service build" 8 | }, 9 | "dependencies": { 10 | "axios": "^0.19.0", 11 | "babel-polyfill": "^6.26.0", 12 | "core-js": "^2.6.5", 13 | "view-design": "^4.0.0", 14 | "mockjs": "^1.0.1-beta3", 15 | "moment": "^2.24.0", 16 | "vue": "^2.6.10", 17 | "vue-router": "^3.0.3", 18 | "vuex": "^3.0.1" 19 | }, 20 | "devDependencies": { 21 | "@vue/cli-plugin-babel": "^3.11.0", 22 | "@vue/cli-service": "^3.11.0", 23 | "less": "^3.0.4", 24 | "less-loader": "^5.0.0", 25 | "vue-template-compiler": "^2.6.10" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengxiaohui2019/Hui-Admin-Template/48a38f8acd2db67fc25ef73f051f30c6881e049e/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Hui Admin Template 9 | 10 | 11 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /src/api/request.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 接口请求拦截 3 | * @author Xiaohui Zeng 4 | * @date 2020/5/14 5 | */ 6 | import axios from 'axios' 7 | import {Message} from 'view-design'; 8 | // 创建一个axios实例 9 | const service = axios.create({ 10 | baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url 11 | timeout: 30000, // 请求超时时间 默认30秒 12 | headers: { 13 | 'Content-Type': 'application/json' 14 | } 15 | }) 16 | // 请求拦截器 17 | service.interceptors.request.use(config => { 18 | const token = localStorage.getItem('userToken'); 19 | // 判断是否存在token,如果存在的话,则每个http header都加上token 20 | if (token) { 21 | config.headers['accessToken'] = token 22 | } 23 | return config 24 | }, 25 | error => { 26 | // debug 27 | console.log(error) 28 | return Promise.reject(error) 29 | } 30 | ) 31 | 32 | // 响应拦截器 33 | service.interceptors.response.use( 34 | response => { 35 | const res = response.data 36 | 37 | if (res.code !== 1) { 38 | Message.warning(res.msg || 'Error') 39 | return Promise.reject(new Error(res.msg || 'Error')) 40 | } else { 41 | return res 42 | } 43 | }, 44 | error => { 45 | // debug 46 | console.log('err' + error) 47 | Message.error(res.msg || 'Error') 48 | return Promise.reject(error) 49 | } 50 | ) 51 | 52 | export default service 53 | -------------------------------------------------------------------------------- /src/api/user.js: -------------------------------------------------------------------------------- 1 | import request from './request' 2 | 3 | export function login(data) { 4 | return request({ 5 | url: '/login', 6 | method: 'post', 7 | data 8 | }) 9 | } 10 | 11 | export function editPwd(data) { 12 | return request({ 13 | url: '/api/account/password/modify', 14 | method: 'post', 15 | data 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /src/assets/login/login-bg-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengxiaohui2019/Hui-Admin-Template/48a38f8acd2db67fc25ef73f051f30c6881e049e/src/assets/login/login-bg-1.jpg -------------------------------------------------------------------------------- /src/assets/login/login-bg-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengxiaohui2019/Hui-Admin-Template/48a38f8acd2db67fc25ef73f051f30c6881e049e/src/assets/login/login-bg-2.jpg -------------------------------------------------------------------------------- /src/assets/login/qq.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/login/weibo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/login/weixin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/main/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengxiaohui2019/Hui-Admin-Template/48a38f8acd2db67fc25ef73f051f30c6881e049e/src/assets/main/avatar.jpg -------------------------------------------------------------------------------- /src/assets/main/logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengxiaohui2019/Hui-Admin-Template/48a38f8acd2db67fc25ef73f051f30c6881e049e/src/assets/main/logo-dark.png -------------------------------------------------------------------------------- /src/assets/main/logo-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengxiaohui2019/Hui-Admin-Template/48a38f8acd2db67fc25ef73f051f30c6881e049e/src/assets/main/logo-light.png -------------------------------------------------------------------------------- /src/assets/main/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengxiaohui2019/Hui-Admin-Template/48a38f8acd2db67fc25ef73f051f30c6881e049e/src/assets/main/logo-small.png -------------------------------------------------------------------------------- /src/assets/main/nav-theme-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 Copy 5 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/assets/main/nav-theme-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/components/footer/footer.vue: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 统一版权页脚 3 | * @author Xiaohui Zeng 4 | * @date 2019/10/7 5 | */ 6 | 20 | 21 | 35 | 36 | 58 | -------------------------------------------------------------------------------- /src/components/main/main.less: -------------------------------------------------------------------------------- 1 | .layout-page { 2 | background: #f5f7f9; 3 | position: relative; 4 | border-radius: 4px; 5 | overflow: hidden; 6 | font-size: 14px; 7 | 8 | & > .layout-div { 9 | /*顶部区域*/ 10 | 11 | .header { 12 | background: #fff; 13 | box-shadow: 0 1px 4px rgba(0,21,41,.08); 14 | transition: all .2s ease-in-out; 15 | justify-content: space-between; 16 | height: 64px; 17 | line-height: 64px; 18 | padding: 0; 19 | .ivu-breadcrumb>span:last-child { 20 | font-weight: lighter; 21 | } 22 | i { 23 | cursor: pointer; 24 | margin-right: 10px; 25 | } 26 | 27 | .header_left { 28 | min-width: 300px; 29 | width: auto; 30 | padding-left: 15px; 31 | 32 | & > span { 33 | display: inline-flex; 34 | //flex-grow: 1; 35 | min-width: 45px; 36 | //&:hover{ 37 | // background-color: #F8F8F9; 38 | //} 39 | } 40 | } 41 | 42 | .header_right { 43 | min-width: 240px; 44 | 45 | i:last-child { 46 | margin-right: 0px; 47 | } 48 | 49 | & > span { 50 | display: inline-flex; 51 | flex-grow: 1; 52 | //&:hover{ 53 | // background-color: #F8F8F9; 54 | //} 55 | &:last-child { 56 | flex-grow: 0.5; 57 | } 58 | 59 | .ivu-badge { 60 | display: inline-flex; 61 | top: 5px; 62 | 63 | sup { 64 | transform: translateX(-19%); 65 | top: -14px; 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | /*导航切换*/ 73 | 74 | .tabsNav { 75 | background: #f5f7f9; 76 | padding: 6px 0; 77 | margin: 3px 15px 0 15px; 78 | justify-content: space-between; 79 | 80 | & > .left { 81 | width: calc(100% - 80px); 82 | overflow: hidden; 83 | 84 | & > div { 85 | position: relative; 86 | transition: transform .5s ease-in-out; 87 | 88 | & > div { 89 | height: 32px; 90 | background: #fff; 91 | border-radius: 3px; 92 | margin-right: 6px; 93 | color: #808695; 94 | padding: 5px 10px 4px 0px; 95 | justify-content: space-around; 96 | white-space: nowrap; 97 | span{ 98 | padding-left: 16px; 99 | } 100 | i { 101 | margin-left: 5px; 102 | cursor: pointer; 103 | } 104 | 105 | & > .active { 106 | color: #2d8cf0; 107 | } 108 | } 109 | } 110 | 111 | .ivu-select-dropdown { 112 | top: 101px !important; 113 | } 114 | } 115 | 116 | & > .right { 117 | .xiala { 118 | width: 32px; 119 | height: 32px; 120 | line-height: 32px; 121 | text-align: center; 122 | background-color: #fff; 123 | border-radius: 2px; 124 | } 125 | } 126 | } 127 | 128 | /*左边部分*/ 129 | /*黑色主题风格 默认*/ 130 | .ivu-layout-sider { 131 | background: #191a23; 132 | min-height: 100vh; 133 | 134 | .logo { 135 | border-bottom: 1px solid #101117; 136 | height: 70px; 137 | line-height: 70px; 138 | text-align: center; 139 | overflow: hidden; 140 | background: #191a23; 141 | box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35); 142 | img { 143 | height: 80%; 144 | vertical-align: middle; 145 | } 146 | } 147 | // 最大宽度 148 | .headMaxWidth{ 149 | background-color: white; 150 | border-right: none!important; 151 | box-shadow: none!important; 152 | } 153 | .ivu-menu-dark { 154 | background: #191a23; 155 | } 156 | 157 | .ivu-menu-submenu-title:hover { 158 | background: none !important; 159 | } 160 | 161 | .ivu-menu-dark.ivu-menu-vertical .ivu-menu-opened { 162 | background: #101117; 163 | } 164 | 165 | .ivu-menu-dark.ivu-menu-vertical .ivu-menu-opened .ivu-menu-submenu-title { 166 | background: #191a23; 167 | } 168 | 169 | /*默认 打开*/ 170 | 171 | .menu-item { 172 | height: calc(100vh - 70px); 173 | overflow-y: auto; 174 | &::-webkit-scrollbar { 175 | display: none; /* Chrome Safari */ 176 | } 177 | box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35); 178 | /*单页面 s*/ 179 | &>.ivu-menu-item{ 180 | &:hover{ 181 | background: none; 182 | color: #fff; 183 | } 184 | } 185 | &>.liActive{ 186 | background-color: #2d8cf0; 187 | &>div{ 188 | color: #fff; 189 | } 190 | &:hover{ 191 | background-color: #2d8cf0; 192 | color: #fff; 193 | } 194 | } 195 | /*单页面 e*/ 196 | .ivu-menu-item { 197 | padding-left: 50px !important; 198 | } 199 | 200 | i { 201 | transition: font-size .2s ease, transform .2s ease; 202 | vertical-align: middle; 203 | font-size: 16px; 204 | } 205 | 206 | .ivu-menu-submenu-title, .ivu-menu, .ivu-menu-submenu { 207 | overflow: hidden; 208 | text-overflow: ellipsis; 209 | white-space: nowrap; 210 | } 211 | 212 | .ivu-menu-item { 213 | span { 214 | width: calc(100% - 50px); 215 | overflow: hidden; 216 | text-overflow: ellipsis; 217 | white-space: nowrap; 218 | } 219 | } 220 | 221 | .titlespan { 222 | display: inline-block; 223 | width: 90px; 224 | margin-left: 10px; 225 | vertical-align: bottom; 226 | transition: width .2s ease .2s; 227 | } 228 | } 229 | } 230 | /*白色主题风格*/ 231 | .themeLight{ 232 | background: #fff; 233 | .logo{ 234 | background: #fff; 235 | border-bottom: 1px solid #f8f8f9; 236 | border-right: 1px solid #f1f1f2; 237 | box-shadow: 2px 7px 8px 0 rgba(29,35,41,.05); 238 | } 239 | .menu-item{ 240 | box-shadow: 2px 7px 8px 0 rgba(29,35,41,.05); 241 | /*单页面 s*/ 242 | &>.ivu-menu-item{ 243 | &:hover{ 244 | background: none; 245 | color: #fff; 246 | } 247 | } 248 | &>.liActive{ 249 | background-color: #EBF5FA; 250 | &>div{ 251 | color: #4398F2; 252 | } 253 | &:hover{ 254 | background-color: #EBF5FA; 255 | color: #4398F2; 256 | } 257 | &:after { 258 | content: ''; 259 | display: block; 260 | width: 2px; 261 | position: absolute; 262 | top: 0; 263 | bottom: 0; 264 | right: 0; 265 | background: #2d8cf0; 266 | } 267 | } 268 | /*单页面 e*/ 269 | } 270 | /*收起*/ 271 | .collapsed-menu{ 272 | align-content: flex-start; 273 | &>.menuSmall{ 274 | width: 100%; 275 | } 276 | .menuSmall > div:first-child{ 277 | background: #fff; 278 | i { 279 | color: #565F73; 280 | } 281 | .active { 282 | position: relative; 283 | background-color: #f0faff; 284 | i { 285 | color: #2D8CF0; 286 | } 287 | &:after{ 288 | content: ''; 289 | display: block; 290 | width: 2px; 291 | position: absolute; 292 | top: 0; 293 | bottom: 0; 294 | right: 1px; 295 | background: #2d8cf0; 296 | } 297 | } 298 | } 299 | .menuSmall > div:last-child { 300 | background: #fff; 301 | left: 79px !important; 302 | width: 160px; 303 | .ivu-dropdown-item-selected { 304 | background-color: #f0faff; 305 | color: #2D8CF0; 306 | &:hover { 307 | background-color: #f0faff!important; 308 | } 309 | } 310 | ul{ 311 | li{ 312 | color: #515a6e; 313 | } 314 | } 315 | .ivu-select-dropdown{ 316 | min-width: 160px; 317 | } 318 | 319 | .ivu-dropdown-item:hover { 320 | background-color: #F3F3F3; 321 | color: #2D8CF0; 322 | } 323 | } 324 | } 325 | } 326 | /*收起 默认*/ 327 | .collapsed-menu { 328 | display: flex; 329 | flex-direction: row; 330 | justify-content: center; 331 | flex-wrap: wrap; 332 | align-items: center; 333 | width: 100%; 334 | align-content: flex-start; 335 | // 栏目收缩 无法显示提示bug 336 | overflow-y: visible!important; 337 | &::-webkit-scrollbar { 338 | display: none; /* Chrome Safari */ 339 | } 340 | .menuSmall { 341 | position: relative; 342 | width: 100%; 343 | & > div:first-child { 344 | span { 345 | width: 100%; 346 | height: 52px; 347 | padding: 14px 24px; 348 | display: inline-flex; 349 | align-items: center; 350 | justify-content: center; 351 | 352 | i { 353 | color: #BABBBD; 354 | } 355 | } 356 | 357 | .active { 358 | background-color: #2e93f8; 359 | 360 | i { 361 | color: #fff; 362 | } 363 | } 364 | } 365 | 366 | /*下拉菜单弹出层*/ 367 | 368 | & > div:last-child { 369 | left: 79px !important; 370 | top: 10px !important; 371 | background-color: #191A23; 372 | ul { 373 | min-width: 160px; 374 | li { 375 | text-align: left; 376 | color: #BABBBD; 377 | width: 100%; 378 | } 379 | } 380 | .ivu-dropdown-item-selected { 381 | background-color: #2D8CF0; 382 | color: white; 383 | 384 | &:hover { 385 | background-color: #2D8CF0 !important; 386 | } 387 | } 388 | 389 | .ivu-dropdown-item:hover { 390 | background-color: #191A23; 391 | color: white; 392 | } 393 | } 394 | .ivu-select-dropdown{ 395 | min-width: 160px; 396 | } 397 | } 398 | 399 | .ivu-menu-opened { 400 | i { 401 | color: white !important; 402 | } 403 | } 404 | } 405 | /*内容区*/ 406 | .main_content{ 407 | background-color: #f5f7f9; 408 | overflow-y: auto; 409 | justify-content: space-between; 410 | &>div:first-child{ 411 | //overflow-y: auto; 412 | padding-right: 24px; 413 | //height: 100%; 414 | } 415 | 416 | } 417 | } 418 | } 419 | /*主题风格设置*/ 420 | .optionDrawer{ 421 | .ivu-drawer-header{ 422 | border-bottom: 0px; 423 | padding: 14px 16px 0px 16px; 424 | .header{ 425 | width: 95%; 426 | min-width: 95%; 427 | font-size: 14px!important; 428 | } 429 | } 430 | .ivu-drawer-body{ 431 | .imgs{ 432 | width: 100%; 433 | justify-content: center; 434 | &>div{ 435 | cursor: pointer; 436 | position: relative; 437 | } 438 | &>div:nth-child(2n){ 439 | margin-left: 40px; 440 | } 441 | .active{ 442 | &:after{ 443 | position: absolute; 444 | left: 50%; 445 | content: ""; 446 | width: 6px; 447 | height: 6px; 448 | border-radius: 6px; 449 | background: #19be6b; 450 | } 451 | } 452 | } 453 | .otherDiv{ 454 | width: 95%; 455 | min-width: 95%; 456 | font-size: 14px!important; 457 | } 458 | .kaiguan{ 459 | justify-content: space-between; 460 | width: 65%; 461 | margin: 0 auto 15px auto; 462 | } 463 | } 464 | } 465 | -------------------------------------------------------------------------------- /src/components/main/main.vue: -------------------------------------------------------------------------------- 1 | 193 | 194 | 483 | 484 | 487 | -------------------------------------------------------------------------------- /src/components/message/message.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 175 | 176 | -------------------------------------------------------------------------------- /src/config/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 配置文件 3 | * @author Xiaohui Zeng 4 | * @date 2019/10/5 5 | */ 6 | // 默认首页 7 | export const siteTitle = 'HuiVue Admin Pro 企业级中台前端解决方案' 8 | // 默认首页 9 | export const indexPage = 'workplace' 10 | // 默认登录页 11 | export const loginPage = 'login' 12 | 13 | // 主题风格 14 | export const themeData = { 15 | themeType: 'dark', // 主题风格配置 dark(经典酷黑) 或者 light(极简雅白) 默认dark经典酷黑 16 | isTabsShow: true, // 是否显示多页签 默认true 17 | headMaxWidth: false // 栏目头部是否通顶(最大宽度) 默认false 18 | } 19 | -------------------------------------------------------------------------------- /src/css/public.less: -------------------------------------------------------------------------------- 1 | body{ 2 | overflow: hidden; 3 | } 4 | .flexR { 5 | display: inline-flex; 6 | flex-direction: row; 7 | align-items: center; 8 | flex-shrink: 0; /*如果元素的大小是根据其子元素的来决定,并没有设置width、height或flex-basis 容器重叠在一起的兼容问题*/ 9 | } 10 | 11 | .flexC { 12 | display: inline-flex; 13 | flex-direction: column; 14 | flex-shrink: 0; 15 | } 16 | 17 | .pointer { 18 | cursor: pointer; 19 | &:hover{ 20 | color: #1890FF; 21 | } 22 | } 23 | 24 | .blueText { 25 | color: #1890FF; 26 | cursor: pointer; 27 | } 28 | 29 | textarea { 30 | resize: none; 31 | } 32 | /*滚动条美化*/ 33 | *::-webkit-scrollbar { 34 | width: 8px; 35 | height: 6px; 36 | } 37 | *::-webkit-scrollbar-track { 38 | background-color:#F1F1F1; 39 | -webkit-border-radius: 2em; 40 | -moz-border-radius: 2em; 41 | border-radius:2em; 42 | } 43 | *::-webkit-scrollbar-thumb { 44 | background-color: #d8d9da; 45 | -webkit-border-radius: 2em; 46 | -moz-border-radius: 2em; 47 | border-radius:2em; 48 | } 49 | /*页面头部 公用*/ 50 | .pageHead{ 51 | padding: 16px 32px 0 32px; 52 | background: #fff; 53 | border-bottom: 1px solid #e8eaec; 54 | p{ 55 | margin-bottom: 16px; 56 | } 57 | &>p:first-child{ 58 | color: #17233d; 59 | font-weight: 500; 60 | font-size: 20px; 61 | } 62 | &>p:last-child{ 63 | font-size: 14px; 64 | } 65 | } 66 | .pageContent{ 67 | margin-top: 16px; 68 | padding: 16px; 69 | background: #fff; 70 | border-radius: 4px; 71 | font-size: 14px; 72 | position: relative; 73 | transition: all .2s ease-in-out; 74 | .pageContent_head{ 75 | justify-content: space-between; 76 | border-bottom: 1px solid #e8eaec; 77 | padding: 14px 16px; 78 | &>div:first-child{ 79 | font-size: 16px; 80 | color: #17233d; 81 | font-weight: 500; 82 | } 83 | } 84 | .pageDiv{ 85 | padding: 16px; 86 | justify-content: flex-end; 87 | } 88 | } 89 | /*model垂直居中*/ 90 | .vertical-center-modal{ 91 | display: flex; 92 | align-items: center; 93 | justify-content: center; 94 | 95 | .ivu-modal{ 96 | top: 0; 97 | } 98 | } 99 | /*阴影效果*/ 100 | .hui-box-shadow{ 101 | position: relative; 102 | &:hover { 103 | z-index: 1; 104 | -webkit-box-shadow: 0 1px 6px rgba(0, 0, 0, .2); 105 | box-shadow: 0 1px 6px rgba(0, 0, 0, .2); 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App.vue"; 3 | import router from "./router"; // 路由 4 | import './router/permission'; // 加载路由权限控制 5 | import store from "./store"; 6 | import 'babel-polyfill' // 解决ie兼容问题 7 | import ViewUI from 'view-design'; 8 | import 'view-design/dist/styles/iview.css'; 9 | import "./css/public.less"; // 公共css样式 10 | Vue.use(ViewUI); 11 | // 工具 12 | import {backPage, isNotNull, isBlank} from "./utils/util"; 13 | // 按钮组件级别权限校验 14 | import checkPermission from "./utils/permission"; 15 | // 配置文件 16 | import {indexPage, loginPage} from "./config/config"; 17 | 18 | // 引入mock文件 目前登录使用的是在线mock 19 | // require('./mock/index') // mock 方式,正式发布时,注释掉该处即可 20 | 21 | Vue.prototype.$backPage = backPage 22 | Vue.prototype.$isNotNull = isNotNull 23 | Vue.prototype.$isBlank = isBlank 24 | Vue.prototype.$indexPage = indexPage 25 | Vue.prototype.$loginPage = loginPage 26 | Vue.prototype.$checkPermission = checkPermission 27 | 28 | Vue.config.productionTip = false; 29 | 30 | new Vue({ 31 | router, 32 | store, 33 | render: h => h(App) 34 | }).$mount("#app"); 35 | -------------------------------------------------------------------------------- /src/mock/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 定义本地模拟接口,最好与正式接口一致,避免联调阶段修改工作量 3 | * @author Xiaohui Zeng 4 | * @date 2019/10/5 5 | */ 6 | import Mock from 'mockjs' 7 | // 引入接口模板 8 | import {login} from './mock' 9 | 10 | Mock.setup({ 11 | timeout: '200 - 400', // 设置延迟响应,模拟向后端请求数据 12 | }) 13 | 14 | // Mock.mock( url, post/get , 返回的数据); 15 | // Mock.mock(/\/api\/healthPlat\/getRecipe\/\w*\/\w*/, 'post', ) 16 | Mock.mock('/api/login', 'post', login) 17 | 18 | export default Mock; -------------------------------------------------------------------------------- /src/mock/mock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 模拟接口 3 | * @author Xiaohui Zeng 4 | * @date 2019/10/5 5 | */ 6 | import Mock from 'mockjs' 7 | // 用户信息 8 | let userData = { 9 | userId: 'admin', 10 | userName: '曾小晖', 11 | password: '123456', 12 | roles: ['admin'] // 用户拥有的角色 数组 13 | } 14 | // 登陆 15 | export const login = (prarms) => { 16 | const prarmsObj = JSON.parse(prarms.body); 17 | console.log(prarmsObj); 18 | let backData = Mock.mock({ 19 | userId: userData.userId, 20 | userName: userData.userName, 21 | roles: userData.roles 22 | }) 23 | if (checkData(prarmsObj.userName,userData.userId) && checkData(prarmsObj.password,userData.password)) { 24 | return {code: 1, data: backData, info: '登录成功'} 25 | } else { 26 | return {code: 0, info: '账号或密码错误'} 27 | } 28 | } 29 | // 数据校验 30 | const checkData =(d1,d2) =>{ 31 | if(d1 === d2) { 32 | return true 33 | } else { 34 | return false 35 | } 36 | } 37 | //数据占位符使用 38 | /* 39 | { 40 | "integer": "@integer(10, 30)", //随机生成一个10~30之间的正整数 41 | "float": "@float(60, 100, 2, 2)", //随机生成浮点数,参数分别为整数部分最小值和最大值、小数部分保留最小位数和最大位数 42 | "boolean": "@boolean", //随机生成boolean 43 | "string|1-2": "@string", //随机生成字符串 44 | "name":"@cname", //随机生成名字 45 | 46 | "date": "@date(yyyy-MM-dd)", //按照格式随机生成时间 47 | "datetime": "@datetime", //随机生成时间 48 | "now": "@now", //当前时间 49 | 50 | "id": "@id", //随机生成一个 18 位身份证 51 | "guid": "@guid", //随机生成一个 GUID 52 | "url": "@url", //随机生成url字符串 53 | "email": "@email", //随机生成邮箱 54 | "image": "@image(200x200)", //随机生成一个大小为200x200的图片链接 55 | "title": "@title", //随机生成一句标题,其中每个单词的首字母大写 56 | "upper": "@upper(@title)", //把生随机成的标题全部转为大写 57 | "cparagraph": "@cparagraph", //随机生成一段中文文本 58 | "csentence": "@csentence", //随机生成一段中文文本 59 | "range": "@range(2, 10)" , //返回一个内容从2开始到9的整型数组 60 | 61 | "region": "@region", //随机生成地区 华中 62 | "province": "@province", //随机生成省会 省 63 | "city": "@city", //随机生成城市 市 64 | "county": "@county", //随机生成一个(中国)县 65 | }*/ 66 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | Vue.use(Router) 5 | import Main from "@/components/main/main"; 6 | 7 | /** 8 | meta: { 9 | hide: false, 是否在左侧菜单显示 不显示请设为 true 10 | title: "列表页面", 菜单标题 11 | icon: "md-grid", 图标 12 | roleId: 1 菜单权限id 不填写等同于不需要权限校验 13 | singlePage: true 单页 非嵌套栏目显示 14 | } 15 | */ 16 | 17 | // 不需要权限校验的静态路由 18 | export const constantRoutes = [ 19 | { 20 | path: "/login", 21 | name: "login", 22 | meta: { 23 | hide: true, 24 | title: "登录", 25 | // singlePage: true // 单页 26 | }, 27 | component: () => import("@/views/login/login") 28 | }, 29 | { 30 | path: "/", 31 | name: "Dashboard", 32 | component: Main, 33 | meta: { 34 | hide: false, 35 | title: "Dashboard", 36 | icon: "md-speedometer", 37 | }, 38 | children: [ 39 | { 40 | path: "/Dashboard/workplace", 41 | name: "workplace", 42 | meta: { 43 | hide: false, 44 | title: "主控台" 45 | }, 46 | component: () => import("@/views/Dashboard/workplace") 47 | }, 48 | { 49 | path: "/Dashboard/workplace1", 50 | name: "workplace1", 51 | meta: { 52 | hide: false, 53 | title: "工作台", 54 | permission: ['admin'] 55 | }, 56 | component: () => import("@/views/Dashboard/workplace1") 57 | }, 58 | ] 59 | }, 60 | { 61 | path: "/brief", 62 | name: "brief", 63 | component: Main, 64 | meta: { 65 | hide: false, 66 | title: "简叙", 67 | icon: "md-heart-outline", 68 | singlePage: true 69 | }, 70 | children: [ 71 | { 72 | path: "/brief/brief", 73 | name: "brief_brief", 74 | meta: { 75 | hide: false, 76 | title: "简叙", 77 | icon: 'md-heart-outline' 78 | }, 79 | component: () => import("@/views/brief/brief") 80 | } 81 | ] 82 | }, 83 | ] 84 | // 需要权限校验的异步路由 85 | export const asyncRoutes = [ 86 | { 87 | path: "/permission", 88 | name: "permission", 89 | redirect: '/permission-index', 90 | component: Main, 91 | meta: { 92 | hide: false, 93 | title: "权限测试", 94 | icon: "ios-switch-outline", 95 | roleId: 8, 96 | singlePage: true 97 | }, 98 | children: [ 99 | { 100 | path: "/permission-index", 101 | name: "permission-index", 102 | meta: { 103 | hide: false, 104 | title: "权限测试", 105 | roleId: 8 106 | }, 107 | component: () => import("@/views/permission") 108 | }, 109 | ] 110 | }, 111 | { 112 | path: "/form", 113 | name: "form", 114 | component: Main, 115 | meta: { 116 | hide: false, 117 | title: "表单页面", 118 | icon: "md-cube", 119 | roleId: 6 120 | }, 121 | children: [ 122 | { 123 | path: "/form/basic_form", 124 | name: "basic-form", 125 | meta: { 126 | hide: false, 127 | title: "基础表单", 128 | roleId: 5 129 | }, 130 | component: () => import("@/views/form/basic_form") 131 | }, 132 | { 133 | path: "/form/advanced_form", 134 | name: "advanced_form", 135 | meta: { 136 | hide: false, 137 | title: "高级表单", 138 | roleId: 7 139 | }, 140 | component: () => import("@/views/form/advanced_form") 141 | }, 142 | ] 143 | } 144 | ] 145 | 146 | const createRouter = () => new Router({ 147 | // mode: 'history', // require service support 148 | scrollBehavior: () => ({y: 0}), 149 | routes: constantRoutes 150 | }) 151 | 152 | const router = createRouter() 153 | 154 | export function resetRouter() { 155 | const newRouter = createRouter() 156 | router.matcher = newRouter.matcher // reset router 157 | } 158 | 159 | // 解决跳转同一个路由报错 160 | const originalPush = Router.prototype.push 161 | Router.prototype.push = function push(location) { 162 | return originalPush.call(this, location).catch(err => err) 163 | } 164 | 165 | export default router 166 | -------------------------------------------------------------------------------- /src/router/permission.js: -------------------------------------------------------------------------------- 1 | import router from './index' 2 | import store from '../store' 3 | import {indexPage, loginPage, siteTitle} from "../config/config"; 4 | import Vue from "vue"; 5 | import ViewUI from 'view-design'; 6 | 7 | const whiteList = ['/login', '/forget-password'] // 无需权限路由集合 8 | 9 | router.beforeEach(async(to, from, next) => { 10 | // 开始加载进度条 11 | ViewUI.LoadingBar.start() 12 | 13 | // 设置页面title 14 | document.title = to.meta.title+' -'+siteTitle 15 | 16 | // 确定用户是否已登录 17 | const isLogin = store.getters.isLogin 18 | if (isLogin) { // 判断是否有token 19 | if (to.path === '/login') { 20 | // 如果已登录,重定向到主页 21 | next({ name: indexPage }) 22 | // 停止加载进度条 23 | ViewUI.LoadingBar.finish() 24 | } else { 25 | // 确定用户是否已通过getInfo获得其权限角色 26 | const hasRoles = store.getters.roles && store.getters.roles.length > 0 27 | if (hasRoles) { 28 | // debugger 29 | let item = { 30 | fullPath: to.fullPath, 31 | meta: to.meta, 32 | name: to.name, 33 | path: to.path 34 | } 35 | // 增加tab多页签数据 36 | store.dispatch('tagsView/pushRouter', item) 37 | next() 38 | } else { 39 | try { 40 | // 获取用户信息 这里可以进行一些操作 41 | const { roles } = await store.dispatch('user/getInfo') 42 | 43 | // 根据角色生成可访问的路由 44 | const accessRoutes = await store.dispatch('permission/generateRoutes', roles) 45 | // 增加异步路由 46 | router.addRoutes(accessRoutes) 47 | 48 | // hack方法,以确保addRoutes是完整的 49 | // 设置replace:true,导航不会留下历史记录 50 | next({ ...to, replace: true }) 51 | } catch (error) { 52 | // 删除token并进入登录页面以重新登录 53 | // await store.dispatch('user/resetToken') 54 | ViewUI.Message.error(error || 'Has Error') 55 | next({name: loginPage}) 56 | // 停止加载进度条 57 | ViewUI.LoadingBar.finish() 58 | } 59 | } 60 | } 61 | } else { 62 | /* 没有 token*/ 63 | 64 | if (whiteList.indexOf(to.path) !== -1) { 65 | // 免权限直接进入 66 | next() 67 | } else { 68 | // 其他无权访问的页面将重定向到登录页面 69 | next({name: loginPage}) 70 | // 停止加载进度条 71 | ViewUI.LoadingBar.finish() 72 | } 73 | } 74 | }) 75 | 76 | router.afterEach((to, from, next) => { 77 | // 停止加载进度条 78 | ViewUI.LoadingBar.finish() 79 | // 路由切换 滚到到顶部 80 | window.scrollTo(0, 0); 81 | if(to.name !== loginPage){ 82 | // 如果不是登录页 内容区域滚动到顶部 83 | Vue.nextTick(() => { 84 | if (document.getElementById('main_content')) { 85 | document.getElementById('main_content').scrollTo(0, 0); 86 | } 87 | }) 88 | } 89 | }) 90 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | token: state => state.user.token, 3 | // 是否登录 4 | isLogin: state => state.user.isLogin, 5 | // 权限id集合 6 | roles: state => state.user.roles, 7 | // 已打开的路由数据 8 | routerArr: state => state.tagsView.routerArr, 9 | // 用户信息 10 | userData: state => state.user.userData, 11 | // 所有有权限的路由 12 | routes: state => state.permission.routes, 13 | // 异步路由 14 | addRoutes: state => state.permission.addRoutes, 15 | } 16 | export default getters 17 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import getters from './getters' 4 | 5 | Vue.use(Vuex) 6 | 7 | const modulesFiles = require.context('./modules', true, /\.js$/) 8 | 9 | // 自动加载 './modules'文件下的所有vuex模块 10 | const modules = modulesFiles.keys().reduce((modules, modulePath) => { 11 | // set './app.js' => 'app' 12 | const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1') 13 | const value = modulesFiles(modulePath) 14 | modules[moduleName] = value.default 15 | return modules 16 | }, {}) 17 | 18 | const store = new Vuex.Store({ 19 | modules, 20 | getters 21 | }) 22 | 23 | export default store 24 | -------------------------------------------------------------------------------- /src/store/modules/permission.js: -------------------------------------------------------------------------------- 1 | import { asyncRoutes, constantRoutes } from '@/router' 2 | 3 | /** 4 | * 使用meta.roleId确定当前用户是否具有权限 5 | * @param roles 6 | * @param route 7 | */ 8 | function hasPermission(roles, route) { 9 | if (route.meta && route.meta.roleId) { 10 | return roles.includes(route.meta.roleId) 11 | } else { 12 | // 如果存在子路由 子路由有一个有权限就返回true 否则false 13 | if (route.children) { 14 | const tmp = route.children 15 | let flag = false 16 | for (const v of tmp) { 17 | flag = hasPermission(roles, v) 18 | if (flag) { 19 | break 20 | } 21 | } 22 | return flag 23 | } else { 24 | return true 25 | } 26 | } 27 | } 28 | 29 | /** 30 | * 通过递归过滤异步路由表 31 | * @param routes asyncRoutes 32 | * @param roles 33 | */ 34 | export function filterAsyncRoutes(routes, roles) { 35 | const res = [] 36 | 37 | routes.forEach(route => { 38 | const tmp = { ...route } 39 | if (hasPermission(roles, tmp)) { 40 | if (tmp.children) { 41 | tmp.children = filterAsyncRoutes(tmp.children, roles) 42 | } 43 | res.push(tmp) 44 | } 45 | }) 46 | 47 | return res 48 | } 49 | 50 | const state = { 51 | // 所有有权限的路由 52 | routes: [], 53 | // 异步路由 54 | addRoutes: [] 55 | } 56 | // 处理路由数据 添加父级name 57 | const routersArr = (routers)=>{ 58 | for(let v of routers) { 59 | if (v.children) { 60 | setFuName(v.children, v) 61 | } 62 | function setFuName(chid, v) { 63 | // 开始处理子级 64 | for (let k of chid) { 65 | k.meta.fuName = v.name 66 | k.meta.fuTitle = v.meta.title 67 | } 68 | } 69 | } 70 | return routers 71 | } 72 | 73 | const mutations = { 74 | setRoutes: (state, routes) => { 75 | state.addRoutes = routes 76 | state.routes = routersArr(constantRoutes.concat(routes)) 77 | } 78 | } 79 | 80 | const actions = { 81 | generateRoutes({ commit }, roles) { 82 | return new Promise(resolve => { 83 | const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles) 84 | commit('setRoutes', accessedRoutes) 85 | resolve(accessedRoutes) 86 | }) 87 | } 88 | } 89 | 90 | export default { 91 | namespaced: true, 92 | state, 93 | mutations, 94 | actions 95 | } 96 | -------------------------------------------------------------------------------- /src/store/modules/tagsView.js: -------------------------------------------------------------------------------- 1 | import {indexPage} from '@/config/config' 2 | const state = { 3 | // 已打开的路由数据 4 | routerArr: [] // localStorage.getItem('dataRouter') ? JSON.parse(localStorage.getItem('dataRouter')) : 5 | } 6 | 7 | const mutations = { 8 | setRouterArr(state, data) { // 设置路由 9 | state.routerArr = data 10 | }, 11 | pushRouterArr(state, data) { // 增加路由 12 | const flag = state.routerArr.some(r => r.path === data.path) 13 | if(!flag) { 14 | // 如果是默认主页 就放到第一个 15 | if(data.name === indexPage) { 16 | state.routerArr.unshift(data) 17 | } else { 18 | state.routerArr.push(data) 19 | } 20 | } 21 | }, 22 | delRouterArr(state, index) { // 删除路由 23 | state.routerArr.splice(index, 1) 24 | } 25 | } 26 | 27 | const actions = { 28 | setRouterArr(context, data) { // 设置路由 29 | context.commit('setRouterArr', data); 30 | }, 31 | pushRouter(context, data) { // 增加路由 32 | context.commit('pushRouterArr', data); 33 | }, 34 | delRouter(context, index) { // 删除路由 35 | context.commit('delRouterArr', index); 36 | } 37 | } 38 | 39 | export default { 40 | namespaced: true, 41 | state, 42 | mutations, 43 | actions 44 | } 45 | -------------------------------------------------------------------------------- /src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import { login } from '@/api/user' 2 | const state = { 3 | // 用户信息数据 4 | userData: sessionStorage.getItem('userData') ? JSON.parse(sessionStorage.getItem('userData')) : {}, 5 | // 是否登陆 6 | isLogin: sessionStorage.getItem('userData') ? true : false, 7 | // 角色权限集合 8 | roles: [] 9 | } 10 | 11 | const mutations = { 12 | // 设置角色 13 | setRoles: (state, roles) => { 14 | state.roles = roles 15 | }, 16 | // 设置用户数据 17 | setUserData(state, data) { 18 | state.userData = data 19 | }, 20 | // 修改登陆状态 21 | setIsLogin(state, data) { 22 | state.isLogin = data 23 | } 24 | } 25 | 26 | const actions = { 27 | // 登录 28 | login({ commit }, userInfo) { 29 | const { userName, passWord } = userInfo 30 | return new Promise((resolve, reject) => { 31 | login({ userName: userName.trim(), passWord: passWord }).then(response => { 32 | const { data } = response 33 | sessionStorage.setItem('userData', JSON.stringify(data.userInfo)) 34 | sessionStorage.setItem('userToken', JSON.stringify(data.token)) 35 | commit('setUserData', data.userInfo) 36 | // 设置登录状态为false 37 | commit('setIsLogin', true) 38 | resolve() 39 | }).catch(error => { 40 | reject(error) 41 | }) 42 | }) 43 | }, 44 | 45 | // 获取用户信息 46 | getInfo({ commit, state }) { 47 | return new Promise((resolve, reject) => { 48 | if (!sessionStorage.getItem('userData')) { 49 | reject('验证失败,请重新登录') 50 | } 51 | const userInfo = JSON.parse(sessionStorage.getItem('userData')) 52 | if(!userInfo.roles) { 53 | console.error('权限不能为空数组,否则路由死循环') 54 | return 55 | } 56 | // userInfo.roles = ['admin'] 57 | commit('setRoles', userInfo.roles) 58 | resolve(userInfo) 59 | }) 60 | }, 61 | 62 | // 退出登录 63 | logout({ commit, state, dispatch }) { 64 | return new Promise((resolve, reject) => { 65 | // commit('setToken', '') 66 | // 清除用户数据 67 | commit('setUserData', '') 68 | // 清楚用户权限集合 69 | commit('setRoles', []) 70 | // 设置登录状态为false 71 | commit('setIsLogin', false) 72 | // 清除缓存的用户数据 73 | sessionStorage.removeItem('userData') 74 | sessionStorage.removeItem('userToken') 75 | // 清除缓存的多页签数据 76 | dispatch('tagsView/setRouterArr', [], { root: true }) 77 | localStorage.removeItem('dataRouter') 78 | resolve() 79 | }) 80 | }, 81 | 82 | // 设置用户数据 83 | setUserData(context, data) { 84 | context.commit('setUserData', data); 85 | }, 86 | // 修改登陆状态 87 | setIsLogin(context, data) { 88 | context.commit('setIsLogin', data); 89 | } 90 | } 91 | 92 | export default { 93 | namespaced: true, 94 | state, 95 | mutations, 96 | actions 97 | } 98 | -------------------------------------------------------------------------------- /src/utils/permission.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 按扭级权限校验 3 | * @author Xiaohui Zeng 4 | * @date 2020-05-16 5 | */ 6 | import store from '@/store' 7 | // 权限校验 8 | /** 9 | * @param {Array} roleIds 权限id 10 | * @returns {Boolean} 11 | * @example v-if="checkPermission([1])" 12 | */ 13 | export default function checkPermission(roleIds) { 14 | if (roleIds && roleIds instanceof Array && roleIds.length > 0) { 15 | const roles = store.getters && store.getters.roles 16 | let flag = false 17 | roleIds.map(r => { 18 | flag = roles.includes(r) 19 | }) 20 | return flag 21 | } else { 22 | // 如果是挂在到vue全局使用 v-if="$checkPermission([1])" 23 | console.error(`需要按照这个格式来设置 v-if="checkPermission([1])"`) 24 | return false 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/utils/util.js: -------------------------------------------------------------------------------- 1 | import router from "@/router" 2 | 3 | // 判断是否为空 不为空返回数据 为空返回-- 4 | export const isNotNull = (data, str) => { 5 | if (isBlank(str)) { 6 | str = "--" 7 | } 8 | if (data == null || data === 'null' || data === '' || data === undefined || data === 'undefined' || data === 'unknown') { 9 | return str 10 | } else { 11 | return data 12 | } 13 | } 14 | 15 | // 判断是否为空 返回true/false 16 | export const isBlank = (data) => { 17 | if (data == null || data === 'null' || data === '' || data === undefined || data === 'undefined' || data === 'unknown') { 18 | return true 19 | } else { 20 | return false 21 | } 22 | } 23 | 24 | // 返回上一页 25 | export const backPage = () => { 26 | router.go(-1) 27 | } 28 | -------------------------------------------------------------------------------- /src/views/Dashboard/workplace.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | -------------------------------------------------------------------------------- /src/views/Dashboard/workplace1.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | -------------------------------------------------------------------------------- /src/views/brief/brief.vue: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 简叙单页面 3 | * @author Xiaohui Zeng 4 | * @date 2019/10/6 5 | */ 6 | 46 | 47 | 62 | 63 | 81 | -------------------------------------------------------------------------------- /src/views/form/advanced_form.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | -------------------------------------------------------------------------------- /src/views/form/basic_form.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | -------------------------------------------------------------------------------- /src/views/login/login.vue: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 登录页面 3 | * @author Xiaohui Zeng 4 | * @date 2019/10/5 5 | */ 6 | 78 | 79 | 267 | 268 | 352 | -------------------------------------------------------------------------------- /src/views/permission/index.vue: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 权限测试 3 | * @author Xiaohui Zeng 4 | * @date 2020-05-16 5 | */ 6 | 39 | 40 | 55 | 56 | 61 | -------------------------------------------------------------------------------- /src/views/tmp.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | publicPath: "./", // ./相对路径 3 | productionSourceMap: false,// 打包时不生成.map文件 4 | // 这里写你调用接口的基础路径,来解决跨域,如果设置了代理,那你本地开发环境的axios的baseUrl要写为 '' ,即空字符串 5 | devServer: { 6 | open: true, 7 | proxy: { 8 | '/dev-api': { 9 | target: 'https://www.fastmock.site/mock/8b8187de5502cc6a522b78638621c2c4/HuiAdmin/', 10 | pathRewrite: { '^/dev-api': '' } 11 | } 12 | } 13 | } 14 | } 15 | --------------------------------------------------------------------------------