📝 作者:一名 Vue 的学习者
🕒 记录时间:2025年11月
💡 目标:使用axios处理网络请求,从后端获取token,F12可以看到我们的网络请求:

写到这里,我已经有了登录页面、有了布局、有了菜单,接下来要做的就是让整个项目“跑起来”。
而这一步的关键就是:把 Axios 封装好。
为什么要封装 Axios?因为:
- 每个请求都要带 Token,不封装会让每个 API 都写一遍;
- 错误需要统一处理,不可能 everywhere 都 try/catch;
- 后端的成功/失败格式不一致时,前端需要自己规范化数据,避免代码满天飞;
- 未来要做“自动刷新 Token”,也必须先封装。
所以,这篇就是——让项目具备“成熟 API 调用能力”的核心篇章。
1、创建目录结构
我习惯在 src 下加一个 utils/request.js:
src/
├── api/
│ └── user.js
├── utils/
│ └── request.js
└── main.js
request.js→ Axios 封装api/**→ 所有业务接口
2、安装 Axios
如果项目还没安装,则需要执行以下命令:
npm install axios
3、开始封装 request.js
这是本篇的核心内容,我会一步步写,不会用网上那种巨复杂模板,保证看得懂。
📌(3.1)创建 axios 实例
// 创建 axios 实例
const service = axios.create({
baseURL: '/api', // 基础URL
timeout: 10000, // 超时时间
withCredentials: true, // 跨域请求时发送 cookies
})
📌(3.2)请求拦截器:自动携带 Token
我们要从 localStorage(或 pinia/store)里读取 token:
// 请求拦截器
service.interceptors.request.use(
(config) => {
// 请求开始时间(用于计算请求耗时)
config.metadata = { startTime: new Date() }
// 如果配置了 skipAuth 为 true,则不添加 token
if (!config.skipAuth) {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
}
// 处理 Content-Type
// 如果手动设置了 Content-Type,则使用手动设置的值
// 否则根据 data 类型自动设置
if (!config.headers['Content-Type']) {
if (config.data) {
if (config.data instanceof FormData) {
config.headers['Content-Type'] = 'multipart/form-data'
} else if (typeof config.data === 'string') {
config.headers['Content-Type'] = 'application/x-www-form-urlencoded'
} else {
config.headers['Content-Type'] = 'application/json'
}
}
}
// 合并额外的 headers
if (config.extraHeaders) {
config.headers = {
...config.headers,
...config.extraHeaders
}
}
return config
},
(error) => {
console.error('Request Error:', error)
return Promise.reject(error)
}
)
只要登录时把 token 存起来,这里就会自动带上。
📌(3.3)响应拦截器:统一处理错误
假设后端返回格式如下:
{
"code": 0,
"msg": "ok",
"data": {...}
}
我们把它规范化,让调用方更轻松:
// 响应拦截器(保持不变)
service.interceptors.response.use(
(response) => {
// 计算请求耗时
const endTime = new Date()
const startTime = response.config.metadata?.startTime
if (startTime) {
const duration = endTime - startTime
console.log(`请求 ${response.config.url} 完成,耗时: ${duration}ms`)
}
// 根据后端返回的数据结构调整
if (response.data && typeof response.data === 'object') {
const { code, message } = response.data
// 如果 code 不是成功状态码,统一处理错误
if (code && code !== 200 && code !== 0) {
ElMessage.error(message || '请求失败')
return Promise.reject(new Error(message || '请求失败'))
}
// 返回实际数据
return response.data.data !== undefined ? response.data.data : response.data
}
return response.data
},
(error) => {
// 统一错误处理
let message = '请求失败'
if (error.response) {
switch (error.response.status) {
case 400:
message = '请求参数错误'
break
case 401:
message = '未授权,请重新登录'
// 如果是认证失败,清除 token 并跳转到登录页
if (!error.config.skipAuth) {
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
window.location.href = '/login'
}
break
case 403:
message = '拒绝访问'
break
case 404:
message = '请求地址不存在'
break
case 500:
message = '服务器内部错误'
break
case 502:
message = '网关错误'
break
case 503:
message = '服务不可用'
break
default:
message = `网络错误 (${error.response.status})`
}
} else if (error.request) {
message = '网络连接失败,请检查网络'
} else {
message = error.message
}
ElMessage.error(message)
console.error('Response Error:', error)
return Promise.reject(error)
}
)
注意:
这里的 window.$message 需要你在 main.js 里处理,下一节会讲。
4、在 main.js 里挂载全局 message
如果你用的是 Element Plus:
// main.js
import { ElMessage } from "element-plus";
window.$message = ElMessage;
5、在 API 模块中使用封装后的请求
例如创建一个用户相关 API 文件:
src/api/user.js
内容如下,这里请求登录接口,这里暂时先把用户名密码写死,没有从前端获取:
import request from '@/utils/request'
export function getUserList(params) {
return request.get('/admin/user/page',params)
}
export function getToken() {
return request.postForm('/auth/oauth2/token', {
username: '',
password: '',
grant_type: 'password',
scope: 'server'
}, {
skipAuth: true, // 跳过自动添加 token
extraHeaders: {
'authorization': 'Basic xxx'
}
})
}
这样写之后,每个 API 都会:
✔ 自动携带 token
✔ 自动处理错误信息
✔ 返回 data 数据,不用再 .data.data
6、在登录页中使用
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Monitor, User, Lock } from '@element-plus/icons-vue'
import { getToken } from '@/api/user'
const router = useRouter()
const loginFormRef = ref()
// 登录表单数据
const loginForm = reactive({
username: '',
password: ''
})
// 加载状态
const loading = ref(false)
// 表单验证规则
const loginRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '用户名长度在 3 到 20 个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度在 6 到 20 个字符', trigger: 'blur' }
]
}
// 处理登录
const handleLogin = async () => {
if (!loginFormRef.value) return
try {
// 表单验证
await loginFormRef.value.validate()
loading.value = true
// 调用登录接口
const response = await getToken()
console.log(response)
if (response!=null) {
ElMessage.success('登录成功')
// 存储token和用户信息(实际项目中应该使用更安全的方式)
localStorage.setItem('token', response.access_token)
localStorage.setItem('userInfo', JSON.stringify(response.user_info))
// 跳转到首页
router.push('/home')
}
} catch (error) {
if (error.code === 401) {
ElMessage.error(error.message)
} else {
ElMessage.error('登录失败,请重试')
}
} finally {
loading.value = false
}
}
// 页面加载时的初始化
onMounted(() => {
// 检查是否已经登录
const token = localStorage.getItem('token')
if (token) {
router.push('/home')
}
})
</script>
这样我们就实现了从后端请求登录接口
7、解决跨域问题
server: {
port: 6173, // 设置开发服务器端口
host: '0.0.0.0', // 允许外部访问
open: true, // 启动时自动打开浏览器
proxy: {
'/api': {
target: 'http://xxx',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
},
8、常见扩展点
本篇主要做基础封装,后面还会继续扩展:
|
功能 |
|
Token 自动刷新(refresh token) |
|
取消重复请求、防抖 API |
|
多环境 BaseURL 自动切换 |
|
文件上传、进度条 |
|
错误码自定义 |
这一篇只是“打地基”,后面会在这基础上继续升级。
8、小结
本篇完成之后,Axios 封装已具备后台项目应有的能力:
✔ 统一 baseURL
✔ 自动携带 Token
✔ 自动提示错误
✔ 登录失效自动跳回登录
✔ API 模块清爽可维护
我们的项目正式进入“成熟结构”。
🌱 下一步计划:table表格、分页

被折叠的 条评论
为什么被折叠?



