best-lowcode-runtime 0.1.0

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 (52) hide show
  1. package/README.md +59 -0
  2. package/dist/dev.d.ts +2 -0
  3. package/dist/dev.d.ts.map +1 -0
  4. package/dist/dev.js +269 -0
  5. package/dist/dev.js.map +7 -0
  6. package/dist/index.css +16 -0
  7. package/dist/index.css.map +7 -0
  8. package/dist/index.d.ts +9 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +1130 -0
  11. package/dist/index.js.map +7 -0
  12. package/dist/lowcode/BestCrudPage.d.ts +16 -0
  13. package/dist/lowcode/BestCrudPage.d.ts.map +1 -0
  14. package/dist/lowcode/dev.d.ts +9 -0
  15. package/dist/lowcode/dev.d.ts.map +1 -0
  16. package/dist/lowcode/index.d.ts +8 -0
  17. package/dist/lowcode/index.d.ts.map +1 -0
  18. package/dist/lowcode/list.d.ts +14 -0
  19. package/dist/lowcode/list.d.ts.map +1 -0
  20. package/dist/lowcode/runtime.d.ts +21 -0
  21. package/dist/lowcode/runtime.d.ts.map +1 -0
  22. package/dist/lowcode/schema.d.ts +101 -0
  23. package/dist/lowcode/schema.d.ts.map +1 -0
  24. package/dist/lowcode/validate.d.ts +14 -0
  25. package/dist/lowcode/validate.d.ts.map +1 -0
  26. package/dist/runtime/BestProvider.d.ts +46 -0
  27. package/dist/runtime/BestProvider.d.ts.map +1 -0
  28. package/dist/runtime/index.d.ts +4 -0
  29. package/dist/runtime/index.d.ts.map +1 -0
  30. package/dist/ui/BestBatchInput.d.ts +11 -0
  31. package/dist/ui/BestBatchInput.d.ts.map +1 -0
  32. package/dist/ui/BestDetail.d.ts +8 -0
  33. package/dist/ui/BestDetail.d.ts.map +1 -0
  34. package/dist/ui/BestFilePreview.d.ts +11 -0
  35. package/dist/ui/BestFilePreview.d.ts.map +1 -0
  36. package/dist/ui/BestForm.d.ts +16 -0
  37. package/dist/ui/BestForm.d.ts.map +1 -0
  38. package/dist/ui/BestLightTable.d.ts +13 -0
  39. package/dist/ui/BestLightTable.d.ts.map +1 -0
  40. package/dist/ui/BestOverlay.d.ts +11 -0
  41. package/dist/ui/BestOverlay.d.ts.map +1 -0
  42. package/dist/ui/BestSearch.d.ts +14 -0
  43. package/dist/ui/BestSearch.d.ts.map +1 -0
  44. package/dist/ui/BestStates.d.ts +10 -0
  45. package/dist/ui/BestStates.d.ts.map +1 -0
  46. package/dist/ui/BestTable.d.ts +11 -0
  47. package/dist/ui/BestTable.d.ts.map +1 -0
  48. package/dist/ui/index.d.ts +18 -0
  49. package/dist/ui/index.d.ts.map +1 -0
  50. package/dist/ui/types.d.ts +47 -0
  51. package/dist/ui/types.d.ts.map +1 -0
  52. package/package.json +65 -0
package/dist/index.js ADDED
@@ -0,0 +1,1130 @@
1
+ // src/lowcode/BestCrudPage.tsx
2
+ import { Button as Button4, Modal as Modal2, message } from "antd";
3
+ import dayjs3 from "dayjs";
4
+ import { useCallback, useEffect as useEffect3, useMemo as useMemo3, useRef, useState as useState2 } from "react";
5
+
6
+ // src/runtime/BestProvider.tsx
7
+ import { ConfigProvider } from "antd";
8
+ import { createContext, useContext, useMemo } from "react";
9
+ import { jsx } from "react/jsx-runtime";
10
+ var emptyRegistry = {
11
+ listServices: {},
12
+ services: {},
13
+ dictionaries: {},
14
+ actions: {},
15
+ slots: {},
16
+ access: () => true
17
+ };
18
+ var defaultTheme = {
19
+ token: {
20
+ borderRadius: 6,
21
+ controlHeight: 32,
22
+ fontSize: 14
23
+ }
24
+ };
25
+ var BestRuntimeContext = createContext(emptyRegistry);
26
+ function createBestRegistry(registry = {}) {
27
+ return {
28
+ listServices: registry.listServices ?? {},
29
+ services: registry.services ?? {},
30
+ dictionaries: registry.dictionaries ?? {},
31
+ actions: registry.actions ?? {},
32
+ slots: registry.slots ?? {},
33
+ access: registry.access ?? emptyRegistry.access
34
+ };
35
+ }
36
+ function BestProvider({ children, registry, theme }) {
37
+ const value = useMemo(() => createBestRegistry(registry), [registry]);
38
+ const mergedTheme = useMemo(
39
+ () => ({ ...defaultTheme, ...theme, token: { ...defaultTheme.token, ...theme?.token } }),
40
+ [theme]
41
+ );
42
+ return /* @__PURE__ */ jsx(BestRuntimeContext.Provider, { value, children: /* @__PURE__ */ jsx(ConfigProvider, { theme: mergedTheme, children }) });
43
+ }
44
+ function useBestRegistry() {
45
+ return useContext(BestRuntimeContext);
46
+ }
47
+ function useBestService(key) {
48
+ const service = useBestRegistry().services[key];
49
+ if (!service) throw new Error(`\u672A\u6CE8\u518C\u670D\u52A1\uFF1A${key}`);
50
+ return service;
51
+ }
52
+ function useBestListService(key) {
53
+ return useBestRegistry().listServices[key];
54
+ }
55
+ function useBestAccess(key) {
56
+ const registry = useBestRegistry();
57
+ return key ? registry.access(key) : true;
58
+ }
59
+ function useBestDictionary(key) {
60
+ const dictionaries = useBestRegistry().dictionaries;
61
+ return key ? dictionaries[key] ?? [] : [];
62
+ }
63
+ function useBestAction(key) {
64
+ const action = useBestRegistry().actions[key];
65
+ if (!action) throw new Error(`\u672A\u6CE8\u518C\u52A8\u4F5C\uFF1A${key}`);
66
+ return action;
67
+ }
68
+
69
+ // src/ui/BestBatchInput.tsx
70
+ import { Input } from "antd";
71
+ import { useImperativeHandle, useMemo as useMemo2, useState } from "react";
72
+ import { jsx as jsx2 } from "react/jsx-runtime";
73
+ function parse(value, separator) {
74
+ const escaped = separator.replace(/[\\\]\-^]/g, "\\$&");
75
+ const delimiter = new RegExp(`[${escaped}]`);
76
+ return value.split("\n").map((row) => row.trim()).filter(Boolean).map((row) => row.split(delimiter).map((cell) => cell.trim()));
77
+ }
78
+ function BestBatchInput({
79
+ ref,
80
+ separator = "|",
81
+ value,
82
+ defaultValue,
83
+ onChange,
84
+ ...props
85
+ }) {
86
+ const [innerValue, setInnerValue] = useState(String(defaultValue ?? ""));
87
+ const currentValue = value == null ? innerValue : String(value);
88
+ const parsed = useMemo2(() => parse(currentValue, separator), [currentValue, separator]);
89
+ useImperativeHandle(ref, () => ({ getParsedValue: () => parsed }), [parsed]);
90
+ return /* @__PURE__ */ jsx2(
91
+ Input.TextArea,
92
+ {
93
+ ...props,
94
+ value: currentValue,
95
+ onChange: (event) => {
96
+ const nextValue = event.target.value;
97
+ if (value == null) setInnerValue(nextValue);
98
+ onChange?.(nextValue, parse(nextValue, separator));
99
+ }
100
+ }
101
+ );
102
+ }
103
+
104
+ // src/ui/BestDetail.tsx
105
+ import { Descriptions } from "antd";
106
+ import { jsx as jsx3 } from "react/jsx-runtime";
107
+ function BestDetail({ fields, record = {}, column = 2 }) {
108
+ return /* @__PURE__ */ jsx3(Descriptions, { bordered: true, column, size: "small", children: fields.map((field) => {
109
+ const value = record[field.field];
110
+ const display = field.render ? field.render(value, record) : field.valueEnum?.[String(value)] ?? (value == null || value === "" ? "-" : String(value));
111
+ return /* @__PURE__ */ jsx3(Descriptions.Item, { label: field.label, span: field.span, children: display }, field.field);
112
+ }) });
113
+ }
114
+
115
+ // src/ui/BestFilePreview.tsx
116
+ import { Button, Image, List, Typography } from "antd";
117
+ import { jsx as jsx4, jsxs } from "react/jsx-runtime";
118
+ function isImage(file) {
119
+ return file.mimeType?.startsWith("image/") || /\.(avif|bmp|gif|jpe?g|png|svg|webp)(?:$|[?#])/i.test(file.url);
120
+ }
121
+ function BestFilePreview({ files, onDownload }) {
122
+ return /* @__PURE__ */ jsx4(
123
+ List,
124
+ {
125
+ dataSource: files,
126
+ renderItem: (file) => /* @__PURE__ */ jsxs(
127
+ List.Item,
128
+ {
129
+ actions: [
130
+ /* @__PURE__ */ jsx4(Button, { type: "link", onClick: () => onDownload?.(file), children: "\u4E0B\u8F7D" }, "download")
131
+ ],
132
+ children: [
133
+ isImage(file) ? /* @__PURE__ */ jsx4(Image, { alt: file.name ?? "\u6587\u4EF6\u9884\u89C8", height: 48, preview: true, src: file.url, width: 48 }) : null,
134
+ /* @__PURE__ */ jsx4(Typography.Text, { ellipsis: true, style: { marginInlineStart: isImage(file) ? 12 : 0 }, children: file.name ?? file.url })
135
+ ]
136
+ }
137
+ )
138
+ }
139
+ );
140
+ }
141
+
142
+ // src/ui/BestForm.tsx
143
+ import { Button as Button2, DatePicker, Form, Input as Input2, InputNumber, Select, Space } from "antd";
144
+ import { useEffect } from "react";
145
+ import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
146
+ function FormControl({
147
+ field,
148
+ values,
149
+ mode = "create",
150
+ form,
151
+ ...controlProps
152
+ }) {
153
+ switch (field.component) {
154
+ case "select":
155
+ return /* @__PURE__ */ jsx5(
156
+ Select,
157
+ {
158
+ ...controlProps,
159
+ disabled: field.disabled,
160
+ options: field.options,
161
+ placeholder: field.placeholder
162
+ }
163
+ );
164
+ case "number":
165
+ return /* @__PURE__ */ jsx5(
166
+ InputNumber,
167
+ {
168
+ ...controlProps,
169
+ disabled: field.disabled,
170
+ placeholder: field.placeholder,
171
+ style: { width: "100%" }
172
+ }
173
+ );
174
+ case "date":
175
+ return /* @__PURE__ */ jsx5(DatePicker, { ...controlProps, disabled: field.disabled, style: { width: "100%" } });
176
+ case "dateRange":
177
+ return /* @__PURE__ */ jsx5(
178
+ DatePicker.RangePicker,
179
+ {
180
+ ...controlProps,
181
+ disabled: field.disabled,
182
+ style: { width: "100%" }
183
+ }
184
+ );
185
+ case "textarea":
186
+ return /* @__PURE__ */ jsx5(
187
+ Input2.TextArea,
188
+ {
189
+ ...controlProps,
190
+ disabled: field.disabled,
191
+ placeholder: field.placeholder
192
+ }
193
+ );
194
+ case "slot":
195
+ return /* @__PURE__ */ jsx5(Fragment, { children: field.render?.({
196
+ field: field.field,
197
+ values,
198
+ value: values[field.field],
199
+ mode,
200
+ disabled: field.disabled,
201
+ setValue: (name, value) => form.setFieldValue(name, value)
202
+ }) });
203
+ case "repeatable":
204
+ return /* @__PURE__ */ jsx5(Form.List, { name: field.field, children: (items, { add, remove }) => /* @__PURE__ */ jsxs2(Fragment, { children: [
205
+ items.map((item) => /* @__PURE__ */ jsxs2(Space, { align: "start", children: [
206
+ field.itemFields?.map((itemField) => /* @__PURE__ */ jsx5(
207
+ Form.Item,
208
+ {
209
+ label: itemField.label,
210
+ name: [item.name, itemField.field],
211
+ rules: itemField.rules,
212
+ children: /* @__PURE__ */ jsx5(FormControl, { field: itemField, form, mode, values })
213
+ },
214
+ itemField.field
215
+ )),
216
+ /* @__PURE__ */ jsx5(
217
+ Button2,
218
+ {
219
+ disabled: items.length <= (field.minItems ?? 0),
220
+ onClick: () => remove(item.name),
221
+ type: "link",
222
+ children: "\u5220\u9664"
223
+ }
224
+ )
225
+ ] }, item.key)),
226
+ /* @__PURE__ */ jsx5(
227
+ Button2,
228
+ {
229
+ disabled: Boolean(field.maxItems && items.length >= field.maxItems),
230
+ onClick: () => add(),
231
+ type: "dashed",
232
+ children: "\u65B0\u589E\u4E00\u9879"
233
+ }
234
+ )
235
+ ] }) });
236
+ default:
237
+ return /* @__PURE__ */ jsx5(Input2, { ...controlProps, disabled: field.disabled, placeholder: field.placeholder });
238
+ }
239
+ }
240
+ function FormFields({ fields, form, initialValues, mode = "create" }) {
241
+ const values = Form.useWatch([], { form, preserve: true }) ?? initialValues ?? {};
242
+ const isVisible = (field) => !field.hidden && (!field.visibleWhen || evaluateCondition(field.visibleWhen, values, mode));
243
+ useEffect(() => {
244
+ fields.filter((field) => field.clearWhenHidden && !isVisible(field)).forEach((field) => {
245
+ if (values[field.field] !== void 0) form.setFieldValue(field.field, void 0);
246
+ });
247
+ }, [fields, form, mode, values]);
248
+ return /* @__PURE__ */ jsx5(Fragment, { children: fields.filter((field) => isVisible(field)).map((field) => {
249
+ const control = /* @__PURE__ */ jsx5(
250
+ FormControl,
251
+ {
252
+ field: {
253
+ ...field,
254
+ disabled: field.disabled || Boolean(field.disabledWhen && evaluateCondition(field.disabledWhen, values, mode))
255
+ },
256
+ form,
257
+ mode,
258
+ values
259
+ },
260
+ field.field
261
+ );
262
+ return field.component === "repeatable" ? /* @__PURE__ */ jsx5(Form.Item, { label: field.label, children: control }, field.field) : /* @__PURE__ */ jsx5(Form.Item, { label: field.label, name: field.field, rules: field.rules, children: control }, field.field);
263
+ }) });
264
+ }
265
+ function BestForm({
266
+ fields,
267
+ initialValues,
268
+ loading,
269
+ submitText = "\u63D0\u4EA4",
270
+ extra,
271
+ mode,
272
+ onSubmit,
273
+ onCancel
274
+ }) {
275
+ const [form] = Form.useForm();
276
+ return /* @__PURE__ */ jsxs2(Form, { form, initialValues, layout: "vertical", onFinish: onSubmit, children: [
277
+ /* @__PURE__ */ jsx5(FormFields, { fields, form, initialValues, mode }),
278
+ /* @__PURE__ */ jsxs2(Space, { children: [
279
+ /* @__PURE__ */ jsx5(Button2, { htmlType: "submit", loading, type: "primary", children: submitText }),
280
+ onCancel ? /* @__PURE__ */ jsx5(Button2, { onClick: onCancel, children: "\u53D6\u6D88" }) : null,
281
+ extra
282
+ ] })
283
+ ] });
284
+ }
285
+ function evaluateCondition(condition, values, mode) {
286
+ switch (condition.operator) {
287
+ case "equals":
288
+ return values[condition.field] === condition.value;
289
+ case "notEmpty":
290
+ return values[condition.field] !== void 0 && values[condition.field] !== null && values[condition.field] !== "";
291
+ case "modeEquals":
292
+ return mode === condition.value;
293
+ case "and":
294
+ return condition.conditions.every((item) => evaluateCondition(item, values, mode));
295
+ case "or":
296
+ return condition.conditions.some((item) => evaluateCondition(item, values, mode));
297
+ case "not":
298
+ return !evaluateCondition(condition.condition, values, mode);
299
+ }
300
+ }
301
+
302
+ // src/ui/BestLightTable.tsx
303
+ import { Table } from "antd";
304
+ import { jsx as jsx6 } from "react/jsx-runtime";
305
+ function BestLightTable({
306
+ columns,
307
+ data,
308
+ rowKey
309
+ }) {
310
+ const tableColumns = columns.map((column) => ({
311
+ dataIndex: column.field,
312
+ key: column.field,
313
+ title: column.title,
314
+ width: column.width,
315
+ render: column.render
316
+ }));
317
+ return /* @__PURE__ */ jsx6(
318
+ Table,
319
+ {
320
+ columns: tableColumns,
321
+ dataSource: data,
322
+ pagination: false,
323
+ rowKey,
324
+ size: "small"
325
+ }
326
+ );
327
+ }
328
+
329
+ // src/ui/BestOverlay.tsx
330
+ import { Drawer, Modal } from "antd";
331
+ import { jsx as jsx7 } from "react/jsx-runtime";
332
+ function BestDrawer({ children, open, title, onClose }) {
333
+ return /* @__PURE__ */ jsx7(Drawer, { destroyOnHidden: true, open, title, width: 640, onClose, children });
334
+ }
335
+ function BestModal({ children, open, title, onClose }) {
336
+ return /* @__PURE__ */ jsx7(Modal, { destroyOnHidden: true, footer: null, open, title, onCancel: onClose, children });
337
+ }
338
+
339
+ // src/ui/BestSearch.tsx
340
+ import { Button as Button3, Col, DatePicker as DatePicker2, Form as Form2, Input as Input3, InputNumber as InputNumber2, Row, Select as Select2 } from "antd";
341
+ import dayjs from "dayjs";
342
+ import { useEffect as useEffect2 } from "react";
343
+ import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
344
+ function FieldControl({ field }) {
345
+ switch (field.component) {
346
+ case "select":
347
+ return /* @__PURE__ */ jsx8(
348
+ Select2,
349
+ {
350
+ allowClear: true,
351
+ disabled: field.disabled,
352
+ options: field.options,
353
+ placeholder: field.placeholder
354
+ }
355
+ );
356
+ case "number":
357
+ return /* @__PURE__ */ jsx8(
358
+ InputNumber2,
359
+ {
360
+ disabled: field.disabled,
361
+ placeholder: field.placeholder,
362
+ style: { width: "100%" }
363
+ }
364
+ );
365
+ case "date":
366
+ return /* @__PURE__ */ jsx8(
367
+ DatePicker2,
368
+ {
369
+ disabled: field.disabled,
370
+ disabledDate: (current) => field.maxDate === "today" && current.isAfter(dayjs(), "day"),
371
+ placeholder: field.placeholder,
372
+ style: { width: "100%" }
373
+ }
374
+ );
375
+ case "dateRange":
376
+ return /* @__PURE__ */ jsx8(
377
+ DatePicker2.RangePicker,
378
+ {
379
+ disabled: field.disabled,
380
+ disabledDate: (current) => field.maxDate === "today" && current.isAfter(dayjs(), "day"),
381
+ style: { width: "100%" }
382
+ }
383
+ );
384
+ case "textarea":
385
+ return /* @__PURE__ */ jsx8(Input3.TextArea, { disabled: field.disabled, placeholder: field.placeholder });
386
+ default:
387
+ return /* @__PURE__ */ jsx8(Input3, { allowClear: true, disabled: field.disabled, placeholder: field.placeholder });
388
+ }
389
+ }
390
+ function syncValues(form, value) {
391
+ form.setFieldsValue(value ?? {});
392
+ }
393
+ function BestSearch({
394
+ fields,
395
+ value,
396
+ loading,
397
+ submitText = "\u67E5\u8BE2",
398
+ extra,
399
+ onChange,
400
+ onSearch,
401
+ onReset
402
+ }) {
403
+ const [form] = Form2.useForm();
404
+ useEffect2(() => {
405
+ syncValues(form, value);
406
+ }, [form, value]);
407
+ return /* @__PURE__ */ jsx8(
408
+ Form2,
409
+ {
410
+ form,
411
+ layout: "vertical",
412
+ onFinish: (values) => onSearch(values),
413
+ onValuesChange: (_, values) => onChange?.(values),
414
+ style: { marginBottom: 16 },
415
+ children: /* @__PURE__ */ jsxs3(Row, { gutter: 16, align: "bottom", children: [
416
+ fields.filter((field) => !field.hidden).map((field) => /* @__PURE__ */ jsx8(Col, { span: field.span ?? 6, children: /* @__PURE__ */ jsx8(Form2.Item, { label: field.label, name: field.field, rules: field.rules, children: /* @__PURE__ */ jsx8(FieldControl, { field }) }) }, field.field)),
417
+ /* @__PURE__ */ jsx8(Col, { children: /* @__PURE__ */ jsxs3(Form2.Item, { children: [
418
+ /* @__PURE__ */ jsx8(Button3, { htmlType: "submit", loading, type: "primary", children: submitText }),
419
+ /* @__PURE__ */ jsx8(
420
+ Button3,
421
+ {
422
+ onClick: () => {
423
+ form.resetFields();
424
+ onChange?.({});
425
+ onReset?.();
426
+ },
427
+ style: { marginInlineStart: 8 },
428
+ children: "\u91CD\u7F6E"
429
+ }
430
+ ),
431
+ extra
432
+ ] }) })
433
+ ] })
434
+ }
435
+ );
436
+ }
437
+
438
+ // src/ui/BestStates.tsx
439
+ import { Empty, Result, Spin } from "antd";
440
+ import { jsx as jsx9 } from "react/jsx-runtime";
441
+ function BestLoading({ description = "\u52A0\u8F7D\u4E2D" }) {
442
+ return /* @__PURE__ */ jsx9("div", { style: { display: "grid", minHeight: 160, placeItems: "center" }, children: /* @__PURE__ */ jsx9(Spin, { tip: description }) });
443
+ }
444
+ function BestEmpty({ description = "\u6682\u65E0\u6570\u636E" }) {
445
+ return /* @__PURE__ */ jsx9(Empty, { description });
446
+ }
447
+ function BestError({ description = "\u52A0\u8F7D\u5931\u8D25" }) {
448
+ return /* @__PURE__ */ jsx9(Result, { status: "error", title: description });
449
+ }
450
+
451
+ // src/ui/BestTable.tsx
452
+ import {
453
+ ProTable
454
+ } from "@ant-design/pro-components";
455
+
456
+ // src/ui/BestTable.module.less
457
+ var BestTable_module_default = {};
458
+
459
+ // src/ui/BestTable.tsx
460
+ import { jsx as jsx10 } from "react/jsx-runtime";
461
+ var defaultSearch = {
462
+ layout: "vertical",
463
+ labelWidth: "auto",
464
+ defaultCollapsed: false,
465
+ collapseRender: false,
466
+ span: 6
467
+ };
468
+ function mergeSearch(defaults, value) {
469
+ if (value === false) return false;
470
+ return { ...defaults, ...value ?? {} };
471
+ }
472
+ function BestTable({
473
+ columns,
474
+ cardProps,
475
+ className,
476
+ search,
477
+ emptyText = "\u6682\u65E0\u6570\u636E",
478
+ ...props
479
+ }) {
480
+ return /* @__PURE__ */ jsx10(
481
+ ProTable,
482
+ {
483
+ ...props,
484
+ className: [BestTable_module_default.root, className].filter(Boolean).join(" "),
485
+ columns,
486
+ options: false,
487
+ search: mergeSearch(defaultSearch, search),
488
+ cardProps: cardProps === false ? false : {
489
+ ...cardProps,
490
+ styles: {
491
+ ...cardProps?.styles,
492
+ body: { ...cardProps?.styles?.body, padding: 12 }
493
+ }
494
+ },
495
+ locale: { emptyText }
496
+ }
497
+ );
498
+ }
499
+
500
+ // src/lowcode/runtime.ts
501
+ import dayjs2 from "dayjs";
502
+ import utc from "dayjs/plugin/utc.js";
503
+ dayjs2.extend(utc);
504
+ function toBestListQuery(params, sort) {
505
+ const { current, pageSize, ...filters } = params;
506
+ const normalizedSort = Object.fromEntries(
507
+ Object.entries(sort ?? {}).filter(
508
+ (entry) => entry[1] === "ascend" || entry[1] === "descend"
509
+ )
510
+ );
511
+ return {
512
+ page: typeof current === "number" && current > 0 ? current : 1,
513
+ pageSize: typeof pageSize === "number" && pageSize > 0 ? pageSize : 20,
514
+ filters,
515
+ ...Object.keys(normalizedSort).length ? { sort: normalizedSort } : {}
516
+ };
517
+ }
518
+ function formatCrudValue(value, format) {
519
+ if (value == null || value === "") return "-";
520
+ if (!format || format === "text") return String(value);
521
+ if (format === "money") {
522
+ const amount = typeof value === "number" ? value : Number(value);
523
+ return Number.isFinite(amount) ? new Intl.NumberFormat("zh-CN", {
524
+ minimumFractionDigits: 2,
525
+ maximumFractionDigits: 2
526
+ }).format(amount) : String(value);
527
+ }
528
+ if (typeof value !== "string" && typeof value !== "number" && !(value instanceof Date)) {
529
+ return String(value);
530
+ }
531
+ let timestamp = value;
532
+ if (typeof value === "number" || typeof value === "string") {
533
+ const numericValue = typeof value === "number" ? value : Number(value.trim());
534
+ timestamp = Number.isFinite(numericValue) && numericValue !== 0 ? Math.abs(numericValue) >= 1e12 ? numericValue : numericValue * 1e3 : value;
535
+ }
536
+ const date = dayjs2.utc(timestamp);
537
+ if (!date.isValid()) return String(value);
538
+ return date.format(format === "date" ? "YYYY-MM-DD" : "YYYY-MM-DD HH:mm:ss");
539
+ }
540
+ function errorMessage(error, fallback) {
541
+ return error instanceof Error && error.message.trim() ? error.message : fallback;
542
+ }
543
+ function isAbortError(error, signal) {
544
+ return signal?.aborted || error instanceof DOMException && error.name === "AbortError";
545
+ }
546
+ async function confirmBeforeAction(content, confirm) {
547
+ return !content || confirm(content);
548
+ }
549
+ async function removeCrudRecord(record, service, content, confirm) {
550
+ if (!await confirmBeforeAction(content, confirm)) return false;
551
+ await service(record);
552
+ return true;
553
+ }
554
+ function createLatestPageRequest(service, onError) {
555
+ let sequence = 0;
556
+ let controller;
557
+ let latest = { data: [], success: false, total: 0 };
558
+ return {
559
+ async request(query) {
560
+ controller?.abort();
561
+ controller = new AbortController();
562
+ const currentController = controller;
563
+ const currentSequence = ++sequence;
564
+ try {
565
+ const response = await service(query, { signal: currentController.signal });
566
+ const pageResponse = {
567
+ data: response.items,
568
+ success: true,
569
+ total: response.total
570
+ };
571
+ if (currentSequence !== sequence) return latest;
572
+ latest = pageResponse;
573
+ return pageResponse;
574
+ } catch (error) {
575
+ if (currentSequence !== sequence || isAbortError(error, currentController.signal))
576
+ return latest;
577
+ onError(error);
578
+ return { data: [], success: false, total: 0 };
579
+ }
580
+ },
581
+ abort() {
582
+ controller?.abort();
583
+ }
584
+ };
585
+ }
586
+
587
+ // src/lowcode/schema.ts
588
+ var CRUD_SCHEMA_ID = "https://best.dev/schema/crud/v1";
589
+ var CRUD_SCHEMA_VERSION = 1;
590
+
591
+ // src/lowcode/validate.ts
592
+ var fieldComponents = /* @__PURE__ */ new Set([
593
+ "input",
594
+ "number",
595
+ "select",
596
+ "date",
597
+ "dateRange",
598
+ "textarea",
599
+ "slot",
600
+ "repeatable"
601
+ ]);
602
+ var builtInEffects = /* @__PURE__ */ new Set([
603
+ "openCreate",
604
+ "openDetail",
605
+ "openEdit",
606
+ "remove",
607
+ "runAction",
608
+ "slot"
609
+ ]);
610
+ var columnFormats = /* @__PURE__ */ new Set(["date", "datetime", "money", "text"]);
611
+ function push(diagnostics, path, code, message2) {
612
+ diagnostics.push({ path, code, message: message2 });
613
+ }
614
+ function validateCondition(condition, path, diagnostics) {
615
+ if ((condition.operator === "and" || condition.operator === "or") && condition.conditions.length === 0) {
616
+ push(diagnostics, path, "condition.empty", "\u7EC4\u5408\u6761\u4EF6\u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u5B50\u6761\u4EF6");
617
+ }
618
+ if (condition.operator === "and" || condition.operator === "or") {
619
+ condition.conditions.forEach((item, index) => {
620
+ validateCondition(item, `${path}/conditions/${index}`, diagnostics);
621
+ });
622
+ }
623
+ if (condition.operator === "not")
624
+ validateCondition(condition.condition, `${path}/condition`, diagnostics);
625
+ }
626
+ function validateFields(fields, path, registry, diagnostics) {
627
+ const seen = /* @__PURE__ */ new Set();
628
+ fields?.forEach((field, index) => {
629
+ const fieldPath = `${path}/${index}`;
630
+ if (!field.field) push(diagnostics, fieldPath, "field.missing", "\u5B57\u6BB5\u540D\u4E0D\u80FD\u4E3A\u7A7A");
631
+ if (seen.has(field.field))
632
+ push(diagnostics, fieldPath, "field.duplicate", `\u5B57\u6BB5\u91CD\u590D\uFF1A${field.field}`);
633
+ seen.add(field.field);
634
+ if (!fieldComponents.has(field.component))
635
+ push(diagnostics, fieldPath, "field.component", `\u4E0D\u652F\u6301\u7684\u7EC4\u4EF6\uFF1A${field.component}`);
636
+ if (field.dict && registry && !registry.dictionaries[field.dict]) {
637
+ push(diagnostics, fieldPath, "registry.dictionary", `\u672A\u6CE8\u518C\u5B57\u5178\uFF1A${field.dict}`);
638
+ }
639
+ if (field.component === "slot" && !field.slot)
640
+ push(diagnostics, fieldPath, "field.slot", "slot \u7EC4\u4EF6\u5FC5\u987B\u6307\u5B9A slot key");
641
+ if (field.component === "repeatable" && !field.itemFields?.length)
642
+ push(
643
+ diagnostics,
644
+ fieldPath,
645
+ "field.repeatable",
646
+ "repeatable \u7EC4\u4EF6\u81F3\u5C11\u9700\u8981\u4E00\u4E2A itemFields \u5B57\u6BB5"
647
+ );
648
+ if (field.minItems !== void 0 && field.maxItems !== void 0 && field.minItems > field.maxItems)
649
+ push(diagnostics, fieldPath, "field.repeatable.range", "minItems \u4E0D\u80FD\u5927\u4E8E maxItems");
650
+ if (field.slot && registry && !registry.slots[field.slot])
651
+ push(diagnostics, fieldPath, "registry.slot", `\u672A\u6CE8\u518C\u63D2\u69FD\uFF1A${field.slot}`);
652
+ if (field.visibleWhen)
653
+ validateCondition(field.visibleWhen, `${fieldPath}/visibleWhen`, diagnostics);
654
+ if (field.disabledWhen)
655
+ validateCondition(field.disabledWhen, `${fieldPath}/disabledWhen`, diagnostics);
656
+ });
657
+ }
658
+ function validateActions(actions, path, registry, hasRemoveService, diagnostics) {
659
+ actions?.forEach((action, index) => {
660
+ const actionPath = `${path}/${index}`;
661
+ if (!action.id) push(diagnostics, actionPath, "action.missing", "\u52A8\u4F5C id \u4E0D\u80FD\u4E3A\u7A7A");
662
+ if (!builtInEffects.has(action.effect))
663
+ push(diagnostics, actionPath, "action.effect", `\u4E0D\u652F\u6301\u7684\u52A8\u4F5C\uFF1A${action.effect}`);
664
+ if (action.effect === "runAction" && !action.action)
665
+ push(diagnostics, actionPath, "action.key", "runAction \u5FC5\u987B\u6307\u5B9A action key");
666
+ if (action.effect === "remove" && !hasRemoveService)
667
+ push(diagnostics, actionPath, "action.removeService", "remove \u52A8\u4F5C\u5FC5\u987B\u914D\u7F6E dataSource.remove");
668
+ if (action.effect === "slot" && !action.slot)
669
+ push(diagnostics, actionPath, "action.slot", "slot \u52A8\u4F5C\u5FC5\u987B\u6307\u5B9A slot key");
670
+ if (action.action && registry && !registry.actions[action.action]) {
671
+ push(diagnostics, actionPath, "registry.action", `\u672A\u6CE8\u518C\u52A8\u4F5C\uFF1A${action.action}`);
672
+ }
673
+ if (action.slot && registry && !registry.slots[action.slot]) {
674
+ push(diagnostics, actionPath, "registry.slot", `\u672A\u6CE8\u518C\u63D2\u69FD\uFF1A${action.slot}`);
675
+ }
676
+ });
677
+ }
678
+ function validateCrudPageSchema(schema, registry) {
679
+ const diagnostics = [];
680
+ if (schema.$schema !== CRUD_SCHEMA_ID)
681
+ push(diagnostics, "/$schema", "schema.id", `\u4EC5\u652F\u6301 ${CRUD_SCHEMA_ID}`);
682
+ if (schema.version !== CRUD_SCHEMA_VERSION)
683
+ push(diagnostics, "/version", "schema.version", `\u4EC5\u652F\u6301\u7248\u672C ${CRUD_SCHEMA_VERSION}`);
684
+ if (!schema.id) push(diagnostics, "/id", "page.id", "\u9875\u9762 id \u4E0D\u80FD\u4E3A\u7A7A");
685
+ if (!schema.title) push(diagnostics, "/title", "page.title", "\u9875\u9762\u6807\u9898\u4E0D\u80FD\u4E3A\u7A7A");
686
+ if (!schema.dataSource?.list)
687
+ push(diagnostics, "/dataSource/list", "service.list", "\u5217\u8868\u670D\u52A1\u4E0D\u80FD\u4E3A\u7A7A");
688
+ if (schema.dataSource?.list && registry && !registry.listServices[schema.dataSource.list]) {
689
+ push(
690
+ diagnostics,
691
+ "/dataSource/list",
692
+ "registry.service",
693
+ `\u672A\u6CE8\u518C\u670D\u52A1\uFF1A${schema.dataSource.list}`
694
+ );
695
+ }
696
+ if (schema.dataSource?.detail && registry && !registry.services[schema.dataSource.detail]) {
697
+ push(
698
+ diagnostics,
699
+ "/dataSource/detail",
700
+ "registry.service",
701
+ `\u672A\u6CE8\u518C\u670D\u52A1\uFF1A${schema.dataSource.detail}`
702
+ );
703
+ }
704
+ if (schema.dataSource?.remove && registry && !registry.services[schema.dataSource.remove]) {
705
+ push(
706
+ diagnostics,
707
+ "/dataSource/remove",
708
+ "registry.service",
709
+ `\u672A\u6CE8\u518C\u670D\u52A1\uFF1A${schema.dataSource.remove}`
710
+ );
711
+ }
712
+ validateFields(schema.search, "/search", registry, diagnostics);
713
+ validateFields(schema.form, "/form", registry, diagnostics);
714
+ if (!schema.table?.rowKey) push(diagnostics, "/table/rowKey", "table.rowKey", "rowKey \u4E0D\u80FD\u4E3A\u7A7A");
715
+ if (!schema.table?.columns?.length)
716
+ push(diagnostics, "/table/columns", "table.columns", "\u81F3\u5C11\u9700\u8981\u4E00\u5217");
717
+ schema.table?.columns?.forEach((column, index) => {
718
+ if (column.format && !columnFormats.has(column.format)) {
719
+ push(
720
+ diagnostics,
721
+ `/table/columns/${index}/format`,
722
+ "column.format",
723
+ `\u4E0D\u652F\u6301\u7684\u683C\u5F0F\uFF1A${column.format}`
724
+ );
725
+ }
726
+ if (column.dict && registry && !registry.dictionaries[column.dict]) {
727
+ push(
728
+ diagnostics,
729
+ `/table/columns/${index}/dict`,
730
+ "registry.dictionary",
731
+ `\u672A\u6CE8\u518C\u5B57\u5178\uFF1A${column.dict}`
732
+ );
733
+ }
734
+ if (column.slot && registry && !registry.slots[column.slot]) {
735
+ push(
736
+ diagnostics,
737
+ `/table/columns/${index}/slot`,
738
+ "registry.slot",
739
+ `\u672A\u6CE8\u518C\u63D2\u69FD\uFF1A${column.slot}`
740
+ );
741
+ }
742
+ });
743
+ schema.detail?.fields.forEach((field, index) => {
744
+ if (field.slot && registry && !registry.slots[field.slot])
745
+ push(
746
+ diagnostics,
747
+ `/detail/fields/${index}/slot`,
748
+ "registry.slot",
749
+ `\u672A\u6CE8\u518C\u63D2\u69FD\uFF1A${field.slot}`
750
+ );
751
+ });
752
+ validateActions(
753
+ schema.table?.actions,
754
+ "/table/actions",
755
+ registry,
756
+ Boolean(schema.dataSource?.remove),
757
+ diagnostics
758
+ );
759
+ validateActions(
760
+ schema.toolbar,
761
+ "/toolbar",
762
+ registry,
763
+ Boolean(schema.dataSource?.remove),
764
+ diagnostics
765
+ );
766
+ return { valid: diagnostics.length === 0, diagnostics };
767
+ }
768
+ function assertValidCrudPageSchema(schema, registry) {
769
+ const result = validateCrudPageSchema(schema, registry);
770
+ if (!result.valid) {
771
+ const message2 = result.diagnostics.map((item) => `${item.path}: ${item.message}`).join("\n");
772
+ throw new Error(`Schema \u6821\u9A8C\u5931\u8D25\uFF1A
773
+ ${message2}`);
774
+ }
775
+ }
776
+
777
+ // src/lowcode/BestCrudPage.tsx
778
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
779
+ function toFieldDefinition(field, options, slots) {
780
+ const slot = field.slot;
781
+ return {
782
+ field: field.field,
783
+ label: field.label,
784
+ component: field.component,
785
+ placeholder: field.placeholder,
786
+ options,
787
+ required: field.required,
788
+ disabled: field.disabled,
789
+ hidden: field.hidden,
790
+ maxDate: field.maxDate,
791
+ span: field.span,
792
+ visibleWhen: field.visibleWhen,
793
+ disabledWhen: field.disabledWhen,
794
+ clearWhenHidden: field.clearWhenHidden,
795
+ itemFields: field.itemFields?.map((item) => toFieldDefinition(item, [], slots)),
796
+ minItems: field.minItems,
797
+ maxItems: field.maxItems,
798
+ render: slot ? (context) => slots[slot]?.(context) : void 0,
799
+ rules: field.required ? [{ required: true, message: `\u8BF7\u586B\u5199${field.label}` }] : void 0
800
+ };
801
+ }
802
+ function useFields(fields = []) {
803
+ const registry = useBestRegistry();
804
+ return fields.map(
805
+ (field) => toFieldDefinition(
806
+ field,
807
+ field.dict ? registry.dictionaries[field.dict] ?? [] : [],
808
+ registry.slots
809
+ )
810
+ );
811
+ }
812
+ function toDetailField(field, dictionary, slots) {
813
+ const slot = field.slot;
814
+ const valueEnum = field.dict ? Object.fromEntries(
815
+ (dictionary[field.dict] ?? []).map((item) => [String(item.value), item.label])
816
+ ) : void 0;
817
+ return {
818
+ field: field.field,
819
+ label: field.label,
820
+ span: field.span,
821
+ valueEnum,
822
+ render: slot ? (value, record) => slots[slot]?.({ field: field.field, record, value }) ?? "-" : void 0
823
+ };
824
+ }
825
+ function toTableColumn(column, dictionary, slots) {
826
+ const valueEnum = column.dict ? Object.fromEntries(
827
+ (dictionary[column.dict] ?? []).map((item) => [String(item.value), { text: item.label }])
828
+ ) : void 0;
829
+ return {
830
+ dataIndex: column.field,
831
+ search: false,
832
+ title: column.title,
833
+ width: column.width,
834
+ valueEnum,
835
+ render: (value, record) => column.slot ? slots[column.slot]?.({ field: column.field, record, value }) ?? "-" : formatCrudValue(value, column.format)
836
+ };
837
+ }
838
+ function toProTableSearchColumn(field, dictionary) {
839
+ const valueType = (() => {
840
+ switch (field.component) {
841
+ case "number":
842
+ return "digit";
843
+ case "select":
844
+ return "select";
845
+ case "date":
846
+ return "date";
847
+ case "dateRange":
848
+ return "dateRange";
849
+ default:
850
+ return "text";
851
+ }
852
+ })();
853
+ const options = field.dict ? dictionary[field.dict] ?? [] : void 0;
854
+ return {
855
+ dataIndex: field.field,
856
+ title: field.label,
857
+ valueType,
858
+ search: true,
859
+ hideInTable: true,
860
+ initialValue: field.defaultValue,
861
+ fieldProps: {
862
+ ...options ? { options } : {},
863
+ ...field.maxDate === "today" ? { disabledDate: (current) => current.isAfter(dayjs3(), "day") } : {}
864
+ }
865
+ };
866
+ }
867
+ function confirmAction(content) {
868
+ return new Promise((resolve) => {
869
+ Modal2.confirm({
870
+ content,
871
+ okText: "\u786E\u8BA4",
872
+ cancelText: "\u53D6\u6D88",
873
+ onOk: () => resolve(true),
874
+ onCancel: () => resolve(false)
875
+ });
876
+ });
877
+ }
878
+ function BestCrudPage({ adapter, className, schema }) {
879
+ const registry = useBestRegistry();
880
+ assertValidCrudPageSchema(schema, registry);
881
+ const registeredListService = useBestListService(schema.dataSource.list);
882
+ if (!registeredListService) throw new Error(`\u672A\u6CE8\u518C\u5217\u8868\u670D\u52A1\uFF1A${schema.dataSource.list}`);
883
+ const listService = useCallback(
884
+ async (query2, options) => {
885
+ const response = await registeredListService(query2, options);
886
+ return adapter?.fromList ? { ...response, items: adapter.fromList(response.items) } : response;
887
+ },
888
+ [adapter, registeredListService]
889
+ );
890
+ const searchFields = useFields(schema.search);
891
+ const useBestSearch = schema.searchMode === "bestSearch";
892
+ const formFields = useFields(schema.form);
893
+ const actionRef = useRef(void 0);
894
+ const pageRequestRef = useRef(void 0);
895
+ const [query, setQuery] = useState2({});
896
+ const [drawer, setDrawer] = useState2({ mode: "closed" });
897
+ const [submitting, setSubmitting] = useState2(false);
898
+ const handleAction = useCallback(
899
+ async (action, record) => {
900
+ if (action.access && !registry.access(action.access)) return;
901
+ if (action.effect !== "remove" && !await confirmBeforeAction(action.confirm, confirmAction))
902
+ return;
903
+ if (action.effect === "openDetail") setDrawer({ mode: "detail", record });
904
+ if (action.effect === "openEdit") {
905
+ let editRecord = record;
906
+ if (schema.dataSource.detail) {
907
+ const detailService = registry.services[schema.dataSource.detail];
908
+ if (!detailService) {
909
+ message.error(`\u672A\u6CE8\u518C\u670D\u52A1\uFF1A${schema.dataSource.detail}`);
910
+ return;
911
+ }
912
+ try {
913
+ const detail = await detailService(record ?? {});
914
+ if (detail && typeof detail === "object" && !Array.isArray(detail)) {
915
+ editRecord = adapter?.fromDetail ? adapter.fromDetail(detail) : detail;
916
+ }
917
+ } catch (error) {
918
+ message.error(errorMessage(error, `${schema.title}\u8BE6\u60C5\u52A0\u8F7D\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5`));
919
+ return;
920
+ }
921
+ }
922
+ setDrawer({ mode: "edit", record: editRecord });
923
+ }
924
+ if (action.effect === "openCreate") setDrawer({ mode: "create" });
925
+ if (action.effect === "remove" && schema.dataSource.remove) {
926
+ const removeService = registry.services[schema.dataSource.remove];
927
+ if (!removeService) return;
928
+ try {
929
+ const deleted = await removeCrudRecord(
930
+ record ?? {},
931
+ removeService,
932
+ action.confirm ?? `\u786E\u8BA4\u5220\u9664${schema.title}\uFF1F`,
933
+ confirmAction
934
+ );
935
+ if (deleted) {
936
+ message.success("\u5220\u9664\u6210\u529F");
937
+ actionRef.current?.reload();
938
+ }
939
+ } catch (error) {
940
+ message.error(errorMessage(error, "\u5220\u9664\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"));
941
+ }
942
+ }
943
+ if (action.effect === "runAction" && action.action) {
944
+ try {
945
+ await registry.actions[action.action]?.({ record });
946
+ actionRef.current?.reload();
947
+ } catch (error) {
948
+ message.error(errorMessage(error, `${action.label}\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5`));
949
+ }
950
+ }
951
+ },
952
+ [registry]
953
+ );
954
+ const columns = useMemo3(() => {
955
+ const proTableSearchFields = useBestSearch ? [] : schema.search ?? [];
956
+ const searchColumns = new Map(
957
+ proTableSearchFields.map((field) => [
958
+ field.field,
959
+ toProTableSearchColumn(field, registry.dictionaries)
960
+ ])
961
+ );
962
+ const configuredColumns = schema.table.columns.map((column) => {
963
+ const tableColumn = toTableColumn(column, registry.dictionaries, registry.slots);
964
+ const searchColumn = searchColumns.get(column.field);
965
+ if (!searchColumn) return tableColumn;
966
+ searchColumns.delete(column.field);
967
+ return { ...tableColumn, ...searchColumn, hideInTable: false };
968
+ });
969
+ configuredColumns.push(...searchColumns.values());
970
+ if (!schema.table.actions?.length) return configuredColumns;
971
+ configuredColumns.push({
972
+ title: "\u64CD\u4F5C",
973
+ valueType: "option",
974
+ render: (_, record) => schema.table.actions?.map((action) => /* @__PURE__ */ jsx11(ActionButton, { action, record, onExecute: handleAction }, action.id))
975
+ });
976
+ return configuredColumns;
977
+ }, [
978
+ handleAction,
979
+ registry.dictionaries,
980
+ registry.slots,
981
+ schema.search,
982
+ schema.table.actions,
983
+ schema.table.columns,
984
+ useBestSearch
985
+ ]);
986
+ const request = useMemo3(() => {
987
+ pageRequestRef.current?.abort();
988
+ const pageRequest = createLatestPageRequest(
989
+ listService,
990
+ (error) => message.error(errorMessage(error, `${schema.title}\u52A0\u8F7D\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5`))
991
+ );
992
+ pageRequestRef.current = pageRequest;
993
+ return (params, sort) => pageRequest.request(toBestListQuery({ ...params, ...query }, sort));
994
+ }, [listService, query, schema.title]);
995
+ useEffect3(() => () => pageRequestRef.current?.abort(), []);
996
+ const rowKey = schema.table.rowKey;
997
+ const submitForm = useCallback(
998
+ async (values) => {
999
+ const serviceKey = drawer.mode === "edit" ? schema.dataSource.update : schema.dataSource.create;
1000
+ if (!serviceKey) return;
1001
+ const record = drawer.mode === "closed" ? void 0 : drawer.record;
1002
+ setSubmitting(true);
1003
+ try {
1004
+ const mergedValues = { ...record, ...values };
1005
+ const payload = drawer.mode === "edit" ? adapter?.toUpdatePayload?.(mergedValues, record) ?? mergedValues : adapter?.toCreatePayload?.(mergedValues) ?? mergedValues;
1006
+ await registry.services[serviceKey]?.(payload);
1007
+ message.success("\u4FDD\u5B58\u6210\u529F");
1008
+ setDrawer({ mode: "closed" });
1009
+ actionRef.current?.reload();
1010
+ } catch (error) {
1011
+ message.error(errorMessage(error, "\u4FDD\u5B58\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"));
1012
+ } finally {
1013
+ setSubmitting(false);
1014
+ }
1015
+ },
1016
+ [adapter, drawer, registry.services, schema.dataSource.create, schema.dataSource.update]
1017
+ );
1018
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1019
+ useBestSearch && searchFields.length ? /* @__PURE__ */ jsx11(
1020
+ BestSearch,
1021
+ {
1022
+ fields: searchFields,
1023
+ onReset: () => {
1024
+ setQuery({});
1025
+ actionRef.current?.reload();
1026
+ },
1027
+ onSearch: (values) => {
1028
+ setQuery(values);
1029
+ actionRef.current?.reload();
1030
+ }
1031
+ }
1032
+ ) : null,
1033
+ /* @__PURE__ */ jsx11(
1034
+ BestTable,
1035
+ {
1036
+ actionRef,
1037
+ className,
1038
+ columns,
1039
+ pagination: { showSizeChanger: true, defaultPageSize: schema.table.pageSize ?? 20 },
1040
+ request,
1041
+ rowKey: Array.isArray(rowKey) ? (record) => rowKey.map((field) => String(record[field] ?? "")).join("-") : rowKey,
1042
+ search: useBestSearch ? false : void 0,
1043
+ scroll: schema.table.scrollX ? { x: schema.table.scrollX } : void 0,
1044
+ toolBarRender: () => schema.toolbar?.map((action) => /* @__PURE__ */ jsx11(ActionButton, { action, onExecute: handleAction }, action.id)) ?? []
1045
+ }
1046
+ ),
1047
+ /* @__PURE__ */ jsxs4(
1048
+ BestDrawer,
1049
+ {
1050
+ open: drawer.mode !== "closed",
1051
+ title: drawer.mode === "detail" ? `${schema.title}\u8BE6\u60C5` : drawer.mode === "edit" ? `\u7F16\u8F91${schema.title}` : `\u65B0\u5EFA${schema.title}`,
1052
+ onClose: () => setDrawer({ mode: "closed" }),
1053
+ children: [
1054
+ drawer.mode === "detail" ? /* @__PURE__ */ jsx11(
1055
+ BestDetail,
1056
+ {
1057
+ fields: schema.detail?.fields.map(
1058
+ (field) => toDetailField(field, registry.dictionaries, registry.slots)
1059
+ ) ?? [],
1060
+ record: drawer.record
1061
+ }
1062
+ ) : null,
1063
+ drawer.mode === "edit" || drawer.mode === "create" ? /* @__PURE__ */ jsx11(
1064
+ BestForm,
1065
+ {
1066
+ fields: formFields,
1067
+ initialValues: drawer.record,
1068
+ loading: submitting,
1069
+ mode: drawer.mode,
1070
+ onCancel: () => setDrawer({ mode: "closed" }),
1071
+ onSubmit: (values) => {
1072
+ void submitForm(values);
1073
+ }
1074
+ }
1075
+ ) : null
1076
+ ]
1077
+ }
1078
+ )
1079
+ ] });
1080
+ function ActionButton({
1081
+ action,
1082
+ record,
1083
+ onExecute
1084
+ }) {
1085
+ const permitted = !action.access || registry.access(action.access);
1086
+ if (!permitted) return null;
1087
+ if (action.effect === "slot" && action.slot) {
1088
+ return registry.slots[action.slot]?.({ record }) ?? null;
1089
+ }
1090
+ return /* @__PURE__ */ jsx11(
1091
+ Button4,
1092
+ {
1093
+ type: "link",
1094
+ onClick: () => {
1095
+ void onExecute(action, record);
1096
+ },
1097
+ children: action.label
1098
+ },
1099
+ action.id
1100
+ );
1101
+ }
1102
+ }
1103
+ export {
1104
+ BestBatchInput,
1105
+ BestCrudPage,
1106
+ BestDetail,
1107
+ BestDrawer,
1108
+ BestEmpty,
1109
+ BestError,
1110
+ BestFilePreview,
1111
+ BestForm,
1112
+ BestLightTable,
1113
+ BestLoading,
1114
+ BestModal,
1115
+ BestProvider,
1116
+ BestSearch,
1117
+ BestTable,
1118
+ CRUD_SCHEMA_ID,
1119
+ CRUD_SCHEMA_VERSION,
1120
+ assertValidCrudPageSchema,
1121
+ createBestRegistry,
1122
+ useBestAccess,
1123
+ useBestAction,
1124
+ useBestDictionary,
1125
+ useBestListService,
1126
+ useBestRegistry,
1127
+ useBestService,
1128
+ validateCrudPageSchema
1129
+ };
1130
+ //# sourceMappingURL=index.js.map