# API 客户端模式

## 标准响应类型

```typescript
// @/types/api.d.ts
export interface ApiResponse<T = unknown> {
  code: number
  status: number
  timestamp: string
  data: T
  message?: string
  error?: string
}

export interface PageParams {
  page: number
  pageSize: number
  sort?: string
  order?: 'asc' | 'desc'
}

export interface PageResult<T> {
  list: T[]
  total: number
  page: number
  pageSize: number
}

export enum ApiErrorCode {
  Success = 0,
  ServerError = 10001,
  ParamError = 10002,
  Unauthorized = 10003,
  Forbidden = 10004,
  NotFound = 10005,
  Timeout = 10006,
  ValidationFail = 10007,
  BusinessError = 20001,
}
```

## Axios 实例

```typescript
// @/api/index.ts
import axios from 'axios'
import { setupInterceptors } from './interceptors'

const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 15000,
  headers: {
    'Content-Type': 'application/json',
  },
})

setupInterceptors(request)

export default request
```

## 响应拦截器

```typescript
// @/api/interceptors.ts
import type { AxiosInstance, AxiosResponse } from 'axios'
import { useSnackbar } from '@/composables/useSnackbar'
import { useAuthStore } from '@/store/auth'
import router from '@/router'
import { ApiErrorCode, type ApiResponse } from '@/types/api'

export function setupInterceptors(instance: AxiosInstance) {
  // 请求拦截器
  instance.interceptors.request.use(
    (config) => {
      const authStore = useAuthStore()
      if (authStore.token) {
        config.headers.Authorization = `Bearer ${authStore.token}`
      }
      return config
    },
    (error) => Promise.reject(error)
  )

  // 响应拦截器
  instance.interceptors.response.use(
    (response: AxiosResponse<ApiResponse>) => {
      const { code, data, message } = response.data

      // 业务成功
      if (code === ApiErrorCode.Success) {
        return data
      }

      // 业务失败
      const snackbar = useSnackbar()
      snackbar.error(message || '操作失败')

      return Promise.reject(new Error(message))
    },
    (error) => {
      const snackbar = useSnackbar()
      const status = error.response?.status

      switch (status) {
        case 401:
          useAuthStore().logout()
          router.push('/login')
          snackbar.error('登录已过期，请重新登录')
          break
        case 403:
          snackbar.error('无权访问')
          break
        case 404:
          snackbar.error('请求的资源不存在')
          break
        case 500:
          snackbar.error('服务器错误，请稍后重试')
          break
        default:
          if (error.code === 'ECONNABORTED') {
            snackbar.error('请求超时，请检查网络')
          } else if (!error.response) {
            snackbar.error('网络连接失败')
          }
      }

      return Promise.reject(error)
    }
  )
}
```

## API 模块模板

```typescript
// @/api/modules/[domain].ts
import request from '@/api'
import type { PageParams, PageResult } from '@/types/api'
import type { Entity, CreateDto, UpdateDto } from '@/types/[domain]'

export const entityApi = {
  // 分页列表
  getPage: (params: PageParams) =>
    request.get<PageResult<Entity>>('/entities', { params }),

  // 详情
  getById: (id: string) =>
    request.get<Entity>(`/entities/${id}`),

  // 新增
  create: (data: CreateDto) =>
    request.post<Entity>('/entities', data),

  // 更新
  update: (id: string, data: UpdateDto) =>
    request.put<Entity>(`/entities/${id}`, data),

  // 删除
  remove: (id: string) =>
    request.delete<void>(`/entities/${id}`),
}
```

## 请求取消处理

```typescript
// composables/useCancelableRequest.ts
import { onUnmounted } from 'vue'
import type { AxiosRequestConfig } from 'axios'

export function useCancelableRequest() {
  const controller = new AbortController()

  onUnmounted(() => {
    controller.abort()
  })

  function withCancel<T>(
    requestFn: (config?: AxiosRequestConfig) => Promise<T>
  ): Promise<T> {
    return requestFn({ signal: controller.signal })
  }

  return { withCancel, abort: () => controller.abort() }
}
```

## 请求重试

```typescript
// utils/retryRequest.ts
export async function retryRequest<T>(
  fn: () => Promise<T>,
  options: { retries?: number; delay?: number } = {}
): Promise<T> {
  const { retries = 3, delay = 1000 } = options

  for (let attempt = 0; attempt < retries; attempt++) {
    try {
      return await fn()
    } catch (error) {
      if (attempt === retries - 1) throw error
      await new Promise((resolve) => setTimeout(resolve, delay * (attempt + 1)))
    }
  }

  throw new Error('Max retries exceeded')
}
```

## 并发请求处理

```typescript
// 使用 Promise.all 并发请求
async function fetchDashboardData() {
  const [users, orders, stats] = await Promise.all([
    userApi.getList(),
    orderApi.getRecent(),
    statsApi.getSummary(),
  ])

  return { users, orders, stats }
}

// 使用 Promise.allSettled 处理部分失败
async function fetchWithFallback() {
  const results = await Promise.allSettled([
    userApi.getList(),
    orderApi.getList(),
  ])

  return results.map((result) =>
    result.status === 'fulfilled' ? result.value : []
  )
}
```
