@robot-admin/request-core 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -0
- package/LICENSE +21 -0
- package/README.md +248 -357
- package/SECURITY.md +14 -0
- package/dist/axios.cjs +134 -809
- package/dist/axios.cjs.map +1 -1
- package/dist/axios.d.cts +55 -220
- package/dist/axios.d.ts +55 -220
- package/dist/axios.js +2 -790
- package/dist/axios.js.map +1 -1
- package/dist/chunk-2JXH7SIL.js +711 -0
- package/dist/chunk-2JXH7SIL.js.map +1 -0
- package/dist/chunk-56QOEWUS.js +8 -0
- package/dist/chunk-56QOEWUS.js.map +1 -0
- package/dist/chunk-DUP3Y4DN.js +320 -0
- package/dist/chunk-DUP3Y4DN.js.map +1 -0
- package/dist/chunk-JGFRUAZT.cjs +1007 -0
- package/dist/chunk-JGFRUAZT.cjs.map +1 -0
- package/dist/chunk-KHI6XQRN.cjs +717 -0
- package/dist/chunk-KHI6XQRN.cjs.map +1 -0
- package/dist/chunk-OAZ5BUBU.js +969 -0
- package/dist/chunk-OAZ5BUBU.js.map +1 -0
- package/dist/chunk-QSDNDTOZ.js +28 -0
- package/dist/chunk-QSDNDTOZ.js.map +1 -0
- package/dist/chunk-UHXBAFY6.cjs +323 -0
- package/dist/chunk-UHXBAFY6.cjs.map +1 -0
- package/dist/chunk-WUZ43MLM.cjs +30 -0
- package/dist/chunk-WUZ43MLM.cjs.map +1 -0
- package/dist/chunk-WXHQXPS4.cjs +10 -0
- package/dist/chunk-WXHQXPS4.cjs.map +1 -0
- package/dist/crud.cjs +12 -484
- package/dist/crud.cjs.map +1 -1
- package/dist/crud.d.cts +8 -269
- package/dist/crud.d.ts +8 -269
- package/dist/crud.js +4 -487
- package/dist/crud.js.map +1 -1
- package/dist/index.cjs +136 -1302
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -165
- package/dist/index.d.ts +7 -165
- package/dist/index.js +5 -1283
- package/dist/index.js.map +1 -1
- package/dist/naive.cjs +14 -0
- package/dist/naive.cjs.map +1 -0
- package/dist/naive.d.cts +9 -0
- package/dist/naive.d.ts +9 -0
- package/dist/naive.js +5 -0
- package/dist/naive.js.map +1 -0
- package/dist/types-BRDRqYFs.d.cts +197 -0
- package/dist/types-BVJhlSuh.d.cts +310 -0
- package/dist/types-BVJhlSuh.d.ts +310 -0
- package/dist/types-CUyibJZX.d.ts +197 -0
- package/dist/vue.cjs +93 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.d.cts +38 -0
- package/dist/vue.d.ts +38 -0
- package/dist/vue.js +71 -0
- package/dist/vue.js.map +1 -0
- package/package.json +60 -16
package/README.md
CHANGED
|
@@ -1,455 +1,346 @@
|
|
|
1
1
|
# @robot-admin/request-core
|
|
2
2
|
|
|
3
|
-
> 为 Vue 3 打造的企业级请求解决方案:Axios 增强 + 6 个请求插件 + CRUD Composable
|
|
4
|
-
|
|
5
3
|
[](https://www.npmjs.com/package/@robot-admin/request-core)
|
|
6
|
-
[](./LICENSE)
|
|
9
5
|
|
|
10
|
-
|
|
6
|
+
面向生产环境的实例化请求编排与 Vue 3 Headless CRUD 工具。当前版本:
|
|
7
|
+
`0.4.0`。
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
它保留 Axios 的完整能力,只收拢应用中最容易重复出错的部分:并发请求、缓存、
|
|
10
|
+
取消、重试、Token 刷新、错误标准化以及列表 CRUD 生命周期。
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
- 🔌 **请求插件**:缓存、重试、去重、取消与可共享的 reLogin 协调
|
|
16
|
-
- 📊 **useTableCrud**:配置式表格 CRUD,自动处理分页/搜索/增删改查
|
|
17
|
-
- 🎯 **智能适配**:自动兼容不同后端响应格式(字段名、成功码)
|
|
18
|
-
- 💪 **类型安全**:完整 TypeScript 支持
|
|
19
|
-
- 🎨 **Naive UI 集成**:深度集成 Naive UI 组件
|
|
12
|
+
## 特性
|
|
20
13
|
|
|
21
|
-
|
|
14
|
+
- 每个 Client 独立持有缓存、待处理请求、取消作用域和认证状态,无跨应用污染。
|
|
15
|
+
- 简单调用保持一行,复杂调用通过平铺的请求配置按需启用。
|
|
16
|
+
- 相同请求支持 `join`、`takeLatest`、`takeFirst`、`allow` 四种并发语义。
|
|
17
|
+
- 内存 LRU 缓存支持 TTL、标签/前缀失效、自定义同步 CacheStore 和引用保护。
|
|
18
|
+
- 重试支持幂等方法白名单、指数退避、抖动、`Retry-After` 和总时间预算。
|
|
19
|
+
- Token 主动刷新、并发 401 恢复和重新登录均使用 single-flight。
|
|
20
|
+
- `RequestError` 统一业务、HTTP、网络、超时、取消和配置错误。
|
|
21
|
+
- Vue 层不依赖具体 UI;Naive UI 仅作为可选兼容适配层。
|
|
22
|
+
- ESM、CJS 和 TypeScript 类型入口均经过发布前验证。
|
|
22
23
|
|
|
23
|
-
##
|
|
24
|
+
## 安装与入口
|
|
24
25
|
|
|
25
26
|
```bash
|
|
26
|
-
|
|
27
|
-
# 或
|
|
28
|
-
bun add @robot-admin/request-core
|
|
27
|
+
bun add @robot-admin/request-core axios
|
|
29
28
|
```
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
`
|
|
30
|
+
| 入口 | 依赖边界 | 用途 |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| `@robot-admin/request-core/axios` | Axios | 推荐的请求 Client、策略及兼容 API |
|
|
33
|
+
| `@robot-admin/request-core/vue` | Axios + Vue | `useRequest`、Headless `useTableCrud`、Client 注入 |
|
|
34
|
+
| `@robot-admin/request-core/naive` | Axios + Vue + Naive UI | `useNaiveTableCrud` 兼容适配 |
|
|
35
|
+
| `@robot-admin/request-core` | 完整兼容入口 | 旧项目平滑迁移,当前仍会引用 Naive UI |
|
|
36
|
+
| `@robot-admin/request-core/crud` | 兼容入口 | 已废弃,迁移到 `/vue` 或 `/naive` |
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
纯请求项目应使用 `/axios`,这样不会引入 Vue 或任何 UI 框架。
|
|
39
39
|
|
|
40
|
-
##
|
|
40
|
+
## 推荐接入
|
|
41
41
|
|
|
42
|
-
###
|
|
42
|
+
### 创建唯一的应用 Client
|
|
43
43
|
|
|
44
44
|
```ts
|
|
45
|
-
|
|
46
|
-
import {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
app.use(createRequestCore({
|
|
51
|
-
request: { baseURL: '/api', timeout: 10000 },
|
|
52
|
-
interceptors: {
|
|
53
|
-
request: (config) => {
|
|
54
|
-
config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`
|
|
55
|
-
return config
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}))
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### 2️⃣ 使用 CRUD(任意组件)
|
|
45
|
+
// src/services/request.ts
|
|
46
|
+
import {
|
|
47
|
+
createRequestClient,
|
|
48
|
+
type ResponseAdapter,
|
|
49
|
+
} from '@robot-admin/request-core/axios'
|
|
62
50
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
51
|
+
const responseAdapter: ResponseAdapter = {
|
|
52
|
+
isSuccess: data => [0, 200].includes((data as { code: number }).code),
|
|
53
|
+
getData: data => (data as { data: unknown }).data,
|
|
54
|
+
getError: data => ({
|
|
55
|
+
code: (data as { code?: number }).code,
|
|
56
|
+
message: (data as { message?: string }).message ?? '请求失败',
|
|
57
|
+
data,
|
|
58
|
+
}),
|
|
59
|
+
}
|
|
66
60
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
61
|
+
export const request = createRequestClient({
|
|
62
|
+
request: {
|
|
63
|
+
baseURL: import.meta.env.VITE_API_BASE,
|
|
64
|
+
timeout: 10_000,
|
|
65
|
+
},
|
|
66
|
+
response: responseAdapter,
|
|
67
|
+
defaults: {
|
|
68
|
+
concurrency: 'join',
|
|
69
|
+
retry: { enabled: false },
|
|
70
|
+
cache: { enabled: false },
|
|
71
|
+
},
|
|
72
|
+
hooks: {
|
|
73
|
+
onError: error => {
|
|
74
|
+
if (error.kind !== 'canceled') window.$message?.error(error.message)
|
|
75
|
+
},
|
|
76
|
+
},
|
|
74
77
|
})
|
|
75
|
-
</script>
|
|
76
|
-
|
|
77
|
-
<template>
|
|
78
|
-
<n-data-table :data="table.data.value" :columns="table.columns.value" :loading="table.loading.value" />
|
|
79
|
-
</template>
|
|
80
78
|
```
|
|
81
79
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
## 📚 核心 API
|
|
87
|
-
|
|
88
|
-
### 请求方法
|
|
89
|
-
|
|
90
|
-
| 方法 | 说明 | 示例 |
|
|
91
|
-
|------|------|------|
|
|
92
|
-
| `getData(url, config?)` | GET 请求 | `getData('/users')` |
|
|
93
|
-
| `postData(url, data, config?)` | POST 请求 | `postData('/users', { name: '张三' })` |
|
|
94
|
-
| `putData(url, data, config?)` | PUT 请求 | `putData('/users/1', { name: '李四' })` |
|
|
95
|
-
| `deleteData(url, config?)` | DELETE 请求 | `deleteData('/users/1')` |
|
|
96
|
-
|
|
97
|
-
### useTableCrud 配置
|
|
98
|
-
|
|
99
|
-
| 属性 | 类型 | 必填 | 说明 |
|
|
100
|
-
|------|------|------|------|
|
|
101
|
-
| `api.list` | `string` | ✅ | 列表接口 |
|
|
102
|
-
| `api.get` | `string` | - | 详情接口(支持 `:id`) |
|
|
103
|
-
| `api.create` | `string` | - | 创建接口 |
|
|
104
|
-
| `api.update` | `string` | - | 更新接口(支持 `:id`) |
|
|
105
|
-
| `api.remove` | `string` | - | 删除接口(支持 `:id`) |
|
|
106
|
-
| `columns` | `TableColumn[]` | ✅ | 表格列配置 |
|
|
107
|
-
| `customActions` | `CustomAction[]` | - | 自定义操作按钮 |
|
|
108
|
-
| `idKey` | `string` | - | ID 字段名(默认 `'id'`) |
|
|
109
|
-
| `defaultPageSize` | `number` | - | 每页条数(默认 `10`) |
|
|
110
|
-
| `autoLoad` | `boolean` | - | 自动加载(默认 `true`) |
|
|
111
|
-
|
|
112
|
-
### useTableCrud 返回值
|
|
113
|
-
|
|
114
|
-
| 属性/方法 | 类型 | 说明 |
|
|
115
|
-
|-----------|------|------|
|
|
116
|
-
| `data` | `Ref<T[]>` | 表格数据 |
|
|
117
|
-
| `loading` | `Ref<boolean>` | 加载状态 |
|
|
118
|
-
| `total` | `Ref<number>` | 总条数 |
|
|
119
|
-
| `pagination` | `object` | 分页配置(Naive UI 格式) |
|
|
120
|
-
| `columns` | `Ref<DataTableColumn[]>` | 表格列(含操作列) |
|
|
121
|
-
| `search()` | `() => Promise<void>` | 搜索 |
|
|
122
|
-
| `resetSearch()` | `() => void` | 重置搜索 |
|
|
123
|
-
| `refresh()` | `() => Promise<void>` | 刷新数据 |
|
|
124
|
-
| `viewDetail(row)` | `(row: T) => void` | 查看详情 |
|
|
125
|
-
| `handleEdit(row)` | `(row: T) => void` | 编辑 |
|
|
126
|
-
| `handleDelete(row)` | `(row: T) => void` | 删除 |
|
|
127
|
-
|
|
128
|
-
---
|
|
80
|
+
所有能力均可省略。默认缓存和重试关闭;新 Client 的相同安全读取请求默认共享结果,
|
|
81
|
+
副作用方法默认允许并发。
|
|
129
82
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
所有请求方法都支持插件配置:
|
|
133
|
-
|
|
134
|
-
### 缓存插件(仅 GET)
|
|
83
|
+
### 类型化调用
|
|
135
84
|
|
|
136
85
|
```ts
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
forceUpdate: false // 不强制更新
|
|
142
|
-
}
|
|
143
|
-
})
|
|
144
|
-
```
|
|
86
|
+
interface User {
|
|
87
|
+
id: number
|
|
88
|
+
name: string
|
|
89
|
+
}
|
|
145
90
|
|
|
146
|
-
|
|
91
|
+
interface CreateUser {
|
|
92
|
+
name: string
|
|
93
|
+
}
|
|
147
94
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
retry: {
|
|
151
|
-
enabled: true, // 启用重试
|
|
152
|
-
count: 3, // 最多重试 3 次
|
|
153
|
-
delay: 1000, // 基础延迟 1 秒
|
|
154
|
-
exponentialBackoff: true,// 指数退避
|
|
155
|
-
jitter: true, // ±25% 随机抖动,降低并发惊群
|
|
156
|
-
}
|
|
95
|
+
const users = await request.get<User[]>('/users', {
|
|
96
|
+
params: { keyword: 'robot' },
|
|
157
97
|
})
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
默认只重试 `GET/HEAD/OPTIONS/PUT/DELETE`。只有服务端实现幂等键等保护、确认重复
|
|
161
|
-
执行安全时,才应通过 `retryableMethods: ['POST']` 显式允许 POST。
|
|
162
98
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
```ts
|
|
166
|
-
getData('/users', {
|
|
167
|
-
dedupe: {
|
|
168
|
-
enabled: true, // 启用去重(默认启用)
|
|
169
|
-
keyGenerator: (config) => `${config.method}-${config.url}` // 自定义去重 key
|
|
170
|
-
}
|
|
99
|
+
const user = await request.post<User, CreateUser>('/users', {
|
|
100
|
+
name: 'Robot',
|
|
171
101
|
})
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
默认 key 会包含 `Authorization`、`X-Tenant-Id` 和 `X-User-Id`,防止多租户或
|
|
175
|
-
多用户场景下缓存/去重串用;如自定义 `keyGenerator`,也应保留等价的身份维度。
|
|
176
|
-
|
|
177
|
-
### 取消插件
|
|
178
102
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
enabled: true, // 启用自动取消(路由切换时)
|
|
183
|
-
whitelist: [] // 白名单接口(不取消)
|
|
184
|
-
}
|
|
103
|
+
const response = await request.raw<User>({
|
|
104
|
+
method: 'GET',
|
|
105
|
+
url: '/users/1',
|
|
185
106
|
})
|
|
186
107
|
```
|
|
187
108
|
|
|
188
|
-
|
|
109
|
+
支持 `request/get/post/put/patch/delete/head/options/raw`。`raw()` 返回完整
|
|
110
|
+
`AxiosResponse`,其他方法返回响应适配器处理后的数据。
|
|
189
111
|
|
|
190
|
-
|
|
191
|
-
刷新 token 和重发请求,库不会擅自决定业务流程:
|
|
112
|
+
## 全局配置与按需能力
|
|
192
113
|
|
|
193
|
-
|
|
194
|
-
import {
|
|
195
|
-
waitForReLogin,
|
|
196
|
-
onReLoginSuccess,
|
|
197
|
-
onReLoginCancel,
|
|
198
|
-
} from '@robot-admin/request-core/axios'
|
|
114
|
+
策略可以在 Client 层配置默认值,也可以被单次请求覆盖:
|
|
199
115
|
|
|
200
|
-
|
|
201
|
-
await
|
|
116
|
+
```ts
|
|
117
|
+
await request.get('/dashboard', {
|
|
118
|
+
cache: { enabled: true, ttl: 60_000, tags: ['dashboard'] },
|
|
119
|
+
retry: { enabled: true, count: 2, maxElapsedMs: 8_000 },
|
|
120
|
+
concurrency: 'takeLatest',
|
|
121
|
+
scope: 'dashboard-page',
|
|
122
|
+
})
|
|
202
123
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
onReLoginCancel()
|
|
124
|
+
request.cache.invalidateTag('dashboard')
|
|
125
|
+
request.requests.cancelScope('dashboard-page')
|
|
206
126
|
```
|
|
207
127
|
|
|
208
|
-
|
|
128
|
+
并发策略:
|
|
209
129
|
|
|
210
|
-
|
|
130
|
+
- `join`:相同请求共享一次网络调用,适合字典和初始化数据。
|
|
131
|
+
- `takeLatest`:取消旧请求,只保留最新请求,适合搜索和分页。
|
|
132
|
+
- `takeFirst`:已有相同请求时拒绝新请求,适合提交按钮。
|
|
133
|
+
- `allow`:允许全部并发,适合调用方自行管理的任务。
|
|
211
134
|
|
|
212
|
-
|
|
135
|
+
旧 `dedupe` 配置继续可用。隐式去重只作用于 GET、HEAD、OPTIONS;POST 等
|
|
136
|
+
副作用请求不会被默认取消。
|
|
213
137
|
|
|
214
|
-
|
|
215
|
-
<script setup lang="ts">
|
|
216
|
-
import { useTableCrud } from '@robot-admin/request-core'
|
|
138
|
+
### 缓存
|
|
217
139
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
role: string
|
|
223
|
-
}
|
|
140
|
+
```ts
|
|
141
|
+
const client = createRequestClient({
|
|
142
|
+
cache: { maxSize: 500, clone: true },
|
|
143
|
+
})
|
|
224
144
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
remove: '/api/users/:id'
|
|
145
|
+
await client.get('/users', {
|
|
146
|
+
cache: {
|
|
147
|
+
enabled: true,
|
|
148
|
+
ttl: 5 * 60_000,
|
|
149
|
+
tags: ['users'],
|
|
150
|
+
varyHeaders: ['accept-language'],
|
|
232
151
|
},
|
|
233
|
-
columns: [
|
|
234
|
-
{ key: 'id', title: 'ID', width: 80 },
|
|
235
|
-
{ key: 'name', title: '姓名', width: 120 },
|
|
236
|
-
{ key: 'email', title: '邮箱', width: 200 },
|
|
237
|
-
{ key: 'role', title: '角色', width: 100 }
|
|
238
|
-
],
|
|
239
|
-
customActions: [
|
|
240
|
-
{
|
|
241
|
-
key: 'resetPassword',
|
|
242
|
-
label: '重置密码',
|
|
243
|
-
icon: 'mdi:lock-reset',
|
|
244
|
-
handler: async (row, ctx) => {
|
|
245
|
-
await postData(`/api/users/${row.id}/reset-password`, {})
|
|
246
|
-
ctx.message.success('密码已重置')
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
]
|
|
250
152
|
})
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
<!-- 搜索栏 -->
|
|
256
|
-
<n-space>
|
|
257
|
-
<n-input v-model:value="table.searchKeyword.value" placeholder="搜索用户..." />
|
|
258
|
-
<n-button type="primary" @click="table.search()">搜索</n-button>
|
|
259
|
-
<n-button @click="table.resetSearch()">重置</n-button>
|
|
260
|
-
<n-button type="success" @click="table.handleCreate()">新增用户</n-button>
|
|
261
|
-
</n-space>
|
|
262
|
-
|
|
263
|
-
<!-- 表格 -->
|
|
264
|
-
<n-data-table
|
|
265
|
-
:data="table.data.value"
|
|
266
|
-
:columns="table.columns.value"
|
|
267
|
-
:loading="table.loading.value"
|
|
268
|
-
:pagination="table.pagination"
|
|
269
|
-
/>
|
|
270
|
-
</n-space>
|
|
271
|
-
</template>
|
|
153
|
+
|
|
154
|
+
client.cache.clear()
|
|
155
|
+
client.cache.invalidateTag('users')
|
|
156
|
+
client.cache.invalidatePrefix('GET|')
|
|
272
157
|
```
|
|
273
158
|
|
|
274
|
-
|
|
159
|
+
缓存按 Client 隔离,键包含 `baseURL`、URL、方法、参数、请求体、响应类型和身份
|
|
160
|
+
相关请求头摘要。切换用户或租户时仍建议显式 `client.cache.clear()`。
|
|
275
161
|
|
|
276
|
-
|
|
277
|
-
import { getData, postData } from '@robot-admin/request-core'
|
|
162
|
+
### 重试
|
|
278
163
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
cache: { enabled: true, ttl: 300000 } // 缓存 5 分钟
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
// 默认仅重试幂等方法;POST 必须由业务明确确认可安全重复后才能加入白名单
|
|
285
|
-
const result = await postData('/api/idempotent-submit', { data: 'test' }, {
|
|
164
|
+
```ts
|
|
165
|
+
await request.get('/reports', {
|
|
286
166
|
retry: {
|
|
287
167
|
enabled: true,
|
|
288
168
|
count: 3,
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
169
|
+
delay: 500,
|
|
170
|
+
maxDelay: 10_000,
|
|
171
|
+
maxElapsedMs: 20_000,
|
|
172
|
+
respectRetryAfter: true,
|
|
173
|
+
onRetry: ({ attempt, delay }) => reportRetry(attempt, delay),
|
|
174
|
+
},
|
|
292
175
|
})
|
|
293
176
|
```
|
|
294
177
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
自动重试普通 POST 请求。
|
|
298
|
-
|
|
299
|
-
---
|
|
300
|
-
|
|
301
|
-
## ⚙️ 高级配置
|
|
302
|
-
|
|
303
|
-
### 适配不同后端响应格式
|
|
304
|
-
|
|
305
|
-
#### 1. 自定义成功状态码
|
|
178
|
+
默认只允许 GET、HEAD、OPTIONS、PUT、DELETE 重试。POST 不会自动重试;流式或
|
|
179
|
+
不可重放的 body 也会被保护性跳过。
|
|
306
180
|
|
|
307
|
-
|
|
181
|
+
## 认证恢复
|
|
308
182
|
|
|
309
183
|
```ts
|
|
310
|
-
|
|
184
|
+
const request = createRequestClient({
|
|
311
185
|
request: { baseURL: '/api' },
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
186
|
+
auth: {
|
|
187
|
+
getToken: () => userStore.token,
|
|
188
|
+
shouldRefresh: () => userStore.isTokenExpiringSoon(),
|
|
189
|
+
refresh: async ({ raw, signal }) => {
|
|
190
|
+
const response = await raw<{ data: { token: string } }>({
|
|
191
|
+
method: 'POST',
|
|
192
|
+
url: '/auth/refresh-token',
|
|
193
|
+
data: { refreshToken: userStore.refreshToken },
|
|
194
|
+
signal,
|
|
195
|
+
})
|
|
196
|
+
const token = response.data.data.token
|
|
197
|
+
userStore.setToken(token)
|
|
198
|
+
return token
|
|
199
|
+
},
|
|
200
|
+
reauthenticate: async () => {
|
|
201
|
+
await reLoginDialog.open()
|
|
202
|
+
return userStore.token
|
|
203
|
+
},
|
|
204
|
+
isAuthRequest: config => config.url?.startsWith('/auth/') === true,
|
|
205
|
+
},
|
|
322
206
|
})
|
|
323
207
|
```
|
|
324
208
|
|
|
325
|
-
|
|
209
|
+
并发请求共享同一次刷新或重新登录。401 请求最多自动重放一次;`raw` 会自动
|
|
210
|
+
设置 `skipAuth` 并关闭重试、缓存去重和取消跟踪,防止刷新接口递归。
|
|
211
|
+
|
|
212
|
+
单次请求也可显式使用 `skipAuth: true`。
|
|
326
213
|
|
|
327
|
-
|
|
214
|
+
## Vue 接入
|
|
328
215
|
|
|
329
216
|
```ts
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
total: ['totalCount', 'total'] // 总数字段
|
|
336
|
-
}
|
|
337
|
-
})
|
|
217
|
+
// main.ts
|
|
218
|
+
import { createRequestPlugin } from '@robot-admin/request-core/vue'
|
|
219
|
+
import { request } from '@/services/request'
|
|
220
|
+
|
|
221
|
+
app.use(createRequestPlugin(request))
|
|
338
222
|
```
|
|
339
223
|
|
|
340
|
-
|
|
224
|
+
这只通过 Vue InjectionKey 提供 Client,不写入 `window`,也不修改 Vue 全局类型。
|
|
225
|
+
组件仍可通过配置显式传入 Client,适合多后端或多租户应用。
|
|
226
|
+
|
|
227
|
+
### useRequest
|
|
341
228
|
|
|
342
229
|
```ts
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
|
|
230
|
+
import { useRequest } from '@robot-admin/request-core/vue'
|
|
231
|
+
import { request } from '@/services/request'
|
|
232
|
+
|
|
233
|
+
const user = useRequest(
|
|
234
|
+
({ signal }, id: number) => request.get<User>(`/users/${id}`, { signal }),
|
|
235
|
+
{ concurrency: 'takeLatest', keepPreviousData: true },
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
await user.run(1)
|
|
350
239
|
```
|
|
351
240
|
|
|
352
|
-
|
|
241
|
+
提供 `data/error/loading/run/cancel/reset`,并在 Vue 作用域销毁时自动取消。
|
|
242
|
+
取消或重置后,即使业务执行器没有响应 `AbortSignal`,过期结果也不会回写;
|
|
243
|
+
`onSuccess/onError` 仅用于观察生命周期,其自身异常不会篡改真实请求结果。
|
|
353
244
|
|
|
354
|
-
##
|
|
245
|
+
## Headless useTableCrud
|
|
355
246
|
|
|
356
|
-
|
|
247
|
+
```ts
|
|
248
|
+
import { useTableCrud } from '@robot-admin/request-core/vue'
|
|
249
|
+
import { request } from '@/services/request'
|
|
250
|
+
|
|
251
|
+
const table = useTableCrud<User, UserFilters, UserSort>({
|
|
252
|
+
client: request,
|
|
253
|
+
autoLoad: 'mounted',
|
|
254
|
+
initialFilters: { keyword: '' },
|
|
255
|
+
query: ({ page, pageSize, filters, sort, signal }) =>
|
|
256
|
+
request.get('/users', {
|
|
257
|
+
params: { page, pageSize, ...filters, sort },
|
|
258
|
+
signal,
|
|
259
|
+
concurrency: 'takeLatest',
|
|
260
|
+
}),
|
|
261
|
+
mutations: {
|
|
262
|
+
create: (row, { signal }) => request.post('/users', row, { signal }),
|
|
263
|
+
update: (row, { signal }) =>
|
|
264
|
+
request.put(`/users/${row.id}`, row, { signal }),
|
|
265
|
+
remove: (row, { signal }) =>
|
|
266
|
+
request.delete(`/users/${row.id}`, { signal }),
|
|
267
|
+
},
|
|
268
|
+
createNewRow: () => ({ id: 0, name: '' }),
|
|
269
|
+
})
|
|
357
270
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
export function setupRequestCore(app: App) {
|
|
362
|
-
app.use(createRequestCore({ /* 统一配置 */ }))
|
|
363
|
-
}
|
|
364
|
-
```
|
|
271
|
+
await table.search({ keyword: 'robot' })
|
|
272
|
+
await table.resetSearch()
|
|
273
|
+
```
|
|
365
274
|
|
|
366
|
-
|
|
367
|
-
```ts
|
|
368
|
-
// composables/useUsers.ts
|
|
369
|
-
export function useUsers() {
|
|
370
|
-
return useTableCrud<User>({
|
|
371
|
-
api: { /* ... */ },
|
|
372
|
-
columns: [ /* ... */ ]
|
|
373
|
-
})
|
|
374
|
-
}
|
|
375
|
-
```
|
|
275
|
+
主要返回值:
|
|
376
276
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
277
|
+
| 状态/方法 | 说明 |
|
|
278
|
+
| --- | --- |
|
|
279
|
+
| `rows`, `total`, `error`, `lastUpdated` | 数据与错误状态 |
|
|
280
|
+
| `loading`, `isInitialLoading`, `isRefreshing` | 查询状态 |
|
|
281
|
+
| `creating`, `updating`, `removing` | 独立变更状态 |
|
|
282
|
+
| `filters`, `sort`, `page`, `pagination` | 查询条件与分页 |
|
|
283
|
+
| `refresh/reload/search/resetSearch/setSort` | 查询操作 |
|
|
284
|
+
| `create/save/remove/batchRemove/getDetail` | CRUD 操作 |
|
|
285
|
+
| `createDraft/cancel/dispose` | 草稿和生命周期 |
|
|
381
286
|
|
|
382
|
-
4
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
```
|
|
287
|
+
刷新采用 latest-wins,旧响应不会覆盖新数据;批量删除默认最多并发 4 个请求;
|
|
288
|
+
关闭删除后刷新时会同步维护本地行和总数;组件作用域销毁会终止未完成任务。
|
|
289
|
+
Message 适配器属于呈现观察层,其异常不会改变 CRUD 请求结果。
|
|
386
290
|
|
|
387
|
-
###
|
|
291
|
+
### Naive UI 兼容层
|
|
388
292
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
3. ❌ 不要在 useTableCrud 外部调用其内部方法
|
|
392
|
-
4. ❌ 不要为付款、创建订单等非幂等 POST 盲目开启重试
|
|
293
|
+
```ts
|
|
294
|
+
import { useNaiveTableCrud } from '@robot-admin/request-core/naive'
|
|
393
295
|
|
|
394
|
-
|
|
296
|
+
const table = useNaiveTableCrud({
|
|
297
|
+
client: request,
|
|
298
|
+
query: context => userApi.list(context),
|
|
299
|
+
columns,
|
|
300
|
+
})
|
|
301
|
+
```
|
|
395
302
|
|
|
396
|
-
|
|
303
|
+
`useNaiveTableCrud` 只注入 Naive UI 的 Message/Dialog,数据能力与 `/vue` 完全
|
|
304
|
+
共用。Element Plus 项目直接使用 `/vue`,在真实业务需要前无需额外 UI 适配包。
|
|
397
305
|
|
|
398
|
-
|
|
399
|
-
// 核心配置
|
|
400
|
-
export type RequestCoreConfig = {
|
|
401
|
-
request: AxiosRequestConfig // Axios 基础配置
|
|
402
|
-
successCodes?: (string | number)[] // 成功状态码
|
|
403
|
-
fieldAliases?: FieldAliases // 字段映射
|
|
404
|
-
interceptors?: InterceptorConfig // 拦截器
|
|
405
|
-
}
|
|
306
|
+
## 兼容 API
|
|
406
307
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
api: ApiEndpoints // API 端点
|
|
410
|
-
columns: TableColumn[] // 表格列
|
|
411
|
-
customActions?: CustomAction[] // 自定义操作
|
|
412
|
-
idKey?: string // ID 字段名
|
|
413
|
-
defaultPageSize?: number // 默认分页大小
|
|
414
|
-
autoLoad?: boolean // 是否自动加载
|
|
415
|
-
extractListData?: (res: any) => { items: T[]; total: number }
|
|
416
|
-
}
|
|
308
|
+
`createRequestCore()`、`getData()`、`postData()`、`putData()`、`patchData()`、
|
|
309
|
+
`deleteData()` 继续可用。需要让这些全局兼容函数指向新 Client 时:
|
|
417
310
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
cache?: CacheConfig // 缓存配置
|
|
421
|
-
retry?: RetryConfig // 重试配置
|
|
422
|
-
dedupe?: DedupeConfig // 去重配置
|
|
423
|
-
cancel?: CancelConfig // 取消配置
|
|
424
|
-
}
|
|
311
|
+
```ts
|
|
312
|
+
const request = createRequestClient({ setAsDefault: true })
|
|
425
313
|
```
|
|
426
314
|
|
|
427
|
-
|
|
315
|
+
或者调用 `setDefaultRequestClient(request)`。全局兼容入口只保存“默认 Client”引用;
|
|
316
|
+
请求运行状态仍属于具体实例。
|
|
428
317
|
|
|
429
|
-
## 从
|
|
318
|
+
## 从 0.2.x 升级
|
|
430
319
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
320
|
+
1. 请求代码改从 `/axios` 导入,新代码优先使用 `createRequestClient()`。
|
|
321
|
+
2. Vue 应用通过 `/vue` 的 `createRequestPlugin()` 注入 Client。
|
|
322
|
+
3. Headless CRUD 从 `/vue` 导入;需要现有 Naive 消息行为时使用
|
|
323
|
+
`/naive` 的 `useNaiveTableCrud()`。
|
|
324
|
+
4. 根入口和 `/crud` 暂时兼容,`/crud` 已标记废弃。
|
|
325
|
+
5. `dedupe: true` 仍表示 take-latest;副作用方法不再隐式启用去重。
|
|
326
|
+
6. 缓存、取消和 reLogin 现在按 Axios 实例隔离;多 Client 场景应通过各自控制器
|
|
327
|
+
清理状态。
|
|
439
328
|
|
|
440
|
-
|
|
329
|
+
没有删除 0.2.x 的公开请求方法、配置字段或 Naive CRUD 交互能力。
|
|
441
330
|
|
|
442
|
-
##
|
|
331
|
+
## 开发与发布验证
|
|
443
332
|
|
|
444
333
|
```bash
|
|
445
|
-
bun
|
|
446
|
-
bun run
|
|
447
|
-
bun run build
|
|
448
|
-
bun run
|
|
334
|
+
bun run type-check
|
|
335
|
+
bun run test
|
|
336
|
+
bun run build
|
|
337
|
+
bun run check:package
|
|
338
|
+
bun run verify
|
|
449
339
|
```
|
|
450
340
|
|
|
451
|
-
|
|
341
|
+
`prepublishOnly` 会执行完整 `verify`,包含类型、测试、构建、publint、ESM/CJS
|
|
342
|
+
入口、跨入口默认实例和依赖边界检查。
|
|
452
343
|
|
|
453
|
-
##
|
|
344
|
+
## License
|
|
454
345
|
|
|
455
|
-
MIT
|
|
346
|
+
[MIT](./LICENSE)
|