UNIVPLMDataIntegration/Web/src/utils/idleTimeout.ts

167 lines
4.7 KiB
TypeScript
Raw Normal View History

2025-04-11 10:55:36 +08:00
import { debounce } from 'lodash-es';
import { Local, Session } from '/@/utils/storage';
2025-04-15 18:41:09 +08:00
// import { signalR } from '/@/views/system/onlineUser/signalR';
2025-04-11 10:55:36 +08:00
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;
};
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();
// 关闭连接
2025-04-15 18:41:09 +08:00
// signalR.stop();
2025-04-11 10:55:36 +08:00
// 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'), {
2025-04-14 11:46:31 +08:00
type: 'warning',
2025-04-11 10:55:36 +08:00
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;
}
}