@nocobase/client-v2 2.1.0-beta.27 → 2.1.0-beta.30
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/components/form/JsonTextArea.d.ts +18 -0
- package/es/components/index.d.ts +1 -0
- package/es/flow/actions/dateRangeLimit.d.ts +9 -0
- package/es/flow/actions/index.d.ts +2 -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/models/base/PageModel/PageModel.d.ts +4 -0
- package/es/flow/models/base/PageModel/RootPageModel.d.ts +9 -0
- package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +7 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +5 -0
- package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +20 -0
- package/es/flow/models/fields/JSEditableFieldModel.d.ts +4 -0
- package/es/index.mjs +79 -67
- package/lib/index.js +80 -68
- package/package.json +6 -5
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +67 -46
- package/src/__tests__/settings-center.test.tsx +30 -0
- package/src/components/form/JsonTextArea.tsx +129 -0
- package/src/components/index.ts +1 -0
- 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__/fieldLinkageRules.scopeDepth.test.ts +478 -0
- 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__/pattern.test.ts +190 -0
- package/src/flow/actions/dateRangeLimit.tsx +66 -0
- package/src/flow/actions/index.ts +3 -0
- package/src/flow/actions/linkageRules.tsx +194 -42
- package/src/flow/actions/linkageRulesFormValueRefresh.ts +2 -8
- package/src/flow/actions/openView.tsx +2 -1
- package/src/flow/actions/pattern.tsx +25 -2
- package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +7 -1
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +117 -0
- 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/FlowRoute.tsx +17 -4
- package/src/flow/models/base/PageModel/PageModel.tsx +15 -3
- package/src/flow/models/base/PageModel/RootPageModel.tsx +37 -2
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +73 -0
- package/src/flow/models/base/PageModel/__tests__/RootPageModel.test.ts +116 -0
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +167 -1
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +103 -11
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +4 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +34 -3
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +47 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +42 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableField.refresh.test.tsx +122 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -1
- package/src/flow/models/fields/ClickableFieldModel.tsx +21 -9
- package/src/flow/models/fields/DateTimeFieldModel/DateOnlyFieldModel.tsx +9 -0
- package/src/flow/models/fields/DateTimeFieldModel/DateTimeFieldModel.tsx +4 -0
- package/src/flow/models/fields/DateTimeFieldModel/DateTimeNoTzFieldModel.tsx +9 -0
- package/src/flow/models/fields/DateTimeFieldModel/DateTimeTzFieldModel.tsx +9 -0
- package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +242 -0
- package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +152 -0
- package/src/flow/models/fields/JSEditableFieldModel.tsx +110 -14
- package/src/flow/models/fields/__tests__/ClickableFieldModel.test.ts +87 -0
- package/src/flow/models/fields/__tests__/JSEditableFieldModel.test.tsx +210 -0
- package/src/flow/system-settings/useSystemSettings.tsx +36 -1
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
11
|
+
import { FlowEngine, FlowModel } from '@nocobase/flow-engine';
|
|
12
|
+
import { FieldModel } from '../../models/base/FieldModel';
|
|
13
|
+
import { JSEditableFieldModel } from '../../models/fields/JSEditableFieldModel';
|
|
14
|
+
import { DetailsItemModel } from '../../models/blocks/details/DetailsItemModel';
|
|
15
|
+
import { pattern } from '../pattern';
|
|
16
|
+
|
|
17
|
+
class DummyDisplayFieldModel extends FieldModel {}
|
|
18
|
+
|
|
19
|
+
class DummyFormItemModel extends FlowModel<{ subModels: { field?: FieldModel } }> {
|
|
20
|
+
collectionField: any;
|
|
21
|
+
|
|
22
|
+
static getDefaultBindingByField() {
|
|
23
|
+
return { modelName: 'FieldModel' };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getFieldSettingsInitParams() {
|
|
27
|
+
return { mock: true };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function makeCollectionField() {
|
|
32
|
+
return {
|
|
33
|
+
targetCollection: undefined,
|
|
34
|
+
isAssociationField: () => false,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function makeCtx(parent: DummyFormItemModel) {
|
|
39
|
+
const collectionField = makeCollectionField();
|
|
40
|
+
parent.collectionField = collectionField;
|
|
41
|
+
return {
|
|
42
|
+
model: parent,
|
|
43
|
+
collectionField,
|
|
44
|
+
engine: parent.flowEngine,
|
|
45
|
+
} as any;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe('pattern action', () => {
|
|
49
|
+
it('keeps JS editable field model when switching to display only', async () => {
|
|
50
|
+
const engine = new FlowEngine();
|
|
51
|
+
engine.registerModels({
|
|
52
|
+
DummyFormItemModel,
|
|
53
|
+
FieldModel,
|
|
54
|
+
JSEditableFieldModel,
|
|
55
|
+
DummyDisplayFieldModel,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const parent = engine.createModel<DummyFormItemModel>({
|
|
59
|
+
use: DummyFormItemModel,
|
|
60
|
+
uid: 'form-item-js',
|
|
61
|
+
subModels: {
|
|
62
|
+
field: {
|
|
63
|
+
use: FieldModel,
|
|
64
|
+
uid: 'field-js',
|
|
65
|
+
stepParams: {
|
|
66
|
+
fieldBinding: {
|
|
67
|
+
use: 'JSEditableFieldModel',
|
|
68
|
+
},
|
|
69
|
+
jsSettings: {
|
|
70
|
+
runJs: {
|
|
71
|
+
code: 'ctx.render("hello")',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
const field = parent.subModels.field;
|
|
79
|
+
const saveSpy = vi.spyOn(engine, 'saveModel');
|
|
80
|
+
const applyJsSettingsSpy = vi.spyOn(field as JSEditableFieldModel, 'scheduleApplyJsSettings');
|
|
81
|
+
|
|
82
|
+
await pattern.afterParamsSave?.(makeCtx(parent), { pattern: 'readPretty' }, { pattern: 'editable' });
|
|
83
|
+
|
|
84
|
+
expect(parent.subModels.field).toBe(field);
|
|
85
|
+
expect(parent.subModels.field).toBeInstanceOf(JSEditableFieldModel);
|
|
86
|
+
expect(parent.subModels.field?.uid).toBe('field-js');
|
|
87
|
+
expect(parent.subModels.field?.getStepParams('jsSettings', 'runJs')).toMatchObject({
|
|
88
|
+
code: 'ctx.render("hello")',
|
|
89
|
+
});
|
|
90
|
+
expect(saveSpy).not.toHaveBeenCalled();
|
|
91
|
+
expect(applyJsSettingsSpy).toHaveBeenCalledTimes(1);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('keeps JS editable field model when leaving display only', async () => {
|
|
95
|
+
const engine = new FlowEngine();
|
|
96
|
+
engine.registerModels({
|
|
97
|
+
DummyFormItemModel,
|
|
98
|
+
FieldModel,
|
|
99
|
+
JSEditableFieldModel,
|
|
100
|
+
DummyDisplayFieldModel,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const parent = engine.createModel<DummyFormItemModel>({
|
|
104
|
+
use: DummyFormItemModel,
|
|
105
|
+
uid: 'form-item-js-leave',
|
|
106
|
+
subModels: {
|
|
107
|
+
field: {
|
|
108
|
+
use: FieldModel,
|
|
109
|
+
uid: 'field-js-leave',
|
|
110
|
+
stepParams: {
|
|
111
|
+
fieldBinding: {
|
|
112
|
+
use: 'JSEditableFieldModel',
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
const field = parent.subModels.field;
|
|
119
|
+
const saveSpy = vi.spyOn(engine, 'saveModel');
|
|
120
|
+
const applyJsSettingsSpy = vi.spyOn(field as JSEditableFieldModel, 'scheduleApplyJsSettings');
|
|
121
|
+
|
|
122
|
+
await pattern.afterParamsSave?.(makeCtx(parent), { pattern: 'editable' }, { pattern: 'readPretty' });
|
|
123
|
+
|
|
124
|
+
expect(parent.subModels.field).toBe(field);
|
|
125
|
+
expect(parent.subModels.field).toBeInstanceOf(JSEditableFieldModel);
|
|
126
|
+
expect(saveSpy).not.toHaveBeenCalled();
|
|
127
|
+
expect(applyJsSettingsSpy).toHaveBeenCalledTimes(1);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('does not reapply JS settings when pattern is unchanged', async () => {
|
|
131
|
+
const engine = new FlowEngine();
|
|
132
|
+
engine.registerModels({
|
|
133
|
+
DummyFormItemModel,
|
|
134
|
+
FieldModel,
|
|
135
|
+
JSEditableFieldModel,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const parent = engine.createModel<DummyFormItemModel>({
|
|
139
|
+
use: DummyFormItemModel,
|
|
140
|
+
uid: 'form-item-js-unchanged',
|
|
141
|
+
subModels: {
|
|
142
|
+
field: {
|
|
143
|
+
use: FieldModel,
|
|
144
|
+
uid: 'field-js-unchanged',
|
|
145
|
+
stepParams: {
|
|
146
|
+
fieldBinding: {
|
|
147
|
+
use: 'JSEditableFieldModel',
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
const applyJsSettingsSpy = vi.spyOn(parent.subModels.field as JSEditableFieldModel, 'scheduleApplyJsSettings');
|
|
154
|
+
|
|
155
|
+
await pattern.afterParamsSave?.(makeCtx(parent), { pattern: 'readPretty' }, { pattern: 'readPretty' });
|
|
156
|
+
|
|
157
|
+
expect(applyJsSettingsSpy).not.toHaveBeenCalled();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('still rebuilds regular fields when switching to display only', async () => {
|
|
161
|
+
const engine = new FlowEngine();
|
|
162
|
+
engine.registerModels({
|
|
163
|
+
DummyFormItemModel,
|
|
164
|
+
FieldModel,
|
|
165
|
+
DummyDisplayFieldModel,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const parent = engine.createModel<DummyFormItemModel>({
|
|
169
|
+
use: DummyFormItemModel,
|
|
170
|
+
uid: 'form-item-regular',
|
|
171
|
+
subModels: {
|
|
172
|
+
field: {
|
|
173
|
+
use: FieldModel,
|
|
174
|
+
uid: 'field-regular',
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
const ctx = makeCtx(parent);
|
|
179
|
+
const displayBinding = { modelName: 'DummyDisplayFieldModel', defaultProps: { display: true } } as any;
|
|
180
|
+
const getDisplayBindingSpy = vi.spyOn(DetailsItemModel, 'getDefaultBindingByField').mockReturnValue(displayBinding);
|
|
181
|
+
|
|
182
|
+
await pattern.afterParamsSave?.(ctx, { pattern: 'readPretty' }, { pattern: 'editable' });
|
|
183
|
+
|
|
184
|
+
expect(parent.subModels.field).toBeInstanceOf(DummyDisplayFieldModel);
|
|
185
|
+
expect(parent.subModels.field?.uid).toBe('field-regular');
|
|
186
|
+
expect(parent.subModels.field?.props).toMatchObject({ display: true });
|
|
187
|
+
|
|
188
|
+
getDisplayBindingSpy.mockRestore();
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { defineAction, tExpr } from '@nocobase/flow-engine';
|
|
11
|
+
import { FieldAssignValueInput } from '../components/FieldAssignValueInput';
|
|
12
|
+
|
|
13
|
+
function normalizeDateRangeValue(value: any) {
|
|
14
|
+
if (value === '' || value === null || typeof value === 'undefined') {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const dateRangeLimit = defineAction({
|
|
21
|
+
name: 'dateRangeLimit',
|
|
22
|
+
title: tExpr('Date range limit'),
|
|
23
|
+
uiMode: {
|
|
24
|
+
type: 'dialog',
|
|
25
|
+
props: {
|
|
26
|
+
width: 720,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
uiSchema(ctx: any) {
|
|
30
|
+
const targetPath = ctx.model.context?.collectionField?.name || '';
|
|
31
|
+
const dateLimitInputProps = {
|
|
32
|
+
targetPath,
|
|
33
|
+
enableDateVariableAsConstant: true,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
_minDate: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
title: tExpr('MinDate'),
|
|
40
|
+
'x-decorator': 'FormItem',
|
|
41
|
+
'x-component': FieldAssignValueInput,
|
|
42
|
+
'x-component-props': dateLimitInputProps,
|
|
43
|
+
},
|
|
44
|
+
_maxDate: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
title: tExpr('MaxDate'),
|
|
47
|
+
'x-decorator': 'FormItem',
|
|
48
|
+
'x-component': FieldAssignValueInput,
|
|
49
|
+
'x-component-props': dateLimitInputProps,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
defaultParams(ctx: any) {
|
|
54
|
+
return {
|
|
55
|
+
_minDate: normalizeDateRangeValue(ctx.model.props?._minDate),
|
|
56
|
+
_maxDate: normalizeDateRangeValue(ctx.model.props?._maxDate),
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
useRawParams: true,
|
|
60
|
+
async handler(ctx: any, params) {
|
|
61
|
+
ctx.model.setProps({
|
|
62
|
+
_minDate: normalizeDateRangeValue(params?._minDate),
|
|
63
|
+
_maxDate: normalizeDateRangeValue(params?._maxDate),
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
});
|
|
@@ -20,6 +20,7 @@ export * from './refreshTargetBlocks';
|
|
|
20
20
|
export * from './setTargetDataScope';
|
|
21
21
|
export { titleField } from './titleField';
|
|
22
22
|
export * from './dateTimeFormat';
|
|
23
|
+
export * from './dateRangeLimit';
|
|
23
24
|
export * from './sortingRules';
|
|
24
25
|
export * from './dataLoadingMode';
|
|
25
26
|
export * from './renderMode';
|
|
@@ -41,9 +42,11 @@ export {
|
|
|
41
42
|
detailsFieldLinkageRules,
|
|
42
43
|
linkageSetDetailsFieldProps,
|
|
43
44
|
actionLinkageRules,
|
|
45
|
+
menuLinkageRules,
|
|
44
46
|
blockLinkageRules,
|
|
45
47
|
linkageSetBlockProps,
|
|
46
48
|
linkageSetActionProps,
|
|
49
|
+
linkageSetMenuItemProps,
|
|
47
50
|
linkageSetFieldProps,
|
|
48
51
|
subFormLinkageSetFieldProps,
|
|
49
52
|
linkageAssignField,
|
|
@@ -51,11 +51,7 @@ import {
|
|
|
51
51
|
getCollectionFromModel,
|
|
52
52
|
isToManyAssociationField,
|
|
53
53
|
} from '../internal/utils/modelUtils';
|
|
54
|
-
import {
|
|
55
|
-
namePathToPathKey,
|
|
56
|
-
parsePathString,
|
|
57
|
-
resolveDynamicNamePath,
|
|
58
|
-
} from '../models/blocks/form/value-runtime/path';
|
|
54
|
+
import { namePathToPathKey, parsePathString, resolveDynamicNamePath } from '../models/blocks/form/value-runtime/path';
|
|
59
55
|
import { ensureFormValueDrivenLinkageRefresh } from './linkageRulesFormValueRefresh';
|
|
60
56
|
|
|
61
57
|
interface LinkageRule {
|
|
@@ -483,6 +479,41 @@ export const linkageSetActionProps = defineAction({
|
|
|
483
479
|
},
|
|
484
480
|
});
|
|
485
481
|
|
|
482
|
+
export const linkageSetMenuItemProps = defineAction({
|
|
483
|
+
name: 'linkageSetMenuItemProps',
|
|
484
|
+
title: tExpr('Set menu item state'),
|
|
485
|
+
scene: ActionScene.MENU_LINKAGE_RULES,
|
|
486
|
+
sort: 100,
|
|
487
|
+
uiSchema: {
|
|
488
|
+
value: {
|
|
489
|
+
type: 'string',
|
|
490
|
+
'x-component': (props) => {
|
|
491
|
+
const { value, onChange } = props;
|
|
492
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
493
|
+
const ctx = useFlowContext();
|
|
494
|
+
const t = ctx.model.translate.bind(ctx.model);
|
|
495
|
+
|
|
496
|
+
return (
|
|
497
|
+
<Select
|
|
498
|
+
value={value}
|
|
499
|
+
onChange={onChange}
|
|
500
|
+
placeholder={t('Please select state')}
|
|
501
|
+
style={{ width: '100%' }}
|
|
502
|
+
options={[
|
|
503
|
+
{ label: t('Visible'), value: 'visible' },
|
|
504
|
+
{ label: t('Hidden'), value: 'hidden' },
|
|
505
|
+
]}
|
|
506
|
+
allowClear
|
|
507
|
+
/>
|
|
508
|
+
);
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
handler(ctx, { value, setProps }) {
|
|
513
|
+
setProps(ctx.model, { hiddenModel: value === 'hidden' });
|
|
514
|
+
},
|
|
515
|
+
});
|
|
516
|
+
|
|
486
517
|
export const linkageSetFieldProps = defineAction({
|
|
487
518
|
name: 'linkageSetFieldProps',
|
|
488
519
|
title: tExpr('Set field state'),
|
|
@@ -1292,6 +1323,7 @@ export const linkageRunjs = defineAction({
|
|
|
1292
1323
|
ActionScene.BLOCK_LINKAGE_RULES,
|
|
1293
1324
|
ActionScene.FIELD_LINKAGE_RULES,
|
|
1294
1325
|
ActionScene.ACTION_LINKAGE_RULES,
|
|
1326
|
+
ActionScene.MENU_LINKAGE_RULES,
|
|
1295
1327
|
ActionScene.DETAILS_FIELD_LINKAGE_RULES,
|
|
1296
1328
|
ActionScene.SUB_FORM_FIELD_LINKAGE_RULES,
|
|
1297
1329
|
],
|
|
@@ -1759,7 +1791,9 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1759
1791
|
|
|
1760
1792
|
const linkageRules: LinkageRule[] = params.value as LinkageRule[];
|
|
1761
1793
|
const allModels: FlowModel[] = ctx.model.__allModels || (ctx.model.__allModels = []);
|
|
1762
|
-
const
|
|
1794
|
+
const modelsToApply = new Set<FlowModel>(allModels);
|
|
1795
|
+
const patchPropsByModel = new Map<FlowModel, any>();
|
|
1796
|
+
const directValuePatches: Array<{ path: Array<string | number>; value: any; whenEmpty?: boolean }> = [];
|
|
1763
1797
|
const rootCollection = getCollectionFromModel((ctx.model as any)?.context?.blockModel ?? ctx.model);
|
|
1764
1798
|
const isSafeToWriteAssociationSubpath = (namePath: any): boolean => {
|
|
1765
1799
|
if (!Array.isArray(namePath) || !namePath.length) return true;
|
|
@@ -1800,6 +1834,24 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1800
1834
|
const fieldIndex = (ctx.model as any)?.context?.fieldIndex;
|
|
1801
1835
|
return resolveDynamicNamePath(path, fieldIndex);
|
|
1802
1836
|
};
|
|
1837
|
+
const getDefaultPatchRuntime = () => {
|
|
1838
|
+
const blockModel = (ctx.model as any)?.context?.blockModel ?? ctx.model;
|
|
1839
|
+
return blockModel?.formValueRuntime ?? (ctx as any)?.formValueRuntime;
|
|
1840
|
+
};
|
|
1841
|
+
const rememberAppliedDefaultPatches = (patches: typeof directValuePatches) => {
|
|
1842
|
+
const runtime = getDefaultPatchRuntime();
|
|
1843
|
+
if (typeof runtime?.recordDefaultValuePatch !== 'function') return;
|
|
1844
|
+
|
|
1845
|
+
const lastPatchByPathKey = new Map<string, (typeof directValuePatches)[number]>();
|
|
1846
|
+
for (const patch of patches) {
|
|
1847
|
+
lastPatchByPathKey.set(namePathToPathKey(patch.path), patch);
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
for (const patch of lastPatchByPathKey.values()) {
|
|
1851
|
+
if (!patch.whenEmpty) continue;
|
|
1852
|
+
runtime.recordDefaultValuePatch(patch.path, patch.value);
|
|
1853
|
+
}
|
|
1854
|
+
};
|
|
1803
1855
|
const addFormValuePatch = (patch: { path: any; value: any; whenEmpty?: boolean }) => {
|
|
1804
1856
|
if (!patch) return;
|
|
1805
1857
|
const path = (patch as any)?.path;
|
|
@@ -1823,20 +1875,33 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1823
1875
|
return;
|
|
1824
1876
|
}
|
|
1825
1877
|
const whenEmpty = !!(patch as any)?.whenEmpty;
|
|
1878
|
+
const value = (patch as any)?.value;
|
|
1826
1879
|
try {
|
|
1827
1880
|
const form = ctx.model?.context?.form;
|
|
1828
1881
|
const current = form?.getFieldValue?.(resolvedPath);
|
|
1829
|
-
if (whenEmpty
|
|
1830
|
-
|
|
1882
|
+
if (whenEmpty) {
|
|
1883
|
+
const runtime = getDefaultPatchRuntime();
|
|
1884
|
+
if (typeof runtime?.canApplyDefaultValuePatch === 'function') {
|
|
1885
|
+
const canApply = runtime.canApplyDefaultValuePatch(resolvedPath, value);
|
|
1886
|
+
if (!canApply) {
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
} else if (typeof current !== 'undefined' && current !== null && current !== '') {
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1831
1892
|
}
|
|
1832
|
-
if (_.isEqual(current,
|
|
1893
|
+
if (_.isEqual(current, value)) {
|
|
1833
1894
|
return;
|
|
1834
1895
|
}
|
|
1835
1896
|
} catch {
|
|
1836
1897
|
// ignore
|
|
1837
1898
|
}
|
|
1838
1899
|
|
|
1839
|
-
directValuePatches.push({
|
|
1900
|
+
directValuePatches.push({
|
|
1901
|
+
path: resolvedPath,
|
|
1902
|
+
value,
|
|
1903
|
+
...(whenEmpty ? { whenEmpty: true } : {}),
|
|
1904
|
+
});
|
|
1840
1905
|
};
|
|
1841
1906
|
|
|
1842
1907
|
const getModelTargetPathForPatch = (model: any): string | null => {
|
|
@@ -1879,11 +1944,6 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1879
1944
|
return null;
|
|
1880
1945
|
};
|
|
1881
1946
|
|
|
1882
|
-
allModels.forEach((model: any) => {
|
|
1883
|
-
// 重置临时属性
|
|
1884
|
-
model.__props = {};
|
|
1885
|
-
});
|
|
1886
|
-
|
|
1887
1947
|
// 1. 运行所有的联动规则
|
|
1888
1948
|
for (const rule of linkageRules.filter((r) => r.enable)) {
|
|
1889
1949
|
const { condition: conditions, actions } = rule;
|
|
@@ -1892,10 +1952,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1892
1952
|
if (!matched) continue;
|
|
1893
1953
|
|
|
1894
1954
|
for (const action of actions) {
|
|
1895
|
-
const setProps = (
|
|
1896
|
-
model: FlowModel & { __originalProps?: any; __props?: any; __shouldReset?: boolean },
|
|
1897
|
-
props: any,
|
|
1898
|
-
) => {
|
|
1955
|
+
const setProps = (model: FlowModel & { __originalProps?: any; __shouldReset?: boolean }, props: any) => {
|
|
1899
1956
|
// 存储原始值,用于恢复
|
|
1900
1957
|
if (!model.__originalProps) {
|
|
1901
1958
|
model.__originalProps = {
|
|
@@ -1908,19 +1965,16 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1908
1965
|
};
|
|
1909
1966
|
}
|
|
1910
1967
|
|
|
1911
|
-
if (!model.__props) {
|
|
1912
|
-
model.__props = {};
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
1968
|
// 临时存起来,遍历完所有规则后,再统一处理
|
|
1916
|
-
model
|
|
1917
|
-
...model
|
|
1969
|
+
patchPropsByModel.set(model, {
|
|
1970
|
+
...(patchPropsByModel.get(model) || {}),
|
|
1918
1971
|
...props,
|
|
1919
|
-
};
|
|
1972
|
+
});
|
|
1920
1973
|
|
|
1921
1974
|
if (allModels.indexOf(model) === -1) {
|
|
1922
1975
|
allModels.push(model);
|
|
1923
1976
|
}
|
|
1977
|
+
modelsToApply.add(model);
|
|
1924
1978
|
};
|
|
1925
1979
|
|
|
1926
1980
|
// TODO: 需要改成 runAction 的写法。但 runAction 是异步的,用在这里会不符合预期。后面需要解决这个问题
|
|
@@ -1929,15 +1983,12 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1929
1983
|
}
|
|
1930
1984
|
|
|
1931
1985
|
// 2. 合并去重(按 uid)后再实际更改相关 model 的状态,避免重复项把“已设置的临时属性”覆盖掉
|
|
1932
|
-
const mergedByUid = new Map<
|
|
1933
|
-
string,
|
|
1934
|
-
FlowModel & { __originalProps?: any; __props?: any; isFork?: boolean; forkId?: number }
|
|
1935
|
-
>();
|
|
1986
|
+
const mergedByUid = new Map<string, FlowModel & { __originalProps?: any; isFork?: boolean; forkId?: number }>();
|
|
1936
1987
|
const mergedPropsByUid = new Map<string, any>();
|
|
1937
1988
|
|
|
1938
|
-
|
|
1989
|
+
modelsToApply.forEach((m: any) => {
|
|
1939
1990
|
const uid = m?.uid || String(m);
|
|
1940
|
-
const curProps = m
|
|
1991
|
+
const curProps = patchPropsByModel.get(m) || {};
|
|
1941
1992
|
if (!mergedByUid.has(uid)) {
|
|
1942
1993
|
mergedByUid.set(uid, m);
|
|
1943
1994
|
mergedPropsByUid.set(uid, { ...curProps });
|
|
@@ -1956,7 +2007,11 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1956
2007
|
const newProps = { ...model.__originalProps, ...patchProps };
|
|
1957
2008
|
|
|
1958
2009
|
model.setProps(_.omit(newProps, ['hiddenModel', 'value', 'hiddenText']));
|
|
1959
|
-
model.
|
|
2010
|
+
if (typeof model.setHidden === 'function') {
|
|
2011
|
+
model.setHidden(!!newProps.hiddenModel);
|
|
2012
|
+
} else {
|
|
2013
|
+
model.hidden = !!newProps.hiddenModel;
|
|
2014
|
+
}
|
|
1960
2015
|
|
|
1961
2016
|
if (newProps.required === true) {
|
|
1962
2017
|
const rules = (model.props.rules || []).filter((rule) => !rule.required);
|
|
@@ -2018,6 +2073,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
2018
2073
|
if (typeof directSetter === 'function') {
|
|
2019
2074
|
try {
|
|
2020
2075
|
await trySetFormValues(directSetter, directCtx, 'linkage');
|
|
2076
|
+
rememberAppliedDefaultPatches(allPatches);
|
|
2021
2077
|
return;
|
|
2022
2078
|
} catch (error) {
|
|
2023
2079
|
console.warn('[linkageRules] Failed to set form values via setFormValues', {
|
|
@@ -2034,6 +2090,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
2034
2090
|
if (typeof blockSetter === 'function') {
|
|
2035
2091
|
try {
|
|
2036
2092
|
await trySetFormValues(blockSetter, blockCtx, 'linkage');
|
|
2093
|
+
rememberAppliedDefaultPatches(allPatches);
|
|
2037
2094
|
return;
|
|
2038
2095
|
} catch (error) {
|
|
2039
2096
|
console.warn('[linkageRules] Failed to set form values via setFormValues', {
|
|
@@ -2063,6 +2120,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
2063
2120
|
console.warn('[linkageRules] Failed to set form field value (fallback setFieldValue)', { path }, error);
|
|
2064
2121
|
}
|
|
2065
2122
|
});
|
|
2123
|
+
rememberAppliedDefaultPatches(allPatches);
|
|
2066
2124
|
};
|
|
2067
2125
|
|
|
2068
2126
|
export const blockLinkageRules = defineAction({
|
|
@@ -2119,6 +2177,32 @@ export const actionLinkageRules = defineAction({
|
|
|
2119
2177
|
},
|
|
2120
2178
|
});
|
|
2121
2179
|
|
|
2180
|
+
export const menuLinkageRules = defineAction({
|
|
2181
|
+
name: 'menuLinkageRules',
|
|
2182
|
+
title: tExpr('Menu linkage rules'),
|
|
2183
|
+
uiMode: 'embed',
|
|
2184
|
+
uiSchema(ctx) {
|
|
2185
|
+
return {
|
|
2186
|
+
value: {
|
|
2187
|
+
type: 'array',
|
|
2188
|
+
'x-component': LinkageRulesUI,
|
|
2189
|
+
'x-component-props': {
|
|
2190
|
+
supportedActions: getSupportedActions(ctx, ActionScene.MENU_LINKAGE_RULES),
|
|
2191
|
+
title: tExpr('Menu linkage rules'),
|
|
2192
|
+
},
|
|
2193
|
+
},
|
|
2194
|
+
};
|
|
2195
|
+
},
|
|
2196
|
+
defaultParams: {
|
|
2197
|
+
value: [],
|
|
2198
|
+
},
|
|
2199
|
+
useRawParams: true,
|
|
2200
|
+
handler: async (ctx, params) => {
|
|
2201
|
+
const resolved = await resolveLinkageRulesParamsPreservingRunJsScripts(ctx, params);
|
|
2202
|
+
return commonLinkageRulesHandler(ctx, resolved);
|
|
2203
|
+
},
|
|
2204
|
+
});
|
|
2205
|
+
|
|
2122
2206
|
export const fieldLinkageRules = defineAction({
|
|
2123
2207
|
name: 'fieldLinkageRules',
|
|
2124
2208
|
title: tExpr('Field linkage rules'),
|
|
@@ -2348,33 +2432,95 @@ export const fieldLinkageRules = defineAction({
|
|
|
2348
2432
|
return;
|
|
2349
2433
|
}
|
|
2350
2434
|
|
|
2351
|
-
const
|
|
2435
|
+
const getRowFieldIndexInfoFromModel = (model: any) => {
|
|
2352
2436
|
const fieldIndex = model?.context?.fieldIndex;
|
|
2353
2437
|
const arr = Array.isArray(fieldIndex) ? fieldIndex : [];
|
|
2354
|
-
|
|
2438
|
+
const normalized = arr.filter((it): it is string => typeof it === 'string');
|
|
2355
2439
|
const entries: Array<{ name: string; index: number }> = [];
|
|
2356
|
-
|
|
2357
|
-
|
|
2440
|
+
const path: Array<string | number> = [];
|
|
2441
|
+
for (const it of normalized) {
|
|
2358
2442
|
const [name, indexStr] = it.split(':');
|
|
2359
2443
|
const index = Number(indexStr);
|
|
2360
2444
|
if (!name || Number.isNaN(index)) continue;
|
|
2361
2445
|
entries.push({ name, index });
|
|
2446
|
+
path.push(name, index);
|
|
2362
2447
|
}
|
|
2448
|
+
return { normalized, entries, path };
|
|
2449
|
+
};
|
|
2450
|
+
|
|
2451
|
+
const getRowScopeKeyFromModel = (model: any): string | null => {
|
|
2452
|
+
const { entries } = getRowFieldIndexInfoFromModel(model);
|
|
2363
2453
|
if (!entries.length) return null;
|
|
2364
2454
|
const deepest = entries[entries.length - 1].name;
|
|
2365
2455
|
const occurrence = entries.reduce((count, e) => (e.name === deepest ? count + 1 : count), 0);
|
|
2366
2456
|
return `${deepest}#${occurrence}`;
|
|
2367
2457
|
};
|
|
2368
2458
|
|
|
2459
|
+
const getRowFieldIndexKeyFromModel = (model: any): string | null => {
|
|
2460
|
+
const { normalized } = getRowFieldIndexInfoFromModel(model);
|
|
2461
|
+
if (!normalized.length) return null;
|
|
2462
|
+
return JSON.stringify(normalized);
|
|
2463
|
+
};
|
|
2464
|
+
|
|
2465
|
+
const getRowPathFromModel = (model: any): Array<string | number> | null => {
|
|
2466
|
+
const { path } = getRowFieldIndexInfoFromModel(model);
|
|
2467
|
+
return path.length ? path : null;
|
|
2468
|
+
};
|
|
2469
|
+
|
|
2470
|
+
const getFormForRowFork = (model: any) => {
|
|
2471
|
+
return (
|
|
2472
|
+
model?.context?.form ??
|
|
2473
|
+
model?.context?.blockModel?.context?.form ??
|
|
2474
|
+
ctx.model?.context?.form ??
|
|
2475
|
+
ctx.model?.context?.blockModel?.context?.form
|
|
2476
|
+
);
|
|
2477
|
+
};
|
|
2478
|
+
|
|
2479
|
+
const isRowForkMountedInCurrentValue = (model: any): boolean => {
|
|
2480
|
+
const rowPath = getRowPathFromModel(model);
|
|
2481
|
+
if (!rowPath) return true;
|
|
2482
|
+
const form = getFormForRowFork(model);
|
|
2483
|
+
if (!form || typeof form.getFieldValue !== 'function') return true;
|
|
2484
|
+
return typeof form.getFieldValue(rowPath as any) !== 'undefined';
|
|
2485
|
+
};
|
|
2486
|
+
|
|
2487
|
+
const hasRowItemContext = (model: any): boolean => {
|
|
2488
|
+
const itemOptions = model?.context?.getPropertyOptions?.('item');
|
|
2489
|
+
if (itemOptions) return true;
|
|
2490
|
+
return typeof model?.context?.item !== 'undefined';
|
|
2491
|
+
};
|
|
2492
|
+
|
|
2493
|
+
const hasSubTableRowMarker = (model: any): boolean => {
|
|
2494
|
+
const markerOptions = model?.context?.getPropertyOptions?.('subTableRowFork');
|
|
2495
|
+
if (markerOptions) return true;
|
|
2496
|
+
return (
|
|
2497
|
+
typeof model?.context?.subTableRowFork !== 'undefined' ||
|
|
2498
|
+
typeof model?.subTableRowFork !== 'undefined' ||
|
|
2499
|
+
model?.subTableRowFork === true
|
|
2500
|
+
);
|
|
2501
|
+
};
|
|
2502
|
+
|
|
2369
2503
|
const isRowGridForkModel = (model: any): boolean => {
|
|
2370
2504
|
if (!model || typeof model !== 'object') return false;
|
|
2371
2505
|
if ((model as any)?.subModels?.field) return false;
|
|
2372
2506
|
if (!(model as any)?.subModels?.items) return false;
|
|
2373
|
-
return !!getRowScopeKeyFromModel(model);
|
|
2507
|
+
return !!getRowScopeKeyFromModel(model) && !!getRowFieldIndexKeyFromModel(model);
|
|
2508
|
+
};
|
|
2509
|
+
|
|
2510
|
+
const isSubTableRowForkModel = (model: any): boolean => {
|
|
2511
|
+
if (!model || typeof model !== 'object') return false;
|
|
2512
|
+
if (!hasSubTableRowMarker(model)) return false;
|
|
2513
|
+
if (!getRowScopeKeyFromModel(model) || !getRowFieldIndexKeyFromModel(model)) return false;
|
|
2514
|
+
return hasRowItemContext(model);
|
|
2515
|
+
};
|
|
2516
|
+
|
|
2517
|
+
const isRowScopedForkModel = (model: any): boolean => {
|
|
2518
|
+
return isRowGridForkModel(model) || isSubTableRowForkModel(model);
|
|
2374
2519
|
};
|
|
2375
2520
|
|
|
2376
|
-
const
|
|
2521
|
+
const collectRowScopedForksByKey = (): Map<string, FlowModel[]> => {
|
|
2377
2522
|
const out = new Map<string, FlowModel[]>();
|
|
2523
|
+
const seenByKey = new Map<string, Set<string>>();
|
|
2378
2524
|
const engine = ctx.engine;
|
|
2379
2525
|
if (!engine?.forEachModel) return out;
|
|
2380
2526
|
|
|
@@ -2383,9 +2529,15 @@ export const fieldLinkageRules = defineAction({
|
|
|
2383
2529
|
if (!forks || typeof forks.forEach !== 'function') return;
|
|
2384
2530
|
forks.forEach((fork: any) => {
|
|
2385
2531
|
if (!fork || fork.disposed) return;
|
|
2386
|
-
if (!
|
|
2532
|
+
if (!isRowScopedForkModel(fork)) return;
|
|
2533
|
+
if (!isRowForkMountedInCurrentValue(fork)) return;
|
|
2387
2534
|
const rowScopeKey = getRowScopeKeyFromModel(fork);
|
|
2388
|
-
|
|
2535
|
+
const fieldIndexKey = getRowFieldIndexKeyFromModel(fork);
|
|
2536
|
+
if (!rowScopeKey || !fieldIndexKey) return;
|
|
2537
|
+
const seen = seenByKey.get(rowScopeKey) || new Set<string>();
|
|
2538
|
+
if (seen.has(fieldIndexKey)) return;
|
|
2539
|
+
seen.add(fieldIndexKey);
|
|
2540
|
+
seenByKey.set(rowScopeKey, seen);
|
|
2389
2541
|
const arr = out.get(rowScopeKey) || [];
|
|
2390
2542
|
arr.push(fork as FlowModel);
|
|
2391
2543
|
out.set(rowScopeKey, arr);
|
|
@@ -2396,7 +2548,7 @@ export const fieldLinkageRules = defineAction({
|
|
|
2396
2548
|
};
|
|
2397
2549
|
|
|
2398
2550
|
const runRowScoped = async (): Promise<boolean> => {
|
|
2399
|
-
const forksByKey =
|
|
2551
|
+
const forksByKey = collectRowScopedForksByKey();
|
|
2400
2552
|
let hasAnyRowFork = false;
|
|
2401
2553
|
for (const [rowScopeKey, rowParams] of rowParamsByKey.entries()) {
|
|
2402
2554
|
const forks = forksByKey.get(rowScopeKey) || [];
|