@nocobase/client-v2 2.2.0-beta.3 → 2.2.0-beta.6

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 (133) hide show
  1. package/es/RouteRepository.d.ts +21 -6
  2. package/es/components/form/JsonTextArea.d.ts +2 -1
  3. package/es/components/form/VariableJsonTextArea.d.ts +19 -0
  4. package/es/components/form/index.d.ts +1 -0
  5. package/es/components/index.d.ts +1 -0
  6. package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
  7. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
  8. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +2 -0
  9. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
  10. package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +4 -0
  11. package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
  12. package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
  13. package/es/flow/models/base/GridModel.d.ts +1 -1
  14. package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
  15. package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
  16. package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
  17. package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +21 -0
  18. package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
  19. package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +2 -0
  20. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +1 -0
  21. package/es/flow/utils/dateTimeDisplayProps.d.ts +49 -0
  22. package/es/flow-compat/FieldValidation.d.ts +2 -1
  23. package/es/index.d.ts +2 -1
  24. package/es/index.mjs +167 -121
  25. package/lib/index.js +167 -121
  26. package/package.json +7 -7
  27. package/src/RouteRepository.ts +126 -24
  28. package/src/__tests__/RouteRepository.test.ts +216 -0
  29. package/src/__tests__/exports.test.ts +16 -0
  30. package/src/__tests__/settings-center.test.tsx +33 -0
  31. package/src/components/form/JsonTextArea.tsx +21 -11
  32. package/src/components/form/VariableJsonTextArea.tsx +175 -0
  33. package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
  34. package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
  35. package/src/components/form/index.tsx +1 -0
  36. package/src/components/index.ts +1 -0
  37. package/src/flow/__tests__/FlowRoute.test.tsx +443 -5
  38. package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
  39. package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
  40. package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
  41. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
  42. package/src/flow/actions/customVariable.tsx +1 -2
  43. package/src/flow/actions/dateTimeFormat.tsx +42 -28
  44. package/src/flow/actions/linkageRules.tsx +122 -34
  45. package/src/flow/actions/runjs.tsx +2 -14
  46. package/src/flow/actions/validation.tsx +62 -30
  47. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +33 -10
  48. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +310 -0
  49. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +44 -8
  50. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
  51. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
  52. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
  53. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
  54. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +21 -8
  55. package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
  56. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +188 -0
  57. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
  58. package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +166 -2
  59. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
  60. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
  61. package/src/flow/components/DefaultValue.tsx +1 -2
  62. package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
  63. package/src/flow/components/FieldAssignRulesEditor.tsx +62 -12
  64. package/src/flow/components/FlowRoute.tsx +56 -11
  65. package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
  66. package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +81 -4
  67. package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
  68. package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
  69. package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
  70. package/src/flow/flows/editMarkdownFlow.tsx +1 -1
  71. package/src/flow/index.ts +1 -1
  72. package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
  73. package/src/flow/models/actions/JSActionModel.tsx +2 -7
  74. package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
  75. package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
  76. package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
  77. package/src/flow/models/base/ActionModel.tsx +12 -1
  78. package/src/flow/models/base/GridModel.tsx +38 -7
  79. package/src/flow/models/base/PageModel/PageModel.tsx +4 -1
  80. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +28 -4
  81. package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
  82. package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
  83. package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
  84. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +31 -3
  85. package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
  86. package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +77 -0
  87. package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
  88. package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
  89. package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
  90. package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
  91. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
  92. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +880 -102
  93. package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
  94. package/src/flow/models/blocks/form/value-runtime/runtime.ts +257 -13
  95. package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
  96. package/src/flow/models/blocks/js-block/JSBlock.tsx +2 -7
  97. package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
  98. package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +1 -1
  99. package/src/flow/models/blocks/table/TableBlockModel.tsx +13 -3
  100. package/src/flow/models/blocks/table/TableColumnModel.tsx +36 -3
  101. package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +54 -0
  102. package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
  103. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +159 -1
  104. package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
  105. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +29 -6
  106. package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +16 -4
  107. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +24 -1
  108. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
  109. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +12 -0
  110. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
  111. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
  112. package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
  113. package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
  114. package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
  115. package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
  116. package/src/flow/models/fields/InputFieldModel.tsx +14 -22
  117. package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
  118. package/src/flow/models/fields/JSFieldModel.tsx +2 -7
  119. package/src/flow/models/fields/JSItemModel.tsx +2 -14
  120. package/src/flow/models/fields/SelectFieldModel.tsx +6 -4
  121. package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
  122. package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +17 -0
  123. package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
  124. package/src/flow/models/topbar/TopbarActionModel.tsx +93 -10
  125. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +258 -0
  126. package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
  127. package/src/flow-compat/FieldValidation.tsx +122 -60
  128. package/src/flow-compat/Popover.tsx +43 -4
  129. package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
  130. package/src/index.ts +8 -1
  131. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
  132. package/src/settings-center/AdminSettingsLayout.tsx +3 -0
  133. package/src/settings-center/plugin-manager/index.tsx +3 -0
@@ -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';
@@ -40,7 +36,7 @@ import { FilterGroup } from '../components/filter/FilterGroup';
40
36
  import { LinkageFilterItem } from '../components/filter';
41
37
  import { CodeEditor } from '../components/code-editor';
42
38
  import { FieldAssignRulesEditor } from '../components/FieldAssignRulesEditor';
43
- import type { FieldAssignRuleItem } from '../components/FieldAssignRulesEditor';
39
+ import type { AssignMode, FieldAssignRuleItem } from '../components/FieldAssignRulesEditor';
44
40
  import { collectFieldAssignCascaderOptions } from '../components/fieldAssignOptions';
45
41
  import { useAssociationTitleFieldSync } from '../components/useAssociationTitleFieldSync';
46
42
  import _ from 'lodash';
@@ -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
  }
@@ -888,6 +884,20 @@ type ArrayFieldComponentProps = {
888
884
 
889
885
  const LEGACY_ASSIGN_RULE = { mode: 'assign', valueKey: 'assignValue' } as const;
890
886
  const LEGACY_DEFAULT_RULE = { mode: 'default', valueKey: 'initialValue' } as const;
887
+ const LINKAGE_ASSIGN_MODE_PROP = '__linkageAssignMode';
888
+
889
+ function normalizeLinkageAssignMode(mode: unknown): AssignMode {
890
+ if (mode === 'default') return 'default';
891
+ if (mode === 'override') return 'override';
892
+ return 'assign';
893
+ }
894
+
895
+ type LinkageValuePatch = {
896
+ path: Array<string | number>;
897
+ value: unknown;
898
+ whenEmpty?: boolean;
899
+ mode?: AssignMode;
900
+ };
891
901
 
892
902
  const FieldAssignRulesActionComponent: React.FC<
893
903
  ArrayFieldComponentProps & {
@@ -1002,16 +1012,24 @@ export const linkageAssignField = defineAction({
1002
1012
  continue;
1003
1013
  }
1004
1014
 
1005
- const mode = it?.mode === 'default' ? 'default' : 'assign';
1015
+ const mode = normalizeLinkageAssignMode(it?.mode);
1006
1016
  if (fieldModel) {
1007
1017
  if (mode === 'default') {
1008
1018
  setProps(fieldModel as FlowModel, { initialValue: finalValue });
1009
1019
  } else {
1010
- setProps(fieldModel as FlowModel, { value: finalValue });
1020
+ setProps(fieldModel as FlowModel, {
1021
+ value: finalValue,
1022
+ ...(mode === 'override' ? { [LINKAGE_ASSIGN_MODE_PROP]: mode } : {}),
1023
+ });
1011
1024
  }
1012
1025
  } else if (typeof addFormValuePatch === 'function') {
1013
1026
  // 对关联字段子属性(如 user.name)等没有独立 FormItemModel 的目标,直接写入表单值
1014
- addFormValuePatch({ path: targetPath, value: finalValue, whenEmpty: mode === 'default' });
1027
+ addFormValuePatch({
1028
+ path: targetPath,
1029
+ value: finalValue,
1030
+ whenEmpty: mode === 'default',
1031
+ ...(mode === 'override' ? { mode } : {}),
1032
+ });
1015
1033
  }
1016
1034
  }
1017
1035
  } catch (error) {
@@ -1177,7 +1195,7 @@ export const subFormLinkageAssignField = defineAction({
1177
1195
  continue;
1178
1196
  }
1179
1197
 
1180
- const mode = it?.mode === 'default' ? 'default' : 'assign';
1198
+ const mode = normalizeLinkageAssignMode(it?.mode);
1181
1199
  const actionName = (ctx.model as any)?.getAclActionName?.() ?? (ctx.model as any)?.context?.actionName;
1182
1200
  const isEditForm = actionName === 'update';
1183
1201
  const isNewItem = (ctx as any)?.item?.__is_new__ === true;
@@ -1207,12 +1225,21 @@ export const subFormLinkageAssignField = defineAction({
1207
1225
  continue;
1208
1226
  }
1209
1227
 
1228
+ if (mode === 'override' && hasExplicitPathHit(targetPath)) {
1229
+ continue;
1230
+ }
1231
+
1210
1232
  if (!fieldUid) {
1211
1233
  if (mode === 'default' && hasExplicitPathHit(targetPath)) {
1212
1234
  continue;
1213
1235
  }
1214
1236
  if (typeof addFormValuePatch === 'function') {
1215
- addFormValuePatch({ path: targetPath, value: finalValue, whenEmpty: mode === 'default' });
1237
+ addFormValuePatch({
1238
+ path: targetPath,
1239
+ value: finalValue,
1240
+ whenEmpty: mode === 'default',
1241
+ ...(mode === 'override' ? { mode } : {}),
1242
+ });
1216
1243
  }
1217
1244
  continue;
1218
1245
  }
@@ -1223,7 +1250,10 @@ export const subFormLinkageAssignField = defineAction({
1223
1250
  if (mode === 'default') {
1224
1251
  setProps(model, { initialValue: finalValue });
1225
1252
  } else {
1226
- setProps(model, { value: finalValue });
1253
+ setProps(model, {
1254
+ value: finalValue,
1255
+ ...(mode === 'override' ? { [LINKAGE_ASSIGN_MODE_PROP]: mode } : {}),
1256
+ });
1227
1257
  }
1228
1258
  }
1229
1259
  } catch (error) {
@@ -1363,8 +1393,7 @@ export const linkageRunjs = defineAction({
1363
1393
  }
1364
1394
 
1365
1395
  try {
1366
- const navigator = createSafeNavigator();
1367
- await ctx.runjs(script, { window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator });
1396
+ await ctx.runjs(script);
1368
1397
  } catch (error) {
1369
1398
  console.error('Script execution error:', error);
1370
1399
  // 可以选择显示错误信息给用户
@@ -1781,7 +1810,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1781
1810
  const modelsToApply = new Set<FlowModel>(allModels);
1782
1811
  const patchPropsByModel = new Map<FlowModel, any>();
1783
1812
  const clearValueOnHiddenModelUids = new Set<string>();
1784
- const directValuePatches: Array<{ path: Array<string | number>; value: any; whenEmpty?: boolean }> = [];
1813
+ const directValuePatches: LinkageValuePatch[] = [];
1785
1814
  const rootCollection = getCollectionFromModel((ctx.model as any)?.context?.blockModel ?? ctx.model);
1786
1815
  const isSafeToWriteAssociationSubpath = (namePath: any): boolean => {
1787
1816
  if (!Array.isArray(namePath) || !namePath.length) return true;
@@ -1836,13 +1865,18 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1836
1865
  }
1837
1866
 
1838
1867
  for (const patch of lastPatchByPathKey.values()) {
1839
- if (!patch.whenEmpty) continue;
1868
+ if (!patch.whenEmpty && patch.mode !== 'default') continue;
1840
1869
  runtime.recordDefaultValuePatch(patch.path, patch.value);
1841
1870
  }
1842
1871
  };
1843
- const addFormValuePatch = (patch: { path: any; value: any; whenEmpty?: boolean }) => {
1872
+ const getPatchMode = (patch: { mode?: unknown; whenEmpty?: boolean }): AssignMode => {
1873
+ if (patch?.mode === 'default') return 'default';
1874
+ if (patch?.mode === 'override') return 'override';
1875
+ return patch?.whenEmpty ? 'default' : 'assign';
1876
+ };
1877
+ const addFormValuePatch = (patch: { path: unknown; value: unknown; whenEmpty?: boolean; mode?: AssignMode }) => {
1844
1878
  if (!patch) return;
1845
- const path = (patch as any)?.path;
1879
+ const path = patch.path;
1846
1880
  if (!path) return;
1847
1881
  const resolvedPath = resolveNamePathForPatch(path);
1848
1882
  if (!resolvedPath) {
@@ -1862,8 +1896,9 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1862
1896
  });
1863
1897
  return;
1864
1898
  }
1865
- const whenEmpty = !!(patch as any)?.whenEmpty;
1866
- const value = (patch as any)?.value;
1899
+ const mode = getPatchMode(patch);
1900
+ const whenEmpty = mode === 'default';
1901
+ const value = patch.value;
1867
1902
  try {
1868
1903
  const form = ctx.model?.context?.form;
1869
1904
  const current = form?.getFieldValue?.(resolvedPath);
@@ -1878,6 +1913,15 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1878
1913
  return;
1879
1914
  }
1880
1915
  }
1916
+ if (mode === 'override') {
1917
+ const runtime = getDefaultPatchRuntime();
1918
+ if (
1919
+ typeof runtime?.canApplyOverrideValuePatch === 'function' &&
1920
+ !runtime.canApplyOverrideValuePatch(resolvedPath)
1921
+ ) {
1922
+ return;
1923
+ }
1924
+ }
1881
1925
  if (_.isEqual(current, value)) {
1882
1926
  return;
1883
1927
  }
@@ -1889,6 +1933,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1889
1933
  path: resolvedPath,
1890
1934
  value,
1891
1935
  ...(whenEmpty ? { whenEmpty: true } : {}),
1936
+ ...(mode === 'override' ? { mode } : {}),
1892
1937
  });
1893
1938
  };
1894
1939
  const removePendingFormValuePatches = (path: any) => {
@@ -1948,6 +1993,21 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1948
1993
  >;
1949
1994
  return normalized.length ? normalized : null;
1950
1995
  };
1996
+ const pathKeysEqual = (
1997
+ a: Array<string | number> | null | undefined,
1998
+ b: Array<string | number> | null | undefined,
1999
+ ) => {
2000
+ if (!a || !b) return false;
2001
+ return namePathToPathKey(a) === namePathToPathKey(b);
2002
+ };
2003
+ const namePathEndsWith = (
2004
+ namePath: Array<string | number> | null | undefined,
2005
+ suffix: Array<string | number> | null | undefined,
2006
+ ) => {
2007
+ if (!namePath || !suffix || suffix.length > namePath.length) return false;
2008
+ const offset = namePath.length - suffix.length;
2009
+ return suffix.every((seg, index) => namePath[offset + index] === seg);
2010
+ };
1951
2011
  const getFieldIndexEntries = (fieldIndex: any): Array<{ name: string; index: number }> => {
1952
2012
  if (!Array.isArray(fieldIndex)) return [];
1953
2013
  return fieldIndex
@@ -1996,17 +2056,35 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
1996
2056
 
1997
2057
  return out;
1998
2058
  };
2059
+ const getTrustedFieldPathArray = (
2060
+ fieldPathArray: Array<string | number> | null,
2061
+ targetPath: string | null,
2062
+ fieldIndex: unknown,
2063
+ ): Array<string | number> | null => {
2064
+ if (!fieldPathArray || !targetPath) return null;
2065
+
2066
+ const targetNamePath = normalizeNamePathForKey(
2067
+ parsePathString(targetPath).filter((seg) => typeof seg === 'string' || typeof seg === 'number'),
2068
+ );
2069
+ const resolvedTargetPath = normalizeNamePathForKey(resolveDynamicNamePath(targetPath, fieldIndex));
2070
+ const indexedRelativePath = resolveIndexedRelativePath(targetPath, fieldIndex);
2071
+
2072
+ if (
2073
+ pathKeysEqual(fieldPathArray, resolvedTargetPath) ||
2074
+ pathKeysEqual(fieldPathArray, indexedRelativePath) ||
2075
+ namePathEndsWith(fieldPathArray, targetNamePath)
2076
+ ) {
2077
+ return fieldPathArray;
2078
+ }
2079
+
2080
+ return null;
2081
+ };
1999
2082
  const getModelTargetPathForHiddenClear = (model: any): string | Array<string | number> | null => {
2000
2083
  const fieldPathArray = normalizeNamePathForKey(model?.context?.fieldPathArray);
2001
2084
  const targetPath = getModelTargetPathForPatch(model);
2002
- if (fieldPathArray) {
2003
- const targetPathLastString = targetPath
2004
- ? ([...parsePathString(targetPath)].reverse().find((seg) => typeof seg === 'string') as string | undefined)
2005
- : undefined;
2006
- const fieldPathArrayLastString = [...fieldPathArray].reverse().find((seg) => typeof seg === 'string');
2007
- if (!targetPathLastString || targetPathLastString === fieldPathArrayLastString) {
2008
- return fieldPathArray;
2009
- }
2085
+ const trustedFieldPathArray = getTrustedFieldPathArray(fieldPathArray, targetPath, model?.context?.fieldIndex);
2086
+ if (trustedFieldPathArray) {
2087
+ return trustedFieldPathArray;
2010
2088
  }
2011
2089
 
2012
2090
  if (!targetPath) return null;
@@ -2016,12 +2094,13 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
2016
2094
  const getModelTargetPathKeys = (model: any): Set<string> => {
2017
2095
  const keys = new Set<string>();
2018
2096
  const fieldPathArray = normalizeNamePathForKey(model?.context?.fieldPathArray);
2019
- if (fieldPathArray) {
2020
- keys.add(namePathToPathKey(fieldPathArray));
2097
+ const targetPath = getModelTargetPathForPatch(model);
2098
+ const trustedFieldPathArray = getTrustedFieldPathArray(fieldPathArray, targetPath, model?.context?.fieldIndex);
2099
+ if (trustedFieldPathArray) {
2100
+ keys.add(namePathToPathKey(trustedFieldPathArray));
2021
2101
  return keys;
2022
2102
  }
2023
2103
 
2024
- const targetPath = getModelTargetPathForPatch(model);
2025
2104
  if (targetPath) {
2026
2105
  const fieldIndexEntries = getFieldIndexEntries(model?.context?.fieldIndex);
2027
2106
  if (!fieldIndexEntries.length) {
@@ -2082,6 +2161,14 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
2082
2161
 
2083
2162
  for (const action of actions) {
2084
2163
  const setProps = (model: FlowModel & { __originalProps?: any; __shouldReset?: boolean }, props: any) => {
2164
+ const normalizedProps =
2165
+ props && typeof props === 'object' && Object.prototype.hasOwnProperty.call(props, 'value')
2166
+ ? {
2167
+ ...props,
2168
+ [LINKAGE_ASSIGN_MODE_PROP]: normalizeLinkageAssignMode(props?.[LINKAGE_ASSIGN_MODE_PROP]),
2169
+ }
2170
+ : props;
2171
+
2085
2172
  // 存储原始值,用于恢复
2086
2173
  if (!model.__originalProps) {
2087
2174
  model.__originalProps = {
@@ -2097,7 +2184,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
2097
2184
  // 临时存起来,遍历完所有规则后,再统一处理
2098
2185
  patchPropsByModel.set(model, {
2099
2186
  ...(patchPropsByModel.get(model) || {}),
2100
- ...props,
2187
+ ...normalizedProps,
2101
2188
  });
2102
2189
 
2103
2190
  if (
@@ -2151,7 +2238,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
2151
2238
  const prevHidden = !!model.hidden;
2152
2239
  const nextHidden = !!newProps.hiddenModel;
2153
2240
 
2154
- model.setProps(_.omit(newProps, ['hiddenModel', 'value', 'hiddenText']));
2241
+ model.setProps(_.omit(newProps, ['hiddenModel', 'value', 'hiddenText', LINKAGE_ASSIGN_MODE_PROP]));
2155
2242
  syncFieldOptionsToForks(model, patchProps);
2156
2243
  if (typeof model.setHidden === 'function') {
2157
2244
  model.setHidden(nextHidden);
@@ -2190,7 +2277,8 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
2190
2277
  targetUid: model?.uid,
2191
2278
  });
2192
2279
  } else {
2193
- addFormValuePatch({ path: targetPath, value: newProps.value });
2280
+ const mode = normalizeLinkageAssignMode(patchProps?.[LINKAGE_ASSIGN_MODE_PROP]);
2281
+ addFormValuePatch({ path: targetPath, value: newProps.value, ...(mode === 'override' ? { mode } : {}) });
2194
2282
  }
2195
2283
  }
2196
2284
 
@@ -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
  });
@@ -10,6 +10,56 @@
10
10
  import { defineAction, jioToJoiSchema, tExpr } from '@nocobase/flow-engine';
11
11
  import { FieldValidation } from '../../flow-compat';
12
12
 
13
+ type ValidationType = 'string' | 'number' | 'array' | 'boolean' | 'any';
14
+
15
+ interface ValidationRule {
16
+ name: string;
17
+ args?: any;
18
+ }
19
+
20
+ interface ValidationData {
21
+ type: ValidationType;
22
+ rules?: ValidationRule[];
23
+ }
24
+
25
+ interface ValidationContext {
26
+ model: {
27
+ props: {
28
+ label?: string;
29
+ };
30
+ };
31
+ t: (key: string, options?: Record<string, unknown>) => string;
32
+ }
33
+
34
+ function buildValidationRules(ctx: ValidationContext, validation: ValidationData) {
35
+ const rules = [];
36
+ const schema = jioToJoiSchema(validation);
37
+ const label = ctx.model.props.label;
38
+ rules.push({
39
+ validator: (_: unknown, value: unknown) => {
40
+ const { error } = schema.validate(value, {
41
+ abortEarly: false,
42
+ });
43
+
44
+ if (error) {
45
+ const messages = error.details.map((d: { type: string; context?: Record<string, unknown> }) => {
46
+ return ctx.t(`${d.type}`, {
47
+ ...d.context,
48
+ ns: 'data-source-main',
49
+ label,
50
+ });
51
+ });
52
+ const div = document.createElement('div');
53
+ div.innerHTML = messages.join('; ');
54
+ return Promise.reject(div.textContent);
55
+ }
56
+
57
+ return Promise.resolve();
58
+ },
59
+ });
60
+ return rules;
61
+ }
62
+
13
63
  export const validation = defineAction({
14
64
  title: tExpr('Validation'),
15
65
  name: 'validation',
@@ -29,6 +79,7 @@ export const validation = defineAction({
29
79
  type: targetInterface.validationType,
30
80
  availableValidationOptions: [...new Set(targetInterface.availableValidationOptions)],
31
81
  excludeValidationOptions: [...new Set(targetInterface.excludeValidationOptions)],
82
+ inheritedValue: ctx.model.collectionField.validation,
32
83
  isAssociation: targetInterface.isAssociation,
33
84
  },
34
85
  },
@@ -36,40 +87,21 @@ export const validation = defineAction({
36
87
  },
37
88
  handler(ctx, params) {
38
89
  if (params.validation) {
39
- const rules = [];
40
- const schema = jioToJoiSchema(params.validation);
41
- const label = ctx.model.props.label;
42
- rules.push({
43
- validator: (_, value) => {
44
- const { error } = schema.validate(value, {
45
- abortEarly: false,
46
- });
47
-
48
- if (error) {
49
- const messages = error.details.map((d) => {
50
- return ctx.t(`${d.type}`, {
51
- ...d.context,
52
- ns: 'data-source-main',
53
- label,
54
- });
55
- });
56
- const div = document.createElement('div');
57
- div.innerHTML = messages.join('; ');
58
- return Promise.reject(div.textContent);
59
- }
60
-
61
- return Promise.resolve();
62
- },
63
- });
64
- const hasRequiredInCollection = params.validation.rules.some((rule) => rule.name === 'required');
65
- if (hasRequiredInCollection) {
90
+ const collectionValidation = ctx.model.collectionField?.validation;
91
+ const collectionRules = ctx.model.collectionField?.getComponentProps?.().rules || [];
92
+ const uiRules = params.validation.rules?.length
93
+ ? buildValidationRules(ctx as unknown as ValidationContext, params.validation)
94
+ : [];
95
+ const hasRequiredInValidation = [collectionValidation, params.validation].some(
96
+ (validation) => validation?.rules?.some((rule) => rule.name === 'required'),
97
+ );
98
+ if (hasRequiredInValidation) {
66
99
  ctx.model.setProps({
67
- required: hasRequiredInCollection,
100
+ required: hasRequiredInValidation,
68
101
  });
69
102
  }
70
- console.log(rules);
71
103
  ctx.model.setProps({
72
- rules,
104
+ rules: [...collectionRules, ...uiRules],
73
105
  validation: params.validation,
74
106
  });
75
107
  }
@@ -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`
@@ -314,25 +315,37 @@ const DesignerButtonMenuItem: FC<{ item: AdminLayoutMenuNode; fallbackParentRout
314
315
  );
315
316
  };
316
317
 
317
- const matchesRoutePath = (route: NocoBaseDesktopRoute | undefined, pathname: string): boolean => {
318
+ const matchesRoutePath = (
319
+ route: NocoBaseDesktopRoute | undefined,
320
+ pathname: string,
321
+ layout?: AdminLayoutRoutePathLike | null,
322
+ ): boolean => {
318
323
  if (!route) {
319
324
  return false;
320
325
  }
321
326
 
322
327
  const candidates = [
323
- route.id != null ? `/admin/${route.id}` : null,
324
- route.schemaUid ? `/admin/${route.schemaUid}` : null,
328
+ route.id != null ? joinAdminLayoutRoutePath(layout, route.id) : null,
329
+ route.schemaUid ? joinAdminLayoutRoutePath(layout, route.schemaUid) : null,
325
330
  ].filter(Boolean) as string[];
326
331
 
327
332
  if (candidates.some((candidate) => pathname === candidate || pathname.startsWith(`${candidate}/`))) {
328
333
  return true;
329
334
  }
330
335
 
331
- return Array.isArray(route.children) ? route.children.some((child) => matchesRoutePath(child, pathname)) : false;
336
+ return Array.isArray(route.children)
337
+ ? route.children.some((child) => matchesRoutePath(child, pathname, layout))
338
+ : false;
332
339
  };
333
340
 
334
- const findSelectedTopGroupRoute = (routes: NocoBaseDesktopRoute[], pathname: string) => {
335
- return routes.find((route) => route.type === NocoBaseDesktopRouteType.group && matchesRoutePath(route, pathname));
341
+ const findSelectedTopGroupRoute = (
342
+ routes: NocoBaseDesktopRoute[],
343
+ pathname: string,
344
+ layout?: AdminLayoutRoutePathLike | null,
345
+ ) => {
346
+ return routes.find(
347
+ (route) => route.type === NocoBaseDesktopRouteType.group && matchesRoutePath(route, pathname, layout),
348
+ );
336
349
  };
337
350
 
338
351
  const renderMenuNodeWithModel = (
@@ -376,6 +389,11 @@ export const AdminLayoutComponent = observer((props: any) => {
376
389
  const { token } = antdTheme.useToken();
377
390
  const customToken = token as CustomToken;
378
391
  const isMobileLayout = !!adminLayoutModel?.isMobileLayout;
392
+ const adminLayoutRoutePath = adminLayoutModel?.layout?.routePath;
393
+ const adminLayoutRoutePathLike = useMemo<AdminLayoutRoutePathLike | undefined>(
394
+ () => (adminLayoutRoutePath ? { routePath: adminLayoutRoutePath } : undefined),
395
+ [adminLayoutRoutePath],
396
+ );
379
397
  const menuRouteRefreshVersion = adminLayoutModel?.menuRouteRefreshVersion || 0;
380
398
  const isMobileSider = isMobileLayout;
381
399
  const [collapsed, setCollapsed] = useState(isMobileSider);
@@ -403,8 +421,8 @@ export const AdminLayoutComponent = observer((props: any) => {
403
421
  [adminLayoutModel],
404
422
  );
405
423
  const selectedTopGroupRoute = useMemo(
406
- () => findSelectedTopGroupRoute(allAccessRoutes, location.pathname),
407
- [allAccessRoutes, location.pathname],
424
+ () => findSelectedTopGroupRoute(allAccessRoutes, location.pathname, adminLayoutRoutePathLike),
425
+ [adminLayoutRoutePathLike, allAccessRoutes, location.pathname],
408
426
  );
409
427
 
410
428
  const handleMenuDragEnd = useCallback(
@@ -458,6 +476,7 @@ export const AdminLayoutComponent = observer((props: any) => {
458
476
 
459
477
  useEffect(() => {
460
478
  const routeRepository = flowEngine.context.routeRepository;
479
+ const deactivateLayout = routeRepository?.activateLayout?.(adminLayoutModel?.layout);
461
480
  const subscriber = () => {
462
481
  const updatedRoutes = routeRepository?.listAccessible() || [];
463
482
  setAllAccessRoutes(updatedRoutes);
@@ -470,8 +489,9 @@ export const AdminLayoutComponent = observer((props: any) => {
470
489
 
471
490
  return () => {
472
491
  routeRepository?.unsubscribe(subscriber);
492
+ deactivateLayout?.();
473
493
  };
474
- }, [flowEngine]);
494
+ }, [adminLayoutModel, adminLayoutModel?.layout?.uid, flowEngine]);
475
495
 
476
496
  useEffect(() => {
477
497
  if (typeof window === 'undefined') {
@@ -697,7 +717,10 @@ export const AdminLayoutComponent = observer((props: any) => {
697
717
  <SetIsMobileLayout isMobile={isMobile} model={adminLayoutModel}>
698
718
  <ConfigProvider theme={isMobile ? mobileTheme : theme}>
699
719
  <GlobalStyle />
700
- <AdminLayoutContent onContentElementChange={handleLayoutContentElementChange} />
720
+ <AdminLayoutContent
721
+ layout={adminLayoutModel?.layout}
722
+ onContentElementChange={handleLayoutContentElementChange}
723
+ />
701
724
  </ConfigProvider>
702
725
  </SetIsMobileLayout>
703
726
  );