feat: 优化子任务进度显示为时间线样式 + 修复需求者进行中任务显示
1. 将子任务进度从标签式改为时间线流程图样式,更直观展示任务流转进度 2. 修复需求部门用户在"进行中任务"页面看不到自己发起的任务的问题 3. 添加子任务状态辅助函数(状态类名、标签、状态文本) 4. 在MyTasksParams中添加keyword参数支持搜索
This commit is contained in:
parent
552c9900f0
commit
9b849ccb40
@ -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);
|
||||
|
||||
@ -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查询所有子任务
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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 * * ?")
|
||||
|
||||
@ -170,6 +170,7 @@ export interface MyTasksParams {
|
||||
status?: string
|
||||
page?: number
|
||||
pageSize?: number
|
||||
keyword?: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user