Files
schoolNews/schoolNewsWeb/src/views/public/login/Login.vue
2025-10-27 17:29:25 +08:00

478 lines
10 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-container">
<!-- 左侧励志区域 -->
<div class="login-left">
<div class="left-content">
<div class="quote-text">
<span class="quote-mark"></span>
<div class="quote-content">
<p>不负时代韶华</p>
<p>争做时代新人</p>
</div>
</div>
</div>
</div>
<!-- 右侧登录表单区域 -->
<div class="login-right">
<div class="login-form-container">
<!-- Logo和标题区域 -->
<div class="login-header">
<div class="logo-section">
<div class="logo">
<img src="@/assets/imgs/logo-icon.svg" alt="Logo" />
</div>
<h1 class="platform-title">红色思政学习平台</h1>
</div>
<h2 class="login-title">账号登录</h2>
</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="请输入学号、手机号"
class="form-input"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="请输入密码"
show-password
class="form-input"
/>
</el-form-item>
<el-form-item>
<div class="login-options">
<div class="remember-me">
<el-checkbox v-model="loginForm.rememberMe">
自动登录
</el-checkbox>
</div>
<el-link type="primary" @click="goToForgotPassword" class="forgot-password">
忘记密码
</el-link>
</div>
</el-form-item>
<el-form-item>
<el-button
type="primary"
:loading="loginLoading"
@click="handleLogin"
class="login-button"
>
登录
</el-button>
<p class="agreement-text">
<el-checkbox v-model="loginForm.rememberMe">登录即为同意<span class="agreement-link" style="color: red">红色思政智能体平台</span></el-checkbox>
</p>
</el-form-item>
</el-form>
</div>
<!-- 底部信息 -->
<div class="login-footer">
<p class="register-link">
没有账号<span class="register-link-text" @click="goToRegister">现在就注册</span>
</p>
<p class="copyright">
Copyright ©红色思政智能体平台
</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { useStore } from 'vuex';
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
import type { LoginParam } from '@/types';
import { authApi } from '@/apis/system/auth';
// 响应式引用
const loginFormRef = ref<FormInstance>();
const loginLoading = ref(false);
const showCaptcha = ref(false);
const captchaImage = ref('');
// Composition API
const router = useRouter();
const route = useRoute();
const store = useStore();
// 表单数据
const loginForm = reactive<LoginParam>({
username: '',
password: '',
captcha: '',
captchaId: '',
rememberMe: false
});
// 表单验证规则
const loginRules: FormRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '用户名长度为3-20个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码至少6个字符', trigger: 'blur' }
],
captcha: [
{ required: true, message: '请输入验证码', trigger: 'blur' },
{ len: 4, message: '验证码为4位', trigger: 'blur' }
]
};
// 方法
const handleLogin = async () => {
if (!loginFormRef.value) return;
try {
const valid = await loginFormRef.value.validate();
if (!valid) return;
loginLoading.value = true;
// 调用store中的登录action
const result = await store.dispatch('auth/login', loginForm);
ElMessage.success('登录成功!');
// 优先使用 query 中的 redirect其次使用返回的 redirectUrl最后使用默认首页
const redirectPath = (route.query.redirect as string) || result.redirectUrl || '/home';
router.push(redirectPath);
} catch (error: any) {
console.error('登录失败:', error);
ElMessage.error(error.message || '登录失败,请检查用户名和密码');
// 登录失败后显示验证码
showCaptcha.value = true;
refreshCaptcha();
} finally {
loginLoading.value = false;
}
};
const refreshCaptcha = async () => {
try {
const result = await authApi.getCaptcha();
if (result.data) {
captchaImage.value = result.data.captchaImage;
loginForm.captchaId = result.data.captchaId;
}
} catch (error) {
console.error('获取验证码失败:', error);
ElMessage.error('获取验证码失败');
}
};
function goToRegister() {
router.push('/register');
}
function goToForgotPassword() {
router.push('/forgot-password');
}
// 组件挂载时检查是否需要显示验证码
onMounted(() => {
// 可以根据需要决定是否默认显示验证码
// refreshCaptcha();
});
</script>
<style lang="scss" scoped>
.login-container {
display: flex;
width: 100%;
height: 80%;
max-width: 1142px;
margin: auto auto;
box-shadow: 0px 4px 30px 0px rgba(176, 196, 225, 0.25);
border-radius: 30px;
overflow: hidden;
justify-content: center;
align-items: center;
}
.login-left {
height: 100%;
flex: 1;
display: flex;
/* min-height: 671px; */
padding: 113px 120px;
border-radius: 30px 0 0 30px;
overflow: hidden;
background: url(/schoolNewsWeb/src/assets/imgs/login-bg.png);
background-size: cover;
background-position: center;
background-repeat: no-repeat;
.left-content {
width: 100%;
max-width: 350px;
}
.quote-text {
color: #FFF2D3;
.quote-mark {
font-family: 'Arial Black', sans-serif;
font-weight: 900;
font-size: 71.096px;
line-height: 0.74;
display: block;
margin-left: 1.48px;
}
.quote-content {
margin-top: 46.66px;
p {
font-family: 'Taipei Sans TC Beta', 'PingFang SC', sans-serif;
font-weight: 700;
font-size: 50px;
line-height: 1.42;
margin: 0;
}
}
}
}
.login-right {
flex: 1;
background: #FFFFFF;
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
border-radius: 0 30px 30px 0;
padding: 40px 0;
}
.login-form-container {
width: 287px;
padding: 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.login-header {
margin-bottom: 24px;
.logo-section {
display: flex;
align-items: center;
gap: 11px;
margin-bottom: 16px;
.logo {
width: 36px;
height: 36px;
background: #C62828;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
padding: 3px;
img {
width: 30px;
height: 30px;
}
}
.platform-title {
font-family: 'Taipei Sans TC Beta', sans-serif;
font-weight: 700;
font-size: 26px;
line-height: 1.31;
color: #141F38;
margin: 0;
}
}
.login-title {
font-family: 'PingFang SC', sans-serif;
font-weight: 600;
font-size: 16px;
line-height: 1.5;
color: #141F38;
margin: 0;
}
}
.login-form {
.form-input {
width: 100%;
height: 48px;
background: #F2F3F5;
border: none;
border-radius: 12px;
font-size: 14px;
color: rgba(0, 0, 0, 0.3);
&::placeholder {
color: rgba(0, 0, 0, 0.3);
}
&:focus {
outline: none;
background: #FFFFFF;
border: 1px solid #C62828;
}
}
.login-options {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
margin-bottom: 16px;
.remember-me {
display: flex;
align-items: center;
gap: 4px;
.el-checkbox {
font-size: 12px;
color: rgba(0, 0, 0, 0.3);
.el-checkbox__label {
font-size: 12px;
line-height: 1.67;
}
}
}
.forgot-password {
font-size: 12px;
color: rgba(0, 0, 0, 0.3);
text-decoration: none;
&:hover {
color: #C62828;
}
}
}
.login-button {
width: 100%;
height: 46px;
background: #C62828;
border: none;
border-radius: 12px;
color: #FFFFFF;
font-family: 'PingFang SC', sans-serif;
font-weight: 500;
font-size: 16px;
line-height: 1.4;
margin-bottom: 8px;
&:hover {
background: #B71C1C;
}
}
.agreement-text {
font-family: 'PingFang SC', sans-serif;
font-weight: 400;
font-size: 10px;
line-height: 1.8;
color: rgba(0, 0, 0, 0.3);
text-align: center;
margin: 0;
}
}
.login-footer {
width: 100%;
.register-link {
font-family: 'PingFang SC', sans-serif;
font-weight: 600;
font-size: 12px;
line-height: 1.83;
color: #141F38;
text-align: center;
margin: 0 0 16px 0;
.register-link-text {
cursor: pointer;
color: #ff0000;
&:hover {
color: #C62828;
}
}
}
.copyright {
font-family: 'PingFang SC', sans-serif;
font-size: 12px;
line-height: 2;
color: #D9D9D9;
text-align: center;
padding-bottom: 0;
}
}
// 响应式设计
@media (max-width: 768px) {
.login-container {
flex-direction: column;
border-radius: 0;
min-height: 100vh;
}
.login-left {
min-height: 300px;
padding: 40px;
.left-content {
max-width: 100%;
}
.quote-text {
.quote-mark {
font-size: 50px;
}
.quote-content p {
font-size: 36px;
}
}
}
.login-right {
min-height: auto;
padding: 20px;
}
.login-form-container {
width: 100%;
max-width: 400px;
}
}
</style>