UNIVPLMDataIntegration/Web/src/utils/idleTimeout.ts
2025-04-18 14:53:53 +08:00

181 lines
4.9 KiB
TypeScript
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.

import { debounce } from 'lodash-es';
import { Local, Session } from '/@/utils/storage';
import { ElMessageBox } from 'element-plus';
import { accessTokenKey, refreshAccessTokenKey } from '/@/utils/axios-utils';
import { i18n } from '/@/i18n';
type IdleTimeoutConfig = {
/** 空闲超时时间默认30分钟 */
timeout?: number;
/** 用于设置最后活动时间的监听事件列表 */
events?: string[];
/** 登出回调函数,在超时发生时执行 */
onTimeout?: () => void;
/** 监听事件防抖间隔(毫秒),默认 200设为0表示不启用防抖 */
debounceInterval?: number;
};
// 动态导入 signalR
let signalR: any;
/** 动态导入 signalR */
async function loadSignalR() {
if (!signalR) {
const module = await import('/@/views/system/onlineUser/signalR');
signalR = module.signalR;
}
return signalR;
}
class IdleTimeoutManager {
private timerId: number | null = null;
private readonly config: Required<IdleTimeoutConfig>;
private readonly debouncedReset: () => void;
/** 检查闲置超时时间间隔 */
private readonly checkTimeoutInterval = 2 * 1000;
constructor(config: IdleTimeoutConfig = {}) {
this.config = {
timeout: 30 * 60,
events: ['mousewheel', 'keydown', 'click'],
onTimeout: this.timeOutExec.bind(this),
debounceInterval: 200,
...config,
};
// 防抖处理
this.debouncedReset = this.config.debounceInterval > 0 ? debounce(this.setLastActivityTime.bind(this), this.config.debounceInterval) : this.setLastActivityTime.bind(this);
this.init();
}
private init() {
// 初始化事件监听
this.config.events.forEach((event) => {
window.addEventListener(event, this.debouncedReset);
});
// 页面可见性监听
document.addEventListener('visibilitychange', this.handleVisibilityChange);
// 设置最后活动时间
this.setLastActivityTime();
// 更新空闲超时时间(会按需启动检查定时器)
this.updateIdleTimeout(this.config.timeout);
}
private handleVisibilityChange = () => {
if (document.visibilityState === 'visible') {
this.setLastActivityTime();
}
};
/** 设置最后活动时间 */
public setLastActivityTime() {
Local.set('lastActivityTime', new Date().getTime());
}
/**
* 更新空闲超时时间
* @param timeout - 新的超时时间(毫秒)
*/
public updateIdleTimeout(timeout: number) {
this.config.timeout = timeout;
// 如果闲置超时时间大于0且检测定时器没启动
if (this.config.timeout > 0 && this.timerId == null) {
this.timerId = window.setInterval(this.checkTimeout.bind(this), this.checkTimeoutInterval);
} else if (this.config.timeout == 0 && this.timerId != null) {
window.clearInterval(this.timerId);
this.timerId = null;
}
}
/** 检查是否超时 */
public checkTimeout() {
const currentTime = new Date().getTime(); // 当前时间
const lastActivityTime = Number(Local.get('lastActivityTime'));
if (lastActivityTime == 0) return;
const accessToken = Local.get(accessTokenKey);
if (!accessToken || accessToken == 'invalid_token') return;
const timeout = this.config.timeout * 1000;
if (currentTime - lastActivityTime > timeout) {
this.destroy();
this.config.onTimeout();
}
}
/** 销毁实例 */
public destroy() {
this.config.events.forEach((event) => {
window.removeEventListener(event, this.debouncedReset);
});
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
if (this.timerId !== null) {
window.clearInterval(this.timerId);
this.timerId = null;
}
}
/** 超时时执行 */
private timeOutExec() {
// 移除 app 元素,即清空主界面
const appEl = document.getElementById('app')!;
appEl?.remove();
// 动态加载 signalR 并调用 stop 方法
loadSignalR().then((signalR) => {
signalR.stop();
});
// TODO: 如果要改成调用 logout 登出接口,需要调整 clearAccessTokens 会 reload 页面的问题
// 清除 token
Local.remove(accessTokenKey);
Local.remove(refreshAccessTokenKey);
// 清除其他
Session.clear();
ElMessageBox.alert(i18n.global.t('message.list.idleTimeoutMessage'), i18n.global.t('message.list.sysMessage'), {
type: 'warning',
draggable: true,
callback: () => {
window.location.reload();
},
});
}
}
/** 初始化函数(在应用启动时调用) */
export function initIdleTimeout(config?: IdleTimeoutConfig) {
// 确保单例模式
if (!window.__IDLE_TIMEOUT__) {
window.__IDLE_TIMEOUT__ = new IdleTimeoutManager(config);
}
return window.__IDLE_TIMEOUT__;
}
/** 销毁函数(在需要时调用) */
export function destroyIdleTimeout() {
if (window.__IDLE_TIMEOUT__) {
window.__IDLE_TIMEOUT__.destroy();
window.__IDLE_TIMEOUT__ = undefined;
}
}
/** 更新空闲超时时间(毫秒) */
export function updateIdleTimeout(timeout: number) {
if (window.__IDLE_TIMEOUT__) {
window.__IDLE_TIMEOUT__.updateIdleTimeout(timeout);
}
}
// 类型扩展
declare global {
interface Window {
__IDLE_TIMEOUT__?: IdleTimeoutManager;
}
}