提交 80cd3855 authored 作者: douxy's avatar douxy

增加导出策略模式和常规陈列实现类

上级 44b9c1b6
package com.sfa.operation.config;
import com.sfa.operation.util.excel.ExcelStyleUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.poi.ss.usermodel.DataValidation;
import java.util.List;
/**
* @Author: DouXinYu
* @Date: 2025-12-08 16:43
* @Description: AP导出列配置
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExportColumnConfig {
// 字段名
private String fieldName;
// 表头名
private String headerName;
// 格式(如:日期 yyyy-mm)
private String format;
// 列样式
private ExcelStyleUtils.ExcelStyle style;
// 合法值列表(null/空则不验证)
private List<String> validationValidOptions;
// 输入提示标题(可选)
private String validationPromptTitle;
// 输入提示内容(可选)
private String validationPromptMsg;
// 错误提示标题(可选)
private String validationErrorTitle;
// 错误提示内容(可选)
private String validationErrorMsg;
// 错误级别(默认阻止非法输入)
private int validationErrorStyle = DataValidation.ErrorStyle.STOP;
// 验证生效起始行(默认第1行,跳过表头)
private int validationStartRow = 1;
private int validationEndRow = 1048575;
// 数字最小值(null则不限制)
private Double validationNumberMin;
// 数字最大值(null则不限制)
private Double validationNumberMax;
// 是否允许小数(默认整数)
private boolean validationNumberAllowDecimal = false;
// 标记:是否启用数字验证
private boolean isNumberValidation = false;
// 标记:是否启用条件样式
private boolean conditionalStyling = false;
public ExportColumnConfig(String fieldName, String headerName, String format, ExcelStyleUtils.ExcelStyle style) {
this.fieldName = fieldName;
this.headerName = headerName;
this.format = format;
this.style = style;
}
}
package com.sfa.operation.controller.sales.excel;
import com.sfa.common.core.domain.R;
import com.sfa.common.core.enums.ECode;
import com.sfa.operation.pojo.sales.request.SalesApRequest;
import com.sfa.operation.service.sales.export.IExportExcelService;
import com.sfa.operation.strategy.IExportApExcelStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
/**
* @Author: DouXinYu
* @Date: 2025-12-05 17:31
* @Description: 导出excel控制类
*/
@Slf4j
@RestController
@RequestMapping("/sales/export")
public class ApExportExcelController {
@Autowired
private IExportExcelService exportApExcelService;
/**
* 导出常规陈列的excel表格
* @param salesApRequest 查询参数
* @return 导出文件
*/
@PostMapping("/ap_display")
public R exportApDisplayExcel(@RequestBody SalesApRequest salesApRequest, HttpServletResponse response) {
return exportApExcelService.exportApDisplayExcel(salesApRequest, response);
}
}
package com.sfa.operation.domain.sales.dao; package com.sfa.operation.domain.sales.dao;
import com.sfa.common.core.web.domain.PageInfo; import com.sfa.common.core.web.domain.PageInfo;
import com.sfa.operation.domain.sales.entity.SalesApDisplay;
import com.sfa.operation.domain.sales.wq.SalesApWq; import com.sfa.operation.domain.sales.wq.SalesApWq;
import com.sfa.operation.pojo.sales.response.SalesApDisplayDto; import com.sfa.operation.pojo.sales.response.SalesApDisplayDto;
import java.util.List;
/** /**
* @author : liqiulin * @author : liqiulin
* @date : 2025-09-08 14 * @date : 2025-09-08 14
...@@ -17,4 +20,6 @@ public interface ISalesApDisplayDao { ...@@ -17,4 +20,6 @@ public interface ISalesApDisplayDao {
Object queryStoreAPReport(SalesApWq build); Object queryStoreAPReport(SalesApWq build);
Object queryDeptAPReport(SalesApWq build); Object queryDeptAPReport(SalesApWq build);
List<SalesApDisplay> queryDataListByCondition(SalesApWq build);
} }
...@@ -67,6 +67,13 @@ public class SalesApDisplayDaoImpl implements ISalesApDisplayDao { ...@@ -67,6 +67,13 @@ public class SalesApDisplayDaoImpl implements ISalesApDisplayDao {
} }
@Override
public List<SalesApDisplay> queryDataListByCondition(SalesApWq build) {
LambdaQueryWrapper<SalesApDisplay> queryWrapper = buildWq(build);
List<SalesApDisplay> apDisplayList = salesapdisMapper.selectList(queryWrapper);
return apDisplayList;
}
private LambdaQueryWrapper<SalesApDisplay> buildWq(SalesApWq salesApWq) { private LambdaQueryWrapper<SalesApDisplay> buildWq(SalesApWq salesApWq) {
LambdaQueryWrapper<SalesApDisplay> qw = new LambdaQueryWrapper<>(); LambdaQueryWrapper<SalesApDisplay> qw = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(salesApWq.getDealerCode())) { if (StringUtils.isNotBlank(salesApWq.getDealerCode())) {
......
package com.sfa.operation.enums;
import lombok.Getter;
/**
* @Author: DouXinYu
* @Date: 2025-12-05 18:31
* @Description: ap导出页面类型枚举类(与策略对应)
*/
@Getter
public enum ExportAPType {
/**
* 常规陈列策略
*/
NORMAL_DISPLAY_EXPORT("normalDisplayExportStrategy"),
/**
* 档期计划策略
*/
PROMOTION_PLAN_EXPORT("promotionPlanExportStrategy"),
/**
* 零食陈列策略
*/
SNACK_DISPLAY_EXPORT("snackDisplayExportStrategy"),
/**
* 三米两秒策略
*/
THREE_METER_TWO_SECONDS_EXPORT("threeMeterTwoSecondsExportStrategy"),
/**
* 六小金刚策略
*/
SIX_KINGkONG_EXPORT("sixKingKongExportStrategy");
private final String strategy;
ExportAPType(String strategy) {
this.strategy = strategy;
}
}
package com.sfa.operation.factory;
import com.sfa.operation.enums.ExportAPType;
import com.sfa.operation.strategy.IExportApExcelStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: DouXinYu
* @Date: 2025-12-08 16:49
* @Description: AP导出excel策略工厂类
*/
@Slf4j
@Component
public class ApExportExcelStrategyFactory {
private final Map<String, IExportApExcelStrategy> exportApExcelStrategyMap;
//构造器注入
@Autowired
public ApExportExcelStrategyFactory(Map<String, IExportApExcelStrategy> exportApExcelStrategyMap) {
this.exportApExcelStrategyMap = exportApExcelStrategyMap;
}
public IExportApExcelStrategy getStrategy(String exportApType) {
System.out.println(exportApExcelStrategyMap.size());
System.out.println(exportApType);
if (exportApType == null || exportApType.trim().isEmpty()) {
log.error("AP导出Excel策略工厂:传入的导出类型为空!");
throw new IllegalArgumentException("传入的导出类型为空!");
}
ExportAPType typeEnum;
try {
typeEnum = ExportAPType.valueOf(exportApType.toUpperCase());
} catch (IllegalArgumentException e) {
log.error("AP导出Excel策略工厂:传入的导出类型不存在!目标类型为:{}", exportApType);
throw new IllegalArgumentException("传入的导出类型不存在!");
}
// 3. 从枚举中获取策略Bean名称,再从Map中查找策略
String strategyBeanName = typeEnum.getStrategy();
System.out.println(strategyBeanName);
IExportApExcelStrategy strategy = exportApExcelStrategyMap.get(strategyBeanName);
if (strategy == null) {
log.error("AP导出Excel策略工厂:未找到对应的导出策略!目标策略Bean名称为:{}", strategyBeanName);
throw new IllegalArgumentException("未找到对应的导出策略!");
}
log.info("AP导出Excel策略工厂:找到对应的导出策略!目标策略Bean名称为:{}", strategyBeanName);
return strategy;
}
}
...@@ -62,6 +62,16 @@ public class SalesApRequest { ...@@ -62,6 +62,16 @@ public class SalesApRequest {
*/ */
private String rqStatus; private String rqStatus;
/**
* 当前查询的页面类型
* 1.常规陈列 :NORMAL_DISPLAY_EXPORT
* 2.档期计划 :PROMOTION_PLAN_EXPORT
* 3.零食陈列 :SNACK_DISPLAY_EXPORT
* 4.三米两秒 :THREE_METER_TWO_SECONDS_EXPORT
* 5.六小金刚 :SIX_KINGkONG_EXPORT
*/
private String pageType;
// ######################## 通用查询 ######################## // ######################## 通用查询 ########################
/** /**
......
package com.sfa.operation.service.sales; package com.sfa.operation.service.sales;
import com.sfa.common.core.web.domain.PageInfo; import com.sfa.common.core.web.domain.PageInfo;
import com.sfa.operation.domain.sales.entity.SalesApDisplay;
import com.sfa.operation.domain.sales.wq.SalesApWq;
import com.sfa.operation.pojo.sales.request.SalesApRequest; import com.sfa.operation.pojo.sales.request.SalesApRequest;
import java.util.List; import java.util.List;
...@@ -26,4 +28,5 @@ public interface IApDisplayQueryService { ...@@ -26,4 +28,5 @@ public interface IApDisplayQueryService {
Object queryStoreAPReport(SalesApRequest request); Object queryStoreAPReport(SalesApRequest request);
Object queryDeptAPReport(SalesApRequest request); Object queryDeptAPReport(SalesApRequest request);
List<SalesApDisplay> queryDataListByCondition(SalesApRequest build);
} }
package com.sfa.operation.service.sales.export;
import com.sfa.common.core.domain.R;
import com.sfa.operation.pojo.sales.request.SalesApRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Author: DouXinYu
* @Date: 2025-12-09 14:20
* @Description:
*/
public interface IExportExcelService {
R exportApDisplayExcel(SalesApRequest salesApRequest, HttpServletResponse response);
}
package com.sfa.operation.service.sales.export.impl;
import com.sfa.common.core.domain.R;
import com.sfa.operation.factory.ApExportExcelStrategyFactory;
import com.sfa.operation.pojo.sales.request.SalesApRequest;
import com.sfa.operation.service.sales.export.IExportExcelService;
import com.sfa.operation.strategy.IExportApExcelStrategy;
import com.sfa.operation.util.excel.ExcelUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
/**
* @Author: DouXinYu
* @Date: 2025-12-09 14:20
* @Description: 导出excel服务实现类
*/
@Slf4j
@Service
public class ExportExcelServiceImpl implements IExportExcelService {
@Autowired
private ApExportExcelStrategyFactory exportApExcelStrategyFactory;
@Override
public R exportApDisplayExcel(SalesApRequest salesApRequest, HttpServletResponse response) {
try {
IExportApExcelStrategy strategy = exportApExcelStrategyFactory.getStrategy(salesApRequest.getPageType());
String fileNamePrefix = strategy.getExportFileNamePrefix();
log.info("导出前缀为:{}的excel", fileNamePrefix);
byte[] excelBytesArray = strategy.generateExcel(salesApRequest);
ExcelUtils.exportExcelBytesToResponse(excelBytesArray, response, fileNamePrefix);
log.info("常规陈列数据导出成功!");
return R.ok(R.SUCCESS, "数据导出成功") ;
} catch (Exception e) {
log.error("常规陈列数据导出异常!具体错误原因:", e);
return R.fail(R.FAIL,"数据导出失败,请联系后台管理员处理!");
}
}
}
...@@ -7,6 +7,7 @@ import com.sfa.common.core.utils.bean.BeanUtils; ...@@ -7,6 +7,7 @@ import com.sfa.common.core.utils.bean.BeanUtils;
import com.sfa.common.core.web.domain.PageInfo; import com.sfa.common.core.web.domain.PageInfo;
import com.sfa.operation.domain.feishu.dao.IQinceMarketEmployeeDao; import com.sfa.operation.domain.feishu.dao.IQinceMarketEmployeeDao;
import com.sfa.operation.domain.sales.dao.*; import com.sfa.operation.domain.sales.dao.*;
import com.sfa.operation.domain.sales.entity.SalesApDisplay;
import com.sfa.operation.domain.sales.wq.SalesApWq; import com.sfa.operation.domain.sales.wq.SalesApWq;
import com.sfa.operation.pojo.sales.request.SalesApRequest; import com.sfa.operation.pojo.sales.request.SalesApRequest;
import com.sfa.operation.service.sales.IApDisplayQueryService; import com.sfa.operation.service.sales.IApDisplayQueryService;
...@@ -15,6 +16,7 @@ import org.springframework.stereotype.Service; ...@@ -15,6 +16,7 @@ import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import java.awt.dnd.Autoscroll; import java.awt.dnd.Autoscroll;
import java.util.Collections;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
...@@ -79,6 +81,11 @@ public class ApDisplayQueryServiceImpl implements IApDisplayQueryService { ...@@ -79,6 +81,11 @@ public class ApDisplayQueryServiceImpl implements IApDisplayQueryService {
return salesApDisplayDao.queryDeptAPReport(build(request)); return salesApDisplayDao.queryDeptAPReport(build(request));
} }
@Override
public List<SalesApDisplay> queryDataListByCondition(SalesApRequest request) {
return salesApDisplayDao.queryDataListByCondition(build(request));
}
private SalesApWq build(SalesApRequest salesApRequest){ private SalesApWq build(SalesApRequest salesApRequest){
SalesApWq salesApWq = new SalesApWq(); SalesApWq salesApWq = new SalesApWq();
BeanUtils.copyProperties(salesApRequest,salesApWq); BeanUtils.copyProperties(salesApRequest,salesApWq);
...@@ -91,4 +98,5 @@ public class ApDisplayQueryServiceImpl implements IApDisplayQueryService { ...@@ -91,4 +98,5 @@ public class ApDisplayQueryServiceImpl implements IApDisplayQueryService {
// } // }
return salesApWq; return salesApWq;
} }
} }
package com.sfa.operation.strategy;
import com.sfa.operation.config.ExportColumnConfig;
import com.sfa.operation.pojo.sales.request.SalesApRequest;
import com.sfa.operation.util.excel.ExcelUtils;
import java.util.List;
/**
* @Author: DouXinYu
* @Date: 2025-12-08 16:17
* @Description: 策略接口
*/
public interface IExportApExcelStrategy {
/**
* 获取导出列配置
*
* @return 导出列配置
*/
List<ExportColumnConfig> getExportColumnConfig();
/**
* 查询数据
*
* @param salesApRequest 请求参数
* @return 查询结果
*/
List<?> queryData(SalesApRequest salesApRequest);
/**
* 获取导出sheet名称
*/
String getExportSheetName();
/**
* 获取导出文件名前缀
*
* @return 导出文件名前缀
*/
String getExportFileNamePrefix();
/**
* 生成excel
*
* @param salesApRequest 请求参数
* @return excel字节数组
*/
default byte[] generateExcel(SalesApRequest salesApRequest) throws Exception {
return ExcelUtils.generateExcelBytes(
this.getExportColumnConfig(),
this.queryData(salesApRequest),
this.getExportSheetName()
);
}
}
package com.sfa.operation.strategy.impl;
import com.sfa.common.core.constant.RoleConstants;
import com.sfa.common.core.enums.ECode;
import com.sfa.common.core.exception.CheckedException;
import com.sfa.common.security.utils.SecurityUtils;
import com.sfa.operation.config.ConstantValue;
import com.sfa.operation.config.ExportColumnConfig;
import com.sfa.operation.pojo.sales.request.SalesApRequest;
import com.sfa.operation.service.qc.IQinceMarketEmployeeService;
import com.sfa.operation.service.sales.IApDisplayQueryService;
import com.sfa.operation.strategy.IExportApExcelStrategy;
import com.sfa.operation.util.excel.ExcelStyleUtils;
import com.sfa.system.api.domain.SysRole;
import com.sfa.system.api.model.LoginUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
/**
* @Author: DouXinYu
* @Date: 2025-12-08 17:24
* @Description: 常规陈列策略接口实现类
*/
@Component("normalDisplayExportStrategy")
public class NormalDisplayExportStrategyImpl implements IExportApExcelStrategy {
private static final String EXPORT_SHEET_NAME = "常规陈列";
private static final String EXPORT_FILE_NAME_PREFIX = "常规陈列";
@Autowired
private IApDisplayQueryService apDisplayQueryService;
@Autowired
private IQinceMarketEmployeeService qinceMarketEmployeeService;
@Autowired
private ConstantValue constantValue;
/**
* 获取导出列配置
* @return 列配置
*/
@Override
public List<ExportColumnConfig> getExportColumnConfig() {
List<ExportColumnConfig> column = new ArrayList<>();
//处理填报列的规则限制
//主货架形式-实际
ExportColumnConfig actualMainShelfType = new ExportColumnConfig("actualMainShelfType", "主货架形式(实际)", "", ExcelStyleUtils.ExcelStyle.LIGHT_BLUE_BG);
actualMainShelfType.setValidationValidOptions(Arrays.asList("3纵", "4纵", "5纵", "6纵", "7纵", "8纵及以上"));
actualMainShelfType.setValidationErrorTitle("输入错误");
actualMainShelfType.setValidationErrorMsg("主货架形式必须输入”3纵、4纵、5纵、6纵、7纵、8纵及以上“之一!");
actualMainShelfType.setConditionalStyling(true);
//主货架数量-实际
ExportColumnConfig actualMainShelfQty = new ExportColumnConfig("actualMainShelfQty", "主货架数量(实际)", "", ExcelStyleUtils.ExcelStyle.LIGHT_BLUE_BG);
actualMainShelfQty.setNumberValidation(true);
actualMainShelfQty.setValidationNumberMin(0.0);
actualMainShelfQty.setValidationNumberMax(999999999999999999.999999999999999999);
actualMainShelfQty.setValidationErrorTitle("输入错误");
actualMainShelfQty.setValidationErrorMsg("主货架数量必须输入大于等于0的数字!");
actualMainShelfQty.setConditionalStyling(true);
//端架数量-实际
ExportColumnConfig actualEndCapQty = new ExportColumnConfig("actualEndCapQty", "端架数量(实际)", "", ExcelStyleUtils.ExcelStyle.LIGHT_BLUE_BG);
actualEndCapQty.setNumberValidation(true);
actualEndCapQty.setValidationNumberMin(0.0);
actualEndCapQty.setValidationNumberMax(999999999999999999.999999999999999999);
actualEndCapQty.setValidationErrorTitle("输入错误");
actualEndCapQty.setValidationErrorMsg("端架数量必须输入大于等于0的数字!");
actualEndCapQty.setConditionalStyling(true);
//地堆面积-实际
ExportColumnConfig actualFloorStackArea = new ExportColumnConfig("actualFloorStackArea", "地堆平米数(实际)", "", ExcelStyleUtils.ExcelStyle.LIGHT_GREY_BG);
actualFloorStackArea.setValidationValidOptions(Arrays.asList("0","0.5","0.8","1","2","3","4","5","6","8"));
actualFloorStackArea.setValidationErrorTitle("输入错误");
actualFloorStackArea.setValidationErrorMsg("地堆面积必须输入0、0.5、0.8、1、2、3、4、5、6、8之一!");
actualFloorStackArea.setConditionalStyling(true);
//地堆数量-实际
ExportColumnConfig actualFloorStackQty = new ExportColumnConfig("actualFloorStackQty", "地堆数量(实际)", "", ExcelStyleUtils.ExcelStyle.LIGHT_GREY_BG);
actualFloorStackQty.setValidationValidOptions(Arrays.asList("0","1","2","3","4","5"));
actualFloorStackQty.setValidationErrorTitle("输入错误");
actualFloorStackQty.setValidationErrorMsg("地堆数量必须输入0、1、2、3、4、5之一!");
actualFloorStackQty.setConditionalStyling(true);
//多点陈列数量形式-实际
ExportColumnConfig actualMultiDisplay = new ExportColumnConfig("actualMultiDisplay", "多点陈列数量形式(实际)", "", ExcelStyleUtils.ExcelStyle.LIGHT_BLUE_BG);
actualMultiDisplay.setValidationValidOptions(Arrays.asList("执行与计划一致", "执行与计划不一致"));
actualMultiDisplay.setValidationErrorTitle("输入错误");
actualMultiDisplay.setValidationErrorMsg("多点陈列数量形式必须输入“执行与计划一致”或“执行与计划不一致”!");
actualMultiDisplay.setConditionalStyling(true);
//挂条数量形式-实际
ExportColumnConfig actualHangingStripQuantityForm = new ExportColumnConfig("actualHangingStripQuantityForm", "挂条数量形式(实际)", "", ExcelStyleUtils.ExcelStyle.LIGHT_BLUE_BG);
actualHangingStripQuantityForm.setValidationValidOptions(Arrays.asList("执行与计划一致", "执行与计划不一致"));
actualHangingStripQuantityForm.setValidationErrorTitle("输入错误");
actualHangingStripQuantityForm.setValidationErrorMsg("挂条数量形式必须输入“执行与计划一致”或“执行与计划不一致”!");
actualHangingStripQuantityForm.setConditionalStyling(true);
column.add(new ExportColumnConfig("sadId", "序号", "", ExcelStyleUtils.ExcelStyle.UNMODIFIABLE));
column.add(new ExportColumnConfig("salesMonth", "计划月份", "YYYY-MM", ExcelStyleUtils.ExcelStyle.UNMODIFIABLE));
column.add(new ExportColumnConfig("regionName", "销售大区", "", ExcelStyleUtils.ExcelStyle.UNMODIFIABLE));
column.add(new ExportColumnConfig("districtName", "销售战区", "", ExcelStyleUtils.ExcelStyle.UNMODIFIABLE));
column.add(new ExportColumnConfig("dealerCode", "经销商代码", "", ExcelStyleUtils.ExcelStyle.UNMODIFIABLE));
column.add(new ExportColumnConfig("dealerName", "经销商名称", "", ExcelStyleUtils.ExcelStyle.UNMODIFIABLE));
column.add(new ExportColumnConfig("storeCode", "门店编码", "", ExcelStyleUtils.ExcelStyle.UNMODIFIABLE));
column.add(new ExportColumnConfig("storeName", "门店名称", "", ExcelStyleUtils.ExcelStyle.UNMODIFIABLE));
column.add(new ExportColumnConfig("lineName", "系统名称", "", ExcelStyleUtils.ExcelStyle.UNMODIFIABLE));
column.add(new ExportColumnConfig("plannedMainShelfType", "主货架形式(计划)", "", ExcelStyleUtils.ExcelStyle.UNMODIFIABLE));
column.add(actualMainShelfType);
column.add(new ExportColumnConfig("plannedMainShelfQty", "主货架数量(计划)", "", ExcelStyleUtils.ExcelStyle.UNMODIFIABLE));
column.add(actualMainShelfQty);
column.add(new ExportColumnConfig("plannedEndCapQty", "端架数量(计划)", "", ExcelStyleUtils.ExcelStyle.UNMODIFIABLE));
column.add(actualEndCapQty);
column.add(new ExportColumnConfig("plannedFloorStackArea", "地堆平米数(计划)", "", ExcelStyleUtils.ExcelStyle.UNMODIFIABLE));
column.add(actualFloorStackArea);
column.add(new ExportColumnConfig("plannedFloorStackQty", "地堆数量(计划)", "", ExcelStyleUtils.ExcelStyle.UNMODIFIABLE));
column.add(actualFloorStackQty);
column.add(new ExportColumnConfig("plannedMultiDisplay", "多点陈列数量形式(计划)", "", ExcelStyleUtils.ExcelStyle.UNMODIFIABLE));
column.add(actualMultiDisplay);
column.add(new ExportColumnConfig("plannedHangingStripQuantityForm", "挂条数量形式(计划)", "", ExcelStyleUtils.ExcelStyle.UNMODIFIABLE));
column.add(actualHangingStripQuantityForm);
for (ExportColumnConfig exportColumnConfig : column) {
System.out.println(exportColumnConfig.toString());
}
return column;
}
/**
* 查询数据
* @param salesApRequest 请求参数
* @return 根据条换查询出的数据列表
*/
@Override
public List<?> queryData(SalesApRequest salesApRequest) {
return apDisplayQueryService.queryDataListByCondition(salesApRequest);
}
@Override
public String getExportSheetName() {
return EXPORT_SHEET_NAME;
}
@Override
public String getExportFileNamePrefix() {
return EXPORT_FILE_NAME_PREFIX;
}
/* *//**
* 判断登录人权限
* 判断是否存在人客关系,存在:根据负责经销商查询数据;不存在:判断是否是销售部人员。是:根据部门查询;不是:返回全部数据
*//*
private void checkPermission(SalesApRequest salesApRequest) {
LoginUser loginUser = SecurityUtils.getLoginUser();
String ancestors = loginUser.getSysUser().getDept().getAncestors();
List<SysRole> roles = loginUser.getSysUser().getRoles();
// 不是区域销售部的人员,可以查看所有
if (!ancestors.contains(constantValue.deptYX) && roles.stream().filter(role -> RoleConstants.OPERATION_CENTER_AP_MANAGER.equals(role.getRoleKey())).findFirst().isPresent()) {
return;
}
// 是区域销售部的人员:1、销售查看人客关系;2、大区TM查看当前部门下所有数据
// 判断是否是大区TM
String empNo = loginUser.getUsername();
Optional<SysRole> first = roles.stream().filter(role -> RoleConstants.DISTRICT_AP_MANAGER.equals(role.getRoleKey())).findFirst();
if (first.isPresent()) {
ArrayList<String> deptNames = new ArrayList<>();
deptNames.add(loginUser.getSysUser().getDept().getDeptName());
if ("000889".equals(empNo)){
deptNames.add("北京特区");
}
return;
}
// 不是大区TM,判断是否是销售人员(人客关系)
List<String> dealerCodes = qinceMarketEmployeeService.checkPermission(empNo);
if (dealerCodes.isEmpty()) {
throw new CheckedException(ECode.QC_MARKET_EMP_ERROR);
}
// salesApRequest.setNickName(loginUser.getNickName());
salesApRequest.setDealerCodes(dealerCodes);
}*/
}
package com.sfa.operation.util.excel;
import com.sfa.operation.config.ExportColumnConfig;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import java.util.List;
/**
* @Author: DouXinYu
* @Date: 2025-12-08 12:08
* @Description: 配置导出的EXCEL样式
*/
public class ExcelStyleUtils {
public enum ExcelStyle {
// 不可修改
UNMODIFIABLE,
// 浅蓝色
LIGHT_BLUE_BG,
// 浅灰色
LIGHT_GREY_BG,
//默认
DEFAULT_STYLE,
RED_STYLE,
HEADER_STYLE
}
/**
* 获取表格样式
*
* @param workbook 工作表格
* @param style 样式
* @return 已经设置好的样式
*/
/**
* 获取表格样式
*
* @param workbook 工作表格
* @param style 样式
* @return 已经设置好的样式
*/
public static CellStyle getStyle(Workbook workbook, ExcelStyle style) {
CellStyle cellStyle = workbook.createCellStyle();
Font font = workbook.createFont();
// 设置通用样式属性
cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
cellStyle.setAlignment(HorizontalAlignment.CENTER);
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
cellStyle.setBorderBottom(BorderStyle.THIN);
cellStyle.setBorderTop(BorderStyle.THIN);
cellStyle.setBorderLeft(BorderStyle.THIN);
cellStyle.setBorderRight(BorderStyle.THIN);
switch (style) {
// 不可修改
case UNMODIFIABLE:
cellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
cellStyle.setLocked(true);
font.setBold(true);
cellStyle.setFont(font);
break;
// 浅蓝色
case LIGHT_BLUE_BG:
cellStyle.setFillForegroundColor(IndexedColors.PALE_BLUE.getIndex());
cellStyle.setLocked(false);
break;
// 浅灰色
case LIGHT_GREY_BG:
cellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
cellStyle.setLocked(true);
break;
// 红色样式
case RED_STYLE:
font.setColor(IndexedColors.RED.getIndex());
font.setFontName("微软雅黑");
font.setFontHeightInPoints((short) 11);
cellStyle.setFont(font);
cellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
cellStyle.setLocked(false);
break;
// 表头样式
case HEADER_STYLE:
font.setBold(true);
font.setFontName("微软雅黑");
font.setFontHeightInPoints((short) 12);
cellStyle.setFont(font);
cellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
cellStyle.setLocked(false);
break;
// 默认
case DEFAULT_STYLE:
default:
cellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
cellStyle.setLocked(false);
break;
}
return cellStyle;
}
/**
* 匹配不可修改样式的列
*
* @param headerName 表头名称
* @param unmodifiableHeaderList 不可修改的列名
* @return 匹配到的样式
*/
public static ExcelStyle matchUnmodifiedStyle(String headerName, List<String> unmodifiableHeaderList) {
return isHeaderInList(headerName, unmodifiableHeaderList) ? ExcelStyle.UNMODIFIABLE : ExcelStyle.DEFAULT_STYLE;
}
/**
* 匹配浅蓝色样式的列
*
* @param headerName 表头名称
* @param lightBlueHeaderList 浅蓝色列名
* @return 匹配到的样式
*/
public static ExcelStyle matchLightBlueStyle(String headerName, List<String> lightBlueHeaderList) {
return isHeaderInList(headerName, lightBlueHeaderList) ? ExcelStyle.LIGHT_BLUE_BG : ExcelStyle.DEFAULT_STYLE;
}
/**
* 匹配浅灰色样式的列
*
* @param headerName 表头名称
* @param lightGreyHeaderList 浅灰色列名
* @return 匹配到的样式
*/
public static ExcelStyle matchLightGreyStyle(String headerName, List<String> lightGreyHeaderList) {
return isHeaderInList(headerName, lightGreyHeaderList) ? ExcelStyle.LIGHT_GREY_BG : ExcelStyle.DEFAULT_STYLE;
}
/**
* 判断表头是否在列表中
*
* @param headerName 表头名称
* @param headerList 表头列表
* @return 是否在列表中
*/
public static boolean isHeaderInList(String headerName, List<String> headerList) {
if (headerList == null || CollectionUtils.isEmpty(headerList)) {
return false;
}
if (headerName == null || headerName.trim().isEmpty()) {
return false;
}
if (!headerList.contains(headerName)) {
return false;
}
return headerList.contains(headerName);
}
/**
* 添加列数据验证
*
* @param sheet 工作表
* @param columnIndex 列索引
* @param config 列配置
*/
public static void addColumnDataValidation(Sheet sheet, int columnIndex, ExportColumnConfig config) {
// 空值校验:Sheet或配置为空,直接返回(避免空指针)
if (sheet == null || config == null) {
return;
}
// 获取核心对象:工作簿、数据验证助手
Workbook workbook = sheet.getWorkbook();
DataValidationHelper helper = sheet.getDataValidationHelper();
// 声明约束对象(后续根据验证类型赋值)
DataValidationConstraint constraint = null;
// 数字验证分支
if (config.isNumberValidation()) {
// 获取数字验证的最值配置
Double min = config.getValidationNumberMin();
Double max = config.getValidationNumberMax();
// 校验最值配置:至少配置一个才生效
if (min == null && max == null) {
return;
}
// 选择数字类型:整数/小数
// DECIMAL=2(小数),INTEGER=1(整数)
int numericType = config.isValidationNumberAllowDecimal()
? DataValidationConstraint.ValidationType.DECIMAL
: DataValidationConstraint.ValidationType.INTEGER;
// 转换最值为字符串(POI要求的参数格式)
String minStr = (min == null) ? "" : min.toString();
String maxStr = (max == null) ? "" : max.toString();
// 根据最值配置,构建不同的数字约束
if (min != null && max != null) {
// 介于min和max之间(OperatorType.BETWEEN = 1)
constraint = helper.createNumericConstraint(
numericType,
DataValidationConstraint.OperatorType.BETWEEN,
minStr,
maxStr
);
} else if (min != null) {
// 大于等于min(OperatorType.GREATER_OR_EQUAL = 6)
constraint = helper.createNumericConstraint(
numericType,
DataValidationConstraint.OperatorType.GREATER_OR_EQUAL,
minStr,
""
);
} else {
// 小于等于max(OperatorType.LESS_OR_EQUAL = 7)
constraint = helper.createNumericConstraint(
numericType,
DataValidationConstraint.OperatorType.LESS_OR_EQUAL,
"",
maxStr
);
}
}
// 列表验证分支(原有逻辑)
else if (config.getValidationValidOptions() != null && !config.getValidationValidOptions().isEmpty()) {
// 获取列表合法值
List<String> validOptions = config.getValidationValidOptions();
// 构建列表约束(仅允许输入列表中的值)
constraint = helper.createExplicitListConstraint(
validOptions.toArray(new String[0])
);
}
// 无有效约束,直接返回
if (constraint == null) {
return;
}
// 配置验证生效的行范围(默认从第1行开始,跳过表头)
CellRangeAddressList addressList = new CellRangeAddressList(
config.getValidationStartRow(),
config.getValidationEndRow(),
columnIndex,
columnIndex
);
// 创建数据验证规则(绑定约束+生效范围)
DataValidation validation = helper.createValidation(constraint, addressList);
// 配置错误提示(非法输入时弹窗)
if (config.getValidationErrorTitle() != null && config.getValidationErrorMsg() != null) {
validation.setShowErrorBox(true);
validation.setErrorStyle(config.getValidationErrorStyle());
// 设置错误弹窗的标题和内容
validation.createErrorBox(config.getValidationErrorTitle(), config.getValidationErrorMsg());
}
// 配置输入提示(鼠标选中单元格时的引导提示)
if (config.getValidationPromptTitle() != null && config.getValidationPromptMsg() != null) {
validation.setShowPromptBox(true);
// 设置输入提示的标题和内容
validation.createPromptBox(config.getValidationPromptTitle(), config.getValidationPromptMsg());
}
// 将验证规则添加到Sheet,使其生效
sheet.addValidationData(validation);
}
}
package com.sfa.operation.util.excel;
import cn.hutool.core.bean.BeanUtil;
import com.sfa.operation.config.ExportColumnConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
/**
* @Author: DouXinYu
* @Date: 2025-12-05 16:56
* @Description: AP导出excel工具类
*/
@Slf4j
public class ExcelUtils {
private static final Logger logger = LoggerFactory.getLogger(ExcelUtils.class);
private static final ConcurrentHashMap<String, CellStyle> CACHE_CELL_STYLE = new ConcurrentHashMap<>();
/**
* 将Excel字节数组导出为文件(写入Http响应流)
*
* @param excelBytes 生成的Excel字节数组
* @param response Http响应对象
* @param fileNamePrefix 导出文件名前缀(如"常规陈列")
* @throws Exception 导出异常
*/
public static void exportExcelBytesToResponse(byte[] excelBytes, HttpServletResponse response, String fileNamePrefix) throws Exception {
if (excelBytes == null || excelBytes.length == 0) {
throw new Exception("导出Excel失败:生成的Excel字节数组为空");
}
if (StringUtils.isBlank(fileNamePrefix)) {
fileNamePrefix = "导出文件";
}
// 1. 设置响应头(解决中文乱码、指定文件类型)
String fileName = String.format("%s_%s.xlsx", fileNamePrefix, new SimpleDateFormat("yyyyMMdd-HH:mm:ss").format(new Date()));
// URLEncoder编码兼容所有浏览器
String encodedFileName = URLEncoder.encode(fileName, String.valueOf(StandardCharsets.UTF_8));
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setHeader("Content-Disposition", "attachment;filename=" + encodedFileName);
// 禁止缓存
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
response.setDateHeader("Expires", 0);
// 设置文件大小(可选,提升下载体验)
response.setContentLength(excelBytes.length);
// 2. 将字节数组写入响应流
try (OutputStream outputStream = response.getOutputStream()) {
outputStream.write(excelBytes);
outputStream.flush();
} catch (Exception e) {
logger.error("写入Excel响应流失败:{}", e.getMessage(), e);
throw new Exception("导出Excel失败:" + e.getMessage());
}
}
/**
* 生成Excel文件
*
* @param exportColumnConfigList 表头配置
* @param dataList 数据列表
* @param sheetName sheetName
* @return byte[] excel 数组
* @throws IOException
*/
public static byte[] generateExcelBytes(List<ExportColumnConfig> exportColumnConfigList, List<?> dataList, String sheetName) throws IOException {
//数据校验
if (CollectionUtils.isEmpty(exportColumnConfigList)) {
throw new IOException("导出Excel失败:没有配置导出列");
}
if (CollectionUtils.isEmpty(dataList)) {
throw new IOException("导出Excel失败:没有获取到相关数据,请检查条件后重试");
}
if (sheetName == null || sheetName.trim().isEmpty()) {
throw new IOException("导出Excel失败:没有配置sheetName");
}
// 根据sheetName创建工作表
try ( // 创建工作簿
Workbook workbook = createExcelWorkbook(dataList);
// 创建文件字节数组
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();) {
try {
Sheet sheet = workbook.createSheet(sheetName);
// 创建表头
createHeader(sheet, exportColumnConfigList);
// 创建数据行
createDataRows(sheet, dataList, exportColumnConfigList);
// 处理需要验证的列
for (int i = 0; i < exportColumnConfigList.size(); i++) {
ExportColumnConfig config = exportColumnConfigList.get(i);
// 给列配置数据验证(策略自定义的规则)
ExcelStyleUtils.addColumnDataValidation(sheet, i, config);
// 给列配置只读验证
if (isColumnProtected(config)) {
// 为该列所有数据行添加只读验证
// 从第1行开始(跳过表头)
addReadOnlyValidation(sheet, i, 1, dataList.size());
}
}
// 自适应所有列宽
// autoSizeAllColumns(sheet, exportColumnConfigList.size(),dataList.size());
workbook.write(outputStream);
return outputStream.toByteArray();
} finally {
//如果创建的是 sxssf 清理临时文件
if (workbook instanceof SXSSFWorkbook) {
((SXSSFWorkbook) workbook).dispose();
}
workbook.close();
}
} catch (IOException e) {
logger.error("生成Excel文件失败:{}", e.getMessage(), e);
throw new IOException("生成Excel文件失败:" + e.getMessage());
}
}
/**
* 创建工作簿
*
* @param dataList 数据列表
* @return 工作簿
*/
private static Workbook createExcelWorkbook(List<?> dataList) {
if (dataList.size() > 1000) {
SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(100);
sxssfWorkbook.setCompressTempFiles(true);
return sxssfWorkbook;
} else {
return new XSSFWorkbook();
}
}
/**
* 创建表头
*
* @param sheet 工作表
* @param exportColumnConfigList 表头配置
*/
public static void createHeader(Sheet sheet, List<ExportColumnConfig> exportColumnConfigList) {
Workbook workbook = sheet.getWorkbook();
Row headerRow = sheet.createRow(0);
// 调整表头行高(适配换行)
headerRow.setHeightInPoints(40);
for (int i = 0; i < exportColumnConfigList.size(); i++) {
ExportColumnConfig config = exportColumnConfigList.get(i);
Cell cell = headerRow.createCell(i);
String originalHeader = config.getHeaderName() == null ? "" : config.getHeaderName();
// 处理表头文本:拆分括号内容+换行
String processedHeader = processHeaderText(originalHeader);
cell.setCellValue(processedHeader);
// 使用 HEADER_STYLE 作为表头的基础样式
CellStyle headerStyle = cacheStyle(workbook, ExcelStyleUtils.ExcelStyle.HEADER_STYLE);
cell.setCellStyle(headerStyle);
cell.getCellStyle().setWrapText(true);
// 表头强制锁定(不可修改)
cell.getCellStyle().setLocked(true);
// 设置初始列宽
String headerText = config.getHeaderName();
if (headerText != null) {
int maxWidth = calculateTextWidth(processedHeader);
// 至少20个字符宽度
sheet.setColumnWidth(i, Math.max(maxWidth, 20 * 256));
}
}
// 为表头行添加只读验证(防止用户修改表头)
for (int i = 0; i < exportColumnConfigList.size(); i++) {
// 只保护第0行(表头行)
addReadOnlyValidation(sheet, i, 0, 0);
}
}
/**
* 创建数据行
*
* @param sheet 工作表
* @param dataList 数据列表
* @param exportColumnConfigList 表头配置
*/
public static void createDataRows(Sheet sheet, List<?> dataList, List<ExportColumnConfig> exportColumnConfigList) {
Workbook workbook = sheet.getWorkbook();
for (int i = 0; i < dataList.size(); i++) {
// 创建数据行
Row dataRow = sheet.createRow(i + 1);
// 获取数据
Object data = dataList.get(i);
// 渲染数据列
for (int j = 0; j < exportColumnConfigList.size(); j++) {
// 获取数据列的配置
ExportColumnConfig exportColumnConfig = exportColumnConfigList.get(j);
Cell cell = dataRow.createCell(j);
// 处理条件样式
CellStyle style;
if (exportColumnConfig.isConditionalStyling() && j > 0) {
// 检查前一列是否有值
Cell previousCell = dataRow.getCell(j - 1);
boolean hasPreviousValue = false;
if (previousCell != null) {
switch (previousCell.getCellType()) {
case STRING:
hasPreviousValue = previousCell.getStringCellValue() != null &&
!previousCell.getStringCellValue().trim().isEmpty();
break;
case NUMERIC:
hasPreviousValue = true;
break;
case BOOLEAN:
hasPreviousValue = true;
break;
default:
}
}
// 根据前一列是否有值来设置样式
if (hasPreviousValue) {
style = cacheStyle(workbook, ExcelStyleUtils.ExcelStyle.LIGHT_BLUE_BG);
} else {
style = cacheStyle(workbook, ExcelStyleUtils.ExcelStyle.DEFAULT_STYLE);
}
} else {
// 使用配置的默认样式
style = cacheStyle(workbook, exportColumnConfig.getStyle());
}
cell.setCellStyle(style);
Object value = getFieldValueByOgnl(exportColumnConfig.getFieldName(), data);
// 设置单元格值并应用格式
setCellValueWithFormat(cell, value, exportColumnConfig.getFormat());
}
}
}
private static Object getFieldValueByOgnl(String fieldName, Object data) {
// 避免空指针访问
if (data == null || fieldName == null || fieldName.trim().isEmpty()) {
return "";
}
try {
// 使用 BeanUtils 获取属性值
return BeanUtil.getProperty(data, fieldName);
} catch (Exception e) {
logger.error("获取字段值失败:{}", fieldName, e);
// 空值兜底,避免崩溃
return "";
}
}
/**
* 设置单元格值并应用格式(修复:日期/数字格式处理)
*
* @param cell 单元格
* @param value 数据值
* @param format 格式(如yyyy-MM-dd、0.00)
*/
private static void setCellValueWithFormat(Cell cell, Object value, String format) {
if (value == null) {
cell.setCellValue("");
return;
}
// 1. 日期类型
if (value instanceof Date) {
if (format != null && !format.isEmpty()) {
SimpleDateFormat sdf = new SimpleDateFormat(format);
cell.setCellValue(sdf.format((Date) value));
} else {
cell.setCellValue((Date) value);
}
return;
}
// 2. 数值类型(整数/小数):设置为数值单元格,保证数值校验生效
try {
double numericValue = Double.parseDouble(value.toString());
cell.setCellValue(numericValue);
// 数值格式(如保留2位小数)
if (format != null && !format.isEmpty()) {
DataFormat formatInstance = cell.getSheet().getWorkbook().createDataFormat();
CellStyle numericStyle = cell.getSheet().getWorkbook().createCellStyle();
numericStyle.setDataFormat(formatInstance.getFormat(format));
cell.setCellStyle(numericStyle);
}
return;
} catch (NumberFormatException e) {
// 非数值类型,走字符串逻辑
}
// 3. 字符串类型
cell.setCellValue(value.toString());
}
/**
* 计算文本宽度(支持换行)
* @param text 文本内容
* @return 宽度值
*/
private static int calculateTextWidth(String text) {
if (text == null || text.isEmpty()) {
return 10 * 256;
}
String[] lines = text.split("\n");
int maxLineLength = 0;
for (String line : lines) {
maxLineLength = Math.max(maxLineLength, line.length());
}
// 每个字符大约占用256单位宽度,增加一些缓冲
return maxLineLength * 256 + 1000;
}
/**
* 根据表头内容自动调整所有列的宽度
* @param sheet 工作表
* @param columnCount 列数
* @param dataRowCount 数据行数(未使用)
*/
private static void autoSizeAllColumns(Sheet sheet, int columnCount, int dataRowCount) {
// 对于SXSSFSheet,先跟踪所有列以便自动调整大小
if (sheet instanceof SXSSFSheet) {
SXSSFSheet sxssfSheet = (SXSSFSheet) sheet;
for (int i = 0; i < columnCount; i++) {
sxssfSheet.trackColumnForAutoSizing(i);
}
}
// 基于表头内容自动调整每列宽度
for (int i = 0; i < columnCount; i++) {
try {
sheet.autoSizeColumn(i);
// 添加适量的缓冲确保内容显示完整
int currentWidth = sheet.getColumnWidth(i);
sheet.setColumnWidth(i, Math.min(currentWidth + 800, 255 * 256));
} catch (Exception e) {
sheet.setColumnWidth(i, 20 * 256);
logger.warn("Failed to auto-size column {}: {}", i, e.getMessage());
}
}
}
/**
* 缓存样式避免内存溢出
*
* @param workbook 工作簿
* @param style 目标样式
* @return 缓存的样式
*/
private static CellStyle cacheStyle(Workbook workbook, ExcelStyleUtils.ExcelStyle style) {
ExcelStyleUtils.ExcelStyle targetStyle = style == null ? ExcelStyleUtils.ExcelStyle.DEFAULT_STYLE : style;
// 使用更稳定的缓存键
String styleKey = System.identityHashCode(workbook) + "_" + targetStyle.name();
if (!CACHE_CELL_STYLE.containsKey(styleKey)) {
CellStyle cellStyle = ExcelStyleUtils.getStyle(workbook, targetStyle);
CACHE_CELL_STYLE.put(styleKey, cellStyle);
return cellStyle;
}
return CACHE_CELL_STYLE.get(styleKey);
}
/**
* 处理表头文本:拆分括号内容+换行
*
* @param originalText 原始文本
* @return 处理后的文本
*/
private static String processHeaderText(String originalText) {
if (originalText == null || originalText.isEmpty()) {
return "";
}
// 替换中文括号及其内容为换行形式
String result = originalText.replaceAll("((.*?))", "\n$1");
// 替换英文括号及其内容为换行形式
result = result.replaceAll("\\((.*?)\\)", "\n$1");
return result;
}
/**
* 为特定单元格区域添加只读数据验证
*
* @param sheet 工作表
* @param columnIndex 列索引
* @param startRow 起始行
* @param endRow 结束行
*/
private static void addReadOnlyValidation(Sheet sheet, int columnIndex, int startRow, int endRow) {
DataValidationHelper helper = sheet.getDataValidationHelper();
// 创建自定义公式验证,始终返回false(禁止输入)
DataValidationConstraint constraint = helper.createCustomConstraint("FALSE");
CellRangeAddressList addressList = new CellRangeAddressList(startRow, endRow, columnIndex, columnIndex);
DataValidation validation = helper.createValidation(constraint, addressList);
validation.setShowErrorBox(true);
validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
validation.createErrorBox("禁止修改", "此单元格内容不可修改");
validation.setEmptyCellAllowed(false);
validation.setSuppressDropDownArrow(false);
sheet.addValidationData(validation);
}
/**
* 判断列是否需要保护
*
* @param config 列配置
* @return 是否需要保护
*/
private static boolean isColumnProtected(ExportColumnConfig config) {
return config.getStyle() == ExcelStyleUtils.ExcelStyle.UNMODIFIABLE;
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论