From dc4090b20b69355f15cd4bc39d6b355b58141399 Mon Sep 17 00:00:00 2001 From: smail Date: Sun, 13 Sep 2026 23:23:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(security):=20=E4=BC=98=E5=8C=96=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF=E4=B8=93=E5=B1=9E=E6=8E=88=E6=9D=83=E7=A0=81?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E5=B8=83=E5=B1=80=E5=B9=B6=E6=94=AF=E6=8C=81?= =?UTF-8?q?60=E7=A7=92=E8=87=AA=E5=8A=A8=E9=9A=90=E8=97=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ThirdPartyAuthorization.css | 61 +++++++++----- .../ThirdPartyAuthorization.js | 62 ++++++++++---- .../ThirdPartyAuthorization.vue | 83 ++++++++++--------- 3 files changed, 126 insertions(+), 80 deletions(-) diff --git a/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.css b/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.css index 904d320..090ac05 100644 --- a/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.css +++ b/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.css @@ -1,4 +1,4 @@ -.third-party-authorization { +.third-party-authorization { padding: 0; display: flex; flex-direction: column; @@ -11,7 +11,7 @@ gap: 12px; } -/* 顶部轻量工具条 */ +/* 顶部轻量工具条:纯净标题栏 */ .compact-header { display: flex; align-items: center; @@ -45,40 +45,34 @@ text-overflow: ellipsis; } -.header-right { - display: flex; - align-items: center; - gap: 8px; - flex-shrink: 0; -} - /* 客户端选择与管理主卡片 */ .client-main-card { - padding: 12px 14px; + padding: 14px 16px; background: var(--pm-bg-secondary); border: 1px solid var(--pm-border-color); border-radius: var(--pm-radius-md); display: flex; flex-direction: column; - gap: 10px; + gap: 12px; } .client-picker-row { display: flex; align-items: center; justify-content: space-between; - gap: 12px; + gap: 14px; flex-wrap: wrap; } .picker-group { display: flex; align-items: center; - gap: 8px; + gap: 10px; + flex-wrap: wrap; } .picker-label { - font-size: 13px; + font-size: 14px; font-weight: 600; color: var(--pm-text-primary); white-space: nowrap; @@ -96,15 +90,19 @@ width: 100%; } +.btn-create-client { + font-weight: 600; +} + .picker-actions { display: flex; align-items: center; - gap: 8px; + gap: 10px; } /* 授权码详情框 */ .client-detail-box { - padding: 10px 12px; + padding: 12px 14px; background: var(--pm-bg-primary); border: 1px dashed var(--pm-border-color); border-radius: var(--pm-radius-sm); @@ -115,7 +113,7 @@ align-items: center; justify-content: space-between; flex-wrap: wrap; - gap: 8px; + gap: 10px; } .code-label { @@ -128,7 +126,8 @@ .code-value-area { display: flex; align-items: center; - gap: 8px; + gap: 10px; + flex-wrap: wrap; } .masked-code { @@ -136,22 +135,30 @@ font-family: monospace; font-size: 14px; color: var(--pm-text-secondary); + font-weight: 600; } .revealed-code { - padding: 2px 8px; + padding: 4px 10px; border-radius: 4px; background: color-mix(in srgb, var(--pm-primary-color) 12%, var(--pm-bg-secondary)); color: var(--pm-primary-color); font-family: monospace; - font-size: 14px; + font-size: 15px; font-weight: 700; letter-spacing: 1px; } .action-btn { font-size: 13px; - padding: 0 4px; +} + +.countdown-tag { + font-weight: 600; +} + +.btn-verify-reveal { + font-weight: 600; } .text-tip { @@ -169,11 +176,15 @@ } .empty-client-box { - padding: 12px; + padding: 18px 12px; text-align: center; background: var(--pm-bg-primary); border: 1px dashed var(--pm-border-color); border-radius: var(--pm-radius-sm); + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; } .empty-tip { @@ -181,6 +192,10 @@ color: var(--pm-text-placeholder, #909399); } +.empty-action { + margin-top: 4px; +} + /* 紧凑指引网格 */ .compact-guide-section { display: flex; @@ -280,4 +295,4 @@ flex-direction: column; align-items: stretch; } -} +} \ No newline at end of file diff --git a/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.js b/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.js index a27006e..3ad9b11 100644 --- a/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.js +++ b/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.js @@ -1,4 +1,4 @@ -import { computed, onMounted, reactive, ref } from 'vue' +import { computed, onMounted, onUnmounted, reactive, ref } from 'vue' import { ElMessageBox, ElNotification } from 'element-plus' @@ -13,6 +13,8 @@ export function usePage() { // 解密后的各客户端授权码明文 { [clientId]: 'xxxx-yyyy-...' } const revealedCodes = reactive({}) const codesRevealed = ref(false) + const revealCountdown = ref(0) + let countdownTimer = null // 新增客户端弹窗 const createDialogVisible = ref(false) @@ -37,6 +39,36 @@ export function usePage() { }) } + const clearCountdownTimer = () => { + if (countdownTimer) { + clearInterval(countdownTimer) + countdownTimer = null + } + } + + const hideCodesAutomatically = (silent = false) => { + clearCountdownTimer() + revealCountdown.value = 0 + Object.keys(revealedCodes).forEach(k => delete revealedCodes[k]) + codesRevealed.value = false + if (!silent) { + notify('授权码明文已达到 60 秒安全保护期,已自动隐藏', 'info') + } + } + + const startRevealCountdown = (seconds = 60) => { + clearCountdownTimer() + revealCountdown.value = seconds + codesRevealed.value = true + + countdownTimer = setInterval(() => { + revealCountdown.value -= 1 + if (revealCountdown.value <= 0) { + hideCodesAutomatically(false) + } + }, 1000) + } + const formatTime = (timeStr) => { if (!timeStr) return '--' try { @@ -124,9 +156,9 @@ export function usePage() { if (item && item.id) { selectedClientId.value = item.id revealedCodes[item.id] = code - codesRevealed.value = true + startRevealCountdown(60) } - notify(`客户端「${name}」专属授权码已创建并就绪`, 'success') + notify(`客户端「${name}」专属授权码已创建并就绪(60秒内有效显示)`, 'success') } catch (error) { notify(error?.message || '创建客户端授权码失败') } finally { @@ -160,8 +192,8 @@ export function usePage() { } const newCode = response.data?.code || '' revealedCodes[client.id] = newCode - codesRevealed.value = true - notify(`客户端「${client.client_name}」专属授权码已重新生成`, 'success') + startRevealCountdown(60) + notify(`客户端「${client.client_name}」专属授权码已重新生成(60秒内有效显示)`, 'success') await loadClientCredentials() } catch (error) { notify(error?.message || '重新生成失败') @@ -253,13 +285,13 @@ export function usePage() { const map = response.data || {} Object.keys(revealedCodes).forEach(k => delete revealedCodes[k]) Object.assign(revealedCodes, map) - codesRevealed.value = true closeVerifyDialog() const count = Object.keys(map).length if (count > 0) { - notify(`安全验证通过,已解密显示客户端专属授权码`, 'success') + startRevealCountdown(60) + notify('安全验证通过,授权码将在 60 秒内明文显示', 'success') } else { - notify('安全验证通过,当前未发现加密密文,可重新生成', 'warning') + notify('安全验证通过,当前未发现加密密文,可点击重新生成', 'warning') } } catch (error) { notify(error?.message || '验证失败,请检查密码是否正确') @@ -268,17 +300,15 @@ export function usePage() { } } - const hideRevealedCodes = () => { - Object.keys(revealedCodes).forEach(k => delete revealedCodes[k]) - codesRevealed.value = false - notify('已恢复安全隐藏状态', 'info') - } - onMounted(() => { loadBaseInfo() loadClientCredentials() }) + onUnmounted(() => { + clearCountdownTimer() + }) + return { loading, clientList, @@ -287,6 +317,7 @@ export function usePage() { currentDomain, revealedCodes, codesRevealed, + revealCountdown, createDialogVisible, newClientName, createLoading, @@ -304,6 +335,5 @@ export function usePage() { openVerifyDialog, closeVerifyDialog, submitVerifyAndReveal, - hideRevealedCodes, } -} +} \ No newline at end of file diff --git a/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.vue b/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.vue index 1527d96..3d942ff 100644 --- a/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.vue +++ b/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.vue @@ -1,39 +1,12 @@ -