Layui树形表格组件的实现

效果图:
在这里插入图片描述
在这里插入图片描述

一、Layui树形组件:treetable.js

可在layui下创建一个文件夹保存组件的js代码,以便引用。

// 基于layui table的树形表格实现,只支持页面仅有一个表格的情况
layui.define(['table', 'util'], function(exports) {
	var $ = layui.$,
		util = layui.util,
		table = layui.table;

	var ICON_FILE = 'layui-icon-file',
		ICON_RIGHT = 'layui-icon-triangle-r',
		ICON_DOWN = 'layui-icon-triangle-d',
		TABLE_VIEW = '.layui-table-view';
	var treetable = {
		config: {
			tempTop: 0,
			currIndex: -1,
			field: ''
		}, //全局配置项
		render: function(options) {
			var that = this;
			that.config = $.extend({}, that.config, options);
			render(options);
		},
		renderNode: function(node, data) {
			this.config.field = node.field;
			return renderNode(node, data);
		},
		done: function(datas) {
			var that = this;
			that.config.data = datas;
			done(that.config);
		},
		add: function(param) {
			add(this.config, param);
		},
		del: function(param) {
			del(this.config, param);
		},
		expandAll: function(tableID, successEvent) {
			this.config.successEvent = successEvent;
			expandAll(this.config, tableID);
		},
		foldAll: function(tableID) {
			foldAll(tableID);
		}
	};
	var render = function() {
		var that = treetable,
			options = that.config;
		var data = {
			id: 0
		};
		options.level = 0;
		options.data = data;
		if (options.load) {
			options.load(options, function(data) {
				$.each(data, function(index, item) {
					item.level = 1; //菜单级别,初始化时level=1:一级
				});
				table.reload(options.id, {
					data: data
				});
			});
		}
	};
	var renderNode = function(node, data) {
		var name = node.field;
		var spread = data.spread && true;
		let result = '<i class="layui-icon layui-treetable-node-op layui-icon-';
		let icon = 'file';
		let icon2 = '';
		let event = '';
		if (!data.leaf) {
			icon = spread ? 'triangle-d' : 'triangle-r';
			icon2 = ' <i class="layui-icon layui-icon-layer"></i>';
			event = ' lay-op-event="toggle"';
		}
		var style = '" style="margin-left: ' + (data.left || 0) + 'px"';
		var fname = data[name] || '';
		return result + icon + style + event + '></i>' + icon2 + '&nbsp;&nbsp;<span class="layui-treetable-node-span">' +
			fname + '</span>';
	};
	var done = function(config) {
		var rows = config.data;
		var tableID = config.id;
		var tempTop = config.tempTop;
		var currIndex = config.currIndex;
		var trs = $('[lay-id="' + tableID + '"] .layui-table-main tr');
		//新增行、展开行被选中
		if (currIndex > -1) {
			trs.eq(currIndex).addClass('layui-table-click').siblings().removeClass(
				'layui-table-click');
		}
		for (let i = 0; i < rows.length; i++) {
			//隐藏加载前的行
			if (rows[i].hide) {
				trs.eq(i).addClass('layui-hide');
			}
			//添加级别
			trs.eq(i).attr('lay-level', rows[i].level);
		}
		//重回到上次浏览位置
		$("[lay-id='" + tableID + "']").find(".layui-table-main").animate({
			scrollTop: tempTop
		}, 0);
	}
	var add = function(config, param) {
		var tableID = param.elem;
		var currTr = param.tr; //
		var data = param.data;
		var field = config.field;
		//叶子
		//获取新ID
		//获取选中行
		var rowIndex;
		var oldData = table.cache[tableID];
		var newData = {
			level: 0,
			parent: 0,
			leaf: true
		};
		$.extend(newData, data);
		if (currTr.length == 0) {
			//在末尾添加行
			rowIndex = oldData.length;
			config.currIndex = oldData.length;
			oldData.push(newData);
		} else {
			var currIndex = currTr.eq(0).data('index');
			//在选中行子集中末尾插入行
			config.currIndex = currIndex;
			var rowData = oldData[currIndex];
			var opIcon = currTr.find('[data-field="' + field + '"] .layui-icon').eq(0);
			var left = Number(opIcon.css("margin-left").replace('px', ''));
			var level = rowData.level;
			var id = rowData.id;
			//改变操作列图标
			if (opIcon.hasClass('layui-icon-file')) {
				//文件图标改为可点击图标
				rowData.leaf = false;
				rowData.spread = true;
				rowData.loaded = true;
			} else if (opIcon.hasClass('layui-icon-triangle-r')) {
				//调用点击展开事件
				opIcon.trigger('click');
			}
			//同级的下一个节点即为新增节点的下标,如果同级没有下一个节点,则上级的下一节点
			var maxIndex = config.currIndex + 1;
			var breakFlag = false;
			for (let i = maxIndex; i < oldData.length; i++) {
				if (oldData[i].level <= level) {
					maxIndex = i;
					breakFlag = true;
					break;
				}
			}
			if (!breakFlag) {
				maxIndex = oldData.length;
			}
			rowIndex = maxIndex;
			config.currIndex = maxIndex;
			newData.left = left + 20;
			newData.level = level + 1;
			newData.parent = rowData.id;
			oldData.splice(rowIndex, 0, newData); //插入
		}
		var rowHeight = $("[lay-id='" + tableID + "']").find(".layui-table-main").find('tr').height() + 1;
		config.tempTop = $("[lay-id='" + tableID + "']").find(".layui-table-main").scrollTop() + rowHeight;
		table.reload(tableID, {
			data: oldData
		});
	}
	var del = function(config, param) {
		var tableID = param.elem;
		var rowIndex = param.index;
		var onlyLeaf = param.onlyLeaf;
		//获取选中行
		var currTr = $("[lay-id='" + tableID + "'] .layui-table-main").find("tr").eq(rowIndex);
		var oldData = table.cache[tableID];
		var rowData = oldData[rowIndex];
		if (!rowData) {
			return;
		}
		var pid = rowData.parent;
		//如果只能删除叶子节点
		if (onlyLeaf) {
			//判断是否是叶子节点
			if (rowData.leaf) {
				oldData.splice(rowIndex, 1);
			}
		} else {
			//删除选中节点及子节点
			var ids = [];
			setDeleteIds(rowData.id);
			oldData = oldData.filter(function(v) {
				return ids.indexOf(v.id) == -1;
			});

			function setDeleteIds(id) {
				ids.push(id);
				//是否有子节点
				var children = oldData.filter(function(v) {
					return v.parent == id;
				});
				if (children.length > 0) {
					for (var i = 0; i < children.length; i++) {
						setDeleteIds(children[i].id);
					}
				}
			}
		}
		if (pid != 0) {
			//检查父节点是否还有子节点
			var siblings = oldData.filter(function(v) {
				return v.parent == pid;
			});
			if (siblings.length == 0) {
				var parentData = oldData.filter(function(v) {
					return v.id == pid;
				})[0];
				oldData[parentData.LAY_TABLE_INDEX].leaf = true;
			}
		}
		config.tempTop = $("[lay-id='" + tableID + "']").find(".layui-table-main").scrollTop();
		table.reload(tableID, {
			data: oldData
		});
	}
	var expandAll = function(options, tableID) {
		options.expandAll = true;
		var ICONS = {
			ICON_ADD: 'layui-icon-triangle-r',
			ICON_SUB: 'layui-icon-triangle-d',
			ICON_FILE: 'layui-icon-file'
		};
		tableID = '#' + tableID;
		var elem = $(tableID).next();
		if (options.expanded) {
			//显示所有行
			elem.find('tr.layui-hide').removeClass('layui-hide');
			//将折叠图标变为展开图标
			elem.find('.' + ICONS.ICON_ADD).addClass(ICONS.ICON_SUB).removeClass(ICONS.ICON_ADD);
			//回调方法
			if (options.successEvent) {
				options.successEvent();
			}
		} else {
			//触发展开图标的点击事件
			elem.find('.' + ICONS.ICON_ADD).addClass('spread').trigger('click');
			//设置定时任务,每0.5秒检查是否有未展开节点
			var count = 0;
			var interval = setInterval(function(e) {
				elem = $(tableID).next();
				count++;
				var addCount = elem.find('.' + ICONS.ICON_ADD).not('.spread').length;
				if (addCount == 0) {
					// 已全部展开标志
					options.expanded = true;
					delete options.expandAll;
					clearInterval(interval);
					//回调方法
					if (options.successEvent) {
						options.successEvent();
					}
				}else{
					elem.find('.' + ICONS.ICON_ADD).addClass('spread').trigger('click');
				}
			}, 200);
		}
	}
	var foldAll = function(tableID) {
		// 将缓存数据设置为收缩
		var datas = table.cache[tableID];
		for (var i = 0; i < datas.length; i++) {
			var item = datas[i];
			if (item.spread) {
				item.spread = false;
			}
			if (item.level > 1) {
				item.hide = true;
			}
		}
		var layBody = $('[lay-id="' + tableID + '"] .layui-table-main');
		// 隐藏除一级节点外的所有节点
		layBody.find('tr').not('[lay-level="1"]').addClass('layui-hide');
		// 将已展开节点图标变为未展开
		layBody.find('.' + ICON_DOWN).addClass(ICON_RIGHT).removeClass(ICON_DOWN);
		// 移除选中
		layBody.find('.layui-table-click').removeClass('layui-table-click');
		// 重新调整表格尺寸
		table.resize(tableID);
	}
	util.event("lay-op-event", {
		toggle: function(obj) { // 伸缩图标点击事件
			var config = treetable.config;
			//获取DOM元素
			var tableID = $(obj).parents(TABLE_VIEW).attr('lay-id');
			var tr = $(obj).parents('tr').eq(0);
			var td = $(obj).parents('td').eq(0);
			var rowIndex = tr.data('index');
			var rowData = table.cache[tableID][rowIndex];
			config.tempTop = $('[lay-id="' + tableID + '"] .layui-table-main').scrollTop();
			if (!rowData.loaded) { //未加载过
				rowData.loaded = true; //设为已加载
				rowData.spread = true; //设为已展开
				var left = Number($(obj).css('margin-left').replace('px', ''));
				var level = rowData.level || 0;
				//未加载过
				var node = {
					level: level,
					data: rowData
				}
				config.load(node, function(data) {
					var oldData = table.cache[tableID];
					for (let i = 0; i < data.length; i++) {
						let item = $.extend(data[i], {
							left: left + 20,
							level: level + 1
						});
						oldData.splice(rowIndex + i + 1, 0, item);
					}
					currIndex = rowIndex;
					table.reload(tableID, {
						data: oldData
					});
				});

				return;
			}
			//显示隐藏标志
			var showFlag = $(obj).hasClass(ICON_RIGHT); //true-显示
			var pid = rowData.id;
			if (showFlag) {
				$(obj).addClass(ICON_DOWN).removeClass(ICON_RIGHT);
				showChildren(pid);
			} else {
				$(obj).addClass(ICON_RIGHT).removeClass(ICON_DOWN);
				hideChildren(pid);
			}
			rowData.spread = showFlag;
			//重载尺寸
			table.resize(tableID);
		}

	});
	//展开
	var showChildren = function(pid) {
		//获取显示子菜单
		var children = table.cache[tableID].filter(function(v) {
			return v.parent == pid;
		});
		var trs = $('[lay-id="' + tableID + '"] .layui-table-main tr');
		for (let i = 0; i < children.length; i++) {
			trs.eq(children[i].LAY_TABLE_INDEX).removeClass('layui-hide');
			children[i].hide = false;
			//如果有子菜单,则显示子菜单
			if (children[i].spread && !children[i].leaf) {
				showChildren(children[i].id);
			}
		}
	}
	//收缩
	var hideChildren = function(pid) {
		//获取隐藏子菜单
		var children = table.cache[tableID].filter(function(v) {
			return v.parent == pid;
		});
		var trs = $('[lay-id="' + tableID + '"] .layui-table-main tr');
		for (let i = 0; i < children.length; i++) {
			trs.eq(children[i].LAY_TABLE_INDEX).addClass('layui-hide');
			children[i].hide = true;
			//如果有子菜单,则隐藏子菜单
			if (children[i].spread && !children[i].leaf) {
				hideChildren(children[i].id);
			}
		}
	}

	//输出treetable接口
	exports('treetable', treetable);
});

二、组件测试代码:treetableDemo.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<link rel="stylesheet" href="../layui/css/layui.css" />
		<!-- <link rel="stylesheet" href="../layui/css/layui-extend.css" /> -->
		<title></title>
		<style>
			/* 树形表格树下拉节点样式 */
			.layui-treetable-node-op{
				position: relative;
				bottom: 1px;
			}
			.layui-treetable-node-span{
				position: relative;
				bottom: 2px;
			}
		</style>
	</head>
	<body>
		<div class="layui-fluid">
			<div class="layui-row">
				<div class="layui-btn-group">
					<button class="layui-btn layui-btn-sm" lay-event="add"><i class="layui-icon layui-icon-add-1"></i> 新增</button>
					<button class="layui-btn layui-btn-sm" lay-event="del"><i class="layui-icon layui-icon-delete"></i> 删除</button>
					<button class="layui-btn layui-btn-sm" lay-event="expandAll"><i class="layui-icon layui-icon-down"></i> 全部展开</button>
					<button class="layui-btn layui-btn-sm" lay-event="foldAll"><i class="layui-icon layui-icon-up"></i> 全部折叠</button>
				</div>
			</div>
			<div class="layui-row">
				<table class="layui-hide" id="table" lay-filter="table"></table>
			</div>
		</div>
	</body>
	<script src="../layui/layui.js"></script>
	<script>
		layui.extend({
			treetable: '../layui/extend/treetable'
		});
		var tableID = 'table';
		/******************** layui.use初始化 *************************/
		layui.use(['table', 'treetable', 'util'], function() {
			var $ = layui.$,
				table = layui.table,
				util = layui.util,
				treetable = layui.treetable;
			//初始化渲染表格
			// var datas = getTableDatas(0);
			var tableIns = table.render({
				elem: '#' + tableID,
				id: tableID,
				height: 'full-60',
				page: false,
				size: 'sm',
				limit: '1000',
				cols: [
					[{
						type: 'numbers'
					}, {
						field: 'name',
						title: '菜单名称',
						width: '20%',
						templet: function(d) {
							//渲染树形图标
							return treetable.renderNode(this, d);
						}
					}, {
						field: 'code',
						title: '菜单代码',
						width: '12%',
						edit: 'text',
						event: 'kk'
					}, {
						field: 'page',
						title: '关联页面'
					}, {
						field: 'valid',
						title: '启用标志',
						width: 120
					}]
				],
				data: [],
				done: function(res, data, curr) {
					//调用表格加载完成的方法
					treetable.done(res.data);
				}
			});
			//初始化加载表格数据
			treetable.render({
				id: tableID,
				load: function(node, render) {
					var pid = 0;
					if (node.level > 0) {
						pid = node.data.id;
					}
					render(getTableDatas(pid));
				}
			});
			/***************** 监听区 *******************/
			//监听行点击事件
			table.on('row(' + tableID + ')', function(obj) {
				//选中行高亮显示
				obj.tr.addClass('layui-table-click').siblings().removeClass('layui-table-click');
			});
			//监听工具点击事件
			/***************** 事件区 *******************/
			var newID = 1000;
			util.event("lay-event", {
				add: function() {
					treetable.add({
						elem: tableID,
						tr: $('[lay-id="' + tableID + '"] .layui-table-main').find(".layui-table-click"),
						data: {
							id: newID++,
							name: 'testAdd'
						}
					});
				},
				del: function() {
					treetable.del({
						elem: tableID,
						index: $('[lay-id="' + tableID + '"] .layui-table-main').find(".layui-table-click").eq(0).data('index'),
						// onlyLeaf: true,//只能删除叶子节点

					});
				},
				expandAll: function() {
					treetable.expandAll(tableID, function() {
						console.log('加载成功!');
					});
				},
				foldAll: function() {
					treetable.foldAll(tableID);
				}
			});
		});
	</script>
	<script>
		//造测试数据,实际开发使用ajax从后台查询子数据
		var map = new Map();
		//一级
		var datas1 = [];
		for (var i = 1; i < 10; i++) {
			datas1.push({
				id: i,
				name: '一级菜单-' + i,
				parent: 0
			});
			//二级菜单
			var datas2 = [];
			for (var j = i * 10; j < i * 10 + 5; j++) {
				datas2.push({
					id: j,
					name: '二级菜单-' + j,
					parent: i
				});
				//三级菜单
				var datas3 = [];
				for (var k = j * 10; k < j * 10 + 9; k++) {
					datas3.push({
						id: k,
						name: '三级菜单-' + k,
						leaf: true,
						parent: j
					});
				}
				map.set(j, datas3);
			}
			map.set(i, datas2);
		}
		map.set(0, datas1);

		function getTableDatas(pid) {
			var $ = layui.$;
			var data = [];
			$.ajax({
				type: 'post',
				url: 'http://localhost:8081/getMenus',
				data: {
					node: pid
				},
				async: false,
				success: function(obj) {
					data = obj.data;
					$.each(data, function(index, item){
						item.leaf = item.leaf == 1;
					});
				}
			});
			return data;
			// return map.get(pid) || [];
		}
	</script>
</html>

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值