@nocobase/client-v2 2.2.0-beta.8 → 2.2.0

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 (271) hide show
  1. package/es/APIClient.d.ts +13 -3
  2. package/es/BaseApplication.d.ts +3 -0
  3. package/es/RouteRepository.d.ts +8 -0
  4. package/es/RouterManager.d.ts +15 -0
  5. package/es/authRedirect.d.ts +1 -0
  6. package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
  7. package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
  8. package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
  9. package/es/components/category-tabs/index.d.ts +9 -0
  10. package/es/components/form/ScanInput/useCodeScanner.d.ts +3 -2
  11. package/es/components/form/TypedVariableInput.d.ts +31 -5
  12. package/es/components/form/filter/CollectionFilter.d.ts +2 -0
  13. package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
  14. package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
  15. package/es/components/form/filter/index.d.ts +2 -0
  16. package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
  17. package/es/components/index.d.ts +1 -0
  18. package/es/entry-actions/EntryActionManager.d.ts +24 -0
  19. package/es/entry-actions/index.d.ts +9 -0
  20. package/es/flow/actions/index.d.ts +1 -1
  21. package/es/flow/actions/linkageRules.d.ts +27 -0
  22. package/es/flow/admin-shell/BaseLayoutModel.d.ts +6 -0
  23. package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +7 -0
  24. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
  25. package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +2 -1
  26. package/es/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.d.ts +24 -0
  27. package/es/flow/admin-shell/admin-layout/index.d.ts +1 -0
  28. package/es/flow/admin-shell/admin-layout/useApplications.d.ts +7 -2
  29. package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
  30. package/es/flow/components/FieldAssignValueInput.d.ts +7 -24
  31. package/es/flow/components/FlowRoute.d.ts +2 -2
  32. package/es/flow/components/RunJSValueEditor.d.ts +1 -0
  33. package/es/flow/components/field-value-variable/DateVariableEditor.d.ts +24 -0
  34. package/es/flow/components/field-value-variable/FieldValueVariableInput.d.ts +31 -0
  35. package/es/flow/components/field-value-variable/dateValue.d.ts +36 -0
  36. package/es/flow/components/field-value-variable/index.d.ts +11 -0
  37. package/es/flow/components/filter/FilterGroup.d.ts +1 -0
  38. package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
  39. package/es/flow/components/placeholders/BlockPlaceholder.d.ts +1 -0
  40. package/es/flow/index.d.ts +1 -0
  41. package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
  42. package/es/flow/internal/utils/operatorSchemaHelper.d.ts +2 -2
  43. package/es/flow/models/actions/UpdateRecordActionUtils.d.ts +8 -2
  44. package/es/flow/models/base/ActionModelCore.d.ts +6 -2
  45. package/es/flow/models/base/BlockGridModel.d.ts +3 -0
  46. package/es/flow/models/base/CollectionBlockModel.d.ts +3 -0
  47. package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
  48. package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
  49. package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
  50. package/es/flow/models/blocks/details/DetailsBlockModel.d.ts +3 -0
  51. package/es/flow/models/blocks/filter-form/FilterFormGridModel.d.ts +1 -0
  52. package/es/flow/models/blocks/filter-form/FilterFormSubmitActionModel.d.ts +3 -1
  53. package/es/flow/models/blocks/form/FormBlockModel.d.ts +7 -0
  54. package/es/flow/models/blocks/form/FormGridModel.d.ts +6 -0
  55. package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
  56. package/es/flow/models/blocks/form/value-runtime/rules.d.ts +1 -0
  57. package/es/flow/models/blocks/table/JSColumnModel.d.ts +2 -0
  58. package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
  59. package/es/flow/models/blocks/table/TableColumnModel.d.ts +12 -0
  60. package/es/flow/models/blocks/table/utils.d.ts +1 -0
  61. package/es/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.d.ts +2 -1
  62. package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +0 -17
  63. package/es/flow/models/fields/AssociationFieldModel/recordSelectShared.d.ts +41 -0
  64. package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
  65. package/es/flow/routeTransientInputArgs.d.ts +14 -0
  66. package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
  67. package/es/flow-compat/passwordUtils.d.ts +1 -1
  68. package/es/flow-compat/routeTypes.d.ts +1 -0
  69. package/es/index.d.ts +7 -0
  70. package/es/index.mjs +301 -177
  71. package/es/ui-operation/index.d.ts +15 -0
  72. package/es/ui-operation/ui-operation-codec.d.ts +10 -0
  73. package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
  74. package/es/utils/index.d.ts +1 -0
  75. package/es/utils/markdownSanitize.d.ts +11 -0
  76. package/lib/index.js +313 -189
  77. package/lib/locale/languageCodes.js +3 -1
  78. package/package.json +9 -7
  79. package/src/APIClient.ts +90 -7
  80. package/src/Application.tsx +7 -1
  81. package/src/BaseApplication.tsx +13 -6
  82. package/src/RouteRepository.ts +25 -0
  83. package/src/RouterManager.tsx +142 -1
  84. package/src/__tests__/APIClient.test.tsx +58 -0
  85. package/src/__tests__/RouteRepository.test.ts +23 -0
  86. package/src/__tests__/RouterManager.test.ts +125 -0
  87. package/src/__tests__/app.test.tsx +73 -0
  88. package/src/__tests__/authRedirect.test.ts +17 -0
  89. package/src/__tests__/browserChecker.test.ts +61 -0
  90. package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
  91. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +35 -2
  92. package/src/__tests__/settings-center.test.tsx +270 -6
  93. package/src/acl/ACLProvider.tsx +2 -0
  94. package/src/acl/__tests__/ACLProvider.test.tsx +92 -0
  95. package/src/authRedirect.ts +38 -0
  96. package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
  97. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
  98. package/src/collection-manager/field-validation.ts +1 -1
  99. package/src/components/README.md +7 -1
  100. package/src/components/README.zh-CN.md +6 -1
  101. package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
  102. package/src/components/category-tabs/index.ts +10 -0
  103. package/src/components/form/JsonTextArea.tsx +4 -0
  104. package/src/components/form/ScanInput/CodeScanner.tsx +79 -35
  105. package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +101 -0
  106. package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +284 -11
  107. package/src/components/form/ScanInput/useCodeScanner.ts +332 -24
  108. package/src/components/form/TypedVariableInput.tsx +452 -101
  109. package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
  110. package/src/components/form/filter/CollectionFilter.tsx +4 -0
  111. package/src/components/form/filter/CollectionFilterItem.tsx +36 -9
  112. package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
  113. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +55 -0
  114. package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
  115. package/src/components/form/filter/index.ts +2 -0
  116. package/src/components/form/filter/useFilterActionProps.ts +37 -6
  117. package/src/components/form/table/Table.tsx +81 -6
  118. package/src/components/form/table/__tests__/Table.columnWidth.test.tsx +433 -0
  119. package/src/components/index.ts +1 -0
  120. package/src/entry-actions/EntryActionManager.ts +76 -0
  121. package/src/entry-actions/index.ts +10 -0
  122. package/src/flow/FlowPage.tsx +38 -3
  123. package/src/flow/__tests__/FlowPage.test.tsx +201 -27
  124. package/src/flow/__tests__/FlowRoute.test.tsx +675 -12
  125. package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
  126. package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +314 -0
  127. package/src/flow/actions/__tests__/dataScopeFilter.test.ts +62 -0
  128. package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +102 -0
  129. package/src/flow/actions/__tests__/linkageRules.actionStates.test.ts +33 -0
  130. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +72 -0
  131. package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
  132. package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
  133. package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +80 -20
  134. package/src/flow/actions/dataScopeFilter.ts +10 -1
  135. package/src/flow/actions/dateTimeFormat.tsx +2 -2
  136. package/src/flow/actions/index.ts +2 -0
  137. package/src/flow/actions/linkageRules.tsx +144 -26
  138. package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
  139. package/src/flow/actions/openView.tsx +21 -1
  140. package/src/flow/admin-shell/BaseLayoutModel.tsx +124 -0
  141. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +184 -42
  142. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1016 -119
  143. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +41 -5
  144. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +177 -1
  145. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +20 -0
  146. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +78 -59
  147. package/src/flow/admin-shell/admin-layout/AppListRender.tsx +13 -2
  148. package/src/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.tsx +289 -0
  149. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +253 -6
  150. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +228 -0
  151. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +436 -2
  152. package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +48 -0
  153. package/src/flow/admin-shell/admin-layout/index.ts +1 -0
  154. package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
  155. package/src/flow/common/Markdown/Display.tsx +14 -3
  156. package/src/flow/common/Markdown/Edit.tsx +19 -7
  157. package/src/flow/common/Markdown/style.ts +4 -0
  158. package/src/flow/components/BlockItemCard.tsx +2 -2
  159. package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
  160. package/src/flow/components/FieldAssignValueInput.tsx +72 -584
  161. package/src/flow/components/FlowRoute.tsx +204 -46
  162. package/src/flow/components/RunJSValueEditor.tsx +9 -1
  163. package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
  164. package/src/flow/components/field-value-variable/DateVariableEditor.tsx +181 -0
  165. package/src/flow/components/field-value-variable/FieldValueVariableInput.tsx +326 -0
  166. package/src/flow/components/field-value-variable/__tests__/FieldValueVariableInput.test.tsx +380 -0
  167. package/src/flow/components/field-value-variable/dateValue.ts +223 -0
  168. package/src/flow/components/field-value-variable/index.ts +12 -0
  169. package/src/flow/components/filter/FilterGroup.tsx +33 -5
  170. package/src/flow/components/filter/VariableFilterItem.tsx +170 -13
  171. package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
  172. package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
  173. package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +195 -1
  174. package/src/flow/components/placeholders/BlockPlaceholder.tsx +70 -23
  175. package/src/flow/components/placeholders/__tests__/BlockPlaceholder.test.tsx +57 -7
  176. package/src/flow/index.ts +3 -0
  177. package/src/flow/internal/components/Markdown/util.ts +2 -1
  178. package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
  179. package/src/flow/internal/utils/operatorSchemaHelper.ts +3 -2
  180. package/src/flow/models/actions/UpdateRecordActionModel.tsx +18 -1
  181. package/src/flow/models/actions/UpdateRecordActionUtils.ts +18 -6
  182. package/src/flow/models/actions/__tests__/UpdateRecordActionModel.test.ts +76 -4
  183. package/src/flow/models/base/ActionModel.tsx +10 -8
  184. package/src/flow/models/base/ActionModelCore.tsx +16 -2
  185. package/src/flow/models/base/BlockGridModel.tsx +26 -0
  186. package/src/flow/models/base/CollectionBlockModel.tsx +38 -3
  187. package/src/flow/models/base/PageModel/PageModel.tsx +387 -53
  188. package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
  189. package/src/flow/models/base/PageModel/PageTabModel.tsx +249 -18
  190. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +790 -10
  191. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
  192. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
  193. package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +1075 -7
  194. package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +49 -0
  195. package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
  196. package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
  197. package/src/flow/models/base/__tests__/CollectionBlockModel.initialBeforeRenderRefresh.test.ts +125 -9
  198. package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +28 -53
  199. package/src/flow/models/blocks/details/DetailsBlockModel.tsx +8 -3
  200. package/src/flow/models/blocks/details/__tests__/DetailsBlockModel.initialPaginationChangeRefresh.test.ts +32 -1
  201. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +28 -3
  202. package/src/flow/models/blocks/filter-form/FilterFormGridModel.tsx +36 -5
  203. package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +155 -25
  204. package/src/flow/models/blocks/filter-form/FilterFormSubmitActionModel.tsx +57 -4
  205. package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.onModelCreated.test.ts +174 -1
  206. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +395 -1
  207. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.getFilterValue.test.ts +72 -0
  208. package/src/flow/models/blocks/filter-form/__tests__/FilterFormSubmitActionModel.lifecycle.test.ts +90 -0
  209. package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +66 -4
  210. package/src/flow/models/blocks/filter-manager/FilterManager.ts +5 -0
  211. package/src/flow/models/blocks/filter-manager/__tests__/FilterManager.test.ts +40 -0
  212. package/src/flow/models/blocks/form/FormBlockModel.tsx +47 -0
  213. package/src/flow/models/blocks/form/FormGridModel.tsx +4 -0
  214. package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
  215. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +8 -2
  216. package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
  217. package/src/flow/models/blocks/form/__tests__/runJsFormSubmit.test.ts +131 -0
  218. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +435 -0
  219. package/src/flow/models/blocks/form/value-runtime/rules.ts +67 -14
  220. package/src/flow/models/blocks/form/value-runtime/runtime.ts +43 -19
  221. package/src/flow/models/blocks/js-block/JSBlock.tsx +1 -1
  222. package/src/flow/models/blocks/table/JSColumnModel.tsx +10 -1
  223. package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +2 -1
  224. package/src/flow/models/blocks/table/TableBlockModel.tsx +29 -3
  225. package/src/flow/models/blocks/table/TableColumnModel.tsx +54 -11
  226. package/src/flow/models/blocks/table/__tests__/JSColumnModel.test.tsx +52 -5
  227. package/src/flow/models/blocks/table/__tests__/TableBlockModel.filterReset.test.ts +78 -0
  228. package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
  229. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +84 -0
  230. package/src/flow/models/blocks/table/utils.ts +7 -0
  231. package/src/flow/models/fields/AssociationFieldModel/CascadeSelectFieldModel.tsx +33 -1
  232. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +10 -1
  233. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/__tests__/popupContext.test.ts +120 -0
  234. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/actions/PopupSubTableEditActionModel.tsx +10 -2
  235. package/src/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.tsx +24 -1
  236. package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +54 -149
  237. package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +167 -3
  238. package/src/flow/models/fields/AssociationFieldModel/recordSelectShared.tsx +156 -0
  239. package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
  240. package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
  241. package/src/flow/models/fields/DisplayNumberFieldModel.tsx +1 -1
  242. package/src/flow/models/fields/JsonFieldModel.tsx +31 -8
  243. package/src/flow/models/fields/NumberFieldModel.tsx +1 -1
  244. package/src/flow/models/fields/TextareaFieldModel.tsx +42 -19
  245. package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
  246. package/src/flow/models/fields/__tests__/DisplayNumberFieldModel.test.ts +33 -0
  247. package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -0
  248. package/src/flow/models/fields/__tests__/NumberFieldModel.test.tsx +47 -0
  249. package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +32 -1
  250. package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
  251. package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +26 -10
  252. package/src/flow/models/fields/mobile-components/MobileSelect.tsx +51 -14
  253. package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +139 -13
  254. package/src/flow/models/topbar/TopbarActionModel.tsx +78 -3
  255. package/src/flow/routeTransientInputArgs.ts +27 -0
  256. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +42 -0
  257. package/src/flow/utils/dataScopeFormValueClear.ts +4 -3
  258. package/src/flow-compat/fieldValidationConstants.ts +1 -1
  259. package/src/flow-compat/routeTypes.ts +1 -0
  260. package/src/index.ts +7 -0
  261. package/src/layout-manager/LayoutContentRoute.tsx +2 -1
  262. package/src/layout-manager/LayoutRoute.tsx +2 -1
  263. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +87 -1
  264. package/src/locale/languageCodes.ts +3 -1
  265. package/src/nocobase-buildin-plugin/index.tsx +19 -1
  266. package/src/settings-center/AdminSettingsLayout.tsx +23 -2
  267. package/src/ui-operation/index.ts +33 -0
  268. package/src/ui-operation/ui-operation-codec.ts +54 -0
  269. package/src/utils/getRouteRuntimeVersion.ts +185 -0
  270. package/src/utils/index.tsx +1 -0
  271. package/src/utils/markdownSanitize.ts +88 -0
@@ -7,9 +7,11 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import { FlowModel, FlowModelRenderer, observable, tExpr } from '@nocobase/flow-engine';
10
+ import { FlowModel, FlowModelRenderer, observable, type ParamObject, tExpr } from '@nocobase/flow-engine';
11
11
  import { Icon, type NocoBaseDesktopRoute } from '../../../../flow-compat';
12
+ import type { RouteRepository } from '../../../../RouteRepository';
12
13
  import { useRequest } from 'ahooks';
14
+ import _ from 'lodash';
13
15
  import React from 'react';
14
16
  import { SkeletonFallback } from '../../../components/SkeletonFallback';
15
17
  import { TextAreaWithContextSelector } from '../../../components/TextAreaWithContextSelector';
@@ -52,6 +54,43 @@ function normalizePersistedRoute(payload: unknown): Partial<NocoBaseDesktopRoute
52
54
  return undefined;
53
55
  }
54
56
 
57
+ type PersistedFlowModelAnchor = Record<string, unknown> & {
58
+ uid?: unknown;
59
+ stepParams?: unknown;
60
+ subModels?: unknown;
61
+ };
62
+
63
+ type LinkageRulesStepParams = ParamObject & {
64
+ value?: unknown;
65
+ };
66
+
67
+ function isRecord(value: unknown): value is Record<string, unknown> {
68
+ return !!value && typeof value === 'object' && !Array.isArray(value);
69
+ }
70
+
71
+ function isLinkageRulesStepParams(value: unknown): value is LinkageRulesStepParams {
72
+ return isRecord(value);
73
+ }
74
+
75
+ type RefreshableRouteRepository = Pick<RouteRepository, 'refreshAccessible'>;
76
+
77
+ const routeRefreshQueues = new WeakMap<RefreshableRouteRepository, Promise<unknown>>();
78
+
79
+ async function refreshRouteRepository(repository: RefreshableRouteRepository) {
80
+ const previous = routeRefreshQueues.get(repository) || Promise.resolve();
81
+ const current = previous.catch(() => undefined).then(() => repository.refreshAccessible());
82
+
83
+ routeRefreshQueues.set(repository, current);
84
+
85
+ try {
86
+ await current;
87
+ } finally {
88
+ if (routeRefreshQueues.get(repository) === current) {
89
+ routeRefreshQueues.delete(repository);
90
+ }
91
+ }
92
+ }
93
+
55
94
  export class BasePageTabModel extends FlowModel<{
56
95
  subModels: {
57
96
  grid: BlockGridModel;
@@ -139,13 +178,100 @@ BasePageTabModel.registerFlow({
139
178
  ctx.model.setProps('title', translate(params.title, { ns: 'lm-desktop-routes' }));
140
179
  ctx.model.setProps('icon', params.icon);
141
180
  const pageModel = ctx.engine.getModel(ctx.model.parentId) as { updateDocumentTitle?: () => Promise<void> };
142
- void pageModel?.updateDocumentTitle?.();
181
+ await pageModel?.updateDocumentTitle?.();
143
182
  },
144
183
  },
184
+ linkageRules: {
185
+ use: 'tabLinkageRules',
186
+ },
145
187
  },
146
188
  });
147
189
 
148
190
  export class RootPageTabModel extends BasePageTabModel {
191
+ private persistedLinkageRulesHydrated = false;
192
+ private persistedLinkageRulesHydrating?: Promise<void>;
193
+
194
+ onInit(options) {
195
+ super.onInit(options);
196
+ if (this.shouldHydratePersistedLinkageRules()) {
197
+ this.hydratePersistedLinkageRules().catch((error) => {
198
+ console.warn('[RootPageTabModel] Failed to hydrate tab linkage rules', error);
199
+ });
200
+ }
201
+ }
202
+
203
+ private hasExplicitLinkageRulesStep() {
204
+ const pageTabSettings = this.stepParams?.pageTabSettings;
205
+ return isRecord(pageTabSettings) && Object.prototype.hasOwnProperty.call(pageTabSettings, 'linkageRules');
206
+ }
207
+
208
+ private shouldHydratePersistedLinkageRules() {
209
+ return !!this.context.flowSettingsEnabled || this.props.route?.options?.hasPersistedPageTabFlowModel === true;
210
+ }
211
+
212
+ private async fetchPersistedAnchor(): Promise<PersistedFlowModelAnchor | undefined> {
213
+ const response = await this.context.api.request({
214
+ url: 'flowModels:findOne',
215
+ params: { uid: this.uid },
216
+ });
217
+ const anchor = response?.data?.data;
218
+ return isRecord(anchor) ? anchor : undefined;
219
+ }
220
+
221
+ private async performPersistedLinkageRulesHydrate() {
222
+ if (this.hasExplicitLinkageRulesStep()) {
223
+ return;
224
+ }
225
+
226
+ const anchor = await this.fetchPersistedAnchor();
227
+ if (this.hasExplicitLinkageRulesStep()) {
228
+ return;
229
+ }
230
+
231
+ const stepParams = isRecord(anchor?.stepParams) ? anchor.stepParams : undefined;
232
+ const pageTabSettings = isRecord(stepParams?.pageTabSettings) ? stepParams.pageTabSettings : undefined;
233
+ const linkageRules = pageTabSettings?.linkageRules;
234
+ if (!isLinkageRulesStepParams(linkageRules)) {
235
+ return;
236
+ }
237
+
238
+ this.setStepParams('pageTabSettings', 'linkageRules', _.cloneDeep(linkageRules));
239
+ this.invalidateFlowCache('beforeRender', true);
240
+ await this.rerender();
241
+ }
242
+
243
+ private hydratePersistedLinkageRules(): Promise<void> {
244
+ if (this.persistedLinkageRulesHydrated) {
245
+ return Promise.resolve();
246
+ }
247
+ if (this.persistedLinkageRulesHydrating) {
248
+ return this.persistedLinkageRulesHydrating;
249
+ }
250
+
251
+ const hydration = this.performPersistedLinkageRulesHydrate();
252
+ this.persistedLinkageRulesHydrating = hydration;
253
+ hydration
254
+ .then(() => {
255
+ if (this.persistedLinkageRulesHydrating === hydration) {
256
+ this.persistedLinkageRulesHydrated = true;
257
+ this.persistedLinkageRulesHydrating = undefined;
258
+ }
259
+ })
260
+ .catch(() => {
261
+ if (this.persistedLinkageRulesHydrating === hydration) {
262
+ this.persistedLinkageRulesHydrating = undefined;
263
+ }
264
+ });
265
+ return hydration;
266
+ }
267
+
268
+ async openFlowSettings(options?: Parameters<FlowModel['openFlowSettings']>[0]) {
269
+ if (options?.flowKey === 'pageTabSettings' && options?.stepKey === 'linkageRules') {
270
+ await this.hydratePersistedLinkageRules();
271
+ }
272
+ return super.openFlowSettings(options);
273
+ }
274
+
149
275
  renderChildren() {
150
276
  return (
151
277
  <PageTabChildrenRenderer
@@ -162,28 +288,50 @@ export class RootPageTabModel extends BasePageTabModel {
162
288
  }
163
289
 
164
290
  async saveStepParams() {
165
- return this.save();
291
+ const hasExplicitLinkageRulesStep = this.hasExplicitLinkageRulesStep();
292
+ const linkageRules = hasExplicitLinkageRulesStep
293
+ ? (_.cloneDeep(this.stepParams.pageTabSettings.linkageRules) as LinkageRulesStepParams)
294
+ : undefined;
295
+
296
+ await this.save();
297
+ if (hasExplicitLinkageRulesStep && linkageRules) {
298
+ await this.persistLinkageRulesToAnchor(linkageRules);
299
+ }
166
300
  }
167
301
 
168
302
  async save() {
169
303
  const json = this.serialize();
170
304
  const documentTitle = this.stepParams?.pageTabSettings?.tab?.documentTitle;
171
- const response = await this.context.api.request({
172
- method: 'post',
173
- url: 'desktopRoutes:updateOrCreate',
174
- params: {
175
- filterKeys: ['schemaUid'],
305
+ const route = this.props.route || {};
306
+ const persisted = route.id != null;
307
+ const currentRoute =
308
+ persisted && route.schemaUid ? this.context.routeRepository?.getRouteBySchemaUid?.(route.schemaUid) : undefined;
309
+ const data = {
310
+ ...(persisted ? { schemaUid: route.schemaUid } : route),
311
+ title: this.getTabTitle(''),
312
+ icon: this.getTabIcon(),
313
+ options: {
314
+ ...(currentRoute ? currentRoute.options : route.options),
315
+ flowRegistry: json.flowRegistry,
316
+ documentTitle,
176
317
  },
177
- data: {
178
- ...this.props.route,
179
- title: this.getTabTitle(''),
180
- icon: this.getTabIcon(),
181
- options: {
182
- flowRegistry: json.flowRegistry,
183
- documentTitle,
184
- },
185
- },
186
- });
318
+ };
319
+ const response = await this.context.api.request(
320
+ persisted
321
+ ? {
322
+ method: 'post',
323
+ url: `desktopRoutes:update?filter[id]=${route.id}`,
324
+ data,
325
+ }
326
+ : {
327
+ method: 'post',
328
+ url: 'desktopRoutes:updateOrCreate',
329
+ params: {
330
+ filterKeys: ['schemaUid'],
331
+ },
332
+ data,
333
+ },
334
+ );
187
335
  const persistedRoute = normalizePersistedRoute(response?.data?.data);
188
336
 
189
337
  // 新建 tab 首次保存后需要立即拿到持久化 route id,拖拽排序会直接依赖它。
@@ -197,6 +345,89 @@ export class RootPageTabModel extends BasePageTabModel {
197
345
  },
198
346
  });
199
347
  }
348
+
349
+ // 后续保存会从 RouteRepository 读取扩展 options,当前写入完成后必须先同步最新路由快照。
350
+ try {
351
+ if (this.context.routeRepository?.refreshAccessible) {
352
+ await refreshRouteRepository(this.context.routeRepository);
353
+ } else {
354
+ await this.context.refreshDesktopRoutes?.();
355
+ }
356
+ } catch (error) {
357
+ this.context.logger?.warn?.(
358
+ { err: error },
359
+ '[client-v2] Failed to refresh desktop routes after saving a page tab',
360
+ );
361
+ }
362
+ }
363
+
364
+ private buildAnchorPayload(latestAnchor: PersistedFlowModelAnchor | undefined, linkageRules: LinkageRulesStepParams) {
365
+ const hasPersistedAnchor = typeof latestAnchor?.uid === 'string' && latestAnchor.uid.length > 0;
366
+ const anchorRoot: PersistedFlowModelAnchor = hasPersistedAnchor
367
+ ? _.cloneDeep(latestAnchor)
368
+ : { uid: this.uid, use: 'RouteModel' };
369
+ delete anchorRoot.subModels;
370
+
371
+ const latestStepParams = isRecord(anchorRoot.stepParams) ? anchorRoot.stepParams : {};
372
+ const latestPageTabSettings = isRecord(latestStepParams.pageTabSettings) ? latestStepParams.pageTabSettings : {};
373
+
374
+ return {
375
+ ...anchorRoot,
376
+ stepParams: {
377
+ ...latestStepParams,
378
+ pageTabSettings: {
379
+ ...latestPageTabSettings,
380
+ linkageRules: _.cloneDeep(linkageRules),
381
+ },
382
+ },
383
+ };
384
+ }
385
+
386
+ private async persistLinkageRulesToAnchor(linkageRules: LinkageRulesStepParams) {
387
+ const latestAnchor = await this.fetchPersistedAnchor();
388
+ const anchorPayload = this.buildAnchorPayload(latestAnchor, linkageRules);
389
+ await this.context.api.request({
390
+ method: 'post',
391
+ url: 'flowModels:save',
392
+ data: anchorPayload,
393
+ });
394
+
395
+ const hasRules = Array.isArray(linkageRules.value) && linkageRules.value.length > 0;
396
+ await this.syncPersistedPageTabFlowModelMarker(hasRules);
397
+ this.persistedLinkageRulesHydrated = true;
398
+ }
399
+
400
+ private async syncPersistedPageTabFlowModelMarker(hasRules: boolean) {
401
+ const route = this.props.route;
402
+ if (route?.id == null) {
403
+ throw new Error('Cannot persist page tab FlowModel marker before the desktop route is saved.');
404
+ }
405
+ if (typeof this.context.routeRepository?.updateRoute !== 'function') {
406
+ throw new Error('Route repository is unavailable while persisting the page tab FlowModel marker.');
407
+ }
408
+
409
+ const currentRoute = route.schemaUid
410
+ ? this.context.routeRepository.getRouteBySchemaUid?.(route.schemaUid)
411
+ : undefined;
412
+ const nextOptions = {
413
+ ...(currentRoute?.options || route.options || {}),
414
+ };
415
+ if (hasRules) {
416
+ nextOptions.hasPersistedPageTabFlowModel = true;
417
+ } else {
418
+ delete nextOptions.hasPersistedPageTabFlowModel;
419
+ }
420
+ const persistedOptions = Object.keys(nextOptions).length > 0 ? nextOptions : undefined;
421
+
422
+ await this.context.routeRepository.updateRoute(
423
+ route.id,
424
+ { options: persistedOptions },
425
+ { refreshAfterMutation: false },
426
+ );
427
+ this.setProps('route', {
428
+ ...route,
429
+ options: persistedOptions,
430
+ });
200
431
  }
201
432
 
202
433
  async destroy() {