@base-web-kits/base-tools-web 1.1.18-alpha.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -39,22 +39,24 @@ export type RequestConfigBase<D extends RequestData = RequestData> = {
39
39
  successCode: (number | string)[];
40
40
  /** 登录过期状态码 */
41
41
  reloginCode: (number | string)[];
42
- /** 是否显示进度条: 支持字符串,自定义文本 (默认true) */
43
- showLoading?: boolean | string;
44
- /** 是否提示接口异常 (默认true) */
45
- toastError?: boolean;
46
- /** 是否输出日志 (默认true) */
47
- isLog?: boolean;
48
- /** 额外输出的日志数据 */
49
- extraLog?: Record<string, unknown>;
50
- /** 响应数据的缓存时间, 单位毫秒。仅在成功时缓存;仅缓存在内存,应用退出,缓存消失。(默认0,不开启缓存) */
51
- cacheTime?: number;
52
42
  /** 是否开启流式传输 (如 SSE) */
53
43
  enableChunked?: boolean;
54
44
  /** 响应类型 (默认 json, enableChunked为true时忽略) */
55
45
  responseType?: 'text' | 'arraybuffer' | 'json';
46
+ /** 响应数据的缓存时间, 单位毫秒。仅在成功时缓存;仅缓存在内存,应用退出,缓存消失。(默认0,不开启缓存) */
47
+ cacheTime?: number;
48
+ /** 是否提示接口异常 (默认true) */
49
+ toastError?: boolean;
50
+ /** 是否显示进度条: 支持字符串,自定义文本 (默认true) */
51
+ showLoading?: boolean | string;
52
+ /** 是否输出日志 (默认true) */
53
+ showLog?: boolean;
54
+ /** 成功和失败时,额外输出的日志数据 (可覆盖内部log参数,如'name') */
55
+ logExtra?: Record<string, unknown>;
56
56
  /** 响应拦截 */
57
- responseInterceptor?: (data: ResponseData) => ResponseData;
57
+ resMap?: (data: ResponseData) => ResponseData;
58
+ /** 获取task对象, 用于取消请求或监听流式数据 */
59
+ onTaskReady?: (task: RequestTask) => void;
58
60
  };
59
61
  /**
60
62
  * 请求任务对象 (用于取消请求或监听流式数据)
@@ -77,7 +79,6 @@ export type ChunkCallback = (response: {
77
79
  * 基础请求 (返回 Promise 和 Task 对象)
78
80
  * 基于 fetch API 封装,支持流式请求
79
81
  * @param config 请求配置
80
- * @returns Promise<T> & { task?: RequestTask }
81
82
  * @example
82
83
  * // 在入口文件完成配置 (确保请求失败有toast提示,登录过期能够触发重新登录,log有日志输出)
83
84
  * setBaseToolsConfig({
@@ -100,7 +101,7 @@ export type ChunkCallback = (response: {
100
101
  * export function requestApi<T>(config: RequestConfig) {
101
102
  * return request<T>({
102
103
  * header: { token: 'xx', version: 'xx', tid: 'xx' }, // 会自动过滤空值
103
- * // responseInterceptor: (res) => res, // 响应拦截,可预处理响应数据,如解密 (可选)
104
+ * // resMap: (res) => res, // 响应拦截,可预处理响应数据,如解密 (可选)
104
105
  * resKey: 'data',
105
106
  * msgKey: 'message',
106
107
  * codeKey: 'status',
@@ -122,32 +123,36 @@ export type ChunkCallback = (response: {
122
123
  * return requestApi<GoodItem[]>({ url: '/goods/list', resKey: 'data.list', ...config });
123
124
  * }
124
125
  *
125
- * const goodList = await apiGoodList({ data: { page:1, size:10 }, showLoading: false });
126
+ * const goodList = await apiGoodList({ data: { page:1, size:10 } });
126
127
  *
127
128
  * // 3. 基于上面 requestApi 的流式接口
128
- * export function apiChatStream(data: { question: string }) {
129
- * return requestApi<T>({
129
+ * export function apiChatStream(config: RequestConfig) {
130
+ * return requestApi({
131
+ * ...config,
130
132
  * url: '/sse/chatStream',
131
- * data,
132
133
  * resKey: false,
133
134
  * showLoading: false,
134
- * responseType: 'arraybuffer',
135
- * enableChunked: true,
135
+ * responseType: 'arraybuffer', // 流式响应类型
136
+ * enableChunked: true, // 开启分块传输
136
137
  * });
137
138
  * }
138
139
  *
139
- * const { task } = apiChatStream({question: '你好'}); // 发起流式请求
140
+ * // 流式监听
141
+ * const onTaskReady = (task: RequestTask) => {
142
+ * task.onChunkReceived((res) => {
143
+ * console.log('ArrayBuffer', res.data);
144
+ * });
145
+ * }
140
146
  *
141
- * task.onChunkReceived((res) => {
142
- * console.log('ArrayBuffer', res.data); // 接收流式数据
143
- * });
147
+ * // 流式发起
148
+ * const data: ChatData = { content: '你好', conversationId: 123 };
149
+ * await apiChatStream({ data, onTaskReady });
144
150
  *
145
- * task.offChunkReceived(); // 取消监听,中断流式接收 (调用时机:流式结束,组件销毁,页面关闭)
146
- * task.abort(); // 取消请求 (若流式传输中,会中断流并抛出异常)
151
+ * // 流式取消 (在组件销毁或页面关闭时调用)
152
+ * task?.offChunkReceived(); // 取消监听,中断流式接收
153
+ * task?.abort(); // 取消请求 (若流式已生成,此时abort无效,因为请求已成功)
147
154
  */
148
- export declare function request<T, D extends RequestData = RequestData>(config: RequestConfigBase<D>): Promise<T> & {
149
- task?: RequestTask;
150
- };
155
+ export declare function request<T, D extends RequestData = RequestData>(config: RequestConfigBase<D>): Promise<T>;
151
156
  /**
152
157
  * 参数过滤undefined, 避免接口处理异常 (不可过滤 null 、 "" 、 false 、 0 这些有效值)
153
158
  */
@@ -1 +1 @@
1
- {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/web/network/request.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/web/network/request.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,7 @@
1
1
  /**
2
2
  * 写入 localStorage(自动 JSON 序列化)
3
- * 当 `value` 为 `null` 或 `undefined` 时,会移除该键。
4
- * 支持保存:对象、数组、字符串、数字、布尔值。
5
3
  * @param key 键名
6
- * @param value 任意可序列化的值
4
+ * @param value 任意可序列化的值:对象、数组、字符串、数字、布尔值。(`null` 或 `undefined` 会自动移除该键)
7
5
  * @param days 过期天数(从当前时间起算)
8
6
  * @example
9
7
  * setLocalStorage('user', { id: 1, name: 'Alice' }); // 对象
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/web/storage/index.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,QAiBzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAiBlE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,QAE7C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/web/storage/index.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,QAiBzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAiBlE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,QAE7C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base-web-kits/base-tools-web",
3
- "version": "1.1.18-alpha.1",
3
+ "version": "1.2.0",
4
4
  "sideEffects": false,
5
5
  "description": "Independent Web utilities package built from src/web.",
6
6
  "keywords": [
@@ -25,7 +25,7 @@
25
25
  "src"
26
26
  ],
27
27
  "dependencies": {
28
- "@base-web-kits/base-tools-ts": "^1.1.11"
28
+ "@base-web-kits/base-tools-ts": "^1.2.0"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "axios": ">=0.18.0"
@@ -1,4 +1,6 @@
1
+ import { cloneDeep } from '@base-web-kits/base-tools-ts';
1
2
  import { getBaseToolsConfig } from '../index';
3
+ import type { AppLogInfo } from '../index';
2
4
 
3
5
  type WebApi<Option = any, Res = any, Config = any> = (
4
6
  option: Option,
@@ -20,6 +22,12 @@ export type WebApiConfig<Res = any, Err = any> = {
20
22
 
21
23
  /** 是否显示日志, 默认 true */
22
24
  showLog?: boolean;
25
+
26
+ /** 成功响应数据的处理, 如解密操作 (返回值在成功日志中输出'resMap'字段) */
27
+ resMap?: (res: any) => Res;
28
+
29
+ /** 成功和失败时,额外输出的日志数据 (可覆盖内部log参数,如'name') */
30
+ logExtra?: Record<string, unknown>;
23
31
  };
24
32
 
25
33
  /**
@@ -42,7 +50,10 @@ export function enhanceWebApi<Option = any, Res = any, Err = any, Config = any>(
42
50
  toastSuccess = false,
43
51
  toastError = true,
44
52
  showLog = true,
53
+ resMap,
54
+ logExtra,
45
55
  } = finalConfig;
56
+
46
57
  const {
47
58
  log,
48
59
  toast,
@@ -60,15 +71,30 @@ export function enhanceWebApi<Option = any, Res = any, Err = any, Config = any>(
60
71
  webApi(option, finalConfig)
61
72
  .then((res) => {
62
73
  if (showLoading) hideLoadingFn?.();
63
- if (showLog) log?.('info', { name: fname, status: 'success', option, res });
64
- resolve(res);
65
74
 
66
- const msg = typeof toastSuccess === 'function' ? toastSuccess(res) : toastSuccess;
75
+ const finalRes = resMap ? resMap(res) : res;
76
+
77
+ if (showLog) {
78
+ const logData: AppLogInfo = { name: fname, status: 'success', option, ...logExtra };
79
+
80
+ if (resMap) {
81
+ logData.res = res; // 输出原始数据
82
+ logData.resMap = cloneDeep(finalRes); // 深拷贝处理后数据,避免外部修改对象,造成输出不一致
83
+ } else {
84
+ logData.res = cloneDeep(res); // 深拷贝原始数据,避免外部修改对象,造成输出不一致
85
+ }
86
+
87
+ log?.('info', logData);
88
+ }
89
+
90
+ resolve(finalRes);
91
+
92
+ const msg = typeof toastSuccess === 'function' ? toastSuccess(finalRes) : toastSuccess;
67
93
  if (msg) toast?.({ msg, status: 'success' });
68
94
  })
69
95
  .catch((e) => {
70
96
  if (showLoading) hideLoadingFn?.();
71
- if (showLog) log?.('error', { name: fname, status: 'fail', option, e });
97
+ if (showLog) log?.('error', { name: fname, status: 'fail', option, e, ...logExtra });
72
98
 
73
99
  const msg = typeof toastError === 'function' ? toastError(e) : toastError;
74
100
  if (msg) {
@@ -52,12 +52,16 @@ export type RequestConfig<D extends RequestData = RequestData> = Partial<Request
52
52
  export type RequestConfigBase<D extends RequestData = RequestData> = {
53
53
  /** 接口地址 */
54
54
  url: string;
55
+
55
56
  /** 请求方法 */
56
57
  method?: RequestMethod;
58
+
57
59
  /** 请求头(会自动过滤undefined, null, "";不过滤0和false; 数字和布尔值会自动转换为字符串) */
58
60
  header?: Record<string, string | number | boolean | null | undefined>;
61
+
59
62
  /** 请求参数 */
60
63
  data?: D;
64
+
61
65
  /** 超时时间 (毫秒), 默认 60000 */
62
66
  timeout?: number;
63
67
 
@@ -79,29 +83,32 @@ export type RequestConfigBase<D extends RequestData = RequestData> = {
79
83
  /** 登录过期状态码 */
80
84
  reloginCode: (number | string)[];
81
85
 
82
- /** 是否显示进度条: 支持字符串,自定义文本 (默认true) */
83
- showLoading?: boolean | string;
84
-
85
- /** 是否提示接口异常 (默认true) */
86
- toastError?: boolean;
87
-
88
- /** 是否输出日志 (默认true) */
89
- isLog?: boolean;
86
+ /** 是否开启流式传输 (如 SSE) */
87
+ enableChunked?: boolean;
90
88
 
91
- /** 额外输出的日志数据 */
92
- extraLog?: Record<string, unknown>;
89
+ /** 响应类型 (默认 json, enableChunked为true时忽略) */
90
+ responseType?: 'text' | 'arraybuffer' | 'json';
93
91
 
94
92
  /** 响应数据的缓存时间, 单位毫秒。仅在成功时缓存;仅缓存在内存,应用退出,缓存消失。(默认0,不开启缓存) */
95
93
  cacheTime?: number;
96
94
 
97
- /** 是否开启流式传输 (如 SSE) */
98
- enableChunked?: boolean;
95
+ /** 是否提示接口异常 (默认true) */
96
+ toastError?: boolean;
99
97
 
100
- /** 响应类型 (默认 json, enableChunked为true时忽略) */
101
- responseType?: 'text' | 'arraybuffer' | 'json';
98
+ /** 是否显示进度条: 支持字符串,自定义文本 (默认true) */
99
+ showLoading?: boolean | string;
100
+
101
+ /** 是否输出日志 (默认true) */
102
+ showLog?: boolean;
103
+
104
+ /** 成功和失败时,额外输出的日志数据 (可覆盖内部log参数,如'name') */
105
+ logExtra?: Record<string, unknown>;
102
106
 
103
107
  /** 响应拦截 */
104
- responseInterceptor?: (data: ResponseData) => ResponseData;
108
+ resMap?: (data: ResponseData) => ResponseData;
109
+
110
+ /** 获取task对象, 用于取消请求或监听流式数据 */
111
+ onTaskReady?: (task: RequestTask) => void;
105
112
  };
106
113
 
107
114
  /**
@@ -130,7 +137,6 @@ const requestCache = new Map<string, { res: unknown; expire: number }>();
130
137
  * 基础请求 (返回 Promise 和 Task 对象)
131
138
  * 基于 fetch API 封装,支持流式请求
132
139
  * @param config 请求配置
133
- * @returns Promise<T> & { task?: RequestTask }
134
140
  * @example
135
141
  * // 在入口文件完成配置 (确保请求失败有toast提示,登录过期能够触发重新登录,log有日志输出)
136
142
  * setBaseToolsConfig({
@@ -153,7 +159,7 @@ const requestCache = new Map<string, { res: unknown; expire: number }>();
153
159
  * export function requestApi<T>(config: RequestConfig) {
154
160
  * return request<T>({
155
161
  * header: { token: 'xx', version: 'xx', tid: 'xx' }, // 会自动过滤空值
156
- * // responseInterceptor: (res) => res, // 响应拦截,可预处理响应数据,如解密 (可选)
162
+ * // resMap: (res) => res, // 响应拦截,可预处理响应数据,如解密 (可选)
157
163
  * resKey: 'data',
158
164
  * msgKey: 'message',
159
165
  * codeKey: 'status',
@@ -175,30 +181,57 @@ const requestCache = new Map<string, { res: unknown; expire: number }>();
175
181
  * return requestApi<GoodItem[]>({ url: '/goods/list', resKey: 'data.list', ...config });
176
182
  * }
177
183
  *
178
- * const goodList = await apiGoodList({ data: { page:1, size:10 }, showLoading: false });
184
+ * const goodList = await apiGoodList({ data: { page:1, size:10 } });
179
185
  *
180
186
  * // 3. 基于上面 requestApi 的流式接口
181
- * export function apiChatStream(data: { question: string }) {
182
- * return requestApi<T>({
187
+ * export function apiChatStream(config: RequestConfig) {
188
+ * return requestApi({
189
+ * ...config,
183
190
  * url: '/sse/chatStream',
184
- * data,
185
191
  * resKey: false,
186
192
  * showLoading: false,
187
- * responseType: 'arraybuffer',
188
- * enableChunked: true,
193
+ * responseType: 'arraybuffer', // 流式响应类型
194
+ * enableChunked: true, // 开启分块传输
189
195
  * });
190
196
  * }
191
197
  *
192
- * const { task } = apiChatStream({question: '你好'}); // 发起流式请求
198
+ * // 流式监听
199
+ * const onTaskReady = (task: RequestTask) => {
200
+ * task.onChunkReceived((res) => {
201
+ * console.log('ArrayBuffer', res.data);
202
+ * });
203
+ * }
193
204
  *
194
- * task.onChunkReceived((res) => {
195
- * console.log('ArrayBuffer', res.data); // 接收流式数据
196
- * });
205
+ * // 流式发起
206
+ * const data: ChatData = { content: '你好', conversationId: 123 };
207
+ * await apiChatStream({ data, onTaskReady });
197
208
  *
198
- * task.offChunkReceived(); // 取消监听,中断流式接收 (调用时机:流式结束,组件销毁,页面关闭)
199
- * task.abort(); // 取消请求 (若流式传输中,会中断流并抛出异常)
209
+ * // 流式取消 (在组件销毁或页面关闭时调用)
210
+ * task?.offChunkReceived(); // 取消监听,中断流式接收
211
+ * task?.abort(); // 取消请求 (若流式已生成,此时abort无效,因为请求已成功)
200
212
  */
201
213
  export function request<T, D extends RequestData = RequestData>(config: RequestConfigBase<D>) {
214
+ const {
215
+ url,
216
+ data,
217
+ header,
218
+ method = 'GET',
219
+ resKey,
220
+ msgKey,
221
+ codeKey,
222
+ successKey,
223
+ successCode,
224
+ reloginCode,
225
+ showLoading = true,
226
+ toastError = true,
227
+ enableChunked = false,
228
+ cacheTime,
229
+ resMap,
230
+ responseType = 'json',
231
+ timeout = 60000,
232
+ onTaskReady,
233
+ } = config;
234
+
202
235
  // 1. 初始化控制对象
203
236
  const controller = new AbortController();
204
237
  const signal = controller.signal;
@@ -214,30 +247,11 @@ export function request<T, D extends RequestData = RequestData>(config: RequestC
214
247
  chunkCallback = null;
215
248
  },
216
249
  };
250
+ onTaskReady?.(task);
217
251
 
218
252
  // 2. 创建 Promise
219
- const promise = new Promise<T>((resolve, reject) => {
253
+ return new Promise<T>((resolve, reject) => {
220
254
  const execute = async () => {
221
- const {
222
- url,
223
- data,
224
- header,
225
- method = 'GET',
226
- resKey,
227
- msgKey,
228
- codeKey,
229
- successKey,
230
- successCode,
231
- reloginCode,
232
- showLoading = true,
233
- toastError = true,
234
- enableChunked = false,
235
- cacheTime,
236
- responseInterceptor,
237
- responseType = 'json',
238
- timeout = 60000,
239
- } = config;
240
-
241
255
  const isGet = method === 'GET';
242
256
  const isObjectData = isPlainObject(data);
243
257
  const isArrayData = !isObjectData && Array.isArray(data);
@@ -349,7 +363,7 @@ export function request<T, D extends RequestData = RequestData>(config: RequestC
349
363
  if (showLoading) appConfig.hideLoading?.();
350
364
 
351
365
  // 响应拦截
352
- const res = responseInterceptor ? responseInterceptor(resData) : resData;
366
+ const res = resMap ? resMap(resData) : resData;
353
367
 
354
368
  // 2.10 业务状态码解析
355
369
  const code = getObjectValue(res, codeKey);
@@ -395,12 +409,7 @@ export function request<T, D extends RequestData = RequestData>(config: RequestC
395
409
  };
396
410
 
397
411
  execute();
398
- }) as Promise<T> & { task?: RequestTask };
399
-
400
- // 3. 挂载 Task
401
- promise.task = task;
402
-
403
- return promise;
412
+ });
404
413
  }
405
414
 
406
415
  /**
@@ -439,12 +448,12 @@ function logRequestInfo(options: {
439
448
  e?: unknown;
440
449
  }) {
441
450
  const { log } = getBaseToolsConfig();
442
- const { isLog = true } = options.config;
451
+ const { showLog = true } = options.config;
443
452
 
444
- if (!log || !isLog) return;
453
+ if (!log || !showLog) return;
445
454
 
446
455
  const { config, res, fromCache = false, startTime, status, e } = options;
447
- const { url, data, header, method, extraLog } = config;
456
+ const { url, data, header, method, logExtra } = config;
448
457
  const endTime = Date.now();
449
458
  const fmt = 'YYYY-MM-DD HH:mm:ss.SSS';
450
459
 
@@ -459,7 +468,7 @@ function logRequestInfo(options: {
459
468
  startTime: toDayjs(startTime).format(fmt),
460
469
  endTime: toDayjs(endTime).format(fmt),
461
470
  duration: endTime - startTime,
462
- ...extraLog,
471
+ ...logExtra,
463
472
  };
464
473
 
465
474
  if (status === 'success') {
@@ -6,10 +6,8 @@ const WK = {
6
6
 
7
7
  /**
8
8
  * 写入 localStorage(自动 JSON 序列化)
9
- * 当 `value` 为 `null` 或 `undefined` 时,会移除该键。
10
- * 支持保存:对象、数组、字符串、数字、布尔值。
11
9
  * @param key 键名
12
- * @param value 任意可序列化的值
10
+ * @param value 任意可序列化的值:对象、数组、字符串、数字、布尔值。(`null` 或 `undefined` 会自动移除该键)
13
11
  * @param days 过期天数(从当前时间起算)
14
12
  * @example
15
13
  * setLocalStorage('user', { id: 1, name: 'Alice' }); // 对象