vue2实现一个树型控件(支持展开树与checkbox勾选)
TreeItem.vue
<template>
<div class="tree-item">
<span @click="toggleExpanded" class="icon" v-show="treeNode && treeNode.children && treeNode.children.length">
<span
class="triangle"
:class="[ expanded ? 'triangle_down' : 'triangle_up']"
></span>
</span>
<span class="icon-font icon-kaiwenjianjia-shense icon-wenjianjia"></span>
<span @click="toggleExpanded">{
{
treeNode.deptName }}</span>
<input class="check-item check-style" type="checkbox" v-model="treeNode.checked" @change="handleChange(treeNode)">
<div class="children" v-show="expanded">
<TreeItem v-for="childNode in treeNode.children" :key="childNode.id" :tree-node="childNode" @checkItem="handleChange"></TreeItem>
</div>
</div>
</template>
<script>
export default {
name: 'TreeItem',
props: {
treeNode: {
type: Object,
required: true
}
},
data() {
return {
expanded: false,
};
},
methods: {
toggleExpanded() {
this.expanded = !this.expanded;
},
handleChange(item) {
console.log('handleChange',item, "treeNode",this.treeNode);
this.setChecked(item,item.checked);
this.$emit('checkItem',item)
},
setChecked(node, checked) {
node.checked = checked;
if (node.children && node.children.length > 0) {
for (let child of node.children) {
this.setChecked(child, checked);
}
}
}
}
};
</script>
<style lang="less" scoped>
.tree-item {
position: relative;
font-size: 14px;
.check-item {
position: absolute;
top: 10px;
right: 4px;
z-index: 111;
cursor: pointer;
}
}
.icon {
width: 16px;
display: inline-block;
margin-right: 4px;
line-height: 20px;
cursor: pointer;
}
.icon-wenjianjia {
color: #ccc;
margin-right: 6px;
}
.children {
margin-left: 20px;
}
input[type="checkbox"] {
appearance: none;
border: 1px solid transparent;
width: 14px;
height: 14px;
display: inline-block;
position: relative;
vertical-align: middle;
cursor: pointer;
background-color: #eee;
}
input[type="checkbox"]:checked {
background-color: #1bc5bd;
}
input[type="checkbox"]:checked:after {
content: "✔";
position: absolute;
left: 1px;
top: -11px;
font-size: 12px;
color: #fff;
}
.triangle {
position: relative;
top: -4px;
transition: 0.5s;
}
.triangle_up {
display: inline-block;
margin: 0px;
width: 0px;
height: 0px;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 4px solid #ccc;
}
.triangle_down {
display: inline-block;
margin: 0px;
width: 0px;
height: 0px;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid #ccc;
}
</style>
Tree.vue
<template>
<div class="select-tree-com">
<TreeItem
class="tree-item" v-for="treeNode in treeData" :key="treeNode.id" :tree-node="treeNode"
@checkItem="checkItem"
></TreeItem>
</div>
</template>
<script>
import TreeItem from "./TreeItem"
export default {
name:'SelectTreeCom',
components:{
TreeItem
},
props: {
lists: {
type: Array,
default () {
return []
}
},
checkbox: {
type: Boolean,
default: false
},
},
data() {
return {
treeData: [
{
id: 1,
name: 'Node 1',
deptCode:1,
deptName:'Node-1',
checked: false,
children: [
{
id: 11,
deptCode:11,
deptName:'Node-11',
parentId: 1,
name: 'Node 11',
checked: false,
children: [
{
id: 111,
deptName:'Node-111',
deptCode:111,
parentId: 11,
name: 'Node 111',
checked: false,
children: [
{
id: 1111,
deptName:'Node-1111',
deptCode:1111,
parentId: 111,
name: 'Node 1111',
checked: false,
children: []
},
{
id: 1112,
deptName:'Node-1112',
deptCode:1112,
parentId: 111,
name: 'Node 1112',
checked: false,
children: []
}
]
},
{
id: 112,
deptName:'Node-112',
deptCode:112,
parentId: 11,
name: 'Node 112',
checked: false,
children: []
}
]
},
{
id: 12,
deptName:'Node-12',
deptCode:12,
parentId: 1,
name: 'Node 12',
checked: false,
children: []
},
{
id: 13,
deptName:'Node-13',
deptCode:13,
parentId: 1,
name: 'Node 13',
checked: false,
children: [
{
id: 131,
deptName:'Node-131',
deptCode:131,
parentId: 13,
name: 'Node 131',
checked: false,
children: [
{
id: 1311,
deptName:'Node-1311',
deptCode:1311,
parentId: 131,
name: 'Node 1311',
checked: false,
children: []
},
{
id: 1312,
deptName:'Node-1312',
deptCode:1312,
parentId: 131,
name: 'Node 1312',
checked: false,
children: []
}
]
},
{
id: 132,
deptName:'Node-132',
deptCode:132,
parentId: 13,
name: 'Node 132',
checked: false,
children: []
}
]
},
]
},
{
id:2,
deptName:'Node-2',
deptCode:2,
name: 'Node 2',
checked: false,
children: []
}
],
checkList:[],
};
},
watch:{
lists:{
handler(newV){
console.log('selectTreeeCom组件lists',newV);
},
}
},
created() {
},
methods: {
checkItem(item) {
let newArr = []
newArr = this.flattenNodes(item)
console.log('newArr',newArr);
newArr && newArr.length && newArr.forEach(item => {
if ( item.checked ) {
this.checkList.push(item)
}
});
console.log('存储选中的-this.checkList',this.checkList);
this.checkList && this.checkList.length && this.checkList.forEach(itemB =>{
newArr.some(itemA => {
if ( itemA.id === itemB.id ) {
itemB.checked = itemA.checked
}
})
})
console.log('处理this.checkList',this.checkList);
this.checkList = this.checkList.filter(item=>{
if(item.checked) {
return item;
}
})
let uniqueArr = []
uniqueArr = Array.from(new Set(this.checkList.map(item => item.id))).map(id => this.checkList.find(item => item.id === id));
console.log('uniqueArr',uniqueArr);
this.$emit('getCheckList', uniqueArr)
},
flattenNodes(data) {
let nodes = [];
nodes.push({
id: data.id,
name: data.name,
checked: data.checked,
deptCode: data.deptCode,
deptName: data.deptName
});
if (data.children && data.children.length > 0) {
for (let child of data.children) {
nodes = nodes.concat(this.flattenNodes(child));
}
}
return nodes;
},
setCheckAll(params){
const allTreeData = this.treeToOneArr(this.treeData)
if ( params ) {
this.checkList = [...allTreeData]
return this.checkList
} else {
this.checkList = []
return this.checkList
}
},
cancelCheckAll(){
this.checkList = []
},
treeToOneArr(arr) {
const data = JSON.parse(JSON.stringify(arr))
const newData = []
const hasChildren = item => {
(item.children || (item.children = [])).map(v => {
hasChildren(v)
})
delete item.children
newData.push(item)
}
data.map(v => hasChildren(v))
return newData
},
oneArrToTree(data) {
const cloneData = JSON.parse(JSON.stringify(data))
const result = cloneData.filter(parent => {
const branchArr = cloneData.filter(child => parent.parentCode === child.parentCode)
if (branchArr.length > 0) {
branchArr.sort(this.compare('order'))
parent.children = branchArr
}
return parent.parentCode === '00'
})
result.sort(this.compare('order'))
return result
},
compare(property) {
return function(a, b) {
const value1 = a[property]
const value2 = b[property]
return value1 - value2
}
},
}
};
</script>
<style lang="less" scoped>
.select-tree-com {
padding: 10px 0;
}
.tree-item {
line-height: 34px;
}
</style>
效果