class_name PayPanel extends PanelModule # 充值/充值入口面板: # - 负责充值、金豆购买等配置展示和模拟购买流程。 # - 海外 H5 首版不接真实支付 SDK,本面板只保留 UI 和后端接口入口。 # - 商品卡片不保存全局状态,支付结果通过 callbacks 更新玩家资源。 const NumberFormatterScript := preload("res://scripts/core/number_formatter.gd") var _pay_api var _tab_index := 0 var _content: Control func setup(options: Dictionary) -> void: super.setup(options) _pay_api = options.get("pay_api") func open() -> void: _close_modal(false) _tab_index = 0 var common_path: String = _texture_provider.atlas_path("common") var panel: Control = _ui.create_common_panel_shell("PayPanel", Vector2(600.0, 790.0), "panel_icon_pay", common_path, "panel_title_pay", common_path) _ui.add_content_panel_background(panel, "PayInner", Vector2(78.0, 238.0), Vector2(484.0, 550.0)) _create_tab_bar(panel, "PayTabs", ["金豆充值"], Vector2(93.0, 189.0), _tab_index, Callable(self, "_on_tab_pressed")) _content = Control.new() _content.name = "PayContent" _content.position = Vector2(83.0, 242.0) _content.size = Vector2(476.0, 540.0) panel.add_child(_content) _open_modal(panel) render() func _on_tab_pressed(index: int) -> void: _tab_index = 0 var panel := _content.get_parent() as Control if _content != null else null var tabs := panel.get_node_or_null("PayTabs") as Control if panel != null else null _update_tab_bar(tabs, _tab_index) render() func render() -> void: if _content == null: return _clear_children(_content) var rows := _filter_rows(_tab_index) if rows.is_empty(): _ui.add_panel_label(_content, "Empty", "暂无充值配置", Vector2(6.0, 196.0), Vector2(464.0, 52.0), 26, Color.html("#753f24"), 0, Color.TRANSPARENT) return for index in range(mini(rows.size(), 6)): var row := rows[index] var col := index % 3 var row_index := index / 3 var card_position := Vector2(10.0 + float(col) * 145.0, 10.0 + float(row_index) * 262.0) _add_card(_content, row, index, card_position) func _filter_rows(tab_index: int) -> Array[Dictionary]: var result: Array[Dictionary] = [] for raw_row in _game_config.get_pay_config_list(): if not raw_row is Dictionary: continue var row: Dictionary = raw_row.duplicate(true) var id := int(row.get("id", 0)) var is_gold := int(row.get("gold", 0)) > 0 or id > 6 if is_gold: result.append(row) result.sort_custom(func(left: Dictionary, right: Dictionary) -> bool: return int(left.get("id", 0)) < int(right.get("id", 0)) ) return result func _add_card(parent: Control, row: Dictionary, index: int, position: Vector2) -> void: var holder := Control.new() holder.name = "PayItem%s" % index holder.position = position holder.size = Vector2(134.0, 255.0) holder.pivot_offset = holder.size * 0.5 holder.mouse_filter = Control.MOUSE_FILTER_STOP parent.add_child(holder) _ui.add_texture(holder, "Bg", _texture_provider.standalone("res://assets/egret/assets/game/pay/pay_item_bg.png"), Vector2.ZERO, Vector2(134.0, 224.0), TextureRect.STRETCH_SCALE) var icon_prefix := "pay_g" var icon_path := "res://assets/egret/assets/game/pay/%s%s.png" % [icon_prefix, index + 1] _ui.add_texture(holder, "PayIcon", _texture_provider.standalone(icon_path, _texture_provider.standalone("res://assets/egret/assets/game/pay/pay_d1.png")), Vector2(25.0, 31.0), Vector2(85.0, 75.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED) _ui.add_texture(holder, "ValueBar", _texture_provider.standalone("res://assets/egret/assets/game/pay/pay_bar.png"), Vector2(0.0, 126.0), Vector2(134.0, 36.0), TextureRect.STRETCH_SCALE) var currency_icon: Texture2D = _texture_provider.common("sicon_gold") _ui.add_texture(holder, "Currency", currency_icon, Vector2(12.0, 132.0), Vector2(32.0, 31.0), TextureRect.STRETCH_KEEP_ASPECT_CENTERED) _ui.add_panel_label(holder, "Value", NumberFormatterScript.compact(_pay_value(row)), Vector2(43.0, 133.0), Vector2(88.0, 31.0), 22, Color.html("#f7b607"), 2, Color.html("#7d4b21"), HORIZONTAL_ALIGNMENT_LEFT) _ui.add_texture_button(holder, "Buy", _texture_provider.common("c_btn2"), Vector2(26.0, 179.0), Vector2(88.0, 44.0), "购买", Callable(self, "_buy_item").bind(row.duplicate(true)), 22) _ui.add_panel_label(holder, "Price", "¥%s" % int(row.get("rmb", 0)), Vector2(3.0, 228.0), Vector2(132.0, 27.0), 22, Color.html("#ff9b73"), 2, Color.WHITE) func _pay_value(row: Dictionary) -> int: return int(row.get("gold", 0)) func _buy_item(row: Dictionary) -> void: if _is_busy(): return var token := _token() var pay_id := int(row.get("id", 0)) if token.is_empty() or pay_id <= 0: _show_toast("登录信息失效") return _set_busy(true) var result: Dictionary = await _pay_api.buy(token, pay_id) _set_busy(false) if not result.get("ok", false): _show_toast(_response_status_text(result, "充值下单失败")) return var url := _payment_url(result) if url.is_empty(): _show_toast("充值下单成功") return _show_toast("正在打开支付") _open_payment_url(url) func _payment_url(result: Dictionary) -> String: var data = _response_data(result, {}) if data is Dictionary: return str(data.get("url", data.get("pay_url", ""))) return "" func _open_payment_url(url: String) -> void: if url.strip_edges().is_empty(): return if Engine.has_singleton("JavaScriptBridge"): var bridge := Engine.get_singleton("JavaScriptBridge") if bridge != null: bridge.call("eval", "window.location.href = %s;" % JSON.stringify(url), true) return OS.shell_open(url)