126 lines
3.2 KiB
JavaScript
126 lines
3.2 KiB
JavaScript
// 赛马竞猜悬浮按钮组件,负责拖动位置持久化和打开赛马面板。
|
|
|
|
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<string, any>}
|
|
*/
|
|
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;
|
|
}
|