140 lines
No EOL
3 KiB
Vue
140 lines
No EOL
3 KiB
Vue
<template>
|
||
<div class="app-container">
|
||
<router-view v-slot="{ Component }">
|
||
<transition
|
||
name="fade"
|
||
mode="out-in"
|
||
@before-leave="showLoadingMask"
|
||
@after-enter="hideLoadingMask">
|
||
<component :is="Component" />
|
||
</transition>
|
||
</router-view>
|
||
|
||
<!-- 全局加载遮罩 -->
|
||
<transition name="fade">
|
||
<div v-if="isLoading" class="global-loading-mask">
|
||
<div class="loading-spinner"></div>
|
||
</div>
|
||
</transition>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { ElMessage } from 'element-plus'
|
||
import { UserAPI } from '@/api/user'
|
||
import { initFingerprint } from '@/utils/fingerprint'
|
||
|
||
const router = useRouter()
|
||
const isLoading = ref(false)
|
||
|
||
const showLoadingMask = () => {
|
||
isLoading.value = true
|
||
}
|
||
|
||
const hideLoadingMask = () => {
|
||
isLoading.value = false
|
||
}
|
||
|
||
// 检查登录状态
|
||
const checkAuthStatus = async () => {
|
||
const token = localStorage.getItem('token')
|
||
const isAuthenticated = localStorage.getItem('isAuthenticated')
|
||
|
||
// 只有当存在token和认证状态时才检查
|
||
if (token && isAuthenticated) {
|
||
try {
|
||
const response = await UserAPI.getUserInfo()
|
||
if (response.retcode !== 0) {
|
||
clearAuthData('登录已失效,请重新登录')
|
||
}
|
||
} catch (error) {
|
||
// 如果是401错误,清除认证数据并重定向
|
||
if (error.response?.status === 401) {
|
||
clearAuthData('登录已过期,请重新登录')
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 清除认证数据
|
||
const clearAuthData = (message) => {
|
||
localStorage.removeItem('token')
|
||
localStorage.removeItem('isAuthenticated')
|
||
localStorage.removeItem('userRole')
|
||
localStorage.removeItem('userInfo')
|
||
|
||
// 如果当前不在访客页面,显示提示并重定向到登录页
|
||
if (!router.currentRoute.value.path.startsWith('/visitor')) {
|
||
if (message) {
|
||
ElMessage.warning(message)
|
||
}
|
||
router.push('/login')
|
||
}
|
||
}
|
||
|
||
onMounted(async () => {
|
||
// 初始化指纹
|
||
await initFingerprint()
|
||
// 检查登录状态
|
||
await checkAuthStatus()
|
||
})
|
||
</script>
|
||
|
||
<style>
|
||
.app-container {
|
||
height: 100vh;
|
||
}
|
||
|
||
/* 过渡动画 */
|
||
.fade-enter-active,
|
||
.fade-leave-active {
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
.fade-enter-from,
|
||
.fade-leave-to {
|
||
opacity: 0;
|
||
}
|
||
|
||
/* 全局加载遮罩 */
|
||
.global-loading-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(255, 255, 255, 0.8);
|
||
backdrop-filter: blur(8px);
|
||
z-index: 9999;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
/* 加载动画 */
|
||
.loading-spinner {
|
||
width: 40px;
|
||
height: 40px;
|
||
border: 3px solid #f3f3f3;
|
||
border-top: 3px solid #1890ff;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
/* 全局气泡提示样式 */
|
||
.el-popper {
|
||
z-index: 9999 !important;
|
||
}
|
||
|
||
/* 确保日志内容的气泡提示显示在最上层 */
|
||
.el-popper.is-pure.el-tooltip__popper {
|
||
z-index: 99999 !important;
|
||
}
|
||
</style> |