@nocobase/client-v2 2.1.0-beta.30 → 2.1.0-beta.32
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/es/BaseApplication.d.ts +1 -0
- package/es/PluginManager.d.ts +1 -0
- package/es/components/form/DrawerFormLayout.d.ts +49 -0
- package/es/components/form/EnvVariableInput.d.ts +42 -0
- package/es/components/form/FileSizeInput.d.ts +27 -0
- package/es/components/form/createFormRegistry.d.ts +33 -0
- package/es/components/form/index.d.ts +13 -0
- package/es/components/index.d.ts +1 -1
- package/es/flow/components/FieldAssignRulesEditor.d.ts +1 -0
- package/es/flow/internal/utils/enumOptionsUtils.d.ts +5 -0
- package/es/flow/models/actions/AssociationActionUtils.d.ts +5 -0
- package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +4 -0
- package/es/flow/models/blocks/filter-manager/FilterManager.d.ts +5 -1
- package/es/flow/models/blocks/table/TableSelectModel.d.ts +8 -0
- package/es/flow/models/fields/DisplayTitleFieldModel.d.ts +1 -1
- package/es/flow/models/utils/displayValueUtils.d.ts +12 -0
- package/es/flow-compat/passwordUtils.d.ts +1 -1
- package/es/index.mjs +87 -76
- package/es/utils/remotePlugins.d.ts +0 -4
- package/lib/index.js +97 -86
- package/package.json +6 -5
- package/src/BaseApplication.tsx +14 -8
- package/src/PluginManager.ts +1 -0
- package/src/__tests__/app.test.tsx +28 -1
- package/src/__tests__/remotePlugins.test.ts +29 -18
- package/src/components/form/DrawerFormLayout.tsx +103 -0
- package/src/components/form/EnvVariableInput.tsx +126 -0
- package/src/components/form/FileSizeInput.tsx +105 -0
- package/src/components/form/createFormRegistry.ts +60 -0
- package/src/components/form/index.tsx +14 -0
- package/src/components/index.ts +1 -1
- package/src/flow/actions/__tests__/dataScopeFilter.test.ts +92 -13
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +476 -1
- package/src/flow/actions/linkageRules.tsx +240 -258
- package/src/flow/actions/setTargetDataScope.tsx +32 -3
- package/src/flow/components/FieldAssignRulesEditor.tsx +2 -0
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +81 -4
- package/src/flow/components/filter/LinkageFilterItem.tsx +9 -2
- package/src/flow/components/filter/VariableFilterItem.tsx +2 -6
- package/src/flow/components/filter/__tests__/LinkageFilterItem.test.tsx +71 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +48 -0
- package/src/flow/internal/utils/__tests__/enumOptionsUtils.test.ts +10 -1
- package/src/flow/internal/utils/enumOptionsUtils.ts +29 -0
- package/src/flow/models/actions/AssociateActionModel.tsx +2 -2
- package/src/flow/models/actions/AssociationActionUtils.ts +14 -0
- package/src/flow/models/actions/__tests__/AssociationActionModel.test.ts +63 -0
- package/src/flow/models/base/CollectionBlockModel.tsx +7 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +33 -9
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +53 -13
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.getFilterValue.test.ts +63 -3
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +33 -1
- package/src/flow/models/blocks/filter-manager/FilterManager.ts +66 -2
- package/src/flow/models/blocks/filter-manager/__tests__/FilterManager.test.ts +270 -0
- package/src/flow/models/blocks/form/FormBlockModel.tsx +8 -5
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +30 -0
- package/src/flow/models/blocks/form/value-runtime/rules.ts +6 -1
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +6 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +11 -6
- package/src/flow/models/blocks/table/TableColumnModel.tsx +3 -0
- package/src/flow/models/blocks/table/TableSelectModel.tsx +36 -26
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowClick.test.ts +69 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +96 -1
- package/src/flow/models/blocks/table/__tests__/TableSelectModel.test.ts +41 -0
- package/src/flow/models/fields/ClickableFieldModel.tsx +9 -4
- package/src/flow/models/fields/DisplayTitleFieldModel.tsx +12 -4
- package/src/flow/models/fields/SelectFieldModel.tsx +31 -1
- package/src/flow/models/fields/__tests__/ClickableFieldModel.test.ts +23 -0
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +2 -1
- package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +7 -0
- package/src/flow/models/utils/displayValueUtils.ts +57 -0
- package/src/utils/globalDeps.ts +2 -0
- package/src/utils/remotePlugins.ts +7 -27
|
@@ -46,6 +46,7 @@ import { useAssociationTitleFieldSync } from '../components/useAssociationTitleF
|
|
|
46
46
|
import _ from 'lodash';
|
|
47
47
|
import { SubFormFieldModel, SubFormListFieldModel } from '../models';
|
|
48
48
|
import { coerceForToOneField } from '../internal/utils/associationValueCoercion';
|
|
49
|
+
import { enumToOptions } from '../internal/utils/enumOptionsUtils';
|
|
49
50
|
import {
|
|
50
51
|
findFormItemModelByFieldPath,
|
|
51
52
|
getCollectionFromModel,
|
|
@@ -120,6 +121,214 @@ const getFormFields = (ctx: any) => {
|
|
|
120
121
|
}
|
|
121
122
|
};
|
|
122
123
|
|
|
124
|
+
const cloneOptions = (options: any[]) =>
|
|
125
|
+
options.map((option) => (option && typeof option === 'object' ? { ...option } : option));
|
|
126
|
+
|
|
127
|
+
const LIMIT_OPTIONS_INTERFACES = new Set(['select', 'multipleSelect', 'radioGroup', 'checkboxGroup']);
|
|
128
|
+
|
|
129
|
+
const getFieldInterface = (fieldModel: any) => {
|
|
130
|
+
return fieldModel?.collectionField?.interface || fieldModel?.subModels?.field?.context?.collectionField?.interface;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const supportsLimitOptions = (fieldModel: any) => {
|
|
134
|
+
return LIMIT_OPTIONS_INTERFACES.has(getFieldInterface(fieldModel));
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const getOriginalFieldOptions = (fieldModel: any) => {
|
|
138
|
+
const field = fieldModel?.subModels?.field || fieldModel;
|
|
139
|
+
const enumOptions = enumToOptions(fieldModel?.collectionField?.uiSchema?.enum, (text) => text) || [];
|
|
140
|
+
if (enumOptions.length > 0) {
|
|
141
|
+
field._originalOptionsFallback = cloneOptions(enumOptions);
|
|
142
|
+
return field._originalOptionsFallback;
|
|
143
|
+
}
|
|
144
|
+
if (Array.isArray(field?._originalOptionsFallback) && field._originalOptionsFallback.length > 0) {
|
|
145
|
+
return field._originalOptionsFallback;
|
|
146
|
+
}
|
|
147
|
+
if (Array.isArray(field?.props?.options) && field.props.options.length > 0) {
|
|
148
|
+
field._originalOptionsFallback = cloneOptions(field.props.options);
|
|
149
|
+
return field._originalOptionsFallback;
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const getFieldModelOptions = (fieldModel: any, t: (s: string) => string) => {
|
|
155
|
+
const originalOptions = getOriginalFieldOptions(fieldModel);
|
|
156
|
+
if (originalOptions) {
|
|
157
|
+
return originalOptions.map((option: any) =>
|
|
158
|
+
option && typeof option === 'object' && typeof option.label === 'string'
|
|
159
|
+
? { ...option, label: t(option.label) }
|
|
160
|
+
: option,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
return [];
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const getFieldOptionsBySelectedFields = (fieldOptions: any[], fields: string[] = [], t: (s: string) => string) => {
|
|
167
|
+
const selected = fields
|
|
168
|
+
.map((fieldUid) => fieldOptions.find((option) => option.value === fieldUid)?.model)
|
|
169
|
+
.filter((model) => supportsLimitOptions(model))
|
|
170
|
+
.filter(Boolean);
|
|
171
|
+
|
|
172
|
+
const options = selected.flatMap((model) => getFieldModelOptions(model, t));
|
|
173
|
+
const deduped = new Map<string, any>();
|
|
174
|
+
options.forEach((option) => {
|
|
175
|
+
deduped.set(JSON.stringify(option?.value), option);
|
|
176
|
+
});
|
|
177
|
+
return Array.from(deduped.values());
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const getFieldStateProps = (state: string, selectedOptions?: any[]) => {
|
|
181
|
+
switch (state) {
|
|
182
|
+
case 'visible':
|
|
183
|
+
return { hiddenModel: false };
|
|
184
|
+
case 'hidden':
|
|
185
|
+
return { hiddenModel: true };
|
|
186
|
+
case 'hiddenReservedValue':
|
|
187
|
+
return { hidden: true };
|
|
188
|
+
case 'required':
|
|
189
|
+
return { required: true };
|
|
190
|
+
case 'notRequired':
|
|
191
|
+
return { required: false };
|
|
192
|
+
case 'disabled':
|
|
193
|
+
return { disabled: true };
|
|
194
|
+
case 'enabled':
|
|
195
|
+
return { disabled: false };
|
|
196
|
+
case 'limitOptions':
|
|
197
|
+
return Array.isArray(selectedOptions) ? { options: selectedOptions } : {};
|
|
198
|
+
default:
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const getFieldStateTargetModel = (state: string, model: any) => {
|
|
204
|
+
return state === 'limitOptions' ? model?.subModels?.field || model : model;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const isFieldOptionsPatch = (props: any) => {
|
|
208
|
+
return props && typeof props === 'object' && Object.keys(props).length === 1 && Array.isArray(props.options);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const syncFieldOptionsToForks = (model: any, props: any) => {
|
|
212
|
+
if (!isFieldOptionsPatch(props) || !model?.forks || typeof model.forks.forEach !== 'function') return;
|
|
213
|
+
model.forks.forEach((fork: any) => {
|
|
214
|
+
fork?.setProps?.({ options: cloneOptions(props.options) });
|
|
215
|
+
});
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
type FieldStateEditorValue = {
|
|
219
|
+
fields: string[];
|
|
220
|
+
state?: string;
|
|
221
|
+
selectedOptions?: any[];
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const FieldStateEditor = ({
|
|
225
|
+
value = { fields: [] } as FieldStateEditorValue,
|
|
226
|
+
onChange,
|
|
227
|
+
includeFormStates = true,
|
|
228
|
+
fieldOptionsGetter = getFormFields,
|
|
229
|
+
}) => {
|
|
230
|
+
const ctx = useFlowContext();
|
|
231
|
+
const t = ctx.model.translate.bind(ctx.model);
|
|
232
|
+
|
|
233
|
+
const fieldOptions = fieldOptionsGetter(ctx);
|
|
234
|
+
const selectedFieldModels = (value.fields || [])
|
|
235
|
+
.map((fieldUid) => fieldOptions.find((option) => option.value === fieldUid)?.model)
|
|
236
|
+
.filter(Boolean);
|
|
237
|
+
const canConfigureLimitOptions =
|
|
238
|
+
selectedFieldModels.length === 1 && selectedFieldModels.every((model: any) => supportsLimitOptions(model));
|
|
239
|
+
const stateOptions = [
|
|
240
|
+
{ label: t('Visible'), value: 'visible' },
|
|
241
|
+
{ label: t('Hidden'), value: 'hidden' },
|
|
242
|
+
{ label: t('Hidden (reserved value)'), value: 'hiddenReservedValue' },
|
|
243
|
+
includeFormStates && { label: t('Required'), value: 'required' },
|
|
244
|
+
includeFormStates && { label: t('Not required'), value: 'notRequired' },
|
|
245
|
+
includeFormStates && { label: t('Disabled'), value: 'disabled' },
|
|
246
|
+
includeFormStates && { label: t('Enabled'), value: 'enabled' },
|
|
247
|
+
includeFormStates && canConfigureLimitOptions && { label: t('Options'), value: 'limitOptions' },
|
|
248
|
+
].filter(Boolean);
|
|
249
|
+
const selectableOptions = getFieldOptionsBySelectedFields(fieldOptions, value.fields, t);
|
|
250
|
+
|
|
251
|
+
const handleFieldsChange = (selectedFields: string[]) => {
|
|
252
|
+
const nextSelectedFieldModels = selectedFields
|
|
253
|
+
.map((fieldUid) => fieldOptions.find((option) => option.value === fieldUid)?.model)
|
|
254
|
+
.filter(Boolean);
|
|
255
|
+
const nextCanConfigureLimitOptions =
|
|
256
|
+
nextSelectedFieldModels.length === 1 &&
|
|
257
|
+
nextSelectedFieldModels.every((model: any) => supportsLimitOptions(model));
|
|
258
|
+
const nextState = value.state === 'limitOptions' && !nextCanConfigureLimitOptions ? undefined : value.state;
|
|
259
|
+
const nextSelectedOptions =
|
|
260
|
+
value.state === 'limitOptions' || nextState === 'limitOptions' ? [] : value.selectedOptions;
|
|
261
|
+
onChange({
|
|
262
|
+
...value,
|
|
263
|
+
fields: selectedFields,
|
|
264
|
+
state: nextState,
|
|
265
|
+
selectedOptions: nextSelectedOptions,
|
|
266
|
+
});
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const handleStateChange = (selectedState: string) => {
|
|
270
|
+
onChange({
|
|
271
|
+
...value,
|
|
272
|
+
state: selectedState,
|
|
273
|
+
selectedOptions: selectedState === 'limitOptions' ? value.selectedOptions || [] : undefined,
|
|
274
|
+
});
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const handleSelectedOptionsChange = (selectedValues: any[]) => {
|
|
278
|
+
onChange({
|
|
279
|
+
...value,
|
|
280
|
+
selectedOptions: selectableOptions.filter((option) => selectedValues.includes(option.value)),
|
|
281
|
+
});
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
286
|
+
<div>
|
|
287
|
+
<div style={{ marginBottom: '4px', fontSize: '14px' }}>{t('Fields')}</div>
|
|
288
|
+
<Select
|
|
289
|
+
mode="multiple"
|
|
290
|
+
value={value.fields}
|
|
291
|
+
onChange={handleFieldsChange}
|
|
292
|
+
placeholder={t('Please select fields')}
|
|
293
|
+
style={{ width: '100%' }}
|
|
294
|
+
options={fieldOptions}
|
|
295
|
+
showSearch
|
|
296
|
+
// @ts-ignore
|
|
297
|
+
filterOption={(input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
|
|
298
|
+
allowClear
|
|
299
|
+
/>
|
|
300
|
+
</div>
|
|
301
|
+
<div>
|
|
302
|
+
<div style={{ marginBottom: '4px', fontSize: '14px' }}>{t('State')}</div>
|
|
303
|
+
<Select
|
|
304
|
+
value={value.state}
|
|
305
|
+
onChange={handleStateChange}
|
|
306
|
+
placeholder={t('Please select state')}
|
|
307
|
+
style={{ width: '100%' }}
|
|
308
|
+
options={stateOptions}
|
|
309
|
+
allowClear
|
|
310
|
+
/>
|
|
311
|
+
</div>
|
|
312
|
+
{value.state === 'limitOptions' && canConfigureLimitOptions && (
|
|
313
|
+
<div>
|
|
314
|
+
<div style={{ marginBottom: '4px', fontSize: '14px' }}>{t('Options')}</div>
|
|
315
|
+
<Select
|
|
316
|
+
mode="multiple"
|
|
317
|
+
value={(value.selectedOptions || []).map((option: any) => option.value)}
|
|
318
|
+
onChange={handleSelectedOptionsChange}
|
|
319
|
+
style={{ width: '100%' }}
|
|
320
|
+
options={selectableOptions}
|
|
321
|
+
showSearch
|
|
322
|
+
// @ts-ignore
|
|
323
|
+
filterOption={(input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
|
|
324
|
+
allowClear
|
|
325
|
+
/>
|
|
326
|
+
</div>
|
|
327
|
+
)}
|
|
328
|
+
</div>
|
|
329
|
+
);
|
|
330
|
+
};
|
|
331
|
+
|
|
123
332
|
const getFormFieldsByForkModel = (ctx: any) => {
|
|
124
333
|
try {
|
|
125
334
|
const fieldModels = ctx.model?.subModels?.grid?.subModels?.items || [];
|
|
@@ -523,68 +732,7 @@ export const linkageSetFieldProps = defineAction({
|
|
|
523
732
|
value: {
|
|
524
733
|
type: 'object',
|
|
525
734
|
'x-component': (props) => {
|
|
526
|
-
|
|
527
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
528
|
-
const ctx = useFlowContext();
|
|
529
|
-
const t = ctx.model.translate.bind(ctx.model);
|
|
530
|
-
|
|
531
|
-
const fieldOptions = getFormFields(ctx);
|
|
532
|
-
|
|
533
|
-
// 状态选项
|
|
534
|
-
const stateOptions = [
|
|
535
|
-
{ label: t('Visible'), value: 'visible' },
|
|
536
|
-
{ label: t('Hidden'), value: 'hidden' },
|
|
537
|
-
{ label: t('Hidden (reserved value)'), value: 'hiddenReservedValue' },
|
|
538
|
-
{ label: t('Required'), value: 'required' },
|
|
539
|
-
{ label: t('Not required'), value: 'notRequired' },
|
|
540
|
-
{ label: t('Disabled'), value: 'disabled' },
|
|
541
|
-
{ label: t('Enabled'), value: 'enabled' },
|
|
542
|
-
];
|
|
543
|
-
|
|
544
|
-
const handleFieldsChange = (selectedFields: string[]) => {
|
|
545
|
-
onChange({
|
|
546
|
-
...value,
|
|
547
|
-
fields: selectedFields,
|
|
548
|
-
});
|
|
549
|
-
};
|
|
550
|
-
|
|
551
|
-
const handleStateChange = (selectedState: string) => {
|
|
552
|
-
onChange({
|
|
553
|
-
...value,
|
|
554
|
-
state: selectedState,
|
|
555
|
-
});
|
|
556
|
-
};
|
|
557
|
-
|
|
558
|
-
return (
|
|
559
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
560
|
-
<div>
|
|
561
|
-
<div style={{ marginBottom: '4px', fontSize: '14px' }}>{t('Fields')}</div>
|
|
562
|
-
<Select
|
|
563
|
-
mode="multiple"
|
|
564
|
-
value={value.fields}
|
|
565
|
-
onChange={handleFieldsChange}
|
|
566
|
-
placeholder={t('Please select fields')}
|
|
567
|
-
style={{ width: '100%' }}
|
|
568
|
-
options={fieldOptions}
|
|
569
|
-
showSearch
|
|
570
|
-
// @ts-ignore
|
|
571
|
-
filterOption={(input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
|
|
572
|
-
allowClear
|
|
573
|
-
/>
|
|
574
|
-
</div>
|
|
575
|
-
<div>
|
|
576
|
-
<div style={{ marginBottom: '4px', fontSize: '14px' }}>{t('State')}</div>
|
|
577
|
-
<Select
|
|
578
|
-
value={value.state}
|
|
579
|
-
onChange={handleStateChange}
|
|
580
|
-
placeholder={t('Please select state')}
|
|
581
|
-
style={{ width: '100%' }}
|
|
582
|
-
options={stateOptions}
|
|
583
|
-
allowClear
|
|
584
|
-
/>
|
|
585
|
-
</div>
|
|
586
|
-
</div>
|
|
587
|
-
);
|
|
735
|
+
return <FieldStateEditor {...props} />;
|
|
588
736
|
},
|
|
589
737
|
},
|
|
590
738
|
},
|
|
@@ -602,36 +750,13 @@ export const linkageSetFieldProps = defineAction({
|
|
|
602
750
|
const fieldModel = gridModels.find((model: any) => model.uid === fieldUid);
|
|
603
751
|
|
|
604
752
|
if (fieldModel) {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
props = { hiddenModel: false };
|
|
610
|
-
break;
|
|
611
|
-
case 'hidden':
|
|
612
|
-
props = { hiddenModel: true };
|
|
613
|
-
break;
|
|
614
|
-
case 'hiddenReservedValue':
|
|
615
|
-
props = { hidden: true };
|
|
616
|
-
break;
|
|
617
|
-
case 'required':
|
|
618
|
-
props = { required: true };
|
|
619
|
-
break;
|
|
620
|
-
case 'notRequired':
|
|
621
|
-
props = { required: false };
|
|
622
|
-
break;
|
|
623
|
-
case 'disabled':
|
|
624
|
-
props = { disabled: true };
|
|
625
|
-
break;
|
|
626
|
-
case 'enabled':
|
|
627
|
-
props = { disabled: false };
|
|
628
|
-
break;
|
|
629
|
-
default:
|
|
630
|
-
console.warn(`Unknown state: ${state}`);
|
|
631
|
-
return;
|
|
753
|
+
const props = getFieldStateProps(state, value?.selectedOptions);
|
|
754
|
+
if (!props) {
|
|
755
|
+
console.warn(`Unknown state: ${state}`);
|
|
756
|
+
return;
|
|
632
757
|
}
|
|
633
758
|
|
|
634
|
-
setProps(fieldModel as FlowModel, props);
|
|
759
|
+
setProps(getFieldStateTargetModel(state, fieldModel) as FlowModel, props);
|
|
635
760
|
}
|
|
636
761
|
} catch (error) {
|
|
637
762
|
console.warn(`Failed to set props for field ${fieldUid}:`, error);
|
|
@@ -649,68 +774,7 @@ export const subFormLinkageSetFieldProps = defineAction({
|
|
|
649
774
|
value: {
|
|
650
775
|
type: 'object',
|
|
651
776
|
'x-component': (props) => {
|
|
652
|
-
|
|
653
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
654
|
-
const ctx = useFlowContext();
|
|
655
|
-
const t = ctx.model.translate.bind(ctx.model);
|
|
656
|
-
|
|
657
|
-
const fieldOptions = getFormFieldsByForkModel(ctx);
|
|
658
|
-
|
|
659
|
-
// 状态选项
|
|
660
|
-
const stateOptions = [
|
|
661
|
-
{ label: t('Visible'), value: 'visible' },
|
|
662
|
-
{ label: t('Hidden'), value: 'hidden' },
|
|
663
|
-
{ label: t('Hidden (reserved value)'), value: 'hiddenReservedValue' },
|
|
664
|
-
{ label: t('Required'), value: 'required' },
|
|
665
|
-
{ label: t('Not required'), value: 'notRequired' },
|
|
666
|
-
{ label: t('Disabled'), value: 'disabled' },
|
|
667
|
-
{ label: t('Enabled'), value: 'enabled' },
|
|
668
|
-
];
|
|
669
|
-
|
|
670
|
-
const handleFieldsChange = (selectedFields: string[]) => {
|
|
671
|
-
onChange({
|
|
672
|
-
...value,
|
|
673
|
-
fields: selectedFields,
|
|
674
|
-
});
|
|
675
|
-
};
|
|
676
|
-
|
|
677
|
-
const handleStateChange = (selectedState: string) => {
|
|
678
|
-
onChange({
|
|
679
|
-
...value,
|
|
680
|
-
state: selectedState,
|
|
681
|
-
});
|
|
682
|
-
};
|
|
683
|
-
|
|
684
|
-
return (
|
|
685
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
686
|
-
<div>
|
|
687
|
-
<div style={{ marginBottom: '4px', fontSize: '14px' }}>{t('Fields')}</div>
|
|
688
|
-
<Select
|
|
689
|
-
mode="multiple"
|
|
690
|
-
value={value.fields}
|
|
691
|
-
onChange={handleFieldsChange}
|
|
692
|
-
placeholder={t('Please select fields')}
|
|
693
|
-
style={{ width: '100%' }}
|
|
694
|
-
options={fieldOptions}
|
|
695
|
-
showSearch
|
|
696
|
-
// @ts-ignore
|
|
697
|
-
filterOption={(input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
|
|
698
|
-
allowClear
|
|
699
|
-
/>
|
|
700
|
-
</div>
|
|
701
|
-
<div>
|
|
702
|
-
<div style={{ marginBottom: '4px', fontSize: '14px' }}>{t('State')}</div>
|
|
703
|
-
<Select
|
|
704
|
-
value={value.state}
|
|
705
|
-
onChange={handleStateChange}
|
|
706
|
-
placeholder={t('Please select state')}
|
|
707
|
-
style={{ width: '100%' }}
|
|
708
|
-
options={stateOptions}
|
|
709
|
-
allowClear
|
|
710
|
-
/>
|
|
711
|
-
</div>
|
|
712
|
-
</div>
|
|
713
|
-
);
|
|
777
|
+
return <FieldStateEditor {...props} fieldOptionsGetter={getFormFieldsByForkModel} />;
|
|
714
778
|
},
|
|
715
779
|
},
|
|
716
780
|
},
|
|
@@ -748,36 +812,13 @@ export const subFormLinkageSetFieldProps = defineAction({
|
|
|
748
812
|
}
|
|
749
813
|
|
|
750
814
|
if (model) {
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
props = { hiddenModel: false };
|
|
756
|
-
break;
|
|
757
|
-
case 'hidden':
|
|
758
|
-
props = { hiddenModel: true };
|
|
759
|
-
break;
|
|
760
|
-
case 'hiddenReservedValue':
|
|
761
|
-
props = { hidden: true };
|
|
762
|
-
break;
|
|
763
|
-
case 'required':
|
|
764
|
-
props = { required: true };
|
|
765
|
-
break;
|
|
766
|
-
case 'notRequired':
|
|
767
|
-
props = { required: false };
|
|
768
|
-
break;
|
|
769
|
-
case 'disabled':
|
|
770
|
-
props = { disabled: true };
|
|
771
|
-
break;
|
|
772
|
-
case 'enabled':
|
|
773
|
-
props = { disabled: false };
|
|
774
|
-
break;
|
|
775
|
-
default:
|
|
776
|
-
console.warn(`Unknown state: ${state}`);
|
|
777
|
-
return;
|
|
815
|
+
const props = getFieldStateProps(state, value?.selectedOptions);
|
|
816
|
+
if (!props) {
|
|
817
|
+
console.warn(`Unknown state: ${state}`);
|
|
818
|
+
return;
|
|
778
819
|
}
|
|
779
820
|
|
|
780
|
-
setProps(model as FlowModel, props);
|
|
821
|
+
setProps(getFieldStateTargetModel(state, model) as FlowModel, props);
|
|
781
822
|
}
|
|
782
823
|
} catch (error) {
|
|
783
824
|
console.warn(`Failed to set props for field ${fieldUid}:`, error);
|
|
@@ -795,64 +836,7 @@ export const linkageSetDetailsFieldProps = defineAction({
|
|
|
795
836
|
value: {
|
|
796
837
|
type: 'object',
|
|
797
838
|
'x-component': (props) => {
|
|
798
|
-
|
|
799
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
800
|
-
const ctx = useFlowContext();
|
|
801
|
-
const t = ctx.model.translate.bind(ctx.model);
|
|
802
|
-
|
|
803
|
-
const fieldOptions = getFormFields(ctx);
|
|
804
|
-
|
|
805
|
-
// 状态选项
|
|
806
|
-
const stateOptions = [
|
|
807
|
-
{ label: t('Visible'), value: 'visible' },
|
|
808
|
-
{ label: t('Hidden'), value: 'hidden' },
|
|
809
|
-
{ label: t('Hidden (reserved value)'), value: 'hiddenReservedValue' },
|
|
810
|
-
];
|
|
811
|
-
|
|
812
|
-
const handleFieldsChange = (selectedFields: string[]) => {
|
|
813
|
-
onChange({
|
|
814
|
-
...value,
|
|
815
|
-
fields: selectedFields,
|
|
816
|
-
});
|
|
817
|
-
};
|
|
818
|
-
|
|
819
|
-
const handleStateChange = (selectedState: string) => {
|
|
820
|
-
onChange({
|
|
821
|
-
...value,
|
|
822
|
-
state: selectedState,
|
|
823
|
-
});
|
|
824
|
-
};
|
|
825
|
-
|
|
826
|
-
return (
|
|
827
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
828
|
-
<div>
|
|
829
|
-
<div style={{ marginBottom: '4px', fontSize: '14px' }}>{t('Fields')}</div>
|
|
830
|
-
<Select
|
|
831
|
-
mode="multiple"
|
|
832
|
-
value={value.fields}
|
|
833
|
-
onChange={handleFieldsChange}
|
|
834
|
-
placeholder={t('Please select fields')}
|
|
835
|
-
style={{ width: '100%' }}
|
|
836
|
-
options={fieldOptions}
|
|
837
|
-
showSearch
|
|
838
|
-
// @ts-ignore
|
|
839
|
-
filterOption={(input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
|
|
840
|
-
allowClear
|
|
841
|
-
/>
|
|
842
|
-
</div>
|
|
843
|
-
<div>
|
|
844
|
-
<div style={{ marginBottom: '4px', fontSize: '14px' }}>{t('State')}</div>
|
|
845
|
-
<Select
|
|
846
|
-
value={value.state}
|
|
847
|
-
onChange={handleStateChange}
|
|
848
|
-
placeholder={t('Please select state')}
|
|
849
|
-
style={{ width: '100%' }}
|
|
850
|
-
options={stateOptions}
|
|
851
|
-
allowClear
|
|
852
|
-
/>
|
|
853
|
-
</div>
|
|
854
|
-
</div>
|
|
855
|
-
);
|
|
839
|
+
return <FieldStateEditor {...props} includeFormStates={false} />;
|
|
856
840
|
},
|
|
857
841
|
},
|
|
858
842
|
},
|
|
@@ -870,24 +854,13 @@ export const linkageSetDetailsFieldProps = defineAction({
|
|
|
870
854
|
const fieldModel = gridModels.find((model: any) => model.uid === fieldUid);
|
|
871
855
|
|
|
872
856
|
if (fieldModel) {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
props = { hiddenModel: false };
|
|
878
|
-
break;
|
|
879
|
-
case 'hidden':
|
|
880
|
-
props = { hiddenModel: true };
|
|
881
|
-
break;
|
|
882
|
-
case 'hiddenReservedValue':
|
|
883
|
-
props = { hidden: true };
|
|
884
|
-
break;
|
|
885
|
-
default:
|
|
886
|
-
console.warn(`Unknown state: ${state}`);
|
|
887
|
-
return;
|
|
857
|
+
const props = getFieldStateProps(state);
|
|
858
|
+
if (!props) {
|
|
859
|
+
console.warn(`Unknown state: ${state}`);
|
|
860
|
+
return;
|
|
888
861
|
}
|
|
889
862
|
|
|
890
|
-
setProps(fieldModel as FlowModel, props);
|
|
863
|
+
setProps(getFieldStateTargetModel(state, fieldModel) as FlowModel, props);
|
|
891
864
|
}
|
|
892
865
|
} catch (error) {
|
|
893
866
|
console.warn(`Failed to set props for field ${fieldUid}:`, error);
|
|
@@ -1994,9 +1967,15 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1994
1967
|
mergedPropsByUid.set(uid, { ...curProps });
|
|
1995
1968
|
} else {
|
|
1996
1969
|
// 合并属性:后写覆盖先写;优先选择 fork 模型作为应用目标
|
|
1997
|
-
|
|
1970
|
+
const prevProps = mergedPropsByUid.get(uid) || {};
|
|
1971
|
+
const nextProps = { ...prevProps, ...curProps };
|
|
1972
|
+
mergedPropsByUid.set(uid, nextProps);
|
|
1998
1973
|
const exist = mergedByUid.get(uid) as any;
|
|
1999
|
-
|
|
1974
|
+
const hasCurrentPatch = Object.keys(curProps).length > 0;
|
|
1975
|
+
const hasExistingPatch = Object.keys(prevProps).length > 0;
|
|
1976
|
+
if (hasCurrentPatch && (!hasExistingPatch || !m.isFork || !exist.isFork)) {
|
|
1977
|
+
mergedByUid.set(uid, m);
|
|
1978
|
+
} else if (m.isFork && !exist.isFork) {
|
|
2000
1979
|
mergedByUid.set(uid, m);
|
|
2001
1980
|
}
|
|
2002
1981
|
}
|
|
@@ -2007,6 +1986,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
2007
1986
|
const newProps = { ...model.__originalProps, ...patchProps };
|
|
2008
1987
|
|
|
2009
1988
|
model.setProps(_.omit(newProps, ['hiddenModel', 'value', 'hiddenText']));
|
|
1989
|
+
syncFieldOptionsToForks(model, patchProps);
|
|
2010
1990
|
if (typeof model.setHidden === 'function') {
|
|
2011
1991
|
model.setHidden(!!newProps.hiddenModel);
|
|
2012
1992
|
} else {
|
|
@@ -2029,8 +2009,10 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
2029
2009
|
model.setProps('title', '');
|
|
2030
2010
|
}
|
|
2031
2011
|
|
|
2032
|
-
//
|
|
2033
|
-
|
|
2012
|
+
// 只有本轮联动动作显式写入 value 时,才应执行表单赋值。
|
|
2013
|
+
// 普通字段组件也会由 Form.Item/FieldModelRenderer 注入 value;如果从 __originalProps 继承该值,
|
|
2014
|
+
// limitOptions/disabled 等状态联动会误把当前表单值清空。
|
|
2015
|
+
if (Object.prototype.hasOwnProperty.call(patchProps, 'value') && model.context.form) {
|
|
2034
2016
|
const targetPath = getModelTargetPathForPatch(model);
|
|
2035
2017
|
if (!targetPath) {
|
|
2036
2018
|
console.warn('[linkageRules] Skip linkage assignment due to missing target path', {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import {
|
|
11
11
|
ActionScene,
|
|
12
12
|
defineAction,
|
|
13
|
+
extractUsedVariablePaths,
|
|
13
14
|
FlowModel,
|
|
14
15
|
MultiRecordResource,
|
|
15
16
|
useFlowContext,
|
|
@@ -20,6 +21,34 @@ import React from 'react';
|
|
|
20
21
|
import { FilterGroup, VariableFilterItem } from '../components/filter';
|
|
21
22
|
import { normalizeDataScopeFilter } from './dataScopeFilter';
|
|
22
23
|
|
|
24
|
+
function dependsOnClickedRowRecord(filter: any) {
|
|
25
|
+
if (!filter) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const used = extractUsedVariablePaths(filter) || {};
|
|
29
|
+
return Object.prototype.hasOwnProperty.call(used, 'clickedRowRecord');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function shouldClearClickedRowDataScope(ctx: any, params: any) {
|
|
33
|
+
return ctx.inputArgs?.selected === false && dependsOnClickedRowRecord(params.filter);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function resolveTargetDataScopeFilter(ctx: any, params: any, resolvedParams: any) {
|
|
37
|
+
if (shouldClearClickedRowDataScope(ctx, params)) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return normalizeDataScopeFilter(params.filter, resolvedParams.filter);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function shouldRefreshTargetResource(resource: MultiRecordResource) {
|
|
45
|
+
if (resource.hasData()) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return resource.getMeta?.('count') !== undefined || resource.getMeta?.('hasNext') !== undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
23
52
|
export const setTargetDataScope = defineAction({
|
|
24
53
|
name: 'setTargetDataScope',
|
|
25
54
|
title: tExpr('Set data scope'),
|
|
@@ -69,20 +98,20 @@ export const setTargetDataScope = defineAction({
|
|
|
69
98
|
return;
|
|
70
99
|
}
|
|
71
100
|
const model: FlowModel = ctx.model;
|
|
101
|
+
const filter = resolveTargetDataScopeFilter(ctx, params, resolvedParams);
|
|
102
|
+
|
|
72
103
|
model.scheduleModelOperation(targetBlockUid, (targetModel) => {
|
|
73
104
|
const resource = targetModel['resource'] as MultiRecordResource;
|
|
74
105
|
if (!resource) {
|
|
75
106
|
return;
|
|
76
107
|
}
|
|
77
108
|
|
|
78
|
-
const filter = normalizeDataScopeFilter(params.filter, resolvedParams.filter);
|
|
79
|
-
|
|
80
109
|
if (isEmptyFilter(filter)) {
|
|
81
110
|
resource.removeFilterGroup(`setTargetDataScope_${ctx.model.uid}`);
|
|
82
111
|
} else {
|
|
83
112
|
resource.addFilterGroup(`setTargetDataScope_${ctx.model.uid}`, filter);
|
|
84
113
|
}
|
|
85
|
-
if (resource
|
|
114
|
+
if (shouldRefreshTargetResource(resource)) {
|
|
86
115
|
resource.refresh();
|
|
87
116
|
}
|
|
88
117
|
});
|
|
@@ -35,6 +35,7 @@ type CollectionFieldLike = {
|
|
|
35
35
|
title?: unknown;
|
|
36
36
|
type?: unknown;
|
|
37
37
|
interface?: unknown;
|
|
38
|
+
uiSchema?: unknown;
|
|
38
39
|
targetKey?: unknown;
|
|
39
40
|
targetCollectionTitleFieldName?: unknown;
|
|
40
41
|
targetCollection?: any;
|
|
@@ -194,6 +195,7 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
194
195
|
title,
|
|
195
196
|
type: String(f.type || 'string'),
|
|
196
197
|
interface: fieldInterface,
|
|
198
|
+
uiSchema: (f as any).uiSchema,
|
|
197
199
|
paths: [...basePaths, name],
|
|
198
200
|
};
|
|
199
201
|
|