feat: 优化子任务进度显示为时间线样式 + 修复需求者进行中任务显示

1. 将子任务进度从标签式改为时间线流程图样式,更直观展示任务流转进度
2. 修复需求部门用户在"进行中任务"页面看不到自己发起的任务的问题
3. 添加子任务状态辅助函数(状态类名、标签、状态文本)
4. 在MyTasksParams中添加keyword参数支持搜索
This commit is contained in:
Your Name 2026-01-07 15:38:59 +08:00
parent 552c9900f0
commit 9b849ccb40
7 changed files with 437 additions and 42 deletions

View File

@ -56,6 +56,7 @@ public class TaskItemController {
public Result<IPage<TaskItem>> 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<TaskItem> items;
if (isAdmin) {
// 管理员:看到所有进行中的子任务
items = taskItemService.getInProgressItems(page, pageSize, null, null);
items = taskItemService.getInProgressItems(page, pageSize, null, null, keyword);
} else if (isDeptLeader) {
// 部门负责人:看到自己负责的所有部门的子任务
// 获取所有负责的部门ID
List<Long> 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<IPage<TaskItem>> 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<TaskItem> items;
if (isAdmin) {
// 管理员:看到所有已完成的子任务
items = taskItemService.getCompletedItems(page, pageSize, null, null);
items = taskItemService.getCompletedItems(page, pageSize, null, null, keyword);
} else if (isDeptLeader) {
// 部门负责人:看到自己负责的所有部门的子任务
List<Long> 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);

View File

@ -90,16 +90,24 @@ public interface TaskItemMapper extends BaseMapper<TaskItem> {
" AND t.deleted = 0 " +
" <if test='receiverId != null'> AND ti.receiver_id = #{receiverId} </if>" +
" <if test='deptId != null'> AND ti.receiver_dept_id = #{deptId} </if>" +
" <if test='keyword != null and keyword != \"\"'> " +
" 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}, '%'))" +
" </if>" +
"ORDER BY ti.update_time DESC" +
"</script>")
IPage<TaskItem> selectInProgressItems(Page<TaskItem> 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("<script>" +
"SELECT ti.*, " +
@ -128,11 +136,18 @@ public interface TaskItemMapper extends BaseMapper<TaskItem> {
" AND t.deleted = 0 " +
" <if test='receiverId != null'> AND ti.receiver_id = #{receiverId} </if>" +
" <if test='deptId != null'> AND ti.receiver_dept_id = #{deptId} </if>" +
" <if test='keyword != null and keyword != \"\"'> " +
" 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}, '%'))" +
" </if>" +
"ORDER BY ti.complete_time DESC" +
"</script>")
IPage<TaskItem> selectCompletedItems(Page<TaskItem> 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<TaskItem> {
" <foreach collection='deptIds' item='deptId' open='(' separator=',' close=')'>" +
" #{deptId}" +
" </foreach> " +
" <if test='keyword != null and keyword != \"\"'> " +
" 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}, '%'))" +
" </if>" +
"ORDER BY ti.update_time DESC" +
"</script>")
IPage<TaskItem> selectInProgressItemsByDeptIds(Page<TaskItem> page,
@Param("deptIds") List<Long> deptIds);
@Param("deptIds") List<Long> deptIds,
@Param("keyword") String keyword);
/**
* 查询已完成的子任务列表(按多个部门ID过滤,用于部门负责人)
@ -203,10 +225,17 @@ public interface TaskItemMapper extends BaseMapper<TaskItem> {
" <foreach collection='deptIds' item='deptId' open='(' separator=',' close=')'>" +
" #{deptId}" +
" </foreach> " +
" <if test='keyword != null and keyword != \"\"'> " +
" 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}, '%'))" +
" </if>" +
"ORDER BY ti.complete_time DESC" +
"</script>")
IPage<TaskItem> selectCompletedItemsByDeptIds(Page<TaskItem> page,
@Param("deptIds") List<Long> deptIds);
@Param("deptIds") List<Long> deptIds,
@Param("keyword") String keyword);
/**
* 根据任务ID查询所有子任务

View File

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

View File

@ -50,52 +50,70 @@ public class TaskItemService {
* 获取进行中的子任务列表
* @param receiverId 接单人ID,null表示不按接单人过滤
* @param deptId 部门ID,用于部门负责人查看本部门所有任务
* @param keyword 搜索关键词
*/
public IPage<TaskItem> getInProgressItems(Integer page, Integer pageSize, Long receiverId, Long deptId, String keyword) {
Page<TaskItem> pageParam = new Page<>(page, pageSize);
return taskItemMapper.selectInProgressItems(pageParam, receiverId, deptId, keyword);
}
/**
* 获取进行中的子任务列表(向后兼容)
*/
public IPage<TaskItem> getInProgressItems(Integer page, Integer pageSize, Long receiverId, Long deptId) {
Page<TaskItem> pageParam = new Page<>(page, pageSize);
return taskItemMapper.selectInProgressItems(pageParam, receiverId, deptId);
return getInProgressItems(page, pageSize, receiverId, deptId, null);
}
/**
* 获取进行中的子任务列表(向后兼容)
*/
public IPage<TaskItem> 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<TaskItem> getInProgressItemsByDeptIds(Integer page, Integer pageSize, List<Long> deptIds) {
public IPage<TaskItem> getInProgressItemsByDeptIds(Integer page, Integer pageSize, List<Long> deptIds, String keyword) {
Page<TaskItem> 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<TaskItem> getCompletedItems(Integer page, Integer pageSize, Long receiverId, Long deptId, String keyword) {
Page<TaskItem> pageParam = new Page<>(page, pageSize);
return taskItemMapper.selectCompletedItems(pageParam, receiverId, deptId, keyword);
}
/**
* 获取已完成的子任务列表(向后兼容)
*/
public IPage<TaskItem> getCompletedItems(Integer page, Integer pageSize, Long receiverId, Long deptId) {
Page<TaskItem> pageParam = new Page<>(page, pageSize);
return taskItemMapper.selectCompletedItems(pageParam, receiverId, deptId);
return getCompletedItems(page, pageSize, receiverId, deptId, null);
}
/**
* 获取已完成的子任务列表(向后兼容)
*/
public IPage<TaskItem> 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<TaskItem> getCompletedItemsByDeptIds(Integer page, Integer pageSize, List<Long> deptIds) {
public IPage<TaskItem> getCompletedItemsByDeptIds(Integer page, Integer pageSize, List<Long> deptIds, String keyword) {
Page<TaskItem> 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);
}
}
/**

View File

@ -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<TaskItem> inProgressItems = taskItemMapper.selectList(
new LambdaQueryWrapper<TaskItem>()
.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 * * ?")

View File

@ -170,6 +170,7 @@ export interface MyTasksParams {
status?: string
page?: number
pageSize?: number
keyword?: string
}
/**

View File

@ -1584,18 +1584,30 @@
</span>
<span>处理人: {{ task.receiverName || '待接单' }}</span>
</div>
<!-- 子任务进度(流水线任务) -->
<div v-if="task.isPipeline === 1 && task.subTaskTotal" class="detail-item subtask-progress">
<span class="icon">
<Icon name="layers" size="sm" color="#31f08b" />
</span>
<span class="subtask-stats">
子任务:
<span class="stat completed">完成{{ task.subTaskCompleted || 0 }}</span>
<span class="stat in-progress">进行{{ task.subTaskInProgress || 0 }}</span>
<span class="stat pending">待接{{ task.subTaskPending || 0 }}</span>
<span class="stat-label">/共{{ task.subTaskTotal }}</span>
</span>
<!-- 子任务进度时间线(流水线任务) -->
<div v-if="task.isPipeline === 1 && task.subTaskTotal" class="subtask-timeline-wrapper">
<div class="subtask-timeline">
<div
v-for="(subTask, index) in (task.subTasks || []).slice(0, 4)"
:key="subTask.id || index"
class="timeline-node"
:class="getSubTaskStatusClass(subTask.status)"
>
<div class="node-dot"></div>
<div class="node-label">{{ getSubTaskLabel(subTask, index) }}</div>
<div class="node-status">{{ getSubTaskStatusText(subTask.status) }}</div>
</div>
<!-- 显示更多子任务提示 -->
<div v-if="(task.subTasks || []).length > 4" class="timeline-node more">
<div class="node-dot"></div>
<div class="node-label">+{{ (task.subTasks || []).length - 4 }}</div>
</div>
<!-- 完结节点 -->
<div class="timeline-node final" :class="{ completed: task.status === 'completed' }">
<div class="node-dot"></div>
<div class="node-label">完结</div>
</div>
</div>
</div>
<div v-if="task.brandName" class="detail-item">
<span class="icon">
@ -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;