业务场景:主项目是用vue写的单页面应用,但是有多开页面的需求,现在需要在用户关闭了所有的浏览器标签页面后,自动退出登录。
思路:因为是不同的tab页面,我只能用localStorage来通信,新打开一个标签页(页面初次装载时),我就往数组中加一个页面,在(页面关闭或刷新等)页面卸载时移除它。这样就只需要在页面装载时(load事件)判断当前是不是刷新页面就可以了,只要是其他来源就直接登出。
代码
import { MessageBox } from 'element-ui'
import store from '@/store'
const cache_key = 'currently_open_page'
const event_key = 'currently_open_page_change'
const eve = new CustomEvent(event_key, {
detail: {
eventName: event_key,
list: [],
operationType: 'clear',
},
})
/**
* 主方法,外部只要调用此方法就可以了
*/
function mount() {
window.addEventListener('beforeunload', function () {
const currentRoute = getCurrentPage()
delPage(currentRoute)
})
window.addEventListener('load', function () {
// 添加监听
addPageListener()
// 网页通过“重新加载”按钮或者location.reload()方法加载
if (window.performance.navigation.type != 1) {
// 如果页面不是刷新进来,不管是任何来源,都可以认为是新进入页面,此时应该就去登录页面
if (!getCurrentOpenPageList().length) {
// 将当前页面的 URL 设置为新的 URL(不包含任何参数)
history.pushState(null, null, window.location.href)
window.location.hash = ''
store.dispatch('user/logout')
}
}
// 添加新的页面
const currentRoute = getCurrentPage()
addPage(currentRoute)
})
}
/**
* 获取当前的页面(tab页面),目前就用时间值吧
* @returns
*/
function getCurrentPage(reset = false) {
if (!window._currentPage || reset) {
window._currentPage = 'page_' + new Date().getTime()
}
return window._currentPage
}
/**
* 获取当前已打开的页面列表
* @returns
*/
function getCurrentOpenPageList() {
const t = window.localStorage.getItem(cache_key)
if (t) {
return JSON.parse(t)
} else {
window.localStorage.setItem(cache_key, JSON.stringify([]))
return []
}
}
/**
* 往缓存中新增页面
*/
function addPage(page) {
const list = getCurrentOpenPageList()
list.push(page)
eve.detail.list = list
eve.detail.operationType = 'add'
window.dispatchEvent(eve)
}
/**
* 往缓存中移除页面
*/
function delPage(page) {
const list = getCurrentOpenPageList()
const findIndex = list.indexOf(page)
if (findIndex != -1) {
list.splice(findIndex, 1)
}
eve.detail.list = list
eve.detail.operationType = 'delete'
window.dispatchEvent(eve)
}
/**
* 清除所有的页面
*/
function clearAllPage() {
eve.detail.list = []
eve.detail.operationType = 'clear'
window.dispatchEvent(eve)
}
/**
* 此方法在登陆后使用,登录后重载,清空当前页面列表
* 此方法出现的原因:在实际使用中发现,正常情况下,用户手动点击标签页的关闭、整个浏览器的关闭,都可以正确的移除页面,
* 但是如果用户在任务管理器结束了浏览器进程,或者用户未关闭浏览器,直接关机了,此时并不会触发页面的beforeunload事件。
* 导致getCurrentOpenPageList()的数量会因为这样的操作越来越用,使得在load事件中写的判断永远触发不到了
*/
function loginAfterReload() {
// 清空了所有的页面
clearAllPage()
// 将当前页面加回去
addPage(getCurrentPage(true))
}
/**
* 检查页面是否存在
*/
function checkPageExist() {
const currentPage = getCurrentPage()
const list = getCurrentOpenPageList()
const find = list.find((x) => x == currentPage)
return find ? true : false
}
function addPageListener() {
// 此监听只会触发本页面
window.addEventListener(
event_key,
function (event) {
// 将数据存储到本地
window.localStorage.setItem(cache_key, JSON.stringify(event.detail.list))
},
false
)
// 通知其他的同源页面,内容发生了变化
let timer = null
let onlyOne = false
window.addEventListener('storage', (event) => {
// 仅触发当前事件
if (event.key != cache_key) return
// 只要触发一次就好了
if (onlyOne) return
clearTimeout(timer)
timer = setTimeout(async () => {
// 检查页面是否依然在页面数组中
if (!checkPageExist()) {
onlyOne = true
MessageBox.confirm('页面过期失效', '提示', {
cancelButtonText: '关闭',
confirmButtonText: '刷新',
showCancelButton: true,
closeOnClickModal: false,
closeOnPressEscape: false,
})
.then(() => {
window.location.reload()
})
.catch(() => {
window.close()
})
}
}, 1000)
})
}
export {
mount, // 外部在main.js中调用即可,哪里都无所谓
loginAfterReload, // 在登录后调用
}