【vue3后台项目】登录逻辑处理

完善登录

登录表单校验

对于element-plus中想要对表单进行表单校验需要关注以下3点:

  • 为e-form绑定model属性
  • 为el-form绑定rules属性
  • 为el-form-item绑定prop属性

详情🔎

新建views/login/rules.js,用来存放验证规则,如果是大厂项目,它的验证规则比较复杂,适合用单独文件进行存储;

views/login/rules.js 复制代码
export const validatePassword = () => {
  return (rule, value, cb) => {
    if (value.length < 6) {
      cb(new Error('密码不能少于6位!'))
    } else {
      cb()
    }
  }
}

密码框状态通用处理

密码框默认是密文状态,如果点击小眼睛图标,则会显示出明文状态;这里我们是动态修改input的type类型即可,type为password是密文显示,type为text则为明文显示;

此时views/login/index.vue代码为:

views/login/index.vue 复制代码
<template>
  <div class="login-container">
    <el-form class="login-form" :model="loginForm" :rules="loginRules">
      <div class="title-container">
        <h3 class="title">用户登录</h3>
      </div>
      <el-form-item prop="username">
        <span class="svg-container">
          <SvgIcon icon="user"></SvgIcon>
        </span>
        <el-input
          placeholder="username"
          name="username"
          type="text"
          v-model="loginForm.username"
        ></el-input>
      </el-form-item>
      <el-form-item prop="password">
        <span class="svg-container">
          <SvgIcon icon="password"></SvgIcon>
        </span>
        <el-input
          placeholder="password"
          name="password"
          :type="passwordType"
          v-model="loginForm.password"
        ></el-input>
        <span class="show-pwd">
          <span class="svg-container" @click="changeType">
            <SvgIcon
              :icon="passwordType === 'password' ? 'eye' : 'eye-open'"
            ></SvgIcon>
          </span>
        </span>
      </el-form-item>
      <el-button type="primary" style="width: 100%; margin-bottom: 30px"
        >登录</el-button
      >
    </el-form>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import SvgIcon from '@/components/SvgIcon'
import { validatePassword } from './rules'

// 数据源
const loginForm = ref({
  username: 'super-admin',
  password: '123456'
})

// 验证规则
const loginRules = ref({
  username: [
    {
      required: true,
      trigger: 'blur',
      message: '用户名为必填项'
    }
  ],
  password: [
    {
      required: true,
      trigger: 'blur',
      validator: validatePassword()
    }
  ]
})
// 处理密码框文本显示
const passwordType = ref('password')

const changeType = () => {
  // 当passwordType的值为password改成text
  if (passwordType.value === 'password') {
    passwordType.value = 'text'
  } else {
    passwordType.value = 'password'
  }
}
</script>

通用后台登录方案解析

1.axios模块封装;

2.接口请求模块;

3.登录请求动作;

4.Token缓存;

5.登录鉴权;

1.配置环境变量封装axios模块

我们希望封装出来的axios模块,必须能根据当前模式的不同,设定不同的BaseUrl,因为企业级项目在开发状态和生产状态的下的BaseUrl是不同的;

对于@vue/cli来说,它具备3种模式:

1.development

2.test

3.production

我们这里使用@vue/cli提供的环境变量实现,详情🔎

我们可以在项目中创建两个文件:

  1. 开发状态:.env.development
.env.development 复制代码
ENV = 'development'

VUE_APP_BASE_API = '/api'
  1. 生产状态:.env.production
.env.production 复制代码
ENV = 'production'

VUE_APP_BASE_API = '/prod-api'
js 复制代码
// 安装axios
npm i axios --save

新建utils/request.js

utils/request.js 复制代码
import axios from 'axios'

const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API,
  timeout: 5000
})

export default service

2.封装请求动作

1.封装接口请求模块

2.封装登录请求动作

封装接口请求模块:

创建api/sys.js

api/sys.js 复制代码
import request from '@/utils/request'

// 登录
export const login = (data) => {
  return request({
    url: 'sys/login',
    methods: 'POST',
    data
  })
}

封装登录请求动作:

我们期望把它封装到vuex的action中; 创建store/modules/user.js,用于处理所有和用户相关的内容(此处用第三方包md5)

js 复制代码
// 安装md5
npm i md5 --save
store/modules/user.js 复制代码
import { login } from '@/api/sys'
import md5 from 'md5'

export default {
  namespaced: true,
  state: () => ({}),
  mutations: {},
  actions: {
    /**
     * 登录请求action
     */
    login: (userInfo) => {
      const { username, password } = userInfo
      return new Promise((resolve, reject) => {
        login({
          username,
          password: md5(password)
        })
          .then((data) => {
            resolve()
          })
          .catch((err) => {
            reject(err)
          })
      })
    }
  }
}

在store/index.js完成模块注册:

store/index.js 复制代码
import { createStore } from 'vuex'
import user from './modules/user'

export default createStore({
  modules: {
    user
  }
})

3.登录触发动作

webpack DevServer

在vue.config.js中配置devServer;老师给的api地址不好用,我这里是用egg起了一个服务,所以target是我本地的地址,这里大家也可以freestyle;

vue.config.js 复制代码
// webpack devServer提供了代理的功能,该代理可以把所有请求到当前服务中的请求,转发(代理)到另外的服务器上
  devServer: {
    proxy: {
      // 当地址中包含/api时,触发此代理
      // changeOrigin跨域设置
      '/project': {
        target: 'http://localhost:7001/',
        changeOrigin: true
      }
    }
  },
views/login/index.vue 复制代码
// 按钮添加点击事件
// form上加一个ref
<template>
    <el-form
      ref="loginFormRef"
    >
        <el-button
            type="primary"
            :loading="loading"
            @click="handleLogin"
            style="width: 100%; margin-bottom: 30px"
            >登录</el-button
        >
    </el-form>
</template>
<script>
import { useStore } from 'vuex'

// 处理登录
const loading = ref(false)
const store = useStore()
const loginFormRef = ref(null)
const handleLogin = () => {
  // 1.进行表单校验
  
  loginFormRef.value.validate((valid) => {
    if (!valid) return
    // 2.触发登录动作
    loading.value = true

    store
      .dispatch('user/login', loginForm.value)
      .then(() => {
        loading.value = false
        // 3.进行登录后处理
      })
      .catch((err) => {
        loading.value = false
        console.log('err--->', err)
      })
  })
}
</script>

4.本地缓存处理方案

在获取到token之后,我们会把token进行缓存,而缓存的方式将会分为两种:

1.本地缓存(LocalStorage): 为了方便实现自动登录;

2.全局状态管理(Vuex): 为了后面在其他位置使用;

LocalStorage

创建utils/storage.js文件,封装3个对应方法:

utils/storage.js 复制代码
/**
 * 存储数据
 */
export const setItem = (key, value) => {
  // value分为复杂数据和基本数据

  if (typeof value === 'object') {
    value = JSON.stringify(value)
  }
  window.localStorage.setItem(key, value)
}
/**
 * 获取数据
 */
export const getItem = (key) => {
  const data = window.localStorage.getItem(key)

  try {
    return JSON.parse(data)
  } catch {
    return data
  }
}
/**
 * 删除指定数据
 */
export const removeItem = (key) => {
  window.localStorage.removeItem(key)
}
/**
 * 删除所有数据
 */
export const removeAll = () => {
  window.localStorage.clear()
}

修改modules/user.js

js 复制代码
import { login } from '@/api/sys'
import md5 from 'md5'
import { setItem, getItem } from '@/utils/storage'
import { TOKEN } from '@/constant'

export default {
  namespaced: true,
  state: () => ({
    token: getItem(TOKEN) || ''
  }),
  mutations: {
    setToken(state, token) {
      state.token = token
      setItem(TOKEN, token)
    }
  },
  actions: {
    /**
     * 登录请求action
     */
    login(context, userInfo) {
      const { username, password } = userInfo

      return new Promise((resolve, reject) => {
        login({
          username,
          password: md5(password)
        })
          .then((res) => {
            this.commit('user/setToken', res.data.data.token)
            resolve()
          })
          .catch((err) => {
            reject(err)
          })
      })
    }
  }
}

创建constants/index.js,用来定义常量;

constants/index.js 复制代码
export const TOKEN = 'token'

5.响应数据统一处理

上面通过res.data.data.token获取token是非常不方便的,这里用axios响应拦截器进行优化;

修改utils/request.js

utils/request.js 复制代码
import axios from 'axios'
import { ElMessage } from 'element-plus'

const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API,
  timeout: 5000
})
// 响应拦截器
service.interceptors.response.use(
  (response) => {
    const { success, message } = response.data

    if (success) {
      return response.data
    } else {
      // 失败时,消息提示
      ElMessage.error(message)
      return Promise.reject(new Error(message))
    }
  },
  (err) => {
    ElMessage.error(err)
    return Promise.reject(new Error(err))
  }
)

export default service

6.登录后操作

我们距离登录操作还差最后一个登录鉴权;

什么是登录鉴权?

当用户未登陆时,不允许进入除login之外的其他页面;

用户登录后,token未过期之前,不允许进入login页面;

想要实现这个功能,最好的方式是通过路由守卫来进行实现;

创建layout/index.vue;

layout/index.vue 复制代码
<template>
  <div>layout</div>
</template>

<script>
export default {}
</script>

<style></style>

router/index.js添加一个路由;

router/index.js 复制代码
 {
    path: '/',
    component: () => import('@/layout/index')
 }

创建一个permission.js,和main.js同级;

permission.js 复制代码
import router from '@/router'
import store from '@/store'

// 白名单
const whiteList = ['/login']
/**
 * 处理前置守卫
 * to: 要到哪里去
 * from: 从哪里来
 * next: 是否要去
 */
router.beforeEach((to, from, next) => {
  // 1. 用户已登录,则不允许进入login
  // 2. 用户未登录,则不允许出login
  
  // 用token判断是否登录
  if (store.getters.token) {
    if (to.path === '/login') {
      next('/')
    } else {
      next()
    }
  } else {
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next('/login')
    }
  }
  return false
})

在main.js中导入permisssion.js

main.js 复制代码
// 导入路由鉴权
import './permission'

给store加一个getters;

/store/getters.js 复制代码
// 快捷访问

const getters = {
  token: (state) => state.user.token
}
export default getters
相关推荐
qq_390161778 分钟前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test37 分钟前
js下载excel示例demo
前端·javascript·excel
Yaml41 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事1 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶1 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json
getaxiosluo1 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx
理想不理想v1 小时前
vue种ref跟reactive的区别?
前端·javascript·vue.js·webpack·前端框架·node.js·ecmascript
知孤云出岫1 小时前
web 渗透学习指南——初学者防入狱篇
前端·网络安全·渗透·web
贩卖纯净水.1 小时前
Chrome调试工具(查看CSS属性)
前端·chrome
栈老师不回家2 小时前
Vue 计算属性和监听器
前端·javascript·vue.js