Vue + ElementUI 后台管理项目实战

文章目录

  • 项目演示
  • 八、项目实战八
  • Ⅰ、登录界面
  • 1. 编写 login 页面
  • 2. 登录权限 & 导航守卫
  • 2. 登录接口逻辑
  • 3. 菜单权限功能
  • Ⅱ、权限管理问题 & 退出登录
  • 1. 刷新白屏的解决方法
  • 2. 权限管理
  • 3. 退出功能

项目演示

项目教学视频链接

vue + element-ui 项目演示

八、项目实战八

Ⅰ、登录界面
1. 编写 login 页面
  1. ./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>

登录页面

Element UI商品显示 element ui项目_Element UI商品显示

2. 登录权限 & 导航守卫
  1. 安装缓存插件
npm i js-cookie
  1. 在 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;
	    },
	  },
	};
  1. 在 ./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
	    }
	})
  1. 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. 登录接口逻辑
  1. ./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: '密码错误'
	        }
	      }
	    }
	  }
	}
  1. 在 mock.js 中进行接口拦截
import permissionApi from './mockServerData/permission'
	Mock.mock(/permission\/getMenu/, 'post', permissionApi.getMenu)
3. 菜单权限功能
  1. 动态添加路由,在 tab.js 中定义 menu 空数组
menu: []
  1. 在 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)
      })
    }
  1. 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: []。

  1. 在 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
	    }
	  }

登录成功

Element UI商品显示 element ui项目_elementui_02


Element UI商品显示 element ui项目_Element UI商品显示_03

Ⅱ、权限管理问题 & 退出登录
1. 刷新白屏的解决方法
  1. main.js 中,在 vue实例生成前, created 钩子中调用动态路由的方法。
created() {
      store.commit('addMenu', router)
    }
2. 权限管理

已经登录后,不应该还能访问登录页面,而是让它跳转到首页。

  1. 在 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"); //跳转到登录界面
    }

点击退出,返回到登录界面

Element UI商品显示 element ui项目_Element UI商品显示_04