@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
@@ -42,6 +42,12 @@ import { normalizeFilterValueByOperator } from '../models/blocks/filter-form/val
42
42
  import { FieldAssignExactDatePicker, type ExactDatePickerMode } from './FieldAssignExactDatePicker';
43
43
  import { limitAssociationMetaTree } from './filter/metaTreeAssociationDepth';
44
44
 
45
+ type VariableInputConverters = {
46
+ renderInputComponent?: (metaTreeNode: MetaTreeNode | null) => React.ComponentType<any> | null;
47
+ resolvePathFromValue?: (value: any) => string[] | undefined;
48
+ resolveValueFromPath?: (metaTreeNode: MetaTreeNode) => any;
49
+ };
50
+
45
51
  const DATE_FIELD_INTERFACES = new Set(['date', 'datetime', 'datetimeNoTz', 'createdAt', 'updatedAt', 'unixTimestamp']);
46
52
 
47
53
  const TZ_AWARE_DATE_INTERFACES = new Set(['datetime', 'createdAt', 'updatedAt', 'unixTimestamp']);
@@ -341,7 +347,11 @@ interface Props {
341
347
  * 默认 false,保持历史行为。
342
348
  */
343
349
  enableDateVariableAsConstant?: boolean;
350
+ /** 是否允许在变量选择器中使用 RunJS。默认 true,保持历史行为。 */
351
+ allowRunJS?: boolean;
344
352
  maxAssociationFieldDepth?: number;
353
+ disabled?: boolean;
354
+ variableConverters?: VariableInputConverters;
345
355
  }
346
356
 
347
357
  type ResolvedFieldContext = {
@@ -712,7 +722,10 @@ export const FieldAssignValueInput: React.FC<Props> = ({
712
722
  preferFormItemFieldModel,
713
723
  associationFieldNamesOverride,
714
724
  enableDateVariableAsConstant = false,
725
+ allowRunJS = true,
715
726
  maxAssociationFieldDepth = 2,
727
+ disabled = false,
728
+ variableConverters,
716
729
  }) => {
717
730
  const flowCtx = useFlowContext<FlowModelContext>();
718
731
  const normalizeEventValue = React.useCallback((eventOrValue: unknown) => {
@@ -1077,7 +1090,7 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1077
1090
  fm.setStepParams('selectSettings', 'fieldNames', { label: overrideLabel });
1078
1091
  }
1079
1092
  fm?.setProps?.({
1080
- disabled: false,
1093
+ disabled,
1081
1094
  readPretty: false,
1082
1095
  pattern: 'editable',
1083
1096
  updateAssociation: false,
@@ -1140,6 +1153,7 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1140
1153
  preferFormItemFieldModel,
1141
1154
  associationFieldNamesOverride?.label,
1142
1155
  associationFieldNamesOverride?.value,
1156
+ disabled,
1143
1157
  ]);
1144
1158
 
1145
1159
  // 当传入 operator / operatorMetaList 时,按 operator schema 适配临时字段的输入组件与 props。
@@ -1192,6 +1206,9 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1192
1206
  React.useEffect(() => {
1193
1207
  const coercedValue = coerceEmptyValueForRenderer(inputProps?.value);
1194
1208
  const handleChange = (ev: any) => {
1209
+ if (inputProps?.disabled) {
1210
+ return;
1211
+ }
1195
1212
  const nextRaw = normalizeEventValue(ev);
1196
1213
  const normalizedForStore = operator ? normalizeFilterValueByOperator(operator, nextRaw) : nextRaw;
1197
1214
  const nextValue = coerceEmptyValueForRenderer(normalizedForStore);
@@ -1223,6 +1240,7 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1223
1240
  value={inputProps?.value}
1224
1241
  onChange={(e) => inputProps?.onChange?.(normalizeEventValue(e))}
1225
1242
  placeholder={placeholder}
1243
+ disabled={inputProps?.disabled}
1226
1244
  style={withFullWidthStyle(wrapperStyle)}
1227
1245
  />
1228
1246
  );
@@ -1241,6 +1259,7 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1241
1259
  const C: React.FC<any> = (inputProps) => {
1242
1260
  const wrapperStyle = pickStyle(inputProps?.style);
1243
1261
  const raw = inputProps?.value;
1262
+ const isDisabled = Boolean(inputProps?.disabled);
1244
1263
  const parsed = isCtxDateExpression(raw) ? parseCtxDateExpression(raw) : raw;
1245
1264
  const parsedValue = typeof parsed === 'undefined' ? undefined : parsed;
1246
1265
  const { token } = theme.useToken();
@@ -1265,6 +1284,9 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1265
1284
  });
1266
1285
 
1267
1286
  const handleSelect = (val: string) => {
1287
+ if (isDisabled) {
1288
+ return;
1289
+ }
1268
1290
  setOpen(false);
1269
1291
  if (val === 'exact') {
1270
1292
  inputProps?.onChange?.('');
@@ -1279,10 +1301,16 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1279
1301
  };
1280
1302
 
1281
1303
  const handleExactSingleChange = (nextValue: any) => {
1304
+ if (isDisabled) {
1305
+ return;
1306
+ }
1282
1307
  inputProps?.onChange?.(nextValue || '');
1283
1308
  };
1284
1309
 
1285
1310
  const handleExactRangeChange = (nextValue: any) => {
1311
+ if (isDisabled) {
1312
+ return;
1313
+ }
1286
1314
  inputProps?.onChange?.(nextValue || '');
1287
1315
  };
1288
1316
 
@@ -1327,7 +1355,11 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1327
1355
  <Select
1328
1356
  options={options}
1329
1357
  open={open}
1330
- onDropdownVisibleChange={setOpen}
1358
+ onDropdownVisibleChange={(nextOpen) => {
1359
+ if (!isDisabled) {
1360
+ setOpen(nextOpen);
1361
+ }
1362
+ }}
1331
1363
  allowClear={false}
1332
1364
  style={{
1333
1365
  width: '100%',
@@ -1337,13 +1369,18 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1337
1369
  value={selectedType}
1338
1370
  onChange={handleSelect}
1339
1371
  dropdownRender={dropdownRender}
1372
+ disabled={isDisabled}
1340
1373
  />
1341
1374
  {['past', 'next'].includes(selectedType) && [
1342
1375
  <InputNumber
1343
1376
  key="number"
1344
1377
  style={{ flex: 1 }}
1345
1378
  value={(parsedValue as any)?.number}
1379
+ disabled={isDisabled}
1346
1380
  onChange={(nextNumber) => {
1381
+ if (isDisabled) {
1382
+ return;
1383
+ }
1347
1384
  inputProps?.onChange?.({
1348
1385
  ...(parsedValue as any),
1349
1386
  type: selectedType,
@@ -1356,7 +1393,11 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1356
1393
  key="unit"
1357
1394
  value={(parsedValue as any)?.unit}
1358
1395
  style={{ minWidth: 130, maxWidth: 140 }}
1396
+ disabled={isDisabled}
1359
1397
  onChange={(nextUnit) => {
1398
+ if (isDisabled) {
1399
+ return;
1400
+ }
1360
1401
  inputProps?.onChange?.({
1361
1402
  ...(parsedValue as any),
1362
1403
  type: selectedType,
@@ -1379,6 +1420,7 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1379
1420
  isRange={isRange}
1380
1421
  value={isRange ? exactRangeValue : exactSingleValue}
1381
1422
  onChange={isRange ? handleExactRangeChange : handleExactSingleChange}
1423
+ disabled={isDisabled}
1382
1424
  style={{ flex: 1 }}
1383
1425
  />
1384
1426
  )}
@@ -1398,7 +1440,12 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1398
1440
 
1399
1441
  const RunJSComponent = React.useMemo(() => {
1400
1442
  const C: React.FC<any> = (inputProps) => (
1401
- <RunJSValueEditor t={flowCtx.t} value={inputProps?.value} onChange={inputProps?.onChange} />
1443
+ <RunJSValueEditor
1444
+ t={flowCtx.t}
1445
+ value={inputProps?.value}
1446
+ onChange={inputProps?.onChange}
1447
+ disabled={inputProps?.disabled}
1448
+ />
1402
1449
  );
1403
1450
  return C;
1404
1451
  }, [flowCtx]);
@@ -1421,11 +1468,13 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1421
1468
  render: ConstantEditor,
1422
1469
  },
1423
1470
  { title: tExpr('Null'), name: 'null', type: 'object', paths: ['null'], render: NullComponent },
1424
- { title: tExpr('RunJS'), name: 'runjs', type: 'object', paths: ['runjs'], render: RunJSComponent },
1471
+ ...(allowRunJS
1472
+ ? [{ title: tExpr('RunJS'), name: 'runjs', type: 'object', paths: ['runjs'], render: RunJSComponent }]
1473
+ : []),
1425
1474
  ...limitedBase,
1426
1475
  ];
1427
1476
  };
1428
- }, [flowCtx, ConstantEditor, NullComponent, RunJSComponent, maxAssociationFieldDepth]);
1477
+ }, [flowCtx, ConstantEditor, NullComponent, RunJSComponent, allowRunJS, maxAssociationFieldDepth]);
1429
1478
 
1430
1479
  const displayValue = React.useMemo(() => {
1431
1480
  if (!useDateVariableConstant) {
@@ -1442,6 +1491,10 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1442
1491
 
1443
1492
  const handleVariableInputChange = React.useCallback(
1444
1493
  (nextValue: any) => {
1494
+ if (disabled) {
1495
+ return;
1496
+ }
1497
+
1445
1498
  if (!useDateVariableConstant) {
1446
1499
  onChange(nextValue);
1447
1500
  return;
@@ -1449,7 +1502,7 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1449
1502
 
1450
1503
  onChange(normalizeDateVariableOutput(nextValue, dateVariableComponentProps));
1451
1504
  },
1452
- [dateVariableComponentProps, onChange, useDateVariableConstant],
1505
+ [dateVariableComponentProps, disabled, onChange, useDateVariableConstant],
1453
1506
  );
1454
1507
 
1455
1508
  if (!fieldPath) {
@@ -1464,26 +1517,40 @@ export const FieldAssignValueInput: React.FC<Props> = ({
1464
1517
  metaTree={metaTree}
1465
1518
  style={{ width: '100%' }}
1466
1519
  clearValue={''}
1520
+ disabled={disabled}
1467
1521
  converters={{
1522
+ ...variableConverters,
1468
1523
  renderInputComponent: (meta) => {
1524
+ const external = variableConverters?.renderInputComponent?.(meta ?? null);
1525
+ if (external) {
1526
+ return external;
1527
+ }
1469
1528
  const firstPath = meta?.paths?.[0];
1470
1529
  if (firstPath === 'constant') return ConstantEditor;
1471
1530
  if (firstPath === 'null') return NullComponent;
1472
- if (firstPath === 'runjs') return RunJSComponent;
1531
+ if (allowRunJS && firstPath === 'runjs') return RunJSComponent;
1473
1532
  return null;
1474
1533
  },
1475
1534
  resolveValueFromPath: (item) => {
1535
+ const external = variableConverters?.resolveValueFromPath?.(item);
1536
+ if (external !== undefined) {
1537
+ return external;
1538
+ }
1476
1539
  const firstPath = item?.paths?.[0];
1477
1540
  if (firstPath === 'constant') {
1478
1541
  return useDateVariableConstant ? { type: 'today' } : '';
1479
1542
  }
1480
1543
  if (firstPath === 'null') return null;
1481
- if (firstPath === 'runjs') return { code: '', version: 'v2' };
1544
+ if (allowRunJS && firstPath === 'runjs') return { code: '', version: 'v2' };
1482
1545
  return undefined;
1483
1546
  },
1484
1547
  resolvePathFromValue: (currentValue) => {
1548
+ const external = variableConverters?.resolvePathFromValue?.(currentValue);
1549
+ if (external !== undefined) {
1550
+ return external;
1551
+ }
1485
1552
  if (currentValue === null) return ['null'];
1486
- if (isRunJSValue(currentValue)) return ['runjs'];
1553
+ if (allowRunJS && isRunJSValue(currentValue)) return ['runjs'];
1487
1554
  if (useDateVariableConstant && isCtxDateExpression(currentValue)) {
1488
1555
  return ['constant'];
1489
1556
  }
@@ -11,11 +11,14 @@ import { type FlowEngine, useFlowContext, useFlowEngine } from '@nocobase/flow-e
11
11
  import { Button, Result } from 'antd';
12
12
  import React, { useEffect, useMemo, useRef, useState } from 'react';
13
13
  import { useTranslation } from 'react-i18next';
14
- import { useLocation, useParams } from 'react-router-dom';
14
+ import { useLocation, useNavigate, useParams } from 'react-router-dom';
15
15
  import { getModernClientPrefix, stripModernClientPrefix } from '../../authRedirect';
16
16
  import { useApp } from '../../hooks/useApp';
17
- import { NocoBaseDesktopRouteType } from '../../flow-compat';
18
- import { resolveAdminRouteRuntimeTarget } from '../admin-shell/admin-layout/resolveAdminRouteRuntimeTarget';
17
+ import { NocoBaseDesktopRouteType, type NocoBaseDesktopRoute } from '../../flow-compat';
18
+ import {
19
+ resolveAdminRouteRuntimeTarget,
20
+ toRouterNavigationPath,
21
+ } from '../admin-shell/admin-layout/resolveAdminRouteRuntimeTarget';
19
22
  import { getAdminLayoutModel, type AdminLayoutModel } from '../admin-shell/admin-layout/AdminLayoutModel';
20
23
  import { getLayoutModel, type BaseLayoutModel } from '../admin-shell/BaseLayoutModel';
21
24
  import { useLayoutRoutePage } from '../admin-shell/useLayoutRoutePage';
@@ -24,6 +27,7 @@ import { useKeepAlive } from '../../components/KeepAlive';
24
27
  import { registerDeviceTypeContext } from '../internal/registerDeviceTypeContext';
25
28
 
26
29
  type FlowRouteGuardState = {
30
+ pageUid?: string;
27
31
  pending: boolean;
28
32
  allowBridge: boolean;
29
33
  notFound: boolean;
@@ -32,6 +36,21 @@ type FlowRouteGuardState = {
32
36
 
33
37
  export type LegacyPageBehavior = 'redirect' | 'notFound' | 'bridge';
34
38
 
39
+ type FlowRouteLayoutContext = {
40
+ authCheck?: boolean;
41
+ routePath?: string;
42
+ routeName?: string;
43
+ uid?: string;
44
+ };
45
+
46
+ type FlowRouteRepositoryLike = {
47
+ refreshAccessible?: () => Promise<unknown>;
48
+ isAccessibleLoaded?: () => boolean;
49
+ ensureAccessibleLoaded?: () => Promise<unknown>;
50
+ getRouteBySchemaUid?: (schemaUid: string) => NocoBaseDesktopRoute | undefined;
51
+ listAccessible?: () => NocoBaseDesktopRoute[];
52
+ };
53
+
35
54
  export type FlowRouteProps = {
36
55
  pageUid?: string;
37
56
  active?: boolean;
@@ -42,7 +61,7 @@ export type FlowRouteProps = {
42
61
  const getDefaultAdminLayoutModel = (flowEngine: FlowEngine) =>
43
62
  getAdminLayoutModel<AdminLayoutModel>(flowEngine, { required: true });
44
63
 
45
- const getDefaultLayoutModel = (flowEngine: FlowEngine, contextLayout?: any) => {
64
+ const getDefaultLayoutModel = (flowEngine: FlowEngine, contextLayout?: FlowRouteLayoutContext) => {
46
65
  const layout = contextLayout || flowEngine.context.layout;
47
66
 
48
67
  if (layout?.uid) {
@@ -52,7 +71,10 @@ const getDefaultLayoutModel = (flowEngine: FlowEngine, contextLayout?: any) => {
52
71
  return getDefaultAdminLayoutModel(flowEngine);
53
72
  };
54
73
 
55
- const getDefaultLegacyPageBehavior = (flowEngine: FlowEngine, contextLayout?: any): LegacyPageBehavior => {
74
+ const getDefaultLegacyPageBehavior = (
75
+ flowEngine: FlowEngine,
76
+ contextLayout?: FlowRouteLayoutContext,
77
+ ): LegacyPageBehavior => {
56
78
  const layout = contextLayout || flowEngine.context.layout;
57
79
 
58
80
  if (layout?.routeName && layout.routeName !== 'admin') {
@@ -62,6 +84,46 @@ const getDefaultLegacyPageBehavior = (flowEngine: FlowEngine, contextLayout?: an
62
84
  return 'redirect';
63
85
  };
64
86
 
87
+ const shouldRequireAccessibleRoute = (contextLayout?: FlowRouteLayoutContext) => contextLayout?.authCheck !== false;
88
+
89
+ const findAccessibleRouteByIdentity = (
90
+ routes: NocoBaseDesktopRoute[] | undefined,
91
+ pageUid: string,
92
+ ): NocoBaseDesktopRoute | undefined => {
93
+ if (!Array.isArray(routes)) {
94
+ return undefined;
95
+ }
96
+
97
+ for (const route of routes) {
98
+ if (
99
+ (route.type !== NocoBaseDesktopRouteType.group && route.schemaUid === pageUid) ||
100
+ (route.type === NocoBaseDesktopRouteType.group && route.id != null && String(route.id) === pageUid)
101
+ ) {
102
+ return route;
103
+ }
104
+
105
+ const matched = findAccessibleRouteByIdentity(route.children, pageUid);
106
+ if (matched) {
107
+ return matched;
108
+ }
109
+ }
110
+
111
+ return undefined;
112
+ };
113
+
114
+ const getAccessibleRouteByPageUid = (
115
+ routeRepository: FlowRouteRepositoryLike | undefined,
116
+ pageUid: string,
117
+ ): NocoBaseDesktopRoute | undefined => {
118
+ const routeBySchemaUid = routeRepository?.getRouteBySchemaUid?.(pageUid);
119
+
120
+ if (routeBySchemaUid && routeBySchemaUid.type !== NocoBaseDesktopRouteType.group) {
121
+ return routeBySchemaUid;
122
+ }
123
+
124
+ return findAccessibleRouteByIdentity(routeRepository?.listAccessible?.(), pageUid);
125
+ };
126
+
65
127
  const hasFlowModel = async (flowEngine: FlowEngine, pageUid: string) => {
66
128
  if (flowEngine.getModel(pageUid)) {
67
129
  return true;
@@ -177,21 +239,52 @@ const LegacyPageUnsupported = ({
177
239
  * @throws {Error} 当缺少 `route.params.name` 时抛出异常
178
240
  */
179
241
  const FlowRoute = (props: FlowRouteProps = {}) => {
242
+ const {
243
+ active,
244
+ getLayoutModel: getLayoutModelProp,
245
+ legacyPageBehavior: legacyPageBehaviorProp,
246
+ pageUid: pageUidProp,
247
+ } = props;
180
248
  const flowEngine = useFlowEngine();
181
- const flowContext = useFlowContext<any>();
249
+ const flowContext = useFlowContext<{ layout?: FlowRouteLayoutContext }>();
182
250
  const contextLayout = flowContext?.layout;
251
+ const propsLayoutModel = useMemo(() => getLayoutModelProp?.(flowEngine), [flowEngine, getLayoutModelProp]);
252
+ const rawRouteLayout = contextLayout || propsLayoutModel?.layout;
253
+ const routeLayoutUid = rawRouteLayout?.uid;
254
+ const routeLayoutRouteName = rawRouteLayout?.routeName;
255
+ const routeLayoutRoutePath = rawRouteLayout?.routePath;
256
+ const routeLayoutAuthCheck = rawRouteLayout?.authCheck;
257
+ const routeLayout = useMemo(() => {
258
+ if (
259
+ !routeLayoutUid &&
260
+ !routeLayoutRouteName &&
261
+ !routeLayoutRoutePath &&
262
+ typeof routeLayoutAuthCheck === 'undefined'
263
+ ) {
264
+ return undefined;
265
+ }
266
+ return {
267
+ authCheck: routeLayoutAuthCheck,
268
+ routeName: routeLayoutRouteName,
269
+ routePath: routeLayoutRoutePath,
270
+ uid: routeLayoutUid,
271
+ };
272
+ }, [routeLayoutAuthCheck, routeLayoutRouteName, routeLayoutRoutePath, routeLayoutUid]);
183
273
  const getLayoutModel = useMemo(
184
- () => props.getLayoutModel || ((engine: FlowEngine) => getDefaultLayoutModel(engine, contextLayout)),
185
- [contextLayout, props.getLayoutModel],
274
+ () => getLayoutModelProp || ((engine: FlowEngine) => getDefaultLayoutModel(engine, routeLayout)),
275
+ [getLayoutModelProp, routeLayout],
186
276
  );
187
- const legacyPageBehavior = props.legacyPageBehavior || getDefaultLegacyPageBehavior(flowEngine, contextLayout);
277
+ const legacyPageBehavior = legacyPageBehaviorProp || getDefaultLegacyPageBehavior(flowEngine, routeLayout);
188
278
  const app = useApp();
189
- const routeRepository = flowEngine.context.routeRepository;
190
279
  const location = useLocation();
280
+ const navigate = useNavigate();
281
+ const routeRepository = flowEngine.context.routeRepository as FlowRouteRepositoryLike | undefined;
191
282
  const params = useParams();
192
- const pageUid = props.pageUid || params?.name;
283
+ const pageUid = pageUidProp || params?.name;
284
+ const hasNestedRoutePath = typeof params?.tabUid !== 'undefined' || typeof params?.['*'] !== 'undefined';
193
285
  const skipRouteRepositoryCheck = !routeRepository;
194
286
  const [guardState, setGuardState] = useState<FlowRouteGuardState>({
287
+ pageUid: undefined,
195
288
  pending: true,
196
289
  allowBridge: false,
197
290
  notFound: false,
@@ -203,19 +296,24 @@ const FlowRoute = (props: FlowRouteProps = {}) => {
203
296
  throw new Error('[NocoBase] FlowRoute requires pageUid or route.params.name.');
204
297
  }
205
298
 
299
+ useEffect(() => {
300
+ replaceTriggeredRef.current = false;
301
+ }, [location.hash, location.pathname, location.search, pageUid]);
302
+
206
303
  useEffect(() => {
207
304
  let active = true;
208
305
  const requestId = ++requestIdRef.current;
306
+ const requiresAccessibleRoute = shouldRequireAccessibleRoute(routeLayout);
209
307
 
210
308
  const run = async () => {
211
- setGuardState({ pending: true, allowBridge: false, notFound: false });
309
+ setGuardState({ pageUid, pending: true, allowBridge: false, notFound: false });
212
310
 
213
- if (!skipRouteRepositoryCheck && !routeRepository?.isAccessibleLoaded?.()) {
311
+ if (requiresAccessibleRoute && !skipRouteRepositoryCheck && !routeRepository?.isAccessibleLoaded?.()) {
214
312
  try {
215
313
  await routeRepository?.ensureAccessibleLoaded?.();
216
314
  } catch (_error) {
217
315
  if (active && requestId === requestIdRef.current) {
218
- setGuardState({ pending: false, allowBridge: true, notFound: false });
316
+ setGuardState({ pageUid, pending: false, allowBridge: true, notFound: false });
219
317
  }
220
318
  return;
221
319
  }
@@ -225,25 +323,67 @@ const FlowRoute = (props: FlowRouteProps = {}) => {
225
323
  return;
226
324
  }
227
325
 
228
- const route = skipRouteRepositoryCheck ? undefined : routeRepository?.getRouteBySchemaUid?.(pageUid);
229
- const shouldCheckFlowModel =
230
- legacyPageBehavior === 'notFound' || (!skipRouteRepositoryCheck && legacyPageBehavior === 'redirect');
231
- if (!route && shouldCheckFlowModel) {
326
+ const route =
327
+ skipRouteRepositoryCheck || !requiresAccessibleRoute
328
+ ? undefined
329
+ : getAccessibleRouteByPageUid(routeRepository, pageUid);
330
+ if (!route && !skipRouteRepositoryCheck && requiresAccessibleRoute) {
331
+ setGuardState({ pageUid, pending: false, allowBridge: false, notFound: true });
332
+ return;
333
+ }
334
+
335
+ if (!route && legacyPageBehavior === 'notFound') {
232
336
  const flowModelExists = await hasFlowModel(flowEngine, pageUid);
233
337
  if (active && requestId === requestIdRef.current) {
234
- setGuardState({ pending: false, allowBridge: flowModelExists, notFound: !flowModelExists });
338
+ setGuardState({ pageUid, pending: false, allowBridge: flowModelExists, notFound: !flowModelExists });
235
339
  }
236
340
  return;
237
341
  }
238
342
 
343
+ if (route?.type === NocoBaseDesktopRouteType.group) {
344
+ if (hasNestedRoutePath) {
345
+ setGuardState({ pageUid, pending: false, allowBridge: false, notFound: true });
346
+ return;
347
+ }
348
+
349
+ const target = resolveAdminRouteRuntimeTarget({
350
+ app,
351
+ route,
352
+ layout: routeLayout,
353
+ });
354
+
355
+ if (target.reason === 'emptyGroup') {
356
+ setGuardState({ pageUid, pending: false, allowBridge: false, notFound: false });
357
+ return;
358
+ }
359
+
360
+ if (target.runtimePath) {
361
+ if (replaceTriggeredRef.current) {
362
+ setGuardState({ pageUid, pending: false, allowBridge: false, notFound: false });
363
+ return;
364
+ }
365
+
366
+ replaceTriggeredRef.current = true;
367
+ if (target.navigationMode === 'document') {
368
+ window.location.replace(target.runtimePath);
369
+ return;
370
+ }
371
+ navigate(toRouterNavigationPath(target.runtimePath, app.router?.getBasename?.()), { replace: true });
372
+ return;
373
+ }
374
+
375
+ setGuardState({ pageUid, pending: false, allowBridge: false, notFound: true });
376
+ return;
377
+ }
378
+
239
379
  if (route?.type === NocoBaseDesktopRouteType.page) {
240
380
  if (legacyPageBehavior === 'notFound') {
241
- setGuardState({ pending: false, allowBridge: false, notFound: true });
381
+ setGuardState({ pageUid, pending: false, allowBridge: false, notFound: true });
242
382
  return;
243
383
  }
244
384
 
245
385
  if (legacyPageBehavior === 'bridge') {
246
- setGuardState({ pending: false, allowBridge: true, notFound: false });
386
+ setGuardState({ pageUid, pending: false, allowBridge: true, notFound: false });
247
387
  return;
248
388
  }
249
389
 
@@ -266,26 +406,42 @@ const FlowRoute = (props: FlowRouteProps = {}) => {
266
406
 
267
407
  if (target.reason === 'unsupportedV2Runtime') {
268
408
  if (active && requestId === requestIdRef.current) {
269
- setGuardState({ pending: false, allowBridge: false, notFound: false, legacyPageUnsupported: true });
409
+ setGuardState({
410
+ pageUid,
411
+ pending: false,
412
+ allowBridge: false,
413
+ notFound: false,
414
+ legacyPageUnsupported: true,
415
+ });
270
416
  }
271
417
  return;
272
418
  }
273
419
  }
274
420
 
275
421
  if (active && requestId === requestIdRef.current) {
276
- setGuardState({ pending: false, allowBridge: true, notFound: false });
422
+ setGuardState({ pageUid, pending: false, allowBridge: true, notFound: false });
277
423
  }
278
424
  };
279
425
 
280
- void run();
426
+ run();
281
427
 
282
428
  return () => {
283
429
  active = false;
284
430
  };
285
- }, [app, flowEngine, legacyPageBehavior, pageUid, routeRepository, skipRouteRepositoryCheck]);
431
+ }, [
432
+ app,
433
+ flowEngine,
434
+ hasNestedRoutePath,
435
+ legacyPageBehavior,
436
+ navigate,
437
+ pageUid,
438
+ routeLayout,
439
+ routeRepository,
440
+ skipRouteRepositoryCheck,
441
+ ]);
286
442
 
287
443
  const content = useMemo(() => {
288
- if (guardState.pending) {
444
+ if (guardState.pageUid !== pageUid || guardState.pending) {
289
445
  return null;
290
446
  }
291
447
 
@@ -301,17 +457,18 @@ const FlowRoute = (props: FlowRouteProps = {}) => {
301
457
  return null;
302
458
  }
303
459
 
304
- return <BridgeFlowRoute pageUid={pageUid} active={props.active} getLayoutModel={getLayoutModel} />;
460
+ return <BridgeFlowRoute pageUid={pageUid} active={active} getLayoutModel={getLayoutModel} />;
305
461
  }, [
462
+ active,
306
463
  app,
307
464
  getLayoutModel,
308
465
  guardState.allowBridge,
309
466
  guardState.legacyPageUnsupported,
310
467
  guardState.notFound,
468
+ guardState.pageUid,
311
469
  guardState.pending,
312
470
  location,
313
471
  pageUid,
314
- props.active,
315
472
  ]);
316
473
 
317
474
  return content;
@@ -15,6 +15,7 @@ export interface RunJSValueEditorProps {
15
15
  t?: (key: string) => string;
16
16
  value?: unknown;
17
17
  onChange?: (value: RunJSValue) => void;
18
+ disabled?: boolean;
18
19
  height?: string;
19
20
  scene?: string;
20
21
  containerStyle?: React.CSSProperties;
@@ -25,6 +26,7 @@ export const RunJSValueEditor: React.FC<RunJSValueEditorProps> = (props) => {
25
26
  t,
26
27
  value,
27
28
  onChange,
29
+ disabled,
28
30
  height = '200px',
29
31
  scene = 'formValue',
30
32
  containerStyle = { flex: 1, minWidth: 0 },
@@ -38,9 +40,15 @@ export const RunJSValueEditor: React.FC<RunJSValueEditorProps> = (props) => {
38
40
  <div style={containerStyle}>
39
41
  <CodeEditor
40
42
  value={current.code}
41
- onChange={(code) => onChange?.({ ...current, code })}
43
+ onChange={(code) => {
44
+ if (disabled) {
45
+ return;
46
+ }
47
+ onChange?.({ ...current, code });
48
+ }}
42
49
  version={current.version}
43
50
  height={height}
51
+ readonly={disabled}
44
52
  enableLinter
45
53
  placeholder={placeholderText}
46
54
  scene={scene}