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

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 (154) hide show
  1. package/es/Application.d.ts +1 -0
  2. package/es/BaseApplication.d.ts +4 -0
  3. package/es/RouterManager.d.ts +1 -0
  4. package/es/components/KeepAlive.d.ts +22 -0
  5. package/es/components/RouterBridge.d.ts +9 -0
  6. package/es/components/form/DialogFormLayout.d.ts +5 -29
  7. package/es/components/form/filter/CollectionFilter.d.ts +41 -0
  8. package/es/components/form/filter/CollectionFilterItem.d.ts +41 -0
  9. package/es/components/form/filter/DateFilterDynamicComponent.d.ts +57 -0
  10. package/es/components/form/filter/FilterValueInput.d.ts +29 -0
  11. package/es/components/form/filter/index.d.ts +11 -0
  12. package/es/components/form/filter/useFilterActionProps.d.ts +96 -0
  13. package/es/components/form/index.d.ts +1 -0
  14. package/es/data-source/ExtendCollectionsProvider.d.ts +50 -0
  15. package/es/data-source/index.d.ts +9 -0
  16. package/es/flow/FlowPage.d.ts +2 -1
  17. package/es/flow/admin-shell/AdminLayoutRouteCoordinator.d.ts +8 -40
  18. package/es/flow/admin-shell/BaseLayoutModel.d.ts +89 -0
  19. package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +74 -0
  20. package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +12 -0
  21. package/es/flow/admin-shell/admin-layout/AdminLayoutModel.d.ts +7 -92
  22. package/es/flow/admin-shell/admin-layout/index.d.ts +2 -0
  23. package/es/flow/admin-shell/useAdminLayoutRoutePage.d.ts +2 -2
  24. package/es/flow/admin-shell/useLayoutRoutePage.d.ts +23 -0
  25. package/es/flow/components/FlowRoute.d.ts +10 -1
  26. package/es/flow/components/filter/index.d.ts +2 -0
  27. package/es/flow/components/filter/useFilterOptions.d.ts +54 -0
  28. package/es/flow/index.d.ts +4 -0
  29. package/es/flow/models/base/PageModel/PageModel.d.ts +3 -1
  30. package/es/flow/models/blocks/form/FormActionGroupModel.d.ts +1 -0
  31. package/es/flow/models/blocks/table/TableBlockModel.d.ts +10 -0
  32. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.d.ts +1 -1
  33. package/es/flow-compat/passwordUtils.d.ts +1 -1
  34. package/es/index.d.ts +2 -0
  35. package/es/index.mjs +491 -439
  36. package/es/layout-manager/LayoutContentRoute.d.ts +14 -0
  37. package/es/layout-manager/LayoutManager.d.ts +22 -0
  38. package/es/layout-manager/LayoutRoute.d.ts +14 -0
  39. package/es/layout-manager/index.d.ts +13 -0
  40. package/es/layout-manager/types.d.ts +20 -0
  41. package/es/layout-manager/utils.d.ts +14 -0
  42. package/es/nocobase-buildin-plugin/index.d.ts +3 -10
  43. package/es/settings-center/index.d.ts +1 -1
  44. package/es/settings-center/plugin-manager/BulkEnableButton.d.ts +15 -0
  45. package/es/settings-center/plugin-manager/PluginCard.d.ts +15 -0
  46. package/es/settings-center/plugin-manager/PluginDetail.d.ts +16 -0
  47. package/es/settings-center/{PluginManagerPage.d.ts → plugin-manager/index.d.ts} +1 -7
  48. package/es/settings-center/plugin-manager/types.d.ts +34 -0
  49. package/lib/index.js +491 -439
  50. package/package.json +8 -7
  51. package/src/Application.tsx +27 -12
  52. package/src/BaseApplication.tsx +19 -0
  53. package/src/PluginSettingsManager.ts +1 -1
  54. package/src/RouterManager.tsx +17 -1
  55. package/src/__tests__/PluginSettingsManager.test.ts +41 -2
  56. package/src/__tests__/app.test.tsx +17 -1
  57. package/src/__tests__/globalDeps.test.ts +1 -0
  58. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +45 -2
  59. package/src/__tests__/plugin-manager.test.tsx +177 -0
  60. package/src/__tests__/settings-center.test.tsx +24 -2
  61. package/src/components/KeepAlive.tsx +131 -0
  62. package/src/components/README.md +89 -6
  63. package/src/components/README.zh-CN.md +89 -7
  64. package/src/components/RouterBridge.tsx +28 -4
  65. package/src/components/__tests__/KeepAlive.test.tsx +63 -0
  66. package/src/components/__tests__/RouterBridge.test.tsx +27 -0
  67. package/src/components/form/DialogFormLayout.tsx +5 -29
  68. package/src/components/form/filter/CollectionFilter.tsx +101 -0
  69. package/src/components/form/filter/CollectionFilterItem.tsx +176 -0
  70. package/src/components/form/filter/DateFilterDynamicComponent.tsx +283 -0
  71. package/src/components/form/filter/FilterValueInput.tsx +198 -0
  72. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +205 -0
  73. package/src/components/form/filter/__tests__/DateFilterDynamicComponent.test.tsx +148 -0
  74. package/src/components/form/filter/__tests__/FilterValueInput.test.tsx +243 -0
  75. package/src/components/form/filter/__tests__/compileFilterGroup.test.ts +146 -0
  76. package/src/components/form/filter/index.ts +13 -0
  77. package/src/components/form/filter/useFilterActionProps.ts +200 -0
  78. package/src/components/form/index.tsx +1 -0
  79. package/src/data-source/ExtendCollectionsProvider.tsx +144 -0
  80. package/src/data-source/__tests__/ExtendCollectionsProvider.test.tsx +264 -0
  81. package/src/data-source/index.ts +10 -0
  82. package/src/flow/FlowPage.tsx +35 -7
  83. package/src/flow/__tests__/FlowPage.test.tsx +79 -0
  84. package/src/flow/__tests__/FlowRoute.test.tsx +529 -2
  85. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +191 -0
  86. package/src/flow/actions/__tests__/openView.subModelKey.test.tsx +33 -0
  87. package/src/flow/actions/aclCheck.tsx +4 -0
  88. package/src/flow/actions/aclCheckRefresh.tsx +4 -0
  89. package/src/flow/actions/dateTimeFormat.tsx +12 -8
  90. package/src/flow/actions/linkageRules.tsx +122 -0
  91. package/src/flow/actions/openView.tsx +28 -4
  92. package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +11 -329
  93. package/src/flow/admin-shell/BaseLayoutModel.tsx +455 -0
  94. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +502 -0
  95. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +547 -3
  96. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +4 -4
  97. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +160 -0
  98. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +0 -12
  99. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +28 -201
  100. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +11 -2
  101. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +1 -26
  102. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +149 -27
  103. package/src/flow/admin-shell/admin-layout/index.ts +2 -0
  104. package/src/flow/admin-shell/useAdminLayoutRoutePage.ts +10 -26
  105. package/src/flow/admin-shell/useLayoutRoutePage.ts +61 -0
  106. package/src/flow/components/AdminLayout.tsx +4 -154
  107. package/src/flow/components/FlowRoute.tsx +105 -15
  108. package/src/flow/components/filter/index.ts +3 -0
  109. package/src/flow/components/filter/useFilterOptions.ts +80 -0
  110. package/src/flow/index.ts +4 -0
  111. package/src/flow/models/base/ActionModel.tsx +8 -1
  112. package/src/flow/models/base/PageModel/PageModel.tsx +51 -18
  113. package/src/flow/models/base/PageModel/RootPageModel.tsx +6 -13
  114. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +102 -1
  115. package/src/flow/models/base/RouteModel.tsx +1 -1
  116. package/src/flow/models/blocks/form/FormActionGroupModel.tsx +14 -0
  117. package/src/flow/models/blocks/form/FormItemModel.tsx +8 -1
  118. package/src/flow/models/blocks/form/__tests__/FormActionGroupModel.test.ts +46 -0
  119. package/src/flow/models/blocks/form/submitValues.ts +4 -1
  120. package/src/flow/models/blocks/table/TableBlockModel.tsx +118 -16
  121. package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +114 -0
  122. package/src/flow/models/fields/AssociationFieldModel/SubFormFieldModel.tsx +7 -1
  123. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +1 -1
  124. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -5
  125. package/src/flow/models/fields/ClickableFieldModel.tsx +9 -1
  126. package/src/flow/models/fields/DisplayTimeFieldModel.tsx +1 -1
  127. package/src/flow/models/fields/TimeFieldModel.tsx +1 -1
  128. package/src/flow/models/fields/__tests__/TimeFieldModel.test.tsx +61 -0
  129. package/src/flow/models/fields/mobile-components/MobileDatePicker.tsx +19 -3
  130. package/src/flow/models/fields/mobile-components/__tests__/MobileDatePicker.test.tsx +94 -0
  131. package/src/flow/models/topbar/TopbarActionModel.tsx +1 -1
  132. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +91 -0
  133. package/src/index.ts +2 -0
  134. package/src/layout-manager/LayoutContentRoute.tsx +90 -0
  135. package/src/layout-manager/LayoutManager.tsx +185 -0
  136. package/src/layout-manager/LayoutRoute.tsx +138 -0
  137. package/src/layout-manager/__tests__/LayoutManager.test.tsx +335 -0
  138. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +473 -0
  139. package/src/layout-manager/index.ts +14 -0
  140. package/src/layout-manager/types.ts +22 -0
  141. package/src/layout-manager/utils.ts +37 -0
  142. package/src/nocobase-buildin-plugin/index.tsx +69 -67
  143. package/src/nocobase-buildin-plugin/plugins/LocalePlugin.ts +1 -0
  144. package/src/settings-center/index.ts +1 -1
  145. package/src/settings-center/plugin-manager/BulkEnableButton.tsx +111 -0
  146. package/src/settings-center/plugin-manager/PluginCard.tsx +270 -0
  147. package/src/settings-center/plugin-manager/PluginDetail.tsx +195 -0
  148. package/src/settings-center/plugin-manager/index.tsx +254 -0
  149. package/src/settings-center/plugin-manager/types.ts +35 -0
  150. package/src/settings-center/utils.tsx +8 -1
  151. package/src/theme/__tests__/globalStyles.test.ts +24 -0
  152. package/src/theme/globalStyles.ts +10 -0
  153. package/src/utils/globalDeps.ts +2 -0
  154. package/src/settings-center/PluginManagerPage.tsx +0 -162
@@ -13,6 +13,113 @@ import { FlowSettingsContextProvider } from '@nocobase/flow-engine';
13
13
  import { describe, expect, it, vi } from 'vitest';
14
14
  import { fieldLinkageRules, linkageSetFieldProps, subFormLinkageSetFieldProps } from '../linkageRules';
15
15
 
16
+ const createSubFormFieldModel = ({
17
+ uid,
18
+ fieldPath,
19
+ hidden,
20
+ blockUid = 'block-1',
21
+ fieldIndex,
22
+ fieldPathArray,
23
+ }: {
24
+ uid: string;
25
+ fieldPath: string;
26
+ hidden: boolean;
27
+ blockUid?: string;
28
+ fieldIndex?: string[];
29
+ fieldPathArray?: Array<string | number>;
30
+ }) => ({
31
+ uid,
32
+ hidden,
33
+ props: {},
34
+ context: {
35
+ blockModel: { uid: blockUid },
36
+ ...(fieldIndex ? { fieldIndex } : {}),
37
+ ...(fieldPathArray ? { fieldPathArray } : {}),
38
+ },
39
+ forks: new Set(),
40
+ getStepParams: (flowKey: string, stepKey: string) => {
41
+ if (flowKey === 'fieldSettings' && stepKey === 'init') {
42
+ return { fieldPath };
43
+ }
44
+ },
45
+ setProps(key: any, value?: any) {
46
+ if (typeof key === 'string') {
47
+ this.props[key] = value;
48
+ } else {
49
+ this.props = { ...this.props, ...key };
50
+ }
51
+ },
52
+ });
53
+
54
+ const runSubFormFieldStateRule = async ({
55
+ state,
56
+ fieldUid,
57
+ fieldKey,
58
+ targetModel,
59
+ models,
60
+ }: {
61
+ state: 'visible' | 'hidden';
62
+ fieldUid: string;
63
+ fieldKey: string[];
64
+ targetModel: any;
65
+ models: any[];
66
+ }) => {
67
+ const formItemModel: any = {
68
+ uid: fieldUid,
69
+ forks: new Set([targetModel]),
70
+ getFork: vi.fn((key: string) => (key === `${fieldKey}:${fieldUid}` ? targetModel : undefined)),
71
+ };
72
+ const allModels = [formItemModel, ...models];
73
+ const ctx: any = {
74
+ app: {
75
+ jsonLogic: {
76
+ apply: vi.fn(() => true),
77
+ },
78
+ },
79
+ model: {
80
+ context: {
81
+ blockModel: { uid: 'block-1' },
82
+ fieldKey,
83
+ },
84
+ flowEngine: {
85
+ forEachModel: (visitor: (model: any) => void) => {
86
+ allModels.forEach(visitor);
87
+ },
88
+ },
89
+ },
90
+ engine: {
91
+ getModel: vi.fn((uid: string) => (uid === fieldUid ? formItemModel : undefined)),
92
+ forEachModel: (visitor: (model: any) => void) => {
93
+ allModels.forEach(visitor);
94
+ },
95
+ },
96
+ getAction: (name: string) => (name === 'subFormLinkageSetFieldProps' ? subFormLinkageSetFieldProps : null),
97
+ resolveJsonTemplate: vi.fn(async (value) => value),
98
+ };
99
+
100
+ await fieldLinkageRules.handler(ctx, {
101
+ value: [
102
+ {
103
+ key: 'rule-1',
104
+ enable: true,
105
+ condition: { logic: '$and', items: [] },
106
+ actions: [
107
+ {
108
+ key: 'action-1',
109
+ name: 'subFormLinkageSetFieldProps',
110
+ params: {
111
+ value: {
112
+ fields: [fieldUid],
113
+ state,
114
+ },
115
+ },
116
+ },
117
+ ],
118
+ },
119
+ ],
120
+ });
121
+ };
122
+
16
123
  describe('subFormLinkageSetFieldProps action', () => {
17
124
  it('should not throw when engine.getModel returns undefined', () => {
18
125
  const setProps = vi.fn();
@@ -144,6 +251,90 @@ describe('subFormLinkageSetFieldProps action', () => {
144
251
 
145
252
  expect(setProps).toHaveBeenCalledWith(forkModel, { options: selectedOptions });
146
253
  });
254
+
255
+ it('should sync hidden state when a subform field changes from hidden to visible', async () => {
256
+ const targetModel = createSubFormFieldModel({
257
+ uid: 'field-a',
258
+ fieldPath: 'a',
259
+ hidden: true,
260
+ fieldIndex: ['items:0'],
261
+ });
262
+ const collectedModel = createSubFormFieldModel({
263
+ uid: 'field-a-collected',
264
+ fieldPath: 'a',
265
+ hidden: true,
266
+ fieldPathArray: ['items', 0, 'a'],
267
+ });
268
+
269
+ await runSubFormFieldStateRule({
270
+ state: 'visible',
271
+ fieldUid: 'field-a',
272
+ fieldKey: ['items:0'],
273
+ targetModel,
274
+ models: [collectedModel],
275
+ });
276
+
277
+ expect(targetModel.hidden).toBe(false);
278
+ expect(collectedModel.hidden).toBe(false);
279
+ });
280
+
281
+ it('should sync hidden state when a subform field changes from visible to hidden', async () => {
282
+ const targetModel = createSubFormFieldModel({
283
+ uid: 'field-a',
284
+ fieldPath: 'a',
285
+ hidden: false,
286
+ fieldIndex: ['items:0'],
287
+ });
288
+ const collectedModel = createSubFormFieldModel({
289
+ uid: 'field-a-collected',
290
+ fieldPath: 'a',
291
+ hidden: false,
292
+ fieldPathArray: ['items', 0, 'a'],
293
+ });
294
+
295
+ await runSubFormFieldStateRule({
296
+ state: 'hidden',
297
+ fieldUid: 'field-a',
298
+ fieldKey: ['items:0'],
299
+ targetModel,
300
+ models: [collectedModel],
301
+ });
302
+
303
+ expect(targetModel.hidden).toBe(true);
304
+ expect(collectedModel.hidden).toBe(true);
305
+ });
306
+
307
+ it('should only sync hidden state for the current subform row', async () => {
308
+ const targetModel = createSubFormFieldModel({
309
+ uid: 'field-a',
310
+ fieldPath: 'a',
311
+ hidden: true,
312
+ fieldIndex: ['items:0'],
313
+ });
314
+ const row0CollectedModel = createSubFormFieldModel({
315
+ uid: 'field-a-row-0',
316
+ fieldPath: 'a',
317
+ hidden: true,
318
+ fieldPathArray: ['items', 0, 'a'],
319
+ });
320
+ const row1CollectedModel = createSubFormFieldModel({
321
+ uid: 'field-a-row-1',
322
+ fieldPath: 'a',
323
+ hidden: true,
324
+ fieldPathArray: ['items', 1, 'a'],
325
+ });
326
+
327
+ await runSubFormFieldStateRule({
328
+ state: 'visible',
329
+ fieldUid: 'field-a',
330
+ fieldKey: ['items:0'],
331
+ targetModel,
332
+ models: [row0CollectedModel, row1CollectedModel],
333
+ });
334
+
335
+ expect(row0CollectedModel.hidden).toBe(false);
336
+ expect(row1CollectedModel.hidden).toBe(true);
337
+ });
147
338
  });
148
339
 
149
340
  describe('linkageSetFieldProps action', () => {
@@ -28,6 +28,7 @@ describe('openView action - subModelKey behavior', () => {
28
28
  flowEngine: { context: { themeToken: { colorBgLayout: '#fff' } } },
29
29
  },
30
30
  layoutContentElement: {},
31
+ layoutContext: { source: 'layout-context' },
31
32
  view: {},
32
33
  viewer: {
33
34
  open: vi.fn(async (_opts: any) => undefined),
@@ -90,6 +91,38 @@ describe('openView action - subModelKey behavior', () => {
90
91
  // FlowPage should receive parent model uid
91
92
  expect(capturedElement?.type).toBe(FlowPage);
92
93
  expect(capturedElement?.props?.parentId).toBe('parent-model-uid');
94
+ expect(capturedElement?.props).not.toHaveProperty('layoutContext');
93
95
  expect(capturedElement?.props?.pageModelClass).toBe('ChildPageModel');
94
96
  });
97
+
98
+ it('uses current layout child page model as default pageModelClass', async () => {
99
+ const { ctx } = createCtx();
100
+ ctx.layout = {
101
+ childPageModelClass: 'MobileChildPageModel',
102
+ };
103
+
104
+ let capturedElement: any;
105
+ (ctx.viewer.open as any).mockImplementation(async (opts) => {
106
+ const currentView = { close: vi.fn(), update: vi.fn() };
107
+ capturedElement = opts.content(currentView);
108
+ return undefined;
109
+ });
110
+
111
+ await openView.handler(ctx, { navigation: false });
112
+
113
+ expect(capturedElement?.type).toBe(FlowPage);
114
+ expect(capturedElement?.props?.pageModelClass).toBe('MobileChildPageModel');
115
+ });
116
+
117
+ it('uses current layout child page model in default params', async () => {
118
+ const { ctx } = createCtx();
119
+ ctx.layout = {
120
+ childPageModelClass: 'MobileChildPageModel',
121
+ };
122
+ ctx.getPropertyMetaTree = vi.fn(() => []);
123
+
124
+ await expect(openView.defaultParams(ctx)).resolves.toMatchObject({
125
+ pageModelClass: 'MobileChildPageModel',
126
+ });
127
+ });
95
128
  });
@@ -24,6 +24,10 @@ const getRecordPkValue = (value: any) => {
24
24
  export const aclCheck = defineAction({
25
25
  name: 'aclCheck',
26
26
  async handler(ctx, params) {
27
+ if (ctx.skipAclCheck) {
28
+ return;
29
+ }
30
+
27
31
  const result = await ctx.aclCheck({
28
32
  dataSourceKey: ctx.dataSource?.key,
29
33
  resourceName: ctx.collectionField?.collectionName || ctx.resourceName,
@@ -158,6 +158,10 @@ export const aclCheckRefresh = defineAction({
158
158
  // 先清理旧状态,避免翻页/fork 复用造成污染
159
159
  resetAclDerivedState(ctx);
160
160
 
161
+ if (ctx.skipAclCheck) {
162
+ return;
163
+ }
164
+
161
165
  if (strategy === 'formItem') {
162
166
  await runFormItemAclCheck(ctx);
163
167
  return;
@@ -17,7 +17,7 @@ export const dateTimeFormat = defineAction({
17
17
  name: 'dateDisplayFormat',
18
18
  uiSchema: (ctx) => {
19
19
  const { collectionField } = ctx.model.context as any;
20
- const type = collectionField.type;
20
+ const isTimeField = collectionField.type === 'time' || collectionField.interface === 'time';
21
21
  const timeFormatField = {
22
22
  type: 'string',
23
23
  title: '{{t("Time format")}}',
@@ -41,7 +41,7 @@ export const dateTimeFormat = defineAction({
41
41
  ],
42
42
  'x-reactions': [
43
43
  (field) => {
44
- if (type !== 'time') {
44
+ if (!isTimeField) {
45
45
  const { showTime, picker } = field.form.values || {};
46
46
  field.hidden = !showTime || picker !== 'date';
47
47
  }
@@ -49,7 +49,7 @@ export const dateTimeFormat = defineAction({
49
49
  ],
50
50
  };
51
51
 
52
- if (type === 'time') {
52
+ if (isTimeField) {
53
53
  return {
54
54
  timeFormat: timeFormatField,
55
55
  };
@@ -159,24 +159,28 @@ export const dateTimeFormat = defineAction({
159
159
  };
160
160
  },
161
161
  defaultParams: (ctx: any) => {
162
- const { showTime, dateFormat, timeFormat, picker }: any = {
162
+ const { showTime, dateFormat, format, timeFormat, picker }: any = {
163
163
  ...ctx.model.context.collectionField.getComponentProps(),
164
164
  ...ctx.model.props,
165
165
  };
166
+ const collectionField = ctx.model.context.collectionField;
167
+ const isTimeField = collectionField.type === 'time' || collectionField.interface === 'time';
166
168
  return {
167
169
  picker: picker || 'date',
168
170
  dateFormat: dateFormat || 'YYYY-MM-DD',
169
- timeFormat: timeFormat || 'HH:mm:ss',
171
+ timeFormat: timeFormat || (isTimeField ? format : undefined) || 'HH:mm:ss',
170
172
  showTime,
171
173
  };
172
174
  },
173
175
  handler(ctx: any, params) {
174
176
  const { collectionField } = ctx.model.context as any;
175
- const type = collectionField.type;
176
- if (type === 'time') {
177
+ const isTimeField = collectionField.type === 'time' || collectionField.interface === 'time';
178
+ if (isTimeField) {
179
+ const timeFormat = params?.timeFormat || params?.format || 'HH:mm:ss';
177
180
  ctx.model.setProps({
178
181
  ...params,
179
- format: params?.timeFormat,
182
+ timeFormat,
183
+ format: timeFormat,
180
184
  });
181
185
  } else {
182
186
  ctx.model.setProps({
@@ -1916,6 +1916,120 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1916
1916
 
1917
1917
  return null;
1918
1918
  };
1919
+ const normalizeNamePathForKey = (namePath: any): Array<string | number> | null => {
1920
+ if (!Array.isArray(namePath) || !namePath.length) return null;
1921
+ const normalized = namePath.filter((seg) => typeof seg === 'string' || typeof seg === 'number') as Array<
1922
+ string | number
1923
+ >;
1924
+ return normalized.length ? normalized : null;
1925
+ };
1926
+ const getFieldIndexEntries = (fieldIndex: any): Array<{ name: string; index: number }> => {
1927
+ if (!Array.isArray(fieldIndex)) return [];
1928
+ return fieldIndex
1929
+ .map((entry) => {
1930
+ if (typeof entry !== 'string') return null;
1931
+ const [name, indexText] = entry.split(':');
1932
+ const index = Number(indexText);
1933
+ if (!name || !Number.isFinite(index)) return null;
1934
+ return { name, index };
1935
+ })
1936
+ .filter(Boolean) as Array<{ name: string; index: number }>;
1937
+ };
1938
+ const resolveIndexedRelativePath = (path: string, fieldIndex: any): Array<string | number> | null => {
1939
+ const pathSegs = parsePathString(path).filter((seg) => typeof seg === 'string' || typeof seg === 'number') as Array<
1940
+ string | number
1941
+ >;
1942
+ if (!pathSegs.length) return null;
1943
+
1944
+ const entries = getFieldIndexEntries(fieldIndex);
1945
+ if (!entries.length) return null;
1946
+
1947
+ const firstSeg = typeof pathSegs[0] === 'string' ? pathSegs[0] : '';
1948
+ const matchedEntryIndex = firstSeg ? entries.findIndex((entry) => entry.name === firstSeg) : -1;
1949
+ const prefixEntries = matchedEntryIndex >= 0 ? entries.slice(0, matchedEntryIndex) : entries;
1950
+ const remainingEntries = matchedEntryIndex >= 0 ? entries.slice(matchedEntryIndex) : [];
1951
+ const out: Array<string | number> = [];
1952
+
1953
+ prefixEntries.forEach((entry) => {
1954
+ out.push(entry.name, entry.index);
1955
+ });
1956
+
1957
+ let entryIndex = 0;
1958
+ pathSegs.forEach((seg, index) => {
1959
+ out.push(seg);
1960
+ const entry = remainingEntries[entryIndex];
1961
+ if (typeof seg !== 'string' || !entry || entry.name !== seg) return;
1962
+
1963
+ const nextSeg = pathSegs[index + 1];
1964
+ if (typeof nextSeg === 'number') {
1965
+ entryIndex += 1;
1966
+ return;
1967
+ }
1968
+ out.push(entry.index);
1969
+ entryIndex += 1;
1970
+ });
1971
+
1972
+ return out;
1973
+ };
1974
+ const getModelTargetPathKeys = (model: any): Set<string> => {
1975
+ const keys = new Set<string>();
1976
+ const fieldPathArray = normalizeNamePathForKey(model?.context?.fieldPathArray);
1977
+ if (fieldPathArray) {
1978
+ keys.add(namePathToPathKey(fieldPathArray));
1979
+ return keys;
1980
+ }
1981
+
1982
+ const targetPath = getModelTargetPathForPatch(model);
1983
+ if (targetPath) {
1984
+ const fieldIndexEntries = getFieldIndexEntries(model?.context?.fieldIndex);
1985
+ if (!fieldIndexEntries.length) {
1986
+ keys.add(`raw:${targetPath}`);
1987
+ }
1988
+
1989
+ const resolved = normalizeNamePathForKey(resolveDynamicNamePath(targetPath, model?.context?.fieldIndex));
1990
+ if (resolved) {
1991
+ keys.add(namePathToPathKey(resolved));
1992
+ }
1993
+
1994
+ const indexedRelativePath = resolveIndexedRelativePath(targetPath, model?.context?.fieldIndex);
1995
+ if (indexedRelativePath) {
1996
+ keys.add(namePathToPathKey(indexedRelativePath));
1997
+ }
1998
+ }
1999
+
2000
+ return keys;
2001
+ };
2002
+ const forEachModelIncludingForks = (visitor: (model: any) => void) => {
2003
+ const engine = (ctx as any)?.engine || ctx.model?.flowEngine;
2004
+ if (!engine?.forEachModel) return;
2005
+ engine.forEachModel((model: any) => {
2006
+ visitor(model);
2007
+
2008
+ const forks: any = model?.forks;
2009
+ if (forks && typeof forks.forEach === 'function') {
2010
+ forks.forEach((fork: any) => {
2011
+ if (!fork || fork.disposed) return;
2012
+ visitor(fork);
2013
+ });
2014
+ }
2015
+ });
2016
+ };
2017
+ const syncHiddenStateByTargetPath = (targetModel: any, hidden: boolean) => {
2018
+ const targetPathKeys = getModelTargetPathKeys(targetModel);
2019
+ if (!targetPathKeys.size) return;
2020
+
2021
+ const targetBlockUid = targetModel?.context?.blockModel?.uid;
2022
+ if (!targetBlockUid) return;
2023
+ forEachModelIncludingForks((model: any) => {
2024
+ if (!model || typeof model !== 'object') return;
2025
+ const modelBlockUid = model?.context?.blockModel?.uid;
2026
+ if (String(modelBlockUid) !== String(targetBlockUid)) return;
2027
+ const modelPathKeys = getModelTargetPathKeys(model);
2028
+ if (!Array.from(modelPathKeys).some((key) => targetPathKeys.has(key))) return;
2029
+
2030
+ model.hidden = hidden;
2031
+ });
2032
+ };
1919
2033
 
1920
2034
  // 1. 运行所有的联动规则
1921
2035
  for (const rule of linkageRules.filter((r) => r.enable)) {
@@ -1981,9 +2095,11 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1981
2095
  }
1982
2096
  });
1983
2097
 
2098
+ const hiddenStatePatches: Array<{ model: any; hidden: boolean }> = [];
1984
2099
  mergedByUid.forEach((model: any, uid) => {
1985
2100
  const patchProps = mergedPropsByUid.get(uid) || {};
1986
2101
  const newProps = { ...model.__originalProps, ...patchProps };
2102
+ const hasHiddenModelPatch = Object.prototype.hasOwnProperty.call(patchProps, 'hiddenModel');
1987
2103
 
1988
2104
  model.setProps(_.omit(newProps, ['hiddenModel', 'value', 'hiddenText']));
1989
2105
  syncFieldOptionsToForks(model, patchProps);
@@ -1991,6 +2107,9 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1991
2107
  model.setHidden(!!newProps.hiddenModel);
1992
2108
  } else {
1993
2109
  model.hidden = !!newProps.hiddenModel;
2110
+ if (hasHiddenModelPatch) {
2111
+ hiddenStatePatches.push({ model, hidden: model.hidden });
2112
+ }
1994
2113
  }
1995
2114
 
1996
2115
  if (newProps.required === true) {
@@ -2027,6 +2146,9 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
2027
2146
 
2028
2147
  model.__props = null;
2029
2148
  });
2149
+ hiddenStatePatches.forEach(({ model, hidden }) => {
2150
+ syncHiddenStateByTargetPath(model, hidden);
2151
+ });
2030
2152
 
2031
2153
  const allPatches = [...directValuePatches];
2032
2154
  if (!allPatches.length) return;
@@ -10,7 +10,7 @@
10
10
  import { defineAction, tExpr, FlowModelContext, FlowModel, FlowExitAllException } from '@nocobase/flow-engine';
11
11
  import React from 'react';
12
12
  import { FlowPage } from '../FlowPage';
13
- import { RootPageModel } from '../models';
13
+ import { PageModel, RootPageModel } from '../models';
14
14
  import _ from 'lodash';
15
15
 
16
16
  type DirtyAwareFlowModel = FlowModel & {
@@ -230,7 +230,7 @@ export const openView = defineAction({
230
230
  return {
231
231
  mode: 'drawer',
232
232
  size: 'medium',
233
- pageModelClass: 'ChildPageModel',
233
+ pageModelClass: ctx.layout?.childPageModelClass || 'ChildPageModel',
234
234
  uid: ctx.model?.uid,
235
235
  ...(filterByTkExpr ? { filterByTk: filterByTkExpr } : {}),
236
236
  ...(sourceIdExpr ? { sourceId: sourceIdExpr } : {}),
@@ -371,7 +371,8 @@ export const openView = defineAction({
371
371
  embed: {},
372
372
  };
373
373
 
374
- const pageModelClass = ctx.inputArgs.pageModelClass || params.pageModelClass || 'ChildPageModel';
374
+ const pageModelClass =
375
+ ctx.inputArgs.pageModelClass || params.pageModelClass || ctx.layout?.childPageModelClass || 'ChildPageModel';
375
376
  const size = ctx.inputArgs.size || params.size || 'medium';
376
377
  let pageModelUid: string | null = null;
377
378
  let pageModelRef: FlowModel | null = null;
@@ -414,7 +415,12 @@ export const openView = defineAction({
414
415
  associationName: runtimeAssociationName,
415
416
  tabUid: mergedTabUid,
416
417
  openerUids,
417
- pageActive: true, // 打开一个弹窗时,页面肯定是激活的
418
+ pageActive:
419
+ typeof inputArgs.pageActive === 'boolean'
420
+ ? inputArgs.pageActive
421
+ : typeof ctx.inputArgs.pageActive === 'boolean'
422
+ ? ctx.inputArgs.pageActive
423
+ : true,
418
424
  };
419
425
  // Ensure runtime keys propagate to view.inputArgs
420
426
  finalInputArgs.filterByTk = mergedFilterByTk;
@@ -477,6 +483,24 @@ export const openView = defineAction({
477
483
  });
478
484
  }
479
485
 
486
+ if (ctx.inputArgs.activateRef) {
487
+ ctx.inputArgs.activateRef.current = (forceRefresh = false) => {
488
+ currentView.inputArgs.pageActive = true;
489
+ if (pageModel instanceof PageModel) {
490
+ pageModel.activateCurrentTab(forceRefresh);
491
+ }
492
+ };
493
+ }
494
+
495
+ if (ctx.inputArgs.deactivateRef) {
496
+ ctx.inputArgs.deactivateRef.current = () => {
497
+ currentView.inputArgs.pageActive = false;
498
+ if (pageModel instanceof PageModel) {
499
+ pageModel.deactivateCurrentTab();
500
+ }
501
+ };
502
+ }
503
+
480
504
  Object.entries(defineProperties as Record<string, any>).forEach(([key, p]) => {
481
505
  pageModel.context.defineProperty(key, p);
482
506
  });