• 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文件

    1. const state = {
    2. /**
    3. * 存储用户信息
    4. */
    5. userInfo: {},
    6. /**
    7. * 存储接口访问令牌
    8. */
    9. token: "",
    10. /**
    11. * 角色权限
    12. */
    13. roles: [],
    14. /**
    15. * 菜单列表
    16. */
    17. menuList: []
    18. }
    19. export default state;

    2.2 gettter.js文件

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

    1. const getters = {
    2. /**
    3. * 用户信息
    4. */
    5. userInfo(state){
    6. if(state.userInfo){
    7. let { username, company } = state.userInfo;
    8. return { username, company };
    9. }else{
    10. return null;
    11. }
    12. },
    13. /**
    14. * 访问令牌
    15. */
    16. accessToken(state){
    17. return state.token;
    18. },
    19. /**
    20. * 用户角色
    21. */
    22. userRoles(state){
    23. return state.userInfo&&Array.isArray(state.userInfo['roles'])?state.userInfo.roles:[];
    24. },
    25. /**
    26. * 菜单列表
    27. */
    28. menu_list(state){
    29. return Array.isArray(state.menu_list)?state.menu_list:[];
    30. },
    31. /**
    32. * 过滤权限的菜单列表
    33. */
    34. menuList(state, {menu_list, userRoles}){
    35. return Array.isArray(menu_list)&&Array.isArray(userRoles)?menu_list.filter(
    36. item => Array.isArray(item['permission'])&&
    37. (
    38. item.permission.length==0 || //没有权限信息的,直接通过
    39. userRoles.filter(sub => item.permission.includes(sub.roleName)).length>0 //在权限数组中的,可以通过
    40. )
    41. ):[];
    42. }
    43. }
    44. export default getters;

    2.3 mutationTyoe.js文件

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

    1. /**
    2. * 用户信息
    3. */
    4. export const USERINFO = "USERINFO";
    5. /**
    6. * 访问令牌
    7. */
    8. export const TOKEN = "TOKEN";
    9. /**
    10. * 当前栏目列表
    11. */
    12. export const MENU_LIST = "MENU_LIST";

    2.4 mutaions.js文件

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

    1. import { USERINFO, TOKEN, MENU_LIST } from './mutationsType'
    2. /**
    3. * 裂变器
    4. */
    5. const mutations = {
    6. /**
    7. * 修改访问令牌信息
    8. */
    9. [TOKEN](state, param){
    10. state.token = param;
    11. },
    12. /**
    13. * 修改用户信息
    14. */
    15. [USERINFO](state, param){
    16. state.userInfo = param;
    17. },
    18. /**
    19. * 修改菜单列表
    20. */
    21. [MENU_LIST](state, param){
    22. state.menu_list = param;
    23. }
    24. }
    25. export default mutations;

    2.5 actions.js文件

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

    1. import Vue from 'vue'
    2. import { MENU_LIST } from './mutationsType'
    3. import { Loading } from 'element-ui'
    4. /**
    5. * 业务层
    6. */
    7. const actions = {
    8. /**
    9. * 保存登录信息
    10. */
    11. saveLoginInfo({commit}, param){
    12. if(param['token']) {
    13. commit(TOKEN, param.token);
    14. Vue.ls.set(TOKEN, param.token);
    15. }
    16. if(param['userinfo']) {
    17. commit(USERINFO, param.userinfo);
    18. Vue.ls.set(USERINFO, param.userinfo);
    19. }
    20. },
    21. /**
    22. * 退出登录
    23. */
    24. exitLogin({commit}, param){
    25. commit(TOKEN, '');
    26. commit(USERINFO, '');
    27. Vue.ls.remove(TOKEN);
    28. Vue.ls.remove(USERINFO);
    29. },
    30. /**
    31. * 检测是否登录
    32. */
    33. checkIsLogin({ commit, state }){
    34. let _token = Vue.ls.get(TOKEN),
    35. _userinfo = Vue.ls.get(USERINFO);
    36. if(!(state.token&&state.userInfo)&&(_token&&_userinfo)){
    37. commit(TOKEN, _token);
    38. commit(USERINFO, _userinfo);
    39. }
    40. return new Promise((resolve, reject) => {
    41. if(state.token&&state.userInfo){
    42. resolve();
    43. }else{
    44. reject();
    45. }
    46. })
    47. },
    48. /**
    49. * 检查路由访问权限
    50. */
    51. checkRolesRoutePermission({ commit, getters }, param){
    52. let handle = null,
    53. timeIndex = 0,
    54. timeTotal = 10, //每1秒执行一次,执行10后未校验到权限,进入无权限页面
    55. loading = Loading.service({
    56. text: "权限校验中...",
    57. background: "rgba(0, 0, 0, 0)"
    58. }),
    59. //递归检测子项
    60. DGFilter = (_child, _path) => {
    61. return Array.isArray(_child) && _child.length>0 && _child.filter( item => item.path == _path || DGFilter(item['children'], _path) ).length > 0;
    62. },
    63. //检测回调函数
    64. callback = (resolve, reject) => {
    65. loading.close();
    66. let { menu_list, userRoles } = getters,
    67. //筛选出当前路径对应的一线栏目
    68. filterMenuList = menu_list.filter(
    69. item => item.path == param || DGFilter(item['children'], param)
    70. );
    71. //如果菜单列表中有筛选到数据,进入处理,否则放行
    72. if(filterMenuList.length>0){
    73. let _permission = filterMenuList[0]['permission'];
    74. //如果菜单列表中权限列表长度大于0,表示有权限数据,进入处理, 否则放行
    75. if(Array.isArray(_permission)&&_permission.length>0){
    76. //判断角色列表中,是否存在该该权限,存在则可放行,不存在,无法通行
    77. if( userRoles.filter(item => _permission.includes(item.roleName)).length>0 ){
    78. resolve();
    79. }else{
    80. reject({
    81. code: 404,
    82. message: "当前页面无访问权限"
    83. });
    84. }
    85. }else{
    86. resolve();
    87. }
    88. }else{
    89. resolve();
    90. }
    91. //if end
    92. };
    93. return new Promise((resolve, reject) => {
    94. if(getters.menuList.length>0){
    95. clearInterval(handle);
    96. callback(resolve, reject);
    97. }else{
    98. handle = setInterval(() => {
    99. //检测到菜单数组大于0时
    100. if(getters.menuList.length>0){
    101. clearInterval(handle);
    102. loading.close();
    103. callback(resolve, reject);
    104. return;
    105. }
    106. timeIndex++;
    107. //超时自动停止
    108. if(timeIndex>=timeTotal){
    109. clearInterval(handle);
    110. loading.close();
    111. reject({
    112. code: 404,
    113. message: "当前页面不存在"
    114. });
    115. }
    116. }, 1000);
    117. }
    118. //if end
    119. });
    120. },
    121. /**
    122. * 业务层 - 初始化菜单
    123. */
    124. initalMenu({commit}, params){
    125. if(Array.isArray(params)){
    126. commit(MENU_LIST, params);
    127. }
    128. //if end
    129. }
    130. }
    131. export default actions;

    2.6 store/index.js文件

    1. import Vue from 'vue'
    2. import Vuex from 'vuex'
    3. import state from './state'
    4. import getters from './getters'
    5. import actions from './actions'
    6. import mutations from './mutations'
    7. Vue.use(Vuex);
    8. export default new Vuex.Store({
    9. state,
    10. getters,
    11. actions,
    12. mutations
    13. })

    2.7 注入Vue对象中

    1. import Vue from 'vue'
    2. import App from './App'
    3. import store from '@/store/index'
    4. new Vue({
    5. el: '#app',
    6. store,
    7. components: { App },
    8. template: ''
    9. })

    三、路由定义

    3.1 路由定义

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

    1. import Vue from 'vue'
    2. import Router from 'vue-router'
    3. import Error404 from '@/pages/Error/err404'
    4. import Error405 from '@/pages/Error/err405'
    5. import Index from '@/pages/index'
    6. import Login from '@/pages/login'
    7. import store from '@/store'
    8. Vue.use(Router);
    9. let _router = new Router({
    10. routes: [
    11. {
    12. path: '/',
    13. name: 'Index',
    14. component: Index,
    15. },
    16. {
    17. path: '/login',
    18. name: 'Login',
    19. component: Login,
    20. },
    21. {
    22. path: '/no-permission',
    23. name: 'Error405',
    24. component: Error405,
    25. },
    26. {
    27. path: '*',
    28. name: 'Error404',
    29. component: Error404,
    30. },
    31. ]
    32. });
    33. _router.beforeEach((toRoute, fromRoute, next) => {
    34. next();
    35. });

    3.2 路由卫士

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

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

    1. _router.beforeEach((toRoute, fromRoute, next) => {
    2. //检测是否登录
    3. store.dispatch('checkIsLogin').then(() => {
    4. console.log('login success');
    5. //检测路由是否权限
    6. store.dispatch('checkRolesRoutePermission', toRoute.path).then(res => {
    7. //本页面禁用跳转
    8. if(toRoute.path!=fromRoute.path){
    9. next();
    10. }
    11. }).catch(e => {
    12. //本页面禁用跳转
    13. if(toRoute.path!=fromRoute.path){
    14. next(e.code==405?'/no-permission':'/error');
    15. }
    16. });
    17. }).catch(() => {
    18. console.log('login error...')
    19. if('/login'==toRoute.path){
    20. next();
    21. }else{
    22. next('/login')
    23. }
    24. });
    25. });

    四、API实现

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

    4.1 封装axios请求

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

    1. import axios from 'axios'
    2. import { Message, Loading } from 'element-ui'
    3. //配置全局数据请求类型
    4. axios.defaults.headers['Content-Type'] = "application/json;charset=utf-8";
    5. //实例新的请求
    6. const Service = axios.create({
    7. baseURL: "",
    8. timeout: 30 * 1000
    9. });
    10. //配置加载参数
    11. let loadingOption = {
    12. text: "正在努力加载中...",
    13. background: "rgba(0, 0, 0, 0)"
    14. }, loading;
    15. //请求拦截
    16. Service.interceptors.request.use(config => {
    17. loading = Loading.service(loadingOption);
    18. //数据转换
    19. config.data = 'object'===typeof config.data?JSON.stringify(config.data):config.data;
    20. return config;
    21. }, error => {
    22. loading.close();
    23. return Promise.reject(error);
    24. })
    25. Service.interceptors.response.use(response => {
    26. loading.close();
    27. if(response.status==200){
    28. return response['data'];
    29. }
    30. return Promise.reject(response);
    31. }, error => {
    32. loading.close();
    33. return Promise.reject(error);
    34. })
    35. export default Service;

    4.2 定义mockjs/index.js文件

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

    1. import { mock } from 'mockjs'
    2. import DBData from '@/db'
    3. import { randomStrName } from '@/utils/utils'
    4. /**
    5. * 获取栏目列表信息
    6. */
    7. mock('/api/category/list', 'get', (request, response) => {
    8. let _code = 200, _result = {}, _msg = 'success';
    9. return {
    10. code: _code,
    11. data: DBData.get('category').map(item => item),
    12. message: _msg
    13. };
    14. });
    15. /**
    16. * 登录功能
    17. */
    18. mock('/api/login', 'post', (request, response) => {
    19. let _code = 0, _result = {}, _msg = 'success';
    20. if(request.body){
    21. try{
    22. let _data = JSON.parse(request.body);
    23. //判断用户名和密码是否正确
    24. if(_data.username=='admin'&&_data.password=='123456'){
    25. _code = 200;
    26. _result = {
    27. token: randomStrName(30),
    28. users: DBData.get('users')
    29. };
    30. }else{
    31. _result = null;
    32. _msg = '用户名或密码错误';
    33. }
    34. }catch(e){
    35. console.log('error', e);
    36. }
    37. }
    38. return {
    39. code: _code,
    40. data: _result,
    41. message: _msg
    42. };
    43. });

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

    1. import { Random } from 'mockjs'
    2. /**
    3. * 定义数据库容器
    4. */
    5. const DBData = new Map();
    6. /**
    7. * 栏目信息
    8. */
    9. DBData.set('category', [{
    10. "name": "首页",
    11. "path": "/",
    12. "permission": ['系统设置_首页'],
    13. "icon": "el-icon-data-line"
    14. },
    15. {
    16. "name": "栏目管理",
    17. "icon": "el-icon-data-board",
    18. "permission": ['系统设置_栏目管理'],
    19. "children": []
    20. },
    21. {
    22. "name": "内容管理",
    23. "path": "/auditing/index",
    24. "permission": ['系统设置_内容管理'],
    25. "icon": "el-icon-pie-chart"
    26. },
    27. {
    28. "name": "系统设置",
    29. "icon": "el-icon-data-analysis",
    30. "permission": ['系统设置_系统设置'],
    31. "children": []
    32. }
    33. ].map(item => {
    34. item['id'] = Random.id();
    35. return item;
    36. }));
    37. /**
    38. * 用户信息
    39. */
    40. DBData.set('users', {
    41. username: "用户名",
    42. company: "公司信息",
    43. roles: [
    44. {
    45. "roleId": Random.id(),
    46. "roleName": "系统设置_首页"
    47. },
    48. {
    49. "roleId": Random.id(),
    50. "roleName": "系统设置_栏目管理"
    51. },
    52. {
    53. "roleId": Random.id(),
    54. "roleName": "系统设置_内容管理"
    55. },
    56. {
    57. "roleId": Random.id(),
    58. "roleName": "系统设置_系统设置"
    59. }
    60. ]
    61. })
    62. export default DBData;

    4.3 定义api/index.js文件

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

    1. import Service from '@/utils/request'
    2. /**
    3. * 获取栏目列表
    4. */
    5. export const getCategoryList = () => {
    6. return Service.get('/api/category/list', {});
    7. }
    8. /**
    9. * 登录
    10. */
    11. export const loginInfo = params => {
    12. return Service.get('/api/category/list', params);
    13. }

    五、开发登录界面

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

    5.1 基本样式

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

    html代码部分如下:

    1. <template>
    2. <div class="container">
    3. <div class="login-box">
    4. <div class="title">
    5. <div class="login-info">
    6. <h3>系统登录h3>
    7. div>
    8. div>
    9. <div class="content">
    10. <el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="ruleForm">
    11. <el-form-item label="用户名" prop="username">
    12. <el-input v-model="ruleForm.username">el-input>
    13. el-form-item>
    14. <el-form-item label="密码" prop="password">
    15. <el-input type="password" v-model="ruleForm.password" autocomplete="off" show-password>el-input>
    16. el-form-item>
    17. <el-form-item>
    18. <el-button type="primary" class="btn-submit" @click="submitForm()">提交el-button>
    19. el-form-item>
    20. el-form>
    21. div>
    22. div>
    23. div>
    24. template>

    css部分样式代码:

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

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

    5.3 引入请求接口函数

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

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

    5.4 实现登录功能

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

    1. submitForm() {
    2. this.$refs['ruleForm'].validate((valid) => {
    3. if (valid) {
    4. loginInfo(this.ruleForm).then(res => {
    5. if(res.code==200){
    6. this.$store.dispatch('saveLoginInfo', {
    7. userinfo: res.data['users'],
    8. token: res.data['token']
    9. });
    10. setTimeout(() => {
    11. this.$router.push('/');
    12. }, 200);
    13. }else{
    14. this.ruleForm = {};
    15. this.$message.error(res.message);
    16. }
    17. });
    18. } else {
    19. this.$message.info('账号或密码错误');
    20. return false;
    21. }
    22. })
    23. }

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

  • 相关阅读:
    Elasticsearch:wildcard - 通配符搜索
    python / pyside6 + pymysql 实现简单的个人资金管理系统
    SQLite3 数据库学习(一):数据库和 SQLite 基础
    Ubuntu20.04+GTX 1050(notebook)安装paddlepaddle
    自用版:客服话术大全
    编译器优化等级对程序性能的影响
    防火墙旁挂、和热备
    Go 学习笔记(87) — 函数式选项,初始化结构体对象可变参数
    Spring框架概述 --- 控制反转, 依赖注入, 容器和Bean
    计算机网络-网络文件共享协议
  • 原文地址:https://blog.csdn.net/jiciqiang/article/details/115548471