hyapp-h5/activity/cp/script.js
2026-06-25 14:20:07 +08:00

550 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(function () {
var DESIGN_WIDTH = 1080;
var MAX_PAGE_WIDTH = 768;
var STORAGE_KEY = 'hy_cp_activity_end_at';
var stageHeights = {
plaza: 4283,
rank: 3670,
rewards: 4797,
};
var activeTab = 'plaza';
var countdownEndAt = 0;
var countdownTimer = 0;
var activityData = {
rank: null,
rewards: null,
periodEndMS: 0,
};
var viewport = document.getElementById('appViewport');
var page = document.getElementById('page');
var stage = document.querySelector('.stage');
var stageWrap = document.getElementById('stageWrap');
var tabContent = document.getElementById('tabContent');
var copyright = document.querySelector('.copyright');
var tabs = Array.prototype.slice.call(document.querySelectorAll('.tab'));
var mockData = {
plaza: {
exchangeRows: buildRows('gift', 4),
previousRows: buildRows('score', 4),
},
rank: [
{
place: 1,
score: '1,234,567',
leftName: 'Namename',
rightName: 'Namename',
},
{
place: 2,
score: '987,654',
leftName: 'Namename',
rightName: 'Namename',
},
{
place: 3,
score: '876,543',
leftName: 'Namename',
rightName: 'Namename',
},
],
rewards: [
{
crown: 'crown-gold.png',
items: [
{ key: 'cp.rewardMedal7', noteKey: 'cp.rewardMedalNote' },
{ key: 'cp.rewardHonorBanner7' },
{ key: 'cp.rewardHeadwear7' },
{ key: 'cp.rewardMount7' },
{ key: 'cp.rewardCoins20000' },
{ key: 'cp.rewardDuke7' },
],
},
{
crown: 'crown-silver.png',
items: [
{ key: 'cp.rewardMedal7', noteKey: 'cp.rewardMedalNote' },
{ key: 'cp.rewardHonorBanner7' },
{ key: 'cp.rewardMount7' },
{ key: 'cp.rewardCoins20000' },
{ key: 'cp.rewardMarquis7' },
],
},
{
crown: 'crown-bronze.png',
items: [
{ key: 'cp.rewardMedal7', noteKey: 'cp.rewardMedalNote' },
{ key: 'cp.rewardHonorBanner7' },
{ key: 'cp.rewardCoins10000' },
{ key: 'cp.rewardCount7' },
],
},
],
};
function buildRows(type, count) {
var rows = [];
for (var index = 0; index < count; index += 1) {
rows.push({
type: type,
leftName: 'Namename',
rightName: 'Namename',
score: '1234567',
});
}
return rows;
}
function t(key, fallback) {
if (window.HyAppI18n && window.HyAppI18n.t) {
return window.HyAppI18n.t(key, fallback);
}
return fallback || key;
}
function escapeHTML(value) {
return String(value == null ? '' : value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function twoDigits(value) {
return String(Math.max(0, value)).padStart(2, '0');
}
function resolveFallbackEndAt() {
var params = new URLSearchParams(window.location.search || '');
var explicitEnd = Date.parse(
params.get('end_at') || params.get('endAt') || ''
);
if (Number.isFinite(explicitEnd) && explicitEnd > Date.now())
return explicitEnd;
try {
var cached = Number(window.localStorage.getItem(STORAGE_KEY));
if (Number.isFinite(cached) && cached > Date.now()) return cached;
var fallback =
Date.now() +
2 * 86400 * 1000 +
2 * 3600 * 1000 +
2 * 60 * 1000 +
2 * 1000;
window.localStorage.setItem(STORAGE_KEY, String(fallback));
return fallback;
} catch (error) {
return (
Date.now() +
2 * 86400 * 1000 +
2 * 3600 * 1000 +
2 * 60 * 1000 +
2 * 1000
);
}
}
function getTimeParts() {
var diff = Math.max(0, countdownEndAt - Date.now());
var totalSeconds = Math.floor(diff / 1000);
return {
days: Math.floor(totalSeconds / 86400),
hours: Math.floor((totalSeconds % 86400) / 3600),
minutes: Math.floor((totalSeconds % 3600) / 60),
seconds: totalSeconds % 60,
};
}
function updateCountdown() {
var parts = getTimeParts();
Object.keys(parts).forEach(function (key) {
document
.querySelectorAll('[data-count="' + key + '"]')
.forEach(function (node) {
node.textContent = twoDigits(parts[key]);
});
});
}
function renderPanel(className, top, height, bodyHTML) {
return [
'<section class="ornate-panel ',
className,
'" style="top:',
top,
'px;height:',
height,
'px">',
'<img class="panel-top" src="./assets/panel-top.png" alt="" aria-hidden="true" />',
'<div class="panel-mid" aria-hidden="true"></div>',
'<img class="panel-bottom" src="./assets/panel-bottom.png" alt="" aria-hidden="true" />',
'<div class="panel-body">',
bodyHTML,
'</div>',
'</section>',
].join('');
}
function renderCPRow(row) {
var center =
row.type === 'score'
? [
'<img class="score-heart" src="./assets/score-heart.png" alt="" aria-hidden="true" />',
'<strong class="score-value">',
escapeHTML(row.score),
'</strong>',
].join('')
: [
'<div class="gift-center">',
'<img src="./assets/gift-plane.png" alt="" aria-hidden="true" />',
'</div>',
].join('');
return [
'<article class="cp-row">',
'<div class="avatar avatar-left"><img src="',
escapeHTML(row.leftAvatar || './assets/avatar-sample.png'),
'" alt="" /></div>',
'<div class="avatar avatar-right"><img src="',
escapeHTML(row.rightAvatar || './assets/avatar-sample.png'),
'" alt="" /></div>',
'<span class="name name-left">',
escapeHTML(row.leftName),
'</span>',
'<span class="name name-right">',
escapeHTML(row.rightName),
'</span>',
'<img class="deco-line deco-left" src="./assets/deco-line.png" alt="" aria-hidden="true" />',
'<img class="deco-line deco-right" src="./assets/deco-line.png" alt="" aria-hidden="true" />',
center,
'</article>',
].join('');
}
function renderPlaza() {
var firstBody = [
'<h2 class="panel-title">',
t(
'cp.plazaGiftTitle',
'Gifts exchanged between CPs ≥ 5000 gold will be displayed here'
),
'</h2>',
'<p class="panel-subtitle">',
t(
'cp.plazaGiftSubtitle',
'(Display the last 10 records, sorted in ascending order.)'
),
'</p>',
'<div class="cp-list">',
mockData.plaza.exchangeRows.map(renderCPRow).join(''),
'</div>',
].join('');
var secondBody = [
'<h2 class="panel-title">',
t('cp.previousNo1Title', 'The previous number one position'),
'</h2>',
'<p class="panel-subtitle">',
t(
'cp.plazaGiftSubtitle',
'(Display the last 10 records, sorted in ascending order.)'
),
'</p>',
'<div class="cp-list">',
mockData.plaza.previousRows.map(renderCPRow).join(''),
'</div>',
].join('');
return [
renderPanel('plaza-panel-a', 1816, 1152, firstBody),
renderPanel('plaza-panel-b', 3024, 1126, secondBody),
].join('');
}
function renderInlineCountdown() {
var units = [
['days', 'cp.days', 'D'],
['hours', 'cp.hours', 'H'],
['minutes', 'cp.minutes', 'M'],
['seconds', 'cp.seconds', 'S'],
];
return [
'<div class="countdown-inline">',
units
.map(function (unit) {
return [
'<div class="time-unit">',
'<img src="./assets/countdown-heart.png" alt="" aria-hidden="true" />',
'<strong data-count="',
unit[0],
'">02</strong>',
'<span>',
t(unit[1], unit[2]),
'</span>',
'</div>',
].join('');
})
.join(''),
'</div>',
].join('');
}
function renderPodiumCard(item) {
var crown =
item.place === 1
? 'rank-crown-gold.png'
: item.place === 2
? 'rank-crown-silver.png'
: 'crown-bronze.png';
return [
'<article class="podium-card rank-',
item.place,
'">',
'<img class="podium-bg" src="./assets/rank-wing-heart.png" alt="" aria-hidden="true" />',
'<img class="podium-crown" src="./assets/',
crown,
'" alt="" aria-hidden="true" />',
'<div class="avatar podium-avatar left"><img src="',
escapeHTML(item.leftAvatar || './assets/avatar-sample.png'),
'" alt="" /></div>',
'<div class="avatar podium-avatar right"><img src="',
escapeHTML(item.rightAvatar || './assets/avatar-sample.png'),
'" alt="" /></div>',
'<div class="podium-names"><span>',
escapeHTML(item.leftName),
'</span><span>',
escapeHTML(item.rightName),
'</span></div>',
'</article>',
].join('');
}
function renderRankRow(item) {
return renderCPRow({
type: 'score',
leftName: item.leftName,
rightName: item.rightName,
score: item.score,
});
}
function renderRank() {
var rankRows = activityData.rank || mockData.rank;
var podium = [
'<section class="rank-podium">',
rankRows.slice(0, 3).map(renderPodiumCard).join(''),
'</section>',
].join('');
var panelRows = rankRows.length ? rankRows : mockData.rank;
var panelBody = [
renderInlineCountdown(),
'<div class="cp-list">',
panelRows.map(renderRankRow).join(''),
'</div>',
].join('');
return podium + renderPanel('rank-panel', 2430, 1107, panelBody);
}
function renderRewardItem(item) {
var name = item.name || t(item.key, item.key);
var note = item.note || (item.noteKey ? t(item.noteKey, '') : '');
var cover = item.coverUrl || './assets/reward-frame.png';
return [
'<article class="reward-card">',
'<div class="reward-icon-box"><img src="',
escapeHTML(cover),
'" alt="" aria-hidden="true" /></div>',
'<div class="reward-name">',
escapeHTML(name),
'</div>',
note
? '<div class="reward-note">' + escapeHTML(note) + '</div>'
: '',
'</article>',
].join('');
}
function renderRewardPanel(section, index) {
var className = [
'reward-panel',
'reward-' + ['a', 'b', 'c'][index],
].join(' ');
var top = [1816, 2784, 3752][index];
var body = [
'<img class="reward-crown" src="./assets/',
section.crown,
'" alt="" aria-hidden="true" />',
'<div class="reward-grid">',
section.items.map(renderRewardItem).join(''),
'</div>',
].join('');
return renderPanel(className, top, 912, body);
}
function renderRewards() {
return (activityData.rewards || mockData.rewards)
.map(renderRewardPanel)
.join('');
}
function formatScore(value) {
var number = Number(value || 0);
if (!Number.isFinite(number)) return '0';
return number.toLocaleString('en-US');
}
function normalizeUser(user) {
return user || {};
}
function normalizeRankEntry(entry) {
var userA = normalizeUser(entry.user_a);
var userB = normalizeUser(entry.user_b);
return {
place: Number(entry.rank || 0),
score: formatScore(entry.score),
leftName: userA.username || userA.display_user_id || 'Namename',
rightName: userB.username || userB.display_user_id || 'Namename',
leftAvatar: userA.avatar || '',
rightAvatar: userB.avatar || '',
};
}
function rewardName(item) {
var type = item.resource_type || 'reward';
if (item.duration_days > 0)
return type + ' ' + item.duration_days + 'D';
if (item.quantity > 0) return type + ' x' + item.quantity;
return type;
}
function normalizeRewardSection(reward, index) {
var crowns = ['crown-gold.png', 'crown-silver.png', 'crown-bronze.png'];
return {
crown: crowns[index] || 'crown-bronze.png',
items: (reward.items || []).map(function (item) {
return {
name: rewardName(item),
note:
item.duration_days > 0
? item.duration_days + ' days'
: item.quantity > 0
? 'x' + item.quantity
: '',
coverUrl: item.cover_url || '',
};
}),
};
}
function applyActivityStatus(status) {
if (!status) return;
if (status.period && status.period.end_ms > Date.now()) {
activityData.periodEndMS = status.period.end_ms;
countdownEndAt = status.period.end_ms;
}
if (Array.isArray(status.entries) && status.entries.length) {
activityData.rank = status.entries.map(normalizeRankEntry);
}
if (Array.isArray(status.rewards) && status.rewards.length) {
activityData.rewards = status.rewards.map(normalizeRewardSection);
}
}
function loadActivityStatus() {
if (!window.HyAppAPI || !window.HyAppAPI.cpWeeklyRank) return;
// 周榜配置和奖励由后端控制H5 只在接口成功时替换展示数据,失败保持静态兜底,避免活动页因为鉴权或网络问题白屏。
window.HyAppAPI.cpWeeklyRank
.status({ limit: 10 })
.then(function (status) {
applyActivityStatus(status);
render();
})
.catch(function () {});
}
function render() {
document.title = t('cp.pageTitle', 'Last Weekly CP');
tabContent.innerHTML =
activeTab === 'rank'
? renderRank()
: activeTab === 'rewards'
? renderRewards()
: renderPlaza();
applyTabState();
updateStageHeight();
updateCountdown();
}
function applyTabState() {
tabs.forEach(function (tab) {
var isActive = tab.getAttribute('data-tab') === activeTab;
tab.classList.toggle('is-active', isActive);
tab.querySelector('img').src = isActive
? './assets/tab-active.png'
: './assets/tab-inactive.png';
});
page.setAttribute('data-tab', activeTab);
}
function getPageWidth() {
var viewportWidth =
window.innerWidth ||
document.documentElement.clientWidth ||
DESIGN_WIDTH;
return Math.min(Math.max(viewportWidth, 1), MAX_PAGE_WIDTH);
}
function updateStageHeight() {
var designHeight = stageHeights[activeTab] || stageHeights.plaza;
var pageWidth = getPageWidth();
var designScale = pageWidth / DESIGN_WIDTH;
stage.style.minHeight = designHeight + 'px';
stage.style.height = designHeight + 'px';
// 页面保持 1080 设计稿坐标系,大屏只放大到 768px避免桌面或平板 WebView 把活动页拉得过宽。
document.documentElement.style.setProperty(
'--page-width',
pageWidth + 'px'
);
document.documentElement.style.setProperty(
'--design-scale',
String(designScale)
);
stageWrap.style.height = Math.ceil(designHeight * designScale) + 'px';
copyright.style.top = designHeight - 77 + 'px';
document.body.style.minHeight = stageWrap.style.height;
viewport.style.minHeight = stageWrap.style.height;
page.style.minHeight = stageWrap.style.height;
}
function bindTabs() {
tabs.forEach(function (tab) {
tab.addEventListener('click', function () {
activeTab = tab.getAttribute('data-tab') || 'plaza';
render();
});
});
}
function init() {
countdownEndAt = resolveFallbackEndAt();
bindTabs();
render();
loadActivityStatus();
countdownTimer = window.setInterval(updateCountdown, 1000);
window.addEventListener('resize', updateStageHeight);
window.addEventListener('hyapp:i18n-ready', render);
}
window.addEventListener('beforeunload', function () {
if (countdownTimer) window.clearInterval(countdownTimer);
});
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();