vue2.x + zTree,简单的二次封装(二) -- 添加模糊搜索功能

2 篇文章 0 订阅

注:本方法是根据官方提供的模糊搜索功能fuzzySearch函数进行稍微修改,添加到自己的封装Tree中

话不多说,直接上效果图

在这里插入图片描述

引入zTree以及使用方式请看第一篇文章 vue2.x + zTree,简单的二次封装

目录层级

在这里插入图片描述
目录层级中的文件可以在第一篇文章中找到。

实现的功能。

  1. 配置zTree 的节点数据 data,属性zTreeNodes。
  2. 配置zTree的setting属性,拆分优化,分为3个优先级
  3. 增加默认展开的节点的 key 的数组, 属性checkNodeKeys
  4. 增加默认展开的节点的 key 的数组, 属性expandNodeKeys
  5. 增加每个树节点用来作为唯一标识的属性, 属性zTreeNodeKey
  6. 整理获取节点的事件,以及返回值
  7. 增加模糊搜索功能

主要代码:

  1. 在index.vue引入fuzzysearch.js,用来模糊搜索。

    在这里插入图片描述

  2. 使用fuzzySearch()函数

    在这里插入图片描述

代码如下

文件:fuzzysearch.js

/**
 * @param ztree对象的id,不需要#
 * @param keywords 模糊搜索的内容
 * @param isHighLight 是否高亮,默认高亮,传入false禁用
 * @param isExpand 是否展开,默认合拢,传入true展开
 *
 * @returns
 */
export function fuzzySearch(zTreeId, keywords, isHighLight = true, isExpand = true) {
	const zTreeObj = $.fn.zTree.getZTreeObj(zTreeId);//get the ztree object by ztree id
	if (!zTreeObj) {
		alert("fail to get ztree object");
	}
	let nameKey = zTreeObj.setting.data.key.name; //get the key of the node name
	zTreeObj.setting.view.nameIsHTML = isHighLight; //allow use html in node name for highlight use

	const metaChar = "[\\[\\]\\\\\^\\$\\.\\|\\?\\*\\+\\(\\)]"; //js meta characters
	const rexMeta = new RegExp(metaChar, "gi");//regular expression to match meta characters

	// keywords filter function
	function ztreeFilter(zTreeObj, _keywords, callBackFunc) {
		if (!_keywords) {
			_keywords = ""; //default blank for _keywords
		}

		// function to find the matching node
		function filterFunc(node) {
			if (node && node.oldname && node.oldname.length > 0) {
				node[nameKey] = node.oldname; //recover oldname of the node if exist
			}
			zTreeObj.updateNode(node); //update node to for modifications take effect
			if (_keywords.length === 0) {
				//return true to show all nodes if the keyword is blank
				zTreeObj.showNode(node);
				zTreeObj.expandNode(node, false);
				return true;
			}
			//transform node name and keywords to lowercase
			if (node[nameKey] && node[nameKey].toLowerCase().indexOf(_keywords.toLowerCase()) !== -1) {
				if (isHighLight) { //highlight process
					//a new variable 'newKeywords' created to store the keywords information
					//keep the parameter '_keywords' as initial and it will be used in next node
					//process the meta characters in _keywords thus the RegExp can be correctly used in str.replace
					var newKeywords = _keywords.replace(rexMeta, function (matchStr) {
						//add escape character before meta characters
						return "\\" + matchStr;
					});
					node.oldname = node[nameKey]; //store the old name
					var rexGlobal = new RegExp(newKeywords, "gi");//'g' for global,'i' for ignore case
					// use replace(RegExp,replacement) since replace(/substr/g,replacement) cannot be used here
					node[nameKey] = node.oldname.replace(rexGlobal, function (originalText) {
						// highlight the matching words in node name
						return `<span style="color: whitesmoke;background-color: darkred;">${originalText}</span>"`;
					});
					zTreeObj.updateNode(node); //update node for modifications take effect
				}
				zTreeObj.showNode(node);//show node with matching keywords
				return true; //return true and show this node
			}

			zTreeObj.hideNode(node); // hide node that not matched
			return false; //return false for node not matched
		}

		const nodesShow = zTreeObj.getNodesByFilter(filterFunc); //get all nodes that would be shown
		processShowNodes(nodesShow, _keywords);//nodes should be reprocessed to show correctly
	}

	/**
	 * reprocess of nodes before showing
	 */
	function processShowNodes(nodesShow, _keywords) {
		if (nodesShow && nodesShow.length > 0) {
			//process the ancient nodes if _keywords is not blank
			if (_keywords.length > 0) {
				$.each(nodesShow, function (n, obj) {
					var pathOfOne = obj.getPath();//get all the ancient nodes including current node
					if (pathOfOne && pathOfOne.length > 0) {
						//i < pathOfOne.length-1 process every node in path except self
						for (var i = 0; i < pathOfOne.length - 1; i++) {
							zTreeObj.showNode(pathOfOne[i]); //show node
							zTreeObj.expandNode(pathOfOne[i], true); //expand node
						}
					}
				});
			} else { //show all nodes when _keywords is blank and expand the root nodes
				var rootNodes = zTreeObj.getNodesByParam("level", "0");//get all root nodes
				$.each(rootNodes, function (n, obj) {
					zTreeObj.expandNode(obj, true); //expand all root nodes
				});
			}
		}
	}

	ztreeFilter(zTreeObj, keywords); //lazy load ztreeFilter function
}

文件:r-z-tree/index.vue

<template>
    <section class="r-z-tree">
        <slot name="form">
            <div class="default-search-form">
                <el-input
                        suffix-icon="el-icon-search"
                        placeholder="请输入关键字"
                        v-model="keyword"
                        size="mini"
                        clearable
                        @keyup.enter.native="createFuzzySearch"
                        @clear="createFuzzySearch"
                >
                </el-input>
                <el-button type="primary" size="mini" @click="createFuzzySearch">查询</el-button>
            </div>
        </slot>
        <ul :id="treeId" class="ztree"></ul>

    </section>
</template>

<script>
    import _ from "lodash";
    import $ from "jquery";
    import "@ztree/ztree_v3/js/jquery.ztree.core.min.js";
    import "@ztree/ztree_v3/js/jquery.ztree.excheck.min.js";
    import "@ztree/ztree_v3/js/jquery.ztree.exedit.min.js";
    import "@ztree/ztree_v3/js/jquery.ztree.exhide.min.js";
    import "@ztree/ztree_v3/css/zTreeStyle/zTreeStyle.css";
    import "@ztree/ztree_v3/css/metroStyle/metroStyle.css";
    import props from "./props";
    import settingConfig, * as settingFun from "./setting";
    import { fuzzySearch } from "./js/fuzzysearch";

    export default {
        props,
        name: "r-z-tree",
        data() {
            return {
           		keyword: "",
                treeObj: null,
                dialogVisible: false,
            };
        },
        computed: {
            settingDeep() {
                // props传输setting,最高优先级
                const zTreeSetting = this.setting;
                // props传输setting.check
                const zTreeCheck = this.zTreeCheck;
                // props传输setting.data.keep
                const zTreeKeep = this.zTreeKeep;
                // props传输setting.data.key
                const zTreeKey = this.zTreeKey;
                // props传输setting.data.simpleData
                const zTreeSimpleData = this.zTreeSimpleData;
                // props传输setting.data.render
                const zTreeRender = this.zTreeRender;
                // props传输setting.edit
                const zTreeEdit = this.zTreeEdit;
                // props传输setting.view
                const zTreeView = this.zTreeView;

                // 默认使用settingConfig中的配置,最低优先级
                let setting = _.cloneDeep(settingConfig(this));

                // 最高优先级 props传输setting
                if (zTreeSetting) return zTreeSetting;
                // 其次优先级
                // 自定义setting.check
                if (zTreeCheck) setting.check = { ...setting.check, ...zTreeCheck };
                // 自定义setting.data.keep
                if (zTreeKeep) setting.data.keep = { ...setting.data.keep, ...zTreeKeep };
                // 自定义setting.data.key
                if (zTreeKey) setting.data.key = { ...setting.data.key, ...zTreeKey };
                // 自定义setting.data.simpleData
                if (zTreeSimpleData) setting.data.simpleData = { ...setting.data.simpleData, ...zTreeSimpleData };
                // 自定义setting.data.render
                if (zTreeRender) setting.data.render = { ...setting.data.render, ...zTreeRender };
                // 自定义setting.edit
                if (zTreeEdit) setting.edit = { ...setting.edit, ...zTreeEdit };
                // 自定义setting.view
                if (zTreeView) setting.view = { ...setting.view, ...zTreeView };
                return setting;
            },
        },
        watch: {
            zTreeNodes: {
                handler() {
                    if (this.zTreeNodes.length > 0) {
                        // 销毁zTree
                        this.destroyTree();
                        // 初始化zTree
                        this.zTreeInit();
                    }
                },
                deep: true,
                immediate: true,
            },
            checkNodeKeys: {
                handler() {
                    // zTree 的节点已选择的keys
                    if (this.checkNodeKeys.length > 0) this.zTree_checkNodes();
                },
                deep: true,
                immediate: true,
            },
            expandNodeKeys: {
                handler() {
                    // zTree 的节点已展开的keys
                    if (this.expandNodeKeys.length > 0) this.zTree_expandNodes();
                },
                deep: true,
                immediate: true,
            },
        },
        methods: {
            async zTree_checkNodes() {
                try {
                    // 选择指定节点 keys checkNodeKeys = []
                    const { treeObj } = await this.zTreeInit();
                    const nodeKey = settingFun.createZTreeNodeKey(treeObj, this.zTreeNodeKey);
                    for (let value of this.checkNodeKeys) {
                        const node = await this.getNodesByParam(nodeKey, value);
                        if (node.length !== 1) {
                            console.error(`设置的zTreeNodeKey或setting.data.simpleData.idKey不是唯一值。nodeKey=${ nodeKey }`);
                            if (node.length === 0) continue;
                        }
                        this.checkNode(node[0], true, true);
                    }
                } catch (e) {
                    console.error("选择指定节点 keys checkNodeKeys = []", e);
                }
            },
            async checkNode(treeNode, checked = null, checkTypeFlag = null, callbackFlag = false) {
                try {
                    // 勾选 或 取消勾选 单个节点。[setting.check.enable = true 时有效]
                    const { treeObj } = await this.zTreeInit();
                    treeObj.checkNode(treeNode, checked, checkTypeFlag, callbackFlag);
                } catch (e) {
                    console.error("勾选 或 取消勾选 单个节点。[setting.check.enable = true 时有效]", e);
                }
            },
            async checkAllNodes(checked = true) {
                try {
                    // 勾选 或 取消勾选 全部节点。[setting.check.enable = true 且 setting.check.chkStyle = "checkbox" 时有效]
                    // 此方法不会触发 beforeCheck / onCheck 事件回调函数。
                    const { treeObj } = await this.zTreeInit();
                    treeObj.checkAllNodes(checked);
                } catch (e) {
                    console.error("勾选 或 取消勾选 全部节点。[setting.check.enable = true 且 setting.check.chkStyle = \"checkbox\" 时有效]", e);
                }
            },
            async zTree_expandNodes() {
                try {
                    // 展开指定节点 expandNodeKeys = []
                    const { treeObj } = await this.zTreeInit();
                    const nodeKey = settingFun.createZTreeNodeKey(treeObj, this.zTreeNodeKey);
                    for (let value of this.expandNodeKeys) {
                        const node = await this.getNodesByParam(nodeKey, value);
                        if (node.length !== 1) {
                            console.error(`设置的zTreeNodeKey或setting.data.simpleData.idKey不是唯一值。nodeKey=${ nodeKey }`);
                            if (node.length === 0) continue;
                        }
                        this.expandNode(node[0], true);
                    }
                } catch (e) {
                    console.error("展开指定节点 expandNodeKeys = []", e);
                }
            },
            async expandNode(treeNode, expandFlag = null, sonSign = false, focus = true, callbackFlag = false) {
                try {
                    // 展开 / 折叠 指定的节点
                    const { treeObj } = await this.zTreeInit();
                    return treeObj.expandNode(treeNode, expandFlag, sonSign, focus, callbackFlag);
                } catch (e) {
                    console.error("展开 / 折叠 指定的节点", e);
                }
            },
            async setEditable(editable) {
                try {
                    // 设置 zTree 进入 / 取消 编辑状态。
                    // 对于编辑状态的各种功能需要提前设置对应 setting 中的不同属性
                    // 请通过 zTree 对象执行此方法。
                    // editable Boolean true 表示进入 编辑状态; false 表示取消 编辑状态
                    const { treeObj } = await this.zTreeInit();
                    treeObj.setEditable(editable);
                } catch (e) {
                    console.error("设置 zTree 进入 / 取消 编辑状态。", e);
                }
            },
            onAddNodeSubmit(treeObj, treeNode, newNode) {
                this.$emit("on-add-node", treeObj, treeNode, newNode);
            },
            async editName(treeNode) {
                try {
                    // 设置某节点进入编辑名称状态。
                    // 1、如果需要用 js 取消编辑名称状态,请使用 cancelEditName(newName) 方法。
                    // 2、可利用此方法让当前正编辑的节点 input 输入框获取焦点。
                    // 3、请通过 zTree 对象执行此方法。
                    const { treeObj } = await this.zTreeInit();
                    treeObj.editName(treeNode);
                } catch (e) {
                    console.error("设置某节点进入编辑名称状态", e);
                }
            },
            async updateNode(treeNode, checkTypeFlag) {
                try {
                    // 更新某节点数据,主要用于该节点显示属性的更新。
                    // 1、可针对 name、target、 url、icon、 iconSkin、checked、nocheck 等这几个用于显示效果的参数进行更新,
                    // 其他用于 zTreeNodes 的参数请不要随意更新,对于展开节点,还请调用 expandNode方法,因此请勿随意修改 open 属性。
                    // 2、用此方法修改 checked 勾选状态不会触发 beforeCheck / onCheck 事件回调函数。
                    // 请通过 zTree 对象执行此方法。
                    const { treeObj } = await this.zTreeInit();
                    treeObj.updateNode(treeNode, checkTypeFlag);
                } catch (e) {
                    console.error("更新某节点数据,主要用于该节点显示属性的更新。", e);
                }
            },
            async removeNode(treeNode, callbackFlag = false) {
                try {
                    // 删除节点 treeNode [string, Number, Object],可以传node-key或treeNode
                    const { treeObj } = await this.zTreeInit();
                    const nodeKey = settingFun.createZTreeNodeKey(treeObj, this.zTreeNodeKey);
                    // 如果传的treeNode直接使用,如果传的node-key,先获取treeNode
                    treeNode = typeof treeNode === "object" ? treeNode : await this.getNodeByParam(nodeKey, treeNode);
                    const zTreeObject = $.fn.zTree.getZTreeObj(this.treeId);
                    zTreeObject.removeNode(treeNode, callbackFlag);
                } catch (e) {
                    console.error("删除节点", e);
                }
            },
            getCheckedNodes() {
                // 开启复选框和单选框 setting.check.enable = true
                // 获取zTree已选择的树节点 返回值 checkNodes Array, checkKeys Array
                return settingFun.createZTreeCheckNodes(this.treeId, this.zTreeNodeKey);
            },
            getSelectedNodes() {
                // 未开启复选框和单选框 setting.check.enable = false
                // 获取 zTree 当前被选中的节点数据集合 selectNodes Array, selectKeys Array
                return settingFun.createZTreeSelectNodes(this.treeId, this.zTreeNodeKey);
            },
            async getNodeByParam(key, value, parentNode = null) {
                try {
                    // 根据节点数据的属性搜索,获取条件完全匹配的节点数据 JSON 对象 如无结果,返回 null
                    const { treeObj } = await this.zTreeInit();
                    return treeObj.getNodeByParam(key, value, parentNode);
                } catch (e) {
                    console.error("根据节点数据的属性搜索,获取条件完全匹配的节点数据 JSON 对象", e);
                }
            },
            async getNodesByParam(key, value, parentNode = null) {
                try {
                    // 根据节点数据的属性搜索,获取条件完全匹配的节点数据 JSON 对象集合 如无结果,返回 [ ]
                    const { treeObj } = await this.zTreeInit();
                    return treeObj.getNodesByParam(key, value, parentNode);
                } catch (e) {
                    console.error("根据节点数据的属性搜索,获取条件完全匹配的节点数据 JSON 对象集合", e);
                }
            },
            createFuzzySearch() {
                // 模糊搜索
                fuzzySearch(this.treeId, this.keyword);
            },
            destroyTree() {
                // 销毁zTree
                if (this.treeObj) {
                    this.treeObj.destroy();
                    this.treeObj = null;
                }
            },
            zTreeInit() {
                // 初始化zTree
                return new Promise(resolve => {
                    this.$nextTick(() => {
                        if (this.treeObj) return resolve({ treeObj: this.treeObj });
                        this.treeObj = $.fn.zTree.init($(`#${ this.treeId }`), this.settingDeep, this.zTreeNodes);
                        console.log(`创建zTree-id=${ this.treeId }`);
                        return resolve({ treeObj: this.treeObj });
                    });
                });
            },
        },
        beforeDestroy() {
            // 销毁zTree
            this.destroyTree();
        },
    };
</script>


使用,效果如上图所示

<template>
    <el-row :gutter="20">
       <el-col :span="6">
           <h1>封装的搜索</h1>
           <r-z-tree
                   ref="zTreeRef"
                   treeId="treeId1"
                   :z-tree-nodes="zTreeNodes"
                   :expand-node-keys="[1]"
                   :check-node-keys="checkNodeKeys"
                   :z-tree-check="zTreeCheck"
                   :z-tree-simple-data="zTreeSimpleData"
           ></r-z-tree>
       </el-col>
       <el-col :span="6">
           <h1>可自定义搜索,使用slot="form"</h1>
           <r-z-tree
                   ref="zTreeRef"
                   treeId="treeId2"
                   :z-tree-nodes="zTreeNodes"
                   :expand-node-keys="[1]"
                   :check-node-keys="checkNodeKeys"
                   :z-tree-check="zTreeCheck"
                   :z-tree-simple-data="zTreeSimpleData"
           >
               <div class="search-form" slot="form">
                   <el-input
                           suffix-icon="el-icon-search"
                           placeholder="请输入关键字"
                           v-model="keyword"
                           size="mini"
                           clearable
                           @keyup.enter.native="createFuzzySearch"
                           @clear="createFuzzySearch"
                   >
                   </el-input>
                   <el-button type="primary" size="mini" @click="createFuzzySearch">查询</el-button>
               </div>
           </r-z-tree>
       </el-col>
   </el-row>
</template>

<script>
    const treeData = [
        {
            "id": 1,
            "name": "根节点",
            "parentId": 0,
            "isSite": 0,
            "typeCode": null,
            "typeName": null,
            "sortNumber": 1,
            "children": [
                {
                    "id": 11,
                    "name": "二级节点1-1",
                    "parentId": 1,
                    "isSite": 0,
                    "typeCode": null,
                    "typeName": null,
                    "sortNumber": 1,
                    "children": [
                        {
                            "id": 111,
                            "name": "三级节点1-1-1",
                            "parentId": 1,
                            "isSite": 0,
                            "typeCode": null,
                            "typeName": null,
                            "sortNumber": 1
                        },
                        {
                            "id": 112,
                            "name": "三级节点1-1-2",
                            "parentId": 1,
                            "isSite": 0,
                            "typeCode": null,
                            "typeName": null,
                            "sortNumber": 1
                        }
                    ]
                },
                {
                    "id": 12,
                    "name": "二级节点1-2",
                    "parentId": 1,
                    "isSite": 0,
                    "typeCode": null,
                    "typeName": null,
                    "sortNumber": 1,
                    "children": [
                        {
                            "id": 121,
                            "name": "三级节点1-2-1",
                            "parentId": 12,
                            "isSite": 0,
                            "typeCode": null,
                            "typeName": null,
                            "sortNumber": 1
                        },
                        {
                            "id": 112,
                            "name": "三级节点1-2-2",
                            "parentId": 12,
                            "isSite": 0,
                            "typeCode": null,
                            "typeName": null,
                            "sortNumber": 1
                        }
                    ]
                },
                {
                    "id": 13,
                    "name": "二级节点1-3",
                    "parentId": 1,
                    "isSite": 0,
                    "typeCode": null,
                    "typeName": null,
                    "sortNumber": 1,
                    "children": [
                        {
                            "id": 131,
                            "name": "三级节点1-3-1",
                            "parentId": 13,
                            "isSite": 0,
                            "typeCode": null,
                            "typeName": null,
                            "sortNumber": 1
                        },
                        {
                            "id": 132,
                            "name": "三级节点1-3-2",
                            "parentId": 13,
                            "isSite": 0,
                            "typeCode": null,
                            "typeName": null,
                            "sortNumber": 1
                        }
                    ]
                },
                {
                    "id": 14,
                    "name": "二级节点1-4",
                    "parentId": 1,
                    "isSite": 0,
                    "typeCode": null,
                    "typeName": null,
                    "sortNumber": 1,
                    "children": [
                        {
                            "id": 141,
                            "name": "三级节点1-4-1",
                            "parentId": 14,
                            "isSite": 0,
                            "typeCode": null,
                            "typeName": null,
                            "sortNumber": 1
                        },
                        {
                            "id": 142,
                            "name": "三级节点1-4-2",
                            "parentId": 14,
                            "isSite": 0,
                            "typeCode": null,
                            "typeName": null,
                            "sortNumber": 1
                        }
                    ]
                }
            ]
        }
    ]
    export default {
        name: "base1",
        data() {
            return {
                zTreeNodes: treeData,
                checkNodeKeys: [31895, 31896, 31898, 31899],
                zTreeCheck: {
                    enable: true,
                },
                zTreeSimpleData: {
                    enable: true,
                },
                keyword: "",
            };
        },
        methods: {
            createFuzzySearch(){
               
            },
        },
    };
</script>

<style scoped>
    .search-form {
        display: flex;
    }
</style>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mf_717714

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值