1033 lines
35 KiB
GDScript
1033 lines
35 KiB
GDScript
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()
|