diff --git a/UI/Windows/AuthScene.gd b/UI/Windows/AuthScene.gd deleted file mode 100644 index 4c235c0..0000000 --- a/UI/Windows/AuthScene.gd +++ /dev/null @@ -1,1032 +0,0 @@ -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() diff --git a/UI/Windows/AuthScene.gd.uid b/UI/Windows/AuthScene.gd.uid deleted file mode 100644 index 56d8ef1..0000000 --- a/UI/Windows/AuthScene.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bs1vy3ierj66t diff --git a/UI/Windows/LoginWindow.tscn b/UI/Windows/LoginWindow.tscn deleted file mode 100644 index 50e32f6..0000000 --- a/UI/Windows/LoginWindow.tscn +++ /dev/null @@ -1,561 +0,0 @@ -[gd_scene load_steps=10 format=3 uid="uid://by7m8snb4xllf"] - -[ext_resource type="Texture2D" uid="uid://bx17oy8lvaca4" path="res://assets/ui/auth/bg_auth_scene.png" id="1_background"] -[ext_resource type="Texture2D" uid="uid://de4q4s1gxivtf" path="res://assets/ui/auth/login_frame_smart_transparent.png" id="2_frame"] -[ext_resource type="Script" uid="uid://bs1vy3ierj66t" path="res://UI/Windows/AuthScene.gd" id="3_script"] - -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_1"] - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hover"] -bg_color = Color(0.3, 0.6, 0.9, 1) -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.2, 0.5, 0.8, 1) -corner_radius_top_left = 8 -corner_radius_top_right = 8 -corner_radius_bottom_right = 8 -corner_radius_bottom_left = 8 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_normal"] -bg_color = Color(0.2, 0.5, 0.8, 1) -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.15, 0.4, 0.7, 1) -corner_radius_top_left = 8 -corner_radius_top_right = 8 -corner_radius_bottom_right = 8 -corner_radius_bottom_left = 8 - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pressed"] -bg_color = Color(0.4, 0.7, 1, 1) -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 -border_color = Color(0.3, 0.6, 0.9, 1) -corner_radius_top_left = 8 -corner_radius_top_right = 8 -corner_radius_bottom_right = 8 -corner_radius_bottom_left = 8 - -[sub_resource type="Theme" id="Theme_main_button"] -Button/colors/font_color = Color(1, 1, 1, 1) -Button/colors/font_hover_color = Color(1, 1, 1, 1) -Button/colors/font_pressed_color = Color(1, 1, 1, 1) -Button/font_sizes/font_size = 18 -Button/styles/hover = SubResource("StyleBoxFlat_hover") -Button/styles/normal = SubResource("StyleBoxFlat_normal") -Button/styles/pressed = SubResource("StyleBoxFlat_pressed") - -[sub_resource type="Theme" id="Theme_button"] -Button/colors/font_color = Color(1, 1, 1, 1) -Button/colors/font_hover_color = Color(1, 1, 1, 1) -Button/colors/font_pressed_color = Color(1, 1, 1, 1) -Button/styles/hover = SubResource("StyleBoxFlat_hover") -Button/styles/normal = SubResource("StyleBoxFlat_normal") -Button/styles/pressed = SubResource("StyleBoxFlat_pressed") - -[node name="AuthScene" type="Control"] -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("3_script") - -[node name="HTTPRequest" type="HTTPRequest" parent="."] - -[node name="BackgroundImage" type="TextureRect" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -texture = ExtResource("1_background") -expand_mode = 1 -stretch_mode = 6 - -[node name="WhaleFrame" type="TextureRect" parent="."] -layout_mode = 1 -anchors_preset = 8 -anchor_left = 0.5 -anchor_top = 0.5 -anchor_right = 0.5 -anchor_bottom = 0.5 -offset_left = -300.0 -offset_top = -300.0 -offset_right = 300.0 -offset_bottom = 300.0 -grow_horizontal = 2 -grow_vertical = 2 -texture = ExtResource("2_frame") -expand_mode = 1 -stretch_mode = 5 - -[node name="CenterContainer" type="CenterContainer" parent="."] -layout_mode = 1 -anchors_preset = 8 -anchor_left = 0.5 -anchor_top = 0.5 -anchor_right = 0.5 -anchor_bottom = 0.5 -offset_left = -175.0 -offset_top = -184.0 -offset_right = 175.0 -offset_bottom = 236.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="LoginPanel" type="Panel" parent="CenterContainer"] -custom_minimum_size = Vector2(350, 400) -layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxEmpty_1") - -[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/LoginPanel"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = 30.0 -offset_top = 30.0 -offset_right = -30.0 -offset_bottom = -30.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="TitleLabel" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_font_sizes/font_size = 24 -text = "Whaletown" -horizontal_alignment = 1 -vertical_alignment = 1 - -[node name="SubtitleLabel" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_font_sizes/font_size = 14 -text = "开始你的小镇之旅!" -horizontal_alignment = 1 -vertical_alignment = 1 - -[node name="HSeparator" type="HSeparator" parent="CenterContainer/LoginPanel/VBoxContainer"] -layout_mode = 2 - -[node name="LoginForm" type="VBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="UsernameContainer" type="VBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm"] -layout_mode = 2 - -[node name="UsernameLabelContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer"] -layout_mode = 2 - -[node name="UsernameLabel" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer/UsernameLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "用户名/手机/邮箱" - -[node name="RequiredStar" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer/UsernameLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -text = " *" - -[node name="Spacer" type="Control" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer/UsernameLabelContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="UsernameError" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer/UsernameLabelContainer"] -visible = false -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -theme_override_font_sizes/font_size = 12 -text = "用户名不能为空" -horizontal_alignment = 2 - -[node name="UsernameInput" type="LineEdit" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/UsernameContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) -placeholder_text = "用户名/手机/邮箱" - -[node name="PasswordContainer" type="VBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm"] -layout_mode = 2 - -[node name="PasswordLabelContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer"] -layout_mode = 2 - -[node name="PasswordLabel" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer/PasswordLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "密码" - -[node name="RequiredStar" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer/PasswordLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -text = " *" - -[node name="Spacer" type="Control" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer/PasswordLabelContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="PasswordError" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer/PasswordLabelContainer"] -visible = false -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -theme_override_font_sizes/font_size = 12 -text = "密码不能为空" -horizontal_alignment = 2 - -[node name="PasswordInput" type="LineEdit" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/PasswordContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) -placeholder_text = "请输入密码" -secret = true - -[node name="VerificationContainer" type="VBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm"] -visible = false -layout_mode = 2 - -[node name="VerificationLabelContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer"] -layout_mode = 2 - -[node name="VerificationLabel" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "验证码" - -[node name="RequiredStar" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -text = " *" - -[node name="Spacer" type="Control" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="VerificationError" type="Label" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationLabelContainer"] -visible = false -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -theme_override_font_sizes/font_size = 12 -text = "请输入验证码" -horizontal_alignment = 2 - -[node name="VerificationInputContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer"] -layout_mode = 2 - -[node name="VerificationInput" type="LineEdit" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationInputContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) -placeholder_text = "请输入6位验证码" -max_length = 6 - -[node name="GetCodeBtn" type="Button" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/VerificationContainer/VerificationInputContainer"] -layout_mode = 2 -text = "获取验证码" - -[node name="CheckboxContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm"] -layout_mode = 2 - -[node name="RememberPassword" type="CheckBox" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/CheckboxContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "记住密码" - -[node name="AutoLogin" type="CheckBox" parent="CenterContainer/LoginPanel/VBoxContainer/LoginForm/CheckboxContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "自动登录" - -[node name="HSeparator2" type="HSeparator" parent="CenterContainer/LoginPanel/VBoxContainer"] -layout_mode = 2 - -[node name="MainButton" type="Button" parent="CenterContainer/LoginPanel/VBoxContainer"] -custom_minimum_size = Vector2(280, 50) -layout_mode = 2 -theme = SubResource("Theme_main_button") -text = "进入小镇" - -[node name="HSeparator3" type="HSeparator" parent="CenterContainer/LoginPanel/VBoxContainer"] -layout_mode = 2 - -[node name="ButtonContainer" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer"] -layout_mode = 2 -alignment = 1 - -[node name="LoginBtn" type="Button" parent="CenterContainer/LoginPanel/VBoxContainer/ButtonContainer"] -custom_minimum_size = Vector2(100, 35) -layout_mode = 2 -theme = SubResource("Theme_button") -text = "密码登录" - -[node name="BottomLinks" type="HBoxContainer" parent="CenterContainer/LoginPanel/VBoxContainer"] -layout_mode = 2 -alignment = 1 - -[node name="ForgotPassword" type="Button" parent="CenterContainer/LoginPanel/VBoxContainer/BottomLinks"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "忘记密码?" -flat = true - -[node name="RegisterLink" type="Button" parent="CenterContainer/LoginPanel/VBoxContainer/BottomLinks"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "注册居民身份" -flat = true - -[node name="RegisterPanel" type="Panel" parent="CenterContainer"] -visible = false -custom_minimum_size = Vector2(400, 570) -layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxEmpty_1") - -[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/RegisterPanel"] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = 30.0 -offset_top = 75.0 -offset_right = -30.0 -offset_bottom = -72.0 -grow_horizontal = 2 -grow_vertical = 2 -alignment = 1 - -[node name="TitleLabel" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_font_sizes/font_size = 20 -text = "注册新居民" -horizontal_alignment = 1 -vertical_alignment = 1 - -[node name="HSeparator" type="HSeparator" parent="CenterContainer/RegisterPanel/VBoxContainer"] -layout_mode = 2 - -[node name="RegisterForm" type="VBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer"] -layout_mode = 2 -size_flags_vertical = 3 -alignment = 1 - -[node name="UsernameContainer" type="VBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm"] -layout_mode = 2 - -[node name="UsernameLabelContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer"] -layout_mode = 2 - -[node name="UsernameLabel" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer/UsernameLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "用户名" - -[node name="RequiredStar" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer/UsernameLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -text = " *" - -[node name="Spacer" type="Control" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer/UsernameLabelContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="UsernameError" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer/UsernameLabelContainer"] -visible = false -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -theme_override_font_sizes/font_size = 12 -text = "用户名不能为空" -horizontal_alignment = 2 - -[node name="UsernameInput" type="LineEdit" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/UsernameContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) -placeholder_text = "请输入用户名" - -[node name="EmailContainer" type="VBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm"] -layout_mode = 2 - -[node name="EmailLabelContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer"] -layout_mode = 2 - -[node name="EmailLabel" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer/EmailLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "邮箱" - -[node name="RequiredStar" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer/EmailLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -text = " *" - -[node name="Spacer" type="Control" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer/EmailLabelContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="EmailError" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer/EmailLabelContainer"] -visible = false -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -theme_override_font_sizes/font_size = 12 -text = "邮箱不能为空" -horizontal_alignment = 2 - -[node name="EmailInput" type="LineEdit" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/EmailContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) -placeholder_text = "请输入邮箱地址" - -[node name="PasswordContainer" type="VBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm"] -layout_mode = 2 - -[node name="PasswordLabelContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer"] -layout_mode = 2 - -[node name="PasswordLabel" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer/PasswordLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "密码" - -[node name="RequiredStar" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer/PasswordLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -text = " *" - -[node name="Spacer" type="Control" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer/PasswordLabelContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="PasswordError" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer/PasswordLabelContainer"] -visible = false -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -theme_override_font_sizes/font_size = 12 -text = "密码不能为空" -horizontal_alignment = 2 - -[node name="PasswordInput" type="LineEdit" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/PasswordContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) -placeholder_text = "请输入密码(至少8位)" -secret = true - -[node name="ConfirmContainer" type="VBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm"] -layout_mode = 2 - -[node name="ConfirmLabelContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer"] -layout_mode = 2 - -[node name="ConfirmLabel" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer/ConfirmLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "确认密码" - -[node name="RequiredStar" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer/ConfirmLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -text = " *" - -[node name="Spacer" type="Control" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer/ConfirmLabelContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="ConfirmError" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer/ConfirmLabelContainer"] -visible = false -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -theme_override_font_sizes/font_size = 12 -text = "确认密码不能为空" -horizontal_alignment = 2 - -[node name="ConfirmInput" type="LineEdit" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/ConfirmContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) -placeholder_text = "请再次输入密码" -secret = true - -[node name="VerificationContainer" type="VBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm"] -layout_mode = 2 - -[node name="VerificationLabelContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer"] -layout_mode = 2 - -[node name="VerificationLabel" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(0, 0, 0, 1) -text = "邮箱验证码" - -[node name="RequiredStar" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationLabelContainer"] -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -text = " *" - -[node name="Spacer" type="Control" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationLabelContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="VerificationError" type="Label" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationLabelContainer"] -visible = false -layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.2, 0.2, 1) -theme_override_font_sizes/font_size = 12 -text = "验证码不能为空" -horizontal_alignment = 2 - -[node name="VerificationInputContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer"] -layout_mode = 2 - -[node name="VerificationInput" type="LineEdit" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationInputContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_colors/font_color = Color(0, 0, 0, 1) -theme_override_colors/font_placeholder_color = Color(0.5, 0.5, 0.5, 1) -placeholder_text = "请输入6位验证码" -max_length = 6 - -[node name="SendCodeBtn" type="Button" parent="CenterContainer/RegisterPanel/VBoxContainer/RegisterForm/VerificationContainer/VerificationInputContainer"] -layout_mode = 2 -text = "发送验证码" - -[node name="HSeparator2" type="HSeparator" parent="CenterContainer/RegisterPanel/VBoxContainer"] -layout_mode = 2 - -[node name="ButtonContainer" type="HBoxContainer" parent="CenterContainer/RegisterPanel/VBoxContainer"] -layout_mode = 2 -alignment = 1 - -[node name="RegisterBtn" type="Button" parent="CenterContainer/RegisterPanel/VBoxContainer/ButtonContainer"] -custom_minimum_size = Vector2(120, 45) -layout_mode = 2 -theme = SubResource("Theme_button") -text = "注册" - -[node name="ToLoginBtn" type="Button" parent="CenterContainer/RegisterPanel/VBoxContainer/ButtonContainer"] -custom_minimum_size = Vector2(120, 45) -layout_mode = 2 -theme = SubResource("Theme_button") -text = "返回登录" - -[node name="ToastContainer" type="Control" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -mouse_filter = 2 diff --git a/Utils/StringUtils.gd b/Utils/StringUtils.gd deleted file mode 100644 index 4d69728..0000000 --- a/Utils/StringUtils.gd +++ /dev/null @@ -1,199 +0,0 @@ -class_name StringUtils - -# 字符串工具类 - 提供常用的字符串处理功能 - -# 验证邮箱格式 -static func is_valid_email(email: String) -> bool: - var regex = RegEx.new() - regex.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$") - return regex.search(email) != null - -# 验证用户名格式(字母、数字、下划线) -static func is_valid_username(username: String) -> bool: - if username.is_empty() or username.length() > 50: - return false - - var regex = RegEx.new() - regex.compile("^[a-zA-Z0-9_]+$") - return regex.search(username) != null - -# 验证密码强度 -static func validate_password_strength(password: String) -> Dictionary: - var result = {"valid": false, "message": "", "strength": 0} - - if password.length() < 8: - result.message = "密码长度至少8位" - return result - - if password.length() > 128: - result.message = "密码长度不能超过128位" - return result - - var has_letter = false - var has_digit = false - var has_special = false - - for i in range(password.length()): - var character = password[i] - if character >= 'a' and character <= 'z' or character >= 'A' and character <= 'Z': - has_letter = true - elif character >= '0' and character <= '9': - has_digit = true - elif character in "!@#$%^&*()_+-=[]{}|;:,.<>?": - has_special = true - - var strength = 0 - if has_letter: - strength += 1 - if has_digit: - strength += 1 - if has_special: - strength += 1 - if password.length() >= 12: - strength += 1 - - result.strength = strength - - if not (has_letter and has_digit): - result.message = "密码必须包含字母和数字" - return result - - result.valid = true - result.message = "密码强度: " + ["弱", "中", "强", "很强"][min(strength - 1, 3)] - return result - -# 截断字符串 -static func truncate(text: String, max_length: int, suffix: String = "...") -> String: - if text.length() <= max_length: - return text - return text.substr(0, max_length - suffix.length()) + suffix - -# 首字母大写 -static func capitalize_first(text: String) -> String: - if text.is_empty(): - return text - return text[0].to_upper() + text.substr(1) - -# 转换为标题格式(每个单词首字母大写) -static func to_title_case(text: String) -> String: - var words = text.split(" ") - var result = [] - for word in words: - if not word.is_empty(): - result.append(capitalize_first(word.to_lower())) - return " ".join(result) - -# 移除HTML标签 -static func strip_html_tags(html: String) -> String: - var regex = RegEx.new() - regex.compile("<[^>]*>") - return regex.sub(html, "", true) - -# 格式化文件大小 -static func format_file_size(bytes: int) -> String: - var units = ["B", "KB", "MB", "GB", "TB"] - var size = float(bytes) - var unit_index = 0 - - while size >= 1024.0 and unit_index < units.size() - 1: - size /= 1024.0 - unit_index += 1 - - if unit_index == 0: - return str(int(size)) + " " + units[unit_index] - else: - return "%.1f %s" % [size, units[unit_index]] - -# 将UTC时间字符串转换为本地时间显示 -static func format_utc_to_local_time(utc_time_str: String) -> String: - # 解析UTC时间字符串 (格式: 2025-12-25T11:23:52.175Z) - var regex = RegEx.new() - regex.compile("(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})") - var result = regex.search(utc_time_str) - - if result == null: - return utc_time_str # 如果解析失败,返回原字符串 - - # 提取时间组件 - var year = int(result.get_string(1)) - var month = int(result.get_string(2)) - var day = int(result.get_string(3)) - var hour = int(result.get_string(4)) - var minute = int(result.get_string(5)) - var second = int(result.get_string(6)) - - # 创建UTC时间字典 - var utc_dict = { - "year": year, - "month": month, - "day": day, - "hour": hour, - "minute": minute, - "second": second - } - - # 转换为Unix时间戳(UTC) - var utc_timestamp = Time.get_unix_time_from_datetime_dict(utc_dict) - - # 获取本地时间(Godot会自动处理时区转换) - var local_dict = Time.get_datetime_dict_from_unix_time(utc_timestamp) - - # 格式化为易读的本地时间 - return "%04d年%02d月%02d日 %02d:%02d:%02d" % [ - local_dict.year, - local_dict.month, - local_dict.day, - local_dict.hour, - local_dict.minute, - local_dict.second - ] - -# 获取相对时间描述(多少分钟后) -static func get_relative_time_until(utc_time_str: String) -> String: - # 解析UTC时间字符串 - var regex = RegEx.new() - regex.compile("(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})") - var result = regex.search(utc_time_str) - - if result == null: - return "时间格式错误" - - # 提取时间组件 - var year = int(result.get_string(1)) - var month = int(result.get_string(2)) - var day = int(result.get_string(3)) - var hour = int(result.get_string(4)) - var minute = int(result.get_string(5)) - var second = int(result.get_string(6)) - - # 创建UTC时间字典 - var utc_dict = { - "year": year, - "month": month, - "day": day, - "hour": hour, - "minute": minute, - "second": second - } - - # 转换为Unix时间戳 - var target_timestamp = Time.get_unix_time_from_datetime_dict(utc_dict) - var current_timestamp = Time.get_unix_time_from_system() - - # 计算时间差(秒) - var diff_seconds = target_timestamp - current_timestamp - - if diff_seconds <= 0: - return "现在可以重试" - elif diff_seconds < 60: - return "%d秒后" % diff_seconds - elif diff_seconds < 3600: - var minutes = int(diff_seconds / 60) - return "%d分钟后" % minutes - else: - var hours = int(diff_seconds / 3600) - var minutes = int((diff_seconds % 3600) / 60) - if minutes > 0: - return "%d小时%d分钟后" % [hours, minutes] - else: - return "%d小时后" % hours diff --git a/Utils/StringUtils.gd.uid b/Utils/StringUtils.gd.uid deleted file mode 100644 index aded9a0..0000000 --- a/Utils/StringUtils.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://csxag1y8yq57j diff --git a/scenes/Components/characters/.gitkeep b/scenes/Components/characters/.gitkeep deleted file mode 100644 index fb0695e..0000000 --- a/scenes/Components/characters/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# 保持目录结构 - 角色预制体目录 \ No newline at end of file diff --git a/scenes/Components/effects/.gitkeep b/scenes/Components/effects/.gitkeep deleted file mode 100644 index 6691578..0000000 --- a/scenes/Components/effects/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# 保持目录结构 - 特效预制体目录 \ No newline at end of file diff --git a/scenes/Components/items/.gitkeep b/scenes/Components/items/.gitkeep deleted file mode 100644 index 2e3c48c..0000000 --- a/scenes/Components/items/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# 保持目录结构 - 物品预制体目录 \ No newline at end of file diff --git a/scenes/Components/ui/.gitkeep b/scenes/Components/ui/.gitkeep deleted file mode 100644 index 884109f..0000000 --- a/scenes/Components/ui/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# 保持目录结构 - UI预制体目录 \ No newline at end of file