最近要做一个项目,被要求前端要使用layui,甲方爸爸很牛逼的好吗,既然要求这样了,二话不说,撸起袖子就开干,由于从来没用过layui这个框架,对框架的不熟悉导致在使用的过程中是步步都是障碍啊,还是那句话“好记性不如烂笔头”,那就记录一下都遇到了些什么问题以及一些用法吧。
首先呢,引入这个框架,很简单,直接去官网下载,LAYUI官网地址:https://www.layui.com,官方文档:https://www.layui.com/doc
然后在合适的位置引入框架即可,下面就是最简单的引入方式了:
代码语言:javascript复制<link rel="stylesheet" href="/layui/css/layui.css">
<script src="/layui/layui.js"></script>
再来看看后台经典三栏式布局
代码语言:javascript复制<body class="layui-layout-body" layadmin-themealias="default">
<div class="layadmin-tabspage-none">
<div class="layui-layout layui-layout-admin">
<!-- 头部区域(可配合layui已有的水平导航) -->
<div class="header layui-header"></div>
<!-- 左侧导航区域(可配合layui已有的垂直导航) -->
<div class="left layui-side layui-side-menu "></div>
<!-- 内容主体区域 -->
<div class="right layui-body"></div>
<!-- 用于移动设备下遮罩 -->
<div class="layadmin-body-shade" layadmin-event="shade" display="none"></div>
<!-- 底部固定区域 -->
<div class="footer layui-footer">
© xxx.com
</div>
</div>
</div>
</body>
嗯,一切都很顺利,看着蛮舒服的嘛,但是问题也接踵而至。
1、实现侧边栏显示与隐藏
看官网的后台演示模板(layAdmin),怎么看都比自己这个舒服啊,首先,左边栏是可以隐藏的,按照官方的演示模板,添加了一个按钮,用来显示和隐藏侧边栏(这里需要说明一下,就目前的网页设计要求,不仅仅要PC端使用,还有移动端也是要使用的,所以需要实现左边栏的显示和隐藏),因为设计是右侧(页面内容区域)异步加载(这是最终确定的方案),所以页面上的所有事件的绑定都需要用事件委托来处理(刚开始我也没注意到这个问题,导致后面遇到了其他问题)。
代码很快就写完了,然后进行效果测试,诶···怎么怪怪的,左边栏隐藏和显示实现了啊,但是头部的logo没有隐藏啊,然后不断的查看layui的源码(开启扒站模式),发现要完美呈现官网的layAdmin的侧边栏隐藏效果是需要重新写css的,又重新搞了一下admin.css(重要:需要在外部容器的classname增加“layui-layout-admin”否则admin.css不生效),引入了新的CSS,再看源码是通过修改classname来实现的,于是开始改改改,最后改完了就是下面这个样子的啦。
代码语言:javascript复制 /*
* @todo 左侧导航菜单的显示和隐藏
*/
$('.header').on('click', '.layadmin-flexible', function(event) {
if (device.ios || device.android || dwidth) {
//mobile 移动端用到的classname是“layadmin-side-spread-sm”
$('.layadmin-tabspage-none').toggleClass("layadmin-side-spread-sm");
$('.layadmin-body-shade').toggle();//隐藏移动端遮罩层
}else{
//PC PC上并不是直接隐藏而是变成了图标的小导航,用到的classname是“layadmin-side-shrink”
$('.layadmin-tabspage-none').toggleClass("layadmin-side-shrink");
}
$('.layadmin-flexible i').toggleClass('layui-icon-shrink-right');
$('.layadmin-flexible i').toggleClass('layui-icon-spread-left');
});
//点击遮罩背景,隐藏左侧菜单和遮罩自身
$('.layadmin-body-shade').click(function(event) {
if (device.ios || device.android || dwidth) {
//移动端
$('.layadmin-tabspage-none').toggleClass("layadmin-side-spread-sm");
}else{
//PC端
$('.layadmin-tabspage-none').toggleClass("layadmin-side-shrink");
}
$(this).hide();
});
看到了吧,所有的实现就是这么简单,因为官方没有在文档里面写这些,鼓捣出来这么个结果也是花了一些时间,挺佩服我自己的呢!
注意:上面的代码用到了device模块,在使用之前必须先加载(use),详细的可以参照官方的加载所需模块
2、实现右侧内容部分的异步加载(局部刷新)
刚开始想到的是直接用html的iframe来实现,很快就实现了,而且用起来还算正常,但被否定了,因为iframe一旦多起来之后,浏览器的开销就相当大,这样对于手机端是很不利的,所以毅然决然的毙了这种方案,代码如下:
HTML部分
代码语言:javascript复制<div class="main layui-tab" lay-allowclose="true" lay-filter="frame" style="padding: 15px;">
<ul class="layui-tab-title"></ul>
<div class="layui-tab-content"></div>
</div>
<!-- 左侧导航区域(可配合layui已有的垂直导航) -->
<div class="left layui-side layui-side-menu ">
<div class="layui-side-scroll">
<div class="layui-logo" lay-href=""><span>layui</span></div>
<ul class="layui-nav layui-nav-tree" lay-filter="menu">
<li class="layui-nav-item layui-nav-itemed">
<a lay-href="/index/index/no1" data-id="1" data-title="菜单1" data-type="tabAdd">
<i class="layui-icon layui-icon-dollar"></i>
<cite>菜单1</cite>
</a>
</li>
<li class="layui-nav-item layui-nav-itemed">
<a lay-href="/index/index/no2" data-id="2" data-title="菜单2" data-type="tabAdd">
<i class="layui-icon layui-icon-read"></i>
<cite>菜单2</cite>
</a>
</li>
</ul>
</div>
</div>
JS部分
代码语言:javascript复制// 配置tab实践在下面无法获取到菜单元素
$('body').on('click',".layui-nav-itemed a", function () {
var dataid = $(this);
//这时会判断右侧.layui-tab-title属性下的有lay-id属性的li的数目,即已经打开的tab项数目
if ($(".layui-tab-title li[lay-id]").length <= 0) {
//如果比零小,则直接打开新的tab项
active.tabAdd(dataid.attr("lay-href"), dataid.attr("data-id"), dataid.attr("data-title"));
} else {
//否则判断该tab项是否以及存在
var isData = false; //初始化一个标志,为false说明未打开该tab项 为true则说明已有
$.each($(".layui-tab-title li[lay-id]"), function () {
//如果点击左侧菜单栏所传入的id 在右侧tab项中的lay-id属性可以找到,则说明该tab项已经打开
if ($(this).attr("lay-id") == dataid.attr("data-id")) {
isData = true;
}
})
if (isData == false) {
//标志为false 新增一个tab项
active.tabAdd(dataid.attr("lay-href"), dataid.attr("data-id"), dataid.attr("data-title"));
}
}
//最后不管是否新增tab,最后都转到要打开的选项页面上
active.tabChange(dataid.attr("data-id"));
});
var active = {
//在这里给active绑定几项事件,后面可通过active调用这些事件
tabAdd:function (url, id, name) {
//新增一个Tab项 传入三个参数,分别对应其标题,tab页面的地址,还有一个规定的id,是标签中data-id的属性值
//关于tabAdd的方法所传入的参数可看layui的开发文档中基础方法部分
element.tabAdd('frame', {
title:name,
content:'<iframe data-frameid="' id '" scrolling="auto" frameborder="0" src="' url '" style="width:100%;height:99%;"></iframe>',
id:id //规定好的id
})
FrameWH(); //计算ifram层的大小
},
tabChange:function (id) {
//切换到指定Tab项
element.tabChange('frame', id); //根据传入的id传入到指定的tab项
},
tabDelete:function (id) {
element.tabDelete("frame", id);//删除
}
};
function FrameWH() {
var h = $(window).height();
$("iframe").css("height",h "px");
}
那么,既然iframe的方案被毙了,就需要有新的方案来实现,有两种方案可行;
第一种,每一页都独立加载,使用框架的模板继承来实现头部和左边导航栏的重载(原样输出),这样有一个问题,就是用户不知道自己刚刚点击的是导航栏的哪一项,以及重复验证用户是否登录,这需要借助cookie和session来实现,最关键的问题在于点击后页面会有一次跳转,用户体验不太好,不是首选。
第二种,头部和左边栏固定,右侧内容区域使用异步加载,ajax去向后台取内容,并进行局部刷新,这个方案很符合要求,所以这次的项目也是采用这个方案来实现的。
已经确定列实现方案,立马开始实现左边栏的点击功能
HTML部分
代码语言:javascript复制<!-- 左侧导航区域(可配合layui已有的垂直导航) -->
<div class="left layui-side layui-side-menu ">
<div class="layui-side-scroll">
<div class="layui-logo" lay-href="">
<span>layui</span>
</div>
<ul class="layui-nav layui-nav-tree" lay-filter="menu">
<li class="layui-nav-item layui-nav-itemed">
<a lay-href="/index/index/no1" data-id="1" data-title="菜单1" data-type="tabAdd">
<i class="layui-icon layui-icon-dollar"></i>
<cite>菜单1</cite>
</a>
</li>
<li class="layui-nav-item layui-nav-itemed">
<a lay-href="/index/index/no2" data-id="2" data-title="菜单2" data-type="tabAdd">
<i class="layui-icon layui-icon-read"></i>
<cite>菜单2</cite>
</a>
</li>
</ul>
</div>
</div>
JS部分
代码语言:javascript复制 /*
* @todo 左侧菜单事件,示例中菜单只有一级,多级菜单看情况修改就好
*/
$('.layui-side-menu').on('click', '.layui-nav-itemed', function(event) {
var url = $(this).children('a').attr('lay-href'),
title = $(this).find('cite').html(),
index = $('.left-nav #nav li').index($(this));
$.ajax({
"type":"GET",
"url":url,
"dataType":"text",
"success":function(data){
data = JSON.parse(data);//解析后台返回的json,如果你的后台返回的是text,这步可以忽略
$('.main').empty();
$(".main").html(data);
$('.layadmin-tabspage-none').removeClass("layadmin-side-spread-sm");//隐藏左侧菜单Mobile
$('.layadmin-tabspage-none').removeClass("layadmin-side-shrink");//隐藏左侧菜单PC
$('.layadmin-body-shade').hide();//隐藏遮罩
}
});
});
3、异步加载的页面内容中的按钮点击无效
这个也怪自己没有经验,解决方法很简单,直接把事件委托到祖先元素上(这个元素必须是首页模板里面就存在的,也就是非异步加载的元素,否则绑定失败),我这里用的是JQ的 on() 方法,on() 方法自JQuery1.7引入,是 bind()、live() 和 delegate() 的替代品。这里使用 on() 方法是因为她添加的事件处理程序适用于当前及未来的元素。
提示:移除事件,使用 off() 方法。
提示:添加只运行一次的事件然后移除,使用 one() 方法。
语法:$(selector).on(event,childSelector,data,function)
参数 | 描述 |
---|---|
event | 必需。规定要从被选元素移除的一个或多个事件或命名空间。 由空格分隔多个事件值,也可以是数组。必须是有效的事件。 |
childSelector | 可选。规定只能添加到指定的子元素上的事件处理程序(且不是选择器本身,比如已废弃的 delegate() 方法)。 |
data | 可选。规定传递到函数的额外数据。 |
function | 可选。规定当事件发生时运行的函数。 |
示例代码如下:
代码语言:javascript复制$(".layui-body").on("click",".flowTable .search-btn",function (){
//some js code...
});
4、异步加载的页面内容中的事件被重复执行
上面说到了,要使异步加载的页面内容的事件生效,需要进行事件委托,但我在委托完毕之后发现事件会被重复执行,表现的现象是:第一次点击,执行一次;第二次点击,执行两次;第N次点击,执行N次,这个问题很严重,造成服务器请求次数过多时“小事”,因为事件被重复执行,部分页面功能无法按既定目标完成执行才是“大事”。
举个例子:index是固定内容,当点击index里面的“菜单1”之后异步加载“页面1”的内容,这时如果事件委托写在“页面1”中,事件就会被重复执行。
解决方法:将事件委托写在外面,也就是写在上面这个例子的index中。
为此,我新扩展了一个admin模块,admin.js:
代码语言:javascript复制layui.define(["jquery","table","form","layer","util","element"],function(exports){
var $ = layui.jquery,
table = layui.table,
form = layui.form,
layer = layui.layer,
util = layui.util,
element = layui.element,
device = layui.device();
exports('admin', {}); //模块输出的核心,这句是必须的
});
要加载自定义模块,方法如下(写在index中):
方法:layui.define(mods, callback),mods模块依赖,callback回调函数
代码语言:javascript复制layui.config({
base: '/js/',
version: '1.1.2'
}).use('admin');
因为扩展了模块,所以干脆把所有的东西都搬到这个admin.js来了,图个方便
5、数据表格
先记下官方文档地址:https://www.layui.com/doc/modules/table.html
我这里用的基本方法渲染,也就是 table.render() 方法,本应该用下面这样的方法来实现
HTM L部分
代码语言:javascript复制<table id="demo" lay-filter="test"></table>
JS部分
代码语言:javascript复制 table.render({
elem: '#demo' //指定原始表格元素选择器(推荐id选择器)
,height: 312 //容器高度
,url: '/demo/table/user/' //数据接口
,page: true //开启分页
,cols: [[ //表头
{field: 'id', title: 'ID', width:80, sort: true, fixed: 'left'}
,{field: 'username', title: '用户名', width:80}
,{field: 'sex', title: '性别', width:80, sort: true}
,{field: 'city', title: '城市', width:80}
,{field: 'sign', title: '签名', width: 177}
,{field: 'experience', title: '积分', width: 80, sort: true}
,{field: 'score', title: '评分', width: 80, sort: true}
,{field: 'classify', title: '职业', width: 80}
,{field: 'wealth', title: '财富', width: 135, sort: true}
]]
});
但因为页面需求的问题,需要在同一个页面加载不同的表格(点击某个按钮之后)将这个按钮对应的表格渲染出来(也就是异步的)因为前面对框架不熟悉,使用传递已知数据的方法进行了渲染
代码语言:javascript复制table.render({
elem: '#table',
cellMinWidth: 80, //全局定义常规单元格的最小宽度,layui 2.2.1 新增
page: true,
cols: cols,//格式是这样 [[{},{},{}]]
data: data //这里的data是ajax异步取到的数据,已经严格按照layui的格式在后端进行了数据重组,所以前端这里可以直接使用
});
直到这里其实都还算顺利,官方文档写的还算详细,虽然有那么一点点乱吧。
坑从何来,因为突然收到一个需求的变化,就是当点击按钮时,渲染的数据表格只是数据集中的一部分(因为数据确实有些多),要查看没一条信息对应的详细情况,需要在表格最右侧增加一列操作列,里面放的是按钮,点这个按钮的时候需要给弹窗出来,然后在弹窗内再展示一个子表。
这个坑呢,主要是因为官方文档是真的有那么一点点乱,按照正常逻辑,是不是应该先讲怎么使用行内工具条,再讲怎么监听工具条事件吧,而官方文档写的尽然是监听头部工具栏事件(然后在下面写了个具体用法参见:绑定工具条,最关键不是这个,而是目录里面没有这一项,^哭唧唧^),搞得我一度认为LAYUI怎么如此不成熟,连行内工具条都没提供…
说明:要使用工具条,先得有工具条的模版(写在body中就可以)
代码语言:javascript复制<script type="text/html" id="barDemo">
<a class="layui-btn layui-btn-xs" lay-event="detail">查看</a>
<a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
<!-- 这里支持 laytpl 语法,如: -->
{{# if(d.auth > 2){ }}
<a class="layui-btn layui-btn-xs" lay-event="check">审核</a>
{{# } }}
</script>
注意:属性 lay-event="" 是模板的关键所在,值可随意定义。
我没有用layui的模版引擎,所以并不会用,这里是laytpl模版引擎文档:https://www.layui.com/doc/modules/laytpl.html
然后在配置列(cols)的时候增加下面这一条就可以啦
代码语言:javascript复制{fixed:'right', title:'操作', align:'center', toolbar:'#barDemo', width:150}//这里的toolbar值是模板元素的选择器
然后就是监听工具条事件,偷个懒,直接把官方的例子贴过来,这个比较清楚
代码语言:javascript复制//监听工具条
table.on('tool(test)', function(obj){ //注:tool是工具条事件名,test是table原始容器的属性 lay-filter="对应的值"
var data = obj.data; //获得当前行数据
var layEvent = obj.event; //获得 lay-event 对应的值(也可以是表头的 event 参数对应的值)
var tr = obj.tr; //获得当前行 tr 的DOM对象
if(layEvent === 'detail'){ //查看
//do somehing
} else if(layEvent === 'del'){ //删除
layer.confirm('真的删除行么', function(index){
obj.del(); //删除对应行(tr)的DOM结构,并更新缓存
layer.close(index);
//向服务端发送删除指令
});
} else if(layEvent === 'edit'){ //编辑
//do something
//同步更新缓存对应的值
obj.update({
username: '123'
,title: 'xxx'
});
}
});
6、layer.open()弹窗的使用
弹窗如果只是要给用户一个提示这样就很简单,官方文档里面写的比较详细,一个简单的例子如下:
代码语言:javascript复制layer.msg('hello');//弹出一个消息,停留一段时间后消失
layer.open({
title: '在线调试' //标题
,content: '可以填写任意的layer代码' //可以是文本,也可以是DOM
});
刚刚提到了,我是要在弹窗里面再渲染一个表格,那怎么实现呢?官方文档里面一个字都没提到过这种形式(可以理解,写文档的人也不可能知道所有程序员的需求,就跟程序员永远也不清楚产品经理的需求一样),因为这个需求,反复的把弹出层和数据表格的文档啃了一遍又一遍,终于,被我发现了这么一个东西
代码语言:javascript复制layer.open({
content: '测试回调',
success: function(layero, index){
console.log(layero, index);
}
});
对,没错,就是弹出层open后的success回调,那么我就可以把table.render()方法写在这个回调里面啦,这样不就能实现了么,于是踩到了这个坑。
前面提到过,渲染表格,是需要一个已经存在的DOM的,但是弹出层是未来元素,并不是已经存在的元素,是不可控的,那就得再写一个table,然后js写成了这样
代码语言:javascript复制layer.open({
title: '详情查看',
content: $("#view-details-div"),//这里content是一个DOM,注意:最好该元素要存放在body最外层,否则可能被其它的相对元素所影响
success: function(layero, index){
table.render({
elem: "#details",
cellMinWidth: 80, //全局定义常规单元格的最小宽度,layui 2.2.1 新增
cols: [[
{field:'0', title:'姓名', align:'center'},
{field:'1', title:'语文', align:'center'},
{field:'2', title:'数学', align:'center'},
{field:'3', title:'英语', align:'center'},
{field:'4', title:'物理', align:'center'},
{field:'5', title:'化学', align:'center'}
]],
data: data.details
});
}
});
在PC上的效果还是不错的(后来发现弹出层只会出现一次,再次点按钮时就没有反应了),到了手机上,表格展示不完整(对,就是没有自适应),而且弹窗无法移动,继续啃文档,查资料(资料是真的很少),然后不停的改,不停的调,终于找到了解决方案
代码语言:javascript复制layer.open({
type: 1,//弹出层open之后,content引用的DOM会被销毁,如果不写这句,type默认为0,DOM不会被还原,所以只有第一次点击才能有效弹出
title: '详情查看',
area: '100%',//弹出层宽高,这里只设置了宽度,这样就能在双端自适应了
btn: '我知道了',//按钮文本,只有一个按钮时是字符串,多个按钮时是数组
btnAlign: 'c',//按钮居中
shade: 0,//不显示遮罩层
content: $("#view-details-div"),//这里content是一个DOM,注意:最好该元素要存放在body最外层,否则可能被其它的相对元素所影响
success: function(layero, index){
table.render({
elem: "#details",
cellMinWidth: 80, //全局定义常规单元格的最小宽度,layui 2.2.1 新增
cols: [[
{field:'0', title:'姓名', align:'center'},
{field:'1', title:'语文', align:'center'},
{field:'2', title:'数学', align:'center'},
{field:'3', title:'英语', align:'center'},
{field:'4', title:'物理', align:'center'},
{field:'5', title:'化学', align:'center'}
]],
data: data.details
});
}
});
LayUI: 1 / 2
- LayUI之旅-入门
- LayUI之旅-数据表格
本文采用 「CC BY-NC-SA 4.0」创作共享协议,转载请标注以下信息:
原文出处:Yiiven https://cloud.tencent.com/developer/article/2193126