立项配置页初步优化

This commit is contained in:
Cheng Zhou
2026-02-24 16:53:22 +08:00
parent 0693f09b1d
commit 8f3f717e48
124 changed files with 12519 additions and 2954 deletions
+92 -16
View File
@@ -1,4 +1,3 @@
import { ElMessage } from "element-plus";
import router from "../router";
import { useAuthStore } from "../store/auth";
import { useSessionStore } from "../store/session";
@@ -7,16 +6,19 @@ import { extendToken, unlockSession } from "../api/authClient";
import { parseJwtExp } from "./jwt";
export const IDLE_TIMEOUT_MINUTES = 30;
export const AUTO_LOGOUT_TIMEOUT_HOURS = 2;
export const EXTEND_EARLY_SECONDS = 120;
export const EXTEND_MIN_INTERVAL_SECONDS = 60;
export const UNLOCK_MAX_ATTEMPTS = 5;
export const LOGOUT_REASON_TIMEOUT = "timeout";
const LOGOUT_REASON_STORAGE_KEY = "ctms_logout_reason";
type BroadcastMessage =
| { type: "ACTIVE"; at: number }
| { type: "NETWORK"; at: number }
| { type: "LOCK"; reason: string }
| { type: "LOCK"; reason: string; email?: string; deadlineAt?: number }
| { type: "TOKEN_UPDATED"; token: string }
| { type: "LOGOUT" };
| { type: "LOGOUT"; reason?: string };
let idleTimer: number | null = null;
let extendPromise: Promise<{ token: string | null; authFailed: boolean }> | null = null;
@@ -38,16 +40,19 @@ const handleBroadcast = (message: BroadcastMessage) => {
const session = useSessionStore();
const auth = useAuthStore();
if (message.type === "ACTIVE") {
if (session.locked) return;
session.recordUserActivity(message.at);
scheduleIdleCheck();
}
if (message.type === "NETWORK") {
if (session.locked) return;
session.recordNetworkActivity(message.at);
scheduleIdleCheck();
}
if (message.type === "LOCK") {
const reason = message.reason as "idle" | "extend_failed" | "token_invalid";
session.lock(reason || "idle");
session.lock(reason || "idle", message.email, message.deadlineAt);
scheduleIdleCheck();
}
if (message.type === "TOKEN_UPDATED") {
auth.setToken(message.token);
@@ -55,7 +60,7 @@ const handleBroadcast = (message: BroadcastMessage) => {
session.unlock();
}
if (message.type === "LOGOUT") {
forceLogout();
performLogout(message.reason);
}
};
@@ -64,9 +69,22 @@ const scheduleIdleCheck = () => {
window.clearTimeout(idleTimer);
}
const session = useSessionStore();
const timeoutMs = IDLE_TIMEOUT_MINUTES * 60 * 1000;
const nextCheck =
Math.min(session.lastUserActiveAt, session.lastNetworkActiveAt) + timeoutMs - Date.now();
const now = Date.now();
if (session.locked) {
const remain = (session.lockAutoLogoutDeadlineAt || 0) - now;
if (remain <= 0) {
checkIdle();
return;
}
idleTimer = window.setTimeout(checkIdle, remain);
return;
}
const idleTimeoutMs = IDLE_TIMEOUT_MINUTES * 60 * 1000;
const autoLogoutMs = AUTO_LOGOUT_TIMEOUT_HOURS * 60 * 60 * 1000;
const baseActiveAt = Math.min(session.lastUserActiveAt, session.lastNetworkActiveAt);
const idleRemain = baseActiveAt + idleTimeoutMs - now;
const logoutRemain = baseActiveAt + autoLogoutMs - now;
const nextCheck = Math.min(idleRemain, logoutRemain);
if (nextCheck <= 0) {
checkIdle();
return;
@@ -76,12 +94,28 @@ const scheduleIdleCheck = () => {
const checkIdle = () => {
const session = useSessionStore();
const timeoutMs = IDLE_TIMEOUT_MINUTES * 60 * 1000;
const now = Date.now();
if (now - session.lastUserActiveAt > timeoutMs && now - session.lastNetworkActiveAt > timeoutMs) {
lockSession("idle");
if (session.locked) {
const deadlineAt = session.lockAutoLogoutDeadlineAt || 0;
if (deadlineAt > 0 && Date.now() >= deadlineAt) {
forceLogout(LOGOUT_REASON_TIMEOUT);
return;
}
scheduleIdleCheck();
return;
}
const idleTimeoutMs = IDLE_TIMEOUT_MINUTES * 60 * 1000;
const autoLogoutMs = AUTO_LOGOUT_TIMEOUT_HOURS * 60 * 60 * 1000;
const now = Date.now();
const idleElapsed = now - session.lastUserActiveAt > idleTimeoutMs && now - session.lastNetworkActiveAt > idleTimeoutMs;
const autoLogoutElapsed =
now - session.lastUserActiveAt > autoLogoutMs && now - session.lastNetworkActiveAt > autoLogoutMs;
if (autoLogoutElapsed) {
forceLogout(LOGOUT_REASON_TIMEOUT);
return;
}
if (idleElapsed) {
lockSession("idle");
}
scheduleIdleCheck();
};
@@ -115,6 +149,7 @@ export const initSessionManager = () => {
export const markUserActive = (at: number = Date.now()) => {
const session = useSessionStore();
if (session.locked) return;
session.recordUserActivity(at);
broadcast({ type: "ACTIVE", at });
scheduleIdleCheck();
@@ -122,6 +157,7 @@ export const markUserActive = (at: number = Date.now()) => {
export const markNetworkActive = (at: number = Date.now()) => {
const session = useSessionStore();
if (session.locked) return;
session.recordNetworkActivity(at);
broadcast({ type: "NETWORK", at });
scheduleIdleCheck();
@@ -129,21 +165,53 @@ export const markNetworkActive = (at: number = Date.now()) => {
export const lockSession = (reason: "idle" | "extend_failed" | "token_invalid") => {
const session = useSessionStore();
const auth = useAuthStore();
if (session.locked) return;
session.lock(reason);
broadcast({ type: "LOCK", reason });
const email = auth.user?.email || "";
const autoLogoutMs = AUTO_LOGOUT_TIMEOUT_HOURS * 60 * 60 * 1000;
const deadlineAt = Math.min(session.lastUserActiveAt, session.lastNetworkActiveAt) + autoLogoutMs;
session.lock(reason, email, deadlineAt);
broadcast({ type: "LOCK", reason, email, deadlineAt });
scheduleIdleCheck();
};
export const forceLogout = () => {
const setLogoutReason = (reason?: string) => {
try {
if (!reason) {
sessionStorage.removeItem(LOGOUT_REASON_STORAGE_KEY);
return;
}
sessionStorage.setItem(LOGOUT_REASON_STORAGE_KEY, reason);
} catch {
/* ignore */
}
};
export const consumeLogoutReason = () => {
try {
const reason = sessionStorage.getItem(LOGOUT_REASON_STORAGE_KEY);
sessionStorage.removeItem(LOGOUT_REASON_STORAGE_KEY);
return reason;
} catch {
return null;
}
};
const performLogout = (reason?: string) => {
setLogoutReason(reason);
const auth = useAuthStore();
const session = useSessionStore();
auth.logout();
session.unlock();
clearToken();
broadcast({ type: "LOGOUT" });
router.replace("/login");
};
export const forceLogout = (reason?: string) => {
performLogout(reason);
broadcast({ type: "LOGOUT", reason });
};
const updateToken = (token: string) => {
const auth = useAuthStore();
auth.setToken(token);
@@ -205,8 +273,16 @@ export const startTokenKeepAlive = () => {
export const unlockWithPassword = async (email: string, password: string) => {
const session = useSessionStore();
const auth = useAuthStore();
const { data } = await unlockSession({ email, password });
updateToken(data.accessToken);
session.unlock();
try {
await auth.fetchMe();
} catch {
// token已更新但用户信息拉取失败时,避免界面进入无角色状态
forceLogout();
throw new Error("无法获取用户信息,请重新登录");
}
markUserActive();
};