|
|
@@ -1,26 +1,229 @@
|
|
|
<%
|
|
|
// 侧边栏的默认显示样式:1:默认显示侧边栏;2:默认折叠侧边栏
|
|
|
-var sidebarStyle = @Global.getConfig('sys.index.sidebarStyle', '1');
|
|
|
-var sidebarCollapse = (sidebarStyle == '2' ? 'sidebar-collapse' : '');
|
|
|
-var bodyClass = 'fixed noscroll2 sidebar-mini ' + sidebarCollapse;
|
|
|
+var sidebarStyle = @Global.getConfig('sys.index.sidebarStyle', '1'); // 读取侧边栏默认样式配置
|
|
|
+var sidebarCollapse = (sidebarStyle == '2' ? 'sidebar-collapse' : ''); // 根据配置决定是否默认折叠
|
|
|
+var bodyClass = 'fixed noscroll2 sidebar-mini ' + sidebarCollapse; // 拼接页面 body 使用的 class
|
|
|
%>
|
|
|
-<% layout('/layouts/default.html', {title: '', bodyClass: bodyClass, libs: ['tabPage']}){ %>
|
|
|
-<!--[if lte IE 8]><script>window.location.href='${ctxStatic}/upbw/index.html';</script><![endif]-->
|
|
|
-<link rel="stylesheet" href="${ctxStatic}/jquery-toastr/2.1/toastr.min.css?${_version}">
|
|
|
-<link rel="stylesheet" href="${ctxStatic}/modules/sys/sysIndex.css?${_version}">
|
|
|
-<header class="main-header">
|
|
|
- <% include('/include/sysIndex/topMenu.html'){} %>
|
|
|
-</header>
|
|
|
-<aside class="main-sidebar">
|
|
|
- <% include('/include/sysIndex/leftMenu.html'){} %>
|
|
|
-</aside>
|
|
|
-<div class="content-wrapper">
|
|
|
- <div id="tabpanel"></div>
|
|
|
-</div>
|
|
|
-<% } %>
|
|
|
+<% layout('/layouts/default.html', {title: '', bodyClass: bodyClass, libs: ['tabPage']}){ %><!-- 使用默认布局,并启用 tabPage 页签库 -->
|
|
|
+<!--[if lte IE 8]><script>window.location.href='${ctxStatic}/upbw/index.html';</script><![endif]--><!-- 低版本 IE 跳转提示页 -->
|
|
|
+<link rel="stylesheet" href="${ctxStatic}/jquery-toastr/2.1/toastr.min.css?${_version}"><!-- 消息提示样式 -->
|
|
|
+<link rel="stylesheet" href="${ctxStatic}/modules/sys/sysIndex.css?${_version}"><!-- 主框架原有样式 -->
|
|
|
+<style>/* 顶部搜索框样式开始 */
|
|
|
+.navbar-menu-search{position:absolute;top:8px;right:430px;width:280px;z-index:1050}/* 搜索框固定在顶部右侧 */
|
|
|
+.menu-search-control{height:34px;border-radius:3px;background:rgba(255,255,255,.18);border:1px solid rgba(255,255,255,.28);display:flex;align-items:center;color:#fff}/* 搜索输入框外壳 */
|
|
|
+.menu-search-control .fa-search{width:34px;text-align:center;font-size:14px;opacity:.9}/* 左侧放大镜 */
|
|
|
+.menu-search-control input{flex:1;min-width:0;height:32px;border:0;background:transparent;color:#fff;outline:0;font-size:13px}/* 输入框本体 */
|
|
|
+.menu-search-control input::placeholder{color:rgba(255,255,255,.78)}/* 提示文字颜色 */
|
|
|
+.menu-search-control button{display:none;width:30px;height:32px;border:0;background:transparent;color:rgba(255,255,255,.85);outline:0}/* 清空按钮默认隐藏 */
|
|
|
+.menu-search-control.has-value button{display:block}/* 有内容时显示清空按钮 */
|
|
|
+.menu-search-control:focus-within{background:#fff;color:#3c8dbc;border-color:#fff;box-shadow:0 2px 10px rgba(0,0,0,.18)}/* 聚焦时高亮 */
|
|
|
+.menu-search-control:focus-within input{color:#333}/* 聚焦后输入文字变深色 */
|
|
|
+.menu-search-control:focus-within input::placeholder{color:#999}/* 聚焦后提示文字变灰 */
|
|
|
+.menu-search-control:focus-within button{color:#777}/* 聚焦后清空按钮变灰 */
|
|
|
+.menu-search-result{display:none;position:absolute;top:38px;left:0;right:0;max-height:360px;overflow:auto;margin:0;padding:6px 0;list-style:none;background:#fff;border:1px solid #d2d6de;border-radius:3px;box-shadow:0 4px 14px rgba(0,0,0,.18)}/* 下拉结果面板 */
|
|
|
+.menu-search-result li{padding:8px 12px;line-height:18px;cursor:pointer;color:#333}/* 单条结果样式 */
|
|
|
+.menu-search-result li:hover,.menu-search-result li.active{background:#f3f7fb;color:#1f6fb2}/* 鼠标悬停和键盘选中 */
|
|
|
+.menu-search-result .menu-title{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}/* 菜单标题,只显示最后一级 */
|
|
|
+.menu-search-result .empty{cursor:default;color:#999}/* 没结果时的提示 */
|
|
|
+@media (max-width:1200px){.navbar-menu-search{right:330px;width:240px}}/* 中等屏幕收窄 */
|
|
|
+@media (max-width:992px){.navbar-menu-search{display:none}}/* 小屏隐藏 */
|
|
|
+</style><!-- 顶部搜索框样式结束 -->
|
|
|
+<header class="main-header"><!-- 顶部栏容器 -->
|
|
|
+ <% include('/include/sysIndex/topMenu.html'){} %><!-- 引入顶部菜单模板 -->
|
|
|
+</header><!-- 顶部栏容器结束 -->
|
|
|
+<aside class="main-sidebar"><!-- 左侧菜单容器 -->
|
|
|
+ <% include('/include/sysIndex/leftMenu.html'){} %><!-- 引入左侧菜单模板 -->
|
|
|
+</aside><!-- 左侧菜单容器结束 -->
|
|
|
+<div class="content-wrapper"><!-- 主内容区域 -->
|
|
|
+ <div id="tabpanel"></div><!-- 页签容器,菜单打开后会显示在这里 -->
|
|
|
+</div><!-- 主内容区域结束 -->
|
|
|
+<% } %><!-- 默认布局结束 -->
|
|
|
+<!-- 隐藏的首页页签配置 -->
|
|
|
<div class="hide" id="desktopTabPage" data-title="${text('仪表盘')}"
|
|
|
- data-url="${ctx}${@Global.getConfig('sys.index.desktopUrl')}"></div>
|
|
|
-<div class="hide" id="modifyPasswordTip" data-message="${modifyPasswordTip!}"></div>
|
|
|
-<script src="${ctxStatic}/jquery-toastr/2.1/toastr.min.js?${_version}"></script>
|
|
|
-<script src="${ctxStatic}/jquery-plugins/jquery.slimscroll.js"></script>
|
|
|
-<script src="${ctxStatic}/modules/sys/sysIndex.js?${_version}"></script>
|
|
|
+ data-url="${ctx}${@Global.getConfig('sys.index.desktopUrl')}"></div><!-- 首页页签地址 -->
|
|
|
+<div class="hide" id="modifyPasswordTip" data-message="${modifyPasswordTip!}"></div><!-- 修改密码提醒配置 -->
|
|
|
+<script src="${ctxStatic}/jquery-toastr/2.1/toastr.min.js?${_version}"></script><!-- 消息提示脚本 -->
|
|
|
+<script src="${ctxStatic}/jquery-plugins/jquery.slimscroll.js"></script><!-- 滚动条插件 -->
|
|
|
+<script src="${ctxStatic}/modules/sys/sysIndex.js?${_version}"></script><!-- 主框架原有脚本 -->
|
|
|
+<script>// 菜单搜索脚本开始
|
|
|
+(function($){// 让脚本只依赖 jQuery
|
|
|
+ $(function(){// 页面加载完成后再执行
|
|
|
+ var $box = $('#menuSearch');// 搜索框外层
|
|
|
+ var $input = $('#menuSearchInput');// 输入框
|
|
|
+ var $clear = $('#menuSearchClear');// 清空按钮
|
|
|
+ var $result = $('#menuSearchResult');// 下拉结果列表
|
|
|
+ var menuItems = [];// 叶子菜单数据,只保存最后一级菜单
|
|
|
+ var activeIndex = -1;// 当前高亮的结果下标
|
|
|
+ var maxResultSize = 12;// 最多显示多少条
|
|
|
+ if (!$box.length || !$input.length || !$result.length){// 找不到页面元素就退出
|
|
|
+ return;// 防止报错
|
|
|
+ }// 初始化检查结束
|
|
|
+ function cleanText(text){// 清洗文本
|
|
|
+ return $.trim(String(text || '').replace(/\s+/g, ' '));// 去掉多余空格
|
|
|
+ }// 清洗文本结束
|
|
|
+ function normalize(text){// 转成统一格式,方便模糊匹配
|
|
|
+ return cleanText(text).toLowerCase();// 统一成小写
|
|
|
+ }// 统一格式结束
|
|
|
+ function html(text){// 把内容转成安全的 HTML
|
|
|
+ return $('<div/>').text(text || '').html();// 防止标签注入
|
|
|
+ }// 安全转义结束
|
|
|
+ function getLinkTitle($link){// 取菜单名称
|
|
|
+ return cleanText($link.attr('data-title') || $link.children('span').first().text() || $link.text());// 优先取标题属性
|
|
|
+ }// 取菜单名称结束
|
|
|
+ function getLinkHref($link){// 取菜单地址
|
|
|
+ var href = $link.attr('data-href') || $link.attr('href') || '';// 先找 data-href
|
|
|
+ return cleanText(href);// 再做清洗
|
|
|
+ }// 取菜单地址结束
|
|
|
+ function isLeafMenu($link){// 判断是不是最后一级菜单
|
|
|
+ var $li = $link.closest('li');// 找到当前菜单的 li
|
|
|
+ if (!$li.length){// 没有 li 就不是菜单
|
|
|
+ return false;// 直接排除
|
|
|
+ }// li 判断结束
|
|
|
+ if ($li.find('ul.treeview-menu, ul.menu-open, ul').length){// 还有子菜单就不是叶子
|
|
|
+ return false;// 直接排除
|
|
|
+ }// 子菜单判断结束
|
|
|
+ return true;// 剩下的就是叶子菜单
|
|
|
+ }// 判断叶子菜单结束
|
|
|
+ function canOpenHref(href){// 判断地址是否可直接打开
|
|
|
+ return href && href !== '#' && href !== 'javascript:' && href.indexOf('javascript:void') !== 0;// 排除空地址和占位地址
|
|
|
+ }// 判断地址结束
|
|
|
+ function collectMenus(){// 收集可搜索的叶子菜单
|
|
|
+ var exists = {};// 用来去重
|
|
|
+ $('#menuSearchSource a, #leftMenu a, #topMenu a').each(function(){// 遍历页面里的菜单链接
|
|
|
+ var $link = $(this);// 当前链接对象
|
|
|
+ var title = getLinkTitle($link);// 当前链接标题
|
|
|
+ var href = getLinkHref($link);// 当前链接地址
|
|
|
+ if (!title || !canOpenHref(href) || !isLeafMenu($link)){// 只保留最后一级且可打开的菜单,避免父级菜单 404
|
|
|
+ return;// 不符合就跳过
|
|
|
+ }// 过滤条件结束
|
|
|
+ var key = title + '|' + href;// 组合去重键
|
|
|
+ if (exists[key]){// 如果已经收集过
|
|
|
+ return;// 直接跳过
|
|
|
+ }// 去重判断结束
|
|
|
+ exists[key] = true;// 标记已收集
|
|
|
+ menuItems.push({// 记录到搜索列表
|
|
|
+ title: title,// 只显示最后一级菜单名
|
|
|
+ href: href,// 点击后跳转的地址
|
|
|
+ text: normalize(title + ' ' + href)// 搜索时使用的文本
|
|
|
+ });// 一条菜单记录结束
|
|
|
+ });// 遍历结束
|
|
|
+ }// 收集菜单结束
|
|
|
+ function fuzzyMatch(text, keyword){// 模糊匹配
|
|
|
+ if (!keyword){// 关键词为空时不匹配
|
|
|
+ return false;// 直接返回
|
|
|
+ }// 空关键词判断结束
|
|
|
+ if (text.indexOf(keyword) > -1){// 先做普通包含匹配
|
|
|
+ return true;// 包含就算命中
|
|
|
+ }// 包含匹配结束
|
|
|
+ var index = 0;// 记录关键词匹配到哪一位
|
|
|
+ for (var i = 0; i < text.length && index < keyword.length; i++){// 按字符顺序尝试匹配
|
|
|
+ if (text.charAt(i) === keyword.charAt(index)){// 当前字符相等就往后走
|
|
|
+ index++;// 命中下一位
|
|
|
+ }// 单字符判断结束
|
|
|
+ }// 循环结束
|
|
|
+ return index === keyword.length;// 所有关键字都按顺序命中才算成功
|
|
|
+ }// 模糊匹配结束
|
|
|
+ function search(keyword){// 执行搜索
|
|
|
+ keyword = normalize(keyword);// 搜索词先统一格式
|
|
|
+ if (!keyword){// 没有搜索词时
|
|
|
+ return [];// 返回空数组
|
|
|
+ }// 空搜索词判断结束
|
|
|
+ var list = [];// 搜索结果数组
|
|
|
+ $.each(menuItems, function(_, item){// 遍历所有叶子菜单
|
|
|
+ if (fuzzyMatch(item.text, keyword)){// 命中就加入结果
|
|
|
+ list.push(item);// 保存结果
|
|
|
+ }// 命中判断结束
|
|
|
+ });// 遍历结束
|
|
|
+ list.sort(function(a, b){// 结果简单排序
|
|
|
+ var aTitle = normalize(a.title);// 取当前标题
|
|
|
+ var bTitle = normalize(b.title);// 取对比标题
|
|
|
+ var aScore = aTitle.indexOf(keyword) === 0 ? 0 : (aTitle.indexOf(keyword) > -1 ? 1 : 2);// 更靠前的关键词排前面
|
|
|
+ var bScore = bTitle.indexOf(keyword) === 0 ? 0 : (bTitle.indexOf(keyword) > -1 ? 1 : 2);// 同样计算另一个结果
|
|
|
+ return aScore - bScore || a.title.length - b.title.length;// 先比命中位置,再比长度
|
|
|
+ });// 排序结束
|
|
|
+ return list.slice(0, maxResultSize);// 最多显示固定条数
|
|
|
+ }// 搜索结束
|
|
|
+ function setActive(index){// 设置当前高亮项
|
|
|
+ var $items = $result.children('li[data-index]');// 取所有结果项
|
|
|
+ activeIndex = index;// 保存当前下标
|
|
|
+ $items.removeClass('active');// 先清掉旧高亮
|
|
|
+ if (activeIndex >= 0 && activeIndex < $items.length){// 下标合法时
|
|
|
+ $items.eq(activeIndex).addClass('active');// 给当前项加高亮
|
|
|
+ }// 高亮判断结束
|
|
|
+ }// 设置高亮结束
|
|
|
+ function render(list){// 渲染结果列表
|
|
|
+ $result.empty();// 先清空旧结果
|
|
|
+ activeIndex = -1;// 重置高亮
|
|
|
+ if (!list.length){// 没有结果时
|
|
|
+ $result.append('<li class="empty">未找到匹配菜单</li>').show();// 显示空提示
|
|
|
+ return;// 结束渲染
|
|
|
+ }// 空结果判断结束
|
|
|
+ $.each(list, function(index, item){// 遍历结果
|
|
|
+ $result.append(// 追加每一条结果
|
|
|
+ '<li data-index="' + index + '">' +// 结果项
|
|
|
+ '<div class="menu-title">' + html(item.title) + '</div>' +// 只显示最后一级菜单名
|
|
|
+ '</li>'// 结束结果项
|
|
|
+ );// 追加完成
|
|
|
+ });// 遍历结束
|
|
|
+ $result.data('items', list).show();// 保存结果并显示
|
|
|
+ setActive(0);// 默认高亮第一条
|
|
|
+ }// 渲染结束
|
|
|
+ function hideResult(){// 隐藏结果
|
|
|
+ $result.hide().empty().removeData('items');// 收起面板并清空缓存
|
|
|
+ activeIndex = -1;// 重置高亮
|
|
|
+ }// 隐藏结束
|
|
|
+ function openMenu(item){// 打开菜单
|
|
|
+ if (!item){// 没有选中项时
|
|
|
+ return;// 不处理
|
|
|
+ }// 空值判断结束
|
|
|
+ $input.val('');// 清空输入框
|
|
|
+ $input.closest('.menu-search-control').removeClass('has-value');// 移除按钮显示状态
|
|
|
+ hideResult();// 关闭结果面板
|
|
|
+ if (window.js && $.isFunction(window.js.addTabPage)){// 如果框架支持打开页签
|
|
|
+ window.js.addTabPage(null, item.title, item.href);// 用页签打开
|
|
|
+ }else{// 如果没有页签能力
|
|
|
+ window.location.href = item.href;// 直接跳转
|
|
|
+ }// 打开方式判断结束
|
|
|
+ }// 打开菜单结束
|
|
|
+ collectMenus();// 初始化时收集菜单
|
|
|
+ $input.on('input', function(){// 输入时实时搜索
|
|
|
+ var value = this.value;// 当前输入值
|
|
|
+ $input.closest('.menu-search-control').toggleClass('has-value', !!value);// 控制清空按钮显示
|
|
|
+ if (!value){// 为空就收起结果
|
|
|
+ hideResult();// 隐藏结果
|
|
|
+ return;// 结束
|
|
|
+ }// 为空判断结束
|
|
|
+ render(search(value));// 执行搜索并渲染
|
|
|
+ }).on('keydown', function(e){// 键盘控制上下选择和回车打开
|
|
|
+ var items = $result.data('items') || [];// 取当前结果列表
|
|
|
+ if (e.keyCode === 38 || e.keyCode === 40){// 上下方向键
|
|
|
+ if (!items.length){// 没有结果就不处理
|
|
|
+ return;// 直接返回
|
|
|
+ }// 结果为空判断结束
|
|
|
+ e.preventDefault();// 阻止光标移动
|
|
|
+ setActive(e.keyCode === 38/* 按上方向键 */
|
|
|
+ ? (activeIndex <= 0 ? items.length - 1 : activeIndex - 1)/* 上一条 */
|
|
|
+ : (activeIndex >= items.length - 1 ? 0 : activeIndex + 1));/* 下一条 */
|
|
|
+ }else if (e.keyCode === 13){// 回车键
|
|
|
+ e.preventDefault();// 阻止表单提交
|
|
|
+ openMenu(items[activeIndex] || items[0]);// 打开当前项或第一项
|
|
|
+ }else if (e.keyCode === 27){// Esc 键
|
|
|
+ hideResult();// 直接关闭
|
|
|
+ }// 键盘分支结束
|
|
|
+ });// 键盘绑定结束
|
|
|
+ $clear.on('click', function(){// 点击清空按钮
|
|
|
+ $input.val('').trigger('input').focus();// 清空并重新聚焦
|
|
|
+ });// 清空按钮结束
|
|
|
+ $result.on('mousedown', 'li[data-index]', function(e){// 点击某一条结果
|
|
|
+ e.preventDefault();// 防止输入框失焦过早
|
|
|
+ var items = $result.data('items') || [];// 取结果数组
|
|
|
+ openMenu(items[$(this).data('index')]);// 打开对应菜单
|
|
|
+ });// 结果点击结束
|
|
|
+ $(document).on('mousedown', function(e){// 点击页面其他地方时
|
|
|
+ if (!$(e.target).closest('#menuSearch').length){// 如果点的不是搜索框内部
|
|
|
+ hideResult();// 就关闭结果
|
|
|
+ }// 点击区域判断结束
|
|
|
+ });// 文档点击结束
|
|
|
+ });// 页面加载结束
|
|
|
+})(jQuery);// 立即执行结束
|
|
|
+</script><!-- 菜单搜索脚本结束 -->
|