前言
这是一个在实习中的小项目,其中需要使用LayUI进行构造前端,JSON控制菜单的控制面板。
简介
LayUI
LayUI是一种基于JavaScript的前端框架,让Web开发变得更加容易。而JSON(JavaScript Object Notation)则是一种用于数据交换的轻量级格式,被广泛用于Web应用程序中。
LayUI的特点:
- 简单易用:LayUI的API设计非常简单,易于学习和使用。
- 功能丰富:LayUI提供了丰富的UI组件,包括按钮、表单、弹窗、表格等。
- 灵活性高:LayUI提供了丰富的配置项,可以灵活地满足不同的需求。
- 高性能:LayUI的渲染速度非常快,可以提高用户的使用体验。
LayUI的使用非常广泛,特别是在后台管理系统、企业管理系统、电商平台等领域。如果想要快速构建一个Web应用程序,LayUI是一个不错的选择。
这里就拿LayUI当做练手。LayUI文档:http://layui.org.cn
JSON
JSON(JavaScript Object Notation)是一种用于数据交换的轻量级格式,它易于阅读和编写,同时也易于解析和生成。JSON格式由键值对组成,键和值之间使用冒号分隔,键值对之间使用逗号分隔,最外层使用花括号或方括号括起来。
实践
先放一个实践图:

Header
在head部分,需要添加LayUI的CSS:
<link rel="stylesheet" href="layui/css/layui.css">我的布局包含了:头部、菜单列表和选项卡容器。头部包含了一个网站的标志和用户信息,菜单列表包含了网站的主要导航菜单,选项卡容器包含了用户在网站中打开的各个选项卡。
Layout
布局代码如下:
<!-- LayUI layout --> <div class="layui-layout layui-layout-admin"> <!-- Header section --> <div class="layui-header"> <div class="layui-logo">Hoyue Panel</div> <ul class="layui-nav layui-layout-right"> <li class="layui-nav-item"> Welcome, Hoyue </li> <li class="layui-nav-item"><a href="#" onclick="out_msg()">Log Out</a></li> </ul> </div> <!-- Menu list --> <div class="layui-side layui-bg-black"> <div class="layui-side-scroll"> <ul id="menu" class="layui-nav layui-nav-tree" lay-filter="meun"> </ul> </div> </div> <!-- Tab container --> <div class="layui-tab" lay-filter="demo" lay-allowclose="true" style="margin-left: 200px;"> <ul class="layui-tab-title"></ul> <div class="layui-tab-content"></div> </div> </div>其中,菜单栏的id为menu,便于我之后通过JSON导入。
Logic
首先就是在尾部载入LayUI的JS:
<script src="layui/layui.js"></script>在加载菜单之前,我们先来看看JSON文件的格式。以被要求的格式为例:
[ { "menuid":"00", "menu":"Home", "parentid": "", "operationid": "", "levelno": "1", "notes": "", "url": "home.html", "active": "1" }, { "menuid":"01", "menu":"Website", "parentid": "", "operationid": "", "levelno": "1", "notes": "", "url": "", "active": "1" }, { "menuid":"0101", "menu":"Files", "parentid": "01", "operationid": "", "levelno": "2", "notes": "", "url": "file.html", "active": "1" }, { "menuid":"0102", "menu":"Logs", "parentid": "01", "operationid": "", "levelno": "2", "notes": "", "url": "", "active": "1" }, { "menuid":"010201", "menu":"Logs 1", "parentid": "0102", "operationid": "", "levelno": "3", "notes": "", "url": "log1.html", "active": "1" }, { "menuid":"010202", "menu":"Logs 2", "parentid": "0102", "operationid": "", "levelno": "3", "notes": "", "url": "log2.html", "active": "1" }, { "menuid":"02", "menu":"Database", "parentid": "", "operationid": "", "levelno": "1", "notes": "", "url": "", "active": "1" }, { "menuid":"0201", "menu":"Database Lists", "parentid": "02", "operationid": "", "levelno": "2", "notes": "", "url": "db.html", "active": "1" }, { "menuid":"0202", "menu":"Admin", "parentid": "02", "operationid": "", "levelno": "2", "notes": "", "url": "", "active": "0" }, { "menuid":"03", "menu":"Panel Setting", "parentid": "", "operationid": "", "levelno": "1", "notes": "", "url": "setting.html", "active": "1" }, { "menuid":"04", "menu":"Log Out", "parentid": "", "operationid": "", "levelno": "1", "notes": "", "url": "", "active": "1" }]其中:
menuid:菜单项的ID,用于唯一标识该菜单项。menu:菜单项的标题,用于在菜单列表中显示。parentid:该菜单项的父菜单项ID,用于构建菜单项的层级结构。operationid:该菜单项对应的操作ID,用于记录用户操作日志等。这个在这个例子中没有读取。levelno:该菜单项在菜单项层级结构中的层级编号,从1开始递增。notes:该菜单项的备注信息。url:该菜单项对应的页面URL,用于在选项卡组件中打开对应的页面。active:该菜单项是否处于激活状态,1表示激活,0表示不激活。
加载菜单
首先是加载菜单数据,它是从一个JSON文件中动态获取的:
//Load menu from JSON function loadMenu() { fetch('ComMenu.json') //JSON文件URL .then(response => response.json()) .then(data => { const menuElement = document.getElementById('menu'); for (let i = 0; i < data.length; i++) { const item = data[i]; if(item.active == 0) continue; else { const menuItem = createMenuItem(item); menuElement.appendChild(menuItem); } } }); }这部分代码使用Fetch API发起请求,获取菜单的JSON数据。然后使用Promise处理响应,将JSON解析为JavaScript对象。最后遍历对象中的菜单数据,动态插入到页面中。
这样实现的好处是前后端分离,菜单数据可配置化,只需要修改JSON文件就可以改变菜单内容,无需修改代码。并且利用Promise可以非常优雅地处理异步请求。
在读取数组数据的时候,判断active位,如果为0就跳过。菜单在createMenuItem()方法中创建,使用appendChild()方法累加。
处理主菜单
该函数接收一个菜单项的JSON数据,并根据数据创建对应的菜单项DOM节点。
//Create Main menu function createMenuItem(item) { // Handle submenu if(item.parentid){ return subChild(item); }
// create li and a const li = document.createElement('li'); li.classList.add('layui-nav-item'); const a = document.createElement('a'); a.href = 'javascript:;'; a.textContent = item.menu;
// Construct li li.appendChild(a);
// Set ID and data attributes a.setAttribute("id",item.menuid); a.setAttribute('data-id', item.menuid); a.setAttribute('data-title', item.menu); a.setAttribute('data-url', item.url); a.setAttribute('data-type', 'tabAdd');
// Add styles a.classList.add('site-demo-active');
// Special handle for logout link if(a.textContent == "Log Out") { a.setAttribute("onclick","out_msg()"); a.setAttribute("href","#"); a.classList.remove('site-demo-active'); }
return li; }对于创建菜单,我分为创建一级菜单和多级子菜单,因为它们在静态上不同。首先判断菜单项的parentid字段是否存在,如果存在则调用subChild()函数创建子菜单项。如果不存在,则创建一个简单的菜单项。
创建菜单项所需的DOM节点包括一个<li>标签和一个<a>标签,其中<li>标签用于表示菜单项,<a>标签用于显示菜单项的标题和处理点击事件。
在创建<a>标签时,需要设置其href属性为javascript:;,以便在点击时不会跳转到其他页面。同时,还需要设置一些自定义的属性,包括菜单项的ID、标题、URL和类型等,以便在用户点击菜单项时打开对应的选项卡。
在设置完这些属性之后,还需要为菜单项添加一些样式,以便在菜单列表中显示为激活状态。特别地,对于”Log Out”菜单项需要进行特殊处理,即添加onclick事件处理函数和删除一些样式。
最后,函数返回创建好的菜单项DOM节点。使用这个函数可以方便地创建菜单项,并在用户点击菜单项时处理对应的事件。
处理子菜单
与主菜单类似的,但子菜单需要多做一些特判。
// Handle submenu function subChild(item){ // Get parent a and remove parent's click style const parentA = document.getElementById(item.parentid); parentA.classList.remove('site-demo-active');
/* Level 2 submenu, get parent li Level 3 submenu, get parent dl */ if(item.levelno == 2) var parentLi = parentA.parentElement; else var parentLi = parentA.closest('dd');
// Query and Add drop down icon var iElement = parentA.querySelector('i.layui-icon.layui-icon-down.layui-nav-more'); if (!iElement) { parentA.insertAdjacentHTML('beforeend', '<i class="layui-icon layui-icon-down layui-nav-more"></i>'); // Click handler to expand/collapse parentA.addEventListener('click', (e) => { if (parentLi.classList.contains('layui-nav-itemed')) { parentLi.classList.remove('layui-nav-itemed'); } else { parentLi.classList.add('layui-nav-itemed'); } }) };
// Create dl and dd const dl = document.createElement('dl'); dl.classList.add('layui-nav-child'); parentLi.appendChild(dl);
const dd = document.createElement('dd'); dl.appendChild(dd);
// Create submenu a const sub_a = document.createElement('a'); sub_a.href = 'javascript:;'; sub_a.textContent = item.menu; dd.appendChild(sub_a);
// Set attributes sub_a.setAttribute('id', item.menuid); sub_a.setAttribute('data-id', item.menuid); sub_a.setAttribute('data-title', item.menu); sub_a.setAttribute('data-url', item.url); sub_a.setAttribute('data-type', 'tabAdd'); sub_a.classList.add('site-demo-active');
// Retrun li if(item.levelno == "3") // level 3 may a dl return parentLi.closest('li'); return parentLi; }在函数中,首先获取该子菜单项的父菜单项<a>标签,并移除其激活样式。然后根据子菜单项的层级编号判断其父菜单项的类型,如果是二级菜单,则获取父菜单项的<li>标签,否则获取父菜单项的最近的<dd>标签。
接下来,需要为父菜单项添加一个下拉箭头图标,以便在用户点击时展开或收起子菜单项。如果已经存在下拉箭头图标,则不需要再次添加。同时,还需要为父菜单项的<a>标签添加一个点击事件处理函数,用于展开或收起子菜单项。
在添加完下拉箭头图标和点击事件处理函数之后,需要创建子菜单项的DOM节点。该节点包括一个<dl>标签和一个<dd>标签,用于表示子菜单项的层级结构。然后再创建一个<a>标签,用于显示子菜单项的标题和处理点击事件。
在创建<a>标签时,需要设置其href属性为javascript:;,以便在点击时不会跳转到其他页面。同时,还需要设置一些自定义的属性,包括菜单项的ID、标题、URL和类型等,以便在用户点击菜单项时打开对应的选项卡。
最后,函数返回创建好的子菜单项DOM节点。如果子菜单项是三级菜单,则需要返回其父菜单项的<li>标签。使用这个函数可以方便地创建子菜单项,并在用户点击菜单项时处理对应的事件。
Tab处理
点击菜单后,会打开一个新的Tab标签页。首先需要检查该Tab是否已存在:
// 点击菜单处理$('#menu').on('click','a', function(){
// 判断Tab是否存在 if($(".layui-tab-title li[lay-id]").length == 0) { // 不存在则新增 } else { var isData = false;
// 循环检查已有的Tab $.each($(".layui-tab-title li[lay-id]"), function(){ if($(this).attr("lay-id") == dataid.attr("data-id")){ isData = true; // 存在 } });
if(isData == false) { // 不存在则新增 } }
// 切换 Tab active.tabChange(id);
});遍历已打开的Tab,检查其id是否与当前点击菜单对应的id相同。如果没有匹配的则表示该Tab未打开,需要新增。
新增和切换Tab的方法封装成了active对象:
// Tab handling object var active = {
tabAdd: function (url, id, name) { element.tabAdd('demo', { title: name, content: '<iframe data-frameid="' + id + '" scrolling="auto" frameborder="0" src="' + url + '" style="width:100%;height:99%;"></iframe>', id: id }) FrameWH(); }, tabChange: function (id) {
element.tabChange('demo', id); }, tabDelete: function (id) { element.tabDelete("demo", id); } };
// Set iframe height function FrameWH() { var h = $(window).height(); $("iframe").css("height",h+"px"); } });这样可以方便统一调用,添加新功能也比较容易通过新增方法实现。
后记
至此,整个小项目的重要逻辑已经写完了,附上一个整篇代码:
<!DOCTYPE html><html><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <title>Hoyue Panel</title> <link rel="stylesheet" href="layui/css/layui.css"></head><body> <!-- LayUI layout --> <div class="layui-layout layui-layout-admin"> <!-- Header section --> <div class="layui-header"> <div class="layui-logo">Hoyue Panel</div> <ul class="layui-nav layui-layout-right"> <li class="layui-nav-item"> Welcome, Hoyue </li> <li class="layui-nav-item"><a href="#" onclick="out_msg()">Log Out</a></li> </ul> </div>
<!-- Menu list --> <div class="layui-side layui-bg-black"> <div class="layui-side-scroll">
<ul id="menu" class="layui-nav layui-nav-tree" lay-filter="meun"> </ul> </div> </div>
<!-- Tab container --> <div class="layui-tab" lay-filter="demo" lay-allowclose="true" style="margin-left: 200px;"> <ul class="layui-tab-title"></ul> <div class="layui-tab-content"></div>
</div>
</div>
<script src="layui/layui.js"></script><script>//Load menu from JSON function loadMenu() { fetch('ComMenu.json') .then(response => response.json()) .then(data => { const menuElement = document.getElementById('menu'); for (let i = 0; i < data.length; i++) { const item = data[i]; if(item.active == 0) continue; else { const menuItem = createMenuItem(item); menuElement.appendChild(menuItem); } } }); }
//Create Main menu function createMenuItem(item) { // Handle submenu if(item.parentid){ return subChild(item); }
// create li and a const li = document.createElement('li'); li.classList.add('layui-nav-item'); const a = document.createElement('a'); a.href = 'javascript:;'; a.textContent = item.menu;
// Construct li li.appendChild(a);
// Set ID and data attributes a.setAttribute("id",item.menuid); a.setAttribute('data-id', item.menuid); a.setAttribute('data-title', item.menu); a.setAttribute('data-url', item.url); a.setAttribute('data-type', 'tabAdd');
// Add styles a.classList.add('site-demo-active');
// Special handle for logout link if(a.textContent == "Log Out") { a.setAttribute("onclick","out_msg()"); a.setAttribute("href","#"); a.classList.remove('site-demo-active'); }
return li; }
// Handle submenu function subChild(item){ // Get parent a and remove parent's click style const parentA = document.getElementById(item.parentid); parentA.classList.remove('site-demo-active');
/* Level 2 submenu, get parent li Level 3 submenu, get parent dl */ if(item.levelno == 2) var parentLi = parentA.parentElement; else var parentLi = parentA.closest('dl');
// Query and Add drop down icon var iElement = parentA.querySelector('i.layui-icon.layui-icon-down.layui-nav-more'); if (!iElement) { parentA.insertAdjacentHTML('beforeend', '<i class="layui-icon layui-icon-down layui-nav-more"></i>'); // Click handler to expand/collapse parentA.addEventListener('click', (e) => { if (parentLi.classList.contains('layui-nav-itemed')) { parentLi.classList.remove('layui-nav-itemed'); } else { parentLi.classList.add('layui-nav-itemed'); } }) };
// Create dl and dd const dl = document.createElement('dl'); dl.classList.add('layui-nav-child'); parentLi.appendChild(dl);
const dd = document.createElement('dd'); dl.appendChild(dd);
// Create submenu a const sub_a = document.createElement('a'); sub_a.href = 'javascript:;'; sub_a.textContent = item.menu; dd.appendChild(sub_a);
// Set attributes sub_a.setAttribute('id', item.menuid); sub_a.setAttribute('data-id', item.menuid); sub_a.setAttribute('data-title', item.menu); sub_a.setAttribute('data-url', item.url); sub_a.setAttribute('data-type', 'tabAdd'); sub_a.classList.add('site-demo-active');
// Retrun li if(item.levelno == "3") // level 3 may a dl return parentLi.closest('li'); return parentLi; } </script><script> loadMenu();
// Initialize LayUI tab layui.use(['element'], function(){ var element = layui.element; element.tabAdd('demo', { title: 'home', content: '<iframe data-frameid="0" scrolling="auto" frameborder="0" src="home.html" style="width:100%;height:99%;"></iframe>', id: 0 }); element.tabChange('demo', 0); });
// Tab handling logic layui.use(['element', 'layer', 'jquery'], function () { var element = layui.element; var $ = layui.$;
// Use event delegation for menu click handlers $('#menu').on('click', '.site-demo-active', function () { var dataid = $(this);
// Check if tab already exists if ($(".layui-tab-title li[lay-id]").length < 0) { // If not, add new tab active.tabAdd(dataid.attr("data-url"), dataid.attr("data-id"), dataid.attr("data-title")); } else { var isData = false;
// Loop through existing tabs $.each($(".layui-tab-title li[lay-id]"), function () {
if ($(this).attr("lay-id") == dataid.attr("data-id")) { isData = true; } })
// If tab doesn't exist, add it if (isData == false) {
active.tabAdd(dataid.attr("data-url"), dataid.attr("data-id"), dataid.attr("data-title")); } }
// Switch to new tab active.tabChange(dataid.attr("data-id"));
// Remove current selected menu style $('#menu .layui-this').removeClass('layui-this');
// Add selected style to clicked menu item $(this).addClass('layui-this'); });
// Tab handling object var active = {
tabAdd: function (url, id, name) { element.tabAdd('demo', { title: name, content: '<iframe data-frameid="' + id + '" scrolling="auto" frameborder="0" src="' + url + '" style="width:100%;height:99%;"></iframe>', id: id }) FrameWH(); }, tabChange: function (id) {
element.tabChange('demo', id); }, tabDelete: function (id) { element.tabDelete("demo", id); } };
// Set iframe height function FrameWH() { var h = $(window).height(); $("iframe").css("height",h+"px"); } });
// Logout message function out_msg() { layer.msg('You have been logged out.', {icon: 6}); }
</script></body></html>