(() => { "use strict"; const colors = ["#2563eb", "#0f9f6e", "#7a5af8", "#f79009", "#06aed5", "#e0528d", "#64748b", "#84cc16"]; const BUSINESS_TIME_ZONE = "Asia/Bangkok"; const $ = (selector) => document.querySelector(selector); function escapeHtml(value) { return String(value ?? "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'"); } function monthNow() { const parts = Object.fromEntries(new Intl.DateTimeFormat("en-CA", { timeZone: BUSINESS_TIME_ZONE, year: "numeric", month: "2-digit", }).formatToParts(new Date()).map((part) => [part.type, part.value])); return `${parts.year}-${parts.month}`; } function integer(value) { return new Intl.NumberFormat("zh-CN", { maximumFractionDigits: 0 }).format(Number(value || 0)); } function money(value, compact = true) { const amount = Number(value || 0); if (compact && amount >= 10000) return `¥${(amount / 10000).toFixed(amount >= 100000 ? 1 : 2)}万`; return new Intl.NumberFormat("zh-CN", { style: "currency", currency: "CNY", maximumFractionDigits: 0 }).format(amount); } async function api(path) { const response = await fetch(path, { credentials: "same-origin" }); const payload = await response.json(); if (!response.ok || !payload.ok) throw new Error(payload?.error?.message || "数据读取失败"); return payload.data; } function notify(message) { const node = $("#h5-message"); node.textContent = message; node.classList.add("visible"); window.setTimeout(() => node.classList.remove("visible"), 3000); } function reset(message) { ["#h5-rooms", "#h5-revenue", "#h5-nights", "#h5-companies", "#h5-donut-total"].forEach((id) => { $(id).textContent = "—"; }); $("#h5-ranking").innerHTML = `
${escapeHtml(message)}
`; $("#h5-matrix").innerHTML = `${escapeHtml(message)}
`; $("#h5-legend").innerHTML = ""; $("#h5-donut").style.background = "#d8e0ec"; } function renderSales(channels) { const sorted = [...channels].sort((a, b) => Number(b.totals.total_price) - Number(a.totals.total_price)); const total = sorted.reduce((sum, item) => sum + Number(item.totals.total_price || 0), 0); const maximum = Math.max(1, ...sorted.map((item) => Number(item.totals.total_price || 0))); let cursor = 0; const stops = sorted.map((item, index) => { const start = cursor; cursor += total ? Number(item.totals.total_price || 0) / total * 100 : 0; return `${colors[index % colors.length]} ${start.toFixed(3)}% ${cursor.toFixed(3)}%`; }); $("#h5-donut").style.background = total ? `conic-gradient(${stops.join(",")})` : "#d8e0ec"; $("#h5-donut-total").textContent = money(total); $("#h5-legend").innerHTML = sorted.map((item, index) => ` ${escapeHtml(item.worksheet)}${total ? (Number(item.totals.total_price || 0) / total * 100).toFixed(1) : "0.0"}%`).join(""); [...document.querySelectorAll("#h5-legend .mobile-legend-item i")].forEach((marker) => { marker.style.backgroundColor = marker.dataset.color; }); $("#h5-ranking").innerHTML = sorted.length ? sorted.map((item) => `暂无销售数据
'; [...document.querySelectorAll("#h5-ranking .track i")].forEach((bar) => { bar.style.width = `${Number(bar.dataset.width || 0)}%`; }); } function renderMatrix(data) { const rooms = data.overall.room_types.map((item) => item.room_type); if (!rooms.length || !data.channels.length) { $("#h5-matrix").innerHTML = '暂无渠道与房型数据
'; return; } const rows = data.channels.map((channel) => { const values = new Map(channel.room_types.map((item) => [item.room_type, item.rooms_sold])); return `| 渠道 | ${rooms.map((room) => `${escapeHtml(room)} | `).join("")}合计 |
|---|---|---|
| 合计 | ${rooms.map((room) => `${integer(totals.get(room) || 0)} | `).join("")}${integer(data.overall.totals.rooms_sold)} |