更新记录

1.28(2020-12-16)

允许通过函数方式配置节点icon

1.27(2020-12-03)

修改node-key合法性判断逻辑

1.26(2020-12-02)

node-key配置错误时的提示,app端treeData变化数据不更新的bug,其他自测的一些小问题和优化

查看更多

平台兼容性

Vue2 Vue3
×
App 快应用 微信小程序 支付宝小程序 百度小程序 字节小程序 QQ小程序
× × × × × × ×
钉钉小程序 快手小程序 飞书小程序 京东小程序
× × × ×
H5-Safari Android Browser 微信浏览器(Android) QQ浏览器(Android) Chrome IE Edge Firefox PC-Safari
× × × × × × × × ×

lyTree 树组件(看文档!!!看文档!!!看文档!!!导入工程项目!有很多示例!)

重要!nvue版本已发布,在components/ly-tree-nvue中,功能与原始版本一致,样式有略微变动,欢迎下载使用,如有问题请加群及时反馈哦~~~

感谢小伙伴们的支持,请点个五星好评~~

心灵脆弱的程序媛求好评~~~切勿做伸手党和喷子

有问题可以直接加QQ群:1079028385,在群里沟通哦~~

pages.json配置(非常重要!!!小程序和APP端只显示一个层级、无法展开就是因为没有配置这个属性!!请使用复制粘贴!不要自己写错单词!):

提示:为了集中精力、减少包体积,2020年4月发布的HBuilderX 2.7版本起,及对应的App SDK包,将不再提供“自定义组件模式”。(uni小程序sdk本身也仅支持v3)。 经验证,如果项目只编译到APP端,这里可以不配置,但配置了也不回有影响,小程序端还是需要配置。

综上:这里的配置建议总是加上,避免小程序端无法展开节点

"globalStyle": {
        ...
        "usingComponents": {
            "ly-tree-node": "/components/ly-tree/ly-tree-node"
        }
    }

关于render-content:

·因为uni非H5端目前不支持render函数,插件依赖于uni,目前这个需求还无法实现,如果有特殊的需求需要自定义节点样式,可以自行修改ly-tree-node哦~;

属性说明:

属性名 类型 默认值 说明
nodeKey String - 必填,每个树节点用来作为唯一标识的属性,整棵树应该是唯一的
treeData Array - 非懒加载时的展示数据
emptyText String 暂无数据 内容为空的时候展示的文本
renderAfterExpand Boolean true 是否在第一次展开某个树节点后才渲染其子节点
checkStrictly Boolean false 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 false,如果设置了checkOnlyLeaf为true,这里也将变为true
defaultExpandAll Boolean false 是否默认展开所有节点
expandOnClickNode Boolean true 是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点
checkOnClickNode Boolean false 是否在点击节点的时候选中节点,默认值为 false,即只有在点击复选框时才会选中节点
autoExpandParent Boolean true 是否在第一次展开某个树节点后才渲染其子节点
defaultCheckedKeys Array - 默认勾选的节点的 key 的数组
defaultExpandedKeys Array - 默认展开的节点的 key 的数组
currentNodeKey String, Number - 当前选中的节点
showCheckbox Boolean false 节点是否可被选择
showRadio Boolean false 节点是否可被单选
props Object 见下文 数据中对应的属性名
lazy Boolean false 是否懒加载子节点,需与 load 方法结合使用
highlightCurrent Boolean false 是否高亮当前选中节点,默认值是 false
load Function - 加载子树数据的方法,仅当 lazy 属性为true 时生效
filterNodeMethod Function - 对树节点进行筛选时执行的方法,返回 true 表示这个节点可以显示,返回 false 则表示这个节点会被隐藏
accordion Boolean false 是否每次只打开一个同级树节点展开
indent Number 18 相邻级节点间的水平缩进,单位为像素
iconClass String - 自定义树节点的展开图标,图标class
ready【新】 Boolean true 自主控制loading加载,避免数据还没获取到的空档出现“暂无数据”字样
showNodeIcon【新】 Boolean false 是否显示节点图标,如果配置为true,需要配置props中对应的图标属性名称
expandOnCheckNode【新】 Boolean true 节点被选中时展开节点
toggleExpendAll【新】 Boolean false 切换全部展开、全部折叠
childVisibleForFilterNode【新】 Boolean false 搜索时是否展示匹配项的所有子节点
checkOnlyLeaf【新】 Boolean false 是否最后一层叶子节点才显示单选/多选框
expandCurrentNodeParent【新】 Boolean false 是否展开当前节点的父节点,设置当前节点时生效
isInjectParentInNode【新】 Boolean false 是否向node示例中挂载parent(父节点实例),如果配置为true,那么在点击或者展开节点等事件中能直接获取到parent;如果数据量较大,建议不要在node节点中添加parent属性,会造成性能损耗
defaultNodeIcon【新】 String - 当节点图标显示出错时,显示的默认图标

基本用法:

·请注意node-key必须配置,指数据中每个节点的唯一标志,打个比方:

如果数据中以id数据项当做节点的唯一标志,node-key就是"id";

如果数据中以guid数据项当做节点的唯一标志,node-key就是"guid";

如果数据中以personId数据项当做节点的唯一标志,node-key就是"personId"...
<template>
    <ly-tree :tree-data="treeData" 
        :ready="ready"
        node-key="id" 
        @node-expand="handleNodeExpand" 
        @node-click="handleNodeClick">
        </ly-tree>
</template>
import LyTree from '@/components/ly-tree/ly-tree.vue'
export default {
    components: {
        LyTree
    },
    data() {
        return {
            ready: false, // 这里用于自主控制loading加载状态,避免异步正在加载数据的空档显示“暂无数据”
            treeData: []
        };
    },
    onLoad() {
        // 模拟异步请求
        setTimeout(() => {
            this.treeData = [{
                id: 1,
                label: '一级 1',
                children: [{
                    id: 11,
                    label: '二级 1-1',
                    children: [{
                        id: 111,
                        label: '三级 1-1-1'
                    }]
                }]
            }, {
                id: 2,
                label: '一级 2',
                children: [{
                    id: 21,
                    label: '二级 2-1',
                    children: [{
                        id: 211,
                        label: '三级 2-1-1'
                    }]
                }, {
                    id: 22,
                    label: '二级 2-2',
                    children: [{
                        id: 221,
                        label: '三级 2-2-1'
                    }]
                }]
            }, {
                id: 3,
                label: '一级 3',
                children: [{
                    id: 31,
                    label: '二级 3-1',
                    children: [{
                        id: 311,
                        label: '三级 3-1-1'
                    }]
                }, {
                    id: 32,
                    label: '二级 3-2',
                    children: [{
                        id: 321,
                        label: '三级 3-2-1'
                    }]
                }]
            }];

            this.ready = true;
        }, 2000);
    },
    methods: {
        // uni-app中emit触发的方法只能接受一个参数,所以会回传一个对象,打印对象即可见到其中的内容
        handleNodeClick(obj) {
            console.log('handleNodeClick', JSON.stringify(obj));
        },
        handleNodeExpand(obj) {
            console.log('handleNodeExpand', JSON.stringify(obj));
        }
    }
};

这个时候可能有人提出:如果我的数据结构不是{id: 11, label: 'xx', children: []},而是{personId: 11, name: 'xx', childs: []},该怎么办??以下props属性配置

props 属性说明:

属性名 类型 默认值 说明
label string, function(data, node) - 指定节点标签为节点对象的某个属性值
icon[new] String - 指定节点图标为节点对象的某个属性值
children string - 指定子树为节点对象的某个属性值
disabled String boolean, function(data, node) 指定节点选择框是否禁用为节点对象的某个属性值
isLeaf boolean, function(data, node) true 指定节点是否为叶子节点,仅在指定了 lazy 属性的情况下生效
<template>
    <ly-tree :tree-data="treeData" 
        :props="props"
        node-key="personId" 
        @node-expand="handleNodeExpand" 
        @node-click="handleNodeClick">
        </ly-tree>
</template>
import LyTree from '@/components/ly-tree/ly-tree.vue'
export default {
    components: {
        LyTree
    },
    data() {
        return {
            ready: false,
            // 如果想自定义函数渲染节点名称等,需要将props设置成返回配置对象的函数,否则如果props是一个对象(uni会将对象中的函数过滤掉),对象中的函数属性会被过滤掉
            props: function() {
                return {
                    // 这里的label就可以使用函数进行自定义的渲染了
                    label(data, node) {
                        return '节点' + node.data.personName;
                    },
                    // label: 'personName', // 指把数据中的‘personName’当做label也就是节点名称
                    children: 'childs' // 指把数据中的‘childs’当做children当做子节点数据
                }
            },
            // 如果只是单纯的从数据中取某一个字段作为对应属性,props可以直接设置为对象
            // props: {
            //  label: 'personName', // 指把数据中的‘personName’当做label也就是节点名称
            //  children: 'childs' // 指把数据中的‘childs’当做children当做子节点数据
            // },
            treeData: [{
                personId: 1,
                personName: '一级 1',
                childs: [{
                    personId: 11,
                    personName: '二级 1-1',
                    childs: [{
                        personId: 111,
                        personName: '三级 1-1-1'
                    }]
                }]
            }, {
                personId: 2,
                personName: '一级 2',
                childs: [{
                    personId: 21,
                    personName: '二级 2-1',
                    childs: [{
                        personId: 211,
                        personName: '三级 2-1-1'
                    }]
                }, {
                    personId: 22,
                    personName: '二级 2-2',
                    childs: [{
                        personId: 221,
                        personName: '三级 2-2-1'
                    }]
                }]
            }, {
                personId: 3,
                personName: '一级 3',
                childs: [{
                    personId: 31,
                    personName: '二级 3-1',
                    childs: [{
                        personId: 311,
                        personName: '三级 3-1-1'
                    }]
                }, {
                    personId: 32,
                    personName: '二级 3-2',
                    childs: [{
                        personId: 321,
                        personName: '三级 3-2-1'
                    }]
                }]
            }]
        };
    },
    methods: {
        // uni-app中emit触发的方法只能接受一个参数,所以会回传一个对象,打印对象即可见到其中的内容
        handleNodeClick(obj) {
            console.log('handleNodeClick', JSON.stringify(obj));
        },
        handleNodeExpand(obj) {
            console.log('handleNodeExpand', JSON.stringify(obj));
        }
    }
};

懒加载:

tips:为了确保页面加载完成后才去调用load方法,this指向正确,使用v-if懒加载树组件,在页面的onLoad之后调用

<template>
    <ly-tree v-if="isReady" :props="props" node-key="name" :load="loadNode" lazy>
    </ly-tree>
</template>
import LyTree from '@/components/ly-tree/ly-tree.vue'

var _self;
export default {
    components: {
        LyTree
    },
    data() {
        return {
            //为了确保页面加载完成后才去调用load方法,this指向正确
            isReady: false,
            props: {
                label: 'name',
                children: 'zones',
                isLeaf: 'leaf'
            }
        };
    },

    onLoad() {
        _self = this;
        this.isReady = true;
    },

    methods: {
        // 因为这个函数是在Vue实例以外的地方调用,如果函数内部需要用到this,需要改成_self
        loadNode(node, resolve) {
            // _self.xxx; 这里用_self而不是this

            if (node.level === 0) {
                setTimeout(() => {
                    resolve([{
                        name: 'region'
                    }]);
                }, 1000);
            } else {
                if (node.level > 1) return resolve([]);

                setTimeout(() => {
                    const data = [{
                        name: 'leaf',
                        leaf: true
                    }, {
                        name: 'zone'
                    }];

                    resolve(data);
                }, 500);
            }
        }
    }
};

方法及事件:

以下是目前树支持的一些方法,更多详情请参见elementUI说明:https://element.eleme.cn/#/zh-CN/component/tree

tips:为树组件绑定ref属性,如ref="tree",则调用方式为this.$refs.tree.getCheckedNodes(),详情见工程示例“树节点的选择”

/*
 * @description 对树节点进行筛选操作
 * @method filter
 * @param {all} value 在 filter-node-method 中作为第一个参数
 * @param {Object} data 搜索指定节点的节点数据,不传代表搜索所有节点,假如要搜索A节点下面的数据,那么nodeData代表treeData中A节点的数据
*/
filter(value, data) {
    if (!this.filterNodeMethod) throw new Error('[Tree] filterNodeMethod is required when filter');
    this.store.filter(value, data);
},

/*
 * @description 获取节点的唯一标识符
 * @method getNodeKey
 * @param {String, Number} nodeId
 * @return {String, Number} 匹配到的数据中的某一项数据
*/
getNodeKey(nodeId) {
    let node = this.store.root.getChildNodes([nodeId])[0];
    return getNodeKey(this.nodeKey, node.data);
},

/*
* @description 获取节点路径
* @method getNodePath
* @param {Object} data 节点数据
* @return {Array} 路径数组
*/
getNodePath(data) {
    return this.store.getNodePath(data);
},

/*
 * @description 若节点可被选择(即 show-checkbox 为 true),则返回目前被选中的节点所组成的数组
 * @method getCheckedNodes
 * @param {Boolean} leafOnly 是否只是叶子节点,默认false
 * @param {Boolean} includeHalfChecked 是否包含半选节点,默认false
 * @return {Array} 目前被选中的节点所组成的数组
*/
getCheckedNodes(leafOnly, includeHalfChecked) {
    return this.store.getCheckedNodes(leafOnly, includeHalfChecked);
},

/*
 * @description 若节点可被选择(即 show-checkbox 为 true),则返回目前被选中的节点的 key 所组成的数组
 * @method getCheckedKeys
 * @param {Boolean} leafOnly 是否只是叶子节点,默认false,若为 true 则仅返回被选中的叶子节点的 keys
 * @param {Boolean} includeHalfChecked 是否返回indeterminate为true的节点,默认false
 * @return {Array} 目前被选中的节点所组成的数组
*/
getCheckedKeys(leafOnly, includeHalfChecked) {
    return this.store.getCheckedKeys(leafOnly, includeHalfChecked);
},

/*
 * @description 获取当前被选中节点的 data,若没有节点被选中则返回 null
 * @method getCurrentNode
 * @return {Object} 当前被选中节点的 data,若没有节点被选中则返回 null
*/
getCurrentNode() {
    const currentNode = this.store.getCurrentNode();
    return currentNode ? currentNode.data : null;
},

/*
 * @description 获取当前被选中节点的 key,若没有节点被选中则返回 null
 * @method getCurrentKey
 * @return {all} 当前被选中节点的 key, 若没有节点被选中则返回 null
*/
getCurrentKey() {
    const currentNode = this.getCurrentNode();
    return currentNode ? currentNode[this.nodeKey] : null;
},

/*
 * @description 设置全选/取消全选
 * @method setCheckAll
 * @param {Boolean} isCheckAll 选中状态,默认为true
*/
setCheckAll(isCheckAll = true) {
    if (this.showRadio) throw new Error('You set the "show-radio" property, so you cannot select all nodes');

    if (!this.showCheckbox) console.warn('You have not set the property "show-checkbox". Please check your settings');

    this.store.setCheckAll(isCheckAll);
},

/*
 * @description 设置目前勾选的节点
 * @method setCheckedNodes
 * @param {Array} nodes 接收勾选节点数据的数组
 * @param {Boolean} leafOnly 是否只是叶子节点, 若为 true 则仅设置叶子节点的选中状态,默认值为 false
*/
setCheckedNodes(nodes, leafOnly) {
    this.store.setCheckedNodes(nodes, leafOnly);
},

/*
 * @description 通过 keys 设置目前勾选的节点
 * @method setCheckedKeys
 * @param {Array} keys 勾选节点的 key 的数组 
 * @param {Boolean} leafOnly 是否只是叶子节点, 若为 true 则仅设置叶子节点的选中状态,默认值为 false
*/
setCheckedKeys(keys, leafOnly) {
    if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCheckedKeys');
    this.store.setCheckedKeys(keys, leafOnly);
},

/*
 * @description 通过 key / data 设置某个节点的勾选状态
 * @method setChecked
 * @param {all} data 勾选节点的 key 或者 data 
 * @param {Boolean} checked 节点是否选中
 * @param {Boolean} deep 是否设置子节点 ,默认为 false
*/
setChecked(data, checked, deep) {
    this.store.setChecked(data, checked, deep);
},

/*
 * @description 若节点可被选择(即 show-checkbox 为 true),则返回目前半选中的节点所组成的数组
 * @method getHalfCheckedNodes
 * @return {Array} 目前半选中的节点所组成的数组
*/
getHalfCheckedNodes() {
    return this.store.getHalfCheckedNodes();
},

/*
 * @description 若节点可被选择(即 show-checkbox 为 true),则返回目前半选中的节点的 key 所组成的数组
 * @method getHalfCheckedKeys
 * @return {Array} 目前半选中的节点的 key 所组成的数组
*/
getHalfCheckedKeys() {
    return this.store.getHalfCheckedKeys();
},

/*
 * @description 通过 node 设置某个节点的当前选中状态
 * @method setCurrentNode
 * @param {Object} node 待被选节点的 node
*/
setCurrentNode(node) {
    if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCurrentNode');
    this.store.setUserCurrentNode(node);
},

/*
 * @description 通过 key 设置某个节点的当前选中状态
 * @method setCurrentKey
 * @param {all} key 待被选节点的 key,若为 null 则取消当前高亮的节点
*/
setCurrentKey(key) {
    if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCurrentKey');
    this.store.setCurrentNodeKey(key);
},

/*
 * @description 根据 data 或者 key 拿到 Tree 组件中的 node
 * @method getNode
 * @param {all} data 要获得 node 的 key 或者 data
*/
getNode(data) {
    return this.store.getNode(data);
},

/*
 * @description 删除 Tree 中的一个节点
 * @method remove
 * @param {all} data 要删除的节点的 data 或者 node
*/
remove(data) {
    this.store.remove(data);
},

/*
 * @description 为 Tree 中的一个节点追加一个子节点
 * @method append
 * @param {Object} data 要追加的子节点的 data 
 * @param {Object} parentNode 子节点的 parent 的 data、key 或者 node
*/
append(data, parentNode) {
    this.store.append(data, parentNode);
},

/*
 * @description 为 Tree 的一个节点的前面增加一个节点
 * @method insertBefore
 * @param {Object} data 要增加的节点的 data 
 * @param {all} refNode 要增加的节点的后一个节点的 data、key 或者 node
*/
insertBefore(data, refNode) {
    this.store.insertBefore(data, refNode);
},

/*
 * @description 为 Tree 的一个节点的后面增加一个节点
 * @method insertAfter
 * @param {Object} data 要增加的节点的 data 
 * @param {all} refNode 要增加的节点的前一个节点的 data、key 或者 node
*/
insertAfter(data, refNode) {
    this.store.insertAfter(data, refNode);
},

/*
 * @description 通过 keys 设置节点子元素
 * @method updateKeyChildren
 * @param {String, Number} key 节点 key 
 * @param {Object} data 节点数据的数组
*/
updateKeyChildren(key, data) {
    if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in updateKeyChild');
    this.store.updateChildren(key, data);
}

说明:

·本插件改编自element-UI,去掉了拖拽和动画,其他功能基本全部保留了,并且新增了单选功能,代码改动非常大;

·element-UI的树组件仅支持H5端,APP端无法给组件挂载深度嵌套的数据对象,所以做了处理,使用组件时必须配置nodeKey;

·APP端语法很严格,各种奇怪的bug(但都解决了),做起来非常头疼,可能有些细节方面有些小问题,希望大家多多包涵~

·因为本人工作非常忙,没有太多时间关注插件市场,所以如果有扩展需求或者bug,可以自己解决的尽量自己解决哟~

·亲测H5端、APP端、微信小程序可用,其他小程序没测过,应该也没什么问题~~

工作太忙了,没有时间详细的写文档,请移步element-UI官网,感谢大家的支持,多多包涵~~

版本更新:

版本 更新时间 更新说明
1.01 2019-12-04 修复异步获取数据treeData不加载的问题
1.02 2019-12-12 解决小程序手风琴不生效的bug 使用闭包方式解决多个树并存时,store对象被覆盖导致的一系列指向问题
1.03 2019-12-16 修改样式class(iconfont => ly-iconfont),避免与项目冲突
1.04 2019-12-21 解决checkOnClickNode在单选中失效的bug 新增checkOnlyLeaf配置,当节点是最后一层叶子节点时才显示选择框
1.05 2019-12-26 解决undefined is not an object (evaluating 'e.detail.args'); 的bug
1.06 2019-12-27 uni全局监听事件加上唯一标志前缀,避免多个tree并存时监听到同一个事件
1.07 2020-01-04 新增showNodeIcon,可配置是否显示节点图标,如果配置为true,需要配置props中对应的图标属性名称 新增childVisibleForFilterNode配置,可配置搜索时是否展示匹配项的所有子节点,默认false
1.08 2020-01-05 新增搜索指定空节点功能及样例
1.09 2020-02-08 解决HbuilderX2.5.1版本更新后由于底层编译机制变化导致APP端显示“暂无数据”的问题;新增ready属性已自主控制loading加载状态避免未获取到数据时显示“暂无数据”的问题;新增在抽屉/弹框中使用树组件的示例
1.10 2020-02-20 修改了一个小bug
1.11 2020-03-19 解决icon不支持class样式的问题,解决icon图标在APP端的显示问题 新增toggleExpendAll配置:切换全部展开、全部折叠
1.12 2020-03-23 一些实现方式的调整,补充方法说明文档,样例样式改为普通css,避免部分用户需要安装sass插件才能运行
1.13 2020-04-06 解决showRadio时,默认选中会选择多个的问题;支持props配置函数,返回配置对象,解决label属性等无法使用函数渲染的问题
1.14 2020-04-17 新增全选/取消全选事件 check-change事件参数新增checkedall表示当前树所有节点是否都选中 新增expandOnCheckNode表示选中时展开节点
1.15 2020-04-22 修改一个小bug,并新增删除节点示例
1.16 2020-05-11 可配置数据中的checked属性对应字段,组件事件可获取Node,Node中新增parent属性,getCheckedKeys方法新增includeHalfChecked配置可返回indeterminate为true的节点
1.17 2020-05-18 新增expandCurrentNodeParent配置,是否展开当前节点的父节点,设置当前节点时生效
1.18 2020-05-25 新增isInjectParentInNode配置,可配置是否向node示例中添加parent实例对象;调整radio选择框大小,在原有基础上调大了一些更容易点击到
1.19 2020-05-29 解决handleRadioChange的bug,完善示例,一些优化
1.20 2020-05-29 解决IOS上运行出错的bug
1.21 2020-06-30 样式改为组件内引入,无需全局引入样式
1.22 2020-07-09 发布nvue版本组件,欢迎下载试用
1.23 2020-07-22 解决expandOnCheckNode设置为false时不生效的bug,解决微信小程序端highlightCurrent不生效的bug
1.24 2020-08-12 解决微信小程序端点击时出现背景色的问题,解决某些场景下比如key为0时出现的样式等异常
1.25 2020-10-28 解决数据改变时,树组件不更新的bug,新增节点图标加载出错时,显示的默认图标配置
1.26 2020-12-02 node-key配置错误时的提示,app端treeData变化数据不更新的bug,其他自测的一些小问题和优化
1.27 2020-12-03 修改node-key合法性判断逻辑
1.28 2020-12-16 允许通过函数方式配置节点icon

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

使用中有什么不明白的地方,就向插件作者提问吧~ 我要提问