feat: 添加任务节点查询功能 - 支持通过任务编号或标题查询任务流程状态和当前节点
This commit is contained in:
parent
e3cb59539f
commit
c575d6fedb
@ -618,5 +618,16 @@ public class TaskController {
|
||||
taskApprovalService.completeTaskDetail(id, userId, taskItems);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
// ==================== 任务流程查询接口 ====================
|
||||
|
||||
@Operation(summary = "查询任务流程状态", description = "根据任务编号或关键字查询任务当前所在流程节点")
|
||||
@GetMapping("/flow-status")
|
||||
public Result<com.designhub.vo.TaskFlowStatusVO> getTaskFlowStatus(
|
||||
@Parameter(description = "任务编号") @RequestParam(required = false) String taskNo,
|
||||
@Parameter(description = "任务标题关键字") @RequestParam(required = false) String keyword) {
|
||||
log.info("查询任务流程状态: taskNo={}, keyword={}", taskNo, keyword);
|
||||
return Result.success(taskService.getTaskFlowStatus(taskNo, keyword));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1082,6 +1082,19 @@ public class TaskApprovalService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务的审批日志列表
|
||||
* @param taskId 任务ID
|
||||
* @return 审批日志列表
|
||||
*/
|
||||
public List<TaskApprovalLog> getApprovalLogs(Long taskId) {
|
||||
return approvalLogMapper.selectList(
|
||||
new LambdaQueryWrapper<TaskApprovalLog>()
|
||||
.eq(TaskApprovalLog::getTaskId, taskId)
|
||||
.orderByAsc(TaskApprovalLog::getCreateTime)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录审核日志
|
||||
*/
|
||||
|
||||
@ -4466,5 +4466,483 @@ public class TaskService {
|
||||
taskMapper.updateById(task);
|
||||
log.info("任务 {} 子任务创建完成,共 {} 个子任务组,总积分 {}", taskId, groupedItems.size(), totalScore);
|
||||
}
|
||||
|
||||
// ==================== 任务流程状态查询 ====================
|
||||
|
||||
/**
|
||||
* 获取任务流程状态(用于任务节点查询功能)
|
||||
* @param taskNo 任务编号
|
||||
* @param keyword 任务标题关键字
|
||||
* @return 任务流程状态信息
|
||||
*/
|
||||
public com.designhub.vo.TaskFlowStatusVO getTaskFlowStatus(String taskNo, String keyword) {
|
||||
// 1. 查询任务
|
||||
Task task = null;
|
||||
if (taskNo != null && !taskNo.trim().isEmpty()) {
|
||||
task = taskMapper.selectOne(
|
||||
new LambdaQueryWrapper<Task>()
|
||||
.eq(Task::getTaskNo, taskNo.trim())
|
||||
.eq(Task::getDeleted, 0)
|
||||
);
|
||||
} else if (keyword != null && !keyword.trim().isEmpty()) {
|
||||
task = taskMapper.selectOne(
|
||||
new LambdaQueryWrapper<Task>()
|
||||
.like(Task::getTitle, keyword.trim())
|
||||
.eq(Task::getDeleted, 0)
|
||||
.orderByDesc(Task::getId)
|
||||
.last("LIMIT 1")
|
||||
);
|
||||
}
|
||||
|
||||
if (task == null) {
|
||||
throw new BusinessException(ResultCode.NOT_FOUND, "任务不存在");
|
||||
}
|
||||
|
||||
// 2. 构建返回结果
|
||||
com.designhub.vo.TaskFlowStatusVO vo = new com.designhub.vo.TaskFlowStatusVO();
|
||||
vo.setTask(buildTaskBasicInfo(task));
|
||||
vo.setCurrentNode(buildCurrentNodeInfo(task));
|
||||
vo.setFlowTimeline(buildFlowTimeline(task));
|
||||
vo.setRelatedUsers(buildRelatedUsers(task));
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建任务基本信息
|
||||
*/
|
||||
private com.designhub.vo.TaskFlowStatusVO.TaskBasicInfo buildTaskBasicInfo(Task task) {
|
||||
com.designhub.vo.TaskFlowStatusVO.TaskBasicInfo info = new com.designhub.vo.TaskFlowStatusVO.TaskBasicInfo();
|
||||
info.setId(task.getId());
|
||||
info.setTaskNo(task.getTaskNo());
|
||||
info.setTitle(task.getTitle());
|
||||
info.setScore(task.getScore());
|
||||
info.setIsPrimaryTask(task.getIsPrimaryTask() != null && task.getIsPrimaryTask() == 1);
|
||||
info.setIsPipeline(task.getIsPipeline() != null && task.getIsPipeline() == 1);
|
||||
info.setStatus(task.getStatus());
|
||||
info.setStatusName(getStatusName(task.getStatus()));
|
||||
info.setCreateTime(task.getCreateTime());
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建当前节点信息
|
||||
*/
|
||||
private com.designhub.vo.TaskFlowStatusVO.CurrentNodeInfo buildCurrentNodeInfo(Task task) {
|
||||
com.designhub.vo.TaskFlowStatusVO.CurrentNodeInfo info = new com.designhub.vo.TaskFlowStatusVO.CurrentNodeInfo();
|
||||
|
||||
String approvalStatus = task.getApprovalStatus();
|
||||
String status = task.getStatus();
|
||||
boolean isPrimaryTask = task.getIsPrimaryTask() != null && task.getIsPrimaryTask() == 1;
|
||||
|
||||
info.setStatus(approvalStatus != null ? approvalStatus : status);
|
||||
info.setStatusName(getApprovalStatusName(approvalStatus, status, isPrimaryTask));
|
||||
info.setDescription(getApprovalStatusDescription(approvalStatus, status, isPrimaryTask));
|
||||
|
||||
// 设置等待操作人
|
||||
if (task.getCurrentApproverId() != null) {
|
||||
com.designhub.vo.TaskFlowStatusVO.WaitingUser waitingUser = new com.designhub.vo.TaskFlowStatusVO.WaitingUser();
|
||||
waitingUser.setUserId(task.getCurrentApproverId());
|
||||
|
||||
User approver = userMapper.selectById(task.getCurrentApproverId());
|
||||
if (approver != null) {
|
||||
waitingUser.setUserName(approver.getRealName());
|
||||
waitingUser.setDeptName(approver.getDeptName());
|
||||
}
|
||||
|
||||
String role = task.getCurrentApproverRole();
|
||||
waitingUser.setRole(role);
|
||||
waitingUser.setRoleName(getApproverRoleName(role));
|
||||
|
||||
info.setWaitingFor(waitingUser);
|
||||
} else if ("pending".equals(status) && task.getReceiverId() == null) {
|
||||
// 待接单状态
|
||||
com.designhub.vo.TaskFlowStatusVO.WaitingUser waitingUser = new com.designhub.vo.TaskFlowStatusVO.WaitingUser();
|
||||
waitingUser.setRoleName("等待设计师接单");
|
||||
info.setWaitingFor(waitingUser);
|
||||
} else if ("in_progress".equals(status) && task.getReceiverId() != null) {
|
||||
// 进行中状态
|
||||
com.designhub.vo.TaskFlowStatusVO.WaitingUser waitingUser = new com.designhub.vo.TaskFlowStatusVO.WaitingUser();
|
||||
waitingUser.setUserId(task.getReceiverId());
|
||||
|
||||
User receiver = userMapper.selectById(task.getReceiverId());
|
||||
if (receiver != null) {
|
||||
waitingUser.setUserName(receiver.getRealName());
|
||||
waitingUser.setDeptName(receiver.getDeptName());
|
||||
}
|
||||
waitingUser.setRoleName("设计师执行中");
|
||||
info.setWaitingFor(waitingUser);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建流程时间线
|
||||
*/
|
||||
private java.util.List<com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode> buildFlowTimeline(Task task) {
|
||||
java.util.List<com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode> timeline = new java.util.ArrayList<>();
|
||||
boolean isPrimaryTask = task.getIsPrimaryTask() != null && task.getIsPrimaryTask() == 1;
|
||||
String approvalStatus = task.getApprovalStatus();
|
||||
String status = task.getStatus();
|
||||
|
||||
// 查询审批日志
|
||||
java.util.List<TaskApprovalLog> logs = taskApprovalService.getApprovalLogs(task.getId());
|
||||
java.util.Map<String, TaskApprovalLog> logMap = new java.util.HashMap<>();
|
||||
for (TaskApprovalLog log : logs) {
|
||||
logMap.put(log.getAction(), log);
|
||||
}
|
||||
|
||||
if (isPrimaryTask) {
|
||||
// 一级需求任务流程
|
||||
|
||||
// 1. 一级需求发起
|
||||
com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode node1 = new com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode();
|
||||
node1.setNodeName("一级需求发起");
|
||||
node1.setStatus("completed");
|
||||
node1.setTime(task.getCreateTime());
|
||||
if (task.getPrimaryPublisherId() != null) {
|
||||
User publisher = userMapper.selectById(task.getPrimaryPublisherId());
|
||||
if (publisher != null) {
|
||||
node1.setOperatorName(publisher.getRealName());
|
||||
node1.setOperatorDept(publisher.getDeptName());
|
||||
}
|
||||
}
|
||||
timeline.add(node1);
|
||||
|
||||
// 2. 一级需求审批
|
||||
com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode node2 = new com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode();
|
||||
node2.setNodeName("一级需求审批");
|
||||
TaskApprovalLog primaryLog = logMap.get("approve_primary");
|
||||
if (primaryLog != null) {
|
||||
node2.setStatus("completed");
|
||||
node2.setOperatorName(primaryLog.getApproverName());
|
||||
node2.setTime(primaryLog.getCreateTime());
|
||||
node2.setComment(primaryLog.getComment());
|
||||
// 获取审批人部门
|
||||
User approver = userMapper.selectById(primaryLog.getApproverId());
|
||||
if (approver != null) {
|
||||
node2.setOperatorDept(approver.getDeptName());
|
||||
}
|
||||
} else if ("pending_primary_approval".equals(approvalStatus)) {
|
||||
node2.setStatus("pending");
|
||||
if (task.getCurrentApproverId() != null) {
|
||||
User approver = userMapper.selectById(task.getCurrentApproverId());
|
||||
if (approver != null) {
|
||||
node2.setOperatorName(approver.getRealName());
|
||||
node2.setOperatorDept(approver.getDeptName());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node2.setStatus("waiting");
|
||||
}
|
||||
timeline.add(node2);
|
||||
|
||||
// 3. 明细完善
|
||||
com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode node3 = new com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode();
|
||||
node3.setNodeName("明细完善");
|
||||
TaskApprovalLog detailLog = logMap.get("complete_detail");
|
||||
if (detailLog != null) {
|
||||
node3.setStatus("completed");
|
||||
node3.setOperatorName(detailLog.getApproverName());
|
||||
node3.setTime(detailLog.getCreateTime());
|
||||
node3.setComment(detailLog.getComment());
|
||||
User completer = userMapper.selectById(detailLog.getApproverId());
|
||||
if (completer != null) {
|
||||
node3.setOperatorDept(completer.getDeptName());
|
||||
}
|
||||
} else if ("pending_detail_completion".equals(approvalStatus)) {
|
||||
node3.setStatus("pending");
|
||||
if (task.getDetailCompleterId() != null) {
|
||||
User completer = userMapper.selectById(task.getDetailCompleterId());
|
||||
if (completer != null) {
|
||||
node3.setOperatorName(completer.getRealName());
|
||||
node3.setOperatorDept(completer.getDeptName());
|
||||
}
|
||||
}
|
||||
} else if (primaryLog != null) {
|
||||
node3.setStatus("waiting");
|
||||
} else {
|
||||
node3.setStatus("waiting");
|
||||
}
|
||||
timeline.add(node3);
|
||||
|
||||
// 4. 接单部门审批(如果是流水线任务)
|
||||
boolean isPipeline = task.getIsPipeline() != null && task.getIsPipeline() == 1;
|
||||
if (isPipeline || "pending_receiving_approval".equals(approvalStatus) ||
|
||||
logMap.containsKey("approve_receiving") || logMap.containsKey("reject_receiving")) {
|
||||
com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode node4 = new com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode();
|
||||
node4.setNodeName("接单部门审批");
|
||||
TaskApprovalLog receivingLog = logMap.get("approve_receiving");
|
||||
if (receivingLog != null) {
|
||||
node4.setStatus("completed");
|
||||
node4.setOperatorName(receivingLog.getApproverName());
|
||||
node4.setTime(receivingLog.getCreateTime());
|
||||
node4.setComment(receivingLog.getComment());
|
||||
User approver = userMapper.selectById(receivingLog.getApproverId());
|
||||
if (approver != null) {
|
||||
node4.setOperatorDept(approver.getDeptName());
|
||||
}
|
||||
} else if ("pending_receiving_approval".equals(approvalStatus)) {
|
||||
node4.setStatus("pending");
|
||||
if (task.getCurrentApproverId() != null) {
|
||||
User approver = userMapper.selectById(task.getCurrentApproverId());
|
||||
if (approver != null) {
|
||||
node4.setOperatorName(approver.getRealName());
|
||||
node4.setOperatorDept(approver.getDeptName());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node4.setStatus("waiting");
|
||||
}
|
||||
timeline.add(node4);
|
||||
}
|
||||
|
||||
// 5. 任务发布/执行
|
||||
com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode node5 = new com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode();
|
||||
node5.setNodeName("任务执行");
|
||||
if ("approved".equals(approvalStatus) && ("pending".equals(status) || "in_progress".equals(status) || "completed".equals(status))) {
|
||||
if ("completed".equals(status)) {
|
||||
node5.setStatus("completed");
|
||||
node5.setTime(task.getCompleteTime());
|
||||
} else if ("in_progress".equals(status)) {
|
||||
node5.setStatus("pending");
|
||||
} else {
|
||||
node5.setStatus("pending");
|
||||
}
|
||||
if (task.getReceiverId() != null) {
|
||||
User receiver = userMapper.selectById(task.getReceiverId());
|
||||
if (receiver != null) {
|
||||
node5.setOperatorName(receiver.getRealName());
|
||||
node5.setOperatorDept(receiver.getDeptName());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node5.setStatus("waiting");
|
||||
}
|
||||
timeline.add(node5);
|
||||
|
||||
} else {
|
||||
// 普通任务流程
|
||||
|
||||
// 1. 任务发起
|
||||
com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode node1 = new com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode();
|
||||
node1.setNodeName("任务发起");
|
||||
node1.setStatus("completed");
|
||||
node1.setTime(task.getCreateTime());
|
||||
if (task.getPublisherId() != null) {
|
||||
User publisher = userMapper.selectById(task.getPublisherId());
|
||||
if (publisher != null) {
|
||||
node1.setOperatorName(publisher.getRealName());
|
||||
node1.setOperatorDept(publisher.getDeptName());
|
||||
}
|
||||
}
|
||||
timeline.add(node1);
|
||||
|
||||
// 2. 任务审核
|
||||
com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode node2 = new com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode();
|
||||
node2.setNodeName("任务审核");
|
||||
if ("approved".equals(approvalStatus) || "pending".equals(status) || "in_progress".equals(status) || "completed".equals(status)) {
|
||||
node2.setStatus("completed");
|
||||
// 查找审批日志
|
||||
TaskApprovalLog approveLog = logMap.get("approve");
|
||||
if (approveLog != null) {
|
||||
node2.setOperatorName(approveLog.getApproverName());
|
||||
node2.setTime(approveLog.getCreateTime());
|
||||
node2.setComment(approveLog.getComment());
|
||||
User approver = userMapper.selectById(approveLog.getApproverId());
|
||||
if (approver != null) {
|
||||
node2.setOperatorDept(approver.getDeptName());
|
||||
}
|
||||
}
|
||||
} else if ("pending_approval".equals(approvalStatus)) {
|
||||
node2.setStatus("pending");
|
||||
if (task.getCurrentApproverId() != null) {
|
||||
User approver = userMapper.selectById(task.getCurrentApproverId());
|
||||
if (approver != null) {
|
||||
node2.setOperatorName(approver.getRealName());
|
||||
node2.setOperatorDept(approver.getDeptName());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node2.setStatus("waiting");
|
||||
}
|
||||
timeline.add(node2);
|
||||
|
||||
// 3. 任务执行
|
||||
com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode node3 = new com.designhub.vo.TaskFlowStatusVO.FlowTimelineNode();
|
||||
node3.setNodeName("任务执行");
|
||||
if ("completed".equals(status)) {
|
||||
node3.setStatus("completed");
|
||||
node3.setTime(task.getCompleteTime());
|
||||
if (task.getReceiverId() != null) {
|
||||
User receiver = userMapper.selectById(task.getReceiverId());
|
||||
if (receiver != null) {
|
||||
node3.setOperatorName(receiver.getRealName());
|
||||
node3.setOperatorDept(receiver.getDeptName());
|
||||
}
|
||||
}
|
||||
} else if ("in_progress".equals(status)) {
|
||||
node3.setStatus("pending");
|
||||
if (task.getReceiverId() != null) {
|
||||
User receiver = userMapper.selectById(task.getReceiverId());
|
||||
if (receiver != null) {
|
||||
node3.setOperatorName(receiver.getRealName());
|
||||
node3.setOperatorDept(receiver.getDeptName());
|
||||
}
|
||||
}
|
||||
} else if ("pending".equals(status)) {
|
||||
node3.setStatus("waiting");
|
||||
} else {
|
||||
node3.setStatus("waiting");
|
||||
}
|
||||
timeline.add(node3);
|
||||
}
|
||||
|
||||
return timeline;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建相关人员信息
|
||||
*/
|
||||
private com.designhub.vo.TaskFlowStatusVO.RelatedUsers buildRelatedUsers(Task task) {
|
||||
com.designhub.vo.TaskFlowStatusVO.RelatedUsers users = new com.designhub.vo.TaskFlowStatusVO.RelatedUsers();
|
||||
|
||||
// 发起人
|
||||
Long publisherId = task.getPrimaryPublisherId() != null ? task.getPrimaryPublisherId() : task.getPublisherId();
|
||||
if (publisherId != null) {
|
||||
User publisher = userMapper.selectById(publisherId);
|
||||
if (publisher != null) {
|
||||
com.designhub.vo.TaskFlowStatusVO.UserInfo info = new com.designhub.vo.TaskFlowStatusVO.UserInfo();
|
||||
info.setUserId(publisherId);
|
||||
info.setName(publisher.getRealName());
|
||||
info.setDept(publisher.getDeptName());
|
||||
users.setPublisher(info);
|
||||
}
|
||||
}
|
||||
|
||||
// 明细完善人
|
||||
if (task.getDetailCompleterId() != null) {
|
||||
User completer = userMapper.selectById(task.getDetailCompleterId());
|
||||
if (completer != null) {
|
||||
com.designhub.vo.TaskFlowStatusVO.UserInfo info = new com.designhub.vo.TaskFlowStatusVO.UserInfo();
|
||||
info.setUserId(task.getDetailCompleterId());
|
||||
info.setName(completer.getRealName());
|
||||
info.setDept(completer.getDeptName());
|
||||
users.setDetailCompleter(info);
|
||||
}
|
||||
}
|
||||
|
||||
// 接单人
|
||||
if (task.getReceiverId() != null) {
|
||||
User receiver = userMapper.selectById(task.getReceiverId());
|
||||
if (receiver != null) {
|
||||
com.designhub.vo.TaskFlowStatusVO.UserInfo info = new com.designhub.vo.TaskFlowStatusVO.UserInfo();
|
||||
info.setUserId(task.getReceiverId());
|
||||
info.setName(receiver.getRealName());
|
||||
info.setDept(receiver.getDeptName());
|
||||
users.setReceiver(info);
|
||||
}
|
||||
}
|
||||
|
||||
// 当前审批人
|
||||
if (task.getCurrentApproverId() != null) {
|
||||
User approver = userMapper.selectById(task.getCurrentApproverId());
|
||||
if (approver != null) {
|
||||
com.designhub.vo.TaskFlowStatusVO.UserInfo info = new com.designhub.vo.TaskFlowStatusVO.UserInfo();
|
||||
info.setUserId(task.getCurrentApproverId());
|
||||
info.setName(approver.getRealName());
|
||||
info.setDept(approver.getDeptName());
|
||||
users.setCurrentApprover(info);
|
||||
}
|
||||
}
|
||||
|
||||
return users;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务执行状态中文名称
|
||||
*/
|
||||
private String getStatusName(String status) {
|
||||
if (status == null) return "未知";
|
||||
switch (status) {
|
||||
case "draft": return "草稿";
|
||||
case "pending_audit": return "待审核";
|
||||
case "pending": return "待接单";
|
||||
case "in_progress": return "进行中";
|
||||
case "completed": return "已完成";
|
||||
case "cancelled": return "已取消";
|
||||
default: return status;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取审批状态中文名称
|
||||
*/
|
||||
private String getApprovalStatusName(String approvalStatus, String status, boolean isPrimaryTask) {
|
||||
if (approvalStatus == null) {
|
||||
return getStatusName(status);
|
||||
}
|
||||
|
||||
switch (approvalStatus) {
|
||||
case "draft": return "草稿";
|
||||
case "pending_primary_approval": return "待一级需求审批";
|
||||
case "pending_detail_completion": return "待完善明细";
|
||||
case "pending_demand_approval": return "待需求部门审批";
|
||||
case "pending_receiving_approval": return "待接单部门审批";
|
||||
case "pending_approval": return "待审核";
|
||||
case "approved":
|
||||
// 已审核通过,显示执行状态
|
||||
return getStatusName(status);
|
||||
case "rejected": return "已驳回";
|
||||
case "rejected_by_demand": return "需求部门驳回";
|
||||
case "rejected_by_receiving": return "接单部门驳回";
|
||||
default: return approvalStatus;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取审批状态描述
|
||||
*/
|
||||
private String getApprovalStatusDescription(String approvalStatus, String status, boolean isPrimaryTask) {
|
||||
if (approvalStatus == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
switch (approvalStatus) {
|
||||
case "draft": return "任务已创建但未提交";
|
||||
case "pending_primary_approval": return "等待需求部门负责人审核并指定明细完善人";
|
||||
case "pending_detail_completion": return "等待指定的明细完善人填写任务明细";
|
||||
case "pending_demand_approval": return "明细完善后等待需求部门负责人审核";
|
||||
case "pending_receiving_approval": return "等待接单部门负责人审核";
|
||||
case "pending_approval": return "等待审核通过后发布";
|
||||
case "approved":
|
||||
if ("pending".equals(status)) {
|
||||
return "任务已发布,等待设计师接单";
|
||||
} else if ("in_progress".equals(status)) {
|
||||
return "设计师正在执行任务";
|
||||
} else if ("completed".equals(status)) {
|
||||
return "任务已完成";
|
||||
}
|
||||
return "任务已审核通过";
|
||||
case "rejected": return "任务被驳回,请修改后重新提交";
|
||||
case "rejected_by_demand": return "被需求部门负责人驳回";
|
||||
case "rejected_by_receiving": return "被接单部门负责人驳回";
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取审批人角色中文名称
|
||||
*/
|
||||
private String getApproverRoleName(String role) {
|
||||
if (role == null) return "";
|
||||
switch (role) {
|
||||
case "demand_leader": return "需求部门负责人";
|
||||
case "detail_completer": return "明细完善人";
|
||||
case "receiving_leader": return "接单部门负责人";
|
||||
default: return role;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
144
backend/src/main/java/com/designhub/vo/TaskFlowStatusVO.java
Normal file
144
backend/src/main/java/com/designhub/vo/TaskFlowStatusVO.java
Normal file
@ -0,0 +1,144 @@
|
||||
package com.designhub.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 任务流程状态VO - 用于任务节点查询功能
|
||||
*/
|
||||
@Data
|
||||
public class TaskFlowStatusVO {
|
||||
|
||||
/**
|
||||
* 任务基本信息
|
||||
*/
|
||||
private TaskBasicInfo task;
|
||||
|
||||
/**
|
||||
* 当前节点信息
|
||||
*/
|
||||
private CurrentNodeInfo currentNode;
|
||||
|
||||
/**
|
||||
* 流程时间线
|
||||
*/
|
||||
private List<FlowTimelineNode> flowTimeline;
|
||||
|
||||
/**
|
||||
* 相关人员信息
|
||||
*/
|
||||
private RelatedUsers relatedUsers;
|
||||
|
||||
/**
|
||||
* 任务基本信息
|
||||
*/
|
||||
@Data
|
||||
public static class TaskBasicInfo {
|
||||
private Long id;
|
||||
private String taskNo;
|
||||
private String title;
|
||||
private BigDecimal score;
|
||||
private Boolean isPrimaryTask;
|
||||
private Boolean isPipeline;
|
||||
private String status;
|
||||
private String statusName;
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前节点信息
|
||||
*/
|
||||
@Data
|
||||
public static class CurrentNodeInfo {
|
||||
private String status;
|
||||
private String statusName;
|
||||
private String description;
|
||||
private WaitingUser waitingFor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待操作的用户
|
||||
*/
|
||||
@Data
|
||||
public static class WaitingUser {
|
||||
private Long userId;
|
||||
private String userName;
|
||||
private String deptName;
|
||||
private String role;
|
||||
private String roleName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 流程时间线节点
|
||||
*/
|
||||
@Data
|
||||
public static class FlowTimelineNode {
|
||||
/**
|
||||
* 节点名称
|
||||
*/
|
||||
private String nodeName;
|
||||
|
||||
/**
|
||||
* 节点状态:completed-已完成, pending-进行中, waiting-等待中
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 操作人姓名
|
||||
*/
|
||||
private String operatorName;
|
||||
|
||||
/**
|
||||
* 操作人部门
|
||||
*/
|
||||
private String operatorDept;
|
||||
|
||||
/**
|
||||
* 操作时间
|
||||
*/
|
||||
private LocalDateTime time;
|
||||
|
||||
/**
|
||||
* 操作备注
|
||||
*/
|
||||
private String comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* 相关人员信息
|
||||
*/
|
||||
@Data
|
||||
public static class RelatedUsers {
|
||||
/**
|
||||
* 发起人(普通任务)或一级需求发起人
|
||||
*/
|
||||
private UserInfo publisher;
|
||||
|
||||
/**
|
||||
* 明细完善人(一级需求任务)
|
||||
*/
|
||||
private UserInfo detailCompleter;
|
||||
|
||||
/**
|
||||
* 接单人/执行人
|
||||
*/
|
||||
private UserInfo receiver;
|
||||
|
||||
/**
|
||||
* 当前审批人
|
||||
*/
|
||||
private UserInfo currentApprover;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
@Data
|
||||
public static class UserInfo {
|
||||
private Long userId;
|
||||
private String name;
|
||||
private String dept;
|
||||
}
|
||||
}
|
||||
499
docs/任务节点查询功能开发文档.md
Normal file
499
docs/任务节点查询功能开发文档.md
Normal file
@ -0,0 +1,499 @@
|
||||
# 任务节点查询功能开发文档
|
||||
|
||||
## 1. 需求背景
|
||||
|
||||
用户反馈经常找不到任务当前处于什么节点,不清楚:
|
||||
- 任务当前状态是什么
|
||||
- 下一步需要谁操作
|
||||
- 任务经历了哪些审批流程
|
||||
|
||||
因此需要开发一个**任务节点查询功能**,让用户可以通过任务编号快速查询任务的流程状态。
|
||||
|
||||
---
|
||||
|
||||
## 2. 功能设计
|
||||
|
||||
### 2.1 功能入口
|
||||
|
||||
在页面顶部导航栏或任务大厅页面增加「任务查询」按钮,点击后弹出查询对话框。
|
||||
|
||||
### 2.2 查询方式
|
||||
|
||||
- **任务编号查询**:输入完整的任务编号(如 `TASK202601221029343146`)
|
||||
- **模糊搜索**:支持输入任务标题关键字搜索
|
||||
|
||||
### 2.3 展示内容
|
||||
|
||||
#### 基本信息卡片
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| 任务编号 | TASK202601221029343146 |
|
||||
| 任务标题 | AD0259-欧意朗-天猫 |
|
||||
| 任务类型 | 一级需求任务 / 普通任务 |
|
||||
| 任务积分 | 5.20 分 |
|
||||
| 创建时间 | 2026-01-22 10:29:34 |
|
||||
|
||||
#### 当前节点信息(重点展示)
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 📍 当前节点:待接单部门审核 │
|
||||
│ │
|
||||
│ 等待操作人:清云(营销策划中心) │
|
||||
│ 操作说明:接单部门负责人需审核此任务 │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 流程时间线(可视化展示)
|
||||
```
|
||||
● 一级需求发起 2026-01-22 10:29:34 瑶池(亿家达组)
|
||||
↓
|
||||
● 一级需求审批通过 2026-01-22 13:46:56 步惊云(策划三部)
|
||||
↓
|
||||
● 明细完善完成 2026-01-23 10:44:24 殷云(策划四部)
|
||||
↓
|
||||
○ 接单部门审核 等待中... 清云(营销策划中心) ← 当前
|
||||
↓
|
||||
○ 任务发布 - -
|
||||
```
|
||||
|
||||
#### 相关人员信息
|
||||
| 角色 | 姓名 | 部门 |
|
||||
|------|------|------|
|
||||
| 发起人 | 瑶池 | 亿家达组 |
|
||||
| 明细完善人 | 殷云 | 策划四部 |
|
||||
| 接单人 | 乐游 | 平面视觉五组 |
|
||||
| 当前审批人 | 清云 | 营销策划中心 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 任务状态映射表
|
||||
|
||||
### 3.1 普通任务状态
|
||||
|
||||
| approval_status | 中文名称 | 说明 |
|
||||
|-----------------|----------|------|
|
||||
| draft | 草稿 | 任务已创建但未提交 |
|
||||
| pending_approval | 待审核 | 等待需求部门负责人审核 |
|
||||
| approved | 已审核 | 审核通过,任务已发布 |
|
||||
| rejected | 已驳回 | 审核被驳回 |
|
||||
|
||||
### 3.2 一级需求任务状态
|
||||
|
||||
| approval_status | 中文名称 | 当前操作人角色 | 说明 |
|
||||
|-----------------|----------|----------------|------|
|
||||
| draft | 草稿 | 发起人 | 任务已创建但未提交 |
|
||||
| pending_primary_approval | 待一级需求审批 | 需求部门负责人 | 等待需求部门负责人审核并指定明细完善人 |
|
||||
| pending_detail_completion | 待完善明细 | 明细完善人 | 等待指定的明细完善人填写任务明细 |
|
||||
| pending_demand_approval | 待需求部门审批 | 需求部门负责人 | 明细完善后等待需求部门负责人审核 |
|
||||
| pending_receiving_approval | 待接单部门审批 | 接单部门负责人 | 等待接单部门负责人审核 |
|
||||
| approved | 已审核 | - | 所有审批通过,任务已发布 |
|
||||
| rejected_by_demand | 需求部门驳回 | 明细完善人 | 被需求部门负责人驳回 |
|
||||
| rejected_by_receiving | 接单部门驳回 | 需求部门负责人 | 被接单部门负责人驳回 |
|
||||
|
||||
### 3.3 任务执行状态
|
||||
|
||||
| status | 中文名称 | 说明 |
|
||||
|--------|----------|------|
|
||||
| draft | 草稿 | 未提交 |
|
||||
| pending_audit | 待审核 | 审批流程中 |
|
||||
| pending | 待接单/进行中 | 已发布等待接单或执行中 |
|
||||
| in_progress | 进行中 | 设计师正在执行 |
|
||||
| completed | 已完成 | 任务完成 |
|
||||
| cancelled | 已取消 | 任务被取消 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 技术实现
|
||||
|
||||
### 4.1 后端接口
|
||||
|
||||
#### 4.1.1 任务节点查询接口
|
||||
|
||||
**接口地址**:`GET /api/task/flow-status`
|
||||
|
||||
**请求参数**:
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| taskNo | String | 否 | 任务编号 |
|
||||
| keyword | String | 否 | 任务标题关键字 |
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"task": {
|
||||
"id": 432,
|
||||
"taskNo": "TASK202601221029343146",
|
||||
"title": "AD0259-欧意朗-天猫",
|
||||
"score": 5.20,
|
||||
"isPrimaryTask": true,
|
||||
"createTime": "2026-01-22 10:29:34"
|
||||
},
|
||||
"currentNode": {
|
||||
"status": "pending_receiving_approval",
|
||||
"statusName": "待接单部门审批",
|
||||
"description": "等待接单部门负责人审核此任务",
|
||||
"waitingFor": {
|
||||
"userId": 17,
|
||||
"userName": "清云",
|
||||
"deptName": "营销策划中心",
|
||||
"role": "接单部门负责人"
|
||||
}
|
||||
},
|
||||
"flowTimeline": [
|
||||
{
|
||||
"nodeName": "一级需求发起",
|
||||
"status": "completed",
|
||||
"operatorName": "瑶池",
|
||||
"operatorDept": "亿家达组",
|
||||
"time": "2026-01-22 10:29:34"
|
||||
},
|
||||
{
|
||||
"nodeName": "一级需求审批",
|
||||
"status": "completed",
|
||||
"operatorName": "步惊云",
|
||||
"operatorDept": "策划三部",
|
||||
"time": "2026-01-22 13:46:56",
|
||||
"comment": "审核通过,请完善任务明细"
|
||||
},
|
||||
{
|
||||
"nodeName": "明细完善",
|
||||
"status": "completed",
|
||||
"operatorName": "殷云",
|
||||
"operatorDept": "策划四部",
|
||||
"time": "2026-01-23 10:44:24"
|
||||
},
|
||||
{
|
||||
"nodeName": "接单部门审批",
|
||||
"status": "pending",
|
||||
"operatorName": "清云",
|
||||
"operatorDept": "营销策划中心",
|
||||
"time": null
|
||||
},
|
||||
{
|
||||
"nodeName": "任务发布",
|
||||
"status": "waiting",
|
||||
"operatorName": null,
|
||||
"operatorDept": null,
|
||||
"time": null
|
||||
}
|
||||
],
|
||||
"relatedUsers": {
|
||||
"publisher": { "name": "瑶池", "dept": "亿家达组" },
|
||||
"detailCompleter": { "name": "殷云", "dept": "策划四部" },
|
||||
"receiver": { "name": "乐游", "dept": "平面视觉五组" },
|
||||
"currentApprover": { "name": "清云", "dept": "营销策划中心" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 后端实现
|
||||
|
||||
#### 4.2.1 Controller 层
|
||||
|
||||
```java
|
||||
// TaskController.java
|
||||
|
||||
/**
|
||||
* 查询任务流程状态
|
||||
*/
|
||||
@GetMapping("/flow-status")
|
||||
public Result<TaskFlowStatusVO> getTaskFlowStatus(
|
||||
@RequestParam(required = false) String taskNo,
|
||||
@RequestParam(required = false) String keyword) {
|
||||
return Result.success(taskService.getTaskFlowStatus(taskNo, keyword));
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2.2 Service 层
|
||||
|
||||
```java
|
||||
// TaskService.java
|
||||
|
||||
/**
|
||||
* 获取任务流程状态
|
||||
*/
|
||||
public TaskFlowStatusVO getTaskFlowStatus(String taskNo, String keyword) {
|
||||
// 1. 查询任务
|
||||
Task task = findTaskByNoOrKeyword(taskNo, keyword);
|
||||
if (task == null) {
|
||||
throw new BusinessException("任务不存在");
|
||||
}
|
||||
|
||||
// 2. 构建基本信息
|
||||
TaskFlowStatusVO vo = new TaskFlowStatusVO();
|
||||
vo.setTask(buildTaskInfo(task));
|
||||
|
||||
// 3. 构建当前节点信息
|
||||
vo.setCurrentNode(buildCurrentNode(task));
|
||||
|
||||
// 4. 构建流程时间线
|
||||
vo.setFlowTimeline(buildFlowTimeline(task));
|
||||
|
||||
// 5. 构建相关人员信息
|
||||
vo.setRelatedUsers(buildRelatedUsers(task));
|
||||
|
||||
return vo;
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2.3 VO 类
|
||||
|
||||
```java
|
||||
// TaskFlowStatusVO.java
|
||||
|
||||
@Data
|
||||
public class TaskFlowStatusVO {
|
||||
private TaskBasicInfo task;
|
||||
private CurrentNodeInfo currentNode;
|
||||
private List<FlowTimelineNode> flowTimeline;
|
||||
private RelatedUsers relatedUsers;
|
||||
|
||||
@Data
|
||||
public static class TaskBasicInfo {
|
||||
private Long id;
|
||||
private String taskNo;
|
||||
private String title;
|
||||
private BigDecimal score;
|
||||
private Boolean isPrimaryTask;
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class CurrentNodeInfo {
|
||||
private String status;
|
||||
private String statusName;
|
||||
private String description;
|
||||
private WaitingUser waitingFor;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class WaitingUser {
|
||||
private Long userId;
|
||||
private String userName;
|
||||
private String deptName;
|
||||
private String role;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class FlowTimelineNode {
|
||||
private String nodeName;
|
||||
private String status; // completed, pending, waiting
|
||||
private String operatorName;
|
||||
private String operatorDept;
|
||||
private LocalDateTime time;
|
||||
private String comment;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class RelatedUsers {
|
||||
private UserInfo publisher;
|
||||
private UserInfo detailCompleter;
|
||||
private UserInfo receiver;
|
||||
private UserInfo currentApprover;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class UserInfo {
|
||||
private String name;
|
||||
private String dept;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 前端实现
|
||||
|
||||
#### 4.3.1 查询对话框组件
|
||||
|
||||
```vue
|
||||
<!-- TaskFlowQueryDialog.vue -->
|
||||
<template>
|
||||
<el-dialog title="任务节点查询" v-model="visible" width="700px">
|
||||
<!-- 搜索框 -->
|
||||
<div class="search-box">
|
||||
<el-input
|
||||
v-model="searchInput"
|
||||
placeholder="请输入任务编号或任务标题"
|
||||
@keyup.enter="handleSearch"
|
||||
clearable>
|
||||
<template #append>
|
||||
<el-button @click="handleSearch" :loading="loading">
|
||||
<el-icon><Search /></el-icon> 查询
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<!-- 查询结果 -->
|
||||
<div v-if="result" class="result-container">
|
||||
<!-- 基本信息 -->
|
||||
<el-card class="info-card">
|
||||
<template #header>任务信息</template>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="任务编号">{{ result.task.taskNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务标题">{{ result.task.title }}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务类型">
|
||||
<el-tag :type="result.task.isPrimaryTask ? 'warning' : ''">
|
||||
{{ result.task.isPrimaryTask ? '一级需求任务' : '普通任务' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="任务积分">{{ result.task.score }} 分</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
<!-- 当前节点(重点展示) -->
|
||||
<el-card class="current-node-card">
|
||||
<div class="current-node">
|
||||
<div class="node-icon">📍</div>
|
||||
<div class="node-info">
|
||||
<div class="node-status">当前节点:{{ result.currentNode.statusName }}</div>
|
||||
<div class="node-waiting">
|
||||
等待操作人:{{ result.currentNode.waitingFor?.userName }}
|
||||
({{ result.currentNode.waitingFor?.deptName }})
|
||||
</div>
|
||||
<div class="node-desc">{{ result.currentNode.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 流程时间线 -->
|
||||
<el-card class="timeline-card">
|
||||
<template #header>流程进度</template>
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="node in result.flowTimeline"
|
||||
:key="node.nodeName"
|
||||
:type="getTimelineType(node.status)"
|
||||
:hollow="node.status !== 'completed'">
|
||||
<div class="timeline-node">
|
||||
<div class="node-name">{{ node.nodeName }}</div>
|
||||
<div class="node-operator" v-if="node.operatorName">
|
||||
{{ node.operatorName }}({{ node.operatorDept }})
|
||||
</div>
|
||||
<div class="node-time" v-if="node.time">{{ formatTime(node.time) }}</div>
|
||||
<div class="node-comment" v-if="node.comment">备注:{{ node.comment }}</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<el-empty v-else-if="searched && !result" description="未找到相关任务" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
```
|
||||
|
||||
#### 4.3.2 样式设计
|
||||
|
||||
```scss
|
||||
.current-node-card {
|
||||
margin: 16px 0;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
|
||||
.current-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
|
||||
.node-icon {
|
||||
font-size: 48px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.node-status {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.node-waiting {
|
||||
font-size: 16px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.node-desc {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-card {
|
||||
.timeline-node {
|
||||
.node-name {
|
||||
font-weight: bold;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.node-operator {
|
||||
color: #409eff;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.node-time {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.node-comment {
|
||||
color: #67c23a;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 功能入口位置
|
||||
|
||||
### 5.1 方案A:顶部导航栏
|
||||
|
||||
在导航栏右侧增加「🔍 任务查询」按钮,全局可用。
|
||||
|
||||
### 5.2 方案B:任务大厅页面
|
||||
|
||||
在任务大厅页面顶部筛选区域增加「任务节点查询」按钮。
|
||||
|
||||
### 5.3 方案C:快捷搜索框
|
||||
|
||||
在页面顶部增加全局搜索框,支持直接输入任务编号快速查询。
|
||||
|
||||
**推荐方案**:方案A + 方案C 组合,既有明显入口,又支持快捷查询。
|
||||
|
||||
---
|
||||
|
||||
## 6. 权限控制
|
||||
|
||||
| 用户角色 | 可查询范围 |
|
||||
|----------|-----------|
|
||||
| 管理员 | 所有任务 |
|
||||
| 需求部门成员 | 本部门发起的任务 + 自己参与的任务 |
|
||||
| 接单部门成员 | 自己接单的任务 + 本部门的任务 |
|
||||
| 普通用户 | 自己发起或参与的任务 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 开发工作量估算
|
||||
|
||||
| 模块 | 工作内容 | 预估时间 |
|
||||
|------|----------|----------|
|
||||
| 后端 | 接口开发、VO类、Service逻辑 | 4小时 |
|
||||
| 前端 | 对话框组件、时间线、样式 | 4小时 |
|
||||
| 测试 | 功能测试、边界情况 | 2小时 |
|
||||
| **合计** | | **10小时** |
|
||||
|
||||
---
|
||||
|
||||
## 8. 后续扩展
|
||||
|
||||
1. **消息通知集成**:在通知消息中增加「查看任务进度」链接
|
||||
2. **批量查询**:支持一次查询多个任务的状态
|
||||
3. **导出功能**:支持导出任务流程记录
|
||||
4. **订阅提醒**:用户可订阅任务状态变更通知
|
||||
@ -792,3 +792,51 @@ export interface TaskItemDTO {
|
||||
export const completeTaskDetail = (taskId: number, taskItems: TaskItemDTO[]) => {
|
||||
return request.post(`/tasks/${taskId}/complete-detail`, taskItems)
|
||||
}
|
||||
|
||||
// ==================== 任务流程查询接口 ====================
|
||||
|
||||
// 任务流程状态响应类型
|
||||
export interface TaskFlowStatusVO {
|
||||
task: {
|
||||
id: number
|
||||
taskNo: string
|
||||
title: string
|
||||
score: number
|
||||
isPrimaryTask: boolean
|
||||
isPipeline: boolean
|
||||
status: string
|
||||
statusName: string
|
||||
createTime: string
|
||||
}
|
||||
currentNode: {
|
||||
status: string
|
||||
statusName: string
|
||||
description: string
|
||||
waitingFor?: {
|
||||
userId: number
|
||||
userName: string
|
||||
deptName: string
|
||||
role: string
|
||||
roleName: string
|
||||
}
|
||||
}
|
||||
flowTimeline: Array<{
|
||||
nodeName: string
|
||||
status: 'completed' | 'pending' | 'waiting'
|
||||
operatorName?: string
|
||||
operatorDept?: string
|
||||
time?: string
|
||||
comment?: string
|
||||
}>
|
||||
relatedUsers: {
|
||||
publisher?: { userId: number; name: string; dept: string }
|
||||
detailCompleter?: { userId: number; name: string; dept: string }
|
||||
receiver?: { userId: number; name: string; dept: string }
|
||||
currentApprover?: { userId: number; name: string; dept: string }
|
||||
}
|
||||
}
|
||||
|
||||
// 查询任务流程状态
|
||||
export const getTaskFlowStatus = (params: { taskNo?: string; keyword?: string }) => {
|
||||
return request.get<TaskFlowStatusVO>('/tasks/flow-status', { params })
|
||||
}
|
||||
|
||||
@ -84,19 +84,35 @@
|
||||
|
||||
<!-- 通知铃铛 -->
|
||||
<NotificationBell v-if="navbarConfigStore.config.actions.notification" />
|
||||
|
||||
<!-- 任务查询 -->
|
||||
<div
|
||||
class="navbar-icon"
|
||||
@click="showTaskFlowQuery = true"
|
||||
title="任务节点查询"
|
||||
>
|
||||
<Icon name="search" size="md" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务流程查询对话框 -->
|
||||
<TaskFlowQueryDialog v-model="showTaskFlowQuery" />
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useSiteSettingsStore } from '@/stores/siteSettings'
|
||||
import { useNavbarConfigStore } from '@/stores/navbarConfig'
|
||||
import NotificationBell from '@/components/NotificationBell.vue'
|
||||
import Icon from '@/components/Icon.vue'
|
||||
import TaskFlowQueryDialog from '@/components/TaskFlowQueryDialog.vue'
|
||||
|
||||
// 任务查询对话框显示状态
|
||||
const showTaskFlowQuery = ref(false)
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
405
frontend/src/components/TaskFlowQueryDialog.vue
Normal file
405
frontend/src/components/TaskFlowQueryDialog.vue
Normal file
@ -0,0 +1,405 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="任务节点查询"
|
||||
width="720px"
|
||||
:close-on-click-modal="false"
|
||||
@close="handleClose"
|
||||
>
|
||||
<!-- 搜索框 -->
|
||||
<div class="search-box">
|
||||
<el-input
|
||||
v-model="searchInput"
|
||||
placeholder="请输入任务编号或任务标题关键字"
|
||||
clearable
|
||||
size="large"
|
||||
@keyup.enter="handleSearch"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
</template>
|
||||
<template #append>
|
||||
<el-button type="primary" :loading="loading" @click="handleSearch">
|
||||
查询
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="search-tip">
|
||||
支持输入完整任务编号(如 TASK202601221029343146)或任务标题关键字搜索
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 查询结果 -->
|
||||
<div v-if="result" class="result-container">
|
||||
<!-- 基本信息卡片 -->
|
||||
<el-card class="info-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>任务信息</span>
|
||||
<el-tag v-if="result.task.isPrimaryTask" type="warning" size="small">一级需求任务</el-tag>
|
||||
<el-tag v-else size="small">普通任务</el-tag>
|
||||
<el-tag v-if="result.task.isPipeline" type="info" size="small" style="margin-left: 8px;">流水线</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="任务编号">
|
||||
<el-text type="primary" copyable>{{ result.task.taskNo }}</el-text>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="任务标题">{{ result.task.title }}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务积分">
|
||||
<el-text type="success">{{ result.task.score }} 分</el-text>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ formatTime(result.task.createTime) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
|
||||
<!-- 当前节点卡片(重点展示) -->
|
||||
<div class="current-node-card">
|
||||
<div class="current-node">
|
||||
<div class="node-icon">📍</div>
|
||||
<div class="node-info">
|
||||
<div class="node-status">当前节点:{{ result.currentNode.statusName }}</div>
|
||||
<div class="node-waiting" v-if="result.currentNode.waitingFor">
|
||||
<span v-if="result.currentNode.waitingFor.userName">
|
||||
等待操作人:<strong>{{ result.currentNode.waitingFor.userName }}</strong>
|
||||
<span v-if="result.currentNode.waitingFor.deptName">({{ result.currentNode.waitingFor.deptName }})</span>
|
||||
</span>
|
||||
<span v-else-if="result.currentNode.waitingFor.roleName">
|
||||
{{ result.currentNode.waitingFor.roleName }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="node-desc">{{ result.currentNode.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 流程时间线 -->
|
||||
<el-card class="timeline-card" shadow="never">
|
||||
<template #header>
|
||||
<span>流程进度</span>
|
||||
</template>
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(node, index) in result.flowTimeline"
|
||||
:key="index"
|
||||
:type="getTimelineType(node.status)"
|
||||
:hollow="node.status !== 'completed'"
|
||||
:timestamp="node.time ? formatTime(node.time) : (node.status === 'pending' ? '进行中...' : '等待中')"
|
||||
placement="top"
|
||||
>
|
||||
<div class="timeline-node">
|
||||
<div class="node-name">
|
||||
{{ node.nodeName }}
|
||||
<el-tag v-if="node.status === 'pending'" type="warning" size="small" style="margin-left: 8px;">当前</el-tag>
|
||||
</div>
|
||||
<div class="node-operator" v-if="node.operatorName">
|
||||
<el-icon><User /></el-icon>
|
||||
{{ node.operatorName }}
|
||||
<span v-if="node.operatorDept">({{ node.operatorDept }})</span>
|
||||
</div>
|
||||
<div class="node-comment" v-if="node.comment">
|
||||
<el-icon><ChatDotRound /></el-icon>
|
||||
{{ node.comment }}
|
||||
</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</el-card>
|
||||
|
||||
<!-- 相关人员 -->
|
||||
<el-card class="users-card" shadow="never">
|
||||
<template #header>
|
||||
<span>相关人员</span>
|
||||
</template>
|
||||
<div class="users-grid">
|
||||
<div class="user-item" v-if="result.relatedUsers.publisher">
|
||||
<div class="user-role">发起人</div>
|
||||
<div class="user-name">{{ result.relatedUsers.publisher.name }}</div>
|
||||
<div class="user-dept">{{ result.relatedUsers.publisher.dept }}</div>
|
||||
</div>
|
||||
<div class="user-item" v-if="result.relatedUsers.detailCompleter">
|
||||
<div class="user-role">明细完善人</div>
|
||||
<div class="user-name">{{ result.relatedUsers.detailCompleter.name }}</div>
|
||||
<div class="user-dept">{{ result.relatedUsers.detailCompleter.dept }}</div>
|
||||
</div>
|
||||
<div class="user-item" v-if="result.relatedUsers.receiver">
|
||||
<div class="user-role">接单人</div>
|
||||
<div class="user-name">{{ result.relatedUsers.receiver.name }}</div>
|
||||
<div class="user-dept">{{ result.relatedUsers.receiver.dept }}</div>
|
||||
</div>
|
||||
<div class="user-item" v-if="result.relatedUsers.currentApprover">
|
||||
<div class="user-role">当前审批人</div>
|
||||
<div class="user-name">{{ result.relatedUsers.currentApprover.name }}</div>
|
||||
<div class="user-dept">{{ result.relatedUsers.currentApprover.dept }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<el-empty v-else-if="searched && !result && !loading" description="未找到相关任务" />
|
||||
|
||||
<!-- 初始状态 -->
|
||||
<div v-else-if="!searched" class="initial-state">
|
||||
<el-icon class="search-icon"><Search /></el-icon>
|
||||
<p>输入任务编号或标题关键字查询任务流程状态</p>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { Search, User, ChatDotRound } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getTaskFlowStatus, type TaskFlowStatusVO } from '@/api/task'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
initialTaskNo?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
}>()
|
||||
|
||||
const dialogVisible = ref(props.modelValue)
|
||||
const searchInput = ref('')
|
||||
const loading = ref(false)
|
||||
const searched = ref(false)
|
||||
const result = ref<TaskFlowStatusVO | null>(null)
|
||||
|
||||
// 同步外部 v-model
|
||||
watch(() => props.modelValue, (val) => {
|
||||
dialogVisible.value = val
|
||||
if (val && props.initialTaskNo) {
|
||||
searchInput.value = props.initialTaskNo
|
||||
handleSearch()
|
||||
}
|
||||
})
|
||||
|
||||
watch(dialogVisible, (val) => {
|
||||
emit('update:modelValue', val)
|
||||
})
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = async () => {
|
||||
if (!searchInput.value.trim()) {
|
||||
ElMessage.warning('请输入任务编号或关键字')
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
searched.value = true
|
||||
result.value = null
|
||||
|
||||
try {
|
||||
const input = searchInput.value.trim()
|
||||
// 判断是否为任务编号格式
|
||||
const isTaskNo = /^TASK\d+$/.test(input)
|
||||
|
||||
const params = isTaskNo
|
||||
? { taskNo: input }
|
||||
: { keyword: input }
|
||||
|
||||
const res = await getTaskFlowStatus(params)
|
||||
if (res.data) {
|
||||
result.value = res.data
|
||||
}
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error.message || '查询失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 处理关闭
|
||||
const handleClose = () => {
|
||||
searchInput.value = ''
|
||||
result.value = null
|
||||
searched.value = false
|
||||
}
|
||||
|
||||
// 获取时间线类型
|
||||
const getTimelineType = (status: string) => {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return 'success'
|
||||
case 'pending':
|
||||
return 'warning'
|
||||
default:
|
||||
return 'info'
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (time: string | undefined) => {
|
||||
if (!time) return ''
|
||||
const date = new Date(time)
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
search: (taskNo: string) => {
|
||||
searchInput.value = taskNo
|
||||
handleSearch()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.search-box {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.search-tip {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.result-container {
|
||||
.info-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.current-node-card {
|
||||
margin-bottom: 16px;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
|
||||
.current-node {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
color: #fff;
|
||||
|
||||
.node-icon {
|
||||
font-size: 40px;
|
||||
margin-right: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.node-info {
|
||||
flex: 1;
|
||||
|
||||
.node-status {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.node-waiting {
|
||||
font-size: 14px;
|
||||
margin-bottom: 6px;
|
||||
opacity: 0.95;
|
||||
|
||||
strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.node-desc {
|
||||
font-size: 13px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.timeline-node {
|
||||
.node-name {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.node-operator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: #409eff;
|
||||
font-size: 13px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.node-comment {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: #67c23a;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.users-card {
|
||||
.users-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16px;
|
||||
|
||||
.user-item {
|
||||
text-align: center;
|
||||
padding: 12px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
|
||||
.user-role {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.user-dept {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.initial-state {
|
||||
text-align: center;
|
||||
padding: 60px 0;
|
||||
color: #909399;
|
||||
|
||||
.search-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
// 暗色模式适配
|
||||
:deep(.el-card) {
|
||||
--el-card-bg-color: var(--el-bg-color);
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user