优化性能
This commit is contained in:
172
frontend/src/components/LoginForm.vue
Normal file
172
frontend/src/components/LoginForm.vue
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div class="bg-white/10 backdrop-blur-lg rounded-2xl p-8 border border-white/20 shadow-2xl">
|
||||||
|
<form @submit.prevent="handleLogin" class="space-y-6">
|
||||||
|
<!-- 用户名输入框 -->
|
||||||
|
<div>
|
||||||
|
<label for="username" class="block text-sm font-medium text-slate-200 mb-2">
|
||||||
|
用户名
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="username"
|
||||||
|
v-model="loginForm.username"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-slate-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
||||||
|
placeholder="请输入用户名"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 密码输入框 -->
|
||||||
|
<div>
|
||||||
|
<label for="password" class="block text-sm font-medium text-slate-200 mb-2">
|
||||||
|
密码
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
v-model="loginForm.password"
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-slate-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 记住我 -->
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input
|
||||||
|
id="remember-me"
|
||||||
|
v-model="loginForm.rememberMe"
|
||||||
|
type="checkbox"
|
||||||
|
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-white/20 rounded bg-white/10"
|
||||||
|
/>
|
||||||
|
<label for="remember-me" class="ml-2 block text-sm text-slate-200">
|
||||||
|
记住我
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="handleForgotPassword"
|
||||||
|
class="text-slate-300 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
忘记密码?
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 登录按钮 -->
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
:disabled="authStore.loading"
|
||||||
|
class="w-full flex justify-center py-3 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
|
||||||
|
>
|
||||||
|
<span v-if="authStore.loading" class="flex items-center justify-center">
|
||||||
|
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
登录中...
|
||||||
|
</span>
|
||||||
|
<span v-else>登录</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
// 登录表单数据
|
||||||
|
const loginForm = reactive({
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
rememberMe: false
|
||||||
|
})
|
||||||
|
|
||||||
|
// 登录处理函数
|
||||||
|
const handleLogin = async () => {
|
||||||
|
if (!loginForm.username || !loginForm.password) {
|
||||||
|
toast.error('请填写完整的登录信息')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await authStore.login({
|
||||||
|
username: loginForm.username,
|
||||||
|
password: loginForm.password
|
||||||
|
})
|
||||||
|
|
||||||
|
// 如果选择记住我,保存登录信息到本地存储
|
||||||
|
if (loginForm.rememberMe) {
|
||||||
|
localStorage.setItem('rememberedUser', JSON.stringify({
|
||||||
|
username: loginForm.username,
|
||||||
|
password: loginForm.password
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('rememberedUser')
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push('/dashboard')
|
||||||
|
} catch (error: any) {
|
||||||
|
console.log(error)
|
||||||
|
// 根据不同的错误类型显示不同的错误信息
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
const errorMessage = error.response?.data?.error || '用户名或密码错误'
|
||||||
|
const remainingAttempts = error.response?.data?.remainingAttempts
|
||||||
|
|
||||||
|
if (errorMessage.includes('锁定')) {
|
||||||
|
toast.error(errorMessage)
|
||||||
|
} else if (errorMessage.includes('禁用') || errorMessage.includes('inactive')) {
|
||||||
|
toast.error('您的账户尚未激活,请联系管理员激活账户')
|
||||||
|
} else {
|
||||||
|
if (remainingAttempts !== undefined) {
|
||||||
|
toast.error(`用户名或密码错误,还剩${remainingAttempts}次尝试机会`)
|
||||||
|
} else {
|
||||||
|
toast.error('用户名或密码错误,请检查后重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (error.response?.status === 400) {
|
||||||
|
toast.error(error.response?.data?.message || '请求参数错误')
|
||||||
|
} else if (error.response?.status === 500) {
|
||||||
|
toast.error('服务器内部错误,请稍后重试')
|
||||||
|
} else if (error.code === 'NETWORK_ERROR') {
|
||||||
|
toast.error('网络连接失败,请检查网络连接')
|
||||||
|
} else {
|
||||||
|
toast.error(error.response?.data?.message || '登录失败,请稍后重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 忘记密码处理函数
|
||||||
|
const handleForgotPassword = () => {
|
||||||
|
toast.info('请联系管理员')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载时检查是否有记住的登录信息
|
||||||
|
const loadRememberedUser = () => {
|
||||||
|
const remembered = localStorage.getItem('rememberedUser')
|
||||||
|
if (remembered) {
|
||||||
|
try {
|
||||||
|
const userData = JSON.parse(remembered)
|
||||||
|
loginForm.username = userData.username || ''
|
||||||
|
loginForm.rememberMe = true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析记住的用户信息失败:', error)
|
||||||
|
localStorage.removeItem('rememberedUser')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面挂载时初始化
|
||||||
|
onMounted(() => {
|
||||||
|
loadRememberedUser()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
148
frontend/src/components/RegisterForm.vue
Normal file
148
frontend/src/components/RegisterForm.vue
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
<template>
|
||||||
|
<div class="bg-white/10 backdrop-blur-lg rounded-2xl p-8 border border-white/20 shadow-2xl">
|
||||||
|
<form @submit.prevent="handleRegister" class="space-y-6">
|
||||||
|
<!-- 用户名输入框 -->
|
||||||
|
<div>
|
||||||
|
<label for="regUsername" class="block text-sm font-medium text-slate-200 mb-2">
|
||||||
|
用户名
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="regUsername"
|
||||||
|
v-model="registerForm.username"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-slate-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
||||||
|
placeholder="请输入用户名"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 密码输入框 -->
|
||||||
|
<div>
|
||||||
|
<label for="regPassword" class="block text-sm font-medium text-slate-200 mb-2">
|
||||||
|
密码
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="regPassword"
|
||||||
|
v-model="registerForm.password"
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-slate-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 确认密码输入框 -->
|
||||||
|
<div>
|
||||||
|
<label for="confirmPassword" class="block text-sm font-medium text-slate-200 mb-2">
|
||||||
|
确认密码
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="confirmPassword"
|
||||||
|
v-model="registerForm.confirmPassword"
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-slate-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
||||||
|
placeholder="请再次输入密码"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 注册按钮 -->
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
:disabled="authStore.loading"
|
||||||
|
class="w-full bg-gradient-to-r from-green-600 to-blue-600 hover:from-green-700 hover:to-blue-700 text-white font-semibold py-3 px-6 rounded-lg transition-all duration-200 transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 focus:ring-offset-slate-900 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none"
|
||||||
|
>
|
||||||
|
<span v-if="authStore.loading" class="flex items-center justify-center">
|
||||||
|
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
注册中...
|
||||||
|
</span>
|
||||||
|
<span v-else>注册</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, defineEmits } from 'vue'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
|
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
const toast = useToast()
|
||||||
|
const emit = defineEmits(['registered'])
|
||||||
|
|
||||||
|
// 注册表单数据
|
||||||
|
const registerForm = reactive({
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 注册处理函数
|
||||||
|
const handleRegister = async () => {
|
||||||
|
if (!registerForm.username || !registerForm.password || !registerForm.confirmPassword) {
|
||||||
|
toast.error('请填写完整的注册信息')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 验证用户名格式
|
||||||
|
if (!/^[a-zA-Z0-9_]+$/.test(registerForm.username)) {
|
||||||
|
toast.error('用户名只能包含字母、数字和下划线')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 验证用户名长度
|
||||||
|
if (registerForm.username.length < 3 || registerForm.username.length > 30) {
|
||||||
|
toast.error('用户名长度必须在3-30个字符之间')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 验证密码长度
|
||||||
|
if (registerForm.password.length < 8) {
|
||||||
|
toast.error('密码长度至少8个字符')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (registerForm.password !== registerForm.confirmPassword) {
|
||||||
|
toast.error('两次输入的密码不一致')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await authStore.register({
|
||||||
|
username: registerForm.username,
|
||||||
|
password: registerForm.password,
|
||||||
|
confirmPassword: registerForm.confirmPassword
|
||||||
|
})
|
||||||
|
|
||||||
|
toast.success('注册成功!请等待管理员激活您的账户。')
|
||||||
|
|
||||||
|
// 注册成功时清空注册表单
|
||||||
|
registerForm.username = ''
|
||||||
|
registerForm.password = ''
|
||||||
|
registerForm.confirmPassword = ''
|
||||||
|
|
||||||
|
emit('registered')
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('注册错误详情:', error)
|
||||||
|
console.error('错误响应:', error.response.data.error)
|
||||||
|
|
||||||
|
// 根据不同的错误类型显示不同的错误信息
|
||||||
|
if (error.response?.status === 400) {
|
||||||
|
const details = error.response?.data?.error
|
||||||
|
if (details) {
|
||||||
|
// 显示具体的验证错误信息
|
||||||
|
const errorMessages = details
|
||||||
|
toast.error(`注册失败: ${errorMessages}`)
|
||||||
|
}
|
||||||
|
} else if (error.response?.status === 409) {
|
||||||
|
toast.error('用户名已存在,请使用其他信息')
|
||||||
|
} else if (error.response?.status === 500) {
|
||||||
|
toast.error('服务器内部错误,请稍后重试')
|
||||||
|
} else if (error.code === 'NETWORK_ERROR') {
|
||||||
|
toast.error('网络连接失败,请检查网络连接')
|
||||||
|
} else {
|
||||||
|
toast.error(error.response?.data?.message || '注册失败,请稍后重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -53,145 +53,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 登录表单 -->
|
<!-- 登录表单 -->
|
||||||
<div v-if="currentMode === 'login'" class="bg-white/10 backdrop-blur-lg rounded-2xl p-8 border border-white/20 shadow-2xl">
|
<LoginForm v-if="currentMode === 'login'" />
|
||||||
<form @submit.prevent="handleLogin" class="space-y-6">
|
|
||||||
<!-- 用户名输入框 -->
|
|
||||||
<div>
|
|
||||||
<label for="username" class="block text-sm font-medium text-slate-200 mb-2">
|
|
||||||
用户名
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="username"
|
|
||||||
v-model="loginForm.username"
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-slate-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
|
||||||
placeholder="请输入用户名"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 密码输入框 -->
|
|
||||||
<div>
|
|
||||||
<label for="password" class="block text-sm font-medium text-slate-200 mb-2">
|
|
||||||
密码
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="password"
|
|
||||||
v-model="loginForm.password"
|
|
||||||
type="password"
|
|
||||||
required
|
|
||||||
class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-slate-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
|
||||||
placeholder="请输入密码"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 记住我 -->
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<input
|
|
||||||
id="remember-me"
|
|
||||||
v-model="loginForm.rememberMe"
|
|
||||||
type="checkbox"
|
|
||||||
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-white/20 rounded bg-white/10"
|
|
||||||
/>
|
|
||||||
<label for="remember-me" class="ml-2 block text-sm text-slate-200">
|
|
||||||
记住我
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="text-sm">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
@click="handleForgotPassword"
|
|
||||||
class="text-slate-300 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
忘记密码?
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 登录按钮 -->
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
:disabled="authStore.loading"
|
|
||||||
class="w-full flex justify-center py-3 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
|
|
||||||
>
|
|
||||||
<span v-if="authStore.loading" class="flex items-center justify-center">
|
|
||||||
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
||||||
</svg>
|
|
||||||
登录中...
|
|
||||||
</span>
|
|
||||||
<span v-else>登录</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 注册表单 -->
|
<!-- 注册表单 -->
|
||||||
<div v-if="currentMode === 'register'" class="bg-white/10 backdrop-blur-lg rounded-2xl p-8 border border-white/20 shadow-2xl">
|
<RegisterForm v-if="currentMode === 'register'" @registered="switchToLogin" />
|
||||||
<form @submit.prevent="handleRegister" class="space-y-6">
|
|
||||||
<!-- 用户名输入框 -->
|
|
||||||
<div>
|
|
||||||
<label for="regUsername" class="block text-sm font-medium text-slate-200 mb-2">
|
|
||||||
用户名
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="regUsername"
|
|
||||||
v-model="registerForm.username"
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-slate-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
|
||||||
placeholder="请输入用户名"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 密码输入框 -->
|
|
||||||
<div>
|
|
||||||
<label for="regPassword" class="block text-sm font-medium text-slate-200 mb-2">
|
|
||||||
密码
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="regPassword"
|
|
||||||
v-model="registerForm.password"
|
|
||||||
type="password"
|
|
||||||
required
|
|
||||||
class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-slate-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
|
||||||
placeholder="请输入密码"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 确认密码输入框 -->
|
|
||||||
<div>
|
|
||||||
<label for="confirmPassword" class="block text-sm font-medium text-slate-200 mb-2">
|
|
||||||
确认密码
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="confirmPassword"
|
|
||||||
v-model="registerForm.confirmPassword"
|
|
||||||
type="password"
|
|
||||||
required
|
|
||||||
class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-slate-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
|
||||||
placeholder="请再次输入密码"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 注册按钮 -->
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
:disabled="authStore.loading"
|
|
||||||
class="w-full bg-gradient-to-r from-green-600 to-blue-600 hover:from-green-700 hover:to-blue-700 text-white font-semibold py-3 px-6 rounded-lg transition-all duration-200 transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 focus:ring-offset-slate-900 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none"
|
|
||||||
>
|
|
||||||
<span v-if="authStore.loading" class="flex items-center justify-center">
|
|
||||||
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
||||||
</svg>
|
|
||||||
注册中...
|
|
||||||
</span>
|
|
||||||
<span v-else>注册</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 错误提示 -->
|
<!-- 错误提示 -->
|
||||||
<div v-if="authStore.error" class="mt-4 p-3 bg-red-500/20 border border-red-500/30 rounded-lg">
|
<div v-if="authStore.error" class="mt-4 p-3 bg-red-500/20 border border-red-500/30 rounded-lg">
|
||||||
@@ -208,184 +73,31 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, onMounted } from 'vue'
|
import { ref, defineAsyncComponent } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { useToast } from 'vue-toastification'
|
import { useToast } from 'vue-toastification'
|
||||||
|
import LoginForm from '@/components/LoginForm.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
const RegisterForm = defineAsyncComponent(() => import('@/components/RegisterForm.vue'))
|
||||||
|
|
||||||
// 当前模式:login 或 register
|
// 当前模式:login 或 register
|
||||||
const currentMode = ref<'login' | 'register'>('login')
|
const currentMode = ref<'login' | 'register'>('login')
|
||||||
|
|
||||||
// 成功消息
|
// 成功消息
|
||||||
const successMessage = ref('')
|
const successMessage = ref('')
|
||||||
|
|
||||||
// 登录表单数据
|
const switchToLogin = () => {
|
||||||
const loginForm = reactive({
|
currentMode.value = 'login'
|
||||||
username: '',
|
successMessage.value = '注册成功!您的账户已创建,请等待管理员激活后即可登录。'
|
||||||
password: '',
|
|
||||||
rememberMe: false
|
|
||||||
})
|
|
||||||
|
|
||||||
// 注册表单数据
|
|
||||||
const registerForm = reactive({
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
confirmPassword: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
// 登录处理函数
|
|
||||||
const handleLogin = async () => {
|
|
||||||
if (!loginForm.username || !loginForm.password) {
|
|
||||||
toast.error('请填写完整的登录信息')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await authStore.login({
|
|
||||||
username: loginForm.username,
|
|
||||||
password: loginForm.password
|
|
||||||
})
|
|
||||||
|
|
||||||
// 如果选择记住我,保存登录信息到本地存储
|
|
||||||
if (loginForm.rememberMe) {
|
|
||||||
localStorage.setItem('rememberedUser', JSON.stringify({
|
|
||||||
username: loginForm.username,
|
|
||||||
password: loginForm.password
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
localStorage.removeItem('rememberedUser')
|
|
||||||
}
|
|
||||||
|
|
||||||
router.push('/dashboard')
|
|
||||||
} catch (error: any) {
|
|
||||||
console.log(error)
|
|
||||||
// 根据不同的错误类型显示不同的错误信息
|
|
||||||
if (error.response?.status === 401) {
|
|
||||||
const errorMessage = error.response?.data?.error || '用户名或密码错误'
|
|
||||||
const remainingAttempts = error.response?.data?.remainingAttempts
|
|
||||||
|
|
||||||
if (errorMessage.includes('锁定')) {
|
|
||||||
toast.error(errorMessage)
|
|
||||||
} else if (errorMessage.includes('禁用') || errorMessage.includes('inactive')) {
|
|
||||||
toast.error('您的账户尚未激活,请联系管理员激活账户')
|
|
||||||
} else {
|
|
||||||
if (remainingAttempts !== undefined) {
|
|
||||||
toast.error(`用户名或密码错误,还剩${remainingAttempts}次尝试机会`)
|
|
||||||
} else {
|
|
||||||
toast.error('用户名或密码错误,请检查后重试')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (error.response?.status === 400) {
|
|
||||||
toast.error(error.response?.data?.message || '请求参数错误')
|
|
||||||
} else if (error.response?.status === 500) {
|
|
||||||
toast.error('服务器内部错误,请稍后重试')
|
|
||||||
} else if (error.code === 'NETWORK_ERROR') {
|
|
||||||
toast.error('网络连接失败,请检查网络连接')
|
|
||||||
} else {
|
|
||||||
toast.error(error.response?.data?.message || '登录失败,请稍后重试')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册处理函数
|
|
||||||
const handleRegister = async () => {
|
|
||||||
if (!registerForm.username || !registerForm.password || !registerForm.confirmPassword) {
|
|
||||||
toast.error('请填写完整的注册信息')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证用户名格式
|
|
||||||
if (!/^[a-zA-Z0-9_]+$/.test(registerForm.username)) {
|
|
||||||
toast.error('用户名只能包含字母、数字和下划线')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证用户名长度
|
|
||||||
if (registerForm.username.length < 3 || registerForm.username.length > 30) {
|
|
||||||
toast.error('用户名长度必须在3-30个字符之间')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证密码长度
|
|
||||||
if (registerForm.password.length < 8) {
|
|
||||||
toast.error('密码长度至少8个字符')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (registerForm.password !== registerForm.confirmPassword) {
|
|
||||||
toast.error('两次输入的密码不一致')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await authStore.register({
|
|
||||||
username: registerForm.username,
|
|
||||||
password: registerForm.password,
|
|
||||||
confirmPassword: registerForm.confirmPassword
|
|
||||||
})
|
|
||||||
|
|
||||||
successMessage.value = '注册成功!您的账户已创建,请等待管理员激活后即可登录。'
|
|
||||||
toast.success('注册成功!请等待管理员激活您的账户。')
|
|
||||||
|
|
||||||
// 注册成功时清空注册表单
|
|
||||||
registerForm.username = ''
|
|
||||||
registerForm.password = ''
|
|
||||||
registerForm.confirmPassword = ''
|
|
||||||
|
|
||||||
// 切换到登录模式
|
|
||||||
currentMode.value = 'login'
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('注册错误详情:', error)
|
|
||||||
console.error('错误响应:', error.response.data.error)
|
|
||||||
|
|
||||||
// 根据不同的错误类型显示不同的错误信息
|
|
||||||
if (error.response?.status === 400) {
|
|
||||||
const details = error.response?.data?.error
|
|
||||||
if (details) {
|
|
||||||
// 显示具体的验证错误信息
|
|
||||||
const errorMessages = details
|
|
||||||
toast.error(`注册失败: ${errorMessages}`)
|
|
||||||
}
|
|
||||||
} else if (error.response?.status === 409) {
|
|
||||||
toast.error('用户名已存在,请使用其他信息')
|
|
||||||
} else if (error.response?.status === 500) {
|
|
||||||
toast.error('服务器内部错误,请稍后重试')
|
|
||||||
} else if (error.code === 'NETWORK_ERROR') {
|
|
||||||
toast.error('网络连接失败,请检查网络连接')
|
|
||||||
} else {
|
|
||||||
toast.error(error.response?.data?.message || '注册失败,请稍后重试')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 忘记密码处理函数
|
// 忘记密码处理函数
|
||||||
const handleForgotPassword = () => {
|
const handleForgotPassword = () => {
|
||||||
toast.info('请联系管理员')
|
toast.info('请联系管理员')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面加载时检查是否有记住的登录信息
|
|
||||||
const loadRememberedUser = () => {
|
|
||||||
const remembered = localStorage.getItem('rememberedUser')
|
|
||||||
if (remembered) {
|
|
||||||
try {
|
|
||||||
const userData = JSON.parse(remembered)
|
|
||||||
loginForm.username = userData.username || ''
|
|
||||||
loginForm.rememberMe = true
|
|
||||||
} catch (error) {
|
|
||||||
console.error('解析记住的用户信息失败:', error)
|
|
||||||
localStorage.removeItem('rememberedUser')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面挂载时初始化
|
|
||||||
onMounted(() => {
|
|
||||||
loadRememberedUser()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
Reference in New Issue
Block a user