新增赠送金币功能:任意用户可从自己余额赠送金币给他人,成功后聊天室系统传音广播;职务奖励金币移入管理区,删除管理区私信按钮

This commit is contained in:
2026-03-18 20:12:17 +08:00
parent c7063e02c2
commit 0ca028f73d
5 changed files with 137 additions and 9 deletions
+69
View File
@@ -1135,4 +1135,73 @@ class ChatController extends Controller
return $isLeveledUp;
}
/**
* 用户间赠送金币(任何登录用户均可调用)
*
* 从自己的余额中扣除指定金额,转入对方账户,
* 并在房间内通过「系统传音」广播一条赠送提示。
*/
public function giftGold(Request $request): JsonResponse
{
$request->validate([
'to_user' => 'required|string',
'room_id' => 'required|integer',
'amount' => 'required|integer|min:1|max:999999',
]);
$sender = Auth::user();
$toName = $request->input('to_user');
$roomId = $request->integer('room_id');
$amount = $request->integer('amount');
// 不能给自己转账
if ($toName === $sender->username) {
return response()->json(['status' => 'error', 'message' => '不能给自己赠送哦~']);
}
// 查目标用户
$receiver = User::where('username', $toName)->first();
if (! $receiver) {
return response()->json(['status' => 'error', 'message' => '用户不存在']);
}
// 余额校验
if (($sender->jjb ?? 0) < $amount) {
return response()->json([
'status' => 'error',
'message' => '金币不足!您当前余额 '.($sender->jjb ?? 0)." 金币,无法赠送 {$amount} 金币。",
]);
}
// 执行转账(直接操作字段,与 sendFlower 保持一致风格)
$sender->decrement('jjb', $amount);
$receiver->increment('jjb', $amount);
// 广播「系统传音」条目至全房间
$sysMsg = [
'id' => $this->chatState->nextMessageId($roomId),
'room_id' => $roomId,
'from_user' => '系统传音',
'to_user' => '大家',
'content' => "💝 【{$sender->username}】 向 【{$toName}】 赠送了 {$amount} 金币!",
'is_secret' => false,
'font_color' => '#b45309',
'action' => '',
'sent_at' => now()->toDateTimeString(),
];
$this->chatState->pushMessage($roomId, $sysMsg);
broadcast(new MessageSent($roomId, $sysMsg));
SaveMessageJob::dispatch($sysMsg);
return response()->json([
'status' => 'success',
'message' => "赠送成功!已向 {$toName} 赠送 {$amount} 金币。",
'data' => [
'my_jjb' => $sender->fresh()->jjb,
'target_jjb' => $receiver->fresh()->jjb,
],
]);
}
}
+2
View File
@@ -69,6 +69,8 @@
rewardUrl: "{{ route('command.reward') }}",
rewardQuotaUrl: "{{ route('command.reward_quota') }}",
userJjb: {{ (int) $user->jjb }}, // 当前用户金币(求婚前金额预检查用)
myGold: {{ (int) $user->jjb }}, // 赠金币面板显示余额用(赠送成功后前端更新)
// ─── 婚姻系统 ──────────────────────────────
minWeddingCost: {{ (int) \App\Models\WeddingTier::where('is_active', true)->orderBy('amount')->value('amount') ?? 0 }},
marriage: {
@@ -96,7 +96,8 @@
];
@endphp
@foreach ($welcomeMessages as $msg)
<div class="welcome-menu-item" onclick="sendWelcomeTpl({{ json_encode($msg) }})">{{ $msg }}</div>
<div class="welcome-menu-item" onclick="sendWelcomeTpl({{ json_encode($msg) }})">
{{ $msg }}</div>
@endforeach
</div>
</div>
@@ -145,8 +146,8 @@
{{-- 第二行:输入框 + 发送 --}}
<div class="input-row">
<input type="text" id="content" name="content" class="say-input" placeholder="在这里输入聊天内容,按 Enter 发送..."
autocomplete="off">
<input type="text" id="content" name="content" class="say-input"
placeholder="在这里输入聊天内容,按 Enter 发送..." autocomplete="off">
<button type="submit" id="send-btn" class="send-btn">发送</button>
</div>
</form>
@@ -759,7 +759,7 @@
</div>
{{-- 操作按钮区:加好友 + 送礼物 + 送金币(有职务且有奖励权限时显示) --}}
<div x-data="{ showGiftPanel: false, showRewardPanel: false }" x-show="userInfo.username !== window.chatContext.username">
<div x-data="{ showGiftPanel: false, showGiftGoldPanel: false, giftGoldAmount: '', giftGoldSending: false }" x-show="userInfo.username !== window.chatContext.username">
<div class="modal-actions" style="margin-bottom: 0; display: flex; gap: 6px;">
{{-- 加好友 / 删好友 --}}
@@ -774,16 +774,16 @@
{{-- 送礼物按钮 --}}
<button class="btn-whisper" style="flex:1;"
x-on:click="showGiftPanel = !showGiftPanel; showRewardPanel = false;">
x-on:click="showGiftPanel = !showGiftPanel; showGiftGoldPanel = false;">
🎁 送礼物
</button>
{{-- 送金币按钮:有在职职务且 max_reward 不为 00=禁止,-1=不限,正数=有上限) --}}
<button x-show="window.chatContext?.myMaxReward !== 0"
{{-- 送金币按钮:所有人均可使用,自己金币转给对方 --}}
<button
style="flex:1; padding: 7px 10px; border-radius: 5px; font-size: 12px; font-weight: bold; cursor: pointer;
background: linear-gradient(135deg,#f59e0b,#d97706); color:#fff; border:none;"
x-on:click="openRewardModal(userInfo.username)">
💰 金币
x-on:click="showGiftGoldPanel = !showGiftGoldPanel; showGiftPanel = false;">
💰 金币
</button>
{{-- 求婚按钮:对方未婚 + 双方均已设置性别 + 异性 --}}
@@ -831,6 +831,55 @@
</button>
</div>
{{-- 内联赠送金币面板 --}}
<div x-show="showGiftGoldPanel" x-transition
style="display: none; padding: 12px 16px; background: #fffbeb; border-top: 1px solid #fde68a;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;">
<span style="font-size:13px; color:#92400e; font-weight:bold;">💰 赠送金币给 <span x-text="userInfo.username"></span></span>
<button x-on:click="showGiftGoldPanel = false"
style="background:none; border:none; color:#92400e; cursor:pointer; font-size:18px; line-height:1; opacity:.6;">×</button>
</div>
<div style="font-size:11px; color:#b45309; margin-bottom:8px;">您当前余额:<b x-text="window.chatContext.myGold ?? '—'"></b> 金币</div>
<div style="display:flex; gap:8px; align-items:center;">
<input type="number" x-model.number="giftGoldAmount" min="1" max="999999"
placeholder="输入金额"
style="flex:1; height:36px; padding:0 10px; border:1px solid #fbbf24; border-radius:6px; font-size:13px; color:#334155;">
<button
x-on:click="
if (!giftGoldAmount || giftGoldAmount <= 0) return;
giftGoldSending = true;
fetch('/gift/gold', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
to_user: userInfo.username,
room_id: window.chatContext.roomId,
amount: giftGoldAmount
})
}).then(r => r.json()).then(d => {
if (d.status === 'success') {
window.chatContext.myGold = d.data?.my_jjb ?? window.chatContext.myGold;
showGiftGoldPanel = false;
giftGoldAmount = '';
$alert(d.message, '赠送成功 💝', '#d97706');
} else {
$alert(d.message, '赠送失败', '#cc4444');
}
}).catch(() => $alert('网络异常', '错误', '#cc4444'))
.finally(() => { giftGoldSending = false; });
"
:disabled="giftGoldSending"
style="height:36px; padding:0 16px; background:linear-gradient(135deg,#f59e0b,#d97706); color:#fff;
border:none; border-radius:6px; font-size:13px; font-weight:bold; cursor:pointer; white-space:nowrap;"
:style="giftGoldSending ? 'opacity:.6; cursor:not-allowed;' : ''"
x-text="giftGoldSending ? '赠送中…' : '💝 确认赠送'"></button>
</div>
</div>
{{-- 内联礼物面板 --}}
<div x-show="showGiftPanel" x-transition
style="display: none;
@@ -934,6 +983,12 @@
style="flex:1; padding: 5px; border-radius: 4px; font-size: 11px; background: #f3e8ff; border: 1px solid #a855f7; cursor: pointer;"
x-on:click="loadWhispers()">🔍 私信</button>
@endif
{{-- 职务奖励金币(凭空产生),仅有在职职务且 max_reward != 0 的人可见 --}}
@if (Auth::user()->activePosition || $myLevel >= $superLevel)
<button x-show="window.chatContext?.myMaxReward !== 0"
style="flex:1; padding: 5px; border-radius: 4px; font-size: 11px; background: #fef3c7; border: 1px solid #f59e0b; cursor: pointer;"
x-on:click="openRewardModal(userInfo.username)">💰 奖励金币</button>
@endif
</div>
</div>
@endif
+1
View File
@@ -270,6 +270,7 @@ Route::middleware(['chat.auth'])->group(function () {
// ---- 送花/礼物互动 ----
Route::post('/gift/flower', [ChatController::class, 'sendFlower'])->name('gift.flower');
Route::post('/gift/gold', [ChatController::class, 'giftGold'])->name('gift.gold');
// ---- 管理员命令(聊天室内实时操作)----
Route::post('/command/warn', [AdminCommandController::class, 'warn'])->name('command.warn');