From 9b849ccb40e62f9d133a4e7bc5c080be662a3f02 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 7 Jan 2026 15:38:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=AD=90=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E8=BF=9B=E5=BA=A6=E6=98=BE=E7=A4=BA=E4=B8=BA=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E7=BA=BF=E6=A0=B7=E5=BC=8F=20+=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=9C=80=E6=B1=82=E8=80=85=E8=BF=9B=E8=A1=8C=E4=B8=AD=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 将子任务进度从标签式改为时间线流程图样式,更直观展示任务流转进度 2. 修复需求部门用户在"进行中任务"页面看不到自己发起的任务的问题 3. 添加子任务状态辅助函数(状态类名、标签、状态文本) 4. 在MyTasksParams中添加keyword参数支持搜索 --- .../controller/TaskItemController.java | 15 +- .../com/designhub/mapper/TaskItemMapper.java | 37 ++- .../service/NotificationService.java | 17 ++ .../designhub/service/TaskItemService.java | 84 +++++- .../service/TaskProgressCheckService.java | 74 +++++- frontend/src/api/task.ts | 1 + frontend/src/views/TaskHall.vue | 251 ++++++++++++++++-- 7 files changed, 437 insertions(+), 42 deletions(-) diff --git a/backend/src/main/java/com/designhub/controller/TaskItemController.java b/backend/src/main/java/com/designhub/controller/TaskItemController.java index edc39eb..9d53df7 100644 --- a/backend/src/main/java/com/designhub/controller/TaskItemController.java +++ b/backend/src/main/java/com/designhub/controller/TaskItemController.java @@ -56,6 +56,7 @@ public class TaskItemController { public Result> getInProgressItems( @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page, @Parameter(description = "每页数量") @RequestParam(defaultValue = "10") Integer pageSize, + @Parameter(description = "搜索关键词") @RequestParam(required = false) String keyword, @RequestHeader("Authorization") String token) { String actualToken = token.replace("Bearer ", ""); @@ -75,17 +76,16 @@ public class TaskItemController { IPage items; if (isAdmin) { // 管理员:看到所有进行中的子任务 - items = taskItemService.getInProgressItems(page, pageSize, null, null); + items = taskItemService.getInProgressItems(page, pageSize, null, null, keyword); } else if (isDeptLeader) { // 部门负责人:看到自己负责的所有部门的子任务 - // 获取所有负责的部门ID List leadingDeptIds = leadingDepts.stream() .map(com.designhub.entity.Department::getId) .collect(java.util.stream.Collectors.toList()); - items = taskItemService.getInProgressItemsByDeptIds(page, pageSize, leadingDeptIds); + items = taskItemService.getInProgressItemsByDeptIds(page, pageSize, leadingDeptIds, keyword); } else { // 普通设计师:只看自己的子任务 - items = taskItemService.getInProgressItems(page, pageSize, userId, null); + items = taskItemService.getInProgressItems(page, pageSize, userId, null, keyword); } return Result.success(items); @@ -102,6 +102,7 @@ public class TaskItemController { public Result> getCompletedItems( @Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer page, @Parameter(description = "每页数量") @RequestParam(defaultValue = "10") Integer pageSize, + @Parameter(description = "搜索关键词") @RequestParam(required = false) String keyword, @RequestHeader("Authorization") String token) { String actualToken = token.replace("Bearer ", ""); @@ -121,16 +122,16 @@ public class TaskItemController { IPage items; if (isAdmin) { // 管理员:看到所有已完成的子任务 - items = taskItemService.getCompletedItems(page, pageSize, null, null); + items = taskItemService.getCompletedItems(page, pageSize, null, null, keyword); } else if (isDeptLeader) { // 部门负责人:看到自己负责的所有部门的子任务 List leadingDeptIds = leadingDepts.stream() .map(com.designhub.entity.Department::getId) .collect(java.util.stream.Collectors.toList()); - items = taskItemService.getCompletedItemsByDeptIds(page, pageSize, leadingDeptIds); + items = taskItemService.getCompletedItemsByDeptIds(page, pageSize, leadingDeptIds, keyword); } else { // 普通设计师:只看自己的子任务 - items = taskItemService.getCompletedItems(page, pageSize, userId, null); + items = taskItemService.getCompletedItems(page, pageSize, userId, null, keyword); } return Result.success(items); diff --git a/backend/src/main/java/com/designhub/mapper/TaskItemMapper.java b/backend/src/main/java/com/designhub/mapper/TaskItemMapper.java index 1808738..6a2a616 100644 --- a/backend/src/main/java/com/designhub/mapper/TaskItemMapper.java +++ b/backend/src/main/java/com/designhub/mapper/TaskItemMapper.java @@ -90,16 +90,24 @@ public interface TaskItemMapper extends BaseMapper { " AND t.deleted = 0 " + " AND ti.receiver_id = #{receiverId} " + " AND ti.receiver_dept_id = #{deptId} " + + " " + + " AND (t.title LIKE CONCAT('%', #{keyword}, '%') " + + " OR t.spu_info LIKE CONCAT('%', #{keyword}, '%') " + + " OR u.real_name LIKE CONCAT('%', #{keyword}, '%') " + + " OR ti.item_no LIKE CONCAT('%', #{keyword}, '%'))" + + " " + "ORDER BY ti.update_time DESC" + "") IPage selectInProgressItems(Page page, @Param("receiverId") Long receiverId, - @Param("deptId") Long deptId); + @Param("deptId") Long deptId, + @Param("keyword") String keyword); /** * 查询已完成的子任务列表 * @param receiverId 接单人ID,null表示不按接单人过滤 * @param deptId 部门ID,用于部门负责人查看本部门所有任务 + * @param keyword 搜索关键词 */ @Select("") IPage selectCompletedItems(Page page, @Param("receiverId") Long receiverId, - @Param("deptId") Long deptId); + @Param("deptId") Long deptId, + @Param("keyword") String keyword); /** * 查询进行中的子任务列表(按多个部门ID过滤,用于部门负责人) @@ -166,10 +181,17 @@ public interface TaskItemMapper extends BaseMapper { " " + " #{deptId}" + " " + + " " + + " AND (t.title LIKE CONCAT('%', #{keyword}, '%') " + + " OR t.spu_info LIKE CONCAT('%', #{keyword}, '%') " + + " OR u.real_name LIKE CONCAT('%', #{keyword}, '%') " + + " OR ti.item_no LIKE CONCAT('%', #{keyword}, '%'))" + + " " + "ORDER BY ti.update_time DESC" + "") IPage selectInProgressItemsByDeptIds(Page page, - @Param("deptIds") List deptIds); + @Param("deptIds") List deptIds, + @Param("keyword") String keyword); /** * 查询已完成的子任务列表(按多个部门ID过滤,用于部门负责人) @@ -203,10 +225,17 @@ public interface TaskItemMapper extends BaseMapper { " " + " #{deptId}" + " " + + " " + + " AND (t.title LIKE CONCAT('%', #{keyword}, '%') " + + " OR t.spu_info LIKE CONCAT('%', #{keyword}, '%') " + + " OR u.real_name LIKE CONCAT('%', #{keyword}, '%') " + + " OR ti.item_no LIKE CONCAT('%', #{keyword}, '%'))" + + " " + "ORDER BY ti.complete_time DESC" + "") IPage selectCompletedItemsByDeptIds(Page page, - @Param("deptIds") List deptIds); + @Param("deptIds") List deptIds, + @Param("keyword") String keyword); /** * 根据任务ID查询所有子任务 diff --git a/backend/src/main/java/com/designhub/service/NotificationService.java b/backend/src/main/java/com/designhub/service/NotificationService.java index 8e4ebd4..df47fc2 100644 --- a/backend/src/main/java/com/designhub/service/NotificationService.java +++ b/backend/src/main/java/com/designhub/service/NotificationService.java @@ -352,5 +352,22 @@ public class NotificationService { notification.setPushDingtalk(1); // 默认推送到钉钉 sendNotification(notification); } + + /** + * 发送通用通知(支持指定通知类型和关联类型) + */ + public void sendNotification(Long receiverId, String title, String content, + String notificationType, String relatedType, Long relatedId) { + Notification notification = new Notification(); + notification.setReceiverId(receiverId); + notification.setNotificationType(notificationType); + notification.setRelatedType(relatedType); + notification.setRelatedId(relatedId); + notification.setTitle(title); + notification.setContent(content); + notification.setPriority(1); // 普通 + notification.setPushDingtalk(1); // 默认推送到钉钉 + sendNotification(notification); + } } diff --git a/backend/src/main/java/com/designhub/service/TaskItemService.java b/backend/src/main/java/com/designhub/service/TaskItemService.java index 12a13b4..46554b0 100644 --- a/backend/src/main/java/com/designhub/service/TaskItemService.java +++ b/backend/src/main/java/com/designhub/service/TaskItemService.java @@ -50,52 +50,70 @@ public class TaskItemService { * 获取进行中的子任务列表 * @param receiverId 接单人ID,null表示不按接单人过滤 * @param deptId 部门ID,用于部门负责人查看本部门所有任务 + * @param keyword 搜索关键词 + */ + public IPage getInProgressItems(Integer page, Integer pageSize, Long receiverId, Long deptId, String keyword) { + Page pageParam = new Page<>(page, pageSize); + return taskItemMapper.selectInProgressItems(pageParam, receiverId, deptId, keyword); + } + + /** + * 获取进行中的子任务列表(向后兼容) */ public IPage getInProgressItems(Integer page, Integer pageSize, Long receiverId, Long deptId) { - Page pageParam = new Page<>(page, pageSize); - return taskItemMapper.selectInProgressItems(pageParam, receiverId, deptId); + return getInProgressItems(page, pageSize, receiverId, deptId, null); } /** * 获取进行中的子任务列表(向后兼容) */ public IPage getInProgressItems(Integer page, Integer pageSize, Long receiverId) { - return getInProgressItems(page, pageSize, receiverId, null); + return getInProgressItems(page, pageSize, receiverId, null, null); } /** * 获取进行中的子任务列表(部门负责人:按多个部门ID过滤) * @param deptIds 部门ID列表 + * @param keyword 搜索关键词 */ - public IPage getInProgressItemsByDeptIds(Integer page, Integer pageSize, List deptIds) { + public IPage getInProgressItemsByDeptIds(Integer page, Integer pageSize, List deptIds, String keyword) { Page pageParam = new Page<>(page, pageSize); - return taskItemMapper.selectInProgressItemsByDeptIds(pageParam, deptIds); + return taskItemMapper.selectInProgressItemsByDeptIds(pageParam, deptIds, keyword); } /** * 获取已完成的子任务列表 * @param receiverId 接单人ID,null表示不按接单人过滤 * @param deptId 部门ID,用于部门负责人查看本部门所有任务 + * @param keyword 搜索关键词 + */ + public IPage getCompletedItems(Integer page, Integer pageSize, Long receiverId, Long deptId, String keyword) { + Page pageParam = new Page<>(page, pageSize); + return taskItemMapper.selectCompletedItems(pageParam, receiverId, deptId, keyword); + } + + /** + * 获取已完成的子任务列表(向后兼容) */ public IPage getCompletedItems(Integer page, Integer pageSize, Long receiverId, Long deptId) { - Page pageParam = new Page<>(page, pageSize); - return taskItemMapper.selectCompletedItems(pageParam, receiverId, deptId); + return getCompletedItems(page, pageSize, receiverId, deptId, null); } /** * 获取已完成的子任务列表(向后兼容) */ public IPage getCompletedItems(Integer page, Integer pageSize, Long receiverId) { - return getCompletedItems(page, pageSize, receiverId, null); + return getCompletedItems(page, pageSize, receiverId, null, null); } /** * 获取已完成的子任务列表(部门负责人:按多个部门ID过滤) * @param deptIds 部门ID列表 + * @param keyword 搜索关键词 */ - public IPage getCompletedItemsByDeptIds(Integer page, Integer pageSize, List deptIds) { + public IPage getCompletedItemsByDeptIds(Integer page, Integer pageSize, List deptIds, String keyword) { Page pageParam = new Page<>(page, pageSize); - return taskItemMapper.selectCompletedItemsByDeptIds(pageParam, deptIds); + return taskItemMapper.selectCompletedItemsByDeptIds(pageParam, deptIds, keyword); } /** @@ -290,12 +308,58 @@ public class TaskItemService { throw new BusinessException(ResultCode.PARAM_ERROR, "进度值必须在0-100之间"); } + // 记录更新前的进度 + Integer oldProgress = item.getProgress() != null ? item.getProgress() : 0; + // 更新进度 item.setProgress(progress); item.setUpdateTime(LocalDateTime.now()); taskItemMapper.updateById(item); log.info("子任务 {} 进度已更新为 {}%", itemId, progress); + + // 获取主任务信息,通知需求者(发布者) + try { + Task mainTask = taskMapper.selectById(item.getTaskId()); + if (mainTask != null && mainTask.getPublisherId() != null) { + // 获取设计师姓名 + String designerName = item.getReceiverName(); + if (designerName == null && item.getReceiverId() != null) { + com.designhub.entity.User designer = userService.getUserById(item.getReceiverId()); + if (designer != null) { + designerName = designer.getRealName() != null ? designer.getRealName() : designer.getUsername(); + } + } + + // 构建通知内容 + StringBuilder content = new StringBuilder(); + content.append("### 任务进度更新\n\n"); + content.append("**任务名称**:").append(mainTask.getTitle()).append(" \n"); + content.append("**子任务编号**:").append(item.getItemNo()).append(" \n"); + content.append("**设计师**:").append(designerName != null ? designerName : "未知").append(" \n"); + content.append("**进度变化**:").append(oldProgress).append("% → ").append(progress).append("% \n"); + + if (progress == 100) { + content.append("\n🎉 子任务进度已达100%,请关注完结状态。"); + } + + // 发送通知给需求者(发布者) + notificationService.sendNotification( + mainTask.getPublisherId(), + "任务进度已更新", + content.toString(), + "PROGRESS_UPDATE", + "task_item", + itemId + ); + + log.info("已通知需求者 {} 任务进度更新: 子任务={}, 进度={}%", + mainTask.getPublisherId(), item.getItemNo(), progress); + } + } catch (Exception e) { + // 通知失败不影响主流程 + log.error("发送进度更新通知给需求者失败: itemId={}", itemId, e); + } } /** diff --git a/backend/src/main/java/com/designhub/service/TaskProgressCheckService.java b/backend/src/main/java/com/designhub/service/TaskProgressCheckService.java index d05762b..96e6fe3 100644 --- a/backend/src/main/java/com/designhub/service/TaskProgressCheckService.java +++ b/backend/src/main/java/com/designhub/service/TaskProgressCheckService.java @@ -2,8 +2,11 @@ package com.designhub.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.designhub.entity.Task; +import com.designhub.entity.TaskItem; import com.designhub.entity.TaskProgressUpdateLog; +import com.designhub.entity.User; import com.designhub.mapper.TaskMapper; +import com.designhub.mapper.TaskItemMapper; import com.designhub.mapper.TaskProgressUpdateLogMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -18,7 +21,7 @@ import java.util.List; /** * 任务进度检查服务 - * 每日18:00检查任务进度是否更新,并发送通知 + * 每日17:30检查任务进度是否更新,并发送钉钉通知提醒设计师 */ @Service @RequiredArgsConstructor @@ -26,12 +29,79 @@ import java.util.List; public class TaskProgressCheckService { private final TaskMapper taskMapper; + private final TaskItemMapper taskItemMapper; private final TaskProgressUpdateLogMapper progressUpdateLogMapper; private final NotificationService notificationService; private final UserService userService; + private final DingTalkService dingTalkService; /** - * 每日18:00执行进度更新检查 + * 每日17:30提醒设计师更新子任务进度 + * cron: 秒 分 时 日 月 周 + */ + @Scheduled(cron = "0 30 17 * * ?") + @Transactional + public void remindDesignersToUpdateProgress() { + log.info("=== 开始执行每日进度更新提醒任务(17:30) ==="); + + // 查询所有进行中的子任务 + List inProgressItems = taskItemMapper.selectList( + new LambdaQueryWrapper() + .eq(TaskItem::getStatus, "in_progress") + .isNotNull(TaskItem::getReceiverId) + ); + + log.info("当前进行中的子任务数量: {}", inProgressItems.size()); + + int reminderSentCount = 0; + + for (TaskItem item : inProgressItems) { + try { + if (item.getReceiverId() == null) continue; + + // 获取主任务信息 + Task mainTask = taskMapper.selectById(item.getTaskId()); + if (mainTask == null) continue; + + // 获取设计师信息 + User designer = userService.getUserById(item.getReceiverId()); + if (designer == null) continue; + + String designerName = designer.getRealName() != null ? designer.getRealName() : designer.getUsername(); + + // 构建提醒消息内容 + StringBuilder content = new StringBuilder(); + content.append("### 进度更新提醒\n\n"); + content.append("**设计师**:").append(designerName).append(" \n"); + content.append("**任务名称**:").append(mainTask.getTitle()).append(" \n"); + content.append("**子任务编号**:").append(item.getItemNo()).append(" \n"); + content.append("**当前进度**:").append(item.getProgress() != null ? item.getProgress() : 0).append("% \n\n"); + content.append("请及时更新您的任务进度,以便项目负责人了解任务进展情况。"); + + // 发送钉钉个人通知 + notificationService.sendNotification( + item.getReceiverId(), + "请更新任务进度", + content.toString(), + "PROGRESS_REMINDER", + "task_item", + item.getId() + ); + + reminderSentCount++; + log.debug("已向设计师 {} 发送进度更新提醒,子任务: {}", designerName, item.getItemNo()); + + } catch (Exception e) { + log.error("发送进度更新提醒失败: itemId={}, receiverId={}", + item.getId(), item.getReceiverId(), e); + } + } + + log.info("=== 每日进度更新提醒完成 === 发送提醒数: {}", reminderSentCount); + } + + /** + * 每日18:00执行进度更新检查(记录未更新的任务) * cron: 秒 分 时 日 月 周 */ @Scheduled(cron = "0 0 18 * * ?") diff --git a/frontend/src/api/task.ts b/frontend/src/api/task.ts index 666f250..3afbb5e 100644 --- a/frontend/src/api/task.ts +++ b/frontend/src/api/task.ts @@ -170,6 +170,7 @@ export interface MyTasksParams { status?: string page?: number pageSize?: number + keyword?: string } /** diff --git a/frontend/src/views/TaskHall.vue b/frontend/src/views/TaskHall.vue index 3cc5361..0ae6a5d 100644 --- a/frontend/src/views/TaskHall.vue +++ b/frontend/src/views/TaskHall.vue @@ -1584,18 +1584,30 @@ 处理人: {{ task.receiverName || '待接单' }} - -
- - - - - 子任务: - 完成{{ task.subTaskCompleted || 0 }} - 进行{{ task.subTaskInProgress || 0 }} - 待接{{ task.subTaskPending || 0 }} - /共{{ task.subTaskTotal }} - + +
+
+
+
+
{{ getSubTaskLabel(subTask, index) }}
+
{{ getSubTaskStatusText(subTask.status) }}
+
+ +
+
+
+{{ (task.subTasks || []).length - 4 }}
+
+ +
+
+
完结
+
+
@@ -2698,6 +2710,39 @@ const formatEstimatedTime = (hours: number | undefined) => { const days = Math.floor(hours / WORK_HOURS_PER_DAY) const remainingHours = Math.round((hours % WORK_HOURS_PER_DAY) * 100) / 100 +// 子任务时间线辅助函数 +const getSubTaskStatusClass = (status: string | undefined) => { + switch (status) { + case 'completed': return 'completed' + case 'in_progress': return 'in-progress' + default: return 'pending' + } +} + +const getSubTaskLabel = (subTask: any, index: number) => { + // 如果有子任务编号,显示子任务编号后两位;否则显示序号 + if (subTask.itemNo) { + const parts = subTask.itemNo.split('-') + return parts.length > 1 ? `子${parts[parts.length - 1]}` : `子${index + 1}` + } + return `子${index + 1}` +} + +const getSubTaskStatusText = (status: string | undefined) => { + switch (status) { + case 'completed': return '已完成' + case 'in_progress': return '进行中' + default: return '待接单' + } +} + +// 判断任务是否有子任务在进行中 +const hasInProgressSubTask = (task: any) => { + if (task.isPipeline !== 1) return false + return (task.subTaskInProgress || 0) > 0 +} + + if (days === 0) { return Number.isInteger(remainingHours) ? `${remainingHours}小时` : `${remainingHours.toFixed(1)}小时` } else if (remainingHours === 0) { @@ -3706,15 +3751,30 @@ const loadInProgressTasks = async () => { totalCount += itemResponse.data.total || 0 } } else if (userDeptType === 'demand') { - // 需求部门用户:只看母任务卡片(自己发起的任务) - const taskResponse = await taskApi.getTasks(params) - if (taskResponse.data && taskResponse.data.records) { - const tasks = taskResponse.data.records.map((task: any) => ({ + // 需求部门用户:看自己发起的任务中有子任务在进行中的 + // 调用 getMyTasks 获取自己发起的所有任务(包含子任务进度信息) + 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) => { + // 流水线任务:检查是否有子任务在进行中 + if (task.isPipeline === 1) { + return (task.subTaskInProgress || 0) > 0 || task.status === 'in_progress' + } + // 普通任务:状态为进行中 + return task.status === 'in_progress' + }) + allTasks = tasksWithInProgress.map((task: any) => ({ ...task, isSubTask: false })) - allTasks = tasks - totalCount = taskResponse.data.total || 0 + // 由于是前端过滤,total 用过滤后的数量 + totalCount = allTasks.length } } else { // 接单部门用户(设计师):只看子任务卡片 @@ -5389,7 +5449,7 @@ onUnmounted(() => { } } - // 子任务进度样式 + // 子任务进度样式(保留旧样式备用) &.subtask-progress { grid-column: 1 / -1; @@ -5441,6 +5501,159 @@ onUnmounted(() => { } } +// 子任务时间线样式 +.subtask-timeline-wrapper { + width: 100%; + margin: 12px 0; + padding: 12px; + background: rgba(0, 0, 0, 0.2); + border-radius: 8px; +} + +.subtask-timeline { + display: flex; + align-items: flex-start; + position: relative; + + // 连接线 + &::before { + content: ''; + position: absolute; + top: 8px; + left: 8px; + right: 8px; + height: 2px; + background: linear-gradient(90deg, #303030 0%, #303030 100%); + z-index: 0; + } + + .timeline-node { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + position: relative; + z-index: 1; + min-width: 50px; + + .node-dot { + width: 16px; + height: 16px; + border-radius: 50%; + background: #303030; + border: 2px solid #444; + margin-bottom: 6px; + transition: all 0.3s ease; + } + + .node-label { + font-size: 11px; + color: #888; + text-align: center; + white-space: nowrap; + margin-bottom: 2px; + } + + .node-status { + font-size: 10px; + color: #666; + text-align: center; + white-space: nowrap; + } + + // 待接单状态 + &.pending { + .node-dot { + background: #303030; + border-color: #faad14; + } + .node-label { + color: #faad14; + } + .node-status { + color: #faad14; + opacity: 0.8; + } + } + + // 进行中状态 + &.in-progress { + .node-dot { + background: #1890ff; + border-color: #1890ff; + box-shadow: 0 0 8px rgba(24, 144, 255, 0.5); + animation: pulse 1.5s ease-in-out infinite; + } + .node-label { + color: #1890ff; + font-weight: 600; + } + .node-status { + color: #1890ff; + } + } + + // 已完成状态 + &.completed { + .node-dot { + background: #52c41a; + border-color: #52c41a; + } + .node-label { + color: #52c41a; + } + .node-status { + color: #52c41a; + } + } + + // 更多节点 + &.more { + flex: 0.5; + .node-dot { + background: #444; + border-color: #666; + } + .node-label { + color: #888; + font-size: 10px; + } + } + + // 完结节点 + &.final { + .node-dot { + background: #303030; + border-color: #666; + } + .node-label { + color: #888; + font-weight: 500; + } + + &.completed { + .node-dot { + background: #52c41a; + border-color: #52c41a; + } + .node-label { + color: #52c41a; + } + } + } + } +} + +// 脉冲动画 +@keyframes pulse { + 0%, 100% { + box-shadow: 0 0 4px rgba(24, 144, 255, 0.4); + } + 50% { + box-shadow: 0 0 12px rgba(24, 144, 255, 0.8); + } +} + // 任务操作 .task-actions { display: flex;