@nocobase/client-v2 2.1.0-beta.29 → 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/actions/index.d.ts +1 -1
- package/es/flow/actions/linkageRules.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +4 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutModel.d.ts +7 -0
- package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +5 -0
- 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/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +3 -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 +122 -106
- package/es/utils/remotePlugins.d.ts +0 -4
- package/lib/index.js +121 -105
- 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__/nocobase-buildin-plugin-auth.test.tsx +67 -46
- package/src/__tests__/remotePlugins.test.ts +29 -18
- package/src/__tests__/settings-center.test.tsx +30 -0
- 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/__tests__/FlowRoute.test.tsx +4 -5
- package/src/flow/actions/__tests__/actionLinkageRules.race.repro.test.ts +199 -0
- package/src/flow/actions/__tests__/dataScopeFilter.test.ts +92 -13
- package/src/flow/actions/__tests__/linkageRules.formValueDrivenRefresh.test.ts +6 -1
- package/src/flow/actions/__tests__/linkageRules.menu.test.ts +90 -0
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +476 -1
- package/src/flow/actions/index.ts +2 -0
- package/src/flow/actions/linkageRules.tsx +316 -280
- package/src/flow/actions/linkageRulesFormValueRefresh.ts +2 -8
- package/src/flow/actions/setTargetDataScope.tsx +32 -3
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +8 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +70 -12
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +26 -87
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +11 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +5 -1
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +292 -31
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +50 -12
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +77 -56
- package/src/flow/components/AdminLayout.tsx +2 -2
- package/src/flow/components/FieldAssignRulesEditor.tsx +2 -0
- package/src/flow/components/FlowRoute.tsx +17 -4
- 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/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +4 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +7 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +4 -1
- 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/flow/system-settings/useSystemSettings.tsx +36 -1
- 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 || [];
|
|
@@ -479,80 +688,54 @@ export const linkageSetActionProps = defineAction({
|
|
|
479
688
|
},
|
|
480
689
|
});
|
|
481
690
|
|
|
482
|
-
export const
|
|
483
|
-
name: '
|
|
484
|
-
title: tExpr('Set
|
|
485
|
-
scene: ActionScene.
|
|
691
|
+
export const linkageSetMenuItemProps = defineAction({
|
|
692
|
+
name: 'linkageSetMenuItemProps',
|
|
693
|
+
title: tExpr('Set menu item state'),
|
|
694
|
+
scene: ActionScene.MENU_LINKAGE_RULES,
|
|
486
695
|
sort: 100,
|
|
487
696
|
uiSchema: {
|
|
488
697
|
value: {
|
|
489
|
-
type: '
|
|
698
|
+
type: 'string',
|
|
490
699
|
'x-component': (props) => {
|
|
491
|
-
const { value
|
|
700
|
+
const { value, onChange } = props;
|
|
492
701
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
493
702
|
const ctx = useFlowContext();
|
|
494
703
|
const t = ctx.model.translate.bind(ctx.model);
|
|
495
704
|
|
|
496
|
-
const fieldOptions = getFormFields(ctx);
|
|
497
|
-
|
|
498
|
-
// 状态选项
|
|
499
|
-
const stateOptions = [
|
|
500
|
-
{ label: t('Visible'), value: 'visible' },
|
|
501
|
-
{ label: t('Hidden'), value: 'hidden' },
|
|
502
|
-
{ label: t('Hidden (reserved value)'), value: 'hiddenReservedValue' },
|
|
503
|
-
{ label: t('Required'), value: 'required' },
|
|
504
|
-
{ label: t('Not required'), value: 'notRequired' },
|
|
505
|
-
{ label: t('Disabled'), value: 'disabled' },
|
|
506
|
-
{ label: t('Enabled'), value: 'enabled' },
|
|
507
|
-
];
|
|
508
|
-
|
|
509
|
-
const handleFieldsChange = (selectedFields: string[]) => {
|
|
510
|
-
onChange({
|
|
511
|
-
...value,
|
|
512
|
-
fields: selectedFields,
|
|
513
|
-
});
|
|
514
|
-
};
|
|
515
|
-
|
|
516
|
-
const handleStateChange = (selectedState: string) => {
|
|
517
|
-
onChange({
|
|
518
|
-
...value,
|
|
519
|
-
state: selectedState,
|
|
520
|
-
});
|
|
521
|
-
};
|
|
522
|
-
|
|
523
705
|
return (
|
|
524
|
-
<
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
// @ts-ignore
|
|
536
|
-
filterOption={(input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
|
|
537
|
-
allowClear
|
|
538
|
-
/>
|
|
539
|
-
</div>
|
|
540
|
-
<div>
|
|
541
|
-
<div style={{ marginBottom: '4px', fontSize: '14px' }}>{t('State')}</div>
|
|
542
|
-
<Select
|
|
543
|
-
value={value.state}
|
|
544
|
-
onChange={handleStateChange}
|
|
545
|
-
placeholder={t('Please select state')}
|
|
546
|
-
style={{ width: '100%' }}
|
|
547
|
-
options={stateOptions}
|
|
548
|
-
allowClear
|
|
549
|
-
/>
|
|
550
|
-
</div>
|
|
551
|
-
</div>
|
|
706
|
+
<Select
|
|
707
|
+
value={value}
|
|
708
|
+
onChange={onChange}
|
|
709
|
+
placeholder={t('Please select state')}
|
|
710
|
+
style={{ width: '100%' }}
|
|
711
|
+
options={[
|
|
712
|
+
{ label: t('Visible'), value: 'visible' },
|
|
713
|
+
{ label: t('Hidden'), value: 'hidden' },
|
|
714
|
+
]}
|
|
715
|
+
allowClear
|
|
716
|
+
/>
|
|
552
717
|
);
|
|
553
718
|
},
|
|
554
719
|
},
|
|
555
720
|
},
|
|
721
|
+
handler(ctx, { value, setProps }) {
|
|
722
|
+
setProps(ctx.model, { hiddenModel: value === 'hidden' });
|
|
723
|
+
},
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
export const linkageSetFieldProps = defineAction({
|
|
727
|
+
name: 'linkageSetFieldProps',
|
|
728
|
+
title: tExpr('Set field state'),
|
|
729
|
+
scene: ActionScene.FIELD_LINKAGE_RULES,
|
|
730
|
+
sort: 100,
|
|
731
|
+
uiSchema: {
|
|
732
|
+
value: {
|
|
733
|
+
type: 'object',
|
|
734
|
+
'x-component': (props) => {
|
|
735
|
+
return <FieldStateEditor {...props} />;
|
|
736
|
+
},
|
|
737
|
+
},
|
|
738
|
+
},
|
|
556
739
|
handler: (ctx, { value, setProps }) => {
|
|
557
740
|
const { fields, state } = value || {};
|
|
558
741
|
|
|
@@ -567,36 +750,13 @@ export const linkageSetFieldProps = defineAction({
|
|
|
567
750
|
const fieldModel = gridModels.find((model: any) => model.uid === fieldUid);
|
|
568
751
|
|
|
569
752
|
if (fieldModel) {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
props = { hiddenModel: false };
|
|
575
|
-
break;
|
|
576
|
-
case 'hidden':
|
|
577
|
-
props = { hiddenModel: true };
|
|
578
|
-
break;
|
|
579
|
-
case 'hiddenReservedValue':
|
|
580
|
-
props = { hidden: true };
|
|
581
|
-
break;
|
|
582
|
-
case 'required':
|
|
583
|
-
props = { required: true };
|
|
584
|
-
break;
|
|
585
|
-
case 'notRequired':
|
|
586
|
-
props = { required: false };
|
|
587
|
-
break;
|
|
588
|
-
case 'disabled':
|
|
589
|
-
props = { disabled: true };
|
|
590
|
-
break;
|
|
591
|
-
case 'enabled':
|
|
592
|
-
props = { disabled: false };
|
|
593
|
-
break;
|
|
594
|
-
default:
|
|
595
|
-
console.warn(`Unknown state: ${state}`);
|
|
596
|
-
return;
|
|
753
|
+
const props = getFieldStateProps(state, value?.selectedOptions);
|
|
754
|
+
if (!props) {
|
|
755
|
+
console.warn(`Unknown state: ${state}`);
|
|
756
|
+
return;
|
|
597
757
|
}
|
|
598
758
|
|
|
599
|
-
setProps(fieldModel as FlowModel, props);
|
|
759
|
+
setProps(getFieldStateTargetModel(state, fieldModel) as FlowModel, props);
|
|
600
760
|
}
|
|
601
761
|
} catch (error) {
|
|
602
762
|
console.warn(`Failed to set props for field ${fieldUid}:`, error);
|
|
@@ -614,68 +774,7 @@ export const subFormLinkageSetFieldProps = defineAction({
|
|
|
614
774
|
value: {
|
|
615
775
|
type: 'object',
|
|
616
776
|
'x-component': (props) => {
|
|
617
|
-
|
|
618
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
619
|
-
const ctx = useFlowContext();
|
|
620
|
-
const t = ctx.model.translate.bind(ctx.model);
|
|
621
|
-
|
|
622
|
-
const fieldOptions = getFormFieldsByForkModel(ctx);
|
|
623
|
-
|
|
624
|
-
// 状态选项
|
|
625
|
-
const stateOptions = [
|
|
626
|
-
{ label: t('Visible'), value: 'visible' },
|
|
627
|
-
{ label: t('Hidden'), value: 'hidden' },
|
|
628
|
-
{ label: t('Hidden (reserved value)'), value: 'hiddenReservedValue' },
|
|
629
|
-
{ label: t('Required'), value: 'required' },
|
|
630
|
-
{ label: t('Not required'), value: 'notRequired' },
|
|
631
|
-
{ label: t('Disabled'), value: 'disabled' },
|
|
632
|
-
{ label: t('Enabled'), value: 'enabled' },
|
|
633
|
-
];
|
|
634
|
-
|
|
635
|
-
const handleFieldsChange = (selectedFields: string[]) => {
|
|
636
|
-
onChange({
|
|
637
|
-
...value,
|
|
638
|
-
fields: selectedFields,
|
|
639
|
-
});
|
|
640
|
-
};
|
|
641
|
-
|
|
642
|
-
const handleStateChange = (selectedState: string) => {
|
|
643
|
-
onChange({
|
|
644
|
-
...value,
|
|
645
|
-
state: selectedState,
|
|
646
|
-
});
|
|
647
|
-
};
|
|
648
|
-
|
|
649
|
-
return (
|
|
650
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
651
|
-
<div>
|
|
652
|
-
<div style={{ marginBottom: '4px', fontSize: '14px' }}>{t('Fields')}</div>
|
|
653
|
-
<Select
|
|
654
|
-
mode="multiple"
|
|
655
|
-
value={value.fields}
|
|
656
|
-
onChange={handleFieldsChange}
|
|
657
|
-
placeholder={t('Please select fields')}
|
|
658
|
-
style={{ width: '100%' }}
|
|
659
|
-
options={fieldOptions}
|
|
660
|
-
showSearch
|
|
661
|
-
// @ts-ignore
|
|
662
|
-
filterOption={(input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
|
|
663
|
-
allowClear
|
|
664
|
-
/>
|
|
665
|
-
</div>
|
|
666
|
-
<div>
|
|
667
|
-
<div style={{ marginBottom: '4px', fontSize: '14px' }}>{t('State')}</div>
|
|
668
|
-
<Select
|
|
669
|
-
value={value.state}
|
|
670
|
-
onChange={handleStateChange}
|
|
671
|
-
placeholder={t('Please select state')}
|
|
672
|
-
style={{ width: '100%' }}
|
|
673
|
-
options={stateOptions}
|
|
674
|
-
allowClear
|
|
675
|
-
/>
|
|
676
|
-
</div>
|
|
677
|
-
</div>
|
|
678
|
-
);
|
|
777
|
+
return <FieldStateEditor {...props} fieldOptionsGetter={getFormFieldsByForkModel} />;
|
|
679
778
|
},
|
|
680
779
|
},
|
|
681
780
|
},
|
|
@@ -713,36 +812,13 @@ export const subFormLinkageSetFieldProps = defineAction({
|
|
|
713
812
|
}
|
|
714
813
|
|
|
715
814
|
if (model) {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
props = { hiddenModel: false };
|
|
721
|
-
break;
|
|
722
|
-
case 'hidden':
|
|
723
|
-
props = { hiddenModel: true };
|
|
724
|
-
break;
|
|
725
|
-
case 'hiddenReservedValue':
|
|
726
|
-
props = { hidden: true };
|
|
727
|
-
break;
|
|
728
|
-
case 'required':
|
|
729
|
-
props = { required: true };
|
|
730
|
-
break;
|
|
731
|
-
case 'notRequired':
|
|
732
|
-
props = { required: false };
|
|
733
|
-
break;
|
|
734
|
-
case 'disabled':
|
|
735
|
-
props = { disabled: true };
|
|
736
|
-
break;
|
|
737
|
-
case 'enabled':
|
|
738
|
-
props = { disabled: false };
|
|
739
|
-
break;
|
|
740
|
-
default:
|
|
741
|
-
console.warn(`Unknown state: ${state}`);
|
|
742
|
-
return;
|
|
815
|
+
const props = getFieldStateProps(state, value?.selectedOptions);
|
|
816
|
+
if (!props) {
|
|
817
|
+
console.warn(`Unknown state: ${state}`);
|
|
818
|
+
return;
|
|
743
819
|
}
|
|
744
820
|
|
|
745
|
-
setProps(model as FlowModel, props);
|
|
821
|
+
setProps(getFieldStateTargetModel(state, model) as FlowModel, props);
|
|
746
822
|
}
|
|
747
823
|
} catch (error) {
|
|
748
824
|
console.warn(`Failed to set props for field ${fieldUid}:`, error);
|
|
@@ -760,64 +836,7 @@ export const linkageSetDetailsFieldProps = defineAction({
|
|
|
760
836
|
value: {
|
|
761
837
|
type: 'object',
|
|
762
838
|
'x-component': (props) => {
|
|
763
|
-
|
|
764
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
765
|
-
const ctx = useFlowContext();
|
|
766
|
-
const t = ctx.model.translate.bind(ctx.model);
|
|
767
|
-
|
|
768
|
-
const fieldOptions = getFormFields(ctx);
|
|
769
|
-
|
|
770
|
-
// 状态选项
|
|
771
|
-
const stateOptions = [
|
|
772
|
-
{ label: t('Visible'), value: 'visible' },
|
|
773
|
-
{ label: t('Hidden'), value: 'hidden' },
|
|
774
|
-
{ label: t('Hidden (reserved value)'), value: 'hiddenReservedValue' },
|
|
775
|
-
];
|
|
776
|
-
|
|
777
|
-
const handleFieldsChange = (selectedFields: string[]) => {
|
|
778
|
-
onChange({
|
|
779
|
-
...value,
|
|
780
|
-
fields: selectedFields,
|
|
781
|
-
});
|
|
782
|
-
};
|
|
783
|
-
|
|
784
|
-
const handleStateChange = (selectedState: string) => {
|
|
785
|
-
onChange({
|
|
786
|
-
...value,
|
|
787
|
-
state: selectedState,
|
|
788
|
-
});
|
|
789
|
-
};
|
|
790
|
-
|
|
791
|
-
return (
|
|
792
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
793
|
-
<div>
|
|
794
|
-
<div style={{ marginBottom: '4px', fontSize: '14px' }}>{t('Fields')}</div>
|
|
795
|
-
<Select
|
|
796
|
-
mode="multiple"
|
|
797
|
-
value={value.fields}
|
|
798
|
-
onChange={handleFieldsChange}
|
|
799
|
-
placeholder={t('Please select fields')}
|
|
800
|
-
style={{ width: '100%' }}
|
|
801
|
-
options={fieldOptions}
|
|
802
|
-
showSearch
|
|
803
|
-
// @ts-ignore
|
|
804
|
-
filterOption={(input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
|
|
805
|
-
allowClear
|
|
806
|
-
/>
|
|
807
|
-
</div>
|
|
808
|
-
<div>
|
|
809
|
-
<div style={{ marginBottom: '4px', fontSize: '14px' }}>{t('State')}</div>
|
|
810
|
-
<Select
|
|
811
|
-
value={value.state}
|
|
812
|
-
onChange={handleStateChange}
|
|
813
|
-
placeholder={t('Please select state')}
|
|
814
|
-
style={{ width: '100%' }}
|
|
815
|
-
options={stateOptions}
|
|
816
|
-
allowClear
|
|
817
|
-
/>
|
|
818
|
-
</div>
|
|
819
|
-
</div>
|
|
820
|
-
);
|
|
839
|
+
return <FieldStateEditor {...props} includeFormStates={false} />;
|
|
821
840
|
},
|
|
822
841
|
},
|
|
823
842
|
},
|
|
@@ -835,24 +854,13 @@ export const linkageSetDetailsFieldProps = defineAction({
|
|
|
835
854
|
const fieldModel = gridModels.find((model: any) => model.uid === fieldUid);
|
|
836
855
|
|
|
837
856
|
if (fieldModel) {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
props = { hiddenModel: false };
|
|
843
|
-
break;
|
|
844
|
-
case 'hidden':
|
|
845
|
-
props = { hiddenModel: true };
|
|
846
|
-
break;
|
|
847
|
-
case 'hiddenReservedValue':
|
|
848
|
-
props = { hidden: true };
|
|
849
|
-
break;
|
|
850
|
-
default:
|
|
851
|
-
console.warn(`Unknown state: ${state}`);
|
|
852
|
-
return;
|
|
857
|
+
const props = getFieldStateProps(state);
|
|
858
|
+
if (!props) {
|
|
859
|
+
console.warn(`Unknown state: ${state}`);
|
|
860
|
+
return;
|
|
853
861
|
}
|
|
854
862
|
|
|
855
|
-
setProps(fieldModel as FlowModel, props);
|
|
863
|
+
setProps(getFieldStateTargetModel(state, fieldModel) as FlowModel, props);
|
|
856
864
|
}
|
|
857
865
|
} catch (error) {
|
|
858
866
|
console.warn(`Failed to set props for field ${fieldUid}:`, error);
|
|
@@ -1288,6 +1296,7 @@ export const linkageRunjs = defineAction({
|
|
|
1288
1296
|
ActionScene.BLOCK_LINKAGE_RULES,
|
|
1289
1297
|
ActionScene.FIELD_LINKAGE_RULES,
|
|
1290
1298
|
ActionScene.ACTION_LINKAGE_RULES,
|
|
1299
|
+
ActionScene.MENU_LINKAGE_RULES,
|
|
1291
1300
|
ActionScene.DETAILS_FIELD_LINKAGE_RULES,
|
|
1292
1301
|
ActionScene.SUB_FORM_FIELD_LINKAGE_RULES,
|
|
1293
1302
|
],
|
|
@@ -1755,6 +1764,8 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1755
1764
|
|
|
1756
1765
|
const linkageRules: LinkageRule[] = params.value as LinkageRule[];
|
|
1757
1766
|
const allModels: FlowModel[] = ctx.model.__allModels || (ctx.model.__allModels = []);
|
|
1767
|
+
const modelsToApply = new Set<FlowModel>(allModels);
|
|
1768
|
+
const patchPropsByModel = new Map<FlowModel, any>();
|
|
1758
1769
|
const directValuePatches: Array<{ path: Array<string | number>; value: any; whenEmpty?: boolean }> = [];
|
|
1759
1770
|
const rootCollection = getCollectionFromModel((ctx.model as any)?.context?.blockModel ?? ctx.model);
|
|
1760
1771
|
const isSafeToWriteAssociationSubpath = (namePath: any): boolean => {
|
|
@@ -1906,11 +1917,6 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1906
1917
|
return null;
|
|
1907
1918
|
};
|
|
1908
1919
|
|
|
1909
|
-
allModels.forEach((model: any) => {
|
|
1910
|
-
// 重置临时属性
|
|
1911
|
-
model.__props = {};
|
|
1912
|
-
});
|
|
1913
|
-
|
|
1914
1920
|
// 1. 运行所有的联动规则
|
|
1915
1921
|
for (const rule of linkageRules.filter((r) => r.enable)) {
|
|
1916
1922
|
const { condition: conditions, actions } = rule;
|
|
@@ -1919,10 +1925,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1919
1925
|
if (!matched) continue;
|
|
1920
1926
|
|
|
1921
1927
|
for (const action of actions) {
|
|
1922
|
-
const setProps = (
|
|
1923
|
-
model: FlowModel & { __originalProps?: any; __props?: any; __shouldReset?: boolean },
|
|
1924
|
-
props: any,
|
|
1925
|
-
) => {
|
|
1928
|
+
const setProps = (model: FlowModel & { __originalProps?: any; __shouldReset?: boolean }, props: any) => {
|
|
1926
1929
|
// 存储原始值,用于恢复
|
|
1927
1930
|
if (!model.__originalProps) {
|
|
1928
1931
|
model.__originalProps = {
|
|
@@ -1935,19 +1938,16 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1935
1938
|
};
|
|
1936
1939
|
}
|
|
1937
1940
|
|
|
1938
|
-
if (!model.__props) {
|
|
1939
|
-
model.__props = {};
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1942
1941
|
// 临时存起来,遍历完所有规则后,再统一处理
|
|
1943
|
-
model
|
|
1944
|
-
...model
|
|
1942
|
+
patchPropsByModel.set(model, {
|
|
1943
|
+
...(patchPropsByModel.get(model) || {}),
|
|
1945
1944
|
...props,
|
|
1946
|
-
};
|
|
1945
|
+
});
|
|
1947
1946
|
|
|
1948
1947
|
if (allModels.indexOf(model) === -1) {
|
|
1949
1948
|
allModels.push(model);
|
|
1950
1949
|
}
|
|
1950
|
+
modelsToApply.add(model);
|
|
1951
1951
|
};
|
|
1952
1952
|
|
|
1953
1953
|
// TODO: 需要改成 runAction 的写法。但 runAction 是异步的,用在这里会不符合预期。后面需要解决这个问题
|
|
@@ -1956,23 +1956,26 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1956
1956
|
}
|
|
1957
1957
|
|
|
1958
1958
|
// 2. 合并去重(按 uid)后再实际更改相关 model 的状态,避免重复项把“已设置的临时属性”覆盖掉
|
|
1959
|
-
const mergedByUid = new Map<
|
|
1960
|
-
string,
|
|
1961
|
-
FlowModel & { __originalProps?: any; __props?: any; isFork?: boolean; forkId?: number }
|
|
1962
|
-
>();
|
|
1959
|
+
const mergedByUid = new Map<string, FlowModel & { __originalProps?: any; isFork?: boolean; forkId?: number }>();
|
|
1963
1960
|
const mergedPropsByUid = new Map<string, any>();
|
|
1964
1961
|
|
|
1965
|
-
|
|
1962
|
+
modelsToApply.forEach((m: any) => {
|
|
1966
1963
|
const uid = m?.uid || String(m);
|
|
1967
|
-
const curProps = m
|
|
1964
|
+
const curProps = patchPropsByModel.get(m) || {};
|
|
1968
1965
|
if (!mergedByUid.has(uid)) {
|
|
1969
1966
|
mergedByUid.set(uid, m);
|
|
1970
1967
|
mergedPropsByUid.set(uid, { ...curProps });
|
|
1971
1968
|
} else {
|
|
1972
1969
|
// 合并属性:后写覆盖先写;优先选择 fork 模型作为应用目标
|
|
1973
|
-
|
|
1970
|
+
const prevProps = mergedPropsByUid.get(uid) || {};
|
|
1971
|
+
const nextProps = { ...prevProps, ...curProps };
|
|
1972
|
+
mergedPropsByUid.set(uid, nextProps);
|
|
1974
1973
|
const exist = mergedByUid.get(uid) as any;
|
|
1975
|
-
|
|
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) {
|
|
1976
1979
|
mergedByUid.set(uid, m);
|
|
1977
1980
|
}
|
|
1978
1981
|
}
|
|
@@ -1983,7 +1986,12 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1983
1986
|
const newProps = { ...model.__originalProps, ...patchProps };
|
|
1984
1987
|
|
|
1985
1988
|
model.setProps(_.omit(newProps, ['hiddenModel', 'value', 'hiddenText']));
|
|
1986
|
-
model
|
|
1989
|
+
syncFieldOptionsToForks(model, patchProps);
|
|
1990
|
+
if (typeof model.setHidden === 'function') {
|
|
1991
|
+
model.setHidden(!!newProps.hiddenModel);
|
|
1992
|
+
} else {
|
|
1993
|
+
model.hidden = !!newProps.hiddenModel;
|
|
1994
|
+
}
|
|
1987
1995
|
|
|
1988
1996
|
if (newProps.required === true) {
|
|
1989
1997
|
const rules = (model.props.rules || []).filter((rule) => !rule.required);
|
|
@@ -2001,8 +2009,10 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
2001
2009
|
model.setProps('title', '');
|
|
2002
2010
|
}
|
|
2003
2011
|
|
|
2004
|
-
//
|
|
2005
|
-
|
|
2012
|
+
// 只有本轮联动动作显式写入 value 时,才应执行表单赋值。
|
|
2013
|
+
// 普通字段组件也会由 Form.Item/FieldModelRenderer 注入 value;如果从 __originalProps 继承该值,
|
|
2014
|
+
// limitOptions/disabled 等状态联动会误把当前表单值清空。
|
|
2015
|
+
if (Object.prototype.hasOwnProperty.call(patchProps, 'value') && model.context.form) {
|
|
2006
2016
|
const targetPath = getModelTargetPathForPatch(model);
|
|
2007
2017
|
if (!targetPath) {
|
|
2008
2018
|
console.warn('[linkageRules] Skip linkage assignment due to missing target path', {
|
|
@@ -2149,6 +2159,32 @@ export const actionLinkageRules = defineAction({
|
|
|
2149
2159
|
},
|
|
2150
2160
|
});
|
|
2151
2161
|
|
|
2162
|
+
export const menuLinkageRules = defineAction({
|
|
2163
|
+
name: 'menuLinkageRules',
|
|
2164
|
+
title: tExpr('Menu linkage rules'),
|
|
2165
|
+
uiMode: 'embed',
|
|
2166
|
+
uiSchema(ctx) {
|
|
2167
|
+
return {
|
|
2168
|
+
value: {
|
|
2169
|
+
type: 'array',
|
|
2170
|
+
'x-component': LinkageRulesUI,
|
|
2171
|
+
'x-component-props': {
|
|
2172
|
+
supportedActions: getSupportedActions(ctx, ActionScene.MENU_LINKAGE_RULES),
|
|
2173
|
+
title: tExpr('Menu linkage rules'),
|
|
2174
|
+
},
|
|
2175
|
+
},
|
|
2176
|
+
};
|
|
2177
|
+
},
|
|
2178
|
+
defaultParams: {
|
|
2179
|
+
value: [],
|
|
2180
|
+
},
|
|
2181
|
+
useRawParams: true,
|
|
2182
|
+
handler: async (ctx, params) => {
|
|
2183
|
+
const resolved = await resolveLinkageRulesParamsPreservingRunJsScripts(ctx, params);
|
|
2184
|
+
return commonLinkageRulesHandler(ctx, resolved);
|
|
2185
|
+
},
|
|
2186
|
+
});
|
|
2187
|
+
|
|
2152
2188
|
export const fieldLinkageRules = defineAction({
|
|
2153
2189
|
name: 'fieldLinkageRules',
|
|
2154
2190
|
title: tExpr('Field linkage rules'),
|