@kine-design/crud 0.0.1-beta.1

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 (61) hide show
  1. package/dist/components/layout/KContent.d.ts +2 -0
  2. package/dist/components/layout/KHeader.d.ts +2 -0
  3. package/dist/components/layout/KLayout.d.ts +44 -0
  4. package/dist/components/layout/KSider.d.ts +2 -0
  5. package/dist/components/layout/index.d.ts +13 -0
  6. package/dist/components/login/KLoginPage.d.ts +43 -0
  7. package/dist/components/login/index.d.ts +10 -0
  8. package/dist/components/navMenu/KNavMenu.d.ts +27 -0
  9. package/dist/components/navMenu/index.d.ts +2 -0
  10. package/dist/components/pageHeader/KPageHeader.d.ts +49 -0
  11. package/dist/components/pageHeader/index.d.ts +9 -0
  12. package/dist/components/searchTable/KSearchTable.d.ts +99 -0
  13. package/dist/components/searchTable/index.d.ts +9 -0
  14. package/dist/components/upload/KFileList.d.ts +27 -0
  15. package/dist/components/upload/KImageUpload.d.ts +91 -0
  16. package/dist/components/upload/KUpload.d.ts +82 -0
  17. package/dist/components/upload/index.d.ts +12 -0
  18. package/dist/components/upload/types.d.ts +23 -0
  19. package/dist/composables/auth/authGuard.d.ts +67 -0
  20. package/dist/composables/auth/index.d.ts +14 -0
  21. package/dist/composables/auth/types.d.ts +90 -0
  22. package/dist/composables/auth/useAuth.d.ts +45 -0
  23. package/dist/composables/auth/vCan.d.ts +44 -0
  24. package/dist/composables/defineRepository.d.ts +25 -0
  25. package/dist/composables/error/createErrorHandler.d.ts +22 -0
  26. package/dist/composables/error/defaultFeedbackHandler.d.ts +3 -0
  27. package/dist/composables/error/dispatchError.d.ts +6 -0
  28. package/dist/composables/error/index.d.ts +13 -0
  29. package/dist/composables/error/types.d.ts +28 -0
  30. package/dist/composables/error/useErrorHandler.d.ts +26 -0
  31. package/dist/composables/index.d.ts +15 -0
  32. package/dist/composables/request/composables.d.ts +44 -0
  33. package/dist/composables/request/controlGate.d.ts +21 -0
  34. package/dist/composables/request/createRequest.d.ts +31 -0
  35. package/dist/composables/request/index.d.ts +23 -0
  36. package/dist/composables/request/orchestrator.d.ts +16 -0
  37. package/dist/composables/request/requestBuilder.d.ts +34 -0
  38. package/dist/composables/request/transport/fetchTransport.d.ts +4 -0
  39. package/dist/composables/request/transport/xhrTransport.d.ts +4 -0
  40. package/dist/composables/request/types.d.ts +166 -0
  41. package/dist/composables/request/upload.d.ts +17 -0
  42. package/dist/composables/router/createRouterGuard.d.ts +50 -0
  43. package/dist/composables/router/defineCrudRoutes.d.ts +26 -0
  44. package/dist/composables/router/index.d.ts +14 -0
  45. package/dist/composables/router/types.d.ts +110 -0
  46. package/dist/composables/router/useMenuFromRoutes.d.ts +30 -0
  47. package/dist/composables/router/useTabStore.d.ts +35 -0
  48. package/dist/composables/setupCrud.d.ts +17 -0
  49. package/dist/composables/storage/createStorageAdapter.d.ts +22 -0
  50. package/dist/composables/storage/index.d.ts +12 -0
  51. package/dist/composables/storage/types.d.ts +14 -0
  52. package/dist/composables/storage/useStorage.d.ts +17 -0
  53. package/dist/composables/store/defineUserStore.d.ts +62 -0
  54. package/dist/composables/store/index.d.ts +10 -0
  55. package/dist/composables/types.d.ts +68 -0
  56. package/dist/crud.css +1308 -0
  57. package/dist/crud.js +3046 -0
  58. package/dist/index.d.ts +29 -0
  59. package/dist/setup.d.ts +95 -0
  60. package/dist/vite.config.build.d.ts +2 -0
  61. package/package.json +33 -0
package/dist/crud.js ADDED
@@ -0,0 +1,3046 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { defineComponent, ref, watch, provide, createVNode, inject, computed, Fragment, Comment, createTextVNode, mergeProps, isRef, toRef, reactive, createApp, shallowRef, triggerRef, resolveComponent, h, onMounted, onBeforeUnmount, onUnmounted } from "vue";
5
+ import { PaginationCore, usePagination } from "@kine-design/core";
6
+ import { useQueryClient, useMutation, useQuery, QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
7
+ import { createPinia, defineStore } from "pinia";
8
+ import { useRouter, useRoute } from "vue-router";
9
+ const LAYOUT_COLLAPSED_KEY = Symbol("kCrudLayoutCollapsed");
10
+ const LAYOUT_TOGGLE_KEY = Symbol("kCrudLayoutToggle");
11
+ const LAYOUT_SIDER_WIDTH_KEY = Symbol("kCrudLayoutSiderWidth");
12
+ const LAYOUT_COLLAPSED_WIDTH_KEY = Symbol("kCrudLayoutCollapsedWidth");
13
+ const KLayout = /* @__PURE__ */ defineComponent({
14
+ name: "KLayout",
15
+ props: {
16
+ /** 受控模式:外部传入 collapsed 状态 */
17
+ collapsed: {
18
+ type: Boolean,
19
+ default: void 0
20
+ },
21
+ /** 侧边栏展开宽度 */
22
+ siderWidth: {
23
+ type: String,
24
+ default: "240px"
25
+ },
26
+ /** 侧边栏折叠宽度 */
27
+ collapsedWidth: {
28
+ type: String,
29
+ default: "64px"
30
+ }
31
+ },
32
+ emits: ["update:collapsed"],
33
+ setup(props2, ctx) {
34
+ const innerCollapsed = ref(props2.collapsed ?? false);
35
+ watch(() => props2.collapsed, (val) => {
36
+ if (val !== void 0) {
37
+ innerCollapsed.value = val;
38
+ }
39
+ });
40
+ const toggleCollapsed = () => {
41
+ const next = !innerCollapsed.value;
42
+ innerCollapsed.value = next;
43
+ ctx.emit("update:collapsed", next);
44
+ };
45
+ provide(LAYOUT_COLLAPSED_KEY, innerCollapsed);
46
+ provide(LAYOUT_TOGGLE_KEY, toggleCollapsed);
47
+ provide(LAYOUT_SIDER_WIDTH_KEY, props2.siderWidth);
48
+ provide(LAYOUT_COLLAPSED_WIDTH_KEY, props2.collapsedWidth);
49
+ return () => {
50
+ var _a, _b;
51
+ return createVNode("div", {
52
+ "class": "k-crud-layout"
53
+ }, [(_b = (_a = ctx.slots).default) == null ? void 0 : _b.call(_a)]);
54
+ };
55
+ }
56
+ });
57
+ const KSider = /* @__PURE__ */ defineComponent({
58
+ name: "KSider",
59
+ setup(_, ctx) {
60
+ const collapsed = inject(LAYOUT_COLLAPSED_KEY);
61
+ const toggle = inject(LAYOUT_TOGGLE_KEY);
62
+ const siderWidth = inject(LAYOUT_SIDER_WIDTH_KEY, "240px");
63
+ const collapsedWidth = inject(LAYOUT_COLLAPSED_WIDTH_KEY, "64px");
64
+ const currentWidth = computed(() => {
65
+ if (!collapsed) return siderWidth;
66
+ return collapsed.value ? collapsedWidth : siderWidth;
67
+ });
68
+ const isCollapsed = computed(() => (collapsed == null ? void 0 : collapsed.value) ?? false);
69
+ const handleTriggerClick = () => {
70
+ toggle == null ? void 0 : toggle();
71
+ };
72
+ const defaultTrigger = () => createVNode("button", {
73
+ "class": "k-crud-sider__trigger-btn",
74
+ "onClick": handleTriggerClick,
75
+ "title": isCollapsed.value ? "展开侧边栏" : "折叠侧边栏"
76
+ }, [createVNode("span", {
77
+ "class": ["k-crud-sider__trigger-icon", isCollapsed.value && "k-crud-sider__trigger-icon--collapsed"]
78
+ }, ["‹"])]);
79
+ return () => {
80
+ var _a, _b;
81
+ return createVNode("aside", {
82
+ "class": ["k-crud-sider", isCollapsed.value && "k-crud-sider--collapsed"],
83
+ "style": {
84
+ width: currentWidth.value
85
+ }
86
+ }, [ctx.slots.logo && createVNode("div", {
87
+ "class": "k-crud-sider__logo"
88
+ }, [ctx.slots.logo()]), createVNode("div", {
89
+ "class": "k-crud-sider__body"
90
+ }, [(_b = (_a = ctx.slots).default) == null ? void 0 : _b.call(_a)]), createVNode("div", {
91
+ "class": "k-crud-sider__trigger"
92
+ }, [ctx.slots.trigger ? ctx.slots.trigger({
93
+ collapsed: isCollapsed.value,
94
+ toggle
95
+ }) : defaultTrigger()])]);
96
+ };
97
+ }
98
+ });
99
+ const KHeader = /* @__PURE__ */ defineComponent({
100
+ name: "KHeader",
101
+ setup(_, ctx) {
102
+ return () => {
103
+ var _a, _b;
104
+ return createVNode("header", {
105
+ "class": "k-crud-header"
106
+ }, [createVNode("div", {
107
+ "class": "k-crud-header__main"
108
+ }, [(_b = (_a = ctx.slots).default) == null ? void 0 : _b.call(_a)]), ctx.slots.extra && createVNode("div", {
109
+ "class": "k-crud-header__extra"
110
+ }, [ctx.slots.extra()])]);
111
+ };
112
+ }
113
+ });
114
+ const KContent = /* @__PURE__ */ defineComponent({
115
+ name: "KContent",
116
+ setup(_, ctx) {
117
+ return () => {
118
+ var _a, _b;
119
+ return createVNode("main", {
120
+ "class": "k-crud-content"
121
+ }, [(_b = (_a = ctx.slots).default) == null ? void 0 : _b.call(_a)]);
122
+ };
123
+ }
124
+ });
125
+ const props$1 = {
126
+ data: { type: Array, default: () => [] },
127
+ height: { type: String, default: null },
128
+ paramClass: { type: Boolean, default: false },
129
+ loading: { type: Boolean, default: false }
130
+ };
131
+ function useTable() {
132
+ const error = (msg) => console.warn(`[水墨UI表格组件] ${msg}`);
133
+ const initTable = (renders, columns, data) => {
134
+ const tbodyTrList = [];
135
+ data.forEach(() => {
136
+ tbodyTrList.push([]);
137
+ });
138
+ const getData = (i, param) => {
139
+ if (data[i] && data[i][param]) {
140
+ return data[i][param];
141
+ }
142
+ return "";
143
+ };
144
+ const pushTd = (param, bodySlot, style) => {
145
+ if (param) {
146
+ tbodyTrList.forEach((t, i) => {
147
+ t.push(renders.tbodyTr({
148
+ data: getData(i, param),
149
+ style,
150
+ param,
151
+ slot: bodySlot,
152
+ slotInfo: {
153
+ data: data[i],
154
+ index: i
155
+ }
156
+ }));
157
+ });
158
+ } else {
159
+ error("param is undefined, column without param will be ignored!");
160
+ }
161
+ };
162
+ const getStyle = (options) => {
163
+ if (!options || !options.width) {
164
+ return {};
165
+ }
166
+ const numberWidth = Number(options.width);
167
+ if (!isNaN(numberWidth)) {
168
+ return { width: numberWidth + "px" };
169
+ }
170
+ return { width: options.width };
171
+ };
172
+ const initTHead = () => {
173
+ const ths = (columns ?? []).filter((column) => {
174
+ if (!column.props) {
175
+ error("column.props is undefined, column without param will be ignored!");
176
+ return false;
177
+ }
178
+ return true;
179
+ }).map((column) => {
180
+ const slots = renders.initSlot(column);
181
+ let bodySlot;
182
+ let headSlot;
183
+ const style = getStyle(column.props);
184
+ if (slots) {
185
+ bodySlot = slots.body;
186
+ headSlot = slots.head;
187
+ }
188
+ pushTd(column.props.param, bodySlot, style);
189
+ return renders.theadTh({
190
+ label: column.props.label,
191
+ param: column.props.param,
192
+ slot: headSlot,
193
+ style
194
+ });
195
+ });
196
+ return renders.thead(ths);
197
+ };
198
+ const thead = initTHead();
199
+ const tbody = tbodyTrList.length > 0 ? renders.tbody(tbodyTrList.map((tds, i) => renders.tbodyTrs(tds, i))) : renders.empty;
200
+ return {
201
+ thead,
202
+ tbody
203
+ };
204
+ };
205
+ return {
206
+ initTable,
207
+ error
208
+ };
209
+ }
210
+ const TableCore = {
211
+ props: props$1
212
+ };
213
+ const {
214
+ props
215
+ } = TableCore;
216
+ const KTable = /* @__PURE__ */ defineComponent((_props, {
217
+ slots
218
+ }) => {
219
+ const p = _props;
220
+ const {
221
+ initTable
222
+ } = useTable();
223
+ return () => {
224
+ var _a, _b;
225
+ const columns = [];
226
+ const defaultSlot = ((_a = slots.default) == null ? void 0 : _a.call(slots)) ?? [];
227
+ defaultSlot.forEach((s) => {
228
+ var _a2;
229
+ if (s.type === Fragment) {
230
+ (_a2 = s.children) == null ? void 0 : _a2.forEach((c) => columns.push(c));
231
+ return;
232
+ }
233
+ if (s.type === Comment) return;
234
+ if (typeof s.type === "object" && s.type.name === "KTableColumn") {
235
+ columns.push(s);
236
+ }
237
+ });
238
+ const style = p.height ? {
239
+ height: p.height
240
+ } : void 0;
241
+ const {
242
+ thead,
243
+ tbody
244
+ } = initTable({
245
+ // 数据为空时的占位渲染
246
+ empty: createVNode("tbody", {
247
+ "class": "m-table-empty k-table-empty"
248
+ }, [createVNode("tr", null, [createVNode("th", {
249
+ "colspan": columns.length
250
+ }, [((_b = slots.empty) == null ? void 0 : _b.call(slots)) ?? "暂无数据"])])]),
251
+ // 渲染单个数据单元格
252
+ tbodyTr: ({
253
+ data,
254
+ param,
255
+ slot,
256
+ style: cellStyle,
257
+ slotInfo
258
+ }) => createVNode("td", {
259
+ "style": cellStyle,
260
+ "class": ["m-td", "k-td", p.paramClass ? `m-td-${param}` : null, p.paramClass ? `k-td-${param}` : null]
261
+ }, [slot ? slot({
262
+ data: slotInfo == null ? void 0 : slotInfo.data,
263
+ index: slotInfo == null ? void 0 : slotInfo.index
264
+ }) : data]),
265
+ // 渲染单个表头单元格
266
+ theadTh: ({
267
+ label,
268
+ param,
269
+ slot,
270
+ style: cellStyle
271
+ }) => createVNode("th", {
272
+ "class": ["m-th", "k-th", p.paramClass ? `m-th-${param}` : null, p.paramClass ? `k-th-${param}` : null],
273
+ "style": cellStyle
274
+ }, [slot ? slot() : label]),
275
+ // 渲染整个 thead
276
+ thead: (ths) => createVNode("thead", {
277
+ "class": "m-thead k-thead"
278
+ }, [createVNode("tr", {
279
+ "class": "m-tr k-tr"
280
+ }, [ths])]),
281
+ // 渲染整个 tbody
282
+ tbody: (trs) => createVNode("tbody", {
283
+ "class": "m-tbody k-tbody"
284
+ }, [trs]),
285
+ // 渲染单行 tr
286
+ tbodyTrs: (tds, i) => createVNode("tr", {
287
+ "class": "m-tr k-tr",
288
+ "key": i
289
+ }, [...tds]),
290
+ // 从 KTableColumn VNode 中提取 body / head 插槽
291
+ initSlot: (tableColumn) => {
292
+ const children = tableColumn.children;
293
+ if (!children || Array.isArray(children) || typeof children !== "object") {
294
+ return void 0;
295
+ }
296
+ return {
297
+ body: children.default,
298
+ head: children.head
299
+ };
300
+ }
301
+ }, columns, p.data);
302
+ return createVNode("div", {
303
+ "class": ["m-table", "k-table", p.loading && "k-table-loading-wrap"],
304
+ "style": style
305
+ }, [p.loading && createVNode("div", {
306
+ "class": "k-table-loading-overlay"
307
+ }, [createVNode("div", {
308
+ "class": "k-table-loading-spinner"
309
+ }, null)]), createVNode("table", {
310
+ "class": "m-table-inner k-table-inner"
311
+ }, [thead, tbody])]);
312
+ };
313
+ }, {
314
+ name: "KTable",
315
+ props
316
+ });
317
+ const {
318
+ props: paginationProps
319
+ } = PaginationCore;
320
+ const KPagination = /* @__PURE__ */ defineComponent((_props, {
321
+ emit
322
+ }) => {
323
+ const p = _props;
324
+ const currentValue = ref(p.modelValue ?? p.defaultCurrent ?? 1);
325
+ watch(() => p.modelValue, (val) => {
326
+ if (val !== void 0) {
327
+ currentValue.value = val;
328
+ }
329
+ });
330
+ const {
331
+ getPageNumList,
332
+ getPageBtnLength
333
+ } = usePagination(p, currentValue);
334
+ const changePage = (page) => {
335
+ const total = getPageBtnLength();
336
+ if (page < 1 || page > total) return;
337
+ currentValue.value = page;
338
+ emit("update:modelValue", page);
339
+ emit("change", page);
340
+ };
341
+ const toPrev = () => {
342
+ if (currentValue.value === 1) return;
343
+ changePage(currentValue.value - 1);
344
+ };
345
+ const toNext = () => {
346
+ if (currentValue.value === getPageBtnLength()) return;
347
+ changePage(currentValue.value + 1);
348
+ };
349
+ return () => {
350
+ const pages = getPageNumList();
351
+ const layoutKeys = p.layout.split(",").map((k) => k.trim());
352
+ const totalPages = getPageBtnLength();
353
+ const btnGetter = {
354
+ /** 总条数区域 */
355
+ total: () => createVNode("div", {
356
+ "class": "m-page-total k-page-total"
357
+ }, [createTextVNode("共 "), createVNode("span", null, [p.total]), createTextVNode(" 条")]),
358
+ /** 上一页按钮 */
359
+ prev: () => createVNode("div", {
360
+ "class": ["m-page-prev", "k-page-prev", currentValue.value === 1 ? "m-page-prev-disabled k-page-prev-disabled" : ""],
361
+ "onClick": toPrev
362
+ }, [createTextVNode("‹")]),
363
+ /** 页码列表(含折叠省略号) */
364
+ pager: () => pages.map((page) => createVNode("div", {
365
+ "key": `${page.type}-${page.value}`,
366
+ "class": ["m-pager", "k-pager", page.type === "folded" ? "m-pager-folded k-pager-folded" : "", page.isCurrent ? "m-pager-current k-pager-current" : ""],
367
+ "onClick": () => changePage(page.jump)
368
+ }, [page.value])),
369
+ /** 下一页按钮 */
370
+ next: () => createVNode("div", {
371
+ "class": ["m-page-next", "k-page-next", currentValue.value === totalPages ? "m-page-next-disabled k-page-next-disabled" : ""],
372
+ "onClick": toNext
373
+ }, [createTextVNode("›")])
374
+ };
375
+ return createVNode("div", {
376
+ "class": "m-pagination k-pagination"
377
+ }, [layoutKeys.map((key) => {
378
+ var _a;
379
+ return (_a = btnGetter[key]) == null ? void 0 : _a.call(btnGetter);
380
+ })]);
381
+ };
382
+ }, {
383
+ name: "KPagination",
384
+ props: paginationProps,
385
+ emits: ["update:modelValue", "change"]
386
+ });
387
+ const KSearchTable = /* @__PURE__ */ defineComponent({
388
+ name: "KSearchTable",
389
+ props: {
390
+ /** 加载状态 */
391
+ loading: {
392
+ type: Boolean,
393
+ default: false
394
+ },
395
+ /** 表格数据 */
396
+ data: {
397
+ type: Array,
398
+ default: () => []
399
+ },
400
+ /** 总条数 */
401
+ total: {
402
+ type: Number,
403
+ default: 0
404
+ },
405
+ /** 当前页(受控,配合 v-model:page) */
406
+ page: {
407
+ type: Number,
408
+ default: 1
409
+ },
410
+ /** 每页条数(受控,配合 v-model:pageSize) */
411
+ pageSize: {
412
+ type: Number,
413
+ default: 20
414
+ },
415
+ /** 是否显示搜索区 */
416
+ searchable: {
417
+ type: Boolean,
418
+ default: true
419
+ },
420
+ /** 是否显示分页 */
421
+ pageable: {
422
+ type: Boolean,
423
+ default: true
424
+ },
425
+ /** 透传给 KTable 的额外 props */
426
+ tableProps: {
427
+ type: Object,
428
+ default: () => ({})
429
+ }
430
+ },
431
+ emits: [
432
+ "update:page",
433
+ "update:pageSize",
434
+ /** 点击搜索按钮 */
435
+ "search",
436
+ /** 点击重置按钮 */
437
+ "reset"
438
+ ],
439
+ setup(props2, ctx) {
440
+ const onSearch = () => {
441
+ ctx.emit("search");
442
+ };
443
+ const onReset = () => {
444
+ ctx.emit("reset");
445
+ };
446
+ const onPageChange = (page) => {
447
+ ctx.emit("update:page", page);
448
+ };
449
+ return () => {
450
+ var _a, _b;
451
+ return createVNode("div", {
452
+ "class": ["k-search-table", props2.loading ? "k-search-table--loading" : ""]
453
+ }, [props2.searchable && createVNode("div", {
454
+ "class": "k-search-table-search"
455
+ }, [createVNode("div", {
456
+ "class": "k-search-table-search-form"
457
+ }, [(_b = (_a = ctx.slots).search) == null ? void 0 : _b.call(_a)]), createVNode("div", {
458
+ "class": "k-search-table-search-actions"
459
+ }, [createVNode("button", {
460
+ "type": "button",
461
+ "class": "k-search-table-btn k-search-table-btn--primary",
462
+ "onClick": onSearch
463
+ }, [createTextVNode("搜索")]), createVNode("button", {
464
+ "type": "button",
465
+ "class": "k-search-table-btn",
466
+ "onClick": onReset
467
+ }, [createTextVNode("重置")])])]), ctx.slots.toolbar && createVNode("div", {
468
+ "class": "k-search-table-toolbar"
469
+ }, [ctx.slots.toolbar()]), createVNode("div", {
470
+ "class": "k-search-table-body"
471
+ }, [createVNode(KTable, mergeProps({
472
+ "data": props2.data
473
+ }, props2.tableProps), {
474
+ default: ctx.slots.default,
475
+ empty: ctx.slots.empty
476
+ })]), props2.pageable && createVNode("div", {
477
+ "class": "k-search-table-pagination"
478
+ }, [createVNode(KPagination, {
479
+ "modelValue": props2.page,
480
+ "total": props2.total,
481
+ "pageSize": props2.pageSize,
482
+ "onUpdate:modelValue": onPageChange
483
+ }, null)])]);
484
+ };
485
+ }
486
+ });
487
+ const KLoginPage = /* @__PURE__ */ defineComponent({
488
+ name: "KLoginPage",
489
+ props: {
490
+ /** 系统名称 */
491
+ title: {
492
+ type: String,
493
+ default: "系统登录"
494
+ },
495
+ /** 登录中状态 */
496
+ loading: {
497
+ type: Boolean,
498
+ default: false
499
+ },
500
+ /** 登录提交回调 */
501
+ onSubmit: {
502
+ type: Function,
503
+ default: void 0
504
+ }
505
+ },
506
+ setup(props2, ctx) {
507
+ const username = ref("");
508
+ const password = ref("");
509
+ const remember = ref(false);
510
+ const handleSubmit = async (e) => {
511
+ var _a;
512
+ e.preventDefault();
513
+ if (props2.loading) return;
514
+ await ((_a = props2.onSubmit) == null ? void 0 : _a.call(props2, {
515
+ username: username.value,
516
+ password: password.value,
517
+ remember: remember.value
518
+ }));
519
+ };
520
+ return () => createVNode("div", {
521
+ "class": "k-login-page"
522
+ }, [createVNode("div", {
523
+ "class": "k-login-card"
524
+ }, [ctx.slots.logo && createVNode("div", {
525
+ "class": "k-login-logo"
526
+ }, [ctx.slots.logo()]), createVNode("h2", {
527
+ "class": "k-login-title"
528
+ }, [props2.title]), createVNode("form", {
529
+ "class": "k-login-form",
530
+ "onSubmit": handleSubmit
531
+ }, [createVNode("div", {
532
+ "class": "k-login-form-item"
533
+ }, [createVNode("label", null, [createTextVNode("用户名")]), createVNode("input", {
534
+ "type": "text",
535
+ "value": username.value,
536
+ "placeholder": "请输入用户名",
537
+ "autocomplete": "username",
538
+ "required": true,
539
+ "onInput": (e) => {
540
+ username.value = e.target.value;
541
+ }
542
+ }, null)]), createVNode("div", {
543
+ "class": "k-login-form-item"
544
+ }, [createVNode("label", null, [createTextVNode("密码")]), createVNode("input", {
545
+ "type": "password",
546
+ "value": password.value,
547
+ "placeholder": "请输入密码",
548
+ "autocomplete": "current-password",
549
+ "required": true,
550
+ "onInput": (e) => {
551
+ password.value = e.target.value;
552
+ }
553
+ }, null)]), createVNode("label", {
554
+ "class": "k-login-remember"
555
+ }, [createVNode("input", {
556
+ "type": "checkbox",
557
+ "checked": remember.value,
558
+ "onChange": (e) => {
559
+ remember.value = e.target.checked;
560
+ }
561
+ }, null), createTextVNode("记住我")]), createVNode("button", {
562
+ "type": "submit",
563
+ "class": ["k-login-submit", props2.loading && "k-login-submit--loading"],
564
+ "disabled": props2.loading
565
+ }, [props2.loading ? "登录中..." : "登 录"])]), ctx.slots.extra && createVNode("div", {
566
+ "class": "k-login-extra"
567
+ }, [ctx.slots.extra()])]), ctx.slots.footer && createVNode("div", {
568
+ "class": "k-login-footer"
569
+ }, [ctx.slots.footer()])]);
570
+ }
571
+ });
572
+ const KPageHeader = /* @__PURE__ */ defineComponent({
573
+ name: "KPageHeader",
574
+ props: {
575
+ /** 页面标题 */
576
+ title: {
577
+ type: String,
578
+ default: ""
579
+ },
580
+ /** 副标题 */
581
+ subtitle: {
582
+ type: String,
583
+ default: ""
584
+ },
585
+ /** 是否显示返回按钮 */
586
+ backable: {
587
+ type: Boolean,
588
+ default: false
589
+ },
590
+ /** 返回回调 */
591
+ onBack: {
592
+ type: Function,
593
+ default: void 0
594
+ }
595
+ },
596
+ setup(props2, ctx) {
597
+ const handleBack = () => {
598
+ var _a;
599
+ (_a = props2.onBack) == null ? void 0 : _a.call(props2);
600
+ };
601
+ return () => createVNode("div", {
602
+ "class": "k-page-header"
603
+ }, [ctx.slots.breadcrumb && createVNode("div", {
604
+ "class": "k-page-header-breadcrumb"
605
+ }, [ctx.slots.breadcrumb()]), createVNode("div", {
606
+ "class": "k-page-header-main"
607
+ }, [createVNode("div", {
608
+ "class": "k-page-header-left"
609
+ }, [props2.backable && createVNode("button", {
610
+ "type": "button",
611
+ "class": "k-page-header-back",
612
+ "aria-label": "返回",
613
+ "onClick": handleBack
614
+ }, [createTextVNode("←")]), createVNode("div", {
615
+ "class": "k-page-header-heading"
616
+ }, [props2.title && createVNode("h2", {
617
+ "class": "k-page-header-title"
618
+ }, [props2.title]), props2.subtitle && createVNode("p", {
619
+ "class": "k-page-header-subtitle"
620
+ }, [props2.subtitle])])]), ctx.slots.extra && createVNode("div", {
621
+ "class": "k-page-header-extra"
622
+ }, [ctx.slots.extra()])]), ctx.slots.default && createVNode("div", {
623
+ "class": "k-page-header-content"
624
+ }, [ctx.slots.default()])]);
625
+ }
626
+ });
627
+ function normalizeRef(value) {
628
+ return isRef(value) ? value : toRef(() => value);
629
+ }
630
+ function defineRepository(domain, endpoints) {
631
+ const useList = (params) => {
632
+ const paramsRef = normalizeRef(params ?? {});
633
+ const query = useQuery({
634
+ // query key 包含 params,params 变化时自动重新请求
635
+ queryKey: computed(() => [domain, "list", paramsRef.value]),
636
+ queryFn: () => {
637
+ if (!endpoints.list) {
638
+ return Promise.reject(new Error(`[defineRepository:${domain}] list endpoint 未定义`));
639
+ }
640
+ return endpoints.list(paramsRef.value);
641
+ }
642
+ });
643
+ return {
644
+ data: computed(() => query.data.value),
645
+ isLoading: computed(() => query.isLoading.value),
646
+ isFetching: computed(() => query.isFetching.value),
647
+ isError: computed(() => query.isError.value),
648
+ error: computed(() => query.error.value),
649
+ refetch: () => {
650
+ query.refetch();
651
+ }
652
+ };
653
+ };
654
+ const useDetail = (id) => {
655
+ const idRef = normalizeRef(id);
656
+ const query = useQuery({
657
+ queryKey: computed(() => [domain, "detail", idRef.value]),
658
+ queryFn: () => {
659
+ if (!endpoints.detail) {
660
+ return Promise.reject(new Error(`[defineRepository:${domain}] detail endpoint 未定义`));
661
+ }
662
+ return endpoints.detail(idRef.value);
663
+ },
664
+ // id 为空时不发请求
665
+ enabled: computed(() => idRef.value !== void 0 && idRef.value !== null && idRef.value !== "")
666
+ });
667
+ return {
668
+ data: computed(() => query.data.value),
669
+ isLoading: computed(() => query.isLoading.value),
670
+ isFetching: computed(() => query.isFetching.value),
671
+ isError: computed(() => query.isError.value),
672
+ error: computed(() => query.error.value),
673
+ refetch: () => {
674
+ query.refetch();
675
+ }
676
+ };
677
+ };
678
+ function buildInvalidateHandler(options) {
679
+ return async (queryClient) => {
680
+ var _a;
681
+ await queryClient.invalidateQueries({ queryKey: [domain] });
682
+ if ((_a = options == null ? void 0 : options.invalidates) == null ? void 0 : _a.length) {
683
+ await Promise.all(
684
+ options.invalidates.map(
685
+ (d) => queryClient.invalidateQueries({ queryKey: [d] })
686
+ )
687
+ );
688
+ }
689
+ };
690
+ }
691
+ const useCreate = (options) => {
692
+ const queryClient = useQueryClient();
693
+ const invalidate = buildInvalidateHandler(options);
694
+ const mutation = useMutation({
695
+ mutationFn: (data) => {
696
+ if (!endpoints.create) {
697
+ return Promise.reject(new Error(`[defineRepository:${domain}] create endpoint 未定义`));
698
+ }
699
+ return endpoints.create(data);
700
+ },
701
+ onSuccess: () => invalidate(queryClient)
702
+ });
703
+ return {
704
+ // 类型断言:确保签名与泛型约束一致
705
+ mutate: (data) => mutation.mutateAsync(data),
706
+ isPending: computed(() => mutation.isPending.value),
707
+ isError: computed(() => mutation.isError.value),
708
+ error: computed(() => mutation.error.value)
709
+ };
710
+ };
711
+ const useUpdate = (options) => {
712
+ const queryClient = useQueryClient();
713
+ const invalidate = buildInvalidateHandler(options);
714
+ const mutation = useMutation({
715
+ mutationFn: ({ id, data }) => {
716
+ if (!endpoints.update) {
717
+ return Promise.reject(new Error(`[defineRepository:${domain}] update endpoint 未定义`));
718
+ }
719
+ return endpoints.update(id, data);
720
+ },
721
+ onSuccess: () => invalidate(queryClient)
722
+ });
723
+ return {
724
+ mutate: (variables) => mutation.mutateAsync(variables),
725
+ isPending: computed(() => mutation.isPending.value),
726
+ isError: computed(() => mutation.isError.value),
727
+ error: computed(() => mutation.error.value)
728
+ };
729
+ };
730
+ const useRemove = (options) => {
731
+ const queryClient = useQueryClient();
732
+ const invalidate = buildInvalidateHandler(options);
733
+ const mutation = useMutation({
734
+ mutationFn: (id) => {
735
+ if (!endpoints.remove) {
736
+ return Promise.reject(new Error(`[defineRepository:${domain}] remove endpoint 未定义`));
737
+ }
738
+ return endpoints.remove(id);
739
+ },
740
+ onSuccess: () => invalidate(queryClient)
741
+ });
742
+ return {
743
+ mutate: (id) => mutation.mutateAsync(id),
744
+ isPending: computed(() => mutation.isPending.value),
745
+ isError: computed(() => mutation.isError.value),
746
+ error: computed(() => mutation.error.value)
747
+ };
748
+ };
749
+ return { useList, useDetail, useCreate, useUpdate, useRemove };
750
+ }
751
+ function createStorageAdapter(storageType) {
752
+ function resolveStorage() {
753
+ if (typeof window === "undefined") return null;
754
+ return storageType === "local" ? window.localStorage : window.sessionStorage;
755
+ }
756
+ return {
757
+ get(key) {
758
+ const storage = resolveStorage();
759
+ if (!storage) return null;
760
+ const raw = storage.getItem(key);
761
+ if (raw === null) return null;
762
+ try {
763
+ return JSON.parse(raw);
764
+ } catch {
765
+ return raw;
766
+ }
767
+ },
768
+ set(key, value) {
769
+ const storage = resolveStorage();
770
+ if (!storage) return;
771
+ storage.setItem(key, typeof value === "string" ? value : JSON.stringify(value));
772
+ },
773
+ remove(key) {
774
+ const storage = resolveStorage();
775
+ if (!storage) return;
776
+ storage.removeItem(key);
777
+ }
778
+ };
779
+ }
780
+ const AUTH_INJECT_KEY = Symbol("kine-design:auth");
781
+ const SCOPE_LEVEL = {
782
+ own: 1,
783
+ department: 2,
784
+ all: 3,
785
+ "*": Infinity
786
+ };
787
+ function isScopeSatisfied(permScope, requiredScope) {
788
+ const permLevel = SCOPE_LEVEL[permScope ?? "all"] ?? 0;
789
+ const requiredLevel = SCOPE_LEVEL[requiredScope] ?? 0;
790
+ return permLevel >= requiredLevel;
791
+ }
792
+ function matchesPermission(perm, resource, action, scope) {
793
+ if (perm.resource !== resource) return false;
794
+ const actionMatched = perm.action === "*" || perm.action === action;
795
+ if (!actionMatched) return false;
796
+ if (scope === void 0) return true;
797
+ return isScopeSatisfied(perm.scope, scope);
798
+ }
799
+ const STORAGE_KEY_TOKEN = "kine_auth_token";
800
+ const STORAGE_KEY_USER = "kine_auth_user";
801
+ function buildAuthReturn(state, options, adapter) {
802
+ const user = computed(() => state.user);
803
+ const permissions = computed(() => state.permissions);
804
+ const token = computed(() => state.token);
805
+ const isAuthenticated = computed(() => state.user !== null && state.token !== null);
806
+ function can(resource, action, scope) {
807
+ if (!state.permissions) return false;
808
+ return state.permissions.some((p) => matchesPermission(p, resource, action, scope));
809
+ }
810
+ function persistToStorage() {
811
+ if (!adapter) return;
812
+ adapter.set(STORAGE_KEY_TOKEN, state.token);
813
+ adapter.set(STORAGE_KEY_USER, state.user);
814
+ }
815
+ function clearStorage() {
816
+ if (!adapter) return;
817
+ adapter.remove(STORAGE_KEY_TOKEN);
818
+ adapter.remove(STORAGE_KEY_USER);
819
+ }
820
+ async function login() {
821
+ const result = await options.fetchUser();
822
+ state.user = result.user;
823
+ state.permissions = result.permissions;
824
+ state.token = result.token;
825
+ persistToStorage();
826
+ }
827
+ function loginWith(result) {
828
+ state.user = result.user;
829
+ state.permissions = result.permissions;
830
+ state.token = result.token;
831
+ persistToStorage();
832
+ }
833
+ function logout() {
834
+ state.user = null;
835
+ state.permissions = [];
836
+ state.token = null;
837
+ clearStorage();
838
+ }
839
+ async function restore() {
840
+ if (!adapter) return;
841
+ const storedToken = adapter.get(STORAGE_KEY_TOKEN);
842
+ const storedUser = adapter.get(STORAGE_KEY_USER);
843
+ if (!storedToken || !storedUser) return;
844
+ state.token = storedToken;
845
+ state.user = storedUser;
846
+ try {
847
+ const result = await options.fetchUser();
848
+ state.user = result.user;
849
+ state.permissions = result.permissions;
850
+ state.token = result.token;
851
+ persistToStorage();
852
+ } catch {
853
+ state.user = null;
854
+ state.permissions = [];
855
+ state.token = null;
856
+ clearStorage();
857
+ }
858
+ }
859
+ return { user, permissions, token, isAuthenticated, can, login, loginWith, logout, restore };
860
+ }
861
+ function createAuth(app, options) {
862
+ const state = reactive({
863
+ user: null,
864
+ permissions: [],
865
+ token: null
866
+ });
867
+ const storageType = options.storage ?? false;
868
+ const adapter = storageType !== false ? createStorageAdapter(storageType) : null;
869
+ const auth = buildAuthReturn(state, options, adapter);
870
+ if (storageType === "local" && typeof window !== "undefined") {
871
+ const onStorageChange = (e) => {
872
+ if (e.key !== STORAGE_KEY_TOKEN) return;
873
+ if (e.newValue === null) {
874
+ state.user = null;
875
+ state.permissions = [];
876
+ state.token = null;
877
+ }
878
+ };
879
+ window.addEventListener("storage", onStorageChange);
880
+ }
881
+ app.provide(AUTH_INJECT_KEY, { auth, onUnauthorized: options.onUnauthorized });
882
+ return auth;
883
+ }
884
+ function useAuth() {
885
+ const provided = inject(AUTH_INJECT_KEY);
886
+ if (!provided) {
887
+ throw new Error("[useAuth] 未找到 auth 实例,请先在根 App 调用 createAuth()。");
888
+ }
889
+ return provided.auth;
890
+ }
891
+ function createAuthGuard(auth, options) {
892
+ const loginPath = (options == null ? void 0 : options.loginPath) ?? "/login";
893
+ const forbiddenPath = (options == null ? void 0 : options.forbiddenPath) ?? "/403";
894
+ return (to) => {
895
+ const meta = to.meta;
896
+ const requiresAuth = meta.requiresAuth !== false;
897
+ if (requiresAuth && !auth.isAuthenticated.value) {
898
+ if (loginPath === false) return void 0;
899
+ if (to.path === loginPath) return void 0;
900
+ return loginPath;
901
+ }
902
+ if (meta.can) {
903
+ const { resource, action } = meta.can;
904
+ if (!auth.can(resource, action)) {
905
+ if (forbiddenPath === false) return void 0;
906
+ if (to.path === forbiddenPath) return void 0;
907
+ return forbiddenPath;
908
+ }
909
+ }
910
+ return void 0;
911
+ };
912
+ }
913
+ function parseBinding(value) {
914
+ if (typeof value === "object") return value;
915
+ const parts = value.split(":");
916
+ const resource = parts[0] ?? "";
917
+ const action = parts[1] ?? "";
918
+ const scope = parts[2];
919
+ return { resource, action, scope };
920
+ }
921
+ function applyVisibility(el, auth, binding) {
922
+ const { resource, action, scope } = parseBinding(binding);
923
+ const allowed = auth.can(resource, action, scope);
924
+ el.style.display = allowed ? "" : "none";
925
+ }
926
+ function createVCan(auth) {
927
+ return {
928
+ mounted(el, binding) {
929
+ applyVisibility(el, auth, binding.value);
930
+ },
931
+ updated(el, binding) {
932
+ applyVisibility(el, auth, binding.value);
933
+ }
934
+ };
935
+ }
936
+ function buildCacheKey(method, url, body) {
937
+ const bodyStr = body !== void 0 && body !== null ? stableStringify(body) : "";
938
+ return `${method}:${url}:${bodyStr}`;
939
+ }
940
+ function stableStringify(obj) {
941
+ if (obj === null || obj === void 0) return "";
942
+ if (typeof obj !== "object") return String(obj);
943
+ if (obj instanceof FormData || obj instanceof Blob || obj instanceof File) return "";
944
+ if (Array.isArray(obj)) return `[${obj.map(stableStringify).join(",")}]`;
945
+ const sorted = Object.keys(obj).sort();
946
+ const pairs = sorted.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`);
947
+ return `{${pairs.join(",")}}`;
948
+ }
949
+ class ControlGate {
950
+ constructor() {
951
+ /** 防抖:取消前一个 timer */
952
+ __publicField(this, "debounceMap", /* @__PURE__ */ new Map());
953
+ /** 节流:记录上次执行时间 */
954
+ __publicField(this, "throttleMap", /* @__PURE__ */ new Map());
955
+ /** 去重:共享同一个 Promise */
956
+ __publicField(this, "deduplicateMap", /* @__PURE__ */ new Map());
957
+ }
958
+ /**
959
+ * 通过控制门执行请求。
960
+ * 按配置依次应用防抖、节流、去重策略。
961
+ */
962
+ async execute(key, fn, policy) {
963
+ if (policy.debounce !== void 0 && policy.debounce > 0) {
964
+ return this.withDebounce(key, fn, policy.debounce);
965
+ }
966
+ if (policy.throttle !== void 0 && policy.throttle > 0) {
967
+ await this.waitThrottle(key, policy.throttle);
968
+ }
969
+ if (policy.deduplicate) {
970
+ return this.withDeduplicate(key, fn);
971
+ }
972
+ return fn();
973
+ }
974
+ /** 清理所有挂起的 timer 和状态 */
975
+ dispose() {
976
+ for (const entry of this.debounceMap.values()) {
977
+ clearTimeout(entry.timer);
978
+ entry.reject(new Error("ControlGate 已销毁"));
979
+ }
980
+ this.debounceMap.clear();
981
+ this.throttleMap.clear();
982
+ this.deduplicateMap.clear();
983
+ }
984
+ // ── 防抖 ──────────────────────────────────────────────────────────────────
985
+ withDebounce(key, fn, delay) {
986
+ const prev = this.debounceMap.get(key);
987
+ if (prev) {
988
+ clearTimeout(prev.timer);
989
+ prev.reject(new Error("请求已被防抖取消"));
990
+ }
991
+ return new Promise((resolve, reject) => {
992
+ const timer = setTimeout(async () => {
993
+ this.debounceMap.delete(key);
994
+ try {
995
+ resolve(await fn());
996
+ } catch (e) {
997
+ reject(e);
998
+ }
999
+ }, delay);
1000
+ this.debounceMap.set(key, { timer, reject });
1001
+ });
1002
+ }
1003
+ // ── 节流 ──────────────────────────────────────────────────────────────────
1004
+ waitThrottle(key, interval) {
1005
+ const lastTime = this.throttleMap.get(key) ?? 0;
1006
+ const now = Date.now();
1007
+ const elapsed = now - lastTime;
1008
+ if (elapsed >= interval) {
1009
+ this.throttleMap.set(key, now);
1010
+ return Promise.resolve();
1011
+ }
1012
+ const waitTime = interval - elapsed;
1013
+ return new Promise((resolve) => {
1014
+ setTimeout(() => {
1015
+ this.throttleMap.set(key, Date.now());
1016
+ resolve();
1017
+ }, waitTime);
1018
+ });
1019
+ }
1020
+ // ── 去重 ──────────────────────────────────────────────────────────────────
1021
+ withDeduplicate(key, fn) {
1022
+ const existing = this.deduplicateMap.get(key);
1023
+ if (existing) return existing;
1024
+ const promise = fn().finally(() => {
1025
+ this.deduplicateMap.delete(key);
1026
+ });
1027
+ this.deduplicateMap.set(key, promise);
1028
+ return promise;
1029
+ }
1030
+ }
1031
+ function headersToRecord(headers) {
1032
+ const record = {};
1033
+ headers.forEach((value, key) => {
1034
+ record[key.toLowerCase()] = value;
1035
+ });
1036
+ return record;
1037
+ }
1038
+ function wrapFetchResponse(response) {
1039
+ return {
1040
+ status: response.status,
1041
+ statusText: response.statusText,
1042
+ headers: headersToRecord(response.headers),
1043
+ body: response.body,
1044
+ text: () => response.text(),
1045
+ json: () => response.json(),
1046
+ blob: () => response.blob()
1047
+ };
1048
+ }
1049
+ class FetchTransport {
1050
+ async send(request) {
1051
+ const controller = new AbortController();
1052
+ if (request.signal) {
1053
+ if (request.signal.aborted) {
1054
+ controller.abort(request.signal.reason);
1055
+ } else {
1056
+ request.signal.addEventListener("abort", () => {
1057
+ controller.abort(request.signal.reason);
1058
+ }, { once: true });
1059
+ }
1060
+ }
1061
+ let timeoutId;
1062
+ if (request.timeout !== void 0 && request.timeout > 0) {
1063
+ timeoutId = setTimeout(() => controller.abort("timeout"), request.timeout);
1064
+ }
1065
+ try {
1066
+ const response = await fetch(request.url, {
1067
+ method: request.method,
1068
+ headers: request.headers,
1069
+ body: request.body,
1070
+ signal: controller.signal
1071
+ });
1072
+ return wrapFetchResponse(response);
1073
+ } catch (error) {
1074
+ if (error instanceof DOMException && error.name === "AbortError") {
1075
+ if (controller.signal.reason === "timeout") {
1076
+ throw { type: "timeout" };
1077
+ }
1078
+ throw { type: "aborted" };
1079
+ }
1080
+ throw error;
1081
+ } finally {
1082
+ if (timeoutId !== void 0) {
1083
+ clearTimeout(timeoutId);
1084
+ }
1085
+ }
1086
+ }
1087
+ }
1088
+ class BusinessError extends Error {
1089
+ constructor(message) {
1090
+ super(message);
1091
+ __publicField(this, "name", "BusinessError");
1092
+ }
1093
+ }
1094
+ class NetworkRequestError extends Error {
1095
+ constructor(detail) {
1096
+ super(networkErrorMessage(detail));
1097
+ __publicField(this, "name", "NetworkRequestError");
1098
+ __publicField(this, "detail");
1099
+ this.detail = detail;
1100
+ }
1101
+ }
1102
+ function networkErrorMessage(e) {
1103
+ switch (e.type) {
1104
+ case "invalidURL":
1105
+ return `无效的 URL: ${e.url}`;
1106
+ case "timeout":
1107
+ return "请求超时";
1108
+ case "httpError":
1109
+ return `HTTP ${e.status} ${e.statusText}`;
1110
+ case "decodingFailed":
1111
+ return `解码失败: ${e.reason}`;
1112
+ case "aborted":
1113
+ return "请求已取消";
1114
+ case "unknown":
1115
+ return `未知错误: ${String(e.error)}`;
1116
+ }
1117
+ }
1118
+ const cacheStore = /* @__PURE__ */ new Map();
1119
+ function containsFile(obj) {
1120
+ if (obj instanceof File || obj instanceof Blob || obj instanceof FormData) return true;
1121
+ if (typeof obj === "object" && obj !== null) {
1122
+ return Object.values(obj).some(containsFile);
1123
+ }
1124
+ return false;
1125
+ }
1126
+ function toFormData(obj) {
1127
+ const fd = new FormData();
1128
+ for (const [key, value] of Object.entries(obj)) {
1129
+ if (value instanceof File || value instanceof Blob) {
1130
+ fd.append(key, value);
1131
+ } else if (Array.isArray(value)) {
1132
+ value.forEach((item) => {
1133
+ if (item instanceof File || item instanceof Blob) {
1134
+ fd.append(key, item);
1135
+ } else {
1136
+ fd.append(key, String(item));
1137
+ }
1138
+ });
1139
+ } else if (value !== null && value !== void 0) {
1140
+ fd.append(key, String(value));
1141
+ }
1142
+ }
1143
+ return fd;
1144
+ }
1145
+ function toQueryString(params) {
1146
+ const parts = [];
1147
+ for (const [key, value] of Object.entries(params)) {
1148
+ if (value === null || value === void 0) continue;
1149
+ if (Array.isArray(value)) {
1150
+ value.forEach((v) => parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(v))}`));
1151
+ } else {
1152
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
1153
+ }
1154
+ }
1155
+ return parts.join("&");
1156
+ }
1157
+ async function unwrapResponse(response) {
1158
+ if (response.status < 200 || response.status >= 300) {
1159
+ throw new NetworkRequestError({
1160
+ type: "httpError",
1161
+ status: response.status,
1162
+ statusText: response.statusText
1163
+ });
1164
+ }
1165
+ const contentType = response.headers["content-type"] ?? "";
1166
+ if (contentType.includes("application/json")) {
1167
+ const json = await response.json();
1168
+ if (isWrappedResponse$1(json)) {
1169
+ if (!json.success) {
1170
+ throw new BusinessError(json.message || "请求失败");
1171
+ }
1172
+ return json.data;
1173
+ }
1174
+ return json;
1175
+ }
1176
+ const text = await response.text();
1177
+ return text;
1178
+ }
1179
+ function isWrappedResponse$1(value) {
1180
+ return typeof value === "object" && value !== null && "success" in value && "data" in value && typeof value.success === "boolean";
1181
+ }
1182
+ async function withRetry(fn, policy, signal) {
1183
+ if (policy.type === "none") return fn();
1184
+ let lastError;
1185
+ const maxAttempts = policy.maxAttempts;
1186
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1187
+ try {
1188
+ return await fn();
1189
+ } catch (e) {
1190
+ lastError = e;
1191
+ if (signal == null ? void 0 : signal.aborted) throw e;
1192
+ if (e instanceof BusinessError) throw e;
1193
+ if (attempt < maxAttempts) {
1194
+ const delay = policy.type === "fixed" ? policy.delay : Math.min(policy.initialDelay * Math.pow(policy.multiplier, attempt - 1), policy.maxDelay);
1195
+ await new Promise((resolve, reject) => {
1196
+ const timer = setTimeout(resolve, delay);
1197
+ signal == null ? void 0 : signal.addEventListener("abort", () => {
1198
+ clearTimeout(timer);
1199
+ reject(new NetworkRequestError({ type: "aborted" }));
1200
+ }, { once: true });
1201
+ });
1202
+ }
1203
+ }
1204
+ }
1205
+ throw lastError;
1206
+ }
1207
+ class RequestBuilder {
1208
+ constructor(context, method, url, body) {
1209
+ __publicField(this, "method");
1210
+ __publicField(this, "url");
1211
+ __publicField(this, "body");
1212
+ __publicField(this, "config", {});
1213
+ __publicField(this, "context");
1214
+ __publicField(this, "customHeaders", {});
1215
+ this.context = context;
1216
+ this.method = method;
1217
+ this.url = url;
1218
+ this.body = body;
1219
+ }
1220
+ // ── 链式 API ────────────────────────────────────────────────────────────
1221
+ cache(policy) {
1222
+ this.config.cache = policy;
1223
+ return this;
1224
+ }
1225
+ retry(policy) {
1226
+ this.config.retry = policy;
1227
+ return this;
1228
+ }
1229
+ debounce(delay) {
1230
+ this.config.control = { ...this.config.control, debounce: delay };
1231
+ return this;
1232
+ }
1233
+ throttle(interval) {
1234
+ this.config.control = { ...this.config.control, throttle: interval };
1235
+ return this;
1236
+ }
1237
+ deduplicate(enabled = true) {
1238
+ this.config.control = { ...this.config.control, deduplicate: enabled };
1239
+ return this;
1240
+ }
1241
+ timeout(ms) {
1242
+ this.config.timeout = ms;
1243
+ return this;
1244
+ }
1245
+ totalTimeout(ms) {
1246
+ this.config.totalTimeout = ms;
1247
+ return this;
1248
+ }
1249
+ lifecycle(lc) {
1250
+ this.config.lifecycle = lc;
1251
+ return this;
1252
+ }
1253
+ priority(p) {
1254
+ this.config.control = { ...this.config.control, priority: p };
1255
+ return this;
1256
+ }
1257
+ headers(h2) {
1258
+ Object.assign(this.customHeaders, h2);
1259
+ return this;
1260
+ }
1261
+ // ── 执行 ────────────────────────────────────────────────────────────────
1262
+ async execute() {
1263
+ const { transport, controlGate, baseURL, defaultHeaders, getToken, onUnauthorized, responseInterceptor, registerAbort } = this.context;
1264
+ let fullURL = this.url.startsWith("http") ? this.url : `${baseURL}${this.url}`;
1265
+ const abortController = new AbortController();
1266
+ registerAbort == null ? void 0 : registerAbort(abortController);
1267
+ let totalTimeoutId;
1268
+ if (this.config.totalTimeout) {
1269
+ totalTimeoutId = setTimeout(() => abortController.abort("totalTimeout"), this.config.totalTimeout);
1270
+ }
1271
+ try {
1272
+ const reqHeaders = { ...defaultHeaders, ...this.customHeaders };
1273
+ const token = getToken == null ? void 0 : getToken();
1274
+ if (token) {
1275
+ reqHeaders["Authorization"] = `Bearer ${token}`;
1276
+ }
1277
+ let reqBody = null;
1278
+ if (this.method === "GET" && this.body && typeof this.body === "object") {
1279
+ const qs = toQueryString(this.body);
1280
+ if (qs) {
1281
+ fullURL += (fullURL.includes("?") ? "&" : "?") + qs;
1282
+ }
1283
+ } else if (this.body !== void 0 && this.body !== null) {
1284
+ if (this.body instanceof FormData) {
1285
+ reqBody = this.body;
1286
+ } else if (containsFile(this.body)) {
1287
+ reqBody = toFormData(this.body);
1288
+ } else {
1289
+ reqBody = JSON.stringify(this.body);
1290
+ reqHeaders["Content-Type"] = "application/json";
1291
+ }
1292
+ }
1293
+ const cacheKey = buildCacheKey(this.method, fullURL, this.body);
1294
+ const cachePolicy = this.config.cache ?? { type: "none" };
1295
+ if (cachePolicy.type === "cacheFirst" || cachePolicy.type === "staleWhileRevalidate") {
1296
+ const cached = cacheStore.get(cacheKey);
1297
+ if (cached) {
1298
+ const age = (Date.now() - cached.timestamp) / 1e3;
1299
+ if (age < cachePolicy.maxAge) {
1300
+ if (cachePolicy.type === "staleWhileRevalidate") {
1301
+ this.executeTransport(transport, fullURL, reqHeaders, reqBody, abortController.signal).then((data) => {
1302
+ cacheStore.set(cacheKey, { data, timestamp: Date.now() });
1303
+ }).catch(() => {
1304
+ });
1305
+ }
1306
+ return cached.data;
1307
+ }
1308
+ }
1309
+ }
1310
+ const doRequest = () => this.executeTransport(transport, fullURL, reqHeaders, reqBody, abortController.signal);
1311
+ const controlPolicy = this.config.control ?? {};
1312
+ const retryPolicy = this.config.retry ?? { type: "none" };
1313
+ const result = await controlGate.execute(cacheKey, async () => {
1314
+ return withRetry(doRequest, retryPolicy, abortController.signal);
1315
+ }, controlPolicy);
1316
+ if (cachePolicy.type !== "none") {
1317
+ cacheStore.set(cacheKey, { data: result, timestamp: Date.now() });
1318
+ }
1319
+ const finalResult = responseInterceptor ? responseInterceptor(result) : result;
1320
+ return finalResult;
1321
+ } catch (error) {
1322
+ if (error instanceof NetworkRequestError && error.detail.type === "httpError" && error.detail.status === 401) {
1323
+ onUnauthorized == null ? void 0 : onUnauthorized();
1324
+ }
1325
+ throw error;
1326
+ } finally {
1327
+ if (totalTimeoutId !== void 0) clearTimeout(totalTimeoutId);
1328
+ }
1329
+ }
1330
+ // ── 内部传输 ──────────────────────────────────────────────────────────────
1331
+ async executeTransport(transport, url, headers, body, signal) {
1332
+ const response = await transport.send({
1333
+ url,
1334
+ method: this.method,
1335
+ headers,
1336
+ body,
1337
+ signal,
1338
+ timeout: this.config.timeout
1339
+ });
1340
+ return unwrapResponse(response);
1341
+ }
1342
+ }
1343
+ function createRequest(options = {}) {
1344
+ const transport = options.transport ?? new FetchTransport();
1345
+ const controlGate = new ControlGate();
1346
+ const abortControllers = /* @__PURE__ */ new Set();
1347
+ const context = {
1348
+ transport,
1349
+ controlGate,
1350
+ baseURL: options.baseURL ?? "",
1351
+ defaultHeaders: options.headers ?? {},
1352
+ getToken: options.getToken,
1353
+ onUnauthorized: options.onUnauthorized,
1354
+ responseInterceptor: options.responseInterceptor,
1355
+ registerAbort: (controller) => {
1356
+ abortControllers.add(controller);
1357
+ const originalAbort = controller.abort.bind(controller);
1358
+ controller.abort = (reason) => {
1359
+ abortControllers.delete(controller);
1360
+ return originalAbort(reason);
1361
+ };
1362
+ }
1363
+ };
1364
+ function createBuilder(method, url, body) {
1365
+ var _a, _b, _c;
1366
+ const builder = new RequestBuilder(context, method, url, body);
1367
+ const dc = options.defaultConfig;
1368
+ if (dc == null ? void 0 : dc.cache) builder.cache(dc.cache);
1369
+ if (dc == null ? void 0 : dc.retry) builder.retry(dc.retry);
1370
+ if (dc == null ? void 0 : dc.timeout) builder.timeout(dc.timeout);
1371
+ if (dc == null ? void 0 : dc.totalTimeout) builder.totalTimeout(dc.totalTimeout);
1372
+ if (dc == null ? void 0 : dc.lifecycle) builder.lifecycle(dc.lifecycle);
1373
+ if ((_a = dc == null ? void 0 : dc.control) == null ? void 0 : _a.debounce) builder.debounce(dc.control.debounce);
1374
+ if ((_b = dc == null ? void 0 : dc.control) == null ? void 0 : _b.throttle) builder.throttle(dc.control.throttle);
1375
+ if ((_c = dc == null ? void 0 : dc.control) == null ? void 0 : _c.deduplicate) builder.deduplicate(dc.control.deduplicate);
1376
+ return builder;
1377
+ }
1378
+ const client = {
1379
+ send: (method, url, body) => createBuilder(method, url, body).execute(),
1380
+ get: (url, params) => createBuilder("GET", url, params).execute(),
1381
+ post: (url, body) => createBuilder("POST", url, body).execute(),
1382
+ put: (url, body) => createBuilder("PUT", url, body).execute(),
1383
+ patch: (url, body) => createBuilder("PATCH", url, body).execute(),
1384
+ delete: (url, body) => createBuilder("DELETE", url, body).execute(),
1385
+ request: (method, url, body) => createBuilder(method, url, body),
1386
+ registerAbortController: (controller) => {
1387
+ abortControllers.add(controller);
1388
+ },
1389
+ abortAll: () => {
1390
+ for (const controller of abortControllers) {
1391
+ controller.abort("客户端取消所有请求");
1392
+ }
1393
+ abortControllers.clear();
1394
+ },
1395
+ dispose: () => {
1396
+ client.abortAll();
1397
+ controlGate.dispose();
1398
+ }
1399
+ };
1400
+ return client;
1401
+ }
1402
+ function createDefaultRequest(options = {}) {
1403
+ return createRequest({
1404
+ ...options,
1405
+ defaultConfig: {
1406
+ retry: { type: "exponential", maxAttempts: 3, initialDelay: 300, multiplier: 2, maxDelay: 5e3 },
1407
+ timeout: 3e4,
1408
+ ...options.defaultConfig
1409
+ }
1410
+ });
1411
+ }
1412
+ function createStrictRequest(options = {}) {
1413
+ return createRequest({
1414
+ ...options,
1415
+ defaultConfig: {
1416
+ retry: { type: "none" },
1417
+ timeout: 1e4,
1418
+ ...options.defaultConfig
1419
+ }
1420
+ });
1421
+ }
1422
+ function createPermissiveRequest(options = {}) {
1423
+ return createRequest({
1424
+ ...options,
1425
+ defaultConfig: {
1426
+ retry: { type: "exponential", maxAttempts: 5, initialDelay: 500, multiplier: 2, maxDelay: 3e4 },
1427
+ timeout: 6e4,
1428
+ totalTimeout: 12e4,
1429
+ ...options.defaultConfig
1430
+ }
1431
+ });
1432
+ }
1433
+ const ERROR_HANDLER_INJECT_KEY = Symbol("kine-design:error-handler");
1434
+ function tryGetMessageApi() {
1435
+ try {
1436
+ const { useMessage } = require("@kine-design/ui");
1437
+ return useMessage();
1438
+ } catch {
1439
+ return null;
1440
+ }
1441
+ }
1442
+ let messageApi = void 0;
1443
+ function getMessageApi() {
1444
+ if (messageApi === void 0) {
1445
+ messageApi = tryGetMessageApi();
1446
+ }
1447
+ return messageApi;
1448
+ }
1449
+ const defaultFeedbackHandler = {
1450
+ showSuccess(message) {
1451
+ const api = getMessageApi();
1452
+ if (api) {
1453
+ api.success(message);
1454
+ } else {
1455
+ console.log(`[success] ${message}`);
1456
+ }
1457
+ },
1458
+ showError(message) {
1459
+ const api = getMessageApi();
1460
+ if (api) {
1461
+ api.error(message);
1462
+ } else {
1463
+ console.error(`[error] ${message}`);
1464
+ }
1465
+ },
1466
+ showWarning(message) {
1467
+ const api = getMessageApi();
1468
+ if (api) {
1469
+ api.warning(message);
1470
+ } else {
1471
+ console.warn(`[warning] ${message}`);
1472
+ }
1473
+ },
1474
+ handleAuthenticationFailure() {
1475
+ console.warn("[auth] 认证失败,请重新登录");
1476
+ }
1477
+ };
1478
+ function dispatchError(error, options) {
1479
+ var _a, _b;
1480
+ const feedback = options.feedbackHandler ?? defaultFeedbackHandler;
1481
+ if (error instanceof BusinessError) {
1482
+ (_a = options.onBusinessError) == null ? void 0 : _a.call(options, error);
1483
+ feedback.showError(error.message);
1484
+ return;
1485
+ }
1486
+ if (error instanceof NetworkRequestError) {
1487
+ const detail = error.detail;
1488
+ (_b = options.onNetworkError) == null ? void 0 : _b.call(options, detail);
1489
+ switch (detail.type) {
1490
+ case "httpError":
1491
+ if (detail.status === 401) {
1492
+ feedback.handleAuthenticationFailure();
1493
+ return;
1494
+ }
1495
+ if (detail.status === 403) {
1496
+ feedback.showWarning("权限不足,无法执行此操作");
1497
+ return;
1498
+ }
1499
+ feedback.showError(`请求失败 (${detail.status})`);
1500
+ return;
1501
+ case "timeout":
1502
+ feedback.showWarning("请求超时,请稍后重试");
1503
+ return;
1504
+ case "aborted":
1505
+ return;
1506
+ case "invalidURL":
1507
+ feedback.showError(`无效的请求地址: ${detail.url}`);
1508
+ return;
1509
+ case "decodingFailed":
1510
+ feedback.showError("数据解析失败");
1511
+ return;
1512
+ case "unknown":
1513
+ feedback.showError("网络异常,请检查网络连接");
1514
+ return;
1515
+ }
1516
+ }
1517
+ const message = error instanceof Error ? error.message : String(error);
1518
+ feedback.showError(message || "发生了未知错误");
1519
+ }
1520
+ function createErrorHandler(app, options = {}) {
1521
+ app.config.errorHandler = (err) => {
1522
+ dispatchError(err, options);
1523
+ };
1524
+ app.provide(ERROR_HANDLER_INJECT_KEY, options);
1525
+ }
1526
+ const REQUEST_CLIENT_KEY = Symbol("kine-design:request");
1527
+ function useRequestClient() {
1528
+ const client = inject(REQUEST_CLIENT_KEY);
1529
+ if (!client) {
1530
+ throw new Error("[useRequestClient] 未找到 RequestClient,请在 createCrudApp 中配置 request 选项。");
1531
+ }
1532
+ return client;
1533
+ }
1534
+ function createQueryClient() {
1535
+ return new QueryClient({
1536
+ defaultOptions: {
1537
+ queries: {
1538
+ // 窗口重新聚焦时不自动重新请求,业务层按需覆盖
1539
+ refetchOnWindowFocus: false,
1540
+ // 失败后重试 1 次
1541
+ retry: 1,
1542
+ // 数据保鲜时间 1 分钟
1543
+ staleTime: 1e3 * 60
1544
+ }
1545
+ }
1546
+ });
1547
+ }
1548
+ function setupCrud(app, options) {
1549
+ const queryClient = (options == null ? void 0 : options.queryClient) ?? createQueryClient();
1550
+ app.use(VueQueryPlugin, {
1551
+ queryClient,
1552
+ ...options == null ? void 0 : options.vueQueryOptions
1553
+ });
1554
+ installPinia(app);
1555
+ }
1556
+ function installPinia(app) {
1557
+ const provides = app._context.provides;
1558
+ const hasPinia = Object.getOwnPropertySymbols(provides).some((sym) => {
1559
+ const val = provides[sym];
1560
+ return val !== null && typeof val === "object" && "_a" in val;
1561
+ });
1562
+ if (!hasPinia) {
1563
+ app.use(createPinia());
1564
+ }
1565
+ }
1566
+ function createCrudApp(rootComponent, rootPropsOrOptions, crudOptions) {
1567
+ if (rootPropsOrOptions && isCrudAppOptions(rootPropsOrOptions)) {
1568
+ return createCrudAppWithOptions(rootComponent, rootPropsOrOptions);
1569
+ }
1570
+ const app = createApp(rootComponent, rootPropsOrOptions);
1571
+ setupCrud(app, crudOptions);
1572
+ return app;
1573
+ }
1574
+ function isCrudAppOptions(obj) {
1575
+ return "router" in obj || "auth" in obj || "request" in obj || "error" in obj || "query" in obj;
1576
+ }
1577
+ function createCrudAppWithOptions(rootComponent, options) {
1578
+ var _a;
1579
+ const app = createApp(rootComponent);
1580
+ let auth;
1581
+ if (options.auth) {
1582
+ auth = createAuth(app, {
1583
+ fetchUser: options.auth.fetchUser,
1584
+ onUnauthorized: options.auth.onUnauthorized,
1585
+ storage: options.auth.storage
1586
+ });
1587
+ app.directive("can", createVCan(auth));
1588
+ if (options.router) {
1589
+ const guard = createAuthGuard(auth, { loginPath: options.auth.loginPath ?? "/login" });
1590
+ options.router.beforeEach(guard);
1591
+ }
1592
+ }
1593
+ if (options.router) {
1594
+ app.use(options.router);
1595
+ }
1596
+ setupCrud(app, options.query);
1597
+ if (options.request) {
1598
+ const requestOptions = { ...options.request };
1599
+ if (auth && !requestOptions.getToken) {
1600
+ requestOptions.getToken = () => auth.token.value;
1601
+ }
1602
+ if (auth && !requestOptions.onUnauthorized) {
1603
+ requestOptions.onUnauthorized = (_a = options.auth) == null ? void 0 : _a.onUnauthorized;
1604
+ }
1605
+ const client = createRequest(requestOptions);
1606
+ app.provide(REQUEST_CLIENT_KEY, client);
1607
+ }
1608
+ if (options.error) {
1609
+ createErrorHandler(app, options.error);
1610
+ }
1611
+ const enhancedApp = Object.create(app);
1612
+ enhancedApp.mount = (rootContainer) => {
1613
+ const doMount = () => app.mount(rootContainer);
1614
+ if (auth) {
1615
+ auth.restore().finally(doMount);
1616
+ } else {
1617
+ doMount();
1618
+ }
1619
+ return app;
1620
+ };
1621
+ return enhancedApp;
1622
+ }
1623
+ const DEFAULT_CONFIG = { key: "key", label: "label", children: "children" };
1624
+ function buildNodes(data, config, nodeMap, defaultExpandAll = false, parent) {
1625
+ return data.map((item) => {
1626
+ const key = item[config.key];
1627
+ const childrenKey = config.children;
1628
+ const node = {
1629
+ ...item,
1630
+ expand: defaultExpandAll,
1631
+ isActive: false,
1632
+ checked: false,
1633
+ indeterminate: false,
1634
+ parent,
1635
+ isRoot: !parent
1636
+ };
1637
+ nodeMap.set(key, node);
1638
+ const rawChildren = item[childrenKey];
1639
+ if (Array.isArray(rawChildren) && rawChildren.length > 0) {
1640
+ node.children = buildNodes(
1641
+ rawChildren,
1642
+ config,
1643
+ nodeMap,
1644
+ defaultExpandAll,
1645
+ node
1646
+ );
1647
+ }
1648
+ return node;
1649
+ });
1650
+ }
1651
+ function useMenu(props2, emit) {
1652
+ const config = { ...DEFAULT_CONFIG, ...props2.config };
1653
+ const nodeMap = /* @__PURE__ */ new Map();
1654
+ const nodesRef = shallowRef([]);
1655
+ const initNodes = (data = []) => {
1656
+ var _a;
1657
+ nodeMap.clear();
1658
+ nodesRef.value = buildNodes(data, config, nodeMap, props2.defaultExpandAll ?? false, null);
1659
+ if ((_a = props2.checkedKeys) == null ? void 0 : _a.length) {
1660
+ props2.checkedKeys.forEach((key) => {
1661
+ const node = nodeMap.get(key);
1662
+ if (node) {
1663
+ node.checked = true;
1664
+ }
1665
+ });
1666
+ }
1667
+ triggerRef(nodesRef);
1668
+ };
1669
+ initNodes(props2.data ?? []);
1670
+ if (props2.active !== void 0) {
1671
+ const activeNode = nodeMap.get(props2.active);
1672
+ if (activeNode) {
1673
+ activeNode.isActive = true;
1674
+ }
1675
+ }
1676
+ const toggleExpand = (node) => {
1677
+ node.expand = !node.expand;
1678
+ triggerRef(nodesRef);
1679
+ };
1680
+ const setActive = (node) => {
1681
+ nodeMap.forEach((n) => {
1682
+ n.isActive = false;
1683
+ });
1684
+ node.isActive = true;
1685
+ triggerRef(nodesRef);
1686
+ };
1687
+ const toggleChecked = (node, checked) => {
1688
+ node.checked = checked;
1689
+ node.indeterminate = false;
1690
+ triggerRef(nodesRef);
1691
+ };
1692
+ const getCheckedKeys = () => {
1693
+ const keys = [];
1694
+ nodeMap.forEach((node, key) => {
1695
+ if (node.checked && !node.indeterminate) {
1696
+ keys.push(key);
1697
+ }
1698
+ });
1699
+ return keys;
1700
+ };
1701
+ const getNodesByKeys = (keys) => {
1702
+ return keys.map((key) => nodeMap.get(key)).filter((n) => n !== void 0);
1703
+ };
1704
+ return {
1705
+ nodesRef,
1706
+ nodeMap,
1707
+ initNodes,
1708
+ toggleExpand,
1709
+ setActive,
1710
+ toggleChecked,
1711
+ getCheckedKeys,
1712
+ getNodesByKeys,
1713
+ config
1714
+ };
1715
+ }
1716
+ const KMenuItem = /* @__PURE__ */ defineComponent({
1717
+ name: "KMenuItem",
1718
+ inheritAttrs: false,
1719
+ props: {
1720
+ data: {
1721
+ type: Array,
1722
+ default: () => []
1723
+ },
1724
+ config: {
1725
+ type: Object,
1726
+ required: true
1727
+ },
1728
+ checkbox: {
1729
+ type: Boolean,
1730
+ default: false
1731
+ },
1732
+ root: {
1733
+ type: Boolean,
1734
+ default: false
1735
+ },
1736
+ getNodesByKeys: {
1737
+ type: Function,
1738
+ required: true
1739
+ },
1740
+ handleExpand: {
1741
+ type: Function,
1742
+ required: true
1743
+ },
1744
+ handleCheck: {
1745
+ type: Function,
1746
+ required: true
1747
+ },
1748
+ handleItemClick: {
1749
+ type: Function,
1750
+ required: true
1751
+ }
1752
+ },
1753
+ setup(_props, {
1754
+ slots
1755
+ }) {
1756
+ const props2 = _props;
1757
+ const clickEvent = (e, d) => {
1758
+ props2.handleItemClick(d, e);
1759
+ };
1760
+ const expandEvent = (e, d) => {
1761
+ props2.handleExpand(d, e);
1762
+ e.stopPropagation();
1763
+ };
1764
+ const checkEvent = (e, d) => {
1765
+ const checked = e.target.checked;
1766
+ props2.handleCheck(d, checked);
1767
+ };
1768
+ return () => {
1769
+ const {
1770
+ label: l,
1771
+ key: k,
1772
+ children: c
1773
+ } = props2.config;
1774
+ const KMenuItemComp = resolveComponent("KMenuItem");
1775
+ return createVNode(Fragment, null, [props2.data.map((d) => {
1776
+ const childNodes = d.children ? d.children : (() => {
1777
+ const raw = d[c];
1778
+ return raw ? props2.getNodesByKeys(raw.map((it) => it[k])) : [];
1779
+ })();
1780
+ const hasChildren = childNodes.length > 0;
1781
+ return createVNode("li", {
1782
+ "key": d[k],
1783
+ "class": ["m-menu-item", "k-menu-item", {
1784
+ "m-menu-item-root": props2.root
1785
+ }, {
1786
+ "k-menu-item-root": props2.root
1787
+ }, {
1788
+ "m-menu-item-disabled": !!d.disabled
1789
+ }, {
1790
+ "k-menu-item-disabled": !!d.disabled
1791
+ }, {
1792
+ "m-menu-item-active": !!d.isActive
1793
+ }, {
1794
+ "k-menu-item-active": !!d.isActive
1795
+ }],
1796
+ "onClick": (e) => clickEvent(e, d)
1797
+ }, [createVNode("div", {
1798
+ "class": "m-menu-item-content k-menu-item-content"
1799
+ }, [props2.checkbox ? createVNode("input", {
1800
+ "type": "checkbox",
1801
+ "class": "m-menu-item-checkbox k-menu-item-checkbox",
1802
+ "checked": !!d.checked,
1803
+ "disabled": !!d.disabled,
1804
+ "onChange": (e) => checkEvent(e, d),
1805
+ "onClick": (e) => e.stopPropagation()
1806
+ }, null) : null, createVNode("span", {
1807
+ "class": "m-menu-item-label k-menu-item-label",
1808
+ "onClick": (e) => expandEvent(e, d)
1809
+ }, [slots.default ? slots.default({
1810
+ data: d[l],
1811
+ node: d
1812
+ }) : d[l]]), hasChildren ? createVNode("span", {
1813
+ "class": ["m-menu-item-arrow", "k-menu-item-arrow", {
1814
+ "m-menu-item-arrow-expand": d.expand
1815
+ }, {
1816
+ "k-menu-item-arrow-expand": d.expand
1817
+ }],
1818
+ "onClick": (e) => expandEvent(e, d)
1819
+ }, null) : null]), hasChildren && d.expand ? createVNode("div", {
1820
+ "class": "m-menu-item-children k-menu-item-children"
1821
+ }, [h(KMenuItemComp, {
1822
+ data: childNodes,
1823
+ config: props2.config,
1824
+ checkbox: props2.checkbox,
1825
+ root: false,
1826
+ getNodesByKeys: props2.getNodesByKeys,
1827
+ handleExpand: props2.handleExpand,
1828
+ handleCheck: props2.handleCheck,
1829
+ handleItemClick: props2.handleItemClick
1830
+ }, slots.default ? {
1831
+ default: slots.default
1832
+ } : void 0)]) : null]);
1833
+ })]);
1834
+ };
1835
+ }
1836
+ });
1837
+ function findKeyByPath(data, path) {
1838
+ for (const item of data) {
1839
+ if (item.path === path) return item.key;
1840
+ if (item.children) {
1841
+ const found = findKeyByPath(item.children, path);
1842
+ if (found !== null) return found;
1843
+ }
1844
+ }
1845
+ return null;
1846
+ }
1847
+ const KNavMenu = /* @__PURE__ */ defineComponent({
1848
+ name: "KNavMenu",
1849
+ props: {
1850
+ data: {
1851
+ type: Array,
1852
+ default: () => []
1853
+ }
1854
+ },
1855
+ setup(props2, {
1856
+ slots
1857
+ }) {
1858
+ const router = useRouter();
1859
+ const route = useRoute();
1860
+ const {
1861
+ nodesRef,
1862
+ nodeMap,
1863
+ initNodes,
1864
+ toggleExpand,
1865
+ setActive,
1866
+ getNodesByKeys,
1867
+ config
1868
+ } = useMenu({
1869
+ data: props2.data
1870
+ });
1871
+ watch(() => props2.data, (newData) => {
1872
+ initNodes(newData ?? []);
1873
+ syncActiveByRoute(route.path);
1874
+ }, {
1875
+ deep: true
1876
+ });
1877
+ const syncActiveByRoute = (path) => {
1878
+ const key = findKeyByPath(props2.data, path);
1879
+ if (key === null) return;
1880
+ const node = nodeMap.get(key);
1881
+ if (!node) return;
1882
+ setActive(node);
1883
+ let parent = node.parent;
1884
+ while (parent) {
1885
+ if (!parent.expand) {
1886
+ parent.expand = true;
1887
+ }
1888
+ parent = parent.parent;
1889
+ }
1890
+ };
1891
+ watch(() => route.path, syncActiveByRoute);
1892
+ onMounted(() => {
1893
+ syncActiveByRoute(route.path);
1894
+ });
1895
+ const handleItemClick = (node, e) => {
1896
+ if (node.disabled) return;
1897
+ const navNode = node;
1898
+ if (navNode.path) {
1899
+ router.push(navNode.path);
1900
+ setActive(node);
1901
+ } else {
1902
+ toggleExpand(node);
1903
+ }
1904
+ e.stopPropagation();
1905
+ };
1906
+ const handleExpand = (node, e) => {
1907
+ if (node.disabled) return;
1908
+ const navNode = node;
1909
+ const hasChildren = node.children && node.children.length > 0;
1910
+ if (hasChildren) {
1911
+ toggleExpand(node);
1912
+ } else if (navNode.path) {
1913
+ router.push(navNode.path);
1914
+ setActive(node);
1915
+ }
1916
+ e.stopPropagation();
1917
+ };
1918
+ const handleCheck = () => {
1919
+ };
1920
+ const defaultSlot = slots.default ? slots.default : (slotProps) => {
1921
+ const label = slotProps.data;
1922
+ const icon = slotProps.node ? slotProps.node.icon : void 0;
1923
+ return [icon ? h("span", {
1924
+ class: "k-nav-menu-icon"
1925
+ }, icon) : null, h("span", {
1926
+ class: "k-nav-menu-label"
1927
+ }, label)];
1928
+ };
1929
+ return () => createVNode("div", {
1930
+ "class": "k-nav-menu"
1931
+ }, [createVNode(KMenuItem, {
1932
+ "data": [...nodesRef.value],
1933
+ "config": config,
1934
+ "checkbox": false,
1935
+ "root": true,
1936
+ "getNodesByKeys": getNodesByKeys,
1937
+ "handleExpand": handleExpand,
1938
+ "handleCheck": handleCheck,
1939
+ "handleItemClick": handleItemClick
1940
+ }, {
1941
+ default: defaultSlot
1942
+ })]);
1943
+ }
1944
+ });
1945
+ let uidCounter$1 = 0;
1946
+ function genUid$1() {
1947
+ return `upload-${Date.now()}-${++uidCounter$1}`;
1948
+ }
1949
+ function formatSize$1(bytes) {
1950
+ if (bytes < 1024) return `${bytes} B`;
1951
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1952
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1953
+ }
1954
+ const KUpload = /* @__PURE__ */ defineComponent({
1955
+ name: "KUpload",
1956
+ props: {
1957
+ request: {
1958
+ type: Function,
1959
+ required: true
1960
+ },
1961
+ accept: {
1962
+ type: String,
1963
+ default: ""
1964
+ },
1965
+ multiple: {
1966
+ type: Boolean,
1967
+ default: false
1968
+ },
1969
+ maxCount: {
1970
+ type: Number,
1971
+ default: Infinity
1972
+ },
1973
+ maxSize: {
1974
+ type: Number,
1975
+ default: Infinity
1976
+ },
1977
+ disabled: {
1978
+ type: Boolean,
1979
+ default: false
1980
+ },
1981
+ fileList: {
1982
+ type: Array,
1983
+ default: () => []
1984
+ },
1985
+ drag: {
1986
+ type: Boolean,
1987
+ default: true
1988
+ }
1989
+ },
1990
+ emits: ["update:fileList", "change", "error"],
1991
+ setup(props2, ctx) {
1992
+ const inputRef = ref(null);
1993
+ const isDragOver = ref(false);
1994
+ const openFileDialog = () => {
1995
+ var _a;
1996
+ if (props2.disabled) return;
1997
+ (_a = inputRef.value) == null ? void 0 : _a.click();
1998
+ };
1999
+ const validateFile = (file) => {
2000
+ if (props2.maxSize !== Infinity && file.size > props2.maxSize) {
2001
+ return `文件 "${file.name}" 超过大小限制(最大 ${formatSize$1(props2.maxSize)})`;
2002
+ }
2003
+ return null;
2004
+ };
2005
+ const processFiles = async (rawFiles) => {
2006
+ const fileArray = Array.from(rawFiles);
2007
+ const currentCount = props2.fileList.length;
2008
+ const remaining = props2.maxCount - currentCount;
2009
+ if (remaining <= 0) return;
2010
+ const filesToProcess = fileArray.slice(0, remaining);
2011
+ const newList = [...props2.fileList];
2012
+ for (const raw of filesToProcess) {
2013
+ const err = validateFile(raw);
2014
+ if (err) {
2015
+ ctx.emit("error", {
2016
+ file: raw,
2017
+ message: err
2018
+ });
2019
+ continue;
2020
+ }
2021
+ const uploadFile = {
2022
+ uid: genUid$1(),
2023
+ name: raw.name,
2024
+ size: raw.size,
2025
+ type: raw.type,
2026
+ status: "pending",
2027
+ percent: 0,
2028
+ raw
2029
+ };
2030
+ newList.push(uploadFile);
2031
+ ctx.emit("update:fileList", [...newList]);
2032
+ ctx.emit("change", uploadFile);
2033
+ const idx = newList.indexOf(uploadFile);
2034
+ newList[idx] = {
2035
+ ...uploadFile,
2036
+ status: "uploading"
2037
+ };
2038
+ ctx.emit("update:fileList", [...newList]);
2039
+ try {
2040
+ const result = await props2.request(raw, (percent) => {
2041
+ const current2 = newList.find((f) => f.uid === uploadFile.uid);
2042
+ if (current2) {
2043
+ const i = newList.indexOf(current2);
2044
+ newList[i] = {
2045
+ ...current2,
2046
+ percent
2047
+ };
2048
+ ctx.emit("update:fileList", [...newList]);
2049
+ }
2050
+ });
2051
+ const current = newList.find((f) => f.uid === uploadFile.uid);
2052
+ if (current) {
2053
+ const i = newList.indexOf(current);
2054
+ newList[i] = {
2055
+ ...current,
2056
+ status: "success",
2057
+ percent: 100,
2058
+ url: result.url
2059
+ };
2060
+ ctx.emit("update:fileList", [...newList]);
2061
+ ctx.emit("change", newList[i]);
2062
+ }
2063
+ } catch (e) {
2064
+ const current = newList.find((f) => f.uid === uploadFile.uid);
2065
+ if (current) {
2066
+ const i = newList.indexOf(current);
2067
+ newList[i] = {
2068
+ ...current,
2069
+ status: "error"
2070
+ };
2071
+ ctx.emit("update:fileList", [...newList]);
2072
+ ctx.emit("error", {
2073
+ file: raw,
2074
+ message: String(e)
2075
+ });
2076
+ }
2077
+ }
2078
+ }
2079
+ };
2080
+ const onInputChange = (e) => {
2081
+ const target = e.target;
2082
+ if (target.files) {
2083
+ processFiles(target.files);
2084
+ target.value = "";
2085
+ }
2086
+ };
2087
+ const onDragOver = (e) => {
2088
+ if (props2.disabled || !props2.drag) return;
2089
+ e.preventDefault();
2090
+ isDragOver.value = true;
2091
+ };
2092
+ const onDragLeave = () => {
2093
+ isDragOver.value = false;
2094
+ };
2095
+ const onDrop = (e) => {
2096
+ var _a;
2097
+ if (props2.disabled || !props2.drag) return;
2098
+ e.preventDefault();
2099
+ isDragOver.value = false;
2100
+ if ((_a = e.dataTransfer) == null ? void 0 : _a.files) {
2101
+ processFiles(e.dataTransfer.files);
2102
+ }
2103
+ };
2104
+ return () => {
2105
+ var _a, _b;
2106
+ const classes = ["k-upload", props2.drag ? "k-upload-drag" : "", props2.disabled ? "k-upload-disabled" : "", isDragOver.value ? "k-upload-drag-over" : ""].filter(Boolean);
2107
+ return createVNode("div", {
2108
+ "class": classes,
2109
+ "onClick": openFileDialog,
2110
+ "onDragover": onDragOver,
2111
+ "onDragleave": onDragLeave,
2112
+ "onDrop": onDrop
2113
+ }, [createVNode("input", {
2114
+ "ref": inputRef,
2115
+ "type": "file",
2116
+ "class": "k-upload-input",
2117
+ "accept": props2.accept,
2118
+ "multiple": props2.multiple,
2119
+ "onChange": onInputChange
2120
+ }, null), ((_b = (_a = ctx.slots).default) == null ? void 0 : _b.call(_a)) ?? createVNode("div", {
2121
+ "class": "k-upload-placeholder"
2122
+ }, [createVNode("span", {
2123
+ "class": "k-upload-icon"
2124
+ }, [createTextVNode("↑")]), createVNode("span", {
2125
+ "class": "k-upload-text"
2126
+ }, [props2.drag ? "点击或拖拽文件到此处上传" : "点击上传文件"])])]);
2127
+ };
2128
+ }
2129
+ });
2130
+ let uidCounter = 0;
2131
+ function genUid() {
2132
+ return `img-upload-${Date.now()}-${++uidCounter}`;
2133
+ }
2134
+ const KImageUpload = /* @__PURE__ */ defineComponent({
2135
+ name: "KImageUpload",
2136
+ props: {
2137
+ request: {
2138
+ type: Function,
2139
+ required: true
2140
+ },
2141
+ accept: {
2142
+ type: String,
2143
+ default: "image/*"
2144
+ },
2145
+ multiple: {
2146
+ type: Boolean,
2147
+ default: true
2148
+ },
2149
+ maxCount: {
2150
+ type: Number,
2151
+ default: Infinity
2152
+ },
2153
+ maxSize: {
2154
+ type: Number,
2155
+ default: Infinity
2156
+ },
2157
+ disabled: {
2158
+ type: Boolean,
2159
+ default: false
2160
+ },
2161
+ fileList: {
2162
+ type: Array,
2163
+ default: () => []
2164
+ },
2165
+ drag: {
2166
+ type: Boolean,
2167
+ default: true
2168
+ },
2169
+ previewSize: {
2170
+ type: String,
2171
+ default: "100px"
2172
+ }
2173
+ },
2174
+ emits: ["update:fileList", "change", "error"],
2175
+ setup(props2, ctx) {
2176
+ const inputRef = ref(null);
2177
+ const isDragOver = ref(false);
2178
+ const objectUrlMap = ref(/* @__PURE__ */ new Map());
2179
+ const getPreviewUrl = (file) => {
2180
+ if (file.thumbUrl) return file.thumbUrl;
2181
+ if (file.url) return file.url;
2182
+ if (file.raw) {
2183
+ const existing = objectUrlMap.value.get(file.uid);
2184
+ if (existing) return existing;
2185
+ const url = URL.createObjectURL(file.raw);
2186
+ objectUrlMap.value.set(file.uid, url);
2187
+ return url;
2188
+ }
2189
+ return "";
2190
+ };
2191
+ onBeforeUnmount(() => {
2192
+ objectUrlMap.value.forEach((url) => URL.revokeObjectURL(url));
2193
+ objectUrlMap.value.clear();
2194
+ });
2195
+ const showAddCard = computed(() => !props2.disabled && props2.fileList.length < props2.maxCount);
2196
+ const openFileDialog = () => {
2197
+ var _a;
2198
+ if (props2.disabled) return;
2199
+ (_a = inputRef.value) == null ? void 0 : _a.click();
2200
+ };
2201
+ const processFiles = async (rawFiles) => {
2202
+ const fileArray = Array.from(rawFiles);
2203
+ const currentCount = props2.fileList.length;
2204
+ const remaining = props2.maxCount - currentCount;
2205
+ if (remaining <= 0) return;
2206
+ const filesToProcess = fileArray.slice(0, remaining);
2207
+ const newList = [...props2.fileList];
2208
+ for (const raw of filesToProcess) {
2209
+ if (props2.maxSize !== Infinity && raw.size > props2.maxSize) {
2210
+ ctx.emit("error", {
2211
+ file: raw,
2212
+ message: `文件 "${raw.name}" 超过大小限制`
2213
+ });
2214
+ continue;
2215
+ }
2216
+ const uploadFile = {
2217
+ uid: genUid(),
2218
+ name: raw.name,
2219
+ size: raw.size,
2220
+ type: raw.type,
2221
+ status: "uploading",
2222
+ percent: 0,
2223
+ raw
2224
+ };
2225
+ newList.push(uploadFile);
2226
+ ctx.emit("update:fileList", [...newList]);
2227
+ ctx.emit("change", uploadFile);
2228
+ try {
2229
+ const result = await props2.request(raw, (percent) => {
2230
+ const current2 = newList.find((f) => f.uid === uploadFile.uid);
2231
+ if (current2) {
2232
+ const i = newList.indexOf(current2);
2233
+ newList[i] = {
2234
+ ...current2,
2235
+ percent
2236
+ };
2237
+ ctx.emit("update:fileList", [...newList]);
2238
+ }
2239
+ });
2240
+ const current = newList.find((f) => f.uid === uploadFile.uid);
2241
+ if (current) {
2242
+ const localUrl = objectUrlMap.value.get(uploadFile.uid);
2243
+ if (localUrl) {
2244
+ URL.revokeObjectURL(localUrl);
2245
+ objectUrlMap.value.delete(uploadFile.uid);
2246
+ }
2247
+ const i = newList.indexOf(current);
2248
+ newList[i] = {
2249
+ ...current,
2250
+ status: "success",
2251
+ percent: 100,
2252
+ url: result.url
2253
+ };
2254
+ ctx.emit("update:fileList", [...newList]);
2255
+ ctx.emit("change", newList[i]);
2256
+ }
2257
+ } catch (e) {
2258
+ const current = newList.find((f) => f.uid === uploadFile.uid);
2259
+ if (current) {
2260
+ const i = newList.indexOf(current);
2261
+ newList[i] = {
2262
+ ...current,
2263
+ status: "error"
2264
+ };
2265
+ ctx.emit("update:fileList", [...newList]);
2266
+ ctx.emit("error", {
2267
+ file: raw,
2268
+ message: String(e)
2269
+ });
2270
+ }
2271
+ }
2272
+ }
2273
+ };
2274
+ const onInputChange = (e) => {
2275
+ const target = e.target;
2276
+ if (target.files) {
2277
+ processFiles(target.files);
2278
+ target.value = "";
2279
+ }
2280
+ };
2281
+ const onDragOver = (e) => {
2282
+ if (props2.disabled || !props2.drag) return;
2283
+ e.preventDefault();
2284
+ isDragOver.value = true;
2285
+ };
2286
+ const onDragLeave = () => {
2287
+ isDragOver.value = false;
2288
+ };
2289
+ const onDrop = (e) => {
2290
+ var _a;
2291
+ if (props2.disabled || !props2.drag) return;
2292
+ e.preventDefault();
2293
+ isDragOver.value = false;
2294
+ if ((_a = e.dataTransfer) == null ? void 0 : _a.files) {
2295
+ processFiles(e.dataTransfer.files);
2296
+ }
2297
+ };
2298
+ const removeFile = (uid) => {
2299
+ const localUrl = objectUrlMap.value.get(uid);
2300
+ if (localUrl) {
2301
+ URL.revokeObjectURL(localUrl);
2302
+ objectUrlMap.value.delete(uid);
2303
+ }
2304
+ const next = props2.fileList.filter((f) => f.uid !== uid);
2305
+ ctx.emit("update:fileList", next);
2306
+ };
2307
+ return () => {
2308
+ const cardStyle = {
2309
+ width: props2.previewSize,
2310
+ height: props2.previewSize
2311
+ };
2312
+ const previewCards = props2.fileList.map((file) => {
2313
+ const previewUrl = getPreviewUrl(file);
2314
+ const statusClass = `k-image-upload-card--${file.status}`;
2315
+ return createVNode("div", {
2316
+ "key": file.uid,
2317
+ "class": ["k-image-upload-card", statusClass],
2318
+ "style": cardStyle
2319
+ }, [previewUrl && createVNode("img", {
2320
+ "class": "k-image-upload-preview",
2321
+ "src": previewUrl,
2322
+ "alt": file.name
2323
+ }, null), file.status === "uploading" && createVNode("div", {
2324
+ "class": "k-image-upload-overlay"
2325
+ }, [createVNode("div", {
2326
+ "class": "k-image-upload-progress",
2327
+ "style": {
2328
+ height: `${file.percent}%`
2329
+ }
2330
+ }, null), createVNode("span", {
2331
+ "class": "k-image-upload-percent"
2332
+ }, [file.percent, createTextVNode("%")])]), file.status === "error" && createVNode("div", {
2333
+ "class": "k-image-upload-overlay k-image-upload-overlay--error"
2334
+ }, [createVNode("span", null, [createTextVNode("上传失败")])]), !props2.disabled && createVNode("button", {
2335
+ "class": "k-image-upload-remove",
2336
+ "type": "button",
2337
+ "onClick": (e) => {
2338
+ e.stopPropagation();
2339
+ removeFile(file.uid);
2340
+ }
2341
+ }, [createTextVNode("×")])]);
2342
+ });
2343
+ const addCard = showAddCard.value && createVNode("div", {
2344
+ "class": ["k-image-upload-card", "k-image-upload-card--add", isDragOver.value ? "k-upload-drag-over" : ""].filter(Boolean),
2345
+ "style": cardStyle,
2346
+ "onClick": openFileDialog,
2347
+ "onDragover": onDragOver,
2348
+ "onDragleave": onDragLeave,
2349
+ "onDrop": onDrop
2350
+ }, [createVNode("span", {
2351
+ "class": "k-image-upload-add-icon"
2352
+ }, [createTextVNode("+")])]);
2353
+ return createVNode("div", {
2354
+ "class": ["k-image-upload", props2.disabled ? "k-upload-disabled" : ""].filter(Boolean)
2355
+ }, [createVNode("input", {
2356
+ "ref": inputRef,
2357
+ "type": "file",
2358
+ "class": "k-upload-input",
2359
+ "accept": props2.accept,
2360
+ "multiple": props2.multiple,
2361
+ "onChange": onInputChange
2362
+ }, null), createVNode("div", {
2363
+ "class": "k-image-upload-grid"
2364
+ }, [previewCards, addCard])]);
2365
+ };
2366
+ }
2367
+ });
2368
+ const STATUS_TEXT = {
2369
+ pending: "等待中",
2370
+ uploading: "上传中",
2371
+ success: "已完成",
2372
+ error: "上传失败"
2373
+ };
2374
+ function formatSize(bytes) {
2375
+ if (bytes < 1024) return `${bytes} B`;
2376
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
2377
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2378
+ }
2379
+ const KFileList = /* @__PURE__ */ defineComponent({
2380
+ name: "KFileList",
2381
+ props: {
2382
+ files: {
2383
+ type: Array,
2384
+ default: () => []
2385
+ },
2386
+ removable: {
2387
+ type: Boolean,
2388
+ default: true
2389
+ }
2390
+ },
2391
+ emits: ["remove"],
2392
+ setup(props2, ctx) {
2393
+ return () => {
2394
+ if (props2.files.length === 0) return null;
2395
+ return createVNode("ul", {
2396
+ "class": "k-file-list"
2397
+ }, [props2.files.map((file) => {
2398
+ const statusClass = `k-file-list-item--${file.status}`;
2399
+ return createVNode("li", {
2400
+ "key": file.uid,
2401
+ "class": ["k-file-list-item", statusClass]
2402
+ }, [createVNode("span", {
2403
+ "class": "k-file-list-icon",
2404
+ "aria-hidden": "true"
2405
+ }, [file.status === "success" ? "✓" : file.status === "error" ? "✕" : "○"]), createVNode("div", {
2406
+ "class": "k-file-list-info"
2407
+ }, [createVNode("span", {
2408
+ "class": "k-file-list-name",
2409
+ "title": file.name
2410
+ }, [file.url ? createVNode("a", {
2411
+ "class": "k-file-list-link",
2412
+ "href": file.url,
2413
+ "target": "_blank",
2414
+ "rel": "noopener noreferrer"
2415
+ }, [file.name]) : file.name]), createVNode("span", {
2416
+ "class": "k-file-list-meta"
2417
+ }, [formatSize(file.size), createTextVNode(" · "), STATUS_TEXT[file.status]])]), props2.removable && createVNode("button", {
2418
+ "class": "k-file-list-remove",
2419
+ "type": "button",
2420
+ "title": "移除文件",
2421
+ "onClick": () => ctx.emit("remove", file)
2422
+ }, [createTextVNode("×")]), file.status === "uploading" && createVNode("div", {
2423
+ "class": "k-file-list-progress-track"
2424
+ }, [createVNode("div", {
2425
+ "class": "k-file-list-progress-fill",
2426
+ "style": {
2427
+ width: `${file.percent}%`
2428
+ }
2429
+ }, null)])]);
2430
+ })]);
2431
+ };
2432
+ }
2433
+ });
2434
+ function defineCrudRoutes(config) {
2435
+ const { name, path, permission, title, icon } = config;
2436
+ const resource = permission ?? name.toLowerCase();
2437
+ const basePath = path.replace(/\/+$/, "");
2438
+ const opSpecs = [
2439
+ { op: "list", suffix: "", action: "read", component: config.list, titleSuffix: "", keepAlive: true, hidden: false },
2440
+ { op: "new", suffix: "/new", action: "create", component: config.new, titleSuffix: " - 新建", keepAlive: false, hidden: true },
2441
+ { op: "modify", suffix: "/:id/edit", action: "update", component: config.modify, titleSuffix: " - 编辑", keepAlive: false, hidden: true },
2442
+ { op: "detail", suffix: "/:id", action: "read", component: config.detail, titleSuffix: " - 详情", keepAlive: false, hidden: true }
2443
+ ];
2444
+ const routes = [];
2445
+ for (const spec of opSpecs) {
2446
+ if (!spec.component) continue;
2447
+ routes.push({
2448
+ path: basePath + spec.suffix,
2449
+ // 路由名称格式:EntityOp,如 OrderList、OrderNew
2450
+ name: `${name}${capitalize(spec.op)}`,
2451
+ component: spec.component,
2452
+ meta: {
2453
+ crud: {
2454
+ entity: name,
2455
+ op: spec.op
2456
+ },
2457
+ // 仅在显式传入 permission 时生成权限检查
2458
+ ...permission ? { can: { resource, action: spec.action } } : void 0,
2459
+ requiresAuth: true,
2460
+ // 自动填充 title:基于 config.title + 操作类型后缀
2461
+ ...title ? { title: `${title}${spec.titleSuffix}` } : void 0,
2462
+ // 继承 config.icon
2463
+ ...icon ? { icon } : void 0,
2464
+ keepAlive: spec.keepAlive,
2465
+ hidden: spec.hidden
2466
+ }
2467
+ });
2468
+ }
2469
+ return routes;
2470
+ }
2471
+ function capitalize(str) {
2472
+ if (!str) return str;
2473
+ return str.charAt(0).toUpperCase() + str.slice(1);
2474
+ }
2475
+ const TAB_STORE_KEY = Symbol("tab-store");
2476
+ function createTabStoreInstance() {
2477
+ const tabs = ref([]);
2478
+ const activeTab = ref("");
2479
+ const cachedNames = computed(
2480
+ () => tabs.value.map((tab) => tab.name)
2481
+ );
2482
+ const addTab = (tab) => {
2483
+ const exists = tabs.value.some((t) => t.path === tab.path);
2484
+ if (!exists) {
2485
+ tabs.value.push(tab);
2486
+ }
2487
+ activeTab.value = tab.path;
2488
+ };
2489
+ const addTabFromRoute2 = (route) => {
2490
+ const meta = route.meta;
2491
+ const title = meta.title ?? (typeof route.name === "string" ? route.name : route.path);
2492
+ const name = typeof route.name === "string" ? route.name : route.path;
2493
+ addTab({
2494
+ path: route.path,
2495
+ title,
2496
+ name,
2497
+ closable: true
2498
+ });
2499
+ };
2500
+ const removeTab = (path) => {
2501
+ const index = tabs.value.findIndex((t) => t.path === path);
2502
+ if (index === -1) return void 0;
2503
+ const target = tabs.value[index];
2504
+ if (!target.closable) return void 0;
2505
+ tabs.value.splice(index, 1);
2506
+ if (activeTab.value === path) {
2507
+ const nextTab = tabs.value[index] ?? tabs.value[index - 1];
2508
+ activeTab.value = nextTab ? nextTab.path : "";
2509
+ return activeTab.value || void 0;
2510
+ }
2511
+ return void 0;
2512
+ };
2513
+ const removeOthers = (path) => {
2514
+ tabs.value = tabs.value.filter((t) => !t.closable || t.path === path);
2515
+ if (!tabs.value.some((t) => t.path === activeTab.value)) {
2516
+ activeTab.value = path;
2517
+ }
2518
+ };
2519
+ const removeAll = () => {
2520
+ tabs.value = tabs.value.filter((t) => !t.closable);
2521
+ const first = tabs.value[0];
2522
+ activeTab.value = first ? first.path : "";
2523
+ };
2524
+ const setActive = (path) => {
2525
+ activeTab.value = path;
2526
+ };
2527
+ return {
2528
+ tabs,
2529
+ activeTab,
2530
+ cachedNames,
2531
+ addTab,
2532
+ addTabFromRoute: addTabFromRoute2,
2533
+ removeTab,
2534
+ removeOthers,
2535
+ removeAll,
2536
+ setActive
2537
+ };
2538
+ }
2539
+ function createTabStore() {
2540
+ const store = createTabStoreInstance();
2541
+ provide(TAB_STORE_KEY, store);
2542
+ return store;
2543
+ }
2544
+ function useTabStore() {
2545
+ const store = inject(TAB_STORE_KEY);
2546
+ if (!store) {
2547
+ throw new Error(
2548
+ "[useTabStore] 未找到 tab store 实例,请确保在父级组件中调用了 createTabStore()"
2549
+ );
2550
+ }
2551
+ return store;
2552
+ }
2553
+ function useMenuFromRoutes(routes) {
2554
+ return computed(() => transformRoutes(routes));
2555
+ }
2556
+ function transformRoutes(routes) {
2557
+ var _a;
2558
+ const result = [];
2559
+ for (const route of routes) {
2560
+ const meta = route.meta;
2561
+ if (meta == null ? void 0 : meta.hidden) continue;
2562
+ const label = meta == null ? void 0 : meta.title;
2563
+ if (!label) continue;
2564
+ const key = typeof route.name === "string" ? route.name : route.path;
2565
+ const item = {
2566
+ key,
2567
+ label,
2568
+ path: route.path,
2569
+ icon: meta == null ? void 0 : meta.icon
2570
+ };
2571
+ if ((_a = route.children) == null ? void 0 : _a.length) {
2572
+ const children = transformRoutes(route.children);
2573
+ if (children.length > 0) {
2574
+ item.children = children;
2575
+ delete item.path;
2576
+ }
2577
+ }
2578
+ result.push(item);
2579
+ }
2580
+ return result.sort((a, b) => {
2581
+ const sortA = findSort(routes, a.key);
2582
+ const sortB = findSort(routes, b.key);
2583
+ return sortA - sortB;
2584
+ });
2585
+ }
2586
+ function findSort(routes, key) {
2587
+ var _a;
2588
+ for (const route of routes) {
2589
+ const routeKey = typeof route.name === "string" ? route.name : route.path;
2590
+ if (routeKey === key) {
2591
+ return ((_a = route.meta) == null ? void 0 : _a.sort) ?? Infinity;
2592
+ }
2593
+ }
2594
+ return Infinity;
2595
+ }
2596
+ function createRouterGuard(options) {
2597
+ const {
2598
+ auth,
2599
+ tabStore,
2600
+ loginPath = "/login",
2601
+ forbiddenPath = "/403"
2602
+ } = options;
2603
+ return (to) => {
2604
+ const meta = to.meta;
2605
+ if (to.path === loginPath && auth.isAuthenticated.value) {
2606
+ return "/";
2607
+ }
2608
+ if (meta.can) {
2609
+ const { resource, action } = meta.can;
2610
+ if (!auth.can(resource, action)) {
2611
+ if (to.path === forbiddenPath) return void 0;
2612
+ return forbiddenPath;
2613
+ }
2614
+ }
2615
+ const requiresAuth = meta.requiresAuth !== false;
2616
+ if (requiresAuth && !auth.isAuthenticated.value) {
2617
+ if (to.path === loginPath) return void 0;
2618
+ return loginPath;
2619
+ }
2620
+ if (tabStore) {
2621
+ addTabFromRoute(tabStore, to);
2622
+ }
2623
+ return void 0;
2624
+ };
2625
+ }
2626
+ function addTabFromRoute(tabStore, route) {
2627
+ const meta = route.meta;
2628
+ const title = meta.title ?? (typeof route.name === "string" ? route.name : route.path);
2629
+ const name = typeof route.name === "string" ? route.name : route.path;
2630
+ tabStore.addTab({
2631
+ path: route.path,
2632
+ title,
2633
+ name,
2634
+ closable: true
2635
+ });
2636
+ }
2637
+ function parseResponseHeaders(rawHeaders) {
2638
+ const headers = {};
2639
+ if (!rawHeaders) return headers;
2640
+ rawHeaders.split("\r\n").forEach((line) => {
2641
+ const idx = line.indexOf(":");
2642
+ if (idx > 0) {
2643
+ const key = line.slice(0, idx).trim().toLowerCase();
2644
+ const value = line.slice(idx + 1).trim();
2645
+ headers[key] = value;
2646
+ }
2647
+ });
2648
+ return headers;
2649
+ }
2650
+ class XhrTransport {
2651
+ send(request) {
2652
+ return new Promise((resolve, reject) => {
2653
+ const xhr = new XMLHttpRequest();
2654
+ xhr.open(request.method, request.url, true);
2655
+ for (const [key, value] of Object.entries(request.headers)) {
2656
+ xhr.setRequestHeader(key, value);
2657
+ }
2658
+ if (request.timeout !== void 0 && request.timeout > 0) {
2659
+ xhr.timeout = request.timeout;
2660
+ }
2661
+ if (request.onUploadProgress) {
2662
+ const onProgress = request.onUploadProgress;
2663
+ xhr.upload.addEventListener("progress", (event) => {
2664
+ if (event.lengthComputable) {
2665
+ onProgress({ loaded: event.loaded, total: event.total });
2666
+ }
2667
+ });
2668
+ }
2669
+ if (request.signal) {
2670
+ if (request.signal.aborted) {
2671
+ xhr.abort();
2672
+ reject({ type: "aborted" });
2673
+ return;
2674
+ }
2675
+ request.signal.addEventListener("abort", () => xhr.abort(), { once: true });
2676
+ }
2677
+ xhr.onload = () => {
2678
+ const headers = parseResponseHeaders(xhr.getAllResponseHeaders());
2679
+ const responseText = xhr.responseText;
2680
+ const response = {
2681
+ status: xhr.status,
2682
+ statusText: xhr.statusText,
2683
+ headers,
2684
+ body: null,
2685
+ text: () => Promise.resolve(responseText),
2686
+ json: () => Promise.resolve(JSON.parse(responseText)),
2687
+ blob: () => Promise.resolve(new Blob([responseText]))
2688
+ };
2689
+ resolve(response);
2690
+ };
2691
+ xhr.onerror = () => {
2692
+ reject({ type: "unknown", error: new Error("网络错误") });
2693
+ };
2694
+ xhr.ontimeout = () => {
2695
+ reject({ type: "timeout" });
2696
+ };
2697
+ xhr.onabort = () => {
2698
+ reject({ type: "aborted" });
2699
+ };
2700
+ xhr.send(request.body);
2701
+ });
2702
+ }
2703
+ }
2704
+ function topologicalSort(nodes) {
2705
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
2706
+ const inDegree = /* @__PURE__ */ new Map();
2707
+ const adjacency = /* @__PURE__ */ new Map();
2708
+ for (const node of nodes) {
2709
+ inDegree.set(node.id, 0);
2710
+ adjacency.set(node.id, []);
2711
+ }
2712
+ for (const node of nodes) {
2713
+ for (const dep of node.dependencies) {
2714
+ if (!nodeMap.has(dep)) {
2715
+ throw new Error(`[orchestrator] 节点 "${node.id}" 依赖的 "${dep}" 不存在`);
2716
+ }
2717
+ adjacency.get(dep).push(node.id);
2718
+ inDegree.set(node.id, (inDegree.get(node.id) ?? 0) + 1);
2719
+ }
2720
+ }
2721
+ const layers = [];
2722
+ let queue = nodes.filter((n) => inDegree.get(n.id) === 0).map((n) => n.id);
2723
+ while (queue.length > 0) {
2724
+ layers.push([...queue]);
2725
+ const nextQueue = [];
2726
+ for (const id of queue) {
2727
+ for (const neighbor of adjacency.get(id)) {
2728
+ const degree = inDegree.get(neighbor) - 1;
2729
+ inDegree.set(neighbor, degree);
2730
+ if (degree === 0) {
2731
+ nextQueue.push(neighbor);
2732
+ }
2733
+ }
2734
+ }
2735
+ queue = nextQueue;
2736
+ }
2737
+ const totalSorted = layers.reduce((sum, layer) => sum + layer.length, 0);
2738
+ if (totalSorted !== nodes.length) {
2739
+ throw new Error("[orchestrator] 检测到循环依赖");
2740
+ }
2741
+ return layers;
2742
+ }
2743
+ async function orchestrate(nodes, failureStrategy = "failFast") {
2744
+ if (nodes.length === 0) {
2745
+ return { results: /* @__PURE__ */ new Map(), errors: /* @__PURE__ */ new Map() };
2746
+ }
2747
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
2748
+ const layers = topologicalSort(nodes);
2749
+ const results = /* @__PURE__ */ new Map();
2750
+ const errors = /* @__PURE__ */ new Map();
2751
+ for (const layer of layers) {
2752
+ const tasks = layer.map(async (id) => {
2753
+ const node = nodeMap.get(id);
2754
+ try {
2755
+ const result = await node.execute();
2756
+ results.set(id, result);
2757
+ } catch (e) {
2758
+ const error = e instanceof Error ? e : new Error(String(e));
2759
+ errors.set(id, error);
2760
+ if (failureStrategy === "failFast") {
2761
+ throw error;
2762
+ }
2763
+ }
2764
+ });
2765
+ if (failureStrategy === "failFast") {
2766
+ await Promise.all(tasks);
2767
+ } else {
2768
+ await Promise.allSettled(tasks);
2769
+ }
2770
+ }
2771
+ return { results, errors };
2772
+ }
2773
+ function createNode(id, execute, dependencies = []) {
2774
+ return { id, execute, dependencies };
2775
+ }
2776
+ function after(...deps) {
2777
+ return (id, execute) => {
2778
+ return { id, execute, dependencies: deps };
2779
+ };
2780
+ }
2781
+ function useRequest(client) {
2782
+ const controllers = [];
2783
+ onUnmounted(() => {
2784
+ for (const controller of controllers) {
2785
+ controller.abort("组件已卸载");
2786
+ }
2787
+ controllers.length = 0;
2788
+ });
2789
+ const proxiedClient = {
2790
+ ...client,
2791
+ registerAbortController: (controller) => {
2792
+ controllers.push(controller);
2793
+ client.registerAbortController(controller);
2794
+ }
2795
+ };
2796
+ return proxiedClient;
2797
+ }
2798
+ function usePolling(fn, interval) {
2799
+ const data = ref();
2800
+ const isPolling = ref(false);
2801
+ const error = ref(null);
2802
+ let timer = null;
2803
+ async function tick() {
2804
+ try {
2805
+ const result = await fn();
2806
+ data.value = result;
2807
+ error.value = null;
2808
+ } catch (e) {
2809
+ error.value = e instanceof Error ? e : new Error(String(e));
2810
+ }
2811
+ if (isPolling.value) {
2812
+ timer = setTimeout(tick, interval);
2813
+ }
2814
+ }
2815
+ function start() {
2816
+ if (isPolling.value) return;
2817
+ isPolling.value = true;
2818
+ tick();
2819
+ }
2820
+ function stop() {
2821
+ isPolling.value = false;
2822
+ if (timer !== null) {
2823
+ clearTimeout(timer);
2824
+ timer = null;
2825
+ }
2826
+ }
2827
+ onUnmounted(stop);
2828
+ return { data, isPolling, error, start, stop };
2829
+ }
2830
+ function useBatchLoader(batchFn, options = {}) {
2831
+ const windowMs = options.windowMs ?? 16;
2832
+ const maxBatchSize = options.maxBatchSize ?? 100;
2833
+ let queue = [];
2834
+ let timer = null;
2835
+ async function executeBatch(batch) {
2836
+ try {
2837
+ const keys = batch.map((e) => e.key);
2838
+ const values = await batchFn(keys);
2839
+ if (values.length !== batch.length) {
2840
+ const error = new Error(`[useBatchLoader] 批量函数返回 ${values.length} 个结果,期望 ${batch.length} 个`);
2841
+ batch.forEach((e) => e.reject(error));
2842
+ return;
2843
+ }
2844
+ batch.forEach((entry, i) => entry.resolve(values[i]));
2845
+ } catch (e) {
2846
+ batch.forEach((entry) => entry.reject(e));
2847
+ }
2848
+ }
2849
+ function scheduleFlush() {
2850
+ if (timer !== null) return;
2851
+ timer = setTimeout(() => {
2852
+ timer = null;
2853
+ flush();
2854
+ }, windowMs);
2855
+ }
2856
+ function flush() {
2857
+ if (timer !== null) {
2858
+ clearTimeout(timer);
2859
+ timer = null;
2860
+ }
2861
+ while (queue.length > 0) {
2862
+ const batch = queue.splice(0, maxBatchSize);
2863
+ executeBatch(batch);
2864
+ }
2865
+ }
2866
+ function load(key) {
2867
+ return new Promise((resolve, reject) => {
2868
+ queue.push({ key, resolve, reject });
2869
+ if (queue.length >= maxBatchSize) {
2870
+ flush();
2871
+ } else {
2872
+ scheduleFlush();
2873
+ }
2874
+ });
2875
+ }
2876
+ onUnmounted(() => {
2877
+ if (timer !== null) {
2878
+ clearTimeout(timer);
2879
+ timer = null;
2880
+ }
2881
+ queue.forEach((e) => e.reject(new Error("组件已卸载")));
2882
+ queue = [];
2883
+ });
2884
+ return { load, flush };
2885
+ }
2886
+ function createUploader(uploaderOptions = {}) {
2887
+ const transport = new XhrTransport();
2888
+ function buildURL(url) {
2889
+ if (url.startsWith("http")) return url;
2890
+ return `${uploaderOptions.baseURL ?? ""}${url}`;
2891
+ }
2892
+ function buildHeaders(options) {
2893
+ var _a;
2894
+ const headers = { ...uploaderOptions.headers, ...options.headers };
2895
+ const token = (_a = uploaderOptions.getToken) == null ? void 0 : _a.call(uploaderOptions);
2896
+ if (token) {
2897
+ headers["Authorization"] = `Bearer ${token}`;
2898
+ }
2899
+ return headers;
2900
+ }
2901
+ function doUpload(formData, options) {
2902
+ const progress = ref(0);
2903
+ const abortController = new AbortController();
2904
+ const promise = transport.send({
2905
+ url: buildURL(options.url),
2906
+ method: "POST",
2907
+ headers: buildHeaders(options),
2908
+ body: formData,
2909
+ signal: abortController.signal,
2910
+ onUploadProgress: (event) => {
2911
+ var _a;
2912
+ const percent = event.total > 0 ? Math.round(event.loaded / event.total * 100) : 0;
2913
+ progress.value = percent;
2914
+ (_a = options.onProgress) == null ? void 0 : _a.call(options, percent);
2915
+ }
2916
+ }).then(async (response) => {
2917
+ if (response.status < 200 || response.status >= 300) {
2918
+ throw new NetworkRequestError({
2919
+ type: "httpError",
2920
+ status: response.status,
2921
+ statusText: response.statusText
2922
+ });
2923
+ }
2924
+ const contentType = response.headers["content-type"] ?? "";
2925
+ if (contentType.includes("application/json")) {
2926
+ const json = await response.json();
2927
+ if (isWrappedResponse(json)) {
2928
+ if (!json.success) throw new BusinessError(json.message || "上传失败");
2929
+ return json.data;
2930
+ }
2931
+ return json;
2932
+ }
2933
+ return await response.text();
2934
+ });
2935
+ return {
2936
+ progress,
2937
+ abort: () => abortController.abort("上传已取消"),
2938
+ promise
2939
+ };
2940
+ }
2941
+ return {
2942
+ upload(file, options) {
2943
+ const fieldName = options.fieldName ?? "file";
2944
+ const formData = new FormData();
2945
+ formData.append(fieldName, file);
2946
+ if (options.data) {
2947
+ for (const [key, value] of Object.entries(options.data)) {
2948
+ formData.append(key, value);
2949
+ }
2950
+ }
2951
+ return doUpload(formData, options);
2952
+ },
2953
+ uploadMultiple(files, options) {
2954
+ const fieldName = options.fieldName ?? "files";
2955
+ const formData = new FormData();
2956
+ files.forEach((file) => formData.append(fieldName, file));
2957
+ if (options.data) {
2958
+ for (const [key, value] of Object.entries(options.data)) {
2959
+ formData.append(key, value);
2960
+ }
2961
+ }
2962
+ return doUpload(formData, options);
2963
+ }
2964
+ };
2965
+ }
2966
+ function isWrappedResponse(value) {
2967
+ return typeof value === "object" && value !== null && "success" in value && "data" in value && typeof value.success === "boolean";
2968
+ }
2969
+ function defineUserStore(options) {
2970
+ const { loginApi, onLoginSuccess, onLogoutSuccess } = options;
2971
+ const useStore = defineStore("kine-user", () => {
2972
+ const auth = useAuth();
2973
+ const user = computed(() => auth.user.value);
2974
+ const isLoggedIn = computed(() => auth.isAuthenticated.value);
2975
+ const token = computed(() => auth.token.value);
2976
+ const permissions = computed(() => auth.permissions.value);
2977
+ function can(resource, action, scope) {
2978
+ return auth.can(resource, action, scope);
2979
+ }
2980
+ async function login(account, password) {
2981
+ const result = await loginApi({ account, password });
2982
+ auth.loginWith(result);
2983
+ onLoginSuccess == null ? void 0 : onLoginSuccess();
2984
+ }
2985
+ function logout() {
2986
+ auth.logout();
2987
+ onLogoutSuccess == null ? void 0 : onLogoutSuccess();
2988
+ }
2989
+ async function initUser() {
2990
+ await auth.restore();
2991
+ }
2992
+ return { user, isLoggedIn, token, permissions, can, login, logout, initUser };
2993
+ });
2994
+ return useStore;
2995
+ }
2996
+ export {
2997
+ BusinessError,
2998
+ ControlGate,
2999
+ FetchTransport,
3000
+ KContent,
3001
+ KFileList,
3002
+ KHeader,
3003
+ KImageUpload,
3004
+ KLayout,
3005
+ KLoginPage,
3006
+ KNavMenu,
3007
+ KPageHeader,
3008
+ KSearchTable,
3009
+ KSider,
3010
+ KUpload,
3011
+ LAYOUT_COLLAPSED_KEY,
3012
+ LAYOUT_COLLAPSED_WIDTH_KEY,
3013
+ LAYOUT_SIDER_WIDTH_KEY,
3014
+ LAYOUT_TOGGLE_KEY,
3015
+ NetworkRequestError,
3016
+ REQUEST_CLIENT_KEY,
3017
+ RequestBuilder,
3018
+ XhrTransport,
3019
+ addTabFromRoute,
3020
+ after,
3021
+ createAuth,
3022
+ createAuthGuard,
3023
+ createCrudApp,
3024
+ createDefaultRequest,
3025
+ createNode,
3026
+ createPermissiveRequest,
3027
+ createQueryClient,
3028
+ createRequest,
3029
+ createRouterGuard,
3030
+ createStrictRequest,
3031
+ createTabStore,
3032
+ createUploader,
3033
+ createVCan,
3034
+ defineCrudRoutes,
3035
+ defineRepository,
3036
+ defineUserStore,
3037
+ orchestrate,
3038
+ setupCrud,
3039
+ useAuth,
3040
+ useBatchLoader,
3041
+ useMenuFromRoutes,
3042
+ usePolling,
3043
+ useRequest,
3044
+ useRequestClient,
3045
+ useTabStore
3046
+ };