diff --git a/backend/src/main/java/com/designhub/service/AnalyticsService.java b/backend/src/main/java/com/designhub/service/AnalyticsService.java index 6523bf9..727b77f 100644 --- a/backend/src/main/java/com/designhub/service/AnalyticsService.java +++ b/backend/src/main/java/com/designhub/service/AnalyticsService.java @@ -1975,6 +1975,12 @@ public class AnalyticsService { result.setDemandDeptAnalysis(buildDemandDeptAnalysis(tasks)); result.setPlatformAnalysis(buildPlatformAnalysis(tasks)); + // 构建任务类型分析(一级、二级、三级) + Map> taskTypeAnalysisMap = buildTaskTypeAnalysis(tasks); + result.setTaskTypeAnalysis(taskTypeAnalysisMap.getOrDefault(1, new ArrayList<>())); + result.setTaskTypeLevel2Analysis(taskTypeAnalysisMap.getOrDefault(2, new ArrayList<>())); + result.setTaskTypeLevel3Analysis(taskTypeAnalysisMap.getOrDefault(3, new ArrayList<>())); + return result; } @@ -2857,5 +2863,258 @@ public class AnalyticsService { return result; } + + /** + * 构建任务类型分析数据(一级、二级、三级分类) + * @param tasks 任务列表 + * @return 按层级分组的任务类型分析数据,key为层级(1/2/3) + */ + private Map> buildTaskTypeAnalysis(List tasks) { + log.info("开始构建任务类型分析数据,任务数量: {}", tasks.size()); + + // 获取所有任务类型 + List allTypes = demandTypeMapper.selectList( + new LambdaQueryWrapper() + .eq(DemandType::getDeleted, 0) + ); + + // 构建类型ID到类型的映射 + Map typeMap = allTypes.stream() + .collect(Collectors.toMap(DemandType::getId, t -> t, (a, b) -> a)); + + // 构建父类型名称映射 + Map typeNameMap = allTypes.stream() + .collect(Collectors.toMap(DemandType::getId, t -> t.getName() != null ? t.getName() : "未知", (a, b) -> a)); + + // 收集所有任务中涉及的任务类型ID + Set allTaskTypeIds = new HashSet<>(); + for (Task task : tasks) { + // 单个任务类型ID + if (task.getTaskTypeId() != null) { + allTaskTypeIds.add(task.getTaskTypeId()); + } + // 多个任务类型ID(逗号分隔) + if (task.getTaskTypeIds() != null && !task.getTaskTypeIds().trim().isEmpty()) { + String[] ids = task.getTaskTypeIds().split(","); + for (String id : ids) { + try { + allTaskTypeIds.add(Long.parseLong(id.trim())); + } catch (NumberFormatException e) { + // 忽略无效ID + } + } + } + } + + log.info("涉及的任务类型ID数量: {}", allTaskTypeIds.size()); + + // 按任务类型ID分组统计任务 + Map> tasksByTypeId = new HashMap<>(); + for (Task task : tasks) { + Set typeIds = new HashSet<>(); + if (task.getTaskTypeId() != null) { + typeIds.add(task.getTaskTypeId()); + } + if (task.getTaskTypeIds() != null && !task.getTaskTypeIds().trim().isEmpty()) { + String[] ids = task.getTaskTypeIds().split(","); + for (String id : ids) { + try { + typeIds.add(Long.parseLong(id.trim())); + } catch (NumberFormatException e) { + // 忽略无效ID + } + } + } + for (Long typeId : typeIds) { + tasksByTypeId.computeIfAbsent(typeId, k -> new ArrayList<>()).add(task); + } + } + + // 计算每个类型的层级 + Map typeLevelMap = new HashMap<>(); + for (DemandType type : allTypes) { + int level = calculateTypeLevel(type.getId(), typeMap); + typeLevelMap.put(type.getId(), level); + } + + // 构建各层级的分析数据 + Map> result = new HashMap<>(); + result.put(1, new ArrayList<>()); + result.put(2, new ArrayList<>()); + result.put(3, new ArrayList<>()); + + for (Long typeId : allTaskTypeIds) { + DemandType type = typeMap.get(typeId); + if (type == null) { + continue; + } + + List typeTasks = tasksByTypeId.getOrDefault(typeId, new ArrayList<>()); + if (typeTasks.isEmpty()) { + continue; + } + + int level = typeLevelMap.getOrDefault(typeId, 1); + + // 构建分析数据 + DimensionAnalysisVO.TaskTypeAnalysis analysis = buildSingleTaskTypeAnalysis( + type, typeTasks, level, typeMap, typeNameMap + ); + + result.get(level).add(analysis); + } + + // 对各层级按任务数量降序排序 + for (int level = 1; level <= 3; level++) { + List levelList = result.get(level); + levelList.sort((a, b) -> { + Integer totalA = a.getTotalTasks() != null ? a.getTotalTasks() : 0; + Integer totalB = b.getTotalTasks() != null ? b.getTotalTasks() : 0; + return totalB.compareTo(totalA); + }); + } + + log.info("任务类型分析完成: 一级={}, 二级={}, 三级={}", + result.get(1).size(), result.get(2).size(), result.get(3).size()); + + return result; + } + + /** + * 计算任务类型的层级 + * @param typeId 类型ID + * @param typeMap 类型映射 + * @return 层级(1/2/3) + */ + private int calculateTypeLevel(Long typeId, Map typeMap) { + DemandType type = typeMap.get(typeId); + if (type == null) { + return 1; + } + + int level = 1; + Long parentId = type.getParentId(); + while (parentId != null && level < 3) { + level++; + DemandType parent = typeMap.get(parentId); + if (parent == null) { + break; + } + parentId = parent.getParentId(); + } + + return level; + } + + /** + * 构建单个任务类型的分析数据 + */ + private DimensionAnalysisVO.TaskTypeAnalysis buildSingleTaskTypeAnalysis( + DemandType type, + List typeTasks, + int level, + Map typeMap, + Map typeNameMap) { + + DimensionAnalysisVO.TaskTypeAnalysis analysis = new DimensionAnalysisVO.TaskTypeAnalysis(); + analysis.setTypeId(type.getId()); + analysis.setTypeName(type.getName() != null ? type.getName() : "未知"); + analysis.setParentId(type.getParentId()); + analysis.setLevel(level); + analysis.setUnitScore(type.getUnitScore()); + analysis.setEstimatedHours(type.getEstimatedHours()); + + // 设置父级名称 + if (type.getParentId() != null) { + analysis.setParentName(typeNameMap.getOrDefault(type.getParentId(), "未知")); + } + + // 统计任务数量 + analysis.setTotalTasks(typeTasks.size()); + + long completedCount = typeTasks.stream() + .filter(t -> "completed".equals(t.getStatus())) + .count(); + analysis.setCompletedTasks((int) completedCount); + + long inProgressCount = typeTasks.stream() + .filter(t -> "in_progress".equals(t.getStatus())) + .count(); + analysis.setInProgressTasks((int) inProgressCount); + + long pendingCount = typeTasks.stream() + .filter(t -> t.getStatus() != null && t.getStatus().contains("pending")) + .count(); + analysis.setPendingTasks((int) pendingCount); + + // 计算总数量 + int totalQuantity = typeTasks.stream() + .filter(t -> t.getQuantity() != null) + .mapToInt(Task::getQuantity) + .sum(); + analysis.setTotalQuantity(totalQuantity); + + // 计算总积分(优先使用 scoreReward) + BigDecimal totalScore = typeTasks.stream() + .map(t -> { + if (t.getScoreReward() != null && t.getScoreReward().compareTo(BigDecimal.ZERO) > 0) { + return t.getScoreReward(); + } + return t.getScore() != null ? t.getScore() : BigDecimal.ZERO; + }) + .reduce(BigDecimal.ZERO, BigDecimal::add); + analysis.setTotalScore(totalScore); + + // 计算平均积分 + if (typeTasks.size() > 0) { + analysis.setAvgScore(totalScore.divide(BigDecimal.valueOf(typeTasks.size()), 2, RoundingMode.HALF_UP)); + } else { + analysis.setAvgScore(BigDecimal.ZERO); + } + + // 计算完成率 + if (typeTasks.size() > 0) { + BigDecimal completionRate = BigDecimal.valueOf(completedCount) + .divide(BigDecimal.valueOf(typeTasks.size()), 4, RoundingMode.HALF_UP) + .multiply(BigDecimal.valueOf(100)); + analysis.setCompletionRate(completionRate); + } else { + analysis.setCompletionRate(BigDecimal.ZERO); + } + + // 计算准时率(已完成任务中准时完成的比例) + if (completedCount > 0) { + long onTimeCount = typeTasks.stream() + .filter(t -> "completed".equals(t.getStatus())) + .filter(t -> { + if (t.getDeadline() == null || t.getActualEndTime() == null) { + return true; // 没有截止时间或完成时间的视为准时 + } + return !t.getActualEndTime().isAfter(t.getDeadline()); + }) + .count(); + analysis.setOnTimeRate((double) onTimeCount / completedCount * 100); + } else { + analysis.setOnTimeRate(0.0); + } + + // 计算平均完成时长(小时) + List completedTasks = typeTasks.stream() + .filter(t -> "completed".equals(t.getStatus()) + && t.getActualStartTime() != null + && t.getActualEndTime() != null) + .collect(Collectors.toList()); + + if (!completedTasks.isEmpty()) { + double totalHours = completedTasks.stream() + .mapToDouble(t -> Duration.between(t.getActualStartTime(), t.getActualEndTime()).toMinutes() / 60.0) + .sum(); + analysis.setAvgCompletionHours(totalHours / completedTasks.size()); + } else { + analysis.setAvgCompletionHours(0.0); + } + + return analysis; + } } diff --git a/backend/src/main/java/com/designhub/vo/DimensionAnalysisVO.java b/backend/src/main/java/com/designhub/vo/DimensionAnalysisVO.java index 8c6eb4b..27c5fca 100644 --- a/backend/src/main/java/com/designhub/vo/DimensionAnalysisVO.java +++ b/backend/src/main/java/com/designhub/vo/DimensionAnalysisVO.java @@ -42,6 +42,21 @@ public class DimensionAnalysisVO { */ private List platformAnalysis; + /** + * 任务类型维度分析(一级分类) + */ + private List taskTypeAnalysis; + + /** + * 任务类型维度分析(二级分类) + */ + private List taskTypeLevel2Analysis; + + /** + * 任务类型维度分析(三级分类) + */ + private List taskTypeLevel3Analysis; + /** * 类目分析数据 */ @@ -170,4 +185,31 @@ public class DimensionAnalysisVO { private Integer demandDeptCount; // 需求部门数量 private Integer receivingDeptCount; // 接单部门数量 } + + /** + * 任务类型分析数据 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class TaskTypeAnalysis { + private Long typeId; // 类型ID + private String typeName; // 类型名称 + private Long parentId; // 父级ID + private String parentName; // 父级名称 + private Integer level; // 层级(1=一级,2=二级,3=三级) + private Integer totalTasks; // 总任务数 + private Integer completedTasks; // 已完成任务数 + private Integer inProgressTasks; // 进行中任务数 + private Integer pendingTasks; // 待接单任务数 + private Integer totalQuantity; // 总数量 + private BigDecimal totalScore; // 总积分 + private BigDecimal avgScore; // 平均积分 + private BigDecimal completionRate; // 完成率 + private Double onTimeRate; // 准时率 + private Double avgCompletionHours; // 平均完成时长(小时) + private BigDecimal unitScore; // 单价分值 + private BigDecimal estimatedHours; // 预估工时 + } } diff --git a/frontend/src/api/analytics.ts b/frontend/src/api/analytics.ts index 71e7437..5fda77d 100644 --- a/frontend/src/api/analytics.ts +++ b/frontend/src/api/analytics.ts @@ -465,12 +465,36 @@ export interface PlatformAnalysis { totalQuantity: number; } +// 任务类型分析 +export interface TaskTypeAnalysis { + typeId: number; + typeName: string; + parentId: number | null; + parentName: string | null; + level: number; + totalTasks: number; + completedTasks: number; + inProgressTasks: number; + pendingTasks: number; + totalQuantity: number; + totalScore: number; + avgScore: number; + completionRate: number; + onTimeRate: number; + avgCompletionHours: number; + unitScore: number; + estimatedHours: number; +} + export interface DimensionAnalysis { categoryAnalysis: CategoryAnalysis[]; brandAnalysis: BrandAnalysis[]; receivingDeptAnalysis: DeptAnalysis[]; demandDeptAnalysis: DeptAnalysis[]; platformAnalysis: PlatformAnalysis[]; + taskTypeAnalysis: TaskTypeAnalysis[]; + taskTypeLevel2Analysis: TaskTypeAnalysis[]; + taskTypeLevel3Analysis: TaskTypeAnalysis[]; } /** @@ -499,7 +523,10 @@ export const getDimensionAnalysis = async ( brandAnalysis: [], receivingDeptAnalysis: [], demandDeptAnalysis: [], - platformAnalysis: [] + platformAnalysis: [], + taskTypeAnalysis: [], + taskTypeLevel2Analysis: [], + taskTypeLevel3Analysis: [] }; }; diff --git a/frontend/src/views/Analytics.vue b/frontend/src/views/Analytics.vue index f8edcd4..fe7ca8f 100644 --- a/frontend/src/views/Analytics.vue +++ b/frontend/src/views/Analytics.vue @@ -579,6 +579,138 @@ + + +
+
+

任务类型分析(一级分类)

+
+ + + +
+ + +
+
+

任务类型分析(二级分类)

+
+ + + +
+ + +
+
+

任务类型分析(三级分类)

+
+ + + +
@@ -810,6 +942,7 @@ import { type DelayDeductionItem, type ProgressDeductionItem, type DimensionAnalysis, + type TaskTypeAnalysis, type PrimaryDeptStatistics, type PrimaryDeptRanking, type BrandTaskDistribution, @@ -916,7 +1049,10 @@ const dimensionData = reactive({ brandAnalysis: [], receivingDeptAnalysis: [], demandDeptAnalysis: [], - platformAnalysis: [] + platformAnalysis: [], + taskTypeAnalysis: [], + taskTypeLevel2Analysis: [], + taskTypeLevel3Analysis: [] }) // 一级需求部门统计数据 @@ -1163,6 +1299,59 @@ const platformColumns = [ { title: '总积分', key: 'totalScore', align: 'center', width: 100 } ] +// 任务类型分析列(一级分类) +const taskTypeColumns = [ + { title: '类型名称', dataIndex: 'typeName', key: 'typeName', ellipsis: true, width: 150 }, + { title: '任务数', dataIndex: 'totalTasks', key: 'totalTasks', align: 'center', width: 80 }, + { title: '总数量', dataIndex: 'totalQuantity', key: 'totalQuantity', align: 'center', width: 80 }, + { title: '已完成', dataIndex: 'completedTasks', key: 'completedTasks', align: 'center', width: 80 }, + { title: '进行中', dataIndex: 'inProgressTasks', key: 'inProgressTasks', align: 'center', width: 80 }, + { title: '待接单', dataIndex: 'pendingTasks', key: 'pendingTasks', align: 'center', width: 80 }, + { title: '完成率', key: 'completionRate', align: 'center', width: 90 }, + { title: '准时率', key: 'onTimeRate', align: 'center', width: 90 }, + { title: '总积分', key: 'totalScore', align: 'center', width: 100 }, + { title: '平均积分', key: 'avgScore', align: 'center', width: 100 }, + { title: '单价分值', key: 'unitScore', align: 'center', width: 100 }, + { title: '预估工时', key: 'estimatedHours', align: 'center', width: 100 }, + { title: '平均完成时长', key: 'avgCompletionHours', align: 'center', width: 120 } +] + +// 任务类型分析列(二级分类,带父级名称) +const taskTypeLevel2Columns = [ + { title: '父级分类', dataIndex: 'parentName', key: 'parentName', ellipsis: true, width: 120 }, + { title: '类型名称', dataIndex: 'typeName', key: 'typeName', ellipsis: true, width: 150 }, + { title: '任务数', dataIndex: 'totalTasks', key: 'totalTasks', align: 'center', width: 80 }, + { title: '总数量', dataIndex: 'totalQuantity', key: 'totalQuantity', align: 'center', width: 80 }, + { title: '已完成', dataIndex: 'completedTasks', key: 'completedTasks', align: 'center', width: 80 }, + { title: '进行中', dataIndex: 'inProgressTasks', key: 'inProgressTasks', align: 'center', width: 80 }, + { title: '待接单', dataIndex: 'pendingTasks', key: 'pendingTasks', align: 'center', width: 80 }, + { title: '完成率', key: 'completionRate', align: 'center', width: 90 }, + { title: '准时率', key: 'onTimeRate', align: 'center', width: 90 }, + { title: '总积分', key: 'totalScore', align: 'center', width: 100 }, + { title: '平均积分', key: 'avgScore', align: 'center', width: 100 }, + { title: '单价分值', key: 'unitScore', align: 'center', width: 100 }, + { title: '预估工时', key: 'estimatedHours', align: 'center', width: 100 }, + { title: '平均完成时长', key: 'avgCompletionHours', align: 'center', width: 120 } +] + +// 任务类型分析列(三级分类,带父级名称) +const taskTypeLevel3Columns = [ + { title: '父级分类', dataIndex: 'parentName', key: 'parentName', ellipsis: true, width: 120 }, + { title: '类型名称', dataIndex: 'typeName', key: 'typeName', ellipsis: true, width: 150 }, + { title: '任务数', dataIndex: 'totalTasks', key: 'totalTasks', align: 'center', width: 80 }, + { title: '总数量', dataIndex: 'totalQuantity', key: 'totalQuantity', align: 'center', width: 80 }, + { title: '已完成', dataIndex: 'completedTasks', key: 'completedTasks', align: 'center', width: 80 }, + { title: '进行中', dataIndex: 'inProgressTasks', key: 'inProgressTasks', align: 'center', width: 80 }, + { title: '待接单', dataIndex: 'pendingTasks', key: 'pendingTasks', align: 'center', width: 80 }, + { title: '完成率', key: 'completionRate', align: 'center', width: 90 }, + { title: '准时率', key: 'onTimeRate', align: 'center', width: 90 }, + { title: '总积分', key: 'totalScore', align: 'center', width: 100 }, + { title: '平均积分', key: 'avgScore', align: 'center', width: 100 }, + { title: '单价分值', key: 'unitScore', align: 'center', width: 100 }, + { title: '预估工时', key: 'estimatedHours', align: 'center', width: 100 }, + { title: '平均完成时长', key: 'avgCompletionHours', align: 'center', width: 120 } +] + // 一级需求部门排名列 const primaryDeptColumns = [ { title: '排名', key: 'rank', dataIndex: 'rank', align: 'center', width: 60 },