放假不停歇,趁着假期学习下VUE3相关的内容,一方面是自己保持活力,另一方面也是工作需要,本系列是我的自学教程,如果有从0开始学习VUE3的,可以跟着一起练习下,毕竟前端我也是泥腿子出身,这一系列会使用Vite、TS、Pinia、Element-Plus等新知识点,既是查漏补缺,也是知识分享。
代码地址:
https://github.com/anjoy8/bcvp.vue3.git
这是每篇文章一节课一个分支,方便大家学习,会慢慢的将blog.admin项目进行翻新,使用的后端接口还是BlogCore。
系列文章:
通常,为了更规范地封装登录接口,并且处理全局的请求和响应逻辑,我们可以通过设置 axios 的拦截器来处理 request 和 response,包括自定义 token 的注入以及响应状态码的处理。
axiosInstance.ts 中我们封装了常用的 HTTP 请求方法:get、post、put 和 delete。
每个方法返回 AxiosResponse 中的 data,这样可以直接获取到返回的数据,而无需在调用时处理 response。
请求拦截器和响应拦截器保持不变,用于处理 token 注入和全局错误处理。
通过这种方式,我们可以在项目中更清晰地发起不同的 API 请求,并确保请求的规范性和可维护性。
在src文件夹下,创建utils文件夹,然后创建一个axiosInstance.ts实例并添加拦截器,处理请求和响应,并封装get、post、put和delete等四种请求。
import axios from 'axios';
import type { InternalAxiosRequestConfig, AxiosResponse } from 'axios';
import { useAuthStore } from '@/stores/auth';
import router from '@/router';
// 创建 axios 实例
const axiosInstance = axios.create({
baseURL: '', // 替换为你的 API 基础 URL
timeout: 10000, // 请求超时时间
});
// 请求拦截器
axiosInstance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => { // 使用 InternalAxiosRequestConfig 类型
const authStore = useAuthStore();
if (authStore.token) {
config.headers['Authorization'] = `Bearer ${authStore.token}`; // 在请求头中添加 token
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
axiosInstance.interceptors.response.use(
(response: AxiosResponse) => {
return response;
},
(error) => {
if (error.response) {
const { status } = error.response;
if (status === 401) {
// 未授权,跳转到登录页面
router.push({ name: 'login' });
} else if (status === 403) {
// 无权限访问,提示用户
console.error('无权限访问');
} else if (status === 500) {
// 服务器错误
console.error('服务器错误');
}
}
return Promise.reject(error);
}
);
// 封装 get 请求
export const get = async <T>(url: string, params?: any): Promise<T> => {
const response: AxiosResponse<T> = await axiosInstance.get(url, { params });
return response.data;
};
// 封装 post 请求
export const post = async <T>(url: string, data?: any): Promise<T> => {
const response: AxiosResponse<T> = await axiosInstance.post(url, data);
return response.data;
};
// 封装 put 请求
export const put = async <T>(url: string, data?: any): Promise<T> => {
const response: AxiosResponse<T> = await axiosInstance.put(url, data);
return response.data;
};
// 封装 delete 请求
export const del = async <T>(url: string, params?: any): Promise<T> => {
const response: AxiosResponse<T> = await axiosInstance.delete(url, { params });
return response.data;
};
export default axiosInstance;
接下来,使用配置好的 axios 实例来封装登录请求:
import { get } from '@/utils/axiosInstance';
/**
* 请求的入参接口
* @interface LoginRequest
* @property {string} name - 用户名
* @property {string} pass - 密码
*/
export interface LoginRequest {
name: string;
pass: string;
}
/**
* 基础响应接口,使用泛型 T 来表示响应体
* @template T
* @interface BaseResponse
* @property {number} status - HTTP 响应状态码
* @property {boolean} success - 请求是否成功
* @property {string} msg - 响应的消息
* @property {string | null} [msgDev] - 开发用的详细信息,可能为空
* @property {T} response - 具体的响应数据
*/
export interface BaseResponse<T> {
status: number;
success: boolean;
msg: string;
msgDev?: string | null;
response: T;
}
/**
* 登录响应接口
* @interface LoginResponse
* @property {boolean} success - 是否登录成功
* @property {string} token - JWT token
* @property {number} expires_in - token 的有效时长(秒)
* @property {string} token_type - token 类型,通常为 "Bearer"
*/
export interface LoginResponse {
success: boolean;
token: string;
expires_in: number;
token_type: string;
}
/**
* 发起登录请求
* @function login
* @param {LoginRequest} params - 登录请求的参数
* @returns {Promise<BaseResponse<LoginResponse>>} 返回一个包含登录响应数据的 Promise
* @throws {Error} 请求失败时抛出错误
*/
export const login = async (params: LoginRequest): Promise<BaseResponse<LoginResponse>> => {
try {
const response = await get<BaseResponse<LoginResponse>>('/api/Login/JWTToken3.0', {
name: params.name,
pass: params.pass,
});
return response;
} catch (error) {
throw new Error('请求失败');
}
};
这种写法对于习惯vue2的开发来说,还是不太舒服,直接用setup的语法糖更好理解一些:
<template>
<div class="login">
<h1>登录</h1>
<form @submit.prevent="onSubmit">
<div>
<label for="name">用户名</label>
<input v-model="loginForm.name" id="name" type="text" required />
</div>
<div>
<label for="pass">密码</label>
<input v-model="loginForm.pass" id="pass" type="password" required />
</div>
<button type="submit" :disabled="loading">
{{ loading ? '登录中...' : '登录' }}
</button>
</form>
<p v-if="errorMessage" class="error">{{ errorMessage }}</p>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { login } from '@/api/loginApi';
import type { LoginRequest, BaseResponse, LoginResponse } from '@/api/loginApi';
const router = useRouter();
const loginForm = ref<LoginRequest>({
name: '',
pass: '',
});
const loading = ref(false);
const errorMessage = ref<string | null>(null);
/**
* 登录表单提交处理函数
*/
const onSubmit = async () => {
loading.value = true;
errorMessage.value = null;
try {
const response: BaseResponse<LoginResponse> = await login(loginForm.value);
if (response.success) {
// 登录成功,跳转到首页或者其他页面
router.push({ name: 'Home' });
} else {
// 登录失败,显示错误信息
errorMessage.value = response.msg;
}
} catch (error) {
// 请求错误处理
errorMessage.value = '登录失败,请重试';
} finally {
loading.value = false;
}
};
</script>
<style scoped>
.login {
max-width: 400px;
margin: 0 auto;
padding: 1rem;
}
.error {
color: red;
margin-top: 1rem;
}
</style>
修改地方如下:
通过设置 axios 的拦截器,我们能够在每次请求时自动注入 token,并且统一处理响应状态码,提高了代码的规范性和可维护性。同时,通过拦截器,所有 API 调用共享相同的请求和响应处理逻辑,避免重复代码。
下篇文章就开始正式写代码,包括前端样式和接口联调,借鉴第三方项目,实现完整的登录效果样式,也会开始慢慢讲BlogAdmin进行翻译到Vue3版本。