789 lines
25 KiB
JavaScript
789 lines
25 KiB
JavaScript
(function () {
|
|
"use strict";
|
|
|
|
var API_BASE_URL = "https://jvapi.haiyihy.com";
|
|
var ASSET_BASE = "/assets/yumi-ranking/Ranking/Overall/";
|
|
var BG_VERSION = "20260424c";
|
|
var DEFAULT_AVATAR = "/assets/da.png";
|
|
var COIN_ICON = "";
|
|
var LOADING_GIF = "/assets/loading-ranking.gif";
|
|
var BACK_ICON =
|
|
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAADxSURBVHgB7dvBDYJAFEXRHyuhBEuzA+3EWAmlUIJ2MDJCNATy5m1I1H9PwoZhww3Mgh8iAAAAAACllG48joGlOUxfPoZ6LvCOM5S1PrITcV4is1acuhY7O8SXmveX+gp14rJbZGQ8OdUlMiKOQByBOAJxBOIIxBGIIxBHII5AHIE4AnEE4gjEEYgjEKeBOMJ488d/irPHN+lHYz33JKIqywHflnNkZm7SRCJSA5EMRDIQyUAkA5EMRDIQyUAkA5EMRDIQyUAkA5EMRDIQyWBGOkVmRqR7ZNeKFJCRroHJRqS+8DvUWpkGk10AAAAAwO96AiOznp1YXpZWAAAAAElFTkSuQmCC";
|
|
|
|
var RANKING_TYPES = ["Wealth", "Charm", "Room"];
|
|
var PERIODS = [
|
|
{ key: "Daily", api: "DAY", label: "ranking_daily" },
|
|
{ key: "Weekly", api: "WEEK", label: "ranking_weekly" },
|
|
{ key: "Monthly", api: "MONTH", label: "ranking_monthly" }
|
|
];
|
|
var TYPE_LABELS = {
|
|
Wealth: "ranking_wealth",
|
|
Charm: "ranking_charm",
|
|
Room: "ranking_room"
|
|
};
|
|
var TYPE_TO_MY_RANK = {
|
|
Wealth: "GIFTS_SEND",
|
|
Charm: "GIFTS_RECEIVED",
|
|
Room: "ROOM_GIFTS"
|
|
};
|
|
var TYPE_ENDPOINTS = {
|
|
Wealth: "/activity/leaderboard/gifts-send",
|
|
Charm: "/activity/leaderboard/gifts-received",
|
|
Room: "/activity/leaderboard/room-gifts"
|
|
};
|
|
var TYPE_ASSETS = {
|
|
Wealth: {
|
|
bg: "bgWealth",
|
|
top1: "rankAvatarFrame1",
|
|
top2: "rankAvatarFrame2",
|
|
top3: "rankAvatarFrame3"
|
|
},
|
|
Charm: {
|
|
bg: "bgCharm",
|
|
top1: "rankAvatarFrame1",
|
|
top2: "rankAvatarFrame2",
|
|
top3: "rankAvatarFrame3"
|
|
},
|
|
Room: {
|
|
bg: "bgRoom",
|
|
top1: "rankAvatarFrame1",
|
|
top2: "rankAvatarFrame2",
|
|
top3: "rankAvatarFrame3"
|
|
}
|
|
};
|
|
var I18N = {
|
|
en: {
|
|
ranking_wealth: "Wealth",
|
|
ranking_charm: "Charm",
|
|
ranking_room: "Room",
|
|
ranking_daily: "Daily",
|
|
ranking_weekly: "Weekly",
|
|
ranking_monthly: "Monthly",
|
|
loading: "Loading"
|
|
}
|
|
};
|
|
|
|
var commonHeaders = {
|
|
authorization: "Bearer undefined",
|
|
"req-app-intel": "version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google",
|
|
"req-client": "Android",
|
|
"req-imei": "8fa54d728ab449e04f9292329ed44bda",
|
|
"req-lang": "id-ID",
|
|
"req-sys-origin": "origin=ATYOU;originChild=ATYOU",
|
|
"req-version": "V2",
|
|
"req-zone": "Asia/Shanghai"
|
|
};
|
|
|
|
var state = {
|
|
rankingType: getInitialRankingType(),
|
|
period: "Daily",
|
|
dateType: "DAY",
|
|
myRankType: "GIFTS_SEND",
|
|
sourceRanking: {},
|
|
visibleRanking: [],
|
|
myRanking: {},
|
|
visibleCount: 10,
|
|
hasMore: true,
|
|
loading: true,
|
|
locale: getLocale(),
|
|
isApp: isAppEnvironment(),
|
|
requestId: 0
|
|
};
|
|
|
|
var stickyObserver = null;
|
|
var loadObserver = null;
|
|
var lastTypeClick = 0;
|
|
var lastPeriodClick = 0;
|
|
|
|
function asset(name) {
|
|
return ASSET_BASE + name + ".png";
|
|
}
|
|
|
|
function backgroundAsset(type) {
|
|
return asset(TYPE_ASSETS[type].bg) + "?v=" + BG_VERSION;
|
|
}
|
|
|
|
function t(key) {
|
|
var messages = I18N[state.locale] || I18N.en;
|
|
return messages[key] || I18N.en[key] || key;
|
|
}
|
|
|
|
function getLocale() {
|
|
var raw = getParam("lang") || navigator.language || "en";
|
|
raw = String(raw).toLowerCase();
|
|
if (raw.indexOf("ar") === 0) return "ar";
|
|
if (raw.indexOf("tr") === 0) return "tr";
|
|
if (raw.indexOf("bn") === 0) return "bn";
|
|
return "en";
|
|
}
|
|
|
|
function getInitialRankingType() {
|
|
var first = getParam("first") || "Wealth";
|
|
return RANKING_TYPES.indexOf(first) >= 0 ? first : "Wealth";
|
|
}
|
|
|
|
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 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("child=" + info.sysOriginChild);
|
|
next["req-sys-origin"] = origin.join(";");
|
|
}
|
|
commonHeaders = Object.assign({}, commonHeaders, next);
|
|
}
|
|
|
|
function isMobileDevice() {
|
|
return /Android|iPad|iPhone|iPod/.test(navigator.userAgent);
|
|
}
|
|
|
|
function isAppEnvironment() {
|
|
return !!(window.app || window.webkit || window.FlutterPageControl);
|
|
}
|
|
|
|
function defaultBrowserHeaders() {
|
|
var lang = navigator.language || "";
|
|
return {
|
|
authorization: "",
|
|
reqLang: lang.toLowerCase().indexOf("ar") === 0
|
|
? "ar"
|
|
: lang.toLowerCase().indexOf("tr") === 0
|
|
? "tr"
|
|
: lang.toLowerCase().indexOf("bn") === 0
|
|
? "bn"
|
|
: "en",
|
|
appVersion: "1.0.0",
|
|
buildVersion: "1",
|
|
appChannel: "Web",
|
|
reqImei: "H5-STATIC",
|
|
sysOrigin: "LIKEI",
|
|
sysOriginChild: "LIKEI"
|
|
};
|
|
}
|
|
|
|
function mockAppAccessOrigin() {
|
|
return JSON.stringify({
|
|
Authorization: "Bearer FLUTTER_MOCK_TOKEN",
|
|
"Req-Lang": "en",
|
|
"Req-App-Intel": "build=1.0;version=2.1;channel=flutter;Req-Imei=flutter-device",
|
|
"Req-Sys-Origin": "origin=ATYOU;originChild=ATYOU"
|
|
});
|
|
}
|
|
|
|
function parseKeyValueList(value) {
|
|
var result = {};
|
|
if (!value) return result;
|
|
String(value).split(";").forEach(function (part) {
|
|
if (!part || part.indexOf("=") < 0) return;
|
|
var index = part.indexOf("=");
|
|
result[part.slice(0, index)] = part.slice(index + 1);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
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.child
|
|
};
|
|
}
|
|
|
|
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;
|
|
|
|
var attempts = 0;
|
|
var check = function () {
|
|
attempts += 1;
|
|
if (window.app && typeof window.app.getAccessOrigin === "function") {
|
|
try {
|
|
finish(window.app.getAccessOrigin());
|
|
} catch (error) {
|
|
console.error("getAccessOrigin failed:", error);
|
|
finish(mockAppAccessOrigin());
|
|
}
|
|
return;
|
|
}
|
|
if (attempts < 50) {
|
|
window.setTimeout(check, 100);
|
|
return;
|
|
}
|
|
if (window.FlutterApp && window.FlutterApp.postMessage) {
|
|
window.FlutterApp.postMessage("requestAccessOrigin");
|
|
}
|
|
finish(mockAppAccessOrigin());
|
|
};
|
|
|
|
if (isMobileDevice()) {
|
|
check();
|
|
} else {
|
|
finish(mockAppAccessOrigin());
|
|
}
|
|
});
|
|
}
|
|
|
|
async function initializeAppConnection() {
|
|
if (!isAppEnvironment()) {
|
|
setHeadersFromApp(defaultBrowserHeaders());
|
|
await fetchUserProfileAfterConnection();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
var raw = await getAccessOriginFromApp();
|
|
var parsed = JSON.parse(raw);
|
|
setHeadersFromApp(normalizeAccessOrigin(parsed));
|
|
} catch (error) {
|
|
console.error("Failed to parse access origin:", error);
|
|
setHeadersFromApp(defaultBrowserHeaders());
|
|
}
|
|
await fetchUserProfileAfterConnection();
|
|
}
|
|
|
|
async function fetchUserProfileAfterConnection() {
|
|
try {
|
|
var response = await apiGet("/team/member/profile");
|
|
if (response && response.status && response.body) {
|
|
localStorage.setItem("userProfile", JSON.stringify(response.body));
|
|
}
|
|
} catch (error) {
|
|
console.warn("Failed to fetch user info after connection:", error);
|
|
}
|
|
}
|
|
|
|
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 makeApiError("HTTP Error: " + response.status + " " + response.statusText, response.status, data);
|
|
}
|
|
if (data && data.status === false) {
|
|
throw makeApiError(data.errorMsg || data.message || "API Error: " + data.errorCode, response.status, data);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function apiGet(path) {
|
|
return apiRequest(path, { method: "GET" });
|
|
}
|
|
|
|
function makeApiError(message, status, response) {
|
|
var error = new Error(message);
|
|
error.name = "ApiError";
|
|
error.status = status || null;
|
|
error.response = response || null;
|
|
error.errorMsg = response && response.errorMsg;
|
|
error.errorCode = response && response.errorCode;
|
|
return error;
|
|
}
|
|
|
|
async function fetchLeaderboard(type) {
|
|
var response = await apiGet(TYPE_ENDPOINTS[type]);
|
|
if (response && response.status && response.body) {
|
|
state.sourceRanking = response.body;
|
|
updatePeriodRanking();
|
|
}
|
|
}
|
|
|
|
async function fetchMyRanking() {
|
|
var path = "/activity/leaderboard/my-rank?type=" +
|
|
encodeURIComponent(state.myRankType) +
|
|
"&dateType=" +
|
|
encodeURIComponent(state.dateType);
|
|
var response = await apiGet(path);
|
|
state.myRanking = response && response.status && response.body ? response.body : {};
|
|
}
|
|
|
|
function updatePeriodRanking() {
|
|
var body = state.sourceRanking || {};
|
|
var rows = [];
|
|
if (state.period === "Hourly" && body.hourly) rows = body.hourly;
|
|
if (state.period === "Daily" && body.daily) rows = body.daily;
|
|
if (state.period === "Weekly" && body.weekly) rows = body.weekly;
|
|
if (state.period === "Monthly" && body.monthly) rows = body.monthly;
|
|
state.visibleRanking = Array.isArray(rows) ? rows : [];
|
|
state.visibleCount = 10;
|
|
state.hasMore = true;
|
|
}
|
|
|
|
async function preloadImages(urls) {
|
|
await Promise.all(urls.map(function (url) {
|
|
return new Promise(function (resolve) {
|
|
var image = new Image();
|
|
image.onload = resolve;
|
|
image.onerror = resolve;
|
|
image.src = url;
|
|
});
|
|
}));
|
|
}
|
|
|
|
function preloadRankingAssets() {
|
|
return preloadImages([
|
|
backgroundAsset("Wealth"),
|
|
backgroundAsset("Charm"),
|
|
backgroundAsset("Room"),
|
|
asset("rankAvatarFrame1"),
|
|
asset("rankAvatarFrame2"),
|
|
asset("rankAvatarFrame3")
|
|
]);
|
|
}
|
|
|
|
async function loadInitialData() {
|
|
applyTypeState(state.rankingType);
|
|
render();
|
|
try {
|
|
await initializeAppConnection();
|
|
await Promise.all([
|
|
fetchCurrentRankingGroup(),
|
|
preloadRankingAssets()
|
|
]);
|
|
} catch (error) {
|
|
console.error("Ranking preload failed:", error);
|
|
} finally {
|
|
state.loading = false;
|
|
render();
|
|
}
|
|
}
|
|
|
|
async function fetchCurrentRankingGroup() {
|
|
var requestId = ++state.requestId;
|
|
var type = state.rankingType;
|
|
await Promise.all([
|
|
fetchLeaderboard(type).catch(function (error) {
|
|
console.error("Leaderboard request failed:", error);
|
|
}),
|
|
fetchMyRanking().catch(function (error) {
|
|
console.error("My ranking request failed:", error);
|
|
})
|
|
]);
|
|
if (requestId !== state.requestId) return;
|
|
render();
|
|
}
|
|
|
|
async function fetchOnlyMyRanking() {
|
|
var requestId = ++state.requestId;
|
|
try {
|
|
await fetchMyRanking();
|
|
} catch (error) {
|
|
console.error("My ranking request failed:", error);
|
|
}
|
|
if (requestId !== state.requestId) return;
|
|
render();
|
|
}
|
|
|
|
function applyTypeState(type) {
|
|
state.rankingType = type;
|
|
state.myRankType = TYPE_TO_MY_RANK[type];
|
|
}
|
|
|
|
function switchRankingType(type) {
|
|
if (state.rankingType === type) return;
|
|
var now = Date.now();
|
|
if (now - lastTypeClick < 1000) return;
|
|
lastTypeClick = now;
|
|
|
|
applyTypeState(type);
|
|
state.period = "Daily";
|
|
state.dateType = "DAY";
|
|
state.sourceRanking = {};
|
|
state.visibleRanking = [];
|
|
state.myRanking = {};
|
|
state.visibleCount = 10;
|
|
state.hasMore = true;
|
|
render();
|
|
fetchCurrentRankingGroup();
|
|
}
|
|
|
|
function switchPeriod(period) {
|
|
if (state.period === period.key) return;
|
|
var now = Date.now();
|
|
if (now - lastPeriodClick < 500) return;
|
|
lastPeriodClick = now;
|
|
|
|
state.period = period.key;
|
|
state.dateType = period.api;
|
|
state.visibleRanking = [];
|
|
state.myRanking = {};
|
|
updatePeriodRanking();
|
|
render();
|
|
fetchOnlyMyRanking();
|
|
}
|
|
|
|
function loadMoreRows() {
|
|
if (!state.hasMore) return;
|
|
var total = state.visibleRanking.filter(function (_, index) {
|
|
return index >= 3;
|
|
}).length;
|
|
var current = state.visibleCount;
|
|
if (current < total) {
|
|
state.visibleCount = Math.min(current + 10, total);
|
|
if (state.visibleCount >= total) state.hasMore = false;
|
|
render();
|
|
} else {
|
|
state.hasMore = false;
|
|
}
|
|
}
|
|
|
|
function getTopUsers() {
|
|
var list = state.visibleRanking.slice();
|
|
while (list.length < 3) {
|
|
list.push({
|
|
avatar: "",
|
|
id: "",
|
|
nickname: "",
|
|
quantityFormat: ""
|
|
});
|
|
}
|
|
return list.slice(0, 3);
|
|
}
|
|
|
|
function getListRows() {
|
|
return state.visibleRanking.filter(function (_, index) {
|
|
return index >= 3;
|
|
}).slice(0, state.visibleCount);
|
|
}
|
|
|
|
function topLayout(isTopOne) {
|
|
return {
|
|
width: isTopOne ? "44%" : "34%",
|
|
minHeight: isTopOne ? "66vw" : "55vw",
|
|
avatarLayer: "height:55%",
|
|
avatarWidth: isTopOne ? "45%" : "47%",
|
|
nameBox: isTopOne ? "height:6vw;width:55%" : "height:4.5vw;width:65%",
|
|
pillWidth: isTopOne ? "55%" : "70%",
|
|
infoStyle: isTopOne ? "bottom:27%" : "bottom:29%"
|
|
};
|
|
}
|
|
|
|
function render() {
|
|
var app = document.getElementById("app");
|
|
if (!app) return;
|
|
if (state.loading) {
|
|
app.innerHTML = renderLoading();
|
|
return;
|
|
}
|
|
app.innerHTML = renderRanking();
|
|
bindEvents();
|
|
setupObservers();
|
|
}
|
|
|
|
function renderBgLayer(zIndex) {
|
|
var style = zIndex === undefined ? "" : ' style="z-index:' + zIndex + '"';
|
|
return '<div class="bg-layer"' + style + '><img class="bg-image" src="' +
|
|
escapeAttribute(backgroundAsset(state.rankingType)) +
|
|
'" alt="background-0"></div>';
|
|
}
|
|
|
|
function renderLoading() {
|
|
return '<main class="ranking-page" data-ranking="' + state.rankingType + '">' +
|
|
'<div class="loading-container" style="color:#fff">' +
|
|
renderBgLayer() +
|
|
'<img src="' + LOADING_GIF + '" alt="" class="ranking-loading-image">' +
|
|
'<p>' + escapeHtml(t("loading")) + '...</p>' +
|
|
'</div>' +
|
|
'</main>';
|
|
}
|
|
|
|
function renderRanking() {
|
|
return '<main class="ranking-page" data-ranking="' + state.rankingType + '">' +
|
|
'<div class="ranking-content">' +
|
|
renderBgLayer(-1) +
|
|
'<div class="sentinel"></div>' +
|
|
renderHeader() +
|
|
'<div class="ranking-main">' +
|
|
renderTopSection() +
|
|
renderRankList() +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div class="bottom-spacer"></div>' +
|
|
renderMyRanking() +
|
|
'<div class="sr-status" aria-live="polite"></div>' +
|
|
'</main>';
|
|
}
|
|
|
|
function renderHeader() {
|
|
return '<div class="sticky-header">' +
|
|
'<div class="sticky-spacer"></div>' +
|
|
'<div class="type-tabs-wrap"><div class="type-tabs">' +
|
|
RANKING_TYPES.map(function (type) {
|
|
return '<button class="type-tab' + (state.rankingType === type ? " active" : "") +
|
|
'" data-ranking-type="' + type + '">' +
|
|
escapeHtml(t(TYPE_LABELS[type])) +
|
|
'</button>';
|
|
}).join("") +
|
|
'<button class="back-btn" type="button" aria-label="back"><img src="' + BACK_ICON + '" alt=""></button>' +
|
|
'</div></div>' +
|
|
'<div class="period-tabs">' +
|
|
PERIODS.map(function (period) {
|
|
return '<div class="period-item' + (state.period === period.key ? " active" : "") + '">' +
|
|
'<button class="period-tab" data-period="' + period.key + '">' +
|
|
escapeHtml(t(period.label)) +
|
|
'</button>' +
|
|
'</div>';
|
|
}).join("") +
|
|
'</div>' +
|
|
'</div>';
|
|
}
|
|
|
|
function renderTopSection() {
|
|
var users = getTopUsers();
|
|
return '<div class="top-section">' +
|
|
'<div class="top-one-wrap">' +
|
|
renderTopUser(users[0], 0, true) +
|
|
'</div>' +
|
|
'<div class="top-pair-wrap">' +
|
|
renderTopUser(users[1], 1, false) +
|
|
renderTopUser(users[2], 2, false) +
|
|
'</div>' +
|
|
'</div>';
|
|
}
|
|
|
|
function renderTopUser(user, index, isTopOne) {
|
|
var assets = TYPE_ASSETS[state.rankingType];
|
|
var frame = isTopOne ? assets.top1 : index === 1 ? assets.top2 : assets.top3;
|
|
var layout = topLayout(isTopOne);
|
|
var name = user && user.nickname ? user.nickname : "";
|
|
var avatar = user && user.avatar ? user.avatar : "";
|
|
var quantity = user && user.quantityFormat ? user.quantityFormat : "";
|
|
var id = user && user.id ? user.id : "";
|
|
return '<div class="top-user rank-' + (index + 1) + '" style="width:' + layout.width + '" data-user-id="' + escapeAttribute(id) + '">' +
|
|
'<div class="top-user-inner" style="min-height:' + layout.minHeight + '">' +
|
|
'<img class="top-frame" src="' + asset(frame) + '" alt="">' +
|
|
'<div class="top-avatar-layer" style="' + layout.avatarLayer + '">' +
|
|
'<div class="top-avatar-box" style="width:' + layout.avatarWidth + '">' +
|
|
'<img src="' + escapeAttribute(avatar) + '" alt="" data-avatar>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div class="top-user-info" style="' + layout.infoStyle + '">' +
|
|
'<div class="top-name-box" style="' + layout.nameBox + '">' +
|
|
'<div class="show-text ' + (isTopOne ? "top1-text" : "top-text") + '" style="font-weight:860">' + escapeHtml(name) + '</div>' +
|
|
'</div>' +
|
|
(quantity ? renderQuantityPill(quantity, layout.pillWidth, isTopOne) : "") +
|
|
'</div>' +
|
|
'</div>' +
|
|
'</div>';
|
|
}
|
|
|
|
function renderQuantityPill(quantity, width, isTopOne) {
|
|
return '<div class="quantity-pill" style="width:' + width + '">' +
|
|
'<div class="quantity-inner">' +
|
|
'<div class="quantity-text show-text ' + (isTopOne ? "top1-text" : "top-text") + '">' +
|
|
escapeHtml(quantity) +
|
|
'</div>' +
|
|
'</div>' +
|
|
'</div>';
|
|
}
|
|
|
|
function renderRankList() {
|
|
var rows = getListRows();
|
|
return '<div class="rank-list">' +
|
|
rows.map(function (row, index) {
|
|
return renderRankRow(row, index + 4);
|
|
}).join("") +
|
|
(state.hasMore ? '<div class="load-more-sentinel"></div>' : "") +
|
|
'</div>';
|
|
}
|
|
|
|
function renderItemCenter(classes, minHeight, image, content, attrs) {
|
|
return '<div class="item-center ' + classes + '"' + (attrs || "") + ' style="min-height:' + minHeight + '">' +
|
|
'<div class="item-content">' + content + '</div>' +
|
|
'</div>';
|
|
}
|
|
|
|
function renderRankRow(row, rank) {
|
|
var id = row && row.id ? row.id : "";
|
|
var avatar = row && row.avatar ? row.avatar : "";
|
|
var name = row && row.nickname ? row.nickname : "";
|
|
var quantity = row && row.quantityFormat ? row.quantityFormat : "";
|
|
var content = '<div class="rank-left">' +
|
|
'<div class="rank-number"><div>' + rank + '</div></div>' +
|
|
'<img class="avatar" src="' + escapeAttribute(avatar) + '" alt="" data-avatar>' +
|
|
'<div class="rank-name">' + escapeHtml(name) + '</div>' +
|
|
'</div>' +
|
|
'<div class="rank-value">' +
|
|
'<div class="rank-value-text">' + escapeHtml(quantity) + '</div>' +
|
|
'</div>';
|
|
return renderItemCenter(
|
|
"rank-row",
|
|
"19vw",
|
|
asset("RankingItem"),
|
|
content,
|
|
' data-user-id="' + escapeAttribute(id) + '"'
|
|
);
|
|
}
|
|
|
|
function renderMyRanking() {
|
|
var mine = state.myRanking || {};
|
|
var rank = mine.rank || 999;
|
|
var avatar = mine.avatar || "";
|
|
var name = mine.nickname || "";
|
|
var quantity = mine.quantityFormat || 0;
|
|
var content = '<div class="mine-left">' +
|
|
'<div class="mine-rank"><div>' + escapeHtml(String(rank)) + '</div></div>' +
|
|
'<img class="avatar" src="' + escapeAttribute(avatar) + '" alt="" data-avatar>' +
|
|
'<div class="mine-name">' + escapeHtml(name) + '</div>' +
|
|
'</div>' +
|
|
'<div class="mine-value">' +
|
|
'<div class="mine-value-text">' + escapeHtml(String(quantity)) + '</div>' +
|
|
'</div>';
|
|
return renderItemCenter("my-ranking", "", asset("myRankingBg"), content);
|
|
}
|
|
|
|
function bindEvents() {
|
|
document.querySelectorAll("[data-ranking-type]").forEach(function (button) {
|
|
button.addEventListener("click", function () {
|
|
switchRankingType(button.getAttribute("data-ranking-type"));
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll("[data-period]").forEach(function (button) {
|
|
button.addEventListener("click", function () {
|
|
var key = button.getAttribute("data-period");
|
|
var period = PERIODS.filter(function (item) {
|
|
return item.key === key;
|
|
})[0];
|
|
if (period) switchPeriod(period);
|
|
});
|
|
});
|
|
|
|
var back = document.querySelector(".back-btn");
|
|
if (back) back.addEventListener("click", closePage);
|
|
|
|
document.querySelectorAll("[data-user-id]").forEach(function (node) {
|
|
node.addEventListener("click", function () {
|
|
openAppTarget(node.getAttribute("data-user-id"));
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll("[data-avatar]").forEach(function (img) {
|
|
img.addEventListener("error", function () {
|
|
if (img.getAttribute("src") !== DEFAULT_AVATAR) {
|
|
img.setAttribute("src", DEFAULT_AVATAR);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function setupObservers() {
|
|
if (stickyObserver) stickyObserver.disconnect();
|
|
if (loadObserver) loadObserver.disconnect();
|
|
|
|
var sentinel = document.querySelector(".sentinel");
|
|
var stickyHeader = document.querySelector(".sticky-header");
|
|
if (sentinel && stickyHeader && "IntersectionObserver" in window) {
|
|
stickyObserver = new IntersectionObserver(function (entries) {
|
|
entries.forEach(function (entry) {
|
|
if (entry.isIntersecting) {
|
|
stickyHeader.classList.remove("stuck");
|
|
} else {
|
|
stickyHeader.classList.add("stuck");
|
|
}
|
|
});
|
|
});
|
|
stickyObserver.observe(sentinel);
|
|
}
|
|
|
|
var loadMore = document.querySelector(".load-more-sentinel");
|
|
if (loadMore && "IntersectionObserver" in window) {
|
|
loadObserver = new IntersectionObserver(function (entries) {
|
|
entries.forEach(function (entry) {
|
|
if (entry.isIntersecting && state.hasMore) loadMoreRows();
|
|
});
|
|
}, { rootMargin: "100px" });
|
|
loadObserver.observe(loadMore);
|
|
}
|
|
}
|
|
|
|
function closePage() {
|
|
try {
|
|
if (window.app && window.app.closePage) {
|
|
window.app.closePage();
|
|
} else if (window.FlutterPageControl && window.FlutterPageControl.postMessage) {
|
|
window.FlutterPageControl.postMessage("close_page");
|
|
} else {
|
|
window.history.back();
|
|
}
|
|
} catch (error) {
|
|
console.error("close page failed:", error);
|
|
window.history.back();
|
|
}
|
|
}
|
|
|
|
function openAppTarget(id) {
|
|
if (!id || !state.isApp) return;
|
|
try {
|
|
if (state.rankingType === "Room") {
|
|
if (window.app && window.app.gotoRoom) {
|
|
window.app.gotoRoom(id);
|
|
} else if (window.FlutterPageControl && window.FlutterPageControl.postMessage) {
|
|
window.FlutterPageControl.postMessage("go_to_room:" + id);
|
|
}
|
|
} else 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.error("open app target failed:", error);
|
|
}
|
|
}
|
|
|
|
function escapeHtml(value) {
|
|
return String(value == null ? "" : value)
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """)
|
|
.replace(/'/g, "'");
|
|
}
|
|
|
|
function escapeAttribute(value) {
|
|
return escapeHtml(value);
|
|
}
|
|
|
|
loadInitialData();
|
|
})();
|