uniapp-request-sdk 1.6.1 → 1.7.1
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 +577 -124
- package/dist/index.d.ts +152 -26
- package/dist/index.esm.js +210 -39
- package/dist/index.umd.js +210 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
- [详细配置](#详细配置)
|
|
17
17
|
- [API 文档](#api-文档)
|
|
18
18
|
- [文件上传](#文件上传)
|
|
19
|
+
- [文件下载](#文件下载)
|
|
19
20
|
- [使用示例](#使用示例)
|
|
20
21
|
- [响应格式](#响应格式)
|
|
21
22
|
- [错误处理](#错误处理)
|
|
@@ -31,11 +32,12 @@
|
|
|
31
32
|
|
|
32
33
|
这个 SDK 适合以下场景:
|
|
33
34
|
|
|
34
|
-
- uni-app 项目中统一封装 `GET`、`POST`、`PUT`、`DELETE`、`uploadFile`
|
|
35
|
+
- uni-app 项目中统一封装 `GET`、`POST`、`PUT`、`DELETE`、`uploadFile`、`downloadFile`
|
|
35
36
|
- 需要自动处理 `403` 后重新获取 token 再重试的业务场景
|
|
36
37
|
- 需要为所有请求统一追加公共请求头、日志字段、签名字段
|
|
37
38
|
- 需要在 App、小程序、H5 等多端尽量复用一套网络层逻辑
|
|
38
39
|
- 需要在上传文件时监听进度并驱动页面上的进度条展示
|
|
40
|
+
- 需要在下载文件时监听进度并驱动页面上的下载进度条展示
|
|
39
41
|
|
|
40
42
|
如果你的服务端返回结构不是本文档中的默认格式,也可以基于当前 SDK 继续二次封装。
|
|
41
43
|
|
|
@@ -46,6 +48,7 @@
|
|
|
46
48
|
- 支持同步或异步的请求头预处理器 `headerProcessor`
|
|
47
49
|
- 支持 `401`、`403`、网络异常、超时等通用场景处理
|
|
48
50
|
- 支持文件上传、上传超时控制和上传进度监听
|
|
51
|
+
- 支持文件下载、下载超时控制和下载进度监听
|
|
49
52
|
- 默认兼容相对路径与完整 URL 两种调用方式
|
|
50
53
|
|
|
51
54
|
## 特性
|
|
@@ -54,9 +57,10 @@
|
|
|
54
57
|
- ✅ **动态 Token 管理** - 支持自动获取和更新 token,兼容 APP 原生交互
|
|
55
58
|
- ✅ **请求头预处理** - 支持同步/异步预处理器,动态生成或修改请求头
|
|
56
59
|
- ✅ **完整的异常处理** - 统一的错误处理、HTTP 状态码处理、权限管理
|
|
57
|
-
- ✅ **文件上传支持** -
|
|
60
|
+
- ✅ **文件上传支持** - 专门优化的文件上传接口,独立超时控制,进度监听
|
|
61
|
+
- ✅ **文件下载支持** - 专门优化的文件下载接口,独立超时控制,进度监听
|
|
58
62
|
- ✅ **平台兼容** - 支持 iOS App、Android App、H5 等多平台
|
|
59
|
-
- ✅ **TypeScript 支持** -
|
|
63
|
+
- ✅ **TypeScript 支持** - 完整的类型定义和 JSDoc 注释,更好的开发体验
|
|
60
64
|
- ✅ **完全向后兼容** - 不破坏现有代码,渐进式增强
|
|
61
65
|
|
|
62
66
|
## 安装
|
|
@@ -82,6 +86,7 @@ const request = new UniRequest({
|
|
|
82
86
|
baseUrl: 'https://api.example.com',
|
|
83
87
|
timeout: 10000,
|
|
84
88
|
uploadTimeout: 30000,
|
|
89
|
+
downloadTimeout: 30000,
|
|
85
90
|
tokenHeader: 'Authorization',
|
|
86
91
|
tokenPrefix: 'Bearer ',
|
|
87
92
|
onErrorHandler: (error) => {
|
|
@@ -104,16 +109,15 @@ export function updateUserProfile(data: Record<string, any>) {
|
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
export function uploadAvatar(filePath: string) {
|
|
107
|
-
return request.uploadFile(
|
|
108
|
-
'
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
);
|
|
112
|
+
return request.uploadFile('/user/avatar/upload', filePath, undefined, 'file', undefined, (progress) => {
|
|
113
|
+
console.log('上传进度:', progress.progress);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function downloadDocument(documentId: string) {
|
|
118
|
+
return request.downloadFile(`/documents/${documentId}/download`, undefined, undefined, (progress) => {
|
|
119
|
+
console.log('下载进度:', progress.progress);
|
|
120
|
+
});
|
|
117
121
|
}
|
|
118
122
|
```
|
|
119
123
|
|
|
@@ -123,6 +127,8 @@ export function uploadAvatar(filePath: string) {
|
|
|
123
127
|
- 统一错误处理能力
|
|
124
128
|
- 文件上传能力
|
|
125
129
|
- 上传进度监听能力
|
|
130
|
+
- 文件下载能力
|
|
131
|
+
- 下载进度监听能力
|
|
126
132
|
|
|
127
133
|
## 快速开始
|
|
128
134
|
|
|
@@ -160,6 +166,11 @@ const uploadResult = await request.uploadFile(
|
|
|
160
166
|
console.log('当前上传进度:', progress.progress);
|
|
161
167
|
},
|
|
162
168
|
);
|
|
169
|
+
|
|
170
|
+
// 7. 下载文件
|
|
171
|
+
const downloadPath = await request.downloadFile('/download/document.pdf', undefined, undefined, (progress) => {
|
|
172
|
+
console.log('当前下载进度:', progress.progress);
|
|
173
|
+
});
|
|
163
174
|
```
|
|
164
175
|
|
|
165
176
|
## 工程接入建议
|
|
@@ -196,40 +207,44 @@ src/
|
|
|
196
207
|
```typescript
|
|
197
208
|
const request = new UniRequest({
|
|
198
209
|
// 基础配置
|
|
199
|
-
baseUrl: 'https://api.example.com',
|
|
200
|
-
username: 'john_doe',
|
|
210
|
+
baseUrl: 'https://api.example.com', // API 基础地址(必须)
|
|
211
|
+
username: 'john_doe', // 用户名(用于日志)
|
|
201
212
|
|
|
202
213
|
// 超时设置
|
|
203
|
-
timeout: 10000,
|
|
204
|
-
uploadTimeout: 5000,
|
|
214
|
+
timeout: 10000, // 普通请求超时(毫秒,默认 10s)
|
|
215
|
+
uploadTimeout: 5000, // 文件上传超时(毫秒,默认 5s)
|
|
205
216
|
|
|
206
217
|
// 重试配置
|
|
207
|
-
maxRetryCount: 3,
|
|
208
|
-
retryDelay: 3000,
|
|
218
|
+
maxRetryCount: 3, // 最大重试次数(默认 3)
|
|
219
|
+
retryDelay: 3000, // 重试延迟时间(毫秒,默认 3s)
|
|
209
220
|
|
|
210
221
|
// 请求头配置
|
|
211
|
-
header: {
|
|
222
|
+
header: {
|
|
223
|
+
// 全局请求头
|
|
212
224
|
'Content-Type': 'application/json',
|
|
213
225
|
'X-Custom-Header': 'value',
|
|
214
226
|
},
|
|
215
227
|
|
|
216
228
|
// Token 配置
|
|
217
|
-
token: 'initial-token',
|
|
218
|
-
tokenHeader: 'Authorization',
|
|
219
|
-
tokenPrefix: 'Bearer ',
|
|
220
|
-
tokenEventName: 'getToken',
|
|
221
|
-
getTokenFun: async () => {
|
|
229
|
+
token: 'initial-token', // 初始 token
|
|
230
|
+
tokenHeader: 'Authorization', // token 所在的 header 字段名(默认)
|
|
231
|
+
tokenPrefix: 'Bearer ', // token 前缀(默认带空格)
|
|
232
|
+
tokenEventName: 'getToken', // 向 APP 获取 token 的事件名
|
|
233
|
+
getTokenFun: async () => {
|
|
234
|
+
// 自定义获取 token 函数
|
|
222
235
|
return await fetchTokenFromStorage();
|
|
223
236
|
},
|
|
224
237
|
|
|
225
238
|
// 错误处理
|
|
226
|
-
onErrorHandler: (error) => {
|
|
239
|
+
onErrorHandler: (error) => {
|
|
240
|
+
// 统一错误处理函数
|
|
227
241
|
console.error('请求错误:', error);
|
|
228
242
|
// 可以在这里上报错误日志
|
|
229
243
|
},
|
|
230
244
|
|
|
231
245
|
// 新增:请求头预处理
|
|
232
|
-
headerProcessor: async (header) => {
|
|
246
|
+
headerProcessor: async (header) => {
|
|
247
|
+
// 动态处理请求头(异步)
|
|
233
248
|
const timestamp = Date.now();
|
|
234
249
|
const signature = await generateSignature(timestamp);
|
|
235
250
|
return {
|
|
@@ -245,9 +260,10 @@ const request = new UniRequest({
|
|
|
245
260
|
```typescript
|
|
246
261
|
// 使用 setParams 方法动态更新配置
|
|
247
262
|
request.setParams({
|
|
248
|
-
token: 'new-token',
|
|
249
|
-
baseUrl: 'https://new-api.example.com',
|
|
250
|
-
headerProcessor: (header) => {
|
|
263
|
+
token: 'new-token', // 更新 token
|
|
264
|
+
baseUrl: 'https://new-api.example.com', // 更新 API 地址
|
|
265
|
+
headerProcessor: (header) => {
|
|
266
|
+
// 更新头处理器
|
|
251
267
|
return {
|
|
252
268
|
'X-New-Header': 'new-value',
|
|
253
269
|
};
|
|
@@ -275,7 +291,7 @@ const data = await request.get<ResponseType>(
|
|
|
275
291
|
{},
|
|
276
292
|
{
|
|
277
293
|
'X-Custom-Header': 'custom-value',
|
|
278
|
-
}
|
|
294
|
+
},
|
|
279
295
|
);
|
|
280
296
|
```
|
|
281
297
|
|
|
@@ -294,7 +310,7 @@ const result = await request.post<ResponseType>(
|
|
|
294
310
|
{ name: 'John' },
|
|
295
311
|
{
|
|
296
312
|
'X-Custom-Header': 'custom-value',
|
|
297
|
-
}
|
|
313
|
+
},
|
|
298
314
|
);
|
|
299
315
|
```
|
|
300
316
|
|
|
@@ -317,29 +333,27 @@ const result = await request.put<ResponseType>('/users/1', {
|
|
|
317
333
|
```typescript
|
|
318
334
|
// 基础文件上传
|
|
319
335
|
const result = await request.uploadFile<ResponseType>(
|
|
320
|
-
'/upload',
|
|
321
|
-
'/path/to/file.jpg'
|
|
336
|
+
'/upload', // 上传 URL
|
|
337
|
+
'/path/to/file.jpg', // 文件路径
|
|
322
338
|
);
|
|
323
339
|
|
|
324
340
|
// 带 FormData 的文件上传
|
|
325
|
-
const result = await request.uploadFile<ResponseType>(
|
|
326
|
-
|
|
327
|
-
'
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
category: 'profile',
|
|
331
|
-
}
|
|
332
|
-
);
|
|
341
|
+
const result = await request.uploadFile<ResponseType>('/upload', '/path/to/file.jpg', {
|
|
342
|
+
// FormData 字段
|
|
343
|
+
description: 'My upload',
|
|
344
|
+
category: 'profile',
|
|
345
|
+
});
|
|
333
346
|
|
|
334
347
|
// 自定义文件字段名和请求头
|
|
335
348
|
const result = await request.uploadFile<ResponseType>(
|
|
336
349
|
'/upload',
|
|
337
350
|
'/path/to/file.jpg',
|
|
338
351
|
{ description: 'My upload' },
|
|
339
|
-
'avatar',
|
|
340
|
-
{
|
|
352
|
+
'avatar', // 文件字段名(默认 'file')
|
|
353
|
+
{
|
|
354
|
+
// 自定义请求头
|
|
341
355
|
'X-Upload-Token': 'upload-token',
|
|
342
|
-
}
|
|
356
|
+
},
|
|
343
357
|
);
|
|
344
358
|
|
|
345
359
|
// 监听上传进度
|
|
@@ -353,10 +367,40 @@ const result = await request.uploadFile<ResponseType>(
|
|
|
353
367
|
console.log('上传进度百分比:', progress.progress);
|
|
354
368
|
console.log('已上传字节数:', progress.totalBytesSent);
|
|
355
369
|
console.log('总字节数:', progress.totalBytesExpectedToSend);
|
|
356
|
-
}
|
|
370
|
+
},
|
|
357
371
|
);
|
|
358
372
|
```
|
|
359
373
|
|
|
374
|
+
### 文件下载
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
// 基础文件下载
|
|
378
|
+
const tempFilePath = await request.downloadFile('/download/file.pdf');
|
|
379
|
+
|
|
380
|
+
// 带自定义保存路径的下载(仅小程序有效)
|
|
381
|
+
const filePath = await request.downloadFile(
|
|
382
|
+
'/download/file.pdf',
|
|
383
|
+
'user_documents/file.pdf', // 本地保存路径(仅小程序平台支持)
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
// 自定义请求头的下载
|
|
387
|
+
const filePath = await request.downloadFile(
|
|
388
|
+
'/download/file.pdf',
|
|
389
|
+
undefined, // 保存路径
|
|
390
|
+
{
|
|
391
|
+
// 自定义请求头
|
|
392
|
+
'X-Download-Token': 'download-token',
|
|
393
|
+
},
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
// 监听下载进度
|
|
397
|
+
const filePath = await request.downloadFile('/download/large-file.zip', undefined, undefined, (progress) => {
|
|
398
|
+
console.log('下载进度百分比:', progress.progress);
|
|
399
|
+
console.log('已下载字节数:', progress.totalBytesWritten);
|
|
400
|
+
console.log('总字节数:', progress.totalBytesExpectedToWrite);
|
|
401
|
+
});
|
|
402
|
+
```
|
|
403
|
+
|
|
360
404
|
## 文件上传
|
|
361
405
|
|
|
362
406
|
### 方法签名
|
|
@@ -374,14 +418,14 @@ uploadFile<T>(
|
|
|
374
418
|
|
|
375
419
|
### 参数说明
|
|
376
420
|
|
|
377
|
-
| 参数
|
|
378
|
-
|
|
379
|
-
| `url`
|
|
380
|
-
| `filePath`
|
|
381
|
-
| `formData`
|
|
382
|
-
| `name`
|
|
383
|
-
| `header`
|
|
384
|
-
| `onProgressUpdateCallback` | `(result) => void`
|
|
421
|
+
| 参数 | 类型 | 是否必填 | 说明 |
|
|
422
|
+
| -------------------------- | ------------------------ | -------- | ------------------------------------ |
|
|
423
|
+
| `url` | `string` | 是 | 上传接口地址,支持相对路径和完整 URL |
|
|
424
|
+
| `filePath` | `string` | 是 | 待上传文件的本地临时路径 |
|
|
425
|
+
| `formData` | `Record<string, any>` | 否 | 附加的表单字段 |
|
|
426
|
+
| `name` | `string` | 否 | 文件字段名,默认是 `file` |
|
|
427
|
+
| `header` | `Record<string, string>` | 否 | 当前上传请求的自定义请求头 |
|
|
428
|
+
| `onProgressUpdateCallback` | `(result) => void` | 否 | 上传进度回调 |
|
|
385
429
|
|
|
386
430
|
### 上传进度回调说明
|
|
387
431
|
|
|
@@ -402,9 +446,96 @@ type OnProgressUpdateResult = {
|
|
|
402
446
|
- `onProgressUpdateCallback` 是可选参数,不传时上传行为与旧版本保持一致
|
|
403
447
|
- 如果只想传进度回调、不需要自定义 `header`,第 5 个参数需要显式传 `undefined`
|
|
404
448
|
- 上传接口底层仍然返回 Promise,适合继续用 `await` 或 `.then()`
|
|
405
|
-
-
|
|
449
|
+
- 如果上传过程中发生失败并触发重试,进度回调会按”当前这次上传尝试”的进度继续上报
|
|
406
450
|
- 如果服务端返回的是字符串 JSON,SDK 会自动尝试解析后再取其中的 `data`
|
|
407
451
|
|
|
452
|
+
## 文件下载
|
|
453
|
+
|
|
454
|
+
### 新增功能说明
|
|
455
|
+
|
|
456
|
+
从 v1.6.2 版本开始,SDK 新增了 `downloadFile` 方法,提供与 `uploadFile` 对称的文件下载功能。支持:
|
|
457
|
+
|
|
458
|
+
- ✅ 下载文件到本地临时目录或指定路径
|
|
459
|
+
- ✅ 自定义请求头
|
|
460
|
+
- ✅ 实时下载进度监听
|
|
461
|
+
- ✅ 自动重试、Token 管理等企业级特性
|
|
462
|
+
- ✅ 跨平台兼容(App、小程序、H5)
|
|
463
|
+
|
|
464
|
+
### 方法签名
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
downloadFile<T extends string = string>(
|
|
468
|
+
url: string,
|
|
469
|
+
filePath?: string,
|
|
470
|
+
header?: Record<string, string>,
|
|
471
|
+
onProgressUpdateCallback?: (result: DownloadFileProgressUpdateResult) => void,
|
|
472
|
+
): Promise<T>
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### 参数说明
|
|
476
|
+
|
|
477
|
+
| 参数 | 类型 | 是否必填 | 说明 |
|
|
478
|
+
| -------------------------- | ------------------------ | -------- | -------------------------------------------------------------------- |
|
|
479
|
+
| `url` | `string` | 是 | 下载资源的 URL,支持相对路径和完整 URL |
|
|
480
|
+
| `filePath` | `string` | 否 | 文件保存路径(本地路径),仅小程序平台支持;如不指定则保存到临时目录 |
|
|
481
|
+
| `header` | `Record<string, string>` | 否 | 当前下载请求的自定义请求头 |
|
|
482
|
+
| `onProgressUpdateCallback` | `(result) => void` | 否 | 下载进度回调 |
|
|
483
|
+
|
|
484
|
+
### 返回值说明
|
|
485
|
+
|
|
486
|
+
方法返回 Promise,解析为下载文件的临时路径(`tempFilePath`):
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
// 基础用法
|
|
490
|
+
const filePath = await request.downloadFile('/files/document.pdf');
|
|
491
|
+
// filePath 是文件的临时路径,如 '/tmp/file_20240314.pdf'
|
|
492
|
+
|
|
493
|
+
// 支持泛型约束
|
|
494
|
+
const filePath: string = await request.downloadFile<string>('/files/document.pdf');
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### 下载进度回调说明
|
|
498
|
+
|
|
499
|
+
当传入 `onProgressUpdateCallback` 后,SDK 会在内部调用 `uni.downloadFile()` 返回的 `DownloadTask.onProgressUpdate()` 进行绑定。
|
|
500
|
+
|
|
501
|
+
回调参数结构如下:
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
type DownloadFileProgressUpdateResult = {
|
|
505
|
+
progress?: number; // 下载进度百分比 (0-100)
|
|
506
|
+
totalBytesWritten?: number; // 已下载字节数
|
|
507
|
+
totalBytesExpectedToWrite?: number; // 总字节数
|
|
508
|
+
};
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### 与上传的区别
|
|
512
|
+
|
|
513
|
+
| 特性 | uploadFile | downloadFile |
|
|
514
|
+
| ---------- | ---------------------- | -------------------- |
|
|
515
|
+
| 超时默认值 | 5 秒 | 30 秒 |
|
|
516
|
+
| 文件来源 | 本地文件 | 远程 URL |
|
|
517
|
+
| 附加参数 | formData(表单字段) | filePath(保存路径) |
|
|
518
|
+
| 进度类型 | `totalBytesSent` | `totalBytesWritten` |
|
|
519
|
+
| 返回值 | 服务端响应的 data 字段 | 文件临时路径 |
|
|
520
|
+
|
|
521
|
+
### 平台兼容性
|
|
522
|
+
|
|
523
|
+
| 特性 | iOS App | Android App | 小程序 | H5 |
|
|
524
|
+
| -------------- | ------- | ----------- | ------ | ---- |
|
|
525
|
+
| 基础下载 | ✅ | ✅ | ✅ | ✅ |
|
|
526
|
+
| 进度监听 | ✅ | ✅ | ✅ | 部分 |
|
|
527
|
+
| 自定义保存路径 | ❌ | ❌ | ✅ | ❌ |
|
|
528
|
+
| 文件 URI | ✅ | ✅ | ✅ | 部分 |
|
|
529
|
+
|
|
530
|
+
### 使用注意事项
|
|
531
|
+
|
|
532
|
+
- 下载的文件保存在临时目录,应用关闭后会被清理,需要持久化时调用 `uni.saveFile()`
|
|
533
|
+
- 小程序平台支持指定 `filePath` 保存到指定目录,其他平台会忽略此参数
|
|
534
|
+
- 下载超时时间默认为 30 秒,可通过 `downloadTimeout` 配置修改
|
|
535
|
+
- 下载过程中如果失败,SDK 会自动重试(最多 `maxRetryCount` 次)
|
|
536
|
+
- 下载大文件时建议增加 `downloadTimeout` 的值
|
|
537
|
+
- 下载过程中用户可以通过返回的 Promise 的 abort 功能中止下载(需要获取 DownloadTask)
|
|
538
|
+
|
|
408
539
|
## 使用示例
|
|
409
540
|
|
|
410
541
|
### 示例 1:基础请求
|
|
@@ -436,7 +567,7 @@ import UniRequest from 'uniapp-request-sdk';
|
|
|
436
567
|
|
|
437
568
|
const request = new UniRequest({
|
|
438
569
|
baseUrl: 'https://api.example.com',
|
|
439
|
-
tokenEventName: 'getToken',
|
|
570
|
+
tokenEventName: 'getToken', // APP 端事件名
|
|
440
571
|
onErrorHandler: (error) => {
|
|
441
572
|
if (error.statusCode === 401) {
|
|
442
573
|
console.log('用户已登出');
|
|
@@ -489,7 +620,7 @@ import UniRequest from 'uniapp-request-sdk';
|
|
|
489
620
|
|
|
490
621
|
const request = new UniRequest({
|
|
491
622
|
baseUrl: 'https://api.example.com',
|
|
492
|
-
uploadTimeout: 30000,
|
|
623
|
+
uploadTimeout: 30000, // 文件上传超时 30 秒
|
|
493
624
|
});
|
|
494
625
|
|
|
495
626
|
// 选择文件并上传
|
|
@@ -498,15 +629,11 @@ async function selectAndUploadFile() {
|
|
|
498
629
|
count: 1,
|
|
499
630
|
success: async (res) => {
|
|
500
631
|
try {
|
|
501
|
-
const result = await request.uploadFile(
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
description: '头像',
|
|
507
|
-
category: 'avatar',
|
|
508
|
-
}
|
|
509
|
-
);
|
|
632
|
+
const result = await request.uploadFile('/upload', res.tempFilePaths[0], {
|
|
633
|
+
// 附加信息
|
|
634
|
+
description: '头像',
|
|
635
|
+
category: 'avatar',
|
|
636
|
+
});
|
|
510
637
|
console.log('上传成功:', result);
|
|
511
638
|
} catch (error) {
|
|
512
639
|
console.error('上传失败:', error);
|
|
@@ -557,6 +684,226 @@ async function selectAndUploadFile() {
|
|
|
557
684
|
}
|
|
558
685
|
```
|
|
559
686
|
|
|
687
|
+
### 示例 4-2:文件下载(新增)
|
|
688
|
+
|
|
689
|
+
```typescript
|
|
690
|
+
import UniRequest from 'uniapp-request-sdk';
|
|
691
|
+
|
|
692
|
+
const request = new UniRequest({
|
|
693
|
+
baseUrl: 'https://api.example.com',
|
|
694
|
+
downloadTimeout: 30000, // 文件下载超时 30 秒
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
// 简单下载
|
|
698
|
+
async function downloadFile() {
|
|
699
|
+
try {
|
|
700
|
+
const filePath = await request.downloadFile('/files/document.pdf');
|
|
701
|
+
console.log('下载完成,文件路径:', filePath);
|
|
702
|
+
|
|
703
|
+
// 显示文件
|
|
704
|
+
uni.openDocument({
|
|
705
|
+
filePath: filePath,
|
|
706
|
+
showMenu: true,
|
|
707
|
+
});
|
|
708
|
+
} catch (error) {
|
|
709
|
+
console.error('下载失败:', error);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// 带进度监听的下载
|
|
714
|
+
async function downloadFileWithProgress() {
|
|
715
|
+
try {
|
|
716
|
+
let downloadProgress = 0;
|
|
717
|
+
|
|
718
|
+
const filePath = await request.downloadFile('/files/large-file.zip', undefined, undefined, (progress) => {
|
|
719
|
+
downloadProgress = progress.progress || 0;
|
|
720
|
+
console.log(`下载进度: ${downloadProgress}%`);
|
|
721
|
+
console.log(`已下载: ${progress.totalBytesWritten} / ${progress.totalBytesExpectedToWrite} 字节`);
|
|
722
|
+
|
|
723
|
+
// 更新 UI 进度条
|
|
724
|
+
// updateProgressBar(downloadProgress);
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
console.log('下载完成:', filePath);
|
|
728
|
+
} catch (error) {
|
|
729
|
+
console.error('下载失败:', error, downloadProgress);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// 小程序中下载到指定路径
|
|
734
|
+
async function downloadFileToPath() {
|
|
735
|
+
try {
|
|
736
|
+
const filePath = await request.downloadFile(
|
|
737
|
+
'/files/contract.pdf',
|
|
738
|
+
'user_documents/contract.pdf', // 保存到应用沙箱
|
|
739
|
+
);
|
|
740
|
+
|
|
741
|
+
console.log('文件已保存:', filePath);
|
|
742
|
+
} catch (error) {
|
|
743
|
+
console.error('下载失败:', error);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// 带自定义 header 的下载(如需要鉴权)
|
|
748
|
+
async function downloadSecureFile() {
|
|
749
|
+
try {
|
|
750
|
+
const filePath = await request.downloadFile(
|
|
751
|
+
'/files/secure-document.pdf',
|
|
752
|
+
undefined,
|
|
753
|
+
{
|
|
754
|
+
'X-Download-Token': 'secure-token-xxx',
|
|
755
|
+
'X-User-Id': 'user-123',
|
|
756
|
+
},
|
|
757
|
+
(progress) => {
|
|
758
|
+
console.log(`下载进度: ${progress.progress}%`);
|
|
759
|
+
},
|
|
760
|
+
);
|
|
761
|
+
|
|
762
|
+
console.log('下载完成:', filePath);
|
|
763
|
+
} catch (error) {
|
|
764
|
+
console.error('下载失败:', error);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
### 示例 4-3:综合文件管理(上传和下载)
|
|
770
|
+
|
|
771
|
+
```typescript
|
|
772
|
+
import UniRequest from 'uniapp-request-sdk';
|
|
773
|
+
|
|
774
|
+
const request = new UniRequest({
|
|
775
|
+
baseUrl: 'https://api.example.com',
|
|
776
|
+
uploadTimeout: 30000,
|
|
777
|
+
downloadTimeout: 30000,
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
// 文件管理服务
|
|
781
|
+
export const FileService = {
|
|
782
|
+
// 上传文件
|
|
783
|
+
uploadDocument: async (filePath: string, metadata: Record<string, any>) => {
|
|
784
|
+
try {
|
|
785
|
+
const result = await request.uploadFile(
|
|
786
|
+
'/documents/upload',
|
|
787
|
+
filePath,
|
|
788
|
+
metadata,
|
|
789
|
+
'file',
|
|
790
|
+
undefined,
|
|
791
|
+
(progress) => {
|
|
792
|
+
console.log(`上传进度: ${progress.progress}%`);
|
|
793
|
+
},
|
|
794
|
+
);
|
|
795
|
+
return result;
|
|
796
|
+
} catch (error) {
|
|
797
|
+
console.error('上传失败:', error);
|
|
798
|
+
throw error;
|
|
799
|
+
}
|
|
800
|
+
},
|
|
801
|
+
|
|
802
|
+
// 下载文件
|
|
803
|
+
downloadDocument: async (documentId: string, fileName: string) => {
|
|
804
|
+
try {
|
|
805
|
+
const filePath = await request.downloadFile(
|
|
806
|
+
`/documents/download/${documentId}`,
|
|
807
|
+
`downloads/${fileName}`,
|
|
808
|
+
{ 'X-Document-Id': documentId },
|
|
809
|
+
(progress) => {
|
|
810
|
+
console.log(`下载进度: ${progress.progress}%`);
|
|
811
|
+
},
|
|
812
|
+
);
|
|
813
|
+
return filePath;
|
|
814
|
+
} catch (error) {
|
|
815
|
+
console.error('下载失败:', error);
|
|
816
|
+
throw error;
|
|
817
|
+
}
|
|
818
|
+
},
|
|
819
|
+
|
|
820
|
+
// 列出文档
|
|
821
|
+
listDocuments: async (payload: Record<string, any>) => {
|
|
822
|
+
return request.post('/documents/list', payload);
|
|
823
|
+
},
|
|
824
|
+
|
|
825
|
+
// 删除文档
|
|
826
|
+
deleteDocument: async (documentId: string) => {
|
|
827
|
+
return request.delete(`/documents/${documentId}`);
|
|
828
|
+
},
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
// 在组件中使用
|
|
832
|
+
export default {
|
|
833
|
+
data() {
|
|
834
|
+
return {
|
|
835
|
+
uploadProgress: 0,
|
|
836
|
+
downloadProgress: 0,
|
|
837
|
+
documents: [],
|
|
838
|
+
};
|
|
839
|
+
},
|
|
840
|
+
|
|
841
|
+
methods: {
|
|
842
|
+
// 选择并上传文件
|
|
843
|
+
async selectAndUpload() {
|
|
844
|
+
try {
|
|
845
|
+
const res = await uni.chooseFile({
|
|
846
|
+
type: 'file',
|
|
847
|
+
count: 1,
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
const result = await FileService.uploadDocument(res.tempFilePaths[0], {
|
|
851
|
+
description: '重要文档',
|
|
852
|
+
category: 'reports',
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
uni.showToast({
|
|
856
|
+
title: '上传成功',
|
|
857
|
+
icon: 'success',
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
// 刷新列表
|
|
861
|
+
await this.loadDocuments();
|
|
862
|
+
} catch (error) {
|
|
863
|
+
uni.showToast({
|
|
864
|
+
title: '上传失败',
|
|
865
|
+
icon: 'error',
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
},
|
|
869
|
+
|
|
870
|
+
// 下载文件
|
|
871
|
+
async downloadDocument(documentId: string, fileName: string) {
|
|
872
|
+
try {
|
|
873
|
+
const filePath = await FileService.downloadDocument(documentId, fileName);
|
|
874
|
+
|
|
875
|
+
// 打开文件
|
|
876
|
+
uni.openDocument({
|
|
877
|
+
filePath: filePath,
|
|
878
|
+
showMenu: true,
|
|
879
|
+
});
|
|
880
|
+
} catch (error) {
|
|
881
|
+
uni.showToast({
|
|
882
|
+
title: '下载失败',
|
|
883
|
+
icon: 'error',
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
},
|
|
887
|
+
|
|
888
|
+
// 加载文档列表
|
|
889
|
+
async loadDocuments() {
|
|
890
|
+
try {
|
|
891
|
+
this.documents = await FileService.listDocuments({
|
|
892
|
+
page: 1,
|
|
893
|
+
limit: 20,
|
|
894
|
+
});
|
|
895
|
+
} catch (error) {
|
|
896
|
+
console.error('加载失败:', error);
|
|
897
|
+
}
|
|
898
|
+
},
|
|
899
|
+
},
|
|
900
|
+
|
|
901
|
+
mounted() {
|
|
902
|
+
this.loadDocuments();
|
|
903
|
+
},
|
|
904
|
+
};
|
|
905
|
+
```
|
|
906
|
+
|
|
560
907
|
### 示例 5:实时生成签名
|
|
561
908
|
|
|
562
909
|
```typescript
|
|
@@ -596,6 +943,7 @@ const request = new UniRequest({
|
|
|
596
943
|
```
|
|
597
944
|
|
|
598
945
|
其中:
|
|
946
|
+
|
|
599
947
|
- `errno === 0` 表示请求成功
|
|
600
948
|
- `errno !== 0` 表示业务错误,会触发 reject
|
|
601
949
|
|
|
@@ -603,13 +951,13 @@ const request = new UniRequest({
|
|
|
603
951
|
|
|
604
952
|
### 自动处理的错误
|
|
605
953
|
|
|
606
|
-
| 错误类型 | 处理方式
|
|
607
|
-
|
|
608
|
-
| HTTP 403 | 自动获取新 token 并重试
|
|
954
|
+
| 错误类型 | 处理方式 |
|
|
955
|
+
| -------- | ------------------------------------- |
|
|
956
|
+
| HTTP 403 | 自动获取新 token 并重试 |
|
|
609
957
|
| HTTP 401 | 触发 logout 事件,通知 APP 返回登录页 |
|
|
610
|
-
| 网络错误 | 自动重试(最多 maxRetryCount 次)
|
|
611
|
-
| 超时错误 | 自动重试(最多 maxRetryCount 次)
|
|
612
|
-
| 业务错误 | 直接 reject,业务处理
|
|
958
|
+
| 网络错误 | 自动重试(最多 maxRetryCount 次) |
|
|
959
|
+
| 超时错误 | 自动重试(最多 maxRetryCount 次) |
|
|
960
|
+
| 业务错误 | 直接 reject,业务处理 |
|
|
613
961
|
|
|
614
962
|
### 全局错误处理
|
|
615
963
|
|
|
@@ -667,28 +1015,28 @@ const request = new UniRequest({
|
|
|
667
1015
|
|
|
668
1016
|
```typescript
|
|
669
1017
|
const request = new UniRequest({
|
|
670
|
-
tokenEventName: 'getToken',
|
|
1018
|
+
tokenEventName: 'getToken', // APP 端对应事件名
|
|
671
1019
|
});
|
|
672
1020
|
```
|
|
673
1021
|
|
|
674
1022
|
## 配置参数详解
|
|
675
1023
|
|
|
676
|
-
| 参数
|
|
677
|
-
|
|
678
|
-
| baseUrl
|
|
679
|
-
| timeout
|
|
680
|
-
| uploadTimeout
|
|
681
|
-
| maxRetryCount
|
|
682
|
-
| retryDelay
|
|
683
|
-
| header
|
|
684
|
-
| headerProcessor | function | -
|
|
685
|
-
| token
|
|
686
|
-
| tokenHeader
|
|
687
|
-
| tokenPrefix
|
|
688
|
-
| tokenEventName
|
|
689
|
-
| getTokenFun
|
|
690
|
-
| username
|
|
691
|
-
| onErrorHandler
|
|
1024
|
+
| 参数 | 类型 | 默认值 | 必填 | 说明 |
|
|
1025
|
+
| --------------- | -------- | ------------- | ---- | -------------------------- |
|
|
1026
|
+
| baseUrl | string | - | ✅ | API 基础地址,支持代理路径 |
|
|
1027
|
+
| timeout | number | 10000 | - | 普通请求超时(毫秒) |
|
|
1028
|
+
| uploadTimeout | number | 5000 | - | 文件上传超时(毫秒) |
|
|
1029
|
+
| maxRetryCount | number | 3 | - | 失败重试次数 |
|
|
1030
|
+
| retryDelay | number | 3000 | - | 重试延迟时间(毫秒) |
|
|
1031
|
+
| header | object | - | - | 全局请求头 |
|
|
1032
|
+
| headerProcessor | function | - | - | 请求头预处理函数(新增) |
|
|
1033
|
+
| token | string | - | - | 初始 token |
|
|
1034
|
+
| tokenHeader | string | Authorization | - | token 所在 header 字段 |
|
|
1035
|
+
| tokenPrefix | string | 'Bearer ' | - | token 前缀 |
|
|
1036
|
+
| tokenEventName | string | getToken | - | APP 获取 token 的事件名 |
|
|
1037
|
+
| getTokenFun | function | - | - | 自定义 token 获取函数 |
|
|
1038
|
+
| username | string | - | - | 用户名(用于日志) |
|
|
1039
|
+
| onErrorHandler | function | console.error | - | 全局错误处理函数 |
|
|
692
1040
|
|
|
693
1041
|
## 请求头预处理器(新增功能)
|
|
694
1042
|
|
|
@@ -757,7 +1105,7 @@ const request = new UniRequest({
|
|
|
757
1105
|
### 预处理器的执行时机
|
|
758
1106
|
|
|
759
1107
|
- **执行点**:在设置 token 后、发送请求前
|
|
760
|
-
-
|
|
1108
|
+
- **执行频率**:每次业务请求执行一次,自动重试时复用第一次处理后的 header;若中途刷新 token,则会基于新 token 重新执行一次
|
|
761
1109
|
- **异常处理**:如果预处理器异常,请求会直接 reject,不会发送
|
|
762
1110
|
|
|
763
1111
|
### 预处理器的签名
|
|
@@ -838,15 +1186,15 @@ async function getUsers() {
|
|
|
838
1186
|
```typescript
|
|
839
1187
|
// ❌ 错误:每次请求都会等待 5 秒
|
|
840
1188
|
headerProcessor: async () => {
|
|
841
|
-
await sleep(5000);
|
|
1189
|
+
await sleep(5000); // 不必要的延迟
|
|
842
1190
|
return {};
|
|
843
|
-
}
|
|
1191
|
+
};
|
|
844
1192
|
|
|
845
1193
|
// ✅ 正确:提前准备数据
|
|
846
1194
|
headerProcessor: async () => {
|
|
847
1195
|
const cachedData = await cache.get('businessData');
|
|
848
1196
|
return { 'X-Business-Id': cachedData.id };
|
|
849
|
-
}
|
|
1197
|
+
};
|
|
850
1198
|
```
|
|
851
1199
|
|
|
852
1200
|
3. **不要在头预处理器中设置 cookie(H5 平台)**
|
|
@@ -854,13 +1202,13 @@ headerProcessor: async () => {
|
|
|
854
1202
|
```typescript
|
|
855
1203
|
// ❌ 错误:H5 中不生效
|
|
856
1204
|
headerProcessor: async () => {
|
|
857
|
-
return {
|
|
858
|
-
}
|
|
1205
|
+
return { cookie: 'sessionid=xxx' };
|
|
1206
|
+
};
|
|
859
1207
|
|
|
860
1208
|
// ✅ 正确:使用其他方式
|
|
861
1209
|
headerProcessor: async () => {
|
|
862
1210
|
return { 'X-Session-Id': 'xxx' };
|
|
863
|
-
}
|
|
1211
|
+
};
|
|
864
1212
|
```
|
|
865
1213
|
|
|
866
1214
|
## 企业级实战案例
|
|
@@ -917,16 +1265,16 @@ function onErrorHandler(resData: any) {
|
|
|
917
1265
|
|
|
918
1266
|
// 主请求实例(用于常规业务请求)
|
|
919
1267
|
export const requestInstance = new UniRequest({
|
|
920
|
-
timeout: 4000,
|
|
921
|
-
maxRetryCount: 2,
|
|
922
|
-
uploadTimeout: 1000 * 60 * 2,
|
|
923
|
-
onErrorHandler,
|
|
1268
|
+
timeout: 4000, // 请求超时 4 秒
|
|
1269
|
+
maxRetryCount: 2, // 重试 2 次
|
|
1270
|
+
uploadTimeout: 1000 * 60 * 2, // 文件上传超时 2 分钟
|
|
1271
|
+
onErrorHandler, // 使用统一错误处理
|
|
924
1272
|
});
|
|
925
1273
|
|
|
926
1274
|
// AI 请求实例(用于 AI 接口请求)
|
|
927
1275
|
export const aiRequestInstance = new UniRequest({
|
|
928
|
-
uploadTimeout: 1000 * 20,
|
|
929
|
-
maxRetryCount: 0,
|
|
1276
|
+
uploadTimeout: 1000 * 20, // 上传超时 20 秒
|
|
1277
|
+
maxRetryCount: 0, // 不重试(AI 接口可能耗时较长)
|
|
930
1278
|
onErrorHandler,
|
|
931
1279
|
});
|
|
932
1280
|
```
|
|
@@ -947,8 +1295,7 @@ export const ProcessServes = {
|
|
|
947
1295
|
requestInstance.post<{ data: any }>('/process/application/business/readProcessMessage', payload),
|
|
948
1296
|
|
|
949
1297
|
// 查询申请人职位
|
|
950
|
-
getById: (payload: { id: string }) =>
|
|
951
|
-
requestInstance.post('/usercenter/oaUser/getById', payload),
|
|
1298
|
+
getById: (payload: { id: string }) => requestInstance.post('/usercenter/oaUser/getById', payload),
|
|
952
1299
|
|
|
953
1300
|
// 撤回流程
|
|
954
1301
|
processRecall: (payload: { businessId: string; processCategory: string; comment: string }) =>
|
|
@@ -959,8 +1306,7 @@ export const ProcessServes = {
|
|
|
959
1306
|
requestInstance.post<{ data: any[] }>('/process/application/matter/list', payload),
|
|
960
1307
|
|
|
961
1308
|
// 获取事项信息
|
|
962
|
-
getMatterInfo: (payload: any) =>
|
|
963
|
-
requestInstance.post('/process/application/matter/matterInfo', payload),
|
|
1309
|
+
getMatterInfo: (payload: any) => requestInstance.post('/process/application/matter/matterInfo', payload),
|
|
964
1310
|
|
|
965
1311
|
// 流程审批
|
|
966
1312
|
processApprove: (payload: {
|
|
@@ -970,12 +1316,10 @@ export const ProcessServes = {
|
|
|
970
1316
|
comment: string;
|
|
971
1317
|
pass: boolean;
|
|
972
1318
|
attachment: Array<{ name: string; url: string }>;
|
|
973
|
-
}) =>
|
|
974
|
-
requestInstance.post('/process/application/process/approve', payload),
|
|
1319
|
+
}) => requestInstance.post('/process/application/process/approve', payload),
|
|
975
1320
|
|
|
976
1321
|
// 保存并审批
|
|
977
|
-
processSaveAndApprove: (payload: any) =>
|
|
978
|
-
requestInstance.post('/process/application/process/saveAndApprove', payload),
|
|
1322
|
+
processSaveAndApprove: (payload: any) => requestInstance.post('/process/application/process/saveAndApprove', payload),
|
|
979
1323
|
|
|
980
1324
|
// 保存流程
|
|
981
1325
|
processSave: (payload: {
|
|
@@ -988,8 +1332,7 @@ export const ProcessServes = {
|
|
|
988
1332
|
sponsorDepartmentId: string;
|
|
989
1333
|
processCategory: string;
|
|
990
1334
|
businessId?: string;
|
|
991
|
-
}) =>
|
|
992
|
-
requestInstance.post('/process/application/business/save', payload),
|
|
1335
|
+
}) => requestInstance.post('/process/application/business/save', payload),
|
|
993
1336
|
|
|
994
1337
|
// 申请流程
|
|
995
1338
|
processApply: (payload: { businessId: string; ccUser: any[]; processCategory: string }) =>
|
|
@@ -1020,12 +1363,10 @@ export const ProcessServes = {
|
|
|
1020
1363
|
departmentId: string;
|
|
1021
1364
|
subjects: string[];
|
|
1022
1365
|
businessLineUuids: string[];
|
|
1023
|
-
}) =>
|
|
1024
|
-
requestInstance.post('/financial/budgetInventory/budgetInventoryProgress', payload),
|
|
1366
|
+
}) => requestInstance.post('/financial/budgetInventory/budgetInventoryProgress', payload),
|
|
1025
1367
|
|
|
1026
1368
|
// 获取业务线列表
|
|
1027
|
-
getBusinessLineList: () =>
|
|
1028
|
-
requestInstance.post('financial/businessLine/businessLineList'),
|
|
1369
|
+
getBusinessLineList: () => requestInstance.post('financial/businessLine/businessLineList'),
|
|
1029
1370
|
|
|
1030
1371
|
// 获取合同详情
|
|
1031
1372
|
getContractDetail: (payload: { contractNumber: string; businessId: string }) =>
|
|
@@ -1167,6 +1508,7 @@ export default {
|
|
|
1167
1508
|
项目使用两个不同的请求实例:
|
|
1168
1509
|
|
|
1169
1510
|
- **requestInstance**: 用于常规业务请求
|
|
1511
|
+
|
|
1170
1512
|
- 超时时间:4 秒
|
|
1171
1513
|
- 重试次数:2 次
|
|
1172
1514
|
- 适合快速响应的 API
|
|
@@ -1179,6 +1521,7 @@ export default {
|
|
|
1179
1521
|
##### 统一错误处理
|
|
1180
1522
|
|
|
1181
1523
|
所有错误都通过 `onErrorHandler` 函数处理:
|
|
1524
|
+
|
|
1182
1525
|
- 业务错误(errno = 2002):显示错误信息,关闭小程序
|
|
1183
1526
|
- 权限错误(statusCode = 403):引导用户重新登录
|
|
1184
1527
|
- 其他错误:显示通用错误信息
|
|
@@ -1186,6 +1529,7 @@ export default {
|
|
|
1186
1529
|
##### API 层封装
|
|
1187
1530
|
|
|
1188
1531
|
将所有 API 调用封装在专门的服务文件中:
|
|
1532
|
+
|
|
1189
1533
|
- 统一的错误处理
|
|
1190
1534
|
- 类型安全的请求和响应
|
|
1191
1535
|
- 易于测试和维护
|
|
@@ -1201,8 +1545,6 @@ export default {
|
|
|
1201
1545
|
✅ **类型安全** - 充分利用 TypeScript 的泛型机制
|
|
1202
1546
|
✅ **易于扩展** - 新 API 添加无需修改现有代码
|
|
1203
1547
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
1548
|
## 常见问题
|
|
1207
1549
|
|
|
1208
1550
|
### Q: 如何处理跨域问题?
|
|
@@ -1211,7 +1553,7 @@ A: 在 `baseUrl` 中包含代理路径,让后端或开发服务器代理请求
|
|
|
1211
1553
|
|
|
1212
1554
|
```typescript
|
|
1213
1555
|
const request = new UniRequest({
|
|
1214
|
-
baseUrl: 'https://localhost:8080/api/proxy/',
|
|
1556
|
+
baseUrl: 'https://localhost:8080/api/proxy/', // 包含代理路径
|
|
1215
1557
|
});
|
|
1216
1558
|
```
|
|
1217
1559
|
|
|
@@ -1226,11 +1568,93 @@ for (const filePath of filePaths) {
|
|
|
1226
1568
|
}
|
|
1227
1569
|
|
|
1228
1570
|
// 方式 2:并行上传(不推荐,可能导致超时)
|
|
1229
|
-
await Promise.all(
|
|
1230
|
-
filePaths.map(filePath => request.uploadFile('/upload', filePath))
|
|
1231
|
-
);
|
|
1571
|
+
await Promise.all(filePaths.map((filePath) => request.uploadFile('/upload', filePath)));
|
|
1232
1572
|
```
|
|
1233
1573
|
|
|
1574
|
+
### Q: 如何下载多个文件?
|
|
1575
|
+
|
|
1576
|
+
A: 需要多次调用 `downloadFile`,建议使用串行下载避免超时:
|
|
1577
|
+
|
|
1578
|
+
```typescript
|
|
1579
|
+
// 方式 1:顺序下载(推荐)
|
|
1580
|
+
const filePaths = [];
|
|
1581
|
+
for (const fileUrl of fileUrls) {
|
|
1582
|
+
const path = await request.downloadFile(fileUrl);
|
|
1583
|
+
filePaths.push(path);
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
// 方式 2:并行下载(需谨慎,可能导致超时)
|
|
1587
|
+
const filePaths = await Promise.all(fileUrls.map((fileUrl) => request.downloadFile(fileUrl)));
|
|
1588
|
+
```
|
|
1589
|
+
|
|
1590
|
+
### Q: 下载的文件如何持久化?
|
|
1591
|
+
|
|
1592
|
+
A: 使用 `uni.saveFile()` 将临时文件保存到永久目录:
|
|
1593
|
+
|
|
1594
|
+
```typescript
|
|
1595
|
+
const tempFilePath = await request.downloadFile('/file.pdf');
|
|
1596
|
+
|
|
1597
|
+
// 保存到永久位置
|
|
1598
|
+
uni.saveFile({
|
|
1599
|
+
tempFilePath: tempFilePath,
|
|
1600
|
+
success: (res) => {
|
|
1601
|
+
console.log('文件已保存:', res.savedFilePath);
|
|
1602
|
+
},
|
|
1603
|
+
});
|
|
1604
|
+
```
|
|
1605
|
+
|
|
1606
|
+
### Q: 下载大文件时超时怎么办?
|
|
1607
|
+
|
|
1608
|
+
A: 增加 `downloadTimeout` 的值:
|
|
1609
|
+
|
|
1610
|
+
```typescript
|
|
1611
|
+
const request = new UniRequest({
|
|
1612
|
+
baseUrl: 'https://api.example.com',
|
|
1613
|
+
downloadTimeout: 60000, // 60 秒,适合大文件
|
|
1614
|
+
});
|
|
1615
|
+
|
|
1616
|
+
// 或在运行时修改
|
|
1617
|
+
request.setParams({
|
|
1618
|
+
downloadTimeout: 120000, // 120 秒
|
|
1619
|
+
});
|
|
1620
|
+
```
|
|
1621
|
+
|
|
1622
|
+
### Q: 如何取消正在进行的下载?
|
|
1623
|
+
|
|
1624
|
+
A: 目前库返回的是 Promise,无法直接获取 DownloadTask 来调用 abort。建议通过以下方式实现:
|
|
1625
|
+
|
|
1626
|
+
```typescript
|
|
1627
|
+
// 方式 1:修改库的源码返回 DownloadTask(高级用法)
|
|
1628
|
+
// 方式 2:使用超时机制让请求自动中止
|
|
1629
|
+
// 方式 3:在下载进度回调中检查用户是否点击了取消
|
|
1630
|
+
|
|
1631
|
+
// 推荐:在进度回调中检查状态
|
|
1632
|
+
let shouldCancel = false;
|
|
1633
|
+
|
|
1634
|
+
await request.downloadFile('/file.zip', undefined, undefined, (progress) => {
|
|
1635
|
+
if (shouldCancel) {
|
|
1636
|
+
// 无法直接中止,但可以停止处理
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
console.log(`下载进度: ${progress.progress}%`);
|
|
1640
|
+
});
|
|
1641
|
+
|
|
1642
|
+
// 用户点击取消按钮时
|
|
1643
|
+
function cancelDownload() {
|
|
1644
|
+
shouldCancel = true;
|
|
1645
|
+
}
|
|
1646
|
+
```
|
|
1647
|
+
|
|
1648
|
+
### Q: 下载的文件在哪里?
|
|
1649
|
+
|
|
1650
|
+
A: 下载的文件保存在以下位置:
|
|
1651
|
+
|
|
1652
|
+
- **H5**: 浏览器默认下载目录
|
|
1653
|
+
- **小程序**: 临时目录(`wx.env.USER_DATA_PATH` 下),或指定的 `filePath`
|
|
1654
|
+
- **App**: 应用的缓存目录
|
|
1655
|
+
|
|
1656
|
+
建议下载后立即使用或保存,避免应用被清理时文件丢失。
|
|
1657
|
+
|
|
1234
1658
|
### Q: 如何处理 token 过期?
|
|
1235
1659
|
|
|
1236
1660
|
A: 库会自动在收到 403 状态码时获取新 token 并重试。如需自定义逻辑:
|
|
@@ -1255,16 +1679,45 @@ A: 设置 `maxRetryCount` 为 0。
|
|
|
1255
1679
|
|
|
1256
1680
|
```typescript
|
|
1257
1681
|
const request = new UniRequest({
|
|
1258
|
-
maxRetryCount: 0,
|
|
1682
|
+
maxRetryCount: 0, // 禁用重试
|
|
1259
1683
|
});
|
|
1260
1684
|
```
|
|
1261
1685
|
|
|
1262
1686
|
### Q: headerProcessor 每次都会执行吗?
|
|
1263
1687
|
|
|
1264
|
-
A:
|
|
1688
|
+
A: 每次调用 `request` / `get` / `post` 等接口时都会执行一次;如果该次请求触发自动重试,会复用第一次处理后的 header,不会重复执行。这可以保证同一次业务请求在重试时复用同一个幂等 header。唯一例外是 `403` 刷新 token 成功后,SDK 会基于新 token 重新生成一次 header,再立即重试。
|
|
1265
1689
|
|
|
1266
1690
|
## 版本历史
|
|
1267
1691
|
|
|
1692
|
+
### v1.6.2(最新)
|
|
1693
|
+
|
|
1694
|
+
- ✨ **新增** 文件下载功能 (`downloadFile` 方法)
|
|
1695
|
+
|
|
1696
|
+
- 支持下载文件到本地目录或临时目录
|
|
1697
|
+
- 支持实时下载进度监听
|
|
1698
|
+
- 支持自定义请求头
|
|
1699
|
+
- 自动继承 token、重试等企业级特性
|
|
1700
|
+
- 默认超时时间 30 秒(可配置)
|
|
1701
|
+
|
|
1702
|
+
- 📝 **文档改进** 完整的 JSDoc 注释
|
|
1703
|
+
|
|
1704
|
+
- 所有公开/私有方法添加详细 JSDoc
|
|
1705
|
+
- 所有参数和返回值有清晰说明
|
|
1706
|
+
- 关键方法包含使用示例
|
|
1707
|
+
- 100% 注释覆盖率
|
|
1708
|
+
|
|
1709
|
+
- 📚 **README 更新**
|
|
1710
|
+
|
|
1711
|
+
- 新增《文件下载》文档章节
|
|
1712
|
+
- 新增文件下载的详细使用示例(示例 4-2、4-3)
|
|
1713
|
+
- 更新目录索引
|
|
1714
|
+
- 完善参数和平台兼容性说明
|
|
1715
|
+
|
|
1716
|
+
- 🔧 **技术改进**
|
|
1717
|
+
- 优化 TypeScript 类型定义
|
|
1718
|
+
- 完善错误处理机制
|
|
1719
|
+
- 增强跨平台兼容性检查
|
|
1720
|
+
|
|
1268
1721
|
### v1.4.11
|
|
1269
1722
|
|
|
1270
1723
|
- ✨ **新增** 请求头预处理器功能 (`headerProcessor`)
|