任务大厅导出:每个任务类型单独一列,方便数据分析

- 动态生成任务类型列:每种任务类型自动生成两列(数量和积分)
- 表头格式:xxx(数量)、xxx(积分)
- 同一任务的多个子任务组数据会汇总到一起
- 支持任意数量的任务类型,自动扩展列
This commit is contained in:
Your Name 2026-01-24 12:00:56 +08:00
parent 27ed077290
commit 785248c5d7
3 changed files with 130 additions and 88 deletions

View File

@ -104,109 +104,144 @@ public class ExportService {
}
}
// 转换为导出数据
List<TaskHallExportVO> exportList = new ArrayList<>();
// 🔥 第一遍遍历:收集所有出现的任务类型名称(用于动态列)
java.util.LinkedHashSet<String> allTypeNames = new java.util.LinkedHashSet<>();
for (Task task : tasks) {
TaskHallExportVO vo = new TaskHallExportVO();
vo.setTaskNo(task.getTaskNo());
vo.setTitle(task.getTitle());
vo.setTaskType(taskTypeMap.getOrDefault(task.getTaskTypeId(), ""));
// 需求类型使用 taskSubType 字段(新品/优化/实拍等)
vo.setDemandType(task.getTaskSubType() != null ? task.getTaskSubType() : "");
vo.setBrand(brandMap.getOrDefault(task.getBrandId(), ""));
vo.setCategory(categoryMap.getOrDefault(task.getCategoryId(), ""));
vo.setPlatform(platformMap.getOrDefault(task.getPlatformId(), ""));
vo.setSpuInfo(task.getSpuInfo());
vo.setScore(task.getScore() != null ? task.getScore().toString() : "0");
vo.setEstimatedHours(task.getEstimatedHours() != null ? task.getEstimatedHours().toString() : "0");
vo.setQuantity(task.getQuantity() != null ? task.getQuantity() : 1);
vo.setPublisher(userMap.getOrDefault(task.getPublisherId(), ""));
vo.setPublisherDept(deptMap.getOrDefault(task.getPublisherDeptId(), ""));
vo.setReceiver(userMap.getOrDefault(task.getReceiverId(), ""));
vo.setReceiverDept(deptMap.getOrDefault(task.getReceiverDeptId(), ""));
vo.setStatus(getStatusText(task.getStatus()));
vo.setReleaseType(getReleaseTypeText(task.getReleaseType()));
vo.setIsUrgent(task.getIsUrgent() != null && task.getIsUrgent() == 1 ? "是" : "否");
vo.setIsPipeline(task.getIsPipeline() != null && task.getIsPipeline() == 1 ? "是" : "否");
vo.setCreateTime(task.getCreateTime() != null ? task.getCreateTime().format(DATE_FORMATTER) : "");
vo.setDeadline(task.getDeadline() != null ? task.getDeadline().format(DATE_FORMATTER) : "");
// 🔥 添加任务明细和积分明细
List<TaskItem> items = taskItemsMap.get(task.getId());
if (items != null && !items.isEmpty()) {
StringBuilder taskDetails = new StringBuilder();
StringBuilder scoreDetails = new StringBuilder();
for (int i = 0; i < items.size(); i++) {
TaskItem item = items.get(i);
if (i > 0) {
taskDetails.append("\n");
scoreDetails.append("\n");
}
// 解析typeDetails JSON
if (items != null) {
for (TaskItem item : items) {
if (item.getTypeDetails() != null && !item.getTypeDetails().isEmpty()) {
try {
@SuppressWarnings("unchecked")
List<Map<String, Object>> typeDetails = objectMapper.readValue(item.getTypeDetails(), List.class);
StringBuilder itemDetail = new StringBuilder();
StringBuilder itemScore = new StringBuilder();
for (int j = 0; j < typeDetails.size(); j++) {
Map<String, Object> detail = typeDetails.get(j);
for (Map<String, Object> detail : typeDetails) {
String typeName = String.valueOf(detail.get("typeName"));
Object qtyObj = detail.get("quantity");
Object scoreObj = detail.get("totalScore");
int qty = qtyObj instanceof Number ? ((Number) qtyObj).intValue() : 1;
String score = scoreObj != null ? String.valueOf(scoreObj) : "0";
if (j > 0) {
itemDetail.append("; ");
itemScore.append("; ");
if (typeName != null && !typeName.equals("null")) {
allTypeNames.add(typeName);
}
itemDetail.append(typeName).append(" x").append(qty);
itemScore.append(typeName).append(": ").append(score).append("分");
}
// 如果有多个子任务组,添加组号前缀
if (items.size() > 1) {
taskDetails.append("第").append(item.getGroupNo()).append("组: ");
scoreDetails.append("第").append(item.getGroupNo()).append("组: ");
}
taskDetails.append(itemDetail);
scoreDetails.append(itemScore);
} catch (Exception e) {
log.warn("解析任务明细失败: taskId={}, itemId={}, error={}",
task.getId(), item.getId(), e.getMessage());
log.warn("解析任务类型失败: itemId={}", item.getId());
}
}
}
}
}
List<String> typeNameList = new ArrayList<>(allTypeNames);
log.info("导出任务大厅数据,发现 {} 种任务类型: {}", typeNameList.size(), typeNameList);
// 转换为导出数据(使用Map存储动态列)
List<Map<String, Object>> exportList = new ArrayList<>();
for (Task task : tasks) {
Map<String, Object> row = new java.util.LinkedHashMap<>();
row.put("taskNo", task.getTaskNo());
row.put("title", task.getTitle());
row.put("taskType", taskTypeMap.getOrDefault(task.getTaskTypeId(), ""));
row.put("demandType", task.getTaskSubType() != null ? task.getTaskSubType() : "");
row.put("brand", brandMap.getOrDefault(task.getBrandId(), ""));
row.put("category", categoryMap.getOrDefault(task.getCategoryId(), ""));
row.put("platform", platformMap.getOrDefault(task.getPlatformId(), ""));
row.put("spuInfo", task.getSpuInfo() != null ? task.getSpuInfo() : "");
row.put("score", task.getScore() != null ? task.getScore().toString() : "0");
row.put("estimatedHours", task.getEstimatedHours() != null ? task.getEstimatedHours().toString() : "0");
row.put("quantity", task.getQuantity() != null ? task.getQuantity() : 1);
// 🔥 初始化所有任务类型列为空(数量和积分都初始化)
for (String typeName : typeNameList) {
row.put("qty_" + typeName, "");
row.put("score_" + typeName, "");
}
// 🔥 填充该任务的各任务类型数量和积分
List<TaskItem> items = taskItemsMap.get(task.getId());
if (items != null) {
// 汇总同一任务所有子任务的类型数据
Map<String, Integer> typeQtyMap = new HashMap<>();
Map<String, Double> typeScoreMap = new HashMap<>();
for (TaskItem item : items) {
if (item.getTypeDetails() != null && !item.getTypeDetails().isEmpty()) {
try {
@SuppressWarnings("unchecked")
List<Map<String, Object>> typeDetails = objectMapper.readValue(item.getTypeDetails(), List.class);
for (Map<String, Object> detail : typeDetails) {
String typeName = String.valueOf(detail.get("typeName"));
if (typeName != null && !typeName.equals("null")) {
Object qtyObj = detail.get("quantity");
Object scoreObj = detail.get("totalScore");
int qty = qtyObj instanceof Number ? ((Number) qtyObj).intValue() : 0;
double score = scoreObj instanceof Number ? ((Number) scoreObj).doubleValue() : 0;
typeQtyMap.merge(typeName, qty, Integer::sum);
typeScoreMap.merge(typeName, score, Double::sum);
}
}
} catch (Exception e) {
log.warn("解析任务明细失败: taskId={}, itemId={}", task.getId(), item.getId());
}
}
}
vo.setTaskDetails(taskDetails.toString());
vo.setScoreDetails(scoreDetails.toString());
} else {
vo.setTaskDetails("");
vo.setScoreDetails("");
// 填入汇总数据
for (Map.Entry<String, Integer> entry : typeQtyMap.entrySet()) {
row.put("qty_" + entry.getKey(), entry.getValue());
}
for (Map.Entry<String, Double> entry : typeScoreMap.entrySet()) {
row.put("score_" + entry.getKey(), entry.getValue());
}
}
exportList.add(vo);
row.put("publisher", userMap.getOrDefault(task.getPublisherId(), ""));
row.put("publisherDept", deptMap.getOrDefault(task.getPublisherDeptId(), ""));
row.put("receiver", userMap.getOrDefault(task.getReceiverId(), ""));
row.put("receiverDept", deptMap.getOrDefault(task.getReceiverDeptId(), ""));
row.put("status", getStatusText(task.getStatus()));
row.put("releaseType", getReleaseTypeText(task.getReleaseType()));
row.put("isUrgent", task.getIsUrgent() != null && task.getIsUrgent() == 1 ? "是" : "否");
row.put("isPipeline", task.getIsPipeline() != null && task.getIsPipeline() == 1 ? "是" : "否");
row.put("createTime", task.getCreateTime() != null ? task.getCreateTime().format(DATE_FORMATTER) : "");
row.put("deadline", task.getDeadline() != null ? task.getDeadline().format(DATE_FORMATTER) : "");
exportList.add(row);
}
// 定义表头
String[] headers = {
"任务编号", "任务标题", "任务类型", "需求类型", "品牌", "类目", "平台",
"SPU信息", "任务积分", "积分明细", "任务明细", "预估工时(小时)", "数量",
"发布人", "发布部门", "接收人", "接收部门", "状态", "发布类型",
"是否紧急", "是否流水线", "创建时间", "截止时间"
};
// 🔥 动态构建表头和字段名
List<String> headerList = new ArrayList<>();
List<String> fieldList = new ArrayList<>();
// 定义字段名
String[] fieldNames = {
"taskNo", "title", "taskType", "demandType", "brand", "category", "platform",
"spuInfo", "score", "scoreDetails", "taskDetails", "estimatedHours", "quantity",
"publisher", "publisherDept", "receiver", "receiverDept", "status", "releaseType",
"isUrgent", "isPipeline", "createTime", "deadline"
};
// 基本信息列
headerList.add("任务编号"); fieldList.add("taskNo");
headerList.add("任务标题"); fieldList.add("title");
headerList.add("任务类型"); fieldList.add("taskType");
headerList.add("需求类型"); fieldList.add("demandType");
headerList.add("品牌"); fieldList.add("brand");
headerList.add("类目"); fieldList.add("category");
headerList.add("平台"); fieldList.add("platform");
headerList.add("SPU信息"); fieldList.add("spuInfo");
headerList.add("任务积分"); fieldList.add("score");
headerList.add("预估工时(小时)"); fieldList.add("estimatedHours");
headerList.add("数量"); fieldList.add("quantity");
// 🔥 动态添加任务类型列(数量和积分分开)
for (String typeName : typeNameList) {
headerList.add(typeName + "(数量)"); fieldList.add("qty_" + typeName);
headerList.add(typeName + "(积分)"); fieldList.add("score_" + typeName);
}
// 其他信息列
headerList.add("发布人"); fieldList.add("publisher");
headerList.add("发布部门"); fieldList.add("publisherDept");
headerList.add("接收人"); fieldList.add("receiver");
headerList.add("接收部门"); fieldList.add("receiverDept");
headerList.add("状态"); fieldList.add("status");
headerList.add("发布类型"); fieldList.add("releaseType");
headerList.add("是否紧急"); fieldList.add("isUrgent");
headerList.add("是否流水线"); fieldList.add("isPipeline");
headerList.add("创建时间"); fieldList.add("createTime");
headerList.add("截止时间"); fieldList.add("deadline");
String[] headers = headerList.toArray(new String[0]);
String[] fieldNames = fieldList.toArray(new String[0]);
// 生成文件名
String fileName = "任务大厅数据_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
@ -318,7 +353,7 @@ public class ExportService {
}
/**
* 任务大厅导出VO
* 任务大厅导出VO(保留用于兼容,实际使用Map动态列)
*/
@lombok.Data
public static class TaskHallExportVO {
@ -331,8 +366,6 @@ public class ExportService {
private String platform;
private String spuInfo;
private String score;
private String scoreDetails; // 积分明细
private String taskDetails; // 任务明细
private String estimatedHours;
private Integer quantity;
private String publisher;
@ -345,6 +378,7 @@ public class ExportService {
private String isPipeline;
private String createTime;
private String deadline;
// 动态任务类型列通过Map处理,不在此定义
}
/**

View File

@ -136,9 +136,16 @@ public class ExcelExportUtil {
}
/**
* 通过反射获取字段值
* 通过反射获取字段值(支持Map和普通对象)
*/
@SuppressWarnings("unchecked")
private static Object getFieldValue(Object obj, String fieldName) {
// 🔥 如果是Map类型,直接通过key获取值
if (obj instanceof java.util.Map) {
return ((java.util.Map<String, Object>) obj).get(fieldName);
}
// 普通对象通过反射获取
try {
Field field = findField(obj.getClass(), fieldName);
if (field != null) {

View File

@ -53,6 +53,7 @@
### 导出功能
- 修复导出功能报错问题
- 导出数据现在正确显示需求类型(新品/优化等)
- 任务大厅导出数据新增"任务明细"和"积分明细"列
---