【V3 Admin Vite】教程三:掌握登录模块(涉及 API、Axios、Pinia、路由守卫、鉴权)

阅读: 评论:0

【V3 Admin Vite】教程三:掌握登录模块(涉及 API、Axios、Pinia、路由守卫、鉴权)

【V3 Admin Vite】教程三:掌握登录模块(涉及 API、Axios、Pinia、路由守卫、鉴权)

前言

本系列文章是为了帮助没有直接上手(或上手比较困难)做项目能力的初级前端开发工程师采用 V3 Admin Vite 开源模板来编写业务代码。

如果你是一个有经验的朋友,那建议你直接阅读文档即可:V3 Admin Vite 中文文档,因为本系列教程节奏偏慢。

本系列文章的同步视频教程版本地址:B 站(群友好心录制)

文章目的

本文将通过登录模块教会你配置 api 接口、在页面上调用接口发起请求、Pinia 保存用户信息、经过路由守卫的拦截,成功跳转到首页、Token 鉴权,判断是否退出登录。

Begin

配置登录接口

建立目录结构

我们在 @/api 目录下找到 login 文件夹,没有的话就需要新建一个,这个文件夹即代表了登录模块(注意是登录模块,不止是登录接口。如果该模块下还有子模块的话,你可以继续往下面再建立子模块的文件夹)。然后再在 login 文件夹里面再建立一个 types 文件夹(这个文件夹专门放置和登录模块相关的 TS 类型)和 index.ts

最后成果就像截图这样:

举一反三,如果复杂一点,假如我们有一个模块叫系统管理 system,里面有两个子模块,分别叫用户管理 user、角色管理 role,那么我们建立的目录大致就应该长这个样子:

编写 TS 类型

编写接口的 TS 类型,我们必须提前拿到接口的请求参数和响应数据的格式,这里就需要你的后端同事提供接口文档了。

这个项目本身的登录接口的类型定义如下:

请求数据类型 ILoginRequestData

export interface ILoginRequestData {/** admin 或 editor */username: "admin" | "editor"/** 密码 */password: string/** 验证码 */code: string
}

响应数据类型 LoginResponseData

export type LoginResponseData = IApiResponseData<{ token: string }>

这里的意思是,将类型 { token: string } 作为泛型传递给类型 IApiResponseData,IApiResponseData 这个类型作为一个全局类型,被定义在 @/types/api.d.ts 文件里:

/** 所有 api 接口的响应数据都应该准守该格式 */
interface IApiResponseData<T> {code: numberdata: Tmessage: string
}

最终响应数据类型 LoginResponseData 就相当于:

{code: numberdata: { token: string }message: string
}

类型写好后就如下图:

编写接口

我们发送请求是通过封装好的 Axios,所以第一步就是导入相关的方法

import { request } from "@/utils/service"

我们还需要上文写好的登录接口的类型,将其导入进来

import type * as Login from "./types/login"

然后就可以开始写接口了:

/** 登录并返回 Token */
export function loginApi(data: Login.ILoginRequestData) {return request<Login.LoginResponseData>({url: "users/login",method: "post",data})
}

这表示登录接口的函数名为 loginApi,它接受一个参数 data,类型为 ILoginRequestData

request<Login.LoginResponseData> 则表示的是待会接口响应成功的数据类型为 LoginResponseData

url 代表接口地址,method 代表接口方法(get/post/put/delete),data 表示请求体数据(如果是 get 请求,则要换成 params

更多关于 Axios 的参数,你就得去 官网 学习了,比如工作中我们偶尔会遇到后端需要一些奇奇怪怪格式的数据,普通的 JSON 满足不了的情况下,可能就需要添加 transformRequest 属性来 qs 格式化 请求数据…

接口写好后就如下图:

调用登录接口

点击登录按钮

来到登录页面上,首先是找到登录按钮将调用的函数是 handleLogin:

const handleLogin = () => {loginFormRef.value?.validate((valid: boolean) => {if (valid) {loading.value = trueuseUserStore().login({username: loginForm.username,password: loginForm.password,code: de}).then(() => {router.push({ path: "/" })}).catch(() => {createCode()loginForm.password = ""}).finally(() => {loading.value = false})} else {return false}})
}

这里的 loginFormRef.value?.validate 是校验登录表单,

useUserStore() 是写好的 状态管理器 PiniaStore 待会我们下文就会讲到。

调用该 Store 的 login action,并传入用户名、密码、验证码三个参数即可。

login action 返回值是一个 Promise,所以我们后面链式跟一个 .then.catch.finally,接口调用成功则会执行 .then (跳转到首页),如果途中发生错误,则会执行 .catch,而无论什么情况都会执行 .finally

当然当然,到这里其实有人会注意到,我们是可以直接在登录页调用登录接口的(而不是将登录接口放到状态管理逻辑里面去),这样的话等拿到接口返回的数据之后,再调用状态管理器保存用户登录状态,然后再跳转页面也行

状态管理

由于点击登录按钮触发了 useUserStorelogin action,我们现在顺着逻辑找到它:

可以看到,在 useUserStore 里,我们引入了上文写好的登录接口 loginApi

import { loginApi, getUserInfoApi } from "@/api/login"

然后在 login action 中调用这个 loginApi 并传入对应参数(如果这里参数传递错误,那么 TS 就会报错提醒我们,因为我们在上文中定义接口的时候已经约束了类型

调用登录接口成功时,我们将接口返回的响应数据 res 中的 token 分别保存到 cookie(对应语句 setToken(ken))和 当前 Store(对应语句 token.value = ken) 中,如果接口失败,则直接 reject(ES6 Promise 知识) 即可

如果这里执行了 .then 那么登录页面也将执行 .then,也就会开始跳转路由到首页,那么就会触发路由守卫

路由守卫

@/router/permission.ts

import router from "@/router"
import { useUserStoreHook } from "@/store/modules/user"
import { usePermissionStoreHook } from "@/store/modules/permission"
import { ElMessage } from "element-plus"
import { whiteList } from "@/config/white-list"
import { getToken } from "@/utils/cache/cookies"
import asyncRouteSettings from "@/config/async-route"
import NProgress from "nprogress"
import "nprogress/nprogress.css&#figure({ showSpinner: false })router.beforeEach(async (to, _from, next) => {NProgress.start()const userStore = useUserStoreHook()const permissionStore = usePermissionStoreHook()// 判断该用户是否登录if (getToken()) {if (to.path === "/login") {// 如果已经登录,并准备进入 Login 页面,则重定向到主页next({ path: "/" })NProgress.done()} else {// 检查用户是否已获得其权限角色if (les.length === 0) {try {if (asyncRouteSettings.open) {// 注意:角色必须是一个数组! 例如: ['admin'] 或 ['developer', 'editor']Info()const roles = les// 根据角色生成可访问的 Routes(可访问路由 = 常驻路由 + 有访问权限的动态路由)permissionStore.setRoutes(roles)} else {// 没有开启动态路由功能,则启用默认角色userStore.setRoles(asyncRouteSettings.defaultRoles)permissionStore.setRoutes(asyncRouteSettings.defaultRoles)}// 将'有访问权限的动态路由' 添加到 Router 中permissionStore.dynamicRoutes.forEach((route) => {router.addRoute(route)})// 确保添加路由已完成// 设置 replace: true, 因此导航将不会留下历史记录next({ ...to, replace: true })} catch (err: any) {// 过程中发生任何错误,都直接重置 Token,并重定向到登录页面setToken()(ssage || "路由守卫过程发生错误")next("/login")NProgress.done()}} else {next()}}} else {// 如果没有 Tokenif (whiteList.indexOf(to.path) !== -1) {// 如果在免登录的白名单中,则直接进入next()} else {// 其他没有访问权限的页面将被重定向到登录页面next("/login")NProgress.done()}}
})router.afterEach(() => {NProgress.done()
})

路由守卫全部的代码如上,由于注释已经写的很清楚了,建议大家慢慢的仔细阅读即可,我这里只简单概述一下路由守卫做了什么事:

  1. 判断用户是否登录,没登录则只能进入白名单页面,比如登录页
  2. 如果已经登录,那么将不允许进入登录页
  3. 如果已经登录,那么还要检查是否拿到用户角色,如果没有,并且开启了动态路由功能,则要调用用户详情接口
  4. 如果没有开启动态路由功能,则启用默认角色
  5. 不管什么情况,一旦发生错误,就重置 Token,并重定向到登录页

一般情况下,不建议大家更改路由守卫逻辑,如果必须要更改,请大家一定熟练掌握原本的逻辑以及 vue-router 路由守卫本身的知识点

这里如果通过路由守卫的检查后,我们就能正常跳转到首页了。

鉴权

后续所有的操作,我都将携带保存在前端的 token 去调用接口,token 将是后端服务判断当前请求合不合法的依据。

如何携带,项目本身已经写在 Axios 的封装里面了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AhXxD0AD-1689152777812)(.image?)]

假如 token 已经过期后,理论上接口会给我们抛出一个 http code 401 的错误,我们只需要在响应拦截器里重定向到登录页即可:

End

本系列所有手摸手教程

  • 掘金专栏(文字版)
  • B 站(视频版)

V3 Admin Vite 相关链接

  • 中文文档
  • GitHub
  • Gitee

本文发布于:2024-02-04 11:53:09,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170706560655315.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:路由   模块   教程   Admin   Vite
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23