前言
使用hooks写法,省去日常请求中响应式变量的定义,提高开发效率。
1.安装vueuse以及对应插件
npm i @vueuse/core @vueuse/integrations
2.核心代码
import { useAxios } from "@vueuse/integrations/useAxios";
import axios from "axios";
import { ElMessage } from "element-plus";
// 创建 axios 实例
const instance = axios.create({
baseURL: "/api", // 基础 URL
timeout: 10000, // 请求超时时间(10秒),可根据需要调整
headers: {
"Content-Type": "application/json;charset=utf-8", // 默认 Content-Type
},
});
// 获取本地存储的 token
const getToken = () => localStorage.getItem("token");
// 请求拦截器:动态添加 Authorization 头
instance.interceptors.request.use(
(config) => {
const token = getToken();
const isTokenDisabled = config.headers?.isToken === false; // 是否禁用 token
if (token && !isTokenDisabled) {
config.headers["Authorization"] = `Bearer ${token}`;
}
return config;
},
(error) => {
// 请求发送前的错误处理
return Promise.reject(error);
}
);
// 业务状态码映射表(根据后端实际返回的 code 定义)
const businessStatusMap = {
400: "错误请求",
401: "未授权,请重新登录",
403: "拒绝访问",
404: "请求资源未找到",
500: "服务器错误",
503: "服务不可用",
// 可根据实际业务扩展
};
/**
* 封装的 HTTP 请求函数
* @param {Object} config 请求配置
* @param {string} config.url 请求的 URL(相对路径)
* @param {string} [config.method="GET"] 请求方法,默认为 GET
* @param {Object} [config.data] 请求体数据
* @param {Object} [config.headers] 自定义请求头
* @param {boolean} [config.immediate=false] 是否立即发起请求,默认为 false
* @returns 返回 useAxios 的响应对象
*/
export default function useHttp(config = {}) {
// 默认配置
const defaultConfig = {
method: "GET", // 默认请求方法
immediate: false, // 默认立即发起请求
...config, // 合并用户传入的配置
};
const { url, immediate } = defaultConfig;
// 使用 useAxios 发送请求
const {
data, // 响应数据
isLoading, // 是否正在加载
isFinished, // 请求是否完成
error, // 错误对象
execute, // 手动触发请求的函数
abort, // 取消请求的函数
isAborted, // 是否已取消
} = useAxios(
url,
defaultConfig, // 合并后的配置
instance,
{
immediate, // 是否立即执行
onSuccess: (res) => {
// 处理业务逻辑
const code = res.code; // 假设后端返回的业务状态码在 res.code 中
if (code !== 200) {
const message = businessStatusMap[code] || res.msg || "业务请求失败";
ElMessage.error(message);
throw new Error(message); // 抛出错误,方便调用方捕获
}
},
onError: (err) => {
// 网络层错误已在拦截器中处理,这里只记录详细日志
console.error("Request failed:", {
url,
error: err.response || err.message,
});
},
}
);
return {
data,
isLoading,
isFinished,
error,
execute,
abort,
isAborted,
};
}
//@/api/login.js
import useHttp from "@/utils/request";
export const login = (data) => {
return useHttp({
url: "/login",
method: "POST",
data: data,
headers: {
isToken: false,
},
});
};
3.使用
<template>
<div>
<el-button type="primary" :loading="isLoading" @click="handleLogin">
登录
</el-button>
<el-button type="danger" :loading="listLoading" @click="getData">
列表
</el-button>
<div>{{ listCode }} - {{ listMsg }}</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import { login, getList } from "./api/login";
import { ElMessage } from "element-plus";
// 登录请求
const { data, isLoading, execute } = login({
username: "admin",
password: "admin123",
});
// 列表请求
const {
data: listData,
isLoading: listLoading,
execute: listExecute,
} = getList({
pageSize: 10,
pageNum: 1,
});
// 解包 listData 用于模板显示
const listCode = computed(() => listData.value?.code);
const listMsg = computed(() => listData.value?.msg);
const handleLogin = async () => {
try {
await execute();
if (data.value.code === 200) {
localStorage.setItem("token", data.value.token);
ElMessage.success("登录成功");
getData(); // 登录成功后获取列表
} else {
ElMessage.error(data.value.msg || "登录失败");
}
} catch (err) {
ElMessage.error(err.message || "登录请求失败");
}
};
const getData = async () => {
try {
await listExecute();
if (listData.value.code === 200) {
console.log("列表数据:", listData.value);
} else {
ElMessage.error(listData.value.msg || "获取列表失败");
}
} catch (err) {
ElMessage.error(err.message || "列表请求失败");
}
};
</script>
<style lang="scss" scoped></style>