@agile-team/wl-skills-kit 1.0.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 (112) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +328 -0
  3. package/bin/wl-skills.js +104 -0
  4. package/files/.github/copilot-instructions.md +211 -0
  5. package/files/.github/docs/SYS_MENU_INFO.md +247 -0
  6. package/files/.github/docs/menu-sync-design.md +265 -0
  7. package/files/.github/docs/use-skill.md +379 -0
  8. package/files/.github/docs/wl-skills-kit.md +266 -0
  9. package/files/.github/skills/api-contract/SKILL.md +247 -0
  10. package/files/.github/skills/convention-extract/SKILL.md +355 -0
  11. package/files/.github/skills/menu-sync/SKILL.md +255 -0
  12. package/files/.github/skills/menu-sync/env/guide.md +73 -0
  13. package/files/.github/skills/page-codegen/SKILL.md +825 -0
  14. package/files/.github/skills/page-codegen/TPL-CHANGE-HISTORY.md +281 -0
  15. package/files/.github/skills/page-codegen/TPL-DETAIL-TABS.md +1112 -0
  16. package/files/.github/skills/page-codegen/TPL-DRIVEN.md +124 -0
  17. package/files/.github/skills/page-codegen/TPL-FORM-ROUTE.md +441 -0
  18. package/files/.github/skills/page-codegen/TPL-LIST.md +196 -0
  19. package/files/.github/skills/page-codegen/TPL-MASTER-DETAIL.md +153 -0
  20. package/files/.github/skills/page-codegen/TPL-OPERATION-STATION.md +442 -0
  21. package/files/.github/skills/page-codegen/TPL-RECORD-FORM.md +376 -0
  22. package/files/.github/skills/page-codegen/TPL-TREE-LIST.md +191 -0
  23. package/files/.github/skills/prototype-scan/SKILL.md +414 -0
  24. package/files/demo/README.md +44 -0
  25. package/files/demo/produce/aiflow/mmwr-customer-apply-add/api.md +54 -0
  26. package/files/demo/produce/aiflow/mmwr-customer-apply-add/data.ts +346 -0
  27. package/files/demo/produce/aiflow/mmwr-customer-apply-add/index.scss +1 -0
  28. package/files/demo/produce/aiflow/mmwr-customer-apply-add/index.vue +28 -0
  29. package/files/demo/produce/aiflow/mmwr-customer-apply-add-form/data.ts +115 -0
  30. package/files/demo/produce/aiflow/mmwr-customer-apply-add-form/index.scss +44 -0
  31. package/files/demo/produce/aiflow/mmwr-customer-apply-add-form/index.vue +43 -0
  32. package/files/demo/produce/aiflow/mmwr-customer-apply-change/data.ts +338 -0
  33. package/files/demo/produce/aiflow/mmwr-customer-apply-change/index.scss +1 -0
  34. package/files/demo/produce/aiflow/mmwr-customer-apply-change/index.vue +28 -0
  35. package/files/demo/produce/aiflow/mmwr-customer-apply-change-form/data.ts +115 -0
  36. package/files/demo/produce/aiflow/mmwr-customer-apply-change-form/index.scss +44 -0
  37. package/files/demo/produce/aiflow/mmwr-customer-apply-change-form/index.vue +43 -0
  38. package/files/demo/produce/aiflow/mmwr-customer-apply-change-history/data.ts +196 -0
  39. package/files/demo/produce/aiflow/mmwr-customer-apply-change-history/index.scss +150 -0
  40. package/files/demo/produce/aiflow/mmwr-customer-apply-change-history/index.vue +79 -0
  41. package/files/demo/produce/aiflow/mmwr-customer-archive/api.md +88 -0
  42. package/files/demo/produce/aiflow/mmwr-customer-archive/data.ts +601 -0
  43. package/files/demo/produce/aiflow/mmwr-customer-archive/index.scss +1 -0
  44. package/files/demo/produce/aiflow/mmwr-customer-archive/index.vue +64 -0
  45. package/files/demo/produce/aiflow/mmwr-customer-detail/api.md +67 -0
  46. package/files/demo/produce/aiflow/mmwr-customer-detail/data.ts +286 -0
  47. package/files/demo/produce/aiflow/mmwr-customer-detail/index.scss +139 -0
  48. package/files/demo/produce/aiflow/mmwr-customer-detail/index.vue +318 -0
  49. package/files/demo/produce/aiflow/mmwr-temp-customer-archive/api.md +98 -0
  50. package/files/demo/produce/aiflow/mmwr-temp-customer-archive/data.ts +543 -0
  51. package/files/demo/produce/aiflow/mmwr-temp-customer-archive/index.scss +1 -0
  52. package/files/demo/produce/aiflow/mmwr-temp-customer-archive/index.vue +52 -0
  53. package/files/demo/sale/demo/add-demo/data.ts +518 -0
  54. package/files/demo/sale/demo/add-demo/index.scss +207 -0
  55. package/files/demo/sale/demo/add-demo/index.vue +167 -0
  56. package/files/demo/sale/demo/billet-flame-cut-plan/data.ts +524 -0
  57. package/files/demo/sale/demo/billet-flame-cut-plan/index.scss +155 -0
  58. package/files/demo/sale/demo/billet-flame-cut-plan/index.vue +117 -0
  59. package/files/demo/sale/demo/domestic-trade-order/data.ts +308 -0
  60. package/files/demo/sale/demo/domestic-trade-order/index.scss +99 -0
  61. package/files/demo/sale/demo/domestic-trade-order/index.vue +77 -0
  62. package/files/demo/sale/demo/heat-batch-return/data.ts +367 -0
  63. package/files/demo/sale/demo/heat-batch-return/index.scss +100 -0
  64. package/files/demo/sale/demo/heat-batch-return/index.vue +170 -0
  65. package/files/demo/sale/demo/heat-batch-return/meltDialog.vue +320 -0
  66. package/files/demo/sale/demo/metallurgical-spec/data.ts +825 -0
  67. package/files/demo/sale/demo/metallurgical-spec/index.scss +264 -0
  68. package/files/demo/sale/demo/metallurgical-spec/index.vue +309 -0
  69. package/files/docs/jh-date-range.md +257 -0
  70. package/files/docs/jh-date.md +222 -0
  71. package/files/docs/jh-dept-picker.md +190 -0
  72. package/files/docs/jh-drag-row.md +590 -0
  73. package/files/docs/jh-file-upload.md +216 -0
  74. package/files/docs/jh-pagination.md +505 -0
  75. package/files/docs/jh-picker.md +218 -0
  76. package/files/docs/jh-select.md +148 -0
  77. package/files/docs/jh-text.md +248 -0
  78. package/files/docs/jh-user-picker.md +197 -0
  79. package/files/docs/page-query-hook-best-practices.md +362 -0
  80. package/files/docs/request.md +925 -0
  81. package/files/src/components/global/C_ParentView/index.vue +3 -0
  82. package/files/src/components/global/C_RightToolbar/index.vue +459 -0
  83. package/files/src/components/global/C_Splitter/index.vue +195 -0
  84. package/files/src/components/global/C_SvgIcon/index.vue +61 -0
  85. package/files/src/components/global/C_SvgIcon/svgicon.js +10 -0
  86. package/files/src/components/global/C_TagStatus/README.md +264 -0
  87. package/files/src/components/global/C_TagStatus/config.ts +192 -0
  88. package/files/src/components/global/C_TagStatus/index.vue +127 -0
  89. package/files/src/components/global/C_TagStatus/types.ts +64 -0
  90. package/files/src/components/global/C_Tree/README.md +153 -0
  91. package/files/src/components/global/C_Tree/index.scss +42 -0
  92. package/files/src/components/global/C_Tree/index.vue +119 -0
  93. package/files/src/components/global/C_Tree/types.ts +59 -0
  94. package/files/src/components/local/c_formModal/README.md +235 -0
  95. package/files/src/components/local/c_formModal/data.ts +95 -0
  96. package/files/src/components/local/c_formModal/index.scss +8 -0
  97. package/files/src/components/local/c_formModal/index.vue +107 -0
  98. package/files/src/components/local/c_formSections/README.md +496 -0
  99. package/files/src/components/local/c_formSections/data.ts +175 -0
  100. package/files/src/components/local/c_formSections/index.scss +280 -0
  101. package/files/src/components/local/c_formSections/index.vue +429 -0
  102. package/files/src/components/local/c_listModal/data.ts +41 -0
  103. package/files/src/components/local/c_listModal/index.vue +136 -0
  104. package/files/src/components/local/c_spliterTitle/index.scss +25 -0
  105. package/files/src/components/local/c_spliterTitle/index.vue +21 -0
  106. package/files/src/components/remote/AGGrid/README.md +530 -0
  107. package/files/src/components/remote/BaseForm/README.md +508 -0
  108. package/files/src/components/remote/BaseQuery/README.md +865 -0
  109. package/files/src/components/remote/BaseTable/README.md +941 -0
  110. package/files/src/components/remote/BaseToolbar/README.md +496 -0
  111. package/files/src/types/page.ts +24 -0
  112. package/package.json +31 -0
@@ -0,0 +1,925 @@
1
+ # request - HTTP 请求工具
2
+
3
+ > 基于 axios 封装的统一请求工具,自动处理响应数据解包、错误拦截、token 注入等
4
+
5
+ ## 导入方式
6
+
7
+ ```typescript
8
+ import request from "@jhlc/common-core/src/util/request";
9
+ ```
10
+
11
+ ## 基本用法
12
+
13
+ ### GET 请求
14
+
15
+ ```typescript
16
+ // 基础 GET 请求
17
+ const data = await request({
18
+ url: "/api/user/list",
19
+ method: "get"
20
+ });
21
+
22
+ // 带查询参数
23
+ const data = await request({
24
+ url: "/api/user/detail",
25
+ method: "get",
26
+ params: { id: "123" }
27
+ });
28
+ ```
29
+
30
+ ### POST 请求
31
+
32
+ ```typescript
33
+ // 创建数据
34
+ const result = await request({
35
+ url: "/api/user/save",
36
+ method: "post",
37
+ data: {
38
+ name: "张三",
39
+ age: 25
40
+ }
41
+ });
42
+
43
+ // 带查询参数的 POST
44
+ const result = await request({
45
+ url: "/auth/oauth/token",
46
+ method: "post",
47
+ params: {
48
+ grant_type: "password",
49
+ username: "admin"
50
+ }
51
+ });
52
+ ```
53
+
54
+ ### PUT 请求
55
+
56
+ ```typescript
57
+ // 更新数据
58
+ const result = await request({
59
+ url: "/api/user/update",
60
+ method: "put",
61
+ data: {
62
+ id: "123",
63
+ name: "李四"
64
+ }
65
+ });
66
+ ```
67
+
68
+ ### DELETE 请求
69
+
70
+ ```typescript
71
+ // 删除数据
72
+ const result = await request({
73
+ url: "/api/user/delete",
74
+ method: "delete",
75
+ params: { id: "123" }
76
+ });
77
+ ```
78
+
79
+ ## 响应数据结构
80
+
81
+ request 会自动解包响应数据,直接返回 `{ code, message, data }` 格式:
82
+
83
+ ```typescript
84
+ // 后端返回格式
85
+ {
86
+ code: 200,
87
+ message: "操作成功",
88
+ data: { id: 1, name: "张三" }
89
+ }
90
+
91
+ // 使用 request 后直接拿到解包数据
92
+ const result = await request({ url: "/api/xxx", method: "get" });
93
+ console.log(result.code); // 200
94
+ console.log(result.message); // "操作成功"
95
+ console.log(result.data); // { id: 1, name: "张三" }
96
+ ```
97
+
98
+ ## 常见场景
99
+
100
+ ### 场景 1:列表查询
101
+
102
+ ```typescript
103
+ // src/views/your-module/data.ts
104
+ async select() {
105
+ const params = {
106
+ current: this.page.current,
107
+ size: this.page.size,
108
+ ...this.queryParam.value
109
+ };
110
+
111
+ const result = await request({
112
+ url: "/api/order/list",
113
+ method: "get",
114
+ params
115
+ });
116
+
117
+ this.list.value = result.data.records;
118
+ this.page.total = result.data.total;
119
+ }
120
+ ```
121
+
122
+ ### 场景 2:表单保存
123
+
124
+ ```typescript
125
+ // 新增/编辑统一处理
126
+ async save() {
127
+ const result = await request({
128
+ url: this.isEdit ? "/api/user/update" : "/api/user/save",
129
+ method: this.isEdit ? "put" : "post",
130
+ data: this.form.value
131
+ });
132
+
133
+ this.msgSuccess(result.message || "操作成功");
134
+ }
135
+ ```
136
+
137
+ ### 场景 3:获取详情
138
+
139
+ ```typescript
140
+ async getById(id: string) {
141
+ const result = await request({
142
+ url: "/api/order/getOneById",
143
+ method: "get",
144
+ params: { id }
145
+ });
146
+
147
+ this.form.value = result.data;
148
+ }
149
+ ```
150
+
151
+ ### 场景 4:删除数据
152
+
153
+ ```typescript
154
+ async remove(id: string) {
155
+ const result = await request({
156
+ url: "/api/order/remove",
157
+ method: "delete",
158
+ params: { id }
159
+ });
160
+
161
+ this.msgSuccess(result.message || "删除成功");
162
+ this.select(); // 刷新列表
163
+ }
164
+ ```
165
+
166
+ ### 场景 5:文件下载
167
+
168
+ ```typescript
169
+ // 下载文件需要设置 responseType
170
+ async downloadFile() {
171
+ const res = await request({
172
+ url: "/api/file/download",
173
+ method: "get",
174
+ params: { id: "123" },
175
+ responseType: "arraybuffer"
176
+ });
177
+
178
+ // 创建 Blob 并下载
179
+ const blob = new Blob([res], { type: "application/vnd.ms-excel" });
180
+ const downloadElement = document.createElement("a");
181
+ const href = window.URL.createObjectURL(blob);
182
+ downloadElement.href = href;
183
+ downloadElement.download = "文件名.xlsx";
184
+ document.body.appendChild(downloadElement);
185
+ downloadElement.click();
186
+ document.body.removeChild(downloadElement);
187
+ }
188
+ ```
189
+
190
+ ### 场景 6:获取字典数据
191
+
192
+ ```typescript
193
+ // 获取下拉选项
194
+ async loadDictOptions() {
195
+ const result = await request({
196
+ url: "/system/dictDtl/getListByDicSn",
197
+ method: "get",
198
+ params: { strSn: "ORDER_STATUS" }
199
+ });
200
+
201
+ this.statusOptions = result.data;
202
+ }
203
+ ```
204
+
205
+ ## 配置选项
206
+
207
+ ```typescript
208
+ interface RequestConfig {
209
+ url: string; // 请求地址(必填)
210
+ method: string; // 请求方法:get、post、put、delete(必填)
211
+ params?: object; // URL 查询参数
212
+ data?: object; // 请求体数据(POST/PUT)
213
+ headers?: object; // 自定义请求头
214
+ responseType?: string; // 响应类型:json、arraybuffer、blob
215
+ timeout?: number; // 超时时间(毫秒)
216
+ }
217
+ ```
218
+
219
+ ## 与原生 axios 对比
220
+
221
+ ### 原生 axios(啰嗦)
222
+
223
+ ```typescript
224
+ import axios from "axios";
225
+
226
+ // 需要手动解包 res.data
227
+ const res = await axios.get("/api/user/list");
228
+ console.log(res.data.data); // 两层 data
229
+
230
+ // POST 请求参数位置不一致
231
+ await axios.post("/api/user/save", formData);
232
+ await axios.get("/api/user/detail", { params: { id: 1 } });
233
+ ```
234
+
235
+ ### 封装 request(统一)
236
+
237
+ ```typescript
238
+ import request from "@jhlc/common-core/src/util/request";
239
+
240
+ // 自动解包,直接拿到数据
241
+ const result = await request({
242
+ url: "/api/user/list",
243
+ method: "get"
244
+ });
245
+ console.log(result.data); // 一层 data
246
+
247
+ // 所有请求配置统一
248
+ await request({ url: "/api/user/save", method: "post", data: formData });
249
+ await request({ url: "/api/user/detail", method: "get", params: { id: 1 } });
250
+ ```
251
+
252
+ ## 自动处理功能
253
+
254
+ 1. **自动解包**:返回 `{ code, message, data }` 格式,无需 `res.data.data`
255
+ 2. **Token 注入**:自动从 localStorage 读取 token 并添加到请求头
256
+ 3. **错误拦截**:统一处理 401/403/500 等错误,自动弹出提示
257
+ 4. **Loading 状态**:可选的全局 loading 动画
258
+ 5. **重复请求取消**:防止短时间内重复请求
259
+
260
+ ## 在组件中使用
261
+
262
+ ### 页面 Hook 中
263
+
264
+ ```typescript
265
+ // src/views/your-module/data.ts
266
+ import request from "@jhlc/common-core/src/util/request";
267
+
268
+ export function createPage() {
269
+ const Page = new (class extends AbstractPageQueryHook {
270
+ async customMethod() {
271
+ const result = await request({
272
+ url: "/api/custom",
273
+ method: "post",
274
+ data: { key: "value" }
275
+ });
276
+ return result.data;
277
+ }
278
+ })();
279
+
280
+ return Page.create();
281
+ }
282
+ ```
283
+
284
+ ### API 文件中
285
+
286
+ ```typescript
287
+ // src/api/user.ts
288
+ import request from "@jhlc/common-core/src/util/request";
289
+
290
+ export function getUserList(params: any) {
291
+ return request({
292
+ url: "/api/user/list",
293
+ method: "get",
294
+ params
295
+ });
296
+ }
297
+
298
+ export function saveUser(data: any) {
299
+ return request({
300
+ url: "/api/user/save",
301
+ method: "post",
302
+ data
303
+ });
304
+ }
305
+ ```
306
+
307
+ ## 错误处理
308
+
309
+ ```typescript
310
+ try {
311
+ const result = await request({
312
+ url: "/api/user/save",
313
+ method: "post",
314
+ data: formData
315
+ });
316
+ console.log("成功:", result.message);
317
+ } catch (error) {
318
+ console.error("失败:", error);
319
+ // request 会自动弹出错误提示,这里可以做额外处理
320
+ }
321
+ ```
322
+
323
+ ## 注意事项
324
+
325
+ 1. **method 必填**:必须明确指定请求方法(get、post、put、delete)
326
+ 2. **params vs data**:
327
+ - `params`:URL 查询参数,适用于 GET/DELETE
328
+ - `data`:请求体数据,适用于 POST/PUT
329
+ 3. **返回值**:直接返回解包后的数据,不是 axios 的 response 对象
330
+ 4. **错误处理**:request 会自动处理错误并提示,通常不需要手动 catch
331
+
332
+ ## 实际案例
333
+
334
+ ### 案例 1:登录
335
+
336
+ ```typescript
337
+ // src/api/login.ts
338
+ export function login(username: string, password: string) {
339
+ return request({
340
+ url: "/auth/oauth/token",
341
+ method: "post",
342
+ params: {
343
+ grant_type: "password",
344
+ username,
345
+ password,
346
+ client_id: "c1",
347
+ client_secret: "secret"
348
+ }
349
+ });
350
+ }
351
+
352
+ // 使用
353
+ const result = await login("admin", "123456");
354
+ localStorage.setItem("token", result.data.access_token);
355
+ ```
356
+
357
+ ### 案例 2:CRUD 完整流程
358
+
359
+ ```typescript
360
+ // src/views/order/data.ts
361
+ import request from "@jhlc/common-core/src/util/request";
362
+
363
+ export function createPage(modalRef: Ref<any>) {
364
+ const Page = new (class extends AbstractPageQueryHook {
365
+ // 查询列表
366
+ async select() {
367
+ const result = await request({
368
+ url: "/api/order/list",
369
+ method: "get",
370
+ params: {
371
+ current: this.page.current,
372
+ size: this.page.size
373
+ }
374
+ });
375
+ this.list.value = result.data.records;
376
+ }
377
+
378
+ // 删除
379
+ async remove(id: string) {
380
+ const result = await request({
381
+ url: "/api/order/remove",
382
+ method: "delete",
383
+ params: { id }
384
+ });
385
+ this.msgSuccess(result.message);
386
+ this.select();
387
+ }
388
+ })();
389
+
390
+ return Page.create();
391
+ }
392
+
393
+ // Modal 弹窗
394
+ export function createFormModal(props, mode, emit) {
395
+ const Page = new (class extends AbstractFormHook {
396
+ // 保存(新增/编辑)
397
+ async save() {
398
+ const result = await request({
399
+ url: mode.value === "add" ? props.api.save : props.api.update,
400
+ method: mode.value === "add" ? "post" : "put",
401
+ data: this.form.value
402
+ });
403
+ this.msgSuccess(result.message);
404
+ emit("ok");
405
+ }
406
+
407
+ // 获取详情
408
+ async getById(id: string) {
409
+ const result = await request({
410
+ url: props.api.getById,
411
+ method: "get",
412
+ params: { id }
413
+ });
414
+ this.form.value = result.data;
415
+ }
416
+ })();
417
+
418
+ return Page.create();
419
+ }
420
+ ```
421
+
422
+ ---
423
+
424
+ ## 📌 AbstractPageQueryHook 基类内置方法
425
+
426
+ > 在继承 `AbstractPageQueryHook` 的页面中,可以直接使用以下内置方法,**无需单独创建 API 层文件**。
427
+
428
+ ### 可用方法一览
429
+
430
+ | 方法 | 作用 | 推荐场景 |
431
+ |------|------|---------|
432
+ | `this.getAction` | GET 请求 | 查询详情、获取下拉选项 |
433
+ | `this.postAction` | POST 请求 | 新增、批量审批、自定义操作 |
434
+ | `this.putAction` | PUT 请求 | 修改、批量更新 |
435
+ | `this.deleteAction` | DELETE 请求 | 删除(单条/批量) |
436
+ | `this.actionBatch` | 批量操作封装 | 带确认框的批量POST/PUT |
437
+ | `this.postBatch` | POST批量快捷方法 | 批量审批、批量导入 |
438
+ | `this.putBatch` | PUT批量快捷方法 | 批量修改状态 |
439
+ | `this.deleteBatch` | DELETE批量快捷方法 | 批量删除 |
440
+
441
+ ---
442
+
443
+ ### 方法签名与参数说明
444
+
445
+ #### getAction
446
+ ```typescript
447
+ /**
448
+ * GET 请求
449
+ * @param url - 接口地址
450
+ * @param params - 查询参数(拼接到URL上)
451
+ * @param headers - 请求头配置(可选)
452
+ */
453
+ this.getAction<T>(url: string, params?: object, headers?: RequestHeader): Promise<ApiResult<T>>
454
+
455
+ // 示例
456
+ const res = await this.getAction("/api/order/detail", { id: "123" });
457
+ console.log(res.data);
458
+ ```
459
+
460
+ #### postAction
461
+ ```typescript
462
+ /**
463
+ * POST 请求
464
+ * @param url - 接口地址
465
+ * @param data - 请求体数据
466
+ * @param query - URL查询参数(可选)
467
+ * @param headers - 请求头配置(可选)
468
+ */
469
+ this.postAction<T>(url: string, data?: any, query?: object, headers?: RequestHeader): Promise<ApiResult<T>>
470
+
471
+ // 示例:审批操作
472
+ this.postAction("/api/order/approve", { ids: [row.id], status: "approved" });
473
+
474
+ // 示例:带query参数的POST
475
+ this.postAction("/api/user/import", fileData, { type: "excel" });
476
+ ```
477
+
478
+ #### putAction
479
+ ```typescript
480
+ /**
481
+ * PUT 请求
482
+ * @param url - 接口地址
483
+ * @param data - 请求体数据
484
+ * @param query - URL查询参数(可选)
485
+ * @param headers - 请求头配置(可选)
486
+ */
487
+ this.putAction<T>(url: string, data?: any, query?: object, headers?: RequestHeader): Promise<ApiResult<T>>
488
+
489
+ // 示例:修改状态
490
+ this.putAction("/api/order/updateStatus", { id: row.id, status: "completed" });
491
+ ```
492
+
493
+ #### deleteAction ⚠️
494
+ ```typescript
495
+ /**
496
+ * DELETE 请求
497
+ * @param url - 接口地址
498
+ * @param params - URL查询参数(第二个参数)
499
+ * @param data - 请求体数据(第三个参数)⚠️
500
+ * @param headers - 请求头配置(可选)
501
+ */
502
+ this.deleteAction<T>(url: string, params?: any, data?: any, headers?: RequestHeader): Promise<ApiResult<T>>
503
+
504
+ // ✅ 正确用法:把 ids 放在 data(第三个参数)
505
+ this.deleteAction("/api/order/remove", {}, { ids: [row.id] });
506
+
507
+ // ❌ 错误用法:ids 会被当作 params(query参数)
508
+ this.deleteAction("/api/order/remove", { ids: [row.id] }); // Mock/后端期望body时会失败
509
+ ```
510
+
511
+ #### actionBatch
512
+ ```typescript
513
+ /**
514
+ * 批量操作封装(自动处理选择、确认、刷新)
515
+ * @param action - 要执行的方法(this.postAction / this.putAction)
516
+ * @param url - 接口地址
517
+ * @param tip - 确认提示文字
518
+ * @param idList - ID数组(可选,默认获取选中行)
519
+ * @param autoTipSuccess - 是否自动提示成功(默认false)
520
+ */
521
+ this.actionBatch(
522
+ action: Function,
523
+ url: string,
524
+ tip: string,
525
+ idList?: string[],
526
+ autoTipSuccess?: boolean
527
+ ): Promise<ApiResult>
528
+
529
+ // 示例:批量审批
530
+ this.actionBatch(
531
+ this.postAction,
532
+ "/api/order/batchApprove",
533
+ "确定审批选中的订单吗?",
534
+ this.getSelection().map(i => i.id)
535
+ );
536
+
537
+ // ⚠️ 注意:不要用于 deleteAction(参数位置不匹配)
538
+ // ❌ this.actionBatch(this.deleteAction, url, tip, ids) // ids会变成params
539
+ ```
540
+
541
+ #### postBatch / putBatch / deleteBatch
542
+ ```typescript
543
+ /**
544
+ * 快捷批量方法(自动处理选择、确认、刷新、成功提示)
545
+ * @param url - 接口地址
546
+ * @param tip - 确认提示
547
+ * @param idList - ID数组(可选)
548
+ */
549
+ this.postBatch(url: string, tip: string, idList?: string[]): Promise<ApiResult>
550
+ this.putBatch(url: string, tip: string, idList?: string[]): Promise<ApiResult>
551
+ this.deleteBatch(url: string, tip: string, idList?: string[]): Promise<ApiResult>
552
+
553
+ // 示例
554
+ this.postBatch("/api/order/batchApprove", "确定审批选中数据?");
555
+ ```
556
+
557
+ ---
558
+
559
+ ### 📝 实战示例
560
+
561
+ #### 示例1:单行操作按钮
562
+
563
+ ```typescript
564
+ // data.ts
565
+ columnsDef(): TableColumnDesc[] {
566
+ return [
567
+ // ... 其他列
568
+ {
569
+ label: "操作",
570
+ width: 200,
571
+ fixed: "right",
572
+ operations: [
573
+ {
574
+ name: "approve",
575
+ label: "审批",
576
+ onClick: (row: any) =>
577
+ this.confirm("确定审批该记录吗?", "提示").then(() => {
578
+ this.postAction(API_CONFIG.approve, { id: row.id, status: "approved" })
579
+ .then(res => {
580
+ this.msgSuccess(res.message);
581
+ this.select(); // 刷新列表
582
+ });
583
+ })
584
+ },
585
+ {
586
+ name: "delete",
587
+ label: "删除",
588
+ onClick: (row: any) =>
589
+ this.confirm("确定删除吗?", "警告").then(() => {
590
+ // ✅ 注意:deleteAction 的 ids 放在第三个参数(data)
591
+ this.deleteAction(API_CONFIG.remove, {}, { ids: [row.id] })
592
+ .then(res => {
593
+ this.msgSuccess(res.message);
594
+ this.select();
595
+ });
596
+ })
597
+ }
598
+ ]
599
+ }
600
+ ];
601
+ }
602
+ ```
603
+
604
+ #### 示例2:工具栏批量操作
605
+
606
+ ```typescript
607
+ // data.ts
608
+ toolbarDef(): ActionButtonDesc[] {
609
+ return [
610
+ {
611
+ label: "批量审批",
612
+ type: "primary",
613
+ icon: "Check",
614
+ onClick: () => {
615
+ const ids = this.getSelection().map(i => i.id);
616
+ if (!ids.length) {
617
+ this.msgWarning("请选择数据");
618
+ return;
619
+ }
620
+
621
+ // ✅ 方式1:使用 actionBatch(推荐)
622
+ this.actionBatch(
623
+ this.postAction,
624
+ API_CONFIG.batchApprove,
625
+ "确定审批选中数据吗?",
626
+ ids
627
+ );
628
+ }
629
+ },
630
+ {
631
+ label: "批量删除",
632
+ type: "danger",
633
+ icon: "Delete",
634
+ onClick: () => {
635
+ const ids = this.getSelection().map(i => i.id);
636
+ if (!ids.length) {
637
+ this.msgWarning("请选择数据");
638
+ return;
639
+ }
640
+
641
+ // ✅ 方式2:手动调用(DELETE需要这样)
642
+ this.confirm("确定删除选中数据吗?", "警告").then(() => {
643
+ this.deleteAction(API_CONFIG.remove, {}, { ids }).then(res => {
644
+ this.msgSuccess(res.message);
645
+ this.select();
646
+ });
647
+ });
648
+ }
649
+ },
650
+ {
651
+ label: "批量发布",
652
+ type: "success",
653
+ icon: "Upload",
654
+ onClick: () => {
655
+ // ✅ 方式3:使用快捷方法 postBatch
656
+ this.postBatch(API_CONFIG.batchPublish, "确定发布选中数据吗?");
657
+ }
658
+ }
659
+ ];
660
+ }
661
+ ```
662
+
663
+ #### 示例3:获取详情/下拉选项
664
+
665
+ ```typescript
666
+ // data.ts
667
+ async loadOptions() {
668
+ // 获取字典选项
669
+ const res = await this.getAction("/system/dictDtl/getListByDicSn", { strSn: "ORDER_STATUS" });
670
+ this.statusOptions = res.data;
671
+ }
672
+
673
+ async viewDetail(id: string) {
674
+ // 查看详情
675
+ const res = await this.getAction(API_CONFIG.getById, { id });
676
+ this.detailData.value = res.data;
677
+ }
678
+ ```
679
+
680
+ #### 示例4:复杂参数场景
681
+
682
+ ```typescript
683
+ // 带复杂参数的批量操作
684
+ onClick: () => {
685
+ const rows = this.getSelection();
686
+ if (!rows.length) {
687
+ this.msgWarning("请选择数据");
688
+ return;
689
+ }
690
+
691
+ this.confirm("确定提交选中数据吗?", "提示").then(() => {
692
+ // 自定义参数结构
693
+ this.postAction(API_CONFIG.batchSubmit, {
694
+ ids: rows.map(r => r.id),
695
+ submitTime: new Date().toISOString(),
696
+ operator: "admin",
697
+ remark: "批量提交"
698
+ }).then(res => {
699
+ this.msgSuccess(res.message);
700
+ this.select();
701
+ });
702
+ });
703
+ }
704
+ ```
705
+
706
+ ---
707
+
708
+ ### ⚠️ 常见错误与注意事项
709
+
710
+ #### 错误1:deleteAction 参数位置错误
711
+
712
+ ```typescript
713
+ // ❌ 错误:ids被当作params(query参数)
714
+ this.deleteAction(API_CONFIG.remove, { ids: [row.id] });
715
+ // 实际请求:DELETE /api/remove?ids=xxx (Mock/后端期望body时会失败)
716
+
717
+ // ✅ 正确:ids放在第三个参数(data/body)
718
+ this.deleteAction(API_CONFIG.remove, {}, { ids: [row.id] });
719
+ // 实际请求:DELETE /api/remove Body: { ids: ["xxx"] }
720
+ ```
721
+
722
+ #### 错误2:actionBatch 用于 deleteAction
723
+
724
+ ```typescript
725
+ // ❌ 错误:actionBatch会把ids传给deleteAction的第二个参数(params)
726
+ this.actionBatch(this.deleteAction, API_CONFIG.remove, "确定删除?", ids);
727
+ // 等价于:this.deleteAction(url, ids) ← ids变成了params
728
+
729
+ // ✅ 正确:手动调用
730
+ this.confirm("确定删除?", "警告").then(() => {
731
+ this.deleteAction(API_CONFIG.remove, {}, { ids }).then(res => {
732
+ this.msgSuccess(res.message);
733
+ this.select();
734
+ });
735
+ });
736
+ ```
737
+
738
+ #### 错误3:忘记刷新列表
739
+
740
+ ```typescript
741
+ // ❌ 错误:操作成功后没有刷新
742
+ onClick: (row) =>
743
+ this.postAction(API_CONFIG.approve, { id: row.id })
744
+ .then(res => this.msgSuccess(res.message));
745
+
746
+ // ✅ 正确:调用 this.select() 刷新
747
+ onClick: (row) =>
748
+ this.postAction(API_CONFIG.approve, { id: row.id })
749
+ .then(res => {
750
+ this.msgSuccess(res.message);
751
+ this.select(); // ← 刷新列表
752
+ });
753
+ ```
754
+
755
+ #### 错误4:没有选中数据检查
756
+
757
+ ```typescript
758
+ // ❌ 错误:没有检查是否选中数据
759
+ onClick: () => {
760
+ const ids = this.getSelection().map(i => i.id);
761
+ this.postBatch(API_CONFIG.batchApprove, "确定审批?", ids);
762
+ }
763
+
764
+ // ✅ 正确:先检查
765
+ onClick: () => {
766
+ const ids = this.getSelection().map(i => i.id);
767
+ if (!ids.length) {
768
+ this.msgWarning("请选择数据");
769
+ return;
770
+ }
771
+ this.postBatch(API_CONFIG.batchApprove, "确定审批?", ids);
772
+ }
773
+ ```
774
+
775
+ ---
776
+
777
+ ### 🎯 最佳实践
778
+
779
+ #### 1. 统一使用 API_CONFIG 管理路径
780
+
781
+ ```typescript
782
+ // data.ts
783
+ export const API_CONFIG = {
784
+ list: "/api/order/list",
785
+ remove: "/api/order/remove",
786
+ approve: "/api/order/approve",
787
+ batchSubmit: "/api/order/batchSubmit"
788
+ } as const;
789
+
790
+ // 使用时引用配置
791
+ this.postAction(API_CONFIG.approve, data);
792
+ ```
793
+
794
+ #### 2. 单行操作用 confirm + action
795
+
796
+ ```typescript
797
+ // 单行删除、审批等
798
+ onClick: (row) =>
799
+ this.confirm("确定操作吗?", "提示").then(() => {
800
+ this.postAction(API_CONFIG.xxx, { id: row.id })
801
+ .then(res => {
802
+ this.msgSuccess(res.message);
803
+ this.select();
804
+ });
805
+ })
806
+ ```
807
+
808
+ #### 3. 批量操作用 actionBatch 或 xxxBatch
809
+
810
+ ```typescript
811
+ // 批量POST/PUT:使用 actionBatch
812
+ this.actionBatch(this.postAction, API_CONFIG.batchApprove, "确定审批?", ids);
813
+
814
+ // 批量DELETE:手动调用
815
+ this.confirm("确定删除?", "警告").then(() => {
816
+ this.deleteAction(API_CONFIG.remove, {}, { ids }).then(/* ... */);
817
+ });
818
+
819
+ // 或使用快捷方法
820
+ this.postBatch(API_CONFIG.batchApprove, "确定审批?");
821
+ ```
822
+
823
+ #### 4. 复杂场景可封装方法
824
+
825
+ ```typescript
826
+ // data.ts
827
+ export function createPage() {
828
+ return new class extends AbstractPageQueryHook {
829
+ // 封装复杂的批量操作
830
+ async batchApproveWithRemark() {
831
+ const rows = this.getSelection();
832
+ if (!rows.length) {
833
+ this.msgWarning("请选择数据");
834
+ return;
835
+ }
836
+
837
+ // 弹出输入框获取备注
838
+ const remark = await this.prompt("请输入审批意见", "审批");
839
+ if (!remark) return;
840
+
841
+ return this.postAction(API_CONFIG.batchApprove, {
842
+ ids: rows.map(r => r.id),
843
+ remark,
844
+ approveTime: new Date()
845
+ }).then(res => {
846
+ this.msgSuccess(res.message);
847
+ this.select();
848
+ });
849
+ }
850
+ }
851
+ }
852
+ ```
853
+
854
+ ---
855
+
856
+ ### 📚 何时需要独立 API 文件?
857
+
858
+ 虽然基类方法已覆盖大部分场景,但以下情况仍建议创建 `api/*.ts`:
859
+
860
+ 1. **复杂参数转换**
861
+ ```typescript
862
+ // 需要复杂的前置数据处理
863
+ export function submitOrder(form: OrderForm) {
864
+ const params = {
865
+ ...form,
866
+ items: form.items.map(transformItem), // 复杂转换
867
+ attachments: await uploadFiles(form.files) // 异步前置
868
+ };
869
+ return request({ url: "/api/order/submit", method: "post", data: params });
870
+ }
871
+ ```
872
+
873
+ 2. **特殊 Header 或 Content-Type**
874
+ ```typescript
875
+ // 文件上传、multipart等
876
+ export function uploadFile(file: File) {
877
+ const formData = new FormData();
878
+ formData.append("file", file);
879
+ return request({
880
+ url: "/api/file/upload",
881
+ method: "post",
882
+ data: formData,
883
+ headers: { "Content-Type": "multipart/form-data" }
884
+ });
885
+ }
886
+ ```
887
+
888
+ 3. **多个接口组合调用**
889
+ ```typescript
890
+ // 需要先后调用多个接口
891
+ export async function publishOrder(id: string) {
892
+ await request({ url: `/api/order/validate/${id}`, method: "get" });
893
+ await request({ url: `/api/order/lock/${id}`, method: "post" });
894
+ return request({ url: `/api/order/publish/${id}`, method: "post" });
895
+ }
896
+ ```
897
+
898
+ 4. **跨模块复用**
899
+ ```typescript
900
+ // 多个页面都要调用的通用接口
901
+ export function getUserInfo() {
902
+ return request({ url: "/api/user/info", method: "get" });
903
+ }
904
+ ```
905
+
906
+ 对于**简单的 CRUD + 按钮操作**,直接在 `data.ts` 中使用基类方法即可,**无需创建 API 文件**。
907
+
908
+ ---
909
+
910
+ ## 总结
911
+
912
+ - **统一配置**:所有请求使用相同的配置格式
913
+ - **自动解包**:无需关心 axios 的 response 结构
914
+ - **类型安全**:配合 TypeScript 获得完整类型提示
915
+ - **易于维护**:所有 HTTP 请求逻辑统一管理
916
+ - **项目规范**:与团队其他成员保持一致的代码风格
917
+ - **基类优先**:继承 `AbstractPageQueryHook` 时优先使用内置方法,减少 API 层文件
918
+
919
+ ---
920
+
921
+ **相关文档**:
922
+ - [AbstractPageQueryHook 页面开发最佳实践](./page-query-hook-best-practices.md)
923
+
924
+ ---
925
+