@nocobase/client-v2 2.1.0-beta.38 → 2.1.0-beta.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/es/Application.d.ts +6 -0
  2. package/es/collection-field-interface/CollectionFieldInterface.d.ts +51 -15
  3. package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +82 -3
  4. package/es/collection-manager/field-configure.d.ts +80 -0
  5. package/es/collection-manager/field-validation.d.ts +43 -0
  6. package/es/collection-manager/filter-operators/index.d.ts +46 -0
  7. package/es/collection-manager/filter-operators/operators.d.ts +30 -0
  8. package/es/collection-manager/interfaces/checkbox.d.ts +1 -41
  9. package/es/collection-manager/interfaces/checkboxGroup.d.ts +12 -44
  10. package/es/collection-manager/interfaces/collection.d.ts +12 -51
  11. package/es/collection-manager/interfaces/color.d.ts +1 -16
  12. package/es/collection-manager/interfaces/createdAt.d.ts +1 -44
  13. package/es/collection-manager/interfaces/createdBy.d.ts +0 -4
  14. package/es/collection-manager/interfaces/dateOnly.d.ts +7 -44
  15. package/es/collection-manager/interfaces/datetime.d.ts +1 -44
  16. package/es/collection-manager/interfaces/datetimeNoTz.d.ts +1 -44
  17. package/es/collection-manager/interfaces/email.d.ts +1 -29
  18. package/es/collection-manager/interfaces/id.d.ts +1 -16
  19. package/es/collection-manager/interfaces/index.d.ts +2 -3
  20. package/es/collection-manager/interfaces/input.d.ts +1 -102
  21. package/es/collection-manager/interfaces/integer.d.ts +1 -95
  22. package/es/collection-manager/interfaces/json.d.ts +16 -7
  23. package/es/collection-manager/interfaces/m2m.d.ts +11 -19
  24. package/es/collection-manager/interfaces/m2o.d.ts +11 -19
  25. package/es/collection-manager/interfaces/markdown.d.ts +1 -63
  26. package/es/collection-manager/interfaces/multipleSelect.d.ts +12 -44
  27. package/es/collection-manager/interfaces/nanoid.d.ts +1 -34
  28. package/es/collection-manager/interfaces/number.d.ts +1 -87
  29. package/es/collection-manager/interfaces/o2m.d.ts +12 -24
  30. package/es/collection-manager/interfaces/obo.d.ts +207 -0
  31. package/es/collection-manager/interfaces/oho.d.ts +207 -0
  32. package/es/collection-manager/interfaces/password.d.ts +1 -56
  33. package/es/collection-manager/interfaces/percent.d.ts +1 -84
  34. package/es/collection-manager/interfaces/phone.d.ts +1 -25
  35. package/es/collection-manager/interfaces/properties/index.d.ts +0 -28
  36. package/es/collection-manager/interfaces/radioGroup.d.ts +1 -29
  37. package/es/collection-manager/interfaces/richText.d.ts +1 -63
  38. package/es/collection-manager/interfaces/select.d.ts +12 -44
  39. package/es/collection-manager/interfaces/snowflake-id.d.ts +1 -34
  40. package/es/collection-manager/interfaces/tableoid.d.ts +1 -10
  41. package/es/collection-manager/interfaces/textarea.d.ts +1 -51
  42. package/es/collection-manager/interfaces/time.d.ts +1 -16
  43. package/es/collection-manager/interfaces/types.d.ts +3 -12
  44. package/es/collection-manager/interfaces/unixTimestamp.d.ts +1 -44
  45. package/es/collection-manager/interfaces/updatedAt.d.ts +1 -44
  46. package/es/collection-manager/interfaces/updatedBy.d.ts +0 -4
  47. package/es/collection-manager/interfaces/url.d.ts +1 -20
  48. package/es/collection-manager/interfaces/uuid.d.ts +1 -34
  49. package/es/collection-manager/template-fields.d.ts +53 -0
  50. package/es/components/form/VariableInput.d.ts +53 -2
  51. package/es/components/form/filter/CollectionFilter.d.ts +9 -1
  52. package/es/components/form/filter/CollectionFilterItem.d.ts +10 -2
  53. package/es/flow/components/filter/useFilterOptions.d.ts +13 -1
  54. package/es/flow/models/blocks/assign-form/assignFieldValuesFlow.d.ts +84 -0
  55. package/es/flow/models/blocks/assign-form/index.d.ts +1 -0
  56. package/es/flow/models/blocks/form/FormActionModel.d.ts +9 -2
  57. package/es/index.d.ts +4 -0
  58. package/es/index.mjs +105 -142
  59. package/lib/index.js +106 -143
  60. package/package.json +7 -7
  61. package/src/Application.tsx +24 -0
  62. package/src/collection-field-interface/CollectionFieldInterface.ts +71 -77
  63. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +201 -4
  64. package/src/collection-manager/field-configure.ts +548 -0
  65. package/src/collection-manager/field-validation.ts +195 -0
  66. package/src/collection-manager/filter-operators/index.ts +176 -0
  67. package/src/collection-manager/{interfaces/properties → filter-operators}/operators.ts +24 -13
  68. package/src/collection-manager/interfaces/checkbox.ts +2 -9
  69. package/src/collection-manager/interfaces/checkboxGroup.ts +2 -10
  70. package/src/collection-manager/interfaces/collection.ts +2 -15
  71. package/src/collection-manager/interfaces/color.ts +2 -2
  72. package/src/collection-manager/interfaces/createdAt.ts +2 -2
  73. package/src/collection-manager/interfaces/createdBy.ts +1 -12
  74. package/src/collection-manager/interfaces/dateOnly.ts +8 -2
  75. package/src/collection-manager/interfaces/datetime.ts +2 -2
  76. package/src/collection-manager/interfaces/datetimeNoTz.ts +2 -2
  77. package/src/collection-manager/interfaces/email.ts +2 -9
  78. package/src/collection-manager/interfaces/id.ts +1 -2
  79. package/src/collection-manager/interfaces/index.ts +2 -3
  80. package/src/collection-manager/interfaces/input.ts +2 -133
  81. package/src/collection-manager/interfaces/integer.ts +2 -71
  82. package/src/collection-manager/interfaces/json.tsx +17 -11
  83. package/src/collection-manager/interfaces/m2m.tsx +0 -21
  84. package/src/collection-manager/interfaces/m2o.tsx +0 -22
  85. package/src/collection-manager/interfaces/markdown.ts +2 -51
  86. package/src/collection-manager/interfaces/multipleSelect.ts +2 -14
  87. package/src/collection-manager/interfaces/nanoid.ts +2 -2
  88. package/src/collection-manager/interfaces/number.ts +2 -85
  89. package/src/collection-manager/interfaces/o2m.tsx +1 -22
  90. package/src/collection-manager/interfaces/obo.tsx +145 -0
  91. package/src/collection-manager/interfaces/oho.tsx +145 -0
  92. package/src/collection-manager/interfaces/password.ts +2 -44
  93. package/src/collection-manager/interfaces/percent.ts +2 -74
  94. package/src/collection-manager/interfaces/phone.ts +2 -2
  95. package/src/collection-manager/interfaces/properties/index.ts +0 -133
  96. package/src/collection-manager/interfaces/radioGroup.ts +2 -2
  97. package/src/collection-manager/interfaces/richText.ts +2 -51
  98. package/src/collection-manager/interfaces/select.ts +2 -14
  99. package/src/collection-manager/interfaces/snowflake-id.ts +2 -2
  100. package/src/collection-manager/interfaces/tableoid.ts +1 -2
  101. package/src/collection-manager/interfaces/textarea.ts +2 -51
  102. package/src/collection-manager/interfaces/time.ts +2 -2
  103. package/src/collection-manager/interfaces/types.ts +4 -12
  104. package/src/collection-manager/interfaces/unixTimestamp.tsx +2 -2
  105. package/src/collection-manager/interfaces/updatedAt.ts +2 -2
  106. package/src/collection-manager/interfaces/updatedBy.ts +1 -12
  107. package/src/collection-manager/interfaces/url.ts +2 -4
  108. package/src/collection-manager/interfaces/uuid.ts +2 -2
  109. package/src/collection-manager/template-fields.ts +109 -0
  110. package/src/components/README.md +1 -0
  111. package/src/components/README.zh-CN.md +1 -0
  112. package/src/components/form/VariableInput.tsx +101 -28
  113. package/src/components/form/__tests__/VariableInput.test.ts +85 -0
  114. package/src/components/form/filter/CollectionFilter.tsx +11 -1
  115. package/src/components/form/filter/CollectionFilterItem.tsx +12 -4
  116. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +42 -0
  117. package/src/components/form/filter/useFilterActionProps.ts +7 -4
  118. package/src/flow/components/filter/useFilterOptions.ts +27 -5
  119. package/src/flow/models/actions/UpdateRecordActionModel.tsx +14 -95
  120. package/src/flow/models/actions/UpdateRecordActionUtils.ts +4 -7
  121. package/src/flow/models/actions/__tests__/AssignFormRefill.test.ts +26 -1
  122. package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +63 -2
  123. package/src/flow/models/blocks/assign-form/assignFieldValuesFlow.tsx +206 -0
  124. package/src/flow/models/blocks/assign-form/index.ts +1 -0
  125. package/src/flow/models/blocks/form/FormActionModel.tsx +30 -3
  126. package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +71 -0
  127. package/src/flow/models/blocks/form/submitHandler.ts +8 -1
  128. package/src/flow/models/fields/CollectionSelectorFieldModel.tsx +8 -2
  129. package/src/flow/models/fields/DisplayEnumFieldModel.tsx +8 -2
  130. package/src/index.ts +4 -0
  131. package/es/collection-manager/interfaces/linkTo.d.ts +0 -90
  132. package/es/collection-manager/interfaces/o2o.d.ts +0 -621
  133. package/es/collection-manager/interfaces/properties/operators.d.ts +0 -294
  134. package/es/collection-manager/interfaces/subTable.d.ts +0 -172
  135. package/src/collection-manager/interfaces/linkTo.ts +0 -120
  136. package/src/collection-manager/interfaces/o2o.tsx +0 -561
  137. 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
- stepParams: { fieldSettings: { init } },
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
+ }
@@ -10,3 +10,4 @@
10
10
  export * from './AssignFormModel';
11
11
  export * from './AssignFormGridModel';
12
12
  export * from './AssignFormItemModel';
13
+ export * from './assignFieldValuesFlow';
@@ -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 values = omitHiddenModelValuesFromSubmit(rawValues, blockModel);
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.enum || [];
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.find((c) => c.value === v.name);
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.enum || [];
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.find((c) => c.value === v.name);
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
- }