layui表单项次大数据量导入并提交

场景:通过excel批量导入数据,将数据回显在页面上,在渲染layui组件时消耗浏览器大量内存导致页面崩溃。

解决方法:通过分页的方式,只显示部分数据,减少内存消耗。

具体代码

  1. html代码块,layui分页组件

    <!- 批量导入按钮 -->

    批量导入:

    <!- 分页组件 -->

    <!- 表单提交按钮 -->

    <button type="button" class="layui-btn" lay-filter="submitBtn" id="submitBtn">立即提交</button>
  2. 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("申请成功");
    }

注意

  1. 如果是 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);
    }
相关推荐
张小潇2 小时前
AOSP15 WMS/AMS系统开发 - 窗口层级源码分析
android·前端
whuhewei2 小时前
HTTP1/2/3演变
前端·计算机网络
腹黑天蝎座2 小时前
从零实现一个前端监控系统:性能、错误与用户行为全方位监控
前端·监控
Hello--_--World3 小时前
ES13:类私有属性和方法、顶层 await、at() 方法、Object.hasOwnProperty()、类静态块 相关知识点
开发语言·javascript·es13
comerzhang6553 小时前
Web 性能的架构边界:跨线程信令通道的确定性分析
javascript·webassembly
Hooray3 小时前
为了在 Vue 项目里用上想要的 React 组件,我写了这个 skill
前端·ai编程
咸鱼翻身了么3 小时前
模仿ai数据流 开箱即用
前端
风花雪月_3 小时前
🔥IntersectionObserver:前端性能优化的“隐形监工”
前端
Bigger3 小时前
告别 AI 塑料感:我是如何用 frontend-design skill 重塑项目官网的
前端·ai编程·trae