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导入

在数据库新增一条数据

相关推荐
MaCa .BaKa24 分钟前
35-疫苗预约管理系统(微服务)
spring boot·redis·微服务·云原生·架构·springcloud
秋野酱32 分钟前
基于 Spring Boot 的银行柜台管理系统设计与实现(源码+文档+部署讲解)
java·spring boot·后端
獨枭1 小时前
Spring Boot 连接 Microsoft SQL Server 实现登录验证
spring boot·后端·microsoft
shanzhizi1 小时前
springboot入门-controller层
java·spring boot·后端
爱的叹息1 小时前
Spring和Spring Boot集成MyBatis的完整对比示例,包含从项目创建到测试的全流程代码
spring boot·spring·mybatis
游王子1 小时前
springboot3 声明式 HTTP 接口
网络·spring boot·网络协议·http
三原2 小时前
2025 乾坤(qiankun)和 Vue3 最佳实践(提供模版)
vue.js·架构·前端框架
DC...2 小时前
vue滑块组件设计与实现
前端·javascript·vue.js
H5开发新纪元3 小时前
Vite 项目打包分析完整指南:从配置到优化
前端·vue.js
CHQIUU3 小时前
告别手动映射:在 Spring Boot 3 中优雅集成 MapStruct
spring boot·后端·状态模式