迁移会员中心脚本

This commit is contained in:
2026-04-25 14:40:54 +08:00
parent 8a690ac40d
commit dac8adfc5a
3 changed files with 441 additions and 309 deletions
+13 -3
View File
@@ -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;
+427 -18
View File
@@ -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
? `<div style="font-size:10px; color:#4f46e5; font-weight:bold; margin-bottom:8px;">已省 ¥${formatMoney(Number(level.price || 0) - Number(level.upgrade_price || 0))}</div>`
: "";
const actionHtml = !data.vipPaymentEnabled || isDisabled
? `<button disabled style="width:100%; border:none; border-radius:8px; padding:10px; font-size:13px; font-weight:bold; cursor:not-allowed; transition:all .2s; background:${buttonColor}; color:${buttonTextColor};">${!data.vipPaymentEnabled && !isLower ? "支付暂未开启" : buttonText}</button>`
: `<div style="display:grid; grid-template-columns:1fr 1fr; gap:8px;">
<button data-vip-buy-level="${Number(level.id)}" data-vip-buy-provider="alipay"
style="border:none; border-radius:8px; padding:10px; font-size:13px; font-weight:bold; cursor:pointer; transition:all .2s; background:${buttonColor}; color:${buttonTextColor};">
支付宝
</button>
<button data-vip-buy-level="${Number(level.id)}" data-vip-buy-provider="wechat"
style="border:none; border-radius:8px; padding:10px; font-size:13px; font-weight:bold; cursor:pointer; transition:all .2s; background:#16a34a; color:#fff;">
微信
</button>
</div>
<div style="font-size:10px; color:#64748b; margin-top:8px; text-align:center;">${buttonText}后将跳转到对应支付页面</div>`;
return `
<div style="font-size:18px; font-weight:900; color:#e11d48; margin-bottom:5px;">
¥${formatMoney(priceToDisplay)}
<span style="font-size:11px; font-weight:normal; color:#94a3b8;">/ ${Number(level.duration_days || 0)}天</span>
</div>
${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 `
<div class="vip-level-card ${isCurrent ? "current" : ""}">
${isCurrent ? '<span class="vip-level-badge">当前档位</span>' : ""}
<div style="display:flex; align-items:center; gap:10px;">
<span style="font-size:24px;">${escapeHtml(level.icon || "✨")}</span>
<span style="font-size:16px; font-weight:bold; color:${color}">${escapeHtml(level.name || "")}</span>
</div>
<div style="font-size:11px; color:#64748b; line-height:1.5; min-height:33px;">${escapeHtml(level.description || "")}</div>
<div style="margin-top:5px; space-y:6px;">
<div class="vip-feature-item"><span class="vip-feature-icon">✓</span> 经验获取 <b>${Number(level.exp_multiplier || 1)}x</b></div>
<div class="vip-feature-item"><span class="vip-feature-icon">✓</span> 金币获取 <b>${Number(level.jjb_multiplier || 1)}x</b></div>
<div class="vip-feature-item"><span class="vip-feature-icon">✓</span> 专属入场特效 & 横幅</div>
</div>
<div style="margin-top:auto; padding-top:10px;">
${renderVipAction(level, data)}
</div>
</div>
`;
}
/**
* 渲染 VIP 购买记录。
*
* @param {Array<object>} logs
* @returns {void}
*/
function renderVipLogs(logs) {
const logsBody = element("vip-logs-body");
if (!logsBody) {
return;
}
if (!logs.length) {
logsBody.innerHTML = '<tr><td colspan="5" style="padding:40px; text-align:center; color:#94a3b8;">暂无记录</td></tr>';
return;
}
const statusMap = {
created: { text: "待创建", className: "background:#f1f5f9;color:#475569" },
pending: { text: "待支付", className: "background:#fef3c7;color:#b45309" },
paid: { text: "已支付", className: "background:#dcfce7;color:#166534" },
closed: { text: "已关闭", className: "background:#f1f5f9;color:#64748b" },
failed: { text: "失败", className: "background:#fee2e2;color:#991b1b" },
};
logsBody.innerHTML = logs.map((log) => {
const status = statusMap[log.status] || { text: escapeHtml(log.status || "未知"), className: "background:#f1f5f9" };
const openedAt = log.opened_vip_at ? String(log.opened_vip_at).substring(0, 16).replace("T", " ") : "—";
return `
<tr style="border-bottom:1px solid #f1f5f9;">
<td style="padding:10px; font-family:monospace; color:#64748b;">${escapeHtml(log.order_no || "")}</td>
<td style="padding:10px; font-weight:bold;">${escapeHtml(log.vip_name || "")}</td>
<td style="padding:10px; font-weight:bold; color:#e11d48;">¥${formatMoney(log.amount)}</td>
<td style="padding:10px;"><span style="display:inline-block; padding:2px 8px; border-radius:10px; font-size:10px; font-weight:bold; ${status.className}">${status.text}</span></td>
<td style="padding:10px; color:#64748b;">${escapeHtml(openedAt)}</td>
</tr>
`;
}).join("");
}
/**
* 渲染 VIP 个性化设置。
*
* @returns {void}
*/
function renderVipSettings() {
const data = vipData;
const user = data?.user;
const level = user?.vip_level;
if (!data || !user || !level) {
return;
}
element("vip-theme-icon").textContent = level.icon || "✨";
element("vip-theme-name").textContent = level.name || "";
element("vip-join-effect").textContent = data.effectOptions[user.custom_join_effect || level.join_effect] || "无";
element("vip-join-banner").textContent = `风格:${data.bannerStyleOptions[level.join_banner] || "默认"}`;
element("vip-leave-effect").textContent = data.effectOptions[user.custom_leave_effect || level.leave_effect] || "无";
element("vip-leave-banner").textContent = `风格:${data.bannerStyleOptions[level.leave_banner] || "默认"}`;
element("vip-default-join").textContent = level.join_templates?.[0] || "当前档位尚未配置默认欢迎语。";
element("vip-default-leave").textContent = level.leave_templates?.[0] || "当前档位尚未配置默认离开语。";
const badge = element("vip-customize-badge");
const form = element("vip-customize-form");
const notice = element("vip-customize-notice");
if (user.can_customize) {
badge.textContent = "已开启";
badge.style.background = "#dcfce7";
badge.style.color = "#166534";
form.style.display = "block";
notice.style.display = "none";
const joinSelect = element("vip-custom-join-effect");
const leaveSelect = element("vip-custom-leave-effect");
const optionsHtml = Object.entries(data.effectOptions || {})
.map(([key, label]) => `<option value="${escapeHtml(key)}">${escapeHtml(label)}</option>`)
.join("");
joinSelect.innerHTML = optionsHtml;
leaveSelect.innerHTML = optionsHtml;
joinSelect.value = user.custom_join_effect || level.join_effect || "none";
leaveSelect.value = user.custom_leave_effect || level.leave_effect || "none";
element("vip-custom-join").value = user.custom_join_message || "";
element("vip-custom-leave").value = user.custom_leave_message || "";
return;
}
badge.textContent = "未开放";
badge.style.background = "#f1f5f9";
badge.style.color = "#64748b";
form.style.display = "none";
notice.style.display = "block";
notice.textContent = user.is_vip
? "当前会员档位暂未开放个人自定义功能,不过你仍会自动使用本等级配置的专属欢迎语、离开语和华丽特效。"
: "开通会员后,这里会解锁对应等级的专属进退场主题;若等级允许,还能设置你自己的欢迎语和离开语。";
}
/**
* 渲染 VIP 弹窗整体内容。
*
* @returns {void}
*/
function renderVipModal() {
const data = vipData;
if (!data) {
return;
}
element("vip-current-name").textContent = data.user.is_vip ? (data.user.vip_name || "尊贵会员") : "普通用户";
element("vip-current-name").style.color = data.user.vip_level?.color || "#1e293b";
element("vip-expire-time").textContent = data.user.is_vip ? `到期时间:${data.user.hy_time}` : "开通会员享特权";
element("vip-total-amount").textContent = `¥${formatMoney(data.totalAmount)}`;
const grid = element("vip-levels-grid");
grid.innerHTML = (data.vipLevels || []).map((level) => renderVipLevelCard(level, data)).join("");
const settingsButton = element("vip-tabbtn-settings");
if (data.user.is_vip) {
settingsButton.style.display = "block";
renderVipSettings();
} else {
settingsButton.style.display = "none";
}
renderVipLogs(data.paymentLogs || []);
}
/**
* 拉取 VIP 中心数据。
*
* @returns {Promise<void>}
*/
async function fetchVipData() {
try {
const response = await fetch(VIP_CENTER_URL, {
headers: {
Accept: "application/json",
},
});
const json = await response.json();
if (json.status === "success") {
vipData = json.data;
renderVipModal();
}
} catch (error) {
console.error("Failed to fetch VIP data", error);
}
}
/**
* 绑定会员中心 tab、关闭、购买与个性化保存事件
* 打开 VIP 中心弹窗
*
* @returns {void}
*/
export function openVipModal() {
element("vip-modal").style.display = "flex";
void fetchVipData();
}
/**
* 关闭 VIP 中心弹窗。
*
* @returns {void}
*/
export function closeVipModal() {
element("vip-modal").style.display = "none";
}
/**
* 切换 VIP 中心页签。
*
* @param {string} tabName
* @returns {void}
*/
export function switchVipTab(tabName) {
document.querySelectorAll(".vip-tab-btn").forEach((button) => button.classList.remove("active"));
document.querySelectorAll(".vip-view-pane").forEach((pane) => pane.classList.remove("active"));
element(`vip-tabbtn-${tabName}`)?.classList.add("active");
element(`vip-view-${tabName}`)?.classList.add("active");
}
/**
* 创建 VIP 支付表单并打开支付页。
*
* @param {number} levelId
* @param {string} provider
* @returns {void}
*/
export function buyVip(levelId, provider = "alipay") {
const form = document.createElement("form");
form.method = "POST";
form.action = VIP_PAYMENT_URL;
form.target = "_blank";
for (const [name, value] of Object.entries({
_token: csrf(),
vip_level_id: levelId,
provider,
})) {
const input = document.createElement("input");
input.type = "hidden";
input.name = name;
input.value = String(value);
form.appendChild(input);
}
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
closeVipModal();
const providerText = provider === "wechat" ? "微信支付二维码页" : "支付宝支付页";
showDialog(`正在为您打开${providerText},请在新页面完成支付。`, "支付提示", "#3b82f6");
}
/**
* 保存 VIP 专属进退场设置。
*
* @returns {Promise<void>}
*/
export async function saveVipPresenceSettings() {
try {
const response = await fetch(VIP_PRESENCE_UPDATE_URL, {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
"X-CSRF-TOKEN": csrf(),
},
body: JSON.stringify({
custom_join_message: element("vip-custom-join").value,
custom_leave_message: element("vip-custom-leave").value,
custom_join_effect: element("vip-custom-join-effect").value,
custom_leave_effect: element("vip-custom-leave-effect").value,
}),
});
const json = await response.json();
if (json.status === "success") {
showDialog(json.message, "提示", "#10b981");
await fetchVipData();
return;
}
showDialog(json.message || "保存失败", "提示", "#f59e0b");
} catch (error) {
showDialog("网络异常,请重试", "错误", "#ef4444");
}
}
/**
* 绑定 VIP 中心所有按钮事件,并挂载兼容全局函数。
*
* @returns {void}
*/
export function bindVipControls() {
if (typeof window === "undefined") {
return;
}
window.openVipModal = openVipModal;
window.closeVipModal = closeVipModal;
window.switchVipTab = switchVipTab;
window.buyVip = buyVip;
window.saveVipPresenceSettings = saveVipPresenceSettings;
if (vipControlEventsBound || typeof document === "undefined") {
return;
}
@@ -32,37 +438,40 @@ export function bindVipControls() {
return;
}
// VIP 内容由接口动态渲染,tab 和购买按钮通过 data-* 代理避免重复绑定。
const tabButton = event.target.closest("[data-vip-tab]");
if (tabButton) {
event.preventDefault();
callVipGlobal("switchVipTab", tabButton.getAttribute("data-vip-tab") || "");
switchVipTab(tabButton.getAttribute("data-vip-tab") || "");
return;
}
if (event.target.closest("[data-vip-modal-close]")) {
event.preventDefault();
callVipGlobal("closeVipModal");
closeVipModal();
return;
}
const buyButton = event.target.closest("[data-vip-buy-level]");
if (buyButton) {
event.preventDefault();
// 购买按钮由 VIP 数据动态渲染,等级和支付渠道从 data 属性读取。
const levelId = Number.parseInt(buyButton.getAttribute("data-vip-buy-level") || "", 10);
const provider = buyButton.getAttribute("data-vip-buy-provider") || "alipay";
if (Number.isInteger(levelId)) {
callVipGlobal("buyVip", levelId, provider);
}
if (Number.isInteger(levelId)) {
buyVip(levelId, provider);
}
return;
}
if (event.target.closest("[data-vip-save-presence]")) {
event.preventDefault();
callVipGlobal("saveVipPresenceSettings");
void saveVipPresenceSettings();
return;
}
const modal = element("vip-modal");
if (modal && event.target === modal) {
closeVipModal();
}
});
}
@@ -1623,294 +1623,7 @@ async function generateWechatBindCode() {
</div>
</div>
<script>
(function() {
const CSRF = () => document.querySelector('meta[name="csrf-token"]')?.content ?? '';
let vipData = null;
window.openVipModal = function() {
document.getElementById('vip-modal').style.display = 'flex';
fetchVipData();
};
window.closeVipModal = function() {
document.getElementById('vip-modal').style.display = 'none';
};
window.switchVipTab = function(tabName) {
document.querySelectorAll('.vip-tab-btn').forEach(btn => btn.classList.remove('active'));
document.querySelectorAll('.vip-view-pane').forEach(pane => pane.classList.remove('active'));
document.getElementById('vip-tabbtn-' + tabName).classList.add('active');
document.getElementById('vip-view-' + tabName).classList.add('active');
};
async function fetchVipData() {
try {
const res = await fetch('{{ route('vip.center') }}', {
headers: { 'Accept': 'application/json' }
});
const json = await res.json();
if (json.status === 'success') {
vipData = json.data;
renderVipModal();
}
} catch (e) {
console.error('Failed to fetch VIP data', e);
}
}
function renderVipModal() {
const d = vipData;
// 状态栏
document.getElementById('vip-current-name').textContent = d.user.is_vip ? (d.user.vip_name || '尊贵会员') : '普通用户';
document.getElementById('vip-current-name').style.color = d.user.vip_level?.color || '#1e293b';
document.getElementById('vip-expire-time').textContent = d.user.is_vip ? `到期时间:${d.user.hy_time}` : '开通会员享特权';
document.getElementById('vip-total-amount').textContent = `¥${Number(d.totalAmount).toFixed(2)}`;
// 会员等级
const grid = document.getElementById('vip-levels-grid');
grid.innerHTML = d.vipLevels.map(v => {
const isCurrent = v.is_current;
const isHigher = v.is_higher;
const isLower = v.is_lower;
let btnText = '立即购买';
let btnColor = '#1e293b';
let btnTextColor = '#fff';
let priceToDisplay = v.price;
let isDisabled = !d.vipPaymentEnabled;
let showUpgradeInfo = false;
if (isCurrent) {
btnText = '立即续费';
btnColor = '#f59e0b';
btnTextColor = '#fff';
} else if (isHigher && d.user.is_vip) {
btnText = '补差价升级';
btnColor = '#4f46e5';
btnTextColor = '#fff';
priceToDisplay = v.upgrade_price;
showUpgradeInfo = true;
} else if (isLower) {
btnText = '无法降级';
btnColor = '#f1f5f9';
btnTextColor = '#94a3b8';
isDisabled = true;
}
return `
<div class="vip-level-card ${isCurrent ? 'current' : ''}">
${isCurrent ? '<span class="vip-level-badge">当前档位</span>' : ''}
<div style="display:flex; align-items:center; gap:10px;">
<span style="font-size:24px;">${v.icon}</span>
<span style="font-size:16px; font-weight:bold; color:${v.color}">${v.name}</span>
</div>
<div style="font-size:11px; color:#64748b; line-height:1.5; min-height:33px;">${v.description || ''}</div>
<div style="margin-top:5px; space-y:6px;">
<div class="vip-feature-item"><span class="vip-feature-icon"></span> 经验获取 <b>${v.exp_multiplier}x</b></div>
<div class="vip-feature-item"><span class="vip-feature-icon"></span> 金币获取 <b>${v.jjb_multiplier}x</b></div>
<div class="vip-feature-item"><span class="vip-feature-icon"></span> 专属入场特效 & 横幅</div>
</div>
<div style="margin-top:auto; padding-top:10px;">
<div style="font-size:18px; font-weight:900; color:#e11d48; margin-bottom:5px;">
¥${Number(priceToDisplay).toFixed(2)}
<span style="font-size:11px; font-weight:normal; color:#94a3b8;">/ ${v.duration_days}</span>
</div>
${showUpgradeInfo ? `<div style="font-size:10px; color:#4f46e5; font-weight:bold; margin-bottom:8px;">已省 ¥${(v.price - v.upgrade_price).toFixed(2)}</div>` : ''}
${!d.vipPaymentEnabled || isDisabled
? `<button ${isDisabled ? 'disabled' : 'disabled'}
style="width:100%; border:none; border-radius:8px; padding:10px; font-size:13px; font-weight:bold; cursor:not-allowed; transition:all .2s;
background:${btnColor}; color:${btnTextColor};">
${!d.vipPaymentEnabled && !isLower ? '支付暂未开启' : btnText}
</button>`
: `<div style="display:grid; grid-template-columns:1fr 1fr; gap:8px;">
<button data-vip-buy-level="${v.id}" data-vip-buy-provider="alipay"
style="border:none; border-radius:8px; padding:10px; font-size:13px; font-weight:bold; cursor:pointer; transition:all .2s; background:${btnColor}; color:${btnTextColor};">
支付宝
</button>
<button data-vip-buy-level="${v.id}" data-vip-buy-provider="wechat"
style="border:none; border-radius:8px; padding:10px; font-size:13px; font-weight:bold; cursor:pointer; transition:all .2s; background:#16a34a; color:#fff;">
微信
</button>
</div>
<div style="font-size:10px; color:#64748b; margin-top:8px; text-align:center;">${btnText}后将跳转到对应支付页面</div>`
}
</div>
</div>
`;
}).join('');
// 会员设置 Tab 显隐
const settingsBtn = document.getElementById('vip-tabbtn-settings');
if (d.user.is_vip) {
settingsBtn.style.display = 'block';
renderVipSettings();
} else {
settingsBtn.style.display = 'none';
}
// 购买记录
const logsBody = document.getElementById('vip-logs-body');
if (d.paymentLogs.length === 0) {
logsBody.innerHTML = '<tr><td colspan="5" style="padding:40px; text-align:center; color:#94a3b8;">暂无记录</td></tr>';
} else {
logsBody.innerHTML = d.paymentLogs.map(log => {
const statusMap = {
'created': { text: '待创建', class: 'background:#f1f5f9;color:#475569' },
'pending': { text: '待支付', class: 'background:#fef3c7;color:#b45309' },
'paid': { text: '已支付', class: 'background:#dcfce7;color:#166534' },
'closed': { text: '已关闭', class: 'background:#f1f5f9;color:#64748b' },
'failed': { text: '失败', class: 'background:#fee2e2;color:#991b1b' },
};
const s = statusMap[log.status] || { text: log.status, class: 'background:#f1f5f9' };
return `
<tr style="border-bottom:1px solid #f1f5f9;">
<td style="padding:10px; font-family:monospace; color:#64748b;">${log.order_no}</td>
<td style="padding:10px; font-weight:bold;">${log.vip_name}</td>
<td style="padding:10px; font-weight:bold; color:#e11d48;">¥${Number(log.amount).toFixed(2)}</td>
<td style="padding:10px;"><span style="display:inline-block; padding:2px 8px; border-radius:10px; font-size:10px; font-weight:bold; ${s.class}">${s.text}</span></td>
<td style="padding:10px; color:#64748b;">${log.opened_vip_at ? log.opened_vip_at.substring(0, 16).replace('T', ' ') : '—'}</td>
</tr>
`;
}).join('');
}
}
function renderVipSettings() {
const d = vipData;
const u = d.user;
const vl = u.vip_level;
if (!vl) return;
document.getElementById('vip-theme-icon').textContent = vl.icon || '✨';
document.getElementById('vip-theme-name').textContent = vl.name;
document.getElementById('vip-join-effect').textContent = d.effectOptions[u.custom_join_effect || vl.join_effect] || '无';
document.getElementById('vip-join-banner').textContent = `风格:${d.bannerStyleOptions[vl.join_banner] || '默认'}`;
document.getElementById('vip-leave-effect').textContent = d.effectOptions[u.custom_leave_effect || vl.leave_effect] || '无';
document.getElementById('vip-leave-banner').textContent = `风格:${d.bannerStyleOptions[vl.leave_banner] || '默认'}`;
document.getElementById('vip-default-join').textContent = vl.join_templates?.[0] || '当前档位尚未配置默认欢迎语。';
document.getElementById('vip-default-leave').textContent = vl.leave_templates?.[0] || '当前档位尚未配置默认离开语。';
const badge = document.getElementById('vip-customize-badge');
const form = document.getElementById('vip-customize-form');
const notice = document.getElementById('vip-customize-notice');
if (u.can_customize) {
badge.textContent = '已开启';
badge.style.background = '#dcfce7';
badge.style.color = '#166534';
form.style.display = 'block';
notice.style.display = 'none';
// 填充特效选项
const joinSelect = document.getElementById('vip-custom-join-effect');
const leaveSelect = document.getElementById('vip-custom-leave-effect');
let optionsHtml = '';
for (const [key, label] of Object.entries(d.effectOptions)) {
optionsHtml += `<option value="${key}">${label}</option>`;
}
joinSelect.innerHTML = optionsHtml;
leaveSelect.innerHTML = optionsHtml;
// 设置当前值
joinSelect.value = u.custom_join_effect || vl.join_effect || 'none';
leaveSelect.value = u.custom_leave_effect || vl.leave_effect || 'none';
document.getElementById('vip-custom-join').value = u.custom_join_message || '';
document.getElementById('vip-custom-leave').value = u.custom_leave_message || '';
} else {
badge.textContent = '未开放';
badge.style.background = '#f1f5f9';
badge.style.color = '#64748b';
form.style.display = 'none';
notice.style.display = 'block';
notice.textContent = u.is_vip
? '当前会员档位暂未开放个人自定义功能,不过你仍会自动使用本等级配置的专属欢迎语、离开语和华丽特效。'
: '开通会员后,这里会解锁对应等级的专属进退场主题;若等级允许,还能设置你自己的欢迎语和离开语。';
}
}
window.buyVip = function(levelId, provider = 'alipay') {
// 这里我们模拟提交表单,因为支付逻辑通常需要页面跳转
// 修改为在新窗口打开支付,避免聊天室页面丢失
const form = document.createElement('form');
form.method = 'POST';
form.action = '{{ route('vip.payment.store') }}';
form.target = '_blank'; // 新窗口打开支付
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = '_token';
csrfInput.value = CSRF();
form.appendChild(csrfInput);
const idInput = document.createElement('input');
idInput.type = 'hidden';
idInput.name = 'vip_level_id';
idInput.value = levelId;
form.appendChild(idInput);
const providerInput = document.createElement('input');
providerInput.type = 'hidden';
providerInput.name = 'provider';
providerInput.value = provider;
form.appendChild(providerInput);
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
// 提交后关闭弹窗并提示用户
closeVipModal();
if (window.chatDialog) {
const providerText = provider === 'wechat' ? '微信支付二维码页' : '支付宝支付页';
window.chatDialog.alert(`正在为您打开${providerText},请在新页面完成支付。`, '支付提示', '#3b82f6');
}
};
window.saveVipPresenceSettings = async function() {
const joinMsg = document.getElementById('vip-custom-join').value;
const leaveMsg = document.getElementById('vip-custom-leave').value;
const joinEffect = document.getElementById('vip-custom-join-effect').value;
const leaveEffect = document.getElementById('vip-custom-leave-effect').value;
try {
const res = await fetch('{{ route('vip.center.presence.update') }}', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-TOKEN': CSRF()
},
body: JSON.stringify({
custom_join_message: joinMsg,
custom_leave_message: leaveMsg,
custom_join_effect: joinEffect,
custom_leave_effect: leaveEffect
})
});
const json = await res.json();
if (json.status === 'success') {
window.chatDialog?.alert(json.message, '提示', '#10b981');
fetchVipData();
} else {
window.chatDialog?.alert(json.message || '保存失败', '提示', '#f59e0b');
}
} catch (e) {
window.chatDialog?.alert('网络异常,请重试', '错误', '#ef4444');
}
};
document.getElementById('vip-modal').addEventListener('click', function(e) {
if (e.target === this) closeVipModal();
});
})();
</script>
{{-- VIP 中心业务脚本已迁移到 resources/js/chat-room/vip-controls.js --}}
{{-- ═══════════ 婚姻状态弹窗 ═══════════ --}}
<style>