删除模块 取消打开紫鸟
This commit is contained in:
18
app/main.py
18
app/main.py
@@ -16,7 +16,6 @@ import webview
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import requests
|
import requests
|
||||||
import subprocess
|
|
||||||
|
|
||||||
|
|
||||||
with open("version.txt","w",encoding="utf-8") as file:
|
with open("version.txt","w",encoding="utf-8") as file:
|
||||||
@@ -235,21 +234,6 @@ class WindowAPI:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {'success': False, 'error': str(e)}
|
return {'success': False, 'error': str(e)}
|
||||||
|
|
||||||
def open_external_url(self, url):
|
|
||||||
if not url or not str(url).strip():
|
|
||||||
return {'success': False, 'error': '链接为空'}
|
|
||||||
target = str(url).strip()
|
|
||||||
try:
|
|
||||||
if sys.platform.startswith('win'):
|
|
||||||
os.startfile(target)
|
|
||||||
elif sys.platform == 'darwin':
|
|
||||||
subprocess.Popen(['open', target])
|
|
||||||
else:
|
|
||||||
subprocess.Popen(['xdg-open', target])
|
|
||||||
return {'success': True}
|
|
||||||
except Exception as e:
|
|
||||||
return {'success': False, 'error': str(e)}
|
|
||||||
|
|
||||||
def enqueue_json(self, data):
|
def enqueue_json(self, data):
|
||||||
"""保存任务到队列"""
|
"""保存任务到队列"""
|
||||||
try:
|
try:
|
||||||
@@ -366,7 +350,7 @@ def main():
|
|||||||
)
|
)
|
||||||
api = WindowAPI(window)
|
api = WindowAPI(window)
|
||||||
window.expose(api.close, api.minimize, api.maximize, api.toggle_maximize, generate_images, api.save_image, api.save_image_to_folder, api.select_folder, api.select_brand_xlsx_files, api.select_brand_folder,
|
window.expose(api.close, api.minimize, api.maximize, api.toggle_maximize, generate_images, api.save_image, api.save_image_to_folder, api.select_folder, api.select_brand_xlsx_files, api.select_brand_folder,
|
||||||
api.save_file_from_url, api.save_template_xlsx,api.save_template_zip,api.upload_file_to_java,api.open_external_url,api.enqueue_json)
|
api.save_file_from_url, api.save_template_xlsx,api.save_template_zip,api.upload_file_to_java,api.enqueue_json)
|
||||||
webview.start(
|
webview.start(
|
||||||
debug=True,
|
debug=True,
|
||||||
storage_path=cache_path,
|
storage_path=cache_path,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.nanri.aiimage.modules.deletebrand.model.dto.DeleteBrandTaskBatchReque
|
|||||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandHistoryVo;
|
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandHistoryVo;
|
||||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandRunVo;
|
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandRunVo;
|
||||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskBatchVo;
|
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskBatchVo;
|
||||||
|
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskDeletionStatusVo;
|
||||||
import com.nanri.aiimage.modules.deletebrand.service.DeleteBrandRunService;
|
import com.nanri.aiimage.modules.deletebrand.service.DeleteBrandRunService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
@@ -83,6 +84,15 @@ public class DeleteBrandRunController {
|
|||||||
return ApiResponse.success(deleteBrandRunService.getTaskDetails(request.getTaskIds()));
|
return ApiResponse.success(deleteBrandRunService.getTaskDetails(request.getTaskIds()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/tasks/{taskId}/deletion-status")
|
||||||
|
@Operation(summary = "获取删除品牌任务删除状态", description = "供 Python/插件按 taskId 查询该删除品牌任务是否已被前端删空。")
|
||||||
|
public ApiResponse<DeleteBrandTaskDeletionStatusVo> getTaskDeletionStatus(
|
||||||
|
@PathVariable Long taskId,
|
||||||
|
@Parameter(name = "user_id", description = "当前登录用户 ID", required = true, in = ParameterIn.QUERY)
|
||||||
|
@RequestParam("user_id") Long userId) {
|
||||||
|
return ApiResponse.success(deleteBrandRunService.getTaskDeletionStatus(taskId, userId));
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/tasks/{taskId}/result")
|
@PostMapping("/tasks/{taskId}/result")
|
||||||
@Operation(summary = "提交删除品牌处理结果", description = "插件/前端分片回传处理结果;后端累计分片并推进进度,全部完成后再执行最终组装与落库。")
|
@Operation(summary = "提交删除品牌处理结果", description = "插件/前端分片回传处理结果;后端累计分片并推进进度,全部完成后再执行最终组装与落库。")
|
||||||
public ApiResponse<Void> submitResult(
|
public ApiResponse<Void> submitResult(
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.nanri.aiimage.modules.deletebrand.model.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "删除品牌任务删除状态响应")
|
||||||
|
public class DeleteBrandTaskDeletionStatusVo {
|
||||||
|
@Schema(description = "任务ID")
|
||||||
|
private Long taskId;
|
||||||
|
|
||||||
|
@Schema(description = "该任务的结果记录是否已被全部删除")
|
||||||
|
private boolean deleted;
|
||||||
|
|
||||||
|
@Schema(description = "该任务当前剩余的结果记录数量")
|
||||||
|
private long remainingResultCount;
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandLineProgressVo;
|
|||||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandPreviewRowVo;
|
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandPreviewRowVo;
|
||||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandResultItemVo;
|
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandResultItemVo;
|
||||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandRunVo;
|
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandRunVo;
|
||||||
|
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskDeletionStatusVo;
|
||||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskDetailVo;
|
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskDetailVo;
|
||||||
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskItemVo;
|
import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskItemVo;
|
||||||
import com.nanri.aiimage.modules.file.service.LocalFileStorageService;
|
import com.nanri.aiimage.modules.file.service.LocalFileStorageService;
|
||||||
@@ -31,6 +32,8 @@ import com.nanri.aiimage.modules.task.mapper.FileResultMapper;
|
|||||||
import com.nanri.aiimage.modules.task.mapper.FileTaskMapper;
|
import com.nanri.aiimage.modules.task.mapper.FileTaskMapper;
|
||||||
import com.nanri.aiimage.modules.task.model.entity.FileResultEntity;
|
import com.nanri.aiimage.modules.task.model.entity.FileResultEntity;
|
||||||
import com.nanri.aiimage.modules.task.model.entity.FileTaskEntity;
|
import com.nanri.aiimage.modules.task.model.entity.FileTaskEntity;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoShopMatchResultVo;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.service.ZiniaoShopSwitchService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.poi.ss.usermodel.Cell;
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
@@ -68,7 +71,7 @@ public class DeleteBrandRunService {
|
|||||||
private final FileResultMapper fileResultMapper;
|
private final FileResultMapper fileResultMapper;
|
||||||
private final LocalFileStorageService localFileStorageService;
|
private final LocalFileStorageService localFileStorageService;
|
||||||
private final DeleteBrandTaskCacheService deleteBrandTaskCacheService;
|
private final DeleteBrandTaskCacheService deleteBrandTaskCacheService;
|
||||||
private final com.nanri.aiimage.modules.ziniao.service.ZiniaoAuthService ziniaoAuthService;
|
private final ZiniaoShopSwitchService ziniaoShopSwitchService;
|
||||||
private final OssStorageService ossStorageService;
|
private final OssStorageService ossStorageService;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final DeleteBrandProgressProperties deleteBrandProgressProperties;
|
private final DeleteBrandProgressProperties deleteBrandProgressProperties;
|
||||||
@@ -96,7 +99,7 @@ public class DeleteBrandRunService {
|
|||||||
int parsedSuccessCount = 0;
|
int parsedSuccessCount = 0;
|
||||||
int parsedFailedCount = 0;
|
int parsedFailedCount = 0;
|
||||||
Map<String, DeleteBrandParsedFileCacheDto> parsedPayloadByFileIdentity = new LinkedHashMap<>();
|
Map<String, DeleteBrandParsedFileCacheDto> parsedPayloadByFileIdentity = new LinkedHashMap<>();
|
||||||
Map<String, com.nanri.aiimage.modules.ziniao.service.ZiniaoAuthService.StoreMatchResult> storeMatchByShopName = new LinkedHashMap<>();
|
Map<String, ZiniaoShopMatchResultVo> storeMatchByShopName = new LinkedHashMap<>();
|
||||||
|
|
||||||
for (DeleteBrandSourceFileDto sourceFile : request.getFiles()) {
|
for (DeleteBrandSourceFileDto sourceFile : request.getFiles()) {
|
||||||
DeleteBrandResultItemVo item = new DeleteBrandResultItemVo();
|
DeleteBrandResultItemVo item = new DeleteBrandResultItemVo();
|
||||||
@@ -117,25 +120,22 @@ public class DeleteBrandRunService {
|
|||||||
item.setPreviewRows(parsed.previewRows());
|
item.setPreviewRows(parsed.previewRows());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String normalizedShopName = normalizeShopName(item.getShopName());
|
String normalizedShopName = ziniaoShopSwitchService.normalizeShopName(item.getShopName());
|
||||||
com.nanri.aiimage.modules.ziniao.service.ZiniaoAuthService.StoreMatchResult matchResult = normalizedShopName.isBlank()
|
ZiniaoShopMatchResultVo matchResult = normalizedShopName.isBlank()
|
||||||
? new com.nanri.aiimage.modules.ziniao.service.ZiniaoAuthService.StoreMatchResult(false, null, null, null, null, null)
|
? ziniaoShopSwitchService.emptyMatchResult()
|
||||||
: storeMatchByShopName.computeIfAbsent(normalizedShopName,
|
: storeMatchByShopName.computeIfAbsent(normalizedShopName,
|
||||||
ignored -> ziniaoAuthService.matchStoreByNameAcrossStaff(item.getShopName(), null));
|
ignored -> ziniaoShopSwitchService.matchStoreByNameAcrossStaff(item.getShopName(), null));
|
||||||
item.setMatched(matchResult.matched());
|
item.setMatched(matchResult.isMatched());
|
||||||
if (matchResult.matched()) {
|
if (matchResult.isMatched()) {
|
||||||
item.setShopId(matchResult.shopId());
|
item.setShopId(matchResult.getShopId());
|
||||||
item.setPlatform(matchResult.platform());
|
item.setPlatform(matchResult.getPlatform());
|
||||||
item.setOpenStoreUrl(matchResult.openStoreUrl());
|
item.setOpenStoreUrl(matchResult.getOpenStoreUrl());
|
||||||
} else {
|
} else {
|
||||||
throw new BusinessException("未匹配到紫鸟店铺,跳过处理");
|
throw new BusinessException("未匹配到紫鸟店铺,跳过处理");
|
||||||
}
|
}
|
||||||
} catch (BusinessException ex) {
|
} catch (BusinessException ex) {
|
||||||
String message = ex.getMessage();
|
String message = ex.getMessage();
|
||||||
if (message != null && (message.contains("code=40004")
|
if (ziniaoShopSwitchService.isSkippableMatchError(ex)) {
|
||||||
|| message.contains("userId存在无效的参数值")
|
|
||||||
|| message.contains("无权限")
|
|
||||||
|| message.contains("没有权限")) && !message.contains("白名单")) {
|
|
||||||
log.warn("[ziniao-match] suppressed upstream permission error for {}, msg: {}", item.getShopName(), message);
|
log.warn("[ziniao-match] suppressed upstream permission error for {}, msg: {}", item.getShopName(), message);
|
||||||
item.setMatched(false);
|
item.setMatched(false);
|
||||||
} else {
|
} else {
|
||||||
@@ -301,6 +301,24 @@ public class DeleteBrandRunService {
|
|||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DeleteBrandTaskDeletionStatusVo getTaskDeletionStatus(Long taskId, Long userId) {
|
||||||
|
FileTaskEntity task = fileTaskMapper.selectById(taskId);
|
||||||
|
if (task == null || !MODULE_TYPE.equals(task.getModuleType()) || !userId.equals(task.getUserId())) {
|
||||||
|
throw new BusinessException("任务不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
Long remainingResultCount = fileResultMapper.selectCount(new LambdaQueryWrapper<FileResultEntity>()
|
||||||
|
.eq(FileResultEntity::getTaskId, taskId)
|
||||||
|
.eq(FileResultEntity::getModuleType, MODULE_TYPE)
|
||||||
|
.eq(FileResultEntity::getUserId, userId));
|
||||||
|
|
||||||
|
DeleteBrandTaskDeletionStatusVo vo = new DeleteBrandTaskDeletionStatusVo();
|
||||||
|
vo.setTaskId(taskId);
|
||||||
|
vo.setRemainingResultCount(remainingResultCount == null ? 0L : remainingResultCount);
|
||||||
|
vo.setDeleted(vo.getRemainingResultCount() == 0);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
public void deleteHistory(Long resultId, Long userId) {
|
public void deleteHistory(Long resultId, Long userId) {
|
||||||
FileResultEntity entity = fileResultMapper.selectById(resultId);
|
FileResultEntity entity = fileResultMapper.selectById(resultId);
|
||||||
if (entity == null || !MODULE_TYPE.equals(entity.getModuleType()) || !userId.equals(entity.getUserId())) {
|
if (entity == null || !MODULE_TYPE.equals(entity.getModuleType()) || !userId.equals(entity.getUserId())) {
|
||||||
@@ -400,13 +418,6 @@ public class DeleteBrandRunService {
|
|||||||
return value.replace(" ", "").trim().toUpperCase(Locale.ROOT);
|
return value.replace(" ", "").trim().toUpperCase(Locale.ROOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String normalizeShopName(String value) {
|
|
||||||
if (value == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return value.replace("\u3000", " ").trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<CountryColumnPair> resolveCountryPairs(Row titleRow, Row headerRow, DataFormatter formatter) {
|
private List<CountryColumnPair> resolveCountryPairs(Row titleRow, Row headerRow, DataFormatter formatter) {
|
||||||
List<CountryColumnPair> pairs = new ArrayList<>();
|
List<CountryColumnPair> pairs = new ArrayList<>();
|
||||||
int lastCellNum = Math.max(titleRow.getLastCellNum(), headerRow.getLastCellNum());
|
int lastCellNum = Math.max(titleRow.getLastCellNum(), headerRow.getLastCellNum());
|
||||||
@@ -876,7 +887,7 @@ public class DeleteBrandRunService {
|
|||||||
throw new BusinessException("存在未完成的结果分片: " + parsedFile.getSourceFilename());
|
throw new BusinessException("存在未完成的结果分片: " + parsedFile.getSourceFilename());
|
||||||
}
|
}
|
||||||
MergedDeleteBrandFile mergedFile = mergeChunks(parsedFile, chunks);
|
MergedDeleteBrandFile mergedFile = mergeChunks(parsedFile, chunks);
|
||||||
File outputFile = buildResultWorkbook(task.getId(), parsedFile, mergedFile);
|
File outputFile = buildResultWorkbookPreserveLayout(task.getId(), parsedFile, mergedFile);
|
||||||
|
|
||||||
deleteBrandTaskCacheService.saveProgress(task.getId(), Map.of(
|
deleteBrandTaskCacheService.saveProgress(task.getId(), Map.of(
|
||||||
"phase", DeleteBrandTaskCacheService.PHASE_UPLOADING,
|
"phase", DeleteBrandTaskCacheService.PHASE_UPLOADING,
|
||||||
@@ -1032,6 +1043,52 @@ public class DeleteBrandRunService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private File buildResultWorkbookPreserveLayout(Long taskId,
|
||||||
|
DeleteBrandParsedFileCacheDto parsedFile,
|
||||||
|
MergedDeleteBrandFile mergedFile) {
|
||||||
|
File outputDir = FileUtil.mkdir(FileUtil.file(System.getProperty("java.io.tmpdir"), "delete-brand-result", String.valueOf(taskId)));
|
||||||
|
File outputFile = buildNamedOutputFile(outputDir, blankToDefault(parsedFile.getSourceFilename(), "delete-brand-result.xlsx"));
|
||||||
|
|
||||||
|
try (Workbook workbook = new XSSFWorkbook(); FileOutputStream outputStream = new FileOutputStream(outputFile)) {
|
||||||
|
Sheet sheet = workbook.createSheet("删除品牌结果");
|
||||||
|
Row countryRow = sheet.createRow(0);
|
||||||
|
Row headerRow = sheet.createRow(1);
|
||||||
|
|
||||||
|
for (int countryIndex = 0; countryIndex < mergedFile.countries().size(); countryIndex++) {
|
||||||
|
DeleteBrandProcessedCountryDto country = mergedFile.countries().get(countryIndex);
|
||||||
|
int asinColumnIndex = countryIndex * 2;
|
||||||
|
int statusColumnIndex = asinColumnIndex + 1;
|
||||||
|
|
||||||
|
createTextCell(countryRow, asinColumnIndex, country.getCountry());
|
||||||
|
createTextCell(headerRow, asinColumnIndex, "删除ASIN");
|
||||||
|
createTextCell(headerRow, statusColumnIndex, "状态");
|
||||||
|
|
||||||
|
List<DeleteBrandCountryResultItemDto> items = country.getItems();
|
||||||
|
if (items == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) {
|
||||||
|
DeleteBrandCountryResultItemDto item = items.get(itemIndex);
|
||||||
|
Row row = sheet.getRow(itemIndex + 2);
|
||||||
|
if (row == null) {
|
||||||
|
row = sheet.createRow(itemIndex + 2);
|
||||||
|
}
|
||||||
|
createTextCell(row, asinColumnIndex, item.getAsin());
|
||||||
|
createTextCell(row, statusColumnIndex, item.getStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < mergedFile.countries().size() * 2; i++) {
|
||||||
|
sheet.autoSizeColumn(i);
|
||||||
|
}
|
||||||
|
workbook.write(outputStream);
|
||||||
|
return outputFile;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new BusinessException("生成删除品牌结果文件失败: " + parsedFile.getSourceFilename());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void createTextCell(Row row, int index, String value) {
|
private void createTextCell(Row row, int index, String value) {
|
||||||
Cell cell = row.createCell(index);
|
Cell cell = row.createCell(index);
|
||||||
cell.setCellValue(value == null ? "" : value);
|
cell.setCellValue(value == null ? "" : value);
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.nanri.aiimage.modules.ziniao.model.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "紫鸟店铺匹配结果")
|
||||||
|
public class ZiniaoShopMatchResultVo {
|
||||||
|
@Schema(description = "是否匹配成功")
|
||||||
|
private boolean matched;
|
||||||
|
|
||||||
|
@Schema(description = "店铺ID")
|
||||||
|
private String shopId;
|
||||||
|
|
||||||
|
@Schema(description = "店铺名称")
|
||||||
|
private String shopName;
|
||||||
|
|
||||||
|
@Schema(description = "平台")
|
||||||
|
private String platform;
|
||||||
|
|
||||||
|
@Schema(description = "匹配到的员工 userId")
|
||||||
|
private Long matchedUserId;
|
||||||
|
|
||||||
|
@Schema(description = "打开店铺链接")
|
||||||
|
private String openStoreUrl;
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.nanri.aiimage.modules.ziniao.service;
|
||||||
|
|
||||||
|
import com.nanri.aiimage.common.exception.BusinessException;
|
||||||
|
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoShopMatchResultVo;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ZiniaoShopSwitchService {
|
||||||
|
|
||||||
|
private final ZiniaoAuthService ziniaoAuthService;
|
||||||
|
|
||||||
|
public ZiniaoShopMatchResultVo matchStoreByNameAcrossStaff(String targetShopName, Long preferUserId) {
|
||||||
|
String normalizedShopName = normalizeShopName(targetShopName);
|
||||||
|
if (normalizedShopName.isBlank()) {
|
||||||
|
return emptyMatchResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
ZiniaoAuthService.StoreMatchResult result = ziniaoAuthService.matchStoreByNameAcrossStaff(normalizedShopName, preferUserId);
|
||||||
|
return new ZiniaoShopMatchResultVo(
|
||||||
|
result.matched(),
|
||||||
|
result.shopId(),
|
||||||
|
result.shopName(),
|
||||||
|
result.platform(),
|
||||||
|
result.matchedUserId(),
|
||||||
|
result.openStoreUrl()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZiniaoShopMatchResultVo emptyMatchResult() {
|
||||||
|
return new ZiniaoShopMatchResultVo(false, null, null, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String normalizeShopName(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return value.replace("\u3000", " ").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSkippableMatchError(BusinessException ex) {
|
||||||
|
String message = ex == null ? null : ex.getMessage();
|
||||||
|
if (message == null || message.isBlank()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (message.contains("code=40004")
|
||||||
|
|| message.contains("userId\u5b58\u5728\u65e0\u6548\u7684\u53c2\u6570\u503c")
|
||||||
|
|| message.contains("\u65e0\u6743\u9650")
|
||||||
|
|| message.contains("\u6ca1\u6709\u6743\u9650"))
|
||||||
|
&& !message.contains("\u767d\u540d\u5355");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -249,6 +249,9 @@ const queuePayloadText = ref('')
|
|||||||
const taskDetails = ref<Record<number, DeleteBrandTaskDetailVo>>({})
|
const taskDetails = ref<Record<number, DeleteBrandTaskDetailVo>>({})
|
||||||
const pollTimer = ref<number | null>(null)
|
const pollTimer = ref<number | null>(null)
|
||||||
const pollingInFlight = ref(false)
|
const pollingInFlight = ref(false)
|
||||||
|
const chainStarted = ref(false)
|
||||||
|
const activeItemKey = ref('')
|
||||||
|
const autoAdvancing = ref(false)
|
||||||
const displayPaths = computed(() => selectedPaths.value.slice(0, 10))
|
const displayPaths = computed(() => selectedPaths.value.slice(0, 10))
|
||||||
|
|
||||||
const currentSectionItems = computed(() => {
|
const currentSectionItems = computed(() => {
|
||||||
@@ -517,6 +520,7 @@ function scheduleNextPoll(immediate = false) {
|
|||||||
let stateChanged = false
|
let stateChanged = false
|
||||||
try {
|
try {
|
||||||
await refreshTaskDetails(taskIds)
|
await refreshTaskDetails(taskIds)
|
||||||
|
await maybeAutoAdvance()
|
||||||
for (const taskId of taskIds) {
|
for (const taskId of taskIds) {
|
||||||
if (isTaskTerminal(taskId)) {
|
if (isTaskTerminal(taskId)) {
|
||||||
removeSessionTaskFromStorage(taskId)
|
removeSessionTaskFromStorage(taskId)
|
||||||
@@ -766,34 +770,38 @@ function isPythonQueueBusy() {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
async function triggerItemRun(item: DeleteBrandResultItem) {
|
function getItemKey(item: DeleteBrandResultItem) {
|
||||||
|
if (item.resultId != null) return `result:${item.resultId}`
|
||||||
|
return `task:${item.taskId || 0}:${item.sourceFilename || ''}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNextAutoRunnableItem() {
|
||||||
|
return currentSectionItems.value.find((item) => {
|
||||||
|
if (!item.openStoreUrl) return false
|
||||||
|
if (getItemKey(item) === activeItemKey.value) return false
|
||||||
|
return getQueueStatus(item) === '未入队'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runItem(item: DeleteBrandResultItem, options?: { auto?: boolean }) {
|
||||||
if (isPythonQueueBusy()) {
|
if (isPythonQueueBusy()) {
|
||||||
|
if (!options?.auto) {
|
||||||
ElMessage.warning('当前存在正在处理的任务,请等待其完成后再推送下一条!')
|
ElMessage.warning('当前存在正在处理的任务,请等待其完成后再推送下一条!')
|
||||||
return
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const api = getPywebviewApi()
|
const api = getPywebviewApi()
|
||||||
if (item.openStoreUrl) {
|
|
||||||
if (api?.open_external_url) {
|
|
||||||
const openResult = await api.open_external_url(item.openStoreUrl)
|
|
||||||
if (!openResult?.success) {
|
|
||||||
ElMessage.error(openResult?.error || '打开紫鸟失败')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
window.open(item.openStoreUrl, '_self')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const taskId = item.taskId
|
const taskId = item.taskId
|
||||||
if (!taskId) return
|
if (!taskId) return false
|
||||||
|
|
||||||
const sessionTask = sessionTasks.value.find(t => t.taskId === taskId)
|
const sessionTask = sessionTasks.value.find(t => t.taskId === taskId)
|
||||||
if (!sessionTask) return
|
if (!sessionTask) return false
|
||||||
|
|
||||||
if (!api?.enqueue_json) {
|
if (!api?.enqueue_json) {
|
||||||
ElMessage.error('当前环境未启用 pywebview enqueue_json(浏览器环境不会推送)')
|
ElMessage.error('当前环境未启用 pywebview enqueue_json(浏览器环境不会推送)')
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
@@ -811,18 +819,60 @@ async function triggerItemRun(item: DeleteBrandResultItem) {
|
|||||||
if (pushResult?.success) {
|
if (pushResult?.success) {
|
||||||
qpr = `已单独推送文件 ${item.sourceFilename},当前队列长度:${pushResult.queue_size ?? '-'}`
|
qpr = `已单独推送文件 ${item.sourceFilename},当前队列长度:${pushResult.queue_size ?? '-'}`
|
||||||
|
|
||||||
// Mark the item as pushed
|
|
||||||
const sessionItem = sessionTask.items.find(i => i.resultId === item.resultId || i.sourceFilename === item.sourceFilename)
|
const sessionItem = sessionTask.items.find(i => i.resultId === item.resultId || i.sourceFilename === item.sourceFilename)
|
||||||
if (sessionItem) {
|
if (sessionItem) {
|
||||||
(sessionItem as any)._pushed = true
|
(sessionItem as any)._pushed = true
|
||||||
}
|
}
|
||||||
|
activeItemKey.value = getItemKey(item)
|
||||||
saveSessionTask(taskId, sessionTask.items, qpr, queuePayloadText.value)
|
saveSessionTask(taskId, sessionTask.items, qpr, queuePayloadText.value)
|
||||||
|
|
||||||
ensurePolling(true)
|
ensurePolling(true)
|
||||||
} else {
|
} else {
|
||||||
qpr = `推送到 Python 队列失败:${pushResult?.error || '未知错误'}`
|
qpr = `推送到 Python 队列失败:${pushResult?.error || '未知错误'}`
|
||||||
}
|
}
|
||||||
queuePushResult.value = qpr
|
queuePushResult.value = qpr
|
||||||
|
return Boolean(pushResult?.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function maybeAutoAdvance() {
|
||||||
|
if (!chainStarted.value || autoAdvancing.value) return
|
||||||
|
if (isPythonQueueBusy()) return
|
||||||
|
|
||||||
|
const currentKey = activeItemKey.value
|
||||||
|
if (currentKey) {
|
||||||
|
const currentItem = currentSectionItems.value.find(item => getItemKey(item) === currentKey)
|
||||||
|
if (currentItem) {
|
||||||
|
const status = getQueueStatus(currentItem)
|
||||||
|
if (status === '已入队' || status === '处理中') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextItem = findNextAutoRunnableItem()
|
||||||
|
if (!nextItem) {
|
||||||
|
activeItemKey.value = ''
|
||||||
|
chainStarted.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
autoAdvancing.value = true
|
||||||
|
try {
|
||||||
|
const started = await runItem(nextItem, { auto: true })
|
||||||
|
if (!started) {
|
||||||
|
chainStarted.value = false
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
autoAdvancing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function triggerItemRun(item: DeleteBrandResultItem) {
|
||||||
|
chainStarted.value = true
|
||||||
|
const started = await runItem(item)
|
||||||
|
if (!started) {
|
||||||
|
chainStarted.value = false
|
||||||
|
activeItemKey.value = ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openShop(url: string) {
|
function openShop(url: string) {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ export interface PywebviewApi {
|
|||||||
save_template_xlsx?: () => Promise<{ success: boolean; path?: string; error?: string }>
|
save_template_xlsx?: () => Promise<{ success: boolean; path?: string; error?: string }>
|
||||||
save_template_zip?: () => Promise<{ success: boolean; path?: string; error?: string }>
|
save_template_zip?: () => Promise<{ success: boolean; path?: string; error?: string }>
|
||||||
enqueue_json?: (data: unknown) => Promise<{ success: boolean; queue_size?: number; error?: string }>
|
enqueue_json?: (data: unknown) => Promise<{ success: boolean; queue_size?: number; error?: string }>
|
||||||
open_external_url?: (url: string) => Promise<{ success: boolean; error?: string }>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
Reference in New Issue
Block a user