mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-09-21 10:35:57 +08:00
feat(增加注释|编写Demo|修复bug):
1.移动DropDownOptions类至excel包下 2.增加对下拉选项逻辑方法的注释 3.编写Demo方法 4.修复一处Bug,该Bug将导致下拉选项校验位置不正确
This commit is contained in:
parent
16d692734f
commit
09ca147ab2
@ -0,0 +1,77 @@
|
|||||||
|
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<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建只有一级的下拉选
|
||||||
|
*/
|
||||||
|
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("_");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stringBuffer.toString().matches("^\\d_*$")) {
|
||||||
|
throw new ServiceException("禁止以数字开头");
|
||||||
|
}
|
||||||
|
return stringBuffer.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,15 +2,11 @@ package com.ruoyi.common.excel;
|
|||||||
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
import com.alibaba.excel.write.handler.SheetWriteHandler;
|
import com.alibaba.excel.write.handler.SheetWriteHandler;
|
||||||
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
|
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
|
||||||
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
|
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
|
||||||
import com.ruoyi.common.annotation.DropDown;
|
import com.ruoyi.common.annotation.DropDown;
|
||||||
import com.ruoyi.common.exception.ServiceException;
|
|
||||||
import com.ruoyi.common.utils.spring.SpringUtils;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.poi.ss.usermodel.*;
|
import org.apache.poi.ss.usermodel.*;
|
||||||
import org.apache.poi.ss.util.CellRangeAddressList;
|
import org.apache.poi.ss.util.CellRangeAddressList;
|
||||||
@ -21,7 +17,10 @@ import java.lang.reflect.Field;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Excel表格下拉选
|
* <h1>Excel表格下拉选操作</h1>
|
||||||
|
* 考虑到下拉选过多可能导致Excel打开缓慢的问题,只校验前1000行
|
||||||
|
* <p>
|
||||||
|
* 即只有前1000行的数据可以用下拉框,超出的自行通过限制数据量的形式,第二次输出
|
||||||
*
|
*
|
||||||
* @author Emil.Zhang
|
* @author Emil.Zhang
|
||||||
*/
|
*/
|
||||||
@ -29,7 +28,8 @@ import java.util.*;
|
|||||||
public class ExcelDownHandler implements SheetWriteHandler {
|
public class ExcelDownHandler implements SheetWriteHandler {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Excel表格中的列名
|
* Excel表格中的列名英文
|
||||||
|
* 仅为了解析列英文,禁止修改
|
||||||
*/
|
*/
|
||||||
private static final String EXCEL_COLUMN_NAME = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
private static final String EXCEL_COLUMN_NAME = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
/**
|
/**
|
||||||
@ -37,13 +37,9 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
*/
|
*/
|
||||||
private static final String OPTIONS_SHEET_NAME = "options";
|
private static final String OPTIONS_SHEET_NAME = "options";
|
||||||
/**
|
/**
|
||||||
* 联动选择数据Sheet名
|
* 联动选择数据Sheet名的头
|
||||||
*/
|
*/
|
||||||
private static final String LINKED_OPTIONS_SHEET_NAME = "linkedOptions";
|
private static final String LINKED_OPTIONS_SHEET_NAME = "linkedOptions";
|
||||||
/**
|
|
||||||
* 是否隐藏数据Sheet
|
|
||||||
*/
|
|
||||||
private static final Boolean HIDDEN_FLAG = !"dev".equals(SpringUtils.getActiveProfile());
|
|
||||||
/**
|
/**
|
||||||
* 下拉可选项
|
* 下拉可选项
|
||||||
*/
|
*/
|
||||||
@ -64,7 +60,13 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始创建下拉数据
|
* <h2>开始创建下拉数据</h2>
|
||||||
|
* 1.通过解析传入的@ExcelProperty同级是否标注有@DropDown选项
|
||||||
|
* 如果有且设置了value值,则将其直接置为下拉可选项
|
||||||
|
* <p>
|
||||||
|
* 2.或者在调用ExcelUtil时指定了可选项,将依据传入的可选项做下拉
|
||||||
|
* <p>
|
||||||
|
* 3.二者并存,注意调用方式
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
|
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
|
||||||
@ -76,11 +78,18 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
int length = fields.length;
|
int length = fields.length;
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
if (fields[i].isAnnotationPresent(DropDown.class)) {
|
if (fields[i].isAnnotationPresent(DropDown.class)) {
|
||||||
|
// 获取设定的下拉选
|
||||||
List<String> options = Arrays.asList(fields[i].getDeclaredAnnotation(DropDown.class).value());
|
List<String> options = Arrays.asList(fields[i].getDeclaredAnnotation(DropDown.class).value());
|
||||||
|
// 获取列下标,默认为当前循环次数
|
||||||
|
int index = i;
|
||||||
|
if (fields[i].isAnnotationPresent(ExcelProperty.class)) {
|
||||||
|
// 如果制定了列下标,以指定的为主
|
||||||
|
index = fields[i].getDeclaredAnnotation(ExcelProperty.class).index();
|
||||||
|
}
|
||||||
if (options.size() > 20) {
|
if (options.size() > 20) {
|
||||||
dropDownWithSheet(helper, workbook, sheet, i, options);
|
dropDownWithSheet(helper, workbook, sheet, index, options);
|
||||||
} else {
|
} else {
|
||||||
dropDownWithSimple(helper, sheet, i, options);
|
dropDownWithSimple(helper, sheet, index, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,24 +110,30 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 简单下拉框
|
* <h2>简单下拉框</h2>
|
||||||
|
* 直接将可选项拼接为指定列的数据校验值
|
||||||
|
*
|
||||||
|
* @param celIndex 列index
|
||||||
|
* @param value 下拉选可选值
|
||||||
*/
|
*/
|
||||||
private void dropDownWithSimple(DataValidationHelper helper, Sheet sheet, Integer celIndex, List<String> value) {
|
private void dropDownWithSimple(DataValidationHelper helper, Sheet sheet, Integer celIndex, List<String> value) {
|
||||||
if (value.isEmpty()) {
|
if (ObjectUtil.isEmpty(value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.markOptionsToSheet(helper, sheet, celIndex, helper.createExplicitListConstraint(value.toArray(new String[0])));
|
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) {
|
private void dropDownLinkedOptions(DataValidationHelper helper, Workbook workbook, Sheet sheet, DropDownOptions options) {
|
||||||
String linkedOptionsSheetName = String.format("%s_%d", LINKED_OPTIONS_SHEET_NAME, currentLinkedOptionsSheetIndex);
|
String linkedOptionsSheetName = String.format("%s_%d", LINKED_OPTIONS_SHEET_NAME, currentLinkedOptionsSheetIndex);
|
||||||
// 创建联动下拉数据表
|
// 创建联动下拉数据表
|
||||||
Sheet linkedOptionsDataSheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(linkedOptionsSheetName));
|
Sheet linkedOptionsDataSheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(linkedOptionsSheetName));
|
||||||
// 将下拉表隐藏
|
// 将下拉表隐藏
|
||||||
workbook.setSheetHidden(workbook.getSheetIndex(linkedOptionsDataSheet), HIDDEN_FLAG);
|
workbook.setSheetHidden(workbook.getSheetIndex(linkedOptionsDataSheet), true);
|
||||||
// 完善横向的一级选项数据表
|
// 完善横向的一级选项数据表
|
||||||
List<String> firstOptions = options.getOptions();
|
List<String> firstOptions = options.getOptions();
|
||||||
Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
|
Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
|
||||||
@ -209,14 +224,18 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 额外表格形式的下拉框
|
* <h2>额外表格形式的普通下拉框</h2>
|
||||||
|
* 由于下拉框可选值数量过多,为提升Excel打开效率,使用额外表格形式做下拉
|
||||||
|
*
|
||||||
|
* @param celIndex 下拉选
|
||||||
|
* @param value 下拉选可选值
|
||||||
*/
|
*/
|
||||||
private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List<String> 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)))
|
Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)))
|
||||||
.orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)));
|
.orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)));
|
||||||
// 将下拉表隐藏
|
// 将下拉表隐藏
|
||||||
workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), HIDDEN_FLAG);
|
workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true);
|
||||||
// 完善纵向的一级选项数据表
|
// 完善纵向的一级选项数据表
|
||||||
for (int i = 0; i < value.size(); i++) {
|
for (int i = 0; i < value.size(); i++) {
|
||||||
int finalI = i;
|
int finalI = i;
|
||||||
@ -301,7 +320,14 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取栏位名
|
* <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) {
|
private String getExcelColumnName(int columnIndex) {
|
||||||
// 26一循环的次数
|
// 26一循环的次数
|
||||||
@ -311,71 +337,10 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
// 26一循环的次数大于0,则视为栏名至少两位
|
// 26一循环的次数大于0,则视为栏名至少两位
|
||||||
String columnPrefix = columnCircleCount == 0
|
String columnPrefix = columnCircleCount == 0
|
||||||
? ""
|
? ""
|
||||||
: StrUtil.subWithLength(EXCEL_COLUMN_NAME, columnCircleCount, 1);
|
: StrUtil.subWithLength(EXCEL_COLUMN_NAME, columnCircleCount - 1, 1);
|
||||||
// 从26一循环内取对应的栏位名
|
// 从26一循环内取对应的栏位名
|
||||||
String columnNext = StrUtil.subWithLength(EXCEL_COLUMN_NAME, thisCircleColumnIndex, 1);
|
String columnNext = StrUtil.subWithLength(EXCEL_COLUMN_NAME, thisCircleColumnIndex, 1);
|
||||||
// 将二者拼接即为最终的栏位名
|
// 将二者拼接即为最终的栏位名
|
||||||
return columnPrefix + columnNext;
|
return columnPrefix + columnNext;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 下拉可选项
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
public static 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<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建只有一级的下拉选
|
|
||||||
*/
|
|
||||||
public DropDownOptions(int index, List<String> options) {
|
|
||||||
this.index = index;
|
|
||||||
this.options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建选项可选值
|
|
||||||
* <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("_");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (stringBuffer.toString().matches("^\\d_*$")) {
|
|
||||||
throw new ServiceException("禁止以数字开头");
|
|
||||||
}
|
|
||||||
return stringBuffer.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -116,7 +116,7 @@ public class ExcelUtil {
|
|||||||
* @param options 下拉框数据
|
* @param options 下拉框数据
|
||||||
*/
|
*/
|
||||||
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz,
|
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz,
|
||||||
HttpServletResponse response, List<ExcelDownHandler.DropDownOptions> options) {
|
HttpServletResponse response, List<DropDownOptions> options) {
|
||||||
try {
|
try {
|
||||||
resetResponse(sheetName, response);
|
resetResponse(sheetName, response);
|
||||||
ServletOutputStream os = response.getOutputStream();
|
ServletOutputStream os = response.getOutputStream();
|
||||||
@ -177,7 +177,7 @@ public class ExcelUtil {
|
|||||||
* @param options 下拉框数据
|
* @param options 下拉框数据
|
||||||
*/
|
*/
|
||||||
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz,
|
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz,
|
||||||
OutputStream os, List<ExcelDownHandler.DropDownOptions> options) {
|
OutputStream os, List<DropDownOptions> options) {
|
||||||
ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
|
ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
|
||||||
.autoCloseStream(false)
|
.autoCloseStream(false)
|
||||||
// 自动适配
|
// 自动适配
|
||||||
|
|||||||
@ -1,10 +1,14 @@
|
|||||||
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.utils.poi.ExcelUtil;
|
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||||
|
import com.ruoyi.demo.service.IExportExcelService;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
@ -20,9 +24,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 +83,17 @@ 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);
|
||||||
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
static class TestObj1 {
|
static class TestObj1 {
|
||||||
|
|||||||
@ -0,0 +1,93 @@
|
|||||||
|
package com.ruoyi.demo.domain.vo;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
|
import com.ruoyi.common.annotation.DropDown;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带有下拉选的Excel导出
|
||||||
|
*
|
||||||
|
* @author Emil.Zhang
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ExcelIgnoreUnannotated
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ExportDemoVo {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户昵称
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "用户昵称", index = 0)
|
||||||
|
private String nickName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 性别
|
||||||
|
* <p>
|
||||||
|
* 使用DropDown形式注入的下拉选可以使用任意形式,自己能解析出来就行
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "性别", index = 1)
|
||||||
|
@DropDown({"1=男", "2=女"})
|
||||||
|
private String genderStr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库中的性别
|
||||||
|
*/
|
||||||
|
private Integer gender;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手机号
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "手机号", index = 2)
|
||||||
|
private String phoneNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Email
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "Email", index = 3)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 省
|
||||||
|
* <p>
|
||||||
|
* 级联下拉
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "省", index = 25)
|
||||||
|
private String province;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库中的省ID
|
||||||
|
*/
|
||||||
|
private Integer provinceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 市
|
||||||
|
* <p>
|
||||||
|
* 级联下拉
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "市", index = 26)
|
||||||
|
private String city;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库中的市ID
|
||||||
|
*/
|
||||||
|
private Integer cityId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 县
|
||||||
|
* <p>
|
||||||
|
* 级联下拉
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "县", index = 27)
|
||||||
|
private String area;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库中的县ID
|
||||||
|
*/
|
||||||
|
private Integer areaId;
|
||||||
|
}
|
||||||
@ -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,265 @@
|
|||||||
|
package com.ruoyi.demo.service.impl;
|
||||||
|
|
||||||
|
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.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(25);
|
||||||
|
// 以市为二级
|
||||||
|
provinceToCity.setNextIndex(26);
|
||||||
|
// 补充省的内容以及市的内容
|
||||||
|
provinceToCity.setOptions(provinceOptions);
|
||||||
|
provinceToCity.setNextOptions(provinceToCityOptions);
|
||||||
|
|
||||||
|
// 创建市-县级联
|
||||||
|
DropDownOptions cityToArea = new DropDownOptions();
|
||||||
|
cityToArea.setIndex(26);
|
||||||
|
cityToArea.setNextIndex(27);
|
||||||
|
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 -> {
|
||||||
|
// 首先转换性别,性别是通过注解的,则自行提取为对应的值
|
||||||
|
// 业务逻辑中一般会使用枚举的形式,这里直接通过判断值的形式拼接
|
||||||
|
Integer gender = everyRowData.getGender();
|
||||||
|
everyRowData.setGenderStr(gender == 1 ? "1=男" : "2=女");
|
||||||
|
|
||||||
|
// 下面处理下拉框的问题,对于数据本身只需要将对应的下拉值转换为下拉选以选中的选项
|
||||||
|
// 一般来说,可以直接在数据库查询即查询出省市县信息,这里通过模拟操作赋值
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user