Files
huachuang/docs/superpowers/plans/2026-08-17-sub-package-relation-batch-create.md
2026-08-18 18:44:13 +08:00

28 KiB
Raw Blame History

子卷包装关系批量新增实现计划

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

目标: 为子卷包装关系提供可在卡片/表格间自由切换的批量新增弹窗,并由后端按“木箱码 + 木箱类型”分组校验容量、隔离保存和逐行返回失败原因。

架构: 前端批量弹窗以单一 rows 数组驱动两种视图,通过稳定 clientKey 映射后端错误;木箱类型复用现有选择弹窗,状态复用 sub_package_status 字典。后端新增独立批量 VO 和接口,由主 Service 完成逐行校验、分组和容量判断,再调用独立 REQUIRES_NEW 保存服务逐组写入,保证失败组不影响合法组。

技术栈: Java 17、Spring Boot、Jakarta Validation、MyBatis-Plus、JUnit 5、Mockito、Vue 3、TypeScript、Vben Modal、Antdv Next、Vitest。


文件结构

后端新增

  • nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/vo/SubPackageRelationBatchCreateReqVO.java:批量请求容器。
  • nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/vo/SubPackageRelationBatchCreateItemReqVO.java:单条批量录入数据。
  • nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/vo/SubPackageRelationBatchCreateRespVO.java:成功/失败数量和失败行结构。
  • nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationBatchSaveService.java:每个业务分组的独立事务写入器。
  • nl-module-lms/nl-module-lms-server/src/test/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationServiceImplTest.java:分组、容量、部分成功和失败映射单元测试。
  • nl-module-lms/nl-module-lms-server/src/test/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationBatchSaveServiceTest.java:分组批量写入委托测试。

后端修改

  • nl-module-lms/nl-module-lms-server/pom.xml:补充 Mockito Inline 测试依赖。
  • nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/SubPackageRelationController.java:暴露 POST /batch-create
  • nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationService.java:声明批量创建方法。
  • nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationServiceImpl.java:逐行校验、类型查询、分组、容量校验、逐组保存和响应组装。
  • nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/subpackagerelation/SubPackageRelationMapper.java:统计相同木箱码和木箱类型已有记录。
  • nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/boxtype/BoxTypeMapper.java:批量查询木箱类型。

前端新增

  • nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.ts:批量行创建、复制、校验和失败结果归并的纯函数。
  • nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.test.ts:纯函数单元测试。
  • nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form.vue:批量新增弹窗、双视图和木箱类型选择组件接入。

前端修改

  • nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/packages/constants/src/dict-enum.ts:增加 SUB_PACKAGE_STATUS: 'sub_package_status'
  • nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/lms/subpackagerelation/index.ts:增加批量请求/响应类型和 API 方法。
  • nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/index.vue:增加批量弹窗和工具栏入口。

任务 1定义后端批量契约和查询能力

文件:

  • 创建:nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/vo/SubPackageRelationBatchCreateReqVO.java

  • 创建:nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/vo/SubPackageRelationBatchCreateItemReqVO.java

  • 创建:nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/vo/SubPackageRelationBatchCreateRespVO.java

  • 修改:nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/subpackagerelation/SubPackageRelationMapper.java

  • 修改:nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/boxtype/BoxTypeMapper.java

  • 步骤 1创建独立批量请求实体

请求容器只对列表本身做 Jakarta Validation行字段由 Service 手动校验,以便返回逐行错误而不是整次请求直接返回 400

@Data
@Schema(description = "管理后台 - 子卷包装关系批量新增 Request VO")
public class SubPackageRelationBatchCreateReqVO {

    @NotEmpty(message = "批量新增数据不能为空")
    @Schema(description = "批量新增数据", requiredMode = Schema.RequiredMode.REQUIRED)
    private List<SubPackageRelationBatchCreateItemReqVO> items;
}

单条实体字段固定为:

@Data
@Schema(description = "管理后台 - 子卷包装关系批量新增单条数据")
public class SubPackageRelationBatchCreateItemReqVO {
    private String clientKey;
    private String packageBoxSn;
    private String boxType;
    private String boxLength;
    private String boxWidth;
    private String boxHigh;
    private String qualityGuaranPeriod;
    private String dateOfFgInbound;
    private String containerName;
    private String status;
}
  • 步骤 2创建结构化批量响应实体
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "管理后台 - 子卷包装关系批量新增 Response VO")
public class SubPackageRelationBatchCreateRespVO {
    private Integer successCount;
    private Integer failureCount;
    private List<Failure> failures;

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Failure {
        private String clientKey;
        private Integer rowIndex;
        private String errorCode;
        private String message;
    }
}
  • 步骤 3增加 Mapper 查询方法

BoxTypeMapper 增加:

default List<BoxTypeDO> selectListByBoxTypes(Collection<String> boxTypes) {
    return selectList(BoxTypeDO::getBoxType, boxTypes);
}

SubPackageRelationMapper 增加:

default Long selectCountByPackageBoxSnAndBoxType(String packageBoxSn, String boxType) {
    return selectCount(new LambdaQueryWrapper<SubPackageRelationDO>()
            .eq(SubPackageRelationDO::getPackageBoxSn, packageBoxSn)
            .eq(SubPackageRelationDO::getBoxType, boxType));
}
  • 步骤 4编译后端契约

运行:

mvn -pl nl-module-lms/nl-module-lms-server -am -DskipTests compile

预期:BUILD SUCCESS,新 VO 和 Mapper 默认方法编译通过。

  • 步骤 5提交后端契约
git add nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/vo nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/boxtype/BoxTypeMapper.java nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/dal/mysql/subpackagerelation/SubPackageRelationMapper.java
git commit -m "feat: 定义子卷包装关系批量新增契约"

任务 2测试并实现分组事务写入器

文件:

  • 修改:nl-module-lms/nl-module-lms-server/pom.xml

  • 创建:nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationBatchSaveService.java

  • 创建:nl-module-lms/nl-module-lms-server/src/test/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationBatchSaveServiceTest.java

  • 步骤 1增加 Mockito Inline 测试依赖

pom.xml 的测试依赖区增加:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <scope>test</scope>
</dependency>
  • 步骤 2编写失败测试
@ExtendWith(MockitoExtension.class)
class SubPackageRelationBatchSaveServiceTest {

    @Mock
    private SubPackageRelationMapper subPackageRelationMapper;

    @InjectMocks
    private SubPackageRelationBatchSaveService service;

    @Test
    void shouldInsertWholeGroup() {
        List<SubPackageRelationDO> group = List.of(
                SubPackageRelationDO.builder().packageBoxSn("BX001").boxType("A").build(),
                SubPackageRelationDO.builder().packageBoxSn("BX001").boxType("A").build());

        service.saveGroup(group);

        verify(subPackageRelationMapper).insertBatch(group);
    }
}
  • 步骤 3运行测试确认失败

运行:

mvn -pl nl-module-lms/nl-module-lms-server -Dtest=SubPackageRelationBatchSaveServiceTest test

预期FAIL提示 SubPackageRelationBatchSaveService 不存在。

  • 步骤 4实现最小独立事务写入器
@Service
public class SubPackageRelationBatchSaveService {

    @Resource
    private SubPackageRelationMapper subPackageRelationMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void saveGroup(List<SubPackageRelationDO> group) {
        subPackageRelationMapper.insertBatch(group);
    }
}
  • 步骤 5运行测试确认通过

运行同一步骤 3。预期测试 PASS。

  • 步骤 6提交事务写入器
git add nl-module-lms/nl-module-lms-server/pom.xml nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationBatchSaveService.java nl-module-lms/nl-module-lms-server/src/test/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationBatchSaveServiceTest.java
git commit -m "feat: 增加包装关系分组事务写入器"

任务 3测试并实现批量业务编排

文件:

  • 创建:nl-module-lms/nl-module-lms-server/src/test/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationServiceImplTest.java

  • 修改:nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationService.java

  • 修改:nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationServiceImpl.java

  • 步骤 1编写容量刚好满足和超限整组失败测试

使用 MockitoExtension mock SubPackageRelationMapperBoxTypeMapperSubPackageRelationBatchSaveService。构造两组数据:

private SubPackageRelationBatchCreateItemReqVO item(
        String clientKey, String packageBoxSn, String boxType, String containerName) {
    SubPackageRelationBatchCreateItemReqVO item = new SubPackageRelationBatchCreateItemReqVO();
    item.setClientKey(clientKey);
    item.setPackageBoxSn(packageBoxSn);
    item.setBoxType(boxType);
    item.setBoxLength("1200");
    item.setBoxWidth("1000");
    item.setBoxHigh("900");
    item.setQualityGuaranPeriod("180");
    item.setDateOfFgInbound("2026-08-17");
    item.setContainerName(containerName);
    item.setStatus("0");
    return item;
}

核心断言:

when(boxTypeMapper.selectListByBoxTypes(Set.of("A")))
        .thenReturn(List.of(BoxTypeDO.builder().boxType("A").maxNum(3).build()));
when(subPackageRelationMapper.selectCountByPackageBoxSnAndBoxType("BX001", "A"))
        .thenReturn(1L);

SubPackageRelationBatchCreateRespVO result = service.batchCreateSubPackageRelation(req(
        item("k1", "BX001", "A", "R1"),
        item("k2", "BX001", "A", "R2"))));

assertEquals(2, result.getSuccessCount());
assertEquals(0, result.getFailureCount());
verify(batchSaveService).saveGroup(argThat(group -> group.size() == 2));

将已有数量改为 2L 后断言:成功数为 0、失败数为 2、errorCodeBOX_CAPACITY_EXCEEDED、两个 clientKey 都返回,且 batchSaveService 未被调用。

  • 步骤 2编写部分成功测试

构造 BX001/A 超限、BX002/A 合法两个分组,断言:

assertEquals(1, result.getSuccessCount());
assertEquals(2, result.getFailureCount());
assertEquals(Set.of("k1", "k2"), result.getFailures().stream()
        .map(SubPackageRelationBatchCreateRespVO.Failure::getClientKey)
        .collect(Collectors.toSet()));
verify(batchSaveService, times(1)).saveGroup(argThat(group ->
        group.size() == 1 && "BX002".equals(group.get(0).getPackageBoxSn())));
  • 步骤 3编写木箱类型不存在、必填失败和保存异常测试

分别断言:

  • 类型不存在返回 BOX_TYPE_NOT_FOUND,对应分组不保存。

  • packageBoxSn 返回 REQUIRED_FIELD_MISSINGrowIndex 为请求中的一基行号。

  • batchSaveService.saveGroup 对一个合法分组抛出异常时,该组返回 GROUP_SAVE_FAILED,后续合法组仍调用保存器并计入成功数。

  • 步骤 4运行测试确认失败

运行:

mvn -pl nl-module-lms/nl-module-lms-server -Dtest=SubPackageRelationServiceImplTest test

预期FAIL提示批量方法和依赖尚不存在。

  • 步骤 5声明 Service 方法并注入依赖

SubPackageRelationService 增加:

SubPackageRelationBatchCreateRespVO batchCreateSubPackageRelation(
        SubPackageRelationBatchCreateReqVO reqVO);

SubPackageRelationServiceImpl 注入 BoxTypeMapperSubPackageRelationBatchSaveService

  • 步骤 6实现逐行校验、分组与容量判断

实现顺序必须是:

  1. 遍历请求项,按原始下标生成一基 rowIndex
  2. 校验 clientKeypackageBoxSnboxTypequalityGuaranPerioddateOfFgInboundcontainerNamestatus 非空;失败项加入 failures,不参与分组。
  3. 批量查询去重后的木箱类型并建立 Map<String, BoxTypeDO>
  4. 按不可变分组键 packageBoxSn + "\u0000" + boxType 对合法项分组,同时保留每项的 clientKeyrowIndex
  5. 类型不存在时整组追加 BOX_TYPE_NOT_FOUND
  6. 查询相同木箱码和类型的数据库已有数量;当 existing + group.size() > maxNum 时整组追加 BOX_CAPACITY_EXCEEDED
  7. 将合法项手动映射为 SubPackageRelationDO,只设置批量请求允许的业务字段。

不得使用 BeanUtils.copyProperties;使用显式 setter 或 builder 映射字段。

  • 步骤 7实现逐组隔离保存和响应汇总

逐个合法分组调用 batchSaveService.saveGroup(group)。成功时累加该组条数;捕获运行时异常时,为该组所有请求项追加 GROUP_SAVE_FAILED,然后继续处理下一组。

最终返回:

return SubPackageRelationBatchCreateRespVO.builder()
        .successCount(successCount)
        .failureCount(failures.size())
        .failures(failures)
        .build();
  • 步骤 8运行 Service 测试确认通过

运行同一步骤 4。预期全部测试 PASS。

  • 步骤 9提交批量业务逻辑
git add nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/service/subpackagerelation nl-module-lms/nl-module-lms-server/src/test/java/cn/code/nl/module/lms/service/subpackagerelation/SubPackageRelationServiceImplTest.java
git commit -m "feat: 实现包装关系分组批量保存"

任务 4暴露后端批量创建接口

文件:

  • 修改:nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/SubPackageRelationController.java

  • 步骤 1增加 Controller 方法

@PostMapping("/batch-create")
@Operation(summary = "批量创建子卷包装关系")
@PreAuthorize("@ss.hasPermission('lms:sub-package-relation:create')")
public CommonResult<SubPackageRelationBatchCreateRespVO> batchCreateSubPackageRelation(
        @Valid @RequestBody SubPackageRelationBatchCreateReqVO reqVO) {
    return success(subPackageRelationService.batchCreateSubPackageRelation(reqVO));
}
  • 步骤 2运行 LMS Service 测试和编译
mvn -pl nl-module-lms/nl-module-lms-server -Dtest=SubPackageRelationServiceImplTest,SubPackageRelationBatchSaveServiceTest test
mvn -pl nl-module-lms/nl-module-lms-server -am -DskipTests compile

预期:测试全部 PASS编译 BUILD SUCCESS

  • 步骤 3提交接口
git add nl-module-lms/nl-module-lms-server/src/main/java/cn/code/nl/module/lms/controller/admin/subpackagerelation/SubPackageRelationController.java
git commit -m "feat: 暴露包装关系批量新增接口"

任务 5定义前端字典、类型和 API

文件:

  • 修改:nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/packages/constants/src/dict-enum.ts

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

  • 步骤 1增加 LMS 状态字典常量

LMS_DICT 中增加:

SUB_PACKAGE_STATUS: 'sub_package_status', // 子卷包装关系状态
  • 步骤 2增加批量请求和响应类型
export interface BatchCreateItem {
  clientKey: string;
  packageBoxSn: string;
  boxType: string;
  boxLength?: string;
  boxWidth?: string;
  boxHigh?: string;
  qualityGuaranPeriod: string;
  dateOfFgInbound: string;
  containerName: string;
  status: string;
}

export interface BatchCreateFailure {
  clientKey: string;
  rowIndex: number;
  errorCode: string;
  message: string;
}

export interface BatchCreateResult {
  successCount: number;
  failureCount: number;
  failures: BatchCreateFailure[];
}
  • 步骤 3增加批量 API 方法
export function batchCreateSubPackageRelation(
  items: LmsSubPackageRelationApi.BatchCreateItem[],
) {
  return requestClient.post<LmsSubPackageRelationApi.BatchCreateResult>(
    '/lms/sub-package-relation/batch-create',
    { items },
  );
}
  • 步骤 4运行目标应用类型检查并记录基线
pnpm -F @vben/web-antdv-next typecheck

预期PASS若现有工程基线报错保存完整输出后续验证只允许新增文件不增加错误。

  • 步骤 5提交前端契约
git add nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/packages/constants/src/dict-enum.ts nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/lms/subpackagerelation/index.ts
git commit -m "feat: 增加包装关系批量新增前端契约"

任务 6测试并实现批量行模型

文件:

  • 创建:nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.ts

  • 创建:nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.test.ts

  • 步骤 1编写失败的纯函数测试

测试必须覆盖:

describe('batch form model', () => {
  it('creates a blank row with status 0 and a stable key', () => {
    const row = createBatchRow();
    expect(row.status).toBe('0');
    expect(row.clientKey).toBeTruthy();
  });

  it('copies reusable values and clears unique identifiers', () => {
    const copied = copyBatchRow(sourceRow);
    expect(copied.boxType).toBe(sourceRow.boxType);
    expect(copied.boxLength).toBe(sourceRow.boxLength);
    expect(copied.packageBoxSn).toBe('');
    expect(copied.containerName).toBe('');
    expect(copied.clientKey).not.toBe(sourceRow.clientKey);
  });

  it('returns field errors for every incomplete row', () => {
    const errors = validateBatchRows([createBatchRow()]);
    expect(errors.get(0)).toEqual(expect.objectContaining({
      packageBoxSn: expect.any(String),
      boxType: expect.any(String),
      containerName: expect.any(String),
    }));
  });

  it('keeps only backend failure rows and maps messages by clientKey', () => {
    const result = applyBatchResult(rows, {
      successCount: 1,
      failureCount: 1,
      failures: [{ clientKey: rows[1]!.clientKey, rowIndex: 2, errorCode: 'BOX_CAPACITY_EXCEEDED', message: '容量超限' }],
    });
    expect(result.rows).toHaveLength(1);
    expect(result.rows[0]!.clientKey).toBe(rows[1]!.clientKey);
    expect(result.rowErrors.get(rows[1]!.clientKey)).toBe('容量超限');
  });
});
  • 步骤 2运行测试确认失败

在前端仓库根目录运行:

pnpm exec vitest run apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.test.ts

预期FAIL提示模型模块或导出函数不存在。

  • 步骤 3实现最小批量行模型

导出以下稳定类型和函数:

export interface BatchFormRow extends LmsSubPackageRelationApi.BatchCreateItem {}
export type BatchFieldErrors = Partial<Record<keyof BatchFormRow, string>>;

export function createBatchRow(): BatchFormRow;
export function copyBatchRow(source: BatchFormRow): BatchFormRow;
export function validateBatchRows(rows: BatchFormRow[]): Map<number, BatchFieldErrors>;
export function applyBatchResult(
  rows: BatchFormRow[],
  result: LmsSubPackageRelationApi.BatchCreateResult,
): { rowErrors: Map<string, string>; rows: BatchFormRow[] };

clientKey 使用 crypto.randomUUID();如果运行环境不可用,则使用时间戳与自增序号组合的模块内 fallback。必填错误信息使用明确中文字段名。

  • 步骤 4运行测试确认通过

运行同一步骤 2。预期测试 PASS。

  • 步骤 5提交批量行模型
git add nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.ts nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.test.ts
git commit -m "feat: 增加包装关系批量录入模型"

任务 7实现卡片/表格自由切换弹窗

文件:

  • 创建:nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form.vue

  • 步骤 1建立弹窗状态和木箱类型选择接入

组件状态固定为:

const emit = defineEmits<{ success: [] }>();
const rows = ref<BatchFormRow[]>([createBatchRow()]);
const viewMode = ref<'card' | 'table'>('table');
const activeClientKey = ref<string>();
const fieldErrors = ref(new Map<number, BatchFieldErrors>());
const rowErrors = ref(new Map<string, string>());

const [BoxTypeSelectModal, boxTypeSelectModalApi] = useVbenModal({
  connectedComponent: BoxTypeSelectModalComponent,
});

点击任一行木箱类型时设置 activeClientKey 并以 { multiple: false } 打开选择弹窗;select 回调按 clientKey 精确回填 boxTypeboxLengthboxWidthboxHigh

  • 步骤 2实现行操作和双视图共享数据

新增、复制、删除、清空都只修改 rows。删除最后一条或清空时恢复 [createBatchRow()]。视图切换只修改 viewMode

卡片视图按记录渲染两栏表单;表格视图使用原生 table 或 Antdv Next Table 的横向可编辑布局。两种视图必须包含相同字段和相同 v-model 数据源,尺寸字段只读。

  • 步骤 3接入状态字典和日期控件
const statusOptions = getDictOptions(DICT_TYPE.SUB_PACKAGE_STATUS);

状态使用 Select,新记录默认 '0'。入库日期使用日期控件并在请求前格式化为 YYYY-MM-DD;不得把 Dayjs 对象传给后端。

  • 步骤 4实现提交和部分成功处理

确认按钮逻辑:

const validation = validateBatchRows(rows.value);
if (validation.size > 0) {
  fieldErrors.value = validation;
  message.warning('请检查必填项');
  return;
}

const result = await batchCreateSubPackageRelation(rows.value);
const applied = applyBatchResult(rows.value, result);
if (result.successCount > 0) emit('success');
if (result.failureCount === 0) {
  await modalApi.close();
  message.success(`成功新增 ${result.successCount} 条子卷包装关系`);
  return;
}
rows.value = applied.rows;
rowErrors.value = applied.rowErrors;
message.warning(`成功 ${result.successCount} 条,失败 ${result.failureCount} 条`);

finally 中解锁弹窗。关闭弹窗时重置所有状态,防止下次打开残留数据和错误。

  • 步骤 5执行组件静态检查
pnpm exec eslint apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form.vue apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.ts
pnpm -F @vben/web-antdv-next typecheck

预期:无新增 ESLint 或 TypeScript 错误。

  • 步骤 6提交批量弹窗
git add nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form.vue
git commit -m "feat: 实现包装关系批量新增弹窗"

任务 8接入列表入口并完成联调验证

文件:

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

  • 步骤 1接入批量弹窗

import BatchForm from './modules/batch-form.vue';

const [BatchFormModal, batchFormModalApi] = useVbenModal({
  connectedComponent: BatchForm,
  destroyOnClose: true,
});

function handleBatchCreate() {
  batchFormModalApi.open();
}

模板中增加:

<BatchFormModal @success="handleRefresh" />
  • 步骤 2增加工具栏按钮

在普通新增按钮后增加:

{
  label: '批量新增',
  type: 'primary',
  icon: ACTION_ICON.ADD,
  auth: ['lms:sub-package-relation:create'],
  onClick: handleBatchCreate,
},
  • 步骤 3运行后端定向测试和编译
mvn -pl nl-module-lms/nl-module-lms-server -Dtest=SubPackageRelationServiceImplTest,SubPackageRelationBatchSaveServiceTest test
mvn -pl nl-module-lms/nl-module-lms-server -am -DskipTests compile

预期:测试 PASS编译 BUILD SUCCESS

  • 步骤 4运行前端测试和检查
pnpm exec vitest run apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.test.ts
pnpm exec eslint apps/web-antdv-next/src/views/lms/subpackagerelation/index.vue apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form.vue apps/web-antdv-next/src/views/lms/subpackagerelation/modules/batch-form-model.ts
pnpm -F @vben/web-antdv-next typecheck

预期:测试 PASS目标文件静态检查通过类型检查无新增错误。

  • 步骤 5手工联调验收

按以下顺序检查:

  1. 列表页能打开批量弹窗。
  2. 新增两行,在表格输入后切到卡片,数据保持一致。
  3. 选择木箱类型,确认回填目标行和尺寸。
  4. 确认状态选项来自 sub_package_status,新增行默认 0
  5. 提交一个合法组,弹窗关闭且列表刷新。
  6. 提交一个超限组和一个合法组,合法组保存,超限组保留并逐行显示容量错误。
  7. 修改失败行后再次提交,成功后弹窗关闭。
  • 步骤 6提交列表接入
git add nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/lms/subpackagerelation/index.vue
git commit -m "feat: 接入包装关系批量新增入口"

任务 9最终一致性检查

文件:

  • 检查本计划列出的全部新增和修改文件。

  • 步骤 1检查工作区变更范围

git status --short
git diff --check
git diff --stat

预期:没有空白错误;不修改用户已有的配置、原型和其他计划文件。

  • 步骤 2核对规格覆盖

逐项确认:独立接口、独立 VO、原型字段范围、木箱类型弹窗、尺寸回填、状态字典默认值、双视图共享数据、容量统计包含数据库已有数量、超限整组失败、合法组独立保存、逐行错误展示全部已实现。

  • 步骤 3记录验证证据

交付说明必须列出实际运行的每条命令、退出码和关键结果;如果检查被现有依赖或工具链阻塞,明确给出原始错误摘要,不得声称对应检查通过。