需求:有一个几万条数据量的树状结构的数据需要显示,但是数据量太大了的话,页面会卡死,所以想用el-tree的懒加载的功能。可能很多地方会用到就封装了一个。
后端需要提供的接口:1、获取所有根目录的信息数组。2、点击可以获取所有下一级菜单的目录。3、查找最后一级菜单的信息以及所有兄弟节点的信息,还有所有父级节点的信息,一起返回来,前端根据子父节点的绑定关系pid进行分类,最后组装成为一个真的树状的结构信息。
回显的操作:点击确定的时候,后端需要保存,最后一级的node的id,以及所有点击的父级菜单的id,会存储在jlflsz中,是一个数组。
1、点击输入框展示树状结构弹框
1.1、<el-input v-model="info.jlflstr" readonly placeholder="结论分类"></el-input>是为了展示选择的项目,jlflModel 是为了显示树状数据封装的组件。
1.2、jlflObj是为了传递用户选中的分类的id ,jlflszout传递的是用户保存的所有分类的id,包括自己的id和所有的父级id,保存的格式是用,分隔的字符串。监听了一个函数savejlfl,是为了保存子组件传递过来的数据,最后可以保存到数据库中去。
<!--父组件点击出现弹框-->
<template>
<div @click="openJlflDialog">
<el-input v-model="info.jlflstr" readonly placeholder="结论分类"></el-input>
</div>
<!-- 结论分类弹框 -->
<jlflModel :uid="info.id" ref="jlfl" :data="jlflObj" :jlflszout="info.jlflsz"
@savejlfl="savejlfl">
</jlflModel>
</template>
<!--methods-->
import jlflModel from "./xxx/jlflModel.vue";
export default {
components: {
jlflModel: jlflModel
},
methods: {
openJlflDialog () {
this.jlflObj = this.info.jlfl
// jlflObj是为了传递用户选中的分类的id jlflszout传递的是用户保存的所有分类的id,包括自己的id和所有的父级id,保存的格式是用,分隔的字符串。
this.$refs.jlfl.dialogVisible = true
}
}
}
2.封装jlflModel 组件。
其中最关键的就是loadNode函数了,每次点击下级菜单和回显数据都会用到这个方法,通过level来判断层级。
<template>
<div class="tx">
<el-dialog v-if="dialogVisible" width="350px" :title="title" :visible.sync="dialogVisible" :before-close="handleClose"
:close-on-click-modal="false" append-to-body>
<div class="flex" style="margin-bottom: 10px" @keyup.enter="getjlc">
<el-input v-model="jlckey" placeholder="请输入关键字" clearable style="width: 320px;" class="t10"></el-input>
<el-button type="gren" style="margin-left: 15px;" @click="getjlc">查询</el-button>
</div>
<div class="box">
<!-- 懒加载树状组件-->
<el-tree :expand-on-click-node="false" class="treebox" :highlight-current="true" ref="tree" node-key="id"
:data="treeData" :filter-node-method="getjlc" :props="props" :load="loadNode" lazy
@node-click="handleCheckChange">
</el-tree>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false; jlckey = ''">取 消</el-button>
<el-button type="primary" @click="confirm">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import "ztree";
import { Utils, User } from "@/utils/common";
import "ztree/css/metroStyle/metroStyle.css";
// import utils from "@/utils/utils";
/**
* 绑定用户与权限关系
* uid 用户ID必传
*/
export default {
data() {
return {
dialogVisible: false,
props: {
label: 'name',
children: 'sonGathering',
isLeaf: 'leaf'
},
selectCheck: {},
jlckey: "",
reshowArr: [],
fatherId: "", // 父级id
fatherArr: [], // 父级id数组
idArrs: [], // 所有点击过的节点的id
jlflsz: [], //结论分类选择数组
jlflIndex: '',
isReshow: false, // 是否是回显操作
treeData: [],
expandedNodes: [], // 已经展开过的节点(拿到过下级的节点)
// value: [], // 已选节点
openList: [], // 后端返回的展开节点数据(需要处理成key数组)
choliceList: [], // 后端返回的勾选节点数据(需要处理成key数组)
isDoubleShow: false, // 是否是第一次选择项目信息
parentFlsz: [],
};
},
// model: {
// prop: "value",
// event: "change",
// },
props: {
jlflCheckStr: {
type: String,
default: ''
},
title: {
type: String,
default: "结论分类",
},
// 用户ID 或 角色ID
uid: {
require: true,
type: String,
default: "",
},
data: {
type: String,
default: ""
},
jlflszout: {
type: String,
default: ''
}
},
watch: {
jlflszout(newVal) {
console.log("%%", newVal)
if (newVal) {
this.jlflsz = newVal.split(",")
this.parentFlsz = newVal.split(",")
}
},
expandArr: {
handler(newV) {
if (newV) {
this.$nextTick(() => {
this.openList = newV.split(',')
})
}
},
immediate: true,
},
selectArr: {
handler(newV) {
if (newV) {
this.$nextTick(() => {
this.choliceList = newV.split(',')
})
}
},
immediate: true,
},
// 弹框打开 判断是回显还是新增操作
dialogVisible(res) {
let idsParam = Utils.toData(this.ids)
if (this.jlflszout) {
this.jlflsz = this.jlflszout.split(",")
} else {
this.jlflsz = []
}
if (res) {
if (this.jlflsz.length > 0) {
// 回显操作
this.isReshow = true
this.reshow(this.jlflsz)
} else {
this.isReshow = false
// 查询根项目的数据
this.getJlfl()
}
}
},
data(res) {
let ids = res.split(",")
this.idArrs = ids
}
},
computed: {
ids() {
return this.data
}
},
methods: {
// 查询所有一级目录
async getJlfl() {
const result = await Utils.request({
url: "zdxmApi2",
query: "/selectJLFL",
show: false
}).then(res => {
console.log("结论分类的信息", res)
this.treeData = res
// setTimeout(() => {
this.$nextTick(() => {
// 创建一个新的 Promise 对象,并将其 resolve 方法保存在变量中
let resolveFunc;
const loadPromise = new Promise(resolve => {
resolveFunc = resolve;
});
if (this.$refs.tree) {
this.$refs.tree.load(res, resolveFunc);
}
})
// }, 200)
})
console.log(result)
},
loadNode(node, resolve, i) {
var index = i
// 存储选择的路径
// if (this.jlflsz[this.jlflsz.length - 1]) {
// console.log("4", this.jlflsz[this.jlflsz.length - 1].level)
// let level = this.jlflsz[this.jlflsz.length - 1].level
// console.log("4", node.level)
// if (level) {
// if (this.jlflsz[this.jlflsz.length - 1].level == node.level) {
// this.jlflsz.pop()
// } else {
// this.jlflsz.push(node.data.id)
// }
// }
// }
if (node.level == 1 && !this.isReshow) {
this.isDoubleShow = true
this.jlflsz = []
node.data.level = node.level
this.jlflsz.push(node.data.id)
console.log("jlflsz333", this.jlflsz)
}
else if (node.level == 1 && this.isReshow) {
// parentFlsz
this.jlflsz = this.jlflsz.filter(item => {
this.parentFlsz.forEach(it => {
return item.id != it
})
})
this.jlflsz.push(node.data.id)
}
// else {
// }
if (node.level) {
if (node.level >= 1) {
Utils.request({
url: "zdxmApi2",
query: "/querySubset",
data: {
pid: node.data.id
},
show: false
}).then(res => {
if (!this.jlflsz.includes(node.data.id)) {
this.jlflsz.push(node.data.id)
}
const result = res.filter(item => {
if (this.jlflsz.includes(item.id)) {
return item
}
}) if (result.length > 0) {
var findIndex = this.jlflsz.findIndex(item => item == result[0].id)
console.log("findIndex", findIndex);
this.$nextTick(() => {
// 创建一个新的 Promise 对象,并将其 resolve 方法保存在变量中
let resolveFunc;
const loadPromise = new Promise(resolve => {
resolveFunc = resolve;
});
this.$refs.tree.load(res, resolveFunc, findIndex);
})
}
// this.addArr(res, node.data)
resolve(res)
})
}
} else {
console.log("ddd", this.treeData)
// resolve(this.treeData)
if (this.treeData) {
// console.log('1', tree)
// console.log('treedata', tree.getNode(this.jlflsz[0]))
// 手动触发点击事件
// console.log(this.data.split(","))
var _this = this
if (this.jlflsz) {
setTimeout(() => {
this.$nextTick(() => {
(function (index) {
const tree = _this.$refs.tree
console.log('index::', index)
if (_this.jlflszout) {
const parentNode = tree.getNode(_this.jlflszout.split(",")[index]); // 获取父节点
console.log('parentdata', parentNode)
if (parentNode && parentNode.level >= 1) {
Utils.request({
url: "zdxmApi2",
query: "/querySubset",
data: {
pid: parentNode.data.id
},
show: false
}).then(res => {
console.log("p", res)
const result = res.filter(item => {
console.log("p1", item)
console.log("p2", _this.jlflszout.split(","))
if (_this.jlflszout.split(",").includes(item.id)) {
return item
}
})
console.log("resttt", result)
if (result.length > 0) {
var findIndex = _this.jlflszout.split(",").findIndex(item => item == result[0].id)
console.log("findIndex", findIndex);
_this.$nextTick(() => {
// 创建一个新的 Promise 对象,并将其 resolve 方法保存在变量中
let resolveFunc;
const loadPromise = new Promise(resolve => {
resolveFunc = resolve;
});
_this.$refs.tree.load(res, resolveFunc, findIndex);
})
}
// this.addArr(res, node.data)
resolve(res)
})
}
if (parentNode) {
const tree = _this.$refs.tree
tree.setCurrentKey(parentNode.data.id)
parentNode.expand()
}
}
})(index)
})
}, 200)
}
}
}
},
reshow(param) {
console.log("param", param)
// 获取当前节点的所有兄弟节点
Utils.request({
url: "zdxmApi2",
query: "/selectEcho",
data: {
id: param[0].trim()
},
show: false
}).then(async res => {
console.log("回显数据", res)
const reshow = await this.filterRes(res)
console.log("reshow", reshow)
this.treeData = reshow
var _this = this
for (let i = 0; i < this.jlflsz.length; i++) {
(function (index) {
_this.$nextTick(() => {
_this.jlflIndex = index
// 创建一个新的 Promise 对象,并将其 resolve 方法保存在变量中
let resolveFunc;
const loadPromise = new Promise(resolve => {
resolveFunc = resolve;
});
_this.$refs.tree.load(reshow, resolveFunc, index);
})
})(i)
}
})
},
filterRes(res) {
return new Promise((resolve) => {
// console.log("filterres", res)
const data = Utils.toData(res)
this.reshowArr = []
const obj = {};
data.forEach(item => {
// 将每个对象加入到 obj 中,以对象的 id 作为键名
obj[item.id] = { ...item }
});
// console.log("dasssta", obj)
// 遍历数据数组, 将是子集的数据添加到对应的对象下面
for (let index = 0; index < data.length; index++) {
const element = data[index];
// 判断当前对象的 pid 是否存在于 obj
if (obj[data[index].pid]) {
// 将当前对象加入到对应的父节点的 sonGathering 数组中
obj[data[index].pid].sonGathering.push(obj[data[index].id]);
// 保存一份父级id
// console.log("obj[data[index].id].pid", data[index].id)
this.fatherId = data[index].pid
this.fatherArr = obj[data[index].id]
delete obj[data[index].id]
}
}
// console.log(obj[this.fatherId])
// 递归寻找树的某一个对象
// this.traverse(obj[this.fatherId].sonGathering, obj)
obj[this.fatherId].sonGathering.forEach((item, index) => {
// console.log("&", item)
for (var k in obj) {
if (item.id == obj[k].pid) {
// console.log(item)
// console.log(obj)
obj[this.fatherId].sonGathering[index].sonGathering.push(obj[k])
// this.fatherId = item.idy
delete obj[k]
}
}
})
data.forEach(item => {
// 将每个对象加入到 obj 中,以对象的 id 作为键名
item.children = Utils.toData(item.sonGathering)
});
// 提取 ID 对象中的值数组
const result = Object.values(obj);
console.log("result", result);
this.reshowArr = result
resolve(result)
})
},
traverse(arr, paramsObj) {
const obj = paramsObj
arr.forEach((item, index) => {
// console.log("&", item)
for (var k in obj) {
if (item.id == obj[k].pid) {
console.log(item)
console.log(obj)
obj[this.fatherId].sonGathering[index].sonGathering.push(obj[k])
// this.fatherId = item.idy
delete obj[k]
}
}
if (item.sonGathering && item.sonGathering.length > 0) {
this.traverse(item.sonGathering)
}
})
},
handleCheckChange(data) {
console.log("点击node", data)
this.selectCheck = data
if (data.id) {
if (!this.jlflsz.includes(data.id)) {
this.jlflsz.push(data.id)
}
}
console.log("5", this.jlflsz)
},
getjlc() {
Utils.request({
url: "zdxmApi2",
query: "/queryKeyword",
data: {
keyword: this.jlckey.trim()
},
show: false
}).then(res => {
console.log("查询接口", res)
this.treeData = res
this.$refs.tree.load(res);
})
},
handleClose() {
this.jlckey = ""
this.dialogVisible = false;
},
confirm() {
if (!this.selectCheck.name) {
this.selectCheck.name = this.jlflCheckStr
}
var arr = []
this.jlflsz.forEach(item => {
if (item.id) {
arr.push(item.id)
} else {
arr.push(item)
}
})
this.$emit("savejlfl", this.selectCheck, arr[arr.length - 1], arr)
this.dialogVisible = false
}
}
}
</script>
<style lang="scss" scoped>
.tx {
background: #eeeeee;
.imgbox {
overflow: hidden;
}
}
.el-icon-plus {
font-size: 25px;
}
.box {
height: 300px;
overflow: auto;
}
::v-deep.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content div {
background-color: #fe9236;
padding: 5px;
color: #fff;
}
.selected-node {
padding: 5px;
background-color: #fe9236;
color: #fff;
}
.treebox ::v-deep.el-tree-node__content:hover {
background-color: #fe9236 !important;
color: #fff;
}
.treebox ::v-deep .el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content {
background-color: #fe9236 !important;
color: #fff;
}
::v-deep.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content {
background-color: #fe9236 !important;
color: #fff;
}
::v-deep .is-expanded>.el-tree-node__content {
background-color: #fe9236;
/* 设置背景色 */
color: #fff;
}
.el-tree-node__content:hover div {
padding: 5px;
color: #fff;
background-color: #fe9236;
}
</style>
2.1 根据自己的数据结构修改props
props: {
label: 'name',
children: 'sonGathering',
isLeaf: 'leaf'
},
2.2 传入的数据解释
:uid="info.id" ref="jlfl" :data="jlflObj" :jlflszout="info.jlflsz"
uid:为了避免多个树状结构会重复,添加了一个id
ref:绑定结构,类似于id
data:选中的项目的id
jlflszout:所有选中的节点的id,用,分割
2.3 PS:这个组件有一个问题,回显的时候,如果是查询到内容有时候会回显不出来,因为他可能不在一级目录中,有时间再进行修改了,你们也可以自行修改。