feat(security): 优化客户端专属授权码页面空间利用与三板块饱满布局
Some checks are pending
Security Checks / security (push) Waiting to run

This commit is contained in:
smail 2026-09-13 23:33:48 +08:00
parent cc34b64648
commit 6087f028a2
3 changed files with 459 additions and 272 deletions

View File

@ -2,61 +2,28 @@
padding: 0; padding: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 16px;
} }
.authorization-section { .authorization-section {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 16px;
} }
/* 顶部轻量工具条:纯净标题栏 */ /* 通用板块卡片容器 */
.compact-header { .panel-card {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 14px;
background: var(--pm-bg-secondary);
border: 1px solid var(--pm-border-color);
border-radius: var(--pm-radius-md);
}
.header-left {
display: flex;
align-items: baseline;
gap: 8px;
min-width: 0;
}
.header-title {
font-size: 15px;
font-weight: 700;
color: var(--pm-text-primary);
white-space: nowrap;
}
.header-subtitle {
font-size: 12px;
color: var(--pm-text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* 客户端选择与管理主卡片 */
.client-main-card {
padding: 14px 16px;
background: var(--pm-bg-secondary); background: var(--pm-bg-secondary);
border: 1px solid var(--pm-border-color); border: 1px solid var(--pm-border-color);
border-radius: var(--pm-radius-md); border-radius: var(--pm-radius-md);
padding: 16px 18px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 14px;
} }
.client-picker-row { /* 顶部标题栏 */
.card-header-bar {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
@ -64,22 +31,68 @@
flex-wrap: wrap; flex-wrap: wrap;
} }
.picker-group { .header-left {
display: flex;
align-items: baseline;
gap: 10px;
min-width: 0;
flex-wrap: wrap;
}
.main-title {
font-size: 16px;
font-weight: 700;
color: var(--pm-text-primary);
white-space: nowrap;
}
.sub-title {
font-size: 13px;
color: var(--pm-text-secondary);
line-height: 1.4;
}
.header-actions {
display: flex;
align-items: center;
gap: 10px;
}
.btn-primary-action {
font-weight: 600;
}
/* 卡片内容区域 */
.card-content-area {
display: flex;
flex-direction: column;
gap: 12px;
}
.client-op-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
flex-wrap: wrap;
}
.op-left-group {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
flex-wrap: wrap; flex-wrap: wrap;
} }
.picker-label { .item-label {
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
color: var(--pm-text-primary); color: var(--pm-text-primary);
white-space: nowrap; white-space: nowrap;
} }
.client-select { .client-select-input {
width: 220px; width: 240px;
} }
.client-option-item { .client-option-item {
@ -90,40 +103,40 @@
width: 100%; width: 100%;
} }
.btn-create-client { .op-right-group {
font-weight: 600;
}
.picker-actions {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
} }
/* 授权码详情框 */ /* 授权码展示横条 */
.client-detail-box { .code-banner {
padding: 12px 14px; padding: 14px 16px;
background: var(--pm-bg-primary); background: var(--pm-bg-primary);
border: 1px dashed var(--pm-border-color); border: 1px dashed var(--pm-border-color);
border-radius: var(--pm-radius-sm); border-radius: var(--pm-radius-sm);
}
.code-display-row {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 12px;
flex-wrap: wrap; flex-wrap: wrap;
gap: 10px;
} }
.code-label { .banner-left {
font-size: 13px; display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.banner-label {
font-size: 14px;
font-weight: 600; font-weight: 600;
color: var(--pm-text-secondary); color: var(--pm-text-secondary);
white-space: nowrap; white-space: nowrap;
} }
.code-value-area { .banner-code-box {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
@ -133,7 +146,7 @@
.masked-code { .masked-code {
letter-spacing: 2px; letter-spacing: 2px;
font-family: monospace; font-family: monospace;
font-size: 14px; font-size: 15px;
color: var(--pm-text-secondary); color: var(--pm-text-secondary);
font-weight: 600; font-weight: 600;
} }
@ -149,8 +162,8 @@
letter-spacing: 1px; letter-spacing: 1px;
} }
.action-btn { .btn-copy-code {
font-size: 13px; font-weight: 600;
} }
.countdown-tag { .countdown-tag {
@ -161,22 +174,27 @@
font-weight: 600; font-weight: 600;
} }
.text-tip { .text-hint {
font-size: 12px; font-size: 13px;
color: var(--pm-text-placeholder, #909399); color: var(--pm-text-placeholder, #909399);
} }
.client-meta { .banner-right {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 14px;
font-size: 12px; font-size: 12px;
color: var(--pm-text-secondary); color: var(--pm-text-secondary);
margin-left: auto; margin-left: auto;
} }
.empty-client-box { .meta-tag {
padding: 18px 12px; white-space: nowrap;
}
/* 空状态占位 */
.empty-client-placeholder {
padding: 24px 16px;
text-align: center; text-align: center;
background: var(--pm-bg-primary); background: var(--pm-bg-primary);
border: 1px dashed var(--pm-border-color); border: 1px dashed var(--pm-border-color);
@ -184,89 +202,150 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 10px; gap: 12px;
} }
.empty-tip { .placeholder-text {
font-size: 13px; font-size: 14px;
color: var(--pm-text-placeholder, #909399); color: var(--pm-text-placeholder, #909399);
} }
.empty-action { /* 通用协议指引网格 */
margin-top: 4px; .proto-grid {
}
/* 紧凑指引网格 */
.compact-guide-section {
display: flex;
flex-direction: column;
gap: 8px;
}
.guide-title {
display: flex;
align-items: baseline;
gap: 8px;
font-size: 13px;
font-weight: 600;
color: var(--pm-text-primary);
}
.guide-sub {
font-size: 12px;
font-weight: normal;
color: var(--pm-text-secondary);
}
.guide-grid {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
gap: 10px; gap: 14px;
} }
.guide-card { .proto-card {
padding: 10px 12px; padding: 14px 16px;
background: var(--pm-bg-secondary); background: var(--pm-bg-primary);
border: 1px solid var(--pm-border-color); border: 1px solid var(--pm-border-color);
border-radius: var(--pm-radius-md); border-radius: var(--pm-radius-sm);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 6px; gap: 10px;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
} }
.card-head { .proto-card:hover {
border-color: var(--pm-primary-color);
}
.proto-header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-bottom: 2px; gap: 8px;
padding-bottom: 8px;
border-bottom: 1px solid var(--pm-border-color);
} }
.card-proto { .proto-name-badge {
display: flex;
align-items: center;
gap: 8px;
}
.proto-name {
font-size: 14px;
font-weight: 700;
color: var(--pm-text-primary);
}
.proto-fields {
display: flex;
flex-direction: column;
gap: 8px;
}
.field-item {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 13px;
gap: 8px;
}
.field-k {
color: var(--pm-text-secondary);
white-space: nowrap;
}
.field-v {
color: var(--pm-text-primary);
text-align: right;
word-break: break-all;
}
.font-mono {
font-family: monospace;
}
.field-v.highlight {
color: var(--pm-primary-color);
font-weight: 700;
}
/* 模块 3:配置提示与常见问题卡片 */
.instructions-card {
background: var(--pm-bg-secondary);
}
.instructions-content {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 14px;
}
.instruct-item {
display: flex;
gap: 12px;
padding: 12px 14px;
background: var(--pm-bg-primary);
border: 1px solid var(--pm-border-color);
border-radius: var(--pm-radius-sm);
}
.instruct-num {
flex-shrink: 0;
width: 24px;
height: 24px;
border-radius: 50%;
background: color-mix(in srgb, var(--pm-primary-color) 16%, var(--pm-bg-secondary));
color: var(--pm-primary-color);
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 700;
}
.instruct-body {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.instruct-title {
font-size: 13px; font-size: 13px;
font-weight: 700; font-weight: 700;
color: var(--pm-text-primary); color: var(--pm-text-primary);
} }
.card-line { .instruct-desc {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 12px; font-size: 12px;
} line-height: 1.5;
.cl-label {
color: var(--pm-text-secondary); color: var(--pm-text-secondary);
} }
.cl-val { .instruct-desc code {
font-family: monospace; padding: 1px 4px;
color: var(--pm-text-primary); border-radius: 3px;
font-weight: 500; background: color-mix(in srgb, var(--pm-primary-color) 10%, var(--pm-bg-secondary));
}
.cl-val.highlight {
color: var(--pm-primary-color); color: var(--pm-primary-color);
font-weight: 700; font-family: monospace;
} }
/* 弹窗通用紧凑样式 */ /* 弹窗通用紧凑样式 */
@ -283,16 +362,24 @@
gap: 10px; gap: 10px;
} }
@media (max-width: 800px) { @media (max-width: 960px) {
.guide-grid { .proto-grid,
.instructions-content {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.compact-header { .card-header-bar {
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: stretch;
} }
.client-picker-row { .client-op-row {
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: stretch;
} }
.code-banner {
flex-direction: column;
align-items: stretch;
}
.banner-right {
margin-left: 0;
}
} }

View File

@ -106,7 +106,6 @@ export function usePage() {
const list = Array.isArray(response.data) ? response.data : [] const list = Array.isArray(response.data) ? response.data : []
clientList.value = list clientList.value = list
if (list.length > 0) { if (list.length > 0) {
// 如果当前选中的不在列表中,默认选中第一个
if (!selectedClientId.value || !list.some(c => c.id === selectedClientId.value)) { if (!selectedClientId.value || !list.some(c => c.id === selectedClientId.value)) {
selectedClientId.value = list[0].id selectedClientId.value = list[0].id
} }
@ -236,28 +235,33 @@ export function usePage() {
} }
} }
const copyCode = async (codeText) => { const copyText = async (text, label = '内容') => {
if (!codeText) { if (!text) return
notify('未找到可复制的授权码')
return
}
try { try {
if (navigator?.clipboard?.writeText) { if (navigator?.clipboard?.writeText) {
await navigator.clipboard.writeText(codeText) await navigator.clipboard.writeText(text)
} else { } else {
const input = document.createElement('input') const input = document.createElement('input')
input.value = codeText input.value = text
document.body.appendChild(input) document.body.appendChild(input)
input.select() input.select()
document.execCommand('copy') document.execCommand('copy')
document.body.removeChild(input) document.body.removeChild(input)
} }
notify('专属授权码已复制到剪贴板', 'success') notify(`${label}已复制到剪贴板`, 'success')
} catch { } catch {
notify('复制失败,请手动选择复制') notify('复制失败,请手动选择复制')
} }
} }
const copyCode = async (codeText) => {
if (!codeText) {
notify('未找到可复制的授权码')
return
}
await copyText(codeText, '专属授权码')
}
const openVerifyDialog = () => { const openVerifyDialog = () => {
verifyPassword.value = '' verifyPassword.value = ''
verifyDialogVisible.value = true verifyDialogVisible.value = true
@ -330,6 +334,7 @@ export function usePage() {
submitCreateClient, submitCreateClient,
regenerateClientCode, regenerateClientCode,
revokeClientCode, revokeClientCode,
copyText,
copyCode, copyCode,
formatTime, formatTime,
openVerifyDialog, openVerifyDialog,

View File

@ -1,182 +1,276 @@
<template> <template>
<div class="third-party-authorization" v-loading="loading"> <div class="third-party-authorization" v-loading="loading">
<section class="authorization-section"> <section class="authorization-section">
<!-- 顶部轻量工具条:纯净标题与说明 --> <!-- 模块 1:客户端与专属授权码管理卡片 -->
<div class="compact-header"> <div class="panel-card client-control-card">
<div class="header-left"> <div class="card-header-bar">
<span class="header-title">客户端专属授权码</span> <div class="header-left">
<span class="header-subtitle">为手机、Foxmail 等设备创建独立授权码,随时查看或撤销</span> <span class="main-title">客户端专属授权码</span>
</div> <span class="sub-title">为手机、Foxmail、Outlook 等设备创建独立授权码,随时查看或撤销</span>
</div> </div>
<!-- 客户端选择与管理主卡片 -->
<div class="client-main-card">
<div class="client-picker-row">
<div class="picker-group">
<span class="picker-label">选择客户端:</span>
<el-select
v-model="selectedClientId"
placeholder="请选择或添加客户端"
size="default"
class="client-select"
@change="handleClientSelect"
>
<el-option
v-for="item in clientList"
:key="item.id"
:label="item.client_name"
:value="item.id"
>
<div class="client-option-item">
<span>{{ item.client_name }}</span>
<el-tag size="small" type="success">已就绪</el-tag>
</div>
</el-option>
</el-select>
<div class="header-actions">
<el-button <el-button
type="primary" type="primary"
size="default" size="default"
class="btn-create-client" class="btn-primary-action"
@click="openCreateDialog" @click="openCreateDialog"
> >
+ 新增客户端 + 新增客户端
</el-button> </el-button>
</div> </div>
<div class="picker-actions" v-if="currentClient">
<el-button
type="warning"
plain
size="default"
@click="regenerateClientCode(currentClient)"
>
重新生成
</el-button>
<el-button
type="danger"
plain
size="default"
@click="revokeClientCode(currentClient)"
>
撤销删除
</el-button>
</div>
</div> </div>
<!-- 当前选中客户端的授权码展示条 --> <div class="card-content-area">
<div class="client-detail-box" v-if="currentClient"> <div class="client-op-row">
<div class="code-display-row"> <div class="op-left-group">
<span class="code-label">当前专属授权码:</span> <span class="item-label">选择客户端:</span>
<div class="code-value-area"> <el-select
<template v-if="codesRevealed && revealedCodes[currentClient.id]"> v-model="selectedClientId"
<code class="revealed-code">{{ revealedCodes[currentClient.id] }}</code> placeholder="请选择客户端设备"
<el-button size="default"
type="primary" class="client-select-input"
size="default" @change="handleClientSelect"
class="action-btn" >
@click="copyCode(revealedCodes[currentClient.id])" <el-option
v-for="item in clientList"
:key="item.id"
:label="item.client_name"
:value="item.id"
> >
一键复制 <div class="client-option-item">
</el-button> <span class="opt-name">{{ item.client_name }}</span>
<el-tag <el-tag size="small" type="success" effect="light">已就绪</el-tag>
size="default" </div>
type="warning" </el-option>
effect="plain" </el-select>
class="countdown-tag"
>
{{ revealCountdown }}s 后自动隐藏
</el-tag>
</template>
<template v-else-if="codesRevealed && !revealedCodes[currentClient.id]">
<span class="text-tip">未保留加密密文,可点击重新生成</span>
</template>
<template v-else>
<span class="masked-code">••••••••••••••••••••</span>
<el-button
type="primary"
plain
size="default"
class="btn-verify-reveal"
@click="openVerifyDialog"
>
安全验证查看明文
</el-button>
</template>
</div> </div>
<div class="client-meta">
<span class="meta-item">创建时间:{{ formatTime(currentClient.created_at) }}</span> <div class="op-right-group" v-if="currentClient">
<span class="meta-item" v-if="currentClient.last_used_at"> <el-button
type="warning"
plain
size="default"
@click="regenerateClientCode(currentClient)"
>
重新生成
</el-button>
<el-button
type="danger"
plain
size="default"
@click="revokeClientCode(currentClient)"
>
撤销删除
</el-button>
</div>
</div>
<!-- 授权码展示横条 -->
<div class="code-banner" v-if="currentClient">
<div class="banner-left">
<span class="banner-label">当前专属授权码:</span>
<div class="banner-code-box">
<template v-if="codesRevealed && revealedCodes[currentClient.id]">
<code class="revealed-code">{{ revealedCodes[currentClient.id] }}</code>
<el-button
type="primary"
size="default"
class="btn-copy-code"
@click="copyCode(revealedCodes[currentClient.id])"
>
一键复制
</el-button>
<el-tag
size="default"
type="warning"
effect="plain"
class="countdown-tag"
>
{{ revealCountdown }}s 后自动隐藏
</el-tag>
</template>
<template v-else-if="codesRevealed && !revealedCodes[currentClient.id]">
<span class="text-hint">未保留加密密文,可点击右上方重新生成</span>
</template>
<template v-else>
<span class="masked-code">••••••••••••••••••••</span>
<el-button
type="primary"
plain
size="default"
class="btn-verify-reveal"
@click="openVerifyDialog"
>
安全验证查看明文
</el-button>
</template>
</div>
</div>
<div class="banner-right">
<span class="meta-tag">创建时间:{{ formatTime(currentClient.created_at) }}</span>
<span class="meta-tag" v-if="currentClient.last_used_at">
最近活跃:{{ formatTime(currentClient.last_used_at) }} 最近活跃:{{ formatTime(currentClient.last_used_at) }}
</span> </span>
</div> </div>
</div> </div>
</div>
<div class="empty-client-box" v-else> <!-- 空状态引导 -->
<span class="empty-tip">暂无可用客户端,请点击上方「+ 新增客户端」为您的设备配置专属授权码</span> <div class="empty-client-placeholder" v-else>
<div class="empty-action"> <div class="placeholder-text">暂无可用客户端,请点击右上方「+ 新增客户端」为您的设备配置专属授权码</div>
<el-button <el-button
type="primary" type="primary"
size="default" size="default"
@click="openCreateDialog" @click="openCreateDialog"
> >
+ 立即新增客户端 立即新增客户端
</el-button> </el-button>
</div> </div>
</div> </div>
</div> </div>
<!-- 客户端配置指引紧凑网格(一屏显示完整,无滚动条) --> <!-- 模块 2:通用客户端配置参数卡片 -->
<div class="compact-guide-section"> <div class="panel-card proto-guide-card">
<div class="guide-title"> <div class="card-header-bar">
<span>通用客户端配置参数</span> <div class="header-left">
<span class="guide-sub">支持用户名直接登录或完整邮箱登录,密码统一使用上方专属授权码</span> <span class="main-title">通用客户端配置参数</span>
<span class="sub-title">支持用户名(如 xuans)或完整邮箱登录,密码统一使用上方专属授权码</span>
</div>
</div> </div>
<div class="guide-grid"> <div class="proto-grid">
<div class="guide-card"> <!-- IMAP 收信 -->
<div class="card-head"> <div class="proto-card">
<span class="card-proto">IMAP 收信服务</span> <div class="proto-header">
<el-tag size="small" type="primary">SSL/TLS</el-tag> <div class="proto-name-badge">
<span class="proto-name">IMAP 收信服务</span>
<el-tag size="small" type="primary" effect="light">SSL / TLS</el-tag>
</div>
<el-button
type="primary"
link
size="small"
@click="copyText(`imap.${currentDomain || 'solit.cn'}:993`, 'IMAP 参数')"
>
复制
</el-button>
</div> </div>
<div class="card-line">
<span class="cl-label">服务器地址:</span> <div class="proto-fields">
<span class="cl-val">imap.{{ currentDomain || 'solit.cn' }}</span> <div class="field-item">
</div> <span class="field-k">服务器地址:</span>
<div class="card-line"> <span class="field-v font-mono">imap.{{ currentDomain || 'solit.cn' }}</span>
<span class="cl-label">服务端口:</span> </div>
<span class="cl-val highlight">993</span> <div class="field-item">
<span class="field-k">服务端口:</span>
<span class="field-v font-mono highlight">993</span>
</div>
<div class="field-item">
<span class="field-k">加密方式:</span>
<span class="field-v">SSL / TLS 隐式加密</span>
</div>
</div> </div>
</div> </div>
<div class="guide-card"> <!-- POP3 收信 -->
<div class="card-head"> <div class="proto-card">
<span class="card-proto">POP3 收信服务</span> <div class="proto-header">
<el-tag size="small" type="primary">SSL/TLS</el-tag> <div class="proto-name-badge">
<span class="proto-name">POP3 收信服务</span>
<el-tag size="small" type="primary" effect="light">SSL / TLS</el-tag>
</div>
<el-button
type="primary"
link
size="small"
@click="copyText(`pop.${currentDomain || 'solit.cn'}:995`, 'POP3 参数')"
>
复制
</el-button>
</div> </div>
<div class="card-line">
<span class="cl-label">服务器地址:</span> <div class="proto-fields">
<span class="cl-val">pop.{{ currentDomain || 'solit.cn' }}</span> <div class="field-item">
</div> <span class="field-k">服务器地址:</span>
<div class="card-line"> <span class="field-v font-mono">pop.{{ currentDomain || 'solit.cn' }}</span>
<span class="cl-label">服务端口:</span> </div>
<span class="cl-val highlight">995</span> <div class="field-item">
<span class="field-k">服务端口:</span>
<span class="field-v font-mono highlight">995</span>
</div>
<div class="field-item">
<span class="field-k">加密方式:</span>
<span class="field-v">SSL / TLS 隐式加密</span>
</div>
</div> </div>
</div> </div>
<div class="guide-card"> <!-- SMTP 发信 -->
<div class="card-head"> <div class="proto-card">
<span class="card-proto">SMTP 发信服务</span> <div class="proto-header">
<el-tag size="small" type="primary">SSL / STARTTLS</el-tag> <div class="proto-name-badge">
<span class="proto-name">SMTP 发信服务</span>
<el-tag size="small" type="primary" effect="light">SSL / STARTTLS</el-tag>
</div>
<el-button
type="primary"
link
size="small"
@click="copyText(`smtp.${currentDomain || 'solit.cn'}:465`, 'SMTP 参数')"
>
复制
</el-button>
</div> </div>
<div class="card-line">
<span class="cl-label">服务器地址:</span> <div class="proto-fields">
<span class="cl-val">smtp.{{ currentDomain || 'solit.cn' }}</span> <div class="field-item">
<span class="field-k">服务器地址:</span>
<span class="field-v font-mono">smtp.{{ currentDomain || 'solit.cn' }}</span>
</div>
<div class="field-item">
<span class="field-k">服务端口:</span>
<span class="field-v font-mono highlight">465 (SSL) / 587</span>
</div>
<div class="field-item">
<span class="field-k">加密方式:</span>
<span class="field-v">SSL 隐式加密 / STARTTLS</span>
</div>
</div> </div>
<div class="card-line"> </div>
<span class="cl-label">服务端口:</span> </div>
<span class="cl-val highlight">465 (SSL) / 587</span> </div>
<!-- 模块 3:客户端使用与排错说明卡片(充实页面、解决大面积留白) -->
<div class="panel-card instructions-card">
<div class="card-header-bar">
<div class="header-left">
<span class="main-title">配置提示与常见问题</span>
</div>
</div>
<div class="instructions-content">
<div class="instruct-item">
<span class="instruct-num">1</span>
<div class="instruct-body">
<div class="instruct-title">一机一码隔离保护</div>
<div class="instruct-desc">建议手机、办公电脑与备用客户端分别创建专属授权码。若设备遗失或更换,仅需在上方撤销对应客户端,其他设备与主密码完全不受影响。</div>
</div>
</div>
<div class="instruct-item">
<span class="instruct-num">2</span>
<div class="instruct-body">
<div class="instruct-title">登录用户名规范</div>
<div class="instruct-desc">第三方客户端的“账户名称/用户名”一栏可直接填写账号名(如 <code>xuans</code>)或完整邮箱(如 <code>xuans@{{ currentDomain || 'solit.cn' }}</code>),密码填写生成的专属授权码。</div>
</div>
</div>
<div class="instruct-item">
<span class="instruct-num">3</span>
<div class="instruct-body">
<div class="instruct-title">证书信任与安全握手</div>
<div class="instruct-desc">当前邮件系统所有协议均强制启用安全加密连接。如使用第三方邮件软件出现证书提示,请确认服务器域名解析是否正确指向当前邮件服务器。</div>
</div> </div>
</div> </div>
</div> </div>
@ -294,6 +388,7 @@ const {
submitCreateClient, submitCreateClient,
regenerateClientCode, regenerateClientCode,
revokeClientCode, revokeClientCode,
copyText,
copyCode, copyCode,
formatTime, formatTime,
openVerifyDialog, openVerifyDialog,