Vue3 + TS + Element-Plus 封装Tree组件 《亲测可用》

9a69fede8b2044a79dd834e3e48f20b4.png前期回顾f8e3cc1a0f694ac2b665ca2ad14c49d7.png

Vite + Vue3 + Ts 《企业级项目》二次封装 el-table、el-pagination、el-tooltip、el-dialog_vue后台管理系统需要二次封装的组件有哪些_彩色之外的博客-CSDN博客封装的功能有哪些?分页、表格排序、文字居中、溢出隐藏、操作列、开关、宽、最小宽、type类型(selection/index/expand)、格式化 、不同页面不同操作列、vuex、vue持久化插件、(此处没有接口所以用到,还涉及了query与params传值区别)子组件说思路:data数据请求接口拿到,表头数据一般也是后台接口,如没,前台可自定义自己写......_vue后台管理系统需要二次封装的组件有哪些https://blog.csdn.net/m0_57904695/article/details/125613767?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168906559516800227493706%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=168906559516800227493706&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-5-125613767-null-null.268^v1^koosearch&utm_term=%E5%B0%81%E8%A3%85&spm=1018.2226.3001.4450

目录

?  子组件:

?  父组件:

??  谢谢观看

 文章来源地址https://uudwc.com/A/LRGpM


 

 为了全局使用类型,可以新建

// tree
declare interface Tree {
	id: number;
	label: string;
	children?: Tree[];
	[key: string]: any;
}

?  子组件:

<template>
	<div>
		<el-button @click="addNode" type="primary">添加节点</el-button>
		<el-button @click="removeNode" type="danger">删除节点</el-button>
		<div class="tree-container">
			<el-tree
				class="tree-line"
				ref="treeRef"
				:indent="0"
				node-key="id"
				:data="treeData"
				:props="defaultProps"
				:check-strictly="checkStrictly"
				:show-checkbox="isShowCheckbox"
				:check-on-click-node="checkOnClickNode"
				:default-expand-all="defaultExpandAll"
				:draggable="isDraggable"
				:allow-drag="allowDrag"
				:allow-drop="allowDrop"
				@node-drag-end="handleDragEnd"
				@node-click="handleNodeClick"
				@node-contextmenu="editNode"
				@check-change="getCheckedAllNodes"
			>
				<template #default="{ node }">
					<i :class="checkIconByNodeLevel(node)" />
					<input
						v-if="showIpt && node.label === curNodLabel"
						ref="inputRef"
						type="text"
						:value="node.label"
						@blur="showIpt = false"
						@keyup.enter="updateNodeLabel($event, node)"
					/>
					<span v-else>{{ node.label }}</span>
				</template>
			</el-tree>
		</div>
	</div>
</template>

<script setup lang="ts">
import { nextTick, ref } from 'vue';
import type Node from 'element-plus/es/components/tree/src/model/node';
const showIpt = ref<boolean>(false); // 是否显示输入框
const curNodLabel = ref<string>(); // 记录右键点击的节点
const inputRef = ref(); // 输入框实例

const treeRef = ref(); // 树实例
// 默认配置
const defaultProps = {
	children: 'children',
	label: 'label',
};
// 判断节点能否被放置 如果返回 false ,节点不能被放置
const allowDrop = () => true;
// 判断节点能否被拖拽 如果返回 false ,节点不能被拖动
const allowDrag = () => true;

// 子组件事件发送
const emits = defineEmits(['eCurNode', 'eCheckedNodes', 'eSaveNodes']);

// 接受父组件传递过来的数据
const props = defineProps({
	// 树型数据
	treeData: {
		type: Array,
		default: () => [],
	},
	// 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法
	checkStrictly: {
		type: Boolean,
		default: () => false,
	},
	// 是否显示复选框
	isShowCheckbox: {
		type: Boolean,
		default: () => true,
	},
	// 选中节点时是否选中复选框
	checkOnClickNode: {
		type: Boolean,
		default: () => true,
	},
	// 是否默认展开所有节点
	defaultExpandAll: {
		type: Boolean,
		default: () => true,
	},
	// 是否开启拖拽节点功能
	isDraggable: {
		type: Boolean,
		default: () => false,
	},
});

// 点击节点时触发
const handleNodeClick = (data: Tree) => {
	// console.log('点击节点时触发 ? ==>:', data);
	emits('eCurNode', data);
};

// 删除节点
const removeNode = () => {
	const checkedNodes = treeRef.value.getCheckedNodes();
	if (checkedNodes.length === 0) return alert('请至少勾选一项才能删除节点');
	for (const node of checkedNodes) {
		// 根据节点的id删除节点
		nextTick(() => {
			treeRef.value.remove(node.id, false);
			// 根据接口重新获取树型数据
		});
	}
};

// 右击节点时触发
const editNode = (event: MouseEvent, node: Node) => {
	event.preventDefault();
	curNodLabel.value = node.label;
	showIpt.value = true;
	nextTick(() => {
		inputRef.value.focus();
	});
};

// 更新节点的label
const updateNodeLabel = (e: Event, node: Tree) => {
	const target = e.target as HTMLInputElement;
	// 递归树 如果target.value有重复的label,就不允许修改
	if (isValueInTree(props.treeData, target.value)) return alert('该节点已存在');
	// 浅拷贝只会影响引用类型的属性,而不会影响基本类型的属性。当浅拷贝一个对象时,基本类型的属性会被复制而不是引用
	// 浅拷贝只有是引用类型才会 两个对象相互影响,如果是基本类型不会互相影响
	node = Object.assign({}, node);
	node.data.label = target.value;
	showIpt.value = false;
};
function isValueInTree(data: string | any[], value: string) {
	for (let i = 0; i < data.length; i++) {
		if (data[i].label === value) {
			return true; // 如果找到匹配项,则返回 true
		}
		// 如果当前节点有子节点,则递归调用遍历子节点
		if (Array.isArray(data[i].children)) {
			if (isValueInTree(data[i].children, value)) {
				return true; // 如果在子节点中找到匹配项,则返回 true
			}
		}
	}
	return false; // 如果遍历完所有节点都没有找到匹配项,则返回 false
}

// 新增节点
const addNode = () => {
	const checkedNodes = treeRef.value.getCheckedNodes();

	if (checkedNodes.length === 0) return alert('请至少勾选一项才能添加节点');

	const nodeName = prompt('请输入节点名称');
	if (!nodeName) return;

	if (isValueInTree(props.treeData, nodeName)) return alert('该节点已存在');

	for (const parentNode of checkedNodes) {
		const newNode = {
			id: props.treeData.length + 1,
			label: nodeName,
		};
		if (!parentNode.children) {
			parentNode.children = [];
		}
		parentNode.children.push(newNode);
	}
};

// 结束拖拽
const handleDragEnd = (dropNode: Node) => {
	if (!dropNode) return;
	if (props.isDraggable === false) return;
	// 保存节点
	saveNode();
};

function saveNode() {
	emits('eSaveNodes', props.treeData);
}

// 复选框改变
const getCheckedAllNodes = (data: Tree, isSelected: boolean) => {
	if (!props.isShowCheckbox) return;
	// 获取所有选中的节点
	const checkedNodes = treeRef.value.getCheckedNodes();
	// 获取所有半选中的节点
	const halfCheckedNodes = treeRef.value.getHalfCheckedNodes();
	// data: 当前节点的数据
	// isSelected: 当前节点是否被选中
	// checkedNodes: 所有选中的节点
	// halfCheckedNodes: 所有半选中的节点
	emits('eCheckedNodes', data, isSelected, checkedNodes, halfCheckedNodes);
};

// 根据节点层级显示不同的图标
const checkIconByNodeLevel = (node: {
	childNodes: [];
	expanded: boolean;
	data: { id: number };
}) => {
	if (node.childNodes.length === 0) return 'iconfont icon-24gl-fileEmpty';
	return node.expanded ? 'iconfont icon-wenjianzhankai' : 'iconfont icon-jian';
};

defineExpose({
	treeRef,
	removeNode,
	addNode,
});
</script>

<style lang="scss" scoped>
@import url('/@/myIcon/iconfont.css');
.tree-container {
	width: 20%;
	height: calc(100vh - 130px);
	background-color: #fff;
	overflow-y: auto;
}
// 树样式
.tree-line {
	::v-deep(.el-tree-node) {
		position: relative;
		// padding-left: 10px; // 缩进量
	}
	::v-deep(.el-tree-node__children) {
		padding-left: 16px; // 缩进量
	}
	// 竖线
	::v-deep(.el-tree-node::before) {
		content: '';
		width: 22px;
		height: 20px;
		position: absolute;
		left: -3px;
		top: -28px;
		border-width: 1px;
		border-left: 1px dashed #ccc;
	}
	// 当前层最后⼀个节点的竖线⾼度固定
	::v-deep(.el-tree-node:last-child::before) {
		height: 38px; // 可以⾃⼰调节到合适数值
	}
	// 横线
	::v-deep(.el-tree-node::after) {
		content: '';
		width: 22px;
		height: 20px;
		position: absolute;
		left: -3px;
		top: 11px;
		border-width: 1px;
		border-top: 1px dashed #ccc;
	}
	// 去掉最顶层的虚线,放最下⾯样式才不会被上⾯的覆盖了
	& > ::v-deep(.el-tree-node::after) {
		border-top: none;
	}
	& > ::v-deep(.el-tree-node::before) {
		border-left: none;
	}
	// 展开关闭的icon
	::v-deep(.el-tree-node__expand-icon) {
		font-size: 16px;
		// 叶⼦节点(⽆⼦节点)
		::v-deep(&.is-leaf) {
			color: transparent;
			display: none;
		}
	}
}
</style>

?  父组件:

<template>
	<div>
		<zw-tree :treeData="state.treeData" isDraggable @eSaveNodes="onSaveNodes" />
	</div>
</template>

<script setup lang="ts">
import { reactive } from 'vue';
const state = reactive({
	// 树型数据
	treeData: [
		{
			id: 1,
			label: '一级 1',
			children: [
				{
					id: 4,
					label: '二级 1-1',
					children: [
						{
							id: 9,
							label: '三级 1-1-1',
						},
						{
							id: 10,
							label: '三级 1-1-2',
						},
					],
				},
			],
		},
		{
			id: 2,
			label: '一级 2',
			children: [
				{
					id: 5,
					label: '二级 2-1',
				},
				{
					id: 6,
					label: '二级 2-2',
				},
			],
		},
		{
			id: 3,
			label: '一级 3',
			children: [
				{
					id: 7,
					label: '二级 3-1',
				},
				{
					id: 8,
					label: '二级 3-2',
				},
			],
		},
	],
});

function onSaveNodes(data: Tree) {
	console.log(data);
}
</script>

更多的el-tree看这里 ??  自定义《element-UI》el-tree 的样式 、亲测管用_自定义《element-UI》el-tree 的样式 、亲测管用_https://blog.csdn.net/m0_57904695/article/details/123514519?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168906618216800211567162%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=168906618216800211567162&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-123514519-null-null.268^v1^koosearch&utm_term=el-tree&spm=1018.2226.3001.4450彩色之外的博客-csdn博客<>

更多的el-table看这里  ? 

点击《el-table》让选中的行变色,亲测实用_彩色之外的博客-CSDN博客公司各种需求又来了,直接看下面文吧,一看就懂就不在说需求了,此时我觉得我的表情包是【我就像是一个小朋友站在路标下满头的问号】亲测管用,希望可以帮助到大家https://blog.csdn.net/m0_57904695/article/details/123722382?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168906621616782425128470%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=168906621616782425128470&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-9-123722382-null-null.268^v1^koosearch&utm_term=%E8%A1%A8%E6%A0%BC&spm=1018.2226.3001.4450

??  谢谢观看

7730e2bd39d64179909767e1967da702.jpeg

 _______________________________  期待再见  _______________________________ 

 

原文地址:https://blog.csdn.net/m0_57904695/article/details/131664157

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请联系站长进行投诉反馈,一经查实,立即删除!

h
上一篇 2023年07月16日 06:13
【Android知识笔记】系统进程(一)
下一篇 2023年07月16日 06:15