diff --git a/app/amazon/__pycache__/approve.cpython-312.pyc b/app/amazon/__pycache__/approve.cpython-312.pyc new file mode 100644 index 0000000..48da58c Binary files /dev/null and b/app/amazon/__pycache__/approve.cpython-312.pyc differ diff --git a/app/amazon/__pycache__/del_brand.cpython-312.pyc b/app/amazon/__pycache__/del_brand.cpython-312.pyc index 96fe63e..093aca3 100644 Binary files a/app/amazon/__pycache__/del_brand.cpython-312.pyc and b/app/amazon/__pycache__/del_brand.cpython-312.pyc differ diff --git a/app/amazon/__pycache__/main.cpython-312.pyc b/app/amazon/__pycache__/main.cpython-312.pyc index e5f9bda..d385bf6 100644 Binary files a/app/amazon/__pycache__/main.cpython-312.pyc and b/app/amazon/__pycache__/main.cpython-312.pyc differ 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 8307cdc..4b1a1df 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 @@ -229,6 +229,9 @@ public class ProductRiskTaskService { task.setSuccessFileCount(ok); task.setFailedFileCount(fail); task.setUpdatedAt(LocalDateTime.now()); + + log.info("[product-risk] reconcile summary taskId={} ok={} fail={} allDone={}", + taskId, ok, fail, allDone); if (!allDone) { task.setStatus("RUNNING"); task.setErrorMessage(null); @@ -399,10 +402,17 @@ public class ProductRiskTaskService { payloadByShop.put(key, p); } } + log.info("[product-risk] submitResult received taskId={} payloadShopCount={} payloadKeys={}", + taskId, payloadByShop.size(), payloadByShop.keySet()); File workRoot = FileUtil.mkdir(FileUtil.file(System.getProperty("java.io.tmpdir"), "product-risk-result", String.valueOf(taskId))); List batchErrors = new ArrayList<>(); + int matchedShopCount = 0; + int skippedUnmatchedCount = 0; + int waitingCount = 0; + int assembledCount = 0; + int fallbackMatchedCount = 0; for (FileResultEntity fr : resultRows) { String shopKey = fr.getSourceFilename(); @@ -411,14 +421,17 @@ public class ProductRiskTaskService { // 兼容单店任务:若规范化店名偶发不一致(空格/符号差异)导致未命中,且任务与回传都只有 1 个店时按唯一店回填。 if (payload == null && resultRows.size() == 1 && payloadByShop.size() == 1) { payload = payloadByShop.values().iterator().next(); + fallbackMatchedCount++; log.warn("[product-risk] single-shop fallback matched taskId={} dbShop={} payloadShop={}", taskId, shopKey, payload.getShopName()); } if (payload == null) { + skippedUnmatchedCount++; // 与删除品牌一致:支持队列逐条处理、分次回传,未在本次 payload 中的店铺保持待处理 continue; } + matchedShopCount++; if (payload.getError() != null && !payload.getError().isBlank()) { markResultFailed(fr, payload.getError()); batchErrors.add(shopKey + ": " + payload.getError()); @@ -428,7 +441,16 @@ public class ProductRiskTaskService { // 分次上报累积:合并到 Redis 缓存,再按“合并后是否完成”决定是否出包。 ProductRiskShopPayloadDto mergedPayload = mergeShopPayload(taskId, shopKey, payload); - if (!isShopPayloadCompleted(mergedPayload)) { + boolean shopDone = isShopPayloadCompleted(mergedPayload); + log.info("[product-risk] shop merged taskId={} shop={} incomingDone={} mergedDone={} mergedRows={} payloadCountries={}", + taskId, + shopKey, + payloadHasAnyDoneTrue(payload), + shopDone, + countPayloadRows(mergedPayload), + mergedPayload.getCountries() == null ? 0 : mergedPayload.getCountries().size()); + if (!shopDone) { + waitingCount++; continue; } @@ -451,6 +473,9 @@ public class ProductRiskTaskService { fr.setSuccess(1); fr.setErrorMessage(null); fileResultMapper.updateById(fr); + assembledCount++; + log.info("[product-risk] shop assembled taskId={} shop={} rowCount={} file={}", + taskId, shopKey, fr.getRowCount(), fr.getResultFilename()); FileUtil.del(xlsx); FileUtil.del(zip); productRiskTaskCacheService.removeShopMergedPayload(taskId, shopKey); @@ -708,6 +733,20 @@ public class ProductRiskTaskService { if (row == null) { continue; } + + // 兼容 Python 末次“空行 done=true”心跳:视为当前国家已有行全部完成。 + if (isDoneValue(row.getDone()) && isEmptyBusinessRow(row) && !byKey.isEmpty()) { + for (Map.Entry entry : byKey.entrySet()) { + ProductRiskRowDto existed = entry.getValue(); + ProductRiskRowDto doneMerged = mergeRow(existed, row); + if (doneMerged.getDone() == null || doneMerged.getDone().isBlank()) { + doneMerged.setDone("true"); + } + entry.setValue(doneMerged); + } + continue; + } + String key = buildRowKey(row); ProductRiskRowDto prev = byKey.get(key); byKey.put(key, mergeRow(prev, row)); @@ -760,6 +799,22 @@ public class ProductRiskTaskService { return out; } + private boolean isEmptyBusinessRow(ProductRiskRowDto row) { + if (row == null) { + return true; + } + // 注意:shopName 只是上下文信息,不作为业务行是否为空的判定条件。 + // Python 末次心跳行通常只带 shopName + done=true,其他业务字段为空。 + return isBlank(row.getProductAsinSku()) + && isBlank(row.getRemoveAsin()) + && isBlank(row.getStatus()) + && isBlank(row.getRemoveStatus()); + } + + private boolean isBlank(String val) { + return val == null || val.trim().isEmpty(); + } + private String firstNonBlank(String preferred, String fallback) { if (preferred != null && !preferred.isBlank()) { return preferred; @@ -771,22 +826,61 @@ public class ProductRiskTaskService { if (payload == null || payload.getCountries() == null || payload.getCountries().isEmpty()) { return false; } - boolean hasAnyRow = false; + boolean hasAnyBusinessRow = false; for (List rows : payload.getCountries().values()) { if (rows == null || rows.isEmpty()) { continue; } for (ProductRiskRowDto row : rows) { - if (row == null) { + if (row == null || isEmptyBusinessRow(row)) { continue; } - hasAnyRow = true; + hasAnyBusinessRow = true; if (!isDoneValue(row.getDone())) { return false; } } } - return hasAnyRow; + return hasAnyBusinessRow; + } + + private int countPayloadRows(ProductRiskShopPayloadDto payload) { + if (payload == null || payload.getCountries() == null || payload.getCountries().isEmpty()) { + return 0; + } + int n = 0; + for (List rows : payload.getCountries().values()) { + if (rows == null) { + continue; + } + for (ProductRiskRowDto row : rows) { + if (row == null || isEmptyBusinessRow(row)) { + continue; + } + n++; + } + } + return n; + } + + private boolean payloadHasAnyDoneTrue(ProductRiskShopPayloadDto payload) { + if (payload == null || payload.getCountries() == null) { + return false; + } + for (List rows : payload.getCountries().values()) { + if (rows == null) { + continue; + } + for (ProductRiskRowDto row : rows) { + if (row == null || isEmptyBusinessRow(row)) { + continue; + } + if (isDoneValue(row.getDone())) { + return true; + } + } + } + return false; } private boolean isDoneValue(String value) { diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/dto/ShopManageCreateRequest.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/dto/ShopManageCreateRequest.java index 71ccced..b9148a0 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/dto/ShopManageCreateRequest.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/dto/ShopManageCreateRequest.java @@ -13,6 +13,9 @@ public class ShopManageCreateRequest { @NotBlank(message = "店铺名不能为空") private String shopName; + @NotBlank(message = "店铺商城名不能为空") + private String mallName; + @NotBlank(message = "账号不能为空") private String account; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/dto/ShopManageUpdateRequest.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/dto/ShopManageUpdateRequest.java index 193b46c..2f2fe5e 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/dto/ShopManageUpdateRequest.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/dto/ShopManageUpdateRequest.java @@ -13,6 +13,9 @@ public class ShopManageUpdateRequest { @NotBlank(message = "店铺名不能为空") private String shopName; + @NotBlank(message = "店铺商城名不能为空") + private String mallName; + @NotBlank(message = "账号不能为空") private String account; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/entity/ShopManageEntity.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/entity/ShopManageEntity.java index 5f4c00a..502110c 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/entity/ShopManageEntity.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/entity/ShopManageEntity.java @@ -1,6 +1,7 @@ package com.nanri.aiimage.modules.shopkey.model.entity; import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @@ -16,6 +17,8 @@ public class ShopManageEntity { private Long groupId; private String groupName; private String shopName; + @TableField("mall_name") + private String mallName; private String account; private String password; private LocalDateTime createdAt; diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/vo/ShopManageCredentialVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/vo/ShopManageCredentialVo.java index d7dd379..fc4890d 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/vo/ShopManageCredentialVo.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/vo/ShopManageCredentialVo.java @@ -8,6 +8,7 @@ public class ShopManageCredentialVo { private Long groupId; private String groupName; private String shopName; + private String mallName; private String account; private String password; } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/vo/ShopManageItemVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/vo/ShopManageItemVo.java index 6967d77..709772a 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/vo/ShopManageItemVo.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/shopkey/model/vo/ShopManageItemVo.java @@ -11,6 +11,7 @@ public class ShopManageItemVo { private Long groupId; private String groupName; private String shopName; + private String mallName; private String account; private String password; private String passwordMasked; 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 6aea8b0..661824b 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 @@ -57,12 +57,14 @@ Map groupNameById = buildGroupNameMap(rows.stream() public ShopManageItemVo create(ShopManageCreateRequest request) { ShopManageGroupEntity group = shopManageGroupService.getById(request.getGroupId()); String shopName = normalizeRequired(request.getShopName(), "店铺名不能为空"); + String mallName = normalizeRequired(request.getMallName(), "店铺商城名不能为空"); ensureShopNameUnique(shopName, null); ShopManageEntity entity = new ShopManageEntity(); entity.setGroupId(group.getId()); entity.setGroupName(group.getGroupName()); entity.setShopName(shopName); + entity.setMallName(mallName); entity.setAccount(normalizeRequired(request.getAccount(), "账号不能为空")); entity.setPassword(shopCredentialCryptoService.encrypt(normalizeRequired(request.getPassword(), "密码不能为空"))); shopManageMapper.insert(entity); @@ -75,11 +77,13 @@ Map groupNameById = buildGroupNameMap(rows.stream() ShopManageEntity entity = getById(id); ShopManageGroupEntity group = shopManageGroupService.getById(request.getGroupId()); String shopName = normalizeRequired(request.getShopName(), "店铺名不能为空"); + String mallName = normalizeRequired(request.getMallName(), "店铺商城名不能为空"); ensureShopNameUnique(shopName, id); entity.setGroupId(group.getId()); entity.setGroupName(group.getGroupName()); entity.setShopName(shopName); + entity.setMallName(mallName); entity.setAccount(normalizeRequired(request.getAccount(), "账号不能为空")); entity.setPassword(shopCredentialCryptoService.encrypt(normalizeRequired(request.getPassword(), "密码不能为空"))); shopManageMapper.updateById(entity); @@ -105,6 +109,7 @@ Map groupNameById = buildGroupNameMap(rows.stream() vo.setId(entity.getId()); vo.setGroupId(entity.getGroupId()); vo.setShopName(entity.getShopName()); + vo.setMallName(entity.getMallName()); vo.setAccount(entity.getAccount()); vo.setPassword(shopCredentialCryptoService.decrypt(entity.getPassword())); try { @@ -166,6 +171,7 @@ Map groupNameById = buildGroupNameMap(rows.stream() vo.setGroupId(entity.getGroupId()); vo.setGroupName(groupName == null ? "" : groupName); vo.setShopName(entity.getShopName()); + vo.setMallName(entity.getMallName()); vo.setAccount(entity.getAccount()); String masked = entity.getPassword() == null || entity.getPassword().isBlank() ? "" : "******"; // 兼容旧前端字段:password 继续返回脱敏值 diff --git a/backend-java/src/main/resources/db/V16__add_mall_name_to_shop_manage.sql b/backend-java/src/main/resources/db/V16__add_mall_name_to_shop_manage.sql new file mode 100644 index 0000000..8d530d9 --- /dev/null +++ b/backend-java/src/main/resources/db/V16__add_mall_name_to_shop_manage.sql @@ -0,0 +1,17 @@ +SET @db_name = DATABASE(); + +SET @mall_name_col_exists := ( + SELECT COUNT(*) + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db_name + AND TABLE_NAME = 'biz_shop_manage' + AND COLUMN_NAME = 'mall_name' +); + +SET @sql_add_mall_name_col := IF(@mall_name_col_exists = 0, + 'ALTER TABLE biz_shop_manage ADD COLUMN mall_name VARCHAR(128) NOT NULL DEFAULT '''' COMMENT ''店铺商城名'' AFTER shop_name', + 'SELECT 1' +); +PREPARE stmt_add_mall_name_col FROM @sql_add_mall_name_col; +EXECUTE stmt_add_mall_name_col; +DEALLOCATE PREPARE stmt_add_mall_name_col; diff --git a/backend/__pycache__/config.cpython-312.pyc b/backend/__pycache__/config.cpython-312.pyc index 7684111..7003e12 100644 Binary files a/backend/__pycache__/config.cpython-312.pyc and b/backend/__pycache__/config.cpython-312.pyc differ diff --git a/backend/blueprints/__pycache__/admin_api.cpython-312.pyc b/backend/blueprints/__pycache__/admin_api.cpython-312.pyc index cc9225f..7fa3a6a 100644 Binary files a/backend/blueprints/__pycache__/admin_api.cpython-312.pyc and b/backend/blueprints/__pycache__/admin_api.cpython-312.pyc differ diff --git a/backend/blueprints/admin_api.py b/backend/blueprints/admin_api.py index 1497c40..31a69f4 100644 --- a/backend/blueprints/admin_api.py +++ b/backend/blueprints/admin_api.py @@ -961,6 +961,7 @@ def list_shop_manages(): 'group_id': item.get('groupId'), 'group_name': item.get('groupName') or '', 'shop_name': item.get('shopName') or '', + 'mall_name': item.get('mallName') or '', 'account': item.get('account') or '', 'password': item.get('passwordMasked') or '', 'created_at': (item.get('createdAt') or '').replace('T', ' ')[:16], @@ -984,6 +985,7 @@ def create_shop_manage(): payload = { 'groupId': data.get('group_id'), 'shopName': (data.get('shop_name') or '').strip(), + 'mallName': (data.get('mall_name') or '').strip(), 'account': (data.get('account') or '').strip(), 'password': (data.get('password') or '').strip(), } @@ -1003,6 +1005,7 @@ def create_shop_manage(): 'group_id': item.get('groupId'), 'group_name': item.get('groupName') or '', 'shop_name': item.get('shopName') or '', + 'mall_name': item.get('mallName') or '', 'account': item.get('account') or '', 'password': item.get('password') or '', 'created_at': (item.get('createdAt') or '').replace('T', ' ')[:16], @@ -1018,6 +1021,7 @@ def update_shop_manage(item_id): payload = { 'groupId': data.get('group_id'), 'shopName': (data.get('shop_name') or '').strip(), + 'mallName': (data.get('mall_name') or '').strip(), 'account': (data.get('account') or '').strip(), 'password': (data.get('password') or '').strip(), } @@ -1036,6 +1040,7 @@ def update_shop_manage(item_id): 'id': item.get('id'), 'group_name': item.get('groupName') or '', 'shop_name': item.get('shopName') or '', + 'mall_name': item.get('mallName') or '', 'account': item.get('account') or '', 'password': item.get('password') or '', 'created_at': (item.get('createdAt') or '').replace('T', ' ')[:16], diff --git a/backend/config.py b/backend/config.py index 6692838..97a3eb7 100644 --- a/backend/config.py +++ b/backend/config.py @@ -23,8 +23,8 @@ 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://8.136.19.173: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('/') os.environ['OSS_ACCESS_KEY_ID'] = accessKeyId os.environ['OSS_ACCESS_KEY_SECRET'] = accessKeySecret os.environ['SECRET_KEY'] = "ddffc7c1d02121d9554d7b080b2511b6" diff --git a/backend/web_source/admin.html b/backend/web_source/admin.html index 668b502..d098ab0 100644 --- a/backend/web_source/admin.html +++ b/backend/web_source/admin.html @@ -364,6 +364,10 @@ +
+ + +
@@ -384,6 +388,7 @@ 序号 分组 店铺名 + 店铺商城名 账号 密码 创建时间 @@ -556,6 +561,10 @@
+
+ + +
@@ -1538,16 +1547,16 @@ .then(function(res) { var tbody = document.getElementById('shopManageListBody'); if (!res.success) { - tbody.innerHTML = '加载失败: ' + (res.error || '') + ''; + tbody.innerHTML = '加载失败: ' + (res.error || '') + ''; return; } var items = res.items || []; if (items.length === 0) { - tbody.innerHTML = '暂无店铺'; + 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.account || '') + '' + (item.password || '') + '' + (item.created_at || '') + '' + (item.updated_at || '') + '' + + return '' + rowNo + '' + (item.group_name || '') + '' + (item.shop_name || '') + '' + (item.mall_name || '') + '' + (item.account || '') + '' + (item.password || '') + '' + (item.created_at || '') + '' + (item.updated_at || '') + '' + ' ' + '' + ''; @@ -1557,7 +1566,7 @@ bindShopManageActions(); }) .catch(function() { - document.getElementById('shopManageListBody').innerHTML = '请求失败'; + document.getElementById('shopManageListBody').innerHTML = '请求失败'; }); } @@ -1568,6 +1577,7 @@ 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 = ''; @@ -1643,26 +1653,28 @@ 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 || !account || !password) { - msgEl.textContent = '请完整填写分组、店铺名、账号、密码'; + 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, account: account, password: password }) + 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 || '创建成功'; @@ -1683,20 +1695,21 @@ 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 || !account || !password) { - msgEl.textContent = '请完整填写分组、店铺名、账号、密码'; + 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, account: account, password: password }) + 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) { diff --git a/frontend-vue/src/pages/brand/components/BrandProductRiskTab.vue b/frontend-vue/src/pages/brand/components/BrandProductRiskTab.vue index 899cde4..d3d648a 100644 --- a/frontend-vue/src/pages/brand/components/BrandProductRiskTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandProductRiskTab.vue @@ -37,6 +37,7 @@
@@ -278,9 +279,18 @@ function isCountrySelected(code: string) { return orderedCountryCodes.value.includes(code) } +function isCountrySelectionLocked(code: string) { + return orderedCountryCodes.value.length === 1 && orderedCountryCodes.value[0] === code +} + function onCountryNativeChange(code: string, e: Event) { const el = e.target as HTMLInputElement | null if (!el) return + if (!el.checked && isCountrySelectionLocked(code)) { + el.checked = true + ElMessage.warning('至少保留 1 个国家') + return + } onCountryToggle(code, el.checked) }