整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

php手把手教你做网站(四)layui 的使用

php手把手教你做网站(四)layui 的使用

载layui.css layui.js以后需要注意以下方面:

1、所有的radio checkbox要改变样式的都要放到form 里边;

2、form 要加上 class="layui-form"

3、复选框有两种显示方式

图1 复选框显示方式1

图2 复选框显示方式2

第一种:<input type="checkbox" title='显示' checked />

第二种:<input type="checkbox" title='显示' checked lay-skin="primary"/>

比较会发现 第二种只是多了一个选择 lay-skin="primary"

4、在</form>以后要加上

<script>

layui.use('form', function(){

var form=layui.form;

})

</script>

我是加到了页面底部,对form内的样式渲染

5、鼠标监听事件 form内的select button 等

select 触发 通常用在调用下级分类

button 会提交信息 可以实现ajax提交数据

事实上不管哪种触发都需要用到 lay-filter

<script>

layui.use('form', function(){

var form=layui.form;

form.on('select(myselect)', function(data){

这是select触发的事件 <select lay-filter="myselect">

data.value 就是获取到的option的value

});

form.on('submit(btnsub)', function(data){

这是button触发的事件 <button lay-submit lay-filter="btnsub">

只是多了一个lay-submit

});

})

如果用到ajax获取下级分类以后,比如select ,要在完成以后加上form.render(); 重新渲染

通过ajax提交数据,需要先获取到form表单的数据,parms=$("#formact").serialize();formact是form的ID;但是这样获取不到图片上传选择图片的数据,这就需要我们先上传图片,把图片地址赋值到input里边,建议图片上传先通过ajax 或者iframe上传图片;

</script>

这些可以和jquery nude.js等等交叉使用,不必拘泥一个思路。

css calc的用法

calc是页面布局会经常用到,比如想让页面两边空出来10个像素;通过width:calc(100% - 20px); 设置居中,就轻易实现了两边空白10px,需要注意的是 100% 和 - 以及后边的20px是有空格的

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>栅格系统</title>
        <!--LayUI的核心css文件-->
        <link rel="stylesheet" type="text/css" href="layui/css/layui.css"/>
        <!--LayUI的核心js文件-->
        <script src="layui/layui.js" type="text/javascript" charset="utf-8"></script>
    </head>
    <body>
    <!--
        栅格系统
          列组合
            1.定义行 .layui-row
            2.定义列 .layui-col-md*
                md 表示不同屏幕的标识(xs sm md lg) 
                *  表示列数量
            3.每一行均分为12列,列的总数不能超过12,否则会自动换行
            4.响应式规则
                栅格会自动根据屏幕分辨率选择对应的样式效果
          列边距
            .layui-col-space*
               * 代表的式px值(1~30)
          列偏移
             将列向右移动指定列数
            .layui-col-md-offset*
               * 代表的是列数
          列嵌套
            列之间可以无限嵌套列
            
    -------------------------------------------------------------------------
                |超小屏幕      |小屏幕        |中等屏幕        |大型屏幕
                |(手机<768px)  |(平板>=768px) |(桌面>=992px)  |(桌面>=1200px)
    --------------------------------------------------------------------------
    .layui-   |auto         |750px         |970px          |1170px
    container |
    的值      |
    --------------------------------------------------------------------------
    标记      |xs           |sm            |md             |lg
    --------------------------------------------------------------------------
    列对应类* |layui-col-xs* |layui-col-sm*|layui-col-md*  |layui-col-lg*
    为1-12的  |
    等分的值  |
    --------------------------------------------------------------------------
    总列数    |12            |12           |12             |12
    --------------------------------------------------------------------------
    响应行为  |始终按设定的比 |在当前屏幕下水 |在当前屏幕下水平|在当前屏幕下水平
                |例水平排列     |平排列,如果屏 |排列,如果屏幕大|排列,如果屏幕大
                |              |幕大小低于临界 |小低于临界则堆叠|小低于临界则堆叠
                |              |则堆叠排列     | 排列          |排列
    --------------------------------------------------------------------------
    -->

        <!--布局容器-->
        <div class="layui-container">
            <!--定义行-->
            <div class="layui-row">
                <!--定义列-->
                <div class="layui-col-md5" style="background-color: rgb(130, 215, 243);">
                    内容5/12
                </div>

                <div class="layui-col-md7" style="background-color: rgb(44, 141, 173);">
                    内容7/12
                </div>
            </div>

            <!--定义行-->
            <div class="layui-row">
                <!--定义列-->
                <div class="layui-col-md4" style="background-color: rgb(6, 126, 18);">
                    内容4/12
                </div>

                <div class="layui-col-md4" style="background-color: rgb(116, 180, 122);">
                    内容4/12
                </div>
            </div>
        </div>

        <hr>
        <h3>平板、桌面端的不同表现:</h3>
        <div class="layui-row">
            <div class="layui-col-sm6 layui-col-md4" style="background-color:thistle">
                平板>=768px:6/12  |  桌面>=992px: 4/12
            </div>
        </div>
        <div class="layui-row">
            <div class="layui-col-sm4 layui-col-md6" style="background-color: rgb(28, 104, 107);">
                平板>=768px:4/12  |  桌面>=992px: 6/12
            </div>
        </div>
        <div class="layui-row">
            <div class="layui-col-sm12 layui-col-md8" style="background-color: rgb(201, 32, 130);">
                平板>=768px:12/12  |  桌面>=992px: 8/12
            </div>
        </div>

        <hr>
        <h3>列边距</h3>
        <div class="layui-row layui-col-space10">
            <div class="layui-col-md4" >
                <div style="background-color:rgb(189, 139, 247)">4</div>
            </div>
            <div class="layui-col-md4" >
                <div style="background-color:rgb(134, 39, 197)">5</div>
            </div>
            <div class="layui-col-md4" >
                <div style="background-color:rgb(116, 19, 243)">6</div>
            </div>
        </div>

        <hr>
        <h3>列偏移</h3>
        <div class="layui-row">
            <div class="layui-col-md4" >
                <div style="background-color:rgb(247, 240, 139)">4</div>
            </div>
            <div class="layui-col-md4 layui-col-md-offset4" >
                <div style="background-color:rgb(163, 180, 6)">向右移动4个位置</div>
            </div>
        </div>

        <hr>
        <h3>列嵌套</h3>
        <div class="layui-row">
            <div class="layui-col-md6" >
                <div style="background-color:rgb(247, 240, 139)">
                    <div class="layui-row">
                        <div class="layui-col-md3" style="background-color:rgb(248, 149, 118)">内部列</div>
                        <div class="layui-col-md5" style="background-color:rgb(207, 250, 166)">内部列</div>
                        <div class="layui-col-md4" style="background-color:rgb(133, 248, 181)">内部列</div>
                    </div>
                    
                </div>
            </div>
            
        </div>
    </body>
</html>

现Tree树的插件很多,比如常见的UI:Layui、ElementUI、iView ... 。这里我们介绍一个小巧的构建Tree树的插件 zTree.js

zTree.js 官网API介绍的灰常详细了,这里我们实战使用zTree.js构建一棵Tree树。

<!--more-->

写在前面 下列文章中讲述的实例,需要使用的后端数据是已经查询好的,这里我们不讲怎么查询数据,只讲如何使用现有的数据构建Tree树,详细的教程请查看我的 GitHub, 如果你觉得写得好,欢迎star呀!!

起步

使用zTree.js首先需要导入zTree的依赖库文件,传送门。

由于我使用了基于boostrap主题的zTree,所以还是建议大家去我的GitHub项目地址下载(CSS是修改过的),传送门:GitHub

页面中需要引入如下依赖库文件:

<link rel="stylesheet" href="static/lib/bootstrap.min.css"/>
<link rel="stylesheet" href="static/lib/css/demo.css"/>
<link rel="stylesheet" href="static/lib/css/metroStyle/metroStyle.css"/>
<script type="text/javascript" src="static/lib/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="static/lib/jquery.ztree.core.min.js"></script>
<script type="text/javascript" src="static/lib/jquery.ztree.excheck.min.js"></script>

<!--more-->

前端构建一棵tree树

查阅zTree.js官网API,构建一棵Tree树很简单:

一、前端初始化一个div,用来展示Tree树

zTree构建的Tree树是用iframe嵌套的,所以不用担心宽度、高度的问题

<div class="zTreeDemoBackground">
 <ul id="tree" class="ztree"></ul>
</div>

初始化的div只需要关注id属性即可,因为JS中会根据这个ID找到构建Tree树的位置。

二、javaScript加载Tree树

为了真实点构建Tree树,我这里用一个json文件来模拟请求后端的数据。在同级目录下创建data.json,在其中写入指定格式的JSON字符串:

[{ "id": 21,
 "name": "总经理",
 "pid": 0,
 "parent": true
}, {"id": 26,
 "name": "技术部",
 "pid": 0,
 "parent": true
}, {
 "id": 27,
 "name": "项目经理",
 "pid": 26,
 "parent": false
}, {"id": 28,
 "name": "项目组组长",
 "pid": 26,
 "parent": false
}, {
 "id": 29,
 "name": "安全部",
 "pid": 0,
 "parent": true
}, {"id": 30,
 "name": "网络安全部负责人",
 "pid": 29,
 "parent": false
}, {
 "id": 31,
 "name": "项目安全测试员",
 "pid": 29,
 "parent": false
}]

然后,写JavaScript代码:

var setting={
 view: {
 selectedMulti: true
 },
 check: {
 enable: true,
 },
 data: {
 simpleData: {
 enable: true,//是否采用简单数据模式
 idKey: "id",//树节点ID名称
 pIdKey: "pid",//父节点ID名称
 rootPId: -1,//根节点ID
 }
 }
};
$(function () {
 //加载后端构建的ZTree树(节点的数据格式已在后端格式化好了)
 $.ajax({
 url: 'data.json',
 type: 'get',
 dataType: "json",
 success: (data)=> {
 console.log(data);
 $.fn.zTree.init($("#tree"), setting, data);//初始化树节点时,添加同步获取的数据
 },
 error: (data)=> {
 alert(data.message);
 }
 });
});

解释

  1. setting中包含了ztree的所有配置。
  2. view中包含了Tree树的一些视图样式配置,例如是否显示节点间的连线,是否显示节点的图标,等...
  3. selectedMulti是view的一个配置参数,设置是否允许同时选中多个节点。
  4. data中包含了要展示的数据以及展示数据的配置,因为我们采用了ajax请求数据,这里需要配置simpleData。
  5. simpleData数据展示的配置:enable是否采用简单的数据模式;idKey树节点ID名称;pIdKey父节点ID名称;rootPId根节点ID

以上参数配置,大家最好去参看zTree.js官网API。

如果配置好了setting,那下面就要ajax请求数据并渲染出来。如上在ajax的success回调函数中使用$.fn.zTree.init($("#treeID"),setting,data)渲染树节点,其中第一个参数:树要渲染的位置、第二个参数:刚才写的setting配置,第三个参数:要加载的数据。

如上,我们先看下效果:

其中我们最该关心的是如何实现节点的渲染,说白了就是要弄明白怎样的数据结构zTree才能渲染出一棵树。

Tree树数据结构分析

首先,zTree渲染节点需要的数据一定是JSON格式的数据,且JSON数据的格式和simpleData配置参数有关;想要使用ajax这种方式渲染节点,你必须开启enable: true,其次idKey是树节点ID名称,也就是说树的每个节点都有一个id,我们在这里要指定被渲染的数据中展示id的名称;其次要指定pIdKey,因为你的节点不会都是平级的没有子节点,当需要子节点,就必须指定一个区分父子节点的ID名称;最后就是rootPId表示根节点ID,即最上层的节点ID,一般写为-1即可。

此时,你或许应该参考一下我这篇文章:Shiro实现权限管理之表结构设计 ,表结构的设计和tree树的构建也算是有一部分的关系吧。

如果你的simpleData是这样配置的:

simpleData: {
 enable: true,//是否采用简单数据模式
 idKey: "id",//树节点ID名称
 pIdKey: "pid",//父节点ID名称
 rootPId: -1,//根节点ID
}

那么你就应该提供这样的JSON数据:

[{"id": "xx", "pid": "xx", "pid": "xx"},{"id": "xx",....},{....}]

只要名称和JSON数据中对应就行,不然无法渲染出节点。

实例

如何实现默认选中

实现默认选中,就是在初始化树的时候,将(用户)已拥有的节点选项选中。要知道所有的节点数据应该是从数据库中读取出出来的,例如这篇博文 权限管理系统数据库表设计 中用户都可能拥有一个角色,那么在遍历角色树的时候就应该默认选中一些节点表述用户已经拥有了这个节点角色。

  • 如何实现默认选中?

简单一句话:遍历需要默认选中的节点数据(ID..),调用zTree.js相关的方法根据(ID)实现默认选中。

首先我们需要了解:

| 函数 | 用处 | | -- | -- | | $.fn.zTree.getZTreeObj('') | 获取zTree对象,根据div中指定的ID获取此渲染的ZTree对象,下面的方法都用到此对象调用 | | zTree.selectNode(treeNode,addFlag,isSilent) | 根据上面获取的zTree对象调用selectNode,参数一:要选中的节点数据;参数二:是否允许同时选中多个节点;参数三:为false选中节点自动滚动到可视区域,实现选中子节点的父节点默认展开 | | zTree.checkNode(treeNode,checked,checkTypeFlag,callbackFlag) | selectNode只实现选择了节点,checkNode实现勾选节点,参数二:是否勾选节点;参数三:勾选父节点是否联动勾选其下的子节点;参数四:是否自动触发beforeCheck & onCkeck 回调函数 | | zTree.getNodeByParam(key,value,parentNode) | 获取完全匹配节点数据的JSON对象,参数一:要精确匹配的属性名称;参数二:要精确匹配的属性值;参数三:在某个父节点下查找 |

用法

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Title</title>
 <link rel="stylesheet" href="static/lib/bootstrap.min.css">
 <link rel="stylesheet" href="static/lib/css/metroStyle/metroStyle.css">
 <link rel="stylesheet" href="static/lib/css/demo.css">
</head>
<body>
<div class="zTreeDemoBackground">
 <ul id="tree" class="ztree"></ul>
</div>
</body>
<script type="text/javascript" src="static/lib/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="static/lib/jquery.ztree.core.min.js"></script>
<script type="text/javascript" src="static/lib/jquery.ztree.excheck.min.js"></script>
<script type="text/javascript">
 var setting={
 view: {
 selectedMulti: false
 },
 check: {
 enable: true,
 },
 data: {
 simpleData: {
 enable: true,//是否采用简单数据模式
 idKey: "id",//树节点ID名称
 pIdKey: "pid",//父节点ID名称
 rootPId: -1,//根节点ID
 }
 }
 };
 $(function () {
 //加载后端构建的ZTree树(节点的数据格式已在后端格式化好了)
 $.ajax({
 url: 'data.json',
 type: 'get',
 dataType: "json",
 success: (data)=> {
 console.log(data);
 $.fn.zTree.init($("#tree"), setting, data);//初始化树节点时,添加同步获取的数据
 checkNodes();
 },
 error: (data)=> {
 alert(data.message);
 }
 });
 });
 //处理默认选中的方法
 function checkNodes(){
 //模拟数据库中已存在的数据(要实现默认选中的数据)
 var data=[{"id": 21, "name": "总经理", "pid": 0},{"id":'27', "name": "项目经理", "pid": 26}];
 var zTree=$.fn.zTree.getZTreeObj("tree"); //获取zTree对象
 data.forEach(row=> {
 zTree.selectNode(zTree.getNodeByParam("id", row.id), true, false);
 zTree.checkNode(zTree.getNodeByParam("id", row.id), true, false);
 });
 }
</script>
</html>

如上,实现默认选中,在初始化树后立即调用处理默认选中的方法即可。我们模拟默认选中的数据中包含了id和name以及pid,这些都是比较基础的数据,ztree的selectNode和checkNode方法都是根据id实现选中的, 默认选中要提供的数据和渲染树用的格式是相同的。其中:

  1. getZTreeObj()将根据<div>中定义的id值来获取当前树的对象;
  2. selectNode实现选择节点,不会勾选节点,但是它能实现将被勾选的子节点所在的父节点展开;
  3. checkNode实现勾选节点,设置第三个参数是false,则表示选中父节点时不联动勾选其下的子节点,因为子节点未必都要默认选中。

如何获取选中的节点

获取选中的节点,只需要了解zTree.getCheckedNodes(),用来获取选中节点数据的JSON对象。其中获取到的选中节点数据包含一定顺序:选中父节点永远在选中子节点的最前面。

如果想要在提交表单的时候,将选中节点的值传给后台,就可以使用getCheckedNodes()方法获取到选中节点数据,然后遍历得到各个选中节点的数据。

如何实现单选

实现单选,只需要在setting的check中配置chkStyle: "radio"即可实现单选,但是,此时实现的单选只在同级节点上才能实现单选,也就是说你在同级节点上只能单选,但是在你可以同时选中子节点和父节点。

那么,在你调用getCheckedNodes()方法获取选中节点数据时,其中也包含了选中的父节点,因为父节点可能只是个分组不一定要存入到数据库中;那么此时你就要判断下如果选中的节点的长度>0,那么就取索引位置的最后一个值;

<!--more-->

后端如何封装Tree树结构

上面我们将的都是前端如何将JSON数据渲染成一棵Tree树,但是渲染用的数据应该是冲数据库中读取的。下面我们应该学习一下后端如何实现封装Tree树用的JSON数据。

  • SpringMVC

我们使用SpringMVC作为与后端交互的Web层框架,关于SpringMVC + Spring + Mybatis 框架的整合,大家可以参看我的这个项目 SSM框架整合

封装实体类

想必大家一定知道@ResponseBody这个注解,如果方法或类上添加了这个注解,那么@RequestMapping()映射return的东西将不再是InternalResourcecViewResolver视图解析器解析的视图地址,而是JSON格式的数据。 那么想要让SpringMVC相应一串[{"id": "xx", "name": "", "xx"},{"id": "xx", "name": "xx"}]这种格式的数据,我们就必须手动将数据封装成这种格式,如此SpringMVC才能将对象转换成JSON串。

我们会想到,我们可以将从数据库中读取的数据,依次存入到Map(或List)集合中,然后return map。当然,这是可行的,但是或许麻烦了些,因为整个项目中不止要构建一棵Tree树,每次都要new Map重用率就太低了。所以,一个简单的方式,就是手动创建一个实体类TreeEntity.java用以存放从数据库中读取到的数据,这样每次构建Tree树都能使用这个实体类对象。

TreeEntity.java属性如下:

public class TreeEntity implements Serializable {
 private Long id; //节点的id值
 private String name; //节点的名称
 private Boolean isParent; //是否是父节点
 private Long pid; //当前节点对应父节点的id值
 public TreeEntity(Long id, String name, Boolean isParent, Long pid){
 this.id=id;
 this.name=name;
 this.isParent=isParent;
 this.pid=pid;
 }
 
 getter/setter ....
}

如上,你会发现,这是不是和我们前面说的前端构建Tree树的结构是一样的呢。没错,我们前端既然定义了这种格式,后端就必须要给它一个这样格式的数据。

Web层封装Tree数据结构

  • 注: 本例中涉及的表设计请参考我的这篇博文 Shiro实现权限管理之表结构设计 。

这里不再讲Dao层中如何查询的数据,我们仅以一个最简单的查询(findAll查询所有)来讲述Tree的数据结构封装。

先看代码:

@ResponseBody
@RequestMapping("/getZTreeForUserRoles")
public List<TreeEntity> getTreeForUserRoles() {
 try {
 List<TreeEntity> treeList=new ArrayList<TreeEntity>();
 List<Role> roleList=roleService.findAll();
 for (Role role : roleList) {
 // 为tree树节点添加数据,节点pid为0的都是父节点,其他为子节点
 if(role.getPid() !=null){
 if (role.getPid()==0) {
 treeList.add(new TreeEntity(role.getId(), role.getDescription(), true, (long) 0));
 } else {
 treeList.add(new TreeEntity(role.getId(), role.getDescription(), false, role.getPid()));
 }
 }
 }
 return treeList;
 } catch (Exception e) {
 e.printStackTrace();
 return null;
 }
}

解释

  1. 首先要定义映射方法返回的数据类型是List<TreeEntity>;即返回的是一个List集合,但是其中存的是TreeEntity实体类的数据。
  2. 初始化一个空的List集合new ArrayList<TreeEntity>();并调用Service层的方法获取到sys_roles表中的所有数据,当然findAll()方法的返回值也是List集合。
  3. 遍历findAll()查询到的数据;这就体现了返回值是List并且泛型是实体类的优势了,这样我们可以直接通过实体类中定义的setter/getter来存取数据。
  4. 调用TreeEntity中定义的带参构造方法,将3中遍历得到的数据依次循环啊添加到List<TreeEntity>集合中。
  5. 将List<TreeEntity>集合返回。

我们来看一下这个请求映射返回的数据格式是如何的:

如上,我们已经实现了目的。

拓展

上面的代码中还要说明的就是调用TreeEntity的带参构造函数传入的参数值。我们定义的带参构造函数如下:

public TreeEntity(Long id, String name, Boolean isParent, Long pid) {
 this.id=id;
 this.name=name;
 this.isParent=isParent;
 this.pid=pid;
}

在为List<TreeEntity>集合循环添加值时,要弄清楚:

  1. 节点id是什么?

节点的id是每个节点的唯一标识,就像数据库的主键值一样,所以我们通常将其设置为数据库中的主键值。并且以后也要获取这个主键值。

  1. 节点名称是什么?

节点的名称name是前端展示的各个节点的名称。而这些名称应该和数据库中的值是相同的,所以我们将其设置为数据库的description的值。

  1. 父节点是什么?

父节点,我们在数据库中已经定义了,即数据库中存在一个字段pid,这个字段表示的是上级节点的id值,即如果存在上级节点(或叫上级分组),那么就给此row的pid字段设置为上级row的主键id值。

  1. 如何定义父节点?

根据构造方法中的isParent字段,如果是父节点就直接手动设置为true,否则就设置为false。 如何判断是父节点?根据数据库(实体类)中已有的属性值pid判断,如果pid不为0就表示是子节点,如果pid是0就是父节点(因为主键值不可能为0)。