新增空闲锁屏、修复401等问题,新增文件版本管理、共享库等占位符
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user