diff --git a/resources/js/chat-room.js b/resources/js/chat-room.js
index 76a5df4..541519e 100644
--- a/resources/js/chat-room.js
+++ b/resources/js/chat-room.js
@@ -36,7 +36,7 @@
* - profile-controls.js:处理用户资料和资料相关按钮。
* - shop-controls.js:处理商店弹窗的基础按钮事件。
* - slot-machine.js:提供老虎机 slotPanel/slotFab Alpine 组件。
- * - vip-controls.js:处理 VIP 中心相关入口。
+ * - vip-controls.js:处理 VIP 中心弹窗、会员数据渲染、支付跳转和专属进退场设置。
* - preferences-status.js:处理聊天偏好、屏蔽系统播报和静音状态。
* - right-panel.js:处理右侧在线用户列表和用户名交互。
* - rooms.js:处理房间在线状态渲染和跳转 URL。
@@ -111,7 +111,7 @@ export { bindFortunePanelControls, fortunePanel } from "./chat-room/fortune-pane
export { bindProfileControls } from "./chat-room/profile-controls.js";
export { bindShopControls } from "./chat-room/shop-controls.js";
export { bindSlotMachineControls, slotFab, slotPanel } from "./chat-room/slot-machine.js";
-export { bindVipControls } from "./chat-room/vip-controls.js";
+export { bindVipControls, buyVip, closeVipModal, openVipModal, saveVipPresenceSettings, switchVipTab } from "./chat-room/vip-controls.js";
export {
BLOCKABLE_SYSTEM_SENDERS,
BLOCKED_SYSTEM_SENDERS_STORAGE_KEY,
@@ -205,7 +205,7 @@ import { bindFortunePanelControls, fortunePanel } from "./chat-room/fortune-pane
import { bindProfileControls } from "./chat-room/profile-controls.js";
import { bindShopControls } from "./chat-room/shop-controls.js";
import { bindSlotMachineControls, slotFab, slotPanel } from "./chat-room/slot-machine.js";
-import { bindVipControls } from "./chat-room/vip-controls.js";
+import { bindVipControls, buyVip, closeVipModal, openVipModal, saveVipPresenceSettings, switchVipTab } from "./chat-room/vip-controls.js";
import {
BLOCKABLE_SYSTEM_SENDERS,
BLOCKED_SYSTEM_SENDERS_STORAGE_KEY,
@@ -323,6 +323,11 @@ if (typeof window !== "undefined") {
slotFab,
slotPanel,
bindVipControls,
+ buyVip,
+ closeVipModal,
+ openVipModal,
+ saveVipPresenceSettings,
+ switchVipTab,
CHAT_FONT_SIZE_STORAGE_KEY,
restoreChatFontSize,
closeChatImageLightbox,
@@ -403,6 +408,11 @@ if (typeof window !== "undefined") {
window.openLotteryPanel = openLotteryPanel;
window.openBankModal = openBankModal;
window.showLotteryMsg = showLotteryMsg;
+ window.buyVip = buyVip;
+ window.closeVipModal = closeVipModal;
+ window.openVipModal = openVipModal;
+ window.saveVipPresenceSettings = saveVipPresenceSettings;
+ window.switchVipTab = switchVipTab;
window.switchBankTab = switchBankTab;
window.toggleBankRankSort = toggleBankRankSort;
window.applyFontSize = applyFontSize;
diff --git a/resources/js/chat-room/vip-controls.js b/resources/js/chat-room/vip-controls.js
index 1d4acd5..61b7808 100644
--- a/resources/js/chat-room/vip-controls.js
+++ b/resources/js/chat-room/vip-controls.js
@@ -1,27 +1,433 @@
-// 会员中心基础按钮事件绑定,替代 toolbar VIP 区域内联 onclick。
+// 聊天室 VIP 中心模块,负责弹窗开关、会员数据渲染、支付跳转和专属进退场设置。
+
+import { escapeHtml } from "./html.js";
+
+const VIP_CENTER_URL = "/vip-center";
+const VIP_PAYMENT_URL = "/vip/payment";
+const VIP_PRESENCE_UPDATE_URL = "/vip-center/presence-settings";
let vipControlEventsBound = false;
+let vipData = null;
/**
- * 调用会员中心存量全局函数。
+ * 读取 CSRF Token。
*
- * @param {string} functionName 全局函数名
- * @param {...unknown} args 参数
+ * @returns {string}
+ */
+function csrf() {
+ return document.querySelector('meta[name="csrf-token"]')?.content || "";
+}
+
+/**
+ * 按 ID 获取 DOM 节点。
+ *
+ * @param {string} id
+ * @returns {HTMLElement|null}
+ */
+function element(id) {
+ return document.getElementById(id);
+}
+
+/**
+ * 显示全局弹窗提示。
+ *
+ * @param {string} message
+ * @param {string} title
+ * @param {string} color
* @returns {void}
*/
-function callVipGlobal(functionName, ...args) {
- // VIP 业务函数暂留在 Blade 内,当前模块只统一按钮事件与旧函数调用边界。
- if (typeof window[functionName] === "function") {
- window[functionName](...args);
+function showDialog(message, title = "提示", color = "#3b82f6") {
+ window.chatDialog?.alert?.(message, title, color);
+}
+
+/**
+ * 格式化金额。
+ *
+ * @param {unknown} value
+ * @returns {string}
+ */
+function formatMoney(value) {
+ return Number(value || 0).toFixed(2);
+}
+
+/**
+ * 过滤可写入 style 的颜色值。
+ *
+ * @param {unknown} value
+ * @param {string} fallback
+ * @returns {string}
+ */
+function sanitizeColor(value, fallback = "#1e293b") {
+ const color = String(value || "").trim();
+
+ if (/^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/.test(color) || /^rgba?\([\d\s,.%]+\)$/.test(color)) {
+ return color;
+ }
+
+ return fallback;
+}
+
+/**
+ * 生成会员等级按钮 HTML。
+ *
+ * @param {object} level
+ * @param {object} data
+ * @returns {string}
+ */
+function renderVipAction(level, data) {
+ const isCurrent = Boolean(level.is_current);
+ const isHigher = Boolean(level.is_higher);
+ const isLower = Boolean(level.is_lower);
+ let buttonText = "立即购买";
+ let buttonColor = "#1e293b";
+ let buttonTextColor = "#fff";
+ let priceToDisplay = level.price;
+ let isDisabled = !data.vipPaymentEnabled;
+ let showUpgradeInfo = false;
+
+ if (isCurrent) {
+ buttonText = "立即续费";
+ buttonColor = "#f59e0b";
+ } else if (isHigher && data.user.is_vip) {
+ buttonText = "补差价升级";
+ buttonColor = "#4f46e5";
+ priceToDisplay = level.upgrade_price;
+ showUpgradeInfo = true;
+ } else if (isLower) {
+ buttonText = "无法降级";
+ buttonColor = "#f1f5f9";
+ buttonTextColor = "#94a3b8";
+ isDisabled = true;
+ }
+
+ const upgradeInfo = showUpgradeInfo
+ ? `
已省 ¥${formatMoney(Number(level.price || 0) - Number(level.upgrade_price || 0))}
`
+ : "";
+ const actionHtml = !data.vipPaymentEnabled || isDisabled
+ ? ``
+ : `
+
+
+
+ ${buttonText}后将跳转到对应支付页面
`;
+
+ return `
+
+ ¥${formatMoney(priceToDisplay)}
+ / ${Number(level.duration_days || 0)}天
+
+ ${upgradeInfo}
+ ${actionHtml}
+ `;
+}
+
+/**
+ * 渲染会员等级卡片。
+ *
+ * @param {object} level
+ * @param {object} data
+ * @returns {string}
+ */
+function renderVipLevelCard(level, data) {
+ const isCurrent = Boolean(level.is_current);
+ const color = sanitizeColor(level.color);
+
+ return `
+
+ ${isCurrent ? '
当前档位' : ""}
+
+ ${escapeHtml(level.icon || "✨")}
+ ${escapeHtml(level.name || "")}
+
+
${escapeHtml(level.description || "")}
+
+
✓ 经验获取 ${Number(level.exp_multiplier || 1)}x
+
✓ 金币获取 ${Number(level.jjb_multiplier || 1)}x
+
✓ 专属入场特效 & 横幅
+
+
+ ${renderVipAction(level, data)}
+
+
+ `;
+}
+
+/**
+ * 渲染 VIP 购买记录。
+ *
+ * @param {Array