108 lines
2.5 KiB
Vue
108 lines
2.5 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, onUnmounted, watch } from 'vue'
|
|
import { RouterView, useRoute, useRouter } from 'vue-router'
|
|
|
|
import Container from '@/components/layout/Container.vue'
|
|
import Header from '@/components/layout/Header.vue'
|
|
import Menu from '@/components/layout/Menu.vue'
|
|
import { useLoginStore } from '@/stores/login'
|
|
|
|
const loginStore = useLoginStore()
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
let authorizationRefreshing = false
|
|
|
|
async function redirectIfUnauthorized() {
|
|
const resource = loginStore.menuForPath(route.path)
|
|
if (
|
|
route.name === 'menu-resource' &&
|
|
(!resource ||
|
|
resource.resourceType !== 'MENU' ||
|
|
resource.status !== 'ENABLED' ||
|
|
!resource.code ||
|
|
!loginStore.hasPermission(resource.code))
|
|
) {
|
|
await router.replace(loginStore.firstAuthorizedPath ?? '/forbidden')
|
|
}
|
|
}
|
|
|
|
async function refreshAuthorization() {
|
|
if (authorizationRefreshing) return
|
|
authorizationRefreshing = true
|
|
try {
|
|
await loginStore.refreshAuthorization()
|
|
await redirectIfUnauthorized()
|
|
} catch {
|
|
// A transient refresh error should not interrupt an otherwise active session.
|
|
} finally {
|
|
authorizationRefreshing = false
|
|
}
|
|
}
|
|
|
|
function handleWindowFocus() {
|
|
void refreshAuthorization()
|
|
}
|
|
|
|
/**
|
|
* 切标签页回来时浏览器不一定派发 window focus(窗口本身没失焦),
|
|
* 所以补一条 visibilitychange,保证「离开页面期间别人改了权限」能立刻收敛。
|
|
*/
|
|
function handleVisibilityChange() {
|
|
if (document.visibilityState === 'visible') {
|
|
void refreshAuthorization()
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
window.addEventListener('focus', handleWindowFocus)
|
|
document.addEventListener('visibilitychange', handleVisibilityChange)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
window.removeEventListener('focus', handleWindowFocus)
|
|
document.removeEventListener('visibilitychange', handleVisibilityChange)
|
|
})
|
|
|
|
watch(
|
|
() => [route.path, loginStore.authorization] as const,
|
|
() => {
|
|
if (loginStore.authorization) {
|
|
void redirectIfUnauthorized()
|
|
}
|
|
},
|
|
)
|
|
</script>
|
|
|
|
<template>
|
|
<div class="clinical-layout">
|
|
<Menu />
|
|
<div class="clinical-main">
|
|
<Header />
|
|
<Container>
|
|
<RouterView />
|
|
</Container>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.clinical-layout {
|
|
display: flex;
|
|
width: 100%;
|
|
height: 100vh;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
background: var(--bg);
|
|
}
|
|
|
|
.clinical-main {
|
|
display: flex;
|
|
flex: 1;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
}
|
|
</style>
|