@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.
Files changed (59) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/LICENSE +21 -0
  3. package/README.md +248 -357
  4. package/SECURITY.md +14 -0
  5. package/dist/axios.cjs +134 -809
  6. package/dist/axios.cjs.map +1 -1
  7. package/dist/axios.d.cts +55 -220
  8. package/dist/axios.d.ts +55 -220
  9. package/dist/axios.js +2 -790
  10. package/dist/axios.js.map +1 -1
  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 +12 -484
  32. package/dist/crud.cjs.map +1 -1
  33. package/dist/crud.d.cts +8 -269
  34. package/dist/crud.d.ts +8 -269
  35. package/dist/crud.js +4 -487
  36. package/dist/crud.js.map +1 -1
  37. package/dist/index.cjs +136 -1302
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +7 -165
  40. package/dist/index.d.ts +7 -165
  41. package/dist/index.js +5 -1283
  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 +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
  [![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
- 当前版本:`0.2.0`。
4
+ [![license](https://img.shields.io/npm/l/@robot-admin/request-core.svg)](./LICENSE)
9
5
 
10
- ---
6
+ 面向生产环境的实例化请求编排与 Vue 3 Headless CRUD 工具。当前版本:
7
+ `0.4.0`。
11
8
 
12
- ## 核心特性
9
+ 它保留 Axios 的完整能力,只收拢应用中最容易重复出错的部分:并发请求、缓存、
10
+ 取消、重试、Token 刷新、错误标准化以及列表 CRUD 生命周期。
13
11
 
14
- - 🚀 **开箱即用**:3 步接入,5 分钟实现完整 CRUD
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
- npm install @robot-admin/request-core
27
- # 或
28
- bun add @robot-admin/request-core
27
+ bun add @robot-admin/request-core axios
29
28
  ```
30
29
 
31
- **Peer Dependencies**: `axios@^1.7.0`;使用根入口或 `./crud` 时还需
32
- `vue@^3.4.0` `naive-ui@^2.38.0`。
33
-
34
- 纯请求场景可从 `@robot-admin/request-core/axios` 导入 `createAxiosInstance`
35
- API;CRUD 可使用 `@robot-admin/request-core/crud`。根入口继续保留
36
- `createRequestCore` 和全部 API,兼容已有项目。
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
- ## 🚀 30 秒快速上手
40
+ ## 推荐接入
41
41
 
42
- ### 1️⃣ 初始化(main.ts)
42
+ ### 创建唯一的应用 Client
43
43
 
44
44
  ```ts
45
- import { createApp } from 'vue'
46
- import { createRequestCore } from '@robot-admin/request-core'
47
-
48
- const app = createApp(App)
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
- ```vue
64
- <script setup lang="ts">
65
- import { useTableCrud } from '@robot-admin/request-core'
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 table = useTableCrud({
68
- api: { list: '/users', get: '/users/:id', create: '/users', update: '/users/:id', remove: '/users/:id' },
69
- columns: [
70
- { key: 'id', title: 'ID' },
71
- { key: 'name', title: '姓名' },
72
- { key: 'email', title: '邮箱' }
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
- getData('/users', {
138
- cache: {
139
- enabled: true, // 启用缓存
140
- ttl: 300000, // 5 分钟过期
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
- ```ts
149
- getData('/report', {
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
- ```ts
180
- getData('/users', {
181
- cancel: {
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
- ### reLogin 插件
109
+ 支持 `request/get/post/put/patch/delete/head/options/raw`。`raw()` 返回完整
110
+ `AxiosResponse`,其他方法返回响应适配器处理后的数据。
189
111
 
190
- 为多个 401 请求提供同一个等待 Promise。业务拦截器仍负责展示登录界面、
191
- 刷新 token 和重发请求,库不会擅自决定业务流程:
112
+ ## 全局配置与按需能力
192
113
 
193
- ```ts
194
- import {
195
- waitForReLogin,
196
- onReLoginSuccess,
197
- onReLoginCancel,
198
- } from '@robot-admin/request-core/axios'
114
+ 策略可以在 Client 层配置默认值,也可以被单次请求覆盖:
199
115
 
200
- // 每个收到 401 的请求都等待同一个 Promise
201
- await waitForReLogin()
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
- onReLoginSuccess()
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
- ### 场景 1:完整的数据表格(分页 + 搜索 + CRUD)
135
+ `dedupe` 配置继续可用。隐式去重只作用于 GET、HEAD、OPTIONS;POST
136
+ 副作用请求不会被默认取消。
213
137
 
214
- ```vue
215
- <script setup lang="ts">
216
- import { useTableCrud } from '@robot-admin/request-core'
138
+ ### 缓存
217
139
 
218
- interface User {
219
- id: number
220
- name: string
221
- email: string
222
- role: string
223
- }
140
+ ```ts
141
+ const client = createRequestClient({
142
+ cache: { maxSize: 500, clone: true },
143
+ })
224
144
 
225
- const table = useTableCrud<User>({
226
- api: {
227
- list: '/api/users/list',
228
- get: '/api/users/:id',
229
- create: '/api/users',
230
- update: '/api/users/:id',
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
- </script>
252
-
253
- <template>
254
- <n-space vertical>
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
- ### 场景 2:自定义请求(带插件)
159
+ 缓存按 Client 隔离,键包含 `baseURL`、URL、方法、参数、请求体、响应类型和身份
160
+ 相关请求头摘要。切换用户或租户时仍建议显式 `client.cache.clear()`。
275
161
 
276
- ```ts
277
- import { getData, postData } from '@robot-admin/request-core'
162
+ ### 重试
278
163
 
279
- // 带缓存的 GET 请求
280
- const users = await getData('/api/users', {
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
- retryableMethods: ['POST'],
290
- jitter: true,
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
- 调用方传入的 `AbortSignal` 会与 dedupe、路由批量取消共享同一取消链;自动重试
296
- 的退避等待也可立即取消。默认重试方法为 `GET/HEAD/OPTIONS/PUT/DELETE`,不会
297
- 自动重试普通 POST 请求。
298
-
299
- ---
300
-
301
- ## ⚙️ 高级配置
302
-
303
- ### 适配不同后端响应格式
304
-
305
- #### 1. 自定义成功状态码
178
+ 默认只允许 GET、HEAD、OPTIONS、PUT、DELETE 重试。POST 不会自动重试;流式或
179
+ 不可重放的 body 也会被保护性跳过。
306
180
 
307
- 如果后端返回的成功码不是 `0` 或 `200`:
181
+ ## 认证恢复
308
182
 
309
183
  ```ts
310
- createRequestCore({
184
+ const request = createRequestClient({
311
185
  request: { baseURL: '/api' },
312
- successCodes: [1, '1', 'success'], // 自定义成功码
313
- interceptors: {
314
- response: (response) => {
315
- const { code } = response.data
316
- if (![1, '1', 'success'].includes(code)) {
317
- throw new Error(response.data.message)
318
- }
319
- return response
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
- #### 2. 自定义字段名映射
209
+ 并发请求共享同一次刷新或重新登录。401 请求最多自动重放一次;`raw` 会自动
210
+ 设置 `skipAuth` 并关闭重试、缓存去重和取消跟踪,防止刷新接口递归。
211
+
212
+ 单次请求也可显式使用 `skipAuth: true`。
326
213
 
327
- 如果后端返回的字段名不标准(如 `items` 而非 `list`):
214
+ ## Vue 接入
328
215
 
329
216
  ```ts
330
- createRequestCore({
331
- request: { baseURL: '/api' },
332
- fieldAliases: {
333
- data: ['result', 'data'], // 数据层字段
334
- list: ['items', 'records', 'list'], // 列表字段
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
- #### 3. 单个接口特殊处理
224
+ 这只通过 Vue InjectionKey 提供 Client,不写入 `window`,也不修改 Vue 全局类型。
225
+ 组件仍可通过配置显式传入 Client,适合多后端或多租户应用。
226
+
227
+ ### useRequest
341
228
 
342
229
  ```ts
343
- useTableCrud({
344
- api: { list: '/special/api' },
345
- extractListData: (response) => ({
346
- items: response.result?.data || [],
347
- total: response.result?.count || 0
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
- 1. **统一初始化配置**
359
- ```ts
360
- // src/plugins/request-core.ts
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
- 2. **使用 composable 封装业务逻辑**
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
- 3. **开启缓存减少重复请求**
378
- ```ts
379
- getData('/api/config', { cache: { enabled: true, ttl: 600000 } })
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
- ```ts
384
- getData('/api/report', { retry: { enabled: true, count: 3, jitter: true } })
385
- ```
287
+ 刷新采用 latest-wins,旧响应不会覆盖新数据;批量删除默认最多并发 4 个请求;
288
+ 关闭删除后刷新时会同步维护本地行和总数;组件作用域销毁会终止未完成任务。
289
+ Message 适配器属于呈现观察层,其异常不会改变 CRUD 请求结果。
386
290
 
387
- ### 避免的做法
291
+ ### Naive UI 兼容层
388
292
 
389
- 1. ❌ 不要在每个组件中重复配置 axios
390
- 2. 不要禁用去重插件(除非有特殊需求)
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
- ```ts
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
- // CRUD 配置
408
- export type UseTableCrudConfig<T> = {
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
- export type EnhancedAxiosRequestConfig = AxiosRequestConfig & {
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
- 查看完整类型定义:[src/index.ts](./src/index.ts)
315
+ 或者调用 `setDefaultRequestClient(request)`。全局兼容入口只保存“默认 Client”引用;
316
+ 请求运行状态仍属于具体实例。
428
317
 
429
- ## 从 v0.1.x 升级到 v0.2.0
318
+ ## 从 0.2.x 升级
430
319
 
431
- - `axios` 现在是必须的 peerDependency,应用应显式安装 `axios ^1.7`;这可以避免
432
- Axios 实例导致取消错误识别失效。
433
- - 纯请求场景建议改从 `@robot-admin/request-core/axios` 导入,避免引入 Vue
434
- Naive UI;CRUD 可从 `@robot-admin/request-core/crud` 导入,根入口保持兼容。
435
- - POST 不再默认重试。确有幂等保障时,通过 `retryableMethods` 显式开启。
436
- - 去重和路由取消现在共享取消控制器,调用方 `AbortSignal` 也会参与同一取消链。
437
- - 401 协调使用共享 Promise;登录成功或取消后必须分别调用 `onReLoginSuccess()`
438
- 或 `onReLoginCancel()` 释放所有等待请求。
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 install # 安装依赖
446
- bun run dev # 开发模式(watch)
447
- bun run build # 构建
448
- bun run type-check # 类型检查
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
- ## 📄 License
344
+ ## License
454
345
 
455
- MIT © [ChenYu](https://github.com/ChenyCHENYU)
346
+ [MIT](./LICENSE)