参考网上黄龙的表格树进行完善,并添加固定表头等的功能,目前是在iview的项目中实现,如果想在element中实现的话修改对应的元素标签及相关写法即可。

<!--
@events @on-row-click 单击行或者单击操作按钮方法
@on-selection-change 多选模式下 选中项变化时触发
@on-sort-change 排序时有效,当点击排序时触发
@props data 显示的结构化数据
columns 表格列的配置描述 sortable:true 开启排序功能
showHeader 是否显示表头
type: 'selection'为多选功能 type: 'template' 为操作功能 slot为插槽名
-->
<template>
<div ref="table" class='autoTable tree-grid'>
<!-- <div ref="table" :style="{width:treeGridWidth}" class='autoTable tree-grid'> -->
<div ref="header" class="tree-grid-header" v-if="showHeader">
<table class="table table-bordered hl-tree-table">
<colgroup>
<col v-for="(column, index) in cloneColumns" :width="column.width" :align="column.align" :key="index">
<col v-if="showVerticalScrollBar" :width="scrollBarWidth"/>
</colgroup>
<thead>
<tr>
<th v-for="(column,index) in cloneColumns" :key="column.key">
<div v-if="column.type === 'selection'" class="selection-wrapper">
<Checkbox v-model="checks" @click="handleCheckAll"></Checkbox>
<input type="checkbox" v-model="checks" @click="handleCheckAll" class="selection-checkbox">
</div>
<div v-else class="tree-grid-cell">{{renderHeader(column, index)}}
<span class="ivu-table-sort" v-if="column.sortable">
<Icon type="md-arrow-dropup" :class="{on: column._sortType === 'asc'}" @click.native="handleSort(index, 'asc')" />
<Icon type="md-arrow-dropdown" :class="{on: column._sortType === 'desc'}" @click.native="handleSort(index, 'desc')" />
</span>
</div>
</th>
<th v-if="showVerticalScrollBar" rowspan="1"></th>
</tr>
</thead>
</table>
</div>
<div ref="body" class="tree-grid-body" :style="{height:tBodyHeight}">
<table ref="bodyTable" class="table table-bordered hl-tree-table">
<colgroup ref="bodyColgroup">
<col v-for="(column, index) in cloneColumns" :width="column.width" :align="column.align" :key="index">
</colgroup>
<tbody>
<tr v-for="(item,index) in initGridData" :key="item.id" v-show="show(item)" :class="{'child-tr':item.parent}">
<td v-for="(column,snum) in columns" :key="column.key">
<div v-if="column.type === 'selection'" class="selection-wrapper">
<CheckboxGroup v-model="checkGroup">
<Checkbox :label="item.id"><span></span></Checkbox>
</CheckboxGroup>
<input type="checkbox" :value="item.id" v-model="checkGroup" @click="handleCheckClick(item,$event,index)" class="selection-checkbox">
</div>
<div v-if="column.type === 'template'" class="tree-grid-cell">
<slot :name="column.slot" :scope="item"></slot>
</div>
<div @click="toggle(index,item)" v-if="!column.type" class="tree-grid-cell">
<template v-if='snum===iconRow()'>
<i v-html='item.spaceHtml'></i>
<span v-if="item.children&&item.children.length>0" class="tree-grid-arrow" :class="{'tree-grid-arrow-open': item.expanded}" >
<i class="ivu-icon ivu-icon-ios-arrow-forward"></i>
</span>
<i v-else class="ms-tree-space"></i>
</template>{{renderBody(item,column)}}
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
let cached
export default {
name: 'treeGrid',
props: {
columns: {
type: Array,
default: () => []
},
data: {
type: Array,
default: () => []
},
showHeader: {
type: Boolean,
default: true
},
height: {
type: [Number, String]
}
},
data () {
return {
initGridData: [], // 处理后数据数组
cloneColumns: [], // 处理后的表头数据
showVerticalScrollBar: false,
scrollBarWidth: undefined,
checkGroup: [], // 复选框数组
checks: false, // 全选
screenWidth: document.body.clientWidth, // 自适应宽
headerHeight: 0,
columnsWidth: {},
timer: false, // 控制监听时长
dataLength: 0 // 树形数据长度
}
},
computed: {
treeGridWidth () {
let treeGridWidth = this.$el ? this.$el.offsetWidth : '1000'
return treeGridWidth
},
tBodyHeight () {
return parseFloat(this.height) > 0 ? (parseFloat(this.height) - parseFloat(this.headerHeight)) + 'px' : 'auto'
}
},
watch: {
screenWidth (val) {
if (!this.timer) {
this.screenWidth = val
this.timer = true
setTimeout(() => {
this.timer = false
}, 400)
}
},
data () {
if (this.data) {
this.dataLength = this.Length(this.data)
this.initData(this.deepCopy(this.data), 1, null)
this.checkGroup = this.renderCheck(this.data)
if (this.checkGroup.length === this.dataLength) {
this.checks = true
} else {
this.checks = false
}
this.showScrollBar()
}
},
columns: {
handler () {
this.cloneColumns = this.makeColumns()
},
deep: true
},
checkGroup (data) {
this.checkAllGroupChange(data)
}
},
mounted () {
if (this.data) {
this.dataLength = this.Length(this.data)
this.initData(this.deepCopy(this.data), 1, null)
this.cloneColumns = this.makeColumns()
this.checkGroup = this.renderCheck(this.data)
if (this.checkGroup.length === this.dataLength) {
this.checks = true
} else {
this.checks = false
}
}
// 绑定onresize事件 监听屏幕变化设置宽
this.$nextTick(() => {
this.screenWidth = document.body.clientWidth
this.headerHeight = this.showHeader ? this.$refs.header.clientHeight : 0
this.cloneColumns = this.makeColumns()
this.showScrollBar()
})
window.onresize = () => {
return (() => {
window.screenWidth = document.body.clientWidth
window.screenHeight = document.body.clientHeight
this.screenWidth = window.screenWidth
this.headerHeight = this.showHeader ? this.$refs.header.clientHeight : 0
this.cloneColumns = this.makeColumns()
this.showScrollBar()
})()
}
},
methods: {
showScrollBar () {
this.$nextTick(() => {
// console.error(this.$refs.bodyTable.clientHeight, this.$refs.body.clientHeight)
if (this.$refs.bodyTable.clientHeight > this.$refs.body.clientHeight) {
if (!this.showVerticalScrollBar) {
this.showVerticalScrollBar = true
this.scrollBarWidth = this.getScrollBarSize()
}
} else {
if (this.showVerticalScrollBar) {
this.showVerticalScrollBar = false
}
}
})
},
// 有无多选框折叠位置优化
iconRow () {
for (let i = 0, len = this.columns.length; i < len; i++) {
if (this.columns[i].type === 'selection') {
return 1
}
}
return 0
},
// 排序事件
handleSort (index, type) {
this.cloneColumns.forEach(col => {
col._sortType = 'normal'
})
if (this.cloneColumns[index]._sortType === type) {
this.cloneColumns[index]._sortType = 'normal'
} else {
this.cloneColumns[index]._sortType = type
}
this.$emit('on-sort-change', this.cloneColumns[index]['key'], this.cloneColumns[index]['_sortType'])
},
// 点击某一行事件
RowClick (data, event, index, text) {
let result = this.makeData(data)
this.$emit('on-row-click', result, event, index, text)
},
// 点击事件 返回数据处理
makeData (data) {
const t = this.type(data)
let o
if (t === 'array') {
o = []
} else if (t === 'object') {
o = {}
} else {
return data
} if (t === 'array') {
for (let i = 0; i < data.length; i++) {
o.push(this.makeData(data[i]))
}
} else if (t === 'object') {
for (let i in data) {
if (i !== 'spaceHtml' && i !== 'parent' && i !== 'level' && i !== 'expanded' && i !== 'isShow' && i !== 'load') {
o[i] = this.makeData(data[i])
}
}
}
return o
},
// 处理表头数据
makeColumns () {
let columns = this.deepCopy(this.columns)
let tableWidth = this.$el.offsetWidth
let noWidthLength = 0
let nanWidthLength = 0
let widthSum = 0
columns.forEach((column, index) => {
column._index = index
column._sortType = 'normal'
if (column.width) {
if (!/^(-?\d+)(\.\d+)?$/.test(column.width)) {
let width = column.width
if (width.slice(-1) === '%') {
let percent = (column.width).slice(0, -1)
column.width = ''
column._width = ''
nanWidthLength += 1
column._nanWidth = percent
} else {
this.$Message.error('请输入正确的宽度:数字(例如:100)或者百分比(例如:10%)')
}
} else {
widthSum += column.width
column._width = column.width
}
} else {
noWidthLength += 1
column._width = ''
}
column._width = column.width ? column.width : ''
})
if (nanWidthLength > 0) {
columns.forEach((column, index) => {
if (column._nanWidth) {
column.width = parseInt((tableWidth - widthSum) * column._nanWidth / 100)
// column.width = (tableWidth - widthSum) * column._nanWidth / 100
column._width = column.width
widthSum += column.width
}
})
}
if (noWidthLength > 0) {
columns.forEach((column, index) => {
if (column._width === '') {
column.width = parseInt((tableWidth - widthSum) / noWidthLength)
// column.width = (tableWidth - widthSum) / noWidthLength
column._width = column.width
}
})
}
return columns
},
// 数据处理 增加自定义属性监听
initData (data, level, parent) {
this.initGridData = []
let spaceHtml = ''
for (let i = 1; i < level; i++) {
spaceHtml += '<i class="ms-tree-space"></i>'
}
data.forEach((item, index) => {
item = Object.assign({}, item, {
'parent': parent,
'level': level,
'spaceHtml': spaceHtml
})
if ((typeof item.expanded) === 'undefined') {
item = Object.assign({}, item, {
'expanded': false
})
}
if ((typeof item.show) === 'undefined') {
item = Object.assign({}, item, {
'isShow': false
})
}
if ((typeof item.isChecked) === 'undefined') {
item = Object.assign({}, item, {
'isChecked': false
})
}
item = Object.assign({}, item, {
'load': (item.expanded ? 1 : false)
})
this.initGridData.push(item)
if (item.children && item.expanded) {
this.initData(item.children, level + 1, item)
}
})
},
// 隐藏显示
show (item) {
return ((item.level === 1) || (item.parent && item.parent.expanded && item.isShow))
},
toggle (index, item) {
let level = item.level + 1
let spaceHtml = ''
for (let i = 1; i < level; i++) {
spaceHtml += '<i class="ms-tree-space"></i>'
}
if (item.children) {
if (item.expanded) {
item.expanded = !item.expanded
this.close(index, item)
} else {
item.expanded = !item.expanded
if (item.load) {
this.open(index, item)
} else {
item.load = true
item.children.forEach((child, childIndex) => {
this.initGridData.splice((index + childIndex + 1), 0, child)
// 设置监听属性
this.$set(this.initGridData[index + childIndex + 1], 'parent', item)
this.$set(this.initGridData[index + childIndex + 1], 'level', level)
this.$set(this.initGridData[index + childIndex + 1], 'spaceHtml', spaceHtml)
this.$set(this.initGridData[index + childIndex + 1], 'isShow', true)
this.$set(this.initGridData[index + childIndex + 1], 'expanded', false)
})
}
}
}
this.showScrollBar()
},
open (index, item) {
if (item.children) {
item.children.forEach((child, childIndex) => {
child.isShow = true
if (child.children && child.expanded) {
this.open(index + childIndex + 1, child)
}
})
}
},
close (index, item) {
if (item.children) {
item.children.forEach((child, childIndex) => {
child.isShow = false
child.expanded = false
if (child.children) {
this.close(index + childIndex + 1, child)
}
})
}
},
// 点击check勾选框, 父子不相关联
handleCheckClick (data, event, index) {
data.isChecked = !data.isChecked
if (data.isChecked) {
this.checkGroup.push(data.id)
} else {
for (let i = 0; i < this.checkGroup.length; i++) {
if (this.checkGroup[i] === data.id) {
this.checkGroup.splice(i, 1)
}
}
}
this.checkGroup = this.getArray(this.checkGroup)
let itemsIds = this.getArray(this.checkGroup.concat(this.All(this.data)))
if (this.checkGroup.length === itemsIds.length) {
this.checks = true
} else {
this.checks = false
}
},
// checkbox 全选 选择事件
handleCheckAll () {
this.checks = !this.checks
if (this.checks) {
this.checkGroup = this.getArray(this.checkGroup.concat(this.All(this.data)))
} else {
this.checkGroup = []
}
// this.$emit('on-selection-change', this.checkGroup)
},
// 数组去重
getArray (a) {
let hash = {}
let len = a.length
let result = []
for (let i = 0; i < len; i++) {
if (!hash[a[i]]) {
hash[a[i]] = true
result.push(a[i])
}
}
return result
},
checkAllGroupChange (data) {
if (this.dataLength > 0 && data.length === this.dataLength) {
this.checks = true
} else {
this.checks = false
}
this.$emit('on-selection-change', this.checkGroup)
},
All (data) {
let arr = []
data.forEach((item) => {
arr.push(item.id)
if (item.children && item.children.length > 0) {
arr = arr.concat(this.All(item.children))
}
})
return arr
},
// 返回树形数据长度
Length (data) {
let length = data.length
data.forEach((child) => {
if (child.children) {
length += this.Length(child.children)
}
})
return length
},
// 返回表头
renderHeader (column, $index) {
if ('renderHeader' in this.columns[$index]) {
return this.columns[$index].renderHeader(column, $index)
} else {
return column.title || '#'
}
},
// 返回内容
renderBody (row, column, index) {
return row[column.key]
},
// 默认选中
renderCheck (data) {
let arr = []
data.forEach((item) => {
if (item._checked) {
arr.push(item.id)
}
if (item.children && item.children.length > 0) {
arr = arr.concat(this.renderCheck(item.children))
}
})
return arr
},
// 深度拷贝函数
deepCopy (data) {
let t = this.type(data)
let o
let i
let ni
if (t === 'array') {
o = []
} else if (t === 'object') {
o = {}
} else {
return data
}
if (t === 'array') {
for (i = 0, ni = data.length; i < ni; i++) {
o.push(this.deepCopy(data[i]))
}
return o
} else if (t === 'object') {
for (i in data) {
o[i] = this.deepCopy(data[i])
}
return o
}
},
type (obj) {
let toString = Object.prototype.toString
let map = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object'
}
return map[toString.call(obj)]
},
getScrollBarSize (fresh) {
if (this.$isServer) return 0
if (fresh || cached === undefined) {
const inner = document.createElement('div')
inner.style.width = '100%'
inner.style.height = '200px'
const outer = document.createElement('div')
const outerStyle = outer.style
outerStyle.position = 'absolute'
outerStyle.top = 0
outerStyle.left = 0
outerStyle.pointerEvents = 'none'
outerStyle.visibility = 'hidden'
outerStyle.width = '200px'
outerStyle.height = '150px'
outerStyle.overflow = 'hidden'
outer.appendChild(inner)
document.body.appendChild(outer)
const widthContained = inner.offsetWidth
outer.style.overflow = 'scroll'
let widthScroll = inner.offsetWidth
if (widthContained === widthScroll) {
widthScroll = outer.clientWidth
}
document.body.removeChild(outer)
cached = widthContained - widthScroll
}
return cached
}
},
beforeDestroy () {
window.onresize = null
}
}
</script>
<style lang="less">
.tree-grid {
@keyframes opacityChild{
0% { opacity: 0; }
50% { opacity: .5; }
100% { opacity: 1; }
}
width: 100%;
color: #1f2d3d;
color: #495167;
&.autoTable {
overflow: auto;
}
.tree-grid-body {
overflow: auto;
}
table {
width: 100%;
border-spacing: 0;
border-collapse: collapse;
&.hl-tree-table {
&>tbody{
&>tr {
height: 50px;
background-color: #fff;
border-bottom: 1px solid #e8eaec;
&:hover {
background-color: #ebf7ff;
}
}
&>.child-tr {
background-color: #fff;
}
}
th>label {
display: inline-block;
margin: 0 12px;
}
}
.ivu-icon {
font-size: 18px;
}
.tree-grid-arrow {
cursor: pointer;
width: 14px;
text-align: center;
display: inline-block;
i {
position: relative;
top: -1px;
transition: all .2s ease-in-out;
font-size:14px;
vertical-align:middle;
}
&-open {
i {
transform:rotate(90deg);
}
}
}
}
.table>tbody>tr>td,
.table>tbody>tr>th,
.table>thead>tr>td,
.table>thead>tr>th {
vertical-align: middle;
box-sizing: border-box;
&:last-child {
border-right: 0;
}
}
// .table>tbody>tr>td,
// .table>tbody>tr>th {
// border-right: 1px solid #ccc;
// }
.table>thead>tr>th {
text-align: left;
}
.table-bordered>thead>tr>td,
.table-bordered>thead>tr>th {
height: 32px;
padding: 0;
vertical-align: middle;
background: #E1E4E5;
border-right: 1px solid #fff;
.tree-grid-cell {
padding: 0 12px;
}
}
.tree-grid-cell {
padding: 0 12px;
font-size: 12px;
}
.ms-tree-space {
position: relative;
top: 1px;
display: inline-block;
font-style: normal;
font-weight: 400;
line-height: 1em;
width: 14px;
height: 14px;
}
.ms-tree-space::before {
content: "";
}
.selection-wrapper {
position: relative;
text-align: center;
width: 18px;
height: 18px;
margin: 0 auto;
vertical-align: middle;
.selection-checkbox {
position: absolute;
left: 0;
top: 0;
z-index: 2;
width: 18px;
height: 18px;
vertical-align: middle;
opacity: 0;
cursor: pointer;
}
.ivu-checkbox-wrapper {
position: absolute;
left: 0;
top: 0;
z-index: 1;
margin: 0;
line-height: 15px;
}
}
}
</style>

vue 表格树 固定表头的更多相关文章

  1. vue表格实现固定表头首列

    前言 最近在做vue移动端项目,需要做一个可以固定表头首列的表格,而且由于一些原因不能使用任何UI插件,网上找了很久也没什么好方法,所以在解决了问题之后,写下了这篇文章供后来人参考,文章有什么错漏的问 ...

  2. bootstrap-table固定表头固定列

    1.引入 bootstrap依赖于jquery bootstrap-table依赖于bootstrap,所以都需要引入 2. bootstrap-table有两种方式,html.js <tabl ...

  3. bootstrap 冻结表格,冻结表头

    需要的文件下载: bootstrap-table:https://github.com/wenzhixin/bootstrap-table bootstrap-table-fiex-column:ht ...

  4. 固定表头,单元格td宽度自适应,多内容出现-横向纵向滚动条数据表格的<前世今生>

    固定表头,单元格td宽度自适应,多内容出现-横向纵向滚动条数据表格的<前世今生>     先上图例   & 无论多少数据--都完美! 背景:由于我司行业方向,需要很多数据报表,则t ...

  5. 使用bootstrap table 插件固定表头时 表头与表格内容无法对齐

    在使用bootstrap table开发后台管理系统,表格利用bootstrap-table插件来实现,使用bootstrap-table过程中,会出现表头错位的情况 表头对不齐效果: 解决的方法: ...

  6. jQuery制作多表格固定表头、切换表头的特效

    做了好几天的固定表头特效,总算是搞定了.先说明一下基本功能:我们在一个网页上浏览很多份表格数据的时候,肯定会碰到很多分不清表头,也分不清表 格是哪个的情况,这个时候我们就希望能有一种功能,就是我们再下 ...

  7. Qt实现表格树控件-支持多级表头

    目录 一.概述 二.效果展示 三.实现方式 四.多级表头 1.数据源 2.表格 3.QStyledItemDelegate绘制代理 五.测试代码 六.相关文章 原文链接:Qt实现表格树控件-支持多级表 ...

  8. css 固定表头的表格,和 width:auto, margin:auto等 自计算方法

    实现思路: 外层用一个table,里面写好Header,然后里面再写一个table里面写好header.然后自己控制overflow的值使内部的tablemargin-top和外层的行高一致就可以实现 ...

  9. vue表格之tableHeaderColor(修改表头背景色)

    <el-table :header-cell-style="tableHeaderColor"></el-table> // 更改表头样式 tableHea ...

随机推荐

  1. 使用plantuml,业务交接就是这么简单

    使用plantuml,业务交接就是这么简单 你好,我是轩脉刃. 最近交接了一个业务,原本还是有挺复杂的业务逻辑的,但发现交接过来的项目大有文章,在项目代码中有一个docs文件夹,里面躺着若干个 pum ...

  2. pipeline 共享库

    目录 一.简介 二.共享库扩展 共享库使用 共享库结构 pipeline模板 一些小问题 三.共享库例子 使用公共变量 使用共享库的src方法 使用共享库的vars方法 四.插件实现pipeline ...

  3. 如何用PHP/MySQL为 iOS App 写一个简单的web服务器(译) PART1

    原文:http://www.raywenderlich.com/2941/how-to-write-a-simple-phpmysql-web-service-for-an-ios-app 作为一个i ...

  4. Sentry 开发者贡献指南 - 后端服务(Python/Go/Rust/NodeJS)

    内容整理自官方开发文档 系列 1 分钟快速使用 Docker 上手最新版 Sentry-CLI - 创建版本 快速使用 Docker 上手 Sentry-CLI - 30 秒上手 Source Map ...

  5. [BUUCTF]PWN——[ZJCTF 2019]EasyHeap

    [ZJCTF 2019]EasyHeap 附件 步骤: 例行检查,64位程序 试运行一下看看程序大概执行的情况,经典的堆块的菜单 64位ida载入,首先检索字符串,发现了读出flag的函数 看一下每个 ...

  6. java nio 写一个完整的http服务器 支持文件上传 chunk传输 gzip 压缩 使用过程 和servlet差不多

    java nio 写一个完整的http服务器  支持文件上传   chunk传输    gzip 压缩      也仿照着 netty处理了NIO的空轮询BUG        本项目并不复杂 代码不多 ...

  7. grep 命令过滤配置文件中的注释和空

    grep 用法 Usage: grep [OPTION]... PATTERN [FILE]... Search for PATTERN in each FILE or standard input. ...

  8. canvas 实现渐变色填充的三角形

    实现效果 效果一: 效果二: 实现思路 canvas实现 1. 绘制三角形 // html <canvas id="triangle" width="30" ...

  9. SpringBoot整合MQTT (使用官方demo)

    依赖 <dependency> <groupId>org.eclipse.paho</groupId> <artifactId>org.eclipse. ...

  10. vue-子组件创建/注册/使用流程

    流程分为三步 非单文件组件:(实际不用,因为很麻烦,框架都是多文件组件) 局部注册 1.创建一个组件 const school = Vue.extend({ // 传入配置对象 // 子组件配置对象不 ...