nuxt3模块化API架构

nuxt3模块化API架构

bash 复制代码
composables/useApi.js (核心 composable)
    ↓ 统一底层
api/modules/
  ├── projects.js    →  基于 useApi() 实现
  ├── categories.js →  基于 useApi() 实现
  ├── types.js      →  类型定义
  └── api.js        →  统一导出入口
    ↓ Vue 组件调用
pages/*.vue → import { getProjects } from '~/api/projects'

优势

符合 Nuxt3 最佳实践 - 使用 composable 模式

模块化组织 - 按业务模块分离

向后兼容 - Vue 组件调用方式无需修改

代码精简 - 删除了冗余的未使用模块

统一底层 - 所有 API 调用都基于 useApi()

Vue 组件中的调用方式:

javascript 复制代码
import { getProjects } from '~/api/projects'
const response = await getProjects(params)

附上composables/useApi.js

js 复制代码
// composables/useApi.js
// 使用 Composable 模式封装 API 请求,符合 Nuxt3 最佳实践
import { ofetch } from 'ofetch'

/**
 * API Composable Hook
 * @returns {Object} API 请求方法和工具函数
 */
export const useApi = () => {
  const config = useRuntimeConfig()
  const API_BASE_URL = config.public.expressApiUrl || 'http://localhost:3001'

  // 从 localStorage 获取 token
  const getToken = () => {
    if (import.meta.client) {
      return localStorage.getItem('token')
    }
    return null
  }

  // 创建请求选项
  const createRequestOptions = (customOptions = {}) => {
    const token = getToken()
    const options = {
      baseURL: API_BASE_URL,
      timeout: 10000,
      headers: {
        'Content-Type': 'application/json',
        ...(token && { Authorization: `Bearer ${token}` })
      },
      onRequestError: (error) => {
        console.error('❌ Request Error:', error)
        throw error
      },
      onResponseError: ({ response, error }) => {
        console.error('❌ Response Error:', error)

        // 统一错误处理
        if (response) {
          const { status } = response
          const data = response._data || {}

          switch (status) {
            case 401:
              // 未授权,清除 token
              if (import.meta.client) {
                localStorage.removeItem('token')
              }
              break
            case 403:
              console.error('没有权限访问')
              break
            case 404:
              console.error('请求的资源不存在')
              break
            case 500:
              console.error('服务器内部错误')
              break
          }

          error.message = data?.message || error.message
        } else {
          error.message = '网络连接失败,请检查网络设置'
        }

        throw error
      }
    }

    return { ...options, ...customOptions }
  }

  // 通用 API 请求函数
  const request = async (config) => {
    const { method, url, params, data, ...otherOptions } = config
    const options = createRequestOptions(otherOptions)

    // 构建完整 URL
    let fullUrl = url
    if (params) {
      const searchParams = new URLSearchParams(params)
      const queryString = searchParams.toString()
      const separator = fullUrl.includes('?') ? '&' : '?'
      fullUrl = `${url}${separator}${queryString}`
    }

    // 添加请求体
    if (data && ['post', 'put', 'patch'].includes(method?.toLowerCase())) {
      options.body = data
    }

    // 开发环境日志
    if (import.meta.client && window.location.hostname === 'localhost') {
      console.log(`🚀 API Request: ${method?.toUpperCase()} ${url}`, {
        params,
        data,
      })
    }

    try {
      const response = await ofetch(fullUrl, {
        ...options,
        method: method?.toLowerCase()
      })

      // 开发环境日志
      if (import.meta.client && window.location.hostname === 'localhost') {
        console.log(`✅ API Response: ${method?.toUpperCase()} ${url}`, response)
      }

      return response
    } catch (error) {
      console.error('API 请求错误:', error)
      throw error
    }
  }

  // HTTP 方法封装
  const get = (endpoint, params) => request({ method: 'GET', url: endpoint, params })
  const post = (endpoint, data) => request({ method: 'POST', url: endpoint, data })
  const put = (endpoint, data) => request({ method: 'PUT', url: endpoint, data })
  const del = (endpoint) => request({ method: 'DELETE', url: endpoint })
  const patch = (endpoint, data) => request({ method: 'PATCH', url: endpoint, data })

  // Token 管理
  const tokenManager = {
    get: getToken,
    set: (token) => {
      if (import.meta.client) {
        localStorage.setItem('token', token)
      }
    },
    remove: () => {
      if (import.meta.client) {
        localStorage.removeItem('token')
      }
    }
  }

  // 导出 ofetch 实例以供特殊情况使用
  const apiFetch = ofetch

  return {
    get,
    post,
    put,
    del,
    patch,
    tokenManager,
    request,
    apiFetch,
    getBaseUrl: () => API_BASE_URL
  }
}
相关推荐
Cobyte16 分钟前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc
NEXT0628 分钟前
前端算法:从 O(n²) 到 O(n),列表转树的极致优化
前端·数据结构·算法
剪刀石头布啊34 分钟前
生成随机数,Math.random的使用
前端
剪刀石头布啊35 分钟前
css外边距重叠问题
前端
剪刀石头布啊36 分钟前
chrome单页签内存分配上限问题,怎么解决
前端
剪刀石头布啊38 分钟前
css实现一个宽高固定百分比的布局的一个方式
前端
剪刀石头布啊41 分钟前
js数组之快速组、慢数组、密集数组、稀松数组
前端
mango_mangojuice1 小时前
Linux学习笔记(make/Makefile)1.23
java·linux·前端·笔记·学习
Days20501 小时前
简单处理接口返回400条数据本地数据分页加载
前端
Novlan12 小时前
@tdesign/uniapp 图标瘦身
前端