精华内容
下载资源
问答
  • 实现对表格数据的头几行或者头几列进行冻结,即滚动保持这几行/不动,通过网上查找代码,参考已有的代码的思路,实现了可以任意对列进行冻结、实现原理: 创建多个div,div之间通过css实现层叠,每...

    实现对表格数据的头几行或者头几列进行冻结,即滚动时保持这几行/列不动,通过网上查找代码,参考已有的代码的思路,实现了可以任意对行、列进行冻结。

    一、实现原理:

    创建多个div,div之间通过css实现层叠,每个div放置当前表格的克隆。

    例如:需要行冻结时,创建存放冻结行表格的div,通过设置z-index属性和position属性,让冻结行表格在数据表格的上层。同理,需要列冻结时,创建存放冻结列表格的div,并放置在数据表格的上层。如果需要行列都冻结时,则除了创建冻结行、冻结列表格的div,还需要创建左上角的固定行列表格的div,并放置在所有div的最上层。

    处理表格的滚动事件,在表格横向或者纵向滚动时,同时让相应的冻结行和冻结列也同步滚动。

    处理html的resize事件,同步修改表格的滚动区域宽度和高度

    二、核心代码

    /*
     * 锁定表头和列
     * 
     * 参数定义
     *   table - 要锁定的表格元素或者表格ID
     *   freezeRowNum - 要锁定的前几行行数,如果行不锁定,则设置为0
     *   freezeColumnNum - 要锁定的前几列列数,如果列不锁定,则设置为0
     *   width - 表格的滚动区域宽度
     *   height - 表格的滚动区域高度
     */
    function freezeTable(table, freezeRowNum, freezeColumnNum, width, height) {
    
        //获取冻结行数或者列数
          if (typeof(freezeRowNum) == 'string')
            freezeRowNum = parseInt(freezeRowNum)
    
          if (typeof(freezeColumnNum) == 'string')
            freezeColumnNum = parseInt(freezeColumnNum)
         //获取table
          var tableId;
          if (typeof(table) == 'string') {
            tableId = table;
            table = $('#' + tableId);
          } else
            tableId = table.attr('id');
    
        /**
         * 生成最外层的DIV用来承载内部的四个DIV
         */
          var divTableLayout = $("#" + tableId + "_tableLayout");
    
          if (divTableLayout.length != 0) {
            divTableLayout.before(table);
            divTableLayout.empty();
          } else {
            table.after("<div id='" + tableId + "_tableLayout' style='overflow:hidden;height:" + height + "px; width:" + width + "px;'></div>");
    
            divTableLayout = $("#" + tableId + "_tableLayout");
          }
    
        /**
         * 根据需要页面table定义的属性 需要冻结的行或者列 ,对应的拼接字符串用于生成不同的DIV  【如果行列同时冻结:生成总共四个DIV】【单独冻结行或列,仅需要生成两个DIV】
         * 这个四个DIV都是包括数据在内,完全克隆了原本的table,
         */
          var html = '';
          if (freezeRowNum > 0 && freezeColumnNum > 0)
            html += '<div id="' + tableId + '_tableFix" style="padding: 0px;"></div>';
    
          if (freezeRowNum > 0)
            html += '<div id="' + tableId + '_tableHead" style="padding: 0px;"></div>';
    
          if (freezeColumnNum > 0)
            html += '<div id="' + tableId + '_tableColumn" style="padding: 0px;"></div>';
    
          html += '<div id="' + tableId + '_tableData" style="padding: 0px;"></div>';
    
           //将div追加到页面
          $(html).appendTo("#" + tableId + "_tableLayout");
    
          var divTableFix = freezeRowNum > 0 && freezeColumnNum > 0 ? $("#" + tableId + "_tableFix") : null;
          var divTableHead = freezeRowNum > 0 ? $("#" + tableId + "_tableHead") : null;
          var divTableColumn = freezeColumnNum > 0 ? $("#" + tableId + "_tableColumn") : null;
          var divTableData = $("#" + tableId + "_tableData"); //位于最底层的【数据DIV】【第一个DIV,也就是原本的那个真身】
    
          divTableData.append(table);
           //行列同时冻结生成的【行列DIV】【第二个DIV】,一般位于左上角重叠部分
          if (divTableFix != null) {
            var tableFixClone = table.clone(true);    //克隆1
            tableFixClone.attr("id", tableId + "_tableFixClone");
            divTableFix.append(tableFixClone);
          }
           //行冻结生成的【冻结行DIV】【第三个DIV】
          if (divTableHead != null) {
            var tableHeadClone = table.clone(true);//克隆2
            tableHeadClone.attr("id", tableId + "_tableHeadClone");
            divTableHead.append(tableHeadClone);
          }
           //列冻结生成的【冻结列DIV】【第四个DIV】
          if (divTableColumn != null) {
            var tableColumnClone = table.clone(true);//克隆3
            tableColumnClone.attr("id", tableId + "_tableColumnClone");
            divTableColumn.append(tableColumnClone);
          }
    
          $("#" + tableId + "_tableLayout table").css("margin", "0");
    
        /**
         * 根据冻结行数,设置行冻结的div的高度【如果行列同时冻结,则行列div也设置对应高度】
         */
          if (freezeRowNum > 0) {
            var HeadHeight = 0;
            var ignoreRowNum = 0;
            $("#" + tableId + "_tableHead tr:lt(" + freezeRowNum + ")").each(function () {
              if (ignoreRowNum > 0)
                ignoreRowNum--;
              else {
                var td = $(this).find('td:first, th:first');
                HeadHeight += td.outerHeight(true);
    
                ignoreRowNum = td.attr('rowSpan');
                if (typeof(ignoreRowNum) == 'undefined')
                  ignoreRowNum = 0;
                else
                  ignoreRowNum = parseInt(ignoreRowNum) - 1;
              }
            });
            HeadHeight += 2;
    
            divTableHead.css("height", HeadHeight);
            divTableFix != null && divTableFix.css("height", HeadHeight);
          }
    
        /**
         * 根据冻结列数,对冻结列DIV设置宽度【如果行列同时冻结,则行列div也设置对应宽度】
         */
          if (freezeColumnNum > 0) {
            var ColumnsWidth = 0;
            var ColumnsNumber = 0;
            $("#" + tableId + "_tableColumn tr:eq(" + freezeRowNum + ")").find("td:lt(" + freezeColumnNum + "), th:lt(" + freezeColumnNum + ")").each(function () {
              if (ColumnsNumber >= freezeColumnNum)
                return;
    
              ColumnsWidth += $(this).outerWidth(true);
    
              ColumnsNumber += $(this).attr('colSpan') ? parseInt($(this).attr('colSpan')) : 1;
            });
            ColumnsWidth += 2;
    
            divTableColumn.css("width", ColumnsWidth);
            divTableFix != null && divTableFix.css("width", ColumnsWidth);
          }
    
            //滚动
          divTableData.scroll(function () {
            divTableHead != null && divTableHead.scrollLeft(divTableData.scrollLeft());
    
            divTableColumn != null && divTableColumn.scrollTop(divTableData.scrollTop());
          });
    
          divTableFix != null && divTableFix.css({ "overflow": "hidden", "position": "absolute", "z-index": "50" });
          divTableHead != null && divTableHead.css({ "overflow": "hidden", "width": width - 17, "position": "absolute", "z-index": "45" });
          divTableColumn != null && divTableColumn.css({ "overflow": "hidden", "height": height - 17, "position": "absolute", "z-index": "40" });
          divTableData.css({ "overflow": "scroll", "width": width, "height": height, "position": "absolute" });
    
          divTableFix != null && divTableFix.offset(divTableLayout.offset());
          divTableHead != null && divTableHead.offset(divTableLayout.offset());
          divTableColumn != null && divTableColumn.offset(divTableLayout.offset());
          divTableData.offset(divTableLayout.offset());
    }

    三、辅助代码

    1、调整表的高度和宽度
    /*
     * 调整锁定表的宽度和高度,这个函数在resize事件中调用
     * 
     * 参数定义
     *   table - 要锁定的表格元素或者表格ID
     *   width - 表格的滚动区域宽度
     *   height - 表格的滚动区域高度
     */
    function adjustTableSize(table, width, height) {
      var tableId;
      if (typeof(table) == 'string')
        tableId = table;
      else
        tableId = table.attr('id');
    
      $("#" + tableId + "_tableLayout").width(width).height(height);
      $("#" + tableId + "_tableHead").width(width - 17);
      $("#" + tableId + "_tableColumn").height(height - 17);
      $("#" + tableId + "_tableData").width(width).height(height);
    }
    2、返回当前页面高度
    function pageHeight() {
      if ($.browser.msie) {
        return document.compatMode == "CSS1Compat" ? document.documentElement.clientHeight : document.body.clientHeight;
      } else {
        return self.innerHeight;
      }
    };
    3、返回当前页面宽度
    function pageWidth() {
      if ($.browser.msie) {
        return document.compatMode == "CSS1Compat" ? document.documentElement.clientWidth : document.body.clientWidth;
      } else {
        return self.innerWidth;
      }
    };

    四、使用方法

    在js中进行加载,在需要使用的方法后边调用此js函数:freezeTable(table, freezeRowNum, freezeColumnNum, width, height);

    table - 需要冻结的表格Table的Id属性;

    freezeRowNum - 要锁定几行的行数,不锁定为0;

    freezeColumnNum - 要锁定几列的列数,不锁定为0;

    width - 表格滚动区域宽度;

    height - 表格滚动区域高度;

    例子:

    $(function(){
        //调用表格冻结事件
        freezeTable("Tables","0","2","1920","1080");
    })
    $(window).resize(function() {
        //在窗口变化时调整冻结表格的高度和宽度
        adjustTableSize("Tables",pageWidth(), pageHeight());
    });

    这样,表格就固定了,可能你看不到你的页面被冻结,或者进去之后页面样式错乱,这个时候就需要使用检查元素,检查固定的div表格位置是否正确,背景是否透明,通过css样式调整即可。

    主要是设置冻结列或者行的高度和宽度,将冻结的列或者行正好盖住需要冻结的列或者行上边,即可实现冻结前几列或者前几行,其余行和列滚动!

    展开全文
  • 这篇文章主要介绍了JS 组件系列之Bootstrap Table 冻结列功能IE浏览器兼容性问题解决方案,需要的朋友可以参考前言:最近项目里面需要用到表格的冻结列功能,所谓“冻结列”,就是某些情况表格的比较多,需要...

    这篇文章主要介绍了JS 组件系列之Bootstrap Table 冻结列功能IE浏览器兼容性问题解决方案,需要的朋友可以参考下

    前言:最近项目里面需要用到表格的冻结列功能,所谓“冻结列”,就是某些情况下表格的列比较多,需要固定前面的几列,后面的列滚动。遗憾的是,bootstrap table里自带的fixed column功能有一点bug,于是和同事讨论该如何解决,于是就有了这篇文章。

    一、起因回顾

    最近项目里面有一个表格需求,该表格列是动态产生的,而且列的数量操作一定值以后就会出现横向滚动条,滚动的时候需要前面几列固定。也就是所谓的excel的冻结列功能。该如何实现呢?不用多说,当然是查文档,于是找到了这篇issues.wenzhixin.net.cn/bootstrap-table/index.html#extensions/fixed-columns.html。谷歌浏览器效果如下:

    第一列固定

    421801d975852705fb6fb936f4dc7510.png

    7dde7b2c5c033c8945709ce41e11401b.png

    貌似问题完美解决!可是,事与愿违,很遗憾,上面说了,这是谷歌浏览器的效果,没有问题。我们来看看IE里面

    IE11效果:

    29d7a385dd60ecd0294826bc89a13671.png

    IE10效果:

    701a76d39c03dbf44ae2cd8fc532170a.png

    很显然,不管是IE内核版本多少,冻结的列里面的内容都无法显示。怎么办?这可为难死宝宝了!

    二、解决方案

    还好有万能的开源,查看该页面源代码发现可以找到冻结列这个js的源码。

    e042823f24f035c1d972c2b04a6b83df.png

    点击进入可以看到这个js的所有源码,找到源码就好办了,我们试着改改源码看是否能解决这个bug。

    我们在bootstrap-table下面的extensions文件夹下面新增加一个文件夹fixed-column

    9edccc084d9546ea686fcc2ed9846225.png

    下面就贴出我们改好的源码:(function ($) {

    'use strict';

    $.extend($.fn.bootstrapTable.defaults, {

    fixedColumns: false,

    fixedNumber: 1

    });

    var BootstrapTable = $.fn.bootstrapTable.Constructor,

    _initHeader = BootstrapTable.prototype.initHeader,

    _initBody = BootstrapTable.prototype.initBody,

    _resetView = BootstrapTable.prototype.resetView;

    BootstrapTable.prototype.initFixedColumns = function () {

    this.$fixedBody = $([

    '

    ',

    '

    '',

    '

    ',

    '

    ',

    '

    '].join(''));

    this.timeoutHeaderColumns_ = 0;

    this.timeoutBodyColumns_ = 0;

    this.$fixedBody.find('table').attr('class', this.$el.attr('class'));

    this.$fixedHeaderColumns = this.$fixedBody.find('thead');

    this.$fixedBodyColumns = this.$fixedBody.find('tbody');

    this.$tableBody.before(this.$fixedBody);

    };

    BootstrapTable.prototype.initHeader = function () {

    _initHeader.apply(this, Array.prototype.slice.apply(arguments));

    if (!this.options.fixedColumns) {

    return;

    }

    this.initFixedColumns();

    var $tr = this.$header.find('tr:eq(0)').clone(),

    $ths = $tr.clone().find('th');

    $tr.html('');

    for (var i = 0; i < this.options.fixedNumber; i++) {

    $tr.append($ths.eq(i).clone());

    }

    this.$fixedHeaderColumns.html('').append($tr);

    };

    BootstrapTable.prototype.initBody = function () {

    _initBody.apply(this, Array.prototype.slice.apply(arguments));

    if (!this.options.fixedColumns) {

    return;

    }

    var that = this;

    this.$fixedBodyColumns.html('');

    this.$body.find('> tr[data-index]').each(function () {

    var $tr = $(this).clone(),

    $tds = $tr.clone().find('td');

    $tr.html('');

    for (var i = 0; i < that.options.fixedNumber; i++) {

    $tr.append($tds.eq(i).clone());

    }

    that.$fixedBodyColumns.append($tr);

    });

    };

    BootstrapTable.prototype.resetView = function () {

    _resetView.apply(this, Array.prototype.slice.apply(arguments));

    if (!this.options.fixedColumns) {

    return;

    }

    clearTimeout(this.timeoutHeaderColumns_);

    this.timeoutHeaderColumns_ = setTimeout($.proxy(this.fitHeaderColumns, this), this.$el.is(':hidden') ? 100 : 0);

    clearTimeout(this.timeoutBodyColumns_);

    this.timeoutBodyColumns_ = setTimeout($.proxy(this.fitBodyColumns, this), this.$el.is(':hidden') ? 100 : 0);

    };

    BootstrapTable.prototype.fitHeaderColumns = function () {

    var that = this,

    visibleFields = this.getVisibleFields(),

    headerWidth = 0;

    this.$body.find('tr:first-child:not(.no-records-found) > *').each(function (i) {

    var $this = $(this),

    index = i;

    if (i >= that.options.fixedNumber) {

    return false;

    }

    if (that.options.detailView && !that.options.cardView) {

    index = i - 1;

    }

    that.$fixedBody.find('thead th[data-field="' + visibleFields[index] + '"]')

    .find('.fht-cell').width($this.innerWidth() - 1);

    headerWidth += $this.outerWidth();

    });

    this.$fixedBody.width(headerWidth - 1).show();

    };

    BootstrapTable.prototype.fitBodyColumns = function () {

    var that = this,

    top = -(parseInt(this.$el.css('margin-top')) - 2),

    height = this.$tableBody.height() - 2;

    if (!this.$body.find('> tr[data-index]').length) {

    this.$fixedBody.hide();

    return;

    }

    this.$body.find('> tr').each(function (i) {

    that.$fixedBody.find('tbody tr:eq(' + i + ')').height($(this).height() - 1);

    });

    events

    this.$tableBody.on('scroll', function () {

    that.$fixedBody.find('table').css('top', -$(this).scrollTop());

    });

    this.$body.find('> tr[data-index]').off('hover').hover(function () {

    var index = $(this).data('index');

    that.$fixedBody.find('tr[data-index="' + index + '"]').addClass('hover');

    }, function () {

    var index = $(this).data('index');

    that.$fixedBody.find('tr[data-index="' + index + '"]').removeClass('hover');

    });

    this.$fixedBody.find('tr[data-index]').off('hover').hover(function () {

    var index = $(this).data('index');

    that.$body.find('tr[data-index="' + index + '"]').addClass('hover');

    }, function () {

    var index = $(this).data('index');

    that.$body.find('> tr[data-index="' + index + '"]').removeClass('hover');

    });

    };

    })(jQuery);.fixed-table-container thead th .th-inner, .fixed-table-container tbody td .th-inner {

    line-height: 18px;

    }

    .fixed-table-pagination .pagination a {

    padding: 5px 10px;

    }

    .fixed-table-toolbar .bars, .fixed-table-toolbar .search, .fixed-table-toolbar .columns {

    margin-top: 5px;

    margin-bottom: 5px;

    }

    主要修改的地方:

    1)源码里面将thead和tbody分别封装成了一个单独的表格,修改后将thead和tbody放到了一个table里面;

    2)依次遍历冻结的列放入到固定的tbody里面;

    其实也就改了那么几个地方,就能完美解决IE的bug。我们先来看看效果:

    IE11

    f942bb14b7cdf353dde5deed5c72bd4d.png

    IE10

    4b3a72b8df48f096b7c3bee41c928700.png

    IE8

    aa1baef8747905ac285966d5560af26d.png

    我们再来看看如何使用。

    1、引用js和对应的css

    2、js调用如下

    6834f78c021628484ef85da1b85c0bd7.png

    加两个参数fixedColumns和fixedNumber即可,什么意思不用过多解释,是否冻结列、冻结列的列数。还有一点需要说明的是,这里调用的时候不能指定它的height,如果指定height,表格的冻结显示会有问题。

    展开全文
  • 文章目录、按读easyExcel核心原理按读二、按读(按单元格读:适合模板型的excel)解析单元格的小demo实战:解析工具类(最重要)如何使用: 近期有个需求,说是用到excel导入导出,一般我们的想法都是按照...

    近期有个需求,说是要用到excel导入导出,一般我们的想法都是按照行数,于是实现了,后面发现公司需求的是列读,甚至不规则的单个excel的读。于是就用poi自己写了按照单元格读的实现。

    一、按行读

    想起了之前用到的poi,经过搜索发现,开源的项目中有比较好的封装poi的框架,一个是阿里出的easyExcel,另一个是easypoi,感觉使用起来都很方便。网上说easyExcel能解决大文件内存溢出问题,于是项目中就使用easyExcel了。

    简单普及下easyExcel原理,不做底层码农,了解点上层设计有好处:

    easyExcel核心原理

    写有大量数据的xlsx文件时,POI为我们提供了SXSSFWorkBook类来处理,这个类的处理机制是当内存中的数据条数达到一个极限数量的时候就flush这部分数据,再依次处理余下的数据,这个在大多数场景能够满足需求。
    读有大量数据的文件时,使用WorkBook处理就不行了,因为POI对文件是先将文件中的cell读入内存,生成一个树的结构(针对Excel中的每个sheet,使用TreeMap存储sheet中的行)。
    如果数据量比较大,则同样会产生java.lang.OutOfMemoryError: Java heap space错误。POI官方推荐使用“XSSF and SAX(event API)”方式来解决。
    分析清楚POI后要解决OOM有3个关键.

    • 读取的数据转换流程
      在这里插入图片描述
    • easyexcel解析数据的 设计思想和相关角色。
      在这里插入图片描述

    根据上面官网给的信息,我们得有个模型来接收每行的数据,本例用CommonUser对象,该对象上在这上面也可以加数据校验,还需要个解析每个行的监听器CommonUserListener,可以来处理每行的数据,然后进行数据库操作读写。

    来个小demo(用的mybatis-plus框架)

    controler

    @RestController
    @RequestMapping("info/commonuser")
    public class CommonUserController {
        @Autowired
        private CommonUserService commonUserService;
        /**
         * excel导入(按照行读)
         * <p>
         * 1. 创建excel对应的实体对象 参照{@link CommonUser}
         * <p>
         * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link CommonUserListener}
         * <p>
         * 3. 直接读即可
         */
        @PostMapping("upload")
        @ResponseBody
        public String upload(MultipartFile file) throws IOException {
            EasyExcel.read(file.getInputStream(), CommonUser.class, new CommonUserListener(commonUserService))
                    .sheet()
                    .doRead();
            return "success";
        }
    
        /**
         * 文件下载(失败了会返回一个有部分数据的Excel)
         * <p>
         * 1. 创建excel对应的实体对象 参照{@link CommonUser}
         * <p>
         * 2. 设置返回的 参数
         * <p>
         * 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大
         */
        @GetMapping("download")
        public void download(HttpServletResponse response) throws IOException {
            // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
            response.setContentType("application/vnd.ms-excel");
            response.setCharacterEncoding("utf-8");
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            String fileName = URLEncoder.encode("用户表", "UTF-8");
            response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
            EasyExcel.write(response.getOutputStream(), CommonUser.class).sheet("模板").doWrite(data());
        }
    
    
    
        /**
         * excel导出
         * 文件下载并且失败的时候返回json(默认失败了会返回一个有部分数据的Excel)
         *
         * @since 2.1.1
         */
        @GetMapping("downloadFailedUsingJson")
        public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {
            // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
            try {
                response.setContentType("application/vnd.ms-excel");
                response.setCharacterEncoding("utf-8");
                // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
                String fileName = URLEncoder.encode("测试", "UTF-8");
                response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
                // 这里需要设置不关闭流
                EasyExcel.write(response.getOutputStream(), CommonUser.class).autoCloseStream(Boolean.FALSE).sheet("模板")
                        .doWrite(data());
            } catch (Exception e) {
                // 重置response
                response.reset();
                response.setContentType("application/json");
                response.setCharacterEncoding("utf-8");
                Map<String, String> map = new HashMap<String, String>();
                map.put("status", "failure");
                map.put("message", "下载文件失败" + e.getMessage());
                response.getWriter().println(new Gson().toJson(map));
            }
        }
    
        private List<CommonUser> data() {
            List<CommonUser> list = commonUserService.list();
            return list;
        }
    }
    

    CommonUserService (和读excel无关,业务需要)

    /**
     * 用户Service
     *
     * @author hfl 690328661@qq.com
     * @date 2020-05-16 08:42:50
     */
    public interface CommonUserService extends IService<CommonUser> {
    
    
    }
    

    CommonUserServiceImpl

    @Service("commonUserService")
    public class CommonUserServiceImpl extends ServiceImpl<CommonUserMapper, CommonUser> implements CommonUserService {
        private final static Logger logger = LoggerFactory.getLogger(CommonUserServiceImpl.class);
    
    
    }
    
    

    CommonUserListener(负责获取每行的数据,然后根据需要进行db保存)

    public class CommonUserListener extends AnalysisEventListener<CommonUser> {
        private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class);
    
        /**
         * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
         * mybatis-plus 默认1000
         */
        private static final int BATCH_COUNT = 1000;
        List<CommonUser> list = new ArrayList<>();
    
        /**
         * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
         */
        private CommonUserService commonUserService;
    
        public CommonUserListener(CommonUserService commonUserService) {
            // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
    
            this.commonUserService = commonUserService;
        }
    
        /**
         * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
         */
        @Override
        public void invoke(CommonUser data, AnalysisContext context) {
            LOGGER.info("解析到一条数据:{}", new Gson().toJson(data));
            list.add(data);
            // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
            if (list.size() >= BATCH_COUNT) {
                saveData();
                // 存储完成清理 list
                list.clear();
            }
        }
    
        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
            // 这里也要保存数据,确保最后遗留的数据也存储到数据库
            saveData();
            LOGGER.info("所有数据解析完成!");
        }
    
        /**
         * 加上存储数据库
         */
        private void saveData() {
            LOGGER.info("{}条数据,开始存储数据库!", list.size());
            commonUserService.saveBatch(list);
            LOGGER.info("存储数据库成功!");
        }
    }
    
    

    CommonUserMapper(数据库操作的)

    /**
     * 用户表
     * 
     * @author hfl
    
     * @date 2020-05-16 08:42:50
     */
    @Mapper
    public interface CommonUserMapper extends BaseMapper<CommonUser> {
    	
    }
    

    实体对象

    @Data
    @TableName("common_user")
    public class CommonUser extends BaseEntity {
    
    	/**
    	 * 用户ID
    	 */
    	@TableId
    	private String userId;
    	/**
    	 * 用户名
    	 */
    	@ExcelProperty("字符串标题")
    	private String userName;
    	/**
    	 * 真实姓名
    	 */
    	private String userRealname;
    	/**
    	 * 密码
    	 */
    	private String userPassword;
    	/**
    	 * 盐
    	 */
    	private String userSalt;
    	/**
    	 * 手机号码
    	 */
    	private String userMobile;
    	/**
    	 * 性别
    	 */
    	private String userSex;
    	/**
    	 * 头像url
    	 */
    	private String userAvatar;
    	/**
    	 * 电子邮箱
    	 */
    	private String userEmail;
    	/**
    	 * 账号状态(0-正常,1-冻结)
    	 */
    	private Integer userStatus;
    	/**
    	 * 扩展字段
    	 */
    	private String userEx;
    
    	@Override
    	public String toString() {
    		return "CommonUser{" +
    				"userId='" + userId + '\'' +
    				", userName='" + userName + '\'' +
    				", userRealname='" + userRealname + '\'' +
    				", userPassword='" + userPassword + '\'' +
    				", userSalt='" + userSalt + '\'' +
    				", userMobile='" + userMobile + '\'' +
    				", userSex='" + userSex + '\'' +
    				", userAvatar='" + userAvatar + '\'' +
    				", userEmail='" + userEmail + '\'' +
    				", userStatus=" + userStatus +
    				", userEx='" + userEx + '\'' +
    				'}';
    	}
    }
    
    

    数据库表结构:

    CREATE TABLE `common_user` (
      `user_id` varchar(32) NOT NULL COMMENT '用户ID',
      `user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
      `user_realname` varchar(50) DEFAULT NULL COMMENT '真实姓名',
      `user_password` varchar(50) DEFAULT NULL COMMENT '密码',
      `user_salt` varchar(50) DEFAULT NULL COMMENT '盐',
      `user_mobile` varchar(20) DEFAULT NULL COMMENT '手机号码',
      `user_sex` varchar(20) DEFAULT NULL COMMENT '性别',
      `user_avatar` varchar(255) DEFAULT NULL COMMENT '头像url',
      `user_email` varchar(50) DEFAULT NULL COMMENT '电子邮箱',
      `user_status` tinyint(1) DEFAULT NULL COMMENT '账号状态(0-正常,1-冻结)',
      `user_ex` text COMMENT '扩展字段',
      `creater` varchar(32) DEFAULT NULL COMMENT '创建者',
      `modifier` varchar(32) DEFAULT NULL COMMENT '修改者',
      `create_time` datetime DEFAULT NULL COMMENT '创建时间',
      `update_time` datetime DEFAULT NULL COMMENT '更新时间',
      `is_deleted` tinyint(1) DEFAULT NULL COMMENT '0:未删除,1:删除',
      PRIMARY KEY (`user_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
    
    

    启动项目:
    在这里插入图片描述
    使用postman先把本地的数据库数据导出到excel中试试:
    浏览器中输入: localhost:7000/info/commonuser/downloadFailedUsingJson

    在这里插入图片描述
    可以看出数据库中的被导出来了: 标题头和实体中的@ExcelProperty对应:这里只写了字符串标题这个表头,其他的会默认显示英文字段。
    在这里插入图片描述

    按行读

    将刚才数据库中的数据全部删除:导入刚才的Excel数据试试

    在这里插入图片描述
    在这里插入图片描述
    使用post测试:
    在这里插入图片描述
    在这里插入图片描述
    可以看到控制已经显示成功,
    在这里插入图片描述
    看下数据库:显示已经按照行读,并写入数据库中了
    在这里插入图片描述

    二、按列读(按单元格读:适合模板型的excel)

    上面的导入excel数据是按照行读的,但是我的需求是这样的:
    在这里插入图片描述
    列形式,很多个sheet都不一样,怎么处理呢?
    于是想到,easyExcel肯定是实现不了了,干脆使用poi自带的按照单元格自己去读。

    思路: 每个模板excel对应一个实体,每个单元格的位置(行号和列号)在实体上通过注解对应好,这样我解析单元格,取出每个单元格的值,和位置,赋值给对应的实体的属性。

    解析单元格的小demo

    因为easyExcel默认引用poi,所以不需要引maven包,直接写就好了:

      @Test
        public void AllExcelRead() throws IOException, InvalidFormatException {
            //1、指定要读取EXCEL文档名称
            String filename="C:\\Users\\69032\\Desktop\\vechicleService.xlsx";
            //2、创建输入流
            FileInputStream input=new FileInputStream(filename);
            //3、通过工作簿工厂类来创建工作簿对象
            Workbook workbook= WorkbookFactory.create(input);
            //4、获取工作表 (可以按照sheet名字,也可以按照sheet的索引)
            String sheetName = "车辆信息备案申请";
            Sheet sheet=workbook.getSheet(sheetName);
    //        Sheet sheet=workbook.getSheet("工作表1");
    //        Sheet sheet = workbook.getSheetAt(0);
            //5、获取行
            Row row=sheet.getRow(53);
            //6、获取单元格
            Cell cell=row.getCell(2);
            //7、读取单元格内容
            System.out.println(cell.getStringCellValue());
        }
    
    

    运行结果如下:
    在这里插入图片描述
    完美找到excel对应单元格的数据:
    在这里插入图片描述

    实战:

    定义个属性上的注解,指定某个单元格的属性:

    RecordTemplate

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RecordTemplate {
         //行号
         int rowNo();
         //列号
         int columnNo();
         //是否必填
         FillCommentEnum comment();
         //单元格名称
         String name();
         //数据类型
    
    }
    
    

    是否必填的枚举:
    FillCommentEnum

    /**
     * Title: FillCommentEnum
     * Description: 单元格 填充枚举
     *
     * @author hfl
     * @version V1.0
     * @date 2020-05-29
     */
    public enum FillCommentEnum {
    
        FILL(0, "必填"),
        EMPTY(1, "非必填");
    
        private  int code;
        private  String description;
    
        FillCommentEnum(int code, String description) {
            this.code = code;
            this.description = description;
        }
    
        public int getCode() {
            return code;
        }
    
        public void setCode(int code) {
            this.code = code;
        }
    
        public String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    }
    

    接下来,我需要定义实体和模板对应。

    @Data
    @TableName("vehicle_static_info")
    public class VehicleStaticInfo extends BaseEntity {
    
        /**
         * 车辆ID(主键)
         */
        @TableId
        private String vehicleId;
        /**
         * 最大基准扭矩
         */
        private String engineMaxTorque;
        /**
         * 车牌号码((GB17691-2005必填,GB17691-2018选填))
         * 此字段,数据库中已经移除,提取到车辆公共信息表中了
         */
        @RecordTemplate(rowNo = 3, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "车牌号码")
        @TableField(exist = false)
        private String vehicleLicense;
        /**
         * 车牌颜色(车牌颜色)
         */
        @RecordTemplate(rowNo = 4, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "车牌颜色")
        private String licenseColor;
        /**
         * 车体结构((编码见说明3))
         */
        @RecordTemplate(rowNo = 5, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "车体结构")
        private Integer vehicleStructure;
        /**
         * 车辆颜色
         */
        @RecordTemplate(rowNo = 6, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "车辆颜色")
        private String vehicleColor;
        /**
         * 核定载重
         */
        @RecordTemplate(rowNo = 7, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "核定载重")
        private Double vehicleLoad;
        /**
         * 车辆尺寸mm(长)(32960、17691等)
         */
        @RecordTemplate(rowNo = 8, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "车辆尺寸mm(长)")
        private Double vehicleLong;
        /**
         * 车辆尺寸mm(宽)(国六,新能源等字典)
         */
        @RecordTemplate(rowNo = 9, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "车辆尺寸mm(宽)")
        private Double vehicleWide;
        /**
         * 车辆尺寸mm(高)
         */
        @RecordTemplate(rowNo = 10, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "车辆尺寸mm(高)")
        private Double vehicleHigh;
        /**
         * 总质量
         */
        @RecordTemplate(rowNo = 11, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "总质量")
        private Double grossVehicleMass;
        /**
         * 车辆类型((编码见说明1))
         */
        @RecordTemplate(rowNo = 12, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "车辆类型(编码见右面1)")
        private Integer vehicleType;
        /**
         * 行业类型((编码见说明8 http://www.stats.gov.cn/tjsj/tjbz/hyflbz/201905/P020190716349644060705.pdf))
         */
        @RecordTemplate(rowNo = 13, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "行业类型(编码见右面8)")
        private String industryType;
        /**
         * 车辆型号((GB17691-2005必填,GB17691-2018选填))
         */
        @RecordTemplate(rowNo = 14, columnNo = 2, comment = FillCommentEnum.FILL, name = "车辆型号(GB17691-2005必填,GB17691-2018选填)")
        @NotEmpty(message = "车辆型号不能为空", groups = {ImportGroup.class})
        private String vehicleModel;
        /**
         * 购买时间
         */
        @RecordTemplate(rowNo = 15, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "购买时间")
        private String buyingDate;
        /**
         * 车架号VIN((必填))
         */
        @RecordTemplate(rowNo = 16, columnNo = 2, comment = FillCommentEnum.FILL, name = "车架号VIN(必填)")
        @NotEmpty(message = "车架号VIN不能为空", groups = {ImportGroup.class})
        @TableField(exist = false)
        private String vehicleFrameNo;
        /**
         * 行驶证号
         */
        @RecordTemplate(rowNo = 17, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "行驶证号")
        private String drivingLicenseNo;
        /**
         * 发动机型号((GB17691-2005必填,GB17691-2018选填))
         */
        @RecordTemplate(rowNo = 18, columnNo = 2, comment = FillCommentEnum.FILL, name = "发动机型号(GB17691-2005必填,GB17691-2018选填)")
        @NotEmpty(message = "发动机型号不能为空", groups = {ImportGroup.class})
        private String engineModel;
        /**
         * 发动机编号
         */
        @RecordTemplate(rowNo = 19, columnNo = 2, comment = FillCommentEnum.FILL, name = "发动机编号")
        @NotEmpty(message = "发动机编号不能为空", groups = {ImportGroup.class})
        private String engineNo;
        /**
         * 车籍地
         */
        @RecordTemplate(rowNo = 20, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "车籍地")
        private String vehiclePlace;
        /**
         * 车辆技术等级((编码见说明2))
         */
        @RecordTemplate(rowNo = 21, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "车辆技术等级(编码见右面2)")
        private Integer technicalLevel;
        /**
         * 出厂日期
         */
        @RecordTemplate(rowNo = 22, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "出厂日期")
        private String productionDate;
        /**
         * 等级评定日期
         */
        @RecordTemplate(rowNo = 23, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "等级评定日期")
        private String gradeAssessmentDate;
        /**
         * 二级维护日期
         */
        @RecordTemplate(rowNo = 24, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "二级维护日期")
        private String twoMaintenanceDate;
        /**
         * 二级维护状态((编码见说明5))
         */
        @RecordTemplate(rowNo = 25, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "二级维护状态(编码见右面5)")
        private Integer twoMaintenanceStatus;
        /**
         * 年审状态((编码见说明4))
         */
        @RecordTemplate(rowNo = 26, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "年审状态(编码见右面4)")
        private Integer yearEvaluationStatus;
        /**
         * 年检有效期
         */
        @RecordTemplate(rowNo = 27, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "年检有效期")
        private String yearinspectionPeriod;
        /**
         * 保险有效期
         */
        @RecordTemplate(rowNo = 28, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "保险有效期")
        private String insurancePeriod;
        /**
         * 保养有效期
         */
        @RecordTemplate(rowNo = 29, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "保养有效期")
        private String maintenancePeriod;
        /**
         * 所属单位名称
         */
        @RecordTemplate(rowNo = 30, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "所属单位名称")
        private String enterpriseName;
        /**
         * 车辆联系人
         */
        @RecordTemplate(rowNo = 31, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "车辆联系人")
        private String contactsName;
        /**
         * 车辆联系电话
         */
        @RecordTemplate(rowNo = 32, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "车辆联系电话")
        private String contactPhone;
        /**
         * 车辆sim卡号
         */
        @RecordTemplate(rowNo = 33, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "车辆sim卡号")
        private String terminalSim;
        /**
         * 车辆注册时间
         */
        @RecordTemplate(rowNo = 34, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "车辆注册时间")
        private String registerDate;
        /**
         * 所属组织ID
         */
        @RecordTemplate(rowNo = 35, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "所属组织ID")
        private String organizationId;
        /**
         * 环保局车辆类型((编码见说明6))
         */
        @RecordTemplate(rowNo = 36, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "环保局车辆类型(编码见右面6)")
        private Integer epaVehicleType;
        /**
         * 运输局车辆类型((编码见说明7))
         */
        @RecordTemplate(rowNo = 37, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "运输局车辆类型(编码见右面7)")
        private Integer transVehicleType;
        /**
         * 所有绑定的sim卡
         */
        @RecordTemplate(rowNo = 38, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "所有绑定的sim卡")
        private String terminalAllSim;
        /**
         * 所有者地址
         */
        @RecordTemplate(rowNo = 39, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "所有者地址")
        private String ownerAddress;
        /**
         * 车牌型号
         */
        @RecordTemplate(rowNo = 40, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "车牌型号")
        private String licenseModel;
        /**
         * 行政区划
         */
        @RecordTemplate(rowNo = 41, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "行政区划")
        private String administrativeArea;
        /**
         * 行政地址
         */
        @RecordTemplate(rowNo = 42, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "行政地址")
        private String administrativeAddress;
        /**
         * 总客数
         */
        @RecordTemplate(rowNo = 43, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "总客数")
        private Integer totalnumberGuest;
        /**
         * 整备质量
         */
        @RecordTemplate(rowNo = 44, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "整备质量")
        private Double curbWeight;
        /**
         * 列车最大总质量
         */
        @RecordTemplate(rowNo = 45, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "列车最大总质量")
        private Double maximumTotalMassOfTrain;
        /**
         * 入网证号
         */
        @RecordTemplate(rowNo = 46, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "入网证号")
        private String netNumber;
        /**
         * 初次登记日期
         */
        @RecordTemplate(rowNo = 47, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "初次登记日期")
        private String initialRegistrationDate;
        /**
         * 年检日期
         */
        @RecordTemplate(rowNo = 48, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "年检日期")
        private String annualInspectionDate;
        /**
         * 强制报废日期
         */
        @RecordTemplate(rowNo = 49, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "强制报废日期")
        private String mandatoryScrapDate;
        /**
         * 所属企业简称
         */
        @RecordTemplate(rowNo = 50, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "所属企业简称")
        private String enterpriseShortName;
        /**
         * 车辆SN
         */
        @RecordTemplate(rowNo = 51, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "车辆SN")
        private String vehicleSn;
        /**
         * 安全芯片型号((车载终端含有安全芯片的必填))
         */
        @RecordTemplate(rowNo = 52, columnNo = 2, comment = FillCommentEnum.EMPTY, name = "安全芯片型号(车载终端含有安全芯片的必填)")
        private String chipType;
        /**
         * 车载终端型号((必填))
         */
        @RecordTemplate(rowNo = 53, columnNo = 2, comment = FillCommentEnum.FILL, name = "车载终端型号(必填)")
        @NotEmpty(message = "车载终端型号不能为空", groups = {ImportGroup.class})
        private String tboxType;
        /**
         * 激活模式((编码见说明9,必填)0-无需激活,1-需要激活)
         */
        @RecordTemplate(rowNo = 54, columnNo = 2, comment = FillCommentEnum.FILL, name = "激活模式(编码见右面9,必填)")
        @NotEmpty(message = "激活模式不能为空", groups = {ImportGroup.class})
        private Integer vehRegisterMode;
        /**
         * 排放水平((编码见说明10,必填)(1-国六,2-国五,3-国四,4-国三,5-国二,6-排气后处理系统改装车辆))
         */
        @RecordTemplate(rowNo = 55, columnNo = 2, comment = FillCommentEnum.FILL, name = "排放水平(编码见右面10,必填)")
        @NotEmpty(message = "放水平不能为空", groups = {ImportGroup.class})
        private Integer emissionlLevelType;
        /**
         *
         * 整车生产企业
         */
        @RecordTemplate(rowNo = 56, columnNo = 2, comment = FillCommentEnum.FILL, name = "整车生产企业")
        @NotEmpty(message = "整车生产企业不能为空", groups = {ImportGroup.class})
        private String vehicleFirm;
    
        /**
         * 安全芯片编号(备案不需要)
         */
        private String chipCode;
        /**
         * 备注
         */
        private String remark;
    
        /**
         * 车辆备案结果(0:草稿;1:待审核;2:未备案 3:审核通过4:审核未通过)
         * @return
         */
        @TableField(exist = false)
        private Integer recordResult;
    
    

    解析工具类(最重要)

    思路: 遍历实体类上的有@ExcelTemplate注解的属性,有的话说明事由单元格和它对应的,把该属性的行号和列号传递给 解析单元格数据的方法,返回单元格的值,再通过java的 Filed的反射机制,给类赋值,就获取了excel中所有值了。在解析单元格的时候根据是否必填,也可以提前抛出异常。

    解析单元格的相关方法:

     /**
         * 获取sheet对象
         */
        public static Sheet getSheetByStream(InputStream inputStream, String sheetName) {
            Workbook workbook = null;
            try {
                workbook = WorkbookFactory.create(inputStream);
            } catch (Exception e) {
                throw new ServiceException("excel文件有误");
            }
            Sheet sheet = null;
            if (StringUtils.isBlank(sheetName)) {
                //取第一个
                sheet = workbook.getSheetAt(0);
            } else {
                sheet = workbook.getSheet(sheetName.trim());
            }
            return sheet;
        }
    
        /**
         * 读取单个单元格的值
         *
         * @param sheet
         * @param name
         * @param rowNo
         * @param columnNo
         */
        public static String readCell(Sheet sheet, int rowNo, int columnNo, FillCommentEnum comment, String name) {
            //5、获取行
            Row row = sheet.getRow(rowNo);
            //6、获取单元格
            Cell cell = row.getCell(columnNo);
            //7、读取单元格内容
            String stringCellValue = getCellValueByType(cell, name);
            if (comment.getCode() == 0 && StringUtils.isBlank(stringCellValue)) {
                throw new ServiceException(name + "不能为空");
            }
            logger.info(stringCellValue);
            return stringCellValue;
        }
    
       public static String getCellValueByType(Cell cell,String name){
           String cellValue = "";
           if(cell.getCellTypeEnum() == CellType.NUMERIC){
               if (HSSFDateUtil.isCellDateFormatted(cell)) {
                   cellValue = DateFormatUtils.format(cell.getDateCellValue(), "yyyy-MM-dd");
               } else {
                   NumberFormat nf = NumberFormat.getInstance();
                   cellValue = String.valueOf(nf.format(cell.getNumericCellValue())).replace(",", "");
               }
               logger.info(cellValue);
           }else if(cell.getCellTypeEnum() == CellType.STRING){
               cellValue = String.valueOf(cell.getStringCellValue());
           }else if(cell.getCellTypeEnum() == CellType.BOOLEAN){
               cellValue = String.valueOf(cell.getBooleanCellValue());
           }else if(cell.getCellTypeEnum() == CellType.ERROR){
               cellValue = "错误类型";
               throw new ServiceException("单元格"+name+": "+cellValue);
           }
           return cellValue;
       }
    

    反射给对象赋值的相关方法:

       /**
         * 解析excel备案数据到对象
         *
         * @return
         * @throws ClassNotFoundException
         * @throws IllegalAccessException
         * @throws InstantiationException
         */
        public static Object parseExcelToModel(String className, Sheet sheet) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
            Class<?> clazz = Class.forName(className);
            Field[] declaredFields = clazz.getDeclaredFields();
            Object o = clazz.newInstance();
            //获取excel的流,前端传入
            for (Field field: declaredFields) {
                field.setAccessible(true);
                if (field.isAnnotationPresent(RecordTemplate.class)) {
                    RecordTemplate annotation = field.getAnnotation(RecordTemplate.class);
                    Class<?> type = field.getType();
                    logger.info(type.getName());
                    //单元格的值
                    String value = ReadExcellCellUtils.readCell(sheet, annotation.rowNo(), annotation.columnNo(), annotation.comment(), annotation.name());
                    //通过反射把 单元格的值 给对象属性
                    setFieldValue(o, field, type, value);
                }
            }
            return o;
        }
    
    
        /**
         * 通过反射把 单元格的值 给对象属性
         * @param o
         * @param field
         * @param type
         * @param value
         * @throws IllegalAccessException
         */
        private static void setFieldValue(Object o, Field field, Class<?> type, String value) throws IllegalAccessException {
            Object targetValue = null;
            if (StringUtils.isEmpty(value)) {
                return;
            }
            if (type.equals(Integer.class)) {
                targetValue = Integer.parseInt(value);
            } else if (type.equals(Double.class)) {
                targetValue = Double.parseDouble(value);
            } else if (type.equals(Float.class)) {
                targetValue =  Float.parseFloat(value);
            } else if (type.equals(Boolean.class)) {
                targetValue = Boolean.getBoolean(value);
            }else{
                targetValue = value;
            }
            field.set(o, targetValue);
        }
    
        /**
         * 解析数据到model
         *
         * @param className   类名
         * @param inputStream 输入流
         * @param sheetName   sheetname 可以为null,为null时取第一页
         * @return 映射对象
         */
        public static Object parseExcelToModel(String className, InputStream inputStream, String sheetName) {
            Sheet sheetByStream = getSheetByStream(inputStream, sheetName);
            try {
                return parseExcelToModel(className, sheetByStream);
            } catch (Exception e) {
                e.printStackTrace();
                throw  new ServiceException("解析数据到model失败");
            }
        }
    

    如何使用:

    在这里插入图片描述
    提供这3个值就可以了。
    举例说明:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    测试:
    使用postman请求: excel的数据都获取到了:
    在这里插入图片描述

    自此,按照单元格的例子就完成了,这种适合不规则的excel,而且行数确定,特别是针对模板excel的读都是很通用的。


    总结:本文主要实现了easyExcel按照行数的例子,还有自己封装的针对通用不规则excel按照列(确切是按照单元格)读的例子。如有问题或者错误,欢迎留言讨论。


    个人微信公众号:
    搜索: 怒放de每一天
    不定时推送相关文章,期待和大家一起成长!!
    在这里插入图片描述


    展开全文
  • 前言:最近项目里面需要用到表格的冻结列功能,所谓“冻结列”,就是某些情况表格的比较多,需要固定前面的几列,后面的滚动。遗憾的是,bootstrap table里自带的fixed column功能有一点bug,于是和同事讨论该...

    前言:最近项目里面需要用到表格的冻结列功能,所谓“冻结列”,就是某些情况下表格的列比较多,需要固定前面的几列,后面的列滚动。遗憾的是,bootstrap table里自带的fixed column功能有一点bug,于是和同事讨论该如何解决,于是就有了这篇文章。

    一、起因回顾

    最近项目里面有一个表格需求,该表格列是动态产生的,而且列的数量操作一定值以后就会出现横向滚动条,滚动的时候需要前面几列固定。也就是所谓的excel的冻结列功能。该如何实现呢?不用多说,当然是查文档,于是找到了这篇http://issues.wenzhixin.net.cn/bootstrap-table/index.html#extensions/fixed-columns.html。谷歌浏览器效果如下:

    第一列固定

    1d81dd2ea143cc988cc710d3a2cc638f.png

    69035a73d1f6b626251850fd2479de9f.png

    貌似问题完美解决!可是,事与愿违,很遗憾,上面说了,这是谷歌浏览器的效果,没有问题。我们来看看IE里面

    IE11效果:

    e985a7837e8f6b70fb742063d132ccbd.png

    IE10效果:

    e0ee15d0052b384fc179b0242774a572.png

    很显然,不管是IE内核版本多少,冻结的列里面的内容都无法显示。怎么办?这可为难死宝宝了!

    二、解决方案

    还好有万能的开源,查看该页面源代码发现可以找到冻结列这个js的源码。

    4c5ee6cea26b7bafc733c272c3071b1c.png

    点击进入可以看到这个js的所有源码,找到源码就好办了,我们试着改改源码看是否能解决这个bug。

    我们在bootstrap-table下面的extensions文件夹下面新增加一个文件夹fixed-column

    f654f8049cf113c3bbaceda3f88a1311.png

    下面就贴出我们改好的源码:

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    (function($) {'use strict';

    $.extend($.fn.bootstrapTable.defaults, {

    fixedColumns:false,

    fixedNumber:1});var BootstrapTable =$.fn.bootstrapTable.Constructor,

    _initHeader=BootstrapTable.prototype.initHeader,

    _initBody=BootstrapTable.prototype.initBody,

    _resetView=BootstrapTable.prototype.resetView;

    BootstrapTable.prototype.initFixedColumns= function() {this.$fixedBody =$(['

    ','','
    '].join(''));this.timeoutHeaderColumns_ = 0;this.timeoutBodyColumns_ = 0;this.$fixedBody.find('table').attr('class', this.$el.attr('class'));this.$fixedHeaderColumns = this.$fixedBody.find('thead');this.$fixedBodyColumns = this.$fixedBody.find('tbody');this.$tableBody.before(this.$fixedBody);

    };

    BootstrapTable.prototype.initHeader= function() {

    _initHeader.apply(this, Array.prototype.slice.apply(arguments));if (!this.options.fixedColumns) {return;

    }this.initFixedColumns();var $tr = this.$header.find('tr:eq(0)').clone(),

    $ths= $tr.clone().find('th');

    $tr.html('');for (var i = 0; i < this.options.fixedNumber; i++) {

    $tr.append($ths.eq(i).clone());

    }this.$fixedHeaderColumns.html('').append($tr);

    };

    BootstrapTable.prototype.initBody= function() {

    _initBody.apply(this, Array.prototype.slice.apply(arguments));if (!this.options.fixedColumns) {return;

    }var that = this;this.$fixedBodyColumns.html('');this.$body.find('> tr[data-index]').each(function() {var $tr = $(this).clone(),

    $tds= $tr.clone().find('td');

    $tr.html('');for (var i = 0; i < that.options.fixedNumber; i++) {

    $tr.append($tds.eq(i).clone());

    }

    that.$fixedBodyColumns.append($tr);

    });

    };

    BootstrapTable.prototype.resetView= function() {

    _resetView.apply(this, Array.prototype.slice.apply(arguments));if (!this.options.fixedColumns) {return;

    }

    clearTimeout(this.timeoutHeaderColumns_);this.timeoutHeaderColumns_ = setTimeout($.proxy(this.fitHeaderColumns, this), this.$el.is(':hidden') ? 100 : 0);

    clearTimeout(this.timeoutBodyColumns_);this.timeoutBodyColumns_ = setTimeout($.proxy(this.fitBodyColumns, this), this.$el.is(':hidden') ? 100 : 0);

    };

    BootstrapTable.prototype.fitHeaderColumns= function() {var that = this,

    visibleFields= this.getVisibleFields(),

    headerWidth= 0;this.$body.find('tr:first-child:not(.no-records-found) > *').each(function(i) {var $this = $(this),

    index=i;if (i >=that.options.fixedNumber) {return false;

    }if (that.options.detailView && !that.options.cardView) {

    index= i - 1;

    }

    that.$fixedBody.find('thead th[data-field="' + visibleFields[index] + '"]')

    .find('.fht-cell').width($this.innerWidth() - 1);

    headerWidth+= $this.outerWidth();

    });this.$fixedBody.width(headerWidth - 1).show();

    };

    BootstrapTable.prototype.fitBodyColumns= function() {var that = this,

    top= -(parseInt(this.$el.css('margin-top')) - 2),

    height= this.$tableBody.height() - 2;if (!this.$body.find('> tr[data-index]').length) {this.$fixedBody.hide();return;

    }this.$body.find('> tr').each(function(i) {

    that.$fixedBody.find('tbody tr:eq(' + i + ')').height($(this).height() - 1);

    }); events

    this.$tableBody.on('scroll', function() {

    that.$fixedBody.find('table').css('top', -$(this).scrollTop());

    });this.$body.find('> tr[data-index]').off('hover').hover(function() {var index = $(this).data('index');

    that.$fixedBody.find('tr[data-index="' + index + '"]').addClass('hover');

    },function() {var index = $(this).data('index');

    that.$fixedBody.find('tr[data-index="' + index + '"]').removeClass('hover');

    });this.$fixedBody.find('tr[data-index]').off('hover').hover(function() {var index = $(this).data('index');

    that.$body.find('tr[data-index="' + index + '"]').addClass('hover');

    },function() {var index = $(this).data('index');

    that.$body.find('> tr[data-index="' + index + '"]').removeClass('hover');

    });

    };

    })(jQuery);

    bootstrap-table-fixed-columns.js修改后的源码

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    .fixed-table-container thead th .th-inner, .fixed-table-container tbody td .th-inner{line-height:18px;

    }.fixed-table-pagination .pagination a{padding:5px 10px;

    }.fixed-table-toolbar .bars, .fixed-table-toolbar .search, .fixed-table-toolbar .columns{margin-top:5px;margin-bottom:5px;

    }

    bootstrap-table-fixed-columns.css修改后

    主要修改的地方:

    1)源码里面将thead和tbody分别封装成了一个单独的表格,修改后将thead和tbody放到了一个table里面;

    2)依次遍历冻结的列放入到固定的tbody里面;

    其实也就改了那么几个地方,就能完美解决IE的bug。我们先来看看效果:

    IE11

    11cfa7bdd7f35dfb2bc0a96ffe2a2a2b.png

    IE10

    5c074d8693b74b065d7e6a202c533446.png

    IE8

    1731f51541178b4623d1d9b8af495b3c.png

    我们再来看看如何使用。

    1、引用js和对应的css

    2、js调用如下

    e162c60c77f01ef19c78bf8de8ef3f0d.png

    加两个参数fixedColumns和fixedNumber即可,什么意思不用过多解释,是否冻结列、冻结列的列数。还有一点需要说明的是,这里调用的时候不能指定它的height,如果指定height,表格的冻结显示会有问题。

    三、总结

    以上就是表格冻结关于IE兼容性问题的解决方案,如果你也正好用到bootstrap table 的列冻结,呵呵,福利来了。总体上来说,就是在原有扩展js的基础上面做了一些小小的修改。能用,如果大伙觉得有什么问题,欢迎指出。

    展开全文
  • 关于表格冻结行的方法

    千次阅读 2013-03-15 21:24:09
    有些项目,由于特殊的需求,必须冻结行或列,或者同时都需冻结,几个月前,我曾解决过冻结行的操作,即冻结表头,这种插件和代码方式网上到处都是,我比较喜欢的是名为fixedtableheader的插件,只是可能会...
  • Excel教程: Excel的窗口冻结与拆分

    千次阅读 2020-06-21 15:13:36
    如果工作表里数据较多,鼠标向滚动,就看不到标题了,查看、编辑着实不方便。有没有办法解决这问题呢?当然有啊,Excel冻结窗格功能来帮忙。 为大家介绍一下Excel的窗格操作。 1 冻结首行 选择...
  • 但html编写的web网页表格并没有冻结表格这功能,那又如何才能实现表格的 冻结呢? 行列冻结是处理表格非常常见的应用,我们都知道用excel可以很轻松的实现这功能。但html编写的web网页表格并没有...
  • excel冻结多行(固定表头)

    千次阅读 2019-05-14 11:42:52
    1、首先打开相应的excel表格,确定要冻结冻结的多行,下面以冻结多行为例。先界面上找到“视图”,“冻结窗格”,最后找到“冻结首行”。 2、点击“冻结首行”后,表格第一行下面会出现一条细实线,...
  • C# DataGridView控件动态添加

    千次阅读 2020-12-24 22:32:45
    DataGridView控件实际应用非常实用,特别需要表格显示数据。可以静态绑定数据源,这样就自动为DataGridView控件添加相应的。假如需要动态为DataGridView控件添加新,方法有很多种,下面简单介绍如何为...
  • 篇说过,还可以扩展gridview的分页功能以及实现导出结果为EXCEL/PDF的功能。实现好后应该封装起来,以方便后续的项目简单使用。至于如何实现,我想不必过多的说了。下面是显示结果和主要的代码。如果有不明白...
  • 看下部的数据看不到表格的标题,让人搞不清每一列的天文数字究竟代表什么意思。而且正常打印输出,只有第一页能打印出标题,其余各页则是光秃秃的,单独看后几页内容又让人不知所以然。我们看怎么解决这种...
  • 具体操作为:选中需要冻结的内容的下一行或下一列,点击冻结窗口。 可以看出,页面滑动至100多行,仍能看到顶端的信息。 二、窗口拆分 当我们表格存放了许多数据且需要对比(如对比一月与十二月的数据),...
  • 看下部的数据看不到表格的标题,让人搞不清每一列的天文数字究竟代表什么意思。而且正常打印输出,只有第一页能打印出标题,其余各页则是光秃秃的,单独看后几页内容又让人不知所以然。我们看怎么解决这种...
  • 方法: 选择窗口排列方式,即右击任务栏,XP系统中选择横向平铺窗口纵向平铺窗口,如果是WIN7,则选择堆叠显示窗口并排显示窗口,即可实现你的目的.不过如果窗口过多,每个窗口的显示空间就越小. 方法二: 任务栏按...
  • 1、Word如何使用着重号Word我们可以把着重号请到工具栏上。打开“工具—自定义”命令选项,打开“自定义”对话框。“命令”标签卡的“类别”栏里选中“所有命令”选项。此时右边“命令”栏会出现按...
  • Excel数据分析从入门到精通()软件操作技巧1.单元格内换行2.锁定标题3.查找重复值4.删除重复值5.将金额转换为万元显示6.隐藏0值7.隐藏单元格内所有的值8.单元格内输入000019.按月补充日期10.合并多个单元格的...
  • 制作和数字的统计,且对数据进行分析和整理,可和Word,、PowerPoint协同工作,与多种数据库其他类软件交流信息。 4.1 基本概念与基本操作 4.1.1 Excel的启动和退出 1. Excel的启动 3种方法: ① 开始-&...
  • 【计算机基础04】Excel习题

    千次阅读 2020-05-13 20:20:28
    还可提高:选定插入分页符位置的下一行页面布局选项卡的页面设置组单击分隔符,下拉列表中选择插入分页符命令。 3. 输入分数,应先分数前输入0及一个空格,直接输入3/8,系统将其视为日期 4. 输入6/20...
  • 比如我们经常需要插入工作表行或列,但是需要通过“开始”选项卡的“单元格”组单击“插入”按钮,打开的下拉列表中选择“插入工作表/插入工作表”命令才能做到。打开UC浏览器 查看更多精彩图片当然了,这...
  • 虽然说Access数据处理方面会比Excel好些,但它一些简单的日常事务象执行一些快速输入排序却不那么方便简单。它是个常用的事务处理工具。这10年来,有许多AutoCAD程序是用于将提取数据并将其输出为报告。...
  • Java创建Excel文档,POI使用详解

    千次阅读 2019-12-25 13:22:50
     即使你没有用CreateRow和CreateCell创建过行或单元格,也完全可以直接创建区域然后把这区域合并,Excel的区域合并信息是单独存储的,和RowRecord、ColumnInfoRecord不存在直接关系。 (3)单元格对齐 ...
  • excel基础操作

    2021-05-18 14:05:06
    ctrl + shift +方向 可以快速选中这一行()的左(右)边的连续文本(空格) ,类似于同一行()的全选 ctrl + f 查找和替换 ctrl + h 替换 ctrl + e 快速填充 ctrl + g 定位 如果有大量的市区,去掉一个字!市,只需要吧...
  • Saving an Excel spreadsheet as a PDF can be confusing, and the finished file often looks different from how we want it to be presented. Here’s how to save a sheet as a readable clean PDF file. 将...
  • C# DataGridView控件动态添加新

    千次阅读 2019-03-26 13:47:37
    方法: 方法二: ① DataGridView 取得或者修改当前单元格的内容: ② DataGridView 设定单元格只读: ③ DataGridView 不显示最下面的新: ④ DataGridView 判断新增: ⑤ DataGridView 的用户...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 950
精华内容 380
关键字:

34、在excel中,单独冻结行或列时,通常是选择要冻结行或列的下一行或下一列进行操。(