@nocobase/client-v2 2.1.0-alpha.40 → 2.1.0-alpha.45

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 (278) hide show
  1. package/es/Application.d.ts +7 -0
  2. package/es/BaseApplication.d.ts +13 -0
  3. package/es/RouterManager.d.ts +1 -0
  4. package/es/collection-field-interface/CollectionFieldInterface.d.ts +51 -15
  5. package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +82 -3
  6. package/es/collection-manager/field-configure.d.ts +80 -0
  7. package/es/collection-manager/field-validation.d.ts +43 -0
  8. package/es/collection-manager/filter-operators/index.d.ts +46 -0
  9. package/es/collection-manager/filter-operators/operators.d.ts +30 -0
  10. package/es/collection-manager/interfaces/checkbox.d.ts +1 -41
  11. package/es/collection-manager/interfaces/checkboxGroup.d.ts +12 -44
  12. package/es/collection-manager/interfaces/collection.d.ts +12 -51
  13. package/es/collection-manager/interfaces/color.d.ts +1 -16
  14. package/es/collection-manager/interfaces/createdAt.d.ts +1 -44
  15. package/es/collection-manager/interfaces/createdBy.d.ts +0 -4
  16. package/es/collection-manager/interfaces/dateOnly.d.ts +7 -44
  17. package/es/collection-manager/interfaces/datetime.d.ts +1 -44
  18. package/es/collection-manager/interfaces/datetimeNoTz.d.ts +1 -44
  19. package/es/collection-manager/interfaces/email.d.ts +1 -29
  20. package/es/collection-manager/interfaces/id.d.ts +1 -16
  21. package/es/collection-manager/interfaces/index.d.ts +2 -3
  22. package/es/collection-manager/interfaces/input.d.ts +1 -102
  23. package/es/collection-manager/interfaces/integer.d.ts +1 -95
  24. package/es/collection-manager/interfaces/json.d.ts +16 -7
  25. package/es/collection-manager/interfaces/m2m.d.ts +11 -19
  26. package/es/collection-manager/interfaces/m2o.d.ts +11 -19
  27. package/es/collection-manager/interfaces/markdown.d.ts +1 -63
  28. package/es/collection-manager/interfaces/multipleSelect.d.ts +12 -44
  29. package/es/collection-manager/interfaces/nanoid.d.ts +1 -34
  30. package/es/collection-manager/interfaces/number.d.ts +1 -87
  31. package/es/collection-manager/interfaces/o2m.d.ts +12 -24
  32. package/es/collection-manager/interfaces/obo.d.ts +207 -0
  33. package/es/collection-manager/interfaces/oho.d.ts +207 -0
  34. package/es/collection-manager/interfaces/password.d.ts +1 -56
  35. package/es/collection-manager/interfaces/percent.d.ts +1 -84
  36. package/es/collection-manager/interfaces/phone.d.ts +1 -25
  37. package/es/collection-manager/interfaces/properties/index.d.ts +0 -28
  38. package/es/collection-manager/interfaces/radioGroup.d.ts +1 -29
  39. package/es/collection-manager/interfaces/richText.d.ts +1 -63
  40. package/es/collection-manager/interfaces/select.d.ts +12 -44
  41. package/es/collection-manager/interfaces/snowflake-id.d.ts +1 -34
  42. package/es/collection-manager/interfaces/tableoid.d.ts +1 -10
  43. package/es/collection-manager/interfaces/textarea.d.ts +1 -51
  44. package/es/collection-manager/interfaces/time.d.ts +1 -16
  45. package/es/collection-manager/interfaces/types.d.ts +3 -12
  46. package/es/collection-manager/interfaces/unixTimestamp.d.ts +1 -44
  47. package/es/collection-manager/interfaces/updatedAt.d.ts +1 -44
  48. package/es/collection-manager/interfaces/updatedBy.d.ts +0 -4
  49. package/es/collection-manager/interfaces/url.d.ts +1 -20
  50. package/es/collection-manager/interfaces/uuid.d.ts +1 -34
  51. package/es/collection-manager/template-fields.d.ts +53 -0
  52. package/es/components/KeepAlive.d.ts +22 -0
  53. package/es/components/RouterBridge.d.ts +9 -0
  54. package/es/components/form/DialogFormLayout.d.ts +5 -29
  55. package/es/components/form/VariableInput.d.ts +53 -2
  56. package/es/components/form/filter/CollectionFilter.d.ts +49 -0
  57. package/es/components/form/filter/CollectionFilterItem.d.ts +49 -0
  58. package/es/components/form/filter/DateFilterDynamicComponent.d.ts +57 -0
  59. package/es/components/form/filter/FilterValueInput.d.ts +29 -0
  60. package/es/components/form/filter/index.d.ts +11 -0
  61. package/es/components/form/filter/useFilterActionProps.d.ts +96 -0
  62. package/es/components/form/index.d.ts +1 -0
  63. package/es/data-source/ExtendCollectionsProvider.d.ts +50 -0
  64. package/es/data-source/index.d.ts +9 -0
  65. package/es/flow/FlowPage.d.ts +2 -1
  66. package/es/flow/admin-shell/AdminLayoutRouteCoordinator.d.ts +8 -40
  67. package/es/flow/admin-shell/BaseLayoutModel.d.ts +89 -0
  68. package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +74 -0
  69. package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +12 -0
  70. package/es/flow/admin-shell/admin-layout/AdminLayoutModel.d.ts +7 -92
  71. package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +11 -0
  72. package/es/flow/admin-shell/admin-layout/index.d.ts +3 -0
  73. package/es/flow/admin-shell/admin-layout/useApplications.d.ts +3 -2
  74. package/es/flow/admin-shell/useAdminLayoutRoutePage.d.ts +2 -2
  75. package/es/flow/admin-shell/useLayoutRoutePage.d.ts +23 -0
  76. package/es/flow/components/FlowRoute.d.ts +10 -1
  77. package/es/flow/components/filter/index.d.ts +2 -0
  78. package/es/flow/components/filter/useFilterOptions.d.ts +66 -0
  79. package/es/flow/index.d.ts +4 -0
  80. package/es/flow/models/base/PageModel/PageModel.d.ts +3 -1
  81. package/es/flow/models/blocks/assign-form/assignFieldValuesFlow.d.ts +84 -0
  82. package/es/flow/models/blocks/assign-form/index.d.ts +1 -0
  83. package/es/flow/models/blocks/form/FormActionGroupModel.d.ts +1 -0
  84. package/es/flow/models/blocks/form/FormActionModel.d.ts +9 -2
  85. package/es/flow/models/blocks/table/TableBlockModel.d.ts +10 -0
  86. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.d.ts +1 -1
  87. package/es/flow-compat/passwordUtils.d.ts +1 -1
  88. package/es/index.d.ts +6 -0
  89. package/es/index.mjs +552 -459
  90. package/es/layout-manager/LayoutContentRoute.d.ts +14 -0
  91. package/es/layout-manager/LayoutManager.d.ts +22 -0
  92. package/es/layout-manager/LayoutRoute.d.ts +14 -0
  93. package/es/layout-manager/index.d.ts +13 -0
  94. package/es/layout-manager/types.d.ts +20 -0
  95. package/es/layout-manager/utils.d.ts +14 -0
  96. package/es/nocobase-buildin-plugin/index.d.ts +3 -10
  97. package/es/settings-center/index.d.ts +1 -1
  98. package/es/settings-center/plugin-manager/BulkEnableButton.d.ts +15 -0
  99. package/es/settings-center/plugin-manager/PluginCard.d.ts +15 -0
  100. package/es/settings-center/plugin-manager/PluginDetail.d.ts +16 -0
  101. package/es/settings-center/{PluginManagerPage.d.ts → plugin-manager/index.d.ts} +1 -7
  102. package/es/settings-center/plugin-manager/types.d.ts +34 -0
  103. package/lib/index.js +552 -459
  104. package/package.json +8 -7
  105. package/src/Application.tsx +51 -12
  106. package/src/BaseApplication.tsx +32 -0
  107. package/src/PluginSettingsManager.ts +1 -1
  108. package/src/RouterManager.tsx +17 -1
  109. package/src/__tests__/PluginSettingsManager.test.ts +41 -2
  110. package/src/__tests__/app.test.tsx +17 -1
  111. package/src/__tests__/globalDeps.test.ts +1 -0
  112. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +45 -2
  113. package/src/__tests__/plugin-manager.test.tsx +177 -0
  114. package/src/__tests__/settings-center.test.tsx +24 -2
  115. package/src/collection-field-interface/CollectionFieldInterface.ts +71 -77
  116. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +201 -4
  117. package/src/collection-manager/field-configure.ts +548 -0
  118. package/src/collection-manager/field-validation.ts +195 -0
  119. package/src/collection-manager/filter-operators/index.ts +176 -0
  120. package/src/collection-manager/{interfaces/properties → filter-operators}/operators.ts +24 -13
  121. package/src/collection-manager/interfaces/checkbox.ts +2 -9
  122. package/src/collection-manager/interfaces/checkboxGroup.ts +2 -10
  123. package/src/collection-manager/interfaces/collection.ts +2 -15
  124. package/src/collection-manager/interfaces/color.ts +2 -2
  125. package/src/collection-manager/interfaces/createdAt.ts +2 -2
  126. package/src/collection-manager/interfaces/createdBy.ts +1 -12
  127. package/src/collection-manager/interfaces/dateOnly.ts +8 -2
  128. package/src/collection-manager/interfaces/datetime.ts +2 -2
  129. package/src/collection-manager/interfaces/datetimeNoTz.ts +2 -2
  130. package/src/collection-manager/interfaces/email.ts +2 -9
  131. package/src/collection-manager/interfaces/id.ts +1 -2
  132. package/src/collection-manager/interfaces/index.ts +2 -3
  133. package/src/collection-manager/interfaces/input.ts +2 -133
  134. package/src/collection-manager/interfaces/integer.ts +2 -71
  135. package/src/collection-manager/interfaces/json.tsx +17 -11
  136. package/src/collection-manager/interfaces/m2m.tsx +0 -21
  137. package/src/collection-manager/interfaces/m2o.tsx +0 -22
  138. package/src/collection-manager/interfaces/markdown.ts +2 -51
  139. package/src/collection-manager/interfaces/multipleSelect.ts +2 -14
  140. package/src/collection-manager/interfaces/nanoid.ts +2 -2
  141. package/src/collection-manager/interfaces/number.ts +2 -85
  142. package/src/collection-manager/interfaces/o2m.tsx +1 -22
  143. package/src/collection-manager/interfaces/obo.tsx +145 -0
  144. package/src/collection-manager/interfaces/oho.tsx +145 -0
  145. package/src/collection-manager/interfaces/password.ts +2 -44
  146. package/src/collection-manager/interfaces/percent.ts +2 -74
  147. package/src/collection-manager/interfaces/phone.ts +2 -2
  148. package/src/collection-manager/interfaces/properties/index.ts +0 -133
  149. package/src/collection-manager/interfaces/radioGroup.ts +2 -2
  150. package/src/collection-manager/interfaces/richText.ts +2 -51
  151. package/src/collection-manager/interfaces/select.ts +2 -14
  152. package/src/collection-manager/interfaces/snowflake-id.ts +2 -2
  153. package/src/collection-manager/interfaces/tableoid.ts +1 -2
  154. package/src/collection-manager/interfaces/textarea.ts +2 -51
  155. package/src/collection-manager/interfaces/time.ts +2 -2
  156. package/src/collection-manager/interfaces/types.ts +4 -12
  157. package/src/collection-manager/interfaces/unixTimestamp.tsx +2 -2
  158. package/src/collection-manager/interfaces/updatedAt.ts +2 -2
  159. package/src/collection-manager/interfaces/updatedBy.ts +1 -12
  160. package/src/collection-manager/interfaces/url.ts +2 -4
  161. package/src/collection-manager/interfaces/uuid.ts +2 -2
  162. package/src/collection-manager/template-fields.ts +109 -0
  163. package/src/components/KeepAlive.tsx +131 -0
  164. package/src/components/README.md +90 -6
  165. package/src/components/README.zh-CN.md +90 -7
  166. package/src/components/RouterBridge.tsx +28 -4
  167. package/src/components/__tests__/KeepAlive.test.tsx +63 -0
  168. package/src/components/__tests__/RouterBridge.test.tsx +27 -0
  169. package/src/components/form/DialogFormLayout.tsx +5 -29
  170. package/src/components/form/VariableInput.tsx +101 -28
  171. package/src/components/form/__tests__/VariableInput.test.ts +85 -0
  172. package/src/components/form/filter/CollectionFilter.tsx +111 -0
  173. package/src/components/form/filter/CollectionFilterItem.tsx +184 -0
  174. package/src/components/form/filter/DateFilterDynamicComponent.tsx +283 -0
  175. package/src/components/form/filter/FilterValueInput.tsx +198 -0
  176. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +247 -0
  177. package/src/components/form/filter/__tests__/DateFilterDynamicComponent.test.tsx +148 -0
  178. package/src/components/form/filter/__tests__/FilterValueInput.test.tsx +243 -0
  179. package/src/components/form/filter/__tests__/compileFilterGroup.test.ts +146 -0
  180. package/src/components/form/filter/index.ts +13 -0
  181. package/src/components/form/filter/useFilterActionProps.ts +203 -0
  182. package/src/components/form/index.tsx +1 -0
  183. package/src/data-source/ExtendCollectionsProvider.tsx +144 -0
  184. package/src/data-source/__tests__/ExtendCollectionsProvider.test.tsx +264 -0
  185. package/src/data-source/index.ts +10 -0
  186. package/src/flow/FlowPage.tsx +35 -7
  187. package/src/flow/__tests__/FlowPage.test.tsx +79 -0
  188. package/src/flow/__tests__/FlowRoute.test.tsx +529 -2
  189. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +191 -0
  190. package/src/flow/actions/__tests__/openView.subModelKey.test.tsx +33 -0
  191. package/src/flow/actions/aclCheck.tsx +4 -0
  192. package/src/flow/actions/aclCheckRefresh.tsx +4 -0
  193. package/src/flow/actions/dateTimeFormat.tsx +12 -8
  194. package/src/flow/actions/linkageRules.tsx +122 -0
  195. package/src/flow/actions/openView.tsx +28 -4
  196. package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +11 -329
  197. package/src/flow/admin-shell/BaseLayoutModel.tsx +455 -0
  198. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +502 -0
  199. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +547 -3
  200. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +35 -7
  201. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +160 -0
  202. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +0 -12
  203. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +28 -201
  204. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +11 -2
  205. package/src/flow/admin-shell/admin-layout/AppListRender.tsx +139 -0
  206. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +1 -26
  207. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +149 -27
  208. package/src/flow/admin-shell/admin-layout/index.ts +3 -0
  209. package/src/flow/admin-shell/admin-layout/useApplications.tsx +34 -1
  210. package/src/flow/admin-shell/useAdminLayoutRoutePage.ts +10 -26
  211. package/src/flow/admin-shell/useLayoutRoutePage.ts +61 -0
  212. package/src/flow/components/AdminLayout.tsx +4 -154
  213. package/src/flow/components/FlowRoute.tsx +105 -15
  214. package/src/flow/components/filter/index.ts +3 -0
  215. package/src/flow/components/filter/useFilterOptions.ts +102 -0
  216. package/src/flow/index.ts +4 -0
  217. package/src/flow/models/actions/UpdateRecordActionModel.tsx +14 -95
  218. package/src/flow/models/actions/UpdateRecordActionUtils.ts +4 -7
  219. package/src/flow/models/actions/__tests__/AssignFormRefill.test.ts +26 -1
  220. package/src/flow/models/base/ActionModel.tsx +8 -1
  221. package/src/flow/models/base/PageModel/PageModel.tsx +51 -18
  222. package/src/flow/models/base/PageModel/RootPageModel.tsx +6 -13
  223. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +102 -1
  224. package/src/flow/models/base/RouteModel.tsx +1 -1
  225. package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +63 -2
  226. package/src/flow/models/blocks/assign-form/assignFieldValuesFlow.tsx +206 -0
  227. package/src/flow/models/blocks/assign-form/index.ts +1 -0
  228. package/src/flow/models/blocks/form/FormActionGroupModel.tsx +14 -0
  229. package/src/flow/models/blocks/form/FormActionModel.tsx +30 -3
  230. package/src/flow/models/blocks/form/FormItemModel.tsx +8 -1
  231. package/src/flow/models/blocks/form/__tests__/FormActionGroupModel.test.ts +46 -0
  232. package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +71 -0
  233. package/src/flow/models/blocks/form/submitHandler.ts +8 -1
  234. package/src/flow/models/blocks/form/submitValues.ts +4 -1
  235. package/src/flow/models/blocks/table/TableBlockModel.tsx +118 -16
  236. package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +114 -0
  237. package/src/flow/models/fields/AssociationFieldModel/SubFormFieldModel.tsx +7 -1
  238. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +1 -1
  239. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -5
  240. package/src/flow/models/fields/ClickableFieldModel.tsx +9 -1
  241. package/src/flow/models/fields/CollectionSelectorFieldModel.tsx +8 -2
  242. package/src/flow/models/fields/DisplayEnumFieldModel.tsx +8 -2
  243. package/src/flow/models/fields/DisplayTimeFieldModel.tsx +1 -1
  244. package/src/flow/models/fields/TimeFieldModel.tsx +1 -1
  245. package/src/flow/models/fields/__tests__/TimeFieldModel.test.tsx +61 -0
  246. package/src/flow/models/fields/mobile-components/MobileDatePicker.tsx +19 -3
  247. package/src/flow/models/fields/mobile-components/__tests__/MobileDatePicker.test.tsx +94 -0
  248. package/src/flow/models/topbar/TopbarActionModel.tsx +1 -1
  249. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +91 -0
  250. package/src/index.ts +6 -0
  251. package/src/layout-manager/LayoutContentRoute.tsx +90 -0
  252. package/src/layout-manager/LayoutManager.tsx +185 -0
  253. package/src/layout-manager/LayoutRoute.tsx +138 -0
  254. package/src/layout-manager/__tests__/LayoutManager.test.tsx +335 -0
  255. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +473 -0
  256. package/src/layout-manager/index.ts +14 -0
  257. package/src/layout-manager/types.ts +22 -0
  258. package/src/layout-manager/utils.ts +37 -0
  259. package/src/nocobase-buildin-plugin/index.tsx +69 -67
  260. package/src/nocobase-buildin-plugin/plugins/LocalePlugin.ts +1 -0
  261. package/src/settings-center/index.ts +1 -1
  262. package/src/settings-center/plugin-manager/BulkEnableButton.tsx +111 -0
  263. package/src/settings-center/plugin-manager/PluginCard.tsx +270 -0
  264. package/src/settings-center/plugin-manager/PluginDetail.tsx +195 -0
  265. package/src/settings-center/plugin-manager/index.tsx +254 -0
  266. package/src/settings-center/plugin-manager/types.ts +35 -0
  267. package/src/settings-center/utils.tsx +8 -1
  268. package/src/theme/__tests__/globalStyles.test.ts +24 -0
  269. package/src/theme/globalStyles.ts +10 -0
  270. package/src/utils/globalDeps.ts +2 -0
  271. package/es/collection-manager/interfaces/linkTo.d.ts +0 -90
  272. package/es/collection-manager/interfaces/o2o.d.ts +0 -621
  273. package/es/collection-manager/interfaces/properties/operators.d.ts +0 -294
  274. package/es/collection-manager/interfaces/subTable.d.ts +0 -172
  275. package/src/collection-manager/interfaces/linkTo.ts +0 -120
  276. package/src/collection-manager/interfaces/o2o.tsx +0 -561
  277. package/src/collection-manager/interfaces/subTable.ts +0 -218
  278. package/src/settings-center/PluginManagerPage.tsx +0 -162
@@ -68,22 +68,15 @@ export class RootPageModel extends PageModel {
68
68
  reaction(
69
69
  () => this.context.pageActive.value,
70
70
  () => {
71
+ if (this.context.view?.inputArgs?.activationControlledByLayout) {
72
+ this.mounted = true;
73
+ return;
74
+ }
71
75
  if (this.context.pageActive.value && this.mounted) {
72
- const firstTab = this.subModels.tabs?.[0];
73
- if (firstTab) {
74
- this.setProps('tabActiveKey', firstTab.uid);
75
- this.invokeTabModelLifecycleMethod(firstTab.uid, 'onActive', true);
76
- }
76
+ this.activateCurrentTab(true);
77
77
  }
78
78
  if (this.context.pageActive.value === false) {
79
- if (this.props.tabActiveKey) {
80
- this.invokeTabModelLifecycleMethod(this.props.tabActiveKey, 'onInactive');
81
- } else {
82
- const firstTab = this.subModels.tabs?.[0];
83
- if (firstTab) {
84
- this.invokeTabModelLifecycleMethod(firstTab.uid, 'onInactive');
85
- }
86
- }
79
+ this.deactivateCurrentTab();
87
80
  }
88
81
  this.mounted = true;
89
82
  },
@@ -47,6 +47,13 @@ vi.mock('@nocobase/flow-engine', () => {
47
47
  return [];
48
48
  }
49
49
 
50
+ findSubModel(key: string, callback: any) {
51
+ if (this.subModels[key]) {
52
+ return this.subModels[key].find(callback) || null;
53
+ }
54
+ return null;
55
+ }
56
+
50
57
  addSubModel() {}
51
58
  setSubModel() {}
52
59
 
@@ -260,6 +267,44 @@ describe('PageModel', () => {
260
267
  });
261
268
  });
262
269
 
270
+ describe('tab lifecycle', () => {
271
+ it('should invoke tab lifecycle on PageModel subModels before engine lookup', () => {
272
+ const blockOnActive = vi.fn();
273
+ const blockOnInactive = vi.fn();
274
+ const tabModel = {
275
+ uid: 'tab1',
276
+ context: {
277
+ tabActive: { value: false },
278
+ },
279
+ subModels: {
280
+ grid: {
281
+ mapSubModels: vi.fn((_key, callback) => {
282
+ callback({ onActive: blockOnActive, onInactive: blockOnInactive });
283
+ }),
284
+ },
285
+ },
286
+ };
287
+ (pageModel as any).subModels = { tabs: [tabModel] };
288
+ (pageModel as any).flowEngine = {
289
+ getModel: vi.fn(() => undefined),
290
+ };
291
+ (pageModel as any).context = {
292
+ pageInfo: {},
293
+ view: {
294
+ inputArgs: { pageActive: true },
295
+ },
296
+ };
297
+
298
+ pageModel.invokeTabModelLifecycleMethod('tab1', 'onActive', true);
299
+ pageModel.invokeTabModelLifecycleMethod('tab1', 'onInactive');
300
+
301
+ expect((pageModel as any).flowEngine.getModel).not.toHaveBeenCalled();
302
+ expect(tabModel.context.tabActive.value).toBe(false);
303
+ expect(blockOnActive).toHaveBeenCalledWith(true);
304
+ expect(blockOnInactive).toHaveBeenCalledWith(false);
305
+ });
306
+ });
307
+
263
308
  describe('renderTabs activeKey logic', () => {
264
309
  beforeEach(() => {
265
310
  // Mock mapTabs to avoid complex rendering logic inside it
@@ -455,6 +500,7 @@ describe('PageModel', () => {
455
500
  } as any;
456
501
  (pageModel as any).context = {
457
502
  currentRoute: {
503
+ id: 'route-1',
458
504
  enableTabs: false,
459
505
  },
460
506
  };
@@ -479,6 +525,7 @@ describe('PageModel', () => {
479
525
  } as any;
480
526
  (pageModel as any).context = {
481
527
  currentRoute: {
528
+ id: 'route-1',
482
529
  enableTabs: true,
483
530
  },
484
531
  };
@@ -495,6 +542,59 @@ describe('PageModel', () => {
495
542
  paddingBottom: 0,
496
543
  });
497
544
  });
545
+
546
+ it('should ignore stale desktop route enableTabs=false from another route', () => {
547
+ pageModel.props = {
548
+ routeId: 'route-1',
549
+ displayTitle: true,
550
+ enableTabs: true,
551
+ title: 'Title',
552
+ headerStyle: { backgroundColor: 'var(--colorBgLayout)' },
553
+ } as any;
554
+ (pageModel as any).context = {
555
+ currentRoute: {
556
+ id: 'route-2',
557
+ enableTabs: false,
558
+ },
559
+ };
560
+ pageModel.renderTabs = vi.fn(() => null);
561
+ pageModel.renderFirstTab = vi.fn(() => null);
562
+
563
+ const result = pageModel.render() as any;
564
+ const header = result.props.children[0];
565
+
566
+ expect(pageModel.renderTabs).toHaveBeenCalled();
567
+ expect(pageModel.renderFirstTab).not.toHaveBeenCalled();
568
+ expect(header.props.style).toMatchObject({
569
+ backgroundColor: 'var(--colorBgLayout)',
570
+ paddingBottom: 0,
571
+ });
572
+ });
573
+
574
+ it('should ignore stale desktop route enableTabs=true from another route', () => {
575
+ pageModel.props = {
576
+ routeId: 'route-1',
577
+ displayTitle: true,
578
+ enableTabs: false,
579
+ title: 'Title',
580
+ headerStyle: { backgroundColor: 'var(--colorBgLayout)' },
581
+ } as any;
582
+ (pageModel as any).context = {
583
+ currentRoute: {
584
+ id: 'route-2',
585
+ enableTabs: true,
586
+ },
587
+ };
588
+ pageModel.renderTabs = vi.fn(() => null);
589
+ pageModel.renderFirstTab = vi.fn(() => null);
590
+
591
+ const result = pageModel.render() as any;
592
+ const header = result.props.children[0];
593
+
594
+ expect(pageModel.renderTabs).not.toHaveBeenCalled();
595
+ expect(pageModel.renderFirstTab).toHaveBeenCalled();
596
+ expect(header.props.style).toEqual({ backgroundColor: 'var(--colorBgLayout)' });
597
+ });
498
598
  });
499
599
 
500
600
  describe('dirty refresh signal', () => {
@@ -554,7 +654,7 @@ describe('PageModel', () => {
554
654
  pageModel.onMount();
555
655
 
556
656
  expect(typeof listeners['view:activated']).toBe('function');
557
- expect(invokeSpy).toHaveBeenCalledWith('tab1', 'onActive');
657
+ expect(invokeSpy).toHaveBeenCalledWith('tab1', 'onActive', false);
558
658
  });
559
659
  });
560
660
 
@@ -630,6 +730,7 @@ describe('PageModel', () => {
630
730
  it('should use page documentTitle when desktop route disables tabs even if flow model enables tabs', async () => {
631
731
  pageModel.props = { routeId: 'route-1', enableTabs: true, title: 'Route page title' } as any;
632
732
  (pageModel as any).context.currentRoute = {
733
+ id: 'route-1',
633
734
  enableTabs: false,
634
735
  };
635
736
  (pageModel as any).stepParams = {
@@ -21,7 +21,7 @@ RouteModel.registerFlow({
21
21
  return {
22
22
  mode: 'embed',
23
23
  preventClose: true,
24
- pageModelClass: 'RootPageModel',
24
+ pageModelClass: ctx.layout?.rootPageModelClass || 'RootPageModel',
25
25
  };
26
26
  },
27
27
  },
@@ -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';
@@ -12,4 +12,18 @@ import { FormActionModel } from './FormActionModel';
12
12
 
13
13
  export class FormActionGroupModel extends ActionGroupModel {
14
14
  static baseClass = FormActionModel;
15
+
16
+ static async defineChildren(ctx) {
17
+ const allowedModelNames = ctx.allowedFormActionModelNames;
18
+
19
+ if (!Array.isArray(allowedModelNames) || allowedModelNames.length === 0) {
20
+ return super.defineChildren(ctx);
21
+ }
22
+
23
+ await Promise.all(allowedModelNames.map((name) => ctx.engine?.getModelClassAsync?.(name)));
24
+
25
+ const items = await super.defineChildren(ctx);
26
+ const allowedSet = new Set(allowedModelNames);
27
+ return items.filter((item) => allowedSet.has(item.useModel || item.key));
28
+ }
15
29
  }
@@ -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);
@@ -138,6 +138,10 @@ export class FormItemModel<T extends DefaultStructure = DefaultStructure> extend
138
138
  });
139
139
  }
140
140
  }
141
+ fork.context.defineProperty('fieldPathArray', {
142
+ get: () => this.context.fieldPathArray,
143
+ cache: false,
144
+ });
141
145
  if (isHiddenReservedValuePreview) {
142
146
  fork.setProps({ hidden: false });
143
147
  }
@@ -151,7 +155,10 @@ export class FormItemModel<T extends DefaultStructure = DefaultStructure> extend
151
155
  : { hidden, ...mergedPropsWithoutInitial };
152
156
  const fieldPath = buildDynamicNamePath(this.props.name, idx);
153
157
  this.context.defineProperty('fieldPathArray', {
154
- value: [...parentFieldPathArray, ..._.castArray(fieldPath)],
158
+ get: () => {
159
+ return [...parentFieldPathArray, ..._.castArray(fieldPath)];
160
+ },
161
+ cache: false,
155
162
  });
156
163
  const record = this.context.item?.value || this.context.record;
157
164
  const content = (
@@ -0,0 +1,46 @@
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 { FlowEngine } from '@nocobase/flow-engine';
11
+ import { describe, expect, it } from 'vitest';
12
+ import { FormActionGroupModel, FormActionModel } from '../../../..';
13
+
14
+ describe('FormActionGroupModel', () => {
15
+ it('filters addable form actions by allowedFormActionModelNames', async () => {
16
+ class AllowedFormActionModel extends FormActionModel {}
17
+ class HiddenFormActionModel extends FormActionModel {}
18
+
19
+ AllowedFormActionModel.define({
20
+ label: 'Allowed action',
21
+ sort: 10,
22
+ });
23
+ HiddenFormActionModel.define({
24
+ label: 'Hidden action',
25
+ sort: 20,
26
+ });
27
+
28
+ const engine = new FlowEngine();
29
+ engine.registerModels({
30
+ FormActionModel,
31
+ HiddenFormActionModel,
32
+ });
33
+ engine.registerModelLoaders({
34
+ AllowedFormActionModel: {
35
+ loader: async () => ({ AllowedFormActionModel }),
36
+ },
37
+ });
38
+
39
+ const items = await FormActionGroupModel.defineChildren({
40
+ engine,
41
+ allowedFormActionModelNames: ['AllowedFormActionModel'],
42
+ } as any);
43
+
44
+ expect(items.map((item: any) => item.useModel)).toEqual(['AllowedFormActionModel']);
45
+ });
46
+ });