1、效果
1、代码
<template>
<Button type="primary" @click="modal = true">点击选择</Button>
<div v-if="selectedArr.length > 0">
<p>已选择项:</p>
<div v-for="(item, index) in selectedArr" :key="index">{{ item.title }}</div>
</div>
<Modal width="600" v-model="modal" title="请选择" @on-ok="ok" @on-cancel="cancel">
<div class="tr-modal">
<div class="org-tree">
<div class="title">
<span>组织机构</span>
<Input v-model="keyOrg" @on-change="onInput" placeholder="请输入"></Input>
</div>
<Tree
ref="tree"
class="tree-main"
@on-check-change="onCheckChange"
:data="data"
show-checkbox
check-directly
multiple
></Tree>
</div>
<div class="to-right">
<Icon type="ios-arrow-forward" size="30" color="#1890ff" />
</div>
<div class="select-con">
<div class="title">
<span>已选择项</span>
</div>
<div class="to-right">
<ul class="select-ul">
<li
v-for="(item, index) in selectedArr"
:key="index"
@mouseenter="item.ishover = true"
@mouseleave="item.ishover = false"
@click="onDelete(index, item)"
>
<span>{{ item.title }}</span>
<Icon v-if="item.ishover" type="md-close" size="20" color="#1890ff" />
</li>
</ul>
</div>
</div>
</div>
</Modal>
</template>
<script>
export default {
data() {
return {
modal: false,
selectedArr: [],
data: [
{
industryName: '农业、林业',
node: 1,
children: [
{
industryName: '农产品基地项目(含药材基地)',
children: [],
id: '01--001',
title: '01--001_农产品基地项目(含药材基地)',
industryState: '1',
parentId: '01',
industryCode: '01--001',
},
{
industryName: '经济林基地项目',
children: [],
id: '01--002',
title: '01--002_经济林基地项目',
industryState: '1',
parentId: '01',
industryCode: '01--002',
},
],
id: '01',
title: '01_农业、林业',
industryState: '1',
parentId: null,
industryCode: '01',
},
{
industryName: '畜牧业',
node: 1,
children: [
{
industryName: '牲畜饲养;家禽饲养;其他畜牧业',
children: [],
id: '02--003',
title: '02--003_牲畜饲养;家禽饲养;其他畜牧业',
industryState: '1',
parentId: '02',
industryCode: '02--003',
},
],
id: '02',
title: '02_畜牧业',
industryState: '1',
parentId: null,
industryCode: '02',
},
{
industryName: '渔业',
node: 1,
children: [
{
industryName: '海水养殖',
children: [],
id: '03--004',
title: '03--004_海水养殖',
industryState: '1',
parentId: '03',
industryCode: '03--004',
},
{
industryName: '内陆养殖',
children: [],
id: '03--005',
title: '03--005_内陆养殖',
industryState: '1',
parentId: '03',
industryCode: '03--005',
},
],
id: '03',
title: '03_渔业',
industryState: '1',
parentId: null,
industryCode: '03',
},
],
keyOrg: '',
dataCp: [],
}
},
methods: {
ok() {
this.$Message.info('Clicked ok')
},
cancel() {
this.$Message.info('Clicked cancel')
},
onInput() {
if (this.keyOrg) {
let flattenTree = this.flattenTree(this.dataCp)
let filterChildren = flattenTree.filter(
(el) => el.parentId && el.title.includes(this.keyOrg),
)
this.data = filterChildren
} else {
this.data = this.dataCp
}
},
onDelete(index, item) {
this.selectedArr.splice(index, 1)
// 先扁平化
let flattenTree = this.flattenTree(this.data)
// 先根据parentId找到当前项的父节点
let parent = flattenTree.find((el) => el.id == item.parentId)
if (parent) {
parent.checked = false
}
// 再取消勾选
let checkItem = flattenTree.find((el) => el.id == item.id)
checkItem.checked = false
// 再还原成树形结构
this.data = this.unflattenTree(flattenTree)
},
onCheckChange(prev, curr) {
// 选中
if (curr.checked) {
// 选中的节点没有子节点(选中一个)
if (curr.children.length === 0) {
curr.ishover = false
this.selectedArr.push(curr)
} else {
// 选中的节点有子节点(选中一个,然后勾选其子节点)
curr.children.forEach((el) => {
el.ishover = false
})
this.selectedArr.push(...curr.children)
}
} else {
// 选中的节点没有子节点(选中一个)
if (curr.children.length === 0) {
// 取消
let index = this.selectedArr.findIndex((item) => item.id == curr.id)
// 删除已选项
this.selectedArr.splice(index, 1)
} else {
curr.children.forEach((el) => {
// 取消
let index = this.selectedArr.findIndex((item) => item.id == el.id)
// 删除已选项
this.selectedArr.splice(index, 1)
})
}
}
},
// 扁平化树形数据
flattenTree(treeData) {
const result = []
function flatten(node) {
// 创建新节点对象(浅拷贝,避免修改原对象)
const newNode = { ...node }
// 如果有子节点,先递归处理子节点
if (Array.isArray(newNode.children) && newNode.children.length > 0) {
// 临时保存子节点引用
const children = newNode.children
// 移除children属性(根据需求可选)
delete newNode.children
// 将当前节点加入结果
result.push(newNode)
// 递归处理子节点
children.forEach((child) => flatten(child))
} else {
// 无子节点直接加入结果
result.push(newNode)
}
}
// 遍历所有根节点
treeData.forEach((root) => flatten(root))
return result
},
// 将扁平数据还原为树形结构
unflattenTree(flatData) {
// 创建ID映射字典和结果集
const nodeMap = {}
const roots = []
// 第一遍遍历:创建所有节点的映射
flatData.forEach((node) => {
nodeMap[node.id] = { ...node, children: [] }
})
// 第二遍遍历:构建树形结构
flatData.forEach((node) => {
const currentNode = nodeMap[node.id]
if (node.parentId) {
// 找到父节点并添加到其children
const parent = nodeMap[node.parentId]
if (parent) {
parent.children.push(currentNode)
}
} else {
// 根节点
roots.push(currentNode)
}
})
return roots
},
},
mounted() {
this.dataCp = JSON.parse(JSON.stringify(this.data))
},
}
</script>
<style lang="scss" scoped>
.tr-modal {
display: flex;
height: 400px;
.org-tree {
width: 260px;
height: 100%;
border: 1px solid #d9d9d9;
}
.to-right {
display: flex;
align-items: center;
margin: 0 5px;
cursor: pointer;
}
.select-con {
width: 260px;
border: 1px solid #d9d9d9;
}
.title {
height: 32px;
display: flex;
align-items: center;
border-bottom: 1px solid #d9d9d9;
span {
display: flex;
font-size: 14px;
color: #1890ff;
justify-content: center;
align-items: center;
width: 70px;
font-weight: 700;
}
:deep(.ivu-input-wrapper) {
flex: 1;
width: auto;
}
}
.tree-main {
height: calc(100% - 32px);
overflow-y: auto;
}
.select-ul {
width: 100%;
li {
padding: 0 5px;
height: 24px;
display: flex;
align-items: center;
font-size: 14px;
justify-content: space-between;
margin: 4px 0;
background-color: #eee;
span {
width: 90%;
overflow: hidden;
white-space: nowrap;
}
}
}
}
</style>