Files
schoolNews/schoolNewsWeb/src/views/public/login/Register.vue

811 lines
20 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="register-container">
<!-- 左侧励志区域 -->
<div class="register-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="register-right">
<div class="register-form-container">
<!-- Logo和标题区域 -->
<div class="register-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="register-title">{{ registerTypeTitle }}</h2>
<!-- 注册方式切换 -->
<div class="register-type-tabs">
<div
class="tab-item"
:class="{ active: registerType === RegisterType.USERNAME }"
@click="switchRegisterType(RegisterType.USERNAME)"
>
用户名
</div>
<div
class="tab-item"
:class="{ active: registerType === RegisterType.PHONE }"
@click="switchRegisterType(RegisterType.PHONE)"
>
手机号
</div>
<div
class="tab-item"
:class="{ active: registerType === RegisterType.EMAIL }"
@click="switchRegisterType(RegisterType.EMAIL)"
>
邮箱
</div>
</div>
</div>
<!-- 注册表单 -->
<el-form
ref="registerFormRef"
:model="registerForm"
:rules="registerRules"
class="register-form"
@submit.prevent="handleRegister"
>
<!-- 用户名注册 -->
<template v-if="registerType === RegisterType.USERNAME">
<el-form-item prop="username">
<el-input
v-model="registerForm.username"
placeholder="请输入用户名"
class="form-input"
/>
</el-form-item>
</template>
<!-- 手机号注册 -->
<template v-if="registerType === RegisterType.PHONE">
<el-form-item prop="phone">
<el-input
v-model="registerForm.phone"
placeholder="请输入手机号"
class="form-input"
/>
</el-form-item>
<el-form-item prop="smsCode">
<div class="phone-input-wrapper">
<el-input
v-model="registerForm.smsCode"
placeholder="请输入手机验证码"
class="form-input phone-input"
/>
<el-button
:disabled="smsCountdown > 0"
@click="handleSendSmsCode"
class="code-button"
>
{{ smsCountdown > 0 ? `${smsCountdown}s` : '获取验证码' }}
</el-button>
</div>
</el-form-item>
</template>
<!-- 邮箱注册 -->
<template v-if="registerType === RegisterType.EMAIL">
<el-form-item prop="email">
<el-input
v-model="registerForm.email"
placeholder="请输入邮箱"
class="form-input"
/>
</el-form-item>
<el-form-item prop="emailCode">
<div class="phone-input-wrapper">
<el-input
v-model="registerForm.emailCode"
placeholder="请输入邮箱验证码"
class="form-input phone-input"
/>
<el-button
:disabled="emailCountdown > 0"
@click="handleSendEmailCode"
class="code-button"
>
{{ emailCountdown > 0 ? `${emailCountdown}s` : '获取验证码' }}
</el-button>
</div>
</el-form-item>
</template>
<!-- 通用字段 -->
<!-- <el-form-item prop="studentId">
<el-input
v-model="registerForm.studentId"
placeholder="请输入学号"
class="form-input"
/>
</el-form-item> -->
<el-form-item prop="password">
<el-input
v-model="registerForm.password"
type="password"
placeholder="请输入密码至少6个字符"
show-password
class="form-input"
/>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input
v-model="registerForm.confirmPassword"
type="password"
placeholder="请再次输入密码"
show-password
class="form-input"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
:loading="registerLoading"
@click="handleRegister"
class="register-button"
>
注册
</el-button>
</el-form-item>
<el-form-item prop="agree">
<p class="agreement-text">
<el-checkbox v-model="registerForm.agree">注册即为同意<span class="agreement-link" style="color: red">红色思政智能体平台</span></el-checkbox>
</p>
</el-form-item>
</el-form>
</div>
<!-- 底部信息 -->
<div class="register-footer">
<p class="login-link">
已有账号<span class="login-link-text" @click="goToLogin">立即登录</span>
</p>
<p class="copyright">
Copyright ©红色思政智能体平台
</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
import type { RegisterParam } from '@/types';
import { RegisterType } from '@/types';
import { authApi } from '@/apis/system/auth';
// 响应式引用
const registerFormRef = ref<FormInstance>();
const registerLoading = ref(false);
const smsCountdown = ref(0);
const emailCountdown = ref(0);
let smsTimer: number | null = null;
let emailTimer: number | null = null;
// Composition API
const router = useRouter();
// 注册方式
const registerType = ref<RegisterType>(RegisterType.USERNAME);
// 表单数据
const registerForm = reactive<RegisterParam>({
registerType: RegisterType.USERNAME,
username: '',
studentId: '',
phone: '',
email: '',
password: '',
confirmPassword: '',
smsCode: '',
emailCode: '',
smsSessionId: '',
emailSessionId: '',
agree: true
});
// 根据注册方式显示不同的标题
const registerTypeTitle = computed(() => {
switch (registerType.value) {
case RegisterType.USERNAME:
return '用户名注册';
case RegisterType.PHONE:
return '手机号注册';
case RegisterType.EMAIL:
return '邮箱注册';
default:
return '账号注册';
}
});
// 切换注册方式
const switchRegisterType = (type: RegisterType) => {
registerType.value = type;
registerForm.registerType = type;
// 清空表单验证
registerFormRef.value?.clearValidate();
};
// 自定义验证器
const validatePass = (rule: any, value: string, callback: any) => {
if (value === '') {
callback(new Error('请输入密码'));
} else if (value.length < 6) {
callback(new Error('密码至少6个字符'));
} else {
if (registerForm.confirmPassword !== '') {
registerFormRef.value?.validateField('confirmPassword');
}
callback();
}
};
const validateConfirmPass = (rule: any, value: string, callback: any) => {
if (value === '') {
callback(new Error('请再次输入密码'));
} else if (value !== registerForm.password) {
callback(new Error('两次输入的密码不一致'));
} else {
callback();
}
};
const validatePhone = (rule: any, value: string, callback: any) => {
if (registerType.value === RegisterType.PHONE) {
if (value === '') {
callback(new Error('请输入手机号'));
} else if (!/^1[3-9]\d{9}$/.test(value)) {
callback(new Error('请输入有效的手机号'));
} else {
callback();
}
} else {
callback();
}
};
const validateEmail = (rule: any, value: string, callback: any) => {
if (registerType.value === RegisterType.EMAIL) {
if (value === '') {
callback(new Error('请输入邮箱'));
} else if (!/^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/.test(value)) {
callback(new Error('请输入有效的邮箱地址'));
} else {
callback();
}
} else {
callback();
}
};
const validateUsername = (rule: any, value: string, callback: any) => {
if (registerType.value === RegisterType.USERNAME) {
if (value === '') {
callback(new Error('请输入用户名'));
} else if (value.length < 3 || value.length > 20) {
callback(new Error('用户名长度为3-20个字符'));
} else if (!/^[a-zA-Z0-9_]+$/.test(value)) {
callback(new Error('用户名只能包含字母、数字和下划线'));
} else {
callback();
}
} else {
callback();
}
};
// 表单验证规则
const registerRules: FormRules = {
username: [
{ validator: validateUsername, trigger: 'blur' }
],
studentId: [
{ required: true, message: '请输入学号', trigger: 'blur' },
{ min: 5, max: 20, message: '学号长度为5-20个字符', trigger: 'blur' }
],
phone: [
{ validator: validatePhone, trigger: 'blur' }
],
email: [
{ validator: validateEmail, trigger: 'blur' }
],
smsCode: [
{
validator: (rule: any, value: string, callback: any) => {
if (registerType.value === RegisterType.PHONE) {
if (value === '') {
callback(new Error('请输入手机验证码'));
} else if (value.length !== 6) {
callback(new Error('验证码为6位'));
} else {
callback();
}
} else {
callback();
}
},
trigger: 'blur'
}
],
emailCode: [
{
validator: (rule: any, value: string, callback: any) => {
if (registerType.value === RegisterType.EMAIL) {
if (value === '') {
callback(new Error('请输入邮箱验证码'));
} else if (value.length !== 6) {
callback(new Error('验证码为6位'));
} else {
callback();
}
} else {
callback();
}
},
trigger: 'blur'
}
],
password: [
{ required: true, validator: validatePass, trigger: 'blur' }
],
confirmPassword: [
{ required: true, validator: validateConfirmPass, trigger: 'blur' }
],
agree: [
{
validator: (rule: any, value: boolean, callback: any) => {
if (!value) {
callback(new Error('请同意《红色思政智能体平台》用户协议'));
} else {
callback();
}
},
trigger: 'change'
}
]
};
// 发送手机验证码
const handleSendSmsCode = async () => {
// 先验证手机号
try {
await registerFormRef.value?.validateField('phone');
} catch (error) {
return;
}
try {
const result = await authApi.sendSmsCode(registerForm.phone!);
if (result.code === 200 && result.data) {
// 保存sessionId
registerForm.smsSessionId = result.data.sessionId;
ElMessage.success(result.data.message || '验证码已发送');
// 开始倒计时
smsCountdown.value = 60;
smsTimer = window.setInterval(() => {
smsCountdown.value--;
if (smsCountdown.value <= 0 && smsTimer) {
clearInterval(smsTimer);
smsTimer = null;
}
}, 1000);
} else {
ElMessage.error(result.message || '发送验证码失败');
}
} catch (error: any) {
console.error('发送验证码失败:', error);
ElMessage.error(error.message || '发送验证码失败');
}
};
// 发送邮箱验证码
const handleSendEmailCode = async () => {
// 先验证邮箱
try {
await registerFormRef.value?.validateField('email');
} catch (error) {
return;
}
try {
const result = await authApi.sendEmailCode(registerForm.email!);
if (result.code === 200 && result.data) {
// 保存sessionId
registerForm.emailSessionId = result.data.sessionId;
ElMessage.success(result.data.message || '验证码已发送到邮箱');
// 开始倒计时
emailCountdown.value = 60;
emailTimer = window.setInterval(() => {
emailCountdown.value--;
if (emailCountdown.value <= 0 && emailTimer) {
clearInterval(emailTimer);
emailTimer = null;
}
}, 1000);
} else {
ElMessage.error(result.message || '发送验证码失败');
}
} catch (error: any) {
console.error('发送验证码失败:', error);
ElMessage.error(error.message || '发送验证码失败');
}
};
// 注册处理
const handleRegister = async () => {
if (!registerFormRef.value) return;
// 检查是否同意用户协议
if (!registerForm.agree) {
ElMessage.warning('请先同意《红色思政智能体平台》用户协议');
return;
}
try {
const valid = await registerFormRef.value.validate();
if (!valid) return;
registerLoading.value = true;
// 调用注册API
const result = await authApi.register(registerForm);
if (result.code === 200) {
ElMessage.success('注册成功!请登录');
// 注册成功后跳转到登录页
setTimeout(() => {
router.push('/login');
}, 1500);
} else {
ElMessage.error(result.message || '注册失败,请重试');
}
} catch (error: any) {
console.error('注册失败:', error);
ElMessage.error(error.message || '注册失败,请重试');
} finally {
registerLoading.value = false;
}
};
function goToLogin() {
router.push('/login');
}
// 组件卸载时清除定时器
import { onUnmounted } from 'vue';
onUnmounted(() => {
if (smsTimer) {
clearInterval(smsTimer);
}
if (emailTimer) {
clearInterval(emailTimer);
}
});
</script>
<style lang="scss" scoped>
.register-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;
}
.register-left {
height: 100%;
flex: 1;
display: flex;
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;
}
}
}
}
.register-right {
flex: 1;
background: #FFFFFF;
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
border-radius: 0 30px 30px 0;
padding: 40px 0;
}
.register-form-container {
width: 287px;
padding: 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.register-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;
}
}
.register-title {
font-family: 'PingFang SC', sans-serif;
font-weight: 600;
font-size: 16px;
line-height: 1.5;
color: #141F38;
margin: 0 0 16px 0;
}
.register-type-tabs {
display: flex;
gap: 8px;
.tab-item {
flex: 1;
padding: 8px 12px;
text-align: center;
background: #F2F3F5;
border-radius: 8px;
font-size: 14px;
color: rgba(0, 0, 0, 0.5);
cursor: pointer;
transition: all 0.3s;
&:hover {
background: #E8E9EB;
}
&.active {
background: #C62828;
color: #FFFFFF;
font-weight: 600;
}
}
}
}
.register-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;
}
}
.phone-input-wrapper {
display: flex;
gap: 8px;
width: 100%;
.phone-input {
flex: 1;
}
.code-button {
height: 48px;
padding: 0 16px;
background: #C62828;
border: none;
border-radius: 12px;
color: #FFFFFF;
font-size: 12px;
white-space: nowrap;
&:hover:not(:disabled) {
background: #B71C1C;
}
&:disabled {
background: #D9D9D9;
color: rgba(0, 0, 0, 0.3);
cursor: not-allowed;
}
}
}
.register-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;
}
}
.register-footer {
width: 100%;
.login-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;
.login-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) {
.register-container {
flex-direction: column;
border-radius: 0;
min-height: 100vh;
}
.register-left {
min-height: 300px;
padding: 40px;
.left-content {
max-width: 100%;
}
.quote-text {
.quote-mark {
font-size: 50px;
}
.quote-content p {
font-size: 36px;
}
}
}
.register-right {
min-height: auto;
padding: 20px;
}
.register-form-container {
width: 100%;
max-width: 400px;
}
}
</style>