422 lines
9.1 KiB
Vue
422 lines
9.1 KiB
Vue
<template>
|
|
<div class="login-form">
|
|
<div>注册</div>
|
|
<form class="login-form-container" @submit.prevent="handleRegister">
|
|
<div class="input-container">
|
|
<label for="username">QQ号</label>
|
|
<input type="text" id="username" v-model="username" placeholder="请输入QQ号" @blur="validateUsername"
|
|
:class="{ 'error': usernameError }" />
|
|
<span class="error-message" v-if="usernameError">{{ usernameError }}</span>
|
|
</div>
|
|
<div class="input-container">
|
|
<label for="password">密码</label>
|
|
<input type="password" id="password" v-model="password" placeholder="请输入密码" @blur="validatePassword"
|
|
:class="{ 'error': passwordError }" />
|
|
<span class="error-message" v-if="passwordError">{{ passwordError }}</span>
|
|
</div>
|
|
<div class="input-container">
|
|
<label for="confirmPassword">再次输入密码</label>
|
|
<input type="password" id="confirmPassword" v-model="confirmPassword" placeholder="请输入密码"
|
|
@blur="validateConfirmPassword" :class="{ 'error': confirmPasswordError }" />
|
|
<span class="error-message" v-if="confirmPasswordError">{{ confirmPasswordError }}</span>
|
|
</div>
|
|
<div class="input-container captcha-container">
|
|
<label for="captcha">验证码</label>
|
|
<div class="captcha-wrapper">
|
|
<input
|
|
type="text"
|
|
id="captcha"
|
|
v-model="captcha"
|
|
placeholder="请输入验证码"
|
|
@blur="validateCaptcha"
|
|
:class="{ 'error': captchaError }"
|
|
/>
|
|
<img
|
|
v-if="captchaImage"
|
|
:src="captchaImage"
|
|
alt="验证码"
|
|
class="captcha-image"
|
|
@click="refreshCaptcha"
|
|
/>
|
|
</div>
|
|
<span class="error-message" v-if="captchaError">{{ captchaError }}</span>
|
|
</div>
|
|
<div class="login-button">
|
|
<button type="submit" :disabled="!isFormValid">注册</button>
|
|
</div>
|
|
<div class="register-link">
|
|
<a @click.prevent="$emit('login')">返回登陆</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, computed } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { userRegister, getCaptcha } from "@/api/login.js";
|
|
|
|
const router = useRouter()
|
|
const username = ref('')
|
|
const password = ref('')
|
|
const confirmPassword = ref('')
|
|
const captcha = ref('')
|
|
const captchaImage = ref('')
|
|
const captchaToken = ref('')
|
|
|
|
// 定义 emit
|
|
const emit = defineEmits(['login'])
|
|
|
|
// 错误信息
|
|
const usernameError = ref('')
|
|
const passwordError = ref('')
|
|
const confirmPasswordError = ref('')
|
|
const captchaError = ref('')
|
|
|
|
// 表单验证规则
|
|
const validateUsername = () => {
|
|
if (!username.value) {
|
|
usernameError.value = '请输入QQ号码'
|
|
return false
|
|
}
|
|
// 只允许纯数字
|
|
if (!/^\d+$/.test(username.value)) {
|
|
usernameError.value = 'QQ号只能包含数字'
|
|
return false
|
|
}
|
|
usernameError.value = ''
|
|
return true
|
|
}
|
|
|
|
const validatePassword = () => {
|
|
if (!password.value) {
|
|
passwordError.value = '请输入密码'
|
|
return false
|
|
}
|
|
if (password.value.length < 6) {
|
|
passwordError.value = '密码长度不能小于6个字符'
|
|
return false
|
|
}
|
|
passwordError.value = ''
|
|
return true
|
|
}
|
|
|
|
const validateConfirmPassword = () => {
|
|
if (!confirmPassword.value) {
|
|
confirmPasswordError.value = '请再次输入密码'
|
|
return false
|
|
}
|
|
if (confirmPassword.value !== password.value) {
|
|
confirmPasswordError.value = '两次输入的密码不一致'
|
|
return false
|
|
}
|
|
confirmPasswordError.value = ''
|
|
return true
|
|
}
|
|
|
|
const validateCaptcha = () => {
|
|
if (!captcha.value) {
|
|
captchaError.value = '请输入验证码'
|
|
return false
|
|
}
|
|
if (captcha.value.length !== 4) {
|
|
captchaError.value = '验证码长度不正确'
|
|
return false
|
|
}
|
|
captchaError.value = ''
|
|
return true
|
|
}
|
|
|
|
// 计算表单是否有效
|
|
const isFormValid = computed(() => {
|
|
return !usernameError.value &&
|
|
!passwordError.value &&
|
|
!confirmPasswordError.value &&
|
|
!captchaError.value &&
|
|
username.value &&
|
|
password.value &&
|
|
confirmPassword.value &&
|
|
captcha.value
|
|
})
|
|
|
|
const refreshCaptcha = async () => {
|
|
try {
|
|
const response = await getCaptcha()
|
|
captchaImage.value = `data:image/png;base64,${response.img}`
|
|
captchaToken.value = response.token
|
|
captcha.value = '' // 清空验证码输入
|
|
} catch (error) {
|
|
console.error('获取验证码失败:', error)
|
|
alert('获取验证码失败,请刷新页面重试')
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
refreshCaptcha()
|
|
})
|
|
|
|
const handleRegister = async () => {
|
|
try {
|
|
// 验证表单
|
|
if (!validateUsername() || !validatePassword() || !validateConfirmPassword() || !validateCaptcha()) {
|
|
return
|
|
}
|
|
|
|
// 发送注册请求
|
|
await userRegister(
|
|
username.value,
|
|
password.value,
|
|
captchaToken.value,
|
|
captcha.value
|
|
)
|
|
|
|
alert('注册成功')
|
|
// 切换到登录模块
|
|
emit('login')
|
|
} catch (error) {
|
|
console.error('注册失败:', error)
|
|
if (error.response && error.response.status === 400) {
|
|
alert('该QQ号已被注册')
|
|
} else {
|
|
alert(error.response?.data?.message || error.message || '注册失败,请稍后重试')
|
|
}
|
|
// 注册失败时刷新验证码
|
|
refreshCaptcha()
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.login-form {
|
|
width: 340px;
|
|
margin: 0 auto;
|
|
background: rgba(255, 255, 255, 0.95);
|
|
border-radius: 16px;
|
|
box-shadow: 0 4px 32px rgba(0, 0, 0, 0.10);
|
|
padding: 36px 32px 28px 32px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
|
|
.login-form>div:first-child {
|
|
font-size: 26px;
|
|
font-weight: bold;
|
|
color: #222;
|
|
margin-bottom: 28px;
|
|
letter-spacing: 2px;
|
|
}
|
|
|
|
.login-form-container {
|
|
width: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 18px;
|
|
}
|
|
|
|
.input-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
}
|
|
|
|
.input-container label {
|
|
font-size: 14px;
|
|
color: #666;
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.input-container input {
|
|
height: 40px;
|
|
border: 1px solid #d0d7de;
|
|
border-radius: 6px;
|
|
padding: 0 12px;
|
|
font-size: 15px;
|
|
background: #f7fbfd;
|
|
transition: border 0.2s;
|
|
}
|
|
|
|
.input-container input:focus {
|
|
border: 1.5px solid #409eff;
|
|
outline: none;
|
|
background: #fff;
|
|
}
|
|
|
|
.input-container input.error {
|
|
border-color: #f56c6c;
|
|
}
|
|
|
|
.error-message {
|
|
color: #f56c6c;
|
|
font-size: 12px;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.login-button {
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.login-button button {
|
|
width: 100%;
|
|
height: 42px;
|
|
background: linear-gradient(90deg, #409eff 0%, #6dd5fa 100%);
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: 6px;
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.10);
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.login-button button:hover:not(:disabled) {
|
|
background: linear-gradient(90deg, #66b1ff 0%, #6dd5fa 100%);
|
|
}
|
|
|
|
.login-button button:disabled {
|
|
background: #a0cfff;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.register-link {
|
|
margin-top: 12px;
|
|
text-align: right;
|
|
}
|
|
|
|
.register-link a {
|
|
color: #409eff;
|
|
font-size: 14px;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
transition: color 0.2s;
|
|
}
|
|
|
|
.register-link a:hover {
|
|
color: #1a73e8;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
/* VAPTCHA 相关样式 */
|
|
.VAPTCHA-init-main {
|
|
display: table;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: #f7fbfd;
|
|
border-radius: 6px;
|
|
border: 1px solid #d0d7de;
|
|
}
|
|
|
|
.VAPTCHA-init-loading {
|
|
display: table-cell;
|
|
vertical-align: middle;
|
|
text-align: center;
|
|
}
|
|
|
|
.VAPTCHA-init-loading>a {
|
|
display: inline-block;
|
|
width: 18px;
|
|
height: 18px;
|
|
border: none;
|
|
}
|
|
|
|
.VAPTCHA-init-loading .VAPTCHA-text {
|
|
font-family: sans-serif;
|
|
font-size: 12px;
|
|
color: #666;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.captcha-container {
|
|
margin-bottom: 16px;
|
|
width: 100%;
|
|
}
|
|
|
|
.captcha-wrapper {
|
|
display: flex;
|
|
gap: 8px;
|
|
align-items: center;
|
|
width: 100%;
|
|
position: relative;
|
|
}
|
|
|
|
.captcha-wrapper input {
|
|
flex: 1;
|
|
height: 40px;
|
|
border: 1px solid #d0d7de;
|
|
border-radius: 6px;
|
|
padding: 0 12px;
|
|
font-size: 15px;
|
|
background: #f7fbfd;
|
|
transition: border 0.2s;
|
|
width: calc(100% - 120px); /* 减去验证码图片的宽度和间距 */
|
|
}
|
|
|
|
.captcha-wrapper input:focus {
|
|
border: 1.5px solid #409eff;
|
|
outline: none;
|
|
background: #fff;
|
|
}
|
|
|
|
.captcha-image {
|
|
width: 110px;
|
|
height: 40px;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
object-fit: cover;
|
|
border: 1px solid #d0d7de;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.captcha-image:hover {
|
|
opacity: 0.8;
|
|
}
|
|
|
|
/* 移动端适配 */
|
|
@media screen and (max-width: 480px) {
|
|
.login-form {
|
|
width: 90%;
|
|
max-width: 340px;
|
|
padding: 24px 20px 20px 20px;
|
|
}
|
|
|
|
.captcha-wrapper {
|
|
gap: 6px;
|
|
}
|
|
|
|
.captcha-wrapper input {
|
|
width: calc(100% - 100px);
|
|
}
|
|
|
|
.captcha-image {
|
|
width: 90px;
|
|
}
|
|
|
|
.input-container input,
|
|
.captcha-wrapper input {
|
|
height: 38px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.login-button button {
|
|
height: 40px;
|
|
font-size: 15px;
|
|
}
|
|
}
|
|
|
|
/* 超小屏幕适配 */
|
|
@media screen and (max-width: 320px) {
|
|
.login-form {
|
|
padding: 20px 16px 16px 16px;
|
|
}
|
|
|
|
.captcha-wrapper input {
|
|
width: calc(100% - 90px);
|
|
padding: 0 8px;
|
|
}
|
|
|
|
.captcha-image {
|
|
width: 80px;
|
|
}
|
|
}
|
|
</style> |