需求:表格中需要使用tree表格,展开折叠效果,还需要多选表格数据,elementui中有树表格,查看样式及文档,但是没有办法多选,本文章中可以多选获取数据。

直接下载地址:

直接复制粘贴到html查看演示

本文样式:

element ui tree 选中颜色 element ui tree table_tree table


组件化使用: 请查看

element ui tree 选中颜色 element ui tree table_vue_02

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
		<meta content="yes" name="apple-mobile-web-app-capable">
		<meta content="black" name="apple-mobile-web-app-status-bar-style">		
		<title>slider</title>
		<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
		<link rel="stylesheet" href="treeTable/components/treeTable.css">
		<style>
			@keyframes treeTableShow {
				from {
					opacity: 0;
				}
			
				to {
					opacity: 1;
				}
			}
			
			@-webkit-keyframes treeTableShow {
				from {
					opacity: 0;
				}
			
				to {
					opacity: 1;
				}
			}
			
			.ms-tree-space {
			  position: relative;
			  top: 1px;
			  display: inline-block;
			  font-style: normal;
			  font-weight: 400;
			  line-height: 1;
			  width: 18px;
			  height: 14px;
			}
			.ms-tree-space::before {
			  content: "";
			}
			.processContainer {
			  width: 100%;
			  height: 100%;
			}
			table td {
			  line-height: 26px;
			}
			.tree-ctrl {
			  position: relative;
			  cursor: pointer;
			  color: #2196f3;
			  margin-left: -18px;
			}
			
		</style>
	</head>

	<body>
		<div id="app" class="text-center" style="margin-top: 0px">

			<template>
				<div class="app-container">
					<tree-table ref="tree1"
						:data="data" 
						:columns="columns" 
						:expand-all="expandAll" 
						:primary_fld="'id'" 
						:parent_fld="'parent_id'"
						:select_fld="'is_select'"
					 border>

						<el-table-column label="id">
							<template slot-scope="scope">
								<span> {{ scope.row.id }}</span>
							</template>
						</el-table-column>
						<el-table-column label="人口">
							<template slot-scope="scope">
								<el-tooltip :content="scope.row.peoples" effect="dark" placement="left">
									<div class="processContainer">
										<div :style="{ width:scope.row._width * 500+'px',
                          background:scope.row._width>0.5?'rgba(233,0,0,.5)':'rgba(0,0,233,0.5)',
                          marginLeft:scope.row._marginLeft * 500+'px' }"
										 class="process">
											<span style="display:inline-block" />
										</div>
									</div>
								</el-tooltip>
							</template>
						</el-table-column>
						<el-table-column label="test">
							<template slot-scope="scope">
								<span>{{scope.row.descrip}}</span>
								<!--<el-input v-model="scope.row.descrip">-->
							</template>
						</el-table-column>
						<el-table-column label="操作" width="200">
							<template slot="header" slot-scope="scope">								
								<el-button type="primary" size="mini" icon="el-icon-plus" circle @click="$refs.tree1.append()"></el-button>								
							</template>
							<template slot-scope="scope">
								<el-button type="text" @click="$refs.tree1.delete(scope.row)">删除</el-button>
								<el-button type="text" @click="$refs.tree1.append(scope.row)">添加子节点</el-button>
								<el-button type="text" @click="$refs.tree1.append(scope.row,-1)">插入</el-button>								
							</template>
						</el-table-column>
					</tree-table>
				</div>
			</template>
		
		<template >			
		<el-button @click="getSelectData">获取数据</el-button>
		<el-tag style="margin-top: 10px;">原始数据</el-tag>
		<el-table :data="data" border >
			<el-table-column label="id" width=150>
				<template slot-scope="scope">
					<span>{{scope.row.id}}</span>
				</template>			
			</el-table-column>
			<el-table-column label="地区" width=150>
				<template slot-scope="scope">
					<el-input v-model="scope.row.descrip"></el-input>
				</template>			
			</el-table-column>
		</el-table>
		</template>
		</div>
	</body>
	
	<script src="https://cdn.bootcss.com/vue/2.5.18/vue.min.js"></script>	
	<script src="https://cdn.bootcss.com/element-ui/2.5.4/index.js"></script>	
	<script type="text/javascript">
				
        /**
  author	: benson
  date		: 2019.02.20 1435
*/
Vue.component("TreeTable", {
	data() {
		return {
			//chooseson: true, //全选
			key: true, //单个点击直到全部选中
			//当前全选状态
			chooseall: false,
		};
	},
	props: {
		/* eslint-disable */
		data: {
			type: [Array, Object],
			required: true
		},
		columns: {
			type: Array,
			default: () => []
		},
		evalFunc: Function,
		evalArgs: Array,
		expandAll: {
			type: Boolean,
			default: false
		},
		//-----------------------------------------
		tree_width: {
			type: String,
			default: 150
		},
		select_fld: {
			type: String,
			//default: "id"
			default: ""
		},
		//-----------------------------------------
		// 如果数据源是靠字父级字段来表示树的话,primary_fld和parent_fld必须同时传入,并指定root_value
		primary_fld: {
			type: String,
			default: "id",
			required: true

		},
		parent_fld: {
			type: String,
			//default: "parent_id"
			default: ""
		},
		// 根级节点值
		root_value: {
			type: [String, Number],
			//default: "parent_id"
			default: ""
		},
		// 可选,子节点数的字段,如果含有此字段,将会加快加载速度
		child_count_fld: {
			type: String,
			//default: "childCount"
			default: ""
		}
	},
	computed: {
		// 格式化数据源
		formatData: function() {
			let result = [];
			let tmp;
			let func = this.evalFunc;

			if (this.is_source_table) {
				// 数据源是一个表格数组,依靠关键字段和父级id组成关系
				tmp = 0;
				func = this.tableToTree;
			} else if (Array.isArray(this.data)) {
				tmp = this.data;
			} else {
				tmp = [this.data];
			}

			const args = this.evalArgs ? [result, tmp, this.expandAll].concat(this.evalArgs) : [result, tmp, this.expandAll];
			//return func.apply(null, args);
			func.apply(null, args);
			return result;
		},
		// 判断数据源是否使用父级字段来表示的二维的表格树
		is_source_table: function() {
			return this.primary_fld && this.parent_fld;
		}
	},
	methods: {
		//代替原来的 treeToArray函数
		tableToTree: function(tResult, tStartRecno, expandAll, parent, level, item) {

			const that = this;
			let parent_value = that.root_value;
			let _level = 1;
			let record = null;
			let child = null;
			const marLTemp = []
			let tmp = []

			if (parent) {
				parent_value = parent[that.primary_fld];
				_level = level;
			}


			//Array.from(data).forEach(function(record) {
			for (let i = tStartRecno, count = that.data.length; i < count; i++) {
				record = that.data[i];

				if (record[that.parent_fld] !== parent_value) {
					continue;
				}
				if (record._expanded === undefined) {
					Vue.set(record, '_expanded', expandAll)
				}

				Vue.set(record, '_level', _level)
				// 如果有父元素
				if (parent) {
					Vue.set(record, 'parent', parent)
					// 如果父元素有偏移量,需要计算在this的偏移量中
					// 偏移量还与前面同级元素有关,需要加上前面所有元素的长度和
					if (!marLTemp[_level]) {
						marLTemp[_level] = 0
					}
					Vue.set(record, '_marginLeft', marLTemp[_level] + parent._marginLeft)
					Vue.set(record, '_width', record[item] / parent[item] * parent._width)
					// 在本次计算过偏移量后加上自己长度,以供下一个元素使用
					marLTemp[_level] += record._width
				} else {
					// 如果为根
					// 初始化偏移量存储map
					marLTemp[record[that.primary_fld]] = []
					// map中是一个数组,存储的是每级的长度和
					// 初始情况下为0
					marLTemp[record[that.primary_fld]][_level] = 0
					Vue.set(record, '_marginLeft', 0)
					Vue.set(record, '_width', 1)
				}
				tmp.push(record)
				tResult.push(record);
				// 表含有表示子节点数量的字段
				let is_seek_child = 1;
				if (that.child_count_fld) {
					is_seek_child = record[that.child_count_fld];
				}
				if (is_seek_child) {
					const next_recno = i + 1;
					const next_level = _level + 1;

					child = that.tableToTree(tResult, next_recno, expandAll, record, next_level, item);

					if (child.length > 0) {
						//tmp = tmp.concat(child);
						if (!record.child) {
							Vue.set(record, 'child', child);
						}
					}

				}
			}
			//})


			return tmp
		},

		next_brother: function(tRow) {
			const that = this;
			let id = tRow[that.primary_fld],
				targetIdx = -1,
				source = (tRow.parent) ? tRow.parent.child : that.formatData;
			result = null;

			targetIdx = source.findIndex(function(value, index, arr) {
				return value[that.primary_fld] === id;
			});
			targetIdx++;
			if (targetIdx < source.length) {
				result = source[targetIdx];
			}
			return result;
		},
		// 添加节点
		// 1.不传递tRow,表示在表尾追加根节点
		// 2.传递tRow,默认是在当前节点下添加子节点
		// 3. 传递tRow ,并且 tIsInsert 大于或等于1,表示在当前节点前插入一节点
		append: function(tRow, tIsInsert) {
			const that = this;
			let scatter = {},
				new_id = (new Date()).getTime().toString(),
				id = (tRow)?tRow[that.primary_fld]:"";
			scatter[that.primary_fld] = new_id;
			if (tRow && tIsInsert) {				
					let targetIdx = that.data.findIndex(function(value, index, arr) {
						return value[that.primary_fld] === id;
					});
				if (id >= 0) {
					if (that.parent_fld) {
						scatter[that.parent_fld] = tRow[that.parent_fld];
					}
					if (tRow.parent) {
						scatter.parent = tRow.parent;
					}
					that.data.splice(targetIdx, 0, scatter);
				}
			} else {
				//添加根节点或子节点
				scatter[that.parent_fld] = (tRow)?id:that.root_value;
				
				that.data.push(scatter);
			}
		},
		delete: function(tRow) {
			let that = this,
				node = null,
				parent = null,
				id = tRow[that.primary_fld];

			if (tRow.child && tRow.child.length > 0) {
				tRow.child.forEach((v, i) => {
					that.delete(v);
				});
			}
			let targetIdx = that.data.findIndex(function(value, index, arr) {
				return value[that.primary_fld] === id;
			});

			that.data.splice(targetIdx, 1);

			node = tRow;
			//while (node["parent"]) {
			if (node["parent"]) {
				node = node["parent"];
				targetIdx = node.child.findIndex(function(value, index, arr) {
					return value[that.primary_fld] === id;
				});

				if (targetIdx >= 0) {
					node.child.splice(targetIdx, 1);
				}
			}
			if (tRow.parent) {
				that.refresh_node_select(tRow.parent);
			}
		},

		showRow: function(row) {
			const show = row.row.parent ?
				row.row.parent._expanded && row.row.parent._show :
				true;
			row.row._show = show;
			return show ?
				"animation:treeTableShow 1s;-webkit-animation:treeTableShow 1s;" :
				"display:none;";
		},
		// 切换下级是否展开
		toggleExpanded: function(trIndex) {
			const record = this.formatData[trIndex];
			record._expanded = !record._expanded;
		},
		// 图标显示
		iconShow(index, tScope) {
			const record = tScope.row;
			//console.info(tScope.$index);
			return index === 0 && record.child && record.child.length > 0;


		},
		//功能函数:选中部分子集
		setchildtobeselect(arr, key) {
			const that = this;
			arr.forEach((v, i) => {
				//v.checks = key;
				v[that.select_fld] = key;
				// v._expanded = key;//选中后展开子项
				if (v.child) {
					this.setchildtobeselect(v.child, v[that.select_fld]);
				}
			});
		},
		//是否所有的都被选中
		isallchecked(arr) {
			const that = this;
			arr.forEach((v, i) => {
				if (!v[that.select_fld]) {
					this.key = false;
				}
				if (v.child) {
					this.isallchecked(v.child);
				}
			});
		},

		// 节点下的子节点,全部都没有被选中或子节点个数为0时返回true,否则返回false
		is_all_false(tNode) {
			const that = this;
			let result = true;
			if (!Array.isArray(tNode.child)) {
				return result;
			}
			tNode.child.forEach((v, index) => {
				// 只要有一个兄弟是true,终止递归
				if (v[that.select_fld]) {
					result = false;
					return result; //终止此次循环,减少循环次数
				}
			});
			return result;
		},
		// 如果节点为被选状态,通过检查子节点是否有被选中,否则修正它
		refresh_node_select(tNode) {
			const that = this;
			let node = tNode;
			while (true) {
				// 选中?
				if (node[that.select_fld]) {
					if (that.is_all_false(node)) {
						//Vue.set(node,that.select_fld,false);
						node[that.select_fld] = false;
					} else {
						break;
					}
				}
				if (node["parent"]) {
					node = node["parent"];
				} else {
					break;
				}

			}
		},

		//某个复选框被点击时
		toselect(row) {
			const that = this;
			let node = null;
			// row._expanded = row.checks;//选中后是否展开
			//1、若有子集先让子选中
			if (row.child) {
				this.setchildtobeselect(row.child, row[that.select_fld]);
			}
			//2、然后判断是否全选中
			this.key = true; //重置为true,防止上次已经是false的状态  |半选状态is-indeterminate
			this.isallchecked(this.formatData);
			//3、设置多选框的状态
			if (!row[that.select_fld]) {
				//this.setparentfalse(this.formatData, row[that.primary_fld], row._level); //设置父级选中的状态为false
				if (row.parent) {
					that.refresh_node_select(row.parent);
				}
				//设置全选框的状态
				that.chooseall = false;
				//document.getElementById("chooseall").checked = false; 
			} else {
				node = row;
				while (node["parent"]) {
					node = node["parent"];
					if (!node[that.select_fld]) {
						node[that.select_fld] = true;
					}
				}
				//this.setparenttrue(this.formatData, row[that.primary_fld], row._level); //设置父级选中的状态为true
			}
			if (this.key) {
				that.chooseall = true;
				//document.getElementById("chooseall").checked = true; //设置全选框的状态
			}
		}
	},
	mounted() {
		this.$nextTick(() => {
			var that = this;
			//const all = document.getElementById("chooseall");

		});
	},
	template: `
			<el-table :data="formatData" :row-style="showRow" v-bind="$attrs" v-on="$listeners">,
				
				<template v-if="select_fld">
					<el-table-column width="50" align="center">,
					<template slot="header" slot-scope="scope">
						<el-checkbox v-model="chooseall" @change="setchildtobeselect(formatData,$event)"/>
					</template>
					  <template slot-scope="scope">
						<el-checkbox v-model="scope.row[select_fld]" @change="toselect(scope.row)"></el-checkbox>
					  </template>
					</el-table-column>
				</template>
			
				<el-table-column v-if="columns.length===0" width="150">
				  <template slot-scope="scope">
					<span v-for="space in scope.row._level" :key="space" class="ms-tree-space"/>
					<span v-if="iconShow(0,scope)" class="tree-ctrl" @click="toggleExpanded(scope.$index)">
					  <i v-if="!scope.row._expanded" class="el-icon-plus"/>
					  <i v-else class="el-icon-minus"/>
					</span>
					{{ scope.$index }}
				  </template>
				</el-table-column>
				<!--自定义树节点显示的列值-->
				<el-table-column v-for="(column, index) in columns" v-else :key="column.value" :label="column.text" :width="column.width">
				  <template slot-scope="scope">
					<!-- Todo -->
					<!-- eslint-disable-next-line vue/no-confusing-v-for-v-if -->
					<span v-for="space in scope.row._level" v-if="index === 0" :key="space" class="ms-tree-space"/>
					<span v-if="iconShow(index,scope)" class="tree-ctrl" @click="toggleExpanded(scope.$index)">
					  <i v-if="!scope.row._expanded" class="el-icon-arrow-right"/>
					  <i v-else class="el-icon-arrow-down"/>
					</span>					
					{{ scope.row[column.value] }}
				  </template>
				</el-table-column>
				<slot/>
			  </el-table>


			`
});

		new Vue({
			el: "#app",
			data() {
				return {
					//func: treeToArray,
					expandAll: true,
					columns: [{value:"descrip",text:"地区",width:150}],
					data: [{
							id: "1",
							parent_id: "",
							descrip: '广东',
							peoples: 104320459,
							comment: "",
							is_select: false,
							type: '省'
						},
						{
							id: "2",
							parent_id: "",
							descrip: '广西',
							peoples: 46023800,
							comment: "",
							is_select: false,
							type: '省'
						},
						{
							id: "3",
							parent_id: "1",
							descrip: '东莞',
							peoples: 8261400,
							comment: "",
							is_select: false,
							type: '市'
						},
						{
							id: "4",
							parent_id: "2",
							descrip: '南宁',
							peoples: 6835100,
							comment: "",
							is_select: false,
							type: '市'
						},
						{
							id: "5",
							parent_id: "1",
							descrip: '深圳',
							peoples: 10770000,
							comment: "",
							is_select: false,
							type: '市'
						},
						{
							id: "9",
							parent_id: "3",
							descrip: '虎门',
							peoples: 99999,
							comment: "",
							is_select: false,
							type: '区'
						},
						{
							id: "10",
							parent_id: "3",
							descrip: '石龙',
							peoples: 99999,
							comment: "",
							is_select: false,
							type: '区'
						},
						{
							id: "11",
							parent_id: "5",
							descrip: '石龙',
							peoples: 99999,
							comment: "",
							is_select: false,
							type: '区'
						},
						{
							id: "12",
							parent_id: "4",
							descrip: '石龙',
							peoples: 99999,
							comment: "",
							is_select: false,
							type: '区'
						}
					],
					selected: []
					//args: [null, null, 'timeLine']
				}
			},
			methods: {
				message(row) {
					this.$message.info(row.descrip)
				},
				getSelectData(){
					this.selected = []
					this.data.forEach((item,index)=>{
						if(item.is_select && item.type==='区'){
							this.selected.push(item.id)
						}
					})
					alert(`只获取子节点id=${this.selected}`)
				}
			}
		})
	</script>
</html>