2 changed files with 429 additions and 1 deletions
@ -0,0 +1,428 @@ |
|||||
|
<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 |
||||
|
}>(), |
||||
|
{} |
||||
|
) |
||||
|
interface User { |
||||
|
id: number |
||||
|
name: string |
||||
|
number: string |
||||
|
key: string |
||||
|
icon: string |
||||
|
department: string |
||||
|
role: string |
||||
|
} |
||||
|
const emits = defineEmits<{ |
||||
|
(e: 'update:modelValue', value: string): void |
||||
|
}>() |
||||
|
const userDialogEl = ref() |
||||
|
|
||||
|
|
||||
|
|
||||
|
const loading = ref(true) |
||||
|
const value = ref([]) |
||||
|
|
||||
|
|
||||
|
|
||||
|
watch(value, (newValue) => { |
||||
|
|
||||
|
const checkReady = () => { |
||||
|
|
||||
|
if (loading.value == false) { |
||||
|
if (newValue.length > 0) { |
||||
|
let str = "" |
||||
|
|
||||
|
let userAry = new Array |
||||
|
|
||||
|
|
||||
|
newValue.forEach(item => { |
||||
|
|
||||
|
userAry.push(item) |
||||
|
|
||||
|
}) |
||||
|
|
||||
|
str = userAry.join(',') |
||||
|
//console.log(str) |
||||
|
emits('update:modelValue', str) |
||||
|
|
||||
|
} else { |
||||
|
let str = "" |
||||
|
|
||||
|
emits('update:modelValue', str) |
||||
|
} |
||||
|
} else { |
||||
|
// 未就绪,100ms 后重试(非阻塞) |
||||
|
setTimeout(checkReady, 100) |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
// 启动检查 |
||||
|
checkReady() |
||||
|
}, { deep: true }) |
||||
|
|
||||
|
|
||||
|
|
||||
|
const openDialog = () => { |
||||
|
// console.log("value-----》",value.value) |
||||
|
userDialogEl.value.open() |
||||
|
} |
||||
|
|
||||
|
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(() => { |
||||
|
getManConts() |
||||
|
getTree() |
||||
|
setTimeout(() => { |
||||
|
value.value = parseStringToArray(props.modelValue) |
||||
|
}, 700) |
||||
|
}) |
||||
|
const url = "/javasys/lowCode/manCont/getManContsByKeys" |
||||
|
//console.log(attrs.value) |
||||
|
const keys = computed(() => { |
||||
|
if (attrs.queryBy == 'role') { |
||||
|
return attrs.roleRange |
||||
|
} else { |
||||
|
return attrs.orgRange |
||||
|
} |
||||
|
}) |
||||
|
function getManContsByKeys() { |
||||
|
//console.log(11111) |
||||
|
|
||||
|
return request({ |
||||
|
url: url, |
||||
|
method: "post", |
||||
|
data: { |
||||
|
keys: keys.value, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
} |
||||
|
const options = ref([]) |
||||
|
|
||||
|
function getManConts() { |
||||
|
getManContsByKeys().then(({ data }) => { |
||||
|
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 |
||||
|
//console.log(options.value) |
||||
|
|
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
|
||||
|
const treeUrl = '/javasys/lowCode/transfer/getOrgAndManTree' |
||||
|
function getOrgAndManTree() { |
||||
|
return request({ |
||||
|
url: treeUrl, |
||||
|
method: 'post', |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
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); |
||||
|
} |
||||
|
let resData = ref([]) |
||||
|
|
||||
|
const userList = computed({ |
||||
|
|
||||
|
get() { |
||||
|
|
||||
|
|
||||
|
return [{ |
||||
|
id: '全选', |
||||
|
label: '全选', |
||||
|
children: [...resData.value] |
||||
|
}] |
||||
|
|
||||
|
|
||||
|
|
||||
|
}, |
||||
|
set() { |
||||
|
|
||||
|
} |
||||
|
}) |
||||
|
/** |
||||
|
* 根据列表数据过滤树形数据,保留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; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 在树结构中查找 id 等于 targetId 的节点 |
||||
|
* @param {Array} tree - 树结构数据(数组形式) |
||||
|
* @param {string} targetId - 要查找的节点 id |
||||
|
* @returns {Object|null} 找到的节点对象,未找到则返回 null |
||||
|
*/ |
||||
|
function findNodeById(tree: any, targetId: any) { |
||||
|
|
||||
|
if (!Array.isArray(tree)) return null; |
||||
|
|
||||
|
for (const node of tree) { |
||||
|
|
||||
|
let str = node.label + "(" + node.number + ")" |
||||
|
/* console.log(node) |
||||
|
console.log(targetId) */ |
||||
|
if (node.number == targetId) { |
||||
|
/* console.log("fdsdfafdsafdsafdsafsdadsf") |
||||
|
console.log(node) |
||||
|
console.log(targetId) */ |
||||
|
return node; |
||||
|
} |
||||
|
|
||||
|
if (node.children && node.children.length > 0) { |
||||
|
const found = findNodeById(node.children, targetId); |
||||
|
if (found) { |
||||
|
return found; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 将 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 getTree() { |
||||
|
getOrgAndManTree().then(({ data }) => { |
||||
|
|
||||
|
|
||||
|
let data1 = processTreeData(data) |
||||
|
//console.log(data1) |
||||
|
let data2 = filterTreeByList(data1.children, options.value) |
||||
|
|
||||
|
//console.log(data2) |
||||
|
data1.children = data2 |
||||
|
|
||||
|
//console.log(resData.value) |
||||
|
//console.log(`获取穿梭框接口数据`); |
||||
|
resData.value = data1.children |
||||
|
userList.value = [{ |
||||
|
id: '全选', |
||||
|
label: '全选', |
||||
|
children: [...resData.value] |
||||
|
}] |
||||
|
|
||||
|
enrichTreeWithEmployeeInfo(options.value, userList.value) |
||||
|
loading.value = false |
||||
|
|
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
</script> |
||||
|
<template> |
||||
|
|
||||
|
<!-- <el-select-v2 |
||||
|
placeholder="请选择用户" |
||||
|
v-model="value" |
||||
|
node-key="key" |
||||
|
|
||||
|
:options="options" |
||||
|
filterable |
||||
|
:render-after-expand="false" |
||||
|
|
||||
|
|
||||
|
multiple |
||||
|
clearable |
||||
|
collapse-tags |
||||
|
collapse-tags-tooltip |
||||
|
:max-collapse-tags="4" |
||||
|
/> --> |
||||
|
<div style="width:100%"> |
||||
|
<el-tree-select node-key="number" v-model="value" :data="userList" multiple :render-after-expand="false" |
||||
|
show-checkbox clearable collapse-tags collapse-tags-tooltip :max-collapse-tags="4" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
<style lang='scss' scoped></style> |
||||
Loading…
Reference in new issue