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
  }
}
相关推荐
沛沛老爹2 小时前
Web开发者进阶AI:企业级Agent Skills安全策略与合规架构实战
前端·人工智能·架构
摘星编程2 小时前
React Native for OpenHarmony 实战:SegmentControl 分段控件详解
javascript·react native·react.js
摘星编程2 小时前
React Native for OpenHarmony 实战:ProgressRing 环形进度详解
javascript·react native·react.js
遗憾随她而去.2 小时前
前端首屏加载时间的度量:FCP、LCP等指标的规范理解
前端
TAEHENGV2 小时前
React Native for OpenHarmony 实战:数学练习实现
javascript·react native·react.js
CDwenhuohuo2 小时前
安卓app巨坑 nvue后者页面要写画笔绘制功能nvue canvas
前端·javascript·vue.js
一只小bit2 小时前
Qt 事件:覆盖介绍、处理、各种类型及运用全详解
前端·c++·qt·cpp
Never_Satisfied2 小时前
在JavaScript / HTML中,HTML元素自定义属性使用指南
开发语言·javascript·html
前端 贾公子2 小时前
husky 9.0升级指南
javascript