老虎机三项修复:①来源label已有枚举(刷新即显中文) ②普通中奖/诅咒向本人发私聊通知+三7全服广播 ③FAB按钮支持拖动+位置localStorage持久化
This commit is contained in:
@@ -146,6 +146,7 @@ class SlotMachineController extends Controller
|
||||
}
|
||||
|
||||
// ⑤ 写游戏日志
|
||||
$resultLabel = SlotMachineLog::resultLabel($resultType);
|
||||
SlotMachineLog::create([
|
||||
'user_id' => $user->id,
|
||||
'reel1' => $r1,
|
||||
@@ -156,9 +157,23 @@ class SlotMachineController extends Controller
|
||||
'payout' => $payout,
|
||||
]);
|
||||
|
||||
// ⑥ 三个7:全服公屏广播
|
||||
// ⑥ 广播通知
|
||||
$e1 = $symbols[$r1]['emoji'];
|
||||
$e2 = $symbols[$r2]['emoji'];
|
||||
$e3 = $symbols[$r3]['emoji'];
|
||||
|
||||
if ($resultType === 'jackpot') {
|
||||
// 三个7:全服公屏广播
|
||||
$this->broadcastJackpot($user->username, $payout, $cost);
|
||||
} elseif (in_array($resultType, ['triple_gem', 'triple', 'pair'], true)) {
|
||||
// 普通中奖:仅向本人发送聊天室系统通知
|
||||
$net = $payout - $cost;
|
||||
$content = "🎰 {$resultLabel}!{$e1}{$e2}{$e3} 赢得 +🪙".number_format($net).' 金币';
|
||||
$this->broadcastPersonal($user->username, $content);
|
||||
} elseif ($resultType === 'curse') {
|
||||
// 诅咒:通知本人
|
||||
$content = "☠️ 三骷髅诅咒!{$e1}{$e2}{$e3} 额外扣除 🪙".number_format($cost).' 金币!';
|
||||
$this->broadcastPersonal($user->username, $content);
|
||||
}
|
||||
|
||||
$user->refresh();
|
||||
@@ -246,4 +261,28 @@ class SlotMachineController extends Controller
|
||||
broadcast(new MessageSent(1, $msg));
|
||||
SaveMessageJob::dispatch($msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向特定用户发送聊天室私人系统通知(仅该用户可见)。
|
||||
*
|
||||
* @param string $toUsername 接收用户名
|
||||
* @param string $content 消息内容
|
||||
*/
|
||||
private function broadcastPersonal(string $toUsername, string $content): void
|
||||
{
|
||||
$msg = [
|
||||
'id' => $this->chatState->nextMessageId(1),
|
||||
'room_id' => 1,
|
||||
'from_user' => '系统传音',
|
||||
'to_user' => $toUsername,
|
||||
'content' => $content,
|
||||
'is_secret' => true,
|
||||
'font_color' => '#f59e0b',
|
||||
'action' => '',
|
||||
'sent_at' => now()->toDateTimeString(),
|
||||
];
|
||||
|
||||
broadcast(new MessageSent(1, $msg));
|
||||
SaveMessageJob::dispatch($msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,16 +9,24 @@
|
||||
- 最近记录展示
|
||||
--}}
|
||||
|
||||
{{-- ─── 老虎机悬浮按钮 ─── --}}
|
||||
<div id="slot-fab" x-data="slotFab()" x-show="visible" x-cloak
|
||||
style="position:fixed; bottom:150px; right:18px; z-index:9900;">
|
||||
<button x-on:click="openPanel()"
|
||||
style="width:52px; height:52px; border-radius:50%; border:none; cursor:pointer;
|
||||
{{-- ─── 老虎机悬浮按钮(可拖动) ─── --}}
|
||||
<div id="slot-fab"
|
||||
x-data="slotFab()"
|
||||
x-show="visible"
|
||||
x-cloak
|
||||
:style="'position:fixed; right:' + posX + 'px; bottom:' + posY + 'px; z-index:9900; touch-action:none; user-select:none;'"
|
||||
@pointerdown.prevent="startDrag($event)"
|
||||
@pointermove.window="onDrag($event)"
|
||||
@pointerup.window="endDrag($event)"
|
||||
@pointercancel.window="endDrag($event)">
|
||||
<button
|
||||
style="width:52px; height:52px; border-radius:50%; border:none;
|
||||
background:linear-gradient(135deg,#d97706,#f59e0b);
|
||||
box-shadow:0 4px 20px rgba(245,158,11,.5);
|
||||
font-size:22px; display:flex; align-items:center; justify-content:center;
|
||||
animation:slot-pulse 2s infinite;"
|
||||
title="老虎机">🎰</button>
|
||||
:style="dragging ? 'cursor:grabbing;' : 'cursor:grab;'"
|
||||
title="老虎机(可拖动)">🎰</button>
|
||||
</div>
|
||||
|
||||
{{-- ─── 老虎机主面板 ─── --}}
|
||||
@@ -259,15 +267,51 @@
|
||||
* 老虎机悬浮按钮 Alpine 组件(检查游戏是否开启)
|
||||
*/
|
||||
function slotFab() {
|
||||
const STORAGE_KEY = 'slot_fab_pos';
|
||||
const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || 'null');
|
||||
return {
|
||||
visible: false,
|
||||
visible: false,
|
||||
posX: saved?.x ?? 18,
|
||||
posY: saved?.y ?? 150,
|
||||
dragging: false,
|
||||
_startX: 0, _startY: 0,
|
||||
_origX: 0, _origY: 0,
|
||||
_moved: false,
|
||||
|
||||
async init() {
|
||||
try {
|
||||
const res = await fetch('/slot/info');
|
||||
const res = await fetch('/slot/info');
|
||||
const data = await res.json();
|
||||
this.visible = data.enabled === true;
|
||||
} catch {}
|
||||
},
|
||||
|
||||
startDrag(e) {
|
||||
this.dragging = true;
|
||||
this._moved = false;
|
||||
this._startX = e.clientX;
|
||||
this._startY = e.clientY;
|
||||
this._origX = this.posX;
|
||||
this._origY = this.posY;
|
||||
e.currentTarget.setPointerCapture?.(e.pointerId);
|
||||
},
|
||||
|
||||
onDrag(e) {
|
||||
if (!this.dragging) return;
|
||||
const dx = e.clientX - this._startX;
|
||||
const dy = e.clientY - this._startY;
|
||||
if (Math.abs(dx) > 3 || Math.abs(dy) > 3) this._moved = true;
|
||||
this.posX = Math.max(4, Math.min(window.innerWidth - 60, this._origX - dx));
|
||||
this.posY = Math.max(4, Math.min(window.innerHeight - 60, this._origY + dy));
|
||||
},
|
||||
|
||||
endDrag(e) {
|
||||
if (!this.dragging) return;
|
||||
this.dragging = false;
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify({ x: this.posX, y: this.posY }));
|
||||
if (!this._moved) this.openPanel();
|
||||
},
|
||||
|
||||
openPanel() {
|
||||
const panel = document.getElementById('slot-panel');
|
||||
if (panel) Alpine.$data(panel).open();
|
||||
|
||||
Reference in New Issue
Block a user