chatapp-h5/h5/yumi/cp-ranking/cp-ranking.js
2026-05-22 16:04:43 +08:00

580 lines
19 KiB
JavaScript

(function () {
"use strict";
var API_BASE_URL = "https://jvapi.haiyihy.com";
var ASSET_ROOT = "../../assets/yumi/cp-ranking";
var DEFAULT_AVATAR = ASSET_ROOT + "/avatar-sample.png";
var DEBUG = /(?:^|[?&])debug=1(?:&|$)/.test(window.location.search);
var PAGE_HEIGHTS = {
plaza: 4560,
rank: 3825,
rewards: 4800
};
var commonHeaders = {
authorization: "",
"req-app-intel": "version=1.0.0;build=1;model=H5;sysVersion=web;channel=Web",
"req-client": "H5",
"req-imei": "H5-STATIC",
"req-lang": "en",
"req-sys-origin": "origin=ATYOU;originChild=ATYOU",
"req-version": "V2",
"req-zone": "Asia/Shanghai"
};
var state = {
activeTab: initialTab(),
ready: false,
rows: sampleRankRows(),
lastRows: sampleRankRows().slice(0, 4),
rewards: sampleRewards(),
countdownTarget: resolveCountdownTarget()
};
var dom = {};
var countdownTimer = null;
init();
function init() {
dom.page = document.getElementById("page");
dom.canvas = document.getElementById("canvas");
dom.tabs = Array.prototype.slice.call(document.querySelectorAll("[data-tab]"));
dom.panels = Array.prototype.slice.call(document.querySelectorAll("[data-panel]"));
dom.status = document.getElementById("status");
dom.plazaList = document.getElementById("plazaList");
dom.historyList = document.getElementById("historyList");
dom.rankList = document.getElementById("rankList");
dom.podium = document.getElementById("podium");
dom.rewardGroups = [
document.getElementById("rewardGroup1"),
document.getElementById("rewardGroup2"),
document.getElementById("rewardGroup3")
];
bindEvents();
syncScale();
applyTab(state.activeTab);
render();
startCountdown();
initializeAppConnection().then(loadRemoteData).catch(function (error) {
console.warn("CP ranking init failed:", error);
state.ready = true;
render();
});
}
function bindEvents() {
window.addEventListener("resize", syncScale);
dom.tabs.forEach(function (button) {
button.addEventListener("click", function () {
applyTab(button.getAttribute("data-tab"));
});
});
document.getElementById("rulesButton").addEventListener("click", function () {
openRules();
});
dom.canvas.addEventListener("click", function (event) {
var target = event.target.closest("[data-user-id]");
if (!target) return;
openUser(target.getAttribute("data-user-id"));
});
}
function syncScale() {
var viewport = Math.min(window.innerWidth || 375, 430);
var scale = viewport / 1080;
document.documentElement.style.setProperty("--scale", String(scale));
}
function initialTab() {
var value = getParam("tab") || "plaza";
return PAGE_HEIGHTS[value] ? value : "plaza";
}
function applyTab(tab) {
if (!PAGE_HEIGHTS[tab]) tab = "plaza";
state.activeTab = tab;
document.documentElement.style.setProperty("--canvas-height", PAGE_HEIGHTS[tab] + "px");
dom.tabs.forEach(function (button) {
button.classList.toggle("is-active", button.getAttribute("data-tab") === tab);
});
dom.panels.forEach(function (panel) {
panel.classList.toggle("is-active", panel.getAttribute("data-panel") === tab);
});
var title = tab === "rank" ? "CP Rank" : tab === "rewards" ? "Rewards" : "CP Plaza";
setStatus(title);
}
async function initializeAppConnection() {
if (!isAppEnvironment()) {
setHeadersFromApp(defaultBrowserHeaders());
return;
}
try {
var raw = await getAccessOriginFromApp();
var parsed = JSON.parse(raw);
setHeadersFromApp(normalizeAccessOrigin(parsed));
} catch (error) {
console.warn("Failed to parse app access origin:", error);
setHeadersFromApp(defaultBrowserHeaders());
}
}
async function loadRemoteData() {
try {
var rankingResponse = await apiGet("/activity/week-cp-gift/ranking-data?type=WEEK");
var ranking = unwrapBody(rankingResponse) || {};
var rows = normalizeRankRows(ranking.thisWeekUserTop);
var lastRows = normalizeRankRows(ranking.lastWeekUserTop);
if (rows.length) state.rows = rows;
if (lastRows.length) state.lastRows = lastRows;
if (!lastRows.length && rows.length) state.lastRows = rows.slice(0, 4);
} catch (error) {
debugLog("CP ranking data failed:", error);
}
try {
var rewardResponse = await apiGet("/activity/week-cp-gift/list-reward-pool?type=WEEK_CP_GIFT");
var rewards = normalizeRewardGroups(unwrapBody(rewardResponse));
if (rewards.length) state.rewards = rewards;
} catch (error) {
debugLog("CP reward pool failed:", error);
}
state.ready = true;
render();
}
function render() {
renderRows(dom.plazaList, state.rows.slice(0, 4), "gift");
renderRows(dom.historyList, state.lastRows.slice(0, 4), "score");
renderRows(dom.rankList, state.rows.slice(3, 7), "score");
renderPodium(state.rows.slice(0, 3));
renderRewards();
}
function renderRows(container, rows, mode) {
if (!container) return;
if (!rows.length) {
container.innerHTML = '<div class="cp-row"><div class="empty-state">No ranking data</div></div>';
return;
}
container.innerHTML = rows.map(function (row) {
return renderCpRow(row, mode);
}).join("");
}
function renderCpRow(row, mode) {
var left = normalizeUser(row.left);
var right = normalizeUser(row.right);
var total = row.total || "0";
var center = mode === "gift"
? '<img class="gift-icon" src="' + ASSET_ROOT + '/gift-plane.png" alt="">'
: '<div class="score-heart">' + escapeHtml(total) + '</div>';
return '<div class="cp-row">' +
'<img class="row-wave row-wave-left" src="' + ASSET_ROOT + '/row-wave.png" alt="">' +
'<img class="row-wave row-wave-right" src="' + ASSET_ROOT + '/row-wave.png" alt="">' +
'<div class="cp-user cp-user-left" data-user-id="' + escapeAttribute(left.id) + '">' +
'<img class="cp-avatar" src="' + escapeAttribute(left.avatar) + '" alt="" onerror="this.onerror=null;this.src=&quot;' + DEFAULT_AVATAR + '&quot;">' +
'<span class="cp-name">' + escapeHtml(left.name) + '</span>' +
'</div>' +
'<div class="row-center">' + center + '</div>' +
'<div class="cp-user cp-user-right" data-user-id="' + escapeAttribute(right.id) + '">' +
'<img class="cp-avatar" src="' + escapeAttribute(right.avatar) + '" alt="" onerror="this.onerror=null;this.src=&quot;' + DEFAULT_AVATAR + '&quot;">' +
'<span class="cp-name">' + escapeHtml(right.name) + '</span>' +
'</div>' +
'</div>';
}
function renderPodium(rows) {
if (!dom.podium) return;
var padded = rows.slice();
while (padded.length < 3) padded.push(sampleRankRows()[padded.length]);
var order = [padded[0], padded[1], padded[2]];
dom.podium.innerHTML = order.map(function (row, index) {
return renderPodiumCard(row, index + 1);
}).join("");
}
function renderPodiumCard(row, rank) {
var left = normalizeUser(row.left);
var right = normalizeUser(row.right);
var crown = ASSET_ROOT + "/rank-crown-" + rank + ".png";
return '<div class="podium-card rank-' + rank + '">' +
'<img class="podium-crown" src="' + crown + '" alt="Rank ' + rank + '">' +
'<div class="podium-person podium-person-left" data-user-id="' + escapeAttribute(left.id) + '">' +
'<img class="podium-avatar" src="' + escapeAttribute(left.avatar) + '" alt="" onerror="this.onerror=null;this.src=&quot;' + DEFAULT_AVATAR + '&quot;">' +
'<span class="podium-name">' + escapeHtml(left.name) + '</span>' +
'</div>' +
'<div class="podium-person podium-person-right" data-user-id="' + escapeAttribute(right.id) + '">' +
'<img class="podium-avatar" src="' + escapeAttribute(right.avatar) + '" alt="" onerror="this.onerror=null;this.src=&quot;' + DEFAULT_AVATAR + '&quot;">' +
'<span class="podium-name">' + escapeHtml(right.name) + '</span>' +
'</div></div>';
}
function renderRewards() {
dom.rewardGroups.forEach(function (container, index) {
if (!container) return;
var rewards = state.rewards[index] || [];
container.innerHTML = rewards.map(renderRewardCard).join("");
});
}
function renderRewardCard(item) {
var icon = item.cover || ASSET_ROOT + "/reward-icon.png";
var note = item.note ? '<div class="reward-note">' + escapeHtml(item.note) + '</div>' : "";
return '<div class="reward-card">' +
'<div class="reward-frame"><img src="' + escapeAttribute(icon) + '" alt="" onerror="this.onerror=null;this.src=&quot;' + ASSET_ROOT + '/reward-icon.png&quot;"></div>' +
'<div class="reward-title">' + escapeHtml(item.title) + '</div>' +
note +
'</div>';
}
function normalizeRankRows(list) {
if (!Array.isArray(list)) return [];
return list.map(function (item) {
return {
rank: item.rank,
total: item.total || formatNumber(item.totalNumber),
left: {
id: item.userId,
name: item.userNickname,
avatar: item.userAvatar
},
right: {
id: item.cpUserId,
name: item.cpUserNickname,
avatar: item.cpUserAvatar
}
};
}).filter(function (row) {
return row.left.id || row.right.id;
});
}
function normalizeRewardGroups(body) {
if (!Array.isArray(body)) return [];
return body.map(function (group) {
var list = Array.isArray(group.activityRewardProps) ? group.activityRewardProps : [];
return list.map(function (item) {
return {
title: rewardTitle(item),
note: item.remark || "",
cover: item.cover || ""
};
});
}).filter(function (group) {
return group.length;
}).slice(0, 3);
}
function rewardTitle(item) {
if (!item) return "Reward";
var name = item.content || item.detailType || item.type || "Reward";
var quantity = item.quantity || item.amount;
if (quantity && String(name).indexOf("*") < 0) {
return name + "*" + quantity;
}
return name;
}
function normalizeUser(user) {
user = user || {};
return {
id: user.id || "",
name: user.name || "Namename",
avatar: user.avatar || DEFAULT_AVATAR
};
}
function startCountdown() {
updateCountdown();
if (countdownTimer) window.clearInterval(countdownTimer);
countdownTimer = window.setInterval(updateCountdown, 1000);
}
function updateCountdown() {
var remain = Math.max(0, state.countdownTarget - Date.now());
var seconds = Math.floor(remain / 1000);
var days = Math.floor(seconds / 86400);
seconds -= days * 86400;
var hours = Math.floor(seconds / 3600);
seconds -= hours * 3600;
var minutes = Math.floor(seconds / 60);
seconds -= minutes * 60;
setCountdownValue("days", days);
setCountdownValue("hours", hours);
setCountdownValue("minutes", minutes);
setCountdownValue("seconds", seconds);
}
function setCountdownValue(key, value) {
document.querySelectorAll('[data-countdown="' + key + '"]').forEach(function (node) {
node.textContent = pad2(value);
});
}
function resolveCountdownTarget() {
var endTime = getParam("endTime") || getParam("endAt");
if (endTime) {
var parsed = /^\d+$/.test(endTime) ? Number(endTime) : Date.parse(endTime);
if (parsed && parsed < 1000000000000) parsed *= 1000;
if (parsed && parsed > Date.now()) return parsed;
}
return Date.now() + (2 * 86400 + 2 * 3600 + 2 * 60 + 2) * 1000;
}
async function apiRequest(path, options) {
var url = path.indexOf("http") === 0 ? path : API_BASE_URL + path;
var init = Object.assign({ method: "GET" }, options || {});
init.headers = Object.assign({}, commonHeaders, init.headers || {});
if (init.body && typeof init.body !== "string" && !(init.body instanceof FormData)) {
init.headers["Content-Type"] = "application/json";
init.body = JSON.stringify(init.body);
}
var response = await fetch(url, init);
var data = await response.json();
if (!response.ok) throw new Error("HTTP " + response.status);
if (data && data.status === false) throw new Error(data.errorMsg || data.message || "API Error");
return data;
}
function apiGet(path) {
return apiRequest(path, { method: "GET" });
}
function debugLog() {
if (!DEBUG || !window.console) return;
console.debug.apply(console, arguments);
}
function unwrapBody(response) {
if (response && Object.prototype.hasOwnProperty.call(response, "body")) return response.body;
return response;
}
function setHeadersFromApp(info) {
var next = {};
if (info.authorization !== undefined) next.authorization = info.authorization;
if (info.reqLang) next["req-lang"] = info.reqLang;
if (info.appVersion || info.buildVersion || info.appChannel) {
var appIntel = [];
if (info.appVersion) appIntel.push("version=" + info.appVersion);
if (info.buildVersion) appIntel.push("build=" + info.buildVersion);
if (info.appChannel) appIntel.push("channel=" + info.appChannel);
if (appIntel.length) next["req-app-intel"] = appIntel.join(";");
}
if (info.reqImei) next["req-imei"] = info.reqImei;
if (info.sysOrigin) {
var origin = ["origin=" + info.sysOrigin];
if (info.sysOriginChild) origin.push("originChild=" + info.sysOriginChild);
next["req-sys-origin"] = origin.join(";");
}
commonHeaders = Object.assign({}, commonHeaders, next);
}
function defaultBrowserHeaders() {
var lang = (navigator.language || "en").toLowerCase();
return {
authorization: "",
reqLang: lang.indexOf("ar") === 0 ? "ar" : lang.indexOf("tr") === 0 ? "tr" : lang.indexOf("bn") === 0 ? "bn" : "en",
appVersion: "1.0.0",
buildVersion: "1",
appChannel: "Web",
reqImei: "H5-STATIC",
sysOrigin: "ATYOU",
sysOriginChild: "ATYOU"
};
}
function getAccessOriginFromApp() {
return new Promise(function (resolve) {
var resolved = false;
var finish = function (value) {
if (resolved) return;
resolved = true;
resolve(value || "{}");
};
window.renderData = finish;
window.getIosAccessOriginParam = finish;
if (window.app && typeof window.app.getAccessOrigin === "function") {
try {
finish(window.app.getAccessOrigin());
} catch (error) {
finish("{}");
}
return;
}
if (window.FlutterApp && window.FlutterApp.postMessage) {
window.FlutterApp.postMessage("requestAccessOrigin");
}
window.setTimeout(function () {
finish("{}");
}, 800);
});
}
function normalizeAccessOrigin(data) {
if (!data) return {};
var appIntel = parseKeyValueList(data["Req-App-Intel"] || "");
var sysOrigin = parseKeyValueList(data["Req-Sys-Origin"] || "");
return {
reqLang: data["Req-Lang"],
authorization: data.Authorization,
buildVersion: appIntel.build,
appVersion: appIntel.version,
appChannel: appIntel.channel,
reqImei: appIntel["Req-Imei"],
sysOrigin: sysOrigin.origin,
sysOriginChild: sysOrigin.originChild || sysOrigin.child
};
}
function parseKeyValueList(value) {
var result = {};
String(value || "").split(";").forEach(function (part) {
var index = part.indexOf("=");
if (index < 0) return;
result[part.slice(0, index)] = part.slice(index + 1);
});
return result;
}
function isAppEnvironment() {
return !!(window.app || window.webkit || window.FlutterPageControl || window.FlutterApp);
}
function openUser(id) {
if (!id) return;
try {
if (window.app && window.app.viewUserInfo) {
window.app.viewUserInfo(id);
} else if (window.FlutterPageControl && window.FlutterPageControl.postMessage) {
window.FlutterPageControl.postMessage("view_user_info:" + id);
}
} catch (error) {
console.warn("open user failed:", error);
}
}
function openRules() {
var message = "CP Ranking Rules\\n1. Gifts exchanged between CPs count toward the ranking.\\n2. CPs with at least 5000 gold exchanged can appear in CP Plaza.\\n3. Final rewards follow the activity settlement result.";
try {
if (window.app && window.app.openRuleDialog) {
window.app.openRuleDialog(message);
} else if (window.FlutterPageControl && window.FlutterPageControl.postMessage) {
window.FlutterPageControl.postMessage("open_rules:" + message);
} else {
window.alert(message);
}
} catch (error) {
window.alert(message);
}
}
function getParam(name) {
var hashQuery = "";
if (window.location.hash && window.location.hash.indexOf("?") >= 0) {
hashQuery = window.location.hash.slice(window.location.hash.indexOf("?") + 1);
}
var hashValue = hashQuery ? new URLSearchParams(hashQuery).get(name) : null;
var searchValue = new URLSearchParams(window.location.search).get(name);
return hashValue || searchValue;
}
function setStatus(text) {
if (dom.status) dom.status.textContent = text;
}
function pad2(value) {
value = Math.max(0, Number(value) || 0);
return value < 10 ? "0" + value : String(value);
}
function formatNumber(value) {
if (value == null || value === "") return "0";
var number = Number(value);
if (!Number.isFinite(number)) return String(value);
if (number >= 1000000) return (number / 1000000).toFixed(1).replace(/\\.0$/, "") + "M";
if (number >= 1000) return (number / 1000).toFixed(1).replace(/\\.0$/, "") + "K";
return String(number);
}
function escapeHtml(value) {
return String(value == null ? "" : value)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function escapeAttribute(value) {
return escapeHtml(value);
}
function sampleRankRows() {
var names = [
["Ariya", "Mira"],
["Sana", "Lina"],
["Nora", "Yara"],
["Hana", "Rina"],
["Maya", "Luna"],
["Iris", "Zara"],
["Alia", "Nina"]
];
return names.map(function (pair, index) {
return {
rank: index + 1,
total: ["1234567", "982340", "750120", "608880", "502340", "456700", "388200"][index],
left: {
id: "sample-left-" + index,
name: pair[0],
avatar: DEFAULT_AVATAR
},
right: {
id: "sample-right-" + index,
name: pair[1],
avatar: DEFAULT_AVATAR
}
};
});
}
function sampleRewards() {
return [
[
{ title: "Medal*7 days", note: "(Permanent medal after getting 3 times)" },
{ title: "Honor banner*7 days" },
{ title: "Headwear*7days" },
{ title: "Mount*7days" },
{ title: "Coins*20000" },
{ title: "Duke*7days" }
],
[
{ title: "Medal*7 days", note: "(Permanent medal after getting 3 times)" },
{ title: "Honor banner*7 days" },
{ title: "Mount*7days" },
{ title: "Coins*15000" },
{ title: "Marquis*7days" }
],
[
{ title: "Medal*7 days", note: "(Permanent medal after getting 3 times)" },
{ title: "Honor banner*7 days" },
{ title: "Coins*10000" },
{ title: "Count*7days" }
]
];
}
})();