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导入
在数据库新增一条数据