总表更新
This commit is contained in:
@@ -75,6 +75,23 @@ public class DedupeTotalDataController {
|
|||||||
return ApiResponse.success(dedupeTotalDataService.getImportProgress(importId));
|
return ApiResponse.success(dedupeTotalDataService.getImportProgress(importId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/delete-import")
|
||||||
|
@Operation(summary = "删除导入总数据", description = "上传 xlsx 文件,读取 ASIN 列并批量删除已存在的数据去重总数据。")
|
||||||
|
@ApiResponses({
|
||||||
|
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "启动删除成功", content = @Content(schema = @Schema(implementation = DedupeTotalDataImportStartVo.class))),
|
||||||
|
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "文件不合法或缺少 ASIN 列")
|
||||||
|
})
|
||||||
|
public ApiResponse<DedupeTotalDataImportStartVo> deleteImportExcel(
|
||||||
|
@Parameter(description = "xlsx 文件", required = true) @RequestParam("file") MultipartFile file) {
|
||||||
|
return ApiResponse.success("开始删除", dedupeTotalDataService.startDeleteImport(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/delete-import/{importId}")
|
||||||
|
@Operation(summary = "查询删除导入进度", description = "根据删除导入任务 ID 查询当前进度。")
|
||||||
|
public ApiResponse<DedupeTotalDataImportProgressVo> deleteImportProgress(@PathVariable String importId) {
|
||||||
|
return ApiResponse.success(dedupeTotalDataService.getDeleteImportProgress(importId));
|
||||||
|
}
|
||||||
|
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
@Operation(summary = "更新总数据", description = "按 ID 更新一条数据去重总数据。")
|
@Operation(summary = "更新总数据", description = "按 ID 更新一条数据去重总数据。")
|
||||||
@ApiResponses({
|
@ApiResponses({
|
||||||
|
|||||||
@@ -262,25 +262,9 @@ public class DedupeRunService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Integer idColumnIndex = headerMap.get("id");
|
Integer idColumnIndex = headerMap.get("id");
|
||||||
Map<String, Boolean> mainIdHasSubIdMap = new HashMap<>();
|
|
||||||
if (idColumnIndex != null) {
|
if (idColumnIndex != null) {
|
||||||
for (int rowNum = 1; rowNum <= sheet.getLastRowNum(); rowNum++) {
|
// The preliminary global scan for mainIdHasSubIdMap was removed.
|
||||||
Row row = sheet.getRow(rowNum);
|
// We now determine subset existence contextually (per group) during the main loop.
|
||||||
if (row == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String idValue = normalizeCellText(formatter.formatCellValue(row.getCell(idColumnIndex)));
|
|
||||||
String mainId = extractMainId(idValue);
|
|
||||||
if (mainId.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!mainIdHasSubIdMap.containsKey(mainId)) {
|
|
||||||
mainIdHasSubIdMap.put(mainId, false);
|
|
||||||
}
|
|
||||||
if (idValue.matches("\\d+_\\d+")) {
|
|
||||||
mainIdHasSubIdMap.put(mainId, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
int outputRowIndex = 1;
|
int outputRowIndex = 1;
|
||||||
for (int rowNum = 1; rowNum <= sheet.getLastRowNum(); rowNum++) {
|
for (int rowNum = 1; rowNum <= sheet.getLastRowNum(); rowNum++) {
|
||||||
@@ -290,7 +274,7 @@ public class DedupeRunService {
|
|||||||
}
|
}
|
||||||
if (idColumnIndex != null) {
|
if (idColumnIndex != null) {
|
||||||
String idValue = normalizeCellText(formatter.formatCellValue(row.getCell(idColumnIndex)));
|
String idValue = normalizeCellText(formatter.formatCellValue(row.getCell(idColumnIndex)));
|
||||||
if (!shouldKeepId(idValue, keepIntegerIds, keepUnderscoreIds, keepIntegerMainIdsWhenNoSubIds, mainIdHasSubIdMap)) {
|
if (!shouldKeepId(idValue, keepIntegerIds, keepUnderscoreIds, keepIntegerMainIdsWhenNoSubIds, sheet, rowNum, idColumnIndex, formatter)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -342,7 +326,7 @@ public class DedupeRunService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldKeepId(String text, boolean keepIntegerIds, boolean keepUnderscoreIds,
|
private boolean shouldKeepId(String text, boolean keepIntegerIds, boolean keepUnderscoreIds,
|
||||||
boolean keepIntegerMainIdsWhenNoSubIds, Map<String, Boolean> mainIdHasSubIdMap) {
|
boolean keepIntegerMainIdsWhenNoSubIds, Sheet sheet, int currentRowNum, int idColumnIndex, DataFormatter formatter) {
|
||||||
if (text == null || text.isBlank()) {
|
if (text == null || text.isBlank()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -354,7 +338,29 @@ public class DedupeRunService {
|
|||||||
}
|
}
|
||||||
if (keepIntegerMainIdsWhenNoSubIds && text.matches("\\d+")) {
|
if (keepIntegerMainIdsWhenNoSubIds && text.matches("\\d+")) {
|
||||||
String mainId = extractMainId(text);
|
String mainId = extractMainId(text);
|
||||||
return !mainId.isEmpty() && !Boolean.TRUE.equals(mainIdHasSubIdMap.get(mainId));
|
if (mainId.isEmpty()) return false;
|
||||||
|
|
||||||
|
boolean hasSubIdInGroup = false;
|
||||||
|
for (int r = currentRowNum + 1; r <= sheet.getLastRowNum(); r++) {
|
||||||
|
Row nextRow = sheet.getRow(r);
|
||||||
|
if (nextRow == null) continue;
|
||||||
|
Cell cell = nextRow.getCell(idColumnIndex);
|
||||||
|
if (cell == null) continue;
|
||||||
|
String nextIdValue = normalizeCellText(formatter.formatCellValue(cell));
|
||||||
|
if (nextIdValue.isBlank()) continue;
|
||||||
|
|
||||||
|
String nextMainId = extractMainId(nextIdValue);
|
||||||
|
if (nextMainId.equals(mainId)) {
|
||||||
|
if (nextIdValue.matches("\\d+_\\d+")) {
|
||||||
|
hasSubIdInGroup = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Encountered a different main ID. The group for 'mainId' has ended.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !hasSubIdInGroup;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ public class DedupeTotalDataService {
|
|||||||
|
|
||||||
private final DedupeTotalDataMapper dedupeTotalDataMapper;
|
private final DedupeTotalDataMapper dedupeTotalDataMapper;
|
||||||
private final Map<String, DedupeTotalDataImportProgressVo> importProgressMap = new ConcurrentHashMap<>();
|
private final Map<String, DedupeTotalDataImportProgressVo> importProgressMap = new ConcurrentHashMap<>();
|
||||||
|
private final Map<String, DedupeTotalDataImportProgressVo> deleteImportProgressMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public DedupeTotalDataPageVo page(long page, long pageSize, String keyword) {
|
public DedupeTotalDataPageVo page(long page, long pageSize, String keyword) {
|
||||||
long safePage = Math.max(page, 1);
|
long safePage = Math.max(page, 1);
|
||||||
@@ -105,6 +106,62 @@ public class DedupeTotalDataService {
|
|||||||
return progress;
|
return progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DedupeTotalDataImportStartVo startDeleteImport(MultipartFile file) {
|
||||||
|
if (file == null || file.isEmpty()) {
|
||||||
|
throw new BusinessException("请上传 xlsx 文件");
|
||||||
|
}
|
||||||
|
String importId = IdUtil.fastSimpleUUID();
|
||||||
|
DedupeTotalDataImportProgressVo progress = new DedupeTotalDataImportProgressVo();
|
||||||
|
progress.setStatus("pending");
|
||||||
|
progress.setTotalRows(0);
|
||||||
|
progress.setProcessedRows(0);
|
||||||
|
progress.setAsinCount(0);
|
||||||
|
progress.setInsertedCount(0);
|
||||||
|
progress.setSkippedCount(0);
|
||||||
|
deleteImportProgressMap.put(importId, progress);
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] fileBytes = file.getBytes();
|
||||||
|
String filename = file.getOriginalFilename();
|
||||||
|
Thread.ofVirtual().start(() -> runDeleteImportTask(importId, fileBytes, filename));
|
||||||
|
} catch (Exception e) {
|
||||||
|
deleteImportProgressMap.remove(importId);
|
||||||
|
throw new BusinessException("读取上传文件失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
DedupeTotalDataImportStartVo vo = new DedupeTotalDataImportStartVo();
|
||||||
|
vo.setImportId(importId);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DedupeTotalDataImportProgressVo getDeleteImportProgress(String importId) {
|
||||||
|
DedupeTotalDataImportProgressVo progress = deleteImportProgressMap.get(importId);
|
||||||
|
if (progress == null) {
|
||||||
|
throw new BusinessException("删除任务不存在");
|
||||||
|
}
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runDeleteImportTask(String importId, byte[] fileBytes, String filename) {
|
||||||
|
DedupeTotalDataImportProgressVo progress = deleteImportProgressMap.get(importId);
|
||||||
|
if (progress == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
progress.setStatus("running");
|
||||||
|
try (InputStream inputStream = new ByteArrayInputStream(fileBytes)) {
|
||||||
|
DedupeTotalDataImportVo result = deleteFromExcelInternal(inputStream, filename, progress);
|
||||||
|
progress.setTotalRows(result.getTotalRows());
|
||||||
|
progress.setAsinCount(result.getAsinCount());
|
||||||
|
progress.setInsertedCount(result.getInsertedCount());
|
||||||
|
progress.setSkippedCount(result.getSkippedCount());
|
||||||
|
progress.setProcessedRows(result.getTotalRows());
|
||||||
|
progress.setStatus("success");
|
||||||
|
} catch (Exception e) {
|
||||||
|
progress.setStatus("failed");
|
||||||
|
progress.setErrorMessage(e instanceof BusinessException ? e.getMessage() : "删除 Excel 匹配数据失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void runImportTask(String importId, byte[] fileBytes, String filename) {
|
private void runImportTask(String importId, byte[] fileBytes, String filename) {
|
||||||
DedupeTotalDataImportProgressVo progress = importProgressMap.get(importId);
|
DedupeTotalDataImportProgressVo progress = importProgressMap.get(importId);
|
||||||
if (progress == null) {
|
if (progress == null) {
|
||||||
@@ -139,6 +196,20 @@ public class DedupeTotalDataService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public DedupeTotalDataImportVo deleteFromExcel(MultipartFile file) {
|
||||||
|
if (file == null || file.isEmpty()) {
|
||||||
|
throw new BusinessException("请上传 xlsx 文件");
|
||||||
|
}
|
||||||
|
try (InputStream inputStream = file.getInputStream()) {
|
||||||
|
return deleteFromExcelInternal(inputStream, file.getOriginalFilename(), null);
|
||||||
|
} catch (BusinessException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new BusinessException("删除 Excel 匹配数据失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
private DedupeTotalDataImportVo importFromExcelInternal(InputStream inputStream, String filename, DedupeTotalDataImportProgressVo progress) {
|
private DedupeTotalDataImportVo importFromExcelInternal(InputStream inputStream, String filename, DedupeTotalDataImportProgressVo progress) {
|
||||||
String lowerFilename = filename == null ? "" : filename.toLowerCase();
|
String lowerFilename = filename == null ? "" : filename.toLowerCase();
|
||||||
@@ -252,6 +323,112 @@ public class DedupeTotalDataService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
private DedupeTotalDataImportVo deleteFromExcelInternal(InputStream inputStream, String filename, DedupeTotalDataImportProgressVo progress) {
|
||||||
|
String lowerFilename = filename == null ? "" : filename.toLowerCase();
|
||||||
|
if (!(lowerFilename.endsWith(".xlsx") || lowerFilename.endsWith(".xls"))) {
|
||||||
|
throw new BusinessException("仅支持 .xlsx 或 .xls 文件");
|
||||||
|
}
|
||||||
|
|
||||||
|
DataFormatter formatter = new DataFormatter();
|
||||||
|
try (Workbook workbook = org.apache.poi.ss.usermodel.WorkbookFactory.create(inputStream)) {
|
||||||
|
Sheet sheet = workbook.getSheetAt(0);
|
||||||
|
Row headerRow = sheet.getRow(0);
|
||||||
|
if (headerRow == null) {
|
||||||
|
throw new BusinessException("Excel 表头为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Integer> headerMap = new HashMap<>();
|
||||||
|
for (int i = 0; i < headerRow.getLastCellNum(); i++) {
|
||||||
|
Cell cell = headerRow.getCell(i);
|
||||||
|
String value = normalizeExcelText(cell == null ? null : formatter.formatCellValue(cell));
|
||||||
|
if (!value.isBlank() && !headerMap.containsKey(value)) {
|
||||||
|
headerMap.put(value, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer asinIndex = headerMap.get("ASIN");
|
||||||
|
if (asinIndex == null) {
|
||||||
|
throw new BusinessException("缺少 ASIN 列");
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> seenInFile = new HashSet<>();
|
||||||
|
int totalRows = Math.max(sheet.getLastRowNum(), 0);
|
||||||
|
int asinCount = 0;
|
||||||
|
int deletedCount = 0;
|
||||||
|
int skippedCount = 0;
|
||||||
|
if (progress != null) {
|
||||||
|
progress.setTotalRows(totalRows);
|
||||||
|
progress.setProcessedRows(0);
|
||||||
|
progress.setAsinCount(0);
|
||||||
|
progress.setInsertedCount(0);
|
||||||
|
progress.setSkippedCount(0);
|
||||||
|
progress.setErrorMessage(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int rowNum = 1; rowNum <= sheet.getLastRowNum(); rowNum++) {
|
||||||
|
Row row = sheet.getRow(rowNum);
|
||||||
|
if (row == null) {
|
||||||
|
skippedCount++;
|
||||||
|
if (progress != null) {
|
||||||
|
progress.setProcessedRows(rowNum);
|
||||||
|
progress.setAsinCount(asinCount);
|
||||||
|
progress.setInsertedCount(deletedCount);
|
||||||
|
progress.setSkippedCount(skippedCount);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String asin = normalizeExcelText(formatter.formatCellValue(row.getCell(asinIndex)));
|
||||||
|
if (asin.isBlank()) {
|
||||||
|
skippedCount++;
|
||||||
|
if (progress != null) {
|
||||||
|
progress.setProcessedRows(rowNum);
|
||||||
|
progress.setAsinCount(asinCount);
|
||||||
|
progress.setInsertedCount(deletedCount);
|
||||||
|
progress.setSkippedCount(skippedCount);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
asinCount++;
|
||||||
|
String dataValue = normalizeDataValue(asin);
|
||||||
|
if (!seenInFile.add(dataValue)) {
|
||||||
|
skippedCount++;
|
||||||
|
if (progress != null) {
|
||||||
|
progress.setProcessedRows(rowNum);
|
||||||
|
progress.setAsinCount(asinCount);
|
||||||
|
progress.setInsertedCount(deletedCount);
|
||||||
|
progress.setSkippedCount(skippedCount);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int deletedThisRow = deleteByDataValue(dataValue);
|
||||||
|
if (deletedThisRow > 0) {
|
||||||
|
deletedCount += deletedThisRow;
|
||||||
|
} else {
|
||||||
|
skippedCount++;
|
||||||
|
}
|
||||||
|
if (progress != null) {
|
||||||
|
progress.setProcessedRows(rowNum);
|
||||||
|
progress.setAsinCount(asinCount);
|
||||||
|
progress.setInsertedCount(deletedCount);
|
||||||
|
progress.setSkippedCount(skippedCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DedupeTotalDataImportVo vo = new DedupeTotalDataImportVo();
|
||||||
|
vo.setTotalRows(totalRows);
|
||||||
|
vo.setAsinCount(asinCount);
|
||||||
|
vo.setInsertedCount(deletedCount);
|
||||||
|
vo.setSkippedCount(skippedCount);
|
||||||
|
return vo;
|
||||||
|
} catch (BusinessException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new BusinessException("删除 Excel 匹配数据失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public DedupeTotalDataItemVo update(Long id, DedupeTotalDataUpdateRequest request) {
|
public DedupeTotalDataItemVo update(Long id, DedupeTotalDataUpdateRequest request) {
|
||||||
DedupeTotalDataEntity entity = getById(id);
|
DedupeTotalDataEntity entity = getById(id);
|
||||||
@@ -301,6 +478,11 @@ public class DedupeTotalDataService {
|
|||||||
return dedupeTotalDataMapper.selectOne(query) != null;
|
return dedupeTotalDataMapper.selectOne(query) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int deleteByDataValue(String dataValue) {
|
||||||
|
return dedupeTotalDataMapper.delete(new LambdaQueryWrapper<DedupeTotalDataEntity>()
|
||||||
|
.eq(DedupeTotalDataEntity::getDataValue, dataValue));
|
||||||
|
}
|
||||||
|
|
||||||
private String normalizeExcelText(String value) {
|
private String normalizeExcelText(String value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -679,6 +679,54 @@ def import_dedupe_total_data():
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@admin_api.route('/dedupe-total-data/delete-import/<import_id>')
|
||||||
|
@admin_required
|
||||||
|
def dedupe_total_data_delete_import_progress(import_id):
|
||||||
|
result, error_response, status = _proxy_backend_java(
|
||||||
|
'GET',
|
||||||
|
f'/api/admin/dedupe-total-data/delete-import/{import_id}',
|
||||||
|
)
|
||||||
|
if error_response is not None:
|
||||||
|
return error_response, status
|
||||||
|
progress = result.get('data') or {}
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'progress': {
|
||||||
|
'status': progress.get('status') or 'pending',
|
||||||
|
'total_rows': progress.get('totalRows') or 0,
|
||||||
|
'processed_rows': progress.get('processedRows') or 0,
|
||||||
|
'asin_count': progress.get('asinCount') or 0,
|
||||||
|
'deleted_count': progress.get('insertedCount') or 0,
|
||||||
|
'skipped_count': progress.get('skippedCount') or 0,
|
||||||
|
'error_message': progress.get('errorMessage') or '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@admin_api.route('/dedupe-total-data/delete-import', methods=['POST'])
|
||||||
|
@admin_required
|
||||||
|
def delete_import_dedupe_total_data():
|
||||||
|
file_storage = request.files.get('file')
|
||||||
|
if not file_storage or file_storage.filename == '':
|
||||||
|
return jsonify({'success': False, 'error': '请选择 Excel 文件'})
|
||||||
|
files = {
|
||||||
|
'file': (file_storage.filename, file_storage.stream, file_storage.mimetype or 'application/octet-stream')
|
||||||
|
}
|
||||||
|
result, error_response, status = _proxy_backend_java(
|
||||||
|
'POST',
|
||||||
|
'/api/admin/dedupe-total-data/delete-import',
|
||||||
|
files=files,
|
||||||
|
)
|
||||||
|
if error_response is not None:
|
||||||
|
return error_response, status
|
||||||
|
summary = result.get('data') or {}
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'msg': result.get('message') or '开始删除',
|
||||||
|
'import_id': summary.get('importId') or '',
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@admin_api.route('/dedupe-total-data', methods=['POST'])
|
@admin_api.route('/dedupe-total-data', methods=['POST'])
|
||||||
@admin_required
|
@admin_required
|
||||||
def create_dedupe_total_data():
|
def create_dedupe_total_data():
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ bucket_path = "nanri-image/"
|
|||||||
file_url_pre = f"https://{bucket}.oss-cn-hangzhou.aliyuncs.com/"
|
file_url_pre = f"https://{bucket}.oss-cn-hangzhou.aliyuncs.com/"
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
# backend_java_base_url = os.environ.get('BACKEND_JAVA_BASE_URL', 'http://127.0.0.1:18080').rstrip('/')
|
||||||
backend_java_base_url = os.environ.get('BACKEND_JAVA_BASE_URL', 'http://8.136.19.173:18080').rstrip('/')
|
backend_java_base_url = os.environ.get('BACKEND_JAVA_BASE_URL', 'http://8.136.19.173:18080').rstrip('/')
|
||||||
os.environ['OSS_ACCESS_KEY_ID'] = accessKeyId
|
os.environ['OSS_ACCESS_KEY_ID'] = accessKeyId
|
||||||
os.environ['OSS_ACCESS_KEY_SECRET'] = accessKeySecret
|
os.environ['OSS_ACCESS_KEY_SECRET'] = accessKeySecret
|
||||||
|
|||||||
@@ -251,6 +251,8 @@
|
|||||||
<!-- 数据去重总数据 -->
|
<!-- 数据去重总数据 -->
|
||||||
<div id="panel-dedupe-total-data" class="tab-panel">
|
<div id="panel-dedupe-total-data" class="tab-panel">
|
||||||
<div class="form-box">
|
<div class="form-box">
|
||||||
|
<div class="form-row" style="align-items:flex-start;gap:16px;">
|
||||||
|
<div style="flex:1;min-width:320px;">
|
||||||
<h3 style="margin-bottom:16px;font-size:15px;">新增总数据</h3>
|
<h3 style="margin-bottom:16px;font-size:15px;">新增总数据</h3>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group" style="min-width:320px;">
|
<div class="form-group" style="min-width:320px;">
|
||||||
@@ -265,6 +267,23 @@
|
|||||||
<div class="progress-text" id="dedupeTotalDataProgressText"></div>
|
<div class="progress-text" id="dedupeTotalDataProgressText"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="flex:1;min-width:320px;">
|
||||||
|
<h3 style="margin-bottom:16px;font-size:15px;">删除匹配总数据</h3>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group" style="min-width:320px;">
|
||||||
|
<label>上传 Excel 文件(读取 ASIN 列)</label>
|
||||||
|
<input type="file" id="dedupeTotalDataDeleteFile" accept=".xlsx,.xls">
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-danger" id="btnDeleteImportDedupeTotalData">上传并删除</button>
|
||||||
|
</div>
|
||||||
|
<p class="msg" id="msgDeleteDedupeTotalData"></p>
|
||||||
|
<div class="progress-wrap" id="dedupeTotalDataDeleteProgressWrap" style="display:none;">
|
||||||
|
<div class="progress-bar"><div class="progress-fill" id="dedupeTotalDataDeleteProgressFill"></div></div>
|
||||||
|
<div class="progress-text" id="dedupeTotalDataDeleteProgressText"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="panel-box">
|
<div class="panel-box">
|
||||||
<h3 style="margin-bottom:16px;font-size:15px;">总数据列表</h3>
|
<h3 style="margin-bottom:16px;font-size:15px;">总数据列表</h3>
|
||||||
<div class="form-row" style="margin-bottom:16px;">
|
<div class="form-row" style="margin-bottom:16px;">
|
||||||
@@ -869,12 +888,19 @@
|
|||||||
}
|
}
|
||||||
document.getElementById('btnSearchDedupeTotalData').onclick = function() { loadDedupeTotalData(1); };
|
document.getElementById('btnSearchDedupeTotalData').onclick = function() { loadDedupeTotalData(1); };
|
||||||
var dedupeImportPollTimer = null;
|
var dedupeImportPollTimer = null;
|
||||||
|
var dedupeDeleteImportPollTimer = null;
|
||||||
function stopDedupeImportProgress() {
|
function stopDedupeImportProgress() {
|
||||||
if (dedupeImportPollTimer) {
|
if (dedupeImportPollTimer) {
|
||||||
clearInterval(dedupeImportPollTimer);
|
clearInterval(dedupeImportPollTimer);
|
||||||
dedupeImportPollTimer = null;
|
dedupeImportPollTimer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function stopDedupeDeleteImportProgress() {
|
||||||
|
if (dedupeDeleteImportPollTimer) {
|
||||||
|
clearInterval(dedupeDeleteImportPollTimer);
|
||||||
|
dedupeDeleteImportPollTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
function setDedupeImportProgress(percent, text) {
|
function setDedupeImportProgress(percent, text) {
|
||||||
var wrap = document.getElementById('dedupeTotalDataProgressWrap');
|
var wrap = document.getElementById('dedupeTotalDataProgressWrap');
|
||||||
var fill = document.getElementById('dedupeTotalDataProgressFill');
|
var fill = document.getElementById('dedupeTotalDataProgressFill');
|
||||||
@@ -883,6 +909,14 @@
|
|||||||
fill.style.width = Math.max(0, Math.min(100, percent || 0)) + '%';
|
fill.style.width = Math.max(0, Math.min(100, percent || 0)) + '%';
|
||||||
textEl.textContent = text || '';
|
textEl.textContent = text || '';
|
||||||
}
|
}
|
||||||
|
function setDedupeDeleteImportProgress(percent, text) {
|
||||||
|
var wrap = document.getElementById('dedupeTotalDataDeleteProgressWrap');
|
||||||
|
var fill = document.getElementById('dedupeTotalDataDeleteProgressFill');
|
||||||
|
var textEl = document.getElementById('dedupeTotalDataDeleteProgressText');
|
||||||
|
wrap.style.display = 'block';
|
||||||
|
fill.style.width = Math.max(0, Math.min(100, percent || 0)) + '%';
|
||||||
|
textEl.textContent = text || '';
|
||||||
|
}
|
||||||
function pollDedupeImport(importId) {
|
function pollDedupeImport(importId) {
|
||||||
stopDedupeImportProgress();
|
stopDedupeImportProgress();
|
||||||
function tick() {
|
function tick() {
|
||||||
@@ -920,6 +954,43 @@
|
|||||||
tick();
|
tick();
|
||||||
dedupeImportPollTimer = setInterval(tick, 1000);
|
dedupeImportPollTimer = setInterval(tick, 1000);
|
||||||
}
|
}
|
||||||
|
function pollDedupeDeleteImport(importId) {
|
||||||
|
stopDedupeDeleteImportProgress();
|
||||||
|
function tick() {
|
||||||
|
fetch('/api/admin/dedupe-total-data/delete-import/' + encodeURIComponent(importId))
|
||||||
|
.then(function(r) { return r.json(); })
|
||||||
|
.then(function(res) {
|
||||||
|
if (!res.success) {
|
||||||
|
stopDedupeDeleteImportProgress();
|
||||||
|
document.getElementById('msgDeleteDedupeTotalData').textContent = res.error || '查询删除进度失败';
|
||||||
|
document.getElementById('msgDeleteDedupeTotalData').className = 'msg err';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var progress = res.progress || {};
|
||||||
|
var totalRows = progress.total_rows || 0;
|
||||||
|
var processedRows = progress.processed_rows || 0;
|
||||||
|
var percent = totalRows > 0 ? Math.round(processedRows * 100 / totalRows) : 0;
|
||||||
|
setDedupeDeleteImportProgress(percent, '处理中:已处理 ' + processedRows + ' / ' + totalRows + ',删除 ' + (progress.deleted_count || 0) + ',跳过 ' + (progress.skipped_count || 0));
|
||||||
|
if (progress.status === 'success') {
|
||||||
|
stopDedupeDeleteImportProgress();
|
||||||
|
document.getElementById('msgDeleteDedupeTotalData').textContent = '删除成功:总行数 ' + (progress.total_rows || 0) + ',ASIN 数量 ' + (progress.asin_count || 0) + ',删除 ' + (progress.deleted_count || 0) + ',跳过 ' + (progress.skipped_count || 0);
|
||||||
|
document.getElementById('msgDeleteDedupeTotalData').className = 'msg ok';
|
||||||
|
loadDedupeTotalData(1);
|
||||||
|
} else if (progress.status === 'failed') {
|
||||||
|
stopDedupeDeleteImportProgress();
|
||||||
|
document.getElementById('msgDeleteDedupeTotalData').textContent = progress.error_message || '删除失败';
|
||||||
|
document.getElementById('msgDeleteDedupeTotalData').className = 'msg err';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
stopDedupeDeleteImportProgress();
|
||||||
|
document.getElementById('msgDeleteDedupeTotalData').textContent = '查询删除进度失败';
|
||||||
|
document.getElementById('msgDeleteDedupeTotalData').className = 'msg err';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tick();
|
||||||
|
dedupeDeleteImportPollTimer = setInterval(tick, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('btnAddDedupeTotalData').onclick = function() {
|
document.getElementById('btnAddDedupeTotalData').onclick = function() {
|
||||||
var fileInput = document.getElementById('dedupeTotalDataFile');
|
var fileInput = document.getElementById('dedupeTotalDataFile');
|
||||||
@@ -974,6 +1045,62 @@
|
|||||||
};
|
};
|
||||||
xhr.send(formData);
|
xhr.send(formData);
|
||||||
};
|
};
|
||||||
|
document.getElementById('btnDeleteImportDedupeTotalData').onclick = function() {
|
||||||
|
var fileInput = document.getElementById('dedupeTotalDataDeleteFile');
|
||||||
|
var msgEl = document.getElementById('msgDeleteDedupeTotalData');
|
||||||
|
msgEl.textContent = '';
|
||||||
|
msgEl.className = 'msg';
|
||||||
|
stopDedupeDeleteImportProgress();
|
||||||
|
document.getElementById('dedupeTotalDataDeleteProgressWrap').style.display = 'none';
|
||||||
|
if (!fileInput.files || fileInput.files.length === 0) {
|
||||||
|
msgEl.textContent = '请选择 Excel 文件';
|
||||||
|
msgEl.classList.add('err');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var file = fileInput.files[0];
|
||||||
|
var lowerName = (file.name || '').toLowerCase();
|
||||||
|
if (!(lowerName.endsWith('.xlsx') || lowerName.endsWith('.xls'))) {
|
||||||
|
msgEl.textContent = '仅支持 .xlsx 或 .xls 文件';
|
||||||
|
msgEl.classList.add('err');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!confirm('确定按 Excel 中的 ASIN 批量删除匹配的总数据吗?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', '/api/admin/dedupe-total-data/delete-import', true);
|
||||||
|
xhr.upload.onprogress = function(event) {
|
||||||
|
if (event.lengthComputable) {
|
||||||
|
var percent = Math.round(event.loaded * 100 / event.total);
|
||||||
|
setDedupeDeleteImportProgress(percent, '上传中:' + percent + '%');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
if (xhr.readyState !== 4) return;
|
||||||
|
if (xhr.status < 200 || xhr.status >= 300) {
|
||||||
|
msgEl.textContent = '请求失败';
|
||||||
|
msgEl.className = 'msg err';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var res;
|
||||||
|
try { res = JSON.parse(xhr.responseText || '{}'); } catch (e) { res = { success: false, error: '返回格式错误' }; }
|
||||||
|
if (!res.success) {
|
||||||
|
msgEl.textContent = res.error || '删除失败';
|
||||||
|
msgEl.className = 'msg err';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setDedupeDeleteImportProgress(100, '上传完成,后端处理中...');
|
||||||
|
pollDedupeDeleteImport(res.import_id);
|
||||||
|
fileInput.value = '';
|
||||||
|
};
|
||||||
|
xhr.onerror = function() {
|
||||||
|
msgEl.textContent = '请求失败';
|
||||||
|
msgEl.className = 'msg err';
|
||||||
|
};
|
||||||
|
xhr.send(formData);
|
||||||
|
};
|
||||||
document.getElementById('btnSaveDedupeTotalData').onclick = function() {
|
document.getElementById('btnSaveDedupeTotalData').onclick = function() {
|
||||||
var itemId = document.getElementById('editDedupeTotalDataId').value;
|
var itemId = document.getElementById('editDedupeTotalDataId').value;
|
||||||
var value = (document.getElementById('editDedupeTotalDataValue').value || '').trim();
|
var value = (document.getElementById('editDedupeTotalDataValue').value || '').trim();
|
||||||
|
|||||||
Reference in New Issue
Block a user