@moluoxixi/ajax-package 0.0.31 → 0.0.33

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/README.md ADDED
@@ -0,0 +1,1016 @@
1
+ # AjaxPackage
2
+
3
+ 统一的 HTTP 服务封装与约定。提供 `getHttpService` 工厂函数、`BaseApi` 类以及 Vue 插件等多种使用方式,内置超时、token、响应字段映射、错误处理等能力。
4
+
5
+ ## 导出
6
+
7
+ - 函数:`getHttpService(options: HttpServiceOptions): HttpService`
8
+ - 创建 HTTP 服务实例,提供快捷方法
9
+ - 函数:`createHttpService(options: HttpServiceOptions): HttpService`
10
+ - 与 `getHttpService` 功能相同
11
+ - 类:`BaseApi`
12
+ - 基于 axios 封装的类式 API 请求工具
13
+ - 插件:`VueAxiosPlugin`
14
+ - Vue Axios 插件,提供全局的 `$http` 方法
15
+
16
+ ## 快速开始
17
+
18
+ ### 使用 getHttpService
19
+
20
+ ```ts
21
+ import { getHttpService } from '@moluoxixi/ajaxpackage'
22
+
23
+ const httpApi = getHttpService({
24
+ baseURL: 'https://api.example.com',
25
+ timeout: 5000,
26
+ getToken: () => localStorage.getItem('token'),
27
+ responseFields: {
28
+ code: 'Code',
29
+ message: 'Message',
30
+ data: 'data',
31
+ },
32
+ })
33
+
34
+ export function getUserList(params: any) {
35
+ return httpApi.get('/users', params)
36
+ }
37
+
38
+ export function createUser(data: any) {
39
+ return httpApi.post('/users', data)
40
+ }
41
+ ```
42
+
43
+ ### 使用 BaseApi 类
44
+
45
+ ```ts
46
+ import { BaseApi } from '@moluoxixi/ajaxpackage'
47
+
48
+ const api = new BaseApi({
49
+ baseURL: 'https://api.example.com',
50
+ timeout: 5000,
51
+ responseFields: {
52
+ code: 'code',
53
+ message: 'message',
54
+ data: 'data',
55
+ },
56
+ })
57
+
58
+ export async function getUserList(params: any) {
59
+ return api.get('/users', params)
60
+ }
61
+
62
+ export async function createUser(data: any) {
63
+ return api.post('/users', data)
64
+ }
65
+ ```
66
+
67
+ ### 使用 Vue 插件
68
+
69
+ ```ts
70
+ import { createApp } from 'vue'
71
+ import VueAxiosPlugin from '@moluoxixi/ajaxpackage'
72
+
73
+ const app = createApp(App)
74
+
75
+ app.use(VueAxiosPlugin, {
76
+ default: {
77
+ baseURL: 'https://api.example.com',
78
+ timeout: 5000,
79
+ getToken: () => localStorage.getItem('token'),
80
+ },
81
+ })
82
+
83
+ // 在组件中使用
84
+ export default {
85
+ async mounted() {
86
+ const data = await this.$http.get('/users')
87
+ },
88
+ }
89
+ ```
90
+
91
+ ## getHttpService 配置项
92
+
93
+ ### HttpServiceOptions
94
+
95
+ | 选项 | 说明 | 类型 | 必填 | 默认值 |
96
+ | --- | --- | --- | --- | --- |
97
+ | `baseURL` | 服务地址 | `string` | 是 | `''` |
98
+ | `timeout` | 超时时间(毫秒) | `number` | 否 | `5000` |
99
+ | `getToken` | 获取 token 的函数 | ^[Function]`() => string \| null` | 否 | `() => null` |
100
+ | `onLoginRequired` | 登录失效回调函数 | ^[Function]`() => void` | 否 | `() => { window.location.href = '/login?redirect=' + encodeURIComponent(window.location.href) }` |
101
+ | `responseFields` | 响应字段映射 | ^[Object]`{ code?: string; message?: string; data?: string; errors?: string; tips?: string }` | 否 | `{ code: 'code', message: 'msg', data: 'data' }` |
102
+
103
+ ### responseFields 字段说明
104
+
105
+ | 字段 | 说明 | 类型 | 默认值 |
106
+ | --- | --- | --- | --- |
107
+ | `code` | 状态码字段名,支持路径解析(如 `'result.code'`) | `string` | `'code'` |
108
+ | `message` | 消息字段名,支持路径解析 | `string` | `'msg'` |
109
+ | `data` | 数据字段名,支持路径解析 | `string` | `'data'` |
110
+ | `errors` | 错误数组字段名 | `string` | `undefined` |
111
+ | `tips` | 提示信息字段名 | `string` | `undefined` |
112
+
113
+ ### 使用示例:基础配置
114
+
115
+ ```ts
116
+ import { getHttpService } from '@moluoxixi/ajaxpackage'
117
+
118
+ const httpApi = getHttpService({
119
+ baseURL: 'https://api.example.com',
120
+ timeout: 5000,
121
+ getToken: () => {
122
+ return localStorage.getItem('token') || ''
123
+ },
124
+ responseFields: {
125
+ code: 'Code',
126
+ message: 'Message',
127
+ data: 'data',
128
+ },
129
+ })
130
+ ```
131
+
132
+ ### 使用示例:自定义响应字段映射
133
+
134
+ ```ts
135
+ import { getHttpService } from '@moluoxixi/ajaxpackage'
136
+
137
+ const httpApi = getHttpService({
138
+ baseURL: 'https://api.example.com',
139
+ timeout: 5000,
140
+ responseFields: {
141
+ code: 'status', // 自定义状态码字段
142
+ message: 'msg', // 自定义消息字段
143
+ data: 'result', // 自定义数据字段
144
+ errors: 'errorList', // 自定义错误数组字段
145
+ tips: 'tipList', // 自定义提示信息字段
146
+ },
147
+ })
148
+ ```
149
+
150
+ ### 使用示例:路径解析
151
+
152
+ ```ts
153
+ import { getHttpService } from '@moluoxixi/ajaxpackage'
154
+
155
+ const httpApi = getHttpService({
156
+ baseURL: 'https://api.example.com',
157
+ responseFields: {
158
+ code: 'result.code', // 支持嵌套路径解析
159
+ message: 'result.message',
160
+ data: 'result.data',
161
+ },
162
+ })
163
+ ```
164
+
165
+ ### 使用示例:登录失效处理
166
+
167
+ ```ts
168
+ import { getHttpService } from '@moluoxixi/ajaxpackage'
169
+
170
+ const httpApi = getHttpService({
171
+ baseURL: 'https://api.example.com',
172
+ getToken: () => localStorage.getItem('token'),
173
+ onLoginRequired: () => {
174
+ // 清除本地 token
175
+ localStorage.removeItem('token')
176
+ // 跳转到登录页
177
+ window.location.href = '/login?redirect=' + encodeURIComponent(window.location.href)
178
+ },
179
+ })
180
+ ```
181
+
182
+ ## HttpService 实例方法
183
+
184
+ ### get(url, params, config)
185
+
186
+ 发送 GET 请求。
187
+
188
+ **参数:**
189
+ - `url: string` - 请求 URL
190
+ - `params?: object` - 查询参数
191
+ - `config?: AxiosRequestConfig` - axios 配置
192
+
193
+ **返回:** `Promise<any>`
194
+
195
+ **示例:**
196
+
197
+ ```ts
198
+ import { getHttpService } from '@moluoxixi/ajaxpackage'
199
+
200
+ const httpApi = getHttpService({
201
+ baseURL: 'https://api.example.com',
202
+ })
203
+
204
+ async function getUserList() {
205
+ const data = await httpApi.get('/users', { page: 1, size: 10 })
206
+ return data
207
+ }
208
+
209
+ async function getUserById(id: string) {
210
+ const data = await httpApi.get(`/users/${id}`)
211
+ return data
212
+ }
213
+
214
+ async function searchUsers(keyword: string) {
215
+ const data = await httpApi.get('/users/search', { keyword }, {
216
+ headers: {
217
+ 'X-Custom-Header': 'custom-value',
218
+ },
219
+ })
220
+ return data
221
+ }
222
+ ```
223
+
224
+ ### post(url, data, config, addSign)
225
+
226
+ 发送 POST 请求。
227
+
228
+ **参数:**
229
+ - `url: string` - 请求 URL
230
+ - `data?: any` - 请求体数据
231
+ - `config?: AxiosRequestConfig` - axios 配置
232
+ - `addSign?: (config: AxiosRequestConfig) => void` - 签名函数,用于在请求前修改配置
233
+
234
+ **返回:** `Promise<any>`
235
+
236
+ **示例:**
237
+
238
+ ```ts
239
+ import { getHttpService } from '@moluoxixi/ajaxpackage'
240
+ import type { AxiosRequestConfig } from 'axios'
241
+
242
+ const httpApi = getHttpService({
243
+ baseURL: 'https://api.example.com',
244
+ })
245
+
246
+ async function createUser(userData: any) {
247
+ const data = await httpApi.post('/users', userData)
248
+ return data
249
+ }
250
+
251
+ async function updateUser(id: string, userData: any) {
252
+ const data = await httpApi.post(`/users/${id}`, userData, {
253
+ headers: {
254
+ 'Content-Type': 'application/json',
255
+ },
256
+ })
257
+ return data
258
+ }
259
+
260
+ async function createUserWithSign(userData: any) {
261
+ function addSign(config: AxiosRequestConfig) {
262
+ const timestamp = Date.now()
263
+ const sign = generateSign(userData, timestamp)
264
+ config.headers = config.headers || {}
265
+ config.headers['X-Timestamp'] = timestamp.toString()
266
+ config.headers['X-Sign'] = sign
267
+ }
268
+
269
+ const data = await httpApi.post('/users', userData, {}, addSign)
270
+ return data
271
+ }
272
+
273
+ function generateSign(data: any, timestamp: number): string {
274
+ // 签名生成逻辑
275
+ return 'signature'
276
+ }
277
+ ```
278
+
279
+ ### put(url, data, config)
280
+
281
+ 发送 PUT 请求。
282
+
283
+ **参数:**
284
+ - `url: string` - 请求 URL
285
+ - `data?: any` - 请求体数据
286
+ - `config?: AxiosRequestConfig` - axios 配置
287
+
288
+ **返回:** `Promise<any>`
289
+
290
+ **示例:**
291
+
292
+ ```ts
293
+ import { getHttpService } from '@moluoxixi/ajaxpackage'
294
+
295
+ const httpApi = getHttpService({
296
+ baseURL: 'https://api.example.com',
297
+ })
298
+
299
+ async function updateUser(id: string, userData: any) {
300
+ const data = await httpApi.put(`/users/${id}`, userData)
301
+ return data
302
+ }
303
+
304
+ async function updateUserPartial(id: string, partialData: any) {
305
+ const data = await httpApi.put(`/users/${id}`, partialData, {
306
+ headers: {
307
+ 'Content-Type': 'application/json-patch+json',
308
+ },
309
+ })
310
+ return data
311
+ }
312
+ ```
313
+
314
+ ### delete(url, params, config)
315
+
316
+ 发送 DELETE 请求。
317
+
318
+ **参数:**
319
+ - `url: string` - 请求 URL
320
+ - `params?: object` - 查询参数
321
+ - `config?: AxiosRequestConfig` - axios 配置
322
+
323
+ **返回:** `Promise<any>`
324
+
325
+ **示例:**
326
+
327
+ ```ts
328
+ import { getHttpService } from '@moluoxixi/ajaxpackage'
329
+
330
+ const httpApi = getHttpService({
331
+ baseURL: 'https://api.example.com',
332
+ })
333
+
334
+ async function deleteUser(id: string) {
335
+ const data = await httpApi.delete(`/users/${id}`)
336
+ return data
337
+ }
338
+
339
+ async function deleteUsers(ids: string[]) {
340
+ const data = await httpApi.delete('/users', { ids }, {
341
+ headers: {
342
+ 'X-Batch-Delete': 'true',
343
+ },
344
+ })
345
+ return data
346
+ }
347
+ ```
348
+
349
+ ### uploadFile(url, file, config)
350
+
351
+ 上传文件。
352
+
353
+ **参数:**
354
+ - `url: string` - 上传 URL
355
+ - `file: File` - 要上传的文件
356
+ - `config?: AxiosRequestConfig` - axios 配置
357
+
358
+ **返回:** `Promise<any>`
359
+
360
+ **示例:**
361
+
362
+ ```ts
363
+ import { getHttpService } from '@moluoxixi/ajaxpackage'
364
+
365
+ const httpApi = getHttpService({
366
+ baseURL: 'https://api.example.com',
367
+ })
368
+
369
+ async function uploadAvatar(file: File) {
370
+ const data = await httpApi.uploadFile('/upload/avatar', file)
371
+ return data
372
+ }
373
+
374
+ async function uploadFileWithProgress(file: File, onProgress: (progress: number) => void) {
375
+ const data = await httpApi.uploadFile('/upload/file', file, {
376
+ onUploadProgress: (progressEvent) => {
377
+ const progress = progressEvent.total
378
+ ? Math.round((progressEvent.loaded * 100) / progressEvent.total)
379
+ : 0
380
+ onProgress(progress)
381
+ },
382
+ })
383
+ return data
384
+ }
385
+ ```
386
+
387
+ ### all(requests)
388
+
389
+ 批量请求。
390
+
391
+ **参数:**
392
+ - `requests: Promise<any>[]` - 请求 Promise 数组
393
+
394
+ **返回:** `Promise<any[]>`
395
+
396
+ **示例:**
397
+
398
+ ```ts
399
+ import { getHttpService } from '@moluoxixi/ajaxpackage'
400
+
401
+ const httpApi = getHttpService({
402
+ baseURL: 'https://api.example.com',
403
+ })
404
+
405
+ async function loadDashboardData() {
406
+ const [users, posts, comments] = await httpApi.all([
407
+ httpApi.get('/users'),
408
+ httpApi.get('/posts'),
409
+ httpApi.get('/comments'),
410
+ ])
411
+ return { users, posts, comments }
412
+ }
413
+
414
+ async function loadUserData(userId: string) {
415
+ const [user, posts, followers] = await httpApi.all([
416
+ httpApi.get(`/users/${userId}`),
417
+ httpApi.get(`/users/${userId}/posts`),
418
+ httpApi.get(`/users/${userId}/followers`),
419
+ ])
420
+ return { user, posts, followers }
421
+ }
422
+ ```
423
+
424
+ ## BaseApi 类
425
+
426
+ ### 构造函数配置
427
+
428
+ | 选项 | 说明 | 类型 | 必填 | 默认值 |
429
+ | --- | --- | --- | --- | --- |
430
+ | `baseURL` | 服务地址 | `string` | 是 | - |
431
+ | `timeout` | 超时时间(毫秒) | `number` | 否 | `5000` |
432
+ | `responseFields` | 响应字段映射 | ^[Object]`{ code?: string; message?: string; data?: string; errors?: string; tips?: string }` | 否 | `{ code: 'code', message: 'message', data: 'data', errors: 'errors', tips: 'tips' }` |
433
+ | `onTimeout` | 超时回调函数 | ^[Function]`() => void` | 否 | `() => {}` |
434
+
435
+ ### BaseApi 实例方法
436
+
437
+ #### get(url, params, data, config)
438
+
439
+ 发送 GET 请求。
440
+
441
+ **示例:**
442
+
443
+ ```ts
444
+ import { BaseApi } from '@moluoxixi/ajaxpackage'
445
+
446
+ const api = new BaseApi({
447
+ baseURL: 'https://api.example.com',
448
+ })
449
+
450
+ async function getUserList() {
451
+ const data = await api.get('/users', { page: 1, size: 10 })
452
+ return data
453
+ }
454
+ ```
455
+
456
+ #### post(url, data, params, config)
457
+
458
+ 发送 POST 请求。
459
+
460
+ **示例:**
461
+
462
+ ```ts
463
+ import { BaseApi } from '@moluoxixi/ajaxpackage'
464
+
465
+ const api = new BaseApi({
466
+ baseURL: 'https://api.example.com',
467
+ })
468
+
469
+ async function createUser(userData: any) {
470
+ const data = await api.post('/users', userData)
471
+ return data
472
+ }
473
+ ```
474
+
475
+ #### put(url, data, params, config)
476
+
477
+ 发送 PUT 请求。
478
+
479
+ **示例:**
480
+
481
+ ```ts
482
+ import { BaseApi } from '@moluoxixi/ajaxpackage'
483
+
484
+ const api = new BaseApi({
485
+ baseURL: 'https://api.example.com',
486
+ })
487
+
488
+ async function updateUser(id: string, userData: any) {
489
+ const data = await api.put(`/users/${id}`, userData)
490
+ return data
491
+ }
492
+ ```
493
+
494
+ #### delete(url, params, data, config)
495
+
496
+ 发送 DELETE 请求。
497
+
498
+ **示例:**
499
+
500
+ ```ts
501
+ import { BaseApi } from '@moluoxixi/ajaxpackage'
502
+
503
+ const api = new BaseApi({
504
+ baseURL: 'https://api.example.com',
505
+ })
506
+
507
+ async function deleteUser(id: string) {
508
+ const data = await api.delete(`/users/${id}`)
509
+ return data
510
+ }
511
+ ```
512
+
513
+ #### upload(url, formData, config)
514
+
515
+ 上传文件,自动携带 `multipart/form-data` 头信息。
516
+
517
+ **示例:**
518
+
519
+ ```ts
520
+ import { BaseApi } from '@moluoxixi/ajaxpackage'
521
+
522
+ const api = new BaseApi({
523
+ baseURL: 'https://api.example.com',
524
+ })
525
+
526
+ async function uploadFile(file: File) {
527
+ const formData = new FormData()
528
+ formData.append('file', file)
529
+ const data = await api.upload('/upload', formData, {
530
+ onUploadProgress: (event) => {
531
+ const percent = event.total ? Math.round(event.loaded / event.total * 100) : 0
532
+ console.log(`上传进度:${percent}%`)
533
+ },
534
+ })
535
+ return data
536
+ }
537
+ ```
538
+
539
+ #### all(requests)
540
+
541
+ 批量请求。`requests` 既可以是 `AxiosRequestConfig[]`,也可以是已经发起的请求 `Promise[]`。
542
+
543
+ **示例:**
544
+
545
+ ```ts
546
+ import { BaseApi } from '@moluoxixi/ajaxpackage'
547
+ import type { AxiosRequestConfig } from 'axios'
548
+
549
+ const api = new BaseApi({
550
+ baseURL: 'https://api.example.com',
551
+ })
552
+
553
+ async function loadDashboardData() {
554
+ const requests: AxiosRequestConfig[] = [
555
+ { url: '/users', method: 'get' },
556
+ { url: '/posts', method: 'get' },
557
+ { url: '/comments', method: 'get' },
558
+ ]
559
+ const [users, posts, comments] = await api.all(requests)
560
+ return { users, posts, comments }
561
+ }
562
+
563
+ // 也可以传入已经发起的请求
564
+ async function loadDashboardDataByPromises() {
565
+ const api = new BaseApi({ baseURL: 'https://api.example.com' })
566
+ const [users, posts, comments] = await api.all([
567
+ api.get('/users'),
568
+ api.get('/posts'),
569
+ api.get('/comments'),
570
+ ])
571
+ return { users, posts, comments }
572
+ }
573
+ ```
574
+
575
+ ### 继承扩展 BaseApi
576
+
577
+ 可以通过继承 `BaseApi` 创建自定义的 API 类,重写 `processRequestConfig` 和 `processResponseError` 方法。
578
+
579
+ **示例:**
580
+
581
+ ```ts
582
+ import { BaseApi } from '@moluoxixi/ajaxpackage'
583
+ import type { InternalAxiosRequestConfig, AxiosError } from 'axios'
584
+
585
+ class UserApi extends BaseApi {
586
+ constructor() {
587
+ super({
588
+ baseURL: 'https://api.example.com/users',
589
+ timeout: 5000,
590
+ })
591
+ }
592
+
593
+ processRequestConfig(config: InternalAxiosRequestConfig) {
594
+ // 自定义请求拦截器处理
595
+ config.headers = config.headers || {}
596
+ config.headers['X-Custom-Header'] = 'custom-value'
597
+ return config
598
+ }
599
+
600
+ async processResponseError(error: AxiosError): Promise<AxiosError> {
601
+ // 自定义错误处理
602
+ if (error.response?.status === 404) {
603
+ console.error('资源未找到')
604
+ }
605
+ return error
606
+ }
607
+
608
+ async getUserById(id: string) {
609
+ return this.get(`/${id}`)
610
+ }
611
+
612
+ async createUser(userData: any) {
613
+ return this.post('/', userData)
614
+ }
615
+ }
616
+
617
+ const userApi = new UserApi()
618
+ export default userApi
619
+ ```
620
+
621
+ ## VueAxiosPlugin 插件
622
+
623
+ ### 安装插件
624
+
625
+ ```ts
626
+ import { createApp } from 'vue'
627
+ import VueAxiosPlugin from '@moluoxixi/ajaxpackage'
628
+
629
+ const app = createApp(App)
630
+
631
+ app.use(VueAxiosPlugin, {
632
+ default: {
633
+ baseURL: 'https://api.example.com',
634
+ timeout: 5000,
635
+ getToken: () => localStorage.getItem('token'),
636
+ },
637
+ })
638
+ ```
639
+
640
+ ### 插件配置选项
641
+
642
+ | 选项 | 说明 | 类型 | 必填 |
643
+ | --- | --- | --- | --- |
644
+ | `default` | 默认实例配置 | `HttpServiceOptions` | 否 |
645
+ | `instances` | 其他实例配置 | ^[Object]`Record<string, HttpServiceOptions>` | 否 |
646
+ | `globalMixin` | 是否启用全局 mixin | `boolean` | 否,默认 `true` |
647
+
648
+ ### 使用示例:单实例
649
+
650
+ ```ts
651
+ import { createApp } from 'vue'
652
+ import VueAxiosPlugin from '@moluoxixi/ajaxpackage'
653
+
654
+ const app = createApp(App)
655
+
656
+ app.use(VueAxiosPlugin, {
657
+ default: {
658
+ baseURL: 'https://api.example.com',
659
+ timeout: 5000,
660
+ getToken: () => localStorage.getItem('token'),
661
+ },
662
+ })
663
+
664
+ // 在组件中使用
665
+ export default {
666
+ async mounted() {
667
+ const users = await this.$http.get('/users')
668
+ console.log(users)
669
+ },
670
+ }
671
+ ```
672
+
673
+ ### 使用示例:多实例
674
+
675
+ ```ts
676
+ import { createApp } from 'vue'
677
+ import VueAxiosPlugin from '@moluoxixi/ajaxpackage'
678
+
679
+ const app = createApp(App)
680
+
681
+ app.use(VueAxiosPlugin, {
682
+ default: {
683
+ baseURL: 'https://api.example.com',
684
+ },
685
+ instances: {
686
+ admin: {
687
+ baseURL: 'https://admin-api.example.com',
688
+ getToken: () => localStorage.getItem('adminToken'),
689
+ },
690
+ public: {
691
+ baseURL: 'https://public-api.example.com',
692
+ },
693
+ },
694
+ })
695
+
696
+ // 在组件中使用
697
+ export default {
698
+ async mounted() {
699
+ // 使用默认实例
700
+ const users = await this.$http.get('/users')
701
+
702
+ // 使用 admin 实例
703
+ const adminData = await this.$httpAdmin.get('/admin/users')
704
+
705
+ // 使用 public 实例
706
+ const publicData = await this.$httpPublic.get('/public/news')
707
+ },
708
+ }
709
+ ```
710
+
711
+ ### 使用示例:Composition API
712
+
713
+ ```ts
714
+ import { inject } from 'vue'
715
+
716
+ export default {
717
+ setup() {
718
+ const $http = inject('$http')
719
+
720
+ async function loadData() {
721
+ const data = await $http.get('/users')
722
+ return data
723
+ }
724
+
725
+ return {
726
+ loadData,
727
+ }
728
+ },
729
+ }
730
+ ```
731
+
732
+ ## 错误处理
733
+
734
+ ### 自动错误处理
735
+
736
+ AjaxPackage 会自动处理以下错误:
737
+
738
+ 1. **401 错误(登录失效)**
739
+ - 自动调用 `onLoginRequired` 回调
740
+ - 显示错误提示
741
+
742
+ 2. **超时错误**
743
+ - 显示超时提示信息
744
+
745
+ 3. **网络错误**
746
+ - 显示网络错误提示
747
+
748
+ 4. **响应错误码**
749
+ - 根据 `responseFields.code` 判断错误
750
+ - 显示错误消息
751
+
752
+ 5. **错误数组(errors)**
753
+ - 如果响应中包含错误数组,会以通知形式显示所有错误
754
+
755
+ 6. **提示信息(tips)**
756
+ - 如果响应中包含提示信息,会以警告通知形式显示
757
+
758
+ ### 自定义错误处理
759
+
760
+ 如果需要对响应结构进行更细粒度的处理,可以继承 `BaseApi` 并重写 `processResponseConfig` 或 `processResponseError`。在这些方法中根据业务规则解析数据、抛出错误或执行额外的副作用。
761
+
762
+ ## SSR 支持
763
+
764
+ AjaxPackage 支持 SSR(服务端渲染)环境。当 `document` 不存在时,会自动使用 `console` 输出消息和通知,而不是使用 Element Plus 的组件。
765
+
766
+ ## 注意事项
767
+
768
+ 1. **响应字段映射**
769
+ - 支持路径解析,如 `'result.code'` 可以访问嵌套字段
770
+ - 如果路径解析失败,会尝试使用默认字段名
771
+
772
+ 2. **Token 处理**
773
+ - Token 会自动添加到请求头的 `Token` 字段
774
+ - 如果 `getToken` 返回 `null` 或空字符串,不会添加 Token
775
+
776
+ 3. **错误处理**
777
+ - 401 错误会自动触发登录失效回调
778
+ - 其他错误会显示错误提示,但不会自动处理
779
+
780
+ 4. **批量请求**
781
+ - `all` 方法使用 `Promise.all`,如果任何一个请求失败,整个批量请求会失败
782
+ - 建议在业务代码中处理错误情况
783
+
784
+ 5. **请求取消**
785
+ - 当前版本未提供请求取消封装,若业务需要可直接使用 axios 自带的取消能力自行实现
786
+
787
+ ## SystemErrorDialog 组件
788
+
789
+ ### 概述
790
+
791
+ `SystemErrorDialog` 是一个用于显示系统异常信息的对话框组件,专门用于展示请求错误时的详细信息,包括用户信息、科室信息、网络信息和错误详情。
792
+
793
+ ### 组件特性
794
+
795
+ - 🎯 **轻量级设计**:只接收必要的字符串参数,避免大对象响应式开销
796
+ - 📱 **响应式布局**:支持移动端和桌面端的良好显示效果
797
+ - 🔧 **高度可配置**:支持自定义标题、宽度和各种信息字段
798
+ - 🎨 **美观界面**:采用现代化的UI设计,信息展示清晰易读
799
+
800
+ ### Props 参数
801
+
802
+ | 参数 | 说明 | 类型 | 默认值 | 必填 |
803
+ |------|------|------|--------|------|
804
+ | `title` | 对话框标题 | `string` | `'系统异常信息'` | 否 |
805
+ | `width` | 对话框宽度 | `number \| string` | `520` | 否 |
806
+ | `userName` | 用户名 | `string` | - | 否 |
807
+ | `userId` | 用户ID | `string` | - | 否 |
808
+ | `deptName` | 科室名称 | `string` | - | 否 |
809
+ | `deptId` | 科室ID | `string` | - | 否 |
810
+ | `clientIp` | 客户端IP地址 | `string` | - | 否 |
811
+ | `requestUrl` | 请求URL路径 | `string` | - | 否 |
812
+ | `traceId` | 链路追踪ID | `string` | - | 否 |
813
+ | `errorMessage` | 错误消息 | `string` | - | 否 |
814
+ | `errorCode` | 错误代码 | `string` | - | 否 |
815
+
816
+ ### Events 事件
817
+
818
+ | 事件名 | 说明 | 参数 |
819
+ |--------|------|------|
820
+ | `close` | 关闭对话框时触发 | - |
821
+ | `confirm` | 点击确认按钮时触发 | - |
822
+
823
+ ### 使用示例
824
+
825
+ #### 基础用法
826
+
827
+ ```vue
828
+ <template>
829
+ <div>
830
+ <ElButton @click="showErrorDialog">显示异常信息</ElButton>
831
+
832
+ <SystemErrorDialog
833
+ v-model="dialogVisible"
834
+ :user-name="errorInfo.userName"
835
+ :user-id="errorInfo.userId"
836
+ :dept-name="errorInfo.deptName"
837
+ :dept-id="errorInfo.deptId"
838
+ :client-ip="errorInfo.clientIp"
839
+ :request-url="errorInfo.requestUrl"
840
+ :trace-id="errorInfo.traceId"
841
+ @close="dialogVisible = false"
842
+ @confirm="handleConfirm"
843
+ />
844
+ </div>
845
+ </template>
846
+
847
+ <script setup>
848
+ import { ref } from 'vue'
849
+ import { ElButton } from 'element-plus'
850
+ import SystemErrorDialog from '@moluoxixi/ajaxpackage/SystemErrorDialog.vue'
851
+
852
+ const dialogVisible = ref(false)
853
+
854
+ const errorInfo = {
855
+ userName: '张三',
856
+ userId: '12345',
857
+ deptName: '信息科',
858
+ deptId: 'IT001',
859
+ clientIp: '192.168.1.100',
860
+ requestUrl: '/api/user/login',
861
+ traceId: 'trace-123456789',
862
+ }
863
+
864
+ function showErrorDialog() {
865
+ dialogVisible.value = true
866
+ }
867
+
868
+ function handleConfirm() {
869
+ console.log('用户确认了异常信息')
870
+ dialogVisible.value = false
871
+ }
872
+ </script>
873
+ ```
874
+
875
+ #### 完整配置
876
+
877
+ ```vue
878
+ <template>
879
+ <SystemErrorDialog
880
+ v-model="dialogVisible"
881
+ title="详细系统异常信息"
882
+ :width="600"
883
+ :user-name="fullErrorInfo.userName"
884
+ :user-id="fullErrorInfo.userId"
885
+ :dept-name="fullErrorInfo.deptName"
886
+ :dept-id="fullErrorInfo.deptId"
887
+ :client-ip="fullErrorInfo.clientIp"
888
+ :request-url="fullErrorInfo.requestUrl"
889
+ :trace-id="fullErrorInfo.traceId"
890
+ :error-code="fullErrorInfo.errorCode"
891
+ :error-message="fullErrorInfo.errorMessage"
892
+ @close="dialogVisible = false"
893
+ @confirm="handleConfirm"
894
+ />
895
+ </template>
896
+
897
+ <script setup>
898
+ import { ref } from 'vue'
899
+ import SystemErrorDialog from '@moluoxixi/ajaxpackage/SystemErrorDialog.vue'
900
+
901
+ const dialogVisible = ref(false)
902
+
903
+ const fullErrorInfo = {
904
+ userName: '李四',
905
+ userId: '67890',
906
+ deptName: '财务科',
907
+ deptId: 'FIN001',
908
+ clientIp: '192.168.1.200',
909
+ requestUrl: '/api/finance/report',
910
+ traceId: 'trace-987654321',
911
+ errorCode: 'ERR_500',
912
+ errorMessage: '服务器内部错误,请联系系统管理员',
913
+ }
914
+
915
+ function handleConfirm() {
916
+ // 处理确认逻辑
917
+ dialogVisible.value = false
918
+ }
919
+ </script>
920
+ ```
921
+
922
+ #### 从响应对象提取信息
923
+
924
+ ```typescript
925
+ import type { AxiosResponse } from 'axios'
926
+ import SystemErrorDialog from '@moluoxixi/ajaxpackage/SystemErrorDialog.vue'
927
+
928
+ // 工具函数:从响应对象提取异常信息
929
+ function extractErrorInfo(response: AxiosResponse) {
930
+ const requestData = {
931
+ ...response.config?.params,
932
+ ...normalizePayload(response.config?.data),
933
+ }
934
+
935
+ return {
936
+ userName: requestData.userName || requestData.username,
937
+ userId: requestData.userId || requestData.userid,
938
+ deptName: requestData.deptName || requestData.departmentName,
939
+ deptId: requestData.deptId || requestData.departmentId,
940
+ clientIp: requestData.clientIp || requestData.ip,
941
+ requestUrl: response.config?.url,
942
+ traceId: resolveTraceId(response.headers),
943
+ errorCode: response.data?.code || response.status?.toString(),
944
+ errorMessage: response.data?.message || response.statusText,
945
+ }
946
+ }
947
+
948
+ // 在错误处理中使用
949
+ function handleApiError(error: any) {
950
+ if (error.response) {
951
+ const errorInfo = extractErrorInfo(error.response)
952
+ // 显示异常对话框
953
+ showSystemErrorDialog(errorInfo)
954
+ }
955
+ }
956
+ ```
957
+
958
+ ### 组件方法
959
+
960
+ 通过 `ref` 可以访问组件的方法:
961
+
962
+ ```vue
963
+ <template>
964
+ <SystemErrorDialog
965
+ ref="errorDialogRef"
966
+ v-model="dialogVisible"
967
+ :user-name="errorInfo.userName"
968
+ @close="dialogVisible = false"
969
+ />
970
+ </template>
971
+
972
+ <script setup>
973
+ import { ref } from 'vue'
974
+ import SystemErrorDialog from '@moluoxixi/ajaxpackage/SystemErrorDialog.vue'
975
+
976
+ const errorDialogRef = ref()
977
+
978
+ // 通过方法关闭对话框
979
+ function closeDialog() {
980
+ errorDialogRef.value?.close()
981
+ }
982
+ </script>
983
+ ```
984
+
985
+ ### 样式定制
986
+
987
+ 组件使用 scoped 样式,如需自定义样式,可以通过深度选择器:
988
+
989
+ ```vue
990
+ <style>
991
+ .custom-error-dialog :deep(.error-info-container) {
992
+ background-color: #f5f5f5;
993
+ }
994
+
995
+ .custom-error-dialog :deep(.error-code) {
996
+ color: #ff4757;
997
+ font-weight: bold;
998
+ }
999
+ </style>
1000
+ ```
1001
+
1002
+ ### 设计理念
1003
+
1004
+ #### 为什么不直接传递 response 对象?
1005
+
1006
+ 1. **性能考虑**:response 对象通常很大,包含大量不必要的信息,将其变为响应式会造成性能开销
1007
+ 2. **数据安全**:避免在组件中暴露完整的请求/响应信息
1008
+ 3. **接口清晰**:明确的 props 定义让组件的使用更加清晰和可控
1009
+ 4. **灵活性**:调用方可以自由决定传递哪些信息,支持多种数据来源
1010
+
1011
+ #### 信息展示逻辑
1012
+
1013
+ - **菜单名称**:自动获取当前页面的 URL(`location.href`)
1014
+ - **未知值处理**:对于未传递的信息显示"未知"
1015
+ - **错误信息**:错误代码和错误消息会以不同颜色突出显示
1016
+ - **响应式设计**:在移动端会自动调整布局为垂直排列