场景:通过excel批量导入数据,将数据回显在页面上,在渲染layui组件时消耗浏览器大量内存导致页面崩溃。
解决方法:通过分页的方式,只显示部分数据,减少内存消耗。
具体代码:
-
html代码块,layui分页组件
<!- 批量导入按钮 -->
批量导入: <!- 分页组件 -->
<!- 表单提交按钮 -->
<button type="button" class="layui-btn" lay-filter="submitBtn" id="submitBtn">立即提交</button> -
js 部分
在 layui use 模块引入 laypage 并初始化变量,以下代码块都是在layui.use模块里边。
layui.use(['form', 'laydate', 'laypage'], function () {
var form = layui.form,
laydate = layui.laydate,
laypage = layui.laypage,
let allData = []; // 全部数据
let currentPage = 1; // 当前页
let pageSize = 10; // 每页10条
let totalCount = 0; // 总条数
})
渲染分页组件
// 分页组件
function renderPagination(total, currentPage, pageSize) {
$('#paginationContainer').empty()
laypage.render({
elem: 'paginationContainer', // 元素 id
count: total, // 数据总数
limits: [10, 20, 30, 40, 50],
curr: currentPage, // 使用最新的当前页码
limit: pageSize,
layout: ['count', 'prev', 'page', 'next', 'limit', 'skip'], // 功能布局
jump: function(obj, first){
pageSize = obj.limit; // 每页条数
currentPage = obj.curr; // 当前页
// 首次不执行
if (!first) {
generatorTr(pageSize, currentPage) // 每次选择分页后重新渲染数据行
}
}
});
}
renderPagination(0, 1, 10) // 渲染分页组件
读取excel表格数据
// key值中英文映射,方便后端接收
const fieldMapping = {
'部门': 'dept',
'部门代码': 'deptCode',
'工号': 'username',
'姓名': 'name',
};
// 一键导入数据
window.inDateByExcel = function (e) {
var file = e.target.files[0]; // 得到上传的文件
var type = file.name.split('.'); // 得到一个数组存放文件名和文件后缀
// 判断上传的文件是否是excel 类型,不是则提示
if (type[type.length - 1] !== 'xlsx' && type[type.length - 1] !== 'xls') {
layer.alert('只能导入excel文件', {
title: '上传失败', icon: 5, time: 3000
});
e.target.value = '' // 重置文件选中
return false;
}
allData = [] // 每次读取时清空
id = 0 // 唯一表示
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (e) {
var data = new Uint8Array(reader.result);
var workbook = XLSX.read(data, {type: 'array'});
// 假设我们只读取第一个工作表
var firstSheetName = workbook.SheetNames[0];
var worksheet = workbook.Sheets[firstSheetName];
// 将工作表转换为JSON数组赋值给allData
var json = XLSX.utils.sheet_to_json(worksheet);
// 转换key:中文 → 英文
const convertedData = json.map(row => {
const newRow = {};
// Object.entries(row) 将对象转换为二维数组,每次循环取出 [key, value] 并赋值给变量。
for (const [key, value] of Object.entries(row)) {
const englishKey = fieldMapping[key] || key;
newRow[englishKey] = value;
}
return {
id: id++, // 添加唯一ID
...newRow
};
});
// 追加到全局数组
allData.push(...convertedData);
// console.log(JSON.stringify(json))
let pageSize = allData.length <= 10 ? allData.length : 10 // 当导入的行数小于10时,一页显示实际数量,否则一页显示10条
generatorTr(pageSize, 1) // 调用生成行的方法
}
e.target.value = '' // 重置文件选中
}
通过分页控制组件条数,动态生成行组件。其中分页中最重要的就是数据分割,需要确定数据起止索引位置。
开始索引位置计算:start = (当前页码 - 1) * 每页数据量大小
// 最后一页不足页面数据量大小时,按实际数量显示
结束索引位置计算: const end = Math.min(start + pageSize, allData.length);
// 分割元素数组并将分割的数据赋值给 pageData 变量
const pageData = allData.slice(start, end);
// 选择分页条数动态生成行
function generatorTr(pageSize, currentPage) {
// 清空行
$('.itemTr').remove()
// 计算当前页数据
const start = (currentPage - 1) * pageSize;
const end = Math.min(start + pageSize, allData.length);
const pageData = allData.slice(start, end);
// 添加输入框
for (let j = 0; j < (end - start); j++) {
$("#f").before('<tr class="itemTr">\n' +
' <td>' +
' <select id="deptCode' + k +'" lay-filter="deptCodeFilter" lay-verify="required" lay-search="">' + deptCodeStr + '</select>' +
' </td>\n' +
' <td><input type="text" autocomplete="off" id="dept' + k + '" class="layui-input projectCode" ></td>\n' +
' <td><input type="text" autocomplete="off" id="username' + k + '" lay-verify="required" class="layui-input" ></td>\n' +
' <td><input type="text" autocomplete="off" id="name' + k + '" lay-verify="required" class="layui-input" ></td>\n' +
' <th style="width: 100px">\n' +
' <input type="hidden" id="id' + k + '" class="layui-input" >\n' +
' <div style="margin: 5px"><button type="button" onclick="delList(this);" title="删除" class="layui-btn layui-btn-sm layui-btn-danger">删除</button></div>' +
' <div style="margin: 5px"><button type="button" onclick="edtList(this);" title="修改" class="layui-btn layui-btn-sm layui-btn-normal">修改</button></div>' +
' </th>\n' +
' </tr>');
$('#id' + k).val(pageData[j].id); // id标识
$('#dept' + k).val(pageData[j].dept); // 部门
$('#deptCode' + k).val(pageData[j].deptCode); // 部门代码
$('#username' + k).val(pageData[j].username); // 工号
$('#name' + k).val(pageData[j].name); // 姓名
k++;
}
renderPagination(allData.length, currentPage, pageSize) // 重新渲染
}
删除、修改行数据。因为表单渲染只渲染了部分数据,通过赋值给输入框,name属性自动与后端绑定的方式不可行。因此,需要通过formData的方式拼接数据。所以需要修改某一行数据时,实际是对存放数据的数组进行删除或修改。这里由于我传递了一个数组,直接将数组转化成json格式。
// 删除行
window.delList = function(e) {
var id = parseInt($(e).closest('tr').find('th input').val()) // 被删行的数据id
$(e).closest('tr').remove();
// totalCount = $('.itemTr').length
// 从数组中过滤删除
allData = allData.filter(item => item.id !== id);
renderPagination(allData.length, currentPage, pageSize) // 重新渲染分页组件
}
// 修改行
window.edtList = function(e) {
var id = parseInt($(e).closest('tr').find('th input').val()) // 行的数据id
const $row = $(e).closest('tr');
// 获取输入框的值
var dept = $row.find('td:eq(0) select').val()
var deptCode = $row.find('td:eq(1) input').val()
var username = $row.find('td:eq(2) input').val()
var name = $row.find('td:eq(3) input').val()
allData.forEach(item => {
if (item.id === id) {
item.dept = dept
item.deptCode = deptCode
item.username = username
item.name = name
}
});
layer.msg('修改成功', {icon: 6, time: 2000})
}
表单提交事件请求
// 监听表单提交事件
$('#submitBtn').on('click', function() {
$('#submitBtn').addClass('layui-btn-disabled');
$('#submitBtn').attr('disabled', true);
const data = {
list: allData,
factory: $('#factorySelect').val(),
};
$.ajax({
url: '/overtimeWork/apply',
type: 'post',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify(data),
success: function(res) {
if (res.code === 0) {
// 成功后关闭页面
layer.msg(res.msg || '提交成功', { icon: 6, time: 2000 }, function() {
window.close()
});
} else {
layer.msg(res.msg || '提交失败', { icon: 2 });
}
},
error: function(res) {
layer.msg(res.msg || '系统错误', { icon: 2 });
$('#submitBtn').removeClass('layui-btn-disabled');
$('#submitBtn').attr('disabled', false);
}
})
})
后端接口
// 申请
@PostMapping("/apply")
@ResponseBody
public ResultBean apply(@RequestBody BaseInfo baseInfo ) {
myService.apply(baseInfo );
return ResultBean.success("申请成功");
}
注意:
- 如果是 json格式的数据,需要添加 @RequestBody 注解, 此时 contentType 格式为 'application/json'。如果是通过原生 HTML
<form>表单提交时提交方式不用此注解,因为原始方式表单的 contentType 是application/x-www-form-urlencoded。
默认情况下,form表单提交时list集合是有大小限制的。所以需要重新配置大小
/**
* 局部配置List集合大小,form表单提交时list集合默认为256大小
* */
@InitBinder
public void initBinder(WebDataBinder binder) {
// 设置处理嵌套属性
binder.setAutoGrowNestedPaths(true);
// 配置集合上限数量
binder.setAutoGrowCollectionLimit(Integer.MAX_VALUE);
}