优化系统发信管理弹窗
Some checks are pending
Security Checks / security (push) Waiting to run

将账号配置改为管理弹窗,增加管理员查看授权码入口,并在账号列表下方安全展示授权码。
This commit is contained in:
smail 2026-09-27 16:20:45 +08:00
parent 0eb3ff1a81
commit bf89198414
3 changed files with 166 additions and 6 deletions

View File

@ -335,6 +335,84 @@
margin-top: 0;
}
.notification-row-actions {
display: inline-flex;
align-items: center;
gap: 4px;
white-space: nowrap;
}
.notification-row-actions :deep(.el-button + .el-button) {
margin-left: 0;
}
.notification-manager-secret {
flex: 1 0 100%;
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 8px 14px;
align-items: center;
padding: 10px 12px;
border: 1px solid var(--el-color-success-light-5);
border-radius: 10px;
background: var(--el-color-success-light-9);
}
.notification-manager-secret-heading {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px 14px;
min-width: 0;
font-size: 13px;
font-weight: 600;
}
.notification-manager-secret .notification-credential-code {
grid-column: 1 / -1;
min-width: 0;
padding: 8px 10px;
border: 1px solid var(--el-border-color-light);
border-radius: 6px;
background: var(--el-bg-color);
}
.notification-manager-secret .notification-credential-actions {
grid-column: 1 / -1;
}
.notification-heading-actions {
display: inline-flex;
align-items: center;
gap: 8px;
}
.notification-detail-modal-active {
position: fixed;
z-index: 2000;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
width: auto !important;
min-width: 0;
max-height: 100vh;
padding: 24px !important;
border: 0;
border-radius: 0;
background: rgb(15 23 42 / 42%) !important;
}
.notification-detail-modal-active > .notification-detail-dialog {
width: min(1080px, 100%);
max-height: calc(100vh - 48px);
overflow: auto;
padding: 18px;
border: 1px solid var(--el-border-color-light);
border-radius: 16px;
box-shadow: 0 20px 60px rgb(15 23 42 / 22%);
}
.notification-settings:has(> .notification-manager-panel) > .notification-detail-panel {
display: flex;
flex-direction: column;
@ -508,6 +586,10 @@
.notification-settings:has(> .notification-manager-panel) > .notification-manager-panel .el-pagination {
margin-left: auto;
}
.notification-row-actions {
flex-wrap: wrap;
}
}
@media (max-width: 820px) {
@ -520,6 +602,16 @@
padding: 12px;
}
.notification-detail-modal-active {
align-items: flex-start;
padding: 12px !important;
}
.notification-detail-modal-active > .notification-detail-dialog {
max-height: calc(100vh - 24px);
padding: 12px;
}
.notification-settings:has(> .notification-manager-panel) > .notification-detail-panel .notification-columns {
grid-template-columns: 1fr;
}
@ -559,4 +651,14 @@
padding-inline: 4px;
font-size: 12px;
}
.notification-row-actions {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.notification-manager-secret {
grid-template-columns: 1fr;
}
}

View File

@ -22,6 +22,7 @@ export function usePage(props = {}) {
const seconds = ref(0)
const verifyVisible = ref(false)
const verifyPassword = ref('')
const manageVisible = ref(false)
let verifyTarget = null
let secretSequence = 0
let secretTimer = null
@ -83,6 +84,29 @@ export function usePage(props = {}) {
verifyTarget = status.value
verifyVisible.value = true
}
const openManage = async row => {
if (loading.value || submitting.value || !(await beforeLeave())) return
clearSecret()
status.value = null
selectedID.value = row.user_id
await loadStatus()
if (!disposed) manageVisible.value = true
}
const closeManage = async () => {
if (!(await beforeLeave())) return false
manageVisible.value = false
clearSecret()
return true
}
const revealAccount = async row => {
if (loading.value || submitting.value || !row.configured || !(await beforeLeave())) return
clearSecret()
status.value = null
selectedID.value = row.user_id
await loadStatus()
if (disposed || !status.value || status.value.user_id !== row.user_id) return
if (canReveal.value) openVerify()
}
const verifyAndReveal = async () => {
if (!verifyTarget || !verifyPassword.value || submitting.value || !canReveal.value) return
const target = verifyTarget
@ -229,6 +253,7 @@ export function usePage(props = {}) {
accountsSequence += 1
clearSecret()
clearVerification()
manageVisible.value = false
document.removeEventListener('visibilitychange', hideOnBackground)
})
@ -236,6 +261,7 @@ export function usePage(props = {}) {
manager, loading, submitting, error, status, accounts, page, total, form,
secret, secretAccount, secretVisible, seconds, canManage, stateText, dirty,
canReveal, verifyVisible, verifyPassword, openVerify, verifyAndReveal, clearVerification,
loadAccounts, selectAccount, refresh, save, rotate, clearSecret, copySecret, beforeLeave
loadAccounts, selectAccount: openManage, openManage, closeManage, revealAccount, refresh, save, rotate, clearSecret, copySecret, beforeLeave,
manageVisible
}
}

View File

@ -29,9 +29,17 @@
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="90">
<el-table-column label="操作" width="170">
<template #default="{ row }">
<el-button link type="primary" :disabled="submitting || loading" @click="selectAccount(row)">管理</el-button>
<div class="notification-row-actions">
<el-button link type="primary" :disabled="submitting || loading" @click="openManage(row)">管理</el-button>
<el-button
link
type="primary"
:disabled="submitting || loading || !row.configured || row.disabled"
@click="revealAccount(row)"
>查看授权码</el-button>
</div>
</template>
</el-table-column>
</el-table>
@ -43,14 +51,36 @@
:disabled="submitting"
@current-change="loadAccounts"
/>
<div v-if="manager && !userId && secretVisible && secretAccount" class="notification-manager-secret" role="status">
<div class="notification-manager-secret-heading">
<span>账号 {{ secretAccount }} 的通知授权码</span>
<span class="notification-countdown">{{ seconds }} 秒后自动隐藏</span>
</div>
<code class="notification-credential-code">{{ secret }}</code>
<div class="notification-credential-actions">
<el-button size="small" type="primary" @click="copySecret">复制授权码</el-button>
<el-button size="small" @click="clearSecret">隐藏</el-button>
</div>
</div>
</div>
<el-alert v-if="error" :title="error" type="error" :closable="false" show-icon />
<div v-loading="loading" class="notification-panel notification-detail-panel" aria-live="polite">
<div
v-if="!manager || userId || manageVisible"
class="notification-detail-modal notification-detail-panel"
:class="{ 'notification-detail-modal-active': manager && !userId }"
role="dialog"
:aria-modal="manager && !userId ? 'true' : undefined"
@click.self="manager && !userId ? closeManage() : null"
>
<div v-loading="loading" class="notification-panel notification-detail-dialog" aria-live="polite">
<template v-if="status">
<div class="notification-heading">
<h3>账号:{{ status.account }}</h3>
<el-tag :type="status.enabled && status.configured ? 'success' : 'info'">{{ stateText }}</el-tag>
<div class="notification-heading-actions">
<el-tag :type="status.enabled && status.configured ? 'success' : 'info'">{{ stateText }}</el-tag>
<el-button v-if="manager && !userId" link @click="closeManage">关闭</el-button>
</div>
</div>
<p v-if="!status.account_available" class="notification-warning">账号已禁用或尚未修改临时密码,不能生成或使用通知凭据。</p>
@ -127,6 +157,7 @@
</div>
</template>
<el-empty v-else-if="!loading && !error" description="尚未读取账号状态" />
</div>
</div>
<el-dialog
@ -174,7 +205,8 @@ const {
manager, loading, submitting, error, status, accounts, page, total, form,
secret, secretAccount, secretVisible, seconds, canManage, stateText, dirty,
canReveal, verifyVisible, verifyPassword, openVerify, verifyAndReveal, clearVerification,
loadAccounts, selectAccount, refresh, save, rotate, clearSecret, copySecret, beforeLeave
loadAccounts, openManage, closeManage, revealAccount, manageVisible,
refresh, save, rotate, clearSecret, copySecret, beforeLeave
} = usePage(props)
defineExpose({ beforeLeave })