# 搬运任务操作路由表重构 + 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:整体替换为以下内容** ```java package cn.code.nl.module.task.enums; import lombok.Getter; /** * 任务操作类型枚举 *

* 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` - [ ] **步骤 1:在 `TRANSPORT_TASK_RUNNING_ALREADY_EXIST` 之后追加两行** ```java 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:创建文件** ```java 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; } ``` --- ### 任务 4:`TransportTaskService` 接口新增方法 **文件:** - 修改:`nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/service/transporttask/TransportTaskService.java` - [ ] **步骤 1:新增 import** 在现有 import 区追加(`vo.*` 通配已覆盖新 VO,无需单独引入): ```java import cn.code.nl.module.task.dto.AcsFeedbackReqDTO; import cn.code.nl.module.task.enums.TaskOperationTypeEnum; ``` - [ ] **步骤 2:在 `getTransportTaskPage` 方法之后新增两个方法声明** ```java /** * 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); ``` --- ### 任务 5:`TransportTaskServiceImpl` 路由表 + 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 区追加: ```java 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.mq.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` 注解** ```java @Slf4j @Service @Validated public class TransportTaskServiceImpl implements TransportTaskService { ``` - [ ] **步骤 3:新增依赖字段与路由表字段(放在 `transportTaskMapper` 字段之后)** ```java @Resource private TaskEventProducer taskEventProducer; @Resource private TaskCommonApiFactory taskCommonApiFactory; /** * 操作类型 -> 处理方法 路由表(ACS 反馈与 PC 端操作共用) */ private final Map> 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` 方法之后)** ```java /** * 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 handler = operationHandlers.get(type); if (handler == null) { log.warn("操作类型未注册处理器, taskId={}, type={}", task.getTaskId(), type.getCode()); return; } handler.accept(task, reqDTO); } ``` - [ ] **步骤 5:迁入六个业务方法(放在类末尾,方法体与原 `TransportTaskFeedbackServiceImpl` 完全一致)** ```java // ==================== 各状态处理方法(自 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 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; } ``` --- ### 任务 6:`TransportTaskFeedbackServiceImpl` 瘦身 **文件:** - 修改:`nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/service/transporttask/TransportTaskFeedbackServiceImpl.java` - [ ] **步骤 1:整体替换为以下内容** ```java 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 反馈处理服务实现 *

* 只做 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*`、`publishEvent`、`buildCallbackReq` 及其相关 import(TaskCommonApi、TaskCommonApiFactory、TaskEventMessage、TaskStatusCallbackReqDTO、CallbackStatusEnum、TaskEventTypeEnum、TaskEventProducer、StrUtil、isAllowedFrom、Map)随整体替换一并移除,已在任务 5 迁入 `TransportTaskServiceImpl`。 --- ### 任务 7:`TransportTaskController` 新增 operate 接口 **文件:** - 修改:`nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/controller/admin/transporttask/TransportTaskController.java` - [ ] **步骤 1:在 `updateTransportTask` 方法之后新增(`vo.*` 通配 import 已覆盖新 VO)** ```java @PostMapping("/operate") @Operation(summary = "PC 端操作搬运任务(完成/取消/强制完成)") @PreAuthorize("@ss.hasPermission('task:transport-task:operate')") public CommonResult operateTransportTask(@Valid @RequestBody TransportTaskOperateReqVO reqVO) { transportTaskService.operateTransportTask(reqVO); return success(true); } ``` --- ### 任务 8:后端编译验证 - [ ] **步骤 1:编译 task 模块** 运行(仓库根目录): ```bash mvn -f nl-module-task/pom.xml compile -q ``` 预期:BUILD SUCCESS,无编译错误。若因兄弟模块依赖未安装到本地仓库而失败,改用: ```bash 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` - [ ] **步骤 1:在 `exportTransportTask` 之后新增** ```ts /** PC 端操作搬运任务(完成/取消/强制完成) */ export function operateTransportTask(data: { operationType: string; taskId: number | string; }) { return requestClient.post('/task/transport-task/operate', data); } ``` --- ### 任务 10:`index.vue` 三个按钮绑定操作类型 **文件:** - 修改:`nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/task/transporttask/index.vue` - [ ] **步骤 1:import 块补充 `operateTransportTask`** ```ts import { deleteTransportTask, deleteTransportTaskList, exportTransportTask, getTransportTaskPage, operateTransportTask, } from '#/api/task/transporttask'; ``` - [ ] **步骤 2:在 `handleDelete` 之前新增操作函数** ```ts /** 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)`)** ```ts { 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` 目录): ```bash pnpm typecheck ``` 预期:本次改动的两个文件无新增类型错误(仓库既有错误不属本次范围)。 --- ### 任务 12:统一提交(可选,需用户同意) - [ ] **步骤 1:确认用户同意后提交** ```bash 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 传播覆盖 `dispatchOperation`;PC 入口自带 `@Transactional`。 3. **FORCE-FINISH 的 finishedType**:PC 入口会 set 但 handler 为 todo 不落库,属规格已确认的预期行为。 4. **工作区有未提交改动**:本计划涉及的部分文件已有本地修改(见 git status),实现时在现有基础上叠加,不要回退已有改动。