feat(security): 优化客户端专属授权码页面空间利用与三板块饱满布局
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
cc34b64648
commit
6087f028a2
@ -2,61 +2,28 @@
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.authorization-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* 顶部轻量工具条:纯净标题栏 */
|
||||
.compact-header {
|
||||
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;
|
||||
/* 通用板块卡片容器 */
|
||||
.panel-card {
|
||||
background: var(--pm-bg-secondary);
|
||||
border: 1px solid var(--pm-border-color);
|
||||
border-radius: var(--pm-radius-md);
|
||||
padding: 16px 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.client-picker-row {
|
||||
/* 顶部标题栏 */
|
||||
.card-header-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@ -64,22 +31,68 @@
|
||||
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;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.picker-label {
|
||||
.item-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--pm-text-primary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.client-select {
|
||||
width: 220px;
|
||||
.client-select-input {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.client-option-item {
|
||||
@ -90,40 +103,40 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-create-client {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.picker-actions {
|
||||
.op-right-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* 授权码详情框 */
|
||||
.client-detail-box {
|
||||
padding: 12px 14px;
|
||||
/* 授权码展示横条 */
|
||||
.code-banner {
|
||||
padding: 14px 16px;
|
||||
background: var(--pm-bg-primary);
|
||||
border: 1px dashed var(--pm-border-color);
|
||||
border-radius: var(--pm-radius-sm);
|
||||
}
|
||||
|
||||
.code-display-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.code-label {
|
||||
font-size: 13px;
|
||||
.banner-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.banner-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--pm-text-secondary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.code-value-area {
|
||||
.banner-code-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
@ -133,7 +146,7 @@
|
||||
.masked-code {
|
||||
letter-spacing: 2px;
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
color: var(--pm-text-secondary);
|
||||
font-weight: 600;
|
||||
}
|
||||
@ -149,8 +162,8 @@
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
font-size: 13px;
|
||||
.btn-copy-code {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.countdown-tag {
|
||||
@ -161,22 +174,27 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.text-tip {
|
||||
font-size: 12px;
|
||||
.text-hint {
|
||||
font-size: 13px;
|
||||
color: var(--pm-text-placeholder, #909399);
|
||||
}
|
||||
|
||||
.client-meta {
|
||||
.banner-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
gap: 14px;
|
||||
font-size: 12px;
|
||||
color: var(--pm-text-secondary);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.empty-client-box {
|
||||
padding: 18px 12px;
|
||||
.meta-tag {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 空状态占位 */
|
||||
.empty-client-placeholder {
|
||||
padding: 24px 16px;
|
||||
text-align: center;
|
||||
background: var(--pm-bg-primary);
|
||||
border: 1px dashed var(--pm-border-color);
|
||||
@ -184,89 +202,150 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
font-size: 13px;
|
||||
.placeholder-text {
|
||||
font-size: 14px;
|
||||
color: var(--pm-text-placeholder, #909399);
|
||||
}
|
||||
|
||||
.empty-action {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* 紧凑指引网格 */
|
||||
.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 {
|
||||
/* 通用协议指引网格 */
|
||||
.proto-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.guide-card {
|
||||
padding: 10px 12px;
|
||||
background: var(--pm-bg-secondary);
|
||||
.proto-card {
|
||||
padding: 14px 16px;
|
||||
background: var(--pm-bg-primary);
|
||||
border: 1px solid var(--pm-border-color);
|
||||
border-radius: var(--pm-radius-md);
|
||||
border-radius: var(--pm-radius-sm);
|
||||
display: flex;
|
||||
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;
|
||||
align-items: center;
|
||||
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-weight: 700;
|
||||
color: var(--pm-text-primary);
|
||||
}
|
||||
|
||||
.card-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.instruct-desc {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cl-label {
|
||||
line-height: 1.5;
|
||||
color: var(--pm-text-secondary);
|
||||
}
|
||||
|
||||
.cl-val {
|
||||
font-family: monospace;
|
||||
color: var(--pm-text-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.cl-val.highlight {
|
||||
.instruct-desc code {
|
||||
padding: 1px 4px;
|
||||
border-radius: 3px;
|
||||
background: color-mix(in srgb, var(--pm-primary-color) 10%, var(--pm-bg-secondary));
|
||||
color: var(--pm-primary-color);
|
||||
font-weight: 700;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/* 弹窗通用紧凑样式 */
|
||||
@ -283,16 +362,24 @@
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.guide-grid {
|
||||
@media (max-width: 960px) {
|
||||
.proto-grid,
|
||||
.instructions-content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.compact-header {
|
||||
.card-header-bar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.client-picker-row {
|
||||
.client-op-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.code-banner {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.banner-right {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
@ -106,7 +106,6 @@ export function usePage() {
|
||||
const list = Array.isArray(response.data) ? response.data : []
|
||||
clientList.value = list
|
||||
if (list.length > 0) {
|
||||
// 如果当前选中的不在列表中,默认选中第一个
|
||||
if (!selectedClientId.value || !list.some(c => c.id === selectedClientId.value)) {
|
||||
selectedClientId.value = list[0].id
|
||||
}
|
||||
@ -236,28 +235,33 @@ export function usePage() {
|
||||
}
|
||||
}
|
||||
|
||||
const copyCode = async (codeText) => {
|
||||
if (!codeText) {
|
||||
notify('未找到可复制的授权码')
|
||||
return
|
||||
}
|
||||
const copyText = async (text, label = '内容') => {
|
||||
if (!text) return
|
||||
try {
|
||||
if (navigator?.clipboard?.writeText) {
|
||||
await navigator.clipboard.writeText(codeText)
|
||||
await navigator.clipboard.writeText(text)
|
||||
} else {
|
||||
const input = document.createElement('input')
|
||||
input.value = codeText
|
||||
input.value = text
|
||||
document.body.appendChild(input)
|
||||
input.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(input)
|
||||
}
|
||||
notify('专属授权码已复制到剪贴板', 'success')
|
||||
notify(`${label}已复制到剪贴板`, 'success')
|
||||
} catch {
|
||||
notify('复制失败,请手动选择复制')
|
||||
}
|
||||
}
|
||||
|
||||
const copyCode = async (codeText) => {
|
||||
if (!codeText) {
|
||||
notify('未找到可复制的授权码')
|
||||
return
|
||||
}
|
||||
await copyText(codeText, '专属授权码')
|
||||
}
|
||||
|
||||
const openVerifyDialog = () => {
|
||||
verifyPassword.value = ''
|
||||
verifyDialogVisible.value = true
|
||||
@ -330,6 +334,7 @@ export function usePage() {
|
||||
submitCreateClient,
|
||||
regenerateClientCode,
|
||||
revokeClientCode,
|
||||
copyText,
|
||||
copyCode,
|
||||
formatTime,
|
||||
openVerifyDialog,
|
||||
|
||||
@ -1,182 +1,276 @@
|
||||
<template>
|
||||
<div class="third-party-authorization" v-loading="loading">
|
||||
<section class="authorization-section">
|
||||
<!-- 顶部轻量工具条:纯净标题与说明 -->
|
||||
<div class="compact-header">
|
||||
<div class="header-left">
|
||||
<span class="header-title">客户端专属授权码</span>
|
||||
<span class="header-subtitle">为手机、Foxmail 等设备创建独立授权码,随时查看或撤销</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>
|
||||
<!-- 模块 1:客户端与专属授权码管理卡片 -->
|
||||
<div class="panel-card client-control-card">
|
||||
<div class="card-header-bar">
|
||||
<div class="header-left">
|
||||
<span class="main-title">客户端专属授权码</span>
|
||||
<span class="sub-title">为手机、Foxmail、Outlook 等设备创建独立授权码,随时查看或撤销</span>
|
||||
</div>
|
||||
|
||||
<div class="header-actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="default"
|
||||
class="btn-create-client"
|
||||
class="btn-primary-action"
|
||||
@click="openCreateDialog"
|
||||
>
|
||||
+ 新增客户端
|
||||
</el-button>
|
||||
</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 class="client-detail-box" v-if="currentClient">
|
||||
<div class="code-display-row">
|
||||
<span class="code-label">当前专属授权码:</span>
|
||||
<div class="code-value-area">
|
||||
<template v-if="codesRevealed && revealedCodes[currentClient.id]">
|
||||
<code class="revealed-code">{{ revealedCodes[currentClient.id] }}</code>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="default"
|
||||
class="action-btn"
|
||||
@click="copyCode(revealedCodes[currentClient.id])"
|
||||
<div class="card-content-area">
|
||||
<div class="client-op-row">
|
||||
<div class="op-left-group">
|
||||
<span class="item-label">选择客户端:</span>
|
||||
<el-select
|
||||
v-model="selectedClientId"
|
||||
placeholder="请选择客户端设备"
|
||||
size="default"
|
||||
class="client-select-input"
|
||||
@change="handleClientSelect"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in clientList"
|
||||
:key="item.id"
|
||||
:label="item.client_name"
|
||||
:value="item.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-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 class="client-option-item">
|
||||
<span class="opt-name">{{ item.client_name }}</span>
|
||||
<el-tag size="small" type="success" effect="light">已就绪</el-tag>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="client-meta">
|
||||
<span class="meta-item">创建时间:{{ formatTime(currentClient.created_at) }}</span>
|
||||
<span class="meta-item" v-if="currentClient.last_used_at">
|
||||
|
||||
<div class="op-right-group" 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 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) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="empty-client-box" v-else>
|
||||
<span class="empty-tip">暂无可用客户端,请点击上方「+ 新增客户端」为您的设备配置专属授权码</span>
|
||||
<div class="empty-action">
|
||||
<!-- 空状态引导 -->
|
||||
<div class="empty-client-placeholder" v-else>
|
||||
<div class="placeholder-text">暂无可用客户端,请点击右上方「+ 新增客户端」为您的设备配置专属授权码</div>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="default"
|
||||
@click="openCreateDialog"
|
||||
>
|
||||
+ 立即新增客户端
|
||||
立即新增客户端
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 客户端配置指引紧凑网格(一屏显示完整,无滚动条) -->
|
||||
<div class="compact-guide-section">
|
||||
<div class="guide-title">
|
||||
<span>通用客户端配置参数</span>
|
||||
<span class="guide-sub">支持用户名直接登录或完整邮箱登录,密码统一使用上方专属授权码</span>
|
||||
<!-- 模块 2:通用客户端配置参数卡片 -->
|
||||
<div class="panel-card proto-guide-card">
|
||||
<div class="card-header-bar">
|
||||
<div class="header-left">
|
||||
<span class="main-title">通用客户端配置参数</span>
|
||||
<span class="sub-title">支持用户名(如 xuans)或完整邮箱登录,密码统一使用上方专属授权码</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="guide-grid">
|
||||
<div class="guide-card">
|
||||
<div class="card-head">
|
||||
<span class="card-proto">IMAP 收信服务</span>
|
||||
<el-tag size="small" type="primary">SSL/TLS</el-tag>
|
||||
<div class="proto-grid">
|
||||
<!-- IMAP 收信 -->
|
||||
<div class="proto-card">
|
||||
<div class="proto-header">
|
||||
<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 class="card-line">
|
||||
<span class="cl-label">服务器地址:</span>
|
||||
<span class="cl-val">imap.{{ currentDomain || 'solit.cn' }}</span>
|
||||
</div>
|
||||
<div class="card-line">
|
||||
<span class="cl-label">服务端口:</span>
|
||||
<span class="cl-val highlight">993</span>
|
||||
|
||||
<div class="proto-fields">
|
||||
<div class="field-item">
|
||||
<span class="field-k">服务器地址:</span>
|
||||
<span class="field-v font-mono">imap.{{ currentDomain || 'solit.cn' }}</span>
|
||||
</div>
|
||||
<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 class="guide-card">
|
||||
<div class="card-head">
|
||||
<span class="card-proto">POP3 收信服务</span>
|
||||
<el-tag size="small" type="primary">SSL/TLS</el-tag>
|
||||
<!-- POP3 收信 -->
|
||||
<div class="proto-card">
|
||||
<div class="proto-header">
|
||||
<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 class="card-line">
|
||||
<span class="cl-label">服务器地址:</span>
|
||||
<span class="cl-val">pop.{{ currentDomain || 'solit.cn' }}</span>
|
||||
</div>
|
||||
<div class="card-line">
|
||||
<span class="cl-label">服务端口:</span>
|
||||
<span class="cl-val highlight">995</span>
|
||||
|
||||
<div class="proto-fields">
|
||||
<div class="field-item">
|
||||
<span class="field-k">服务器地址:</span>
|
||||
<span class="field-v font-mono">pop.{{ currentDomain || 'solit.cn' }}</span>
|
||||
</div>
|
||||
<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 class="guide-card">
|
||||
<div class="card-head">
|
||||
<span class="card-proto">SMTP 发信服务</span>
|
||||
<el-tag size="small" type="primary">SSL / STARTTLS</el-tag>
|
||||
<!-- SMTP 发信 -->
|
||||
<div class="proto-card">
|
||||
<div class="proto-header">
|
||||
<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 class="card-line">
|
||||
<span class="cl-label">服务器地址:</span>
|
||||
<span class="cl-val">smtp.{{ currentDomain || 'solit.cn' }}</span>
|
||||
|
||||
<div class="proto-fields">
|
||||
<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 class="card-line">
|
||||
<span class="cl-label">服务端口:</span>
|
||||
<span class="cl-val highlight">465 (SSL) / 587</span>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
@ -294,6 +388,7 @@ const {
|
||||
submitCreateClient,
|
||||
regenerateClientCode,
|
||||
revokeClientCode,
|
||||
copyText,
|
||||
copyCode,
|
||||
formatTime,
|
||||
openVerifyDialog,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user