文章目录
前言
这是一个在实习中的小项目,其中需要使用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', '');
// 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', '');
// 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%;"&rt;</iframe&rt;',
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%;"&rt;</iframe&rt;',
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>
Comments NOTHING