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
}
}