Files
jurong_circle_frontdesk/src/views/Login.vue
2025-07-26 15:35:53 +08:00

455 lines
9.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="login-page">
<div class="login-container">
<div class="login-card">
<div class="login-header">
<h2>用户登录</h2>
<p>欢迎回到前端H5系统</p>
</div>
<el-form
ref="loginFormRef"
:model="loginForm"
:rules="loginRules"
class="login-form"
@submit.prevent="handleLogin"
>
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
placeholder="请输入用户名或邮箱"
size="large"
:prefix-icon="User"
clearable
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="请输入密码"
size="large"
:prefix-icon="Lock"
show-password
clearable
@keyup.enter="handleLogin"
/>
</el-form-item>
<el-form-item prop="captcha">
<Captcha
ref="captchaRef"
v-model="loginForm.captcha"
placeholder="请输入验证码"
size="large"
/>
</el-form-item>
<el-form-item>
<div class="form-options">
<el-checkbox v-model="rememberMe">记住我</el-checkbox>
<el-link type="primary" @click="showForgotPassword">
忘记密码
</el-link>
</div>
</el-form-item>
<el-form-item>
<el-button
type="primary"
size="large"
class="login-button"
:loading="userStore.loading"
@click="handleLogin"
>
{{ userStore.loading ? '登录中...' : '登录' }}
</el-button>
</el-form-item>
</el-form>
<div class="login-footer">
<p>
还没有账号
<el-link type="primary" @click="$router.push('/register')">
立即注册
</el-link>
</p>
</div>
<div class="quick-login">
<el-divider>快速登录</el-divider>
<div class="demo-accounts">
<el-button
type="info"
plain
size="small"
@click="quickLogin('admin')"
>
管理员账号
</el-button>
<el-button
type="success"
plain
size="small"
@click="quickLogin('user')"
>
普通用户
</el-button>
</div>
</div>
</div>
</div>
<!-- 背景装饰 -->
<div class="background-decoration">
<div class="decoration-circle circle-1"></div>
<div class="decoration-circle circle-2"></div>
<div class="decoration-circle circle-3"></div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { ElMessage, ElMessageBox } from 'element-plus'
import { User, Lock } from '@element-plus/icons-vue'
import Captcha from '@/components/Captcha.vue'
const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
// 表单引用
const loginFormRef = ref()
const captchaRef = ref()
// 表单数据
const loginForm = reactive({
username: '',
password: '',
captcha: ''
})
// 其他状态
const rememberMe = ref(false)
// 表单验证规则
const loginRules = {
username: [
{ required: true, message: '请输入用户名或邮箱', trigger: 'blur' },
{ min: 3, message: '用户名至少3个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码至少6个字符', trigger: 'blur' }
],
captcha: [
{ required: true, message: '请输入验证码', trigger: 'blur' },
{ min: 4, max: 4, message: '验证码为4位字符', trigger: 'blur' }
]
}
// 处理登录
const handleLogin = async () => {
if (!loginFormRef.value || !captchaRef.value) return
try {
// 先验证表单
const valid = await loginFormRef.value.validate()
if (!valid) return
// 验证验证码
const captchaValid = await captchaRef.value.verifyCaptcha(loginForm.captcha)
if (!captchaValid) {
loginForm.captcha = ''
return
}
// 获取验证码信息
const captchaInfo = captchaRef.value.getCaptchaInfo()
// 提交登录请求(包含验证码信息)
const loginData = {
username: loginForm.username,
password: loginForm.password,
captchaId: captchaInfo.captchaId,
captchaText: captchaInfo.captchaText
}
const result = await userStore.login(loginData)
if (result.success) {
// 登录成功,跳转到目标页面或转账管理
const redirectPath = route.query.redirect || '/transfers'
router.push(redirectPath)
}
} catch (error) {
console.error('登录失败:', error)
// 登录失败后刷新验证码
if (captchaRef.value) {
await captchaRef.value.refreshCaptcha()
}
loginForm.captcha = ''
}
}
// 快速登录(演示用)
const quickLogin = async (type) => {
if (type === 'admin') {
loginForm.username = 'admin'
loginForm.password = 'admin123'
} else {
loginForm.username = 'user'
loginForm.password = 'user123'
}
// 清空验证码,让用户手动输入
loginForm.captcha = ''
ElMessage.info('请输入验证码后登录')
}
// 忘记密码
const showForgotPassword = () => {
ElMessageBox.alert(
'请联系管理员重置密码,或使用演示账号进行体验。',
'忘记密码',
{
confirmButtonText: '确定',
type: 'info'
}
)
}
// 组件挂载时的处理
onMounted(() => {
// 如果已经登录,直接跳转
if (userStore.isAuthenticated) {
const redirectPath = route.query.redirect || '/transfers'
router.push(redirectPath)
}
// 从localStorage恢复记住我状态
const savedUsername = localStorage.getItem('rememberedUsername')
if (savedUsername) {
loginForm.username = savedUsername
rememberMe.value = true
}
})
// 监听记住我状态变化
const handleRememberMe = () => {
if (rememberMe.value && loginForm.username) {
localStorage.setItem('rememberedUsername', loginForm.username)
} else {
localStorage.removeItem('rememberedUsername')
}
}
</script>
<style scoped>
.login-page {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
.login-container {
width: 100%;
max-width: 400px;
padding: 20px;
position: relative;
z-index: 10;
}
.login-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 16px;
padding: 40px 30px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.login-header {
text-align: center;
margin-bottom: 30px;
}
.login-header h2 {
color: #303133;
margin-bottom: 8px;
font-weight: 600;
}
.login-header p {
color: #909399;
font-size: 14px;
}
.login-form {
margin-bottom: 20px;
}
.form-options {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.login-button {
width: 100%;
height: 44px;
font-size: 16px;
font-weight: 600;
}
.login-footer {
text-align: center;
margin-bottom: 20px;
}
.login-footer p {
color: #606266;
font-size: 14px;
}
.quick-login {
margin-top: 20px;
}
.demo-accounts {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 15px;
}
.background-decoration {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.decoration-circle {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
animation: float 6s ease-in-out infinite;
}
.circle-1 {
width: 200px;
height: 200px;
top: 10%;
left: 10%;
animation-delay: 0s;
}
.circle-2 {
width: 150px;
height: 150px;
top: 60%;
right: 10%;
animation-delay: 2s;
}
.circle-3 {
width: 100px;
height: 100px;
bottom: 20%;
left: 20%;
animation-delay: 4s;
}
@keyframes float {
0%, 100% {
transform: translateY(0px) rotate(0deg);
}
50% {
transform: translateY(-20px) rotate(180deg);
}
}
/* 响应式设计 */
@media (max-width: 480px) {
.login-container {
padding: 15px;
}
.login-card {
padding: 30px 20px;
}
.demo-accounts {
flex-direction: column;
}
.form-options {
flex-direction: column;
gap: 10px;
align-items: flex-start;
}
}
/* Element Plus 组件样式覆盖 */
:deep(.el-input__wrapper) {
border-radius: 8px;
}
:deep(.el-button) {
border-radius: 8px;
}
:deep(.el-divider__text) {
background-color: rgba(255, 255, 255, 0.95);
color: #909399;
}
/* 输入框聚焦效果 */
:deep(.el-input__wrapper:hover),
:deep(.el-input__wrapper.is-focus) {
box-shadow: 0 0 0 1px #409eff inset;
}
/* 加载状态样式 */
.login-button.is-loading {
pointer-events: none;
}
/* 动画效果 */
.login-card {
animation: slideInUp 0.6s ease-out;
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 错误状态样式 */
:deep(.el-form-item.is-error .el-input__wrapper) {
box-shadow: 0 0 0 1px #f56c6c inset;
}
/* 成功状态样式 */
:deep(.el-form-item.is-success .el-input__wrapper) {
box-shadow: 0 0 0 1px #67c23a inset;
}
</style>