首先,虚拟滚动的场景是什么? 项目中可能会遇到一些通用情况,它要求列表不能分页处理,也不能懒加载,必须一次性展示“所有数据”,且不能有过分的卡顿等问题。
1. 行业常见的报警列表,实时性要求很高,必须全部展示,且不可分页。
2. 后端提供的接口为一个list,没做分页处理,也没有按关键字查询接口,只能前端自行解决页面渲染和查询。
这次就以第二种情况来说明如何处理,接上一篇博客 一文搞懂web端国际化方案(以vue2+elementUI为例,包含远程获取国际化)
为了给多语言提供一个可供展示和修改的页面,前端自己实现了一个虚拟滚动且能修改多语言的页面。
直接上代码:
<div class="gl-cell-card-box p-10 bg_white">
<el-table
v-loading="tableLoading"
v-bind="_options"
ref="tableRef"
max-height="600"
:data="sliceTable"
:row-key="row => row.id"
@select="handleSelect"
@select-all="handleSelectAll"
>
<!-- <el-table-column type="index" width="40"> </el-table-column> -->
<el-table-column prop="module" label="模块" width="120"></el-table-column>
<el-table-column prop="app" label="所属端"></el-table-column>
<el-table-column prop="key" label="key"></el-table-column>
<el-table-column prop="value" label="值">
<template #default="{ row }">
<el-input
v-if="editing[row.key]"
v-model="row.value"
ref="inputRef"
size="small"
@blur="(e) => blur(e, row)"
>
</el-input>
<span
v-else
@click="editRow(row)"
>
{{ row.value }}
</span>
</template>
</el-table-column>
</el-table>
</div>
import {
getI18nMessageSource,
updateI18nMessage
} from '../../api/api';
import _ from 'lodash';
const showNums = 12;
let originTableData = [];
export default {
name: 'languageMain',
data() {
return {
// ITable配置
tableData: [],
tableLoading: false,
// 开始索引
startIndex: 0,
// 选中的数据
selectedRows: [],
// 空元素,用于撑开table的高度
vEle: undefined,
// 是否全选
isSelectedAll: false,
editing: {},
// currentValue: {},
currentRow: {},
}
},
computed: {
// 这个是截取表格中的部分数据,放到了 table 组件中来显示
sliceTable: {
get() {
return this.tableData.slice(
this.startIndex,
(
(this.tableData.length - this.startIndex) > showNums) ?
(this.startIndex + showNums)
:
this.tableData.length
);
},
set(val) {
this.tableData = val;
}
},
_options() {
const option = {
border: false,
stripe: false,
headerCellStyle: { background: '#e9e9ef', color: '#515a6e', },
tooltipEffect: "dark",
showHeader: true,
showPagination: false,
rowStyle: () => "cursor:pointer", // 行样式
};
return option;
},
},
created() {
// 创建一个空元素,这个空元素用来撑开 table 的高度,模拟所有数据的高度
this.vEle = document.createElement("div");
},
mounted() {
this.getLanguage()
// 绑定滚动事件
this.$refs.tableRef.$el
.querySelector(".el-table__body-wrapper")
.addEventListener("scroll", this.tableScroll, {
passive: true
});
},
methods: {
searchModelFun() {
this.tableData = _.cloneDeep(originTableData);
if(!this.searchModel.value) {
return;
}
const { key, value } = this.searchModel;
const filterData = this.tableData.filter(item => item[key].includes(value));
this.tableData = filterData;
},
getLanguage(language = 'zh-CN') {
this.tableLoading = true;
getI18nMessageSource(this.searchForm).then(res=> {
const { success, data } = res
if (success) {
if(!_.isEmpty(data)) {
this.tableData = [];
Object.keys(data).forEach(key => {
this.tableData.push({
id: key,
module: this.searchForm.module,
app: this.searchForm.app,
key: key,
value: data[key]
})
})
// 存储原始数据,用于筛选重置
originTableData = _.cloneDeep(this.tableData);
// 重新加载数据,重新计算高度
this.loadData();
}
} else {
console.log('获取国际化失败')
}
}).catch(err => {
console.log('获取国际化失败err', err)
}).finally(() => {
this.tableLoading = false;
})
},
editRow(row) {
this.editing[this.currentRow.key] = false;
this.currentRow = _.cloneDeep(row);
if (this.editing[row.key] === false) {
this.editing[row.key] = true;
this.$nextTick(()=> {
this.$refs.inputRef.focus();
})
return;
}
this.$set(this.editing, row.key, true);
this.$nextTick(()=> {
this.$refs.inputRef.focus();
})
},
blur(e, row) {
this.editing[row.key] = false;
// 如果没有变化,则不更新数据
if (this.currentRow.value === row.value) {
this.currentRow = {};
return;
}
const params = {
app: this.searchForm.app,
module: this.searchForm.module,
language: this.activeName,
label: row.key,
value: row.value,
}
this.updateLanguage(params)
},
updateLanguage(params) {
updateI18nMessage(params).then(res => {
const { success, data } = res
if (success) {
this.getLanguage()
this.$message({
type: 'success',
message: '更新成功!'
});
} else {
this.$message({
type: 'success',
message: '更新失败!'
});
}
}).catch(err => {
this.$message({
type: 'success',
message: '更新失败~'
});
})
},
// 加载数据
loadData() {
this.$nextTick(() => {
// 设置成绝对定位,这个元素需要我们去控制滚动
this.$refs.tableRef.$el.querySelector(".el-table__body").style.position = "absolute";
// 计算表格所有数据所占内容的高度
this.vEle.style.height = this.tableData.length * 48 + "px";
// 把这个节点加到表格中去,用它来撑开表格的高度
this.$refs.tableRef.$el.querySelector(".el-table__body-wrapper").appendChild(this.vEle);
// 重新设置曾经被选中的数据
this.selectedRows.forEach(row => {
this.$refs.tableRef.toggleRowSelection(row, true);
});
});
},
/**
* @description: 手动勾选时的事件
* @param {*} selection - 选中的所有数据
* @param {*} row - 当前选中的数据
* @return {*}
*/
handleSelect(selection, row) {
this.selectedRows = selection;
},
/**
* @description: 全选事件
* @param {*} selection
* @return {*}
*/
handleSelectAll(selection) {
this.isSelectedAll = !this.isSelectedAll;
if (this.isSelectedAll) {
this.selectedRows = this.tableData;
} else {
this.selectedRows = [];
this.$refs.tableRef.clearSelection();
}
},
/**
* @description: table 滚动事件
* @param {*}
* @return {*}
*/
tableScroll() {
let bodyWrapperEle = this.$refs.tableRef.$el.querySelector(".el-table__body-wrapper");
// 滚动的高度
let scrollTop = bodyWrapperEle.scrollTop;
// 下一次开始的索引
this.startIndex = Math.floor(scrollTop / 48);
// console.log(scrollTop, this.startIndex,'scrollTop, startIndex');
// 滚动操作
bodyWrapperEle.querySelector(".el-table__body").style.transform = `translateY(${this.startIndex * 48}px)`;
// 滚动操作后,上面的一些 tr 没有了,所以需要重新设置曾经被选中的数据
this.$nextTick(() => {
this.selectedRows.forEach(row => {
this.$refs.tableRef.toggleRowSelection(row, true);
});
})
// 滚动到底,加载新数据
if (bodyWrapperEle.scrollHeight <= scrollTop + bodyWrapperEle.clientHeight) {
this.$message.warning("没有更多了");
return;
}
}
},
};