放假不停歇,趁着假期学习下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等四种请求。 接下来,使用配置好的 axios 实例来封装登录请求: 这种写法对于习惯vue2的开发来说,还是不太舒服,直接用setup的语法糖更好理解一些: 修改地方如下: 通过设置 axios 的拦截器,我们能够在每次请求时自动注入 token,并且统一处理响应状态码,提高了代码的规范性和可维护性。同时,通过拦截器,所有 API 调用共享相同的请求和响应处理逻辑,避免重复代码。 下篇文章就开始正式写代码,包括前端样式和接口联调,借鉴第三方项目,实现完整的登录效果样式,也会开始慢慢讲BlogAdmin进行翻译到Vue3版本。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 (url: string, params?: any): Promise => { const response: AxiosResponse = await axiosInstance.get(url, { params }); return response.data;};// 封装 post 请求export const post = async (url: string, data?: any): Promise => { const response: AxiosResponse = await axiosInstance.post(url, data); return response.data;};// 封装 put 请求export const put = async (url: string, data?: any): Promise => { const response: AxiosResponse = await axiosInstance.put(url, data); return response.data;};// 封装 delete 请求export const del = async (url: string, params?: any): Promise => { const response: AxiosResponse = await axiosInstance.delete(url, { params }); return response.data;};export default axiosInstance;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 { 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>} 返回一个包含登录响应数据的 Promise * @throws {Error} 请求失败时抛出错误 */export const login = async (params: LoginRequest): Promise> => { try { const response = await get>('/api/Login/JWTToken3.0', { name: params.name, pass: params.pass, }); return response; } catch (error) { throw new Error('请求失败'); }};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({ name: '', pass: '',});const loading = ref(false);const errorMessage = refnull>(null);/** * 登录表单提交处理函数 */const onSubmit = async () => { loading.value = true; errorMessage.value = null; try { const response: BaseResponse = 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>
