-
-
-
-
-
-
-
+
+
+
+
+ Level
+
+
+
+
+
+
+
+
-
-
-
- Level 0
-
-
![]()
-
![]()
-
-
-
Lv.0
-
0%
-
-
Lv.1
-
You need 12,345,600 more experience points
-
+
+
+
+
+
+ Level 0
+
+
![]()
+
![]()
+
+
+
Lv.0
+
0%
+
+
Lv.1
+
+ You need 12,345,600 more experience points
+
+
-
- Range
- Icons
- Profile Frame
-
-
+
+ Range
+ Icons
+ Profile Frame
+
+
- How to upgrade?
-
-
-
-
-
-
-
-
+
+ How to upgrade?
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
-
-
-
-
-
+
+
+
+
-
+ function byId(id) {
+ return document.getElementById(id);
+ }
+ function track() {
+ return tracks[state.track] ? state.track : 'charm';
+ }
+ function fmt(n) {
+ return Number(n || 0).toLocaleString('en-US');
+ }
+ function ratio(d) {
+ var o = d && d.overview ? d.overview : {};
+ var cur = Number(o.current_level_required_value || 0);
+ var next = Number(o.next_level_required_value || 0);
+ var total = Number(o.total_value || 0);
+ if (!d || !d.overview) return 0;
+ if (next <= cur) return total >= cur && cur > 0 ? 1 : 0;
+ return Math.max(
+ 0,
+ Math.min(1, (total - cur) / (next - cur))
+ );
+ }
+ function profileFromOverview(current) {
+ var user = current && current.user;
+ return (user && (user.profile || user.user || user)) || {};
+ }
+ function profileAvatar(profile) {
+ return String(
+ (profile &&
+ (profile.avatar ||
+ profile.user_avatar ||
+ profile.userAvatar ||
+ profile.avatar_url ||
+ profile.avatarUrl ||
+ profile.profile_avatar ||
+ profile.profileAvatar)) ||
+ ''
+ );
+ }
+ function applyUserProfile(current) {
+ state.userAvatar = profileAvatar(
+ profileFromOverview(current)
+ );
+ render();
+ }
+ function setAvatarSource(src, fallback) {
+ var avatar = byId('avatar');
+ if (!avatar) return;
+ var fallbackSrc = fallback || '';
+ var nextSrc = src || fallbackSrc;
+ avatar.onerror = function () {
+ if (
+ fallbackSrc &&
+ avatar.getAttribute('src') !== fallbackSrc
+ ) {
+ avatar.src = fallbackSrc;
+ }
+ };
+ if (avatar.getAttribute('src') !== nextSrc)
+ avatar.src = nextSrc;
+ }
+ function resizeApp() {
+ var scale = Math.min(window.innerWidth / 375, 1.25);
+ var viewportHeight = Math.max(
+ 1,
+ window.innerHeight / scale
+ );
+ document.documentElement.style.setProperty(
+ '--app-scale',
+ String(scale)
+ );
+ // 页面自身承接滚动,标题和 Tab 才能在 WebView 可视区内真正 sticky;
+ // 不再让整个 826px 设计稿随 document 滚动而把顶部导航带走。
+ byId('appViewport').style.height = viewportHeight + 'px';
+ byId('page').style.height = viewportHeight + 'px';
+ document.body.style.minHeight = window.innerHeight + 'px';
+ }
+ function syncLoading() {
+ var overlay = byId('loadingOverlay');
+ overlay.classList.toggle('active', state.loading);
+ overlay.setAttribute(
+ 'aria-hidden',
+ state.loading ? 'false' : 'true'
+ );
+ }
+ function showToast(message) {
+ var toast = byId('toast');
+ toast.textContent = message;
+ toast.classList.add('show');
+ window.clearTimeout(state.toastTimer);
+ state.toastTimer = window.setTimeout(function () {
+ toast.classList.remove('show');
+ }, 1800);
+ }
+ function readField(source, snakeKey, camelKey) {
+ if (!source) return null;
+ return source[snakeKey] != null
+ ? source[snakeKey]
+ : source[camelKey];
+ }
+ function staticImageURL(resource) {
+ var candidates = [
+ readField(resource, 'preview_url', 'previewUrl'),
+ readField(resource, 'asset_url', 'assetUrl'),
+ readField(resource, 'animation_url', 'animationUrl'),
+ ];
+ for (var i = 0; i < candidates.length; i += 1) {
+ var url = String(candidates[i] || '').trim();
+ // 等级表使用 img 展示静态封面;SVGA/PAG/MP4 主资产不能直接塞入 img,必须等待后端下发 preview_url。
+ if (
+ /^(https?:\/\/|\/|\.\.\/|\.\/)/i.test(url) &&
+ /\.(avif|gif|jpe?g|png|svg|webp)(\?|#|$)/i.test(url)
+ )
+ return url;
+ }
+ return '';
+ }
+ function escapeHTML(value) {
+ return String(value == null ? '' : value).replace(
+ /[&<>"']/g,
+ function (char) {
+ return {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": ''',
+ }[char];
+ }
+ );
+ }
+ function tierResource(tier, snakeKey, camelKey) {
+ return readField(tier, snakeKey, camelKey) || {};
+ }
+ function rowsHTML(detail) {
+ var tiers = ((detail && detail.tiers) || [])
+ .slice()
+ .sort(function (a, b) {
+ return (
+ Number(a.min_level || 0) -
+ Number(b.min_level || 0)
+ );
+ });
+ if (!tiers.length)
+ return 'No level data
';
+ return tiers
+ .map(function (tier) {
+ var min = Number(tier.min_level || 0);
+ var max = Number(tier.max_level || 0);
+ var range =
+ min === max ? String(min) : min + '-' + max;
+ var badgeURL = staticImageURL(
+ tierResource(
+ tier,
+ 'display_badge_resource',
+ 'displayBadgeResource'
+ )
+ );
+ var frameURL = staticImageURL(
+ tierResource(
+ tier,
+ 'display_avatar_frame_resource',
+ 'displayAvatarFrameResource'
+ )
+ );
+ var badge = badgeURL
+ ? '
'
+ : '—';
+ var frame = frameURL
+ ? '
'
+ : '—';
+ return (
+ '' +
+ escapeHTML(range) +
+ '
' +
+ badge +
+ frame +
+ '
'
+ );
+ })
+ .join('');
+ }
+ function renderTierRows(detail) {
+ var root = byId('tierRows');
+ root.innerHTML = rowsHTML(detail);
+ root.querySelectorAll('img').forEach(function (image) {
+ // CDN 单个素材异常时只隐藏该格,不让一个坏 URL 破坏整张等级层表。
+ image.addEventListener(
+ 'error',
+ function () {
+ image.style.visibility = 'hidden';
+ },
+ { once: true }
+ );
+ });
+ }
+ function setOptionalImageSource(element, src) {
+ if (!element) return;
+ if (!src) {
+ element.removeAttribute('src');
+ element.style.display = 'none';
+ return;
+ }
+ element.style.display = 'block';
+ element.src = src;
+ }
+ function upgradesHTML(cfg) {
+ var cls =
+ cfg.upgrades.length === 3
+ ? 'upgrade-three'
+ : 'upgrade-one';
+ return cfg.upgrades
+ .map(function (item) {
+ return (
+ '
' +
+ item.name +
+ '
' +
+ item.exp +
+ '
'
+ );
+ })
+ .join('');
+ }
+ function buttonHTML(cfg) {
+ var corner =
+ cfg.button === 'gold'
+ ? A + 'corner-gold.png'
+ : A + 'corner-purple.png';
+ var texture =
+ cfg.button === 'gold'
+ ? A + 'button-gold.webp'
+ : A + 'button-purple.webp';
+ return (
+ '



To consume'
+ );
+ }
+ function applyData() {
+ var d = state.details[state.track];
+ var o = d && d.overview ? d.overview : {};
+ var level = Number(o.level || 0);
+ var next = Number(o.next_level || level + 1);
+ var percent = Math.round(ratio(d) * 100);
+ byId('levelName').textContent = 'Level ' + level;
+ byId('lvCurrent').textContent = 'Lv.' + level;
+ byId('lvNext').textContent = 'Lv.' + next;
+ byId('progressPercent').textContent = percent + '%';
+ byId('progressFill').style.width = percent + '%';
+ var curRequired = Number(
+ o.current_level_required_value || 0
+ );
+ var nextRequired = Number(o.next_level_required_value || 0);
+ var total = Number(o.total_value || 0);
+ if (d && d.overview && nextRequired <= curRequired) {
+ byId('needText').textContent = 'Max level reached';
+ return;
+ }
+ var need = Math.max(0, nextRequired - total);
+ byId('needText').textContent =
+ 'You need ' + fmt(need) + ' more experience points';
+ }
+ function render() {
+ state.track = track();
+ var cfg = tracks[state.track];
+ byId('pageBg').src = cfg.bg;
+ byId('cardBg').src = cfg.cardBg;
+ byId('decorLeft').src = cfg.decor;
+ byId('decorRight').src = cfg.decor;
+ setAvatarSource(state.userAvatar, cfg.avatar);
+ var detail = state.details[state.track];
+ var overview = detail && detail.overview;
+ setOptionalImageSource(
+ byId('avatarFrame'),
+ staticImageURL(
+ readField(
+ overview,
+ 'display_avatar_frame_resource',
+ 'displayAvatarFrameResource'
+ )
+ )
+ );
+ renderTierRows(detail);
+ byId('upgradeList').innerHTML = upgradesHTML(cfg);
+ byId('consumeButton').className =
+ 'consume ' + (cfg.button === 'gold' ? 'gold' : '');
+ byId('consumeButton').innerHTML = buttonHTML(cfg);
+ document.documentElement.style.setProperty(
+ '--card-texture',
+ cfg.texture
+ );
+ document.documentElement.style.setProperty(
+ '--card-texture-opacity',
+ cfg.textureOpacity
+ );
+ document.documentElement.style.setProperty(
+ '--card-glow',
+ cfg.glow
+ );
+ document.documentElement.style.setProperty(
+ '--title-shadow',
+ cfg.titleShadow
+ );
+ document.documentElement.style.setProperty(
+ '--title-fill',
+ cfg.titleFill
+ );
+ document.documentElement.style.setProperty(
+ '--title-gradient',
+ cfg.titleGradient
+ );
+ document.documentElement.style.setProperty(
+ '--progress-text',
+ cfg.progress
+ );
+ document.documentElement.style.setProperty(
+ '--progress-track',
+ cfg.progressTrack
+ );
+ document.documentElement.style.setProperty(
+ '--progress-fill',
+ cfg.progressFill
+ );
+ document.documentElement.style.setProperty(
+ '--need-text',
+ cfg.need
+ );
+ document.documentElement.style.setProperty(
+ '--upgrade-title',
+ cfg.upgrade
+ );
+ document.querySelectorAll('.tab').forEach(function (node) {
+ node.classList.toggle(
+ 'active',
+ node.dataset.track === state.track
+ );
+ });
+ applyData();
+ syncLoading();
+ resizeApp();
+ }
+ function loadOverview() {
+ if (
+ !window.HyAppAPI ||
+ !window.HyAppAPI.level ||
+ !window.HyAppAPI.getAccessToken()
+ )
+ return Promise.resolve(null);
+ return window.HyAppAPI.level
+ .overview()
+ .then(function (data) {
+ ((data && data.tracks) || []).forEach(
+ function (item) {
+ if (
+ !item ||
+ !item.track ||
+ !tracks[item.track]
+ )
+ return;
+ if (!state.details[item.track])
+ state.details[item.track] = {};
+ state.details[item.track].overview = item;
+ }
+ );
+ render();
+ return data;
+ })
+ .catch(function () {
+ return null;
+ });
+ }
+ function loadTrack(name) {
+ state.track = tracks[name] ? name : 'charm';
+ if (window.history && window.history.replaceState) {
+ var url = new URL(window.location.href);
+ url.searchParams.set('track', state.track);
+ window.history.replaceState(null, '', url.toString());
+ }
+ render();
+ if (
+ !window.HyAppAPI ||
+ !window.HyAppAPI.level ||
+ !window.HyAppAPI.getAccessToken()
+ )
+ return Promise.resolve(null);
+ state.loading = true;
+ render();
+ return window.HyAppAPI.level
+ .track(state.track)
+ .then(function (data) {
+ state.details[state.track] = data;
+ })
+ .catch(function (error) {
+ showToast(
+ (error && error.message) || 'Network error'
+ );
+ })
+ .finally(function () {
+ state.loading = false;
+ render();
+ });
+ }
+ function loadUserProfile() {
+ if (!window.HyAppParams || !window.HyAppParams.loadUser)
+ return Promise.resolve(null);
+ return window.HyAppParams.loadUser()
+ .then(function (current) {
+ applyUserProfile(current);
+ return current;
+ })
+ .catch(function () {
+ return null;
+ });
+ }
+ byId('tabs').addEventListener('click', function (event) {
+ var target = event.target.closest('[data-track]');
+ if (!target) return;
+ loadTrack(target.dataset.track);
+ });
+ byId('backButton').addEventListener('click', function () {
+ window.HyAppBridge.back();
+ });
+ byId('consumeHit').addEventListener('click', function () {
+ // Level 页只发稳定的 recharge 动作,Flutter 统一负责打开原生钱包充值页。
+ window.HyAppBridge.post('recharge', {
+ source: 'level',
+ track: state.track,
+ });
+ });
+ window.addEventListener('resize', resizeApp);
+ window.addEventListener('orientationchange', function () {
+ window.requestAnimationFrame(resizeApp);
+ });
+ render();
+ window.HyAppBridge.ready({ page: 'level' });
+ loadTrack(state.track).finally(function () {
+ loadOverview();
+ loadUserProfile();
+ });
+ })();
+
+
diff --git a/rank/index.html b/rank/index.html
index 70d9d74..a463da0 100644
--- a/rank/index.html
+++ b/rank/index.html
@@ -232,6 +232,7 @@
.badges img {
width: 12px;
height: 12px;
+ object-fit: contain;
}
.score-pill {
position: absolute;
@@ -377,6 +378,7 @@
.row-badges img {
width: 12px;
height: 12px;
+ object-fit: contain;
}
.row-score {
display: flex;
@@ -925,6 +927,14 @@
function byId(id) {
return document.getElementById(id);
}
+ function escapeAttr(value) {
+ return String(value || '')
+ .replace(/&/g, '&')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+ .replace(//g, '>');
+ }
function queryValue(name) {
return new URLSearchParams(window.location.search).get(
name
@@ -994,11 +1004,22 @@
};
});
}
- function badges() {
- var html = '';
- for (var i = 0; i < 4; i += 1)
- html += '
';
- return html;
+ function badges(item) {
+ var covers =
+ item && item.user
+ ? item.user.short_badge_cover_urls || []
+ : [];
+ // 榜单设计最多预留四个短徽章位;没有真实封面时返回空串,不再展示占位徽章。
+ return covers
+ .slice(0, 4)
+ .map(function (coverURL) {
+ return (
+ '
'
+ );
+ })
+ .join('');
}
function score(value, className) {
return (
@@ -1015,6 +1036,13 @@
var room = item.room || {};
var userId = item.user_id || user.user_id || '';
var roomId = item.room_id || room.room_id || '';
+ var shortBadgeCovers =
+ user.short_badge_cover_urls ||
+ user.shortBadgeCoverUrls ||
+ [];
+ if (!Array.isArray(shortBadgeCovers)) {
+ shortBadgeCovers = [];
+ }
return {
rank: Number(item.rank || 0),
user_id: String(userId || ''),
@@ -1048,6 +1076,13 @@
user.avatar_url ||
user.avatarUrl ||
'',
+ short_badge_cover_urls:
+ shortBadgeCovers.filter(function (url) {
+ return (
+ typeof url === 'string' &&
+ url.trim()
+ );
+ }),
}
: null,
room: roomId
@@ -1108,7 +1143,7 @@
'" alt="">
' +
userName(top[0]) +
'
' +
- badges() +
+ badges(top[0]) +
'
' +
score(top[0].gift_value, 'score-pill') +
'';
@@ -1120,7 +1155,7 @@
'" alt="">
' +
userName(top[1]) +
'
' +
- badges() +
+ badges(top[1]) +
'
' +
score(top[1].gift_value, 'score-pill') +
'';
@@ -1132,7 +1167,7 @@
'" alt="">
' +
userName(top[2]) +
'
' +
- badges() +
+ badges(top[2]) +
'
' +
score(top[2].gift_value, 'score-pill') +
'';
@@ -1151,7 +1186,7 @@
'" alt="">' +
userName(item) +
'
' +
- badges() +
+ badges(item) +
'
' +
score(item.gift_value, 'row-score') +
''