@moluoxixi/ajax-package 0.0.55 → 0.0.57

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
+ - **响应式设计**:在移动端会自动调整布局为垂直排列
@@ -23,11 +23,6 @@ export default class BaseHttpClient {
23
23
  protected errorDebounceTime: number;
24
24
  protected errorCacheTimer: NodeJS.Timeout | null;
25
25
  protected static readonly hasDocument: boolean;
26
- private static sharedContainerId;
27
- private static sharedPopoverContainerId;
28
- private static sharedMessageContainerId;
29
- private static sharedAppendTo?;
30
- private static sharedAppendToFallback;
31
26
  protected get hasDocument(): boolean;
32
27
  protected resolveAppendToTarget(): HTMLElement | null;
33
28
  protected getContainer(): HTMLElement | null;
package/es/index.mjs CHANGED
@@ -2,9 +2,9 @@
2
2
  "use strict";
3
3
  try {
4
4
  if (typeof document !== "undefined") {
5
- if (!document.getElementById("f8f93b76-32f3-462e-81ba-ac1c757d561e")) {
5
+ if (!document.getElementById("ecc878b1-9f7c-4e35-8686-808c53787a8a")) {
6
6
  var elementStyle = document.createElement("style");
7
- elementStyle.id = "f8f93b76-32f3-462e-81ba-ac1c757d561e";
7
+ elementStyle.id = "ecc878b1-9f7c-4e35-8686-808c53787a8a";
8
8
  elementStyle.appendChild(document.createTextNode("._root_11p33_1 .el-dialog__header {\n padding: 0 12px 12px;\n}\n\n._root_11p33_1 .el-dialog__body {\n border-top: 1px solid #e5e7eb;\n border-bottom: 1px solid #e5e7eb;\n padding: 0 12px;\n}\n\n._root_11p33_1 .el-dialog__footer {\n padding: 0 12px;\n}"));
9
9
  document.head.appendChild(elementStyle);
10
10
  }
@@ -7478,12 +7478,6 @@ const _BaseHttpClient = class _BaseHttpClient {
7478
7478
  this.timeout = timeout;
7479
7479
  this.appendTo = appendTo;
7480
7480
  this.appendToFallback = appendToFallback;
7481
- if (appendTo && !_BaseHttpClient.sharedAppendTo) {
7482
- _BaseHttpClient.sharedAppendTo = appendTo;
7483
- }
7484
- if (appendToFallback && !_BaseHttpClient.sharedAppendToFallback) {
7485
- _BaseHttpClient.sharedAppendToFallback = appendToFallback;
7486
- }
7487
7481
  this.messageInstance = createMessageWrapper(this.hasDocument, () => this.getMessageContainer());
7488
7482
  this.notificationInstance = createNotificationWrapper(this.hasDocument, () => this.getMessageContainer());
7489
7483
  this.onTimeout = onTimeout;
@@ -7505,30 +7499,28 @@ const _BaseHttpClient = class _BaseHttpClient {
7505
7499
  if (!this.hasDocument) {
7506
7500
  return document.body;
7507
7501
  }
7508
- const appendTo = _BaseHttpClient.sharedAppendTo ?? this.appendTo;
7509
- if (!appendTo) {
7502
+ if (!this.appendTo) {
7510
7503
  return document.body;
7511
7504
  }
7512
- if (appendTo instanceof HTMLElement) {
7513
- return appendTo;
7505
+ if (this.appendTo instanceof HTMLElement) {
7506
+ return this.appendTo;
7514
7507
  }
7515
- if (typeof appendTo === "string") {
7516
- const element = document.querySelector(appendTo);
7508
+ if (typeof this.appendTo === "string") {
7509
+ const element = document.querySelector(this.appendTo);
7517
7510
  if (element) {
7518
7511
  return element;
7519
7512
  }
7520
- const appendToFallback = _BaseHttpClient.sharedAppendToFallback ?? this.appendToFallback;
7521
- if (appendToFallback === null) {
7522
- console.warn(`appendTo 选择器 "${appendTo}" 未找到元素,appendToFallback 为 null,返回 null`);
7513
+ if (this.appendToFallback === null) {
7514
+ console.warn(`appendTo 选择器 "${this.appendTo}" 未找到元素,appendToFallback null,返回 null`);
7523
7515
  return null;
7524
- } else if (appendToFallback === "body") {
7516
+ } else if (this.appendToFallback === "body") {
7525
7517
  return document.body;
7526
- } else if (typeof appendToFallback === "string") {
7527
- const fallbackElement = document.querySelector(appendToFallback);
7518
+ } else if (typeof this.appendToFallback === "string") {
7519
+ const fallbackElement = document.querySelector(this.appendToFallback);
7528
7520
  if (fallbackElement) {
7529
7521
  return fallbackElement;
7530
7522
  }
7531
- console.warn(`appendTo 选择器 "${appendTo}" 和 appendToFallback 选择器 "${appendToFallback}" 都未找到元素,返回 null`);
7523
+ console.warn(`appendTo 选择器 "${this.appendTo}" 和 appendToFallback 选择器 "${this.appendToFallback}" 都未找到元素,返回 null`);
7532
7524
  return null;
7533
7525
  }
7534
7526
  }
@@ -7538,19 +7530,16 @@ const _BaseHttpClient = class _BaseHttpClient {
7538
7530
  if (!this.hasDocument) {
7539
7531
  return null;
7540
7532
  }
7541
- if (!_BaseHttpClient.sharedContainerId) {
7533
+ if (!this.containerId) {
7542
7534
  const targetElementId = generateUUID();
7543
- _BaseHttpClient.sharedContainerId = `ajaxPackage-container-${targetElementId}`;
7544
- _BaseHttpClient.sharedPopoverContainerId = `ajaxPackage-popover-${targetElementId}`;
7545
- _BaseHttpClient.sharedMessageContainerId = `ajaxPackage-message-${targetElementId}`;
7546
- }
7547
- this.containerId = _BaseHttpClient.sharedContainerId;
7548
- this.popoverContainerId = _BaseHttpClient.sharedPopoverContainerId;
7549
- this.messageContainerId = _BaseHttpClient.sharedMessageContainerId;
7550
- let container = document.getElementById(_BaseHttpClient.sharedContainerId);
7535
+ this.containerId = `ajaxPackage-container-${targetElementId}`;
7536
+ this.popoverContainerId = `ajaxPackage-popover-${targetElementId}`;
7537
+ this.messageContainerId = `ajaxPackage-message-${targetElementId}`;
7538
+ }
7539
+ let container = document.getElementById(this.containerId);
7551
7540
  if (!container) {
7552
7541
  container = document.createElement("div");
7553
- container.id = _BaseHttpClient.sharedContainerId;
7542
+ container.id = this.containerId;
7554
7543
  const targetElement = this.resolveAppendToTarget();
7555
7544
  if (targetElement) {
7556
7545
  targetElement.appendChild(container);
@@ -7558,12 +7547,12 @@ const _BaseHttpClient = class _BaseHttpClient {
7558
7547
  return null;
7559
7548
  }
7560
7549
  const popoverContainer = document.createElement("div");
7561
- popoverContainer.id = _BaseHttpClient.sharedPopoverContainerId;
7550
+ popoverContainer.id = this.popoverContainerId;
7562
7551
  popoverContainer.style.position = "relative";
7563
7552
  popoverContainer.style.zIndex = "999999";
7564
7553
  container.appendChild(popoverContainer);
7565
7554
  const messageContainer = document.createElement("div");
7566
- messageContainer.id = _BaseHttpClient.sharedMessageContainerId;
7555
+ messageContainer.id = this.messageContainerId;
7567
7556
  messageContainer.style.position = "relative";
7568
7557
  messageContainer.style.zIndex = "99999999";
7569
7558
  container.appendChild(messageContainer);
@@ -7582,11 +7571,10 @@ const _BaseHttpClient = class _BaseHttpClient {
7582
7571
  if (!container) {
7583
7572
  return null;
7584
7573
  }
7585
- const popoverContainerId = _BaseHttpClient.sharedPopoverContainerId || this.popoverContainerId;
7586
- if (!popoverContainerId) {
7574
+ if (!this.popoverContainerId) {
7587
7575
  return null;
7588
7576
  }
7589
- const popoverContainer = document.getElementById(popoverContainerId);
7577
+ const popoverContainer = document.getElementById(this.popoverContainerId);
7590
7578
  if (popoverContainer && !popoverContainer.style.position) {
7591
7579
  popoverContainer.style.position = "relative";
7592
7580
  popoverContainer.style.zIndex = "999999";
@@ -7605,11 +7593,10 @@ const _BaseHttpClient = class _BaseHttpClient {
7605
7593
  if (!container) {
7606
7594
  return null;
7607
7595
  }
7608
- const messageContainerId = _BaseHttpClient.sharedMessageContainerId || this.messageContainerId;
7609
- if (!messageContainerId) {
7596
+ if (!this.messageContainerId) {
7610
7597
  return null;
7611
7598
  }
7612
- const messageContainer = document.getElementById(messageContainerId);
7599
+ const messageContainer = document.getElementById(this.messageContainerId);
7613
7600
  if (messageContainer && !messageContainer.style.position) {
7614
7601
  messageContainer.style.position = "relative";
7615
7602
  messageContainer.style.zIndex = "99999999";
@@ -7761,12 +7748,6 @@ const _BaseHttpClient = class _BaseHttpClient {
7761
7748
  }
7762
7749
  };
7763
7750
  __publicField(_BaseHttpClient, "hasDocument", typeof document !== "undefined");
7764
- // 静态共享容器配置 - 所有实例共享同一个容器
7765
- __publicField(_BaseHttpClient, "sharedContainerId", "");
7766
- __publicField(_BaseHttpClient, "sharedPopoverContainerId", "");
7767
- __publicField(_BaseHttpClient, "sharedMessageContainerId", "");
7768
- __publicField(_BaseHttpClient, "sharedAppendTo");
7769
- __publicField(_BaseHttpClient, "sharedAppendToFallback", "body");
7770
7751
  let BaseHttpClient = _BaseHttpClient;
7771
7752
  async function copyToClipboard(text) {
7772
7753
  if (!text) {
@@ -8716,7 +8697,7 @@ const SystemErrorDialog = defineComponent({
8716
8697
  marginBottom: isMobile ? "4px" : 0
8717
8698
  }
8718
8699
  }, `${item.label}:`),
8719
- h("span", { style: { flex: 1, color: "#303133", wordBreak: "break-all", wordWrap: "break-word" } }, item.value || "未知")
8700
+ h("span", { style: { flex: 1, color: "#303133", wordBreak: "break-all", wordWrap: "break-word", whiteSpace: "pre-wrap" } }, item.value || "未知")
8720
8701
  ]
8721
8702
  )
8722
8703
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moluoxixi/ajax-package",
3
- "version": "0.0.55",
3
+ "version": "0.0.57",
4
4
  "description": "AjaxPackage 组件",
5
5
  "sideEffects": [
6
6
  "*.css",