diff --git a/src/views/login/components/Login/index.vue b/src/views/login/components/Login/index.vue index 678ea02..82b4266 100644 --- a/src/views/login/components/Login/index.vue +++ b/src/views/login/components/Login/index.vue @@ -1,94 +1,119 @@ @@ -106,6 +132,7 @@ import type { FormInst } from 'naive-ui' import { useAppStore, useAuthStore } from '@/store' import { fetchCaptchaPng } from '@/service/api/auth' import { local } from '@/utils' +import { coiMsgWarning } from '@/utils/coi' const emit = defineEmits(['update:modelValue']) @@ -148,6 +175,7 @@ const formValue = ref({ }) const isRemember = ref(false) const isLoading = ref(false) +const isRedirectLoading = ref(false) // 验证码相关 const captchaImage = ref('') @@ -169,6 +197,10 @@ async function getCaptcha() { const formRef = ref(null) function handleLogin() { + // 防止重复提交 + if (isLoading.value) + return + formRef.value?.validate(async (errors) => { if (errors) return @@ -180,14 +212,38 @@ function handleLogin() { local.set('loginAccount', { account, pwd }) else local.remove('loginAccount') - await authStore.login(account, pwd, captchaKey.value, securityCode, isRemember.value) - isLoading.value = false + try { + await authStore.login(account, pwd, captchaKey.value, securityCode, isRemember.value) + } + catch (error) { + // 登录失败时刷新验证码并清空验证码输入框 + await getCaptcha() + formValue.value.securityCode = '' + console.warn('[Login Failed]:', error) + } + finally { + isLoading.value = false + } }) } onMounted(() => { checkUserAccount() getCaptcha() + + // 检查是否有重定向参数,显示身份过期提示和loading + const route = useRoute() + if (route.query.redirect) { + isRedirectLoading.value = true + // 显示loading状态,然后显示提示 + setTimeout(() => { + coiMsgWarning('账号身份过期,请你重新登录') + // 延迟隐藏loading,让过渡更自然 + setTimeout(() => { + isRedirectLoading.value = false + }, 200) + }, 1000) + } }) function checkUserAccount() { @@ -257,6 +313,40 @@ function checkUserAccount() { --n-text-color: #f3f4f6; } +/* 过渡动画 */ +.fade-enter-active, +.fade-leave-active { + transition: all 0.3s ease; +} + +.fade-enter-from { + opacity: 0; + transform: translateY(10px); +} + +.fade-leave-to { + opacity: 0; + transform: translateY(-10px); +} + +/* Loading点动画 */ +.loading-dot { + width: 8px; + height: 8px; + background: v-bind(primaryColor); + border-radius: 50%; + animation: bounce 1.4s ease-in-out infinite both; +} + +@keyframes bounce { + 0%, 80%, 100% { + transform: scale(0); + } + 40% { + transform: scale(1); + } +} + /* 响应式设计 */ @media (max-width: 640px) { .login-input {