|
|
|
@ -10,6 +10,7 @@ const props = withDefaults( |
|
|
|
modelValue?: string |
|
|
|
disabled?: boolean |
|
|
|
orgAndManTree?: any; |
|
|
|
data?: any; |
|
|
|
}>(), |
|
|
|
{} |
|
|
|
) |
|
|
|
@ -19,239 +20,152 @@ const emits = defineEmits<{ |
|
|
|
(e: 'update:modelValue', value: string): void |
|
|
|
}>() |
|
|
|
|
|
|
|
const value = ref([]) |
|
|
|
watch(value,(newValue)=>{ |
|
|
|
if(newValue.length > 0){ |
|
|
|
let str = "" |
|
|
|
|
|
|
|
let userAry = new Array |
|
|
|
|
|
|
|
newValue.forEach(item =>{ |
|
|
|
//console.log(item) |
|
|
|
userAry.push(item) |
|
|
|
|
|
|
|
}) |
|
|
|
str = userAry.join(',') |
|
|
|
//console.log(str) |
|
|
|
emits('update:modelValue', str) |
|
|
|
// userlist.value = userAry.join(',') |
|
|
|
// |
|
|
|
}else{ |
|
|
|
let str = "" |
|
|
|
//console.log(str) |
|
|
|
emits('update:modelValue', str) |
|
|
|
} |
|
|
|
},{ deep: true }) |
|
|
|
|
|
|
|
const value = ref([]) |
|
|
|
const treeData = ref([]) // 存储懒加载的数据 |
|
|
|
const isDataLoaded = ref(false) // 标记数据是否已加载 |
|
|
|
|
|
|
|
watch(value, (newValue) => { |
|
|
|
if (newValue.length > 0) { |
|
|
|
let str = "" |
|
|
|
let userAry = new Array |
|
|
|
|
|
|
|
newValue.forEach(item => { |
|
|
|
userAry.push(item) |
|
|
|
}) |
|
|
|
str = userAry.join(',') |
|
|
|
emits('update:modelValue', str) |
|
|
|
} else { |
|
|
|
let str = "" |
|
|
|
emits('update:modelValue', str) |
|
|
|
} |
|
|
|
}, { deep: true }) |
|
|
|
|
|
|
|
function parseStringToArray(str:string) { |
|
|
|
function parseStringToArray(str: string) { |
|
|
|
try { |
|
|
|
// 尝试解析JSON格式字符串 |
|
|
|
const result = JSON.parse(str); |
|
|
|
// 验证解析结果是否为数组 |
|
|
|
if (Array.isArray(result)) { |
|
|
|
return result; |
|
|
|
} else { |
|
|
|
//console.error("解析结果不是数组"); |
|
|
|
return []; |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
//console.error("字符串格式错误,无法解析为数组:", error.message); |
|
|
|
return []; |
|
|
|
} |
|
|
|
} |
|
|
|
onBeforeMount(() => { |
|
|
|
checkorgAndManTree() |
|
|
|
setTimeout(()=>{ |
|
|
|
value.value = parseStringToArray(props.modelValue) |
|
|
|
},500 ) |
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const keys = computed(()=>{ |
|
|
|
if(attrs.queryBy == 'role'){ |
|
|
|
return attrs.roleRange |
|
|
|
}else{ |
|
|
|
return attrs.orgRange |
|
|
|
} |
|
|
|
onBeforeMount(() => { |
|
|
|
setTimeout(() => { |
|
|
|
value.value = parseStringToArray(props.modelValue) |
|
|
|
}, 500) |
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const resData = ref() |
|
|
|
|
|
|
|
const checkorgAndManTree = (()=>{ |
|
|
|
if(props.orgAndManTree&&props.orgAndManTree.children&&props.orgAndManTree.children.length>0){ |
|
|
|
//alert(1) |
|
|
|
let arr = [] |
|
|
|
arr.push(modifyTreeData(props.orgAndManTree,keys.value)) |
|
|
|
|
|
|
|
resData.value = arr |
|
|
|
}else{ |
|
|
|
setTimeout(()=>{ |
|
|
|
checkorgAndManTree() |
|
|
|
},100) |
|
|
|
// 加载完整数据的函数 |
|
|
|
const loadFullData = async () => { |
|
|
|
if (isDataLoaded.value) return treeData.value; |
|
|
|
|
|
|
|
try { |
|
|
|
const result = checkorgAndManTree1(); |
|
|
|
treeData.value = result; |
|
|
|
isDataLoaded.value = true; |
|
|
|
return result; |
|
|
|
} catch (error) { |
|
|
|
console.error('加载组织数据失败:', error); |
|
|
|
return []; |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// 点击树选择器时的处理 |
|
|
|
const handleTreeSelectClick = async () => { |
|
|
|
if (!isDataLoaded.value) { |
|
|
|
await loadFullData(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 下拉框显示状态变化时的处理 |
|
|
|
const handleVisibleChange = async (visible: boolean) => { |
|
|
|
if (visible && !isDataLoaded.value) { |
|
|
|
await loadFullData(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
onUnmounted(()=>{ |
|
|
|
alert(1) |
|
|
|
const resData = computed(() => { |
|
|
|
return treeData.value; |
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function modifyTreeData(treeData, idList) { |
|
|
|
console.log("modifyTreeData 执行了") |
|
|
|
// 深拷贝树数据,避免修改原数据 |
|
|
|
const newTreeData = JSON.parse(JSON.stringify(treeData)); |
|
|
|
|
|
|
|
// 确保idList是一个数组 |
|
|
|
const idArray = Array.isArray(idList) ? idList : [idList]; |
|
|
|
if (idArray.length === 0) return Array.isArray(newTreeData) ? [] : null; |
|
|
|
|
|
|
|
// 1. 收集所有节点,记录:id→节点映射、id→父节点id映射(检测多父节点) |
|
|
|
const nodeMap = new Map(); // id → 节点 |
|
|
|
const parentMap = new Map(); // id → 父节点id(确保每个节点只有一个父节点) |
|
|
|
const multiParentIds = new Set(); // 记录有多个父节点的id |
|
|
|
|
|
|
|
function collectNodes(node, parentId) { |
|
|
|
if (!node || typeof node !== 'object' || !node.id) return; // 跳过无效节点 |
|
|
|
|
|
|
|
// 记录节点映射 |
|
|
|
if (!nodeMap.has(node.id)) { |
|
|
|
nodeMap.set(node.id, node); |
|
|
|
function checkorgAndManTree1() { |
|
|
|
let result = [] |
|
|
|
let i = 0 |
|
|
|
props.orgAndManTree.forEach((element: any) => { |
|
|
|
if (element.hasOwnProperty('tree') && hasNodesInTree(element.tree)) { |
|
|
|
i++ |
|
|
|
} |
|
|
|
|
|
|
|
// 记录父节点(检测多父节点) |
|
|
|
if (parentMap.has(node.id)) { |
|
|
|
// 已存在父节点,且与当前父节点不同 → 多父节点 |
|
|
|
if (parentMap.get(node.id) !== parentId) { |
|
|
|
multiParentIds.add(node.id); |
|
|
|
} |
|
|
|
} else { |
|
|
|
parentMap.set(node.id, parentId); // 首次记录父节点 |
|
|
|
} |
|
|
|
|
|
|
|
// 递归处理子节点,传入当前节点id作为父节点id |
|
|
|
if (node.children && Array.isArray(node.children)) { |
|
|
|
node.children.forEach(child => collectNodes(child, node.id)); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
// 处理根节点(可能是单个节点或数组) |
|
|
|
if (Array.isArray(newTreeData)) { |
|
|
|
newTreeData.forEach(root => collectNodes(root, null)); // 根节点父id为null |
|
|
|
if (i == props.orgAndManTree.length) { |
|
|
|
props.orgAndManTree.forEach((item: any) => { |
|
|
|
if (props.data.name == item.name) { |
|
|
|
console.log(item.tree) |
|
|
|
result = item.tree |
|
|
|
} |
|
|
|
}); |
|
|
|
return result |
|
|
|
} else { |
|
|
|
collectNodes(newTreeData, null); |
|
|
|
setTimeout(() => { |
|
|
|
checkorgAndManTree1() |
|
|
|
}, 100) |
|
|
|
} |
|
|
|
|
|
|
|
// 警告多父节点问题(核心重复原因) |
|
|
|
if (multiParentIds.size > 0) { |
|
|
|
console.warn(`以下节点存在多个父节点,可能导致重复:${Array.from(multiParentIds).join(', ')}`); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 判断树形结构是否存在节点 |
|
|
|
*/ |
|
|
|
function hasNodesInTree(tree) { |
|
|
|
if (!Array.isArray(tree)) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// 2. 收集需要保留的节点ID:目标节点+直系祖先(基于parentMap追溯,确保唯一路径) |
|
|
|
const keepIds = new Set(); |
|
|
|
idArray.forEach(targetId => { |
|
|
|
let currentId = targetId; |
|
|
|
// 从目标节点向上追溯,直到根节点(parentId为null) |
|
|
|
while (currentId !== null && nodeMap.has(currentId)) { |
|
|
|
keepIds.add(currentId); |
|
|
|
currentId = parentMap.get(currentId); // 基于唯一父节点追溯 |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
// 3. 过滤树结构:仅保留keepIds中的节点,且严格校验子节点的parentId是否匹配当前父节点 |
|
|
|
function filterTree(node) { |
|
|
|
if (!node || typeof node !== 'object' || !keepIds.has(node.id)) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
// 复制节点(避免修改原引用) |
|
|
|
const filteredNode = { ...node }; |
|
|
|
|
|
|
|
// 处理子节点:仅保留符合条件的子节点(在keepIds中,且parentId等于当前节点id) |
|
|
|
if (filteredNode.children && Array.isArray(filteredNode.children)) { |
|
|
|
// 临时存储符合条件的子节点(去重) |
|
|
|
const validChildren = new Map(); // 用id去重 |
|
|
|
|
|
|
|
filteredNode.children.forEach(child => { |
|
|
|
// 校验:子节点必须在keepIds中,且其父节点id是当前节点id |
|
|
|
if (child && typeof child === 'object' && child.id && |
|
|
|
keepIds.has(child.id) && parentMap.get(child.id) === filteredNode.id) { |
|
|
|
const filteredChild = filterTree(child); |
|
|
|
if (filteredChild) { |
|
|
|
validChildren.set(filteredChild.id, filteredChild); // 用id去重 |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
// 转为数组,保持原数据格式(空数组→null) |
|
|
|
filteredNode.children = Array.from(validChildren.values()); |
|
|
|
if (filteredNode.children.length === 0) { |
|
|
|
filteredNode.children = null; |
|
|
|
} |
|
|
|
} else { |
|
|
|
filteredNode.children = null; |
|
|
|
|
|
|
|
function checkNode(node) { |
|
|
|
if (node == null) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
return filteredNode; |
|
|
|
} |
|
|
|
|
|
|
|
// 处理根节点(数组或单个节点) |
|
|
|
let filteredTree; |
|
|
|
if (Array.isArray(newTreeData)) { |
|
|
|
// 根节点必须在keepIds中,且父节点为null |
|
|
|
filteredTree = newTreeData |
|
|
|
.map(root => { |
|
|
|
if (root && keepIds.has(root.id) && parentMap.get(root.id) === null) { |
|
|
|
return filterTree(root); |
|
|
|
} |
|
|
|
return null; |
|
|
|
}) |
|
|
|
.filter(Boolean); |
|
|
|
} else { |
|
|
|
filteredTree = (keepIds.has(newTreeData.id) && parentMap.get(newTreeData.id) === null) |
|
|
|
? filterTree(newTreeData) |
|
|
|
: null; |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
// 4. 对目标节点修改label和添加number属性 |
|
|
|
function modifyLabels(node) { |
|
|
|
if (!node || typeof node !== 'object') return; |
|
|
|
|
|
|
|
if (idArray.includes(node.id)) { |
|
|
|
const newLabel = `${node.label}(${node.value || ''})`; |
|
|
|
node.label = newLabel; |
|
|
|
node.number = newLabel; |
|
|
|
|
|
|
|
for (const node of tree) { |
|
|
|
if (checkNode(node)) { |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
if (node.children && Array.isArray(node.children)) { |
|
|
|
node.children.forEach(child => modifyLabels(child)); |
|
|
|
if (Array.isArray(node?.children) && hasNodesInTree(node.children)) { |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 执行标签修改 |
|
|
|
if (Array.isArray(filteredTree)) { |
|
|
|
filteredTree.forEach(root => modifyLabels(root)); |
|
|
|
} else { |
|
|
|
modifyLabels(filteredTree); |
|
|
|
} |
|
|
|
|
|
|
|
return filteredTree; |
|
|
|
|
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
</script> |
|
|
|
<template> |
|
|
|
|
|
|
|
|
|
|
|
<div style="width:100%"> |
|
|
|
<el-tree-select node-key="number" v-model="value" :data="resData" multiple :render-after-expand="false" |
|
|
|
show-checkbox clearable collapse-tags collapse-tags-tooltip :max-collapse-tags="4" filterable /> |
|
|
|
<el-tree-select |
|
|
|
node-key="number" |
|
|
|
v-model="value" |
|
|
|
:data="resData" |
|
|
|
multiple |
|
|
|
:render-after-expand="false" |
|
|
|
show-checkbox |
|
|
|
clearable |
|
|
|
collapse-tags |
|
|
|
collapse-tags-tooltip |
|
|
|
:max-collapse-tags="4" |
|
|
|
filterable |
|
|
|
@click="handleTreeSelectClick" |
|
|
|
@visible-change="handleVisibleChange" |
|
|
|
:disabled="props.disabled" |
|
|
|
:popper-append-to-body="false" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
<style lang='scss' scoped></style> |
|
|
|
|