From 6f779edb91961f143911e5a3a538abe58d12b5f1 Mon Sep 17 00:00:00 2001 From: lkddi Date: Sat, 25 Apr 2026 19:00:41 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=81=E7=A7=BB=E5=A5=96=E5=8A=B1=E9=87=91?= =?UTF-8?q?=E5=B8=81=E5=BC=B9=E7=AA=97=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/js/chat-room.js | 6 +- resources/js/chat-room/reward-modal.js | 217 +++++++++++++++++- resources/js/chat-room/user-card.js | 53 +++++ .../chat/partials/user-actions.blade.php | 117 +--------- 4 files changed, 278 insertions(+), 115 deletions(-) diff --git a/resources/js/chat-room.js b/resources/js/chat-room.js index a43765d..e4a453a 100644 --- a/resources/js/chat-room.js +++ b/resources/js/chat-room.js @@ -216,7 +216,7 @@ export { renderRoomsOnlineStatusToContainer, resolveRoomUrl, } from "./chat-room/rooms.js"; -export { bindRewardModalControls, openRewardModal } from "./chat-room/reward-modal.js"; +export { bindRewardModalControls, openRewardModal, rewardModal } from "./chat-room/reward-modal.js"; export { bindRedPacketPanelControls, claimRedPacket, @@ -387,7 +387,7 @@ import { renderRoomsOnlineStatusToContainer, resolveRoomUrl, } from "./chat-room/rooms.js"; -import { bindRewardModalControls, openRewardModal } from "./chat-room/reward-modal.js"; +import { bindRewardModalControls, openRewardModal, rewardModal } from "./chat-room/reward-modal.js"; import { bindRedPacketPanelControls, claimRedPacket, @@ -594,6 +594,7 @@ if (typeof window !== "undefined") { resolveRoomUrl, bindRewardModalControls, openRewardModal, + rewardModal, bindRedPacketPanelControls, claimRedPacket, closeRedPacketModal, @@ -618,6 +619,7 @@ if (typeof window !== "undefined") { window.openMobileDrawer = openMobileDrawer; window.openUserCard = openUserCard; window.openRewardModal = openRewardModal; + window.rewardModal = rewardModal; window.renderMobileRoomList = renderMobileRoomList; window.renderMobileUserList = renderMobileUserList; window.scheduleRenderMobileUserList = scheduleRenderMobileUserList; diff --git a/resources/js/chat-room/reward-modal.js b/resources/js/chat-room/reward-modal.js index fe62bcf..6153f50 100644 --- a/resources/js/chat-room/reward-modal.js +++ b/resources/js/chat-room/reward-modal.js @@ -1,4 +1,218 @@ -// 聊天室职务奖励金币弹窗入口,提供 openRewardModal 全局兼容函数。 +// 聊天室职务奖励金币弹窗组件,负责额度查询、发放提交和全局打开入口。 + +const EMPTY_REWARD_QUOTA = { + max_once: null, + daily_limit: null, + today_sent: 0, + daily_remaining: null, + recent_rewards: [], +}; + +/** + * 生成新的空额度对象,避免多个弹窗状态共用同一引用。 + * + * @returns {Record} + */ +function emptyRewardQuota() { + return { + ...EMPTY_REWARD_QUOTA, + recent_rewards: [], + }; +} + +/** + * 读取 CSRF Token。 + * + * @returns {string} + */ +function csrfToken() { + return document.querySelector('meta[name="csrf-token"]')?.content || ""; +} + +/** + * 格式化本地时间为 MM-DD HH:mm,作为刚发放记录的临时展示时间。 + * + * @returns {string} + */ +function currentRewardTimestamp() { + const now = new Date(); + const month = String(now.getMonth() + 1).padStart(2, "0"); + const day = String(now.getDate()).padStart(2, "0"); + const hour = String(now.getHours()).padStart(2, "0"); + const minute = String(now.getMinutes()).padStart(2, "0"); + + return `${month}-${day} ${hour}:${minute}`; +} + +/** + * 创建职务奖励金币弹窗 Alpine 组件。 + * + * @returns {Record} + */ +export function rewardModal() { + return { + show: false, + targetUsername: "", + amount: "", + sending: false, + loading: false, + quota: emptyRewardQuota(), + + /** + * 格式化额度数值。 + * + * @param {number|null} value 额度值 + * @returns {string} + */ + fmt(value) { + if (value === null) { + return "不限"; + } + + if (value === 0) { + return "—"; + } + + return `${value.toLocaleString()} 金币`; + }, + + /** + * 关闭奖励弹窗并同步关闭用户名片弹窗。 + * + * @returns {void} + */ + closeRelatedModals() { + this.show = false; + + const userModalElement = document.getElementById("user-modal-container"); + if (!window.Alpine || !userModalElement) { + return; + } + + const userModalData = window.Alpine.$data(userModalElement); + if (userModalData) { + userModalData.showUserModal = false; + } + }, + + /** + * 打开奖励弹窗并加载当前额度。 + * + * @param {string} username 目标用户名 + * @returns {Promise} + */ + async open(username) { + this.targetUsername = username; + this.amount = ""; + this.sending = false; + this.loading = true; + this.show = true; + + try { + const response = await fetch(window.chatContext.rewardQuotaUrl, { + headers: { + Accept: "application/json", + }, + }); + this.quota = await response.json(); + if (!this.quota.recent_rewards) { + this.quota.recent_rewards = []; + } + } catch (error) { + this.quota = emptyRewardQuota(); + } + + this.loading = false; + }, + + /** + * 提交奖励金币发放。 + * + * @returns {Promise} + */ + async send() { + if (this.sending) { + return; + } + + const amount = Number.parseInt(this.amount, 10); + if (!amount || amount <= 0) { + window.chatDialog.alert("请输入有效金额", "提示", "#f59e0b"); + return; + } + + const maxOnce = window.chatContext?.myMaxReward; + if (maxOnce === 0) { + window.chatDialog.alert("你的职务没有奖励发放权限", "无权限", "#cc4444"); + return; + } + + if (maxOnce > 0 && amount > maxOnce) { + window.chatDialog.alert(`超出单次上限 ${maxOnce} 金币`, "超出上限", "#cc4444"); + return; + } + + this.sending = true; + + try { + const response = await fetch(window.chatContext.rewardUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-CSRF-TOKEN": csrfToken(), + Accept: "application/json", + }, + body: JSON.stringify({ + username: this.targetUsername, + room_id: window.chatContext.roomId, + amount, + }), + }); + const data = await response.json(); + + if (data.status === "success") { + this.recordRewardSuccess(amount); + this.closeRelatedModals(); + window.chatDialog.alert(data.message, "🎉 奖励发放成功", "#d97706"); + return; + } + + window.chatDialog.alert(data.message || "发放失败", "操作失败", "#cc4444"); + } catch (error) { + window.chatDialog.alert("网络异常,请稍后重试", "错误", "#cc4444"); + } + + this.sending = false; + }, + + /** + * 本地更新额度和最近发放记录。 + * + * @param {number} amount 发放金额 + * @returns {void} + */ + recordRewardSuccess(amount) { + this.quota.today_sent += amount; + + if (this.quota.daily_remaining !== null) { + this.quota.daily_remaining = Math.max(0, this.quota.daily_remaining - amount); + } + + this.quota.recent_rewards.unshift({ + target: this.targetUsername, + amount, + created_at: currentRewardTimestamp(), + }); + + if (this.quota.recent_rewards.length > 10) { + this.quota.recent_rewards.pop(); + } + + this.amount = ""; + this.sending = false; + }, + }; +} /** * 打开奖励金币独立弹窗。 @@ -25,5 +239,6 @@ export function bindRewardModalControls() { return; } + window.rewardModal = rewardModal; window.openRewardModal = openRewardModal; } diff --git a/resources/js/chat-room/user-card.js b/resources/js/chat-room/user-card.js index 68f370f..3cdf177 100644 --- a/resources/js/chat-room/user-card.js +++ b/resources/js/chat-room/user-card.js @@ -47,6 +47,10 @@ export function userCardComponent() { selectedGiftId: giftDefaults.defaultGiftId, giftCount: 1, sendingGift: false, + showGiftPanel: false, + showGiftGoldPanel: false, + giftGoldAmount: "", + giftGoldSending: false, // 职务奖励金币 rewardAmount: 0, @@ -518,6 +522,55 @@ export function userCardComponent() { this.sendingGift = false; }, + /** 切换礼物面板,和赠送金币面板互斥显示 */ + toggleGiftPanel() { + this.showGiftPanel = !this.showGiftPanel; + this.showGiftGoldPanel = false; + }, + + /** 切换赠送金币面板,和礼物面板互斥显示 */ + toggleGiftGoldPanel() { + this.showGiftGoldPanel = !this.showGiftGoldPanel; + this.showGiftPanel = false; + }, + + /** 给用户赠送自己的金币 */ + async sendGiftGold() { + const amount = Number.parseInt(this.giftGoldAmount, 10); + if (this.giftGoldSending || !amount || amount <= 0) { + return; + } + + this.giftGoldSending = true; + + try { + const response = await fetch("/gift/gold", { + method: "POST", + headers: this._headers(), + body: JSON.stringify({ + to_user: this.userInfo.username, + room_id: window.chatContext.roomId, + amount, + }), + }); + const data = await response.json(); + + if (data.status === "success") { + window.chatContext.myGold = data.data?.my_jjb ?? window.chatContext.myGold; + this.showGiftGoldPanel = false; + this.giftGoldAmount = ""; + this.$alert(data.message, "赠送成功 💝", "#d97706"); + return; + } + + this.$alert(data.message, "赠送失败", "#cc4444"); + } catch (error) { + this.$alert("网络异常", "错误", "#cc4444"); + } + + this.giftGoldSending = false; + }, + /** 职务奖励:向用户发放金币(凭空产生,记入履职记录) */ async sendReward() { if (this.sendingReward) return; diff --git a/resources/views/chat/partials/user-actions.blade.php b/resources/views/chat/partials/user-actions.blade.php index 54a619e..ec0a34e 100644 --- a/resources/views/chat/partials/user-actions.blade.php +++ b/resources/views/chat/partials/user-actions.blade.php @@ -249,8 +249,7 @@ {{-- 操作按钮区:加好友 + 送礼物 + 送金币(有职务且有奖励权限时显示) --}} -
+