2026-08-05 14:31:42 +08:00
|
|
|
|
/* iAOP 全站统一布局(Ant Design Pro 左侧边栏风格,2026-08)。
|
2026-08-05 14:17:37 +08:00
|
|
|
|
*
|
2026-08-05 22:43:47 +08:00
|
|
|
|
* 三种运行模式(自动判别):
|
|
|
|
|
|
* 1. SPA 外壳模式(web/index.html,body[data-pro-shell]):
|
|
|
|
|
|
* Sider/顶栏常驻;菜单点击不整页跳转,改调 IAOP_SHELL.navigate(url)
|
|
|
|
|
|
* 换 iframe 内容(对齐 Ant Design Pro 的无刷新体验);
|
|
|
|
|
|
* 导出 IAOP_PRO.setActive(moduleKey, childHash, title) 供 shell.js
|
|
|
|
|
|
* 在 iframe 加载/子页切换后同步菜单高亮与顶栏页题。
|
|
|
|
|
|
* 2. 被嵌模式(模块页在 iframe 内,window.self !== window.top):
|
|
|
|
|
|
* 不注入 Sider/顶栏、不铺 Sider 底色,保留页内 Tab ↔ hash 联动;
|
|
|
|
|
|
* 隐藏页内顶栏的跨页链接与重复用户标识(外壳已提供全局导航)。
|
|
|
|
|
|
* 3. 独立模式(模块页直接打开):注入 Sider/顶栏(老行为),
|
|
|
|
|
|
* 菜单按角色过滤,整页跳转。
|
2026-08-05 14:17:37 +08:00
|
|
|
|
*
|
2026-08-05 22:43:47 +08:00
|
|
|
|
* 页内 Tab ↔ URL hash 联动(studio/admin)在三种模式下都生效:
|
|
|
|
|
|
* #tabs .tab[data-page] 自动隐藏,改由 URL hash 驱动切换,URL 可分享直达。
|
2026-08-05 14:40:15 +08:00
|
|
|
|
*
|
2026-08-05 14:31:42 +08:00
|
|
|
|
* 用法(各模块页相同,无需改动):
|
2026-08-05 22:43:47 +08:00
|
|
|
|
* <body data-pro-title="配置化驾驶舱">
|
2026-08-05 14:17:37 +08:00
|
|
|
|
* <script src="../auth/user_store.js"></script>
|
|
|
|
|
|
* <script src="../shared/session.js"></script>
|
|
|
|
|
|
* <script src="../shared/pro-header.js"></script>
|
|
|
|
|
|
*
|
2026-08-05 22:43:47 +08:00
|
|
|
|
* 本组件不强制登录(守卫由 IAOP_SESSION 负责)。
|
2026-08-05 14:17:37 +08:00
|
|
|
|
*/
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
|
|
function el(tag, cls, text) {
|
|
|
|
|
|
var n = document.createElement(tag);
|
|
|
|
|
|
if (cls) n.className = cls;
|
|
|
|
|
|
if (text !== undefined) n.textContent = text;
|
|
|
|
|
|
return n;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-05 14:31:42 +08:00
|
|
|
|
/* 站点根相对前缀:/index.html 为 "",/cockpit/、/auth/x.html 等一级目录为 "../" */
|
|
|
|
|
|
function rootPrefix() {
|
|
|
|
|
|
var path = location.pathname.replace(/\/index\.html$/, "/");
|
|
|
|
|
|
var depth = path.split("/").filter(Boolean).length;
|
|
|
|
|
|
return depth > 0 ? "../" : "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-05 22:43:47 +08:00
|
|
|
|
/* 当前页面命中的模块 key(独立模式菜单高亮/展开) */
|
2026-08-05 14:40:15 +08:00
|
|
|
|
function activeModuleKey() {
|
2026-08-05 14:31:42 +08:00
|
|
|
|
var path = location.pathname;
|
|
|
|
|
|
var mods = IAOP_SESSION.MODULES;
|
|
|
|
|
|
for (var i = 0; i < mods.length; i++) {
|
|
|
|
|
|
var url = mods[i].url.replace(/\/$/, "");
|
|
|
|
|
|
if (path.indexOf("/" + url) === 0 || path.indexOf("/" + url + "/") === 0) {
|
|
|
|
|
|
return mods[i].key;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-05 22:43:47 +08:00
|
|
|
|
return "";
|
2026-08-05 14:31:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-05 14:40:15 +08:00
|
|
|
|
/* 当前 hash 命中的子菜单 key(# 前缀归一化) */
|
|
|
|
|
|
function activeChildKey() {
|
|
|
|
|
|
return location.hash.replace(/^#/, "");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---- 页内 Tab ↔ URL hash 联动(studio/admin 的 #tabs .tab[data-page]) --- */
|
|
|
|
|
|
function bindPageTabs() {
|
|
|
|
|
|
var tabsBar = document.getElementById("tabs");
|
|
|
|
|
|
if (!tabsBar) return;
|
|
|
|
|
|
var tabs = tabsBar.querySelectorAll(".tab[data-page]");
|
|
|
|
|
|
if (!tabs.length) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 子菜单已上收到 Sider:隐藏页内 Tab 条
|
|
|
|
|
|
tabsBar.classList.add("pro-tabs-hidden");
|
|
|
|
|
|
|
|
|
|
|
|
function activate(page) {
|
|
|
|
|
|
var btn = tabsBar.querySelector('.tab[data-page="' + page + '"]');
|
|
|
|
|
|
if (btn) btn.click();
|
|
|
|
|
|
}
|
|
|
|
|
|
window.addEventListener("hashchange", function () {
|
|
|
|
|
|
activate(activeChildKey());
|
|
|
|
|
|
});
|
2026-08-06 08:20:17 +08:00
|
|
|
|
// 等页面自身脚本(studio.js/admin.js)绑好点击事件后再做初始激活。
|
|
|
|
|
|
// 注意:studio.js/admin.js 的 Tab 事件在异步 boot(user) 里绑定,
|
|
|
|
|
|
// window load 时可能尚未就绪——带重试地激活,直到 Tab 真的点亮。
|
|
|
|
|
|
var activateTries = 0;
|
|
|
|
|
|
function activateInitial() {
|
2026-08-05 14:40:15 +08:00
|
|
|
|
var h = activeChildKey();
|
2026-08-06 08:20:17 +08:00
|
|
|
|
if (!h) return;
|
|
|
|
|
|
var btn = tabsBar.querySelector('.tab[data-page="' + h + '"]');
|
|
|
|
|
|
if (!btn) return;
|
|
|
|
|
|
btn.click();
|
|
|
|
|
|
if (!btn.classList.contains("active") && ++activateTries < 40) {
|
|
|
|
|
|
setTimeout(activateInitial, 250); // 最多重试 10s
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
window.addEventListener("load", activateInitial);
|
2026-08-05 14:40:15 +08:00
|
|
|
|
// 页内 Tab 点击回写 hash(保持 URL 可分享)
|
|
|
|
|
|
tabsBar.addEventListener("click", function (e) {
|
|
|
|
|
|
var t = e.target.closest(".tab[data-page]");
|
|
|
|
|
|
if (t && ("#" + t.getAttribute("data-page")) !== location.hash) {
|
|
|
|
|
|
history.replaceState(null, "", "#" + t.getAttribute("data-page"));
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-05 14:17:37 +08:00
|
|
|
|
function build() {
|
|
|
|
|
|
var body = document.body;
|
2026-08-05 22:43:47 +08:00
|
|
|
|
if (!body) return;
|
2026-08-05 14:17:37 +08:00
|
|
|
|
|
2026-08-05 22:43:47 +08:00
|
|
|
|
/* ---- 被嵌模式:模块页在 SPA 外壳的 iframe 内,只做 Tab↔hash 联动 ------ */
|
|
|
|
|
|
if (window.self !== window.top) {
|
|
|
|
|
|
// 覆盖模块页 head 内联的「左深右灰」防闪底色(iframe 内容区无 Sider)
|
|
|
|
|
|
document.documentElement.style.background = "#f0f2f5";
|
|
|
|
|
|
// 外壳已提供全局导航与登录用户展示:隐藏页内顶栏里的跨页跳转链接
|
|
|
|
|
|
// (驾驶舱/配置台等 <a>,避免 iframe 内自行跳走)与重复的用户标识 #who。
|
|
|
|
|
|
// 保留功能性控件(模板/角色下拉、状态点等)。
|
|
|
|
|
|
var whoEl = document.getElementById("who");
|
|
|
|
|
|
if (whoEl) whoEl.style.display = "none";
|
|
|
|
|
|
Array.prototype.forEach.call(
|
|
|
|
|
|
document.querySelectorAll("#topbar-right a, #topbar a"),
|
|
|
|
|
|
function (a) { a.style.display = "none"; }
|
|
|
|
|
|
);
|
|
|
|
|
|
bindPageTabs();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (document.querySelector(".pro-sider")) return;
|
|
|
|
|
|
|
|
|
|
|
|
var SHELL = body.hasAttribute("data-pro-shell");
|
2026-08-05 14:17:37 +08:00
|
|
|
|
var title = body.getAttribute("data-pro-title") || document.title || "";
|
2026-08-05 15:54:14 +08:00
|
|
|
|
var home = body.getAttribute("data-pro-home") || "../cockpit/";
|
2026-08-05 14:17:37 +08:00
|
|
|
|
var loginUrl = body.getAttribute("data-pro-login") || "../auth/login.html";
|
2026-08-05 14:31:42 +08:00
|
|
|
|
var prefix = rootPrefix();
|
2026-08-05 22:43:47 +08:00
|
|
|
|
var active = SHELL ? "" : activeModuleKey();
|
2026-08-05 14:17:37 +08:00
|
|
|
|
|
2026-08-05 14:31:42 +08:00
|
|
|
|
/* ---- 左侧 Sider ----------------------------------------------------- */
|
|
|
|
|
|
var sider = el("aside", "pro-sider");
|
2026-08-05 14:17:37 +08:00
|
|
|
|
|
2026-08-05 14:31:42 +08:00
|
|
|
|
var logo = el("a", "pro-sider-logo");
|
2026-08-05 14:17:37 +08:00
|
|
|
|
logo.href = home;
|
|
|
|
|
|
logo.appendChild(el("span", "pro-logo-badge", "iA"));
|
2026-08-05 14:31:42 +08:00
|
|
|
|
logo.appendChild(el("span", "pro-sider-logo-text", "iAOP 云美工业AI优化平台"));
|
|
|
|
|
|
sider.appendChild(logo);
|
2026-08-05 14:17:37 +08:00
|
|
|
|
|
2026-08-05 14:31:42 +08:00
|
|
|
|
var menu = el("nav", "pro-sider-menu");
|
|
|
|
|
|
sider.appendChild(menu);
|
|
|
|
|
|
|
|
|
|
|
|
// 折叠按钮(AntD Pro Sider 底部 trigger)
|
|
|
|
|
|
var trigger = el("button", "pro-sider-trigger", "⟨ 折叠");
|
|
|
|
|
|
trigger.type = "button";
|
|
|
|
|
|
trigger.addEventListener("click", function () {
|
|
|
|
|
|
var collapsed = body.classList.toggle("pro-sider-collapsed");
|
|
|
|
|
|
trigger.textContent = collapsed ? "⟩" : "⟨ 折叠";
|
|
|
|
|
|
});
|
|
|
|
|
|
sider.appendChild(trigger);
|
|
|
|
|
|
|
|
|
|
|
|
body.insertBefore(sider, body.firstChild);
|
|
|
|
|
|
|
|
|
|
|
|
/* ---- 顶部细栏 -------------------------------------------------------- */
|
|
|
|
|
|
var bar = el("div", "pro-header");
|
2026-08-05 22:43:47 +08:00
|
|
|
|
var titleEl = el("span", "pro-header-page-title", title);
|
|
|
|
|
|
if (title) bar.appendChild(titleEl);
|
2026-08-05 14:17:37 +08:00
|
|
|
|
|
|
|
|
|
|
var right = el("div", "pro-header-right");
|
|
|
|
|
|
var userBox = el("span", "pro-user");
|
|
|
|
|
|
right.appendChild(userBox);
|
|
|
|
|
|
|
|
|
|
|
|
var logoutBtn = el("button", "pro-logout", "退出");
|
|
|
|
|
|
logoutBtn.type = "button";
|
|
|
|
|
|
logoutBtn.addEventListener("click", function () {
|
|
|
|
|
|
IAOP_SESSION.logout(loginUrl);
|
|
|
|
|
|
});
|
|
|
|
|
|
right.appendChild(logoutBtn);
|
|
|
|
|
|
|
|
|
|
|
|
bar.appendChild(right);
|
2026-08-05 14:31:42 +08:00
|
|
|
|
body.insertBefore(bar, sider.nextSibling);
|
2026-08-05 14:17:37 +08:00
|
|
|
|
|
2026-08-05 14:31:42 +08:00
|
|
|
|
body.classList.add("has-pro-sider");
|
|
|
|
|
|
|
2026-08-05 22:43:47 +08:00
|
|
|
|
/* ---- 菜单项导航:外壳模式走 IAOP_SHELL(无刷新),独立模式整页跳转 ----- */
|
|
|
|
|
|
function go(url) {
|
|
|
|
|
|
if (SHELL && window.IAOP_SHELL) {
|
|
|
|
|
|
IAOP_SHELL.navigate(url);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
location.href = url;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-05 14:31:42 +08:00
|
|
|
|
/* ---- 菜单渲染(需要登录角色,异步) ----------------------------------- */
|
2026-08-05 14:40:15 +08:00
|
|
|
|
function addLeafItem(key, icon, text, href) {
|
|
|
|
|
|
var a = el("a", "pro-menu-item" + (key === active ? " active" : ""));
|
|
|
|
|
|
a.href = href;
|
|
|
|
|
|
a.title = text;
|
2026-08-05 22:43:47 +08:00
|
|
|
|
a.setAttribute("data-module", key);
|
2026-08-05 14:40:15 +08:00
|
|
|
|
a.appendChild(el("span", "pro-menu-icon", icon));
|
|
|
|
|
|
a.appendChild(el("span", "pro-menu-text", text));
|
2026-08-05 22:43:47 +08:00
|
|
|
|
if (SHELL) {
|
|
|
|
|
|
a.addEventListener("click", function (e) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
go(href);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-08-05 14:40:15 +08:00
|
|
|
|
menu.appendChild(a);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function addSubMenu(m) {
|
|
|
|
|
|
var isActiveModule = (m.key === active);
|
|
|
|
|
|
var wrap = el("div", "pro-submenu" +
|
|
|
|
|
|
(isActiveModule ? " open" : ""));
|
2026-08-05 22:43:47 +08:00
|
|
|
|
wrap.setAttribute("data-module", m.key);
|
2026-08-05 14:40:15 +08:00
|
|
|
|
|
|
|
|
|
|
var head = el("a", "pro-menu-item pro-submenu-title" +
|
|
|
|
|
|
(isActiveModule ? " active" : ""));
|
2026-08-05 16:20:52 +08:00
|
|
|
|
head.href = "#";
|
2026-08-05 14:40:15 +08:00
|
|
|
|
head.title = m.title;
|
|
|
|
|
|
head.appendChild(el("span", "pro-menu-icon", m.icon));
|
|
|
|
|
|
head.appendChild(el("span", "pro-menu-text", m.title));
|
|
|
|
|
|
var arrow = el("span", "pro-submenu-arrow", "▸");
|
|
|
|
|
|
head.appendChild(arrow);
|
2026-08-05 22:43:47 +08:00
|
|
|
|
// AntD Pro inline 菜单交互:整行点击 = 就地展开/收起(不跳转)
|
2026-08-05 16:20:52 +08:00
|
|
|
|
head.addEventListener("click", function (e) {
|
2026-08-05 14:40:15 +08:00
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
wrap.classList.toggle("open");
|
|
|
|
|
|
});
|
|
|
|
|
|
wrap.appendChild(head);
|
|
|
|
|
|
|
|
|
|
|
|
var childBox = el("div", "pro-submenu-items");
|
|
|
|
|
|
var currentChild = activeChildKey();
|
|
|
|
|
|
m.children.forEach(function (c) {
|
|
|
|
|
|
var a = el("a", "pro-subitem" +
|
|
|
|
|
|
(isActiveModule && c.key === currentChild ? " active" : ""));
|
2026-08-05 22:43:47 +08:00
|
|
|
|
var href = prefix + m.url.replace(/\/$/, "/") + c.hash;
|
|
|
|
|
|
a.href = href;
|
2026-08-05 14:40:15 +08:00
|
|
|
|
a.textContent = c.title;
|
|
|
|
|
|
a.title = c.title;
|
2026-08-05 22:43:47 +08:00
|
|
|
|
a.setAttribute("data-child", c.key);
|
|
|
|
|
|
if (SHELL) {
|
|
|
|
|
|
a.addEventListener("click", function (e) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
go(href);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-08-05 14:40:15 +08:00
|
|
|
|
childBox.appendChild(a);
|
|
|
|
|
|
});
|
|
|
|
|
|
wrap.appendChild(childBox);
|
|
|
|
|
|
menu.appendChild(wrap);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-06 10:04:36 +08:00
|
|
|
|
function renderMenu(mods) {
|
2026-08-05 22:43:47 +08:00
|
|
|
|
menu.innerHTML = "";
|
|
|
|
|
|
// AntD Menu.ItemGroup 风格分组标题
|
|
|
|
|
|
var group = el("div", "pro-menu-group-title", "功能模块");
|
|
|
|
|
|
menu.appendChild(group);
|
2026-08-06 10:04:36 +08:00
|
|
|
|
// 菜单即模块清单:Epic #159 Phase 2 起由 session.js menuTree()
|
|
|
|
|
|
// 输出(FBA 服务端按角色授权过滤,本地角色过滤兜底)
|
|
|
|
|
|
(mods || []).forEach(function (m) {
|
2026-08-05 22:43:47 +08:00
|
|
|
|
if (m.children && m.children.length) addSubMenu(m);
|
|
|
|
|
|
else addLeafItem(m.key, m.icon, m.title, prefix + m.url);
|
|
|
|
|
|
});
|
|
|
|
|
|
if (!SHELL) syncSubHighlight();
|
|
|
|
|
|
// 菜单(重)渲染完成事件:shell.js 据此重放当前高亮,
|
|
|
|
|
|
// 消除「导航先于菜单渲染」的竞态(直达链接/刷新场景)。
|
|
|
|
|
|
window.dispatchEvent(new CustomEvent("pro:menu-rendered"));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* hash → 子菜单高亮同步(独立模式:同页切换与分享直达都经过这里) */
|
2026-08-05 17:33:39 +08:00
|
|
|
|
function syncSubHighlight() {
|
|
|
|
|
|
var h = activeChildKey();
|
|
|
|
|
|
menu.querySelectorAll(".pro-subitem").forEach(function (x) {
|
|
|
|
|
|
var href = x.getAttribute("href") || "";
|
|
|
|
|
|
var hash = href.indexOf("#") >= 0 ? href.slice(href.indexOf("#")) : "";
|
|
|
|
|
|
x.classList.toggle("active", !!h && hash === "#" + h);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-05 22:43:47 +08:00
|
|
|
|
/* ---- 外壳模式:供 shell.js 同步菜单高亮与顶栏页题 ---------------------- */
|
|
|
|
|
|
if (SHELL) {
|
|
|
|
|
|
window.IAOP_PRO = {
|
|
|
|
|
|
setActive: function (moduleKey, childHash, moduleTitle) {
|
|
|
|
|
|
menu.querySelectorAll(".pro-menu-item").forEach(function (x) {
|
|
|
|
|
|
x.classList.remove("active");
|
|
|
|
|
|
});
|
|
|
|
|
|
menu.querySelectorAll(".pro-subitem").forEach(function (x) {
|
|
|
|
|
|
x.classList.remove("active");
|
|
|
|
|
|
});
|
|
|
|
|
|
if (!moduleKey) return;
|
|
|
|
|
|
var wrap = menu.querySelector('.pro-submenu[data-module="' + moduleKey + '"]');
|
|
|
|
|
|
if (wrap) {
|
|
|
|
|
|
wrap.classList.add("open");
|
|
|
|
|
|
wrap.querySelector(".pro-submenu-title").classList.add("active");
|
|
|
|
|
|
if (childHash) {
|
|
|
|
|
|
var sub = wrap.querySelector('.pro-subitem[data-child="' + childHash + '"]');
|
|
|
|
|
|
if (sub) sub.classList.add("active");
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
var leaf = menu.querySelector('.pro-menu-item[data-module="' + moduleKey + '"]');
|
|
|
|
|
|
if (leaf) leaf.classList.add("active");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (moduleTitle) titleEl.textContent = moduleTitle;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2026-08-05 14:31:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-06 10:04:36 +08:00
|
|
|
|
/* ---- 用户区 + 菜单(FBA 服务端授权优先,本地角色过滤兜底) ------------- */
|
2026-08-05 14:17:37 +08:00
|
|
|
|
IAOP_SESSION.currentUser().then(function (u) {
|
|
|
|
|
|
userBox.innerHTML = "";
|
|
|
|
|
|
if (!u) {
|
|
|
|
|
|
userBox.appendChild(el("span", "pro-tag pro-tag-default", "未登录"));
|
2026-08-06 10:04:36 +08:00
|
|
|
|
renderMenu([]);
|
2026-08-05 14:17:37 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var avatar = el("span", "pro-avatar",
|
|
|
|
|
|
(u.username || "?").slice(0, 1).toUpperCase());
|
|
|
|
|
|
userBox.appendChild(avatar);
|
|
|
|
|
|
userBox.appendChild(el("span", null, u.username));
|
|
|
|
|
|
var color = IAOP_SESSION.ROLE_TAG_COLORS[u.role] || "default";
|
|
|
|
|
|
var label = IAOP_SESSION.ROLE_LABELS[u.role] || u.role;
|
|
|
|
|
|
userBox.appendChild(el("span", "pro-tag pro-tag-" + color, label));
|
2026-08-06 10:04:36 +08:00
|
|
|
|
// 菜单异步:FBA token 存在时取服务端授权菜单树(Phase 2)
|
|
|
|
|
|
IAOP_SESSION.menuTree(u).then(renderMenu);
|
2026-08-05 14:17:37 +08:00
|
|
|
|
});
|
2026-08-05 14:40:15 +08:00
|
|
|
|
|
|
|
|
|
|
bindPageTabs();
|
2026-08-05 22:43:47 +08:00
|
|
|
|
// 子菜单高亮全局同步(独立模式:同页 hash 切换 / 分享直达 / 跨页返回)
|
|
|
|
|
|
if (!SHELL) window.addEventListener("hashchange", syncSubHighlight);
|
2026-08-05 14:17:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (document.readyState === "loading") {
|
|
|
|
|
|
document.addEventListener("DOMContentLoaded", build);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
build();
|
|
|
|
|
|
}
|
|
|
|
|
|
})();
|