vue项目中api的配置使用 - 完整解决方案与实战教程

在 Vue 项目开发中,API 配置与使用是绕不开的核心环节。无论是中小型后台管理系统,还是大型电商前台,只要涉及前后端分离,就必然要处理请求封装、环境区分、拦截器、错误统一处理等问题。很多开发者初期直接在组件里写 axios.get('/api/user'),随着项目膨胀,接口地址散落各处、环境切换靠手动改代码、token 过期无法统一跳转、重复请求导致数据错乱——这些看似零散的问题,最终会演变成维护噩梦,甚至引发线上事故。本文从真实排坑角度出发,带你彻底理清 Vue 项目中 API 的配置与使用。

【问题现象】

先看几个典型“症状”,如果你中了任意一条,说明 API 层已经失控:

【原因分析】

这些现象背后,本质是三个层面的缺失:

  1. 环境配置未抽象:Vue CLI 或 Vite 都支持 .env 文件,但很多项目只用了 process.env.NODE_ENV,没有定义 VUE_APP_BASE_APIVITE_API_URL,导致 baseURL 硬编码。
  2. 请求层未封装:直接使用 axios 实例,没有创建独立的 request 模块,拦截器、错误码映射、token 注入全部缺失。
  3. API 管理未模块化:接口按组件散落,没有按业务域拆分 api/user.jsapi/order.js,复用性极差。

【解决方案(附完整代码)】

下面以 Vue 3 + Vite + axios 为例,给出一套可直接落地的方案。Vue 2 + Vue CLI 同理,仅环境变量前缀不同。

第一步:环境变量配置

在项目根目录创建三个文件:

# .env.development
VITE_API_BASE_URL = /dev-api
VITE_APP_TITLE = 管理后台-开发

# .env.production
VITE_API_BASE_URL = https://api.yourdomain.com
VITE_APP_TITLE = 管理后台

# .env.test
VITE_API_BASE_URL = https://test-api.yourdomain.com
VITE_APP_TITLE = 管理后台-测试

注意:Vite 中只有以 VITE_ 开头的变量才会暴露给客户端。Vue CLI 则使用 VUE_APP_ 前缀。

第二步:封装 axios 请求实例

创建 src/utils/request.js,这是整个 API 层的核心:

import axios from 'axios'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getToken, removeToken } from '@/utils/auth'
import router from '@/router'

// 1. 创建 axios 实例
const service = axios.create({
  // 从环境变量读取 baseURL,避免硬编码
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 15000 // 超时时间 15s
})

// 2. 请求拦截器:注入 token、处理重复请求
const pendingMap = new Map() // 用于取消重复请求

function getPendingKey(config) {
  const { method, url, params, data } = config
  return [method, url, JSON.stringify(params), JSON.stringify(data)].join('&')
}

service.interceptors.request.use(
  config => {
    // 注入 token
    const token = getToken()
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`
    }

    // 取消重复请求(同一请求未返回前再次发起则取消前一个)
    const key = getPendingKey(config)
    if (pendingMap.has(key)) {
      pendingMap.get(key)('取消重复请求')
      pendingMap.delete(key)
    }
    config.cancelToken = new axios.CancelToken(c => {
      pendingMap.set(key, c)
    })

    return config
  },
  error => Promise.reject(error)
)

// 3. 响应拦截器:统一处理业务码、401、错误提示
service.interceptors.response.use(
  response => {
    const res = response.data
    // 假设后端约定 code === 200 为成功
    if (res.code !== 200) {
      ElMessage.error(res.message || '请求失败')

      // 401: token 过期或未登录
      if (res.code === 401) {
        ElMessageBox.confirm('登录状态已过期,请重新登录', '提示', {
          confirmButtonText: '重新登录',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          removeToken()
          router.push('/login')
        })
      }
      return Promise.reject(new Error(res.message || 'Error'))
    }
    return res.data // 直接返回业务数据,组件无需再解构
  },
  error => {
    // 网络层错误处理
    if (axios.isCancel(error)) {
      console.warn('请求被取消:', error.message)
      return Promise.reject(error)
    }
    let message = error.message
    if (error.response) {
      const status = error.response.status
      const statusMap = {
        400: '请求参数错误',
        401: '未授权,请重新登录',
        403: '拒绝访问',
        404: '请求地址不存在',
        500: '服务器内部错误',
        502: '网关错误',
        503: '服务不可用'
      }
      message = statusMap[status] || `连接错误${status}`
    } else if (message.includes('timeout')) {
      message = '请求超时,请稍后重试'
    } else if (message.includes('Network')) {
      message = '网络异常,请检查网络连接'
    }
    ElMessage.error(message)
    return Promise.reject(error)
  }
)

export default service

第三步:按业务域组织 API 模块

创建 src/api/user.jssrc/api/order.js,每个模块只负责定义接口,不处理逻辑:

// src/api/user.js
import request from '@/utils/request'

// 登录
export function login(data) {
  return request({
    url: '/user/login',
    method: 'post',
    data
  })
}

// 获取用户信息
export function getUserInfo(params) {
  return request({
    url: '/user/info',
    method: 'get',
    params
  })
}

// src/api/order.js
import request from '@/utils/request'

export function getOrderList(params) {
  return request({
    url: '/order/list',
    method: 'get',
    params
  })
}

export function createOrder(data) {
  return request({
    url: '/order/create',
    method: 'post',
    data
  })
}

第四步:组件中调用

<script setup>
import { ref, onMounted } from 'vue'
import { getUserInfo } from '@/api/user'
import { getOrderList } from '@/api/order'

const userInfo = ref({})
const orderList = ref([])

onMounted(async () => {
  // 由于响应拦截器已返回 res.data,这里直接拿到业务数据
  userInfo.value = await getUserInfo({ id: 1 })
  orderList.value = await getOrderList({ page: 1, size: 10 })
})
</script>

第五步:排查与验证步骤

配置完成后,按以下顺序验证,可快速定位问题:

  1. 检查 .env.developmentVITE_API_BASE_URL 是否生效:在 vite.config.js 中打印 console.log(import.meta.env) 或直接在 request.js 中打印 baseURL。
  2. 确认代理是否配置:开发环境如果使用 /dev-api 前缀,必须在 vite.config.js 中配置 server.proxy,否则请求会 404。
  3. 检查 token 注入:在浏览器 Network 面板查看请求头是否携带 Authorization
  4. 验证 401 跳转:手动清除 localStorage 中的 token,发起请求,观察是否弹出重新登录确认框。
  5. 验证重复请求取消:快速点击同一按钮两次,Network 中应只保留一个请求,另一个显示 canceled

最后补充一个 Vite 代理配置示例,避免开发环境跨域:

// vite.config.js
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd())
  return {
    plugins: [vue()],
    server: {
      proxy: {
        '/dev-api': {
          target: 'http://localhost:8080', // 后端真实地址
          changeOrigin: true,
          rewrite: path => path.replace(/^\/dev-api/, '')
        }
      }
    }
  }
})

这套方案的核心思想是:环境变量管地址,request 实例管行为,api 模块管定义,组件只管调用。四层分离后,无论项目扩展到多少接口,API 层始终清晰可控,线上事故率会大幅下降。