From ee32247f4abb465b7779fb1e2e7e6036e604be5f Mon Sep 17 00:00:00 2001 From: zhx Date: Tue, 14 Jul 2026 14:07:20 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- level/index.html | 1653 +++++++++++++++++++++++++++++++++++----------- rank/index.html | 53 +- 2 files changed, 1326 insertions(+), 380 deletions(-) diff --git a/level/index.html b/level/index.html index 0e62de3..7b2d762 100644 --- a/level/index.html +++ b/level/index.html @@ -1,380 +1,1291 @@ - - - - - Level - - - - -
-
- - -
- - - -
+ + + + + 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?
-
- - -
- -
-
- - -
+
- - - - - + + + + - + 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') + ''