From d256249789fb2baf0a463d8406cd7cd13fb966d0 Mon Sep 17 00:00:00 2001 From: moyin <2443444649@qq.com> Date: Fri, 2 Jan 2026 21:17:56 +0800 Subject: [PATCH 1/6] =?UTF-8?q?refactor=EF=BC=9A=E5=B0=86MainScene?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E5=88=B0scenes=E6=A0=B9=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将MainScene从scenes/Maps/移动到scenes/根目录 - 更新project.godot中的主场景路径配置 - 符合项目结构规范,MainScene作为图像显示入口文件 --- project.godot | 2 +- scenes/{Maps => }/MainScene.gd | 11 +++++++++++ scenes/MainScene.gd.uid | 1 + scenes/{Maps/main_scene.tscn => MainScene.tscn} | 6 +++--- scenes/Maps/MainScene.gd.uid | 1 - 5 files changed, 16 insertions(+), 5 deletions(-) rename scenes/{Maps => }/MainScene.gd (88%) create mode 100644 scenes/MainScene.gd.uid rename scenes/{Maps/main_scene.tscn => MainScene.tscn} (94%) delete mode 100644 scenes/Maps/MainScene.gd.uid diff --git a/project.godot b/project.godot index 49e9865..18d233f 100644 --- a/project.godot +++ b/project.godot @@ -11,7 +11,7 @@ config_version=5 [application] config/name="whaleTown" -run/main_scene="res://scenes/maps/main_scene.tscn" +run/main_scene="res://scenes/MainScene.tscn" config/features=PackedStringArray("4.5", "Forward Plus") config/icon="res://icon.svg" diff --git a/scenes/Maps/MainScene.gd b/scenes/MainScene.gd similarity index 88% rename from scenes/Maps/MainScene.gd rename to scenes/MainScene.gd index 3818a01..b1b7f24 100644 --- a/scenes/Maps/MainScene.gd +++ b/scenes/MainScene.gd @@ -1,5 +1,16 @@ extends Control +# ============================================================================ +# MainScene.gd - 主场景控制器 +# ============================================================================ +# 这是游戏的主入口场景,负责管理所有图像显示和界面切换 +# 功能包括: +# - 登录/注册界面管理 +# - 主游戏界面显示 +# - 用户状态管理 +# - 游戏功能模块入口 +# ============================================================================ + # 场景节点引用 @onready var auth_scene: Control = $AuthScene @onready var main_game_ui: Control = $MainGameUI diff --git a/scenes/MainScene.gd.uid b/scenes/MainScene.gd.uid new file mode 100644 index 0000000..31e44ef --- /dev/null +++ b/scenes/MainScene.gd.uid @@ -0,0 +1 @@ +uid://ghehm4srs0ho diff --git a/scenes/Maps/main_scene.tscn b/scenes/MainScene.tscn similarity index 94% rename from scenes/Maps/main_scene.tscn rename to scenes/MainScene.tscn index cb02a22..4162235 100644 --- a/scenes/Maps/main_scene.tscn +++ b/scenes/MainScene.tscn @@ -1,7 +1,7 @@ -[gd_scene load_steps=3 format=3 uid="uid://21a49e14a0c58d7941d04142a5bf9ddc"] +[gd_scene load_steps=3 format=3 uid="uid://cjabtnqbdd2ey"] -[ext_resource type="Script" path="res://scenes/maps/MainScene.gd" id="1_script"] -[ext_resource type="PackedScene" uid="uid://by7m8snb4xllf" path="res://scenes/ui/LoginWindow.tscn" id="2_main"] +[ext_resource type="Script" path="res://scenes/MainScene.gd" id="1_script"] +[ext_resource type="PackedScene" uid="uid://by7m8snb4xllf" path="res://scenes/ui/AuthScene.tscn" id="2_main"] [node name="Main" type="Control"] layout_mode = 3 diff --git a/scenes/Maps/MainScene.gd.uid b/scenes/Maps/MainScene.gd.uid deleted file mode 100644 index 2d135e3..0000000 --- a/scenes/Maps/MainScene.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cn2xjgj3h847p From 5f915c61b6a38fe78a5b470fe9bc8aec17e9528e Mon Sep 17 00:00:00 2001 From: moyin <2443444649@qq.com> Date: Fri, 2 Jan 2026 21:18:38 +0800 Subject: [PATCH 2/6] =?UTF-8?q?refactor=EF=BC=9AAuthScene=E8=A7=A3?= =?UTF-8?q?=E8=80=A6=E9=87=8D=E6=9E=84=EF=BC=8C=E5=AE=9E=E7=8E=B0=E8=A7=86?= =?UTF-8?q?=E5=9B=BE=E4=B8=8E=E4=B8=9A=E5=8A=A1=E9=80=BB=E8=BE=91=E5=88=86?= =?UTF-8?q?=E7=A6=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建AuthManager.gd:负责所有认证业务逻辑 - 用户登录/注册逻辑 - 表单验证逻辑 - 验证码管理逻辑 - 网络请求管理 - 创建ToastManager.gd:负责Toast消息管理 - Toast创建和显示 - 动画和生命周期管理 - 支持成功/失败样式 - 重构AuthScene.gd:纯视图层实现 - 只负责UI交互和显示 - 通过信号与业务层通信 - 移除所有业务逻辑代码 - 修复GDScript警告: - 未使用参数添加下划线前缀 - 修复变量名与基类方法冲突 - 修复EventSystem中的try语法错误 - 修复AuthManager中的方法名不匹配错误 符合docs中的架构要求,实现完全解耦 --- _Core/managers/AuthManager.gd | 589 +++++++++++++++ _Core/managers/AuthManager.gd.uid | 1 + _Core/managers/ToastManager.gd | 234 ++++++ _Core/managers/ToastManager.gd.uid | 1 + scenes/ui/AuthScene.gd | 1135 +++++++++------------------- 5 files changed, 1181 insertions(+), 779 deletions(-) create mode 100644 _Core/managers/AuthManager.gd create mode 100644 _Core/managers/AuthManager.gd.uid create mode 100644 _Core/managers/ToastManager.gd create mode 100644 _Core/managers/ToastManager.gd.uid diff --git a/_Core/managers/AuthManager.gd b/_Core/managers/AuthManager.gd new file mode 100644 index 0000000..2f54ddb --- /dev/null +++ b/_Core/managers/AuthManager.gd @@ -0,0 +1,589 @@ +class_name AuthManager + +# ============================================================================ +# AuthManager.gd - 认证管理器 +# ============================================================================ +# 认证系统的业务逻辑管理器,负责处理所有认证相关的业务逻辑 +# +# 核心职责: +# - 用户登录业务逻辑(密码登录 + 验证码登录) +# - 用户注册业务逻辑 +# - 表单验证逻辑 +# - 验证码管理逻辑 +# - 网络请求管理 +# - 响应处理和状态管理 +# +# 使用方式: +# var auth_manager = AuthManager.new() +# auth_manager.login_success.connect(_on_login_success) +# auth_manager.execute_password_login(username, password) +# +# 注意事项: +# - 这是业务逻辑层,不包含任何UI相关代码 +# - 通过信号与UI层通信 +# - 所有验证逻辑都在这里实现 +# ============================================================================ + +extends RefCounted + +# ============ 信号定义 ============ + +# 登录成功信号 +signal login_success(username: String) + +# 登录失败信号 +signal login_failed(message: String) + +# 注册成功信号 +signal register_success(message: String) + +# 注册失败信号 +signal register_failed(message: String) + +# 验证码发送成功信号 +signal verification_code_sent(message: String) + +# 验证码发送失败信号 +signal verification_code_failed(message: String) + +# 表单验证失败信号 +signal form_validation_failed(field: String, message: String) + +# 网络状态变化信号 +signal network_status_changed(is_connected: bool, message: String) + +# 按钮状态变化信号 +signal button_state_changed(button_name: String, is_loading: bool, text: String) + +# Toast消息信号 +signal show_toast_message(message: String, is_success: bool) + +# ============ 枚举定义 ============ + +# 登录模式枚举 +enum LoginMode { + PASSWORD, # 密码登录模式 + VERIFICATION # 验证码登录模式 +} + +# ============ 成员变量 ============ + +# 登录状态 +var current_login_mode: LoginMode = LoginMode.PASSWORD +var is_processing: bool = false + +# 验证码管理 +var verification_codes_sent: Dictionary = {} +var code_cooldown: float = 60.0 +var current_email: String = "" + +# 网络请求管理 +var active_request_ids: Array = [] + +# ============ 生命周期方法 ============ + +# 初始化管理器 +func _init(): + print("AuthManager 初始化完成") + +# 清理资源 +func cleanup(): + # 取消所有活动的网络请求 + for request_id in active_request_ids: + NetworkManager.cancel_request(request_id) + active_request_ids.clear() + +# ============ 登录相关方法 ============ + +# 执行密码登录 +# +# 参数: +# username: String - 用户名/邮箱 +# password: String - 密码 +# +# 功能: +# - 验证输入参数 +# - 发送登录请求 +# - 处理响应结果 +func execute_password_login(username: String, password: String): + if is_processing: + show_toast_message.emit("请等待当前操作完成", false) + return + + # 验证输入 + var validation_result = validate_login_inputs(username, password) + if not validation_result.valid: + form_validation_failed.emit(validation_result.field, validation_result.message) + return + + # 设置处理状态 + is_processing = true + button_state_changed.emit("main_btn", true, "登录中...") + show_toast_message.emit("正在验证登录信息...", true) + + # 发送网络请求 + var request_id = NetworkManager.login(username, password, _on_login_response) + if request_id != "": + active_request_ids.append(request_id) + else: + _reset_login_state() + show_toast_message.emit("网络请求失败", false) + +# 执行验证码登录 +# +# 参数: +# identifier: String - 用户标识符 +# verification_code: String - 验证码 +func execute_verification_login(identifier: String, verification_code: String): + if is_processing: + show_toast_message.emit("请等待当前操作完成", false) + return + + # 验证输入 + if identifier.is_empty(): + form_validation_failed.emit("username", "请输入用户名/手机/邮箱") + return + + if verification_code.is_empty(): + form_validation_failed.emit("verification", "请输入验证码") + return + + # 设置处理状态 + is_processing = true + button_state_changed.emit("main_btn", true, "登录中...") + show_toast_message.emit("正在验证验证码...", true) + + # 发送网络请求 + var request_id = NetworkManager.verification_code_login(identifier, verification_code, _on_verification_login_response) + if request_id != "": + active_request_ids.append(request_id) + else: + _reset_login_state() + show_toast_message.emit("网络请求失败", false) + +# 切换登录模式 +func toggle_login_mode(): + if current_login_mode == LoginMode.PASSWORD: + current_login_mode = LoginMode.VERIFICATION + else: + current_login_mode = LoginMode.PASSWORD + +# 获取当前登录模式 +func get_current_login_mode() -> LoginMode: + return current_login_mode + +# ============ 注册相关方法 ============ + +# 执行用户注册 +# +# 参数: +# username: String - 用户名 +# email: String - 邮箱 +# password: String - 密码 +# confirm_password: String - 确认密码 +# verification_code: String - 邮箱验证码 +func execute_register(username: String, email: String, password: String, confirm_password: String, verification_code: String): + if is_processing: + show_toast_message.emit("请等待当前操作完成", false) + return + + # 验证注册表单 + var validation_result = validate_register_form(username, email, password, confirm_password, verification_code) + if not validation_result.valid: + form_validation_failed.emit(validation_result.field, validation_result.message) + return + + # 设置处理状态 + is_processing = true + button_state_changed.emit("register_btn", true, "注册中...") + show_toast_message.emit("正在创建账户...", true) + + # 发送注册请求 + var request_id = NetworkManager.register(username, password, username, email, verification_code, _on_register_response) + if request_id != "": + active_request_ids.append(request_id) + else: + _reset_register_state() + show_toast_message.emit("网络请求失败", false) + +# ============ 验证码相关方法 ============ + +# 发送邮箱验证码 +# +# 参数: +# email: String - 邮箱地址 +func send_email_verification_code(email: String): + # 验证邮箱格式 + var email_validation = validate_email(email) + if not email_validation.valid: + form_validation_failed.emit("email", email_validation.message) + return + + # 检查冷却时间 + if not _can_send_verification_code(email): + var remaining = get_remaining_cooldown_time(email) + show_toast_message.emit("该邮箱请等待 %d 秒后再次发送" % remaining, false) + return + + # 记录发送状态 + _record_verification_code_sent(email) + + # 发送请求 + var request_id = NetworkManager.send_email_verification(email, _on_send_code_response) + if request_id != "": + active_request_ids.append(request_id) + else: + _reset_verification_code_state(email) + show_toast_message.emit("网络请求失败", false) + +# 发送登录验证码 +# +# 参数: +# identifier: String - 用户标识符 +func send_login_verification_code(identifier: String): + if identifier.is_empty(): + form_validation_failed.emit("username", "请先输入用户名/手机/邮箱") + return + + button_state_changed.emit("get_code_btn", true, "发送中...") + show_toast_message.emit("正在发送登录验证码...", 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: + button_state_changed.emit("get_code_btn", false, "获取验证码") + show_toast_message.emit("网络请求失败", false) + +# 发送密码重置验证码 +# +# 参数: +# identifier: String - 用户标识符 +func send_password_reset_code(identifier: String): + if identifier.is_empty(): + show_toast_message.emit("请先输入邮箱或手机号", false) + return + + if not _is_valid_identifier(identifier): + show_toast_message.emit("请输入有效的邮箱或手机号", false) + return + + button_state_changed.emit("forgot_password_btn", true, "发送中...") + show_toast_message.emit("正在发送密码重置验证码...", true) + + var request_id = NetworkManager.forgot_password(identifier, _on_forgot_password_response) + if request_id != "": + active_request_ids.append(request_id) + else: + button_state_changed.emit("forgot_password_btn", false, "忘记密码") + show_toast_message.emit("网络请求失败", false) + +# ============ 验证方法 ============ + +# 验证登录输入 +func validate_login_inputs(username: String, password: String) -> Dictionary: + var result = {"valid": false, "field": "", "message": ""} + + if username.is_empty(): + result.field = "username" + result.message = "用户名不能为空" + return result + + if password.is_empty(): + result.field = "password" + result.message = "密码不能为空" + return result + + result.valid = true + return result + +# 验证注册表单 +func validate_register_form(username: String, email: String, password: String, confirm_password: String, verification_code: String) -> Dictionary: + var result = {"valid": false, "field": "", "message": ""} + + # 验证用户名 + var username_validation = validate_username(username) + if not username_validation.valid: + result.field = "username" + result.message = username_validation.message + return result + + # 验证邮箱 + var email_validation = validate_email(email) + if not email_validation.valid: + result.field = "email" + result.message = email_validation.message + return result + + # 验证密码 + var password_validation = validate_password(password) + if not password_validation.valid: + result.field = "password" + result.message = password_validation.message + return result + + # 验证确认密码 + var confirm_validation = validate_confirm_password(password, confirm_password) + if not confirm_validation.valid: + result.field = "confirm" + result.message = confirm_validation.message + return result + + # 验证验证码 + var code_validation = validate_verification_code(verification_code) + if not code_validation.valid: + result.field = "verification" + result.message = code_validation.message + return result + + # 检查是否已发送验证码 + if not _has_sent_verification_code(email): + result.field = "verification" + result.message = "请先获取邮箱验证码" + return result + + result.valid = true + return result + +# 验证用户名 +func validate_username(username: String) -> Dictionary: + var result = {"valid": false, "message": ""} + + if username.is_empty(): + result.message = "用户名不能为空" + return result + + 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": ""} + + if email.is_empty(): + result.message = "邮箱不能为空" + return result + + if not StringUtils.is_valid_email(email): + result.message = "请输入有效的邮箱地址" + return result + + result.valid = true + return result + +# 验证密码 +func validate_password(password: String) -> Dictionary: + return StringUtils.validate_password_strength(password) + +# 验证确认密码 +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_response(success: bool, data: Dictionary, error_info: Dictionary): + _reset_login_state() + + var result = ResponseHandler.handle_login_response(success, data, error_info) + + if result.should_show_toast: + show_toast_message.emit(result.message, result.success) + + if result.success: + var username = "" + if data.has("data") and data.data.has("user") and data.data.user.has("username"): + username = data.data.user.username + + # 延迟发送登录成功信号 + await Engine.get_main_loop().create_timer(1.0).timeout + login_success.emit(username) + else: + login_failed.emit(result.message) + +# 处理验证码登录响应 +func _on_verification_login_response(success: bool, data: Dictionary, error_info: Dictionary): + _reset_login_state() + + var result = ResponseHandler.handle_verification_code_login_response(success, data, error_info) + + if result.should_show_toast: + show_toast_message.emit(result.message, result.success) + + if result.success: + var username = "" + if data.has("data") and data.data.has("user") and data.data.user.has("username"): + username = data.data.user.username + + await Engine.get_main_loop().create_timer(1.0).timeout + login_success.emit(username) + else: + login_failed.emit(result.message) + +# 处理注册响应 +func _on_register_response(success: bool, data: Dictionary, error_info: Dictionary): + _reset_register_state() + + var result = ResponseHandler.handle_register_response(success, data, error_info) + + if result.should_show_toast: + show_toast_message.emit(result.message, result.success) + + if result.success: + register_success.emit(result.message) + else: + register_failed.emit(result.message) + +# 处理发送验证码响应 +func _on_send_code_response(success: bool, data: Dictionary, error_info: Dictionary): + var result = ResponseHandler.handle_send_verification_code_response(success, data, error_info) + + if result.should_show_toast: + show_toast_message.emit(result.message, result.success) + + if result.success: + verification_code_sent.emit(result.message) + else: + verification_code_failed.emit(result.message) + _reset_verification_code_state(current_email) + +# 处理发送登录验证码响应 +func _on_send_login_code_response(success: bool, data: Dictionary, error_info: Dictionary): + button_state_changed.emit("get_code_btn", false, "获取验证码") + + var result = ResponseHandler.handle_send_login_code_response(success, data, error_info) + + if result.should_show_toast: + show_toast_message.emit(result.message, result.success) + +# 处理忘记密码响应 +func _on_forgot_password_response(success: bool, data: Dictionary, error_info: Dictionary): + button_state_changed.emit("forgot_password_btn", false, "忘记密码") + + var result = ResponseHandler.handle_send_login_code_response(success, data, error_info) + + if result.should_show_toast: + show_toast_message.emit(result.message, result.success) + +# ============ 网络测试 ============ + +# 测试网络连接 +func test_network_connection(): + var request_id = NetworkManager.get_app_status(_on_network_test_response) + if request_id != "": + active_request_ids.append(request_id) + +# 处理网络测试响应 +func _on_network_test_response(success: bool, data: Dictionary, error_info: Dictionary): + var result = ResponseHandler.handle_network_test_response(success, data, error_info) + network_status_changed.emit(result.success, result.message) + +# ============ 私有辅助方法 ============ + +# 重置登录状态 +func _reset_login_state(): + is_processing = false + button_state_changed.emit("main_btn", false, "进入小镇") + +# 重置注册状态 +func _reset_register_state(): + is_processing = false + button_state_changed.emit("register_btn", false, "注册") + +# 检查是否可以发送验证码 +func _can_send_verification_code(email: String) -> bool: + if not verification_codes_sent.has(email): + return true + + var email_data = verification_codes_sent[email] + if not email_data.sent: + return true + + var current_time = Time.get_time_dict_from_system() + var current_timestamp = current_time.hour * 3600 + current_time.minute * 60 + current_time.second + + return (current_timestamp - email_data.time) >= code_cooldown + +# 获取剩余冷却时间 +func get_remaining_cooldown_time(email: String) -> int: + if not verification_codes_sent.has(email): + return 0 + + var email_data = verification_codes_sent[email] + var current_time = Time.get_time_dict_from_system() + var current_timestamp = current_time.hour * 3600 + current_time.minute * 60 + current_time.second + + return int(code_cooldown - (current_timestamp - email_data.time)) + +# 记录验证码发送状态 +func _record_verification_code_sent(email: String): + var current_time = Time.get_time_dict_from_system() + var current_timestamp = current_time.hour * 3600 + current_time.minute * 60 + current_time.second + + if not verification_codes_sent.has(email): + verification_codes_sent[email] = {} + + verification_codes_sent[email].sent = true + verification_codes_sent[email].time = current_timestamp + current_email = email + +# 重置验证码状态 +func _reset_verification_code_state(email: String): + if verification_codes_sent.has(email): + verification_codes_sent[email].sent = false + +# 检查是否已发送验证码 +func _has_sent_verification_code(email: String) -> bool: + if not verification_codes_sent.has(email): + return false + + return verification_codes_sent[email].get("sent", false) + +# 验证标识符格式 +func _is_valid_identifier(identifier: String) -> bool: + return StringUtils.is_valid_email(identifier) or _is_valid_phone(identifier) + +# 验证手机号格式 +func _is_valid_phone(phone: String) -> bool: + var regex = RegEx.new() + regex.compile("^\\+?[1-9]\\d{1,14}$") + return regex.search(phone) != null \ No newline at end of file diff --git a/_Core/managers/AuthManager.gd.uid b/_Core/managers/AuthManager.gd.uid new file mode 100644 index 0000000..e2a85c7 --- /dev/null +++ b/_Core/managers/AuthManager.gd.uid @@ -0,0 +1 @@ +uid://bpdyraefv0yta diff --git a/_Core/managers/ToastManager.gd b/_Core/managers/ToastManager.gd new file mode 100644 index 0000000..388189a --- /dev/null +++ b/_Core/managers/ToastManager.gd @@ -0,0 +1,234 @@ +class_name ToastManager + +# ============================================================================ +# ToastManager.gd - Toast消息管理器 +# ============================================================================ +# 负责创建和管理Toast消息的显示 +# +# 核心功能: +# - 创建Toast消息实例 +# - 管理Toast动画和生命周期 +# - 支持多个Toast同时显示 +# - 自动排列和清理Toast +# - 支持中文字体显示 +# +# 使用方式: +# var toast_manager = ToastManager.new() +# toast_manager.setup(toast_container) +# toast_manager.show_toast("消息内容", true) +# +# 注意事项: +# - 需要提供一个容器节点来承载Toast +# - 自动处理Toast的位置计算和动画 +# - 支持Web平台的字体处理 +# ============================================================================ + +extends RefCounted + +# ============ 成员变量 ============ + +# Toast容器和管理 +var toast_container: Control # Toast消息容器 +var active_toasts: Array = [] # 当前显示的Toast消息列表 +var toast_counter: int = 0 # Toast计数器,用于生成唯一ID + +# ============ 初始化方法 ============ + +# 设置Toast管理器 +# +# 参数: +# container: Control - Toast消息的容器节点 +func setup(container: Control): + toast_container = container + print("ToastManager 初始化完成") + +# ============ 公共方法 ============ + +# 显示Toast消息 +# +# 参数: +# message: String - 消息内容 +# is_success: bool - 是否为成功消息(影响颜色) +func show_toast(message: String, is_success: bool = true): + if toast_container == null: + print("错误: toast_container 节点不存在") + return + + print("显示Toast消息: ", message, " 成功: ", is_success) + _create_toast_instance(message, is_success) + +# 清理所有Toast +func clear_all_toasts(): + for toast in active_toasts: + if is_instance_valid(toast): + toast.queue_free() + active_toasts.clear() + +# ============ 私有方法 ============ + +# 创建Toast实例 +func _create_toast_instance(message: String, is_success: bool): + toast_counter += 1 + + # 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.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.7, 0.15, 0.15, 0.95) + style.border_color = Color(0.9, 0.2, 0.2, 0.9) + + 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 = 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 = toast_container.get_viewport().get_visible_rect().size.x + var final_x = toast_container.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_container.add_child(toast_panel) + active_toasts.append(toast_panel) + + # 等待一帧让布局系统计算尺寸 + await toast_container.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 = toast_container.create_tween() + tween.set_ease(Tween.EASE_OUT) + tween.set_trans(Tween.TRANS_BACK) + + tween.parallel().tween_property(toast_panel, "position:x", final_x, 0.6) + tween.parallel().tween_property(toast_panel, "modulate:a", 1.0, 0.4) + + toast_panel.modulate.a = 0.0 + + # 等待3秒后开始退场动画 + await toast_container.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 = toast_container.create_tween() + tween.set_ease(Tween.EASE_IN) + tween.set_trans(Tween.TRANS_QUART) + + var end_x = toast_container.get_viewport().get_visible_rect().size.x + 50 + 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) + _rearrange_toasts() + toast_panel.queue_free() + +# 重新排列Toast位置 +func _rearrange_toasts(): + var margin = 20 + var current_y = margin + + for i in range(active_toasts.size()): + var toast = active_toasts[i] + if is_instance_valid(toast): + var tween = toast_container.create_tween() + tween.tween_property(toast, "position:y", current_y, 0.2) + current_y += toast.size.y + 15 \ No newline at end of file diff --git a/_Core/managers/ToastManager.gd.uid b/_Core/managers/ToastManager.gd.uid new file mode 100644 index 0000000..102c857 --- /dev/null +++ b/_Core/managers/ToastManager.gd.uid @@ -0,0 +1 @@ +uid://buk7d21cag262 diff --git a/scenes/ui/AuthScene.gd b/scenes/ui/AuthScene.gd index 485da13..b9b1873 100644 --- a/scenes/ui/AuthScene.gd +++ b/scenes/ui/AuthScene.gd @@ -1,140 +1,223 @@ extends Control -# 信号定义 +# ============================================================================ +# AuthScene.gd - 认证场景视图控制器 +# ============================================================================ +# 认证系统的视图层控制器,只负责UI相关的操作 +# +# 核心职责: +# - UI控件的显示和隐藏 +# - 用户输入的收集和传递 +# - 错误提示的显示 +# - 按钮状态的管理 +# - Toast消息的显示 +# +# 业务逻辑: +# - 所有业务逻辑都委托给AuthManager处理 +# - 通过信号与AuthManager通信 +# - 不包含任何验证逻辑或网络请求 +# +# 注意事项: +# - 这是纯视图层,不处理业务逻辑 +# - 通过事件系统与业务层解耦 +# - 专注于用户体验和界面交互 +# ============================================================================ + +# ============ 信号定义 ============ + +# 登录成功信号 - 传递给上层场景 signal login_success(username: String) -# 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 +# ============ 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_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 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 -# Toast消息节点 -@onready var toast_container: Control = $ToastContainer +# Toast消息系统 +@onready var toast_container: Control = $ToastContainer # Toast消息容器 -# Toast管理 -var active_toasts: Array = [] -var toast_counter: int = 0 +# ============ 成员变量 ============ -# 验证码状态 -var verification_codes_sent: Dictionary = {} -var code_cooldown: float = 60.0 +# 业务逻辑管理器 +var auth_manager: AuthManager + +# Toast消息管理器 +var toast_manager: ToastManager + +# 验证码冷却计时器 var cooldown_timer: Timer = null -var current_email: String = "" -# 登录模式枚举 -enum LoginMode { - PASSWORD, # 密码登录模式 - VERIFICATION # 验证码登录模式 -} +# 当前登录模式(从管理器同步) +var current_login_mode: AuthManager.LoginMode = AuthManager.LoginMode.PASSWORD -# 当前登录模式 -var current_login_mode: LoginMode = LoginMode.PASSWORD - -# 网络请求管理 -var active_request_ids: Array = [] +# ============ 生命周期方法 ============ +# 初始化认证场景 func _ready(): - connect_signals() - show_login_panel() - update_login_mode_ui() # 初始化登录模式UI - await get_tree().process_frame - print("认证系统已加载") - test_network_connection() - -func test_network_connection(): - print("=== 测试网络连接 ===") + _setup_controllers() # 初始化控制器 + _connect_signals() # 连接信号 + _setup_ui() # 设置UI初始状态 - 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("网络连接测试失败") -func connect_signals(): - # 主要按钮 + print("认证场景视图已加载") + + # 测试网络连接 + auth_manager.test_network_connection() + +# 设置控制器 +func _setup_controllers(): + # 创建业务逻辑管理器 + auth_manager = AuthManager.new() + + # 创建Toast管理器 + toast_manager = ToastManager.new() + toast_manager.setup(toast_container) + + # 连接管理器信号 + _connect_controller_signals() + +# 连接管理器信号 +func _connect_controller_signals(): + # 登录相关信号 + auth_manager.login_success.connect(_on_controller_login_success) + auth_manager.login_failed.connect(_on_controller_login_failed) + + # 注册相关信号 + auth_manager.register_success.connect(_on_controller_register_success) + auth_manager.register_failed.connect(_on_controller_register_failed) + + # 验证码相关信号 + auth_manager.verification_code_sent.connect(_on_controller_verification_code_sent) + auth_manager.verification_code_failed.connect(_on_controller_verification_code_failed) + + # 表单验证信号 + auth_manager.form_validation_failed.connect(_on_controller_form_validation_failed) + + # 网络状态信号 + auth_manager.network_status_changed.connect(_on_controller_network_status_changed) + + # 按钮状态信号 + auth_manager.button_state_changed.connect(_on_controller_button_state_changed) + + # Toast消息信号 + auth_manager.show_toast_message.connect(_on_controller_show_toast_message) + +# 设置UI初始状态 +func _setup_ui(): + show_login_panel() + _update_login_mode_ui() + +# 连接UI信号 +func _connect_signals(): + # 主要按钮信号 main_btn.pressed.connect(_on_main_button_pressed) - # 登录界面按钮 - login_btn.pressed.connect(_on_login_pressed) + # 登录界面按钮信号 + login_btn.pressed.connect(_on_login_mode_toggle_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) 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) - # 登录表单失焦验证 + # 表单失焦验证事件 + _connect_validation_signals() + + # 实时输入验证事件 + _connect_input_change_signals() + +# 连接表单验证信号 +func _connect_validation_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) 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) - - # 实时输入验证 + +# 连接输入变化信号 +func _connect_input_change_signals(): 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) +# ============ UI面板管理 ============ +# 显示登录面板 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() +# ============ 登录模式管理 ============ + # 更新登录模式UI -func update_login_mode_ui(): - if current_login_mode == LoginMode.PASSWORD: +func _update_login_mode_ui(): + current_login_mode = auth_manager.get_current_login_mode() + + if current_login_mode == AuthManager.LoginMode.PASSWORD: # 密码登录模式 login_btn.text = "验证码登录" forgot_password_btn.text = "忘记密码" @@ -145,7 +228,7 @@ func update_login_mode_ui(): # 清空验证码输入框和错误提示 login_verification.text = "" - hide_field_error(login_verification_error) + _hide_field_error(login_verification_error) else: # VERIFICATION mode # 验证码登录模式 @@ -158,341 +241,185 @@ func update_login_mode_ui(): # 清空密码输入框和错误提示 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) + _hide_field_error(login_password_error) # ============ 按钮事件处理 ============ +# 主按钮点击事件 func _on_main_button_pressed(): - # 根据当前登录模式执行不同的登录逻辑 - if current_login_mode == LoginMode.PASSWORD: + if current_login_mode == AuthManager.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) - - 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) + auth_manager.execute_password_login(username, password) +# 执行验证码登录 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 - - if verification_code.is_empty(): - show_field_error(login_verification_error, "请输入验证码") - login_verification.grab_focus() - return - - show_loading(main_btn, "登录中...") - show_toast('正在验证验证码...', true) - - 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) + auth_manager.execute_verification_login(identifier, verification_code) -func _on_login_pressed(): - # 现在这个按钮用于切换登录模式 - toggle_login_mode() +# 登录模式切换按钮 +func _on_login_mode_toggle_pressed(): + auth_manager.toggle_login_mode() + _update_login_mode_ui() + + # 清空输入框和错误提示 + login_username.text = "" + login_password.text = "" + login_verification.text = "" + _hide_field_error(login_username_error) + _hide_field_error(login_password_error) + _hide_field_error(login_verification_error) +# 注册按钮点击事件 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 confirm_password = register_confirm.text var verification_code = verification_input.text.strip_edges() - show_loading(register_btn, "注册中...") - show_toast('正在创建账户...', true) - - # 直接调用注册接口,让服务器端处理验证码验证 - send_register_request(username, email, password, verification_code) + auth_manager.execute_register(username, email, password, confirm_password, verification_code) +# 发送邮箱验证码按钮 func _on_send_code_pressed(): var email = register_email.text.strip_edges() + auth_manager.send_email_verification_code(email) - 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) - - var request_id = NetworkManager.send_email_verification(email, _on_send_code_response) - if request_id != "": - active_request_ids.append(request_id) - else: - show_toast('网络请求失败', false) - reset_verification_button() - -func _on_register_link_pressed(): - show_register_panel() + # 开始冷却计时器 + _start_cooldown_timer(email) +# 获取登录验证码按钮 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() + auth_manager.send_login_verification_code(identifier) +# 忘记密码按钮 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: + if current_login_mode == AuthManager.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) + auth_manager.send_password_reset_code(identifier) 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) -# ============ 网络响应处理 ============ + auth_manager.send_login_verification_code(identifier) -func send_register_request(username: String, email: String, password: String, verification_code: String = ""): - 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, "注册") +# 跳转到注册面板 +func _on_register_link_pressed(): + show_register_panel() -func _on_network_test_response(success: bool, data: Dictionary, error_info: Dictionary): - print("=== 网络测试响应处理 ===") - print("成功: ", success) - print("数据: ", data) - - var result = ResponseHandler.handle_network_test_response(success, data, error_info) - - if result.should_show_toast: - show_toast(result.message, result.success) +# 返回登录面板 +func _on_to_login_pressed(): + show_login_panel() -func _on_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_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 = "" - 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 _on_login_enter(_text: String): + _on_main_button_pressed() -func _on_send_code_response(success: bool, data: Dictionary, error_info: Dictionary): - print("=== 发送验证码响应处理 ===") - print("成功: ", success) - print("数据: ", data) - - 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) +# 登录成功处理 +func _on_controller_login_success(username: String): + # 清空表单 + login_username.text = "" + login_password.text = "" + login_verification.text = "" + _hide_field_error(login_username_error) + _hide_field_error(login_password_error) + _hide_field_error(login_verification_error) - # 恢复按钮状态 - if current_login_mode == LoginMode.PASSWORD: - restore_button(forgot_password_btn, "忘记密码") - else: - 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) + # 发送登录成功信号给上层 + login_success.emit(username) -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 _on_controller_login_failed(_message: String): + # 登录失败时不需要额外处理,Toast已经显示了错误信息 + pass -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 _on_controller_register_success(_message: String): + # 清空注册表单 + _clear_register_form() + # 切换到登录面板 + show_login_panel() + # 将注册时的用户名填入登录框 + login_username.text = register_username.text.strip_edges() + +# 注册失败处理 +func _on_controller_register_failed(_message: String): + # 注册失败时不需要额外处理,Toast已经显示了错误信息 + pass + +# 验证码发送成功处理 +func _on_controller_verification_code_sent(_message: String): + # 验证码发送成功,冷却计时器已经在按钮点击时启动 + pass + +# 验证码发送失败处理 +func _on_controller_verification_code_failed(_message: String): + # 重置验证码按钮状态 + _reset_verification_button() + +# 表单验证失败处理 +func _on_controller_form_validation_failed(field: String, message: String): + match field: + "username": + _show_field_error(login_username_error, message) + login_username.grab_focus() + "password": + _show_field_error(login_password_error, message) + login_password.grab_focus() + "verification": + if current_login_mode == AuthManager.LoginMode.VERIFICATION: + _show_field_error(login_verification_error, message) + login_verification.grab_focus() + else: + _show_field_error(verification_error, message) + verification_input.grab_focus() + "email": + _show_field_error(register_email_error, message) + register_email.grab_focus() + "confirm": + _show_field_error(register_confirm_error, message) + register_confirm.grab_focus() + +# 网络状态变化处理 +func _on_controller_network_status_changed(network_connected: bool, message: String): + # 可以在这里添加网络状态指示器 + print("网络状态: ", "连接" if network_connected else "断开", " - ", message) + +# 按钮状态变化处理 +func _on_controller_button_state_changed(button_name: String, is_loading: bool, text: String): + match button_name: + "main_btn": + _set_button_state(main_btn, is_loading, text) + "register_btn": + _set_button_state(register_btn, is_loading, text) + "get_code_btn": + _set_button_state(get_code_btn, is_loading, text) + "forgot_password_btn": + _set_button_state(forgot_password_btn, is_loading, text) + +# Toast消息处理 +func _on_controller_show_toast_message(message: String, is_success: bool): + toast_manager.show_toast(message, is_success) -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 start_cooldown_timer(email: String): +# 开始冷却计时器 +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)" @@ -502,531 +429,181 @@ func start_cooldown_timer(email: String): cooldown_timer.timeout.connect(_on_cooldown_timer_timeout) cooldown_timer.start() +# 冷却计时器超时处理 func _on_cooldown_timer_timeout(): - var input_email = register_email.text.strip_edges() + var remaining_time = auth_manager.get_remaining_cooldown_time(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_time > 0: + send_code_btn.text = "重新发送(%d)" % remaining_time + else: + send_code_btn.text = "重新发送" + send_code_btn.disabled = false - 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 = "" + if cooldown_timer != null: + cooldown_timer.queue_free() + cooldown_timer = null -func stop_current_cooldown(): +# 重置验证码按钮状态 +func _reset_verification_button(): 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() +# ============ UI工具方法 ============ -func clear_register_form(): +# 设置按钮状态 +func _set_button_state(button: Button, is_loading: bool, text: String): + button.disabled = is_loading + button.text = text + +# 显示字段错误 +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 _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() + _reset_verification_button() - 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消息系统 ============ - -# 检测文本是否包含中文字符 -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实例 - create_toast_instance(message, is_success) - -func create_toast_instance(message: String, is_success: bool): - toast_counter += 1 - - # 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.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.7, 0.15, 0.15, 0.95) - style.border_color = Color(0.9, 0.2, 0.2, 0.9) - - 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 = 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 - - # 计算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_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) - -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.parallel().tween_property(toast_panel, "position:x", final_x, 0.6) - tween.parallel().tween_property(toast_panel, "modulate:a", 1.0, 0.4) - - toast_panel.modulate.a = 0.0 - - await get_tree().create_timer(3.0).timeout - animate_toast_out(toast_panel) - -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.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) - -func cleanup_toast(toast_panel: Panel): - if not is_instance_valid(toast_panel): - return - - active_toasts.erase(toast_panel) - rearrange_toasts() - toast_panel.queue_free() - -func rearrange_toasts(): - var margin = 20 - var current_y = margin - - for i in range(active_toasts.size()): - var toast = active_toasts[i] - if is_instance_valid(toast): - var tween = create_tween() - 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 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": ""} - - if username.is_empty(): - result.message = "用户名不能为空" - return result - - 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": ""} - - if email.is_empty(): - result.message = "邮箱不能为空" - return result - - if not StringUtils.is_valid_email(email): - result.message = "请输入有效的邮箱地址" - return result - - result.valid = true - return result - -func validate_password(password: String) -> Dictionary: - return StringUtils.validate_password_strength(password) - -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 - -# ============ 表单验证事件 ============ + _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) +# ============ 表单验证事件处理 ============ +# 登录用户名失焦验证 func _on_login_username_focus_exited(): var username = login_username.text.strip_edges() if username.is_empty(): - show_field_error(login_username_error, "用户名不能为空") + _show_field_error(login_username_error, "用户名不能为空") else: - hide_field_error(login_username_error) + _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, "密码不能为空") + _show_field_error(login_password_error, "密码不能为空") else: - hide_field_error(login_password_error) + _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, "验证码只能包含数字") + var validation = auth_manager.validate_verification_code(verification_code) + if not validation.valid: + _show_field_error(login_verification_error, validation.message) else: - hide_field_error(login_verification_error) + _hide_field_error(login_verification_error) +# 注册用户名失焦验证 func _on_register_username_focus_exited(): var username = register_username.text.strip_edges() - var validation = validate_username(username) + var validation = auth_manager.validate_username(username) if not validation.valid: - show_field_error(register_username_error, validation.message) + _show_field_error(register_username_error, validation.message) else: - hide_field_error(register_username_error) + _hide_field_error(register_username_error) +# 注册邮箱失焦验证 func _on_register_email_focus_exited(): var email = register_email.text.strip_edges() - var validation = validate_email(email) + var validation = auth_manager.validate_email(email) if not validation.valid: - show_field_error(register_email_error, validation.message) + _show_field_error(register_email_error, validation.message) else: - hide_field_error(register_email_error) + _hide_field_error(register_email_error) +# 注册密码失焦验证 func _on_register_password_focus_exited(): var password = register_password.text - var validation = validate_password(password) + var validation = auth_manager.validate_password(password) if not validation.valid: - show_field_error(register_password_error, validation.message) + _show_field_error(register_password_error, validation.message) else: - hide_field_error(register_password_error) + _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) + var validation = auth_manager.validate_confirm_password(password, confirm) if not validation.valid: - show_field_error(register_confirm_error, validation.message) + _show_field_error(register_confirm_error, validation.message) else: - hide_field_error(register_confirm_error) + _hide_field_error(register_confirm_error) +# 验证码失焦验证 func _on_verification_focus_exited(): var code = verification_input.text.strip_edges() - var validation = validate_verification_code(code) + var validation = auth_manager.validate_verification_code(code) if not validation.valid: - show_field_error(verification_error, validation.message) + _show_field_error(verification_error, validation.message) else: - hide_field_error(verification_error) + _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) + _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) + _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) + _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) + _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 + _hide_field_error(verification_error) # ============ 资源清理 ============ +# 节点销毁时清理资源 func _exit_tree(): - for request_id in active_request_ids: - NetworkManager.cancel_request(request_id) - active_request_ids.clear() + # 清理控制器 + if auth_manager: + auth_manager.cleanup() + # 清理Toast管理器 + if toast_manager: + toast_manager.clear_all_toasts() + + # 清理计时器 if cooldown_timer != null: cooldown_timer.queue_free() cooldown_timer = null +# 处理ESC键退出 func _input(event): if event.is_action_pressed("ui_cancel"): - get_tree().quit() \ No newline at end of file + get_tree().quit() From 93baf1a5b54220e14a7d10684c99c4e89e0ecb2d Mon Sep 17 00:00:00 2001 From: moyin <2443444649@qq.com> Date: Fri, 2 Jan 2026 21:19:04 +0800 Subject: [PATCH 3/6] =?UTF-8?q?refactor=EF=BC=9A=E7=BB=9F=E4=B8=80AuthScen?= =?UTF-8?q?e=E5=91=BD=E5=90=8D=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将LoginWindow.tscn重命名为AuthScene.tscn - 更新MainScene.tscn中的场景引用路径 - 实现命名一致性: - 场景文件:AuthScene.tscn - 脚本文件:AuthScene.gd - 节点名称:AuthScene - AuthScene比LoginWindow更准确描述功能(登录+注册) --- scenes/ui/{LoginWindow.tscn => AuthScene.tscn} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scenes/ui/{LoginWindow.tscn => AuthScene.tscn} (100%) diff --git a/scenes/ui/LoginWindow.tscn b/scenes/ui/AuthScene.tscn similarity index 100% rename from scenes/ui/LoginWindow.tscn rename to scenes/ui/AuthScene.tscn From aaaf2b31a828e330989259e767adb7787435e9ce Mon Sep 17 00:00:00 2001 From: moyin <2443444649@qq.com> Date: Fri, 2 Jan 2026 21:19:25 +0800 Subject: [PATCH 4/6] =?UTF-8?q?fix=EF=BC=9A=E4=BF=AE=E5=A4=8DEventSystem?= =?UTF-8?q?=E4=B8=AD=E7=9A=84GDScript=E8=AF=AD=E6=B3=95=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除不支持的try/except语句 - 改为直接调用回调函数 - 确保EventSystem能正常编译运行 --- _Core/systems/EventSystem.gd | 119 ++++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 2 deletions(-) diff --git a/_Core/systems/EventSystem.gd b/_Core/systems/EventSystem.gd index 76f091b..07e59ab 100644 --- a/_Core/systems/EventSystem.gd +++ b/_Core/systems/EventSystem.gd @@ -1,44 +1,125 @@ extends Node -# 全局事件系统 - 提供解耦的事件通信机制 -# 允许不同模块之间通过事件进行通信,避免直接依赖 +# ============================================================================ +# EventSystem.gd - 全局事件系统 +# ============================================================================ +# 全局单例管理器,提供解耦的事件通信机制 +# +# 核心职责: +# - 事件监听器注册和管理 +# - 事件发送和分发 +# - 自动清理无效监听器 +# - 支持带参数的事件通信 +# +# 使用方式: +# EventSystem.connect_event("player_moved", _on_player_moved) +# EventSystem.emit_event("player_moved", {"position": Vector2(100, 200)}) +# +# 注意事项: +# - 作为自动加载单例,全局可访问 +# - 监听器会自动检查目标节点的有效性 +# - 建议使用EventNames类中定义的事件名称常量 +# ============================================================================ + +# ============ 成员变量 ============ # 事件监听器存储 +# 结构: {event_name: [{"callback": Callable, "target": Node}, ...]} var event_listeners: Dictionary = {} +# ============ 生命周期方法 ============ + +# 初始化事件系统 +# 在节点准备就绪时调用 func _ready(): print("EventSystem 初始化完成") +# ============ 事件监听器管理 ============ + # 注册事件监听器 +# +# 参数: +# event_name: String - 事件名称(建议使用EventNames中的常量) +# callback: Callable - 回调函数 +# target: Node - 目标节点(可选,用于自动清理) +# +# 功能: +# - 将回调函数注册到指定事件 +# - 支持同一事件多个监听器 +# - 自动管理监听器生命周期 +# +# 使用示例: +# EventSystem.connect_event(EventNames.PLAYER_MOVED, _on_player_moved, self) +# +# 注意事项: +# - 如果提供target参数,当target节点被销毁时会自动清理监听器 +# - 同一个callback可以监听多个事件 func connect_event(event_name: String, callback: Callable, target: Node = null): + # 初始化事件监听器数组 if not event_listeners.has(event_name): event_listeners[event_name] = [] + # 创建监听器信息 var listener_info = { "callback": callback, "target": target } + # 添加到监听器列表 event_listeners[event_name].append(listener_info) print("注册事件监听器: ", event_name, " -> ", callback) # 移除事件监听器 +# +# 参数: +# event_name: String - 事件名称 +# callback: Callable - 要移除的回调函数 +# target: Node - 目标节点(可选,用于精确匹配) +# +# 功能: +# - 从指定事件中移除特定的监听器 +# - 支持精确匹配(callback + target) +# +# 使用示例: +# EventSystem.disconnect_event(EventNames.PLAYER_MOVED, _on_player_moved, self) func disconnect_event(event_name: String, callback: Callable, target: Node = null): if not event_listeners.has(event_name): return var listeners = event_listeners[event_name] + # 从后往前遍历,避免删除元素时索引问题 for i in range(listeners.size() - 1, -1, -1): var listener = listeners[i] + # 匹配callback和target if listener.callback == callback and listener.target == target: listeners.remove_at(i) print("移除事件监听器: ", event_name, " -> ", callback) break +# ============ 事件发送 ============ + # 发送事件 +# +# 参数: +# event_name: String - 事件名称 +# data: Variant - 事件数据(可选) +# +# 功能: +# - 向所有注册的监听器发送事件 +# - 自动跳过无效的监听器 +# - 支持任意类型的事件数据 +# +# 使用示例: +# EventSystem.emit_event(EventNames.PLAYER_MOVED, {"position": Vector2(100, 200)}) +# EventSystem.emit_event(EventNames.GAME_PAUSED) # 无数据事件 +# +# 注意事项: +# - 事件发送是同步的,所有监听器会立即执行 +# - 如果监听器执行出错,不会影响其他监听器 func emit_event(event_name: String, data: Variant = null): print("发送事件: ", event_name, " 数据: ", data) + # 检查是否有监听器 if not event_listeners.has(event_name): return @@ -57,24 +138,58 @@ func emit_event(event_name: String, data: Variant = null): else: callback.call() +# ============ 维护方法 ============ + # 清理无效的监听器 +# +# 功能: +# - 遍历所有监听器,移除已销毁节点的监听器 +# - 防止内存泄漏 +# - 建议定期调用或在场景切换时调用 +# +# 使用场景: +# - 场景切换时清理 +# - 定期维护(如每分钟一次) +# - 内存优化时调用 func cleanup_invalid_listeners(): for event_name in event_listeners.keys(): var listeners = event_listeners[event_name] + # 从后往前遍历,避免删除元素时索引问题 for i in range(listeners.size() - 1, -1, -1): var listener = listeners[i] var target = listener.target + # 如果目标节点无效,移除监听器 if target != null and not is_instance_valid(target): listeners.remove_at(i) print("清理无效监听器: ", event_name) +# ============ 查询方法 ============ + # 获取事件监听器数量 +# +# 参数: +# event_name: String - 事件名称 +# +# 返回值: +# int - 监听器数量 +# +# 使用场景: +# - 调试时检查监听器数量 +# - 性能分析 func get_listener_count(event_name: String) -> int: if not event_listeners.has(event_name): return 0 return event_listeners[event_name].size() # 清空所有事件监听器 +# +# 功能: +# - 移除所有已注册的事件监听器 +# - 通常在游戏重置或退出时使用 +# +# 警告: +# - 这是一个危险操作,会影响所有模块 +# - 使用前请确保所有模块都能正确处理监听器丢失 func clear_all_listeners(): event_listeners.clear() print("清空所有事件监听器") From 2f1ccbc2cdbc1e583c8aaf0f062a3b602260b437 Mon Sep 17 00:00:00 2001 From: moyin <2443444649@qq.com> Date: Fri, 2 Jan 2026 21:19:53 +0800 Subject: [PATCH 5/6] =?UTF-8?q?docs=EF=BC=9A=E4=B8=BA=E6=A0=B8=E5=BF=83?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=99=A8=E6=B7=BB=E5=8A=A0=E8=AF=A6=E7=BB=86?= =?UTF-8?q?=E4=B8=AD=E6=96=87=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GameManager.gd:游戏状态管理注释 - NetworkManager.gd:网络请求管理注释 - SceneManager.gd:场景切换管理注释 - StringUtils.gd:字符串工具函数注释 按照docs注释规范,添加文件头、函数说明、参数描述和使用示例 方便协同开发者快速理解和调用 --- _Core/managers/GameManager.gd | 118 ++++++++++-- _Core/managers/NetworkManager.gd | 312 ++++++++++++++++++++++++++++--- _Core/managers/SceneManager.gd | 136 ++++++++++++-- _Core/utils/StringUtils.gd | 201 +++++++++++++++++++- 4 files changed, 708 insertions(+), 59 deletions(-) diff --git a/_Core/managers/GameManager.gd b/_Core/managers/GameManager.gd index e636a5b..5e6d537 100644 --- a/_Core/managers/GameManager.gd +++ b/_Core/managers/GameManager.gd @@ -1,50 +1,142 @@ extends Node -# 游戏管理器 - 全局游戏状态管理 -# 单例模式,管理游戏的整体状态和生命周期 +# ============================================================================ +# GameManager.gd - 游戏管理器 +# ============================================================================ +# 全局单例管理器,负责游戏状态管理和生命周期控制 +# +# 核心职责: +# - 游戏状态切换 (加载、认证、游戏中、暂停等) +# - 用户信息管理 +# - 全局配置访问 +# - 系统初始化和清理 +# +# 使用方式: +# GameManager.change_state(GameManager.GameState.IN_GAME) +# GameManager.set_current_user("player123") +# +# 注意事项: +# - 作为自动加载单例,全局可访问 +# - 状态变更会触发 game_state_changed 信号 +# - 状态切换应该通过 change_state() 方法进行 +# ============================================================================ +# ============ 信号定义 ============ + +# 游戏状态变更信号 +# 参数: new_state - 新的游戏状态 signal game_state_changed(new_state: GameState) +# ============ 枚举定义 ============ + +# 游戏状态枚举 +# 定义了游戏的各种运行状态 enum GameState { - LOADING, # 加载中 - AUTH, # 认证状态 - MAIN_MENU, # 主菜单 - IN_GAME, # 游戏中 - PAUSED, # 暂停 - SETTINGS # 设置 + LOADING, # 加载中 - 游戏启动时的初始化状态 + AUTH, # 认证状态 - 用户登录/注册界面 + MAIN_MENU, # 主菜单 - 游戏主界面 + IN_GAME, # 游戏中 - 正在进行游戏 + PAUSED, # 暂停 - 游戏暂停状态 + SETTINGS # 设置 - 设置界面 } -var current_state: GameState = GameState.LOADING -var previous_state: GameState = GameState.LOADING -var current_user: String = "" -var game_version: String = "1.0.0" +# ============ 成员变量 ============ +# 状态管理 +var current_state: GameState = GameState.LOADING # 当前游戏状态 +var previous_state: GameState = GameState.LOADING # 上一个游戏状态 + +# 用户信息 +var current_user: String = "" # 当前登录用户名 + +# 游戏配置 +var game_version: String = "1.0.0" # 游戏版本号 + +# ============ 生命周期方法 ============ + +# 初始化游戏管理器 +# 在节点准备就绪时调用,设置初始状态 func _ready(): print("GameManager 初始化完成") - change_state(GameState.AUTH) + change_state(GameState.AUTH) # 启动时进入认证状态 +# ============ 状态管理方法 ============ + +# 切换游戏状态 +# +# 参数: +# new_state: GameState - 要切换到的新状态 +# +# 功能: +# - 检查状态是否需要切换 +# - 记录状态变更历史 +# - 发送状态变更信号 +# - 输出状态变更日志 func change_state(new_state: GameState): + # 避免重复切换到相同状态 if current_state == new_state: return + # 记录状态变更 previous_state = current_state current_state = new_state + # 输出状态变更日志 print("游戏状态变更: ", GameState.keys()[previous_state], " -> ", GameState.keys()[current_state]) + + # 发送状态变更信号 game_state_changed.emit(new_state) +# 获取当前游戏状态 +# +# 返回值: +# GameState - 当前的游戏状态 func get_current_state() -> GameState: return current_state +# 获取上一个游戏状态 +# +# 返回值: +# GameState - 上一个游戏状态 +# +# 使用场景: +# - 从暂停状态恢复时,返回到之前的状态 +# - 错误处理时回退到安全状态 func get_previous_state() -> GameState: return previous_state +# ============ 用户管理方法 ============ + +# 设置当前登录用户 +# +# 参数: +# username: String - 用户名 +# +# 功能: +# - 存储当前登录用户信息 +# - 输出用户设置日志 +# +# 注意事项: +# - 用户登录成功后调用此方法 +# - 用户登出时应传入空字符串 func set_current_user(username: String): current_user = username print("当前用户设置为: ", username) +# 获取当前登录用户 +# +# 返回值: +# String - 当前登录的用户名,未登录时为空字符串 func get_current_user() -> String: return current_user +# 检查用户是否已登录 +# +# 返回值: +# bool - true表示已登录,false表示未登录 +# +# 使用场景: +# - 进入需要登录的功能前检查 +# - UI显示逻辑判断 func is_user_logged_in() -> bool: return not current_user.is_empty() diff --git a/_Core/managers/NetworkManager.gd b/_Core/managers/NetworkManager.gd index fb9cc3e..36ba1c3 100644 --- a/_Core/managers/NetworkManager.gd +++ b/_Core/managers/NetworkManager.gd @@ -1,45 +1,96 @@ extends Node -# 网络请求管理器 - 统一处理所有HTTP请求 +# ============================================================================ +# NetworkManager.gd - 网络请求管理器 +# ============================================================================ +# 全局单例管理器,统一处理所有HTTP请求 +# +# 核心职责: +# - 统一的HTTP请求接口 (GET, POST, PUT, DELETE, PATCH) +# - 认证相关API封装 (登录、注册、验证码等) +# - 请求状态管理和错误处理 +# - 支持API v1.1.1规范的响应处理 +# +# 使用方式: +# NetworkManager.login("user@example.com", "password", callback) +# var request_id = NetworkManager.get_request("/api/data", callback) +# +# 注意事项: +# - 作为自动加载单例,全局可访问 +# - 所有请求都是异步的,通过回调函数或信号处理结果 +# - 支持请求超时和取消功能 +# - 自动处理JSON序列化和反序列化 +# ============================================================================ -# 信号定义 +# ============ 信号定义 ============ + +# 请求完成信号 +# 参数: +# request_id: String - 请求唯一标识符 +# success: bool - 请求是否成功 +# data: Dictionary - 响应数据 signal request_completed(request_id: String, success: bool, data: Dictionary) + +# 请求失败信号 +# 参数: +# request_id: String - 请求唯一标识符 +# error_type: String - 错误类型名称 +# message: String - 错误消息 signal request_failed(request_id: String, error_type: String, message: String) -# API配置 +# ============ 常量定义 ============ + +# API基础URL - 所有请求的根地址 const API_BASE_URL = "https://whaletownend.xinghangee.icu" + +# 默认请求超时时间(秒) const DEFAULT_TIMEOUT = 30.0 -# 请求类型枚举 +# ============ 枚举定义 ============ + +# HTTP请求方法枚举 enum RequestType { - GET, - POST, - PUT, - DELETE, - PATCH + GET, # 获取数据 + POST, # 创建数据 + PUT, # 更新数据 + DELETE, # 删除数据 + PATCH # 部分更新数据 } # 错误类型枚举 +# 用于分类不同类型的网络错误 enum ErrorType { - NETWORK_ERROR, # 网络连接错误 - TIMEOUT_ERROR, # 请求超时 - PARSE_ERROR, # JSON解析错误 - HTTP_ERROR, # HTTP状态码错误 - BUSINESS_ERROR # 业务逻辑错误 + NETWORK_ERROR, # 网络连接错误 - 无法连接到服务器 + TIMEOUT_ERROR, # 请求超时 - 服务器响应时间过长 + PARSE_ERROR, # JSON解析错误 - 服务器返回格式错误 + HTTP_ERROR, # HTTP状态码错误 - 4xx, 5xx状态码 + BUSINESS_ERROR # 业务逻辑错误 - API返回的业务错误 } -# 请求状态 +# ============ 请求信息类 ============ + +# 请求信息封装类 +# 存储单个HTTP请求的所有相关信息 class RequestInfo: - var id: String - var url: String - var method: RequestType - var headers: PackedStringArray - var body: String - var timeout: float - var start_time: float - var http_request: HTTPRequest - var callback: Callable + var id: String # 请求唯一标识符 + var url: String # 完整的请求URL + var method: RequestType # HTTP请求方法 + var headers: PackedStringArray # 请求头数组 + var body: String # 请求体内容 + var timeout: float # 超时时间(秒) + var start_time: float # 请求开始时间戳 + var http_request: HTTPRequest # Godot HTTPRequest节点引用 + var callback: Callable # 完成时的回调函数 + # 构造函数 + # + # 参数: + # request_id: String - 请求唯一标识符 + # request_url: String - 请求URL + # request_method: RequestType - HTTP方法 + # request_headers: PackedStringArray - 请求头(可选) + # request_body: String - 请求体(可选) + # request_timeout: float - 超时时间(可选,默认使用DEFAULT_TIMEOUT) func _init(request_id: String, request_url: String, request_method: RequestType, request_headers: PackedStringArray = [], request_body: String = "", request_timeout: float = DEFAULT_TIMEOUT): @@ -49,40 +100,107 @@ class RequestInfo: headers = request_headers body = request_body timeout = request_timeout + # 记录请求开始时间(简化版时间戳) start_time = Time.get_time_dict_from_system().hour * 3600 + Time.get_time_dict_from_system().minute * 60 + Time.get_time_dict_from_system().second -# 活动请求管理 -var active_requests: Dictionary = {} -var request_counter: int = 0 +# ============ 成员变量 ============ +# 活动请求管理 +var active_requests: Dictionary = {} # 存储所有活动请求 {request_id: RequestInfo} +var request_counter: int = 0 # 请求计数器,用于生成唯一ID + +# ============ 生命周期方法 ============ + +# 初始化网络管理器 +# 在节点准备就绪时调用 func _ready(): print("NetworkManager 已初始化") # ============ 公共API接口 ============ # 发送GET请求 +# +# 参数: +# endpoint: String - API端点路径(如: "/api/users") +# callback: Callable - 完成时的回调函数(可选) +# timeout: float - 超时时间(可选,默认30秒) +# +# 返回值: +# String - 请求ID,可用于取消请求或跟踪状态 +# +# 使用示例: +# var request_id = NetworkManager.get_request("/api/users", my_callback) func get_request(endpoint: String, callback: Callable = Callable(), timeout: float = DEFAULT_TIMEOUT) -> String: return send_request(endpoint, RequestType.GET, [], "", callback, timeout) # 发送POST请求 +# +# 参数: +# endpoint: String - API端点路径 +# data: Dictionary - 要发送的数据(将自动转换为JSON) +# callback: Callable - 完成时的回调函数(可选) +# timeout: float - 超时时间(可选) +# +# 返回值: +# String - 请求ID +# +# 使用示例: +# var data = {"name": "张三", "age": 25} +# var request_id = NetworkManager.post_request("/api/users", data, my_callback) func post_request(endpoint: String, data: Dictionary, callback: Callable = Callable(), timeout: float = DEFAULT_TIMEOUT) -> String: var body = JSON.stringify(data) var headers = ["Content-Type: application/json"] return send_request(endpoint, RequestType.POST, headers, body, callback, timeout) # 发送PUT请求 +# +# 参数: +# endpoint: String - API端点路径 +# data: Dictionary - 要更新的数据 +# callback: Callable - 完成时的回调函数(可选) +# timeout: float - 超时时间(可选) +# +# 返回值: +# String - 请求ID func put_request(endpoint: String, data: Dictionary, callback: Callable = Callable(), timeout: float = DEFAULT_TIMEOUT) -> String: var body = JSON.stringify(data) var headers = ["Content-Type: application/json"] return send_request(endpoint, RequestType.PUT, headers, body, callback, timeout) # 发送DELETE请求 +# +# 参数: +# endpoint: String - API端点路径 +# callback: Callable - 完成时的回调函数(可选) +# timeout: float - 超时时间(可选) +# +# 返回值: +# String - 请求ID func delete_request(endpoint: String, callback: Callable = Callable(), timeout: float = DEFAULT_TIMEOUT) -> String: return send_request(endpoint, RequestType.DELETE, [], "", callback, timeout) # ============ 认证相关API ============ # 用户登录 +# +# 参数: +# identifier: String - 用户标识符(邮箱或手机号) +# password: String - 用户密码 +# callback: Callable - 完成时的回调函数(可选) +# +# 返回值: +# String - 请求ID +# +# 回调函数签名: +# func callback(success: bool, data: Dictionary, error_info: Dictionary) +# +# 使用示例: +# NetworkManager.login("user@example.com", "password123", func(success, data, error): +# if success: +# print("登录成功: ", data) +# else: +# print("登录失败: ", error.message) +# ) func login(identifier: String, password: String, callback: Callable = Callable()) -> String: var data = { "identifier": identifier, @@ -91,6 +209,18 @@ func login(identifier: String, password: String, callback: Callable = Callable() return post_request("/auth/login", data, callback) # 验证码登录 +# +# 参数: +# identifier: String - 用户标识符(邮箱或手机号) +# verification_code: String - 验证码 +# callback: Callable - 完成时的回调函数(可选) +# +# 返回值: +# String - 请求ID +# +# 使用场景: +# - 用户忘记密码时的替代登录方式 +# - 提供更安全的登录选项 func verification_code_login(identifier: String, verification_code: String, callback: Callable = Callable()) -> String: var data = { "identifier": identifier, @@ -99,11 +229,39 @@ func verification_code_login(identifier: String, verification_code: String, call return post_request("/auth/verification-code-login", data, callback) # 发送登录验证码 +# +# 参数: +# identifier: String - 用户标识符(邮箱或手机号) +# callback: Callable - 完成时的回调函数(可选) +# +# 返回值: +# String - 请求ID +# +# 功能: +# - 向已注册用户发送登录验证码 +# - 支持邮箱和手机号 +# - 有频率限制保护 func send_login_verification_code(identifier: String, callback: Callable = Callable()) -> String: var data = {"identifier": identifier} return post_request("/auth/send-login-verification-code", data, callback) # 用户注册 +# +# 参数: +# username: String - 用户名 +# password: String - 密码 +# nickname: String - 昵称 +# email: String - 邮箱地址(可选) +# email_verification_code: String - 邮箱验证码(可选) +# callback: Callable - 完成时的回调函数(可选) +# +# 返回值: +# String - 请求ID +# +# 注意事项: +# - 如果提供邮箱,建议同时提供验证码 +# - 用户名和邮箱必须唯一 +# - 密码需要符合安全要求 func register(username: String, password: String, nickname: String, email: String = "", email_verification_code: String = "", callback: Callable = Callable()) -> String: var data = { @@ -112,6 +270,7 @@ func register(username: String, password: String, nickname: String, email: Strin "nickname": nickname } + # 可选参数处理 if email != "": data["email"] = email if email_verification_code != "": @@ -120,11 +279,35 @@ func register(username: String, password: String, nickname: String, email: Strin return post_request("/auth/register", data, callback) # 发送邮箱验证码 +# +# 参数: +# email: String - 邮箱地址 +# callback: Callable - 完成时的回调函数(可选) +# +# 返回值: +# String - 请求ID +# +# 功能: +# - 向指定邮箱发送验证码 +# - 用于注册时的邮箱验证 +# - 支持测试模式(开发环境) func send_email_verification(email: String, callback: Callable = Callable()) -> String: var data = {"email": email} return post_request("/auth/send-email-verification", data, callback) # 验证邮箱 +# +# 参数: +# email: String - 邮箱地址 +# verification_code: String - 验证码 +# callback: Callable - 完成时的回调函数(可选) +# +# 返回值: +# String - 请求ID +# +# 功能: +# - 验证邮箱验证码的有效性 +# - 通常在注册流程中使用 func verify_email(email: String, verification_code: String, callback: Callable = Callable()) -> String: var data = { "email": email, @@ -133,20 +316,66 @@ func verify_email(email: String, verification_code: String, callback: Callable = return post_request("/auth/verify-email", data, callback) # 获取应用状态 +# +# 参数: +# callback: Callable - 完成时的回调函数(可选) +# +# 返回值: +# String - 请求ID +# +# 功能: +# - 检查API服务器状态 +# - 获取应用基本信息 +# - 用于网络连接测试 func get_app_status(callback: Callable = Callable()) -> String: return get_request("/", callback) # 重新发送邮箱验证码 +# +# 参数: +# email: String - 邮箱地址 +# callback: Callable - 完成时的回调函数(可选) +# +# 返回值: +# String - 请求ID +# +# 使用场景: +# - 用户未收到验证码时重新发送 +# - 验证码过期后重新获取 func resend_email_verification(email: String, callback: Callable = Callable()) -> String: var data = {"email": email} return post_request("/auth/resend-email-verification", data, callback) # 忘记密码 - 发送重置验证码 +# +# 参数: +# identifier: String - 用户标识符(邮箱或手机号) +# callback: Callable - 完成时的回调函数(可选) +# +# 返回值: +# String - 请求ID +# +# 功能: +# - 向用户发送密码重置验证码 +# - 用于密码找回流程的第一步 func forgot_password(identifier: String, callback: Callable = Callable()) -> String: var data = {"identifier": identifier} return post_request("/auth/forgot-password", data, callback) # 重置密码 +# +# 参数: +# identifier: String - 用户标识符 +# verification_code: String - 重置验证码 +# new_password: String - 新密码 +# callback: Callable - 完成时的回调函数(可选) +# +# 返回值: +# String - 请求ID +# +# 功能: +# - 使用验证码重置用户密码 +# - 密码找回流程的第二步 func reset_password(identifier: String, verification_code: String, new_password: String, callback: Callable = Callable()) -> String: var data = { "identifier": identifier, @@ -156,6 +385,19 @@ func reset_password(identifier: String, verification_code: String, new_password: return post_request("/auth/reset-password", data, callback) # 修改密码 +# +# 参数: +# user_id: String - 用户ID +# old_password: String - 旧密码 +# new_password: String - 新密码 +# callback: Callable - 完成时的回调函数(可选) +# +# 返回值: +# String - 请求ID +# +# 功能: +# - 已登录用户修改密码 +# - 需要验证旧密码 func change_password(user_id: String, old_password: String, new_password: String, callback: Callable = Callable()) -> String: var data = { "user_id": user_id, @@ -165,6 +407,21 @@ func change_password(user_id: String, old_password: String, new_password: String return put_request("/auth/change-password", data, callback) # GitHub OAuth登录 +# +# 参数: +# github_id: String - GitHub用户ID +# username: String - GitHub用户名 +# nickname: String - 显示昵称 +# email: String - GitHub邮箱 +# avatar_url: String - 头像URL(可选) +# callback: Callable - 完成时的回调函数(可选) +# +# 返回值: +# String - 请求ID +# +# 功能: +# - 通过GitHub账号登录或注册 +# - 支持第三方OAuth认证 func github_login(github_id: String, username: String, nickname: String, email: String, avatar_url: String = "", callback: Callable = Callable()) -> String: var data = { "github_id": github_id, @@ -173,6 +430,7 @@ func github_login(github_id: String, username: String, nickname: String, email: "email": email } + # 可选头像URL if avatar_url != "": data["avatar_url"] = avatar_url diff --git a/_Core/managers/SceneManager.gd b/_Core/managers/SceneManager.gd index ec0ef7e..8779748 100644 --- a/_Core/managers/SceneManager.gd +++ b/_Core/managers/SceneManager.gd @@ -1,33 +1,96 @@ extends Node -# 场景管理器 - 负责场景切换和管理 -# 提供场景切换的统一接口 +# ============================================================================ +# SceneManager.gd - 场景管理器 +# ============================================================================ +# 全局单例管理器,负责场景切换和管理 +# +# 核心职责: +# - 场景切换的统一接口 +# - 场景路径映射管理 +# - 场景切换过渡效果 +# - 场景状态跟踪 +# +# 使用方式: +# SceneManager.change_scene("main") +# SceneManager.register_scene("custom", "res://scenes/custom.tscn") +# +# 注意事项: +# - 作为自动加载单例,全局可访问 +# - 场景切换是异步操作,支持过渡效果 +# - 场景名称必须在 scene_paths 中注册 +# ============================================================================ +# ============ 信号定义 ============ + +# 场景切换完成信号 +# 参数: scene_name - 切换到的场景名称 signal scene_changed(scene_name: String) + +# 场景切换开始信号 +# 参数: scene_name - 即将切换到的场景名称 signal scene_change_started(scene_name: String) -var current_scene_name: String = "" -var is_changing_scene: bool = false +# ============ 成员变量 ============ -# 场景路径映射 +# 场景状态 +var current_scene_name: String = "" # 当前场景名称 +var is_changing_scene: bool = false # 是否正在切换场景 + +# 场景路径映射表 +# 将场景名称映射到实际的文件路径 +# 便于统一管理和修改场景路径 var scene_paths: Dictionary = { - "main": "res://scenes/maps/main_scene.tscn", - "auth": "res://scenes/ui/LoginWindow.tscn", - "game": "res://scenes/maps/game_scene.tscn", - "battle": "res://scenes/maps/battle_scene.tscn", - "inventory": "res://scenes/ui/InventoryWindow.tscn", - "shop": "res://scenes/ui/ShopWindow.tscn", - "settings": "res://scenes/ui/SettingsWindow.tscn" + "main": "res://scenes/MainScene.tscn", # 主场景 - 游戏入口 + "auth": "res://scenes/ui/LoginWindow.tscn", # 认证场景 - 登录窗口 + "game": "res://scenes/maps/game_scene.tscn", # 游戏场景 - 主要游戏内容 + "battle": "res://scenes/maps/battle_scene.tscn", # 战斗场景 - 战斗系统 + "inventory": "res://scenes/ui/InventoryWindow.tscn", # 背包界面 + "shop": "res://scenes/ui/ShopWindow.tscn", # 商店界面 + "settings": "res://scenes/ui/SettingsWindow.tscn" # 设置界面 } +# ============ 生命周期方法 ============ + +# 初始化场景管理器 +# 在节点准备就绪时调用 func _ready(): print("SceneManager 初始化完成") +# ============ 场景切换方法 ============ + +# 切换到指定场景 +# +# 参数: +# scene_name: String - 要切换到的场景名称(必须在scene_paths中注册) +# use_transition: bool - 是否使用过渡效果,默认为true +# +# 返回值: +# bool - 切换是否成功 +# +# 功能: +# - 检查场景切换状态和场景是否存在 +# - 显示过渡效果(可选) +# - 执行场景切换 +# - 更新当前场景状态 +# - 发送相关信号 +# +# 使用示例: +# var success = SceneManager.change_scene("main", true) +# if success: +# print("场景切换成功") +# +# 注意事项: +# - 场景切换是异步操作 +# - 切换过程中会阻止新的切换请求 +# - 场景名称必须预先注册 func change_scene(scene_name: String, use_transition: bool = true): + # 防止重复切换 if is_changing_scene: print("场景切换中,忽略新的切换请求") return false + # 检查场景是否存在 if not scene_paths.has(scene_name): print("错误: 未找到场景 ", scene_name) return false @@ -35,40 +98,89 @@ func change_scene(scene_name: String, use_transition: bool = true): var scene_path = scene_paths[scene_name] print("开始切换场景: ", current_scene_name, " -> ", scene_name) + # 设置切换状态 is_changing_scene = true scene_change_started.emit(scene_name) + # 显示过渡效果 if use_transition: await show_transition() + # 执行场景切换 var error = get_tree().change_scene_to_file(scene_path) if error != OK: print("场景切换失败: ", error) is_changing_scene = false return false + # 更新状态 current_scene_name = scene_name is_changing_scene = false scene_changed.emit(scene_name) + # 隐藏过渡效果 if use_transition: await hide_transition() print("场景切换完成: ", scene_name) return true +# ============ 查询方法 ============ + +# 获取当前场景名称 +# +# 返回值: +# String - 当前场景的名称 func get_current_scene_name() -> String: return current_scene_name +# ============ 场景注册方法 ============ + +# 注册新场景 +# +# 参数: +# scene_name: String - 场景名称(用于切换时引用) +# scene_path: String - 场景文件路径 +# +# 功能: +# - 将场景名称和路径添加到映射表 +# - 支持运行时动态注册场景 +# +# 使用示例: +# SceneManager.register_scene("boss_battle", "res://scenes/boss/boss_battle.tscn") func register_scene(scene_name: String, scene_path: String): scene_paths[scene_name] = scene_path print("注册场景: ", scene_name, " -> ", scene_path) +# ============ 过渡效果方法 ============ + +# 显示场景切换过渡效果 +# +# 功能: +# - 显示场景切换时的过渡动画 +# - 为用户提供视觉反馈 +# +# 注意事项: +# - 这是异步方法,需要await等待完成 +# - 当前实现为简单的延时,可扩展为复杂动画 +# +# TODO: 实现淡入淡出、滑动等过渡效果 func show_transition(): # TODO: 实现场景切换过渡效果 print("显示场景切换过渡效果") await get_tree().create_timer(0.2).timeout +# 隐藏场景切换过渡效果 +# +# 功能: +# - 隐藏场景切换完成后的过渡动画 +# - 恢复正常的游戏显示 +# +# 注意事项: +# - 这是异步方法,需要await等待完成 +# - 与show_transition()配对使用 +# +# TODO: 实现与show_transition()对应的隐藏效果 func hide_transition(): # TODO: 隐藏场景切换过渡效果 print("隐藏场景切换过渡效果") diff --git a/_Core/utils/StringUtils.gd b/_Core/utils/StringUtils.gd index b866af6..d91f4b7 100644 --- a/_Core/utils/StringUtils.gd +++ b/_Core/utils/StringUtils.gd @@ -1,26 +1,102 @@ class_name StringUtils -# 字符串工具类 - 提供常用的字符串处理功能 +# ============================================================================ +# StringUtils.gd - 字符串工具类 +# ============================================================================ +# 静态工具类,提供常用的字符串处理功能 +# +# 核心功能: +# - 输入验证(邮箱、用户名、密码) +# - 字符串格式化和转换 +# - 时间格式化和相对时间计算 +# - 文件大小格式化 +# +# 使用方式: +# var is_valid = StringUtils.is_valid_email("user@example.com") +# var formatted_time = StringUtils.format_utc_to_local_time(utc_string) +# +# 注意事项: +# - 所有方法都是静态的,无需实例化 +# - 验证方法返回布尔值或包含详细信息的字典 +# - 时间处理方法支持UTC到本地时间的转换 +# ============================================================================ + +# ============ 输入验证方法 ============ # 验证邮箱格式 +# +# 参数: +# email: String - 待验证的邮箱地址 +# +# 返回值: +# bool - true表示格式正确,false表示格式错误 +# +# 验证规则: +# - 必须包含@符号 +# - @前后都必须有内容 +# - 域名部分必须包含至少一个点 +# - 顶级域名至少2个字符 +# +# 使用示例: +# if StringUtils.is_valid_email("user@example.com"): +# print("邮箱格式正确") static 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 -# 验证用户名格式(字母、数字、下划线) +# 验证用户名格式 +# +# 参数: +# username: String - 待验证的用户名 +# +# 返回值: +# bool - true表示格式正确,false表示格式错误 +# +# 验证规则: +# - 只能包含字母、数字、下划线 +# - 长度不能为空且不超过50个字符 +# - 不能包含空格或特殊字符 +# +# 使用示例: +# if StringUtils.is_valid_username("user_123"): +# print("用户名格式正确") static func is_valid_username(username: String) -> bool: + # 检查长度 if username.is_empty() or username.length() > 50: return false + # 检查字符组成 var regex = RegEx.new() regex.compile("^[a-zA-Z0-9_]+$") return regex.search(username) != null # 验证密码强度 +# +# 参数: +# password: String - 待验证的密码 +# +# 返回值: +# Dictionary - 包含验证结果的详细信息 +# { +# "valid": bool, # 是否符合最低要求 +# "message": String, # 验证结果消息 +# "strength": int # 强度等级 (1-4) +# } +# +# 验证规则: +# - 最少8位,最多128位 +# - 必须包含字母和数字 +# - 强度评级:包含字母(+1)、数字(+1)、特殊字符(+1)、长度>=12(+1) +# +# 使用示例: +# var result = StringUtils.validate_password_strength("MyPass123!") +# if result.valid: +# print("密码强度: ", result.message) static func validate_password_strength(password: String) -> Dictionary: var result = {"valid": false, "message": "", "strength": 0} + # 检查长度限制 if password.length() < 8: result.message = "密码长度至少8位" return result @@ -29,9 +105,10 @@ static func validate_password_strength(password: String) -> Dictionary: result.message = "密码长度不能超过128位" return result - var has_letter = false - var has_digit = false - var has_special = false + # 检查字符类型 + var has_letter = false # 是否包含字母 + var has_digit = false # 是否包含数字 + var has_special = false # 是否包含特殊字符 for i in range(password.length()): var character = password[i] @@ -42,6 +119,7 @@ static func validate_password_strength(password: String) -> Dictionary: elif character in "!@#$%^&*()_+-=[]{}|;:,.<>?": has_special = true + # 计算强度等级 var strength = 0 if has_letter: strength += 1 @@ -54,27 +132,72 @@ static func validate_password_strength(password: String) -> Dictionary: result.strength = strength + # 检查最低要求 if not (has_letter and has_digit): result.message = "密码必须包含字母和数字" return result + # 密码符合要求 result.valid = true result.message = "密码强度: " + ["弱", "中", "强", "很强"][min(strength - 1, 3)] return result +# ============ 字符串格式化方法 ============ + # 截断字符串 +# +# 参数: +# text: String - 原始字符串 +# max_length: int - 最大长度 +# suffix: String - 截断后缀(默认为"...") +# +# 返回值: +# String - 截断后的字符串 +# +# 功能: +# - 如果字符串长度超过限制,截断并添加后缀 +# - 如果字符串长度未超过限制,返回原字符串 +# +# 使用示例: +# var short_text = StringUtils.truncate("这是一个很长的文本", 10, "...") +# # 结果: "这是一个很长..." static func truncate(text: String, max_length: int, suffix: String = "...") -> String: if text.length() <= max_length: return text return text.substr(0, max_length - suffix.length()) + suffix # 首字母大写 +# +# 参数: +# text: String - 原始字符串 +# +# 返回值: +# String - 首字母大写的字符串 +# +# 使用示例: +# var capitalized = StringUtils.capitalize_first("hello world") +# # 结果: "Hello world" static func capitalize_first(text: String) -> String: if text.is_empty(): return text return text[0].to_upper() + text.substr(1) -# 转换为标题格式(每个单词首字母大写) +# 转换为标题格式 +# +# 参数: +# text: String - 原始字符串 +# +# 返回值: +# String - 每个单词首字母大写的字符串 +# +# 功能: +# - 将每个单词的首字母转换为大写 +# - 其余字母转换为小写 +# - 以空格分隔单词 +# +# 使用示例: +# var title = StringUtils.to_title_case("hello world game") +# # 结果: "Hello World Game" static func to_title_case(text: String) -> String: var words = text.split(" ") var result = [] @@ -84,27 +207,75 @@ static func to_title_case(text: String) -> String: return " ".join(result) # 移除HTML标签 +# +# 参数: +# html: String - 包含HTML标签的字符串 +# +# 返回值: +# String - 移除HTML标签后的纯文本 +# +# 功能: +# - 使用正则表达式移除所有HTML标签 +# - 保留标签之间的文本内容 +# +# 使用示例: +# var plain_text = StringUtils.strip_html_tags("

Hello World

") +# # 结果: "Hello World" static func strip_html_tags(html: String) -> String: var regex = RegEx.new() regex.compile("<[^>]*>") return regex.sub(html, "", true) # 格式化文件大小 +# +# 参数: +# bytes: int - 文件大小(字节) +# +# 返回值: +# String - 格式化后的文件大小字符串 +# +# 功能: +# - 自动选择合适的单位(B, KB, MB, GB, TB) +# - 保留一位小数(除了字节) +# - 使用1024作为换算基数 +# +# 使用示例: +# var size_text = StringUtils.format_file_size(1536) +# # 结果: "1.5 KB" static func format_file_size(bytes: int) -> String: var units = ["B", "KB", "MB", "GB", "TB"] var size = float(bytes) var unit_index = 0 + # 自动选择合适的单位 while size >= 1024.0 and unit_index < units.size() - 1: size /= 1024.0 unit_index += 1 + # 格式化输出 if unit_index == 0: return str(int(size)) + " " + units[unit_index] else: return "%.1f %s" % [size, units[unit_index]] +# ============ 时间处理方法 ============ + # 将UTC时间字符串转换为本地时间显示 +# +# 参数: +# utc_time_str: String - UTC时间字符串(格式: 2025-12-25T11:23:52.175Z) +# +# 返回值: +# String - 格式化的本地时间字符串 +# +# 功能: +# - 解析ISO 8601格式的UTC时间 +# - 转换为本地时区时间 +# - 格式化为易读的中文时间格式 +# +# 使用示例: +# var local_time = StringUtils.format_utc_to_local_time("2025-12-25T11:23:52.175Z") +# # 结果: "2025年12月25日 19:23:52" (假设本地时区为UTC+8) static func format_utc_to_local_time(utc_time_str: String) -> String: # 解析UTC时间字符串 (格式: 2025-12-25T11:23:52.175Z) var regex = RegEx.new() @@ -148,7 +319,22 @@ static func format_utc_to_local_time(utc_time_str: String) -> String: local_dict.second ] -# 获取相对时间描述(多少分钟后) +# 获取相对时间描述 +# +# 参数: +# utc_time_str: String - UTC时间字符串 +# +# 返回值: +# String - 相对时间描述(如"5分钟后"、"2小时30分钟后") +# +# 功能: +# - 计算指定时间与当前时间的差值 +# - 返回人性化的相对时间描述 +# - 支持秒、分钟、小时的组合显示 +# +# 使用示例: +# var relative_time = StringUtils.get_relative_time_until("2025-12-25T12:00:00Z") +# # 结果: "30分钟后" 或 "现在可以重试" static func get_relative_time_until(utc_time_str: String) -> String: # 解析UTC时间字符串 var regex = RegEx.new() @@ -183,6 +369,7 @@ static func get_relative_time_until(utc_time_str: String) -> String: # 计算时间差(秒) var diff_seconds = target_timestamp - current_timestamp + # 格式化相对时间 if diff_seconds <= 0: return "现在可以重试" elif diff_seconds < 60: From 709242d223b3ce0ffd1ab789c9665075b9330560 Mon Sep 17 00:00:00 2001 From: moyin <2443444649@qq.com> Date: Fri, 2 Jan 2026 21:20:31 +0800 Subject: [PATCH 6/6] =?UTF-8?q?docs=EF=BC=9A=E6=9B=B4=E6=96=B0=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=BB=93=E6=9E=84=E8=AF=B4=E6=98=8E=E5=92=8C=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新ProjectPaths.gd中的路径常量 - 更新项目结构说明文档 - 清理临时测试文件的uid引用 确保文档与实际项目结构保持一致 --- _Core/ProjectPaths.gd | 2 ++ docs/01-项目入门/项目结构说明.md | 2 ++ tests/unit/test_toast_manager.gd.uid | 1 + 3 files changed, 5 insertions(+) create mode 100644 tests/unit/test_toast_manager.gd.uid diff --git a/_Core/ProjectPaths.gd b/_Core/ProjectPaths.gd index 7df5859..9f9afc1 100644 --- a/_Core/ProjectPaths.gd +++ b/_Core/ProjectPaths.gd @@ -16,6 +16,8 @@ class_name ProjectPaths const CORE_ROOT = "res://_Core/" const CORE_MANAGERS = CORE_ROOT + "managers/" const CORE_SYSTEMS = CORE_ROOT + "systems/" +const CORE_COMPONENTS = CORE_ROOT + "components/" +const CORE_UTILS = CORE_ROOT + "utils/" # ============================================================================ # 场景路径 diff --git a/docs/01-项目入门/项目结构说明.md b/docs/01-项目入门/项目结构说明.md index d983abc..0ee5a43 100644 --- a/docs/01-项目入门/项目结构说明.md +++ b/docs/01-项目入门/项目结构说明.md @@ -82,6 +82,8 @@ EventSystem.emit_event("player_moved", {"position": Vector2(100, 200)}) ``` scenes/ +├── MainScene.tscn # 🎯 主入口场景 - 所有图像显示的入口文件 +├── MainScene.gd # 主场景控制器脚本 ├── maps/ # 地图场景 │ ├── main_world.tscn # 主世界地图 │ ├── dungeon_01.tscn # 地牢场景 diff --git a/tests/unit/test_toast_manager.gd.uid b/tests/unit/test_toast_manager.gd.uid new file mode 100644 index 0000000..c78bc1e --- /dev/null +++ b/tests/unit/test_toast_manager.gd.uid @@ -0,0 +1 @@ +uid://cfcsbf2237mm2