@pactor-app/app 1.46.0 → 1.48.0
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/README.md +3 -1
- package/dist/index.cjs +84 -28
- package/dist/index.d.cts +24 -3
- package/dist/index.d.ts +24 -3
- package/dist/index.js +83 -28
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -124,6 +124,8 @@ import { SitemapApp } from '@pactor-app/app';
|
|
|
124
124
|
|
|
125
125
|
内置能力(无需自行拼装):多源拉取合并(冲突后源覆盖前源)、`menus × pages` 继承(`path` / `title` 缺省自引用页面)、MenuRegistry 路由解析(含 `:param`)、访问守卫(缺省匿名守卫:非 `public` 且无 user → `navigate(auth.loginPath)`,`guard` prop 可替换为角色鉴权等策略)、DSL 布局渲染(`PageOutlet` 页面出口、`SitemapMenu` 选中态与 `activeMenu` 高亮)、页面内联 / 懒加载(`id@version` 缓存,`pageEndpoint` 可配)、`session.context` 服务桥(DSL 经 `${data.session.*}` 读 user / app / menus)、缺省动作(`app.reload` 整页重载、`service.call` 服务调用,宿主 `actions` 同名覆盖)、布局常驻(同布局名切换导航仅内容区重建)。布局名解析链:`menu.layout` → 顶层 `layout` → `layouts` 首键。
|
|
126
126
|
|
|
127
|
+
布局可声明 `state` / `lifecycle`(布局运行时常驻,state 跨页面保持);`LayoutDsl.parent` 引用另一布局形成**嵌套链**——如 `agent.parent = 'admin'` 时渲染为 `admin`(其 `PageOutlet` 渲染 `agent` 布局,`agent` 的 `PageOutlet` 渲染页面)。每级布局是独立运行时(独立 state / lifecycle / 弹层宿主),导航时链共享前缀的外层布局保持挂载(一级外壳不重建),仅分叉层重建;`parent` 悬空或成环时链截断兜底。隐藏页(不进菜单的路由页)用菜单条目的 `activeMenu` 指向一级菜单 id,访问时外层导航菜单保持对应选中态。
|
|
128
|
+
|
|
127
129
|
| Props | 说明 |
|
|
128
130
|
| --- | --- |
|
|
129
131
|
| `sources`(必填) | sitemap 接口 URL 列表(相对路径以 `baseUrl` 补全) |
|
|
@@ -139,7 +141,7 @@ import { SitemapApp } from '@pactor-app/app';
|
|
|
139
141
|
| `sessionService` | 会话桥服务名(缺省 `session.context`) |
|
|
140
142
|
| `loading` / `renderError` / `renderNotFound` | 状态视图定制 |
|
|
141
143
|
|
|
142
|
-
配套导出(高级用法):`fetchSitemap`、`createSitemapLoader`、`mergeSitemapMenus`、`toSitemapNavItems`、`resolveSitemapRoute`、`defaultSitemapGuard`、`defaultSitemapActions`、`createHttpHooks`、`withHttpHooks`、`createSitemapSessionServices` 与类型 `SitemapResource` / `SitemapMenuItem` / `SitemapPageEntry` / `LayoutDsl` / `SitemapRouteMatch` / `SitemapRouteResolution` / `SitemapGuard` / `SitemapSessionContext`。完整示例工程见 `starters/next-app-starter`(宿主路由模式)与 `starters/spa-starter`(纯前端内置路由模式)。
|
|
144
|
+
配套导出(高级用法):`fetchSitemap`、`createSitemapLoader`、`mergeSitemapMenus`、`toSitemapNavItems`、`resolveSitemapRoute`、`resolveLayoutChain`、`defaultSitemapGuard`、`defaultSitemapActions`、`createHttpHooks`、`withHttpHooks`、`createSitemapSessionServices` 与类型 `SitemapResource` / `SitemapMenuItem` / `SitemapPageEntry` / `LayoutDsl` / `SitemapRouteMatch` / `SitemapRouteResolution` / `SitemapGuard` / `SitemapSessionContext`。完整示例工程见 `starters/next-app-starter`(宿主路由模式)与 `starters/spa-starter`(纯前端内置路由模式)。
|
|
143
145
|
|
|
144
146
|
## 文档
|
|
145
147
|
|
package/dist/index.cjs
CHANGED
|
@@ -37,6 +37,7 @@ __export(index_exports, {
|
|
|
37
37
|
defaultSitemapGuard: () => defaultSitemapGuard,
|
|
38
38
|
fetchSitemap: () => fetchSitemap,
|
|
39
39
|
mergeSitemapMenus: () => mergeSitemapMenus,
|
|
40
|
+
resolveLayoutChain: () => resolveLayoutChain,
|
|
40
41
|
resolveSitemapRoute: () => resolveSitemapRoute,
|
|
41
42
|
toSitemapNavItems: () => toSitemapNavItems,
|
|
42
43
|
withHttpHooks: () => withHttpHooks,
|
|
@@ -1291,11 +1292,26 @@ function resolveSitemapRoute(sitemap, registry, pathname) {
|
|
|
1291
1292
|
route: {
|
|
1292
1293
|
pageId: menu.pageId,
|
|
1293
1294
|
layoutName,
|
|
1295
|
+
layoutChain: resolveLayoutChain(sitemap, layoutName),
|
|
1294
1296
|
highlightKey: highlightItem?.path ?? highlightId,
|
|
1295
1297
|
params: match.params
|
|
1296
1298
|
}
|
|
1297
1299
|
};
|
|
1298
1300
|
}
|
|
1301
|
+
function resolveLayoutChain(sitemap, layoutName) {
|
|
1302
|
+
const chain = [layoutName];
|
|
1303
|
+
const seen = new Set(chain);
|
|
1304
|
+
let current = layoutName;
|
|
1305
|
+
for (; ; ) {
|
|
1306
|
+
const parent = sitemap.layouts[current]?.parent;
|
|
1307
|
+
if (parent === void 0 || seen.has(parent) || sitemap.layouts[parent] === void 0) {
|
|
1308
|
+
return chain;
|
|
1309
|
+
}
|
|
1310
|
+
chain.unshift(parent);
|
|
1311
|
+
seen.add(parent);
|
|
1312
|
+
current = parent;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1299
1315
|
var defaultSitemapGuard = ({
|
|
1300
1316
|
sitemap,
|
|
1301
1317
|
pathname,
|
|
@@ -1414,24 +1430,24 @@ function SitemapAppInner(props) {
|
|
|
1414
1430
|
if (resolution.route === null) {
|
|
1415
1431
|
return props.renderNotFound !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: props.renderNotFound(pathname) }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Centered, { children: t("pactor.sitemap.notFound", { path: pathname }) });
|
|
1416
1432
|
}
|
|
1417
|
-
const { pageId,
|
|
1433
|
+
const { pageId, layoutChain, highlightKey, params } = resolution.route;
|
|
1418
1434
|
return (
|
|
1419
|
-
// key
|
|
1420
|
-
//
|
|
1435
|
+
// key 只随最外层布局名变化:布局链共享前缀时外层布局常驻,
|
|
1436
|
+
// 仅分叉的内层布局与 PageOutlet 内页面随路由重建
|
|
1421
1437
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NavHighlightContext.Provider, { value: highlightKey, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1422
1438
|
SitemapLayout,
|
|
1423
1439
|
{
|
|
1424
1440
|
sitemap,
|
|
1425
1441
|
registry,
|
|
1426
1442
|
pageId,
|
|
1427
|
-
|
|
1443
|
+
layoutChain,
|
|
1428
1444
|
pathname,
|
|
1429
1445
|
query,
|
|
1430
1446
|
navigate,
|
|
1431
1447
|
routeParams: params,
|
|
1432
1448
|
props
|
|
1433
1449
|
},
|
|
1434
|
-
|
|
1450
|
+
layoutChain[0]
|
|
1435
1451
|
) })
|
|
1436
1452
|
);
|
|
1437
1453
|
}
|
|
@@ -1439,14 +1455,13 @@ function SitemapLayout({
|
|
|
1439
1455
|
sitemap,
|
|
1440
1456
|
registry,
|
|
1441
1457
|
pageId,
|
|
1442
|
-
|
|
1458
|
+
layoutChain,
|
|
1443
1459
|
pathname,
|
|
1444
1460
|
query,
|
|
1445
1461
|
navigate: hostNavigate,
|
|
1446
1462
|
routeParams,
|
|
1447
1463
|
props
|
|
1448
1464
|
}) {
|
|
1449
|
-
const { t } = (0, import_runtime6.useI18n)();
|
|
1450
1465
|
const sessionService = props.sessionService ?? "session.context";
|
|
1451
1466
|
const session = (0, import_react8.useMemo)(
|
|
1452
1467
|
() => ({
|
|
@@ -1494,23 +1509,6 @@ function SitemapLayout({
|
|
|
1494
1509
|
() => ({ ...defaultSitemapActions, ...props.actions }),
|
|
1495
1510
|
[props.actions]
|
|
1496
1511
|
);
|
|
1497
|
-
const layoutDsl = sitemap.layouts[layoutName];
|
|
1498
|
-
const layoutPage = (0, import_react8.useMemo)(() => {
|
|
1499
|
-
if (layoutDsl === void 0) {
|
|
1500
|
-
return null;
|
|
1501
|
-
}
|
|
1502
|
-
return {
|
|
1503
|
-
dsl: layoutDsl.dsl,
|
|
1504
|
-
page: { id: `layout:${layoutName}`, title: layoutName },
|
|
1505
|
-
dataSources: {
|
|
1506
|
-
session: { type: "service", service: sessionService, autoLoad: true }
|
|
1507
|
-
},
|
|
1508
|
-
body: layoutDsl.body
|
|
1509
|
-
};
|
|
1510
|
-
}, [layoutDsl, layoutName, sessionService]);
|
|
1511
|
-
if (layoutDsl === void 0 || layoutPage === null) {
|
|
1512
|
-
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Centered, { children: t("pactor.sitemap.layoutMissing", { name: layoutName }) });
|
|
1513
|
-
}
|
|
1514
1512
|
const messagesSignature = JSON.stringify(props.messages ?? {});
|
|
1515
1513
|
const pageFactory = (0, import_react8.useMemo)(
|
|
1516
1514
|
() => createPactorPage({
|
|
@@ -1541,6 +1539,9 @@ function SitemapLayout({
|
|
|
1541
1539
|
);
|
|
1542
1540
|
const layoutFactory = (0, import_react8.useMemo)(
|
|
1543
1541
|
() => createPactorPage({
|
|
1542
|
+
// loader 注入使布局运行时的 OverlayHost 支持 page 类型弹层
|
|
1543
|
+
//(布局内按钮经 overlay.open 打开懒加载表单页,如创建实例)
|
|
1544
|
+
loader,
|
|
1544
1545
|
services,
|
|
1545
1546
|
actions,
|
|
1546
1547
|
navigate,
|
|
@@ -1552,6 +1553,7 @@ function SitemapLayout({
|
|
|
1552
1553
|
}),
|
|
1553
1554
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1554
1555
|
[
|
|
1556
|
+
loader,
|
|
1555
1557
|
services,
|
|
1556
1558
|
actions,
|
|
1557
1559
|
navigate,
|
|
@@ -1563,11 +1565,15 @@ function SitemapLayout({
|
|
|
1563
1565
|
]
|
|
1564
1566
|
);
|
|
1565
1567
|
const Page = pageFactory;
|
|
1566
|
-
const LayoutPage = layoutFactory;
|
|
1567
1568
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1568
|
-
|
|
1569
|
+
LayoutLevel,
|
|
1569
1570
|
{
|
|
1570
|
-
|
|
1571
|
+
sitemap,
|
|
1572
|
+
chain: layoutChain,
|
|
1573
|
+
index: 0,
|
|
1574
|
+
sessionService,
|
|
1575
|
+
layoutFactory,
|
|
1576
|
+
outlet: (
|
|
1571
1577
|
// 页面实例:key 随 pageId + pathname 变化(route 参数变化需重建,
|
|
1572
1578
|
// PactorPage 的 route prop 不参与运行时重建)——布局保持挂载
|
|
1573
1579
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
@@ -1578,8 +1584,57 @@ function SitemapLayout({
|
|
|
1578
1584
|
},
|
|
1579
1585
|
`${pageId}@${pathname}`
|
|
1580
1586
|
)
|
|
1587
|
+
)
|
|
1588
|
+
}
|
|
1589
|
+
);
|
|
1590
|
+
}
|
|
1591
|
+
function LayoutLevel({
|
|
1592
|
+
sitemap,
|
|
1593
|
+
chain,
|
|
1594
|
+
index,
|
|
1595
|
+
sessionService,
|
|
1596
|
+
layoutFactory,
|
|
1597
|
+
outlet
|
|
1598
|
+
}) {
|
|
1599
|
+
const { t } = (0, import_runtime6.useI18n)();
|
|
1600
|
+
const layoutName = chain[index];
|
|
1601
|
+
const layoutDsl = sitemap.layouts[layoutName];
|
|
1602
|
+
const layoutPage = (0, import_react8.useMemo)(() => {
|
|
1603
|
+
if (layoutDsl === void 0) {
|
|
1604
|
+
return null;
|
|
1605
|
+
}
|
|
1606
|
+
return {
|
|
1607
|
+
dsl: layoutDsl.dsl,
|
|
1608
|
+
page: { id: `layout:${layoutName}`, title: layoutName },
|
|
1609
|
+
// 布局级 state / lifecycle 透传(布局运行时常驻,state 跨页面保持)
|
|
1610
|
+
state: layoutDsl.state,
|
|
1611
|
+
lifecycle: layoutDsl.lifecycle,
|
|
1612
|
+
dataSources: {
|
|
1613
|
+
session: { type: "service", service: sessionService, autoLoad: true }
|
|
1614
|
+
},
|
|
1615
|
+
body: layoutDsl.body
|
|
1616
|
+
};
|
|
1617
|
+
}, [layoutDsl, layoutName, sessionService]);
|
|
1618
|
+
if (layoutDsl === void 0 || layoutPage === null) {
|
|
1619
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Centered, { children: t("pactor.sitemap.layoutMissing", { name: layoutName }) });
|
|
1620
|
+
}
|
|
1621
|
+
const LayoutPage = layoutFactory;
|
|
1622
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1623
|
+
OutletContext.Provider,
|
|
1624
|
+
{
|
|
1625
|
+
value: index === chain.length - 1 ? outlet : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1626
|
+
LayoutLevel,
|
|
1627
|
+
{
|
|
1628
|
+
sitemap,
|
|
1629
|
+
chain,
|
|
1630
|
+
index: index + 1,
|
|
1631
|
+
sessionService,
|
|
1632
|
+
layoutFactory,
|
|
1633
|
+
outlet
|
|
1634
|
+
},
|
|
1635
|
+
chain[index + 1]
|
|
1581
1636
|
),
|
|
1582
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LayoutPage, { pageDsl: layoutPage })
|
|
1637
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LayoutPage, { pageDsl: layoutPage }, layoutName)
|
|
1583
1638
|
}
|
|
1584
1639
|
);
|
|
1585
1640
|
}
|
|
@@ -1619,6 +1674,7 @@ function Centered({ children }) {
|
|
|
1619
1674
|
defaultSitemapGuard,
|
|
1620
1675
|
fetchSitemap,
|
|
1621
1676
|
mergeSitemapMenus,
|
|
1677
|
+
resolveLayoutChain,
|
|
1622
1678
|
resolveSitemapRoute,
|
|
1623
1679
|
toSitemapNavItems,
|
|
1624
1680
|
withHttpHooks,
|
package/dist/index.d.cts
CHANGED
|
@@ -432,9 +432,20 @@ declare const defaultSitemapActions: Record<string, ActionHandler>;
|
|
|
432
432
|
* 服务端按会话裁剪内容(匿名 / 角色),对象本身不为角色建模。
|
|
433
433
|
*/
|
|
434
434
|
/** 布局定义:DSL 描述外壳结构,PageOutlet 组件为页面内容出口。
|
|
435
|
-
* 不套 PageDsl 的 page 包装——id 即 layouts
|
|
435
|
+
* 不套 PageDsl 的 page 包装——id 即 layouts 的键,无重复声明。
|
|
436
|
+
* state / lifecycle 可选:布局运行时在相同布局名导航间常驻(key 不变
|
|
437
|
+
* 不重建),其中的 state 跨页面保持(如侧栏选中 / 折叠态),onInit
|
|
438
|
+
* 随布局运行时创建执行一次。
|
|
439
|
+
* parent 可选:引用另一布局作为外层外壳,形成布局嵌套链(如
|
|
440
|
+
* agent.parent = admin → 渲染 admin(PageOutlet=agent(PageOutlet=页面)))。
|
|
441
|
+
* 每级布局是独立运行时;跨页面导航时共享前缀的外层布局保持挂载,
|
|
442
|
+
* 其 state 不受内层布局 / 页面切换影响 */
|
|
436
443
|
interface LayoutDsl {
|
|
437
444
|
dsl: string;
|
|
445
|
+
/** 外层布局名,引用 layouts 的键;缺省无外层(自身为最外层) */
|
|
446
|
+
parent?: string;
|
|
447
|
+
state?: PageDsl['state'];
|
|
448
|
+
lifecycle?: PageDsl['lifecycle'];
|
|
438
449
|
body: DslNode;
|
|
439
450
|
}
|
|
440
451
|
/** 页面定义:id 主键;pageDsl 缺省 → loadPage(id) 懒加载 */
|
|
@@ -472,7 +483,10 @@ interface SitemapResource extends BootstrapResource {
|
|
|
472
483
|
/** 可渲染路由匹配结果(resolveSitemapRoute 产物) */
|
|
473
484
|
interface SitemapRouteMatch {
|
|
474
485
|
pageId: string;
|
|
486
|
+
/** 页面直接布局名(布局链的最内层) */
|
|
475
487
|
layoutName: string;
|
|
488
|
+
/** 布局嵌套链(最外层在前):沿 LayoutDsl.parent 上溯,含自身 */
|
|
489
|
+
layoutChain: string[];
|
|
476
490
|
/** 菜单高亮 key:activeMenu 指向的菜单项 path(缺省自身 path) */
|
|
477
491
|
highlightKey: string;
|
|
478
492
|
params: Record<string, string>;
|
|
@@ -528,6 +542,11 @@ interface SitemapRouteResolution {
|
|
|
528
542
|
* key 与 toSitemapNavItems 一致(path ?? id)。
|
|
529
543
|
*/
|
|
530
544
|
declare function resolveSitemapRoute(sitemap: SitemapResource, registry: MenuRegistry, pathname: string): SitemapRouteResolution;
|
|
545
|
+
/**
|
|
546
|
+
* 布局嵌套链(最外层在前):自布局名沿 LayoutDsl.parent 上溯至无外层。
|
|
547
|
+
* parent 悬空(未定义的布局名)或成环时截断,保证有限终止。
|
|
548
|
+
*/
|
|
549
|
+
declare function resolveLayoutChain(sitemap: SitemapResource, layoutName: string): string[];
|
|
531
550
|
/** 守卫判定:放行或重定向 */
|
|
532
551
|
type SitemapGuardDecision = {
|
|
533
552
|
allow: true;
|
|
@@ -574,7 +593,9 @@ declare const defaultSitemapGuard: SitemapGuard;
|
|
|
574
593
|
* 页面内联 / 懒加载(id@version 缓存)、session.context 服务桥
|
|
575
594
|
* (${data.session.*})、缺省动作(app.reload / service.call)。
|
|
576
595
|
*
|
|
577
|
-
*
|
|
596
|
+
* 布局常驻与嵌套:LayoutDsl.parent 引用外层布局形成嵌套链(最外层在
|
|
597
|
+
* 前),每级布局为独立运行时;切换导航时链共享前缀的外层布局保持
|
|
598
|
+
* 挂载(state 跨页面保持),仅分叉层与 PageOutlet 内页面随路由重建。
|
|
578
599
|
*/
|
|
579
600
|
interface SitemapAppProps {
|
|
580
601
|
/** sitemap 接口 URL 列表(多源合并,冲突后源覆盖前源);相对路径以 baseUrl 补全 */
|
|
@@ -686,4 +707,4 @@ interface SitemapSessionContext {
|
|
|
686
707
|
/** 注册会话桥服务(缺省名 session.context,与 loader 注入的 dataSource 对应) */
|
|
687
708
|
declare function createSitemapSessionServices(session: SitemapSessionContext, serviceName?: string): Record<string, ServiceHandler>;
|
|
688
709
|
|
|
689
|
-
export { Application, type ApplicationProps, type AuthTokenProvider, type CreateHttpHooksOptions, DslPageLoader, type DslPageLoaderProps, type FetchSitemapOptions, type LayoutDsl, type LocaleStorage, type PactorAppSession, type PactorApplication, type PactorApplicationHandle, type PactorApplicationOptions, type PactorHttpHooks, PactorPage, type PactorPageOptions, type PactorPageProps, type PactorRouterOptions, PageErrorBoundary, ReactRouterBinding, SitemapApp, type SitemapAppProps, type SitemapGuard, type SitemapGuardContext, type SitemapGuardDecision, type SitemapLoaderOptions, type SitemapMenuItem, type SitemapNavItem, type SitemapPageEntry, type SitemapResource, type SitemapRouteMatch, type SitemapRouteResolution, type SitemapSessionContext, createHttpHooks, createLocalStorageLocaleStorage, createPactorApplication, createPactorPage, createRuntimeHttpAdapter, createSitemapLoader, createSitemapSessionServices, defaultSitemapActions, defaultSitemapGuard, fetchSitemap, mergeSitemapMenus, resolveSitemapRoute, toSitemapNavItems, withHttpHooks, wrapHttpAdapter };
|
|
710
|
+
export { Application, type ApplicationProps, type AuthTokenProvider, type CreateHttpHooksOptions, DslPageLoader, type DslPageLoaderProps, type FetchSitemapOptions, type LayoutDsl, type LocaleStorage, type PactorAppSession, type PactorApplication, type PactorApplicationHandle, type PactorApplicationOptions, type PactorHttpHooks, PactorPage, type PactorPageOptions, type PactorPageProps, type PactorRouterOptions, PageErrorBoundary, ReactRouterBinding, SitemapApp, type SitemapAppProps, type SitemapGuard, type SitemapGuardContext, type SitemapGuardDecision, type SitemapLoaderOptions, type SitemapMenuItem, type SitemapNavItem, type SitemapPageEntry, type SitemapResource, type SitemapRouteMatch, type SitemapRouteResolution, type SitemapSessionContext, createHttpHooks, createLocalStorageLocaleStorage, createPactorApplication, createPactorPage, createRuntimeHttpAdapter, createSitemapLoader, createSitemapSessionServices, defaultSitemapActions, defaultSitemapGuard, fetchSitemap, mergeSitemapMenus, resolveLayoutChain, resolveSitemapRoute, toSitemapNavItems, withHttpHooks, wrapHttpAdapter };
|
package/dist/index.d.ts
CHANGED
|
@@ -432,9 +432,20 @@ declare const defaultSitemapActions: Record<string, ActionHandler>;
|
|
|
432
432
|
* 服务端按会话裁剪内容(匿名 / 角色),对象本身不为角色建模。
|
|
433
433
|
*/
|
|
434
434
|
/** 布局定义:DSL 描述外壳结构,PageOutlet 组件为页面内容出口。
|
|
435
|
-
* 不套 PageDsl 的 page 包装——id 即 layouts
|
|
435
|
+
* 不套 PageDsl 的 page 包装——id 即 layouts 的键,无重复声明。
|
|
436
|
+
* state / lifecycle 可选:布局运行时在相同布局名导航间常驻(key 不变
|
|
437
|
+
* 不重建),其中的 state 跨页面保持(如侧栏选中 / 折叠态),onInit
|
|
438
|
+
* 随布局运行时创建执行一次。
|
|
439
|
+
* parent 可选:引用另一布局作为外层外壳,形成布局嵌套链(如
|
|
440
|
+
* agent.parent = admin → 渲染 admin(PageOutlet=agent(PageOutlet=页面)))。
|
|
441
|
+
* 每级布局是独立运行时;跨页面导航时共享前缀的外层布局保持挂载,
|
|
442
|
+
* 其 state 不受内层布局 / 页面切换影响 */
|
|
436
443
|
interface LayoutDsl {
|
|
437
444
|
dsl: string;
|
|
445
|
+
/** 外层布局名,引用 layouts 的键;缺省无外层(自身为最外层) */
|
|
446
|
+
parent?: string;
|
|
447
|
+
state?: PageDsl['state'];
|
|
448
|
+
lifecycle?: PageDsl['lifecycle'];
|
|
438
449
|
body: DslNode;
|
|
439
450
|
}
|
|
440
451
|
/** 页面定义:id 主键;pageDsl 缺省 → loadPage(id) 懒加载 */
|
|
@@ -472,7 +483,10 @@ interface SitemapResource extends BootstrapResource {
|
|
|
472
483
|
/** 可渲染路由匹配结果(resolveSitemapRoute 产物) */
|
|
473
484
|
interface SitemapRouteMatch {
|
|
474
485
|
pageId: string;
|
|
486
|
+
/** 页面直接布局名(布局链的最内层) */
|
|
475
487
|
layoutName: string;
|
|
488
|
+
/** 布局嵌套链(最外层在前):沿 LayoutDsl.parent 上溯,含自身 */
|
|
489
|
+
layoutChain: string[];
|
|
476
490
|
/** 菜单高亮 key:activeMenu 指向的菜单项 path(缺省自身 path) */
|
|
477
491
|
highlightKey: string;
|
|
478
492
|
params: Record<string, string>;
|
|
@@ -528,6 +542,11 @@ interface SitemapRouteResolution {
|
|
|
528
542
|
* key 与 toSitemapNavItems 一致(path ?? id)。
|
|
529
543
|
*/
|
|
530
544
|
declare function resolveSitemapRoute(sitemap: SitemapResource, registry: MenuRegistry, pathname: string): SitemapRouteResolution;
|
|
545
|
+
/**
|
|
546
|
+
* 布局嵌套链(最外层在前):自布局名沿 LayoutDsl.parent 上溯至无外层。
|
|
547
|
+
* parent 悬空(未定义的布局名)或成环时截断,保证有限终止。
|
|
548
|
+
*/
|
|
549
|
+
declare function resolveLayoutChain(sitemap: SitemapResource, layoutName: string): string[];
|
|
531
550
|
/** 守卫判定:放行或重定向 */
|
|
532
551
|
type SitemapGuardDecision = {
|
|
533
552
|
allow: true;
|
|
@@ -574,7 +593,9 @@ declare const defaultSitemapGuard: SitemapGuard;
|
|
|
574
593
|
* 页面内联 / 懒加载(id@version 缓存)、session.context 服务桥
|
|
575
594
|
* (${data.session.*})、缺省动作(app.reload / service.call)。
|
|
576
595
|
*
|
|
577
|
-
*
|
|
596
|
+
* 布局常驻与嵌套:LayoutDsl.parent 引用外层布局形成嵌套链(最外层在
|
|
597
|
+
* 前),每级布局为独立运行时;切换导航时链共享前缀的外层布局保持
|
|
598
|
+
* 挂载(state 跨页面保持),仅分叉层与 PageOutlet 内页面随路由重建。
|
|
578
599
|
*/
|
|
579
600
|
interface SitemapAppProps {
|
|
580
601
|
/** sitemap 接口 URL 列表(多源合并,冲突后源覆盖前源);相对路径以 baseUrl 补全 */
|
|
@@ -686,4 +707,4 @@ interface SitemapSessionContext {
|
|
|
686
707
|
/** 注册会话桥服务(缺省名 session.context,与 loader 注入的 dataSource 对应) */
|
|
687
708
|
declare function createSitemapSessionServices(session: SitemapSessionContext, serviceName?: string): Record<string, ServiceHandler>;
|
|
688
709
|
|
|
689
|
-
export { Application, type ApplicationProps, type AuthTokenProvider, type CreateHttpHooksOptions, DslPageLoader, type DslPageLoaderProps, type FetchSitemapOptions, type LayoutDsl, type LocaleStorage, type PactorAppSession, type PactorApplication, type PactorApplicationHandle, type PactorApplicationOptions, type PactorHttpHooks, PactorPage, type PactorPageOptions, type PactorPageProps, type PactorRouterOptions, PageErrorBoundary, ReactRouterBinding, SitemapApp, type SitemapAppProps, type SitemapGuard, type SitemapGuardContext, type SitemapGuardDecision, type SitemapLoaderOptions, type SitemapMenuItem, type SitemapNavItem, type SitemapPageEntry, type SitemapResource, type SitemapRouteMatch, type SitemapRouteResolution, type SitemapSessionContext, createHttpHooks, createLocalStorageLocaleStorage, createPactorApplication, createPactorPage, createRuntimeHttpAdapter, createSitemapLoader, createSitemapSessionServices, defaultSitemapActions, defaultSitemapGuard, fetchSitemap, mergeSitemapMenus, resolveSitemapRoute, toSitemapNavItems, withHttpHooks, wrapHttpAdapter };
|
|
710
|
+
export { Application, type ApplicationProps, type AuthTokenProvider, type CreateHttpHooksOptions, DslPageLoader, type DslPageLoaderProps, type FetchSitemapOptions, type LayoutDsl, type LocaleStorage, type PactorAppSession, type PactorApplication, type PactorApplicationHandle, type PactorApplicationOptions, type PactorHttpHooks, PactorPage, type PactorPageOptions, type PactorPageProps, type PactorRouterOptions, PageErrorBoundary, ReactRouterBinding, SitemapApp, type SitemapAppProps, type SitemapGuard, type SitemapGuardContext, type SitemapGuardDecision, type SitemapLoaderOptions, type SitemapMenuItem, type SitemapNavItem, type SitemapPageEntry, type SitemapResource, type SitemapRouteMatch, type SitemapRouteResolution, type SitemapSessionContext, createHttpHooks, createLocalStorageLocaleStorage, createPactorApplication, createPactorPage, createRuntimeHttpAdapter, createSitemapLoader, createSitemapSessionServices, defaultSitemapActions, defaultSitemapGuard, fetchSitemap, mergeSitemapMenus, resolveLayoutChain, resolveSitemapRoute, toSitemapNavItems, withHttpHooks, wrapHttpAdapter };
|
package/dist/index.js
CHANGED
|
@@ -1293,11 +1293,26 @@ function resolveSitemapRoute(sitemap, registry, pathname) {
|
|
|
1293
1293
|
route: {
|
|
1294
1294
|
pageId: menu.pageId,
|
|
1295
1295
|
layoutName,
|
|
1296
|
+
layoutChain: resolveLayoutChain(sitemap, layoutName),
|
|
1296
1297
|
highlightKey: highlightItem?.path ?? highlightId,
|
|
1297
1298
|
params: match.params
|
|
1298
1299
|
}
|
|
1299
1300
|
};
|
|
1300
1301
|
}
|
|
1302
|
+
function resolveLayoutChain(sitemap, layoutName) {
|
|
1303
|
+
const chain = [layoutName];
|
|
1304
|
+
const seen = new Set(chain);
|
|
1305
|
+
let current = layoutName;
|
|
1306
|
+
for (; ; ) {
|
|
1307
|
+
const parent = sitemap.layouts[current]?.parent;
|
|
1308
|
+
if (parent === void 0 || seen.has(parent) || sitemap.layouts[parent] === void 0) {
|
|
1309
|
+
return chain;
|
|
1310
|
+
}
|
|
1311
|
+
chain.unshift(parent);
|
|
1312
|
+
seen.add(parent);
|
|
1313
|
+
current = parent;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1301
1316
|
var defaultSitemapGuard = ({
|
|
1302
1317
|
sitemap,
|
|
1303
1318
|
pathname,
|
|
@@ -1416,24 +1431,24 @@ function SitemapAppInner(props) {
|
|
|
1416
1431
|
if (resolution.route === null) {
|
|
1417
1432
|
return props.renderNotFound !== void 0 ? /* @__PURE__ */ jsx6(Fragment, { children: props.renderNotFound(pathname) }) : /* @__PURE__ */ jsx6(Centered, { children: t("pactor.sitemap.notFound", { path: pathname }) });
|
|
1418
1433
|
}
|
|
1419
|
-
const { pageId,
|
|
1434
|
+
const { pageId, layoutChain, highlightKey, params } = resolution.route;
|
|
1420
1435
|
return (
|
|
1421
|
-
// key
|
|
1422
|
-
//
|
|
1436
|
+
// key 只随最外层布局名变化:布局链共享前缀时外层布局常驻,
|
|
1437
|
+
// 仅分叉的内层布局与 PageOutlet 内页面随路由重建
|
|
1423
1438
|
/* @__PURE__ */ jsx6(NavHighlightContext.Provider, { value: highlightKey, children: /* @__PURE__ */ jsx6(
|
|
1424
1439
|
SitemapLayout,
|
|
1425
1440
|
{
|
|
1426
1441
|
sitemap,
|
|
1427
1442
|
registry,
|
|
1428
1443
|
pageId,
|
|
1429
|
-
|
|
1444
|
+
layoutChain,
|
|
1430
1445
|
pathname,
|
|
1431
1446
|
query,
|
|
1432
1447
|
navigate,
|
|
1433
1448
|
routeParams: params,
|
|
1434
1449
|
props
|
|
1435
1450
|
},
|
|
1436
|
-
|
|
1451
|
+
layoutChain[0]
|
|
1437
1452
|
) })
|
|
1438
1453
|
);
|
|
1439
1454
|
}
|
|
@@ -1441,14 +1456,13 @@ function SitemapLayout({
|
|
|
1441
1456
|
sitemap,
|
|
1442
1457
|
registry,
|
|
1443
1458
|
pageId,
|
|
1444
|
-
|
|
1459
|
+
layoutChain,
|
|
1445
1460
|
pathname,
|
|
1446
1461
|
query,
|
|
1447
1462
|
navigate: hostNavigate,
|
|
1448
1463
|
routeParams,
|
|
1449
1464
|
props
|
|
1450
1465
|
}) {
|
|
1451
|
-
const { t } = useI18n2();
|
|
1452
1466
|
const sessionService = props.sessionService ?? "session.context";
|
|
1453
1467
|
const session = useMemo2(
|
|
1454
1468
|
() => ({
|
|
@@ -1496,23 +1510,6 @@ function SitemapLayout({
|
|
|
1496
1510
|
() => ({ ...defaultSitemapActions, ...props.actions }),
|
|
1497
1511
|
[props.actions]
|
|
1498
1512
|
);
|
|
1499
|
-
const layoutDsl = sitemap.layouts[layoutName];
|
|
1500
|
-
const layoutPage = useMemo2(() => {
|
|
1501
|
-
if (layoutDsl === void 0) {
|
|
1502
|
-
return null;
|
|
1503
|
-
}
|
|
1504
|
-
return {
|
|
1505
|
-
dsl: layoutDsl.dsl,
|
|
1506
|
-
page: { id: `layout:${layoutName}`, title: layoutName },
|
|
1507
|
-
dataSources: {
|
|
1508
|
-
session: { type: "service", service: sessionService, autoLoad: true }
|
|
1509
|
-
},
|
|
1510
|
-
body: layoutDsl.body
|
|
1511
|
-
};
|
|
1512
|
-
}, [layoutDsl, layoutName, sessionService]);
|
|
1513
|
-
if (layoutDsl === void 0 || layoutPage === null) {
|
|
1514
|
-
return /* @__PURE__ */ jsx6(Centered, { children: t("pactor.sitemap.layoutMissing", { name: layoutName }) });
|
|
1515
|
-
}
|
|
1516
1513
|
const messagesSignature = JSON.stringify(props.messages ?? {});
|
|
1517
1514
|
const pageFactory = useMemo2(
|
|
1518
1515
|
() => createPactorPage({
|
|
@@ -1543,6 +1540,9 @@ function SitemapLayout({
|
|
|
1543
1540
|
);
|
|
1544
1541
|
const layoutFactory = useMemo2(
|
|
1545
1542
|
() => createPactorPage({
|
|
1543
|
+
// loader 注入使布局运行时的 OverlayHost 支持 page 类型弹层
|
|
1544
|
+
//(布局内按钮经 overlay.open 打开懒加载表单页,如创建实例)
|
|
1545
|
+
loader,
|
|
1546
1546
|
services,
|
|
1547
1547
|
actions,
|
|
1548
1548
|
navigate,
|
|
@@ -1554,6 +1554,7 @@ function SitemapLayout({
|
|
|
1554
1554
|
}),
|
|
1555
1555
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1556
1556
|
[
|
|
1557
|
+
loader,
|
|
1557
1558
|
services,
|
|
1558
1559
|
actions,
|
|
1559
1560
|
navigate,
|
|
@@ -1565,11 +1566,15 @@ function SitemapLayout({
|
|
|
1565
1566
|
]
|
|
1566
1567
|
);
|
|
1567
1568
|
const Page = pageFactory;
|
|
1568
|
-
const LayoutPage = layoutFactory;
|
|
1569
1569
|
return /* @__PURE__ */ jsx6(
|
|
1570
|
-
|
|
1570
|
+
LayoutLevel,
|
|
1571
1571
|
{
|
|
1572
|
-
|
|
1572
|
+
sitemap,
|
|
1573
|
+
chain: layoutChain,
|
|
1574
|
+
index: 0,
|
|
1575
|
+
sessionService,
|
|
1576
|
+
layoutFactory,
|
|
1577
|
+
outlet: (
|
|
1573
1578
|
// 页面实例:key 随 pageId + pathname 变化(route 参数变化需重建,
|
|
1574
1579
|
// PactorPage 的 route prop 不参与运行时重建)——布局保持挂载
|
|
1575
1580
|
/* @__PURE__ */ jsx6(
|
|
@@ -1580,8 +1585,57 @@ function SitemapLayout({
|
|
|
1580
1585
|
},
|
|
1581
1586
|
`${pageId}@${pathname}`
|
|
1582
1587
|
)
|
|
1588
|
+
)
|
|
1589
|
+
}
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
function LayoutLevel({
|
|
1593
|
+
sitemap,
|
|
1594
|
+
chain,
|
|
1595
|
+
index,
|
|
1596
|
+
sessionService,
|
|
1597
|
+
layoutFactory,
|
|
1598
|
+
outlet
|
|
1599
|
+
}) {
|
|
1600
|
+
const { t } = useI18n2();
|
|
1601
|
+
const layoutName = chain[index];
|
|
1602
|
+
const layoutDsl = sitemap.layouts[layoutName];
|
|
1603
|
+
const layoutPage = useMemo2(() => {
|
|
1604
|
+
if (layoutDsl === void 0) {
|
|
1605
|
+
return null;
|
|
1606
|
+
}
|
|
1607
|
+
return {
|
|
1608
|
+
dsl: layoutDsl.dsl,
|
|
1609
|
+
page: { id: `layout:${layoutName}`, title: layoutName },
|
|
1610
|
+
// 布局级 state / lifecycle 透传(布局运行时常驻,state 跨页面保持)
|
|
1611
|
+
state: layoutDsl.state,
|
|
1612
|
+
lifecycle: layoutDsl.lifecycle,
|
|
1613
|
+
dataSources: {
|
|
1614
|
+
session: { type: "service", service: sessionService, autoLoad: true }
|
|
1615
|
+
},
|
|
1616
|
+
body: layoutDsl.body
|
|
1617
|
+
};
|
|
1618
|
+
}, [layoutDsl, layoutName, sessionService]);
|
|
1619
|
+
if (layoutDsl === void 0 || layoutPage === null) {
|
|
1620
|
+
return /* @__PURE__ */ jsx6(Centered, { children: t("pactor.sitemap.layoutMissing", { name: layoutName }) });
|
|
1621
|
+
}
|
|
1622
|
+
const LayoutPage = layoutFactory;
|
|
1623
|
+
return /* @__PURE__ */ jsx6(
|
|
1624
|
+
OutletContext.Provider,
|
|
1625
|
+
{
|
|
1626
|
+
value: index === chain.length - 1 ? outlet : /* @__PURE__ */ jsx6(
|
|
1627
|
+
LayoutLevel,
|
|
1628
|
+
{
|
|
1629
|
+
sitemap,
|
|
1630
|
+
chain,
|
|
1631
|
+
index: index + 1,
|
|
1632
|
+
sessionService,
|
|
1633
|
+
layoutFactory,
|
|
1634
|
+
outlet
|
|
1635
|
+
},
|
|
1636
|
+
chain[index + 1]
|
|
1583
1637
|
),
|
|
1584
|
-
children: /* @__PURE__ */ jsx6(LayoutPage, { pageDsl: layoutPage })
|
|
1638
|
+
children: /* @__PURE__ */ jsx6(LayoutPage, { pageDsl: layoutPage }, layoutName)
|
|
1585
1639
|
}
|
|
1586
1640
|
);
|
|
1587
1641
|
}
|
|
@@ -1620,6 +1674,7 @@ export {
|
|
|
1620
1674
|
defaultSitemapGuard,
|
|
1621
1675
|
fetchSitemap,
|
|
1622
1676
|
mergeSitemapMenus,
|
|
1677
|
+
resolveLayoutChain,
|
|
1623
1678
|
resolveSitemapRoute,
|
|
1624
1679
|
toSitemapNavItems,
|
|
1625
1680
|
withHttpHooks,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pactor-app/app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.48.0",
|
|
4
4
|
"description": "Pactor application: one-stop composition layer (createPactorApplication) wiring loader, bootstrap, registries, router, layout, interaction and page runtimes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pactor",
|
|
@@ -46,14 +46,14 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@pactor-app/core": "1.
|
|
50
|
-
"@pactor-app/
|
|
51
|
-
"@pactor-app/
|
|
52
|
-
"@pactor-app/
|
|
53
|
-
"@pactor-app/runtime": "1.
|
|
54
|
-
"@pactor-app/
|
|
55
|
-
"@pactor-app/
|
|
56
|
-
"@pactor-app/
|
|
49
|
+
"@pactor-app/core": "1.48.0",
|
|
50
|
+
"@pactor-app/permission": "1.48.0",
|
|
51
|
+
"@pactor-app/expression": "1.48.0",
|
|
52
|
+
"@pactor-app/router": "1.48.0",
|
|
53
|
+
"@pactor-app/runtime": "1.48.0",
|
|
54
|
+
"@pactor-app/resource": "1.48.0",
|
|
55
|
+
"@pactor-app/ui": "1.48.0",
|
|
56
|
+
"@pactor-app/schema": "1.48.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"react": ">=18",
|