diff --git a/admin-frontend-vue/src/pages/shop/ShopKeysPage.vue b/admin-frontend-vue/src/pages/shop/ShopKeysPage.vue index d46485f5..f01f2981 100644 --- a/admin-frontend-vue/src/pages/shop/ShopKeysPage.vue +++ b/admin-frontend-vue/src/pages/shop/ShopKeysPage.vue @@ -4,8 +4,8 @@ import { formatDateTime } from '@/utils/datetime' * script 逻辑沿用现有 Vue 实现(编辑留空令牌=沿用);弹窗保留现有组件。 */ import { computed, onMounted, reactive, ref } from 'vue' import { ElMessage } from 'element-plus' -import { fetchShopKeyList, submitCreateShopKey, updateShopKey, deleteShopKey } from './shop-key-api.ts' -import { emptyShopKeyForm, normalizeZiniaoToken } from './shop-key-form-model.ts' +import { fetchShopKeyList, submitCreateShopKey, updateShopKey, deleteShopKey, checkShopKeyWhitelist } from './shop-key-api.ts' +import { emptyShopKeyForm, normalizeZiniaoToken, validateProxyUrl } from './shop-key-form-model.ts' import type { ShopKeyItem } from './shop-dto.ts' import { whitelistStatusMeta } from './shop-key-model.ts' import OldPagination from '@/components/OldPagination.vue' @@ -30,6 +30,8 @@ const filteredRows = computed(() => { const dialogVisible = ref(false) const editingId = ref(null) const saving = ref(false) +/** 正在检测白名单的行 id,用于按钮 loading 态。 */ +const checkingId = ref(null) const form = reactive(emptyShopKeyForm()) function maskToken(token: string): string { @@ -46,6 +48,7 @@ function whitelistTooltip(row: ShopKeyItem): string { const parts: string[] = [] if (row.ipWhitelistCheckedAt) parts.push(`检测时间:${formatDateTime(row.ipWhitelistCheckedAt)}`) if (row.ipWhitelistMessage) parts.push(row.ipWhitelistMessage) + if (row.ipWhitelistFailCount > 0) parts.push(`连续失败 ${row.ipWhitelistFailCount} 次`) if (parts.length === 0) parts.push(`白名单状态:${whitelistStatusMeta(row.ipWhitelistStatus).label}`) return parts.join('\n') } @@ -76,9 +79,30 @@ function openEdit(row: ShopKeyItem) { Object.assign(form, emptyShopKeyForm()) form.remarkName = row.remarkName form.ziniaoAccountName = row.ziniaoAccountName + form.proxyUrl = row.proxyUrl || '' dialogVisible.value = true } +/** 手动检测白名单:绕过缓存真实请求一次紫鸟,并重置自动重试次数。 */ +async function checkWhitelist(row: ShopKeyItem) { + checkingId.value = row.id + try { + const updated = await checkShopKeyWhitelist(row.id) + if (updated.ipWhitelistStatus === 'ALLOWED') { + ElMessage.success('白名单检测通过') + } else if (updated.ipWhitelistStatus === 'BLOCKED') { + ElMessage.error(`白名单未放行:${updated.ipWhitelistMessage || '请求被拒绝'}`) + } else { + ElMessage.warning(updated.ipWhitelistMessage || '检测未通过') + } + load() + } catch (error) { + ElMessage.error(error instanceof Error ? error.message : '检测失败') + } finally { + checkingId.value = null + } +} + async function submit() { const token = normalizeZiniaoToken(form.ziniaoToken) const creating = editingId.value == null @@ -90,6 +114,11 @@ async function submit() { ElMessage.warning('请完整填写紫鸟账号名称、紫鸟令牌') return } + const proxyError = validateProxyUrl(form.proxyUrl) + if (proxyError) { + ElMessage.warning(proxyError) + return + } // 编辑留空令牌 = 沿用原令牌(令牌不明文展示)。 form.ziniaoToken = creating ? token : token || originalToken.value saving.value = true @@ -169,6 +198,7 @@ onMounted(load) 备注名 紫鸟账号名称 紫鸟令牌 + 代理 白名单状态 创建时间 修改时间 @@ -184,6 +214,9 @@ onMounted(load) {{ maskToken(row.ziniaoToken) }} + + {{ row.proxyUrl || '直连' }} + {{ formatDateTime(row.updatedAt) }} + - 加载中... + 加载中... - {{ filterKeyword ? '暂无匹配密钥' : '暂无店铺密钥' }} + {{ filterKeyword ? '暂无匹配密钥' : '暂无店铺密钥' }} @@ -233,6 +274,15 @@ onMounted(load) :placeholder="editingId == null ? '粘贴紫鸟令牌(将自动去除 Bearer 前缀)' : '留空则沿用原令牌;如需更换请粘贴新令牌(将自动去除 Bearer 前缀)'" /> + + +
+ 仅当该账号的服务器出口 IP 不在紫鸟白名单时填写:代理 IP 需已由紫鸟公司加白,留空则直连。 +
+