添加多维度分析功能:类目/品牌/接单部门/需求部门/平台维度分析

This commit is contained in:
Your Name 2026-01-16 15:01:17 +08:00
parent 1b2525dd9c
commit 7c618fd3a4
4 changed files with 801 additions and 3 deletions

View File

@ -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));
}
}

View File

@ -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;
}
}

View 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; // 接单部门数量
}
}

View File

@ -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的图表