10 changed files with 1046 additions and 1 deletions
@ -0,0 +1,182 @@ |
|||
from robyn import jsonify, Response |
|||
from app import app |
|||
import os |
|||
import uuid |
|||
from service.UserService import user_service |
|||
|
|||
# 头像上传目录 |
|||
AVATAR_UPLOAD_FOLDER = 'resource/avatar' |
|||
|
|||
@app.post("/api/register") |
|||
def register_route(request): |
|||
"""用户注册接口""" |
|||
try: |
|||
# 获取表单数据 |
|||
form_data = getattr(request, 'form_data', {}) if hasattr(request, 'form_data') else {} |
|||
|
|||
# 获取文件上传 |
|||
files = getattr(request, 'files', {}) if hasattr(request, 'files') else {} |
|||
|
|||
username = form_data.get("username", "").strip() |
|||
password = form_data.get("password", "").strip() |
|||
|
|||
# 验证必填字段 |
|||
if not username or not password: |
|||
return Response( |
|||
status_code=400, |
|||
description=jsonify({"success": False, "message": "用户名和密码不能为空"}), |
|||
headers={"Content-Type": "application/json; charset=utf-8"} |
|||
) |
|||
|
|||
# 检查用户名是否已存在 |
|||
existing_user = user_service.get_user_by_username(username) |
|||
if existing_user: |
|||
return Response( |
|||
status_code=409, |
|||
description=jsonify({"success": False, "message": "用户名已存在"}), |
|||
headers={"Content-Type": "application/json; charset=utf-8"} |
|||
) |
|||
|
|||
# 处理头像上传 |
|||
avatar_path = "/resource/avatar/4.png" # 默认头像 |
|||
|
|||
# 获取头像文件 |
|||
avatar_file = files.get('avatar') |
|||
|
|||
# 如果有头像文件,处理上传 |
|||
if avatar_file: |
|||
# 获取文件内容和文件名 |
|||
file_content = None |
|||
filename = "" |
|||
|
|||
# Robyn框架中,files字典的值通常是字节数据 |
|||
if isinstance(avatar_file, bytes): |
|||
file_content = avatar_file |
|||
filename = "avatar.jpg" |
|||
elif hasattr(avatar_file, 'content'): |
|||
file_content = avatar_file.content |
|||
filename = getattr(avatar_file, 'filename', 'avatar.jpg') |
|||
elif hasattr(avatar_file, 'read'): |
|||
file_content = avatar_file.read() |
|||
filename = getattr(avatar_file, 'filename', 'avatar.jpg') |
|||
else: |
|||
filename = avatar_file if isinstance(avatar_file, str) else 'avatar.jpg' |
|||
|
|||
# 如果我们有文件内容,继续处理 |
|||
if file_content: |
|||
# 处理文件名 |
|||
file_extension = filename.split('.')[-1] if '.' in filename else 'jpg' |
|||
|
|||
# 验证文件类型 |
|||
is_valid_image = False |
|||
|
|||
# 通过扩展名验证 |
|||
if file_extension.lower() in ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']: |
|||
is_valid_image = True |
|||
|
|||
# 如果扩展名验证失败,尝试读取文件头验证 |
|||
if not is_valid_image: |
|||
try: |
|||
if (file_content.startswith(b'\xFF\xD8\xFF') or # JPEG |
|||
file_content.startswith(b'\x89PNG\r\n\x1a\n') or # PNG |
|||
file_content.startswith(b'GIF87a') or # GIF |
|||
file_content.startswith(b'GIF89a')): # GIF |
|||
is_valid_image = True |
|||
except: |
|||
pass |
|||
|
|||
if not is_valid_image: |
|||
return Response( |
|||
status_code=400, |
|||
description=jsonify({"success": False, "message": "不支持的图片格式"}), |
|||
headers={"Content-Type": "application/json; charset=utf-8"} |
|||
) |
|||
|
|||
# 确保上传目录存在 |
|||
if not os.path.exists(AVATAR_UPLOAD_FOLDER): |
|||
os.makedirs(AVATAR_UPLOAD_FOLDER) |
|||
|
|||
# 生成唯一文件名 |
|||
unique_filename = f"{uuid.uuid4().hex}.{file_extension}" |
|||
file_path = os.path.join(AVATAR_UPLOAD_FOLDER, unique_filename) |
|||
|
|||
# 保存文件 |
|||
try: |
|||
with open(file_path, 'wb') as f: |
|||
f.write(file_content) |
|||
avatar_path = f"/{file_path}" |
|||
except Exception as e: |
|||
print(f"保存头像失败: {e}") |
|||
return Response( |
|||
status_code=500, |
|||
description=jsonify({"success": False, "message": "头像保存失败"}), |
|||
headers={"Content-Type": "application/json; charset=utf-8"} |
|||
) |
|||
|
|||
# 创建用户 |
|||
user_id = user_service.create_user(username, password, avatar_path) |
|||
if user_id: |
|||
return Response( |
|||
status_code=201, |
|||
description=jsonify({ |
|||
"success": True, |
|||
"message": "注册成功", |
|||
"user": { |
|||
"id": user_id, |
|||
"username": username, |
|||
"avatar": avatar_path |
|||
} |
|||
}), |
|||
headers={"Content-Type": "application/json; charset=utf-8"} |
|||
) |
|||
else: |
|||
return Response( |
|||
status_code=500, |
|||
description=jsonify({"success": False, "message": "注册失败,请稍后再试"}), |
|||
headers={"Content-Type": "application/json; charset=utf-8"} |
|||
) |
|||
except Exception as e: |
|||
print(f"注册异常: {e}") |
|||
return Response( |
|||
status_code=500, |
|||
description=jsonify({"success": False, "message": f"注册失败: {str(e)}"}), |
|||
headers={"Content-Type": "application/json; charset=utf-8"} |
|||
) |
|||
|
|||
@app.get("/api/checkUsername") |
|||
def check_username_route(request): |
|||
"""检查用户名是否可用""" |
|||
try: |
|||
# 获取查询参数 |
|||
username = "" |
|||
|
|||
# 尝试从query_params获取 |
|||
if hasattr(request, 'query_params') and request.query_params: |
|||
username = request.query_params.get("username", "").strip() |
|||
|
|||
if not username: |
|||
return Response( |
|||
status_code=400, |
|||
description=jsonify({"success": False, "message": "用户名不能为空"}), |
|||
headers={"Content-Type": "application/json; charset=utf-8"} |
|||
) |
|||
|
|||
# 检查用户名是否已存在 |
|||
existing_user = user_service.get_user_by_username(username) |
|||
|
|||
return Response( |
|||
status_code=200, |
|||
description=jsonify({ |
|||
"success": True, |
|||
"available": existing_user is None, |
|||
"message": "用户名可用" if not existing_user else "用户名已存在" |
|||
}), |
|||
headers={"Content-Type": "application/json; charset=utf-8"} |
|||
) |
|||
except Exception as e: |
|||
print(f"检查用户名异常: {e}") |
|||
return Response( |
|||
status_code=500, |
|||
description=jsonify({"success": False, "message": f"检查失败: {str(e)}"}), |
|||
headers={"Content-Type": "application/json; charset=utf-8"} |
|||
) |
|||
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 48 KiB |
@ -0,0 +1,33 @@ |
|||
// src/api/register.js
|
|||
import request from '@/utils/request'; |
|||
|
|||
/** |
|||
* 用户注册 |
|||
* 后端接口:POST /api/register |
|||
* @param {FormData} formData - 包含用户名、密码和头像的表单数据 |
|||
*/ |
|||
export function register(formData) { |
|||
return request({ |
|||
url: '/api/register', |
|||
method: 'post', |
|||
data: formData, |
|||
headers: { |
|||
'Content-Type': 'multipart/form-data' |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 检查用户名是否可用 |
|||
* 后端接口:GET /api/checkUsername |
|||
* @param {string} username - 要检查的用户名 |
|||
*/ |
|||
export function checkUsername(username) { |
|||
return request({ |
|||
url: '/api/checkUsername', |
|||
method: 'get', |
|||
params: { |
|||
username |
|||
} |
|||
}); |
|||
} |
|||
@ -0,0 +1,763 @@ |
|||
<template> |
|||
<div class="register-container"> |
|||
<!-- 左上角Logo和标题 --> |
|||
<div class="logo-header"> |
|||
<img src="@/assets/logo.png" alt="Logo" class="logo"> |
|||
<h1 class="register-title">面向疾病预测的知识图谱应用系统</h1> |
|||
</div> |
|||
|
|||
<!-- 左侧注册区域 --> |
|||
<div class="register-form-container"> |
|||
<div class="register-header"> |
|||
</div> |
|||
|
|||
<div class="register-form"> |
|||
<h2 class="form-title">注册</h2> |
|||
<p class="form-description">创建您的账户以访问系统功能。</p> |
|||
|
|||
<form class="form" @submit.prevent="handleRegister"> |
|||
<!-- 错误信息显示 --> |
|||
<div v-if="errorMessage" class="error-message"> |
|||
{{ errorMessage }} |
|||
</div> |
|||
|
|||
<!-- 头像上传区域 --> |
|||
<div class="avatar-upload-container"> |
|||
<div class="avatar-container"> |
|||
<img |
|||
:src="avatarPreview" |
|||
alt="用户头像" |
|||
class="avatar-image" |
|||
@error="handleAvatarError" |
|||
> |
|||
<div class="avatar-overlay" @click="triggerFileInput"> |
|||
<span class="camera-icon">📷</span> |
|||
<span class="overlay-text">上传头像</span> |
|||
</div> |
|||
<input |
|||
type="file" |
|||
ref="fileInput" |
|||
@change="handleAvatarChange" |
|||
accept="image/*" |
|||
class="hidden-input" |
|||
> |
|||
</div> |
|||
<div class="avatar-status"> |
|||
<p v-if="avatarUploading" class="uploading-text">上传中...</p> |
|||
<p v-if="avatarUploadError" class="error-text">{{ avatarUploadError }}</p> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<label for="username" class="form-label">用户名</label> |
|||
<input |
|||
type="text" |
|||
id="username" |
|||
v-model="registerForm.username" |
|||
placeholder="输入您的用户名" |
|||
class="form-input" |
|||
:class="{ 'error': errors.username }" |
|||
> |
|||
<p v-if="errors.username" class="error-text">{{ errors.username }}</p> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<label for="password" class="form-label">密码</label> |
|||
<input |
|||
type="password" |
|||
id="password" |
|||
v-model="registerForm.password" |
|||
placeholder="输入您的密码" |
|||
class="form-input" |
|||
:class="{ 'error': errors.password }" |
|||
> |
|||
<p v-if="errors.password" class="error-text">{{ errors.password }}</p> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<label for="confirmPassword" class="form-label">确认密码</label> |
|||
<input |
|||
type="password" |
|||
id="confirmPassword" |
|||
v-model="registerForm.confirmPassword" |
|||
placeholder="再次输入您的密码" |
|||
class="form-input" |
|||
:class="{ 'error': errors.confirmPassword }" |
|||
> |
|||
<p v-if="errors.confirmPassword" class="error-text">{{ errors.confirmPassword }}</p> |
|||
</div> |
|||
|
|||
<div class="form-checkbox"> |
|||
<input |
|||
type="checkbox" |
|||
id="agree" |
|||
v-model="registerForm.agree" |
|||
class="checkbox" |
|||
> |
|||
<label for="agree" class="checkbox-label">我同意服务条款和隐私政策</label> |
|||
</div> |
|||
|
|||
<button |
|||
type="submit" |
|||
class="register-button" |
|||
:disabled="loading || !isFormValid" |
|||
> |
|||
<span v-if="!loading">注册</span> |
|||
<span v-else>注册中...</span> |
|||
</button> |
|||
</form> |
|||
|
|||
<div class="social-login"> |
|||
<p class="social-text">使用其他方式注册</p> |
|||
</div> |
|||
|
|||
<div class="login-link"> |
|||
<p>已有账户? <a href="#" class="login" @click.prevent="goToLogin"> 立即登录</a></p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 右侧知识图谱可视化区域 --> |
|||
<div class="graph-container"> |
|||
<!-- 背景装饰 --> |
|||
<div class="background-decoration"> |
|||
<div class="bg-circle circle-1"></div> |
|||
<div class="bg-circle circle-2"></div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useRouter } from 'vue-router'; |
|||
import { register } from '@/api/register'; |
|||
|
|||
const router = useRouter(); |
|||
|
|||
// 注册表单数据 |
|||
const registerForm = ref({ |
|||
username: '', |
|||
password: '', |
|||
confirmPassword: '', |
|||
agree: false, |
|||
avatar: null |
|||
}); |
|||
|
|||
// 状态变量 |
|||
const fileInput = ref(null); |
|||
const avatarUploading = ref(false); |
|||
const avatarUploadError = ref(''); |
|||
const loading = ref(false); |
|||
const errorMessage = ref(''); |
|||
const errors = ref({ |
|||
username: '', |
|||
password: '', |
|||
confirmPassword: '' |
|||
}); |
|||
|
|||
// 默认头像 |
|||
const defaultAvatar = '/resource/avatar/4.png'; |
|||
const avatarPreview = ref(defaultAvatar); |
|||
|
|||
// 计算属性 - 表单验证 |
|||
const isFormValid = computed(() => { |
|||
return registerForm.value.username && |
|||
registerForm.value.password && |
|||
registerForm.value.confirmPassword && |
|||
registerForm.value.agree && |
|||
!errors.value.username && |
|||
!errors.value.password && |
|||
!errors.value.confirmPassword && |
|||
registerForm.value.password === registerForm.value.confirmPassword; |
|||
}); |
|||
|
|||
// 触发文件选择 |
|||
const triggerFileInput = () => { |
|||
fileInput.value.click(); |
|||
}; |
|||
|
|||
// 处理头像加载错误 |
|||
const handleAvatarError = (event) => { |
|||
avatarPreview.value = defaultAvatar; |
|||
}; |
|||
|
|||
// 处理头像更改 |
|||
const handleAvatarChange = (event) => { |
|||
const file = event.target.files[0]; |
|||
if (file) { |
|||
// 检查文件类型 |
|||
if (!file.type.startsWith('image/')) { |
|||
avatarUploadError.value = '请选择图片文件'; |
|||
return; |
|||
} |
|||
|
|||
// 检查文件大小 (限制为2MB) |
|||
if (file.size > 2 * 1024 * 1024) { |
|||
avatarUploadError.value = '图片大小不能超过2MB'; |
|||
return; |
|||
} |
|||
|
|||
avatarUploadError.value = ''; |
|||
|
|||
// 创建预览 |
|||
const reader = new FileReader(); |
|||
reader.onload = (e) => { |
|||
avatarPreview.value = e.target.result; |
|||
registerForm.value.avatar = file; |
|||
}; |
|||
reader.readAsDataURL(file); |
|||
} |
|||
}; |
|||
|
|||
// 验证表单 |
|||
const validateForm = () => { |
|||
errors.value = { |
|||
username: '', |
|||
password: '', |
|||
confirmPassword: '' |
|||
}; |
|||
|
|||
// 验证用户名 |
|||
if (!registerForm.value.username) { |
|||
errors.value.username = '用户名不能为空'; |
|||
} |
|||
|
|||
// 验证密码 |
|||
if (!registerForm.value.password) { |
|||
errors.value.password = '密码不能为空'; |
|||
} |
|||
|
|||
// 验证确认密码 |
|||
if (!registerForm.value.confirmPassword) { |
|||
errors.value.confirmPassword = '请确认密码'; |
|||
} else if (registerForm.value.password !== registerForm.value.confirmPassword) { |
|||
errors.value.confirmPassword = '两次输入的密码不一致'; |
|||
} |
|||
|
|||
return !errors.value.username && !errors.value.password && !errors.value.confirmPassword; |
|||
}; |
|||
|
|||
// 处理注册 |
|||
const handleRegister = async () => { |
|||
if (!validateForm()) { |
|||
return; |
|||
} |
|||
|
|||
if (!registerForm.value.agree) { |
|||
errorMessage.value = '请同意服务条款和隐私政策'; |
|||
return; |
|||
} |
|||
|
|||
loading.value = true; |
|||
errorMessage.value = ''; |
|||
|
|||
try { |
|||
// 创建FormData对象 |
|||
const formData = new FormData(); |
|||
formData.append('username', registerForm.value.username); |
|||
formData.append('password', registerForm.value.password); |
|||
|
|||
// 如果有头像,添加到FormData |
|||
if (registerForm.value.avatar) { |
|||
formData.append('avatar', registerForm.value.avatar); |
|||
} |
|||
|
|||
// 调用注册API |
|||
const response = await register(formData); |
|||
|
|||
if (response.success) { |
|||
// 注册成功,跳转到登录页面 |
|||
router.push('/login'); |
|||
} else { |
|||
errorMessage.value = response.message || '注册失败'; |
|||
} |
|||
} catch (error) { |
|||
console.error('注册失败:', error); |
|||
errorMessage.value = error.response?.data?.message || '注册过程中发生错误'; |
|||
} finally { |
|||
loading.value = false; |
|||
} |
|||
}; |
|||
|
|||
// 跳转到登录页面 |
|||
const goToLogin = () => { |
|||
router.push('/login'); |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
/* 全局样式,防止页面滚动 */ |
|||
body, html { |
|||
margin: 0; |
|||
padding: 0; |
|||
overflow: hidden; |
|||
height: 100%; |
|||
} |
|||
</style> |
|||
|
|||
<style scoped> |
|||
/* 基础容器样式 */ |
|||
.register-container { |
|||
display: flex; |
|||
height: 100vh; |
|||
overflow: hidden; |
|||
flex-direction: row; |
|||
font-family: 'SimSun', '宋体', serif; |
|||
} |
|||
|
|||
/* 左上角Logo和标题样式 */ |
|||
.logo-header { |
|||
position: fixed; |
|||
top: 40px; |
|||
left: 25px; |
|||
display: flex; |
|||
align-items: center; |
|||
z-index: 10; |
|||
} |
|||
|
|||
.logo { |
|||
height: 15px; |
|||
width: 15px; |
|||
margin-right: 7px; |
|||
} |
|||
|
|||
.register-title { |
|||
font-size: 17px; |
|||
font-weight: 900; |
|||
font-family: 'SimSun Bold', '宋体', serif; |
|||
color: #1f2937; |
|||
margin: 0; |
|||
white-space: nowrap; |
|||
text-shadow: 0.4px 0.4px 0 #1f2937; |
|||
} |
|||
|
|||
/* 左侧注册区域样式 */ |
|||
.register-form-container { |
|||
width: 25%; |
|||
background-color: #ffffff; |
|||
padding: 2rem; |
|||
padding-left: 40px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
position: relative; |
|||
} |
|||
|
|||
.register-header { |
|||
margin-bottom: 1.5rem; |
|||
width: 100%; |
|||
max-width: 24rem; |
|||
margin-top: 20px; |
|||
} |
|||
|
|||
.register-form { |
|||
max-width: 24rem; |
|||
width: 100%; |
|||
text-align: left; |
|||
} |
|||
|
|||
.form-title { |
|||
font-size: 18px; |
|||
font-weight: 900; |
|||
color: #333333; |
|||
margin-top: -7px; |
|||
margin-bottom: 10px; |
|||
margin-left: 13px; |
|||
text-shadow: 0.2px 0.2px 0 #1f2937; |
|||
text-align: left; |
|||
font-family: 'SimSun', '宋体', serif; |
|||
} |
|||
|
|||
.form-description { |
|||
color: #B5B5B5; |
|||
margin-bottom: 2rem; |
|||
margin-left: 13px; |
|||
text-align: left; |
|||
font-size: 11px; |
|||
font-weight: bold; |
|||
font-family: 'SimSun', '宋体', serif; |
|||
} |
|||
|
|||
/* 头像上传区域 */ |
|||
.avatar-upload-container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
margin-bottom: 25px; |
|||
margin-left: 13px; |
|||
} |
|||
|
|||
.avatar-container { |
|||
position: relative; |
|||
width: 80px; |
|||
height: 80px; |
|||
margin-bottom: 10px; |
|||
} |
|||
|
|||
.avatar-image { |
|||
width: 100%; |
|||
height: 100%; |
|||
border-radius: 50%; |
|||
object-fit: cover; |
|||
border: 3px solid #e5e7eb; |
|||
} |
|||
|
|||
.avatar-overlay { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
background-color: rgba(0, 0, 0, 0.5); |
|||
border-radius: 50%; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
opacity: 0; |
|||
transition: opacity 0.2s; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.avatar-container:hover .avatar-overlay { |
|||
opacity: 1; |
|||
} |
|||
|
|||
.camera-icon { |
|||
font-size: 1.2rem; |
|||
margin-bottom: 0.2rem; |
|||
} |
|||
|
|||
.overlay-text { |
|||
font-size: 0.6rem; |
|||
color: white; |
|||
} |
|||
|
|||
.hidden-input { |
|||
display: none; |
|||
} |
|||
|
|||
.avatar-status { |
|||
text-align: center; |
|||
} |
|||
|
|||
.uploading-text, .error-text { |
|||
font-size: 9px; |
|||
margin: 0.25rem 0; |
|||
} |
|||
|
|||
.uploading-text { |
|||
color: #3b82f6; |
|||
} |
|||
|
|||
.form { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 1rem; |
|||
width: 100%; |
|||
} |
|||
|
|||
.error-message { |
|||
color: #ef4444; |
|||
font-size: 11px; |
|||
padding: 0.5rem; |
|||
background-color: #fef2f2; |
|||
border: 1px solid #fecaca; |
|||
border-radius: 0.375rem; |
|||
margin-bottom: 0.5rem; |
|||
} |
|||
|
|||
.form-group { |
|||
display: flex; |
|||
flex-direction: column; |
|||
width: 100%; |
|||
margin-bottom: 0.5rem; |
|||
} |
|||
|
|||
.form-label { |
|||
display: block; |
|||
font-size: 11px; |
|||
font-weight: 700; |
|||
color: #374151; |
|||
margin-bottom: 0.3rem; |
|||
text-align: left; |
|||
font-family:'STSong', '宋体', serif; |
|||
} |
|||
|
|||
.form-input { |
|||
width: 100%; |
|||
padding: 0.6rem 0.8rem; |
|||
border-radius: 0.5rem; |
|||
border: 2px solid #A3A3A3; |
|||
transition: all 0.2s; |
|||
font-size: 9px; |
|||
box-sizing: border-box; |
|||
font-family: 'SimSun', '宋体', serif; |
|||
background-color: #FFFFFF; |
|||
} |
|||
|
|||
.form-input:focus { |
|||
outline: none; |
|||
border-color: #2563eb; |
|||
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.2); |
|||
} |
|||
|
|||
.form-input.error { |
|||
border-color: #ef4444; |
|||
} |
|||
|
|||
.form-input::placeholder { |
|||
color: #9ca3af; |
|||
} |
|||
|
|||
.password-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
margin-bottom: 0.1rem; |
|||
} |
|||
|
|||
.forgot-password { |
|||
font-size: 9px; |
|||
color: #B5B5B5; |
|||
text-decoration: none; |
|||
font-family: 'SimSun', '宋体', serif; |
|||
} |
|||
|
|||
.forgot-password:hover { |
|||
color: #1d4ed8; |
|||
} |
|||
|
|||
.form-checkbox { |
|||
display: flex; |
|||
align-items: center; |
|||
margin-top: -5px; |
|||
margin-bottom: -5px; |
|||
} |
|||
|
|||
.checkbox { |
|||
height: 0.8rem; |
|||
width: 0.8rem; |
|||
color: #2563eb; |
|||
border-radius: 0.25rem; |
|||
border: 1px solid #d1d5db; |
|||
} |
|||
|
|||
.checkbox-label { |
|||
margin-left: 0.1rem; |
|||
font-size: 11px; |
|||
color: #444040ba; |
|||
font-weight: bold; |
|||
font-family: 'SimSun', '宋体', serif; |
|||
} |
|||
|
|||
.register-button { |
|||
width: 100%; |
|||
background-color: #409EFF; |
|||
color: white; |
|||
font-weight: 500; |
|||
font-size: 11px; |
|||
padding: 0.6rem 0.8rem; |
|||
border-radius: 0; |
|||
border: none; |
|||
cursor: pointer; |
|||
transition: background-color 0.2s; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
box-sizing: border-box; |
|||
font-family: 'SimSun', '宋体', 'STSong', '华文宋体', serif; |
|||
box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.2); |
|||
} |
|||
|
|||
.login-icon { |
|||
height: 0.9rem; |
|||
width: auto; |
|||
margin-right: 8px; |
|||
} |
|||
|
|||
.register-button:hover:not(:disabled) { |
|||
background-color: #1d4ed8; |
|||
} |
|||
|
|||
.register-button:disabled { |
|||
background-color: #bdc3c7; |
|||
cursor: not-allowed; |
|||
} |
|||
|
|||
.arrow-icon { |
|||
margin-left: 0.5rem; |
|||
} |
|||
|
|||
/* 分割线样式 */ |
|||
.divider { |
|||
display: flex; |
|||
align-items: center; |
|||
margin: 1.5rem 0; |
|||
font-family: 'SimSun', '宋体', serif; |
|||
} |
|||
|
|||
.divider::before, |
|||
.divider::after { |
|||
content: ''; |
|||
flex: 1; |
|||
height: 1px; |
|||
background-color: #e5e7eb; |
|||
} |
|||
|
|||
.divider span { |
|||
padding: 0 1rem; |
|||
font-size: 11px; |
|||
color: #B5B5B5; |
|||
font-family: 'SimSun', '宋体', serif; |
|||
} |
|||
|
|||
.social-login { |
|||
margin-top: 2rem; |
|||
} |
|||
|
|||
.social-text { |
|||
text-align: center; |
|||
color: #B5B5B5; |
|||
margin-bottom: 1rem; |
|||
font-size: 11px; |
|||
font-weight: bold; |
|||
font-family: 'SimSun', '宋体', serif; |
|||
} |
|||
|
|||
.social-icons { |
|||
display: flex; |
|||
justify-content: center; |
|||
gap: 1rem; |
|||
} |
|||
|
|||
.social-icon { |
|||
width: 2.5rem; |
|||
height: 2.5rem; |
|||
border-radius: 50%; |
|||
background-color: #f3f4f6; |
|||
border: none; |
|||
cursor: pointer; |
|||
transition: background-color 0.2s; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
color: #6b7280; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.social-icon:hover { |
|||
background-color: #e5e7eb; |
|||
} |
|||
|
|||
.login-link { |
|||
position: absolute; |
|||
bottom: 7px; |
|||
left: 50%; |
|||
transform: translateX(-50%); |
|||
text-align: center; |
|||
} |
|||
|
|||
.login-link p { |
|||
color: #B5B5B5; |
|||
font-size: 11px; |
|||
font-weight: bold; |
|||
font-family: 'SimSun', '宋体', serif; |
|||
} |
|||
|
|||
.login { |
|||
color: #B5B5B5; |
|||
font-weight: 500; |
|||
text-decoration: none; |
|||
font-weight: bold; |
|||
font-family: 'SimSun', '宋体', serif; |
|||
} |
|||
|
|||
.login:hover { |
|||
color: #1d4ed8; |
|||
} |
|||
|
|||
/* 右侧知识图谱可视化区域样式 */ |
|||
.graph-container { |
|||
width: 75%; |
|||
background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%), |
|||
url('@/assets/背景.png'); |
|||
background-size: cover; |
|||
background-position: center; |
|||
background-blend-mode: overlay; |
|||
padding: 2rem; |
|||
position: relative; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.background-decoration { |
|||
position: absolute; |
|||
inset: 0; |
|||
opacity: 0.2; |
|||
} |
|||
|
|||
.bg-circle { |
|||
position: absolute; |
|||
border-radius: 50%; |
|||
filter: blur(3rem); |
|||
} |
|||
|
|||
.circle-1 { |
|||
top: 25%; |
|||
left: 25%; |
|||
width: 16rem; |
|||
height: 16rem; |
|||
background-color: #60a5fa; |
|||
} |
|||
|
|||
.circle-2 { |
|||
bottom: 33%; |
|||
right: 33%; |
|||
width: 20rem; |
|||
height: 20rem; |
|||
background-color: #818cf8; |
|||
} |
|||
|
|||
.graph-content { |
|||
position: relative; |
|||
z-index: 10; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
height: 100%; |
|||
font-family: 'SimSun', '宋体', serif; |
|||
} |
|||
|
|||
.graph-wrapper { |
|||
position: relative; |
|||
width: 100%; |
|||
max-width: 48rem; |
|||
aspect-ratio: 1 / 1; |
|||
} |
|||
|
|||
.error-text { |
|||
color: #ef4444; |
|||
font-size: 9px; |
|||
margin-top: 5px; |
|||
} |
|||
|
|||
/* 响应式设计 */ |
|||
@media (max-width: 768px) { |
|||
.register-container { |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.register-form-container, |
|||
.graph-container { |
|||
width: 100%; |
|||
} |
|||
|
|||
.register-form-container { |
|||
padding: 2rem; |
|||
} |
|||
|
|||
.graph-container { |
|||
min-height: 400px; |
|||
} |
|||
} |
|||
</style> |
|||
Loading…
Reference in new issue