项目中需要一个iview框架的树形控件,需要里面包含以下功能
- 1、控件宽度可展开,可缩小
- 2、树形控件可搜索,并且定位到搜索的节点
- 3、控件可以一键勾选,一键取消
- 4、控件图标自定义
- 5、 点击最后一个节点时可以进入到二级节点,点击上一节点可返回
- 完整代码:
- listToTree文件
效果图:
具体实现
1、控件宽度可展开,可缩小
这个比较简单,就是控制控件的宽度
// 放大expansion() {this.isZhankai = true;this.width = "422";this.$store.commit("compration/saveLeftWidth", this.isZhankai);},// 缩小packUp() {this.isZhankai = false;this.width = "230";this.$store.commit("compration/saveLeftWidth", this.isZhankai);},
2、树形控件可搜索,并且定位到搜索的节点
//输入节点名称后按回车健搜索inputChange() {let exist = false;if (this.isShowTwo) {// 二级树形控件搜索let data = JSON.parse(JSON.stringify(this.treeDataRowTwo));for (var i = 0; i < data.length; i++) {if (data[i].title.indexOf(this.value) !== -1) {//在树中存在exist = true;this.currentId = data[i].id;/*需要重新获取treeDataRowTwo,不然会出现这种情况:数据是对的,但是视图渲染不出来,导致没有搜索结果*/data = JSON.parse(JSON.stringify(this.treeDataRowTwo));this.treeDataTwo = listToTree(data);/*需要重新获取treeDataRowTwo,不然会出现这种情况:数据是对的,但是视图渲染不出来,导致没有搜索结果*/this.treeDataTwo = this.fineCurrentIdRecursive(this.treeDataTwo);break;} else if (!exist && i === data.length - 1) {//在树中不存在this.$Message.error("无搜索结果");}}} else {// 一级树形控件搜索for (var i = 0; i < this.treeRawData.length; i++) {if (this.treeRawData[i].title.indexOf(this.value) !== -1) {//在树中存在exist = true;this.currentId = this.treeRawData[i].id;/*需要重新获取treeRawData,不然会出现这种情况:数据是对的,但是视图渲染不出来,导致没有搜索结果*/this.treeRawData = JSON.parse(JSON.stringify(this.authTreeData));this.treeData = listToTree(this.treeRawData);/*需要重新获取treeRawData,不然会出现这种情况:数据是对的,但是视图渲染不出来,导致没有搜索结果*/this.treeData = this.fineCurrentIdRecursive(this.treeData);break;} else if (!exist && i === this.treeRawData.length - 1) {//在树中不存在this.$Message.error("无搜索结果");}}}},//通过节点id选中树中节点并展开它的父节点-递归方式fineCurrentIdRecursive(list) {for (var i = 0; i < list.length; i++) {if (list[i].id === this.currentId) {list[i].selected = true; //如果节点id等于currentId,则选中该节点break;} else {if (list[i].children && list[i].children.length > 0) {list[i].children = this.fineCurrentIdRecursive(list[i].children); //找不到想要的节点则继续找孩子的(递归)for (var j = 0; j < list[i].children.length; j++) {if (list[i].children[j].selected || list[i].children[j].expand) {list[i].expand = true; //如果子节点(末端节点)选中或者子节点(非末端节点)展开,则展开该子节点的父节点break;}}}}}return list;},
3、控件可以一键勾选,一键取消
// 全选按钮handleCheckAll(val) {this.indeterminate = false;if (val) {let arr = this.treeData;const addKey = arr =>arr.map(item => ({...item,checked: true,children: item.children ? addKey(item.children) : [] // 这里要判断原数据有没有子级如果没有判断会报错}));this.treeData = addKey(arr);} else {let arr = this.treeData;const addKey = arr =>arr.map(item => ({...item,checked: false,children: item.children ? addKey(item.children) : [] // 这里要判断原数据有没有子级如果没有判断会报错}));this.treeData = addKey(arr);}// 全选的idsif (this.checkAll) {this.ids = [];this.authTreeData.forEach(k => {this.ids.push(k.id);});this.removeDuplicate(this.ids);} else {this.ids = [];}this.$emit("changePower", this.ids);},// 树形控件勾选多选框checkChange(data) {// 半选if (data.length === this.authTreeData.length) {this.indeterminate = false;this.checkAll = true;} else if (data.length > 0) {this.indeterminate = true;this.checkAll = false;} else {this.indeterminate = false;this.checkAll = false;}this.ids = [];data.forEach(k => {this.ids.push(k.id);});this.removeDuplicate(this.ids);this.$emit("changePower", this.ids);},
4、控件图标自定义
// 自定义树结构图标renderContent(h, { root, node, data }) {return h("span", [h("Tooltip",{props: {placement: "top",transfer: true}},[h("span",{slot: "content",style: {whiteSpace: "normal"}},data.title),h("img", {attrs: {src:data.parentId === 0? this.yiji: data.children === undefined || data.children.length === 0? this.sanji: this.erji},style: {marginRight: "8px",width: "16px",height: "16px"}}),h("span",{style: {fontSize: "14px",width: this.width === "230" ? "89px" : "",overflow: this.width === "230" ? "hidden" : "inherit",textOverflow: this.width === "230" ? "ellipsis" : "inherit"}},data.title)])]);},renderContentTwo(h, { root, node, data }) {return h("span", [h("Tooltip",{props: {placement: "top",transfer: true}},[h("span",{slot: "content",style: {whiteSpace: "normal"}},data.title),h("img", {attrs: {src:data.parentId === this.powerId? this.one: data.children === undefined || data.children.length === 0? this.three: this.two},style: {marginRight: "8px",width: "16px",height: "16px",verticalAlign: "text-top"}}),h("span",{style: {fontSize: "14px",width: this.width === "230" ? "89px" : "",overflow: this.width === "230" ? "hidden" : "inherit",textOverflow: this.width === "230" ? "ellipsis" : "inherit"}},data.title)])]);},
5、 点击最后一个节点时可以进入到二级节点,点击上一节点可返回
// 点击节点展开收缩selectChange(data, selectedNode) {this.$set(selectedNode, "expand", !selectedNode.expand);// 二级树形的标题this.twoTitle = data[0].title;// 获取选择节点的idthis.powerId = data[0].id;this.$emit("domainId", this.powerId, this.twoTitle);if (this.isTwo) {if (data[0].children === undefined || data[0].children.length === 0) {this.isSearch = false;this.checkAll = false;this.value = "";this.isShowTwo = true;// 二级树形结构处理this.treeDataTwo = this.transitionToTree(JSON.parse(JSON.stringify(this.treeDataRowTwo)),1);} else {this.isShowTwo = false;}}},selectChangeTwo(data, selectedNode) {this.$set(selectedNode, "expand", !selectedNode.expand);},
完整代码:
父组件
<leftTree@changePower="changePower"@domainId="domainId":showCheckBox="true":isCheckAll="true":isTwo="true":isExpand="true"></leftTree>
子组件
<template><div class="leftTree" :style="'width:' + width + 'px'"><div class="powerList"><div class="initPower" v-if="!isSearch"><div v-if="isShowTwo" class="twoDianZhan"><div style="margin-right:5px; cursor: pointer;" @click="back"><</div><div>{{ twoTitle }}</div></div><div v-if="!isShowTwo" class="dianzhanliebiao">肖战影视作品</div><div style="display: flex;justify-content: center;align-items: center;"><Tooltip content="搜索"><imgclass="img"@click="search"src="../../assets/images/overview/sousuo.svg"alt=""/></Tooltip><Tooltip content="放大"><imgclass="img"@click="expansion"v-show="!isZhankai"src="../../assets/images/overview/zhankai.svg"alt=""/></Tooltip><Tooltip content="缩小"><imgclass="img"@click="packUp"v-show="isZhankai"src="../../assets/images/overview/shouqi.svg"alt=""/></Tooltip><Tooltip content="全选" v-if="isShowCheckAll"><Checkbox@on-change="handleCheckAll"v-model="checkAll":indeterminate="indeterminate"style="margin-left: 10px;margin-right: 0;margin-bottom: 0;"></Checkbox></Tooltip></div></div><div v-if="isSearch" class="searchPower"><Inputref="power"v-model="value"placeholder="请输入电站"style="width: 180px"@on-blur="inputBlur"@on-clear="inputClear"@on-enter="inputChange"/></div></div><div class="powerTree"><Treev-if="isShowTwo":data="treeDataTwo":show-checkbox="true":render="renderContentTwo"@on-select-change="selectChangeTwo"@on-check-change="checkChangeTwo"></Tree><Treev-if="!isShowTwo":data="treeData":show-checkbox="showCheck":render="renderContent"@on-select-change="selectChange"@on-check-change="checkChange"></Tree></div></div>
</template>
<script>
import { listToTree } from "./../../utils/tree";
export default {name: "leftTree",components: {},props: {// 是否显示树形多选框showCheckBox: {type: Boolean,default: true},// 是否全选isCheckAll: {type: Boolean,default: true},// 集控中心是否展开isExpand: {type: Boolean,default: false},// 是否显示二级树形控件isTwo: {type: Boolean,default: false}},data() {return {// 是否全选isShowCheckAll: this.isCheckAll,// 是否显示树形多选框showCheck: this.showCheckBox,// 全选半选indeterminate: false,// 全选绑定数据checkAll: false,// 树形控件原始数据authTreeData: [{id: 1,title: "肖战",parentId: 0},{id: 2,title: "现代剧",parentId: 1},{id: 3,title: "古装剧",parentId: 1},{id: 4,title: "《余生,请多指教》",parentId: 2},{id: 5,title: "《梦中那片海》",parentId: 2},{id: 6,title: "《王牌部队》",parentId: 2},{id: 7,title: "《超星星学园》",parentId: 2},{id: 8,title: "《陈情令》",parentId: 3},{id: 9,title: "《狼殿下》",parentId: 3},{id: 10,title: "《玉骨遥》",parentId: 3},{id: 11,title: "《哦,我的皇帝陛下》",parentId: 3},{id: 12,title: "《斗罗大陆》",parentId: 3}],// 电站搜索绑定数据value: "",// 展开缩小宽度width: "230",// 展开isZhankai: false,// 搜索isSearch: false,// 勾选的节点数据ids: [],currentId: "", //需要选中的节点idtreeData: [], //前端处理后的数据// 树形控件过渡数据treeRawData: [],// 树形控件自定义图标yiji: require("../../assets/images/overview/yijitubiao.svg"),erji: require("../../assets/images/overview/erjitubiao.svg"),sanji: require("../../assets/images/overview/sanjitubiao.svg"),// 树形控件自定义图标// 二级树形控件数据isShowTwo: false,twoTitle: "",// 二级树形控件数据treeDataTwo: [],// 二级树形控件处理数据treeDataRowTwo: [{id: 2,title: "音乐",parentId: 1},{id: 3,title: "广告",parentId: 1},{id: 4,title: "光点",parentId: 2},{id: 5,title: "最幸运的幸运",parentId: 2},{id: 6,title: "余年",parentId: 2},{id: 7,title: "满足",parentId: 2},{id: 8,title: "余生,请多指教",parentId: 2},{id: 9,title: "问少年",parentId: 2},{id: 10,title: "等等",parentId: 2},{id: 11,title: "TOD‘S",parentId: 3},{id: 12,title: "GUCCI",parentId: 3},{id: 13,title: "真力时",parentId: 3},{id: 14,title: "石头科技",parentId: 3},{id: 15,title: "倍轻松",parentId: 3},{id: 16,title: "开小灶",parentId: 3},{id: 17,title: "等等",parentId: 3}],one: require("../../assets/images/overview/one.svg"),two: require("../../assets/images/overview/two.svg"),three: require("../../assets/images/overview/three.svg"),powerId: ""// 二级树形控件数据};},created() {this.getData();},mounted() {this.$store.state.compration.isZhankai = this.isZhankai;},methods: {// 全选按钮handleCheckAll(val) {this.indeterminate = false;if (val) {let arr = this.treeData;const addKey = arr =>arr.map(item => ({...item,checked: true,children: item.children ? addKey(item.children) : [] // 这里要判断原数据有没有子级如果没有判断会报错}));this.treeData = addKey(arr);} else {let arr = this.treeData;const addKey = arr =>arr.map(item => ({...item,checked: false,children: item.children ? addKey(item.children) : [] // 这里要判断原数据有没有子级如果没有判断会报错}));this.treeData = addKey(arr);}// 全选的idsif (this.checkAll) {this.ids = [];this.authTreeData.forEach(k => {this.ids.push(k.id);});this.removeDuplicate(this.ids);} else {this.ids = [];}this.$emit("changePower", this.ids);},// tree树结构转换transitionToTree(data, rootValue) {// data是转换树形结构的数据源// rootValue是根节点的特征const parents = data.filter(item => item.parentId === rootValue); // 把所有顶级节点拆分出来const children = data.filter(item => item.parentId !== rootValue); // 把所有子级节点(无论多少级,二三四五六级..)拆分出来dataToTree(parents, children);function dataToTree(parents, children) {parents.map(p => {children.map((c, index) => {// 判断当前循环的子节点是否是当前循环父节点的子级if (c.parentId === p.id) {let newChildren = JSON.parse(JSON.stringify(children));newChildren.splice(index, 1);dataToTree([c], newChildren);// 如果是当前循环父节点的子级,那就看父节点有没有children属性,// 如果有就push到children属性的数组,// 如果没有就给父节点一个children属性并把当前循环的子级放进去if (p.children) {p.children.push(c);} else {p.children = [c];}}});});}return parents;},// 自定义树结构图标renderContent(h, { root, node, data }) {return h("span", [h("Tooltip",{props: {placement: "top",transfer: true}},[h("span",{slot: "content",style: {whiteSpace: "normal"}},data.title),h("img", {attrs: {src:data.parentId === 0? this.yiji: data.children === undefined || data.children.length === 0? this.sanji: this.erji},style: {marginRight: "8px",width: "16px",height: "16px"}}),h("span",{style: {fontSize: "14px",width: this.width === "230" ? "89px" : "",overflow: this.width === "230" ? "hidden" : "inherit",textOverflow: this.width === "230" ? "ellipsis" : "inherit"}},data.title)])]);},renderContentTwo(h, { root, node, data }) {return h("span", [h("Tooltip",{props: {placement: "top",transfer: true}},[h("span",{slot: "content",style: {whiteSpace: "normal"}},data.title),h("img", {attrs: {src:data.parentId === this.powerId? this.one: data.children === undefined || data.children.length === 0? this.three: this.two},style: {marginRight: "8px",width: "16px",height: "16px",verticalAlign: "text-top"}}),h("span",{style: {fontSize: "14px",width: this.width === "230" ? "89px" : "",overflow: this.width === "230" ? "hidden" : "inherit",textOverflow: this.width === "230" ? "ellipsis" : "inherit"}},data.title)])]);},back() {this.isShowTwo = false;},// 点击节点展开收缩selectChange(data, selectedNode) {this.$set(selectedNode, "expand", !selectedNode.expand);// 二级树形的标题this.twoTitle = data[0].title;// 获取选择节点的idthis.powerId = data[0].id;this.$emit("domainId", this.powerId, this.twoTitle);if (this.isTwo) {if (data[0].children === undefined || data[0].children.length === 0) {//this.isSearch = false;this.checkAll = false;this.value = "";this.isShowTwo = true;// 二级树形结构处理this.treeDataTwo = this.transitionToTree(JSON.parse(JSON.stringify(this.treeDataRowTwo)),1);} else {this.isShowTwo = false;}}},selectChangeTwo(data, selectedNode) {this.$set(selectedNode, "expand", !selectedNode.expand);},checkChangeTwo(data) {this.ids = [];data.forEach(k => {if (k.name) {this.ids.push({unit: k.unit,name: k.name,title: k.title});}});this.$emit("changePower", this.ids);},// 树形控件勾选多选框checkChange(data) {// 半选if (data.length === this.authTreeData.length) {this.indeterminate = false;this.checkAll = true;} else if (data.length > 0) {this.indeterminate = true;this.checkAll = false;} else {this.indeterminate = false;this.checkAll = false;}this.ids = [];data.forEach(k => {this.ids.push(k.id);});this.removeDuplicate(this.ids);this.$emit("changePower", this.ids);},//输入节点名称后按回车健搜索inputChange() {let exist = false;if (this.isShowTwo) {// 二级树形控件搜索let data = JSON.parse(JSON.stringify(this.treeDataRowTwo));for (var i = 0; i < data.length; i++) {if (data[i].title.indexOf(this.value) !== -1) {//在树中存在exist = true;this.currentId = data[i].id;/*需要重新获取treeDataRowTwo,不然会出现这种情况:数据是对的,但是视图渲染不出来,导致没有搜索结果*/data = JSON.parse(JSON.stringify(this.treeDataRowTwo));this.treeDataTwo = listToTree(data);/*需要重新获取treeDataRowTwo,不然会出现这种情况:数据是对的,但是视图渲染不出来,导致没有搜索结果*/this.treeDataTwo = this.fineCurrentIdRecursive(this.treeDataTwo);break;} else if (!exist && i === data.length - 1) {//在树中不存在this.$Message.error("无搜索结果");}}} else {// 一级树形控件搜索for (var i = 0; i < this.treeRawData.length; i++) {if (this.treeRawData[i].title.indexOf(this.value) !== -1) {//在树中存在exist = true;this.currentId = this.treeRawData[i].id;/*需要重新获取treeRawData,不然会出现这种情况:数据是对的,但是视图渲染不出来,导致没有搜索结果*/this.treeRawData = JSON.parse(JSON.stringify(this.authTreeData));this.treeData = listToTree(this.treeRawData);/*需要重新获取treeRawData,不然会出现这种情况:数据是对的,但是视图渲染不出来,导致没有搜索结果*/this.treeData = this.fineCurrentIdRecursive(this.treeData);break;} else if (!exist && i === this.treeRawData.length - 1) {//在树中不存在this.$Message.error("无搜索结果");}}}},//通过节点id选中树中节点并展开它的父节点-递归方式fineCurrentIdRecursive(list) {for (var i = 0; i < list.length; i++) {if (list[i].id === this.currentId) {list[i].selected = true; //如果节点id等于currentId,则选中该节点break;} else {if (list[i].children && list[i].children.length > 0) {list[i].children = this.fineCurrentIdRecursive(list[i].children); //找不到想要的节点则继续找孩子的(递归)for (var j = 0; j < list[i].children.length; j++) {if (list[i].children[j].selected || list[i].children[j].expand) {list[i].expand = true; //如果子节点(末端节点)选中或者子节点(非末端节点)展开,则展开该子节点的父节点break;}}}}}return list;},// 电站获取数据getData() {this.treeRawData = JSON.parse(JSON.stringify(this.authTreeData));this.treeData = this.transitionToTree(this.treeRawData, 0);if (this.isExpand) {this.treeData.forEach(k => {if (k.title === "肖战") {k.expand = true;}});}},// 去重removeDuplicate(arr) {let len = arr.length;for (let i = 0; i < len; i++) {for (let j = i + 1; j < len; j++) {if (arr[i] === arr[j]) {arr.splice(j, 1);len--; // 减少循环次数提高性能j--; // 保证j的值自加后不变}}}return arr;},// 放大expansion() {this.isZhankai = true;this.width = "422";this.$store.commit("compration/saveLeftWidth", this.isZhankai);},// 缩小packUp() {this.isZhankai = false;this.width = "230";this.$store.commit("compration/saveLeftWidth", this.isZhankai);},// 列表搜索search() {this.isSearch = true;this.$nextTick(() => {this.$refs.power.focus();});},// 失去焦点显示inputBlur() {if (this.value === "") {this.isSearch = false;}},// 清除搜索框inputClear() {this.isSearch = false;this.value = "";}}
};
</script>
<style lang="scss" scoped>
::v-deep .ivu-input {width: 100% !important;
}
.leftTree {height: 100%;.powerList {height: 52px;background-color: #fff;margin-bottom: 1px;padding: 0 16px;display: flex;align-items: center;.initPower {width: 100%;height: 100%;display: flex;align-items: center;justify-content: space-between;}.img {cursor: pointer;margin-left: 10px;}.twoDianZhan {display: flex;font-family: PingFangSC-Medium;font-size: 14px;color: #3d3d3d;}.dianzhanliebiao {font-family: PingFangSC-Medium;font-size: 14px;color: #3d3d3d;}}.powerTree {height: calc(100% - 53px);background-color: #fff;}
}
::v-deep .ivu-tooltip-rel {display: flex;align-items: center;
}
::v-deep .ivu-tree {height: 100%;overflow-y: auto;
}::v-deep .ivu-tree-children .ivu-checkbox-wrapper {position: absolute;right: 30px;top: 5px;
}
::v-deep .ivu-tree-children li {&::before {content: none !important;}&::after {content: none !important;}
}
::v-deep .ivu-tree .ivu-tree-children li .ivu-tree-arrow .ivu-icon {&::before {content: "\f11f" !important;border: none;top: 2px;transition: all 0.2s ease-in-out;-webkit-transition: all 0.2s ease-in-out;}
}
::v-deep .ivu-tree .ivu-tree-children li .ivu-tree-arrow-open .ivu-icon {&::before {-webkit-transform: rotate(90deg);transform: rotate(90deg);transition: all 0.2s ease-in-out;-webkit-transition: all 0.2s ease-in-out;}
}
</style>
listToTree文件
//将后端返回的list数据转化为树结构
export const listToTree = list => {var arr = [];let items = {};var idsStr = "";let array = [];// 获取每个节点的直属子节点(是直属,不是所有子节点)for (let i = 0; i < list.length; i++) {let key = list[i].parentId;if (items[key]) {items[key].push(list[i]);} else {items[key] = [];items[key].push(list[i]);}array.push(list[i].id);// idsStr += idsStr === "" ? list[i].id : "," + list[i].id;}for (var key in items) {if (array.indexOf(Number(key)) === -1) {//找到最大的父节点keyarr = formatTree(items, Number(key));}}delete arr[0].parentId;return arr;
};
function formatTree(items, parentId) {let result = [];if (!items[parentId]) {return result;}for (let t of items[parentId]) {t.children = formatTree(items, t.id); //递归获取childrenresult.push(t);}return result;
}