mirror of
https://github.com/Cccc-owo/CheckInApp.git
synced 2026-06-17 14:06:28 +00:00
feat: migrate from Element Plus to Ant Design Vue and update Vite configuration for better dependency management
- Updated Vite configuration to manually chunk Ant Design Vue for improved dependency management. - Added a comprehensive migration testing checklist for transitioning from Element Plus 2.13.0 to Ant Design Vue 4.x, covering various components and functionalities.
This commit is contained in:
+340
-156
@@ -6,15 +6,13 @@
|
||||
<div class="flex items-center space-x-8">
|
||||
<router-link to="/" class="flex items-center space-x-3 group">
|
||||
<div class="w-10 h-10 bg-gradient-to-br from-primary-500 to-secondary-500 rounded-md3 flex items-center justify-center transform group-hover:scale-110 transition-transform">
|
||||
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<CheckCircleOutlined class="text-white text-xl" />
|
||||
</div>
|
||||
<span class="text-xl font-bold text-gradient">接龙自动打卡</span>
|
||||
</router-link>
|
||||
|
||||
<!-- Navigation Links -->
|
||||
<div class="hidden md:flex items-center space-x-2">
|
||||
<!-- Desktop Navigation Links -->
|
||||
<div v-if="!isMobile" class="hidden md:flex items-center space-x-2">
|
||||
<router-link
|
||||
to="/dashboard"
|
||||
v-slot="{ isActive }"
|
||||
@@ -30,9 +28,7 @@
|
||||
]"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
</svg>
|
||||
<HomeOutlined />
|
||||
<span>仪表盘</span>
|
||||
</div>
|
||||
</a>
|
||||
@@ -53,9 +49,7 @@
|
||||
]"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
||||
</svg>
|
||||
<FileTextOutlined />
|
||||
<span>任务管理</span>
|
||||
</div>
|
||||
</a>
|
||||
@@ -76,156 +70,203 @@
|
||||
]"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
|
||||
</svg>
|
||||
<UnorderedListOutlined />
|
||||
<span>打卡记录</span>
|
||||
</div>
|
||||
</a>
|
||||
</router-link>
|
||||
|
||||
<!-- Admin Menu -->
|
||||
<div v-if="authStore.isAdmin" class="relative" @mouseenter="showAdminMenu = true" @mouseleave="showAdminMenu = false">
|
||||
<button
|
||||
<!-- Admin Dropdown Menu -->
|
||||
<a-dropdown v-if="authStore.isAdmin" :trigger="['hover']">
|
||||
<a
|
||||
:class="[
|
||||
'px-4 py-2 rounded-full font-medium transition-all flex items-center space-x-2',
|
||||
'px-4 py-2 rounded-full font-medium transition-all flex items-center space-x-2 cursor-pointer',
|
||||
isAdminPath ? 'bg-secondary-100 text-secondary-700' : 'text-gray-700 hover:bg-gray-100'
|
||||
]"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
<SettingOutlined />
|
||||
<span>管理后台</span>
|
||||
<svg class="w-4 h-4 transition-transform" :class="{ 'rotate-180': showAdminMenu }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Admin Dropdown -->
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200"
|
||||
enter-from-class="opacity-0 translate-y-1"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-in duration-150"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 translate-y-1"
|
||||
>
|
||||
<div v-show="showAdminMenu" class="absolute top-full left-0 mt-2 w-48 glass-effect rounded-md3 shadow-md3-3 py-2 border border-gray-200/50">
|
||||
<router-link
|
||||
to="/admin/users"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
<span>用户管理</span>
|
||||
</div>
|
||||
</router-link>
|
||||
<router-link
|
||||
to="/admin/templates"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<span>模板管理</span>
|
||||
</div>
|
||||
</router-link>
|
||||
<router-link
|
||||
to="/admin/records"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
|
||||
</svg>
|
||||
<span>打卡记录</span>
|
||||
</div>
|
||||
</router-link>
|
||||
<router-link
|
||||
to="/admin/stats"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
<span>统计信息</span>
|
||||
</div>
|
||||
</router-link>
|
||||
<router-link
|
||||
to="/admin/logs"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<span>系统日志</span>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
<DownOutlined class="text-xs" />
|
||||
</a>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="users" @click="router.push('/admin/users')">
|
||||
<UserOutlined />
|
||||
<span class="ml-2">用户管理</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="templates" @click="router.push('/admin/templates')">
|
||||
<FileOutlined />
|
||||
<span class="ml-2">模板管理</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="records" @click="router.push('/admin/records')">
|
||||
<CheckSquareOutlined />
|
||||
<span class="ml-2">打卡记录</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="stats" @click="router.push('/admin/stats')">
|
||||
<BarChartOutlined />
|
||||
<span class="ml-2">统计信息</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="logs" @click="router.push('/admin/logs')">
|
||||
<FileTextOutlined />
|
||||
<span class="ml-2">系统日志</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Menu -->
|
||||
<!-- User Menu & Mobile Hamburger -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- User Avatar and Menu -->
|
||||
<div class="relative" @mouseenter="showUserMenu = true" @mouseleave="showUserMenu = false">
|
||||
<button class="flex items-center space-x-3 px-4 py-2 rounded-full hover:bg-gray-100 transition-all">
|
||||
<div class="w-8 h-8 bg-gradient-to-br from-accent-400 to-accent-600 rounded-full flex items-center justify-center text-white font-semibold">
|
||||
{{ userInitial }}
|
||||
</div>
|
||||
<span class="hidden md:block font-medium text-gray-700">{{ authStore.user?.alias || '用户' }}</span>
|
||||
<svg class="w-4 h-4 text-gray-500 transition-transform" :class="{ 'rotate-180': showUserMenu }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- User Dropdown -->
|
||||
<transition
|
||||
enter-active-class="transition ease-out duration-200"
|
||||
enter-from-class="opacity-0 translate-y-1"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-in duration-150"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 translate-y-1"
|
||||
<!-- Token Status Indicator (Desktop) -->
|
||||
<a-tooltip v-if="!isMobile && showTokenStatus" :title="tokenStatusTooltip">
|
||||
<div
|
||||
class="px-3 py-1.5 rounded-full cursor-pointer transition-all hover:bg-gray-100 flex items-center space-x-2"
|
||||
@click="handleTokenStatusClick"
|
||||
>
|
||||
<div v-show="showUserMenu" class="absolute top-full right-0 mt-2 w-48 glass-effect rounded-md3 shadow-md3-3 py-2 border border-gray-200/50">
|
||||
<div class="px-4 py-2 border-b border-gray-200/50">
|
||||
<p class="text-sm font-medium text-gray-900">{{ authStore.user?.alias }}</p>
|
||||
<p class="text-xs text-gray-500 mt-1">{{ authStore.isAdmin ? '管理员' : '普通用户' }}</p>
|
||||
</div>
|
||||
<button
|
||||
@click="router.push('/settings')"
|
||||
class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 transition-colors flex items-center space-x-2"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
<span>个人设置</span>
|
||||
</button>
|
||||
<button
|
||||
@click="handleLogout"
|
||||
class="w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-red-50 transition-colors flex items-center space-x-2"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
|
||||
</svg>
|
||||
<span>退出登录</span>
|
||||
</button>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
<a-badge :status="tokenBadgeStatus" />
|
||||
<ClockCircleOutlined :class="tokenIconClass" />
|
||||
<span class="text-sm">{{ tokenBadgeText }}</span>
|
||||
<!-- 过期时显示刷新按钮 -->
|
||||
<a-button
|
||||
v-if="remainingMinutes !== null && remainingMinutes < 0"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click.stop="handleRefreshToken"
|
||||
>
|
||||
刷新
|
||||
</a-button>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
|
||||
<!-- Desktop User Menu -->
|
||||
<a-dropdown v-if="!isMobile" :trigger="['hover']">
|
||||
<a class="flex items-center space-x-3 px-4 py-2 rounded-full hover:bg-gray-100 transition-all cursor-pointer">
|
||||
<a-avatar :style="{ backgroundColor: '#f56a00' }">
|
||||
{{ userInitial }}
|
||||
</a-avatar>
|
||||
<span class="hidden md:block font-medium text-gray-700">{{ authStore.user?.alias || '用户' }}</span>
|
||||
<DownOutlined class="text-xs text-gray-500" />
|
||||
</a>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="info" disabled>
|
||||
<div class="px-2 py-1">
|
||||
<p class="text-sm font-medium text-gray-900">{{ authStore.user?.alias }}</p>
|
||||
<p class="text-xs text-gray-500 mt-1">{{ authStore.isAdmin ? '管理员' : '普通用户' }}</p>
|
||||
</div>
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="settings" @click="router.push('/settings')">
|
||||
<SettingOutlined />
|
||||
<span class="ml-2">个人设置</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="logout" @click="handleLogout" danger>
|
||||
<LogoutOutlined />
|
||||
<span class="ml-2">退出登录</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
|
||||
<!-- Mobile Hamburger Button -->
|
||||
<a-button
|
||||
v-if="isMobile"
|
||||
type="text"
|
||||
@click="drawerVisible = true"
|
||||
class="!p-2"
|
||||
>
|
||||
<MenuOutlined class="text-xl" />
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Mobile Drawer -->
|
||||
<a-drawer
|
||||
v-model:open="drawerVisible"
|
||||
placement="left"
|
||||
:width="280"
|
||||
title="菜单"
|
||||
>
|
||||
<!-- User Info in Drawer -->
|
||||
<div class="mb-6 pb-4 border-b border-gray-200">
|
||||
<div class="flex items-center space-x-3">
|
||||
<a-avatar :size="48" :style="{ backgroundColor: '#f56a00' }">
|
||||
{{ userInitial }}
|
||||
</a-avatar>
|
||||
<div>
|
||||
<p class="font-medium text-gray-900">{{ authStore.user?.alias || '用户' }}</p>
|
||||
<p class="text-xs text-gray-500">{{ authStore.isAdmin ? '管理员' : '普通用户' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Navigation Menu -->
|
||||
<a-menu
|
||||
mode="inline"
|
||||
:selected-keys="[currentMenuKey]"
|
||||
@click="handleMenuClick"
|
||||
>
|
||||
<a-menu-item key="dashboard">
|
||||
<template #icon><HomeOutlined /></template>
|
||||
仪表盘
|
||||
</a-menu-item>
|
||||
<a-menu-item key="tasks">
|
||||
<template #icon><FileTextOutlined /></template>
|
||||
任务管理
|
||||
</a-menu-item>
|
||||
<a-menu-item key="records">
|
||||
<template #icon><UnorderedListOutlined /></template>
|
||||
打卡记录
|
||||
</a-menu-item>
|
||||
|
||||
<!-- Admin Menu Group -->
|
||||
<a-sub-menu v-if="authStore.isAdmin" key="admin">
|
||||
<template #icon><SettingOutlined /></template>
|
||||
<template #title>管理后台</template>
|
||||
<a-menu-item key="admin-users">
|
||||
<template #icon><UserOutlined /></template>
|
||||
用户管理
|
||||
</a-menu-item>
|
||||
<a-menu-item key="admin-templates">
|
||||
<template #icon><FileOutlined /></template>
|
||||
模板管理
|
||||
</a-menu-item>
|
||||
<a-menu-item key="admin-records">
|
||||
<template #icon><CheckSquareOutlined /></template>
|
||||
打卡记录
|
||||
</a-menu-item>
|
||||
<a-menu-item key="admin-stats">
|
||||
<template #icon><BarChartOutlined /></template>
|
||||
统计信息
|
||||
</a-menu-item>
|
||||
<a-menu-item key="admin-logs">
|
||||
<template #icon><FileTextOutlined /></template>
|
||||
系统日志
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
|
||||
<a-menu-divider />
|
||||
|
||||
<a-menu-item key="settings">
|
||||
<template #icon><SettingOutlined /></template>
|
||||
个人设置
|
||||
</a-menu-item>
|
||||
<a-menu-item key="logout" danger>
|
||||
<template #icon><LogoutOutlined /></template>
|
||||
退出登录
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-drawer>
|
||||
|
||||
<!-- Token 刷新 QR 码模态框 -->
|
||||
<QRCodeModal
|
||||
v-model:visible="qrcodeModalVisible"
|
||||
:alias="authStore.user?.alias || ''"
|
||||
@success="handleQRCodeSuccess"
|
||||
@error="handleQRCodeError"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -233,14 +274,36 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useTokenMonitor } from '@/composables/useTokenMonitor'
|
||||
import { useBreakpoint } from '@/composables/useBreakpoint'
|
||||
import { Modal, message } from 'ant-design-vue'
|
||||
import QRCodeModal from './QRCodeModal.vue'
|
||||
import {
|
||||
MenuOutlined,
|
||||
HomeOutlined,
|
||||
FileTextOutlined,
|
||||
UnorderedListOutlined,
|
||||
SettingOutlined,
|
||||
UserOutlined,
|
||||
FileOutlined,
|
||||
CheckSquareOutlined,
|
||||
BarChartOutlined,
|
||||
LogoutOutlined,
|
||||
DownOutlined,
|
||||
CheckCircleOutlined,
|
||||
ClockCircleOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const authStore = useAuthStore()
|
||||
const userStore = useUserStore()
|
||||
const { isMobile } = useBreakpoint()
|
||||
const { getRemainingMinutes, tokenStatus } = useTokenMonitor()
|
||||
|
||||
const showAdminMenu = ref(false)
|
||||
const showUserMenu = ref(false)
|
||||
const drawerVisible = ref(false)
|
||||
const qrcodeModalVisible = ref(false)
|
||||
|
||||
const isAdminPath = computed(() => route.path.startsWith('/admin'))
|
||||
|
||||
@@ -249,19 +312,140 @@ const userInitial = computed(() => {
|
||||
return name.charAt(0).toUpperCase()
|
||||
})
|
||||
|
||||
// Token 状态计算
|
||||
const remainingMinutes = computed(() => {
|
||||
return getRemainingMinutes()
|
||||
})
|
||||
|
||||
const showTokenStatus = computed(() => {
|
||||
if (!authStore.isAuthenticated || !tokenStatus.value) return false
|
||||
|
||||
const mins = remainingMinutes.value
|
||||
// 显示条件:Token 即将过期(60分钟内)或已过期(5分钟内)
|
||||
if (mins === null) return false
|
||||
return mins <= 60 || (mins < 0 && Math.abs(mins) <= 5)
|
||||
})
|
||||
|
||||
const tokenBadgeStatus = computed(() => {
|
||||
const mins = remainingMinutes.value
|
||||
if (mins === null) return 'default'
|
||||
if (mins < 0) return 'error' // 已过期
|
||||
if (mins <= 10) return 'error' // 10分钟内过期
|
||||
if (mins <= 30) return 'warning' // 30分钟内过期
|
||||
return 'processing' // 正常但快过期
|
||||
})
|
||||
|
||||
const tokenBadgeText = computed(() => {
|
||||
const mins = remainingMinutes.value
|
||||
if (mins === null) return ''
|
||||
if (mins < 0) return 'Token 已过期'
|
||||
if (mins < 60) return `Token 剩余:${mins}分钟`
|
||||
return ''
|
||||
})
|
||||
|
||||
const tokenIconClass = computed(() => {
|
||||
const mins = remainingMinutes.value
|
||||
if (mins === null) return 'text-gray-500'
|
||||
if (mins < 0) return 'text-red-500' // 已过期
|
||||
if (mins <= 10) return 'text-red-500 animate-pulse' // 10分钟内,闪烁
|
||||
if (mins <= 30) return 'text-orange-500' // 30分钟内
|
||||
return 'text-blue-500' // 正常
|
||||
})
|
||||
|
||||
const tokenStatusTooltip = computed(() => {
|
||||
const mins = remainingMinutes.value
|
||||
if (mins === null) return 'Token 状态未知'
|
||||
if (mins < 0) {
|
||||
const expiredMins = Math.abs(mins)
|
||||
return `登录凭证已过期 ${expiredMins} 分钟,点击右侧按钮刷新`
|
||||
}
|
||||
if (mins < 60) {
|
||||
return `Token 剩余时间:${mins} 分钟,过期后可刷新)`
|
||||
}
|
||||
return 'Token 状态正常'
|
||||
})
|
||||
|
||||
const handleTokenStatusClick = () => {
|
||||
const mins = remainingMinutes.value
|
||||
|
||||
// Token 已过期时提醒刷新
|
||||
if (mins !== null && mins < 0) {
|
||||
message.info('Token 已过期,请进行刷新')
|
||||
}
|
||||
// Token 未过期时,点击无效果
|
||||
}
|
||||
|
||||
const currentMenuKey = computed(() => {
|
||||
const path = route.path
|
||||
if (path.startsWith('/admin/users')) return 'admin-users'
|
||||
if (path.startsWith('/admin/templates')) return 'admin-templates'
|
||||
if (path.startsWith('/admin/records')) return 'admin-records'
|
||||
if (path.startsWith('/admin/stats')) return 'admin-stats'
|
||||
if (path.startsWith('/admin/logs')) return 'admin-logs'
|
||||
if (path.startsWith('/dashboard')) return 'dashboard'
|
||||
if (path.startsWith('/tasks')) return 'tasks'
|
||||
if (path.startsWith('/records')) return 'records'
|
||||
if (path.startsWith('/settings')) return 'settings'
|
||||
return ''
|
||||
})
|
||||
|
||||
const handleMenuClick = ({ key }) => {
|
||||
const routes = {
|
||||
'dashboard': '/dashboard',
|
||||
'tasks': '/tasks',
|
||||
'records': '/records',
|
||||
'admin-users': '/admin/users',
|
||||
'admin-templates': '/admin/templates',
|
||||
'admin-records': '/admin/records',
|
||||
'admin-stats': '/admin/stats',
|
||||
'admin-logs': '/admin/logs',
|
||||
'settings': '/settings',
|
||||
}
|
||||
|
||||
if (key === 'logout') {
|
||||
handleLogout()
|
||||
} else if (routes[key]) {
|
||||
router.push(routes[key])
|
||||
drawerVisible.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleLogout = () => {
|
||||
ElMessageBox.confirm('确定要退出登录吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要退出登录吗?',
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onOk() {
|
||||
authStore.logout()
|
||||
router.push('/login')
|
||||
})
|
||||
.catch(() => {
|
||||
// 取消操作
|
||||
})
|
||||
drawerVisible.value = false
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 处理 Token 刷新
|
||||
const handleRefreshToken = () => {
|
||||
qrcodeModalVisible.value = true
|
||||
}
|
||||
|
||||
// 处理 QR 码扫码成功
|
||||
const handleQRCodeSuccess = async () => {
|
||||
message.success('Token 刷新成功')
|
||||
qrcodeModalVisible.value = false
|
||||
|
||||
// 刷新用户信息和 Token 状态
|
||||
try {
|
||||
await authStore.fetchCurrentUser()
|
||||
await userStore.fetchTokenStatus()
|
||||
} catch (error) {
|
||||
console.error('刷新用户信息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 QR 码扫码失败
|
||||
const handleQRCodeError = (error) => {
|
||||
message.error(error?.message || 'Token 刷新失败')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user