Files
ctms/frontend/src/components/LockScreenModal.vue
T

150 lines
3.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<teleport to="body">
<div class="lock-screen" role="dialog" aria-modal="true">
<div class="lock-backdrop" />
<div class="lock-panel">
<h2 class="lock-title">已锁定</h2>
<p class="lock-desc">30 分钟无操作请输入密码解锁</p>
<el-input
ref="passwordInput"
v-model="password"
type="password"
placeholder="请输入登录密码"
show-password
@keyup.enter="onUnlock"
/>
<div class="lock-actions">
<el-button type="primary" :loading="loading" @click="onUnlock">解锁</el-button>
<el-button @click="onLogout">退出登录</el-button>
</div>
<p v-if="errorMessage" class="lock-error">{{ errorMessage }}</p>
</div>
</div>
</teleport>
</template>
<script setup lang="ts">
import { nextTick, onMounted, ref, watch } from "vue";
import { ElMessage } from "element-plus";
import { useAuthStore } from "../store/auth";
import { useSessionStore } from "../store/session";
import { UNLOCK_MAX_ATTEMPTS, unlockWithPassword, forceLogout } from "../session/sessionManager";
const auth = useAuthStore();
const session = useSessionStore();
const password = ref("");
const loading = ref(false);
const errorMessage = ref("");
const passwordInput = ref<{ focus?: () => void } | null>(null);
const focusPassword = () => {
nextTick(() => {
passwordInput.value?.focus?.();
});
};
const onUnlock = async () => {
if (loading.value) return;
errorMessage.value = "";
const email = auth.user?.email;
if (!email) {
ElMessage.error("无法获取用户信息,请重新登录");
forceLogout();
return;
}
if (!password.value) {
errorMessage.value = "请输入密码";
focusPassword();
return;
}
loading.value = true;
try {
await unlockWithPassword(email, password.value);
password.value = "";
} catch (err: any) {
session.incrementUnlockAttempt();
const message = err?.response?.data?.detail || err?.response?.data?.message || "密码错误";
errorMessage.value = message;
if (session.unlockAttempts >= UNLOCK_MAX_ATTEMPTS) {
ElMessage.error("错误次数过多,请重新登录");
forceLogout();
}
} finally {
loading.value = false;
focusPassword();
}
};
const onLogout = () => {
forceLogout();
};
onMounted(() => {
focusPassword();
});
watch(
() => session.locked,
(value) => {
if (value) {
focusPassword();
}
}
);
</script>
<style scoped>
.lock-screen {
position: fixed;
inset: 0;
z-index: 2000;
display: grid;
place-items: center;
}
.lock-backdrop {
position: absolute;
inset: 0;
background: rgba(7, 10, 20, 0.35);
backdrop-filter: blur(10px);
}
.lock-panel {
position: relative;
z-index: 1;
width: min(420px, 92vw);
background: #ffffff;
border-radius: 16px;
padding: 28px;
box-shadow: 0 18px 48px rgba(15, 20, 35, 0.2);
display: flex;
flex-direction: column;
gap: 16px;
}
.lock-title {
margin: 0;
font-size: 22px;
font-weight: 600;
color: #0f172a;
}
.lock-desc {
margin: 0;
color: #64748b;
font-size: 14px;
}
.lock-actions {
display: flex;
gap: 12px;
justify-content: flex-end;
}
.lock-error {
margin: 0;
color: #d8342c;
font-size: 13px;
}
</style>