参考
Github高star项目:
手摸手,带你用vue撸后台 系列四(vueAdmin 一个极简的后台基础模板)
b站教学视频:
VUE项目,VUE项目实战,vue后台管理系统,前端面试,前端面试项目
项目总结
应用场景、作用
页面展示
1.登录页,做了权限的控制,非管理员账号只能看到首页和商品管理;
2.登录进去后,是首页看板(包括左上角的用户名称、权限名称;还有几个table表格、echarts图标展示数据)
3.顶部header区域,左侧是一个折叠菜单的按钮和一个面包屑;右侧是用户头像,可以点击进行登出;
4.左侧是菜单栏,有一级菜单和二级菜单;菜单栏里包括商品管理页面和用户管理页面;
5.点击菜单栏,header下面会相应出现tab,点击tab会切换不同的页面
6.用户管理页面,可以新增用户、对用户数据进行编辑;用户数据是分页的列表;顶部有一个搜索功能,使用姓名做数据层面的过滤
技术栈
1. vue-router
现在的页面基本上都是单应用,对于vue项目都是vuejs和vue-router结合形成的单页面应用。vue-router是vue官方指定的路由管理器,通过vue.js组合组件形成应用程序,vue-router将组件映射到浏览器的路由上,然后告诉vue-router在哪里渲染它。
在根目录下创建router文件夹(index.js),专门管理项目路由,main.js中引入vue-router。
index.js中,引入vue和vue-router,将vue-router全局引入,进行初始配置;就可以给首页写一个路由;在App.vue中放置路由容器<router-view></router-view>;
设置路由跳转,使用编程式导航$router.push();在Main主页中使用嵌套路由,显示主体区域;
2. vuex
store文件夹中引入vue和vuex,创造新的vuex实例;把引入的vuex实例注入到根组件中;这里的vuex使用modules模块,包括tab和user模块;
tab模块中,state包括isCollapse、tabList、currentMenu、menu。mutations包括collapseMenu(将控制侧边栏缩放状态的数据取反)、selectMenu(改变tabList内容,点击菜单栏就多一个tab(且当前点击高亮))、closeTag(点击tag的关闭按钮,数据源state也随之改变,只能通过mutation来改变)。使用$store.state.xxx取数据,$store.commit()调用方法
3. axios
axios是基于promise的http库,有几个好处
- 支持promiseAPI,可以避免回调地狱;
- 可以在请求和响应前进行拦截;
- 支持防御XSRF的攻击
npm安装axios;全局引入,axios不是插件,想在全局中使用,需要绑定在Vue的prototype属性上
调用class类:将接口请求都写在data.js中
4. element-ui
分为全局引入和按需引入,全局引入会下载很多不必要的组件,影响性能,真实项目中应该用按需引入。
5. 二次封装axios--接口请求的二次封装
需要将项目的配置逻辑添加到axios的请求中,通过配置来改变接口请求的地址,同时对所有接口进行监听,在请求前和请求后进行对应的拦截(在请求前添加统一的header,请求后捕获全局的catch,同时对异常的http状态进行处理)
将axios生成一个工具类,在api文件夹下新建axios.js文件;配置文件config中定义相关配置,baseUrl定义开发环境和生产环境
首先对当前环境变量进行判断;再写axios的工具类,class语法;getInsideConfig() 定义axios的相关配置;interceptors(instance)拦截器;request(options) 后续接口请求时,会调用此函数
总结:这只是简单封装,最主要的功能是将配置文件与axios进行结合
6. mock--后台接口的模拟数据
可以拦截ajax请求,并且在回调函数中通过自定义数据模板,或者说在回调函数中直接返回接口的响应数据,用于模拟后台返回的接口。
mock.js下对ajax请求进行拦截,mockServeData文件夹存放所有数据
7. echarts--数据的可视化展示
需要为echarts准备一个定义了宽高的DOM;在script中调用echarts的init方法,传如DOM节点;指定图表的配置项和数据;
echarts也有全局引入和按需引入
8. 其他
- less:npm下载less、以及less解析器less-loader
项目搭建
1.项目架构分析
2.项目模块搭建
3.脚手架搭建配置
yarn和npm的对比:
- npm的坑:
npm i时慢;包的版本号无法保持一致;安装时,所有包同时下载安装,导致包抛出错误时在终端发现不了 - yarn的优点:速度快(并行安装,npm是按队列执行每个package);离线模式(有缓存);安装版本统一;更简洁的输出;多注册来源处理;语义化
基于上,选择用yarn包管理工具(实际上,就开始的时候用了,后面也没用啊?)
vue-cli4脚手架搭建
- 首先需要nodejs环境(通过安装包安装nodejs环境时,会自带npm环境)
- 安装cnpm和yarn:来替代npm进行包管理
- 安装vue-cli脚手架:用cnpm来安装
- 创建项目:
vue create 项目名称;选择自己想要的插件(这里选默认的vue2+babel+eslint),此时项目基本文件和依赖项都生成和下载好了 - 启动项目:
npm run serve
4.组件初始化
5.路由初始化
6.vuex初始化
模块分配
1登录页
路由中定义登录页的路由;使用element-ui的form表单,对表单进行校验
2.后台首页
使用element-ui的Container布局;
侧边栏使用element-ui的NavMenu组件。因为每个页面都要用到侧边栏,故将其抽离成一个公共组件,定义在components文件夹下CommonAside.vue;添加数据,后面是用mock模拟的,对有子项目的菜单和无子项目的菜单分别使用计算属性;设置路由跳转,使用编程式导航,并且,在Main主页中使用嵌套路由,显示主体区域;
header部分也抽离成一个公共组件。左侧按钮的样式用element-ui的icon来实现,右侧下拉菜单用element-ui的dropdown。左侧按钮点击实现侧边栏收缩,而侧边栏收缩是通过iscollapse数据控制,用vuex来实现兄弟组件间的通信;
主体区域home页面 使用element-ui的Layout布局,card卡片显示登录信息(这里数据是写死的,要做优化),table组件;图表数据用mock模拟(getData()),这里echarts还封装了公共组件,我没有做
面包屑和tag区域:面包屑的数据用vuex存储;header区域使用element-ui的breadcrumb组件,在computed中将vuex属性注入;tag区域用element-ui的tag标签组件,将tag抽离成公共组件,对tag删除时,vuex中的数据源也要相应改变
3.用户管理页
顶部form表单区域,使用element-ui的form表单组件,封装CommonForm组件;当表单内容填写完后点击确认按钮,需要将数据上传,这里使用mock模拟拦截数据(createUser、updateUser分别处理数据);
table表格区域,封装成CommonTable组件,其中使用element-ui的table组件;注意父子组件之间的通信,用props和this.$emit;
4.分页处理
table下面使用element-ui的pagination组件
5.用户crud
addUser、editUser、delUser、getList、confirm(comfirm、delUser都会调用getList更新用户列表,tabledata存请求返回的数据)
6.路由守卫
什么时候校验token是否存在?在路由跳转的时候,可以用导航守卫来监听token是否存在,
7.权限管理
分为两个部分,第一个是登陆权限(在未登录的状态下通过路由不能访问到页面),第二个是菜单权限(在登陆权限的基础上加角色权限的验证,不同的用户看到不同的菜单)
登录机制:填写用户名和密码后,点击登录将用户名和密码传给后端,后端与数据库进行匹配,匹配后返回一个登录的凭证(token),前端将这个凭证缓存起来(cookie),登陆的时候将token传给后端验证,这样便建立起了登陆权限体系。
-
store中管理token,将token缓存的时候依赖这个js-cookie库;
-
使用vue-router的动态添加路由的方式,将之前写死的路由全部替换;permission中用mock模拟了接口数据
-
click事件触发登录操作,调用写好的接口(getMenu),服务器返回token(Mock.Random生成的)(返回code=2000、data(menu、token、message:“获取成功”))
getMenu(this.form).then(({ data: res }) => {
console.log(res, 'res')
//根据返回的数据,来做对应的处理
if (res.code === 20000) {
//清空Menu,cookie也移除menu
this.$store.commit('clearMenu')
//将服务器返回的menu数据传入vuex管理的Menu,并将menu存入cookie
this.$store.commit('setMenu', res.data.menu)
//将服务器返回的token值存入cookie
this.$store.commit('setToken', res.data.token)
//路由的动态添加
this.$store.commit('addMenu', this.$router)
//进行页面跳转,跳到首页
this.$router.push({ name: 'home' })
} else {
this.$message.warning(res.data.message)
}
})
- login设置权限,不同的用户对应不同的Menu(接口准备好了),vuex管理menu,侧边栏拿到menu的数据进行动态切换
- 登录权限
- 导航守卫:在路由进行跳转的时候,通过导航守卫来实现路由的监听。此部分即实现登陆权限验证。
router.beforeEach((to, from, next) => {
store.commit('getToken')
//拿到token
const token = store.state.user.token
//如果token不存在且要跳转的不是登录页,则让其返回登录页
if (!token && to.name !== 'login') {
next({ name: 'login' })
//如果token存在且要跳转的是login,则直接进首页
} else if (token && to.name === 'login') {
next({ name: 'home' })
}else {
next()
}
})
- 菜单权限
- 动态添加路由:
addRoute()给main添加children(不同权限的用户拿到不同的children(也就是menu))
if (!Cookie.get('menu')) {
return
}
const menu = JSON.parse(Cookie.get('menu'))
state.menu = menu
const menuArray = []
//相当于把侧边栏对应的页面都设置一个路由,添加完component属性。那么menuArray里面存入的就是路由的相关数据
menu.forEach(item => {
//有children的再进行一次遍历
if (item.children) {
item.children = item.children.map(item => {
item.component = () => import(`../views/${item.url}`)
return item
})
menuArray.push(...item.children)
} else {
item.component = () => import(`../views/${item.url}`)
menuArray.push(item)
}
});
// 路由的动态添加。即给layout(也就是main页面)添加children。
menuArray.forEach(item => {
router.addRoute('Main', item)
})
}
后端路由:后端中,采用的是后端渲染模式,就是在他的router中接收到了哪一项就会去寻找当前项对应的controller层,去渲染相应的界面。这个过程要经过http请求
前端路由:接收不同的路由,然后匹配当前路由对应的组件。前端路由的使用可以构建单页面,就是匹配不同的路由去显示不同的组件,不用经http请求,也不用重新加载资源,大大减少了服务区的压力。
8.顶部导航菜单与左侧导航联动的面包屑
vuex实现组件间的相互通信,selectMenu(改变tabList内容,点击菜单栏就多一个tab(且当前点击高亮))、closeTag(点击tag的关闭按钮,数据源state也随之改变,只能通过mutation来改变)。
header组件中使用element-ui的面包屑,需要拿到vuex中的tabList数据(在computed属性中使用mapState将数据拿过来);
Aside组件中点击菜单就会触发selectMenu方法,改变tabList内容;
tag组件也需要拿到vuex中的tabList数据,跟header组件中一样,点击tag会触发使路由跳转的方法,点击tag的删除按钮会触发closeTag(随之改变tabList)且改变路由。
9.路由懒加载
//非懒加载的情况
import List from '@/components/list.vue'
const router = new VueRouter({
routes: [
{ path: '/list', component: List }
]
})
//使用懒加载的情况。另外还有require等方法实现懒加载
const List = () => import('@/components/list.vue')
const router = new VueRouter({
routes: [
{ path: '/list', component: List }
]
})
//可以再简写
component: () => import('@/components/list.vue')
实现原理(参考):
- 前提:ES6动态地加载模块——import()。通过import()引用的子模块会被单独分离出来,打包成一个单独的文件Chunk
- 无论使用函数声明还是函数表达式创建函数,函数被创建后并不会立即执行函数内部的代码,只有等到函数被调用之后,才执行内部的代码。只要将需要进行懒加载的子模块文件(children chunk)的引入语句(import())放到一个函数内部。然后在需要加载的时候再执行该函数。这样就可以实现懒加载(按需加载)。
注意:
“
component(和components)配置接收一个返回 Promise 组件的函数,Vue Router 只会在第一次进入页面时才会获取这个函数,然后使用缓存数据。”—— 这说明更好的利用了浏览器的缓存
10.axios二次封装
将项目的配置逻辑添加到axios的请求中,通过配置来改变接口请求的地址,可以在本地调试线上环境。
同时对所有接口进行监听,在请求前和请求后进行对应的拦截。比如说,在请求前添加统一的header,在请求后捕获全局的cache,对异常的http状态进行监听和处理。