/** * 移动端侧栏增强(视口 ≤ 768px) * * 为什么要这个脚本 * ---------------- * Pear Admin 的"侧栏折叠"由三处状态共同决定,缺一个就会错位: * * 1. body/.pear-admin 上的 `pear-mini` —— 决定整页布局(CSS 把 .layui-side 压成 0/60px) * 2. #side 上的 `pear-nav-mini` —— 决定菜单自身宽度、子菜单是内联展开还是悬浮弹出 * 3. PearAdmin.instances.menu.isCollapse —— 框架内部状态机,决定后续 collapse 的方向 * * 框架把这三个状态的同步封装在 admin.js 的**私有函数** collapse() 里, * 而它只由 `body.on("click", ".collapse,.pear-cover")` 这一个委托触发。 * 外部拿不到该函数,因此"手动 addClass('pear-mini')"必然导致 1 和 2 反相: * 布局说已折叠(侧栏 0px、菜单文字被隐藏),菜单却还停留在展开态, * 于是出现「侧栏只剩一列图标」「子菜单点开后不收起」的现象。 * * 本脚本的原则:**折叠一律走框架原生入口**(触发 .collapse 的点击), * 绝不手写 class,从而保证三处状态永远同步。 */ (function () { "use strict"; var MOBILE_MAX = 768; function isMobile() { return window.innerWidth <= MOBILE_MAX; } function getAdminEl() { return document.querySelector(".pear-admin"); } function isCollapsed() { var el = getAdminEl(); return !!(el && el.classList.contains("pear-mini")); } function menuRendered() { return document.querySelectorAll("#side .layui-nav-item").length > 0; } /** * 折叠/展开侧栏:只触发框架委托的 .collapse 点击。 * 这样 admin.js 的私有 collapse() 会同步 body class、#side 的 pear-nav-mini、 * isCollapse 以及顶栏图标,不会出现状态错位。 */ function toggleViaFramework() { var btn = document.querySelector("li.collapse") || document.querySelector(".collapse"); if (!btn) { return false; } btn.click(); return true; } /** * 补齐框架内部状态 isCollapse。 * 后端 /rights/configs 里菜单配置的键拼成了 "collaspe"(框架读 "collapse"), * 因此 admin.js 的 done 回调会把 isCollapse 覆写为 undefined(falsy)。 * 折叠后若 isCollapse 不为 true,窄屏下任何 resize 都会触发框架的反向 * collapse,把已经折叠的侧栏重新弹开 —— 这里把状态补齐。 */ function patchCollapseState() { try { var m = window.PearAdmin && window.PearAdmin.instances && window.PearAdmin.instances.menu; if (m) { m.isCollapse = true; } } catch (e) { /* 忽略:状态补齐失败不影响布局正确性 */ } } // --------------------------------------------------------------------- // 1) 首屏折叠:菜单是异步渲染的,轮询等它就绪后再确认折叠(幂等) // --------------------------------------------------------------------- (function foldOnStart() { var tries = 0; var timer = setInterval(function () { if (++tries > 40) { // 最多等约 6 秒,避免无限轮询 clearInterval(timer); return; } if (!isMobile() || !menuRendered()) { return; } if (isCollapsed()) { // 框架自己(resize 兼容逻辑)已完成折叠 clearInterval(timer); patchCollapseState(); return; } if (toggleViaFramework()) { // 否则由我们走原生入口折叠 clearInterval(timer); patchCollapseState(); } }, 150); })(); // --------------------------------------------------------------------- // 2) 触屏补位:mini 模式下子菜单是「悬浮浮层」,靠 mouseenter/mouseleave 开关, // 而触屏 tap 只可靠触发 mouseenter、mouseleave 常常不来,浮层就赖着不走。 // 这里在点菜单以外的地方时主动收起浮层。 // --------------------------------------------------------------------- function closeFlyout() { var list = document.querySelectorAll("#side .layui-nav-child.layui-nav-hover"); for (var i = 0; i < list.length; i++) { list[i].classList.remove("layui-nav-hover"); list[i].style.left = "0px"; list[i].style.top = "0px"; } } document.addEventListener("touchstart", function (e) { if (!isMobile()) { return; } var t = e.target; if (t && t.closest && t.closest("#side")) { return; // 点在菜单里,交给框架自己处理 } closeFlyout(); }, { passive: true }); // --------------------------------------------------------------------- // 3) 移动端习惯:展开态下点了叶子菜单(要跳转的项)之后自动收起侧栏, // 否则 220px 的侧栏会一直压在内容上。目录项(有子菜单)不收起。 // 注:a.site-demo-active 只挂在可跳转的叶子菜单上,目录项没有这个 class。 // --------------------------------------------------------------------- document.addEventListener("click", function (e) { if (!isMobile()) { return; } var t = e.target; if (!t || !t.closest) { return; } if (!t.closest('#side a.site-demo-active')) { return; } if (isCollapsed()) { return; } setTimeout(function () { if (!isCollapsed()) { toggleViaFramework(); } }, 250); }); })();