Files
huachuang/docs/superpowers/plans/2026-07-15-task-operation-refactor.md
2026-07-20 13:49:32 +08:00

27 KiB
Raw Permalink Blame History

搬运任务操作路由表重构 + PC 端操作接口 实现计划

面向 AI 代理的工作者: 必需子技能:使用 superpowers:subagent-driven-development推荐或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(- [ ])语法来跟踪进度。

目标: 消灭 ACS 反馈的 if-else 分发,改为 TransportTaskServiceImpl 内的枚举路由表;新增 PC 端统一操作接口(完成/取消/强制完成)并打通前端按钮。

架构: TaskOperationTypeEnum 增加 acsAllowed/pcAllowed 属性与 getByCode 解析;TransportTaskServiceImpl 持有 EnumMap<操作类型, BiConsumer> 路由表,handle* 业务方法原样迁入;TransportTaskFeedbackServiceImpl 瘦身为 ACS 前置处理层后委托分发PC 入口 POST /task/transport-task/operate 走同一路由表,差异仅在前置校验(抛异常)与记录 finishedType。

技术栈: Java 17 / Spring Boot / MyBatis-Plus后端Vue 3 + Vben + antdv-next前端

规格: docs/superpowers/specs/2026-07-15-task-operation-refactor-design.md

约定(来自 CLAUDE.md 与 nl-java-style 注释/日志用中文;注入用 @Resource;不写测试;不频繁 commit最后统一一次需用户同意枚举解析方法命名 getByCode


文件结构

文件 操作 职责
nl-module-task/nl-module-task-api/src/main/java/cn/code/nl/module/task/enums/TaskOperationTypeEnum.java 修改 操作类型枚举:增加入口允许标记与 getByCode 解析
nl-module-task/nl-module-task-api/src/main/java/cn/code/nl/module/task/enums/ErrorCodeConstants.java 修改 新增 PC 操作两个错误码
nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/controller/admin/transporttask/vo/TransportTaskOperateReqVO.java 创建 PC 操作请求 VO
nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/service/transporttask/TransportTaskService.java 修改 接口新增 operateTransportTask / dispatchOperation
nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/service/transporttask/TransportTaskServiceImpl.java 修改 路由表 + handle* 迁入 + PC 入口
nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/service/transporttask/TransportTaskFeedbackServiceImpl.java 修改 瘦身为 ACS 前置处理层
nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/controller/admin/transporttask/TransportTaskController.java 修改 新增 POST /operate
nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/task/transporttask/index.ts 修改 新增 operateTransportTask API
nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/task/transporttask/index.vue 修改 三个按钮绑定 handleOperate

任务 1枚举增强 TaskOperationTypeEnum

文件:

  • 修改:nl-module-task/nl-module-task-api/src/main/java/cn/code/nl/module/task/enums/TaskOperationTypeEnum.java

  • 步骤 1整体替换为以下内容

package cn.code.nl.module.task.enums;

import lombok.Getter;

/**
 * 任务操作类型枚举
 * <p>
 * acsAllowed是否允许 ACS 反馈触发pcAllowed是否允许 PC 端触发
 *
 * @Author: liyongde
 * @Date: 2026/7/15 19:11
 */
@Getter
public enum TaskOperationTypeEnum {

    EXECUTING("EXECUTING", "执行中", true, false),

    PICKED("PICKED", "取货完成", true, false),

    FINISHED("FINISHED", "完成任务", true, true),

    CANCELLED("CANCELLED", "取消任务", true, true),

    APPLY_AGAIN("APPLY-AGAIN", "二次请求", true, false),

    FORCE_FINISH("FORCE-FINISH", "强制完成", false, true);

    /** 操作编码 */
    private final String code;
    /** 操作名称 */
    private final String name;
    /** 是否允许 ACS 反馈触发 */
    private final boolean acsAllowed;
    /** 是否允许 PC 端触发 */
    private final boolean pcAllowed;

    TaskOperationTypeEnum(String code, String name, boolean acsAllowed, boolean pcAllowed) {
        this.code = code;
        this.name = name;
        this.acsAllowed = acsAllowed;
        this.pcAllowed = pcAllowed;
    }

    /**
     * 根据编码解析枚举(忽略大小写),找不到返回 null
     */
    public static TaskOperationTypeEnum getByCode(String code) {
        for (TaskOperationTypeEnum type : values()) {
            if (type.getCode().equalsIgnoreCase(code)) {
                return type;
            }
        }
        return null;
    }
}

任务 2新增错误码

文件:

  • 修改:nl-module-task/nl-module-task-api/src/main/java/cn/code/nl/module/task/enums/ErrorCodeConstants.java

  • 步骤 1TRANSPORT_TASK_RUNNING_ALREADY_EXIST 之后追加两行

    ErrorCode TRANSPORT_TASK_OPERATION_NOT_SUPPORTED = new ErrorCode(5006, "不支持的任务操作类型");
    ErrorCode TRANSPORT_TASK_ALREADY_FINAL = new ErrorCode(5007, "任务已处于终态,不允许操作");

任务 3新增 TransportTaskOperateReqVO

文件:

  • 创建:nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/controller/admin/transporttask/vo/TransportTaskOperateReqVO.java

  • 步骤 1创建文件

package cn.code.nl.module.task.controller.admin.transporttask.vo;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;

/**
 * PC 端搬运任务操作 Request VO
 *
 * @Author: liyongde
 * @Date: 2026/7/15
 */
@Schema(description = "管理后台 - PC 端搬运任务操作 Request VO")
@Data
public class TransportTaskOperateReqVO {

    @Schema(description = "任务ID", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "任务ID不能为空")
    private Long taskId;

    @Schema(description = "操作类型FINISHED/CANCELLED/FORCE-FINISH", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotEmpty(message = "操作类型不能为空")
    private String operationType;
}

任务 4TransportTaskService 接口新增方法

文件:

  • 修改:nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/service/transporttask/TransportTaskService.java

  • 步骤 1新增 import

在现有 import 区追加(vo.* 通配已覆盖新 VO无需单独引入

import cn.code.nl.module.task.dto.AcsFeedbackReqDTO;
import cn.code.nl.module.task.enums.TaskOperationTypeEnum;
  • 步骤 2getTransportTaskPage 方法之后新增两个方法声明
    /**
     * PC 端操作搬运任务(完成/取消/强制完成)
     *
     * @param reqVO 操作请求
     */
    void operateTransportTask(@Valid TransportTaskOperateReqVO reqVO);

    /**
     * 按操作类型分发处理任务ACS 反馈与 PC 端操作共用路由表)
     *
     * @param task   任务
     * @param type   操作类型
     * @param reqDTO 反馈请求PC 端为构造的精简对象)
     */
    void dispatchOperation(TransportTaskDO task, TaskOperationTypeEnum type, AcsFeedbackReqDTO reqDTO);

任务 5TransportTaskServiceImpl 路由表 + handle* 迁入 + PC 入口

文件:

  • 修改:nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/service/transporttask/TransportTaskServiceImpl.java

说明: handleExecuting/handlePicked/handleFinished/handleCancelled/publishEvent/buildCallbackReq 六个方法从 TransportTaskFeedbackServiceImpl 原样迁入(方法体一字不改),本任务只新增代码,删除动作在任务 6。

  • 步骤 1补充 import

在现有 import 区追加:

import cn.code.nl.framework.execute.biz.api.TaskCommonApi;
import cn.code.nl.framework.execute.core.TaskCommonApiFactory;
import cn.code.nl.module.task.dto.AcsFeedbackReqDTO;
import cn.code.nl.module.task.dto.TaskStatusCallbackReqDTO;
import cn.code.nl.module.task.enums.TaskEventTypeEnum;
import cn.code.nl.module.task.enums.TaskOperationTypeEnum;
import cn.code.nl.module.task.message.TaskEventMessage;
import cn.code.nl.module.task.mq.producer.TaskEventProducer;
import cn.hutool.core.util.StrUtil;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;

import java.util.EnumMap;
import java.util.Map;
import java.util.function.BiConsumer;

import static cn.code.nl.module.task.enums.ErrorCodeConstants.TRANSPORT_TASK_ALREADY_FINAL;
import static cn.code.nl.module.task.enums.ErrorCodeConstants.TRANSPORT_TASK_OPERATION_NOT_SUPPORTED;
import static cn.code.nl.module.task.framework.common.util.TaskUtil.isAllowedFrom;

注意:TRANSPORT_TASK_NOT_EXISTS 的静态导入已存在,不要重复。

  • 步骤 2类上补充 @Slf4j 注解
@Slf4j
@Service
@Validated
public class TransportTaskServiceImpl implements TransportTaskService {
  • 步骤 3新增依赖字段与路由表字段放在 transportTaskMapper 字段之后)
    @Resource
    private TaskEventProducer taskEventProducer;

    @Resource
    private TaskCommonApiFactory taskCommonApiFactory;

    /**
     * 操作类型 -> 处理方法 路由表ACS 反馈与 PC 端操作共用)
     */
    private final Map<TaskOperationTypeEnum, BiConsumer<TransportTaskDO, AcsFeedbackReqDTO>> operationHandlers =
            new EnumMap<>(TaskOperationTypeEnum.class);

    /**
     * 初始化操作类型路由表
     */
    @PostConstruct
    public void initOperationHandlers() {
        operationHandlers.put(TaskOperationTypeEnum.EXECUTING, (task, reqDTO) -> handleExecuting(task));
        operationHandlers.put(TaskOperationTypeEnum.PICKED, this::handlePicked);
        operationHandlers.put(TaskOperationTypeEnum.FINISHED, this::handleFinished);
        operationHandlers.put(TaskOperationTypeEnum.CANCELLED, this::handleCancelled);
        // todo: 二次请求业务未开发
        operationHandlers.put(TaskOperationTypeEnum.APPLY_AGAIN,
                (task, reqDTO) -> log.info("二次请求业务未开发, taskId={}", task.getTaskId()));
        // todo: 强制完成业务未开发
        operationHandlers.put(TaskOperationTypeEnum.FORCE_FINISH,
                (task, reqDTO) -> log.info("强制完成业务未开发, taskId={}", task.getTaskId()));
    }
  • 步骤 4新增 PC 入口与分发方法(放在 getTransportTaskPage 方法之后)
    /**
     * PC 端操作搬运任务(完成/取消/强制完成):先校验再走统一路由表分发
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void operateTransportTask(TransportTaskOperateReqVO reqVO) {
        // 1. 校验任务存在
        TransportTaskDO task = transportTaskMapper.selectById(reqVO.getTaskId());
        if (task == null) {
            throw exception(TRANSPORT_TASK_NOT_EXISTS);
        }
        // 2. 校验操作类型允许 PC 端触发
        TaskOperationTypeEnum type = TaskOperationTypeEnum.getByCode(reqVO.getOperationType());
        if (type == null || !type.isPcAllowed()) {
            throw exception(TRANSPORT_TASK_OPERATION_NOT_SUPPORTED);
        }
        // 3. 终态校验:已完成/已取消不允许再操作
        if (TransportTaskStatusEnum.FINISHED.getCode().equals(task.getTaskStatus())
                || TransportTaskStatusEnum.CANCELLED.getCode().equals(task.getTaskStatus())) {
            throw exception(TRANSPORT_TASK_ALREADY_FINAL);
        }
        // 4. PC 端与 ACS 的差异点:记录完成类型
        if (type == TaskOperationTypeEnum.FINISHED) {
            task.setFinishedType("MANUAL");
        } else if (type == TaskOperationTypeEnum.FORCE_FINISH) {
            task.setFinishedType("FORCE");
        }
        // 5. 构造精简反馈对象,走统一路由表分发
        AcsFeedbackReqDTO reqDTO = new AcsFeedbackReqDTO();
        reqDTO.setTaskId(task.getTaskId());
        reqDTO.setStatus(type.getCode());
        dispatchOperation(task, type, reqDTO);
    }

    /**
     * 按操作类型查路由表分发(消灭 if-else
     */
    @Override
    public void dispatchOperation(TransportTaskDO task, TaskOperationTypeEnum type, AcsFeedbackReqDTO reqDTO) {
        BiConsumer<TransportTaskDO, AcsFeedbackReqDTO> handler = operationHandlers.get(type);
        if (handler == null) {
            log.warn("操作类型未注册处理器, taskId={}, type={}", task.getTaskId(), type.getCode());
            return;
        }
        handler.accept(task, reqDTO);
    }
  • 步骤 5迁入六个业务方法放在类末尾方法体与原 TransportTaskFeedbackServiceImpl 完全一致)
    // ==================== 各状态处理方法(自 TransportTaskFeedbackServiceImpl 迁入,逻辑未改动) ====================

    /**
     * 处理执行中(60):直接更新状态,不做业务回调
     */
    private void handleExecuting(TransportTaskDO task) {
        if (!isAllowedFrom(task.getTaskStatus(), "40", "50")) {
            log.warn("执行中反馈状态校验不通过, taskId={}, currentStatus={}",
                    task.getTaskId(), task.getTaskStatus());
            return;
        }
        task.setTaskStatus(TransportTaskStatusEnum.EXECUTING.getCode());
        transportTaskMapper.updateById(task);
        log.info("任务执行中, taskId={}", task.getTaskId());
    }

    /**
     * 处理取货完成(61):更新状态 + 同步 HTTP 回调 LMS/WMS
     * ownerService 服务提供商
     */
    private void handlePicked(TransportTaskDO task, AcsFeedbackReqDTO reqDTO) {
        if (!isAllowedFrom(task.getTaskStatus(), "60", "61")) {
            log.warn("取货完成反馈状态校验不通过, taskId={}, currentStatus={}",
                    task.getTaskId(), task.getTaskStatus());
            return;
        }
        // 更新状态
        task.setTaskStatus(TransportTaskStatusEnum.PICKED.getCode());
        transportTaskMapper.updateById(task);
        log.info("任务已取货, taskId={}", task.getTaskId());

        // 同步请求业务
        TaskCommonApi byServerName = taskCommonApiFactory.getByServerName("lms-server");
        byServerName.doHandlePicked();
    }

    /**
     * 处理完成(67):更新状态 + 异步 MQ 通知 LMS/WMS
     */
    private void handleFinished(TransportTaskDO task, AcsFeedbackReqDTO reqDTO) {
        if (!isAllowedFrom(task.getTaskStatus(), "10", "40", "45", "50", "60", "61", "67")) {
            log.warn("完成反馈状态校验不通过, taskId={}, currentStatus={}",
                    task.getTaskId(), task.getTaskStatus());
            return;
        }
        // 已是 67 则只发 MQ不改状态
        if (!TransportTaskStatusEnum.FINISHED_CALLBACK_PENDING.getCode().equals(task.getTaskStatus())) {
            task.setTaskStatus(TransportTaskStatusEnum.FINISHED_CALLBACK_PENDING.getCode());
        }
        task.setCallbackStatus(CallbackStatusEnum.PENDING.getCode());
        transportTaskMapper.updateById(task);

        publishEvent(task, TaskEventTypeEnum.TASK_FINISHED, reqDTO.getPayload());
    }

    /**
     * 处理取消(69):更新状态 + 异步 MQ 通知 LMS/WMS
     */
    private void handleCancelled(TransportTaskDO task, AcsFeedbackReqDTO reqDTO) {
        if (!isAllowedFrom(task.getTaskStatus(), "10", "40", "45", "50", "60", "61", "67", "69")) {
            log.warn("取消反馈状态校验不通过, taskId={}, currentStatus={}",
                    task.getTaskId(), task.getTaskStatus());
            return;
        }
        if (!TransportTaskStatusEnum.CANCEL_CALLBACK_PENDING.getCode().equals(task.getTaskStatus())) {
            task.setTaskStatus(TransportTaskStatusEnum.CANCEL_CALLBACK_PENDING.getCode());
        }
        task.setCallbackStatus(CallbackStatusEnum.PENDING.getCode());
        transportTaskMapper.updateById(task);

        publishEvent(task, TaskEventTypeEnum.TASK_CANCELLED, reqDTO.getPayload());
    }

    // ==================== 工具方法 ====================

    /**
     * 发布 MQ 事件。发送失败时记录 callbackStatus=FAILED
     */
    private void publishEvent(TransportTaskDO task, TaskEventTypeEnum eventType,
                              Map<String, Object> payload) {
        TaskEventMessage msg = new TaskEventMessage();
        msg.setEventType(eventType.getCode());
        msg.setTaskId(task.getTaskId());
        msg.setTaskCode(task.getTaskCode());
        msg.setOwnerService(task.getOwnerService());
        msg.setBizType(task.getBizType());
        msg.setBizId(task.getBizId());
        msg.setHandleCode(task.getHandleCode());
        msg.setPayload(payload);

        try {
            taskEventProducer.publishEvent(msg);
        } catch (Exception e) {
            log.error("MQ 发布失败, taskId={}, eventType={}",
                    task.getTaskId(), eventType.getCode(), e);
            task.setCallbackStatus(CallbackStatusEnum.FAILED.getCode());
            task.setCallbackErrorMsg(StrUtil.maxLength(e.getMessage(), 500));
            transportTaskMapper.updateById(task);
        }
    }

    /**
     * 构建同步回调请求 DTO当前无调用方随迁保留
     */
    private TaskStatusCallbackReqDTO buildCallbackReq(TransportTaskDO task,
                                                       AcsFeedbackReqDTO reqDTO) {
        TaskStatusCallbackReqDTO req = new TaskStatusCallbackReqDTO();
        req.setTaskId(task.getTaskId());
        req.setTaskCode(task.getTaskCode());
        req.setStatus(TransportTaskStatusEnum.PICKED.getCode());
        req.setOwnerService(task.getOwnerService());
        req.setBizType(task.getBizType());
        req.setBizId(task.getBizId());
        req.setHandleCode(task.getHandleCode());
        req.setPayload(reqDTO.getPayload());
        return req;
    }

任务 6TransportTaskFeedbackServiceImpl 瘦身

文件:

  • 修改:nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/service/transporttask/TransportTaskFeedbackServiceImpl.java

  • 步骤 1整体替换为以下内容

package cn.code.nl.module.task.service.transporttask;

import cn.code.nl.module.task.dal.dataobject.transporttask.TransportTaskDO;
import cn.code.nl.module.task.dal.mysql.transporttask.TransportTaskMapper;
import cn.code.nl.module.task.dto.AcsFeedbackReqDTO;
import cn.code.nl.module.task.enums.TaskOperationTypeEnum;
import cn.code.nl.module.task.enums.TransportTaskStatusEnum;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import jakarta.annotation.Resource;

/**
 * ACS 反馈处理服务实现
 * <p>
 * 只做 ACS 前置处理(查任务/幂等/保存反馈参数),具体操作业务统一由 {@link TransportTaskService} 路由表分发
 *
 * @author 诺力管理员
 */
@Slf4j
@Service
@Validated
public class TransportTaskFeedbackServiceImpl implements TransportTaskFeedbackService {

    @Resource
    private TransportTaskMapper transportTaskMapper;

    @Resource
    private TransportTaskService transportTaskService;

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void receiveAcsFeedback(AcsFeedbackReqDTO reqDTO) {
        // 1. 查询任务
        TransportTaskDO task = transportTaskMapper.selectById(reqDTO.getTaskId());
        if (task == null) {
            log.warn("ACS 反馈 taskId 不存在, taskId={}, 直接返回成功", reqDTO.getTaskId());
            return;
        }

        // 2. 幂等判断:已经是终态(完成/取消)则不重复处理
        String currentStatus = task.getTaskStatus();
        if (TransportTaskStatusEnum.FINISHED.getCode().equals(currentStatus)
            || TransportTaskStatusEnum.CANCELLED.getCode().equals(currentStatus)) {
            log.info("任务已处终态, taskId={}, currentStatus={}, 忽略反馈",
                    reqDTO.getTaskId(), currentStatus);
            return;
        }

        // 3. 保存 resultParam
        if (reqDTO.getPayload() != null) {
            try {
                task.setResultParam(OBJECT_MAPPER.writeValueAsString(reqDTO.getPayload()));
            } catch (Exception e) {
                log.warn("resultParam 序列化失败, taskId={}", reqDTO.getTaskId(), e);
            }
        }

        // 4. 解析操作类型并校验允许 ACS 触发,走统一路由表分发
        TaskOperationTypeEnum type = TaskOperationTypeEnum.getByCode(reqDTO.getStatus());
        if (type == null || !type.isAcsAllowed()) {
            log.warn("未知或不允许的 ACS 反馈状态, taskId={}, status={}",
                    reqDTO.getTaskId(), reqDTO.getStatus());
            return;
        }
        transportTaskService.dispatchOperation(task, type, reqDTO);
    }
}

注意:原类中 handle*publishEventbuildCallbackReq 及其相关 importTaskCommonApi、TaskCommonApiFactory、TaskEventMessage、TaskStatusCallbackReqDTO、CallbackStatusEnum、TaskEventTypeEnum、TaskEventProducer、StrUtil、isAllowedFrom、Map随整体替换一并移除已在任务 5 迁入 TransportTaskServiceImpl


任务 7TransportTaskController 新增 operate 接口

文件:

  • 修改:nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/controller/admin/transporttask/TransportTaskController.java

  • 步骤 1updateTransportTask 方法之后新增(vo.* 通配 import 已覆盖新 VO

    @PostMapping("/operate")
    @Operation(summary = "PC 端操作搬运任务(完成/取消/强制完成)")
    @PreAuthorize("@ss.hasPermission('task:transport-task:operate')")
    public CommonResult<Boolean> operateTransportTask(@Valid @RequestBody TransportTaskOperateReqVO reqVO) {
        transportTaskService.operateTransportTask(reqVO);
        return success(true);
    }

任务 8后端编译验证

  • 步骤 1编译 task 模块

运行(仓库根目录):

mvn -f nl-module-task/pom.xml compile -q

预期BUILD SUCCESS无编译错误。若因兄弟模块依赖未安装到本地仓库而失败改用

mvn -pl nl-module-task -am compile -q

任务 9前端 API 新增 operateTransportTask

文件:

  • 修改:nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/task/transporttask/index.ts

  • 步骤 1exportTransportTask 之后新增

/** PC 端操作搬运任务(完成/取消/强制完成) */
export function operateTransportTask(data: {
  operationType: string;
  taskId: number | string;
}) {
  return requestClient.post('/task/transport-task/operate', data);
}

任务 10index.vue 三个按钮绑定操作类型

文件:

  • 修改:nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/task/transporttask/index.vue

  • 步骤 1import 块补充 operateTransportTask

import {
  deleteTransportTask,
  deleteTransportTaskList,
  exportTransportTask,
  getTransportTaskPage,
  operateTransportTask,
} from '#/api/task/transporttask';
  • 步骤 2handleDelete 之前新增操作函数
/** PC 端操作搬运任务(完成/取消/强制完成) */
async function handleOperate(
  row: TaskTransportTaskApi.TransportTask,
  operationType: string,
  label: string,
) {
  await confirm(`确认要${label}${row.taskCode}】吗?`);
  const hideLoading = message.loading({
    content: `正在${label}...`,
    duration: 0,
  });
  try {
    await operateTransportTask({ taskId: row.taskId!, operationType });
    message.success(`${label}成功`);
    handleRefresh();
  } finally {
    hideLoading();
  }
}
  • 步骤 3替换三个按钮的 onClick(原来错绑 handleEdit.bind(null, row)
            {
              label: '完成任务',
              type: 'link',
              icon: ACTION_ICON.SEND,
              auth: ['task:transport-task:finish'],
              disabled: ['79', '89', '99'].includes(row.taskStatus),
              onClick: handleOperate.bind(null, row, 'FINISHED', '完成任务'),
            },
            {
              label: '取消任务',
              type: 'link',
              icon: ACTION_ICON.CANCEL,
              auth: ['task:transport-task:cancel'],
              disabled: ['79', '89', '99'].includes(row.taskStatus),
              onClick: handleOperate.bind(null, row, 'CANCELLED', '取消任务'),
            },
            {
              label: '强制完成任务',
              type: 'link',
              icon: ACTION_ICON.CANCEL,
              auth: ['task:transport-task:cancel'],
              disabled: ['79', '89', '99'].includes(row.taskStatus),
              onClick: handleOperate.bind(null, row, 'FORCE-FINISH', '强制完成任务'),
            },

说明:auth/icon 维持现状(强制完成的独立权限属规格范围外)。


任务 11前端验证

  • 步骤 1类型检查可选monorepo 全量较慢)

运行(nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next 目录):

pnpm typecheck

预期:本次改动的两个文件无新增类型错误(仓库既有错误不属本次范围)。


任务 12统一提交可选需用户同意

  • 步骤 1确认用户同意后提交
git add docs/superpowers/specs/2026-07-15-task-operation-refactor-design.md \
        docs/superpowers/plans/2026-07-15-task-operation-refactor.md \
        nl-module-task/nl-module-task-api/src/main/java/cn/code/nl/module/task/enums/TaskOperationTypeEnum.java \
        nl-module-task/nl-module-task-api/src/main/java/cn/code/nl/module/task/enums/ErrorCodeConstants.java \
        nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/controller/admin/transporttask/vo/TransportTaskOperateReqVO.java \
        nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/controller/admin/transporttask/TransportTaskController.java \
        nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/service/transporttask/TransportTaskService.java \
        nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/service/transporttask/TransportTaskServiceImpl.java \
        nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/service/transporttask/TransportTaskFeedbackServiceImpl.java \
        nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/task/transporttask/index.ts \
        nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/task/transporttask/index.vue
git commit -m "feat任务操作路由表重构 + PC 端完成/取消/强制完成操作"

风险与注意事项

  1. 循环依赖:依赖方向为 FeedbackServiceImpl → TransportTaskService 单向,TransportTaskServiceImpl 不得反向注入 TransportTaskFeedbackService
  2. 事务receiveAcsFeedback@Transactional 经跨 Bean 调用按 REQUIRED 传播覆盖 dispatchOperationPC 入口自带 @Transactional
  3. FORCE-FINISH 的 finishedTypePC 入口会 set 但 handler 为 todo 不落库,属规格已确认的预期行为。
  4. 工作区有未提交改动:本计划涉及的部分文件已有本地修改(见 git status实现时在现有基础上叠加不要回退已有改动。