功能更新与UI优化:游戏图标移除、用户名片修复、婚礼红包界面重设计
- 移除聊天室右下角浮动游戏图标(占卜、百家乐、赛马、老虎机) - 用户名片按钮区:修复已婚/已好友时按钮换行问题,统一单行显示 - 婚礼红包弹窗:重设计为喜庆鲜红背景,领取按钮改为圆形米黄样式 - 新增婚礼红包恢复接口(/wedding/pending-envelopes),刷新后自动恢复领取按钮 - 修复 Alpine :style 字符串覆盖静态 style 导致圆形按钮失效的问题 - 撤职后用户等级改为根据经验值重新计算,不再无条件重置为1 - 管理员修改用户经验值后自动重算等级,有职务用户等级锁定 - 娱乐大厅钓鱼游戏按钮直接调用 startFishing() 简化操作流程 - 新增赛马、占卜、百家乐游戏及相关后端逻辑
This commit is contained in:
@@ -143,6 +143,12 @@
|
||||
@include('chat.partials.slot-machine')
|
||||
{{-- ═══════════ 神秘箱子游戏面板 ═══════════ --}}
|
||||
@include('chat.partials.mystery-box')
|
||||
{{-- ═══════════ 赛马竞猜游戏面板 ═══════════ --}}
|
||||
@include('chat.partials.horse-race-panel')
|
||||
{{-- ═══════════ 神秘占卜游戏面板 ═══════════ --}}
|
||||
@include('chat.partials.fortune-panel')
|
||||
{{-- ═══════════ 娱乐游戏大厅弹窗 ═══════════ --}}
|
||||
@include('chat.partials.game-hall')
|
||||
|
||||
{{-- 全屏特效系统:管理员烟花/下雨/雷电/下雪 --}}
|
||||
<script src="/js/effects/effect-sounds.js"></script>
|
||||
|
||||
@@ -8,207 +8,203 @@
|
||||
- 展示近10局历史趋势
|
||||
--}}
|
||||
|
||||
{{-- ─── 百家乐主面板 ─── --}}
|
||||
{{-- 百家乐主面板 --}}
|
||||
<div id="baccarat-panel" x-data="baccaratPanel()" x-show="show" x-cloak>
|
||||
<div x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
|
||||
style="position:fixed; inset:0; background:rgba(0,0,0,.7); z-index:9940;
|
||||
style="position:fixed; inset:0; background:rgba(0,0,0,.55); z-index:9940;
|
||||
display:flex; align-items:center; justify-content:center;">
|
||||
|
||||
<div
|
||||
style="width:480px; max-width:96vw; border-radius:24px; overflow:hidden;
|
||||
box-shadow:0 24px 80px rgba(139,92,246,.5); font-family:system-ui,sans-serif;">
|
||||
style="width:460px; max-width:96vw; border-radius:8px; overflow:hidden;
|
||||
box-shadow:0 8px 32px rgba(0,0,0,.3); font-family:'Microsoft YaHei',SimSun,sans-serif; background:#fff;">
|
||||
|
||||
{{-- ─── 顶部标题 ─── --}}
|
||||
<div
|
||||
style="background:linear-gradient(135deg,#4c1d95,#6d28d9,#7c3aed); padding:18px 22px 14px; position:relative;">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between;">
|
||||
<div>
|
||||
<div style="color:#fff; font-weight:900; font-size:18px; letter-spacing:1px;">🎲 百家乐</div>
|
||||
<div style="color:rgba(255,255,255,.6); font-size:12px; margin-top:2px;">
|
||||
第 <span x-text="'#' + roundId"></span> 局
|
||||
</div>
|
||||
</div>
|
||||
{{-- 倒计时 --}}
|
||||
<div x-show="phase === 'betting'" style="text-align:center;">
|
||||
<div style="color:#fbbf24; font-size:32px; font-weight:900; line-height:1;" x-text="countdown">
|
||||
</div>
|
||||
<div style="color:rgba(255,255,255,.5); font-size:11px;">秒后截止</div>
|
||||
</div>
|
||||
{{-- 骰子结果 --}}
|
||||
<div x-show="phase === 'settled'" style="display:none; text-align:center;">
|
||||
<div style="font-size:28px;" x-text="diceEmoji"></div>
|
||||
<div style="color:#fbbf24; font-size:12px; font-weight:bold; margin-top:2px;"
|
||||
x-text="resultLabel"></div>
|
||||
style="background:linear-gradient(135deg,#336699,#5a8fc0); padding:10px 16px;
|
||||
display:flex; align-items:center; justify-content:space-between;">
|
||||
<div>
|
||||
<div style="color:#fff; font-weight:bold; font-size:14px;">🎲 百家乐</div>
|
||||
<div style="color:rgba(255,255,255,.75); font-size:11px; margin-top:1px;">
|
||||
第 <span x-text="'#' + roundId"></span> 局
|
||||
</div>
|
||||
</div>
|
||||
{{-- 倒计时 --}}
|
||||
<div x-show="phase === 'betting'" style="text-align:center;">
|
||||
<div style="color:#fbbf24; font-size:28px; font-weight:900; line-height:1;" x-text="countdown">
|
||||
</div>
|
||||
<div style="color:rgba(255,255,255,.7); font-size:11px;">秒后截止</div>
|
||||
</div>
|
||||
{{-- 骰子结果 --}}
|
||||
<div x-show="phase === 'settled'" style="display:none; text-align:center;">
|
||||
<div style="font-size:24px;" x-text="diceEmoji"></div>
|
||||
<div style="color:#fbbf24; font-size:12px; font-weight:bold; margin-top:2px;" x-text="resultLabel">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 进度条 --}}
|
||||
<div x-show="phase === 'betting'"
|
||||
style="margin-top:10px; height:4px; background:rgba(255,255,255,.15); border-radius:2px; overflow:hidden;">
|
||||
<div style="height:100%; background:#fbbf24; border-radius:2px; transition:width 1s linear;"
|
||||
:style="'width:' + Math.max(0, (countdown / totalSeconds * 100)) + '%'"></div>
|
||||
</div>
|
||||
{{-- 进度条 --}}
|
||||
<div x-show="phase === 'betting'" style="height:3px; background:rgba(255,255,255,.2);">
|
||||
<div style="height:100%; background:#fbbf24; transition:width 1s linear;"
|
||||
:style="'width:' + Math.max(0, (countdown / totalSeconds * 100)) + '%'"></div>
|
||||
</div>
|
||||
|
||||
{{-- ─── 历史趋势 ─── --}}
|
||||
<div
|
||||
style="background:#1e1b4b; padding:8px 16px; display:flex; gap:6px; align-items:center; flex-wrap:wrap;">
|
||||
<span style="color:rgba(255,255,255,.4); font-size:11px; margin-right:2px;">近期</span>
|
||||
style="background:#f0f6ff; padding:7px 14px; display:flex; gap:5px; align-items:center;
|
||||
flex-wrap:wrap; border-bottom:1px solid #d0e4f5;">
|
||||
<span style="color:#99b0cc; font-size:11px; margin-right:2px;">近期</span>
|
||||
<template x-for="h in history" :key="h.id">
|
||||
<span
|
||||
style="width:22px; height:22px; border-radius:50%; font-size:11px; font-weight:bold;
|
||||
display:flex; align-items:center; justify-content:center;"
|
||||
:style="h.result === 'big' ? 'background:#1d4ed8; color:#fff' :
|
||||
h.result === 'small' ? 'background:#b45309; color:#fff' :
|
||||
h.result === 'triple' ? 'background:#7c3aed; color:#fff' :
|
||||
'background:#374151; color:#9ca3af'"
|
||||
display:flex; align-items:center; justify-content:center; color:#fff;"
|
||||
:style="h.result === 'big' ? 'background:#1d4ed8' :
|
||||
h.result === 'small' ? 'background:#d97706' :
|
||||
h.result === 'triple' ? 'background:#7c3aed' :
|
||||
'background:#94a3b8'"
|
||||
:title="'#' + h.id + ' ' + (h.result === 'big' ? '大' : h.result === 'small' ? '小' : h
|
||||
.result === 'triple' ? '豹' : '☠')"
|
||||
x-text="h.result === 'big' ? '大' : h.result === 'small' ? '小' : h.result === 'triple' ? '豹' : '☠'">
|
||||
</span>
|
||||
</template>
|
||||
<span x-show="history.length === 0" style="color:rgba(255,255,255,.3); font-size:11px;">暂无记录</span>
|
||||
<span x-show="history.length === 0" style="color:#b0c4d8; font-size:11px;">暂无记录</span>
|
||||
</div>
|
||||
|
||||
{{-- ─── 主体内容 ─── --}}
|
||||
<div style="background:linear-gradient(180deg,#1e1b4b,#1a1035); padding:18px 20px;">
|
||||
{{-- ─── 主体内容(白底) ─── --}}
|
||||
<div style="background:#fff; padding:14px 16px;">
|
||||
|
||||
{{-- 押注阶段 --}}
|
||||
<div x-show="phase === 'betting'">
|
||||
{{-- 当前下注池统计 --}}
|
||||
<div style="display:grid; grid-template-columns:1fr 1fr 1fr; gap:8px; margin-bottom:14px;">
|
||||
<div style="background:rgba(29,78,216,.3); border-radius:10px; padding:8px; text-align:center;">
|
||||
<div style="color:#60a5fa; font-size:11px;">押大</div>
|
||||
<div style="color:#fff; font-weight:bold; font-size:13px;"
|
||||
|
||||
{{-- 下注池统计 --}}
|
||||
<div style="display:grid; grid-template-columns:1fr 1fr 1fr; gap:8px; margin-bottom:12px;">
|
||||
<div
|
||||
style="background:#eff6ff; border:1px solid #bfdbfe; border-radius:8px; padding:8px; text-align:center;">
|
||||
<div style="color:#1d4ed8; font-size:11px; font-weight:bold;">押大</div>
|
||||
<div style="color:#1e40af; font-weight:bold; font-size:13px;"
|
||||
x-text="Number(totalBetBig).toLocaleString()"></div>
|
||||
</div>
|
||||
<div style="background:rgba(180,83,9,.3); border-radius:10px; padding:8px; text-align:center;">
|
||||
<div style="color:#fbbf24; font-size:11px;">押小</div>
|
||||
<div style="color:#fff; font-weight:bold; font-size:13px;"
|
||||
<div
|
||||
style="background:#fffbeb; border:1px solid #fde68a; border-radius:8px; padding:8px; text-align:center;">
|
||||
<div style="color:#d97706; font-size:11px; font-weight:bold;">押小</div>
|
||||
<div style="color:#b45309; font-weight:bold; font-size:13px;"
|
||||
x-text="Number(totalBetSmall).toLocaleString()"></div>
|
||||
</div>
|
||||
<div
|
||||
style="background:rgba(124,58,237,.3); border-radius:10px; padding:8px; text-align:center;">
|
||||
<div style="color:#c4b5fd; font-size:11px;">押豹子</div>
|
||||
<div style="color:#fff; font-weight:bold; font-size:13px;"
|
||||
style="background:#f5f3ff; border:1px solid #ddd6fe; border-radius:8px; padding:8px; text-align:center;">
|
||||
<div style="color:#7c3aed; font-size:11px; font-weight:bold;">押豹子</div>
|
||||
<div style="color:#6d28d9; font-weight:bold; font-size:13px;"
|
||||
x-text="Number(totalBetTriple).toLocaleString()"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 已下注状态 / 下注表单 --}}
|
||||
{{-- 已下注状态 --}}
|
||||
<div x-show="myBet">
|
||||
<div
|
||||
style="background:rgba(34,197,94,.15); border:1px solid rgba(34,197,94,.3); border-radius:12px;
|
||||
padding:12px 16px; text-align:center; margin-bottom:12px;">
|
||||
<div style="color:#4ade80; font-weight:bold; font-size:14px;">
|
||||
style="background:#f0fdf4; border:1px solid #86efac; border-radius:10px;
|
||||
padding:10px 14px; text-align:center; margin-bottom:10px;">
|
||||
<div style="color:#16a34a; font-weight:bold; font-size:13px;">
|
||||
✅ 已押注「<span x-text="betTypeLabel(myBetType)"></span>」
|
||||
<span x-text="Number(myBetAmount).toLocaleString()"></span> 金币
|
||||
</div>
|
||||
<div style="color:rgba(255,255,255,.4); font-size:11px; margin-top:4px;">等待开奖中…</div>
|
||||
<div style="color:#86a896; font-size:11px; margin-top:3px;">等待开奖中…</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 下注表单 --}}
|
||||
<div x-show="!myBet">
|
||||
{{-- 押注选项 --}}
|
||||
<div style="display:grid; grid-template-columns:1fr 1fr 1fr; gap:8px; margin-bottom:12px;">
|
||||
{{-- 大 --}}
|
||||
<button x-on:click="selectedType='big'"
|
||||
style="border:none; border-radius:12px; padding:12px 0; cursor:pointer; transition:all .15s; font-weight:bold;"
|
||||
:style="selectedType === 'big' ?
|
||||
'background:#1d4ed8; color:#fff; transform:scale(1.05); box-shadow:0 4px 20px rgba(29,78,216,.5)' :
|
||||
'background:rgba(29,78,216,.2); color:#93c5fd;'">
|
||||
<div style="font-size:20px;">🔵</div>
|
||||
<div style="font-size:13px; margin-top:2px;">大</div>
|
||||
<div style="font-size:10px; opacity:.7;">11~17点 • 1:1</div>
|
||||
style="border-radius:10px; padding:12px 0; cursor:pointer; transition:all .15s; font-weight:bold; font-family:inherit;"
|
||||
:style="selectedType === 'big'
|
||||
?
|
||||
'border:2px solid #1d4ed8; background:#1d4ed8; color:#fff; transform:scale(1.05); box-shadow:0 4px 14px rgba(29,78,216,.3);' :
|
||||
'border:2px solid #bfdbfe; background:#eff6ff; color:#1d4ed8;'">
|
||||
<div style="font-size:22px;">🔵</div>
|
||||
<div style="font-size:13px; margin-top:4px;">大</div>
|
||||
<div style="font-size:10px; margin-top:2px; opacity:.75;">11~17点 • 1:1</div>
|
||||
</button>
|
||||
{{-- 小 --}}
|
||||
<button x-on:click="selectedType='small'"
|
||||
style="border:none; border-radius:12px; padding:12px 0; cursor:pointer; transition:all .15s; font-weight:bold;"
|
||||
:style="selectedType === 'small' ?
|
||||
'background:#b45309; color:#fff; transform:scale(1.05); box-shadow:0 4px 20px rgba(180,83,9,.5)' :
|
||||
'background:rgba(180,83,9,.2); color:#fcd34d;'">
|
||||
<div style="font-size:20px;">🟡</div>
|
||||
<div style="font-size:13px; margin-top:2px;">小</div>
|
||||
<div style="font-size:10px; opacity:.7;">4~10点 • 1:1</div>
|
||||
style="border-radius:10px; padding:12px 0; cursor:pointer; transition:all .15s; font-weight:bold; font-family:inherit;"
|
||||
:style="selectedType === 'small'
|
||||
?
|
||||
'border:2px solid #d97706; background:#d97706; color:#fff; transform:scale(1.05); box-shadow:0 4px 14px rgba(217,119,6,.3);' :
|
||||
'border:2px solid #fde68a; background:#fffbeb; color:#b45309;'">
|
||||
<div style="font-size:22px;">🟡</div>
|
||||
<div style="font-size:13px; margin-top:4px;">小</div>
|
||||
<div style="font-size:10px; margin-top:2px; opacity:.75;">4~10点 • 1:1</div>
|
||||
</button>
|
||||
{{-- 豹子 --}}
|
||||
<button x-on:click="selectedType='triple'"
|
||||
style="border:none; border-radius:12px; padding:12px 0; cursor:pointer; transition:all .15s; font-weight:bold;"
|
||||
:style="selectedType === 'triple' ?
|
||||
'background:#7c3aed; color:#fff; transform:scale(1.05); box-shadow:0 4px 20px rgba(124,58,237,.5)' :
|
||||
'background:rgba(124,58,237,.2); color:#c4b5fd;'">
|
||||
<div style="font-size:20px;">💥</div>
|
||||
<div style="font-size:13px; margin-top:2px;">豹子</div>
|
||||
<div style="font-size:10px; opacity:.7;">三同 • 1:24</div>
|
||||
style="border-radius:10px; padding:12px 0; cursor:pointer; transition:all .15s; font-weight:bold; font-family:inherit;"
|
||||
:style="selectedType === 'triple'
|
||||
?
|
||||
'border:2px solid #7c3aed; background:#7c3aed; color:#fff; transform:scale(1.05); box-shadow:0 4px 14px rgba(124,58,237,.3);' :
|
||||
'border:2px solid #ddd6fe; background:#f5f3ff; color:#7c3aed;'">
|
||||
<div style="font-size:22px;">💥</div>
|
||||
<div style="font-size:13px; margin-top:4px;">豹子</div>
|
||||
<div style="font-size:10px; margin-top:2px; opacity:.75;">三同 • 1:24</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- 快捷金额 + 自定义 --}}
|
||||
<div style="margin-bottom:10px;">
|
||||
<div style="display:flex; gap:6px; margin-bottom:8px; flex-wrap:wrap;">
|
||||
<template x-for="preset in [100, 500, 1000, 5000, 10000]" :key="preset">
|
||||
<button x-on:click="betAmount = preset"
|
||||
style="flex:1; min-width:50px; border:none; border-radius:8px; padding:6px 4px;
|
||||
font-size:12px; font-weight:bold; cursor:pointer; transition:all .1s;"
|
||||
:style="betAmount === preset ?
|
||||
'background:#fbbf24; color:#1a1035;' :
|
||||
'background:rgba(255,255,255,.1); color:rgba(255,255,255,.7);'"
|
||||
x-text="preset >= 1000 ? (preset/1000)+'k' : preset">
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
<input type="number" x-model.number="betAmount" min="100" placeholder="自定义金额"
|
||||
style="width:100%; background:rgba(255,255,255,.1); border:1px solid rgba(255,255,255,.15);
|
||||
border-radius:8px; padding:8px 12px; color:#fff; font-size:13px; box-sizing:border-box;"
|
||||
x-on:focus="$event.target.select()">
|
||||
{{-- 快捷金额 --}}
|
||||
<div style="display:grid; grid-template-columns:repeat(5,1fr); gap:6px; margin-bottom:10px;">
|
||||
<template x-for="preset in [100, 500, 1000, 5000, 10000]" :key="preset">
|
||||
<button x-on:click="betAmount = preset"
|
||||
style="border-radius:20px; padding:8px 2px; font-size:13px; font-weight:bold;
|
||||
cursor:pointer; transition:all .15s; font-family:inherit; text-align:center;"
|
||||
:style="betAmount === preset ?
|
||||
'background:#336699; color:#fff; border:none; box-shadow:0 3px 10px rgba(51,102,153,.35); transform:translateY(-1px);' :
|
||||
'background:#fff; color:#336699; border:1.5px solid #c0d8ef;'"
|
||||
x-text="preset >= 1000 ? (preset/1000)+'k' : preset">
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
{{-- 自定义金额 --}}
|
||||
<input type="number" x-model.number="betAmount" min="100" placeholder="自定义金额"
|
||||
style="width:100%; background:#f6faff; border:1.5px solid #d0e4f5;
|
||||
border-radius:8px; padding:8px 12px; color:#225588; font-size:13px;
|
||||
box-sizing:border-box; margin-bottom:10px;"
|
||||
x-on:focus="$event.target.select()">
|
||||
|
||||
{{-- 下注按钮 --}}
|
||||
<button x-on:click="submitBet()" :disabled="!selectedType || betAmount < 100 || submitting"
|
||||
style="width:100%; border:none; border-radius:12px; padding:14px;
|
||||
font-size:15px; font-weight:bold; cursor:pointer; transition:all .2s;
|
||||
letter-spacing:1px;"
|
||||
:style="(!selectedType || betAmount < 100 || submitting) ? {
|
||||
background: '#f1f5f9',
|
||||
color: '#94a3b8',
|
||||
cursor: 'not-allowed',
|
||||
boxShadow: 'none'
|
||||
} : {
|
||||
background: 'linear-gradient(135deg,#6d28d9,#7c3aed,#8b5cf6)',
|
||||
color: '#fff',
|
||||
cursor: 'pointer',
|
||||
boxShadow: '0 4px 16px rgba(124,58,237,0.5)'
|
||||
}">
|
||||
:style="(!selectedType || betAmount < 100 || submitting) ?
|
||||
'display:block; width:100%; border:none; border-radius:12px; padding:13px 0; font-size:14px; font-weight:bold; cursor:not-allowed; transition:all .2s; background:#e0e8f0; color:#99a8b8; box-shadow:none; font-family:inherit;' :
|
||||
'display:block; width:100%; border:none; border-radius:12px; padding:13px 0; font-size:14px; font-weight:bold; cursor:pointer; transition:all .2s; background:linear-gradient(135deg,#336699,#5a8fc0); color:#fff; box-shadow:0 4px 14px rgba(51,102,153,.3); font-family:inherit;'">
|
||||
<span
|
||||
x-text="submitting ? '提交中…' : (!selectedType ? '请先选择大/小/豹子' : '🎲 押注「' + betTypeLabel(selectedType) + '」 ' + Number(betAmount).toLocaleString() + ' 金币')"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- 规则提示 --}}
|
||||
<div style="margin-top:10px; color:rgba(255,255,255,.3); font-size:10px; text-align:center;">
|
||||
<div style="margin-top:10px; color:#99b0cc; font-size:10px; text-align:center;">
|
||||
☠️ 3点或18点为庄家收割,全灭无退款。豹子优先于大小判断。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 等待开奖阶段 --}}
|
||||
<div x-show="phase === 'waiting'" style="display:none; text-align:center; padding:16px 0;">
|
||||
<div style="font-size:40px; animation:spin 1s linear infinite; display:inline-block;">🎲</div>
|
||||
<div style="color:rgba(255,255,255,.6); margin-top:8px;">正在摇骰子…</div>
|
||||
<div x-show="phase === 'waiting'" style="display:none; text-align:center; padding:20px 0;">
|
||||
<div style="font-size:44px; animation:spin 1s linear infinite; display:inline-block;">🎲</div>
|
||||
<div style="color:#5a8fc0; margin-top:10px; font-weight:bold;">正在摇骰子…</div>
|
||||
</div>
|
||||
|
||||
{{-- 结算阶段 --}}
|
||||
<div x-show="phase === 'settled'" style="display:none;">
|
||||
|
||||
{{-- 骰子展示(数字方块,跨平台兼容) --}}
|
||||
<div style="display:flex; justify-content:center; gap:10px; margin-bottom:14px;">
|
||||
{{-- 骰子展示 --}}
|
||||
<div style="display:flex; justify-content:center; gap:10px; margin-bottom:16px;">
|
||||
<template x-for="(d, i) in settledDice" :key="i">
|
||||
<div style="width:54px; height:54px; border-radius:12px; font-weight:900;
|
||||
<div style="width:56px; height:56px; border-radius:12px; font-weight:900;
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
font-size:26px; box-shadow:0 6px 20px rgba(0,0,0,.5);
|
||||
animation:dice-pop .4s ease-out both; color:#1e1b4b;
|
||||
background:linear-gradient(145deg,#fff,#e0e7ff);"
|
||||
font-size:26px; animation:dice-pop .4s ease-out both;
|
||||
color:#1e3a5f; background:linear-gradient(145deg,#ffffff,#e8f0fb);
|
||||
border:1.5px solid #d0e4f5; box-shadow:0 4px 14px rgba(51,102,153,.12);"
|
||||
:style="'animation-delay:' + (i * 0.18) + 's'" x-text="d">
|
||||
</div>
|
||||
</template>
|
||||
@@ -216,13 +212,13 @@
|
||||
|
||||
{{-- 结果标签 --}}
|
||||
<div style="text-align:center; margin-bottom:14px;">
|
||||
<div style="font-size:26px; font-weight:900; letter-spacing:2px;"
|
||||
:style="settledResult === 'big' ? 'color:#60a5fa' :
|
||||
settledResult === 'small' ? 'color:#fbbf24' :
|
||||
settledResult === 'triple' ? 'color:#c4b5fd' :
|
||||
settledResult === 'kill' ? 'color:#f87171' : 'color:#fbbf24'"
|
||||
<div style="font-size:24px; font-weight:900; letter-spacing:2px;"
|
||||
:style="settledResult === 'big' ? 'color:#1d4ed8' :
|
||||
settledResult === 'small' ? 'color:#d97706' :
|
||||
settledResult === 'triple' ? 'color:#7c3aed' :
|
||||
settledResult === 'kill' ? 'color:#dc2626' : 'color:#336699'"
|
||||
x-text="resultLabel"></div>
|
||||
<div style="color:rgba(255,255,255,.35); font-size:12px; margin-top:4px;"
|
||||
<div style="color:#99b0cc; font-size:12px; margin-top:4px;"
|
||||
x-text="'骰子总点数:' + settledTotal + ' 点'"></div>
|
||||
</div>
|
||||
|
||||
@@ -230,15 +226,14 @@
|
||||
<div x-show="myBet">
|
||||
{{-- 中奖 --}}
|
||||
<div x-show="myWon"
|
||||
style="border-radius:14px; overflow:hidden; margin-bottom:4px;
|
||||
background:linear-gradient(135deg,rgba(16,185,129,.25),rgba(5,150,105,.15));
|
||||
border:1px solid rgba(52,211,153,.4);">
|
||||
style="border-radius:12px; overflow:hidden; margin-bottom:4px;
|
||||
background:#f0fdf4; border:1px solid #86efac;">
|
||||
<div style="padding:14px 16px; text-align:center;">
|
||||
<div style="font-size:32px; margin-bottom:4px;">🎉</div>
|
||||
<div style="color:#34d399; font-size:18px; font-weight:900;">恭喜中奖!</div>
|
||||
<div style="color:#6ee7b7; font-size:24px; font-weight:bold; margin:6px 0;"
|
||||
<div style="color:#16a34a; font-size:18px; font-weight:900;">恭喜中奖!</div>
|
||||
<div style="color:#15803d; font-size:24px; font-weight:bold; margin:6px 0;"
|
||||
x-text="'+' + Number(myPayout).toLocaleString() + ' 🪙'"></div>
|
||||
<div style="color:rgba(255,255,255,.4); font-size:11px;"
|
||||
<div style="color:#86a896; font-size:11px;"
|
||||
x-text="'押「' + betTypeLabel(myBetType) + '」' + Number(myBetAmount).toLocaleString() + ' 金币 → 赢得 ' + Number(myPayout).toLocaleString() + ' 金币'">
|
||||
</div>
|
||||
</div>
|
||||
@@ -246,48 +241,46 @@
|
||||
|
||||
{{-- 未中奖 --}}
|
||||
<div x-show="!myWon"
|
||||
style="border-radius:14px; overflow:hidden; margin-bottom:4px;
|
||||
background:linear-gradient(135deg,rgba(239,68,68,.15),rgba(185,28,28,.1));
|
||||
border:1px solid rgba(248,113,113,.25);">
|
||||
style="border-radius:12px; overflow:hidden; margin-bottom:4px;
|
||||
background:#fff5f5; border:1px solid #fecaca;">
|
||||
<div style="padding:14px 16px; text-align:center;">
|
||||
<div style="font-size:28px; margin-bottom:4px;">😔</div>
|
||||
<div style="color:#f87171; font-size:16px; font-weight:bold; margin-bottom:6px;">本局未中奖
|
||||
<div style="color:#dc2626; font-size:16px; font-weight:bold; margin-bottom:8px;">本局未中奖
|
||||
</div>
|
||||
<div
|
||||
style="display:inline-flex; align-items:center; gap:8px;
|
||||
background:rgba(0,0,0,.2); border-radius:20px; padding:5px 14px;">
|
||||
<span style="color:rgba(255,255,255,.5); font-size:12px;">你押了</span>
|
||||
<span style="font-weight:bold; font-size:13px; color:#fbbf24;"
|
||||
background:#fef2f2; border-radius:20px; padding:5px 14px; border:1px solid #fecaca;">
|
||||
<span style="color:#64748b; font-size:12px;">你押了</span>
|
||||
<span style="font-weight:bold; font-size:13px; color:#d97706;"
|
||||
x-text="betTypeLabel(myBetType)"></span>
|
||||
<span style="color:rgba(255,255,255,.3); font-size:11px;">·</span>
|
||||
<span style="color:rgba(255,255,255,.5); font-size:12px;">开了</span>
|
||||
<span style="color:#d0d5db; font-size:11px;">·</span>
|
||||
<span style="color:#64748b; font-size:12px;">开了</span>
|
||||
<span style="font-weight:bold; font-size:13px;"
|
||||
:style="settledResult === 'big' ? 'color:#60a5fa' :
|
||||
settledResult === 'small' ? 'color:#fbbf24' :
|
||||
settledResult === 'triple' ? 'color:#c4b5fd' : 'color:#f87171'"
|
||||
:style="settledResult === 'big' ? 'color:#1d4ed8' :
|
||||
settledResult === 'small' ? 'color:#d97706' :
|
||||
settledResult === 'triple' ? 'color:#7c3aed' : 'color:#dc2626'"
|
||||
x-text="resultLabel"></span>
|
||||
</div>
|
||||
<div style="color:rgba(255,255,255,.3); font-size:11px; margin-top:8px;"
|
||||
<div style="color:#94a3b8; font-size:11px; margin-top:8px;"
|
||||
x-text="'损失 ' + Number(myBetAmount).toLocaleString() + ' 金币'"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 未下注但看结果 --}}
|
||||
<div x-show="!myBet"
|
||||
style="text-align:center; color:rgba(255,255,255,.3); font-size:12px; padding:8px 0;">
|
||||
<div x-show="!myBet" style="text-align:center; color:#99b0cc; font-size:12px; padding:8px 0;">
|
||||
本局未参与下注
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ─── 底部关闭 ─── --}}
|
||||
<div style="background:rgba(15,10,40,.95); padding:10px 20px; display:flex; justify-content:center;">
|
||||
{{-- 底部关闭 --}}
|
||||
<div
|
||||
style="background:#fff; border-top:1px solid #d0e4f5; padding:12px 16px; display:flex; justify-content:center;">
|
||||
<button x-on:click="close()"
|
||||
style="padding:7px 28px; background:rgba(255,255,255,.08); border:none; border-radius:20px;
|
||||
font-size:12px; color:rgba(255,255,255,.5); cursor:pointer; transition:all .15s;"
|
||||
onmouseover="this.style.background='rgba(255,255,255,.15)'"
|
||||
onmouseout="this.style.background='rgba(255,255,255,.08)'">
|
||||
style="padding:10px 48px; min-width:140px; background:#f0f6ff; border:1px solid #b0d0ee; border-radius:12px;
|
||||
font-size:14px; font-weight:bold; color:#336699; cursor:pointer; transition:all .15s; font-family:inherit;"
|
||||
onmouseover="this.style.background='#ddeeff'" onmouseout="this.style.background='#f0f6ff'">
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
@@ -295,19 +288,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ─── 骨骰悬浮入口(游戏开启时常驻,支持拖拽) ─── --}}
|
||||
<div id="baccarat-fab" x-data="baccaratFab()" 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,#7c3aed,#4f46e5);
|
||||
box-shadow:0 4px 20px rgba(124,58,237,.5);
|
||||
font-size:22px; display:flex; align-items:center; justify-content:center;
|
||||
animation:pulse-fab 2s infinite; user-select:none;"
|
||||
:style="dragging ? 'cursor:grabbing;' : 'cursor:grab;'" title="百家乐下注中(可拖动)">🎲</button>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
/**
|
||||
@@ -396,11 +377,11 @@
|
||||
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 4px 20px rgba(124, 58, 237, .5);
|
||||
box-shadow: 0 4px 20px rgba(51, 102, 153, .4);
|
||||
}
|
||||
|
||||
50% {
|
||||
box-shadow: 0 4px 30px rgba(124, 58, 237, .9);
|
||||
box-shadow: 0 4px 30px rgba(51, 102, 153, .8);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -528,7 +509,7 @@
|
||||
this.myBet = true;
|
||||
this.myBetType = data.bet_type;
|
||||
this.myBetAmount = data.amount;
|
||||
window.chatDialog?.alert(data.message, '下注成功', '#7c3aed');
|
||||
window.chatDialog?.alert(data.message, '下注成功', '#336699');
|
||||
} else {
|
||||
window.chatDialog?.alert(data.message || '下注失败', '提示', '#ef4444');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
{{--
|
||||
文件功能:神秘占卜前台弹窗组件
|
||||
|
||||
聊天室内神秘占卜面板:
|
||||
- 点击悬浮 FAB 打开面板
|
||||
- 展示今日签文(免费次数 / 付费次数)
|
||||
- 卦象摇动动画 + 签文翻转展示
|
||||
- 展示近20条历史记录
|
||||
--}}
|
||||
|
||||
{{-- ─── 神秘占卜主面板 ─── --}}
|
||||
<div id="fortune-panel" x-data="fortunePanel()" x-show="show" x-cloak>
|
||||
<div x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
|
||||
style="position:fixed; inset:0; background:rgba(0,0,0,.55); z-index:9942;
|
||||
display:flex; align-items:center; justify-content:center;">
|
||||
|
||||
<div
|
||||
style="width:420px; max-width:96vw; border-radius:8px; overflow:hidden;
|
||||
box-shadow:0 8px 32px rgba(0,0,0,.3); font-family:'Microsoft YaHei',SimSun,sans-serif; background:#fff;">
|
||||
|
||||
{{-- ─── 顶部标题 ─── --}}
|
||||
<div
|
||||
style="background:linear-gradient(135deg,#336699,#5a8fc0); padding:10px 16px; display:flex; align-items:center; gap:10px;">
|
||||
<div style="font-size:14px; font-weight:bold; color:#fff; flex:1;">🔮 神秘占卜</div>
|
||||
<div style="font-size:11px; color:rgba(255,255,255,.75);">
|
||||
今日免费 <span x-text="freeCount"></span> 次,已用 <span x-text="freeUsed"></span> 次
|
||||
</div>
|
||||
<span onclick="Alpine.$data(document.getElementById('fortune-panel')).show = false"
|
||||
style="cursor:pointer; font-size:18px; color:rgba(255,255,255,.8); line-height:1; transition:opacity .15s;"
|
||||
onmouseover="this.style.opacity=1" onmouseout="this.style.opacity=.8">×</span>
|
||||
</div>
|
||||
|
||||
{{-- ─── 主体内容 ─── --}}
|
||||
<div style="background:#fff; padding:14px 16px;">
|
||||
|
||||
{{-- Tab 切换 --}}
|
||||
<div style="display:flex; gap:6px; margin-bottom:16px;">
|
||||
<button @click="activeTab = 'tell'"
|
||||
:style="activeTab === 'tell'
|
||||
?
|
||||
'flex:1; border:none; border-radius:6px; padding:8px 0; font-size:13px; font-weight:bold; cursor:pointer; transition:all .2s; background:#336699; color:#fff; box-shadow:0 2px 6px rgba(51,102,153,.25);' :
|
||||
'flex:1; border:1px solid #d0e4f5; border-radius:6px; padding:8px 0; font-size:13px; font-weight:bold; cursor:pointer; transition:all .2s; background:#f6faff; color:#5a8fc0;'">
|
||||
🔮 今日占卜
|
||||
</button>
|
||||
<button @click="activeTab = 'history'; loadHistory()"
|
||||
:style="activeTab === 'history'
|
||||
?
|
||||
'flex:1; border:none; border-radius:6px; padding:8px 0; font-size:13px; font-weight:bold; cursor:pointer; transition:all .2s; background:#336699; color:#fff; box-shadow:0 2px 6px rgba(51,102,153,.25);' :
|
||||
'flex:1; border:1px solid #d0e4f5; border-radius:6px; padding:8px 0; font-size:13px; font-weight:bold; cursor:pointer; transition:all .2s; background:#f6faff; color:#5a8fc0;'">
|
||||
📜 历史记录
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- ── 占卜 Tab ── --}}
|
||||
<div x-show="activeTab === 'tell'">
|
||||
|
||||
{{-- 占卜动画区 --}}
|
||||
<div style="text-align:center; padding:10px 0 16px;">
|
||||
{{-- 未占卜:摇卦动画 --}}
|
||||
<div x-show="!resultGrade">
|
||||
<div style="font-size:72px; display:inline-block;"
|
||||
:style="shaking ? 'animation:fortune-shake .5s ease-in-out;' :
|
||||
'animation:float-orb 3s ease-in-out infinite;'">
|
||||
🔮
|
||||
</div>
|
||||
<div style="color:#888; font-size:12px; margin-top:8px;">
|
||||
<span x-show="hasFreeLeft">点击下方按钮,开启今日占卜</span>
|
||||
<span x-show="!hasFreeLeft">免费次数已用完,可付费继续占卜</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 已占卜:展示签文 --}}
|
||||
<div x-show="resultGrade" x-transition:enter="transition ease-out duration-500"
|
||||
x-transition:enter-start="opacity-0 scale-75"
|
||||
x-transition:enter-end="opacity-100 scale-100">
|
||||
|
||||
{{-- 签文等级徽章 --}}
|
||||
<div style="display:inline-block; padding:4px 16px; border-radius:20px; font-weight:900;
|
||||
font-size:14px; letter-spacing:2px; margin-bottom:12px; color:#fff; text-shadow:0 1px 2px rgba(0,0,0,.3);"
|
||||
:style="'background:' + resultColor + '; border:1px solid rgba(0,0,0,.1);'"
|
||||
x-text="resultLabel"></div>
|
||||
|
||||
{{-- 签文卡片(白底+左侧彩色装饰条) --}}
|
||||
<div style="border-radius:6px; padding:12px 16px; margin:0 4px;
|
||||
border:1px solid #d0e4f5; background:#f6faff; position:relative;"
|
||||
:style="'border-left:4px solid ' + resultColor + ';'">
|
||||
<div style="color:#225588; font-size:13px; line-height:1.8; text-align:center; font-style:italic;"
|
||||
x-text="'「' + resultText + '」'"></div>
|
||||
</div>
|
||||
|
||||
{{-- 当日加成 --}}
|
||||
<div x-show="resultBuff"
|
||||
style="margin:10px 8px 0; padding:8px 14px; border-radius:10px;
|
||||
background:#f0f6ff; color:#336699; font-size:12px; text-align:left; border:1px solid #d0e4f5;"
|
||||
x-text="resultBuff"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 已有今日签文(只展示最新的,可再次占卜) --}}
|
||||
<div x-show="todayLatest && !resultGrade"
|
||||
style="border-radius:6px; padding:10px 12px; background:#f6faff;
|
||||
border:1px solid #d0e4f5; margin-bottom:10px;">
|
||||
<div style="color:#336699; font-size:11px; margin-bottom:6px; font-weight:bold;">今日最新签文</div>
|
||||
<div style="display:flex; align-items:center; gap:6px;">
|
||||
{{-- 签级标签:改为左彩色竖条 + 圆角徽章文字,不用纯色背景 --}}
|
||||
<span
|
||||
style="padding:2px 10px; border-radius:20px; font-size:11px; font-weight:bold; border:1.5px solid;"
|
||||
:style="todayLatest
|
||||
?
|
||||
'color:' + todayLatest.grade_color + '; border-color:' + todayLatest.grade_color +
|
||||
'; background: transparent;' :
|
||||
'display:none'"
|
||||
x-text="todayLatest?.grade_label"></span>
|
||||
<span style="color:#225588; font-size:12px; flex:1;" x-text="todayLatest?.text"></span>
|
||||
</div>
|
||||
<div x-show="todayLatest?.buff_desc" style="color:#888; font-size:11px; margin-top:3px;"
|
||||
x-text="todayLatest?.buff_desc"></div>
|
||||
</div>
|
||||
|
||||
{{-- 占卜按钮 --}}
|
||||
<div x-show="!resultGrade">
|
||||
<button @click="doFortune()" :disabled="loading"
|
||||
:style="loading
|
||||
?
|
||||
'display:block; width:100%; border:none; border-radius:12px; padding:12px 0; font-size:14px; font-weight:bold; cursor:not-allowed; transition:all .2s; background:#e0e8f0; color:#99a8b8; box-shadow:none;' :
|
||||
hasFreeLeft ?
|
||||
'display:block; width:100%; border:none; border-radius:12px; padding:12px 0; font-size:14px; font-weight:bold; cursor:pointer; transition:all .2s; background:linear-gradient(135deg,#336699,#5a8fc0); color:#fff; box-shadow:0 4px 12px rgba(51,102,153,.25);' :
|
||||
'display:block; width:100%; border:none; border-radius:12px; padding:13px 0; font-size:15px; font-weight:bold; cursor:pointer; transition:all .2s; background:linear-gradient(135deg,#1e4d8c,#336699); color:#fff; box-shadow:0 4px 14px rgba(30,77,140,.3); letter-spacing:0.5px;'">
|
||||
<span
|
||||
x-text="loading ? '占卜中…' : (hasFreeLeft ? '🔮 免费占卜' : '🔮 付费占卜(' + extraCost + ' 金币)')"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- 再占一卦按钮 --}}
|
||||
<div x-show="resultGrade" style="display:flex; gap:10px;">
|
||||
<button @click="resultGrade = ''; resultText = ''; resultBuff = null"
|
||||
style="flex:1; border:1px solid #b0d0ee; border-radius:12px; padding:12px 0;
|
||||
background:#f0f6ff; color:#336699;
|
||||
font-size:14px; font-weight:bold; cursor:pointer; transition:all .15s;"
|
||||
onmouseover="this.style.background='#ddeeff'" onmouseout="this.style.background='#f0f6ff'">
|
||||
↩ 返回
|
||||
</button>
|
||||
<button @click="resultGrade = ''; resultText = ''; resultBuff = null; doFortune()"
|
||||
:disabled="loading"
|
||||
style="flex:2; border:none; border-radius:12px; padding:12px 0;
|
||||
background:linear-gradient(135deg,#336699,#5a8fc0); color:#fff; box-shadow:0 4px 12px rgba(51,102,153,.2);
|
||||
font-size:14px; font-weight:bold; cursor:pointer; transition:all .15s;"
|
||||
x-text="hasFreeLeft ? '🔮 再占一卦(免费)' : '🔮 再占一卦(' + extraCost + ' 金币)'"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── 历史 Tab ── --}}
|
||||
<div x-show="activeTab === 'history'" style="display:none;">
|
||||
<div style="max-height:280px; overflow-y:auto; display:flex; flex-direction:column; gap:6px;">
|
||||
<template x-for="(log, i) in historyLogs" :key="i">
|
||||
<div
|
||||
style="border-radius:6px; padding:8px 10px; background:#f6faff;
|
||||
border:1px solid #d0e4f5;">
|
||||
<div style="display:flex; align-items:center; gap:8px; margin-bottom:4px;">
|
||||
<span
|
||||
style="padding:1px 8px; border-radius:20px; font-size:10px; font-weight:bold; color:#fff;"
|
||||
:style="'background:' + log.grade_color + ';'" x-text="log.grade_label"></span>
|
||||
<span style="color:#aaa; font-size:10px;" x-text="log.date + ' ' + log.time"></span>
|
||||
<span x-show="log.cost > 0" style="color:#b45309; font-size:10px; margin-left:auto;"
|
||||
x-text="'花费 ' + log.cost + '金'"></span>
|
||||
</div>
|
||||
<div style="color:#225588; font-size:12px; line-height:1.6;" x-text="log.text"></div>
|
||||
<div x-show="log.buff_desc" style="color:#888; font-size:11px; margin-top:3px;"
|
||||
x-text="log.buff_desc"></div>
|
||||
</div>
|
||||
</template>
|
||||
<div x-show="historyLogs.length === 0"
|
||||
style="text-align:center; color:#aaa; font-size:12px; padding:20px 0;">
|
||||
尚无占卜记录
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ─── 底部关闭 ─── --}}
|
||||
<div
|
||||
style="background:#fff; border-top:1px solid #d0e4f5; padding:14px 16px; display:flex; justify-content:center;">
|
||||
<button @click="show = false"
|
||||
style="padding:10px 48px; min-width:140px; background:#f0f6ff; border:1px solid #b0d0ee; border-radius:12px;
|
||||
font-size:14px; font-weight:bold; color:#336699; cursor:pointer; transition:all .15s;"
|
||||
onmouseover="this.style.background='#ddeeff'" onmouseout="this.style.background='#f0f6ff'">
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
@keyframes float-orb {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0) scale(1);
|
||||
filter: drop-shadow(0 0 12px rgba(168, 85, 247, .6));
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(-8px) scale(1.05);
|
||||
filter: drop-shadow(0 0 24px rgba(168, 85, 247, .9));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fortune-shake {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(0deg) scale(1.1);
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: rotate(-15deg) scale(1.15);
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: rotate(12deg) scale(1.15);
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: rotate(-8deg) scale(1.12);
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: rotate(5deg) scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-fortune {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 4px 20px rgba(168, 85, 247, .5);
|
||||
}
|
||||
|
||||
50% {
|
||||
box-shadow: 0 4px 30px rgba(168, 85, 247, .9);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 神秘占卜主面板 Alpine 组件
|
||||
*/
|
||||
function fortunePanel() {
|
||||
return {
|
||||
show: false,
|
||||
activeTab: 'tell',
|
||||
loading: false,
|
||||
shaking: false,
|
||||
|
||||
// 游戏配置
|
||||
freeCount: 1,
|
||||
freeUsed: 0,
|
||||
hasFreeLeft: true,
|
||||
extraCost: 500,
|
||||
|
||||
// 今日最新签文(若已占卜过)
|
||||
todayLatest: null,
|
||||
|
||||
// 本次占卜结果
|
||||
resultGrade: '',
|
||||
resultLabel: '',
|
||||
resultColor: '#a855f7',
|
||||
resultText: '',
|
||||
resultBuff: null,
|
||||
|
||||
// 历史记录
|
||||
historyLogs: [],
|
||||
|
||||
/**
|
||||
* 加载今日占卜状态
|
||||
*/
|
||||
async loadTodayStatus() {
|
||||
try {
|
||||
const res = await fetch('/fortune/today');
|
||||
const data = await res.json();
|
||||
if (!data.enabled) return;
|
||||
|
||||
this.freeCount = data.free_count || 1;
|
||||
this.freeUsed = data.free_used || 0;
|
||||
this.hasFreeLeft = data.has_free_left ?? true;
|
||||
this.extraCost = data.extra_cost || 500;
|
||||
this.todayLatest = data.latest || null;
|
||||
} catch {}
|
||||
},
|
||||
|
||||
/**
|
||||
* 执行占卜
|
||||
*/
|
||||
async doFortune() {
|
||||
if (this.loading) return;
|
||||
this.loading = true;
|
||||
this.shaking = true;
|
||||
|
||||
// 摇卦动画
|
||||
await new Promise(r => setTimeout(r, 600));
|
||||
this.shaking = false;
|
||||
|
||||
try {
|
||||
const res = await fetch('/fortune/tell', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]')?.content,
|
||||
},
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (data.ok) {
|
||||
this.resultGrade = data.grade;
|
||||
this.resultLabel = data.grade_label;
|
||||
this.resultColor = data.grade_color;
|
||||
this.resultText = data.text;
|
||||
this.resultBuff = data.buff_desc;
|
||||
|
||||
// 更新今日状态
|
||||
if (data.is_free) {
|
||||
this.freeUsed++;
|
||||
this.hasFreeLeft = this.freeUsed < this.freeCount;
|
||||
}
|
||||
} else {
|
||||
window.chatDialog?.alert(data.message || '占卜失败', '提示', '#ef4444');
|
||||
}
|
||||
} catch {
|
||||
window.chatDialog?.alert('网络异常,请稍后重试。', '错误', '#ef4444');
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载历史记录
|
||||
*/
|
||||
async loadHistory() {
|
||||
if (this.historyLogs.length > 0) return; // 已加载过则不重复请求
|
||||
try {
|
||||
const res = await fetch('/fortune/history');
|
||||
const data = await res.json();
|
||||
this.historyLogs = data.history || [];
|
||||
} catch {}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** 页面加载时:检查游戏是否开启,若开启则初始化面板数据 */
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
try {
|
||||
const res = await fetch('/fortune/today');
|
||||
const data = await res.json();
|
||||
if (data.enabled) {
|
||||
|
||||
const panel = document.getElementById('fortune-panel');
|
||||
if (panel) {
|
||||
const pd = Alpine.$data(panel);
|
||||
pd.freeCount = data.free_count || 1;
|
||||
pd.freeUsed = data.free_used || 0;
|
||||
pd.hasFreeLeft = data.has_free_left ?? true;
|
||||
pd.extraCost = data.extra_cost || 500;
|
||||
pd.todayLatest = data.latest || null;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[神秘占卜] 初始化失败', e);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,396 @@
|
||||
{{--
|
||||
文件功能:娱乐游戏大厅弹窗组件
|
||||
|
||||
点击工具栏「娱乐」按钮后弹出,展示所有已开启的游戏:
|
||||
- 百家乐:当前场次状态 + 倒计时 + 直接参与按钮
|
||||
- 老虎机:今日限额余量 + 直接打开按钮
|
||||
- 神秘箱子:已投放数量 + 直接打开按钮
|
||||
- 赛马竞猜:当前场次状态 + 参与按钮
|
||||
- 神秘占卜:今日占卜次数 + 直接打开按钮
|
||||
- 钓鱼:状态 + 打开按钮
|
||||
|
||||
@author ChatRoom Laravel
|
||||
@version 1.0.0
|
||||
--}}
|
||||
|
||||
{{-- ─── 服务端注入各游戏开关状态(避免前端额外请求)─── --}}
|
||||
@php
|
||||
$gameEnabled = [
|
||||
'baccarat' => \App\Models\GameConfig::isEnabled('baccarat'),
|
||||
'slot_machine' => \App\Models\GameConfig::isEnabled('slot_machine'),
|
||||
'mystery_box' => \App\Models\GameConfig::isEnabled('mystery_box'),
|
||||
'horse_racing' => \App\Models\GameConfig::isEnabled('horse_racing'),
|
||||
'fortune_telling' => \App\Models\GameConfig::isEnabled('fortune_telling'),
|
||||
'fishing' => \App\Models\GameConfig::isEnabled('fishing'),
|
||||
];
|
||||
@endphp
|
||||
<script>
|
||||
/** 后台游戏开关状态(Blade 服务端注入,1分钟缓存) */
|
||||
window.GAME_ENABLED = @json($gameEnabled);
|
||||
</script>
|
||||
|
||||
{{-- ═══════════ 游戏大厅弹窗遮罩 ═══════════ --}}
|
||||
<div id="game-hall-modal"
|
||||
style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.55);
|
||||
z-index:9998; justify-content:center; align-items:center;">
|
||||
<div id="game-hall-inner"
|
||||
style="width:680px; max-width:96vw; max-height:88vh; border-radius:8px; overflow:hidden;
|
||||
box-shadow:0 8px 32px rgba(0,0,0,.3); display:flex; flex-direction:column;
|
||||
background:#fff; font-family:'Microsoft YaHei',SimSun,sans-serif;">
|
||||
|
||||
{{-- ─── 标题栏(与商店弹窗同风格)─── --}}
|
||||
<div
|
||||
style="background:linear-gradient(135deg,#336699,#5a8fc0); color:#fff;
|
||||
padding:10px 16px; display:flex; align-items:center; gap:10px; flex-shrink:0;">
|
||||
<div style="font-size:14px; font-weight:bold; flex:1;">🎮 娱乐大厅</div>
|
||||
<div
|
||||
style="font-size:12px; color:#d0e8ff; display:flex; align-items:center; gap:3px;
|
||||
background:rgba(0,0,0,.2); padding:2px 8px; border-radius:10px;">
|
||||
🪙 <strong id="game-hall-jjb" style="color:#ffe082; font-size:13px;">--</strong> 金币
|
||||
</div>
|
||||
<span onclick="closeGameHall()"
|
||||
style="cursor:pointer; font-size:18px; opacity:.8; line-height:1; transition:opacity .15s;"
|
||||
onmouseover="this.style.opacity=1" onmouseout="this.style.opacity=.8">×</span>
|
||||
</div>
|
||||
|
||||
{{-- ─── 游戏卡片网格 ─── --}}
|
||||
<div style="flex:1; overflow-y:auto; background:#f6faff; padding:12px;">
|
||||
|
||||
{{-- 加载状态 --}}
|
||||
<div id="game-hall-loading" style="text-align:center; color:#336699; padding:40px 0; font-size:13px;">
|
||||
<div style="font-size:28px; margin-bottom:8px;">⏳</div>
|
||||
加载游戏状态中…
|
||||
</div>
|
||||
|
||||
{{-- 游戏卡片容器 --}}
|
||||
<div id="game-hall-cards" style="display:none; grid-template-columns:1fr 1fr; gap:10px;">
|
||||
</div>
|
||||
|
||||
{{-- 全部未开启提示 --}}
|
||||
<div id="game-hall-empty"
|
||||
style="display:none; text-align:center; color:#336699; padding:40px 0; font-size:13px;">
|
||||
<div style="font-size:28px; margin-bottom:8px;">🔒</div>
|
||||
暂无开启的游戏,请联系管理员
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ─── 底部 ─── --}}
|
||||
<div
|
||||
style="background:#fff; border-top:1px solid #d0e4f5; padding:8px 16px;
|
||||
display:flex; justify-content:center; flex-shrink:0;">
|
||||
<button onclick="closeGameHall()"
|
||||
style="padding:5px 24px; background:#f0f6ff; border:1px solid #b0d0ee; border-radius:4px;
|
||||
font-size:12px; color:#336699; cursor:pointer; transition:all .15s;"
|
||||
onmouseover="this.style.background='#ddeeff'" onmouseout="this.style.background='#f0f6ff'">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/** 游戏大厅配置定义(ID → 展示配置) */
|
||||
const GAME_HALL_GAMES = [{
|
||||
id: 'baccarat',
|
||||
name: '🎲 百家乐',
|
||||
desc: '猜骰子大小,1:1 赔率,豹子 1:24',
|
||||
accentColor: '#336699',
|
||||
fetchUrl: '/baccarat/current',
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
const panel = document.getElementById('baccarat-panel');
|
||||
if (panel) Alpine.$data(panel).show = true;
|
||||
},
|
||||
renderStatus: (data) => {
|
||||
if (!data?.round) return {
|
||||
badge: '⏸ 等待开局',
|
||||
badgeStyle: 'background:#e8f0f8; color:#336699; border:1px solid #b8d0e8',
|
||||
detail: '下局即将开始,稍后再来'
|
||||
};
|
||||
const r = data.round;
|
||||
if (r.status === 'betting') {
|
||||
return {
|
||||
badge: '🟢 押注中',
|
||||
badgeStyle: 'background:#d1fae5; color:#065f46; border:1px solid #6ee7b7',
|
||||
detail: `⏱ 剩余 ${r.seconds_left || 0} 秒 | 注池:${Number((r.total_bet_big||0)+(r.total_bet_small||0)+(r.total_bet_triple||0)).toLocaleString()} 金`
|
||||
};
|
||||
}
|
||||
return {
|
||||
badge: '⏳ 开奖中',
|
||||
badgeStyle: 'background:#fef3c7; color:#92400e; border:1px solid #fcd34d',
|
||||
detail: '正在摇骰子…'
|
||||
};
|
||||
},
|
||||
btnLabel: (data) => data?.round?.status === 'betting' ? '🎲 立即下注' : '📊 查看详情',
|
||||
},
|
||||
{
|
||||
id: 'slot_machine',
|
||||
name: '🎰 老虎机',
|
||||
desc: '每日限额旋转,中奖即时到账',
|
||||
accentColor: '#0891b2',
|
||||
fetchUrl: null,
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
const panel = document.getElementById('slot-panel');
|
||||
if (panel) Alpine.$data(panel).show = true;
|
||||
},
|
||||
renderStatus: () => ({
|
||||
badge: '✅ 随时可玩',
|
||||
badgeStyle: 'background:#d1fae5; color:#065f46; border:1px solid #6ee7b7',
|
||||
detail: '每日限额抽奖,旋转即可'
|
||||
}),
|
||||
btnLabel: () => '🎰 开始旋转',
|
||||
},
|
||||
{
|
||||
id: 'mystery_box',
|
||||
name: '📦 神秘箱子',
|
||||
desc: '管理员随机投放,抢到即开奖',
|
||||
accentColor: '#b45309',
|
||||
fetchUrl: '/mystery-box/status',
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
window.dispatchEvent(new CustomEvent('open-mystery-box'));
|
||||
},
|
||||
renderStatus: (data) => {
|
||||
const count = data?.available_count ?? 0;
|
||||
return count > 0 ? {
|
||||
badge: `🎁 ${count} 个待领`,
|
||||
badgeStyle: 'background:#fef3c7; color:#92400e; border:1px solid #fcd34d',
|
||||
detail: '箱子已投放!快去领取'
|
||||
} : {
|
||||
badge: '📭 暂无箱子',
|
||||
badgeStyle: 'background:#e8f0f8; color:#336699; border:1px solid #b8d0e8',
|
||||
detail: '等待管理员投放'
|
||||
};
|
||||
},
|
||||
btnLabel: (data) => (data?.available_count ?? 0) > 0 ? '🎁 立即领取' : '📭 等待投放',
|
||||
},
|
||||
{
|
||||
id: 'horse_racing',
|
||||
name: '🐎 赛马竞猜',
|
||||
desc: '彩池制赛马,押注马匹赢取奖金',
|
||||
accentColor: '#336699',
|
||||
fetchUrl: '/horse-race/current',
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
const panel = document.getElementById('horse-race-panel');
|
||||
if (panel) Alpine.$data(panel).openFromHall();
|
||||
},
|
||||
renderStatus: (data) => {
|
||||
if (!data?.race) return {
|
||||
badge: '⏸ 等待开赛',
|
||||
badgeStyle: 'background:#e8f0f8; color:#336699; border:1px solid #b8d0e8',
|
||||
detail: '下场赛马即将开始'
|
||||
};
|
||||
const r = data.race;
|
||||
if (r.status === 'betting') {
|
||||
return {
|
||||
badge: '🟢 押注中',
|
||||
badgeStyle: 'background:#d1fae5; color:#065f46; border:1px solid #6ee7b7',
|
||||
detail: `⏱ 剩余 ${r.seconds_left || 0} 秒 | 注池:${Number(r.total_pool || 0).toLocaleString()} 金`
|
||||
};
|
||||
}
|
||||
if (r.status === 'running') {
|
||||
return {
|
||||
badge: '🏇 跑马中',
|
||||
badgeStyle: 'background:#fef3c7; color:#92400e; border:1px solid #fcd34d',
|
||||
detail: '比赛进行中…'
|
||||
};
|
||||
}
|
||||
return {
|
||||
badge: '🏆 已结算',
|
||||
badgeStyle: 'background:#e8f0f8; color:#336699; border:1px solid #b8d0e8',
|
||||
detail: '下场即将开始'
|
||||
};
|
||||
},
|
||||
btnLabel: (data) => data?.race?.status === 'betting' ? '🐎 立即押注' : '📊 查看赛况',
|
||||
},
|
||||
{
|
||||
id: 'fortune_telling',
|
||||
name: '🔮 神秘占卜',
|
||||
desc: '每日签文,开启今日运势加成',
|
||||
accentColor: '#6d28d9',
|
||||
fetchUrl: '/fortune/today',
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
const panel = document.getElementById('fortune-panel');
|
||||
if (panel) Alpine.$data(panel).show = true;
|
||||
},
|
||||
renderStatus: (data) => {
|
||||
if (!data?.enabled) return {
|
||||
badge: '🔒 未开启',
|
||||
badgeStyle: 'background:#e8f0f8; color:#336699; border:1px solid #b8d0e8',
|
||||
detail: '此游戏暂未开启'
|
||||
};
|
||||
const used = data.free_used ?? 0;
|
||||
const total = data.free_count ?? 1;
|
||||
return data.has_free_left ? {
|
||||
badge: '✨ 免费可占',
|
||||
badgeStyle: 'background:#ede9fe; color:#5b21b6; border:1px solid #c4b5fd',
|
||||
detail: `今日已占 ${used}/${total} 次,还有免费次数`
|
||||
} : {
|
||||
badge: '💰 付费可占',
|
||||
badgeStyle: 'background:#e8f0f8; color:#336699; border:1px solid #b8d0e8',
|
||||
detail: `今日免费次数已用完(${data.extra_cost} 金/次)`
|
||||
};
|
||||
},
|
||||
btnLabel: (data) => data?.has_free_left ? '🔮 免费占卜' : '🔮 付费占卜',
|
||||
},
|
||||
{
|
||||
id: 'fishing',
|
||||
name: '🎣 钓鱼',
|
||||
desc: '消耗鱼饵钓取金币和道具。背包需有鱼饵才能出竿。',
|
||||
accentColor: '#0d9488',
|
||||
fetchUrl: null,
|
||||
openFn: () => {
|
||||
closeGameHall();
|
||||
// 直接触发钓鱼,无需手动输入指令
|
||||
if (typeof startFishing === 'function') {
|
||||
startFishing();
|
||||
}
|
||||
},
|
||||
renderStatus: () => ({
|
||||
badge: '🎣 随时可钓',
|
||||
badgeStyle: 'background:#d1fae5; color:#065f46; border:1px solid #6ee7b7',
|
||||
detail: '① 点击发言框上方【🎣 钓鱼】按钮 → ② 等待浮漂出现 → ③ 看到 🪝 后立刻点击收竿!'
|
||||
}),
|
||||
btnLabel: () => '🎣 去钓鱼',
|
||||
},
|
||||
|
||||
|
||||
];
|
||||
|
||||
/**
|
||||
* 打开游戏大厅弹窗,加载各游戏状态
|
||||
*/
|
||||
window.openGameHall = async function() {
|
||||
document.getElementById('game-hall-modal').style.display = 'flex';
|
||||
document.getElementById('game-hall-loading').style.display = 'block';
|
||||
document.getElementById('game-hall-cards').style.display = 'none';
|
||||
document.getElementById('game-hall-empty').style.display = 'none';
|
||||
|
||||
const jjbEl = document.getElementById('game-hall-jjb');
|
||||
if (window.chatContext?.userJjb !== undefined) {
|
||||
jjbEl.textContent = Number(window.chatContext.userJjb).toLocaleString();
|
||||
}
|
||||
|
||||
// 每次打开均实时拉取后台开关状态(避免页面不刷新时开关不同步)
|
||||
let enabledMap = window.GAME_ENABLED ?? {};
|
||||
try {
|
||||
const r = await fetch('/games/enabled', {
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
if (r.ok) enabledMap = await r.json();
|
||||
} catch {
|
||||
/* 网络异常时降级使用页面注入值 */
|
||||
}
|
||||
|
||||
// 过滤出后台已开启的游戏
|
||||
const enabledGames = GAME_HALL_GAMES.filter(g => enabledMap[g.id] !== false);
|
||||
|
||||
// 并行请求有状态接口的游戏
|
||||
const statuses = {};
|
||||
await Promise.all(
|
||||
enabledGames.filter(g => g.fetchUrl).map(async g => {
|
||||
try {
|
||||
const res = await fetch(g.fetchUrl);
|
||||
statuses[g.id] = await res.json();
|
||||
} catch {
|
||||
statuses[g.id] = null;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
renderGameCards(enabledGames, statuses);
|
||||
};
|
||||
|
||||
/**
|
||||
* 关闭游戏大厅弹窗
|
||||
*/
|
||||
window.closeGameHall = function() {
|
||||
document.getElementById('game-hall-modal').style.display = 'none';
|
||||
};
|
||||
|
||||
/**
|
||||
* 渲染所有游戏卡片(海军蓝风格)
|
||||
*
|
||||
* @param {Array} games 已过滤的游戏配置列表
|
||||
* @param {Object} statuses 各游戏的 API 返回数据
|
||||
*/
|
||||
function renderGameCards(games, statuses) {
|
||||
const container = document.getElementById('game-hall-cards');
|
||||
container.innerHTML = '';
|
||||
|
||||
if (games.length === 0) {
|
||||
document.getElementById('game-hall-loading').style.display = 'none';
|
||||
document.getElementById('game-hall-empty').style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
games.forEach(game => {
|
||||
const data = statuses[game.id] ?? null;
|
||||
const status = game.renderStatus ? game.renderStatus(data) : {
|
||||
badge: '✅ 可用',
|
||||
badgeStyle: 'background:#d1fae5; color:#065f46; border:1px solid #6ee7b7',
|
||||
detail: ''
|
||||
};
|
||||
const btnLabel = game.btnLabel ? game.btnLabel(data) : '🎮 进入';
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.style.cssText = `
|
||||
background:#fff;
|
||||
border:1px solid #d0e4f5;
|
||||
border-left:4px solid ${game.accentColor};
|
||||
border-radius:6px; padding:12px 14px;
|
||||
cursor:default; transition:border-color .2s, box-shadow .2s;
|
||||
display:flex; flex-direction:column; gap:8px;
|
||||
`;
|
||||
|
||||
card.innerHTML = `
|
||||
<div style="display:flex; align-items:flex-start; justify-content:space-between; gap:8px;">
|
||||
<div style="flex:1;">
|
||||
<div style="color:#225588; font-weight:bold; font-size:13px; margin-bottom:3px;">${game.name}</div>
|
||||
<div style="color:#666; font-size:11px; line-height:1.4;">${game.desc}</div>
|
||||
</div>
|
||||
<span style="padding:2px 8px; border-radius:10px; font-size:10px; font-weight:bold; white-space:nowrap; ${status.badgeStyle}">
|
||||
${status.badge}
|
||||
</span>
|
||||
</div>
|
||||
<div style="color:#888; font-size:10px; line-height:1.4; min-height:14px; border-top:1px dashed #e0ecf8; padding-top:6px;">${status.detail || ' '}</div>
|
||||
<button
|
||||
style="width:100%; border:none; border-radius:4px; padding:7px 8px; font-size:12px; font-weight:bold;
|
||||
cursor:pointer; color:#fff; transition:opacity .15s;
|
||||
background:linear-gradient(135deg,${game.accentColor},${game.accentColor}cc);"
|
||||
onmouseover="this.style.opacity='.85'"
|
||||
onmouseout="this.style.opacity='1'">
|
||||
${btnLabel}
|
||||
</button>
|
||||
`;
|
||||
|
||||
card.querySelector('button').addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
game.openFn();
|
||||
});
|
||||
|
||||
card.addEventListener('mouseenter', () => {
|
||||
card.style.borderColor = game.accentColor;
|
||||
card.style.boxShadow = `0 2px 8px rgba(51,102,153,.18)`;
|
||||
});
|
||||
card.addEventListener('mouseleave', () => {
|
||||
card.style.borderColor = '#d0e4f5';
|
||||
card.style.borderLeftColor = game.accentColor;
|
||||
card.style.boxShadow = '';
|
||||
});
|
||||
|
||||
container.appendChild(card);
|
||||
});
|
||||
|
||||
document.getElementById('game-hall-loading').style.display = 'none';
|
||||
container.style.display = 'grid';
|
||||
}
|
||||
|
||||
// 点击遮罩关闭弹窗
|
||||
document.getElementById('game-hall-modal').addEventListener('click', function(e) {
|
||||
if (e.target === this) closeGameHall();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,677 @@
|
||||
{{--
|
||||
文件功能:赛马竞猜前台弹窗组件
|
||||
|
||||
聊天室内赛马竞猜游戏面板:
|
||||
- 监听 WebSocket horse.opened 事件触发弹窗
|
||||
- 展示参赛马匹列表和实时注池赔率
|
||||
- 倒计时押注后进入跑马阶段(动态进度条)
|
||||
- 监听 horse.progress 更新赛道动画
|
||||
- 监听 horse.settled 展示结果 + 个人赔付
|
||||
- 展示近10场历史趋势
|
||||
--}}
|
||||
|
||||
{{-- ─── 赛马主面板 ─── --}}
|
||||
<div id="horse-race-panel" x-data="horseRacePanel()" x-show="show" x-cloak>
|
||||
<div x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
|
||||
style="position:fixed; inset:0; background:rgba(0,0,0,.55); z-index:9941;
|
||||
display:flex; align-items:center; justify-content:center;">
|
||||
|
||||
<div
|
||||
style="width:500px; max-width:96vw; border-radius:8px; overflow:hidden;
|
||||
box-shadow:0 8px 32px rgba(0,0,0,.3); font-family:'Microsoft YaHei',SimSun,sans-serif; background:#fff;">
|
||||
|
||||
{{-- ─── 标题栏(海军蓝风格)─── --}}
|
||||
<div
|
||||
style="background:linear-gradient(135deg,#336699,#5a8fc0); padding:10px 16px; display:flex; align-items:center; gap:10px;">
|
||||
<div style="font-size:14px; font-weight:bold; color:#fff; flex:1;">
|
||||
🐎 赛马竞猜
|
||||
<span style="font-size:11px; font-weight:normal; color:rgba(255,255,255,.6); margin-left:4px;"
|
||||
x-text="raceId ? '#' + raceId + ' 场' : ''"></span>
|
||||
</div>
|
||||
{{-- 倒计时(押注阶段) --}}
|
||||
<div x-show="phase === 'betting'"
|
||||
style="font-size:12px; color:#fff; background:rgba(255,255,255,.2); padding:4px 12px; border-radius:20px; border:1px solid rgba(255,255,255,.3); box-shadow:0 2px 4px rgba(0,0,0,.1) inset; display:flex; align-items:center; gap:4px;">
|
||||
⏳ 剩 <span x-text="countdown" style="font-weight:bold; color:#fef08a; font-size:14px;"></span> 秒
|
||||
</div>
|
||||
{{-- 跑马阶段 --}}
|
||||
<div x-show="phase === 'running'" style="display:none;"
|
||||
style="font-size:12px; color:rgba(255,255,255,.8); background:rgba(0,0,0,.2); padding:2px 10px; border-radius:10px;">
|
||||
🏇 跑马中…
|
||||
</div>
|
||||
{{-- 结算 --}}
|
||||
<div x-show="phase === 'settled'" style="display:none;"
|
||||
style="font-size:12px; color:#ffe082; font-weight:bold;">🏆 已结算</div>
|
||||
<span onclick="Alpine.$data(document.getElementById('horse-race-panel')).close()"
|
||||
style="cursor:pointer; font-size:18px; color:rgba(255,255,255,.8); line-height:1; transition:opacity .15s;"
|
||||
onmouseover="this.style.opacity=1" onmouseout="this.style.opacity=.8">×</span>
|
||||
</div>
|
||||
|
||||
{{-- 押注进度条(蓝色风格) --}}
|
||||
<div x-show="phase === 'betting'" style="height:3px; background:#d0e4f5; overflow:hidden;">
|
||||
<div style="height:100%; background:#336699; transition:width 1s linear;"
|
||||
:style="'width:' + Math.max(0, (countdown / totalSeconds * 100)) + '%'"></div>
|
||||
</div>
|
||||
|
||||
{{-- ─── 历史趋势(蓝白色系)─── --}}
|
||||
<div
|
||||
style="background:#f6faff; padding:6px 16px; border-bottom:1px solid #d0e4f5;
|
||||
display:flex; gap:5px; align-items:center; flex-wrap:wrap; min-height:32px;">
|
||||
<span style="color:#336699; font-size:11px; margin-right:2px; font-weight:bold;">近期冒涨:</span>
|
||||
<template x-for="h in history" :key="h.id">
|
||||
<span
|
||||
style="padding:1px 8px; border-radius:10px; font-size:10px; font-weight:bold;
|
||||
background:#e8f0f8; color:#336699; border:1px solid #b8d0e8;"
|
||||
:title="'#' + h.id + ' 冠军:' + h.winner_name" x-text="h.winner_name"></span>
|
||||
</template>
|
||||
<span x-show="history.length === 0" style="color:#aaa; font-size:11px;">暂无记录</span>
|
||||
</div>
|
||||
|
||||
{{-- ─── 主体内容(白底)─── --}}
|
||||
<div style="background:#fff; padding:14px 16px;">
|
||||
|
||||
{{-- ── 押注阶段 ── --}}
|
||||
<div x-show="phase === 'betting'">
|
||||
{{-- 注池统计 --}}
|
||||
<div
|
||||
style="color:#336699; font-size:11px; margin-bottom:8px; text-align:center; background:#e8f0f8; border-radius:4px; padding:4px 0;">
|
||||
注池总额:<span style="color:#b45309; font-weight:bold;"
|
||||
x-text="Number(totalPool).toLocaleString() + ' 金币'"></span>
|
||||
</div>
|
||||
|
||||
{{-- 马匹列表 --}}
|
||||
<div style="display:flex; flex-direction:column; gap:8px; margin-bottom:12px;">
|
||||
<template x-for="horse in horses" :key="horse.id">
|
||||
<div style="border-radius:12px; padding:10px 14px; cursor:pointer; transition:all .15s; border:2px solid transparent;"
|
||||
:style="selectedHorse === horse.id ?
|
||||
'background:#e8f0f8; border-color:#336699;' :
|
||||
'background:#f6faff; border-color:#d0e4f5;'"
|
||||
@click="myBet ? null : selectedHorse = horse.id">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between;">
|
||||
<div style="display:flex; align-items:center; gap:10px;">
|
||||
{{-- 选中勾选 --}}
|
||||
<div style="width:20px; height:20px; border-radius:50%; border:2px solid; display:flex; align-items:center; justify-content:center; font-size:10px; flex-shrink:0;"
|
||||
:style="selectedHorse === horse.id ?
|
||||
'border-color:#336699; background:#336699; color:#fff' :
|
||||
'border-color:#b0c8e0; color:transparent'">
|
||||
✓</div>
|
||||
<div style="font-size:22px;" x-text="horse.emoji"></div>
|
||||
<div>
|
||||
<div style="color:#225588; font-weight:bold; font-size:13px;"
|
||||
x-text="horse.name"></div>
|
||||
<div style="color:#888; font-size:10px;">
|
||||
注池:<span x-text="Number(horse.pool || 0).toLocaleString()"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{-- 实时赔率 --}}
|
||||
<div style="text-align:right;">
|
||||
<div style="color:#b45309; font-weight:900; font-size:15px;"
|
||||
x-text="horse.odds ? '×' + horse.odds : '—'"></div>
|
||||
<div style="color:#999; font-size:10px;">赔率</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
{{-- 已下注状态 --}}
|
||||
<div x-show="myBet">
|
||||
<div
|
||||
style="background:#e8fde8; border:1px solid #a3e6b0; border-radius:6px;
|
||||
padding:10px 14px; text-align:center; margin-bottom:8px;">
|
||||
<div style="color:#16a34a; font-weight:bold; font-size:13px;">
|
||||
✅ 已押注「<span x-text="myBetHorseName"></span>」
|
||||
<span x-text="Number(myBetAmount).toLocaleString()"></span> 金币
|
||||
</div>
|
||||
<div style="color:#888; font-size:11px; margin-top:3px;">等待开跑…</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 下注区 --}}
|
||||
<div x-show="!myBet">
|
||||
{{-- 快捷金额 --}}
|
||||
<div style="display:flex; gap:6px; margin-bottom:8px;">
|
||||
<template x-for="preset in [100, 500, 1000, 5000, 10000]" :key="preset">
|
||||
<button @click="betAmount = preset"
|
||||
style="flex:1; border:1px solid #b0d0ee; border-radius:6px; padding:8px 0;
|
||||
font-size:13px; font-weight:bold; cursor:pointer; transition:all .15s;"
|
||||
:style="betAmount === preset ?
|
||||
'background:linear-gradient(135deg,#336699,#5a8fc0); color:#fff; border-color:#2a5580; box-shadow:0 3px 8px rgba(51,102,153,.3);' :
|
||||
'background:#f6faff; color:#336699;'"
|
||||
x-text="preset >= 1000 ? (preset/1000)+'k' : preset"></button>
|
||||
</template>
|
||||
</div>
|
||||
<input type="number" x-model.number="betAmount" min="100" placeholder="自定义金额"
|
||||
style="width:100%; background:#f6faff; border:1px solid #d0e4f5; border-radius:8px;
|
||||
padding:10px 14px; color:#333; font-size:13px; box-sizing:border-box; margin-bottom:12px; outline:none; transition:all .15s;"
|
||||
onfocus="this.style.borderColor='#336699'; this.style.background='#fff'; this.style.boxShadow='0 0 0 2px rgba(51,102,153,.1)';"
|
||||
onblur="this.style.borderColor='#d0e4f5'; this.style.background='#f6faff'; this.style.boxShadow='none';">
|
||||
|
||||
{{-- 下注按钮 --}}
|
||||
<button @click="submitBet()" :disabled="!selectedHorse || betAmount < 100 || submitting"
|
||||
style="display:block; width:100%; border:none; border-radius:12px; padding:12px 0;
|
||||
font-size:14px; font-weight:bold; cursor:pointer; transition:all .2s; box-shadow:0 4px 12px rgba(51,102,153,.2);"
|
||||
:style="(!selectedHorse || betAmount < 100 || submitting) ?
|
||||
'background:#e0e8f0; color:#99a8b8; cursor:not-allowed; box-shadow:none;' :
|
||||
'background:linear-gradient(135deg,#336699,#5a8fc0); color:#fff;'">
|
||||
<span
|
||||
x-text="submitting ? '提交中…' : (!selectedHorse ? '请先选择马匹' : '🐎 确认押注「' + myBetHorsePreviewName + '」 ' + Number(betAmount).toLocaleString() + ' 金币')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── 跑马阶段 ── --}}
|
||||
<div x-show="phase === 'running'" style="display:none;">
|
||||
<div
|
||||
style="margin-bottom:8px; color:#336699; font-size:11px; font-weight:bold; text-align:center; background:#e8f0f8; border-radius:4px; padding:4px;">
|
||||
🏁 赛道实况
|
||||
</div>
|
||||
<div style="display:flex; flex-direction:column; gap:8px;">
|
||||
<template x-for="horse in horses" :key="horse.id">
|
||||
<div style="display:flex; align-items:center; gap:8px;">
|
||||
<div style="width:30px; text-align:center; font-size:18px;" x-text="horse.emoji"></div>
|
||||
<div style="flex:1; position:relative;">
|
||||
{{-- 赛道背景 --}}
|
||||
<div
|
||||
style="height:24px; background:#e8f0f8; border-radius:10px; overflow:hidden; position:relative;">
|
||||
{{-- 进度条 --}}
|
||||
<div style="height:100%; border-radius:20px; transition:width .9s ease-out;"
|
||||
:style="'width:' + (positions[horse.id] || 0) + '%; background:' +
|
||||
(leaderId === horse.id ? '#336699' :
|
||||
'#b8d0e8')">
|
||||
</div>
|
||||
{{-- 马匹图标(跟随进度) --}}
|
||||
<div style="position:absolute; top:50%; transform:translateY(-50%); font-size:16px; transition:left .9s ease-out; pointer-events:none;"
|
||||
:style="'left:' + Math.max(0, (positions[horse.id] || 0) - 5) + '%'">
|
||||
<span x-text="horse.emoji"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{-- 进度数字 --}}
|
||||
<div style="width:38px; text-align:right; color:#336699; font-size:10px; font-weight:bold;"
|
||||
x-text="(positions[horse.id] || 0) + '%'"></div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div style="margin-top:10px; text-align:center; color:#aaa; font-size:10px; text-align:center;">
|
||||
🏁 终点线
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── 结算阶段(蓝白风格)── --}}
|
||||
<div x-show="phase === 'settled'" style="display:none;">
|
||||
{{-- 获胜马匹 --}}
|
||||
<div
|
||||
style="text-align:center; padding:12px 0 10px; border-bottom:1px solid #d0e4f5; margin-bottom:10px;">
|
||||
<div style="font-size:36px; margin-bottom:4px;" x-text="winnerEmoji"></div>
|
||||
<div style="color:#336699; font-size:17px; font-weight:bold;"
|
||||
x-text="'🏆 ' + winnerName + ' 夺冠!'">
|
||||
</div>
|
||||
<div style="color:#888; font-size:11px; margin-top:3px;"
|
||||
x-text="'注池总额:' + Number(totalPool).toLocaleString() + ' 金币'"></div>
|
||||
</div>
|
||||
|
||||
{{-- 个人结果 --}}
|
||||
<div x-show="myBet">
|
||||
{{-- 中奖 --}}
|
||||
<div x-show="myWon"
|
||||
style="border-radius:6px; padding:12px 14px; text-align:center; margin-bottom:4px;
|
||||
background:#e8fde8; border:1px solid #a3e6b0;">
|
||||
<div style="font-size:24px; margin-bottom:4px;">🎉</div>
|
||||
<div style="color:#16a34a; font-size:16px; font-weight:bold;">恭喜中奖!</div>
|
||||
<div style="color:#15803d; font-size:18px; font-weight:bold; margin:4px 0;"
|
||||
x-text="'+' + Number(myPayout).toLocaleString() + ' 🪙'"></div>
|
||||
<div style="color:#888; font-size:10px;"
|
||||
x-text="'押「' + myBetHorseName + '」' + Number(myBetAmount).toLocaleString() + ' 金币 → 赢得 ' + Number(myPayout).toLocaleString() + ' 金币'">
|
||||
</div>
|
||||
</div>
|
||||
{{-- 未中奖 --}}
|
||||
<div x-show="!myWon"
|
||||
style="border-radius:6px; padding:12px 14px; text-align:center;
|
||||
background:#fff0f0; border:1px solid #fca5a5;">
|
||||
<div style="font-size:20px; margin-bottom:4px;">😔</div>
|
||||
<div style="color:#dc2626; font-size:13px; font-weight:bold; margin-bottom:4px;">本场未中奖
|
||||
</div>
|
||||
<div style="color:#888; font-size:11px;"
|
||||
x-text="'押了「' + myBetHorseName + '」' + Number(myBetAmount).toLocaleString() + ' 金币,冠军是「' + winnerName + '」'">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div x-show="!myBet" style="text-align:center; color:#aaa; font-size:12px; padding:8px 0;">
|
||||
本场未参与下注
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ─── 底部关闭 ─── --}}
|
||||
<div
|
||||
style="background:#fff; border-top:1px solid #d0e4f5; padding:14px 16px; display:flex; justify-content:center;">
|
||||
<button @click="close()"
|
||||
style="padding:10px 48px; min-width:140px; background:#f0f6ff; border:1px solid #b0d0ee; border-radius:12px;
|
||||
font-size:14px; font-weight:bold; color:#336699; cursor:pointer; transition:all .15s;"
|
||||
onmouseover="this.style.background='#ddeeff'" onmouseout="this.style.background='#f0f6ff'">
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
@keyframes horse-run {
|
||||
0% {
|
||||
transform: translateX(-2px);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-horse {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 4px 20px rgba(245, 158, 11, .5);
|
||||
}
|
||||
|
||||
50% {
|
||||
box-shadow: 0 4px 32px rgba(245, 158, 11, .9);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 赛马竞猜悬浮按钮 Alpine 组件(拖动 + localStorage 位置持久化)
|
||||
*/
|
||||
function horseRaceFab() {
|
||||
const STORAGE_KEY = 'horse_race_fab_pos';
|
||||
const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || 'null');
|
||||
return {
|
||||
visible: false,
|
||||
posX: saved?.x ?? 80,
|
||||
posY: saved?.y ?? 90,
|
||||
dragging: false,
|
||||
_startX: 0,
|
||||
_startY: 0,
|
||||
_origX: 0,
|
||||
_origY: 0,
|
||||
_moved: false,
|
||||
|
||||
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;
|
||||
// right 定位:往右拖 dx>0 → right 减小;bottom 定位:往下拖 dy>0 → 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));
|
||||
},
|
||||
|
||||
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('horse-race-panel');
|
||||
if (panel) Alpine.$data(panel).openFromHall();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 赛马竞猜主面板 Alpine 组件
|
||||
*/
|
||||
function horseRacePanel() {
|
||||
return {
|
||||
show: false,
|
||||
phase: 'betting', // betting | running | settled
|
||||
|
||||
raceId: null,
|
||||
totalSeconds: 90,
|
||||
countdown: 90,
|
||||
countdownTimer: null,
|
||||
|
||||
// 马匹列表(含实时赔率)
|
||||
horses: [],
|
||||
positions: {}, // 跑马进度 {horse_id: 0~100}
|
||||
leaderId: null,
|
||||
|
||||
// 注池
|
||||
totalPool: 0,
|
||||
|
||||
// 本人下注
|
||||
myBet: false,
|
||||
myBetHorseId: null,
|
||||
myBetHorseName: '',
|
||||
myBetAmount: 0,
|
||||
|
||||
// 下注表单
|
||||
selectedHorse: null,
|
||||
betAmount: 100,
|
||||
submitting: false,
|
||||
|
||||
// 结算结果
|
||||
winnerName: '',
|
||||
winnerEmoji: '',
|
||||
myWon: false,
|
||||
myPayout: 0,
|
||||
|
||||
// 历史记录
|
||||
history: [],
|
||||
|
||||
/**
|
||||
* 获取当前选中马匹的预览名称(用于按钮文字)
|
||||
*/
|
||||
get myBetHorsePreviewName() {
|
||||
if (!this.selectedHorse) return '';
|
||||
const h = this.horses.find(h => h.id === this.selectedHorse);
|
||||
return h ? h.emoji + h.name : '';
|
||||
},
|
||||
|
||||
/**
|
||||
* 开赛:填充场次数据并开始倒计时
|
||||
*/
|
||||
openRace(data) {
|
||||
this.phase = 'betting';
|
||||
this.raceId = data.race_id;
|
||||
this.countdown = data.bet_seconds || 90;
|
||||
this.totalSeconds = this.countdown;
|
||||
this.horses = data.horses || [];
|
||||
this.myBet = false;
|
||||
this.myBetHorseId = null;
|
||||
this.myBetHorseName = '';
|
||||
this.myBetAmount = 0;
|
||||
this.selectedHorse = null;
|
||||
this.betAmount = 100;
|
||||
this.positions = {};
|
||||
this.leaderId = null;
|
||||
this.show = true;
|
||||
|
||||
this.loadCurrentRace();
|
||||
this.startCountdown();
|
||||
this.updateFab(true);
|
||||
},
|
||||
|
||||
/**
|
||||
* 从接口获取当前场次状态(我的下注、注池赔率)
|
||||
*/
|
||||
async loadCurrentRace() {
|
||||
try {
|
||||
const res = await fetch('/horse-race/current');
|
||||
const data = await res.json();
|
||||
if (data.race) {
|
||||
this.horses = data.race.horses || this.horses;
|
||||
this.totalPool = data.race.total_pool || 0;
|
||||
if (data.race.my_bet) {
|
||||
this.myBet = true;
|
||||
this.myBetHorseId = data.race.my_bet.horse_id;
|
||||
this.myBetAmount = data.race.my_bet.amount;
|
||||
const h = this.horses.find(h => h.id === this.myBetHorseId);
|
||||
this.myBetHorseName = h ? h.emoji + h.name : '';
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
},
|
||||
|
||||
/**
|
||||
* 启动倒计时
|
||||
*/
|
||||
startCountdown() {
|
||||
clearInterval(this.countdownTimer);
|
||||
this.countdownTimer = setInterval(() => {
|
||||
this.countdown--;
|
||||
if (this.countdown <= 0) {
|
||||
clearInterval(this.countdownTimer);
|
||||
this.phase = 'running';
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
/**
|
||||
* 提交下注
|
||||
*/
|
||||
async submitBet() {
|
||||
if (!this.selectedHorse || this.betAmount < 100 || this.submitting) return;
|
||||
this.submitting = true;
|
||||
|
||||
try {
|
||||
const res = await fetch('/horse-race/bet', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]')?.content,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
race_id: this.raceId,
|
||||
horse_id: this.selectedHorse,
|
||||
amount: this.betAmount,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (data.ok) {
|
||||
this.myBet = true;
|
||||
this.myBetHorseId = data.horse_id;
|
||||
this.myBetAmount = data.amount;
|
||||
const h = this.horses.find(h => h.id === data.horse_id);
|
||||
this.myBetHorseName = h ? h.emoji + h.name : '';
|
||||
window.chatDialog?.alert(data.message, '下注成功', '#f59e0b');
|
||||
} else {
|
||||
window.chatDialog?.alert(data.message || '下注失败', '提示', '#ef4444');
|
||||
}
|
||||
} catch {
|
||||
window.chatDialog?.alert('网络异常,请稍后重试。', '错误', '#ef4444');
|
||||
}
|
||||
|
||||
this.submitting = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* 接收跑马进度更新
|
||||
*/
|
||||
updateProgress(data) {
|
||||
this.phase = 'running';
|
||||
this.positions = data.positions || {};
|
||||
this.leaderId = data.leader_id;
|
||||
},
|
||||
|
||||
/**
|
||||
* 显示结算结果
|
||||
*/
|
||||
showResult(data) {
|
||||
clearInterval(this.countdownTimer);
|
||||
this.phase = 'settled';
|
||||
this.show = true;
|
||||
|
||||
// 找出获胜马匹信息
|
||||
const winner = this.horses.find(h => h.id === data.winner_horse_id);
|
||||
this.winnerName = winner ? winner.emoji + winner.name : data.winner_name || '未知';
|
||||
this.winnerEmoji = winner ? winner.emoji : '🐎';
|
||||
|
||||
// 判断本人是否中奖
|
||||
if (this.myBet && this.myBetHorseId === data.winner_horse_id) {
|
||||
this.myWon = true;
|
||||
// 赔付前端显示估算(实际以后端为准,后端 WebSocket 无返回赔付金额)
|
||||
this.myPayout = 0; // 无法前端计算,等用户看下一次余额或后端私信
|
||||
} else {
|
||||
this.myWon = false;
|
||||
this.myPayout = 0;
|
||||
}
|
||||
|
||||
this.updateFab(false);
|
||||
this.loadHistory();
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载历史记录
|
||||
*/
|
||||
async loadHistory() {
|
||||
try {
|
||||
const res = await fetch('/horse-race/history');
|
||||
const data = await res.json();
|
||||
this.history = (data.history || []).reverse();
|
||||
} catch {}
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新悬浮按钮显示状态
|
||||
*/
|
||||
updateFab(visible) {
|
||||
const fab = document.getElementById('horse-race-fab');
|
||||
if (fab) Alpine.$data(fab).visible = visible;
|
||||
},
|
||||
|
||||
/**
|
||||
* 关闭面板
|
||||
*/
|
||||
close() {
|
||||
this.show = false;
|
||||
if (this.phase === 'betting') {
|
||||
this.updateFab(true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 从游戏大厅入口打开面板:先重新请求当前场次最新状态,再显示面板。
|
||||
* 解决游戏大厅展示‚押注中‚但面板状态降旧导致提交报错的问题。
|
||||
*/
|
||||
async openFromHall() {
|
||||
try {
|
||||
const res = await fetch('/horse-race/current');
|
||||
const data = await res.json();
|
||||
if (data.race) {
|
||||
const race = data.race;
|
||||
this.raceId = race.id;
|
||||
this.horses = race.horses || [];
|
||||
this.totalPool = race.total_pool || 0;
|
||||
|
||||
// 更新本人下注状态
|
||||
if (race.my_bet) {
|
||||
this.myBet = true;
|
||||
this.myBetHorseId = race.my_bet.horse_id;
|
||||
this.myBetAmount = race.my_bet.amount;
|
||||
const h = this.horses.find(h => h.id === race.my_bet.horse_id);
|
||||
this.myBetHorseName = h ? h.emoji + h.name : '';
|
||||
} else {
|
||||
this.myBet = false;
|
||||
this.myBetHorseId = null;
|
||||
this.myBetHorseName = '';
|
||||
this.myBetAmount = 0;
|
||||
}
|
||||
|
||||
// 同步阶段和倒计时
|
||||
if (race.status === 'betting' && (race.seconds_left ?? 0) > 0) {
|
||||
this.phase = 'betting';
|
||||
this.countdown = race.seconds_left;
|
||||
this.totalSeconds = race.seconds_left;
|
||||
this.startCountdown();
|
||||
} else if (race.status === 'running') {
|
||||
this.phase = 'running';
|
||||
} else {
|
||||
this.phase = 'settled';
|
||||
}
|
||||
} else {
|
||||
// 当前无进行中场次,重置状态
|
||||
this.raceId = null;
|
||||
this.horses = [];
|
||||
this.phase = 'betting';
|
||||
this.countdown = 0;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[\u8d5b\u9a6c] openFromHall 失\u8d25', e);
|
||||
}
|
||||
this.show = true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ─── WebSocket 监听 ──────────────────────────────────────────────
|
||||
|
||||
/** 收到开赛事件:弹出押注面板 */
|
||||
window.addEventListener('chat:horse.opened', (e) => {
|
||||
const panel = document.getElementById('horse-race-panel');
|
||||
if (panel) Alpine.$data(panel).openRace(e.detail);
|
||||
});
|
||||
|
||||
/** 收到跑马进度事件:更新赛道 */
|
||||
window.addEventListener('chat:horse.progress', (e) => {
|
||||
const panel = document.getElementById('horse-race-panel');
|
||||
if (panel) Alpine.$data(panel).updateProgress(e.detail);
|
||||
});
|
||||
|
||||
/** 收到结算事件:展示结果 */
|
||||
window.addEventListener('chat:horse.settled', (e) => {
|
||||
const panel = document.getElementById('horse-race-panel');
|
||||
if (panel) Alpine.$data(panel).showResult(e.detail);
|
||||
});
|
||||
|
||||
/** 页面加载时恢复进行中的场次 */
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
try {
|
||||
const histRes = await fetch('/horse-race/history');
|
||||
const histData = await histRes.json();
|
||||
const panel = document.getElementById('horse-race-panel');
|
||||
const fab = document.getElementById('horse-race-fab');
|
||||
|
||||
if (panel) {
|
||||
Alpine.$data(panel).history = (histData.history || []).reverse();
|
||||
}
|
||||
|
||||
const curRes = await fetch('/horse-race/current');
|
||||
const curData = await curRes.json();
|
||||
|
||||
// 游戏可访问则常驻显示 FAB(与占卜一致)
|
||||
if (fab) Alpine.$data(fab).visible = true;
|
||||
|
||||
if (curData.race && panel) {
|
||||
const race = curData.race;
|
||||
const seconds = race.seconds_left || 0;
|
||||
const panelData = Alpine.$data(panel);
|
||||
|
||||
panelData.raceId = race.id;
|
||||
panelData.horses = race.horses || [];
|
||||
panelData.totalPool = race.total_pool || 0;
|
||||
|
||||
if (race.my_bet) {
|
||||
panelData.myBet = true;
|
||||
panelData.myBetHorseId = race.my_bet.horse_id;
|
||||
panelData.myBetAmount = race.my_bet.amount;
|
||||
const h = panelData.horses.find(h => h.id === race.my_bet.horse_id);
|
||||
panelData.myBetHorseName = h ? h.emoji + h.name : '';
|
||||
}
|
||||
|
||||
if (race.status === 'betting' && seconds > 0) {
|
||||
panelData.phase = 'betting';
|
||||
panelData.countdown = seconds;
|
||||
} else if (race.status === 'running') {
|
||||
panelData.phase = 'running';
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[赛马] 初始化失败', e);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -611,49 +611,54 @@
|
||||
style="position:fixed; inset:0; background:rgba(0,0,0,.6);
|
||||
z-index:9910; display:flex; align-items:center; justify-content:center;">
|
||||
<div
|
||||
style="width:380px; max-width:95vw; background:linear-gradient(160deg,#7c2d12,#9a3412);
|
||||
border-radius:20px; box-shadow:0 24px 80px rgba(220,38,38,.5); overflow:hidden; text-align:center;
|
||||
border:1px solid rgba(251,146,60,.3);">
|
||||
<div style="padding:24px 20px 16px;">
|
||||
<div style="font-size:52px; margin-bottom:8px; animation:pulse 1.5s infinite;">🧧</div>
|
||||
<div style="color:#fef3c7; font-weight:bold; font-size:18px; margin-bottom:4px;" x-text="title"></div>
|
||||
<div style="color:rgba(254,243,199,.7); font-size:12px; margin-bottom:16px;" x-text="subTitle"></div>
|
||||
style="width:380px; max-width:95vw;
|
||||
background:linear-gradient(160deg,#c0392b,#e74c3c,#c0392b);
|
||||
border-radius:20px; overflow:hidden; text-align:center;
|
||||
box-shadow:0 24px 80px rgba(231,76,60,.65), 0 0 0 1px rgba(255,210,100,.25);
|
||||
border:1px solid rgba(255,210,100,.3);">
|
||||
<div style="padding:28px 20px 12px;">
|
||||
<div style="font-size:56px; margin-bottom:8px; animation:pulse 1.5s infinite;">🧧</div>
|
||||
<div style="color:#fff8dc; font-weight:bold; font-size:18px; margin-bottom:4px; text-shadow:0 1px 4px rgba(0,0,0,.3);"
|
||||
x-text="title"></div>
|
||||
<div style="color:rgba(255,248,220,.75); font-size:12px; margin-bottom:8px;" x-text="subTitle"></div>
|
||||
|
||||
{{-- 未领取 --}}
|
||||
<div x-show="!claimed" style="display:none;">
|
||||
<div style="color:rgba(254,243,199,.5); font-size:11px; margin-bottom:14px;">
|
||||
<div style="color:rgba(255,248,220,.55); font-size:11px; margin-bottom:20px;">
|
||||
红包有效期 <strong style="color:#fcd34d;">24小时</strong>,过期自动消失
|
||||
</div>
|
||||
{{-- 仿"同意离婚"按钮:深色外框 + 内部实心颜色按钮 --}}
|
||||
<div style="background:rgba(0,0,0,.35); border-radius:18px; padding:6px; margin-bottom:4px;">
|
||||
|
||||
{{-- 圆形领取按钮(仿「開」按钮,全样式写入 :style 避免 Alpine 覆盖) --}}
|
||||
<div style="display:flex; justify-content:center; margin-bottom:20px;">
|
||||
<button x-on:click="doClaim()" :disabled="claiming"
|
||||
style="display:block; width:100%; padding:14px 0; border:none;
|
||||
border-radius:13px; font-size:16px; font-weight:bold;
|
||||
cursor:pointer; transition:all .15s; letter-spacing:1px; color:#fff;"
|
||||
onmouseover="if(!this.disabled)this.style.transform='scale(1.08)'"
|
||||
onmouseout="this.style.transform=''"
|
||||
:style="claiming
|
||||
?
|
||||
'background:#b45309; opacity:.65; cursor:not-allowed;' :
|
||||
'background:#d97706; box-shadow:0 2px 12px rgba(0,0,0,.4);'"
|
||||
onmouseover="if(!this.disabled) this.style.filter='brightness(1.12)'"
|
||||
onmouseout="this.style.filter=''">
|
||||
<span x-text="claiming ? '领取中…' : '🧧 点击领取红包'"></span>
|
||||
'width:130px; height:130px; border-radius:50%; border:none; cursor:not-allowed; display:flex; align-items:center; justify-content:center; flex-shrink:0; box-sizing:border-box; transition:all .25s; font-size:28px; font-weight:900; letter-spacing:2px; background:#c8b89a; color:rgba(100,50,20,.45); box-shadow:none;' :
|
||||
'width:130px; height:130px; border-radius:50%; border:none; cursor:pointer; display:flex; align-items:center; justify-content:center; flex-shrink:0; box-sizing:border-box; transition:all .25s; font-size:28px; font-weight:900; letter-spacing:2px; background:#f5e6c8; color:#8b3a1a; box-shadow:0 6px 28px rgba(0,0,0,.35), inset 0 -4px 10px rgba(139,58,26,.12); animation:grab-btn-pulse 1.8s ease-in-out infinite;'">
|
||||
<span x-text="claiming ? '…' : '领取'"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{{-- 已领取 --}}
|
||||
<div x-show="claimed" style="display:none;">
|
||||
<div style="font-size:36px; margin-bottom:6px; color:#fcd34d; font-weight:bold;"
|
||||
<div x-show="claimed" style="display:none; padding-bottom:8px;">
|
||||
<div style="font-size:40px; margin-bottom:6px; color:#fcd34d; font-weight:bold;"
|
||||
x-text="'+' + claimedAmount.toLocaleString() + ' 金'"></div>
|
||||
<div style="color:#fef3c7; font-size:13px;">🎉 恭喜你领取了红包!</div>
|
||||
<div style="color:rgba(254,243,199,.6); font-size:11px; margin-top:4px;">金币已自动到账</div>
|
||||
<div style="color:#fff8dc; font-size:13px;">🎉 恭喜你领取了红包!</div>
|
||||
<div style="color:rgba(255,248,220,.6); font-size:11px; margin-top:4px;">金币已自动到账</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding:0 20px 20px;">
|
||||
|
||||
{{-- 关闭按钮 --}}
|
||||
<div style="padding:0 20px 24px; display:flex; justify-content:center;">
|
||||
<button x-on:click="close()"
|
||||
style="padding:10px 32px; background:rgba(0,0,0,.3); border:none; border-radius:30px;
|
||||
font-size:12px; color:rgba(254,243,199,.7); cursor:pointer;">
|
||||
style="width:200px; padding:12px 0; background:rgba(0,0,0,.3); border:none; border-radius:30px;
|
||||
font-size:13px; color:rgba(255,248,220,.8); cursor:pointer; letter-spacing:1px;
|
||||
transition:background .15s;"
|
||||
onmouseover="this.style.background='rgba(0,0,0,.48)'"
|
||||
onmouseout="this.style.background='rgba(0,0,0,.3)'">
|
||||
<span x-text="claimed ? '收下啦 ✨' : '关闭'"></span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -661,6 +666,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
@keyframes pulse {
|
||||
|
||||
@@ -1412,5 +1418,55 @@
|
||||
// 延迟初始化,确保 Echo 已就绪
|
||||
setTimeout(() => window.initMarriagePrivateChannel(userId), 1500);
|
||||
}
|
||||
|
||||
// ── 页面刷新后恢复婚礼红包领取按钮 ─────────────────────────
|
||||
// 延迟 2 秒以确保聊天框和 Alpine 均已完成初始化
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const res = await fetch('/wedding/pending-envelopes', {
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok || !data.envelopes?.length) return;
|
||||
|
||||
// 初始化全局缓存
|
||||
if (!window._weddingEnvelopes) window._weddingEnvelopes = {};
|
||||
|
||||
data.envelopes.forEach(env => {
|
||||
const ceremonyId = env.ceremony_id;
|
||||
|
||||
// 注入 detail 到全局 Map,供领取弹窗使用
|
||||
window._weddingEnvelopes[ceremonyId] = {
|
||||
ceremony_id: ceremonyId,
|
||||
total_amount: env.total_amount,
|
||||
tier_name: env.tier_name,
|
||||
tier_icon: env.tier_icon,
|
||||
user: {
|
||||
username: env.groom
|
||||
},
|
||||
partner: {
|
||||
username: env.bride
|
||||
},
|
||||
};
|
||||
|
||||
// 在包厢窗口追加提示 + 领取按钮
|
||||
if (typeof appendSystemMessage === 'function') {
|
||||
const claimBtn = `<button onclick="(function(){var d=window._weddingEnvelopes[${ceremonyId}];var el=document.getElementById('wedding-envelope-modal');if(el&&d)Alpine.$data(el).open(d);})()"
|
||||
style="display:inline-block; margin-left:10px; padding:4px 14px; border-radius:20px;
|
||||
background:#d97706; color:#fff;
|
||||
border:none; font-size:12px; font-weight:bold; cursor:pointer;
|
||||
vertical-align:middle; line-height:1.8; box-shadow:0 2px 8px rgba(0,0,0,.3);"
|
||||
title="点击领取婚礼红包">🧧 点击领取红包</button>`;
|
||||
appendSystemMessage(
|
||||
`⚠️ 您有来自 ${env.tier_icon} ${env.groom} 与 ${env.bride}【${env.tier_name}】的婚礼红包未领取!${claimBtn}`
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('[婚礼红包] 恢复待领取按钮失败', e);
|
||||
}
|
||||
}, 2000);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -9,157 +9,140 @@
|
||||
- 最近记录展示
|
||||
--}}
|
||||
|
||||
{{-- ─── 老虎机悬浮按钮(可拖动) ─── --}}
|
||||
<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;"
|
||||
:style="dragging ? 'cursor:grabbing;' : 'cursor:grab;'"
|
||||
title="老虎机(可拖动)">🎰</button>
|
||||
</div>
|
||||
|
||||
{{-- ─── 老虎机主面板 ─── --}}
|
||||
|
||||
{{-- \u2500\u2500\u2500 \u8001\u864e\u673a\u4e3b\u9762\u677f \u2500\u2500\u2500 --}}
|
||||
<div id="slot-panel" x-data="slotPanel()" x-show="show" x-cloak>
|
||||
<div x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100" x-transition:leave="transition ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
|
||||
style="position:fixed; inset:0; background:rgba(0,0,0,.75); z-index:9950;
|
||||
style="position:fixed; inset:0; background:rgba(0,0,0,.55); z-index:9950;
|
||||
display:flex; align-items:center; justify-content:center;">
|
||||
|
||||
<div
|
||||
style="width:400px; max-width:96vw; border-radius:24px; overflow:hidden;
|
||||
box-shadow:0 24px 80px rgba(245,158,11,.4); font-family:system-ui,sans-serif;">
|
||||
style="width:420px; max-width:96vw; border-radius:8px; overflow:hidden;
|
||||
box-shadow:0 8px 32px rgba(0,0,0,.3); font-family:'Microsoft YaHei',SimSun,sans-serif; background:#fff;">
|
||||
|
||||
{{-- ─── 顶部标题 ─── --}}
|
||||
<div style="background:linear-gradient(135deg,#78350f,#b45309,#d97706); padding:16px 20px 12px;">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between;">
|
||||
<div>
|
||||
<div style="color:#fff; font-weight:900; font-size:18px;">🎰 老虎机</div>
|
||||
<div style="color:rgba(255,255,255,.6); font-size:11px; margin-top:2px;">
|
||||
每次消耗 <span x-text="costPerSpin" style="color:#fbbf24; font-weight:bold;"></span> 金币
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align:right;">
|
||||
<div style="color:#fbbf24; font-size:18px; font-weight:900;">🪙 <span
|
||||
x-text="Number(balance).toLocaleString()"></span></div>
|
||||
<div x-show="dailyLimit > 0" style="color:rgba(255,255,255,.5); font-size:11px;"
|
||||
x-text="'今日剩余 ' + remaining + ' 次'"></div>
|
||||
</div>
|
||||
{{-- \u2500\u2500\u2500 \u6807\u9898\u680f\uff08\u6d77\u519b\u84dd\u98ce\u683c\uff09\u2500\u2500\u2500 --}}
|
||||
<div
|
||||
style="background:linear-gradient(135deg,#336699,#5a8fc0); padding:10px 16px; display:flex; align-items:center; gap:10px;">
|
||||
<div style="font-size:14px; font-weight:bold; color:#fff; flex:1;">🎰 老虎机</div>
|
||||
<div
|
||||
style="font-size:12px; color:rgba(255,255,255,.8); background:rgba(0,0,0,.15); padding:2px 10px; border-radius:10px; display:flex; align-items:center; gap:4px;">
|
||||
🪙 <span x-text="Number(balance).toLocaleString()" style="color:#fef08a; font-weight:bold;"></span>
|
||||
</div>
|
||||
<div x-show="dailyLimit > 0"
|
||||
style="font-size:11px; color:rgba(255,255,255,.7); background:rgba(255,255,255,.15); padding:2px 8px; border-radius:10px;"
|
||||
x-text="'剩余 ' + remaining + ' 次'"></div>
|
||||
<span onclick="Alpine.$data(document.getElementById('slot-panel')).close()"
|
||||
style="cursor:pointer; font-size:18px; color:rgba(255,255,255,.8); line-height:1; transition:opacity .15s;"
|
||||
onmouseover="this.style.opacity=1" onmouseout="this.style.opacity=.8">×</span>
|
||||
</div>
|
||||
|
||||
{{-- ─── 滚轮区域 ─── --}}
|
||||
<div style="background:linear-gradient(180deg,#1c1007,#292012); padding:20px;">
|
||||
{{-- \u2500\u2500\u2500 \u8de8\u8d39\u8bf4\u660e\u6761 \u2500\u2500\u2500 --}}
|
||||
<div
|
||||
style="background:#f6faff; border-bottom:1px solid #d0e4f5; padding:4px 16px; font-size:11px; color:#5a8fc0; text-align:center;">
|
||||
每次消耗 <span x-text="costPerSpin" style="color:#336699; font-weight:bold;"></span> 金币
|
||||
</div>
|
||||
|
||||
{{-- 三列转轮 --}}
|
||||
{{-- \u2500\u2500\u2500 \u4e3b\u4f53\u5185\u5bb9\uff08\u767d\u5e95\uff09\u2500\u2500\u2500 --}}
|
||||
<div style="background:#fff; padding:16px 16px 12px;">
|
||||
|
||||
{{-- \u4e09\u5217\u8f6c\u8f6e --}}
|
||||
<div
|
||||
style="background:#0f0a02; border-radius:16px; padding:16px 12px; margin-bottom:16px;
|
||||
border:2px solid rgba(245,158,11,.3); box-shadow:inset 0 0 30px rgba(0,0,0,.5);">
|
||||
style="background:#f0f6ff; border-radius:10px; padding:14px 12px; margin-bottom:14px;
|
||||
border:1px solid #d0e4f5; box-shadow:inset 0 2px 6px rgba(51,102,153,.06);">
|
||||
<div style="display:flex; gap:8px; justify-content:center; align-items:center;">
|
||||
{{-- 第一列 --}}
|
||||
<div
|
||||
style="flex:1; background:linear-gradient(180deg,#1a1000,#2d1f00); border-radius:12px;
|
||||
height:90px; display:flex; align-items:center; justify-content:center;
|
||||
border:1px solid rgba(245,158,11,.2); overflow:hidden; position:relative;">
|
||||
<div id="slot-reel-0" style="font-size:44px; transition:all .15s; user-select:none;"
|
||||
x-text="spinning ? spinEmojis[0] : resultEmojis[0]"
|
||||
:style="spinning && !reel1Stopped ? 'animation:reel-spin .1s linear infinite' : ''">
|
||||
style="flex:1; background:#fff; border-radius:8px;
|
||||
height:120px; display:flex; align-items:center; justify-content:center;
|
||||
border:1.5px solid #b8d0e8; box-shadow:0 2px 8px rgba(51,102,153,.1);">
|
||||
<div id="slot-reel-0" x-text="spinning ? spinEmojis[0] : resultEmojis[0]"
|
||||
:style="'font-size:80px; line-height:1; transition:all .15s; user-select:none; ' + (spinning &&
|
||||
!reel1Stopped ? 'animation:reel-spin .1s linear infinite' : '')">
|
||||
</div>
|
||||
</div>
|
||||
{{-- 分隔 --}}
|
||||
<div style="color:rgba(245,158,11,.4); font-size:20px; font-weight:900;">|</div>
|
||||
<div style="color:#b8d0e8; font-size:20px; font-weight:900;">|</div>
|
||||
{{-- 第二列 --}}
|
||||
<div
|
||||
style="flex:1; background:linear-gradient(180deg,#1a1000,#2d1f00); border-radius:12px;
|
||||
height:90px; display:flex; align-items:center; justify-content:center;
|
||||
border:1px solid rgba(245,158,11,.2); overflow:hidden; position:relative;">
|
||||
<div id="slot-reel-1" style="font-size:44px; transition:all .15s; user-select:none;"
|
||||
x-text="spinning ? spinEmojis[1] : resultEmojis[1]"
|
||||
:style="spinning && !reel2Stopped ? 'animation:reel-spin .12s linear infinite' : ''">
|
||||
style="flex:1; background:#fff; border-radius:8px;
|
||||
height:120px; display:flex; align-items:center; justify-content:center;
|
||||
border:1.5px solid #b8d0e8; box-shadow:0 2px 8px rgba(51,102,153,.1);">
|
||||
<div id="slot-reel-1" x-text="spinning ? spinEmojis[1] : resultEmojis[1]"
|
||||
:style="'font-size:80px; line-height:1; transition:all .15s; user-select:none; ' + (spinning &&
|
||||
!reel2Stopped ? 'animation:reel-spin .12s linear infinite' : '')">
|
||||
</div>
|
||||
</div>
|
||||
{{-- 分隔 --}}
|
||||
<div style="color:rgba(245,158,11,.4); font-size:20px; font-weight:900;">|</div>
|
||||
<div style="color:#b8d0e8; font-size:20px; font-weight:900;">|</div>
|
||||
{{-- 第三列 --}}
|
||||
<div
|
||||
style="flex:1; background:linear-gradient(180deg,#1a1000,#2d1f00); border-radius:12px;
|
||||
height:90px; display:flex; align-items:center; justify-content:center;
|
||||
border:1px solid rgba(245,158,11,.2); overflow:hidden; position:relative;">
|
||||
<div id="slot-reel-2" style="font-size:44px; transition:all .15s; user-select:none;"
|
||||
x-text="spinning ? spinEmojis[2] : resultEmojis[2]"
|
||||
:style="spinning && !reel3Stopped ? 'animation:reel-spin .14s linear infinite' : ''">
|
||||
style="flex:1; background:#fff; border-radius:8px;
|
||||
height:120px; display:flex; align-items:center; justify-content:center;
|
||||
border:1.5px solid #b8d0e8; box-shadow:0 2px 8px rgba(51,102,153,.1);">
|
||||
<div id="slot-reel-2" x-text="spinning ? spinEmojis[2] : resultEmojis[2]"
|
||||
:style="'font-size:80px; line-height:1; transition:all .15s; user-select:none; ' + (spinning &&
|
||||
!reel3Stopped ? 'animation:reel-spin .14s linear infinite' : '')">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 中间射线指示条 --}}
|
||||
<div
|
||||
style="height:2px; background:linear-gradient(90deg,transparent,rgba(245,158,11,.6),transparent);
|
||||
margin-top:8px; border-radius:1px;">
|
||||
style="height:2px; background:linear-gradient(90deg,transparent,rgba(51,102,153,.3),transparent);
|
||||
margin-top:10px; border-radius:1px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 结果提示 --}}
|
||||
<div style="text-align:center; min-height:36px; margin-bottom:12px;">
|
||||
{{-- \u7ed3\u679c\u63d0\u793a --}}
|
||||
<div style="text-align:center; min-height:34px; margin-bottom:10px;">
|
||||
<div x-show="!spinning && resultLabel" x-transition
|
||||
style="display:inline-block; padding:5px 18px; border-radius:20px; font-weight:bold; font-size:14px;"
|
||||
style="display:inline-block; padding:4px 18px; border-radius:20px; font-weight:bold; font-size:13px;"
|
||||
:style="resultType === 'jackpot' ?
|
||||
'background:linear-gradient(135deg,#fbbf24,#f59e0b); color:#1c1007; box-shadow:0 0 20px rgba(251,191,36,.5);' :
|
||||
'background:linear-gradient(135deg,#fbbf24,#f59e0b); color:#fff; box-shadow:0 0 16px rgba(251,191,36,.4);' :
|
||||
resultType === 'triple_gem' ?
|
||||
'background:rgba(167,139,250,.2); color:#c4b5fd; border:1px solid rgba(167,139,250,.3);' :
|
||||
'background:linear-gradient(135deg,#7c3aed,#a78bfa); color:#fff; box-shadow:0 0 12px rgba(124,58,237,.3);' :
|
||||
resultType === 'triple' ?
|
||||
'background:rgba(52,211,153,.15); color:#6ee7b7; border:1px solid rgba(52,211,153,.25);' :
|
||||
'background:linear-gradient(135deg,#059669,#34d399); color:#fff; box-shadow:0 0 10px rgba(52,211,153,.3);' :
|
||||
resultType === 'pair' ?
|
||||
'background:rgba(96,165,250,.15); color:#93c5fd; border:1px solid rgba(96,165,250,.25);' :
|
||||
'background:linear-gradient(135deg,#336699,#5a8fc0); color:#fff; box-shadow:0 0 10px rgba(51,102,153,.2);' :
|
||||
resultType === 'curse' ?
|
||||
'background:rgba(239,68,68,.15); color:#f87171; border:1px solid rgba(239,68,68,.25);' :
|
||||
'background:rgba(255,255,255,.06); color:rgba(255,255,255,.4); '"
|
||||
'background:linear-gradient(135deg,#dc2626,#ef4444); color:#fff; box-shadow:0 0 10px rgba(220,38,38,.3);' :
|
||||
'background:#f0f6ff; color:#5a8fc0; border:1px solid #d0e4f5;'"
|
||||
x-text="resultLabel">
|
||||
</div>
|
||||
<div x-show="spinning"
|
||||
style="color:rgba(255,255,255,.4); font-size:13px; animation:blink .6s infinite;">
|
||||
<div x-show="spinning" style="color:#5a8fc0; font-size:13px; animation:blink .6s infinite;">
|
||||
正在转动中…
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 盈亏显示 --}}
|
||||
{{-- \u76c8\u4e8f\u663e\u793a --}}
|
||||
<div x-show="!spinning && resultType" style="text-align:center; margin-bottom:12px;">
|
||||
<div x-show="netChange > 0" style="color:#34d399; font-size:20px; font-weight:bold;"
|
||||
<div x-show="netChange > 0" style="color:#16a34a; font-size:22px; font-weight:bold;"
|
||||
x-text="'+' + Number(netChange).toLocaleString() + ' 🪙'">
|
||||
</div>
|
||||
<div x-show="netChange < 0" style="color:#f87171; font-size:16px; font-weight:bold;"
|
||||
<div x-show="netChange < 0" style="color:#dc2626; font-size:16px; font-weight:bold;"
|
||||
x-text="Number(netChange).toLocaleString() + ' 🪙'">
|
||||
</div>
|
||||
<div x-show="netChange === 0 && resultType === 'miss'"
|
||||
style="color:rgba(255,255,255,.3); font-size:13px;">
|
||||
<div x-show="netChange === 0 && resultType === 'miss'" style="color:#999; font-size:13px;">
|
||||
损失 <span x-text="costPerSpin"></span> 金币
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 转动按钮 --}}
|
||||
{{-- \u65cb\u8f6c\u6309\u94ae --}}
|
||||
<button x-on:click="doSpin()" :disabled="spinning || (dailyLimit > 0 && remaining <= 0)"
|
||||
style="width:100%; border:none; border-radius:14px; padding:14px; font-size:16px;
|
||||
font-weight:900; cursor:pointer; transition:all .2s; letter-spacing:2px;"
|
||||
style="display:block; width:100%; border:none; border-radius:12px; padding:12px 0;
|
||||
font-size:15px; font-weight:bold; cursor:pointer; transition:all .2s; letter-spacing:1px;"
|
||||
:style="(spinning || (dailyLimit > 0 && remaining <= 0)) ? {
|
||||
background: '#292012',
|
||||
color: 'rgba(255,255,255,.3)',
|
||||
cursor: 'not-allowed'
|
||||
background: '#e0e8f0',
|
||||
color: '#99a8b8',
|
||||
cursor: 'not-allowed',
|
||||
boxShadow: 'none'
|
||||
} : {
|
||||
background: 'linear-gradient(135deg,#b45309,#d97706,#f59e0b)',
|
||||
background: 'linear-gradient(135deg,#336699,#5a8fc0)',
|
||||
color: '#fff',
|
||||
boxShadow: '0 4px 20px rgba(245,158,11,.5)',
|
||||
transform: 'scale(1)'
|
||||
boxShadow: '0 4px 14px rgba(51,102,153,.35)'
|
||||
}">
|
||||
<span
|
||||
x-text="spinning ? '🎰 转动中…' :
|
||||
@@ -167,40 +150,78 @@
|
||||
'🎰 SPIN'"></span>
|
||||
</button>
|
||||
|
||||
{{-- 赔率说明 --}}
|
||||
<div style="display:grid; grid-template-columns:repeat(4,1fr); gap:4px; margin-top:10px;">
|
||||
{{-- \u8d54\u7387\u8bf4\u660e --}}
|
||||
<div style="display:grid; grid-template-columns:repeat(4,1fr); gap:6px; margin-top:12px;">
|
||||
<div
|
||||
style="background:rgba(245,158,11,.1); border-radius:6px; padding:4px; text-align:center; font-size:10px;">
|
||||
style="background:#fffbeb; border:1px solid #fde68a; border-radius:6px; padding:5px; text-align:center; font-size:10px;">
|
||||
<div>7️⃣7️⃣7️⃣</div>
|
||||
<div style="color:#fbbf24; font-weight:bold;">×100</div>
|
||||
<div style="color:#d97706; font-weight:bold; margin-top:2px;">×100</div>
|
||||
</div>
|
||||
<div
|
||||
style="background:rgba(167,139,250,.1); border-radius:6px; padding:4px; text-align:center; font-size:10px;">
|
||||
style="background:#f5f3ff; border:1px solid #ddd6fe; border-radius:6px; padding:5px; text-align:center; font-size:10px;">
|
||||
<div>💎💎💎</div>
|
||||
<div style="color:#c4b5fd; font-weight:bold;">×50</div>
|
||||
<div style="color:#7c3aed; font-weight:bold; margin-top:2px;">×50</div>
|
||||
</div>
|
||||
<div
|
||||
style="background:rgba(52,211,153,.1); border-radius:6px; padding:4px; text-align:center; font-size:10px;">
|
||||
style="background:#f0fdf4; border:1px solid #bbf7d0; border-radius:6px; padding:5px; text-align:center; font-size:10px;">
|
||||
<div>三同</div>
|
||||
<div style="color:#6ee7b7; font-weight:bold;">×10</div>
|
||||
<div style="color:#059669; font-weight:bold; margin-top:2px;">×10</div>
|
||||
</div>
|
||||
<div
|
||||
style="background:rgba(96,165,250,.1); border-radius:6px; padding:4px; text-align:center; font-size:10px;">
|
||||
style="background:#f0f6ff; border:1px solid #d0e4f5; border-radius:6px; padding:5px; text-align:center; font-size:10px;">
|
||||
<div>两同</div>
|
||||
<div style="color:#93c5fd; font-weight:bold;">×2</div>
|
||||
<div style="color:#336699; font-weight:bold; margin-top:2px;">×2</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{{-- 玩法说明(可折叠) --}}
|
||||
<div x-data="{ open: false }" style="margin-top:12px;">
|
||||
<button @click="open = !open"
|
||||
style="width:100%; background:#f6faff; border:1px solid #d0e4f5; border-radius:8px;
|
||||
padding:7px 12px; font-size:11px; color:#5a8fc0; cursor:pointer; transition:all .15s;
|
||||
display:flex; align-items:center; justify-content:space-between;"
|
||||
onmouseover="this.style.background='#eaf3ff'" onmouseout="this.style.background='#f6faff'">
|
||||
<span>📖 玩法说明</span>
|
||||
<span x-text="open ? '▲ 收起' : '▼ 展开'" style="font-size:10px; color:#99b0cc;"></span>
|
||||
</button>
|
||||
<div x-show="open" x-transition
|
||||
style="margin-top:6px; background:#f6faff; border:1px solid #d0e4f5; border-radius:8px;
|
||||
padding:10px 12px; font-size:11px; color:#446688; line-height:1.8;">
|
||||
<div style="font-weight:bold; color:#336699; margin-bottom:6px; font-size:12px;">🎰 如何游玩</div>
|
||||
<div>① 点击 <strong>SPIN</strong> 按钮消耗金币抽奖,系统随机确定三列图案</div>
|
||||
<div>② 图案组合决定奖励倍率,奖励金币 = 消耗金币 × 倍率</div>
|
||||
<div>③ 每日有次数上限,用完须等次日 0 点重置</div>
|
||||
|
||||
<div style="height:1px; background:#d0e4f5; margin:8px 0;"></div>
|
||||
<div style="font-weight:bold; color:#336699; margin-bottom:6px; font-size:12px;">💎 图案赔率表</div>
|
||||
<div style="display:grid; grid-template-columns:1fr 1fr; gap:4px;">
|
||||
<div>🎰 7️⃣7️⃣7️⃣ → <strong style="color:#d97706;">×100</strong> 大奖全服广播</div>
|
||||
<div>💎💎💎 → <strong style="color:#7c3aed;">×50</strong> 三钻大奖</div>
|
||||
<div>任意三同 → <strong style="color:#059669;">×10</strong> 三倍以上</div>
|
||||
<div>任意两同 → <strong style="color:#336699;">×2</strong> 回本加成</div>
|
||||
<div>💀 骷髅 → <strong style="color:#dc2626;">另有惩罚</strong></div>
|
||||
<div>其余组合 → <strong style="color:#999;">未中奖</strong></div>
|
||||
</div>
|
||||
|
||||
<div style="height:1px; background:#d0e4f5; margin:8px 0;"></div>
|
||||
<div style="font-weight:bold; color:#336699; margin-bottom:4px; font-size:12px;">⚠️ 注意事项</div>
|
||||
<div>• 奖励直接到账金币,无需额外领取</div>
|
||||
<div>• 三个 7️⃣ 为聊天室大奖,系统会全服广播</div>
|
||||
<div>• 未中奖只损失本次消耗的旋转费用</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 历史记录 --}}
|
||||
<div x-show="history.length > 0" style="margin-top:10px;">
|
||||
<div style="color:rgba(255,255,255,.3); font-size:10px; margin-bottom:4px;">最近记录</div>
|
||||
<div x-show="history.length > 0" style="margin-top:12px;">
|
||||
<div style="color:#99b0cc; font-size:10px; margin-bottom:5px;">最近记录</div>
|
||||
<div style="display:flex; gap:4px; flex-wrap:wrap;">
|
||||
<template x-for="h in history" :key="h.created_at + h.result_label">
|
||||
<div style="background:rgba(255,255,255,.06); border-radius:6px; padding:3px 8px;
|
||||
<div style="background:#f0f6ff; border:1px solid #d0e4f5; border-radius:6px; padding:3px 8px;
|
||||
font-size:11px; display:flex; align-items:center; gap:4px;"
|
||||
:title="h.result_label + ' ' + (h.payout > 0 ? '+' : '') + h.payout">
|
||||
<span x-text="h.emojis.join('')"></span>
|
||||
<span :style="h.payout > 0 ? 'color:#4ade80' : 'color:#f87171'"
|
||||
<span :style="h.payout > 0 ? 'color:#16a34a; font-weight:bold;' : 'color:#dc2626;'"
|
||||
x-text="(h.payout > 0 ? '+' : '') + h.payout"></span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -208,13 +229,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ─── 底部关闭 ─── --}}
|
||||
<div style="background:rgba(15,8,0,.95); padding:8px 20px; display:flex; justify-content:center;">
|
||||
{{-- \u2500\u2500\u2500 \u5e95\u90e8\u5173\u95ed \u2500\u2500\u2500 --}}
|
||||
<div
|
||||
style="background:#fff; border-top:1px solid #d0e4f5; padding:14px 16px; display:flex; justify-content:center;">
|
||||
<button x-on:click="close()"
|
||||
style="padding:6px 28px; background:rgba(255,255,255,.06); border:none; border-radius:20px;
|
||||
font-size:12px; color:rgba(255,255,255,.4); cursor:pointer; transition:all .15s;"
|
||||
onmouseover="this.style.background='rgba(255,255,255,.12)'"
|
||||
onmouseout="this.style.background='rgba(255,255,255,.06)'">
|
||||
style="padding:10px 48px; min-width:140px; background:#f0f6ff; border:1px solid #b0d0ee; border-radius:12px;
|
||||
font-size:14px; font-weight:bold; color:#336699; cursor:pointer; transition:all .15s;"
|
||||
onmouseover="this.style.background='#ddeeff'" onmouseout="this.style.background='#f0f6ff'">
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
@@ -270,17 +291,19 @@
|
||||
const STORAGE_KEY = 'slot_fab_pos';
|
||||
const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || 'null');
|
||||
return {
|
||||
visible: false,
|
||||
posX: saved?.x ?? 18,
|
||||
posY: saved?.y ?? 150,
|
||||
visible: false,
|
||||
posX: saved?.x ?? 18,
|
||||
posY: saved?.y ?? 150,
|
||||
dragging: false,
|
||||
_startX: 0, _startY: 0,
|
||||
_origX: 0, _origY: 0,
|
||||
_moved: 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 {}
|
||||
@@ -288,11 +311,11 @@
|
||||
|
||||
startDrag(e) {
|
||||
this.dragging = true;
|
||||
this._moved = false;
|
||||
this._startX = e.clientX;
|
||||
this._startY = e.clientY;
|
||||
this._origX = this.posX;
|
||||
this._origY = this.posY;
|
||||
this._moved = false;
|
||||
this._startX = e.clientX;
|
||||
this._startY = e.clientY;
|
||||
this._origX = this.posX;
|
||||
this._origY = this.posY;
|
||||
e.currentTarget.setPointerCapture?.(e.pointerId);
|
||||
},
|
||||
|
||||
@@ -301,14 +324,17 @@
|
||||
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.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 }));
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify({
|
||||
x: this.posX,
|
||||
y: this.posY
|
||||
}));
|
||||
if (!this._moved) this.openPanel();
|
||||
},
|
||||
|
||||
@@ -355,6 +381,18 @@
|
||||
_spinInterval: null,
|
||||
_stopTimers: [],
|
||||
|
||||
/**
|
||||
* Alpine 初始化: 监听 show 变化自动加载数据(解决从游戏大厅入口不调用 open() 时历史不刷新的问题)
|
||||
*/
|
||||
init() {
|
||||
this.$watch('show', async (val) => {
|
||||
if (val) {
|
||||
await this.loadInfo();
|
||||
await this.loadHistory();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 打开面板并加载数据
|
||||
*/
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="chat-toolbar" id="toolbar-strip">
|
||||
<div class="tool-btn" onclick="openShopModal()" title="购买道具">商店</div>
|
||||
<div class="tool-btn" onclick="saveExp()" title="手动存经验点">存点</div>
|
||||
<div class="tool-btn" onclick="alert('🚧 娱乐功能开发中,敬请期待!')" title="娱乐(待开发)">娱乐</div>
|
||||
<div class="tool-btn" onclick="openGameHall()" title="娱乐游戏大厅">娱乐</div>
|
||||
<div class="tool-btn" onclick="alert('🚧 银行功能开发中,敬请期待!')" title="银行(待开发)">银行</div>
|
||||
<div class="tool-btn" onclick="openMarriageStatusModal()" title="婚姻状态">婚姻</div>
|
||||
<div class="tool-btn" onclick="openFriendPanel()" title="好友列表">好友</div>
|
||||
|
||||
@@ -193,7 +193,8 @@
|
||||
});
|
||||
if (cfgRes.ok) divorceConfig = await cfgRes.json();
|
||||
} catch (e) {
|
||||
/* 网络异常则使用默认值 */ }
|
||||
/* 网络异常则使用默认值 */
|
||||
}
|
||||
|
||||
// 打开专属离婚确认弹窗
|
||||
const modal = document.getElementById('divorce-confirm-modal');
|
||||
@@ -721,7 +722,7 @@
|
||||
{{-- 操作按钮区:加好友 + 送礼物 + 送金币(有职务且有奖励权限时显示) --}}
|
||||
<div x-data="{ showGiftPanel: false, showRewardPanel: false }" x-show="userInfo.username !== window.chatContext.username">
|
||||
|
||||
<div class="modal-actions" style="margin-bottom: 0; display: flex; gap: 6px; flex-wrap: wrap;">
|
||||
<div class="modal-actions" style="margin-bottom: 0; display: flex; gap: 6px;">
|
||||
{{-- 加好友 / 删好友 --}}
|
||||
<button x-on:click="toggleFriend()" :disabled="friendLoading"
|
||||
:style="is_friend
|
||||
@@ -730,7 +731,7 @@
|
||||
'background: linear-gradient(135deg,#16a34a,#22c55e); color:#fff; border:none;'"
|
||||
style="flex:1; padding: 7px 10px; border-radius: 5px; font-size: 12px;
|
||||
cursor: pointer; font-weight: bold; transition: opacity .15s;"
|
||||
x-text="friendLoading ? '处理中…' : (is_friend ? '✅ 已是好友' : '➕ 加好友')"></button>
|
||||
x-text="friendLoading ? '处理中…' : (is_friend ? '✅ 好友' : '➕ 加好友')"></button>
|
||||
|
||||
{{-- 送礼物按钮 --}}
|
||||
<button class="btn-whisper" style="flex:1;"
|
||||
@@ -774,11 +775,11 @@
|
||||
|
||||
{{-- 对方已婚时显示提示(非伴侣) --}}
|
||||
<div x-show="!marriageLoading && targetMarriage && targetMarriage.status === 'married' && !targetMarriage.is_my_partner"
|
||||
:title="'与 ' + (targetMarriage?.partner_name || '—') + ' 已婚'"
|
||||
style="flex:1; display:flex; align-items:center; justify-content:center;
|
||||
padding:7px 10px; border-radius:5px; font-size:11px; background:#fff1f2;
|
||||
border:1px solid #fecdd3; color:#f43f5e; font-weight:bold;">
|
||||
💑 <span x-text="'与 ' + (targetMarriage?.partner_name || '—') + ' 已婚'"
|
||||
style="margin-left:3px;"></span>
|
||||
padding:7px 10px; border-radius:5px; font-size:12px; background:#fff1f2;
|
||||
border:1px solid #fecdd3; color:#f43f5e; font-weight:bold; white-space:nowrap;">
|
||||
💑 已婚
|
||||
</div>
|
||||
|
||||
{{-- 如果对方是自己的伴侣,显示离婚按钮 --}}
|
||||
|
||||
Reference in New Issue
Block a user