@lark-apaas/client-toolkit 1.0.9 → 1.0.11
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/lib/components/AppContainer/api-proxy/core.d.ts +15 -42
- package/lib/components/AppContainer/api-proxy/core.js +43 -67
- package/lib/components/User/UserDisplay.d.ts +1 -0
- package/lib/components/User/UserDisplay.js +2 -1
- package/lib/components/User/UserWithAvatar.d.ts +1 -1
- package/lib/components/User/UserWithAvatar.js +2 -2
- package/lib/components/User/type.d.ts +4 -0
- package/lib/logger/batch-logger.js +1 -1
- package/package.json +1 -1
|
@@ -35,6 +35,10 @@ export interface ApiResponse<T = any> {
|
|
|
35
35
|
status: number;
|
|
36
36
|
/** 状态文本 */
|
|
37
37
|
statusText: string;
|
|
38
|
+
/** 请求是否成功 (2xx 状态码) */
|
|
39
|
+
success: boolean;
|
|
40
|
+
/** 响应头(序列化为普通对象,可通过 postMessage 传递) */
|
|
41
|
+
headers?: Record<string, string>;
|
|
38
42
|
}
|
|
39
43
|
/**
|
|
40
44
|
* API 错误类
|
|
@@ -45,39 +49,28 @@ export declare class ApiError extends Error {
|
|
|
45
49
|
response?: Response;
|
|
46
50
|
config?: RequestConfig;
|
|
47
51
|
code?: string;
|
|
48
|
-
|
|
52
|
+
/** 业务错误响应数据 */
|
|
53
|
+
responseData?: any;
|
|
54
|
+
constructor(message: string, config?: RequestConfig, response?: Response, code?: string, responseData?: any);
|
|
55
|
+
/**
|
|
56
|
+
* 序列化为可传递的 JSON 对象(用于 postMessage 等场景)
|
|
57
|
+
* 移除不可序列化的对象(如 Response、Request 等)
|
|
58
|
+
*/
|
|
59
|
+
toJSON(): object;
|
|
49
60
|
}
|
|
50
|
-
/**
|
|
51
|
-
* 请求拦截器类型
|
|
52
|
-
*/
|
|
53
|
-
export type RequestInterceptor = (config: RequestConfig) => RequestConfig | Promise<RequestConfig>;
|
|
54
|
-
/**
|
|
55
|
-
* 响应拦截器类型
|
|
56
|
-
*/
|
|
57
|
-
export type ResponseInterceptor = <T = any>(response: ApiResponse<T>) => ApiResponse<T> | Promise<ApiResponse<T>>;
|
|
58
|
-
/**
|
|
59
|
-
* 错误拦截器类型
|
|
60
|
-
*/
|
|
61
|
-
export type ErrorInterceptor = (error: ApiError) => any;
|
|
62
61
|
/**
|
|
63
62
|
* 通用接口测试 API 代理类
|
|
64
63
|
*
|
|
65
64
|
* 特性:
|
|
66
65
|
* - 支持所有 RESTful 请求方法 (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
|
|
67
66
|
* - 完善的错误处理和边界检查
|
|
68
|
-
* - 请求/响应拦截器
|
|
69
67
|
* - 超时控制和请求取消
|
|
70
68
|
* - 支持多种响应类型
|
|
69
|
+
* - 所有响应数据可序列化(可通过 postMessage 传递)
|
|
71
70
|
*/
|
|
72
71
|
declare class ApiProxy {
|
|
73
72
|
/** 默认配置 */
|
|
74
73
|
private defaultConfig;
|
|
75
|
-
/** 请求拦截器队列 */
|
|
76
|
-
private requestInterceptors;
|
|
77
|
-
/** 响应拦截器队列 */
|
|
78
|
-
private responseInterceptors;
|
|
79
|
-
/** 错误拦截器队列 */
|
|
80
|
-
private errorInterceptors;
|
|
81
74
|
/** 活跃的请求控制器 Map */
|
|
82
75
|
private activeRequests;
|
|
83
76
|
/**
|
|
@@ -85,18 +78,6 @@ declare class ApiProxy {
|
|
|
85
78
|
* @param baseConfig 基础配置
|
|
86
79
|
*/
|
|
87
80
|
constructor(baseConfig?: Partial<RequestConfig>);
|
|
88
|
-
/**
|
|
89
|
-
* 添加请求拦截器
|
|
90
|
-
*/
|
|
91
|
-
useRequestInterceptor(interceptor: RequestInterceptor): void;
|
|
92
|
-
/**
|
|
93
|
-
* 添加响应拦截器
|
|
94
|
-
*/
|
|
95
|
-
useResponseInterceptor(interceptor: ResponseInterceptor): void;
|
|
96
|
-
/**
|
|
97
|
-
* 添加错误拦截器
|
|
98
|
-
*/
|
|
99
|
-
useErrorInterceptor(interceptor: ErrorInterceptor): void;
|
|
100
81
|
/**
|
|
101
82
|
* 验证 URL 格式
|
|
102
83
|
*/
|
|
@@ -114,17 +95,9 @@ declare class ApiProxy {
|
|
|
114
95
|
*/
|
|
115
96
|
private parseResponse;
|
|
116
97
|
/**
|
|
117
|
-
*
|
|
118
|
-
*/
|
|
119
|
-
private runRequestInterceptors;
|
|
120
|
-
/**
|
|
121
|
-
* 执行响应拦截器
|
|
122
|
-
*/
|
|
123
|
-
private runResponseInterceptors;
|
|
124
|
-
/**
|
|
125
|
-
* 执行错误拦截器
|
|
98
|
+
* 将 Headers 对象序列化为普通对象(可通过 postMessage 传递)
|
|
126
99
|
*/
|
|
127
|
-
private
|
|
100
|
+
private serializeHeaders;
|
|
128
101
|
/**
|
|
129
102
|
* 生成请求唯一键
|
|
130
103
|
*/
|
|
@@ -4,7 +4,8 @@ class ApiError extends Error {
|
|
|
4
4
|
response;
|
|
5
5
|
config;
|
|
6
6
|
code;
|
|
7
|
-
|
|
7
|
+
responseData;
|
|
8
|
+
constructor(message, config, response, code, responseData){
|
|
8
9
|
super(message);
|
|
9
10
|
this.name = 'ApiError';
|
|
10
11
|
this.config = config;
|
|
@@ -12,8 +13,27 @@ class ApiError extends Error {
|
|
|
12
13
|
this.status = response?.status;
|
|
13
14
|
this.statusText = response?.statusText;
|
|
14
15
|
this.code = code;
|
|
16
|
+
this.responseData = responseData;
|
|
15
17
|
Object.setPrototypeOf(this, ApiError.prototype);
|
|
16
18
|
}
|
|
19
|
+
toJSON() {
|
|
20
|
+
return {
|
|
21
|
+
name: this.name,
|
|
22
|
+
message: this.message,
|
|
23
|
+
code: this.code,
|
|
24
|
+
status: this.status,
|
|
25
|
+
statusText: this.statusText,
|
|
26
|
+
responseData: this.responseData,
|
|
27
|
+
config: this.config ? {
|
|
28
|
+
url: this.config.url,
|
|
29
|
+
method: this.config.method,
|
|
30
|
+
headers: this.config.headers,
|
|
31
|
+
params: this.config.params,
|
|
32
|
+
timeout: this.config.timeout,
|
|
33
|
+
responseType: this.config.responseType
|
|
34
|
+
} : void 0
|
|
35
|
+
};
|
|
36
|
+
}
|
|
17
37
|
}
|
|
18
38
|
class ApiProxy {
|
|
19
39
|
defaultConfig = {
|
|
@@ -23,12 +43,9 @@ class ApiProxy {
|
|
|
23
43
|
headers: {
|
|
24
44
|
'Content-Type': 'application/json',
|
|
25
45
|
'X-Suda-Csrf-Token': window.csrfToken || '',
|
|
26
|
-
'Cache-Control': 'no-store'
|
|
46
|
+
'Cache-Control': 'no-store, no-cache'
|
|
27
47
|
}
|
|
28
48
|
};
|
|
29
|
-
requestInterceptors = [];
|
|
30
|
-
responseInterceptors = [];
|
|
31
|
-
errorInterceptors = [];
|
|
32
49
|
activeRequests = new Map();
|
|
33
50
|
constructor(baseConfig){
|
|
34
51
|
if (baseConfig) this.defaultConfig = {
|
|
@@ -36,18 +53,6 @@ class ApiProxy {
|
|
|
36
53
|
...baseConfig
|
|
37
54
|
};
|
|
38
55
|
}
|
|
39
|
-
useRequestInterceptor(interceptor) {
|
|
40
|
-
if ('function' != typeof interceptor) throw new TypeError('Request interceptor must be a function');
|
|
41
|
-
this.requestInterceptors.push(interceptor);
|
|
42
|
-
}
|
|
43
|
-
useResponseInterceptor(interceptor) {
|
|
44
|
-
if ('function' != typeof interceptor) throw new TypeError('Response interceptor must be a function');
|
|
45
|
-
this.responseInterceptors.push(interceptor);
|
|
46
|
-
}
|
|
47
|
-
useErrorInterceptor(interceptor) {
|
|
48
|
-
if ('function' != typeof interceptor) throw new TypeError('Error interceptor must be a function');
|
|
49
|
-
this.errorInterceptors.push(interceptor);
|
|
50
|
-
}
|
|
51
56
|
validateUrl(url) {
|
|
52
57
|
if (!url || 'string' != typeof url) return false;
|
|
53
58
|
if (url.startsWith('/') || url.startsWith('./') || url.startsWith('../')) return true;
|
|
@@ -115,35 +120,12 @@ class ApiProxy {
|
|
|
115
120
|
throw new ApiError(`Failed to parse response as ${responseType}`, void 0, response, 'PARSE_ERROR');
|
|
116
121
|
}
|
|
117
122
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
} catch (error) {
|
|
125
|
-
console.error('Request interceptor error:', error);
|
|
126
|
-
throw new ApiError('Request interceptor failed', modifiedConfig, void 0, 'INTERCEPTOR_ERROR');
|
|
127
|
-
}
|
|
128
|
-
return modifiedConfig;
|
|
129
|
-
}
|
|
130
|
-
async runResponseInterceptors(response) {
|
|
131
|
-
let modifiedResponse = response;
|
|
132
|
-
for (const interceptor of this.responseInterceptors)try {
|
|
133
|
-
modifiedResponse = await interceptor(modifiedResponse);
|
|
134
|
-
} catch (error) {
|
|
135
|
-
console.error('Response interceptor error:', error);
|
|
136
|
-
}
|
|
137
|
-
return modifiedResponse;
|
|
138
|
-
}
|
|
139
|
-
async runErrorInterceptors(error) {
|
|
140
|
-
for (const interceptor of this.errorInterceptors)try {
|
|
141
|
-
const result = await interceptor(error);
|
|
142
|
-
if (void 0 !== result) return result;
|
|
143
|
-
} catch (err) {
|
|
144
|
-
console.error('Error interceptor failed:', err);
|
|
145
|
-
}
|
|
146
|
-
throw error;
|
|
123
|
+
serializeHeaders(headers) {
|
|
124
|
+
const serialized = {};
|
|
125
|
+
headers.forEach((value, key)=>{
|
|
126
|
+
serialized[key] = value;
|
|
127
|
+
});
|
|
128
|
+
return serialized;
|
|
147
129
|
}
|
|
148
130
|
generateRequestKey(config) {
|
|
149
131
|
return `${config.method || 'GET'}_${config.url}_${Date.now()}_${Math.random()}`;
|
|
@@ -160,15 +142,8 @@ class ApiProxy {
|
|
|
160
142
|
...config.headers
|
|
161
143
|
}
|
|
162
144
|
};
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
const response = await this.executeRequest(finalConfig, requestKey);
|
|
167
|
-
return response;
|
|
168
|
-
} catch (error) {
|
|
169
|
-
const apiError = error instanceof ApiError ? error : new ApiError(error instanceof Error ? error.message : 'Unknown error', finalConfig, void 0, 'REQUEST_FAILED');
|
|
170
|
-
return await this.runErrorInterceptors(apiError);
|
|
171
|
-
}
|
|
145
|
+
const requestKey = this.generateRequestKey(mergedConfig);
|
|
146
|
+
return await this.executeRequest(mergedConfig, requestKey);
|
|
172
147
|
}
|
|
173
148
|
async executeRequest(config, requestKey) {
|
|
174
149
|
let abortController;
|
|
@@ -197,27 +172,28 @@ class ApiProxy {
|
|
|
197
172
|
const response = await fetch(fullUrl, fetchOptions);
|
|
198
173
|
if (timeoutId) clearTimeout(timeoutId);
|
|
199
174
|
this.activeRequests.delete(requestKey);
|
|
200
|
-
|
|
201
|
-
|
|
175
|
+
let data;
|
|
176
|
+
try {
|
|
177
|
+
data = 204 === response.status || 304 === response.status ? null : await this.parseResponse(response, config.responseType || 'json');
|
|
178
|
+
} catch (parseError) {
|
|
179
|
+
console.error('Failed to parse response:', parseError);
|
|
180
|
+
data = null;
|
|
181
|
+
}
|
|
202
182
|
const apiResponse = {
|
|
203
183
|
data,
|
|
204
184
|
status: response.status,
|
|
205
|
-
statusText: response.statusText
|
|
185
|
+
statusText: response.statusText,
|
|
186
|
+
success: response.ok,
|
|
187
|
+
headers: this.serializeHeaders(response.headers)
|
|
206
188
|
};
|
|
207
|
-
return
|
|
189
|
+
return apiResponse;
|
|
208
190
|
} catch (error) {
|
|
209
|
-
let errorResponse = null, errorResponseString = '';
|
|
210
|
-
if (error.response && error.config.responseType) errorResponse = await this.parseResponse(error.response, error.config.responseType || 'json');
|
|
211
|
-
try {
|
|
212
|
-
errorResponseString = JSON.stringify(errorResponse);
|
|
213
|
-
} catch (error) {
|
|
214
|
-
throw new ApiError(error instanceof Error ? error.message : 'Response parse error', config, void 0, 'UNKNOWN_ERROR');
|
|
215
|
-
}
|
|
216
191
|
if (timeoutId) clearTimeout(timeoutId);
|
|
217
192
|
this.activeRequests.delete(requestKey);
|
|
193
|
+
if (error instanceof ApiError) throw error;
|
|
218
194
|
if (error instanceof Error && 'AbortError' === error.name) throw new ApiError(config.timeout ? 'Request timeout' : 'Request cancelled', config, void 0, 'ABORT_ERROR');
|
|
219
195
|
if (error instanceof TypeError) throw new ApiError('Network error or CORS issue', config, void 0, 'NETWORK_ERROR');
|
|
220
|
-
throw new ApiError(
|
|
196
|
+
throw new ApiError(error instanceof Error ? error.message : 'Unknown error', config, void 0, 'UNKNOWN_ERROR');
|
|
221
197
|
}
|
|
222
198
|
}
|
|
223
199
|
async get(url, config) {
|
|
@@ -5,7 +5,7 @@ import { clsxWithTw } from "../../utils/utils.js";
|
|
|
5
5
|
import { UserProfile } from "./UserProfile/index.js";
|
|
6
6
|
import { UserWithAvatar } from "./UserWithAvatar.js";
|
|
7
7
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover.js";
|
|
8
|
-
const UserDisplay = ({ users, size, className, style })=>{
|
|
8
|
+
const UserDisplay = ({ users, size, className, style, showLabel = true })=>{
|
|
9
9
|
const normalizedUsers = useMemo(()=>Array.isArray(users) ? users : [
|
|
10
10
|
users
|
|
11
11
|
].filter(Boolean), [
|
|
@@ -96,6 +96,7 @@ const UserDisplay = ({ users, size, className, style })=>{
|
|
|
96
96
|
children: /*#__PURE__*/ jsx(UserWithAvatar, {
|
|
97
97
|
data: user,
|
|
98
98
|
size: size,
|
|
99
|
+
showLabel: showLabel,
|
|
99
100
|
className: "cursor-pointer hover:bg-[rgba(31,35,41,0.15)] active:bg-[rgba(31,35,41,0.2)]"
|
|
100
101
|
})
|
|
101
102
|
})
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import type { UserWithAvatarProps } from './type';
|
|
3
|
-
export declare function UserWithAvatar({ data, size, mode, className, }: UserWithAvatarProps): React.JSX.Element;
|
|
3
|
+
export declare function UserWithAvatar({ data, size, mode, className, showLabel, }: UserWithAvatarProps): React.JSX.Element;
|
|
@@ -23,7 +23,7 @@ const textVariantMap = {
|
|
|
23
23
|
medium: 'text-[14px] leading-[20px]',
|
|
24
24
|
large: 'text-[16px] leading-[24px]'
|
|
25
25
|
};
|
|
26
|
-
function UserWithAvatar({ data, size = 'medium', mode = 'tag', className }) {
|
|
26
|
+
function UserWithAvatar({ data, size = 'medium', mode = 'tag', className, showLabel = true }) {
|
|
27
27
|
const { avatar, name } = data;
|
|
28
28
|
const displayName = name?.trim() || '无效人员';
|
|
29
29
|
const formatSize = [
|
|
@@ -48,7 +48,7 @@ function UserWithAvatar({ data, size = 'medium', mode = 'tag', className }) {
|
|
|
48
48
|
})
|
|
49
49
|
]
|
|
50
50
|
}),
|
|
51
|
-
/*#__PURE__*/ jsx(OverflowTooltipText, {
|
|
51
|
+
showLabel && /*#__PURE__*/ jsx(OverflowTooltipText, {
|
|
52
52
|
text: displayName,
|
|
53
53
|
className: clsxWithTw('text-card-foreground', textVariantMap[formatSize])
|
|
54
54
|
})
|