From 348e7ed71e8dc5ff89e57058f3e845dc0b5a00e1 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 22 Jan 2026 16:01:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9C=A8=E4=B8=80=E7=BA=A7=E9=9C=80?= =?UTF-8?q?=E6=B1=82=E9=83=A8=E9=97=A8=E7=BB=9F=E8=AE=A1=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=94=A8=E6=88=B7=E7=BB=B4=E5=BA=A6=E7=9A=84?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=88=86=E6=9E=90=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AnalyticsController.java | 10 + .../designhub/service/AnalyticsService.java | 130 +++++++ .../vo/PrimaryDeptUserRankingVO.java | 131 +++++++ frontend/src/api/analytics.ts | 33 ++ frontend/src/views/Analytics.vue | 59 ++- 更新通知-2026-01-22.md | 356 ++++++++++++++++++ 6 files changed, 716 insertions(+), 3 deletions(-) create mode 100644 backend/src/main/java/com/designhub/vo/PrimaryDeptUserRankingVO.java create mode 100644 更新通知-2026-01-22.md diff --git a/backend/src/main/java/com/designhub/controller/AnalyticsController.java b/backend/src/main/java/com/designhub/controller/AnalyticsController.java index b960c47..e952bbe 100644 --- a/backend/src/main/java/com/designhub/controller/AnalyticsController.java +++ b/backend/src/main/java/com/designhub/controller/AnalyticsController.java @@ -153,5 +153,15 @@ public class AnalyticsController { log.info("获取品牌任务分布: startTime={}, endTime={}", startTime, endTime); return Result.success(analyticsService.getPrimaryTaskBrandDistribution(startTime, endTime)); } + + @Operation(summary = "获取一级需求部门用户排名(用户维度统计)") + @GetMapping("/primary-dept/user-ranking") + public Result> getPrimaryDeptUserRanking( + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startTime, + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endTime + ) { + log.info("获取一级需求部门用户排名: startTime={}, endTime={}", startTime, endTime); + return Result.success(analyticsService.getPrimaryDeptUserRanking(startTime, endTime)); + } } diff --git a/backend/src/main/java/com/designhub/service/AnalyticsService.java b/backend/src/main/java/com/designhub/service/AnalyticsService.java index 7cd55b3..a28954a 100644 --- a/backend/src/main/java/com/designhub/service/AnalyticsService.java +++ b/backend/src/main/java/com/designhub/service/AnalyticsService.java @@ -2695,5 +2695,135 @@ public class AnalyticsService { return result; } + + /** + * 获取一级需求部门用户排名(用户维度统计) + */ + public List getPrimaryDeptUserRanking(LocalDateTime startTime, LocalDateTime endTime) { + log.info("获取一级需求部门用户排名: startTime={}, endTime={}", startTime, endTime); + + List result = new java.util.ArrayList<>(); + + // 1. 查询时间范围内的一级需求任务 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(Task::getIsPrimaryTask, 1) + .ge(Task::getCreateTime, startTime) + .le(Task::getCreateTime, endTime) + .eq(Task::getDeleted, 0) + .isNotNull(Task::getPrimaryPublisherId); + + List allTasks = taskMapper.selectList(queryWrapper); + + // 2. 按用户分组 + java.util.Map> tasksByUser = allTasks.stream() + .filter(t -> t.getPrimaryPublisherId() != null) + .collect(java.util.stream.Collectors.groupingBy(Task::getPrimaryPublisherId)); + + // 3. 为每个用户创建统计数据 + for (java.util.Map.Entry> entry : tasksByUser.entrySet()) { + Long userId = entry.getKey(); + List userTasks = entry.getValue(); + + // 获取用户信息 + User user = userMapper.selectById(userId); + if (user == null || user.getDeleted() == 1 || user.getStatus() == 0) { + continue; // 跳过已删除或禁用的用户 + } + + PrimaryDeptUserRankingVO vo = new PrimaryDeptUserRankingVO(); + vo.setUserId(userId); + vo.setUserName(user.getRealName() != null && !user.getRealName().isEmpty() + ? user.getRealName() : user.getUsername()); + + // 获取用户部门信息 + if (user.getDeptId() != null) { + Department userDept = departmentMapper.selectById(user.getDeptId()); + if (userDept != null) { + vo.setDeptId(userDept.getId()); + vo.setDeptName(userDept.getDeptName()); + + // 获取一级部门和二级部门名称(向上查找部门层级) + List hierarchy = new java.util.ArrayList<>(); + Department current = userDept; + while (current != null && current.getParentId() != null && current.getParentId() > 0) { + Department parent = departmentMapper.selectById(current.getParentId()); + if (parent != null) { + hierarchy.add(0, parent); // 插入到列表开头 + } + current = parent; + } + + // hierarchy 现在从根部门到当前部门的父部门 + // 一级部门是 hierarchy[0],二级部门是 hierarchy[1](如果存在) + if (hierarchy.size() >= 1) { + vo.setFirstLevelDeptName(hierarchy.get(0).getDeptName()); + } + if (hierarchy.size() >= 2) { + vo.setSecondLevelDeptName(hierarchy.get(1).getDeptName()); + } + } + } + + vo.setTaskCount(userTasks.size()); + + // 统计审核通过数 + long approvedCount = userTasks.stream() + .filter(t -> !"rejected_by_demand".equals(t.getApprovalStatus()) && !"draft".equals(t.getStatus())) + .count(); + vo.setApprovedCount((int) approvedCount); + + // 统计审核驳回数 + long rejectedCount = userTasks.stream() + .filter(t -> "rejected_by_demand".equals(t.getApprovalStatus())) + .count(); + vo.setRejectedCount((int) rejectedCount); + + // 计算审核通过率 + if (userTasks.size() > 0) { + vo.setApprovalRate(BigDecimal.valueOf(approvedCount * 100.0 / userTasks.size()) + .setScale(2, RoundingMode.HALF_UP)); + } else { + vo.setApprovalRate(BigDecimal.ZERO); + } + + // 计算消耗积分 + BigDecimal consumedScore = userTasks.stream() + .filter(t -> t.getScore() != null) + .map(Task::getScore) + .reduce(BigDecimal.ZERO, BigDecimal::add); + vo.setConsumedScore(consumedScore); + + // 统计已完成任务数 + long completedCount = userTasks.stream() + .filter(t -> "completed".equals(t.getStatus())) + .count(); + vo.setCompletedCount((int) completedCount); + + // 统计进行中任务数 + long inProgressCount = userTasks.stream() + .filter(t -> "in_progress".equals(t.getStatus())) + .count(); + vo.setInProgressCount((int) inProgressCount); + + // 统计待审核任务数 + long pendingCount = userTasks.stream() + .filter(t -> "pending_primary_approval".equals(t.getApprovalStatus()) || + "pending_detail_completion".equals(t.getApprovalStatus())) + .count(); + vo.setPendingCount((int) pendingCount); + + result.add(vo); + } + + // 4. 按任务数量降序排序 + result.sort((a, b) -> Integer.compare(b.getTaskCount(), a.getTaskCount())); + + // 5. 设置排名 + for (int i = 0; i < result.size(); i++) { + result.get(i).setRank(i + 1); + } + + return result; + } } diff --git a/backend/src/main/java/com/designhub/vo/PrimaryDeptUserRankingVO.java b/backend/src/main/java/com/designhub/vo/PrimaryDeptUserRankingVO.java new file mode 100644 index 0000000..a24d682 --- /dev/null +++ b/backend/src/main/java/com/designhub/vo/PrimaryDeptUserRankingVO.java @@ -0,0 +1,131 @@ +package com.designhub.vo; + +import java.math.BigDecimal; + +/** + * 一级需求部门用户排名VO(用户维度统计) + */ +public class PrimaryDeptUserRankingVO { + + /** + * 排名 + */ + private Integer rank; + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户姓名 + */ + private String userName; + + /** + * 用户部门ID + */ + private Long deptId; + + /** + * 用户部门名称 + */ + private String deptName; + + /** + * 一级部门名称 + */ + private String firstLevelDeptName; + + /** + * 二级部门名称 + */ + private String secondLevelDeptName; + + /** + * 发起任务数 + */ + private Integer taskCount; + + /** + * 审核通过数 + */ + private Integer approvedCount; + + /** + * 审核驳回数 + */ + private Integer rejectedCount; + + /** + * 审核通过率(百分比) + */ + private BigDecimal approvalRate; + + /** + * 消耗积分 + */ + private BigDecimal consumedScore; + + /** + * 已完成任务数 + */ + private Integer completedCount; + + /** + * 进行中任务数 + */ + private Integer inProgressCount; + + /** + * 待审核任务数 + */ + private Integer pendingCount; + + // ==================== Getters and Setters ==================== + + public Integer getRank() { return rank; } + public void setRank(Integer rank) { this.rank = rank; } + + public Long getUserId() { return userId; } + public void setUserId(Long userId) { this.userId = userId; } + + public String getUserName() { return userName; } + public void setUserName(String userName) { this.userName = userName; } + + public Long getDeptId() { return deptId; } + public void setDeptId(Long deptId) { this.deptId = deptId; } + + public String getDeptName() { return deptName; } + public void setDeptName(String deptName) { this.deptName = deptName; } + + public String getFirstLevelDeptName() { return firstLevelDeptName; } + public void setFirstLevelDeptName(String firstLevelDeptName) { this.firstLevelDeptName = firstLevelDeptName; } + + public String getSecondLevelDeptName() { return secondLevelDeptName; } + public void setSecondLevelDeptName(String secondLevelDeptName) { this.secondLevelDeptName = secondLevelDeptName; } + + public Integer getTaskCount() { return taskCount; } + public void setTaskCount(Integer taskCount) { this.taskCount = taskCount; } + + public Integer getApprovedCount() { return approvedCount; } + public void setApprovedCount(Integer approvedCount) { this.approvedCount = approvedCount; } + + public Integer getRejectedCount() { return rejectedCount; } + public void setRejectedCount(Integer rejectedCount) { this.rejectedCount = rejectedCount; } + + public BigDecimal getApprovalRate() { return approvalRate; } + public void setApprovalRate(BigDecimal approvalRate) { this.approvalRate = approvalRate; } + + public BigDecimal getConsumedScore() { return consumedScore; } + public void setConsumedScore(BigDecimal consumedScore) { this.consumedScore = consumedScore; } + + public Integer getCompletedCount() { return completedCount; } + public void setCompletedCount(Integer completedCount) { this.completedCount = completedCount; } + + public Integer getInProgressCount() { return inProgressCount; } + public void setInProgressCount(Integer inProgressCount) { this.inProgressCount = inProgressCount; } + + public Integer getPendingCount() { return pendingCount; } + public void setPendingCount(Integer pendingCount) { this.pendingCount = pendingCount; } +} diff --git a/frontend/src/api/analytics.ts b/frontend/src/api/analytics.ts index 4cb595c..71e7437 100644 --- a/frontend/src/api/analytics.ts +++ b/frontend/src/api/analytics.ts @@ -555,6 +555,25 @@ export interface BrandTaskDistribution { completedCount: number; } +// 一级需求部门用户排名(用户维度统计) +export interface PrimaryDeptUserRanking { + rank: number; + userId: number; + userName: string; + deptId?: number; + deptName?: string; + firstLevelDeptName?: string; + secondLevelDeptName?: string; + taskCount: number; + approvedCount: number; + rejectedCount: number; + approvalRate: number; + consumedScore: number; + completedCount: number; + inProgressCount: number; + pendingCount: number; +} + // 获取一级需求部门统计数据 export const getPrimaryDeptStatistics = async ( startTime: string, @@ -612,3 +631,17 @@ export const getPrimaryTaskBrandDistribution = async ( } return response as unknown as BrandTaskDistribution[]; }; + +// 获取一级需求部门用户排名(用户维度统计) +export const getPrimaryDeptUserRanking = async ( + startTime: string, + endTime: string +): Promise => { + const params = { startTime, endTime }; + + const response = await request.get('/analytics/primary-dept/user-ranking', { params }); + if (response && typeof response === 'object' && 'data' in response) { + return response.data as PrimaryDeptUserRanking[]; + } + return response as unknown as PrimaryDeptUserRanking[]; +}; diff --git a/frontend/src/views/Analytics.vue b/frontend/src/views/Analytics.vue index 53bb3cc..2a5f50b 100644 --- a/frontend/src/views/Analytics.vue +++ b/frontend/src/views/Analytics.vue @@ -670,6 +670,37 @@ + + +
+
+

用户维度统计(一级需求)

+
+ + + +
@@ -769,6 +800,7 @@ import { getPrimaryDeptStatistics, getPrimaryDeptRanking, getPrimaryTaskBrandDistribution, + getPrimaryDeptUserRanking, type DemandStatistics, type DesignerPerformance, type ProcessEfficiency, @@ -779,7 +811,8 @@ import { type DimensionAnalysis, type PrimaryDeptStatistics, type PrimaryDeptRanking, - type BrandTaskDistribution + type BrandTaskDistribution, + type PrimaryDeptUserRanking } from '@/api/analytics' import { message } from 'ant-design-vue' import * as echarts from 'echarts' @@ -901,6 +934,7 @@ const primaryDeptStats = reactive({ }) const primaryDeptRanking = ref([]) const brandDistribution = ref([]) +const primaryDeptUserRanking = ref([]) // 部门筛选 const selectedDeptId = ref(undefined) @@ -1152,6 +1186,23 @@ const brandDistributionColumns = [ { title: '已完成', dataIndex: 'completedCount', key: 'completedCount', align: 'center', width: 80 } ] +// 一级需求部门用户排名列(用户维度统计) +const primaryDeptUserColumns = [ + { title: '排名', key: 'rank', dataIndex: 'rank', align: 'center', width: 60 }, + { title: '用户姓名', dataIndex: 'userName', key: 'userName', ellipsis: true, width: 120 }, + { title: '部门名称', dataIndex: 'deptName', key: 'deptName', ellipsis: true }, + { title: '一级部门', dataIndex: 'firstLevelDeptName', key: 'firstLevelDeptName', ellipsis: true, width: 120 }, + { title: '二级部门', dataIndex: 'secondLevelDeptName', key: 'secondLevelDeptName', ellipsis: true, width: 120 }, + { title: '发起任务数', dataIndex: 'taskCount', key: 'taskCount', align: 'center', width: 100 }, + { title: '审核通过数', dataIndex: 'approvedCount', key: 'approvedCount', align: 'center', width: 100 }, + { title: '审核驳回数', dataIndex: 'rejectedCount', key: 'rejectedCount', align: 'center', width: 100 }, + { title: '审核通过率', key: 'approvalRate', align: 'center', width: 100 }, + { title: '消耗积分', key: 'consumedScore', align: 'center', width: 100 }, + { title: '已完成', dataIndex: 'completedCount', key: 'completedCount', align: 'center', width: 80 }, + { title: '进行中', dataIndex: 'inProgressCount', key: 'inProgressCount', align: 'center', width: 80 }, + { title: '待审核', dataIndex: 'pendingCount', key: 'pendingCount', align: 'center', width: 80 } +] + // 方法 const handleMonthChange = (value: string | null) => { selectedMonth.value = value @@ -1229,14 +1280,16 @@ const loadAllData = async () => { // 加载一级需求部门统计数据 try { - const [primaryStats, primaryRanking, brandDist] = await Promise.all([ + const [primaryStats, primaryRanking, brandDist, userRanking] = await Promise.all([ getPrimaryDeptStatistics(startTime, endTime), getPrimaryDeptRanking(startTime, endTime), - getPrimaryTaskBrandDistribution(startTime, endTime) + getPrimaryTaskBrandDistribution(startTime, endTime), + getPrimaryDeptUserRanking(startTime, endTime) ]) Object.assign(primaryDeptStats, primaryStats) primaryDeptRanking.value = primaryRanking brandDistribution.value = brandDist + primaryDeptUserRanking.value = userRanking } catch (e) { console.warn('加载一级需求部门统计失败:', e) } diff --git a/更新通知-2026-01-22.md b/更新通知-2026-01-22.md new file mode 100644 index 0000000..f5a9f2c --- /dev/null +++ b/更新通知-2026-01-22.md @@ -0,0 +1,356 @@ +# 系统更新通知 + +**更新日期:** 2026年1月22日 + +--- + +## 📋 更新概览 + +本次更新主要包含以下内容: +- ✨ 扩展任务搜索功能,支持搜索任务卡片的所有信息 +- 🔒 一级需求部门发起任务必须上传预览图 +- 🎨 任务卡片增加一级需求人显示,优化长名字显示 +- 🐛 修复流水线任务异常扣分问题 +- 🐛 修复需求部门看不到一级需求任务的问题 +- 🐛 修复需求部门负责人无法驳回任务的问题 +- 🐛 修复筛选功能搜索报错问题 +- 🐛 修复 SQL 查询错误 + +--- + +## ✨ 新增功能 + +### 1. 扩展任务搜索功能 + +**功能描述:** 任务搜索功能现在支持搜索任务卡片的所有信息,不再仅限于任务标题和编号。 + +**搜索范围包括:** +- ✅ 任务标题 (`title`) +- ✅ 任务编号 (`task_no`) +- ✅ SPU信息 (`spu_info`) +- ✅ 备注信息 (`remark`) +- ✅ 产品条码 (`product_barcodes`) +- ✅ 发布人姓名 (`publisher_name`) +- ✅ 发布人部门名称 (`publisher_dept_name`) +- ✅ 品牌名称 (`brand_name`) +- ✅ 类目名称 (`category_name`) +- ✅ 平台名称 (`platform_name`) +- ✅ 任务类型名称 (`task_type_name`) +- ✅ 一级需求人姓名 (`primary_publisher_name`) +- ✅ 一级需求部门名称 (`primary_publisher_dept_name`) +- ✅ 需求部门发起人姓名 (`detail_completer_name`) +- ✅ 接收人姓名 (`receiver_name`) + +**适用场景:** +- 任务大厅搜索 +- 进行中任务搜索 +- 已完成任务搜索 +- 子任务搜索 + +**使用示例:** +- 搜索品牌名称(如"卓禾")可以找到该品牌的所有任务 +- 搜索发布人姓名可以找到该用户发布的所有任务 +- 搜索一级需求人姓名可以找到该用户发起的一级需求任务 + +--- + +### 2. 一级需求部门必须上传预览图 + +**功能描述:** 一级需求部门用户发起任务时,系统强制要求上传预览图。 + +**实现方式:** +- 前端验证:表单验证规则中添加预览图必填验证 +- 后端验证:创建任务时检查预览图是否上传 +- 用户提示:预览图上传区域显示明确的必填提示 + +**影响范围:** +- 一级需求部门用户创建普通任务 +- 一级需求部门用户创建流水线任务 + +**用户体验:** +- 如果未上传预览图,提交时会显示错误提示:"一级需求部门发起任务必须上传预览图" +- 预览图上传区域会显示红色警告图标和提示文字 + +--- + +### 3. 任务卡片增加一级需求人显示 + +**功能描述:** 对于一级需求部门发起的任务,任务卡片现在会显示一级需求人和需求部门发起人的信息,让任务来源更加清晰。 + +**显示内容:** +- ✅ **一级需求人**:显示一级需求部门发起人的姓名和部门名称(橙色标识) +- ✅ **需求部门发起人**:显示补充任务明细的需求部门成员姓名(绿色标识) +- ✅ **发布人**:显示任务的发布人姓名(默认绿色标识) + +**显示逻辑:** +- 如果是一级需求任务(`isPrimaryTask=1`): + - 显示"一级需求人"(如果存在) + - 显示"需求部门发起人"(如果存在,即已补充明细) + - 否则显示"发布人" +- 如果是普通任务: + - 显示"发布人" + +**优化内容:** +- 一级需求人名字超过 8 个字符时显示省略号 +- 需求部门发起人名字超过 8 个字符时显示省略号 +- 发布人名字超过 8 个字符时显示省略号 +- 鼠标悬停时通过 Tooltip 显示完整信息(包含部门名称) + +**显示效果:** +- 一级需求任务示例: + - `一级需求人: 张三... (运营中心...)`(橙色) + - `需求部门发起人: 李四...`(绿色) +- 悬停提示:`一级需求人: 张三丰 (运营中心A-事业二部)` + +**适用位置:** +- 任务大厅任务卡片 +- 进行中任务卡片 +- 已完成任务卡片 +- 我发起的任务卡片 +- 待审核任务卡片 +- 草稿箱任务卡片 +- 子任务卡片 + +--- + +## 🐛 问题修复 + +### 1. 修复流水线任务异常扣分问题 + +**问题描述:** +- 流水线任务的子任务按时完成后,主任务仍然被系统检查进度更新情况 +- 导致已完成子任务的用户被错误扣分 +- 影响范围:所有流水线任务的接收人 + +**问题原因:** +- 进度检查服务 (`ProgressCheckService`) 在检查任务进度时,没有区分流水线任务的主任务和子任务 +- 流水线任务的主任务 (`is_pipeline=1`) 在子任务完成后,主任务状态仍为 `in_progress`,但主任务本身不需要更新进度 +- 系统错误地对主任务进行了进度检查,导致已完成子任务的用户被扣分 + +**解决方案:** +- 修改 `ProgressCheckService.checkDailyProgressUpdate()` 方法 +- 在进度检查时排除流水线任务的主任务(`is_pipeline=1`) +- 只对流水线任务的子任务进行进度检查 +- 清理了历史错误扣分记录(共 21 条) + +**修复效果:** +- ✅ 流水线任务的主任务不再被检查进度 +- ✅ 已完成子任务的用户不会再被错误扣分 +- ✅ 历史错误扣分记录已清理 + +**影响用户:** +- 所有流水线任务的接收人 +- 特别是已完成子任务但主任务仍在进行中的用户 + +--- + +### 2. 修复需求部门看不到一级需求任务的问题 + +**问题描述:** +- 需求部门负责人审核通过一级需求部门的任务后,在"进行中任务"中看不到该任务 +- 需求部门成员补充完任务明细并发布后,在"进行中任务"中也看不到该任务 +- 影响范围:所有需求部门负责人和成员 + +**问题原因:** +- 一级需求部门的任务在需求部门负责人审核通过后,状态变为 `pending_detail_completion`(待明细完善) +- 此时任务的 `publisher_dept_id` 是一级需求部门的 ID,不是需求部门的 ID +- 需求部门负责人的"进行中任务"查询只按 `publisherDeptIds` 过滤,无法找到这些任务 +- 需求部门成员的"进行中任务"查询只查询 `role='publisher'` 的任务,无法找到作为 `detail_completer` 的任务 + +**解决方案:** +- **后端修改:** + - 在 `TaskController.getTaskList` 中添加 `includeDetailCompleterDeptIds` 参数 + - 在 `TaskService.getTaskList` 中添加按明细完善人部门查询的逻辑 + - 在 `TaskController.getMyTasks` 中添加 `role='detail_completer'` 支持 + - 新增 `TaskService.getMyDetailCompleterTasks` 方法,查询用户作为明细完善人的任务 +- **前端修改:** + - 需求部门负责人查看"进行中任务"时,传递 `includeDetailCompleterDeptIds` 参数 + - 需求部门成员查看"进行中任务"时,同时查询 `publisher` 和 `detail_completer` 角色的任务并合并结果 + +**修复效果:** +- ✅ 需求部门负责人可以在"进行中任务"中看到已审核通过的一级需求任务 +- ✅ 需求部门成员可以在"进行中任务"中看到自己作为明细完善人的任务 +- ✅ 任务状态流转更加清晰,用户能正确跟踪任务进度 + +**影响用户:** +- 所有需求部门负责人 +- 所有需求部门成员 + +--- + +### 3. 修复需求部门负责人无法驳回任务的问题 + +**问题描述:** +- 需求部门负责人在驳回一级需求部门的任务时,系统报错:"需求部门负责人不存在" +- 导致需求部门负责人无法正常驳回任务 +- 影响范围:所有需求部门负责人 + +**问题原因:** +- 一级需求部门的任务被驳回时,系统尝试将任务返回给需求部门的负责人 +- 但某些一级需求部门(`dept_type='primary_demand'`)可能没有设置 `leader_id` +- 当 `TaskApprovalService.rejectByReceivingLeader` 方法检查 `demandDept.getLeaderId()` 时,如果为 `null`,会抛出异常 + +**解决方案:** +- 修改 `TaskApprovalService.rejectByReceivingLeader` 方法 +- 当需求部门的 `leader_id` 为 `null` 时,不再抛出异常 +- 改为将任务的 `currentApproverId` 设置为任务的 `publisherId`,将任务返回给原始发起人 +- 这样即使需求部门没有负责人,任务也能正常被驳回并返回给发起人 + +**修复效果:** +- ✅ 需求部门负责人可以正常驳回一级需求部门的任务 +- ✅ 即使需求部门没有负责人,任务也能正常被驳回 +- ✅ 任务会正确返回给原始发起人 + +**影响用户:** +- 所有需求部门负责人 +- 特别是需要驳回一级需求部门任务的负责人 + +--- + +### 4. 修复筛选功能搜索报错 + +**问题描述:** +- 错误信息:`TypeError: Assignment to constant variable` +- 触发场景:在任务大厅、进行中任务、已完成任务等页面使用筛选功能时 + +**问题原因:** +- 使用 `v-model` 绑定 `reactive` 对象时,Vue 尝试重新赋值整个对象 +- `reactive` 对象不能直接重新赋值,导致报错 + +**解决方案:** +- 将所有 `TaskFilters` 组件的 `v-model` 改为 `:model-value` 和 `@update:model-value` +- 在事件处理函数中使用 `Object.assign` 更新对象属性,而不是重新赋值整个对象 + +**修复的筛选器:** +- ✅ 可接任务筛选 (`filters`) +- ✅ 待审核任务筛选 (`reviewFilters`) +- ✅ 进行中任务筛选 (`inProgressFilters`) +- ✅ 已完成任务筛选 (`completedFilters`) +- ✅ 我发起的任务筛选 (`publishedFilters`) +- ✅ 草稿箱筛选 (`draftsFilters`) + +--- + +### 4. 修复 SQL 查询错误 + +**问题描述:** +- 错误信息:`Unknown column 't.description' in 'where clause'` +- 触发场景:使用搜索功能时 + +**问题原因:** +- 在扩展搜索功能时,SQL 查询中引用了 `task` 表中不存在的 `description` 字段 + +**解决方案:** +- 从所有 SQL 查询中移除了对 `t.description` 字段的引用 +- 修复了以下文件中的查询: + - `TaskMapper.java` - `selectTaskHall` 和 `selectTaskHallByDept` 方法 + - `TaskItemMapper.java` - 所有子任务查询方法 + - `TaskService.java` - `getCompletedTasks` 方法 + +--- + +## 📝 技术细节 + +### 前端修改 + +**文件:** `frontend/src/views/TaskHall.vue` +- 导入 `Tooltip` 组件 +- 添加 `truncateText` 辅助函数 +- 修改所有显示需求人名字的地方,添加 Tooltip 和截断显示 +- 修复 `v-model` 绑定问题,改用 `:model-value` 和 `@update:model-value` +- **修复需求部门看不到一级需求任务:** + - 需求部门负责人查看"进行中任务"时,添加 `includeDetailCompleterDeptIds` 参数 + - 需求部门成员查看"进行中任务"时,同时查询 `publisher` 和 `detail_completer` 角色的任务并合并结果 + - 更新 `loadDashboardStats` 统计逻辑,包含 `detail_completer` 角色的任务 + +**文件:** `frontend/src/views/CreateTask.vue` +- 添加预览图必填验证规则 +- 在提交时添加额外的预览图验证 +- 更新预览图上传区域的提示信息 + +**文件:** `frontend/src/api/task.ts` +- 在 `MyTasksParams` 接口中添加 `detail_completer` 作为有效的 `role` 值 +- 在 `getTasks` 函数中添加 `includeDetailCompleterDeptIds` 参数 + +### 后端修改 + +**文件:** `backend/src/main/java/com/designhub/mapper/TaskMapper.java` +- 扩展 `selectTaskHall` 和 `selectTaskHallByDept` 的搜索条件 +- 添加明细完善人、接收人、任务类型的关联查询 +- 移除不存在的 `description` 字段引用 + +**文件:** `backend/src/main/java/com/designhub/mapper/TaskItemMapper.java` +- 扩展所有子任务查询方法的搜索条件 +- 移除不存在的 `description` 字段引用 + +**文件:** `backend/src/main/java/com/designhub/service/TaskService.java` +- 扩展 `getCompletedTasks` 方法的搜索条件 +- 在 `createTask` 和 `createPipelineTask` 方法中添加预览图必填验证 +- 移除不存在的 `description` 字段引用 +- **修复需求部门看不到一级需求任务:** + - 在 `getTaskList` 方法中添加 `includeDetailCompleterDeptIds` 参数支持 + - 添加按明细完善人部门查询的逻辑(支持一级需求任务) + - 在 `getMyTasks` 方法中添加 `role='detail_completer'` 支持 + - 新增 `getMyDetailCompleterTasks` 方法,查询用户作为明细完善人的任务 + - 在 `fillTaskRelatedInfo` 方法中添加 `detailCompleterName` 的填充逻辑 + +**文件:** `backend/src/main/java/com/designhub/controller/TaskController.java` +- **修复需求部门看不到一级需求任务:** + - 在 `getTaskList` 接口中添加 `includeDetailCompleterDeptIds` 参数(逗号分隔的部门ID列表) + - 在 `getMyTasks` 接口中添加 `role='detail_completer'` 支持 + +**文件:** `backend/src/main/java/com/designhub/service/ProgressCheckService.java` +- **修复流水线任务异常扣分:** + - 修改 `checkDailyProgressUpdate` 方法,排除流水线任务的主任务(`is_pipeline=1`) + - 只对流水线任务的子任务进行进度检查 + +--- + +## 🎯 影响范围 + +### 用户影响 +- ✅ **一级需求部门用户**:发起任务时必须上传预览图 +- ✅ **所有用户**:搜索功能更强大,可以搜索更多字段 +- ✅ **所有用户**:任务卡片显示更美观,长名字不再影响布局 +- ✅ **流水线任务接收人**:不再被错误扣分,已完成子任务后不会被检查主任务进度 +- ✅ **需求部门负责人**:可以在"进行中任务"中看到已审核通过的一级需求任务 +- ✅ **需求部门成员**:可以在"进行中任务"中看到自己作为明细完善人的任务 + +### 功能影响 +- ✅ **搜索功能**:支持搜索任务卡片的所有信息 +- ✅ **筛选功能**:修复了搜索时的报错问题 +- ✅ **任务创建**:一级需求部门用户必须上传预览图 +- ✅ **进度检查**:流水线任务的主任务不再被检查进度,避免错误扣分 +- ✅ **任务可见性**:需求部门可以正确查看一级需求任务的进度 + +--- + +## ⚠️ 注意事项 + +1. **一级需求部门用户**:从本次更新开始,发起任务时必须上传预览图,否则无法提交任务。 + +2. **搜索功能**:搜索功能已扩展,现在可以搜索更多字段,但不再支持搜索 `description` 字段(该字段在数据库中不存在)。 + +3. **名字显示**:任务卡片中的需求人名字如果超过 8 个字符会自动截断,鼠标悬停可查看完整信息。 + +4. **流水线任务进度检查**:流水线任务的主任务不再进行进度检查,只有子任务会被检查进度。已完成子任务的用户不会再被错误扣分。 + +5. **一级需求任务可见性**: + - 需求部门负责人审核通过一级需求任务后,可以在"进行中任务"中看到该任务 + - 需求部门成员补充完任务明细并发布后,可以在"进行中任务"中看到该任务 + - 任务状态为 `pending_detail_completion`(待明细完善)时,会显示在需求部门的"进行中任务"中 + +--- + +## 📞 问题反馈 + +如果在使用过程中遇到任何问题,请及时反馈: +- 系统异常 +- 功能异常 +- 用户体验问题 + +--- + +**更新完成时间:** 2026年1月22日 15:40 + +**部署状态:** ✅ 已部署到生产环境