forked from datawhale/whale-town-front
refactor:AuthScene解耦重构,实现视图与业务逻辑分离
- 创建AuthManager.gd:负责所有认证业务逻辑 - 用户登录/注册逻辑 - 表单验证逻辑 - 验证码管理逻辑 - 网络请求管理 - 创建ToastManager.gd:负责Toast消息管理 - Toast创建和显示 - 动画和生命周期管理 - 支持成功/失败样式 - 重构AuthScene.gd:纯视图层实现 - 只负责UI交互和显示 - 通过信号与业务层通信 - 移除所有业务逻辑代码 - 修复GDScript警告: - 未使用参数添加下划线前缀 - 修复变量名与基类方法冲突 - 修复EventSystem中的try语法错误 - 修复AuthManager中的方法名不匹配错误 符合docs中的架构要求,实现完全解耦
This commit is contained in:
589
_Core/managers/AuthManager.gd
Normal file
589
_Core/managers/AuthManager.gd
Normal file
@@ -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
|
||||
1
_Core/managers/AuthManager.gd.uid
Normal file
1
_Core/managers/AuthManager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bpdyraefv0yta
|
||||
234
_Core/managers/ToastManager.gd
Normal file
234
_Core/managers/ToastManager.gd
Normal file
@@ -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
|
||||
1
_Core/managers/ToastManager.gd.uid
Normal file
1
_Core/managers/ToastManager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://buk7d21cag262
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user