Files
whale-town-front/scripts/scenes/AuthScene.gd
moyin 92c4eaed07 refactor:重构认证场景和网络集成
- 重构AuthScene脚本,集成新的NetworkManager
- 优化用户界面布局和交互逻辑
- 改进错误处理和用户反馈机制
- 统一网络请求处理流程
2025-12-25 23:12:49 +08:00

1033 lines
35 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
extends Control
# 信号定义
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
# 登录表单
@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管理
var active_toasts: Array = []
var toast_counter: int = 0
# 验证码状态
var verification_codes_sent: Dictionary = {}
var code_cooldown: float = 60.0
var cooldown_timer: Timer = null
var current_email: String = ""
# 登录模式枚举
enum LoginMode {
PASSWORD, # 密码登录模式
VERIFICATION # 验证码登录模式
}
# 当前登录模式
var current_login_mode: LoginMode = LoginMode.PASSWORD
# 网络请求管理
var active_request_ids: Array = []
func _ready():
connect_signals()
show_login_panel()
update_login_mode_ui() # 初始化登录模式UI
await get_tree().process_frame
print("认证系统已加载")
test_network_connection()
func test_network_connection():
print("=== 测试网络连接 ===")
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():
# 主要按钮
main_btn.pressed.connect(_on_main_button_pressed)
# 登录界面按钮
login_btn.pressed.connect(_on_login_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)
# 登录表单失焦验证
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)
# 实时输入验证
register_username.text_changed.connect(_on_register_username_text_changed)
register_email.text_changed.connect(_on_register_email_text_changed)
register_password.text_changed.connect(_on_register_password_text_changed)
register_confirm.text_changed.connect(_on_register_confirm_text_changed)
verification_input.text_changed.connect(_on_verification_text_changed)
func show_login_panel():
login_panel.visible = true
register_panel.visible = false
login_username.grab_focus()
func show_register_panel():
login_panel.visible = false
register_panel.visible = true
register_username.grab_focus()
# 更新登录模式UI
func update_login_mode_ui():
if current_login_mode == LoginMode.PASSWORD:
# 密码登录模式
login_btn.text = "验证码登录"
forgot_password_btn.text = "忘记密码"
# 显示密码输入框,隐藏验证码输入框
password_container.visible = true
verification_container.visible = false
# 清空验证码输入框和错误提示
login_verification.text = ""
hide_field_error(login_verification_error)
else: # VERIFICATION mode
# 验证码登录模式
login_btn.text = "密码登录"
forgot_password_btn.text = "获取验证码"
# 隐藏密码输入框,显示验证码输入框
password_container.visible = false
verification_container.visible = true
# 清空密码输入框和错误提示
login_password.text = ""
hide_field_error(login_password_error)
# 这里需要根据实际UI结构调整
# 切换登录模式
func toggle_login_mode():
if current_login_mode == LoginMode.PASSWORD:
current_login_mode = LoginMode.VERIFICATION
else:
current_login_mode = LoginMode.PASSWORD
update_login_mode_ui()
# 清空输入框
login_username.text = ""
login_password.text = ""
hide_field_error(login_username_error)
hide_field_error(login_password_error)
# ============ 按钮事件处理 ============
func _on_main_button_pressed():
# 根据当前登录模式执行不同的登录逻辑
if current_login_mode == LoginMode.PASSWORD:
_execute_password_login()
else:
_execute_verification_login()
func _execute_password_login():
if not validate_login_form():
return
var username = login_username.text.strip_edges()
var password = login_password.text
show_loading(main_btn, "登录中...")
show_toast('正在验证登录信息...', true)
var request_id = NetworkManager.login(username, password, _on_login_response)
if request_id != "":
active_request_ids.append(request_id)
else:
restore_button(main_btn, "进入小镇")
show_toast('网络请求失败', false)
func _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)
func _on_login_pressed():
# 现在这个按钮用于切换登录模式
toggle_login_mode()
func _on_register_pressed():
print("注册按钮被点击")
if not validate_register_form():
print("注册表单验证失败")
show_toast('请检查并完善注册信息', false)
return
print("注册表单验证通过,开始注册流程")
var username = register_username.text.strip_edges()
var email = register_email.text.strip_edges()
var password = register_password.text
var verification_code = verification_input.text.strip_edges()
show_loading(register_btn, "注册中...")
show_toast('正在创建账户...', true)
# 直接调用注册接口,让服务器端处理验证码验证
send_register_request(username, email, password, verification_code)
func _on_send_code_pressed():
var email = register_email.text.strip_edges()
var email_validation = validate_email(email)
if not email_validation.valid:
show_toast(email_validation.message, false)
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()
func _on_get_login_code_pressed():
var identifier = login_username.text.strip_edges()
if identifier.is_empty():
show_field_error(login_username_error, "请先输入用户名/手机/邮箱")
login_username.grab_focus()
return
show_loading(get_code_btn, "发送中...")
show_toast('正在发送登录验证码...', true)
var request_id = NetworkManager.send_login_verification_code(identifier, _on_send_login_code_response)
if request_id != "":
active_request_ids.append(request_id)
else:
restore_button(get_code_btn, "获取验证码")
show_toast('网络请求失败', false)
func _on_to_login_pressed():
show_login_panel()
func _on_login_enter(_text: String):
_on_login_pressed()
func _on_forgot_password_pressed():
var identifier = login_username.text.strip_edges()
if identifier.is_empty():
show_toast('请先输入邮箱或手机号', false)
login_username.grab_focus()
return
if not is_valid_email(identifier) and not is_valid_phone(identifier):
show_toast('请输入有效的邮箱或手机号', false)
login_username.grab_focus()
return
if current_login_mode == LoginMode.PASSWORD:
# 密码登录模式:发送密码重置验证码
show_loading(forgot_password_btn, "发送中...")
show_toast('正在发送密码重置验证码...', true)
var request_id = NetworkManager.forgot_password(identifier, _on_forgot_password_response)
if request_id != "":
active_request_ids.append(request_id)
else:
restore_button(forgot_password_btn, "忘记密码")
show_toast('网络请求失败', false)
else:
# 验证码登录模式:发送登录验证码
show_loading(forgot_password_btn, "发送中...")
show_toast('正在发送登录验证码...', true)
var request_id = NetworkManager.send_login_verification_code(identifier, _on_send_login_code_response)
if request_id != "":
active_request_ids.append(request_id)
else:
restore_button(forgot_password_btn, "获取验证码")
show_toast('网络请求失败', false)
# ============ 网络响应处理 ============
func send_register_request(username: String, email: String, password: String, verification_code: String = ""):
var 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_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_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_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)
# 恢复按钮状态
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)
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_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_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):
if cooldown_timer != null:
cooldown_timer.queue_free()
current_email = email
send_code_btn.disabled = true
send_code_btn.text = "重新发送(60)"
cooldown_timer = Timer.new()
add_child(cooldown_timer)
cooldown_timer.wait_time = 1.0
cooldown_timer.timeout.connect(_on_cooldown_timer_timeout)
cooldown_timer.start()
func _on_cooldown_timer_timeout():
var input_email = register_email.text.strip_edges()
if input_email != current_email:
stop_current_cooldown()
return
if verification_codes_sent.has(current_email):
var current_time = Time.get_time_dict_from_system()
var current_timestamp = current_time.hour * 3600 + current_time.minute * 60 + current_time.second
var email_data = verification_codes_sent[current_email]
var remaining = code_cooldown - (current_timestamp - email_data.time)
if remaining > 0:
send_code_btn.text = "重新发送(%d)" % remaining
else:
send_code_btn.text = "重新发送"
send_code_btn.disabled = false
if cooldown_timer != null:
cooldown_timer.queue_free()
cooldown_timer = null
current_email = ""
func stop_current_cooldown():
if cooldown_timer != null:
cooldown_timer.queue_free()
cooldown_timer = null
send_code_btn.disabled = false
send_code_btn.text = "发送验证码"
current_email = ""
func reset_verification_button():
if current_email != "" and verification_codes_sent.has(current_email):
verification_codes_sent[current_email].sent = false
stop_current_cooldown()
func clear_register_form():
register_username.text = ""
register_email.text = ""
register_password.text = ""
register_confirm.text = ""
verification_input.text = ""
stop_current_cooldown()
verification_codes_sent.clear()
hide_field_error(register_username_error)
hide_field_error(register_email_error)
hide_field_error(register_password_error)
hide_field_error(register_confirm_error)
hide_field_error(verification_error)
# ============ Toast消息系统 ============
# 检测文本是否包含中文字符
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
# ============ 表单验证事件 ============
func _on_login_username_focus_exited():
var username = login_username.text.strip_edges()
if username.is_empty():
show_field_error(login_username_error, "用户名不能为空")
else:
hide_field_error(login_username_error)
func _on_login_password_focus_exited():
var password = login_password.text
if password.is_empty():
show_field_error(login_password_error, "密码不能为空")
else:
hide_field_error(login_password_error)
func _on_login_verification_focus_exited():
var verification_code = login_verification.text.strip_edges()
if verification_code.is_empty():
show_field_error(login_verification_error, "验证码不能为空")
elif verification_code.length() != 6:
show_field_error(login_verification_error, "验证码必须是6位数字")
elif not verification_code.is_valid_int():
show_field_error(login_verification_error, "验证码只能包含数字")
else:
hide_field_error(login_verification_error)
func _on_register_username_focus_exited():
var username = register_username.text.strip_edges()
var validation = validate_username(username)
if not validation.valid:
show_field_error(register_username_error, validation.message)
else:
hide_field_error(register_username_error)
func _on_register_email_focus_exited():
var email = register_email.text.strip_edges()
var validation = validate_email(email)
if not validation.valid:
show_field_error(register_email_error, validation.message)
else:
hide_field_error(register_email_error)
func _on_register_password_focus_exited():
var password = register_password.text
var validation = validate_password(password)
if not validation.valid:
show_field_error(register_password_error, validation.message)
else:
hide_field_error(register_password_error)
if not register_confirm.text.is_empty():
_on_register_confirm_focus_exited()
func _on_register_confirm_focus_exited():
var password = register_password.text
var confirm = register_confirm.text
var validation = validate_confirm_password(password, confirm)
if not validation.valid:
show_field_error(register_confirm_error, validation.message)
else:
hide_field_error(register_confirm_error)
func _on_verification_focus_exited():
var code = verification_input.text.strip_edges()
var validation = validate_verification_code(code)
if not validation.valid:
show_field_error(verification_error, validation.message)
else:
hide_field_error(verification_error)
# ============ 实时输入验证事件 ============
func _on_register_username_text_changed(new_text: String):
if register_username_error.visible and not new_text.is_empty():
hide_field_error(register_username_error)
func _on_register_email_text_changed(new_text: String):
if register_email_error.visible and not new_text.is_empty():
hide_field_error(register_email_error)
func _on_register_password_text_changed(new_text: String):
if register_password_error.visible and not new_text.is_empty():
hide_field_error(register_password_error)
func _on_register_confirm_text_changed(new_text: String):
if register_confirm_error.visible and not new_text.is_empty():
hide_field_error(register_confirm_error)
func _on_verification_text_changed(new_text: String):
if verification_error.visible and not new_text.is_empty():
hide_field_error(verification_error)
# ============ 表单整体验证 ============
func validate_login_form() -> bool:
var is_valid = true
var username = login_username.text.strip_edges()
var password = login_password.text
if username.is_empty():
show_field_error(login_username_error, "用户名不能为空")
is_valid = false
else:
hide_field_error(login_username_error)
if password.is_empty():
show_field_error(login_password_error, "密码不能为空")
is_valid = false
else:
hide_field_error(login_password_error)
return is_valid
func validate_register_form() -> bool:
print("开始验证注册表单")
var is_valid = true
var username = register_username.text.strip_edges()
var email = register_email.text.strip_edges()
var password = register_password.text
var confirm = register_confirm.text
var verification_code = verification_input.text.strip_edges()
print("表单数据: 用户名='%s', 邮箱='%s', 密码长度=%d, 确认密码长度=%d, 验证码='%s'" % [username, email, password.length(), confirm.length(), verification_code])
var username_validation = validate_username(username)
if not username_validation.valid:
print("用户名验证失败: ", username_validation.message)
show_field_error(register_username_error, username_validation.message)
is_valid = false
else:
hide_field_error(register_username_error)
var email_validation = validate_email(email)
if not email_validation.valid:
print("邮箱验证失败: ", email_validation.message)
show_field_error(register_email_error, email_validation.message)
is_valid = false
else:
hide_field_error(register_email_error)
var password_validation = validate_password(password)
if not password_validation.valid:
print("密码验证失败: ", password_validation.message)
show_field_error(register_password_error, password_validation.message)
is_valid = false
else:
hide_field_error(register_password_error)
var confirm_validation = validate_confirm_password(password, confirm)
if not confirm_validation.valid:
print("确认密码验证失败: ", confirm_validation.message)
show_field_error(register_confirm_error, confirm_validation.message)
is_valid = false
else:
hide_field_error(register_confirm_error)
var code_validation = validate_verification_code(verification_code)
if not code_validation.valid:
print("验证码格式验证失败: ", code_validation.message)
show_field_error(verification_error, code_validation.message)
is_valid = false
else:
hide_field_error(verification_error)
var current_email_input = register_email.text.strip_edges()
var has_sent_code = false
if verification_codes_sent.has(current_email_input):
var email_data = verification_codes_sent[current_email_input]
has_sent_code = email_data.get("sent", false)
if not has_sent_code:
print("当前邮箱验证码未发送email = ", current_email_input)
show_toast("请先获取邮箱验证码", false)
is_valid = false
print("表单验证结果: ", is_valid)
return is_valid
# ============ 资源清理 ============
func _exit_tree():
for request_id in active_request_ids:
NetworkManager.cancel_request(request_id)
active_request_ids.clear()
if cooldown_timer != null:
cooldown_timer.queue_free()
cooldown_timer = null
func _input(event):
if event.is_action_pressed("ui_cancel"):
get_tree().quit()