// 聊天室职务奖励金币弹窗组件,负责额度查询、发放提交和全局打开入口。 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; }, }; } /** * 打开奖励金币独立弹窗。 * * @param {string} username * @returns {void} */ export function openRewardModal(username) { const container = document.getElementById("reward-modal-container"); if (!window.Alpine || !container) { return; } window.Alpine.$data(container)?.open?.(username); } /** * 暴露奖励金币弹窗入口给存量按钮调用。 * * @returns {void} */ export function bindRewardModalControls() { if (typeof window === "undefined") { return; } window.rewardModal = rewardModal; window.openRewardModal = openRewardModal; }