@kine-design/crud 0.0.1-beta.21 → 0.0.1-beta.23

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.
Files changed (3) hide show
  1. package/dist/crud.js +141 -18
  2. package/package.json +3 -3
  3. package/setup.ts +12 -11
package/dist/crud.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Comment, Fragment, Teleport, computed, createApp, createTextVNode, createVNode, defineComponent, h, inject, isRef, mergeProps, nextTick, onBeforeUnmount, onMounted, onUnmounted, provide, reactive, ref, resolveComponent, shallowRef, toRef, triggerRef, watch } from "vue";
1
+ import { Comment, Fragment, Teleport, cloneVNode, computed, createApp, createTextVNode, createVNode, defineComponent, h, inject, isRef, mergeProps, nextTick, onBeforeUnmount, onMounted, onUnmounted, provide, reactive, ref, resolveComponent, shallowRef, toRef, triggerRef, watch } from "vue";
2
2
  import { QueryClient, VueQueryPlugin, useMutation, useQuery, useQueryClient } from "@tanstack/vue-query";
3
3
  import { createPinia, defineStore } from "pinia";
4
4
  import { useRoute, useRouter } from "vue-router";
@@ -283,22 +283,127 @@ function useTable() {
283
283
  };
284
284
  }
285
285
  //#endregion
286
- //#region ../ui/components/table/KTable.tsx
286
+ //#region ../core/components/template/table/index.ts
287
287
  /**
288
- * @description kine-ui table 组件
288
+ * @description table core 导出
289
289
  * @author 阿怪
290
- * @date 2026/2/26
290
+ * @date 2026/2/25
291
291
  * @version v1.0.0
292
292
  *
293
293
  * 江湖的业务千篇一律,复杂的代码好几百行。
294
294
  */
295
- var { props: props$9 } = {
295
+ var TableCore = {
296
296
  props: props$10,
297
297
  useTable
298
298
  };
299
- var KTable_default = /* @__PURE__ */ defineComponent((_props, { slots }) => {
299
+ //#endregion
300
+ //#region ../core/compositions/common/testAnchor.ts
301
+ /**
302
+ * @description Test anchor system: provide/inject keys, types, and kDefineComponent wrapper
303
+ * @author kine-design
304
+ * @date 2026/4/28
305
+ * @version v1.0.0
306
+ *
307
+ * Renders business-semantic `data-k` attributes on DOM elements when testAnchor is enabled,
308
+ * allowing E2E tests to target components by stable identifiers.
309
+ */
310
+ /** Global switch provided by KConfigProvider to enable data-k rendering */
311
+ var K_TEST_ANCHOR_KEY = Symbol("k-test-anchor");
312
+ /** Field identity provided by KFormItem so child inputs inherit field name */
313
+ var K_FIELD_KEY = Symbol("k-field");
314
+ /**
315
+ * Extract text content from a VNode tree (for slotText mode).
316
+ * Walks children recursively, concatenating string segments.
317
+ */
318
+ function extractSlotText(nodes) {
319
+ if (!nodes) return "";
320
+ let text = "";
321
+ for (const node of nodes) if (typeof node.children === "string") text += node.children;
322
+ else if (Array.isArray(node.children)) text += extractSlotText(node.children);
323
+ return text.trim();
324
+ }
325
+ /**
326
+ * Resolve the data-k value for a component instance.
327
+ * Returns undefined when testAnchor is off or no identity can be derived.
328
+ */
329
+ function resolveDataK(config, props, attrs, fieldFromParent, slotNodes) {
330
+ const override = attrs[`k-${config.type}`];
331
+ if (typeof override === "string" && override) return `${config.type}:${override}`;
332
+ if (config.prop) {
333
+ const val = props[config.prop];
334
+ if (typeof val === "string" && val) return `${config.type}:${val}`;
335
+ }
336
+ if (config.slotText && slotNodes) {
337
+ const text = extractSlotText(slotNodes);
338
+ if (text) return `${config.type}:${text}`;
339
+ }
340
+ if (config.slotText && typeof props.text === "string" && props.text) return `${config.type}:${props.text}`;
341
+ if (config.type === "field" && !config.prop && fieldFromParent) return `${config.type}:${fieldFromParent}`;
342
+ }
343
+ /**
344
+ * Inject data-k attribute into a VNode.
345
+ * Uses cloneVNode to avoid mutating the original.
346
+ */
347
+ function injectDataK(vnode, dataK) {
348
+ return cloneVNode(vnode, { "data-k": dataK });
349
+ }
350
+ /**
351
+ * Wrapper around Vue's defineComponent that automatically injects `data-k`
352
+ * test anchor attributes when testAnchor is enabled via KConfigProvider.
353
+ *
354
+ * Usage:
355
+ * ```tsx
356
+ * export default kDefineComponent((_props, ctx) => {
357
+ * return () => <div class="k-form-item">...</div>;
358
+ * }, {
359
+ * name: 'KFormItem',
360
+ * props: FormCore.formItemProps,
361
+ * kAnchor: { type: 'field', prop: 'prop' },
362
+ * });
363
+ * ```
364
+ */
365
+ function kDefineComponent(setup, options) {
366
+ const { kAnchor, ...defineOptions } = options;
367
+ return defineComponent((rawProps, ctx) => {
368
+ const render = setup(rawProps, ctx);
369
+ if (!kAnchor) return render;
370
+ const testAnchor = inject(K_TEST_ANCHOR_KEY, void 0);
371
+ const parentField = kAnchor.type === "field" && !kAnchor.prop ? inject(K_FIELD_KEY, void 0) : void 0;
372
+ return () => {
373
+ const vnode = render();
374
+ if (!testAnchor?.value) return vnode;
375
+ const props = rawProps;
376
+ const slotNodes = kAnchor.slotText ? ctx.slots.default?.() ?? void 0 : void 0;
377
+ const dataK = resolveDataK(kAnchor, props, ctx.attrs, parentField?.value, slotNodes);
378
+ if (!dataK) return vnode;
379
+ if (vnode === null || vnode === void 0) return vnode;
380
+ if (Array.isArray(vnode)) {
381
+ if (vnode.length === 0) return vnode;
382
+ const first = vnode[0];
383
+ if (first && typeof first === "object") return [injectDataK(first, dataK), ...vnode.slice(1)];
384
+ return vnode;
385
+ }
386
+ return injectDataK(vnode, dataK);
387
+ };
388
+ }, defineOptions);
389
+ }
390
+ //#endregion
391
+ //#region ../ui/components/table/KTable.tsx
392
+ /**
393
+ * @description kine-ui table 组件
394
+ * @author 阿怪
395
+ * @date 2026/2/26
396
+ * @version v1.1.0
397
+ *
398
+ * 江湖的业务千篇一律,复杂的代码好几百行。
399
+ *
400
+ * v1.1.0 migrate to kDefineComponent for data-k test anchor 2026/4/28
401
+ */
402
+ var { props: props$9 } = TableCore;
403
+ var KTable_default = kDefineComponent((_props, { slots }) => {
300
404
  const p = _props;
301
405
  const { initTable } = useTable();
406
+ const testAnchor = inject(K_TEST_ANCHOR_KEY, void 0);
302
407
  return () => {
303
408
  const columns = [];
304
409
  (slots.default?.() ?? []).forEach((s) => {
@@ -314,6 +419,7 @@ var KTable_default = /* @__PURE__ */ defineComponent((_props, { slots }) => {
314
419
  empty: createVNode("tbody", { "class": "m-table-empty k-table-empty" }, [createVNode("tr", null, [createVNode("th", { "colspan": columns.length }, [slots.empty?.() ?? "暂无数据"])])]),
315
420
  tbodyTr: ({ data, param, slot, style: cellStyle, slotInfo }) => createVNode("td", {
316
421
  "style": cellStyle,
422
+ "data-k": testAnchor?.value && param ? `col:${param}` : void 0,
317
423
  "class": [
318
424
  "m-td",
319
425
  "k-td",
@@ -360,7 +466,11 @@ var KTable_default = /* @__PURE__ */ defineComponent((_props, { slots }) => {
360
466
  };
361
467
  }, {
362
468
  name: "KTable",
363
- props: props$9
469
+ props: props$9,
470
+ kAnchor: {
471
+ type: "table",
472
+ prop: "name"
473
+ }
364
474
  });
365
475
  //#endregion
366
476
  //#region ../core/components/base/input/api.ts
@@ -7068,11 +7178,13 @@ function useComponentSize(props) {
7068
7178
  * @description kine-ui button 组件
7069
7179
  * @author 阿怪
7070
7180
  * @date 2026/2/27
7071
- * @version v1.1.0
7181
+ * @version v1.2.0
7072
7182
  *
7073
7183
  * 江湖的业务千篇一律,复杂的代码好几百行。
7184
+ *
7185
+ * v1.2.0 migrate to kDefineComponent for data-k test anchor 2026/4/28
7074
7186
  */
7075
- var KButton_default = /* @__PURE__ */ defineComponent((_props, ctx) => {
7187
+ var KButton_default = kDefineComponent((_props, ctx) => {
7076
7188
  const props = _props;
7077
7189
  const componentSize = useComponentSize(props);
7078
7190
  return () => {
@@ -7095,7 +7207,11 @@ var KButton_default = /* @__PURE__ */ defineComponent((_props, ctx) => {
7095
7207
  };
7096
7208
  }, {
7097
7209
  name: "KButton",
7098
- props: ButtonCore.props
7210
+ props: ButtonCore.props,
7211
+ kAnchor: {
7212
+ type: "action",
7213
+ slotText: true
7214
+ }
7099
7215
  });
7100
7216
  //#endregion
7101
7217
  //#region components/searchTable/KSearchTable.tsx
@@ -7396,11 +7512,13 @@ var KImage_default = /* @__PURE__ */ defineComponent((_props, { slots, emit }) =
7396
7512
  * @description kine-ui input 组件
7397
7513
  * @author 阿怪
7398
7514
  * @date 2026/2/26
7399
- * @version v1.0.0
7515
+ * @version v1.1.0
7400
7516
  *
7401
7517
  * 江湖的业务千篇一律,复杂的代码好几百行。
7518
+ *
7519
+ * v1.1.0 migrate to kDefineComponent for data-k test anchor 2026/4/28
7402
7520
  */
7403
- var KInput_default = /* @__PURE__ */ defineComponent((_props, ctx) => {
7521
+ var KInput_default = kDefineComponent((_props, ctx) => {
7404
7522
  const props = _props;
7405
7523
  const size = useComponentSize(props);
7406
7524
  return () => {
@@ -7447,7 +7565,8 @@ var KInput_default = /* @__PURE__ */ defineComponent((_props, ctx) => {
7447
7565
  "focus",
7448
7566
  "blur",
7449
7567
  "clear"
7450
- ]
7568
+ ],
7569
+ kAnchor: { type: "field" }
7451
7570
  });
7452
7571
  //#endregion
7453
7572
  //#region ../ui/constants.ts
@@ -7465,14 +7584,15 @@ var DROPDOWN_HIDDEN_STYLE = {
7465
7584
  * @description kine-ui select 组件
7466
7585
  * @author 阿怪
7467
7586
  * @date 2026/2/26
7468
- * @version v1.0.1
7587
+ * @version v1.1.0
7469
7588
  *
7470
7589
  * 江湖的业务千篇一律,复杂的代码好几百行。
7471
7590
  *
7472
7591
  * v1.0.1 消费 inputReadonly,支持 filterable 可搜索模式 阿怪 2026/2/27
7592
+ * v1.1.0 migrate to kDefineComponent for data-k test anchor 2026/4/28
7473
7593
  */
7474
7594
  var { props: selectProps, useSelect } = SelectCore;
7475
- var KSelect_default = /* @__PURE__ */ defineComponent((_props, _ctx) => {
7595
+ var KSelect_default = kDefineComponent((_props, _ctx) => {
7476
7596
  const props = _props;
7477
7597
  const { slots, expose } = _ctx;
7478
7598
  const componentSize = useComponentSize(props);
@@ -7657,7 +7777,8 @@ var KSelect_default = /* @__PURE__ */ defineComponent((_props, _ctx) => {
7657
7777
  "select",
7658
7778
  "focus",
7659
7779
  "blur"
7660
- ]
7780
+ ],
7781
+ kAnchor: { type: "field" }
7661
7782
  });
7662
7783
  //#endregion
7663
7784
  //#region components/pageHeader/KPageHeader.tsx
@@ -8751,7 +8872,6 @@ function createCrudAppWithOptions(rootComponent, options) {
8751
8872
  options.router.beforeEach(guard);
8752
8873
  }
8753
8874
  }
8754
- if (options.router) app.use(options.router);
8755
8875
  setupCrud(app, options.query);
8756
8876
  if (options.request) {
8757
8877
  const requestOptions = { ...options.request };
@@ -8763,7 +8883,10 @@ function createCrudAppWithOptions(rootComponent, options) {
8763
8883
  if (options.error) createErrorHandler(app, options.error);
8764
8884
  const enhancedApp = Object.create(app);
8765
8885
  enhancedApp.mount = (rootContainer) => {
8766
- const doMount = () => app.mount(rootContainer);
8886
+ const doMount = () => {
8887
+ if (options.router) app.use(options.router);
8888
+ app.mount(rootContainer);
8889
+ };
8767
8890
  if (auth) auth.restore().finally(doMount);
8768
8891
  else doMount();
8769
8892
  return app;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kine-design/crud",
3
- "version": "0.0.1-beta.21",
3
+ "version": "0.0.1-beta.23",
4
4
  "type": "module",
5
5
  "main": "./dist/crud.js",
6
6
  "types": "./dist/index.d.ts",
@@ -9,8 +9,8 @@
9
9
  "pinia": "^3.0.3",
10
10
  "vue": "^3.5.30",
11
11
  "vue-router": "^5.0.3",
12
- "@kine-design/core": "0.0.1-beta.6",
13
- "kine-ui": "0.0.1-beta.12"
12
+ "@kine-design/core": "0.0.1-beta.7",
13
+ "kine-ui": "0.0.1-beta.15"
14
14
  },
15
15
  "publishConfig": {
16
16
  "access": "public",
package/setup.ts CHANGED
@@ -244,22 +244,15 @@ function createCrudAppWithOptions(
244
244
  }
245
245
  }
246
246
 
247
- // 2. Router(install 时触发初始导航,此时守卫已注册)
248
- if (options.router) {
249
- app.use(options.router as { install: (app: App) => void });
250
- }
251
-
252
- // 3. TanStack Query
247
+ // 2. TanStack Query
253
248
  setupCrud(app, options.query);
254
249
 
255
- // 4. Request
250
+ // 3. Request
256
251
  if (options.request) {
257
252
  const requestOptions: RequestOptions = { ...options.request };
258
- // 如果有 auth,自动注入 getToken
259
253
  if (auth && !requestOptions.getToken) {
260
254
  requestOptions.getToken = () => auth!.token.value;
261
255
  }
262
- // 如果有 auth,自动注入 onUnauthorized
263
256
  if (auth && !requestOptions.onUnauthorized) {
264
257
  requestOptions.onUnauthorized = options.auth?.onUnauthorized;
265
258
  }
@@ -267,15 +260,23 @@ function createCrudAppWithOptions(
267
260
  app.provide(REQUEST_CLIENT_KEY, client);
268
261
  }
269
262
 
270
- // 5. Error Handler
263
+ // 4. Error Handler
271
264
  if (options.error) {
272
265
  createErrorHandler(app, options.error);
273
266
  }
274
267
 
275
268
  // 返回增强的 app,mount 前先恢复 auth
269
+ // Router 安装延迟到 doMount:app.use(router) 会触发初始导航,
270
+ // 必须在 auth.restore() 完成(permissions 已加载)之后执行,否则
271
+ // authGuard 会在 permissions 为空时将受保护路由重定向到 /403。
276
272
  const enhancedApp: EnhancedApp = Object.create(app);
277
273
  enhancedApp.mount = (rootContainer: string | Element): App => {
278
- const doMount = () => app.mount(rootContainer);
274
+ const doMount = () => {
275
+ if (options.router) {
276
+ app.use(options.router as { install: (app: App) => void });
277
+ }
278
+ app.mount(rootContainer);
279
+ };
279
280
  if (auth) {
280
281
  auth.restore().finally(doMount);
281
282
  } else {