人员选择树,搜索自动筛选功能 (vue+chosen+zTree)

1648 查看

要实现的功能截图:

要求:
1、点击收件人输入框可以根据拼音自动筛选数据,并且标记已经选择的数据,没有结果的时候提示,相应的更新左边树节点状态
2、勾选树右侧树的节点左侧输入框出现一一对应的节点名称

用到的插件
vue+chosen+ztree
vue:组件化的MVVM库
chosen:基于jquery的单选列表和多选列表增强插件
ztree:基于jquery的树插件

分析
chosen插件已经可以实现要求1中的大部分效果,我们只需要预先提供chosen需要的数据,通过输入框值的变化实现左右两侧数据一 一对应,最后点击发送获取最终的数据集合ID

具体实现:
一、左侧选择数据,右侧树节点更新
chosen需要的html结构

//只需要提供包含数据的select标签即可,该select默认隐藏,chosen依据该数据构建新的html结构
<select name="dept" style="width:150px;" id="dept" class="dept_select" multiple="multiple"> 
    //loop start
    <option value="部门1">部门1</option>
    <option value="部门2">部门2</option>
    <option value="部门3">部门3</option>
    //loop end
</select>

//chosen初始化
$(function(){
    $('.dept_select').chosen({
        no_results_text:'没有结果',
        allow_single_deselect:true
    });
});

这样要求1中的大部分效果就实现了,option里面的数据我们需要通过接口从后台获取,这里采用vue解析数据,相应的html结构和js为:

//基于vue解析的html结构
<select data-placeholder="选择发件人" class="chosen-select form-control" tabindex="-1" multiple="multiple">
    <template v-for='key in zmailTree'>
            <option v-for='item in key.userList' value='{{item.id}}'>{{item.name}}</option>
    </template>
</select>

//vue实例
var zmailForm=new Vue({
    el:'#zmail-form',
    ready:function(){
        var that=this;
        var getToken=$.cookie('dcValidate');
        $.ajax({  
            type:'get',
            async:false,
            url:'后台数据接口地址',
            dataType: "json",  
            success: function(msg){
                that.$set('zmailTree', msg);
            }
        });
    },
    data:{
        zmailTree:[]
    }
});
//总结:通过vue获取后台数据,将json数据赋值给zmailTree这个数组,它是含有层级结构的,我们不需要输出层级结构,只需要输出里面的人员就行了,但是实践中发现一个人问题,数据解析了,鼠标点击输入框出现的下拉列表中并没有出现我们刚才解析出来的数据,我们需要VUE的Vue.nextTick方法,延迟回调chosen初始化代码:

//延迟初始化chosen
Vue.nextTick(function () {
    $('#zmail-select').chosen({
        no_results_text: '没有找到该结果',// 当检索时没有找到匹配项时显示的提示文本
        search_contains: true //从任意位置开始检索
    });
});

然后出现了下面的效果:

接下来我们要做的就是勾选与左侧选择的数据相对应的节点。chosen提供了一个change方法,该方法当选择的值发生改变时触发,有这个方法我们就很容易根据select值的变化来勾选右侧树的节点

$('select.chosen-select').on('change', function(){
    // 用户改变了选择,快快处理
});

我们同样要写到Vue.nextTick中,其中涉及到树的操作请参照zTree API

//延迟初始化chosen
Vue.nextTick(function () {
    $('#zmail-select').chosen({
        no_results_text: '没有找到该结果',// 当检索时没有找到匹配项时显示的提示文本
        search_contains: true //从任意位置开始检索
    });
    $('#zmail-select').on('change', function(){
        //用户改变了值之后作如下处理
        var treeObj = $.fn.zTree.getZTreeObj("zmail-tree");//获取目标容器
        treeObj.expandAll(true);//展开所有树节点
        treeObj.checkAllNodes(false);//清空所有树节点
        $("#zmail-select option:selected").each(function(i,obj){//循环选择出来的数据
            //根据节点数据的ID属性搜索,获取条件完全匹配的节点数据,注:option中解析的是ID
            var node = treeObj.getNodeByParam("id", obj.value, null);
            //将选出的节点全部勾选
            treeObj.checkNode(node, true, true);
        });
    });
});
//总结:输入框的值只要发生了改变option的状态就会自动更新,我们根据已选中的option的value(即ID),加上ztree已有的getNodeByParam方法来获取与左侧数据ID一一对应的树中的节点,然后通过checkNode方法勾选这些节点

到这里要求1中的效果我们就全部实现了。

二、勾选树节点,左侧输入框数据更新

//树的html结构,不要忘记写ztree这个class,否则不显示数据
<ul class="ztree" id="zmail-tree">
        
</ul>
//--------树初始化代码js-----------
//人员树基本设置
var zmailTreeSet={
    view:{
        dblClickExpand:false
    },
    async:{
        enable:true,
        type:'get',
        url:'服务器数据地址',
    },
    data:{
        simpleData:{
            enable:true,
            idKey:'id',
            pIdKey:'parentId'
        },
        key:{
            children:'userList'
        }
    },
    check:{
        enable:true,
        chkboxType:{'Y':'s','N':'s'}
    },
    callback:{
        onCheck:zmailCheck
    }
};

//勾选节点之后要处理的回调函数
function zmailCheck(){

}

//初始化人员树
$.fn.zTree.init($('#zmail-tree'),zmailTreeSet);

zmailCheck方法中实现的思路
(1)首先获取点击复选框的节点

var zmaObj = $.fn.zTree.getZTreeObj(treeId);//getZTreeObj插件方法,获取目标ID
var zmaNodes = zmaObj.getCheckedNodes(true);//getCheckedNodes获取输入框被勾选的节点集合

(2)其次清空select中的选中状态,将其恢复到初始状态,

$("#zmail-select option").each(function(j,obj){
obj.selected='';
});

(3)根据勾选的节点集合使select中的相应option选中,此处判断的关键是数据id

for(var i = 0;i < zmaNodes.length; i++){
    if(typeof(zmaNodes[i].userList) == 'undefined'){//如果该节点的userList属性为空说明不是父节点,存取它的值,如果不为空则跳过
        $("#zmail-select option[value="+zmaNodes[i].id+"]").prop('selected','selected');
    }
}

(4)更新select option列表

//循环外更新select列表
$("#zmail-select").trigger('chosen:updated');

完整的zmailCheck()代码为:

function zmailCheck(){
var zmaObj = $.fn.zTree.getZTreeObj(treeId);
    var zmaNodes = zmaObj.getCheckedNodes(true);
    $("#zmail-select option").each(function(j,obj){
        obj.selected='';
    });
    for(var i = 0;i < zmaNodes.length; i++){
        if(typeof(zmaNodes[i].userList) == 'undefined'){
            $("#zmail-select option[value="+zmaNodes[i].id+"]")[0].selected='selected';
        }
    }
    $("#zmail-select").trigger('chosen:updated');
}

至此要求2的效果我们也实现了。

总结
这个功能的完成离不开对zTree和chosen这两个插件的熟练使用,chosen用到的API为change方法、chosen:updated方法,ztree用到的方法为:getNodeByParam(),checkNode(),getCheckedNodes(),checkAllNodes(),expandAll()。
实现左侧=》右侧功能关键点:选出option的selected状态id,通过getNodeByParam()获取含有该ID的所有节点,然后使用checkNode()使这些节点的状态变为选中;
右侧=》左侧实现的关键点:通过getCheckedNodes()获取所有选中的节点,根据选中的节点ID使左侧的select下的option selected状态变为true,循环结束后('chosen:updated')更新option列表。

这个功能还有不完美的地方,那就是获取后台数据那里,我说过后台数据是有层级的,我现在只循环了两次,只取出了二级列表,如果层级有三级四级五级那获取的还是二级列表,vue我也刚开始学,不知道循环后台数据那里如何一劳永逸的获取不带层级的所有后续动态添加的子级列表。
为了便于讨论给大家看下我从后台获取的json数据:

[
    {
        "id": "20160912113609940493862810692577", 
        "name": "xx公司", 
        "parentId": "0", 
        "userList": [
            {
                "id": "20160912113613185306623590148664", 
                "name": "演示帐号"
            }, 
            {
                "id": "20160912113613154939672576045528", 
                "name": "成员1"
            }, 
            {
                "id": "20160919121104625924937109002003", 
                "name": "成员2"
            }
        ]
    }, 
    {
        "id": "20160912113611625082971312564873", 
        "name": "组1", 
        "parentId": "20160912113609940493862810692577", 
        "userList": [
            {
                "id": "20160912113613138161662196502779", 
                "name": "成员1"
            }, 
            {
                "id": "20160912113613044493434182821910", 
                "name": "成员2"
            }, 
            {
                "id": "20160912113613169436263538841431", 
                "name": "成员3"
            }, 
            {
                "id": "20160912113613169255388288458627", 
                "name": "成员4"
            }, 
            {
                "id": "20160912113613185135119969199907", 
                "name": "成员5"
            }
        ]
    }, 
    {
        "id": "20160912113611625563131574216031", 
        "name": "组2", 
        "parentId": "20160912113609940493862810692577", 
        "userList": [
            {
                "id": "20160912113613044536244270423866", 
                "name": "成员1"
            }, 
            {
                "id": "20160912113613044570977235101231", 
                "name": "成员2"
            }, 
            {
                "id": "20160912113613044945364721175243", 
                "name": "成员3"
            }, 
            {
                "id": "20160912113613138666099077077505", 
                "mobileNum": "18610656364", 
                "name": "成员4"
            }, 
            {
                "id": "20160912113613154114377983132349", 
                "name": "成员5"
            }, 
            {
                "id": "20160912113613169433129887352698", 
                "mobileNum": "18611610919", 
                "name": "成员6"
            }, 
            {
                "id": "20160912113613169883649070173455", 
                "name": "成员7"
            }, 
            {
                "id": "20160912113613185006644025528363", 
                "name": "成员8"
            }, 
            {
                "id": "20160912113613185400355507045112", 
                "name": "成员9"
            }
        ]
    }, 
    {
        "id": "20160919115007372634170390528661", 
        "name": "组3", 
        "parentId": "20160912113609940493862810692577", 
        "userList": [
            {
                "id": "20160914151121249285889869855783", 
                "name": "成员1"
            }, 
            {
                "id": "20160912113613060689705413198400", 
                "name": "成员2"
            }, 
            {
                "id": "20160912113613060223674583460143", 
                "name": "成员3"
            }, 
            {
                "id": "20160912113613154692470389028360", 
                "mobileNum": "13693049532", 
                "name": "成员4"
            }
        ]
    }
]

文章来源:我的博客