项目地址 vue-admin-webapp 欢迎star,forkcss
相信许多人和我同样刚接触 vue 时看文档都很枯燥,看完 vue,还有 vueRouter 、vuex 、vue-cli、es6 (学不动了。。。 ) 对于看完教程以后又迟迟不能上手实际项目,只能写一些简单的小demo,这确定和实际生产工做是有出入的,因而乎我就打算本身从零开始使用最新的技术栈搭建一个vue后台管理系统,依此加深对理论知识的学习,并加强本身的项目能力,因此但愿本系列教程对你开发vue项目有所帮助。html
vue-admin-webapp 是一个后台管理 spa 页面,它基于 vue 和 element-ui 采用了最新的前端技术栈,实现了登陆权限验证,动态路由生成,并使用 easy-mock 来模拟请求数据,实现了典型的业务模型案例,它能够帮你快速搭建后台管理系统模板,并根据实际的业务需求添加路由来实现企业级管理页面,相信本项目必定能帮助到你。前端
- 在线预览-githubvue
- 在线预览-gitee (推荐国内用户)node
目前版本基于 webpack 4.0+
和 vue-cli 3.x
版本构建,须要 Node.js 8.9或更高版本(推荐8.11.0+),相关知识能够自行进官网进行了解android
- 登陆 / 注销
- 登陆仿GeeTest-极验安全策略
- 页面
- 初次进入引导用户
- sideBar收缩和展开
- 全屏控制
- 侧边栏
- 根据不一样用户权限展现相应的动态左侧菜单
- 权限验证
- 管理员页面
- 权限设置
- 表格操做
- 涉及日常业务遇到的相关表格操做(参考)
- Excel
- Excel导出
- Excel导入
- 多级表头导出
- Echarts
- 滑动显示更多数据
- 动态切换charts
- map地图使用
- Icons
- element-icon
- 阿里iconfont
复制代码
在开始以前,请确保在本地安装 node 和 webpack 及 git。 本项目涉及的技术栈主要有 ES6 、vue 、vuex 、vue-router 、vue-cli 、axios 、webpack 、element-ui 、easyMock ,因此你最好提早熟悉了解这些知识,这将对你认识学习该项目有很大帮助webpack
下面是整个项目的目录结构ios
├── public # 静态资源 │ ├── favicon.ico # favicon图标 │ └── index.html # html模板 ├── src # 源代码 │ ├── api # 全部请求 │ ├── assets # 图片、字体等静态资源 │ ├── components # 全局公用组件 │ ├── layout # 页面总体布局盒子 │ ├── mixins # 全局混入模块 │ ├── plugins # 全局插件部分 │ ├── router # 路由 │ ├── store # 全局store管理 │ ├── style # 全局样式 │ ├── utils # 全局公用方法 │ ├── vendor # 公用vendor(excel导入导出) │ ├── views # views全部页面 │ ├── App.vue # 入口页面 │ ├── main.js # 入口文件 加载组件 初始化等 ├── .borwserslistrc # 浏览器兼容相关 ├── .env.xxx # 环境变量配置  ├── .eslintrc.js # eslint 配置项 ├── .gitignore.js # git忽略文件设置 ├── .babelrc.config.js # babel-loader 配置 ├── package.json # package.json ├── postcss.config.js # postcss 配置 └── vue.config.js # vue-cli 配置 复制代码
# 克隆项目 git clone git@github.com:gcddblue/vue-admin-webapp.git # 进入项目目录 cd vue-admin-webapp # 安装依赖 npm install # 启动服务 npm run serve 复制代码
启动完成后将打开浏览器访问 http://localhost:8080
,接下来你就能够根据本身的实际需求,能够添加或修改路由,编写本身的业务代码。git
除去登陆页外,整个页面架构由三个部分组成 头部
侧边栏
右侧内容页
在项目@/layout/index.js文件中对对这三个组件进行封装,经过点击左侧菜单切换右侧router-view
的路由更替,对应的项目文件以下es6
在vue项目中,和后台进行请求交互这块,咱们一般都会选择axios库,它是基于promise的http库,可运行在浏览器端和node.js中。在本项目中主要实现了请求和响应拦截,get,post请求封装。
经过在项目中建立不一样环境的文件,我这里只建立了开发和生产环境的,固然,你也能够建立基于测试的.env.test
等文件,以.env.production
为例:
ENV = 'production' # base api VUE_APP_BASE_API = 'https://www.easy-mock.com/mock/5cee951f11690b5261b75566/admin' 复制代码
只要以 VUE_APP_
开头的变量都会被 webpack.DefinePlugin
静态嵌入到客户端的包中。你能够在应用的代码中这样访问它们,例如我在@/api/index.js中初始化axios:
const $axios = axios.create({ timeout: 30000, // 基础url,会在请求url中自动添加前置连接 baseURL: process.env.VUE_APP_BASE_API }) 复制代码
经过建立api文件夹将全部接口都集中在这个文件夹中,根据不一样的业务建立不一样js文件,来更好的划分接口的功能,其中index.js中代码以下:
import axios from 'axios' import Qs from 'qs' // 处理post请求数据格式 import store from '@/store' import router from '@/router' import Vue from 'vue' import { Loading, Message } from 'element-ui' // 引用element-ui的加载和消息提示组件 const $axios = axios.create({ // 设置超时时间 timeout: 30000, // 基础url,会在请求url中自动添加前置连接 baseURL: process.env.VUE_APP_BASE_API }) Vue.prototype.$http = axios // 这里并发请求以便在组件使用this.$http.all(),具体看dashborad页面 // 在全局请求和响应拦截器中添加请求状态 let loading = null /** * 请求拦截器 * 用于处理请求前添加loading、判断是否已保存token,并在每次请求头部添加token */ $axios.interceptors.request.use( config => { loading = Loading.service({ text: '拼命加载中' }) const token = store.getters.token if (token) { config.headers.Authorization = token // 请求头部添加token } return config }, error => { return Promise.reject(error) } ) /** * 响应拦截器 * 用于处理loading状态关闭、请求成功回调、响应错误处理 */ $axios.interceptors.response.use( response => { if (loading) { loading.close() } const code = response.status // 请求成功返回response.data if ((code >= 200 && code < 300) || code === 304) { return Promise.resolve(response.data) } else { return Promise.reject(response) } }, error => { if (loading) { loading.close() } console.log(error) if (error.response) { switch (error.response.status) { case 401: // 返回401 清除token信息并跳转到登录页面 store.commit('DEL_TOKEN') router.replace({ path: '/login', query: { redirect: router.currentRoute.fullPath } }) break case 404: Message.error('网络请求不存在') break default: Message.error(error.response.data.message) } } else { // 请求超时或者网络有问题 if (error.message.includes('timeout')) { Message.error('请求超时!请检查网络是否正常') } else { Message.error('请求失败,请检查网络是否已链接') } } return Promise.reject(error) } ) // get,post请求方法 export default { post(url, data) { return $axios({ method: 'post', url, data: Qs.stringify(data), headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' } }) }, get(url, params) { return $axios({ method: 'get', url, params }) } } 复制代码
如上,你们能够看个人注释说明,axios配置的封装是整个项目中很重要的模块,其实在不一样的项目中,axios封装都大同小异,因此,只要掌握了一种技巧,下次开发新项目也就很容易完成封装这块。
路由是组织一个vue项目的关键,在对项目原型分析后,接下来的第一步就是编写路由,本项目中,主要分为两种路由,currencyRoutes
和 asyncRoutes
currencyRoutes
:表明通用路由,意思就是不须要权限判断,不一样角色用户都显示的页面,如:登录页、404等
asyncRoutes
: 表明动态路由,须要经过判断权限动态分配的页面,有关的权限判断的方法接下来会介绍。
路由相关配置说明:
/** * 路由相关属性说明 * hidden: 当设置hidden为true时,意思不在sideBars侧边栏中显示 * mete{ * title: xxx, 设置sideBars侧边栏名称 * icon: xxx, 设置ideBars侧边栏图标 * noCache: true 当设置为true时不缓存该路由页面 * } */ 复制代码
本项目经过路由联动更新侧边栏,全部侧边栏配置都是在前端完成的,经过访问接口,后端会返回一个权限相关的list数组,其中数组值为路由的name属性值,前端经过递归遍历asyncRoutes
判断权限list中是否包含有对应的name路由,最终会返回包含该用户角色全部权限路由页面的addRoutes的数组对象。
具体实现是在路由index.js中设置一个全局前置导航守卫,具体判断流程以下:
// 导航守卫 router.beforeEach(async (to, from, next) => { document.title = getTitle(to.meta.title) if (to.path === '/login') { next() } else { if (store.getters.token) { const hasRoles = store.getters.roles.length > 0 if (hasRoles) { next() } else { try { const { roles } = await store.dispatch('user/_getInfo') const addRoutes = await store.dispatch( 'permission/getAsyncRoutes', roles ) router.addRoutes(addRoutes) // hack method to ensure that addRoutes is complete // set the replace: true, so the navigation will not leave a history record next({ ...to, replace: true }) } catch (error) { Message.error(error) } } } else { next({ path: '/login', query: { redirect: to.fullPath } }) } } }) 复制代码
这里我在经过addRoutes添加路由时,遇到一个bug,当切换角色时,并不能删除以前添加动态路由,因此这边从新初始化router.matcher
的属性方式实现:
const creatRouter = () => { return new Router({ routes: currencyRoutes, scrollBehavior() { return { x: 0, y: 0 } } }) } const router = creatRouter() // 解决addRoute不能删除动态路由问题 export function resetRouter() { const reset = creatRouter() router.matcher = reset.matcher } 复制代码
当我每次退出登陆的时候执行resetRouter
方法来初始化router对象,实现删除以前动态添加的路由。
最后经过element-ui的el-menu组件来递归遍历路由对象加载侧边栏。
身为前端开发人员,相信你们都知道Mock数据吧,它的做用主要就是伪造假数据使团队能够并行开发,本项目使用了 easy-mock 来实现接口数据的请求,你们能够去官网看下简单教程,easy-mock
它的好处就是不用像传统mock数据那样须要在项目中建立mock文件夹并拦截ajax来实现假数据请求,它是真真实实的api请求,并容许任何跨域请求,下面是本项目全部接口
其中全部接口经过建立 _res
字段来判断请求是否含有Authorzation头部字段是否含有token来判断用户是不是登录状态,以下 getCardsData接口的配置:
{ code: 0, data: { vistors: '@integer(10000, 100000)', message: '@integer(100, 1000)', order: '@integer(0, 1000)', profit: '@integer(1000, 100000)' }, _res: function({ _req, Mock }) { if (!_req.header.authorization) { return { status: 401, data: { msg: '未受权' } } } else { return { status: 200 } } } } 复制代码
mock数据在项目开发中可以起到推动项目进度的功效,你们能够预先和后端人员商量好,并先拿到假数据字段,而后mock本身的假数据,这样你就能够不用等后端人员开发接口而使项目卡住。通常在项目中,建立.env.development
和.env.production
文件,表明了开发和生产环境,在文件里能够定义不一样环境接口的请求url
# base api VUE_APP_BASE_API = 'https://www.easy-mock.com/mock/5cee951f11690b5261b75566/admin' 复制代码
在封装axios这样初始化
const $axios = axios.create({ // 设置超时时间 timeout: 30000, // 基础url,会在请求url中自动添加前置连接 baseURL: process.env.VUE_APP_BASE_API }) 复制代码
这样就能够自动根据不一样的环境切换请求地址,不用咱们一个一个的修改每个请求接口
经过将登陆函数封装在store中,当点击登录时,调用this.$store.dispatch('user/_login', this.ruleForm)
这个action方法,当后台接口验证成功时,会返回 token
字段,前端会调用 localStroage
接口将这个 token
保存在本地,之后每次请求前经过拦截器将这个token保存在 Authorization
这个头部字段中,后台只要验证这个token就知道这个用户的信息了。还不仅token的同窗,能够 疯狂点击token说明 里面对http为何要添加toekn及token介绍的都很详细。
这里我还采用了仿 geetest 行为验证,经过滑动图片来验证真人操做,其中原理利用 h5 canves绘制功能,绘制底部图片和滑块图片,而后监听mouseMove事件,当滑动block抠出的图片和初始化图片的y坐标差小于10时触发验证成功函数。
若是你的多个组件都用到一个或多个方法,咱们能够不用每次都粘贴复制,这样岂不是很low,咱们能够将这些方法封装在一个js文件中,当个人某个组件须要调用这个方法时
import aMixin from '@/mixins/a-mixin' export default { name: 'page1', mixins: [newsMixin] //调用mixins属性,将aMixin这个模块的数据及方法等都添加进这个组建吧 } 复制代码
这个方法有什么用呢,它主要是能够将一个对象冻结,防止对象被修改,那这个对vue项目有什么优化做用呢,你们都知道vue采用了数据劫持的方式遍历数据对象,把这些属性转为getter、settter方法来监听并通知数据的变化,因此当你遇到一个巨大的数组或者对象,而且肯定数据不会修改,这时就可使用 Object.freeze()
方法来组织vue对这个巨大数据的转化,,这可让性能获得很大的提高,举个例子:
new Vue({ data: { // vue不会对list里的object作getter、setter绑定 list: Object.freeze([ { value: 1 }, { value: 2 } ]) }, mounted () { // 界面不会有响应 this.list[0].value = 100; // 下面两种作法,界面都会响应 this.list = [ { value: 100 }, { value: 200 } ]; this.list = Object.freeze([ { value: 100 }, { value: 200 } ]); } }) 复制代码
当咱们某个组件或js文件须要引入多个模块时,通常作法就是,import每一个模块,这样显然是至关繁琐的,这时 require.context
函数将派上用场,那个这个函数到底怎么用呢,这里官法介绍是 主要用来实现自动化导入模块,在前端工程中,若是遇到从一个文件夹引入不少模块的状况,可使用这个api,它会遍历文件夹中的指定文件,而后自动导入,使得不须要每次显式的调用import导入模块
require.context
函数接受三个参数:
如
require.context('./test', false, /.test.js$/) #上面的代码遍历当前目录下的test文件夹的全部.test.js结尾的文件,不遍历子目录 复制代码
require.context
函数执行后返回一个函数,而且这个函数包含了三个属性:
咱们常会遍历keys返回的数组来对路径进行处理,这是至关方便的,最后 require.context
返回的函数接受keys放回数组中的路径成员做为参数,并返回这个路径文件的模块
下面是我使用 require.context
函数动态生成moudles对象
import Vue from 'vue' import Vuex from 'vuex' import getters from './getters' const path = require('path') Vue.use(Vuex) const files = require.context('./modules', false, /\.js$/) let modules = {} files.keys().forEach(key => { let name = path.basename(key, '.js') modules[name] = files(key).default || files(key) }) const store = new Vuex.Store({ modules, getters }) export default store 复制代码
对于一些不常改动的模块库,例如: vue
vueRouter
vuex
echarts
element-ui
等, 咱们让 webpack
不将他们进行打包,而是经过 cdn
引入,这样就能够减小代码大小,减小服务器带宽,并经过cdn将它们缓存起来,提升网站性能 。
具体实现就是修改 vue.config.js
,为对象模块添加 externals
完整配置以下:
const cdn = { css: [ // element-ui css 'https://unpkg.com/element-ui/lib/theme-chalk/index.css' ], js: [ // vue 'https://unpkg.com/vue/2.5.22/vue.min.js', // element-ui 'https://unpkg.com/element-ui/lib/index.js', // vue 'https://unpkg.com/vuex/3.1.0/vuex.min.js' ] } # 不打包vue、element-ui、vuex module.exports = { externals: { vue: 'Vue', 'element-ui':'ELEMENT', vuex: 'Vuex' }, chainWebpack: config => { config.plugin('html') .tap(args => { args[0].cdn = cdn return args }) } } 复制代码
接下来修改 index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <% if (process.env.NODE_ENV === 'production') { %> <!-- 引入样式 --> <% for(var css of htmlWebpackPlugin.options.cdn.css) { %> <link rel="stylesheet" href="<%=css%>"> <% } %> <!-- 引入js --> <% for(var js of htmlWebpackPlugin.options.cdn.js) { %> <script src="<%=js%>"></script> <% } %> <% } %> <title>vue-admin-webapp</title> </head> <body> <noscript> <strong>We're sorry but vue-admin-webapp doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> </body> </html> 复制代码
好了,大公告成
能够关注个人另外一篇文章 正确姿式使用vue cli3配置多页项目
能够关注个人另外一篇文章 正确姿式使用vue cli3建立项目
这个项目是我在上班之余断断续续开发的,没太写过技术贴,文笔和逻辑组织能力仍是至关差的,你们见谅。起初在没开始作以前以为应该至关的顺利的,没想到真正一步一步实现时,仍是和本身最初设想是有出入的,期间遇到了很多的bug,大多都是由于细节不注意,也让我更加体会 好记性不如烂笔头 这句话,实践才是真理啊,多动手,多探索。最后我会考虑使用 uni-app 这个框架来开发多平台(小程序、android、ios、h5)移动版vue后台管理系统,期待吧...