vue实现后台管理系统-登陆步骤

登录功能的主要步骤

1、创建login.vue静态页面

1.1) 其中,el-form,会直接跳转到主页(确定按钮对应的方法,直接跳转到 “/dashboard”路由)
el-form组件:elementUI插件里面的一个组件,经常展示表单元素 model:用于收集表单数据 rules:表单验证规则

  • rules:表单验证规则具体定义在data(){}loginRules里表单验证,验证用户名与密码操作
    -【涉及三个参数 required: true(是否必填), trigger: ‘blur’(什么事件触发), validator: validateUsername(触发函数)】
  • 这里validateUsername需要额外引入utils/validate.js里已经封装好的方法(判断是否是https?:|mailto:|tal: 开头的链接)

eg: loginRules: {
username: [{ required: true, trigger: ‘blur’, validator: validateUsername }],
password: [{ required: true, trigger: ‘blur’, validator: validatePassword }]
},

//src/views/login/index.vue/<template>
<template>
  <div class="login-container">
  <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" >
 	 <div class="title-container">
        <h3 class="title">登录</h3>
     </div>
     <el-form-item prop="username">
        <span class="svg-container">
          <svg-icon icon-class="user" />
        </span>
        <el-input
          ref="username"
          v-model="loginForm.username"
          placeholder="Username"
          name="username"
          type="text"
          tabindex="1"
          auto-complete="on"
        />
      </el-form-item>

      <el-form-item prop="password">
        <span class="svg-container">
          <svg-icon icon-class="password" />
        </span>
        <el-input
          :key="passwordType"
          ref="password"
          v-model="loginForm.password"
          :type="passwordType"
          placeholder="Password"
          name="password"
          tabindex="2"
          auto-complete="on"
          @keyup.enter.native="handleLogin"
        />
        <span class="show-pwd" @click="showPwd">
          <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
        </span>
      </el-form-item>

      <el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登录</el-button>

      <div class="tips">
        <span style="margin-right:20px;">username: admin</span>
        <span> password: any</span>
      </div>

  </el-form>
    </div>
</template>

-utils/validate.js如下,并记得在login/index.vue/script引入

//src/utils/validate.js
/**
 * @param {string} path
 * @returns {Boolean}
 */
// 判断字符串是否是https?:|mailto:|tal: 开头的
export function isExternal(path) {
  return /^(https?:|mailto:|tel:)/.test(path)
}

/**
 * @param {string} str
 * @returns {Boolean}
 */
export function validUsername(str) {
  const valid_map = ['admin', 'editor']
  return valid_map.indexOf(str.trim()) >= 0
}

//src/views/login/index.js/<script>
<script>
import { validUsername } from '@/utils/validate'
data() {
    //具体定义表单提交事件函数:这里面在进行表单验证,验证用户名与密码操作
    const validateUsername = (rule, value, callback) => {
      if (!validUsername(value)) {
        callback(new Error('Please enter the correct user name'))
      } else {
        callback()
      }
    }
    const validatePassword = (rule, value, callback) => {
      if (value.length < 6) {
        callback(new Error('The password can not be less than 6 digits'))
      } else {
        callback()
      }
    }
    return {
      loginForm: {
        username: 'admin',
        password: '111111'
      },
      //这里定义具体loginRules
      loginRules: {
        username: [{ required: true, trigger: 'blur', validator: validateUsername }],
        password: [{ required: true, trigger: 'blur', validator: validatePassword }]
      },
      loading: false,
      passwordType: 'password',
      redirect: undefined
    }
  },
  </script>

1.2) 上面的提交按钮绑定事件 @click.native.prevent=“handleLogin”,事件操作如下,
假如账号密码验证格式全通过,那么发请求派发action,先触发封装的axios请求拦截器,在utils/request.js(首次登录不会拿到token),并在之后的每一次请求里把header里带上token,后请求后端接口user/login,(在actions里本地永久存储了token在了浏览器cookie里,详见store/user.js的actions:setToken(result.data.token);)

methods:{
handleLogin() {
      //这里是在验证表单元素(用户名与密码)的是否符合规则
      this.$refs.loginForm.validate(valid => {
        //如果符合验证规则
        if (valid) {
          //按钮会有一个loading效果
          this.loading = true;
          //派发一个action:user/login,带着用户名与密码的载荷
          this.$store.dispatch('user/login', this.loginForm).then(() => {
            //登录成功进行路由的跳转
            this.$router.push({ path: this.redirect || '/' });
            //loading效果结束
            this.loading = false
          }).catch(() => {
            this.loading = false
          })
        } else {
          console.log('error submit!!')
          return false
        }
      })
    }
    }

1.2.1)这里是发异步请求之前,先走的请求拦截器,就做一件事:给除了第一次登陆之后的每一次请求头都把token放在header里

//src/utils/request.js
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
//请求拦截器:把携带的token字段放到浏览器cookie里
service.interceptors.request.use(
  config => {
  //判断是否存在token,如果存在将携带的token字段放到浏览器cookie里
    if (store.getters.token) {
      config.headers['token'] = getToken()
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

//响应拦截器
  response => {
    const res = response.data

    //服务器响应失败在干什么,因为咱们真实服务器返回code  20000也有可能200
    if (res.code !== 20000 && res.code!=200) {
      Message({
        message: res.message || 'Error',
        type: 'error',
        duration: 5 * 1000
      })

      // 50008: 非法token; 50012: 其他client初测; 50014: Token expired;
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        // to re-login
        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
          confirmButtonText: 'Re-Login',
          cancelButtonText: 'Cancel',
          type: 'warning'
        }).then(() => {
          store.dispatch('user/resetToken').then(() => {
            location.reload()
          })
        })
      }
      return Promise.reject(new Error(res.message || 'Error'))
    } else {
    //服务器相应成功干什么
      return res
    }
  },
  error => {
    console.log('err' + error) // for debug
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)

这是把token放浏览器cookie的代码,//src/utils/auth.js

//src/utils/auth.js
//将携带的token字段放到浏览器cookie里
import Cookies from 'js-cookie'

const TokenKey = 'vue_admin_template_token'
export function getToken() {
  return Cookies.get(TokenKey)
}

export function setToken(token) {
  return Cookies.set(TokenKey, token)
}

export function removeToken() {
  return Cookies.remove(TokenKey)
}

1.3)定义vuex,做两件事
a)用来异步拿到user/login接口返回的数据(code),存到state.token里以及浏览器缓存cookie里
b)计算出当前用户需要展示所有路由(静态路由+异步路由+任意路由)

//引入登录|退出登录|获取用户信息的接口函数
import { login, logout, getInfo } from '@/api/user'
// 获取token|设置token|删除token的函数
import { getToken, setToken, removeToken } from '@/utils/auth'
//路由模块当中重置路由的方法
import { anyRoutes, resetRouter,asyncRoutes,constantRoutes} from '@/router';
import router from '@/router';
import cloneDeep from 'lodash/cloneDeep'

//箭头函数
const getDefaultState = () => {
  return {
    //获取token
    token: getToken(),
    //存储用户名
    name: '',
    //存储用户头像
    avatar: '',
    //服务器返回的菜单信息【根据不同的角色:返回的标记信息,数组里面的元素是字符串】
    routes:[],
    //角色信息
    roles:[],
    //按钮权限的信息
    buttons:[],
    //对比之后【项目中已有的异步路由,与服务器返回的标记信息进行对比最终需要展示的理由】
    resultAsyncRoutes:[],
    //用户最终需要展示全部路由
    resultAllRputes:[]
  }
}

const state = getDefaultState()

//唯一修改state的地方
const mutations = {
  //重置state
  RESET_STATE: (state) => {
    Object.assign(state, getDefaultState())
  },
  //存储token
  SET_TOKEN: (state, token) => {
    state.token = token
  },
  //存储用户信息
  SET_USERINFO:(state,userInfo)=>{
    //用户名
     state.name = userInfo.name;
     //用户头像
     state.avatar = userInfo.avatar;
     //菜单权限标记
     state.routes = userInfo.routes;
     //按钮权限标记
     state.buttons = userInfo.buttons;
     //角色
     state.roles = userInfo.roles;
  },
  //最终计算出的异步路由
  SET_RESULTASYNCROUTES:(state,asyncRoutes)=>{
     //vuex保存当前用户的异步路由,注意,一个用户需要展示完成路由:常量、异步、任意路由
     state.resultAsyncRoutes = asyncRoutes;
     //计算出当前用户需要展示所有路由(静态路由+异步路由+任意路由)
     state.resultAllRputes = constantRoutes.concat(state.resultAsyncRoutes,anyRoutes);
     //给路由器添加新的路由
      router.addRoutes(state.resultAllRputes)
  }
}


//定义一个函数:两个数组进行对比,对比出当前用户到底显示哪些异步路由
 const computedAsyncRoutes = (asyncRoutes,routes)=>{
     //过滤出当前用户【超级管理|普通员工】需要展示的异步路由
    return asyncRoutes.filter(item=>{
         //数组当中没有这个元素返回索引值-1,如果有这个元素返回的索引值一定不是-1 
        if(routes.indexOf(item.name)!=-1){
          //递归:别忘记还有2、3、4、5、6级路由
          if(item.children&&item.children.length){
              item.children = computedAsyncRoutes(item.children,routes);
          }
          return true;
        }
     })
 }

//actions
const actions = {
  //这里在处理登录业务
  async login({ commit }, userInfo) {
    //解构出用户名与密码
    const { username, password } = userInfo;
    //等待异步login函数返回请求后端返回后的结果,并判断响应code状态,这里的await后面login详见下段代码
    let result = await login({ username: username.trim(), password: password });
    //注意:当前登录请求现在使用mock数据,mock数据code是20000
    if(result.code==20000){
      //vuex存储token
      commit('SET_TOKEN',result.data.token);
      //这里本地持久化存储token
      setToken(result.data.token);
      return 'ok';
    }else{
      return Promise.reject(new Error('faile'));
    }
  },

  //获取用户信息
  getInfo({ commit, state }) {

    return new Promise((resolve, reject) => {
      getInfo(state.token).then(response => {
        //获取用户信息:返回数据包含:用户名name、用户头像avatar、routes[返回的标志:不同的用户应该展示哪些菜单的标记]、roles(用户角色信息)、buttons【按钮的信息:按钮权限用的标记】
        const { data } = response;
        //vuex存储用户全部的信息
        commit('SET_USERINFO',data);
        commit('SET_RESULTASYNCROUTES',computedAsyncRoutes(cloneDeep(asyncRoutes),data.routes));
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  },

  // user logout
  logout({ commit, state }) {
    return new Promise((resolve, reject) => {
      logout(state.token).then(() => {
        removeToken() // must remove  token  first
        resetRouter()
        commit('RESET_STATE')
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },

  // remove token
  resetToken({ commit }) {
    return new Promise(resolve => {
      removeToken() // must remove  token  first
      commit('RESET_STATE')
      resolve()
    })
  }
}



export default {
  namespaced: true,
  state,
  mutations,
  actions
}

2、目前为止,把token放到了cookie中,也放到了state里
接下里做两件事a、路由表的配置
路由分为三种,常态路由,异步路由。和任意路由
即(谁都能看得页面),(权限页面),和(兜底报错友好页面)
b)配置动态路由(全局路由守卫)

//router/index.js---路由表配置

//引入Vue|Vue-router
import Vue from 'vue'
import Router from 'vue-router'
//使用路由插件
Vue.use(Router)
export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },

  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },

  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: "Dashboard",
      component: () => import('@/views/dashboard/index'),
      meta: { title: '首页', icon: 'dashboard' }
    }]
  },
]



//异步理由:不同的用户(角色),需要过滤筛选出的路由,称之为异步路由
//有的用户可以看见测试管理、有的看不见
export const asyncRoutes = [
  {
  name: 'Acl',
  path: '/acl',
  component: Layout,
  redirect: '/acl/user/list',
  meta: {
    title: '权限管理',
    icon: 'el-icon-lock'
  },
  children: [
    {
      name: 'User',
      path: 'user/list',
      component: () => import('@/views/acl/user/list'),
      meta: {
        title: '用户管理',
      },
    },
    {
      name: 'Role',
      path: 'role/list',
      component: () => import('@/views/acl/role/list'),
      meta: {
        title: '角色管理',
      },
    },
    {
      name: 'RoleAuth',
      path: 'role/auth/:id',
      component: () => import('@/views/acl/role/roleAuth'),
      meta: {
        activeMenu: '/acl/role/list',
        title: '角色授权',
      },
      hidden: true,
    },
    {
      name: 'Permission',
      path: 'permission/list',
      component: () => import('@/views/acl/permission/list'),
      meta: {
        title: '菜单管理',
      },
    },
  ]
},
{
  path: '/product',
  component: Layout,
  name: 'Product',
  meta: { title: '商品管理', icon: 'el-icon-goods' },
  children: [
    {
      path: 'trademark',
      name: 'TradeMark',
      component: () => import('@/views/product/tradeMark'),
      meta: { title: '品牌管理' }
    },
    {
      path: 'attr',
      name: 'Attr',
      component: () => import('@/views/product/Attr'),
      meta: { title: '平台属性管理' }
    },
    {
      path: 'spu',
      name: 'Spu',
      component: () => import('@/views/product/Spu'),
      meta: { title: 'Spu管理' }
    },
    {
      path: 'sku',
      name: 'Sku',
      component: () => import('@/views/product/Sku'),
      meta: { title: 'Sku管理' }
    },
  ]
},
{
  path: '/test',
  component: Layout,
  name: 'Test',
  meta: { title: '测试管理', icon: 'el-icon-goods' },
  children: [
    {
      path: 'test1',
      name: 'Test1',
      component: () => import('@/views/Test/Test1'),
      meta: { title: '测试管理1' }
    },
    {
      path: 'test2',
      name: 'Test2',
      component: () => import('@/views/Test/Test2'),
      meta: { title: '测试管理2' }
    },
  ]
},
];


//任意路由:当路径出现错误的时候重定向404
export const anyRoutes ={ path: '*', redirect: '/404', hidden: true };

3、这是全局路由守卫,主要做一件事------判断是否有token→有token再看下一步跳转哪里?
a)去login页面,就直接跳根路径,
b)不去login,看state里有没有用户信息,没有就派发getInfo接口,重新获取用户信息(返回数据包含:用户名name、用户头像avatar、routes[返回的标志:不同的用户应该展示哪些菜单的标记]、roles(用户角色信息)、buttons【按钮的信息:按钮权限用的标记】),再跳转

//src/permission.js
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // 第三档插件进度条
import 'nprogress/nprogress.css' // 第三档插件进度条样式
import { getToken } from '@/utils/auth' // 从浏览器cookie里拿token
import getPageTitle from '@/utils/get-page-title'

NProgress.configure({ showSpinner: false }) // NProgress配置
//白名单
const whiteList = ['/login'] 
//全局前置路由守卫
router.beforeEach(async(to, from, next) => {
  // 进度条开始
  NProgress.start()

  // 动态展示标题
  document.title = getPageTitle(to.meta.title)

  // 前置路由守卫判断是否有token,并跳转
  const hasToken = getToken()

  if (hasToken) {
    if (to.path === '/login') {
      // 代表已经有token,重定向到根路径,又因为路由表里跟路由被重定向到dashboard,因此跳到dashboard
      next({ path: '/' })
      NProgress.done()
    } else {
    //获取登录用户名 如果用户名存在说明用户已经登录且获得了用户信息,直接放行,如果没有用户名,就进行用户信息获取
      const hasGetUserInfo = store.getters.name
      if (hasGetUserInfo) {
        next()
      } else {
        try {
          // 拿到用户信息
          await store.dispatch('user/getInfo')
          next()
        } catch (error) {
          // 重新跳转到登录页
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    /* 没token*/
    if (whiteList.indexOf(to.path) !== -1) {
      // 在白名单里, 放行
      next()
    } else {
      // other pages that do not have permission to access are redirected to the login page.
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  // finish progress bar
  NProgress.done()
})

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值