mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-09-20 10:13:28 +08:00
Excel导入模板增加批注支持
This commit is contained in:
parent
0b0a52c699
commit
3f19a39721
@ -0,0 +1,33 @@
|
|||||||
|
package com.ruoyi.common.excel.builder;
|
||||||
|
|
||||||
|
import com.ruoyi.common.excel.common.SheetModel;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单元格信息构建器
|
||||||
|
*
|
||||||
|
* @author liyang
|
||||||
|
**/
|
||||||
|
@Getter
|
||||||
|
public abstract class CellModelBuilder extends SheetModelBuilder {
|
||||||
|
/**
|
||||||
|
* 列索引
|
||||||
|
*/
|
||||||
|
protected final int colIndex;
|
||||||
|
/**
|
||||||
|
* 行索引
|
||||||
|
*/
|
||||||
|
protected final int rowIndex;
|
||||||
|
|
||||||
|
protected CellModelBuilder(String sheetName, int rowIndex, int colIndex) {
|
||||||
|
super(sheetName);
|
||||||
|
this.rowIndex = rowIndex;
|
||||||
|
this.colIndex = colIndex;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 具体构建方法
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
abstract protected SheetModel build() ;
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
package com.ruoyi.common.excel.builder;
|
||||||
|
|
||||||
|
|
||||||
|
import com.ruoyi.common.excel.common.SheetModel;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sheet页信息构建器
|
||||||
|
*
|
||||||
|
* @author liyang
|
||||||
|
**/
|
||||||
|
@Getter
|
||||||
|
public abstract class SheetModelBuilder {
|
||||||
|
/**
|
||||||
|
* sheet名称
|
||||||
|
*/
|
||||||
|
protected final String sheetName;
|
||||||
|
protected SheetModelBuilder(String sheetName) {
|
||||||
|
this.sheetName = sheetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 具体构建方法
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
abstract protected SheetModel build() ;
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package com.ruoyi.common.excel.common;
|
||||||
|
|
||||||
|
import com.ruoyi.common.excel.builder.CellModelBuilder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单元格信息
|
||||||
|
*
|
||||||
|
* @author liyang
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class SheetCellModel extends SheetModel {
|
||||||
|
/**
|
||||||
|
* 列索引
|
||||||
|
*/
|
||||||
|
protected int colIndex;
|
||||||
|
/**
|
||||||
|
* 行索引
|
||||||
|
*/
|
||||||
|
protected int rowIndex;
|
||||||
|
|
||||||
|
public SheetCellModel(CellModelBuilder cellModelBuilder) {
|
||||||
|
super(cellModelBuilder);
|
||||||
|
this.rowIndex = cellModelBuilder.getRowIndex();
|
||||||
|
this.colIndex = cellModelBuilder.getColIndex();
|
||||||
|
}
|
||||||
|
protected SheetCellModel(){
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package com.ruoyi.common.excel.common;
|
||||||
|
|
||||||
|
import com.ruoyi.common.excel.builder.SheetModelBuilder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sheet页信息
|
||||||
|
*
|
||||||
|
* @author liyang
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class SheetModel {
|
||||||
|
/**
|
||||||
|
* sheet名称
|
||||||
|
*/
|
||||||
|
protected String sheetName;
|
||||||
|
|
||||||
|
public SheetModel(SheetModelBuilder sheetModelBuilder) {
|
||||||
|
this.sheetName = sheetModelBuilder.getSheetName();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SheetModel() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,90 @@
|
|||||||
|
package com.ruoyi.common.excel.handler;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.alibaba.excel.write.handler.RowWriteHandler;
|
||||||
|
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
|
||||||
|
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
|
||||||
|
import com.ruoyi.common.excel.model.CommentModel;
|
||||||
|
import com.ruoyi.common.utils.poi.PoiExcelUtil;
|
||||||
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
|
import org.apache.poi.ss.usermodel.Row;
|
||||||
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义批注处理器
|
||||||
|
*
|
||||||
|
* @author liyang
|
||||||
|
*/
|
||||||
|
public class CustomCommentWriteHandler implements RowWriteHandler {
|
||||||
|
/**
|
||||||
|
* sheet页名称列表
|
||||||
|
*/
|
||||||
|
private List<String> sheetNameList;
|
||||||
|
List<CommentModel> commentList = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义批注适配器构造方法
|
||||||
|
*
|
||||||
|
* @param commentList 批注信息
|
||||||
|
* @param extension 文件后缀(xlsx、xls)
|
||||||
|
*/
|
||||||
|
public CustomCommentWriteHandler(List<CommentModel> commentList, String extension) {
|
||||||
|
if (CollUtil.isEmpty(commentList)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//文件不为指定的格式时,默认为Xlsx
|
||||||
|
if (StrUtil.equals(extension, "xlsx") == false && StrUtil.equals(extension, "xls") == false) {
|
||||||
|
extension = "xlsx";
|
||||||
|
}
|
||||||
|
this.commentList = commentList.stream().filter(x ->
|
||||||
|
StrUtil.isNotBlank(x.getSheetName()) && x.getColIndex() >=0 && x.getRowIndex() >= 0 && StrUtil.isNotBlank(x.getCommentContent())
|
||||||
|
).collect(Collectors.toList());
|
||||||
|
sheetNameList = this.commentList.stream().map(x -> x.getSheetName()).distinct().collect(Collectors.toList());
|
||||||
|
this.extension = extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文档后缀名
|
||||||
|
*/
|
||||||
|
private String extension;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,
|
||||||
|
Integer relativeRowIndex, Boolean isHead) {
|
||||||
|
Sheet sheet = writeSheetHolder.getSheet();
|
||||||
|
//不需要添加批注,或者当前sheet页不需要添加批注
|
||||||
|
if (CollUtil.isEmpty(commentList) || sheetNameList.contains(sheet.getSheetName()) == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//获取当前行的批注信息
|
||||||
|
List<CommentModel> rowCommentList = commentList.stream().filter(x ->
|
||||||
|
StrUtil.equals(x.getSheetName(), sheet.getSheetName())
|
||||||
|
&& StrUtil.equals(String.valueOf(relativeRowIndex), String.valueOf(x.getRowIndex()))).collect(Collectors.toList());
|
||||||
|
//当前行没有批注信息
|
||||||
|
if (CollUtil.isEmpty(rowCommentList)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<Integer> colIndexList = rowCommentList.stream().map(x -> x.getColIndex()).distinct().collect(Collectors.toList());
|
||||||
|
for (Integer colIndex : colIndexList) {
|
||||||
|
//同一单元格的批注信息
|
||||||
|
List<CommentModel> cellCommentList = rowCommentList.stream().filter(x ->
|
||||||
|
StrUtil.equals(String.valueOf(colIndex), String.valueOf(x.getColIndex()))).collect(Collectors.toList());
|
||||||
|
if (CollUtil.isEmpty(cellCommentList)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
//批注内容拼成一条
|
||||||
|
String commentContent = cellCommentList.stream().map(x -> x.getCommentContent()).collect(Collectors.joining());
|
||||||
|
Cell cell = row.getCell(colIndex);
|
||||||
|
PoiExcelUtil.addComment(cell, commentContent, extension);
|
||||||
|
}
|
||||||
|
//删除批注信息
|
||||||
|
commentList.remove(rowCommentList);
|
||||||
|
//重新获取要添加的sheet页姓名
|
||||||
|
sheetNameList = commentList.stream().map(x -> x.getSheetName()).distinct().collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
package com.ruoyi.common.excel.model;
|
||||||
|
|
||||||
|
import com.ruoyi.common.excel.common.SheetCellModel;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批注信息类
|
||||||
|
*
|
||||||
|
* @author liyang
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class CommentModel extends SheetCellModel {
|
||||||
|
/**
|
||||||
|
* 批注内容
|
||||||
|
*/
|
||||||
|
private String commentContent;
|
||||||
|
|
||||||
|
private CommentModel() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成批注信息
|
||||||
|
*
|
||||||
|
* @param sheetName sheet页名称
|
||||||
|
* @param rowIndex 行号
|
||||||
|
* @param columnIndex 列号
|
||||||
|
* @param commentContent 批注内容
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static CommentModel createCommentModel(String sheetName, int rowIndex, int columnIndex, String commentContent) {
|
||||||
|
CommentModel commentModel = new CommentModel();
|
||||||
|
//sheet页名称
|
||||||
|
commentModel.sheetName = sheetName;
|
||||||
|
//行号
|
||||||
|
commentModel.rowIndex = rowIndex;
|
||||||
|
//列号
|
||||||
|
commentModel.colIndex = columnIndex;
|
||||||
|
//批注内容
|
||||||
|
commentModel.commentContent = commentContent;
|
||||||
|
return commentModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,6 +15,8 @@ import com.ruoyi.common.excel.CellMergeStrategy;
|
|||||||
import com.ruoyi.common.excel.DefaultExcelListener;
|
import com.ruoyi.common.excel.DefaultExcelListener;
|
||||||
import com.ruoyi.common.excel.ExcelListener;
|
import com.ruoyi.common.excel.ExcelListener;
|
||||||
import com.ruoyi.common.excel.ExcelResult;
|
import com.ruoyi.common.excel.ExcelResult;
|
||||||
|
import com.ruoyi.common.excel.handler.CustomCommentWriteHandler;
|
||||||
|
import com.ruoyi.common.excel.model.CommentModel;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.common.utils.file.FileUtils;
|
import com.ruoyi.common.utils.file.FileUtils;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
@ -116,7 +118,49 @@ public class ExcelUtil {
|
|||||||
throw new RuntimeException("导出Excel异常");
|
throw new RuntimeException("导出Excel异常");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 导出excel模板
|
||||||
|
*
|
||||||
|
* @param list 导出数据集合
|
||||||
|
* @param sheetName 工作表的名称
|
||||||
|
* @param clazz 实体类
|
||||||
|
* @param commentList 批注
|
||||||
|
* @param response 响应体
|
||||||
|
*/
|
||||||
|
public static <T> void exportExcelCommentTemplate(List<T> list, List<CommentModel> commentList, String sheetName, Class<T> clazz, HttpServletResponse response) {
|
||||||
|
exportExcelCommentTemplate(list,commentList, sheetName, clazz, false, response);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 导出excel模板
|
||||||
|
*
|
||||||
|
* @param list 导出数据集合
|
||||||
|
* @param sheetName 工作表的名称
|
||||||
|
* @param clazz 实体类
|
||||||
|
* @param merge 是否合并单元格
|
||||||
|
* @param commentList 批注
|
||||||
|
* @param response 响应体
|
||||||
|
*/
|
||||||
|
public static <T> void exportExcelCommentTemplate(List<T> list, List<CommentModel> commentList, String sheetName, Class<T> clazz, boolean merge, HttpServletResponse response) {
|
||||||
|
try {
|
||||||
|
resetResponse(sheetName, response);
|
||||||
|
ServletOutputStream os = response.getOutputStream();
|
||||||
|
ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
|
||||||
|
.autoCloseStream(false)
|
||||||
|
// 自动适配
|
||||||
|
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||||
|
.registerWriteHandler(new CustomCommentWriteHandler(commentList, "xlsx"))
|
||||||
|
// 大数值自动转换 防止失真
|
||||||
|
.registerConverter(new ExcelBigNumberConvert())
|
||||||
|
.sheet(sheetName);
|
||||||
|
if (merge) {
|
||||||
|
// 合并处理器
|
||||||
|
builder.registerWriteHandler(new CellMergeStrategy(list, true));
|
||||||
|
}
|
||||||
|
builder.doWrite(list);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("导出Excel异常");
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 单表多数据模板导出 模板格式为 {.属性}
|
* 单表多数据模板导出 模板格式为 {.属性}
|
||||||
*
|
*
|
||||||
|
|||||||
@ -0,0 +1,70 @@
|
|||||||
|
package com.ruoyi.common.utils.poi;
|
||||||
|
|
||||||
|
import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
|
||||||
|
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
|
||||||
|
import org.apache.poi.ss.usermodel.*;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POI Excel工具类
|
||||||
|
*
|
||||||
|
* @author liyang
|
||||||
|
*/
|
||||||
|
public class PoiExcelUtil {
|
||||||
|
/**
|
||||||
|
* 给Cell添加批注
|
||||||
|
*
|
||||||
|
* @param cell 单元格
|
||||||
|
* @param value 批注内容
|
||||||
|
* @param extension 扩展名
|
||||||
|
*/
|
||||||
|
public static void addComment(Cell cell, String value, String extension) {
|
||||||
|
Sheet sheet = cell.getSheet();
|
||||||
|
cell.removeCellComment();
|
||||||
|
if ("xls".equals(extension)) {
|
||||||
|
ClientAnchor anchor = new HSSFClientAnchor();
|
||||||
|
// 关键修改
|
||||||
|
anchor.setDx1(0);
|
||||||
|
anchor.setDx2(0);
|
||||||
|
anchor.setDy1(0);
|
||||||
|
anchor.setDy2(0);
|
||||||
|
anchor.setCol1(cell.getColumnIndex());
|
||||||
|
anchor.setRow1(cell.getRowIndex());
|
||||||
|
anchor.setCol2(cell.getColumnIndex() + 5);
|
||||||
|
anchor.setRow2(cell.getRowIndex() + 6);
|
||||||
|
// 结束
|
||||||
|
Drawing drawing = sheet.getDrawingPatriarch();
|
||||||
|
if (drawing == null) {
|
||||||
|
drawing = sheet.createDrawingPatriarch();
|
||||||
|
}
|
||||||
|
Comment comment = drawing.createCellComment(anchor);
|
||||||
|
// 输入批注信息
|
||||||
|
comment.setString(new HSSFRichTextString(value));
|
||||||
|
// 将批注添加到单元格对象中
|
||||||
|
cell.setCellComment(comment);
|
||||||
|
} else if ("xlsx".equals(extension)) {
|
||||||
|
ClientAnchor anchor = new XSSFClientAnchor();
|
||||||
|
// 关键修改
|
||||||
|
anchor.setDx1(0);
|
||||||
|
anchor.setDx2(0);
|
||||||
|
anchor.setDy1(0);
|
||||||
|
anchor.setDy2(0);
|
||||||
|
anchor.setCol1(cell.getColumnIndex());
|
||||||
|
anchor.setRow1(cell.getRowIndex());
|
||||||
|
anchor.setCol2(cell.getColumnIndex() + 5);
|
||||||
|
anchor.setRow2(cell.getRowIndex() + 6);
|
||||||
|
// 结束
|
||||||
|
Drawing drawing = sheet.getDrawingPatriarch();
|
||||||
|
if (drawing == null) {
|
||||||
|
drawing = sheet.createDrawingPatriarch();
|
||||||
|
}
|
||||||
|
Comment comment = drawing.createCellComment(anchor);
|
||||||
|
// 输入批注信息
|
||||||
|
comment.setString(new XSSFRichTextString(value));
|
||||||
|
// 将批注添加到单元格对象中
|
||||||
|
cell.setCellComment(comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -13,6 +13,7 @@ import com.ruoyi.common.core.validate.EditGroup;
|
|||||||
import com.ruoyi.common.core.validate.QueryGroup;
|
import com.ruoyi.common.core.validate.QueryGroup;
|
||||||
import com.ruoyi.common.enums.BusinessType;
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
import com.ruoyi.common.excel.ExcelResult;
|
import com.ruoyi.common.excel.ExcelResult;
|
||||||
|
import com.ruoyi.common.excel.model.CommentModel;
|
||||||
import com.ruoyi.common.utils.ValidatorUtils;
|
import com.ruoyi.common.utils.ValidatorUtils;
|
||||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||||
import com.ruoyi.demo.domain.TestDemo;
|
import com.ruoyi.demo.domain.TestDemo;
|
||||||
@ -29,6 +30,7 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.validation.constraints.NotEmpty;
|
import javax.validation.constraints.NotEmpty;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@ -145,4 +147,17 @@ public class TestDemoController extends BaseController {
|
|||||||
@PathVariable Long[] ids) {
|
@PathVariable Long[] ids) {
|
||||||
return toAjax(iTestDemoService.deleteWithValidByIds(Arrays.asList(ids), true) ? 1 : 0);
|
return toAjax(iTestDemoService.deleteWithValidByIds(Arrays.asList(ids), true) ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取导入模板
|
||||||
|
*/
|
||||||
|
@PostMapping("/importTemplate")
|
||||||
|
public void importTemplate(HttpServletResponse response) {
|
||||||
|
List<CommentModel> commentList = new ArrayList<>();
|
||||||
|
String sheetName = "组织机构";
|
||||||
|
commentList.add(CommentModel.createCommentModel(sheetName, 0, 0, "部门id不能为空\n换行文本"));
|
||||||
|
commentList.add(CommentModel.createCommentModel(sheetName, 0, 1, "用户id不能为空\n换行文本"));
|
||||||
|
commentList.add(CommentModel.createCommentModel(sheetName, 0, 2, "排序好不能为空\n换行文本"));
|
||||||
|
ExcelUtil.exportExcelCommentTemplate(new ArrayList<>(),commentList, sheetName, TestDemoImportVo.class, response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
如果使用的是RuoYi-Vue3前端,那么需要覆盖一下此目录的模板index.vue.vm、index-tree.vue.vm文件到上级vue目录。
|
如果使用的是Vue3前端,那么需要覆盖一下此目录的模板index.vue.vm、index-tree.vue.vm文件到上级vue目录。
|
||||||
|
|||||||
@ -190,6 +190,10 @@
|
|||||||
>
|
>
|
||||||
<i class="el-icon-upload"></i>
|
<i class="el-icon-upload"></i>
|
||||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||||
|
<div class="el-upload__tip text-center" slot="tip">
|
||||||
|
<span>仅允许导入xls、xlsx格式文件。</span>
|
||||||
|
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
|
||||||
|
</div>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
<div slot="footer" class="dialog-footer">
|
<div slot="footer" class="dialog-footer">
|
||||||
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
||||||
@ -425,7 +429,12 @@ export default {
|
|||||||
// 提交上传文件
|
// 提交上传文件
|
||||||
submitFileForm() {
|
submitFileForm() {
|
||||||
this.$refs.upload.submit();
|
this.$refs.upload.submit();
|
||||||
}
|
},
|
||||||
|
/** 下载模板操作 */
|
||||||
|
importTemplate() {
|
||||||
|
this.download('/demo/demo/importTemplate', {
|
||||||
|
}, `unit_template_${new Date().getTime()}.xlsx`)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user