Kaynağa Gözat

feat:
新增导出excel时级联操作

18339543638 2 yıl önce
ebeveyn
işleme
a7f3ecd8b0

+ 10 - 0
tr-modules-api/tr-module-export-api/src/main/java/cn/tr/module/export/annotation/ExcelSelect.java

@@ -25,4 +25,14 @@ public @interface ExcelSelect {
      */
     Class<? extends AbstractSelectConverter<?>> converter() default NoneSelectConverter.class;
 
+    /**
+     * 级联时的父级属性名称
+     * <pre>
+     *     省-> 市-> 区
+     *     区的 linkageParentProperty 为 市
+     *     市的 linkageParentProperty 为 省
+     * <pre/>
+     */
+    String linkageParentProperty() default "";
+
 }

+ 15 - 2
tr-modules-api/tr-module-export-api/src/main/java/cn/tr/module/export/converter/DictSelectConverter.java

@@ -8,8 +8,10 @@ import cn.tr.module.export.handler.AbstractSelectConverter;
 import cn.tr.module.api.sys.dict.SysDictApi;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * @ClassName : DictComboConverter
@@ -32,10 +34,21 @@ public class DictSelectConverter extends AbstractSelectConverter<String> {
         return CollectionUtil.isNotEmpty(result)?result:new ArrayList<>();
     }
 
+    @Override
+    public List<Pair<String, List<String>>> buildLinkageSelectList(Collection<String> params) {
+        String dictCode = CollectionUtil.getFirst(params);
+        List<Pair<String, String>> result = dictApi.findAllChildrenDictsByCode(dictCode);
+        return Arrays.asList(Pair.of("",
+                result.stream()
+                        .map(Pair::getKey)
+                        .collect(Collectors.toList())));
+    }
+
     @Override
     public String extractUniqueCode(String content, Collection<String> params) {
-        List<Pair<String, String>> pairs = doComboList(params);
-        for (Pair<String, String> pair : pairs) {
+        String dictCode = CollectionUtil.getFirst(params);
+        List<Pair<String, String>> result = dictApi.findAllChildrenDictsByCode(dictCode);
+        for (Pair<String, String> pair : result) {
             if (ObjectUtil.equals(content, pair.getKey())) {
                 return pair.getValue();
             }

+ 20 - 1
tr-modules-api/tr-module-export-api/src/main/java/cn/tr/module/export/handler/AbstractSelectConverter.java

@@ -2,9 +2,11 @@ package cn.tr.module.export.handler;
 
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.lang.Pair;
+import cn.hutool.core.lang.Tuple;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.tr.module.export.annotation.ExcelPropertySupport;
+import cn.tr.module.export.annotation.ExcelSelect;
 import com.alibaba.excel.converters.Converter;
 import com.alibaba.excel.metadata.GlobalConfiguration;
 import com.alibaba.excel.metadata.data.ReadCellData;
@@ -137,11 +139,28 @@ public abstract class AbstractSelectConverter<T> implements Converter<T> {
      * 获取数据集合的真正实现方法
      * @param params
      * @return
+     * 当value为null或"" 且 @link ExcelSelect#linkageParentProperty()} 存在,即存在级联但父级级联为空,此时 父子二级级联下拉菜单都为空
+     * 当value不为空 且 @link ExcelSelect#linkageParentProperty()} 存在,即存在级联但父级级联为空,父级菜单为value值,子级菜单为key值
+     * 当value不为空 且 @link ExcelSelect#linkageParentProperty()} 不存在,即不存在父级菜单,仅对子级菜单进行下拉选项
      * <pre>
-     *     {"key":"uniqueCode","value":"desc}
+     *     {"key":"uniqueCode","value":"级联时的父级值"}
      * </pre>
      */
     public abstract List<Pair<T, String>>  doComboList(Collection<String> params);
 
+    /**
+     * 根据菜单获取级联列表
+     * @param params 查询列表参数集合
+     * @return List<Pair<String,List<String>>> 级联列表
+     * <pre>
+     * [{"河南省",["郑州市","新乡市","平顶山市"]},
+     * {"河北省",["石家庄市","邯郸市"]}]
+     *
+     * Pair中的value值为当前字段所需要的列表,若不存在上级级联,返回一个Pair即可,Key为''或null
+     * [{"",["是","否"]}]
+     * <pre/>
+     */
+    public abstract List<Pair<String,List<String>>>  buildLinkageSelectList(Collection<String> params);
+
     public abstract Class<T> doSupportJavaTypeKey();
 }

+ 5 - 0
tr-modules-api/tr-module-export-api/src/main/java/cn/tr/module/export/handler/NoneSelectConverter.java

@@ -20,6 +20,11 @@ public class NoneSelectConverter extends AbstractSelectConverter<Void> {
         return new ArrayList<>();
     }
 
+    @Override
+    public List<Pair<String, List<String>>> buildLinkageSelectList(Collection<String> params) {
+        return new ArrayList<>();
+    }
+
     @Override
     public Class<Void> doSupportJavaTypeKey() {
         return Void.class;

+ 22 - 0
tr-modules/tr-module-export/src/main/java/cn/tr/module/excel/core/dto/ExcelLinkageSelectDTO.java

@@ -0,0 +1,22 @@
+package cn.tr.module.excel.core.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @ClassName : ExcelLinkageSelectDTO
+ * @Description :
+ * @Author : LF
+ * @Date: 2023年09月22日
+ */
+@Data
+public class ExcelLinkageSelectDTO {
+    @ApiModelProperty("父级级联属性名称")
+    private String parentLinkagePropertyName;
+
+    @ApiModelProperty("子级级联属性名称")
+    private String childLinkagePropertyName;
+
+
+
+}

+ 31 - 15
tr-modules/tr-module-export/src/main/java/cn/tr/module/excel/core/handler/write/CustomCellWriteHandler.java

@@ -1,6 +1,7 @@
 package cn.tr.module.excel.core.handler.write;
 
 import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.lang.Pair;
 import cn.hutool.core.util.CharUtil;
 import cn.hutool.core.util.ClassUtil;
 import cn.hutool.core.util.ReflectUtil;
@@ -19,14 +20,12 @@ import com.alibaba.excel.write.metadata.style.WriteCellStyle;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.ss.util.CellRangeAddressList;
 import org.apache.poi.xssf.usermodel.*;
+import reactor.util.function.Tuple2;
 
 import javax.validation.constraints.NotNull;
 import java.lang.reflect.Field;
 import java.time.temporal.Temporal;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
 
 /**
  * @ClassName : CustomSheetWriteHandler
@@ -36,6 +35,21 @@ import java.util.List;
  */
 
 public class CustomCellWriteHandler implements CellWriteHandler {
+    //excel中的属性列的位置
+   private final Map<String, Integer> excelColumnProperty;
+
+    //级联属性映射集合   map("下级属性","上级属性")
+    private final Map<String, String> linkagePropertyMap;
+
+    //属性所对应的级联集合
+    private final Map<String, List<Pair<String, List<String>>>> selectPropertyMap ;
+
+    public CustomCellWriteHandler(Map<String, Integer> excelColumnProperty, Map<String, String> linkagePropertyMap, Map<String,List<Pair<String, List<String>>>> selectPropertyMap) {
+        this.excelColumnProperty = excelColumnProperty;
+        this.linkagePropertyMap = linkagePropertyMap;
+        this.selectPropertyMap = selectPropertyMap;
+    }
+
     @Override
     public void afterCellDispose(CellWriteHandlerContext context) {
         Cell cell = context.getCell();
@@ -50,12 +64,6 @@ public class CustomCellWriteHandler implements CellWriteHandler {
             createComboColumn(field,cell);
             //进行日期格式校验
             createDateColumn(field,cell);
-        }else {
-            String sheetName = context.getWriteSheetHolder().getSheetName();
-            if (StrUtil.contains(sheetName, "样例")) {
-                //样例
-                cell.getCellStyle().setFillBackgroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
-            }
         }
     }
 
@@ -118,12 +126,20 @@ public class CustomCellWriteHandler implements CellWriteHandler {
                 return;
             }
         }
+
         AbstractSelectConverter<?> converter = ReflectUtil.newInstance(converterClass);
-        List<String> values = converter.comboList(Arrays.asList(select.param()));
-        if(CollectionUtil.isEmpty(values)){
-            return;
-        }
-        generateRangeList( cell.getSheet(),values,cell.getColumnIndex(),cell.getSheet().getWorkbook());
+        List<Pair<String, List<String>>> linkageSelectList = converter.buildLinkageSelectList(Arrays.asList(select.param()));
+
+        excelColumnProperty.put(field.getName(),cell.getColumnIndex());
+        linkagePropertyMap.put(field.getName(),select.linkageParentProperty());
+        selectPropertyMap.put(field.getName(),linkageSelectList);
+//        List<String> values = converter.comboList(Arrays.asList(select.param()));
+//        if(CollectionUtil.isEmpty(values)){
+//            return;
+//        }
+
+
+//        generateRangeList( cell.getSheet(),values,cell.getColumnIndex(),cell.getSheet().getWorkbook());
     }
 
     /**

+ 116 - 3
tr-modules/tr-module-export/src/main/java/cn/tr/module/excel/core/handler/write/CustomSheetWriteHandler.java

@@ -1,20 +1,50 @@
 package cn.tr.module.excel.core.handler.write;
 
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.lang.Pair;
+import cn.hutool.core.util.CharUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.tr.module.constant.ExcelConstant;
 import com.alibaba.excel.write.handler.SheetWriteHandler;
 import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
 import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
-import org.apache.poi.ss.usermodel.Sheet;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddressList;
+import org.apache.poi.xssf.usermodel.XSSFDataValidation;
+import org.apache.poi.xssf.usermodel.XSSFDataValidationConstraint;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
 
 
 /**
  * 固定表头和添加过滤
  */
+@Slf4j
 public class CustomSheetWriteHandler implements SheetWriteHandler {
 
     public int colSplit = 0, rowSplit = 1, leftmostColumn = 0, topRow = 1;
 
-    @Override
+    //excel中的属性列的位置
+    private final Map<String, Integer> excelColumnProperty;
+
+    //级联属性映射集合   map("下级属性","上级属性")
+    private final Map<String, String> linkagePropertyMap;
 
+    //属性所对应的级联集合
+    private final Map<String, List<Pair<String, List<String>>>> selectPropertyMap ;
+
+    public CustomSheetWriteHandler(Map<String, Integer> excelColumnProperty, Map<String, String> linkagePropertyMap, Map<String, List<Pair<String, List<String>>>> selectPropertyMap) {
+        this.excelColumnProperty = excelColumnProperty;
+        this.linkagePropertyMap = linkagePropertyMap;
+        this.selectPropertyMap = selectPropertyMap;
+    }
+
+    @Override
     public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
 
     }
@@ -23,7 +53,90 @@ public class CustomSheetWriteHandler implements SheetWriteHandler {
     public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
         Sheet sheet = writeSheetHolder.getSheet();
         sheet.createFreezePane(colSplit, rowSplit, leftmostColumn, topRow);
-//        sheet.protectSheet("123456");
+        createLinkageSelectColumn(sheet);
+    }
+
+    /**
+     * 创建级联搜索列
+     * @param sheet
+     */
+    private void createLinkageSelectColumn(Sheet sheet){
+        //没有级联
+        if(CollectionUtil.isEmpty(linkagePropertyMap)){
+            return;
+        }
+        //已经处理过的字段
+        List<Object> doneProperty = new ArrayList<>();
+        linkagePropertyMap.forEach((currentProperty,parentProperty)->{
+            if(StrUtil.isBlank(parentProperty)){
+                //非级联
+                noneLinkageSelectColumn(sheet,currentProperty);
+            }else {
+                //级联
+            }
+        });
     }
 
+    private void noneLinkageSelectColumn(Sheet sheet,String property){
+        Integer columnIndex = excelColumnProperty.get(property);
+        if(ObjectUtil.isNull(columnIndex)){
+            log.warn("[execl文件导出],property:{}显示为级联属性,找不到对应的列数",property);
+            return;
+        }
+        List<Pair<String, List<String>>> optionalSelectValues = selectPropertyMap.get(property);
+        List<String> selectValues = new ArrayList<>();
+        if(CollectionUtil.isEmpty(optionalSelectValues)){
+            selectValues.add("--- 暂无数据 ---");
+        }else {
+            optionalSelectValues.forEach(pair->{
+                if(CollectionUtil.isNotEmpty(pair.getValue())){
+                    selectValues.addAll(pair.getValue());
+                }
+            });
+        }
+        doCreateNoneLinkageSelectColumn(property,sheet,selectValues,columnIndex,sheet.getWorkbook());
+    }
+
+
+    private void doCreateNoneLinkageSelectColumn(String propertyName,Sheet sheet, Collection<String> options, int column, Workbook wb) {
+        Sheet hiddenSheet = wb.getSheet(propertyName);
+        if(hiddenSheet==null){
+            hiddenSheet = wb.createSheet(propertyName);
+        }
+        Row row;
+        //写入下拉数据到新的sheet页中
+        for (int i = 0; i < options.size(); i++) {
+            row = hiddenSheet.getRow(i);
+            if(row==null){
+                row=hiddenSheet.createRow(i);
+            }
+            Cell cell = row.createCell(column);
+            cell.setCellValue(CollectionUtil.get(options,i));
+        }
+        //获取新sheet页内容
+        String strFormula = propertyName + "!$"+ CharUtil.toString((char)(65+column))+"$1:$"+CharUtil.toString((char)(65+column))+"$65535";
+        XSSFDataValidationConstraint constraint = new XSSFDataValidationConstraint(DataValidationConstraint.ValidationType.LIST,strFormula);
+        // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列
+        CellRangeAddressList regions = createColumnCellRange(column);
+        // 数据有效性对象
+        DataValidationHelper helper = sheet.getDataValidationHelper();
+        DataValidation dataValidation = helper.createValidation(constraint, regions);
+        if (dataValidation instanceof XSSFDataValidation) {
+            // 数据校验
+            dataValidation.setSuppressDropDownArrow(true);
+            dataValidation.setShowErrorBox(true);
+        } else {
+            dataValidation.setSuppressDropDownArrow(false);
+        }
+        // 作用在目标sheet上
+        sheet.addValidationData(dataValidation);
+        //将新建的sheet页隐藏掉
+        wb.setSheetHidden(wb.getSheetIndex(propertyName), true);
+    }
+
+    private CellRangeAddressList createColumnCellRange(int column){
+        return new CellRangeAddressList(1, ExcelConstant.MAX_ROW,column,column);
+    }
+
+
 }

+ 9 - 2
tr-modules/tr-module-export/src/main/java/cn/tr/module/excel/core/service/ExcelService.java

@@ -2,6 +2,7 @@ package cn.tr.module.excel.core.service;
 
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.lang.Pair;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.tr.module.api.sys.storage.SysStorageApi;
@@ -132,12 +133,18 @@ public class ExcelService {
     }
 
     private <T> void createSheet(ExcelWriter excelWriter,int sheetNo,String sheetName,Class<T> aClass, Collection<T> data){
+        //excel中的属性列的位置
+        Map<String, Integer> excelColumnProperty = new HashMap<>();
+        //级联属性映射集合   map("下级属性","上级属性")
+        Map<String, String> linkagePropertyMap = new HashMap<>();
+        //属性所对应的级联集合
+        Map<String, List<Pair<String, List<String>>>> selectPropertyMap = new HashMap<>();
         WriteSheet writeSheet = EasyExcel.writerSheet(sheetNo, sheetName)
                 .registerWriteHandler(StrUtil.contains(sheetName, "样例")?horizontalSampleCellStyleStrategy:horizontalCellStyleStrategy)
                 .registerWriteHandler(new SimpleColumnWidthStyleStrategy(13))
                 .registerWriteHandler(new SimpleRowHeightStyleStrategy((short) 20, (short) 20))
-                .registerWriteHandler(new CustomCellWriteHandler())
-                .registerWriteHandler(new CustomSheetWriteHandler())
+                .registerWriteHandler(new CustomCellWriteHandler(excelColumnProperty,linkagePropertyMap,selectPropertyMap))
+                .registerWriteHandler(new CustomSheetWriteHandler(excelColumnProperty,linkagePropertyMap,selectPropertyMap))
                 .head(aClass).build();
         excelWriter.write(data,writeSheet);
     }