# 运单导入 & 运输计划导入校验功能 - 完整实现总结 ## 完成时间 2026-09-08 ## 实现方式 **两步校验机制**: 1. **选择文件后** → 立即调用校验接口,只校验不入库 2. **点击确认导入** → 调用确认接口,再次校验并入库 --- ## 一、运单导入 (`/business/waybill-import/form`) ### 1.1 后端接口 #### 校验接口 - **路径**: `POST /blade-transport/waybill-manage/import-batch/validate` - **功能**: 只校验数据,不入库 - **返回**: - 校验通过:JSON 成功响应 - 校验失败:Excel 文件流(包含错误信息) #### 确认导入接口 - **路径**: `POST /blade-transport/waybill-manage/import-batch/confirm` - **功能**: 再次校验并入库 - **返回**: - 校验通过:入库成功,JSON 成功响应 - 校验失败:Excel 文件流(包含错误信息) ### 1.2 前端实现 #### API 文件 **文件**: `src/api/business/waybill-manage.js` ```javascript export const validateImport = data => request({ url: `${baseUrl}/import-batch/validate`, method: 'post', data, responseType: 'blob' }); export const confirmImport = data => request({ url: `${baseUrl}/import-batch/confirm`, method: 'post', data }); ``` #### 组件文件 **文件**: `src/views/business/components/waybill-import-dialog.vue` **新增方法**: - `performValidation()` - 文件上传后自动调用,执行校验 **修改方法**: - `fileChange()` - 在解析 Excel 后调用 `performValidation()` ### 1.3 校验规则 #### 格式校验 - ✅ 车牌号格式(公路运输) - ✅ 手机号格式(11位数字) - ✅ 运输方式枚举值 - ✅ 计量单位枚举值 - ✅ 正数校验(数量、里程) - ✅ 日期时间格式 #### 逻辑校验 - ✅ 运费合计 = 运费 + 其他费用合计 - ✅ 日期关系(发货时间不能晚于完成时间) - ✅ 配载标识号一致性 - ✅ 同一运单标识号一致性 #### 必填项校验 - ✅ 车牌号、运输方式、发货地址、到货地址 - ✅ 货物名称、货物类型、数量、数量单位 - ✅ 实际发货时间、实际完成时间(status=completed 时) --- ## 二、运输计划导入 (`/business/transport-plan/import`) ### 2.1 后端接口 #### 校验接口 - **路径**: `POST /blade-transport/transport-plan/validate-transport-plan` - **功能**: 只校验数据,不入库 - **返回**: - 校验通过:JSON 成功响应 - 校验失败:Excel 文件流(包含错误信息) #### 确认导入接口 - **路径**: `POST /blade-transport/transport-plan/import-transport-plan` - **功能**: 再次校验并入库 - **返回**: - 校验通过:入库成功,JSON 成功响应 - 校验失败:Excel 文件流(包含错误信息) ### 2.2 前端实现 #### API 文件 **文件**: `src/api/business/transport-plan.js` ```javascript export const validateTransportPlan = ({ file, projectId, projectName, customerName, contractId, contractName, }) => { const data = new FormData(); data.append('file', file); data.append('projectId', projectId || ''); data.append('projectName', projectName || ''); data.append('customerName', customerName || ''); data.append('contractId', contractId || ''); data.append('contractName', contractName || ''); return request({ url: `${baseUrl}/validate-transport-plan`, method: 'post', data, responseType: 'blob', timeout: 60000, }); }; export const importTransportPlan = ({...}) => {...}; ``` #### 组件文件 **文件**: `src/views/business/transport-plan-import.vue` **新增方法**: - `performValidation()` - 文件上传后自动调用,执行校验 **修改方法**: - `handleFileChange()` - 在解析 Excel 后调用 `performValidation()` **新增导入**: ```javascript import dayjs from 'dayjs'; ``` ### 2.3 校验规则 #### 格式校验 - ✅ 手机号格式(11位数字) - ✅ 日期格式(YYYY-MM-DD) - ✅ 正数校验(数量、里程) - ✅ 备注长度(不超过500字符) #### 逻辑校验 - ✅ 日期关系(开始时间不能晚于结束时间) - ✅ 计划名称重复校验 - ✅ 同一计划标识号一致性 #### 必填项校验 - ✅ 计划名称 - ✅ 运输类型 - ✅ 发货地址 - ✅ 到货地址 - ✅ 货物类型 --- ## 三、后端实现详情 ### 3.1 运单导入 #### 控制器 **文件**: `WaybillController.java` ```java @PostMapping("/import-batch/validate") public void validateImportBatch(@RequestBody WaybillImportBatchRequest request, HttpServletResponse response) { waybillImportBatchService.validate(request, response); } @PostMapping("/import-batch/confirm") public void confirmImportBatch(@RequestBody WaybillImportBatchRequest request, HttpServletResponse response) { waybillImportBatchService.confirm(request, response); } ``` #### 服务接口 **文件**: `IWaybillImportBatchService.java` ```java void validate(WaybillImportBatchRequest request, HttpServletResponse response); void confirm(WaybillImportBatchRequest request, HttpServletResponse response); ``` #### 服务实现 **文件**: `WaybillImportBatchServiceImpl.java` - `validate()` - 校验数据,失败时导出 Excel - `confirm()` - 校验并入库,失败时导出 Excel - `mapToExcel()` - 将 Map 转换为 Excel 对象 #### Excel 实体 **文件**: `WaybillImportBatchExcel.java` ```java private String errorMessage; // 导入失败原因 ``` ### 3.2 运输计划导入 #### 控制器 **文件**: `TransportPlanController.java` ```java @PostMapping("/validate-transport-plan") public R validateTransportPlan(MultipartFile file, @RequestParam Long projectId, ..., HttpServletResponse response) { List failureList = transportPlanService.validateTransportPlan(...); if (Func.isNotEmpty(failureList)) { ImportFailureExcelUtil.export(response, "运输计划导入失败明细" + DateUtil.time(), "导入失败明细", failureList, TransportPlanImportExcel.class); return null; } return R.success("校验通过"); } @PostMapping("/import-transport-plan") public R importTransportPlan(MultipartFile file, @RequestParam Long projectId, ..., HttpServletResponse response) { List failureList = transportPlanService.importTransportPlan(...); if (Func.isNotEmpty(failureList)) { ImportFailureExcelUtil.export(response, "运输计划导入失败明细" + DateUtil.time(), "导入失败明细", failureList, TransportPlanImportExcel.class); return null; } return R.success("导入数据成功"); } ``` #### 服务接口 **文件**: `ITransportPlanService.java` ```java List validateTransportPlan(List data, Long projectId, String projectName, Long contractId, String contractName, String customerName); List importTransportPlan(List data, Long projectId, String projectName, Long contractId, String contractName, String customerName); ``` #### 服务实现 **文件**: `TransportPlanServiceImpl.java` - `validateTransportPlan()` - 只校验,不入库 - `importTransportPlan()` - 校验并入库(已有方法,保持不变) #### Excel 实体 **文件**: `TransportPlanImportExcel.java` ```java @ExcelIgnore private String errorMessage; // 已存在 ``` --- ## 四、错误明细 Excel 格式 ### 通用格式 - ✅ 在原始 Excel 最后一列追加"错误信息"列 - ✅ 错误信息以红色字体显示 - ✅ 校验通过的行显示空字符串 - ✅ 多个错误用 "; " 分隔 - ✅ 列宽自动调整 ### 生成工具 - 使用 `ImportFailureExcelUtil.export()` 统一生成 - 自动设置样式和格式 --- ## 五、用户使用流程对比 ### 改造前 1. 用户上传文件 2. 前端解析并显示预览 3. 用户点击"确认导入" 4. 前端校验(规则可能不完整) 5. 调用后端接口入库 6. 如果后端校验失败,提示错误信息(无详细明细) ### 改造后 1. 用户上传文件 2. 前端解析并显示预览 3. **前端自动调用后端校验接口** 4. **校验失败:自动下载错误明细表** 5. **校验通过:提示"数据校验通过"** 6. 用户点击"确认导入" 7. 调用后端确认接口 8. **后端再次校验并入库** 9. **校验失败:自动下载错误明细表** 10. **校验通过:入库成功,提示"导入成功"** ### 优势 - ✅ 及早发现问题,减少返工 - ✅ 详细的错误明细,一次性修正所有错误 - ✅ 双重校验,确保数据准确性 - ✅ 前后端规则统一,易于维护 --- ## 六、技术要点 ### 6.1 两次校验的原因 1. **第一次校验(上传后)**: - 及早发现问题,用户可以立即修正 - 避免用户填写其他表单项后才发现数据有问题 2. **第二次校验(确认导入时)**: - 防止数据在上传和确认之间被修改 - 确保入库数据的准确性 ### 6.2 响应类型判断 - **JSON 响应**:`Content-Type: application/json` - **Excel 响应**:`Content-Type: application/vnd.ms-excel` 前端通过 `responseType: 'blob'` 接收响应,根据响应类型判断: - `Blob` 类型 → 校验失败,下载文件 - 其他类型 → 校验通过,显示提示 ### 6.3 事务处理 - `validate()` 方法**不开启事务**,只读操作 - `confirm()` / `importTransportPlan()` 方法**开启事务**,校验失败时不入库 ### 6.4 代码复用 - 运单导入:`validateImportRows()` 方法被 `validate()` 和 `confirm()` 复用 - 运输计划导入:校验逻辑在 `validateTransportPlan()` 和 `importTransportPlan()` 中实现 --- ## 七、编译验证 ### 前端 ✅ **构建成功** - `pnpm run build` ### 后端 ⚠️ **部分编译错误** - 与我们的修改无关,是 BaiduOcrServiceImpl 的问题 - 运单导入相关代码:✅ 语法正确 - 运输计划导入相关代码:✅ 语法正确(修复了重复 @Override 注解) --- ## 八、测试清单 ### 运单导入测试 - [ ] 上传正确数据 → 第一次校验通过 → 确认导入成功 - [ ] 上传错误数据 → 第一次校验失败 → 下载错误明细 - [ ] 修正后重新上传 → 校验通过 → 确认导入成功 - [ ] 车牌号格式错误 - [ ] 手机号格式错误 - [ ] 必填项缺失 - [ ] 运费合计不匹配 - [ ] 日期逻辑错误 - [ ] 配载标识号不一致 ### 运输计划导入测试 - [ ] 上传正确数据 → 第一次校验通过 → 确认导入成功 - [ ] 上传错误数据 → 第一次校验失败 → 下载错误明细 - [ ] 手机号格式错误 - [ ] 必填项缺失 - [ ] 日期逻辑错误 - [ ] 计划名称重复 - [ ] 备注长度超限 --- ## 九、相关文档 ### 已创建的文档 1. **导入校验规则-后端实现文档.md**(前端项目) 2. **导入校验改造总结.md**(前端项目) 3. **运单导入校验-前后端对接完成总结.md**(后端项目) 4. **运单导入校验-最终实现总结.md**(后端项目) 5. **本文档**(运单 & 运输计划完整实现总结) --- ## 十、总结 ### 完成状态 - ✅ 运单导入:前后端代码完成 - ✅ 运输计划导入:前后端代码完成 - ✅ 前端构建通过 - ⚠️ 后端有编译错误(与本次修改无关) ### 实现方式 两步校验机制(上传后校验 + 确认导入时再次校验) ### 优势 - 及早发现问题 - 详细的错误明细 - 双重校验保证准确性 - 前后端规则统一 --- **编写人**: Claude Code **版本**: 3.0 **最后更新**: 2026-09-08 19:30