/** * Layui Admin Module - Theme v2.7.1 * 功能:后台管理系统的核心模块,负责布局、导航、标签页、主题、事件管理等。 */ layui.define("view", function (exports) { // 依赖模块 var $ = layui.jquery, laytpl = layui.laytpl, table = layui.table, element = layui.element, util = layui.util, upload = layui.upload, setter = layui.setter, view = layui.view, device = layui.device(); // DOM 元素缓存 var win = $(window), doc = $(document), body = $("body"), container = $("#" + setter.container), MAIN_BODY_ID = "#LAY_app_body", FLEXIBLE_BTN_ID = "LAY_app_flexible", TABS_CONTAINER_ID = "layadmin-layout-tabs", SIDE_SPREAD_CLASS = "layadmin-side-spread-sm", TABS_BODY_ITEM_CLASS = "layadmin-tabsbody-item", SHRINK_ICON_CLASS = "layui-icon-shrink-right", SPREAD_ICON_CLASS = "layui-icon-spread-left", SIDE_SHRINK_CLASS = "layadmin-side-shrink"; // 核心 Admin 对象 var admin = { version: "2.7.1", mode: "iframe", // 默认使用 iframe 模式 req: view.req, // 复用 view 模块的请求方法 exit: view.exit, // 复用 view 模块的退出方法 escape: util.escape, // 复用工具方法 // 事件监听快捷方式 on: function (eventName, callback) { return layui.onevent.call(this, setter.appName, eventName, callback); }, /** * 根据当前路由高亮侧边栏菜单 * @param {Object} hashObj - 路由哈希对象(可选,默认从当前URL解析) */ setSideMenuStatus: function (hashObj) { hashObj = hashObj || layui.hash(); var $sideMenu = $("#LAY-system-side-menu"), activeClass = "layui-nav-itemed"; // 移除所有激活状态 $sideMenu.find(".layui-this").removeClass("layui-this"); // 如果是小屏幕,自动处理侧边栏伸缩 admin.screen() < 2 && admin.sideFlexible(); var currentPath = hashObj.path.join("/"); // 遍历所有菜单项,匹配当前路由 $sideMenu.find("li, dd").each(function () { var $this = $(this), $link = $this.children("a"), href = admin.correctRouter($link.attr("lay-href")); // 匹配逻辑:完全匹配或路径包含 if (href === hashObj.href || href === currentPath || (href !== "/" && hashObj.href.indexOf(href) !== -1)) { // 激活当前项 $sideMenu.find(".layui-this").removeClass("layui-this"); $this.addClass("layui-this"); // 展开父级菜单 var $parentItem = $this.closest(".layui-nav-item"), $parentDd = $this.parent().closest("dd"); function expandItem($elem) { $elem.addClass(activeClass).siblings().removeClass(activeClass); } expandItem($parentItem); expandItem($parentDd); // 滚动到可视区域 util.toVisibleArea({ scrollElem: container.find(".layui-side-scroll"), thisElem: $this }); } }); }, /** * 获取屏幕尺寸等级 * @returns {Number} 3: >1200px, 2: 992-1200px, 1: 768-992px, 0: <768px */ screen: function () { var width = win.width(); if (width > 1200) return 3; if (width > 992) return 2; if (width > 768) return 1; return 0; }, /** * 侧边栏伸缩切换 * @param {String} status - 'spread' 展开,其他或空为收缩 */ sideFlexible: function (status) { var $flexBtn = $("#" + FLEXIBLE_BTN_ID), screenSize = admin.screen(); if (status === "spread") { $flexBtn.removeClass(SPREAD_ICON_CLASS).addClass(SHRINK_ICON_CLASS); if (screenSize < 2) { container.addClass(SIDE_SPREAD_CLASS); } else { container.removeClass(SIDE_SPREAD_CLASS); } container.removeClass(SIDE_SHRINK_CLASS); } else { $flexBtn.removeClass(SHRINK_ICON_CLASS).addClass(SPREAD_ICON_CLASS); if (screenSize < 2) { container.removeClass(SIDE_SHRINK_CLASS); } else { container.addClass(SIDE_SHRINK_CLASS); } container.removeClass(SIDE_SPREAD_CLASS); } // 触发自定义事件 layui.event.call(this, setter.appName, "side({*})", { status: status }); }, // 弹窗相关方法(复用 view 模块) popup: view.popup, popupRight: function (options) { return admin.popup.index = layer.open($.extend({ type: 1, id: "LAY_adminPopupR", anim: -1, title: false, closeBtn: false, offset: "r", shade: 0.1, shadeClose: true, skin: "layui-anim layui-anim-rl layui-layer-adminRight", area: "300px" }, options)); }, /** * 动态切换主题 * @param {Object} themeConfig - 主题配置对象 */ theme: function (themeConfig) { var localTheme = layui.data(setter.appName).theme; var styleId = "LAY_layadmin_theme"; var oldStyle = document.getElementById(styleId); var newStyle = document.createElement("style"); // 清除主题 if (themeConfig.CLEAR) { $(oldStyle).remove(); layui.data(setter.appName, { key: "theme", remove: true }); return; } // 合并配置并生成 CSS themeConfig = $.extend({}, localTheme, themeConfig); var cssTemplate = [ ".layui-side-menu,", ".layui-layer-admin .layui-layer-title,", ".layadmin-side-shrink .layui-side-menu .layui-nav>.layui-nav-item>.layui-nav-child", "{background-color:{{d.color.main}} !important;}", ".layadmin-pagetabs .layui-tab-title li:after,", ".layadmin-pagetabs .layui-tab-title li.layui-this:after,", ".layui-nav-tree .layui-this,", ".layui-nav-tree .layui-this>a,", ".layui-nav-tree .layui-nav-child dd.layui-this,", ".layui-nav-tree .layui-nav-child dd.layui-this a,", ".layui-nav-tree .layui-nav-bar", "{background-color:{{d.color.selected}} !important;}", ".layadmin-pagetabs .layui-tab-title li:hover,", ".layadmin-pagetabs .layui-tab-title li.layui-this", "{color: {{d.color.selected}} !important;}", ".layui-layout-admin .layui-logo{background-color:{{d.color.logo || d.color.main}} !important;}", "{{# if(d.color.header){ }}", ".layui-layout-admin .layui-header{background-color:{{ d.color.header }};}", ".layui-layout-admin .layui-header a,", ".layui-layout-admin .layui-header a cite{color: #f8f8f8;}", ".layui-layout-admin .layui-header a:hover{color: #fff;}", ".layui-layout-admin .layui-header .layui-nav .layui-nav-more{border-top-color: #fbfbfb;}", ".layui-layout-admin .layui-header .layui-nav .layui-nav-mored{border-color: transparent; border-bottom-color: #fbfbfb;}", ".layui-layout-admin .layui-header .layui-this:after, .layui-layout-admin .layui-header .layui-nav-bar{background-color: #fff; background-color: rgba(255,255,255,.5);}", ".layadmin-pagetabs .layui-tab-title li:after{display: none;}", "{{# } }}" ].join(""); var cssContent = laytpl(cssTemplate).render(themeConfig); // 插入样式 if ("styleSheet" in newStyle) { newStyle.setAttribute("type", "text/css"); newStyle.styleSheet.cssText = cssContent; } else { newStyle.innerHTML = cssContent; } newStyle.id = styleId; if (oldStyle && body.removeChild) { body[0].removeChild(oldStyle); } console.log(body); body[0].appendChild(newStyle); // 更新主题别名和本地存储 if (themeConfig.color) { body.attr("layadmin-themealias", themeConfig.color.alias); } localTheme = localTheme || {}; $.each(themeConfig, function (key, value) { localTheme[key] = value; }); layui.data(setter.appName, { key: "theme", value: localTheme }); }, /** * 初始化主题(从配置中读取) * @param {Number} index - 主题颜色索引 */ initTheme: function (index) { var themeConfig = setter.theme; if (themeConfig.color && themeConfig.color[index]) { themeConfig.color[index].index = index; admin.theme({ color: themeConfig.color[index] }); } }, // 标签页状态 tabsPage: {}, /** * 获取标签页内容区域 * @param {Number} index - 标签页索引 */ tabsBody: function (index) { return $(MAIN_BODY_ID).find("." + TABS_BODY_ITEM_CLASS).eq(index || 0); }, /** * 切换标签页内容 * @param {Number} index - 要切换到的标签页索引 * @param {Object} data - 标签页数据 {url, title} */ tabsBodyChange: function (index, data) { data = data || {}; admin.tabsBody(index).addClass("layui-show").siblings().removeClass("layui-show"); admin.events.rollPage("auto", index); admin.recordURL(data); layui.event.call(this, setter.appName, "tabsPage({*})", data); }, /** * 记录当前URL到本地存储(用于刷新恢复) * @param {Object} data - {url, title} */ recordURL: function (data) { var recordData; if ((setter.record || {}).url && data.url) { // 过滤外部链接 if (/^(\w*:)*\/\/.+/.test(data.url)) { data.url = ""; } // 更新 hash location.hash = admin.correctRouter(data.url); // 存储标题 if (data.url && data.title) { (recordData = {})[data.url] = data.title; layui.data(setter.appName, { key: "record", value: recordData }); } } }, /** * 窗口大小变化监听管理 * @param {Function} fn - 回调函数,传入 'off' 则移除监听 */ resize: function (fn) { var routeKey = layui.hash().path.join("-"); if (admin.resizeFn[routeKey]) { win.off("resize", admin.resizeFn[routeKey]); delete admin.resizeFn[routeKey]; } if (fn !== "off") { fn(); admin.resizeFn[routeKey] = fn; win.on("resize", admin.resizeFn[routeKey]); } }, // 存储 resize 回调函数 resizeFn: {}, /** * 手动触发当前路由对应的 resize 回调 */ runResize: function () { var routeKey = layui.hash().path.join("-"); admin.resizeFn[routeKey] && admin.resizeFn[routeKey]; }, /** * 移除当前路由的 resize 监听 */ delResize: function () { this.resize("off"); }, /** * 关闭当前标签页 */ closeThisTabs: function () { admin.tabsPage.index && $(".layui-tab-title li").eq(admin.tabsPage.index).find(".layui-tab-close").trigger("click"); }, /** * 全屏 */ fullScreen: function () { var docEl = document.documentElement; var requestFullscreen = docEl.requestFullscreen || docEl.webkitRequestFullScreen || docEl.mozRequestFullScreen || docEl.msRequestFullscreen; if (typeof requestFullscreen !== "undefined" && requestFullscreen) { requestFullscreen.call(docEl); } }, /** * 退出全屏 */ exitScreen: function () { if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if (document.webkitCancelFullScreen) { document.webkitCancelFullScreen(); } else if (document.msExitFullscreen) { document.msExitFullscreen(); } }, /** * 修正路由路径 * @param {String} path - 原始路径 */ correctRouter: function (path) { path = /^\//.test(path) ? path : "/" + path; path = path.replace(/^(\/+)/, "/").replace(new RegExp("/" + setter.entry + "$"), "/"); return path; } }; // 事件处理对象 var events = admin.events = { /** * 侧边栏伸缩按钮事件 */ flexible: function ($elem) { var isSpread = $elem.find("#" + FLEXIBLE_BTN_ID).hasClass(SPREAD_ICON_CLASS); admin.sideFlexible(isSpread ? "spread" : null); }, /** * 刷新当前页 */ refresh: function () { var tabCount = $("." + TABS_BODY_ITEM_CLASS).length; // 防止索引越界 if (admin.tabsPage.index >= tabCount) { admin.tabsPage.index = tabCount - 1; } admin.tabsBody(admin.tabsPage.index).find(".layadmin-iframe")[0].contentWindow.location.reload(true); }, /** * 顶部搜索框事件 */ serach: function ($searchInput) { $searchInput.off("keypress").on("keypress", function (e) { var actionUrl, title; if (this.value.replace(/\s/g, "") && e.keyCode === 13) { // 回车键 actionUrl = $searchInput.attr("lay-action"); title = $searchInput.attr("lay-title") || $searchInput.attr("lay-text") || "搜索"; actionUrl += this.value; title = title + ": " + admin.escape(this.value); admin.openTabsPage({ url: actionUrl, title: title, highlight: "color: #FF5722;" }); // 记录搜索关键词 events.serach.keys || (events.serach.keys = {}); events.serach.keys[admin.tabsPage.index] = this.value; // 如果与上次搜索相同,则刷新页面 if (this.value === events.serach.keys[admin.tabsPage.index]) { events.refresh($searchInput); } this.value = ""; } }); }, /** * 清除消息红点 */ message: function ($elem) { $elem.find(".layui-badge-dot").remove(); }, /** * 打开主题设置面板 */ theme: function () { admin.popupRight({ id: "LAY_adminPopupTheme", success: function () { view(this.id).render("theme"); } }); }, /** * 打开便签面板 */ note: function ($btn) { var isSmallScreen = admin.screen() < 2; var savedNote = layui.data(setter.appName).note; events.note.index = admin.popup({ title: "本地便签", shade: 0, offset: ["41px", isSmallScreen ? null : $btn.offset().left - 250 + "px"], anim: -1, id: "LAY_adminNote", skin: "layadmin-note layui-anim layui-anim-upbit", content: '', resize: false, success: function (elem, index) { elem.find("textarea") .val(typeof savedNote === "undefined" ? "便签中的内容会存储在本地,这样即便你关掉了浏览器,在下次打开时,依然会读取到上一次的记录。是个非常小巧实用的本地备忘录。" : savedNote) .focus() .on("keyup", function () { layui.data(setter.appName, { key: "note", value: this.value }); }); } }); }, /** * 全屏/退出全屏切换 */ fullscreen: function ($btn, data) { function toggleIcon(isFullscreen) { isFullscreen ? $btn.addClass("layui-icon-screen-restore").removeClass("layui-icon-screen-full") : $btn.addClass("layui-icon-screen-full").removeClass("layui-icon-screen-restore"); } var $icon = $btn.children("i"); var isFullscreen = $icon.hasClass("layui-icon-screen-full"); if (data) { return toggleIcon(data.status); } toggleIcon(isFullscreen); isFullscreen ? admin.fullScreen() : admin.exitScreen(); }, /** * 打开关于面板 */ about: function () { admin.popupRight({ id: "LAY_adminPopupAbout", success: function () { view(this.id).render("about"); } }); }, /** * 退出登录(对齐 layuiadmin 标准) * 之前的 events 中没有 logout,导致后台/会员中心点击"退出"无反应。 * 这里统一:清本地登录态与菜单缓存 -> 调后端登出 -> 跳登录页。 */ logout: function () { var appName = setter.appName; var segs = location.pathname.split('/').filter(Boolean); var prefix = segs.length ? '/' + segs[0] : ''; // 1. 清空本应用命名空间下的登录态与菜单缓存,避免同浏览器串号、残留旧菜单 ['access_token', 'refresh_token', 'menus', 'user', 'note', 'theme', 'record'] .forEach(function (key) { layui.data(appName, { key: key, remove: true }); }); // 2. 调后端登出接口(best-effort,失败也不影响前端清理) try { layui.http.post(prefix + '/login/logout'); } catch (e) {} // 3. 跳登录页(优先顶级窗口,兼容 iframe 版) var loginUrl = prefix + '/login/index'; if (window.self !== window.top) { window.top.location.href = loginUrl; } else { location.href = loginUrl; } }, /** * 打开更多面板 */ more: function () { admin.popupRight({ id: "LAY_adminPopupMore", success: function () { view(this.id).render("system/more"); } }); }, /** * 浏览器后退 */ back: function () { history.back(); }, /** * 设置主题颜色 */ setTheme: function ($colorBtn) { var colorIndex = $colorBtn.data("index"); var prevIndex = $colorBtn.siblings(".layui-this").data("index"); if (!$colorBtn.hasClass("layui-this")) { $colorBtn.addClass("layui-this").siblings(".layui-this").removeClass("layui-this"); admin.initTheme(colorIndex); view("LAY_adminPopupTheme").render("theme"); } }, /** * 标签页滚动 * @param {String} type - 'left' 向左,'auto' 自动,其他向右 * @param {Number} index - 目标标签页索引(用于 auto 模式) */ rollPage: function (type, index) { var $tabsHeader = $("#LAY_app_tabsheader"); var $tabItems = $tabsHeader.children("li"); var headerWidth = $tabsHeader.outerWidth(); var currentLeft = parseFloat($tabsHeader.css("left")); if (type === "left") { if (currentLeft && currentLeft <= 0) { var targetPos = -currentLeft - headerWidth; $tabItems.each(function (i, elem) { var pos = $(elem).position().left; if (targetPos <= pos) { $tabsHeader.css("left", -pos); return false; // 退出循环 } }); } } else if (type === "auto") { var $targetItem = $tabItems.eq(index); if ($targetItem) { var itemLeft = $targetItem.position().left; // 如果目标项在可视区域左侧之外,滚动到它 if (itemLeft < -currentLeft) { $tabsHeader.css("left", -itemLeft); return; } // 如果目标项在可视区域右侧之外,滚动到它 var itemRight = itemLeft + $targetItem.outerWidth(); if (itemRight >= headerWidth - currentLeft) { var overflow = itemRight - (headerWidth - currentLeft); $tabItems.each(function (i, elem) { var pos = $(elem).position().left; if (0 < pos + currentLeft && overflow < pos - currentLeft) { $tabsHeader.css("left", -pos); return false; } }); } } } else { // 向右滚动到第一个在可视区域右侧的项 $tabItems.each(function (i, elem) { var $elem = $(elem); var pos = $elem.position().left; if (pos + $elem.outerWidth() >= headerWidth - currentLeft) { $tabsHeader.css("left", -pos); return false; } }); } }, leftPage: function () { events.rollPage("left"); }, rightPage: function () { events.rollPage(); }, closeThisTabs: function () { (parent === self ? admin : parent.layui.admin).closeThisTabs(); }, closeOtherTabs: function (type) { var removeClass = "LAY-system-pagetabs-remove"; var currentIndex = admin.tabsPage.index; var $allTabs = $(".layui-tab-title li"); var $allBodies = $(MAIN_BODY_ID).find("." + TABS_BODY_ITEM_CLASS); if (type === "all") { // 关闭所有(保留第一个) $allTabs.filter(":gt(0)").remove(); $allBodies.filter(":gt(0)").remove(); $allTabs.eq(0).trigger("click"); } else if (type === "right") { // 关闭右侧所有 $allTabs.filter(":gt(" + currentIndex + ")").remove(); $allBodies.filter(":gt(" + currentIndex + ")").remove(); } else { // 关闭其他(保留当前) $allTabs.each(function (i) { if (i && i != currentIndex) { $(this).addClass(removeClass); admin.tabsBody(i).addClass(removeClass); } }); $("." + removeClass).remove(); } }, closeRightTabs: function () { events.closeOtherTabs("right"); }, closeAllTabs: function () { events.closeOtherTabs("all"); }, shade: function () { admin.sideFlexible(); }, im: function () { admin.popup({ id: "LAY-popup-layim-demo", shade: 0, area: ["800px", "300px"], title: "面板外的操作示例", offset: "lb", success: function () { layui.view(this.id).render("layim/demo").then(function () { layui.use("im"); }); } }); } }; // 初始化逻辑 (function init() { var $tabHeaders = $("#LAY_app_tabsheader > li"); var $win = $(window); var $doc = $(document); // 标签页配置检查 if (!("pageTabs" in layui.setter)) { layui.setter.pageTabs = true; } if (!setter.pageTabs) { $("#LAY_app_tabs").addClass("layui-hide"); container.addClass("layadmin-tabspage-none"); } // IE 版本提示 if (device.ie && device.ie < 10) { view.error("IE" + device.ie + "下访问可能不佳,推荐使用:Chrome / Firefox / Edge 等高级浏览器", { offset: "auto", id: "LAY_errorIE" }); } // 主题初始化 var localData = layui.data(setter.appName); if (localData.theme) { admin.theme(localData.theme); } else if (setter.theme) { admin.initTheme(setter.theme.initColorIndex); } // 标签页切换事件 element.on("tab(" + TABS_CONTAINER_ID + ")", function (data) { admin.tabsPage.index = data.index; }); // 监听标签页切换,更新侧边栏菜单状态 admin.on("tabsPage(setMenustatus)", function (data) { var hashObj = layui.hash("#" + admin.correctRouter(data.url)); admin.setSideMenuStatus(hashObj); }); // 侧边栏菜单点击事件(小屏自动展开) element.on("nav(layadmin-system-side-menu)", function (data) { if (data.siblings(".layui-nav-child") && container.hasClass(SIDE_SHRINK_CLASS)) { admin.sideFlexible("spread"); layer.close(data.data("index")); } admin.tabsPage.type = "nav"; }); // 标签页导航点击事件 element.on("nav(layadmin-pagetabs-nav)", function (data) { var $parent = $(data.elem).parent(); $parent.removeClass("layui-this"); $parent.parent().removeClass("layui-show"); }); // 标签页头点击事件 body.on("click", "#LAY_app_tabsheader > li", function () { var $this = $(this); var index = $this.index(); admin.tabsPage.type = "tab"; admin.tabsPage.index = index; // 切换标签页内容 var layId = $this.attr("lay-id"); $this.attr("lay-attr"); // 未使用 admin.tabsBodyChange(index, { url: layId, title: $this.children("span").text() }); }); // 标签页关闭事件 element.on("tabDelete(" + TABS_CONTAINER_ID + ")", function (data) { var $activeTab = $(".layui-tab-title li.layui-this"); if (data.index) { admin.tabsBody(data.index).remove(); } // 切换到上一个标签 var layId = $activeTab.attr("lay-id"); $activeTab.attr("lay-attr"); // 未使用 admin.tabsBodyChange($activeTab.index(), { url: layId, title: $activeTab.children("span").text() }); admin.delResize(); }); // 带 lay-href 属性的链接点击事件(打开新标签页) body.on("click", "*[lay-href]", function () { var $this = $(this); var href = $this.attr("lay-href"); var title = $this.attr("lay-title") || $this.attr("lay-text") || $this.text(); admin.tabsPage.elem = $this; (parent === self ? layui : setter.parentLayui || top.layui).ywxapp.openTabsPage({ url: href, title: title }); // 如果开启标签页且点击的是当前页链接,则刷新 if (setter.pageTabs && setter.refreshCurrPage && href === admin.tabsBody(admin.tabsPage.index).find("iframe").attr("src")) { events.refresh(); } }); // 自定义事件委托(layadmin-event 属性) body.on("click", "*[layadmin-event]", function () { var $this = $(this); var eventName = $this.attr("layadmin-event"); if (events[eventName]) { events[eventName].call(this, $this); } }); // 提示框(lay-tips 属性) body.on("mouseenter", "*[lay-tips]", function () { var $this = $(this); // 侧边栏收缩状态下或非导航项才显示提示 if (!$this.parent().hasClass("layui-nav-item") || container.hasClass(SIDE_SHRINK_CLASS)) { var tips = $this.attr("lay-tips"); var offset = $this.attr("lay-offset"); var direction = $this.attr("lay-direction"); var tipsIndex = layer.tips(tips, this, { tips: direction || 1, time: -1, success: function (elem, index) { if (offset) { elem.css("margin-left", offset + "px"); } } }); $this.data("index", tipsIndex); } }).on("mouseleave", "*[lay-tips]", function () { layer.close($(this).data("index")); }); // 窗口大小变化处理(防抖) layui.data.resizeSystem = function () { layer.closeAll("tips"); if (!layui.data.resizeSystem.lock) { setTimeout(function () { admin.sideFlexible(admin.screen() < 2 ? "" : "spread"); delete layui.data.resizeSystem.lock; }, 100); layui.data.resizeSystem.lock = true; } }; $win.on("resize", layui.data.resizeSystem); // 全屏变化事件 $doc.on("fullscreenchange", function () { events.fullscreen($('[layadmin-event="fullscreen"]'), { status: document.fullscreenElement }); }); // 全局请求 Token 注入 var reqConfig = setter.request; if (reqConfig.tokenName) { var headers = {}; headers[reqConfig.tokenName] = layui.data(setter.appName)[reqConfig.tokenName] || ""; table.set({ headers: headers, where: headers }); upload.set({ headers: headers, data: headers }); } })(); // 导出模块 exports("ywxapp", admin); });