数通互联化工云平台
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

896 lines
25 KiB

<template>
<!-- {{ props.data?.control.fixedOptions }} --><!-- {{transferConfig}} --><!-- {{ resData }} --><!-- {{ count1 }} -->
<!-- <hr>
<hr>
<hr> -->
<div v-if="dataFinished" class="transfer"><!-- {{props.selectedValue}} -->
<div class="leftArea"><!-- {{ selectedValueCompu }}{{checkedIdList}} -->
<div class="transferName">{{ transferConfig.transferName }}</div>
<div style="padding-left: 15px; padding-right: 25px;margin-top: 10px;margin-bottom: 8px;">
<ElInput v-model="keyword" placeholder="搜索">
<template #prefix>
<ElIcon class="el-input__icon">
<Search />
</ElIcon>
</template>
</ElInput>
</div>
<div class="leftScroll">
<ElScrollbar height="100%">
<ElTree ref="treeRef" node-key="id" empty-text="暂无数据" :data="userList1" :props="treeProps"
show-checkbox highlight-current :default-expand-all="isExpandAll"
:filter-node-method="filterNode" @check="handleCheckList" @check-change="getCheckedList" />
</ElScrollbar>
</div>
</div>
<div class="rigthArea">
<div style="margin-bottom: 8px;">
<div class="transferName"><span>已选: {{ checkList.length }}</span>
<ElButton link style="float: right;margin-right: 5px;" @click="clearCheckList">清空</ElButton>
</div>
</div>
<ElScrollbar height="calc(100% - 50px)">
<ElTag v-for="user in checkList" :key="user.id"
style="display: block; font-size: 13px; letter-spacing: 1.5px;text-align: center;margin-top: 8px;height: 30px;width: 75%;margin-left:40px;padding-top: 6px;"
closable @close="handleCloseTag(user)">
{{
user.label
}}[{{
user.parent.data.label
}}]
</ElTag>
</ElScrollbar>
</div>
</div>
</template>
<script setup name="MultiTreeSelector">
import { ref, watch, onMounted } from 'vue'
import request from '@/utils/request';
import { forEach } from 'jszip';
const props = defineProps({
// eslint-disable-next-line vue/require-default-prop
data: {
type: Object,
default() {
return {};
},
},
selectedValue: {
type: Array,
default() {
return [];
},
},
})
const emits = defineEmits(['checkedIdListChanged', 'updateModel','reRenderComponent']);
/* const fixedOptions = ref([]) */
/* fixedOptions.value = props.data?.control.fixedOptions */
//const transferConfig = props.data?.config
const transferConfig = computed({
set(){
},
get(){
return props.data?.config
}
})
const treeProps = {
value: 'id',
label: 'label',
disabled: 'disabled',
children: 'children'
}
let dataFinished = ref(false)
const treeRef = ref()
const isExpandAll = ref(transferConfig.value.transferDataSource != "数据源") // 是否全展开
const keyword = ref('') // 搜索关键字
const checkList = ref([]) // 选中的list
const userList = computed({
get(){
if (transferConfig.value.transferDataSource === "数据源") {
return [{
id: '全选',
label: '全选',
children: [...resData.value]
}]
}else{
return [{
id: '全选',
label: '全选',
children: props.data?.control.fixedOptions
}]
}
},
set(){
}
})
let userList1 = ref([]);
//
let checkedIdList = ref([]);
let count = 0
function waitAndReGet() {
/* console.log("waitAndReGet")
console.log(props.selectedValue)
console.log(props.selectedValue.value) */
setTimeout(() => {
let values = props.selectedValue.map(item => {
// 假设这里需要提取每个元素的特定值,可以根据实际情况进行调整
return item;
});
if (props.selectedValue && values) {
/* console.log("有值")
console.log(props.selectedValue)
console.log(values) */
//return props.selectedValue.value
checkedIdList.value = values
} else {
if (count < 3) {
count++
waitAndReGet()
}
/* console.log("waitAndReGet---else") */
}
}, 2000)
}
/*
*/
const selectedValueCompu = computed({
get() {
//console.log("get")
//nextTick(()=>{
let values = props.selectedValue.map(item => {
// 假设这里需要提取每个元素的特定值,可以根据实际情况进行调整
return item;
});
if (props.selectedValue && values) {
/* console.log(props.selectedValue.value)
console.log("computed---if") */
return values
} else {
waitAndReGet()
/* console.log("computed---else") */
return []
}
//})
},
set() { }
})
/* console.log(props.selectedValue) */
const url = transferConfig.value.apiUrl;/* '/javasys/lowCode/transfer/getOrgAndManTree' */
let resData = ref([])
function endsWithGetOrgAndManTree(str) {
// 检查输入是否为字符串
if (typeof str !== 'string') {
return false;
}
const suffix = 'getOrgAndManTree';
// 当字符串长度小于后缀长度时,直接返回false
if (str.length < suffix.length) {
return false;
}
// 获取字符串末尾与后缀长度相同的子字符串并比较
return str.slice(-suffix.length) === suffix;
}
function processTreeData(node) {
if(!endsWithGetOrgAndManTree(url)){
return node
}
// 检查节点及其所有子孙节点中是否存在id长度大于4的节点
function hasLongIdNode(node) {
// 检查当前节点id长度
if (node.id && node.id.length > 4) {
return true;
}
// 处理子节点(考虑children为null的情况)
if (node.children && Array.isArray(node.children) && node.children.length > 0) {
for (const child of node.children) {
if (hasLongIdNode(child)) {
return true;
}
}
}
return false;
}
// 递归处理节点
function processNode(node) {
// 如果当前节点及其所有子孙都没有长id,则移除该节点
if (!hasLongIdNode(node)) {
return null;
}
// 复制节点避免修改原对象
const newNode = {...node};
// 处理子节点
if (newNode.children && Array.isArray(newNode.children) && newNode.children.length > 0) {
// 递归处理每个子节点并过滤掉需要移除的
newNode.children = newNode.children
.map(child => processNode(child))
.filter(child => child !== null);
// 如果所有子节点都被移除,保持children为null
if (newNode.children.length === 0) {
newNode.children = null;
}
}
return newNode;
}
return processNode(node);
}
function getDetail() {
//console.log(11111)
if (transferConfig.value.transferDataSource === "数据源") {
return request({
url: url,
method: transferConfig.value.method,
});
}
}
if (transferConfig.value.transferDataSource === "数据源") {
getDetail().then(({ data }) => {
checkedIdList.value = props.selectedValue
const data1 = processTreeData(data)
//console.log(`获取穿梭框接口数据`);
resData.value = data1.children
// 全选方法2:插入 全选node
userList.value = [{
id: '全选',
label: '全选',
children: [...resData.value]
}]
userList1.value = userList.value
if( treeRef.value){
treeRef.value.setCheckedKeys(checkedIdList.value, true)
}
setTimeout(() => {
checkedIdList.value.forEach(element => {
const _node = treeRef?.value?.getNode(element)
if (!_node) return
const _checkList = checkList.value
if (_node.checked) { // 勾选时
if (_node.checked && _node.childNodes.length === 0) {
_checkList.push(_node)
}
// 通过new Set() 实现数据去重
checkList.value = Array.from(new Set(_checkList))
// 遍历子级,递归,直到获取最后一级的人员
if (_node.childNodes.length > 0) {
_node.childNodes.map(item => handleCheckList(item.data))
}
} else if (!_node.checked) { // 取消勾选
checkList.value = checkList.value.filter(item => item.checked)
}
});
}, 50);
});
}
const getCheckedList = () => {
checkedIdList.value = treeRef.value.getCheckedKeys(true)
}
let resData1 = ref([
{
id: '3',
label: '营销部',
children: [
{
id: '3-1',
label: '威震天'
},
{
id: '3-2',
label: '嫦娥'
}
]
},
{
id: '2',
label: '业务部',
children: [
{
id: '2-1-1',
label: '业务部子部1',
children: [
{
id: '2-1-0001',
label: '李白'
},
{
id: '2-1-0002',
label: '萧峰'
}
]
},
{
id: '2-2-1',
label: '业务部子部2',
children: [
{
id: '2-2-00006',
label: '武则天'
},
{
id: '2-2-00005',
label: '拿破仑'
}
]
}
]
},
{
id: '4',
label: '软件开发部',
children: [
{
id: '5-3',
label: '张辽'
},
{
id: '5-9',
label: '吕布'
},
{
id: '5-0',
label: '许褚'
},
]
}
])
// 勾选或者取消勾选
const handleCheckList = (val) => {
//console.log(val)
const valId = val.id;
//console.log(valId)
const _node = treeRef?.value?.getNode(valId)
//console.log(_node)
if (!_node) return
const _checkList = checkList.value
//console.log(checkedIdList.value)
if (_node.checked||_node.childNodes.length > 0) {
if (_node.checked && _node.childNodes.length === 0) {
let count = 0
checkedIdList.value.forEach(element1 => {
if(element1==valId){
count++
}
});
if(count==1){
_checkList.push(_node)
}
}
//console.log(_checkList)
/* checkList.value.forEach(element => {
console.log(element.data.id)
}); */
//使用 Map 对象实现真正的按 id 去重
checkList.value = Array.from(new Map(
_checkList.map(item => [item.data.id, item])
).values());
// 遍历子级,递归,直到获取最后一级的人员
if (_node.childNodes.length > 0) {
_node.childNodes.map(item => handleCheckList(item.data))
}
} else if (!_node.checked) { // 取消勾选
checkList.value = checkList.value.filter(item => item.checked)
}
}
/* 人员树 父级过滤的递归
* @node 当前节点
* @result 存结果的数组
* @key
* */
const getParentNode = (node, result, key) => {
let isPass = node?.data?.label?.indexOf(key) !== -1
isPass ? result.push(isPass) : ''
if (!isPass && node.level !== 1 && node.parent) {
return getParentNode(node.parent, result, key)
}
}
/*人员树过滤
* @value 过滤的关键字
* @data 被过滤的tree
* @node
* */
const filterNode = (value, data, node) => {
if (!value) {
data.disabled = false
return true
} else {
//带过滤条件时,所有父级都禁止勾选
// array.map(k => (!k.disabled) && (_childArr.push(k)))
(data.children) && (data.disabled = true)
}
let _arr = []
getParentNode(node, _arr, value)
let result = false
_arr.forEach(item => {
result = result || item
})
return result
}
// 通过tag 取消勾选
const handleCloseTag = (tag) => {
checkedIdList.value = checkedIdList.value.filter(item => item !== tag.data.id);
if (!treeRef?.value) return
checkList.value = checkList.value.filter(item => {
treeRef.value.setChecked(tag, false)
if (!item?.data) return
return item.data.id !== tag.data.id
})
}
// 清空CheckList
const clearCheckList = () => {
if (!treeRef?.value) return
treeRef.value.setCheckedKeys([])
while (checkList.value.length > 0) {
checkList.value.pop()
}
}
// 全选 /全不选
const handleCheckAll = (array) => {
treeRef.value.setCheckedNodes(array)
array.map(item => {
handleCheckList(item)
})
}
// 折叠/展开
const expandAll = () => {
const nodesMap = treeRef.value.store.nodesMap
for (let key in nodesMap) {
nodesMap[key].expanded = isExpandAll.value
}
}
let watchCount = 0
// 执行tree数据过滤
watch(keyword, (newVal) => {
//console.log(fuzzyTreeSearch(userList.value,newVal))
userList1.value = fuzzyTreeSearch(userList.value,newVal)
//console.log(userList1.value)
//treeRef.value.data = fuzzyTreeSearch(treeRefValueData,newVal)
watchCount++
isShowMore()
})
function isShowMore() {
isExpandAll.value = true
let nodes = treeRef.value.store._getAllNodes();
nodes.forEach(item => {
item.expanded = true;
});
}
function fuzzyTreeSearch(treeData, keyword) {
if (!keyword?.trim()) return deepClone(treeData); // 空关键词返回完整树
const processNode = (node) => {
const newNode = deepClone(node); // 深拷贝当前节点
const isSelfMatch = newNode.label.toLowerCase().includes(keyword.toLowerCase());
// 处理子节点(关键修复点)
let hasValidChild = false;
if (newNode.children?.length) {
newNode.children = newNode.children
.map(child => processNode(child))
.filter(Boolean); // 过滤无效子节点
hasValidChild = newNode.children.length > 0;
}
// 保留条件:自身匹配 或 子节点有匹配(修复子节点丢失问题)
if (isSelfMatch || hasValidChild) {
// 自身匹配时保留完整子树(即使子节点未匹配)
if (isSelfMatch && newNode.children?.length === 0) {
newNode.children = deepClone(node.children); // 直接复制原子节点
}
return newNode;
}
return null;
};
return treeData.map(root => processNode(root)).filter(Boolean);
}
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
const clone = Array.isArray(obj) ? [] : {};
for (const key in obj) {
clone[key] = deepClone(obj[key]);
}
return clone;
}
//获取配置接口相应或获取固定选项值
if (transferConfig.value.transferDataSource === "固定选项") {
setTimeout(() => {
// 全选方法2:插入 全选node
userList.value = [{
id: '全选',
label: '全选',
children: props.data?.control.fixedOptions
}]
userList1.value = userList.value
//console.log(treeRef)
/* console.log(checkedIdList.value) */
if( treeRef.value){
treeRef.value.setCheckedKeys(checkedIdList.value, true)
}
setTimeout(() => {
checkedIdList.value.forEach(element => {
const _node = treeRef?.value?.getNode(element)
if (!_node) return
const _checkList = checkList.value
if (_node.checked) { // 勾选时
if (_node.checked && _node.childNodes.length === 0) {
_checkList.push(_node)
}
// 通过new Set() 实现数据去重
checkList.value = Array.from(new Set(_checkList))
// 遍历子级,递归,直到获取最后一级的人员
if (_node.childNodes.length > 0) {
_node.childNodes.map(item => handleCheckList(item.data))
}
} else if (!_node.checked) { // 取消勾选
checkList.value = checkList.value.filter(item => item.checked)
}
});
}, 50);
}, 50);
dataFinished.value = true
} else {
getDetail
dataFinished.value = true
}
/* setTimeout(()=>{
console.log("setTimeout")
if(props.data?.config.transferDataSource==="数据源"){
//emits('reRenderComponent');
count1.value++
}
},1500) */
function arrayEquals(arr1, arr2) {
// 首先检查是否都是数组
if (!Array.isArray(arr1) || !Array.isArray(arr2)) {
return false;
}
// 检查数组长度是否相同
if (arr1.length !== arr2.length) {
return false;
}
// 逐个比较元素
for (let i = 0; i < arr1.length; i++) {
const item1 = arr1[i];
const item2 = arr2[i];
// 如果两个元素都是数组,则递归比较
if (Array.isArray(item1) && Array.isArray(item2)) {
if (!arrayEquals(item1, item2)) {
return false;
}
}
// 如果两个元素都是对象,则比较它们的属性
else if (typeof item1 === 'object' && item1 !== null &&
typeof item2 === 'object' && item2 !== null) {
// 简单对象比较(不考虑原型链)
const keys1 = Object.keys(item1);
const keys2 = Object.keys(item2);
if (keys1.length !== keys2.length) return false;
for (const key of keys1) {
if (!keys2.includes(key) || !arrayEquals([item1[key]], [item2[key]])) {
return false;
}
}
}
// 其他类型直接比较
else {
if (item1 !== item2) {
return false;
}
}
}
return true;
}
// 监听 checkedIdList 的变化
watch(checkedIdList, (newValue, oldValue) => {
/* console.log(oldValue)
console.log('checkedIdList 发生了变化', newValue); */
if(!arrayEquals(newValue,oldValue)){
//console.log(1)
emits('checkedIdListChanged', newValue);
}else{
//console.log(2)
}
//emits('checkedIdListChanged', newValue);
}, { deep: true });
let count1 = ref(0);
/* onBeforeMount(() => {
console.log('onBeforeMount: 组件即将挂载');
});
onMounted(() => {
console.log('onMounted: 组件已挂载');
});
onBeforeUpdate(() => {
console.log('onBeforeUpdate: 数据即将更新到 DOM');
});
onUpdated(() => {
console.log('onUpdated: DOM 已更新');
if(props.data?.config.transferDataSource==="数据源"){
console.log("emits('reRenderComponent');")
emits('reRenderComponent');
}
});
onBeforeUnmount(() => {
console.log('onBeforeUnmount: 组件即将卸载,清除定时器');
});
onUnmounted(() => {
console.log('onUnmounted: 组件已卸载');
}); */
watch(transferConfig, (newValue, oldValue) => {
count1.value++
//emits('reRenderComponent', newValue);
try {
count1.value++
if (newValue.transferDataSource === "数据源") {
emits('reRenderComponent');
getDetail().then(({ data }) => {
alert(2)
const data1 = processTreeData(data)
//console.log(`获取穿梭框接口数据`);
resData.value = data1.children
// 全选方法2:插入 全选node
userList.value = [{
id: '全选',
label: '全选',
children: [...resData.value]
}]
userList1.value = userList.value
if( treeRef.value){
treeRef.value.setCheckedKeys(checkedIdList.value, true)
}
setTimeout(() => {
checkedIdList.value.forEach(element => {
const _node = treeRef?.value?.getNode(element)
if (!_node) return
const _checkList = checkList.value
if (_node.checked) { // 勾选时
if (_node.checked && _node.childNodes.length === 0) {
_checkList.push(_node)
}
// 通过new Set() 实现数据去重
checkList.value = Array.from(new Set(_checkList))
// 遍历子级,递归,直到获取最后一级的人员
if (_node.childNodes.length > 0) {
_node.childNodes.map(item => handleCheckList(item.data))
}
} else if (!_node.checked) { // 取消勾选
checkList.value = checkList.value.filter(item => item.checked)
}
});
}, 50);
});
}
} catch (error) {
console.error('Error :', error);
// 可以在这里进行一些错误处理,比如设置默认值或其他操作
//parsedData.value = null;
}
}, { deep: true });
watch(selectedValueCompu, (newValue, oldValue) => {
//console.log('selectedValueCompu 发生了变化', newValue);
checkedIdList.value = selectedValueCompu.value
//获取配置接口相应或获取固定选项值
if (transferConfig.value.transferDataSource === "固定选项") {
setTimeout(() => {
// 全选方法2:插入 全选node
userList.value = [{
id: '全选',
label: '全选',
children: props.data?.control.fixedOptions
}]
userList1.value = userList.value
//console.log(treeRef)
/* console.log(checkedIdList.value) */
if( treeRef.value){
treeRef.value.setCheckedKeys(checkedIdList.value, true)
}
setTimeout(() => {
checkedIdList.value.forEach(element => {
const _node = treeRef?.value?.getNode(element)
if (!_node) return
const _checkList = checkList.value
if (_node.checked) { // 勾选时
if (_node.checked && _node.childNodes.length === 0) {
_checkList.push(_node)
}
// 通过new Set() 实现数据去重
checkList.value = Array.from(new Set(_checkList))
// 遍历子级,递归,直到获取最后一级的人员
if (_node.childNodes.length > 0) {
_node.childNodes.map(item => handleCheckList(item.data))
}
} else if (!_node.checked) { // 取消勾选
checkList.value = checkList.value.filter(item => item.checked)
}
});
}, 50);
}, 50);
dataFinished.value = true
} else {
getDetail
dataFinished.value = true
}
//emits('updateModel',newValue);
}, { deep: true });
</script>
<style scoped>
.transfer {
display: flex;
}
.transferName {
height: 30px;
padding-top: 8px;
background-color: #F5F7FA;
padding-left: 10px;
border-radius: 5px 5px 0 0;
border-bottom: 1px solid gainsboro;
}
.buttonArea {
display: flex;
flex-direction: row;
align-items: center;
}
.buttonArea2 {
height: 33px;
}
.leftScroll {
height: 367px;
}
.leftArea {
border: 1px solid gainsboro;
width: 300px;
border-radius: 5px;
height: 456px;
position: relative;
background-color: white;
}
.rigthArea {
border: 1px solid gainsboro;
width: 300px;
border-radius: 5px;
height: 456px;
background-color: white;
}
</style>