注:本方法是根据官方提供的模糊搜索功能fuzzySearch函数进行稍微修改,添加到自己的封装Tree中
话不多说,直接上效果图
引入zTree以及使用方式请看第一篇文章 vue2.x + zTree,简单的二次封装
目录层级
目录层级中的文件可以在第一篇文章中找到。
实现的功能。
- 配置zTree 的节点数据 data,属性zTreeNodes。
- 配置zTree的setting属性,拆分优化,分为3个优先级
- 增加默认展开的节点的 key 的数组, 属性checkNodeKeys
- 增加默认展开的节点的 key 的数组, 属性expandNodeKeys
- 增加每个树节点用来作为唯一标识的属性, 属性zTreeNodeKey
- 整理获取节点的事件,以及返回值
- 增加模糊搜索功能
主要代码:
-
在index.vue引入fuzzysearch.js,用来模糊搜索。
-
使用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>