jQuery EasyUI-异步树后台代码与数据库设计

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/qq_32588349/article/details/51649444

easyui的异步树创建很简单,只需要指定一个获取树的JSON数据的URL地址就可以了,API是这样写的:这里写图片描述

异步树后台代码设计方式有很多种,我说下我的设计。

数据库表设计

Tree表
这里写图片描述

先来解释下每个字段的含义:
id:节点ID,用于后台接收查询对应数据
pid:父节点ID,用于获取指定ID节点的子节点
text:节点名称
iconCls:节点的图标
state:节点的状态,有open和closed俩种状态,数据库设置默认值为open,与easyui此属性默认值保持一致
checked:节点是否被选中,此属性需要打开tree的checkbox属性,默认值为-1,表示无此属性,可以设置0,表示不选中,1表示选中
attributes:是否具有自定义属性,数据库设置默认值为0表示无,如果有的话需要设置成1
children:是否具有子节点,数据库设置默认值为0表示无,如果有的话需要设置成1
loadChildren:是否立即加载子节点,数据库设置默认值为0表示不立即加载,需要的话可以设置成1表示立即加载子节点
layer:层数,表示该节点位于树的哪一层,根节点层数为1,兄弟节点层数相同,子节点层数+1

节点自定义属性表
这里写图片描述

tid:节点ID
name:属性名
value:属性值
state:状态,表示此属性是否生效,默认值为1表示有效,可以设置为0表示无效

先来说说这么设计表会遇到的几种情况:
1、某节点state值为open,children值为0,loadChildren值为0
此时该节点在页面显示为一个文件的形态
这里写图片描述
2、某节点state值为open,children值为0,loadChildren值为1
这种情况虽然立即加载但是并没有打开children,因此后面我们会在业务逻辑中判断为第一种情况,因此同样显示为一个文件的形态
3、某节点state只为open,children值为1,loadChildren值为0
这种情况表示节点为打开状态且有子节点,那么就应该无视立即加载这个属性,效果同第4条
4、某节点state只为open,children值为1,loadChildren值为1
这种情况表示该节点即存在子节点也立即加载,在后面业务逻辑中会根据当前的节点ID去获取子节点数据,如果子节点中的某个子节点为同样属性,会继续递归获取该子节点下的所有子节点数据,以此类推。此时节点显示为文件夹形态,子节点根据自身设置显示各自的形态
这里写图片描述
5、某节点state只为closed,children值为0,loadChildren值为0
这样会出现如图所示情况:
这里写图片描述
这里写图片描述
因为当展开节点后却无子节点数据,这种情况我们要在后面业务逻辑中阻止,阻止方式为不设置state属性值,转为JSON时不设置state属性(easyui会默认为open)
6、某节点state只为closed,children值为0,loadChildren值为1
已经设置了不存在子节点立即加载也应该无效,情况和第5条一样,同样在业务逻辑中阻止这种情况,
7、某节点state只为closed,children值为1,loadChildren值为0
正常应该存在的情况,此时该节点在页面上显示为一个文件夹形态,该节点具备子节点但是不是立即加载,根据API描述,当展开一个closed的节点,且该节点的此节点属性并没有加载,也就是该节点的JSON数据中无children属性,会将此节点的ID通过URL发送至后台去获取子节点数据。
这里写图片描述
8、某节点state只为closed,children值为1,loadChildren值为1
这也是正常应该存在的情况,此时该节点在页面上显示为一个未展开的文件夹形态,但是此时该节点下的子节点(或者子子节点,根据子节点属性按需求递归,以此类推)已经加载完毕,这时候展开该节点,会显示已经加载好的子节点,因此并不会向后台发送请求。


实体类代码及实体类属性转换为JSON方法代码

根据数据库设计及上面分析的情况,可以看出1、2都作为情况1处理,3、4为一种情况,5、6为一种情况,7为一种情况,8为一种情况。

  • 实体类TreeNode
public class TreeNode {

    /**节点ID*/
    private Integer id;
    /**父节点树ID*/
    private Integer pid;
    /**节点名称*/
    private String text;
    /**节点的图标*/
    private String iconCls;
    /**状态*/
    private String state;
    /**是否选中:-1-无此属性 0-否 1-是*/
    private Integer checked;
    /**是否有属性:0-无 1-有*/
    private Integer attributes;
    /**是否有子节点:0-无 1-有*/
    private Integer children;
    /**是否立即加载子节点:0-不加载 1-加载*/
    private Integer loadChildren;
    /**所在层数 根节点为层1、兄弟节点层数相同、子节点层数+1*/
    private Integer layer;

    /**子节点集合*/
    private List<TreeNode> treeNodes;
    /**节点属性集合*/
    private List<TreeNodeAttr> treeNodeAttrs;

    /**将当前节点及其子节点转为JSON格式*/
    public String toJson() {
        return "[" + toJson(this, "") + "]";
    }
    /**将当前节点下的子节点转为JSON格式*/
    public String childToJson() {
        String json = "[";
        for(int i = 1; i <= treeNodes.size(); i++) {
            json += toJson(treeNodes.get(i - 1), "");
            if(i != treeNodes.size()) {
                json += ",";
            }
        }
        json += "]";
        return json;
    }
    private String toJson(TreeNode treeNode, String json) {
        json = "{\n\"id\": "+ treeNode.getId();
        json += ",\n\"text\": \""+ treeNode.getText() +"\"";
        if(treeNode.getIconCls() != null) {
            json += ",\n\"iconCls\": \""+ treeNode.getIconCls() +"\"";
        }
        if(treeNode.getChecked() == 0) {
            json += ",\n\"checked\": "+ false;
        }else if(treeNode.getChecked() == 1) {
            json += ",\n\"checked\": "+ true;
        }
        //如果该节点具备自定义属性且存在自定义属性数据
        if(treeNode.getAttributes() == 1 && treeNode.getTreeNodeAttrs() != null) {
            json += ",\n\"attributes\":{\n";
            List<TreeNodeAttr> attrsList = treeNode.getTreeNodeAttrs();
            for(int i = 1; i <= attrsList.size(); i++) {
                json += "\""+ attrsList.get(i - 1).getName() +"\": \""+ attrsList.get(i - 1).getValue() +"\"";
                if(i != attrsList.size()) {
                    json += ",\n";
                }else {
                    json += "\n";
                }
            }
            json += "}";
        }
        if("open".equals(treeNode.getState())) {
            json += ",\n\"state\": \"open\"";
        }else if("closed".equals(treeNode.getState()) && treeNode.getChildren() == 1){
            //设置closed属性必须保证有子节点,否则页面文件夹打开会显示根目录
            json += ",\n\"state\": \"closed\"";
        }
        //俩种情况可以拼接子节点JSON字符串
        //1-设置立即加载子节点且存在子节点且子节点属性不为空
        //2-当状态不是closed时(不设置时easyui默认open),无视是否设置立即加载,只要保证存在子节点且子节点属性不为空
        if((treeNode.getLoadChildren() == 1 && treeNode.getChildren() == 1
                && treeNode.getTreeNodes() != null) || (!"closed".equals(treeNode.getState())
                && treeNode.getChildren() == 1 && treeNode.getTreeNodes() != null)) {
            json += ",\n\"children\":[";
            for(int i = 1; i <= treeNode.getTreeNodes().size(); i++) {
                json += toJson(treeNode.getTreeNodes().get(i - 1), json);
                if(i != treeNode.getTreeNodes().size()) {
                    json += ",";
                }
            }
            json += "]";
        }
        json += "\n}";
        return json;
    }

    //get set 构造方法略
  • 实体类TreeNodeAttr
public class TreeNodeAttr {

    private Integer tid;
    private String name;
    private String value;
    private Integer state;

    //get set 构造方法略
}

数据访问层

这里我用的Spring+SpringMVC+MyBatis

  • 数据层接口
@Service
public interface TreeMapper {
    /**查询指定的节点*/
    public TreeNode selectTreeNodeInfo(TreeNode treeNode);
    /**查询指定节点的属性*/
    public TreeNode selectTreeNodeAttrs(TreeNode treeNode);
    /**查询指定节点下的子节点*/
    public TreeNode selectChildrenTreeNodesInfo(TreeNode treeNode);

}
  • TreeMapper.xml
<mapper namespace="com.bc.dao.tree.TreeMapper">
    <select id="selectTreeNodeInfo" resultType="TreeNode" parameterType="TreeNode">
        select * FROM tree where id = #{id}
    </select>

    <resultMap id="treeNodeAttrs" type="TreeNode">
        <collection property="treeNodeAttrs" ofType="TreeNodeAttr">
            <result property="name" column="name"/>
            <result property="value" column="value"/>
        </collection>
    </resultMap>
    <select id="selectTreeNodeAttrs" resultMap="treeNodeAttrs" parameterType="TreeNode">
        select name,value from tree_attributes where tid = #{id}
    </select>

    <resultMap id="treeNodeChildrens" type="TreeNode">
        <collection property="treeNodes" ofType="TreeNode">
            <id property="id" column="id"/>
            <result property="pid" column="pid"/>
            <result property="text" column="text"/>
            <result property="iconCls" column="iconCls"/>
            <result property="state" column="state"/>
            <result property="checked" column="checked"/>
            <result property="attributes" column="attributes"/>
            <result property="children" column="children"/>
            <result property="loadChildren" column="loadChildren"/>
            <result property="layer" column="layer"/>
        </collection>
    </resultMap>
    <select id="selectChildrenTreeNodesInfo" resultMap="treeNodeChildrens" parameterType="TreeNode">
        select id,pid,text,iconCls,state,checked,attributes,children,loadChildren,layer from tree where pid = #{id}
    </select>
</mapper>

业务逻辑层

  • 接口
public interface TreeService {
    /**获取根据ID获取节点的信息*/
    public TreeNode searchTreeNodeInfo(Integer id);
}
  • 接口实现类
@Service
public class TreeServiceImpl implements TreeService {

    @Resource
    private TreeMapper treeMapper;

    @Override
    public TreeNode searchTreeNodeInfo(Integer id) {
        //创建一个节点对象
        TreeNode treeNode = new TreeNode();
        //设置要查询的ID
        treeNode.setId(id);
        //查询该节点信息
        treeNode = treeMapper.selectTreeNodeInfo(treeNode);
        //如果该节点有属性
        if(treeNode.getAttributes() == 1) {
            //查询该节点的属性后存入该节点
            treeNode.setTreeNodeAttrs(treeMapper.selectTreeNodeAttrs(treeNode)
                    .getTreeNodeAttrs());
        }
        //如果该节点有子节点
        //这里只需要判断一个属性就可以了,因为这个节点是我们手动打开的,
        //也就是打开前就能保证其余属性都符合要求
        if(treeNode.getChildren() == 1) {
            treeNode.setTreeNodes(getTreeNodeChildrens(treeMapper
                    .selectChildrenTreeNodesInfo(treeNode).getTreeNodes()));
        }
        return treeNode;
    }

    /**
     * 获取节点集合中每个子节点、子节点中的子节点...以此类推
     * @param treeNodes
     * @return
     */
    private List<TreeNode> getTreeNodeChildrens(List<TreeNode> treeNodes) {
        //如果该节点有属性
        if(t.getAttributes() == 1) {
            //查询该节点的属性后存入该节点
            t.setTreeNodeAttrs(treeMapper.selectTreeNodeAttrs(t)
                    .getTreeNodeAttrs());
        }
        for(TreeNode t : treeNodes) {
            /**
             * 这里是程序递归自动判断是否获取子节点,因此需要根据数据库设计那里的8种情况来过滤条件
             *
             * 1、state:open,children:0,loadChildren:0
             *      这种情况不加载子节点
             * 2、state:open,children:0,loadChildren:1
             *      这种情况不加载子节点
             * 3、state:open,children:1,loadChildren:0
             *      这种情况加载(open状态无视立即加载)
             * 4、state:open,children:1,loadChildren:1
             *      这种情况加载
             * 5、state:closed,children:0,loadChildren:0
             *      这种情况不加载子节点
             * 6、state:closed,children:0,loadChildren:1
             *      这种情况不加载子节点
             * 7、state:closed,children:1,loadChildren:0
             *      这种情况不加载子节点
             * 8、state:closed,children:1,loadChildren:1
             *      这种情况加载
             *
             * 因此只有3、4和8俩种情况可以加载,其他全部拦截
             * 因为easyui对于不设置的state默认为open,因此使用"!closed"来代替"open"
             */
            if((t.getLoadChildren() == 1 && t.getChildren() == 1) ||
                    (!"closed".equals(t.getState()) && t.getChildren() == 1)) {
                t.setTreeNodes(getTreeNodeChildrens(treeMapper.selectChildrenTreeNodesInfo(t)
                        .getTreeNodes()));
            }
        }
        return treeNodes;
    }
}

控制器

  • 关键代码
    @RequestMapping(value = "/data",method = RequestMethod.POST)
    public void root(HttpServletResponse response, Integer id) throws Exception {
        TreeNode tree = null;
        String json = "";
        if(id == null) {
            //第一次进入页面ID为空,此时查询出根节点数据返回
            tree = treeService.searchTreeNodeInfo(1);
            //此时需要将根节点及其子节点(条件符合)转为JSON
            json = tree.toJson();
        }else{
            //再次访问此属性是展开closed状态的节点,发送ID来查询子节点
            tree = treeService.searchTreeNodeInfo(id);
            //此时只需要将子节点转为JSON
            json = tree.childToJson();
        }
        response.getWriter().print(json);
    }

其中”/data”就是easyui的tree属性的URL地址,页面代码略。


测试

最后我们往数据库表中插入一些数据试试

这里写图片描述

对于自定义属性表就不做测试了

  • 效果图

这里写图片描述

代码没认真检查过,不知道有无可优化的地方及错误,仅供参考。

下一篇讲解使用easyui的属性、事件和方法实现一些效果
链接地址:待编辑

猜你喜欢

转载自blog.csdn.net/qq_32588349/article/details/51649444