【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
相关推荐
Pedantic1 小时前
SwiftUI 手势层级(Gesture Hierarchy)详解
前端
飘尘2 小时前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
一颗烂土豆2 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
浏览器工程师3 小时前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
雨季mo浅忆3 小时前
VSCode自动格式化三要素
前端
爱勇宝4 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
kyriewen4 小时前
同事每天催我 Code Review,我写了个脚本让 AI 替我 review PR——现在他反过来催 AI 了
前端·javascript·ai编程
user20585561518136 小时前
Windows 项目安装时报 `node-sass` 错误,如何快速处理
前端
LiaCode6 小时前
Redis 在生产项目的使用
前端·后端