From d5f9cf73715f14cd7417268ecc0c34fed814e9ac Mon Sep 17 00:00:00 2001 From: lkddi Date: Sat, 25 Apr 2026 18:13:00 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=81=E7=A7=BB=E8=B5=9B=E9=A9=AC=E6=82=AC?= =?UTF-8?q?=E6=B5=AE=E6=8C=89=E9=92=AE=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/js/chat-room.js | 7 + resources/js/chat-room/horse-race-fab.js | 125 ++++++++++++++++++ .../partials/games/horse-race-panel.blade.php | 54 +------- 3 files changed, 133 insertions(+), 53 deletions(-) create mode 100644 resources/js/chat-room/horse-race-fab.js diff --git a/resources/js/chat-room.js b/resources/js/chat-room.js index 5c03798..d9c45f6 100644 --- a/resources/js/chat-room.js +++ b/resources/js/chat-room.js @@ -29,6 +29,7 @@ * - game-hall.js:处理娱乐大厅弹窗和游戏入口卡片。 * - game-bootstrap.js:提供非关键游戏延迟初始化工具。 * - game-panels.js:处理通用游戏面板关闭事件。 + * - horse-race-fab.js:处理赛马竞猜悬浮按钮拖动与打开面板。 * - holiday-modal.js:处理节日福利弹窗、广播监听、领取状态和系统消息入口。 * - initial-state.js:恢复首屏历史消息、欢迎消息、入场特效和挂起婚姻事件。 * - bank-modal.js:处理银行弹窗、转账、排行和标签切换。 @@ -106,6 +107,7 @@ export { export { bindGameHallControls, closeGameHall, openGameHall } from "./chat-room/game-hall.js"; export { bindGameBootstrapControls, deferChatGameBootstrap } from "./chat-room/game-bootstrap.js"; export { bindGamePanelControls } from "./chat-room/game-panels.js"; +export { bindHorseRaceFabControls, horseRaceFab } from "./chat-room/horse-race-fab.js"; export { bindHolidayModalControls, buildHolidayClaimActionButton, @@ -250,6 +252,7 @@ import { import { bindGameHallControls, closeGameHall, openGameHall } from "./chat-room/game-hall.js"; import { bindGameBootstrapControls, deferChatGameBootstrap } from "./chat-room/game-bootstrap.js"; import { bindGamePanelControls } from "./chat-room/game-panels.js"; +import { bindHorseRaceFabControls, horseRaceFab } from "./chat-room/horse-race-fab.js"; import { bindHolidayModalControls, buildHolidayClaimActionButton, @@ -401,6 +404,8 @@ if (typeof window !== "undefined") { bindGameBootstrapControls, deferChatGameBootstrap, bindGamePanelControls, + bindHorseRaceFabControls, + horseRaceFab, bindHolidayModalControls, buildHolidayClaimActionButton, buildHolidaySystemMessage, @@ -559,6 +564,7 @@ if (typeof window !== "undefined") { window.deferChatGameBootstrap = deferChatGameBootstrap; window.lotteryPanel = lotteryPanel; window.openGameHall = openGameHall; + window.horseRaceFab = horseRaceFab; window.openLotteryPanel = openLotteryPanel; window.openBankModal = openBankModal; window.showLotteryMsg = showLotteryMsg; @@ -638,6 +644,7 @@ if (typeof window !== "undefined") { bindGameHallControls(); bindGameBootstrapControls(); bindGamePanelControls(); + bindHorseRaceFabControls(); bindHolidayModalControls(); bindChatInitialStateControls(); bindBankControls(); diff --git a/resources/js/chat-room/horse-race-fab.js b/resources/js/chat-room/horse-race-fab.js new file mode 100644 index 0000000..0028477 --- /dev/null +++ b/resources/js/chat-room/horse-race-fab.js @@ -0,0 +1,125 @@ +// 赛马竞猜悬浮按钮组件,负责拖动位置持久化和打开赛马面板。 + +const HORSE_RACE_FAB_STORAGE_KEY = "horse_race_fab_pos"; + +/** + * 安全读取赛马悬浮按钮保存的位置。 + * + * @returns {{x?: number, y?: number}|null} + */ +function readSavedHorseRaceFabPosition() { + try { + return JSON.parse(localStorage.getItem(HORSE_RACE_FAB_STORAGE_KEY) || "null"); + } catch (error) { + return null; + } +} + +/** + * 创建赛马竞猜悬浮按钮 Alpine 组件。 + * + * @returns {Record} + */ +export function horseRaceFab() { + const saved = readSavedHorseRaceFabPosition(); + + return { + visible: false, + posX: saved?.x ?? 80, + posY: saved?.y ?? 90, + dragging: false, + _startX: 0, + _startY: 0, + _origX: 0, + _origY: 0, + _moved: false, + + /** + * 开始拖动赛马悬浮按钮。 + * + * @param {PointerEvent} event 指针事件 + * @returns {void} + */ + startDrag(event) { + this.dragging = true; + this._moved = false; + this._startX = event.clientX; + this._startY = event.clientY; + this._origX = this.posX; + this._origY = this.posY; + event.currentTarget.setPointerCapture?.(event.pointerId); + }, + + /** + * 拖动过程中约束按钮不超出视口。 + * + * @param {PointerEvent} event 指针事件 + * @returns {void} + */ + onDrag(event) { + if (!this.dragging) { + return; + } + + const dx = event.clientX - this._startX; + const dy = event.clientY - this._startY; + + if (Math.abs(dx) > 3 || Math.abs(dy) > 3) { + this._moved = true; + } + + // 赛马按钮使用 right/bottom 定位,拖动方向需要反向换算。 + this.posX = Math.max(4, Math.min(window.innerWidth - 132, this._origX - dx)); + this.posY = Math.max(4, Math.min(window.innerHeight - 132, this._origY - dy)); + }, + + /** + * 结束拖动;没有移动时按点击处理并打开赛马面板。 + * + * @returns {void} + */ + endDrag() { + if (!this.dragging) { + return; + } + + this.dragging = false; + localStorage.setItem(HORSE_RACE_FAB_STORAGE_KEY, JSON.stringify({ + x: this.posX, + y: this.posY, + })); + + if (!this._moved) { + this.openPanel(); + } + }, + + /** + * 打开赛马主面板。 + * + * @returns {void} + */ + openPanel() { + const panel = document.getElementById("horse-race-panel"); + + if (!panel || typeof window.Alpine?.$data !== "function") { + return; + } + + window.Alpine.$data(panel).openFromHall(); + }, + }; +} + +/** + * 挂载赛马悬浮按钮全局组件名,兼容 Blade 的 x-data。 + * + * @returns {void} + */ +export function bindHorseRaceFabControls() { + if (typeof window === "undefined") { + return; + } + + window.horseRaceFab = horseRaceFab; +} diff --git a/resources/views/chat/partials/games/horse-race-panel.blade.php b/resources/views/chat/partials/games/horse-race-panel.blade.php index 2dce920..0853c28 100644 --- a/resources/views/chat/partials/games/horse-race-panel.blade.php +++ b/resources/views/chat/partials/games/horse-race-panel.blade.php @@ -328,59 +328,7 @@