Bootstrap

springboot-vue excel上传导出

数据库

device_manage表

字段,id,workshop,device_number,device_name,device_model,warn_time,expired_time

device_warn表

字段,id,warn_time,expired_time

后端

实体类格式

device_manage

@Data
@TableName("device_manage")

/*
设备管理
*/
public class DeviceManageEntity {

    private static final long serialVersionUID = 1L;
    /**
     * 主键
     */
    @TableId
    private Integer id;
 
    /**
     * 车间名称
     */
    private String workshop;


    /**
     * 设备编号
     */
    private String deviceNumber;

    /**
     * 设备名称
     */
    private String deviceName;

    /**
     * 设备型号
     */
    private String deviceModel;


    /**
     * 维保预警时间
     */
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
    private Date warnTime;

    /**
     * 维保到期时间
     */
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
    private Date expiredTime;

}

device_warn

@Data
@TableName("device_warn")

/*保养预警*/
public class DeviceWarnEntity {
    private static final long serialVersionUID = 1L;
    /**
     * 编号
     */
    @TableId
    private Integer id;

    /**
     * 保养到期时间
     */
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
    private Date expiredTime;

    /**
     * 预警时间
     */
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
    private Date warnTime;

}

选择导出的字段warnVo

@Data
@ColumnWidth(20)
public class WarnVo {
    //传输给前端展示
    //id
    @ExcelIgnore
    private Long id;
    //车间
    @ExcelProperty("车间名称")
    private String workshop;
    //设备编号
    @ExcelProperty("设备编号")
    private String deviceNumber;
    //设备名称
    @ExcelProperty("设备名称")
    private String deviceName;
    //设备型号
    @ExcelProperty("设备型号")
    private String deviceModel;
    //维保到期时间
    @ExcelProperty("维保到期时间")
    @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
    private Date expiredTime;
    //预警时间
    @ExcelProperty("预警时间")
    @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
    private Date warnTime;
}

controller层

@RestController
@RequestMapping("/dev/warn")
public class exportController {
    @Autowired
    private DeviceWarnService iTainWarnService;
//字典类,前端下拉框选项
    @Autowired
    private SysDictService sysDictService;
    @Autowired
    private DeviceManageService iDeviceService;
 

    //文件上传
    @PostMapping("/upload")
    @ResponseBody
    public R upload(MultipartFile file) throws IOException {
        if (file==null){
            System.out.println("文件为空");
        }
        WarnVoListener warnVoListener = new WarnVoListener(sysDictService, iTainWarnService, iDeviceService);
//初始化tips
        List<ImportTips> tips = new ArrayList<>();

            for (WarnVo data : warnVoListener.getDatas()) {
                tips = warnVoListener.getTips();

            }
            if (tips.size() > 0) {
                return R.error();
            }

        EasyExcel.read(file.getInputStream(), WarnVo.class, warnVoListener).sheet().doRead();
        return R.ok();
    }

    //文件导出
    /**
     * 文件下载并且失败的时候返回json(默认失败了会返回一个有部分数据的Excel)
     * 这种方法是将Excel文件的生成过程放在后端进行。前端发起一个请求到后端,后端处理数据并生成Excel文件,然后将文件返回给前端进行下载。
     * 这种方法的优点是可以将数据处理的压力放在后端,前端只需要处理请求和下载文件的逻辑。
     * @since 2.1.1,设置响应头
     */
    private  void setExcelResponseProp(HttpServletResponse response,String rawFileName) throws UnsupportedEncodingException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String fileName = URLEncoder.encode(rawFileName, "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
    }

    @GetMapping("/download")
    public void download(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");
            this.setExcelResponseProp(response,"保养预警");
            List<WarnVo> warnVos = iTainWarnService.listAllWarn();
//得到字典类所有选项
            List<SysDictEntity> workShopList = sysDictService.maintenanceList(" workshop");
            for (WarnVo warnVo : warnVos) {
                for (SysDictEntity sysDictEntity :  workShopList) {
                    if (sysDictEntity.getValue().compareTo(warnVo.getWorkshop())==0){
                        warnVo.setWorkshop(sysDictEntity.getName());
                    }
                }
            }
            List<SysDictEntity> deviceModelList = sysDictService.maintenanceList("deviceModel");
            for (WarnVo warnVo : warnVos) {
                for (SysDictEntity sysDictEntity :  deviceModelList) {
                    if (sysDictEntity.getValue().compareTo(warnVo.getDeviceModel())==0){
                        warnVo.setDeviceModel(sysDictEntity.getName());
                    }
                }
            }
            // 这里需要设置不关闭流
            EasyExcel.write(response.getOutputStream(), WarnVo.class)
                    // 导出Excel时在此处注册handler
                    .registerWriteHandler(new CustomSheetWriteHandler(sysDictService))
                    .autoCloseStream(Boolean.FALSE)
                    .sheet("保养预警")
                    .doWrite(warnVos);
        } catch (Exception e) {
            // 重置response
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map<String, String> map = new HashMap<>();
            map.put("status", "failure");
            map.put("message", "下载文件失败" + e.getMessage());
            response.getWriter().println(JSON.toJSONString(map));
        }
    }

}

listener

CustomSheetWriteHandler导出

@Service
public class CustomSheetWriteHandler implements SheetWriteHandler {
    @Autowired
    private SysDictService sysDictService;


    public CustomSheetWriteHandler(SysDictService sysDictService) {
        this.sysDictService = sysDictService;
    }

    @Override
    public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

    }

    /**
     * 想实现Excel引用其他sheet页数据作为单元格下拉选项值,
     * 需要重写该方法
     *
     * @param writeWorkbookHolder
     * @param writeSheetHolder
     */
    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        // 构造样例数据,该数据可根据实际需要,换成业务数据
        // 实际数据可通过构造方法,get、set方法等由外界传入
        List<String> selectworkshopList = new ArrayList<>();
        List<SysDictEntity> workshoplist = sysDictService.maintenanceList("workshop");
        for (SysDictEntity sysDictEntity : workshoplist) {
            if (sysDictEntity.getSort()!=null){
                selectworkshopList.add(sysDictEntity.getName());
            }
        }
        List<String> selectmodelList = new ArrayList<>();
        List<SysDictEntity> modellist = sysDictService.maintenanceList("deviceModel");
        for (SysDictEntity sysDictEntity : modellist) {
            if (sysDictEntity.getSort()!=null){
                selectmodelList.add(sysDictEntity.getName());
            }
        }

        // 构造下拉选项单元格列的位置,以及下拉选项可选参数值的map集合
        // key:下拉选项要放到哪个单元格,比如A列的单元格那就是0,C列的单元格,那就是2
        // value:key对应的那个单元格下拉列表里的数据项,比如这里就是下拉选项1..100
        Map<Integer, List<String>> selectParamMap = new HashMap<>();
        selectParamMap.put(0, selectworkshopList);
        selectParamMap.put(3, selectmodelList);

        // 获取第一个sheet页
        Sheet sheet = writeSheetHolder.getCachedSheet();
        // 获取sheet页的数据校验对象
        DataValidationHelper helper = sheet.getDataValidationHelper();
        // 获取工作簿对象,用于创建存放下拉数据的字典sheet数据页
        Workbook workbook = writeWorkbookHolder.getWorkbook();

        // 迭代索引,用于存放下拉数据的字典sheet数据页命名
        int index = 1;
        for (Map.Entry<Integer, List<String>> entry : selectParamMap.entrySet()) {

            // 设置存放下拉数据的字典sheet,并把这些sheet隐藏掉,这样用户交互更友好
            String dictSheetName = "dict_hide_sheet" + index;
            Sheet dictSheet = workbook.createSheet(dictSheetName);
            // 隐藏字典sheet页
            workbook.setSheetHidden(index++, true);

            // 设置下拉列表覆盖的行数,从第一行开始到最后一行,这里注意,Excel行的
            // 索引是从0开始的,我这边第0行是标题行,第1行开始时数据化,可根据实
            // 际业务设置真正的数据开始行,如果要设置到最后一行,那么一定注意,
            // 最后一行的行索引是1048575,千万别写成1048576,不然会导致下拉列表
            // 失效,出不来
            CellRangeAddressList infoList = new CellRangeAddressList(1, 1048575, entry.getKey(), entry.getKey());
            int rowLen = entry.getValue().size();
            for (int i = 0; i < rowLen; i++) {
                // 向字典sheet写数据,从第一行开始写,此处可根据自己业务需要,自定
                // 义从第几行还是写,写的时候注意一下行索引是从0开始的即可
                dictSheet.createRow(i).createCell(0).setCellValue(entry.getValue().get(i));
            }

            // 设置关联数据公式,这个格式跟Excel设置有效性数据的表达式是一样的
            String refers = dictSheetName + "!$A$1:$A$" + entry.getValue().size();
            Name name = workbook.createName();
            name.setNameName(dictSheetName);
            // 将关联公式和sheet页做关联
            name.setRefersToFormula(refers);

            // 将上面设置好的下拉列表字典sheet页和目标sheet关联起来
            DataValidationConstraint constraint = helper.createFormulaListConstraint(dictSheetName);
            DataValidation dataValidation = helper.createValidation(constraint, infoList);
            sheet.addValidationData(dataValidation);
        }
    }
}

WarnVoListener导入

@Slf4j
public class WarnVoListener extends AnalysisEventListener<WarnVo> {
    private static final Logger LOGGER = LoggerFactory.getLogger(WarnVoListener.class);
    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    //    List<WarnVo> list = new ArrayList<>();
    List<DeviceManageEntity> deviceList = new ArrayList<>();
    List<DeviceWarnEntity> tainWarnList = new ArrayList<>();

    /**
     * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private SysDictService sysDictService;
    private DeviceWarnService iTainWarnService;
    private DeviceManageService iDeviceService;

//    public WarnVoListener() {
//        // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
//        demoDAO = new DemoDAO();
//    }

    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     */
    public WarnVoListener(SysDictService sysDictService,DeviceWarnService iTainWarnService,DeviceManageService iDeviceService) {
        this.sysDictService = sysDictService;
        this.iTainWarnService = iTainWarnService;
        this.iDeviceService = iDeviceService;
    }

    /**
     * 返回提示语
     */
    private List<ImportTips> tips = new ArrayList<>();

    /**
     * 自定义用于暂时存储data
     * 可以通过实例获取该值
     */
    private List<WarnVo> datas = new ArrayList<>();

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data
     *            one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(WarnVo data, AnalysisContext context) {
//        LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));

        // 该行数据是否有错误
        boolean checkRowError = false;
        LOGGER.info("数据导入,解析第{}行数据:{}" , context.readRowHolder().getRowIndex() , data);


        List<DeviceManageEntity> devList = iDeviceService.list();
        for (DeviceManageEntity devices : devList) {
            if (devices.getDeviceNumber().equals(data.getDeviceNumber())){
                saveTips(context.readRowHolder().getRowIndex(),"导入文件中设备编号有重复",tips);
                checkRowError = true;
            }
        }


        //当该行数据没有错误时,数据存储到集合,供批量处理。
        if(!checkRowError){
            //device表
            DeviceManageEntity device = new DeviceManageEntity();
//            device.setDeviceModel(data.getDeviceModel());
            device.setDeviceName(data.getDeviceName());
            device.setDeviceNumber(data.getDeviceNumber());
            device.setWarnTime(data.getWarnTime());
            device.setExpiredTime(data.getExpiredTime());

            List<SysDictEntity> list = sysDictService.maintenanceList("workshop");
            for (SysDictEntity sysDictEntity : list) {
                if (sysDictEntity.getName().compareTo(data.getWorkshop())!=0){
                    device.setWorkshop(sysDictEntity.getValue());
                }
            }
            List<SysDictEntity> modellist = sysDictService.maintenanceList("deviceModel");
            for (SysDictEntity sysDictEntity : modellist) {
                if (sysDictEntity.getName().compareTo(data.getDeviceModel())!=0){
                    device.setDeviceModel(sysDictEntity.getValue());
                }
            }
            this.deviceList.add(device);

            //tain_warn表
          /*  DeviceWarnEntity tainWarn = new DeviceWarnEntity();
            tainWarn.setExpiredTime(data.getExpiredTime());
            tainWarn.setWarnTime(data.getWarnTime());
            this.tainWarnList.add(tainWarn);*/
            datas.add(data);
        }

        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (this.deviceList.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            this.deviceList.clear();
        }
        if (this.tainWarnList.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            this.tainWarnList.clear();
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        LOGGER.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        LOGGER.info("{}条数据,开始存储数据库!", deviceList.size());
        LOGGER.info("{}条数据,开始存储数据库!", tainWarnList.size());
//        demoDAO.save(list);
//        iTainWarnService.saveBatch(list);
        //存入数据库

        iDeviceService.saveBatch(deviceList);
        iTainWarnService.saveBatch(tainWarnList);

        LOGGER.info("存储数据库成功!");
    }

    /**
     * 保存提示信息到集合
     * @param rowIndex 行数
     * @param desc 提示信息
     * @param tips 存入集合
     */
    private void saveTips(Integer rowIndex, String desc, List<ImportTips> tips) {
        ImportTips tip = new ImportTips();
        tip.setRowNum(rowIndex);
        tip.setTips(desc);
        tips.add(tip);
    }

    /**
     * 返回数据
     * @return 返回提示集合
     **/
    public List<ImportTips> getTips() {
        return tips;
    }

    /**
     * 返回数据
     * @return 返回读取的数据集合
     **/
    public List<WarnVo> getDatas() {
        return datas;
    }
}

service层

device_manage略

device_warn

public interface DeviceWarnService extends IService<DeviceWarnEntity> {

    List<WarnVo> listAllWarn();
}

service_impl层

@Service("DeviceWarnService")
public class DeviceWarnServiceImpl extends ServiceImpl<DeviceWarnDao, DeviceWarnEntity> implements DeviceWarnService {

    @Autowired
    private DeviceWarnDao deviceWarnDao;

    @Override
    public List<WarnVo> listAllWarn() {
        QueryWrapper<WarnVo> qw = new QueryWrapper<>();
        return this.deviceWarnDao.selectWarn(qw);
    }
}

Dao层,使用@Select注解写入sql语句获得所有信息

@Mapper
public interface DeviceWarnDao extends BaseMapper<DeviceWarnEntity> {

    @Select("select w.id,d.workshop,d.device_number,d.device_name,d.device_model,w.warn_time,w.expired_time,w.device_status from device_warn w left join device_manage d on d.id=w.device_id where w.is_deleted = 0")
    List<WarnVo> selectWarn(@Param(Constants.WRAPPER) QueryWrapper<WarnVo> wrapper);
}

前端

<template>
    <div class="mod-config">

      <!-- 导入导出 -->
      <el-form :inline="true">
      <el-form-item>
        <el-button type="primary" icon="el-icon-share" @click="download()" >一键导出
       </el-button>
      </el-form-item>
      <el-form-item>
        <el-upload
          style="width: 400px"
          action="http://localhost:8080/wedu/dev/warn/upload"
          :headers="tokenInfo"
          :on-preview="handlePreview"
          :on-remove="handleRemove"
          :before-remove="beforeRemove"
          multiple
          :limit="3"
          :on-exceed="handleExceed"
          :file-list="fileList">
          <el-button type="primary">点击上传</el-button>
        </el-upload>
      </el-form-item>
    
    </el-form>

      <el-table
        :data="dataList"
        border
        v-loading="dataListLoading"
        @selection-change="selectionChangeHandle"
        style="width: 100%;">
        <el-table-column
          prop="workshop"
          header-align="center"
          align="center"
          label="车间名称">
        </el-table-column>
        <el-table-column
          prop="deviceNumber"
          header-align="center"
          align="center"
          label="设备编号">
        </el-table-column>
        <el-table-column
        prop="deviceName"
        header-align="center"
        align="center"
        label="设备名称">
      </el-table-column>
        <el-table-column
          prop="deviceModel"
          header-align="center"
          align="center"
          label="设备型号">
        </el-table-column>
        <el-table-column
          prop="expiredTime"
          header-align="center"
          align="center"
          label="维保到期时间">
        </el-table-column>
        <el-table-column
          prop="warnTime"
          header-align="center"
          align="center"
          label="维保预警时间">
        </el-table-column>
  </template>
  
  <script>
     export default {
      data () {
        return {
          tokenInfo: {
            token: this.$cookie.get("token"),
          },
          dataForm: {
            deviceNumber: ''
          },
           // 车间字典项
          allchejian: [],
          // 车间筛选
          workshops: [],
          bydicts: [],
          byMap: {},
          fileUploadBtnText: "点击上传", //上传文件提示文字
          fileList: [], // 上传文件列表
          fileUploadVisible:false
            }
          },
     
      activated () {
        this.getDataList();
        this.loadAllChejian("workshop");
        this.getBydicts("deviceModel");
      },
      methods: {
        fileUpload(){
          this.fileUploadVisible = true;
            this.$nextTick(() => {
              this.$refs.FileUpload.init();
            });
        },
        // 文件列表移除文件时的钩子
        handleRemove(file, fileList) {
          console.log(file, fileList);
        },
        // 点击文件列表中已上传的文件时的钩子
        handlePreview(file) {
          console.log(file);
        },
        // 限制上传文件的个数和定义超出限制时的行为
        handleExceed(files, fileList) {
          this.$message.warning(
            `当前限制选择 3 个文件,本次选择了 ${files.length} 个文件,共选择了 ${
              files.length + fileList.length
            } 个文件`
          );
        },
        // 文件列表移除文件时之前的钩子
        beforeRemove(file, fileList) {
          return this.$confirm(`确定移除 ${file.name}?`);
        },
        onUploadExcelError(response) {
          if (res.code === 500) this.$message.error(res.message);
        },

        //文件导出
        download() {
          this.$http({
            url: this.$http.adornUrl("/dev/warn/download"),
            method: "get",
            //设置响应类型(重要
            responseType: "blob",
          }).then((response) => {
            // 创建一个url,接收到的二进制数据
            const url = window.URL.createObjectURL(new Blob([response.data]));
            // 创建一个a标签,用于触发文件下载
            const link = document.createElement("a");
            // a元素的href属性为创建的url
            link.href = url;
            link.setAttribute("download", "保养预警.xlsx");
            // 将a添加到文档
            document.body.appendChild(link);
            // 触发a的点击事件开始下载
            link.click();
          });
        },

        getWorkshopName(value) {
          const workshop = this.allchejian.find((item) => item.value === value);
          return workshop ? workshop.name : "";
        },
        getBydicts(code) {
          this.$http({
            url: this.$http.adornUrl("/sys/dict/maintenanceList"),
            method: "get",
            params: this.$http.adornParams({
              code: code,
            }),
          }).then(({ data }) => {
            if (data && data.code === 0) {
              this.bydicts = data.list;
              this.bydicts.forEach((dict) => {
                this.$set(this.byMap, dict.value, dict.name);
              });
            } else {
              this.bydicts = [];
            }
          });
        },
        //加载车间 所需的数据zyb
        loadAllChejian(code) {
          this.allchejian = [];
          this.$http({
            url: this.$http.adornUrl("/sys/dict/maintenanceList"),
            method: "post",
            params: this.$http.adornParams({
              code: code,
            }),
          }).then(({ data }) => {
            if (data && data.code === 0) {
              this.allchejian = data.list.map((item) => ({
                name: item.name,
                value: item.value,
              }));
              this.workshop = data.list.map((item) => ({
                text: item.name,
                value: item.value,
              }));
            } else {
            }
          });
        },
        // 车间筛选方法 zyb
        filterHandler(value, row, column) {
          const property = column["property"];
          return row[property] === value;
        }
     
    }
  </script>

最终效果

点击导出

下载的excel表格中车间名称和设备型号有下拉框,对应前端配置的字典类

点击导入

将这个格式的excel导入

在数据库新增一条数据

;