需求:可以搜索出符合关键字的全部节点,搜索出的节点可以继续懒加载。
实现方式:后端把符合关键字的节点组装成树结构返回给前端,前端使用packages/tree/src/model/node.js中Node类的doCreateChildren方法手动渲染树结构,并设置节点的expanded和loaded属性。
效果展示
懒加载初始化
逐级加载
按关键字搜索
搜索到的节点可以继续展开
完整代码
html原文件
代码片
关键代码是134行的renderNodes方法
代码片中只保留了北京市、天津市、河北省的数据,完整的省市县数据在附件中
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- import CSS -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<title>el-tree dynamic load</title>
</head>
<body>
<div id="app">
<el-input
v-model="filterText"
style="width: 20%;"
clearable
placeholder="输入关键字搜索"
@keyup.enter.native="search">
</el-input>
<el-button icon="el-icon-search" @click="search"></el-button>
<el-tree
:key="treeKey"
:props="props"
lazy
:load="loadNode">
</el-tree>
</div>
</body>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
// 我发现必须指定props,否则不能识别 isLeaf
props: {
label: 'label',
children: 'children',
isLeaf: 'isLeaf'
},
treeKey: new Date().getTime(),
filterText: null,
// 代码片中只保留了北京市、天津市、河北省的数据,完整数据在附件中
treeData:[{'label':'北京市','children':[{'label':'市辖区','children':[{'label':'东城区','id':3},{'label':'西城区','id':4},{'label':'朝阳区','id':5},{'label':'丰台区','id':6},{'label':'石景山区','id':7},{'label':'海淀区','id':8},{'label':'门头沟区','id':9},{'label':'房山区','id':10},{'label':'通州区','id':11},{'label':'顺义区','id':12},{'label':'昌平区','id':13},{'label':'大兴区','id':14},{'label':'怀柔区','id':15},{'label':'平谷区','id':16},{'label':'密云区','id':17},{'label':'延庆区','id':18}],'id':2}],'id':1},{'label':'天津市','children':[{'label':'市辖区','children':[{'label':'和平区','id':21},{'label':'河东区','id':22},{'label':'河西区','id':23},{'label':'南开区','id':24},{'label':'河北区','id':25},{'label':'红桥区','id':26},{'label':'东丽区','id':27},{'label':'西青区','id':28},{'label':'津南区','id':29},{'label':'北辰区','id':30},{'label':'武清区','id':31},{'label':'宝坻区','id':32},{'label':'滨海新区','id':33},{'label':'宁河区','id':34},{'label':'静海区','id':35},{'label':'蓟州区','id':36}],'id':20}],'id':19},{'label':'河北省','children':[{'label':'石家庄市','children':[{'label':'市辖区','id':39},{'label':'长安区','id':40},{'label':'桥西区','id':41},{'label':'新华区','id':42},{'label':'井陉矿区','id':43},{'label':'裕华区','id':44},{'label':'藁城区','id':45},{'label':'鹿泉区','id':46},{'label':'栾城区','id':47},{'label':'井陉县','id':48},{'label':'正定县','id':49},{'label':'行唐县','id':50},{'label':'灵寿县','id':51},{'label':'高邑县','id':52},{'label':'深泽县','id':53},{'label':'赞皇县','id':54},{'label':'无极县','id':55},{'label':'平山县','id':56},{'label':'元氏县','id':57},{'label':'赵县','id':58},{'label':'晋州市','id':59},{'label':'新乐市','id':60}],'id':38},{'label':'唐山市','children':[{'label':'市辖区','id':62},{'label':'路南区','id':63},{'label':'路北区','id':64},{'label':'古冶区','id':65},{'label':'开平区','id':66},{'label':'丰南区','id':67},{'label':'丰润区','id':68},{'label':'曹妃甸区','id':69},{'label':'滦县','id':70},{'label':'滦南县','id':71},{'label':'乐亭县','id':72},{'label':'迁西县','id':73},{'label':'玉田县','id':74},{'label':'遵化市','id':75},{'label':'迁安市','id':76}],'id':61},{'label':'秦皇岛市','children':[{'label':'市辖区','id':78},{'label':'海港区','id':79},{'label':'山海关区','id':80},{'label':'北戴河区','id':81},{'label':'抚宁区','id':82},{'label':'青龙满族自治县','id':83},{'label':'昌黎县','id':84},{'label':'卢龙县','id':85}],'id':77},{'label':'邯郸市','children':[{'label':'市辖区','id':87},{'label':'邯山区','id':88},{'label':'丛台区','id':89},{'label':'复兴区','id':90},{'label':'峰峰矿区','id':91},{'label':'邯郸县','id':92},{'label':'临漳县','id':93},{'label':'成安县','id':94},{'label':'大名县','id':95},{'label':'涉县','id':96},{'label':'磁县','id':97},{'label':'肥乡县','id':98},{'label':'永年县','id':99},{'label':'邱县','id':100},{'label':'鸡泽县','id':101},{'label':'广平县','id':102},{'label':'馆陶县','id':103},{'label':'魏县','id':104},{'label':'曲周县','id':105},{'label':'武安市','id':106}],'id':86},{'label':'邢台市','children':[{'label':'市辖区','id':108},{'label':'桥东区','id':109},{'label':'桥西区','id':110},{'label':'邢台县','id':111},{'label':'临城县','id':112},{'label':'内丘县','id':113},{'label':'柏乡县','id':114},{'label':'隆尧县','id':115},{'label':'任县','id':116},{'label':'南和县','id':117},{'label':'宁晋县','id':118},{'label':'巨鹿县','id':119},{'label':'新河县','id':120},{'label':'广宗县','id':121},{'label':'平乡县','id':122},{'label':'威县','id':123},{'label':'清河县','id':124},{'label':'临西县','id':125},{'label':'南宫市','id':126},{'label':'沙河市','id':127}],'id':107},{'label':'保定市','children':[{'label':'市辖区','id':129},{'label':'竞秀区','id':130},{'label':'莲池区','id':131},{'label':'满城区','id':132},{'label':'清苑区','id':133},{'label':'徐水区','id':134},{'label':'涞水县','id':135},{'label':'阜平县','id':136},{'label':'定兴县','id':137},{'label':'唐县','id':138},{'label':'高阳县','id':139},{'label':'容城县','id':140},{'label':'涞源县','id':141},{'label':'望都县','id':142},{'label':'安新县','id':143},{'label':'易县','id':144},{'label':'曲阳县','id':145},{'label':'蠡县','id':146},{'label':'顺平县','id':147},{'label':'博野县','id':148},{'label':'雄县','id':149},{'label':'涿州市','id':150},{'label':'安国市','id':151},{'label':'高碑店市','id':152}],'id':128},{'label':'张家口市','children':[{'label':'市辖区','id':154},{'label':'桥东区','id':155},{'label':'桥西区','id':156},{'label':'宣化区','id':157},{'label':'下花园区','id':158},{'label':'万全区','id':159},{'label':'崇礼区','id':160},{'label':'张北县','id':161},{'label':'康保县','id':162},{'label':'沽源县','id':163},{'label':'尚义县','id':164},{'label':'蔚县','id':165},{'label':'阳原县','id':166},{'label':'怀安县','id':167},{'label':'怀来县','id':168},{'label':'涿鹿县','id':169},{'label':'赤城县','id':170}],'id':153},{'label':'承德市','children':[{'label':'市辖区','id':172},{'label':'双桥区','id':173},{'label':'双滦区','id':174},{'label':'鹰手营子矿区','id':175},{'label':'承德县','id':176},{'label':'兴隆县','id':177},{'label':'平泉县','id':178},{'label':'滦平县','id':179},{'label':'隆化县','id':180},{'label':'丰宁满族自治县','id':181},{'label':'宽城满族自治县','id':182},{'label':'围场满族蒙古族自治县','id':183}],'id':171},{'label':'沧州市','children':[{'label':'市辖区','id':185},{'label':'新华区','id':186},{'label':'运河区','id':187},{'label':'沧县','id':188},{'label':'青县','id':189},{'label':'东光县','id':190},{'label':'海兴县','id':191},{'label':'盐山县','id':192},{'label':'肃宁县','id':193},{'label':'南皮县','id':194},{'label':'吴桥县','id':195},{'label':'献县','id':196},{'label':'孟村回族自治县','id':197},{'label':'泊头市','id':198},{'label':'任丘市','id':199},{'label':'黄骅市','id':200},{'label':'河间市','id':201}],'id':184},{'label':'廊坊市','children':[{'label':'市辖区','id':203},{'label':'安次区','id':204},{'label':'广阳区','id':205},{'label':'固安县','id':206},{'label':'永清县','id':207},{'label':'香河县','id':208},{'label':'大城县','id':209},{'label':'文安县','id':210},{'label':'大厂回族自治县','id':211},{'label':'霸州市','id':212},{'label':'三河市','id':213}],'id':202},{'label':'衡水市','children':[{'label':'市辖区','id':215},{'label':'桃城区','id':216},{'label':'冀州区','id':217},{'label':'枣强县','id':218},{'label':'武邑县','id':219},{'label':'武强县','id':220},{'label':'饶阳县','id':221},{'label':'安平县','id':222},{'label':'故城县','id':223},{'label':'景县','id':224},{'label':'阜城县','id':225},{'label':'深州市','id':226}],'id':214},{'label':'省直辖县级行政区划','children':[{'label':'定州市','id':228},{'label':'辛集市','id':229}],'id':227}],'id':37}],
treeMap: new Map()
}
},
watch: {
filterText(val) {
if (val === '') {
this.search()
}
}
},
created() {
this.init(this.treeData)
},
methods: {
init(array) {
for (const node of array) {
this.treeMap.set(node.id, node)
if (node.children && node.children.length > 0) {
node.isLeaf = false
this.init(node.children)
} else {
node.isLeaf = true
}
}
},
search() {
// 刷新key,重新加载组件
this.treeKey = new Date().getTime()
},
loadNode(node, resolve) {
if (node.level > 0) {
// 加载子节点
resolve(this.treeMap.get(node.data.id).children)
} else {
if (this.filterText) {
new Promise((resolve) => {
// 根据关键字搜索,一定要用Promise,先重新加载组件,再渲染数据
const searchResult = this.findNodeTree(this.treeData)
resolve(searchResult)
}).then((data) => {
this.renderNodes(node, data)
})
} else {
// 展示全国省份
const totalCity = this.treeData.map((n) => {
return this.treeMap.get(n.id)
})
resolve(totalCity)
}
}
},
findNodeTree(array) {
const searchResult = []
for (let node of array) {
// 复制一份node,不影响源数据
node = {
id: node.id,
label: node.label,
children: node.children,
isLeaf: node.isLeaf
}
// 先查找符合关键字的子节点
let newChildren = null
if (!node.isLeaf) {
newChildren = this.findNodeTree(node.children)
if (newChildren) {
// 如果有符合关键字的子节点,就把符合关键字的子节点作为children
node.children = newChildren
searchResult.push(node)
}
}
// 子节点都不符合,但当前节点符合关键字,就把当前节点纳入搜索结果中
if (!newChildren && node.label.indexOf(this.filterText) >= 0) {
node.children = null
searchResult.push(node)
}
}
return searchResult.length > 0 ? searchResult : null
},
renderNodes(parent, array) {
// 手动渲染节点,把array渲染成parent的子节点
if (array && array.length > 0) {
parent.doCreateChildren(array)
parent.expanded = true
parent.loaded = true
for (let i = 0; i < parent.childNodes.length; i++) {
this.renderNodes(parent.childNodes[i], array[i].children)
}
}
}
}
})
</script>
</html>