// 聊天室座驾弹窗模块,负责座驾列表、购买、当前座驾和购买记录展示。 import { escapeHtml } from "./html.js"; const DEFAULT_RIDE_ITEMS_URL = "/rides/items"; const DEFAULT_RIDE_BUY_URL = "/rides/buy"; let rideEventsBound = false; let rideLoaded = false; let rideState = { items: [], currentRide: null, purchases: [], }; /** * 读取座驾弹窗接口地址配置。 * * @returns {{items:string,buy:string}} */ function rideUrls() { const modal = document.getElementById("ride-modal"); return { items: modal?.dataset.rideItemsUrl || DEFAULT_RIDE_ITEMS_URL, buy: modal?.dataset.rideBuyUrl || DEFAULT_RIDE_BUY_URL, }; } /** * 读取 CSRF Token。 * * @returns {string} */ function csrf() { return document.querySelector('meta[name="csrf-token"]')?.content || ""; } /** * 获取座驾接口请求头。 * * @param {boolean} withJson 是否携带 JSON Content-Type * @returns {Record} */ function rideHeaders(withJson = false) { const headers = { "X-CSRF-TOKEN": csrf(), "Accept": "application/json", }; if (withJson) { headers["Content-Type"] = "application/json"; } return headers; } /** * 打开座驾弹窗并首次加载数据。 * * @returns {void} */ export function openRideModal() { const modal = document.getElementById("ride-modal"); if (!modal) { return; } modal.style.display = "flex"; if (!rideLoaded) { rideLoaded = true; void loadRides(); } } /** * 关闭座驾弹窗。 * * @returns {void} */ export function closeRideModal() { const modal = document.getElementById("ride-modal"); if (modal) { modal.style.display = "none"; } } /** * 拉取座驾页面数据。 * * @returns {Promise>} */ export async function fetchRideData() { const response = await fetch(rideUrls().items, { headers: rideHeaders(), credentials: "same-origin", }); if (!response.ok) { throw new Error("座驾数据加载失败"); } return response.json(); } /** * 加载并渲染座驾页面。 * * @returns {Promise} */ export async function loadRides() { const list = document.getElementById("ride-items-list"); if (list) { list.innerHTML = '
加载中...
'; } try { const data = await fetchRideData(); rideState = { items: Array.isArray(data.items) ? data.items : [], currentRide: data.current_ride || null, purchases: Array.isArray(data.purchases) ? data.purchases : [], }; renderRides(data); } catch (error) { if (list) { list.innerHTML = '
加载失败,请稍后重试
'; } } } /** * 渲染座驾弹窗全部内容。 * * @param {Record} data 接口返回数据 * @returns {void} */ export function renderRides(data) { const balance = document.getElementById("ride-jjb"); if (balance) { balance.textContent = Number(data.user_jjb || data.jjb || 0).toLocaleString(); } renderCurrentRide(data.current_ride || null); renderRideItems(Array.isArray(data.items) ? data.items : rideState.items); renderRidePurchases(Array.isArray(data.purchases) ? data.purchases : rideState.purchases); } /** * 渲染当前激活座驾。 * * @param {Record|null} currentRide 当前座驾记录 * @returns {void} */ function renderCurrentRide(currentRide) { const box = document.getElementById("ride-current"); if (!box) { return; } const item = currentRide?.item; if (!item) { box.innerHTML = '当前未启用座驾'; return; } box.innerHTML = ` ${escapeHtml(item.icon || "🚘")} ${escapeHtml(item.name)} 生效中 到期:${escapeHtml(currentRide.expires_at || "-")} `; } /** * 渲染座驾商品卡片。 * * @param {Array>} items 座驾商品列表 * @returns {void} */ function renderRideItems(items) { const list = document.getElementById("ride-items-list"); if (!list) { return; } if (!items.length) { list.innerHTML = '
暂无上架座驾
'; return; } const activeItemId = Number(rideState.currentRide?.item?.id || 0); list.innerHTML = items.map((item) => { const isActive = Number(item.id) === activeItemId; const duration = Number(item.duration_days || 0); return `
${escapeHtml(item.icon || "🚘")} ${escapeHtml(item.name || "")} ${isActive ? '当前' : ""}
${escapeHtml(item.description || "")}
💰 ${Number(item.price || 0).toLocaleString()} 金币 ⏱ ${duration > 0 ? `${duration} 天` : "未配置"}
`; }).join(""); } /** * 渲染座驾购买记录。 * * @param {Array>} purchases 购买记录 * @returns {void} */ function renderRidePurchases(purchases) { const list = document.getElementById("ride-purchase-list"); if (!list) { return; } if (!purchases.length) { list.innerHTML = '
暂无座驾购买记录
'; return; } list.innerHTML = purchases.map((purchase) => { const item = purchase.item || {}; const statusMap = { active: "使用中", expired: "已过期", cancelled: "已替换", used: "已使用", }; return `
${escapeHtml(item.icon || "🚘")} ${escapeHtml(item.name || "未知座驾")} ${escapeHtml(statusMap[purchase.status] || purchase.status || "-")} ${Number(purchase.price_paid || 0).toLocaleString()} 金币 ${escapeHtml(purchase.expires_at || "-")}
`; }).join(""); } /** * 购买或续费座驾。 * * @param {number|string} itemId 商品 ID * @returns {Promise} */ export async function buyRide(itemId) { const item = rideState.items.find((entry) => Number(entry.id) === Number(itemId)); if (!item) { return; } const duration = Number(item.duration_days || 0); const ok = await window.chatDialog?.confirm?.( `确认花费 ${Number(item.price || 0).toLocaleString()} 金币购买【${item.name}】吗?\n有效期:${duration} 天\n同款续购会自动叠加有效期。`, "确认购买座驾", ); if (!ok) { return; } try { const response = await fetch(rideUrls().buy, { method: "POST", credentials: "same-origin", headers: rideHeaders(true), body: JSON.stringify({ item_id: Number(itemId), room_id: window.chatContext?.roomId || 0, }), }); const data = await response.json(); if (!response.ok || data.status !== "success") { window.chatDialog?.alert?.(data.message || "购买失败", "座驾购买", "#cc4444"); return; } rideState.currentRide = data.current_ride || null; rideState.purchases = Array.isArray(data.purchases) ? data.purchases : []; renderRides({ items: rideState.items, current_ride: rideState.currentRide, purchases: rideState.purchases, jjb: data.jjb, }); const shopBalance = document.getElementById("shop-jjb"); if (shopBalance) { shopBalance.textContent = Number(data.jjb || 0).toLocaleString(); } window.chatDialog?.alert?.(data.message || "座驾购买成功", "座驾购买", "#16a34a"); } catch (error) { window.chatDialog?.alert?.("网络异常,请稍后重试。", "座驾购买", "#cc4444"); } } /** * 绑定座驾弹窗事件。 * * @returns {void} */ export function bindRideControls() { if (rideEventsBound || typeof document === "undefined") { return; } rideEventsBound = true; document.addEventListener("click", (event) => { if (!(event.target instanceof Element)) { return; } const closeButton = event.target.closest("[data-ride-modal-close]"); const modal = document.getElementById("ride-modal"); if (closeButton || (modal && event.target === modal)) { event.preventDefault(); closeRideModal(); return; } const buyButton = event.target.closest("[data-ride-buy]"); if (buyButton) { event.preventDefault(); void buyRide(buyButton.getAttribute("data-ride-buy") || ""); } }); window.openRideModal = openRideModal; window.closeRideModal = closeRideModal; window.loadRides = loadRides; window.buyRide = buyRide; }