Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
W
wangxiaolu-sfa-module-operation
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
sfa
wangxiaolu-sfa-module-operation
Commits
80cd3855
提交
80cd3855
authored
12月 09, 2025
作者:
douxy
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
增加导出策略模式和常规陈列实现类
上级
44b9c1b6
隐藏空白字符变更
内嵌
并排
正在显示
15 个修改的文件
包含
1253 行增加
和
0 行删除
+1253
-0
ExportColumnConfig.java
...ain/java/com/sfa/operation/config/ExportColumnConfig.java
+64
-0
ApExportExcelController.java
...ation/controller/sales/excel/ApExportExcelController.java
+39
-0
ISalesApDisplayDao.java
...om/sfa/operation/domain/sales/dao/ISalesApDisplayDao.java
+5
-0
SalesApDisplayDaoImpl.java
...peration/domain/sales/dao/impl/SalesApDisplayDaoImpl.java
+7
-0
ExportAPType.java
src/main/java/com/sfa/operation/enums/ExportAPType.java
+37
-0
ApExportExcelStrategyFactory.java
...m/sfa/operation/factory/ApExportExcelStrategyFactory.java
+56
-0
SalesApRequest.java
.../com/sfa/operation/pojo/sales/request/SalesApRequest.java
+10
-0
IApDisplayQueryService.java
...m/sfa/operation/service/sales/IApDisplayQueryService.java
+3
-0
IExportExcelService.java
...a/operation/service/sales/export/IExportExcelService.java
+17
-0
ExportExcelServiceImpl.java
...ion/service/sales/export/impl/ExportExcelServiceImpl.java
+44
-0
ApDisplayQueryServiceImpl.java
...eration/service/sales/impl/ApDisplayQueryServiceImpl.java
+8
-0
IExportApExcelStrategy.java
...va/com/sfa/operation/strategy/IExportApExcelStrategy.java
+55
-0
NormalDisplayExportStrategyImpl.java
...ration/strategy/impl/NormalDisplayExportStrategyImpl.java
+188
-0
ExcelStyleUtils.java
...in/java/com/sfa/operation/util/excel/ExcelStyleUtils.java
+268
-0
ExcelUtils.java
src/main/java/com/sfa/operation/util/excel/ExcelUtils.java
+452
-0
没有找到文件。
src/main/java/com/sfa/operation/config/ExportColumnConfig.java
0 → 100644
浏览文件 @
80cd3855
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
;
}
}
src/main/java/com/sfa/operation/controller/sales/excel/ApExportExcelController.java
0 → 100644
浏览文件 @
80cd3855
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
);
}
}
src/main/java/com/sfa/operation/domain/sales/dao/ISalesApDisplayDao.java
浏览文件 @
80cd3855
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
);
}
}
src/main/java/com/sfa/operation/domain/sales/dao/impl/SalesApDisplayDaoImpl.java
浏览文件 @
80cd3855
...
@@ -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
()))
{
...
...
src/main/java/com/sfa/operation/enums/ExportAPType.java
0 → 100644
浏览文件 @
80cd3855
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
;
}
}
src/main/java/com/sfa/operation/factory/ApExportExcelStrategyFactory.java
0 → 100644
浏览文件 @
80cd3855
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
;
}
}
src/main/java/com/sfa/operation/pojo/sales/request/SalesApRequest.java
浏览文件 @
80cd3855
...
@@ -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
;
// ######################## 通用查询 ########################
// ######################## 通用查询 ########################
/**
/**
...
...
src/main/java/com/sfa/operation/service/sales/IApDisplayQueryService.java
浏览文件 @
80cd3855
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
);
}
}
src/main/java/com/sfa/operation/service/sales/export/IExportExcelService.java
0 → 100644
浏览文件 @
80cd3855
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
);
}
src/main/java/com/sfa/operation/service/sales/export/impl/ExportExcelServiceImpl.java
0 → 100644
浏览文件 @
80cd3855
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
,
"数据导出失败,请联系后台管理员处理!"
);
}
}
}
src/main/java/com/sfa/operation/service/sales/impl/ApDisplayQueryServiceImpl.java
浏览文件 @
80cd3855
...
@@ -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
;
}
}
}
}
src/main/java/com/sfa/operation/strategy/IExportApExcelStrategy.java
0 → 100644
浏览文件 @
80cd3855
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
()
);
}
}
src/main/java/com/sfa/operation/strategy/impl/NormalDisplayExportStrategyImpl.java
0 → 100644
浏览文件 @
80cd3855
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);
}*/
}
src/main/java/com/sfa/operation/util/excel/ExcelStyleUtils.java
0 → 100644
浏览文件 @
80cd3855
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
);
}
}
src/main/java/com/sfa/operation/util/excel/ExcelUtils.java
0 → 100644
浏览文件 @
80cd3855
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
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论