import{_ as i,o as a,c as n,a2 as h}from"./chunks/framework.BbmSSMjL.js";const g=JSON.parse('{"title":"9.4 HTTP 请求封装","description":"","frontmatter":{},"headers":[],"relativePath":"zh/frontend/http.md","filePath":"zh/frontend/http.md"}'),t={name:"zh/frontend/http.md"};function k(l,s,p,e,F,d){return a(),n("div",null,[...s[0]||(s[0]=[h(`

9.4 HTTP 请求封装 ​

概述

前端使用 Axios 封装 HTTP 请求,自动处理 Token 携带、响应码判断与错误提示(401 时提示重新登录)。

封装结构 ​

text
ui/src/utils/http/axios/
├── index.ts          # Axios 实例配置和拦截器
├── Axios.ts          # VAxios 类封装
├── axiosCancel.ts    # 请求取消
├── axiosTransform.ts # 数据转换接口
├── checkStatus.ts    # HTTP 状态码处理
├── helper.ts         # 辅助函数
└── types.ts          # 类型定义

核心拦截器 ​

请求拦截器(自动携带 Token) ​

typescript
// ui/src/utils/http/axios/index.ts

// 请求前自动添加 Token(storage 键名 'ACCESS-TOKEN')
const token = storage.get('ACCESS-TOKEN', '');
config.headers['Authorization'] = \`Bearer \${token}\`;

响应拦截器(统一错误处理) ​

typescript
// 响应处理逻辑(错误提示使用 NaiveUI 的离散式消息 API)
import { useMessage } from '@/hooks/web/useMessage';

const $message = useMessage();
const { code, data, msg } = res.data;

if (code === 0) {
    return data;    // 成功,返回 data(拦截器已解包)
}

if (code === 401 || res.status === 401) {
    // Token 过期 → 弹窗提示 → storage.clear() → 跳转登录(见下方 401 处理)
}

if (code === 403 || res.status === 403) {
    // 权限不足 / 租户异常 / 演示模式拦截
    $message.error(msg || '无访问权限');
}

// 其他业务错误,显示提示
$message.error(msg || '操作失败');

响应处理规则

401 处理(实际实现,摘自 ui/src/utils/http/axios/index.ts) ​

typescript
// code === 401(ResultEnum.TIMEOUT)
case ResultEnum.TIMEOUT:
  if (router.currentRoute.value?.name === PageEnum.BASE_LOGIN_NAME) return;
  $dialog.warning({
    title: '提示',
    content: '登录身份已失效,请重新登录!',
    positiveText: '确定',
    closable: false,
    maskClosable: false,
    onPositiveClick: () => {
      storage.clear();
      window.location.href = PageEnum.BASE_LOGIN;   // '/login'
    },
  });
  break;

自动续签能力在后端

ui/ 当前未内置自动续签(401 直接重新登录)。后端 POST /api/refresh 刷新能力完整保留,详见 5.2 认证流程实战 与 8.6 Token 使用说明。

API 模块编写 ​

typescript
// ui/src/api/tool/example.ts
import { http } from '@/utils/http/axios';

export function getExamplePage(params?: Record<string, any>) {
  return http.request({
    url: '/example/page',
    method: 'GET',
    params,
  });
}

export function exampleAdd(data: any) {
  return http.request({
    url: '/example/add',
    method: 'POST',
    data,
  });
}

// 注意:批量删除为 DELETE(请求体 { ids: [] })
export function exampleBatchDelete(data: any) {
  return http.request({
    url: '/example/batchDelete',
    method: 'DELETE',
    data,
  });
}

(完整规范见 9.13 API 模块编写规范。)

在组件中使用 ​

typescript
import { getExamplePage, exampleAdd } from '@/api/tool/example';

// 获取列表(res 已是 data 部分)
const res = await getExamplePage({ pageNo: 1, pageSize: 10 });
tableData.value = res.records;   // { records, total, current, size }
total.value = res.total;

// 新增
await exampleAdd({ name: '测试', status: 1 });

常见问题 ​

现象原因处理
所有请求 401Token 未携带 / 已过期检查拦截器与登录态;401 弹窗确认后清存储并跳登录页
请求 404URL 与后端路由不一致核对 routes/api.php(注意 batchDelete 为 DELETE)
请求跨域直连后端且未配代理/CORS用 Vite VITE_PROXY 代理或后端配 CORS_ALLOW_ORIGIN
响应取不到 data拦截器未解包统一约定拦截器返回 data;排查是否绕过了 http.request
`,20)])])}const y=i(t,[["render",k]]);export{g as __pageData,y as default};