From 92c4eaed07fc78c2d227bb5bcd2b32fd8b1e9f52 Mon Sep 17 00:00:00 2001 From: moyin <2443444649@qq.com> Date: Thu, 25 Dec 2025 23:12:49 +0800 Subject: [PATCH] =?UTF-8?q?refactor=EF=BC=9A=E9=87=8D=E6=9E=84=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E5=9C=BA=E6=99=AF=E5=92=8C=E7=BD=91=E7=BB=9C=E9=9B=86?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构AuthScene脚本,集成新的NetworkManager - 优化用户界面布局和交互逻辑 - 改进错误处理和用户反馈机制 - 统一网络请求处理流程 --- scenes/auth_scene.tscn | 50 +- scripts/scenes/AuthScene.gd | 974 +++++++++++++++++------------------- 2 files changed, 498 insertions(+), 526 deletions(-) diff --git a/scenes/auth_scene.tscn b/scenes/auth_scene.tscn index 5383f92..28a581b 100644 --- a/scenes/auth_scene.tscn +++ b/scenes/auth_scene.tscn @@ -221,6 +221,50 @@ theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) placeholder_text = "请输入密码" secret = true +[node name="VerificationContainer" type="VBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm"] +visible = false +layout_mode = 2 + +[node name="VerificationLabelContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer"] +layout_mode = 2 + +[node name="VerificationLabel" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(0, 0, 0, 1) +text = "验证码" + +[node name="RequiredStar" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer"] +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) +text = " *" + +[node name="Spacer" type="Control" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="VerificationError" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer"] +visible = false +layout_mode = 2 +theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) +theme_override_font_sizes/font_size = 12 +text = "请输入验证码" +horizontal_alignment = 2 + +[node name="VerificationInputContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer"] +layout_mode = 2 + +[node name="VerificationInput" type="LineEdit" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationInputContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) +placeholder_text = "请输入6位验证码" +max_length = 6 + +[node name="GetCodeBtn" type="Button" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationInputContainer"] +layout_mode = 2 +text = "获取验证码" + [node name="CheckboxContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm"] layout_mode = 2 @@ -256,12 +300,6 @@ layout_mode = 2 theme = SubResource("Theme_button") text = "密码登录" -[node name="ToRegisterBtn" type="Button" parent="CenterContainer/LoginPanel/VBoxContainer/ButtonContainer"] -custom_minimum_size = Vector2(100, 35) -layout_mode = 2 -theme = SubResource("Theme_button") -text = "验证码登录" - [node name="BottomLinks" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer"] layout_mode = 2 alignment = 1 diff --git a/scripts/scenes/AuthScene.gd b/scripts/scenes/AuthScene.gd index a4e467e..4c235c0 100644 --- a/scripts/scenes/AuthScene.gd +++ b/scripts/scenes/AuthScene.gd @@ -3,9 +3,6 @@ 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 @@ -17,11 +14,15 @@ const API_BASE_URL = "https://whaletownend.xinghangee.icu" # 登录表单 @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_verification: LineEdit = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationInputContainer/VerificationInput @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 login_verification_error: Label = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer/VerificationError +@onready var password_container: VBoxContainer = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer +@onready var verification_container: VBoxContainer = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer +@onready var get_code_btn: Button = $CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationInputContainer/GetCodeBtn @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 @@ -42,9 +43,6 @@ const API_BASE_URL = "https://whaletownend.xinghangee.icu" @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 @@ -53,59 +51,49 @@ 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 = "" # 当前正在倒计时的邮箱 +var verification_codes_sent: Dictionary = {} +var code_cooldown: float = 60.0 +var cooldown_timer: Timer = null +var current_email: String = "" + +# 登录模式枚举 +enum LoginMode { + PASSWORD, # 密码登录模式 + VERIFICATION # 验证码登录模式 +} + +# 当前登录模式 +var current_login_mode: LoginMode = LoginMode.PASSWORD + +# 网络请求管理 +var active_request_ids: Array = [] func _ready(): - # 获取HTTP请求节点 - http_request = $HTTPRequest - http_request.request_completed.connect(_on_http_request_completed) - - # 连接信号 connect_signals() - - # 初始显示登录界面 show_login_panel() - - # 测试Toast系统(延迟一下确保节点已初始化) + update_login_mode_ui() # 初始化登录模式UI await get_tree().process_frame - print("测试Toast系统...") - show_toast("认证系统已加载", true) - - # 测试网络连接 + print("认证系统已加载") 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) + var request_id = NetworkManager.get_app_status(_on_network_test_response) + if request_id != "": + active_request_ids.append(request_id) + print("网络测试请求已发送,ID: ", request_id) else: - print("网络测试请求已发送,等待响应...") - + 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) + get_code_btn.pressed.connect(_on_get_login_code_pressed) # 注册界面按钮 register_btn.pressed.connect(_on_register_pressed) @@ -118,6 +106,7 @@ func connect_signals(): # 登录表单失焦验证 login_username.focus_exited.connect(_on_login_username_focus_exited) login_password.focus_exited.connect(_on_login_password_focus_exited) + login_verification.focus_exited.connect(_on_login_verification_focus_exited) # 注册表单失焦验证 register_username.focus_exited.connect(_on_register_username_focus_exited) @@ -143,33 +132,103 @@ func show_register_panel(): register_panel.visible = true register_username.grab_focus() +# 更新登录模式UI +func update_login_mode_ui(): + if current_login_mode == LoginMode.PASSWORD: + # 密码登录模式 + login_btn.text = "验证码登录" + forgot_password_btn.text = "忘记密码" + + # 显示密码输入框,隐藏验证码输入框 + password_container.visible = true + verification_container.visible = false + + # 清空验证码输入框和错误提示 + login_verification.text = "" + hide_field_error(login_verification_error) + + else: # VERIFICATION mode + # 验证码登录模式 + login_btn.text = "密码登录" + forgot_password_btn.text = "获取验证码" + + # 隐藏密码输入框,显示验证码输入框 + password_container.visible = false + verification_container.visible = true + + # 清空密码输入框和错误提示 + login_password.text = "" + hide_field_error(login_password_error) + # 这里需要根据实际UI结构调整 + +# 切换登录模式 +func toggle_login_mode(): + if current_login_mode == LoginMode.PASSWORD: + current_login_mode = LoginMode.VERIFICATION + else: + current_login_mode = LoginMode.PASSWORD + + update_login_mode_ui() + + # 清空输入框 + login_username.text = "" + login_password.text = "" + hide_field_error(login_username_error) + hide_field_error(login_password_error) + +# ============ 按钮事件处理 ============ + func _on_main_button_pressed(): + # 根据当前登录模式执行不同的登录逻辑 + if current_login_mode == LoginMode.PASSWORD: + _execute_password_login() + else: + _execute_verification_login() + +func _execute_password_login(): 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) + var request_id = NetworkManager.login(username, password, _on_login_response) + if request_id != "": + active_request_ids.append(request_id) + else: + restore_button(main_btn, "进入小镇") + show_toast('网络请求失败', false) -func _on_login_pressed(): - if not validate_login_form(): +func _execute_verification_login(): + var identifier = login_username.text.strip_edges() + var verification_code = login_verification.text.strip_edges() + + if identifier.is_empty(): + show_field_error(login_username_error, "请输入用户名/手机/邮箱") + login_username.grab_focus() return - var username = login_username.text.strip_edges() - var password = login_password.text + if verification_code.is_empty(): + show_field_error(login_verification_error, "请输入验证码") + login_verification.grab_focus() + return - # 显示加载状态 - show_loading(login_btn, "登录中...") - show_toast('正在验证登录信息...', true) + show_loading(main_btn, "登录中...") + show_toast('正在验证验证码...', true) - # 发送登录请求 - send_login_request(username, password) + var request_id = NetworkManager.verification_code_login(identifier, verification_code, _on_verification_login_response) + if request_id != "": + active_request_ids.append(request_id) + else: + restore_button(main_btn, "进入小镇") + show_toast('网络请求失败', false) + +func _on_login_pressed(): + # 现在这个按钮用于切换登录模式 + toggle_login_mode() func _on_register_pressed(): print("注册按钮被点击") @@ -186,17 +245,15 @@ func _on_register_pressed(): var password = register_password.text var verification_code = verification_input.text.strip_edges() - # 显示加载状态 show_loading(register_btn, "注册中...") - show_toast('正在验证邮箱验证码...', true) + show_toast('正在创建账户...', true) - # 先验证邮箱验证码,然后注册 - verify_email_then_register(username, email, password, verification_code) + # 直接调用注册接口,让服务器端处理验证码验证 + send_register_request(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) @@ -205,7 +262,7 @@ func _on_send_code_pressed(): 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 @@ -216,13 +273,10 @@ func _on_send_code_pressed(): 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] = {} @@ -230,338 +284,218 @@ func _on_send_code_pressed(): 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 = "" + var request_id = NetworkManager.send_email_verification(email, _on_send_code_response) + if request_id != "": + active_request_ids.append(request_id) 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 = "" + reset_verification_button() + +func _on_register_link_pressed(): + show_register_panel() + +func _on_get_login_code_pressed(): + var identifier = login_username.text.strip_edges() + + if identifier.is_empty(): + show_field_error(login_username_error, "请先输入用户名/手机/邮箱") + login_username.grab_focus() + return + + show_loading(get_code_btn, "发送中...") + show_toast('正在发送登录验证码...', true) + + var request_id = NetworkManager.send_login_verification_code(identifier, _on_send_login_code_response) + if request_id != "": + active_request_ids.append(request_id) + else: + restore_button(get_code_btn, "获取验证码") + show_toast('网络请求失败', false) + +func _on_to_login_pressed(): + show_login_panel() + +func _on_login_enter(_text: String): + _on_login_pressed() + +func _on_forgot_password_pressed(): + var identifier = login_username.text.strip_edges() + + if identifier.is_empty(): + show_toast('请先输入邮箱或手机号', false) + login_username.grab_focus() + return + + if not is_valid_email(identifier) and not is_valid_phone(identifier): + show_toast('请输入有效的邮箱或手机号', false) + login_username.grab_focus() + return + + if current_login_mode == LoginMode.PASSWORD: + # 密码登录模式:发送密码重置验证码 + show_loading(forgot_password_btn, "发送中...") + show_toast('正在发送密码重置验证码...', true) + + var request_id = NetworkManager.forgot_password(identifier, _on_forgot_password_response) + if request_id != "": + active_request_ids.append(request_id) + else: + restore_button(forgot_password_btn, "忘记密码") + show_toast('网络请求失败', false) + else: + # 验证码登录模式:发送登录验证码 + show_loading(forgot_password_btn, "发送中...") + show_toast('正在发送登录验证码...', true) + + var request_id = NetworkManager.send_login_verification_code(identifier, _on_send_login_code_response) + if request_id != "": + active_request_ids.append(request_id) + else: + restore_button(forgot_password_btn, "获取验证码") + show_toast('网络请求失败', false) +# ============ 网络响应处理 ============ -# 发送注册请求 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: + var request_id = NetworkManager.register(username, password, username, email, verification_code, _on_register_response) + if request_id != "": + active_request_ids.append(request_id) + else: 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() +func _on_network_test_response(success: bool, data: Dictionary, error_info: Dictionary): + print("=== 网络测试响应处理 ===") + print("成功: ", success) + print("数据: ", data) - print("=== HTTP响应接收 ===") - print("请求类型: ", current_request_type) - print("响应状态码: ", response_code) - print("响应头: ", _headers) - print("响应体: ", response_text) - print("响应体长度: ", body.size(), " 字节") + var result = ResponseHandler.handle_network_test_response(success, data, error_info) + + if result.should_show_toast: + show_toast(result.message, result.success) + +func _on_login_response(success: bool, data: Dictionary, error_info: Dictionary): + print("=== 登录响应处理 ===") + print("成功: ", success) + print("数据: ", data) + print("错误信息: ", error_info) - # 恢复按钮状态(排除验证码按钮,因为它有自己的状态管理) - 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 + var result = ResponseHandler.handle_login_response(success, data, error_info) - # 解析JSON响应 - var json = JSON.new() - var parse_result = json.parse(response_text) - if parse_result != OK: - show_toast('服务器响应格式错误', false) - current_request_type = "" - return + if result.should_show_toast: + show_toast(result.message, result.success) - 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) + if result.success: + var username = login_username.text.strip_edges() + if data.has("data") and data.data.has("user") and data.data.user.has("username"): + username = data.data.user.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) -# 处理网络测试响应 -func handle_network_test_response(response_code: int, data: Dictionary): - print("=== 网络测试响应 ===") - print("状态码: ", response_code) - print("响应数据: ", data) +func _on_send_code_response(success: bool, data: Dictionary, error_info: Dictionary): + print("=== 发送验证码响应处理 ===") + print("成功: ", success) + print("数据: ", data) - if response_code == 200: - print("✅ 网络连接正常,后端服务可访问") - show_toast("网络连接正常", true) + var result = ResponseHandler.handle_send_verification_code_response(success, data, error_info) + + if result.should_show_toast: + show_toast(result.message, result.success) + + if not result.success: + reset_verification_button() + +func _on_send_login_code_response(success: bool, data: Dictionary, error_info: Dictionary): + print("=== 发送登录验证码响应处理 ===") + print("成功: ", success) + print("数据: ", data) + + # 恢复按钮状态 + if current_login_mode == LoginMode.PASSWORD: + restore_button(forgot_password_btn, "忘记密码") else: - print("❌ 网络连接异常,状态码: ", response_code) - show_toast("网络连接异常: " + str(response_code), false) + restore_button(forgot_password_btn, "获取验证码") + restore_button(get_code_btn, "获取验证码") + + var result = ResponseHandler.handle_send_login_code_response(success, data, error_info) + + if result.should_show_toast: + show_toast(result.message, result.success) -# 处理登录响应 -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 _on_verification_login_response(success: bool, data: Dictionary, error_info: Dictionary): + print("=== 验证码登录响应处理 ===") + print("成功: ", success) + print("数据: ", data) + print("错误信息: ", error_info) + + restore_button(main_btn, "进入小镇") + + var result = ResponseHandler.handle_verification_code_login_response(success, data, error_info) + + if result.should_show_toast: + show_toast(result.message, result.success) + + if result.success: + var username = login_username.text.strip_edges() + if data.has("data") and data.data.has("user") and data.data.user.has("username"): + username = data.data.user.username + + login_username.text = "" + hide_field_error(login_username_error) + + await get_tree().create_timer(1.0).timeout + login_success.emit(username) -# 处理验证码响应 -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 _on_forgot_password_response(success: bool, data: Dictionary, error_info: Dictionary): + print("=== 忘记密码响应处理 ===") + print("成功: ", success) + print("数据: ", data) + + restore_button(forgot_password_btn, "忘记密码") + + # 使用通用的发送验证码响应处理 + var result = ResponseHandler.handle_send_login_code_response(success, data, error_info) + + if result.should_show_toast: + show_toast(result.message, result.success) + + if result.success: + show_toast("密码重置验证码已发送,请查收邮件", true) -# 处理邮箱验证响应 -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 _on_register_response(success: bool, data: Dictionary, error_info: Dictionary): + print("=== 注册响应处理 ===") + print("成功: ", success) + print("数据: ", data) + + restore_button(register_btn, "注册") + + var result = ResponseHandler.handle_register_response(success, data, error_info) + + if result.should_show_toast: + show_toast(result.message, result.success) + + if result.success: + clear_register_form() + show_login_panel() + login_username.text = register_username.text.strip_edges() # 使用注册时的用户名 +# ============ 验证码冷却管理 ============ -# 处理注册响应 -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 @@ -569,15 +503,12 @@ func start_cooldown_timer(email: String): 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 @@ -587,36 +518,29 @@ func _on_cooldown_timer_timeout(): 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 = "" @@ -624,11 +548,9 @@ func clear_register_form(): 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) @@ -637,159 +559,215 @@ func clear_register_form(): # ============ Toast消息系统 ============ -# 显示Toast消息 +# 检测文本是否包含中文字符 +func contains_chinese(text: String) -> bool: + for i in range(text.length()): + var char_code = text.unicode_at(i) + # 中文字符的Unicode范围 + if (char_code >= 0x4E00 and char_code <= 0x9FFF) or \ + (char_code >= 0x3400 and char_code <= 0x4DBF) or \ + (char_code >= 0x20000 and char_code <= 0x2A6DF): + return true + return false + func show_toast(message: String, is_success: bool = true): print("显示Toast消息: ", message, " 成功: ", is_success) - # 确保容器存在 if toast_container == null: print("错误: toast_container 节点不存在") return - # 创建新的Toast实例 + # 异步创建Toast实例 create_toast_instance(message, is_success) -# 创建Toast实例 func create_toast_instance(message: String, is_success: bool): toast_counter += 1 - # 创建Toast Panel + # Web平台字体处理 + var is_web = OS.get_name() == "Web" + + # 1. 创建Toast Panel(方框UI) 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) # 绿色背景 + style.bg_color = Color(0.15, 0.7, 0.15, 0.95) + style.border_color = Color(0.2, 0.9, 0.2, 0.9) else: - style.bg_color = Color(0.8, 0.2, 0.2, 0.95) # 红色背景 + style.bg_color = Color(0.7, 0.15, 0.15, 0.95) + style.border_color = Color(0.9, 0.2, 0.2, 0.9) - 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 + style.border_width_left = 3 + style.border_width_top = 3 + style.border_width_right = 3 + style.border_width_bottom = 3 + style.corner_radius_top_left = 12 + style.corner_radius_top_right = 12 + style.corner_radius_bottom_left = 12 + style.corner_radius_bottom_right = 12 + style.shadow_color = Color(0, 0, 0, 0.3) + style.shadow_size = 4 + style.shadow_offset = Vector2(2, 2) toast_panel.add_theme_stylebox_override("panel", style) - # 设置Toast尺寸和位置(右上角外侧开始) - var toast_width = 280 - var toast_height = 50 + # 设置Toast基本尺寸 + var toast_width = 320 + toast_panel.size = Vector2(toast_width, 60) + + # 2. 创建VBoxContainer + var vbox = VBoxContainer.new() + vbox.add_theme_constant_override("separation", 0) + vbox.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + vbox.alignment = BoxContainer.ALIGNMENT_CENTER + + # 3. 创建CenterContainer + var center_container = CenterContainer.new() + center_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL + center_container.size_flags_vertical = Control.SIZE_SHRINK_CENTER + + # 4. 创建Label(文字控件) + var text_label = Label.new() + text_label.text = message + text_label.add_theme_color_override("font_color", Color(1, 1, 1, 1)) + text_label.add_theme_font_size_override("font_size", 14) + + # 平台特定的字体处理 + if is_web: + print("Web平台Toast字体处理") + # Web平台使用主题文件 + var chinese_theme = load("res://assets/ui/chinese_theme.tres") + if chinese_theme: + text_label.theme = chinese_theme + print("Web平台应用中文主题") + else: + print("Web平台中文主题加载失败") + else: + print("桌面平台Toast字体处理") + # 桌面平台直接加载中文字体 + var desktop_chinese_font = load("res://assets/fonts/msyh.ttc") + if desktop_chinese_font: + text_label.add_theme_font_override("font", desktop_chinese_font) + print("桌面平台使用中文字体") + + text_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + text_label.custom_minimum_size = Vector2(280, 0) + text_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + text_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + + # 组装控件层级 + center_container.add_child(text_label) + vbox.add_child(center_container) + toast_panel.add_child(vbox) + + # 计算位置 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)) # 垂直堆叠 + var start_x = get_viewport().get_visible_rect().size.x + var final_x = get_viewport().get_visible_rect().size.x - toast_width - margin + # 计算Y位置 + var y_position = margin + for existing_toast in active_toasts: + if is_instance_valid(existing_toast): + y_position += existing_toast.size.y + 15 + + # 设置初始位置 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) - # 执行滑入动画(快慢快) + # 等待一帧让布局系统计算尺寸 + await get_tree().process_frame + + # 让Toast高度自适应内容 + var content_size = vbox.get_combined_minimum_size() + var final_height = max(60, content_size.y + 20) # 最小60,加20像素边距 + toast_panel.size.y = final_height + + # 重新排列所有Toast + rearrange_toasts() + + # 开始动画 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.set_ease(Tween.EASE_OUT) + tween.set_trans(Tween.TRANS_BACK) - # 滑入动画 - tween.tween_property(toast_panel, "position:x", final_x, 0.5) + tween.parallel().tween_property(toast_panel, "position:x", final_x, 0.6) + tween.parallel().tween_property(toast_panel, "modulate:a", 1.0, 0.4) - # 等待2秒后滑出 - await get_tree().create_timer(2.0).timeout + toast_panel.modulate.a = 0.0 + + await get_tree().create_timer(3.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_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) + tween.parallel().tween_property(toast_panel, "position:x", end_x, 0.4) + tween.parallel().tween_property(toast_panel, "modulate:a", 0.0, 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 + var current_y = margin 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) + tween.tween_property(toast, "position:y", current_y, 0.2) + current_y += toast.size.y + 15 + +# ============ UI工具方法 ============ -# 显示加载状态 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 is_valid_email(email: String) -> bool: + return StringUtils.is_valid_email(email) + +func is_valid_phone(phone: String) -> bool: + var regex = RegEx.new() + regex.compile("^\\+?[1-9]\\d{1,14}$") + return regex.search(phone) != null + +# ============ 表单验证方法 ============ + func validate_username(username: String) -> Dictionary: var result = {"valid": false, "message": ""} @@ -797,21 +775,16 @@ func validate_username(username: String) -> Dictionary: 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 = "用户名只能包含字母、数字和下划线" + if not StringUtils.is_valid_username(username): + if username.length() > 50: + result.message = "用户名长度不能超过50字符" + else: + result.message = "用户名只能包含字母、数字和下划线" return result result.valid = true return result -# 验证邮箱 func validate_email(email: String) -> Dictionary: var result = {"valid": false, "message": ""} @@ -819,47 +792,16 @@ func validate_email(email: String) -> Dictionary: result.message = "邮箱不能为空" return result - if not is_valid_email(email): + if not StringUtils.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 + return StringUtils.validate_password_strength(password) -# 验证确认密码 func validate_confirm_password(password: String, confirm: String) -> Dictionary: var result = {"valid": false, "message": ""} @@ -874,7 +816,6 @@ func validate_confirm_password(password: String, confirm: String) -> Dictionary: result.valid = true return result -# 验证验证码 func validate_verification_code(code: String) -> Dictionary: var result = {"valid": false, "message": ""} @@ -886,7 +827,6 @@ func validate_verification_code(code: String) -> Dictionary: result.message = "验证码必须是6位数字" return result - # 验证是否为纯数字 for i in range(code.length()): var character = code[i] if not (character >= '0' and character <= '9'): @@ -896,7 +836,7 @@ func validate_verification_code(code: String) -> Dictionary: result.valid = true return result -# ============ 登录表单验证事件 ============ +# ============ 表单验证事件 ============ func _on_login_username_focus_exited(): var username = login_username.text.strip_edges() @@ -912,7 +852,16 @@ func _on_login_password_focus_exited(): else: hide_field_error(login_password_error) -# ============ 注册表单验证事件 ============ +func _on_login_verification_focus_exited(): + var verification_code = login_verification.text.strip_edges() + if verification_code.is_empty(): + show_field_error(login_verification_error, "验证码不能为空") + elif verification_code.length() != 6: + show_field_error(login_verification_error, "验证码必须是6位数字") + elif not verification_code.is_valid_int(): + show_field_error(login_verification_error, "验证码只能包含数字") + else: + hide_field_error(login_verification_error) func _on_register_username_focus_exited(): var username = register_username.text.strip_edges() @@ -937,7 +886,6 @@ func _on_register_password_focus_exited(): 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() @@ -961,7 +909,6 @@ func _on_verification_focus_exited(): # ============ 实时输入验证事件 ============ 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) @@ -980,10 +927,8 @@ func _on_register_confirm_text_changed(new_text: String): 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 @@ -1004,7 +949,6 @@ func validate_login_form() -> bool: return is_valid -# 验证注册表单 func validate_register_form() -> bool: print("开始验证注册表单") var is_valid = true @@ -1017,7 +961,6 @@ func validate_register_form() -> bool: 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) @@ -1026,7 +969,6 @@ func validate_register_form() -> bool: else: hide_field_error(register_username_error) - # 验证邮箱 var email_validation = validate_email(email) if not email_validation.valid: print("邮箱验证失败: ", email_validation.message) @@ -1035,7 +977,6 @@ func validate_register_form() -> bool: else: hide_field_error(register_email_error) - # 验证密码 var password_validation = validate_password(password) if not password_validation.valid: print("密码验证失败: ", password_validation.message) @@ -1044,7 +985,6 @@ func validate_register_form() -> bool: else: hide_field_error(register_password_error) - # 验证确认密码 var confirm_validation = validate_confirm_password(password, confirm) if not confirm_validation.valid: print("确认密码验证失败: ", confirm_validation.message) @@ -1053,7 +993,6 @@ func validate_register_form() -> bool: else: hide_field_error(register_confirm_error) - # 验证验证码 var code_validation = validate_verification_code(verification_code) if not code_validation.valid: print("验证码格式验证失败: ", code_validation.message) @@ -1062,7 +1001,6 @@ func validate_register_form() -> bool: else: hide_field_error(verification_error) - # 检查是否已发送验证码(检查当前邮箱) var current_email_input = register_email.text.strip_edges() var has_sent_code = false @@ -1078,20 +1016,16 @@ func validate_register_form() -> bool: 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 _exit_tree(): + for request_id in active_request_ids: + NetworkManager.cancel_request(request_id) + active_request_ids.clear() + + if cooldown_timer != null: + cooldown_timer.queue_free() + cooldown_timer = null func _input(event): if event.is_action_pressed("ui_cancel"):