layui-tree如何實現懶加載以及動態局部渲染樹節點


眾所周知,layui.tree的樹形控件,在渲染樹節點時,需要后台准備好整個組織樹的數據;如果組織樹的數據量特別大,頁面渲染就特別慢。最近在狐小E智慧辦公平台中,展示企業部門樹時,就遇到這種問題;當時產品要求,部門樹渲染要特別快,同時某部門下新增/刪除了一個子部門時,要動態刷新,實時呈現;然而layui.tree的原始機制是,必須重新加載整個樹的數據再渲染,這樣就會導致展開的節點全收縮回去,而不是剛好展開到該父部門位置;

這樣就有2個問題亟需優化:

1)部門樹數據量大時,渲染慢。

2)父部門如何動態刷新子部門,並且局部更新視圖;

為了解決這2個問題,我們只好修改layui.tree源碼,實現這種高大上的功能;廢話不多說,直接上干貨:

狐小E智慧辦公后台使用的layui版本是v2.5.5,其他版本的layui源碼可能有所不同,這點需注意。

 

解決方案

 

步驟1:源碼修改

首先,在layui前端框架找到實現樹組件的模塊源碼tree.js,如圖所示:

 

 

 

1:打開源碼,在樹的主渲染方法 r.render前,添加上用於加載child節點的方法r.children,代碼如下:

r.children = function (e, i, d) {
    var a = l.that[e];
    return a.children(i, d)
}

效果如下圖:

 

 

 

 

2:找到b.prototype.tree方法,在此方法前,添加兩個用於懶加載子節點的方法:b.prototype.children = function (n1, n2) {

  var e = this;
  e.setchildrendata(e.config.data, n1, n2);
},
b.prototype.setchildrendata = function (n0, n1, n2) {
  var e = this;
  var c = i('#' + e.config.id);
  layui.each(n0, function (a, r) {
    var b = c.find('div[data-id=' + r.id + ']').hasClass(C);
    r.spread = b;
    if (e.config.accordion === !0) { //手風琴模式
      r.spread = !1;
      var cs = c.find('div[data-id=' + n1 + ']').parents('.layui-tree-set');
      cs.each(function () {
        if (r.id === i(this).attr('data-id')) {
          r.spread = !0;
        }
      });
    }
    if (r.id === n1) {
      r.spread = !0;
      if(!r.children){
        r.children=[];
      }
      if (n2.length === 0) {
        delete r.children;
      }else{
        //i.extend(!0, r.children, n2);
        r.children = n2;
      }
      e.reload(e.config.id, e.config.data);
    }
    if (r.children) {
      e.setchildrendata(r.children, n1, n2); //遞歸子節點
    }
  });
}

 

3:修改b.prototype.tree方法的代碼,修改后代碼如下【備注://TODO 部分標識的是layui原始代碼】

b.prototype.tree = function (e, a) {
        var n = this, t = n.config, r = a || t.data;
        layui.each(r, function (a, r) {
            //TODO 此處調整源碼
            //var l = r.children && r.children.length > 0,
            var l = r.children,
                o = i('<div class="layui-tree-pack" ' + (r.spread ? 'style="display: block;"' : "") + '"></div>'),
                //TODO 源碼調整
                //h = i(['<div data-id="' + r.id + '" class="layui-tree-set' + (r.spread ? " layui-tree-spread" : "") + (r.checked ? " layui-tree-checkedFirst" : "") + '">', '<div class="layui-tree-entry">', '<div class="layui-tree-main">', function () {
                h = i(['<div data-id="' + r.id + '" data-parent-id="' + r.parentId + '" class="layui-tree-set' + (r.spread ? " layui-tree-spread" : "") + (r.checked ? " layui-tree-checkedFirst" : "") + '">', '<div class="layui-tree-entry ' + (l ? "taller" : "") + '" >', '<div class="layui-tree-main">', function () {
                    //TODO 源碼調整
                    //return t.showLine ? l ? '<span class="layui-tree-iconClick layui-tree-icon"><i class="layui-icon ' + (r.spread ? "layui-icon-subtraction" : "layui-icon-addition") + '"></i></span>' : '<span class="layui-tree-iconClick"><i class="layui-icon layui-icon-file"></i></span>' : '<span class="layui-tree-iconClick"><i class="layui-tree-iconArrow ' + (l ? "" : c) + '"></i></span>'
                    return t.showLine ? l ? '<span class="layui-tree-iconClick layui-tree-icon"><i class="layui-icon ' + (r.spread ? "layui-icon-subtraction" : "layui-icon-addition") + '"></i></span>' : '<span class="layui-tree-iconClick layui-tree-icon"><i class="layui-icon layui-icon-file"></i></span>' : '<span class="layui-tree-iconClick"><i class="layui-icon ' + ( r.spread ? "layui-tree-active" : "layui-tree-iconArrow") + (l ? "" : c)+' "></i></span>'
                }(), function () {
                    return t.showCheckbox ? '<input type="checkbox" name="' + (r.field || "layuiTreeCheck_" + r.id) + '" same="layuiTreeCheck" lay-skin="primary" ' + (r.disabled ? "disabled" : "") + ' value="' + r.id + '">' : ""
                }(), function () {
                  //TODO 修改源碼顯示title
                  //return t.isJump && r.href ? '<a href="' + r.href + '" target="_blank" class=" + y + ">' + (r.title || r.label || t.text.defaultNodeName) + "</a>" : '<span class="' + y + (r.disabled ? " " + d : "") + '">' + (r.title || r.label || t.text.defaultNodeName) + "</span>"
                  return t.isJump && r.href ? '<a title="' + (r.title || r.label || t.text.defaultNodeName) + '"  href="' + r.href + '" target="_blank" class=" + y + ">' + (r.title || r.label || t.text.defaultNodeName) + "</a>" : '<span title="' + (r.title || r.label || t.text.defaultNodeName) + '" class="' + y + (r.disabled ? " " + d : "") + '" >' + (r.title || r.label || t.text.defaultNodeName) + "</span>"
                }(), "</div>", function () {
                    if (!t.edit) return "";
                    var e = {
                        add: '<i class="layui-icon layui-icon-add-1"  data-type="add"></i>',
                        update: '<i class="layui-icon layui-icon-edit" data-type="update"></i>',
                        del: '<i class="layui-icon layui-icon-delete" data-type="del"></i>'
                    }, i = ['<div class="layui-btn-group layui-tree-btnGroup">'];
                    return t.edit === !0 && (t.edit = ["update", "del"]), "object" == typeof t.edit ? (layui.each(t.edit, function (a, n) {
                        i.push(e[n] || "")
                    }), i.join("") + "</div>") : void 0
                }(), "</div></div>"].join(""));
            l && (h.append(o), n.tree(o, r.children)), e.append(h), h.prev("." + s)[0] && h.prev().children(".layui-tree-pack").addClass("layui-tree-showLine"), l || h.parent(".layui-tree-pack").addClass("layui-tree-lineExtend"), n.spread(h, r), t.showCheckbox && (r.checked && n.checkids.push(r.id), n.checkClick(h, r)), t.edit && n.operate(h, r)
        })
    }

 

4:修改節點展開方法b.prototype.spread,修改后代碼如下【備注://TODO 部分標識的是layui原始代碼】

 

b.prototype.spread = function (e, a) {
        var n = this, t = n.config, r = e.children("." + p), l = r.children("." + f), c = r.find("." + o),
            k = r.find("." + y), m = t.onlyIconControl ? c : l, x = "";
        m.on("click", function (i) {
            var ax = e.children("." + v),//TODO a沖突改成ax
                //TODO 修改源碼
                //n = m.children(".layui-icon")[0] ? m.children(".layui-icon") : m.find(".layui-tree-icon").children(".layui-icon");
                n = m.children(".layui-icon")[0] ? m.find(".layui-tree-iconClick").children(".layui-icon") : m.find(".layui-tree-icon").children(".layui-icon");
            if (ax[0]) {  //TODO a改為ax
                //TODO 展開節點
                if (!e.hasClass(c) && !e.hasClass(C)) {
                    t.spread && t.spread({
                        elem: e,
                        state: a.children.length > 0,
                        data: a
                    })
                }
                //TODO 注釋掉源碼
                /*if (e.hasClass(C)) e.removeClass(C), a.slideUp(200), n.removeClass(u).addClass(h); else if (e.addClass(C), a.slideDown(200), n.addClass(u).removeClass(h), t.accordion) {
                    var r = e.siblings("." + s);
                    r.removeClass(C), r.children("." + v).slideUp(200), r.find(".layui-tree-icon").children(".layui-icon").removeClass(u).addClass(h)
                }*/
                //TODO 節點展開效果調整添加Begin
                if (e.hasClass(C) && !$(i.target).hasClass("layui-tree-txt")) {
                    e.removeClass(C), ax.slideUp(200)
                    //修改源碼,調整無實線狀態時三角圖標有動畫
                    if (!t.showLine) {
                        m.find('span .layui-icon').removeClass(aa).addClass(bb)
                    } else {
                        n.removeClass(u).addClass(h)
                    }
                } else if (e.addClass(C), ax.slideDown(200), n.addClass(u).removeClass(h), t.accordion) {
                    var r = e.siblings("." + s);
                    r.removeClass(C), r.children("." + v).slideUp(200), r.find(".layui-tree-icon").children(".layui-icon").removeClass(u).addClass(h)
                } else if (!t.showLine) {
                    m.find('span .layui-icon').removeClass(bb).addClass(aa)
                } //TODO 節點展開效果調整添加End
            } else x = "normal"
        })

 

5:b.prototype.spread 方法修改中,有兩處樣式添加 aa 、bb:如下所示

 

需要在tree.js中頂部樣式變量處添加。

 

c = "layui-hide", d = "layui-disabled", s = "layui-tree-set", o = "layui-tree-iconClick",
        h = "layui-icon-addition", u = "layui-icon-subtraction", p = "layui-tree-entry", f = "layui-tree-main",
        y = "layui-tree-txt", v = "layui-tree-pack", C = "layui-tree-spread", k = "layui-tree-setLineShort",
        m = "layui-tree-showLine", x = "layui-tree-lineExtend" ,
  aa = "layui-tree-active", bb = "layui-tree-iconArrow", //TODO 添加aabb兩個樣式

 

 

步驟2:方案落地

解決問題1:異步加載子節點

下面是狐小E智慧辦公 (https://www.hixiaoe.com)后台通訊錄模塊中,部門樹渲染加載的業務邏輯,其核心邏輯就是通過父節點ID查詢子節點數據,綁定tree組件的spread函數,捕捉節點的展開事件,用戶點擊樹節點,根據當前節點ID查詢下一級節點,如果children節點有數據,則調用tree的children函數動態地渲染子節點。

//獲取部門樹
function LoadDeptTree() {
  $.ajax({
    url: "${ctx}/dept/tree/one-level?parentId=0",
    dataType: "json",
    async: true,
    type: "GET",
    success: function (resp) {
      if (resp && resp.msgcode == 0) {
        //無連接線風格
        deptTree =tree.render({
          elem: '#leftTreeArea'
          ,id:'leftTreeArea'
          , data: resp.data
          , showLine: false  //是否開啟連接線
          , click: editDept
          , spread: function (obj) {
             if (!obj.state) {
              // 懶加載子節點,異步獲取data數據這里根據obj.data.id向后台請求當前節點數據
              $.ajax({
                url: "${ctx}/dept/tree/one-level?parentId="+obj.data.id,
                dataType: "json",
                async: true,
                type: "GET",
                success: function (resp) {
                  //當前節點展開,如果下一層有children,則調用樹的children方法,動態渲染子節點
                  if (resp && resp.msgcode == 0) {
                    //第一個參數是樹綁定的頁面元素ID
                    //第二個參數是當前展開節點的ID
                    //第三個參數是當前節點子節點的數據(數據格式參照layuitree組件數據格式)
                    tree.children(deptTree.config.id, obj.data.id, resp.data);
                  }
                }
              });
            }
          }
        });
      } else {
        alert("加載部門樹失敗")
      }
    },
    error:function(XMLHttpRequest, textStatus, error){
      if (error.code == 19) {
        window.location.reload();
      }else{
        alert("加載部門樹失敗");
      }
    }
  });
}

 

解決問題2:父部門添加子部門,局部動態更新視圖

選中父部門,動態地為父部門添加一個子部門,添加成功將新增的子部門局部渲染到整體部門樹中。下面是添加子部門后,js調用一下我自己的refreshTreeNode函數,函數里有操作layui.tree的局部更新視圖的代碼,如下:

//重新渲染樹節點
function refreshTreeNode(treeNodeId,treeNodeName) {
  $.ajax({
    url: "${ctx}/dept/tree/one-level?parentId="+treeNodeId,
    dataType: "json",
    async: true,
    type: "GET",
    success: function (resp) {
      if (resp && resp.msgcode == 0) {
        tree.children(deptTree.config.id, treeNodeId, resp.data);
        if (treeNodeName) {
          //修改節點名稱
          $("div[data-id='"+treeNodeId+"']").find(".layui-tree-txt").eq(0).html(treeNodeName).attr("title",treeNodeName);
        }
        //光標重新定位到當前節點
        $("div[data-id='"+treeNodeId+"']").find(".layui-tree-entry").eq(0).addClass("layui-table-click");
      }
    }
  });
}

 

如果想下載我們修改后的代碼,請點擊:

https://oa.hixiaoe.com/static/layui-v2.5.x/src/lay/modules/tree.js

 

作者介紹:小文文,狐小E智慧辦公 (https://www.hixiaoe.com)開發工程師,專注移動辦公軟件的SaaS平台建設以及輕應用開發

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM