cabloy 5.1.177 → 5.1.178
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/.cabloy-version +1 -1
- package/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/repo-e2e/specs/cabloy-basic.spec.ts +68 -1
- package/repo-e2e/specs/controller-boundary.spec.ts +42 -0
- package/repo-e2e/specs/routed-dialog.spec.ts +15 -0
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/controller/{{resourceName}}.ts_ +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/zova/packages-utils/zova-jsx/package.json +2 -2
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +1 -1
- package/zova/packages-zova/zova-core/src/bean/beanContainer.ts +4 -0
- package/zova/packages-zova/zova-core/src/composables/useController.ts +4 -1
- package/zova/src/suite/a-home/modules/home-indexadmin/src/page/dashboard/controller.tsx +31 -0
- package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.tabs.tsx +18 -2
- package/zova/src/suite-vendor/a-zova/modules/a-router/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/bean/bean.router.ts +13 -22
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/bean/sys.router.ts +1 -0
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/lib/routerGuardDisposers.ts +19 -0
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/monkey.ts +1 -0
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/service/routerGuards.ts +12 -3
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/types/pageMeta.ts +6 -1
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/types/routerView.ts +5 -0
- package/zova/src/suite-vendor/a-zova/modules/a-router/test/lib/routerGuardDisposers.test.ts +24 -0
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +3 -3
package/.cabloy-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.1.
|
|
1
|
+
5.1.178
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.1.178
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- Add `setPageMeta` support for configuring page metadata.
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- Fix router behavior.
|
|
12
|
+
- Correct `isAttemptActive` handling.
|
|
13
|
+
- Ensure route resources are ready before initializing controller data.
|
|
14
|
+
|
|
3
15
|
## 5.1.177
|
|
4
16
|
|
|
5
17
|
### Improvements
|
package/package.json
CHANGED
|
@@ -28,6 +28,16 @@ function collectPageErrors(page: Page) {
|
|
|
28
28
|
return errors;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function collectConsoleErrors(page: Page) {
|
|
32
|
+
const errors: string[] = [];
|
|
33
|
+
page.on('console', message => {
|
|
34
|
+
if (message.type() === 'error') {
|
|
35
|
+
errors.push(message.text());
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
return errors;
|
|
39
|
+
}
|
|
40
|
+
|
|
31
41
|
async function loginAsAdmin(page: Page) {
|
|
32
42
|
await page.goto('/admin/', { waitUntil: 'load' });
|
|
33
43
|
if (page.url().includes('/admin/login')) {
|
|
@@ -319,7 +329,64 @@ test(
|
|
|
319
329
|
);
|
|
320
330
|
|
|
321
331
|
test(
|
|
322
|
-
'ATP-BASIC-FLOW-01:
|
|
332
|
+
'ATP-BASIC-FLOW-01: cold Admin navigation initializes dynamic resource selectors',
|
|
333
|
+
{ tag: ['@admin', '@flow'] },
|
|
334
|
+
async ({ browser }) => {
|
|
335
|
+
const context = await browser.newContext();
|
|
336
|
+
const page = await context.newPage();
|
|
337
|
+
const pageErrors = collectPageErrors(page);
|
|
338
|
+
const consoleErrors = collectConsoleErrors(page);
|
|
339
|
+
try {
|
|
340
|
+
await page.goto('/admin/login', { waitUntil: 'load' });
|
|
341
|
+
await page.evaluate(() => {
|
|
342
|
+
indexedDB.deleteDatabase('localforage');
|
|
343
|
+
localStorage.clear();
|
|
344
|
+
sessionStorage.clear();
|
|
345
|
+
});
|
|
346
|
+
await page.goto('/admin/login?returnTo=/rest/resource/training-student%253Astudent', {
|
|
347
|
+
waitUntil: 'load',
|
|
348
|
+
});
|
|
349
|
+
await page.getByPlaceholder('Your Username').fill('admin');
|
|
350
|
+
await page.getByPlaceholder('Your Password').fill('123456');
|
|
351
|
+
await expect(page.getByPlaceholder('Please input captcha')).not.toHaveValue('');
|
|
352
|
+
|
|
353
|
+
const initialSelect = waitForStudentSelect(page);
|
|
354
|
+
await page.getByRole('button', { name: 'Login', exact: true }).click();
|
|
355
|
+
try {
|
|
356
|
+
await expect(page).toHaveURL(studentResourceUrl);
|
|
357
|
+
await initialSelect;
|
|
358
|
+
} finally {
|
|
359
|
+
await initialSelect.catch(() => {});
|
|
360
|
+
}
|
|
361
|
+
await expect(page.getByLabel('Student Name')).toBeVisible();
|
|
362
|
+
|
|
363
|
+
await page.getByRole('link', { name: 'Home', exact: true }).click();
|
|
364
|
+
await expect(page).toHaveURL(/\/admin\/(?:\?|$)/);
|
|
365
|
+
await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'admin');
|
|
366
|
+
await expect(
|
|
367
|
+
page.getByRole('heading', { name: 'Vona integrated SSR', exact: true }),
|
|
368
|
+
).toBeVisible();
|
|
369
|
+
await expect(
|
|
370
|
+
page.getByRole('heading', { name: 'Zova standalone SSR', exact: true }),
|
|
371
|
+
).toBeVisible();
|
|
372
|
+
await expect(page.locator('body')).toBeVisible();
|
|
373
|
+
|
|
374
|
+
expect(pageErrors).toEqual([]);
|
|
375
|
+
expect(
|
|
376
|
+
consoleErrors.some(
|
|
377
|
+
error =>
|
|
378
|
+
error.includes("Cannot read properties of undefined (reading 'resource')") ||
|
|
379
|
+
error.includes('resource not specified'),
|
|
380
|
+
),
|
|
381
|
+
).toBeFalsy();
|
|
382
|
+
} finally {
|
|
383
|
+
await context.close();
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
test(
|
|
389
|
+
'ATP-BASIC-FLOW-02: Training Student flow filter wraps and submits queries',
|
|
323
390
|
{ tag: ['@admin', '@flow'] },
|
|
324
391
|
async ({ page }) => {
|
|
325
392
|
await page.setViewportSize({ width: 1440, height: 900 });
|
|
@@ -101,6 +101,48 @@ test(
|
|
|
101
101
|
},
|
|
102
102
|
);
|
|
103
103
|
|
|
104
|
+
test(
|
|
105
|
+
'ATP-BASIC-CONTROLLER-BOUNDARY-06: disposed and stale loads do not surface errors',
|
|
106
|
+
{ tag: ['@web', '@flow'] },
|
|
107
|
+
async ({ page }) => {
|
|
108
|
+
const pageErrors = collectPageErrors(page);
|
|
109
|
+
const consoleErrors = collectConsoleErrors(page);
|
|
110
|
+
const response = await page.goto('/demo/basic/controllerBoundary', { waitUntil: 'load' });
|
|
111
|
+
expect(response?.ok()).toBeTruthy();
|
|
112
|
+
await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'web');
|
|
113
|
+
await page.waitForTimeout(1_000);
|
|
114
|
+
|
|
115
|
+
const loading = page.getByRole('status');
|
|
116
|
+
const error = page.locator('.alert.alert-error');
|
|
117
|
+
const ready = page.getByText('Controller boundary probe ready', { exact: true });
|
|
118
|
+
|
|
119
|
+
const disposedLoading = loading.waitFor({ state: 'visible' });
|
|
120
|
+
await page.getByRole('button', { name: 'Mount failing probe', exact: true }).click();
|
|
121
|
+
await disposedLoading;
|
|
122
|
+
await page.getByRole('button', { name: 'Clear probe', exact: true }).click();
|
|
123
|
+
await page.waitForTimeout(1_000);
|
|
124
|
+
await expect(loading).toHaveCount(0);
|
|
125
|
+
await expect(error).toHaveCount(0);
|
|
126
|
+
await expect(ready).toHaveCount(0);
|
|
127
|
+
|
|
128
|
+
const staleLoading = loading.waitFor({ state: 'visible' });
|
|
129
|
+
await page.getByRole('button', { name: 'Mount failing probe', exact: true }).click();
|
|
130
|
+
await staleLoading;
|
|
131
|
+
await page.getByRole('button', { name: 'Mount fast success probe', exact: true }).click();
|
|
132
|
+
await expect(ready).toBeVisible();
|
|
133
|
+
await page.waitForTimeout(1_000);
|
|
134
|
+
await expect(error).toHaveCount(0);
|
|
135
|
+
await expect(ready).toBeVisible();
|
|
136
|
+
|
|
137
|
+
expect(pageErrors).toEqual([]);
|
|
138
|
+
expect(
|
|
139
|
+
consoleErrors.filter(error =>
|
|
140
|
+
/Cannot read properties of null \(reading ['"]state['"]\)/.test(error),
|
|
141
|
+
),
|
|
142
|
+
).toEqual([]);
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
|
|
104
146
|
test(
|
|
105
147
|
'ATP-BASIC-CONTROLLER-BOUNDARY-02: inline boundaries preserve phrasing-content markup',
|
|
106
148
|
{ tag: ['@web', '@flow'] },
|
|
@@ -195,6 +195,21 @@ test(
|
|
|
195
195
|
await expect(dialogA.getByRole('button', { name: 'Back', exact: true })).toHaveCount(0);
|
|
196
196
|
expect(page.url()).toBe(browserUrl);
|
|
197
197
|
|
|
198
|
+
await page.keyboard.press('Escape');
|
|
199
|
+
await expect(dialogA).toHaveCount(0);
|
|
200
|
+
expect(page.url()).toBe(browserUrl);
|
|
201
|
+
|
|
202
|
+
await page.getByRole('button', { name: 'Open A', exact: true }).click();
|
|
203
|
+
await expect(dialogA).toHaveCount(1);
|
|
204
|
+
await expect(
|
|
205
|
+
dialogA.getByRole('heading', { name: 'Routed Dialog Entry', exact: true }),
|
|
206
|
+
).toBeVisible();
|
|
207
|
+
await dialogA.getByRole('button', { name: 'Local push to detail', exact: true }).click();
|
|
208
|
+
await expect(
|
|
209
|
+
dialogA.getByRole('heading', { name: 'Routed Dialog Detail', exact: true }),
|
|
210
|
+
).toBeVisible();
|
|
211
|
+
expect(page.url()).toBe(browserUrl);
|
|
212
|
+
|
|
198
213
|
await page.keyboard.press('Escape');
|
|
199
214
|
await expect(dialogA).toHaveCount(0);
|
|
200
215
|
expect(page.url()).toBe(browserUrl);
|
|
@@ -65,5 +65,5 @@ export class Controller<%=argv.resourceNameCapitalize%> extends BeanBase {
|
|
|
65
65
|
@Passport.systemAdmin()
|
|
66
66
|
async deleteBulk(@Arg.body() command: Dto<%=argv.resourceNameCapitalize%>DeleteBulk): Promise<void> {
|
|
67
67
|
await this.scope.service.<%=argv.resourceName%>.deleteBulk(command.ids);
|
|
68
|
-
}
|
|
68
|
+
}
|
|
69
69
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova-jsx",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.91",
|
|
4
4
|
"gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
|
|
5
5
|
"description": "Zova JSX",
|
|
6
6
|
"keywords": [
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@cabloy/word-utils": "^2.1.14",
|
|
51
51
|
"typestyle": "^2.4.0",
|
|
52
52
|
"vue": "^3.5.38",
|
|
53
|
-
"zova-core": "^5.1.
|
|
53
|
+
"zova-core": "^5.1.91"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"clean-package": "^2.2.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.165",
|
|
4
4
|
"gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
|
|
5
5
|
"description": "A vue3 framework with ioc",
|
|
6
6
|
"keywords": [
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"postpack": "clean-package restore"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"zova-core": "^5.1.
|
|
49
|
-
"zova-suite-a-zova": "^5.1.
|
|
48
|
+
"zova-core": "^5.1.91",
|
|
49
|
+
"zova-suite-a-zova": "^5.1.164"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"clean-package": "^2.2.0",
|
|
@@ -489,6 +489,10 @@ export class BeanContainer {
|
|
|
489
489
|
// throw new Error(`bean not found: ${beanFullName}`);
|
|
490
490
|
return null!;
|
|
491
491
|
}
|
|
492
|
+
// controller module: ensure route resources are ready before controller data initialization
|
|
493
|
+
if (controllerData) {
|
|
494
|
+
await this._useModule(beanOptions.module);
|
|
495
|
+
}
|
|
492
496
|
// beanFullName
|
|
493
497
|
return await this._createBeanInstance<T>(
|
|
494
498
|
record,
|
|
@@ -111,6 +111,7 @@ async function _useController(
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
function isAttemptActive(attemptId: number) {
|
|
114
|
+
if (ctx.disposed || !ctx.meta) return false;
|
|
114
115
|
return ctx.meta.state.isLoadAttemptActive(attemptId);
|
|
115
116
|
}
|
|
116
117
|
|
|
@@ -133,9 +134,9 @@ async function _useController(
|
|
|
133
134
|
true,
|
|
134
135
|
false,
|
|
135
136
|
controller => {
|
|
137
|
+
if (!isAttemptActive(attemptId)) return;
|
|
136
138
|
loadBeans.add(controller as object);
|
|
137
139
|
ctx.meta.state.setLoadBeanAttempt(controller as object, attemptId);
|
|
138
|
-
if (!isAttemptActive(attemptId)) return;
|
|
139
140
|
ctx.meta.state.setLoadController(controller);
|
|
140
141
|
setLoadBoundaryOptions((controller as any).constructor);
|
|
141
142
|
},
|
|
@@ -151,6 +152,7 @@ async function _useController(
|
|
|
151
152
|
true,
|
|
152
153
|
false,
|
|
153
154
|
style => {
|
|
155
|
+
if (!isAttemptActive(attemptId)) return;
|
|
154
156
|
loadBeans.add(style as object);
|
|
155
157
|
ctx.meta.state.setLoadBeanAttempt(style as object, attemptId);
|
|
156
158
|
},
|
|
@@ -167,6 +169,7 @@ async function _useController(
|
|
|
167
169
|
true,
|
|
168
170
|
false,
|
|
169
171
|
render => {
|
|
172
|
+
if (!isAttemptActive(attemptId)) return;
|
|
170
173
|
loadBeans.add(render as object);
|
|
171
174
|
ctx.meta.state.setLoadBeanAttempt(render as object, attemptId);
|
|
172
175
|
},
|
|
@@ -4,6 +4,37 @@ import { ZPage, ZSiteEntryTables } from 'zova-module-home-base';
|
|
|
4
4
|
|
|
5
5
|
@Controller()
|
|
6
6
|
export class ControllerPageDashboard extends BeanControllerPageBase {
|
|
7
|
+
private _timer?: number;
|
|
8
|
+
currentTime = '';
|
|
9
|
+
|
|
10
|
+
protected __init__() {
|
|
11
|
+
if (!this.$pageRoute) return;
|
|
12
|
+
this.$ssr.handleDirectOrOnHydrated(() => {
|
|
13
|
+
this.currentTime = new Date().toLocaleTimeString();
|
|
14
|
+
this._timer = window.setInterval(() => {
|
|
15
|
+
this.currentTime = new Date().toLocaleTimeString();
|
|
16
|
+
}, 1000);
|
|
17
|
+
});
|
|
18
|
+
this.$router.setPageMeta(this.$pageRoute, {
|
|
19
|
+
onCustomRenderIsolate: () => (
|
|
20
|
+
<div class="flex w-full items-center justify-center">
|
|
21
|
+
<div class="badge badge-lg gap-2 border border-primary/30 bg-primary/10 px-4 py-3 text-primary shadow-sm">
|
|
22
|
+
<span class="h-2 w-2 animate-pulse rounded-full bg-primary" aria-hidden="true"></span>
|
|
23
|
+
<span class="font-mono text-base font-semibold tracking-wide tabular-nums">
|
|
24
|
+
{this.currentTime || '--:--:--'}
|
|
25
|
+
</span>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
protected __dispose__() {
|
|
33
|
+
if (this._timer !== undefined) {
|
|
34
|
+
window.clearInterval(this._timer);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
7
38
|
protected render() {
|
|
8
39
|
return (
|
|
9
40
|
<ZPage>
|
package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.tabs.tsx
CHANGED
|
@@ -63,6 +63,20 @@ export class RenderTabs extends BeanRenderBase {
|
|
|
63
63
|
const tabCurrent = $$modelTabs.tabCurrent;
|
|
64
64
|
if (!tabCurrent || !tabCurrent.items) return;
|
|
65
65
|
const tabKey = tabCurrent.tabKey;
|
|
66
|
+
const tabItemAnchor = tabCurrent.items.find(item => item.componentKey === tabKey);
|
|
67
|
+
const onCustomRenderIsolate = tabItemAnchor?.pageMeta?.onCustomRenderIsolate;
|
|
68
|
+
if (onCustomRenderIsolate) {
|
|
69
|
+
const domContent = onCustomRenderIsolate(tabCurrent);
|
|
70
|
+
if (!this.$$modelTabs.cache) return domContent;
|
|
71
|
+
return <ClientOnly>{domContent}</ClientOnly>;
|
|
72
|
+
}
|
|
73
|
+
const pageTitleAnchor = tabItemAnchor?.pageMeta?.pageTitle;
|
|
74
|
+
if (pageTitleAnchor) {
|
|
75
|
+
const domWrapper = <div class="flex items-center justify-center">{pageTitleAnchor}</div>;
|
|
76
|
+
if (!this.$$modelTabs.cache) return domWrapper;
|
|
77
|
+
return <ClientOnly>{domWrapper}</ClientOnly>;
|
|
78
|
+
}
|
|
79
|
+
|
|
66
80
|
const domTabs: VNode[] = [];
|
|
67
81
|
for (const tabItem of tabCurrent.items) {
|
|
68
82
|
// ignore first
|
|
@@ -70,7 +84,9 @@ export class RenderTabs extends BeanRenderBase {
|
|
|
70
84
|
const { componentKey, pageMeta } = tabItem;
|
|
71
85
|
const className =
|
|
72
86
|
componentKey === $$modelTabs.componentKeyCurrent ? 'tab-active text-primary' : '';
|
|
73
|
-
const
|
|
87
|
+
const pageContent = pageMeta?.onCustomRender
|
|
88
|
+
? pageMeta.onCustomRender(tabItem)
|
|
89
|
+
: pageMeta?.pageTitle || '';
|
|
74
90
|
const tabItemIcon = this.getTabItemIcon(tabItem);
|
|
75
91
|
const domTab = (
|
|
76
92
|
<a
|
|
@@ -86,7 +102,7 @@ export class RenderTabs extends BeanRenderBase {
|
|
|
86
102
|
class="overflow-hidden text-ellipsis whitespace-nowrap"
|
|
87
103
|
style={{ display: 'inline-block', maxWidth: this.scope.config.tabItem.maxWidth }}
|
|
88
104
|
>
|
|
89
|
-
{
|
|
105
|
+
{pageContent}
|
|
90
106
|
</div>
|
|
91
107
|
<ZIcon
|
|
92
108
|
class="tab-close hidden hover:bg-slate-400 rounded-sm"
|
|
@@ -6,9 +6,10 @@ import type {
|
|
|
6
6
|
RouterOptions,
|
|
7
7
|
} from '@cabloy/vue-router';
|
|
8
8
|
|
|
9
|
-
import { BeanBase,
|
|
9
|
+
import { BeanBase, Use } from 'zova';
|
|
10
10
|
import { Bean } from 'zova-module-a-bean';
|
|
11
11
|
|
|
12
|
+
import { RouterGuardDisposers } from '../lib/routerGuardDisposers.js';
|
|
12
13
|
import { BeanRouterViewBase } from '../lib/routerViewBase.js';
|
|
13
14
|
import { ModelPageData } from '../model/pageData.js';
|
|
14
15
|
import { IPageMeta } from '../types/pageMeta.js';
|
|
@@ -30,7 +31,7 @@ export interface BeanRouter extends Omit<
|
|
|
30
31
|
@Bean()
|
|
31
32
|
export class BeanRouter extends BeanBase {
|
|
32
33
|
private _vueRouterApp: Router;
|
|
33
|
-
private
|
|
34
|
+
private _routerGuardDisposers = new RouterGuardDisposers();
|
|
34
35
|
private _routerViews: BeanRouterViewBase[] = [];
|
|
35
36
|
private _disposed = false;
|
|
36
37
|
|
|
@@ -65,16 +66,9 @@ export class BeanRouter extends BeanBase {
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
public dispose() {
|
|
68
|
-
this.__dispose__();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
protected __dispose__() {
|
|
72
69
|
if (this._disposed) return;
|
|
73
70
|
this._disposed = true;
|
|
74
|
-
|
|
75
|
-
fn();
|
|
76
|
-
}
|
|
77
|
-
this._eventRouterGuards = [];
|
|
71
|
+
this._routerGuardDisposers.dispose();
|
|
78
72
|
this._routerViews = [];
|
|
79
73
|
const router = this._vueRouterApp as Router & {
|
|
80
74
|
__prepareNavigation?: (to: unknown) => Promise<void> | void;
|
|
@@ -82,6 +76,11 @@ export class BeanRouter extends BeanBase {
|
|
|
82
76
|
if (router) router.__prepareNavigation = undefined;
|
|
83
77
|
}
|
|
84
78
|
|
|
79
|
+
protected __dispose__() {
|
|
80
|
+
// Router instances can be borrowed through page host providers. Their
|
|
81
|
+
// creator explicitly calls dispose() when the router itself is finished.
|
|
82
|
+
}
|
|
83
|
+
|
|
85
84
|
protected __get__(prop: string) {
|
|
86
85
|
// SymbolRouter first
|
|
87
86
|
const value = this._vueRouterApp?.[prop];
|
|
@@ -140,27 +139,19 @@ export class BeanRouter extends BeanBase {
|
|
|
140
139
|
}
|
|
141
140
|
|
|
142
141
|
beforeEach(guard: NavigationGuardWithThis<undefined>): () => void {
|
|
143
|
-
|
|
144
|
-
this._eventRouterGuards.push(fn);
|
|
145
|
-
return fn;
|
|
142
|
+
return this._routerGuardDisposers.add(this._vueRouterApp.beforeEach(guard));
|
|
146
143
|
}
|
|
147
144
|
|
|
148
145
|
beforeResolve(guard: NavigationGuardWithThis<undefined>): () => void {
|
|
149
|
-
|
|
150
|
-
this._eventRouterGuards.push(fn);
|
|
151
|
-
return fn;
|
|
146
|
+
return this._routerGuardDisposers.add(this._vueRouterApp.beforeResolve(guard));
|
|
152
147
|
}
|
|
153
148
|
|
|
154
149
|
afterEach(guard: NavigationHookAfter): () => void {
|
|
155
|
-
|
|
156
|
-
this._eventRouterGuards.push(fn);
|
|
157
|
-
return fn;
|
|
150
|
+
return this._routerGuardDisposers.add(this._vueRouterApp.afterEach(guard));
|
|
158
151
|
}
|
|
159
152
|
|
|
160
153
|
onError(handler: TypeErrorListener): () => void {
|
|
161
|
-
|
|
162
|
-
this._eventRouterGuards.push(fn);
|
|
163
|
-
return fn;
|
|
154
|
+
return this._routerGuardDisposers.add(this._vueRouterApp.onError(handler));
|
|
164
155
|
}
|
|
165
156
|
|
|
166
157
|
setPageMeta(route: RouteLocationNormalizedLoadedGeneric, pageMeta: IPageMeta) {
|
|
@@ -388,6 +388,7 @@ export class SysRouter extends BeanBase {
|
|
|
388
388
|
routeData = {
|
|
389
389
|
name: routeNameParent,
|
|
390
390
|
path,
|
|
391
|
+
...(route.alias ? { alias: route.alias } : {}),
|
|
391
392
|
component: this.createAsyncComponent(layout as any),
|
|
392
393
|
children: [{ ...route, name, path: '', component, meta }],
|
|
393
394
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export class RouterGuardDisposers {
|
|
2
|
+
private _disposers: (() => void)[] = [];
|
|
3
|
+
private _disposed = false;
|
|
4
|
+
|
|
5
|
+
add(disposer: () => void) {
|
|
6
|
+
this._disposers.push(disposer);
|
|
7
|
+
return disposer;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
dispose() {
|
|
11
|
+
if (this._disposed) return;
|
|
12
|
+
this._disposed = true;
|
|
13
|
+
const disposers = this._disposers;
|
|
14
|
+
this._disposers = [];
|
|
15
|
+
for (const disposer of disposers) {
|
|
16
|
+
disposer();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -12,9 +12,18 @@ export class ServiceRouterGuards extends BeanRouterGuardsBase {
|
|
|
12
12
|
const self = this;
|
|
13
13
|
router.beforeEach(async to => {
|
|
14
14
|
// match path
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
const aliasMatch = to.matched.find(item => item.aliasOf);
|
|
16
|
+
let match = aliasMatch;
|
|
17
|
+
if (aliasMatch) {
|
|
18
|
+
match = aliasMatch.aliasOf;
|
|
19
|
+
// A path-config alias can initially resolve to a componentless placeholder
|
|
20
|
+
// before its module registers the real layout route. Reload the same path
|
|
21
|
+
// after loading so the router resolves the newly registered component.
|
|
22
|
+
if (!match?.components || Object.keys(match.components).length === 0) {
|
|
23
|
+
const resLoadModule = await this._forceLoadModule(router, match?.name, match?.path);
|
|
24
|
+
if (resLoadModule && resLoadModule !== true) return resLoadModule;
|
|
25
|
+
return to.fullPath;
|
|
26
|
+
}
|
|
18
27
|
} else {
|
|
19
28
|
match = to.matched[to.matched.length - 1];
|
|
20
29
|
// prepareCheck
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { VNodeChild } from 'vue';
|
|
2
|
+
import type { IFormMeta } from 'zova-module-a-openapi';
|
|
3
|
+
|
|
4
|
+
import type { IRouteViewRouteItem, IRouteViewTabCurrent } from './routerView.js';
|
|
2
5
|
|
|
3
6
|
export interface IPageMeta {
|
|
4
7
|
pageTitle?: string;
|
|
5
8
|
pageDirty?: boolean;
|
|
6
9
|
formMeta?: IFormMeta;
|
|
10
|
+
onCustomRender?: (tabItem: IRouteViewRouteItem) => VNodeChild;
|
|
11
|
+
onCustomRenderIsolate?: (tabCurrent: IRouteViewTabCurrent) => VNodeChild;
|
|
7
12
|
}
|
|
@@ -14,6 +14,11 @@ export interface IRouteViewRouteItem extends IRouteViewRouteItemBase {
|
|
|
14
14
|
pageMeta?: IPageMeta;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export interface IRouteViewTabCurrent {
|
|
18
|
+
tabKey: string;
|
|
19
|
+
items?: IRouteViewRouteItem[];
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
export interface IRouteViewRouteMeta extends IRouteViewRouteItemBase {
|
|
18
23
|
tabKey: string;
|
|
19
24
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { RouterGuardDisposers } from '../../src/lib/routerGuardDisposers.js';
|
|
5
|
+
|
|
6
|
+
test('router guard disposers release every callback only once', () => {
|
|
7
|
+
const disposers = new RouterGuardDisposers();
|
|
8
|
+
const calls: string[] = [];
|
|
9
|
+
|
|
10
|
+
const beforeEach = () => calls.push('beforeEach');
|
|
11
|
+
const beforeResolve = () => calls.push('beforeResolve');
|
|
12
|
+
const afterEach = () => calls.push('afterEach');
|
|
13
|
+
const onError = () => calls.push('onError');
|
|
14
|
+
|
|
15
|
+
assert.equal(disposers.add(beforeEach), beforeEach);
|
|
16
|
+
assert.equal(disposers.add(beforeResolve), beforeResolve);
|
|
17
|
+
assert.equal(disposers.add(afterEach), afterEach);
|
|
18
|
+
assert.equal(disposers.add(onError), onError);
|
|
19
|
+
|
|
20
|
+
disposers.dispose();
|
|
21
|
+
disposers.dispose();
|
|
22
|
+
|
|
23
|
+
assert.deepEqual(calls, ['beforeEach', 'beforeResolve', 'afterEach', 'onError']);
|
|
24
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova-module-a-zova",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.107",
|
|
4
4
|
"gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
|
|
5
5
|
"description": "zova",
|
|
6
6
|
"keywords": [
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@cabloy/word-utils": "^2.1.14",
|
|
44
44
|
"defu": "^6.1.7",
|
|
45
45
|
"luxon": "^3.7.2",
|
|
46
|
-
"zova-jsx": "^1.1.
|
|
46
|
+
"zova-jsx": "^1.1.91"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@cabloy/cli": "^3.1.29",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova-suite-a-zova",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.164",
|
|
4
4
|
"gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
|
|
5
5
|
"description": "zova",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"zova-module-a-meta": "^5.1.22",
|
|
23
23
|
"zova-module-a-model": "^5.1.37",
|
|
24
24
|
"zova-module-a-openapi": "^5.1.54",
|
|
25
|
-
"zova-module-a-router": "^5.1.
|
|
25
|
+
"zova-module-a-router": "^5.1.40",
|
|
26
26
|
"zova-module-a-routerstack": "^5.1.29",
|
|
27
27
|
"zova-module-a-routertabs": "^5.1.34",
|
|
28
28
|
"zova-module-a-ssr": "^5.1.40",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"zova-module-a-style": "^5.1.35",
|
|
32
32
|
"zova-module-a-table": "^5.1.43",
|
|
33
33
|
"zova-module-a-zod": "^5.1.37",
|
|
34
|
-
"zova-module-a-zova": "^5.1.
|
|
34
|
+
"zova-module-a-zova": "^5.1.107"
|
|
35
35
|
},
|
|
36
36
|
"title": "a-zova"
|
|
37
37
|
}
|