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
  }
}
相关推荐
掘金安东尼42 分钟前
让 JavaScript 更容易「善后」的新能力
前端·javascript·面试
掘金安东尼44 分钟前
用 HTMX 为 React Data Grid 加速实时更新
前端·javascript·面试
灵感__idea3 小时前
Hello 算法:众里寻她千“百度”
前端·javascript·算法
yinuo3 小时前
轻松接入大语言模型API -04
前端
袋鼠云数栈UED团队4 小时前
基于 Lexical 实现变量输入编辑器
前端·javascript·架构
cipher4 小时前
ERC-4626 通胀攻击:DeFi 金库的"捐款陷阱"
前端·后端·安全
UrbanJazzerati4 小时前
非常友好的Vue 3 生命周期详解
前端·面试
AAA阿giao4 小时前
从零构建一个现代登录页:深入解析 Tailwind CSS + Vite + Lucide React 的完整技术栈
前端·css·react.js
亦妤4 小时前
JS执行机制、作用域及作用域链
javascript
兆子龙5 小时前
像 React Hook 一样「自动触发」:用 Git Hook 拦住忘删的测试代码与其它翻车现场
前端·架构