@robot-admin/request-core 0.1.3 → 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.
Files changed (59) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/LICENSE +21 -0
  3. package/README.md +346 -412
  4. package/SECURITY.md +14 -0
  5. package/dist/axios.cjs +141 -0
  6. package/dist/axios.cjs.map +1 -0
  7. package/dist/axios.d.cts +67 -0
  8. package/dist/axios.d.ts +67 -0
  9. package/dist/axios.js +4 -0
  10. package/dist/axios.js.map +1 -0
  11. package/dist/chunk-2JXH7SIL.js +711 -0
  12. package/dist/chunk-2JXH7SIL.js.map +1 -0
  13. package/dist/chunk-56QOEWUS.js +8 -0
  14. package/dist/chunk-56QOEWUS.js.map +1 -0
  15. package/dist/chunk-DUP3Y4DN.js +320 -0
  16. package/dist/chunk-DUP3Y4DN.js.map +1 -0
  17. package/dist/chunk-JGFRUAZT.cjs +1007 -0
  18. package/dist/chunk-JGFRUAZT.cjs.map +1 -0
  19. package/dist/chunk-KHI6XQRN.cjs +717 -0
  20. package/dist/chunk-KHI6XQRN.cjs.map +1 -0
  21. package/dist/chunk-OAZ5BUBU.js +969 -0
  22. package/dist/chunk-OAZ5BUBU.js.map +1 -0
  23. package/dist/chunk-QSDNDTOZ.js +28 -0
  24. package/dist/chunk-QSDNDTOZ.js.map +1 -0
  25. package/dist/chunk-UHXBAFY6.cjs +323 -0
  26. package/dist/chunk-UHXBAFY6.cjs.map +1 -0
  27. package/dist/chunk-WUZ43MLM.cjs +30 -0
  28. package/dist/chunk-WUZ43MLM.cjs.map +1 -0
  29. package/dist/chunk-WXHQXPS4.cjs +10 -0
  30. package/dist/chunk-WXHQXPS4.cjs.map +1 -0
  31. package/dist/crud.cjs +19 -0
  32. package/dist/crud.cjs.map +1 -0
  33. package/dist/crud.d.cts +10 -0
  34. package/dist/crud.d.ts +10 -0
  35. package/dist/crud.js +6 -0
  36. package/dist/crud.js.map +1 -0
  37. package/dist/index.cjs +136 -1122
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +7 -644
  40. package/dist/index.d.ts +7 -644
  41. package/dist/index.js +5 -1105
  42. package/dist/index.js.map +1 -1
  43. package/dist/naive.cjs +14 -0
  44. package/dist/naive.cjs.map +1 -0
  45. package/dist/naive.d.cts +9 -0
  46. package/dist/naive.d.ts +9 -0
  47. package/dist/naive.js +5 -0
  48. package/dist/naive.js.map +1 -0
  49. package/dist/types-BRDRqYFs.d.cts +197 -0
  50. package/dist/types-BVJhlSuh.d.cts +310 -0
  51. package/dist/types-BVJhlSuh.d.ts +310 -0
  52. package/dist/types-CUyibJZX.d.ts +197 -0
  53. package/dist/vue.cjs +93 -0
  54. package/dist/vue.cjs.map +1 -0
  55. package/dist/vue.d.cts +38 -0
  56. package/dist/vue.d.ts +38 -0
  57. package/dist/vue.js +71 -0
  58. package/dist/vue.js.map +1 -0
  59. package/package.json +77 -14
package/README.md CHANGED
@@ -1,412 +1,346 @@
1
- # @robot-admin/request-core
2
-
3
- > 为 Vue 3 打造的企业级请求解决方案:Axios 增强 + 7 大插件 + CRUD Composable
4
-
5
- [![npm version](https://img.shields.io/npm/v/@robot-admin/request-core.svg)](https://www.npmjs.com/package/@robot-admin/request-core)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
-
8
- ---
9
-
10
- ## 核心特性
11
-
12
- - 🚀 **开箱即用**:3 步接入,5 分钟实现完整 CRUD
13
- - 🔌 **7 大插件**:缓存、重试、去重、取消、reLogin 等开箱即用
14
- - 📊 **useTableCrud**:配置式表格 CRUD,自动处理分页/搜索/增删改查
15
- - 🎯 **智能适配**:自动兼容不同后端响应格式(字段名、成功码)
16
- - 💪 **类型安全**:完整 TypeScript 支持
17
- - 🎨 **Naive UI 集成**:深度集成 Naive UI 组件
18
-
19
- ---
20
-
21
- ## 📦 安装
22
-
23
- ```bash
24
- npm install @robot-admin/request-core
25
- # 或
26
- bun add @robot-admin/request-core
27
- ```
28
-
29
- **Peer Dependencies**: `vue@^3.4.0`, `naive-ui@^2.38.0`
30
-
31
- ---
32
-
33
- ## 🚀 30 秒快速上手
34
-
35
- ### 1️⃣ 初始化(main.ts)
36
-
37
- ```ts
38
- import { createApp } from 'vue'
39
- import { createRequestCore } from '@robot-admin/request-core'
40
-
41
- const app = createApp(App)
42
-
43
- app.use(createRequestCore({
44
- request: { baseURL: '/api', timeout: 10000 },
45
- interceptors: {
46
- request: (config) => {
47
- config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`
48
- return config
49
- }
50
- }
51
- }))
52
- ```
53
-
54
- ### 2️⃣ 使用 CRUD(任意组件)
55
-
56
- ```vue
57
- <script setup lang="ts">
58
- import { useTableCrud } from '@robot-admin/request-core'
59
-
60
- const table = useTableCrud({
61
- api: { list: '/users', get: '/users/:id', create: '/users', update: '/users/:id', remove: '/users/:id' },
62
- columns: [
63
- { key: 'id', title: 'ID' },
64
- { key: 'name', title: '姓名' },
65
- { key: 'email', title: '邮箱' }
66
- ]
67
- })
68
- </script>
69
-
70
- <template>
71
- <n-data-table :data="table.data.value" :columns="table.columns.value" :loading="table.loading.value" />
72
- </template>
73
- ```
74
-
75
- ✅ **完成!** 一个完整的带分页、搜索、编辑、删除的数据表格!
76
-
77
- ---
78
-
79
- ## 📚 核心 API
80
-
81
- ### 请求方法
82
-
83
- | 方法 | 说明 | 示例 |
84
- |------|------|------|
85
- | `getData(url, config?)` | GET 请求 | `getData('/users')` |
86
- | `postData(url, data, config?)` | POST 请求 | `postData('/users', { name: '张三' })` |
87
- | `putData(url, data, config?)` | PUT 请求 | `putData('/users/1', { name: '李四' })` |
88
- | `deleteData(url, config?)` | DELETE 请求 | `deleteData('/users/1')` |
89
-
90
- ### useTableCrud 配置
91
-
92
- | 属性 | 类型 | 必填 | 说明 |
93
- |------|------|------|------|
94
- | `api.list` | `string` | ✅ | 列表接口 |
95
- | `api.get` | `string` | - | 详情接口(支持 `:id`) |
96
- | `api.create` | `string` | - | 创建接口 |
97
- | `api.update` | `string` | - | 更新接口(支持 `:id`) |
98
- | `api.remove` | `string` | - | 删除接口(支持 `:id`) |
99
- | `columns` | `TableColumn[]` | | 表格列配置 |
100
- | `customActions` | `CustomAction[]` | - | 自定义操作按钮 |
101
- | `idKey` | `string` | - | ID 字段名(默认 `'id'`) |
102
- | `defaultPageSize` | `number` | - | 每页条数(默认 `10`) |
103
- | `autoLoad` | `boolean` | - | 自动加载(默认 `true`) |
104
-
105
- ### useTableCrud 返回值
106
-
107
- | 属性/方法 | 类型 | 说明 |
108
- |-----------|------|------|
109
- | `data` | `Ref<T[]>` | 表格数据 |
110
- | `loading` | `Ref<boolean>` | 加载状态 |
111
- | `total` | `Ref<number>` | 总条数 |
112
- | `pagination` | `object` | 分页配置(Naive UI 格式) |
113
- | `columns` | `Ref<DataTableColumn[]>` | 表格列(含操作列) |
114
- | `search()` | `() => Promise<void>` | 搜索 |
115
- | `resetSearch()` | `() => void` | 重置搜索 |
116
- | `refresh()` | `() => Promise<void>` | 刷新数据 |
117
- | `viewDetail(row)` | `(row: T) => void` | 查看详情 |
118
- | `handleEdit(row)` | `(row: T) => void` | 编辑 |
119
- | `handleDelete(row)` | `(row: T) => void` | 删除 |
120
-
121
- ---
122
-
123
- ## 🔌 插件系统
124
-
125
- 所有请求方法都支持插件配置:
126
-
127
- ### 缓存插件(仅 GET)
128
-
129
- ```ts
130
- getData('/users', {
131
- cache: {
132
- enabled: true, // 启用缓存
133
- ttl: 300000, // 5 分钟过期
134
- forceUpdate: false // 不强制更新
135
- }
136
- })
137
- ```
138
-
139
- ### 重试插件
140
-
141
- ```ts
142
- postData('/submit', data, {
143
- retry: {
144
- enabled: true, // 启用重试
145
- count: 3, // 最多重试 3 次
146
- delay: 1000, // 重试延迟 1 秒
147
- exponentialBackoff: true // 指数退避(1s, 2s, 4s)
148
- }
149
- })
150
- ```
151
-
152
- ### 去重插件
153
-
154
- ```ts
155
- getData('/users', {
156
- dedupe: {
157
- enabled: true, // 启用去重(默认启用)
158
- keyGenerator: (config) => `${config.method}-${config.url}` // 自定义去重 key
159
- }
160
- })
161
- ```
162
-
163
- ### 取消插件
164
-
165
- ```ts
166
- getData('/users', {
167
- cancel: {
168
- enabled: true, // 启用自动取消(路由切换时)
169
- whitelist: [] // 白名单接口(不取消)
170
- }
171
- })
172
- ```
173
-
174
- ### reLogin 插件
175
-
176
- 自动管理重新登录场景,多个请求等待登录完成后自动重试:
177
-
178
- ```ts
179
- import { onReLoginSuccess } from '@robot-admin/request-core'
180
-
181
- // 在登录成功后调用
182
- onReLoginSuccess() // 通知所有等待的请求继续
183
- ```
184
-
185
- ---
186
-
187
- ## 🎯 完整示例
188
-
189
- ### 场景 1:完整的数据表格(分页 + 搜索 + CRUD)
190
-
191
- ```vue
192
- <script setup lang="ts">
193
- import { useTableCrud } from '@robot-admin/request-core'
194
-
195
- interface User {
196
- id: number
197
- name: string
198
- email: string
199
- role: string
200
- }
201
-
202
- const table = useTableCrud<User>({
203
- api: {
204
- list: '/api/users/list',
205
- get: '/api/users/:id',
206
- create: '/api/users',
207
- update: '/api/users/:id',
208
- remove: '/api/users/:id'
209
- },
210
- columns: [
211
- { key: 'id', title: 'ID', width: 80 },
212
- { key: 'name', title: '姓名', width: 120 },
213
- { key: 'email', title: '邮箱', width: 200 },
214
- { key: 'role', title: '角色', width: 100 }
215
- ],
216
- customActions: [
217
- {
218
- key: 'resetPassword',
219
- label: '重置密码',
220
- icon: 'mdi:lock-reset',
221
- handler: async (row, ctx) => {
222
- await postData(`/api/users/${row.id}/reset-password`, {})
223
- ctx.message.success('密码已重置')
224
- }
225
- }
226
- ]
227
- })
228
- </script>
229
-
230
- <template>
231
- <n-space vertical>
232
- <!-- 搜索栏 -->
233
- <n-space>
234
- <n-input v-model:value="table.searchKeyword.value" placeholder="搜索用户..." />
235
- <n-button type="primary" @click="table.search()">搜索</n-button>
236
- <n-button @click="table.resetSearch()">重置</n-button>
237
- <n-button type="success" @click="table.handleCreate()">新增用户</n-button>
238
- </n-space>
239
-
240
- <!-- 表格 -->
241
- <n-data-table
242
- :data="table.data.value"
243
- :columns="table.columns.value"
244
- :loading="table.loading.value"
245
- :pagination="table.pagination"
246
- />
247
- </n-space>
248
- </template>
249
- ```
250
-
251
- ### 场景 2:自定义请求(带插件)
252
-
253
- ```ts
254
- import { getData, postData } from '@robot-admin/request-core'
255
-
256
- // 带缓存的 GET 请求
257
- const users = await getData('/api/users', {
258
- cache: { enabled: true, ttl: 300000 } // 缓存 5 分钟
259
- })
260
-
261
- // 带重试的 POST 请求
262
- const result = await postData('/api/submit', { data: 'test' }, {
263
- retry: { enabled: true, count: 3 }
264
- })
265
- ```
266
-
267
- ---
268
-
269
- ## ⚙️ 高级配置
270
-
271
- ### 适配不同后端响应格式
272
-
273
- #### 1. 自定义成功状态码
274
-
275
- 如果后端返回的成功码不是 `0` 或 `200`:
276
-
277
- ```ts
278
- createRequestCore({
279
- request: { baseURL: '/api' },
280
- successCodes: [1, '1', 'success'], // 自定义成功码
281
- interceptors: {
282
- response: (response) => {
283
- const { code } = response.data
284
- if (![1, '1', 'success'].includes(code)) {
285
- throw new Error(response.data.message)
286
- }
287
- return response
288
- }
289
- }
290
- })
291
- ```
292
-
293
- #### 2. 自定义字段名映射
294
-
295
- 如果后端返回的字段名不标准(如 `items` 而非 `list`):
296
-
297
- ```ts
298
- createRequestCore({
299
- request: { baseURL: '/api' },
300
- fieldAliases: {
301
- data: ['result', 'data'], // 数据层字段
302
- list: ['items', 'records', 'list'], // 列表字段
303
- total: ['totalCount', 'total'] // 总数字段
304
- }
305
- })
306
- ```
307
-
308
- #### 3. 单个接口特殊处理
309
-
310
- ```ts
311
- useTableCrud({
312
- api: { list: '/special/api' },
313
- extractListData: (response) => ({
314
- items: response.result?.data || [],
315
- total: response.result?.count || 0
316
- })
317
- })
318
- ```
319
-
320
- ---
321
-
322
- ## 💡 最佳实践
323
-
324
- ### 推荐做法
325
-
326
- 1. **统一初始化配置**
327
- ```ts
328
- // src/plugins/request-core.ts
329
- export function setupRequestCore(app: App) {
330
- app.use(createRequestCore({ /* 统一配置 */ }))
331
- }
332
- ```
333
-
334
- 2. **使用 composable 封装业务逻辑**
335
- ```ts
336
- // composables/useUsers.ts
337
- export function useUsers() {
338
- return useTableCrud<User>({
339
- api: { /* ... */ },
340
- columns: [ /* ... */ ]
341
- })
342
- }
343
- ```
344
-
345
- 3. **开启缓存减少重复请求**
346
- ```ts
347
- getData('/api/config', { cache: { enabled: true, ttl: 600000 } })
348
- ```
349
-
350
- 4. **重要接口启用重试**
351
- ```ts
352
- postData('/api/payment', data, { retry: { enabled: true, count: 3 } })
353
- ```
354
-
355
- ### ❌ 避免的做法
356
-
357
- 1. ❌ 不要在每个组件中重复配置 axios
358
- 2. ❌ 不要禁用去重插件(除非有特殊需求)
359
- 3. ❌ 不要在 useTableCrud 外部调用其内部方法
360
-
361
- ---
362
-
363
- ## 📖 完整类型定义
364
-
365
- ```ts
366
- // 核心配置
367
- export type RequestCoreConfig = {
368
- request: AxiosRequestConfig // Axios 基础配置
369
- successCodes?: (string | number)[] // 成功状态码
370
- fieldAliases?: FieldAliases // 字段映射
371
- interceptors?: InterceptorConfig // 拦截器
372
- }
373
-
374
- // CRUD 配置
375
- export type UseTableCrudConfig<T> = {
376
- api: ApiEndpoints // API 端点
377
- columns: TableColumn[] // 表格列
378
- customActions?: CustomAction[] // 自定义操作
379
- idKey?: string // ID 字段名
380
- defaultPageSize?: number // 默认分页大小
381
- autoLoad?: boolean // 是否自动加载
382
- extractListData?: (res: any) => { items: T[]; total: number }
383
- }
384
-
385
- // 插件配置
386
- export type EnhancedAxiosRequestConfig = AxiosRequestConfig & {
387
- cache?: CacheConfig // 缓存配置
388
- retry?: RetryConfig // 重试配置
389
- dedupe?: DedupeConfig // 去重配置
390
- cancel?: CancelConfig // 取消配置
391
- }
392
- ```
393
-
394
- 查看完整类型定义:[src/index.ts](./src/index.ts)
395
-
396
- ---
397
-
398
- ## 🛠️ 开发
399
-
400
- ```bash
401
- bun install # 安装依赖
402
- bun run dev # 开发模式(watch)
403
- bun run build # 构建
404
- bun run type-check # 类型检查
405
- ```
406
-
407
- ---
408
-
409
- ## 📄 License
410
-
411
- MIT © [ChenYu](https://github.com/ChenyCHENYU)
412
-
1
+ # @robot-admin/request-core
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@robot-admin/request-core.svg)](https://www.npmjs.com/package/@robot-admin/request-core)
4
+ [![license](https://img.shields.io/npm/l/@robot-admin/request-core.svg)](./LICENSE)
5
+
6
+ 面向生产环境的实例化请求编排与 Vue 3 Headless CRUD 工具。当前版本:
7
+ `0.4.0`。
8
+
9
+ 它保留 Axios 的完整能力,只收拢应用中最容易重复出错的部分:并发请求、缓存、
10
+ 取消、重试、Token 刷新、错误标准化以及列表 CRUD 生命周期。
11
+
12
+ ## 特性
13
+
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 类型入口均经过发布前验证。
23
+
24
+ ## 安装与入口
25
+
26
+ ```bash
27
+ bun add @robot-admin/request-core axios
28
+ ```
29
+
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
+
38
+ 纯请求项目应使用 `/axios`,这样不会引入 Vue 或任何 UI 框架。
39
+
40
+ ## 推荐接入
41
+
42
+ ### 创建唯一的应用 Client
43
+
44
+ ```ts
45
+ // src/services/request.ts
46
+ import {
47
+ createRequestClient,
48
+ type ResponseAdapter,
49
+ } from '@robot-admin/request-core/axios'
50
+
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
+ }
60
+
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
+ },
77
+ })
78
+ ```
79
+
80
+ 所有能力均可省略。默认缓存和重试关闭;新 Client 的相同安全读取请求默认共享结果,
81
+ 副作用方法默认允许并发。
82
+
83
+ ### 类型化调用
84
+
85
+ ```ts
86
+ interface User {
87
+ id: number
88
+ name: string
89
+ }
90
+
91
+ interface CreateUser {
92
+ name: string
93
+ }
94
+
95
+ const users = await request.get<User[]>('/users', {
96
+ params: { keyword: 'robot' },
97
+ })
98
+
99
+ const user = await request.post<User, CreateUser>('/users', {
100
+ name: 'Robot',
101
+ })
102
+
103
+ const response = await request.raw<User>({
104
+ method: 'GET',
105
+ url: '/users/1',
106
+ })
107
+ ```
108
+
109
+ 支持 `request/get/post/put/patch/delete/head/options/raw`。`raw()` 返回完整
110
+ `AxiosResponse`,其他方法返回响应适配器处理后的数据。
111
+
112
+ ## 全局配置与按需能力
113
+
114
+ 策略可以在 Client 层配置默认值,也可以被单次请求覆盖:
115
+
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
+ })
123
+
124
+ request.cache.invalidateTag('dashboard')
125
+ request.requests.cancelScope('dashboard-page')
126
+ ```
127
+
128
+ 并发策略:
129
+
130
+ - `join`:相同请求共享一次网络调用,适合字典和初始化数据。
131
+ - `takeLatest`:取消旧请求,只保留最新请求,适合搜索和分页。
132
+ - `takeFirst`:已有相同请求时拒绝新请求,适合提交按钮。
133
+ - `allow`:允许全部并发,适合调用方自行管理的任务。
134
+
135
+ 旧 `dedupe` 配置继续可用。隐式去重只作用于 GET、HEAD、OPTIONS;POST 等
136
+ 副作用请求不会被默认取消。
137
+
138
+ ### 缓存
139
+
140
+ ```ts
141
+ const client = createRequestClient({
142
+ cache: { maxSize: 500, clone: true },
143
+ })
144
+
145
+ await client.get('/users', {
146
+ cache: {
147
+ enabled: true,
148
+ ttl: 5 * 60_000,
149
+ tags: ['users'],
150
+ varyHeaders: ['accept-language'],
151
+ },
152
+ })
153
+
154
+ client.cache.clear()
155
+ client.cache.invalidateTag('users')
156
+ client.cache.invalidatePrefix('GET|')
157
+ ```
158
+
159
+ 缓存按 Client 隔离,键包含 `baseURL`、URL、方法、参数、请求体、响应类型和身份
160
+ 相关请求头摘要。切换用户或租户时仍建议显式 `client.cache.clear()`。
161
+
162
+ ### 重试
163
+
164
+ ```ts
165
+ await request.get('/reports', {
166
+ retry: {
167
+ enabled: true,
168
+ count: 3,
169
+ delay: 500,
170
+ maxDelay: 10_000,
171
+ maxElapsedMs: 20_000,
172
+ respectRetryAfter: true,
173
+ onRetry: ({ attempt, delay }) => reportRetry(attempt, delay),
174
+ },
175
+ })
176
+ ```
177
+
178
+ 默认只允许 GET、HEAD、OPTIONS、PUT、DELETE 重试。POST 不会自动重试;流式或
179
+ 不可重放的 body 也会被保护性跳过。
180
+
181
+ ## 认证恢复
182
+
183
+ ```ts
184
+ const request = createRequestClient({
185
+ request: { baseURL: '/api' },
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
+ },
206
+ })
207
+ ```
208
+
209
+ 并发请求共享同一次刷新或重新登录。401 请求最多自动重放一次;`raw` 会自动
210
+ 设置 `skipAuth` 并关闭重试、缓存去重和取消跟踪,防止刷新接口递归。
211
+
212
+ 单次请求也可显式使用 `skipAuth: true`。
213
+
214
+ ## Vue 接入
215
+
216
+ ```ts
217
+ // main.ts
218
+ import { createRequestPlugin } from '@robot-admin/request-core/vue'
219
+ import { request } from '@/services/request'
220
+
221
+ app.use(createRequestPlugin(request))
222
+ ```
223
+
224
+ 这只通过 Vue InjectionKey 提供 Client,不写入 `window`,也不修改 Vue 全局类型。
225
+ 组件仍可通过配置显式传入 Client,适合多后端或多租户应用。
226
+
227
+ ### useRequest
228
+
229
+ ```ts
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)
239
+ ```
240
+
241
+ 提供 `data/error/loading/run/cancel/reset`,并在 Vue 作用域销毁时自动取消。
242
+ 取消或重置后,即使业务执行器没有响应 `AbortSignal`,过期结果也不会回写;
243
+ `onSuccess/onError` 仅用于观察生命周期,其自身异常不会篡改真实请求结果。
244
+
245
+ ## Headless useTableCrud
246
+
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
+ })
270
+
271
+ await table.search({ keyword: 'robot' })
272
+ await table.resetSearch()
273
+ ```
274
+
275
+ 主要返回值:
276
+
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` | 草稿和生命周期 |
286
+
287
+ 刷新采用 latest-wins,旧响应不会覆盖新数据;批量删除默认最多并发 4 个请求;
288
+ 关闭删除后刷新时会同步维护本地行和总数;组件作用域销毁会终止未完成任务。
289
+ Message 适配器属于呈现观察层,其异常不会改变 CRUD 请求结果。
290
+
291
+ ### Naive UI 兼容层
292
+
293
+ ```ts
294
+ import { useNaiveTableCrud } from '@robot-admin/request-core/naive'
295
+
296
+ const table = useNaiveTableCrud({
297
+ client: request,
298
+ query: context => userApi.list(context),
299
+ columns,
300
+ })
301
+ ```
302
+
303
+ `useNaiveTableCrud` 只注入 Naive UI 的 Message/Dialog,数据能力与 `/vue` 完全
304
+ 共用。Element Plus 项目直接使用 `/vue`,在真实业务需要前无需额外 UI 适配包。
305
+
306
+ ## 兼容 API
307
+
308
+ `createRequestCore()`、`getData()`、`postData()`、`putData()`、`patchData()`、
309
+ `deleteData()` 继续可用。需要让这些全局兼容函数指向新 Client 时:
310
+
311
+ ```ts
312
+ const request = createRequestClient({ setAsDefault: true })
313
+ ```
314
+
315
+ 或者调用 `setDefaultRequestClient(request)`。全局兼容入口只保存“默认 Client”引用;
316
+ 请求运行状态仍属于具体实例。
317
+
318
+ ## 从 0.2.x 升级
319
+
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
+ 清理状态。
328
+
329
+ 没有删除 0.2.x 的公开请求方法、配置字段或 Naive CRUD 交互能力。
330
+
331
+ ## 开发与发布验证
332
+
333
+ ```bash
334
+ bun run type-check
335
+ bun run test
336
+ bun run build
337
+ bun run check:package
338
+ bun run verify
339
+ ```
340
+
341
+ `prepublishOnly` 会执行完整 `verify`,包含类型、测试、构建、publint、ESM/CJS
342
+ 入口、跨入口默认实例和依赖边界检查。
343
+
344
+ ## License
345
+
346
+ [MIT](./LICENSE)