vue实现会员树形结构,会员树

项目中经常会碰到分销功能,用户分享邀请下级用户。会员树能直观的显示各级会员之间的关系。整个树形结构核心代码是用vue写的。我的后台代码是用的layui前端框架。话不多说先看效果图

html代码:

<div class="nav-tabs-custom">
    <div class="tab-content" id="root">
        <div class="box box-success">
            <div class="layui-fluid">
                <div class="layui-row layui-col-space15">
                    <div class="layui-col-md12">
                        <div class="layui-card">
                            <div class="layui-card-header">会员关系树</div>
                            <div class="layui-card-body">
                                <div class="demoTable layui-form" style="margin-bottom:10px">
                                    <div class="layui-inline">
                                      <input class="layui-input"  v-model="keyword" placeholder="请输入UID或者手机号或用户昵称搜索" name="id" id="test-table-demoReload" autocomplete="off">
                                    </div>
                                    <button @click="searchByKeyWord" class="layui-btn" id="searchByKeyWord">搜索</button>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div style="padding:0 20px 20px 20px;width:100%;overflow-x: scroll;">
                <template>
                    <a-tabs v-model="activeKey">
                        <a-tab-pane key="1" tab="会员关系树">
                            <a-spin :spinning="spinning" style="width: 100%; min-height: 50px">
                                <a-tree v-show="treeData.length > 0" class="tree-wrap"
                                        :load-data="onLoadData"
                                        :show-line="true"
                                        :multiple="true"
                                        :tree-data="treeData"
                                        :replaceFields="{children:'children', title: 'is_fans', key: 'uid'}">
                                    <template slot="custom" slot-scope="item">
                                        <p>
                                            <span>
                                                <a-tag class="tags-status" color="#108ee9" v-if="item.status == 0">正常</a-tag>
                                                <a-tag class="tags-status disabled" style="background-color: red;color: #fff;" v-else>禁用</a-tag>
                                            </span>
                                            <span class="vips-name" style="color:#808080">
                                              <span>{{item.nickname ? `(${item.nickname})` : `(${item.realname})`}}</span>
                                            </span>
                                            <span>· 会员ID:<{{item.uid}}> </span>
                                            <span>· 联系电话:<{{item.telphone}}> </span>
                                            <span>· 注册时间:<{{item.created_at}}> </span>
                                        </p>
                                    </template>
                                    <template slot="operate" slot-scope="item">
                                        <a-button
                                                type="link"
                                                size="small"
                                                :disabled="item.finished"
                                                :loading="item.btnLoading"
                                                @click="loadMore(item)">
                                            {{item.title}}
                                        </a-button>
                                    </template>
                                </a-tree>
                                <a-empty  v-show="treeData.length == 0" />
                            </a-spin>
                        </a-tab-pane>
                        <a-tab-pane key="2" tab="搜索结果" v-if="searchResult">
                            <a-spin :spinning="spinning1">
                                <a-tree v-if="treeData1.length > 0" class="tree-wrap"
                                        :default-expanded-keys="defaultExpandedKeys"
                                        :default-selected-keys="defaultSelectedKeys"
                                        :replaceFields="{children:'children', title: 'is_fans', key: 'uid'}"
                                        :load-data="onLoadData1"
                                        :multiple="multiple"
                                        :show-line="true"
                                        :tree-data="treeData1">
                                    <template slot="custom" slot-scope="item">
                                        <p>
                                            <span>
                                                <a-tag class="tags-status" color="#108ee9" v-if="item.status == 0">正常</a-tag>
                                                <a-tag class="tags-status disabled" style="background-color: red;color: #fff;" v-else>禁用</a-tag>
                                            </span>
                                            <span class="vips-name" style="color:#808080">
                                              <span>{{item.nickname ? `(${item.nickname})` : `(${item.realname})`}}</span>
                                            </span>
                                            <span>· 会员ID:<{{item.uid}}> </span>
                                            <span>· 联系电话:<{{item.telphone}}> </span>
                                            <span>· 注册时间:<{{item.created_at}}> </span>
                                        </p>
                                    </template>
                                    <template slot="operate" slot-scope="item">
                                        <a-button
                                                type="link"
                                                size="small"
                                                :disabled="item.finished"
                                                :loading="item.btnLoading"
                                                @click="loadMore1(item)">
                                            {{item.title}}
                                        </a-button>
                                    </template>
                                </a-tree>
                                <a-empty v-else />
                            </a-spin>
                        </a-tab-pane>
                    </a-tabs>
                </template>
            </div>
        </div>
    </div>
</div>

js:

<!-- 引入样式 -->
<!--<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>-->
<script src="/static/js/vue.js"></script>
<!--<script src="https://cdn.jsdelivr.net/npm/ant-design-vue@1.4.10/dist/antd.min.js"></script>-->
<script src="/static/js/antd.min.js"></script>
<!--<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.js"></script>-->
<script src="/static/js/moment.js"></script>
<!--<link href="https://cdn.jsdelivr.net/npm/ant-design-vue@1.4.10/dist/antd.min.css" rel="stylesheet"-->
<link href="/static/css/antd.min.css" rel="stylesheet"
      type="text/css" />
<script src="/static/layuiadmin/layui/layui.js"></script>
<script>
        layui.config({
        base: '/static/layuiadmin/' //静态资源所在路径
    }).extend({
        index: 'lib/index' //主入口模块
    }).use(['jquery','index', 'table'], function(){
        var $ = layui.$

        new Vue({
        el: '#root',
        data(){
            return {
                activeKey:'1',
                keyword: "",
                searchResult:false,
                spinning: false,
                treeData: [],
                spinning1: false,
                treeData1: [],
                pagination1: {
                    current: 1,
                    pageSize: 20,
                },
                defaultExpandedKeys: [],
                defaultSelectedKeys: [],
                multiple: true,
                pagination: {
                    current: 1,
                    pageSize: 20,
                },
            }
        },
        created(){
            this.spinning = true;
            this.initGetUserRelationList(this.pagination);
        },
        methods: {
            // 判断数组中是否有加载更多按钮
            hasLoadBtn(arr){
                return arr.some(item => {
                    return item.uid.toString().indexOf('loadBtn') > -1;
                })
            },
            loadingBtnObj(parent_id, pagination = JSON.parse(JSON.stringify(this.pagination))){
                let loadingBtn = {
                    uid: 'loadBtn-' + parent_id,
                    parent_id: parent_id,
                    title: '加载更多',
                    itemType: 'btn',
                    btnLoading: false,
                    selectable: false,
                    finished: false,
                    isLeaf: true,
                    pagination,
                    scopedSlots: {title: 'operate'}
                }
                return loadingBtn;
            },
            fansChange(is_fans){
                this.is_fans = is_fans;
                this.pagination.current = 1;
                this.treeData = [];
                this.spinning = true;
                this.initGetUserRelationList(this.pagination);
            },
            // 初始化树数据请求
            initGetUserRelationList(pageData){
                getUserRelationList({
                    pageLimit: pageData.pageSize,
                    page: pageData.current,
                    referrer: 0,
                    keyword: "",
                }).then(res => {
                    if(res.code == 200){
                        let currentList = res.data.list;
                        this.initTreeData(currentList)
                        this.addLoadMoreBtn(currentList, 0, cloneDeep(pageData))
                        this.treeData = [...currentList];
                    }
                }).finally(() => {
                    this.spinning = false;
                })
            },
            //
            initTreeData(arr){
                arr.forEach((item) => {
                    // 如果不存在插槽配置,添加
                    if(!item.scopedSlots){
                        this.$set(item, 'scopedSlots', {title: 'custom'})
                    }
                    if(item.is_fans == 1){
                        // 锁粉用户,设置为叶子节点
                        this.$set(item, 'isLeaf', true)
                    }
                    if(item.children && item.children.length > 0){
                        this.initTreeData(item.children)
                    }
                })
            },
            // 根据uid在数组中找到某一项 arr不传则在treeData中查找
            getItemOfKey(uid, arr = this.treeData){
                let node;
                let getItem = (arr) => {
                    for(let i = 0; i < arr.length; i++){
                        let item = arr[i];
                        if(item.uid == uid){
                            node = item;
                            break;
                        }else{
                            if(item.children && item.children.length > 0){
                                getItem(item.children);
                            }
                        }
                    }
                }
                getItem(arr)
                return node;
            },
            // 列表中添加加载更多按钮
            addLoadMoreBtn(list, parent_id, pagination){
                // 不是最后一页 并且没有加载更多按钮 添加按钮
                if(list.length >= this.pagination.pageSize && !this.hasLoadBtn(list)){
                    list.push(this.loadingBtnObj(parent_id, pagination))
                }
            },
            // 列表中删除最后一项加载更多按钮
            delLoadMoreBtn(list){
                // 有按钮才执行删除
                if(this.hasLoadBtn(list)){
                    list.pop();
                }
            },
            // 点击加载更多按钮
            loadMore(item){
                // 按钮节点item
                let data = item;
                item.title = '加载中...';
                item.btnLoading = true;
                item.pagination.current += 1;
                // 父级节点
                let parentItem = null;
                if(data.parent_id != 0){
                    parentItem = this.getItemOfKey(data.parent_id)
                }
                let pagination = cloneDeep(data.pagination);
                getUserRelationList({
                    pageLimit: data.pagination.pageSize,
                    page: data.pagination.current,
                    referrer: data.parent_id,
                    keyword: "",
                }).then(res => {
                    if(res.code == 200){
                        let currentList = res.data.list;
                        if(currentList.length > 0){
                            this.initTreeData(currentList);
                        }
                        // 请求成功后先删除按钮,后添加数据再次添加加载更多按钮;
                        if(parentItem){
                            this.delLoadMoreBtn(parentItem.children);
                        }else{
                            this.delLoadMoreBtn(this.treeData);
                        }
                        // 未加载完毕 按钮重新添加到末尾 加载完毕则不需要加载按钮,不再添加
                        if(parentItem){
                            this.addLoadMoreBtn(currentList, parentItem.uid, cloneDeep(pagination))
                        }else{
                            this.addLoadMoreBtn(currentList, 0, cloneDeep(pagination))
                        }
                        currentList.forEach(item => {
                            if(parentItem){
                                parentItem.children.push(item);
                            }else{
                                this.treeData.push(item);
                            }
                        })
                        this.treeData = [...this.treeData]
                    }
                }).finally(() => {
                    this.spinning = false;
                })
            },
            // 展开时的加载
            onLoadData(treeNode){
                let dataRef = treeNode.dataRef;
                return new Promise((resolve, reject) => {
                    if (dataRef.children && dataRef.children.length > 0) {
                        resolve();
                        return;
                    }
                    getUserRelationList({
                        pageLimit: this.pagination.pageSize,
                        page: this.pagination.current,
                        referrer: dataRef.uid,
                        keyword: "",
                    }).then(res => {
                        if(res.code == 200){
                            let currentList = res.data.list;
                            if(currentList.length > 0){
                                this.initTreeData(currentList)
                                this.addLoadMoreBtn(currentList, dataRef.uid, cloneDeep(this.pagination))
                                this.$set(dataRef, 'children', currentList);
                            }
                        }
                        resolve();
                    }).catch(() => {
                        reject()
                    }).finally(() => {
                        this.spinning = false;
                    })
                })
            },
            // 搜索组件方法
            searchByKeyWord(){
                if(!this.keyword.trim()){
                    layer.msg('请输入搜索内容。');
                    return;
                }
                this.spinning1 = true;
                this.treeData1 = [];
                getUserRelationList({
                    pageLimit: this.pagination1.pageSize,
                    page: this.pagination1.current,
                    referrer: 0,
                    keyword: this.keyword
                }).then(res => {
                    if(res.code == 200){
                        this.searchResult = true;
                        this.activeKey = '2';
                        let currentList = res.data.list;
                        this.initTreeData(currentList);
                        let lastItem = this.getLastItem(currentList);
                        this.addLoadMoreBtn(currentList, 0, cloneDeep(this.pagination1))
                        this.defaultExpandedKeys = [this.spac(currentList)];
                        this.defaultSelectedKeys = [this.spac(currentList) + '-0'];
                        this.treeData1 = [...currentList];
                    }
                }).finally(() => {
                    this.spinning1 = false;
                })
            },
            spac(List){
                var leaf = '0';
                child(List);
                function child(l){
                    if(l[0].children.length > 0){
                        leaf += '-0';
                        child(l[0].children);
                    }else{
                        return;
                    }
                }
                return leaf;
            },
            // 查找最后一项
            getLastItem(arr){
                let node = null;
                let getItem = (arr) => {
                    for(let i = 0; i < arr.length; i++){
                        let item = arr[i];
                        if(item.children && item.children.length > 0){
                            getItem(item.children);
                        }else{
                            node = item;
                        }
                    }
                }
                getItem(arr)
                return node;
            },
            // 展开时的加载
            onLoadData1(treeNode){
                let dataRef = treeNode.dataRef;
                return new Promise((resolve, reject) => {
                    if (dataRef.children && dataRef.children.length > 0) {
                        resolve();
                        return;
                    }
                    getUserRelationList({
                        pageLimit: this.pagination1.pageSize,
                        page: this.pagination1.current,
                        referrer: dataRef.uid,
                        keyword: dataRef.uid?'':this.keyword,
                    }).then(res => {
                        if(res.code == 200){
                            let currentList = res.data.list;
                            if(currentList.length > 0){
                                this.initTreeData(currentList)
                                this.addLoadMoreBtn(currentList, dataRef.uid, cloneDeep(this.pagination1))
                                this.$set(dataRef, 'children', currentList);
                            }
                        }
                        resolve();
                    }).catch(() => {
                        reject()
                    }).finally(() => {
                        this.spinning = false;
                    })
                })
            },
            // 点击加载更多按钮
            loadMore1(item){
                // 按钮节点item
                let data = item;
                data.pagination.current += 1;
                // 父级节点
                let parentItem = this.getItemOfKey(data.parent_id,this.treeData1);
                let pagination = cloneDeep(data.pagination);
                console.log(item)
                console.log(parentItem)
                console.log(pagination)
                data.title = '加载中...';
                data.btnLoading = true;
                getUserRelationList({
                    pageLimit: data.pagination.pageSize,
                    page: data.pagination.current,
                    referrer: data.parent_id,
                    keyword: "",
                }).then(res => {
                    if(res.code == 200){
                        let currentList = res.data.list;
                        if(currentList.length > 0){
                            this.initTreeData(currentList);
                        }
                        // 请求成功后先删除按钮,后添加数据再次添加加载更多按钮;
                        if(parentItem){
                            this.delLoadMoreBtn(parentItem.children);
                        }else{
                            this.delLoadMoreBtn(this.treeData1);
                        }
                        // 未加载完毕 按钮重新添加到末尾 加载完毕则不需要加载按钮,不再添加
                        if(parentItem){
                            this.addLoadMoreBtn(currentList, parentItem.uid, cloneDeep(pagination))
                        }else{
                            this.addLoadMoreBtn(currentList, 0, cloneDeep(pagination))
                        }
                        currentList.forEach(item => {
                            if(parentItem){
                                parentItem.children.push(item);
                            }else{
                                this.treeData1.push(item);
                            }
                        })
                    }
                }).finally(() => {
                    this.spinning1 = false;
                })
            },
        }
    });

    function type(obj) {
        var toString = Object.prototype.toString;
        var map = {
            '[object Boolean]' : 'boolean',
            '[object Number]'  : 'number',
            '[object String]'  : 'string',
            '[object Function]' : 'function',
            '[object Array]'  : 'array',
            '[object Date]'   : 'date',
            '[object RegExp]'  : 'regExp',
            '[object Undefined]': 'undefined',
            '[object Null]'   : 'null',
            '[object Object]'  : 'object'
        };
        if(obj instanceof Element) {
            return 'element';
        }
        return map[toString.call(obj)];
    }
    function cloneDeep(data) {
        var t = type(data), o, i, ni;

        if(t === 'array') {
            o = [];
        }else if( t === 'object') {
            o = {};
        }else {
            return data;
        }

        if(t === 'array') {
            for (i = 0, ni = data.length; i < ni; i++) {
                o.push(cloneDeep(data[i]));
            }
            return o;
        }else if( t === 'object') {
            for( i in data) {
                o[i] = cloneDeep(data[i]);
            }
            return o;
        }
    }
    function getUserRelationList(pageData){
        return new Promise((resolve, reject) => {
            $.ajax({
                url: "/admin/user/getTreeInfo",
                type: "POST",
                data: pageData,
                success:function(res){
                    if(res.code == 200){
                        resolve(res);
                    }
                }
            });
        })
    }

    });



</script>

数据格式:{
  "code": 200,
  "msg": "",
  "data": {
    "pages": {
      "currentPage": "1",
      "lastPage": 1,
      "pageSize": "20",
      "total": 14
    },
    "list": [
      {
        "uid": 2,
        "pid": 1051,
        "nickname": "英雄哥",
        "realname": "石",
        "telphone": "",
        "createtime": 1652671276,
        "isLeaf": false,
        "created_at": "2022-05-16 11:21:16",
        "status": 0,
        "children": []
      },
      {
        "uid": 995,
        "pid": 769,
        "nickname": "那个秋天2021",
        "realname": null,
        "telphone": "",
        "createtime": 1661088860,
        "isLeaf": false,
        "created_at": "2022-08-21 21:34:20",
        "status": 0,
        "children": []
      },
      {
        "uid": 1277,
        "pid": 0,
        "nickname": "A新环房产销售董琪",
        "realname": null,
        "telphone": null,
        "createtime": 1662822961,
        "isLeaf": true,
        "created_at": "2022-09-10 23:16:01",
        "status": 0,
        "children": []
      }
    ]
  }
}

最后放一下代码文件下载地址:会员树下载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值