组件代码:
<template>
<div class="left-tree-wrap">
<el-input
v-if="props.showSearch"
class="left-tree-input"
v-model="searchData"
:placeholder="props.searchPlaceholder"
:prefix-icon="Search"
clearable
@input="debouncedInput"
/>
<el-button v-if="props.showAddBtn" class="left-tree-btn" type="primary" :icon="Plus" @click="handleClick">
{{ props.addBtnText }}
</el-button>
<div class="tree-scroll">
<el-tree
ref="treeRef"
:data="filteredTreeData"
:props="props.treeProps"
:check-strictly="props.checkStrictly"
:nodeKey="props.treeProps.nodeKey"
:show-checkbox="props.showCheckbox"
@check="handleCheck"
@node-click="handleNodeClick"
highlight-current
:default-checked-keys="props.selectedNodes"
:current-node-key="currentNodeKey"
:default-expanded-keys="expandedList"
@node-expand="handleNodeExpand"
@node-collapse="handleNodeCollapse"
>
<template #default="{ node, data }">
<div class="tree-wrap">
<span class="text" :style="`width: ${labelWidth}px`">
{{ node.label }}
<slot name="label" :node="data"></slot>
</span>
<el-dropdown v-if="props.operateList && props.operateList.length > 0" @command="e => handleCommand(node, e)">
<span class="icon">
<el-icon v-if="data.noShowIcon !== true">
<More />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="(item, index) in props.operateList" :key="index" :command="item.type">
{{ item.name }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
</el-tree>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, watch, computed, getCurrentInstance } from 'vue';
import { Search, Plus } from '@element-plus/icons-vue';
import { useRouter } from 'vue-router';
import { debounce } from 'lodash';
import i18n from '@/locale/index';
defineOptions({
name: 'jl-left-tree'
});
const { appContext } = getCurrentInstance();
const { $utils, $request, $message, $rules } = appContext.config.globalProperties;
const emit = defineEmits(['checkCb', 'addCb', 'searchCb', 'treeOperateCb', 'clickTree']);
const props = defineProps({
type: {
type: String,
default: 'primary'
},
operateList: {
type: Array,
default: () => []
},
showSearch: {
type: Boolean,
default: false
},
searchPlaceholder: {
type: String,
default: i18n.global.t('请输入')
},
addBtnText: {
type: String,
default: i18n.global.t('新增')
},
showAddBtn: {
type: Boolean,
default: false
},
treeProps: {
type: Object,
default: () => {
let obj = {
label: 'label',
children: 'children',
nodeKey: 'id'
};
return obj;
}
},
treeData: {
type: Array,
default: () => []
},
showCheckbox: {
type: Boolean,
default: false
},
selectTreeData: {
type: Object,
default: () => {}
},
// 默认展开
defaultExpanded: {
type: Array,
default: () => []
},
// 树label宽度
labelWidth: {
type: [String, Number],
default: 120
},
selectedNodes: {
type: Array,
default: () => []
},
checkStrictly: {
type: Boolean,
default: false
},
isFilter: {
type: Boolean,
default: false
}
});
let currentNodeKey = ref(); // 选中节点
let treeRef = ref();
let searchData = ref('');
let originalTreeData = ref([]); // 保存原始树数据
// 创建深拷贝函数
const deepClone = (data) => {
return JSON.parse(JSON.stringify(data));
};
let expandedList = ref([]);
// 过滤树数据
const filteredTreeData = computed(() => {
if (!props.isFilter || !searchData.value) {
return originalTreeData.value;
}
const filter = searchData.value.toLowerCase();
const propsLabel = props.treeProps.label;
const filterData = (data) => {
return data.filter(item => {
const label = item[propsLabel]?.toString().toLowerCase() || '';
// 检查当前节点是否匹配
const isMatch = label.includes(filter);
// 递归过滤子节点
if (item.children && item.children.length > 0) {
const filteredChildren = filterData(item.children);
if (filteredChildren.length > 0) {
// 如果有匹配的子节点,保留当前节点及其子节点
return {
...item,
children: filteredChildren
};
}
}
// 如果没有子节点或子节点不匹配,但当前节点匹配,则保留
return isMatch ? item : null;
}).filter(Boolean); // 过滤掉null值
};
return filterData(deepClone(originalTreeData.value));
});
// 监听原始树数据变化
watch(
() => props.treeData,
(newValue) => {
originalTreeData.value = deepClone(newValue);
},
{ immediate: true, deep: true }
);
watch(
() => props.selectTreeData,
(newValue) => {
if (newValue) {
currentNodeKey.value = newValue[props.treeProps.nodeKey];
}
},
{ immediate: true, deep: true }
);
/**
* 树选中
*/
const handleCheck = (data, e) => {
emit('checkCb', { data, e });
};
/**
* 新增按钮
*/
const handleClick = () => {
emit('addCb');
};
/**
* 搜索
*/
const handleInputChange = (e) => {
if (props.isFilter) {
// 前端过滤,无需额外处理
} else {
emit('searchCb', e);
}
};
// 创建防抖函数
const debouncedInput = debounce(handleInputChange, 500);
/**
* 树更多菜单
*/
const handleCommand = (item, type) => {
emit('treeOperateCb', { item: item.data, type });
};
/**
* 点击树节点
*/
const handleNodeClick = (data, treeNode, treeNode1, treeNode2) => {
emit('clickTree', data);
};
/**
* 展开树节点
*/
const handleNodeExpand = (data, node, e) => {
expandedList.value.push(data[props.treeProps.nodeKey]);
};
/**
* 收起树节点
*/
const handleNodeCollapse = (data, node, e) => {
let index = expandedList.value.findIndex(item => item === data[props.treeProps.nodeKey]);
expandedList.value.splice(index, 1);
};
onMounted(() => {
expandedList.value = props.defaultExpanded;
});
</script>
<style scoped lang="scss">
.left-tree-wrap {
padding: 12px;
display: flex;
flex-flow: column nowrap;
height: 100%;
.left-tree-input {
margin-bottom: 12px;
}
.left-tree-btn {
width: 100%;
margin-bottom: 12px;
}
.tree-scroll {
:deep(.el-tree-node__content) {
padding-top: 20px !important;
padding-bottom: 20px !important;
padding-right: 8px;
}
overflow-y: auto;
flex-shrink: 2;
.tree-wrap {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
.text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 120px;
}
}
.icon {
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
width: 20px;
height: 20px;
&:hover {
background: #c8c9cc;
}
}
}
}
</style>
页面使用:
<jl-left-tree1
:treeData="treeData"
showSearch
:labelWidth="230"
showCheckbox
:isFilter="true"
@checkCb="checkCb"
:searchPlaceholder="t('请输入名称')"
:treeProps="{
label: 'itemName',
children: 'deviceList',
nodeKey: 'id'
}"
@clickTree="clickTree"
@searchCb="searchCb"
:selectTreeData="selectTreeData"
:selectedNodes="selectedNodes"
></jl-left-tree1>
// 数选择
const checkCb = item => {
console.log('item', item)
selectedNodes.value = item.e.checkedKeys;
isChange.value = true;
};
/**
* 树点击
*/
const clickTree = data => {
selectTreeData.value = data;
};
// 树搜索
const searchCb = e => {
devPointScreen(e, true);
};
let selectTreeData = ref({}); // 选中树的数据
let selectedNodes = ref([]); // 被选中的节点数组
帮我综合分析并解决问题:当我在输入框输入文字利用isFilter=true进行过滤后,将搜索出来的节点进行勾选时会将搜索前已经勾选的节点取消勾选,同理,当我操作搜索结果节点取消勾选时也会将搜索前已勾选的取消勾选,解决此问题,输出为完整组件代码以及页面代码,保证我要的效果(当我搜索后勾选时就只操作当前操作的节点--对应搜索前的所有节点中,当我取消勾选时也只操作取消勾选的节点)
最新发布