Vue+JAVA POI 实现Excel导入导出规则怪谈

怪谈场景:

前端导入一个Excel表格到后端,后端读取Excel表格内容并处理,将处理过的数据生成一个新的EXcel,并在前端浏览器下载。

导入的文件样式:

 

导出的文件样式:

  •  该文章较长,您可以通过目录跳转到想要查看的位置,若您是全栈开发,并且第一次做该操作,建议您阅读全文。 

目录

前端HTML代码: 

el-upload  标签参数注释:

前端JS代码:

         后端Java代码:


前端HTML代码: 

<template>
    <div>  
            <el-row>
              <el-col :span="100">
                <el-upload
                ref="uploadExcel"
                :action="uploadPath"
                :data ="uploadData"
                :headers = "headers"
                :limit="limitNum"
                :auto-upload="false"
                accept="*"
                :before-upload="beforeUploadFile"
                :on-change="fileChange"
                :on-exceed="exceedFile"
                :on-success="handleSuccess"
                :on-error="handleError"
                :file-list="fileList"
              >

              <el-button size="middle" type="primary">点击上传</el-button>
              <div slot="tip" class="el-upload__tip">上传文件不超过10M</div>
              </el-upload>
              </el-col>
              <el-col :span="100">
                <el-button type="primary" size="middle" 
                 @click="downLoad">导出处理结果</el-button>
                </el-col>
            </el-row>
    </div>
  </template>

el-upload  标签参数注释:

action必选参数,上传的地址string
headers设置上传的请求头部object
accept接受上传的文件类型(thumbnail-mode 模式下此参数无效)string
on-success文件上传成功时的钩子function(response, file, fileList)
on-error文件上传失败时的钩子function(err, file, fileList)
on-change文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用function(file, fileList)
before-upload上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。function(file)
auto-upload是否在选取文件后立即进行上传booleantrue
file-list上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}]array[]
limit最大允许上传个数number
on-exceed文件超出个数限制时的钩子function(files, fileList)-

以上是本文章中用到的参数,若您需系统学习,可以到element-UI官网查看更详细的讲解。

前端JS代码:

export default {
  name: 'excelDownLoad',
  components: { },
  data() {
    return {
      limitNum: 1, // 文件个数
      fileList: [], // 文件列表
      uploadData: { // 上传时附带参数
        type: 1
      },
      files: [],
      headers: {
        Authorization: getToken()
      },
      uploadPath: ''
    }
  },
  computed: {
  },
  created() {
  },
  methods: {
    beforeUploadFile(file) {
      const extension = file.name.substring(file.name.lastIndexOf('.') + 1)
      const size = file.size / 1024 / 1024

      if (size > 10) {
        this.$notify.warning({
          title: '警告',
          message: `文件大小不得超过10M`
        })
      }
    },
    async submitUpload() {
      const formData = new FormData()
      formData.append('excelList', this.files.raw)
      await downLoad(formData).then(
        data => {

        var FileSaver = require('file-saver');
        var blob = new Blob([data],{type: "application/vnd.ms-excel"});
        //导出后,Excel名为:新的表格2023-02-24.xsl  ; 名字可以自定义
        FileSaver.saveAs(blob,"新的表格"+fecha.format(new Date(), 'yyyy-MM-dd')+".xsl");
        },

        error => {
         (error);
         this.$("下载异常,请稍后再试");
        });
    },
    // 文件修改时的钩子
    fileChange(file, fileList) {
      this.files = file
    },

    // 文件超出个数限制时的钩子
    exceedFile(files, fileList) {
      this.$notify.warning({
        title: '警告',
        message: `只能选择 ${
          this.limitNum
        } 个文件,当前共选择了 ${files.length + fileList.length} 个`
      })
    },
    // 文件上传成功时的钩子
    handleSuccess(res, file, fileList) {
      addItem({
        fileName: res.data.fileName,
        filePath: res.data.newFilePath,
        idTransportBatch: this.dicFiles.idTransportBatch
      }).then(() => {
        this.$message({
          message: res.data.msg,
          type: 'success'
        })
        this.loading.close()
        this.$refs.uploadExcel.clearFiles()// 清除上次上传记录
        this.showItem({ row: this.dicFiles })
      })
    },

    // 文件上传失败时的钩子
    handleError(err, file, fileList) {
      this.$message.error(err.msg)
    }

  }

}

代码规则怪谈: 

  • 前端掉用接口的时候,您必须加上: responseType :'blob',若您不加可能会导致以下后果:
  1. 返回值得类型不对;
  2. 导出文件乱码;
  3. 导出文件打不开;
  • 前端掉用接口的时候,您可以加上: Authorization: token,若您不加可能会导致接口调用失败,也可能不会。

export function checkStockList(data) {

  return axios.post(

    '/api/downLoad',

    data,

{

      responseType :'blob',

       headers: {

        Authorization: token

      }

   

}

  • var blob = new Blob([data],{type: "application/vnd.ms-excel"});  您需要把这行代码的type设置为和Java代码中相同的类型。否则可能会出现不可预估的问题。
  • 若您做了以上操作仍然出现文件打开失败的情况,请您查看您的前端项目是否启用了mockjs,如果您启用了,请您关闭后接着尝试导出。

 后端Java代码:

/**controller层**/

 @PostMapping(value = "/downLoad")
    public void downLoad(@RequestBody MultipartFile importExcel , HttpServletRequest request, HttpServletResponse response) {
        try {
           downLoadService.downLoad(importExcel ,request,response);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }



/**service层**/

  @Override
    public void downLoad(MultipartFile importExcel , HttpServletRequest request, HttpServletResponse response) {
        List<Map<Integer, List<String>>> totalList = new ArrayList<>();
        Map<String, Object> checkResultMap = new HashMap<>();
        List<List> exceldb = new ArrayList<>();
        List<List> checkChineseList= new ArrayList<>();
        List<List> checkMathList= new ArrayList<>();
        List<List> checkEnglishList= new ArrayList<>();
        int  round= 0;

        /**导入并读取文件开始**/

        //创建工作簿
        Workbook workbook = null;

        //判断接收的文件是否合规
        if (importExcel .isEmpty()) {
            throw new RuntimeException("文件不能为空");
        } else {
            String name = importExcel .getOriginalFilename();
            if (!(name.endsWith(".xls") || name.endsWith(".xlsx"))) {
                throw new RuntimeException("该文件不是Excel");
            }
            try {
                workbook = WorkbookFactory.create(importExcel.getInputStream());

                //获取sheet数量
                int numberOfSheets = workbook.getNumberOfSheets();
                for (int i = 0; i < numberOfSheets; i++) {

                    //存储单页sheet信息map
                    Map<Integer, List<String>> excelMap = new HashMap<>();

                    //获取当前工作表
                    Sheet sheet = workbook.getSheetAt(i);

                    //获取表记录
                    //获取行数
                    int numOfRows = sheet.getLastRowNum() + 1;
                    for (int rowNum = 0; rowNum < numOfRows; rowNum++) {
                        List<String> infoList = new ArrayList<>();
                        Row rowData = sheet.getRow(rowNum);
                        if (rowData != null) {

                            //读取列
                            int cellCount = rowData.getLastCellNum();
                            for (int cellNum = 0; cellNum < cellCount; cellNum++) {
                                Cell cell = rowData.getCell(cellNum);

                                //匹配数据类型
                                String cellValue = "";
                                if (cell != null) {
                                    if (cell.getCellTypeEnum() == CellType.STRING) {
                                        cellValue = cell.getStringCellValue();
                                    }
                                    if (cell.getCellTypeEnum() == CellType.NUMERIC) {

                                        //数值型
                                        //poi读取整数会自动转化成小数,这里对整数进行还原,小数不做处理
                                        long longValue = Math.round(cell.getNumericCellValue());
                                        if (Double.parseDouble(longValue + ".0") == cell.getNumericCellValue()) {
                                            cellValue = String.valueOf(longValue);
                                        } else {
                                            cellValue = String.valueOf(cell.getNumericCellValue());
                                        }
                                    } else if (cell.getCellTypeEnum() == CellType.FORMULA) {
                                        //公式型
                                        //公式计算的值不会转化成小数,这里数值获取失败后会获取字符
                                        try {
                                            cellValue = String.valueOf(cell.getNumericCellValue());
                                        } catch (Exception e) {
                                            cellValue = cell.getStringCellValue();
                                        }
                                    }
                                    infoList.add(cellValue);
                                } else {
                                    infoList.add(null);
                                }
                                excelMap.put(rowNum, infoList);
                                exceldb.add(infoList);
                            }
                        }
                    }
                    totalList.add(excelMap);
                }

                /**导入并读取文件结束---excelMap和exceldb都是读取出来的全部数据,区别在于类型不一样,大家可以打印出来自行查看变量的结构**/

              
            
              /**业务处理开始**/
              /**
               这个部分在实际操作中我是对导出的数据做了一些业务处理,最终我把所有处理的数据放在了checkResultMap中,这里不多赘述,我只写一个例子。
             **/
                        checkChineseList=['李明','李雷','韩梅梅'];
                        checkMathList=['高小轩','高小强','高小盛','高小兰'];
                        checkEnglishList=['韩梅梅','王小楠','张小萌','花小卷']
                        checkResultMap.put("语文", checkChineseList);
                        checkResultMap.put("数学", checkMathList);
                        checkResultMap.put("英语", checkEnglishList);
                        
                /**业务处理结束**/


                /**导出Excel开始**/
                
                //创建工作簿
                HSSFWorkbook workbookExport = new HSSFWorkbook();

                //创建工作表
                HSSFSheet sheet = workbookExport.createSheet("Sheet1");

                //这个地方是对拼好的checkResultList进行从大到小的排序,若不排序生成Excel创建行时会发生逻辑错误
                LinkedHashMap<String, Object> sortedKeywords = checkResultMap.entrySet().stream().sorted(Comparator.comparing(entry -> entry.getValue().toString().length(), Comparator.reverseOrder())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, LinkedHashMap::new));

                //生成Excel这里用到了迭代器处理数据
                Iterator it1 = sortedKeywords.entrySet().iterator();
                HSSFRow row = sheet.createRow(0);
                while (it1.hasNext()) {
                    Map.Entry entry1 = (Map.Entry) it1.next();

                    //取表头值,其实就是map的key
                    Object key1 = entry1.getKey();

                    //取每一列的数据
                    Object value1 = entry1.getValue();

                    //创建单元格
                    HSSFCell cell = row.createCell(round);

                    //给表头赋值
                    cell.setCellValue(key1.toString());

                    //设置列宽
                    sheet.setColumnWidth(round, 50 * 256);

                    //设置单元格样式,这里只给表头设置样式,不需要可以去掉
                    HSSFCellStyle headstyle = workbookExport.createCellStyle();
                    headstyle.setFillPattern(HSSFCellStyle.SPARSE_DOTS);
                    headstyle.setFillForegroundColor(IndexedColors.LIGHT_ORANGE.index);
                    headstyle.setFillPattern(CellStyle.SOLID_FOREGROUND);
                    cell.setCellStyle(headstyle);

                    List<Map> mapList = (List<Map>) value1;
                    for (int m = 0; m < mapList.size(); m++) {
                     //这里判断了round ,如果是第一次循环则createRow,如果不是则getRow,还记得刚才的排序吗?就是为了按照最多数据列创建行。这样写就不会因为重复创建行而导致Excel数据不完整了。
                        if (round == 0) {
                            HSSFRow rows = sheet.createRow(m + 1);
                            HSSFCell cells = rows.createCell(a);
                            cells.setCellValue((mapList.get(m)) + "");
                        } else {
                            HSSFRow rows = sheet.getRow(m + 1);
                            HSSFCell cells = rows.createCell(a);
                            cells.setCellValue((mapList.get(m)) + "");
                        }
                    }
                    round++;
                }
                
                //创建输出流
                OutputStream outputs = response.getOutputStream();
                //下面是返回给前端的响应头,这样写就可以
                response.reset();
                response.setContentType("application/vnd.ms-excel;charset=UTF-8");
                response.setHeader("Pragma", "no-cache");//关闭缓存
                response.setHeader("Cache-Control", "no-cache");//关闭缓存
                response.setDateHeader("Expires", 0);//为了让浏览器不要缓存页面,也可以利用Expires实体报关域,设置为0
                
                //生成文件
                workbookExport.write(outputs);

                //清空缓冲区的数据流
                outputs.flush(); 

                //关闭流
                outputs.close();

                /**导出Excel结束**/

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (workbook != null) {
                        workbook.close();
                    }
                    if (workbookExport!= null) {
                        workbookExport.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

代码规则怪谈: 

本文用到的是 HSSFWorkbook , HSSFWorkbook是操作Excel2003以前(包括2003)的版本,扩展名是.xls;

如果不符合您的需求,您可以尝试以下方法:

XSSFWorkbook:是操作Excel2007后的版本,扩展名是.xlsx

SXSSFWorkbook:是操作Excel2007后的版本,扩展名是.xlsx

具体用法您可以自行百度。

如果不出问题,前端请求后就可以自动导出一个您想要的Excel了 !!!

如果没有解决您的问题,小尼表示非常抱歉,请您点击左上角“←”标识立刻退出,您可以尝试进入别的房间寻找答案。

若您执意留下,可能会给您带来以下后果:

怀疑自我;

主动放弃;

精神混乱;

情绪失控;

若您出现以上反应,请马上关闭浏览器,进行深呼吸,调整情绪后您仍然可以尝试进入其他房间寻找答案,请记住不要再回到本博客。

小尼祝您学习愉快,永远200

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值