@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.
@@ -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
- const storedToken = adapter.get<string>(STORAGE_KEY_TOKEN);
141
- const storedUser = adapter.get<AuthState['user']>(STORAGE_KEY_USER);
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
- const response = await transport.send({
383
- url,
384
- method: this.method,
385
- headers,
386
- body,
387
- signal,
388
- timeout: this.config.timeout,
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>() => Promise.resolve(JSON.parse(responseText) as 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] && data[i][param]) return data[i][param];
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 HIDDEN_STYLE = {
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: "1000",
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 = HIDDEN_STYLE;
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
- return responseInterceptor ? responseInterceptor(result) : result;
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
- return unwrapResponse(await transport.send({
8381
- url,
8382
- method: this.method,
8383
- headers,
8384
- body,
8385
- signal,
8386
- timeout: this.config.timeout
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: () => Promise.resolve(JSON.parse(responseText)),
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kine-design/crud",
3
- "version": "0.0.1-beta.16",
3
+ "version": "0.0.1-beta.17",
4
4
  "type": "module",
5
5
  "main": "./dist/crud.js",
6
6
  "types": "./dist/index.d.ts",