@xbeeant/form-engine-designer 0.0.3 → 0.0.4

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 (131) hide show
  1. package/dist/es/Canvas.js +42 -47
  2. package/dist/es/Canvas.js.map +1 -1
  3. package/dist/es/Designer.js +121 -98
  4. package/dist/es/Designer.js.map +1 -1
  5. package/dist/es/DesignerContext.js +86 -73
  6. package/dist/es/DesignerContext.js.map +1 -1
  7. package/dist/es/Palette.js +3 -3
  8. package/dist/es/Palette.js.map +1 -1
  9. package/dist/es/PropertyPanel.js +99 -100
  10. package/dist/es/PropertyPanel.js.map +1 -1
  11. package/dist/es/catalog.js +17 -10
  12. package/dist/es/catalog.js.map +1 -1
  13. package/dist/es/designer-context.js +110 -0
  14. package/dist/es/designer-context.js.map +1 -0
  15. package/dist/es/history.js +73 -0
  16. package/dist/es/history.js.map +1 -0
  17. package/dist/es/index.css +1 -1
  18. package/dist/es/index.js +3 -3
  19. package/dist/es/property/basic-property.js +6 -0
  20. package/dist/es/property/basic-property.js.map +1 -1
  21. package/dist/es/property-panel.js +294 -0
  22. package/dist/es/property-panel.js.map +1 -0
  23. package/dist/es/resizable-panel.js +103 -0
  24. package/dist/es/resizable-panel.js.map +1 -0
  25. package/dist/es/schema-utils.js +286 -0
  26. package/dist/es/schema-utils.js.map +1 -0
  27. package/dist/es/widgets/BindEditor.js +60 -49
  28. package/dist/es/widgets/BindEditor.js.map +1 -1
  29. package/dist/es/widgets/CodeEditor.js +41 -49
  30. package/dist/es/widgets/CodeEditor.js.map +1 -1
  31. package/dist/es/widgets/DefaultEditor.js +25 -30
  32. package/dist/es/widgets/DefaultEditor.js.map +1 -1
  33. package/dist/es/widgets/DependenciesEditor.js +21 -21
  34. package/dist/es/widgets/DependenciesEditor.js.map +1 -1
  35. package/dist/es/widgets/EnumEditor.js +25 -31
  36. package/dist/es/widgets/EnumEditor.js.map +1 -1
  37. package/dist/es/widgets/ExpressionSwitch.js +32 -30
  38. package/dist/es/widgets/ExpressionSwitch.js.map +1 -1
  39. package/dist/es/widgets/ReactionsEditor.js +103 -103
  40. package/dist/es/widgets/ReactionsEditor.js.map +1 -1
  41. package/dist/es/widgets/RemoteDataEditor.js +63 -63
  42. package/dist/es/widgets/RemoteDataEditor.js.map +1 -1
  43. package/dist/es/widgets/ValidateEditor.js +49 -49
  44. package/dist/es/widgets/ValidateEditor.js.map +1 -1
  45. package/dist/es/widgets/bind-editor.js +78 -0
  46. package/dist/es/widgets/bind-editor.js.map +1 -0
  47. package/dist/es/widgets/code-editor.js +76 -0
  48. package/dist/es/widgets/code-editor.js.map +1 -0
  49. package/dist/es/widgets/default-editor.js +50 -0
  50. package/dist/es/widgets/default-editor.js.map +1 -0
  51. package/dist/es/widgets/dependencies-editor.js +36 -0
  52. package/dist/es/widgets/dependencies-editor.js.map +1 -0
  53. package/dist/es/widgets/enum-editor.js +43 -0
  54. package/dist/es/widgets/enum-editor.js.map +1 -0
  55. package/dist/es/widgets/expression-builder.js +367 -0
  56. package/dist/es/widgets/expression-builder.js.map +1 -0
  57. package/dist/es/widgets/expression-model.js +340 -0
  58. package/dist/es/widgets/expression-model.js.map +1 -0
  59. package/dist/es/widgets/expression-switch.js +49 -0
  60. package/dist/es/widgets/expression-switch.js.map +1 -0
  61. package/dist/es/widgets/index.js +13 -13
  62. package/dist/es/widgets/index.js.map +1 -1
  63. package/dist/es/widgets/options-editor.js +102 -0
  64. package/dist/es/widgets/options-editor.js.map +1 -0
  65. package/dist/es/widgets/reactions-editor.js +401 -0
  66. package/dist/es/widgets/reactions-editor.js.map +1 -0
  67. package/dist/es/widgets/reactions-model.js +107 -0
  68. package/dist/es/widgets/reactions-model.js.map +1 -0
  69. package/dist/es/widgets/remote-data-editor.js +314 -0
  70. package/dist/es/widgets/remote-data-editor.js.map +1 -0
  71. package/dist/es/widgets/use-form-data-fields.js +16 -0
  72. package/dist/es/widgets/use-form-data-fields.js.map +1 -0
  73. package/dist/es/widgets/validate-editor.js +118 -0
  74. package/dist/es/widgets/validate-editor.js.map +1 -0
  75. package/package.json +4 -5
  76. package/dist/es/react/src/components/FormController.js +0 -160
  77. package/dist/es/react/src/components/FormController.js.map +0 -1
  78. package/dist/es/react/src/components/NexusField.js +0 -98
  79. package/dist/es/react/src/components/NexusField.js.map +0 -1
  80. package/dist/es/react/src/components/NexusForm.js +0 -128
  81. package/dist/es/react/src/components/NexusForm.js.map +0 -1
  82. package/dist/es/react/src/components/NexusFormProvider.js +0 -19
  83. package/dist/es/react/src/components/NexusFormProvider.js.map +0 -1
  84. package/dist/es/react/src/components/NexusLayout.js +0 -49
  85. package/dist/es/react/src/components/NexusLayout.js.map +0 -1
  86. package/dist/es/react/src/components/NexusObject.js +0 -65
  87. package/dist/es/react/src/components/NexusObject.js.map +0 -1
  88. package/dist/es/react/src/contexts/FieldInheritContext.js +0 -6
  89. package/dist/es/react/src/contexts/FieldInheritContext.js.map +0 -1
  90. package/dist/es/react/src/contexts/GridContext.js +0 -6
  91. package/dist/es/react/src/contexts/GridContext.js.map +0 -1
  92. package/dist/es/react/src/contexts/LayoutConfigContext.js +0 -6
  93. package/dist/es/react/src/contexts/LayoutConfigContext.js.map +0 -1
  94. package/dist/es/react/src/contexts/NexusContext.js +0 -13
  95. package/dist/es/react/src/contexts/NexusContext.js.map +0 -1
  96. package/dist/es/react/src/hooks/useForm.js +0 -13
  97. package/dist/es/react/src/hooks/useForm.js.map +0 -1
  98. package/dist/es/react/src/utils/renderTreeNode.js +0 -18
  99. package/dist/es/react/src/utils/renderTreeNode.js.map +0 -1
  100. package/dist/es/react/src/utils/resolveColSpan.js +0 -10
  101. package/dist/es/react/src/utils/resolveColSpan.js.map +0 -1
  102. package/src/Canvas.tsx +0 -1111
  103. package/src/Designer.css +0 -206
  104. package/src/Designer.tsx +0 -254
  105. package/src/DesignerContext.tsx +0 -192
  106. package/src/Palette.tsx +0 -261
  107. package/src/PropertyPanel.tsx +0 -486
  108. package/src/ResizablePanel.tsx +0 -210
  109. package/src/catalog.ts +0 -527
  110. package/src/index.ts +0 -25
  111. package/src/property/basic-property.ts +0 -126
  112. package/src/schemaUtils.test.ts +0 -261
  113. package/src/schemaUtils.ts +0 -627
  114. package/src/types.ts +0 -38
  115. package/src/vite-env.d.ts +0 -1
  116. package/src/widgets/BindEditor.tsx +0 -105
  117. package/src/widgets/CodeEditor.tsx +0 -114
  118. package/src/widgets/DefaultEditor.tsx +0 -73
  119. package/src/widgets/DependenciesEditor.tsx +0 -56
  120. package/src/widgets/EnumEditor.tsx +0 -66
  121. package/src/widgets/ExpressionBuilder.tsx +0 -509
  122. package/src/widgets/ExpressionSwitch.tsx +0 -62
  123. package/src/widgets/OptionsEditor.tsx +0 -141
  124. package/src/widgets/ReactionsEditor.tsx +0 -532
  125. package/src/widgets/RemoteDataEditor.tsx +0 -382
  126. package/src/widgets/ValidateEditor.tsx +0 -162
  127. package/src/widgets/expressionModel.test.ts +0 -262
  128. package/src/widgets/expressionModel.ts +0 -573
  129. package/src/widgets/index.ts +0 -45
  130. package/src/widgets/reactionsModel.ts +0 -190
  131. package/src/widgets/useFormDataFields.ts +0 -21
@@ -1,382 +0,0 @@
1
- // ============================================================================
2
- // RemoteDataEditor — 可视化编辑 remoteData 远程数据配置
3
- // 表单化配置代替手写 JSON:
4
- // - 接口地址 / 请求方式(GET | POST)
5
- // - 响应字段映射(数据数组路径 / 值字段 / 文案字段)
6
- // - 请求参数(key-value 行编辑)
7
- // - 请求头(key-value 行编辑,可选)
8
- // - 缓存键 / 超时(可选)
9
- // 值格式:RemoteDataConfig(见 @xbeeant/form-engine-ui 的 _shared.tsx)
10
- // ============================================================================
11
-
12
- import type { WidgetProps } from '@xbeeant/form-engine-ui';
13
- import {
14
- Button,
15
- Collapse,
16
- Form,
17
- Input,
18
- InputNumber,
19
- Select,
20
- Space,
21
- Tooltip,
22
- } from 'antd';
23
- import { useEffect, useRef, useState } from 'react';
24
- import { genId } from './reactionsModel';
25
-
26
- interface KeyValueRow {
27
- id: string;
28
- key: string;
29
- value: string;
30
- }
31
-
32
- interface RemoteDataForm {
33
- url?: string;
34
- method?: 'GET' | 'POST';
35
- dataPath?: string;
36
- valueKey?: string;
37
- labelKey?: string;
38
- params?: KeyValueRow[];
39
- headers?: KeyValueRow[];
40
- cacheKey?: string;
41
- timeout?: number;
42
- }
43
-
44
- /** Record → key-value 行 */
45
- function recordToRows(
46
- value: Record<string, unknown> | undefined,
47
- ): KeyValueRow[] {
48
- if (!value || typeof value !== 'object') {
49
- return [];
50
- }
51
- return Object.entries(value).map(([key, v]) => ({
52
- id: genId(),
53
- key,
54
- value: typeof v === 'string' ? v : JSON.stringify(v),
55
- }));
56
- }
57
-
58
- /** key-value 行 → Record(空 key 或空 value 的行丢弃) */
59
- function rowsToRecord(
60
- rows: KeyValueRow[] | undefined,
61
- ): Record<string, unknown> | undefined {
62
- if (!rows) {
63
- return undefined;
64
- }
65
- const result: Record<string, unknown> = {};
66
- for (const row of rows) {
67
- const key = row.key.trim();
68
- if (!key) {
69
- continue;
70
- }
71
- const raw = row.value.trim();
72
- if (!raw) {
73
- continue;
74
- }
75
- // 尝试解析 JSON(数字 / 布尔 / 对象),失败保留字符串
76
- try {
77
- result[key] = JSON.parse(raw);
78
- } catch {
79
- result[key] = raw;
80
- }
81
- }
82
- return Object.keys(result).length > 0 ? result : undefined;
83
- }
84
-
85
- /** RemoteDataConfig → 表单 */
86
- function configToForm(value: unknown): RemoteDataForm {
87
- const cfg = (value ?? {}) as Record<string, any>;
88
- const rf = (cfg.responseField ?? {}) as Record<string, any>;
89
- const params = cfg.params;
90
- return {
91
- url: cfg.url,
92
- method: cfg.method,
93
- dataPath: rf.data,
94
- valueKey: rf.value,
95
- labelKey: rf.label,
96
- params:
97
- params && typeof params === 'object' ? recordToRows(params) : undefined,
98
- headers: recordToRows(cfg.headers),
99
- cacheKey: cfg.cacheKey,
100
- timeout: cfg.timeout,
101
- };
102
- }
103
-
104
- /** 表单 → RemoteDataConfig */
105
- function formToConfig(
106
- form: RemoteDataForm,
107
- ): Record<string, unknown> | undefined {
108
- const url = form.url?.trim();
109
- if (!url) {
110
- return undefined;
111
- }
112
- const params = rowsToRecord(form.params);
113
- const headers = rowsToRecord(form.headers);
114
- const config: Record<string, unknown> = {
115
- url,
116
- method: form.method || 'GET',
117
- responseField: {
118
- data: form.dataPath?.trim() || 'data',
119
- value: form.valueKey?.trim() || 'value',
120
- label: form.labelKey?.trim() || 'label',
121
- },
122
- };
123
- if (params) {
124
- config.params = params;
125
- }
126
- if (headers) {
127
- config.headers = headers;
128
- }
129
- if (form.cacheKey?.trim()) {
130
- config.cacheKey = form.cacheKey.trim();
131
- }
132
- if (form.timeout != null) {
133
- config.timeout = form.timeout;
134
- }
135
- return config;
136
- }
137
-
138
- /** key-value 行编辑器(参数 / 请求头共用) */
139
- function KeyValueRows({
140
- rows,
141
- onChange,
142
- valuePlaceholder,
143
- keyPlaceholder,
144
- }: {
145
- rows: KeyValueRow[];
146
- onChange: (rows: KeyValueRow[]) => void;
147
- valuePlaceholder: string;
148
- keyPlaceholder: string;
149
- }) {
150
- return (
151
- <div style={{ width: '100%' }}>
152
- {rows.map((row) => (
153
- <div
154
- key={row.id}
155
- style={{
156
- display: 'flex',
157
- gap: 6,
158
- alignItems: 'center',
159
- marginBottom: 6,
160
- }}
161
- >
162
- <Input
163
- size='small'
164
- value={row.key}
165
- placeholder={keyPlaceholder}
166
- onChange={(e) =>
167
- onChange(
168
- rows.map((r) =>
169
- r.id === row.id ? { ...r, key: e.target.value } : r,
170
- ),
171
- )
172
- }
173
- />
174
- <Input
175
- size='small'
176
- value={row.value}
177
- placeholder={valuePlaceholder}
178
- onChange={(e) =>
179
- onChange(
180
- rows.map((r) =>
181
- r.id === row.id ? { ...r, value: e.target.value } : r,
182
- ),
183
- )
184
- }
185
- />
186
- <Button
187
- size='small'
188
- type='text'
189
- danger
190
- onClick={() => onChange(rows.filter((r) => r.id !== row.id))}
191
- >
192
-
193
- </Button>
194
- </div>
195
- ))}
196
- <Button
197
- size='small'
198
- type='dashed'
199
- block
200
- onClick={() => onChange([...rows, { id: genId(), key: '', value: '' }])}
201
- >
202
- + 添加
203
- </Button>
204
- </div>
205
- );
206
- }
207
-
208
- export function RemoteDataEditor({ value, onChange, title }: WidgetProps) {
209
- const [form, setForm] = useState<RemoteDataForm>(() => configToForm(value));
210
- const prevValueRef = useRef(value);
211
- // 自身 onChange 引发的 value 回传不应触发重建(否则每次输入都被重置)
212
- const lastEmittedRef = useRef(value);
213
-
214
- useEffect(() => {
215
- if (prevValueRef.current !== value) {
216
- prevValueRef.current = value;
217
- if (value !== lastEmittedRef.current) {
218
- setForm(configToForm(value));
219
- }
220
- }
221
- }, [value]);
222
-
223
- const commit = (next: RemoteDataForm) => {
224
- setForm(next);
225
- const emitted = formToConfig(next);
226
- lastEmittedRef.current = emitted;
227
- onChange(emitted);
228
- };
229
-
230
- const patch = (partial: Partial<RemoteDataForm>) => {
231
- commit({ ...form, ...partial });
232
- };
233
-
234
- return (
235
- <Form.Item layout={'vertical'} label={title} style={{ width: '100%' }}>
236
- <Space orientation='vertical' style={{ width: '100%' }} size={8}>
237
- {/* 请求地址 + 方式 */}
238
- <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
239
- <Input
240
- size='small'
241
- placeholder='接口地址(必填),如 /api/options'
242
- value={form.url}
243
- onChange={(e) => patch({ url: e.target.value })}
244
- style={{ flex: 1 }}
245
- />
246
- <Select
247
- size='small'
248
- style={{ width: 80 }}
249
- value={form.method}
250
- onChange={(v) => patch({ method: v })}
251
- options={[
252
- { value: 'GET', label: 'GET' },
253
- { value: 'POST', label: 'POST' },
254
- ]}
255
- />
256
- </div>
257
-
258
- {/* 响应字段映射 */}
259
- <div style={{ fontSize: 11, color: '#999' }}>响应字段映射</div>
260
- <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
261
- <Tooltip title='响应数据数组的路径,如 data.list / data.result.records'>
262
- <Input
263
- size='small'
264
- placeholder='数据路径(默认 data)'
265
- value={form.dataPath}
266
- onChange={(e) => patch({ dataPath: e.target.value })}
267
- style={{ flex: 1 }}
268
- />
269
- </Tooltip>
270
- <Tooltip title='作为选项值(value)的字段名'>
271
- <Input
272
- size='small'
273
- placeholder='值字段'
274
- value={form.valueKey}
275
- onChange={(e) => patch({ valueKey: e.target.value })}
276
- style={{ flex: 1 }}
277
- />
278
- </Tooltip>
279
- <Tooltip title='作为选项文案(label)的字段名'>
280
- <Input
281
- size='small'
282
- placeholder='文案字段'
283
- value={form.labelKey}
284
- onChange={(e) => patch({ labelKey: e.target.value })}
285
- style={{ flex: 1 }}
286
- />
287
- </Tooltip>
288
- </div>
289
-
290
- {/* 请求参数 / 请求头 / 高级 */}
291
- <Collapse
292
- size='small'
293
- style={{ width: '100%', background: '#fafafa' }}
294
- items={[
295
- {
296
- key: 'params',
297
- label: '请求参数(可选)',
298
- children: (
299
- <KeyValueRows
300
- rows={form.params ?? []}
301
- onChange={(rows) => patch({ params: rows })}
302
- keyPlaceholder='参数名'
303
- valuePlaceholder='参数值(数字/布尔自动识别)'
304
- />
305
- ),
306
- },
307
- {
308
- key: 'headers',
309
- label: '请求头(可选)',
310
- children: (
311
- <KeyValueRows
312
- rows={form.headers ?? []}
313
- onChange={(rows) => patch({ headers: rows })}
314
- keyPlaceholder='请求头名'
315
- valuePlaceholder='如 Bearer xxxx'
316
- />
317
- ),
318
- },
319
- {
320
- key: 'advanced',
321
- label: '高级',
322
- children: (
323
- <Space
324
- orientation='vertical'
325
- style={{ width: '100%' }}
326
- size={8}
327
- >
328
- <div
329
- style={{ display: 'flex', gap: 6, alignItems: 'center' }}
330
- >
331
- <span
332
- style={{
333
- fontSize: 12,
334
- color: '#666',
335
- whiteSpace: 'nowrap',
336
- }}
337
- >
338
- 缓存键
339
- </span>
340
- <Input
341
- size='small'
342
- placeholder='默认按字段路径缓存'
343
- value={form.cacheKey}
344
- onChange={(e) => patch({ cacheKey: e.target.value })}
345
- />
346
- </div>
347
- <div
348
- style={{ display: 'flex', gap: 6, alignItems: 'center' }}
349
- >
350
- <span
351
- style={{
352
- fontSize: 12,
353
- color: '#666',
354
- whiteSpace: 'nowrap',
355
- }}
356
- >
357
- 超时(秒)
358
- </span>
359
- <InputNumber
360
- size='small'
361
- min={1}
362
- value={form.timeout}
363
- onChange={(v) => patch({ timeout: v ?? undefined })}
364
- placeholder='默认 10'
365
- style={{ width: 120 }}
366
- />
367
- </div>
368
- </Space>
369
- ),
370
- },
371
- ]}
372
- />
373
-
374
- <div style={{ fontSize: 11, color: '#ccc' }}>
375
- 提示:字段值变化后选项自动刷新;POST 时参数放入请求体
376
- </div>
377
- </Space>
378
- </Form.Item>
379
- );
380
- }
381
-
382
- export const remoteDataEditorWidget = RemoteDataEditor;
@@ -1,162 +0,0 @@
1
- // ============================================================================
2
- // ValidateEditor — 可视化编辑 validate 校验规则
3
- // 每行一条规则:「规则名称 + 校验表达式」(表达式需返回 boolean)
4
- // 与 schema 的 ValidateSchema({ key: Expression })互相转换
5
- // ============================================================================
6
-
7
- import type { WidgetProps } from '@xbeeant/form-engine-ui';
8
- import { Button, Form, Input } from 'antd';
9
- import { useEffect, useRef, useState } from 'react';
10
- import { ExpressionBuilder } from './ExpressionBuilder';
11
- import { genId } from './reactionsModel';
12
- import { useFormDataFields } from './useFormDataFields';
13
-
14
- interface ValidateRow {
15
- id: string;
16
- key: string;
17
- expr: string;
18
- }
19
-
20
- /** ValidateSchema 对象 → 规则行 */
21
- function validateToRows(value: unknown): ValidateRow[] {
22
- if (!value || typeof value !== 'object') {
23
- return [];
24
- }
25
- return Object.entries(value as Record<string, unknown>).map(
26
- ([key, expr]) => ({ id: genId(), key, expr: String(expr ?? '') }),
27
- );
28
- }
29
-
30
- /** 规则行 → ValidateSchema 对象(空表达式 / 空 key 的行丢弃) */
31
- function rowsToValidate(
32
- rows: ValidateRow[],
33
- ): Record<string, string> | undefined {
34
- const result: Record<string, string> = {};
35
- for (const row of rows) {
36
- const key = row.key.trim();
37
- const expr = row.expr.trim();
38
- if (!key || !expr || expr === '{{ }}') {
39
- continue;
40
- }
41
- result[key] = expr;
42
- }
43
- return Object.keys(result).length > 0 ? result : undefined;
44
- }
45
-
46
- export function ValidateEditor({ value, onChange, title }: WidgetProps) {
47
- const fields = useFormDataFields();
48
- const [rows, setRows] = useState<ValidateRow[]>(() => validateToRows(value));
49
- const prevValueRef = useRef(value);
50
- // 自身 onChange 引发的 value 回传不应触发重建(否则每次输入都被重置)
51
- const lastEmittedRef = useRef(value);
52
-
53
- useEffect(() => {
54
- if (prevValueRef.current !== value) {
55
- prevValueRef.current = value;
56
- if (value !== lastEmittedRef.current) {
57
- setRows(validateToRows(value));
58
- }
59
- }
60
- }, [value]);
61
-
62
- const commit = (next: ValidateRow[]) => {
63
- setRows(next);
64
- const emitted = rowsToValidate(next);
65
- lastEmittedRef.current = emitted;
66
- onChange(emitted);
67
- };
68
-
69
- const updateRow = (id: string, patch: Partial<ValidateRow>) => {
70
- commit(rows.map((r) => (r.id === id ? { ...r, ...patch } : r)));
71
- };
72
-
73
- const removeRow = (id: string) => {
74
- commit(rows.filter((r) => r.id !== id));
75
- };
76
-
77
- const addRow = () => {
78
- commit([...rows, { id: genId(), key: '', expr: '{{ }}' }]);
79
- };
80
-
81
- return (
82
- <Form.Item layout={'vertical'} label={title} style={{ width: '100%' }}>
83
- {rows.length === 0 ? (
84
- <div
85
- style={{
86
- fontSize: 11,
87
- color: '#999',
88
- background: '#fafafa',
89
- border: '1px dashed #e0e0e0',
90
- borderRadius: 6,
91
- padding: '6px 8px',
92
- marginBottom: 4,
93
- }}
94
- >
95
- 点击下方「添加校验规则」,组合 校验名称 + 表达式(变量 / 操作符 /
96
- 值)。表达式需返回 boolean,可用变量:$self.value、formData。
97
- </div>
98
- ) : (
99
- rows.map((row, i) => (
100
- <div
101
- key={row.id}
102
- style={{
103
- border: '1px solid #e8e8e8',
104
- borderRadius: 8,
105
- padding: 8,
106
- marginBottom: 8,
107
- background: '#fff',
108
- }}
109
- >
110
- <div
111
- style={{
112
- display: 'flex',
113
- alignItems: 'center',
114
- justifyContent: 'space-between',
115
- marginBottom: 6,
116
- }}
117
- >
118
- <span style={{ fontSize: 12, fontWeight: 600, color: '#333' }}>
119
- 校验规则 {i + 1}
120
- </span>
121
- <Button size='small' danger onClick={() => removeRow(row.id)}>
122
- 删除
123
- </Button>
124
- </div>
125
- <div style={{ marginBottom: 6 }}>
126
- <div style={{ fontSize: 11, color: '#666', marginBottom: 2 }}>
127
- 规则名称(key)
128
- </div>
129
- <Input
130
- size='small'
131
- placeholder='如 match / len'
132
- value={row.key}
133
- onChange={(e) => updateRow(row.id, { key: e.target.value })}
134
- />
135
- </div>
136
- <div>
137
- <div style={{ fontSize: 11, color: '#666', marginBottom: 2 }}>
138
- 校验表达式(需返回 boolean)
139
- </div>
140
- <ExpressionBuilder
141
- value={row.expr}
142
- onChange={(s) => updateRow(row.id, { expr: s })}
143
- fields={fields}
144
- showSelf
145
- placeholder='{{ $self.value.length >= 6 }}'
146
- />
147
- </div>
148
- </div>
149
- ))
150
- )}
151
-
152
- <Button size='small' type='dashed' block onClick={addRow}>
153
- + 添加校验规则
154
- </Button>
155
- <div style={{ fontSize: 11, color: '#999', marginTop: 4 }}>
156
- 提示:依赖字段(formData.xxx)变化时会自动联动重校验。
157
- </div>
158
- </Form.Item>
159
- );
160
- }
161
-
162
- export const validateEditorWidget = ValidateEditor;