antd-vue中树结构显示 在线数/总数,实现在线人员绿色高亮,排序在前。(超难)

前言

最近开发中,遇到了一个需求,需要使用到antd-vue下的Tree组件。需要实现的功能点如下:
1.树结构是部门的组织机构,包含的有部门内容和人员。
2.每个树节点标题需要显示(在线人数/总数)的格式。
3.对于在线人员,需要图标点亮,并且排序在最上方展示。对于离线人员,需要图标暗掉。
具体的需求图如下

下面,Yan会逐个讲解每个需求,并且怎么实现的。

需求1:实现每个标题显示(在线/总数)格式

我们首先分析一下:
1.既然需要在标题上面显示新的字样,那么一定用到了插槽,而在Tree组件中有一个
<template #title></template>,这个可以实现我们在标题上显示(0/20)的字样。
2.我们肯定是需要知道后端返回的数据格式,可以初步知道一定是一个嵌套children的格式,
{
title:“xxx”,
key:"xxx",
children:[{},{}]
}
除此之外,后端可能还会在数据中加入其他字段,不难想出,我们这个功能是需要到后端有isOnline这个属性的,一会儿我们看一下后端的数据来进行验证我们的猜想,也会对我们后端数据字段进行一个解释。
3.明白了需要后端数据支持后,我们也不难想出,我们前端需要封装一个方法计算出我们的在线人数以及总人数,然后再给模板中使用。
好的,那我们开始吧。
  1. 分析后端返回的数据字段:

  1. 实现Tree标题有 0/20 的字样,并且每个节点有图标。

<a-tree v-model:checkedKeys="checkedVal" v-model:expandedKeys="expandedVal" show-icon checkable
                            :tree-data="departmentData" :auto-expand-parent="autoExpandParent" @expand="onExpand">
     //1.标题插槽  在这个插槽里可以设置标题之后的(0/100)字样                
    <template #title="{ title, key}">
      <!--在线/总数  -->
      <span v-if="type === '0'">(0/100)</span>
    </template>
    //2.图标插槽  这个插槽可以实现我们的每个标题前面所展示的图标
     <template #icon="{ key, selected}">
     <!-- 组织机构图标 -->
     <template>
        <node-collapse-outlined style=" color:#0b8ee9 " />
      </template>
      <!-- 人员图标 -->
       <template>
            <!-- 在线 -->
         <user-outlined  style="color:#2ed573" />
           <!--不在线 -->
         <user-outlined />
       </template>
    </template>
</a-tree>

//1.运用antd-vue的这些插槽之后,我们就初步实现了每个节点标题中都有(0/20)的字样。
//2.当然,我们顺便把人员在线不在线的图标给准备好,之后我们会根据在线状态,动态的让他们切换起来。
//3.现在全是静态,效果如下。

静态效果:

  1. 静态的已经实现了,那么现在我们着手动态的怎么让(0/20)计算起来,形成真正的数据。

并且我们的图标现在不会根据在线/不在线切换成对应的颜色。

让动态展示起来数据是有点麻烦的,我们先做简单的让图标动态切换起来。
                        <a-tree v-model:checkedKeys="checkedVal" v-model:expandedKeys="expandedVal" show-icon checkable
                            :tree-data="departmentData" :auto-expand-parent="autoExpandParent" @expand="onExpand">
                            <template #title="{ title, key }">
                                <!--在线/总数  -->
                                <span v-if="type === '0'">(0/20)</span>
                            </template>
                               //这里先让图标切换起来
                            <template #icon="{ key, selected, type, isOnline }">
                                <!-- 组织机构图标 -->
                                <template v-if="type === '0'">
                                    <node-collapse-outlined style=" color:#0b8ee9 " />
                                </template>
                                <!-- 人员图标 -->
                                <template v-if="type === '1'">
                                    <!-- 在线 -->
                                    <user-outlined v-if="isOnline" style="color:#2ed573" />
                                    <!--不在线 -->
                                    <user-outlined v-else />
                                </template>
                            </template>
                        </a-tree>'

//重点解释:
 <template #icon="{ key, selected, type, isOnline }">
有的人很不理解,作用域插槽antd只提供了{key,selected},但是type和isOnline怎么来的?
答:因为antd-vue在你绑定:tree-data="departmentData"的时候,内部已经把这个给你提供回来了。
   记性好的小伙伴,可以记起来这不正是我们后端返回数据里面的字段嘛?对!!!我们就是依靠这些字段来进行图标切换的。
   当type==='1'时,我们就知道这是人员,它只展示人员图标。
   当isOnline=='true'时,这不正好渲染我们的在线图标嘛?

这里不理解的小伙伴一定要好好理解下,为啥作用域可以解构出type和isOnline。 是因为antd把你提供的treeData又给你提供回来供你使用的。
下面我们实现动态计算(0/20)也需要用到解构出来其他的数据。
如果动态的展示图标你会了,那么下面的动态展示在线人数就好理解了。
                        <a-tree v-model:checkedKeys="checkedVal" v-model:expandedKeys="expandedVal" show-icon checkable
                            :tree-data="departmentData" :auto-expand-parent="autoExpandParent" @expand="onExpand">
                            <template #title="{ title, key, type, children }">
                                <!--在线/总数  -->
                                  //其实就这里改变了,这里我们执行了一个方法,这个方法就是动态的计算我们的在线人数。
                                <span v-if="type === '0'">{
    
    { formatPersonCount(children) }}</span>
                            </template>
                            <template #icon="{ key, selected, type, isOnline }">
                                <!-- 组织机构图标 -->
                                <template v-if="type === '0'">
                                    <node-collapse-outlined style=" color:#0b8ee9 " />
                                </template>
                                <!-- 人员图标 -->
                                <template v-if="type === '1'">
                                    <!-- 在线 -->
                                    <user-outlined v-if="isOnline" style="color:#2ed573" />
                                    <!--不在线 -->
                                    <user-outlined v-else />
                                </template>
                            </template>
                        </a-tree>

//重点解释:
<template #title="{ title, key, type, children }">
//这里我们通过图标那块知道,我们可以解构出我们提供的treeData,在这里我们需要拿出type和children

<span v-if="type === '0'">{
    
    { formatPersonCount(children) }}</span>
//type和children的作用就是:
type用来区分这是部门还是人员,部门的话才显示(0/20)
{
    
    {formatPersonCount(children)}}
在插值表达式中执行我们的动态计算的方法,children就是我们需要传递进去的参数。
理解到这里,我们就知道了,重点就是这个formatPersonCount(children)方法,下面我们详细解释。
// 在线数量/总数量--格式化 字符串在模板中调用
let formatPersonCount = (node: TreeOutputWithPerson[]): string => {
//node:就是我们模板中传递进来的children
    let { total, onlineCount } = calculatePersonCount(node);
    return `${onlineCount}/${total}`;
}

//计算在线人数和总人数
let calculatePersonCount = (node: TreeOutputWithPerson[]): { total: number, onlineCount: number } => {
    let total = 0;//总数量
    let onlineCount = 0;//在线数量

    for (let i = 0; i < node.length; i++) {
        let item = node[i];//children下边的每一个节点
        //统计总人数
        if (item.type == '1') {
            total++;
        }
        // 统计在线的人员
        if (item.isOnline) {
            onlineCount++;
        }
        // 递归再次判断,将上次结果累加。
        if (item.children) {
            let result = calculatePersonCount(item.children);
            total += result.total;
            onlineCount += result.onlineCount;
        }
    }
    return { total, onlineCount }
}

解释:
猛地一看,你绝对懵了,别着急,看解释:
1.formatPersonCount()其实就是格式化,为了显示出字符串`在线人数/总人数`
2.真正的动态计算在calculatePersonCount()这里面,这里面设置了
  totalCount和onlineCount,递归的调用这个方法,把最后的计算结果返回出去
  然后插值表达式{
    
    {formatPersonCount(children)}}展示就是动态的结果

最后效果如下:

小结:
到这里,我们第一个需求已经解决了,我知道肯定很难理解,大家不能理解的不要看下面的需求了。
先好好的搞明白这边,然后好好理解之后,明天再看下面的也行啊。

需求2:在线的亮起来,不再先的灭掉。

其实需求2和需求3:在线的排序在最上方是可以同时做出来的,因为它们的逻辑都是在一个方法里的。
现在我们的在线不在线的时候,图标已经可以进行绿和灰色的切换了(需求1已经做过了)。
但是,就这个图标的状态,你能依靠后端数据吗?
这样解释:我们是获取到后端数据之后,然后绑定到treeData上的,那么其实这时候后端的isOnlione字段绑定了,之后如果某个人员离线了,你的界面的状态还是在线的,那么这就不是实时的了。
对,实时!怎么让数据是实时的呐?
我们公司采用了消息传递机制:Bus公交车(事件总线)。
有两个消息:
1.在线时候 会接收到节点的key值(我们公司人员的mac)
2.离线时候 会接受到离线人的key(人员的mac)

下面我们讲解实现的思路,毕竟有思路才有一切:

思路:
1.当消息是在线的时候,我们需要接收到在线人员的key(mac)之后,在一个方法
setOnlineStatus()中,找到树结构中的这个人员,并且设置状态为在线,然后再找到该人员所属部门,再把该部门下的所有人员按照在线状态进行排序。
2.当消息是离线的时候,我们需要接收到离线人员的key(mac),然后通过mac找到树结构中的这个人员,并且设置状态为离线,然后再找到该人员所属部门,再把该部门下的所有人员按照在线状态进行排序。
  1. 当理解上面的思路后,我们就知道了,我们要先绑定消息通知:

// 生命周期
onMounted(() => {
    getDepartmentApi();//获取组织机构
    //绑定在线消息
    Bus.$on("receiveLocation", (data) => {
        console.log("消息", data);
        data.forEach(item => {
            console.log(item);
            setOnlineStatus(item.m, true);//设置状态并排序
        })
    })
    // 绑定离线消息
    Bus.$on("receiveOffline", (mac) => {
        console.log("离线", mac);
        setOnlineStatus(mac, false);//设置状态并排序
    })

})

//解释:
1.获得在线消息后,data其实就是传递的消息,数据格式为:
m:人员的mac 我们只需要关注这个,因为需要它去找树结构中的人员,并且设置在线状态,其他的属性没用。
{m: '00000000C391', x: '1336', y: '708', dl: '1', c: '0'}
2.获得离线消息后,mac就是返回的人员mac,用它去找树结构中的人员,并且设置在线状态。
3.最重要核心就在于setOnlineStatus(item.m, true);//设置状态并排序,下面我们要讲解这个方法。

2.设置状态的方法:

//1. 设置在线状态并排序
let setOnlineStatus = (mac: string, isOnline: boolean) => {
    //根据消息返回的mac 找到树中的对应的人
    let tempData = findPersonByMac(mac, departmentData.value);
    console.log("找到了", tempData);
    //设置人的状态为消息传来的状态
    if (tempData != undefined) {
        tempData.isOnline = isOnline;
    }
    //重新排序
    let parentMac = useGetParentKey(mac, departmentData.value);//找到父级节点的key
    let childrenArr = findPersonByMac(<string>parentMac, departmentData.value);//找到该父节点下所有的孩子
    if (childrenArr != null) {
        //对该父级节点下所有的孩子进行排序
        childrenArr.children = _.orderBy(childrenArr.children, ["isOnline"], ["desc"]);
    }
}
//2.通过消息返回的mac查找树节点
let findPersonByMac = (mac: string, treeData) => {
    for (let i = 0; i < treeData.length; i++) {
        let item = treeData[i];//存放树节点
        //如果找到了就返回
        if (item.key == mac) {
            return item;
        } else {
            // 如果没找到,就递归找
            if (item.children != null && item.children.length > 0) {
                let temp = findPersonByMac(mac, item.children);
                if (temp != undefined) {
                    return temp;
                }
            }
        }
    }
}

//解释:
又懵了吧,我们从上往下看。
1.setOnlineStatus()就是设置状态然后排序的,核心都是在这里实现的。
2.findPersonByMac()就是通过人员的key(mac)来查找该节点数据的。
知道了这两个我们就知道了我们思路:
1.找到人员
2.修改人员状态
3.找到该人员所属的父节点(部门)
4.对该部门进行排序。
注意:
1.我们排序使用的是lodash插件中的,orderBy()方法,可以去官网看一下怎么使用,超级好理解的。
2.找到该人员的父节点我们用的是useGetParentKey(),这是我们封装的hooks,下面贴上代码
  1. hooks 找到该人员的父节点(部门)useGetParentKey()

//该hooks可以找到人员的父节点(所属部门)的key值
let useGetParentKey = (key: string | number, tree: any,): string | number | undefined => {
    let parentKey;//定义父节点的变量
    for (let i = 0; i < tree.length; i++) {
        const node = tree[i];
        if (node.children) {
            if (node.children.some(item => item.key === key)) {
                parentKey = node.key;
            } else if (useGetParentKey(key, node.children)) {
                parentKey = useGetParentKey(key, node.children);
            }
        }
    }
    return parentKey;
};

export default useGetParentKey;
  1. 其实,写完这里,我们就已经可以根据实时的消息,来进行树结构中人员的在线状态的切换,并且在线的人排序在最上方了。

后序

这一块真的超级难,从思路到代码实现,哪一个都是非常让人头痛的。
大家理解不了也不要灰心,随着时间,经验的累加,我们一定可以的。
要多多虚心学习!

抓紧时间练起来吧,兄dei,再不练你就废啦!

记得支持我哦,么么哒,祝您好事成双~~~~~~

猜你喜欢

转载自blog.csdn.net/Yan9_9/article/details/129299121
今日推荐