@nocobase/client-v2 2.1.0-beta.38 → 2.1.0-beta.40
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/Application.d.ts +6 -0
- package/es/collection-field-interface/CollectionFieldInterface.d.ts +51 -15
- package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +82 -3
- package/es/collection-manager/field-configure.d.ts +80 -0
- package/es/collection-manager/field-validation.d.ts +43 -0
- package/es/collection-manager/filter-operators/index.d.ts +46 -0
- package/es/collection-manager/filter-operators/operators.d.ts +30 -0
- package/es/collection-manager/interfaces/checkbox.d.ts +1 -41
- package/es/collection-manager/interfaces/checkboxGroup.d.ts +12 -44
- package/es/collection-manager/interfaces/collection.d.ts +12 -51
- package/es/collection-manager/interfaces/color.d.ts +1 -16
- package/es/collection-manager/interfaces/createdAt.d.ts +1 -44
- package/es/collection-manager/interfaces/createdBy.d.ts +0 -4
- package/es/collection-manager/interfaces/dateOnly.d.ts +7 -44
- package/es/collection-manager/interfaces/datetime.d.ts +1 -44
- package/es/collection-manager/interfaces/datetimeNoTz.d.ts +1 -44
- package/es/collection-manager/interfaces/email.d.ts +1 -29
- package/es/collection-manager/interfaces/id.d.ts +1 -16
- package/es/collection-manager/interfaces/index.d.ts +2 -3
- package/es/collection-manager/interfaces/input.d.ts +1 -102
- package/es/collection-manager/interfaces/integer.d.ts +1 -95
- package/es/collection-manager/interfaces/json.d.ts +16 -7
- package/es/collection-manager/interfaces/m2m.d.ts +11 -19
- package/es/collection-manager/interfaces/m2o.d.ts +11 -19
- package/es/collection-manager/interfaces/markdown.d.ts +1 -63
- package/es/collection-manager/interfaces/multipleSelect.d.ts +12 -44
- package/es/collection-manager/interfaces/nanoid.d.ts +1 -34
- package/es/collection-manager/interfaces/number.d.ts +1 -87
- package/es/collection-manager/interfaces/o2m.d.ts +12 -24
- package/es/collection-manager/interfaces/obo.d.ts +207 -0
- package/es/collection-manager/interfaces/oho.d.ts +207 -0
- package/es/collection-manager/interfaces/password.d.ts +1 -56
- package/es/collection-manager/interfaces/percent.d.ts +1 -84
- package/es/collection-manager/interfaces/phone.d.ts +1 -25
- package/es/collection-manager/interfaces/properties/index.d.ts +0 -28
- package/es/collection-manager/interfaces/radioGroup.d.ts +1 -29
- package/es/collection-manager/interfaces/richText.d.ts +1 -63
- package/es/collection-manager/interfaces/select.d.ts +12 -44
- package/es/collection-manager/interfaces/snowflake-id.d.ts +1 -34
- package/es/collection-manager/interfaces/tableoid.d.ts +1 -10
- package/es/collection-manager/interfaces/textarea.d.ts +1 -51
- package/es/collection-manager/interfaces/time.d.ts +1 -16
- package/es/collection-manager/interfaces/types.d.ts +3 -12
- package/es/collection-manager/interfaces/unixTimestamp.d.ts +1 -44
- package/es/collection-manager/interfaces/updatedAt.d.ts +1 -44
- package/es/collection-manager/interfaces/updatedBy.d.ts +0 -4
- package/es/collection-manager/interfaces/url.d.ts +1 -20
- package/es/collection-manager/interfaces/uuid.d.ts +1 -34
- package/es/collection-manager/template-fields.d.ts +53 -0
- package/es/components/form/VariableInput.d.ts +53 -2
- package/es/components/form/filter/CollectionFilter.d.ts +9 -1
- package/es/components/form/filter/CollectionFilterItem.d.ts +10 -2
- package/es/flow/components/filter/useFilterOptions.d.ts +13 -1
- package/es/flow/models/blocks/assign-form/assignFieldValuesFlow.d.ts +84 -0
- package/es/flow/models/blocks/assign-form/index.d.ts +1 -0
- package/es/flow/models/blocks/form/FormActionModel.d.ts +9 -2
- package/es/index.d.ts +4 -0
- package/es/index.mjs +105 -142
- package/lib/index.js +106 -143
- package/package.json +7 -7
- package/src/Application.tsx +24 -0
- package/src/collection-field-interface/CollectionFieldInterface.ts +71 -77
- package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +201 -4
- package/src/collection-manager/field-configure.ts +548 -0
- package/src/collection-manager/field-validation.ts +195 -0
- package/src/collection-manager/filter-operators/index.ts +176 -0
- package/src/collection-manager/{interfaces/properties → filter-operators}/operators.ts +24 -13
- package/src/collection-manager/interfaces/checkbox.ts +2 -9
- package/src/collection-manager/interfaces/checkboxGroup.ts +2 -10
- package/src/collection-manager/interfaces/collection.ts +2 -15
- package/src/collection-manager/interfaces/color.ts +2 -2
- package/src/collection-manager/interfaces/createdAt.ts +2 -2
- package/src/collection-manager/interfaces/createdBy.ts +1 -12
- package/src/collection-manager/interfaces/dateOnly.ts +8 -2
- package/src/collection-manager/interfaces/datetime.ts +2 -2
- package/src/collection-manager/interfaces/datetimeNoTz.ts +2 -2
- package/src/collection-manager/interfaces/email.ts +2 -9
- package/src/collection-manager/interfaces/id.ts +1 -2
- package/src/collection-manager/interfaces/index.ts +2 -3
- package/src/collection-manager/interfaces/input.ts +2 -133
- package/src/collection-manager/interfaces/integer.ts +2 -71
- package/src/collection-manager/interfaces/json.tsx +17 -11
- package/src/collection-manager/interfaces/m2m.tsx +0 -21
- package/src/collection-manager/interfaces/m2o.tsx +0 -22
- package/src/collection-manager/interfaces/markdown.ts +2 -51
- package/src/collection-manager/interfaces/multipleSelect.ts +2 -14
- package/src/collection-manager/interfaces/nanoid.ts +2 -2
- package/src/collection-manager/interfaces/number.ts +2 -85
- package/src/collection-manager/interfaces/o2m.tsx +1 -22
- package/src/collection-manager/interfaces/obo.tsx +145 -0
- package/src/collection-manager/interfaces/oho.tsx +145 -0
- package/src/collection-manager/interfaces/password.ts +2 -44
- package/src/collection-manager/interfaces/percent.ts +2 -74
- package/src/collection-manager/interfaces/phone.ts +2 -2
- package/src/collection-manager/interfaces/properties/index.ts +0 -133
- package/src/collection-manager/interfaces/radioGroup.ts +2 -2
- package/src/collection-manager/interfaces/richText.ts +2 -51
- package/src/collection-manager/interfaces/select.ts +2 -14
- package/src/collection-manager/interfaces/snowflake-id.ts +2 -2
- package/src/collection-manager/interfaces/tableoid.ts +1 -2
- package/src/collection-manager/interfaces/textarea.ts +2 -51
- package/src/collection-manager/interfaces/time.ts +2 -2
- package/src/collection-manager/interfaces/types.ts +4 -12
- package/src/collection-manager/interfaces/unixTimestamp.tsx +2 -2
- package/src/collection-manager/interfaces/updatedAt.ts +2 -2
- package/src/collection-manager/interfaces/updatedBy.ts +1 -12
- package/src/collection-manager/interfaces/url.ts +2 -4
- package/src/collection-manager/interfaces/uuid.ts +2 -2
- package/src/collection-manager/template-fields.ts +109 -0
- package/src/components/README.md +1 -0
- package/src/components/README.zh-CN.md +1 -0
- package/src/components/form/VariableInput.tsx +101 -28
- package/src/components/form/__tests__/VariableInput.test.ts +85 -0
- package/src/components/form/filter/CollectionFilter.tsx +11 -1
- package/src/components/form/filter/CollectionFilterItem.tsx +12 -4
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +42 -0
- package/src/components/form/filter/useFilterActionProps.ts +7 -4
- package/src/flow/components/filter/useFilterOptions.ts +27 -5
- package/src/flow/models/actions/UpdateRecordActionModel.tsx +14 -95
- package/src/flow/models/actions/UpdateRecordActionUtils.ts +4 -7
- package/src/flow/models/actions/__tests__/AssignFormRefill.test.ts +26 -1
- package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +63 -2
- package/src/flow/models/blocks/assign-form/assignFieldValuesFlow.tsx +206 -0
- package/src/flow/models/blocks/assign-form/index.ts +1 -0
- package/src/flow/models/blocks/form/FormActionModel.tsx +30 -3
- package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +71 -0
- package/src/flow/models/blocks/form/submitHandler.ts +8 -1
- package/src/flow/models/fields/CollectionSelectorFieldModel.tsx +8 -2
- package/src/flow/models/fields/DisplayEnumFieldModel.tsx +8 -2
- package/src/index.ts +4 -0
- package/es/collection-manager/interfaces/linkTo.d.ts +0 -90
- package/es/collection-manager/interfaces/o2o.d.ts +0 -621
- package/es/collection-manager/interfaces/properties/operators.d.ts +0 -294
- package/es/collection-manager/interfaces/subTable.d.ts +0 -172
- package/src/collection-manager/interfaces/linkTo.ts +0 -120
- package/src/collection-manager/interfaces/o2o.tsx +0 -561
- package/src/collection-manager/interfaces/subTable.ts +0 -218
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { describe, it, expect } from 'vitest';
|
|
11
11
|
import { FlowEngine, FlowModel } from '@nocobase/flow-engine';
|
|
12
|
-
import { UpdateRecordActionModel } from '../../..'; // 这样可以解决循环依赖问题
|
|
12
|
+
import { FormSubmitActionModel, UpdateRecordActionModel } from '../../..'; // 这样可以解决循环依赖问题
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* 精简版 AssignFormModel(仅用于单测):
|
|
@@ -52,4 +52,29 @@ describe('AssignForm value refill and save (beforeParamsSave)', () => {
|
|
|
52
52
|
const saved = action.getStepParams('assignSettings', 'assignFieldValues');
|
|
53
53
|
expect(saved?.assignedValues).toEqual({ nickname: 'Alice', score: 99 });
|
|
54
54
|
});
|
|
55
|
+
|
|
56
|
+
it('FormSubmitActionModel: reuses assignFieldValues step and saves assignedValues from AssignForm', async () => {
|
|
57
|
+
const root = new FlowEngine();
|
|
58
|
+
|
|
59
|
+
root.registerModels({ FormSubmitActionModel, AssignFormModel: TestAssignFormModel });
|
|
60
|
+
|
|
61
|
+
const action = root.createModel<FormSubmitActionModel>({ use: 'FormSubmitActionModel', uid: 'submit-u' });
|
|
62
|
+
const form = root.createModel<TestAssignFormModel>({
|
|
63
|
+
use: 'AssignFormModel',
|
|
64
|
+
uid: 'submit-form-u',
|
|
65
|
+
parentId: action.uid,
|
|
66
|
+
subKey: 'assignForm',
|
|
67
|
+
});
|
|
68
|
+
form.setAssignedValues({ status: 'published' });
|
|
69
|
+
action.assignFormUid = form.uid;
|
|
70
|
+
|
|
71
|
+
const flow = action.getFlow('submitSettings') as any;
|
|
72
|
+
const step = flow?.steps?.assignFieldValues;
|
|
73
|
+
expect(step?.beforeParamsSave).toBeTypeOf('function');
|
|
74
|
+
|
|
75
|
+
await step.beforeParamsSave({ engine: root, model: action });
|
|
76
|
+
|
|
77
|
+
const saved = action.getStepParams('submitSettings', 'assignFieldValues');
|
|
78
|
+
expect(saved?.assignedValues).toEqual({ status: 'published' });
|
|
79
|
+
});
|
|
55
80
|
});
|
|
@@ -28,6 +28,64 @@ import { customAlphabet as Alphabet } from 'nanoid';
|
|
|
28
28
|
import { ensureOptionsFromUiSchemaEnumIfAbsent } from '../../../internal/utils/enumOptionsUtils';
|
|
29
29
|
import { RunJSValueEditor } from '../../../components/RunJSValueEditor';
|
|
30
30
|
|
|
31
|
+
type AssignFormTempOriginField = {
|
|
32
|
+
uid?: string;
|
|
33
|
+
props?: {
|
|
34
|
+
fieldNames?: unknown;
|
|
35
|
+
titleField?: unknown;
|
|
36
|
+
};
|
|
37
|
+
getStepParams?: (flowKey: string, stepKey: string) => unknown;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function getAssignFormTempFieldOptions(
|
|
41
|
+
originField: AssignFormTempOriginField | undefined,
|
|
42
|
+
init: Record<string, unknown>,
|
|
43
|
+
) {
|
|
44
|
+
const inheritedFieldNames = originField?.props?.fieldNames;
|
|
45
|
+
const inheritedTitleField = originField?.props?.titleField;
|
|
46
|
+
const inheritedSelectFieldNamesStep = originField?.getStepParams?.('selectSettings', 'fieldNames');
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
stepParams: {
|
|
50
|
+
fieldSettings: {
|
|
51
|
+
init,
|
|
52
|
+
},
|
|
53
|
+
...(inheritedSelectFieldNamesStep
|
|
54
|
+
? {
|
|
55
|
+
selectSettings: {
|
|
56
|
+
fieldNames: inheritedSelectFieldNamesStep,
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
: {}),
|
|
60
|
+
},
|
|
61
|
+
props: {
|
|
62
|
+
...(typeof inheritedFieldNames !== 'undefined' ? { fieldNames: inheritedFieldNames } : {}),
|
|
63
|
+
...(typeof inheritedTitleField !== 'undefined' ? { titleField: inheritedTitleField } : {}),
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function stringifyAssignFormTempFieldOption(value: unknown) {
|
|
69
|
+
try {
|
|
70
|
+
return JSON.stringify(value) ?? '';
|
|
71
|
+
} catch {
|
|
72
|
+
return String(value);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getAssignFormTempFieldRefreshKey(originField: AssignFormTempOriginField | undefined) {
|
|
77
|
+
const inheritedFieldNames = originField?.props?.fieldNames;
|
|
78
|
+
const inheritedTitleField = originField?.props?.titleField;
|
|
79
|
+
const inheritedSelectFieldNamesStep = originField?.getStepParams?.('selectSettings', 'fieldNames');
|
|
80
|
+
|
|
81
|
+
return [
|
|
82
|
+
originField?.uid || '',
|
|
83
|
+
stringifyAssignFormTempFieldOption(inheritedFieldNames),
|
|
84
|
+
stringifyAssignFormTempFieldOption(inheritedTitleField),
|
|
85
|
+
stringifyAssignFormTempFieldOption(inheritedSelectFieldNamesStep),
|
|
86
|
+
].join('|');
|
|
87
|
+
}
|
|
88
|
+
|
|
31
89
|
/**
|
|
32
90
|
* 使用 FormItemModel 的“表单项”包装,内部渲染 VariableInput,并将“常量”映射到临时字段模型。
|
|
33
91
|
*/
|
|
@@ -108,6 +166,8 @@ export class AssignFormItemModel extends FormItemModel {
|
|
|
108
166
|
const init = this.fieldInit;
|
|
109
167
|
const namePath = this.props?.name || (this.fieldPath ? [this.fieldPath] : undefined);
|
|
110
168
|
const fieldPath = this.fieldPath;
|
|
169
|
+
const originField = this.subModels?.field as AssignFormTempOriginField | undefined;
|
|
170
|
+
const tempFieldRefreshKey = getAssignFormTempFieldRefreshKey(originField);
|
|
111
171
|
|
|
112
172
|
const FieldRow: React.FC = () => {
|
|
113
173
|
const [tempRoot, setTempRoot] = React.useState<any>(null);
|
|
@@ -121,13 +181,14 @@ export class AssignFormItemModel extends FormItemModel {
|
|
|
121
181
|
return;
|
|
122
182
|
}
|
|
123
183
|
const fieldModel = binding.modelName;
|
|
184
|
+
const tempFieldOptions = getAssignFormTempFieldOptions(originField, init);
|
|
124
185
|
const created = ctx?.engine?.createModel?.({
|
|
125
186
|
use: 'VariableFieldFormModel',
|
|
126
187
|
subModels: {
|
|
127
188
|
fields: [
|
|
128
189
|
{
|
|
129
190
|
use: fieldModel,
|
|
130
|
-
|
|
191
|
+
...tempFieldOptions,
|
|
131
192
|
},
|
|
132
193
|
],
|
|
133
194
|
},
|
|
@@ -302,7 +363,7 @@ export class AssignFormItemModel extends FormItemModel {
|
|
|
302
363
|
);
|
|
303
364
|
};
|
|
304
365
|
|
|
305
|
-
return <FieldRow />;
|
|
366
|
+
return <FieldRow key={tempFieldRefreshKey} />;
|
|
306
367
|
}
|
|
307
368
|
}
|
|
308
369
|
|
|
@@ -0,0 +1,206 @@
|
|
|
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 {
|
|
11
|
+
FlowModelRenderer,
|
|
12
|
+
resolveRunJSObjectValues,
|
|
13
|
+
tExpr,
|
|
14
|
+
type FlowModelContext,
|
|
15
|
+
useFlowEngine,
|
|
16
|
+
useFlowSettingsContext,
|
|
17
|
+
} from '@nocobase/flow-engine';
|
|
18
|
+
import React, { useEffect, useRef } from 'react';
|
|
19
|
+
import { CollectionActionModel } from '../../base/CollectionActionModel';
|
|
20
|
+
import { RecordActionModel } from '../../base/RecordActionModel';
|
|
21
|
+
import { AssignFormModel } from './AssignFormModel';
|
|
22
|
+
|
|
23
|
+
export const ASSIGN_FIELD_VALUES_STEP_KEY = 'assignFieldValues';
|
|
24
|
+
|
|
25
|
+
type AssignedValues = Record<string, unknown>;
|
|
26
|
+
|
|
27
|
+
type AssignFieldValuesCollection = {
|
|
28
|
+
dataSourceKey?: string;
|
|
29
|
+
name?: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type AssignFieldValuesContext = FlowModelContext & {
|
|
33
|
+
collection?: AssignFieldValuesCollection;
|
|
34
|
+
flowSettingsEnabled?: boolean;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type AssignFieldValuesModel = {
|
|
38
|
+
uid: string;
|
|
39
|
+
assignFormUid?: string;
|
|
40
|
+
context?: AssignFieldValuesContext;
|
|
41
|
+
getStepParams?: (flowKey: string, stepKey: string) => { assignedValues?: AssignedValues } | undefined;
|
|
42
|
+
setStepParams?: (flowKey: string, stepKey: string, params: { assignedValues: AssignedValues }) => void;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type AssignFieldValuesStepOptions = {
|
|
46
|
+
settingsFlowKey: string;
|
|
47
|
+
title?: string;
|
|
48
|
+
tipComponent?: React.ComponentType;
|
|
49
|
+
validateBeforeSave?: boolean;
|
|
50
|
+
clearRecordContext?: boolean;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function getContextCollection(ctx: AssignFieldValuesContext | undefined): AssignFieldValuesCollection | undefined {
|
|
54
|
+
const collection = ctx?.collection;
|
|
55
|
+
return collection && typeof collection === 'object' ? collection : undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getResourceInit(ctx: AssignFieldValuesContext): { dataSourceKey: string; collectionName: string } | undefined {
|
|
59
|
+
const collection = getContextCollection(ctx);
|
|
60
|
+
const dsKey = collection?.dataSourceKey;
|
|
61
|
+
const collName = collection?.name;
|
|
62
|
+
return dsKey && collName ? { dataSourceKey: dsKey, collectionName: collName } : undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createAssignFormSubModelOptions(ctx: AssignFieldValuesContext) {
|
|
66
|
+
return {
|
|
67
|
+
async: true,
|
|
68
|
+
use: 'AssignFormModel',
|
|
69
|
+
stepParams: { resourceSettings: { init: getResourceInit(ctx) } },
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getAssignFieldValuesDefaultParams(
|
|
74
|
+
ctx: {
|
|
75
|
+
model: Pick<AssignFieldValuesModel, 'getStepParams'>;
|
|
76
|
+
},
|
|
77
|
+
settingsFlowKey: string,
|
|
78
|
+
): { assignedValues: AssignedValues } {
|
|
79
|
+
const step = ctx.model.getStepParams?.(settingsFlowKey, ASSIGN_FIELD_VALUES_STEP_KEY) || {};
|
|
80
|
+
return { assignedValues: step?.assignedValues || {} };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function resolveAssignFieldValues(
|
|
84
|
+
ctx: {
|
|
85
|
+
message?: { error?: (message: string) => void };
|
|
86
|
+
t?: (message: string) => string;
|
|
87
|
+
},
|
|
88
|
+
rawAssignedValues: unknown,
|
|
89
|
+
logName = 'AssignFieldValues',
|
|
90
|
+
): Promise<AssignedValues | null> {
|
|
91
|
+
try {
|
|
92
|
+
return await resolveRunJSObjectValues(ctx, rawAssignedValues);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error(`[${logName}] RunJS execution failed`, error);
|
|
95
|
+
ctx.message?.error?.(ctx.t?.('RunJS execution failed') || 'RunJS execution failed');
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function mergeAssignFieldValues<T extends Record<string, unknown>>(
|
|
101
|
+
values: T,
|
|
102
|
+
assignedValues?: AssignedValues | null,
|
|
103
|
+
): T {
|
|
104
|
+
if (!assignedValues || typeof assignedValues !== 'object' || !Object.keys(assignedValues).length) {
|
|
105
|
+
return values;
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
...values,
|
|
109
|
+
...assignedValues,
|
|
110
|
+
} as T;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function AssignFieldsEditor(props: { settingsFlowKey: string; clearRecordContext?: boolean }) {
|
|
114
|
+
const { model, blockModel } = useFlowSettingsContext();
|
|
115
|
+
const action = model as AssignFieldValuesModel;
|
|
116
|
+
const engine = useFlowEngine();
|
|
117
|
+
const initializedRef = useRef(false);
|
|
118
|
+
const [formModel, setFormModel] = React.useState<AssignFormModel | null>(null);
|
|
119
|
+
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
let cancelled = false;
|
|
122
|
+
const loadAssignForm = async () => {
|
|
123
|
+
const loaded = (await engine.loadOrCreateModel(
|
|
124
|
+
{
|
|
125
|
+
parentId: action.uid,
|
|
126
|
+
subKey: 'assignForm',
|
|
127
|
+
use: 'AssignFormModel',
|
|
128
|
+
},
|
|
129
|
+
{ skipSave: !model.context.flowSettingsEnabled },
|
|
130
|
+
)) as AssignFormModel;
|
|
131
|
+
if (cancelled) return;
|
|
132
|
+
setFormModel(loaded);
|
|
133
|
+
action.assignFormUid = loaded?.uid || action.assignFormUid;
|
|
134
|
+
};
|
|
135
|
+
loadAssignForm();
|
|
136
|
+
return () => {
|
|
137
|
+
cancelled = true;
|
|
138
|
+
};
|
|
139
|
+
}, [action, engine, model.context.flowSettingsEnabled]);
|
|
140
|
+
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (initializedRef.current) return;
|
|
143
|
+
if (!formModel) return;
|
|
144
|
+
|
|
145
|
+
const prev = action.getStepParams?.(props.settingsFlowKey, ASSIGN_FIELD_VALUES_STEP_KEY) || {};
|
|
146
|
+
const coll = blockModel?.collection || getContextCollection(action?.context);
|
|
147
|
+
const dsKey = coll?.dataSourceKey;
|
|
148
|
+
const collName = coll?.name;
|
|
149
|
+
if (dsKey && collName) {
|
|
150
|
+
formModel.setStepParams('resourceSettings', 'init', {
|
|
151
|
+
dataSourceKey: dsKey,
|
|
152
|
+
collectionName: collName,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
formModel.setInitialAssignedValues(prev?.assignedValues || {});
|
|
156
|
+
|
|
157
|
+
const isBulk =
|
|
158
|
+
props.clearRecordContext || (action instanceof CollectionActionModel && !(action instanceof RecordActionModel));
|
|
159
|
+
if (isBulk && formModel.context?.defineProperty) {
|
|
160
|
+
formModel.context.defineProperty('record', { get: () => undefined, cache: false });
|
|
161
|
+
}
|
|
162
|
+
initializedRef.current = true;
|
|
163
|
+
}, [action, blockModel?.collection, formModel, props.clearRecordContext, props.settingsFlowKey]);
|
|
164
|
+
|
|
165
|
+
return formModel ? <FlowModelRenderer model={formModel} showFlowSettings={false} /> : null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function createAssignFieldValuesStep(options: AssignFieldValuesStepOptions) {
|
|
169
|
+
return {
|
|
170
|
+
title: options.title || tExpr('Assign field values'),
|
|
171
|
+
uiSchema() {
|
|
172
|
+
return {
|
|
173
|
+
tip: options.tipComponent
|
|
174
|
+
? {
|
|
175
|
+
'x-decorator': 'FormItem',
|
|
176
|
+
'x-component': options.tipComponent,
|
|
177
|
+
}
|
|
178
|
+
: undefined,
|
|
179
|
+
editor: {
|
|
180
|
+
'x-decorator': 'FormItem',
|
|
181
|
+
'x-component': () => (
|
|
182
|
+
<AssignFieldsEditor
|
|
183
|
+
settingsFlowKey={options.settingsFlowKey}
|
|
184
|
+
clearRecordContext={options.clearRecordContext}
|
|
185
|
+
/>
|
|
186
|
+
),
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
async beforeParamsSave(ctx: {
|
|
191
|
+
model: AssignFieldValuesModel;
|
|
192
|
+
engine: {
|
|
193
|
+
getModel?: (uid: string, fromRoot?: boolean) => AssignFormModel | undefined;
|
|
194
|
+
};
|
|
195
|
+
}) {
|
|
196
|
+
const form = ctx.model.assignFormUid ? ctx.engine.getModel?.(ctx.model.assignFormUid, true) : undefined;
|
|
197
|
+
if (!form) return;
|
|
198
|
+
if (options.validateBeforeSave) {
|
|
199
|
+
await form.form?.validateFields?.();
|
|
200
|
+
}
|
|
201
|
+
const assignedValues = form.getAssignedValues?.() || {};
|
|
202
|
+
ctx.model.setStepParams?.(options.settingsFlowKey, ASSIGN_FIELD_VALUES_STEP_KEY, { assignedValues });
|
|
203
|
+
},
|
|
204
|
+
handler() {},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
@@ -7,15 +7,27 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { tExpr } from '@nocobase/flow-engine';
|
|
10
|
+
import { DefaultStructure, FlowExitAllException, tExpr } from '@nocobase/flow-engine';
|
|
11
11
|
import { ButtonProps } from 'antd';
|
|
12
12
|
import { AxiosRequestConfig } from 'axios';
|
|
13
13
|
import { ActionModel } from '../../base/ActionModelCore';
|
|
14
|
+
import { AssignFormModel } from '../assign-form/AssignFormModel';
|
|
15
|
+
import {
|
|
16
|
+
createAssignFieldValuesStep,
|
|
17
|
+
createAssignFormSubModelOptions,
|
|
18
|
+
getAssignFieldValuesDefaultParams,
|
|
19
|
+
} from '../assign-form/assignFieldValuesFlow';
|
|
14
20
|
import { shouldSkipSubmitValidation, validateSubmitForm } from './submitValues';
|
|
15
21
|
|
|
16
|
-
export class FormActionModel extends ActionModel {}
|
|
22
|
+
export class FormActionModel<T extends DefaultStructure = DefaultStructure> extends ActionModel<T> {}
|
|
23
|
+
|
|
24
|
+
export class FormSubmitActionModel extends FormActionModel<{
|
|
25
|
+
subModels: {
|
|
26
|
+
assignForm: AssignFormModel;
|
|
27
|
+
};
|
|
28
|
+
}> {
|
|
29
|
+
assignFormUid?: string;
|
|
17
30
|
|
|
18
|
-
export class FormSubmitActionModel extends FormActionModel {
|
|
19
31
|
defaultProps: ButtonProps = {
|
|
20
32
|
title: tExpr('Submit'),
|
|
21
33
|
type: 'primary',
|
|
@@ -35,6 +47,11 @@ export class FormSubmitActionModel extends FormActionModel {
|
|
|
35
47
|
|
|
36
48
|
FormSubmitActionModel.define({
|
|
37
49
|
label: tExpr('Submit'),
|
|
50
|
+
createModelOptions: (ctx) => ({
|
|
51
|
+
subModels: {
|
|
52
|
+
assignForm: createAssignFormSubModelOptions(ctx),
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
38
55
|
});
|
|
39
56
|
|
|
40
57
|
FormSubmitActionModel.registerFlow({
|
|
@@ -82,7 +99,14 @@ FormSubmitActionModel.registerFlow({
|
|
|
82
99
|
},
|
|
83
100
|
handler() {},
|
|
84
101
|
},
|
|
102
|
+
assignFieldValues: createAssignFieldValuesStep({
|
|
103
|
+
settingsFlowKey: 'submitSettings',
|
|
104
|
+
title: tExpr('Assign field values'),
|
|
105
|
+
}),
|
|
85
106
|
saveResource: {
|
|
107
|
+
async defaultParams(ctx) {
|
|
108
|
+
return getAssignFieldValuesDefaultParams(ctx, 'submitSettings');
|
|
109
|
+
},
|
|
86
110
|
async handler(ctx, params) {
|
|
87
111
|
if (!ctx?.resource) {
|
|
88
112
|
throw new Error('Resource is not initialized');
|
|
@@ -96,6 +120,9 @@ FormSubmitActionModel.registerFlow({
|
|
|
96
120
|
await submitHandler(ctx, params);
|
|
97
121
|
} catch (error) {
|
|
98
122
|
ctx.model.setProps('loading', false);
|
|
123
|
+
if (error instanceof FlowExitAllException) {
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
99
126
|
// 显示保存失败提示
|
|
100
127
|
ctx.message.error(ctx.t('Save failed'));
|
|
101
128
|
console.error('Form submission error:', error);
|
|
@@ -0,0 +1,71 @@
|
|
|
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 { SingleRecordResource } from '@nocobase/flow-engine';
|
|
11
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
12
|
+
import { submitHandler } from '../submitHandler';
|
|
13
|
+
|
|
14
|
+
describe('submitHandler', () => {
|
|
15
|
+
it('merges assigned values into submitted values and lets assigned values override form values', async () => {
|
|
16
|
+
const resource = Object.create(SingleRecordResource.prototype);
|
|
17
|
+
resource.save = vi.fn(async () => ({ data: { id: 1 } }));
|
|
18
|
+
|
|
19
|
+
const blockModel = {
|
|
20
|
+
form: {
|
|
21
|
+
validateFields: vi.fn(async () => undefined),
|
|
22
|
+
getFieldsValue: vi.fn(() => ({
|
|
23
|
+
title: 'Draft title',
|
|
24
|
+
status: 'draft',
|
|
25
|
+
})),
|
|
26
|
+
resetFields: vi.fn(),
|
|
27
|
+
},
|
|
28
|
+
emitter: {
|
|
29
|
+
emit: vi.fn(),
|
|
30
|
+
},
|
|
31
|
+
resetUserModifiedFields: vi.fn(),
|
|
32
|
+
formValueRuntime: {
|
|
33
|
+
resetAfterFormReset: vi.fn(),
|
|
34
|
+
},
|
|
35
|
+
collection: {
|
|
36
|
+
name: 'posts',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const ctx = {
|
|
41
|
+
resource,
|
|
42
|
+
blockModel,
|
|
43
|
+
model: {
|
|
44
|
+
getStepParams: vi.fn(),
|
|
45
|
+
},
|
|
46
|
+
view: {
|
|
47
|
+
inputArgs: {},
|
|
48
|
+
},
|
|
49
|
+
message: {
|
|
50
|
+
error: vi.fn(),
|
|
51
|
+
},
|
|
52
|
+
t: (value: string) => value,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
await submitHandler(ctx, {
|
|
56
|
+
assignedValues: {
|
|
57
|
+
status: 'published',
|
|
58
|
+
reviewer: 'Alice',
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(resource.save).toHaveBeenCalledWith(
|
|
63
|
+
{
|
|
64
|
+
title: 'Draft title',
|
|
65
|
+
status: 'published',
|
|
66
|
+
reviewer: 'Alice',
|
|
67
|
+
},
|
|
68
|
+
undefined,
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { MultiRecordResource, SingleRecordResource } from '@nocobase/flow-engine';
|
|
11
|
+
import { mergeAssignFieldValues, resolveAssignFieldValues } from '../assign-form/assignFieldValuesFlow';
|
|
11
12
|
import type { FormBlockModel } from './FormBlockModel';
|
|
12
13
|
import { omitHiddenModelValuesFromSubmit, shouldSkipSubmitValidation, validateSubmitForm } from './submitValues';
|
|
13
14
|
|
|
@@ -24,7 +25,13 @@ export async function submitHandler(ctx, params, cb?: (values?: any, filterByTk?
|
|
|
24
25
|
skipValidator: shouldSkipSubmitValidation(ctx?.model),
|
|
25
26
|
});
|
|
26
27
|
const rawValues = blockModel.form.getFieldsValue(true);
|
|
27
|
-
const
|
|
28
|
+
const formValues = omitHiddenModelValuesFromSubmit(rawValues, blockModel);
|
|
29
|
+
const assignedValues = await resolveAssignFieldValues(ctx, params?.assignedValues, 'FormSubmitAction');
|
|
30
|
+
if (!assignedValues) {
|
|
31
|
+
ctx.exit?.();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const values = mergeAssignFieldValues(formValues, assignedValues);
|
|
28
35
|
if (resource instanceof SingleRecordResource) {
|
|
29
36
|
if (isEditFormModel) {
|
|
30
37
|
const currentFilterByTk = resource.getMeta('currentFilterByTk');
|
|
@@ -38,6 +38,12 @@ const CollectionSelector = (props) => {
|
|
|
38
38
|
);
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
function getConfiguredCollectionNames(collectionField) {
|
|
42
|
+
return (collectionField?.uiSchema?.enum || collectionField?.enum || [])
|
|
43
|
+
.map((item) => (item && typeof item === 'object' ? item.value : item))
|
|
44
|
+
.filter((value) => value !== null && typeof value !== 'undefined');
|
|
45
|
+
}
|
|
46
|
+
|
|
41
47
|
export class CollectionSelectorFieldModel extends FieldModel {
|
|
42
48
|
render() {
|
|
43
49
|
return <CollectionSelector {...this.props} />;
|
|
@@ -66,12 +72,12 @@ CollectionSelectorFieldModel.registerFlow({
|
|
|
66
72
|
});
|
|
67
73
|
} else {
|
|
68
74
|
const collections = ctx.dataSourceManager.getDataSource('main').getCollections();
|
|
69
|
-
const defaultOptions = ctx.model.context.collectionField
|
|
75
|
+
const defaultOptions = getConfiguredCollectionNames(ctx.model.context.collectionField);
|
|
70
76
|
const options = collections
|
|
71
77
|
.filter((item: any) => !item.options.hidden)
|
|
72
78
|
.filter((v) => {
|
|
73
79
|
if (defaultOptions.length) {
|
|
74
|
-
return defaultOptions.
|
|
80
|
+
return defaultOptions.includes(v.name);
|
|
75
81
|
}
|
|
76
82
|
return true;
|
|
77
83
|
})
|
|
@@ -43,6 +43,12 @@ const fieldNames = {
|
|
|
43
43
|
color: 'color',
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
function getConfiguredCollectionNames(collectionField) {
|
|
47
|
+
return (collectionField?.uiSchema?.enum || collectionField?.enum || [])
|
|
48
|
+
.map((item) => (item && typeof item === 'object' ? item.value : item))
|
|
49
|
+
.filter((value) => value !== null && typeof value !== 'undefined');
|
|
50
|
+
}
|
|
51
|
+
|
|
46
52
|
export class DisplayEnumFieldModel extends ClickableFieldModel {
|
|
47
53
|
isEmpty(value: any) {
|
|
48
54
|
return value === null || value === undefined || value === '';
|
|
@@ -134,12 +140,12 @@ DisplayEnumFieldModel.registerFlow({
|
|
|
134
140
|
});
|
|
135
141
|
} else {
|
|
136
142
|
const collections = ctx.dataSourceManager.getDataSource('main').getCollections();
|
|
137
|
-
const defaultOptions = ctx.model.context.collectionField
|
|
143
|
+
const defaultOptions = getConfiguredCollectionNames(ctx.model.context.collectionField);
|
|
138
144
|
const options = collections
|
|
139
145
|
.filter((item: any) => !item.options.hidden)
|
|
140
146
|
.filter((v) => {
|
|
141
147
|
if (defaultOptions.length) {
|
|
142
|
-
return defaultOptions.
|
|
148
|
+
return defaultOptions.includes(v.name);
|
|
143
149
|
}
|
|
144
150
|
return true;
|
|
145
151
|
})
|
package/src/index.ts
CHANGED
|
@@ -32,7 +32,11 @@ export { default as languageCodes } from './locale/languageCodes';
|
|
|
32
32
|
export * from './nocobase-buildin-plugin';
|
|
33
33
|
export * from './collection-field-interface/CollectionFieldInterface';
|
|
34
34
|
export * from './collection-field-interface/CollectionFieldInterfaceManager';
|
|
35
|
+
export * from './collection-manager/field-configure';
|
|
36
|
+
export * from './collection-manager/field-validation';
|
|
37
|
+
export * from './collection-manager/filter-operators';
|
|
35
38
|
export * from './collection-manager/interfaces';
|
|
39
|
+
export * from './collection-manager/template-fields';
|
|
36
40
|
export * from './data-source';
|
|
37
41
|
export * from './flow';
|
|
38
42
|
export { DEFAULT_DATA_SOURCE_KEY, isTitleField, isTitleFieldInterface } from './flow-compat';
|
|
@@ -1,90 +0,0 @@
|
|
|
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
|
-
import { ISchema } from '@formily/react';
|
|
10
|
-
import { CollectionFieldInterface } from '../../collection-field-interface/CollectionFieldInterface';
|
|
11
|
-
export declare class LinkToFieldInterface extends CollectionFieldInterface {
|
|
12
|
-
name: string;
|
|
13
|
-
type: string;
|
|
14
|
-
group: string;
|
|
15
|
-
order: number;
|
|
16
|
-
title: string;
|
|
17
|
-
description: string;
|
|
18
|
-
isAssociation: boolean;
|
|
19
|
-
default: {
|
|
20
|
-
type: string;
|
|
21
|
-
uiSchema: {
|
|
22
|
-
'x-component': string;
|
|
23
|
-
'x-component-props': {
|
|
24
|
-
multiple: boolean;
|
|
25
|
-
fieldNames: {
|
|
26
|
-
label: string;
|
|
27
|
-
value: string;
|
|
28
|
-
};
|
|
29
|
-
};
|
|
30
|
-
};
|
|
31
|
-
reverseField: {
|
|
32
|
-
interface: string;
|
|
33
|
-
type: string;
|
|
34
|
-
uiSchema: {
|
|
35
|
-
'x-component': string;
|
|
36
|
-
'x-component-props': {
|
|
37
|
-
multiple: boolean;
|
|
38
|
-
fieldNames: {
|
|
39
|
-
label: string;
|
|
40
|
-
value: string;
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
};
|
|
44
|
-
};
|
|
45
|
-
};
|
|
46
|
-
schemaInitialize(schema: ISchema, { readPretty, block, targetCollection }: {
|
|
47
|
-
readPretty: any;
|
|
48
|
-
block: any;
|
|
49
|
-
targetCollection: any;
|
|
50
|
-
}): void;
|
|
51
|
-
initialize: (values: any) => void;
|
|
52
|
-
properties: {
|
|
53
|
-
target: {
|
|
54
|
-
type: string;
|
|
55
|
-
title: string;
|
|
56
|
-
required: boolean;
|
|
57
|
-
'x-reactions': string[];
|
|
58
|
-
'x-decorator': string;
|
|
59
|
-
'x-component': string;
|
|
60
|
-
'x-disabled': string;
|
|
61
|
-
};
|
|
62
|
-
'uiSchema.x-component-props.multiple': {
|
|
63
|
-
type: string;
|
|
64
|
-
'x-content': string;
|
|
65
|
-
'x-decorator': string;
|
|
66
|
-
'x-component': string;
|
|
67
|
-
};
|
|
68
|
-
'uiSchema.title': {
|
|
69
|
-
type: string;
|
|
70
|
-
title: string;
|
|
71
|
-
required: boolean;
|
|
72
|
-
'x-decorator': string;
|
|
73
|
-
'x-component': string;
|
|
74
|
-
};
|
|
75
|
-
name: {
|
|
76
|
-
type: string;
|
|
77
|
-
title: string;
|
|
78
|
-
required: boolean;
|
|
79
|
-
'x-disabled': string;
|
|
80
|
-
'x-decorator': string;
|
|
81
|
-
'x-component': string;
|
|
82
|
-
'x-validator': string;
|
|
83
|
-
description: string;
|
|
84
|
-
};
|
|
85
|
-
};
|
|
86
|
-
filterable: {
|
|
87
|
-
nested: boolean;
|
|
88
|
-
children: any[];
|
|
89
|
-
};
|
|
90
|
-
}
|