(function () { var element = layui.element, laytpl = layui.laytpl, layer = layui.layer; var miniMenu = { // 菜单数据缓存(renderMultiModule 入口保存,收藏过滤与面板渲染复用;保存原 menuList 引用) menuData: [], // 顶部「全部模块」面板 layer index(null=面板未打开) modulePanelIndex: null, /** * 菜单初始化 * @param options.menuList 菜单数据信息 * @param options.menuChildOpen 是否展开子菜单 */ render: function (options) { options.menuList = options.menuList || []; options.menuChildOpen = options.menuChildOpen || false; if (!ua.checkMobile()) { miniMenu.renderMultiModule(options.menuList, options.menuChildOpen); } else { miniMenu.renderSingleModule(options.menuList, options.menuChildOpen); } miniMenu.listen(); }, /** * 单模块 * @param menuList 菜单数据 * @param menuChildOpen 是否默认展开 */ renderSingleModule: function (menuList, menuChildOpen) { menuList = menuList || []; var leftMenuHtml = '', childOpenClass = '', leftMenuCheckDefault = 'layui-this'; var me = this; if (menuChildOpen) childOpenClass = ' layui-nav-itemed'; leftMenuHtml = this.renderLeftMenu(menuList, { childOpenClass: childOpenClass }); $('.layui-layout-body').addClass('layuimini-single-module'); //单模块标识 $('.layuimini-menu-left').html(leftMenuHtml); element.init(); }, /** * 渲染一级菜单 */ compileMenu: function (menu, isSub) { var menuHtml = ''; if (isSub) { menuHtml = ''; } return laytpl(menuHtml).render(menu); }, compileMenuContainer: function (menu, isSub) { var wrapperHtml = ''; if (isSub) { wrapperHtml = '
{{-d.children}}
'; } if (!menu.children) { return ""; } return laytpl(wrapperHtml).render(menu); }, each: function (list, callback) { var _list = []; for (var i = 0, length = list.length; i < length; i++) { _list[i] = callback(i, list[i]); } return _list; }, renderChildrenMenu: function (menuList, options) { var me = this; menuList = menuList || []; var html = this.each(menuList, function (idx, menu) { if (menu.child && menu.child.length) { menu.children = me.renderChildrenMenu(menu.child, { childOpenClass: options.childOpenClass || '' }); } menu.className = ""; menu.childOpenClass = options.childOpenClass || ''; return me.compileMenu(menu, true); }).join(""); return me.compileMenuContainer({ children: html }, true); }, renderLeftMenu: function (leftMenus, options) { options = options || {}; var me = this; var leftMenusHtml = me.each(leftMenus || [], function (idx, leftMenu) { // 左侧菜单遍历 var children = me.renderChildrenMenu(leftMenu.child, { childOpenClass: options.childOpenClass }); var leftMenuHtml = me.compileMenu({ href: leftMenu.href, target: leftMenu.target, childOpenClass: options.childOpenClass, icon: leftMenu.icon, title: leftMenu.title, children: children, className: '', }); return leftMenuHtml; }).join(""); leftMenusHtml = me.compileMenuContainer({ id: options.parentMenuId, className: options.leftMenuCheckDefault, children: leftMenusHtml }); return leftMenusHtml; }, /** * 头部模块收藏存储适配层(以 title 为唯一标识,未来可整体替换为后端实现) */ getFavorites: function () { var favorites = []; try { favorites = JSON.parse(localStorage.getItem('layuiminiHeaderModuleFavorites')); } catch (e) { favorites = []; } if (!Array.isArray(favorites)) { return []; } // 与当前菜单 title 求交集,过滤已失效项,保持存储顺序 var valid = []; miniMenu.each(miniMenu.menuData || [], function (index, menu) { valid.push(menu.title); }); var result = []; miniMenu.each(favorites, function (index, title) { if (valid.indexOf(title) !== -1) { result.push(title); } }); return result; }, /** * 写回收藏列表 */ setFavorites: function (list) { localStorage.setItem('layuiminiHeaderModuleFavorites', JSON.stringify(list || [])); }, /** * 是否已收藏 */ isFavorite: function (title) { return miniMenu.getFavorites().indexOf(title) !== -1; }, /** * 切换收藏状态,返回切换后的新状态(true=已收藏) */ toggleFavorite: function (title) { var favorites = miniMenu.getFavorites(); var isAdd = favorites.indexOf(title) === -1; if (isAdd) { favorites.push(title); } else { favorites.splice(favorites.indexOf(title), 1); } miniMenu.setFavorites(favorites); return isAdd; }, /** * 头部模块排序:收藏组在前(组内保持菜单原序),未收藏组回填在后(保持菜单原序) * 返回新数组、不修改入参;multi_module_N 下标仍按原 menuList 索引分配,仅排序显示顺序 * 注:收藏以 title 为唯一标识,顶级模块重名视为同一收藏(同时置顶/同时取消)—— * 框架菜单管理实际不产生重名顶级模块,此为已文档化假设而非保证 */ sortModules: function (menuList) { var favorites = miniMenu.getFavorites(); var sorted = []; var backfill = []; miniMenu.each(menuList || [], function (index, menu) { if (favorites.indexOf(menu.title) !== -1) { sorted.push(menu); } else { backfill.push(menu); } }); return sorted.concat(backfill); }, /** * 多模块 * @param menuList 菜单数据 * @param menuChildOpen 是否默认展开 */ renderMultiModule: function (menuList, menuChildOpen) { menuList = menuList || []; var me = this; miniMenu.menuData = menuList; //入口保存,注意为原数组引用 var sortedMenuList = this.sortModules(menuList); //头部显示顺序:收藏置顶+原序回填,仅排序不改下标映射 var firstMenu = 'multi_module_' + menuList.indexOf(sortedMenuList[0]); //排序后首模块,首屏一致性锚点 var headerMenuHtml = '', leftMenuHtml = '', childOpenClass = '', headerMenuCheckDefault = 'layui-this'; if (menuChildOpen) childOpenClass = ' layui-nav-itemed'; var headerMenuHtml = this.each(sortedMenuList, function (index, val) { //顶部菜单渲染(按排序后顺序编译,data-menu 仍按原 menuList 下标映射) var menu = 'multi_module_' + menuList.indexOf(val); var id = menu + "HeaderId"; var topMenuItemHtml = ""; topMenuItemHtml = me.compileMenu({ className: headerMenuCheckDefault, menu: menu, id: id, title: val.title, href: "", target: "", children: "" }); headerMenuCheckDefault = ""; return topMenuItemHtml; }).join(""); leftMenuHtml = this.each(menuList, function (index, val) { //左侧菜单累积与 ul#multi_module_N 分配保持原 menuList 顺序(收藏只影响头部与面板显示顺序) var menu = 'multi_module_' + index; var leftMenuCheckDefault = (menu === firstMenu) ? 'layui-this' : 'layui-hide'; //默认可见 ul=排序后首模块,与头部激活项首屏一致 return me.renderLeftMenu(val.child, { parentMenuId: menu, childOpenClass: childOpenClass, leftMenuCheckDefault: leftMenuCheckDefault }); }).join(""); $('.layui-layout-body').addClass('layuimini-multi-module'); //多模块标识 $('.layuimini-menu-header-pc').html(headerMenuHtml); //电脑 // 常驻「更多」li:PC 多模块路径专属;自身不带 data-menu(不参与折叠遍历),li 带 lay-unselect 防 clickThis 洗牌激活态; // 不手写 span.layui-nav-more(2.10.1 中手写不可见且会抑制自动注入箭头),由下方 element.init() 自动注入 chevron icon $('.layuimini-menu-header-pc').append('
  • 更多
  • '); $('.layuimini-menu-left').html(leftMenuHtml); element.init(); // 首次渲染后重算宽度折叠,并同步「更多」激活态 miniMenu.relayoutHeaderMenu(); miniMenu.syncHeaderActiveState(); }, /** * 头部模块 DOM 重排:头部 li 显示顺序跟随 sortModules(收藏置顶+原序回填),「更多」li 常驻末尾 * 星标收藏切换后调用实现头部即时重排;移动 li 不改类名,激活态/折叠态由后续 relayout 重算 */ reorderHeaderMenu: function () { var $headerMenu = $('.layuimini-menu-header-pc'); var $more = $headerMenu.children('.layuimini-header-more'); if (!$headerMenu.length || !$more.length) { return; } var sortedMenuList = miniMenu.sortModules(miniMenu.menuData); var moreEl = $more[0]; miniMenu.each(sortedMenuList, function (index, menu) { var li = document.getElementById('multi_module_' + miniMenu.menuData.indexOf(menu) + 'HeaderId'); if (li) { $headerMenu[0].insertBefore(li, moreEl); //依次前插到「更多」li 之前,形成排序后顺序 } }); }, /** * 头部模块宽度自适应折叠重算:累计宽度超出可用宽度的模块收纳进「更多」下拉 */ relayoutHeaderMenu: function () { var $headerMenu = $('.layuimini-menu-header-pc'); if (!$headerMenu.is(':visible')) { //可见性守卫:≤1024 时 pc-show 隐藏,测量隐藏容器得到全零宽度脏状态 return; } var $more = $headerMenu.children('.layuimini-header-more'); if (!$more.length) { return; } // 可用宽度 = 右侧功能区左边界 - 头部菜单容器左边界 - 安全边距 - 「更多」自身常驻占位 var availableWidth = $('.layui-layout-right').first().offset().left - $headerMenu.offset().left - 12 - $more.outerWidth(true); var $items = $headerMenu.children('li[data-menu]'); //仅模块 li;「菜单」模板 li 无 data-menu 不会被触及 $items.removeClass('layui-hide'); //测量前先全部回填可见,保证变宽时能恢复 var usedWidth = 0, hiddenMenus = []; $items.each(function () { var $item = $(this); usedWidth += $item.outerWidth(true); if (usedWidth > availableWidth) { $item.addClass('layui-hide'); hiddenMenus.push($item.attr('data-menu')); } }); // 用被隐藏模块按显示顺序重建下拉:dd 带 lay-unselect 防 clickThis 洗牌、带 a 承接主题悬停/颜色样式、不带 id var ddHtml = ''; miniMenu.each(hiddenMenus, function (index, menuId) { var menuIndex = parseInt(menuId.replace('multi_module_', ''), 10); var menu = (miniMenu.menuData || [])[menuIndex] || {}; var icon = menu.icon ? menu.icon : ''; ddHtml += '
    ' + (menu.title || '') + '
    '; }); $more.children('dl.layui-nav-child').html(ddHtml); element.init(); //已核实安全:只重绑事件与 bar span,不重建 dd DOM,激活态得以保留 miniMenu.syncHeaderActiveState(); //折叠可能隐藏当前激活项,重算后立即同步 }, /** * 「更多」与下拉 dd 的激活态同步:激活态真源是头部模块 li(即使被折叠隐藏) */ syncHeaderActiveState: function () { var $more = $('.layuimini-header-more'); if (!$more.length) { return; } var $activeLi = $('.layuimini-header-menu li.layui-this').not('.layuimini-header-more').first(); var isActiveHidden = $activeLi.length > 0 && $activeLi.hasClass('layui-hide'); if (isActiveHidden) { $more.addClass('layui-this'); } else { $more.removeClass('layui-this'); } // 下拉内对应 dd 与隐藏 li 的激活态对齐 var $dd = $more.find('dl.layui-nav-child dd'); $dd.removeClass('layui-this'); if (isActiveHidden) { $dd.filter('[data-menu="' + $activeLi.attr('data-menu') + '"]').addClass('layui-this'); } }, /** * 顶部「全部模块」面板打开:menuData 经 sortModules 平铺为 tile(收藏置顶+回填序) */ openModulePanel: function () { var me = this; var favorites = me.getFavorites(); var sortedMenuList = me.sortModules(me.menuData); var tileHtml = ''; me.each(sortedMenuList, function (index, menu) { // data-header-id 按 menuList 原下标映射(排序只影响显示顺序,与头部 li id 分配规则一致);不带 data-menu,避免与 [data-menu] 委托双触发 var originalIndex = me.menuData.indexOf(menu); var starActive = favorites.indexOf(menu.title) !== -1 ? ' layuimini-star-active' : ''; var icon = menu.icon ? menu.icon : ''; tileHtml += '
    ' + '' + '
    ' + menu.title + '
    ' + '' + '
    '; }); // 配色继承:非透明头部主题时同步头部背景/字色到面板根容器;透明主题(rgba(0,0,0,0))保留 CSS 白底深字兜底 var panelStyle = ''; var headerEl = $('.layui-header').first()[0]; if (headerEl) { var headerStyle = getComputedStyle(headerEl); if (headerStyle.backgroundColor !== 'rgba(0, 0, 0, 0)') { panelStyle = ' style="background-color:' + headerStyle.backgroundColor + ';color:' + headerStyle.color + ';"'; } } // 打开期间收起「更多」hover 下拉,避免遮罩后方残留展开外观(关闭时移除) $('.layuimini-header-more').addClass('layuimini-header-more-panelopen'); me.modulePanelIndex = layer.open({ type: 1, title: '全部模块', anim: 2, offset: 't', area: ['100%', 'auto'], shade: 0.2, shadeClose: true, closeBtn: 0, content: '
    ' + tileHtml + '
    ', end: function () { me.closeModulePanel(); //遮罩/切tile等 layer 原生关闭路径统一走清理 } }); }, /** * 顶部面板关闭:幂等(end 回调重入时 index 已置 null,重复调用安全) */ closeModulePanel: function () { var index = miniMenu.modulePanelIndex; miniMenu.modulePanelIndex = null; if (index) { layer.close(index); } $('.layuimini-header-more').removeClass('layuimini-header-more-panelopen'); }, /** * 轻量防抖:delay 内重复触发只执行最后一次 */ debounce: function (fn, delay) { var timer = null; return function () { var context = this, args = arguments; clearTimeout(timer); timer = setTimeout(function () { fn.apply(context, args); }, delay); }; }, /** * 监听 */ listen: function () { /** * 菜单模块切换 */ $('body').on('click', '[data-menu]', function () { var loading = layer.load(0, { shade: false, time: 2 * 1000 }); var menuId = $(this).attr('data-menu'); var $headerLi = $("#" + menuId + "HeaderId"); // header:清空范围含被折叠隐藏的 li;激活态真源始终是头部模块 li(即使隐藏),不依赖 $(this) 高亮 $(".layuimini-header-menu .layui-nav-item").removeClass('layui-this'); $headerLi.addClass('layui-this'); // 被点击元素不是头部模块 li 本身(即「更多」下拉 dd 等克隆载体)时,才给它视觉高亮 if (!$(this).is($headerLi)) { $(this).addClass('layui-this'); } // left $(".layuimini-menu-left .layui-nav.layui-nav-tree.layui-this").addClass('layui-hide'); $(".layuimini-menu-left .layui-nav.layui-nav-tree.layui-this.layui-hide").removeClass('layui-this'); $("#" + menuId).removeClass('layui-hide'); $("#" + menuId).addClass('layui-this'); miniMenu.syncHeaderActiveState(); layer.close(loading); }); /** * 窗口尺寸变化时重算头部折叠(防抖,仅绑定一次,避免重渲染重复绑定) */ if (!miniMenu._resizeListenerBound) { miniMenu._resizeListenerBound = true; $(window).on('resize', miniMenu.debounce(function () { miniMenu.relayoutHeaderMenu(); }, 150)); } /** * 菜单缩放 */ $('body').on('click', '.layuimini-site-mobile', function () { var loading = layer.load(0, { shade: false, time: 2 * 1000 }); var isShow = $('.layuimini-tool [data-side-fold]').attr('data-side-fold'); if (isShow == 1) { // 缩放 $('.layuimini-tool [data-side-fold]').attr('data-side-fold', 0); $('.layuimini-tool [data-side-fold]').attr('class', 'fa fa-indent'); $('.layui-layout-body').removeClass('layuimini-all'); $('.layui-layout-body').addClass('layuimini-mini'); } else { // 正常 $('.layuimini-tool [data-side-fold]').attr('data-side-fold', 1); $('.layuimini-tool [data-side-fold]').attr('class', 'fa fa-outdent'); $('.layui-layout-body').removeClass('layuimini-mini'); $('.layui-layout-body').addClass('layuimini-all'); layer.close(window.openTips); } element.init(); setTimeout(miniMenu.relayoutHeaderMenu, 260); //左侧布局带 .2s 过渡,同步测量会读到中间值,等过渡结束再重算 layer.close(loading); }); /** * 菜单缩放 */ $('body').on('click', '[data-side-fold]', function () { var loading = layer.load(0, { shade: false, time: 2 * 1000 }); var isShow = $('.layuimini-tool [data-side-fold]').attr('data-side-fold'); if (isShow == 1) { // 缩放 $('.layuimini-tool [data-side-fold]').attr('data-side-fold', 0); $('.layuimini-tool [data-side-fold]').attr('class', 'fa fa-indent'); $('.layui-layout-body').removeClass('layuimini-all'); $('.layui-layout-body').addClass('layuimini-mini'); // $(".menu-li").each(function (idx,el) { // $(el).addClass("hidden-sub-menu"); // }); } else { // 正常 $('.layuimini-tool [data-side-fold]').attr('data-side-fold', 1); $('.layuimini-tool [data-side-fold]').attr('class', 'fa fa-outdent'); $('.layui-layout-body').removeClass('layuimini-mini'); $('.layui-layout-body').addClass('layuimini-all'); // $(".menu-li").each(function (idx,el) { // $(el).removeClass("hidden-sub-menu"); // }); layer.close(window.openTips); } element.init(); setTimeout(miniMenu.relayoutHeaderMenu, 260); //左侧布局带 .2s 过渡,同步测量会读到中间值,等过渡结束再重算 layer.close(loading); }); /** * 顶部「全部模块」面板:委托绑定一次即可(tile/星标均为动态 DOM,重渲染无需重绑) */ if (!miniMenu._panelListenerBound) { miniMenu._panelListenerBound = true; // 打开/切换面板:「更多」li 自身不带 data-menu,stopPropagation 隔离其他 body 委托 $('body').on('click', '.layuimini-header-more > a', function (e) { e.stopPropagation(); if (miniMenu.modulePanelIndex) { miniMenu.closeModulePanel(); } else { miniMenu.openModulePanel(); } }); // ESC 关闭面板(layui layer 原生不支持 ESC,自实现;仅在面板存在时响应,不影响其他弹层) $(document).on('keydown', function (e) { if (e.keyCode === 27 && miniMenu.modulePanelIndex) { miniMenu.closeModulePanel(); } }); // tile 主体点击:触发真实头部 li(可见或折叠隐藏均可),走 [data-menu] 委托完整链路后关闭面板 $('body').on('click', '.layuimini-module-panel-item', function () { $('#' + $(this).attr('data-header-id')).trigger('click'); layer.close(miniMenu.modulePanelIndex); }); // 星标点击:切换收藏 → 头部即时重排(重排序+折叠重算)→ 就地更新星标类名(面板保持打开,支持连续操作) $('body').on('click', '.layuimini-module-panel-item .layuimini-star', function (e) { e.stopPropagation(); //阻断冒泡,避免同时触发 tile 的切模块点击 var isAdd = miniMenu.toggleFavorite($(this).closest('.layuimini-module-panel-item').attr('data-title')); miniMenu.reorderHeaderMenu(); miniMenu.relayoutHeaderMenu(); $(this).toggleClass('layuimini-star-active', isAdd); }); } }, }; window.miniMenu = miniMenu; })();