系列目录
- 手摸手创建一个 Vue + Ts 项目(一) ------ 初始化项目
- 手摸手创建一个 Vue + Ts 项目(二) ------ 配置路由
- 手摸手创建一个 Vue + Ts 项目(三) ------ 实现一个左侧菜单栏
- 手摸手创建一个 Vue + Ts 项目(四) ------ 完善左侧菜单栏
- 手摸手创建一个 Vue + Ts 项目(五) ------ UnoCSS 的基本使用
- 手摸手创建一个 Vue + Ts 项目(六) ------ 完善布局
- 手摸手创建一个 Vue + Ts 项目(七)------ 封装全局反馈组件
- 手摸手创建一个 Vue + Ts 项目(八)------ 封装 Axios 请求
封装用户登录API
定义用户登录出入参类型
-
api/types/authTypes.ts
tsimport { UserInfo } from './userTypes.ts' export interface LoginRequest { username: string, password: string } export interface LoginResponse { userInfo: UserInfo, token: string }
定义登录API
-
api/authApi.ts
tsimport { LoginRequest, LoginResponse } from './types/authTypes.ts' import request from '@/utils/http' export const login = (data: LoginRequest) => { return request.post<LoginRequest, LoginResponse>('auth/login', data) }
开发登录页面
开发一个登录页面
首先,第一步,实现一个登录表单。在 views
文件夹下,创建 login
文件夹和 index.vue
。
-
views/login/index.vue
vue<template> <div> <div class="e-login-form"> <n-form ref="formRef" label-placement="left" size="large" :model="formModel" :rules="formRules" > <n-form-item path="username"> <n-input v-model:value="formModel.username" placeholder="请输入用户名" > <template #prefix> <n-icon size="18" color="#808695"> <PersonOutline /> </n-icon> </template> </n-input> </n-form-item> <n-form-item path="password"> <n-input v-model:value="formModel.password" placeholder="请输入密码"> <template #prefix> <n-icon size="18" color="#808695"> <LockClosedOutline /> </n-icon> </template> </n-input> </n-form-item> <n-form-item> <n-button type="primary" size="large" @click="toLogin" block> 登录 </n-button> </n-form-item> </n-form> </div> </div> </template> <script setup lang="ts"> import { ref, Ref } from "vue"; import { LoginRequest } from "@/api/types/authTypes.ts"; import { login } from "@/api/authApi"; const formRef = ref(); const formModel: Ref<LoginRequest> = ref({ username: "", password: "", }); const formRules = { username: { required: true, message: "请输入用户名", trigger: "blur" }, password: { required: true, message: "请输入密码", trigger: "blur" }, }; const toLogin = (e) => { e.preventDefault(); formRef.value.validate(async (errors: any) => { // 如果没有校验异常 if (!errors) { login(formModel.value) .then(res => { console.log('res', res) }) } }); }; </script> <style scoped></style>
然后,配置登录页面的路由。在 router/modules
文件夹下,创建 login.ts
:
ts
import { RouteRecord } from '@/router/type'
const loginRoutes: RouteRecord[] = [
{
path: '/login',
name: 'Login',
meta: {
hidden: true
},
component: () => import('@/views/login/index.vue')
}
]
export default loginRoutes
访问登录页面:
输入用户名和密码后,点击登录,控制台中显示了返回结果:
全局管理 token 和用户信息
当登录之后,首先要做的就是将 token 和用户信息全局管理起来,方便在任意页面中获取。这里采用 Store
(Pinia)和 LocalStorage
的方案来全局管理。
首先,在 store/modules
目录下,创建 user.ts
:
ts
import { defineStore } from 'pinia'
import { UserInfo } from '@/api/types/userTypes'
import { LoginRequest } from '@/api/types/authTypes'
import { login } from '@/api/authApi'
import { useStorage } from '@vueuse/core'
export const useUserStore = defineStore('user', () => {
const userInfoRef = useStorage('USER_INFO', {} as UserInfo, sessionStorage)
const tokenRef = useStorage('TOKEN', '', sessionStorage)
function setUserInfo(userInfo: UserInfo) {
userInfoRef.value = userInfo
}
function setToken(token: string) {
tokenRef.value = token
}
async function toLogin(param: LoginRequest) {
await login(param)
.then(res => {
setToken(res.token)
setUserInfo(res.userInfo)
})
}
function getUserId() {
return userInfoRef.value.userId
}
function getUsername() {
return userInfoRef.value.username
}
function getToken() {
return tokenRef.value
}
return { toLogin, getToken, getUserId, getUsername }
})
之后,再调整登录页面:
vue
import { useUserStore } from "@/store/modules/user";
const userStore = useUserStore()
const toLogin = (e) => {
e.preventDefault();
formRef.value.validate(async (errors: any) => {
// 如果没有校验异常
if (!errors) {
await userStore.toLogin(formModel.value)
window.$notification.success({
content: '欢迎回来 - ' + userStore.getNickName(),
duration: 2000
})
// 登录成功后,跳转到原来的页面
const redirect = route.query.redirect as string
if (redirect) {
router.replace(redirect)
} else {
router.replace('/')
}
}
});
};
登录校验
登录校验一般分为两部分,第一是路由跳转时,如果没有登录,则直接跳转到登录页面;第二是与 API 约定一个固定的异常编码,当返回改异常时,则认为需要登录。
路由跳转校验
首先,实现路由跳转时的校验,这里用到的是 vue-router
中的[导航卫士](导航守卫 | Vue Router (vuejs.org))功能。
找到我们定义路由的地方,注册一个全局前置卫士:
-
router/index.ts
:tsrouter.beforeEach((to, from, next) => { const userStore = useUserStore(); const isAuthenticated = userStore.getToken() && userStore.getToken() !== ""; if (isAuthenticated) { next(); } else { if (to.name === "Login") { next(); } else { next({ name: "Login", query: { redirect: to.fullPath } }); } } });
这里判断是否登录,判断方式是 token 是否存在,如果存在,则认为是已经登录过,如果没有登录的话,跳转到登录页面,并且把当前要跳转的路径作为参数带过去。
API 响应校验
接下来,实现接口层面校验,当服务端返回异常状态编码为未登录时,跳转到登录页面。
首先,在每次接口请求时,都把 token 携带到 Header 中。
在 AxiosRequest.ts
中的请求拦截器中,添加相关代码:
ts
private interceptRequest(): void {
this.axiosInstance.interceptors.request.use(
async (config: ExpandInternalAxiosRequestConfig) => {
// token
const userStore = useUserStore()
const token = userStore.getToken()
if (token) {
// 这里的 _tt 是与服务端约定的 token 键名
config.headers._tt = token
}
// ......
return config;
},
errorHandler
);
}
在 store/modules/user.ts
中,增加注销 logout
方法,主要实现清除浏览器中存储的 token 和用户信息,同时跳转到登录页面。
ts
function toLogout() {
setUserInfo({} as UserInfo);
setToken("");
window.location.href = '/login'
}
找到 AxiosRequest.ts
文件,定义异常编码和处理异常编码的方法:
ts
import { useUserStore } from '@/store/modules/user.ts'
const notLoginErrorCode = "999900011";
const handleErrorCode = (errorCode: string) => {
if (notLoginErrorCode === errorCode) {
const userStore = useUserStore()
userStore.logout()
}
}
之后,在其响应拦截器中,添加处理异常编码的逻辑:
ts
private interceptResponse(): void {
this.axiosInstance.interceptors.response.use(
async (response: ExpandAxiosResponse): Promise<any> => {
// ......
const { code, errorCode, message, data } = response.data;
if (code === successCode) {
return data;
} else {
// ......
handleErrorCode(errorCode);
}
return Promise.reject(response.data);
},
errorHandler
);
}
我是「代码笔耕 」,致力于打造高效简洁、稳定可靠代码的后端开发。 由于不是专业的前端开发,也是通过写这一系列的文章,来提升巩固下自己的水平。
本文可能存在纰漏或错误,如有问题欢迎指正,感谢您阅读这篇文章,如果觉得还行的话,不要忘记点赞、评论、收藏喔!
最后欢迎大家关注我的公众号「代码笔耕」和开源项目:easii (easii) - Gitee.com