mirror of
				https://github.com/dromara/RuoYi-Vue-Plus.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	!345 增加Excel导出表格时级联下拉选项功能
* fixed(Excel工具类重载): * fixed(字典接口耦合问题): * fixed(调整接口): * fixed(切换条件不正确): * feat(优化注解|反向解析失效): * feat(增加注释|编写Demo|修复bug): * feat(Excel导出附带有下拉框):
This commit is contained in:
		@@ -37,14 +37,34 @@ public class ExcelEnumConvert implements Converter<Object> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public Object convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
 | 
					    public Object convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
 | 
				
			||||||
        Object codeValue = cellData.getData();
 | 
					        cellData.checkEmpty();
 | 
				
			||||||
 | 
					        // Excel中填入的是枚举中指定的描述
 | 
				
			||||||
 | 
					        Object textValue = null;
 | 
				
			||||||
 | 
					        switch (cellData.getType()) {
 | 
				
			||||||
 | 
					            case STRING:
 | 
				
			||||||
 | 
					            case DIRECT_STRING:
 | 
				
			||||||
 | 
					            case RICH_TEXT_STRING:
 | 
				
			||||||
 | 
					                textValue = cellData.getStringValue();
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case NUMBER:
 | 
				
			||||||
 | 
					                textValue = cellData.getNumberValue();
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case BOOLEAN:
 | 
				
			||||||
 | 
					                textValue = cellData.getBooleanValue();
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        // 如果是空值
 | 
					        // 如果是空值
 | 
				
			||||||
        if (ObjectUtil.isNull(codeValue)) {
 | 
					        if (ObjectUtil.isNull(textValue)) {
 | 
				
			||||||
            return null;
 | 
					            return null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        Map<Object, String> enumValueMap = beforeConvert(contentProperty);
 | 
					        Map<Object, String> enumCodeToTextMap = beforeConvert(contentProperty);
 | 
				
			||||||
        String textValue = enumValueMap.get(codeValue);
 | 
					        // 从Java输出至Excel是code转text
 | 
				
			||||||
        return Convert.convert(contentProperty.getField().getType(), textValue);
 | 
					        // 因此从Excel转Java应该将text与code对调
 | 
				
			||||||
 | 
					        Map<Object, Object> enumTextToCodeMap = new HashMap<>();
 | 
				
			||||||
 | 
					        enumCodeToTextMap.forEach((key, value) -> enumTextToCodeMap.put(value, key));
 | 
				
			||||||
 | 
					        // 应该从text -> code中查找
 | 
				
			||||||
 | 
					        Object codeValue = enumTextToCodeMap.get(textValue);
 | 
				
			||||||
 | 
					        return Convert.convert(contentProperty.getField().getType(), codeValue);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
package com.ruoyi.common.core.service;
 | 
					package com.ruoyi.common.core.service;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 通用 字典服务
 | 
					 * 通用 字典服务
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
@@ -54,4 +56,11 @@ public interface DictService {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    String getDictValue(String dictType, String dictLabel, String separator);
 | 
					    String getDictValue(String dictType, String dictLabel, String separator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 获取字典下所有的字典值与标签
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param dictType 字典类型
 | 
				
			||||||
 | 
					     * @return dictValue为key,dictLabel为值组成的Map
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    Map<String, String> getAllDictByDictType(String dictType);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,91 @@
 | 
				
			|||||||
 | 
					package com.ruoyi.common.excel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.hutool.core.util.StrUtil;
 | 
				
			||||||
 | 
					import com.ruoyi.common.exception.ServiceException;
 | 
				
			||||||
 | 
					import lombok.AllArgsConstructor;
 | 
				
			||||||
 | 
					import lombok.Data;
 | 
				
			||||||
 | 
					import lombok.NoArgsConstructor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * <h1>Excel下拉可选项</h1>
 | 
				
			||||||
 | 
					 * 注意:为确保下拉框解析正确,传值务必使用createOptionValue()做为值的拼接
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Emil.Zhang
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Data
 | 
				
			||||||
 | 
					@AllArgsConstructor
 | 
				
			||||||
 | 
					@NoArgsConstructor
 | 
				
			||||||
 | 
					@SuppressWarnings("unused")
 | 
				
			||||||
 | 
					public class DropDownOptions {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 一级下拉所在列index,从0开始算
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private int index = 0;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 二级下拉所在的index,从0开始算,不能与一级相同
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private int nextIndex = 0;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 一级下拉所包含的数据
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private List<String> options = new ArrayList<>();
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 二级下拉所包含的数据Map
 | 
				
			||||||
 | 
					     * <p>以每一个一级选项值为Key,每个一级选项对应的二级数据为Value</p>
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private Map<String, List<String>> nextOptions = new HashMap<>();
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 分隔符
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private static final String DELIMITER = "_";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 创建只有一级的下拉选
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public DropDownOptions(int index, List<String> options) {
 | 
				
			||||||
 | 
					        this.index = index;
 | 
				
			||||||
 | 
					        this.options = options;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * <h2>创建每个选项可选值</h2>
 | 
				
			||||||
 | 
					     * <p>注意:不能以数字,特殊符号开头,选项中不可以包含任何运算符号</p>
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param vars 可选值内包含的参数
 | 
				
			||||||
 | 
					     * @return 合规的可选值
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static String createOptionValue(Object... vars) {
 | 
				
			||||||
 | 
					        StringBuilder stringBuffer = new StringBuilder();
 | 
				
			||||||
 | 
					        String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$";
 | 
				
			||||||
 | 
					        for (int i = 0; i < vars.length; i++) {
 | 
				
			||||||
 | 
					            Object var = vars[i];
 | 
				
			||||||
 | 
					            if (!var.toString().matches(regex)) {
 | 
				
			||||||
 | 
					                throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            stringBuffer.append(StrUtil.trimToEmpty(var.toString()));
 | 
				
			||||||
 | 
					            if (i < vars.length - 1) {
 | 
				
			||||||
 | 
					                // 直至最后一个前,都以_作为切割线
 | 
				
			||||||
 | 
					                stringBuffer.append(DELIMITER);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (stringBuffer.toString().matches("^\\d_*$")) {
 | 
				
			||||||
 | 
					            throw new ServiceException("禁止以数字开头");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return stringBuffer.toString();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 将处理后合理的可选值解析为原始的参数
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param option 经过处理后的合理的可选项
 | 
				
			||||||
 | 
					     * @return 原始的参数
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static List<String> analyzeOptionValue(String option) {
 | 
				
			||||||
 | 
					        return StrUtil.split(option, DELIMITER, true, true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,391 @@
 | 
				
			|||||||
 | 
					package com.ruoyi.common.excel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.hutool.core.util.EnumUtil;
 | 
				
			||||||
 | 
					import cn.hutool.core.util.ObjectUtil;
 | 
				
			||||||
 | 
					import cn.hutool.core.util.StrUtil;
 | 
				
			||||||
 | 
					import com.alibaba.excel.annotation.ExcelProperty;
 | 
				
			||||||
 | 
					import com.alibaba.excel.write.handler.SheetWriteHandler;
 | 
				
			||||||
 | 
					import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
 | 
				
			||||||
 | 
					import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
 | 
				
			||||||
 | 
					import com.ruoyi.common.annotation.ExcelDictFormat;
 | 
				
			||||||
 | 
					import com.ruoyi.common.annotation.ExcelEnumFormat;
 | 
				
			||||||
 | 
					import com.ruoyi.common.core.service.DictService;
 | 
				
			||||||
 | 
					import com.ruoyi.common.exception.ServiceException;
 | 
				
			||||||
 | 
					import com.ruoyi.common.utils.spring.SpringUtils;
 | 
				
			||||||
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
 | 
					import org.apache.poi.ss.usermodel.*;
 | 
				
			||||||
 | 
					import org.apache.poi.ss.util.CellRangeAddressList;
 | 
				
			||||||
 | 
					import org.apache.poi.ss.util.WorkbookUtil;
 | 
				
			||||||
 | 
					import org.apache.poi.xssf.usermodel.XSSFDataValidation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.reflect.Field;
 | 
				
			||||||
 | 
					import java.util.*;
 | 
				
			||||||
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * <h1>Excel表格下拉选操作</h1>
 | 
				
			||||||
 | 
					 * 考虑到下拉选过多可能导致Excel打开缓慢的问题,只校验前1000行
 | 
				
			||||||
 | 
					 * <p>
 | 
				
			||||||
 | 
					 * 即只有前1000行的数据可以用下拉框,超出的自行通过限制数据量的形式,第二次输出
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Emil.Zhang
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Slf4j
 | 
				
			||||||
 | 
					public class ExcelDownHandler implements SheetWriteHandler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Excel表格中的列名英文
 | 
				
			||||||
 | 
					     * 仅为了解析列英文,禁止修改
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private static final String EXCEL_COLUMN_NAME = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 单选数据Sheet名
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private static final String OPTIONS_SHEET_NAME = "options";
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 联动选择数据Sheet名的头
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private static final String LINKED_OPTIONS_SHEET_NAME = "linkedOptions";
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 下拉可选项
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private final List<DropDownOptions> dropDownOptions;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 当前单选进度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private int currentOptionsColumnIndex;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 当前联动选择进度
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private int currentLinkedOptionsSheetIndex;
 | 
				
			||||||
 | 
					    private final DictService dictService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public ExcelDownHandler(List<DropDownOptions> options) {
 | 
				
			||||||
 | 
					        this.dropDownOptions = options;
 | 
				
			||||||
 | 
					        this.currentOptionsColumnIndex = 0;
 | 
				
			||||||
 | 
					        this.currentLinkedOptionsSheetIndex = 0;
 | 
				
			||||||
 | 
					        this.dictService = SpringUtils.getBean(DictService.class);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * <h2>开始创建下拉数据</h2>
 | 
				
			||||||
 | 
					     * 1.通过解析传入的@ExcelProperty同级是否标注有@DropDown选项
 | 
				
			||||||
 | 
					     * 如果有且设置了value值,则将其直接置为下拉可选项
 | 
				
			||||||
 | 
					     * <p>
 | 
				
			||||||
 | 
					     * 2.或者在调用ExcelUtil时指定了可选项,将依据传入的可选项做下拉
 | 
				
			||||||
 | 
					     * <p>
 | 
				
			||||||
 | 
					     * 3.二者并存,注意调用方式
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
 | 
				
			||||||
 | 
					        Sheet sheet = writeSheetHolder.getSheet();
 | 
				
			||||||
 | 
					        // 开始设置下拉框 HSSFWorkbook
 | 
				
			||||||
 | 
					        DataValidationHelper helper = sheet.getDataValidationHelper();
 | 
				
			||||||
 | 
					        Field[] fields = writeWorkbookHolder.getClazz().getDeclaredFields();
 | 
				
			||||||
 | 
					        Workbook workbook = writeWorkbookHolder.getWorkbook();
 | 
				
			||||||
 | 
					        int length = fields.length;
 | 
				
			||||||
 | 
					        for (int i = 0; i < length; i++) {
 | 
				
			||||||
 | 
					            // 循环实体中的每个属性
 | 
				
			||||||
 | 
					            // 可选的下拉值
 | 
				
			||||||
 | 
					            List<String> options = new ArrayList<>();
 | 
				
			||||||
 | 
					            if (fields[i].isAnnotationPresent(ExcelDictFormat.class)) {
 | 
				
			||||||
 | 
					                // 如果指定了@ExcelDictFormat,则使用字典的逻辑
 | 
				
			||||||
 | 
					                ExcelDictFormat thisFiledExcelDictFormat = fields[i].getDeclaredAnnotation(ExcelDictFormat.class);
 | 
				
			||||||
 | 
					                String dictType = thisFiledExcelDictFormat.dictType();
 | 
				
			||||||
 | 
					                String converterExp = thisFiledExcelDictFormat.readConverterExp();
 | 
				
			||||||
 | 
					                if (StrUtil.isNotBlank(dictType)) {
 | 
				
			||||||
 | 
					                    // 如果传递了字典名,则依据字典建立下拉
 | 
				
			||||||
 | 
					                    options =
 | 
				
			||||||
 | 
					                        new ArrayList<>(
 | 
				
			||||||
 | 
					                            Optional.ofNullable(dictService.getAllDictByDictType(dictType))
 | 
				
			||||||
 | 
					                                .orElseThrow(() -> new ServiceException(String.format("字典 %s 不存在", dictType)))
 | 
				
			||||||
 | 
					                                .values()
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                } else if (StrUtil.isNotBlank(converterExp)) {
 | 
				
			||||||
 | 
					                    // 如果指定了确切的值,则直接解析确切的值
 | 
				
			||||||
 | 
					                    options = StrUtil.split(
 | 
				
			||||||
 | 
					                        converterExp,
 | 
				
			||||||
 | 
					                        thisFiledExcelDictFormat.separator(),
 | 
				
			||||||
 | 
					                        true,
 | 
				
			||||||
 | 
					                        true);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else if (fields[i].isAnnotationPresent(ExcelEnumFormat.class)) {
 | 
				
			||||||
 | 
					                // 否则如果指定了@ExcelEnumFormat,则使用枚举的逻辑
 | 
				
			||||||
 | 
					                ExcelEnumFormat thisFiledExcelEnumFormat = fields[i].getDeclaredAnnotation(ExcelEnumFormat.class);
 | 
				
			||||||
 | 
					                options =
 | 
				
			||||||
 | 
					                    EnumUtil
 | 
				
			||||||
 | 
					                        .getFieldValues(
 | 
				
			||||||
 | 
					                            thisFiledExcelEnumFormat.enumClass(),
 | 
				
			||||||
 | 
					                            thisFiledExcelEnumFormat.textField()
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        .stream()
 | 
				
			||||||
 | 
					                        .map(String::valueOf)
 | 
				
			||||||
 | 
					                        .collect(Collectors.toList());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (ObjectUtil.isNotEmpty(options)) {
 | 
				
			||||||
 | 
					                // 仅当下拉可选项不为空时执行
 | 
				
			||||||
 | 
					                // 获取列下标,默认为当前循环次数
 | 
				
			||||||
 | 
					                int index = i;
 | 
				
			||||||
 | 
					                if (fields[i].isAnnotationPresent(ExcelProperty.class)) {
 | 
				
			||||||
 | 
					                    // 如果指定了列下标,以指定的为主
 | 
				
			||||||
 | 
					                    index = fields[i].getDeclaredAnnotation(ExcelProperty.class).index();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (options.size() > 20) {
 | 
				
			||||||
 | 
					                    // 这里限制如果可选项大于20,则使用额外表形式
 | 
				
			||||||
 | 
					                    dropDownWithSheet(helper, workbook, sheet, index, options);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // 否则使用固定值形式
 | 
				
			||||||
 | 
					                    dropDownWithSimple(helper, sheet, index, options);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        dropDownOptions.forEach(everyOptions -> {
 | 
				
			||||||
 | 
					            // 如果传递了下拉框选择器参数
 | 
				
			||||||
 | 
					            if (!everyOptions.getNextOptions().isEmpty()) {
 | 
				
			||||||
 | 
					                // 当二级选项不为空时,使用额外关联表的形式
 | 
				
			||||||
 | 
					                dropDownLinkedOptions(helper, workbook, sheet, everyOptions);
 | 
				
			||||||
 | 
					            } else if (everyOptions.getOptions().size() > 10) {
 | 
				
			||||||
 | 
					                // 当一级选项参数个数大于10,使用额外表的形式
 | 
				
			||||||
 | 
					                dropDownWithSheet(helper, workbook, sheet, everyOptions.getIndex(), everyOptions.getOptions());
 | 
				
			||||||
 | 
					            } else if (everyOptions.getOptions().size() != 0) {
 | 
				
			||||||
 | 
					                // 当一级选项个数不为空,使用默认形式
 | 
				
			||||||
 | 
					                dropDownWithSimple(helper, sheet, everyOptions.getIndex(), everyOptions.getOptions());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * <h2>简单下拉框</h2>
 | 
				
			||||||
 | 
					     * 直接将可选项拼接为指定列的数据校验值
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param celIndex 列index
 | 
				
			||||||
 | 
					     * @param value    下拉选可选值
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private void dropDownWithSimple(DataValidationHelper helper, Sheet sheet, Integer celIndex, List<String> value) {
 | 
				
			||||||
 | 
					        if (ObjectUtil.isEmpty(value)) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.markOptionsToSheet(helper, sheet, celIndex, helper.createExplicitListConstraint(value.toArray(new String[0])));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * <h2>额外表格形式的级联下拉框</h2>
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param options 额外表格形式存储的下拉可选项
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private void dropDownLinkedOptions(DataValidationHelper helper, Workbook workbook, Sheet sheet, DropDownOptions options) {
 | 
				
			||||||
 | 
					        String linkedOptionsSheetName = String.format("%s_%d", LINKED_OPTIONS_SHEET_NAME, currentLinkedOptionsSheetIndex);
 | 
				
			||||||
 | 
					        // 创建联动下拉数据表
 | 
				
			||||||
 | 
					        Sheet linkedOptionsDataSheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(linkedOptionsSheetName));
 | 
				
			||||||
 | 
					        // 将下拉表隐藏
 | 
				
			||||||
 | 
					        workbook.setSheetHidden(workbook.getSheetIndex(linkedOptionsDataSheet), true);
 | 
				
			||||||
 | 
					        // 完善横向的一级选项数据表
 | 
				
			||||||
 | 
					        List<String> firstOptions = options.getOptions();
 | 
				
			||||||
 | 
					        Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 创建名称管理器
 | 
				
			||||||
 | 
					        Name name = workbook.createName();
 | 
				
			||||||
 | 
					        // 设置名称管理器的别名
 | 
				
			||||||
 | 
					        name.setNameName(linkedOptionsSheetName);
 | 
				
			||||||
 | 
					        // 以横向第一行创建一级下拉拼接引用位置
 | 
				
			||||||
 | 
					        String firstOptionsFunction = String.format("%s!$%s$1:$%s$1",
 | 
				
			||||||
 | 
					            linkedOptionsSheetName,
 | 
				
			||||||
 | 
					            getExcelColumnName(0),
 | 
				
			||||||
 | 
					            getExcelColumnName(firstOptions.size())
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        // 设置名称管理器的引用位置
 | 
				
			||||||
 | 
					        name.setRefersToFormula(firstOptionsFunction);
 | 
				
			||||||
 | 
					        // 设置数据校验为序列模式,引用的是名称管理器中的别名
 | 
				
			||||||
 | 
					        this.markOptionsToSheet(helper, sheet, options.getIndex(), helper.createFormulaListConstraint(linkedOptionsSheetName));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (int columIndex = 0; columIndex < firstOptions.size(); columIndex++) {
 | 
				
			||||||
 | 
					            // 先提取主表中一级下拉的列名
 | 
				
			||||||
 | 
					            String firstOptionsColumnName = getExcelColumnName(columIndex);
 | 
				
			||||||
 | 
					            // 一次循环是每一个一级选项
 | 
				
			||||||
 | 
					            int finalI = columIndex;
 | 
				
			||||||
 | 
					            // 本次循环的一级选项值
 | 
				
			||||||
 | 
					            String thisFirstOptionsValue = firstOptions.get(columIndex);
 | 
				
			||||||
 | 
					            // 创建第一行的数据
 | 
				
			||||||
 | 
					            Optional
 | 
				
			||||||
 | 
					                // 获取第一行
 | 
				
			||||||
 | 
					                .ofNullable(linkedOptionsDataSheet.getRow(0))
 | 
				
			||||||
 | 
					                // 如果不存在则创建第一行
 | 
				
			||||||
 | 
					                .orElseGet(() -> linkedOptionsDataSheet.createRow(finalI))
 | 
				
			||||||
 | 
					                // 第一行当前列
 | 
				
			||||||
 | 
					                .createCell(columIndex)
 | 
				
			||||||
 | 
					                // 设置值为当前一级选项值
 | 
				
			||||||
 | 
					                .setCellValue(thisFirstOptionsValue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 第二行开始,设置第二级别选项参数
 | 
				
			||||||
 | 
					            List<String> secondOptions = secoundOptionsMap.get(thisFirstOptionsValue);
 | 
				
			||||||
 | 
					            if (ObjectUtil.isEmpty(secondOptions)) {
 | 
				
			||||||
 | 
					                // 必须保证至少有一个关联选项,否则将导致Excel解析错误
 | 
				
			||||||
 | 
					                secondOptions = Collections.singletonList("暂无_0");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 以该一级选项值创建子名称管理器
 | 
				
			||||||
 | 
					            Name sonName = workbook.createName();
 | 
				
			||||||
 | 
					            // 设置名称管理器的别名
 | 
				
			||||||
 | 
					            sonName.setNameName(thisFirstOptionsValue);
 | 
				
			||||||
 | 
					            // 以第二行该列数据拼接引用位置
 | 
				
			||||||
 | 
					            String sonFunction = String.format("%s!$%s$2:$%s$%d",
 | 
				
			||||||
 | 
					                linkedOptionsSheetName,
 | 
				
			||||||
 | 
					                firstOptionsColumnName,
 | 
				
			||||||
 | 
					                firstOptionsColumnName,
 | 
				
			||||||
 | 
					                secondOptions.size() + 1
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            // 设置名称管理器的引用位置
 | 
				
			||||||
 | 
					            sonName.setRefersToFormula(sonFunction);
 | 
				
			||||||
 | 
					            // 数据验证为序列模式,引用到每一个主表中的二级选项位置
 | 
				
			||||||
 | 
					            // 创建子项的名称管理器,只是为了使得Excel可以识别到数据
 | 
				
			||||||
 | 
					            String mainSheetFirstOptionsColumnName = getExcelColumnName(options.getIndex());
 | 
				
			||||||
 | 
					            for (int i = 0; i < 100; i++) {
 | 
				
			||||||
 | 
					                // 以一级选项对应的主体所在位置创建二级下拉
 | 
				
			||||||
 | 
					                String secondOptionsFunction = String.format("=INDIRECT(%s%d)", mainSheetFirstOptionsColumnName, i + 1);
 | 
				
			||||||
 | 
					                // 二级只能主表每一行的每一列添加二级校验
 | 
				
			||||||
 | 
					                markLinkedOptionsToSheet(helper, sheet, i, options.getNextIndex(), helper.createFormulaListConstraint(secondOptionsFunction));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (int rowIndex = 0; rowIndex < secondOptions.size(); rowIndex++) {
 | 
				
			||||||
 | 
					                // 从第二行开始填充二级选项
 | 
				
			||||||
 | 
					                int finalRowIndex = rowIndex + 1;
 | 
				
			||||||
 | 
					                int finalColumIndex = columIndex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Row row = Optional
 | 
				
			||||||
 | 
					                    .ofNullable(linkedOptionsDataSheet.getRow(finalRowIndex))
 | 
				
			||||||
 | 
					                    // 没有则创建
 | 
				
			||||||
 | 
					                    .orElseGet(() -> linkedOptionsDataSheet.createRow(finalRowIndex));
 | 
				
			||||||
 | 
					                Optional
 | 
				
			||||||
 | 
					                    // 在本级一级选项所在的列
 | 
				
			||||||
 | 
					                    .ofNullable(row.getCell(finalColumIndex))
 | 
				
			||||||
 | 
					                    // 不存在则创建
 | 
				
			||||||
 | 
					                    .orElseGet(() -> row.createCell(finalColumIndex))
 | 
				
			||||||
 | 
					                    // 设置二级选项值
 | 
				
			||||||
 | 
					                    .setCellValue(secondOptions.get(rowIndex));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        currentLinkedOptionsSheetIndex++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * <h2>额外表格形式的普通下拉框</h2>
 | 
				
			||||||
 | 
					     * 由于下拉框可选值数量过多,为提升Excel打开效率,使用额外表格形式做下拉
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param celIndex 下拉选
 | 
				
			||||||
 | 
					     * @param value    下拉选可选值
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List<String> value) {
 | 
				
			||||||
 | 
					        // 创建下拉数据表
 | 
				
			||||||
 | 
					        Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)))
 | 
				
			||||||
 | 
					            .orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)));
 | 
				
			||||||
 | 
					        // 将下拉表隐藏
 | 
				
			||||||
 | 
					        workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true);
 | 
				
			||||||
 | 
					        // 完善纵向的一级选项数据表
 | 
				
			||||||
 | 
					        for (int i = 0; i < value.size(); i++) {
 | 
				
			||||||
 | 
					            int finalI = i;
 | 
				
			||||||
 | 
					            // 获取每一选项行,如果没有则创建
 | 
				
			||||||
 | 
					            Row row = Optional.ofNullable(simpleDataSheet.getRow(i))
 | 
				
			||||||
 | 
					                .orElseGet(() -> simpleDataSheet.createRow(finalI));
 | 
				
			||||||
 | 
					            // 获取本级选项对应的选项列,如果没有则创建
 | 
				
			||||||
 | 
					            Cell cell = Optional.ofNullable(row.getCell(currentOptionsColumnIndex))
 | 
				
			||||||
 | 
					                .orElseGet(() -> row.createCell(currentOptionsColumnIndex));
 | 
				
			||||||
 | 
					            // 设置值
 | 
				
			||||||
 | 
					            cell.setCellValue(value.get(i));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 创建名称管理器
 | 
				
			||||||
 | 
					        Name name = workbook.createName();
 | 
				
			||||||
 | 
					        // 设置名称管理器的别名
 | 
				
			||||||
 | 
					        String nameName = String.format("%s_%d", OPTIONS_SHEET_NAME, celIndex);
 | 
				
			||||||
 | 
					        name.setNameName(nameName);
 | 
				
			||||||
 | 
					        // 以纵向第一列创建一级下拉拼接引用位置
 | 
				
			||||||
 | 
					        String function = String.format("%s!$%s$1:$%s$%d",
 | 
				
			||||||
 | 
					            OPTIONS_SHEET_NAME,
 | 
				
			||||||
 | 
					            getExcelColumnName(currentOptionsColumnIndex),
 | 
				
			||||||
 | 
					            getExcelColumnName(currentOptionsColumnIndex),
 | 
				
			||||||
 | 
					            value.size());
 | 
				
			||||||
 | 
					        // 设置名称管理器的引用位置
 | 
				
			||||||
 | 
					        name.setRefersToFormula(function);
 | 
				
			||||||
 | 
					        // 设置数据校验为序列模式,引用的是名称管理器中的别名
 | 
				
			||||||
 | 
					        this.markOptionsToSheet(helper, sheet, celIndex, helper.createFormulaListConstraint(nameName));
 | 
				
			||||||
 | 
					        currentOptionsColumnIndex++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 挂载下拉的列,仅限一级选项
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private void markOptionsToSheet(DataValidationHelper helper,
 | 
				
			||||||
 | 
					                                    Sheet sheet,
 | 
				
			||||||
 | 
					                                    Integer celIndex,
 | 
				
			||||||
 | 
					                                    DataValidationConstraint constraint) {
 | 
				
			||||||
 | 
					        // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列
 | 
				
			||||||
 | 
					        CellRangeAddressList addressList = new CellRangeAddressList(1, 1000, celIndex, celIndex);
 | 
				
			||||||
 | 
					        markDataValidationToSheet(helper, sheet, constraint, addressList);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 挂载下拉的列,仅限二级选项
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private void markLinkedOptionsToSheet(DataValidationHelper helper,
 | 
				
			||||||
 | 
					                                          Sheet sheet,
 | 
				
			||||||
 | 
					                                          Integer rowIndex,
 | 
				
			||||||
 | 
					                                          Integer celIndex,
 | 
				
			||||||
 | 
					                                          DataValidationConstraint constraint) {
 | 
				
			||||||
 | 
					        // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列
 | 
				
			||||||
 | 
					        CellRangeAddressList addressList = new CellRangeAddressList(rowIndex, rowIndex, celIndex, celIndex);
 | 
				
			||||||
 | 
					        markDataValidationToSheet(helper, sheet, constraint, addressList);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 应用数据校验
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private void markDataValidationToSheet(DataValidationHelper helper,
 | 
				
			||||||
 | 
					                                           Sheet sheet,
 | 
				
			||||||
 | 
					                                           DataValidationConstraint constraint,
 | 
				
			||||||
 | 
					                                           CellRangeAddressList addressList) {
 | 
				
			||||||
 | 
					        // 数据有效性对象
 | 
				
			||||||
 | 
					        DataValidation dataValidation = helper.createValidation(constraint, addressList);
 | 
				
			||||||
 | 
					        // 处理Excel兼容性问题
 | 
				
			||||||
 | 
					        if (dataValidation instanceof XSSFDataValidation) {
 | 
				
			||||||
 | 
					            //数据校验
 | 
				
			||||||
 | 
					            dataValidation.setSuppressDropDownArrow(true);
 | 
				
			||||||
 | 
					            //错误提示
 | 
				
			||||||
 | 
					            dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);
 | 
				
			||||||
 | 
					            dataValidation.createErrorBox("提示", "此值与单元格定义数据不一致");
 | 
				
			||||||
 | 
					            dataValidation.setShowErrorBox(true);
 | 
				
			||||||
 | 
					            //选定提示
 | 
				
			||||||
 | 
					            dataValidation.createPromptBox("填写说明:", "填写内容只能为下拉中数据,其他数据将导致导入失败");
 | 
				
			||||||
 | 
					            dataValidation.setShowPromptBox(true);
 | 
				
			||||||
 | 
					            sheet.addValidationData(dataValidation);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            dataValidation.setSuppressDropDownArrow(false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        sheet.addValidationData(dataValidation);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * <h2>依据列index获取列名英文</h2>
 | 
				
			||||||
 | 
					     * 依据列index转换为Excel中的列名英文
 | 
				
			||||||
 | 
					     * <p>例如第1列,index为0,解析出来为A列</p>
 | 
				
			||||||
 | 
					     * 第27列,index为26,解析为AA列
 | 
				
			||||||
 | 
					     * <p>第28列,index为27,解析为AB列</p>
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param columnIndex 列index
 | 
				
			||||||
 | 
					     * @return 列index所在得英文名
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private String getExcelColumnName(int columnIndex) {
 | 
				
			||||||
 | 
					        // 26一循环的次数
 | 
				
			||||||
 | 
					        int columnCircleCount = columnIndex / 26;
 | 
				
			||||||
 | 
					        // 26一循环内的位置
 | 
				
			||||||
 | 
					        int thisCircleColumnIndex = columnIndex % 26;
 | 
				
			||||||
 | 
					        // 26一循环的次数大于0,则视为栏名至少两位
 | 
				
			||||||
 | 
					        String columnPrefix = columnCircleCount == 0
 | 
				
			||||||
 | 
					            ? ""
 | 
				
			||||||
 | 
					            : StrUtil.subWithLength(EXCEL_COLUMN_NAME, columnCircleCount - 1, 1);
 | 
				
			||||||
 | 
					        // 从26一循环内取对应的栏位名
 | 
				
			||||||
 | 
					        String columnNext = StrUtil.subWithLength(EXCEL_COLUMN_NAME, thisCircleColumnIndex, 1);
 | 
				
			||||||
 | 
					        // 将二者拼接即为最终的栏位名
 | 
				
			||||||
 | 
					        return columnPrefix + columnNext;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -11,10 +11,7 @@ import com.alibaba.excel.write.metadata.fill.FillConfig;
 | 
				
			|||||||
import com.alibaba.excel.write.metadata.fill.FillWrapper;
 | 
					import com.alibaba.excel.write.metadata.fill.FillWrapper;
 | 
				
			||||||
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
 | 
					import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
 | 
				
			||||||
import com.ruoyi.common.convert.ExcelBigNumberConvert;
 | 
					import com.ruoyi.common.convert.ExcelBigNumberConvert;
 | 
				
			||||||
import com.ruoyi.common.excel.CellMergeStrategy;
 | 
					import com.ruoyi.common.excel.*;
 | 
				
			||||||
import com.ruoyi.common.excel.DefaultExcelListener;
 | 
					 | 
				
			||||||
import com.ruoyi.common.excel.ExcelListener;
 | 
					 | 
				
			||||||
import com.ruoyi.common.excel.ExcelResult;
 | 
					 | 
				
			||||||
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;
 | 
				
			||||||
@@ -94,6 +91,25 @@ public class ExcelUtil {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 导出excel
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param list      导出数据集合
 | 
				
			||||||
 | 
					     * @param sheetName 工作表的名称
 | 
				
			||||||
 | 
					     * @param clazz     实体类
 | 
				
			||||||
 | 
					     * @param response  响应体
 | 
				
			||||||
 | 
					     * @param options   级联下拉选
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response, List<DropDownOptions> options) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            resetResponse(sheetName, response);
 | 
				
			||||||
 | 
					            ServletOutputStream os = response.getOutputStream();
 | 
				
			||||||
 | 
					            exportExcel(list, sheetName, clazz, false, os, options);
 | 
				
			||||||
 | 
					        } catch (IOException e) {
 | 
				
			||||||
 | 
					            throw new RuntimeException("导出Excel异常");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 导出excel
 | 
					     * 导出excel
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@@ -113,6 +129,26 @@ public class ExcelUtil {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 导出excel
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param list      导出数据集合
 | 
				
			||||||
 | 
					     * @param sheetName 工作表的名称
 | 
				
			||||||
 | 
					     * @param clazz     实体类
 | 
				
			||||||
 | 
					     * @param merge     是否合并单元格
 | 
				
			||||||
 | 
					     * @param response  响应体
 | 
				
			||||||
 | 
					     * @param options   级联下拉选
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge, HttpServletResponse response, List<DropDownOptions> options) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            resetResponse(sheetName, response);
 | 
				
			||||||
 | 
					            ServletOutputStream os = response.getOutputStream();
 | 
				
			||||||
 | 
					            exportExcel(list, sheetName, clazz, merge, os, options);
 | 
				
			||||||
 | 
					        } catch (IOException e) {
 | 
				
			||||||
 | 
					            throw new RuntimeException("导出Excel异常");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 导出excel
 | 
					     * 导出excel
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@@ -125,6 +161,19 @@ public class ExcelUtil {
 | 
				
			|||||||
        exportExcel(list, sheetName, clazz, false, os);
 | 
					        exportExcel(list, sheetName, clazz, false, os);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 导出excel
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param list      导出数据集合
 | 
				
			||||||
 | 
					     * @param sheetName 工作表的名称
 | 
				
			||||||
 | 
					     * @param clazz     实体类
 | 
				
			||||||
 | 
					     * @param os        输出流
 | 
				
			||||||
 | 
					     * @param options   级联下拉选内容
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, OutputStream os, List<DropDownOptions> options) {
 | 
				
			||||||
 | 
					        exportExcel(list, sheetName, clazz, false, os, options);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 导出excel
 | 
					     * 导出excel
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@@ -149,6 +198,29 @@ public class ExcelUtil {
 | 
				
			|||||||
        builder.doWrite(list);
 | 
					        builder.doWrite(list);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 导出带有下拉框的Excel表格
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param options 下拉框数据
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz,
 | 
				
			||||||
 | 
					                                       boolean merge, OutputStream os, List<DropDownOptions> options) {
 | 
				
			||||||
 | 
					        ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
 | 
				
			||||||
 | 
					            .autoCloseStream(false)
 | 
				
			||||||
 | 
					            // 自动适配
 | 
				
			||||||
 | 
					            .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
 | 
				
			||||||
 | 
					            // 大数值自动转换 防止失真
 | 
				
			||||||
 | 
					            .registerConverter(new ExcelBigNumberConvert())
 | 
				
			||||||
 | 
					            // 添加下拉框操作
 | 
				
			||||||
 | 
					            .registerWriteHandler(new ExcelDownHandler(options))
 | 
				
			||||||
 | 
					            .sheet(sheetName);
 | 
				
			||||||
 | 
					        if (merge) {
 | 
				
			||||||
 | 
					            // 合并处理器
 | 
				
			||||||
 | 
					            builder.registerWriteHandler(new CellMergeStrategy(list, true));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        builder.doWrite(list);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 单表多数据模板导出 模板格式为 {.属性}
 | 
					     * 单表多数据模板导出 模板格式为 {.属性}
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,18 @@
 | 
				
			|||||||
package com.ruoyi.demo.controller;
 | 
					package com.ruoyi.demo.controller;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.dev33.satoken.annotation.SaIgnore;
 | 
				
			||||||
import cn.hutool.core.collection.CollUtil;
 | 
					import cn.hutool.core.collection.CollUtil;
 | 
				
			||||||
 | 
					import com.ruoyi.common.excel.ExcelResult;
 | 
				
			||||||
import com.ruoyi.common.utils.poi.ExcelUtil;
 | 
					import com.ruoyi.common.utils.poi.ExcelUtil;
 | 
				
			||||||
 | 
					import com.ruoyi.demo.domain.vo.ExportDemoVo;
 | 
				
			||||||
 | 
					import com.ruoyi.demo.listener.ExportDemoListener;
 | 
				
			||||||
 | 
					import com.ruoyi.demo.service.IExportExcelService;
 | 
				
			||||||
import lombok.AllArgsConstructor;
 | 
					import lombok.AllArgsConstructor;
 | 
				
			||||||
import lombok.Data;
 | 
					import lombok.Data;
 | 
				
			||||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
					import org.springframework.http.MediaType;
 | 
				
			||||||
import org.springframework.web.bind.annotation.RestController;
 | 
					import org.springframework.web.bind.annotation.*;
 | 
				
			||||||
 | 
					import org.springframework.web.multipart.MultipartFile;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.servlet.http.HttpServletResponse;
 | 
					import javax.servlet.http.HttpServletResponse;
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
@@ -20,9 +26,12 @@ import java.util.Map;
 | 
				
			|||||||
 * @author Lion Li
 | 
					 * @author Lion Li
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@RestController
 | 
					@RestController
 | 
				
			||||||
 | 
					@RequiredArgsConstructor
 | 
				
			||||||
@RequestMapping("/demo/excel")
 | 
					@RequestMapping("/demo/excel")
 | 
				
			||||||
public class TestExcelController {
 | 
					public class TestExcelController {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final IExportExcelService exportExcelService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 单列表多数据
 | 
					     * 单列表多数据
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@@ -76,6 +85,28 @@ public class TestExcelController {
 | 
				
			|||||||
        ExcelUtil.exportTemplateMultiList(multiListMap, "多列表.xlsx", "excel/多列表.xlsx", response);
 | 
					        ExcelUtil.exportTemplateMultiList(multiListMap, "多列表.xlsx", "excel/多列表.xlsx", response);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 导出下拉框
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param response /
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @SaIgnore
 | 
				
			||||||
 | 
					    @GetMapping("/exportWithOptions")
 | 
				
			||||||
 | 
					    public void exportWithOptions(HttpServletResponse response) {
 | 
				
			||||||
 | 
					        exportExcelService.exportWithOptions(response);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 导入表格
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @SaIgnore
 | 
				
			||||||
 | 
					    @PostMapping(value = "/importWithOptions", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
 | 
				
			||||||
 | 
					    public List<ExportDemoVo> importWithOptions(@RequestPart("file") MultipartFile file) throws Exception {
 | 
				
			||||||
 | 
					        // 处理解析结果
 | 
				
			||||||
 | 
					        ExcelResult<ExportDemoVo> excelResult = ExcelUtil.importExcel(file.getInputStream(), ExportDemoVo.class, new ExportDemoListener());
 | 
				
			||||||
 | 
					        return excelResult.getList();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Data
 | 
					    @Data
 | 
				
			||||||
    @AllArgsConstructor
 | 
					    @AllArgsConstructor
 | 
				
			||||||
    static class TestObj1 {
 | 
					    static class TestObj1 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,119 @@
 | 
				
			|||||||
 | 
					package com.ruoyi.demo.domain.vo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 | 
				
			||||||
 | 
					import com.alibaba.excel.annotation.ExcelProperty;
 | 
				
			||||||
 | 
					import com.ruoyi.common.annotation.ExcelDictFormat;
 | 
				
			||||||
 | 
					import com.ruoyi.common.annotation.ExcelEnumFormat;
 | 
				
			||||||
 | 
					import com.ruoyi.common.convert.ExcelDictConvert;
 | 
				
			||||||
 | 
					import com.ruoyi.common.convert.ExcelEnumConvert;
 | 
				
			||||||
 | 
					import com.ruoyi.common.core.validate.AddGroup;
 | 
				
			||||||
 | 
					import com.ruoyi.common.core.validate.EditGroup;
 | 
				
			||||||
 | 
					import com.ruoyi.common.enums.UserStatus;
 | 
				
			||||||
 | 
					import lombok.AllArgsConstructor;
 | 
				
			||||||
 | 
					import lombok.Data;
 | 
				
			||||||
 | 
					import lombok.NoArgsConstructor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.validation.constraints.NotEmpty;
 | 
				
			||||||
 | 
					import javax.validation.constraints.NotNull;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 带有下拉选的Excel导出
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Emil.Zhang
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Data
 | 
				
			||||||
 | 
					@ExcelIgnoreUnannotated
 | 
				
			||||||
 | 
					@AllArgsConstructor
 | 
				
			||||||
 | 
					@NoArgsConstructor
 | 
				
			||||||
 | 
					public class ExportDemoVo {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final long serialVersionUID = 1L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 用户昵称
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @ExcelProperty(value = "用户名", index = 0)
 | 
				
			||||||
 | 
					    @NotEmpty(message = "用户名不能为空", groups = AddGroup.class)
 | 
				
			||||||
 | 
					    private String nickName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 用户类型
 | 
				
			||||||
 | 
					     * </p>
 | 
				
			||||||
 | 
					     * 使用ExcelEnumFormat注解需要进行下拉选的部分
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @ExcelProperty(value = "用户类型", index = 1, converter = ExcelEnumConvert.class)
 | 
				
			||||||
 | 
					    @ExcelEnumFormat(enumClass = UserStatus.class, textField = "info")
 | 
				
			||||||
 | 
					    @NotEmpty(message = "用户类型不能为空", groups = AddGroup.class)
 | 
				
			||||||
 | 
					    private String userStatus;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 性别
 | 
				
			||||||
 | 
					     * <p>
 | 
				
			||||||
 | 
					     * 使用ExcelDictFormat注解需要进行下拉选的部分
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @ExcelProperty(value = "性别", index = 2, converter = ExcelDictConvert.class)
 | 
				
			||||||
 | 
					    @ExcelDictFormat(dictType = "sys_user_sex")
 | 
				
			||||||
 | 
					    @NotEmpty(message = "性别不能为空", groups = AddGroup.class)
 | 
				
			||||||
 | 
					    private String gender;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 手机号
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @ExcelProperty(value = "手机号", index = 3)
 | 
				
			||||||
 | 
					    @NotEmpty(message = "手机号不能为空", groups = AddGroup.class)
 | 
				
			||||||
 | 
					    private String phoneNumber;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Email
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @ExcelProperty(value = "Email", index = 4)
 | 
				
			||||||
 | 
					    @NotEmpty(message = "Email不能为空", groups = AddGroup.class)
 | 
				
			||||||
 | 
					    private String email;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 省
 | 
				
			||||||
 | 
					     * <p>
 | 
				
			||||||
 | 
					     * 级联下拉,仅判断是否选了
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @ExcelProperty(value = "省", index = 5)
 | 
				
			||||||
 | 
					    @NotNull(message = "省不能为空", groups = AddGroup.class)
 | 
				
			||||||
 | 
					    private String province;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 数据库中的省ID
 | 
				
			||||||
 | 
					     * </p>
 | 
				
			||||||
 | 
					     * 处理完毕后再判断是否市正确的值
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @NotNull(message = "请勿手动输入", groups = EditGroup.class)
 | 
				
			||||||
 | 
					    private Integer provinceId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 市
 | 
				
			||||||
 | 
					     * <p>
 | 
				
			||||||
 | 
					     * 级联下拉
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @ExcelProperty(value = "市", index = 6)
 | 
				
			||||||
 | 
					    @NotNull(message = "市不能为空", groups = AddGroup.class)
 | 
				
			||||||
 | 
					    private String city;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 数据库中的市ID
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @NotNull(message = "请勿手动输入", groups = EditGroup.class)
 | 
				
			||||||
 | 
					    private Integer cityId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 县
 | 
				
			||||||
 | 
					     * <p>
 | 
				
			||||||
 | 
					     * 级联下拉
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @ExcelProperty(value = "县", index = 7)
 | 
				
			||||||
 | 
					    @NotNull(message = "县不能为空", groups = AddGroup.class)
 | 
				
			||||||
 | 
					    private String area;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 数据库中的县ID
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @NotNull(message = "请勿手动输入", groups = EditGroup.class)
 | 
				
			||||||
 | 
					    private Integer areaId;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					package com.ruoyi.demo.listener;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.hutool.core.util.NumberUtil;
 | 
				
			||||||
 | 
					import com.alibaba.excel.context.AnalysisContext;
 | 
				
			||||||
 | 
					import com.ruoyi.common.core.validate.AddGroup;
 | 
				
			||||||
 | 
					import com.ruoyi.common.core.validate.EditGroup;
 | 
				
			||||||
 | 
					import com.ruoyi.common.excel.DefaultExcelListener;
 | 
				
			||||||
 | 
					import com.ruoyi.common.excel.DropDownOptions;
 | 
				
			||||||
 | 
					import com.ruoyi.common.utils.ValidatorUtils;
 | 
				
			||||||
 | 
					import com.ruoyi.demo.domain.vo.ExportDemoVo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Excel带下拉框的解析处理器
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Emil.Zhang
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class ExportDemoListener extends DefaultExcelListener<ExportDemoVo> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public ExportDemoListener() {
 | 
				
			||||||
 | 
					        // 显示使用构造函数,否则将导致空指针
 | 
				
			||||||
 | 
					        super(true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void invoke(ExportDemoVo data, AnalysisContext context) {
 | 
				
			||||||
 | 
					        // 先校验必填
 | 
				
			||||||
 | 
					        ValidatorUtils.validate(data, AddGroup.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 处理级联下拉的部分
 | 
				
			||||||
 | 
					        String province = data.getProvince();
 | 
				
			||||||
 | 
					        String city = data.getCity();
 | 
				
			||||||
 | 
					        String area = data.getArea();
 | 
				
			||||||
 | 
					        // 本行用户选择的省
 | 
				
			||||||
 | 
					        List<String> thisRowSelectedProvinceOption = DropDownOptions.analyzeOptionValue(province);
 | 
				
			||||||
 | 
					        if (thisRowSelectedProvinceOption.size() == 2) {
 | 
				
			||||||
 | 
					            String provinceIdStr = thisRowSelectedProvinceOption.get(1);
 | 
				
			||||||
 | 
					            if (NumberUtil.isNumber(provinceIdStr)) {
 | 
				
			||||||
 | 
					                // 严格要求数据的话可以在这里做与数据库相关的判断
 | 
				
			||||||
 | 
					                // 例如判断省信息是否在数据库中存在等,建议结合RedisCache做缓存10s,减少数据库调用
 | 
				
			||||||
 | 
					                data.setProvinceId(Integer.parseInt(provinceIdStr));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // 本行用户选择的市
 | 
				
			||||||
 | 
					        List<String> thisRowSelectedCityOption = DropDownOptions.analyzeOptionValue(city);
 | 
				
			||||||
 | 
					        if (thisRowSelectedCityOption.size() == 2) {
 | 
				
			||||||
 | 
					            String cityIdStr = thisRowSelectedCityOption.get(1);
 | 
				
			||||||
 | 
					            if (NumberUtil.isNumber(cityIdStr)) {
 | 
				
			||||||
 | 
					                data.setCityId(Integer.parseInt(cityIdStr));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // 本行用户选择的县
 | 
				
			||||||
 | 
					        List<String> thisRowSelectedAreaOption = DropDownOptions.analyzeOptionValue(area);
 | 
				
			||||||
 | 
					        if (thisRowSelectedAreaOption.size() == 2) {
 | 
				
			||||||
 | 
					            String areaIdStr = thisRowSelectedAreaOption.get(1);
 | 
				
			||||||
 | 
					            if (NumberUtil.isNumber(areaIdStr)) {
 | 
				
			||||||
 | 
					                data.setAreaId(Integer.parseInt(areaIdStr));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 处理完毕以后判断是否符合规则
 | 
				
			||||||
 | 
					        ValidatorUtils.validate(data, EditGroup.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 添加到处理结果中
 | 
				
			||||||
 | 
					        getExcelResult().getList().add(data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					package com.ruoyi.demo.service;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.servlet.http.HttpServletResponse;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 导出下拉框Excel示例
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Emil.Zhang
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public interface IExportExcelService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 导出下拉框
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param response /
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    void exportWithOptions(HttpServletResponse response);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,262 @@
 | 
				
			|||||||
 | 
					package com.ruoyi.demo.service.impl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.ruoyi.common.enums.UserStatus;
 | 
				
			||||||
 | 
					import com.ruoyi.common.excel.DropDownOptions;
 | 
				
			||||||
 | 
					import com.ruoyi.common.utils.poi.ExcelUtil;
 | 
				
			||||||
 | 
					import com.ruoyi.demo.domain.vo.ExportDemoVo;
 | 
				
			||||||
 | 
					import com.ruoyi.demo.service.IExportExcelService;
 | 
				
			||||||
 | 
					import lombok.Data;
 | 
				
			||||||
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Service;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.servlet.http.HttpServletResponse;
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 导出下拉框Excel示例
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Emil.Zhang
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Service
 | 
				
			||||||
 | 
					@RequiredArgsConstructor
 | 
				
			||||||
 | 
					public class ExportExcelServiceImpl implements IExportExcelService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void exportWithOptions(HttpServletResponse response) {
 | 
				
			||||||
 | 
					        // 创建表格数据,业务中一般通过数据库查询
 | 
				
			||||||
 | 
					        List<ExportDemoVo> excelDataList = new ArrayList<>();
 | 
				
			||||||
 | 
					        for (int i = 0; i < 3; i++) {
 | 
				
			||||||
 | 
					            // 模拟数据库中的一条数据
 | 
				
			||||||
 | 
					            ExportDemoVo everyRowData = new ExportDemoVo();
 | 
				
			||||||
 | 
					            everyRowData.setNickName("用户-" + i);
 | 
				
			||||||
 | 
					            everyRowData.setUserStatus(UserStatus.OK.getCode());
 | 
				
			||||||
 | 
					            everyRowData.setGender("1");
 | 
				
			||||||
 | 
					            everyRowData.setPhoneNumber(String.format("175%08d", i));
 | 
				
			||||||
 | 
					            everyRowData.setEmail(String.format("175%08d", i) + "@163.com");
 | 
				
			||||||
 | 
					            everyRowData.setProvinceId(i);
 | 
				
			||||||
 | 
					            everyRowData.setCityId(i);
 | 
				
			||||||
 | 
					            everyRowData.setAreaId(i);
 | 
				
			||||||
 | 
					            excelDataList.add(everyRowData);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 通过@ExcelIgnoreUnannotated配合@ExcelProperty合理显示需要的列
 | 
				
			||||||
 | 
					        // 并通过@DropDown注解指定下拉值,或者通过创建ExcelOptions来指定下拉框
 | 
				
			||||||
 | 
					        // 使用ExcelOptions时建议指定列index,防止出现下拉列解析不对齐
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 首先从数据库中查询下拉框内的可选项
 | 
				
			||||||
 | 
					        // 这里模拟查询结果
 | 
				
			||||||
 | 
					        List<DemoCityData> provinceList = getProvinceList();
 | 
				
			||||||
 | 
					        List<DemoCityData> cityList = getCityList(provinceList);
 | 
				
			||||||
 | 
					        List<DemoCityData> areaList = getAreaList(cityList);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 把所有的结果提取为规范的下拉选可选项
 | 
				
			||||||
 | 
					        // 规范的一级省,用于级联省-市
 | 
				
			||||||
 | 
					        List<String> provinceOptions =
 | 
				
			||||||
 | 
					            provinceList.stream()
 | 
				
			||||||
 | 
					                .map(everyProvince ->
 | 
				
			||||||
 | 
					                    DropDownOptions.createOptionValue(
 | 
				
			||||||
 | 
					                        everyProvince.getName(),
 | 
				
			||||||
 | 
					                        everyProvince.getId()
 | 
				
			||||||
 | 
					                    ))
 | 
				
			||||||
 | 
					                .collect(Collectors.toList());
 | 
				
			||||||
 | 
					        // 规范的二级市,用于级联省-市
 | 
				
			||||||
 | 
					        Map<String, List<String>> provinceToCityOptions = new HashMap<>();
 | 
				
			||||||
 | 
					        cityList.stream()
 | 
				
			||||||
 | 
					            .collect(Collectors.groupingBy(DemoCityData::getPData))
 | 
				
			||||||
 | 
					            .forEach((province, thisProvinceCityList) -> {
 | 
				
			||||||
 | 
					                // 每个省下二级的市可选项
 | 
				
			||||||
 | 
					                provinceToCityOptions.put(
 | 
				
			||||||
 | 
					                    DropDownOptions.createOptionValue(province.getName(), province.getId()),
 | 
				
			||||||
 | 
					                    thisProvinceCityList.stream()
 | 
				
			||||||
 | 
					                        .map(everyCity ->
 | 
				
			||||||
 | 
					                            DropDownOptions.createOptionValue(everyCity.getName(), everyCity.getId())
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        .collect(Collectors.toList())
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 规范的一级市,用于级联市-县
 | 
				
			||||||
 | 
					        List<String> cityOptions = cityList.stream()
 | 
				
			||||||
 | 
					            .map(everyCity ->
 | 
				
			||||||
 | 
					                DropDownOptions.createOptionValue(
 | 
				
			||||||
 | 
					                    everyCity.getName(),
 | 
				
			||||||
 | 
					                    everyCity.getId()
 | 
				
			||||||
 | 
					                ))
 | 
				
			||||||
 | 
					            .collect(Collectors.toList());
 | 
				
			||||||
 | 
					        // 规范的二级县,用于级联市-县
 | 
				
			||||||
 | 
					        Map<String, List<String>> cityToAreaOptions = new HashMap<>();
 | 
				
			||||||
 | 
					        areaList.stream()
 | 
				
			||||||
 | 
					            .collect(Collectors.groupingBy(DemoCityData::getPData))
 | 
				
			||||||
 | 
					            .forEach((city, thisCityAreaList) -> {
 | 
				
			||||||
 | 
					                // 每个市下二级的县可选项
 | 
				
			||||||
 | 
					                cityToAreaOptions.put(
 | 
				
			||||||
 | 
					                    DropDownOptions.createOptionValue(city.getName(), city.getId()),
 | 
				
			||||||
 | 
					                    thisCityAreaList.stream()
 | 
				
			||||||
 | 
					                        .map(everyArea ->
 | 
				
			||||||
 | 
					                            DropDownOptions.createOptionValue(everyArea.getName(), everyArea.getId())
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        .collect(Collectors.toList())
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 因为省市县三个都是联动,省级联市,市级联县,因此需要创建两个级联下拉,分别以省和市为判断依据创建
 | 
				
			||||||
 | 
					        // 创建省-市级联
 | 
				
			||||||
 | 
					        DropDownOptions provinceToCity = new DropDownOptions();
 | 
				
			||||||
 | 
					        // 以省为一级
 | 
				
			||||||
 | 
					        provinceToCity.setIndex(5);
 | 
				
			||||||
 | 
					        // 以市为二级
 | 
				
			||||||
 | 
					        provinceToCity.setNextIndex(6);
 | 
				
			||||||
 | 
					        // 补充省的内容以及市的内容
 | 
				
			||||||
 | 
					        provinceToCity.setOptions(provinceOptions);
 | 
				
			||||||
 | 
					        provinceToCity.setNextOptions(provinceToCityOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 创建市-县级联
 | 
				
			||||||
 | 
					        DropDownOptions cityToArea = new DropDownOptions();
 | 
				
			||||||
 | 
					        cityToArea.setIndex(6);
 | 
				
			||||||
 | 
					        cityToArea.setNextIndex(7);
 | 
				
			||||||
 | 
					        cityToArea.setOptions(cityOptions);
 | 
				
			||||||
 | 
					        cityToArea.setNextOptions(cityToAreaOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 把所有的下拉框存储
 | 
				
			||||||
 | 
					        List<DropDownOptions> options = new ArrayList<>();
 | 
				
			||||||
 | 
					        options.add(provinceToCity);
 | 
				
			||||||
 | 
					        options.add(cityToArea);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 到此为止所有的下拉框可选项已全部配置完毕
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 接下来需要将Excel中的展示数据转换为对应的下拉选
 | 
				
			||||||
 | 
					        List<ExportDemoVo> outList = excelDataList.stream().map(everyRowData -> {
 | 
				
			||||||
 | 
					            // 只需要处理没有使用@ExcelDictFormat注解的下拉框
 | 
				
			||||||
 | 
					            // 一般来说,可以直接在数据库查询即查询出省市县信息,这里通过模拟操作赋值
 | 
				
			||||||
 | 
					            everyRowData.setProvince(buildOptions(provinceList, everyRowData.getProvinceId()));
 | 
				
			||||||
 | 
					            everyRowData.setCity(buildOptions(cityList, everyRowData.getCityId()));
 | 
				
			||||||
 | 
					            everyRowData.setArea(buildOptions(areaList, everyRowData.getAreaId()));
 | 
				
			||||||
 | 
					            return everyRowData;
 | 
				
			||||||
 | 
					        }).collect(Collectors.toList());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ExcelUtil.exportExcel(outList, "下拉框示例", ExportDemoVo.class, response, options);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private String buildOptions(List<DemoCityData> cityDataList, Integer id) {
 | 
				
			||||||
 | 
					        Map<Integer, List<DemoCityData>> groupByIdMap =
 | 
				
			||||||
 | 
					            cityDataList.stream().collect(Collectors.groupingBy(DemoCityData::getId));
 | 
				
			||||||
 | 
					        if (groupByIdMap.containsKey(id)) {
 | 
				
			||||||
 | 
					            DemoCityData demoCityData = groupByIdMap.get(id).get(0);
 | 
				
			||||||
 | 
					            return DropDownOptions.createOptionValue(demoCityData.getName(), demoCityData.getId());
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return "";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 模拟查询数据库操作
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return /
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private List<DemoCityData> getProvinceList() {
 | 
				
			||||||
 | 
					        List<DemoCityData> provinceList = new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 实际业务中一般采用数据库读取的形式,这里直接拼接创建
 | 
				
			||||||
 | 
					        provinceList.add(new DemoCityData(0, null, "安徽省"));
 | 
				
			||||||
 | 
					        provinceList.add(new DemoCityData(1, null, "江苏省"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return provinceList;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 模拟查找数据库操作,需要连带查询出省的数据
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param provinceList 模拟的父省数据
 | 
				
			||||||
 | 
					     * @return /
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private List<DemoCityData> getCityList(List<DemoCityData> provinceList) {
 | 
				
			||||||
 | 
					        List<DemoCityData> cityList = new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 实际业务中一般采用数据库读取的形式,这里直接拼接创建
 | 
				
			||||||
 | 
					        cityList.add(new DemoCityData(0, 0, "合肥市"));
 | 
				
			||||||
 | 
					        cityList.add(new DemoCityData(1, 0, "芜湖市"));
 | 
				
			||||||
 | 
					        cityList.add(new DemoCityData(2, 1, "南京市"));
 | 
				
			||||||
 | 
					        cityList.add(new DemoCityData(3, 1, "无锡市"));
 | 
				
			||||||
 | 
					        cityList.add(new DemoCityData(4, 1, "徐州市"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        selectParentData(provinceList, cityList);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return cityList;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 模拟查找数据库操作,需要连带查询出市的数据
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param cityList 模拟的父市数据
 | 
				
			||||||
 | 
					     * @return /
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private List<DemoCityData> getAreaList(List<DemoCityData> cityList) {
 | 
				
			||||||
 | 
					        List<DemoCityData> areaList = new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 实际业务中一般采用数据库读取的形式,这里直接拼接创建
 | 
				
			||||||
 | 
					        areaList.add(new DemoCityData(0, 0, "瑶海区"));
 | 
				
			||||||
 | 
					        areaList.add(new DemoCityData(1, 0, "庐江区"));
 | 
				
			||||||
 | 
					        areaList.add(new DemoCityData(2, 1, "南宁县"));
 | 
				
			||||||
 | 
					        areaList.add(new DemoCityData(3, 1, "镜湖区"));
 | 
				
			||||||
 | 
					        areaList.add(new DemoCityData(4, 2, "玄武区"));
 | 
				
			||||||
 | 
					        areaList.add(new DemoCityData(5, 2, "秦淮区"));
 | 
				
			||||||
 | 
					        areaList.add(new DemoCityData(6, 3, "宜兴市"));
 | 
				
			||||||
 | 
					        areaList.add(new DemoCityData(7, 3, "新吴区"));
 | 
				
			||||||
 | 
					        areaList.add(new DemoCityData(8, 4, "鼓楼区"));
 | 
				
			||||||
 | 
					        areaList.add(new DemoCityData(9, 4, "丰县"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        selectParentData(cityList, areaList);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return areaList;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 模拟数据库的查询父数据操作
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param parentList /
 | 
				
			||||||
 | 
					     * @param sonList    /
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private void selectParentData(List<DemoCityData> parentList, List<DemoCityData> sonList) {
 | 
				
			||||||
 | 
					        Map<Integer, List<DemoCityData>> parentGroupByIdMap =
 | 
				
			||||||
 | 
					            parentList.stream().collect(Collectors.groupingBy(DemoCityData::getId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sonList.forEach(everySon -> {
 | 
				
			||||||
 | 
					            if (parentGroupByIdMap.containsKey(everySon.getPid())) {
 | 
				
			||||||
 | 
					                everySon.setPData(parentGroupByIdMap.get(everySon.getPid()).get(0));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 模拟的数据库省市县
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @Data
 | 
				
			||||||
 | 
					    private static class DemoCityData {
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * 数据库id字段
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        private Integer id;
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * 数据库pid字段
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        private Integer pid;
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * 数据库name字段
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        private String name;
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * MyBatisPlus连带查询父数据
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        private DemoCityData pData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public DemoCityData(Integer id, Integer pid, String name) {
 | 
				
			||||||
 | 
					            this.id = id;
 | 
				
			||||||
 | 
					            this.pid = pid;
 | 
				
			||||||
 | 
					            this.name = name;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -148,7 +148,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
 | 
				
			|||||||
        List<SysDictData> dictDataList = dictDataMapper.selectList(
 | 
					        List<SysDictData> dictDataList = dictDataMapper.selectList(
 | 
				
			||||||
            new LambdaQueryWrapper<SysDictData>().eq(SysDictData::getStatus, UserConstants.DICT_NORMAL));
 | 
					            new LambdaQueryWrapper<SysDictData>().eq(SysDictData::getStatus, UserConstants.DICT_NORMAL));
 | 
				
			||||||
        Map<String, List<SysDictData>> dictDataMap = StreamUtils.groupByKey(dictDataList, SysDictData::getDictType);
 | 
					        Map<String, List<SysDictData>> dictDataMap = StreamUtils.groupByKey(dictDataList, SysDictData::getDictType);
 | 
				
			||||||
        dictDataMap.forEach((k,v) -> {
 | 
					        dictDataMap.forEach((k, v) -> {
 | 
				
			||||||
            List<SysDictData> dictList = StreamUtils.sorted(v, Comparator.comparing(SysDictData::getDictSort));
 | 
					            List<SysDictData> dictList = StreamUtils.sorted(v, Comparator.comparing(SysDictData::getDictSort));
 | 
				
			||||||
            CacheUtils.put(CacheNames.SYS_DICT, k, dictList);
 | 
					            CacheUtils.put(CacheNames.SYS_DICT, k, dictList);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
@@ -279,4 +279,16 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public Map<String, String> getAllDictByDictType(String dictType) {
 | 
				
			||||||
 | 
					        List<SysDictData> thisDictTypeDataList = selectDictDataByType(dictType);
 | 
				
			||||||
 | 
					        Map<String, String> dictMap = null;
 | 
				
			||||||
 | 
					        for (SysDictData everyDictData : thisDictTypeDataList) {
 | 
				
			||||||
 | 
					            if (ObjectUtil.isNull(dictMap)) {
 | 
				
			||||||
 | 
					                dictMap = new HashMap<>();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            dictMap.put(everyDictData.getDictValue(), everyDictData.getDictLabel());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return dictMap;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user