diff --git a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/license/LicenseSnController.java b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/license/LicenseSnController.java index 1954809..d643463 100644 --- a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/license/LicenseSnController.java +++ b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/license/LicenseSnController.java @@ -6,10 +6,12 @@ import cn.craftlabs.platform.api.web.dto.LicenseSnResponse; import cn.craftlabs.platform.api.web.dto.LicenseSnStatusPatchRequest; import cn.craftlabs.platform.api.web.dto.LicenseSnUpdateRequest; import cn.craftlabs.platform.api.web.dto.PageResponse; +import cn.craftlabs.platform.api.web.dto.SnBatchImportRequest; import jakarta.validation.Valid; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -21,6 +23,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import java.util.Map; @RestController @RequestMapping("/api/v1/license-sns") @@ -65,4 +68,9 @@ public class LicenseSnController { @PathVariable("id") long id, @Valid @RequestBody LicenseSnStatusPatchRequest request) { return licenseSnService.patchStatus(id, request); } + + @PostMapping("/batch-import") + public ResponseEntity> batchImport(@RequestBody SnBatchImportRequest request) { + return ResponseEntity.ok(licenseSnService.batchImport(request)); + } } diff --git a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/LicenseSnService.java b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/LicenseSnService.java index 5a5e8b5..bfe279d 100644 --- a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/LicenseSnService.java +++ b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/LicenseSnService.java @@ -15,6 +15,7 @@ import cn.craftlabs.platform.api.web.dto.LicenseSnResponse; import cn.craftlabs.platform.api.web.dto.LicenseSnStatusPatchRequest; import cn.craftlabs.platform.api.web.dto.LicenseSnUpdateRequest; import cn.craftlabs.platform.api.web.dto.PageResponse; +import cn.craftlabs.platform.api.web.dto.SnBatchImportRequest; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -103,6 +104,47 @@ public class LicenseSnService { return toResponse(row); } + @Transactional + public Map batchImport(SnBatchImportRequest request) { + int success = 0; + int failed = 0; + List errors = new java.util.ArrayList<>(); + + for (String snCode : request.getSnCodes()) { + try { + if (snCode == null || snCode.trim().isEmpty()) { + failed++; + continue; + } + var existing = licenseSnMapper.selectOne( + Wrappers.lambdaQuery(PlatformLicenseSn.class) + .eq(PlatformLicenseSn::getSnCode, snCode.trim())); + if (existing != null) { + failed++; + errors.add("SN " + snCode + " 已存在"); + continue; + } + PlatformLicenseSn sn = new PlatformLicenseSn(); + sn.setSnCode(snCode.trim()); + sn.setProjectId(request.getProjectId()); + sn.setContractLineId(request.getContractLineId()); + sn.setActivationRemark(request.getActivationRemark()); + sn.setStatus(LicenseSnStatus.REGISTERED.name()); + licenseSnMapper.insert(sn); + success++; + } catch (Exception e) { + failed++; + errors.add("SN " + snCode + ": " + e.getMessage()); + } + } + + Map result = new LinkedHashMap<>(); + result.put("success", success); + result.put("failed", failed); + result.put("errors", errors); + return result; + } + @Transactional(readOnly = true) public PageResponse page( int page, int size, Long projectId, String keyword, String status) { diff --git a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/SnBatchImportRequest.java b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/SnBatchImportRequest.java new file mode 100644 index 0000000..f043c75 --- /dev/null +++ b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/web/dto/SnBatchImportRequest.java @@ -0,0 +1,19 @@ +package cn.craftlabs.platform.api.web.dto; + +import java.util.List; + +public class SnBatchImportRequest { + private List snCodes; + private Long projectId; + private Long contractLineId; + private String activationRemark; + + public List getSnCodes() { return snCodes; } + public void setSnCodes(List snCodes) { this.snCodes = snCodes; } + public Long getProjectId() { return projectId; } + public void setProjectId(Long projectId) { this.projectId = projectId; } + public Long getContractLineId() { return contractLineId; } + public void setContractLineId(Long contractLineId) { this.contractLineId = contractLineId; } + public String getActivationRemark() { return activationRemark; } + public void setActivationRemark(String activationRemark) { this.activationRemark = activationRemark; } +} diff --git a/web/delivery-platform-ui/src/api/platform.js b/web/delivery-platform-ui/src/api/platform.js index 1947a66..791dcba 100644 --- a/web/delivery-platform-ui/src/api/platform.js +++ b/web/delivery-platform-ui/src/api/platform.js @@ -207,6 +207,10 @@ export function patchLicenseSnStatus(id, body) { return axios.patch(`/api/v1/license-sns/${id}/status`, body); } +export function batchImportLicenseSns(body) { + return axios.post('/api/v1/license-sns/batch-import', body); +} + /* —— I5 Callback Inbox & M6 integration read APIs (paths per docs/engineering/iterations/I5_I6_DESIGN.md A.3) —— */ /** diff --git a/web/delivery-platform-ui/src/views/LicenseSnListView.vue b/web/delivery-platform-ui/src/views/LicenseSnListView.vue index 080eff8..63b98a2 100644 --- a/web/delivery-platform-ui/src/views/LicenseSnListView.vue +++ b/web/delivery-platform-ui/src/views/LicenseSnListView.vue @@ -22,6 +22,7 @@ /> 查询 新建许可 SN + 批量导入 @@ -63,6 +64,25 @@ @size-change="onSizeChange" /> + + + + + + + + + + + + + + + + @@ -71,7 +91,7 @@ import { ref, onMounted } from "vue"; import { useRouter } from "vue-router"; import { ElMessage } from "element-plus"; import { useAuthStore } from "../stores/auth"; -import { listLicenseSns, listProjects } from "../api/platform"; +import { listLicenseSns, listProjects, batchImportLicenseSns } from "../api/platform"; import { apiErrorMessage } from "../utils/apiErrorMessage"; const auth = useAuthStore(); @@ -87,6 +107,11 @@ const filterProjectId = ref(undefined); const projectOptions = ref([]); /** @type {import('vue').Ref>} */ const projectMap = ref(new Map()); +const batchDialogVisible = ref(false); +const batchSnText = ref(''); +const batchProjectId = ref(undefined); +const batchRemark = ref(''); +const batchImporting = ref(false); onMounted(async () => { auth.restoreAxiosAuth(); @@ -178,6 +203,27 @@ function goNew() { function goDetail(id) { router.push({ name: "license-sn-detail", params: { id: String(id) } }); } + +async function handleBatchImport() { + const codes = batchSnText.value.split('\n').map(s => s.trim()).filter(Boolean) + if (codes.length === 0) { ElMessage.warning('请输入 SN 编码'); return } + batchImporting.value = true + try { + const { data } = await batchImportLicenseSns({ + snCodes: codes, + projectId: batchProjectId.value || undefined, + activationRemark: batchRemark.value || undefined, + }) + ElMessage.success(`导入完成:成功 ${data.success} 条,失败 ${data.failed} 条`) + batchDialogVisible.value = false + batchSnText.value = '' + load() + } catch (e) { + ElMessage.error(apiErrorMessage(e, '批量导入失败')) + } finally { + batchImporting.value = false + } +}