@nocobase/client-v2 2.1.31 → 2.2.0-alpha.10

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 (170) hide show
  1. package/es/APIClient.d.ts +1 -3
  2. package/es/BaseApplication.d.ts +2 -0
  3. package/es/PluginSettingsManager.d.ts +33 -0
  4. package/es/RouteRepository.d.ts +29 -6
  5. package/es/RouterManager.d.ts +15 -0
  6. package/es/authRedirect.d.ts +1 -0
  7. package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
  8. package/es/components/category-tabs/index.d.ts +9 -0
  9. package/es/components/form/TypedVariableInput.d.ts +31 -5
  10. package/es/components/form/filter/CollectionFilter.d.ts +2 -0
  11. package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
  12. package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
  13. package/es/components/form/filter/index.d.ts +2 -0
  14. package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
  15. package/es/components/index.d.ts +2 -0
  16. package/es/entry-actions/EntryActionManager.d.ts +24 -0
  17. package/es/entry-actions/index.d.ts +9 -0
  18. package/es/flow/actions/index.d.ts +1 -1
  19. package/es/flow/actions/linkageRules.d.ts +2 -0
  20. package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
  21. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
  22. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +3 -0
  23. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
  24. package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +5 -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/resolveAdminRouteRuntimeTarget.d.ts +6 -0
  29. package/es/flow/admin-shell/admin-layout/useApplications.d.ts +7 -2
  30. package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
  31. package/es/flow/components/FieldAssignValueInput.d.ts +9 -0
  32. package/es/flow/components/RunJSValueEditor.d.ts +1 -0
  33. package/es/flow/components/filter/FilterGroup.d.ts +1 -0
  34. package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
  35. package/es/flow/index.d.ts +1 -0
  36. package/es/flow/models/base/ActionModelCore.d.ts +2 -0
  37. package/es/flow/models/base/GridModel.d.ts +1 -1
  38. package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
  39. package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
  40. package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
  41. package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +9 -0
  42. package/es/flow-compat/routeTypes.d.ts +1 -0
  43. package/es/index.d.ts +7 -1
  44. package/es/index.mjs +223 -135
  45. package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
  46. package/es/utils/index.d.ts +1 -0
  47. package/lib/index.js +220 -132
  48. package/package.json +7 -7
  49. package/src/APIClient.ts +1 -10
  50. package/src/Application.tsx +4 -0
  51. package/src/BaseApplication.tsx +2 -0
  52. package/src/PluginSettingsManager.ts +53 -0
  53. package/src/RouteRepository.ts +151 -24
  54. package/src/RouterManager.tsx +142 -1
  55. package/src/__tests__/PluginSettingsManager.test.ts +13 -0
  56. package/src/__tests__/RouteRepository.test.ts +239 -0
  57. package/src/__tests__/RouterManager.test.ts +125 -0
  58. package/src/__tests__/app.test.tsx +18 -0
  59. package/src/__tests__/authRedirect.test.ts +17 -0
  60. package/src/__tests__/browserChecker.test.ts +61 -0
  61. package/src/__tests__/exports.test.ts +16 -0
  62. package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
  63. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +28 -0
  64. package/src/__tests__/plugin-manager.test.tsx +44 -2
  65. package/src/__tests__/settings-center.test.tsx +40 -1
  66. package/src/authRedirect.ts +38 -0
  67. package/src/components/README.md +7 -1
  68. package/src/components/README.zh-CN.md +6 -1
  69. package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
  70. package/src/components/category-tabs/index.ts +10 -0
  71. package/src/components/form/JsonTextArea.tsx +4 -0
  72. package/src/components/form/TypedVariableInput.tsx +452 -101
  73. package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
  74. package/src/components/form/filter/CollectionFilter.tsx +4 -0
  75. package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
  76. package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
  77. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
  78. package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
  79. package/src/components/form/filter/index.ts +2 -0
  80. package/src/components/form/filter/useFilterActionProps.ts +37 -6
  81. package/src/components/index.ts +2 -0
  82. package/src/entry-actions/EntryActionManager.ts +76 -0
  83. package/src/entry-actions/index.ts +10 -0
  84. package/src/flow/__tests__/FlowRoute.test.tsx +1084 -145
  85. package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
  86. package/src/flow/actions/customVariable.tsx +1 -2
  87. package/src/flow/actions/index.ts +2 -0
  88. package/src/flow/actions/linkageRules.tsx +65 -8
  89. package/src/flow/actions/runjs.tsx +2 -14
  90. package/src/flow/admin-shell/BaseLayoutModel.tsx +13 -0
  91. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +46 -13
  92. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +486 -0
  93. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +64 -8
  94. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
  95. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
  96. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
  97. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
  98. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +23 -9
  99. package/src/flow/admin-shell/admin-layout/AppListRender.tsx +13 -2
  100. package/src/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.tsx +289 -0
  101. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +435 -0
  102. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
  103. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +77 -0
  104. package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +214 -2
  105. package/src/flow/admin-shell/admin-layout/index.ts +1 -0
  106. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
  107. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
  108. package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
  109. package/src/flow/components/DefaultValue.tsx +1 -2
  110. package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
  111. package/src/flow/components/FieldAssignValueInput.tsx +76 -9
  112. package/src/flow/components/FlowRoute.tsx +185 -28
  113. package/src/flow/components/RunJSValueEditor.tsx +9 -1
  114. package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +84 -2
  115. package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
  116. package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
  117. package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
  118. package/src/flow/components/filter/FilterGroup.tsx +33 -5
  119. package/src/flow/components/filter/VariableFilterItem.tsx +158 -10
  120. package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
  121. package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
  122. package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +70 -1
  123. package/src/flow/index.ts +1 -0
  124. package/src/flow/models/actions/JSActionModel.tsx +2 -7
  125. package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
  126. package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
  127. package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
  128. package/src/flow/models/base/ActionModel.tsx +21 -8
  129. package/src/flow/models/base/ActionModelCore.tsx +5 -0
  130. package/src/flow/models/base/GridModel.tsx +38 -7
  131. package/src/flow/models/base/PageModel/PageModel.tsx +387 -53
  132. package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
  133. package/src/flow/models/base/PageModel/PageTabModel.tsx +186 -3
  134. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +790 -14
  135. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
  136. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
  137. package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +586 -1
  138. package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
  139. package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
  140. package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
  141. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +1 -2
  142. package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
  143. package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
  144. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +87 -0
  145. package/src/flow/models/blocks/form/value-runtime/rules.ts +2 -2
  146. package/src/flow/models/blocks/form/value-runtime/runtime.ts +91 -0
  147. package/src/flow/models/blocks/js-block/JSBlock.tsx +2 -7
  148. package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
  149. package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +1 -1
  150. package/src/flow/models/blocks/table/TableBlockModel.tsx +13 -3
  151. package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +54 -0
  152. package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
  153. package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
  154. package/src/flow/models/fields/JSFieldModel.tsx +2 -7
  155. package/src/flow/models/fields/JSItemModel.tsx +2 -14
  156. package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
  157. package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
  158. package/src/flow/models/topbar/TopbarActionModel.tsx +168 -10
  159. package/src/flow-compat/Popover.tsx +43 -4
  160. package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
  161. package/src/flow-compat/routeTypes.ts +1 -0
  162. package/src/index.ts +13 -1
  163. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
  164. package/src/nocobase-buildin-plugin/index.tsx +19 -3
  165. package/src/settings-center/AdminSettingsLayout.tsx +10 -2
  166. package/src/settings-center/SystemSettingsPage.tsx +0 -1
  167. package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
  168. package/src/settings-center/utils.tsx +0 -6
  169. package/src/utils/getRouteRuntimeVersion.ts +185 -0
  170. package/src/utils/index.tsx +1 -0
@@ -0,0 +1,171 @@
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 { ActionScene } from '@nocobase/flow-engine';
11
+ import { describe, expect, it, vi } from 'vitest';
12
+
13
+ import * as registeredActions from '../index';
14
+ import * as linkageActions from '../linkageRules';
15
+
16
+ const tabLinkageRules = (linkageActions as Record<string, unknown>).tabLinkageRules as
17
+ | typeof linkageActions.menuLinkageRules
18
+ | undefined;
19
+ const linkageSetTabProps = (linkageActions as Record<string, unknown>).linkageSetTabProps as
20
+ | typeof linkageActions.linkageSetMenuItemProps
21
+ | undefined;
22
+
23
+ function createRuntime() {
24
+ let matched = true;
25
+ const setProps = vi.fn(function (this: { props: Record<string, unknown> }, props: Record<string, unknown>) {
26
+ this.props = {
27
+ ...this.props,
28
+ ...props,
29
+ };
30
+ });
31
+ const setHidden = vi.fn(function (this: { hidden: boolean }, value: boolean) {
32
+ this.hidden = value;
33
+ });
34
+ const model = {
35
+ uid: 'tab-1',
36
+ props: {} as Record<string, unknown>,
37
+ hidden: false,
38
+ __allModels: [],
39
+ setProps,
40
+ setHidden,
41
+ };
42
+ const ctx = {
43
+ app: { jsonLogic: { apply: () => matched } },
44
+ model,
45
+ t: (text: string) => text,
46
+ getAction: (name: string) => (name === 'linkageSetTabProps' ? linkageSetTabProps : undefined),
47
+ resolveJsonTemplate: vi.fn(async (value: unknown) => value),
48
+ };
49
+
50
+ return {
51
+ ctx,
52
+ model,
53
+ setMatched(value: boolean) {
54
+ matched = value;
55
+ },
56
+ };
57
+ }
58
+
59
+ const hideRule = {
60
+ value: [
61
+ {
62
+ key: 'rule-1',
63
+ title: 'Hide tab',
64
+ enable: true,
65
+ condition: {
66
+ logic: '$and',
67
+ items: [{ path: 'status', operator: '$eq', value: 'visible' }],
68
+ },
69
+ actions: [
70
+ {
71
+ key: 'action-1',
72
+ name: 'linkageSetTabProps',
73
+ params: { value: 'hidden' },
74
+ },
75
+ ],
76
+ },
77
+ ],
78
+ };
79
+
80
+ describe('tab linkage rules', () => {
81
+ it('should append the tab linkage scene without changing existing scene values', () => {
82
+ const scenes = ActionScene as unknown as Record<string, number>;
83
+
84
+ expect(scenes.MENU_LINKAGE_RULES).toBe(7);
85
+ expect(scenes.TAB_LINKAGE_RULES).toBe(8);
86
+ expect(scenes.TAB_LINKAGE_RULES).toBe(Math.max(...Object.values(scenes).filter(Number.isInteger)));
87
+ });
88
+
89
+ it('should register tab linkage actions from the actions entry', () => {
90
+ expect((registeredActions as Record<string, unknown>).tabLinkageRules).toBe(tabLinkageRules);
91
+ expect((registeredActions as Record<string, unknown>).linkageSetTabProps).toBe(linkageSetTabProps);
92
+ expect(tabLinkageRules).toBeDefined();
93
+ expect(linkageSetTabProps).toBeDefined();
94
+ });
95
+
96
+ it('should expose tab state and runjs actions in ui schema', () => {
97
+ expect(tabLinkageRules).toBeDefined();
98
+ expect(linkageSetTabProps).toBeDefined();
99
+
100
+ const schema = tabLinkageRules?.uiSchema?.({
101
+ getActions: () =>
102
+ new Map([
103
+ ['linkageSetTabProps', linkageSetTabProps],
104
+ ['linkageRunjs', linkageActions.linkageRunjs],
105
+ ['linkageSetMenuItemProps', linkageActions.linkageSetMenuItemProps],
106
+ ]),
107
+ } as never) as Record<string, Record<string, Record<string, unknown>>> | undefined;
108
+
109
+ expect(schema?.value?.['x-component-props']?.supportedActions).toEqual(['linkageSetTabProps', 'linkageRunjs']);
110
+ expect(linkageSetTabProps?.scene).toBe((ActionScene as unknown as Record<string, number>).TAB_LINKAGE_RULES);
111
+ expect(linkageActions.linkageRunjs.scene).toContain(
112
+ (ActionScene as unknown as Record<string, number>).TAB_LINKAGE_RULES,
113
+ );
114
+ });
115
+
116
+ it('should hide the tab when the condition matches and restore it when the condition misses', async () => {
117
+ expect(tabLinkageRules).toBeDefined();
118
+ const runtime = createRuntime();
119
+
120
+ await tabLinkageRules?.handler(runtime.ctx as never, hideRule);
121
+ expect(runtime.model.hidden).toBe(true);
122
+
123
+ runtime.setMatched(false);
124
+ await tabLinkageRules?.handler(runtime.ctx as never, hideRule);
125
+ expect(runtime.model.hidden).toBe(false);
126
+ });
127
+
128
+ it('should restore the tab when linkage rules are cleared', async () => {
129
+ expect(tabLinkageRules).toBeDefined();
130
+ const runtime = createRuntime();
131
+
132
+ await tabLinkageRules?.handler(runtime.ctx as never, hideRule);
133
+ expect(runtime.model.hidden).toBe(true);
134
+
135
+ await tabLinkageRules?.handler(runtime.ctx as never, { value: [] });
136
+ expect(runtime.model.hidden).toBe(false);
137
+ });
138
+
139
+ it('should not restore stale tab props while applying or clearing visibility rules', async () => {
140
+ expect(tabLinkageRules).toBeDefined();
141
+ const runtime = createRuntime();
142
+ runtime.model.props = {
143
+ title: 'Original title',
144
+ icon: 'original-icon',
145
+ route: { options: { badge: 'original' } },
146
+ };
147
+
148
+ await tabLinkageRules?.handler(runtime.ctx as never, hideRule);
149
+ expect(runtime.model.hidden).toBe(true);
150
+
151
+ const updatedProps = {
152
+ title: 'Updated title',
153
+ icon: 'updated-icon',
154
+ route: { options: { badge: 'updated' } },
155
+ };
156
+ runtime.model.props = updatedProps;
157
+
158
+ await tabLinkageRules?.handler(runtime.ctx as never, hideRule);
159
+ expect(runtime.model.props).toMatchObject(updatedProps);
160
+ expect(runtime.model.hidden).toBe(true);
161
+
162
+ runtime.setMatched(false);
163
+ await tabLinkageRules?.handler(runtime.ctx as never, hideRule);
164
+ expect(runtime.model.props).toMatchObject(updatedProps);
165
+ expect(runtime.model.hidden).toBe(false);
166
+
167
+ await tabLinkageRules?.handler(runtime.ctx as never, { value: [] });
168
+ expect(runtime.model.props).toMatchObject(updatedProps);
169
+ expect(runtime.model.hidden).toBe(false);
170
+ });
171
+ });
@@ -12,7 +12,6 @@ import {
12
12
  defineAction,
13
13
  isRunJSValue,
14
14
  normalizeRunJSValue,
15
- runjsWithSafeGlobals,
16
15
  tExpr,
17
16
  type RunJSValue,
18
17
  useFlowContext,
@@ -43,7 +42,7 @@ export const customVariable = defineAction({
43
42
  if (variable.type === 'runjs') {
44
43
  const getFunction = async () => {
45
44
  const { code, version } = normalizeRunJSValue(variable.runjs);
46
- return runjsWithSafeGlobals(ctx, code, { version });
45
+ return ctx.runjs(code, undefined, { version });
47
46
  };
48
47
  const metaFunction = () => ({
49
48
  title: variable.title,
@@ -43,10 +43,12 @@ export {
43
43
  linkageSetDetailsFieldProps,
44
44
  actionLinkageRules,
45
45
  menuLinkageRules,
46
+ tabLinkageRules,
46
47
  blockLinkageRules,
47
48
  linkageSetBlockProps,
48
49
  linkageSetActionProps,
49
50
  linkageSetMenuItemProps,
51
+ linkageSetTabProps,
50
52
  linkageSetFieldProps,
51
53
  subFormLinkageSetFieldProps,
52
54
  linkageAssignField,
@@ -16,13 +16,9 @@ import {
16
16
  FlowRuntimeContext,
17
17
  useFlowContext,
18
18
  useFlowEngine,
19
- createSafeWindow,
20
- createSafeDocument,
21
- createSafeNavigator,
22
19
  observer,
23
20
  isRunJSValue,
24
21
  normalizeRunJSValue,
25
- runjsWithSafeGlobals,
26
22
  } from '@nocobase/flow-engine';
27
23
  import { evaluateConditions, FilterGroupType, removeInvalidFilterItems } from '@nocobase/utils/client';
28
24
  import React from 'react';
@@ -492,7 +488,7 @@ async function resolveLinkageAssignRuntimeValue(ctx: FlowContext, rawValue: any)
492
488
 
493
489
  try {
494
490
  const { code, version } = normalizeRunJSValue(rawValue);
495
- const ret = await runjsWithSafeGlobals(ctx, code, { version });
491
+ const ret = await ctx.runjs(code, undefined, { version });
496
492
  if (!ret?.success) {
497
493
  return SKIP_RUNJS_ASSIGN_VALUE;
498
494
  }
@@ -735,6 +731,41 @@ export const linkageSetMenuItemProps = defineAction({
735
731
  },
736
732
  });
737
733
 
734
+ export const linkageSetTabProps = defineAction({
735
+ name: 'linkageSetTabProps',
736
+ title: tExpr('Set tab state'),
737
+ scene: ActionScene.TAB_LINKAGE_RULES,
738
+ sort: 100,
739
+ uiSchema: {
740
+ value: {
741
+ type: 'string',
742
+ 'x-component': (props) => {
743
+ const { value, onChange } = props;
744
+ // eslint-disable-next-line react-hooks/rules-of-hooks
745
+ const ctx = useFlowContext();
746
+ const t = ctx.model.translate.bind(ctx.model);
747
+
748
+ return (
749
+ <Select
750
+ value={value}
751
+ onChange={onChange}
752
+ placeholder={t('Please select state')}
753
+ style={{ width: '100%' }}
754
+ options={[
755
+ { label: t('Visible'), value: 'visible' },
756
+ { label: t('Hidden'), value: 'hidden' },
757
+ ]}
758
+ allowClear
759
+ />
760
+ );
761
+ },
762
+ },
763
+ },
764
+ handler(ctx, { value, setProps }) {
765
+ setProps(ctx.model, { hiddenModel: value === 'hidden' });
766
+ },
767
+ });
768
+
738
769
  export const linkageSetFieldProps = defineAction({
739
770
  name: 'linkageSetFieldProps',
740
771
  title: tExpr('Set field state'),
@@ -1343,6 +1374,7 @@ export const linkageRunjs = defineAction({
1343
1374
  ActionScene.FIELD_LINKAGE_RULES,
1344
1375
  ActionScene.ACTION_LINKAGE_RULES,
1345
1376
  ActionScene.MENU_LINKAGE_RULES,
1377
+ ActionScene.TAB_LINKAGE_RULES,
1346
1378
  ActionScene.DETAILS_FIELD_LINKAGE_RULES,
1347
1379
  ActionScene.SUB_FORM_FIELD_LINKAGE_RULES,
1348
1380
  ],
@@ -1397,8 +1429,7 @@ export const linkageRunjs = defineAction({
1397
1429
  }
1398
1430
 
1399
1431
  try {
1400
- const navigator = createSafeNavigator();
1401
- await ctx.runjs(script, { window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator });
1432
+ await ctx.runjs(script);
1402
1433
  } catch (error) {
1403
1434
  console.error('Script execution error:', error);
1404
1435
  // 可以选择显示错误信息给用户
@@ -2174,7 +2205,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
2174
2205
  }
2175
2206
  : props;
2176
2207
 
2177
- // 只记录联动实际控制的属性,避免恢复状态时覆盖标题等无关的最新配置。
2208
+ // 只记录联动实际控制的属性,避免之后恢复状态时把标题、路由等无关的新配置回滚到旧快照。
2178
2209
  const originalProps = model.__originalProps || (model.__originalProps = {});
2179
2210
  const rememberOriginalProp = (key: string, value: unknown) => {
2180
2211
  if (!Object.prototype.hasOwnProperty.call(originalProps, key)) {
@@ -2480,6 +2511,32 @@ export const menuLinkageRules = defineAction({
2480
2511
  },
2481
2512
  });
2482
2513
 
2514
+ export const tabLinkageRules = defineAction({
2515
+ name: 'tabLinkageRules',
2516
+ title: tExpr('Tab linkage rules'),
2517
+ uiMode: 'embed',
2518
+ uiSchema(ctx) {
2519
+ return {
2520
+ value: {
2521
+ type: 'array',
2522
+ 'x-component': LinkageRulesUI,
2523
+ 'x-component-props': {
2524
+ supportedActions: getSupportedActions(ctx, ActionScene.TAB_LINKAGE_RULES),
2525
+ title: tExpr('Tab linkage rules'),
2526
+ },
2527
+ },
2528
+ };
2529
+ },
2530
+ defaultParams: {
2531
+ value: [],
2532
+ },
2533
+ useRawParams: true,
2534
+ handler: async (ctx, params) => {
2535
+ const resolved = await resolveLinkageRulesParamsPreservingRunJsScripts(ctx, params);
2536
+ return commonLinkageRulesHandler(ctx, resolved);
2537
+ },
2538
+ });
2539
+
2483
2540
  export const fieldLinkageRules = defineAction({
2484
2541
  name: 'fieldLinkageRules',
2485
2542
  title: tExpr('Field linkage rules'),
@@ -7,14 +7,7 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import {
11
- ActionScene,
12
- defineAction,
13
- tExpr,
14
- createSafeWindow,
15
- createSafeDocument,
16
- createSafeNavigator,
17
- } from '@nocobase/flow-engine';
10
+ import { ActionScene, defineAction, tExpr } from '@nocobase/flow-engine';
18
11
  import { CodeEditor } from '../components/code-editor';
19
12
 
20
13
  export const runjs = defineAction({
@@ -38,11 +31,6 @@ export const runjs = defineAction({
38
31
  // 如果是 URL 触发的,则不执行代码
39
32
  if (ctx.inputArgs?.navigation) return;
40
33
 
41
- const navigator = createSafeNavigator();
42
- return ctx.runjs(params.code, {
43
- window: createSafeWindow({ navigator }),
44
- document: createSafeDocument(),
45
- navigator,
46
- });
34
+ return ctx.runjs(params.code);
47
35
  },
48
36
  });
@@ -20,6 +20,7 @@ import {
20
20
  type BaseLayoutRouteCoordinatorOptions,
21
21
  type RoutePageMeta,
22
22
  } from './BaseLayoutRouteCoordinator';
23
+ import { NocoBaseDesktopRouteType } from '../../flow-compat';
23
24
  import type { LayoutDefinition } from '../../layout-manager/types';
24
25
  import { isLayoutContentRouteName } from '../../layout-manager/utils';
25
26
 
@@ -334,6 +335,18 @@ export class BaseLayoutModel<
334
335
  };
335
336
  }
336
337
 
338
+ const routeRepository = this.flowEngine.context.routeRepository;
339
+ const schemaRoute = routeRepository?.getRouteBySchemaUid?.(pageUid);
340
+ const route = schemaRoute ? undefined : routeRepository?.getRouteById?.(pageUid);
341
+ if (!schemaRoute && route?.type === NocoBaseDesktopRouteType.group) {
342
+ return {
343
+ type: 'root',
344
+ pathname,
345
+ basePathname,
346
+ relativePath,
347
+ };
348
+ }
349
+
337
350
  return {
338
351
  type: 'page',
339
352
  pathname,
@@ -42,6 +42,7 @@ import {
42
42
  FLOW_SETTINGS_PREFERENCE_STORAGE_KEY,
43
43
  readFlowSettingsPreference,
44
44
  } from './flowSettingsPreference';
45
+ import { joinAdminLayoutRoutePath, type AdminLayoutRoutePathLike } from './resolveAdminRouteRuntimeTarget';
45
46
  import { useAppListRender } from './AppListRender';
46
47
 
47
48
  const className1 = css`
@@ -294,6 +295,8 @@ function SetIsMobileLayout(props: { isMobile: boolean; children: any; model?: Ad
294
295
  function AdminLayoutContentWithMobileState(props: {
295
296
  isMobile: boolean;
296
297
  model?: AdminLayoutModel;
298
+ designable?: boolean;
299
+ layout?: AdminLayoutModel['layout'];
297
300
  onContentElementChange?: (element: HTMLDivElement | null) => void;
298
301
  }) {
299
302
  const modelRef = useRef(props.model);
@@ -311,7 +314,13 @@ function AdminLayoutContentWithMobileState(props: {
311
314
  onContentElementChangeRef.current?.(element);
312
315
  }, []);
313
316
 
314
- return <AdminLayoutContent onContentElementChange={handleContentElementChange} />;
317
+ return (
318
+ <AdminLayoutContent
319
+ designable={props.designable}
320
+ layout={props.layout}
321
+ onContentElementChange={handleContentElementChange}
322
+ />
323
+ );
315
324
  }
316
325
 
317
326
  const DesignerButtonMenuItem: FC<{ item: AdminLayoutMenuNode; fallbackParentRoute?: NocoBaseDesktopRoute }> = (
@@ -337,25 +346,37 @@ const DesignerButtonMenuItem: FC<{ item: AdminLayoutMenuNode; fallbackParentRout
337
346
  );
338
347
  };
339
348
 
340
- const matchesRoutePath = (route: NocoBaseDesktopRoute | undefined, pathname: string): boolean => {
349
+ const matchesRoutePath = (
350
+ route: NocoBaseDesktopRoute | undefined,
351
+ pathname: string,
352
+ layout?: AdminLayoutRoutePathLike | null,
353
+ ): boolean => {
341
354
  if (!route) {
342
355
  return false;
343
356
  }
344
357
 
345
358
  const candidates = [
346
- route.id != null ? `/admin/${route.id}` : null,
347
- route.schemaUid ? `/admin/${route.schemaUid}` : null,
359
+ route.id != null ? joinAdminLayoutRoutePath(layout, route.id) : null,
360
+ route.schemaUid ? joinAdminLayoutRoutePath(layout, route.schemaUid) : null,
348
361
  ].filter(Boolean) as string[];
349
362
 
350
363
  if (candidates.some((candidate) => pathname === candidate || pathname.startsWith(`${candidate}/`))) {
351
364
  return true;
352
365
  }
353
366
 
354
- return Array.isArray(route.children) ? route.children.some((child) => matchesRoutePath(child, pathname)) : false;
367
+ return Array.isArray(route.children)
368
+ ? route.children.some((child) => matchesRoutePath(child, pathname, layout))
369
+ : false;
355
370
  };
356
371
 
357
- const findSelectedTopGroupRoute = (routes: NocoBaseDesktopRoute[], pathname: string) => {
358
- return routes.find((route) => route.type === NocoBaseDesktopRouteType.group && matchesRoutePath(route, pathname));
372
+ const findSelectedTopGroupRoute = (
373
+ routes: NocoBaseDesktopRoute[],
374
+ pathname: string,
375
+ layout?: AdminLayoutRoutePathLike | null,
376
+ ) => {
377
+ return routes.find(
378
+ (route) => route.type === NocoBaseDesktopRouteType.group && matchesRoutePath(route, pathname, layout),
379
+ );
359
380
  };
360
381
 
361
382
  const renderMenuNodeWithModel = (
@@ -399,6 +420,11 @@ export const AdminLayoutComponent = observer((props: any) => {
399
420
  const { token } = antdTheme.useToken();
400
421
  const customToken = token as CustomToken;
401
422
  const isMobileLayout = !!adminLayoutModel?.isMobileLayout;
423
+ const adminLayoutRoutePath = adminLayoutModel?.layout?.routePath;
424
+ const adminLayoutRoutePathLike = useMemo<AdminLayoutRoutePathLike | undefined>(
425
+ () => (adminLayoutRoutePath ? { routePath: adminLayoutRoutePath } : undefined),
426
+ [adminLayoutRoutePath],
427
+ );
402
428
  const menuRouteRefreshVersion = adminLayoutModel?.menuRouteRefreshVersion || 0;
403
429
  const isMobileSider = isMobileLayout;
404
430
  const [collapsed, setCollapsed] = useState(isMobileSider);
@@ -415,8 +441,8 @@ export const AdminLayoutComponent = observer((props: any) => {
415
441
  );
416
442
  const designable = !isMobileSider && preferredFlowSettingsEnabled;
417
443
  const { styles } = useHeaderStyle();
418
- const { appList } = useApplications();
419
- const appListRender = useAppListRender();
444
+ const { appList, appSwitcherModel } = useApplications(adminLayoutModel);
445
+ const appListRender = useAppListRender(appSwitcherModel);
420
446
  const flowSettingsSyncRef = useRef(0);
421
447
  const desiredFlowSettingsEnabledRef = useRef(false);
422
448
  const handleLayoutContentElementChange = useCallback(
@@ -426,8 +452,8 @@ export const AdminLayoutComponent = observer((props: any) => {
426
452
  [adminLayoutModel],
427
453
  );
428
454
  const selectedTopGroupRoute = useMemo(
429
- () => findSelectedTopGroupRoute(allAccessRoutes, location.pathname),
430
- [allAccessRoutes, location.pathname],
455
+ () => findSelectedTopGroupRoute(allAccessRoutes, location.pathname, adminLayoutRoutePathLike),
456
+ [adminLayoutRoutePathLike, allAccessRoutes, location.pathname],
431
457
  );
432
458
 
433
459
  const handleMenuDragEnd = useCallback(
@@ -481,6 +507,7 @@ export const AdminLayoutComponent = observer((props: any) => {
481
507
 
482
508
  useEffect(() => {
483
509
  const routeRepository = flowEngine.context.routeRepository;
510
+ const deactivateLayout = routeRepository?.activateLayout?.(adminLayoutModel?.layout);
484
511
  const subscriber = () => {
485
512
  const updatedRoutes = routeRepository?.listAccessible() || [];
486
513
  setAllAccessRoutes(updatedRoutes);
@@ -493,8 +520,9 @@ export const AdminLayoutComponent = observer((props: any) => {
493
520
 
494
521
  return () => {
495
522
  routeRepository?.unsubscribe(subscriber);
523
+ deactivateLayout?.();
496
524
  };
497
- }, [flowEngine]);
525
+ }, [adminLayoutModel, adminLayoutModel?.layout?.uid, flowEngine]);
498
526
 
499
527
  useEffect(() => {
500
528
  if (typeof window === 'undefined') {
@@ -637,6 +665,10 @@ export const AdminLayoutComponent = observer((props: any) => {
637
665
  z-index: 2000 !important;
638
666
  }
639
667
 
668
+ .ant-pro-layout-apps-popover .ant-popover-content {
669
+ margin-top: 12px;
670
+ }
671
+
640
672
  .ant-pro-layout-apps-icon {
641
673
  color: ${customToken.colorTextHeaderMenu || 'rgba(255, 255, 255, 0.85)'};
642
674
  }
@@ -680,7 +712,6 @@ export const AdminLayoutComponent = observer((props: any) => {
680
712
  <MobileMenuControlContext.Provider value={{ closeMobileMenu }}>
681
713
  <DndProvider collisionDetection={menuCollisionDetection} onDragEnd={handleMenuDragEnd}>
682
714
  <ProLayout
683
- key={`admin-layout-menu-${menuRouteRefreshVersion}`}
684
715
  {...props}
685
716
  contentStyle={contentStyle}
686
717
  siderWidth={customToken.siderWidth || 200}
@@ -723,6 +754,8 @@ export const AdminLayoutComponent = observer((props: any) => {
723
754
  <AdminLayoutContentWithMobileState
724
755
  isMobile={isMobile}
725
756
  model={adminLayoutModel}
757
+ designable={designable}
758
+ layout={adminLayoutModel?.layout}
726
759
  onContentElementChange={handleLayoutContentElementChange}
727
760
  />
728
761
  </ConfigProvider>