fix(frontend): 修复邮件客户端协议授权码组件中的中文文案乱码
Some checks are pending
Security Checks / security (push) Waiting to run
Some checks are pending
Security Checks / security (push) Waiting to run
This commit is contained in:
parent
fe9f7aa1b2
commit
fd5bc240c4
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,9 +3,9 @@
|
||||
<section class="authorization-section">
|
||||
<div class="authorization-header">
|
||||
<div class="header-main">
|
||||
<h3>???????</h3>
|
||||
<h3>邮件客户端协议</h3>
|
||||
<p class="authorization-desc">
|
||||
?? IMAP?POP3?SMTP ????????????????????????????
|
||||
为 IMAP、POP3、SMTP 配置专属授权码,平时安全隐藏,验证密码后可查看或复制。
|
||||
</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
@ -16,7 +16,7 @@
|
||||
size="small"
|
||||
@click="openVerifyDialog"
|
||||
>
|
||||
?????????
|
||||
安全验证查看授权码
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else-if="codesRevealed"
|
||||
@ -25,13 +25,13 @@
|
||||
size="small"
|
||||
@click="hideRevealedCodes"
|
||||
>
|
||||
???????
|
||||
安全隐藏授权码
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
title="???????????????????????????????????????????????????????????????"
|
||||
title="使用完整邮箱地址作为登录名,专属授权码作为密码。授权码平时处于加密保护状态,验证密码后可完整查看与一键复制。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
@ -47,16 +47,16 @@
|
||||
<div class="protocol-title">
|
||||
<strong>{{ protocolLabel(item.protocol) }}</strong>
|
||||
<el-tag size="small" :type="item.enabled ? 'success' : 'info'">
|
||||
{{ item.enabled ? '???' : '???' }}
|
||||
{{ item.enabled ? '已开启' : '已关闭' }}
|
||||
</el-tag>
|
||||
<el-tag size="small" :type="item.configured ? 'success' : 'warning'">
|
||||
{{ item.configured ? '??????' : '??????' }}
|
||||
{{ item.configured ? '已配置授权码' : '未生成授权码' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<p class="protocol-guide">???????????????????????</p>
|
||||
<p class="protocol-guide">登录名:完整邮箱地址;密码:本协议专属授权码。</p>
|
||||
|
||||
<div class="protocol-credential-slot">
|
||||
<span class="slot-label">??????</span>
|
||||
<span class="slot-label">当前授权码:</span>
|
||||
<template v-if="item.configured">
|
||||
<template v-if="codesRevealed && revealedCodes[item.protocol]">
|
||||
<code class="revealed-code">{{ revealedCodes[item.protocol] }}</code>
|
||||
@ -67,11 +67,11 @@
|
||||
class="copy-btn"
|
||||
@click="copyProtocolCode(revealedCodes[item.protocol])"
|
||||
>
|
||||
??
|
||||
复制
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="masked-code">????????????????</span>
|
||||
<span class="masked-code">••••••••••••••••</span>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@ -79,12 +79,12 @@
|
||||
class="reveal-btn"
|
||||
@click="openVerifyDialog"
|
||||
>
|
||||
??????
|
||||
验证查看
|
||||
</el-button>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="unconfigured-tip">???????????????????</span>
|
||||
<span class="unconfigured-tip">尚未生成专属授权码,请点击右侧生成</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@ -93,9 +93,9 @@
|
||||
<el-switch
|
||||
:model-value="item.enabled"
|
||||
:loading="protocolLoading"
|
||||
active-text="??"
|
||||
inactive-text="??"
|
||||
:aria-label="`${protocolLabel(item.protocol)} ?????????`"
|
||||
active-text="开启"
|
||||
inactive-text="关闭"
|
||||
:aria-label="`${protocolLabel(item.protocol)} 当前账号客户端访问`"
|
||||
@change="value => setProtocolEnabled(item, value)"
|
||||
/>
|
||||
<el-button
|
||||
@ -104,7 +104,7 @@
|
||||
:disabled="!item.enabled"
|
||||
@click="generateProtocolCode(item)"
|
||||
>
|
||||
{{ item.configured ? '???????' : '?????' }}
|
||||
{{ item.configured ? '重新生成授权码' : '生成授权码' }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="item.configured"
|
||||
@ -113,17 +113,17 @@
|
||||
size="small"
|
||||
@click="revokeProtocolCode(item)"
|
||||
>
|
||||
?????
|
||||
撤销授权码
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ???????? -->
|
||||
<!-- 密码验证弹窗 -->
|
||||
<el-dialog
|
||||
v-model="verifyDialogVisible"
|
||||
title="??????"
|
||||
title="安全身份验证"
|
||||
width="min(440px, calc(100vw - 32px))"
|
||||
:close-on-click-modal="false"
|
||||
class="verify-reveal-dialog"
|
||||
@ -131,15 +131,15 @@
|
||||
>
|
||||
<div class="verify-dialog-body">
|
||||
<p class="verify-dialog-tip">
|
||||
?????????????????????????????????
|
||||
查看已持久保存的协议授权码前,请先验证当前账号的登录密码:
|
||||
</p>
|
||||
<el-form @submit.prevent="submitVerifyAndReveal">
|
||||
<el-form-item label="??????">
|
||||
<el-form-item label="登录密码">
|
||||
<el-input
|
||||
v-model="verifyPassword"
|
||||
type="password"
|
||||
show-password
|
||||
placeholder="???????????"
|
||||
placeholder="请输入当前账号登录密码"
|
||||
autocomplete="current-password"
|
||||
@keyup.enter="submitVerifyAndReveal"
|
||||
/>
|
||||
@ -148,14 +148,14 @@
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="verify-dialog-footer">
|
||||
<el-button @click="closeVerifyDialog">??</el-button>
|
||||
<el-button @click="closeVerifyDialog">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="verifyLoading"
|
||||
:disabled="!verifyPassword.trim()"
|
||||
@click="submitVerifyAndReveal"
|
||||
>
|
||||
???????
|
||||
验证并查看授权码
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@ -192,4 +192,4 @@ const hasConfiguredProtocols = computed(() =>
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped src="./ThirdPartyAuthorization.css"></style>
|
||||
<style scoped src="./ThirdPartyAuthorization.css"></style>
|
||||
Loading…
Reference in New Issue
Block a user