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.
372 lines
10 KiB
372 lines
10 KiB
<script lang='ts' setup>
|
|
import { computed, onMounted, onBeforeMount, onUnmounted, nextTick, inject, ref, watch } 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;
|
|
data?: any;
|
|
isEdit?: boolean;
|
|
}>(),
|
|
{}
|
|
)
|
|
|
|
const emits = defineEmits<{
|
|
(e: 'update:modelValue', value: string): void
|
|
}>()
|
|
|
|
const value = ref([])
|
|
const treeData = ref([]) // 存储懒加载的数据
|
|
const isDataLoaded = ref(false) // 标记数据是否已加载
|
|
const loading = ref(false) // 是否正在加载
|
|
const treeSelectRef = ref() // 树选择器引用
|
|
// const { location, updateLocation } = inject('location')
|
|
|
|
// 上一次选择的值,用于比较
|
|
const lastSelectedValue = ref(null)
|
|
|
|
// 修复:组件挂载状态,用于防止卸载后异步操作更新状态
|
|
const isMounted = ref(false)
|
|
|
|
// 处理值变化
|
|
const handleValueChange = (newValue) => {
|
|
if (!multiSelect.value) {
|
|
// 单选模式
|
|
if (newValue.length === 0) {
|
|
// 清空选择
|
|
emits('update:modelValue', '');
|
|
lastSelectedValue.value = null;
|
|
} else if (newValue.length === 1) {
|
|
// 单选模式下只有一个选择
|
|
const currentValue = newValue[0];
|
|
emits('update:modelValue', currentValue);
|
|
lastSelectedValue.value = currentValue;
|
|
} else {
|
|
// 当有多个选择时,找出新增的那个
|
|
const newSelections = newValue.filter(item => item !== lastSelectedValue.value);
|
|
if (newSelections.length > 0) {
|
|
// 只保留最新选择的那个
|
|
const latestSelection = newSelections[newSelections.length - 1];
|
|
value.value = [latestSelection];
|
|
emits('update:modelValue', latestSelection);
|
|
lastSelectedValue.value = latestSelection;
|
|
} else {
|
|
// 如果没有新增的选择,可能是取消选择,保持原样
|
|
value.value = newValue;
|
|
}
|
|
}
|
|
//实现选择用户选择时,自动填充选择组织 liwenxuan 2025-11-11 start
|
|
|
|
//监听选择用户的当前值,告知父组件form
|
|
if (isMounted.value) {
|
|
updateLocation(value.value, props.data.name)
|
|
}
|
|
|
|
//实现选择用户选择时,自动填充选择组织 liwenxuan 2025-11-11 end
|
|
} else {
|
|
// 多选模式下的处理
|
|
if (newValue.length > 0) {
|
|
let str = ""
|
|
let userAry = new Array
|
|
if(Array.isArray(newValue)){
|
|
newValue.forEach(item => {
|
|
userAry.push(item)
|
|
})
|
|
str = userAry.join(',')
|
|
emits('update:modelValue', str)
|
|
}else{
|
|
let a = new Array
|
|
a.push(newValue)
|
|
a.forEach(item => {
|
|
userAry.push(item)
|
|
})
|
|
str = userAry.join(',')
|
|
emits('update:modelValue', str)
|
|
}
|
|
} else {
|
|
let str = ""
|
|
emits('update:modelValue', str)
|
|
}
|
|
}
|
|
}
|
|
|
|
watch(value, handleValueChange, { deep: true })
|
|
|
|
const multiSelect = computed(()=>{
|
|
if(props.data.control.multiSelect && props.data.control.multiSelect == "1"){
|
|
return true
|
|
}else{
|
|
return false
|
|
}
|
|
})
|
|
|
|
function parseStringToArray(str: string) {
|
|
try {
|
|
const result = JSON.parse(str);
|
|
if (Array.isArray(result)) {
|
|
return result;
|
|
} else {
|
|
return [];
|
|
}
|
|
} catch (error) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// 修复:递归清洗树数据,移除所有非数组的children字段
|
|
const sanitizeTreeData = (data: any[]): any[] => {
|
|
if (!Array.isArray(data)) return [];
|
|
return data.map(node => {
|
|
const newNode = { ...node };
|
|
if (newNode.children != null) {
|
|
if (Array.isArray(newNode.children)) {
|
|
// 递归清洗子节点
|
|
newNode.children = sanitizeTreeData(newNode.children);
|
|
} else {
|
|
// 非数组的children会引发el-tree报错,直接删除
|
|
delete newNode.children;
|
|
}
|
|
}
|
|
return newNode;
|
|
});
|
|
}
|
|
|
|
onBeforeMount(() => {
|
|
setTimeout(()=>{
|
|
// 修复:组件卸载后不再执行
|
|
if (!isMounted.value) return;
|
|
if(props.modelValue){
|
|
const initialValue = parseStringToArray(props.modelValue)
|
|
value.value = initialValue;
|
|
if (initialValue.length > 0 && !multiSelect.value) {
|
|
lastSelectedValue.value = initialValue[initialValue.length - 1];
|
|
}
|
|
}
|
|
},1500)
|
|
})
|
|
|
|
onMounted(() => {
|
|
isMounted.value = true;
|
|
})
|
|
|
|
onUnmounted(()=>{
|
|
isMounted.value = false;
|
|
// 清理轮询定时器
|
|
if (pollingTimer) clearTimeout(pollingTimer);
|
|
})
|
|
|
|
// 修复:增加轮询定时器清理
|
|
let pollingTimer: ReturnType<typeof setTimeout> | null = null;
|
|
|
|
// 递归处理树节点,将部门节点设置为禁用
|
|
const processTreeData = (data: any[]) => {
|
|
return data.map(node => {
|
|
const newNode = { ...node };
|
|
|
|
// 如果multiSelect为false且id长度小于5,设置为禁用
|
|
if (!multiSelect.value && node.id && node.id.toString().length < 5) {
|
|
newNode.disabled = true;
|
|
}
|
|
|
|
// 递归处理子节点
|
|
if (newNode.children && Array.isArray(newNode.children)) {
|
|
newNode.children = processTreeData(newNode.children);
|
|
}
|
|
|
|
return newNode;
|
|
});
|
|
}
|
|
|
|
// 加载完整数据的函数
|
|
const loadFullData = async () => {
|
|
if (isDataLoaded.value) return treeData.value;
|
|
|
|
loading.value = true;
|
|
|
|
try {
|
|
// 模拟数据加载过程
|
|
await new Promise(resolve => setTimeout(resolve, 800));
|
|
|
|
const result = await checkorgAndManTree1();
|
|
|
|
// 修复:先清洗非法children,再处理禁用状态
|
|
const cleanedData = sanitizeTreeData(result);
|
|
const processedData = !multiSelect.value ? processTreeData(cleanedData) : cleanedData;
|
|
|
|
// 修复:仅在组件挂载时更新数据
|
|
if (isMounted.value) {
|
|
treeData.value = processedData;
|
|
isDataLoaded.value = true;
|
|
}
|
|
return processedData;
|
|
} catch (error) {
|
|
console.error('加载组织数据失败:', error);
|
|
return [];
|
|
} finally {
|
|
if (isMounted.value) {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 点击树选择器时的处理
|
|
const handleTreeSelectClick = async () => {
|
|
if (!isDataLoaded.value && !loading.value) {
|
|
await loadFullData();
|
|
}
|
|
}
|
|
|
|
// 下拉框显示状态变化时的处理
|
|
const handleVisibleChange = async (visible: boolean) => {
|
|
if (visible && !isDataLoaded.value && !loading.value) {
|
|
await loadFullData();
|
|
}
|
|
}
|
|
|
|
const resData = computed(() => {
|
|
return treeData.value;
|
|
})
|
|
|
|
function checkorgAndManTree1() {
|
|
return new Promise((resolve) => {
|
|
const check = () => {
|
|
// 修复:组件已卸载,不再继续轮询
|
|
if (!isMounted.value) {
|
|
resolve([]);
|
|
return;
|
|
}
|
|
let result = []
|
|
let i = 0
|
|
props.orgAndManTree.forEach((element: any) => {
|
|
if (element.hasOwnProperty('tree') && hasNodesInTree(element.tree)) {
|
|
i++
|
|
}
|
|
});
|
|
if (i == props.orgAndManTree.length) {
|
|
props.orgAndManTree.forEach((item: any) => {
|
|
if (props.data.name == item.name) {
|
|
//console.log(item.tree)
|
|
result = item.tree
|
|
}
|
|
});
|
|
resolve(result)
|
|
} else {
|
|
pollingTimer = setTimeout(check, 100)
|
|
}
|
|
}
|
|
check()
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 判断树形结构是否存在节点
|
|
*/
|
|
function hasNodesInTree(tree) {
|
|
if (!Array.isArray(tree)) {
|
|
return false;
|
|
}
|
|
|
|
function checkNode(node) {
|
|
if (node == null) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
for (const node of tree) {
|
|
if (checkNode(node)) {
|
|
return true;
|
|
}
|
|
if (Array.isArray(node?.children) && hasNodesInTree(node.children)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
</script>
|
|
<template>
|
|
<div class="tree-select-wrapper" >
|
|
<el-tree-select
|
|
ref="treeSelectRef"
|
|
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.isEdit"
|
|
:popper-append-to-body="false"
|
|
>
|
|
<!-- 自定义空状态 -->
|
|
<template #empty>
|
|
<div class="custom-empty">
|
|
{{ loading ? '正在加载' : '无数据' }}
|
|
</div>
|
|
</template>
|
|
</el-tree-select>
|
|
</div>
|
|
</template>
|
|
<style lang='scss' scoped>
|
|
.tree-select-wrapper {
|
|
position: relative;
|
|
min-height: 40px;
|
|
width: 100%;
|
|
}
|
|
|
|
.wordColor {
|
|
color: #000000;
|
|
}
|
|
|
|
.el-tree-node__expand-icon {
|
|
color: var(--el-tree-expand-icon-color);
|
|
cursor: pointer;
|
|
font-size: 18px;
|
|
transform: rotate(0deg);
|
|
transition: transform var(--el-transition-duration) ease-in-out;
|
|
}
|
|
|
|
.el-tree {
|
|
--el-tree-node-content-height: 30px;
|
|
--el-tree-node-hover-bg-color: var(--el-fill-color-light);
|
|
--el-tree-text-color: var(--el-text-color-regular);
|
|
--el-tree-expand-icon-color: var(--el-text-color-placeholder);
|
|
background: var(--el-fill-color-blank);
|
|
color: var(--el-tree-text-color);
|
|
cursor: default;
|
|
font-size: var(--el-font-size-base);
|
|
position: relative;
|
|
}
|
|
|
|
.el-tree-select__popper .el-tree-node__expand-icon {
|
|
margin-left: 7px;
|
|
margin-right: 7px;
|
|
}
|
|
|
|
/* 自定义空状态样式 */
|
|
.custom-empty {
|
|
padding: 10px;
|
|
text-align: center;
|
|
color: #909399;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.el-select-dropdown__item.is-disabled {
|
|
background-color: unset;
|
|
color: #606266;
|
|
cursor: not-allowed;
|
|
}
|
|
</style>
|
|
|