Files
jurong_circle_frontdesk/src/views/MyLogin.vue
2025-09-08 16:12:56 +08:00

514 lines
12 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>欢迎来到炬融圈</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 class="icp-info">
<div class="icp-item">
<svg class="icp-icon" viewBox="0 0 1024 1024" width="16" height="16">
<path d="M512 85.333333c-23.466667 0-42.666667 19.2-42.666667 42.666667v85.333333c0 23.466667 19.2 42.666667 42.666667 42.666667s42.666667-19.2 42.666667-42.666667V128c0-23.466667-19.2-42.666667-42.666667-42.666667z" fill="#909399"/>
<path d="M512 256c-141.226667 0-256 114.773333-256 256 0 70.613333 28.586667 134.4 74.666667 180.48L512 874.666667l181.333333-182.186667C739.413333 646.4 768 582.613333 768 512c0-141.226667-114.773333-256-256-256z m0 341.333333c-47.146667 0-85.333333-38.186667-85.333333-85.333333s38.186667-85.333333 85.333333-85.333333 85.333333 38.186667 85.333333 85.333333-38.186667 85.333333-85.333333 85.333333z" fill="#909399"/>
<path d="M170.666667 298.666667c-11.733333-20.48-37.546667-27.306667-58.026667-15.573334-20.48 11.733333-27.306667 37.546667-15.573333 58.026667l42.666666 74.24c11.733333 20.48 37.546667 27.306667 58.026667 15.573333 20.48-11.733333 27.306667-37.546667 15.573333-58.026666l-42.666666-74.24z" fill="#909399"/>
<path d="M853.333333 298.666667l-42.666666 74.24c-11.733333 20.48-4.906667 46.293333 15.573333 58.026666 20.48 11.733333 46.293333 4.906667 58.026667-15.573333l42.666666-74.24c11.733333-20.48 4.906667-46.293333-15.573333-58.026667-20.48-11.733333-46.293333-4.906667-58.026667 15.573334z" fill="#909399"/>
</svg>
<a href="https://beian.miit.gov.cn/" target="_blank" class="icp-link">
浙ICP备2025186895号
</a>
</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
}
console.log('开始调用登录接口');
try {
const result = await userStore.login(loginData)
console.log('登录接口调用完成');
console.log(result,'result');
if (result.success) {
// 登录成功,跳转到目标页面或转账管理
const redirectPath = route.query.redirect || '/mainpage'
router.push(redirectPath)
} else if (result.needPayment) {
// 用户需要支付激活,直接跳转到支付页面
ElMessage.info('账户尚未激活,正在跳转到支付页面...')
router.push({
path: '/payment',
query: {
userId: result.userId,
from: 'login'
}
})
}
} catch (loginError) {
console.error('登录调用异常:', loginError)
// 如果是支付相关的错误,也要处理
if (loginError.needPayment) {
ElMessage.info('账户尚未激活,正在跳转到支付页面...')
router.push({
path: '/payment',
query: {
userId: loginError.userId,
from: 'login'
}
})
}
}
} catch (error) {
console.error('登录失败:', error)
// 登录失败后刷新验证码
if (captchaRef.value) {
await captchaRef.value.refreshCaptcha()
}
loginForm.captcha = ''
}
}
// 忘记密码
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>
/* 移除三个输入框背景 */
:deep(.el-input__wrapper) {
background: transparent !important;
border-radius: 8px;
}
/* 输入框聚焦及hover效果 */
:deep(.el-input__wrapper:hover),
:deep(.el-input__wrapper.is-focus) {
box-shadow: 0 0 0 1px #409eff inset;
}
/* 错误状态样式 */
: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;
}
/* 其他原有样式保持不变 */
.login-page {
min-height: 100vh;
background: linear-gradient(135deg, #abbaff 0%, #f3f3f3 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 {
backdrop-filter: blur(10px);
border-radius: 16px;
padding: 40px 30px;
}
.login-header {
text-align: center;
margin-bottom: 30px;
}
.login-header h2 {
color: #4784ff;
margin-bottom: 8px;
font-weight: 600;
}
.login-header p {
color: white;
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;
}
.icp-info {
text-align: center;
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid rgba(224, 224, 230, 0.3);
}
.icp-item {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.icp-icon {
flex-shrink: 0;
}
.icp-link {
color: #909399;
font-size: 12px;
text-decoration: none;
transition: color 0.3s ease;
}
.icp-link:hover {
color: #409eff;
text-decoration: underline;
}
.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-button) {
border-radius: 8px;
}
:deep(.el-divider__text) {
background-color: rgba(255, 255, 255, 0.95);
color: #909399;
}
/* 加载状态样式 */
.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);
}
}
</style>