@kine-design/crud 0.0.1-beta.16 → 0.0.1-beta.17
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/.vlaude/last-session-id +1 -0
- package/composables/auth/useAuth.ts +22 -2
- package/composables/request/createRequest.ts +1 -0
- package/composables/request/requestBuilder.ts +26 -8
- package/composables/request/transport/xhrTransport.ts +4 -1
- package/dist/composables/request/requestBuilder.d.ts +2 -0
- package/dist/crud.js +65 -20
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
594292cc-8321-4629-97d9-91ad432e0646
|
|
@@ -134,11 +134,24 @@ function buildAuthReturn(
|
|
|
134
134
|
clearStorage();
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
let restoring: Promise<void> | null = null;
|
|
138
|
+
|
|
137
139
|
async function restore(): Promise<void> {
|
|
138
140
|
if (!adapter) return;
|
|
141
|
+
// 防止并发调用(如 mount + tab 激活同时触发)
|
|
142
|
+
if (restoring) return restoring;
|
|
139
143
|
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
restoring = doRestore();
|
|
145
|
+
try {
|
|
146
|
+
await restoring;
|
|
147
|
+
} finally {
|
|
148
|
+
restoring = null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function doRestore(): Promise<void> {
|
|
153
|
+
const storedToken = adapter!.get<string>(STORAGE_KEY_TOKEN);
|
|
154
|
+
const storedUser = adapter!.get<AuthState['user']>(STORAGE_KEY_USER);
|
|
142
155
|
|
|
143
156
|
// storage 中没有有效数据,跳过
|
|
144
157
|
if (!storedToken || !storedUser) return;
|
|
@@ -213,6 +226,13 @@ export function createAuth(app: App, options: AuthOptions): AuthReturn {
|
|
|
213
226
|
}
|
|
214
227
|
};
|
|
215
228
|
window.addEventListener('storage', onStorageChange);
|
|
229
|
+
|
|
230
|
+
// app.unmount 时清理监听器,防止泄漏
|
|
231
|
+
const origUnmount = app.unmount.bind(app);
|
|
232
|
+
app.unmount = () => {
|
|
233
|
+
window.removeEventListener('storage', onStorageChange);
|
|
234
|
+
origUnmount();
|
|
235
|
+
};
|
|
216
236
|
}
|
|
217
237
|
|
|
218
238
|
// 将 auth 实例注入 Vue 组件树,同时挂载 onUnauthorized 回调供 authGuard 使用
|
|
@@ -58,6 +58,7 @@ export function createRequest(options: RequestOptions = {}): RequestClient {
|
|
|
58
58
|
getToken: options.getToken,
|
|
59
59
|
onUnauthorized: options.onUnauthorized,
|
|
60
60
|
responseInterceptor: options.responseInterceptor,
|
|
61
|
+
feedback: options.feedback,
|
|
61
62
|
registerAbort: (controller: AbortController) => {
|
|
62
63
|
abortControllers.add(controller);
|
|
63
64
|
// 完成后自动移除
|
|
@@ -11,10 +11,12 @@ import type {
|
|
|
11
11
|
CachePolicy,
|
|
12
12
|
ControlPolicy,
|
|
13
13
|
Lifecycle,
|
|
14
|
+
NetworkError,
|
|
14
15
|
RequestConfig,
|
|
15
16
|
RequestMethod,
|
|
16
17
|
RetryPolicy,
|
|
17
18
|
Transport,
|
|
19
|
+
TransportResponse,
|
|
18
20
|
WrappedResponse,
|
|
19
21
|
} from './types';
|
|
20
22
|
import { BusinessError, NetworkRequestError } from './types';
|
|
@@ -194,6 +196,8 @@ export interface RequestBuilderContext {
|
|
|
194
196
|
getToken?: () => string | null | undefined;
|
|
195
197
|
onUnauthorized?: () => void;
|
|
196
198
|
responseInterceptor?: <T>(data: T) => T;
|
|
199
|
+
/** 用户反馈处理器(写操作成功后自动 showSuccess) */
|
|
200
|
+
feedback?: import('./types').UserFeedbackHandler;
|
|
197
201
|
/** 注册 abort controller,供生命周期管理 */
|
|
198
202
|
registerAbort?: (controller: AbortController) => void;
|
|
199
203
|
}
|
|
@@ -358,6 +362,11 @@ export class RequestBuilder {
|
|
|
358
362
|
// 响应拦截器
|
|
359
363
|
const finalResult = responseInterceptor ? responseInterceptor(result) : result;
|
|
360
364
|
|
|
365
|
+
// 写操作成功后自动反馈
|
|
366
|
+
if (this.context.feedback && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(this.method)) {
|
|
367
|
+
this.context.feedback.showSuccess('操作成功');
|
|
368
|
+
}
|
|
369
|
+
|
|
361
370
|
return finalResult as T;
|
|
362
371
|
} catch (error) {
|
|
363
372
|
// 401 检测
|
|
@@ -379,14 +388,23 @@ export class RequestBuilder {
|
|
|
379
388
|
body: BodyInit | null,
|
|
380
389
|
signal: AbortSignal,
|
|
381
390
|
): Promise<T> {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
391
|
+
let response: TransportResponse;
|
|
392
|
+
try {
|
|
393
|
+
response = await transport.send({
|
|
394
|
+
url,
|
|
395
|
+
method: this.method,
|
|
396
|
+
headers,
|
|
397
|
+
body,
|
|
398
|
+
signal,
|
|
399
|
+
timeout: this.config.timeout,
|
|
400
|
+
});
|
|
401
|
+
} catch (error) {
|
|
402
|
+
// transport 层抛出的是 NetworkError 裸对象,统一包装为 NetworkRequestError
|
|
403
|
+
if (error && typeof error === 'object' && 'type' in error) {
|
|
404
|
+
throw new NetworkRequestError(error as NetworkError);
|
|
405
|
+
}
|
|
406
|
+
throw new NetworkRequestError({ type: 'unknown', error });
|
|
407
|
+
}
|
|
390
408
|
|
|
391
409
|
return unwrapResponse<T>(response);
|
|
392
410
|
}
|
|
@@ -72,7 +72,10 @@ export class XhrTransport implements Transport {
|
|
|
72
72
|
headers,
|
|
73
73
|
body: null,
|
|
74
74
|
text: () => Promise.resolve(responseText),
|
|
75
|
-
json: <T>() =>
|
|
75
|
+
json: <T>() => {
|
|
76
|
+
try { return Promise.resolve(JSON.parse(responseText) as T); }
|
|
77
|
+
catch (e) { return Promise.reject(e); }
|
|
78
|
+
},
|
|
76
79
|
blob: () => Promise.resolve(new Blob([responseText])),
|
|
77
80
|
};
|
|
78
81
|
|
|
@@ -8,6 +8,8 @@ export interface RequestBuilderContext {
|
|
|
8
8
|
getToken?: () => string | null | undefined;
|
|
9
9
|
onUnauthorized?: () => void;
|
|
10
10
|
responseInterceptor?: <T>(data: T) => T;
|
|
11
|
+
/** 用户反馈处理器(写操作成功后自动 showSuccess) */
|
|
12
|
+
feedback?: import('./types').UserFeedbackHandler;
|
|
11
13
|
/** 注册 abort controller,供生命周期管理 */
|
|
12
14
|
registerAbort?: (controller: AbortController) => void;
|
|
13
15
|
}
|
package/dist/crud.js
CHANGED
|
@@ -214,8 +214,8 @@ function useTable() {
|
|
|
214
214
|
});
|
|
215
215
|
/** 从 data[i] 中安全取值 */
|
|
216
216
|
const getData = (i, param) => {
|
|
217
|
-
if (data[i]
|
|
218
|
-
return "";
|
|
217
|
+
if (data[i] == null) return "";
|
|
218
|
+
return data[i][param] ?? "";
|
|
219
219
|
};
|
|
220
220
|
/** 向每行的 td 列表中追加一列 */
|
|
221
221
|
const pushTd = (param, bodySlot, style) => {
|
|
@@ -7450,6 +7450,16 @@ var KInput_default = /* @__PURE__ */ defineComponent((_props, ctx) => {
|
|
|
7450
7450
|
]
|
|
7451
7451
|
});
|
|
7452
7452
|
//#endregion
|
|
7453
|
+
//#region ../ui/constants.ts
|
|
7454
|
+
/** 浮层组件的默认 z-index(Select / AutoComplete / DatePicker / TimePicker / Cascader) */
|
|
7455
|
+
var Z_INDEX_DROPDOWN = "1000";
|
|
7456
|
+
/** 浮层隐藏时使用的占位样式(保持 DOM 挂载以便 floating-ui 计算宽度) */
|
|
7457
|
+
var DROPDOWN_HIDDEN_STYLE = {
|
|
7458
|
+
position: "fixed",
|
|
7459
|
+
visibility: "hidden",
|
|
7460
|
+
zIndex: Z_INDEX_DROPDOWN
|
|
7461
|
+
};
|
|
7462
|
+
//#endregion
|
|
7453
7463
|
//#region ../ui/components/select/KSelect.tsx
|
|
7454
7464
|
/**
|
|
7455
7465
|
* @description kine-ui select 组件
|
|
@@ -7469,12 +7479,7 @@ var KSelect_default = /* @__PURE__ */ defineComponent((_props, _ctx) => {
|
|
|
7469
7479
|
const { inputValue, isOpen, fetchLoading, inputReadonly, displayOptions, selectedTags, lastOptionRef, optionsContainerRef, onSelect, onDeleteTag, onInput, onFocus, onBlur, tools, updateFetchObserver } = useSelect(props, _ctx);
|
|
7470
7480
|
const triggerRef = ref(null);
|
|
7471
7481
|
const dropdownRef = ref(null);
|
|
7472
|
-
const
|
|
7473
|
-
position: "fixed",
|
|
7474
|
-
visibility: "hidden",
|
|
7475
|
-
zIndex: "1000"
|
|
7476
|
-
};
|
|
7477
|
-
const dropdownStyle = ref(HIDDEN_STYLE);
|
|
7482
|
+
const dropdownStyle = ref(DROPDOWN_HIDDEN_STYLE);
|
|
7478
7483
|
const cleanupAutoUpdate = ref(null);
|
|
7479
7484
|
const updateDropdownPosition = async () => {
|
|
7480
7485
|
if (!triggerRef.value || !dropdownRef.value) return;
|
|
@@ -7494,7 +7499,7 @@ var KSelect_default = /* @__PURE__ */ defineComponent((_props, _ctx) => {
|
|
|
7494
7499
|
position: "fixed",
|
|
7495
7500
|
left: `${x}px`,
|
|
7496
7501
|
top: `${y}px`,
|
|
7497
|
-
zIndex:
|
|
7502
|
+
zIndex: Z_INDEX_DROPDOWN,
|
|
7498
7503
|
visibility: "visible"
|
|
7499
7504
|
};
|
|
7500
7505
|
};
|
|
@@ -7511,7 +7516,7 @@ var KSelect_default = /* @__PURE__ */ defineComponent((_props, _ctx) => {
|
|
|
7511
7516
|
cleanupAutoUpdate.value?.();
|
|
7512
7517
|
cleanupAutoUpdate.value = null;
|
|
7513
7518
|
isOpen.value = false;
|
|
7514
|
-
dropdownStyle.value =
|
|
7519
|
+
dropdownStyle.value = DROPDOWN_HIDDEN_STYLE;
|
|
7515
7520
|
};
|
|
7516
7521
|
onBeforeUnmount(() => {
|
|
7517
7522
|
cleanupAutoUpdate.value?.();
|
|
@@ -7603,6 +7608,7 @@ var KSelect_default = /* @__PURE__ */ defineComponent((_props, _ctx) => {
|
|
|
7603
7608
|
props.loading && createVNode("span", { "class": "k-select-loading-icon" }, null),
|
|
7604
7609
|
showClear && !props.loading && createVNode("span", {
|
|
7605
7610
|
"class": "k-select-clear",
|
|
7611
|
+
"aria-label": "清空",
|
|
7606
7612
|
"onMousedown": (e) => {
|
|
7607
7613
|
e.preventDefault();
|
|
7608
7614
|
e.stopPropagation();
|
|
@@ -7813,8 +7819,18 @@ function buildAuthReturn(state, options, adapter) {
|
|
|
7813
7819
|
state.token = null;
|
|
7814
7820
|
clearStorage();
|
|
7815
7821
|
}
|
|
7822
|
+
let restoring = null;
|
|
7816
7823
|
async function restore() {
|
|
7817
7824
|
if (!adapter) return;
|
|
7825
|
+
if (restoring) return restoring;
|
|
7826
|
+
restoring = doRestore();
|
|
7827
|
+
try {
|
|
7828
|
+
await restoring;
|
|
7829
|
+
} finally {
|
|
7830
|
+
restoring = null;
|
|
7831
|
+
}
|
|
7832
|
+
}
|
|
7833
|
+
async function doRestore() {
|
|
7818
7834
|
const storedToken = adapter.get(STORAGE_KEY_TOKEN);
|
|
7819
7835
|
const storedUser = adapter.get(STORAGE_KEY_USER);
|
|
7820
7836
|
if (!storedToken || !storedUser) return;
|
|
@@ -7881,6 +7897,11 @@ function createAuth(app, options) {
|
|
|
7881
7897
|
}
|
|
7882
7898
|
};
|
|
7883
7899
|
window.addEventListener("storage", onStorageChange);
|
|
7900
|
+
const origUnmount = app.unmount.bind(app);
|
|
7901
|
+
app.unmount = () => {
|
|
7902
|
+
window.removeEventListener("storage", onStorageChange);
|
|
7903
|
+
origUnmount();
|
|
7904
|
+
};
|
|
7884
7905
|
}
|
|
7885
7906
|
app.provide(AUTH_INJECT_KEY, {
|
|
7886
7907
|
auth,
|
|
@@ -8368,7 +8389,14 @@ var RequestBuilder = class {
|
|
|
8368
8389
|
data: result,
|
|
8369
8390
|
timestamp: Date.now()
|
|
8370
8391
|
});
|
|
8371
|
-
|
|
8392
|
+
const finalResult = responseInterceptor ? responseInterceptor(result) : result;
|
|
8393
|
+
if (this.context.feedback && [
|
|
8394
|
+
"POST",
|
|
8395
|
+
"PUT",
|
|
8396
|
+
"PATCH",
|
|
8397
|
+
"DELETE"
|
|
8398
|
+
].includes(this.method)) this.context.feedback.showSuccess("操作成功");
|
|
8399
|
+
return finalResult;
|
|
8372
8400
|
} catch (error) {
|
|
8373
8401
|
if (error instanceof NetworkRequestError && error.detail.type === "httpError" && error.detail.status === 401) onUnauthorized?.();
|
|
8374
8402
|
throw error;
|
|
@@ -8377,14 +8405,24 @@ var RequestBuilder = class {
|
|
|
8377
8405
|
}
|
|
8378
8406
|
}
|
|
8379
8407
|
async executeTransport(transport, url, headers, body, signal) {
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
8387
|
-
|
|
8408
|
+
let response;
|
|
8409
|
+
try {
|
|
8410
|
+
response = await transport.send({
|
|
8411
|
+
url,
|
|
8412
|
+
method: this.method,
|
|
8413
|
+
headers,
|
|
8414
|
+
body,
|
|
8415
|
+
signal,
|
|
8416
|
+
timeout: this.config.timeout
|
|
8417
|
+
});
|
|
8418
|
+
} catch (error) {
|
|
8419
|
+
if (error && typeof error === "object" && "type" in error) throw new NetworkRequestError(error);
|
|
8420
|
+
throw new NetworkRequestError({
|
|
8421
|
+
type: "unknown",
|
|
8422
|
+
error
|
|
8423
|
+
});
|
|
8424
|
+
}
|
|
8425
|
+
return unwrapResponse(response);
|
|
8388
8426
|
}
|
|
8389
8427
|
};
|
|
8390
8428
|
//#endregion
|
|
@@ -8401,6 +8439,7 @@ function createRequest(options = {}) {
|
|
|
8401
8439
|
getToken: options.getToken,
|
|
8402
8440
|
onUnauthorized: options.onUnauthorized,
|
|
8403
8441
|
responseInterceptor: options.responseInterceptor,
|
|
8442
|
+
feedback: options.feedback,
|
|
8404
8443
|
registerAbort: (controller) => {
|
|
8405
8444
|
abortControllers.add(controller);
|
|
8406
8445
|
const originalAbort = controller.abort.bind(controller);
|
|
@@ -10316,7 +10355,13 @@ var XhrTransport = class {
|
|
|
10316
10355
|
headers,
|
|
10317
10356
|
body: null,
|
|
10318
10357
|
text: () => Promise.resolve(responseText),
|
|
10319
|
-
json: () =>
|
|
10358
|
+
json: () => {
|
|
10359
|
+
try {
|
|
10360
|
+
return Promise.resolve(JSON.parse(responseText));
|
|
10361
|
+
} catch (e) {
|
|
10362
|
+
return Promise.reject(e);
|
|
10363
|
+
}
|
|
10364
|
+
},
|
|
10320
10365
|
blob: () => Promise.resolve(new Blob([responseText]))
|
|
10321
10366
|
});
|
|
10322
10367
|
};
|