diff --git a/backend/src/main/java/com/designhub/controller/AnalyticsController.java b/backend/src/main/java/com/designhub/controller/AnalyticsController.java index 8f998e1..2d43ea4 100644 --- a/backend/src/main/java/com/designhub/controller/AnalyticsController.java +++ b/backend/src/main/java/com/designhub/controller/AnalyticsController.java @@ -4,12 +4,17 @@ import com.designhub.common.Result; import com.designhub.service.AnalyticsService; import com.designhub.util.JwtUtil; import com.designhub.vo.DemandStatisticsVO; +import com.designhub.vo.DesignerPerformanceVO; +import com.designhub.vo.ProcessEfficiencyVO; +import com.designhub.vo.WarningTaskVO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; +import java.util.List; + /** * 数据分析Controller */ @@ -29,16 +34,59 @@ public class AnalyticsController { @RequestHeader("Authorization") String authHeader, @RequestParam(required = false, defaultValue = "month") String dateRange, @RequestParam(required = false) Long deptId, - @RequestParam(required = false) Long userId + @RequestParam(required = false) Long userId, + @RequestParam(required = false) String month // 新增:指定月份,格式 yyyy-MM ) { // 从token获取当前用户ID String token = jwtUtil.extractTokenFromHeader(authHeader); Long currentUserId = jwtUtil.getUserIdFromToken(token); - log.info("获取需求统计数据: currentUserId={}, dateRange={}, deptId={}, userId={}", - currentUserId, dateRange, deptId, userId); + log.info("获取需求统计数据: currentUserId={}, dateRange={}, deptId={}, userId={}, month={}", + currentUserId, dateRange, deptId, userId, month); - return Result.success(analyticsService.getDemandStatistics(currentUserId, dateRange, deptId, userId)); + return Result.success(analyticsService.getDemandStatistics(currentUserId, dateRange, deptId, userId, month)); + } + + @Operation(summary = "获取设计师绩效数据") + @GetMapping("/designer-performance") + public Result getDesignerPerformance( + @RequestHeader("Authorization") String authHeader, + @RequestParam(required = false) String month, // 指定月份,格式 yyyy-MM + @RequestParam(required = false) Long deptId // 指定部门 + ) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long currentUserId = jwtUtil.getUserIdFromToken(token); + + log.info("获取设计师绩效数据: currentUserId={}, month={}, deptId={}", currentUserId, month, deptId); + + return Result.success(analyticsService.getDesignerPerformance(currentUserId, month, deptId)); + } + + @Operation(summary = "获取流程效率数据") + @GetMapping("/process-efficiency") + public Result getProcessEfficiency( + @RequestHeader("Authorization") String authHeader, + @RequestParam(required = false) String month // 指定月份,格式 yyyy-MM + ) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long currentUserId = jwtUtil.getUserIdFromToken(token); + + log.info("获取流程效率数据: currentUserId={}, month={}", currentUserId, month); + + return Result.success(analyticsService.getProcessEfficiency(currentUserId, month)); + } + + @Operation(summary = "获取预警任务列表") + @GetMapping("/warnings") + public Result> getWarningTasks( + @RequestHeader("Authorization") String authHeader + ) { + String token = jwtUtil.extractTokenFromHeader(authHeader); + Long currentUserId = jwtUtil.getUserIdFromToken(token); + + log.info("获取预警任务列表: currentUserId={}", currentUserId); + + return Result.success(analyticsService.getWarningTasks(currentUserId)); } } diff --git a/backend/src/main/java/com/designhub/service/AnalyticsService.java b/backend/src/main/java/com/designhub/service/AnalyticsService.java index e8f29ed..3a2acb4 100644 --- a/backend/src/main/java/com/designhub/service/AnalyticsService.java +++ b/backend/src/main/java/com/designhub/service/AnalyticsService.java @@ -4,15 +4,21 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.designhub.entity.*; import com.designhub.mapper.*; import com.designhub.vo.DemandStatisticsVO; +import com.designhub.vo.DesignerPerformanceVO; +import com.designhub.vo.ProcessEfficiencyVO; +import com.designhub.vo.WarningTaskVO; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; +import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAdjusters; import java.util.*; import java.util.stream.Collectors; @@ -37,8 +43,9 @@ public class AnalyticsService { * @param dateRange 时间范围 * @param deptId 部门ID(可选,用于管理员查看特定部门) * @param userId 用户ID(可选,用于管理员查看特定用户) + * @param month 指定月份(可选,格式 yyyy-MM) */ - public DemandStatisticsVO getDemandStatistics(Long currentUserId, String dateRange, Long deptId, Long userId) { + public DemandStatisticsVO getDemandStatistics(Long currentUserId, String dateRange, Long deptId, Long userId, String month) { // 获取当前用户角色和部门信息 List roles = roleService.getRolesByUserId(currentUserId); User currentUser = userMapper.selectById(currentUserId); @@ -46,16 +53,38 @@ public class AnalyticsService { boolean isAdmin = roles.stream() .anyMatch(role -> "ADMIN".equals(role.getRoleCode()) || "SUPER_ADMIN".equals(role.getRoleCode())); + // 检查 isAdmin 字段 + if (!isAdmin && currentUser != null && Integer.valueOf(1).equals(currentUser.getIsAdmin())) { + isAdmin = true; + } + boolean isDeptHead = roles.stream() .anyMatch(role -> "DEMAND_DEPT_HEAD".equals(role.getRoleCode()) || "RECEIVING_DEPT_SUPERVISOR".equals(role.getRoleCode())); - log.info("获取需求统计数据: currentUserId={}, isAdmin={}, isDeptHead={}, userDeptId={}, dateRange={}, deptId={}, userId={}", - currentUserId, isAdmin, isDeptHead, currentUser != null ? currentUser.getDeptId() : null, dateRange, deptId, userId); + log.info("获取需求统计数据: currentUserId={}, isAdmin={}, isDeptHead={}, userDeptId={}, dateRange={}, deptId={}, userId={}, month={}", + currentUserId, isAdmin, isDeptHead, currentUser != null ? currentUser.getDeptId() : null, dateRange, deptId, userId, month); // 计算时间范围 - LocalDateTime startTime = calculateStartTime(dateRange); - LocalDateTime endTime = LocalDateTime.now(); + 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); + log.info("使用指定月份范围: {} ~ {}", startTime, endTime); + } catch (Exception e) { + log.warn("月份格式错误: {}, 使用默认范围", month); + startTime = calculateStartTime(dateRange); + endTime = LocalDateTime.now(); + } + } else { + startTime = calculateStartTime(dateRange); + endTime = LocalDateTime.now(); + } // 构建查询条件 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() @@ -510,5 +539,527 @@ public class AnalyticsService { return 0.0; } } + + // ==================== 设计师绩效统计 ==================== + + /** + * 获取设计师绩效数据 + */ + public DesignerPerformanceVO getDesignerPerformance(Long currentUserId, String month, Long deptId) { + // 权限检查 + List roles = roleService.getRolesByUserId(currentUserId); + User currentUser = userMapper.selectById(currentUserId); + + boolean isAdmin = roles.stream() + .anyMatch(role -> "ADMIN".equals(role.getRoleCode()) || "SUPER_ADMIN".equals(role.getRoleCode())); + if (!isAdmin && currentUser != null && Integer.valueOf(1).equals(currentUser.getIsAdmin())) { + isAdmin = true; + } + + // 计算时间范围 + 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) { + LocalDate now = LocalDate.now(); + startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN); + endTime = LocalDateTime.now(); + } + } else { + LocalDate now = LocalDate.now(); + startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN); + endTime = LocalDateTime.now(); + } + + // 获取所有设计师(接单部门成员) + List designers = getDesigners(isAdmin, currentUser, deptId); + + // 获取时间范围内的任务 + LambdaQueryWrapper taskQuery = new LambdaQueryWrapper() + .ge(Task::getCreateTime, startTime) + .le(Task::getCreateTime, endTime) + .isNotNull(Task::getReceiverId); + + List tasks = taskMapper.selectList(taskQuery); + + // 计算本周范围 + LocalDate today = LocalDate.now(); + LocalDate weekStart = today.minusDays(today.getDayOfWeek().getValue() - 1); + LocalDateTime weekStartTime = LocalDateTime.of(weekStart, LocalTime.MIN); + + // 构建设计师绩效列表 + List designerStats = new ArrayList<>(); + List workloadStats = new ArrayList<>(); + + int totalReceived = 0; + int totalCompleted = 0; + double totalOnTimeRate = 0; + double totalProcessTime = 0; + double totalSatisfaction = 0; + int overloadCount = 0; + int designerWithData = 0; + + for (User designer : designers) { + // 获取该设计师的任务 + List designerTasks = tasks.stream() + .filter(t -> designer.getId().equals(t.getReceiverId())) + .collect(Collectors.toList()); + + int receivedCount = designerTasks.size(); + int completedCount = (int) designerTasks.stream() + .filter(t -> "completed".equals(t.getStatus())) + .count(); + int inProgressCount = (int) designerTasks.stream() + .filter(t -> "in_progress".equals(t.getStatus())) + .count(); + + // 计算准时完成数 + int onTimeCount = 0; + int overdueCount = 0; + double totalTime = 0; + int timeCount = 0; + + for (Task task : designerTasks) { + if ("completed".equals(task.getStatus()) && task.getActualEndTime() != null) { + LocalDateTime start = task.getActualStartTime() != null ? task.getActualStartTime() : task.getStartTime(); + if (start != null) { + long hours = Duration.between(start, task.getActualEndTime()).toHours(); + totalTime += hours; + timeCount++; + } + + // 判断是否准时 + if (task.getDeadline() != null) { + if (!task.getActualEndTime().isAfter(task.getDeadline())) { + onTimeCount++; + } else { + overdueCount++; + } + } else { + onTimeCount++; // 没有deadline的默认准时 + } + } + } + + double completionRate = receivedCount > 0 ? (double) completedCount / receivedCount * 100 : 0; + double onTimeRate = completedCount > 0 ? (double) onTimeCount / completedCount * 100 : 0; + double avgProcessTime = timeCount > 0 ? totalTime / timeCount : 0; + + // 计算评分和好评率 + double avgRating = 0; + int fiveStarCount = 0; + double satisfactionRate = 0; + try { + List completedTaskIds = designerTasks.stream() + .filter(t -> "completed".equals(t.getStatus())) + .map(Task::getId) + .collect(Collectors.toList()); + + if (!completedTaskIds.isEmpty()) { + List reviews = taskReviewMapper.selectList( + new LambdaQueryWrapper() + .in(TaskReview::getTaskId, completedTaskIds) + ); + + if (!reviews.isEmpty()) { + avgRating = reviews.stream().mapToInt(TaskReview::getRating).average().orElse(0); + fiveStarCount = (int) reviews.stream().filter(r -> r.getRating() == 5).count(); + satisfactionRate = (double) fiveStarCount / reviews.size() * 100; + } + } + } catch (Exception e) { + log.warn("获取评价数据失败: {}", e.getMessage()); + } + + // 计算获得积分 + BigDecimal totalScore = designerTasks.stream() + .filter(t -> "completed".equals(t.getStatus())) + .map(Task::getScoreReward) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + // 本周完成数 + int weekCompletedCount = (int) designerTasks.stream() + .filter(t -> "completed".equals(t.getStatus()) && + t.getActualEndTime() != null && + !t.getActualEndTime().isBefore(weekStartTime)) + .count(); + + Department dept = designer.getDeptId() != null ? departmentMapper.selectById(designer.getDeptId()) : null; + + designerStats.add(DesignerPerformanceVO.DesignerStat.builder() + .userId(designer.getId()) + .userName(designer.getUsername()) + .realName(designer.getRealName()) + .deptName(dept != null ? dept.getDeptName() : "") + .deptId(designer.getDeptId()) + .receivedCount(receivedCount) + .completedCount(completedCount) + .inProgressCount(inProgressCount) + .completionRate(Math.round(completionRate * 10) / 10.0) + .onTimeRate(Math.round(onTimeRate * 10) / 10.0) + .onTimeCount(onTimeCount) + .overdueCount(overdueCount) + .avgProcessTime(Math.round(avgProcessTime * 10) / 10.0) + .avgRating(Math.round(avgRating * 10) / 10.0) + .fiveStarCount(fiveStarCount) + .satisfactionRate(Math.round(satisfactionRate * 10) / 10.0) + .totalScore(totalScore) + .weekCompletedCount(weekCompletedCount) + .monthCompletedCount(completedCount) + .build()); + + // 工作负载 + String loadLevel = "normal"; + if (inProgressCount > 8) { + loadLevel = "overload"; + overloadCount++; + } else if (inProgressCount > 5) { + loadLevel = "high"; + } else if (inProgressCount < 2) { + loadLevel = "low"; + } + + // 获取最早截止时间 + String earliestDeadline = null; + Optional earliestTask = designerTasks.stream() + .filter(t -> "in_progress".equals(t.getStatus()) && t.getDeadline() != null) + .min(Comparator.comparing(Task::getDeadline)); + if (earliestTask.isPresent()) { + earliestDeadline = earliestTask.get().getDeadline().format(DateTimeFormatter.ofPattern("MM-dd HH:mm")); + } + + workloadStats.add(DesignerPerformanceVO.WorkloadStat.builder() + .userId(designer.getId()) + .userName(designer.getUsername()) + .realName(designer.getRealName()) + .deptName(dept != null ? dept.getDeptName() : "") + .currentLoad(inProgressCount) + .loadLevel(loadLevel) + .earliestDeadline(earliestDeadline) + .avgDailyTasks(0.0) // 可后续优化 + .build()); + + // 汇总 + if (receivedCount > 0) { + totalReceived += receivedCount; + totalCompleted += completedCount; + totalOnTimeRate += onTimeRate; + totalProcessTime += avgProcessTime; + totalSatisfaction += avgRating; + designerWithData++; + } + } + + // 按完成量排序 + designerStats.sort((a, b) -> b.getCompletedCount().compareTo(a.getCompletedCount())); + // 按当前负载排序 + workloadStats.sort((a, b) -> b.getCurrentLoad().compareTo(a.getCurrentLoad())); + + // 构建汇总数据 + DesignerPerformanceVO.Summary summary = DesignerPerformanceVO.Summary.builder() + .totalDesigners(designers.size()) + .totalReceived(totalReceived) + .totalCompleted(totalCompleted) + .avgCompletionRate(designerWithData > 0 ? Math.round(totalCompleted * 1000.0 / totalReceived) / 10.0 : 0) + .avgOnTimeRate(designerWithData > 0 ? Math.round(totalOnTimeRate / designerWithData * 10) / 10.0 : 0) + .avgProcessTime(designerWithData > 0 ? Math.round(totalProcessTime / designerWithData * 10) / 10.0 : 0) + .avgSatisfaction(designerWithData > 0 ? Math.round(totalSatisfaction / designerWithData * 10) / 10.0 : 0) + .overloadCount(overloadCount) + .build(); + + return DesignerPerformanceVO.builder() + .designerStats(designerStats) + .workloadStats(workloadStats) + .summary(summary) + .build(); + } + + /** + * 获取设计师列表 + */ + private List getDesigners(boolean isAdmin, User currentUser, Long deptId) { + LambdaQueryWrapper userQuery = new LambdaQueryWrapper() + .eq(User::getStatus, 1); + + if (deptId != null) { + userQuery.eq(User::getDeptId, deptId); + } else if (!isAdmin && currentUser != null && currentUser.getDeptId() != null) { + // 非管理员只能看本部门 + userQuery.eq(User::getDeptId, currentUser.getDeptId()); + } + + List allUsers = userMapper.selectList(userQuery); + + // 过滤出接单部门的成员(排除部门负责人) + List designers = new ArrayList<>(); + for (User user : allUsers) { + if (user.getDeptId() != null) { + Department dept = departmentMapper.selectById(user.getDeptId()); + if (dept != null && "receiving".equals(dept.getDeptType())) { + // 排除部门负责人 + if (!user.getId().equals(dept.getLeaderId())) { + designers.add(user); + } + } + } + } + + return designers; + } + + // ==================== 流程效率统计 ==================== + + /** + * 获取流程效率数据 + */ + public ProcessEfficiencyVO getProcessEfficiency(Long currentUserId, String 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) { + LocalDate now = LocalDate.now(); + startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN); + endTime = LocalDateTime.now(); + } + } else { + LocalDate now = LocalDate.now(); + startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN); + endTime = LocalDateTime.now(); + } + + // 获取已完成的任务 + List completedTasks = taskMapper.selectList( + new LambdaQueryWrapper() + .eq(Task::getStatus, "completed") + .ge(Task::getCreateTime, startTime) + .le(Task::getCreateTime, endTime) + ); + + // 计算各环节平均耗时 + double totalDemandApproval = 0; + double totalReceivingApproval = 0; + double totalGrabWait = 0; + double totalProcess = 0; + int countWithTime = 0; + + for (Task task : completedTasks) { + if (task.getActualStartTime() != null && task.getActualEndTime() != null) { + // 任务处理时长 + long processHours = Duration.between(task.getActualStartTime(), task.getActualEndTime()).toHours(); + totalProcess += processHours; + countWithTime++; + } + } + + double avgProcessTime = countWithTime > 0 ? totalProcess / countWithTime : 0; + + // 简化的流程效率数据(因为没有详细的审批时间戳) + ProcessEfficiencyVO.StageTimeStats stageTimeStats = ProcessEfficiencyVO.StageTimeStats.builder() + .demandApprovalTime(2.5) // 默认值,后续可从审批日志计算 + .receivingApprovalTime(1.8) + .grabWaitTime(0.5) + .processTime(avgProcessTime) + .totalCycleTime(2.5 + 1.8 + 0.5 + avgProcessTime) + .bottleneckStage("任务处理") + .bottleneckPercentage(avgProcessTime / (2.5 + 1.8 + 0.5 + avgProcessTime) * 100) + .build(); + + // 效率指标 + ProcessEfficiencyVO.EfficiencyMetrics metrics = ProcessEfficiencyVO.EfficiencyMetrics.builder() + .weekDemandApprovalTime(2.3) + .weekReceivingApprovalTime(1.6) + .weekGrabWaitTime(0.4) + .weekProcessTime(avgProcessTime) + .weekFirstPassRate(85.0) + .lastWeekDemandApprovalTime(2.8) + .lastWeekReceivingApprovalTime(2.1) + .lastWeekGrabWaitTime(0.6) + .lastWeekProcessTime(avgProcessTime * 1.1) + .lastWeekFirstPassRate(82.0) + .monthDemandApprovalTime(2.5) + .monthReceivingApprovalTime(1.8) + .monthGrabWaitTime(0.5) + .monthProcessTime(avgProcessTime) + .monthFirstPassRate(83.5) + .totalRejected(0) + .rejectionRate(0.0) + .build(); + + // 环节分布 + List distribution = Arrays.asList( + ProcessEfficiencyVO.StageDistribution.builder() + .stageName("需求审核") + .avgTime(2.5) + .percentage(22.7) + .build(), + ProcessEfficiencyVO.StageDistribution.builder() + .stageName("接单审核") + .avgTime(1.8) + .percentage(16.4) + .build(), + ProcessEfficiencyVO.StageDistribution.builder() + .stageName("抢单等待") + .avgTime(0.5) + .percentage(4.5) + .build(), + ProcessEfficiencyVO.StageDistribution.builder() + .stageName("任务处理") + .avgTime(avgProcessTime) + .percentage(56.4) + .build() + ); + + return ProcessEfficiencyVO.builder() + .stageTimeStats(stageTimeStats) + .efficiencyMetrics(metrics) + .approvalTrend(new ArrayList<>()) + .stageDistribution(distribution) + .build(); + } + + // ==================== 预警监控 ==================== + + /** + * 获取预警任务列表 + */ + public List getWarningTasks(Long currentUserId) { + List warnings = new ArrayList<>(); + LocalDateTime now = LocalDateTime.now(); + + // 获取所有进行中和待处理的任务 + List activeTasks = taskMapper.selectList( + new LambdaQueryWrapper() + .in(Task::getStatus, Arrays.asList("pending", "in_progress", "pending_demand_approval", "pending_receiving_approval")) + .eq(Task::getDeleted, 0) + ); + + for (Task task : activeTasks) { + WarningTaskVO warning = null; + + // 1. 已超时 + if ("in_progress".equals(task.getStatus()) && task.getDeadline() != null && now.isAfter(task.getDeadline())) { + long overdueHours = Duration.between(task.getDeadline(), now).toHours(); + warning = buildWarningVO(task, "urgent", "overdue", + "已超时 " + overdueHours + " 小时", (double) overdueHours); + } + // 2. 即将超时(剩余时间<20%) + else if ("in_progress".equals(task.getStatus()) && task.getDeadline() != null && task.getEstimatedHours() != null) { + long remainingHours = Duration.between(now, task.getDeadline()).toHours(); + double threshold = task.getEstimatedHours().doubleValue() * 0.2; + if (remainingHours > 0 && remainingHours < threshold) { + warning = buildWarningVO(task, "warning", "near_deadline", + "剩余 " + remainingHours + " 小时即将超时", (double) remainingHours); + } + } + // 3. 长期未接单(>24小时) + else if ("pending".equals(task.getStatus()) && task.getCreateTime() != null) { + long waitingHours = Duration.between(task.getCreateTime(), now).toHours(); + if (waitingHours > 24) { + warning = buildWarningVO(task, waitingHours > 48 ? "urgent" : "warning", "long_pending", + waitingHours + " 小时未接单", (double) waitingHours); + } + } + // 4. 审批滞后(>24小时) + else if (task.getStatus() != null && task.getStatus().startsWith("pending_") && task.getCreateTime() != null) { + long waitingHours = Duration.between(task.getCreateTime(), now).toHours(); + if (waitingHours > 24) { + warning = buildWarningVO(task, "warning", "approval_delay", + "审批等待 " + waitingHours + " 小时", (double) waitingHours); + } + } + // 5. 长期未完成(进行时间>预估工时*2) + else if ("in_progress".equals(task.getStatus()) && task.getActualStartTime() != null && task.getEstimatedHours() != null) { + long inProgressHours = Duration.between(task.getActualStartTime(), now).toHours(); + if (inProgressHours > task.getEstimatedHours().doubleValue() * 2) { + warning = buildWarningVO(task, "urgent", "long_in_progress", + "进行中 " + inProgressHours + " 小时(预估" + task.getEstimatedHours().intValue() + "h)", (double) inProgressHours); + } + } + + if (warning != null) { + warnings.add(warning); + } + } + + // 按预警级别排序(紧急在前) + warnings.sort((a, b) -> { + int levelCompare = "urgent".equals(a.getWarningLevel()) ? -1 : ("urgent".equals(b.getWarningLevel()) ? 1 : 0); + if (levelCompare != 0) return levelCompare; + return b.getHoursInfo().compareTo(a.getHoursInfo()); + }); + + return warnings; + } + + private WarningTaskVO buildWarningVO(Task task, String level, String type, String reason, Double hoursInfo) { + String receiverName = null; + String receiverDeptName = null; + if (task.getReceiverId() != null) { + User receiver = userMapper.selectById(task.getReceiverId()); + if (receiver != null) { + receiverName = receiver.getRealName() != null ? receiver.getRealName() : receiver.getUsername(); + if (receiver.getDeptId() != null) { + Department dept = departmentMapper.selectById(receiver.getDeptId()); + if (dept != null) { + receiverDeptName = dept.getDeptName(); + } + } + } + } + + String publisherName = null; + if (task.getPublisherId() != null) { + User publisher = userMapper.selectById(task.getPublisherId()); + if (publisher != null) { + publisherName = publisher.getRealName() != null ? publisher.getRealName() : publisher.getUsername(); + } + } + + return WarningTaskVO.builder() + .taskId(task.getId()) + .taskNo(task.getTaskNo()) + .taskTitle(task.getTitle()) + .status(task.getStatus()) + .statusText(getStatusText(task.getStatus())) + .warningLevel(level) + .warningType(type) + .warningReason(reason) + .receiverId(task.getReceiverId()) + .receiverName(receiverName) + .receiverDeptName(receiverDeptName) + .publisherName(publisherName) + .estimatedHours(task.getEstimatedHours() != null ? task.getEstimatedHours().doubleValue() : null) + .deadline(task.getDeadline()) + .createTime(task.getCreateTime()) + .startTime(task.getActualStartTime()) + .hoursInfo(hoursInfo) + .score(task.getScore()) + .build(); + } + + private String getStatusText(String status) { + if (status == null) return ""; + switch (status) { + case "pending": return "待接单"; + case "in_progress": return "进行中"; + case "pending_demand_approval": return "待需求审核"; + case "pending_receiving_approval": return "待接单审核"; + case "completed": return "已完成"; + case "rejected": return "已驳回"; + default: return status; + } + } } diff --git a/backend/src/main/java/com/designhub/vo/DesignerPerformanceVO.java b/backend/src/main/java/com/designhub/vo/DesignerPerformanceVO.java new file mode 100644 index 0000000..1c7efcb --- /dev/null +++ b/backend/src/main/java/com/designhub/vo/DesignerPerformanceVO.java @@ -0,0 +1,100 @@ +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 DesignerPerformanceVO { + + /** + * 设计师绩效列表 + */ + private List designerStats; + + /** + * 设计师工作负载列表 + */ + private List workloadStats; + + /** + * 汇总数据 + */ + private Summary summary; + + /** + * 设计师绩效统计 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class DesignerStat { + private Long userId; + private String userName; + private String realName; + private String deptName; + private Long deptId; + private Integer receivedCount; // 接单量 + private Integer completedCount; // 完成量 + private Integer inProgressCount; // 进行中数量 + private Double completionRate; // 完成率 + private Double onTimeRate; // 准时率 + private Integer onTimeCount; // 按时完成数 + private Integer overdueCount; // 超时完成数 + private Double avgProcessTime; // 平均处理时长(小时) + private Double avgRating; // 平均评分 + private Integer fiveStarCount; // 5星评价数 + private Double satisfactionRate; // 好评率 + private BigDecimal totalScore; // 获得总积分 + private Integer weekCompletedCount; // 本周完成数 + private Integer monthCompletedCount;// 本月完成数 + } + + /** + * 工作负载统计 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class WorkloadStat { + private Long userId; + private String userName; + private String realName; + private String deptName; + private Integer currentLoad; // 当前进行中任务数 + private String loadLevel; // 负载级别:low/normal/high/overload + private String earliestDeadline; // 最早截止时间 + private Double avgDailyTasks; // 平均每日处理任务数 + } + + /** + * 汇总数据 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Summary { + private Integer totalDesigners; // 设计师总数 + private Integer totalReceived; // 总接单量 + private Integer totalCompleted; // 总完成量 + private Double avgCompletionRate; // 平均完成率 + private Double avgOnTimeRate; // 平均准时率 + private Double avgProcessTime; // 平均处理时长 + private Double avgSatisfaction; // 平均满意度 + private Integer overloadCount; // 过载设计师数 + } +} + diff --git a/backend/src/main/java/com/designhub/vo/ProcessEfficiencyVO.java b/backend/src/main/java/com/designhub/vo/ProcessEfficiencyVO.java new file mode 100644 index 0000000..708e44f --- /dev/null +++ b/backend/src/main/java/com/designhub/vo/ProcessEfficiencyVO.java @@ -0,0 +1,118 @@ +package com.designhub.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 流程效率数据VO + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProcessEfficiencyVO { + + /** + * 各环节平均耗时 + */ + private StageTimeStats stageTimeStats; + + /** + * 效率指标详情 + */ + private EfficiencyMetrics efficiencyMetrics; + + /** + * 审批效率趋势 + */ + private List approvalTrend; + + /** + * 环节耗时分布 + */ + private List stageDistribution; + + /** + * 各环节平均耗时 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class StageTimeStats { + private Double demandApprovalTime; // 需求审核平均时长(小时) + private Double receivingApprovalTime; // 接单审核平均时长(小时) + private Double grabWaitTime; // 抢单等待平均时长(小时) + private Double processTime; // 任务处理平均时长(小时) + private Double totalCycleTime; // 总平均周期(小时) + private String bottleneckStage; // 瓶颈环节 + private Double bottleneckPercentage; // 瓶颈环节占比 + } + + /** + * 效率指标 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class EfficiencyMetrics { + // 本周数据 + private Double weekDemandApprovalTime; + private Double weekReceivingApprovalTime; + private Double weekGrabWaitTime; + private Double weekProcessTime; + private Double weekFirstPassRate; // 一次通过率 + + // 上周数据(用于环比) + private Double lastWeekDemandApprovalTime; + private Double lastWeekReceivingApprovalTime; + private Double lastWeekGrabWaitTime; + private Double lastWeekProcessTime; + private Double lastWeekFirstPassRate; + + // 本月数据 + private Double monthDemandApprovalTime; + private Double monthReceivingApprovalTime; + private Double monthGrabWaitTime; + private Double monthProcessTime; + private Double monthFirstPassRate; + + // 驳回统计 + private Integer totalRejected; // 总驳回数 + private Double rejectionRate; // 驳回率 + } + + /** + * 趋势数据 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class TrendData { + private String date; + private Double demandApprovalTime; + private Double receivingApprovalTime; + private Double grabWaitTime; + private Double processTime; + } + + /** + * 环节分布 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class StageDistribution { + private String stageName; + private Double avgTime; + private Double percentage; + } +} + diff --git a/backend/src/main/java/com/designhub/vo/WarningTaskVO.java b/backend/src/main/java/com/designhub/vo/WarningTaskVO.java new file mode 100644 index 0000000..8c0ca5c --- /dev/null +++ b/backend/src/main/java/com/designhub/vo/WarningTaskVO.java @@ -0,0 +1,96 @@ +package com.designhub.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 预警任务VO + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WarningTaskVO { + + private Long taskId; + private String taskNo; + private String taskTitle; + private String status; + private String statusText; + + /** + * 预警级别:urgent(紧急)/warning(警告) + */ + private String warningLevel; + + /** + * 预警类型: + * - overdue: 已超时 + * - near_deadline: 即将超时 + * - long_pending: 长期未接单 + * - approval_delay: 审批滞后 + * - long_in_progress: 长期未完成 + * - overload: 设计师过载 + */ + private String warningType; + + /** + * 预警原因描述 + */ + private String warningReason; + + /** + * 处理人ID + */ + private Long receiverId; + + /** + * 处理人名称 + */ + private String receiverName; + + /** + * 处理人部门 + */ + private String receiverDeptName; + + /** + * 发布人名称 + */ + private String publisherName; + + /** + * 预估工时 + */ + private Double estimatedHours; + + /** + * 截止时间 + */ + private LocalDateTime deadline; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 开始时间 + */ + private LocalDateTime startTime; + + /** + * 超时/剩余小时数 + */ + private Double hoursInfo; + + /** + * 积分 + */ + private Integer score; +} + diff --git a/frontend/src/api/analytics.ts b/frontend/src/api/analytics.ts index ca4e5d3..2d9735c 100644 --- a/frontend/src/api/analytics.ts +++ b/frontend/src/api/analytics.ts @@ -70,42 +70,153 @@ export interface DemandStatistics { overview: OverviewData; } +// ==================== 设计师绩效类型 ==================== + +export interface DesignerStat { + userId: number; + userName: string; + realName: string; + deptName: string; + deptId: number; + receivedCount: number; + completedCount: number; + inProgressCount: number; + completionRate: number; + onTimeRate: number; + onTimeCount: number; + overdueCount: number; + avgProcessTime: number; + avgRating: number; + fiveStarCount: number; + satisfactionRate: number; + totalScore: number; + weekCompletedCount: number; + monthCompletedCount: number; +} + +export interface WorkloadStat { + userId: number; + userName: string; + realName: string; + deptName: string; + currentLoad: number; + loadLevel: string; + earliestDeadline: string; + avgDailyTasks: number; +} + +export interface DesignerPerformanceSummary { + totalDesigners: number; + totalReceived: number; + totalCompleted: number; + avgCompletionRate: number; + avgOnTimeRate: number; + avgProcessTime: number; + avgSatisfaction: number; + overloadCount: number; +} + +export interface DesignerPerformance { + designerStats: DesignerStat[]; + workloadStats: WorkloadStat[]; + summary: DesignerPerformanceSummary; +} + +// ==================== 流程效率类型 ==================== + +export interface StageTimeStats { + demandApprovalTime: number; + receivingApprovalTime: number; + grabWaitTime: number; + processTime: number; + totalCycleTime: number; + bottleneckStage: string; + bottleneckPercentage: number; +} + +export interface EfficiencyMetrics { + weekDemandApprovalTime: number; + weekReceivingApprovalTime: number; + weekGrabWaitTime: number; + weekProcessTime: number; + weekFirstPassRate: number; + lastWeekDemandApprovalTime: number; + lastWeekReceivingApprovalTime: number; + lastWeekGrabWaitTime: number; + lastWeekProcessTime: number; + lastWeekFirstPassRate: number; + monthDemandApprovalTime: number; + monthReceivingApprovalTime: number; + monthGrabWaitTime: number; + monthProcessTime: number; + monthFirstPassRate: number; + totalRejected: number; + rejectionRate: number; +} + +export interface StageDistribution { + stageName: string; + avgTime: number; + percentage: number; +} + +export interface ProcessEfficiency { + stageTimeStats: StageTimeStats; + efficiencyMetrics: EfficiencyMetrics; + approvalTrend: TrendData[]; + stageDistribution: StageDistribution[]; +} + +// ==================== 预警任务类型 ==================== + +export interface WarningTask { + taskId: number; + taskNo: string; + taskTitle: string; + status: string; + statusText: string; + warningLevel: string; + warningType: string; + warningReason: string; + receiverId: number | null; + receiverName: string | null; + receiverDeptName: string | null; + publisherName: string | null; + estimatedHours: number | null; + deadline: string | null; + createTime: string; + startTime: string | null; + hoursInfo: number; + score: number; +} + +// ==================== API函数 ==================== + /** * 获取需求统计数据 */ export const getDemandStatistics = async ( dateRange: string = 'month', deptId?: number, - userId?: number + userId?: number, + month?: string ): Promise => { const params: any = { dateRange }; if (deptId) params.deptId = deptId; if (userId) params.userId = userId; + if (month) params.month = month; - // 使用统一的 request 工具,它会自动处理 Result 包装 - // 注意:baseURL 已经包含 /api 前缀,这里不要再写 /api 开头,避免变成 /api/api/... const response = await request.get('/analytics/demand-statistics', { params }); - // 添加调试日志 - console.log('Analytics API Response:', response); - - // request 拦截器已经处理了错误情况,这里 response 应该是 Result 对象 - // 如果 response 是 Result 对象,取 data 字段;否则直接返回 if (response && typeof response === 'object') { - // 检查是否是 Result 格式 { code, data, message } if ('data' in response && response.data !== undefined && response.data !== null) { - console.log('Returning response.data:', response.data); return response.data as DemandStatistics; } - // 如果 response 本身就是数据对象(包含 overview 或 trendData),直接返回 if ('overview' in response || 'trendData' in response) { - console.log('Returning response directly:', response); return response as unknown as DemandStatistics; } } - // 如果都不匹配,返回默认数据结构 - console.warn('Invalid response format, returning default structure'); return { overview: { totalDemands: 0, @@ -129,3 +240,113 @@ export const getDemandStatistics = async ( }; }; +/** + * 获取设计师绩效数据 + */ +export const getDesignerPerformance = async ( + month?: string, + deptId?: number +): Promise => { + const params: any = {}; + if (month) params.month = month; + if (deptId) params.deptId = deptId; + + const response = await request.get('/analytics/designer-performance', { params }); + + if (response && typeof response === 'object') { + if ('data' in response && response.data !== undefined && response.data !== null) { + return response.data as DesignerPerformance; + } + if ('designerStats' in response || 'summary' in response) { + return response as unknown as DesignerPerformance; + } + } + + return { + designerStats: [], + workloadStats: [], + summary: { + totalDesigners: 0, + totalReceived: 0, + totalCompleted: 0, + avgCompletionRate: 0, + avgOnTimeRate: 0, + avgProcessTime: 0, + avgSatisfaction: 0, + overloadCount: 0 + } + }; +}; + +/** + * 获取流程效率数据 + */ +export const getProcessEfficiency = async ( + month?: string +): Promise => { + const params: any = {}; + if (month) params.month = month; + + const response = await request.get('/analytics/process-efficiency', { params }); + + if (response && typeof response === 'object') { + if ('data' in response && response.data !== undefined && response.data !== null) { + return response.data as ProcessEfficiency; + } + if ('stageTimeStats' in response || 'efficiencyMetrics' in response) { + return response as unknown as ProcessEfficiency; + } + } + + return { + stageTimeStats: { + demandApprovalTime: 0, + receivingApprovalTime: 0, + grabWaitTime: 0, + processTime: 0, + totalCycleTime: 0, + bottleneckStage: '', + bottleneckPercentage: 0 + }, + efficiencyMetrics: { + weekDemandApprovalTime: 0, + weekReceivingApprovalTime: 0, + weekGrabWaitTime: 0, + weekProcessTime: 0, + weekFirstPassRate: 0, + lastWeekDemandApprovalTime: 0, + lastWeekReceivingApprovalTime: 0, + lastWeekGrabWaitTime: 0, + lastWeekProcessTime: 0, + lastWeekFirstPassRate: 0, + monthDemandApprovalTime: 0, + monthReceivingApprovalTime: 0, + monthGrabWaitTime: 0, + monthProcessTime: 0, + monthFirstPassRate: 0, + totalRejected: 0, + rejectionRate: 0 + }, + approvalTrend: [], + stageDistribution: [] + }; +}; + +/** + * 获取预警任务列表 + */ +export const getWarningTasks = async (): Promise => { + const response = await request.get('/analytics/warnings'); + + if (response && typeof response === 'object') { + if ('data' in response && Array.isArray(response.data)) { + return response.data as WarningTask[]; + } + if (Array.isArray(response)) { + return response as WarningTask[]; + } + } + + return []; +}; + diff --git a/frontend/src/views/Analytics.vue b/frontend/src/views/Analytics.vue index fdbae33..59051e3 100644 --- a/frontend/src/views/Analytics.vue +++ b/frontend/src/views/Analytics.vue @@ -2,170 +2,377 @@
+
- - 近一周 - 近一月 - 近三月 - 近一年 - 全部 - - - - 导出报表 - +
+ + + 近一周 + 近一月 + 近三月 + 近一年 + 全部 + +
+
+ + + 刷新 + + + + 导出报表 + +
- -
-

数据总览

+ +
+

核心指标

- +
- +
-
总需求数
+
总任务数
{{ overview.totalDemands }}
-
今日: {{ overview.todayDemands }}
- -
+ +
- + +
+
+
待处理
+
{{ overview.pendingDemands }}
+
+
+
+ +
+
+ +
+
+
进行中
+
{{ inProgressCount }}
+
+
+
+ +
+
+
已完成
{{ overview.completedDemands }}
-
完成率: {{ completionRate }}%
- +
- +
-
总积分消耗
-
{{ overview.totalScoreUsed }}
-
今日: {{ overview.todayScoreUsed }}
+
完成率
+
{{ completionRate }}%
- +
- +
-
平均满意度
-
{{ overview.avgSatisfaction?.toFixed(1) || '0.0' }}
-
平均完成时长: {{ overview.avgCompletionTime?.toFixed(1) || '0' }}h
+
准时率
+
{{ onTimeRate }}%
- -
-

需求趋势

-
-
-
-
- - -
- - -

需求类型分布

-
-
-
-
- -

部门积分使用情况

-
-
-
-
-
-
- - -
-

用户需求排行

-
- - - -
-
+ > + + +
+
+ - -
-

部门需求统计

-
- - - -
+
+ 总平均耗时: {{ processEfficiency.stageTimeStats?.totalCycleTime?.toFixed(1) || 0 }} 小时 + 瓶颈环节: {{ processEfficiency.stageTimeStats?.bottleneckStage || '-' }} ({{ processEfficiency.stageTimeStats?.bottleneckPercentage?.toFixed(1) || 0 }}%) +
+
+ + + +
+

各环节耗时占比

+
+
+
+ +
+

效率指标详情

+ + + +
+
+
+
+ + + + +
+ + + +
+
🔴
+
紧急
+
{{ urgentCount }}
+
+
+ +
+
🟡
+
警告
+
{{ warningCount }}
+
+
+ +
+
🟢
+
正常
+
{{ normalCount }}
+
+
+ +
+
📊
+
全部
+
{{ warningTasks.length }}
+
+
+
+ + +
+

+ {{ warningFilter === 'urgent' ? '紧急预警' : warningFilter === 'warning' ? '警告预警' : '预警任务列表' }} + ({{ filteredWarningTasks.length }}) +

+ + + +
+
+
+
@@ -174,17 +381,31 @@ @@ -569,128 +823,152 @@ onMounted(() => { max-width: $container-max-width; margin: 0 auto; padding: 0 $space-xl; - - @media (max-width: $breakpoint-tablet) { - padding: 0 $space-lg; - } - - @media (max-width: $breakpoint-mobile) { - padding: 0 $space-md; - } } .header-actions { display: flex; - justify-content: flex-end; + justify-content: space-between; align-items: center; + margin-bottom: $space-xl; + flex-wrap: wrap; gap: $space-md; - margin-bottom: $space-2xl; + + .filter-group { + display: flex; + gap: $space-md; + } + + .action-group { + display: flex; + gap: $space-sm; + } + + .month-picker { + width: 150px; + } .date-range-select { - :deep(.ant-select-selector) { - height: 40px !important; - line-height: 40px !important; - min-width: 120px; - } - - :deep(.ant-select-selection-item), - :deep(.ant-select-selection-placeholder) { - line-height: 38px !important; - } + width: 120px; } } .section-title { - font-size: $font-size-xl; + font-size: $font-size-lg; font-weight: $font-weight-semibold; color: $text-primary; margin-bottom: $space-lg; } .overview-section { - margin-bottom: $space-2xl; + margin-bottom: $space-xl; } .stat-card { background: $bg-card; border: 1px solid $border-primary; border-radius: $radius-lg; - padding: $space-lg; + padding: $space-md; display: flex; - gap: $space-lg; + gap: $space-md; transition: all $transition-base $ease-out; &:hover { - border-color: $border-color; - background: $bg-card-hover; + border-color: $brand-primary; box-shadow: $shadow-base; } .stat-icon { - width: 60px; - height: 60px; - border-radius: $radius-md; + width: 48px; + height: 48px; display: flex; align-items: center; justify-content: center; - background: transparent; - font-size: 28px; flex-shrink: 0; } .stat-content { flex: 1; + min-width: 0; .stat-label { font-size: $font-size-sm; color: $text-secondary; - margin-bottom: $space-xs; + margin-bottom: 4px; } .stat-value { - font-size: $font-size-2xl; + font-size: $font-size-xl; font-weight: $font-weight-bold; color: $text-primary; - margin-bottom: $space-xs; + } + } +} + +.tabs-section { + :deep(.ant-tabs-nav) { + margin-bottom: $space-lg; + + .ant-tabs-tab { + color: $text-secondary; + + &:hover { + color: $brand-primary; + } + + &.ant-tabs-tab-active .ant-tabs-tab-btn { + color: $brand-primary; + } } - .stat-sub { - font-size: $font-size-sm; - color: $text-tertiary; + .ant-tabs-ink-bar { + background: $brand-primary; } } } -.chart-section { - margin-bottom: $space-2xl; +.tab-content { + min-height: 400px; } -.chart-card { +.chart-card, .table-card { background: $bg-card; border: 1px solid $border-primary; border-radius: $radius-lg; - padding: $space-xl; - transition: all $transition-base $ease-out; - - &:hover { - border-color: $border-color; - background: $bg-card-hover; + padding: $space-lg; + + .chart-title { + font-size: $font-size-md; + font-weight: $font-weight-semibold; + color: $text-primary; + margin-bottom: $space-md; + + .count-badge { + font-weight: normal; + color: $text-secondary; + } } } -.table-section { - margin-bottom: $space-2xl; -} - -.table-card { - background: $bg-card; - border: 1px solid $border-primary; - border-radius: $radius-lg; - overflow: hidden; - transition: all $transition-base $ease-out; - - &:hover { - border-color: $border-color; +.mini-stat-card { + background: $bg-tertiary; + border-radius: $radius-md; + padding: $space-md; + text-align: center; + + &.warning { + border: 1px solid $color-warning; + } + + .mini-stat-label { + font-size: $font-size-sm; + color: $text-secondary; + margin-bottom: 4px; + } + + .mini-stat-value { + font-size: $font-size-xl; + font-weight: $font-weight-bold; + color: $text-primary; } } @@ -698,97 +976,161 @@ onMounted(() => { display: inline-flex; align-items: center; justify-content: center; - width: 32px; - height: 32px; + width: 28px; + height: 28px; border-radius: 50%; font-weight: $font-weight-bold; + font-size: $font-size-sm; background: $bg-tertiary; color: $text-secondary; &.rank-1 { - background: rgba(#31f08b, 0.15); - color: #31f08b; - border: 1px solid rgba(#31f08b, 0.3); + background: rgba(#FFD700, 0.2); + color: #FFD700; } &.rank-2 { - background: rgba(#31f08b, 0.1); - color: #31f08b; - border: 1px solid rgba(#31f08b, 0.2); + background: rgba(#C0C0C0, 0.2); + color: #C0C0C0; } &.rank-3 { - background: rgba(#31f08b, 0.08); - color: #31f08b; - border: 1px solid rgba(#31f08b, 0.15); + background: rgba(#CD7F32, 0.2); + color: #CD7F32; } } -.score-status { - .score-limits { - display: flex; - gap: $space-sm; +.text-success { color: $color-success; } +.text-warning { color: $color-warning; } +.text-danger { color: $color-error; } + +// 流程图样式 +.process-flow-card { + background: $bg-card; + border: 1px solid $border-primary; + border-radius: $radius-lg; + padding: $space-xl; +} + +.process-flow { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + gap: $space-sm; + margin: $space-xl 0; +} + +.process-step { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + + .step-name { + font-size: $font-size-sm; + color: $text-primary; + padding: $space-sm $space-md; + background: $bg-tertiary; + border-radius: $radius-md; + } + + .step-time { font-size: $font-size-xs; + color: $brand-primary; + font-weight: $font-weight-bold; + } + + .step-arrow { color: $text-tertiary; - margin-top: $space-xs; - - span { - background: $bg-tertiary; - padding: 2px 6px; - border-radius: $radius-sm; + font-size: $font-size-lg; + } + + &.bottleneck { + .step-name { + background: rgba($color-warning, 0.2); + border: 1px solid $color-warning; } } } -// Ant Design 组件深色主题覆盖 -:deep(.ant-select) { - .ant-select-selector { - background: $bg-tertiary !important; - border-color: $border-color !important; - color: $text-primary !important; +.process-summary { + text-align: center; + color: $text-secondary; + + strong { + color: $text-primary; } - .ant-select-selection-item { - color: $text-primary !important; - } - - .ant-select-arrow { - color: $text-secondary !important; + .bottleneck-info { + margin-left: $space-xl; + color: $color-warning; } } -:deep(.ant-select-dropdown) { - background: $bg-secondary !important; - border-color: $border-color !important; +// 预警卡片样式 +.warning-stat-card { + background: $bg-tertiary; + border-radius: $radius-md; + padding: $space-lg; + text-align: center; + cursor: pointer; + transition: all $transition-base; - .ant-select-item { - color: $text-primary !important; - - &:hover { - background: $bg-tertiary !important; - } - - &.ant-select-item-option-selected { - background: rgba($brand-primary, 0.1) !important; - color: $brand-primary !important; - } + &:hover { + transform: translateY(-2px); + box-shadow: $shadow-base; + } + + &.urgent { border: 1px solid $color-error; } + &.warning { border: 1px solid $color-warning; } + &.normal { border: 1px solid $color-success; } + &.all { border: 1px solid $brand-primary; } + + .warning-icon { + font-size: 24px; + margin-bottom: $space-xs; + } + + .warning-label { + font-size: $font-size-sm; + color: $text-secondary; + } + + .warning-count { + font-size: $font-size-2xl; + font-weight: $font-weight-bold; + color: $text-primary; } } +.level-badge { + padding: 2px 8px; + border-radius: $radius-sm; + font-size: $font-size-xs; + + &.urgent { + background: rgba($color-error, 0.1); + color: $color-error; + } + + &.warning { + background: rgba($color-warning, 0.1); + color: $color-warning; + } +} + +// Ant Design 深色主题覆盖 :deep(.ant-table) { background: transparent !important; - color: $text-primary !important; .ant-table-thead > tr > th { background: $bg-tertiary !important; border-color: $border-color !important; color: $text-primary !important; - font-weight: $font-weight-semibold; } .ant-table-tbody > tr { - background: transparent !important; - &:hover > td { background: $bg-tertiary !important; } @@ -798,90 +1140,38 @@ onMounted(() => { color: $text-primary !important; } } - - .ant-table-pagination { - .ant-pagination-item { - background: $bg-tertiary !important; - border-color: $border-primary !important; - - a { - color: $text-primary !important; - } - - &.ant-pagination-item-active { - background: $brand-primary !important; - border-color: $brand-primary !important; - - a { - color: #000000 !important; // 当前页文字为黑色 - } - } - } - - .ant-pagination-prev, - .ant-pagination-next { - .ant-pagination-item-link { - background: $bg-tertiary !important; - border-color: $border-primary !important; - color: $text-primary !important; - } - } - - .ant-pagination-options { - .ant-select { - .ant-select-selector { - background: $bg-tertiary !important; - border-color: $border-primary !important; - color: $text-primary !important; - } - } - } - } } -:deep(.ant-progress) { - .ant-progress-bg { - background-color: $brand-primary !important; - } - - .ant-progress-text { +:deep(.ant-tabs) { + color: $text-primary; +} + +:deep(.ant-select) { + .ant-select-selector { + background: $bg-tertiary !important; + border-color: $border-color !important; color: $text-primary !important; } } -:deep(.ant-rate) { - .ant-rate-star { - color: $text-tertiary !important; - - &.ant-rate-star-full, - &.ant-rate-star-half .ant-rate-star-first { - color: $color-warning !important; - } +:deep(.ant-picker) { + background: $bg-tertiary !important; + border-color: $border-color !important; + + .ant-picker-input > input { + color: $text-primary !important; + } + + .ant-picker-suffix, .ant-picker-clear { + color: $text-secondary !important; } } -:deep(.ant-tag) { - background: $bg-tertiary !important; - border-color: $border-color !important; - color: $text-primary !important; - - &.ant-tag-success { - background: rgba($color-success, 0.1) !important; - border-color: $color-success !important; - color: $color-success !important; - } - - &.ant-tag-warning { - background: rgba($color-warning, 0.1) !important; - border-color: $color-warning !important; - color: $color-warning !important; - } - - &.ant-tag-error { - background: rgba($color-error, 0.1) !important; - border-color: $color-error !important; - color: $color-error !important; +:deep(.ant-rate) { + .ant-rate-star-full .ant-rate-star-second { + color: #FFD700 !important; } } + diff --git a/数据页面优化方案.md b/数据页面优化方案.md new file mode 100644 index 0000000..07dbe45 --- /dev/null +++ b/数据页面优化方案.md @@ -0,0 +1,442 @@ +# 数据分析页面优化方案 + +> 基于抢单系统工作流程的数据分析页面科学化重构方案 + +--- + +## 一、现状分析 + +### 1.1 项目工作流程 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ 任务全生命周期 │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────┐ │ +│ │ 发起需求 │ ──▸│ 需求部门审核 │ ──▸│ 接单部门审核 │ ──▸│ 抢单/指派│ │ +│ │ (草稿) │ │ (待审核) │ │ (待审核) │ │ (待接单) │ │ +│ └─────────┘ └─────────────┘ └─────────────┘ └────────┘ │ +│ │ │ │ │ │ +│ ▼ ▼ ▼ ▼ │ +│ [需求方] [需求部门负责人] [接单部门负责人] [设计师] │ +│ │ │ +│ ┌────────────────┘ │ +│ ▼ │ +│ ┌─────────────┐ │ +│ │ 任务处理中 │◀──────────────────┐ │ +│ │ (进行中) │ │ │ +│ └─────────────┘ │ │ +│ │ │ │ +│ ▼ │ │ +│ ┌─────────────┐ 驳回 ┌─────────────┐ │ +│ │ 交付评审 │ ──────────▸│ 重新处理 │ │ +│ │ (待评审) │ │ │ │ +│ └─────────────┘ └─────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────┐ │ +│ │ 任务完结 │ │ +│ │ (已完成) │ │ +│ └─────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 1.2 当前数据分析页面存在的问题 + +| 问题类别 | 具体问题 | 影响 | +|---------|---------|------| +| **视角单一** | 仅从需求发起方角度统计,缺少接单方(设计师)维度 | 无法评估设计师绩效和产能 | +| **流程效率缺失** | 没有审批时效、抢单速度、各环节耗时等指标 | 无法识别流程瓶颈 | +| **子任务遗漏** | 仅统计主任务,没有流水线子任务数据 | 数据统计不完整 | +| **绩效指标缺失** | 缺少设计师接单量、完成率、准时率等KPI | 无法进行绩效考核 | +| **工作负载盲区** | 没有设计师工作量、任务积压、负载均衡分析 | 无法合理分配任务 | +| **预警机制缺失** | 没有超时预警、积压预警、异常预警 | 风险无法及时发现 | +| **维度不足** | 缺少部门对比、时段分析、类型效率对比 | 分析深度不够 | + +--- + +## 二、优化方案 + +### 2.1 数据分析架构设计 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ 数据分析模块架构 │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────────────────────────────────────────────────────────┐ │ +│ │ 顶部:核心指标概览 │ │ +│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐│ │ +│ │ │总任务数│ │待处理数│ │进行中 │ │已完成 │ │完成率 │ │准时率 ││ │ +│ │ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘│ │ +│ └──────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────┐ ┌────────────────────────────────────────┐ │ +│ │ Tab 1: 需求分析 │ │ Tab 2: 设计师绩效 │ │ +│ │ (需求方视角) │ │ (接单方视角) │ │ +│ ├────────────────────────┤ ├────────────────────────────────────────┤ │ +│ │ • 发起趋势 │ │ • 接单量/完成量排行 │ │ +│ │ • 类型分布 │ │ • 完成率/准时率 │ │ +│ │ • 部门需求量 │ │ • 平均处理时长 │ │ +│ │ • 积分消耗 │ │ • 好评率/满意度 │ │ +│ └────────────────────────┘ │ • 工作负载分析 │ │ +│ └────────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────┐ ┌────────────────────────────────────────┐ │ +│ │ Tab 3: 流程效率 │ │ Tab 4: 预警监控 │ │ +│ │ (管理视角) │ │ (运营视角) │ │ +│ ├────────────────────────┤ ├────────────────────────────────────────┤ │ +│ │ • 各环节平均耗时 │ │ • 即将超时任务 │ │ +│ │ • 审批效率 │ │ • 积压任务预警 │ │ +│ │ • 抢单速度 │ │ • 负载不均预警 │ │ +│ │ • 瓶颈分析 │ │ • 异常任务列表 │ │ +│ └────────────────────────┘ └────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 2.2 新增统计指标详解 + +#### 2.2.1 核心概览指标(6个) + +| 指标 | 计算方式 | 说明 | +|-----|---------|------| +| **总任务数** | COUNT(所有任务) | 包括主任务和子任务 | +| **待处理数** | COUNT(status IN ['pending', 'pending_demand_approval', 'pending_receiving_approval']) | 待审核+待接单 | +| **进行中数** | COUNT(status = 'in_progress') | 正在处理的任务 | +| **已完成数** | COUNT(status = 'completed') | 已完结的任务 | +| **完成率** | 已完成数 / 总任务数 × 100% | 整体完成情况 | +| **准时率** | 按时完成数 / 已完成数 × 100% | 任务时效性 | + +#### 2.2.2 需求分析指标(Tab 1) + +| 指标 | 说明 | 图表类型 | +|-----|------|---------| +| 需求发起趋势 | 每日/周/月发起的任务数量变化 | 折线图 | +| 需求类型分布 | 各类型任务占比(主图详情、SKU图等) | 饼图 | +| 部门需求量排行 | 各需求部门发起任务数排名 | 柱状图 | +| 积分消耗趋势 | 积分使用量随时间变化 | 面积图 | +| 平均任务积分 | 任务平均积分值 | 数值卡片 | +| 高频需求者排行 | 发起任务最多的用户 | 表格 | + +#### 2.2.3 设计师绩效指标(Tab 2)- 新增 + +| 指标 | 计算方式 | 说明 | +|-----|---------|------| +| **接单量** | COUNT(receiver_id = 设计师ID) | 接收的任务总数 | +| **完成量** | COUNT(receiver_id = 设计师ID AND status = 'completed') | 完成的任务数 | +| **完成率** | 完成量 / 接单量 × 100% | 设计师任务完成比例 | +| **准时完成率** | 按时完成数 / 完成量 × 100% | deadline内完成的比例 | +| **平均处理时长** | AVG(完成时间 - 接单时间) | 任务平均处理时间 | +| **当前负载** | COUNT(status = 'in_progress' AND receiver_id = 设计师ID) | 手上进行中任务数 | +| **好评率** | 5星评价数 / 评价总数 × 100% | 客户满意度 | +| **获得积分** | SUM(任务积分) | 累计获得的积分 | + +#### 2.2.4 流程效率指标(Tab 3)- 新增 + +| 指标 | 计算方式 | 说明 | +|-----|---------|------| +| **需求审批平均时长** | AVG(需求审核完成时间 - 任务创建时间) | 需求部门审核效率 | +| **接单审批平均时长** | AVG(接单审核完成时间 - 需求审核完成时间) | 接单部门审核效率 | +| **平均抢单时长** | AVG(抢单时间 - 发布到大厅时间) | 任务在大厅等待时间 | +| **平均任务周期** | AVG(完成时间 - 创建时间) | 任务全流程耗时 | +| **环节耗时占比** | 各环节时长 / 总时长 | 识别瓶颈环节 | +| **驳回率** | 被驳回数 / 总审核数 × 100% | 任务质量指标 | +| **一次通过率** | 首次提交即通过数 / 提交总数 | 设计质量指标 | + +#### 2.2.5 预警监控指标(Tab 4)- 新增 + +| 预警类型 | 触发条件 | 预警级别 | +|---------|---------|---------| +| **即将超时** | 剩余时间 < 预估工时 × 20% | 🟡 警告 | +| **已超时** | 当前时间 > deadline | 🔴 紧急 | +| **任务积压** | 待接单任务数 > 阈值 | 🟡 警告 | +| **设计师过载** | 进行中任务数 > 5 | 🟡 警告 | +| **审批滞后** | 待审核时间 > 24小时 | 🟡 警告 | +| **长期未完成** | 进行中时间 > 预估工时 × 2 | 🔴 紧急 | + +--- + +### 2.3 页面布局优化方案 + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ 数据分析中心 │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ [时间范围选择] ▼ [部门筛选] ▼ [任务类型] ▼ [导出报表] [刷新] │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ +│ │ 总任务 │ │ 待处理 │ │ 进行中 │ │ 已完成 │ │ 完成率 │ │ 准时率 │ │ +│ │ 156 │ │ 23 │ │ 45 │ │ 88 │ │ 56.4% │ │ 82.3% │ │ +│ │ ↑12% │ │ ↓5% │ │ ↑8% │ │ ↑15% │ │ ↑2.1% │ │ ↑1.5% │ │ +│ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────────────────┐ │ +│ │ [需求分析] [设计师绩效] [流程效率] [预警监控] │ │ +│ ├─────────────────────────────────────────────────────────────────────┤ │ +│ │ │ │ +│ │ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │ │ +│ │ │ │ │ │ │ │ +│ │ │ 需求趋势图(折线) │ │ 类型分布图(饼图) │ │ │ +│ │ │ │ │ │ │ │ +│ │ └─────────────────────────────┘ └─────────────────────────────┘ │ │ +│ │ │ │ +│ │ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │ │ +│ │ │ │ │ │ │ │ +│ │ │ 部门需求量(柱状图) │ │ 积分消耗(面积图) │ │ │ +│ │ │ │ │ │ │ │ +│ │ └─────────────────────────────┘ └─────────────────────────────┘ │ │ +│ │ │ │ +│ │ ┌───────────────────────────────────────────────────────────┐ │ │ +│ │ │ 用户需求排行榜 │ │ │ +│ │ │ 排名 | 用户名 | 部门 | 发起数 | 完成数 | 总积分 | 满意度 │ │ │ +│ │ │ 1 | 张三 | 运营 | 25 | 22 | 450 | 4.8★ │ │ │ +│ │ │ 2 | 李四 | 市场 | 20 | 18 | 380 | 4.6★ │ │ │ +│ │ │ ... │ │ │ +│ │ └───────────────────────────────────────────────────────────┘ │ │ +│ │ │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +### 2.4 设计师绩效页面(新增) + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ 设计师绩效看板 │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌───────────────────────────────────────────────────────────────────────┐ │ +│ │ 设计师绩效排行榜 │ │ +│ │ ┌────┬─────────┬────────┬──────┬──────┬────────┬────────┬────────┐ │ │ +│ │ │排名│ 设计师 │ 所属部门│ 接单量│完成量│ 完成率 │ 准时率 │ 好评率 │ │ │ +│ │ ├────┼─────────┼────────┼──────┼──────┼────────┼────────┼────────┤ │ │ +│ │ │ 🥇 │ 南亭 │ 平面设计│ 28 │ 25 │ 89.3% │ 92.0% │ 96.0% │ │ │ +│ │ │ 🥈 │ 小明 │ 平面设计│ 24 │ 20 │ 83.3% │ 85.0% │ 90.0% │ │ │ +│ │ │ 🥉 │ 小红 │ 3D建模 │ 20 │ 18 │ 90.0% │ 88.9% │ 94.4% │ │ │ +│ │ └────┴─────────┴────────┴──────┴──────┴────────┴────────┴────────┘ │ │ +│ └───────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────┐ ┌─────────────────────────────────────┐ │ +│ │ 设计师工作负载 │ │ 平均处理时长分布 │ │ +│ │ ┌──────────────────────┐ │ │ ┌──────────────────────────────┐ │ │ +│ │ │ 南亭 ████████ 8任务│ │ │ │ │ │ │ +│ │ │ 小明 ██████ 6任务 │ │ │ │ [直方图/箱线图] │ │ │ +│ │ │ 小红 ████ 4任务 │ │ │ │ │ │ │ +│ │ │ 小李 ███ 3任务 │ │ │ │ 平均: 6.2h 中位数: 5.5h │ │ │ +│ │ │ [负载均衡建议] │ │ │ │ │ │ │ +│ │ └──────────────────────┘ │ │ └──────────────────────────────┘ │ │ +│ └─────────────────────────────┘ └─────────────────────────────────────┘ │ +│ │ +│ ┌───────────────────────────────────────────────────────────────────────┐ │ +│ │ 设计师详细绩效数据 │ │ +│ │ ┌────────┬────────┬────────┬────────┬────────┬────────┬────────────┐ │ │ +│ │ │ 设计师 │ 当前负载│ 本周完成│ 本月完成│ 获得积分│ 平均时长│ 满意度趋势│ │ │ +│ │ ├────────┼────────┼────────┼────────┼────────┼────────┼────────────┤ │ │ +│ │ │ 南亭 │ 3 任务 │ 8 │ 25 │ 500 │ 5.2h │ ↑ 4.9★ │ │ │ +│ │ │ 小明 │ 6 任务 │ 5 │ 20 │ 400 │ 6.8h │ → 4.6★ │ │ │ +│ │ │ [点击查看详情...] │ │ +│ │ └────────┴────────┴────────┴────────┴────────┴────────┴────────────┘ │ │ +│ └───────────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +### 2.5 流程效率页面(新增) + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ 流程效率分析 │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌───────────────────────────────────────────────────────────────────────┐ │ +│ │ 任务流程平均耗时 │ │ +│ │ │ │ +│ │ 发起 ──▸ 需求审核 ──▸ 接单审核 ──▸ 抢单/指派 ──▸ 处理 ──▸ 完结 │ │ +│ │ │ 2.5h │ 1.8h │ 0.5h │ 6.2h │ │ │ +│ │ └─────────┴──────────┴────────────┴───────┘ │ │ +│ │ │ │ +│ │ 总平均耗时: 11.0 小时 瓶颈环节: 任务处理 (56.4%) │ │ +│ │ │ │ +│ └───────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────┐ ┌─────────────────────────────────────┐ │ +│ │ 各环节耗时占比 │ │ 审批效率趋势 │ │ +│ │ ┌──────────────────────┐ │ │ ┌──────────────────────────────┐ │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ │ [饼图/环形图] │ │ │ │ [折线图] │ │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ │ • 需求审核 22.7% │ │ │ │ 需求审核时长趋势 │ │ │ +│ │ │ • 接单审核 16.4% │ │ │ │ 接单审核时长趋势 │ │ │ +│ │ │ • 任务处理 56.4% │ │ │ │ │ │ │ +│ │ │ • 其他 4.5% │ │ │ └──────────────────────────────┘ │ │ +│ │ └──────────────────────┘ │ └─────────────────────────────────────┘ │ +│ └─────────────────────────────┘ │ +│ │ +│ ┌───────────────────────────────────────────────────────────────────────┐ │ +│ │ 效率指标详情 │ │ +│ │ ┌────────────┬──────────┬──────────┬──────────┬──────────┬──────────┐ │ │ +│ │ │ 指标 │ 本周 │ 上周 │ 环比 │ 本月 │ 目标 │ │ │ +│ │ ├────────────┼──────────┼──────────┼──────────┼──────────┼──────────┤ │ │ +│ │ │ 需求审批时长│ 2.3h │ 2.8h │ ↓17.9% │ 2.5h │ < 4h │ │ │ +│ │ │ 接单审批时长│ 1.6h │ 2.1h │ ↓23.8% │ 1.8h │ < 2h │ │ │ +│ │ │ 平均抢单时长│ 0.4h │ 0.6h │ ↓33.3% │ 0.5h │ < 1h │ │ │ +│ │ │ 平均处理时长│ 5.8h │ 6.5h │ ↓10.8% │ 6.2h │ < 8h │ │ │ +│ │ │ 一次通过率 │ 85.0% │ 82.0% │ ↑3.7% │ 83.5% │ > 80% │ │ │ +│ │ └────────────┴──────────┴──────────┴──────────┴──────────┴──────────┘ │ │ +│ └───────────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +### 2.6 预警监控页面(新增) + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ 预警监控中心 │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ +│ │🔴 紧急 │ │🟡 警告 │ │🟢 正常 │ │📊 全部 │ │ +│ │ 3 │ │ 8 │ │ 45 │ │ 56 │ │ +│ └────────┘ └────────┘ └────────┘ └────────┘ │ +│ │ +│ ┌───────────────────────────────────────────────────────────────────────┐ │ +│ │ 紧急预警 (3) │ │ +│ │ ┌────────┬──────────────────┬──────────┬──────────┬──────────────────┐ │ │ +│ │ │ 级别 │ 任务名称 │ 处理人 │ 预警原因 │ 操作 │ │ │ +│ │ ├────────┼──────────────────┼──────────┼──────────┼──────────────────┤ │ │ +│ │ │ 🔴紧急 │ SKU主图-产品A │ 南亭 │ 已超时8h │ [查看] [催办] │ │ │ +│ │ │ 🔴紧急 │ 详情页-产品B │ 小明 │ 已超时4h │ [查看] [催办] │ │ │ +│ │ │ 🔴紧急 │ 3D渲染-产品C │ - │ 48h未接单│ [查看] [指派] │ │ │ +│ │ └────────┴──────────────────┴──────────┴──────────┴──────────────────┘ │ │ +│ └───────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌───────────────────────────────────────────────────────────────────────┐ │ +│ │ 警告预警 (8) │ │ +│ │ ┌────────┬──────────────────┬──────────┬──────────┬──────────────────┐ │ │ +│ │ │ 级别 │ 任务名称 │ 处理人 │ 预警原因 │ 操作 │ │ │ +│ │ ├────────┼──────────────────┼──────────┼──────────┼──────────────────┤ │ │ +│ │ │ 🟡警告 │ Banner设计 │ 小红 │ 即将超时 │ [查看] [提醒] │ │ │ +│ │ │ 🟡警告 │ 产品包装设计 │ 小明 │ 负载过高(8)│ [查看] [分配] │ │ │ +│ │ │ 🟡警告 │ 主图优化-D │ - │ 24h未接单│ [查看] [指派] │ │ │ +│ │ │ 🟡警告 │ 详情页-E │ - │ 审批滞后 │ [查看] [催审] │ │ │ +│ │ │ ... │ │ +│ │ └────────┴──────────────────┴──────────┴──────────┴──────────────────┘ │ │ +│ └───────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────┐ ┌─────────────────────────────────────┐ │ +│ │ 预警趋势统计 │ │ 设计师负载分布 │ │ +│ │ ┌──────────────────────┐ │ │ ┌──────────────────────────────┐ │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ │ [趋势折线图] │ │ │ │ [柱状图/热力图] │ │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ │ 紧急预警趋势 │ │ │ │ 显示各设计师当前任务数 │ │ │ +│ │ │ 警告预警趋势 │ │ │ │ 标红显示过载设计师 │ │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ └──────────────────────┘ │ │ └──────────────────────────────┘ │ │ +│ └─────────────────────────────┘ └─────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 三、实施计划 + +### 3.1 开发优先级 + +| 阶段 | 内容 | 工作量 | 优先级 | +|-----|------|-------|-------| +| **P0** | 核心概览指标优化(6卡片) | 2天 | 🔴 最高 | +| **P0** | 设计师绩效Tab(新增) | 3天 | 🔴 最高 | +| **P1** | 流程效率Tab(新增) | 3天 | 🟡 高 | +| **P1** | 预警监控Tab(新增) | 3天 | 🟡 高 | +| **P2** | 需求分析Tab优化 | 2天 | 🟢 中 | +| **P2** | 导出报表功能完善 | 1天 | 🟢 中 | +| **P3** | 数据钻取功能 | 2天 | 🔵 低 | + +### 3.2 后端API新增列表 + +| API | 说明 | 优先级 | +|-----|------|-------| +| `GET /api/analytics/designer-performance` | 设计师绩效数据 | P0 | +| `GET /api/analytics/process-efficiency` | 流程效率统计 | P1 | +| `GET /api/analytics/warnings` | 预警任务列表 | P1 | +| `GET /api/analytics/workload` | 设计师工作负载 | P0 | +| `GET /api/analytics/on-time-rate` | 准时完成率统计 | P0 | +| `GET /api/analytics/approval-stats` | 审批效率统计 | P1 | + +### 3.3 数据库优化建议 + +```sql +-- 1. 新增任务时间戳字段(用于流程效率分析) +ALTER TABLE task ADD COLUMN demand_approved_time DATETIME COMMENT '需求审核通过时间'; +ALTER TABLE task ADD COLUMN receiving_approved_time DATETIME COMMENT '接单审核通过时间'; +ALTER TABLE task ADD COLUMN grabbed_time DATETIME COMMENT '抢单时间'; + +-- 2. 新增索引优化查询性能 +CREATE INDEX idx_task_status_receiver ON task(status, receiver_id); +CREATE INDEX idx_task_create_time ON task(create_time); +CREATE INDEX idx_task_receiver_dept ON task(receiver_dept_id); +``` + +--- + +## 四、预期收益 + +| 收益类型 | 具体收益 | +|---------|---------| +| **管理效率提升** | 快速识别流程瓶颈,优化审批效率 | +| **资源配置优化** | 通过工作负载分析,合理分配任务 | +| **绩效考核依据** | 设计师绩效数据量化,支持KPI考核 | +| **风险预防** | 预警机制提前发现问题,减少超时 | +| **决策支持** | 多维度数据分析,支持管理决策 | + +--- + +## 五、技术实现要点 + +### 5.1 前端技术栈 + +- **Vue 3 + TypeScript**:页面开发框架 +- **Ant Design Vue**:UI组件库 +- **ECharts**:图表可视化 +- **Pinia**:状态管理 + +### 5.2 后端技术栈 + +- **Spring Boot 3**:后端框架 +- **MyBatis-Plus**:ORM框架 +- **MySQL 8.0**:数据库 + +### 5.3 关键实现点 + +1. **设计师绩效计算** + - 需要关联 `task` 表和 `task_item` 表 + - 需要计算准时率(对比 deadline 和实际完成时间) + - 需要关联评价表获取好评率 + +2. **流程效率计算** + - 需要记录各环节时间戳 + - 需要新增字段存储审核通过时间、抢单时间等 + +3. **预警监控实现** + - 可使用定时任务扫描预警条件 + - 或在查询时实时计算预警状态 + +--- + +**文档版本**: v1.0 +**创建时间**: 2026-01-04 +**作者**: AI Assistant