1. 程式人生 > >jQuery EasyUI-非同步樹後臺程式碼與資料庫設計

jQuery EasyUI-非同步樹後臺程式碼與資料庫設計

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的屬性、事件和方法實現一些效果
連結地址:待編輯