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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/es/Application.d.ts +1 -0
  2. package/es/BaseApplication.d.ts +4 -0
  3. package/es/RouterManager.d.ts +1 -0
  4. package/es/components/KeepAlive.d.ts +22 -0
  5. package/es/components/RouterBridge.d.ts +9 -0
  6. package/es/components/form/DialogFormLayout.d.ts +5 -29
  7. package/es/components/form/filter/CollectionFilter.d.ts +41 -0
  8. package/es/components/form/filter/CollectionFilterItem.d.ts +41 -0
  9. package/es/components/form/filter/DateFilterDynamicComponent.d.ts +57 -0
  10. package/es/components/form/filter/FilterValueInput.d.ts +29 -0
  11. package/es/components/form/filter/index.d.ts +11 -0
  12. package/es/components/form/filter/useFilterActionProps.d.ts +96 -0
  13. package/es/components/form/index.d.ts +1 -0
  14. package/es/data-source/ExtendCollectionsProvider.d.ts +50 -0
  15. package/es/data-source/index.d.ts +9 -0
  16. package/es/flow/FlowPage.d.ts +2 -1
  17. package/es/flow/admin-shell/AdminLayoutRouteCoordinator.d.ts +8 -40
  18. package/es/flow/admin-shell/BaseLayoutModel.d.ts +89 -0
  19. package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +74 -0
  20. package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +12 -0
  21. package/es/flow/admin-shell/admin-layout/AdminLayoutModel.d.ts +7 -92
  22. package/es/flow/admin-shell/admin-layout/index.d.ts +2 -0
  23. package/es/flow/admin-shell/useAdminLayoutRoutePage.d.ts +2 -2
  24. package/es/flow/admin-shell/useLayoutRoutePage.d.ts +23 -0
  25. package/es/flow/components/FlowRoute.d.ts +10 -1
  26. package/es/flow/components/filter/index.d.ts +2 -0
  27. package/es/flow/components/filter/useFilterOptions.d.ts +54 -0
  28. package/es/flow/index.d.ts +4 -0
  29. package/es/flow/models/base/PageModel/PageModel.d.ts +3 -1
  30. package/es/flow/models/blocks/form/FormActionGroupModel.d.ts +1 -0
  31. package/es/flow/models/blocks/table/TableBlockModel.d.ts +10 -0
  32. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.d.ts +1 -1
  33. package/es/flow-compat/passwordUtils.d.ts +1 -1
  34. package/es/index.d.ts +2 -0
  35. package/es/index.mjs +491 -439
  36. package/es/layout-manager/LayoutContentRoute.d.ts +14 -0
  37. package/es/layout-manager/LayoutManager.d.ts +22 -0
  38. package/es/layout-manager/LayoutRoute.d.ts +14 -0
  39. package/es/layout-manager/index.d.ts +13 -0
  40. package/es/layout-manager/types.d.ts +20 -0
  41. package/es/layout-manager/utils.d.ts +14 -0
  42. package/es/nocobase-buildin-plugin/index.d.ts +3 -10
  43. package/es/settings-center/index.d.ts +1 -1
  44. package/es/settings-center/plugin-manager/BulkEnableButton.d.ts +15 -0
  45. package/es/settings-center/plugin-manager/PluginCard.d.ts +15 -0
  46. package/es/settings-center/plugin-manager/PluginDetail.d.ts +16 -0
  47. package/es/settings-center/{PluginManagerPage.d.ts → plugin-manager/index.d.ts} +1 -7
  48. package/es/settings-center/plugin-manager/types.d.ts +34 -0
  49. package/lib/index.js +491 -439
  50. package/package.json +8 -7
  51. package/src/Application.tsx +27 -12
  52. package/src/BaseApplication.tsx +19 -0
  53. package/src/PluginSettingsManager.ts +1 -1
  54. package/src/RouterManager.tsx +17 -1
  55. package/src/__tests__/PluginSettingsManager.test.ts +41 -2
  56. package/src/__tests__/app.test.tsx +17 -1
  57. package/src/__tests__/globalDeps.test.ts +1 -0
  58. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +45 -2
  59. package/src/__tests__/plugin-manager.test.tsx +177 -0
  60. package/src/__tests__/settings-center.test.tsx +24 -2
  61. package/src/components/KeepAlive.tsx +131 -0
  62. package/src/components/README.md +89 -6
  63. package/src/components/README.zh-CN.md +89 -7
  64. package/src/components/RouterBridge.tsx +28 -4
  65. package/src/components/__tests__/KeepAlive.test.tsx +63 -0
  66. package/src/components/__tests__/RouterBridge.test.tsx +27 -0
  67. package/src/components/form/DialogFormLayout.tsx +5 -29
  68. package/src/components/form/filter/CollectionFilter.tsx +101 -0
  69. package/src/components/form/filter/CollectionFilterItem.tsx +176 -0
  70. package/src/components/form/filter/DateFilterDynamicComponent.tsx +283 -0
  71. package/src/components/form/filter/FilterValueInput.tsx +198 -0
  72. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +205 -0
  73. package/src/components/form/filter/__tests__/DateFilterDynamicComponent.test.tsx +148 -0
  74. package/src/components/form/filter/__tests__/FilterValueInput.test.tsx +243 -0
  75. package/src/components/form/filter/__tests__/compileFilterGroup.test.ts +146 -0
  76. package/src/components/form/filter/index.ts +13 -0
  77. package/src/components/form/filter/useFilterActionProps.ts +200 -0
  78. package/src/components/form/index.tsx +1 -0
  79. package/src/data-source/ExtendCollectionsProvider.tsx +144 -0
  80. package/src/data-source/__tests__/ExtendCollectionsProvider.test.tsx +264 -0
  81. package/src/data-source/index.ts +10 -0
  82. package/src/flow/FlowPage.tsx +35 -7
  83. package/src/flow/__tests__/FlowPage.test.tsx +79 -0
  84. package/src/flow/__tests__/FlowRoute.test.tsx +529 -2
  85. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +191 -0
  86. package/src/flow/actions/__tests__/openView.subModelKey.test.tsx +33 -0
  87. package/src/flow/actions/aclCheck.tsx +4 -0
  88. package/src/flow/actions/aclCheckRefresh.tsx +4 -0
  89. package/src/flow/actions/dateTimeFormat.tsx +12 -8
  90. package/src/flow/actions/linkageRules.tsx +122 -0
  91. package/src/flow/actions/openView.tsx +28 -4
  92. package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +11 -329
  93. package/src/flow/admin-shell/BaseLayoutModel.tsx +455 -0
  94. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +502 -0
  95. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +547 -3
  96. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +4 -4
  97. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +160 -0
  98. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +0 -12
  99. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +28 -201
  100. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +11 -2
  101. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +1 -26
  102. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +149 -27
  103. package/src/flow/admin-shell/admin-layout/index.ts +2 -0
  104. package/src/flow/admin-shell/useAdminLayoutRoutePage.ts +10 -26
  105. package/src/flow/admin-shell/useLayoutRoutePage.ts +61 -0
  106. package/src/flow/components/AdminLayout.tsx +4 -154
  107. package/src/flow/components/FlowRoute.tsx +105 -15
  108. package/src/flow/components/filter/index.ts +3 -0
  109. package/src/flow/components/filter/useFilterOptions.ts +80 -0
  110. package/src/flow/index.ts +4 -0
  111. package/src/flow/models/base/ActionModel.tsx +8 -1
  112. package/src/flow/models/base/PageModel/PageModel.tsx +51 -18
  113. package/src/flow/models/base/PageModel/RootPageModel.tsx +6 -13
  114. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +102 -1
  115. package/src/flow/models/base/RouteModel.tsx +1 -1
  116. package/src/flow/models/blocks/form/FormActionGroupModel.tsx +14 -0
  117. package/src/flow/models/blocks/form/FormItemModel.tsx +8 -1
  118. package/src/flow/models/blocks/form/__tests__/FormActionGroupModel.test.ts +46 -0
  119. package/src/flow/models/blocks/form/submitValues.ts +4 -1
  120. package/src/flow/models/blocks/table/TableBlockModel.tsx +118 -16
  121. package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +114 -0
  122. package/src/flow/models/fields/AssociationFieldModel/SubFormFieldModel.tsx +7 -1
  123. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +1 -1
  124. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -5
  125. package/src/flow/models/fields/ClickableFieldModel.tsx +9 -1
  126. package/src/flow/models/fields/DisplayTimeFieldModel.tsx +1 -1
  127. package/src/flow/models/fields/TimeFieldModel.tsx +1 -1
  128. package/src/flow/models/fields/__tests__/TimeFieldModel.test.tsx +61 -0
  129. package/src/flow/models/fields/mobile-components/MobileDatePicker.tsx +19 -3
  130. package/src/flow/models/fields/mobile-components/__tests__/MobileDatePicker.test.tsx +94 -0
  131. package/src/flow/models/topbar/TopbarActionModel.tsx +1 -1
  132. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +91 -0
  133. package/src/index.ts +2 -0
  134. package/src/layout-manager/LayoutContentRoute.tsx +90 -0
  135. package/src/layout-manager/LayoutManager.tsx +185 -0
  136. package/src/layout-manager/LayoutRoute.tsx +138 -0
  137. package/src/layout-manager/__tests__/LayoutManager.test.tsx +335 -0
  138. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +473 -0
  139. package/src/layout-manager/index.ts +14 -0
  140. package/src/layout-manager/types.ts +22 -0
  141. package/src/layout-manager/utils.ts +37 -0
  142. package/src/nocobase-buildin-plugin/index.tsx +69 -67
  143. package/src/nocobase-buildin-plugin/plugins/LocalePlugin.ts +1 -0
  144. package/src/settings-center/index.ts +1 -1
  145. package/src/settings-center/plugin-manager/BulkEnableButton.tsx +111 -0
  146. package/src/settings-center/plugin-manager/PluginCard.tsx +270 -0
  147. package/src/settings-center/plugin-manager/PluginDetail.tsx +195 -0
  148. package/src/settings-center/plugin-manager/index.tsx +254 -0
  149. package/src/settings-center/plugin-manager/types.ts +35 -0
  150. package/src/settings-center/utils.tsx +8 -1
  151. package/src/theme/__tests__/globalStyles.test.ts +24 -0
  152. package/src/theme/globalStyles.ts +10 -0
  153. package/src/utils/globalDeps.ts +2 -0
  154. package/src/settings-center/PluginManagerPage.tsx +0 -162
@@ -0,0 +1,455 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import { define, observable } from '@formily/reactive';
11
+ import { parsePathnameToViewParams, type FlowEngine, FlowModel, type ViewParam } from '@nocobase/flow-engine';
12
+ import {
13
+ BaseLayoutRouteCoordinator,
14
+ type BaseLayoutRouteCoordinatorOptions,
15
+ type RoutePageMeta,
16
+ } from './BaseLayoutRouteCoordinator';
17
+ import type { LayoutDefinition } from '../../layout-manager/types';
18
+ import { isLayoutContentRouteName } from '../../layout-manager/utils';
19
+
20
+ export type BaseLayoutStructure = {
21
+ subModels?: Record<string, FlowModel[]>;
22
+ };
23
+
24
+ export type GetLayoutModelOptions<TModel extends FlowModel = BaseLayoutModel> = {
25
+ required?: boolean;
26
+ create?: boolean;
27
+ props?: any;
28
+ use?: new (...args: any[]) => TModel;
29
+ };
30
+
31
+ const DEFAULT_LAYOUT_DEFINITION: LayoutDefinition = {
32
+ routeName: 'admin',
33
+ routePath: '/admin',
34
+ rootRouteName: 'admin',
35
+ uid: 'admin-layout-model',
36
+ layoutModelClass: 'AdminLayoutModel',
37
+ rootPageModelClass: 'RootPageModel',
38
+ childPageModelClass: 'ChildPageModel',
39
+ authCheck: true,
40
+ };
41
+
42
+ export type LayoutRouteMatch =
43
+ | {
44
+ type: 'root';
45
+ pathname: string;
46
+ basePathname: string;
47
+ relativePath: string;
48
+ }
49
+ | {
50
+ type: 'page';
51
+ pathname: string;
52
+ basePathname: string;
53
+ relativePath: string;
54
+ pageUid: string;
55
+ tabUid?: string;
56
+ viewStack: ViewParam[];
57
+ }
58
+ | {
59
+ type: 'notFound';
60
+ pathname: string;
61
+ basePathname: string;
62
+ relativePath: string;
63
+ };
64
+
65
+ export interface LayoutRouteLike {
66
+ id?: string;
67
+ name?: string;
68
+ pathname?: string;
69
+ params?: Record<string, string | undefined>;
70
+ layoutRouteName?: string;
71
+ layoutBasePathname?: string;
72
+ }
73
+
74
+ const normalizeBasePathname = (basePathname?: string) =>
75
+ `/${(basePathname || '/admin').replace(/^\/+/, '').replace(/\/+$/, '')}`;
76
+
77
+ const normalizePathname = (pathname?: string) => {
78
+ if (!pathname || pathname === '/') {
79
+ return '/';
80
+ }
81
+ return `/${pathname.replace(/^\/+/, '').replace(/\/+$/, '')}`;
82
+ };
83
+
84
+ const getRelativePath = (pathname: string, basePath: string) => {
85
+ const normalizedPathname = normalizePathname(pathname);
86
+ const normalizedBasePath = normalizeBasePathname(basePath);
87
+
88
+ if (normalizedPathname === normalizedBasePath) {
89
+ return '';
90
+ }
91
+
92
+ if (normalizedPathname.startsWith(`${normalizedBasePath}/`)) {
93
+ return normalizedPathname.slice(normalizedBasePath.length + 1);
94
+ }
95
+
96
+ return null;
97
+ };
98
+
99
+ const getDefaultBasePathnameFromRoutePath = (routePath?: string) => {
100
+ if (routePath?.startsWith('/')) {
101
+ return normalizeBasePathname(routePath);
102
+ }
103
+ return '';
104
+ };
105
+
106
+ const isKnownViewParamName = (segment: string) => ['tab', 'filterbytk', 'sourceid'].includes(segment);
107
+
108
+ const isStandardLayoutRelativePath = (relativePath: string) => {
109
+ if (!relativePath) {
110
+ return true;
111
+ }
112
+
113
+ const segments = relativePath.split('/').filter(Boolean);
114
+ if (!segments.length) {
115
+ return true;
116
+ }
117
+
118
+ let i = 1;
119
+ while (i < segments.length) {
120
+ const segment = segments[i];
121
+ if (segment === 'view') {
122
+ if (!segments[i + 1]) {
123
+ return false;
124
+ }
125
+ i += 2;
126
+ continue;
127
+ }
128
+
129
+ if (!isKnownViewParamName(segment) || !segments[i + 1]) {
130
+ return false;
131
+ }
132
+ i += 2;
133
+ }
134
+
135
+ return true;
136
+ };
137
+
138
+ /**
139
+ * 通用 Layout 运行时模型。
140
+ *
141
+ * 该模型封装页面路由桥接、弹窗路由、page tab 路由、布局容器和移动端状态,
142
+ * Admin、Embed 等具体 Layout 只需要继承并实现各自的渲染和专属业务能力。
143
+ */
144
+ export class BaseLayoutModel<
145
+ TStructure extends BaseLayoutStructure = BaseLayoutStructure,
146
+ > extends FlowModel<TStructure> {
147
+ isMobileLayout = false;
148
+ currentLayoutRoute: LayoutRouteMatch | null = null;
149
+ protected routeCoordinator?: BaseLayoutRouteCoordinator;
150
+ private activePageUid = '';
151
+ private layoutContentElement: HTMLElement | null = null;
152
+ private readonly routePageMetaMap = new Map<string, RoutePageMeta>();
153
+ private contextBindingsActive = false;
154
+
155
+ constructor(options: any) {
156
+ super(options);
157
+ define(this, {
158
+ isMobileLayout: observable.ref,
159
+ currentLayoutRoute: observable.ref,
160
+ });
161
+ }
162
+
163
+ registerRoutePage(pageUid: string, meta: RoutePageMeta) {
164
+ this.routePageMetaMap.set(pageUid, meta);
165
+ const routeModel = this.getCoordinator().registerPage(pageUid, meta);
166
+ this.getCoordinator().syncRoute(this.getCurrentCoordinatorRouteLike());
167
+ return routeModel;
168
+ }
169
+
170
+ updateRoutePage(pageUid: string, meta: Partial<RoutePageMeta>) {
171
+ const prev = this.routePageMetaMap.get(pageUid) || { active: false };
172
+ const next = {
173
+ ...prev,
174
+ ...meta,
175
+ active: typeof meta.active === 'boolean' ? meta.active : prev.active,
176
+ };
177
+ this.routePageMetaMap.set(pageUid, next);
178
+ this.getCoordinator().syncPageMeta(pageUid, meta);
179
+ }
180
+
181
+ unregisterRoutePage(pageUid: string) {
182
+ this.routePageMetaMap.delete(pageUid);
183
+ if (this.activePageUid === pageUid) {
184
+ this.activePageUid = '';
185
+ }
186
+ this.getCoordinator().unregisterPage(pageUid);
187
+ }
188
+
189
+ setLayoutContentElement(element: HTMLElement | null) {
190
+ this.layoutContentElement = element;
191
+ this.getCoordinator().setLayoutContentElement(element);
192
+ }
193
+
194
+ setIsMobileLayout(isMobileLayout: boolean) {
195
+ this.isMobileLayout = !!isMobileLayout;
196
+ }
197
+
198
+ getCurrentRouteByPageUid(pageUid: string) {
199
+ return this.flowEngine.context.routeRepository?.getRouteBySchemaUid?.(pageUid) || {};
200
+ }
201
+
202
+ get layout(): LayoutDefinition {
203
+ return (
204
+ (this.props.layout as LayoutDefinition) || {
205
+ ...DEFAULT_LAYOUT_DEFINITION,
206
+ uid: this.uid || DEFAULT_LAYOUT_DEFINITION.uid,
207
+ }
208
+ );
209
+ }
210
+
211
+ getCoordinator() {
212
+ if (!this.routeCoordinator) {
213
+ this.routeCoordinator = this.createRouteCoordinator();
214
+ }
215
+ return this.routeCoordinator;
216
+ }
217
+
218
+ protected createRouteCoordinator() {
219
+ return new BaseLayoutRouteCoordinator(this.flowEngine, this.getRouteCoordinatorOptions());
220
+ }
221
+
222
+ protected getRouteCoordinatorOptions(): BaseLayoutRouteCoordinatorOptions {
223
+ return {
224
+ layout: this.layout,
225
+ layoutContext: this.context,
226
+ };
227
+ }
228
+
229
+ protected getPageUidFromRoute(route: any) {
230
+ if (route?.pageUid) {
231
+ return route.pageUid;
232
+ }
233
+ if (route?.layoutRoute?.type === 'page') {
234
+ return route.layoutRoute.pageUid;
235
+ }
236
+ return route?.params?.name || '';
237
+ }
238
+
239
+ isLayoutContentRoute(routeLike: LayoutRouteLike) {
240
+ if (routeLike?.layoutRouteName) {
241
+ return routeLike.layoutRouteName === this.layout.routeName;
242
+ }
243
+
244
+ const routeName = routeLike?.name || routeLike?.id;
245
+ return isLayoutContentRouteName(this.layout.routeName, routeName);
246
+ }
247
+
248
+ resolveLayoutRoute(routeLike: LayoutRouteLike): LayoutRouteMatch {
249
+ const pathname = normalizePathname(routeLike?.pathname);
250
+ const rawBasePathname = routeLike.layoutBasePathname || getDefaultBasePathnameFromRoutePath(this.layout.routePath);
251
+ const basePathname = rawBasePathname ? normalizeBasePathname(rawBasePathname) : '';
252
+
253
+ if (!basePathname) {
254
+ return {
255
+ type: 'notFound',
256
+ pathname,
257
+ basePathname: '',
258
+ relativePath: '',
259
+ };
260
+ }
261
+
262
+ const relativePath = getRelativePath(pathname, basePathname);
263
+
264
+ if (relativePath === null) {
265
+ return {
266
+ type: 'notFound',
267
+ pathname,
268
+ basePathname,
269
+ relativePath: '',
270
+ };
271
+ }
272
+
273
+ if (!relativePath) {
274
+ return {
275
+ type: 'root',
276
+ pathname,
277
+ basePathname,
278
+ relativePath,
279
+ };
280
+ }
281
+
282
+ if (!isStandardLayoutRelativePath(relativePath)) {
283
+ return {
284
+ type: 'notFound',
285
+ pathname,
286
+ basePathname,
287
+ relativePath,
288
+ };
289
+ }
290
+
291
+ const viewStack = parsePathnameToViewParams(pathname, { basePath: basePathname });
292
+ const pageUid = viewStack[0]?.viewUid;
293
+
294
+ if (!pageUid) {
295
+ return {
296
+ type: 'notFound',
297
+ pathname,
298
+ basePathname,
299
+ relativePath,
300
+ };
301
+ }
302
+
303
+ return {
304
+ type: 'page',
305
+ pathname,
306
+ basePathname,
307
+ relativePath,
308
+ pageUid,
309
+ tabUid: viewStack[0]?.tabUid,
310
+ viewStack,
311
+ };
312
+ }
313
+
314
+ getPageUidFromLayoutRoute(match: LayoutRouteMatch | null | undefined) {
315
+ return match?.type === 'page' ? match.pageUid : '';
316
+ }
317
+
318
+ syncLayoutRoute(routeLike: LayoutRouteLike) {
319
+ if (!this.isLayoutContentRoute(routeLike)) {
320
+ this.clearLayoutRoute();
321
+ return null;
322
+ }
323
+
324
+ const layoutRoute = this.resolveLayoutRoute(routeLike);
325
+ this.currentLayoutRoute = layoutRoute;
326
+ this.activePageUid = this.getPageUidFromLayoutRoute(layoutRoute);
327
+ this.getCoordinator().syncRoute({
328
+ ...routeLike,
329
+ layoutRouteName: this.layout.routeName,
330
+ pageUid: this.activePageUid,
331
+ pathname: layoutRoute.pathname,
332
+ layoutBasePathname: layoutRoute.basePathname,
333
+ layoutRoute,
334
+ });
335
+
336
+ return layoutRoute;
337
+ }
338
+
339
+ clearLayoutRoute(routeLike?: LayoutRouteLike) {
340
+ const pathname = routeLike?.pathname ? normalizePathname(routeLike.pathname) : '';
341
+ if (pathname && this.currentLayoutRoute?.pathname && pathname !== this.currentLayoutRoute.pathname) {
342
+ return;
343
+ }
344
+
345
+ this.currentLayoutRoute = null;
346
+ this.activePageUid = '';
347
+ this.routeCoordinator?.syncRoute({});
348
+ }
349
+
350
+ protected onMount(): void {
351
+ super.onMount();
352
+ this.setupContextBindings();
353
+ }
354
+
355
+ protected onUnmount(): void {
356
+ this.teardownRuntime();
357
+ super.onUnmount();
358
+ }
359
+
360
+ private setupContextBindings() {
361
+ this.contextBindingsActive = true;
362
+ this.context.defineProperty('layoutContext', {
363
+ get: () => (this.contextBindingsActive ? this.context : undefined),
364
+ cache: false,
365
+ });
366
+ this.context.defineProperty('currentRoute', {
367
+ get: () => (this.contextBindingsActive ? this.getCurrentRouteByActivePage() : {}),
368
+ // 切页后需要立即读取当前激活页面的路由,不能复用首次访问时的缓存值。
369
+ cache: false,
370
+ });
371
+ this.context.defineProperty('layoutContentElement', {
372
+ get: () => (this.contextBindingsActive ? this.layoutContentElement : null),
373
+ // 布局容器 ref 会在挂载和卸载时变化,这里必须实时读取。
374
+ cache: false,
375
+ });
376
+ this.context.defineProperty('isMobileLayout', {
377
+ get: () => (this.contextBindingsActive ? this.isMobileLayout : false),
378
+ observable: true,
379
+ cache: false,
380
+ });
381
+ this.context.defineProperty('layout', {
382
+ get: () => (this.contextBindingsActive ? this.layout : undefined),
383
+ cache: false,
384
+ });
385
+ this.context.defineProperty('layoutRoute', {
386
+ get: () => (this.contextBindingsActive ? this.currentLayoutRoute : undefined),
387
+ observable: true,
388
+ cache: false,
389
+ });
390
+ }
391
+
392
+ private teardownRuntime() {
393
+ this.contextBindingsActive = false;
394
+ this.routeCoordinator?.destroy();
395
+ this.routeCoordinator = undefined;
396
+ this.routePageMetaMap.clear();
397
+ this.activePageUid = '';
398
+ this.currentLayoutRoute = null;
399
+ this.layoutContentElement = null;
400
+ }
401
+
402
+ private getCurrentRouteByActivePage() {
403
+ if (!this.activePageUid) {
404
+ return {};
405
+ }
406
+ return this.getCurrentRouteByPageUid(this.activePageUid);
407
+ }
408
+
409
+ private getCurrentCoordinatorRouteLike() {
410
+ if (this.currentLayoutRoute?.type === 'page') {
411
+ return {
412
+ layoutRouteName: this.layout.routeName,
413
+ pageUid: this.currentLayoutRoute.pageUid,
414
+ pathname: this.currentLayoutRoute.pathname,
415
+ layoutBasePathname: this.currentLayoutRoute.basePathname,
416
+ layoutRoute: this.currentLayoutRoute,
417
+ };
418
+ }
419
+ return {};
420
+ }
421
+ }
422
+
423
+ /**
424
+ * 按固定 UID 获取或创建 Layout host model。
425
+ */
426
+ export function getLayoutModel<TModel extends FlowModel = BaseLayoutModel>(
427
+ flowEngine: FlowEngine,
428
+ uid: string,
429
+ options: GetLayoutModelOptions<TModel> = {},
430
+ ) {
431
+ const { required = false, create = false, props, use } = options;
432
+ let model = flowEngine.getModel<TModel>(uid);
433
+
434
+ if (!model && create) {
435
+ const ModelClass = use as new (...args: any[]) => TModel;
436
+ if (!ModelClass) {
437
+ throw new Error(`[NocoBase] Cannot create layout model ${uid} without model class.`);
438
+ }
439
+ model = flowEngine.createModel<TModel>({
440
+ uid,
441
+ use: ModelClass,
442
+ props,
443
+ });
444
+ }
445
+
446
+ if (model && props) {
447
+ model.setProps(props);
448
+ }
449
+
450
+ if (!model && required) {
451
+ throw new Error(`[NocoBase] FlowRoute requires layout model ${uid}. Please render FlowRoute under Layout.`);
452
+ }
453
+
454
+ return model;
455
+ }