@nocobase/client-v2 2.1.0-alpha.32 → 2.1.0-alpha.34
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.
- package/es/flow/actions/index.d.ts +1 -1
- package/es/flow/actions/linkageRules.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +4 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutModel.d.ts +7 -0
- package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +5 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +3 -0
- package/es/index.mjs +78 -73
- package/lib/index.js +59 -54
- package/package.json +5 -5
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +67 -46
- package/src/__tests__/settings-center.test.tsx +30 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +4 -5
- package/src/flow/actions/__tests__/actionLinkageRules.race.repro.test.ts +199 -0
- package/src/flow/actions/__tests__/linkageRules.formValueDrivenRefresh.test.ts +6 -1
- package/src/flow/actions/__tests__/linkageRules.menu.test.ts +90 -0
- package/src/flow/actions/index.ts +2 -0
- package/src/flow/actions/linkageRules.tsx +77 -23
- package/src/flow/actions/linkageRulesFormValueRefresh.ts +2 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +8 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +70 -12
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +26 -87
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +11 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +5 -1
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +292 -31
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +50 -12
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +77 -56
- package/src/flow/components/AdminLayout.tsx +2 -2
- package/src/flow/components/FlowRoute.tsx +17 -4
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +4 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +7 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +4 -1
- package/src/flow/system-settings/useSystemSettings.tsx +36 -1
|
@@ -479,6 +479,41 @@ export const linkageSetActionProps = defineAction({
|
|
|
479
479
|
},
|
|
480
480
|
});
|
|
481
481
|
|
|
482
|
+
export const linkageSetMenuItemProps = defineAction({
|
|
483
|
+
name: 'linkageSetMenuItemProps',
|
|
484
|
+
title: tExpr('Set menu item state'),
|
|
485
|
+
scene: ActionScene.MENU_LINKAGE_RULES,
|
|
486
|
+
sort: 100,
|
|
487
|
+
uiSchema: {
|
|
488
|
+
value: {
|
|
489
|
+
type: 'string',
|
|
490
|
+
'x-component': (props) => {
|
|
491
|
+
const { value, onChange } = props;
|
|
492
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
493
|
+
const ctx = useFlowContext();
|
|
494
|
+
const t = ctx.model.translate.bind(ctx.model);
|
|
495
|
+
|
|
496
|
+
return (
|
|
497
|
+
<Select
|
|
498
|
+
value={value}
|
|
499
|
+
onChange={onChange}
|
|
500
|
+
placeholder={t('Please select state')}
|
|
501
|
+
style={{ width: '100%' }}
|
|
502
|
+
options={[
|
|
503
|
+
{ label: t('Visible'), value: 'visible' },
|
|
504
|
+
{ label: t('Hidden'), value: 'hidden' },
|
|
505
|
+
]}
|
|
506
|
+
allowClear
|
|
507
|
+
/>
|
|
508
|
+
);
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
handler(ctx, { value, setProps }) {
|
|
513
|
+
setProps(ctx.model, { hiddenModel: value === 'hidden' });
|
|
514
|
+
},
|
|
515
|
+
});
|
|
516
|
+
|
|
482
517
|
export const linkageSetFieldProps = defineAction({
|
|
483
518
|
name: 'linkageSetFieldProps',
|
|
484
519
|
title: tExpr('Set field state'),
|
|
@@ -1288,6 +1323,7 @@ export const linkageRunjs = defineAction({
|
|
|
1288
1323
|
ActionScene.BLOCK_LINKAGE_RULES,
|
|
1289
1324
|
ActionScene.FIELD_LINKAGE_RULES,
|
|
1290
1325
|
ActionScene.ACTION_LINKAGE_RULES,
|
|
1326
|
+
ActionScene.MENU_LINKAGE_RULES,
|
|
1291
1327
|
ActionScene.DETAILS_FIELD_LINKAGE_RULES,
|
|
1292
1328
|
ActionScene.SUB_FORM_FIELD_LINKAGE_RULES,
|
|
1293
1329
|
],
|
|
@@ -1755,6 +1791,8 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1755
1791
|
|
|
1756
1792
|
const linkageRules: LinkageRule[] = params.value as LinkageRule[];
|
|
1757
1793
|
const allModels: FlowModel[] = ctx.model.__allModels || (ctx.model.__allModels = []);
|
|
1794
|
+
const modelsToApply = new Set<FlowModel>(allModels);
|
|
1795
|
+
const patchPropsByModel = new Map<FlowModel, any>();
|
|
1758
1796
|
const directValuePatches: Array<{ path: Array<string | number>; value: any; whenEmpty?: boolean }> = [];
|
|
1759
1797
|
const rootCollection = getCollectionFromModel((ctx.model as any)?.context?.blockModel ?? ctx.model);
|
|
1760
1798
|
const isSafeToWriteAssociationSubpath = (namePath: any): boolean => {
|
|
@@ -1906,11 +1944,6 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1906
1944
|
return null;
|
|
1907
1945
|
};
|
|
1908
1946
|
|
|
1909
|
-
allModels.forEach((model: any) => {
|
|
1910
|
-
// 重置临时属性
|
|
1911
|
-
model.__props = {};
|
|
1912
|
-
});
|
|
1913
|
-
|
|
1914
1947
|
// 1. 运行所有的联动规则
|
|
1915
1948
|
for (const rule of linkageRules.filter((r) => r.enable)) {
|
|
1916
1949
|
const { condition: conditions, actions } = rule;
|
|
@@ -1919,10 +1952,7 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1919
1952
|
if (!matched) continue;
|
|
1920
1953
|
|
|
1921
1954
|
for (const action of actions) {
|
|
1922
|
-
const setProps = (
|
|
1923
|
-
model: FlowModel & { __originalProps?: any; __props?: any; __shouldReset?: boolean },
|
|
1924
|
-
props: any,
|
|
1925
|
-
) => {
|
|
1955
|
+
const setProps = (model: FlowModel & { __originalProps?: any; __shouldReset?: boolean }, props: any) => {
|
|
1926
1956
|
// 存储原始值,用于恢复
|
|
1927
1957
|
if (!model.__originalProps) {
|
|
1928
1958
|
model.__originalProps = {
|
|
@@ -1935,19 +1965,16 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1935
1965
|
};
|
|
1936
1966
|
}
|
|
1937
1967
|
|
|
1938
|
-
if (!model.__props) {
|
|
1939
|
-
model.__props = {};
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1942
1968
|
// 临时存起来,遍历完所有规则后,再统一处理
|
|
1943
|
-
model
|
|
1944
|
-
...model
|
|
1969
|
+
patchPropsByModel.set(model, {
|
|
1970
|
+
...(patchPropsByModel.get(model) || {}),
|
|
1945
1971
|
...props,
|
|
1946
|
-
};
|
|
1972
|
+
});
|
|
1947
1973
|
|
|
1948
1974
|
if (allModels.indexOf(model) === -1) {
|
|
1949
1975
|
allModels.push(model);
|
|
1950
1976
|
}
|
|
1977
|
+
modelsToApply.add(model);
|
|
1951
1978
|
};
|
|
1952
1979
|
|
|
1953
1980
|
// TODO: 需要改成 runAction 的写法。但 runAction 是异步的,用在这里会不符合预期。后面需要解决这个问题
|
|
@@ -1956,15 +1983,12 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1956
1983
|
}
|
|
1957
1984
|
|
|
1958
1985
|
// 2. 合并去重(按 uid)后再实际更改相关 model 的状态,避免重复项把“已设置的临时属性”覆盖掉
|
|
1959
|
-
const mergedByUid = new Map<
|
|
1960
|
-
string,
|
|
1961
|
-
FlowModel & { __originalProps?: any; __props?: any; isFork?: boolean; forkId?: number }
|
|
1962
|
-
>();
|
|
1986
|
+
const mergedByUid = new Map<string, FlowModel & { __originalProps?: any; isFork?: boolean; forkId?: number }>();
|
|
1963
1987
|
const mergedPropsByUid = new Map<string, any>();
|
|
1964
1988
|
|
|
1965
|
-
|
|
1989
|
+
modelsToApply.forEach((m: any) => {
|
|
1966
1990
|
const uid = m?.uid || String(m);
|
|
1967
|
-
const curProps = m
|
|
1991
|
+
const curProps = patchPropsByModel.get(m) || {};
|
|
1968
1992
|
if (!mergedByUid.has(uid)) {
|
|
1969
1993
|
mergedByUid.set(uid, m);
|
|
1970
1994
|
mergedPropsByUid.set(uid, { ...curProps });
|
|
@@ -1983,7 +2007,11 @@ const commonLinkageRulesHandler = async (ctx: FlowContext, params: any) => {
|
|
|
1983
2007
|
const newProps = { ...model.__originalProps, ...patchProps };
|
|
1984
2008
|
|
|
1985
2009
|
model.setProps(_.omit(newProps, ['hiddenModel', 'value', 'hiddenText']));
|
|
1986
|
-
model.
|
|
2010
|
+
if (typeof model.setHidden === 'function') {
|
|
2011
|
+
model.setHidden(!!newProps.hiddenModel);
|
|
2012
|
+
} else {
|
|
2013
|
+
model.hidden = !!newProps.hiddenModel;
|
|
2014
|
+
}
|
|
1987
2015
|
|
|
1988
2016
|
if (newProps.required === true) {
|
|
1989
2017
|
const rules = (model.props.rules || []).filter((rule) => !rule.required);
|
|
@@ -2149,6 +2177,32 @@ export const actionLinkageRules = defineAction({
|
|
|
2149
2177
|
},
|
|
2150
2178
|
});
|
|
2151
2179
|
|
|
2180
|
+
export const menuLinkageRules = defineAction({
|
|
2181
|
+
name: 'menuLinkageRules',
|
|
2182
|
+
title: tExpr('Menu linkage rules'),
|
|
2183
|
+
uiMode: 'embed',
|
|
2184
|
+
uiSchema(ctx) {
|
|
2185
|
+
return {
|
|
2186
|
+
value: {
|
|
2187
|
+
type: 'array',
|
|
2188
|
+
'x-component': LinkageRulesUI,
|
|
2189
|
+
'x-component-props': {
|
|
2190
|
+
supportedActions: getSupportedActions(ctx, ActionScene.MENU_LINKAGE_RULES),
|
|
2191
|
+
title: tExpr('Menu linkage rules'),
|
|
2192
|
+
},
|
|
2193
|
+
},
|
|
2194
|
+
};
|
|
2195
|
+
},
|
|
2196
|
+
defaultParams: {
|
|
2197
|
+
value: [],
|
|
2198
|
+
},
|
|
2199
|
+
useRawParams: true,
|
|
2200
|
+
handler: async (ctx, params) => {
|
|
2201
|
+
const resolved = await resolveLinkageRulesParamsPreservingRunJsScripts(ctx, params);
|
|
2202
|
+
return commonLinkageRulesHandler(ctx, resolved);
|
|
2203
|
+
},
|
|
2204
|
+
});
|
|
2205
|
+
|
|
2152
2206
|
export const fieldLinkageRules = defineAction({
|
|
2153
2207
|
name: 'fieldLinkageRules',
|
|
2154
2208
|
title: tExpr('Field linkage rules'),
|
|
@@ -15,11 +15,7 @@ import {
|
|
|
15
15
|
isRunJSValue,
|
|
16
16
|
} from '@nocobase/flow-engine';
|
|
17
17
|
import _ from 'lodash';
|
|
18
|
-
import {
|
|
19
|
-
namePathToPathKey,
|
|
20
|
-
parsePathString,
|
|
21
|
-
pathKeyToNamePath,
|
|
22
|
-
} from '../models/blocks/form/value-runtime/path';
|
|
18
|
+
import { namePathToPathKey, parsePathString, pathKeyToNamePath } from '../models/blocks/form/value-runtime/path';
|
|
23
19
|
import {
|
|
24
20
|
collectStaticDepsFromRunJSValue,
|
|
25
21
|
collectStaticDepsFromTemplateValue,
|
|
@@ -243,9 +239,7 @@ function collectLinkageRefreshDeps(ctx: FlowContext, params: any): LinkageRefres
|
|
|
243
239
|
|
|
244
240
|
if (depKey === 'ctx:item' || depKey.startsWith('ctx:item:')) {
|
|
245
241
|
const subPath = depKey === 'ctx:item' ? '' : depKey.slice('ctx:item:'.length);
|
|
246
|
-
const depPath = subPath
|
|
247
|
-
? (parsePathString(subPath).filter((seg) => typeof seg !== 'object') as NamePath)
|
|
248
|
-
: [];
|
|
242
|
+
const depPath = subPath ? (parsePathString(subPath).filter((seg) => typeof seg !== 'object') as NamePath) : [];
|
|
249
243
|
const resolved = resolveItemDependencyPath(ctx, depPath);
|
|
250
244
|
wildcard ||= resolved.wildcard;
|
|
251
245
|
valuePaths.push(...resolved.valuePaths);
|
|
@@ -116,6 +116,11 @@ const resetStyle = css`
|
|
|
116
116
|
.ant-pro-base-menu-vertical-collapsed .ant-pro-base-menu-vertical-menu-item {
|
|
117
117
|
height: auto;
|
|
118
118
|
}
|
|
119
|
+
|
|
120
|
+
.ant-menu-item:has([data-nb-hidden-menu-item='true']),
|
|
121
|
+
.ant-menu-submenu:has([data-nb-hidden-menu-item='true']) {
|
|
122
|
+
display: none !important;
|
|
123
|
+
}
|
|
119
124
|
`;
|
|
120
125
|
|
|
121
126
|
const contentStyle = {
|
|
@@ -373,6 +378,7 @@ export const AdminLayoutComponent = observer((props: any) => {
|
|
|
373
378
|
const { token } = antdTheme.useToken();
|
|
374
379
|
const customToken = token as CustomToken;
|
|
375
380
|
const isMobileLayout = !!adminLayoutModel?.isMobileLayout;
|
|
381
|
+
const menuRouteRefreshVersion = adminLayoutModel?.menuRouteRefreshVersion || 0;
|
|
376
382
|
const isMobileSider = isMobileLayout || isMobileViewport;
|
|
377
383
|
const [collapsed, setCollapsed] = useState(isMobileSider);
|
|
378
384
|
const [preferredFlowSettingsEnabled, setPreferredFlowSettingsEnabled] = useState(() => readFlowSettingsPreference());
|
|
@@ -505,7 +511,7 @@ export const AdminLayoutComponent = observer((props: any) => {
|
|
|
505
511
|
children: [],
|
|
506
512
|
};
|
|
507
513
|
setRoute(nextRoute);
|
|
508
|
-
}, [adminLayoutModel, allAccessRoutes, designable, isMobileSider, t]);
|
|
514
|
+
}, [adminLayoutModel, allAccessRoutes, designable, isMobileSider, menuRouteRefreshVersion, t]);
|
|
509
515
|
|
|
510
516
|
useEffect(() => {
|
|
511
517
|
const syncId = ++flowSettingsSyncRef.current;
|
|
@@ -627,6 +633,7 @@ export const AdminLayoutComponent = observer((props: any) => {
|
|
|
627
633
|
<MobileMenuControlContext.Provider value={{ closeMobileMenu }}>
|
|
628
634
|
<DndProvider collisionDetection={menuCollisionDetection} onDragEnd={handleMenuDragEnd}>
|
|
629
635
|
<ProLayout
|
|
636
|
+
key={`admin-layout-menu-${menuRouteRefreshVersion}`}
|
|
630
637
|
{...props}
|
|
631
638
|
contentStyle={contentStyle}
|
|
632
639
|
siderWidth={customToken.siderWidth || 200}
|
|
@@ -28,7 +28,6 @@ import {
|
|
|
28
28
|
reconcileAdminLayoutMenuItems,
|
|
29
29
|
shouldRenderIconInTitle,
|
|
30
30
|
} from './AdminLayoutMenuUtils';
|
|
31
|
-
import { findFirstPageRoute } from './AdminLayoutCompat';
|
|
32
31
|
import {
|
|
33
32
|
buildMenuBasicSchema,
|
|
34
33
|
buildLinkSettingSchema,
|
|
@@ -41,7 +40,14 @@ import {
|
|
|
41
40
|
matchesRoutePath,
|
|
42
41
|
toTreeSelectItems,
|
|
43
42
|
} from './AdminLayoutMenuFlowUtils';
|
|
44
|
-
import {
|
|
43
|
+
import { ADMIN_LAYOUT_MODEL_UID } from './constants';
|
|
44
|
+
import {
|
|
45
|
+
findFirstAccessiblePageRoute,
|
|
46
|
+
findFirstV2LandingRoute,
|
|
47
|
+
isV2AdminRuntime,
|
|
48
|
+
resolveAdminRouteRuntimeTarget,
|
|
49
|
+
toRouterNavigationPath,
|
|
50
|
+
} from './resolveAdminRouteRuntimeTarget';
|
|
45
51
|
|
|
46
52
|
export * from './AdminLayoutMenuUtils';
|
|
47
53
|
const insertPositionToMethod = {
|
|
@@ -122,6 +128,15 @@ export class AdminLayoutMenuItemModel extends FlowModel<AdminLayoutMenuItemStruc
|
|
|
122
128
|
return this.flowRegistry.getFlows().size;
|
|
123
129
|
}
|
|
124
130
|
|
|
131
|
+
hasPersistableMenuLinkageRules() {
|
|
132
|
+
const params = this.getStepParams('menuSettings', 'linkageRules') as { value?: any[] } | undefined;
|
|
133
|
+
return Array.isArray(params?.value) && params.value.length > 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
hasCurrentPersistedMenuState() {
|
|
137
|
+
return this.getCurrentPersistedInstanceFlowCount() > 0 || this.hasPersistableMenuLinkageRules();
|
|
138
|
+
}
|
|
139
|
+
|
|
125
140
|
buildRouteOptionsWithPersistedFlowFlag(hasPersistedMenuInstanceFlow: boolean) {
|
|
126
141
|
const route = this.getRoute();
|
|
127
142
|
const nextOptions = {
|
|
@@ -147,7 +162,7 @@ export class AdminLayoutMenuItemModel extends FlowModel<AdminLayoutMenuItemStruc
|
|
|
147
162
|
return;
|
|
148
163
|
}
|
|
149
164
|
|
|
150
|
-
const hasPersistedMenuInstanceFlow = this.
|
|
165
|
+
const hasPersistedMenuInstanceFlow = this.hasCurrentPersistedMenuState();
|
|
151
166
|
if (this.hasPersistedMenuInstanceFlowFlag(route) === hasPersistedMenuInstanceFlow) {
|
|
152
167
|
return;
|
|
153
168
|
}
|
|
@@ -188,6 +203,7 @@ export class AdminLayoutMenuItemModel extends FlowModel<AdminLayoutMenuItemStruc
|
|
|
188
203
|
if (this.isCreationSession()) {
|
|
189
204
|
return;
|
|
190
205
|
}
|
|
206
|
+
let shouldRerenderAfterHydrate = false;
|
|
191
207
|
|
|
192
208
|
const repository = this.flowEngine.modelRepository;
|
|
193
209
|
if (!repository?.findOne) {
|
|
@@ -201,6 +217,7 @@ export class AdminLayoutMenuItemModel extends FlowModel<AdminLayoutMenuItemStruc
|
|
|
201
217
|
|
|
202
218
|
if (data.stepParams && typeof data.stepParams === 'object') {
|
|
203
219
|
this.setStepParams(data.stepParams);
|
|
220
|
+
shouldRerenderAfterHydrate = this.hasPersistableMenuLinkageRules();
|
|
204
221
|
}
|
|
205
222
|
|
|
206
223
|
if (data.flowRegistry && typeof data.flowRegistry === 'object') {
|
|
@@ -219,9 +236,11 @@ export class AdminLayoutMenuItemModel extends FlowModel<AdminLayoutMenuItemStruc
|
|
|
219
236
|
return typeof flow.on === 'string' ? flow.on === 'beforeRender' : flow.on?.eventName === 'beforeRender';
|
|
220
237
|
});
|
|
221
238
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
239
|
+
shouldRerenderAfterHydrate = shouldRerenderAfterHydrate || hasBeforeRenderFlow;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (shouldRerenderAfterHydrate) {
|
|
243
|
+
void this.rerender();
|
|
225
244
|
}
|
|
226
245
|
})().finally(() => {
|
|
227
246
|
this.persistedStateHydrated = true;
|
|
@@ -242,6 +261,27 @@ export class AdminLayoutMenuItemModel extends FlowModel<AdminLayoutMenuItemStruc
|
|
|
242
261
|
return currentFlowCount > 0 || initialFlowCount > 0;
|
|
243
262
|
}
|
|
244
263
|
|
|
264
|
+
setHidden(value: boolean) {
|
|
265
|
+
const previous = this.hidden;
|
|
266
|
+
super.setHidden(value);
|
|
267
|
+
if (previous !== this.hidden) {
|
|
268
|
+
(this.flowEngine.getModel?.(ADMIN_LAYOUT_MODEL_UID) as any)?.refreshMenuRouteTree?.();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
protected renderHiddenInConfig(): React.ReactNode | undefined {
|
|
273
|
+
const { item, dom, options, renderType } = this.props;
|
|
274
|
+
if (!item || !renderType) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return (
|
|
279
|
+
<div style={{ opacity: 0.3 }}>
|
|
280
|
+
<AdminLayoutMenuItemRenderer item={item} dom={dom} options={options} renderType={renderType} />
|
|
281
|
+
</div>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
245
285
|
async createMenuRoute(
|
|
246
286
|
route: NocoBaseDesktopRoute,
|
|
247
287
|
options?: {
|
|
@@ -436,11 +476,11 @@ export class AdminLayoutMenuItemModel extends FlowModel<AdminLayoutMenuItemStruc
|
|
|
436
476
|
return true;
|
|
437
477
|
}
|
|
438
478
|
|
|
439
|
-
const
|
|
479
|
+
const hasCurrentPersistedMenuState = this.hasCurrentPersistedMenuState();
|
|
440
480
|
|
|
441
481
|
// 菜单基础设置继续直接保存到 route repository;
|
|
442
482
|
// 只有实例事件流需要回退到 FlowModel 默认持久化链路。
|
|
443
|
-
if (
|
|
483
|
+
if (hasCurrentPersistedMenuState) {
|
|
444
484
|
await super.saveStepParams();
|
|
445
485
|
} else if (this.hasPersistedMenuInstanceFlowFlag()) {
|
|
446
486
|
await this.destroyPersistedState();
|
|
@@ -523,6 +563,10 @@ export class AdminLayoutMenuItemModel extends FlowModel<AdminLayoutMenuItemStruc
|
|
|
523
563
|
return null;
|
|
524
564
|
}
|
|
525
565
|
|
|
566
|
+
if (!options.designable && this.hidden) {
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
|
|
526
570
|
const shouldShowIconInTitle = shouldRenderIconInTitle({ depth, isMobile: options.isMobile });
|
|
527
571
|
const { name, icon } = buildMenuTitleWithIcon(route, options.t, shouldShowIconInTitle);
|
|
528
572
|
|
|
@@ -546,6 +590,11 @@ export class AdminLayoutMenuItemModel extends FlowModel<AdminLayoutMenuItemStruc
|
|
|
546
590
|
app: this.context.app,
|
|
547
591
|
route,
|
|
548
592
|
});
|
|
593
|
+
|
|
594
|
+
if (runtimeTarget.reason === 'unsupportedV2Runtime') {
|
|
595
|
+
return null;
|
|
596
|
+
}
|
|
597
|
+
|
|
549
598
|
const path = route.schemaUid
|
|
550
599
|
? `/admin/${route.schemaUid}`
|
|
551
600
|
: getAdminLayoutMenuVirtualPath('link', `${this.uid}-invalid`);
|
|
@@ -579,13 +628,20 @@ export class AdminLayoutMenuItemModel extends FlowModel<AdminLayoutMenuItemStruc
|
|
|
579
628
|
)
|
|
580
629
|
.filter(Boolean) || [];
|
|
581
630
|
|
|
631
|
+
if (isV2AdminRuntime(this.context.app) && children.length === 0) {
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
|
|
582
635
|
if (options.designable && depth === 0) {
|
|
583
636
|
children.push(getAdminLayoutMenuInitializerButton('schema-initializer-Menu-side', this, route));
|
|
584
637
|
}
|
|
585
638
|
|
|
639
|
+
const landingRoute = isV2AdminRuntime(this.context.app)
|
|
640
|
+
? findFirstV2LandingRoute(itemChildren)
|
|
641
|
+
: findFirstAccessiblePageRoute(itemChildren);
|
|
586
642
|
const runtimeTarget = resolveAdminRouteRuntimeTarget({
|
|
587
643
|
app: this.context.app,
|
|
588
|
-
route,
|
|
644
|
+
route: isV2AdminRuntime(this.context.app) && landingRoute ? landingRoute : route,
|
|
589
645
|
});
|
|
590
646
|
|
|
591
647
|
const groupRoute: AdminLayoutMenuNode = {
|
|
@@ -593,9 +649,7 @@ export class AdminLayoutMenuItemModel extends FlowModel<AdminLayoutMenuItemStruc
|
|
|
593
649
|
icon,
|
|
594
650
|
path: `/admin/${route.id}`,
|
|
595
651
|
redirect:
|
|
596
|
-
children[0]?.key === 'x-designer-button'
|
|
597
|
-
? undefined
|
|
598
|
-
: `/admin/${findFirstPageRoute(itemChildren)?.schemaUid || route.id}`,
|
|
652
|
+
children[0]?.key === 'x-designer-button' ? undefined : `/admin/${landingRoute?.schemaUid || route.id}`,
|
|
599
653
|
hideInMenu: route.hideInMenu,
|
|
600
654
|
_runtimePath: runtimeTarget.runtimePath,
|
|
601
655
|
_navigationMode: runtimeTarget.navigationMode,
|
|
@@ -707,6 +761,10 @@ AdminLayoutMenuItemModel.registerFlow({
|
|
|
707
761
|
});
|
|
708
762
|
},
|
|
709
763
|
},
|
|
764
|
+
linkageRules: {
|
|
765
|
+
use: 'menuLinkageRules',
|
|
766
|
+
hideInSettings: async (ctx: FlowSettingsContext<AdminLayoutMenuItemModel>) => ctx.model.isCreationSession(),
|
|
767
|
+
},
|
|
710
768
|
moveTo: {
|
|
711
769
|
title: 'Move to',
|
|
712
770
|
defaultParams: async () => ({
|
|
@@ -15,9 +15,9 @@ import {
|
|
|
15
15
|
FlowModel,
|
|
16
16
|
FlowModelRenderer,
|
|
17
17
|
FlowSettingsButton,
|
|
18
|
+
observer,
|
|
18
19
|
} from '@nocobase/flow-engine';
|
|
19
|
-
import {
|
|
20
|
-
import type { HookAPI } from 'antd/es/modal/useModal';
|
|
20
|
+
import { Badge, Tooltip } from 'antd';
|
|
21
21
|
import qs from 'qs';
|
|
22
22
|
import React, { FC, useCallback, useContext, useEffect } from 'react';
|
|
23
23
|
import { Link, useLocation, type NavigateFunction } from 'react-router-dom';
|
|
@@ -284,33 +284,6 @@ const translateByModel = (model: FlowModel, value: any) => {
|
|
|
284
284
|
return typeof model.context.t === 'function' ? model.context.t(value) : value;
|
|
285
285
|
};
|
|
286
286
|
|
|
287
|
-
const translateMenuNode = (item: AdminLayoutMenuNode, value: any) => {
|
|
288
|
-
return item._model ? translateByModel(item._model, value) : value;
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* 经典页面需要整页跳回 v1,先给用户一个明确确认,避免误离开当前 v2 上下文。
|
|
293
|
-
*/
|
|
294
|
-
const confirmLegacyPageNavigation = async (options: { item: AdminLayoutMenuNode; confirm?: HookAPI['confirm'] }) => {
|
|
295
|
-
const { item, confirm } = options;
|
|
296
|
-
|
|
297
|
-
if (!item._isLegacy || typeof confirm !== 'function') {
|
|
298
|
-
return true;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const confirmed = await confirm({
|
|
302
|
-
title: translateMenuNode(item, 'Open classic page access'),
|
|
303
|
-
content: translateMenuNode(
|
|
304
|
-
item,
|
|
305
|
-
'This page requires the classic version to open properly. Do you want to go there now?',
|
|
306
|
-
),
|
|
307
|
-
okText: translateMenuNode(item, 'Yes'),
|
|
308
|
-
cancelText: translateMenuNode(item, 'Cancel'),
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
return !!confirmed;
|
|
312
|
-
};
|
|
313
|
-
|
|
314
287
|
const MENU_TYPE_ITEMS: Array<{ key: string; label: string; menuType: AdminLayoutMenuCreationType }> = [
|
|
315
288
|
{ key: 'group', label: 'Group', menuType: 'group' },
|
|
316
289
|
{ key: 'flow-page', label: 'Page', menuType: 'flowPage' },
|
|
@@ -520,7 +493,6 @@ export function resolveAdminLayoutMenuDragMoveOptionsFromEvent(
|
|
|
520
493
|
const GroupItem: FC<{ item: AdminLayoutMenuNode; options?: AdminLayoutMenuRenderOptions }> = (props) => {
|
|
521
494
|
const { item } = props;
|
|
522
495
|
const badgeCount = useEvaluatedExpression(item._route.options?.badge?.count, item._model?.context);
|
|
523
|
-
const { modal } = App.useApp();
|
|
524
496
|
const navigate = useNavigateNoUpdate();
|
|
525
497
|
const routerBasename = useRouterBasename();
|
|
526
498
|
const { closeMobileMenu } = useContext(MobileMenuControlContext);
|
|
@@ -556,23 +528,13 @@ const GroupItem: FC<{ item: AdminLayoutMenuNode; options?: AdminLayoutMenuRender
|
|
|
556
528
|
|
|
557
529
|
event.preventDefault();
|
|
558
530
|
event.stopPropagation();
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
runAfterMobileMenuClosed({
|
|
569
|
-
isMobile: !!props.options?.isMobile,
|
|
570
|
-
closeMobileMenu,
|
|
571
|
-
callback: () => {
|
|
572
|
-
window.location.assign(runtimePath);
|
|
573
|
-
},
|
|
574
|
-
});
|
|
575
|
-
})();
|
|
531
|
+
runAfterMobileMenuClosed({
|
|
532
|
+
isMobile: !!props.options?.isMobile,
|
|
533
|
+
closeMobileMenu,
|
|
534
|
+
callback: () => {
|
|
535
|
+
window.location.assign(runtimePath);
|
|
536
|
+
},
|
|
537
|
+
});
|
|
576
538
|
return;
|
|
577
539
|
}
|
|
578
540
|
|
|
@@ -589,16 +551,7 @@ const GroupItem: FC<{ item: AdminLayoutMenuNode; options?: AdminLayoutMenuRender
|
|
|
589
551
|
},
|
|
590
552
|
});
|
|
591
553
|
},
|
|
592
|
-
[
|
|
593
|
-
closeMobileMenu,
|
|
594
|
-
item,
|
|
595
|
-
item._navigationMode,
|
|
596
|
-
modal,
|
|
597
|
-
navigate,
|
|
598
|
-
props.options?.isMobile,
|
|
599
|
-
runtimePath,
|
|
600
|
-
spaRuntimePath,
|
|
601
|
-
],
|
|
554
|
+
[closeMobileMenu, item, item._navigationMode, navigate, props.options?.isMobile, runtimePath, spaRuntimePath],
|
|
602
555
|
);
|
|
603
556
|
|
|
604
557
|
const landingEntryAriaLabel = ariaLabel ? `${ariaLabel}-landing-entry` : 'group-landing-entry';
|
|
@@ -656,7 +609,6 @@ const MenuItem: FC<{ item: AdminLayoutMenuNode; options?: AdminLayoutMenuRenderO
|
|
|
656
609
|
const { item } = props;
|
|
657
610
|
const location = useLocation();
|
|
658
611
|
const badgeCount = useEvaluatedExpression(item._route.options?.badge?.count, item._model?.context);
|
|
659
|
-
const { modal } = App.useApp();
|
|
660
612
|
const navigate = useNavigateNoUpdate();
|
|
661
613
|
const basenameOfCurrentRouter = useRouterBasename();
|
|
662
614
|
const { closeMobileMenu } = useContext(MobileMenuControlContext);
|
|
@@ -717,23 +669,13 @@ const MenuItem: FC<{ item: AdminLayoutMenuNode; options?: AdminLayoutMenuRenderO
|
|
|
717
669
|
|
|
718
670
|
event.preventDefault();
|
|
719
671
|
event.stopPropagation();
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
runAfterMobileMenuClosed({
|
|
730
|
-
isMobile: !!props.options?.isMobile,
|
|
731
|
-
closeMobileMenu,
|
|
732
|
-
callback: () => {
|
|
733
|
-
window.location.assign(runtimePath);
|
|
734
|
-
},
|
|
735
|
-
});
|
|
736
|
-
})();
|
|
672
|
+
runAfterMobileMenuClosed({
|
|
673
|
+
isMobile: !!props.options?.isMobile,
|
|
674
|
+
closeMobileMenu,
|
|
675
|
+
callback: () => {
|
|
676
|
+
window.location.assign(runtimePath);
|
|
677
|
+
},
|
|
678
|
+
});
|
|
737
679
|
return;
|
|
738
680
|
}
|
|
739
681
|
|
|
@@ -757,16 +699,7 @@ const MenuItem: FC<{ item: AdminLayoutMenuNode; options?: AdminLayoutMenuRenderO
|
|
|
757
699
|
},
|
|
758
700
|
});
|
|
759
701
|
},
|
|
760
|
-
[
|
|
761
|
-
props.options?.isMobile,
|
|
762
|
-
closeMobileMenu,
|
|
763
|
-
isDocumentNavigation,
|
|
764
|
-
item,
|
|
765
|
-
modal,
|
|
766
|
-
navigate,
|
|
767
|
-
runtimePath,
|
|
768
|
-
spaRuntimePath,
|
|
769
|
-
],
|
|
702
|
+
[props.options?.isMobile, closeMobileMenu, isDocumentNavigation, item, navigate, runtimePath, spaRuntimePath],
|
|
770
703
|
);
|
|
771
704
|
|
|
772
705
|
if (item._route?.type === NocoBaseDesktopRouteType.link) {
|
|
@@ -889,13 +822,15 @@ export const shouldRenderIconInTitle = ({ depth, isMobile }: { depth: number; is
|
|
|
889
822
|
return depth > 1 || (isMobile && depth > 0);
|
|
890
823
|
};
|
|
891
824
|
|
|
825
|
+
const HiddenMenuItemPlaceholder = () => <span data-nb-hidden-menu-item="true" />;
|
|
826
|
+
|
|
892
827
|
export const AdminLayoutMenuModelRenderer: FC<{
|
|
893
828
|
model: FlowModel;
|
|
894
829
|
item: AdminLayoutMenuNode;
|
|
895
830
|
dom: React.ReactNode;
|
|
896
831
|
renderType: AdminLayoutMenuRenderType;
|
|
897
832
|
options?: AdminLayoutMenuRenderOptions;
|
|
898
|
-
}> = ({ model, item, dom, renderType, options }) => {
|
|
833
|
+
}> = observer(({ model, item, dom, renderType, options }) => {
|
|
899
834
|
const token = model.context.themeToken;
|
|
900
835
|
|
|
901
836
|
useEffect(() => {
|
|
@@ -907,6 +842,10 @@ export const AdminLayoutMenuModelRenderer: FC<{
|
|
|
907
842
|
});
|
|
908
843
|
}, [dom, item, model, options, renderType]);
|
|
909
844
|
|
|
845
|
+
if (!model.context.flowSettingsEnabled && model.hidden) {
|
|
846
|
+
return <HiddenMenuItemPlaceholder />;
|
|
847
|
+
}
|
|
848
|
+
|
|
910
849
|
return (
|
|
911
850
|
<ResetThemeTokenAndKeepAlgorithm>
|
|
912
851
|
<Droppable model={model}>
|
|
@@ -931,7 +870,7 @@ export const AdminLayoutMenuModelRenderer: FC<{
|
|
|
931
870
|
</Droppable>
|
|
932
871
|
</ResetThemeTokenAndKeepAlgorithm>
|
|
933
872
|
);
|
|
934
|
-
};
|
|
873
|
+
});
|
|
935
874
|
|
|
936
875
|
export function getAdminLayoutMenuInitializerButton(
|
|
937
876
|
testId: string,
|
|
@@ -50,6 +50,7 @@ type GetAdminLayoutModelOptions<TModel extends FlowModel = AdminLayoutModel> = {
|
|
|
50
50
|
*/
|
|
51
51
|
export class AdminLayoutModel extends FlowModel<AdminLayoutStructure> {
|
|
52
52
|
isMobileLayout = false;
|
|
53
|
+
menuRouteRefreshVersion = 0;
|
|
53
54
|
private routeCoordinator?: AdminLayoutRouteCoordinator;
|
|
54
55
|
private routeDisposer?: () => void;
|
|
55
56
|
private activePageUid = '';
|
|
@@ -60,9 +61,19 @@ export class AdminLayoutModel extends FlowModel<AdminLayoutStructure> {
|
|
|
60
61
|
super(options);
|
|
61
62
|
define(this, {
|
|
62
63
|
isMobileLayout: observable.ref,
|
|
64
|
+
menuRouteRefreshVersion: observable.ref,
|
|
63
65
|
});
|
|
64
66
|
}
|
|
65
67
|
|
|
68
|
+
/**
|
|
69
|
+
* 通知 Layout 重新生成 ProLayout 菜单路由。
|
|
70
|
+
*
|
|
71
|
+
* @returns {void}
|
|
72
|
+
*/
|
|
73
|
+
refreshMenuRouteTree() {
|
|
74
|
+
this.menuRouteRefreshVersion += 1;
|
|
75
|
+
}
|
|
76
|
+
|
|
66
77
|
/**
|
|
67
78
|
* 注册页面运行时信息。
|
|
68
79
|
*
|
|
@@ -14,6 +14,7 @@ import { Result, theme as antdTheme } from 'antd';
|
|
|
14
14
|
import React, { FC, useCallback, useMemo } from 'react';
|
|
15
15
|
import { useTranslation } from 'react-i18next';
|
|
16
16
|
import { Outlet, useLocation } from 'react-router-dom';
|
|
17
|
+
import { isV2AdminRuntime, isV2MenuRoute } from './resolveAdminRouteRuntimeTarget';
|
|
17
18
|
|
|
18
19
|
type AdminLayoutContentProps = {
|
|
19
20
|
onContentElementChange?: (element: HTMLDivElement | null) => void;
|
|
@@ -67,9 +68,12 @@ const ShowTipWhenNoPages = observer(() => {
|
|
|
67
68
|
const { t } = useTranslation();
|
|
68
69
|
const location = useLocation();
|
|
69
70
|
const allAccessRoutes = flowEngine.context.routeRepository?.listAccessible?.() || [];
|
|
71
|
+
const visibleRoutes = isV2AdminRuntime(flowEngine.context.app)
|
|
72
|
+
? allAccessRoutes.filter((route) => isV2MenuRoute(route))
|
|
73
|
+
: allAccessRoutes;
|
|
70
74
|
const designable = !!flowEngine.context.flowSettingsEnabled;
|
|
71
75
|
|
|
72
|
-
if (
|
|
76
|
+
if (visibleRoutes.length === 0 && !designable && ['/admin', '/admin/'].includes(location.pathname)) {
|
|
73
77
|
return (
|
|
74
78
|
<Result
|
|
75
79
|
icon={<HighlightOutlined style={{ fontSize: '8em', color: token.colorText }} />}
|