本人所负责的后台管理系统几乎都是这种表格功能,且有很多表格功能较为复杂,既要把id一样的数据做合并,还要能编辑单元格(包括但不限于input、select等),还要有复选框勾选,还能动态新增和删除行数据,列数据大概有80列,虽然每页显示100条数据,但是这是合并的数据,还能动态新增行数据,所以行数据不确定,如下图所示:
怎么解决大数据量的复杂表格卡页面的问题呢
在此先推荐一波本人找到的umy-ui,一套为开发者准备的基于 Vue 2.0 的桌面端组件库,完美解决表格万级数据渲染卡顿,编辑表格卡顿问题
github.com/u-leo/umy-u…
看看人家这格局!!!
大家有条件的帮忙奉献一个赞吧
上线的时候用的是umy-ui的表格功能,但是未开启use-virtual属性,即没有开启虚拟功能,因为当时告知过业务,这个页面数据量很大,又是复选框又是60列可以编辑又是动态新增还有合并,会很卡顿,当时双方“拉勾”说的是不会在页面上编辑和新增,就是看看,正式的只会用导入的功能,所以设置的是20条每页。
<u-table
:big-data-checkbox="checked"
:height="height"
:data="tableList"
:span-method="objectSpanMethod"
class="umy-ui-table"
ref="tab"
showBodyOverflow="title"
showHeaderOverflow="title"
:row-height="rowHeight"
:header-cell-class-name="headerRequireCell"
@cell-click="customCellClickUmyUi"
@selection-change="onSelectRowChange"
border
<u-table-column type="selection" width="55" fixed/>
<template v-for="item in tableHeader">
<u-table-column
:key="item.purchaseForecastCode || item.timeStamp"
:prop="item.dataIndex"
show-overflow-tooltip
:label="item.title"
:width="item.width"
<template slot-scope="{row}">
...省略
</template>
</u-table-column>
</template>
</u-table>
根据接口返回的数据,code相同的进行合并某些列
<!-- 合并用的:span-method="objectSpanMethod" -->
objectSpanMethod({ row, column }) {
let rowspanArr = ['lie1', 'lie2', 'lie3', 'lie4', 'lie5', 'lie6', 'lie7']
let span = !column.label || (column.label && column.label.indexOf('N+W') < 0 && !rowspanArr.includes(column.property))
if (span) {
if (column.property == 'typeTran') {
if (row.typeIndex) {
return [row.typeIndex, 1]
} else return [0, 0]
if (row.nameIndex) { // 如果有值,说明需要合并
return [row.nameIndex, 1]
} else return [0, 0]
当时为啥没有开启use-virtual虚拟属性呢
因为复选框和合并同时存在时,会有bug,看不到未合并的列时,高度没有撑开,往右滚动时才正常,往左滚回去时高度又缩回去了,如下图
umy-ui官方说法
so,当时没有开启use-virtual虚拟属性。
but上线之后频频收到业务反馈,页面太卡了。嗯,我又开启了漫漫优化的路!
首先满心欢喜的找到了vxe-table
确实不会像umy-ui那样高度撑不开,但是滚动时行数据会在最上面挤在一起出现,sorry,当时没有截图,给不了效果!
最后决定放弃框架的合并方法,自己写合并方法,这样就不会再冲突!接口返回的数据都是平铺的,自行处理下,新增一个children属性,id相同的放在children里面。
// 处理表格数据
dealTable(data, idName) {
let firstRowIdArr = data.filter(e => e.firstRowId)
let childrenArr = data.filter(e => !e.firstRowId)
firstRowIdArr.forEach(e => {
let children = childrenArr.filter(c => c[idName] == e[idName])
children.unshift(e)
e.children = deepClone(children)
this.tableList = firstRowIdArr
表格还是用的umy-ui里面的ux-grid,没有使用vxe-table(因为横向滚动还是卡顿)
<ux-grid
:big-data-checkbox="checked"
:height="height"
:data="tableList"
class="umy-ui-table"
use-virtual
showBodyOverflow="title"
showHeaderOverflow="title"
:rowClassName="rowClassNameParent"
:cell-class-name="cellClassNameParent"
:header-cell-class-name="headerRequireCell"
:fixed-columns-roll="true"
@cell-click="customCellClickUmyUi"
@selection-change="onSelectRowChange"
border
<ux-table-column type="checkbox" width="55" fixed="left"/>
<template v-for="item in tableHeader">
<ux-table-column
:key="item.dataIndex"
:prop="item.dataIndex"
show-overflow-tooltip
:field="item.dataIndex"
:title="item.title"
:width="item.width"
<template slot-scope="{row}">
<div class="input_width_auto">
<template v-if="'typeTran' == item.dataIndex">
<div class="bb_ccc" v-for="child in row.children" :key="child.detailId">
{{child.typeTran}}
</div>
</template>
<template v-else-if="item.title.indexOf('N+W') > -1">
<div class="bb_ccc" v-for="child in row.children" :key="child.detailId">
<a-input-number
v-if="canEditable(child) && child.type == '1'"
:precision="0"
style="width: 80px"
v-model="child[item.dataIndex]"
:placeholder="$t('Please enter')"
@blur="calWeeklyTotal(child, 'detailId')"
@pressEnter="$event.target.blur()"
<span v-else>{{child[item.dataIndex]}}</span>
</div>
</template>
<template v-else-if="'vendorCode' == item.dataIndex">
<div class="bb_ccc" v-for="child in row.children" :key="child.detailId">
<div v-if="canEditable(child) && child.type == '1'" class="pt_5">
<span :class="child.type == '1'? 'button_action':''"
class="click_dispaly_none pl_10 input_display"
v-if="canEditable(child)"
@click="showVendorModal($event, child)">{{child[item.dataIndex]}}
</span>
</div>
<span v-else>{{!child[item.dataIndex]? (child.type != '1'? '/': ''): child[item.dataIndex]}}</span>
</div>
</template>
<template v-else-if="['itemCategory'].includes(item.dataIndex)">
<span>{{getStatusTran(item.dataIndex, row[item.dataIndex], item.dataIndex)}}</span>
</template>
<template v-else-if="['approveStatus'].includes(item.dataIndex)">
<span>{{row[item.dataIndex]? getStatusTran(item.dataIndex, row[item.dataIndex], item.dataIndex): '未处理'}}</span>
</template>
<template v-else-if="['quantityDetailStatus'].includes(item.dataIndex)">
<div class="bb_ccc" v-for="child in row.children" :key="child.detailId">
<span>{{!child[item.dataIndex]? (child.type != '1'? '/': 'xxx待确认'): getStatusTran(item.dataIndex, child[item.dataIndex], item.dataIndex)}}</span>
</div>
</template>
<template v-else-if="'approveCode' == item.dataIndex">
<span class="color_2539a0 button_action" @click="showApprovelDetail(row)">{{row[item.dataIndex]}}</span>
</template>
<template v-else-if="'operation' == item.dataIndex">
<div class="bb_ccc" v-for="child in row.children" :key="child.detailId">
<div class="table_operation" v-if="canEditable(child) && (child.type == '0' || child.type == '1')">
<div v-if="child.type == '0'">
<span class="ml_5 mr_5 color_2539a0 button_action" @click="assignSupplier(child)">分配</span>
</div>
<div v-if="child.type == '1'">
<span class="ml_5 mr_5 color_2539a0 button_action" @click="deleteForecastDetail(child)">删除</span>
<span class="ml_5 mr_5 color_2539a0 button_action" @click="saveForecastDetail(child)">保存</span>
</div>
</div>
<div v-else>/</div>
</div>
</template>
<template v-else-if="'allocationtor' == item.dataIndex">
<div class="bb_ccc" v-for="child in row.children" :key="child.detailId">
<div v-if="child.type != '1'">
<span>/</span>
</div>
<div v-else>{{child[item.dataIndex]}}</div>
</div>
</template>
<template v-else-if="['不合并列1', '不合并列2', '不合并列3'].includes(item.dataIndex)">
<div class="bb_ccc" v-for="child in row.children" :key="child.detailId">
<span>{{child[item.dataIndex] != 0 && !child[item.dataIndex]? (child.type != '1' && slashArr.includes(item.dataIndex)? '/': ''): child[item.dataIndex]}}</span>
</div>
</template>
<template v-else>
<span>{{row[item.dataIndex] != 0 && !row[item.dataIndex]? (row.type != '1' && slashArr.includes(item.dataIndex)? '/': ''): row[item.dataIndex]}}</span>
</template>
</div>
</template>
</ux-table-column>
</template>
</ux-grid>
最重要的还是:rowClassName="rowClassNameParent"和:cell-class-name="cellClassNameParent"
rowClassNameParent({row}) {
if(!(row && row.children && row.children.length)) return;
let className = `vxe-trHeight${row.children.length}`;
return className;
cellClassNameParent({column}) {
let rowspanArr = ['列1', '列2', '列3', '列4', '列5', '列6', '列7', '列8'];
let span = !column.title || (column.title && column.title.indexOf('N+W') < 0 && !rowspanArr.includes(column.property));
if(!span) {
return 'umy-grid-cell-merge';
给表格单元格加class属性
其中设置高度是老人了,具体请看juejin.cn/post/705891…
class名vxe-trHeight4的生成和下面的类似,只是vxe-trHeight4是以37为基准!
less 动态生成想要的class选择器
此时我们就要用到less中的loop,自动生成很多class名,因为每行数据高度可能对应有几十条数据,这边暂定自动生成38条class,即trHeight1、trHeight2...trHeight38。
.h(@i) when(@i <= 38){
.trHeight@{i} {
height: @i*54px;
.h((@i + 1));
.h(1);
自此四个页面的优化完成,丝滑的不行
