feat: SPA 外壳——菜单切换零刷新,对齐 Ant Design Pro 体验(issue #131)

- web/index.html 重写为 SPA 外壳:Sider/顶栏常驻,模块页在
  #module-frame iframe 内容区加载,点菜单只换 iframe 地址,
  彻底消灭整页刷新与白闪(此前只是铺底色缓解)
- 新增 shared/shell.js:IAOP_SHELL.navigate() 导航、外壳 hash
  (#/admin/#models) 与 iframe 地址双向同步、登录守卫、移动端
  模块新标签打开;iframe 地址用 location.replace 避免污染
  joint history;250ms 轮询回同步(replace 不触发 load 事件);
  导航时乐观高亮(后台标签轮询被节流不能作为唯一途径);
  前进/后退若恢复了 iframe 旧地址则以外壳 hash 为准强制对齐
- pro-header.js 三模式:外壳(菜单点击走 IAOP_SHELL、导出
  IAOP_PRO.setActive)/被嵌(iframe 内,隐藏页内跨页链接与重复
  用户标识、保留 Tab-hash 联动)/独立(原行为);菜单渲染后派发
  pro:menu-rendered 事件消除导航先于渲染的高亮竞态
- session.js:logout/守卫重定向作用于顶层窗口(iframe 内不再
  只跳 iframe 自己)
- 修复 iframe 替换元素宽高 bug:fixed 时左右定位不拉伸(回退
  固有 300x150),改显式 calc 宽高
- 登录页 next 默认回外壳 ../index.html

实测(本地 http.server + WebBridge 真实浏览器):菜单切换/
子菜单切换/前进/后退/直达链接(#/admin/#models) URL、iframe、
菜单高亮、子菜单高亮全链路一致,Sider 节点全程不重建;
登录守卫 logout→顶层跳登录、登录后回外壳默认驾驶舱。
This commit is contained in:
2026-08-05 22:43:47 +08:00
parent 65d3b0952f
commit cfa976cac3
11 changed files with 293 additions and 61 deletions
+12 -3
View File
@@ -151,6 +151,12 @@ var IAOP_SESSION = (function () {
});
}
/* 被 iframe 嵌入时(SPA 外壳)重定向要作用于顶层窗口 */
function topWindow() {
try { return window.self !== window.top ? window.top : window; }
catch (e) { return window; }
}
/* ---- 登出:双轨都清 ----------------------------------------------------- */
function logout(loginUrl) {
clearSession();
@@ -159,15 +165,18 @@ var IAOP_SESSION = (function () {
.catch(function () {});
} catch (e) {}
var next = encodeURIComponent(location.pathname + location.search);
location.href = (loginUrl || "../auth/login.html") + "?next=" + next;
topWindow().location.href = (loginUrl || "../auth/login.html") + "?next=" + next;
}
/* ---- 路由守卫(与 #150 IAOP_AUTH 同语义) ------------------------------- */
function requireLoginElseRedirect(loginUrl) {
return currentUser().then(function (u) {
if (!u) {
var next = encodeURIComponent(location.pathname + location.search);
location.href = (loginUrl || "../auth/login.html") + "?next=" + next;
// 回跳地址取顶层窗口(SPA 外壳 hash 内含模块/子页路径)
var top = topWindow();
var next = encodeURIComponent(
top.location.pathname + top.location.search + top.location.hash);
top.location.href = (loginUrl || "../auth/login.html") + "?next=" + next;
return false;
}
return u;