需求:导出数据并且指定的列通过下拉框展示,不可以自定义编辑
一、导出数据
1.1、创建Vo类,添加@ExcelProperty注解,实现表头展示
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = ZnCarParkVehicleRecord.class)
public class ZnCarParkVehicleRecordVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 唯一标识
*/
@ExcelProperty(value = "唯一标识")
private Long id;
/**
* 停车场id
*/
@ExcelProperty(value = "停车场id")
private Long modelId;
/**
* 停车场收费规则
*/
@ExcelProperty(value = "停车场收费规则")
private String parkingLotConfig;
/**
* 车位id
*/
@ExcelProperty(value = "车位id")
private Long parkingSpaceId;
/**
* 车牌号
*/
@ExcelProperty(value = "车牌号")
private String carPlateNumber;
/**
* 记录状态 0免费 1未缴费 2已交费3待缴费
*/
@ExcelProperty(value = "记录状态 0免费 1未缴费 2已交费3待缴费")
private Integer status;
/**
* 进出状态:0进 1出
*/
@ExcelProperty(value = "进出状态:0进 1出")
private Integer inOut;
/**
* 进场时间
*/
@ExcelProperty(value = "进场时间")
private Date inTime;
}
1.2、controller
/**
* 导出停车场车辆出入记录列表
*/
@SaCheckPermission("system:carParkVehicleRecord:export")
@Log(title = "停车场车辆出入记录", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(ZnCarParkVehicleRecordBo bo, HttpServletResponse response) {
List<ZnCarParkVehicleRecordVo> list = znCarParkVehicleRecordService.queryList(bo);
ExcelUtil.exportExcel(list, "停车场车辆出入记录", ZnCarParkVehicleRecordVo.class, response);
}
1.3、 工具类
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ExcelUtil {
/**
* 导出excel
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param response 响应体
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
try {
resetResponse(sheetName, response);
ServletOutputStream os = response.getOutputStream();
exportExcel(list, sheetName, clazz, false, os, null);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常");
}
}
通过以上代码,就可以实现数据的导出。
二、导出数据(某列动态展示,展示内容通过数据库查询)
注:此处省去Vo类的创建,可参考上面的Vo类。
2.1、controller
/**
* 导出Excel功能(指定列下拉框)
*
* @author Lion Li
*/
@RestController
@RequestMapping("/excel")
public class EasyExcelController {
@Autowired
private IEasyExcelService excelService;
/**
* 导出下拉框
*
* @param response /
*/
@GetMapping("/exportWithOptions")
public void exportWithOption(HttpServletResponse response) {
excelService.getEasyExcelExcel(response);
}
}
2.2、 service
public interface IEasyExcelService {
/**
* 导出下拉框
*
* @param response /
*/
void getEasyExcelExcel(HttpServletResponse response);
}
2.3 serviceImpl
/**
* 导出下拉框Excel示例
*
* @author Emil.Zhang
*/
@Service
@RequiredArgsConstructor
public class EasyExcelServiceImpl implements IEasyExcelService {
@Resource
private IZnCarService znCarService;
@Override
public void getEasyExcelExcel(HttpServletResponse response) {
//查询数据库数据 excelDataList
ZnCarBo znCarBo = new ZnCarBo();
List<ZnCarVo> excelDataList = znCarService.queryList(znCarBo);
//查询数据库数据 carList
List<ZnCarVo> carList = getProvinceList();
//存储要展示下拉框的数据 parkingNameList
ArrayList<String> parkingNameList = new ArrayList<>();
for (ZnCarVo znCarVo : carList) {
parkingNameList.add(znCarVo.getParkingLotName());
}
//对parkingNameList中重复的数据去重
List<String> collectList = parkingNameList.stream().distinct().collect(Collectors.toList());
//下拉框所在的列以及数据
DropDownOptions dropDown = new DropDownOptions(2, collectList);
// 把所有的下拉框存储
List<DropDownOptions> options = new ArrayList<>();
options.add(dropDown);
//将Excel中的展示数据转换为对应的下拉选
List<ZnCarVo> znCarVoList = StreamUtils.toList(excelDataList, everyRowData -> {
everyRowData.setParkingLotName(everyRowData.getParkingLotName());
return everyRowData;
});
ExcelUtil.exportExcel(znCarVoList,"下拉框示例",ZnCarVo.class,response,options);
}
private List<ZnCarVo> getProvinceList() {
ZnCarBo znCarBo = new ZnCarBo();
List<ZnCarVo> znCarVos = znCarService.queryList(znCarBo);
return znCarVos;
}
}
DropDownOptions.class代码如下
@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++) {
String var = StrUtil.trimToEmpty(String.valueOf(vars[i]));
if (!var.matches(regex)) {
throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字");
}
stringBuffer.append(var);
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);
}
/**
* 创建级联下拉选项
*
* @param parentList 父实体可选项原始数据
* @param parentIndex 父下拉选位置
* @param sonList 子实体可选项原始数据
* @param sonIndex 子下拉选位置
* @param parentHowToGetIdFunction 父类如何获取唯一标识
* @param sonHowToGetParentIdFunction 子类如何获取父类的唯一标识
* @param howToBuildEveryOption 如何生成下拉选内容
* @return 级联下拉选项
*/
public static <T> DropDownOptions buildLinkedOptions(List<T> parentList,
int parentIndex,
List<T> sonList,
int sonIndex,
Function<T, Number> parentHowToGetIdFunction,
Function<T, Number> sonHowToGetParentIdFunction,
Function<T, String> howToBuildEveryOption) {
DropDownOptions parentLinkSonOptions = new DropDownOptions();
// 先创建父类的下拉
parentLinkSonOptions.setIndex(parentIndex);
parentLinkSonOptions.setOptions(
parentList.stream()
.map(howToBuildEveryOption)
.collect(Collectors.toList())
);
// 提取父-子级联下拉
Map<String, List<String>> sonOptions = new HashMap<>();
// 父级依据自己的ID分组
Map<Number, List<T>> parentGroupByIdMap =
parentList.stream().collect(Collectors.groupingBy(parentHowToGetIdFunction));
// 遍历每个子集,提取到Map中
sonList.forEach(everySon -> {
if (parentGroupByIdMap.containsKey(sonHowToGetParentIdFunction.apply(everySon))) {
// 找到对应的上级
T parentObj = parentGroupByIdMap.get(sonHowToGetParentIdFunction.apply(everySon)).get(0);
// 提取名称和ID作为Key
String key = howToBuildEveryOption.apply(parentObj);
// Key对应的Value
List<String> thisParentSonOptionList;
if (sonOptions.containsKey(key)) {
thisParentSonOptionList = sonOptions.get(key);
} else {
thisParentSonOptionList = new ArrayList<>();
sonOptions.put(key, thisParentSonOptionList);
}
// 往Value中添加当前子集选项
thisParentSonOptionList.add(howToBuildEveryOption.apply(everySon));
}
});
parentLinkSonOptions.setNextIndex(sonIndex);
parentLinkSonOptions.setNextOptions(sonOptions);
return parentLinkSonOptions;
}
}
通过以上代码,停车场名字这一列展示的将会如下图所示,停车场名字将通过从数据库中查询出来进行动态的展示。
三、导出数据(多列动态展示,展示内容通过数据库查询)
3.1、 controller
/**
* 导出下拉框
*
* @param response /
*/
@GetMapping("/exportWithOptions")
public void exportWithOptions(HttpServletResponse response) {
exportExcelService.exportWithOptions(response);
}
3.2、service
/**
* 导出下拉框Excel示例
*
* @author Emil.Zhang
*/
public interface IExportExcelService {
/**
* 导出下拉框
*
* @param response /
*/
void exportWithOptions(HttpServletResponse response);
}
3.3、serviceImpl
/**
* 导出下拉框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(),
cityList = getCityList(provinceList),
areaList = getAreaList(cityList);
int provinceIndex = 5, cityIndex = 6, areaIndex = 7;
DropDownOptions provinceToCity = DropDownOptions.buildLinkedOptions(
provinceList,
provinceIndex,
cityList,
cityIndex,
DemoCityData::getId,
DemoCityData::getPid,
everyOptions -> DropDownOptions.createOptionValue(
everyOptions.getName(),
everyOptions.getId()
)
);
DropDownOptions cityToArea = DropDownOptions.buildLinkedOptions(
cityList,
cityIndex,
areaList,
areaIndex,
DemoCityData::getId,
DemoCityData::getPid,
everyOptions -> DropDownOptions.createOptionValue(
everyOptions.getName(),
everyOptions.getId()
)
);
// 把所有的下拉框存储
List<DropDownOptions> options = new ArrayList<>();
options.add(provinceToCity);
options.add(cityToArea);
// 到此为止所有的下拉框可选项已全部配置完毕
// 接下来需要将Excel中的展示数据转换为对应的下拉选
List<ExportDemoVo> outList = StreamUtils.toList(excelDataList, everyRowData -> {
// 只需要处理没有使用@ExcelDictFormat注解的下拉框
// 一般来说,可以直接在数据库查询即查询出省市县信息,这里通过模拟操作赋值
everyRowData.setProvince(buildOptions(provinceList, everyRowData.getProvinceId()));
everyRowData.setCity(buildOptions(cityList, everyRowData.getCityId()));
everyRowData.setArea(buildOptions(areaList, everyRowData.getAreaId()));
return everyRowData;
});
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 StrUtil.EMPTY;
}
}
/**
* 模拟查询数据库操作
*
* @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;
}
}
}
注:参考以上代码,替换成从数据库查询并替换成正确的Vo类,即可实现功能。
如果没有权限验证,可以直接通过浏览器测试,如果涉及到权限验证,可以通过Postman测试
键为Authorization,值为你获取到的token