利用AntDesign中a-tree和checkbox构造组织单位人员树选择组件

业务效果图

核心代码

<template>
<div class="select-container">
<a-modal v-model:visible="visible" @ok="handleOk" @cancel="handleCancel" width="1500px">
<template #title>
<div style="font-weight: 600; font-size: 16px">
选择任务协作者
</div>
</template>
<div class="tree-container" ref="treeContainerNodeRef">
<div class="tree-section01">
<div class="section01-left">
<a-input-search v-model:value="searchValue" style="margin-bottom: 8px" placeholder="搜索"
@search="onSearch"
/>
<div class="tree-title">选择组织</div>
<a-tree
:tree-data="treeData"
v-model:expandedKeys="expandedKeys"
v-model:selectedKeys="selectedKeys"
v-model:checkedKeys="checkedKeys"
@select="selectOrgTreeNode"
>
</a-tree>
</div>
<div class="section01-right">
<a-divider
type="vertical"
style="height: 100%;"
/>
</div>
</div>
<div class="tree-section02" ref="treeSection02ScrollRef">
<div class="section-left">
<div class="tree-title">选择人员</div>
<a-checkbox-group
v-model:value="selectedUserData"
name="checkboxgroup"
:options="userDataOptions"
@change="checkUserData"
/>
</div>
<div class="section-right">
<a-divider
type="vertical"
style="height: 100%;"
/>
</div>
</div>
<div class="tree-section03">
<div class="tag-container">
<div
style="font-weight: 600;height: 22px;line-height: 22px; position: relative; top: 3px; margin-bottom: 15px;">
已选人员
</div>
<div class="tag-content">
<a-tag closable class="tag-item"
@close="closeTag(tag)"
v-for="(tag) in tags" :key="nanoid()">
{{ tag.label }}
</a-tag>
</div>
</div>
</div>
</div>
</a-modal>
</div>
</template>
<script setup>
import {onMounted, ref, defineExpose, defineEmits, onBeforeUnmount, toRaw, watch, nextTick} from 'vue';
import {useStore} from 'vuex'
import {nanoid} from 'nanoid'
import mitt from '@/utils/mitt'
import {getOfficeTree, getUserTree} from "@/api/user";
import lodash from 'lodash'
const emit = defineEmits();
const store = useStore()
const visible = ref(false);
// 组织树数据源
const treeData = ref([]);
// 用户树数据源
const userData = ref([]);
// 所有用户数据
const globalUserDataOptions = ref([]);
const userDataOptions = ref([]);
// checkBoxUserData;
const selectedUserData = ref([]);
// 缓存数据
const cacheUserOptions = ref([]);
const curTreeData = ref([]);
const expandedKeys = ref([]);
// 全局selectedKeys
const globalUserData = ref([]);
const selectedKeys = ref([]);
const checkedKeys = ref([]);
const tags = ref([])
const treeSection02ScrollRef = ref(null);
const treeContainerNodeRef = ref(null);
const executeFlag = ref(false);
onMounted(() => {
mitt.on('selectedUserDataCode', (data) => {
selectedUserData.value = data;
globalUserData.value = data;
// 从缓存中搜寻 start
let cacheUserOptionsData = JSON.parse(localStorage.getItem('cacheGlobalUserDataOptions'));
if (cacheUserOptionsData !== null) {
nextTick(() => {
tags.value = cacheUserOptionsData.filter(v => globalUserData.value.some(val => val === v.value))
})
}
// 从缓存中搜索 end
})
mitt.on('deselectTag', (deselectTagValue) => {
tags.value = tags.value.filter(item => item.value !== deselectTagValue);
})
queryData();
})
// 滚动到容器顶部
const scrollToEditflag = (domNode, height) => {
for (let i = 0; i < height + 1000; i++) {
setTimeout(() => {
domNode.value.scrollTo(0, i);
}, 100);
}
};
// 组织架构树搜索
const searchValue = ref('');
const onSearch = (searchInputValue) => {
if ('' === searchInputValue) {
treeData.value = curTreeData.value;
return;
}
treeData.value = mapTreeData(searchInputValue, curTreeData.value)
};
const mapTreeData = (value, arr) => {
const newarr = [];
const keys = [];
arr.forEach((element) => {
if (element.title.indexOf(value) > -1) {
newarr.push(element)
keys.push(element.key)
} else {
// 遍历子层
if (element.children && element.children.length > 0) {
const redata = mapTreeData(value, element.children);
if (redata && redata.length > 0) {
const obj = {
...element,
children: redata
};
keys.push(obj.key)
newarr.push(obj);
}
}
}
})
expandedKeys.value = keys;
return newarr
}
// 点击左侧组织架构树根据组织code获取所属用户
const selectOrgTreeNode = async (selectedKeys) => {
// 重新得到selectUserData
executeFlag.value = true;
let officeCode = selectedKeys[0]
let res = await getUserTree({officeCode: officeCode});
if (200 === res.status) {
userDataOptions.value = [];
userData.value = res.data.data;
userData.value.forEach((user) => {
let option = {
label: user.userName,
value: user.userCode,
}
userDataOptions.value.push(option)
globalUserDataOptions.value.push(option);
})
// selectUserData 是根据当前userDataOptions过滤globalUserData得到的
selectedUserData.value = userDataOptions.value.filter(v => globalUserData.value.some(val => val === v.value)).map(item => item.value);
globalUserDataOptions.value = lodash.unionBy(globalUserDataOptions.value, "value");
// start 更新缓存 cacheGlobalUserDataOptions
let cacheGlobalUserDataOptions = JSON.parse(localStorage.getItem('cacheGlobalUserDataOptions'));
if (cacheGlobalUserDataOptions !== null) {
let cacheData = cacheGlobalUserDataOptions;
// 更新缓存数据并去重
cacheData = [...cacheData, ...globalUserDataOptions.value];
cacheData = lodash.unionBy(cacheData, "value");
localStorage.setItem('cacheGlobalUserDataOptions', JSON.stringify(cacheData));
} else {
localStorage.setItem('cacheGlobalUserDataOptions', JSON.stringify(globalUserDataOptions.value));
}
// end 更新缓存
}
}
// closeTag
const closeTag = (tag) => {
selectedUserData.value = selectedUserData.value.filter((key) => (key !== tag.value));
}
// 左侧组织架构树
const queryData = async () => {
let officeUserData = [];
if (localStorage.getItem('officeUserData') === null) {
let res = await getOfficeTree();
if (res.status === 200) {
officeUserData = res.data.data;
}
localStorage.setItem('officeUserData', JSON.stringify(officeUserData));
} else {
officeUserData = JSON.parse(localStorage.getItem('officeUserData'));
}
recursive(officeUserData);
}
// 根据数据源构建架构树
const recursive = (newList) => {
newList.map((item => {
item.title = item.officeName;
item.key = item.officeCode;
if (item.children !== undefined) {
recursive(item.children)
}
}))
treeData.value = newList;
curTreeData.value = newList;
};
const searchOfficeInfo = (userCode, treeDataParam) => {
for (let i = 0; i < treeDataParam.length; i++) {
if (treeDataParam[i].userCode === userCode) {
return {
label: treeDataParam[i].userName,
value: treeDataParam[i].userCode,
};
} else {
let res = searchOfficeInfo(officeCode, treeDataParam[i].userVos);
if (res != null) {
return res;
}
}
}
}
const checkUserData = (checkedValue) => {
}
watch(() => selectedUserData.value, (newVal, oldVal) => {
// 会记录之前的selectedUserData
let diff = newVal.concat(oldVal)
.filter(v => !newVal.includes(v) || !oldVal.includes(v));
let resGlobalUserData = []
if(newVal.length === 0 && oldVal.length === 0) {
return;
}
if (newVal.length > oldVal.length) {
// 新增
resGlobalUserData = [...new Set([...globalUserData.value, ...diff])];
}
if (newVal.length < oldVal.length) {
if(executeFlag.value) {
executeFlag.value = false;
let cacheData = JSON.parse(localStorage.getItem("cacheGlobalUserDataOptions"));
tags.value = cacheData.filter(v => globalUserData.value.some(val => val === v.value))
return;
}
resGlobalUserData = globalUserData.value.filter(item => item !== diff[0]);
}
globalUserData.value = resGlobalUserData;
// 缓存
let cacheData = JSON.parse(localStorage.getItem("cacheGlobalUserDataOptions"));
tags.value = cacheData.filter(v => globalUserData.value.some(val => val === v.value))
}, {deep: true})
// 显示弹窗
const showModal = () => {
visible.value = true;
};
const handleCancel = (e) => {
visible.value = false;
}
const handleOk = (e) => {
visible.value = false;
emit('tagNodeData', tags.value);
};
onBeforeUnmount(() => {
mitt.off();
})
defineExpose({
showModal
});
</script>
<style lang="scss" scoped>
.tree-container {
display: flex;
flex-direction: row;
min-height: 400px;
max-height: 650px;
.tree-section01 {
overflow-y: scroll;
margin-right: 20px;
display: flex;
}
.tree-section02 {
display: flex;
overflow-y: scroll;
margin-right: 20px;
padding-top: 35px;
flex: 2;
& :deep(.ant-checkbox-group) {
display: flex !important;
flex-direction: column !important;
}
}
.tree-section03 {
padding-top: 30px;
flex: 3;
}
.section-left {
width: 100%;
}
.tree-title {
font-size: 14px;
height: 22px;
line-height: 22px;
font-weight: 600;
color: rgba(21, 22, 24, 0.92);
}
.tag-container {
.tag-content {
width: 100%;
display: flex;
flex-wrap: wrap;
.tag-title {
font-size: 14px;
height: 22px;
line-height: 22px;
font-weight: 600;
color: red;
}
.tag-item {
margin: 2px 2px;
}
}
}
}
</style>
posted @   Felix_Openmind  阅读(824)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
*{cursor: url(https://files-cdn.cnblogs.com/files/morango/fish-cursor.ico),auto;}
点击右上角即可分享
微信分享提示