best-lowcode-runtime 0.1.2 → 0.2.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.
- package/dist/dev.js +23 -0
- package/dist/dev.js.map +2 -2
- package/dist/index.js +650 -131
- package/dist/index.js.map +4 -4
- package/dist/lowcode/BestCrudPage.d.ts.map +1 -1
- package/dist/lowcode/adapter.d.ts.map +1 -1
- package/dist/lowcode/index.d.ts +1 -1
- package/dist/lowcode/index.d.ts.map +1 -1
- package/dist/lowcode/validate.d.ts.map +1 -1
- package/dist/ui/BestForm.d.ts +9 -0
- package/dist/ui/BestForm.d.ts.map +1 -1
- package/dist/ui/BestSearch.d.ts +2 -1
- package/dist/ui/BestSearch.d.ts.map +1 -1
- package/dist/ui/types.d.ts +2 -1
- package/dist/ui/types.d.ts.map +1 -1
- package/package.json +8 -7
package/dist/index.js
CHANGED
|
@@ -1,7 +1,41 @@
|
|
|
1
|
+
// src/lowcode/adapter.ts
|
|
2
|
+
import dayjs from "dayjs";
|
|
3
|
+
function normalizeBestValues(values, normalize = defaultNormalize) {
|
|
4
|
+
return Object.fromEntries(
|
|
5
|
+
Object.entries(values).map(([field, value]) => [
|
|
6
|
+
field,
|
|
7
|
+
normalizeNested(value, normalize, field)
|
|
8
|
+
])
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
function normalizeNested(value, normalize, field) {
|
|
12
|
+
if (Array.isArray(value)) return value.map((item) => normalizeNested(item, normalize, field));
|
|
13
|
+
if (dayjs.isDayjs(value) || value instanceof Date) return value;
|
|
14
|
+
if (value && typeof value === "object") {
|
|
15
|
+
return Object.fromEntries(
|
|
16
|
+
Object.entries(value).map(([key, item]) => [key, normalizeNested(item, normalize, key)])
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return normalize(value, field);
|
|
20
|
+
}
|
|
21
|
+
function defaultNormalize(value) {
|
|
22
|
+
if (typeof value === "string") return value.trim() === "" ? void 0 : value.trim();
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
function createBestCrudAdapter(options = {}) {
|
|
26
|
+
const normalize = options.normalize ?? defaultNormalize;
|
|
27
|
+
return {
|
|
28
|
+
fromList: options.fromList,
|
|
29
|
+
fromDetail: options.fromDetail,
|
|
30
|
+
toCreatePayload: (values) => options.toCreatePayload?.(normalizeBestValues(values, normalize)) ?? normalizeBestValues(values, normalize),
|
|
31
|
+
toUpdatePayload: (values, record) => options.toUpdatePayload?.(normalizeBestValues(values, normalize), record) ?? normalizeBestValues(values, normalize)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
1
35
|
// src/lowcode/BestCrudPage.tsx
|
|
2
36
|
import { Button as Button4, Modal as Modal2, message } from "antd";
|
|
3
|
-
import
|
|
4
|
-
import { useCallback, useEffect as useEffect3, useMemo as
|
|
37
|
+
import dayjs5 from "dayjs";
|
|
38
|
+
import { useCallback, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo5, useRef as useRef3, useState as useState3 } from "react";
|
|
5
39
|
|
|
6
40
|
// src/runtime/BestProvider.tsx
|
|
7
41
|
import { ConfigProvider } from "antd";
|
|
@@ -141,22 +175,105 @@ function BestFilePreview({ files, onDownload }) {
|
|
|
141
175
|
|
|
142
176
|
// src/ui/BestForm.tsx
|
|
143
177
|
import { Button as Button2, DatePicker, Form, Input as Input2, InputNumber, Select, Space } from "antd";
|
|
144
|
-
import
|
|
178
|
+
import dayjs2 from "dayjs";
|
|
179
|
+
import { useEffect, useMemo as useMemo3, useRef, useState as useState2 } from "react";
|
|
145
180
|
import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
181
|
+
function toNamePath(path) {
|
|
182
|
+
return path === void 0 ? [] : Array.isArray(path) ? path : [path];
|
|
183
|
+
}
|
|
184
|
+
function isNamePath(value) {
|
|
185
|
+
return Array.isArray(value);
|
|
186
|
+
}
|
|
187
|
+
function isRecord(value) {
|
|
188
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || value instanceof Date) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
if (dayjs2.isDayjs(value)) return false;
|
|
192
|
+
const prototype = Object.getPrototypeOf(value);
|
|
193
|
+
return prototype === Object.prototype || prototype === null;
|
|
194
|
+
}
|
|
195
|
+
function appendNamePath(path, ...segments) {
|
|
196
|
+
return [...toNamePath(path), ...segments];
|
|
197
|
+
}
|
|
198
|
+
function getConditionFieldValue(values, field) {
|
|
199
|
+
if (Object.hasOwn(values, field)) return values[field];
|
|
200
|
+
return getPathValue(values, field.split("."));
|
|
201
|
+
}
|
|
202
|
+
function getScopedValues(values, itemPath) {
|
|
203
|
+
const itemValues = getPathValue(values, itemPath);
|
|
204
|
+
return isRecord(itemValues) ? { ...values, ...itemValues } : values;
|
|
205
|
+
}
|
|
206
|
+
function isFieldVisible(field, values, mode) {
|
|
207
|
+
return !field.hidden && (!field.visibleWhen || evaluateCondition(field.visibleWhen, values, mode));
|
|
208
|
+
}
|
|
209
|
+
function resolveFieldDisabled(field, values, mode) {
|
|
210
|
+
return field.disabled || Boolean(field.disabledWhen && evaluateCondition(field.disabledWhen, values, mode));
|
|
211
|
+
}
|
|
212
|
+
function toDayjsValue(value) {
|
|
213
|
+
if (dayjs2.isDayjs(value)) return value;
|
|
214
|
+
if (value instanceof Date) return dayjs2(value);
|
|
215
|
+
if (typeof value === "number") {
|
|
216
|
+
const normalized = Math.abs(value) >= 1e12 ? value : value * 1e3;
|
|
217
|
+
const parsed = dayjs2(normalized);
|
|
218
|
+
return parsed.isValid() ? parsed : null;
|
|
219
|
+
}
|
|
220
|
+
if (typeof value === "string") {
|
|
221
|
+
const trimmed = value.trim();
|
|
222
|
+
if (!trimmed) return null;
|
|
223
|
+
const parsed = dayjs2(trimmed);
|
|
224
|
+
return parsed.isValid() ? parsed : null;
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
function toDatePickerValue(value) {
|
|
229
|
+
return toDayjsValue(value);
|
|
230
|
+
}
|
|
231
|
+
function toDateRangePickerValue(value) {
|
|
232
|
+
if (!Array.isArray(value)) return null;
|
|
233
|
+
const nextValue = [toDayjsValue(value[0]), toDayjsValue(value[1])];
|
|
234
|
+
return nextValue[0] || nextValue[1] ? nextValue : null;
|
|
235
|
+
}
|
|
236
|
+
function serializeDatePickerValue(_, dateString) {
|
|
237
|
+
return dateString || void 0;
|
|
238
|
+
}
|
|
239
|
+
function serializeDateRangePickerValue(_, dateStrings) {
|
|
240
|
+
return dateStrings[0] || dateStrings[1] ? dateStrings : void 0;
|
|
241
|
+
}
|
|
242
|
+
function fieldSignature(field) {
|
|
243
|
+
return {
|
|
244
|
+
field: field.field,
|
|
245
|
+
component: field.component,
|
|
246
|
+
defaultValue: field.defaultValue,
|
|
247
|
+
remoteService: field.remoteService,
|
|
248
|
+
searchField: field.searchField,
|
|
249
|
+
labelField: field.labelField,
|
|
250
|
+
valueField: field.valueField,
|
|
251
|
+
debounceMs: field.debounceMs,
|
|
252
|
+
pageSize: field.pageSize,
|
|
253
|
+
itemFields: field.itemFields?.map((itemField) => fieldSignature(itemField))
|
|
254
|
+
};
|
|
255
|
+
}
|
|
146
256
|
function FormControl({
|
|
147
257
|
field,
|
|
148
258
|
values,
|
|
259
|
+
scopeValues,
|
|
149
260
|
mode = "create",
|
|
150
261
|
form,
|
|
262
|
+
namePath,
|
|
263
|
+
value,
|
|
264
|
+
onChange,
|
|
151
265
|
...controlProps
|
|
152
266
|
}) {
|
|
153
267
|
const registry = useBestRegistry();
|
|
268
|
+
const renderValues = scopeValues ?? values;
|
|
154
269
|
switch (field.component) {
|
|
155
270
|
case "select":
|
|
156
271
|
return /* @__PURE__ */ jsx5(
|
|
157
272
|
Select,
|
|
158
273
|
{
|
|
159
274
|
...controlProps,
|
|
275
|
+
value,
|
|
276
|
+
onChange,
|
|
160
277
|
disabled: field.disabled,
|
|
161
278
|
options: field.options,
|
|
162
279
|
placeholder: field.placeholder
|
|
@@ -167,10 +284,13 @@ function FormControl({
|
|
|
167
284
|
RemoteSelect,
|
|
168
285
|
{
|
|
169
286
|
field,
|
|
170
|
-
value: values
|
|
287
|
+
value: value !== void 0 ? value : getPathValue(values, namePath ?? field.field),
|
|
171
288
|
disabled: field.disabled,
|
|
172
289
|
placeholder: field.placeholder,
|
|
173
|
-
onChange: (
|
|
290
|
+
onChange: (nextValue) => {
|
|
291
|
+
if (onChange) onChange(nextValue);
|
|
292
|
+
else form.setFieldValue(namePath ?? field.field, nextValue);
|
|
293
|
+
},
|
|
174
294
|
registry
|
|
175
295
|
}
|
|
176
296
|
);
|
|
@@ -179,18 +299,31 @@ function FormControl({
|
|
|
179
299
|
InputNumber,
|
|
180
300
|
{
|
|
181
301
|
...controlProps,
|
|
302
|
+
value,
|
|
303
|
+
onChange,
|
|
182
304
|
disabled: field.disabled,
|
|
183
305
|
placeholder: field.placeholder,
|
|
184
306
|
style: { width: "100%" }
|
|
185
307
|
}
|
|
186
308
|
);
|
|
187
309
|
case "date":
|
|
188
|
-
return /* @__PURE__ */ jsx5(
|
|
310
|
+
return /* @__PURE__ */ jsx5(
|
|
311
|
+
DatePicker,
|
|
312
|
+
{
|
|
313
|
+
...controlProps,
|
|
314
|
+
value: toDatePickerValue(value),
|
|
315
|
+
onChange: (date, dateString) => onChange?.(serializeDatePickerValue(date, dateString)),
|
|
316
|
+
disabled: field.disabled,
|
|
317
|
+
style: { width: "100%" }
|
|
318
|
+
}
|
|
319
|
+
);
|
|
189
320
|
case "dateRange":
|
|
190
321
|
return /* @__PURE__ */ jsx5(
|
|
191
322
|
DatePicker.RangePicker,
|
|
192
323
|
{
|
|
193
324
|
...controlProps,
|
|
325
|
+
value: toDateRangePickerValue(value),
|
|
326
|
+
onChange: (dates, dateStrings) => onChange?.(serializeDateRangePickerValue(dates, dateStrings)),
|
|
194
327
|
disabled: field.disabled,
|
|
195
328
|
style: { width: "100%" }
|
|
196
329
|
}
|
|
@@ -200,6 +333,8 @@ function FormControl({
|
|
|
200
333
|
Input2.TextArea,
|
|
201
334
|
{
|
|
202
335
|
...controlProps,
|
|
336
|
+
value,
|
|
337
|
+
onChange,
|
|
203
338
|
disabled: field.disabled,
|
|
204
339
|
placeholder: field.placeholder
|
|
205
340
|
}
|
|
@@ -207,48 +342,135 @@ function FormControl({
|
|
|
207
342
|
case "slot":
|
|
208
343
|
return /* @__PURE__ */ jsx5(Fragment, { children: field.render?.({
|
|
209
344
|
field: field.field,
|
|
210
|
-
values,
|
|
211
|
-
value: values
|
|
345
|
+
values: renderValues,
|
|
346
|
+
value: value !== void 0 ? value : getPathValue(values, namePath ?? field.field),
|
|
212
347
|
mode,
|
|
213
348
|
disabled: field.disabled,
|
|
214
|
-
setValue: (name,
|
|
349
|
+
setValue: (name, nextValue) => form.setFieldValue(
|
|
350
|
+
namePath ? isNamePath(name) ? name : [...toNamePath(namePath).slice(0, -1), name] : name,
|
|
351
|
+
nextValue
|
|
352
|
+
)
|
|
215
353
|
}) });
|
|
216
354
|
case "repeatable":
|
|
217
|
-
return /* @__PURE__ */ jsx5(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
355
|
+
return /* @__PURE__ */ jsx5(
|
|
356
|
+
RepeatableField,
|
|
357
|
+
{
|
|
358
|
+
field,
|
|
359
|
+
form,
|
|
360
|
+
mode,
|
|
361
|
+
namePath,
|
|
362
|
+
values
|
|
363
|
+
}
|
|
364
|
+
);
|
|
365
|
+
default:
|
|
366
|
+
return /* @__PURE__ */ jsx5(
|
|
367
|
+
Input2,
|
|
368
|
+
{
|
|
369
|
+
...controlProps,
|
|
370
|
+
value,
|
|
371
|
+
onChange,
|
|
372
|
+
disabled: field.disabled,
|
|
373
|
+
placeholder: field.placeholder
|
|
374
|
+
}
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
function RepeatableField({
|
|
379
|
+
field,
|
|
380
|
+
form,
|
|
381
|
+
mode,
|
|
382
|
+
namePath,
|
|
383
|
+
values
|
|
384
|
+
}) {
|
|
385
|
+
const listName = namePath ?? field.field;
|
|
386
|
+
return /* @__PURE__ */ jsx5(Form.List, { name: listName, children: (items, { add, remove }) => /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
387
|
+
items.map((item) => /* @__PURE__ */ jsx5(
|
|
388
|
+
RepeatableFieldRow,
|
|
389
|
+
{
|
|
390
|
+
field,
|
|
391
|
+
form,
|
|
392
|
+
itemCount: items.length,
|
|
393
|
+
itemName: item.name,
|
|
394
|
+
itemKey: item.key,
|
|
395
|
+
listName,
|
|
396
|
+
mode,
|
|
397
|
+
remove,
|
|
398
|
+
values
|
|
399
|
+
},
|
|
400
|
+
item.key
|
|
401
|
+
)),
|
|
402
|
+
/* @__PURE__ */ jsx5(
|
|
403
|
+
Button2,
|
|
404
|
+
{
|
|
405
|
+
disabled: field.maxItems !== void 0 && items.length >= field.maxItems,
|
|
406
|
+
onClick: () => add(resolveInitialValues(field.itemFields ?? [], void 0)),
|
|
407
|
+
type: "dashed",
|
|
408
|
+
children: "\u65B0\u589E\u4E00\u9879"
|
|
409
|
+
}
|
|
410
|
+
)
|
|
411
|
+
] }) });
|
|
412
|
+
}
|
|
413
|
+
function RepeatableFieldRow({
|
|
414
|
+
field,
|
|
415
|
+
form,
|
|
416
|
+
itemCount,
|
|
417
|
+
itemName,
|
|
418
|
+
itemKey,
|
|
419
|
+
listName,
|
|
420
|
+
mode,
|
|
421
|
+
remove,
|
|
422
|
+
values
|
|
423
|
+
}) {
|
|
424
|
+
const itemPath = appendNamePath(listName, itemName);
|
|
425
|
+
const itemValues = getPathValue(values, itemPath);
|
|
426
|
+
const scopeValues = getScopedValues(values, itemPath);
|
|
427
|
+
useEffect(() => {
|
|
428
|
+
field.itemFields?.forEach((itemField) => {
|
|
429
|
+
if (!itemField.clearWhenHidden) return;
|
|
430
|
+
const nextPath = appendNamePath(itemPath, itemField.field);
|
|
431
|
+
if (!isFieldVisible(itemField, scopeValues, mode) && getPathValue(values, nextPath) !== void 0) {
|
|
432
|
+
form.setFieldValue(nextPath, void 0);
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}, [field.itemFields, form, itemPath, mode, scopeValues, values]);
|
|
436
|
+
return /* @__PURE__ */ jsxs2(Space, { align: "start", children: [
|
|
437
|
+
field.itemFields?.map((itemField) => {
|
|
438
|
+
if (!isFieldVisible(itemField, scopeValues, mode)) return null;
|
|
439
|
+
const nextPath = appendNamePath(itemPath, itemField.field);
|
|
440
|
+
return /* @__PURE__ */ jsx5(
|
|
441
|
+
Form.Item,
|
|
442
|
+
{
|
|
443
|
+
label: itemField.label,
|
|
444
|
+
name: [itemName, itemField.field],
|
|
445
|
+
rules: toAntRules(itemField.rules),
|
|
446
|
+
children: /* @__PURE__ */ jsx5(
|
|
447
|
+
FormControl,
|
|
231
448
|
{
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
449
|
+
field: {
|
|
450
|
+
...itemField,
|
|
451
|
+
disabled: resolveFieldDisabled(itemField, scopeValues, mode)
|
|
452
|
+
},
|
|
453
|
+
form,
|
|
454
|
+
mode,
|
|
455
|
+
namePath: nextPath,
|
|
456
|
+
scopeValues,
|
|
457
|
+
values: isRecord(itemValues) ? { ...values, ...itemValues } : values
|
|
236
458
|
}
|
|
237
459
|
)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
460
|
+
},
|
|
461
|
+
itemField.field
|
|
462
|
+
);
|
|
463
|
+
}),
|
|
464
|
+
/* @__PURE__ */ jsx5(
|
|
465
|
+
Button2,
|
|
466
|
+
{
|
|
467
|
+
disabled: itemCount <= (field.minItems ?? 0),
|
|
468
|
+
onClick: () => remove(itemName),
|
|
469
|
+
type: "link",
|
|
470
|
+
children: "\u5220\u9664"
|
|
471
|
+
}
|
|
472
|
+
)
|
|
473
|
+
] }, itemKey);
|
|
252
474
|
}
|
|
253
475
|
function FormFields({ fields, form, initialValues, mode = "create" }) {
|
|
254
476
|
const values = Form.useWatch([], { form, preserve: true }) ?? initialValues ?? {};
|
|
@@ -290,7 +512,7 @@ function toAntRules(rules) {
|
|
|
290
512
|
type: rule.type === "string" ? void 0 : rule.type,
|
|
291
513
|
min: rule.min ?? rule.minLength,
|
|
292
514
|
max: rule.max ?? rule.maxLength,
|
|
293
|
-
pattern:
|
|
515
|
+
pattern: safeRegExp(rule.pattern),
|
|
294
516
|
message: rule.message,
|
|
295
517
|
validator: rule.type === "integer" ? async (_, value) => {
|
|
296
518
|
if (value === void 0 || value === null || value === "") return;
|
|
@@ -298,6 +520,14 @@ function toAntRules(rules) {
|
|
|
298
520
|
} : void 0
|
|
299
521
|
}));
|
|
300
522
|
}
|
|
523
|
+
function safeRegExp(pattern) {
|
|
524
|
+
if (!pattern) return void 0;
|
|
525
|
+
try {
|
|
526
|
+
return new RegExp(pattern);
|
|
527
|
+
} catch {
|
|
528
|
+
return void 0;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
301
531
|
function RemoteSelect({
|
|
302
532
|
field,
|
|
303
533
|
value,
|
|
@@ -306,39 +536,105 @@ function RemoteSelect({
|
|
|
306
536
|
onChange,
|
|
307
537
|
registry
|
|
308
538
|
}) {
|
|
309
|
-
const [
|
|
539
|
+
const [keywordOptions, setKeywordOptions] = useState2([]);
|
|
540
|
+
const [selectedOptions, setSelectedOptions] = useState2([]);
|
|
310
541
|
const [loading, setLoading] = useState2(false);
|
|
311
542
|
const [keyword, setKeyword] = useState2("");
|
|
543
|
+
const keywordRequestId = useRef(0);
|
|
544
|
+
const selectedRequestId = useRef(0);
|
|
312
545
|
const service = field.remoteService ? registry.services[field.remoteService] : void 0;
|
|
313
546
|
const labelField = field.labelField ?? "label";
|
|
314
547
|
const valueField = field.valueField ?? "value";
|
|
315
548
|
const searchField = field.searchField ?? "keyword";
|
|
316
549
|
const debounceMs = field.debounceMs ?? 300;
|
|
550
|
+
const pageSize = field.pageSize ?? 20;
|
|
551
|
+
const parseOptions = (response) => {
|
|
552
|
+
const raw = Array.isArray(response) ? response : response?.items ?? response?.list ?? [];
|
|
553
|
+
return raw.flatMap((item) => {
|
|
554
|
+
if (!item || typeof item !== "object") return [];
|
|
555
|
+
const record = item;
|
|
556
|
+
const optionValue = record[valueField];
|
|
557
|
+
if (typeof optionValue !== "string" && typeof optionValue !== "number") return [];
|
|
558
|
+
return [
|
|
559
|
+
{
|
|
560
|
+
label: String(record[labelField] ?? optionValue ?? ""),
|
|
561
|
+
value: optionValue,
|
|
562
|
+
disabled: Boolean(record.disabled)
|
|
563
|
+
}
|
|
564
|
+
];
|
|
565
|
+
});
|
|
566
|
+
};
|
|
567
|
+
const mergeOptions = (base, extra) => {
|
|
568
|
+
const merged = new Map(base.map((item) => [String(item.value), item]));
|
|
569
|
+
extra.forEach((item) => {
|
|
570
|
+
merged.set(String(item.value), item);
|
|
571
|
+
});
|
|
572
|
+
return [...merged.values()];
|
|
573
|
+
};
|
|
574
|
+
const options = useMemo3(
|
|
575
|
+
() => mergeOptions(keywordOptions, selectedOptions),
|
|
576
|
+
[keywordOptions, selectedOptions]
|
|
577
|
+
);
|
|
317
578
|
useEffect(() => {
|
|
318
|
-
|
|
579
|
+
const currentRequestId = ++keywordRequestId.current;
|
|
580
|
+
if (!service) {
|
|
581
|
+
setKeywordOptions([]);
|
|
582
|
+
setLoading(false);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
319
585
|
const controller = new AbortController();
|
|
586
|
+
const isCurrent = () => currentRequestId === keywordRequestId.current && !controller.signal.aborted;
|
|
320
587
|
const timer = window.setTimeout(async () => {
|
|
321
588
|
setLoading(true);
|
|
322
589
|
try {
|
|
323
590
|
const response = await service(
|
|
324
|
-
{ [searchField]: keyword, page: 1, pageSize
|
|
591
|
+
{ [searchField]: keyword, page: 1, pageSize },
|
|
325
592
|
{ signal: controller.signal }
|
|
326
593
|
);
|
|
327
|
-
|
|
328
|
-
setOptions(
|
|
329
|
-
raw.filter((item) => !!item && typeof item === "object").map((item) => ({ label: String(item[labelField] ?? ""), value: item[valueField] }))
|
|
330
|
-
);
|
|
594
|
+
if (isCurrent()) setKeywordOptions(parseOptions(response));
|
|
331
595
|
} catch {
|
|
332
|
-
|
|
596
|
+
if (isCurrent()) setKeywordOptions([]);
|
|
333
597
|
} finally {
|
|
334
|
-
if (
|
|
598
|
+
if (isCurrent()) setLoading(false);
|
|
335
599
|
}
|
|
336
600
|
}, debounceMs);
|
|
337
601
|
return () => {
|
|
338
602
|
window.clearTimeout(timer);
|
|
339
603
|
controller.abort();
|
|
340
604
|
};
|
|
341
|
-
}, [debounceMs,
|
|
605
|
+
}, [debounceMs, keyword, labelField, pageSize, searchField, service, valueField]);
|
|
606
|
+
useEffect(() => {
|
|
607
|
+
const currentRequestId = ++selectedRequestId.current;
|
|
608
|
+
if (!service) {
|
|
609
|
+
setSelectedOptions([]);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
if (value === void 0 || value === null || value === "") {
|
|
613
|
+
setSelectedOptions([]);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
617
|
+
setSelectedOptions([]);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const controller = new AbortController();
|
|
621
|
+
const isCurrent = () => currentRequestId === selectedRequestId.current && !controller.signal.aborted;
|
|
622
|
+
const fallbackOption = {
|
|
623
|
+
label: String(value),
|
|
624
|
+
value
|
|
625
|
+
};
|
|
626
|
+
setSelectedOptions([fallbackOption]);
|
|
627
|
+
void service({ [valueField]: value, page: 1, pageSize }, { signal: controller.signal }).then((response) => {
|
|
628
|
+
if (!isCurrent()) return;
|
|
629
|
+
const resolvedOptions = parseOptions(response);
|
|
630
|
+
if (resolvedOptions.length) {
|
|
631
|
+
setSelectedOptions(mergeOptions([fallbackOption], resolvedOptions));
|
|
632
|
+
}
|
|
633
|
+
}).catch(() => void 0);
|
|
634
|
+
return () => {
|
|
635
|
+
controller.abort();
|
|
636
|
+
};
|
|
637
|
+
}, [labelField, pageSize, service, value, valueField]);
|
|
342
638
|
return /* @__PURE__ */ jsx5(
|
|
343
639
|
Select,
|
|
344
640
|
{
|
|
@@ -363,6 +659,39 @@ function BestForm({
|
|
|
363
659
|
mode,
|
|
364
660
|
onSubmit,
|
|
365
661
|
onCancel
|
|
662
|
+
}) {
|
|
663
|
+
const resolvedInitialValues = useMemo3(
|
|
664
|
+
() => resolveInitialValues(fields, initialValues),
|
|
665
|
+
[fields, initialValues]
|
|
666
|
+
);
|
|
667
|
+
const formSignature = useMemo3(
|
|
668
|
+
() => JSON.stringify([mode, fields.map((field) => fieldSignature(field)), resolvedInitialValues]),
|
|
669
|
+
[fields, mode, resolvedInitialValues]
|
|
670
|
+
);
|
|
671
|
+
return /* @__PURE__ */ jsx5(
|
|
672
|
+
BestFormBody,
|
|
673
|
+
{
|
|
674
|
+
extra,
|
|
675
|
+
fields,
|
|
676
|
+
initialValues: resolvedInitialValues,
|
|
677
|
+
loading,
|
|
678
|
+
mode,
|
|
679
|
+
onCancel,
|
|
680
|
+
onSubmit,
|
|
681
|
+
submitText
|
|
682
|
+
},
|
|
683
|
+
formSignature
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
function BestFormBody({
|
|
687
|
+
fields,
|
|
688
|
+
initialValues,
|
|
689
|
+
loading,
|
|
690
|
+
submitText = "\u63D0\u4EA4",
|
|
691
|
+
extra,
|
|
692
|
+
mode,
|
|
693
|
+
onSubmit,
|
|
694
|
+
onCancel
|
|
366
695
|
}) {
|
|
367
696
|
const [form] = Form.useForm();
|
|
368
697
|
return /* @__PURE__ */ jsxs2(Form, { form, initialValues, layout: "vertical", onFinish: onSubmit, children: [
|
|
@@ -374,12 +703,49 @@ function BestForm({
|
|
|
374
703
|
] })
|
|
375
704
|
] });
|
|
376
705
|
}
|
|
706
|
+
function getDefaultValues(fields) {
|
|
707
|
+
return Object.fromEntries(
|
|
708
|
+
fields.filter((field) => field.defaultValue !== void 0).map((field) => [field.field, field.defaultValue])
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
function resolveInitialValues(fields, initialValues) {
|
|
712
|
+
const values = mergeDefinedValues(getDefaultValues(fields), initialValues);
|
|
713
|
+
const resolvedValues = { ...values };
|
|
714
|
+
fields.forEach((field) => {
|
|
715
|
+
if (field.component !== "repeatable" || !field.itemFields) return;
|
|
716
|
+
const items = Array.isArray(resolvedValues[field.field]) ? resolvedValues[field.field] : [];
|
|
717
|
+
resolvedValues[field.field] = items.map(
|
|
718
|
+
(item) => resolveInitialValues(field.itemFields ?? [], isRecord(item) ? item : {})
|
|
719
|
+
);
|
|
720
|
+
});
|
|
721
|
+
return resolvedValues;
|
|
722
|
+
}
|
|
723
|
+
function mergeDefinedValues(baseValues, nextValues) {
|
|
724
|
+
const mergedValues = { ...baseValues };
|
|
725
|
+
Object.entries(nextValues ?? {}).forEach(([key, value]) => {
|
|
726
|
+
if (value === void 0) return;
|
|
727
|
+
const currentValue = mergedValues[key];
|
|
728
|
+
if (isRecord(currentValue) && isRecord(value)) {
|
|
729
|
+
mergedValues[key] = mergeDefinedValues(currentValue, value);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
mergedValues[key] = value;
|
|
733
|
+
});
|
|
734
|
+
return mergedValues;
|
|
735
|
+
}
|
|
736
|
+
function getPathValue(values, path) {
|
|
737
|
+
const parts = Array.isArray(path) ? path : [path];
|
|
738
|
+
return parts.reduce((current, part) => {
|
|
739
|
+
if (current && typeof current === "object") return current[part];
|
|
740
|
+
return void 0;
|
|
741
|
+
}, values);
|
|
742
|
+
}
|
|
377
743
|
function evaluateCondition(condition, values, mode) {
|
|
378
744
|
switch (condition.operator) {
|
|
379
745
|
case "equals":
|
|
380
|
-
return values
|
|
746
|
+
return getConditionFieldValue(values, condition.field) === condition.value;
|
|
381
747
|
case "notEmpty":
|
|
382
|
-
return values
|
|
748
|
+
return getConditionFieldValue(values, condition.field) !== void 0 && getConditionFieldValue(values, condition.field) !== null && getConditionFieldValue(values, condition.field) !== "";
|
|
383
749
|
case "modeEquals":
|
|
384
750
|
return mode === condition.value;
|
|
385
751
|
case "and":
|
|
@@ -430,17 +796,36 @@ function BestModal({ children, open, title, onClose }) {
|
|
|
430
796
|
|
|
431
797
|
// src/ui/BestSearch.tsx
|
|
432
798
|
import { Button as Button3, Col, DatePicker as DatePicker2, Form as Form2, Input as Input3, InputNumber as InputNumber2, Row, Select as Select2 } from "antd";
|
|
433
|
-
import
|
|
434
|
-
import { useEffect as useEffect2 } from "react";
|
|
799
|
+
import dayjs3 from "dayjs";
|
|
800
|
+
import { useEffect as useEffect2, useMemo as useMemo4, useRef as useRef2 } from "react";
|
|
435
801
|
import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
436
|
-
function
|
|
802
|
+
function searchFormSignature(fields, initialValues) {
|
|
803
|
+
return JSON.stringify([
|
|
804
|
+
fields.map((field) => ({
|
|
805
|
+
field: field.field,
|
|
806
|
+
component: field.component,
|
|
807
|
+
defaultValue: field.defaultValue,
|
|
808
|
+
remoteService: field.remoteService,
|
|
809
|
+
searchField: field.searchField,
|
|
810
|
+
labelField: field.labelField,
|
|
811
|
+
valueField: field.valueField,
|
|
812
|
+
debounceMs: field.debounceMs,
|
|
813
|
+
pageSize: field.pageSize
|
|
814
|
+
})),
|
|
815
|
+
initialValues
|
|
816
|
+
]);
|
|
817
|
+
}
|
|
818
|
+
function FieldControl({ field, value, onChange, ...controlProps }) {
|
|
437
819
|
const registry = useBestRegistry();
|
|
438
820
|
switch (field.component) {
|
|
439
821
|
case "select":
|
|
440
822
|
return /* @__PURE__ */ jsx8(
|
|
441
823
|
Select2,
|
|
442
824
|
{
|
|
825
|
+
...controlProps,
|
|
443
826
|
allowClear: true,
|
|
827
|
+
value,
|
|
828
|
+
onChange,
|
|
444
829
|
disabled: field.disabled,
|
|
445
830
|
options: field.options,
|
|
446
831
|
placeholder: field.placeholder
|
|
@@ -450,6 +835,9 @@ function FieldControl({ field }) {
|
|
|
450
835
|
return /* @__PURE__ */ jsx8(
|
|
451
836
|
InputNumber2,
|
|
452
837
|
{
|
|
838
|
+
...controlProps,
|
|
839
|
+
value,
|
|
840
|
+
onChange,
|
|
453
841
|
disabled: field.disabled,
|
|
454
842
|
placeholder: field.placeholder,
|
|
455
843
|
style: { width: "100%" }
|
|
@@ -460,8 +848,10 @@ function FieldControl({ field }) {
|
|
|
460
848
|
RemoteSelect,
|
|
461
849
|
{
|
|
462
850
|
field,
|
|
463
|
-
value
|
|
464
|
-
onChange: () => void 0,
|
|
851
|
+
value,
|
|
852
|
+
onChange: onChange ?? (() => void 0),
|
|
853
|
+
disabled: field.disabled,
|
|
854
|
+
placeholder: field.placeholder,
|
|
465
855
|
registry
|
|
466
856
|
}
|
|
467
857
|
);
|
|
@@ -469,8 +859,11 @@ function FieldControl({ field }) {
|
|
|
469
859
|
return /* @__PURE__ */ jsx8(
|
|
470
860
|
DatePicker2,
|
|
471
861
|
{
|
|
862
|
+
...controlProps,
|
|
863
|
+
value: toDatePickerValue(value),
|
|
864
|
+
onChange: (date, dateString) => onChange?.(serializeDatePickerValue(date, dateString)),
|
|
472
865
|
disabled: field.disabled,
|
|
473
|
-
disabledDate: (current) => field.maxDate === "today" && current.isAfter(
|
|
866
|
+
disabledDate: (current) => field.maxDate === "today" && current.isAfter(dayjs3(), "day"),
|
|
474
867
|
placeholder: field.placeholder,
|
|
475
868
|
style: { width: "100%" }
|
|
476
869
|
}
|
|
@@ -479,38 +872,108 @@ function FieldControl({ field }) {
|
|
|
479
872
|
return /* @__PURE__ */ jsx8(
|
|
480
873
|
DatePicker2.RangePicker,
|
|
481
874
|
{
|
|
875
|
+
...controlProps,
|
|
876
|
+
value: toDateRangePickerValue(value),
|
|
877
|
+
onChange: (dates, dateStrings) => onChange?.(serializeDateRangePickerValue(dates, dateStrings)),
|
|
482
878
|
disabled: field.disabled,
|
|
483
|
-
disabledDate: (current) => field.maxDate === "today" && current.isAfter(
|
|
879
|
+
disabledDate: (current) => field.maxDate === "today" && current.isAfter(dayjs3(), "day"),
|
|
484
880
|
style: { width: "100%" }
|
|
485
881
|
}
|
|
486
882
|
);
|
|
487
883
|
case "textarea":
|
|
488
|
-
return /* @__PURE__ */ jsx8(
|
|
884
|
+
return /* @__PURE__ */ jsx8(
|
|
885
|
+
Input3.TextArea,
|
|
886
|
+
{
|
|
887
|
+
...controlProps,
|
|
888
|
+
value,
|
|
889
|
+
onChange,
|
|
890
|
+
disabled: field.disabled,
|
|
891
|
+
placeholder: field.placeholder
|
|
892
|
+
}
|
|
893
|
+
);
|
|
489
894
|
default:
|
|
490
|
-
return /* @__PURE__ */ jsx8(
|
|
895
|
+
return /* @__PURE__ */ jsx8(
|
|
896
|
+
Input3,
|
|
897
|
+
{
|
|
898
|
+
...controlProps,
|
|
899
|
+
allowClear: true,
|
|
900
|
+
value,
|
|
901
|
+
onChange,
|
|
902
|
+
disabled: field.disabled,
|
|
903
|
+
placeholder: field.placeholder
|
|
904
|
+
}
|
|
905
|
+
);
|
|
491
906
|
}
|
|
492
907
|
}
|
|
493
|
-
function syncValues(form, value) {
|
|
494
|
-
form.setFieldsValue(
|
|
908
|
+
function syncValues(form, value, fields) {
|
|
909
|
+
form.setFieldsValue(Object.fromEntries(fields.map((field) => [field.field, void 0])));
|
|
910
|
+
form.setFieldsValue(value);
|
|
495
911
|
}
|
|
496
912
|
function BestSearch({
|
|
497
913
|
fields,
|
|
498
914
|
value,
|
|
915
|
+
defaultValues,
|
|
499
916
|
loading,
|
|
500
917
|
submitText = "\u67E5\u8BE2",
|
|
501
918
|
extra,
|
|
502
919
|
onChange,
|
|
503
920
|
onSearch,
|
|
504
921
|
onReset
|
|
922
|
+
}) {
|
|
923
|
+
const initialValues = useMemo4(
|
|
924
|
+
() => mergeDefinedValues(getDefaultValues(fields), defaultValues),
|
|
925
|
+
[fields, defaultValues]
|
|
926
|
+
);
|
|
927
|
+
return /* @__PURE__ */ jsx8(
|
|
928
|
+
BestSearchBody,
|
|
929
|
+
{
|
|
930
|
+
extra,
|
|
931
|
+
fields,
|
|
932
|
+
initialValues,
|
|
933
|
+
loading,
|
|
934
|
+
onChange,
|
|
935
|
+
onReset,
|
|
936
|
+
onSearch,
|
|
937
|
+
submitText,
|
|
938
|
+
value
|
|
939
|
+
},
|
|
940
|
+
searchFormSignature(fields, initialValues)
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
function BestSearchBody({
|
|
944
|
+
fields,
|
|
945
|
+
value,
|
|
946
|
+
initialValues,
|
|
947
|
+
loading,
|
|
948
|
+
submitText,
|
|
949
|
+
extra,
|
|
950
|
+
onChange,
|
|
951
|
+
onSearch,
|
|
952
|
+
onReset
|
|
505
953
|
}) {
|
|
506
954
|
const [form] = Form2.useForm();
|
|
955
|
+
const valueSignatureRef = useRef2(void 0);
|
|
956
|
+
const controlledRef = useRef2(false);
|
|
507
957
|
useEffect2(() => {
|
|
508
|
-
|
|
509
|
-
|
|
958
|
+
if (value === void 0) {
|
|
959
|
+
if (controlledRef.current) {
|
|
960
|
+
syncValues(form, {}, fields);
|
|
961
|
+
valueSignatureRef.current = JSON.stringify({});
|
|
962
|
+
controlledRef.current = false;
|
|
963
|
+
}
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
controlledRef.current = true;
|
|
967
|
+
const signature = JSON.stringify(value);
|
|
968
|
+
if (valueSignatureRef.current === signature) return;
|
|
969
|
+
syncValues(form, value, fields);
|
|
970
|
+
valueSignatureRef.current = signature;
|
|
971
|
+
}, [fields, form, value]);
|
|
510
972
|
return /* @__PURE__ */ jsx8(
|
|
511
973
|
Form2,
|
|
512
974
|
{
|
|
513
975
|
form,
|
|
976
|
+
initialValues,
|
|
514
977
|
layout: "vertical",
|
|
515
978
|
onFinish: (values) => onSearch(values),
|
|
516
979
|
onValuesChange: (_, values) => onChange?.(values),
|
|
@@ -523,7 +986,8 @@ function BestSearch({
|
|
|
523
986
|
Button3,
|
|
524
987
|
{
|
|
525
988
|
onClick: () => {
|
|
526
|
-
form
|
|
989
|
+
syncValues(form, {}, fields);
|
|
990
|
+
valueSignatureRef.current = JSON.stringify({});
|
|
527
991
|
onChange?.({});
|
|
528
992
|
onReset?.();
|
|
529
993
|
},
|
|
@@ -601,9 +1065,9 @@ function BestTable({
|
|
|
601
1065
|
}
|
|
602
1066
|
|
|
603
1067
|
// src/lowcode/runtime.ts
|
|
604
|
-
import
|
|
1068
|
+
import dayjs4 from "dayjs";
|
|
605
1069
|
import utc from "dayjs/plugin/utc.js";
|
|
606
|
-
|
|
1070
|
+
dayjs4.extend(utc);
|
|
607
1071
|
function toBestListQuery(params, sort) {
|
|
608
1072
|
const { current, pageSize, ...filters } = params;
|
|
609
1073
|
const normalizedSort = Object.fromEntries(
|
|
@@ -636,7 +1100,7 @@ function formatCrudValue(value, format) {
|
|
|
636
1100
|
const numericValue = typeof value === "number" ? value : Number(value.trim());
|
|
637
1101
|
timestamp = Number.isFinite(numericValue) && numericValue !== 0 ? Math.abs(numericValue) >= 1e12 ? numericValue : numericValue * 1e3 : value;
|
|
638
1102
|
}
|
|
639
|
-
const date =
|
|
1103
|
+
const date = dayjs4.utc(timestamp);
|
|
640
1104
|
if (!date.isValid()) return String(value);
|
|
641
1105
|
return date.format(format === "date" ? "YYYY-MM-DD" : "YYYY-MM-DD HH:mm:ss");
|
|
642
1106
|
}
|
|
@@ -753,6 +1217,13 @@ function validateFields(fields, path, registry, diagnostics) {
|
|
|
753
1217
|
"field.repeatable",
|
|
754
1218
|
"repeatable \u7EC4\u4EF6\u81F3\u5C11\u9700\u8981\u4E00\u4E2A itemFields \u5B57\u6BB5"
|
|
755
1219
|
);
|
|
1220
|
+
if (field.itemFields?.length)
|
|
1221
|
+
validateFields(
|
|
1222
|
+
field.itemFields,
|
|
1223
|
+
`${fieldPath}/itemFields`,
|
|
1224
|
+
registry,
|
|
1225
|
+
diagnostics
|
|
1226
|
+
);
|
|
756
1227
|
if (field.minItems !== void 0 && field.maxItems !== void 0 && field.minItems > field.maxItems)
|
|
757
1228
|
push(diagnostics, fieldPath, "field.repeatable.range", "minItems \u4E0D\u80FD\u5927\u4E8E maxItems");
|
|
758
1229
|
if (field.slot && registry && !registry.slots[field.slot])
|
|
@@ -809,6 +1280,22 @@ function validateCrudPageSchema(schema, registry) {
|
|
|
809
1280
|
`\u672A\u6CE8\u518C\u670D\u52A1\uFF1A${schema.dataSource.detail}`
|
|
810
1281
|
);
|
|
811
1282
|
}
|
|
1283
|
+
if (schema.dataSource?.create && registry && !registry.services[schema.dataSource.create]) {
|
|
1284
|
+
push(
|
|
1285
|
+
diagnostics,
|
|
1286
|
+
"/dataSource/create",
|
|
1287
|
+
"registry.service",
|
|
1288
|
+
`\u672A\u6CE8\u518C\u670D\u52A1\uFF1A${schema.dataSource.create}`
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1291
|
+
if (schema.dataSource?.update && registry && !registry.services[schema.dataSource.update]) {
|
|
1292
|
+
push(
|
|
1293
|
+
diagnostics,
|
|
1294
|
+
"/dataSource/update",
|
|
1295
|
+
"registry.service",
|
|
1296
|
+
`\u672A\u6CE8\u518C\u670D\u52A1\uFF1A${schema.dataSource.update}`
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
812
1299
|
if (schema.dataSource?.remove && registry && !registry.services[schema.dataSource.remove]) {
|
|
813
1300
|
push(
|
|
814
1301
|
diagnostics,
|
|
@@ -884,8 +1371,10 @@ ${message2}`);
|
|
|
884
1371
|
|
|
885
1372
|
// src/lowcode/BestCrudPage.tsx
|
|
886
1373
|
import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
887
|
-
function toFieldDefinition(field,
|
|
1374
|
+
function toFieldDefinition(field, dictionaries, slots) {
|
|
888
1375
|
const slot = field.slot;
|
|
1376
|
+
const options = field.dict ? dictionaries[field.dict] ?? [] : [];
|
|
1377
|
+
const rules = field.rules ?? [];
|
|
889
1378
|
return {
|
|
890
1379
|
field: field.field,
|
|
891
1380
|
label: field.label,
|
|
@@ -903,25 +1392,22 @@ function toFieldDefinition(field, options, slots) {
|
|
|
903
1392
|
hidden: field.hidden,
|
|
904
1393
|
maxDate: field.maxDate,
|
|
905
1394
|
span: field.span,
|
|
1395
|
+
defaultValue: field.defaultValue,
|
|
906
1396
|
visibleWhen: field.visibleWhen,
|
|
907
1397
|
disabledWhen: field.disabledWhen,
|
|
908
1398
|
clearWhenHidden: field.clearWhenHidden,
|
|
909
|
-
itemFields: field.itemFields?.map(
|
|
1399
|
+
itemFields: field.itemFields?.map(
|
|
1400
|
+
(item) => toFieldDefinition(item, dictionaries, slots)
|
|
1401
|
+
),
|
|
910
1402
|
minItems: field.minItems,
|
|
911
1403
|
maxItems: field.maxItems,
|
|
912
1404
|
render: slot ? (context) => slots[slot]?.(context) : void 0,
|
|
913
|
-
rules: field.required ? [{ required: true, message: `\u8BF7\u586B\u5199${field.label}` }, ...
|
|
1405
|
+
rules: field.required && !rules.some((rule) => rule.required) ? [{ required: true, message: `\u8BF7\u586B\u5199${field.label}` }, ...rules] : rules
|
|
914
1406
|
};
|
|
915
1407
|
}
|
|
916
1408
|
function useFields(fields = []) {
|
|
917
1409
|
const registry = useBestRegistry();
|
|
918
|
-
return fields.map(
|
|
919
|
-
(field) => toFieldDefinition(
|
|
920
|
-
field,
|
|
921
|
-
field.dict ? registry.dictionaries[field.dict] ?? [] : [],
|
|
922
|
-
registry.slots
|
|
923
|
-
)
|
|
924
|
-
);
|
|
1410
|
+
return fields.map((field) => toFieldDefinition(field, registry.dictionaries, registry.slots));
|
|
925
1411
|
}
|
|
926
1412
|
function toDetailField(field, dictionary, slots) {
|
|
927
1413
|
const slot = field.slot;
|
|
@@ -974,7 +1460,7 @@ function toProTableSearchColumn(field, dictionary) {
|
|
|
974
1460
|
initialValue: field.defaultValue,
|
|
975
1461
|
fieldProps: {
|
|
976
1462
|
...options ? { options } : {},
|
|
977
|
-
...field.maxDate === "today" ? { disabledDate: (current) => current.isAfter(
|
|
1463
|
+
...field.maxDate === "today" ? { disabledDate: (current) => current.isAfter(dayjs5(), "day") } : {}
|
|
978
1464
|
}
|
|
979
1465
|
};
|
|
980
1466
|
}
|
|
@@ -995,20 +1481,41 @@ function BestCrudPage({ adapter, className, schema }) {
|
|
|
995
1481
|
const registeredListService = useBestListService(schema.dataSource.list);
|
|
996
1482
|
if (!registeredListService) throw new Error(`\u672A\u6CE8\u518C\u5217\u8868\u670D\u52A1\uFF1A${schema.dataSource.list}`);
|
|
997
1483
|
const listService = useCallback(
|
|
998
|
-
async (
|
|
999
|
-
const response = await registeredListService(
|
|
1484
|
+
async (query2, options) => {
|
|
1485
|
+
const response = await registeredListService(query2, options);
|
|
1000
1486
|
return adapter?.fromList ? { ...response, items: adapter.fromList(response.items) } : response;
|
|
1001
1487
|
},
|
|
1002
1488
|
[adapter, registeredListService]
|
|
1003
1489
|
);
|
|
1004
1490
|
const searchFields = useFields(schema.search);
|
|
1005
|
-
const useBestSearch = schema.searchMode === "bestSearch";
|
|
1491
|
+
const useBestSearch = schema.searchMode === "bestSearch" || schema.search?.some((field) => field.component === "remoteSelect");
|
|
1006
1492
|
const formFields = useFields(schema.form);
|
|
1007
|
-
const actionRef =
|
|
1008
|
-
const
|
|
1009
|
-
const
|
|
1493
|
+
const actionRef = useRef3(void 0);
|
|
1494
|
+
const searchDefaultValues = useMemo5(() => getDefaultValues(searchFields), [searchFields]);
|
|
1495
|
+
const searchFieldNames = useMemo5(
|
|
1496
|
+
() => new Set(searchFields.map((field) => field.field)),
|
|
1497
|
+
[searchFields]
|
|
1498
|
+
);
|
|
1499
|
+
const searchSchemaSignature = useMemo5(
|
|
1500
|
+
() => JSON.stringify(
|
|
1501
|
+
searchFields.map((field) => ({
|
|
1502
|
+
field: field.field,
|
|
1503
|
+
component: field.component,
|
|
1504
|
+
defaultValue: field.defaultValue
|
|
1505
|
+
}))
|
|
1506
|
+
),
|
|
1507
|
+
[searchFields]
|
|
1508
|
+
);
|
|
1509
|
+
const [query, setQuery] = useState3(searchDefaultValues);
|
|
1510
|
+
const queryRef = useRef3(searchDefaultValues);
|
|
1511
|
+
const queryStateRef = useRef3({
|
|
1512
|
+
schemaId: schema.id,
|
|
1513
|
+
searchSchemaSignature,
|
|
1514
|
+
userTouched: false
|
|
1515
|
+
});
|
|
1010
1516
|
const [drawer, setDrawer] = useState3({ mode: "closed" });
|
|
1011
1517
|
const [submitting, setSubmitting] = useState3(false);
|
|
1518
|
+
const submittingRef = useRef3(false);
|
|
1012
1519
|
const handleAction = useCallback(
|
|
1013
1520
|
async (action, record) => {
|
|
1014
1521
|
if (action.access && !registry.access(action.access)) return;
|
|
@@ -1038,7 +1545,10 @@ function BestCrudPage({ adapter, className, schema }) {
|
|
|
1038
1545
|
if (action.effect === "openCreate") setDrawer({ mode: "create" });
|
|
1039
1546
|
if (action.effect === "remove" && schema.dataSource.remove) {
|
|
1040
1547
|
const removeService = registry.services[schema.dataSource.remove];
|
|
1041
|
-
if (!removeService)
|
|
1548
|
+
if (!removeService) {
|
|
1549
|
+
message.error(`\u672A\u6CE8\u518C\u670D\u52A1\uFF1A${schema.dataSource.remove}`);
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1042
1552
|
try {
|
|
1043
1553
|
const deleted = await removeCrudRecord(
|
|
1044
1554
|
record ?? {},
|
|
@@ -1055,17 +1565,22 @@ function BestCrudPage({ adapter, className, schema }) {
|
|
|
1055
1565
|
}
|
|
1056
1566
|
}
|
|
1057
1567
|
if (action.effect === "runAction" && action.action) {
|
|
1568
|
+
const runAction = registry.actions[action.action];
|
|
1569
|
+
if (!runAction) {
|
|
1570
|
+
message.error(`\u672A\u6CE8\u518C\u52A8\u4F5C\uFF1A${action.action}`);
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1058
1573
|
try {
|
|
1059
|
-
await
|
|
1574
|
+
await runAction({ record });
|
|
1060
1575
|
actionRef.current?.reload();
|
|
1061
1576
|
} catch (error) {
|
|
1062
1577
|
message.error(errorMessage(error, `${action.label}\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5`));
|
|
1063
1578
|
}
|
|
1064
1579
|
}
|
|
1065
1580
|
},
|
|
1066
|
-
[registry]
|
|
1581
|
+
[adapter, registry, schema]
|
|
1067
1582
|
);
|
|
1068
|
-
const columns =
|
|
1583
|
+
const columns = useMemo5(() => {
|
|
1069
1584
|
const proTableSearchFields = useBestSearch ? [] : schema.search ?? [];
|
|
1070
1585
|
const searchColumns = new Map(
|
|
1071
1586
|
proTableSearchFields.map((field) => [
|
|
@@ -1097,31 +1612,57 @@ function BestCrudPage({ adapter, className, schema }) {
|
|
|
1097
1612
|
schema.table.columns,
|
|
1098
1613
|
useBestSearch
|
|
1099
1614
|
]);
|
|
1100
|
-
const
|
|
1101
|
-
|
|
1102
|
-
const pageRequest = createLatestPageRequest(
|
|
1615
|
+
const pageRequest = useMemo5(
|
|
1616
|
+
() => createLatestPageRequest(
|
|
1103
1617
|
listService,
|
|
1104
1618
|
(error) => message.error(errorMessage(error, `${schema.title}\u52A0\u8F7D\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5`))
|
|
1619
|
+
),
|
|
1620
|
+
[listService, schema.title]
|
|
1621
|
+
);
|
|
1622
|
+
const request = useCallback(
|
|
1623
|
+
(params, sort) => pageRequest.request(
|
|
1624
|
+
toBestListQuery({ ...params, ...useBestSearch ? queryRef.current : {} }, sort)
|
|
1625
|
+
),
|
|
1626
|
+
[pageRequest, useBestSearch]
|
|
1627
|
+
);
|
|
1628
|
+
useLayoutEffect(() => {
|
|
1629
|
+
const previous = queryStateRef.current;
|
|
1630
|
+
if (previous.schemaId === schema.id && previous.searchSchemaSignature === searchSchemaSignature)
|
|
1631
|
+
return;
|
|
1632
|
+
const shouldUseDefaults = previous.schemaId !== schema.id || !previous.userTouched;
|
|
1633
|
+
const nextQuery = shouldUseDefaults ? searchDefaultValues : Object.fromEntries(
|
|
1634
|
+
Object.entries(queryRef.current).filter(([field]) => searchFieldNames.has(field))
|
|
1105
1635
|
);
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1636
|
+
queryRef.current = nextQuery;
|
|
1637
|
+
setQuery(nextQuery);
|
|
1638
|
+
queryStateRef.current = {
|
|
1639
|
+
schemaId: schema.id,
|
|
1640
|
+
searchSchemaSignature,
|
|
1641
|
+
userTouched: shouldUseDefaults ? false : previous.userTouched
|
|
1642
|
+
};
|
|
1643
|
+
}, [schema.id, searchDefaultValues, searchFieldNames, searchSchemaSignature]);
|
|
1644
|
+
useEffect3(() => () => pageRequest.abort(), [pageRequest]);
|
|
1110
1645
|
const rowKey = schema.table.rowKey;
|
|
1111
1646
|
const submitForm = useCallback(
|
|
1112
1647
|
async (values) => {
|
|
1113
|
-
if (
|
|
1648
|
+
if (submittingRef.current) return;
|
|
1114
1649
|
const serviceKey = drawer.mode === "edit" ? schema.dataSource.update : schema.dataSource.create;
|
|
1115
1650
|
if (!serviceKey) {
|
|
1116
1651
|
message.error(`${drawer.mode === "edit" ? "\u66F4\u65B0" : "\u521B\u5EFA"}\u670D\u52A1\u672A\u914D\u7F6E`);
|
|
1117
1652
|
return;
|
|
1118
1653
|
}
|
|
1654
|
+
const service = registry.services[serviceKey];
|
|
1655
|
+
if (!service) {
|
|
1656
|
+
message.error(`\u672A\u6CE8\u518C\u670D\u52A1\uFF1A${serviceKey}`);
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1119
1659
|
const record = drawer.mode === "closed" ? void 0 : drawer.record;
|
|
1660
|
+
submittingRef.current = true;
|
|
1120
1661
|
setSubmitting(true);
|
|
1121
1662
|
try {
|
|
1122
1663
|
const mergedValues = { ...record, ...values };
|
|
1123
1664
|
const payload = drawer.mode === "edit" ? adapter?.toUpdatePayload?.(mergedValues, record) ?? mergedValues : adapter?.toCreatePayload?.(mergedValues) ?? mergedValues;
|
|
1124
|
-
await
|
|
1665
|
+
await service(payload);
|
|
1125
1666
|
message.success("\u4FDD\u5B58\u6210\u529F");
|
|
1126
1667
|
setDrawer({ mode: "closed" });
|
|
1127
1668
|
actionRef.current?.reload();
|
|
@@ -1129,21 +1670,28 @@ function BestCrudPage({ adapter, className, schema }) {
|
|
|
1129
1670
|
message.error(errorMessage(error, "\u4FDD\u5B58\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"));
|
|
1130
1671
|
} finally {
|
|
1131
1672
|
setSubmitting(false);
|
|
1673
|
+
submittingRef.current = false;
|
|
1132
1674
|
}
|
|
1133
1675
|
},
|
|
1134
|
-
[adapter, drawer, registry.services, schema.dataSource.create, schema.dataSource.update
|
|
1676
|
+
[adapter, drawer, registry.services, schema.dataSource.create, schema.dataSource.update]
|
|
1135
1677
|
);
|
|
1136
1678
|
return /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1137
1679
|
useBestSearch && searchFields.length ? /* @__PURE__ */ jsx11(
|
|
1138
1680
|
BestSearch,
|
|
1139
1681
|
{
|
|
1140
1682
|
fields: searchFields,
|
|
1683
|
+
value: query,
|
|
1684
|
+
defaultValues: searchDefaultValues,
|
|
1141
1685
|
onReset: () => {
|
|
1142
1686
|
queryRef.current = {};
|
|
1687
|
+
queryStateRef.current = { ...queryStateRef.current, userTouched: true };
|
|
1688
|
+
setQuery({});
|
|
1143
1689
|
actionRef.current?.reload();
|
|
1144
1690
|
},
|
|
1145
1691
|
onSearch: (values) => {
|
|
1146
1692
|
queryRef.current = values;
|
|
1693
|
+
queryStateRef.current = { ...queryStateRef.current, userTouched: true };
|
|
1694
|
+
setQuery(values);
|
|
1147
1695
|
actionRef.current?.reload();
|
|
1148
1696
|
}
|
|
1149
1697
|
}
|
|
@@ -1218,35 +1766,6 @@ function BestCrudPage({ adapter, className, schema }) {
|
|
|
1218
1766
|
);
|
|
1219
1767
|
}
|
|
1220
1768
|
}
|
|
1221
|
-
|
|
1222
|
-
// src/lowcode/adapter.ts
|
|
1223
|
-
function normalizeBestValues(values, normalize = defaultNormalize) {
|
|
1224
|
-
return Object.fromEntries(
|
|
1225
|
-
Object.entries(values).map(([field, value]) => [field, normalizeNested(value, normalize, field)])
|
|
1226
|
-
);
|
|
1227
|
-
}
|
|
1228
|
-
function normalizeNested(value, normalize, field) {
|
|
1229
|
-
if (Array.isArray(value)) return value.map((item) => normalizeNested(item, normalize, field));
|
|
1230
|
-
if (value && typeof value === "object" && !(value instanceof Date)) {
|
|
1231
|
-
return Object.fromEntries(
|
|
1232
|
-
Object.entries(value).map(([key, item]) => [key, normalizeNested(item, normalize, key)])
|
|
1233
|
-
);
|
|
1234
|
-
}
|
|
1235
|
-
return normalize(value, field);
|
|
1236
|
-
}
|
|
1237
|
-
function defaultNormalize(value) {
|
|
1238
|
-
if (typeof value === "string") return value.trim() === "" ? void 0 : value.trim();
|
|
1239
|
-
return value;
|
|
1240
|
-
}
|
|
1241
|
-
function createBestCrudAdapter(options = {}) {
|
|
1242
|
-
const normalize = options.normalize ?? defaultNormalize;
|
|
1243
|
-
return {
|
|
1244
|
-
fromList: options.fromList,
|
|
1245
|
-
fromDetail: options.fromDetail,
|
|
1246
|
-
toCreatePayload: (values) => options.toCreatePayload?.(normalizeBestValues(values, normalize)) ?? normalizeBestValues(values, normalize),
|
|
1247
|
-
toUpdatePayload: (values, record) => options.toUpdatePayload?.(normalizeBestValues(values, normalize), record) ?? normalizeBestValues(values, normalize)
|
|
1248
|
-
};
|
|
1249
|
-
}
|
|
1250
1769
|
export {
|
|
1251
1770
|
BestBatchInput,
|
|
1252
1771
|
BestCrudPage,
|