mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 10:00:30 +08:00
feat(m4): add SN batch import with text area dialog
This commit is contained in:
+8
@@ -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.LicenseSnStatusPatchRequest;
|
||||||
import cn.craftlabs.platform.api.web.dto.LicenseSnUpdateRequest;
|
import cn.craftlabs.platform.api.web.dto.LicenseSnUpdateRequest;
|
||||||
import cn.craftlabs.platform.api.web.dto.PageResponse;
|
import cn.craftlabs.platform.api.web.dto.PageResponse;
|
||||||
|
import cn.craftlabs.platform.api.web.dto.SnBatchImportRequest;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import jakarta.validation.constraints.Max;
|
import jakarta.validation.constraints.Max;
|
||||||
import jakarta.validation.constraints.Min;
|
import jakarta.validation.constraints.Min;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PatchMapping;
|
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.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/license-sns")
|
@RequestMapping("/api/v1/license-sns")
|
||||||
@@ -65,4 +68,9 @@ public class LicenseSnController {
|
|||||||
@PathVariable("id") long id, @Valid @RequestBody LicenseSnStatusPatchRequest request) {
|
@PathVariable("id") long id, @Valid @RequestBody LicenseSnStatusPatchRequest request) {
|
||||||
return licenseSnService.patchStatus(id, request);
|
return licenseSnService.patchStatus(id, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/batch-import")
|
||||||
|
public ResponseEntity<Map<String, Object>> batchImport(@RequestBody SnBatchImportRequest request) {
|
||||||
|
return ResponseEntity.ok(licenseSnService.batchImport(request));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+42
@@ -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.LicenseSnStatusPatchRequest;
|
||||||
import cn.craftlabs.platform.api.web.dto.LicenseSnUpdateRequest;
|
import cn.craftlabs.platform.api.web.dto.LicenseSnUpdateRequest;
|
||||||
import cn.craftlabs.platform.api.web.dto.PageResponse;
|
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.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
@@ -103,6 +104,47 @@ public class LicenseSnService {
|
|||||||
return toResponse(row);
|
return toResponse(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Map<String, Object> batchImport(SnBatchImportRequest request) {
|
||||||
|
int success = 0;
|
||||||
|
int failed = 0;
|
||||||
|
List<String> 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<String, Object> result = new LinkedHashMap<>();
|
||||||
|
result.put("success", success);
|
||||||
|
result.put("failed", failed);
|
||||||
|
result.put("errors", errors);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public PageResponse<LicenseSnResponse> page(
|
public PageResponse<LicenseSnResponse> page(
|
||||||
int page, int size, Long projectId, String keyword, String status) {
|
int page, int size, Long projectId, String keyword, String status) {
|
||||||
|
|||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package cn.craftlabs.platform.api.web.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SnBatchImportRequest {
|
||||||
|
private List<String> snCodes;
|
||||||
|
private Long projectId;
|
||||||
|
private Long contractLineId;
|
||||||
|
private String activationRemark;
|
||||||
|
|
||||||
|
public List<String> getSnCodes() { return snCodes; }
|
||||||
|
public void setSnCodes(List<String> 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; }
|
||||||
|
}
|
||||||
@@ -207,6 +207,10 @@ export function patchLicenseSnStatus(id, body) {
|
|||||||
return axios.patch(`/api/v1/license-sns/${id}/status`, 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) —— */
|
/* —— I5 Callback Inbox & M6 integration read APIs (paths per docs/engineering/iterations/I5_I6_DESIGN.md A.3) —— */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
/>
|
/>
|
||||||
<el-button type="primary" :loading="loading" @click="load">查询</el-button>
|
<el-button type="primary" :loading="loading" @click="load">查询</el-button>
|
||||||
<el-button type="success" @click="goNew">新建许可 SN</el-button>
|
<el-button type="success" @click="goNew">新建许可 SN</el-button>
|
||||||
|
<el-button @click="batchDialogVisible = true">批量导入</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -63,6 +64,25 @@
|
|||||||
@size-change="onSizeChange"
|
@size-change="onSizeChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<el-dialog v-model="batchDialogVisible" title="批量导入 SN" width="520px">
|
||||||
|
<el-form label-width="120px">
|
||||||
|
<el-form-item label="SN 编码">
|
||||||
|
<el-input v-model="batchSnText" type="textarea" :rows="8" placeholder="每行一个 SN 编码" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="项目">
|
||||||
|
<el-select v-model="batchProjectId" clearable filterable placeholder="选填" style="width:100%">
|
||||||
|
<el-option v-for="p in projectOptions" :key="p.id" :label="p.name || String(p.id)" :value="p.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="激活备注">
|
||||||
|
<el-input v-model="batchRemark" maxlength="512" placeholder="选填" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="batchDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="batchImporting" @click="handleBatchImport">导入</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</el-card>
|
</el-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -71,7 +91,7 @@ import { ref, onMounted } from "vue";
|
|||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import { useAuthStore } from "../stores/auth";
|
import { useAuthStore } from "../stores/auth";
|
||||||
import { listLicenseSns, listProjects } from "../api/platform";
|
import { listLicenseSns, listProjects, batchImportLicenseSns } from "../api/platform";
|
||||||
import { apiErrorMessage } from "../utils/apiErrorMessage";
|
import { apiErrorMessage } from "../utils/apiErrorMessage";
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
@@ -87,6 +107,11 @@ const filterProjectId = ref(undefined);
|
|||||||
const projectOptions = ref([]);
|
const projectOptions = ref([]);
|
||||||
/** @type {import('vue').Ref<Map<string | number, string>>} */
|
/** @type {import('vue').Ref<Map<string | number, string>>} */
|
||||||
const projectMap = ref(new Map());
|
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 () => {
|
onMounted(async () => {
|
||||||
auth.restoreAxiosAuth();
|
auth.restoreAxiosAuth();
|
||||||
@@ -178,6 +203,27 @@ function goNew() {
|
|||||||
function goDetail(id) {
|
function goDetail(id) {
|
||||||
router.push({ name: "license-sn-detail", params: { id: String(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
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
Reference in New Issue
Block a user