- 实现用户登录和注册的完整流程 - 添加邮箱验证码发送和验证功能 - 实现基于邮箱地址的验证码冷却机制 - 添加表单验证和错误提示系统 - 集成Toast消息提示系统 - 支持网络请求处理和错误处理 - 实现按钮状态管理和加载状态显示
1099 lines
36 KiB
GDScript
1099 lines
36 KiB
GDScript
extends Control
|
||
|
||
# 信号定义
|
||
signal login_success(username: String)
|
||
|
||
# API配置
|
||
const API_BASE_URL = "https://whaletownend.xinghangee.icu"
|
||
|
||
# UI节点引用
|
||
@onready var background_image: TextureRect = $BackgroundImage
|
||
@onready var login_panel: Panel = $CenterContainer/LoginPanel
|
||
@onready var register_panel: Panel = $CenterContainer/RegisterPanel
|
||
@onready var title_label: Label = $CenterContainer/LoginPanel/VBoxContainer/TitleLabel
|
||
@onready var subtitle_label: Label = $CenterContainer/LoginPanel/VBoxContainer/SubtitleLabel
|
||
@onready var whale_frame: TextureRect = $WhaleFrame
|
||
|
||
# 登录表单
|
||
@onready var login_username: LineEdit = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer/UsernameInput
|
||
@onready var login_password: LineEdit = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer/PasswordInput
|
||
@onready var login_username_error: Label = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer/UsernameLabelContainer/UsernameError
|
||
@onready var login_password_error: Label = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer/PasswordLabelContainer/PasswordError
|
||
@onready var main_btn: Button = $CenterContainer/LoginPanel/VBoxContainer/MainButton
|
||
@onready var login_btn: Button = $CenterContainer/LoginPanel/VBoxContainer/ButtonContainer/LoginBtn
|
||
@onready var to_register_btn: Button = $CenterContainer/LoginPanel/VBoxContainer/ButtonContainer/ToRegisterBtn
|
||
@onready var forgot_password_btn: Button = $CenterContainer/LoginPanel/VBoxContainer/BottomLinks/ForgotPassword
|
||
@onready var register_link_btn: Button = $CenterContainer/LoginPanel/VBoxContainer/BottomLinks/RegisterLink
|
||
|
||
# 注册表单
|
||
@onready var register_username: LineEdit = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer/UsernameInput
|
||
@onready var register_email: LineEdit = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer/EmailInput
|
||
@onready var register_password: LineEdit = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer/PasswordInput
|
||
@onready var register_confirm: LineEdit = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer/ConfirmInput
|
||
@onready var verification_input: LineEdit = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationInputContainer/VerificationInput
|
||
@onready var send_code_btn: Button = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationInputContainer/SendCodeBtn
|
||
@onready var register_btn: Button = $CenterContainer/RegisterPanel/VBoxContainer/ButtonContainer/RegisterBtn
|
||
@onready var to_login_btn: Button = $CenterContainer/RegisterPanel/VBoxContainer/ButtonContainer/ToLoginBtn
|
||
|
||
# 错误提示标签
|
||
@onready var register_username_error: Label = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer/UsernameLabelContainer/UsernameError
|
||
@onready var register_email_error: Label = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer/EmailLabelContainer/EmailError
|
||
@onready var register_password_error: Label = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer/PasswordLabelContainer/PasswordError
|
||
@onready var register_confirm_error: Label = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer/ConfirmLabelContainer/ConfirmError
|
||
@onready var verification_error: Label = $CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationLabelContainer/VerificationError
|
||
|
||
# HTTP请求节点
|
||
var http_request: HTTPRequest
|
||
|
||
# Toast消息节点
|
||
@onready var toast_container: Control = $ToastContainer
|
||
|
||
# Toast管理
|
||
var active_toasts: Array = []
|
||
var toast_counter: int = 0
|
||
|
||
# 验证码状态
|
||
var verification_codes_sent: Dictionary = {} # 存储每个邮箱的发送状态 {email: {sent: bool, time: float}}
|
||
var code_cooldown: float = 60.0 # 60秒冷却时间
|
||
var current_request_type: String = "" # 跟踪当前请求类型
|
||
var register_data: Dictionary = {} # 存储注册数据
|
||
var cooldown_timer: Timer = null # 倒计时定时器
|
||
var current_email: String = "" # 当前正在倒计时的邮箱
|
||
|
||
func _ready():
|
||
# 获取HTTP请求节点
|
||
http_request = $HTTPRequest
|
||
http_request.request_completed.connect(_on_http_request_completed)
|
||
|
||
# 连接信号
|
||
connect_signals()
|
||
|
||
# 初始显示登录界面
|
||
show_login_panel()
|
||
|
||
# 测试Toast系统(延迟一下确保节点已初始化)
|
||
await get_tree().process_frame
|
||
print("测试Toast系统...")
|
||
show_toast("认证系统已加载", true)
|
||
|
||
# 测试网络连接
|
||
test_network_connection()
|
||
|
||
# 测试网络连接
|
||
func test_network_connection():
|
||
print("=== 测试网络连接 ===")
|
||
var url = API_BASE_URL + "/"
|
||
var headers = ["Content-Type: application/json"]
|
||
|
||
print("测试URL: ", url)
|
||
current_request_type = "network_test"
|
||
|
||
var error = http_request.request(url, headers, HTTPClient.METHOD_GET)
|
||
print("网络测试请求发送结果: ", error)
|
||
|
||
if error != OK:
|
||
print("网络测试请求发送失败: ", error)
|
||
show_toast("网络连接测试失败", false)
|
||
else:
|
||
print("网络测试请求已发送,等待响应...")
|
||
|
||
func connect_signals():
|
||
# 主要按钮
|
||
main_btn.pressed.connect(_on_main_button_pressed)
|
||
|
||
# 登录界面按钮
|
||
login_btn.pressed.connect(_on_login_pressed)
|
||
to_register_btn.pressed.connect(_on_to_register_pressed)
|
||
forgot_password_btn.pressed.connect(_on_forgot_password_pressed)
|
||
register_link_btn.pressed.connect(_on_register_link_pressed)
|
||
|
||
# 注册界面按钮
|
||
register_btn.pressed.connect(_on_register_pressed)
|
||
to_login_btn.pressed.connect(_on_to_login_pressed)
|
||
send_code_btn.pressed.connect(_on_send_code_pressed)
|
||
|
||
# 回车键登录
|
||
login_password.text_submitted.connect(_on_login_enter)
|
||
|
||
# 登录表单失焦验证
|
||
login_username.focus_exited.connect(_on_login_username_focus_exited)
|
||
login_password.focus_exited.connect(_on_login_password_focus_exited)
|
||
|
||
# 注册表单失焦验证
|
||
register_username.focus_exited.connect(_on_register_username_focus_exited)
|
||
register_email.focus_exited.connect(_on_register_email_focus_exited)
|
||
register_password.focus_exited.connect(_on_register_password_focus_exited)
|
||
register_confirm.focus_exited.connect(_on_register_confirm_focus_exited)
|
||
verification_input.focus_exited.connect(_on_verification_focus_exited)
|
||
|
||
# 实时输入验证
|
||
register_username.text_changed.connect(_on_register_username_text_changed)
|
||
register_email.text_changed.connect(_on_register_email_text_changed)
|
||
register_password.text_changed.connect(_on_register_password_text_changed)
|
||
register_confirm.text_changed.connect(_on_register_confirm_text_changed)
|
||
verification_input.text_changed.connect(_on_verification_text_changed)
|
||
|
||
func show_login_panel():
|
||
login_panel.visible = true
|
||
register_panel.visible = false
|
||
login_username.grab_focus()
|
||
|
||
func show_register_panel():
|
||
login_panel.visible = false
|
||
register_panel.visible = true
|
||
register_username.grab_focus()
|
||
|
||
func _on_main_button_pressed():
|
||
if not validate_login_form():
|
||
return
|
||
|
||
var username = login_username.text.strip_edges()
|
||
var password = login_password.text
|
||
|
||
# 显示加载状态
|
||
show_loading(main_btn, "登录中...")
|
||
show_toast('正在验证登录信息...', true)
|
||
|
||
# 发送登录请求
|
||
send_login_request(username, password)
|
||
|
||
func _on_login_pressed():
|
||
if not validate_login_form():
|
||
return
|
||
|
||
var username = login_username.text.strip_edges()
|
||
var password = login_password.text
|
||
|
||
# 显示加载状态
|
||
show_loading(login_btn, "登录中...")
|
||
show_toast('正在验证登录信息...', true)
|
||
|
||
# 发送登录请求
|
||
send_login_request(username, password)
|
||
|
||
func _on_register_pressed():
|
||
print("注册按钮被点击")
|
||
|
||
if not validate_register_form():
|
||
print("注册表单验证失败")
|
||
show_toast('请检查并完善注册信息', false)
|
||
return
|
||
|
||
print("注册表单验证通过,开始注册流程")
|
||
|
||
var username = register_username.text.strip_edges()
|
||
var email = register_email.text.strip_edges()
|
||
var password = register_password.text
|
||
var verification_code = verification_input.text.strip_edges()
|
||
|
||
# 显示加载状态
|
||
show_loading(register_btn, "注册中...")
|
||
show_toast('正在验证邮箱验证码...', true)
|
||
|
||
# 先验证邮箱验证码,然后注册
|
||
verify_email_then_register(username, email, password, verification_code)
|
||
|
||
func _on_send_code_pressed():
|
||
var email = register_email.text.strip_edges()
|
||
|
||
# 验证邮箱
|
||
var email_validation = validate_email(email)
|
||
if not email_validation.valid:
|
||
show_toast(email_validation.message, false)
|
||
register_email.grab_focus()
|
||
return
|
||
|
||
hide_field_error(register_email_error)
|
||
|
||
# 检查该邮箱的冷却时间
|
||
var current_time = Time.get_time_dict_from_system()
|
||
var current_timestamp = current_time.hour * 3600 + current_time.minute * 60 + current_time.second
|
||
|
||
if verification_codes_sent.has(email):
|
||
var email_data = verification_codes_sent[email]
|
||
if email_data.sent and (current_timestamp - email_data.time) < code_cooldown:
|
||
var remaining = code_cooldown - (current_timestamp - email_data.time)
|
||
show_toast('该邮箱请等待 %d 秒后再次发送' % remaining, false)
|
||
return
|
||
|
||
# 如果当前有其他邮箱在倒计时,需要切换到新邮箱
|
||
if current_email != email:
|
||
# 停止当前倒计时
|
||
stop_current_cooldown()
|
||
current_email = email
|
||
|
||
# 立即开始倒计时并禁用按钮
|
||
if not verification_codes_sent.has(email):
|
||
verification_codes_sent[email] = {}
|
||
|
||
verification_codes_sent[email].sent = true
|
||
verification_codes_sent[email].time = current_timestamp
|
||
start_cooldown_timer(email)
|
||
|
||
# 发送验证码请求
|
||
send_verification_code_request(email)
|
||
|
||
# 发送登录请求
|
||
func send_login_request(username: String, password: String):
|
||
var url = API_BASE_URL + "/auth/login"
|
||
var headers = ["Content-Type: application/json"]
|
||
var body = JSON.stringify({
|
||
"username": username,
|
||
"password": password
|
||
})
|
||
|
||
current_request_type = "login"
|
||
|
||
var error = http_request.request(url, headers, HTTPClient.METHOD_POST, body)
|
||
if error != OK:
|
||
show_toast('网络请求失败', false)
|
||
restore_button(login_btn, "密码登录")
|
||
current_request_type = ""
|
||
|
||
# 发送邮箱验证码请求
|
||
func send_verification_code_request(email: String):
|
||
var url = API_BASE_URL + "/auth/send-email-verification"
|
||
var headers = ["Content-Type: application/json"]
|
||
var body = JSON.stringify({"email": email})
|
||
|
||
print("=== 发送验证码请求 ===")
|
||
print("URL: ", url)
|
||
print("Headers: ", headers)
|
||
print("Body: ", body)
|
||
|
||
current_request_type = "send_code"
|
||
|
||
var error = http_request.request(url, headers, HTTPClient.METHOD_POST, body)
|
||
print("HTTP请求发送结果: ", error, " (OK=", OK, ")")
|
||
|
||
if error != OK:
|
||
print("HTTP请求发送失败,错误代码: ", error)
|
||
show_toast('网络请求失败', false)
|
||
restore_button(send_code_btn, "发送验证码")
|
||
current_request_type = ""
|
||
else:
|
||
print("HTTP请求已发送,等待响应...")
|
||
|
||
# 验证邮箱然后注册
|
||
func verify_email_then_register(username: String, email: String, password: String, verification_code: String):
|
||
var url = API_BASE_URL + "/auth/verify-email"
|
||
var headers = ["Content-Type: application/json"]
|
||
var body = JSON.stringify({
|
||
"email": email,
|
||
"verification_code": verification_code
|
||
})
|
||
|
||
current_request_type = "verify_email"
|
||
|
||
# 保存注册信息,验证成功后使用
|
||
register_data = {
|
||
"username": username,
|
||
"email": email,
|
||
"password": password,
|
||
"verification_code": verification_code
|
||
}
|
||
|
||
var error = http_request.request(url, headers, HTTPClient.METHOD_POST, body)
|
||
if error != OK:
|
||
show_toast('网络请求失败', false)
|
||
restore_button(register_btn, "注册")
|
||
current_request_type = ""
|
||
|
||
# 发送注册请求
|
||
func send_register_request(username: String, email: String, password: String, verification_code: String = ""):
|
||
var url = API_BASE_URL + "/auth/register"
|
||
var headers = ["Content-Type: application/json"]
|
||
var body_data = {
|
||
"username": username,
|
||
"password": password,
|
||
"nickname": username, # 使用用户名作为昵称
|
||
"email": email
|
||
}
|
||
|
||
# 如果提供了验证码,则添加到请求体中
|
||
if verification_code != "":
|
||
body_data["email_verification_code"] = verification_code
|
||
|
||
var body = JSON.stringify(body_data)
|
||
|
||
current_request_type = "register"
|
||
|
||
var error = http_request.request(url, headers, HTTPClient.METHOD_POST, body)
|
||
if error != OK:
|
||
show_toast('网络请求失败', false)
|
||
restore_button(register_btn, "注册")
|
||
current_request_type = ""
|
||
|
||
# HTTP请求完成回调
|
||
func _on_http_request_completed(_result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray):
|
||
var response_text = body.get_string_from_utf8()
|
||
|
||
print("=== HTTP响应接收 ===")
|
||
print("请求类型: ", current_request_type)
|
||
print("响应状态码: ", response_code)
|
||
print("响应头: ", _headers)
|
||
print("响应体: ", response_text)
|
||
print("响应体长度: ", body.size(), " 字节")
|
||
|
||
# 恢复按钮状态(排除验证码按钮,因为它有自己的状态管理)
|
||
if current_request_type != "send_code":
|
||
restore_button(send_code_btn, "发送验证码")
|
||
restore_button(register_btn, "注册")
|
||
restore_button(login_btn, "密码登录")
|
||
restore_button(main_btn, "进入小镇")
|
||
|
||
# 处理网络连接失败
|
||
if response_code == 0:
|
||
show_toast('网络连接失败,请检查网络连接', false)
|
||
current_request_type = ""
|
||
return
|
||
|
||
# 解析JSON响应
|
||
var json = JSON.new()
|
||
var parse_result = json.parse(response_text)
|
||
if parse_result != OK:
|
||
show_toast('服务器响应格式错误', false)
|
||
current_request_type = ""
|
||
return
|
||
|
||
var response_data = json.data
|
||
|
||
# 根据请求类型处理响应
|
||
var request_type = current_request_type
|
||
current_request_type = "" # 提前清空,避免异步调用时的竞态条件
|
||
|
||
match request_type:
|
||
"network_test":
|
||
handle_network_test_response(response_code, response_data)
|
||
"login":
|
||
handle_login_response(response_code, response_data)
|
||
"send_code":
|
||
handle_verification_code_response(response_code, response_data)
|
||
"verify_email":
|
||
handle_verify_email_response(response_code, response_data)
|
||
"register":
|
||
handle_register_response(response_code, response_data)
|
||
|
||
# 处理网络测试响应
|
||
func handle_network_test_response(response_code: int, data: Dictionary):
|
||
print("=== 网络测试响应 ===")
|
||
print("状态码: ", response_code)
|
||
print("响应数据: ", data)
|
||
|
||
if response_code == 200:
|
||
print("✅ 网络连接正常,后端服务可访问")
|
||
show_toast("网络连接正常", true)
|
||
else:
|
||
print("❌ 网络连接异常,状态码: ", response_code)
|
||
show_toast("网络连接异常: " + str(response_code), false)
|
||
|
||
# 处理登录响应
|
||
func handle_login_response(response_code: int, data: Dictionary):
|
||
match response_code:
|
||
200:
|
||
show_toast('登录成功!正在进入鲸鱼镇...', true)
|
||
# 获取用户信息
|
||
var username = login_username.text.strip_edges()
|
||
if data.has("data") and data.data.has("user"):
|
||
var user_data = data.data.user
|
||
if user_data.has("username"):
|
||
username = user_data.username
|
||
|
||
# 清空登录表单
|
||
login_username.text = ""
|
||
login_password.text = ""
|
||
hide_field_error(login_username_error)
|
||
hide_field_error(login_password_error)
|
||
|
||
# 延迟一下再发送信号,让用户看到成功消息
|
||
await get_tree().create_timer(1.0).timeout
|
||
|
||
# 发送登录成功信号
|
||
login_success.emit(username)
|
||
400:
|
||
# 参数错误,统一使用Toast显示
|
||
var message = data.get("message", "登录参数错误")
|
||
if "用户名" in message or "username" in message.to_lower():
|
||
show_toast('用户名格式错误', false)
|
||
elif "密码" in message or "password" in message.to_lower():
|
||
show_toast('密码格式错误', false)
|
||
else:
|
||
show_toast('登录信息有误,请检查后重试', false)
|
||
401:
|
||
# 认证失败
|
||
show_toast('用户名或密码错误,请检查后重试', false)
|
||
404:
|
||
# 用户不存在
|
||
show_toast('用户不存在,请先注册', false)
|
||
429:
|
||
# 请求过频
|
||
show_toast('登录请求过于频繁,请稍后再试', false)
|
||
500:
|
||
# 服务器错误
|
||
show_toast('服务器繁忙,请稍后再试', false)
|
||
_:
|
||
var message = data.get("message", "登录失败")
|
||
show_toast(message, false)
|
||
|
||
# 处理验证码响应
|
||
func handle_verification_code_response(response_code: int, data: Dictionary):
|
||
match response_code:
|
||
200:
|
||
show_toast('验证码已发送到您的邮箱,请查收', true)
|
||
|
||
# 开发环境下显示验证码(仅用于测试)
|
||
if data.has("data") and data.data.has("verification_code"):
|
||
print("开发环境验证码: ", data.data.verification_code)
|
||
206:
|
||
# 测试模式
|
||
show_toast('测试模式:验证码已生成,请查看控制台', true)
|
||
if data.has("data") and data.data.has("verification_code"):
|
||
print("测试模式验证码: ", data.data.verification_code)
|
||
400:
|
||
# 根据具体错误信息显示相应的Toast
|
||
var message = data.get("message", "发送验证码失败")
|
||
var error_code = data.get("error_code", "")
|
||
|
||
# 根据错误代码或消息内容判断具体错误类型
|
||
if "邮箱格式" in message or "INVALID_EMAIL" in error_code:
|
||
show_toast('请输入有效的邮箱地址', false)
|
||
elif "每小时发送次数" in message or "HOURLY_LIMIT" in error_code:
|
||
show_toast('每小时发送次数已达上限,请稍后再试', false)
|
||
elif "频率" in message or "RATE_LIMITED" in error_code:
|
||
show_toast('发送过于频繁,请稍后再试', false)
|
||
else:
|
||
# 未知400错误,显示通用消息
|
||
show_toast('发送验证码失败,请检查邮箱地址或稍后再试', false)
|
||
|
||
reset_verification_button()
|
||
429:
|
||
# 频率限制
|
||
var message = data.get("message", "请求过于频繁,请稍后再试")
|
||
show_toast(message, false)
|
||
reset_verification_button()
|
||
500:
|
||
show_toast('服务器繁忙,请稍后再试', false)
|
||
reset_verification_button()
|
||
_:
|
||
var message = data.get("message", "发送验证码失败")
|
||
show_toast(message, false)
|
||
reset_verification_button()
|
||
|
||
# 处理邮箱验证响应
|
||
func handle_verify_email_response(response_code: int, data: Dictionary):
|
||
match response_code:
|
||
200:
|
||
show_toast('邮箱验证成功,正在注册...', true)
|
||
# 邮箱验证成功,继续注册
|
||
if register_data.has("username") and register_data.has("email") and register_data.has("password") and register_data.has("verification_code"):
|
||
send_register_request(register_data.username, register_data.email, register_data.password, register_data.verification_code)
|
||
else:
|
||
show_toast('注册数据丢失,请重新填写', false)
|
||
400:
|
||
show_toast('验证码错误或已过期', false)
|
||
404:
|
||
show_toast('请先获取验证码', false)
|
||
500:
|
||
show_toast('验证失败,请稍后再试', false)
|
||
_:
|
||
var message = data.get("message", "邮箱验证失败")
|
||
show_toast(message, false)
|
||
|
||
# 处理注册响应
|
||
func handle_register_response(response_code: int, data: Dictionary):
|
||
match response_code:
|
||
201:
|
||
show_toast('注册成功!欢迎加入鲸鱼镇', true)
|
||
# 清空表单
|
||
clear_register_form()
|
||
# 返回登录界面
|
||
show_login_panel()
|
||
# 自动填入用户名
|
||
login_username.text = register_data.get("username", "")
|
||
register_data.clear()
|
||
400:
|
||
# 根据具体错误处理
|
||
var message = data.get("message", "参数验证失败")
|
||
|
||
# 针对常见错误提供友好提示,统一使用Toast显示
|
||
if "邮箱验证码" in message or "verification_code" in message:
|
||
show_toast('请先获取并输入邮箱验证码', false)
|
||
elif "用户名" in message:
|
||
show_toast('用户名格式不正确', false)
|
||
elif "邮箱" in message:
|
||
show_toast('邮箱格式不正确', false)
|
||
elif "密码" in message:
|
||
show_toast('密码格式不符合要求', false)
|
||
elif "验证码" in message:
|
||
show_toast('验证码错误或已过期', false)
|
||
else:
|
||
# 显示用户友好的通用错误信息
|
||
show_toast('注册信息有误,请检查后重试', false)
|
||
409:
|
||
# 用户名或邮箱已存在
|
||
var message = data.get("message", "用户名或邮箱已被使用")
|
||
if "用户名" in message:
|
||
show_toast('用户名已被使用,请换一个', false)
|
||
elif "邮箱" in message:
|
||
show_toast('邮箱已被使用,请换一个', false)
|
||
else:
|
||
show_toast('用户名或邮箱已被使用,请换一个', false)
|
||
429:
|
||
# 注册请求过于频繁
|
||
var message = data.get("message", "注册请求过于频繁,请稍后再试")
|
||
show_toast(message, false)
|
||
500:
|
||
show_toast('注册失败,请稍后再试', false)
|
||
_:
|
||
var message = data.get("message", "注册失败")
|
||
show_toast(message, false)
|
||
|
||
# 开始冷却计时器
|
||
func start_cooldown_timer(email: String):
|
||
# 清理之前的计时器
|
||
if cooldown_timer != null:
|
||
cooldown_timer.queue_free()
|
||
|
||
# 设置当前邮箱
|
||
current_email = email
|
||
|
||
# 立即设置按钮状态
|
||
send_code_btn.disabled = true
|
||
send_code_btn.text = "重新发送(60)"
|
||
|
||
# 创建新的计时器
|
||
cooldown_timer = Timer.new()
|
||
add_child(cooldown_timer)
|
||
cooldown_timer.wait_time = 1.0
|
||
cooldown_timer.timeout.connect(_on_cooldown_timer_timeout)
|
||
cooldown_timer.start()
|
||
|
||
func _on_cooldown_timer_timeout():
|
||
# 检查当前邮箱输入框的邮箱
|
||
var input_email = register_email.text.strip_edges()
|
||
|
||
# 如果用户换了邮箱,停止当前倒计时
|
||
if input_email != current_email:
|
||
stop_current_cooldown()
|
||
return
|
||
|
||
# 检查当前邮箱的剩余时间
|
||
if verification_codes_sent.has(current_email):
|
||
var current_time = Time.get_time_dict_from_system()
|
||
var current_timestamp = current_time.hour * 3600 + current_time.minute * 60 + current_time.second
|
||
var email_data = verification_codes_sent[current_email]
|
||
var remaining = code_cooldown - (current_timestamp - email_data.time)
|
||
|
||
if remaining > 0:
|
||
send_code_btn.text = "重新发送(%d)" % remaining
|
||
else:
|
||
# 倒计时结束,恢复按钮
|
||
send_code_btn.text = "重新发送"
|
||
send_code_btn.disabled = false
|
||
|
||
# 清理计时器
|
||
if cooldown_timer != null:
|
||
cooldown_timer.queue_free()
|
||
cooldown_timer = null
|
||
current_email = ""
|
||
|
||
# 停止当前倒计时
|
||
func stop_current_cooldown():
|
||
if cooldown_timer != null:
|
||
cooldown_timer.queue_free()
|
||
cooldown_timer = null
|
||
|
||
# 恢复按钮状态
|
||
send_code_btn.disabled = false
|
||
send_code_btn.text = "发送验证码"
|
||
current_email = ""
|
||
|
||
# 重置验证码按钮状态(发送失败时调用)
|
||
func reset_verification_button():
|
||
# 清除当前邮箱的发送状态
|
||
if current_email != "" and verification_codes_sent.has(current_email):
|
||
verification_codes_sent[current_email].sent = false
|
||
|
||
stop_current_cooldown()
|
||
|
||
# 清空注册表单
|
||
func clear_register_form():
|
||
register_username.text = ""
|
||
register_email.text = ""
|
||
register_password.text = ""
|
||
register_confirm.text = ""
|
||
verification_input.text = ""
|
||
|
||
# 重置验证码状态
|
||
stop_current_cooldown()
|
||
verification_codes_sent.clear()
|
||
|
||
# 隐藏所有错误提示
|
||
hide_field_error(register_username_error)
|
||
hide_field_error(register_email_error)
|
||
hide_field_error(register_password_error)
|
||
hide_field_error(register_confirm_error)
|
||
hide_field_error(verification_error)
|
||
|
||
# ============ Toast消息系统 ============
|
||
|
||
# 显示Toast消息
|
||
func show_toast(message: String, is_success: bool = true):
|
||
print("显示Toast消息: ", message, " 成功: ", is_success)
|
||
|
||
# 确保容器存在
|
||
if toast_container == null:
|
||
print("错误: toast_container 节点不存在")
|
||
return
|
||
|
||
# 创建新的Toast实例
|
||
create_toast_instance(message, is_success)
|
||
|
||
# 创建Toast实例
|
||
func create_toast_instance(message: String, is_success: bool):
|
||
toast_counter += 1
|
||
|
||
# 创建Toast Panel
|
||
var toast_panel = Panel.new()
|
||
toast_panel.name = "Toast_" + str(toast_counter)
|
||
|
||
# 设置Toast样式
|
||
var style = StyleBoxFlat.new()
|
||
if is_success:
|
||
style.bg_color = Color(0.2, 0.8, 0.2, 0.95) # 绿色背景
|
||
else:
|
||
style.bg_color = Color(0.8, 0.2, 0.2, 0.95) # 红色背景
|
||
|
||
style.border_width_left = 2
|
||
style.border_width_top = 2
|
||
style.border_width_right = 2
|
||
style.border_width_bottom = 2
|
||
style.border_color = Color(1, 1, 1, 0.8) # 白色边框
|
||
style.corner_radius_top_left = 8
|
||
style.corner_radius_top_right = 8
|
||
style.corner_radius_bottom_left = 8
|
||
style.corner_radius_bottom_right = 8
|
||
|
||
toast_panel.add_theme_stylebox_override("panel", style)
|
||
|
||
# 设置Toast尺寸和位置(右上角外侧开始)
|
||
var toast_width = 280
|
||
var toast_height = 50
|
||
var margin = 20
|
||
var start_x = get_viewport().get_visible_rect().size.x # 屏幕外右侧
|
||
var final_x = get_viewport().get_visible_rect().size.x - toast_width - margin # 最终位置
|
||
var y_position = margin + (active_toasts.size() * (toast_height + 10)) # 垂直堆叠
|
||
|
||
toast_panel.position = Vector2(start_x, y_position)
|
||
toast_panel.size = Vector2(toast_width, toast_height)
|
||
|
||
# 创建Label
|
||
var toast_label = Label.new()
|
||
toast_label.text = message
|
||
toast_label.add_theme_color_override("font_color", Color(1, 1, 1, 1))
|
||
toast_label.add_theme_font_size_override("font_size", 14)
|
||
toast_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
toast_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||
toast_label.autowrap_mode = TextServer.AUTOWRAP_OFF # 禁用自动换行
|
||
|
||
# 设置Label的位置和大小(不使用anchors_preset)
|
||
toast_label.position = Vector2(10, 5)
|
||
toast_label.size = Vector2(toast_width - 20, toast_height - 10) # 留出边距
|
||
|
||
# 组装Toast
|
||
toast_panel.add_child(toast_label)
|
||
toast_container.add_child(toast_panel)
|
||
active_toasts.append(toast_panel)
|
||
|
||
# 执行滑入动画(快慢快)
|
||
animate_toast_in(toast_panel, final_x)
|
||
|
||
# Toast滑入动画(快慢快)
|
||
func animate_toast_in(toast_panel: Panel, final_x: float):
|
||
var tween = create_tween()
|
||
tween.set_ease(Tween.EASE_OUT) # 开始快,结束慢
|
||
tween.set_trans(Tween.TRANS_BACK) # 回弹效果
|
||
|
||
# 滑入动画
|
||
tween.tween_property(toast_panel, "position:x", final_x, 0.5)
|
||
|
||
# 等待2秒后滑出
|
||
await get_tree().create_timer(2.0).timeout
|
||
animate_toast_out(toast_panel)
|
||
|
||
# Toast滑出动画
|
||
func animate_toast_out(toast_panel: Panel):
|
||
if not is_instance_valid(toast_panel):
|
||
return
|
||
|
||
var tween = create_tween()
|
||
tween.set_ease(Tween.EASE_IN) # 开始慢,结束快
|
||
tween.set_trans(Tween.TRANS_QUART)
|
||
|
||
# 滑出到右侧屏幕外
|
||
var end_x = get_viewport().get_visible_rect().size.x + 50
|
||
tween.tween_property(toast_panel, "position:x", end_x, 0.3)
|
||
|
||
# 动画完成后清理
|
||
await tween.finished
|
||
cleanup_toast(toast_panel)
|
||
|
||
# 清理Toast实例
|
||
func cleanup_toast(toast_panel: Panel):
|
||
if not is_instance_valid(toast_panel):
|
||
return
|
||
|
||
# 从活动列表中移除
|
||
active_toasts.erase(toast_panel)
|
||
|
||
# 重新排列剩余的Toast
|
||
rearrange_toasts()
|
||
|
||
# 删除节点
|
||
toast_panel.queue_free()
|
||
|
||
# 重新排列Toast位置
|
||
func rearrange_toasts():
|
||
var margin = 20
|
||
var toast_height = 50
|
||
|
||
for i in range(active_toasts.size()):
|
||
var toast = active_toasts[i]
|
||
if is_instance_valid(toast):
|
||
var new_y = margin + (i * (toast_height + 10))
|
||
var tween = create_tween()
|
||
tween.tween_property(toast, "position:y", new_y, 0.2)
|
||
|
||
# 显示加载状态
|
||
func show_loading(button: Button, loading_text: String):
|
||
button.disabled = true
|
||
button.text = loading_text
|
||
|
||
# 恢复按钮状态
|
||
func restore_button(button: Button, original_text: String):
|
||
button.disabled = false
|
||
button.text = original_text
|
||
|
||
# 验证邮箱格式
|
||
func is_valid_email(email: String) -> bool:
|
||
var regex = RegEx.new()
|
||
regex.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
|
||
return regex.search(email) != null
|
||
|
||
# 显示错误信息
|
||
func show_field_error(error_label: Label, message: String):
|
||
error_label.text = message
|
||
error_label.visible = true
|
||
|
||
# 隐藏错误信息
|
||
func hide_field_error(error_label: Label):
|
||
error_label.visible = false
|
||
|
||
# 验证用户名
|
||
func validate_username(username: String) -> Dictionary:
|
||
var result = {"valid": false, "message": ""}
|
||
|
||
if username.is_empty():
|
||
result.message = "用户名不能为空"
|
||
return result
|
||
|
||
if username.length() < 1 or username.length() > 50:
|
||
result.message = "用户名长度应为1-50字符"
|
||
return result
|
||
|
||
# 检查用户名格式(字母、数字、下划线)
|
||
var regex = RegEx.new()
|
||
regex.compile("^[a-zA-Z0-9_]+$")
|
||
if not regex.search(username):
|
||
result.message = "用户名只能包含字母、数字和下划线"
|
||
return result
|
||
|
||
result.valid = true
|
||
return result
|
||
|
||
# 验证邮箱
|
||
func validate_email(email: String) -> Dictionary:
|
||
var result = {"valid": false, "message": ""}
|
||
|
||
if email.is_empty():
|
||
result.message = "邮箱不能为空"
|
||
return result
|
||
|
||
if not is_valid_email(email):
|
||
result.message = "请输入有效的邮箱地址"
|
||
return result
|
||
|
||
result.valid = true
|
||
return result
|
||
|
||
# 验证密码
|
||
func validate_password(password: String) -> Dictionary:
|
||
var result = {"valid": false, "message": ""}
|
||
|
||
if password.is_empty():
|
||
result.message = "密码不能为空"
|
||
return result
|
||
|
||
if password.length() < 8:
|
||
result.message = "密码长度至少8位"
|
||
return result
|
||
|
||
if password.length() > 128:
|
||
result.message = "密码长度不能超过128位"
|
||
return result
|
||
|
||
# 验证密码格式(必须包含字母和数字)
|
||
var has_letter = false
|
||
var has_digit = false
|
||
for i in range(password.length()):
|
||
var character = password[i]
|
||
if character >= 'a' and character <= 'z' or character >= 'A' and character <= 'Z':
|
||
has_letter = true
|
||
elif character >= '0' and character <= '9':
|
||
has_digit = true
|
||
|
||
if not (has_letter and has_digit):
|
||
result.message = "密码必须包含字母和数字"
|
||
return result
|
||
|
||
result.valid = true
|
||
return result
|
||
|
||
# 验证确认密码
|
||
func validate_confirm_password(password: String, confirm: String) -> Dictionary:
|
||
var result = {"valid": false, "message": ""}
|
||
|
||
if confirm.is_empty():
|
||
result.message = "确认密码不能为空"
|
||
return result
|
||
|
||
if password != confirm:
|
||
result.message = "两次输入的密码不一致"
|
||
return result
|
||
|
||
result.valid = true
|
||
return result
|
||
|
||
# 验证验证码
|
||
func validate_verification_code(code: String) -> Dictionary:
|
||
var result = {"valid": false, "message": ""}
|
||
|
||
if code.is_empty():
|
||
result.message = "验证码不能为空"
|
||
return result
|
||
|
||
if code.length() != 6:
|
||
result.message = "验证码必须是6位数字"
|
||
return result
|
||
|
||
# 验证是否为纯数字
|
||
for i in range(code.length()):
|
||
var character = code[i]
|
||
if not (character >= '0' and character <= '9'):
|
||
result.message = "验证码必须是6位数字"
|
||
return result
|
||
|
||
result.valid = true
|
||
return result
|
||
|
||
# ============ 登录表单验证事件 ============
|
||
|
||
func _on_login_username_focus_exited():
|
||
var username = login_username.text.strip_edges()
|
||
if username.is_empty():
|
||
show_field_error(login_username_error, "用户名不能为空")
|
||
else:
|
||
hide_field_error(login_username_error)
|
||
|
||
func _on_login_password_focus_exited():
|
||
var password = login_password.text
|
||
if password.is_empty():
|
||
show_field_error(login_password_error, "密码不能为空")
|
||
else:
|
||
hide_field_error(login_password_error)
|
||
|
||
# ============ 注册表单验证事件 ============
|
||
|
||
func _on_register_username_focus_exited():
|
||
var username = register_username.text.strip_edges()
|
||
var validation = validate_username(username)
|
||
if not validation.valid:
|
||
show_field_error(register_username_error, validation.message)
|
||
else:
|
||
hide_field_error(register_username_error)
|
||
|
||
func _on_register_email_focus_exited():
|
||
var email = register_email.text.strip_edges()
|
||
var validation = validate_email(email)
|
||
if not validation.valid:
|
||
show_field_error(register_email_error, validation.message)
|
||
else:
|
||
hide_field_error(register_email_error)
|
||
|
||
func _on_register_password_focus_exited():
|
||
var password = register_password.text
|
||
var validation = validate_password(password)
|
||
if not validation.valid:
|
||
show_field_error(register_password_error, validation.message)
|
||
else:
|
||
hide_field_error(register_password_error)
|
||
# 如果确认密码已填写,重新验证确认密码
|
||
if not register_confirm.text.is_empty():
|
||
_on_register_confirm_focus_exited()
|
||
|
||
func _on_register_confirm_focus_exited():
|
||
var password = register_password.text
|
||
var confirm = register_confirm.text
|
||
var validation = validate_confirm_password(password, confirm)
|
||
if not validation.valid:
|
||
show_field_error(register_confirm_error, validation.message)
|
||
else:
|
||
hide_field_error(register_confirm_error)
|
||
|
||
func _on_verification_focus_exited():
|
||
var code = verification_input.text.strip_edges()
|
||
var validation = validate_verification_code(code)
|
||
if not validation.valid:
|
||
show_field_error(verification_error, validation.message)
|
||
else:
|
||
hide_field_error(verification_error)
|
||
|
||
# ============ 实时输入验证事件 ============
|
||
|
||
func _on_register_username_text_changed(new_text: String):
|
||
# 输入时隐藏错误提示
|
||
if register_username_error.visible and not new_text.is_empty():
|
||
hide_field_error(register_username_error)
|
||
|
||
func _on_register_email_text_changed(new_text: String):
|
||
if register_email_error.visible and not new_text.is_empty():
|
||
hide_field_error(register_email_error)
|
||
|
||
func _on_register_password_text_changed(new_text: String):
|
||
if register_password_error.visible and not new_text.is_empty():
|
||
hide_field_error(register_password_error)
|
||
|
||
func _on_register_confirm_text_changed(new_text: String):
|
||
if register_confirm_error.visible and not new_text.is_empty():
|
||
hide_field_error(register_confirm_error)
|
||
|
||
func _on_verification_text_changed(new_text: String):
|
||
if verification_error.visible and not new_text.is_empty():
|
||
hide_field_error(verification_error)
|
||
|
||
# ============ 表单整体验证 ============
|
||
|
||
# 验证登录表单
|
||
func validate_login_form() -> bool:
|
||
var is_valid = true
|
||
|
||
var username = login_username.text.strip_edges()
|
||
var password = login_password.text
|
||
|
||
if username.is_empty():
|
||
show_field_error(login_username_error, "用户名不能为空")
|
||
is_valid = false
|
||
else:
|
||
hide_field_error(login_username_error)
|
||
|
||
if password.is_empty():
|
||
show_field_error(login_password_error, "密码不能为空")
|
||
is_valid = false
|
||
else:
|
||
hide_field_error(login_password_error)
|
||
|
||
return is_valid
|
||
|
||
# 验证注册表单
|
||
func validate_register_form() -> bool:
|
||
print("开始验证注册表单")
|
||
var is_valid = true
|
||
|
||
var username = register_username.text.strip_edges()
|
||
var email = register_email.text.strip_edges()
|
||
var password = register_password.text
|
||
var confirm = register_confirm.text
|
||
var verification_code = verification_input.text.strip_edges()
|
||
|
||
print("表单数据: 用户名='%s', 邮箱='%s', 密码长度=%d, 确认密码长度=%d, 验证码='%s'" % [username, email, password.length(), confirm.length(), verification_code])
|
||
|
||
# 验证用户名
|
||
var username_validation = validate_username(username)
|
||
if not username_validation.valid:
|
||
print("用户名验证失败: ", username_validation.message)
|
||
show_field_error(register_username_error, username_validation.message)
|
||
is_valid = false
|
||
else:
|
||
hide_field_error(register_username_error)
|
||
|
||
# 验证邮箱
|
||
var email_validation = validate_email(email)
|
||
if not email_validation.valid:
|
||
print("邮箱验证失败: ", email_validation.message)
|
||
show_field_error(register_email_error, email_validation.message)
|
||
is_valid = false
|
||
else:
|
||
hide_field_error(register_email_error)
|
||
|
||
# 验证密码
|
||
var password_validation = validate_password(password)
|
||
if not password_validation.valid:
|
||
print("密码验证失败: ", password_validation.message)
|
||
show_field_error(register_password_error, password_validation.message)
|
||
is_valid = false
|
||
else:
|
||
hide_field_error(register_password_error)
|
||
|
||
# 验证确认密码
|
||
var confirm_validation = validate_confirm_password(password, confirm)
|
||
if not confirm_validation.valid:
|
||
print("确认密码验证失败: ", confirm_validation.message)
|
||
show_field_error(register_confirm_error, confirm_validation.message)
|
||
is_valid = false
|
||
else:
|
||
hide_field_error(register_confirm_error)
|
||
|
||
# 验证验证码
|
||
var code_validation = validate_verification_code(verification_code)
|
||
if not code_validation.valid:
|
||
print("验证码格式验证失败: ", code_validation.message)
|
||
show_field_error(verification_error, code_validation.message)
|
||
is_valid = false
|
||
else:
|
||
hide_field_error(verification_error)
|
||
|
||
# 检查是否已发送验证码(检查当前邮箱)
|
||
var current_email_input = register_email.text.strip_edges()
|
||
var has_sent_code = false
|
||
|
||
if verification_codes_sent.has(current_email_input):
|
||
var email_data = verification_codes_sent[current_email_input]
|
||
has_sent_code = email_data.get("sent", false)
|
||
|
||
if not has_sent_code:
|
||
print("当前邮箱验证码未发送,email = ", current_email_input)
|
||
show_toast("请先获取邮箱验证码", false)
|
||
is_valid = false
|
||
|
||
print("表单验证结果: ", is_valid)
|
||
return is_valid
|
||
|
||
func _on_to_register_pressed():
|
||
show_toast('验证码登录功能待实现', false)
|
||
|
||
func _on_forgot_password_pressed():
|
||
show_toast('忘记密码功能待实现', false)
|
||
|
||
func _on_register_link_pressed():
|
||
show_register_panel()
|
||
|
||
func _on_to_login_pressed():
|
||
show_login_panel()
|
||
|
||
func _on_login_enter(_text: String):
|
||
_on_login_pressed()
|
||
|
||
func _input(event):
|
||
if event.is_action_pressed("ui_cancel"):
|
||
get_tree().quit()
|