充值,新公会开业活动,CP活动

This commit is contained in:
zhx 2026-06-16 16:59:29 +08:00
parent 802bc1c686
commit ce3725ae37
14 changed files with 639 additions and 168 deletions

View File

@ -7,7 +7,7 @@
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
/>
<title>New Agency Opening Event</title>
<link rel="stylesheet" href="./style.css?v=20260612-agency-opening" />
<link rel="stylesheet" href="./style.css?v=20260616-agency-opening-tabs" />
</head>
<body>
<div class="app-viewport" id="appViewport">
@ -366,6 +366,6 @@
</script>
<script src="../../common/i18n.js?v=20260612-agency-opening"></script>
<script src="../../common/api.js?v=20260612-agency-opening"></script>
<script src="./script.js?v=20260612-agency-opening"></script>
<script src="./script.js?v=20260616-agency-opening-tiers"></script>
</body>
</html>

View File

@ -250,7 +250,13 @@
.map(function (item) {
var revenue = firstValue(
item,
['opening_revenue', 'openingRevenue', 'revenue'],
[
'opening_revenue',
'openingRevenue',
'threshold_coin_spent',
'thresholdCoinSpent',
'revenue',
],
'500K'
);
var reward = firstValue(
@ -261,7 +267,7 @@
return [
'<div class="reward-row">',
'<span>',
escapeHTML(revenue),
escapeHTML(formatCoinLabel(revenue)),
'</span>',
'<span>',
escapeHTML(reward),
@ -290,9 +296,8 @@
state.qualification.identity;
document.getElementById('hostCountText').textContent =
state.qualification.hostCount +
' / ' +
state.qualification.hostRequirement +
'+';
' / > ' +
state.qualification.hostRequirement;
document.getElementById('openingStatusText').textContent =
state.qualification.openingStatus;
document.getElementById('eligibilityText').textContent = state
@ -419,6 +424,14 @@
.replace(/'/g, '&#39;');
}
function formatCoinLabel(value) {
var amount = Number(value);
if (!Number.isFinite(amount) || amount <= 0) return value;
if (amount % 1000000 === 0) return amount / 1000000 + 'M';
if (amount % 1000 === 0) return amount / 1000 + 'K';
return String(amount);
}
function init() {
document.title = t(
'agencyOpening.pageTitle',

View File

@ -130,8 +130,8 @@ button:focus-visible {
left: 18px;
top: 46px;
display: grid;
grid-template-columns: 221px repeat(5, 141px);
gap: 0;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 6px;
align-items: center;
width: 1026px;
height: 70px;
@ -141,7 +141,13 @@ button:focus-visible {
.language-tab {
position: relative;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: 100%;
height: 57px;
padding: 0 10px;
border: 2px solid #fde5cc;
border-radius: 39px;
background: linear-gradient(180deg, #4d145a, #210a34);
@ -153,47 +159,14 @@ button:focus-visible {
text-shadow: 0 1px 7px rgba(87, 16, 110, 0.82);
}
.language-tab:first-child {
width: 221px;
height: 65px;
border: 0;
border-radius: 0;
background: transparent;
isolation: isolate;
}
.language-tab span {
position: relative;
z-index: 2;
}
.language-tab:first-child::before,
.language-tab:first-child::after {
position: absolute;
content: '';
inset: 0 auto auto 0;
width: 221px;
height: 65px;
background-image: url('./assets/lang-sheen.png');
background-size: 221px 65px;
background-repeat: no-repeat;
z-index: 1;
-webkit-mask: url('./assets/lang-mask-left.svg') 0 0 / 100px 65px no-repeat;
mask: url('./assets/lang-mask-left.svg') 0 0 / 100px 65px no-repeat;
}
.language-tab:first-child::after {
transform: rotate(180deg) scaleY(-1);
-webkit-mask: url('./assets/lang-mask-right.svg') 121px 0 / 100px 65px
no-repeat;
mask: url('./assets/lang-mask-right.svg') 121px 0 / 100px 65px no-repeat;
}
.language-tab.is-active {
background: linear-gradient(180deg, #6b2a72, #2b0c46);
color: #fff8e4;
}
.language-tab:not(:first-child).is-active {
box-shadow:
inset 0 0 18px rgba(255, 222, 151, 0.2),
0 0 14px rgba(255, 203, 116, 0.22);
@ -618,14 +591,14 @@ button:focus-visible {
.rules-list {
position: absolute;
left: 119px;
top: 306px;
top: 258px;
width: 842px;
margin: 0;
padding: 0 0 0 48px;
color: var(--gold);
font-size: 32px;
font-size: 30px;
font-weight: 500;
line-height: 52px;
line-height: 48px;
text-transform: capitalize;
z-index: 3;
}

View File

@ -31,52 +31,116 @@
>
EN
</button>
<div class="language-menu" data-language-menu hidden>
<button type="button" data-lang-option="en">EN</button>
<button type="button" data-lang-option="ar">AR</button>
<button type="button" data-lang-option="tr">TR</button>
<button type="button" data-lang-option="es">ES</button>
<button type="button" data-lang-option="zh">ZH</button>
<button type="button" data-lang-option="id">ID</button>
<div
class="language-menu"
data-language-menu
hidden
>
<button type="button" data-lang-option="en">
EN
</button>
<button type="button" data-lang-option="ar">
AR
</button>
<button type="button" data-lang-option="tr">
TR
</button>
<button type="button" data-lang-option="es">
ES
</button>
<button type="button" data-lang-option="zh">
ZH
</button>
<button type="button" data-lang-option="id">
ID
</button>
</div>
</div>
<img class="hero-bg" src="./assets/hero-bg.png" alt="" aria-hidden="true" />
<img
class="hero-bg"
src="./assets/hero-bg.png"
alt=""
aria-hidden="true"
/>
<section class="countdown" aria-label="Activity countdown">
<section
class="countdown"
aria-label="Activity countdown"
>
<div class="time-unit">
<img src="./assets/countdown-heart.png" alt="" aria-hidden="true" />
<img
src="./assets/countdown-heart.png"
alt=""
aria-hidden="true"
/>
<strong data-count="days">02</strong>
<span data-i18n="cp.days">D</span>
</div>
<div class="time-unit">
<img src="./assets/countdown-heart.png" alt="" aria-hidden="true" />
<img
src="./assets/countdown-heart.png"
alt=""
aria-hidden="true"
/>
<strong data-count="hours">02</strong>
<span data-i18n="cp.hours">H</span>
</div>
<div class="time-unit">
<img src="./assets/countdown-heart.png" alt="" aria-hidden="true" />
<img
src="./assets/countdown-heart.png"
alt=""
aria-hidden="true"
/>
<strong data-count="minutes">02</strong>
<span data-i18n="cp.minutes">M</span>
</div>
<div class="time-unit">
<img src="./assets/countdown-heart.png" alt="" aria-hidden="true" />
<img
src="./assets/countdown-heart.png"
alt=""
aria-hidden="true"
/>
<strong data-count="seconds">02</strong>
<span data-i18n="cp.seconds">S</span>
</div>
</section>
<nav class="tabs" aria-label="CP activity tabs" data-i18n-aria="cp.tabsLabel">
<button class="tab is-active" type="button" data-tab="plaza">
<img src="./assets/tab-active.png" alt="" aria-hidden="true" />
<nav
class="tabs"
aria-label="CP activity tabs"
data-i18n-aria="cp.tabsLabel"
>
<button
class="tab is-active"
type="button"
data-tab="plaza"
>
<img
src="./assets/tab-active.png"
alt=""
aria-hidden="true"
/>
<span data-i18n="cp.tabPlaza">CP Plaza</span>
</button>
<button class="tab" type="button" data-tab="rank">
<img src="./assets/tab-inactive.png" alt="" aria-hidden="true" />
<img
src="./assets/tab-inactive.png"
alt=""
aria-hidden="true"
/>
<span data-i18n="cp.tabRank">CP Rank</span>
</button>
<button class="tab" type="button" data-tab="rewards">
<img src="./assets/tab-inactive.png" alt="" aria-hidden="true" />
<button
class="tab"
type="button"
data-tab="rewards"
>
<img
src="./assets/tab-inactive.png"
alt=""
aria-hidden="true"
/>
<span data-i18n="cp.tabRewards">Rewards</span>
</button>
</nav>
@ -94,9 +158,15 @@
(function () {
try {
var params = new URLSearchParams(window.location.search);
if (!params.get('lang') && !window.localStorage.getItem('cp_activity_lang_ready')) {
if (
!params.get('lang') &&
!window.localStorage.getItem('cp_activity_lang_ready')
) {
window.localStorage.setItem('hyapp_h5_lang', 'en');
window.localStorage.setItem('cp_activity_lang_ready', '1');
window.localStorage.setItem(
'cp_activity_lang_ready',
'1'
);
}
} catch (error) {
document.documentElement.lang = 'en';
@ -104,6 +174,7 @@
})();
</script>
<script src="../../common/i18n.js?v=20260608-cp"></script>
<script src="../../common/api.js?v=20260608-cp"></script>
<script src="./script.js?v=20260608-cp"></script>
</body>
</html>

View File

@ -10,6 +10,11 @@
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');
@ -25,9 +30,24 @@
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' },
{
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: [
{
@ -98,17 +118,31 @@
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;
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;
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;
return (
Date.now() +
2 * 86400 * 1000 +
2 * 3600 * 1000 +
2 * 60 * 1000 +
2 * 1000
);
}
}
@ -126,9 +160,11 @@
function updateCountdown() {
var parts = getTimeParts();
Object.keys(parts).forEach(function (key) {
document.querySelectorAll('[data-count="' + key + '"]').forEach(function (node) {
node.textContent = twoDigits(parts[key]);
});
document
.querySelectorAll('[data-count="' + key + '"]')
.forEach(function (node) {
node.textContent = twoDigits(parts[key]);
});
});
}
@ -152,23 +188,28 @@
}
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('');
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="./assets/avatar-sample.png" alt="" /></div>',
'<div class="avatar avatar-right"><img src="./assets/avatar-sample.png" alt="" /></div>',
'<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>',
@ -185,10 +226,16 @@
function renderPlaza() {
var firstBody = [
'<h2 class="panel-title">',
t('cp.plazaGiftTitle', 'Gifts exchanged between CPs ≥ 5000 gold will be displayed here'),
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.)'),
t(
'cp.plazaGiftSubtitle',
'(Display the last 10 records, sorted in ascending order.)'
),
'</p>',
'<div class="cp-list">',
mockData.plaza.exchangeRows.map(renderCPRow).join(''),
@ -199,7 +246,10 @@
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.)'),
t(
'cp.plazaGiftSubtitle',
'(Display the last 10 records, sorted in ascending order.)'
),
'</p>',
'<div class="cp-list">',
mockData.plaza.previousRows.map(renderCPRow).join(''),
@ -241,7 +291,12 @@
}
function renderPodiumCard(item) {
var crown = item.place === 1 ? 'rank-crown-gold.png' : item.place === 2 ? 'rank-crown-silver.png' : 'crown-bronze.png';
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,
@ -250,8 +305,12 @@
'<img class="podium-crown" src="./assets/',
crown,
'" alt="" aria-hidden="true" />',
'<div class="avatar podium-avatar left"><img src="./assets/avatar-sample.png" alt="" /></div>',
'<div class="avatar podium-avatar right"><img src="./assets/avatar-sample.png" alt="" /></div>',
'<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>',
@ -271,29 +330,46 @@
}
function renderRank() {
var rankRows = activityData.rank || mockData.rank;
var podium = [
'<section class="rank-podium">',
mockData.rank.map(renderPodiumCard).join(''),
rankRows.slice(0, 3).map(renderPodiumCard).join(''),
'</section>',
].join('');
var panelBody = [renderInlineCountdown(), '<div class="cp-list">', mockData.rank.concat(mockData.rank.slice(0, 1)).map(renderRankRow).join(''), '</div>'].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="./assets/reward-frame.png" alt="" aria-hidden="true" /></div>',
'<div class="reward-icon-box"><img src="',
escapeHTML(cover),
'" alt="" aria-hidden="true" /></div>',
'<div class="reward-name">',
t(item.key, item.key),
escapeHTML(name),
'</div>',
item.noteKey ? '<div class="reward-note">' + t(item.noteKey, '') + '</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 className = [
'reward-panel',
'reward-' + ['a', 'b', 'c'][index],
].join(' ');
var top = [1816, 2784, 3752][index];
var body = [
'<img class="reward-crown" src="./assets/',
@ -307,12 +383,95 @@
}
function renderRewards() {
return mockData.rewards.map(renderRewardPanel).join('');
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();
tabContent.innerHTML =
activeTab === 'rank'
? renderRank()
: activeTab === 'rewards'
? renderRewards()
: renderPlaza();
applyTabState();
updateStageHeight();
updateCountdown();
@ -322,7 +481,9 @@
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';
tab.querySelector('img').src = isActive
? './assets/tab-active.png'
: './assets/tab-inactive.png';
});
page.setAttribute('data-tab', activeTab);
}
@ -331,11 +492,16 @@
var designHeight = stageHeights[activeTab] || stageHeights.plaza;
stage.style.minHeight = designHeight + 'px';
stage.style.height = designHeight + 'px';
stageWrap.style.height = Math.ceil(designHeight * (VIEWPORT_WIDTH / DESIGN_WIDTH)) + 'px';
stageWrap.style.height =
Math.ceil(designHeight * (VIEWPORT_WIDTH / DESIGN_WIDTH)) + 'px';
copyright.style.top = designHeight - 77 + 'px';
var scale = Math.min(window.innerWidth / VIEWPORT_WIDTH, 1);
document.documentElement.style.setProperty('--app-scale', String(scale));
document.body.style.minHeight = Math.ceil(stageWrap.offsetHeight * scale) + 'px';
document.documentElement.style.setProperty(
'--app-scale',
String(scale)
);
document.body.style.minHeight =
Math.ceil(stageWrap.offsetHeight * scale) + 'px';
viewport.style.minHeight = stageWrap.style.height;
page.style.minHeight = stageWrap.style.height;
}
@ -353,6 +519,7 @@
countdownEndAt = resolveFallbackEndAt();
bindTabs();
render();
loadActivityStatus();
countdownTimer = window.setInterval(updateCountdown, 1000);
window.addEventListener('resize', updateStageHeight);
window.addEventListener('hyapp:i18n-ready', render);

View File

@ -19,15 +19,11 @@ body {
width: 100%;
min-height: 100%;
overflow-x: hidden;
background: #1d080d;
background: var(--cp-red);
color: #fff;
font-family:
"Source Han Sans SC",
"Noto Sans CJK SC",
Arial,
"Helvetica Neue",
Helvetica,
sans-serif;
'Source Han Sans SC', 'Noto Sans CJK SC', Arial, 'Helvetica Neue',
Helvetica, sans-serif;
}
button {
@ -54,7 +50,7 @@ button {
width: 375px;
min-height: 1300px;
overflow: hidden;
background: #1d080d;
background: var(--cp-red);
}
.stage-wrap {
@ -89,6 +85,8 @@ button {
height: 1919px;
object-fit: cover;
pointer-events: none;
transform: scale(1.04);
transform-origin: top center;
}
.language-switcher {
@ -290,7 +288,12 @@ button {
border: 8px solid #f62658;
border-radius: 48px;
background:
radial-gradient(circle at 50% 0%, rgba(255, 255, 255, 0.98) 0, rgba(255, 255, 255, 0.86) 23%, rgba(255, 232, 236, 0.92) 100%),
radial-gradient(
circle at 50% 0%,
rgba(255, 255, 255, 0.98) 0,
rgba(255, 255, 255, 0.86) 23%,
rgba(255, 232, 236, 0.92) 100%
),
linear-gradient(180deg, #fff8f8 0%, #ffe6eb 100%);
box-shadow:
inset 0 0 0 4px rgba(255, 204, 119, 0.95),
@ -303,7 +306,7 @@ button {
.ornate-panel::after {
position: absolute;
content: "";
content: '';
pointer-events: none;
}
@ -313,7 +316,12 @@ button {
top: 0;
z-index: 1;
height: 14px;
background: linear-gradient(90deg, transparent, rgba(255, 241, 177, 0.95), transparent);
background: linear-gradient(
90deg,
transparent,
rgba(255, 241, 177, 0.95),
transparent
);
filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.8));
}
@ -336,7 +344,7 @@ button {
.panel-mid {
top: 89px;
bottom: 85px;
background-image: url("./assets/panel-mid.png");
background-image: url('./assets/panel-mid.png');
background-size: 960px 184px;
background-repeat: repeat-y;
}
@ -693,15 +701,15 @@ button {
line-height: 22px;
}
html[dir="rtl"] .tab span,
html[dir="rtl"] .panel-title,
html[dir="rtl"] .panel-subtitle,
html[dir="rtl"] .copyright,
html[dir="rtl"] .reward-name,
html[dir="rtl"] .reward-note {
html[dir='rtl'] .tab span,
html[dir='rtl'] .panel-title,
html[dir='rtl'] .panel-subtitle,
html[dir='rtl'] .copyright,
html[dir='rtl'] .reward-name,
html[dir='rtl'] .reward-note {
direction: rtl;
}
html[dir="rtl"] .language-switcher {
html[dir='rtl'] .language-switcher {
direction: ltr;
}

View File

@ -747,6 +747,15 @@ var default_api = 'https://api.global-interaction.com/';
},
};
var cpWeeklyRankAPI = {
status: function (params) {
return request('/api/v1/activities/cp-weekly-rank', {
method: 'GET',
query: params || {},
});
},
};
// 邀请活动 H5 只关心当前用户本月状态和领取动作;有效人数、累计充值和最高档差额都以后端聚合事实为准。
// 领取时页面必须传入本次点击生成的 command_id服务端按用户、周期、档位和 command_id 做幂等发奖。
var inviteActivityRewardAPI = {
@ -932,6 +941,7 @@ var default_api = 'https://api.global-interaction.com/';
level: levelAPI,
task: taskAPI,
weeklyStar: weeklyStarAPI,
cpWeeklyRank: cpWeeklyRankAPI,
inviteActivityReward: inviteActivityRewardAPI,
game: gameAPI,
};

View File

@ -494,6 +494,7 @@
"recharge.orderStatus": "الحالة",
"recharge.payRedirecting": "جار فتح صفحة الدفع...",
"recharge.payOpenHint": "افتح صفحة الدفع وأكمل الدفع.",
"recharge.confirmingPayment": "جار تأكيد حالة الدفع...",
"recharge.openPayment": "فتح الدفع",
"recharge.usdtPayment": "دفع USDT",
"recharge.usdtAddress": "عنوان USDT-TRC20",

View File

@ -500,6 +500,7 @@
"recharge.orderStatus": "Status",
"recharge.payRedirecting": "Opening payment page...",
"recharge.payOpenHint": "Open the payment page and finish the payment.",
"recharge.confirmingPayment": "Confirming payment status...",
"recharge.openPayment": "Open payment",
"recharge.usdtPayment": "USDT payment",
"recharge.usdtAddress": "USDT-TRC20 address",

View File

@ -494,6 +494,7 @@
"recharge.orderStatus": "Estado",
"recharge.payRedirecting": "Abriendo página de pago...",
"recharge.payOpenHint": "Abre la página de pago y completa el pago.",
"recharge.confirmingPayment": "Confirmando el estado del pago...",
"recharge.openPayment": "Abrir pago",
"recharge.usdtPayment": "Pago USDT",
"recharge.usdtAddress": "Dirección USDT-TRC20",

View File

@ -461,6 +461,7 @@
"recharge.orderStatus": "Status",
"recharge.payRedirecting": "Membuka halaman pembayaran...",
"recharge.payOpenHint": "Buka halaman pembayaran dan selesaikan pembayaran.",
"recharge.confirmingPayment": "Mengonfirmasi status pembayaran...",
"recharge.openPayment": "Buka pembayaran",
"recharge.usdtPayment": "Pembayaran USDT",
"recharge.usdtAddress": "Alamat USDT-TRC20",

View File

@ -461,6 +461,7 @@
"recharge.orderStatus": "Durum",
"recharge.payRedirecting": "Ödeme sayfasıılıyor...",
"recharge.payOpenHint": "Ödeme sayfasınıın ve ödemeyi tamamlayın.",
"recharge.confirmingPayment": "Ödeme durumu doğrulanıyor...",
"recharge.openPayment": "Ödemeyi aç",
"recharge.usdtPayment": "USDT ödeme",
"recharge.usdtAddress": "USDT-TRC20 adresi",

View File

@ -467,6 +467,7 @@
"recharge.orderStatus": "状态",
"recharge.payRedirecting": "正在打开支付页面...",
"recharge.payOpenHint": "打开支付页面并完成付款。",
"recharge.confirmingPayment": "正在确认支付状态...",
"recharge.openPayment": "打开支付",
"recharge.usdtPayment": "USDT 支付",
"recharge.usdtAddress": "USDT-TRC20 收款地址",

View File

@ -1032,6 +1032,7 @@
(function () {
var PENDING_MIFAPAY_ORDER_KEY =
'hyapp_recharge_pending_mifapay_order';
var RETURN_MIFAPAY_ORDER_QUERY_KEY = 'hyapp_order_id';
var PENDING_MIFAPAY_MAX_AGE_MS = 24 * 60 * 60 * 1000;
var state = {
context: null,
@ -1043,6 +1044,8 @@
pollingTimer: 0,
loading: false,
orderNoticeKey: '',
confirmingMifaPay: false,
lastMifaPayResumeAtMS: 0,
};
var nodes = {};
@ -1111,8 +1114,8 @@
bindEvents();
var current = window.HyAppParams
? window.HyAppParams.parse()
: { raw: new URLSearchParams(window.location.search) };
var raw = current.raw || new URLSearchParams();
: { raw: currentRawParams() };
var raw = current.raw || currentRawParams();
var pendingOrder = readPendingMifaPayOrder();
var displayUserID =
raw.get('display_user_id') ||
@ -1153,6 +1156,12 @@
nodes.copyAddress.addEventListener('click', copyAddress);
nodes.copyOrderID.addEventListener('click', copyOrderID);
nodes.txForm.addEventListener('submit', submitTx);
window.addEventListener('pageshow', resumeMifaPayOrder);
window.addEventListener('focus', resumeMifaPayOrder);
document.addEventListener(
'visibilitychange',
resumeVisibleMifaPayOrder
);
window.addEventListener('hyapp:i18n-ready', function () {
document.title = t(
'recharge.pageTitle',
@ -1405,8 +1414,8 @@
state.loading || !state.selectedMethodKey;
}
// 订单弹窗是支付后的唯一操作入口MiFaPay 展示打开支付按钮USDT 展示地址和 txid 表单
// 弹窗关闭只影响展示,不会取消轮询;订单终态仍由服务端查询结果驱动
// 订单弹窗是支付中的操作入口;终态由服务端查询结果驱动,成功后关闭弹窗,避免继续展示打开支付按钮
// 失败仍保留弹窗展示失败原因,方便用户看到三方拒单或本地校验失败的信息
function renderOrder() {
var order = state.order;
nodes.orderModal.classList.toggle(
@ -1426,7 +1435,9 @@
nodes.usdtPanel.classList.toggle('hidden', !isUSDT);
nodes.mifapayPanel.classList.toggle(
'hidden',
order.provider_code !== 'mifapay' || !order.pay_url
order.provider_code !== 'mifapay' ||
!order.pay_url ||
isTerminalOrder(order)
);
if (order.provider_code === 'usdt_trc20') {
nodes.receiveAddress.textContent =
@ -1449,10 +1460,15 @@
} else if (order.provider_code === 'mifapay') {
setStatus(
nodes.orderMessage,
t(
'recharge.payOpenHint',
'Open the payment page and finish the payment.'
)
state.confirmingMifaPay
? t(
'recharge.confirmingPayment',
'Confirming payment status...'
)
: t(
'recharge.payOpenHint',
'Open the payment page and finish the payment.'
)
);
} else {
setStatus(nodes.orderMessage, '');
@ -1546,16 +1562,13 @@
provider_code: method.providerCode,
payment_method_id: method.methodID || 0,
command_id: commandID(product, method),
return_url: window.location.href,
return_url: mifaPayReturnURL(),
})
.then(function (payload) {
state.order = payload.order || payload;
state.orderModalOpen = true;
renderOrder();
if (
state.order.status === 'credited' ||
state.order.status === 'failed'
) {
if (isTerminalOrder(state.order)) {
applyOrderTerminalSideEffects(state.order);
return;
}
@ -1629,36 +1642,58 @@
}
// 轮询只在订单存在时启动credited/failed 是终态,终态后停止,避免 H5 后台持续打接口。
// 返回 H5 后必须立即查一次,不再等第一个 5 秒周期,否则用户会看到“已返回但无反馈”。
function startPolling() {
window.clearInterval(state.pollingTimer);
if (!state.order || !state.order.order_id) return;
applyOrderTerminalSideEffects(state.order);
if (isTerminalOrder(state.order)) {
applyOrderTerminalSideEffects(state.order);
return;
}
refreshOrderStatus();
state.pollingTimer = window.setInterval(function () {
if (
!state.order ||
state.order.status === 'credited' ||
state.order.status === 'failed'
) {
if (!state.order || isTerminalOrder(state.order)) {
window.clearInterval(state.pollingTimer);
return;
}
window.HyAppAPI.recharge
.getOrder(
state.order.order_id,
targetDisplayUserID()
)
.then(function (payload) {
state.order = payload.order || payload;
renderOrder();
applyOrderTerminalSideEffects(state.order);
})
.catch(function () {});
refreshOrderStatus();
}, 5000);
}
function refreshOrderStatus() {
if (!state.order || !state.order.order_id) {
return Promise.resolve();
}
if (isTerminalOrder(state.order)) {
applyOrderTerminalSideEffects(state.order);
return Promise.resolve(state.order);
}
if (state.order.provider_code === 'mifapay') {
state.confirmingMifaPay = true;
state.orderModalOpen = true;
renderOrder();
}
return window.HyAppAPI.recharge
.getOrder(state.order.order_id, targetDisplayUserID())
.then(function (payload) {
state.order = payload.order || payload;
renderOrder();
applyOrderTerminalSideEffects(state.order);
return state.order;
})
.catch(function () {});
}
function restorePendingMifaPayOrder() {
var pending = readPendingMifaPayOrder();
if (!pending || state.order) return;
if (!pending) return;
if (
state.order &&
state.order.order_id &&
state.order.order_id !== pending.order_id
) {
return;
}
var accountDisplayUserID = currentAccountDisplayUserID();
if (
pending.display_user_id &&
@ -1667,6 +1702,17 @@
) {
return;
}
if (
state.order &&
state.order.order_id === pending.order_id
) {
state.confirmingMifaPay = true;
state.orderModalOpen = true;
renderOrder();
startPolling();
return;
}
state.confirmingMifaPay = true;
window.HyAppAPI.recharge
.getOrder(
pending.order_id,
@ -1677,10 +1723,7 @@
state.orderModalOpen = true;
renderOrder();
applyOrderTerminalSideEffects(state.order);
if (
state.order.status !== 'credited' &&
state.order.status !== 'failed'
) {
if (!isTerminalOrder(state.order)) {
startPolling();
}
})
@ -1689,16 +1732,48 @@
});
}
function applyOrderTerminalSideEffects(order) {
function resumeVisibleMifaPayOrder() {
if (
!order ||
(order.status !== 'credited' &&
order.status !== 'failed')
document.visibilityState &&
document.visibilityState !== 'visible'
) {
return;
}
resumeMifaPayOrder();
}
function resumeMifaPayOrder() {
if (!state.context) return;
if (Date.now() - state.lastMifaPayResumeAtMS < 800) {
return;
}
state.lastMifaPayResumeAtMS = Date.now();
if (
state.order &&
state.order.provider_code === 'mifapay' &&
!isTerminalOrder(state.order)
) {
state.confirmingMifaPay = true;
state.orderModalOpen = true;
renderOrder();
startPolling();
return;
}
restorePendingMifaPayOrder();
}
function applyOrderTerminalSideEffects(order) {
if (!isTerminalOrder(order)) {
return;
}
state.confirmingMifaPay = false;
clearPendingMifaPayOrder(order.order_id);
clearReturnedMifaPayOrderQuery();
window.clearInterval(state.pollingTimer);
if (order.status === 'credited') {
state.orderModalOpen = false;
renderOrder();
}
var noticeKey = order.order_id + ':' + order.status;
if (state.orderNoticeKey === noticeKey) return;
state.orderNoticeKey = noticeKey;
@ -1725,6 +1800,8 @@
}
function readPendingMifaPayOrder() {
var returned = readReturnedMifaPayOrder();
if (returned) return returned;
try {
var raw = storageGet(PENDING_MIFAPAY_ORDER_KEY);
if (!raw) return null;
@ -1745,6 +1822,21 @@
}
}
function readReturnedMifaPayOrder() {
var raw = currentRawParams();
var orderID = String(
raw.get(RETURN_MIFAPAY_ORDER_QUERY_KEY) || ''
).trim();
if (!orderID) return null;
return {
order_id: orderID,
display_user_id:
raw.get('display_user_id') ||
currentAccountDisplayUserID(),
created_at_ms: Date.now(),
};
}
function clearPendingMifaPayOrder(orderID) {
var pending = null;
try {
@ -1764,6 +1856,60 @@
}
}
function clearReturnedMifaPayOrderQuery() {
try {
var url = new URL(window.location.href);
var changed = false;
if (
url.searchParams.has(RETURN_MIFAPAY_ORDER_QUERY_KEY)
) {
url.searchParams.delete(
RETURN_MIFAPAY_ORDER_QUERY_KEY
);
changed = true;
}
var hash = url.hash || '';
var hashQueryIndex = hash.indexOf('?');
if (hashQueryIndex >= 0) {
var hashPath = hash.slice(0, hashQueryIndex);
var hashParams = new URLSearchParams(
hash.slice(hashQueryIndex + 1)
);
if (
hashParams.has(RETURN_MIFAPAY_ORDER_QUERY_KEY)
) {
hashParams.delete(
RETURN_MIFAPAY_ORDER_QUERY_KEY
);
url.hash = hashParams.toString()
? hashPath + '?' + hashParams.toString()
: hashPath;
changed = true;
}
}
if (
window.HyAppParams &&
window.HyAppParams.current &&
window.HyAppParams.current.raw
) {
window.HyAppParams.current.raw.delete(
RETURN_MIFAPAY_ORDER_QUERY_KEY
);
}
if (
changed &&
window.history &&
window.history.replaceState
) {
window.history.replaceState(
window.history.state,
document.title,
url.toString()
);
}
} catch (error) {}
}
function storageGet(key) {
try {
return window.localStorage
@ -1869,6 +2015,83 @@
: '';
}
function isTerminalOrder(order) {
return (
!!order &&
(order.status === 'credited' ||
order.status === 'failed')
);
}
function mifaPayReturnURL() {
try {
var url = new URL(window.location.href);
var displayUserID =
targetDisplayUserID() ||
String(nodes.displayUserID.value || '').trim();
if (displayUserID) {
url.searchParams.set(
'display_user_id',
displayUserID
);
}
if (window.HyAppAPI && window.HyAppAPI.getAppCode) {
url.searchParams.set(
'app_code',
window.HyAppAPI.getAppCode()
);
}
url.searchParams.set(
'language',
currentLanguageForReturnURL()
);
url.searchParams.delete(RETURN_MIFAPAY_ORDER_QUERY_KEY);
return url.toString();
} catch (error) {
return window.location.href;
}
}
function currentLanguageForReturnURL() {
var raw = currentRawParams();
var keys = [
'language',
'lang',
'locale',
'app_lang',
'appLanguage',
];
for (var i = 0; i < keys.length; i += 1) {
var value = raw.get(keys[i]);
if (value) return value;
}
return document.documentElement.lang || 'en';
}
function currentRawParams() {
if (
window.HyAppParams &&
window.HyAppParams.current &&
window.HyAppParams.current.raw
) {
return window.HyAppParams.current.raw;
}
var params = new URLSearchParams(window.location.search);
var hash = window.location.hash || '';
var hashQueryIndex = hash.indexOf('?');
if (hashQueryIndex >= 0) {
var hashParams = new URLSearchParams(
hash.slice(hashQueryIndex + 1)
);
hashParams.forEach(function (value, key) {
if (!params.has(key)) {
params.set(key, value);
}
});
}
return params;
}
// command_id 给服务端幂等使用,包含时间、商品、方式和随机后缀;真实订单号仍由后端生成。
// 三方拒单会把本地订单置失败,后续重用同一 command_id 只会回放旧失败结果,所以每次点击都必须生成新命令。
function commandID(product, method) {