Vue.js快速入门之八:实现登录功能

        系统登录是指用户必须提供满足一定条件的信息后,才可以进入系统。最早系统一般是指用户名和密码,如今,登录方式已多元化,系统一般登录方式有:用户名+密码、二维码扫码登录、第三方授权登录、手机号+短信登录等等。移动端登录方式除以上几种外,还有手机号一键登录、人脸识别登录、指纹登录、语音登录等等。

        前面Vue讲解的这些篇幅了解后,可以实现一个简单的登录功能了,这里还是用传统的 用户名+密码 方式实现登录功能。

一、前期准备功能

        在之前几篇中,已详细说明了vue的搭建到各种插件的运用方法,这里不在细说。如果还不了解Vue的基本语法或写法,可以看上之前篇幅后,再来阅读此篇文章。地址如下:

地址一:Vue.js快速入门之一:安装和配置_觉醒法师的博客-CSDN博客_vuejs安装及环境配置

地址二:Vue.js快速入门之二:使用状态管理工具Vuex_觉醒法师的博客-CSDN博客_js中如何使用vuex

地址三:Vue.js快速入门之三:Vue-Router路由_觉醒法师的博客-CSDN博客

地址四:Vue.js快速入门之五:Mockjs的使用和语法详解_觉醒法师的博客-CSDN博客

地址五:Vue.js快速入门之六:Set和Map的妙用_觉醒法师的博客-CSDN博客

地址六:Vue.js快速入门之七:系统权限管理_觉醒法师的博客-CSDN博客_js权限管理

        现在咱们就来实现一个如下图的,简单的登录功能。

二、store本地状态管理器定义

2.1 state.js文件

const state = {
  /**
	 * 存储用户信息
	 */
	userInfo: {},
	/**
	 * 存储接口访问令牌
	 */
	token: "",
	/**
	 * 角色权限
	 */
	roles: [],
	/**
	 * 菜单列表
	 */
	menuList: []
}

export default state;

2.2 gettter.js文件

        这里除了正常返回userInfo,token, menu_list信息外,还增加了userRoles权限信息和menuList中有授权访问菜单信息;通过userRoles直接获取该用户当下所拥有的权限列表,通过menuList可以控制界面哪些栏目有权限访问并显示在菜单栏目中。

const getters = {
  /**
   * 用户信息
   */
  userInfo(state){
    if(state.userInfo){
      let { username, company } = state.userInfo;
      return { username, company };
    }else{
      return null;
    }
  },
  /**
   * 访问令牌
   */
  accessToken(state){
    return state.token;
  },
  /**
   * 用户角色
   */
  userRoles(state){
    return state.userInfo&&Array.isArray(state.userInfo['roles'])?state.userInfo.roles:[];
  },
  /**
   * 菜单列表
   */
  menu_list(state){
    return Array.isArray(state.menu_list)?state.menu_list:[];
  },
  /**
   * 过滤权限的菜单列表
   */
  menuList(state, {menu_list, userRoles}){
    return Array.isArray(menu_list)&&Array.isArray(userRoles)?menu_list.filter(
      item => Array.isArray(item['permission'])&&
      (
        item.permission.length==0 ||                                                        //没有权限信息的,直接通过
        userRoles.filter(sub => item.permission.includes(sub.roleName)).length>0            //在权限数组中的,可以通过
      )
    ):[];
  }
}
export default getters;

2.3 mutationTyoe.js文件

        在该文件中定义常量,作为统一指令进行操作。

/**
 * 用户信息
 */
export const USERINFO = "USERINFO";

/**
 * 访问令牌
 */
export const TOKEN = "TOKEN";

/**
 * 当前栏目列表
 */
export const MENU_LIST = "MENU_LIST";

2.4 mutaions.js文件

        vuex中所有变量值修改,都在mutations中处理,代码如下:

import { USERINFO, TOKEN, MENU_LIST } from './mutationsType'

/**
 * 裂变器
 */
const mutations = {
  /**
   * 修改访问令牌信息
   */
  [TOKEN](state, param){
    state.token = param;
  },
  /**
   * 修改用户信息
   */
  [USERINFO](state, param){
    state.userInfo = param;
  },
  /**
   * 修改菜单列表
   */
  [MENU_LIST](state, param){
    state.menu_list = param;
  }
}
export default mutations;

2.5 actions.js文件

        在业务层添加checkRolesRoutePermission校验函数,判断每次路由跳转时,判断该路由是否有访问权限。其目的是防止某些用户是通过收藏地址,直接通过路由进行访问。所以在路由卫士中添加些函数进行判断,则通过路由或栏目菜单点击时,都可被拦截到。

import Vue from 'vue'
import { MENU_LIST } from './mutationsType'
import { Loading } from 'element-ui'

/**
 * 业务层
 */
const actions = {
	/**
   * 保存登录信息
   */
  saveLoginInfo({commit}, param){
    if(param['token']) {
      commit(TOKEN, param.token);
      Vue.ls.set(TOKEN, param.token);
    }
    if(param['userinfo']) {
      commit(USERINFO, param.userinfo);
      Vue.ls.set(USERINFO, param.userinfo);
    }
  },
  /**
   * 退出登录
   */
  exitLogin({commit}, param){
    commit(TOKEN, '');
    commit(USERINFO, '');
    Vue.ls.remove(TOKEN);
    Vue.ls.remove(USERINFO);
  },
  /**
   * 检测是否登录
   */
  checkIsLogin({ commit, state }){
    let _token = Vue.ls.get(TOKEN),
        _userinfo = Vue.ls.get(USERINFO);

    if(!(state.token&&state.userInfo)&&(_token&&_userinfo)){
      commit(TOKEN, _token);
      commit(USERINFO, _userinfo);
    }
    return new Promise((resolve, reject) => {
      if(state.token&&state.userInfo){
        resolve();
      }else{
        reject();
      }
    })
  },
  /**
   * 检查路由访问权限
   */
  checkRolesRoutePermission({ commit, getters }, param){
    let handle = null,
        timeIndex = 0,
        timeTotal = 10,     //每1秒执行一次,执行10后未校验到权限,进入无权限页面
        loading = Loading.service({
          text: "权限校验中...",
          background: "rgba(0, 0, 0, 0)"
        }),
        //递归检测子项
        DGFilter = (_child, _path) => {
          return Array.isArray(_child) && _child.length>0 && _child.filter( item => item.path == _path || DGFilter(item['children'], _path) ).length > 0;
        },
        //检测回调函数
        callback = (resolve, reject) => {
          loading.close();

          let { menu_list, userRoles } = getters,
              //筛选出当前路径对应的一线栏目
              filterMenuList = menu_list.filter(
                item => item.path == param || DGFilter(item['children'], param)
              );
          //如果菜单列表中有筛选到数据,进入处理,否则放行
          if(filterMenuList.length>0){
            let _permission = filterMenuList[0]['permission'];
            //如果菜单列表中权限列表长度大于0,表示有权限数据,进入处理, 否则放行
            if(Array.isArray(_permission)&&_permission.length>0){
              //判断角色列表中,是否存在该该权限,存在则可放行,不存在,无法通行
              if( userRoles.filter(item => _permission.includes(item.roleName)).length>0 ){
                resolve();
              }else{
                reject({
                  code: 404,
                  message: "当前页面无访问权限"
                });
              }
            }else{
              resolve();
            }
          }else{
            resolve();
          }
          //if end
        };

    return new Promise((resolve, reject) => {
      if(getters.menuList.length>0){
         clearInterval(handle);
         callback(resolve, reject);
      }else{
        handle = setInterval(() => {
          //检测到菜单数组大于0时
          if(getters.menuList.length>0){
             clearInterval(handle);
             loading.close();
             callback(resolve, reject);
             return;
          }
          timeIndex++;
          //超时自动停止
          if(timeIndex>=timeTotal){
            clearInterval(handle);
            loading.close();
            reject({
              code: 404,
              message: "当前页面不存在"
            });
          }
        }, 1000);
      }
      //if end
    });
  },
  /**
   * 业务层 - 初始化菜单
   */
  initalMenu({commit}, params){
    if(Array.isArray(params)){
      commit(MENU_LIST, params);
    }
    //if end
  }
}

export default actions;

2.6 store/index.js文件

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import getters from './getters'
import actions from './actions'
import mutations from './mutations'

Vue.use(Vuex);

export default new Vuex.Store({
  state,
  getters,
  actions,
  mutations
})

2.7 注入Vue对象中

import Vue from 'vue'
import App from './App'
import store from '@/store/index'

new Vue({
  el: '#app',
  store,
  components: { App },
  template: '<App/>'
})

三、路由定义

3.1 路由定义

        在pages目录中,创建相应的页面。这次先创建首页(index)、登录页(login)、页面不存在(err404)、无访问权限(err404)。其他页面大家自行创建,这里只演示登录功能。

import Vue from 'vue'
import Router from 'vue-router'
import Error404 from '@/pages/Error/err404'
import Error405 from '@/pages/Error/err405'
import Index from '@/pages/index'
import Login from '@/pages/login'
import store from '@/store'

Vue.use(Router);

let _router = new Router({
  routes: [
    {
      path: '/',
      name: 'Index',
      component: Index,
    },
    {
      path: '/login',
      name: 'Login',
      component: Login,
    },
    {
      path: '/no-permission',
      name: 'Error405',
      component: Error405,
    },
    {
      path: '*',
      name: 'Error404',
      component: Error404,
    },
  ]
});

_router.beforeEach((toRoute, fromRoute, next) => {
  next();
});

3.2 路由卫士

        判断登录是否失效,以及路由访问权限进行校验。

        刚在vuex中的actions里,已定义了以下鉴权功能函数,直接调用作好对应处理即可。

_router.beforeEach((toRoute, fromRoute, next) => {
  //检测是否登录
 store.dispatch('checkIsLogin').then(() => {
    console.log('login success');
    //检测路由是否权限
     store.dispatch('checkRolesRoutePermission', toRoute.path).then(res => {
       //本页面禁用跳转
       if(toRoute.path!=fromRoute.path){
         next();
       }
     }).catch(e => {
       //本页面禁用跳转
       if(toRoute.path!=fromRoute.path){
         next(e.code==405?'/no-permission':'/error');
       }
     });
  }).catch(() => {
    console.log('login error...')
    if('/login'==toRoute.path){
      next();
    }else{
      next('/login')
    }
  });
});

四、API实现

        这里还是通过mockjs进行本地模拟接口的开发,如果有自己服务器小伙伴和懂后台语言的,如java、php、nodejs、C#等后端语言,可以开发真实系统进行登录操作。

4.1 封装axios请求

        在utils目录创建request.js文件,封闭axios请求,预定义header头部信息,拦截request和response请求和响应,作相应数据处理。

import axios from 'axios'
import { Message, Loading } from 'element-ui'

//配置全局数据请求类型
axios.defaults.headers['Content-Type'] = "application/json;charset=utf-8";

//实例新的请求
const Service = axios.create({
  baseURL: "",
  timeout: 30 * 1000
});

//配置加载参数
let loadingOption = {
  text: "正在努力加载中...",
  background: "rgba(0, 0, 0, 0)"
}, loading;

//请求拦截
Service.interceptors.request.use(config => {
  loading = Loading.service(loadingOption);
  //数据转换
  config.data = 'object'===typeof config.data?JSON.stringify(config.data):config.data;
  return config;
}, error => {
  loading.close();
  return Promise.reject(error);
})

Service.interceptors.response.use(response => {
  loading.close();
  if(response.status==200){
    return response['data'];
  }
  return Promise.reject(response);
}, error => {
  loading.close();
  return Promise.reject(error);
})

export default Service;

4.2 定义mockjs/index.js文件

        在mockjs/index.js文件中,定义模拟接口,实现登录功能。

import { mock } from 'mockjs'
import DBData from '@/db'
import { randomStrName } from '@/utils/utils'

/**
 * 获取栏目列表信息
 */
mock('/api/category/list', 'get', (request, response) => {
  let  _code = 200,  _result = {}, _msg = 'success';
  return {
    code: _code,
    data: DBData.get('category').map(item => item),
    message: _msg
  };
});

/**
 * 登录功能
 */
mock('/api/login', 'post', (request, response) => {
  let  _code = 0,  _result = {}, _msg = 'success';
  if(request.body){
    try{
      let _data = JSON.parse(request.body);
			//判断用户名和密码是否正确
      if(_data.username=='admin'&&_data.password=='123456'){
        _code = 200;
        _result = {
          token: randomStrName(30),
          users: DBData.get('users')
        };
      }else{
        _result = null;
        _msg = '用户名或密码错误';
      }
    }catch(e){
      console.log('error', e);
    }
  }
  return {
    code: _code,
    data: _result,
    message: _msg
  };
});

        这里把数据存储在db目录中了,也可直接放在mockjs/index.js文件中

import { Random } from 'mockjs'

/**
 * 定义数据库容器
 */
const DBData = new Map();

/**
 * 栏目信息
 */
DBData.set('category', [{
    "name": "首页",
    "path": "/",
    "permission": ['系统设置_首页'],
    "icon": "el-icon-data-line"
  },
  {
    "name": "栏目管理",
    "icon": "el-icon-data-board",
    "permission": ['系统设置_栏目管理'],
    "children": []
  },
  {
    "name": "内容管理",
    "path": "/auditing/index",
    "permission": ['系统设置_内容管理'],
    "icon": "el-icon-pie-chart"
  },
  {
    "name": "系统设置",
    "icon": "el-icon-data-analysis",
    "permission": ['系统设置_系统设置'],
    "children": []
  }
].map(item => {
  item['id'] = Random.id();
  return item;
}));

/**
 * 用户信息
 */
DBData.set('users', {
  username: "用户名",
  company: "公司信息",
  roles: [
    {
      "roleId": Random.id(),
      "roleName": "系统设置_首页"
    },
    {
      "roleId": Random.id(),
      "roleName": "系统设置_栏目管理"
    },
    {
      "roleId": Random.id(),
      "roleName": "系统设置_内容管理"
    },
    {
      "roleId": Random.id(),
      "roleName": "系统设置_系统设置"
    }
  ]
})

export default DBData;

4.3 定义api/index.js文件

        在api/index.js文件中,定义登录接口和退出登录接口。

import Service from '@/utils/request'

/**
 * 获取栏目列表
 */
export const getCategoryList = () => {
  return Service.get('/api/category/list', {});
}

/**
 * 登录
 */
export const loginInfo = params => {
  return Service.get('/api/category/list', params);
}

五、开发登录界面

        先实现登录界面的基本样式、输入功能和登录功能。

5.1 基本样式

        页面基本架构和Css样式代码如下:

html代码部分如下:

<template>
  <div class="container">
    <div class="login-box">
      <div class="title">
        <div class="login-info">
          <h3>系统登录</h3>
        </div>
      </div>
      <div class="content">
        <el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="ruleForm">
          <el-form-item label="用户名" prop="username">
            <el-input v-model="ruleForm.username"></el-input>
          </el-form-item>
          <el-form-item label="密码" prop="password">
            <el-input type="password" v-model="ruleForm.password" autocomplete="off" show-password></el-input>
          </el-form-item>

          <el-form-item>
            <el-button type="primary" class="btn-submit" @click="submitForm()">提交</el-button>
          </el-form-item>
        </el-form>
      </div>
    </div>

  </div>
</template>

css部分样式代码:

<style lang="scss" scoped>

.login-box{ text-align: center; padding: 50px 0; width: 600px; margin: 0 auto;
  .title{ padding: 15px 0;
    h3{ font-size: 24px;color: #666666; }
  }
  .content{ padding: 20px 0; }
}

.btn-submit{ width: 100%; }
</style>

5.2 输入和登录校验功能实现

        js部分变量和登录执行函数定义

<script>
  export default {
    data(){
      return {
        rules: {
          username: [
            { required: true, message: '请输入姓名' }
          ],
          password: [
            { required: true, message: '请输入密码' }
          ]
        },
        ruleForm: {}
      }
    },
    methods: {
      submitForm() {
        this.$refs['ruleForm'].validate((valid) => {
          if (valid) {
            console.log('submit!');
          } else {
            this.$message.info('账号或密码错误');
            return false;
          }
        });
      },
      //end
    }
  }
</script>

5.3 引入请求接口函数

        引入刚上面所创建的api/index.js,获取登录接口功能函数。

import { loginInfo } from '@/api/index.js'

5.4 实现登录功能

        对submitForm函数中,登录信息校验成功后,则可以调用登录接口了,实现登录后并获取相应用户信息,保存本地。

submitForm() {
	this.$refs['ruleForm'].validate((valid) => {
		if (valid) {
			loginInfo(this.ruleForm).then(res => {
				if(res.code==200){
					this.$store.dispatch('saveLoginInfo', {
						userinfo: res.data['users'],
						token: res.data['token']
					});
					setTimeout(() => {
						this.$router.push('/');
					}, 200);
				}else{
					this.ruleForm = {};
					this.$message.error(res.message);
				}
			});
		} else {
			this.$message.info('账号或密码错误');
			return false;
		}
	})
}

以上代码功能实现后,就可以进行登录操作了,登录后vuex状态管理器中,则会显示用户和菜单信息,如下图:

猜你喜欢

转载自blog.csdn.net/jiciqiang/article/details/115548471
今日推荐