Files
chatroom/resources/views/chat/partials/shop-panel.blade.php
lkddi 040dbdef3c 优化:全站金币图标由 🪙(银灰色)统一替换为 💰(金黄色)
🪙 在多数平台/字体上渲染为银灰色,与「金币」语义不符;
💰 各平台均渲染为金黄色,更直观传达金币概念。

涉及文件(43处):
- app/Jobs:百家乐、赛马结算广播
- app/Http/Controllers:管理员命令、红包、老虎机、神秘箱子
- app/Listeners
- resources/views:聊天室各游戏面板、商店、toolbar、后台页面等
2026-03-04 15:00:02 +08:00

552 lines
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{--
文件功能:商店面板视图(嵌入聊天室右侧)
展示单次卡、周卡、改名卡,支持购买和改名操作
采用紧凑卡片设计,适配窄侧边栏
--}}
<style>
/* ── 商店面板样式 ──────────────────────────────── */
#shop-panel {
display: none;
position: absolute;
/* 顶部 tab 栏高度约 26px底部状态栏约 22px */
top: 26px;
left: 0;
right: 0;
bottom: 22px;
flex-direction: column;
background: #0f0c29;
z-index: 10;
}
#shop-balance-bar {
padding: 6px 8px;
background: linear-gradient(135deg, #1e1b4b, #312e81);
border-bottom: 1px solid #4f46e5;
font-size: 11px;
color: #a5b4fc;
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
}
#shop-jjb {
color: #fbbf24;
font-weight: 800;
font-size: 12px;
}
#shop-week-badge {
display: none;
margin-left: auto;
font-size: 10px;
background: #4338ca;
padding: 1px 5px;
border-radius: 10px;
color: #c7d2fe;
white-space: nowrap;
}
#shop-toast {
display: none;
margin: 4px 6px;
padding: 5px 8px;
border-radius: 6px;
font-size: 11px;
font-weight: bold;
flex-shrink: 0;
}
#shop-items-list {
flex: 1;
overflow-y: auto;
padding: 6px 5px;
scrollbar-width: thin;
scrollbar-color: #4338ca #0f0c29;
}
/* 分组标题 */
.shop-group-label {
font-size: 10px;
font-weight: 800;
color: #818cf8;
letter-spacing: .5px;
text-transform: uppercase;
padding: 4px 2px 3px;
border-bottom: 1px solid #312e81;
margin-bottom: 5px;
}
.shop-group-desc {
font-size: 9px;
color: #6b7280;
margin-bottom: 5px;
line-height: 1.3;
}
/* 商品卡片 */
.shop-card {
background: linear-gradient(135deg, #1e1b4b 60%, #2d2a6e);
border: 1px solid #3730a3;
border-radius: 8px;
padding: 7px 8px;
margin-bottom: 5px;
transition: border-color .2s, box-shadow .2s;
cursor: default;
}
.shop-card:hover {
border-color: #6366f1;
box-shadow: 0 0 8px rgba(99, 102, 241, .35);
}
.shop-card-row {
display: flex;
align-items: center;
gap: 5px;
}
.shop-card-icon {
font-size: 18px;
flex-shrink: 0;
width: 26px;
text-align: center;
line-height: 1;
}
.shop-card-name {
font-size: 11px;
font-weight: 700;
color: #e0e7ff;
flex: 1;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.shop-card-desc {
font-size: 9px;
color: #6b7280;
margin-top: 3px;
line-height: 1.35;
}
/* 购买按钮 */
.shop-btn {
display: inline-flex;
align-items: center;
gap: 2px;
background: linear-gradient(135deg, #4f46e5, #7c3aed);
color: #fff;
border: none;
border-radius: 5px;
padding: 3px 7px;
cursor: pointer;
font-size: 10px;
font-weight: 800;
white-space: nowrap;
flex-shrink: 0;
transition: opacity .15s, transform .1s;
}
.shop-btn:hover {
opacity: .85;
transform: scale(1.04);
}
.shop-btn-use {
background: linear-gradient(135deg, #7c3aed, #a855f7);
}
/* 改名弹框 */
#rename-modal {
display: none;
position: absolute;
inset: 0;
background: rgba(0, 0, 0, .75);
z-index: 300;
align-items: center;
justify-content: center;
}
#rename-modal-inner {
background: linear-gradient(160deg, #1e1b4b, #1a1060);
border: 1px solid #6366f1;
border-radius: 10px;
padding: 14px 12px;
width: 190px;
box-shadow: 0 8px 30px rgba(99, 102, 241, .4);
}
#rename-modal-title {
font-size: 12px;
font-weight: 800;
color: #c7d2fe;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 4px;
}
#rename-input {
width: 100%;
background: #312e81;
border: 1px solid #4f46e5;
border-radius: 5px;
padding: 5px 7px;
color: #e0e7ff;
font-size: 12px;
box-sizing: border-box;
margin-bottom: 8px;
outline: none;
}
#rename-input:focus {
border-color: #818cf8;
}
.rename-btn-row {
display: flex;
gap: 6px;
}
#rename-confirm {
flex: 1;
background: linear-gradient(135deg, #4f46e5, #7c3aed);
color: #fff;
border: none;
border-radius: 5px;
padding: 5px;
cursor: pointer;
font-size: 11px;
font-weight: 700;
}
#rename-cancel {
flex: 1;
background: #374151;
color: #9ca3af;
border: none;
border-radius: 5px;
padding: 5px;
cursor: pointer;
font-size: 11px;
}
#rename-err {
color: #f87171;
font-size: 10px;
margin-top: 5px;
min-height: 14px;
}
</style>
<div id="shop-panel">
{{-- 余额栏 --}}
<div id="shop-balance-bar">
💰 <span id="shop-jjb">--</span> 金币
<span id="shop-week-badge"></span>
</div>
{{-- Toast --}}
<div id="shop-toast"></div>
{{-- 商品列表 --}}
<div id="shop-items-list">
<div style="text-align:center;color:#6366f1;padding:20px 0;font-size:11px;">加载中…</div>
</div>
{{-- 改名弹框 --}}
<div id="rename-modal">
<div id="rename-modal-inner">
<div id="rename-modal-title">🎭 使用改名卡</div>
<input id="rename-input" type="text" maxlength="10" placeholder="输入新昵称1-10字">
<div class="rename-btn-row">
<button id="rename-confirm" onclick="submitRename()">确认改名</button>
<button id="rename-cancel" onclick="closeRenameModal()">取消</button>
</div>
<div id="rename-err"></div>
</div>
</div>
</div>
<script>
/**
* 商店面板前端逻辑
*/
(function() {
let shopLoaded = false;
/** 打开商店 Tab 时调用 */
window.loadShop = function() {
if (shopLoaded) return;
shopLoaded = true;
fetchShopData();
};
/** 拉取商品数据 */
function fetchShopData() {
fetch('{{ route('shop.items') }}', {
headers: {
'Accept': 'application/json',
'X-CSRF-TOKEN': _csrf()
}
})
.then(r => r.json())
.then(data => renderShop(data))
.catch(() => showShopToast('⚠ 加载失败,请刷新重试', false));
}
/** 渲染商品列表 */
function renderShop(data) {
// 更新金币
document.getElementById('shop-jjb').textContent = Number(data.user_jjb).toLocaleString();
// 周卡状态徽章
const badge = document.getElementById('shop-week-badge');
if (data.active_week_effect) {
const icons = {
fireworks: '🎆',
rain: '🌧',
lightning: '⚡',
snow: '❄️'
};
badge.textContent = (icons[data.active_week_effect] ?? '') + ' 周卡生效中';
badge.style.display = 'inline-block';
}
const ringCounts = data.ring_counts || {};
const groups = [{
label: '⚡ 单次特效卡',
desc: '立即播放一次,仅自己可见',
type: 'instant'
},
{
label: '📅 周卡・7天登录自动播放',
desc: '同时只能激活一种,购新旧失效无退款',
type: 'duration'
},
{
label: '💍 求婚戒指',
desc: '购买后存入背包,求婚时消耗(若被拒绝则遗失)',
type: 'ring'
},
{
label: '🎭 道具',
desc: '',
type: 'one_time'
},
];
const itemsEl = document.getElementById('shop-items-list');
itemsEl.innerHTML = '';
groups.forEach(g => {
const items = data.items.filter(i => i.type === g.type);
if (!items.length) return;
const section = document.createElement('div');
section.style.marginBottom = '10px';
// 分组标题
const label = document.createElement('div');
label.className = 'shop-group-label';
label.textContent = g.label;
section.appendChild(label);
if (g.desc) {
const desc = document.createElement('div');
desc.className = 'shop-group-desc';
desc.textContent = g.desc;
section.appendChild(desc);
}
items.forEach(item => {
const isRename = item.slug === 'rename_card';
const canUseRename = isRename && data.has_rename_card;
const isRing = item.type === 'ring';
const ownedQty = isRing ? (ringCounts[item.id] || 0) : 0;
const card = document.createElement('div');
card.className = 'shop-card';
// 行:图标 + 名称 + 按钮
const row = document.createElement('div');
row.className = 'shop-card-row';
// 图标区(戒指加持有数徽标)
const iconWrap = document.createElement('span');
iconWrap.style.cssText =
'position:relative; flex-shrink:0; width:28px; text-align:center;';
const icon = document.createElement('span');
icon.className = 'shop-card-icon';
icon.textContent = item.icon;
iconWrap.appendChild(icon);
if (isRing && ownedQty > 0) {
const badge = document.createElement('span');
badge.style.cssText =
'position:absolute; top:-4px; right:-4px; background:#f43f5e; color:#fff; font-size:8px; font-weight:800; min-width:14px; height:14px; border-radius:7px; text-align:center; line-height:14px; padding:0 2px;';
badge.textContent = ownedQty;
iconWrap.appendChild(badge);
}
row.appendChild(iconWrap);
// 名称
const name = document.createElement('span');
name.className = 'shop-card-name';
name.title = item.name;
name.textContent = item.name;
row.appendChild(name);
// 按钮
const btn = document.createElement('button');
if (canUseRename) {
btn.className = 'shop-btn shop-btn-use';
btn.textContent = '使用';
btn.onclick = openRenameModal;
} else {
btn.className = 'shop-btn';
btn.innerHTML = `💰 ${Number(item.price).toLocaleString()}`;
btn.onclick = () => buyItem(item.id, item.name, item.price);
}
row.appendChild(btn);
card.appendChild(row);
// 简介
if (item.description) {
const desc = document.createElement('div');
desc.className = 'shop-card-desc';
desc.textContent = item.description;
card.appendChild(desc);
}
// 戒指:加成信息行
if (isRing && (item.intimacy_bonus > 0 || item.charm_bonus > 0)) {
const bonus = document.createElement('div');
bonus.style.cssText =
'font-size:9px; color:#f43f5e; margin-top:3px; display:flex; gap:8px;';
if (item.intimacy_bonus > 0) {
const b1 = document.createElement('span');
b1.textContent = `💞 亲密 +${item.intimacy_bonus}`;
bonus.appendChild(b1);
}
if (item.charm_bonus > 0) {
const b2 = document.createElement('span');
b2.style.color = '#a855f7';
b2.textContent = `✨ 魅力 +${item.charm_bonus}`;
bonus.appendChild(b2);
}
card.appendChild(bonus);
}
section.appendChild(card);
});
itemsEl.appendChild(section);
});
}
/** 购买商品 */
window.buyItem = function(itemId, name, price) {
if (!confirm(`确定花费 ${Number(price).toLocaleString()} 金币购买【${name}】吗?`)) return;
fetch('{{ route('shop.buy') }}', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-TOKEN': _csrf()
},
body: JSON.stringify({
item_id: itemId
}),
})
.then(r => r.json())
.then(data => {
showShopToast(data.message, data.status === 'success');
if (data.status === 'success') {
if (data.jjb !== undefined)
document.getElementById('shop-jjb').textContent = Number(data.jjb)
.toLocaleString();
if (data.play_effect && window.EffectManager)
window.EffectManager.play(data.play_effect);
// 刷新商品状态
shopLoaded = false;
setTimeout(() => {
fetchShopData();
shopLoaded = true;
}, 800);
}
})
.catch(() => showShopToast('⚠ 网络异常,请重试', false));
};
/** Toast 通知 */
window.showShopToast = function(msg, ok) {
const el = document.getElementById('shop-toast');
el.textContent = msg;
el.style.display = 'block';
el.style.background = ok ? '#064e3b' : '#7f1d1d';
el.style.color = ok ? '#6ee7b7' : '#fca5a5';
clearTimeout(el._t);
el._t = setTimeout(() => {
el.style.display = 'none';
}, 3500);
};
/** 打开改名框 */
window.openRenameModal = function() {
const m = document.getElementById('rename-modal');
m.style.display = 'flex';
document.getElementById('rename-input').focus();
document.getElementById('rename-err').textContent = '';
};
/** 关闭改名框 */
window.closeRenameModal = function() {
document.getElementById('rename-modal').style.display = 'none';
};
/** 提交改名 */
window.submitRename = function() {
const newName = document.getElementById('rename-input').value.trim();
if (!newName) {
document.getElementById('rename-err').textContent = '请输入新昵称';
return;
}
fetch('{{ route('shop.rename') }}', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-TOKEN': _csrf()
},
body: JSON.stringify({
new_name: newName
}),
})
.then(r => r.json())
.then(data => {
if (data.status === 'success') {
closeRenameModal();
showShopToast(data.message, true);
shopLoaded = false;
setTimeout(() => window.location.reload(), 2000);
} else {
document.getElementById('rename-err').textContent = data.message;
}
})
.catch(() => {
document.getElementById('rename-err').textContent = '网络异常,请重试';
});
};
function _csrf() {
return document.querySelector('meta[name="csrf-token"]')?.content ?? '';
}
})();
</script>