@pactor-app/app 1.47.0 → 1.48.1
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 +90 -31
- package/dist/index.d.cts +24 -3
- package/dist/index.d.ts +24 -3
- package/dist/index.js +87 -29
- 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,
|
|
@@ -1009,7 +1010,7 @@ var defaultSitemapActions = {
|
|
|
1009
1010
|
|
|
1010
1011
|
// src/sitemap/SitemapApp.tsx
|
|
1011
1012
|
var import_core4 = require("@pactor-app/core");
|
|
1012
|
-
var
|
|
1013
|
+
var import_router4 = require("@pactor-app/router");
|
|
1013
1014
|
var import_runtime6 = require("@pactor-app/runtime");
|
|
1014
1015
|
var import_components2 = require("@pactor-app/ui/components");
|
|
1015
1016
|
var import_react8 = require("react");
|
|
@@ -1277,6 +1278,7 @@ function toSitemapNavItems(menuTree) {
|
|
|
1277
1278
|
}
|
|
1278
1279
|
|
|
1279
1280
|
// src/sitemap/route.ts
|
|
1281
|
+
var import_router3 = require("@pactor-app/router");
|
|
1280
1282
|
function resolveSitemapRoute(sitemap, registry, pathname) {
|
|
1281
1283
|
const match = registry.findByPath(pathname);
|
|
1282
1284
|
const menu = match?.menu ?? null;
|
|
@@ -1286,16 +1288,33 @@ function resolveSitemapRoute(sitemap, registry, pathname) {
|
|
|
1286
1288
|
const layoutName = menu.layout ?? sitemap.layout ?? Object.keys(sitemap.layouts)[0] ?? "";
|
|
1287
1289
|
const highlightId = menu.activeMenu ?? menu.id;
|
|
1288
1290
|
const highlightItem = registry.findById(highlightId);
|
|
1291
|
+
const page = sitemap.pages.find((p) => p.id === menu.pageId);
|
|
1292
|
+
const params = page !== void 0 && page.path !== menu.path ? (0, import_router3.match)(page.path, pathname)?.params ?? match.params : match.params;
|
|
1289
1293
|
return {
|
|
1290
1294
|
menu,
|
|
1291
1295
|
route: {
|
|
1292
1296
|
pageId: menu.pageId,
|
|
1293
1297
|
layoutName,
|
|
1298
|
+
layoutChain: resolveLayoutChain(sitemap, layoutName),
|
|
1294
1299
|
highlightKey: highlightItem?.path ?? highlightId,
|
|
1295
|
-
params
|
|
1300
|
+
params
|
|
1296
1301
|
}
|
|
1297
1302
|
};
|
|
1298
1303
|
}
|
|
1304
|
+
function resolveLayoutChain(sitemap, layoutName) {
|
|
1305
|
+
const chain = [layoutName];
|
|
1306
|
+
const seen = new Set(chain);
|
|
1307
|
+
let current = layoutName;
|
|
1308
|
+
for (; ; ) {
|
|
1309
|
+
const parent = sitemap.layouts[current]?.parent;
|
|
1310
|
+
if (parent === void 0 || seen.has(parent) || sitemap.layouts[parent] === void 0) {
|
|
1311
|
+
return chain;
|
|
1312
|
+
}
|
|
1313
|
+
chain.unshift(parent);
|
|
1314
|
+
seen.add(parent);
|
|
1315
|
+
current = parent;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1299
1318
|
var defaultSitemapGuard = ({
|
|
1300
1319
|
sitemap,
|
|
1301
1320
|
pathname,
|
|
@@ -1381,7 +1400,7 @@ function SitemapAppInner(props) {
|
|
|
1381
1400
|
};
|
|
1382
1401
|
}, [fetcher, baseUrl, props.httpAdapter, JSON.stringify(sources)]);
|
|
1383
1402
|
const registry = (0, import_react8.useMemo)(
|
|
1384
|
-
() => state.status === "ready" ? new
|
|
1403
|
+
() => state.status === "ready" ? new import_router4.MenuRegistry(mergeSitemapMenus(state.sitemap)) : null,
|
|
1385
1404
|
[state]
|
|
1386
1405
|
);
|
|
1387
1406
|
const resolution = (0, import_react8.useMemo)(
|
|
@@ -1414,24 +1433,24 @@ function SitemapAppInner(props) {
|
|
|
1414
1433
|
if (resolution.route === null) {
|
|
1415
1434
|
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
1435
|
}
|
|
1417
|
-
const { pageId,
|
|
1436
|
+
const { pageId, layoutChain, highlightKey, params } = resolution.route;
|
|
1418
1437
|
return (
|
|
1419
|
-
// key
|
|
1420
|
-
//
|
|
1438
|
+
// key 只随最外层布局名变化:布局链共享前缀时外层布局常驻,
|
|
1439
|
+
// 仅分叉的内层布局与 PageOutlet 内页面随路由重建
|
|
1421
1440
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NavHighlightContext.Provider, { value: highlightKey, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1422
1441
|
SitemapLayout,
|
|
1423
1442
|
{
|
|
1424
1443
|
sitemap,
|
|
1425
1444
|
registry,
|
|
1426
1445
|
pageId,
|
|
1427
|
-
|
|
1446
|
+
layoutChain,
|
|
1428
1447
|
pathname,
|
|
1429
1448
|
query,
|
|
1430
1449
|
navigate,
|
|
1431
1450
|
routeParams: params,
|
|
1432
1451
|
props
|
|
1433
1452
|
},
|
|
1434
|
-
|
|
1453
|
+
layoutChain[0]
|
|
1435
1454
|
) })
|
|
1436
1455
|
);
|
|
1437
1456
|
}
|
|
@@ -1439,14 +1458,13 @@ function SitemapLayout({
|
|
|
1439
1458
|
sitemap,
|
|
1440
1459
|
registry,
|
|
1441
1460
|
pageId,
|
|
1442
|
-
|
|
1461
|
+
layoutChain,
|
|
1443
1462
|
pathname,
|
|
1444
1463
|
query,
|
|
1445
1464
|
navigate: hostNavigate,
|
|
1446
1465
|
routeParams,
|
|
1447
1466
|
props
|
|
1448
1467
|
}) {
|
|
1449
|
-
const { t } = (0, import_runtime6.useI18n)();
|
|
1450
1468
|
const sessionService = props.sessionService ?? "session.context";
|
|
1451
1469
|
const session = (0, import_react8.useMemo)(
|
|
1452
1470
|
() => ({
|
|
@@ -1494,23 +1512,6 @@ function SitemapLayout({
|
|
|
1494
1512
|
() => ({ ...defaultSitemapActions, ...props.actions }),
|
|
1495
1513
|
[props.actions]
|
|
1496
1514
|
);
|
|
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
1515
|
const messagesSignature = JSON.stringify(props.messages ?? {});
|
|
1515
1516
|
const pageFactory = (0, import_react8.useMemo)(
|
|
1516
1517
|
() => createPactorPage({
|
|
@@ -1541,6 +1542,9 @@ function SitemapLayout({
|
|
|
1541
1542
|
);
|
|
1542
1543
|
const layoutFactory = (0, import_react8.useMemo)(
|
|
1543
1544
|
() => createPactorPage({
|
|
1545
|
+
// loader 注入使布局运行时的 OverlayHost 支持 page 类型弹层
|
|
1546
|
+
//(布局内按钮经 overlay.open 打开懒加载表单页,如创建实例)
|
|
1547
|
+
loader,
|
|
1544
1548
|
services,
|
|
1545
1549
|
actions,
|
|
1546
1550
|
navigate,
|
|
@@ -1552,6 +1556,7 @@ function SitemapLayout({
|
|
|
1552
1556
|
}),
|
|
1553
1557
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1554
1558
|
[
|
|
1559
|
+
loader,
|
|
1555
1560
|
services,
|
|
1556
1561
|
actions,
|
|
1557
1562
|
navigate,
|
|
@@ -1563,11 +1568,15 @@ function SitemapLayout({
|
|
|
1563
1568
|
]
|
|
1564
1569
|
);
|
|
1565
1570
|
const Page = pageFactory;
|
|
1566
|
-
const LayoutPage = layoutFactory;
|
|
1567
1571
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1568
|
-
|
|
1572
|
+
LayoutLevel,
|
|
1569
1573
|
{
|
|
1570
|
-
|
|
1574
|
+
sitemap,
|
|
1575
|
+
chain: layoutChain,
|
|
1576
|
+
index: 0,
|
|
1577
|
+
sessionService,
|
|
1578
|
+
layoutFactory,
|
|
1579
|
+
outlet: (
|
|
1571
1580
|
// 页面实例:key 随 pageId + pathname 变化(route 参数变化需重建,
|
|
1572
1581
|
// PactorPage 的 route prop 不参与运行时重建)——布局保持挂载
|
|
1573
1582
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
@@ -1578,8 +1587,57 @@ function SitemapLayout({
|
|
|
1578
1587
|
},
|
|
1579
1588
|
`${pageId}@${pathname}`
|
|
1580
1589
|
)
|
|
1590
|
+
)
|
|
1591
|
+
}
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
1594
|
+
function LayoutLevel({
|
|
1595
|
+
sitemap,
|
|
1596
|
+
chain,
|
|
1597
|
+
index,
|
|
1598
|
+
sessionService,
|
|
1599
|
+
layoutFactory,
|
|
1600
|
+
outlet
|
|
1601
|
+
}) {
|
|
1602
|
+
const { t } = (0, import_runtime6.useI18n)();
|
|
1603
|
+
const layoutName = chain[index];
|
|
1604
|
+
const layoutDsl = sitemap.layouts[layoutName];
|
|
1605
|
+
const layoutPage = (0, import_react8.useMemo)(() => {
|
|
1606
|
+
if (layoutDsl === void 0) {
|
|
1607
|
+
return null;
|
|
1608
|
+
}
|
|
1609
|
+
return {
|
|
1610
|
+
dsl: layoutDsl.dsl,
|
|
1611
|
+
page: { id: `layout:${layoutName}`, title: layoutName },
|
|
1612
|
+
// 布局级 state / lifecycle 透传(布局运行时常驻,state 跨页面保持)
|
|
1613
|
+
state: layoutDsl.state,
|
|
1614
|
+
lifecycle: layoutDsl.lifecycle,
|
|
1615
|
+
dataSources: {
|
|
1616
|
+
session: { type: "service", service: sessionService, autoLoad: true }
|
|
1617
|
+
},
|
|
1618
|
+
body: layoutDsl.body
|
|
1619
|
+
};
|
|
1620
|
+
}, [layoutDsl, layoutName, sessionService]);
|
|
1621
|
+
if (layoutDsl === void 0 || layoutPage === null) {
|
|
1622
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Centered, { children: t("pactor.sitemap.layoutMissing", { name: layoutName }) });
|
|
1623
|
+
}
|
|
1624
|
+
const LayoutPage = layoutFactory;
|
|
1625
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1626
|
+
OutletContext.Provider,
|
|
1627
|
+
{
|
|
1628
|
+
value: index === chain.length - 1 ? outlet : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1629
|
+
LayoutLevel,
|
|
1630
|
+
{
|
|
1631
|
+
sitemap,
|
|
1632
|
+
chain,
|
|
1633
|
+
index: index + 1,
|
|
1634
|
+
sessionService,
|
|
1635
|
+
layoutFactory,
|
|
1636
|
+
outlet
|
|
1637
|
+
},
|
|
1638
|
+
chain[index + 1]
|
|
1581
1639
|
),
|
|
1582
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LayoutPage, { pageDsl: layoutPage })
|
|
1640
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LayoutPage, { pageDsl: layoutPage }, layoutName)
|
|
1583
1641
|
}
|
|
1584
1642
|
);
|
|
1585
1643
|
}
|
|
@@ -1619,6 +1677,7 @@ function Centered({ children }) {
|
|
|
1619
1677
|
defaultSitemapGuard,
|
|
1620
1678
|
fetchSitemap,
|
|
1621
1679
|
mergeSitemapMenus,
|
|
1680
|
+
resolveLayoutChain,
|
|
1622
1681
|
resolveSitemapRoute,
|
|
1623
1682
|
toSitemapNavItems,
|
|
1624
1683
|
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
|
@@ -1279,6 +1279,7 @@ function toSitemapNavItems(menuTree) {
|
|
|
1279
1279
|
}
|
|
1280
1280
|
|
|
1281
1281
|
// src/sitemap/route.ts
|
|
1282
|
+
import { match as matchPath } from "@pactor-app/router";
|
|
1282
1283
|
function resolveSitemapRoute(sitemap, registry, pathname) {
|
|
1283
1284
|
const match = registry.findByPath(pathname);
|
|
1284
1285
|
const menu = match?.menu ?? null;
|
|
@@ -1288,16 +1289,33 @@ function resolveSitemapRoute(sitemap, registry, pathname) {
|
|
|
1288
1289
|
const layoutName = menu.layout ?? sitemap.layout ?? Object.keys(sitemap.layouts)[0] ?? "";
|
|
1289
1290
|
const highlightId = menu.activeMenu ?? menu.id;
|
|
1290
1291
|
const highlightItem = registry.findById(highlightId);
|
|
1292
|
+
const page = sitemap.pages.find((p) => p.id === menu.pageId);
|
|
1293
|
+
const params = page !== void 0 && page.path !== menu.path ? matchPath(page.path, pathname)?.params ?? match.params : match.params;
|
|
1291
1294
|
return {
|
|
1292
1295
|
menu,
|
|
1293
1296
|
route: {
|
|
1294
1297
|
pageId: menu.pageId,
|
|
1295
1298
|
layoutName,
|
|
1299
|
+
layoutChain: resolveLayoutChain(sitemap, layoutName),
|
|
1296
1300
|
highlightKey: highlightItem?.path ?? highlightId,
|
|
1297
|
-
params
|
|
1301
|
+
params
|
|
1298
1302
|
}
|
|
1299
1303
|
};
|
|
1300
1304
|
}
|
|
1305
|
+
function resolveLayoutChain(sitemap, layoutName) {
|
|
1306
|
+
const chain = [layoutName];
|
|
1307
|
+
const seen = new Set(chain);
|
|
1308
|
+
let current = layoutName;
|
|
1309
|
+
for (; ; ) {
|
|
1310
|
+
const parent = sitemap.layouts[current]?.parent;
|
|
1311
|
+
if (parent === void 0 || seen.has(parent) || sitemap.layouts[parent] === void 0) {
|
|
1312
|
+
return chain;
|
|
1313
|
+
}
|
|
1314
|
+
chain.unshift(parent);
|
|
1315
|
+
seen.add(parent);
|
|
1316
|
+
current = parent;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1301
1319
|
var defaultSitemapGuard = ({
|
|
1302
1320
|
sitemap,
|
|
1303
1321
|
pathname,
|
|
@@ -1416,24 +1434,24 @@ function SitemapAppInner(props) {
|
|
|
1416
1434
|
if (resolution.route === null) {
|
|
1417
1435
|
return props.renderNotFound !== void 0 ? /* @__PURE__ */ jsx6(Fragment, { children: props.renderNotFound(pathname) }) : /* @__PURE__ */ jsx6(Centered, { children: t("pactor.sitemap.notFound", { path: pathname }) });
|
|
1418
1436
|
}
|
|
1419
|
-
const { pageId,
|
|
1437
|
+
const { pageId, layoutChain, highlightKey, params } = resolution.route;
|
|
1420
1438
|
return (
|
|
1421
|
-
// key
|
|
1422
|
-
//
|
|
1439
|
+
// key 只随最外层布局名变化:布局链共享前缀时外层布局常驻,
|
|
1440
|
+
// 仅分叉的内层布局与 PageOutlet 内页面随路由重建
|
|
1423
1441
|
/* @__PURE__ */ jsx6(NavHighlightContext.Provider, { value: highlightKey, children: /* @__PURE__ */ jsx6(
|
|
1424
1442
|
SitemapLayout,
|
|
1425
1443
|
{
|
|
1426
1444
|
sitemap,
|
|
1427
1445
|
registry,
|
|
1428
1446
|
pageId,
|
|
1429
|
-
|
|
1447
|
+
layoutChain,
|
|
1430
1448
|
pathname,
|
|
1431
1449
|
query,
|
|
1432
1450
|
navigate,
|
|
1433
1451
|
routeParams: params,
|
|
1434
1452
|
props
|
|
1435
1453
|
},
|
|
1436
|
-
|
|
1454
|
+
layoutChain[0]
|
|
1437
1455
|
) })
|
|
1438
1456
|
);
|
|
1439
1457
|
}
|
|
@@ -1441,14 +1459,13 @@ function SitemapLayout({
|
|
|
1441
1459
|
sitemap,
|
|
1442
1460
|
registry,
|
|
1443
1461
|
pageId,
|
|
1444
|
-
|
|
1462
|
+
layoutChain,
|
|
1445
1463
|
pathname,
|
|
1446
1464
|
query,
|
|
1447
1465
|
navigate: hostNavigate,
|
|
1448
1466
|
routeParams,
|
|
1449
1467
|
props
|
|
1450
1468
|
}) {
|
|
1451
|
-
const { t } = useI18n2();
|
|
1452
1469
|
const sessionService = props.sessionService ?? "session.context";
|
|
1453
1470
|
const session = useMemo2(
|
|
1454
1471
|
() => ({
|
|
@@ -1496,23 +1513,6 @@ function SitemapLayout({
|
|
|
1496
1513
|
() => ({ ...defaultSitemapActions, ...props.actions }),
|
|
1497
1514
|
[props.actions]
|
|
1498
1515
|
);
|
|
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
1516
|
const messagesSignature = JSON.stringify(props.messages ?? {});
|
|
1517
1517
|
const pageFactory = useMemo2(
|
|
1518
1518
|
() => createPactorPage({
|
|
@@ -1543,6 +1543,9 @@ function SitemapLayout({
|
|
|
1543
1543
|
);
|
|
1544
1544
|
const layoutFactory = useMemo2(
|
|
1545
1545
|
() => createPactorPage({
|
|
1546
|
+
// loader 注入使布局运行时的 OverlayHost 支持 page 类型弹层
|
|
1547
|
+
//(布局内按钮经 overlay.open 打开懒加载表单页,如创建实例)
|
|
1548
|
+
loader,
|
|
1546
1549
|
services,
|
|
1547
1550
|
actions,
|
|
1548
1551
|
navigate,
|
|
@@ -1554,6 +1557,7 @@ function SitemapLayout({
|
|
|
1554
1557
|
}),
|
|
1555
1558
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1556
1559
|
[
|
|
1560
|
+
loader,
|
|
1557
1561
|
services,
|
|
1558
1562
|
actions,
|
|
1559
1563
|
navigate,
|
|
@@ -1565,11 +1569,15 @@ function SitemapLayout({
|
|
|
1565
1569
|
]
|
|
1566
1570
|
);
|
|
1567
1571
|
const Page = pageFactory;
|
|
1568
|
-
const LayoutPage = layoutFactory;
|
|
1569
1572
|
return /* @__PURE__ */ jsx6(
|
|
1570
|
-
|
|
1573
|
+
LayoutLevel,
|
|
1571
1574
|
{
|
|
1572
|
-
|
|
1575
|
+
sitemap,
|
|
1576
|
+
chain: layoutChain,
|
|
1577
|
+
index: 0,
|
|
1578
|
+
sessionService,
|
|
1579
|
+
layoutFactory,
|
|
1580
|
+
outlet: (
|
|
1573
1581
|
// 页面实例:key 随 pageId + pathname 变化(route 参数变化需重建,
|
|
1574
1582
|
// PactorPage 的 route prop 不参与运行时重建)——布局保持挂载
|
|
1575
1583
|
/* @__PURE__ */ jsx6(
|
|
@@ -1580,8 +1588,57 @@ function SitemapLayout({
|
|
|
1580
1588
|
},
|
|
1581
1589
|
`${pageId}@${pathname}`
|
|
1582
1590
|
)
|
|
1591
|
+
)
|
|
1592
|
+
}
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
function LayoutLevel({
|
|
1596
|
+
sitemap,
|
|
1597
|
+
chain,
|
|
1598
|
+
index,
|
|
1599
|
+
sessionService,
|
|
1600
|
+
layoutFactory,
|
|
1601
|
+
outlet
|
|
1602
|
+
}) {
|
|
1603
|
+
const { t } = useI18n2();
|
|
1604
|
+
const layoutName = chain[index];
|
|
1605
|
+
const layoutDsl = sitemap.layouts[layoutName];
|
|
1606
|
+
const layoutPage = useMemo2(() => {
|
|
1607
|
+
if (layoutDsl === void 0) {
|
|
1608
|
+
return null;
|
|
1609
|
+
}
|
|
1610
|
+
return {
|
|
1611
|
+
dsl: layoutDsl.dsl,
|
|
1612
|
+
page: { id: `layout:${layoutName}`, title: layoutName },
|
|
1613
|
+
// 布局级 state / lifecycle 透传(布局运行时常驻,state 跨页面保持)
|
|
1614
|
+
state: layoutDsl.state,
|
|
1615
|
+
lifecycle: layoutDsl.lifecycle,
|
|
1616
|
+
dataSources: {
|
|
1617
|
+
session: { type: "service", service: sessionService, autoLoad: true }
|
|
1618
|
+
},
|
|
1619
|
+
body: layoutDsl.body
|
|
1620
|
+
};
|
|
1621
|
+
}, [layoutDsl, layoutName, sessionService]);
|
|
1622
|
+
if (layoutDsl === void 0 || layoutPage === null) {
|
|
1623
|
+
return /* @__PURE__ */ jsx6(Centered, { children: t("pactor.sitemap.layoutMissing", { name: layoutName }) });
|
|
1624
|
+
}
|
|
1625
|
+
const LayoutPage = layoutFactory;
|
|
1626
|
+
return /* @__PURE__ */ jsx6(
|
|
1627
|
+
OutletContext.Provider,
|
|
1628
|
+
{
|
|
1629
|
+
value: index === chain.length - 1 ? outlet : /* @__PURE__ */ jsx6(
|
|
1630
|
+
LayoutLevel,
|
|
1631
|
+
{
|
|
1632
|
+
sitemap,
|
|
1633
|
+
chain,
|
|
1634
|
+
index: index + 1,
|
|
1635
|
+
sessionService,
|
|
1636
|
+
layoutFactory,
|
|
1637
|
+
outlet
|
|
1638
|
+
},
|
|
1639
|
+
chain[index + 1]
|
|
1583
1640
|
),
|
|
1584
|
-
children: /* @__PURE__ */ jsx6(LayoutPage, { pageDsl: layoutPage })
|
|
1641
|
+
children: /* @__PURE__ */ jsx6(LayoutPage, { pageDsl: layoutPage }, layoutName)
|
|
1585
1642
|
}
|
|
1586
1643
|
);
|
|
1587
1644
|
}
|
|
@@ -1620,6 +1677,7 @@ export {
|
|
|
1620
1677
|
defaultSitemapGuard,
|
|
1621
1678
|
fetchSitemap,
|
|
1622
1679
|
mergeSitemapMenus,
|
|
1680
|
+
resolveLayoutChain,
|
|
1623
1681
|
resolveSitemapRoute,
|
|
1624
1682
|
toSitemapNavItems,
|
|
1625
1683
|
withHttpHooks,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pactor-app/app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.48.1",
|
|
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/expression": "1.
|
|
51
|
-
"@pactor-app/resource": "1.
|
|
52
|
-
"@pactor-app/permission": "1.
|
|
53
|
-
"@pactor-app/
|
|
54
|
-
"@pactor-app/
|
|
55
|
-
"@pactor-app/
|
|
56
|
-
"@pactor-app/router": "1.
|
|
49
|
+
"@pactor-app/core": "1.48.1",
|
|
50
|
+
"@pactor-app/expression": "1.48.1",
|
|
51
|
+
"@pactor-app/resource": "1.48.1",
|
|
52
|
+
"@pactor-app/permission": "1.48.1",
|
|
53
|
+
"@pactor-app/schema": "1.48.1",
|
|
54
|
+
"@pactor-app/runtime": "1.48.1",
|
|
55
|
+
"@pactor-app/ui": "1.48.1",
|
|
56
|
+
"@pactor-app/router": "1.48.1"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"react": ">=18",
|