Files
chatroom/resources/views/chat/partials/fortune-panel.blade.php
lkddi f45483bcba 功能更新与UI优化:游戏图标移除、用户名片修复、婚礼红包界面重设计
- 移除聊天室右下角浮动游戏图标(占卜、百家乐、赛马、老虎机)
- 用户名片按钮区:修复已婚/已好友时按钮换行问题,统一单行显示
- 婚礼红包弹窗:重设计为喜庆鲜红背景,领取按钮改为圆形米黄样式
- 新增婚礼红包恢复接口(/wedding/pending-envelopes),刷新后自动恢复领取按钮
- 修复 Alpine :style 字符串覆盖静态 style 导致圆形按钮失效的问题
- 撤职后用户等级改为根据经验值重新计算,不再无条件重置为1
- 管理员修改用户经验值后自动重算等级,有职务用户等级锁定
- 娱乐大厅钓鱼游戏按钮直接调用 startFishing() 简化操作流程
- 新增赛马、占卜、百家乐游戏及相关后端逻辑
2026-03-03 23:19:59 +08:00

378 lines
18 KiB
PHP

{{--
文件功能:神秘占卜前台弹窗组件
聊天室内神秘占卜面板:
- 点击悬浮 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">&times;</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>