人员组件(vue3+element+ts)

一、先看效果

1.单选

显示效果:

2.多选

显示效果:

进入默认全部人员,也可以根据左侧部门树来筛选人员

二、代码

<template>
	<template v-if="props.isbutton">
		<span @click="selectPersonnelVisible = true">
			<slot></slot>
		</span>
	</template>
	<el-select
		:disabled="disabled"
		v-if="!props.isbutton"
		ref="chooseProjectName"
		v-model="selectPersonnelInfo"
		multiple
		collapse-tags-tooltip
		:placeholder="props.placeholder"
		style="width: 240px"
		@focus="handleTestTasks"
		@visible-change="visibleType"
	>
		<el-option v-for="item in personnel" :key="item.id" :label="item?.name" :value="item?.id" />
	</el-select>
	<el-dialog v-model="selectPersonnelVisible" append-to-body title="选择" width="60%" draggable>
		<div class="selectPersonnelContent">
			<div class="PersonneTree">
				<div class="header">
					<el-input v-model="departmentName" placeholder="请输入部门名称" :suffix-icon="Search" />
				</div>
				<el-tree v-if="treeFlag" highlight-current style="max-width: 600px" :props="prop" :load="loadNode" lazy @current-change="checkChangeTree" />
			</div>
			<div class="PersonneTable">
				<div class="header">
					<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
						<el-form-item label="姓名" prop="name">
							<el-input placeholder="请输入姓名" v-model="state.queryForm.name" />
						</el-form-item>
						<el-form-item style="margin-bottom: 0 !important">
							<el-button icon="search" type="primary" @click="getDataList"> 查询 </el-button>
							<el-button icon="Refresh" @click="resetQuery">重置</el-button>
						</el-form-item>
					</el-form>
				</div>
				<el-table
					v-if="props.multiple"
					ref="multipleTableRef"
					:data="state.dataList"
					style="width: 100%"
					highlight-current-row
					@selection-change="handleSelectionChange"
					@select="onSelect"
					@select-all="onSelectAll"
					empty-text="请选择组织或更换组织"
				>
					<el-table-column type="selection" width="55" />
					<el-table-column label="用户名" show-overflow-tooltip
						><template #default="scope">{{ scope.row.username }}</template>
					</el-table-column>
					<el-table-column label="姓名" show-overflow-tooltip>
						<template #default="scope">{{ scope.row.name }}</template>
					</el-table-column>
				</el-table>
				<el-table
					v-if="!props.multiple"
					ref="multipleTableRef"
					:data="state.dataList"
					style="width: 100%"
					highlight-current-row
					empty-text="请选择组织或更换组织"
					@current-change="handleCurrentChange"
				>
					<el-table-column label="用户名" show-overflow-tooltip
						><template #default="scope">{{ scope.row.username }}</template>
					</el-table-column>
					<el-table-column label="姓名" show-overflow-tooltip>
						<template #default="scope">{{ scope.row.name }}</template>
					</el-table-column>
				</el-table>
				<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
			</div>
			<div class="PersonneInfo">
				<div class="header">
					<span>已选人员({{ selectionPersonnel.length }})</span>
					<span @click="clearPersonneInfo">清空</span>
				</div>
				<ul>
					<li v-for="(item, index) in selectionPersonnel" :key="index">
						{{ item?.name }}<el-icon class="icon" @click="delPersonneInfo(item)"><Delete /></el-icon>
					</li>
				</ul>
			</div>
		</div>
		<template #footer>
			<div class="dialog-footer">
				<el-button @click="selectPersonnelVisible = false">取消</el-button>
				<el-button type="primary" @click="submit"> 确认 </el-button>
			</div>
		</template>
	</el-dialog>
</template>

<script setup lang="ts" name="selectPersonnel">
import { BasicTableProps, useTable } from '/@/hooks/table';
import { getDepartmentTree, getSimpleUserPage } from '/@/api/selectPersonnel/index';
import type Node from 'element-plus/es/components/tree/src/model/node';
import { Search } from '@element-plus/icons-vue';
const selectPersonnelInfo = ref([]) as any;
const selectPersonnelVisible = ref(false);
const chooseProjectName = ref();
const multipleTableRef = ref();
const queryRef = ref();
const departmentName = ref('');
const personnel = ref([]) as any;
const emit = defineEmits(['update:orgList', 'change']);
const treeFlag = ref(true);
const props = defineProps({
	multiple: {
		type: Boolean,
		default: () => false,
	},
	orgList: {
		type: Array,
		default: () => [],
	},
	placeholder: {
		type: String,
		default: '请选择人员',
	},
	isbutton: {
		type: Boolean,
		detalut: () => false,
	},
	disabled: {
		type: Boolean,
		default: () => false,
	},
});
interface Tree {
	name: string;
	leaf?: boolean;
}
const state: BasicTableProps = reactive<BasicTableProps>({
	queryForm: {},
	pageList: getSimpleUserPage,
});
//  table hook
const { currentChangeHandle, sizeChangeHandle, getDataList } = useTable(state);
const prop = {
	label: 'name',
	// children: 'children',
	isLeaf: 'leaf',
};
// 多选
const multipleSelection = ref([]) as any;
// 人员
const selectionPersonnel = ref([]) as any;
const handleSelectionChange = (val: any) => {
	multipleSelection.value = val;
	// 去重
	val.forEach((el: any) => {
		if (!selectionPersonnel.value.find((item: any) => item.id === el.id)) {
			selectionPersonnel.value.push(el);
		}
	});
};
const onSelect = (rows: any, row: any) => {
	// selected true就是选中,0或者false是取消选中
	let selected = rows.length && rows.indexOf(row) !== -1;
	if (!selected) {
		selectionPersonnel.value.forEach((item: { id: '' }, i: number) => {
			if (row?.id == item.id) {
				nextTick(() => {
					selectionPersonnel.value.splice(i, 1);
				});
			}
		});
	}
};
const onSelectAll = (rows: any) => {
	if (!rows.length) {
		for (let index = 0; index < selectionPersonnel.value.length; index++) {
			const element = selectionPersonnel.value[index];

			if (state.dataList?.find((el: any) => el.id === element.id)) {
				selectionPersonnel.value.splice(index, 1);
				index--;
			}
		}
	}
};
// 单选
const handleCurrentChange = (row: any) => {
	multipleSelection.value = [row];
	// 去重
	if (!selectionPersonnel.value.find((item: any) => item?.id === row?.id)) {
		selectionPersonnel.value = [row];
	}
};
// 阻止下拉框  下拉事件
const handleTestTasks = () => {
	chooseProjectName.value.blur();
};
// 清空搜索条件
const resetQuery = () => {
	// 清空搜索条件
	queryRef.value?.resetFields();
	getDataList();
};
// 删除
const delPersonneInfo = (row: { id: '' }) => {
	state.dataList?.forEach((item: { id: '' }) => {
		if (row?.id == item.id) {
			nextTick(() => {
				multipleTableRef.value!.toggleRowSelection(item, false);
			});
			return;
		}
	});

	selectionPersonnel.value.forEach((item: { id: '' }, i: number) => {
		if (row?.id == item.id) {
			selectionPersonnel.value.splice(i, 1);
			return;
		}
	});
};
// 清空
const clearPersonneInfo = () => {
	nextTick(() => {
		multipleTableRef.value?.clearSelection();
	});
	selectionPersonnel.value = [];
};
// select 回显
watch(
	() => props.orgList,
	() => {
		personnel.value = [];
		selectPersonnelInfo.value = [];
		multipleSelection.value = [];
		// 多选回显
		if (props.orgList.length) {
			multipleSelection.value = props.orgList;
			selectionPersonnel.value = props.orgList;
			personnel.value = props.orgList;
			selectPersonnelInfo.value = multipleSelection.value.map((item: any) => item?.id);
		} else {
			selectionPersonnel.value = [];
		}
	},
	{ deep: true, immediate: true }
);
// 选中状态回显
watch(
	() => selectPersonnelVisible.value,
	() => {
		nextTick(() => {
			multipleTableRef.value?.clearSelection();
		});

		if (!selectPersonnelVisible.value) return;
		if (props.orgList.length) {
			nextTick(() => {
				state.dataList?.forEach((item: any) => {
					if (selectPersonnelInfo.value.includes(item.id)) {
						multipleTableRef.value?.toggleRowSelection(item, true);
					}
				});
			});
		} else {
			nextTick(() => {
				multipleTableRef.value?.clearSelection();
			});
		}
	}
);
// 切换回显
watch(
	() => state.dataList,
	() => {
		if (!props.multiple) return;
		if (selectionPersonnel.value.length) {
			state.dataList?.forEach((el: any) => {
				if (selectionPersonnel.value.find((item: any) => item.id === el.id)) {
					nextTick(() => {
						multipleTableRef.value?.toggleRowSelection(el, true);
					});
				}
			});
		}
	},
	{ immediate: true }
);
const visibleType = () => {
	// 弹框
	nextTick(() => {
		// 不显示下拉框
		chooseProjectName.value.blur();
		selectPersonnelVisible.value = true;
	});
};
const departmentTree = ref();
onBeforeMount(() => {
	selectionPersonnel.value = [];
	getDepartment();
});
// 输入查询
let time: any;
watch(
	() => departmentName.value,
	() => {
		if (time) {
			clearTimeout(time);
			time = setTimeout(() => {
				getDepartment();
			}, 500);
		} else {
			time = setTimeout(() => {
				getDepartment();
			}, 500);
		}
	}
);
// 获取部门
const getDepartment = () => {
	treeFlag.value = false;
	getDepartmentTree({ parentId: -1, deptName: departmentName.value }).then((res) => {
		if (departmentName.value) {
			departmentTree.value = res.data.map((item: any) => {
				item.leaf = item.children ? false : true;
				return item;
			});
			treeFlag.value = true;
		} else {
			res.data.unshift({
				createTime: '',
				id: '',
				isLock: false,
				name: '全部人员',
				parentId: '',
				weight: 9999,
				leaf: true,
			});
			departmentTree.value = res.data.map((item: any) => {
				item.leaf = item.children ? false : true;
				return item;
			});
			treeFlag.value = true;
		}
	});
};
// 选择部门
const checkChangeTree = (data: any) => {
	state.queryForm.deptId = data.id;
	getDataList();
};
// 节点懒加载
const loadNode = async (node: Node, resolve: (data: Tree[]) => void, reject: () => void) => {
	if (node.level === 0) {
		return resolve(departmentTree.value);
	}
	if (node.level >= 1) {
		try {
			let res = await getDepartmentTree({ parentId: node.data.id });
			res.data.forEach((element: any) => {
				element.leaf = element.children ? false : true;
				return resolve(res.data);
			});
		} catch (error) {
			reject();
		}
	}
};
// 确认
const submit = () => {
	if (props.isbutton) {
		emit('change', selectionPersonnel.value);
		selectPersonnelVisible.value = false;
		return;
	}
	emit('update:orgList', selectionPersonnel.value);
	emit('change', selectionPersonnel.value);
	personnel.value = selectionPersonnel.value;
	selectPersonnelInfo.value = selectionPersonnel.value.map((item: any) => item?.id);
	selectPersonnelVisible.value = false;
};
</script>

<style lang="scss" scoped>
:deep(.el-input__wrapper) {
	box-shadow: 0 0 0 1px #dcdfe6 !important;
}
:deep(.el-tooltip__trigger) {
	box-shadow: 0 0 0 1px #dcdfe6 !important;
}
:deep(.el-tag__close) {
	display: none;
}

.selectPersonnelContent {
	width: 100%;
	height: 700px;
	display: flex;
	.PersonneTree {
		width: 350px;
		height: 100%;
		border: 1px solid #eee;
		border-radius: 10px;
		padding: 6px;
		overflow: hidden;
		overflow-y: auto;
		margin-right: 15px;
		.header {
			margin-bottom: 10px;
		}
	}
	.PersonneTable {
		flex: 1;
		height: 100%;
		overflow: hidden;
		overflow-y: auto;
		border: 1px solid #eee;
		padding: 6px;
		border-radius: 10px;
		margin-right: 15px;
		.header {
			margin: 10px 8px 18px;
		}
	}
	.PersonneInfo {
		padding: 10px;
		width: 350px;
		height: 100%;
		border: 1px solid #eee;
		padding: 6px;
		border-radius: 10px;
		overflow: hidden;
		overflow-y: auto;
		.header {
			padding: 0 6px;
			height: 30px;
			border-bottom: 1px solid #eee;
			display: flex;
			align-items: center;
			justify-content: space-between;
			span:nth-child(2) {
				color: rgb(183, 108, 108);
				cursor: pointer;
			}
		}
		ul {
			li {
				height: 25px;
				display: flex;
				align-items: center;
				justify-content: space-between;
				border: 1px solid #eee;
				border-radius: 6px;
				padding: 0 6px;
				margin: 5px 0;
				.icon {
					color: red;
					cursor: pointer;
				}
			}
			li:hover {
				background-color: #eee;
			}
		}
	}
}
</style>

selectPersonnel人员组件

文件位置:src\components\selectPersonnel\index.vue
描述:用于选择人员
参数:
1.multiple 类型 Boolean 是否多选
2.orgList 类型 Array v-model:orgList 绑定 选中的人员
3.placeholder 类型 String 描述
4.isbutton 类型 Boolean 是否自定义button
5.disabled 类型 Boolean 是否禁用
事件:
change(data)=>{} data为选中的人员 该事件只有在isbutton为true时生效

三、优化Tree新增引导线

<template>
	<template v-if="props.isbutton">
		<span @click="selectPersonnelVisible = true">
			<slot></slot>
		</span>
	</template>
	<el-select
		:disabled="disabled"
		v-if="!props.isbutton"
		ref="chooseProjectName"
		v-model="selectPersonnelInfo"
		multiple
		collapse-tags
		:placeholder="props.placeholder"
		style="width: 240px"
		@focus="handleTestTasks"
		@visible-change="visibleType"
	>
		<el-option v-for="item in personnel" :key="item.id" :label="item?.name" :value="item?.id" />
	</el-select>
	<el-dialog v-model="selectPersonnelVisible" append-to-body title="选择" width="60%" draggable>
		<div class="selectPersonnelContent">
			<div class="PersonneTree">
				<div class="header">
					<el-input v-model="departmentName" placeholder="请输入部门名称" :suffix-icon="Search" />
				</div>
				<el-tree
					class="tree-line"
					ref="treeRef"
					v-if="treeFlag"
					highlight-current
					style="max-width: 600px"
					:expand-on-click-node="false"
					:props="prop"
					:load="loadNode"
					:indent="0"
					lazy
					@current-change="checkChangeTree"
					><template #default="{ data }">
						<span class="custom-tree-node">
							<span class="tree-icon">
								<SvgIcon v-if="data.deptType === 0" name="local-all-personnel" :size="16" />
								<SvgIcon v-if="data.deptType === 1" name="local-dept3" :size="16" />
								<SvgIcon v-if="data.deptType === 2" name="local-company" :size="16" />
								<SvgIcon v-if="data.deptType === 3" name="local-construction-team2" :size="16" />
							</span>
							<span>{{ data.name }}</span>
						</span>
					</template>
				</el-tree>
			</div>
			<div class="PersonneTable">
				<div class="header">
					<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
						<el-form-item label="姓名" prop="name">
							<el-input placeholder="请输入姓名" v-model="state.queryForm.name" />
						</el-form-item>
						<el-form-item>
							<el-button icon="search" type="primary" @click="getDataList"> 查询 </el-button>
							<el-button icon="Refresh" @click="resetQuery">重置</el-button>
							<el-button @click="selectAllPersonnel" v-if="props.multiple">
								<SvgIcon name="local-all-personnel" :size="16" style="margin-right: 15px" /> 选中全部人员</el-button
							>
						</el-form-item>
					</el-form>
				</div>
				<el-table
					v-if="props.multiple"
					ref="multipleTableRef"
					:data="state.dataList"
					style="width: 100%"
					highlight-current-row
					@selection-change="handleSelectionChange"
					@select="onSelect"
					@select-all="onSelectAll"
					empty-text="请选择组织或更换组织"
				>
					<el-table-column type="selection" width="55" />
					<el-table-column label="用户名" show-overflow-tooltip
						><template #default="scope">{{ scope.row.username }}</template>
					</el-table-column>
					<el-table-column label="姓名" show-overflow-tooltip>
						<template #default="scope">{{ scope.row.name }}</template>
					</el-table-column>
				</el-table>
				<el-table
					v-if="!props.multiple"
					ref="multipleTableRef"
					:data="state.dataList"
					style="width: 100%"
					highlight-current-row
					empty-text="请选择组织或更换组织"
					@current-change="handleCurrentChange"
				>
					<el-table-column label="用户名" show-overflow-tooltip
						><template #default="scope">{{ scope.row.username }}</template>
					</el-table-column>
					<el-table-column label="姓名" show-overflow-tooltip>
						<template #default="scope">{{ scope.row.name }}</template>
					</el-table-column>
				</el-table>
				<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
			</div>
			<div class="PersonneInfo">
				<div class="header">
					<span>已选人员({{ selectionPersonnel.length }})</span>
					<span @click="clearPersonneInfo">清空</span>
				</div>
				<ul>
					<li v-for="(item, index) in selectionPersonnel" :key="index">
						{{ item?.name }}<el-icon class="icon" @click="delPersonneInfo(item)"><Delete /></el-icon>
					</li>
				</ul>
			</div>
		</div>
		<template #footer>
			<div class="dialog-footer">
				<el-button @click="selectPersonnelVisible = false">取消</el-button>
				<el-button type="primary" @click="submit"> 确认 </el-button>
			</div>
		</template>
	</el-dialog>
</template>

<script setup lang="ts" name="selectPersonnel">
import { BasicTableProps, useTable } from '/@/hooks/table';
import { getDepartmentTree, getSimpleUserPage, getSimpleUserList } from '/@/api/selectPersonnel/index';
import type Node from 'element-plus/es/components/tree/src/model/node';
import { Search } from '@element-plus/icons-vue';
const selectPersonnelInfo = ref([]) as any;
const selectPersonnelVisible = ref(false);
const chooseProjectName = ref();
const multipleTableRef = ref();
const queryRef = ref();
const departmentName = ref('');
const treeRef = ref();
const personnel = ref([]) as any;
const emit = defineEmits(['update:orgList', 'change']);
const treeFlag = ref(true);
const props = defineProps({
	multiple: {
		type: Boolean,
		default: () => false,
	},
	orgList: {
		type: Array,
		default: () => [],
	},
	placeholder: {
		type: String,
		default: '请选择人员',
	},
	isbutton: {
		type: Boolean,
		detalut: () => false,
	},
	disabled: {
		type: Boolean,
		default: () => false,
	},
});
interface Tree {
	name: string;
	leaf?: boolean;
}
const state: BasicTableProps = reactive<BasicTableProps>({
	queryForm: {},
	pageList: getSimpleUserPage,
});
//  table hook
const { currentChangeHandle, sizeChangeHandle, getDataList } = useTable(state);
const prop = {
	label: 'name',
	// children: 'children',
	isLeaf: 'leaf',
};
// 选中全部人员
const selectAllPersonnel = async () => {
	let res = await getSimpleUserList(state.queryForm);
	selectionPersonnel.value = res.data;
	if (selectionPersonnel.value.length) {
		state.dataList?.forEach((el: any) => {
			if (selectionPersonnel.value.find((item: any) => item.id === el.id)) {
				nextTick(() => {
					multipleTableRef.value?.toggleRowSelection(el, true);
				});
			}
		});
	}
};
// 多选
const multipleSelection = ref([]) as any;
// 人员
const selectionPersonnel = ref([]) as any;
const handleSelectionChange = (val: any) => {
	multipleSelection.value = val;
	// 去重
	val.forEach((el: any) => {
		if (!selectionPersonnel.value.find((item: any) => item.id === el.id)) {
			selectionPersonnel.value.push(el);
		}
	});
};
const onSelect = (rows: any, row: any) => {
	// selected true就是选中,0或者false是取消选中
	let selected = rows.length && rows.indexOf(row) !== -1;
	if (!selected) {
		selectionPersonnel.value.forEach((item: { id: '' }, i: number) => {
			if (row?.id == item.id) {
				nextTick(() => {
					selectionPersonnel.value.splice(i, 1);
				});
			}
		});
	}
};
const onSelectAll = (rows: any) => {
	if (!rows.length) {
		for (let index = 0; index < selectionPersonnel.value.length; index++) {
			const element = selectionPersonnel.value[index];

			if (state.dataList?.find((el: any) => el.id === element.id)) {
				selectionPersonnel.value.splice(index, 1);
				index--;
			}
		}
	}
};
// 单选
const handleCurrentChange = (row: any) => {
	multipleSelection.value = [row];
	// 去重
	if (!selectionPersonnel.value.find((item: any) => item?.id === row?.id)) {
		selectionPersonnel.value = [row];
	}
};
// 阻止下拉框  下拉事件
const handleTestTasks = () => {
	chooseProjectName.value.blur();
};
// 清空搜索条件
const resetQuery = () => {
	// 清空搜索条件
	queryRef.value?.resetFields();
	getDataList();
};
// 删除
const delPersonneInfo = (row: { id: '' }) => {
	state.dataList?.forEach((item: { id: '' }) => {
		if (row?.id == item.id) {
			nextTick(() => {
				multipleTableRef.value!.toggleRowSelection(item, false);
			});
			return;
		}
	});

	selectionPersonnel.value.forEach((item: { id: '' }, i: number) => {
		if (row?.id == item.id) {
			selectionPersonnel.value.splice(i, 1);
			return;
		}
	});
};
// 清空
const clearPersonneInfo = () => {
	nextTick(() => {
		multipleTableRef.value?.clearSelection();
	});
	selectionPersonnel.value = [];
};
// select 回显
watch(
	() => props.orgList,
	() => {
		personnel.value = [];
		selectPersonnelInfo.value = [];
		multipleSelection.value = [];
		// 多选回显
		if (props.orgList.length) {
			multipleSelection.value = props.orgList;
			selectionPersonnel.value = props.orgList;
			personnel.value = props.orgList;
			selectPersonnelInfo.value = multipleSelection.value.map((item: any) => item?.id);
		} else {
			selectionPersonnel.value = [];
		}
	},
	{ deep: true, immediate: true }
);
// 选中状态回显
watch(
	() => selectPersonnelVisible.value,
	() => {
		nextTick(() => {
			multipleTableRef.value?.clearSelection();
		});

		if (!selectPersonnelVisible.value) return;
		if (props.orgList.length) {
			nextTick(() => {
				state.dataList?.forEach((item: any) => {
					if (selectPersonnelInfo.value.includes(item.id)) {
						multipleTableRef.value?.toggleRowSelection(item, true);
					}
				});
			});
		} else {
			nextTick(() => {
				multipleTableRef.value?.clearSelection();
			});
		}
	}
);
// 切换回显
watch(
	() => state.dataList,
	() => {
		if (!props.multiple) return;
		if (selectionPersonnel.value.length) {
			state.dataList?.forEach((el: any) => {
				if (selectionPersonnel.value.find((item: any) => item.id === el.id)) {
					nextTick(() => {
						multipleTableRef.value?.toggleRowSelection(el, true);
					});
				}
			});
		}
	},
	{ immediate: true }
);
const visibleType = () => {
	// 弹框
	nextTick(() => {
		// 不显示下拉框
		chooseProjectName.value.blur();
		selectPersonnelVisible.value = true;
	});
};
const departmentTree = ref();
onBeforeMount(() => {
	selectionPersonnel.value = [];
	getDepartment();
});
// 输入查询
let time: any;
watch(
	() => departmentName.value,
	() => {
		if (time) {
			clearTimeout(time);
			time = setTimeout(() => {
				getDepartment();
			}, 500);
		} else {
			time = setTimeout(() => {
				getDepartment();
			}, 500);
		}
	}
);
// 获取部门
const getDepartment = () => {
	treeFlag.value = false;
	getDepartmentTree({ parentId: -1, deptName: departmentName.value }).then((res) => {
		if (departmentName.value) {
			departmentTree.value = res.data.map((item: any) => {
				item.leaf = item.children ? false : true;
				return item;
			});

			treeFlag.value = true;
		} else {
			res.data.unshift({
				createTime: '',
				id: '',
				isLock: false,
				name: '全部人员',
				parentId: '',
				weight: 9999,
				deptType: 0,
				leaf: true,
			});
			departmentTree.value = res.data.map((item: any) => {
				item.leaf = item.children ? false : true;
				return item;
			});
			treeFlag.value = true;
		}
	});
};
// 选择部门
const checkChangeTree = (data: any) => {
	state.queryForm.deptId = data.id;
	getDataList();
};
// 节点懒加载
const loadNode = async (node: Node, resolve: (data: Tree[]) => void, reject: () => void) => {
	if (node.level === 0) {
		resolve(departmentTree.value);
		nextTick(() => {
			// 展开子节点
			let node = treeRef.value.getNode(departmentTree.value[1]);
			node.expand();
			// node.expand(function () {
			// 	for (let i = 0; i < node.childNodes.length; i++) {
			// 		console.log(node.childNodes[i], '子节点');
			// 		if (node.childNodes[i].data.children) {
			// 			node.childNodes[i].expand();
			// 		}
			// 	}
			// });
		});
		return;
	}
	if (node.level >= 1) {
		try {
			let res = await getDepartmentTree({ parentId: node.data.id });
			res.data.forEach((element: any) => {
				element.leaf = element.children ? false : true;
			});
			return resolve(res.data);
		} catch (error) {
			reject();
		}
	}
};
// 确认
const submit = () => {
	if (props.isbutton) {
		emit('change', selectionPersonnel.value);
		selectPersonnelVisible.value = false;
		return;
	}
	emit('update:orgList', selectionPersonnel.value);
	emit('change', selectionPersonnel.value);
	personnel.value = selectionPersonnel.value;
	selectPersonnelInfo.value = selectionPersonnel.value.map((item: any) => item?.id);
	selectPersonnelVisible.value = false;
};
</script>

<style lang="scss" scoped>
:deep(.el-input__wrapper) {
	box-shadow: 0 0 0 1px #dcdfe6 !important;
}
:deep(.el-tooltip__trigger) {
	box-shadow: 0 0 0 1px #dcdfe6 !important;
}
:deep(.el-tag__close) {
	display: none;
}
.tree-icon {
	width: 20px;
	height: 20px;
	display: inline-block;
	margin-right: 8px;
}
.selectPersonnelContent {
	width: 100%;
	height: 700px;
	display: flex;
	.PersonneTree {
		width: 350px;
		height: 100%;
		border: 1px solid #eee;
		border-radius: 10px;
		padding: 6px;
		overflow: hidden;
		overflow-y: auto;
		margin-right: 15px;
		.header {
			margin-bottom: 10px;
		}
	}
	.PersonneTable {
		flex: 1;
		height: 100%;
		overflow: hidden;
		overflow-y: auto;
		border: 1px solid #eee;
		padding: 6px;
		border-radius: 10px;
		margin-right: 15px;
		.header {
			margin: 10px 8px 18px;
		}
	}
	.PersonneInfo {
		padding: 10px;
		width: 350px;
		border: 1px solid #eee;
		padding: 6px;
		border-radius: 10px;
		.header {
			padding: 0 6px;
			height: 30px;
			border-bottom: 1px solid #eee;
			display: flex;
			align-items: center;
			justify-content: space-between;
			span:nth-child(2) {
				color: rgb(183, 108, 108);
				cursor: pointer;
			}
		}
		ul {
			height: calc(100% - 30px);
			overflow: hidden;
			overflow-y: auto;
			li {
				height: 25px;
				display: flex;
				align-items: center;
				justify-content: space-between;
				border: 1px solid #eee;
				border-radius: 6px;
				padding: 0 6px;
				margin: 5px 0;
				.icon {
					color: red;
					cursor: pointer;
				}
			}
			li:hover {
				background-color: #eee;
			}
		}
	}
}
</style>
<style lang="scss">
// 重新修改tree组件样
.tree-line {
	.el-tree-node {
		position: relative;
		padding-left: 16px;
	}
	.el-tree-node__content {
		margin-top: 10px;
	}
	.el-tree-node__children {
		padding-left: 16px;
	}

	.el-tree-node::before {
		content: '';
		height: 105%;
		width: 1px;
		position: absolute;
		left: -3px;
		top: -26px;
		border-width: 1px;
		border-left: 1px dashed #52627c;
	}

	.el-tree-node::after {
		content: '';
		width: 24px;
		height: 20px;
		position: absolute;
		left: -3px;
		top: 12px;
		border-top: 1px dashed #52627c;
	}

	& > .el-tree-node::after {
		border-top: none;
	}
	& > .el-tree-node::before {
		border-left: none;
		border-width: 0px;
	}

	.el-tree-node__expand-icon {
		font-size: 18px;
		color: #000;
		&.is-leaf {
			color: transparent;
			// display: none;
		}
	}
}
</style>

四、二次优化

效果:

1.单选(利用复选框实现单选功能)

2.多选

3.完整代码

<template>
	<template v-if="props.isbutton">
		<span>
			<slot></slot>
		</span>
	</template>
	<el-select
		:disabled="disabled"
		v-if="!props.isbutton"
		ref="chooseProjectName"
		v-model="selectPersonnelInfo"
		multiple
		collapse-tags
		:placeholder="props.placeholder"
		:style="width"
		@focus="handleTestTasks"
		@visible-change="visibleType"
		suffix-icon="MoreFilled"
	>
		<el-option v-for="item in personnel" :key="item.id" :label="item?.name" :value="item?.id" />
	</el-select>
	<el-dialog v-model="selectPersonnelVisible" append-to-body title="选择" width="60%" draggable>
		<div class="selectPersonnelContent">
			<div class="PersonneTree">
				<div class="header">
					<el-input v-model="departmentName" placeholder="请输入部门名称" :suffix-icon="Search" clearable maxlength="30" show-word-limit />
				</div>
				<el-tree
					class="tree-line"
					ref="treeRef"
					v-if="treeFlag"
					highlight-current
					style="max-width: 600px"
					:expand-on-click-node="false"
					:props="prop"
					:load="loadNode"
					:indent="0"
					lazy
					@current-change="checkChangeTree"
					><template #default="{ data }">
						<span class="custom-tree-node">
							<span class="tree-icon">
								<SvgIcon v-if="data.deptType === 0" name="local-all-personnel" :size="16" />
								<SvgIcon v-if="data.deptType === 1" name="local-dept3" :size="16" />
								<SvgIcon v-if="data.deptType === 2" name="local-company" :size="16" />
								<SvgIcon v-if="data.deptType === 3" name="local-construction-team2" :size="16" />
							</span>
							<span>{{ data.name }}</span>
						</span>
					</template>
				</el-tree>
			</div>
			<div class="PersonneTable">
				<div class="header">
					<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
						<el-form-item label="姓名" prop="name">
							<el-input placeholder="请输入姓名" v-model="state.queryForm.name" clearable maxlength="30" show-word-limit />
						</el-form-item>
						<el-form-item>
							<el-button icon="search" type="primary" @click="getDataList"> 查询 </el-button>
							<!-- <el-button icon="Refresh" @click="resetQuery">重置</el-button> -->
							<el-button @click="selectAllPersonnel" v-if="props.multiple">
								<SvgIcon name="local-all-personnel" :size="16" style="margin-right: 15px" /> 选中全部人员</el-button
							>
						</el-form-item>
					</el-form>
				</div>
				<el-table
					ref="multipleTableRef"
					:data="state.dataList"
					style="width: 100%"
					@row-click="rowClick"
					@selection-change="handleSelectionChange"
					@select="onSelect"
					@select-all="onSelectAll"
					empty-text="请选择组织或更换组织"
					:class="!props.multiple ? 'hide-selection' : ''"
				>
					<el-table-column type="selection" width="55" />
					<el-table-column label="用户名" show-overflow-tooltip
						><template #default="scope">{{ scope.row.username }}</template>
					</el-table-column>
					<el-table-column label="姓名" show-overflow-tooltip>
						<template #default="scope">{{ scope.row.name }}</template>
					</el-table-column>
					<el-table-column label="手机号" show-overflow-tooltip>
						<template #default="scope">{{ scope.row.phone }}</template>
					</el-table-column>
				</el-table>
				<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
			</div>
			<div class="PersonneInfo">
				<div class="header">
					<span>已选人员({{ selectionPersonnel.length }})</span>
					<span @click="clearPersonneInfo">清空</span>
				</div>
				<ul>
					<li v-for="(item, index) in selectionPersonnel" :key="index">
						{{ item?.name }}<el-icon class="icon" @click="delPersonneInfo(item)"><Delete /></el-icon>
					</li>
				</ul>
			</div>
		</div>
		<template #footer>
			<div class="dialog-footer">
				<el-button @click="selectPersonnelVisible = false">取消</el-button>
				<el-button type="primary" @click="submit"> 确认 </el-button>
			</div>
		</template>
	</el-dialog>
</template>

<script setup lang="ts" name="selectPersonnel">
import { BasicTableProps, useTable } from '/@/hooks/table';
import { getDepartmentTree, getSimpleUserPage, getSimpleUserByName } from '../../api/components/selectPersonnel/index';
import type Node from 'element-plus/es/components/tree/src/model/node';
import { Search } from '@element-plus/icons-vue';
const selectPersonnelInfo = ref([]) as any;
const selectPersonnelVisible = ref(false);
const chooseProjectName = ref();
const multipleTableRef = ref();
const queryRef = ref();
const departmentName = ref('');
const treeRef = ref();
const personnel = ref([]) as any;
const emit = defineEmits(['update:orgList', 'change']);
const treeFlag = ref(true);
const props = defineProps({
	multiple: {
		type: Boolean,
		default: () => false,
	},
	orgList: {
		type: Array,
		default: () => [],
	},
	placeholder: {
		type: String,
		default: '请选择人员',
	},
	isbutton: {
		type: Boolean,
		detalut: () => false,
	},
	disabled: {
		type: Boolean,
		default: () => false,
	},
	source: {
		type: String,
		default: '',
	},
	width: {
		type: Object,
		default: () => ({ width: '240px' }),
	},
});
interface Tree {
	name: string;
	leaf?: boolean;
}
const state: BasicTableProps = reactive<BasicTableProps>({
	queryForm: {
		source: props.source,
	},
	pageList: getSimpleUserPage,
});
//  table hook
const { currentChangeHandle, sizeChangeHandle, getDataList } = useTable(state);
const prop = {
	label: 'name',
	// children: 'children',
	isLeaf: 'leaf',
};
// 点击行选中
const rowClick = (row: any) => {
	if (!selectionPersonnel.value.find((item: any) => item.id === row.id)) {
		nextTick(() => {
			multipleTableRef.value?.toggleRowSelection(row, true);
		});
	}
};
// 选中全部人员
const selectAllPersonnel = async () => {
	let res = await getSimpleUserByName(state.queryForm);
	selectionPersonnel.value = res.data;
	if (selectionPersonnel.value.length) {
		state.dataList?.forEach((el: any) => {
			if (selectionPersonnel.value.find((item: any) => item.id === el.id)) {
				nextTick(() => {
					multipleTableRef.value?.toggleRowSelection(el, true);
				});
			}
		});
	}
};
// 多选
const multipleSelection = ref([]) as any;
// 人员
const selectionPersonnel = ref([]) as any;
const handleSelectionChange = (val: any) => {
	if (props.multiple) {
		multipleSelection.value = val;
		// 去重
		val.forEach((el: any) => {
			if (!selectionPersonnel.value.find((item: any) => item.id === el.id)) {
				selectionPersonnel.value.push(el);
			}
		});
	} else {
		// multipleSelection.value = val[val.length - 1];
		if (!val.length) return;
		selectionPersonnel.value = [val[val.length - 1]];
		if (selectionPersonnel.value.length) {
			state.dataList?.forEach((el: any) => {
				if (selectionPersonnel.value.find((item: any) => item.id === el.id)) {
					nextTick(() => {
						multipleTableRef.value?.toggleRowSelection(el, true);
					});
				} else {
					nextTick(() => {
						multipleTableRef.value?.toggleRowSelection(el, false);
					});
				}
			});
		}
	}
};
const onSelect = (rows: any, row: any) => {
	// selected true就是选中,0或者false是取消选中
	let selected = rows.length && rows.indexOf(row) !== -1;
	if (!selected) {
		selectionPersonnel.value.forEach((item: { id: '' }, i: number) => {
			if (row?.id == item.id) {
				nextTick(() => {
					selectionPersonnel.value.splice(i, 1);
				});
			}
		});
	}
};
const onSelectAll = (rows: any) => {
	if (!rows.length) {
		for (let index = 0; index < selectionPersonnel.value.length; index++) {
			const element = selectionPersonnel.value[index];

			if (state.dataList?.find((el: any) => el.id === element.id)) {
				selectionPersonnel.value.splice(index, 1);
				index--;
			}
		}
	}
};
// 阻止下拉框  下拉事件
const handleTestTasks = () => {
	chooseProjectName.value.blur();
};
// // 清空搜索条件
// const resetQuery = () => {
// 	// 清空搜索条件
// 	queryRef.value?.resetFields();
// 	getDataList();
// };
// 删除
const delPersonneInfo = (row: { id: '' }) => {
	state.dataList?.forEach((item: { id: '' }) => {
		if (row?.id == item.id) {
			nextTick(() => {
				multipleTableRef.value!.toggleRowSelection(item, false);
			});
			return;
		}
	});

	selectionPersonnel.value.forEach((item: { id: '' }, i: number) => {
		if (row?.id == item.id) {
			selectionPersonnel.value.splice(i, 1);
			return;
		}
	});
};
// 清空
const clearPersonneInfo = () => {
	nextTick(() => {
		multipleTableRef.value?.clearSelection();
	});
	selectionPersonnel.value = [];
};
// select 回显
watch(
	() => props.orgList,
	() => {
		// 校验v-model 数据类型
		if (!Array.isArray(props.orgList)) return;
		personnel.value = [];
		selectPersonnelInfo.value = [];
		multipleSelection.value = [];
		nextTick(() => {
			// 多选回显
			if (props.orgList.length) {
				multipleSelection.value = props.orgList;
				selectionPersonnel.value = props.orgList;
				personnel.value = props.orgList;
				selectPersonnelInfo.value = multipleSelection.value.map((item: any) => item?.id);
			} else {
				selectionPersonnel.value = [];
			}
		});
	},
	{ deep: true, immediate: true }
);
// 选中状态回显
watch(
	() => selectPersonnelVisible.value,
	() => {
		nextTick(() => {
			multipleTableRef.value?.clearSelection();
		});

		if (!selectPersonnelVisible.value) return;
		if (props.orgList.length) {
			nextTick(() => {
				state.dataList?.forEach((item: any) => {
					if (selectPersonnelInfo.value.includes(item.id)) {
						multipleTableRef.value?.toggleRowSelection(item, true);
					}
				});
			});
		} else {
			nextTick(() => {
				selectionPersonnel.value = [];
				multipleTableRef.value?.clearSelection();
				let node = treeRef.value.getNode(departmentTree.value[1]);
				node.expand();
			});
		}
	}
);
// 切换回显
watch(
	() => state.dataList,
	() => {
		if (!props.multiple) return;
		if (selectionPersonnel.value.length) {
			state.dataList?.forEach((el: any) => {
				if (selectionPersonnel.value.find((item: any) => item.id === el.id)) {
					nextTick(() => {
						multipleTableRef.value?.toggleRowSelection(el, true);
					});
				}
			});
		}
	},
	{ immediate: true }
);
const visibleType = () => {
	// 弹框
	nextTick(() => {
		// 不显示下拉框
		chooseProjectName.value.blur();
		selectPersonnelVisible.value = true;
	});
};
const departmentTree = ref();
onBeforeMount(() => {
	selectionPersonnel.value = [];
	getDepartment();
});
// 输入查询
let time: any;
watch(
	() => departmentName.value,
	() => {
		if (time) {
			clearTimeout(time);
			time = setTimeout(() => {
				getDepartment();
			}, 500);
		} else {
			time = setTimeout(() => {
				getDepartment();
			}, 500);
		}
	}
);
// 获取部门
const getDepartment = () => {
	treeFlag.value = false;
	getDepartmentTree({ parentId: -1, deptName: departmentName.value }).then((res) => {
		if (departmentName.value) {
			departmentTree.value = res.data.map((item: any) => {
				item.leaf = item.children ? false : true;
				return item;
			});

			treeFlag.value = true;
		} else {
			res.data.unshift({
				createTime: '',
				id: '',
				isLock: false,
				name: '全部人员',
				parentId: '',
				weight: 9999,
				deptType: 0,
				leaf: true,
			});
			departmentTree.value = res.data.map((item: any) => {
				item.leaf = item.children ? false : true;
				return item;
			});
			treeFlag.value = true;
		}
	});
};
// 选择部门
const checkChangeTree = (data: any) => {
	state.queryForm.deptId = data.id;
	getDataList();
};
// 节点懒加载
const loadNode = async (node: Node, resolve: (data: Tree[]) => void, reject: () => void) => {
	if (node.level === 0) {
		resolve(departmentTree.value);
		nextTick(() => {
			// 展开子节点
			let node = treeRef.value.getNode(departmentTree.value[1]);
			node.expand();
			// node.expand(function () {
			// 	for (let i = 0; i < node.childNodes.length; i++) {
			// 		console.log(node.childNodes[i], '子节点');
			// 		if (node.childNodes[i].data.children) {
			// 			node.childNodes[i].expand();
			// 		}
			// 	}
			// });
		});
		return;
	}
	if (node.level >= 1) {
		try {
			let res = await getDepartmentTree({ parentId: node.data.id });
			res.data.forEach((element: any) => {
				element.leaf = element.children ? false : true;
			});
			return resolve(res.data);
		} catch (error) {
			reject();
		}
	}
};
// 确认
const submit = () => {
	if (props.isbutton) {
		emit('change', selectionPersonnel.value);
		selectPersonnelVisible.value = false;
		return;
	}
	emit('update:orgList', selectionPersonnel.value);
	emit('change', selectionPersonnel.value);
	personnel.value = selectionPersonnel.value;
	selectPersonnelInfo.value = selectionPersonnel.value.map((item: any) => item?.id);
	selectPersonnelVisible.value = false;
};
// 打开弹窗
const openDialog = () => {
	selectPersonnelVisible.value = true;
	getDataList();
};
defineExpose({ openDialog });
</script>

<style lang="scss" scoped>
:deep(.hide-selection .el-table__header-wrapper tr .el-table__cell .el-checkbox) {
	display: none;
}
:deep(.el-input__wrapper) {
	box-shadow: 0 0 0 1px #dcdfe6 !important;
}
:deep(.el-tooltip__trigger) {
	box-shadow: 0 0 0 1px #dcdfe6 !important;
}
:deep(.el-tag__close) {
	display: none;
}
.tree-icon {
	width: 20px;
	height: 20px;
	display: inline-block;
	margin-right: 8px;
}
.selectPersonnelContent {
	width: 100%;
	height: 700px;
	display: flex;
	.PersonneTree {
		width: 350px;
		height: 100%;
		border: 1px solid #eee;
		border-radius: 10px;
		padding: 6px;
		overflow: hidden;
		overflow-y: auto;
		margin-right: 15px;
		.header {
			margin-bottom: 10px;
		}
	}
	.PersonneTable {
		flex: 1;
		height: 100%;
		overflow: hidden;
		overflow-y: auto;
		border: 1px solid #eee;
		padding: 6px;
		border-radius: 10px;
		margin-right: 15px;
		.header {
			margin: 10px 8px 18px;
		}
	}
	.PersonneInfo {
		padding: 10px;
		width: 350px;
		border: 1px solid #eee;
		padding: 6px;
		border-radius: 10px;
		.header {
			padding: 0 6px;
			height: 30px;
			border-bottom: 1px solid #eee;
			display: flex;
			align-items: center;
			justify-content: space-between;
			span:nth-child(2) {
				color: rgb(183, 108, 108);
				cursor: pointer;
			}
		}
		ul {
			height: calc(100% - 30px);
			overflow: hidden;
			overflow-y: auto;
			li {
				height: 25px;
				display: flex;
				align-items: center;
				justify-content: space-between;
				border: 1px solid #eee;
				border-radius: 6px;
				padding: 0 6px;
				margin: 5px 0;
				.icon {
					color: red;
					cursor: pointer;
				}
			}
			li:hover {
				background-color: #eee;
			}
		}
	}
}
</style>

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值