添加多维度分析功能:类目/品牌/接单部门/需求部门/平台维度分析
This commit is contained in:
parent
1b2525dd9c
commit
7c618fd3a4
@ -6,6 +6,7 @@ import com.designhub.util.JwtUtil;
|
||||
import com.designhub.vo.DeductionDetailVO;
|
||||
import com.designhub.vo.DemandStatisticsVO;
|
||||
import com.designhub.vo.DesignerPerformanceVO;
|
||||
import com.designhub.vo.DimensionAnalysisVO;
|
||||
import com.designhub.vo.ProcessEfficiencyVO;
|
||||
import com.designhub.vo.WarningTaskVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@ -101,5 +102,16 @@ public class AnalyticsController {
|
||||
|
||||
return Result.success(analyticsService.getDesignerDeductionDetails(userId, month));
|
||||
}
|
||||
|
||||
@Operation(summary = "获取多维度分析数据(类目/品牌/部门/平台)")
|
||||
@GetMapping("/dimension-analysis")
|
||||
public Result<DimensionAnalysisVO> getDimensionAnalysis(
|
||||
@RequestParam(required = false, defaultValue = "month") String dateRange,
|
||||
@RequestParam(required = false) String month
|
||||
) {
|
||||
log.info("获取多维度分析数据: dateRange={}, month={}", dateRange, month);
|
||||
|
||||
return Result.success(analyticsService.getDimensionAnalysis(dateRange, month));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import com.designhub.mapper.*;
|
||||
import com.designhub.vo.DeductionDetailVO;
|
||||
import com.designhub.vo.DemandStatisticsVO;
|
||||
import com.designhub.vo.DesignerPerformanceVO;
|
||||
import com.designhub.vo.DimensionAnalysisVO;
|
||||
import com.designhub.vo.ProcessEfficiencyVO;
|
||||
import com.designhub.vo.WarningTaskVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -41,6 +42,9 @@ public class AnalyticsService {
|
||||
private final DesignerTargetScoreMapper designerTargetScoreMapper;
|
||||
private final DemandTypeMapper demandTypeMapper;
|
||||
private final ProgressDeductionLogMapper progressDeductionLogMapper;
|
||||
private final BrandMapper brandMapper;
|
||||
private final CategoryMapper categoryMapper;
|
||||
private final PlatformMapper platformMapper;
|
||||
private final com.designhub.util.WorkHoursCalculator workHoursCalculator;
|
||||
|
||||
/**
|
||||
@ -1838,5 +1842,416 @@ public class AnalyticsService {
|
||||
.progressDeductions(progressDeductions)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多维度分析数据
|
||||
* @param dateRange 时间范围
|
||||
* @param month 指定月份
|
||||
*/
|
||||
public DimensionAnalysisVO getDimensionAnalysis(String dateRange, String month) {
|
||||
log.info("获取多维度分析数据: dateRange={}, month={}", dateRange, month);
|
||||
|
||||
// 计算时间范围
|
||||
LocalDateTime startTime;
|
||||
LocalDateTime endTime;
|
||||
|
||||
if (month != null && !month.isEmpty()) {
|
||||
try {
|
||||
LocalDate monthDate = LocalDate.parse(month + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
startTime = LocalDateTime.of(monthDate, LocalTime.MIN);
|
||||
endTime = LocalDateTime.of(monthDate.with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MAX);
|
||||
} catch (Exception e) {
|
||||
log.warn("月份格式错误: {}, 使用默认范围", month);
|
||||
startTime = calculateStartTime(dateRange);
|
||||
endTime = LocalDateTime.now();
|
||||
}
|
||||
} else {
|
||||
startTime = calculateStartTime(dateRange);
|
||||
endTime = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// 查询时间范围内的所有任务
|
||||
LambdaQueryWrapper<Task> queryWrapper = new LambdaQueryWrapper<Task>()
|
||||
.ge(startTime != null, Task::getCreateTime, startTime)
|
||||
.le(Task::getCreateTime, endTime)
|
||||
.ne(Task::getStatus, "draft"); // 排除草稿
|
||||
|
||||
List<Task> tasks = taskMapper.selectList(queryWrapper);
|
||||
log.info("查询到任务数量: {}", tasks.size());
|
||||
|
||||
DimensionAnalysisVO result = new DimensionAnalysisVO();
|
||||
result.setCategoryAnalysis(buildCategoryAnalysis(tasks));
|
||||
result.setBrandAnalysis(buildBrandAnalysis(tasks));
|
||||
result.setReceivingDeptAnalysis(buildReceivingDeptAnalysis(tasks));
|
||||
result.setDemandDeptAnalysis(buildDemandDeptAnalysis(tasks));
|
||||
result.setPlatformAnalysis(buildPlatformAnalysis(tasks));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建类目分析数据
|
||||
*/
|
||||
private List<DimensionAnalysisVO.CategoryAnalysis> buildCategoryAnalysis(List<Task> tasks) {
|
||||
// 按类目ID分组
|
||||
Map<Long, List<Task>> tasksByCategory = tasks.stream()
|
||||
.filter(t -> t.getCategoryId() != null)
|
||||
.collect(Collectors.groupingBy(Task::getCategoryId));
|
||||
|
||||
List<DimensionAnalysisVO.CategoryAnalysis> result = new ArrayList<>();
|
||||
|
||||
// 获取所有类目
|
||||
List<Category> categories = categoryMapper.selectList(null);
|
||||
Map<Long, String> categoryNames = categories.stream()
|
||||
.collect(Collectors.toMap(Category::getId, Category::getName, (a, b) -> a));
|
||||
|
||||
for (Map.Entry<Long, List<Task>> entry : tasksByCategory.entrySet()) {
|
||||
Long categoryId = entry.getKey();
|
||||
List<Task> categoryTasks = entry.getValue();
|
||||
|
||||
DimensionAnalysisVO.CategoryAnalysis analysis = new DimensionAnalysisVO.CategoryAnalysis();
|
||||
analysis.setCategoryId(categoryId);
|
||||
analysis.setCategoryName(categoryNames.getOrDefault(categoryId, "未知类目"));
|
||||
analysis.setTotalTasks(categoryTasks.size());
|
||||
|
||||
long completedCount = categoryTasks.stream()
|
||||
.filter(t -> "completed".equals(t.getStatus()))
|
||||
.count();
|
||||
analysis.setCompletedTasks((int) completedCount);
|
||||
|
||||
long inProgressCount = categoryTasks.stream()
|
||||
.filter(t -> "in_progress".equals(t.getStatus()))
|
||||
.count();
|
||||
analysis.setInProgressTasks((int) inProgressCount);
|
||||
|
||||
long pendingCount = categoryTasks.stream()
|
||||
.filter(t -> t.getStatus() != null && t.getStatus().contains("pending"))
|
||||
.count();
|
||||
analysis.setPendingTasks((int) pendingCount);
|
||||
|
||||
// 计算总积分
|
||||
BigDecimal totalScore = categoryTasks.stream()
|
||||
.filter(t -> t.getScore() != null)
|
||||
.map(Task::getScore)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
analysis.setTotalScore(totalScore);
|
||||
|
||||
// 计算完成率
|
||||
if (categoryTasks.size() > 0) {
|
||||
analysis.setCompletionRate(BigDecimal.valueOf(completedCount * 100.0 / categoryTasks.size())
|
||||
.setScale(2, RoundingMode.HALF_UP));
|
||||
} else {
|
||||
analysis.setCompletionRate(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
// 计算总数量
|
||||
int totalQuantity = categoryTasks.stream()
|
||||
.filter(t -> t.getQuantity() != null)
|
||||
.mapToInt(Task::getQuantity)
|
||||
.sum();
|
||||
analysis.setTotalQuantity(totalQuantity);
|
||||
|
||||
result.add(analysis);
|
||||
}
|
||||
|
||||
// 按任务数量降序排序
|
||||
result.sort((a, b) -> Integer.compare(b.getTotalTasks(), a.getTotalTasks()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建品牌分析数据
|
||||
*/
|
||||
private List<DimensionAnalysisVO.BrandAnalysis> buildBrandAnalysis(List<Task> tasks) {
|
||||
// 按品牌ID分组
|
||||
Map<Long, List<Task>> tasksByBrand = tasks.stream()
|
||||
.filter(t -> t.getBrandId() != null)
|
||||
.collect(Collectors.groupingBy(Task::getBrandId));
|
||||
|
||||
List<DimensionAnalysisVO.BrandAnalysis> result = new ArrayList<>();
|
||||
|
||||
// 获取所有品牌
|
||||
List<Brand> brands = brandMapper.selectList(null);
|
||||
Map<Long, String> brandNames = brands.stream()
|
||||
.collect(Collectors.toMap(Brand::getId, Brand::getName, (a, b) -> a));
|
||||
|
||||
for (Map.Entry<Long, List<Task>> entry : tasksByBrand.entrySet()) {
|
||||
Long brandId = entry.getKey();
|
||||
List<Task> brandTasks = entry.getValue();
|
||||
|
||||
DimensionAnalysisVO.BrandAnalysis analysis = new DimensionAnalysisVO.BrandAnalysis();
|
||||
analysis.setBrandId(brandId);
|
||||
analysis.setBrandName(brandNames.getOrDefault(brandId, "未知品牌"));
|
||||
analysis.setTotalTasks(brandTasks.size());
|
||||
|
||||
long completedCount = brandTasks.stream()
|
||||
.filter(t -> "completed".equals(t.getStatus()))
|
||||
.count();
|
||||
analysis.setCompletedTasks((int) completedCount);
|
||||
|
||||
long inProgressCount = brandTasks.stream()
|
||||
.filter(t -> "in_progress".equals(t.getStatus()))
|
||||
.count();
|
||||
analysis.setInProgressTasks((int) inProgressCount);
|
||||
|
||||
// 计算总积分
|
||||
BigDecimal totalScore = brandTasks.stream()
|
||||
.filter(t -> t.getScore() != null)
|
||||
.map(Task::getScore)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
analysis.setTotalScore(totalScore);
|
||||
|
||||
// 计算完成率
|
||||
if (brandTasks.size() > 0) {
|
||||
analysis.setCompletionRate(BigDecimal.valueOf(completedCount * 100.0 / brandTasks.size())
|
||||
.setScale(2, RoundingMode.HALF_UP));
|
||||
} else {
|
||||
analysis.setCompletionRate(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
// 计算总数量
|
||||
int totalQuantity = brandTasks.stream()
|
||||
.filter(t -> t.getQuantity() != null)
|
||||
.mapToInt(Task::getQuantity)
|
||||
.sum();
|
||||
analysis.setTotalQuantity(totalQuantity);
|
||||
|
||||
result.add(analysis);
|
||||
}
|
||||
|
||||
// 按任务数量降序排序
|
||||
result.sort((a, b) -> Integer.compare(b.getTotalTasks(), a.getTotalTasks()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建接单部门分析数据
|
||||
*/
|
||||
private List<DimensionAnalysisVO.ReceivingDeptAnalysis> buildReceivingDeptAnalysis(List<Task> tasks) {
|
||||
// 按接单部门ID分组
|
||||
Map<Long, List<Task>> tasksByDept = tasks.stream()
|
||||
.filter(t -> t.getReceiverDeptId() != null)
|
||||
.collect(Collectors.groupingBy(Task::getReceiverDeptId));
|
||||
|
||||
List<DimensionAnalysisVO.ReceivingDeptAnalysis> result = new ArrayList<>();
|
||||
|
||||
// 获取所有部门
|
||||
List<Department> departments = departmentMapper.selectList(null);
|
||||
Map<Long, String> deptNames = departments.stream()
|
||||
.collect(Collectors.toMap(d -> d.getId(), d -> d.getName(), (a, b) -> a));
|
||||
|
||||
for (Map.Entry<Long, List<Task>> entry : tasksByDept.entrySet()) {
|
||||
Long deptId = entry.getKey();
|
||||
List<Task> deptTasks = entry.getValue();
|
||||
|
||||
DimensionAnalysisVO.ReceivingDeptAnalysis analysis = new DimensionAnalysisVO.ReceivingDeptAnalysis();
|
||||
analysis.setDeptId(deptId);
|
||||
analysis.setDeptName(deptNames.getOrDefault(deptId, "未知部门"));
|
||||
analysis.setTotalTasks(deptTasks.size());
|
||||
|
||||
long completedCount = deptTasks.stream()
|
||||
.filter(t -> "completed".equals(t.getStatus()))
|
||||
.count();
|
||||
analysis.setCompletedTasks((int) completedCount);
|
||||
|
||||
long inProgressCount = deptTasks.stream()
|
||||
.filter(t -> "in_progress".equals(t.getStatus()))
|
||||
.count();
|
||||
analysis.setInProgressTasks((int) inProgressCount);
|
||||
|
||||
long pendingCount = deptTasks.stream()
|
||||
.filter(t -> t.getStatus() != null && t.getStatus().contains("pending"))
|
||||
.count();
|
||||
analysis.setPendingTasks((int) pendingCount);
|
||||
|
||||
// 计算总积分
|
||||
BigDecimal totalScore = deptTasks.stream()
|
||||
.filter(t -> t.getScore() != null)
|
||||
.map(Task::getScore)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
analysis.setTotalScore(totalScore);
|
||||
|
||||
// 计算完成率
|
||||
if (deptTasks.size() > 0) {
|
||||
analysis.setCompletionRate(BigDecimal.valueOf(completedCount * 100.0 / deptTasks.size())
|
||||
.setScale(2, RoundingMode.HALF_UP));
|
||||
} else {
|
||||
analysis.setCompletionRate(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
// 计算总数量
|
||||
int totalQuantity = deptTasks.stream()
|
||||
.filter(t -> t.getQuantity() != null)
|
||||
.mapToInt(Task::getQuantity)
|
||||
.sum();
|
||||
analysis.setTotalQuantity(totalQuantity);
|
||||
|
||||
// 计算平均完成时长(小时)
|
||||
List<Task> completedTasks = deptTasks.stream()
|
||||
.filter(t -> "completed".equals(t.getStatus()) && t.getCompleteTime() != null && t.getUpdateTime() != null)
|
||||
.collect(Collectors.toList());
|
||||
if (!completedTasks.isEmpty()) {
|
||||
double avgHours = completedTasks.stream()
|
||||
.mapToDouble(t -> {
|
||||
Duration duration = Duration.between(t.getCreateTime(), t.getCompleteTime());
|
||||
return duration.toHours();
|
||||
})
|
||||
.average()
|
||||
.orElse(0);
|
||||
analysis.setAvgCompletionHours(BigDecimal.valueOf(avgHours).setScale(2, RoundingMode.HALF_UP));
|
||||
} else {
|
||||
analysis.setAvgCompletionHours(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
result.add(analysis);
|
||||
}
|
||||
|
||||
// 按任务数量降序排序
|
||||
result.sort((a, b) -> Integer.compare(b.getTotalTasks(), a.getTotalTasks()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建需求部门分析数据
|
||||
*/
|
||||
private List<DimensionAnalysisVO.DemandDeptAnalysis> buildDemandDeptAnalysis(List<Task> tasks) {
|
||||
// 按需求部门ID分组
|
||||
Map<Long, List<Task>> tasksByDept = tasks.stream()
|
||||
.filter(t -> t.getPublisherDeptId() != null)
|
||||
.collect(Collectors.groupingBy(Task::getPublisherDeptId));
|
||||
|
||||
List<DimensionAnalysisVO.DemandDeptAnalysis> result = new ArrayList<>();
|
||||
|
||||
// 获取所有部门
|
||||
List<Department> departments = departmentMapper.selectList(null);
|
||||
Map<Long, String> deptNames = departments.stream()
|
||||
.collect(Collectors.toMap(d -> d.getId(), d -> d.getName(), (a, b) -> a));
|
||||
|
||||
for (Map.Entry<Long, List<Task>> entry : tasksByDept.entrySet()) {
|
||||
Long deptId = entry.getKey();
|
||||
List<Task> deptTasks = entry.getValue();
|
||||
|
||||
DimensionAnalysisVO.DemandDeptAnalysis analysis = new DimensionAnalysisVO.DemandDeptAnalysis();
|
||||
analysis.setDeptId(deptId);
|
||||
analysis.setDeptName(deptNames.getOrDefault(deptId, "未知部门"));
|
||||
analysis.setTotalTasks(deptTasks.size());
|
||||
|
||||
long completedCount = deptTasks.stream()
|
||||
.filter(t -> "completed".equals(t.getStatus()))
|
||||
.count();
|
||||
analysis.setCompletedTasks((int) completedCount);
|
||||
|
||||
long inProgressCount = deptTasks.stream()
|
||||
.filter(t -> "in_progress".equals(t.getStatus()))
|
||||
.count();
|
||||
analysis.setInProgressTasks((int) inProgressCount);
|
||||
|
||||
long pendingCount = deptTasks.stream()
|
||||
.filter(t -> t.getStatus() != null && t.getStatus().contains("pending"))
|
||||
.count();
|
||||
analysis.setPendingTasks((int) pendingCount);
|
||||
|
||||
// 计算总积分
|
||||
BigDecimal totalScore = deptTasks.stream()
|
||||
.filter(t -> t.getScore() != null)
|
||||
.map(Task::getScore)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
analysis.setTotalScore(totalScore);
|
||||
|
||||
// 计算完成率
|
||||
if (deptTasks.size() > 0) {
|
||||
analysis.setCompletionRate(BigDecimal.valueOf(completedCount * 100.0 / deptTasks.size())
|
||||
.setScale(2, RoundingMode.HALF_UP));
|
||||
} else {
|
||||
analysis.setCompletionRate(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
// 计算总数量
|
||||
int totalQuantity = deptTasks.stream()
|
||||
.filter(t -> t.getQuantity() != null)
|
||||
.mapToInt(Task::getQuantity)
|
||||
.sum();
|
||||
analysis.setTotalQuantity(totalQuantity);
|
||||
|
||||
// 需求部门不计算平均完成时长,设置为null
|
||||
analysis.setAvgCompletionHours(null);
|
||||
|
||||
result.add(analysis);
|
||||
}
|
||||
|
||||
// 按任务数量降序排序
|
||||
result.sort((a, b) -> Integer.compare(b.getTotalTasks(), a.getTotalTasks()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建平台分析数据
|
||||
*/
|
||||
private List<DimensionAnalysisVO.PlatformAnalysis> buildPlatformAnalysis(List<Task> tasks) {
|
||||
// 按平台ID分组
|
||||
Map<Long, List<Task>> tasksByPlatform = tasks.stream()
|
||||
.filter(t -> t.getPlatformId() != null)
|
||||
.collect(Collectors.groupingBy(Task::getPlatformId));
|
||||
|
||||
List<DimensionAnalysisVO.PlatformAnalysis> result = new ArrayList<>();
|
||||
|
||||
// 获取所有平台
|
||||
List<Platform> platforms = platformMapper.selectList(null);
|
||||
Map<Long, String> platformNames = platforms.stream()
|
||||
.collect(Collectors.toMap(Platform::getId, Platform::getName, (a, b) -> a));
|
||||
|
||||
for (Map.Entry<Long, List<Task>> entry : tasksByPlatform.entrySet()) {
|
||||
Long platformId = entry.getKey();
|
||||
List<Task> platformTasks = entry.getValue();
|
||||
|
||||
DimensionAnalysisVO.PlatformAnalysis analysis = new DimensionAnalysisVO.PlatformAnalysis();
|
||||
analysis.setPlatformId(platformId);
|
||||
analysis.setPlatformName(platformNames.getOrDefault(platformId, "未知平台"));
|
||||
analysis.setTotalTasks(platformTasks.size());
|
||||
|
||||
long completedCount = platformTasks.stream()
|
||||
.filter(t -> "completed".equals(t.getStatus()))
|
||||
.count();
|
||||
analysis.setCompletedTasks((int) completedCount);
|
||||
|
||||
long inProgressCount = platformTasks.stream()
|
||||
.filter(t -> "in_progress".equals(t.getStatus()))
|
||||
.count();
|
||||
analysis.setInProgressTasks((int) inProgressCount);
|
||||
|
||||
// 计算总积分
|
||||
BigDecimal totalScore = platformTasks.stream()
|
||||
.filter(t -> t.getScore() != null)
|
||||
.map(Task::getScore)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
analysis.setTotalScore(totalScore);
|
||||
|
||||
// 计算完成率
|
||||
if (platformTasks.size() > 0) {
|
||||
analysis.setCompletionRate(BigDecimal.valueOf(completedCount * 100.0 / platformTasks.size())
|
||||
.setScale(2, RoundingMode.HALF_UP));
|
||||
} else {
|
||||
analysis.setCompletionRate(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
// 计算总数量
|
||||
int totalQuantity = platformTasks.stream()
|
||||
.filter(t -> t.getQuantity() != null)
|
||||
.mapToInt(Task::getQuantity)
|
||||
.sum();
|
||||
analysis.setTotalQuantity(totalQuantity);
|
||||
|
||||
result.add(analysis);
|
||||
}
|
||||
|
||||
// 按任务数量降序排序
|
||||
result.sort((a, b) -> Integer.compare(b.getTotalTasks(), a.getTotalTasks()));
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
169
backend/src/main/java/com/designhub/vo/DimensionAnalysisVO.java
Normal file
169
backend/src/main/java/com/designhub/vo/DimensionAnalysisVO.java
Normal file
@ -0,0 +1,169 @@
|
||||
package com.designhub.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 多维分析数据VO
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class DimensionAnalysisVO {
|
||||
|
||||
/**
|
||||
* 类目维度分析
|
||||
*/
|
||||
private List<CategoryAnalysis> categoryAnalysis;
|
||||
|
||||
/**
|
||||
* 品牌维度分析
|
||||
*/
|
||||
private List<BrandAnalysis> brandAnalysis;
|
||||
|
||||
/**
|
||||
* 接单部门维度分析
|
||||
*/
|
||||
private List<ReceivingDeptAnalysis> receivingDeptAnalysis;
|
||||
|
||||
/**
|
||||
* 需求部门维度分析
|
||||
*/
|
||||
private List<DemandDeptAnalysis> demandDeptAnalysis;
|
||||
|
||||
/**
|
||||
* 平台维度分析
|
||||
*/
|
||||
private List<PlatformAnalysis> platformAnalysis;
|
||||
|
||||
/**
|
||||
* 类目分析数据
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class CategoryAnalysis {
|
||||
private Long categoryId;
|
||||
private String categoryName;
|
||||
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 Integer designerCount; // 参与设计师数量
|
||||
}
|
||||
|
||||
/**
|
||||
* 品牌分析数据
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class BrandAnalysis {
|
||||
private Long brandId;
|
||||
private String brandName;
|
||||
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 Integer categoryCount; // 涉及类目数量
|
||||
}
|
||||
|
||||
/**
|
||||
* 接单部门维度分析
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class ReceivingDeptAnalysis {
|
||||
private Long deptId;
|
||||
private String deptName;
|
||||
private Integer totalTasks; // 总接单数(主任务+子任务)
|
||||
private Integer completedTasks; // 已完成任务数
|
||||
private Integer inProgressTasks; // 进行中任务数
|
||||
private Integer pendingTasks; // 待接单任务数
|
||||
private Integer totalQuantity; // 总数量
|
||||
private Integer designerCount; // 设计师数量
|
||||
private Integer activeDesignerCount; // 活跃设计师数量(有接单的设计师)
|
||||
private BigDecimal totalScore; // 获得总积分
|
||||
private BigDecimal avgScore; // 平均积分
|
||||
private BigDecimal completionRate; // 完成率
|
||||
private Double onTimeRate; // 准时率
|
||||
private BigDecimal avgCompletionHours; // 平均完成时长(小时)
|
||||
private BigDecimal totalDeductionScore; // 总扣分
|
||||
private BigDecimal netScore; // 净积分
|
||||
private Double workloadRate; // 工作量占比
|
||||
}
|
||||
|
||||
/**
|
||||
* 需求部门维度分析
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class DemandDeptAnalysis {
|
||||
private Long deptId;
|
||||
private String deptName;
|
||||
private Integer totalTasks; // 总发布任务数
|
||||
private Integer completedTasks; // 已完成任务数
|
||||
private Integer inProgressTasks; // 进行中任务数
|
||||
private Integer pendingTasks; // 待接单任务数
|
||||
private Integer totalQuantity; // 总数量
|
||||
private Integer publisherCount; // 发布人数量
|
||||
private Integer activePublisherCount; // 活跃发布人数量
|
||||
private BigDecimal totalScore; // 消耗总积分
|
||||
private BigDecimal avgScore; // 平均积分
|
||||
private BigDecimal completionRate; // 完成率
|
||||
private Double onTimeRate; // 准时率
|
||||
private BigDecimal avgCompletionHours; // 平均完成时长(小时)
|
||||
private Integer brandCount; // 涉及品牌数量
|
||||
private Integer categoryCount; // 涉及类目数量
|
||||
}
|
||||
|
||||
/**
|
||||
* 平台维度分析
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class PlatformAnalysis {
|
||||
private Long platformId;
|
||||
private String platformName;
|
||||
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 Integer brandCount; // 涉及品牌数量
|
||||
private Integer categoryCount; // 涉及类目数量
|
||||
private Integer demandDeptCount; // 需求部门数量
|
||||
private Integer receivingDeptCount; // 接单部门数量
|
||||
}
|
||||
}
|
||||
@ -432,6 +432,141 @@
|
||||
</div>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="dimension" tab="多维度分析">
|
||||
<!-- 多维度分析内容 -->
|
||||
<div class="tab-content">
|
||||
<a-row :gutter="[16, 16]">
|
||||
<!-- 类目分析 -->
|
||||
<a-col :xs="24" :lg="12">
|
||||
<div class="chart-card">
|
||||
<h3 class="chart-title">类目分析</h3>
|
||||
<a-table
|
||||
:columns="categoryColumns"
|
||||
:data-source="dimensionData.categoryAnalysis"
|
||||
:pagination="{ pageSize: 5 }"
|
||||
row-key="categoryId"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'totalScore'">
|
||||
{{ formatScore(record.totalScore) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'completionRate'">
|
||||
<span :class="record.completionRate >= 80 ? 'text-success' : record.completionRate >= 50 ? 'text-warning' : 'text-danger'">
|
||||
{{ formatScore(record.completionRate) }}%
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<!-- 品牌分析 -->
|
||||
<a-col :xs="24" :lg="12">
|
||||
<div class="chart-card">
|
||||
<h3 class="chart-title">品牌分析</h3>
|
||||
<a-table
|
||||
:columns="brandColumns"
|
||||
:data-source="dimensionData.brandAnalysis"
|
||||
:pagination="{ pageSize: 5 }"
|
||||
row-key="brandId"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'totalScore'">
|
||||
{{ formatScore(record.totalScore) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'completionRate'">
|
||||
<span :class="record.completionRate >= 80 ? 'text-success' : record.completionRate >= 50 ? 'text-warning' : 'text-danger'">
|
||||
{{ formatScore(record.completionRate) }}%
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<!-- 接单部门分析 -->
|
||||
<a-col :xs="24" :lg="12">
|
||||
<div class="chart-card">
|
||||
<h3 class="chart-title">接单部门分析</h3>
|
||||
<a-table
|
||||
:columns="receivingDeptColumns"
|
||||
:data-source="dimensionData.receivingDeptAnalysis"
|
||||
:pagination="{ pageSize: 5 }"
|
||||
row-key="deptId"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'totalScore'">
|
||||
{{ formatScore(record.totalScore) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'completionRate'">
|
||||
<span :class="record.completionRate >= 80 ? 'text-success' : record.completionRate >= 50 ? 'text-warning' : 'text-danger'">
|
||||
{{ formatScore(record.completionRate) }}%
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'avgCompletionHours'">
|
||||
{{ record.avgCompletionHours ? formatScore(record.avgCompletionHours) + 'h' : '-' }}
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<!-- 需求部门分析 -->
|
||||
<a-col :xs="24" :lg="12">
|
||||
<div class="chart-card">
|
||||
<h3 class="chart-title">需求部门分析</h3>
|
||||
<a-table
|
||||
:columns="demandDeptColumns"
|
||||
:data-source="dimensionData.demandDeptAnalysis"
|
||||
:pagination="{ pageSize: 5 }"
|
||||
row-key="deptId"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'totalScore'">
|
||||
{{ formatScore(record.totalScore) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'completionRate'">
|
||||
<span :class="record.completionRate >= 80 ? 'text-success' : record.completionRate >= 50 ? 'text-warning' : 'text-danger'">
|
||||
{{ formatScore(record.completionRate) }}%
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<!-- 平台分析 -->
|
||||
<a-col :xs="24">
|
||||
<div class="chart-card">
|
||||
<h3 class="chart-title">平台分析</h3>
|
||||
<a-table
|
||||
:columns="platformColumns"
|
||||
:data-source="dimensionData.platformAnalysis"
|
||||
:pagination="{ pageSize: 10 }"
|
||||
row-key="platformId"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'totalScore'">
|
||||
{{ formatScore(record.totalScore) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'completionRate'">
|
||||
<span :class="record.completionRate >= 80 ? 'text-success' : record.completionRate >= 50 ? 'text-warning' : 'text-danger'">
|
||||
{{ formatScore(record.completionRate) }}%
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</a-spin>
|
||||
@ -525,13 +660,15 @@ import {
|
||||
getProcessEfficiency,
|
||||
getWarningTasks,
|
||||
getDesignerDeductionDetails,
|
||||
getDimensionAnalysis,
|
||||
type DemandStatistics,
|
||||
type DesignerPerformance,
|
||||
type ProcessEfficiency,
|
||||
type WarningTask,
|
||||
type DeductionDetail,
|
||||
type DelayDeductionItem,
|
||||
type ProgressDeductionItem
|
||||
type ProgressDeductionItem,
|
||||
type DimensionAnalysis
|
||||
} from '@/api/analytics'
|
||||
import { message } from 'ant-design-vue'
|
||||
import * as echarts from 'echarts'
|
||||
@ -627,6 +764,15 @@ const processEfficiency = reactive<ProcessEfficiency>({
|
||||
const warningTasks = ref<WarningTask[]>([])
|
||||
const warningFilter = ref('all')
|
||||
|
||||
// 多维度分析数据
|
||||
const dimensionData = reactive<DimensionAnalysis>({
|
||||
categoryAnalysis: [],
|
||||
brandAnalysis: [],
|
||||
receivingDeptAnalysis: [],
|
||||
demandDeptAnalysis: [],
|
||||
platformAnalysis: []
|
||||
})
|
||||
|
||||
// 部门筛选
|
||||
const selectedDeptId = ref<number | undefined>(undefined)
|
||||
const departmentList = ref<any[]>([])
|
||||
@ -795,6 +941,58 @@ const progressDeductionColumns = [
|
||||
{ title: '扣分原因', dataIndex: 'reason', key: 'reason' }
|
||||
]
|
||||
|
||||
// 多维度分析表格列定义
|
||||
const categoryColumns = [
|
||||
{ title: '类目名称', dataIndex: 'categoryName', key: 'categoryName', ellipsis: true },
|
||||
{ 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: '完成率', key: 'completionRate', align: 'center', width: 80 },
|
||||
{ title: '总积分', key: 'totalScore', align: 'center', width: 80 }
|
||||
]
|
||||
|
||||
const brandColumns = [
|
||||
{ title: '品牌名称', dataIndex: 'brandName', key: 'brandName', ellipsis: true },
|
||||
{ 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: '完成率', key: 'completionRate', align: 'center', width: 80 },
|
||||
{ title: '总积分', key: 'totalScore', align: 'center', width: 80 }
|
||||
]
|
||||
|
||||
const receivingDeptColumns = [
|
||||
{ title: '部门名称', dataIndex: 'deptName', key: 'deptName', ellipsis: true },
|
||||
{ title: '任务数', dataIndex: 'totalTasks', key: 'totalTasks', align: 'center', width: 70 },
|
||||
{ title: '总数量', dataIndex: 'totalQuantity', key: 'totalQuantity', align: 'center', width: 70 },
|
||||
{ title: '已完成', dataIndex: 'completedTasks', key: 'completedTasks', align: 'center', width: 70 },
|
||||
{ title: '进行中', dataIndex: 'inProgressTasks', key: 'inProgressTasks', align: 'center', width: 70 },
|
||||
{ title: '完成率', key: 'completionRate', align: 'center', width: 70 },
|
||||
{ title: '总积分', key: 'totalScore', align: 'center', width: 70 },
|
||||
{ title: '平均耗时', key: 'avgCompletionHours', align: 'center', width: 90 }
|
||||
]
|
||||
|
||||
const demandDeptColumns = [
|
||||
{ title: '部门名称', dataIndex: 'deptName', key: 'deptName', ellipsis: true },
|
||||
{ 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: 'pendingTasks', key: 'pendingTasks', align: 'center', width: 80 },
|
||||
{ title: '完成率', key: 'completionRate', align: 'center', width: 80 },
|
||||
{ title: '总积分', key: 'totalScore', align: 'center', width: 80 }
|
||||
]
|
||||
|
||||
const platformColumns = [
|
||||
{ title: '平台名称', dataIndex: 'platformName', key: 'platformName', ellipsis: true },
|
||||
{ title: '任务数', dataIndex: 'totalTasks', key: 'totalTasks', align: 'center', width: 100 },
|
||||
{ title: '总数量', dataIndex: 'totalQuantity', key: 'totalQuantity', align: 'center', width: 100 },
|
||||
{ title: '已完成', dataIndex: 'completedTasks', key: 'completedTasks', align: 'center', width: 100 },
|
||||
{ title: '进行中', dataIndex: 'inProgressTasks', key: 'inProgressTasks', align: 'center', width: 100 },
|
||||
{ title: '完成率', key: 'completionRate', align: 'center', width: 100 },
|
||||
{ title: '总积分', key: 'totalScore', align: 'center', width: 100 }
|
||||
]
|
||||
|
||||
// 方法
|
||||
const handleMonthChange = (value: string | null) => {
|
||||
selectedMonth.value = value
|
||||
@ -820,11 +1018,12 @@ const loadAllData = async () => {
|
||||
const month = selectedMonth.value || undefined
|
||||
|
||||
// 并行加载所有数据
|
||||
const [demandData, designerData, efficiencyData, warningsData] = await Promise.all([
|
||||
const [demandData, designerData, efficiencyData, warningsData, dimensionAnalysisData] = await Promise.all([
|
||||
getDemandStatistics(dateRange.value, undefined, undefined, month),
|
||||
getDesignerPerformance(month, selectedDeptId.value),
|
||||
getProcessEfficiency(month),
|
||||
getWarningTasks()
|
||||
getWarningTasks(),
|
||||
getDimensionAnalysis(dateRange.value, month)
|
||||
])
|
||||
|
||||
// 更新需求统计
|
||||
@ -839,6 +1038,9 @@ const loadAllData = async () => {
|
||||
// 更新预警任务
|
||||
warningTasks.value = warningsData
|
||||
|
||||
// 更新多维度分析数据
|
||||
Object.assign(dimensionData, dimensionAnalysisData)
|
||||
|
||||
await nextTick()
|
||||
|
||||
// 渲染当前Tab的图表
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user