这次是对上次封装表格的一次升级,增加了多级表头,列合并功能。我将代码和参考文章放在最下面。上面就放一些可能有点重要的废话吧,主要是我的一些总结。可以看看也可以直接滑到下方代码。此次同样只是做个记录,给以后一个方便,因此我也不公开献丑了。如果您能看到,做个参考就行,请勿转载。
效果图:
总结:
- 关于多级表头,由于代码主要集中在列的位置(尤其是需要递归),因此将表格列单独拿出来封装成了一个组件(MyColumn)。
- 效果图的第一列也是一个二级表头,此处难点是隐藏二级标题后(display:"none"),后一个二级标题会进行补位,因此表头行合并这里目前的解决方式是获取dom,直接设置rowSpan=2;而且目前此处是写死的,也不利于扩展,后续有可能的话会改进,目前有个大胆的想法就是列上设置自定义标签(v-*)不知道能不能实现。
- 行列合并且之后的hover样式就是搜集网上的资料进行了一下整合,这里我的问题是目前只能选择一项进行合并,不然hover后的样式会出问题。想看相关资料滑到最下方找参考文章。
- 表格的斑马纹也是拿到dom后添加的elemnt UI表格的斑马纹样式类名,注意:表格必须加上stripe属性。方法依赖,不加就没有斑马线。
- 新增了isMerge属性,不配置isMerge属性,普通表格可正常使用。
- 新增了table的key值,主要是之前的需求变更,表格头部部分改变,又要保留原版(这里靠下拉框用户选择控制)。因此根据选择,对tableTilte进行了重新赋值,这个切换开始是正确的,就直接上线了。然而,上线后我又测试了一次,发现第一轮切换DOM没问题,第二轮切换少两列,第三轮少四列,然后依次类推。。。。。。直到一列都没剩下。后来我尝试看数据,发现获取到的数据是没问题,然而视图不对。于是尝试解决。首先使用$forceUpdate() 强制刷新,然并卵。。。。之后开始各种姿势给数组重新赋值,然并卵。。。。就在我放弃准备两套DOM来回show的时候,偶然看见一个哥们说可以修改key值,我就试了下,bug修复。然而最近又发现因为修改key值,视图刷新。生命周期(目前只测试created)会执行两次。开发时需要注意
- for in循环会访问该对象的原型,应该用在非数组对象的遍历上,不建议使用该方法遍历数组。 给个善意的提醒吧,从表现上来看就是比for循环数组是多循环一次,然后你不慎用来循环数组的话,你的vue就会报错,错误可能还会比较。。。说不上的感觉
代码
父组件:
<template>
<div class="hello">
<MyTable
:tableTitle="MKTitle"
:tableData="marketList"
:page="pages"
:isMerge="true"
mergeName="projectName"
@getPageTable="getList"
@getSize="getList"
:mutiSelect ="mutiSelect"
:key="tableKey"
/>
</div>
</template>
<script>
import MyTable from '@/components/MyTable'
export default {
name: 'HelloWorld',
components: {
MyTable
},
data () {
return {
tableKey: 0,//table的key值
marketList: [],
MKTitle: [//表头数据
{ label: '项目', prop: '', columns: [{ label: '', prop: 'price', isGroup: 'project' }, { label: '', prop: 'type', isGroup: 'project' }], },
{ label: '单位', prop: 'unit' },
{
label: '本期', prop: 'period', formatter: (row) => {
return row.period < 609 ? `<i style="color:red;font-style:normal;">${row.period}</i>` : `${row.period}`
}
},
{ label: '上期', prop: 'prePeriod' },
{ label: '环比', prop: "chainRatio", },
{
label: '样本量(%)', prop: '', columns: [{ label: '总量', prop: 'total' }, {
label: '占比', prop: 'percentage', render: (h, params) => {
return h('el-tag', {
props: { type: params.row.percentage > '15%' ? 'success' : params.row.percentage <= '0.6%' ? 'danger' : 'info' } // 组件的props
}, params.row.percentage)
}
}]
},
],
MKList: [],//表格数据
pages: {//分页数据
curPage: 1, // 分页-当前页
limit: 10,
total: 0,
},
mutiSelect:false,
}
},
created () {
this.getList()
},
methods: {
getList () {
this.marketList = [//表格数据
{
"id":"1212121",
"price": "中长期",
"type": "5500V",
"unit": "元/吨",
"period": 675.1,
"prePeriod": 671.6,
"chainRatio": 3.5,
"total": 79.8,
"percentage": "15%"
},
{
"id":"1212121",
"price": "中长期",
"type": "5000V",
"unit": "元/吨",
"period": 609.1,
"prePeriod": 608.6,
"chainRatio": 0.5,
"total": 165.2,
"percentage": "36.6%"
},
{
"id":"1212121",
"price": "中长期",
"type": "4500V",
"unit": "元/吨",
"period": 554.1,
"prePeriod": '',
"chainRatio": '',
"total": 2.5,
"percentage": "0.6%"
},
{
"id":"1212121",
"price": "现货",
"type": "5500V",
"unit": "元/吨",
"period": 675.1,
"prePeriod": 671.6,
"chainRatio": 3.5,
"total": 79.8,
"percentage": "15%"
},
{
"id":"1212121",
"price": "现货",
"type": "5000V",
"unit": "元/吨",
"period": 609.1,
"prePeriod": 608.6,
"chainRatio": 0.5,
"total": 165.2,
"percentage": "36.6%"
},
{
"id":"1212121",
"price": "周综合",
"type": "5500V",
"unit": "元/吨",
"period": 554.1,
"prePeriod": '',
"chainRatio": '',
"total": 2.5,
"percentage": "0.6%"
},
{
"id":"1212121",
"price": "周综合",
"type": "5000V",
"unit": "元/吨",
"period": 554.1,
"prePeriod": '',
"chainRatio": '',
"total": 2.5,
"percentage": "0.6%"
},
{
"id":"1212121",
"price": "权重指数",
"type": "5000V",
"unit": "点",
"period": 554.1,
"prePeriod": '',
"chainRatio": '',
"total": 2.5,
"percentage": "0.6%"
},
]
this.pages.total = 3;
},
},
}
</script>
表格组件:
<template>
<div class="myTable">
<el-table :data="tableData" border stripe style="width: 100%" :header-cell-style="mergeMehod" :row-class-name="rowClass"
:span-method="mergeCloumn" @selection-change="SelectionChange" @cell-mouse-enter="cellMouseEnter" @cell-mouse-leave="cellMouseLeave">
<el-table-column v-if="mutiSelect" type="selection" style="width: 55px;" />
<MyColumn v-for="(item,i) in tableTitle" :key="i" :my-col="item" />
<slot name="handleColumn" />
</el-table>
<el-pagination class="fy" background hide-on-single-page :page-size.sync="page.limit" :page-sizes="[2,5,10,20]" :current-page.sync="page.curPage"
layout="total, sizes, prev, pager, next,jumper" :total="page.total" @size-change="getSize" @current-change="getCurChange" />
</div>
</template>
<script>
import MyColumn from './MyColumn'
export default {
name: 'MyTable',
components: {
MyColumn,
},
props: {
tableData: {
type: Array,
default: () => {
return []
}
},
mutiSelect: {
type: Boolean,
default: () => {
return false
}
},
isMerge: {
type: Boolean,
default: () => {
return false
}
},
mergeName: {
type: [String, Array],
default: () => {
return ''
}
},
tableTitle: {
type: Array,
default: () => {
return []
}
},
page: {
type: Object,
default: function () {
return {
limit: 2,
}
}
}
},
data () {
return {
total: 100,
mergeData: null,
sameRowArr: [],
curRowArr: [],
}
},
watch: {
tableTitle: {
deep: true, // 深度监听
immediate: true,
handler (newVal) {
return newVal
}
},
tableData: {
deep: true, // 深度监听
immediate: true,
handler (newVal) {
if (this.isMerge) {
this.mergeData = this.dataMerge(newVal, [this.mergeName])
this.sameRowArr = this.getSameRow(newVal, this.mergeName)
this.getStripe()
}
}
},
deep: true
},
created () {
},
methods: {
SelectionChange (val) {//多行选中,暂时没用上
console.log(val, 'ppo')
},
getSize (val) {
this.$set(this.page, 'curPage', 1)
this.$emit('getPageTable', this.page.curPage, val)
},
getCurChange (val) {
this.$emit('getPageTable', val, this.page.limit)
},
mergeMehod ({ row, column, rowIndex, columnIndex }) { //合并表头
if (!this.isMerge) return
for (let i = 0; i < row.length - 1; i++) {
if (row[i].level === row[i + 1].level && row[i].labelClassName && row[i].labelClassName === row[i + 1].labelClassName && column.label === '') {
return { display: 'none' }
}
}
if (rowIndex === 0) {
if (columnIndex === 0) {
this.$nextTick(() => {
if (document.getElementsByClassName(column.id).length !== 0) {
document.getElementsByClassName(column.id)[0].setAttribute('rowSpan', 2);
return false;
}
});
return column;
}
}
},
mergeCloumn ({ row, column, rowIndex, columnIndex }) {
if (!this.isMerge) return
if (this.mergeData) {
//判断合并列
let _row = column.property ? this.mergeData[column.property][rowIndex] : 1
let _col = _row > 0 ? 1 : 0;
return {
rowspan: _row,
colspan: _col
}
}
},
dataMerge: (tableData, mergeCol) => {
// mergeData:最终输出合并的数据,
// pos:记录每个列最终占了几行,可以理解成需要合并的每一列占据的行数,除第一列就是有几行合并就是几,其他列都是0
let [mergeData, pos] = [{}, {}]
//循环数据
for (let i in tableData) {
//循环数据内对象,查看有多少key
for (let j in tableData[i]) {
//如果只有一条数据时默认为1即可,无需合并
if (i == 0) {
mergeData[j] = [1];
pos[j] = 0;
} else {
let [prev, next] = [tableData[i - 1], tableData[i]];
//判断上一级别是否存在 ,如果存在判断两者key是否一致
//判断是否有数组规定只允许那几列需要合并单元格的
if (next && prev[j] == next[j] && ((!mergeCol || mergeCol.length == 0) || mergeCol.includes(j))) {
//如果当前与上一级数据相当,数组就加1 数组后面就添加一个0
mergeData[j][pos[j]] += 1;
mergeData[j].push(0)
} else {
mergeData[j].push(1);
pos[j] = i;
}
}
}
}
return mergeData;
},
// 合并行高亮------------------------------
getSameRow (tableData, mergeCol) {
let sameRowArr = [], sId = 0;
//循环数据
for (let i in tableData) {
tableData[i].index = i;
if (i == 0) {
sameRowArr.push([i])
} else {
if (tableData[i][mergeCol] === tableData[i - 1][mergeCol]) {
sameRowArr[sId].push(i)
} else {
sId = sId + 1;
sameRowArr.push([i])
}
}
}
return sameRowArr
},
rowClass ({ rowIndex }) {
// 同一行
let temArr = this.curRowArr
for (let i = 0; i < temArr.length; i++) {
if (rowIndex == temArr[i]) {
return 'row_class'
}
}
},
cellMouseEnter (row) {
if (!this.isMerge) return
this.sameRowArr.forEach((arr) => {
if (arr.indexOf(row.index) != -1) {
this.curRowArr = arr
}
})
},
cellMouseLeave () {
this.curRowArr = []
this.getStripe()
},
// 斑马线
getStripe () {
if (!this.isMerge) return
this.$nextTick(() => {
let tr = document.getElementsByClassName("el-table__row");
for (let i = 0; i < tr.length; i++) {
if (tr[i].classList.contains('el-table__row--striped')) tr[i].classList.remove('el-table__row--striped')
}
for (let i = 0; i < this.sameRowArr.length; i++) {
if (i % 2 != 0) {
for (let j = 0; j < this.sameRowArr[i].length; j++) {
tr[this.sameRowArr[i][j]].classList.add('el-table__row--striped')
}
}
}
})
}
}
}
</script>
<style lang="scss" scoped>
::v-deep .el-table .row_class td {
background: #f5f7fa !important;
}
.fy {
margin: 10px auto 0;
text-align: center;
}
</style>
列组件:
<template>
<!-- show-overflow-tooltip属性,表格列单行显示,多出隐藏,鼠标放上去tootip显示 -->
<el-table-column
:label="myCol.label"
:label-class-name="myCol.isGroup"
:prop="myCol.prop"
:width="myCol.width"
:max-height="myCol.height"
show-overflow-tooltip
align="center"
>
<!-- 非多级表头 -->
<template slot-scope="scope">
<!-- 非render列 -->
<template v-if="!myCol.render">
<!-- 列formatter -->
<template v-if="myCol.formatter">
<span v-html="myCol.formatter(scope.row, myCol)"/>
</template>
<!-- 普通列 -->
<template v-else>
{{scope.row[myCol.prop]}}
</template>
</template>
<!-- render列 -->
<template v-else>
<MyRenderDom
:column="myCol"
:row="scope.row"
:render="myCol.render"
:index="scope.$index"
/>
</template>
</template>
<!-- 多级表头 -->
<template v-for="(its,i) in myCol.columns">
<MyColumn
v-if="myCol.columns"
:key="i"
:myCol="its"
> </MyColumn>
<el-table-column
v-else
:key="i"
>
</el-table-column>
</template>
</el-table-column>
</template>
<script>
export default {
name: 'MyColumn',
components: {
MyRenderDom: {//函数式组件
functional: true,//设置为true 即表示该组件为一个函数组件
props: {
row: Object,
render: Function,
index: Number,
column: {
type: Object,
default: null
}
},
render: (h, ctx) => {
const params = {
row: ctx.props.row,
index: ctx.props.index
}
if (ctx.props.column) params.column = ctx.props.column
return ctx.props.render(h, params)
}
},
},
props: {
myCol: {
type: Object
},
mutiSelect: {
type: Boolean,
default: () => {
return false
}
}
},
data () {
return {
}
},
created () {
},
methods: {
}
}
</script>