一、表单的验证
完成表单验证的步骤
-
在
el-form
表单元素上添加一个rules属性,rules中配置相关的验证规则 -
在
el-form
表单元素上添加:model
将data中验证的响应式数据关联起来 -
在
el-form-item
中添加prop属性,该属性一定和rules中的key值对应来
<el-form :rules="userFormRules" :model="userForm">
<el-form-item prop="username">
<el-input prefix-icon="el-icon-user-solid" v-model="userForm.username"></el-input>
</el-form-item>
</el-form>
export default {
data(){
return{
userForm:{
username:'',
password:''
},
userFormRules:{
username:[
{required:true,message:'用户名不能为空',trigger:'blur'}
]
}
}
}
}
登录功能实现
-
给登录按钮绑定单击事件
<el-button type="primary" class="loginbtn" @click="login()">登录</el-button>
-
在methods选项定义login方法,完成登录业务
methods:{
login(){
this.$refs.loginForm.validate(async args=>{
if(args){
const {code,message,token}=await this.$api.users.login(this.userForm)
if(code){
//this.$message.success(message)
this.$message({
message,
type:'success'
})
//保存token
setStore(TOKEN_KEY,token)
this.$router.replace('/')
}else{
this.$message.warning(message)
}
}
})
}
}
-
这里将保存token的方法写成了一个工具
在utils下创建h5store.js文件,编写代码如下
export const setStore=(key,value)=>localStorage.setItem(key,value)
export const getStore=key=>localStorage.getItem(key)
export const removeStore=key=>localStorage.removeItem(key)
在utils下创建constants.js,专门存放常量,便于后续的管理
export const TOKEN_KEY="token"
二、后台首页布局
1、静态页面布局
用于布局的容器组件,方便快速搭建页面的基本结构:
<el-container>
:外层容器。当子元素中包含 <el-header>
或 <el-footer>
时,全部子元素会垂直上下排列,否则会水平左右排列。
<el-header>
:顶栏容器。
<el-aside>
:侧边栏容器。
<el-main>
:主要区域容器。
<el-footer>
:底栏容器。
<template>
<el-container>
<!-- 侧边栏 -->
<el-aside width="220px">asider</el-aside>
<el-container>
<el-header>header</el-header>
<el-main>main</el-main>
</el-container>
</el-container>
</template>
2、左侧菜单栏
<template>
<el-container class="container">
<el-aside>
<div class="logo">蜗牛BOSS系统</div>
<el-menu
background-color="#112243"
text-color="#fff"
active-text-color="#ff0"
:unique-opened="true">
<el-submenu index="1">
<template slot="title">
<i class="el-icon-s-management"></i>
<span>控制面板</span>
</template>
<el-menu-item index="11">
<i class="el-icon-tickets"></i>
<span>工作台</span>
</el-menu-item>
<el-menu-item index="12">
<i class="el-icon-tickets"></i>
<span>报表分析</span>
</el-menu-item>
</el-submenu>
<el-submenu index="2">
<template slot="title">
<i class="el-icon-s-management"></i>
<span>业务管理</span>
</template>
<el-menu-item index="21">
<i class="el-icon-tickets"></i>
<span>学生管理</span>
</el-menu-item>
<el-menu-item index="22">
<i class="el-icon-tickets"></i>
<span>教师管理</span>
</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<el-container class="main-container">
<el-header></el-header>
<el-main></el-main>
</el-container>
</el-container>
</template>
3、动态菜单
-
在请求拦截器中设置Authorization
在request.js文件中进行设置
instance.interceptors.request.use(config=>{
//可以设置携带token信息
config.headers.Authorization=getStore(TOKEN_KEY)
return config
},err=>{
return Promise.reject(err)
})
-
在api中添加获取权限的api方法
//导入封装后的axios
import {instance} from '@/util/request'
export default{
getAuthMenus:()=>instance.get('/menus/getAuthMenus')
}
-
在Home组件中在created方法中调用获取权限的方法
export default {
data(){
return{
menus:[]
}
},
methods:{
async getAuthMenus(){
const result=await this.$api.users.getAuthMenus()
console.log('result',result);
this.menus=result.data
}
},
created(){
this.getAuthMenus()
}
}
-
在菜单栏中完成动态渲染
<el-menu
background-color="#112243"
text-color="#fff"
active-text-color="#ff0"
:unique-opened="true">
<el-submenu :index="item._id" v-for="item in menus" :key="item._id">
<template slot="title">
<i :class="item.icon"></i>
<span>{{item.title}}</span>
</template>
<el-menu-item :index="subitem._id" v-for="subitem in item.children" :key="subitem._id">
<i class="el-icon-tickets"></i>
<span>{{subitem.title}}</span>
</el-menu-item>
</el-submenu>
</el-menu>
4、静态路由的配置
实现步骤
-
在views的目录下按照模块创建相应的页面
按照老师指定的要求去定义路由页面
-
在config.js文件中进行二级的配置,这里配置二级路由路径的时候,请参照上图数据库表中的path字段来进行定义
//通过ES6的导入语法将组件导入进来
import Login from '@/views/Login'
import Home from '@/views/Home'
import NotFound from '@/views/NotFound'
export default[
{
path:'/login',
component:Login
},
{
path:'/',
redirect:'/home'
},
{
path:'/home',
component:Home,
children:[
{
path:'workplace',
component:()=>import('@/views/dashboard/workplace.vue')
},
{
path:'analysis',
component:()=>import('@/views/dashboard/analysis.vue')
}
]
},
{
path:'*',
component:NotFound
},
]
-
如果要让elementui的menu菜单启用vu-router路由模式,需要在
el-menu
标签中设置:router="true"
<el-menu
:router="true">
-
设置完如上的属性后,就需要在
el-menu-item
标签中将:index
的属性值,由之前的subItem._id
改成subItem.path
-
在Home的
el-main
区域内容配置二级路由出口
<el-main>
<!-- 配置二级路由出口 -->
<router-view></router-view>
</el-main>
5、菜单折叠
-
在Home.vue文件中定义数据isCollpase
export default {
data(){
return{
isCollapse:false,
logotitle:'蜗牛BOSS系统'
}
}
}
-
在
<el-header>
区域添加折叠和展开按钮,并根据isCollapse的值控制这两个按钮的显示或隐藏
<el-header>
<i v-if="!isCollapse" class="el-icon-s-fold"></i>
<i v-else class="el-icon-s-unfold"></i>
<!--...其余代码省略-->
</el-header>
-
为展开和折叠按钮添加绑定单击事件
<el-aside>
<i v-if="!isCollapse" class="el-icon-s-fold" @click="toggleCollpase" style="margin-left:190px"></i>
<i v-else class="el-icon-s-unfold" @click="toggleCollpase" style="margin-left:40px"></i>
<!--...其余代码省略-->
</el-aside>
methods: {
toggleCollpase(){
this.isCollapse=!this.isCollapse;
if(this.isCollapse){
this.logotitle="蜗牛"
}else{
this.logotitle="蜗牛BOSS系统"
}
}
}
-
在
<el-menu>
标签中添加collpase属性来控制菜单的折叠或展开
<el-menu :collapse="isCollapse">
-
在
<el-aside>
中动态设置菜单栏的宽度,同时删除掉之前style="width: 200px"
<el-aside :width="isCollapse?'68px':'220px'">
-
取消切换动画
<el-menu :collapse-transition="false">
-
去掉菜单栏边框
.el-menu{
border: none;
}
三、路由守卫
在单页面应用SPA,组件之间的切换(页面的跳转)实际上是通过路由跳转来完成,路由守卫的主要作用就是在进入目标路由的时候(可以在之前,也可以在后)先进入到这些函数里边,这些函数可以用来完成逻辑判断,适合要求的进入目标路由,不符合要求的会被拦截到其他地方去
我们把这种函数称为路由守卫函数
路由守卫函数分类
-
全局前置守卫:所有的路由都要进入的守卫函数,而且时机在前
-
全局后置守卫:所有的路由都要进入的守卫函数,时机在后
-
路由独享守卫:指定只进入指定路由的时候触发的函数
-
组件内守卫:进入组件触发,离开组件触发
1、全局前置守卫
-
在router/index.js文件中添加路由守卫
//设置路由全局前置守卫
router.beforeEach(async(to,from,next)=>{
console.log('*********路由全局前置守卫*************');
console.log('to',to);
if(to.path=="/login"){
next()//进入目标路由
}else{
console.log('*********您等等*************');
//获取token信息
if(getStore(TOKEN_KEY)){
try{
await Vue.prototype.$api.users.getUserInfo()
}catch(err){
next('/login')
}
console.log('result',result);
next()//进入到目标地
}else{
Vue.prototype.$message.warning("您还没有登录请先登录")
next("/login")
}
}
})
-
修改request.js中响应拦截器
instance.interceptors.response.use(response=>{
return response.data
},err=>{
if(err.response){
switch(err.response.status){
case 401:
Vue.prototype.$message.warning('Token已经过期,请重新登录')
break;
}
}
return Promise.reject(err)
})
2、动态设置路由规则
前提:由于项目中路由配置都是静态,除了这种方式之外还另外一种方式,是通过代码方法进行动态路由设置
router.beforeEach(async(to,from,next)=>{
if(to.path=="/login"){
next()
}else{
if(getStore(TOKEN_KEY)){
try{
const result=await Vue.prototype.$api.users.getUserInfo()
//定义一个变量用来接收后端的所有路由规则
let permissionList=result.data.permissionList
//将查出来的数据赋值到menus
menus=permissionList
//使用for循环进行遍历
for(let i=0;i<permissionList.length;i++){
let routeObj=permissionList[i]
//addRoute(参数1,参数2):用来动态添加一条新的路由规则,参数1:父路由的name,参数2:路由对象
router.addRoute("homeName",{
path:routeObj.path,
component:()=>import(`@/views${routeObj.permission}.vue`)
})
}
//查看所有路由
console.log('routes',router.getRoutes());
}catch(err){
next('/login')
}
next()//进入到目标地
}else{
Vue.prototype.$message.warning("您还没有登录请先登录")
next("/login")
}
}
})
如上错误的解决办法有三种
-
降低版本
-
在路由跳转中使用catch来处理
this.$router.replace('/').catch(()=>{})
-
统一重写replace或者push方法
const originalPush = VueRouter.prototype.push;
const originalReplace = VueRouter.prototype.replace;
//push
VueRouter.prototype.push = function push(location, onResolve, onReject) {
if (onResolve || onReject)
return originalPush.call(this, location, onResolve, onReject);
return originalPush.call(this, location).catch(err => err);
};
//replace
VueRouter.prototype.replace = function push(location, onResolve, onReject) {
if (onResolve || onReject)
return originalReplace.call(this, location, onResolve, onReject);
return originalReplace.call(this, location).catch(err => err);
};
如上问题解决完毕后,下面来打印一下添加好的路由规则
如上效果,就表示添加成功,路由添加成功后,我们需要让跳转的目标路由上,这里我们通过next({path\:to.path})方式让其进入,会发现网络发送多请求,造成严重影响,如下图所示
解决的办法设置menus变量,然后判断只有menus为空的时候,采取发送网路请求
//定义一个变量
let menus=null
//设置路由全局前置守卫:当进入目标路由之前会调用该函数
router.beforeEach(async(to,from,next)=>{
if(to.path=="/login"){
next()
}else{
if(getStore(TOKEN_KEY)){
if(!menus){
try{
const result=await Vue.prototype.$api.users.getUserInfo()
//定义一个变量用来接收后端的所有路由规则
let permissionList=result.data.permissionList
//将查出来的数据赋值到menus
menus=permissionList
//使用for循环进行遍历
for(let i=0;i<permissionList.length;i++){
let routeObj=permissionList[i]
//addRoute(参数1,参数2):用来动态添加一条新的路由规则,参数1:父路由的name,参数2:路由对象
router.addRoute("homeName",{
path:routeObj.path,
component:()=>import(`@/views${routeObj.permission}.vue`)
})
}
//查看所有路由
console.log('routes',router.getRoutes());
next({path:to.path})
}catch(err){
next('/login')
}
}
next()//进入到目标地
}else{
Vue.prototype.$message.warning("您还没有登录请先登录")
next("/login")
}
}
})
3、路由进度条
使用nprogress插件来完成路由进度操作
-
下载依赖包
npm i nprogress
-
配置
//导入nprogress依赖包
import NProgress from 'nprogress'
//将nprogress的css导入
import 'nprogress/nprogress.css'
NProgress.configure({
easing: "ease-in-out",
speed: 300,
trickeSpeed: 300,
})
-
在路由全局前置守卫上启动动画
router.beforeEach(async(to,from,next)=>{
NProgress.start()
})
-
在路由全局后置守卫上结束动画
router.afterEach((to,from)=>{
NProgress.done()
})