diff --git a/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.css b/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.css index 1626516..4a124ba 100644 --- a/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.css +++ b/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.css @@ -18,8 +18,8 @@ .authorization-header h3 { margin: 0 0 8px; color: var(--pm-text-primary); - font-size: 18px; - font-weight: 600; + font-size: 20px; + font-weight: 700; } .authorization-desc { @@ -40,18 +40,19 @@ } .protocol-list { - display: grid; - gap: 12px; + display: flex; + flex-direction: column; + gap: 16px; } .protocol-item { display: flex; align-items: center; justify-content: space-between; - gap: 18px; - padding: 16px; + gap: 20px; + padding: 16px 20px; + border-radius: var(--pm-radius-md); border: 1px solid var(--pm-border-color); - border-radius: var(--pm-radius-sm); background: var(--pm-bg-secondary); } @@ -63,8 +64,7 @@ .protocol-title { display: flex; align-items: center; - flex-wrap: wrap; - gap: 8px; + gap: 10px; } .protocol-title strong { @@ -154,9 +154,10 @@ .protocol-item { align-items: stretch; flex-direction: column; + gap: 14px; } .protocol-actions { justify-content: flex-start; } -} +} \ 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 2efee64..66e9020 100644 --- a/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.js +++ b/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.js @@ -10,77 +10,107 @@ export function usePage() { const generatedProtocolCode = ref('') const generatedProtocol = ref('') - // ??????????{ imap: 'xxxx', pop3: 'yyyy', smtp: 'zzzz' } + // 解密后的各协议授权码明文 { imap: 'xxxx', pop3: 'yyyy', smtp: 'zzzz' } const revealedCodes = reactive({}) - // ??????????????? + // 是否已完成密码安全验证并展示 const codesRevealed = ref(false) - // ???????? + // 密码验证查看弹窗 const verifyDialogVisible = ref(false) const verifyPassword = ref('') const verifyLoading = ref(false) const notify = (message, type = 'error') => { ElNotification({ - title: type === 'success' ? '????' : '????', + title: type === 'success' ? '操作成功' : '操作失败', message, type, }) } - const protocolLabel = protocol => ({ - imap: 'IMAP', - pop3: 'POP3', - smtp: 'SMTP', - }[protocol] || protocol.toUpperCase()) + const protocolLabel = (protocol = '') => { + const map = { + smtp: 'SMTP (代发/发信)', + imap: 'IMAP (收信/同步)', + pop3: 'POP3 (收信)', + } + return map[protocol.toLowerCase()] || protocol.toUpperCase() + } const loadProtocolCredentials = async () => { protocolLoading.value = true try { const response = await http.get('/api/v1/security/credentials/protocols') if (response.errorNo !== 0) { - throw new Error(response.errorMsg || '?????????????') + throw new Error(response.errorMsg || '读取邮件客户端协议设置失败') } protocolCredentials.value = Array.isArray(response.data) ? response.data : [] } catch (error) { - notify(error?.message || '?????????????') + notify(error?.message || '读取邮件客户端协议设置失败') } finally { protocolLoading.value = false } } const setProtocolEnabled = async (item, enabled) => { - if (protocolLoading.value || item.enabled === enabled) return - const previous = item.enabled - item.enabled = enabled protocolLoading.value = true try { - const response = await http.put('/api/v1/security/credentials/protocols/enabled', { + const response = await http.post('/api/v1/security/credentials/protocols/status', { protocol: item.protocol, enabled, }) if (response.errorNo !== 0) { - throw new Error(response.errorMsg || '??????') + throw new Error(response.errorMsg || '更新协议开启状态失败') } - if (!enabled) { - if (generatedProtocol.value === item.protocol) { - generatedProtocol.value = '' - generatedProtocolCode.value = '' - } - delete revealedCodes[item.protocol] - } - notify(`${protocolLabel(item.protocol)} ??????????${enabled ? '??' : '??'}`, 'success') + notify(`${protocolLabel(item.protocol)} ${enabled ? '已开启' : '已关闭'}`, 'success') + await loadProtocolCredentials() } catch (error) { - item.enabled = previous - notify(error?.message || '??????') + notify(error?.message || '更新协议开启状态失败') } finally { protocolLoading.value = false } } - const generateProtocolCode = async item => { - if (!item.enabled) { - notify(`???? ${protocolLabel(item.protocol)} ??`) + const copyProtocolCode = async (codeToCopy) => { + const targetCode = typeof codeToCopy === 'string' && codeToCopy + ? codeToCopy + : generatedProtocolCode.value + + if (!targetCode) { + notify('未找到可复制的授权码') + return + } + + try { + if (navigator?.clipboard?.writeText) { + await navigator.clipboard.writeText(targetCode) + } else { + const input = document.createElement('input') + input.value = targetCode + document.body.appendChild(input) + input.select() + document.execCommand('copy') + document.body.removeChild(input) + } + notify('协议授权码已复制到剪贴板', 'success') + } catch (error) { + notify('复制失败,请手动选择复制') + } + } + + const generateProtocolCode = async (item) => { + const actionText = item.configured ? '重新生成' : '生成' + try { + await ElMessageBox.confirm( + `确定为 ${protocolLabel(item.protocol)} ${actionText}专属授权码吗?旧授权码将立即失效并更新。`, + '安全提示', + { + confirmButtonText: '确定生成', + cancelButtonText: '取消', + type: 'warning', + } + ) + } catch { return } @@ -90,114 +120,114 @@ export function usePage() { protocol: item.protocol, }) if (response.errorNo !== 0) { - throw new Error(response.errorMsg || '???????') + throw new Error(response.errorMsg || '生成协议授权码失败') } - const newCode = response.data?.code || '' + + const newCode = response?.data?.code || '' generatedProtocol.value = item.protocol generatedProtocolCode.value = newCode - item.configured = true - // ?????????? + + // 自动同步到已解密展示状态,无需再次密码验证 revealedCodes[item.protocol] = newCode codesRevealed.value = true - notify(`${protocolLabel(item.protocol)} ???????????`, 'success') + + notify(`${protocolLabel(item.protocol)} 授权码已生成并持久保存`, 'success') + await loadProtocolCredentials() } catch (error) { - notify(error?.message || '???????') + notify(error?.message || '生成协议授权码失败') } finally { protocolLoading.value = false } } - const copyProtocolCode = async (code) => { - const targetCode = typeof code === 'string' && code ? code : generatedProtocolCode.value - if (!targetCode) return - try { - await navigator.clipboard.writeText(targetCode) - notify('??????????', 'success') - } catch { - notify('?????????????') - } - } - - const revokeProtocolCode = async item => { + const revokeProtocolCode = async (item) => { try { await ElMessageBox.confirm( - `??? ${protocolLabel(item.protocol)} ??????????????`, - '???????', - { type: 'warning' }, + `确定撤销 ${protocolLabel(item.protocol)} 的授权码吗?撤销后客户端将无法使用该授权码登录。`, + '安全提示', + { + confirmButtonText: '确定撤销', + cancelButtonText: '取消', + type: 'warning', + } ) - protocolLoading.value = true + } catch { + return + } + + protocolLoading.value = true + try { const response = await http.post('/api/v1/security/credentials/protocols/revoke', { protocol: item.protocol, }) if (response.errorNo !== 0) { - throw new Error(response.errorMsg || '???????') + throw new Error(response.errorMsg || '撤销协议授权码失败') } - item.configured = false + delete revealedCodes[item.protocol] if (generatedProtocol.value === item.protocol) { generatedProtocol.value = '' generatedProtocolCode.value = '' } - delete revealedCodes[item.protocol] - notify(`${protocolLabel(item.protocol)} ??????`, 'success') + notify(`${protocolLabel(item.protocol)} 授权码已撤销`, 'success') + await loadProtocolCredentials() } catch (error) { - if (error !== 'cancel' && error !== 'close') { - notify(error?.message || '???????') - } + notify(error?.message || '撤销协议授权码失败') } finally { protocolLoading.value = false } } - // ???????? + // 密码验证弹窗控制 const openVerifyDialog = () => { verifyPassword.value = '' verifyDialogVisible.value = true } - // ???????? const closeVerifyDialog = () => { - verifyPassword.value = '' verifyDialogVisible.value = false + verifyPassword.value = '' } - // ???????????????? + // 提交登录密码验证并解密回显 const submitVerifyAndReveal = async () => { - if (!verifyPassword.value.trim()) { - notify('???????????????') + const password = verifyPassword.value.trim() + if (!password) { + notify('请输入当前账号登录密码') return } verifyLoading.value = true try { const response = await http.post('/api/v1/security/credentials/protocols/reveal', { - password: verifyPassword.value, + password, }) if (response.errorNo !== 0) { - throw new Error(response.errorMsg || '????????') + throw new Error(response.errorMsg || '密码验证失败,无法查看授权码') } - const data = response.data || {} + + const codes = response.data?.codes || {} Object.keys(revealedCodes).forEach(k => delete revealedCodes[k]) - Object.assign(revealedCodes, data) + Object.assign(revealedCodes, codes) codesRevealed.value = true closeVerifyDialog() - notify('???????????????', 'success') + notify('安全验证通过,授权码已解密显示', 'success') } catch (error) { - notify(error?.message || '????????') + notify(error?.message || '验证失败,请检查密码是否正确') } finally { verifyLoading.value = false } } - // ??????????? + // 安全隐藏授权码 const hideRevealedCodes = () => { - codesRevealed.value = false Object.keys(revealedCodes).forEach(k => delete revealedCodes[k]) - generatedProtocolCode.value = '' - generatedProtocol.value = '' - notify('????????', 'success') + codesRevealed.value = false + notify('已恢复安全隐藏保护', 'info') } - onMounted(loadProtocolCredentials) + onMounted(() => { + loadProtocolCredentials() + }) return { protocolLoading, @@ -219,4 +249,4 @@ export function usePage() { 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 60e0e3c..0dddc4f 100644 --- a/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.vue +++ b/frontend/src/views/components/ThirdPartyAuthorization/ThirdPartyAuthorization.vue @@ -3,9 +3,9 @@ - ??????? + 邮件客户端协议 - ?? IMAP?POP3?SMTP ???????????????????????????? + 为 IMAP、POP3、SMTP 配置专属授权码,平时安全隐藏,验证密码后可查看或复制。 @@ -16,7 +16,7 @@ size="small" @click="openVerifyDialog" > - ????????? + 安全验证查看授权码 - ??????? + 安全隐藏授权码 {{ protocolLabel(item.protocol) }} - {{ item.enabled ? '???' : '???' }} + {{ item.enabled ? '已开启' : '已关闭' }} - {{ item.configured ? '??????' : '??????' }} + {{ item.configured ? '已配置授权码' : '未生成授权码' }} - ??????????????????????? + 登录名:完整邮箱地址;密码:本协议专属授权码。 - ?????? + 当前授权码: {{ revealedCodes[item.protocol] }} @@ -67,11 +67,11 @@ class="copy-btn" @click="copyProtocolCode(revealedCodes[item.protocol])" > - ?? + 复制 - ???????????????? + •••••••••••••••• - ?????? + 验证查看 - ??????????????????? + 尚未生成专属授权码,请点击右侧生成 @@ -93,9 +93,9 @@ setProtocolEnabled(item, value)" /> - {{ item.configured ? '???????' : '?????' }} + {{ item.configured ? '重新生成授权码' : '生成授权码' }} - ????? + 撤销授权码 - + - ????????????????????????????????? + 查看已持久保存的协议授权码前,请先验证当前账号的登录密码: - + @@ -148,14 +148,14 @@ @@ -192,4 +192,4 @@ const hasConfiguredProtocols = computed(() => ) - + \ No newline at end of file
- ?? IMAP?POP3?SMTP ???????????????????????????? + 为 IMAP、POP3、SMTP 配置专属授权码,平时安全隐藏,验证密码后可查看或复制。
???????????????????????
登录名:完整邮箱地址;密码:本协议专属授权码。
{{ revealedCodes[item.protocol] }}
- ????????????????????????????????? + 查看已持久保存的协议授权码前,请先验证当前账号的登录密码: