Просмотр исходного кода

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

18339543638 2 лет назад
Родитель
Сommit
2208bf1e5f
19 измененных файлов с 622 добавлено и 420 удалено
  1. 2 2
      tr-modules-api/tr-module-export-api/src/main/java/cn/tr/module/export/annotation/ExcelPropertySupport.java
  2. 7 7
      tr-modules-api/tr-module-export-api/src/main/java/cn/tr/module/export/annotation/ExcelSelect.java
  3. 22 18
      tr-modules-api/tr-module-export-api/src/main/java/cn/tr/module/export/converter/DictCascadeSelectConverter.java
  4. 108 0
      tr-modules-api/tr-module-export-api/src/main/java/cn/tr/module/export/handler/AbstractCascadeSelectConverter.java
  5. 0 166
      tr-modules-api/tr-module-export-api/src/main/java/cn/tr/module/export/handler/AbstractSelectConverter.java
  6. 17 0
      tr-modules-api/tr-module-export-api/src/main/java/cn/tr/module/export/handler/NoneCascadeSelectConverter.java
  7. 0 32
      tr-modules-api/tr-module-export-api/src/main/java/cn/tr/module/export/handler/NoneSelectConverter.java
  8. 6 0
      tr-modules/tr-module-export/pom.xml
  9. 72 175
      tr-modules/tr-module-export/src/main/java/cn/tr/module/excel/core/handler/write/CustomCellWriteHandler.java
  10. 261 0
      tr-modules/tr-module-export/src/main/java/cn/tr/module/excel/core/handler/write/CustomRowWriteHandler.java
  11. 4 5
      tr-modules/tr-module-export/src/main/java/cn/tr/module/excel/core/service/ExcelService.java
  12. 7 7
      tr-modules/tr-module-export/src/main/java/cn/tr/module/excel/core/utils/ExcelTemplateDescUtil.java
  13. 32 0
      tr-modules/tr-module-export/src/test/java/cn/tr/module/excel/test/CascadeSelectTestDTO.java
  14. 23 0
      tr-modules/tr-module-export/src/test/java/cn/tr/module/excel/test/CascadeTest.java
  15. 53 0
      tr-modules/tr-module-export/src/test/java/cn/tr/module/excel/test/TestConver.java
  16. 2 2
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/log/dto/SysLoginLogExcelDTO.java
  17. 2 2
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/log/dto/SysOperationLogExcelDTO.java
  18. 2 2
      tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/log/service/impl/SysLogServiceImpl.java
  19. 2 2
      tr-test/src/main/java/cn/tr/test/excel/User.java

+ 2 - 2
tr-modules-api/tr-module-export-api/src/main/java/cn/tr/module/export/annotation/ExcelPropertySupport.java

@@ -1,7 +1,7 @@
 package cn.tr.module.export.annotation;
 
 
-import cn.tr.module.export.handler.NoneSelectConverter;
+import cn.tr.module.export.handler.NoneCascadeSelectConverter;
 
 import java.lang.annotation.*;
 
@@ -23,7 +23,7 @@ public @interface ExcelPropertySupport {
     String dictCode() default "";
 
 
-    ExcelSelect select() default @ExcelSelect(converter = NoneSelectConverter.class);
+    ExcelSelect select() default @ExcelSelect(converter = NoneCascadeSelectConverter.class);
     /**
      * 导出模板批注
      */

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

@@ -1,7 +1,7 @@
 package cn.tr.module.export.annotation;
 
-import cn.tr.module.export.handler.AbstractSelectConverter;
-import cn.tr.module.export.handler.NoneSelectConverter;
+import cn.tr.module.export.handler.AbstractCascadeSelectConverter;
+import cn.tr.module.export.handler.NoneCascadeSelectConverter;
 
 import java.lang.annotation.*;
 
@@ -23,16 +23,16 @@ public @interface ExcelSelect {
     /**
      * 集合转换类
      */
-    Class<? extends AbstractSelectConverter<?>> converter() default NoneSelectConverter.class;
+    Class<? extends AbstractCascadeSelectConverter<?>> converter() default NoneCascadeSelectConverter.class;
 
     /**
-     * 级联时的级属性名称
+     * 级联时的级属性名称
      * <pre>
      *     省-> 市-> 区
-     *     区的 linkageParentProperty 为 市
-     *     市的 linkageParentProperty 为 省
+     *     省的 cascade 为 市
+     *     市的 cascade 为 区
      * <pre/>
      */
-    String linkageParentProperty() default "";
+    String cascade() default "";
 
 }

+ 22 - 18
tr-modules-api/tr-module-export-api/src/main/java/cn/tr/module/export/converter/DictSelectConverter.java → tr-modules-api/tr-module-export-api/src/main/java/cn/tr/module/export/converter/DictCascadeSelectConverter.java

@@ -4,14 +4,10 @@ import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.lang.Pair;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.extra.spring.SpringUtil;
-import cn.tr.module.export.handler.AbstractSelectConverter;
+import cn.tr.module.export.handler.AbstractCascadeSelectConverter;
 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;
+import java.util.*;
 
 /**
  * @ClassName : DictComboConverter
@@ -20,36 +16,44 @@ import java.util.stream.Collectors;
  * @Date: 2023年06月01日
  */
 
-public class DictSelectConverter extends AbstractSelectConverter<String> {
+public class DictCascadeSelectConverter extends AbstractCascadeSelectConverter<String> {
     private final SysDictApi dictApi;
 
-    public DictSelectConverter() {
+    public DictCascadeSelectConverter() {
         this.dictApi = SpringUtil.getBean(SysDictApi.class);
     }
 
     @Override
-    public List<Pair<String, String>> doComboList(Collection<String> params) {
+    public Map<String, List<String>> buildCascadeSelectList(Collection<String> params) {
         String dictCode = CollectionUtil.getFirst(params);
-        List<Pair<String, String>> result = dictApi.findAllChildrenDictsByCode(dictCode);
-        return CollectionUtil.isNotEmpty(result)?result:new ArrayList<>();
+        Map<String, List<String>> result = new HashMap<>();
+        List<Pair<String, String>> dicts = dictApi.findAllChildrenDictsByCode(dictCode);
+        if(CollectionUtil.isNotEmpty(dicts)){
+            for (Pair<String, String> pair : dicts) {
+                result.put(pair.getKey(),null);
+            }
+        }
+        return result;
     }
 
     @Override
-    public List<Pair<String, List<String>>> buildLinkageSelectList(Collection<String> params) {
+    public String excelConverterJavaValue(String content, 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())));
+        for (Pair<String, String> pair : result) {
+            if (ObjectUtil.equals(content, pair.getValue())) {
+                return pair.getKey();
+            }
+        }
+        return null;
     }
 
     @Override
-    public String extractUniqueCode(String content, Collection<String> params) {
+    public String javaConverterExcelCellValue(String value, Collection<String> params) {
         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())) {
+            if (ObjectUtil.equals(value, pair.getKey())) {
                 return pair.getValue();
             }
         }

+ 108 - 0
tr-modules-api/tr-module-export-api/src/main/java/cn/tr/module/export/handler/AbstractCascadeSelectConverter.java

@@ -0,0 +1,108 @@
+package cn.tr.module.export.handler;
+
+import cn.hutool.core.convert.Convert;
+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;
+import com.alibaba.excel.metadata.data.WriteCellData;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import java.util.*;
+
+/**
+ * @ClassName : AbstractCascadeSelectConverter
+ * @Description : 级联选择框构建
+ * @Author : LF
+ * @Date: 2023年06月01日
+ */
+
+public abstract class AbstractCascadeSelectConverter<T> implements Converter<T> {
+
+    @Override
+    public Class<T> supportJavaTypeKey() {
+        return doSupportJavaTypeKey();
+    }
+
+    @Override
+    public T convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
+        if(cellData==null||StrUtil.isEmpty(cellData.getStringValue())){
+            return null;
+        }
+        ExcelPropertySupport excelPropertySupport = contentProperty.getField().getAnnotation(ExcelPropertySupport.class);
+        String content = cellData.getStringValue();
+        //校验是否存在
+        T javaValue = excelConverterJavaValue(content,getParams(excelPropertySupport));
+        if(ObjectUtil.isNull(javaValue)){
+            throw new UnsupportedOperationException("所填值{"+content+"}不合法,请重新下载模板导入");
+        }
+        return javaValue;
+    }
+
+    @Override
+    public WriteCellData<?> convertToExcelData(T value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
+        WriteCellData<String> cellData = new WriteCellData<>("");
+        //java 转execl,java中填写的是key值,填写value(key)放入execl中
+        ExcelPropertySupport excelPropertySupport = contentProperty.getField().getAnnotation(ExcelPropertySupport.class);
+        if(excelPropertySupport!=null){
+            cellData.setStringValue(javaConverterExcelCellValue(value,getParams(excelPropertySupport)));
+        }
+        return cellData;
+    }
+
+    private Collection<String> getParams(ExcelPropertySupport propertySupport){
+        if(propertySupport!=null){
+            return Arrays.asList(propertySupport.select().param());
+        }
+        return null;
+    }
+
+    /**
+     * 根据所给的值将java的值转变为execl所展示的值
+     * @param content execl内容
+     * @param params {@link ExcelSelect#param()}
+     * @return
+     */
+    public T excelConverterJavaValue(String content,Collection<String> params){
+        if(doSupportJavaTypeKey().equals(Boolean.class)){
+            if(StrUtil.equals(content,"1")||StrUtil.equals(content,"true")){
+                return Convert.convert(doSupportJavaTypeKey(),Boolean.TRUE);
+            }else {
+                return Convert.convert(doSupportJavaTypeKey(),Boolean.FALSE);
+            }
+        }
+        return Convert.convert(doSupportJavaTypeKey(), content);
+    }
+
+    /**
+     * 构建菜单获取级联列表
+     * @param params 查询列表参数集合
+     * @return List<Pair<String,List<String>>> 级联列表
+     * <pre>
+     * [{"河南省",["郑州市","新乡市","平顶山市"]},
+     * {"河北省",["石家庄市","邯郸市"]}]
+     *
+     * Pair中的value值为当前字段所需要的列表,若不存在上级级联,返回一个Pair即可,Key为''或null
+     * [{"",["是","否"]}]
+     * <pre/>
+     */
+    public Map<String, List<String>>  buildCascadeSelectList(Collection<String> params){return new HashMap<>();};
+
+
+    /**
+     * 根据所给的值将java的值转变为execl所展示的值
+     * @param value  java所给的value值
+     * @param params {@link ExcelSelect#param()}
+     * @return
+     */
+    public String javaConverterExcelCellValue(T value,Collection<String> params){
+        if(ObjectUtil.isNull(value)){
+            return "";
+        }
+        return String.valueOf(value);
+    };
+
+    public abstract Class<T> doSupportJavaTypeKey();
+}

+ 0 - 166
tr-modules-api/tr-module-export-api/src/main/java/cn/tr/module/export/handler/AbstractSelectConverter.java

@@ -1,166 +0,0 @@
-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;
-import com.alibaba.excel.metadata.data.WriteCellData;
-import com.alibaba.excel.metadata.property.ExcelContentProperty;
-import java.util.*;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-/**
- * @ClassName : AbstractComboConverter
- * @Description :
- * @Author : LF
- * @Date: 2023年06月01日
- */
-
-public abstract class AbstractSelectConverter<T> implements Converter<T> {
-    private static final Pattern PATTERN_PARAMS = Pattern.compile("\\((.*?)\\)");
-
-    @Override
-    public Class<T> supportJavaTypeKey() {
-        return doSupportJavaTypeKey();
-    }
-
-    @Override
-    public T convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
-        if(cellData==null||StrUtil.isEmpty(cellData.getStringValue())){
-            return null;
-        }
-        ExcelPropertySupport excelPropertySupport = contentProperty.getField().getAnnotation(ExcelPropertySupport.class);
-        String content = cellData.getStringValue();
-        //校验是否存在
-        T uniqueCode = extractUniqueCode(content,getParams(excelPropertySupport));
-        if(ObjectUtil.isNull(uniqueCode)){
-            throw new UnsupportedOperationException("所填值{"+content+"}不合法,请重新下载模板导入");
-        }
-        return uniqueCode;
-    }
-
-    @Override
-    public WriteCellData<?> convertToExcelData(T value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
-        WriteCellData<String> cellData = new WriteCellData<>("");
-        //java 转execl,java中填写的是key值,填写value(key)放入execl中
-        ExcelPropertySupport excelPropertySupport = contentProperty.getField().getAnnotation(ExcelPropertySupport.class);
-        if(excelPropertySupport!=null){
-            cellData.setStringValue(buildContent(value,getParams(excelPropertySupport)));
-        }
-        return cellData;
-    }
-
-    private Collection<String> getParams(ExcelPropertySupport propertySupport){
-        if(propertySupport!=null){
-            return Arrays.asList(propertySupport.select().param());
-        }
-        return null;
-    }
-    /**
-     * 获取数据集合
-     * @return
-     */
-    public List<String> comboList(Collection<String> params){
-        if(CollectionUtil.isEmpty(params)){
-            return null;
-        }
-        return doComboList(params)
-                .stream()
-                .map(pair->buildContent(pair.getValue(),pair.getKey()))
-                .collect(Collectors.toList());
-    };
-
-    /**
-     * 从cell的内容中提取唯一标识符,格式统一为 描述(唯一标识符)
-     * <pre>
-     *     云智能(1456795123456) 唯一标识符:1456795123456
-     *     软件部(1456795123452) 唯一标识符:1456795123452
-     * </pre>
-     * @param content 内容
-     * @return
-     */
-    public T extractUniqueCode(String content,Collection<String> params){
-//        return (T) CollectionUtil.getFirst(ReUtil.findAllGroup1(PATTERN_PARAMS,content));
-        return (T) content;
-    }
-
-    /**
-     * 校验唯一标识符是否存在
-     * @param uniqueCode
-     * @return
-     */
-    public boolean existUniqueCode(T uniqueCode,Collection<String> params){
-        List<Pair<T, String>> pairs = doComboList(params);
-        for (Pair<T, String> pair : pairs) {
-            if (ObjectUtil.equals(uniqueCode, pair.getKey())) {
-                return true;
-            }
-        }
-        return false;
-    }
-    /**
-     * 构建cell的内容,格式统一为 描述(唯一标识符)
-     * @param javaValue java值
-     * @return cell的内容
-     */
-    public String buildContent(T javaValue,Collection<String> params){
-        List<Pair<T, String>> pairs = doComboList(params);
-        for (Pair<T, String> pair : pairs) {
-            if (ObjectUtil.equals(javaValue, pair.getValue())) {
-                return buildContent(pair.getValue(),pair.getKey());
-            }
-        }
-        return null;
-    }
-
-    /**
-     * 构建cell的内容,格式统一为 描述(唯一标识符)
-     * <pre>
-     *     云智能(1456795123456)
-     *     软件部(1456795123452)
-     * </pre>
-     * @param desc 描述
-     * @param uniqueCode 唯一标识符
-     * @return
-     */
-    private String buildContent(String desc,T uniqueCode){
-//        return desc+"("+uniqueCode+")";
-        return String.valueOf(uniqueCode);
-    }
-
-    /**
-     * 获取数据集合的真正实现方法
-     * @param params
-     * @return
-     * 当value为null或"" 且 @link ExcelSelect#linkageParentProperty()} 存在,即存在级联但父级级联为空,此时 父子二级级联下拉菜单都为空
-     * 当value不为空 且 @link ExcelSelect#linkageParentProperty()} 存在,即存在级联但父级级联为空,父级菜单为value值,子级菜单为key值
-     * 当value不为空 且 @link ExcelSelect#linkageParentProperty()} 不存在,即不存在父级菜单,仅对子级菜单进行下拉选项
-     * <pre>
-     *     {"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 List<Pair<String,List<String>>>  buildLinkageSelectList(Collection<String> params){return new ArrayList<>();};
-
-    public abstract Class<T> doSupportJavaTypeKey();
-}

+ 17 - 0
tr-modules-api/tr-module-export-api/src/main/java/cn/tr/module/export/handler/NoneCascadeSelectConverter.java

@@ -0,0 +1,17 @@
+package cn.tr.module.export.handler;
+
+
+/**
+ * @ClassName : NoneComboConverter
+ * @Description :
+ * @Author : LF
+ * @Date: 2023年06月01日
+ */
+
+public class NoneCascadeSelectConverter extends AbstractCascadeSelectConverter<Void> {
+
+    @Override
+    public Class<Void> doSupportJavaTypeKey() {
+        return Void.class;
+    }
+}

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

@@ -1,32 +0,0 @@
-package cn.tr.module.export.handler;
-
-import cn.hutool.core.lang.Pair;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * @ClassName : NoneComboConverter
- * @Description :
- * @Author : LF
- * @Date: 2023年06月01日
- */
-
-public class NoneSelectConverter extends AbstractSelectConverter<Void> {
-
-    @Override
-    public List<Pair<Void, String>> doComboList(Collection<String> params) {
-        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;
-    }
-}

+ 6 - 0
tr-modules/tr-module-export/pom.xml

@@ -65,5 +65,11 @@
             <groupId>cn.tr</groupId>
             <artifactId>tr-spring-boot-starter-plugin-lock</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>cn.tr</groupId>
+            <artifactId>tr-spring-boot-starter-plugin-test</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>

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

@@ -1,20 +1,18 @@
 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.*;
-import cn.tr.core.tree.TreeNode;
-import cn.tr.core.utils.TreeUtil;
 import cn.tr.module.export.annotation.ExcelSelect;
 import cn.tr.module.export.annotation.ExcelPropertySupport;
-import cn.tr.module.export.handler.AbstractSelectConverter;
-import cn.tr.module.export.handler.NoneSelectConverter;
+import cn.tr.module.export.handler.AbstractCascadeSelectConverter;
+import cn.tr.module.export.handler.NoneCascadeSelectConverter;
 import cn.tr.module.constant.ExcelConstant;
 import com.alibaba.excel.annotation.ExcelProperty;
 import com.alibaba.excel.converters.Converter;
 import com.alibaba.excel.write.handler.CellWriteHandler;
 import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
 import lombok.Data;
+import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.ss.util.CellRangeAddressList;
@@ -33,15 +31,16 @@ import java.util.stream.Collectors;
  * @Date: 2023年05月19日
  */
 @Slf4j
+@Getter
 public class CustomCellWriteHandler implements CellWriteHandler {
     //excel中的属性列的位置
-    private final Map<String, Integer> excelColumnProperty = new HashMap<>();
-    //级联属性映射集合   map("下级属性","上级属性")
-    private final Map<String, String> linkagePropertyMap = new HashMap<>();
-    //属性所对应的级联集合
-    private final Map<String, List<Pair<String, List<String>>>> selectPropertyMap = new HashMap<>();
+    private final Map<String, Integer> columnIndexMap= new HashMap<>();
 
-    boolean  setSelectColumn=false;
+    //级联属性映射集合   map("上级属性","下级属性")
+    private final List<CascadeSelectNode> cascadeSelectNodeList = new ArrayList<>();
+
+    //已经处理过的子级级联属性
+    private Set<String> doneChildCascadeProperty=new HashSet<>();
 
     @Override
     public void afterCellDispose(CellWriteHandlerContext context) {
@@ -54,14 +53,10 @@ public class CustomCellWriteHandler implements CellWriteHandler {
             //进行数字校验
             createNumColumn(field,cell);
             //进行字典校验
-            createComboColumn(field,cell);
+            createComboColumn(field);
             //进行日期格式校验
             createDateColumn(field,cell);
-        }else {
-            if(!setSelectColumn){
-                createLinkageSelectColumn(cell.getSheet());
-                setSelectColumn=true;
-            }
+            columnIndexMap.put(field.getName(),cell.getColumnIndex());
         }
     }
 
@@ -72,7 +67,6 @@ public class CustomCellWriteHandler implements CellWriteHandler {
         ){
             NotNull notNull = field.getAnnotation(NotNull.class);
             DataValidationHelper helper = cell.getSheet().getDataValidationHelper();
-            //DVConstraint constrain1 = DVConstraint.CreateDateConstraint(条件,"最小时间","最大时间","时间格式"); //这是检查时间的方法
             addValidateData(cell.getSheet(),
                     cell,
                     helper.createDateConstraint(DataValidationConstraint.OperatorType.BETWEEN,"Date(1900, 1, 1)","Date(2099, 12, 31)","yyyy-MM-dd"),
@@ -85,32 +79,33 @@ public class CustomCellWriteHandler implements CellWriteHandler {
     /**
      * 集合列校验
      * @param field
-     * @param cell
      */
-    private void createComboColumn(Field field,Cell cell){
+    private void createComboColumn(Field field){
         ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
         ExcelPropertySupport excelPropertySupport = field.getAnnotation(ExcelPropertySupport.class);
         if(excelPropertySupport==null){
             return;
         }
         ExcelSelect select = excelPropertySupport.select();
-        Class<? extends AbstractSelectConverter<?>> converterClass = select.converter();
-        if(converterClass== NoneSelectConverter.class){
+        Class<? extends AbstractCascadeSelectConverter<?>> converterClass = select.converter();
+        if(converterClass== NoneCascadeSelectConverter.class){
             Class<? extends Converter<?>> excelPropertyConverterClass = excelProperty.converter();
-            if(ClassUtil.isAssignable(AbstractSelectConverter.class,excelPropertyConverterClass)&&excelPropertyConverterClass!= NoneSelectConverter.class){
-                converterClass= (Class<? extends AbstractSelectConverter<?>>) excelPropertyConverterClass;
+            if(ClassUtil.isAssignable(AbstractCascadeSelectConverter.class,excelPropertyConverterClass)&&excelPropertyConverterClass!= NoneCascadeSelectConverter.class){
+                converterClass= (Class<? extends AbstractCascadeSelectConverter<?>>) excelPropertyConverterClass;
             }else {
                 return;
             }
         }
 
-        AbstractSelectConverter<?> converter = ReflectUtil.newInstance(converterClass);
-        List<Pair<String, List<String>>> linkageSelectList = converter.buildLinkageSelectList(Arrays.asList(select.param()));
-
+        AbstractCascadeSelectConverter<?> converter = ReflectUtil.newInstance(converterClass);
+        Map<String, List<String>> cascadeSelectList = converter.buildCascadeSelectList(Arrays.asList(select.param()));
 
-        excelColumnProperty.put(field.getName(),cell.getColumnIndex());
-        linkagePropertyMap.put(field.getName(),select.linkageParentProperty());
-        selectPropertyMap.put(field.getName(),linkageSelectList);
+        if(doneChildCascadeProperty.contains(select.cascade())){
+            throw new IllegalArgumentException("所导出类的级联值-{"+field.getName()+"}不可出现多个上级联结,");
+        }else {
+            doneChildCascadeProperty.add(select.cascade());
+        }
+        cascadeSelectNodeList.add(new CascadeSelectNode(field.getName(),select.cascade(),cascadeSelectList));
     }
 
     /**
@@ -146,8 +141,8 @@ public class CustomCellWriteHandler implements CellWriteHandler {
                             DataValidationConstraint.ValidationType.INTEGER,
                             DataValidationConstraint.OperatorType.BETWEEN, String.valueOf(Integer.MIN_VALUE),  String.valueOf(Integer.MAX_VALUE)),
                     cell.getColumnIndex(),
-                    "请输入"+Integer.MIN_VALUE+"-"+Integer.MAX_VALUE+"之间的数字",
-                    "请输入"+Integer.MIN_VALUE+"-"+Integer.MAX_VALUE+"之间的数字",
+                    "请输入"+0+"-"+1000000+"之间的数字",
+                    "请输入"+0+"-"+1000000+"之间的数字",
                     notNull==null);
         }
     }
@@ -188,162 +183,64 @@ public class CustomCellWriteHandler implements CellWriteHandler {
         cell.setCellComment(comment);
     }
 
-
-    /**
-     * 创建级联搜索列
-     * @param sheet
-     */
-    private void createLinkageSelectColumn(Sheet sheet){
-        //没有级联
-        if(CollectionUtil.isEmpty(linkagePropertyMap)){
-            return;
-        }
-        List<LinkageSelectNode> selectNodes = new ArrayList<>();
-        //已经处理过的字段
-        linkagePropertyMap.forEach((currentProperty,parentProperty)->{
-            selectNodes.add(new LinkageSelectNode(currentProperty,parentProperty,selectPropertyMap.get(currentProperty)));
-        });
-        List<LinkageSelectNode>  treeNode=TreeUtil.buildTree(selectNodes);
-        List<String> doneProperty = new ArrayList<>();
-        linkagePropertyMap.forEach((currentProperty,parentProperty)->{
-            if(StrUtil.isBlank(parentProperty)){
-                //非级联
-                noneLinkageSelectColumn(sheet,currentProperty);
-            }else {
-                //级联,针对本级
-
-            }
-        });
+    private CellRangeAddressList createColumnCellRange(int column){
+        return new CellRangeAddressList(1, ExcelConstant.MAX_ROW,column,column);
     }
 
-    /**
-     * 级联下拉选择框
-     * @param sheet         当前工作簿
-     * @param parentProperty 父级级联参数
-     * @param property      当前需要处理的属性名称
-     * @param doneProperty  已经设置过下拉选择框的属性名称
-     */
-    private void linkageSelectColumn(Sheet sheet,String parentProperty,String property,Set<String> doneProperty ){
-        Integer columnIndex = excelColumnProperty.get(property);
-        if(ObjectUtil.isNull(columnIndex)){
-            log.warn("[execl文件导出],property:{}显示为级联属性,找不到对应的列数",property);
-            return;
-        }
-        //级联时构造一颗级联树
-
-        if(doneProperty.contains(property)){
-            //该属性已经处理过
-
-        }else {
-            //该属性尚未处理
-            List<Pair<String, List<String>>> optionalSelectValues = selectPropertyMap.get(property);
-            //创建工作簿
-            Workbook workbook = sheet.getWorkbook();
-            Sheet selectPropertySheet =workbook.createSheet(property);
-            Sheet parentSelectPropertySheet = workbook.getSheet(parentProperty);
-            if(ObjectUtil.isNotNull(parentSelectPropertySheet)){
-                //若父级工作簿已存在,重置,级联时两字段只能一一对应
-
-            }
-
-
-            workbook.setSheetHidden(workbook.getSheetIndex(selectPropertySheet),true);
+    @Data
+    public static class CascadeSelectNode{
 
-        }
+        /**
+         * 当前属性名称
+         */
+        private String fieldName;
 
-    }
+        /**
+         * 级联下级属性名称
+         */
+        private String subordinateFieldName;
 
+        /**
+         * 下级属性
+         */
+        private CascadeSelectNode subordinateNode;
 
-    /**
-     * 非级联下拉选择框
-     * @param sheet
-     * @param property
-     */
-    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 Map<String,List<String>> cascadeSelect;
 
-    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));
+        public CascadeSelectNode(String fieldName, String subordinateFieldName, Map<String,List<String>> cascadeSelect) {
+            this.fieldName=fieldName;
+            this.subordinateFieldName = subordinateFieldName;
+            this.cascadeSelect=cascadeSelect;
         }
-        //获取新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 createHeadCellRange(int column){
-        return new CellRangeAddressList(0, 0,column,column);
-    }
-
-    private CellRangeAddressList createColumnCellRange(int column){
-        return new CellRangeAddressList(1, ExcelConstant.MAX_ROW,column,column);
-    }
-
-    @Data
-    static class  LinkageSelectNode extends TreeNode<String> {
-
-        private String name;
 
         /**
-         * 当前级联节点所拥有的选择项
+         * 构建树
+         * @return
          */
-        private List<Pair<String, List<String>>> selectValues;
-
-        public LinkageSelectNode(String name, String parentId,List<Pair<String, List<String>>> selectValues) {
-            this.setParentId(parentId);
-            this.name = name;
-            this.selectValues = selectValues;
-        }
+        public static List<CascadeSelectNode> buildTree(List<CascadeSelectNode> source){
+            List<CascadeSelectNode> result = new ArrayList<>();
+            Map<String, CascadeSelectNode> cascadeNodeMap
+                    = source
+                    .stream()
+                    .collect(Collectors.groupingBy(CascadeSelectNode::getFieldName, Collectors.collectingAndThen(Collectors.toList(), CollectionUtil::getFirst)));
+            Set<String> childFieldNameSet = new HashSet<>();
+            for (CascadeSelectNode cascadeSelectNode : source) {
+                String subordinateFieldName = cascadeSelectNode.getSubordinateFieldName();
+                childFieldNameSet.add(subordinateFieldName);
+                CascadeSelectNode subordinateNode = cascadeNodeMap.get(subordinateFieldName);
+                if(subordinateNode==null&&StrUtil.isNotBlank(subordinateFieldName)){
+                    subordinateNode=new CascadeSelectNode(subordinateFieldName,null,null);
+                }
+                cascadeSelectNode.setSubordinateNode(subordinateNode);
+            }
 
-        @Override
-        public String getId() {
-            return name;
+            for (CascadeSelectNode cascadeSelectNode : source) {
+                if (!childFieldNameSet.contains(cascadeSelectNode.getFieldName())) {
+                    result.add(cascadeSelectNode);
+                }
+            }
+            return result;
         }
     }
 }

+ 261 - 0
tr-modules/tr-module-export/src/main/java/cn/tr/module/excel/core/handler/write/CustomRowWriteHandler.java

@@ -0,0 +1,261 @@
+package cn.tr.module.excel.core.handler.write;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.tr.module.constant.ExcelConstant;
+import com.alibaba.excel.write.handler.RowWriteHandler;
+import com.alibaba.excel.write.handler.context.RowWriteHandlerContext;
+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.*;
+
+/**
+ * @ClassName : CustomRowWriteHandler
+ * @Description :
+ * @Author : LF
+ * @Date: 2023年09月23日
+ */
+@Slf4j
+public class CustomRowWriteHandler implements RowWriteHandler {
+    private CustomCellWriteHandler cellWriteHandler;
+
+    public CustomRowWriteHandler(CustomCellWriteHandler cellWriteHandler) {
+        this.cellWriteHandler = cellWriteHandler;
+    }
+
+    @Override
+    public void afterRowDispose(RowWriteHandlerContext context) {
+        context.getWriteSheetHolder().setLastRowIndex(context.getRowIndex());
+        if(Boolean.TRUE.equals(context.getHead())){
+            initCascadeSelectColumn(context.getWriteSheetHolder().getSheet());
+        }
+    }
+
+    /**
+     * 初始化级联列
+     * @param sheet
+     */
+    private void initCascadeSelectColumn(Sheet sheet){
+        List<CustomCellWriteHandler.CascadeSelectNode> cascadeSelectNodeList = cellWriteHandler.getCascadeSelectNodeList();
+        //没有级联
+        if(CollectionUtil.isEmpty(cascadeSelectNodeList)){
+            return;
+        }
+        //构建级联树
+        List<CustomCellWriteHandler.CascadeSelectNode> cascadeTree= CustomCellWriteHandler.CascadeSelectNode.buildTree(cascadeSelectNodeList);
+
+        doCascadeSelectColumn(sheet,cascadeTree);
+    }
+
+    /**
+     * 开始设置级联选择框
+     * @param tree
+     */
+    private void doCascadeSelectColumn(Sheet sheet,List<CustomCellWriteHandler.CascadeSelectNode>  tree){
+        for (CustomCellWriteHandler.CascadeSelectNode cascadeSelectNode : tree) {
+            int depth=calculateCascadeDepth(cascadeSelectNode);
+            CustomCellWriteHandler.CascadeSelectNode tempNode=cascadeSelectNode;
+            while (tempNode!=null){
+                doCascade(sheet,tempNode);
+                tempNode=tempNode.getSubordinateNode();
+            }
+        }
+    }
+
+
+    /**
+     * 计算级联菜单的深度
+     * @param node
+     * @return
+     */
+    private int calculateCascadeDepth(CustomCellWriteHandler.CascadeSelectNode node){
+        int result=0;
+        Set<String> fieldNameSet = new HashSet<>();
+        while (node!=null){
+            if (fieldNameSet.contains(node.getFieldName())) {
+                throw new IllegalArgumentException("级联菜单不可出现递归,递归级联属性{"+node.getFieldName()+"}");
+            }
+            fieldNameSet.add(node.getFieldName());
+            node=node.getSubordinateNode();
+            ++result;
+        }
+        return result;
+    }
+
+    /**
+     *  构造名称管理器和数据验证及公式
+     *
+     * @param sheet 目标工作簿
+     * @param node 级联节点
+     */
+    private void doCascade(Sheet sheet,CustomCellWriteHandler.CascadeSelectNode node)  {
+        Workbook workbook = sheet.getWorkbook();
+        Integer cascadeParentIndex = cellWriteHandler.getColumnIndexMap().get(node.getFieldName());
+        Sheet hiddenSheet = workbook.getSheet(node.getFieldName());
+        if(hiddenSheet==null){
+            hiddenSheet = workbook.createSheet(node.getFieldName());
+        }
+        int hiddenSheetIndex = workbook.getSheetIndex(hiddenSheet);
+//        workbook.setSheetHidden(hiddenSheetIndex,true);
+
+        Map<String, List<String>> cascadeSelectSource = node.getCascadeSelect();
+        if(CollectionUtil.isEmpty(cascadeSelectSource)){
+            return;
+        }
+        Row headerRow = hiddenSheet.createRow(0);
+        String[] firstValidationArray = null;
+        boolean firstTime = true;
+        int columnIndex = 0;
+        // 构造名称管理器数据源
+        for (String key : cascadeSelectSource.keySet()) {
+            Cell cell = headerRow.createCell(columnIndex);
+            cell.setCellValue(key);
+            if (CollectionUtil.isEmpty(cascadeSelectSource.get(key))) {
+                continue;
+            }
+            List<String> values =  cascadeSelectSource.get(key);
+            if (firstTime) {
+                firstValidationArray = values.toArray(new String[CollectionUtil.size(values)]);
+            }
+            int dataRowIndex = 1;
+            for (String value : values) {
+                Row row = firstTime ? hiddenSheet.createRow(dataRowIndex) : hiddenSheet.getRow(dataRowIndex);
+                if (row == null) {
+                    row = hiddenSheet.createRow(dataRowIndex);
+                }
+                row.createCell(columnIndex).setCellValue(value);
+                dataRowIndex++;
+            }
+            // 构造名称管理器
+            String range = buildRange(columnIndex, 2, values.size());
+            Name name = workbook.createName();
+            name.setNameName(key);
+            String formula = node.getFieldName() + "!" + range;
+            name.setRefersToFormula(formula);
+            columnIndex++;
+            firstTime = false;
+        }
+
+
+
+        // 第一级设置DataValidation
+        DataValidationHelper dvHelper = sheet.getDataValidationHelper();
+        DataValidationConstraint firstConstraint = dvHelper.createExplicitListConstraint(new ArrayList<String>(cascadeSelectSource.keySet()).toArray(new String[CollectionUtil.size(cascadeSelectSource)]));
+        CellRangeAddressList firstRangeAddressList = new CellRangeAddressList(1, ExcelConstant.MAX_ROW, cascadeParentIndex , cascadeParentIndex );
+        DataValidation firstDataValidation = dvHelper.createValidation(firstConstraint, firstRangeAddressList);
+        firstDataValidation.setSuppressDropDownArrow(true);
+        sheet.addValidationData(firstDataValidation);
+
+        if(StrUtil.isBlank(node.getSubordinateFieldName())){
+            return;
+        }
+        Integer cascadeChildIndex = cellWriteHandler.getColumnIndexMap().get(node.getSubordinateFieldName());
+        if(cascadeChildIndex==null){
+            return;
+        }
+        // 剩下的层级设置DataValidation
+        char[] offset = new char[1];
+        offset[0] = (char) ('A'  + cascadeChildIndex - 1);
+        String formulaString = buildFormulaString(new String(offset), 2);
+        XSSFDataValidationConstraint dvConstraint = (XSSFDataValidationConstraint) dvHelper.createFormulaListConstraint(formulaString);
+        CellRangeAddressList regions = new CellRangeAddressList(1, ExcelConstant.MAX_ROW,cascadeChildIndex,  cascadeChildIndex);
+        XSSFDataValidation dataValidationList = (XSSFDataValidation) dvHelper.createValidation(dvConstraint, regions);
+        dataValidationList.setSuppressDropDownArrow(true);
+        sheet.addValidationData(dataValidationList);
+    }
+
+    private String buildRange(int offset, int startRow, int rowCount) {
+        char start = (char) ('A' + offset);
+        return "$" + start + "$" + startRow + ":$" + start + "$" + (startRow + rowCount - 1);
+    }
+
+    private String buildFormulaString(String offset, int rowNum) {
+        return "INDIRECT($" + offset + (rowNum) + ")";
+    }
+
+
+    /**
+     * 创建名字管理器
+     * @param workbook
+     * @param name    名字管理器名称
+     * @param order   开始执行索引
+     * @param size    区域大小
+     * @param cascadeFlag  是否为级联子菜单
+     */
+    private void creatExcelNameList(Workbook workbook,String hiddenSheetName, String name,
+                                    int order, int size, boolean cascadeFlag) {
+        Name excelName=workbook.createName();
+        excelName.setNameName(name);
+        excelName.setRefersToFormula(hiddenSheetName + "!"
+                + creatExcelNameList(order, size, cascadeFlag));
+    }
+
+    /**
+     * 名称数据行列表达式
+     * @param order
+     * @param size
+     * @param cascadeFlag
+     * @return
+     */
+    private static String creatExcelNameList(int order, int size,
+                                             boolean cascadeFlag) {
+        char start = 'A';
+        if (cascadeFlag) {
+            start = 'B';
+            if (size <= 25) {
+                char end = (char) (start + size - 1);
+                return "$" + start + "$" + order + ":$" + end + "$" + order;
+            } else {
+                char endPrefix = 'A';
+                char endSuffix = 'A';
+                if ((size - 25) / 26 == 0 || size == 51) {// 26-51之间,包括边界(仅两次字母表计算)
+                    if ((size - 25) % 26 == 0) {// 边界值
+                        endSuffix = (char) ('A' + 25);
+                    } else {
+                        endSuffix = (char) ('A' + (size - 25) % 26 - 1);
+                    }
+                } else {// 51以上
+                    if ((size - 25) % 26 == 0) {
+                        endSuffix = (char) ('A' + 25);
+                        endPrefix = (char) (endPrefix + (size - 25) / 26 - 1);
+                    } else {
+                        endSuffix = (char) ('A' + (size - 25) % 26 - 1);
+                        endPrefix = (char) (endPrefix + (size - 25) / 26);
+                    }
+                }
+                return "$" + start + "$" + order + ":$" + endPrefix + endSuffix
+                        + "$" + order;
+            }
+        } else {
+            if (size <= 26) {
+                char end = (char) (start + size - 1);
+                return "$" + start + "$" + order + ":$" + end + "$" + order;
+            } else {
+                char endPrefix = 'A';
+                char endSuffix = 'A';
+                if (size % 26 == 0) {
+                    endSuffix = (char) ('A' + 25);
+                    if (size > 52 && size / 26 > 0) {
+                        endPrefix = (char) (endPrefix + size / 26 - 2);
+                    }
+                } else {
+                    endSuffix = (char) ('A' + size % 26 - 1);
+                    if (size > 52 && size / 26 > 0) {
+                        endPrefix = (char) (endPrefix + size / 26 - 1);
+                    }
+                }
+                return "$" + start + "$" + order + ":$" + endPrefix + endSuffix
+                        + "$" + order;
+            }
+        }
+    }
+
+
+    private CellRangeAddressList createColumnCellRange(int column){
+        return new CellRangeAddressList(1, ExcelConstant.MAX_ROW,column,column);
+    }
+}

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

@@ -11,6 +11,7 @@ import cn.tr.module.excel.core.dto.ExcelTemplateDescDTO;
 import cn.tr.module.excel.core.handler.read.CustomerReadListener;
 import cn.tr.module.excel.core.handler.write.CustomCellWriteHandler;
 import cn.tr.module.excel.core.handler.write.CustomHorizontalCellStyleStrategy;
+import cn.tr.module.excel.core.handler.write.CustomRowWriteHandler;
 import cn.tr.module.excel.core.handler.write.CustomSheetWriteHandler;
 import cn.tr.module.register.ExportSampleRegister;
 import cn.tr.module.excel.core.utils.ExcelTemplateDescUtil;
@@ -133,17 +134,15 @@ 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<>();
         //属性所对应的级联集合
+        CustomCellWriteHandler customCellWriteHandler = new CustomCellWriteHandler();
         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 CustomRowWriteHandler(customCellWriteHandler))
+                .registerWriteHandler(customCellWriteHandler)
                 .registerWriteHandler(new CustomSheetWriteHandler())
                 .head(aClass).build();
         excelWriter.write(data,writeSheet);

+ 7 - 7
tr-modules/tr-module-export/src/main/java/cn/tr/module/excel/core/utils/ExcelTemplateDescUtil.java

@@ -5,8 +5,8 @@ import cn.hutool.core.util.ClassUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.tr.module.export.annotation.ExcelSelect;
 import cn.tr.module.export.annotation.ExcelPropertySupport;
-import cn.tr.module.export.handler.AbstractSelectConverter;
-import cn.tr.module.export.handler.NoneSelectConverter;
+import cn.tr.module.export.handler.AbstractCascadeSelectConverter;
+import cn.tr.module.export.handler.NoneCascadeSelectConverter;
 import cn.tr.module.excel.core.dto.ExcelTemplateDescDTO;
 import com.alibaba.excel.annotation.ExcelProperty;
 import com.alibaba.excel.converters.Converter;
@@ -38,18 +38,18 @@ public class ExcelTemplateDescUtil {
             desc.setFieldName(field.getName());
             if(excelPropertySupport!=null){
                 ExcelSelect select = excelPropertySupport.select();
-                Class<? extends AbstractSelectConverter<?>> converterClass = select.converter();
-                if(converterClass== NoneSelectConverter.class){
+                Class<? extends AbstractCascadeSelectConverter<?>> converterClass = select.converter();
+                if(converterClass== NoneCascadeSelectConverter.class){
                     Class<? extends Converter<?>> excelPropertyConverterClass = excelProperty.converter();
-                    if(ClassUtil.isAssignable(AbstractSelectConverter.class,excelPropertyConverterClass)&&excelPropertyConverterClass!= NoneSelectConverter.class){
-                        converterClass= (Class<? extends AbstractSelectConverter<?>>) excelPropertyConverterClass;
+                    if(ClassUtil.isAssignable(AbstractCascadeSelectConverter.class,excelPropertyConverterClass)&&excelPropertyConverterClass!= NoneCascadeSelectConverter.class){
+                        converterClass= (Class<? extends AbstractCascadeSelectConverter<?>>) excelPropertyConverterClass;
                     }
                 }
                 String comment = excelPropertySupport.comment();
                 if(StrUtil.isNotBlank(comment)){
                     desc.setRemark(comment);
                 }
-                desc.setSelect(converterClass !=NoneSelectConverter.class);
+                desc.setSelect(converterClass != NoneCascadeSelectConverter.class);
             }else {
                 desc.setSelect(false);
             }

+ 32 - 0
tr-modules/tr-module-export/src/test/java/cn/tr/module/excel/test/CascadeSelectTestDTO.java

@@ -0,0 +1,32 @@
+package cn.tr.module.excel.test;
+
+import cn.tr.module.export.annotation.ExcelPropertySupport;
+import cn.tr.module.export.annotation.ExcelSelect;
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+/**
+ * @ClassName : CascadeSelectTestDTO
+ * @Description :
+ * @Author : LF
+ * @Date: 2023年09月23日
+ */
+@Data
+public class CascadeSelectTestDTO {
+
+
+    @ExcelProperty(value = "省",converter =TestConver.class )
+    @ExcelPropertySupport(select = @ExcelSelect(param = "province",cascade = "city"))
+    private String province;
+
+    @ExcelProperty(value = "市",converter =TestConver.class )
+    @ExcelPropertySupport(select = @ExcelSelect(param = "city",cascade = "xian"))
+    private String city;
+
+    @ExcelProperty(value = "县",converter =TestConver.class)
+    @ExcelPropertySupport(select = @ExcelSelect(param = "xian",cascade = "jiedao"))
+    private String xian;
+
+    @ExcelProperty(value = "街道" )
+    private String jiedao;
+}

+ 23 - 0
tr-modules/tr-module-export/src/test/java/cn/tr/module/excel/test/CascadeTest.java

@@ -0,0 +1,23 @@
+package cn.tr.module.excel.test;
+
+
+import cn.tr.module.excel.core.service.ExcelService;
+import cn.tr.plugin.test.ut.BaseMockitoUnitTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.util.Base64Utils;
+
+/**
+ * @ClassName : CascadeTest
+ * @Description :
+ * @Author : LF
+ * @Date: 2023年09月23日
+ */
+
+public class CascadeTest extends BaseMockitoUnitTest {
+    private ExcelService excelService=new ExcelService();
+
+    @Test
+    public void export(){
+        System.out.println("data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64," + Base64Utils.encodeToString(excelService.exportExcel(CascadeSelectTestDTO.class, null)));
+    }
+}

+ 53 - 0
tr-modules/tr-module-export/src/test/java/cn/tr/module/excel/test/TestConver.java

@@ -0,0 +1,53 @@
+package cn.tr.module.excel.test;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.tr.module.export.handler.AbstractCascadeSelectConverter;
+
+import java.util.*;
+
+/**
+ * @ClassName : TestConver
+ * @Description :
+ * @Author : LF
+ * @Date: 2023年09月23日
+ */
+
+public class TestConver extends AbstractCascadeSelectConverter<String> {
+
+
+    @Override
+    public Map<String, List<String>> buildCascadeSelectList(Collection<String> params) {
+        HashMap<String, List<String>> result = new HashMap<>();
+        String first = CollectionUtil.getFirst(params);
+        if ("province".equals(first)) {
+            result.put("四川省",Arrays.asList("成都","攀枝花","理塘"));
+            result.put("河南省",Arrays.asList("郑州","新乡"));
+            result.put("河北省",Arrays.asList("石家庄"));
+        }
+        if("city".equals(first)){
+            result.put("成都",Arrays.asList("成都1","成都2"));
+            result.put("攀枝花",Arrays.asList("攀枝花1","攀枝花2"));
+            result.put("理塘",Arrays.asList("理塘1","理塘2"));
+            result.put("郑州",Arrays.asList("郑州1","郑州2"));
+            result.put("新乡",Arrays.asList("新乡1","新乡2"));
+            result.put("石家庄",Arrays.asList("石家庄1","石家庄2"));
+        }
+        if("xian".equals(first)){
+            result.put("成都1",Arrays.asList("成都11","成都12"));
+            result.put("成都2",Arrays.asList("成都22","成都23"));
+            result.put("攀枝花1",Arrays.asList("攀枝花11","攀枝花12"));
+            result.put("攀枝花2",Arrays.asList("攀枝花21","攀枝花22"));
+            result.put("理塘1",Arrays.asList("理塘11","理塘12"));
+            result.put("郑州1",Arrays.asList("郑州11","郑州12"));
+            result.put("新乡1",Arrays.asList("新乡11","新乡12"));
+            result.put("石家庄1",Arrays.asList("石家庄11","石家庄12"));
+        }
+
+        return result;
+    }
+
+    @Override
+    public Class<String> doSupportJavaTypeKey() {
+        return String.class;
+    }
+}

+ 2 - 2
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/log/dto/SysLoginLogExcelDTO.java

@@ -2,7 +2,7 @@ package cn.tr.module.sys.log.dto;
 
 import cn.tr.module.export.annotation.ExcelPropertySupport;
 import cn.tr.module.export.annotation.ExcelSelect;
-import cn.tr.module.export.converter.DictSelectConverter;
+import cn.tr.module.export.converter.DictCascadeSelectConverter;
 import com.alibaba.excel.annotation.ExcelProperty;
 import lombok.Data;
 
@@ -33,7 +33,7 @@ public class SysLoginLogExcelDTO {
     @ExcelProperty(value = "方法名称")
     private String javaMethod;
 
-    @ExcelProperty(value = "操作日志类型",converter = DictSelectConverter.class)
+    @ExcelProperty(value = "操作日志类型",converter = DictCascadeSelectConverter.class)
     @ExcelPropertySupport(select = @ExcelSelect(param = "sys_login_log_type"))
     private String type;
 

+ 2 - 2
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/log/dto/SysOperationLogExcelDTO.java

@@ -2,7 +2,7 @@ package cn.tr.module.sys.log.dto;
 
 import cn.tr.module.export.annotation.ExcelPropertySupport;
 import cn.tr.module.export.annotation.ExcelSelect;
-import cn.tr.module.export.converter.DictSelectConverter;
+import cn.tr.module.export.converter.DictCascadeSelectConverter;
 import com.alibaba.excel.annotation.ExcelProperty;
 import lombok.Data;
 
@@ -33,7 +33,7 @@ public class SysOperationLogExcelDTO {
     @ExcelProperty(value = "方法名称")
     private String javaMethod;
 
-    @ExcelProperty(value = "操作日志类型",converter = DictSelectConverter.class)
+    @ExcelProperty(value = "操作日志类型",converter = DictCascadeSelectConverter.class)
     @ExcelPropertySupport(select = @ExcelSelect(param = "sys_log_type"))
     private String type;
 

+ 2 - 2
tr-modules/tr-module-system/src/main/java/cn/tr/module/sys/log/service/impl/SysLogServiceImpl.java

@@ -99,13 +99,13 @@ public class SysLogServiceImpl implements ISysLogService {
 
     @Override
     public String exportLoginByIds(Collection<String> ids) {
-        List<SysLogPO> logs = baseRepository.selectBatchIds(ids);
+        List<SysLogPO> logs =  CollectionUtil.isEmpty(ids)?null:baseRepository.selectBatchIds(ids);
         return  excelApi.exportExcel(SysLoginLogExcelDTO.class,SysLogMapper.INSTANCE.convertLoginExcelDtoList(logs));
     }
 
     @Override
     public String exportOperationByIds(Collection<String> ids) {
-        List<SysLogPO> logs = baseRepository.selectBatchIds(ids);
+        List<SysLogPO> logs = CollectionUtil.isEmpty(ids)?null:baseRepository.selectBatchIds(ids);
         return  excelApi.exportExcel(SysOperationLogExcelDTO.class,SysLogMapper.INSTANCE.convertOperationExcelDtoList(logs));
     }
 }

+ 2 - 2
tr-test/src/main/java/cn/tr/test/excel/User.java

@@ -3,7 +3,7 @@ package cn.tr.test.excel;
 import cn.hutool.core.date.DatePattern;
 import cn.tr.module.export.annotation.ExcelPropertySupport;
 import cn.tr.module.export.annotation.ExcelSelect;
-import cn.tr.module.export.converter.DictSelectConverter;
+import cn.tr.module.export.converter.DictCascadeSelectConverter;
 import com.alibaba.excel.annotation.ExcelProperty;
 import com.alibaba.excel.annotation.format.DateTimeFormat;
 import lombok.AllArgsConstructor;
@@ -25,7 +25,7 @@ public class User implements Serializable {
     private Date birthday;
     @ExcelProperty("年龄")
     private Integer age;
-    @ExcelProperty(value = "性别",converter = DictSelectConverter.class)
+    @ExcelProperty(value = "性别",converter = DictCascadeSelectConverter.class)
     @ExcelPropertySupport(select = @ExcelSelect(param = "sys_sex"))
     @NotNull(message = "性别不能为空")
     private String sex;