From 172917ac419987b359fda6049c68c0a6ca54f6a6 Mon Sep 17 00:00:00 2001 From: super <2903208875@qq.com> Date: Thu, 30 Apr 2026 11:36:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=90=8E=E5=8F=B0=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend-java/pom.xml | 11 + .../client/AppearancePatentCozeClient.java | 2 +- .../AppearancePatentController.java | 5 +- .../service/AppearancePatentTaskService.java | 12 +- .../brand/service/BrandTaskService.java | 4 +- .../convert/service/ConvertRunService.java | 4 +- .../dedupe/service/DedupeRunService.java | 28 +- .../file/service/LocalFileStorageService.java | 4 +- .../service/PatrolDeleteTaskService.java | 2 - .../dto/PriceTrackSubmitResultRequest.java | 5 + .../PriceTrackExcelAssemblyService.java | 25 +- .../service/PriceTrackTaskService.java | 21 +- .../service/ProductRiskTaskService.java | 19 +- .../service/QueryAsinTaskService.java | 2 - .../shopkey/mapper/ShopManageGroupMapper.java | 61 + .../shopkey/service/QueryAsinService.java | 58 +- .../service/ShopManageGroupService.java | 90 +- .../shopkey/service/ShopManageService.java | 44 +- .../shopkey/service/SkipPriceAsinService.java | 18 +- .../service/ShopMatchTaskService.java | 19 +- .../split/service/SplitRunService.java | 4 +- .../task/service/TaskFileJobService.java | 5 - .../task/service/TaskResultItemService.java | 2 - .../service/TaskResultPayloadService.java | 1 - .../ziniao/service/ZiniaoAuthService.java | 2 +- .../service/ZiniaoShopIndexService.java | 2 +- .../src/main/resources/application.yml | 6 +- .../db/V36__shopkey_list_query_indexes.sql | 61 + .../db/V37__permission_menu_query_indexes.sql | 31 + .../db/V38__task_result_hot_path_indexes.sql | 31 + backend/__pycache__/config.cpython-312.pyc | Bin 1517 -> 1512 bytes .../__pycache__/admin_api.cpython-312.pyc | Bin 108369 -> 106583 bytes backend/blueprints/admin_api.py | 139 +- backend/config.py | 4 +- .../utils/__pycache__/auth.cpython-312.pyc | Bin 5017 -> 5408 bytes backend/utils/__pycache__/db.cpython-312.pyc | Bin 8671 -> 8861 bytes backend/utils/auth.py | 30 +- backend/utils/db.py | 18 +- backend/web_source/_tmp_prefix.js | 2000 ----------------- backend/web_source/admin.html | 479 +++- .../components/BrandAppearancePatentTab.vue | 22 +- .../brand/components/BrandDeleteBrandTab.vue | 22 +- .../brand/components/BrandPatrolDeleteTab.vue | 33 +- .../brand/components/BrandPriceTrackTab.vue | 56 +- .../brand/components/BrandProductRiskTab.vue | 41 +- .../brand/components/BrandQueryAsinTab.vue | 33 +- .../brand/components/BrandShopMatchTab.vue | 33 +- .../src/shared/utils/categorized-timers.ts | 113 + 48 files changed, 1211 insertions(+), 2391 deletions(-) create mode 100644 backend-java/src/main/resources/db/V36__shopkey_list_query_indexes.sql create mode 100644 backend-java/src/main/resources/db/V37__permission_menu_query_indexes.sql create mode 100644 backend-java/src/main/resources/db/V38__task_result_hot_path_indexes.sql delete mode 100644 backend/web_source/_tmp_prefix.js create mode 100644 frontend-vue/src/shared/utils/categorized-timers.ts diff --git a/backend-java/pom.xml b/backend-java/pom.xml index 2fab57a..0c4fa85 100644 --- a/backend-java/pom.xml +++ b/backend-java/pom.xml @@ -25,8 +25,19 @@ 5.8.36 2.3.5 8.5.17 + 3.28.0-GA + + + + org.javassist + javassist + ${javassist.version} + + + + org.springframework.boot diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/client/AppearancePatentCozeClient.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/client/AppearancePatentCozeClient.java index 12cd99d..bbc92db 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/client/AppearancePatentCozeClient.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/client/AppearancePatentCozeClient.java @@ -649,7 +649,7 @@ public class AppearancePatentCozeClient { } private String normalize(String value) { - return value == null ? "" : value.replace("\ufeff", "").replace("\u3000", " ").trim(); + return value == null ? "" : value.replace(String.valueOf((char) 0xFEFF), "").replace((char) 0x3000, ' ').trim(); } private String rowKey(AppearancePatentResultRowDto row) { diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/controller/AppearancePatentController.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/controller/AppearancePatentController.java index 8a7157a..0eddd72 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/controller/AppearancePatentController.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/controller/AppearancePatentController.java @@ -83,7 +83,10 @@ public class AppearancePatentController { public ApiResponse result( @Parameter(description = "外观专利检测任务 ID。任务必须处于 RUNNING 状态。", required = true, example = "3938") @PathVariable Long taskId, - @Valid @RequestBody AppearancePatentSubmitResultRequest request) { + @Valid @RequestBody AppearancePatentSubmitResultRequest request, + jakarta.servlet.http.HttpServletResponse response) { + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + response.setContentType("application/json;charset=UTF-8"); service.submitResult(taskId, request); return ApiResponse.success(null); } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java index 9d751c2..2b6f85b 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/appearancepatent/service/AppearancePatentTaskService.java @@ -121,7 +121,7 @@ public class AppearancePatentTaskService { .filter(Objects::nonNull) .toList(); if (sourceFiles.isEmpty()) { - throw new BusinessException("璇峰厛涓婁紶 Excel 鏂囦欢"); + throw new BusinessException("请先上传 Excel 文件"); } List allRows = new ArrayList<>(); List mergedHeaders = new ArrayList<>(); @@ -461,10 +461,10 @@ public class AppearancePatentTaskService { private SubmitContext persistSubmittedChunk(Long taskId, AppearancePatentSubmitResultRequest request) { FileTaskEntity task = fileTaskMapper.selectById(taskId); if (task == null || !MODULE_TYPE.equals(task.getModuleType())) { - throw new BusinessException("浠诲姟涓嶅瓨鍦?"); + throw new BusinessException("任务不存在"); } if (!STATUS_RUNNING.equals(task.getStatus())) { - throw new BusinessException("浠诲姟涓嶆槸杩愯涓姸鎬?"); + throw new BusinessException("任务不是运行中状态"); } int chunkIndex = request.getChunkIndex() == null ? 0 : request.getChunkIndex(); @@ -482,7 +482,7 @@ public class AppearancePatentTaskService { .last("limit 1")); if (existing == null) { List rawRows = flattenSubmittedRows(request); - String payloadJson = writeJson(rawRows, "缁撴灉搴忓垪鍖栧け璐?"); + String payloadJson = writeJson(rawRows, "结果序列化失败"); TaskChunkEntity chunk = new TaskChunkEntity(); chunk.setTaskId(taskId); @@ -514,7 +514,7 @@ public class AppearancePatentTaskService { private void completeSubmittedChunk(SubmitContext context) { FileTaskEntity task = fileTaskMapper.selectById(context.task().getId()); if (task == null || !MODULE_TYPE.equals(task.getModuleType())) { - throw new BusinessException("浠诲姟涓嶅瓨鍦?"); + throw new BusinessException("任务不存在"); } if (!STATUS_RUNNING.equals(task.getStatus())) { log.info("[appearance-patent] skip completion because task already finalized taskId={} status={}", @@ -1379,7 +1379,7 @@ public class AppearancePatentTaskService { } private String normalize(String val) { - return val == null ? "" : val.replace("\ufeff", "").replace("\u3000", " ").trim().replaceAll("\\s+", " "); + return val == null ? "" : val.replace(String.valueOf((char) 0xFEFF), "").replace((char) 0x3000, ' ').trim().replaceAll("\\s+", " "); } private String buildParsedPayloadJson(String aiPrompt, List sourceFiles, List headers, List groups, List allRows) { diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/brand/service/BrandTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/brand/service/BrandTaskService.java index 6361298..5354ceb 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/brand/service/BrandTaskService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/brand/service/BrandTaskService.java @@ -881,8 +881,8 @@ public class BrandTaskService { if (value == null) { return ""; } - return value.replace("\ufeff", "") - .replace("\u3000", " ") + return value.replace(String.valueOf((char) 0xFEFF), "") + .replace((char) 0x3000, ' ') .replace("\r\n", " ") .replace("\r", " ") .replace("\n", " ") diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/convert/service/ConvertRunService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/convert/service/ConvertRunService.java index a8161ed..6d2bf4a 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/convert/service/ConvertRunService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/convert/service/ConvertRunService.java @@ -475,8 +475,8 @@ public class ConvertRunService { if (value == null) { return ""; } - return value.replace("\ufeff", "") - .replace("\u3000", " ") + return value.replace(String.valueOf((char) 0xFEFF), "") + .replace((char) 0x3000, ' ') .replace("\r\n", " ") .replace("\r", " ") .replace("\n", " ") diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/service/DedupeRunService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/service/DedupeRunService.java index 804130e..360a1c4 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/service/DedupeRunService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/dedupe/service/DedupeRunService.java @@ -47,8 +47,8 @@ import java.util.zip.ZipOutputStream; @Slf4j public class DedupeRunService { - private static final String HEADER_SPLIT_MARKER = "idASIN\u56fd\u5bb6\u72b6\u6001\u4ef7\u683c\u53d8\u4f53\u6570\u91cf"; - private static final String HEADER_STOP_COLUMN = "\u7f29\u7565\u56fe\u5730\u57408"; + private static final String HEADER_SPLIT_MARKER = "idASIN国家状态价格变体数量"; + private static final String HEADER_STOP_COLUMN = "缩略图地址8"; private final FileTaskMapper fileTaskMapper; private final FileResultMapper fileResultMapper; @@ -59,7 +59,7 @@ public class DedupeRunService { public DedupeRunVo run(DedupeRunRequest request) { long runStartNs = System.nanoTime(); if (!request.isKeepIntegerIds() && !request.isKeepUnderscoreIds() && !request.isKeepIntegerMainIdsWhenNoSubIds()) { - throw new BusinessException("\u8bf7\u81f3\u5c11\u9009\u62e9\u4e00\u79cd ID \u4fdd\u7559\u89c4\u5219"); + throw new BusinessException("请至少选择一种 ID 保留规则"); } FileTaskEntity task = new FileTaskEntity(); @@ -90,7 +90,7 @@ public class DedupeRunService { File inputFile = findLocalSourceFile(sourceFile.getFileKey()); long findFileNs = elapsedNs(fileStartNs); if (inputFile == null || !inputFile.exists()) { - throw new BusinessException("\u4e0a\u4f20\u6587\u4ef6\u4e0d\u5b58\u5728\uff0c\u8bf7\u91cd\u65b0\u4e0a\u4f20"); + throw new BusinessException("上传文件不存在,请重新上传"); } String inputName = sourceFile.getOriginalFilename() == null ? inputFile.getName() : sourceFile.getOriginalFilename(); @@ -240,7 +240,7 @@ public class DedupeRunService { public void deleteHistory(Long resultId, Long userId) { FileResultEntity entity = fileResultMapper.selectById(resultId); if (entity == null || !"DEDUPE".equals(entity.getModuleType()) || !userId.equals(entity.getUserId())) { - throw new BusinessException("\u8bb0\u5f55\u4e0d\u5b58\u5728"); + throw new BusinessException("记录不存在"); } fileResultMapper.deleteById(resultId); } @@ -248,11 +248,11 @@ public class DedupeRunService { public Resource getResultFile(Long resultId, Long userId) { FileResultEntity entity = fileResultMapper.selectById(resultId); if (entity == null || entity.getResultFileUrl() == null || !"DEDUPE".equals(entity.getModuleType()) || !userId.equals(entity.getUserId())) { - throw new BusinessException("\u7ed3\u679c\u6587\u4ef6\u4e0d\u5b58\u5728"); + throw new BusinessException("结果文件不存在"); } File file = new File(entity.getResultFileUrl()); if (!file.exists()) { - throw new BusinessException("\u7ed3\u679c\u6587\u4ef6\u4e0d\u5b58\u5728"); + throw new BusinessException("结果文件不存在"); } return new FileSystemResource(file); } @@ -342,18 +342,18 @@ public class DedupeRunService { Sheet sheet = workbook.getSheetAt(0); Row headerRow = sheet.getRow(0); if (headerRow == null) { - throw new BusinessException("Excel \u8868\u5934\u4e3a\u7a7a"); + throw new BusinessException("Excel 表头为空"); } Map headerMap = buildHeaderMap(headerRow, formatter); if (headerMap.isEmpty()) { - throw new BusinessException("Excel \u8868\u5934\u4e3a\u7a7a"); + throw new BusinessException("Excel 表头为空"); } List missing = selectedColumns.stream() .filter(column -> !headerMap.containsKey(column)) .toList(); if (!missing.isEmpty()) { - throw new BusinessException("\u7f3a\u5c11\u5217\uff1a" + String.join("\u3001", missing)); + throw new BusinessException("缺少列:" + String.join("、", missing)); } List selectedIndexes = new ArrayList<>(selectedColumns.size()); @@ -478,7 +478,7 @@ public class DedupeRunService { zos.closeEntry(); } } catch (Exception ex) { - throw new BusinessException("\u53bb\u91cd\u7ed3\u679c\u6253\u5305\u5931\u8d25\uff1a" + ex.getMessage()); + throw new BusinessException("去重结果打包失败:" + ex.getMessage()); } return zipFile; } @@ -602,7 +602,7 @@ public class DedupeRunService { boolean previousWhitespace = false; for (int i = start; i < end; i++) { char ch = value.charAt(i); - if (ch == '\uFEFF') { + if (ch == 0xFEFF) { if (builder == null) { builder = new StringBuilder(value.length()); builder.append(value, start, i); @@ -629,11 +629,11 @@ public class DedupeRunService { } private boolean isTrimmedWhitespace(char ch) { - return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\u3000' || Character.isWhitespace(ch); + return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == 0x3000 || Character.isWhitespace(ch); } private boolean isNormalizedWhitespace(char ch) { - return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\u3000' || Character.isWhitespace(ch); + return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == 0x3000 || Character.isWhitespace(ch); } private record DedupeArchiveEntry(String relativePath, String inputName, File outputFile) { diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/file/service/LocalFileStorageService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/file/service/LocalFileStorageService.java index 1263a42..aec1237 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/file/service/LocalFileStorageService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/file/service/LocalFileStorageService.java @@ -109,8 +109,8 @@ public class LocalFileStorageService { if (value == null) { return ""; } - return value.replace("\ufeff", "") - .replace("\u3000", " ") + return value.replace(String.valueOf((char) 0xFEFF), "") + .replace((char) 0x3000, ' ') .replace("\r\n", " ") .replace("\r", " ") .replace("\n", " ") diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/patroldelete/service/PatrolDeleteTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/patroldelete/service/PatrolDeleteTaskService.java index 21298b0..8f6d54d 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/patroldelete/service/PatrolDeleteTaskService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/patroldelete/service/PatrolDeleteTaskService.java @@ -281,7 +281,6 @@ public class PatrolDeleteTaskService { return vo; } - @Transactional public void submitResult(Long taskId, PatrolDeleteSubmitResultRequest request) { if (taskId == null || taskId <= 0) { throw new BusinessException("taskId 不合法"); @@ -334,7 +333,6 @@ public class PatrolDeleteTaskService { tryFinalizeTask(taskId, false); } - @Transactional public boolean tryFinalizeTask(Long taskId, boolean fromCompensation) { if (taskId == null || taskId <= 0) { return false; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/model/dto/PriceTrackSubmitResultRequest.java b/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/model/dto/PriceTrackSubmitResultRequest.java index e0aeff8..f3810a5 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/model/dto/PriceTrackSubmitResultRequest.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/model/dto/PriceTrackSubmitResultRequest.java @@ -1,5 +1,6 @@ package com.nanri.aiimage.modules.pricetrack.model.dto; +import com.fasterxml.jackson.annotation.JsonAlias; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -88,6 +89,10 @@ public class PriceTrackSubmitResultRequest { @Schema(description = "购物车店铺名", example = "店铺A") private String cartShopName; + @JsonAlias({"changedPrice", "changePrice", "modifiedPrice", "newPrice", "targetPrice", "updatedPrice", "改价价格"}) + @Schema(description = "改价价格", example = "18.99") + private String priceChangePrice; + @Schema(description = "改价情况", example = "UPDATED") private String priceChangeStatus; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackExcelAssemblyService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackExcelAssemblyService.java index 6d313a1..c042541 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackExcelAssemblyService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackExcelAssemblyService.java @@ -29,6 +29,7 @@ public class PriceTrackExcelAssemblyService { "第一名", "第二名", "购物车店铺名", + "改价价格", "改价情况", "修改次数", "状态" @@ -40,13 +41,14 @@ public class PriceTrackExcelAssemblyService { workbook.setCompressTempFiles(true); try (FileOutputStream fos = new FileOutputStream(outputXlsx)) { for (ProductRiskCountryCode code : ProductRiskCountryCode.values()) { - String sheetName = code.name(); + String countryCode = code.name(); + String sheetName = sheetNameOf(code); Sheet sheet = workbook.createSheet(sheetName); Row headerRow = sheet.createRow(0); for (int i = 0; i < RESULT_HEADER.length; i++) { headerRow.createCell(i).setCellValue(RESULT_HEADER[i]); } - List rows = safe.getOrDefault(sheetName, List.of()); + List rows = safe.getOrDefault(countryCode, List.of()); int rowIdx = 1; for (PriceTrackSubmitResultRequest.AsinResult item : rows) { if (item == null) { @@ -62,9 +64,10 @@ public class PriceTrackExcelAssemblyService { row.createCell(6).setCellValue(valueOf(item.getFirstPlace())); row.createCell(7).setCellValue(valueOf(item.getSecondPlace())); row.createCell(8).setCellValue(valueOf(item.getCartShopName())); - row.createCell(9).setCellValue(valueOf(item.getPriceChangeStatus())); - row.createCell(10).setCellValue(valueOf(item.getModifyCount())); - row.createCell(11).setCellValue(valueOf(item.getStatus())); + row.createCell(9).setCellValue(valueOf(item.getPriceChangePrice())); + row.createCell(10).setCellValue(valueOf(item.getPriceChangeStatus())); + row.createCell(11).setCellValue(valueOf(item.getModifyCount())); + row.createCell(12).setCellValue(valueOf(item.getStatus())); } applyDefaultColumnWidths(sheet); } @@ -113,8 +116,18 @@ public class PriceTrackExcelAssemblyService { return value == null ? "" : value; } + private static String sheetNameOf(ProductRiskCountryCode code) { + return switch (code) { + case DE -> "德国"; + case FR -> "法国"; + case ES -> "西班牙"; + case IT -> "意大利"; + case UK -> "英国"; + }; + } + private void applyDefaultColumnWidths(Sheet sheet) { - int[] widths = {22, 18, 12, 12, 12, 12, 20, 20, 22, 18, 12, 12}; + int[] widths = {22, 18, 12, 12, 12, 12, 20, 20, 22, 14, 18, 12, 12}; for (int i = 0; i < widths.length; i++) { sheet.setColumnWidth(i, widths[i] * 256); } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackTaskService.java index e9ece5c..34043cb 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackTaskService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackTaskService.java @@ -127,6 +127,21 @@ public class PriceTrackTaskService { return tasks; } + private List selectTaskStatusesByIdsInBatches(List taskIds) { + List tasks = new ArrayList<>(); + if (taskIds == null || taskIds.isEmpty()) { + return tasks; + } + int batchSize = Math.max(1, taskPressureProperties.getDbSelectBatchSize()); + for (int start = 0; start < taskIds.size(); start += batchSize) { + int end = Math.min(start + batchSize, taskIds.size()); + tasks.addAll(fileTaskMapper.selectList(new LambdaQueryWrapper() + .select(FileTaskEntity::getId, FileTaskEntity::getStatus) + .in(FileTaskEntity::getId, taskIds.subList(start, end)))); + } + return tasks; + } + public PriceTrackDashboardVo dashboard(Long userId) { if (userId == null || userId <= 0) throw new BusinessException("user_id 不合法"); PriceTrackDashboardVo vo = new PriceTrackDashboardVo(); @@ -166,7 +181,7 @@ public class PriceTrackTaskService { .distinct() .toList(); if (!taskIds.isEmpty()) { - List tasks = selectTasksByIdsInBatches(taskIds); + List tasks = selectTaskStatusesByIdsInBatches(taskIds); for (FileTaskEntity t : tasks) { if (t != null) statusByTaskId.put(t.getId(), t.getStatus()); } @@ -383,7 +398,6 @@ public class PriceTrackTaskService { return vo; } - @Transactional public void submitResult(Long taskId, PriceTrackSubmitResultRequest request) { if (taskId == null || taskId <= 0) throw new BusinessException("taskId 不合法"); if (request == null || request.getShops() == null || request.getShops().isEmpty()) { @@ -458,7 +472,6 @@ public class PriceTrackTaskService { updateTaskStatusFromLatestRows(task, latest); } - @Transactional public boolean tryFinalizeTask(Long taskId, boolean fromCompensation) { if (taskId == null || taskId <= 0) return false; FileTaskEntity task = loadTaskForExecution(taskId); @@ -1139,6 +1152,7 @@ public class PriceTrackTaskService { merged.setFirstPlace(firstNonBlank(incoming.getFirstPlace(), merged.getFirstPlace())); merged.setSecondPlace(firstNonBlank(incoming.getSecondPlace(), merged.getSecondPlace())); merged.setCartShopName(firstNonBlank(incoming.getCartShopName(), merged.getCartShopName())); + merged.setPriceChangePrice(firstNonBlank(incoming.getPriceChangePrice(), merged.getPriceChangePrice())); merged.setPriceChangeStatus(firstNonBlank(incoming.getPriceChangeStatus(), merged.getPriceChangeStatus())); merged.setModifyCount(firstNonBlank(incoming.getModifyCount(), merged.getModifyCount())); merged.setStatus(firstNonBlank(incoming.getStatus(), merged.getStatus())); @@ -1163,6 +1177,7 @@ public class PriceTrackTaskService { out.setFirstPlace(row.getFirstPlace()); out.setSecondPlace(row.getSecondPlace()); out.setCartShopName(row.getCartShopName()); + out.setPriceChangePrice(row.getPriceChangePrice()); out.setPriceChangeStatus(row.getPriceChangeStatus()); out.setModifyCount(row.getModifyCount()); out.setStatus(row.getStatus()); diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/productrisk/service/ProductRiskTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/productrisk/service/ProductRiskTaskService.java index 1744916..d0e55c8 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/productrisk/service/ProductRiskTaskService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/productrisk/service/ProductRiskTaskService.java @@ -124,6 +124,21 @@ public class ProductRiskTaskService { return tasks; } + private List selectTaskStatusesByIdsInBatches(List taskIds) { + List tasks = new ArrayList<>(); + if (taskIds == null || taskIds.isEmpty()) { + return tasks; + } + int batchSize = Math.max(1, taskPressureProperties.getDbSelectBatchSize()); + for (int start = 0; start < taskIds.size(); start += batchSize) { + int end = Math.min(start + batchSize, taskIds.size()); + tasks.addAll(fileTaskMapper.selectList(new LambdaQueryWrapper() + .select(FileTaskEntity::getId, FileTaskEntity::getStatus) + .in(FileTaskEntity::getId, taskIds.subList(start, end)))); + } + return tasks; + } + public ProductRiskDashboardVo dashboard(Long userId) { if (userId == null || userId <= 0) { throw new BusinessException("user_id 不合法"); @@ -166,7 +181,7 @@ public class ProductRiskTaskService { .distinct() .toList(); if (!taskIds.isEmpty()) { - List tasks = selectTasksByIdsInBatches(taskIds); + List tasks = selectTaskStatusesByIdsInBatches(taskIds); for (FileTaskEntity t : tasks) { if (t != null) { statusByTaskId.put(t.getId(), t.getStatus()); @@ -465,7 +480,6 @@ public class ProductRiskTaskService { return vo; } - @Transactional public void submitResult(Long taskId, ProductRiskSubmitResultRequest request) { if (taskId == null || taskId <= 0) { throw new BusinessException("taskId 不合法"); @@ -630,7 +644,6 @@ public class ProductRiskTaskService { cleanupTaskCacheIfTerminal(taskId, task.getStatus()); } - @Transactional public boolean tryFinalizeTask(Long taskId, boolean fromCompensation) { if (taskId == null || taskId <= 0) { return false; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/queryasin/service/QueryAsinTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/queryasin/service/QueryAsinTaskService.java index b266054..35c74a4 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/queryasin/service/QueryAsinTaskService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/queryasin/service/QueryAsinTaskService.java @@ -287,7 +287,6 @@ public class QueryAsinTaskService { return vo; } - @Transactional public void submitResult(Long taskId, QueryAsinSubmitResultRequest request) { if (taskId == null || taskId <= 0) { throw new BusinessException("taskId 不合法"); @@ -340,7 +339,6 @@ public class QueryAsinTaskService { tryFinalizeTask(taskId, false); } - @Transactional public boolean tryFinalizeTask(Long taskId, boolean fromCompensation) { if (taskId == null || taskId <= 0) { return false; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/mapper/ShopManageGroupMapper.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/mapper/ShopManageGroupMapper.java index fb44b6c..39858ea 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/mapper/ShopManageGroupMapper.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/mapper/ShopManageGroupMapper.java @@ -3,7 +3,68 @@ package com.nanri.aiimage.modules.shopkey.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.nanri.aiimage.modules.shopkey.model.entity.ShopManageGroupEntity; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; @Mapper public interface ShopManageGroupMapper extends BaseMapper { + + @Select(""" + SELECT DISTINCT accessible_group_id + FROM ( + SELECT g.id AS accessible_group_id + FROM biz_shop_manage_group g + WHERE g.created_by_id = #{operatorId} + OR g.user_id = #{operatorId} + UNION + SELECT gm.group_id AS accessible_group_id + FROM biz_shop_manage_group_member gm + WHERE gm.user_id = #{operatorId} + UNION + SELECT g2.id AS accessible_group_id + FROM biz_shop_manage_group_member gm + INNER JOIN biz_shop_manage_group g1 ON g1.id = gm.group_id + INNER JOIN biz_shop_manage_group g2 + ON g2.created_by_id = COALESCE(g1.created_by_id, g1.user_id) + OR g2.user_id = COALESCE(g1.created_by_id, g1.user_id) + WHERE gm.user_id = #{operatorId} + AND COALESCE(g1.created_by_id, g1.user_id) IS NOT NULL + UNION + SELECT member_groups.id AS accessible_group_id + FROM biz_shop_manage_group leader_group + INNER JOIN biz_shop_manage_group_member direct_member ON direct_member.group_id = leader_group.id + INNER JOIN biz_shop_manage_group member_groups + ON member_groups.created_by_id = direct_member.user_id + OR member_groups.user_id = direct_member.user_id + WHERE leader_group.created_by_id = #{operatorId} + OR leader_group.user_id = #{operatorId} + ) accessible_groups + WHERE accessible_group_id IS NOT NULL + ORDER BY accessible_group_id ASC + """) + List selectAccessibleGroupIds(@Param("operatorId") Long operatorId); + + @Select(""" + SELECT DISTINCT visible_user_id + FROM ( + SELECT #{operatorId} AS visible_user_id + UNION + SELECT direct_member.user_id AS visible_user_id + FROM biz_shop_manage_group leader_group + INNER JOIN biz_shop_manage_group_member direct_member ON direct_member.group_id = leader_group.id + WHERE leader_group.created_by_id = #{operatorId} + OR leader_group.user_id = #{operatorId} + UNION + SELECT COALESCE(direct_group.created_by_id, direct_group.user_id) AS visible_user_id + FROM biz_shop_manage_group_member membership + INNER JOIN biz_shop_manage_group direct_group ON direct_group.id = membership.group_id + WHERE membership.user_id = #{operatorId} + AND COALESCE(direct_group.created_by_id, direct_group.user_id) IS NOT NULL + ) visible_users + WHERE visible_user_id IS NOT NULL + ORDER BY visible_user_id ASC + """) + List selectAccessibleCreatorUserIds(@Param("operatorId") Long operatorId); } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/QueryAsinService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/QueryAsinService.java index 2e89945..8517d93 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/QueryAsinService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/QueryAsinService.java @@ -40,7 +40,7 @@ public class QueryAsinService { private static final List SUPPORTED_COUNTRIES = List.of("DE", "UK", "FR", "IT", "ES"); private static final String KEY_SEPARATOR = Character.toString((char) 1); - private static final String UTF8_BOM = Character.toString(0xFEFF); + private static final String UTF8_BOM = String.valueOf((char) 0xFEFF); private final QueryAsinMapper queryAsinMapper; private final ShopManageGroupService shopManageGroupService; @@ -79,7 +79,7 @@ public class QueryAsinService { List rows = queryAsinMapper .selectList(query.last("LIMIT " + ((safePage - 1) * safePageSize) + ", " + safePageSize)); - Map groupNameById = buildGroupNameMap(rows.stream() + Map groupNameById = shopManageGroupService.buildGroupNameMap(rows.stream() .map(QueryAsinEntity::getGroupId) .filter(id -> id != null && id > 0) .distinct() @@ -316,22 +316,6 @@ public class QueryAsinService { return vo; } - private Map buildGroupNameMap(List groupIds) { - if (groupIds == null || groupIds.isEmpty()) { - return Map.of(); - } - LinkedHashMap map = new LinkedHashMap<>(); - for (Long groupId : groupIds) { - try { - ShopManageGroupEntity group = shopManageGroupService.getById(groupId); - map.put(groupId, group.getGroupName()); - } catch (Exception ignored) { - map.put(groupId, ""); - } - } - return map; - } - private Set normalizeCountries(List countries) { LinkedHashSet normalized = new LinkedHashSet<>(); if (countries != null) { @@ -384,11 +368,11 @@ public class QueryAsinService { return upper; } return switch (normalizeHeaderKey(normalized)) { - case "\u5fb7\u56fd", "\u5fb7", "de", "germany", "german", "deutschland" -> "DE"; - case "\u82f1\u56fd", "\u82f1", "uk", "gb", "greatbritain", "britain", "unitedkingdom" -> "UK"; - case "\u6cd5\u56fd", "\u6cd5", "fr", "france", "french" -> "FR"; - case "\u610f\u5927\u5229", "\u610f", "it", "italy", "italian" -> "IT"; - case "\u897f\u73ed\u7259", "\u897f", "es", "spain", "spanish", "espana" -> "ES"; + case "德国", "德", "de", "germany", "german", "deutschland" -> "DE"; + case "英国", "英", "uk", "gb", "greatbritain", "britain", "unitedkingdom" -> "UK"; + case "法国", "法", "fr", "france", "french" -> "FR"; + case "意大利", "意", "it", "italy", "italian" -> "IT"; + case "西班牙", "西", "es", "spain", "spanish", "espana" -> "ES"; default -> ""; }; } @@ -860,11 +844,11 @@ public class QueryAsinService { if (index != null) { return index; } - index = firstHeader("groupname", "\u5206\u7ec4id", "\u5206\u7ec4"); + index = firstHeader("groupname", "分组id", "分组"); if (index != null) { return index; } - return findHeaderByPredicate(key -> key.contains("\u5206\u7ec4") || key.contains("group")); + return findHeaderByPredicate(key -> key.contains("分组") || key.contains("group")); } private Integer resolveShopHeaderIndex() { @@ -873,13 +857,13 @@ public class QueryAsinService { return index; } index = firstHeader( - "\u5e97\u94fa\u540d", "\u5e97\u94fa\u540d\u79f0", "\u5e97\u94fa\u8d26\u53f7", "\u8d26\u53f7", + "店铺名", "店铺名称", "店铺账号", "账号", "account", "selleraccount", "storeaccount", "storename"); if (index != null) { return index; } return findHeaderByPredicate(key -> - (key.contains("\u5e97\u94fa") && (key.contains("\u540d") || key.contains("\u8d26\u53f7"))) + (key.contains("店铺") && (key.contains("名") || key.contains("账号"))) || (key.contains("shop") && (key.contains("name") || key.contains("account"))) || key.contains("selleraccount") || key.contains("storeaccount")); @@ -891,32 +875,32 @@ public class QueryAsinService { return index; } return switch (country) { - case "DE" -> firstHeader("\u5fb7\u56fd", "\u5fb7", "\u5fb7\u56fdasin"); - case "UK" -> firstHeader("\u82f1\u56fd", "\u82f1", "\u82f1\u56fdasin"); - case "FR" -> firstHeader("\u6cd5\u56fd", "\u6cd5", "\u6cd5\u56fdasin"); - case "IT" -> firstHeader("\u610f\u5927\u5229", "\u610f", "\u610f\u5927\u5229asin"); - case "ES" -> firstHeader("\u897f\u73ed\u7259", "\u897f", "\u897f\u73ed\u7259asin"); + case "DE" -> firstHeader("德国", "德", "德国asin"); + case "UK" -> firstHeader("英国", "英", "英国asin"); + case "FR" -> firstHeader("法国", "法", "法国asin"); + case "IT" -> firstHeader("意大利", "意", "意大利asin"); + case "ES" -> firstHeader("西班牙", "西", "西班牙asin"); default -> null; }; } private Integer findSingleCountryIndex() { Integer index = firstHeader( - "\u4e2d\u6587\u56fd\u5bb6\u540d", "\u56fd\u5bb6", "\u56fd\u5bb6\u540d", "\u56fd\u5bb6\u7ad9\u70b9", - "\u7ad9\u70b9", "country", "countryname", "marketplace", "site"); + "中文国家名", "国家", "国家名", "国家站点", + "站点", "country", "countryname", "marketplace", "site"); if (index != null) { return index; } return findHeaderByPredicate(key -> - key.contains("\u56fd\u5bb6") - || key.contains("\u7ad9\u70b9") + key.contains("国家") + || key.contains("站点") || key.contains("country") || key.contains("marketplace") || key.equals("site")); } private Integer findSingleAsinIndex() { - return firstHeader("asin", "\u67e5\u8be2asin", "\u4ea7\u54c1asin", "\u94fe\u63a5asin"); + return firstHeader("asin", "查询asin", "产品asin", "链接asin"); } private boolean hasWideCountryColumns() { diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageGroupService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageGroupService.java index 204beea..73a8471 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageGroupService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageGroupService.java @@ -48,6 +48,7 @@ public class ShopManageGroupService { public List list(Long createdById) { return toVoList(groupMapper.selectList(new LambdaQueryWrapper() .eq(createdById != null && createdById > 0, ShopManageGroupEntity::getCreatedById, createdById) + .or(createdById != null && createdById > 0, wrapper -> wrapper.eq(ShopManageGroupEntity::getUserId, createdById)) .orderByAsc(ShopManageGroupEntity::getId))); } @@ -74,40 +75,44 @@ public class ShopManageGroupService { .collect(Collectors.toCollection(LinkedHashSet::new)); } Long normalizedOperatorId = normalizePositiveId(operatorId, "操作人不能为空"); - LinkedHashSet groupIds = groupMapper.selectList(new LambdaQueryWrapper() - .select(ShopManageGroupEntity::getId) - .eq(ShopManageGroupEntity::getCreatedById, normalizedOperatorId)) + return groupMapper.selectAccessibleGroupIds(normalizedOperatorId) .stream() - .map(ShopManageGroupEntity::getId) .filter(id -> id != null && id > 0) .collect(Collectors.toCollection(LinkedHashSet::new)); - List memberGroupIds = groupMemberMapper.selectList(new LambdaQueryWrapper() - .select(ShopManageGroupMemberEntity::getGroupId) - .eq(ShopManageGroupMemberEntity::getUserId, normalizedOperatorId)) - .stream() - .map(ShopManageGroupMemberEntity::getGroupId) - .filter(id -> id != null && id > 0) - .toList(); - groupIds.addAll(memberGroupIds); - if (!memberGroupIds.isEmpty()) { - LinkedHashSet leaderIds = groupMapper.selectList(new LambdaQueryWrapper() - .select(ShopManageGroupEntity::getCreatedById) - .in(ShopManageGroupEntity::getId, memberGroupIds)) - .stream() - .map(ShopManageGroupEntity::getCreatedById) - .filter(id -> id != null && id > 0) - .collect(Collectors.toCollection(LinkedHashSet::new)); - if (!leaderIds.isEmpty()) { - groupIds.addAll(groupMapper.selectList(new LambdaQueryWrapper() - .select(ShopManageGroupEntity::getId) - .in(ShopManageGroupEntity::getCreatedById, leaderIds)) - .stream() - .map(ShopManageGroupEntity::getId) - .filter(id -> id != null && id > 0) - .toList()); - } + } + + public Set listAccessibleCreatorUserIds(Long operatorId, boolean superAdmin) { + if (superAdmin) { + return Set.of(); } - return groupIds; + Long normalizedOperatorId = normalizePositiveId(operatorId, "操作人不能为空"); + return groupMapper.selectAccessibleCreatorUserIds(normalizedOperatorId) + .stream() + .filter(id -> id != null && id > 0) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + + public Map buildGroupNameMap(List groupIds) { + if (groupIds == null || groupIds.isEmpty()) { + return Map.of(); + } + List normalizedGroupIds = groupIds.stream() + .filter(id -> id != null && id > 0) + .distinct() + .toList(); + if (normalizedGroupIds.isEmpty()) { + return Map.of(); + } + return groupMapper.selectList(new LambdaQueryWrapper() + .select(ShopManageGroupEntity::getId, ShopManageGroupEntity::getGroupName) + .in(ShopManageGroupEntity::getId, normalizedGroupIds)) + .stream() + .filter(group -> group.getId() != null) + .collect(Collectors.toMap( + ShopManageGroupEntity::getId, + group -> normalizeBlank(group.getGroupName()), + (left, right) -> left, + LinkedHashMap::new)); } @Transactional @@ -207,8 +212,9 @@ public class ShopManageGroupService { return; } Long normalizedOperatorId = normalizePositiveId(operatorId, "操作人不能为空"); - boolean createdBySelf = entity.getCreatedById() != null && entity.getCreatedById().equals(normalizedOperatorId); - if (!createdBySelf) { + boolean leaderBySelf = entity.getCreatedById() != null && entity.getCreatedById().equals(normalizedOperatorId); + boolean legacyLeaderBySelf = entity.getUserId() != null && entity.getUserId().equals(normalizedOperatorId); + if (!leaderBySelf && !legacyLeaderBySelf) { throw new BusinessException("只有组长才能维护分组"); } } @@ -332,8 +338,9 @@ public class ShopManageGroupService { LinkedHashSet userIds = new LinkedHashSet<>(); for (ShopManageGroupEntity group : groups) { - if (group.getCreatedById() != null && group.getCreatedById() > 0) { - userIds.add(group.getCreatedById()); + Long leaderUserId = resolveGroupLeaderUserId(group); + if (leaderUserId != null && leaderUserId > 0) { + userIds.add(leaderUserId); } } for (List members : membersByGroupId.values()) { @@ -355,8 +362,9 @@ public class ShopManageGroupService { ShopManageGroupItemVo vo = new ShopManageGroupItemVo(); vo.setId(entity.getId()); vo.setGroupName(entity.getGroupName()); - vo.setLeaderUserId(entity.getCreatedById()); - AdminUserEntity leaderUser = entity.getCreatedById() == null ? null : userById.get(entity.getCreatedById()); + Long leaderUserId = resolveGroupLeaderUserId(entity); + vo.setLeaderUserId(leaderUserId); + AdminUserEntity leaderUser = leaderUserId == null ? null : userById.get(leaderUserId); vo.setLeaderUsername(leaderUser == null ? "" : normalizeBlank(leaderUser.getUsername())); List memberEntities = membersByGroupId.getOrDefault(entity.getId(), Collections.emptyList()); @@ -380,4 +388,14 @@ public class ShopManageGroupService { } return result; } + + private Long resolveGroupLeaderUserId(ShopManageGroupEntity entity) { + if (entity == null) { + return null; + } + if (entity.getCreatedById() != null && entity.getCreatedById() > 0) { + return entity.getCreatedById(); + } + return entity.getUserId(); + } } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageService.java index c994cde..5ab2b6f 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/ShopManageService.java @@ -15,7 +15,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -33,6 +32,7 @@ public class ShopManageService { long safePageSize = Math.min(Math.max(pageSize, 1), 100); String safeShopName = shopName == null ? "" : shopName.trim(); Set accessibleGroupIds = superAdmin ? Set.of() : shopManageGroupService.listAccessibleGroupIds(operatorId, false); + Set accessibleCreatorUserIds = superAdmin ? Set.of() : shopManageGroupService.listAccessibleCreatorUserIds(operatorId, false); LambdaQueryWrapper query = new LambdaQueryWrapper() .eq(groupId != null && groupId > 0, ShopManageEntity::getGroupId, groupId) @@ -40,20 +40,30 @@ public class ShopManageService { .orderByDesc(ShopManageEntity::getId); if (!superAdmin) { - Long normalizedOperatorId = normalizePositiveId(operatorId, "操作人不能为空"); - query.and(wrapper -> { - wrapper.eq(ShopManageEntity::getCreatedById, normalizedOperatorId); - if (!accessibleGroupIds.isEmpty()) { - wrapper.or().in(ShopManageEntity::getGroupId, accessibleGroupIds); - } - }); + normalizePositiveId(operatorId, "操作人不能为空"); + if (accessibleGroupIds.isEmpty() && accessibleCreatorUserIds.isEmpty()) { + query.eq(ShopManageEntity::getId, -1L); + } else { + query.and(wrapper -> { + boolean hasCreatorFilter = !accessibleCreatorUserIds.isEmpty(); + if (hasCreatorFilter) { + wrapper.in(ShopManageEntity::getCreatedById, accessibleCreatorUserIds); + } + if (!accessibleGroupIds.isEmpty()) { + if (hasCreatorFilter) { + wrapper.or(); + } + wrapper.in(ShopManageEntity::getGroupId, accessibleGroupIds); + } + }); + } } Long total = shopManageMapper.selectCount(query); List rows = shopManageMapper .selectList(query.last("LIMIT " + ((safePage - 1) * safePageSize) + ", " + safePageSize)); - Map groupNameById = buildGroupNameMap(rows.stream() + Map groupNameById = shopManageGroupService.buildGroupNameMap(rows.stream() .map(ShopManageEntity::getGroupId) .filter(id -> id != null && id > 0) .distinct() @@ -190,22 +200,6 @@ public class ShopManageService { } } - private Map buildGroupNameMap(List groupIds) { - if (groupIds == null || groupIds.isEmpty()) { - return Map.of(); - } - LinkedHashMap map = new LinkedHashMap<>(); - for (Long groupId : groupIds) { - try { - ShopManageGroupEntity group = shopManageGroupService.getById(groupId); - map.put(groupId, group.getGroupName()); - } catch (Exception ignored) { - map.put(groupId, ""); - } - } - return map; - } - private ShopManageItemVo toItemVo(ShopManageEntity entity, String groupName) { ShopManageItemVo vo = new ShopManageItemVo(); vo.setId(entity.getId()); diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/SkipPriceAsinService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/SkipPriceAsinService.java index db38556..5499804 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/SkipPriceAsinService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/service/SkipPriceAsinService.java @@ -62,7 +62,7 @@ public class SkipPriceAsinService { List rows = skipPriceAsinMapper .selectList(query.last("LIMIT " + ((safePage - 1) * safePageSize) + ", " + safePageSize)); - Map groupNameById = buildGroupNameMap(rows.stream() + Map groupNameById = shopManageGroupService.buildGroupNameMap(rows.stream() .map(SkipPriceAsinEntity::getGroupId) .filter(id -> id != null && id > 0) .distinct() @@ -174,22 +174,6 @@ public class SkipPriceAsinService { return vo; } - private Map buildGroupNameMap(List groupIds) { - if (groupIds == null || groupIds.isEmpty()) { - return Map.of(); - } - LinkedHashMap map = new LinkedHashMap<>(); - for (Long groupId : groupIds) { - try { - ShopManageGroupEntity group = shopManageGroupService.getById(groupId); - map.put(groupId, group.getGroupName()); - } catch (Exception ignored) { - map.put(groupId, ""); - } - } - return map; - } - private Set normalizeCountries(List countries) { LinkedHashSet normalized = new LinkedHashSet<>(); if (countries != null) { diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopmatch/service/ShopMatchTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopmatch/service/ShopMatchTaskService.java index 4f18f2d..17035e6 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopmatch/service/ShopMatchTaskService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopmatch/service/ShopMatchTaskService.java @@ -132,6 +132,21 @@ public class ShopMatchTaskService { return tasks; } + private List selectTaskStatusesByIdsInBatches(List taskIds) { + List tasks = new ArrayList<>(); + if (taskIds == null || taskIds.isEmpty()) { + return tasks; + } + int batchSize = Math.max(1, taskPressureProperties.getDbSelectBatchSize()); + for (int start = 0; start < taskIds.size(); start += batchSize) { + int end = Math.min(start + batchSize, taskIds.size()); + tasks.addAll(fileTaskMapper.selectList(new LambdaQueryWrapper() + .select(FileTaskEntity::getId, FileTaskEntity::getStatus) + .in(FileTaskEntity::getId, taskIds.subList(start, end)))); + } + return tasks; + } + public ProductRiskDashboardVo dashboard(Long userId) { if (userId == null || userId <= 0) { throw new BusinessException("user_id 不合法"); @@ -174,7 +189,7 @@ public class ShopMatchTaskService { .distinct() .toList(); if (!taskIds.isEmpty()) { - for (FileTaskEntity task : selectTasksByIdsInBatches(taskIds)) { + for (FileTaskEntity task : selectTaskStatusesByIdsInBatches(taskIds)) { if (task != null) { statusByTaskId.put(task.getId(), task.getStatus()); } @@ -465,7 +480,6 @@ public class ShopMatchTaskService { } - @Transactional public void submitResult(Long taskId, ShopMatchSubmitResultRequest request) { if (taskId == null || taskId <= 0) { throw new BusinessException("taskId 不合法"); @@ -540,7 +554,6 @@ public class ShopMatchTaskService { updateTaskStatusFromLatestRows(task, latest); } - @Transactional public boolean tryFinalizeTask(Long taskId, boolean fromCompensation) { if (taskId == null || taskId <= 0) { return false; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/split/service/SplitRunService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/split/service/SplitRunService.java index 474e36e..91630f6 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/split/service/SplitRunService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/split/service/SplitRunService.java @@ -456,8 +456,8 @@ public class SplitRunService { if (value == null) { return ""; } - return value.replace("\ufeff", "") - .replace("\u3000", " ") + return value.replace(String.valueOf((char) 0xFEFF), "") + .replace((char) 0x3000, ' ') .replace("\r\n", " ") .replace("\r", " ") .replace("\n", " ") diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java index 8166c2a..8365bf5 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskFileJobService.java @@ -23,7 +23,6 @@ public class TaskFileJobService { private final TaskFileJobMapper taskFileJobMapper; private final ApplicationEventPublisher applicationEventPublisher; - @Transactional public TaskFileJobEntity enqueueAssembleResult(Long taskId, String moduleType, Long resultId, String scopeKey) { if (taskId == null || taskId <= 0 || moduleType == null || moduleType.isBlank() || resultId == null || resultId <= 0) { return null; @@ -146,7 +145,6 @@ public class TaskFileJobService { return reset; } - @Transactional public boolean markRunning(Long jobId) { if (jobId == null || jobId <= 0) { return false; @@ -158,7 +156,6 @@ public class TaskFileJobService { .set(TaskFileJobEntity::getUpdatedAt, LocalDateTime.now())) > 0; } - @Transactional public void touchRunning(Long jobId) { if (jobId == null || jobId <= 0) { return; @@ -169,7 +166,6 @@ public class TaskFileJobService { .set(TaskFileJobEntity::getUpdatedAt, LocalDateTime.now())); } - @Transactional public void markSuccess(TaskFileJobEntity job, String resultFileUrl) { taskFileJobMapper.update(null, new LambdaUpdateWrapper() .eq(TaskFileJobEntity::getId, job.getId()) @@ -180,7 +176,6 @@ public class TaskFileJobService { .set(TaskFileJobEntity::getFinishedAt, LocalDateTime.now())); } - @Transactional public void markFailed(TaskFileJobEntity job, String message) { int retryCount = job.getRetryCount() == null ? 0 : job.getRetryCount() + 1; taskFileJobMapper.update(null, new LambdaUpdateWrapper() diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultItemService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultItemService.java index d955967..66b9de9 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultItemService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultItemService.java @@ -25,7 +25,6 @@ public class TaskResultItemService { private final ObjectMapper objectMapper; private final TransientPayloadStorageService transientPayloadStorageService; - @Transactional public void replaceResultSnapshot(Long taskId, String moduleType, Long resultId, String scopeKey, Object snapshot) { if (taskId == null || taskId <= 0 || isBlank(moduleType) || resultId == null || resultId <= 0 || snapshot == null) { return; @@ -35,7 +34,6 @@ public class TaskResultItemService { upsert(taskId, moduleType, resultId, normalizedScopeKey, itemKey, null, null, null, snapshot); } - @Transactional public void replaceTaskSnapshots(Long taskId, String moduleType, List snapshots, SnapshotKeyResolver resolver) { if (taskId == null || taskId <= 0 || isBlank(moduleType) || snapshots == null) { return; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultPayloadService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultPayloadService.java index e6fdf36..d6e094a 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultPayloadService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResultPayloadService.java @@ -25,7 +25,6 @@ public class TaskResultPayloadService { private final ObjectMapper objectMapper; private final TransientPayloadStorageService transientPayloadStorageService; - @Transactional public void saveLatest(Long taskId, String moduleType, String scopeKey, Object payload) { if (taskId == null || taskId <= 0 || isBlank(moduleType) || isBlank(scopeKey) || payload == null) { return; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoAuthService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoAuthService.java index bc206da..f8c2a15 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoAuthService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoAuthService.java @@ -212,7 +212,7 @@ public boolean isIpWhitelistError(BusinessException ex) { if (value == null) { return ""; } - return value.replace("\u3000", " ") + return value.replace((char) 0x3000, ' ') .trim(); } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopIndexService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopIndexService.java index a446d9a..44959e5 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopIndexService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/ziniao/service/ZiniaoShopIndexService.java @@ -357,7 +357,7 @@ public class ZiniaoShopIndexService { if (value == null) { return ""; } - return value.replace("\u3000", " ").trim(); + return value.replace((char) 0x3000, ' ').trim(); } private ZiniaoShopMatchResultVo pendingResult(String message) { diff --git a/backend-java/src/main/resources/application.yml b/backend-java/src/main/resources/application.yml index 8184634..f1f6719 100644 --- a/backend-java/src/main/resources/application.yml +++ b/backend-java/src/main/resources/application.yml @@ -19,6 +19,10 @@ spring: maximum-pool-size: ${AIIMAGE_DB_POOL_MAX_SIZE:30} minimum-idle: ${AIIMAGE_DB_POOL_MIN_IDLE:5} connection-timeout: ${AIIMAGE_DB_POOL_CONNECTION_TIMEOUT_MS:30000} + validation-timeout: ${AIIMAGE_DB_POOL_VALIDATION_TIMEOUT_MS:5000} + idle-timeout: ${AIIMAGE_DB_POOL_IDLE_TIMEOUT_MS:600000} + max-lifetime: ${AIIMAGE_DB_POOL_MAX_LIFETIME_MS:1500000} + keepalive-time: ${AIIMAGE_DB_POOL_KEEPALIVE_TIME_MS:120000} leak-detection-threshold: ${AIIMAGE_DB_POOL_LEAK_DETECT_MS:0} data: redis: @@ -54,7 +58,7 @@ mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true - log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl springdoc: api-docs: diff --git a/backend-java/src/main/resources/db/V36__shopkey_list_query_indexes.sql b/backend-java/src/main/resources/db/V36__shopkey_list_query_indexes.sql new file mode 100644 index 0000000..747c3ea --- /dev/null +++ b/backend-java/src/main/resources/db/V36__shopkey_list_query_indexes.sql @@ -0,0 +1,61 @@ +SET @db_name = DATABASE(); + +SET @idx_exists := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'biz_shop_manage' + AND INDEX_NAME = 'idx_group_id_id' +); +SET @sql := IF(@idx_exists = 0, + 'ALTER TABLE biz_shop_manage ADD INDEX idx_group_id_id (group_id, id)', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @idx_exists := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'biz_shop_manage' + AND INDEX_NAME = 'idx_created_by_id_id' +); +SET @sql := IF(@idx_exists = 0, + 'ALTER TABLE biz_shop_manage ADD INDEX idx_created_by_id_id (created_by_id, id)', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @idx_exists := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'biz_skip_price_asin' + AND INDEX_NAME = 'idx_group_id_id' +); +SET @sql := IF(@idx_exists = 0, + 'ALTER TABLE biz_skip_price_asin ADD INDEX idx_group_id_id (group_id, id)', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @idx_exists := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'biz_query_asin' + AND INDEX_NAME = 'idx_group_id_id' +); +SET @sql := IF(@idx_exists = 0, + 'ALTER TABLE biz_query_asin ADD INDEX idx_group_id_id (group_id, id)', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/backend-java/src/main/resources/db/V37__permission_menu_query_indexes.sql b/backend-java/src/main/resources/db/V37__permission_menu_query_indexes.sql new file mode 100644 index 0000000..daa2db1 --- /dev/null +++ b/backend-java/src/main/resources/db/V37__permission_menu_query_indexes.sql @@ -0,0 +1,31 @@ +SET @db_name = DATABASE(); + +SET @idx_exists := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'columns' + AND INDEX_NAME = 'idx_menu_type_sort_id' +); +SET @sql := IF(@idx_exists = 0, + 'ALTER TABLE columns ADD INDEX idx_menu_type_sort_id (menu_type, sort_order, id)', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @idx_exists := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'user_column_permission' + AND INDEX_NAME = 'idx_user_column' +); +SET @sql := IF(@idx_exists = 0, + 'ALTER TABLE user_column_permission ADD INDEX idx_user_column (user_id, column_id)', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/backend-java/src/main/resources/db/V38__task_result_hot_path_indexes.sql b/backend-java/src/main/resources/db/V38__task_result_hot_path_indexes.sql new file mode 100644 index 0000000..01ac687 --- /dev/null +++ b/backend-java/src/main/resources/db/V38__task_result_hot_path_indexes.sql @@ -0,0 +1,31 @@ +SET @db_name = DATABASE(); + +SET @idx_exists := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'biz_file_result' + AND INDEX_NAME = 'idx_file_result_task_module_id' +); +SET @sql := IF(@idx_exists = 0, + 'ALTER TABLE biz_file_result ADD INDEX idx_file_result_task_module_id (task_id, module_type, id)', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @idx_exists := ( + SELECT COUNT(*) + FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'biz_task_file_job' + AND INDEX_NAME = 'idx_file_job_status_retry_updated' +); +SET @sql := IF(@idx_exists = 0, + 'ALTER TABLE biz_task_file_job ADD INDEX idx_file_job_status_retry_updated (status, retry_count, updated_at)', + 'SELECT 1' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/backend/__pycache__/config.cpython-312.pyc b/backend/__pycache__/config.cpython-312.pyc index 0afb1db6f5722ef818574e29f7577f8be796375a..f3270ad1edcfed546e447e43970cd67e45fc48c3 100644 GIT binary patch delta 87 zcmaFM{eqk8G%qg~0}y2OZ{+&HBq)|qQc_^0uWx8%u4e#*hMOgr1DP~#NJ!1+n#nbv pZzkUbb-N1=5tk(*uS+Cclt{Rcm~vSn^#X(F2Q~&}y~z_Hq)$ diff --git a/backend/blueprints/__pycache__/admin_api.cpython-312.pyc b/backend/blueprints/__pycache__/admin_api.cpython-312.pyc index e207bee874a8e42f49fe6b60e20cd249f6deb65a..603f21e6e0519f1b2335f2844c4c15d694cbcc7d 100644 GIT binary patch delta 21896 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 delta 23142 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 total or not rows: - break - group_page += 1 - all_items.sort(key=lambda item: item.get('id') or 0, reverse=True) - total = len(all_items) - offset = (page - 1) * page_size - return jsonify({ - 'success': True, - 'items': [_format_shop_manage_item(item) for item in all_items[offset:offset + page_size]], - 'total': total, - 'page': page, - 'page_size': page_size, - }) - params = {'page': page, 'pageSize': page_size} if current_row and current_row.get('id'): params['operatorId'] = current_row.get('id') diff --git a/backend/config.py b/backend/config.py index 4e042f5..ccb1747 100644 --- a/backend/config.py +++ b/backend/config.py @@ -23,9 +23,9 @@ bucket_path = "nanri-image/" file_url_pre = f"https://{bucket}.oss-cn-hangzhou.aliyuncs.com/" 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://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://47.111.163.154:18080').rstrip('/') +# backend_java_base_url = os.environ.get('BACKEND_JAVA_BASE_URL', 'http://47.111.163.154:18080').rstrip('/') os.environ['OSS_ACCESS_KEY_ID'] = accessKeyId os.environ['OSS_ACCESS_KEY_SECRET'] = accessKeySecret os.environ['SECRET_KEY'] = "ddffc7c1d02121d9554d7b080b2511b6" diff --git a/backend/utils/__pycache__/auth.cpython-312.pyc b/backend/utils/__pycache__/auth.cpython-312.pyc index f8172f67ddca9266ff28dfa131c5f0f34e6fba1a..4638aec3e92df46a922f5dbc0093c4b975808a42 100644 GIT binary patch delta 2062 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 f>O#Tx@Cg)YkMU@34WTJe7aXzycm diff --git a/backend/utils/__pycache__/db.cpython-312.pyc b/backend/utils/__pycache__/db.cpython-312.pyc index 7b0e8ace4856a93b43747b263bab1ed37729d453..036fe34ceb48a13a75a58cdd4eb84809e28288b5 100644 GIT binary patch delta 925 zcmccbJl9qIG%qg~0}v$N`IvcFnt|alhyw#GP{!xHiRuRR?2HU4j47-+thpRfoM1K^ zkj)jv4Q8_g**sCaj0~xaS#mG~5OkIzf(>J(@T9V@W`gjd*a+#XhiPMANa0Q8glps= zq!CFUUn)0T9~TMw_)~dTyTV+`lOoW<5G9x*2qcA4gn*=QC6h8kDxV5N5hyG;qS#Y7 zqc~H5A(O%##T&`c(^<|?!5qm@&ZsFoxrouZp79n}Ze?*{PJBjwafv3JS6ZA}1my8S z#Rn0W2yhGz4)+gofeE-chB!Jo2D@%{W71}{(RH(mDJaU% zN=+__NiIq(&q*!PEyyWN&&-QSN=(j9%}a?XEy>I&j!8+UHYa=$F-(ZF{DtLP^-4o0?5V#18P zpLOyW8D)Nah-PHq5dLJrz{03?Lqhoii|7prIS?rWBqeXiXkKEG{J;s6{8cOnbnPSw SCl!W6D!fi6%!iB_fg}KdGwJ96 delta 669 zcmbR1df!?7G%qg~0}ym5|ulo?XFR2Yhi85vR;v&3M=!0E{c`9zgdSX0xVOK$N&L;@UxgTwuUTy8PIL|hz0gdCk5gI#ZN=O$)WWG3dN$0ufP zE@9GUR1!`~OwLZtOVKYa$;>I%Pf5}%sJz9No>~&0l2jxQ3`oJvx0!8N7-cp~a5RcA z8f~5>e(1w8Sk_BUS{$A zB*4MQ_DM{bk@vGs9wVd7j}L*23>?Cr7+8QxZU_obmzpRwU2dY>0-hDwmj$gFyimo` zE(@9h#YDxYM@@{H9yc-Wf{Mw8q|2g~4Sr~XxtB#%p@KSh82#c MP72J2' + (item.name || item.route_path) + ''; - }).join(''); - } - document.querySelectorAll('#adminTabs .tab').forEach(function (tab) { - tab.onclick = function () { - activateAdminTab(tab.dataset.tab); - }; - }); - var fallbackTab = activeAdminTabName && knownItems.some(function (item) { return item.route_path === activeAdminTabName; }) - ? activeAdminTabName - : knownItems[0].route_path; - activateAdminTab(fallbackTab); - } - function loadAdminMenus(preferredTab) { - if (preferredTab) activeAdminTabName = preferredTab; - fetch('/api/admin/current-user/menus') - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - if (adminTabsEl) adminTabsEl.innerHTML = '
鑿滃崟鍔犺浇澶辫触
'; - hideAllAdminPanels(); - return; - } - renderAdminTabs(res.items || []); - }) - .catch(function () { - if (adminTabsEl) adminTabsEl.innerHTML = '
鑿滃崟鍔犺浇澶辫触
'; - hideAllAdminPanels(); - }); - } - hideAllAdminPanels(); - - // ========== 鐢ㄦ埛绠$悊 ========== - var userPage = 1, userPageSize = 15; - var currentUserId = null; - var currentUserRole = 'admin'; - var currentUserUsername = ''; - var adminsList = []; - function roleLabel(role) { - if (role === 'super_admin') return '瓒呯骇绠$悊鍛?; - if (role === 'admin') return '绠$悊鍛?; - return '鏅€氳处鍙?; - } - function buildUserListQuery(page) { - var q = 'page=' + (page || 1) + '&page_size=' + userPageSize; - var kw = (document.getElementById('searchUsername').value || '').trim(); - if (kw) q += '&username=' + encodeURIComponent(kw); - var cby = document.getElementById('filterCreatedBy').value; - if (cby) q += '&created_by_id=' + encodeURIComponent(cby); - return q; - } - function loadUsers(page) { - userPage = page || 1; - fetch('/api/admin/users?' + buildUserListQuery(userPage)) - .then(function (r) { return r.json(); }) - .then(function (res) { - var tbody = document.getElementById('userListBody'); - if (!res.success) { - tbody.innerHTML = '鍔犺浇澶辫触: ' + (res.error || '') + ''; - return; - } - currentUserId = res.current_user_id || null; - currentUserRole = res.current_user_role || 'admin'; - currentUserUsername = (res.current_user_username || '').trim(); - adminsList = res.admins || []; - var items = res.items || []; - if (items.length === 0) { - tbody.innerHTML = '鏆傛棤鐢ㄦ埛'; - } else { - tbody.innerHTML = items.map(function (u) { - return '' + u.id + '' + (u.username || '') + '' + - roleLabel(u.role || 'normal') + '' + (u.creator_username || '-') + '' + (u.created_at || '') + '' + - ' ' + - '' + - ''; - }).join(''); - } - renderPagination('userPagination', res.total, res.page, res.page_size, loadUsers); - bindUserActions(); - updateCreateFormByRole(); - updateUserFilterByRole(); - }) - .catch(function () { - document.getElementById('userListBody').innerHTML = '璇锋眰澶辫触'; - }); - } - function updateUserFilterByRole() { - var grp = document.getElementById('filterCreatedByGroup'); - var sel = document.getElementById('filterCreatedBy'); - if (currentUserRole === 'super_admin') { - grp.style.display = 'block'; - var cur = sel.value; - sel.innerHTML = ''; - adminsList.forEach(function (a) { - var opt = document.createElement('option'); - opt.value = a.id; - opt.textContent = a.username; - sel.appendChild(opt); - }); - sel.value = cur || ''; - } else { - grp.style.display = 'none'; - } - } - - function updateDedupeTotalDataAccess() { - var tab = document.querySelector('.tab[data-tab="dedupe-total-data"]'); - var panel = document.getElementById('panel-dedupe-total-data'); - var canUse = currentUserRole === 'super_admin' || (currentUserRole === 'admin' && currentUserUsername === ''); - if (tab) tab.style.display = canUse ? '' : 'none'; - if (panel) panel.style.display = canUse ? '' : 'none'; - if (!canUse && tab && tab.classList.contains('active')) { - tab.classList.remove('active'); - if (panel) panel.classList.remove('active'); - var usersTab = document.querySelector('.tab[data-tab="users"]'); - var usersPanel = document.getElementById('panel-users'); - if (usersTab) usersTab.classList.add('active'); - if (usersPanel) usersPanel.classList.add('active'); - } - } - function updateShopManageAccess() { - var tab = document.querySelector('.tab[data-tab="shop-manage"]'); - var panel = document.getElementById('panel-shop-manage'); - if (!tab || !panel) return; - var canUse = currentUserRole === 'super_admin' || - !!currentUserAdminPermissionKeys['admin_shop_manage'] || - !!currentUserAdminPermissionRoutes['shop-manage']; - tab.style.display = canUse ? '' : 'none'; - panel.style.display = canUse ? '' : 'none'; - if (!canUse && tab.classList.contains('active')) { - tab.classList.remove('active'); - panel.classList.remove('active'); - var usersTab = document.querySelector('.tab[data-tab="users"]'); - var usersPanel = document.getElementById('panel-users'); - if (usersTab) usersTab.classList.add('active'); - if (usersPanel) usersPanel.classList.add('active'); - } - } - function loadCurrentUserAdminPermissions() { - currentUserAdminPermissionKeys = {}; - currentUserAdminPermissionRoutes = {}; - if (!currentUserId || currentUserRole === 'super_admin') { - updateShopManageAccess(); - return; - } - fetch('/api/admin/user/' + currentUserId + '/column-permissions?menu_type=admin') - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - updateShopManageAccess(); - return; - } - (res.items || []).forEach(function (item) { - var columnKey = (item.column_key || '').trim(); - var routePath = (item.route_path || '').trim(); - if (columnKey) currentUserAdminPermissionKeys[columnKey] = true; - if (routePath) currentUserAdminPermissionRoutes[routePath] = true; - }); - updateShopManageAccess(); - }) - .catch(function () { - updateShopManageAccess(); - }); - } - function applyTabAccess(tabName, canUse) { - var tab = document.querySelector('.tab[data-tab="' + tabName + '"]'); - var panel = document.getElementById('panel-' + tabName); - if (tab) tab.style.display = canUse ? '' : 'none'; - if (panel) panel.style.display = canUse ? '' : 'none'; - if (!canUse && tab && tab.classList.contains('active')) { - tab.classList.remove('active'); - if (panel) panel.classList.remove('active'); - var usersTab = document.querySelector('.tab[data-tab="users"]'); - var usersPanel = document.getElementById('panel-users'); - if (usersTab) usersTab.classList.add('active'); - if (usersPanel) usersPanel.classList.add('active'); - } - } - function hasAdminTabAccess(tabName) { - var config = ADMIN_TAB_ACCESS_CONFIG[tabName]; - if (!config) return true; - if (currentUserRole === 'super_admin') return true; - if (config.superAdminOnly) return false; - return !!currentUserAdminPermissionKeys[config.columnKey] || - !!currentUserAdminPermissionRoutes[config.routePath]; - } - function refreshAdminTabAccess() { - Object.keys(ADMIN_TAB_ACCESS_CONFIG).forEach(function (tabName) { - applyTabAccess(tabName, hasAdminTabAccess(tabName)); - }); - } - function loadCurrentUserAdminPermissions() { - currentUserAdminPermissionKeys = {}; - currentUserAdminPermissionRoutes = {}; - if (!currentUserId || currentUserRole === 'super_admin') { - refreshAdminTabAccess(); - return; - } - fetch('/api/admin/user/' + currentUserId + '/column-permissions?menu_type=admin') - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - refreshAdminTabAccess(); - return; - } - (res.items || []).forEach(function (item) { - var columnKey = (item.column_key || '').trim(); - var routePath = (item.route_path || '').trim(); - if (columnKey) currentUserAdminPermissionKeys[columnKey] = true; - if (routePath) currentUserAdminPermissionRoutes[routePath] = true; - }); - refreshAdminTabAccess(); - }) - .catch(function () { - refreshAdminTabAccess(); - }); - } - var allColumnsList = []; - function loadColumnsForPermission() { - fetch('/api/admin/columns?menu_type=admin') - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) return; - allColumnsList = res.items || []; - renderColumnPermissionWrap('createColumnPermissionWrap'); - renderColumnPermissionWrap('editColumnPermissionWrap'); - var selCreate = document.getElementById('createColumnPermissionWrap'); - var selEdit = document.getElementById('editColumnPermissionWrap'); - if (selCreate && !selCreate._colCardsBound) { - selCreate._colCardsBound = true; - selCreate.onchange = function () { renderColumnCards('createColumnPermissionWrap'); }; - } - if (selEdit && !selEdit._colCardsBound) { - selEdit._colCardsBound = true; - selEdit.onchange = function () { renderColumnCards('editColumnPermissionWrap'); }; - } - }) - .catch(function () { }); - } - var columnCardsContainerMap = { createColumnPermissionWrap: 'createColumnCards', editColumnPermissionWrap: 'editColumnCards' }; - function renderColumnCards(selectId) { - var sel = document.getElementById(selectId); - var cardsId = columnCardsContainerMap[selectId]; - var cardsEl = cardsId ? document.getElementById(cardsId) : null; - if (!sel || sel.tagName !== 'SELECT' || !cardsEl) return; - cardsEl.innerHTML = ''; - for (var i = 0; i < sel.options.length; i++) { - var opt = sel.options[i]; - if (opt.disabled || !opt.selected) continue; - var card = document.createElement('span'); - card.className = 'column-permission-card'; - card.textContent = opt.textContent; - var btn = document.createElement('button'); - btn.type = 'button'; - btn.className = 'col-card-remove'; - btn.setAttribute('aria-label', '绉婚櫎'); - btn.textContent = '脳'; - (function (option, seldId) { - btn.onclick = function () { - option.selected = false; - renderColumnCards(seldId); - }; - })(opt, selectId); - card.appendChild(btn); - cardsEl.appendChild(card); - } - } - function renderColumnPermissionWrap(wrapId) { - var sel = document.getElementById(wrapId); - if (!sel || sel.tagName !== 'SELECT') return; - sel.innerHTML = ''; - allColumnsList.forEach(function (c) { - var opt = document.createElement('option'); - opt.value = c.id; - opt.textContent = c.name + ' (' + c.column_key + ')'; - sel.appendChild(opt); - }); - if (allColumnsList.length === 0) { - var opt = document.createElement('option'); - opt.disabled = true; - opt.textContent = '鏆傛棤鑿滃崟锛岃鍏堝湪鈥滄爮鐩潈闄愰厤缃€濅腑鏂板鑿滃崟'; - sel.appendChild(opt); - } - renderColumnCards(wrapId); - } - function getSelectedColumnIds(wrapId) { - var sel = document.getElementById(wrapId); - if (!sel || sel.tagName !== 'SELECT') return []; - var ids = []; - for (var i = 0; i < sel.options.length; i++) { - if (sel.options[i].selected) { - var v = parseInt(sel.options[i].value, 10); - if (!isNaN(v)) ids.push(v); - } - } - return ids; - } - function setColumnPermissionCheckboxes(wrapId, columnIds) { - var sel = document.getElementById(wrapId); - if (!sel || sel.tagName !== 'SELECT') return; - var set = {}; - (columnIds || []).forEach(function (id) { set[id] = true; }); - for (var i = 0; i < sel.options.length; i++) { - var opt = sel.options[i]; - if (opt.disabled) continue; - opt.selected = set[parseInt(opt.value, 10)] || false; - } - renderColumnCards(wrapId); - } - function updateCreateFormByRole() { - var roleSel = document.getElementById('createRole'); - var optAdmin = document.getElementById('optAdmin'); - var formCreatedBy = document.getElementById('formGroupCreatedBy'); - var selCreatedBy = document.getElementById('createCreatedBy'); - if (currentUserRole === 'super_admin') { - if (optAdmin) optAdmin.style.display = ''; - formCreatedBy.style.display = (roleSel.value === 'normal') ? 'block' : 'none'; - selCreatedBy.innerHTML = ''; - (res.items || []).forEach(function (u) { - var opt = document.createElement('option'); - opt.value = u.id; - opt.textContent = u.username + ' (' + roleLabel(u.role || 'normal') + ')'; - sel.appendChild(opt); - }); - sel.value = cur || ''; - }); - } - var shopManageGroupUsers = []; - function refreshShopManageGroupUserSelect(selectedUserId) { - var sel = document.getElementById('shopManageGroupUserSelect'); - if (!sel) return; - var opts = ['']; - shopManageGroupUsers.forEach(function (u) { - opts.push(''); - }); - sel.innerHTML = opts.join(''); - if (selectedUserId != null && selectedUserId !== '') { - sel.value = String(selectedUserId); - } - syncShopManageGroupNameWithUser(); - } - function syncShopManageGroupNameWithUser() { - var sel = document.getElementById('shopManageGroupUserSelect'); - var input = document.getElementById('shopManageGroupInput'); - if (!sel || !input) return; - var selectedUser = null; - shopManageGroupUsers.some(function (u) { - if (String(u.id) === String(sel.value || '')) { - selectedUser = u; - return true; - } - return false; - }); - input.value = selectedUser ? (selectedUser.username || '') : ''; - } - function loadUserOptions() { - return fetch('/api/admin/users?page=1&page_size=999') - .then(function (r) { return r.json(); }) - .then(function (res) { - var items = res.items || []; - shopManageGroupUsers = items.map(function (u) { - return { id: u.id, username: u.username || '', role: u.role || 'normal' }; - }); - refreshShopManageGroupUserSelect(document.getElementById('shopManageGroupUserSelect') ? document.getElementById('shopManageGroupUserSelect').value : ''); - var sel = document.getElementById('filterUser'); - var cur = sel.value; - sel.innerHTML = ''; - items.forEach(function (u) { - var opt = document.createElement('option'); - opt.value = u.id; - opt.textContent = u.username + ' (' + roleLabel(u.role || 'normal') + ')'; - sel.appendChild(opt); - }); - sel.value = cur || ''; - }); - } - document.getElementById('btnFilterHistory').onclick = function () { loadHistory(1); }; - - // ========== 鏁版嵁鍘婚噸鎬绘暟鎹?========== - var dedupeTotalDataPage = 1, dedupeTotalDataPageSize = 15; - function buildDedupeTotalDataQuery(page) { - var q = 'page=' + (page || 1) + '&page_size=' + dedupeTotalDataPageSize; - var keyword = (document.getElementById('searchDedupeTotalData').value || '').trim(); - if (keyword) q += '&keyword=' + encodeURIComponent(keyword); - return q; - } - function loadDedupeTotalData(page) { - dedupeTotalDataPage = page || 1; - fetch('/api/admin/dedupe-total-data?' + buildDedupeTotalDataQuery(dedupeTotalDataPage)) - .then(function (r) { return r.json(); }) - .then(function (res) { - var tbody = document.getElementById('dedupeTotalDataListBody'); - if (!res.success) { - tbody.innerHTML = '鍔犺浇澶辫触: ' + (res.error || '') + ''; - return; - } - var items = res.items || []; - if (items.length === 0) { - tbody.innerHTML = '鏆傛棤鎬绘暟鎹?/td>'; - } else { - tbody.innerHTML = items.map(function (item) { - return '' + item.id + '' + (item.data_value || '') + '' + (item.created_at || '') + '' + - ' ' + - '' + - ''; - }).join(''); - } - renderPagination('dedupeTotalDataPagination', res.total, res.page, res.page_size, loadDedupeTotalData); - bindDedupeTotalDataActions(); - }) - .catch(function () { - document.getElementById('dedupeTotalDataListBody').innerHTML = '璇锋眰澶辫触'; - }); - } - function bindDedupeTotalDataActions() { - document.querySelectorAll('[data-dedupe-total-edit]').forEach(function (btn) { - btn.onclick = function () { - document.getElementById('editDedupeTotalDataId').value = btn.dataset.dedupeTotalEdit || ''; - document.getElementById('editDedupeTotalDataValue').value = (btn.dataset.value || '').replace(/"/g, '"'); - document.getElementById('msgEditDedupeTotalData').textContent = ''; - document.getElementById('msgEditDedupeTotalData').className = 'msg'; - document.getElementById('editDedupeTotalDataModal').classList.add('show'); - }; - }); - document.querySelectorAll('[data-dedupe-total-delete]').forEach(function (btn) { - btn.onclick = function () { - var value = (btn.dataset.value || '').replace(/"/g, '"'); - if (!confirm('纭畾鍒犻櫎鎬绘暟鎹€? + value + '鈥濆悧锛?)) return; - fetch('/api/admin/dedupe-total-data/' + btn.dataset.dedupeTotalDelete, { method: 'DELETE' }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { loadDedupeTotalData(dedupeTotalDataPage); } - else { alert(res.error || '鍒犻櫎澶辫触'); } - }); - }; - }); - } - document.getElementById('btnSearchDedupeTotalData').onclick = function () { loadDedupeTotalData(1); }; - var dedupeImportPollTimer = null; - var dedupeDeleteImportPollTimer = null; - function stopDedupeImportProgress() { - if (dedupeImportPollTimer) { - clearInterval(dedupeImportPollTimer); - dedupeImportPollTimer = null; - } - } - function stopDedupeDeleteImportProgress() { - if (dedupeDeleteImportPollTimer) { - clearInterval(dedupeDeleteImportPollTimer); - dedupeDeleteImportPollTimer = null; - } - } - function setDedupeImportProgress(percent, text) { - var wrap = document.getElementById('dedupeTotalDataProgressWrap'); - var fill = document.getElementById('dedupeTotalDataProgressFill'); - var textEl = document.getElementById('dedupeTotalDataProgressText'); - wrap.style.display = 'block'; - fill.style.width = Math.max(0, Math.min(100, percent || 0)) + '%'; - 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) { - stopDedupeImportProgress(); - function tick() { - fetch('/api/admin/dedupe-total-data/import/' + encodeURIComponent(importId)) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - stopDedupeImportProgress(); - document.getElementById('msgDedupeTotalData').textContent = res.error || '鏌ヨ瀵煎叆杩涘害澶辫触'; - document.getElementById('msgDedupeTotalData').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; - setDedupeImportProgress(percent, '澶勭悊涓細宸插鐞?' + processedRows + ' / ' + totalRows + '锛屾柊澧?' + (progress.inserted_count || 0) + '锛岃烦杩?' + (progress.skipped_count || 0)); - if (progress.status === 'success') { - stopDedupeImportProgress(); - document.getElementById('msgDedupeTotalData').textContent = '瀵煎叆鎴愬姛锛氭€昏鏁?' + (progress.total_rows || 0) + '锛孉SIN 鏁伴噺 ' + (progress.asin_count || 0) + '锛屾柊澧?' + (progress.inserted_count || 0) + '锛岃烦杩?' + (progress.skipped_count || 0); - document.getElementById('msgDedupeTotalData').className = 'msg ok'; - loadDedupeTotalData(1); - } else if (progress.status === 'failed') { - stopDedupeImportProgress(); - document.getElementById('msgDedupeTotalData').textContent = progress.error_message || '瀵煎叆澶辫触'; - document.getElementById('msgDedupeTotalData').className = 'msg err'; - } - }) - .catch(function () { - stopDedupeImportProgress(); - document.getElementById('msgDedupeTotalData').textContent = '鏌ヨ瀵煎叆杩涘害澶辫触'; - document.getElementById('msgDedupeTotalData').className = 'msg err'; - }); - } - tick(); - 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) + '锛孉SIN 鏁伴噺 ' + (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 () { - var fileInput = document.getElementById('dedupeTotalDataFile'); - var msgEl = document.getElementById('msgDedupeTotalData'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - stopDedupeImportProgress(); - document.getElementById('dedupeTotalDataProgressWrap').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; - } - var formData = new FormData(); - formData.append('file', file); - var xhr = new XMLHttpRequest(); - xhr.open('POST', '/api/admin/dedupe-total-data/import', true); - xhr.upload.onprogress = function (event) { - if (event.lengthComputable) { - var percent = Math.round(event.loaded * 100 / event.total); - setDedupeImportProgress(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; - } - setDedupeImportProgress(100, '涓婁紶瀹屾垚锛屽悗绔鐞嗕腑...'); - pollDedupeImport(res.import_id); - fileInput.value = ''; - }; - xhr.onerror = function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.className = 'msg err'; - }; - 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 () { - var itemId = document.getElementById('editDedupeTotalDataId').value; - var value = (document.getElementById('editDedupeTotalDataValue').value || '').trim(); - var msgEl = document.getElementById('msgEditDedupeTotalData'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!value) { - msgEl.textContent = '璇峰~鍐欐€绘暟鎹€?; - msgEl.classList.add('err'); - return; - } - fetch('/api/admin/dedupe-total-data/' + itemId, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ data_value: value }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { - document.getElementById('editDedupeTotalDataModal').classList.remove('show'); - loadDedupeTotalData(dedupeTotalDataPage); - } else { - msgEl.textContent = res.error || '淇濆瓨澶辫触'; - msgEl.classList.add('err'); - } - }); - }; - document.getElementById('btnCloseEditDedupeTotalData').onclick = function () { - document.getElementById('editDedupeTotalDataModal').classList.remove('show'); - }; - - // ========== 搴楅摵瀵嗛挜绠$悊 ========== - var shopKeyPage = 1, shopKeyPageSize = 15; - function buildShopKeyQuery(page) { - return 'page=' + (page || 1) + '&page_size=' + shopKeyPageSize; - } - function loadShopKeys(page) { - shopKeyPage = page || 1; - fetch('/api/admin/shop-keys?' + buildShopKeyQuery(shopKeyPage)) - .then(function (r) { return r.json(); }) - .then(function (res) { - var tbody = document.getElementById('shopKeyListBody'); - if (!res.success) { - tbody.innerHTML = '鍔犺浇澶辫触: ' + (res.error || '') + ''; - return; - } - var items = res.items || []; - if (items.length === 0) { - tbody.innerHTML = '鏆傛棤搴楅摵瀵嗛挜'; - } else { - tbody.innerHTML = items.map(function (item, index) { - var rowNo = (shopKeyPage - 1) * shopKeyPageSize + index + 1; - return '' + rowNo + '' + (item.ziniao_account_name || '') + '' + (item.ziniao_token || '') + '' + (item.created_at || '') + '' + (item.updated_at || '') + '' + - ' ' + - '' + - ''; - }).join(''); - } - renderPagination('shopKeyPagination', res.total, res.page, res.page_size, loadShopKeys); - bindShopKeyActions(); - }) - .catch(function () { - document.getElementById('shopKeyListBody').innerHTML = '璇锋眰澶辫触'; - }); - } - function bindShopKeyActions() { - document.querySelectorAll('[data-shop-key-edit]').forEach(function (btn) { - btn.onclick = function () { - var item = {}; - try { item = JSON.parse((btn.dataset.shopKey || '').replace(/"/g, '"')); } catch (e) { item = {}; } - document.getElementById('editShopKeyId').value = item.id || ''; - document.getElementById('editShopKeyZiniaoAccountName').value = item.ziniao_account_name || ''; - document.getElementById('editShopKeyZiniaoToken').value = item.ziniao_token || ''; - document.getElementById('msgEditShopKey').textContent = ''; - document.getElementById('msgEditShopKey').className = 'msg'; - document.getElementById('editShopKeyModal').classList.add('show'); - }; - }); - document.querySelectorAll('[data-shop-key-delete]').forEach(function (btn) { - btn.onclick = function () { - var name = (btn.dataset.ziniaoAccountName || '').replace(/"/g, '"'); - if (!confirm('纭畾鍒犻櫎搴楅摵瀵嗛挜鈥? + name + '鈥濆悧锛?)) return; - fetch('/api/admin/shop-key/' + btn.dataset.shopKeyDelete, { method: 'DELETE' }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { loadShopKeys(shopKeyPage); } - else { alert(res.error || '鍒犻櫎澶辫触'); } - }); - }; - }); - } - document.getElementById('btnCreateShopKey').onclick = function () { - var ziniaoAccountName = (document.getElementById('shopKeyZiniaoAccountName').value || '').trim(); - var ziniaoToken = (document.getElementById('shopKeyZiniaoToken').value || '').trim(); - var msgEl = document.getElementById('msgShopKey'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!ziniaoAccountName || !ziniaoToken) { - msgEl.textContent = '璇峰畬鏁村~鍐欑传楦熻处鍙峰悕绉般€佺传楦熶护鐗?; - msgEl.classList.add('err'); - return; - } - fetch('/api/admin/shop-key', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - ziniao_account_name: ziniaoAccountName, - ziniao_token: ziniaoToken - }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { - document.getElementById('shopKeyZiniaoAccountName').value = ''; - document.getElementById('shopKeyZiniaoToken').value = ''; - msgEl.textContent = res.msg || '鍒涘缓鎴愬姛'; - msgEl.className = 'msg ok'; - loadShopKeys(1); - } else { - msgEl.textContent = res.error || '鍒涘缓澶辫触'; - msgEl.className = 'msg err'; - } - }) - .catch(function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.className = 'msg err'; - }); - }; - document.getElementById('btnSaveShopKey').onclick = function () { - var itemId = document.getElementById('editShopKeyId').value; - var ziniaoAccountName = (document.getElementById('editShopKeyZiniaoAccountName').value || '').trim(); - var ziniaoToken = (document.getElementById('editShopKeyZiniaoToken').value || '').trim(); - var msgEl = document.getElementById('msgEditShopKey'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!ziniaoAccountName || !ziniaoToken) { - msgEl.textContent = '璇峰畬鏁村~鍐欑传楦熻处鍙峰悕绉般€佺传楦熶护鐗?; - msgEl.classList.add('err'); - return; - } - fetch('/api/admin/shop-key/' + itemId, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - ziniao_account_name: ziniaoAccountName, - ziniao_token: ziniaoToken - }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { - document.getElementById('editShopKeyModal').classList.remove('show'); - loadShopKeys(shopKeyPage); - } else { - msgEl.textContent = res.error || '淇濆瓨澶辫触'; - msgEl.classList.add('err'); - } - }) - .catch(function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.classList.add('err'); - }); - }; - document.getElementById('btnCloseEditShopKey').onclick = function () { - document.getElementById('editShopKeyModal').classList.remove('show'); - }; - - // ========== 搴楅摵绠$悊 ========== - var shopManagePage = 1, shopManagePageSize = 15; - var shopManageGroups = []; - - function buildShopManageQuery(page) { - var query = 'page=' + (page || 1) + '&page_size=' + shopManagePageSize; - var groupId = (document.getElementById('shopManageFilterGroupId').value || '').trim(); - var shopName = (document.getElementById('shopManageFilterShopName').value || '').trim(); - if (groupId) query += '&group_id=' + encodeURIComponent(groupId); - if (shopName) query += '&shop_name=' + encodeURIComponent(shopName); - return query; - } - - function refreshShopGroupSelects(selectedCreateId, selectedEditId) { - var createSel = document.getElementById('shopManageGroupSelect'); - var editSel = document.getElementById('editShopManageGroupSelect'); - var filterSel = document.getElementById('shopManageFilterGroupId'); - var skipCreateSel = document.getElementById('skipPriceAsinGroupSelect'); - var skipFilterSel = document.getElementById('skipPriceAsinFilterGroupId'); - var chooseSkipShopSel = document.getElementById('chooseSkipPriceAsinShopGroupId'); - var selectedFilterId = filterSel ? filterSel.value : ''; - var selectedSkipCreateId = skipCreateSel ? skipCreateSel.value : ''; - var selectedSkipFilterId = skipFilterSel ? skipFilterSel.value : ''; - var selectedChooseSkipShopId = chooseSkipShopSel ? chooseSkipShopSel.value : ''; - var createOpts = ['']; - var filterOpts = ['']; - shopManageGroups.forEach(function (g) { - var option = ''; - createOpts.push(option); - filterOpts.push(option); - }); - createSel.innerHTML = createOpts.join(''); - editSel.innerHTML = createOpts.join(''); - if (skipCreateSel) skipCreateSel.innerHTML = createOpts.join(''); - if (filterSel) filterSel.innerHTML = filterOpts.join(''); - if (skipFilterSel) skipFilterSel.innerHTML = filterOpts.join(''); - if (chooseSkipShopSel) chooseSkipShopSel.innerHTML = filterOpts.join(''); - if (selectedCreateId != null) createSel.value = String(selectedCreateId); - if (selectedEditId != null) editSel.value = String(selectedEditId); - if (filterSel && selectedFilterId) filterSel.value = selectedFilterId; - if (skipCreateSel && selectedSkipCreateId) skipCreateSel.value = selectedSkipCreateId; - if (skipFilterSel && selectedSkipFilterId) skipFilterSel.value = selectedSkipFilterId; - if (chooseSkipShopSel && selectedChooseSkipShopId) chooseSkipShopSel.value = selectedChooseSkipShopId; - } - - function loadShopManageGroups(selectedCreateId, selectedEditId) { - return fetch('/api/admin/shop-manage-groups') - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) throw new Error(res.error || '鍔犺浇鍒嗙粍澶辫触'); - shopManageGroups = res.items || []; - refreshShopGroupSelects(selectedCreateId, selectedEditId); - return shopManageGroups; - }) - .catch(function () { - shopManageGroups = []; - refreshShopGroupSelects(); - return []; - }); - } - - function openShopManageGroupModal() { - document.getElementById('shopManageGroupEditId').value = ''; - document.getElementById('shopManageGroupInput').value = ''; - document.getElementById('msgShopManageGroup').textContent = ''; - document.getElementById('msgShopManageGroup').className = 'msg'; - loadShopManageGroups().then(function () { - renderShopManageGroupRows(); - document.getElementById('shopManageGroupModal').classList.add('show'); - }); - } - - function renderShopManageGroupRows() { - var tbody = document.getElementById('shopManageGroupListBody'); - if (!shopManageGroups.length) { - tbody.innerHTML = '鏆傛棤鍒嗙粍'; - return; - } - tbody.innerHTML = shopManageGroups.map(function (item, index) { - return '' + (index + 1) + '' + (item.group_name || '') + '' + (item.created_at || '') + '' + (item.updated_at || '') + '' + - ' ' + - '' + - ''; - }).join(''); - - document.querySelectorAll('[data-shop-group-edit]').forEach(function (btn) { - btn.onclick = function () { - document.getElementById('shopManageGroupEditId').value = btn.dataset.shopGroupEdit; - document.getElementById('shopManageGroupInput').value = (btn.dataset.shopGroupName || '').replace(/"/g, '"'); - }; - }); - - document.querySelectorAll('[data-shop-group-delete]').forEach(function (btn) { - btn.onclick = function () { - var gid = btn.dataset.shopGroupDelete; - var gname = (btn.dataset.shopGroupName || '').replace(/"/g, '"'); - if (!confirm('纭畾鍒犻櫎鍒嗙粍鈥? + gname + '鈥濆悧锛?)) return; - fetch('/api/admin/shop-manage-group/' + gid, { method: 'DELETE' }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - alert(res.error || '鍒犻櫎澶辫触'); - return; - } - loadShopManageGroups().then(function () { - renderShopManageGroupRows(); - loadShopManage(shopManagePage); - loadSkipPriceAsin(skipPriceAsinPage); - }); - }); - }; - }); - } - - function loadShopManage(page) { - shopManagePage = page || 1; - fetch('/api/admin/shop-manages?' + buildShopManageQuery(shopManagePage)) - .then(function (r) { return r.json(); }) - .then(function (res) { - var tbody = document.getElementById('shopManageListBody'); - if (!res.success) { - tbody.innerHTML = '鍔犺浇澶辫触: ' + (res.error || '') + ''; - return; - } - var items = res.items || []; - if (items.length === 0) { - tbody.innerHTML = '鏆傛棤搴楅摵'; - } else { - tbody.innerHTML = items.map(function (item, index) { - var rowNo = (shopManagePage - 1) * shopManagePageSize + index + 1; - return '' + rowNo + '' + (item.group_name || '') + '' + (item.shop_name || '') + '' + (item.mall_name || '') + '' + (item.account || '') + '' + (item.password || '') + '' + (item.created_at || '') + '' + (item.updated_at || '') + '' + - ' ' + - '' + - ''; - }).join(''); - } - renderPagination('shopManagePagination', res.total, res.page, res.page_size, loadShopManage); - bindShopManageActions(); - }) - .catch(function () { - document.getElementById('shopManageListBody').innerHTML = '璇锋眰澶辫触'; - }); - } - - function bindShopManageActions() { - document.querySelectorAll('[data-shop-manage-edit]').forEach(function (btn) { - btn.onclick = function () { - var item = {}; - try { item = JSON.parse((btn.dataset.shopManage || '').replace(/"/g, '"')); } catch (e) { item = {}; } - document.getElementById('editShopManageId').value = item.id || ''; - document.getElementById('editShopManageShopName').value = item.shop_name || ''; - document.getElementById('editShopManageMallName').value = item.mall_name || ''; - document.getElementById('editShopManageAccount').value = item.account || ''; - document.getElementById('editShopManagePassword').value = item.password || ''; - document.getElementById('msgEditShopManage').textContent = ''; - document.getElementById('msgEditShopManage').className = 'msg'; - loadShopManageGroups(null, item.group_id).then(function () { - document.getElementById('editShopManageModal').classList.add('show'); - }); - }; - }); - document.querySelectorAll('[data-shop-manage-delete]').forEach(function (btn) { - btn.onclick = function () { - var name = (btn.dataset.shopManageName || '').replace(/"/g, '"'); - if (!confirm('纭畾鍒犻櫎搴楅摵鈥? + name + '鈥濆悧锛?)) return; - fetch('/api/admin/shop-manage/' + btn.dataset.shopManageDelete, { method: 'DELETE' }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { loadShopManage(shopManagePage); } - else { alert(res.error || '鍒犻櫎澶辫触'); } - }); - }; - }); - } - - document.getElementById('btnManageShopGroups').onclick = openShopManageGroupModal; - document.getElementById('btnManageShopGroupsFromEdit').onclick = openShopManageGroupModal; - document.getElementById('btnSearchShopManage').onclick = function () { - loadShopManage(1); - }; - document.getElementById('shopManageFilterShopName').addEventListener('keydown', function (e) { - if (e.key === 'Enter') { - e.preventDefault(); - loadShopManage(1); - } - }); - document.getElementById('btnCloseShopManageGroupModal').onclick = function () { - document.getElementById('shopManageGroupModal').classList.remove('show'); - }; - document.getElementById('btnCancelShopManageGroupEdit').onclick = function () { - document.getElementById('shopManageGroupEditId').value = ''; - document.getElementById('shopManageGroupInput').value = ''; - }; - document.getElementById('btnSaveShopManageGroup').onclick = function () { - var editId = (document.getElementById('shopManageGroupEditId').value || '').trim(); - var groupName = (document.getElementById('shopManageGroupInput').value || '').trim(); - var msgEl = document.getElementById('msgShopManageGroup'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!groupName) { - msgEl.textContent = '璇疯緭鍏ュ垎缁勫悕绉?; - msgEl.className = 'msg err'; - return; - } - var method = editId ? 'PUT' : 'POST'; - var url = editId ? ('/api/admin/shop-manage-group/' + editId) : '/api/admin/shop-manage-group'; - fetch(url, { - method: method, - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ group_name: groupName }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - msgEl.textContent = res.error || '淇濆瓨澶辫触'; - msgEl.className = 'msg err'; - return; - } - document.getElementById('shopManageGroupEditId').value = ''; - document.getElementById('shopManageGroupInput').value = ''; - msgEl.textContent = res.msg || '淇濆瓨鎴愬姛'; - msgEl.className = 'msg ok'; - loadShopManageGroups().then(function () { - renderShopManageGroupRows(); - loadShopManage(shopManagePage); - loadSkipPriceAsin(skipPriceAsinPage); - }); - }) - .catch(function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.className = 'msg err'; - }); - }; - - function refreshShopGroupSelects(selectedCreateId, selectedEditId) { - var createSel = document.getElementById('shopManageGroupSelect'); - var editSel = document.getElementById('editShopManageGroupSelect'); - var filterSel = document.getElementById('shopManageFilterGroupId'); - var skipCreateSel = document.getElementById('skipPriceAsinGroupSelect'); - var skipFilterSel = document.getElementById('skipPriceAsinFilterGroupId'); - var chooseSkipShopSel = document.getElementById('chooseSkipPriceAsinShopGroupId'); - var selectedFilterId = filterSel ? filterSel.value : ''; - var selectedSkipCreateId = skipCreateSel ? skipCreateSel.value : ''; - var selectedSkipFilterId = skipFilterSel ? skipFilterSel.value : ''; - var selectedChooseSkipShopId = chooseSkipShopSel ? chooseSkipShopSel.value : ''; - var createOpts = ['']; - var filterOpts = ['']; - shopManageGroups.forEach(function (g) { - var option = ''; - createOpts.push(option); - filterOpts.push(option); - }); - createSel.innerHTML = createOpts.join(''); - editSel.innerHTML = createOpts.join(''); - if (skipCreateSel) skipCreateSel.innerHTML = createOpts.join(''); - if (filterSel) filterSel.innerHTML = filterOpts.join(''); - if (skipFilterSel) skipFilterSel.innerHTML = filterOpts.join(''); - if (chooseSkipShopSel) chooseSkipShopSel.innerHTML = filterOpts.join(''); - if (selectedCreateId != null) createSel.value = String(selectedCreateId); - if (selectedEditId != null) editSel.value = String(selectedEditId); - if (filterSel && selectedFilterId) filterSel.value = selectedFilterId; - if (skipCreateSel && selectedSkipCreateId) skipCreateSel.value = selectedSkipCreateId; - if (skipFilterSel && selectedSkipFilterId) skipFilterSel.value = selectedSkipFilterId; - if (chooseSkipShopSel && selectedChooseSkipShopId) chooseSkipShopSel.value = selectedChooseSkipShopId; - } - - function loadShopManageGroups(selectedCreateId, selectedEditId) { - return fetch('/api/admin/shop-manage-groups') - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) throw new Error(res.error || '鍔犺浇鍒嗙粍澶辫触'); - shopManageGroups = res.items || []; - refreshShopGroupSelects(selectedCreateId, selectedEditId); - return shopManageGroups; - }) - .catch(function () { - shopManageGroups = []; - refreshShopGroupSelects(); - return []; - }); - } - - function resetShopManageGroupForm() { - document.getElementById('shopManageGroupEditId').value = ''; - document.getElementById('shopManageGroupInput').value = ''; - document.getElementById('msgShopManageGroup').textContent = ''; - document.getElementById('msgShopManageGroup').className = 'msg'; - refreshShopManageGroupUserSelect(''); - } - - function openShopManageGroupModal() { - resetShopManageGroupForm(); - loadUserOptions() - .then(function () { return loadShopManageGroups(); }) - .then(function () { - renderShopManageGroupRows(); - document.getElementById('shopManageGroupModal').classList.add('show'); - }); - } - - function renderShopManageGroupRows() { - var tbody = document.getElementById('shopManageGroupListBody'); - if (!shopManageGroups.length) { - tbody.innerHTML = '鏆傛棤鍒嗙粍'; - return; - } - tbody.innerHTML = shopManageGroups.map(function (item, index) { - return '' + (index + 1) + '' + (item.group_name || '') + '' + (item.username || '') + '' + (item.created_at || '') + '' + (item.updated_at || '') + '' + - ' ' + - '' + - ''; - }).join(''); - - document.querySelectorAll('[data-shop-group-edit]').forEach(function (btn) { - btn.onclick = function () { - document.getElementById('shopManageGroupEditId').value = btn.dataset.shopGroupEdit; - refreshShopManageGroupUserSelect(btn.dataset.shopGroupUserId || ''); - }; - }); - - document.querySelectorAll('[data-shop-group-delete]').forEach(function (btn) { - btn.onclick = function () { - var gid = btn.dataset.shopGroupDelete; - var gname = (btn.dataset.shopGroupName || '').replace(/"/g, '"'); - if (!confirm('纭畾鍒犻櫎鍒嗙粍鈥? + gname + '鈥濆悧锛?)) return; - fetch('/api/admin/shop-manage-group/' + gid, { method: 'DELETE' }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - alert(res.error || '鍒犻櫎澶辫触'); - return; - } - loadShopManageGroups().then(function () { - renderShopManageGroupRows(); - loadShopManage(shopManagePage); - loadSkipPriceAsin(skipPriceAsinPage); - }); - }); - }; - }); - } - - document.getElementById('shopManageGroupUserSelect').onchange = syncShopManageGroupNameWithUser; - document.getElementById('btnManageShopGroups').onclick = openShopManageGroupModal; - document.getElementById('btnManageShopGroupsFromEdit').onclick = openShopManageGroupModal; - document.getElementById('btnManageSkipPriceAsinGroups').onclick = openShopManageGroupModal; - document.getElementById('btnCloseShopManageGroupModal').onclick = function () { - document.getElementById('shopManageGroupModal').classList.remove('show'); - }; - document.getElementById('btnCancelShopManageGroupEdit').onclick = resetShopManageGroupForm; - document.getElementById('btnSaveShopManageGroup').onclick = function () { - var editId = (document.getElementById('shopManageGroupEditId').value || '').trim(); - var groupName = (document.getElementById('shopManageGroupInput').value || '').trim(); - var userId = (document.getElementById('shopManageGroupUserSelect').value || '').trim(); - var msgEl = document.getElementById('msgShopManageGroup'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!userId || !groupName) { - msgEl.textContent = '璇烽€夋嫨鍏宠仈鐢ㄦ埛'; - msgEl.className = 'msg err'; - return; - } - var method = editId ? 'PUT' : 'POST'; - var url = editId ? ('/api/admin/shop-manage-group/' + editId) : '/api/admin/shop-manage-group'; - fetch(url, { - method: method, - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ group_name: groupName, user_id: Number(userId) }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - msgEl.textContent = res.error || '淇濆瓨澶辫触'; - msgEl.className = 'msg err'; - return; - } - resetShopManageGroupForm(); - msgEl.textContent = res.msg || '淇濆瓨鎴愬姛'; - msgEl.className = 'msg ok'; - loadShopManageGroups().then(function () { - renderShopManageGroupRows(); - loadShopManage(shopManagePage); - loadSkipPriceAsin(skipPriceAsinPage); - }); - }) - .catch(function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.className = 'msg err'; - }); - }; - - document.getElementById('btnCreateShopManage').onclick = function () { - var groupId = (document.getElementById('shopManageGroupSelect').value || '').trim(); - var shopName = (document.getElementById('shopManageShopName').value || '').trim(); - var mallName = (document.getElementById('shopManageMallName').value || '').trim(); - var account = (document.getElementById('shopManageAccount').value || '').trim(); - var password = (document.getElementById('shopManagePassword').value || '').trim(); - var msgEl = document.getElementById('msgShopManage'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!groupId || !shopName || !mallName || !account || !password) { - msgEl.textContent = '璇峰畬鏁村~鍐欏垎缁勩€佸簵閾哄悕銆佸簵閾哄晢鍩庡悕銆佽处鍙枫€佸瘑鐮?; - msgEl.classList.add('err'); - return; - } - fetch('/api/admin/shop-manage', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ group_id: Number(groupId), shop_name: shopName, mall_name: mallName, account: account, password: password }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { - document.getElementById('shopManageGroupSelect').value = ''; - document.getElementById('shopManageShopName').value = ''; - document.getElementById('shopManageMallName').value = ''; - document.getElementById('shopManageAccount').value = ''; - document.getElementById('shopManagePassword').value = ''; - msgEl.textContent = res.msg || '鍒涘缓鎴愬姛'; - msgEl.className = 'msg ok'; - loadShopManage(1); - } else { - msgEl.textContent = res.error || '鍒涘缓澶辫触'; - msgEl.className = 'msg err'; - } - }) - .catch(function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.className = 'msg err'; - }); - }; - - document.getElementById('btnSaveShopManage').onclick = function () { - var itemId = document.getElementById('editShopManageId').value; - var groupId = (document.getElementById('editShopManageGroupSelect').value || '').trim(); - var shopName = (document.getElementById('editShopManageShopName').value || '').trim(); - var mallName = (document.getElementById('editShopManageMallName').value || '').trim(); - var account = (document.getElementById('editShopManageAccount').value || '').trim(); - var password = (document.getElementById('editShopManagePassword').value || '').trim(); - var msgEl = document.getElementById('msgEditShopManage'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!groupId || !shopName || !mallName || !account || !password) { - msgEl.textContent = '璇峰畬鏁村~鍐欏垎缁勩€佸簵閾哄悕銆佸簵閾哄晢鍩庡悕銆佽处鍙枫€佸瘑鐮?; - msgEl.classList.add('err'); - return; - } - fetch('/api/admin/shop-manage/' + itemId, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ group_id: Number(groupId), shop_name: shopName, mall_name: mallName, account: account, password: password }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { - document.getElementById('editShopManageModal').classList.remove('show'); - loadShopManage(shopManagePage); - } else { - msgEl.textContent = res.error || '淇濆瓨澶辫触'; - msgEl.classList.add('err'); - } - }) - .catch(function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.classList.add('err'); - }); - }; - document.getElementById('btnCloseEditShopManage').onclick = function () { - document.getElementById('editShopManageModal').classList.remove('show'); - }; - - // ========== 鐗堟湰绠$悊 ========== - // ========== 璺宠繃璺熶环 ASIN ========== - var skipPriceAsinPage = 1, skipPriceAsinPageSize = 15; - var chooseSkipPriceAsinShopPage = 1, chooseSkipPriceAsinShopPageSize = 10; - var skipPriceCountryColumns = [ - { code: 'DE', field: 'asin_de', label: '寰峰浗' }, - { code: 'UK', field: 'asin_uk', label: '鑻卞浗' }, - { code: 'FR', field: 'asin_fr', label: '娉曞浗' }, - { code: 'IT', field: 'asin_it', label: '鎰忓ぇ鍒? }, - { code: 'ES', field: 'asin_es', label: '瑗跨彮鐗? } - ]; - function buildChooseSkipPriceAsinShopQuery(page) { - var query = 'page=' + (page || 1) + '&page_size=' + chooseSkipPriceAsinShopPageSize; - var groupId = (document.getElementById('chooseSkipPriceAsinShopGroupId').value || '').trim(); - var shopName = (document.getElementById('chooseSkipPriceAsinShopKeyword').value || '').trim(); - if (groupId) query += '&group_id=' + encodeURIComponent(groupId); - if (shopName) query += '&shop_name=' + encodeURIComponent(shopName); - return query; - } - function loadChooseSkipPriceAsinShops(page) { - chooseSkipPriceAsinShopPage = page || 1; - fetch('/api/admin/shop-manages?' + buildChooseSkipPriceAsinShopQuery(chooseSkipPriceAsinShopPage)) - .then(function (r) { return r.json(); }) - .then(function (res) { - var tbody = document.getElementById('chooseSkipPriceAsinShopListBody'); - if (!res.success) { - tbody.innerHTML = '鍔犺浇澶辫触: ' + (res.error || '') + ''; - return; - } - var items = res.items || []; - if (!items.length) { - tbody.innerHTML = '鏆傛棤搴楅摵'; - } else { - tbody.innerHTML = items.map(function (item, index) { - var rowNo = (chooseSkipPriceAsinShopPage - 1) * chooseSkipPriceAsinShopPageSize + index + 1; - return '' + rowNo + '' + (item.group_name || '') + '' + (item.shop_name || '') + '' + (item.mall_name || '') + '' + (item.account || '') + '' + - '' + - ''; - }).join(''); - } - renderPagination('chooseSkipPriceAsinShopPagination', res.total, res.page, res.page_size, loadChooseSkipPriceAsinShops); - bindChooseSkipPriceAsinShopActions(); - }) - .catch(function () { - document.getElementById('chooseSkipPriceAsinShopListBody').innerHTML = '璇锋眰澶辫触'; - }); - } - function bindChooseSkipPriceAsinShopActions() { - document.querySelectorAll('[data-choose-skip-price-asin-shop]').forEach(function (btn) { - btn.onclick = function () { - document.getElementById('skipPriceAsinShopName').value = (btn.dataset.shopName || '').replace(/"/g, '"'); - if (!document.getElementById('skipPriceAsinGroupSelect').value && btn.dataset.groupId) { - document.getElementById('skipPriceAsinGroupSelect').value = btn.dataset.groupId; - } - document.getElementById('chooseSkipPriceAsinShopModal').classList.remove('show'); - }; - }); - } - function openChooseSkipPriceAsinShopModal() { - var currentGroupId = (document.getElementById('skipPriceAsinGroupSelect').value || '').trim(); - if (currentGroupId) { - document.getElementById('chooseSkipPriceAsinShopGroupId').value = currentGroupId; - } - document.getElementById('chooseSkipPriceAsinShopKeyword').value = (document.getElementById('skipPriceAsinShopName').value || '').trim(); - document.getElementById('chooseSkipPriceAsinShopModal').classList.add('show'); - loadChooseSkipPriceAsinShops(1); - } - function setupSkipPriceAsinShopPicker() { - var input = document.getElementById('skipPriceAsinShopName'); - var button = document.getElementById('btnChooseSkipPriceAsinShop'); - if (!input || !button) return; - var inputParent = input.parentNode; - if (inputParent && inputParent.style && inputParent.style.display === 'flex') return; - var legacyWrap = button.parentNode; - var wrapper = document.createElement('div'); - wrapper.style.display = 'flex'; - wrapper.style.gap = '8px'; - wrapper.style.alignItems = 'center'; - input.parentNode.insertBefore(wrapper, input); - wrapper.appendChild(input); - wrapper.appendChild(button); - button.style.whiteSpace = 'nowrap'; - button.style.marginTop = '0'; - if (legacyWrap && legacyWrap !== wrapper) { - legacyWrap.style.display = 'none'; - } - } - function getSelectedSkipPriceCountries() { - return Array.from(document.getElementById('skipPriceAsinCountries').selectedOptions).map(function (option) { - return option.value; - }); - } - function renderSkipPriceAsinInputs() { - var container = document.getElementById('skipPriceAsinInputs'); - var selectedCountries = getSelectedSkipPriceCountries(); - var existingValues = {}; - container.querySelectorAll('[data-skip-price-country-input]').forEach(function (input) { - existingValues[input.getAttribute('data-skip-price-country-input')] = input.value; - }); - if (!selectedCountries.length) { - container.innerHTML = '
璇烽€夋嫨鍥藉鍚庤緭鍏?ASIN
'; - return; - } - container.innerHTML = selectedCountries.map(function (countryCode) { - var country = skipPriceCountryColumns.find(function (item) { return item.code === countryCode; }); - var label = country ? country.label : countryCode; - var value = existingValues[countryCode] || ''; - return '
' + - '' + label + '' + - '' + - '
'; - }).join(''); - } - function collectSkipPriceAsinMappings(countries) { - var mappings = {}; - for (var i = 0; i < countries.length; i++) { - var countryCode = countries[i]; - var input = document.querySelector('[data-skip-price-country-input="' + countryCode + '"]'); - var asin = input ? (input.value || '').trim().toUpperCase() : ''; - if (!asin) { - var country = skipPriceCountryColumns.find(function (item) { return item.code === countryCode; }); - var label = country ? country.label : countryCode; - throw new Error(label + ' ASIN 涓嶈兘涓虹┖'); - } - mappings[countryCode] = asin; - } - return mappings; - } - function buildSkipPriceAsinQuery(page) { - var query = 'page=' + (page || 1) + '&page_size=' + skipPriceAsinPageSize; - var groupId = (document.getElementById('skipPriceAsinFilterGroupId').value || '').trim(); - var shopName = (document.getElementById('skipPriceAsinFilterShopName').value || '').trim(); - var asin = (document.getElementById('skipPriceAsinFilterAsin').value || '').trim(); - if (groupId) query += '&group_id=' + encodeURIComponent(groupId); - if (shopName) query += '&shop_name=' + encodeURIComponent(shopName); - if (asin) query += '&asin=' + encodeURIComponent(asin); - return query; - } - function renderSkipPriceAsinCell(item, country) { - var value = item[country.field] || ''; - if (!value) return '-'; - return '
' + - '' + value + '' + - '' + - '' + - '
'; - } - function bindSkipPriceAsinActions() { - document.querySelectorAll('[data-skip-price-asin-edit]').forEach(function (btn) { - btn.onclick = function () { - var shopName = (btn.dataset.shopName || '').replace(/"/g, '"'); - var countryCode = btn.dataset.country || ''; - var currentAsin = (btn.dataset.asin || '').replace(/"/g, '"'); - var nextAsin = prompt('璇疯緭鍏ュ簵閾衡€? + shopName + '鈥濆湪 ' + countryCode + ' 鐨?ASIN', currentAsin); - if (nextAsin === null) return; - nextAsin = (nextAsin || '').trim(); - if (!nextAsin) { - alert('ASIN 涓嶈兘涓虹┖'); - return; - } - fetch('/api/admin/skip-price-asin/' + btn.dataset.skipPriceAsinEdit + '/country/' + countryCode, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ asin: nextAsin }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) loadSkipPriceAsin(skipPriceAsinPage); - else alert(res.error || '淇濆瓨澶辫触'); - }); - }; - }); - document.querySelectorAll('[data-skip-price-asin-delete]').forEach(function (btn) { - btn.onclick = function () { - var shopName = (btn.dataset.shopName || '').replace(/"/g, '"'); - var countryCode = btn.dataset.country || ''; - if (!confirm('纭畾鍒犻櫎搴楅摵鈥? + shopName + '鈥濆湪 ' + countryCode + ' 鐨?ASIN 鍚楋紵')) return; - fetch('/api/admin/skip-price-asin/' + btn.dataset.skipPriceAsinDelete + '/country/' + countryCode, { - method: 'DELETE' - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) loadSkipPriceAsin(skipPriceAsinPage); - else alert(res.error || '鍒犻櫎澶辫触'); - }); - }; - }); - } - function loadSkipPriceAsin(page) { - skipPriceAsinPage = page || 1; - fetch('/api/admin/skip-price-asins?' + buildSkipPriceAsinQuery(skipPriceAsinPage)) - .then(function (r) { return r.json(); }) - .then(function (res) { - var tbody = document.getElementById('skipPriceAsinListBody'); - if (!res.success) { - tbody.innerHTML = '鍔犺浇澶辫触: ' + (res.error || '') + ''; - return; - } - var items = res.items || []; - if (items.length === 0) { - tbody.innerHTML = '鏆傛棤鏁版嵁'; - } else { - tbody.innerHTML = items.map(function (item, index) { - var rowNo = (skipPriceAsinPage - 1) * skipPriceAsinPageSize + index + 1; - return '' + rowNo + '' + (item.group_name || '') + '' + (item.shop_name || '') + '' + - skipPriceCountryColumns.map(function (country) { - return '' + renderSkipPriceAsinCell(item, country) + ''; - }).join('') + - ''; - }).join(''); - } - renderPagination('skipPriceAsinPagination', res.total, res.page, res.page_size, loadSkipPriceAsin); - bindSkipPriceAsinActions(); - }) - .catch(function () { - document.getElementById('skipPriceAsinListBody').innerHTML = '璇锋眰澶辫触'; - }); - } - document.getElementById('btnManageSkipPriceAsinGroups').onclick = openShopManageGroupModal; - document.getElementById('btnChooseSkipPriceAsinShop').onclick = openChooseSkipPriceAsinShopModal; - document.getElementById('btnSearchChooseSkipPriceAsinShop').onclick = function () { - loadChooseSkipPriceAsinShops(1); - }; - document.getElementById('chooseSkipPriceAsinShopGroupId').onchange = function () { - loadChooseSkipPriceAsinShops(1); - }; - document.getElementById('chooseSkipPriceAsinShopKeyword').addEventListener('keydown', function (e) { - if (e.key === 'Enter') { - e.preventDefault(); - loadChooseSkipPriceAsinShops(1); - } - }); - document.getElementById('btnCloseChooseSkipPriceAsinShopModal').onclick = function () { - document.getElementById('chooseSkipPriceAsinShopModal').classList.remove('show'); - }; - document.getElementById('skipPriceAsinCountries').addEventListener('change', renderSkipPriceAsinInputs); - document.getElementById('btnSearchSkipPriceAsin').onclick = function () { - loadSkipPriceAsin(1); - }; - document.getElementById('skipPriceAsinFilterShopName').addEventListener('keydown', function (e) { - if (e.key === 'Enter') { - e.preventDefault(); - loadSkipPriceAsin(1); - } - }); - document.getElementById('skipPriceAsinFilterAsin').addEventListener('keydown', function (e) { - if (e.key === 'Enter') { - e.preventDefault(); - loadSkipPriceAsin(1); - } - }); - document.getElementById('btnCreateSkipPriceAsin').onclick = function () { - var groupId = (document.getElementById('skipPriceAsinGroupSelect').value || '').trim(); - var shopName = (document.getElementById('skipPriceAsinShopName').value || '').trim(); - var countries = getSelectedSkipPriceCountries(); - var msgEl = document.getElementById('msgSkipPriceAsin'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!groupId || !shopName || !countries.length) { - msgEl.textContent = '璇峰畬鏁村~鍐欏垎缁勩€佸簵閾哄悕銆佸浗瀹跺拰 ASIN'; - msgEl.className = 'msg err'; - return; - } - var asinMappings = {}; - try { - asinMappings = collectSkipPriceAsinMappings(countries); - } catch (err) { - msgEl.textContent = err.message || '璇疯緭鍏?ASIN'; - msgEl.className = 'msg err'; - return; - } - var fallbackAsin = ''; - Object.keys(asinMappings).some(function (countryCode) { - fallbackAsin = asinMappings[countryCode] || ''; - return !!fallbackAsin; - }); - fetch('/api/admin/skip-price-asin', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - group_id: Number(groupId), - shop_name: shopName, - countries: countries, - asin: fallbackAsin, - asin_mappings: asinMappings - }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (!res.success) { - msgEl.textContent = res.error || '淇濆瓨澶辫触'; - msgEl.className = 'msg err'; - return; - } - document.getElementById('skipPriceAsinGroupSelect').value = ''; - document.getElementById('skipPriceAsinShopName').value = ''; - Array.from(document.getElementById('skipPriceAsinCountries').options).forEach(function (option) { - option.selected = false; - }); - renderSkipPriceAsinInputs(); - msgEl.textContent = res.msg || '淇濆瓨鎴愬姛'; - msgEl.className = 'msg ok'; - loadSkipPriceAsin(1); - }) - .catch(function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.className = 'msg err'; - }); - }; - setupSkipPriceAsinShopPicker(); - renderSkipPriceAsinInputs(); - function loadVersions() { - fetch('/api/admin/versions') - .then(function (r) { return r.json(); }) - .then(function (res) { - var tbody = document.getElementById('versionListBody'); - if (!res.success) { - tbody.innerHTML = '鍔犺浇澶辫触: ' + (res.error || '') + ''; - return; - } - var items = res.items || []; - if (items.length === 0) { - tbody.innerHTML = '鏆傛棤鐗堟湰璁板綍'; - } else { - tbody.innerHTML = items.map(function (v) { - var url = (v.file_url || '').replace(/"/g, '"'); - return '' + (v.version || '') + '' + url + '' + (v.created_at || '') + '涓嬭浇'; - }).join(''); - } - }) - .catch(function () { - document.getElementById('versionListBody').innerHTML = '璇锋眰澶辫触'; - }); - } - document.getElementById('btnUploadVersion').onclick = function () { - var version = (document.getElementById('versionNumber').value || '').trim(); - var fileInput = document.getElementById('versionZip'); - var msgEl = document.getElementById('msgVersion'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!version) { - msgEl.textContent = '璇峰~鍐欑増鏈彿'; - msgEl.classList.add('err'); - return; - } - if (!fileInput.files || fileInput.files.length === 0) { - msgEl.textContent = '璇烽€夋嫨 zip 鍘嬬缉鍖?; - msgEl.classList.add('err'); - return; - } - var file = fileInput.files[0]; - if (!(file.name || '').toLowerCase().endsWith('.zip')) { - msgEl.textContent = '浠呮敮鎸?.zip 鏍煎紡'; - msgEl.classList.add('err'); - return; - } - var formData = new FormData(); - formData.append('version', version); - formData.append('file', file); - msgEl.textContent = '涓婁紶涓?..'; - msgEl.classList.remove('err', 'ok'); - fetch('/api/admin/version', { - method: 'POST', - body: formData - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { - msgEl.textContent = '鍙戝竷鎴愬姛銆傜増鏈細' + res.version + '锛岄摼鎺ワ細' + (res.file_url || ''); - msgEl.classList.add('ok'); - document.getElementById('versionNumber').value = ''; - fileInput.value = ''; - loadVersions(); - } else { - msgEl.textContent = res.error || '涓婁紶澶辫触'; - msgEl.classList.add('err'); - } - }) - .catch(function () { - msgEl.textContent = '璇锋眰澶辫触'; - msgEl.classList.add('err'); - }); - }; - - // ========== 鏍忕洰鏉冮檺閰嶇疆 ========== - function loadColumns() { - fetch('/api/admin/columns') - .then(function (r) { return r.json(); }) - .then(function (res) { - var tbody = document.getElementById('columnListBody'); - if (!res.success) { - tbody.innerHTML = '鍔犺浇澶辫触: ' + (res.error || '') + ''; - return; - } - allColumnsList = res.items || []; - if (allColumnsList.length === 0) { - tbody.innerHTML = '鏆傛棤鑿滃崟锛岃鍦ㄤ笂鏂规柊澧?/td>'; - } else { - tbody.innerHTML = allColumnsList.map(function (c) { - return '' + c.id + '' + (c.name || '') + '' + (c.column_key || '') + '' + (c.created_at || '') + '' + - ' ' + - ''; - }).join(''); - } - bindColumnActions(); - }) - .catch(function () { - document.getElementById('columnListBody').innerHTML = '璇锋眰澶辫触'; - }); - } - function bindColumnActions() { - document.querySelectorAll('[data-column-edit]').forEach(function (btn) { - btn.onclick = function () { - document.getElementById('editColumnId').value = btn.dataset.columnEdit || ''; - document.getElementById('editColumnName').value = (btn.dataset.name || '').replace(/"/g, '"'); - document.getElementById('editColumnKey').value = (btn.dataset.key || '').replace(/"/g, '"'); - document.getElementById('msgEditColumn').textContent = ''; - document.getElementById('editColumnModal').classList.add('show'); - }; - }); - document.querySelectorAll('[data-column-delete]').forEach(function (btn) { - btn.onclick = function () { - if (!confirm('纭畾鍒犻櫎鑿滃崟鈥? + (btn.dataset.name || '').replace(/"/g, '"') + '鈥濆悧锛?)) return; - fetch('/api/admin/column/' + btn.dataset.columnDelete, { method: 'DELETE' }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { loadColumns(); loadColumnsForPermission(); loadAdminMenus(getActiveAdminTabName()); } - else { alert(res.error || '鍒犻櫎澶辫触'); } - }); - }; - }); - } - document.getElementById('btnAddColumn').onclick = function () { - var name = (document.getElementById('columnName').value || '').trim(); - var key = (document.getElementById('columnKey').value || '').trim(); - var msgEl = document.getElementById('msgColumn'); - msgEl.textContent = ''; - msgEl.className = 'msg'; - if (!name) { msgEl.textContent = '璇峰~鍐欒彍鍗曞悕绉?; msgEl.classList.add('err'); return; } - if (!key) { - msgEl.textContent = '璇峰~鍐欐爮鐩爣璇?; msgEl.classList.add('err'); return; - } - fetch('/api/admin/column', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name: name, column_key: key }) - }) - .then(function (r) { return r.json(); }) - .then(function (res) { - if (res.success) { - msgEl.textContent = res.msg || '鏂板鎴愬姛'; - msgEl.classList.add('ok'); - document.getElementById('columnName').value = ''; - document.getElementById('columnKey').value = ''; - loadColumns(); - loadColumnsForPermission(); - loadAdminMenus(getActiveAdminTabName()); - } else { diff --git a/backend/web_source/admin.html b/backend/web_source/admin.html index ccfb20b..04c7c99 100644 --- a/backend/web_source/admin.html +++ b/backend/web_source/admin.html @@ -336,6 +336,178 @@ font-size: 16px; } + #shopManageGroupModal .shop-group-modal { + width: min(1180px, calc(100vw - 48px)); + max-width: none; + max-height: calc(100vh - 48px); + padding: 0; + overflow: hidden; + display: flex; + flex-direction: column; + } + + .shop-group-modal-header { + padding: 20px 24px 14px; + border-bottom: 1px solid #edf0f5; + flex: 0 0 auto; + } + + .shop-group-modal-header h3 { + margin: 0; + } + + .shop-group-editor { + padding: 18px 24px 14px; + display: grid; + grid-template-columns: minmax(180px, 240px) minmax(240px, 320px) minmax(360px, 1fr); + gap: 16px; + align-items: start; + flex: 0 0 auto; + } + + .shop-group-editor .form-group { + margin-bottom: 0; + } + + .shop-group-member-select { + min-height: 128px; + max-height: 180px; + } + + .shop-group-help { + color: #777; + font-size: 12px; + line-height: 1.5; + margin-top: 6px; + } + + .shop-group-action-bar { + padding: 0 24px 12px; + display: flex; + gap: 8px; + justify-content: flex-end; + align-items: center; + flex: 0 0 auto; + } + + #msgShopManageGroup { + margin: 0; + padding: 0 24px 10px; + min-height: 18px; + flex: 0 0 auto; + } + + .shop-group-table-wrap { + margin: 0 24px; + border: 1px solid #edf0f5; + border-radius: 8px; + overflow: auto; + min-height: 220px; + flex: 1 1 auto; + } + + .shop-group-table { + min-width: 1040px; + table-layout: fixed; + } + + .shop-group-table th { + position: sticky; + top: 0; + z-index: 1; + } + + .shop-group-table th, + .shop-group-table td { + vertical-align: top; + } + + .shop-group-table .col-index { + width: 58px; + } + + .shop-group-table .col-name { + width: 130px; + } + + .shop-group-table .col-leader { + width: 110px; + } + + .shop-group-table .col-count { + width: 86px; + } + + .shop-group-table .col-members { + width: auto; + } + + .shop-group-table .col-time { + width: 120px; + } + + .shop-group-table .col-action { + width: 128px; + } + + .shop-group-time-cell, + .shop-group-action-cell { + white-space: nowrap; + } + + .shop-group-name-cell, + .shop-group-leader-cell { + word-break: break-word; + } + + .shop-group-members { + display: flex; + flex-wrap: wrap; + gap: 6px; + max-height: 96px; + overflow-y: auto; + padding-right: 4px; + } + + .shop-group-member-chip { + display: inline-flex; + align-items: center; + max-width: 150px; + padding: 3px 8px; + border-radius: 999px; + background: #eef1ff; + color: #4f63d8; + font-size: 12px; + line-height: 1.4; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .shop-group-modal-footer { + padding: 14px 24px 18px; + display: flex; + gap: 8px; + justify-content: flex-end; + border-top: 1px solid #edf0f5; + flex: 0 0 auto; + } + + @media (max-width: 900px) { + #shopManageGroupModal .shop-group-modal { + width: calc(100vw - 24px); + max-height: calc(100vh - 24px); + } + + .shop-group-editor { + grid-template-columns: 1fr; + } + + .shop-group-table-wrap { + margin: 0 16px; + } + } + .column-permission-cards { display: flex; flex-wrap: wrap; @@ -347,6 +519,97 @@ overflow: hidden; } + .multi-select-native { + position: absolute; + opacity: 0; + pointer-events: none; + width: 1px !important; + height: 1px !important; + min-height: 0 !important; + padding: 0 !important; + border: 0 !important; + } + + .multi-select-dropdown { + position: relative; + width: 100%; + } + + .multi-select-trigger { + width: 100%; + min-height: 42px; + padding: 10px 34px 10px 12px; + border: 1px solid #ddd; + border-radius: 6px; + background: #fff; + color: #333; + font-size: 14px; + text-align: left; + cursor: pointer; + position: relative; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .multi-select-trigger::after { + content: ""; + position: absolute; + right: 12px; + top: 50%; + width: 7px; + height: 7px; + border-right: 1.5px solid #666; + border-bottom: 1.5px solid #666; + transform: translateY(-65%) rotate(45deg); + } + + .multi-select-dropdown.open .multi-select-trigger { + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.12); + } + + .multi-select-panel { + display: none; + position: absolute; + left: 0; + right: 0; + top: calc(100% + 6px); + z-index: 30; + background: #fff; + border: 1px solid #ddd; + border-radius: 8px; + box-shadow: 0 12px 28px rgba(0, 0, 0, 0.14); + padding: 6px; + max-height: 220px; + overflow-y: auto; + } + + .multi-select-dropdown.open .multi-select-panel { + display: block; + } + + .multi-select-option { + display: flex; + align-items: center; + gap: 8px; + min-height: 34px; + padding: 7px 8px; + border-radius: 6px; + cursor: pointer; + font-size: 14px; + color: #333; + } + + .multi-select-option:hover { + background: #f4f6ff; + } + + .multi-select-option input { + width: auto; + margin: 0; + } + .column-permission-card { display: inline-flex; align-items: center; @@ -762,7 +1025,7 @@ - @@ -840,7 +1103,7 @@
- @@ -1063,48 +1326,62 @@