best-lowcode-runtime 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -34,12 +34,12 @@ function createBestCrudAdapter(options = {}) {
34
34
 
35
35
  // src/lowcode/BestCrudPage.tsx
36
36
  import { Button as Button4, Modal as Modal2, message } from "antd";
37
- import dayjs5 from "dayjs";
38
- import { useCallback, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo5, useRef as useRef3, useState as useState3 } from "react";
37
+ import dayjs6 from "dayjs";
38
+ import { useCallback as useCallback2, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo5, useRef as useRef3, useState as useState4 } from "react";
39
39
 
40
40
  // src/runtime/BestProvider.tsx
41
41
  import { ConfigProvider } from "antd";
42
- import { createContext, useContext, useMemo } from "react";
42
+ import { createContext, useCallback, useContext, useMemo, useState } from "react";
43
43
  import { jsx } from "react/jsx-runtime";
44
44
  var emptyRegistry = {
45
45
  listServices: {},
@@ -57,6 +57,7 @@ var defaultTheme = {
57
57
  }
58
58
  };
59
59
  var BestRuntimeContext = createContext(emptyRegistry);
60
+ var BestDictionaryActionsContext = createContext(null);
60
61
  function composeBestRegistry(...layers) {
61
62
  const result = {
62
63
  listServices: {},
@@ -87,12 +88,20 @@ function createBestRegistry(registry = {}) {
87
88
  return composeBestRegistry(registry);
88
89
  }
89
90
  function BestProvider({ children, registry, theme }) {
90
- const value = useMemo(() => createBestRegistry(registry), [registry]);
91
+ const [dictionaryUpdates, setDictionaryUpdates] = useState({});
92
+ const setDictionary = useCallback((key, items) => {
93
+ setDictionaryUpdates((current) => ({ ...current, [key]: items }));
94
+ }, []);
95
+ const value = useMemo(() => {
96
+ const next = createBestRegistry(registry);
97
+ Object.assign(next.dictionaries, dictionaryUpdates);
98
+ return next;
99
+ }, [dictionaryUpdates, registry]);
91
100
  const mergedTheme = useMemo(
92
101
  () => ({ ...defaultTheme, ...theme, token: { ...defaultTheme.token, ...theme?.token } }),
93
102
  [theme]
94
103
  );
95
- return /* @__PURE__ */ jsx(BestRuntimeContext.Provider, { value, children: /* @__PURE__ */ jsx(ConfigProvider, { theme: mergedTheme, children }) });
104
+ return /* @__PURE__ */ jsx(BestRuntimeContext.Provider, { value, children: /* @__PURE__ */ jsx(ConfigProvider, { theme: mergedTheme, children: /* @__PURE__ */ jsx(BestDictionaryActionsContext.Provider, { value: { setDictionary }, children }) }) });
96
105
  }
97
106
  function useBestRegistry() {
98
107
  return useContext(BestRuntimeContext);
@@ -113,6 +122,11 @@ function useBestDictionary(key) {
113
122
  const dictionaries = useBestRegistry().dictionaries;
114
123
  return key ? dictionaries[key] ?? [] : [];
115
124
  }
125
+ function useBestDictionaryActions() {
126
+ const actions = useContext(BestDictionaryActionsContext);
127
+ if (!actions) throw new Error("useBestDictionaryActions \u5FC5\u987B\u5728 BestProvider \u5185\u4F7F\u7528");
128
+ return actions;
129
+ }
116
130
  function useBestAction(key) {
117
131
  const action = useBestRegistry().actions[key];
118
132
  if (!action) throw new Error(`\u672A\u6CE8\u518C\u52A8\u4F5C\uFF1A${key}`);
@@ -121,7 +135,7 @@ function useBestAction(key) {
121
135
 
122
136
  // src/ui/BestBatchInput.tsx
123
137
  import { Input } from "antd";
124
- import { useImperativeHandle, useMemo as useMemo2, useState } from "react";
138
+ import { useImperativeHandle, useMemo as useMemo2, useState as useState2 } from "react";
125
139
  import { jsx as jsx2 } from "react/jsx-runtime";
126
140
  function parse(value, separator) {
127
141
  const escaped = separator.replace(/[\\\]\-^]/g, "\\$&");
@@ -136,7 +150,7 @@ function BestBatchInput({
136
150
  onChange,
137
151
  ...props
138
152
  }) {
139
- const [innerValue, setInnerValue] = useState(String(defaultValue ?? ""));
153
+ const [innerValue, setInnerValue] = useState2(String(defaultValue ?? ""));
140
154
  const currentValue = value == null ? innerValue : String(value);
141
155
  const parsed = useMemo2(() => parse(currentValue, separator), [currentValue, separator]);
142
156
  useImperativeHandle(ref, () => ({ getParsedValue: () => parsed }), [parsed]);
@@ -155,15 +169,66 @@ function BestBatchInput({
155
169
  }
156
170
 
157
171
  // src/ui/BestDetail.tsx
158
- import { Descriptions } from "antd";
159
- import { jsx as jsx3 } from "react/jsx-runtime";
160
- function BestDetail({ fields, record = {}, column = 2 }) {
161
- return /* @__PURE__ */ jsx3(Descriptions, { bordered: true, column, size: "small", children: fields.map((field) => {
172
+ import { Descriptions, Empty, Table, Typography } from "antd";
173
+ import dayjs2 from "dayjs";
174
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
175
+ function BestDetail({ fields, record = {}, column = 2, sections, sectionColumns, sectionGap = 20 }) {
176
+ const content = sections?.length ? /* @__PURE__ */ jsx3("div", { style: { display: "grid", gridTemplateColumns: `repeat(${sectionColumns ?? 1}, minmax(0, 1fr))`, gap: sectionGap }, children: sections.map((section) => {
177
+ const card = section.variant === "card";
178
+ return /* @__PURE__ */ jsxs("section", { style: { gridColumn: section.span ? `span ${section.span}` : void 0, marginBottom: 0, border: card ? "1px solid #f0f0f0" : void 0, borderRadius: card ? 6 : void 0, overflow: card ? "hidden" : void 0, background: card ? "#fff" : void 0 }, children: [
179
+ section.title ? /* @__PURE__ */ jsx3("div", { style: { padding: card ? "8px 12px" : void 0, background: card ? "#fafafa" : void 0, borderBottom: card ? "1px solid #f0f0f0" : void 0 }, children: /* @__PURE__ */ jsx3(Typography.Title, { level: 5, style: { margin: 0 }, children: section.title }) }) : null,
180
+ /* @__PURE__ */ jsxs("div", { style: { padding: card ? 12 : void 0 }, children: [
181
+ section.description ? /* @__PURE__ */ jsx3(Typography.Paragraph, { type: "secondary", children: section.description }) : null,
182
+ section.content,
183
+ section.fields ? /* @__PURE__ */ jsx3(FieldDescriptions, { fields: section.fields, record, column: section.columns ?? column }) : null,
184
+ section.table ? /* @__PURE__ */ jsx3(DetailTable, { table: section.table }) : null
185
+ ] })
186
+ ] }, section.key);
187
+ }) }) : /* @__PURE__ */ jsx3(FieldDescriptions, { fields, record, column });
188
+ return content;
189
+ }
190
+ function FieldDescriptions({ fields, record, column }) {
191
+ const visibleFields = fields.filter((field) => field.visible !== false);
192
+ return /* @__PURE__ */ jsx3(Descriptions, { bordered: true, column, size: "small", children: visibleFields.map((field) => {
162
193
  const value = getPathValue(record, field.field);
163
- const display = field.render ? field.render(value, record) : field.valueEnum?.[String(value)] ?? (value == null || value === "" ? "-" : String(value));
194
+ const display = field.render ? field.render(value, record) : field.valueEnum?.[String(value)] ?? formatValue(value, field.format, field.emptyText);
164
195
  return /* @__PURE__ */ jsx3(Descriptions.Item, { label: field.label, span: field.span, children: display }, field.field);
165
196
  }) });
166
197
  }
198
+ function DetailTable({ table }) {
199
+ if (!table.data.length) return /* @__PURE__ */ jsx3(Empty, { image: Empty.PRESENTED_IMAGE_SIMPLE, description: "\u6682\u65E0\u6570\u636E" });
200
+ return /* @__PURE__ */ jsx3(
201
+ Table,
202
+ {
203
+ size: "small",
204
+ bordered: true,
205
+ pagination: false,
206
+ rowKey: table.rowKey,
207
+ dataSource: table.data,
208
+ scroll: table.scrollX ? { x: table.scrollX } : void 0,
209
+ columns: table.columns.map((column) => ({ ...column, dataIndex: column.dataIndex ?? column.key }))
210
+ }
211
+ );
212
+ }
213
+ function formatValue(value, format, emptyText = "-") {
214
+ if (value == null || value === "") return emptyText;
215
+ const type = typeof format === "object" ? format.type : format;
216
+ if (!type || type === "text") return String(value);
217
+ if (type === "number") {
218
+ const number = Number(value);
219
+ if (!Number.isFinite(number)) return String(value);
220
+ const precision = typeof format === "object" ? format.precision : void 0;
221
+ return number.toLocaleString("zh-CN", precision === void 0 ? void 0 : { minimumFractionDigits: precision, maximumFractionDigits: precision });
222
+ }
223
+ if (type === "money") {
224
+ const number = Number(value);
225
+ return Number.isFinite(number) ? number.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : String(value);
226
+ }
227
+ if (type === "boolean") return value ? "\u662F" : "\u5426";
228
+ if (type === "json") return typeof value === "string" ? value : JSON.stringify(value);
229
+ const date = dayjs2(value);
230
+ return date.isValid() ? date.format(type === "date" ? "YYYY-MM-DD" : "YYYY-MM-DD HH:mm:ss") : String(value);
231
+ }
167
232
  function getPathValue(values, path) {
168
233
  if (Object.hasOwn(values, path)) return values[path];
169
234
  return path.split(".").reduce((current, part) => {
@@ -175,8 +240,8 @@ function getPathValue(values, path) {
175
240
  }
176
241
 
177
242
  // src/ui/BestFilePreview.tsx
178
- import { Button, Image, List, Typography } from "antd";
179
- import { jsx as jsx4, jsxs } from "react/jsx-runtime";
243
+ import { Button, Image, List, Typography as Typography2 } from "antd";
244
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
180
245
  function isImage(file) {
181
246
  return file.mimeType?.startsWith("image/") || /\.(avif|bmp|gif|jpe?g|png|svg|webp)(?:$|[?#])/i.test(file.url);
182
247
  }
@@ -185,7 +250,7 @@ function BestFilePreview({ files, onDownload }) {
185
250
  List,
186
251
  {
187
252
  dataSource: files,
188
- renderItem: (file) => /* @__PURE__ */ jsxs(
253
+ renderItem: (file) => /* @__PURE__ */ jsxs2(
189
254
  List.Item,
190
255
  {
191
256
  actions: [
@@ -193,7 +258,7 @@ function BestFilePreview({ files, onDownload }) {
193
258
  ],
194
259
  children: [
195
260
  isImage(file) ? /* @__PURE__ */ jsx4(Image, { alt: file.name ?? "\u6587\u4EF6\u9884\u89C8", height: 48, preview: true, src: file.url, width: 48 }) : null,
196
- /* @__PURE__ */ jsx4(Typography.Text, { ellipsis: true, style: { marginInlineStart: isImage(file) ? 12 : 0 }, children: file.name ?? file.url })
261
+ /* @__PURE__ */ jsx4(Typography2.Text, { ellipsis: true, style: { marginInlineStart: isImage(file) ? 12 : 0 }, children: file.name ?? file.url })
197
262
  ]
198
263
  }
199
264
  )
@@ -203,9 +268,9 @@ function BestFilePreview({ files, onDownload }) {
203
268
 
204
269
  // src/ui/BestForm.tsx
205
270
  import { Button as Button2, DatePicker, Form, Input as Input2, InputNumber, Select, Space } from "antd";
206
- import dayjs2 from "dayjs";
207
- import { useEffect, useMemo as useMemo3, useRef, useState as useState2 } from "react";
208
- import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
271
+ import dayjs3 from "dayjs";
272
+ import { useEffect, useMemo as useMemo3, useRef, useState as useState3 } from "react";
273
+ import { Fragment, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
209
274
  function toNamePath(path) {
210
275
  return path === void 0 ? [] : Array.isArray(path) ? path : [path];
211
276
  }
@@ -216,7 +281,7 @@ function isRecord(value) {
216
281
  if (!value || typeof value !== "object" || Array.isArray(value) || value instanceof Date) {
217
282
  return false;
218
283
  }
219
- if (dayjs2.isDayjs(value)) return false;
284
+ if (dayjs3.isDayjs(value)) return false;
220
285
  const prototype = Object.getPrototypeOf(value);
221
286
  return prototype === Object.prototype || prototype === null;
222
287
  }
@@ -238,17 +303,17 @@ function resolveFieldDisabled(field, values, mode) {
238
303
  return field.disabled || Boolean(field.disabledWhen && evaluateCondition(field.disabledWhen, values, mode));
239
304
  }
240
305
  function toDayjsValue(value) {
241
- if (dayjs2.isDayjs(value)) return value;
242
- if (value instanceof Date) return dayjs2(value);
306
+ if (dayjs3.isDayjs(value)) return value;
307
+ if (value instanceof Date) return dayjs3(value);
243
308
  if (typeof value === "number") {
244
309
  const normalized = Math.abs(value) >= 1e12 ? value : value * 1e3;
245
- const parsed = dayjs2(normalized);
310
+ const parsed = dayjs3(normalized);
246
311
  return parsed.isValid() ? parsed : null;
247
312
  }
248
313
  if (typeof value === "string") {
249
314
  const trimmed = value.trim();
250
315
  if (!trimmed) return null;
251
- const parsed = dayjs2(trimmed);
316
+ const parsed = dayjs3(trimmed);
252
317
  return parsed.isValid() ? parsed : null;
253
318
  }
254
319
  return null;
@@ -342,7 +407,7 @@ function FormControl({
342
407
  value: toDatePickerValue(value),
343
408
  onChange: (date, dateString) => onChange?.(serializeDatePickerValue(date, dateString)),
344
409
  disabled: field.disabled,
345
- disabledDate: (current) => field.maxDate === "today" && current.isAfter(dayjs2(), "day"),
410
+ disabledDate: (current) => field.maxDate === "today" && current.isAfter(dayjs3(), "day"),
346
411
  style: { width: "100%" }
347
412
  }
348
413
  );
@@ -354,7 +419,7 @@ function FormControl({
354
419
  value: toDateRangePickerValue(value),
355
420
  onChange: (dates, dateStrings) => onChange?.(serializeDateRangePickerValue(dates, dateStrings)),
356
421
  disabled: field.disabled,
357
- disabledDate: (current) => field.maxDate === "today" && current.isAfter(dayjs2(), "day"),
422
+ disabledDate: (current) => field.maxDate === "today" && current.isAfter(dayjs3(), "day"),
358
423
  style: { width: "100%" }
359
424
  }
360
425
  );
@@ -413,7 +478,7 @@ function RepeatableField({
413
478
  values
414
479
  }) {
415
480
  const listName = namePath ?? field.field;
416
- return /* @__PURE__ */ jsx5(Form.List, { name: listName, children: (items, { add, remove }) => /* @__PURE__ */ jsxs2(Fragment, { children: [
481
+ return /* @__PURE__ */ jsx5(Form.List, { name: listName, children: (items, { add, remove }) => /* @__PURE__ */ jsxs3(Fragment, { children: [
417
482
  items.map((item) => /* @__PURE__ */ jsx5(
418
483
  RepeatableFieldRow,
419
484
  {
@@ -463,7 +528,7 @@ function RepeatableFieldRow({
463
528
  }
464
529
  });
465
530
  }, [field.itemFields, form, itemPath, mode, scopeValues, values]);
466
- return /* @__PURE__ */ jsxs2(Space, { align: "start", children: [
531
+ return /* @__PURE__ */ jsxs3(Space, { align: "start", children: [
467
532
  field.itemFields?.map((itemField) => {
468
533
  if (!isFieldVisible(itemField, scopeValues, mode)) return null;
469
534
  const nextPath = appendNamePath(itemPath, itemField.field);
@@ -571,10 +636,10 @@ function RemoteSelect({
571
636
  onChange,
572
637
  registry
573
638
  }) {
574
- const [keywordOptions, setKeywordOptions] = useState2([]);
575
- const [selectedOptions, setSelectedOptions] = useState2([]);
576
- const [loading, setLoading] = useState2(false);
577
- const [keyword, setKeyword] = useState2("");
639
+ const [keywordOptions, setKeywordOptions] = useState3([]);
640
+ const [selectedOptions, setSelectedOptions] = useState3([]);
641
+ const [loading, setLoading] = useState3(false);
642
+ const [keyword, setKeyword] = useState3("");
578
643
  const keywordRequestId = useRef(0);
579
644
  const selectedRequestId = useRef(0);
580
645
  const service = field.remoteService ? registry.services[field.remoteService] : void 0;
@@ -729,9 +794,9 @@ function BestFormBody({
729
794
  onCancel
730
795
  }) {
731
796
  const [form] = Form.useForm();
732
- return /* @__PURE__ */ jsxs2(Form, { form, initialValues, layout: "vertical", onFinish: onSubmit, children: [
797
+ return /* @__PURE__ */ jsxs3(Form, { form, initialValues, layout: "vertical", onFinish: onSubmit, children: [
733
798
  /* @__PURE__ */ jsx5(FormFields, { fields, form, initialValues, mode }),
734
- /* @__PURE__ */ jsxs2(Space, { children: [
799
+ /* @__PURE__ */ jsxs3(Space, { children: [
735
800
  /* @__PURE__ */ jsx5(Button2, { htmlType: "submit", loading, type: "primary", children: submitText }),
736
801
  onCancel ? /* @__PURE__ */ jsx5(Button2, { onClick: onCancel, children: "\u53D6\u6D88" }) : null,
737
802
  extra
@@ -793,7 +858,7 @@ function evaluateCondition(condition, values, mode) {
793
858
  }
794
859
 
795
860
  // src/ui/BestLightTable.tsx
796
- import { Table } from "antd";
861
+ import { Table as Table2 } from "antd";
797
862
  import { jsx as jsx6 } from "react/jsx-runtime";
798
863
  function BestLightTable({
799
864
  columns,
@@ -808,7 +873,7 @@ function BestLightTable({
808
873
  render: column.render
809
874
  }));
810
875
  return /* @__PURE__ */ jsx6(
811
- Table,
876
+ Table2,
812
877
  {
813
878
  columns: tableColumns,
814
879
  dataSource: data,
@@ -822,18 +887,18 @@ function BestLightTable({
822
887
  // src/ui/BestOverlay.tsx
823
888
  import { Drawer, Modal } from "antd";
824
889
  import { jsx as jsx7 } from "react/jsx-runtime";
825
- function BestDrawer({ children, open, title, onClose }) {
826
- return /* @__PURE__ */ jsx7(Drawer, { destroyOnHidden: true, open, title, width: 640, onClose, children });
890
+ function BestDrawer({ children, open, title, onClose, width, footer }) {
891
+ return /* @__PURE__ */ jsx7(Drawer, { destroyOnHidden: true, open, title, width: width ?? 640, footer, onClose, children });
827
892
  }
828
- function BestModal({ children, open, title, onClose }) {
829
- return /* @__PURE__ */ jsx7(Modal, { destroyOnHidden: true, footer: null, open, title, onCancel: onClose, children });
893
+ function BestModal({ children, open, title, onClose, width, footer }) {
894
+ return /* @__PURE__ */ jsx7(Modal, { destroyOnHidden: true, footer: footer ?? null, open, title, width, onCancel: onClose, children });
830
895
  }
831
896
 
832
897
  // src/ui/BestSearch.tsx
833
898
  import { Button as Button3, Col, DatePicker as DatePicker2, Form as Form2, Input as Input3, InputNumber as InputNumber2, Row, Select as Select2 } from "antd";
834
- import dayjs3 from "dayjs";
899
+ import dayjs4 from "dayjs";
835
900
  import { useEffect as useEffect2, useMemo as useMemo4, useRef as useRef2 } from "react";
836
- import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
901
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
837
902
  function searchFormSignature(fields, initialValues) {
838
903
  return JSON.stringify([
839
904
  fields.map((field) => ({
@@ -898,7 +963,7 @@ function FieldControl({ field, value, onChange, ...controlProps }) {
898
963
  value: toDatePickerValue(value),
899
964
  onChange: (date, dateString) => onChange?.(serializeDatePickerValue(date, dateString)),
900
965
  disabled: field.disabled,
901
- disabledDate: (current) => field.maxDate === "today" && current.isAfter(dayjs3(), "day"),
966
+ disabledDate: (current) => field.maxDate === "today" && current.isAfter(dayjs4(), "day"),
902
967
  placeholder: field.placeholder,
903
968
  style: { width: "100%" }
904
969
  }
@@ -911,7 +976,7 @@ function FieldControl({ field, value, onChange, ...controlProps }) {
911
976
  value: toDateRangePickerValue(value),
912
977
  onChange: (dates, dateStrings) => onChange?.(serializeDateRangePickerValue(dates, dateStrings)),
913
978
  disabled: field.disabled,
914
- disabledDate: (current) => field.maxDate === "today" && current.isAfter(dayjs3(), "day"),
979
+ disabledDate: (current) => field.maxDate === "today" && current.isAfter(dayjs4(), "day"),
915
980
  style: { width: "100%" }
916
981
  }
917
982
  );
@@ -1013,9 +1078,9 @@ function BestSearchBody({
1013
1078
  onFinish: (values) => onSearch(values),
1014
1079
  onValuesChange: (_, values) => onChange?.(values),
1015
1080
  className: "best-lowcode-search",
1016
- children: /* @__PURE__ */ jsxs3(Row, { gutter: 16, align: "bottom", children: [
1081
+ children: /* @__PURE__ */ jsxs4(Row, { gutter: 16, align: "bottom", children: [
1017
1082
  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: toAntRules(field.rules), children: /* @__PURE__ */ jsx8(FieldControl, { field }) }) }, field.field)),
1018
- /* @__PURE__ */ jsx8(Col, { children: /* @__PURE__ */ jsxs3(Form2.Item, { children: [
1083
+ /* @__PURE__ */ jsx8(Col, { children: /* @__PURE__ */ jsxs4(Form2.Item, { children: [
1019
1084
  /* @__PURE__ */ jsx8(Button3, { htmlType: "submit", loading, type: "primary", children: submitText }),
1020
1085
  /* @__PURE__ */ jsx8(
1021
1086
  Button3,
@@ -1038,13 +1103,13 @@ function BestSearchBody({
1038
1103
  }
1039
1104
 
1040
1105
  // src/ui/BestStates.tsx
1041
- import { Empty, Result, Spin } from "antd";
1106
+ import { Empty as Empty2, Result, Spin } from "antd";
1042
1107
  import { jsx as jsx9 } from "react/jsx-runtime";
1043
1108
  function BestLoading({ description = "\u52A0\u8F7D\u4E2D" }) {
1044
1109
  return /* @__PURE__ */ jsx9("div", { style: { display: "grid", minHeight: 160, placeItems: "center" }, children: /* @__PURE__ */ jsx9(Spin, { tip: description }) });
1045
1110
  }
1046
1111
  function BestEmpty({ description = "\u6682\u65E0\u6570\u636E" }) {
1047
- return /* @__PURE__ */ jsx9(Empty, { description });
1112
+ return /* @__PURE__ */ jsx9(Empty2, { description });
1048
1113
  }
1049
1114
  function BestError({ description = "\u52A0\u8F7D\u5931\u8D25" }) {
1050
1115
  return /* @__PURE__ */ jsx9(Result, { status: "error", title: description });
@@ -1095,9 +1160,9 @@ function BestTable({
1095
1160
  }
1096
1161
 
1097
1162
  // src/lowcode/runtime.ts
1098
- import dayjs4 from "dayjs";
1163
+ import dayjs5 from "dayjs";
1099
1164
  import utc from "dayjs/plugin/utc.js";
1100
- dayjs4.extend(utc);
1165
+ dayjs5.extend(utc);
1101
1166
  function toBestListQuery(params, sort) {
1102
1167
  const { current, pageSize, ...filters } = params;
1103
1168
  const normalizedSort = Object.fromEntries(
@@ -1130,7 +1195,7 @@ function formatCrudValue(value, format) {
1130
1195
  const numericValue = typeof value === "number" ? value : Number(value.trim());
1131
1196
  timestamp = Number.isFinite(numericValue) && numericValue !== 0 ? Math.abs(numericValue) >= 1e12 ? numericValue : numericValue * 1e3 : value;
1132
1197
  }
1133
- const date = dayjs4.utc(timestamp);
1198
+ const date = dayjs5.utc(timestamp);
1134
1199
  if (!date.isValid()) return String(value);
1135
1200
  return date.format(format === "date" ? "YYYY-MM-DD" : "YYYY-MM-DD HH:mm:ss");
1136
1201
  }
@@ -1205,9 +1270,11 @@ var builtInEffects = /* @__PURE__ */ new Set([
1205
1270
  "openEdit",
1206
1271
  "remove",
1207
1272
  "runAction",
1208
- "slot"
1273
+ "slot",
1274
+ "closeDetail"
1209
1275
  ]);
1210
1276
  var columnFormats = /* @__PURE__ */ new Set(["date", "datetime", "money", "text"]);
1277
+ var detailFormats = /* @__PURE__ */ new Set(["text", "number", "money", "date", "datetime", "boolean", "json"]);
1211
1278
  function push(diagnostics, path, code, message2) {
1212
1279
  diagnostics.push({ path, code, message: message2 });
1213
1280
  }
@@ -1254,6 +1321,14 @@ function validateCondition(condition, path, diagnostics) {
1254
1321
  }
1255
1322
  push(diagnostics, path, "condition.operator", `\u4E0D\u652F\u6301\u7684\u6761\u4EF6\uFF1A${condition.operator}`);
1256
1323
  }
1324
+ function validateDetailField(field, path, registry, diagnostics) {
1325
+ if (field.visibleWhen) validateCondition(field.visibleWhen, `${path}/visibleWhen`, diagnostics);
1326
+ if (field.slot && registry && !registry.slots[field.slot])
1327
+ push(diagnostics, `${path}/slot`, "registry.slot", `\u672A\u6CE8\u518C\u63D2\u69FD\uFF1A${field.slot}`);
1328
+ const format = typeof field.format === "object" ? field.format.type : field.format;
1329
+ if (format && !detailFormats.has(format))
1330
+ push(diagnostics, `${path}/format`, "detail.format", `\u4E0D\u652F\u6301\u7684\u8BE6\u60C5\u683C\u5F0F\uFF1A${format}`);
1331
+ }
1257
1332
  function validateFields(fields, path, registry, diagnostics) {
1258
1333
  const seen = /* @__PURE__ */ new Set();
1259
1334
  fields?.forEach((field, index) => {
@@ -1407,17 +1482,26 @@ function validateCrudPageSchema(schema, registry) {
1407
1482
  }
1408
1483
  });
1409
1484
  if (schema.detail !== void 0) {
1410
- if (!schema.detail || typeof schema.detail !== "object" || !Array.isArray(schema.detail.fields)) {
1411
- push(diagnostics, "/detail", "detail.type", "detail \u5FC5\u987B\u5305\u542B fields \u5BF9\u8C61\u6570\u7EC4");
1485
+ if (!schema.detail || typeof schema.detail !== "object" || schema.detail.fields !== void 0 && !Array.isArray(schema.detail.fields) || schema.detail.sections !== void 0 && !Array.isArray(schema.detail.sections)) {
1486
+ push(diagnostics, "/detail", "detail.type", "detail \u5FC5\u987B\u5305\u542B fields \u6216 sections \u6570\u7EC4");
1412
1487
  } else {
1413
- schema.detail.fields.forEach((field, index) => {
1414
- if (field.slot && registry && !registry.slots[field.slot])
1415
- push(
1416
- diagnostics,
1417
- `/detail/fields/${index}/slot`,
1418
- "registry.slot",
1419
- `\u672A\u6CE8\u518C\u63D2\u69FD\uFF1A${field.slot}`
1420
- );
1488
+ schema.detail.fields?.forEach((field, index) => validateDetailField(field, `/detail/fields/${index}`, registry, diagnostics));
1489
+ schema.detail.sections?.forEach((section, index) => {
1490
+ if (section.visibleWhen) validateCondition(section.visibleWhen, `/detail/sections/${index}/visibleWhen`, diagnostics);
1491
+ if (!section.key) push(diagnostics, `/detail/sections/${index}/key`, "detail.section.key", "\u8BE6\u60C5\u533A\u5757\u5FC5\u987B\u6307\u5B9A key");
1492
+ if (section.span !== void 0 && (!Number.isInteger(section.span) || section.span < 1))
1493
+ push(diagnostics, `/detail/sections/${index}/span`, "detail.section.span", "\u8BE6\u60C5\u533A\u5757 span \u5FC5\u987B\u662F\u6B63\u6574\u6570");
1494
+ if (section.columns !== void 0 && (!Number.isInteger(section.columns) || section.columns < 1))
1495
+ push(diagnostics, `/detail/sections/${index}/columns`, "detail.section.columns", "\u8BE6\u60C5\u5B57\u6BB5 columns \u5FC5\u987B\u662F\u6B63\u6574\u6570");
1496
+ if (section.layout === "slot" && !section.slot)
1497
+ push(diagnostics, `/detail/sections/${index}/slot`, "detail.slot", "slot \u533A\u5757\u5FC5\u987B\u6307\u5B9A slot");
1498
+ if (section.layout === "table" && !section.table)
1499
+ push(diagnostics, `/detail/sections/${index}/table`, "detail.table", "table \u533A\u5757\u5FC5\u987B\u6307\u5B9A table");
1500
+ if (section.table && typeof section.table.data !== "string")
1501
+ push(diagnostics, `/detail/sections/${index}/table/data`, "detail.table.data", "\u8BE6\u60C5\u8868\u683C data \u5FC5\u987B\u662F\u5B57\u6BB5\u8DEF\u5F84");
1502
+ if (section.slot && registry && !registry.slots[section.slot])
1503
+ push(diagnostics, `/detail/sections/${index}/slot`, "registry.slot", `\u672A\u6CE8\u518C\u63D2\u69FD\uFF1A${section.slot}`);
1504
+ section.fields?.forEach((field, fieldIndex) => validateDetailField(field, `/detail/sections/${index}/fields/${fieldIndex}`, registry, diagnostics));
1421
1505
  });
1422
1506
  }
1423
1507
  }
@@ -1435,6 +1519,13 @@ function validateCrudPageSchema(schema, registry) {
1435
1519
  Boolean(schema.dataSource?.remove),
1436
1520
  diagnostics
1437
1521
  );
1522
+ validateActions(
1523
+ schema.detail?.footer,
1524
+ "/detail/footer",
1525
+ registry,
1526
+ Boolean(schema.dataSource?.remove),
1527
+ diagnostics
1528
+ );
1438
1529
  return { valid: diagnostics.length === 0, diagnostics };
1439
1530
  }
1440
1531
  function assertValidCrudPageSchema(schema, registry) {
@@ -1486,7 +1577,7 @@ ${message2}`);
1486
1577
  }
1487
1578
 
1488
1579
  // src/lowcode/BestCrudPage.tsx
1489
- import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
1580
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
1490
1581
  function pickRowKeyValues(record, rowKey) {
1491
1582
  if (!record) return {};
1492
1583
  const keys = Array.isArray(rowKey) ? rowKey : [rowKey];
@@ -1559,7 +1650,7 @@ function useFields(fields = []) {
1559
1650
  const registry = useBestRegistry();
1560
1651
  return fields.map((field) => toFieldDefinition(field, registry.dictionaries, registry.slots));
1561
1652
  }
1562
- function toDetailField(field, dictionary, slots) {
1653
+ function toDetailField(field, dictionary, slots, record = {}) {
1563
1654
  const slot = field.slot;
1564
1655
  const valueEnum = field.dict ? Object.fromEntries(
1565
1656
  (dictionary[field.dict] ?? []).map((item) => [String(item.value), item.label])
@@ -1568,10 +1659,50 @@ function toDetailField(field, dictionary, slots) {
1568
1659
  field: field.field,
1569
1660
  label: field.label,
1570
1661
  span: field.span,
1662
+ format: field.format,
1663
+ emptyText: field.emptyText,
1664
+ visible: !field.visibleWhen || evaluateCondition(field.visibleWhen, record, "detail"),
1571
1665
  valueEnum,
1572
- render: slot ? (value, record) => slots[slot]?.({ field: field.field, record, value }) ?? "-" : void 0
1666
+ render: slot ? (value, record2) => slots[slot]?.({ field: field.field, record: record2, value }) ?? "-" : void 0
1573
1667
  };
1574
1668
  }
1669
+ function getPathValue3(values, path) {
1670
+ if (Object.hasOwn(values, path)) return values[path];
1671
+ return path.split(".").reduce((current, part) => {
1672
+ if (current && typeof current === "object") return current[part];
1673
+ return void 0;
1674
+ }, values);
1675
+ }
1676
+ function toDetailSection(section, record, dictionaries, slots) {
1677
+ if (section.visibleWhen && !evaluateCondition(section.visibleWhen, record, "detail")) return null;
1678
+ const fields = section.fields?.map((field) => toDetailField(field, dictionaries, slots, record));
1679
+ const table = section.table ? {
1680
+ key: section.key,
1681
+ data: getPathValue3(record, section.table.data) ?? [],
1682
+ rowKey: section.table.rowKey,
1683
+ scrollX: section.table.scrollX,
1684
+ columns: section.table.columns.map((column) => ({
1685
+ key: column.field,
1686
+ title: column.title,
1687
+ dataIndex: column.field,
1688
+ width: column.width,
1689
+ fixed: column.fixed,
1690
+ render: (value, row) => {
1691
+ if (column.slot) return slots[column.slot]?.({ field: column.field, record: row, value }) ?? "-";
1692
+ if (column.dict) {
1693
+ const item = (dictionaries[column.dict] ?? []).find((candidate) => candidate.value === value);
1694
+ if (item) return item.label;
1695
+ }
1696
+ const format = column.format;
1697
+ if (format === "date" || format === "datetime") return formatCrudValue(value, format);
1698
+ if (format === "money") return formatCrudValue(value, format);
1699
+ return value == null || value === "" ? "-" : String(value);
1700
+ }
1701
+ }))
1702
+ } : void 0;
1703
+ const content = section.layout === "slot" && section.slot ? slots[section.slot]?.({ record }) : void 0;
1704
+ return { key: section.key, title: section.title, description: section.description, columns: section.columns, span: section.span, variant: section.variant, fields, table, content };
1705
+ }
1575
1706
  function toTableColumn(column, dictionary, slots) {
1576
1707
  const slot = column.slot;
1577
1708
  const valueEnum = column.dict ? Object.fromEntries(
@@ -1617,7 +1748,7 @@ function toProTableSearchColumn(field, dictionary) {
1617
1748
  initialValue: field.defaultValue,
1618
1749
  fieldProps: {
1619
1750
  ...options ? { options } : {},
1620
- ...field.maxDate === "today" ? { disabledDate: (current) => current.isAfter(dayjs5(), "day") } : {}
1751
+ ...field.maxDate === "today" ? { disabledDate: (current) => current.isAfter(dayjs6(), "day") } : {}
1621
1752
  }
1622
1753
  };
1623
1754
  }
@@ -1637,7 +1768,7 @@ function BestCrudPage({ adapter, className, schema }) {
1637
1768
  assertValidCrudPageSchema(schema, registry);
1638
1769
  const registeredListService = useBestListService(schema.dataSource.list);
1639
1770
  if (!registeredListService) throw new Error(`\u672A\u6CE8\u518C\u5217\u8868\u670D\u52A1\uFF1A${schema.dataSource.list}`);
1640
- const listService = useCallback(
1771
+ const listService = useCallback2(
1641
1772
  async (query2, options) => {
1642
1773
  const response = await registeredListService(query2, options);
1643
1774
  return adapter?.fromList ? { ...response, items: adapter.fromList(response.items) } : response;
@@ -1663,22 +1794,52 @@ function BestCrudPage({ adapter, className, schema }) {
1663
1794
  ),
1664
1795
  [searchFields]
1665
1796
  );
1666
- const [query, setQuery] = useState3(searchDefaultValues);
1797
+ const [query, setQuery] = useState4(searchDefaultValues);
1667
1798
  const queryRef = useRef3(searchDefaultValues);
1668
1799
  const queryStateRef = useRef3({
1669
1800
  schemaId: schema.id,
1670
1801
  searchSchemaSignature,
1671
1802
  userTouched: false
1672
1803
  });
1673
- const [drawer, setDrawer] = useState3({ mode: "closed" });
1674
- const [submitting, setSubmitting] = useState3(false);
1804
+ const [drawer, setDrawer] = useState4({ mode: "closed" });
1805
+ const [detailState, setDetailState] = useState4({ loading: false });
1806
+ const detailRequestRef = useRef3(void 0);
1807
+ const [submitting, setSubmitting] = useState4(false);
1675
1808
  const submittingRef = useRef3(false);
1676
- const handleAction = useCallback(
1809
+ const handleAction = useCallback2(
1677
1810
  async (action, record) => {
1678
1811
  if (action.access && !registry.access(action.access)) return;
1812
+ if (action.effect === "closeDetail") {
1813
+ detailRequestRef.current?.abort();
1814
+ setDrawer({ mode: "closed" });
1815
+ setDetailState({ loading: false });
1816
+ return;
1817
+ }
1679
1818
  if (action.effect !== "remove" && !await confirmBeforeAction(action.confirm, confirmAction))
1680
1819
  return;
1681
- if (action.effect === "openDetail") setDrawer({ mode: "detail", record });
1820
+ if (action.effect === "openDetail") {
1821
+ detailRequestRef.current?.abort();
1822
+ const controller = new AbortController();
1823
+ detailRequestRef.current = controller;
1824
+ setDrawer({ mode: "detail", record });
1825
+ setDetailState({ loading: Boolean(schema.dataSource.detail), record });
1826
+ if (schema.dataSource.detail) {
1827
+ const detailService = registry.services[schema.dataSource.detail];
1828
+ if (!detailService) {
1829
+ setDetailState({ loading: false, error: new Error(`\u672A\u6CE8\u518C\u670D\u52A1\uFF1A${schema.dataSource.detail}`), record });
1830
+ return;
1831
+ }
1832
+ try {
1833
+ const detail = await detailService(record ?? {}, { signal: controller.signal });
1834
+ if (controller.signal.aborted) return;
1835
+ const normalized = isRecord2(detail) ? adapter?.fromDetail ? adapter.fromDetail(detail) : detail : record ?? {};
1836
+ setDetailState({ loading: false, record: normalized });
1837
+ setDrawer({ mode: "detail", record: normalized });
1838
+ } catch (error) {
1839
+ if (!controller.signal.aborted) setDetailState({ loading: false, error, record });
1840
+ }
1841
+ }
1842
+ }
1682
1843
  if (action.effect === "openEdit") {
1683
1844
  let editRecord = record;
1684
1845
  if (schema.dataSource.detail) {
@@ -1781,7 +1942,7 @@ function BestCrudPage({ adapter, className, schema }) {
1781
1942
  [listService, schema.title]
1782
1943
  );
1783
1944
  const rowKey = schema.table.rowKey;
1784
- const request = useCallback(
1945
+ const request = useCallback2(
1785
1946
  (params, sort) => pageRequest.request(
1786
1947
  toBestListQuery({ ...params, ...useBestSearch ? queryRef.current : {} }, sort)
1787
1948
  ),
@@ -1804,7 +1965,7 @@ function BestCrudPage({ adapter, className, schema }) {
1804
1965
  };
1805
1966
  }, [schema.id, searchDefaultValues, searchFieldNames, searchSchemaSignature]);
1806
1967
  useEffect3(() => () => pageRequest.abort(), [pageRequest]);
1807
- const submitForm = useCallback(
1968
+ const submitForm = useCallback2(
1808
1969
  async (values) => {
1809
1970
  if (submittingRef.current) return;
1810
1971
  const serviceKey = drawer.mode === "edit" ? schema.dataSource.update : schema.dataSource.create;
@@ -1844,7 +2005,7 @@ function BestCrudPage({ adapter, className, schema }) {
1844
2005
  schema.dataSource.update
1845
2006
  ]
1846
2007
  );
1847
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
2008
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
1848
2009
  useBestSearch && searchFields.length ? /* @__PURE__ */ jsx11(
1849
2010
  BestSearch,
1850
2011
  {
@@ -1879,22 +2040,41 @@ function BestCrudPage({ adapter, className, schema }) {
1879
2040
  toolBarRender: () => schema.toolbar?.map((action) => /* @__PURE__ */ jsx11(ActionButton, { action, onExecute: handleAction }, action.id)) ?? []
1880
2041
  }
1881
2042
  ),
1882
- /* @__PURE__ */ jsxs4(
2043
+ drawer.mode === "detail" && schema.detail?.mode === "inline" ? /* @__PURE__ */ jsx11(DetailContent, { state: detailState, record: drawer.record }) : null,
2044
+ drawer.mode !== "closed" && (schema.detail?.mode !== "inline" || drawer.mode !== "detail") && schema.detail?.mode === "drawer" ? /* @__PURE__ */ jsxs5(
2045
+ BestDrawer,
2046
+ {
2047
+ open: true,
2048
+ width: schema.detail.width,
2049
+ title: drawer.mode === "detail" ? `${schema.title}\u8BE6\u60C5` : drawer.mode === "edit" ? `\u7F16\u8F91${schema.title}` : `\u65B0\u5EFA${schema.title}`,
2050
+ footer: drawer.mode === "detail" && schema.detail.footer?.length ? /* @__PURE__ */ jsx11("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8 }, children: schema.detail.footer.map((action) => /* @__PURE__ */ jsx11(ActionButton, { action, record: drawer.record, onExecute: handleAction }, action.id)) }) : void 0,
2051
+ onClose: () => {
2052
+ detailRequestRef.current?.abort();
2053
+ setDrawer({ mode: "closed" });
2054
+ setDetailState({ loading: false });
2055
+ },
2056
+ children: [
2057
+ drawer.mode === "detail" ? /* @__PURE__ */ jsx11(DetailContent, { state: detailState, record: drawer.record }) : null,
2058
+ drawer.mode === "edit" || drawer.mode === "create" ? /* @__PURE__ */ jsx11(BestForm, { fields: formFields, initialValues: drawer.record, loading: submitting, mode: drawer.mode, onCancel: () => setDrawer({ mode: "closed" }), onSubmit: (values) => {
2059
+ void submitForm(values);
2060
+ } }) : null
2061
+ ]
2062
+ }
2063
+ ) : null,
2064
+ drawer.mode !== "closed" && (schema.detail?.mode !== "inline" || drawer.mode !== "detail") && schema.detail?.mode !== "drawer" ? /* @__PURE__ */ jsxs5(
1883
2065
  BestModal,
1884
2066
  {
1885
- open: drawer.mode !== "closed",
2067
+ open: true,
1886
2068
  title: drawer.mode === "detail" ? `${schema.title}\u8BE6\u60C5` : drawer.mode === "edit" ? `\u7F16\u8F91${schema.title}` : `\u65B0\u5EFA${schema.title}`,
1887
- onClose: () => setDrawer({ mode: "closed" }),
2069
+ width: schema.detail?.width,
2070
+ footer: drawer.mode === "detail" && schema.detail?.footer?.length ? /* @__PURE__ */ jsx11("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8 }, children: schema.detail.footer.map((action) => /* @__PURE__ */ jsx11(ActionButton, { action, record: drawer.record, onExecute: handleAction }, action.id)) }) : void 0,
2071
+ onClose: () => {
2072
+ detailRequestRef.current?.abort();
2073
+ setDrawer({ mode: "closed" });
2074
+ setDetailState({ loading: false });
2075
+ },
1888
2076
  children: [
1889
- drawer.mode === "detail" ? /* @__PURE__ */ jsx11(
1890
- BestDetail,
1891
- {
1892
- fields: schema.detail?.fields.map(
1893
- (field) => toDetailField(field, registry.dictionaries, registry.slots)
1894
- ) ?? [],
1895
- record: drawer.record
1896
- }
1897
- ) : null,
2077
+ drawer.mode === "detail" ? /* @__PURE__ */ jsx11(DetailContent, { state: detailState, record: drawer.record }) : null,
1898
2078
  drawer.mode === "edit" || drawer.mode === "create" ? /* @__PURE__ */ jsx11(
1899
2079
  BestForm,
1900
2080
  {
@@ -1910,8 +2090,24 @@ function BestCrudPage({ adapter, className, schema }) {
1910
2090
  ) : null
1911
2091
  ]
1912
2092
  }
1913
- )
2093
+ ) : null
1914
2094
  ] });
2095
+ function DetailContent({ state, record }) {
2096
+ if (state.loading) return /* @__PURE__ */ jsx11(BestLoading, {});
2097
+ if (state.error) return /* @__PURE__ */ jsx11(BestError, { description: errorMessage(state.error, `${schema.title}\u8BE6\u60C5\u52A0\u8F7D\u5931\u8D25`) });
2098
+ if (!record) return /* @__PURE__ */ jsx11(BestEmpty, {});
2099
+ const sections = schema.detail?.sections?.map((section) => toDetailSection(section, record, registry.dictionaries, registry.slots)).filter(Boolean);
2100
+ return /* @__PURE__ */ jsx11(
2101
+ BestDetail,
2102
+ {
2103
+ fields: schema.detail?.fields?.map((field) => toDetailField(field, registry.dictionaries, registry.slots, record)) ?? [],
2104
+ sections,
2105
+ sectionColumns: schema.detail?.columns,
2106
+ sectionGap: schema.detail?.gap,
2107
+ record
2108
+ }
2109
+ );
2110
+ }
1915
2111
  function ActionButton({
1916
2112
  action,
1917
2113
  record,
@@ -1979,6 +2175,7 @@ export {
1979
2175
  useBestAccess,
1980
2176
  useBestAction,
1981
2177
  useBestDictionary,
2178
+ useBestDictionaryActions,
1982
2179
  useBestListService,
1983
2180
  useBestRegistry,
1984
2181
  useBestService,