From 93da7aca2255ba928cb6eb35fdc86f7f2222eaf3 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 22 Jan 2026 14:15:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=80=E7=BA=A7=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E5=8D=A1=E7=89=87=E6=98=BE=E7=A4=BA=E4=B8=80?= =?UTF-8?q?=E7=BA=A7=E9=9C=80=E6=B1=82=E4=BA=BA=E5=92=8C=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E9=83=A8=E9=97=A8=E5=8F=91=E8=B5=B7=E4=BA=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 后端 fillTaskRelatedInfo 填充明细完善人姓名 (detailCompleterName) 2. 前端任务卡片统一显示: - 一级需求人(橙色):一级需求部门的原始发起人 + 部门名称 - 需求部门发起人(绿色):明细完善人 - 发布人:非一级需求任务时显示 3. 需求部门负责人和成员可以看到一级需求任务(明细完善人在本部门的) 4. 后端增加 getMyDetailCompleterTasks 接口查询明细完善人任务 --- .../designhub/controller/TaskController.java | 22 +- .../com/designhub/service/TaskService.java | 115 +++++++++- frontend/src/api/task.ts | 2 +- frontend/src/views/TaskHall.vue | 209 +++++++++++++----- 4 files changed, 283 insertions(+), 65 deletions(-) diff --git a/backend/src/main/java/com/designhub/controller/TaskController.java b/backend/src/main/java/com/designhub/controller/TaskController.java index a13215e..72461c2 100644 --- a/backend/src/main/java/com/designhub/controller/TaskController.java +++ b/backend/src/main/java/com/designhub/controller/TaskController.java @@ -68,11 +68,12 @@ public class TaskController { @Parameter(description = "发布者部门ID列表(逗号分隔)") @RequestParam(required = false) String publisherDeptIds, @Parameter(description = "接收部门ID") @RequestParam(required = false) Long receiverDeptId, @Parameter(description = "排除的状态列表(逗号分隔,如 draft,completed,cancelled)") @RequestParam(required = false) String excludeStatuses, + @Parameter(description = "包含明细完善人所属部门ID列表(逗号分隔,用于查询一级需求任务)") @RequestParam(required = false) String includeDetailCompleterDeptIds, @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page, @Parameter(description = "每页数量") @RequestParam(defaultValue = "20") Integer pageSize) { - log.info("查询任务列表: status={}, taskTypeId={}, publisherDeptId={}, publisherDeptIds={}, receiverDeptId={}, excludeStatuses={}, page={}, pageSize={}", - status, taskTypeId, publisherDeptId, publisherDeptIds, receiverDeptId, excludeStatuses, page, pageSize); + log.info("查询任务列表: status={}, taskTypeId={}, publisherDeptId={}, publisherDeptIds={}, receiverDeptId={}, excludeStatuses={}, includeDetailCompleterDeptIds={}, page={}, pageSize={}", + status, taskTypeId, publisherDeptId, publisherDeptIds, receiverDeptId, excludeStatuses, includeDetailCompleterDeptIds, page, pageSize); // 处理多个发布者部门ID List publisherDeptIdList = null; @@ -93,7 +94,17 @@ public class TaskController { .collect(Collectors.toList()); } - IPage taskPage = taskService.getTaskList(page, pageSize, status, taskTypeId, publisherDeptId, publisherDeptIdList, receiverDeptId, excludeStatusList); + // 处理明细完善人部门ID列表 + List detailCompleterDeptIdList = null; + if (includeDetailCompleterDeptIds != null && !includeDetailCompleterDeptIds.isEmpty()) { + detailCompleterDeptIdList = Arrays.stream(includeDetailCompleterDeptIds.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .map(Long::parseLong) + .collect(Collectors.toList()); + } + + IPage taskPage = taskService.getTaskList(page, pageSize, status, taskTypeId, publisherDeptId, publisherDeptIdList, receiverDeptId, excludeStatusList, detailCompleterDeptIdList); return Result.success(taskPage); } @@ -284,7 +295,7 @@ public class TaskController { @Operation(summary = "我的任务", description = "获取当前用户的任务列表") @GetMapping("/my") public Result> getMyTasks( - @RequestParam(required = false) String role, // publisher 或 receiver + @RequestParam(required = false) String role, // publisher、receiver 或 detail_completer @RequestParam(required = false) String status, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "20") Integer pageSize, @@ -299,6 +310,9 @@ public class TaskController { IPage taskPage; if ("publisher".equals(role)) { taskPage = taskService.getMyPublishedTasks(page, pageSize, userId, status); + } else if ("detail_completer".equals(role)) { + // 查询用户作为明细完善人的一级需求任务 + taskPage = taskService.getMyDetailCompleterTasks(page, pageSize, userId, status); } else { taskPage = taskService.getMyReceivedTasks(page, pageSize, userId, status); } diff --git a/backend/src/main/java/com/designhub/service/TaskService.java b/backend/src/main/java/com/designhub/service/TaskService.java index 66bf162..e45621e 100644 --- a/backend/src/main/java/com/designhub/service/TaskService.java +++ b/backend/src/main/java/com/designhub/service/TaskService.java @@ -17,6 +17,7 @@ import com.designhub.mapper.BrandMapper; import com.designhub.mapper.CategoryMapper; import com.designhub.mapper.PlatformMapper; import com.designhub.mapper.DemandTypeMapper; +import com.designhub.mapper.UserMapper; import com.designhub.vo.DashboardStatsVO; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -64,6 +65,7 @@ public class TaskService { private final com.designhub.mapper.DepartmentMapper departmentMapper; private final com.designhub.util.WorkHoursCalculator workHoursCalculator; private final com.designhub.mapper.DelayPenaltyRecordMapper delayPenaltyRecordMapper; + private final UserMapper userMapper; @org.springframework.beans.factory.annotation.Autowired @org.springframework.context.annotation.Lazy @@ -784,7 +786,7 @@ public class TaskService { * 获取任务列表 */ public IPage getTaskList(Integer page, Integer pageSize, String status, Long taskTypeId, Long publisherDeptId, List publisherDeptIds, Long receiverDeptId) { - return getTaskList(page, pageSize, status, taskTypeId, publisherDeptId, publisherDeptIds, receiverDeptId, null); + return getTaskList(page, pageSize, status, taskTypeId, publisherDeptId, publisherDeptIds, receiverDeptId, null, null); } /** @@ -792,6 +794,15 @@ public class TaskService { * @param excludeStatuses 要排除的状态列表(如 draft,completed,cancelled) */ public IPage getTaskList(Integer page, Integer pageSize, String status, Long taskTypeId, Long publisherDeptId, List publisherDeptIds, Long receiverDeptId, List excludeStatuses) { + return getTaskList(page, pageSize, status, taskTypeId, publisherDeptId, publisherDeptIds, receiverDeptId, excludeStatuses, null); + } + + /** + * 获取任务列表(支持排除指定状态 + 包含一级需求任务的明细完善人所属部门) + * @param excludeStatuses 要排除的状态列表(如 draft,completed,cancelled) + * @param includeDetailCompleterDeptIds 包含明细完善人所属部门ID列表(用于需求部门负责人查看一级需求任务) + */ + public IPage getTaskList(Integer page, Integer pageSize, String status, Long taskTypeId, Long publisherDeptId, List publisherDeptIds, Long receiverDeptId, List excludeStatuses, List includeDetailCompleterDeptIds) { Page pageParam = new Page<>(page, pageSize); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); @@ -813,11 +824,63 @@ public class TaskService { // 根据发布者部门筛选(部门负责人视角切换时使用) // 优先使用 publisherDeptIds(多个部门ID列表) - if (publisherDeptIds != null && !publisherDeptIds.isEmpty()) { - wrapper.in(Task::getPublisherDeptId, publisherDeptIds); - log.info("按多个发布者部门筛选: publisherDeptIds={}", publisherDeptIds); - } else if (publisherDeptId != null) { - wrapper.eq(Task::getPublisherDeptId, publisherDeptId); + // 🔥 如果同时有 includeDetailCompleterDeptIds,需要用 OR 条件组合 + if (includeDetailCompleterDeptIds != null && !includeDetailCompleterDeptIds.isEmpty()) { + // 需要查询:(发布者部门在 publisherDeptIds 中) OR (一级需求任务且明细完善人在指定部门中) + // 获取指定部门的所有用户ID + List detailCompleterUserIds = userMapper.selectList( + new LambdaQueryWrapper() + .in(User::getDeptId, includeDetailCompleterDeptIds) + .eq(User::getStatus, 1) + .eq(User::getDeleted, 0) + ).stream().map(User::getId).collect(java.util.stream.Collectors.toList()); + + log.info("查询一级需求任务,明细完善人部门IDs: {}, 对应用户IDs: {}", includeDetailCompleterDeptIds, detailCompleterUserIds); + + if (publisherDeptIds != null && !publisherDeptIds.isEmpty()) { + // 组合条件:(publisherDeptId IN publisherDeptIds) OR (isPrimaryTask=1 AND detailCompleterId IN userIds) + wrapper.and(w -> w + .in(Task::getPublisherDeptId, publisherDeptIds) + .or(orWrapper -> { + orWrapper.eq(Task::getIsPrimaryTask, 1); + if (!detailCompleterUserIds.isEmpty()) { + orWrapper.in(Task::getDetailCompleterId, detailCompleterUserIds); + } else { + // 如果没有用户,添加一个不可能匹配的条件 + orWrapper.eq(Task::getDetailCompleterId, -1); + } + }) + ); + log.info("组合查询: publisherDeptIds={} OR (isPrimaryTask=1 AND detailCompleterId IN {})", publisherDeptIds, detailCompleterUserIds); + } else if (publisherDeptId != null) { + wrapper.and(w -> w + .eq(Task::getPublisherDeptId, publisherDeptId) + .or(orWrapper -> { + orWrapper.eq(Task::getIsPrimaryTask, 1); + if (!detailCompleterUserIds.isEmpty()) { + orWrapper.in(Task::getDetailCompleterId, detailCompleterUserIds); + } else { + orWrapper.eq(Task::getDetailCompleterId, -1); + } + }) + ); + } else { + // 只查询一级需求任务 + wrapper.eq(Task::getIsPrimaryTask, 1); + if (!detailCompleterUserIds.isEmpty()) { + wrapper.in(Task::getDetailCompleterId, detailCompleterUserIds); + } else { + wrapper.eq(Task::getDetailCompleterId, -1); + } + } + } else { + // 原有逻辑:只按发布者部门筛选 + if (publisherDeptIds != null && !publisherDeptIds.isEmpty()) { + wrapper.in(Task::getPublisherDeptId, publisherDeptIds); + log.info("按多个发布者部门筛选: publisherDeptIds={}", publisherDeptIds); + } else if (publisherDeptId != null) { + wrapper.eq(Task::getPublisherDeptId, publisherDeptId); + } } // 根据接收部门筛选(部门负责人视角切换时使用,支持部门ID列表) @@ -1237,6 +1300,38 @@ public class TaskService { return taskPage; } + /** + * 获取我作为明细完善人的一级需求任务 + * 需求部门成员完善任务明细后,可以在"进行中任务"看到这些任务 + */ + public IPage getMyDetailCompleterTasks(Integer page, Integer pageSize, Long userId, String status) { + Page pageParam = new Page<>(page, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + // 查询条件:一级需求任务 且 明细完善人是当前用户 + wrapper.eq(Task::getIsPrimaryTask, 1) + .eq(Task::getDetailCompleterId, userId) + .eq(Task::getDeleted, 0); + + // 根据状态筛选 + if (StringUtils.hasText(status)) { + wrapper.eq(Task::getStatus, status); + } + + // 按更新时间倒序 + wrapper.orderByDesc(Task::getUpdateTime); + + IPage taskPage = taskMapper.selectPage(pageParam, wrapper); + + // 填充关联信息和子任务统计 + for (Task task : taskPage.getRecords()) { + fillTaskRelatedInfo(task); + fillSubTaskStats(task); + } + + return taskPage; + } + /** * 填充流水线任务的子任务统计信息 */ @@ -1445,6 +1540,14 @@ public class TaskService { } } + // 填充明细完善人名称(需求部门成员) + if (task.getDetailCompleterId() != null) { + User detailCompleter = userService.getUserById(task.getDetailCompleterId()); + if (detailCompleter != null) { + task.setDetailCompleterName(detailCompleter.getRealName() != null ? detailCompleter.getRealName() : detailCompleter.getUsername()); + } + } + // 填充子任务统计信息 fillSubTaskStats(task); } diff --git a/frontend/src/api/task.ts b/frontend/src/api/task.ts index 2acd11b..11924c9 100644 --- a/frontend/src/api/task.ts +++ b/frontend/src/api/task.ts @@ -176,7 +176,7 @@ export interface TaskHallParams { } export interface MyTasksParams { - role?: 'publisher' | 'receiver' + role?: 'publisher' | 'receiver' | 'detail_completer' status?: string page?: number pageSize?: number diff --git a/frontend/src/views/TaskHall.vue b/frontend/src/views/TaskHall.vue index caa79e9..479d8d3 100644 --- a/frontend/src/views/TaskHall.vue +++ b/frontend/src/views/TaskHall.vue @@ -221,13 +221,19 @@ 一级需求人: {{ task.primaryPublisherName }} ({{ task.primaryPublisherDeptName || '一级部门' }}) - -
+ +
+ + + + 需求部门发起人: {{ task.detailCompleterName }} +
+ +
- 需求部门发布人: {{ task.publisherName || '未知' }} - 发布人: {{ task.publisherName || '未知' }} + 发布人: {{ task.publisherName || '未知' }}
@@ -424,15 +430,21 @@ - 一级需求人: {{ task.primaryPublisherName }} + 一级需求人: {{ task.primaryPublisherName }} ({{ task.primaryPublisherDeptName || '一级部门' }})
- -
+ +
+ + + + 需求部门发起人: {{ task.detailCompleterName }} +
+ +
- 需求部门发布人: {{ task.publisherName || '--' }} - 发起人: {{ task.publisherName || '--' }} + 发起人: {{ task.publisherName || '--' }}
@@ -516,14 +528,21 @@ - 一级需求人: {{ task.primaryPublisherName }} + 一级需求人: {{ task.primaryPublisherName }} ({{ task.primaryPublisherDeptName || '一级部门' }})
-
+ +
+ + + + 需求部门发起人: {{ task.detailCompleterName }} +
+ +
- 需求部门发布人: {{ task.publisherName }} - 发布人: {{ task.publisherName }} + 发布人: {{ task.publisherName }}
@@ -806,14 +825,21 @@ - 一级需求人: {{ task.primaryPublisherName }} + 一级需求人: {{ task.primaryPublisherName }} ({{ task.primaryPublisherDeptName || '一级部门' }})
-
+ +
+ + + + 需求部门发起人: {{ task.detailCompleterName }} +
+ +
- 需求部门发布人: {{ task.publisherName }} - 发布人: {{ task.publisherName }} + 发布人: {{ task.publisherName }}
@@ -1024,15 +1050,21 @@ - 一级需求人: {{ task.primaryPublisherName }} + 一级需求人: {{ task.primaryPublisherName }} ({{ task.primaryPublisherDeptName || '一级部门' }})
- -
+ +
+ + + + 需求部门发起人: {{ task.detailCompleterName }} +
+ +
- 需求部门发布人: {{ task.publisherName || '--' }} - 发起人: {{ task.publisherName || '--' }} + 发起人: {{ task.publisherName || '--' }}
@@ -1121,14 +1153,21 @@ - 一级需求人: {{ task.primaryPublisherName }} + 一级需求人: {{ task.primaryPublisherName }} ({{ task.primaryPublisherDeptName || '一级部门' }})
-
+ +
+ + + + 需求部门发起人: {{ task.detailCompleterName }} +
+ +
- 需求部门发布人: {{ task.publisherName }} - 发布人: {{ task.publisherName }} + 发布人: {{ task.publisherName }}
@@ -1442,14 +1481,21 @@ - 一级需求人: {{ task.primaryPublisherName }} + 一级需求人: {{ task.primaryPublisherName }} ({{ task.primaryPublisherDeptName || '一级部门' }})
-
+ +
+ + + + 需求部门发起人: {{ task.detailCompleterName }} +
+ +
- 需求部门发布人: {{ task.publisherName }} - 发布人: {{ task.publisherName }} + 发布人: {{ task.publisherName }}
@@ -4593,13 +4639,14 @@ const loadInProgressTasks = async (newFilters?: any) => { console.log('主任务处理后,allTasks长度:', allTasks.length, 'totalCount:', totalCount) } } else { - // 需求部门视角:看该部门发起的任务(通过 deptId 参数) + // 需求部门视角:看该部门发起的任务(通过 deptId 参数)+ 一级需求任务(明细完善人在该部门) console.log('需求部门视角切换,查询部门ID:', currentDeptView.value) // 需求部门负责人:使用 excludeStatuses 排除草稿和已完成的任务 const { status, ...paramsWithoutStatus } = params const taskResponse = await taskApi.getTasks({ ...paramsWithoutStatus, publisherDeptId: currentDeptView.value, // 按发布者部门过滤 + includeDetailCompleterDeptIds: String(currentDeptView.value), // 🔥 同时包含一级需求任务(明细完善人在该部门) excludeStatuses: 'draft,completed,cancelled' // 🔥 后端排除指定状态 }) if (taskResponse.data && taskResponse.data.records) { @@ -4626,7 +4673,7 @@ const loadInProgressTasks = async (newFilters?: any) => { totalCount += itemResponse.data.total || 0 } } else { - // 需求部门负责人:看自己管理的所有需求部门发起的任务 + // 需求部门负责人:看自己管理的所有需求部门发起的任务 + 一级需求任务(指定本部门成员为明细完善人的) // 获取用户管理的所有需求部门ID(包括自己所属的部门) const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}') const demandDeptIds: number[] = [] @@ -4652,10 +4699,12 @@ const loadInProgressTasks = async (newFilters?: any) => { console.log('需求部门负责人查询的部门ID列表:', demandDeptIds) // 需求部门负责人:查询所有处理中的任务(排除 draft、completed、cancelled) + // 🔥 同时查询一级需求任务(指定本部门成员为明细完善人的) const { status: _, ...paramsWithoutStatus } = params const taskResponse = await taskApi.getTasks({ ...paramsWithoutStatus, publisherDeptIds: demandDeptIds.join(','), // 按多个发布者部门过滤 + includeDetailCompleterDeptIds: demandDeptIds.join(','), // 🔥 同时包含一级需求任务(明细完善人在本部门) excludeStatuses: 'draft,completed,cancelled' // 🔥 后端排除指定状态 }) if (taskResponse.data && taskResponse.data.records) { @@ -4693,15 +4742,25 @@ const loadInProgressTasks = async (newFilters?: any) => { totalCount += itemResponse.data.total || 0 } } else if (currentDeptType === 'demand') { - // 需求部门普通用户:看自己发起的任务中有子任务在进行中的 - const taskResponse = await taskApi.getMyTasks({ - role: 'publisher', - page: inProgressPagination.page, - pageSize: inProgressPagination.pageSize, - keyword: inProgressFilters.keyword - }) - if (taskResponse.code === 200 && taskResponse.data && taskResponse.data.records) { - const tasksWithInProgress = taskResponse.data.records.filter((task: any) => { + // 需求部门普通用户:看自己发起的任务 + 自己作为明细完善人的一级需求任务 + const [publisherResponse, detailCompleterResponse] = await Promise.all([ + taskApi.getMyTasks({ + role: 'publisher', + page: inProgressPagination.page, + pageSize: inProgressPagination.pageSize, + keyword: inProgressFilters.keyword + }), + // 🔥 查询自己作为明细完善人的一级需求任务 + taskApi.getMyTasks({ + role: 'detail_completer', + page: 1, + pageSize: 100 // 获取所有 + }) + ]) + + // 处理我发起的任务 + if (publisherResponse.code === 200 && publisherResponse.data && publisherResponse.data.records) { + const tasksWithInProgress = publisherResponse.data.records.filter((task: any) => { if (task.isPipeline === 1) { return (task.subTaskInProgress || 0) > 0 || task.status === 'in_progress' } @@ -4711,8 +4770,29 @@ const loadInProgressTasks = async (newFilters?: any) => { ...task, isSubTask: false })) - totalCount = allTasks.length } + + // 🔥 处理我作为明细完善人的一级需求任务(排除已完成和草稿状态) + if (detailCompleterResponse.code === 200 && detailCompleterResponse.data && detailCompleterResponse.data.records) { + const detailCompleterTasks = detailCompleterResponse.data.records.filter((task: any) => { + // 排除草稿和已完成状态 + return task.status !== 'draft' && task.status !== 'completed' && task.status !== 'cancelled' + }).map((task: any) => ({ + ...task, + isSubTask: false + })) + + // 合并并去重(避免同一任务重复显示) + const existingTaskIds = new Set(allTasks.map((t: any) => t.id)) + detailCompleterTasks.forEach((task: any) => { + if (!existingTaskIds.has(task.id)) { + allTasks.push(task) + } + }) + console.log('需求部门成员 - 发起的任务:', publisherResponse.data.records.length, '明细完善人任务:', detailCompleterTasks.length, '合并后:', allTasks.length) + } + + totalCount = allTasks.length } else { // 接单部门用户(设计师):看子任务卡片 + 直接分配给自己的普通任务 const [itemResponse, taskResponse] = await Promise.all([ @@ -6072,25 +6152,46 @@ const loadAllTabCounts = async () => { } else { // 未切换视角或普通用户:根据部门类型查询 if (currentDeptType === 'demand' || currentDeptType === 'primary_demand') { - // 需求部门/一级需求部门用户:查询自己发起的任务中有子任务在进行中的 + // 需求部门/一级需求部门用户:查询自己发起的任务 + 自己作为明细完善人的一级需求任务 promises.push( - taskApi.getMyTasks({ - role: 'publisher', - page: 1, - pageSize: 100 // 获取所有任务然后过滤 - }).then(res => { - if (res.data && res.data.records) { - // 过滤出有进行中子任务的任务 - const inProgressTasks = res.data.records.filter((task: any) => { + Promise.all([ + taskApi.getMyTasks({ + role: 'publisher', + page: 1, + pageSize: 100 // 获取所有任务然后过滤 + }), + // 🔥 同时查询明细完善人任务 + taskApi.getMyTasks({ + role: 'detail_completer', + page: 1, + pageSize: 100 + }) + ]).then(([publisherRes, detailCompleterRes]) => { + let count = 0 + const countedTaskIds = new Set() + + // 统计我发起的进行中任务 + if (publisherRes.data && publisherRes.data.records) { + const inProgressTasks = publisherRes.data.records.filter((task: any) => { if (task.isPipeline === 1) { return (task.subTaskInProgress || 0) > 0 || task.status === 'in_progress' } return task.status === 'in_progress' }) - inProgressTotal.value = inProgressTasks.length - } else { - inProgressTotal.value = 0 + inProgressTasks.forEach((task: any) => countedTaskIds.add(task.id)) + count += inProgressTasks.length } + + // 🔥 统计我作为明细完善人的进行中任务(去重) + if (detailCompleterRes.data && detailCompleterRes.data.records) { + const detailCompleterInProgress = detailCompleterRes.data.records.filter((task: any) => { + // 排除草稿和已完成状态,且不重复计算 + return task.status !== 'draft' && task.status !== 'completed' && task.status !== 'cancelled' && !countedTaskIds.has(task.id) + }) + count += detailCompleterInProgress.length + } + + inProgressTotal.value = count }).catch(() => { inProgressTotal.value = 0 })