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; 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(); }); // 清除 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; } }