项目演示
https://www.bilibili.com/video/BV1dY411T71E?t=37.4
vue + element-ui 项目演示
八、项目实战八
Ⅰ、登录界面
1. 编写 login 页面
- ./views/Login/login.vue,编写登录页面
<template> <!--status-icon: 在输入框中显示校验结果反馈图标 --> <el-form :model="form" status-icon :rules="rules" ref="form" label-width="100px" class="login-container" > <h3 class="login_title">系统登录</h3> <!-- prop:定义在form中对应的字段 --> <el-form-item label="用户名" label-width="80px" prop="username" class="username" > <!-- autocomplete:表单是否启用自动完成功能。自动完成允许浏览器对字段的输入,是基于之前输入过的值。 --> <el-input type="input" v-model="form.username" autocomplete="off" placeholder="请输入账号" > </el-input> </el-form-item> <el-form-item label="密码" label-width="80px" prop="password" > <el-input type="password" v-model="form.password" autocomplete="off" placeholder="请输入密码" ></el-input> </el-form-item> <el-form-item class="login_submit"> <el-button type="primary" @click="login" class="login_submit">登录</el-button> </el-form-item> </el-form> </template> <script> // import Mock from 'mockjs' import {getMenu} from '../../api/data' export default { name: "Login", data() { return { form: {}, // 表单校验的定义 rules: { username: [ // 用户名校验:必需、没有输入会有提示、失去焦点触发 { required: true, message: "请输入用户名", trigger: "blur" }, { min: 3, message: "用户名长度不能小于3位", trigger: "blur", }, ], // 密码校验:必需、提示、失去焦点触发 password: [{ required: true, message: "请输入密码", trigger: "blur" }], }, }; }, }; </script> <style lang="less" scoped> .login-container { border-radius: 15px; // 背景裁剪的内边距 background-clip: padding-box; margin: 180px auto; width: 350px; padding: 35px 35px 15px 35px; background-color: #fff; border: 1px solid #eaeaea; box-shadow: 0 0 25px #cac6c6; } .login_title { margin: 0px auto 40px auto; text-align: center; color: #505458; } .login_submit { margin: 10px auto 0 auto; } </style>
登录页面
2. 登录权限 & 导航守卫
- 安装缓存插件
npm i js-cookie
- 在 store 文件中,创建 user.js 文件,用于缓存输入的内容。
import Cookie from "js-cookie"; export default { state: { token: "", }, mutations: { // 设置cookie setToken(state, val) { (state.token = val), Cookie.set("token", val); //cookie的名称,传入的值 }, // 清除cookie clearToken(state) { (state.token = ""), Cookie.remove("token"); }, // 获取cookie getToken(state) { // 如果当前的缓存中有token,直接获取。如果没有,要从state中获取 state.token = Cookie.get("token") || state.token; }, }, };
- 在 ./store/index.js 中导入
import Vue from 'vue' import Vuex from 'vuex' import tab from './tab' import user from './user' // 全局使用Vuex Vue.use(Vuex) export default new Vuex.Store({ // 模块化定义 modules:{ tab, user } })
- main.js 中添加前置路由守卫
// 前置路由守卫 router.beforeEach((to, from, next) => { store.commit('getToken') //防止页面刷新后vuex丢失token信息 const token = store.state.user.token // 如果token不存在,并且当前页不是登录页 if(!token && to.name !== 'login') { next({name: 'login'}) // 返回登录页 } else if(token && to.name === 'login'){ next({name: 'home'}) } else { next() } })
2. 登录接口逻辑
- ./api/mockServerData/permission.js,用于定义接口相关的逻辑
// 接口的相关逻辑 import Mock from 'mockjs' export default { // 模拟菜单权限,接收传递进来的参数 getMenu: config => { console.log(config); const { username, password } = JSON.parse(config.body) console.log(JSON.parse(config.body)) // 先判断用户是否存在 // 判断账号和密码是否对应 if (username === 'admin' && password === 'admin') { return { code: 20000, data: { menu: [ { path: '/home', name: 'home', label: '首页', icon: 's-home', url: 'home/index' }, { path: '/mall', name: 'mall', label: '商品管理', icon: 'video-play', url: 'mall/index' }, { path: '/user', name: 'user', label: '用户管理', icon: 'user', url: 'User/index' }, { label: '其他', icon: 'location', children: [ { path: '/page1', name: 'page1', label: '页面1', icon: 'setting', url: 'other/pageOne.vue' }, { path: '/page2', name: 'page2', label: '页面2', icon: 'setting', url: 'other/pageTwo.vue' } ] } ], token: Mock.Random.guid(), message: '获取成功' } } } else if (username === 'xiaoxiao' && password === 'xiaoxiao') { return { code: 20000, data: { menu: [ { path: '/', name: 'home', label: '首页', icon: 's-home', url: 'home/index' }, { path: '/mall', name: 'mall', label: '商品管理', icon: 'video-play', url: 'mall/index' } ], token: Mock.Random.guid(), message: '获取成功' } } } else { return { code: -999, data: { message: '密码错误' } } } } }
- 在 mock.js 中进行接口拦截
import permissionApi from './mockServerData/permission' Mock.mock(/permission\/getMenu/, 'post', permissionApi.getMenu)
3. 菜单权限功能
- 动态添加路由,在 tab.js 中定义 menu 空数组
menu: []
- 在 tab.js 的 mutations 中添加修改方法
setMenu(state, val) { state.menu = val Cookie.set('menu', JSON.stringify(val)) }, clearMenu(state) { state.menu = [] Cookie.remove('menu') }, addMenu(state, router) { if(!Cookie.get('menu')) { return } // 转成对象 const menu = JSON.parse(Cookie.get('menu')) state.menu = menu const menuArray = [] menu.forEach(item => { // 有二级菜单的数据 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) } }) // 路由的动态添加 menuArray.forEach(item => { router.addRoute('Main', item) }) }
- login.vue 中添加方法
login() { getMenu(this.form).then((res) => { console.log(res, "res"); // 接口调用成功 if (res.code === 20000) { // 登录成功后,清除当前路由 this.$store.commit("clearMenu"); // 设置路由,传入数据 this.$store.commit("setMenu", res.data.menu); // 设置token,传入接口的数据 this.$store.commit("setToken", res.data.token); // 动态添加路由,传入router 实例 this.$store.commit("addMenu", this.$router); // 页面跳转 this.$router.push({ name: "home" }); } else { //失败的提示 this.$message.warning(res.data.message); } }); }
这样就可以把 CommonAside.vue 中写死的数据去掉,只留 menu: []。
.router/index.js 中里面的数据都删掉,只保留 children: []。
- 在 CommonAside.vue 中定义 asyncMenu(),用来获取 menu
computed: { noChildren() { // 过滤出来没有子项目的数据 return this.asyncMenu.filter((item) => !item.children); }, hasChildren() { // 过滤出有子项目的数据 return this.asyncMenu.filter((item) => item.children); }, isCollapse() { return this.$store.state.tab.isCollapse; }, asyncMenu() { // 获取menu return this.$store.state.tab.menu } }
登录成功
Ⅱ、权限管理问题 & 退出登录
1. 刷新白屏的解决方法
- main.js 中,在 vue实例生成前, created 钩子中调用动态路由的方法。
created() { store.commit('addMenu', router) }
2. 权限管理
已经登录后,不应该还能访问登录页面,而是让它跳转到首页。
- 在 main.js 中修改路由守卫
router.beforeEach((to, from, next) => { store.commit('getToken') //防止页面刷新后vuex丢失token信息 const token = store.state.user.token // 如果token不存在,并且当前页不是登录页 if(!token && to.name !== 'login') { next({name: 'login'}) // 返回登录页 } else if(token && to.name === 'login'){ next({name: 'home'}) } else { next() } })
3. 退出功能
在 CommonHeader.vue 中添加退出功能
<el-dropdown-menu slot="dropdown"> <el-dropdown-item>个人中心</el-dropdown-item> <el-dropdown-item @click.native="logOut">退出</el-dropdown-item> </el-dropdown-menu>
logOut() { this.$store.commit("clearToken"); //清除token this.$store.commit("clearMenu"); //清除menu this.$router.push("/login"); //跳转到登录界面 }
点击退出,返回到登录界面
不积跬步无以至千里,不积小流无以成江海