layui 首个版本发布于 2016 年,layui 官网于 2021年10月13日 下线。新版下载、文档和示例在内的所有框架日常维护工作,全部迁移到 Github 和 Gitee。
对于下架,作者贤心在知乎的回答大概是:
- ES6 的语言、语法、功能特性的提升,IE 的主动退场,对于内置了简化 DOM 操作及浏览器兼容性问题的 jquery 的 layui 来说失去了它的核心优势。与此同时,其他的框架上架,layui 决定放手
- layui 是 ES3/ES5 时代的产物,对于 DOM 查询、数据的动态变更,效率已不合时宜。重新维护起来也更难
- 缺乏良性的生态支撑和“外界干扰”,作者贤心对此失去信心(有用户猜测是赚不到钱 狗头.jpg,而也认为白嫖党太多,国内的知识产权付费观念有待提升 严肃.jpg)
不管咋样吧,感恩作者这几年的付出(鞠躬.gif)
layui 官网是这样描述这个框架的:layui 兼容人类正在使用的全部浏览器(IE6/7除外),可作为 Web 界面速成开发方案。(我有足够的理由怀疑官网瞧不起 IE 低版本并且有证据)
库 vs 框架
在之前的学习中听说过一些说法:bootstrap 框架、lodash 库、jquery 库,那么库和框架的共同点好像都在于:
- 来源:都是其他开发人员写好的、现成的
- 使用:需要去引入文件
- 开发:需要去查看文档来明确用法
区别则在于:
- 库是用来提供一些方法(函数)的集合,避免重复定义相同功能的函数,并具有一定的模式兼容性
- 而所谓框架,更应该是规范开发者按照框架的设计去做一些事,而非简单的工具集的概念。框架可以提供相应的库或者基于库来实现,但库一般不会具备框架的规范性
再往简单了想,函数库是对于数据的操作,像得到随机数、截取、乱序等,而框架是可以直接解决静态页面的问题,快速搭建网站的模板。
安装
layui 有 CSS、JS、组件库,文件间相互关联,所以建议使用本地文件包,而非使用 bootCDN。
引入 【layui.js】 文件支持的语法与引入 【layui.all.js】 文件支持的语法略有不同
layui.js 需要先“引入”再支持功能: layui.use(‘layer’, function () { // layui.use(模块名称,回调) layui.layer.msg(222); });
不支持: layui.layer.msg(111); 而使用 layui.all.js 两种语法都支持
起步
引入了样式文件和脚本文件以后就可以从官网组件开始了。
<head>
...
<link rel="stylesheet" href="./layui/css/layui.css">
</head>
<body>
<script src="./layui/layui.all.js"></script>
<!--
<script src="./layui/layui.all.js"></script>
-->
</body>
样式起步从按钮开始
拷贝示例代码,并挂类(跟 bootstrap 不能说一毛一样,只能说毫无差别。 狗头.jpg)
模块化使用方式
与引入了 jQuery 文件就拥有 jQuery($)核心对象一样,引入了 layui 的 js 文件就有 layui 核心对象。layui.use()
方法就表示使用模块。
假设当前引入了 layui.js 文件,那么在使用模块时,就要先.use( )
使用某个模块:
// 语法
layui.use('modName',function(){
// 具体代码
});
弹出层 layer
layui.use('layer', function(){
var layer = layui.layer;
layer.msg('hello');
});
// 等同于一个链式操作
layui.use('layer', function () {
layui.layer.msg('Hello world');
});
layui 内置了 jQuery,就可以直接使用 jQuery 语法获取到某个元素节点
const $ = layui.$;
$('#btn').click(function{}); // 可以使用 jquery 的方法了
结合一下点击事件 + 弹出层:
const $ = layui.$;
$('#btn').click(function () {
layui.use('layer', function () {
layui.layer.msg(111);
});
});
日期与时间 laydate
layui.use('laydate', function () {
layui.laydate.render({
elem: '#testLaydate', // 绑定到某一个元素,这里是一个 input 框
});
});
将 key type 还可以设置为 time 显示为时间选择
如果需要使用多个模块,将 use 方法的第一个参数改为数组类型就可以了:
layui.use(['mod1', 'mod2'], mod1callback, mod2callback);
直接使用 layui.all.js (新版本已经没有该文件)
直接引入 layui.all.js 文件的话,就可以省略了.use()
的过程。
// 弹出层
layui.layer.msg('省略了 .use 过程');
// 时间与日期
layui.laydate.render({
elem: '#time'
});
// 颜色选择器
layui.colorpicker.render({
elem: '#p1'
});
页面元素 规范公共基础类、公共属性
内置了一些公共类,类似于 bootstrap,直接给元素挂上类即可。
还定义了一些公共属性(这里并不会列举!自己去官网查看!)
布局
布局依旧按照 bootstrap 的栅格式布局,所以样式部分自行查看官方文档。
快速搭建一个后台管理系统
一个后台管理系统的布局通常长这样:
在官方文档【布局】-【管理系统界面布局】- 【预览布局效果】- 【查看该布局代码】有完整代码可供快速搭建网页结构:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>layout 管理系统大布局 - Layui</title>
<link rel="stylesheet" href="./layui/css/layui.css">
</head>
<body>
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<div class="layui-logo layui-hide-xs layui-bg-black">layout demo</div>
<!-- 头部区域(可配合layui 已有的水平导航) -->
<ul class="layui-nav layui-layout-left">
<!-- 移动端显示 -->
<li class="layui-nav-item layui-show-xs-inline-block layui-hide-sm" lay-header-event="menuLeft">
<i class="layui-icon layui-icon-spread-left"></i>
</li>
<li class="layui-nav-item layui-hide-xs"><a href="">nav 1</a></li>
<li class="layui-nav-item layui-hide-xs"><a href="">nav 2</a></li>
<li class="layui-nav-item layui-hide-xs"><a href="">nav 3</a></li>
<li class="layui-nav-item">
<a href="javascript:;">nav groups</a>
<dl class="layui-nav-child">
<dd><a href="">menu 11</a></dd>
<dd><a href="">menu 22</a></dd>
<dd><a href="">menu 33</a></dd>
</dl>
</li>
</ul>
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item layui-hide layui-show-md-inline-block">
<a href="javascript:;">
<img src="http://tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg"
class="layui-nav-img">
tester
</a>
<dl class="layui-nav-child">
<dd><a href="">Your Profile</a></dd>
<dd><a href="">Settings</a></dd>
<dd><a href="">Sign out</a></dd>
</dl>
</li>
<li class="layui-nav-item" lay-header-event="menuRight" lay-unselect>
<a href="javascript:;">
<i class="layui-icon layui-icon-more-vertical"></i>
</a>
</li>
</ul>
</div>
<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
<!-- 左侧导航区域(可配合layui已有的垂直导航) -->
<ul class="layui-nav layui-nav-tree" lay-filter="test">
<li class="layui-nav-item layui-nav-itemed">
<a class="" href="javascript:;">menu group 1</a>
<dl class="layui-nav-child">
<dd><a href="javascript:;">menu 1</a></dd>
<dd><a href="javascript:;">menu 2</a></dd>
<dd><a href="javascript:;">menu 3</a></dd>
<dd><a href="">the links</a></dd>
</dl>
</li>
<li class="layui-nav-item">
<a href="javascript:;">menu group 2</a>
<dl class="layui-nav-child">
<dd><a href="javascript:;">list 1</a></dd>
<dd><a href="javascript:;">list 2</a></dd>
<dd><a href="">超链接</a></dd>
</dl>
</li>
<li class="layui-nav-item"><a href="javascript:;">click menu item</a></li>
<li class="layui-nav-item"><a href="">the links</a></li>
</ul>
</div>
</div>
<div class="layui-body">
<!-- 内容主体区域 -->
<div style="padding: 15px;">内容主体区域。记得修改 layui.css 和 js 的路径</div>
</div>
<div class="layui-footer">
<!-- 底部固定区域 -->
底部固定区域
</div>
</div>
<script src="./layui/layui.js"></script>
<script>
layui.use(['element', 'layer', 'util'], function () {
var element = layui.element,
layer = layui.layer,
util = layui.util,
$ = layui.$;
//头部事件
util.event('lay-header-event', {
//左侧菜单事件
menuLeft: function (othis) {
layer.msg('展开左侧菜单的操作', { icon: 0 });
},
menuRight: function () {
layer.open({
type: 1,
content: '<div style="padding: 15px;">处理右侧面板的操作</div>',
area: ['260px', '100%'],
offset: 'rt', //右上角
anim: 5,
shadeClose: true
});
}
});
});
</script>
</body>
</html>
在官方布局基础上,修改了一些符合我们需要的部分:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>layout 管理系统大布局 - Layui</title>
<link rel="stylesheet" href="./layui/css/layui.css">
</head>
<body>
<div class="layui-layout layui-layout-admin">
<!-- 头部导航 -->
<div class="layui-header layui-bg-cyan">
<div class="layui-logo ">一个学生管理系统</div>
<!-- 头部区域(可配合layui 已有的水平导航) -->
<ul class="layui-nav layui-layout-left">
<li class="layui-nav-item layui-hide-xs"><a href="">商品管理</a></li>
<li class="layui-nav-item">
<a href="javascript:;">其他内容</a>
<dl class="layui-nav-child">
<dd><a href="">其他内容1</a></dd>
<dd><a href="">其他内容2</a></dd>
<dd><a href="">其他内容3</a></dd>
</dl>
</li>
</ul>
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item layui-hide layui-show-md-inline-block">
<a href="javascript:;">
<img src="http://tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg" class="layui-nav-img">
admin
</a>
<dl class="layui-nav-child">
<dd><a href="">退出登录</a></dd>
</dl>
</li>
</ul>
</div>
<!-- 左侧菜单 -->
<div class="layui-side layui-bg-cyan">
<div class="layui-side-scroll">
<!-- 左侧导航区域(可配合layui已有的垂直导航) -->
<ul class="layui-nav layui-nav-tree layui-bg-cyan" lay-filter="test">
<li class="layui-nav-item layui-nav-itemed">
<a class="" href="javascript:;">学生模块</a>
<dl class="layui-nav-child">
<dd><a href="javascript:;">学生管理</a></dd>
<dd><a href="javascript:;">班级学生</a></dd>
<dd><a href="javascript:;">课程学生</a></dd>
</dl>
</li>
<li class="layui-nav-item">
<a href="javascript:;">退换货记录</a>
<dl class="layui-nav-child">
<dd><a href="javascript:;">退货记录</a></dd>
<dd><a href="javascript:;">换货记录</a></dd>
</dl>
</li>
<li class="layui-nav-item"><a href="javascript:;">库存清点</a></li>
<li class="layui-nav-item"><a href="javascript:;">商品发布</a></li>
</ul>
</div>
</div>
<!-- 右侧内容主体 -->
<div class="layui-body">
<!-- 内容主体区域 -->
<div style="padding: 15px;">内容主体区域</div>
</div>
<!-- 底部固定区域 -->
<div class="layui-footer layui-word-aux">© 哼,总有人想剽窃我的智慧</div>
</div>
<script src="./layui/layui.all.js"></script>
<script>
layui.use(['element', 'layer', 'util'], function () {
var element = layui.element,
layer = layui.layer,
util = layui.util,
$ = layui.$;
//头部事件
util.event('lay-header-event', {
//左侧菜单事件
menuLeft: function (othis) {
layer.msg('展开左侧菜单的操作', { icon: 0 });
},
menuRight: function () {
layer.open({
type: 1,
content: '<div style="padding: 15px;">处理右侧面板的操作</div>',
area: ['260px', '100%'],
offset: 'rt', //右上角
anim: 5,
shadeClose: true
});
}
});
});
</script>
</body>
</html>
显示的主题颜色从 black 变成了 cyan,不要问为啥,就是我乐意。
还写了一个彩色的,不服?
常用功能
导航
截动图截小了,字不重要,凑合看功能:
之前使用过 iframe 实现点击后显示相应内嵌页面的效果,现在可以使用新的方式。
首先需要新建几个新的 html 文件(根据需求创建)这里我创建了 student.html、class.html、course.html 并且只在各文件中书写所需的语义化标签,没有其他的 doctype 之类的东西,因为这些内容最终会被嵌入到index.html,所以只需要书写具体内容即可。
左侧菜单代码段:
<!-- 左侧菜单 -->
<div class="layui-side layui-bg-cyan">
<div class="layui-side-scroll">
<ul class="layui-nav layui-nav-tree layui-bg-cyan" lay-filter="test" id='navTree'>
<li class="layui-nav-item layui-nav-itemed">
<a class="" href="javascript:;">功能模块</a>
<dl class="layui-nav-child">
<dd><a href="javascript:;" data-id='student'>学生管理</a></dd>
<dd><a href="javascript:;" data-id='class'>班级管理</a></dd>
<dd><a href="javascript:;" data-id='course'>课程管理</a></dd>
</dl>
</li>
...
</ul>
</div>
</div>
要获取到具体点击的某一个选项,所以将 3 个 li 设置上 data-id,值与刚才新建的各文件名相同(8、9、10行代码),再为父级元素设置书写事件委托。
点击事件写好后,使用 load()
发送 ajax 请求(目前就用就完了别管那么多,异步那块儿会再详细见到 ajax 的小黄人 xhr 对象的),由于涉及到了 ajax 请求,本地打开会提示跨域的错误,所以这里需要采用服务器的形式打开。
const $ = layui.$;
$('#navTree').click(function (event) {
let pageId = event.target.dataset.id; // 获得当前点击的哪个选项
$('#content').load(`./${pageId}.html`); // $('#content') 为显示区域元素节点
});
编辑器里选择 open with live server,不能用 open in default broswer(凶狠.jpg)
表格和分页
文档跑 这一页 来了,layui 最牛*的就是把表格的分页和排序功能内置了!
那么既然要显示表格,那么 student.html
里自然就该出现 table 标签了。再依照文档语法、利用 mock 生成一些随机数据:
<!-- 这是在 student.html -->
<table lay-filter='studentList' id='studentList'></table>
<!-- 引入的 mock 文件已经引在了 index.html -->
<script>
// 生成随机数据,内容在语雀 Mock 那一篇都有讲解
let data = Mock.mock({
"list|27": [{
"id|+1": 1,
"name": "@cname()",
"age|16-22": 1,
"gender|1": ["男", "女"],
"score|0-100": 1,
"address": "@county(true)",
"phoneNumber": /^1[3458]\d{9}$/
}]
});
layui.table.render({
elem: '#studentList', // 绑定节点
data: data.list, // value 为一个 array,数据从 mock 来
page: true, //开启分页
cols: [[ //表头
{ field: 'id', title: '学号', sort: true, fixed: 'left' },
{ field: 'name', title: '姓名' },
{ field: 'age', title: '年龄', sort: true },
{ field: 'gender', title: '性别', sort: true },
{ field: 'score', title: '成绩', sort: true },
{ field: 'address', title: '城市', width: 300, },
{ field: 'phoneNumber', title: '电话号码', sort: true },
]]
});
</script>
选项卡
在 【页面元素】-【选项卡】的 ‘默认 Tab 选项卡’ 的代码里可以看出整个文档结构。
将官网实例代码 copy 到 index.html 的 content 部分,观察:一级 ul 的内容和兄弟元素 div 内容之间的关系。
那回到管理系统,在 内容主体区域 就可以准备出选项卡的部分了,由于选项卡每个选项的个数是不定的、底下二级显示的内容也是不定的,所以只需要留有父级容器即可:
<!-- 右侧内容主体 -->
<div class="layui-body">
<!-- 选项卡 -->
<div class="layui-tab" lay-allowClose="true" lay-filter="tab-switch">
<!-- 一级 -->
<ul class="layui-tab-title"></ul>
<!-- 二级 -->
<div class="layui-tab-content"></div>
</div>
</div>
动态的选项卡
接下来在左侧菜单的选项中,点击后,不再是调用 .load()
方法,而是应该添加选项卡。动态的往 content 里填充内容。所以就不再需要 <div style="padding: 15px;" id='content'></div>
这个 content 了。(注释掉 content )
在 .layui-tab-title 的一级 ul 里就可以动态添加选项卡内容了:
// 左侧菜单点击事件
$('#navTree').click(function (event) {
let pageId = event.target.dataset.id; // 获得当前点击的哪个选项 student|class|cource
/*
实参说明:动态传入点击的 li 的内容 “学生管理|班级管理|课程管理”,渲染的 html 文件名,data-id
*/
addTab(event.target.innerText, `./${pageId}.html`, pageId);
});
// 参数接收:点击的 li 的标题内容“学生管理|班级管理|课程管理”,渲染的 html 文件名、data-id
function addTab(innerText, url, pageId) {
}
在函数内部应该考虑一个问题:
用户在点击以后,自然会新增一页选项卡,但并不是每点击一次就会新增一页。
例如先点击了‘学生管理’,就会新增一个学生管理的选项页;再点击‘班级管理’,又会新增一个班级管理的选项页。此时,又点回到‘学生管理’,应该再新开学生管理的选项页吗?
答案是否定的,对于已有的选项页,就不该再被作为新增了,而是被打开。所以就应该经过判断。
来分析一下选项卡标签页的文档结构:
// ----------------------------- 这一截同上,避免扰乱视线可以不看 ------------------
const $ = layui.$;
$('#navTree').click(function (event) {
let pageId = event.target.dataset.id;
addTab(event.target.innerText, `./${pageId}.html`, pageId);
});
// ----------------------------------------------------------------------------
// 参数:点击的 li 的标题内容“学生管理、班级管理、课程管理”,渲染的 html 文件名、data-id
function addTab(innerText, url, pageId) {
// 判断父元素 ul 是否已存在相同 data-id 值的后代 li
if ($(`.layui-tab-title>li[lay-id="${pageId}"]`).length > 0) { // length 为 1,则表示已打开相应标签页。就切换至已打开的标签页
layui.element.tabChange('tab-switch', pageId);// [相关动态操作]里有方法说明
} else { // 该标签页还没有开启过,新增一个标签页
layui.element.tabAdd('tab-swtich', { // 【这是一个秘密已做好决定,让你们拷!】
title: innerText,
content: '选项卡的内容', //支持传入html
id: pageId
});
}
}
二级内容会在第二次点击时才出现,解决方案 tabAdd() 后立马 tabChange()。
选项卡内容
从上面 else 的 tabAdd()
参数中还需要二级选项卡的内容展示(18行)。应该是被动态获取出来的,所以发请求去获取:
// ----------------------------- 这一截同上,避免扰乱视线可以不看 ------------------
const $ = layui.$;
$('#navTree').click(function (event) {
let pageId = event.target.dataset.id;
addTab(event.target.innerText, `./${pageId}.html`, pageId);
});
// ----------------------------------------------------------------------------
// 参数:点击的 li 的标题内容“学生管理、班级管理、课程管理”,渲染的 html 文件名、data-id
function addTab(innerText, url, pageId) {
// 判断父元素 ul 是否已存在相同 data-id 值的后代 li
if ($(`.layui-tab-title>li[lay-id="${pageId}"]`).length > 0) { // length 为 1,则表示已打开相应标签页。就切换至已打开的标签页
layui.element.tabChange('tab-switch', pageId);// [相关动态操作]里有方法说明
} else { // 该标签页还没有开启过,新增一个标签页
s// ajax 的请求也是先照着写不要过多纠结
$.ajax({
url: url,
type: 'GET',
success: function (data) {
// layui.element.tabAdd(filter, options); //options 参数类型为对象
console.log('请求回来的data', data);
layui.element.tabAdd('tab-switch', {
title: innerText,//'选项卡的标题'
content: data,//'选项卡的内容',支持传入html
id: pageId, //'选项卡标题的lay-id属性值'
});
// 新建完成后,就显示到该标签页
layui.element.tabChange('tab-switch', pageId);
}
});
}
}
其他更多需求看需求文档吧……我累了。
没事儿,我是自愿写文档的。嗯。是的。
更新一个小提示
看看官网里的这句话,或许在某些地方~能帮你大忙(偷笑.gif)