|
|
|
|
<script lang='ts' setup>
|
|
|
|
|
import { computed, onMounted, nextTick } from 'vue'
|
|
|
|
|
import { criteriaForPeopleList } from '@/api/hr/org/type'
|
|
|
|
|
import request from '@/utils/request';
|
|
|
|
|
import { useAttrs } from 'vue'
|
|
|
|
|
const attrs = useAttrs()
|
|
|
|
|
|
|
|
|
|
const props = withDefaults(
|
|
|
|
|
defineProps<{
|
|
|
|
|
modelValue?: string
|
|
|
|
|
disabled?: boolean
|
|
|
|
|
orgAndManTree?: any;
|
|
|
|
|
}>(),
|
|
|
|
|
{}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface User {
|
|
|
|
|
id: number
|
|
|
|
|
name: string
|
|
|
|
|
number: string
|
|
|
|
|
key: string
|
|
|
|
|
icon: string
|
|
|
|
|
department: string
|
|
|
|
|
role: string
|
|
|
|
|
label: string
|
|
|
|
|
value: string
|
|
|
|
|
}
|
|
|
|
|
const emits = defineEmits<{
|
|
|
|
|
(e: 'update:modelValue', value: string): void
|
|
|
|
|
}>()
|
|
|
|
|
|
|
|
|
|
const value = ref([])
|
|
|
|
|
const loading = ref(true)
|
|
|
|
|
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 })
|
|
|
|
|
|
|
|
|
|
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(() => {
|
|
|
|
|
// 启动检查
|
|
|
|
|
//checkReady()
|
|
|
|
|
|
|
|
|
|
//getTree()
|
|
|
|
|
setTimeout(()=>{
|
|
|
|
|
value.value = parseStringToArray(props.modelValue)
|
|
|
|
|
},500 )
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const url = "/javasys/lowCode/manCont/getManContsByKeys"
|
|
|
|
|
//console.log(attrs.value)
|
|
|
|
|
const keys = computed(()=>{
|
|
|
|
|
if(attrs.queryBy == 'role'){
|
|
|
|
|
return attrs.roleRange
|
|
|
|
|
}else{
|
|
|
|
|
return attrs.orgRange
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const options = ref([])
|
|
|
|
|
const convertToAFormatComplate = ref(false)
|
|
|
|
|
function convertToAFormat(bTree, idArray) {
|
|
|
|
|
const nodeMap = {};
|
|
|
|
|
|
|
|
|
|
// 递归收集节点,增加详细日志
|
|
|
|
|
function collectNodes(node, level = 0) {
|
|
|
|
|
if (!node) {
|
|
|
|
|
//console.log(`跳过无效节点: ${node}`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 记录当前节点信息(方便调试)
|
|
|
|
|
//console.log(`收集节点 [层级${level}]: id=${node.id}, label=${node.label}`);
|
|
|
|
|
// 必须有id才存入映射表(避免非节点元素)
|
|
|
|
|
if (node.id !== undefined && node.id !== null) {
|
|
|
|
|
nodeMap[node.id] = node;
|
|
|
|
|
} else {
|
|
|
|
|
//console.warn(`节点缺少id,跳过:`, node);
|
|
|
|
|
}
|
|
|
|
|
// 处理子节点(确保是数组,且元素有效)
|
|
|
|
|
if (Array.isArray(node.children)) {
|
|
|
|
|
node.children.forEach((child, index) => {
|
|
|
|
|
collectNodes(child, level + 1); // 层级+1,方便查看嵌套关系
|
|
|
|
|
});
|
|
|
|
|
} else if (node.children !== null && node.children !== undefined) {
|
|
|
|
|
//console.warn(`节点children不是数组,跳过:`, node.children);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理根节点(兼容数组或单个对象)
|
|
|
|
|
if (Array.isArray(bTree)) {
|
|
|
|
|
//console.log(`bTree是数组,共${bTree.length}个根节点`);
|
|
|
|
|
bTree.forEach((root, index) => {
|
|
|
|
|
//console.log(`处理根节点${index + 1}`);
|
|
|
|
|
collectNodes(root);
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
//console.log(`bTree是单个根节点`);
|
|
|
|
|
collectNodes(bTree);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 打印收集到的所有节点id(关键调试信息)
|
|
|
|
|
//console.log(`收集到的节点id列表:`, Object.keys(nodeMap));
|
|
|
|
|
|
|
|
|
|
// 匹配idArray并转换
|
|
|
|
|
const result = idArray.map(id => {
|
|
|
|
|
// 打印当前匹配的id,检查是否在nodeMap中
|
|
|
|
|
//console.log(`匹配id: ${id},是否存在: ${id in nodeMap}`);
|
|
|
|
|
const targetNode = nodeMap[id];
|
|
|
|
|
if (!targetNode) return null;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
id: targetNode.id,
|
|
|
|
|
number: targetNode.value ?? '', // 用空字符串替代null/undefined
|
|
|
|
|
name: targetNode.label ?? '',
|
|
|
|
|
icon: null,
|
|
|
|
|
key: String(targetNode.id), // 确保key是字符串(与a格式一致)
|
|
|
|
|
role: '',
|
|
|
|
|
deparment: '',
|
|
|
|
|
parentId: targetNode.parentId ?? ''
|
|
|
|
|
};
|
|
|
|
|
}).filter(Boolean);
|
|
|
|
|
|
|
|
|
|
//console.log(`最终转换结果:`, result);
|
|
|
|
|
convertToAFormatComplate.value = true
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getManConts(){
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let data = convertToAFormat(props.orgAndManTree,keys.value)
|
|
|
|
|
data.forEach((element: any) => {
|
|
|
|
|
element.icon = element.name+"("+element.number+")"
|
|
|
|
|
element.deparment = ""+element.deparment+">"+element.icon
|
|
|
|
|
element.label = element.deparment
|
|
|
|
|
element.value = element.icon
|
|
|
|
|
});
|
|
|
|
|
options.value = data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ 作者: 秦东
|
|
|
|
|
@ 时间: 2025-06-10 11:13:06
|
|
|
|
|
@ 功能: 输出结果
|
|
|
|
|
*/
|
|
|
|
|
const valPrint = (val:any) => {
|
|
|
|
|
|
|
|
|
|
if(Array.isArray(val)){
|
|
|
|
|
return val.join("、")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const resData = computed(()=>{
|
|
|
|
|
if(props.orgAndManTree){
|
|
|
|
|
let data1 = processTreeData(props.orgAndManTree)
|
|
|
|
|
//console.log(data1)
|
|
|
|
|
getManConts()
|
|
|
|
|
|
|
|
|
|
let data2 = filterTreeByList(data1.children, options.value)
|
|
|
|
|
|
|
|
|
|
data1.children = data2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
return enrichTreeWithEmployeeInfo(options.value, data2)
|
|
|
|
|
}else{
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据列表数据过滤树形数据,保留value值在列表number中匹配的节点及其祖先
|
|
|
|
|
* @param {Array} treeData - 树形数据
|
|
|
|
|
* @param {Array} listData - 列表数据
|
|
|
|
|
* @returns {Array} 过滤后的树形数据
|
|
|
|
|
*/
|
|
|
|
|
function filterTreeByList(treeData: string | any[], listData: any[]) {
|
|
|
|
|
// 提取列表中的number值,用于快速查找
|
|
|
|
|
const numberSet = new Set();
|
|
|
|
|
listData.forEach((item: { number: unknown; }) => {
|
|
|
|
|
numberSet.add(item.number);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 用于追踪已访问的节点,避免重复
|
|
|
|
|
const visitedNodes = new Set();
|
|
|
|
|
|
|
|
|
|
// 递归函数:检查节点及其子节点是否需要保留
|
|
|
|
|
function processNode(node: { id: any; value: unknown; children: string | any[] | null; }) {
|
|
|
|
|
// 使用节点的id作为唯一标识,避免重复处理
|
|
|
|
|
const nodeId = node.id;
|
|
|
|
|
if (visitedNodes.has(nodeId)) {
|
|
|
|
|
return false; // 如果已经访问过这个节点,直接返回false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 标记当前节点已访问
|
|
|
|
|
visitedNodes.add(nodeId);
|
|
|
|
|
|
|
|
|
|
// 标记当前节点是否直接匹配
|
|
|
|
|
const isDirectMatch = node.value && numberSet.has(node.value);
|
|
|
|
|
|
|
|
|
|
// 如果没有children属性或为null,直接返回是否直接匹配
|
|
|
|
|
if (!node.children || node.children === null) {
|
|
|
|
|
return isDirectMatch;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 临时存储需要保留的子节点
|
|
|
|
|
const childrenToKeep = [];
|
|
|
|
|
|
|
|
|
|
// 遍历所有子节点
|
|
|
|
|
for (let i = 0; i < node.children.length; i++) {
|
|
|
|
|
const child = node.children[i];
|
|
|
|
|
// 递归检查子节点
|
|
|
|
|
if (processNode(child)) {
|
|
|
|
|
// 只有当子节点需要保留时,才将其添加到childrenToKeep数组中
|
|
|
|
|
childrenToKeep.push(child);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新当前节点的children数组,只保留需要的子节点
|
|
|
|
|
node.children = childrenToKeep;
|
|
|
|
|
|
|
|
|
|
// 返回当前节点是否应该保留(直接匹配或者有匹配的子节点)
|
|
|
|
|
return isDirectMatch || childrenToKeep.length > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理每个根节点
|
|
|
|
|
const result = [];
|
|
|
|
|
for (let i = 0; i < treeData.length; i++) {
|
|
|
|
|
// 创建节点副本以避免修改原始数据
|
|
|
|
|
const nodeCopy = JSON.parse(JSON.stringify(treeData[i]));
|
|
|
|
|
if (processNode(nodeCopy)) {
|
|
|
|
|
result.push(nodeCopy);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 将 a 类型员工数据映射到树形结构中,为匹配的节点添加 number、role、deparment、empType 属性
|
|
|
|
|
* @param {Array} employees - a 类型员工数组,每个元素包含 number, name, role, deparment, empType, key 等字段
|
|
|
|
|
* @param {Array} treeData - 树形结构数据(可能包含嵌套的 children)
|
|
|
|
|
* @returns {Array} - 更新后的树形结构
|
|
|
|
|
*/
|
|
|
|
|
function enrichTreeWithEmployeeInfo(employees: any[], treeData: string | any[]) {
|
|
|
|
|
// 构建以 value 为 key 的员工信息映射表,便于快速查找
|
|
|
|
|
const employeeMap = new Map();
|
|
|
|
|
employees.forEach((emp: { number: any; }) => {
|
|
|
|
|
employeeMap.set(emp.number, emp); // 注意:树节点用 value 字段匹配 a 类型的 number
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 递归处理树节点
|
|
|
|
|
function traverse(nodes: any[]) {
|
|
|
|
|
if (!Array.isArray(nodes)) return;
|
|
|
|
|
|
|
|
|
|
nodes.forEach(node => {
|
|
|
|
|
// 如果当前节点有 value(即叶子员工节点)
|
|
|
|
|
if (node.value !== null && node.value !== undefined) {
|
|
|
|
|
const emp = employeeMap.get(node.value);
|
|
|
|
|
if (emp) {
|
|
|
|
|
// 添加 a 类型中的字段到树节点
|
|
|
|
|
node.label = node.label + "(" + emp.number + ")";
|
|
|
|
|
node.number = node.label
|
|
|
|
|
node.role = emp.role;
|
|
|
|
|
node.deparment = emp.deparment;
|
|
|
|
|
node.empType = emp.empType;
|
|
|
|
|
// 可选:也可以添加 name、key 等其他字段
|
|
|
|
|
// node.name = emp.name;
|
|
|
|
|
// node.key = emp.key;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 递归处理子节点
|
|
|
|
|
if (Array.isArray(node.children)) {
|
|
|
|
|
traverse(node.children);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理根节点(注意:你的树外层有个 "全选" 节点)
|
|
|
|
|
if (treeData && treeData.length > 0) {
|
|
|
|
|
traverse(treeData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return treeData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function processTreeData(node: any) {
|
|
|
|
|
|
|
|
|
|
// 检查节点及其所有子孙节点中是否存在id长度大于4的节点
|
|
|
|
|
function hasLongIdNode(node: { id: string | any[]; children: string | any[]; }) {
|
|
|
|
|
// 检查当前节点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: any) {
|
|
|
|
|
// 如果当前节点及其所有子孙都没有长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: any) => processNode(child))
|
|
|
|
|
.filter((child: null) => child !== null);
|
|
|
|
|
|
|
|
|
|
// 如果所有子节点都被移除,保持children为null
|
|
|
|
|
if (newNode.children.length === 0) {
|
|
|
|
|
newNode.children = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return newNode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return processNode(node);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</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 />
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<style lang='scss' scoped></style>
|