# 请求工具使用说明

本文档介绍项目中封装的请求工具及其配置方式。

## 架构概览

```
┌─────────────────────────────────────────────────────────────┐
│                      业务组件                                │
├─────────────────────────────────────────────────────────────┤
│           usePost / useRunLogic (响应式 Hook)                │
├─────────────────────────────────────────────────────────────┤
│                    restTools.js                             │
│            (post / get / del / put)                         │
├─────────────────────────────────────────────────────────────┤
│                    request.js                               │
│         (axios 拦截器 + 请求去重 + 错误处理)                  │
└─────────────────────────────────────────────────────────────┘
```

---

## 一、基础请求方法 (restTools.js)

### 1. post - POST 请求

```javascript
import { post } from '@vue2-client/services/api'

// 基础用法
const result = await post('/api/user/save', { name: '张三' })

// 带配置
const result = await post('/api/user/save', { name: '张三' }, {
  globalLoading: '保存中...',  // 显示全局 Loading
  dedupe: false,               // 关闭请求去重（POST 默认开启）
  showErrorMessage: false      // 关闭 code 500 / 其他非 200 的默认错误提示，由业务自行处理
})
```

**配置项说明：**

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `globalLoading` | `boolean \| string` | `false` | 是否显示全局 Loading，传字符串可自定义提示文字 |
| `showErrorMessage` | `boolean` | `true` | 对业务 **code 500** 及其他非 200（不含 601）是否显示默认错误提示，设为 `false` 时由业务自行处理 |
| `dedupe` | `boolean` | `true` | 请求去重开关，POST 请求默认开启 |

### 2. get - GET 请求

```javascript
import { get } from '@vue2-client/services/api'

const result = await get('/api/user/list', { page: 1, size: 10 })
```

### 3. del - DELETE 请求

```javascript
import { del } from '@vue2-client/services/api'

const result = await del('/api/user/delete', { id: 1 })
```

### 4. put - PUT 请求

```javascript
import { put } from '@vue2-client/services/api'

const result = await put('/api/user/update', { id: 1, name: '李四' })
```

### 5. postByServiceName - 按服务名请求

```javascript
import { postByServiceName } from '@vue2-client/services/api'

// 自动拼接 URL: /api/{serviceName}/{url}
const result = await postByServiceName('/user/save', { name: '张三' }, 'af-system')

// 开发环境: /devApi/{serviceName}/{url}
const result = await postByServiceName('/user/save', { name: '张三' }, 'af-system', true)
```

---

## 二、Composition API Hooks (usePost.js)

### 1. usePost - 响应式 POST 请求

适用于需要在模板中绑定 loading 状态的场景。

```javascript
import { usePost } from '@vue2-client/composables'

export default {
  setup() {
    const { loading, data, error, execute, reset } = usePost('/api/user/save', {
      globalLoading: '保存中...',
      dedupe: true
    })

    const handleSave = async (formData) => {
      const { success, data, error } = await execute(formData)
      if (success) {
        message.success('保存成功')
      } else {
        // 错误处理（可选，拦截器已统一处理）
        console.error(error)
      }
    }

    return { loading, data, error, handleSave }
  }
}
```

**模板使用：**

```html
<a-button :loading="loading" @click="handleSave(formData)">保存</a-button>
```

**返回值说明：**

| 属性 | 类型 | 说明 |
|------|------|------|
| `loading` | `boolean` | 请求进行中状态（响应式） |
| `data` | `any` | 请求返回的数据（响应式） |
| `error` | `Error \| null` | 请求错误信息（响应式） |
| `execute(params, config)` | `Function` | 执行请求，返回 `{ success, data, error }` |
| `reset()` | `Function` | 重置状态 |

**execute 返回值：**

```javascript
const { success, data, error } = await execute(params)
// success: boolean - 请求是否成功
// data: any - 成功时的响应数据
// error: Error | null - 失败时的错误信息
```

---

## 三、业务逻辑请求 (runLogic / useRunLogic)

### runLogic - 直接调用业务逻辑

```javascript
import { runLogic } from '@vue2-client/services/api/common'

// 基础用法（失败时自动弹出右上角错误提示）
const result = await runLogic('getUserInfo', { userId: 1 }, 'af-system')

// 关闭默认错误提示，自行处理失败
try {
  const result = await runLogic('getUserInfo', { userId: 1 }, 'af-system', false, {
    showErrorMessage: false
  })
} catch (msg) {
  this.$message.warning(msg) // 对 500 及其他非 200 生效；601 等仍会走拦截器默认提示
}
```

**`runLogic` 的 `config` 配置项：**

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `globalLoading` | `boolean \| string` | `false` | 是否显示全局 Loading |
| `showErrorMessage` | `boolean` | `true` | 对业务 **code 500** 及其他非 200（不含 601）是否显示默认错误提示，设为 `false` 时由业务自行处理 |
| `dedupe` | `boolean` | `true` | 请求去重开关 |
| `dedupeStrategy` | `string` | 见环境 | 去重策略：`reject` 或 `reuse` |

> 说明：默认错误提示由 `request.js` 响应拦截器通过 Ant Design Vue 的 `notification` 展示（右上角）。`showErrorMessage: false` 对 **code 500**、**其他非 200**（及 HTTP 500）关闭自动提示；**601**、401/403 等仍按原逻辑提示。请求仍会 `reject`，便于在 `.catch` 或 `try/catch` 中自定义处理。

### useRunLogic - 响应式 Hook

### useRunLogic - 响应式业务逻辑调用

用于调用后端 Logic 业务逻辑，自动拼接 URL。

```javascript
import { useRunLogic } from '@vue2-client/composables'

export default {
  setup() {
    const { loading, data, error, execute, reset } = useRunLogic('getUserInfo', {
      serviceName: 'af-system',  // 可选，默认使用 VUE_APP_SYSTEM_NAME
      isDev: false,              // 可选，是否使用开发环境 API
      globalLoading: '加载中...',
      dedupe: true
    })

    const loadUser = async (userId) => {
      const { success, data: result } = await execute({ userId })
      if (success) {
        console.log('用户信息:', result)
      }
    }

    return { loading, data, loadUser }
  }
}
```

**URL 拼接规则：**
- 生产环境: `/api/{serviceName}/logic/{logicName}`
- 开发环境: `/devApi/{serviceName}/logic/{logicName}`

**配置项说明：**

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `serviceName` | `string` | `VUE_APP_SYSTEM_NAME` | 服务名称 |
| `isDev` | `boolean` | `false` | 是否使用开发环境 API |
| `globalLoading` | `boolean \| string` | `false` | 是否显示全局 Loading |
| `showErrorMessage` | `boolean` | `true` | 对业务 **code 500** 及其他非 200（不含 601）是否显示默认错误提示，设为 `false` 时由业务自行处理 |
| `dedupe` | `boolean` | `false` | 请求去重开关 |

---

## 四、全局功能

### 1. 请求去重

POST 请求默认开启去重，防止用户快速重复点击导致的重复提交。

**工作原理：**
- 相同的 URL + 参数 组合视为重复请求
- **开发环境**：默认使用 `reuse` 策略，复用请求结果，避免热更新（HMR）触发重复请求
- **生产环境**：默认使用 `reject` 策略，拒绝重复请求，严格防止重复提交
- 请求完成后自动清理记录

**去重策略：**

| 策略 | 行为 | 默认环境 | 适用场景 |
|------|------|---------|---------|
| `reuse` | 复用第一个请求的结果 | 开发环境 | 下拉框数据、热更新场景 |
| `reject` | 拒绝后续重复请求 | 生产环境 | 表单提交、数据保存 |

**关闭去重：**

```javascript
post('/api/save', data, { dedupe: false })
```

**显式指定策略：**

```javascript
// 强制使用拒绝策略（即使在开发环境）
post('/api/submit', data, { dedupeStrategy: 'reject' })

// 强制使用复用策略（即使在生产环境）
post('/api/options', params, { dedupeStrategy: 'reuse' })
```

**错误码：**
- 重复请求被拦截时，error.code 为 `ERR_DUPLICATE_REQUEST`（仅 reject 策略）

### 2. 全局 Loading

显示全屏遮罩 Loading，阻止用户操作。

```javascript
// 使用默认文字 "加载中..."
post('/api/save', data, { globalLoading: true })

// 自定义提示文字
post('/api/save', data, { globalLoading: '正在保存...' })
```

**特性：**
- 支持引用计数，多个并发请求时正确处理显示/隐藏
- 默认 30 秒超时自动隐藏，防止异常情况下 Loading 卡死
- 可通过 `forceHideGlobalLoading()` 强制隐藏

**手动控制：**

```javascript
import { 
  showGlobalLoading, 
  hideGlobalLoading, 
  forceHideGlobalLoading 
} from '@vue2-client/composables/useGlobalLoading'

// 显示 Loading（默认 30 秒超时）
showGlobalLoading('处理中...')

// 显示 Loading，自定义超时时间（60 秒）
showGlobalLoading('处理中...', 60000)

// 显示 Loading，禁用超时
showGlobalLoading('处理中...', 0)

// 隐藏 Loading（引用计数减一）
hideGlobalLoading()

// 强制隐藏（重置计数器，立即隐藏）
forceHideGlobalLoading()
```

---

## 五、EventStream 流式请求

用于 SSE (Server-Sent Events) 场景。

```javascript
import { startEventStream, startEventStreamPOST } from '@vue2-client/services/api'

// GET 方式
const stop = startEventStream(
  '/api/stream/data',
  { query: 'test' },
  {},  // headers
  (data, eventType) => {
    console.log('收到数据:', data, eventType)
  },
  (error) => {
    console.error('错误:', error)
  }
)

// 停止监听
stop()

// POST 方式
const stop = startEventStreamPOST('/api/stream/data', params, headers, onData, onError)
```

---

## 六、最佳实践

### 1. 表单提交场景

```javascript
const { loading, execute } = usePost('/api/form/save', {
  globalLoading: '提交中...',
  dedupe: true  // 防止重复提交
})

const handleSubmit = async () => {
  const { success } = await execute(formData)
  if (success) {
    message.success('提交成功')
  }
  // 错误已由拦截器统一处理，无需额外处理
}
```

### 2. 列表查询场景

```javascript
const { loading, data, execute } = usePost('/api/list/query')

const loadData = async () => {
  const { success } = await execute({ page: 1, size: 10 })
  if (success) {
    tableData.value = data
  }
}
```

### 3. 业务逻辑调用

```javascript
const { loading, execute } = useRunLogic('calculatePrice', {
  globalLoading: '计算中...'
})

const calculate = async () => {
  const { success, data } = await execute({ productId: 1, quantity: 10 })
  if (success) {
    price.value = data.totalPrice
  }
}
```

### 4. 错误处理模式

```javascript
const { execute } = usePost('/api/save')

// 模式一：只关心成功（推荐，错误由拦截器统一处理）
const handleSave = async () => {
  const { success, data } = await execute(formData)
  if (success) {
    message.success('保存成功')
  }
}

// 模式二：需要自定义错误处理（关闭 500 / 其他非 200 的默认 notification）
const handleSave = async () => {
  const { success, data, error } = await execute(formData, { showErrorMessage: false })
  if (success) {
    message.success('保存成功')
  } else if (error?.code === 'ERR_DUPLICATE_REQUEST') {
    // 重复请求，静默处理
  } else {
    message.error('保存失败: ' + (error?.message || error))
  }
}

// 模式三：runLogic 自行处理业务错误
import { runLogic } from '@vue2-client/services/api/common'

const loadData = async () => {
  try {
    return await runLogic('queryData', params, 'af-system', false, { showErrorMessage: false })
  } catch (msg) {
    this.$message.warning(typeof msg === 'string' ? msg : '查询失败')
    return null
  }
}
```

### 5. 关闭默认错误提示

当接口返回业务 **code 500**、**其他非 200**（或 HTTP 500）时，拦截器默认会在右上角弹出 `notification`。若需由业务自行处理，在请求 `config` 中设置：

```javascript
post('/api/xxx', data, { showErrorMessage: false })
runLogic('logicName', params, 'af-system', false, { showErrorMessage: false })
useRunLogic('logicName', { showErrorMessage: false })
```

**注意：**

- `showErrorMessage: false` 对 **500** 与 **其他非 200** 生效；**601**、401/403 等仍会弹出默认提示。
- 不会阻止 Promise 进入 `reject`，仅关闭上述场景的自动 notification。
- 登录过期（401）等系统级 `loginExpire` 弹窗不受此配置影响。
