elementUI的固定列实现
根据上图,我们得知Eui的固定列是通过多渲染一个table来实现的,然后设置该table宽度为所有固定列的宽度和
,然后其他的列都visibility:hidden
左固定列会多一个table,右固定列会多一个table,也就是说我们传入的list就20条记录,但是渲染实际会是60条数据,性能差不用说了吧
Eui固定列实现的bug
上图因为数据比较敏感,所以我做了模糊处理,主要功能是右侧有个固定列,然后table里某一列的高度不能超过三行,超过三行会出现省略号,所以我需要使用到
-webkit-line-clamp: 3
,
但是这个属性使用时必须得visibility:visible,而固定列使用的table中的非固定列都是visibility:hidden(所以该行会超过3行),所以会导致主体table和固定列table的行高不一致,就导致上图的错位。
上图中,我设置了四个左固定列且开启了合计行,然后红框框处是无法拖动横向滚动条的,这是因为固定列的table是覆盖住了主体table的,而固定列的table的滚动条是禁止滚动的。
elementUI固定列缺点列举
- 会多渲染几个table,性能差
- 会导致固定列table和主体table的行高不一致,体现为错位
- 使用总计行时,会导致横向滚动条局部无滚动 这三个问题都没什么好办法解决,我试过。
自定义实现一个固定列
我使用了element-ui-2.15.5版本,其他版本会有些许偏差,可以加我微信,我远程帮你
不就是固定列嘛,position:sticky就能实现,大家可以去了解一下这个属性,地址。
position:sticky的浏览器兼容:
改造过程
给eui的组件二次改造的方法有三种,我的另一篇文章写了教程。
而下文改造的方式是将element-ui项目拉下来,然后本地改造,然后运行看效果的。
如果有人希望能将table组件迁移到业务代码中二次改造的话,可以加我微信,我帮你远程。
体验地址
项目地址
*** 重要 ***
对应的文件修改可以参考该commit,以下所有文件修改都可以在该commit找到对应,只不过多了注释。
- 打开
packages/table/src/store/watcher.js
,在data中添加两个属性
...
fixedColumns: [],
/* 新增 */
// 新增左固定列数组
fixedLeftColumns: [],
// 新增右固定列数组
fixedRightColumns: [],
/**/
rightFixedColumns: [],
...
在updateColumns
方法中添加两行代码
updateColumns() {
...
states.fixedColumns = _columns.filter((column) => column.fixed === true || column.fixed === 'left');
/* 新增 */
// 在store中记录左固定和右固定的的列
states.fixedLeftColumns = _columns.filter((column) => column.fixedLeft === true);
states.fixedRightColumns = _columns.filter((column) => column.fixedRight === true);
states.rightFixedColumns = _columns.filter((column) => column.fixed === 'right');
...
}
- 打开
packages/table/src/table-body.js
,原getCellStyle
方法:决定表格某个单元的样式是什么。我们直接改造该方法
getCellStyle(rowIndex, columnIndex, row, column) {
const cellStyle = this.table.cellStyle;
let rsl = {};
if (typeof cellStyle === 'function') {
rsl = cellStyle.call(null, {
rowIndex,
columnIndex,
row,
column
}) || {};
} else {
rsl = cellStyle || {};
}
// 如果该列是左固定列或者右固定列
if (column.fixedLeft || column.fixedRight) {
let obj = {};
// 构造一个position:sticky的样式对象
obj.position = 'sticky';
obj.zIndex = 1;
obj.backgroundColor = 'inherit';
// 比如第一个固定列的left是0,第二个左固定列的left得是第一个固定列的宽度,所以得求和。
column.fixedLeft && (obj.left = column.stickyLeft + 'px');
column.fixedRight && (obj.right = column.stickyRight + 'px');
// 根据开发者返回的样式是对象还是字符串还是数组来决定如何将sticky样式合并进去
switch (Object.prototype.toString.call(rsl)) {
case '[object Object]':
rsl = Object.assign(rsl, obj);
break;
case '[object Array]':
rsl.push(obj);
break;
case '[object String]':
let tempStr = JSON.stringify(obj);
tempStr = tempStr.slice(1, tempStr.length - 2).replace(/\,"/g, ';"').replace(/"/, '');
tempStr[tempStr.length - 1] !== ';' && (tempStr += ';');
rsl = tempStr + rsl;
break;
}
}
return rsl;
},
- 打开
packages/table/src/table-column.js
,该文件是列的配置,原props是
props: {
...
fixed: [Boolean, String],
...
}
我们添加两行,代表该列可以定义为新形式的左固定列,或者右固定列
props: {
...
fixed: [Boolean, String],
// 左固定列配置
fixedLeft: Boolean,
// 有固定列配置
fixedRight: Boolean,
...
}
在原created
钩子函数中
created() {
...
const basicProps = ['columnKey', 'label', 'className', 'labelClassName', 'type', 'renderHeader', 'formatter', 'fixed', 'resizable'];
...
}
给basicProps添加四个item
created() {
...
const basicProps = ['columnKey', 'label', 'className', 'labelClassName', 'type', 'renderHeader', 'formatter', 'fixed', 'resizable',
'fixedLeft', 'fixedRight','stickyLeft', 'stickyRight'];
...
}
- 打开
packages/table/src/table-footer.js
,该文件是table的底部,比如合计行。原render
函数的return
render(h) {
return (
...
<tbody class={ [{ 'has-gutter': this.hasGutter }] }>
<tr>
{
this.columns.map((column, cellIndex) =>
<td
key={cellIndex}
colspan={ column.colSpan }
rowspan={ column.rowSpan }
class={ this.getRowClasses(column, cellIndex) }>
<div class={ ['cell', column.labelClassName] }>
{
sums[cellIndex]
}
</div>
</td>)
}
...
)
}
我们添加一行style={ this.getFooterCellStyle(column) }
决定该列的样式
render(h) {
return (
...
<tbody class={ [{ 'has-gutter': this.hasGutter }] }>
<tr>
{
this.columns.map((column, cellIndex) =>
<td
key={cellIndex}
colspan={ column.colSpan }
rowspan={ column.rowSpan }
style={ this.getFooterCellStyle(column) }
class={ this.getRowClasses(column, cellIndex) }>
<div class={ ['cell', column.labelClassName] }>
{
sums[cellIndex]
}
</div>
</td>)
}
...
)
}
然后我们在methods中添加getFooterCellStyle
方法
getFooterCellStyle(column) {
let rsl = {};
// 如果该列是左固定列或者右固定列
if (column.fixedLeft || column.fixedRight) {
let obj = {};
obj.position = 'sticky';
obj.backgroundColor = 'inherit';
obj.zIndex = 1;
// 比如第一个固定列的left是0,第二个左固定列的left得是第一个固定列的宽度,所以得求和。
column.fixedLeft && (obj.left = column.stickyLeft + 'px');
column.fixedRight && (obj.right = column.stickyRight + (this.tableLayout.scrollY ? this.tableLayout.gutterWidth : 0) + 'px');
rsl = Object.assign(rsl, obj);
}
return rsl;
}
- 打开
packages/table/src/table-header.js
,该文件是表头的配置,原getHeaderCellStyle
方法:决定某个单元格的样式是什么。我们直接将其改造
getHeaderCellStyle(rowIndex, columnIndex, row, column) {
const headerCellStyle = this.table.headerCellStyle;
let rsl = {};
if (typeof headerCellStyle === 'function') {
rsl = headerCellStyle.call(null, {
rowIndex,
columnIndex,
row,
column
}) || {};
} else {
rsl = headerCellStyle || {};
}
// 如果该列是左固定列或者右固定列
if (column.fixedLeft || column.fixedRight) {
let obj = {};
obj.position = 'sticky';
obj.zIndex = 1;
// 比如第一个固定列的left是0,第二个左固定列的left得是第一个固定列的宽度,所以得求和。
column.fixedLeft && (obj.left = column.stickyLeft + 'px');
column.fixedRight && (obj.right = column.stickyRight + (this.tableLayout.scrollY ? this.tableLayout.gutterWidth : 0) + 'px');
// 根据开发者返回的样式是对象还是字符串还是数组来决定如何将sticky样式合并进去
switch (Object.prototype.toString.call(rsl)) {
case '[object Object]':
rsl = Object.assign(rsl, obj);
break;
case '[object Array]':
rsl.push(obj);
break;
case '[object String]':
let tempStr = JSON.stringify(obj);
tempStr = tempStr.slice(1, tempStr.length - 2).replace(/\,"/g, ';"').replace(/"/, '');
tempStr[tempStr.length - 1] !== ';' && (tempStr += ';');
rsl = tempStr + rsl;
break;
}
}
return rsl;
},
- 打开
packages/table/src/table-layout.js
,该文件是table布局相关的属性。原constructor
函数如下
constructor(options) {
...
this.fixedWidth = null;
this.rightFixedWidth = null;
...
}
我们添加两个属性
constructor(options) {
...
this.fixedWidth = null;
this.rightFixedWidth = null;
/* 新增 */
// 左右固定列的总宽度
this.fixedLeftWidth = null;
this.fixedRightWidth = null;
...
}
原updateColumnsWidth
方法:更新列的宽度
updateColumnsWidth() {
...
const fixedColumns = this.store.states.fixedColumns;
if (fixedColumns.length > 0) {
let fixedWidth = 0;
fixedColumns.forEach(function(column) {
fixedWidth += column.realWidth || column.width;
});
this.fixedWidth = fixedWidth;
}
...
}
添加两个属性、两个逻辑
updateColumnsWidth() {
...
const fixedColumns = this.store.states.fixedColumns;
if (fixedColumns.length > 0) {
let fixedWidth = 0;
fixedColumns.forEach(function(column) {
fixedWidth += column.realWidth || column.width;
});
this.fixedWidth = fixedWidth;
}
/* 新增 */
const fixedLeftColumns = this.store.states.fixedLeftColumns;
// 如果存在左侧固定列
if (fixedLeftColumns.length > 0) {
let sum = 0;
let fixedLeftWidth = 0;
fixedLeftColumns.forEach(column => {
// 更新每一个固定列的left属性,比如第一个固定列的left是0,第二个左固定列的left得是第一个固定列的宽度,所以得求和。
column.stickyLeft = sum;
sum += column.realWidth || column.width;
fixedLeftWidth += column.realWidth || column.width;
});
// 更新左固定列的总宽度
this.fixedLeftWidth = fixedLeftWidth;
}
const fixedRightColumns = this.store.states.fixedRightColumns;
if (fixedRightColumns.length > 0) {
let sum = 0;
let fixedRightWidth = 0;
fixedRightColumns.forEach(column => {
// 更新每一个固定列的right属性,比如第一个固定列的right是0,第二个左固定列的right得是第一个固定列的宽度,所以得求和。
column.stickyRight = sum;
sum += column.realWidth || column.width;
fixedRightWidth += column.realWidth || column.width;
});
// 更新右固定列的总宽度
this.fixedRightWidth = fixedRightWidth;
this.notifyObservers('columns');
}
...
}
- 打开
packages/table/src/table.vue
,该文件是table主体。在原template中,
<template>
...
<div
v-if="showSummary"
v-show="data && data.length > 0"
v-mousewheel="handleHeaderFooterMousewheel"
class="el-table__footer-wrapper"
ref="footerWrapper">
<table-footer
:store="store"
:border="border"
:sum-text="sumText || t('el.table.sumText')"
:summary-method="summaryMethod"
:default-sort="defaultSort"
:style="{
width: layout.bodyWidth ? layout.bodyWidth + 'px' : ''
}">
</table-footer>
</div>
...
</template>
添加4个div
<template>
...
<div
v-if="showSummary"
v-show="data && data.length > 0"
v-mousewheel="handleHeaderFooterMousewheel"
class="el-table__footer-wrapper"
ref="footerWrapper">
<table-footer
:store="store"
:border="border"
:sum-text="sumText || t('el.table.sumText')"
:summary-method="summaryMethod"
:default-sort="defaultSort"
:style="{
width: layout.bodyWidth ? layout.bodyWidth + 'px' : ''
}">
</table-footer>
</div>
<!-- 新增 -->
<!-- 用来模拟阴影的 -->
<div
v-if="haveFixedLeft && scrollPosition !== 'left'"
:style="[{'position':'absolute','top':'0px',
'left':layout.fixedLeftWidth - columns.filter(item=>item.fixedLeft).length + 'px',
'z-index':2,'width':'1px',
'box-shadow':'2px 0px 3px 0px rgba(0,0,0,0.75)'},
fixedHeight]">
</div>
<div
v-if="haveFixedLeft && scrollPosition !== 'left'"
:style="[{'position':'absolute','left':'0px',
'width':layout.fixedLeftWidth - columns.filter(item=>item.fixedLeft).length + 'px',
'height':'1px',
'box-shadow':'2px 0px 3px 0px rgba(0,0,0,0.75)'},
fixedHeightForShadow]">
</div>
<div
v-if="haveFixedRight && scrollPosition !== 'right'"
:style="[{'position':'absolute','top':'0px',
'right':layout.fixedRightWidth - columns.filter(item=>item.fixedRight).length + 'px',
'z-index':2,'width':'1px',
'box-shadow':'-2px 0px 3px 0px rgba(0,0,0,0.75)'},
fixedHeight]">
</div>
<div
v-if="haveFixedRight && scrollPosition !== 'left'"
:style="[{'position':'absolute','right':'0px',
'width':layout.fixedRightWidth - columns.filter(item=>item.fixedRight).length + 'px',
'height':'1px',
'box-shadow':'2px 0px 3px 0px rgba(0,0,0,0.75)'},
fixedHeightForShadow]">
</div>
...
</template>
在template的另一处
<div
v-if="rightFixedColumns.length > 0"
class="el-table__fixed-right-patch"
ref="rightFixedPatch"
:style="{
width: layout.scrollY ? layout.gutterWidth + 'px' : '0',
height: layout.headerHeight + 'px'
}"></div>
添加一个条件haveFixedRight
<div
v-if="rightFixedColumns.length > 0 || haveFixedRight"
class="el-table__fixed-right-patch"
ref="rightFixedPatch"
:style="{
width: layout.scrollY ? layout.gutterWidth + 'px' : '0',
height: layout.headerHeight + 'px'
}"></div>
在computed添加两个属性
computed:{
// 是否有新型左侧固定列
haveFixedLeft() {
return this.columns.filter(item => item.fixedLeft).length;
},
// 是否有新型右侧固定列
haveFixedRight() {
return this.columns.filter(item => item.fixedRight).length;
},
...
}
改造shouldUpdateHeight
方法为
shouldUpdateHeight() {
return this.height ||
this.maxHeight ||
this.fixedColumns.length > 0 ||
this.rightFixedColumns.length > 0 ||
this.fixedLeftColumns.length > 0 ||
this.fixedRightColumns.length > 0;
},
在methods里添加一个fixedHeightForShadow
方法
fixedHeightForShadow() {
if (this.maxHeight) {
if (this.showSummary) {
return {
bottom: 0
};
}
return {
bottom: (this.layout.scrollX && this.data.length) ? this.layout.gutterWidth + 'px' : ''
};
} else {
if (this.showSummary) {
return {
top: this.layout.tableHeight ? this.layout.tableHeight + 'px' : ''
};
}
return {
top: this.layout.viewportHeight ? this.layout.viewportHeight + 'px' : ''
};
}
},
改造...mapStates
:
...mapStates({
selection: 'selection',
columns: 'columns',
tableData: 'data',
fixedColumns: 'fixedColumns',
rightFixedColumns: 'rightFixedColumns',
/* 新增 */
// 从存储中心获取左固定列和右固定列
fixedLeftColumns: 'fixedLeftColumns',
fixedRightColumns: 'fixedRightColumns',
})
- 打开
packages/theme-chalk/src/table.scss
文件,找到@include m(striped)
,然后改造为
@include m(striped) {
& .el-table__body {
& tr td {
background-color: inherit;
}
& tr.el-table__row--striped {
td {
background: #FAFAFA;
}
&.current-row td {
background-color: $--table-current-row-background-color;
}
}
}
}