From 72882374be15d1e6a23ccc7d0c8ad3e809caf88a Mon Sep 17 00:00:00 2001 From: "Emil.Zhang" <356141959@qq.com> Date: Sat, 13 May 2023 14:17:59 +0000 Subject: [PATCH] =?UTF-8?q?!345=20=E5=A2=9E=E5=8A=A0Excel=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E8=A1=A8=E6=A0=BC=E6=97=B6=E7=BA=A7=E8=81=94=E4=B8=8B?= =?UTF-8?q?=E6=8B=89=E9=80=89=E9=A1=B9=E5=8A=9F=E8=83=BD=20*=20fixed(Excel?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E7=B1=BB=E9=87=8D=E8=BD=BD):=20*=20fixed(?= =?UTF-8?q?=E5=AD=97=E5=85=B8=E6=8E=A5=E5=8F=A3=E8=80=A6=E5=90=88=E9=97=AE?= =?UTF-8?q?=E9=A2=98):=20*=20fixed(=E8=B0=83=E6=95=B4=E6=8E=A5=E5=8F=A3):?= =?UTF-8?q?=20*=20fixed(=E5=88=87=E6=8D=A2=E6=9D=A1=E4=BB=B6=E4=B8=8D?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE):=20*=20feat(=E4=BC=98=E5=8C=96=E6=B3=A8?= =?UTF-8?q?=E8=A7=A3|=E5=8F=8D=E5=90=91=E8=A7=A3=E6=9E=90=E5=A4=B1?= =?UTF-8?q?=E6=95=88):=20*=20feat(=E5=A2=9E=E5=8A=A0=E6=B3=A8=E9=87=8A|?= =?UTF-8?q?=E7=BC=96=E5=86=99Demo|=E4=BF=AE=E5=A4=8Dbug):=20*=20feat(Excel?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E9=99=84=E5=B8=A6=E6=9C=89=E4=B8=8B=E6=8B=89?= =?UTF-8?q?=E6=A1=86):?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/convert/ExcelEnumConvert.java | 30 +- .../common/core/service/DictService.java | 9 + .../ruoyi/common/excel/DropDownOptions.java | 91 ++++ .../ruoyi/common/excel/ExcelDownHandler.java | 391 ++++++++++++++++++ .../com/ruoyi/common/utils/poi/ExcelUtil.java | 80 +++- .../demo/controller/TestExcelController.java | 37 +- .../ruoyi/demo/domain/vo/ExportDemoVo.java | 119 ++++++ .../demo/listener/ExportDemoListener.java | 68 +++ .../demo/service/IExportExcelService.java | 18 + .../service/impl/ExportExcelServiceImpl.java | 262 ++++++++++++ .../service/impl/SysDictTypeServiceImpl.java | 14 +- 11 files changed, 1106 insertions(+), 13 deletions(-) create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/excel/DropDownOptions.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/excel/ExcelDownHandler.java create mode 100644 ruoyi-demo/src/main/java/com/ruoyi/demo/domain/vo/ExportDemoVo.java create mode 100644 ruoyi-demo/src/main/java/com/ruoyi/demo/listener/ExportDemoListener.java create mode 100644 ruoyi-demo/src/main/java/com/ruoyi/demo/service/IExportExcelService.java create mode 100644 ruoyi-demo/src/main/java/com/ruoyi/demo/service/impl/ExportExcelServiceImpl.java diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelEnumConvert.java b/ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelEnumConvert.java index c69d90cfb..2069c154f 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelEnumConvert.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/convert/ExcelEnumConvert.java @@ -37,14 +37,34 @@ public class ExcelEnumConvert implements Converter { @Override 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; } - Map enumValueMap = beforeConvert(contentProperty); - String textValue = enumValueMap.get(codeValue); - return Convert.convert(contentProperty.getField().getType(), textValue); + Map enumCodeToTextMap = beforeConvert(contentProperty); + // 从Java输出至Excel是code转text + // 因此从Excel转Java应该将text与code对调 + Map 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 diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/service/DictService.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/service/DictService.java index b334c82af..a4dbfd5b7 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/service/DictService.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/service/DictService.java @@ -1,5 +1,7 @@ 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); + /** + * 获取字典下所有的字典值与标签 + * + * @param dictType 字典类型 + * @return dictValue为key,dictLabel为值组成的Map + */ + Map getAllDictByDictType(String dictType); } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/excel/DropDownOptions.java b/ruoyi-common/src/main/java/com/ruoyi/common/excel/DropDownOptions.java new file mode 100644 index 000000000..f15d2f0bb --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/excel/DropDownOptions.java @@ -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; + +/** + *

Excel下拉可选项

+ * 注意:为确保下拉框解析正确,传值务必使用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 options = new ArrayList<>(); + /** + * 二级下拉所包含的数据Map + *

以每一个一级选项值为Key,每个一级选项对应的二级数据为Value

+ */ + private Map> nextOptions = new HashMap<>(); + /** + * 分隔符 + */ + private static final String DELIMITER = "_"; + + /** + * 创建只有一级的下拉选 + */ + public DropDownOptions(int index, List options) { + this.index = index; + this.options = options; + } + + /** + *

创建每个选项可选值

+ *

注意:不能以数字,特殊符号开头,选项中不可以包含任何运算符号

+ * + * @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 analyzeOptionValue(String option) { + return StrUtil.split(option, DELIMITER, true, true); + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/excel/ExcelDownHandler.java b/ruoyi-common/src/main/java/com/ruoyi/common/excel/ExcelDownHandler.java new file mode 100644 index 000000000..5c5f997d7 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/excel/ExcelDownHandler.java @@ -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; + +/** + *

Excel表格下拉选操作

+ * 考虑到下拉选过多可能导致Excel打开缓慢的问题,只校验前1000行 + *

+ * 即只有前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; + /** + * 当前单选进度 + */ + private int currentOptionsColumnIndex; + /** + * 当前联动选择进度 + */ + private int currentLinkedOptionsSheetIndex; + private final DictService dictService; + + public ExcelDownHandler(List options) { + this.dropDownOptions = options; + this.currentOptionsColumnIndex = 0; + this.currentLinkedOptionsSheetIndex = 0; + this.dictService = SpringUtils.getBean(DictService.class); + } + + /** + *

开始创建下拉数据

+ * 1.通过解析传入的@ExcelProperty同级是否标注有@DropDown选项 + * 如果有且设置了value值,则将其直接置为下拉可选项 + *

+ * 2.或者在调用ExcelUtil时指定了可选项,将依据传入的可选项做下拉 + *

+ * 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 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()); + } + }); + } + + /** + *

简单下拉框

+ * 直接将可选项拼接为指定列的数据校验值 + * + * @param celIndex 列index + * @param value 下拉选可选值 + */ + private void dropDownWithSimple(DataValidationHelper helper, Sheet sheet, Integer celIndex, List value) { + if (ObjectUtil.isEmpty(value)) { + return; + } + this.markOptionsToSheet(helper, sheet, celIndex, helper.createExplicitListConstraint(value.toArray(new String[0]))); + } + + /** + *

额外表格形式的级联下拉框

+ * + * @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 firstOptions = options.getOptions(); + Map> 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 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++; + } + + /** + *

额外表格形式的普通下拉框

+ * 由于下拉框可选值数量过多,为提升Excel打开效率,使用额外表格形式做下拉 + * + * @param celIndex 下拉选 + * @param value 下拉选可选值 + */ + private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List 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); + } + + /** + *

依据列index获取列名英文

+ * 依据列index转换为Excel中的列名英文 + *

例如第1列,index为0,解析出来为A列

+ * 第27列,index为26,解析为AA列 + *

第28列,index为27,解析为AB列

+ * + * @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; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java index cf2ba885c..402c4e85b 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java @@ -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.style.column.LongestMatchColumnWidthStyleStrategy; import com.ruoyi.common.convert.ExcelBigNumberConvert; -import com.ruoyi.common.excel.CellMergeStrategy; -import com.ruoyi.common.excel.DefaultExcelListener; -import com.ruoyi.common.excel.ExcelListener; -import com.ruoyi.common.excel.ExcelResult; +import com.ruoyi.common.excel.*; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.file.FileUtils; import lombok.AccessLevel; @@ -94,6 +91,25 @@ public class ExcelUtil { } } + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param response 响应体 + * @param options 级联下拉选 + */ + public static void exportExcel(List list, String sheetName, Class clazz, HttpServletResponse response, List options) { + try { + resetResponse(sheetName, response); + ServletOutputStream os = response.getOutputStream(); + exportExcel(list, sheetName, clazz, false, os, options); + } catch (IOException e) { + throw new RuntimeException("导出Excel异常"); + } + } + /** * 导出excel * @@ -113,6 +129,26 @@ public class ExcelUtil { } } + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param merge 是否合并单元格 + * @param response 响应体 + * @param options 级联下拉选 + */ + public static void exportExcel(List list, String sheetName, Class clazz, boolean merge, HttpServletResponse response, List options) { + try { + resetResponse(sheetName, response); + ServletOutputStream os = response.getOutputStream(); + exportExcel(list, sheetName, clazz, merge, os, options); + } catch (IOException e) { + throw new RuntimeException("导出Excel异常"); + } + } + /** * 导出excel * @@ -125,6 +161,19 @@ public class ExcelUtil { exportExcel(list, sheetName, clazz, false, os); } + /** + * 导出excel + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param clazz 实体类 + * @param os 输出流 + * @param options 级联下拉选内容 + */ + public static void exportExcel(List list, String sheetName, Class clazz, OutputStream os, List options) { + exportExcel(list, sheetName, clazz, false, os, options); + } + /** * 导出excel * @@ -149,6 +198,29 @@ public class ExcelUtil { builder.doWrite(list); } + /** + * 导出带有下拉框的Excel表格 + * + * @param options 下拉框数据 + */ + public static void exportExcel(List list, String sheetName, Class clazz, + boolean merge, OutputStream os, List 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); + } + /** * 单表多数据模板导出 模板格式为 {.属性} * diff --git a/ruoyi-demo/src/main/java/com/ruoyi/demo/controller/TestExcelController.java b/ruoyi-demo/src/main/java/com/ruoyi/demo/controller/TestExcelController.java index 51b81c582..6fba15ea6 100644 --- a/ruoyi-demo/src/main/java/com/ruoyi/demo/controller/TestExcelController.java +++ b/ruoyi-demo/src/main/java/com/ruoyi/demo/controller/TestExcelController.java @@ -1,12 +1,18 @@ package com.ruoyi.demo.controller; +import cn.dev33.satoken.annotation.SaIgnore; import cn.hutool.core.collection.CollUtil; +import com.ruoyi.common.excel.ExcelResult; 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.Data; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; @@ -20,9 +26,12 @@ import java.util.Map; * @author Lion Li */ @RestController +@RequiredArgsConstructor @RequestMapping("/demo/excel") public class TestExcelController { + private final IExportExcelService exportExcelService; + /** * 单列表多数据 */ @@ -76,6 +85,28 @@ public class TestExcelController { 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 importWithOptions(@RequestPart("file") MultipartFile file) throws Exception { + // 处理解析结果 + ExcelResult excelResult = ExcelUtil.importExcel(file.getInputStream(), ExportDemoVo.class, new ExportDemoListener()); + return excelResult.getList(); + } + @Data @AllArgsConstructor static class TestObj1 { diff --git a/ruoyi-demo/src/main/java/com/ruoyi/demo/domain/vo/ExportDemoVo.java b/ruoyi-demo/src/main/java/com/ruoyi/demo/domain/vo/ExportDemoVo.java new file mode 100644 index 000000000..e417dbc4c --- /dev/null +++ b/ruoyi-demo/src/main/java/com/ruoyi/demo/domain/vo/ExportDemoVo.java @@ -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; + + /** + * 用户类型 + *

+ * 使用ExcelEnumFormat注解需要进行下拉选的部分 + */ + @ExcelProperty(value = "用户类型", index = 1, converter = ExcelEnumConvert.class) + @ExcelEnumFormat(enumClass = UserStatus.class, textField = "info") + @NotEmpty(message = "用户类型不能为空", groups = AddGroup.class) + private String userStatus; + + /** + * 性别 + *

+ * 使用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; + + /** + * 省 + *

+ * 级联下拉,仅判断是否选了 + */ + @ExcelProperty(value = "省", index = 5) + @NotNull(message = "省不能为空", groups = AddGroup.class) + private String province; + + /** + * 数据库中的省ID + *

+ * 处理完毕后再判断是否市正确的值 + */ + @NotNull(message = "请勿手动输入", groups = EditGroup.class) + private Integer provinceId; + + /** + * 市 + *

+ * 级联下拉 + */ + @ExcelProperty(value = "市", index = 6) + @NotNull(message = "市不能为空", groups = AddGroup.class) + private String city; + + /** + * 数据库中的市ID + */ + @NotNull(message = "请勿手动输入", groups = EditGroup.class) + private Integer cityId; + + /** + * 县 + *

+ * 级联下拉 + */ + @ExcelProperty(value = "县", index = 7) + @NotNull(message = "县不能为空", groups = AddGroup.class) + private String area; + + /** + * 数据库中的县ID + */ + @NotNull(message = "请勿手动输入", groups = EditGroup.class) + private Integer areaId; +} diff --git a/ruoyi-demo/src/main/java/com/ruoyi/demo/listener/ExportDemoListener.java b/ruoyi-demo/src/main/java/com/ruoyi/demo/listener/ExportDemoListener.java new file mode 100644 index 000000000..95d7093f2 --- /dev/null +++ b/ruoyi-demo/src/main/java/com/ruoyi/demo/listener/ExportDemoListener.java @@ -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 { + + 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 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 thisRowSelectedCityOption = DropDownOptions.analyzeOptionValue(city); + if (thisRowSelectedCityOption.size() == 2) { + String cityIdStr = thisRowSelectedCityOption.get(1); + if (NumberUtil.isNumber(cityIdStr)) { + data.setCityId(Integer.parseInt(cityIdStr)); + } + } + // 本行用户选择的县 + List 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); + } +} diff --git a/ruoyi-demo/src/main/java/com/ruoyi/demo/service/IExportExcelService.java b/ruoyi-demo/src/main/java/com/ruoyi/demo/service/IExportExcelService.java new file mode 100644 index 000000000..d8f024784 --- /dev/null +++ b/ruoyi-demo/src/main/java/com/ruoyi/demo/service/IExportExcelService.java @@ -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); +} diff --git a/ruoyi-demo/src/main/java/com/ruoyi/demo/service/impl/ExportExcelServiceImpl.java b/ruoyi-demo/src/main/java/com/ruoyi/demo/service/impl/ExportExcelServiceImpl.java new file mode 100644 index 000000000..a0da4e69b --- /dev/null +++ b/ruoyi-demo/src/main/java/com/ruoyi/demo/service/impl/ExportExcelServiceImpl.java @@ -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 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 provinceList = getProvinceList(); + List cityList = getCityList(provinceList); + List areaList = getAreaList(cityList); + + // 把所有的结果提取为规范的下拉选可选项 + // 规范的一级省,用于级联省-市 + List provinceOptions = + provinceList.stream() + .map(everyProvince -> + DropDownOptions.createOptionValue( + everyProvince.getName(), + everyProvince.getId() + )) + .collect(Collectors.toList()); + // 规范的二级市,用于级联省-市 + Map> 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 cityOptions = cityList.stream() + .map(everyCity -> + DropDownOptions.createOptionValue( + everyCity.getName(), + everyCity.getId() + )) + .collect(Collectors.toList()); + // 规范的二级县,用于级联市-县 + Map> 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 options = new ArrayList<>(); + options.add(provinceToCity); + options.add(cityToArea); + + // 到此为止所有的下拉框可选项已全部配置完毕 + + // 接下来需要将Excel中的展示数据转换为对应的下拉选 + List 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 cityDataList, Integer id) { + Map> 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 getProvinceList() { + List provinceList = new ArrayList<>(); + + // 实际业务中一般采用数据库读取的形式,这里直接拼接创建 + provinceList.add(new DemoCityData(0, null, "安徽省")); + provinceList.add(new DemoCityData(1, null, "江苏省")); + + return provinceList; + } + + /** + * 模拟查找数据库操作,需要连带查询出省的数据 + * + * @param provinceList 模拟的父省数据 + * @return / + */ + private List getCityList(List provinceList) { + List 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 getAreaList(List cityList) { + List 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 parentList, List sonList) { + Map> 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; + } + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java index 0c897df61..6937f6734 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysDictTypeServiceImpl.java @@ -148,7 +148,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService List dictDataList = dictDataMapper.selectList( new LambdaQueryWrapper().eq(SysDictData::getStatus, UserConstants.DICT_NORMAL)); Map> dictDataMap = StreamUtils.groupByKey(dictDataList, SysDictData::getDictType); - dictDataMap.forEach((k,v) -> { + dictDataMap.forEach((k, v) -> { List dictList = StreamUtils.sorted(v, Comparator.comparing(SysDictData::getDictSort)); CacheUtils.put(CacheNames.SYS_DICT, k, dictList); }); @@ -279,4 +279,16 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService } } + @Override + public Map getAllDictByDictType(String dictType) { + List thisDictTypeDataList = selectDictDataByType(dictType); + Map dictMap = null; + for (SysDictData everyDictData : thisDictTypeDataList) { + if (ObjectUtil.isNull(dictMap)) { + dictMap = new HashMap<>(); + } + dictMap.put(everyDictData.getDictValue(), everyDictData.getDictLabel()); + } + return dictMap; + } }