refactor(account): 账户弹窗与退出确认改用页面通用弹窗外壳
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
||||
import AccountPanel from './AccountPanel.vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
|
||||
import { useLoginStore } from '@/stores/login'
|
||||
|
||||
const loginStore = useLoginStore()
|
||||
const accountPanel = ref<InstanceType<typeof AccountPanel>>()
|
||||
const accountMenuOpen = ref(false)
|
||||
const logoutConfirmOpen = ref(false)
|
||||
const loggingOut = ref(false)
|
||||
const identity = computed(() => {
|
||||
const labels = [
|
||||
@@ -46,6 +46,18 @@ const visibleMenuGroups = computed(() =>
|
||||
.filter((group) => group.items.length),
|
||||
)
|
||||
|
||||
/** 打开二次确认(确认弹窗用页面通用样式,不再走 ElMessageBox 的 Element 默认外观) */
|
||||
function requestLogout() {
|
||||
if (loggingOut.value) return
|
||||
accountMenuOpen.value = false
|
||||
logoutConfirmOpen.value = true
|
||||
}
|
||||
|
||||
function cancelLogout() {
|
||||
if (loggingOut.value) return
|
||||
logoutConfirmOpen.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录:二次确认 → 注销服务端会话 → 清掉本地身份数据 → **整页**回登录页。
|
||||
*
|
||||
@@ -58,21 +70,8 @@ const visibleMenuGroups = computed(() =>
|
||||
* 本地身份数据(token / tokenExpiresAt / userInfo,其中 userInfo 里含 permissions)
|
||||
* 由 `loginStore.logout()` → `clearSession()` → `clearAuthStorage()` 删除。
|
||||
*/
|
||||
async function handleLogout() {
|
||||
async function confirmLogout() {
|
||||
if (loggingOut.value) return
|
||||
accountMenuOpen.value = false
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm('退出后需要重新登录才能继续使用,确定退出当前账号吗?', '退出登录', {
|
||||
confirmButtonText: '退出登录',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
confirmButtonClass: 'logout-confirm-button',
|
||||
})
|
||||
} catch {
|
||||
// 用户点了取消,保持在当前页面
|
||||
return
|
||||
}
|
||||
|
||||
loggingOut.value = true
|
||||
try {
|
||||
@@ -84,6 +83,18 @@ async function handleLogout() {
|
||||
// 用 replace 而不是 assign:退出后按浏览器后退不该再回到已登录页面
|
||||
window.location.replace('/login')
|
||||
}
|
||||
|
||||
/** 确认弹窗挂在 body 上,Esc 关闭要挂 document 才收得到(与 AccountPanel 同一处理) */
|
||||
function handleEscape(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape') cancelLogout()
|
||||
}
|
||||
|
||||
watch(logoutConfirmOpen, (value) => {
|
||||
if (value) document.addEventListener('keydown', handleEscape)
|
||||
else document.removeEventListener('keydown', handleEscape)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => document.removeEventListener('keydown', handleEscape))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -160,18 +171,59 @@ async function handleLogout() {
|
||||
<span>工号 {{ loginStore.user?.employeeNo || '—' }}</span>
|
||||
<span>{{ loginStore.user?.campusName || '—' }} · {{ loginStore.department }}</span>
|
||||
</div>
|
||||
<button type="button" @click="openAccount('profile')">
|
||||
<span aria-hidden="true">👤</span>个人信息
|
||||
</button>
|
||||
<button type="button" @click="openAccount('password')">
|
||||
<span aria-hidden="true">🔑</span>修改密码
|
||||
</button>
|
||||
<button class="account-logout" type="button" :disabled="loggingOut" @click="handleLogout">
|
||||
<span aria-hidden="true">⏻</span>{{ loggingOut ? '退出中…' : '退出登录' }}
|
||||
<button type="button" @click="openAccount('profile')">个人信息</button>
|
||||
<button type="button" @click="openAccount('password')">修改密码</button>
|
||||
<button class="account-logout" type="button" :disabled="loggingOut" @click="requestLogout">
|
||||
退出登录
|
||||
</button>
|
||||
</div>
|
||||
</el-popover>
|
||||
<AccountPanel ref="accountPanel" />
|
||||
|
||||
<!-- 退出二次确认:与账户弹窗同一套页面通用外壳与按钮(不再用 ElMessageBox 默认外观) -->
|
||||
<Teleport to="body">
|
||||
<div v-if="logoutConfirmOpen" class="logout-mask" role="presentation">
|
||||
<section
|
||||
class="logout-card"
|
||||
role="alertdialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="logout-dialog-title"
|
||||
>
|
||||
<header class="logout-header">
|
||||
<strong id="logout-dialog-title">退出登录</strong>
|
||||
<button
|
||||
v-if="!loggingOut"
|
||||
type="button"
|
||||
class="logout-close"
|
||||
aria-label="关闭"
|
||||
@click="cancelLogout"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</header>
|
||||
<div class="logout-body">退出后需要重新登录才能继续使用,确定退出当前账号吗?</div>
|
||||
<footer class="logout-footer">
|
||||
<span class="logout-spacer" />
|
||||
<button
|
||||
type="button"
|
||||
class="action-button action-button--ghost"
|
||||
:disabled="loggingOut"
|
||||
@click="cancelLogout"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="action-button action-button--danger"
|
||||
:disabled="loggingOut"
|
||||
@click="confirmLogout"
|
||||
>
|
||||
{{ loggingOut ? '退出中…' : '退出登录' }}
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
</Teleport>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
@@ -360,7 +412,6 @@ async function handleLogout() {
|
||||
.account-actions button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 9px 12px;
|
||||
background: transparent;
|
||||
@@ -378,4 +429,127 @@ async function handleLogout() {
|
||||
border-top: 1px solid var(--line);
|
||||
color: #d0534f;
|
||||
}
|
||||
|
||||
/* 退出二次确认弹窗:外壳与按钮沿用页面通用弹窗样式(与 AccountPanel 同一套) */
|
||||
.logout-mask {
|
||||
position: fixed;
|
||||
z-index: 50;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
background: rgb(9 40 52 / 45%);
|
||||
}
|
||||
|
||||
.logout-card {
|
||||
display: flex;
|
||||
width: 440px;
|
||||
max-width: 96vw;
|
||||
max-height: calc(100vh - 32px);
|
||||
flex-direction: column;
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
box-shadow: var(--sh-modal);
|
||||
animation: logout-pop 0.18s ease;
|
||||
}
|
||||
|
||||
.logout-header {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
padding: 12px 18px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.logout-header strong {
|
||||
color: var(--ink);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.logout-close {
|
||||
padding: 0;
|
||||
margin-left: auto;
|
||||
color: var(--mut);
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.logout-close:hover {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.logout-body {
|
||||
min-height: 0;
|
||||
padding: 16px 18px;
|
||||
overflow-y: auto;
|
||||
color: var(--ink);
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.logout-footer {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
padding: 10px 18px;
|
||||
background: #f8fbfc;
|
||||
border-top: 1px solid var(--line);
|
||||
border-radius: 0 0 14px 14px;
|
||||
}
|
||||
|
||||
.logout-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
flex-shrink: 0;
|
||||
padding: 8px 16px;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
background: var(--brand);
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.action-button--ghost {
|
||||
color: var(--brand);
|
||||
background: #fff;
|
||||
border: 1px solid var(--brand);
|
||||
}
|
||||
|
||||
.action-button--ghost:hover:not(:disabled) {
|
||||
background: var(--brand-l);
|
||||
}
|
||||
|
||||
.action-button--danger {
|
||||
color: var(--err);
|
||||
background: #fff;
|
||||
border: 1px solid var(--err);
|
||||
}
|
||||
|
||||
.action-button--danger:hover:not(:disabled) {
|
||||
background: var(--err-l);
|
||||
}
|
||||
|
||||
.action-button:disabled {
|
||||
color: #9aabb3;
|
||||
cursor: not-allowed;
|
||||
background: #eef3f5;
|
||||
}
|
||||
|
||||
@keyframes logout-pop {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.96) translateY(8px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user