This commit is contained in:
zhx 2026-07-16 18:52:12 +08:00
parent de739902dc
commit 5d1d743990
10 changed files with 149 additions and 51 deletions

View File

@ -10,7 +10,7 @@
<link rel="stylesheet" href="../../common/theme.css?v=20260608-cp" />
<link
rel="stylesheet"
href="./style.css?v=20260625-cp-plaza-api-reward-auto"
href="./style.css?v=20260716-cp-formation-gift-carousel"
/>
</head>
<body>
@ -177,7 +177,7 @@
})();
</script>
<script src="../../common/i18n.js?v=20260608-cp"></script>
<script src="../../common/api.js?v=20260625-cp-plaza-api"></script>
<script src="./script.js?v=20260625-cp-plaza-api-reward-auto-coin"></script>
<script src="../../common/api.js?v=20260716-cp-formation-gift"></script>
<script src="./script.js?v=20260716-cp-formation-gift-carousel"></script>
</body>
</html>

View File

@ -10,6 +10,11 @@
var REWARD_PANEL_BOTTOM_PADDING = 80;
var REWARD_STAGE_BOTTOM_PADDING = 133;
var REWARD_MIN_PANEL_HEIGHT = 320;
var PLAZA_VISIBLE_ROWS = 4;
var PLAZA_ROW_HEIGHT = 199;
var PLAZA_ROW_GAP = 24;
var PLAZA_SECONDS_PER_ROW = 3.2;
var PLAZA_FEED_LIMIT = 20;
var STORAGE_KEY = 'hy_cp_activity_end_at';
var stageHeights = {
plaza: 4283,
@ -210,10 +215,14 @@
renderAvatar('avatar-left', row.leftAvatar),
renderAvatar('avatar-right', row.rightAvatar),
'<span class="name name-left">',
'<bdi>',
escapeHTML(row.leftName),
'</bdi>',
'</span>',
'<span class="name name-right">',
'<bdi>',
escapeHTML(row.rightName),
'</bdi>',
'</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" />',
@ -222,6 +231,40 @@
].join('');
}
function renderPlazaCarousel(rows) {
var sourceRows = Array.isArray(rows) ? rows : [];
var shouldLoop = sourceRows.length > PLAZA_VISIBLE_ROWS;
var rowHTML = sourceRows.map(renderCPRow).join('');
var loopDistance =
sourceRows.length * (PLAZA_ROW_HEIGHT + PLAZA_ROW_GAP);
var loopDuration = Math.max(
sourceRows.length * PLAZA_SECONDS_PER_ROW,
PLAZA_VISIBLE_ROWS * PLAZA_SECONDS_PER_ROW
);
// 一组数据末尾保留一个 row-gap再紧接完全相同的第二组轨道移动一整组高度后第二组首行与初始首行位置一致循环重置时不会闪白或跳帧。
return [
'<div class="plaza-carousel-viewport">',
'<div class="plaza-carousel-track',
shouldLoop ? ' is-looping' : '',
'" style="--plaza-loop-distance:',
loopDistance,
'px;--plaza-loop-duration:',
loopDuration,
's">',
'<div class="plaza-carousel-set">',
rowHTML,
'</div>',
shouldLoop
? '<div class="plaza-carousel-set" aria-hidden="true">' +
rowHTML +
'</div>'
: '',
'</div>',
'</div>',
].join('');
}
function renderPlaza() {
var plazaRows = activityData.plazaRows || [];
var previousRows = activityData.previousRows || [];
@ -229,12 +272,10 @@
'<h2 class="panel-title">',
t(
'cp.plazaGiftTitle',
'CP pairs with intimacy ≥ 5000 are shown here'
'CP formation gifts worth more than 5,000 coins are shown here'
),
'</h2>',
'<div class="cp-list">',
plazaRows.map(renderCPRow).join(''),
'</div>',
renderPlazaCarousel(plazaRows),
].join('');
var panels = [renderPanel('plaza-panel-a', 1816, 1152, firstBody)];
if (previousRows.length) {
@ -463,21 +504,37 @@
};
}
function normalizePlazaEntry(entry) {
var intimacyValue = Number(
entry.intimacy_value || entry.intimacyValue || 0
function normalizeFormationGiftEntry(entry) {
var giftCoinValue = Number(
(entry &&
(entry.gift_coin_value ||
entry.giftCoinValue ||
entry.coin_spent ||
entry.coinSpent)) ||
0
);
var userA = normalizePairUser(entry.user_a);
var userB = normalizePairUser(entry.user_b);
if (intimacyValue < 5000 || !userA || !userB) return null;
var requester = normalizePairUser(
entry && (entry.requester || entry.user_a || entry.userA)
);
var target = normalizePairUser(
entry && (entry.target || entry.user_b || entry.userB)
);
// 业务定义是“超过 5000 金币”,不是大于等于;即使旧网关误返回 5000 或更低的记录H5 也不能把它展示出来。
if (
!Number.isFinite(giftCoinValue) ||
giftCoinValue <= 5000 ||
!requester ||
!target
)
return null;
return {
type: 'score',
rawScore: intimacyValue,
score: formatScore(intimacyValue),
leftName: userA.name,
rightName: userB.name,
leftAvatar: userA.avatar,
rightAvatar: userB.avatar,
rawGiftCoinValue: giftCoinValue,
score: formatScore(giftCoinValue),
leftName: requester.name,
rightName: target.name,
leftAvatar: requester.avatar,
rightAvatar: target.avatar,
};
}
@ -549,18 +606,14 @@
}
}
function applyPlazaLeaderboard(data) {
function applyPlazaFormationGiftFeed(data) {
var items = data && Array.isArray(data.items) ? data.items : [];
// CP 广场只展示真实接口里亲密值达到门槛的 CP接口为空或低于门槛时保持空列表不再用 mock 用户兜底
// feed 由后端按 CP 成立时间倒序返回H5 只做口径校验并保留返回顺序,不再按金币额排成另一个榜单
activityData.plazaRows = items
.map(normalizePlazaEntry)
.map(normalizeFormationGiftEntry)
.filter(function (item) {
return Boolean(item);
})
.sort(function (left, right) {
return right.rawScore - left.rawScore;
})
.slice(0, 4);
});
}
function loadActivityStatus() {
@ -575,23 +628,19 @@
.catch(function () {});
}
function loadPlazaLeaderboard() {
function loadPlazaFormationGiftFeed() {
if (
!window.HyAppAPI ||
!window.HyAppAPI.cpSpace ||
!window.HyAppAPI.cpSpace.intimacyLeaderboard
!window.HyAppAPI.cpSpace.formationGiftFeed
) {
return;
}
// 后端亲密榜已按亲密值降序聚合H5 仍二次过滤 5000 门槛并限制 4 个,防止旧数据或异常分页把低亲密 CP 展示出来
// 20 条可让四行视窗持续轮播,同时限制头像 DOM 和图片解码数量gateway/user-service 还会再次夹紧上限,避免参数被篡改后无界拉取
window.HyAppAPI.cpSpace
.intimacyLeaderboard({
relation_type: 'cp',
page: 1,
page_size: 4,
})
.formationGiftFeed({ limit: PLAZA_FEED_LIMIT })
.then(function (data) {
applyPlazaLeaderboard(data);
applyPlazaFormationGiftFeed(data);
render();
})
.catch(function () {});
@ -668,7 +717,7 @@
countdownEndAt = resolveFallbackEndAt();
bindTabs();
render();
loadPlazaLeaderboard();
loadPlazaFormationGiftFeed();
loadActivityStatus();
countdownTimer = window.setInterval(updateCountdown, 1000);
window.addEventListener('resize', updateStageHeight);

View File

@ -372,6 +372,38 @@ button {
top: 170px;
}
.plaza-carousel-viewport {
position: absolute;
left: 60px;
top: 170px;
width: 840px;
height: 868px;
overflow: hidden;
}
.plaza-carousel-track {
width: 840px;
will-change: auto;
}
.plaza-carousel-track.is-looping {
animation: plaza-carousel-scroll var(--plaza-loop-duration) linear infinite;
will-change: transform;
}
.plaza-carousel-set {
display: grid;
row-gap: 24px;
width: 840px;
padding-bottom: 24px;
}
@keyframes plaza-carousel-scroll {
to {
transform: translateY(calc(-1 * var(--plaza-loop-distance)));
}
}
.panel-title {
position: absolute;
left: 55px;
@ -454,6 +486,7 @@ button {
text-align: center;
white-space: nowrap;
text-overflow: ellipsis;
unicode-bidi: plaintext;
}
.cp-row .name-left {
@ -492,15 +525,17 @@ button {
.score-value {
position: absolute;
left: 359px;
left: 308px;
top: 71px;
width: 120px;
width: 222px;
height: 36px;
color: #fff7f7;
font-size: 30px;
font-weight: 900;
line-height: 36px;
text-align: center;
white-space: nowrap;
font-variant-numeric: tabular-nums;
text-shadow:
0 2px 0 #a51f32,
0 0 8px rgba(255, 255, 255, 0.32);
@ -729,3 +764,11 @@ html[dir='rtl'] .reward-note {
html[dir='rtl'] .language-switcher {
direction: ltr;
}
@media (prefers-reduced-motion: reduce) {
.plaza-carousel-track.is-looping {
animation: none;
transform: none;
will-change: auto;
}
}

View File

@ -1892,6 +1892,12 @@ var default_api = 'https://api.global-interaction.com/';
query: params || {},
});
},
formationGiftFeed: function (params) {
return request('/api/v1/cp/formation-gift-feed', {
method: 'GET',
query: params || {},
});
},
intimacyLeaderboard: function (params) {
return request('/api/v1/cp/intimacy-leaderboard', {
method: 'GET',

View File

@ -520,8 +520,8 @@
"cp.tabPlaza": "ساحة CP",
"cp.tabRank": "ترتيب CP",
"cp.tabRewards": "المكافآت",
"cp.plazaGiftTitle": "سيتم عرض أزواج CP التي لديها قيمة ألفة >= 5000 هنا",
"cp.plazaGiftSubtitle": "(عرض آخر 10 سجلات مرتبة تصاعديا.)",
"cp.plazaGiftTitle": "تظهر هنا هدايا تكوين CP التي تزيد قيمتها عن 5,000 عملة",
"cp.plazaGiftSubtitle": "تُعرض أحدث أزواج CP المؤهلة هنا باستمرار.",
"cp.previousNo1Title": "أفضل 3 في الفترة السابقة",
"cp.rewardMedal7": "وسام*7 أيام",
"cp.rewardMedalNote": "(وسام دائم بعد الحصول عليه 3 مرات)",

View File

@ -526,8 +526,8 @@
"cp.tabPlaza": "CP Plaza",
"cp.tabRank": "CP Rank",
"cp.tabRewards": "Rewards",
"cp.plazaGiftTitle": "CP pairs with intimacy >= 5000 are shown here",
"cp.plazaGiftSubtitle": "(Display the last 10 records, sorted in ascending order.)",
"cp.plazaGiftTitle": "CP formation gifts worth more than 5,000 coins are shown here",
"cp.plazaGiftSubtitle": "The latest qualifying CP pairs scroll here continuously.",
"cp.previousNo1Title": "Previous Top 3",
"cp.rewardMedal7": "Medal*7 days",
"cp.rewardMedalNote": "(Permanent medal after getting 3 times)",

View File

@ -520,8 +520,8 @@
"cp.tabPlaza": "Plaza CP",
"cp.tabRank": "Ranking CP",
"cp.tabRewards": "Recompensas",
"cp.plazaGiftTitle": "Los CP con intimidad >= 5000 se mostrarán aquí",
"cp.plazaGiftSubtitle": "(Muestra los últimos 10 registros en orden ascendente.)",
"cp.plazaGiftTitle": "Aquí se muestran los regalos para formar CP que valen más de 5.000 monedas",
"cp.plazaGiftSubtitle": "Las parejas CP elegibles más recientes se desplazan aquí continuamente.",
"cp.previousNo1Title": "Top 3 anterior",
"cp.rewardMedal7": "Medalla*7 días",
"cp.rewardMedalNote": "(Medalla permanente tras obtenerla 3 veces)",

View File

@ -487,8 +487,8 @@
"cp.tabPlaza": "CP Plaza",
"cp.tabRank": "Peringkat CP",
"cp.tabRewards": "Hadiah",
"cp.plazaGiftTitle": "CP dengan intimacy >= 5000 akan ditampilkan di sini",
"cp.plazaGiftSubtitle": "(Menampilkan 10 catatan terbaru, diurutkan naik.)",
"cp.plazaGiftTitle": "Hadiah pembentukan CP bernilai lebih dari 5.000 koin ditampilkan di sini",
"cp.plazaGiftSubtitle": "Pasangan CP terbaru yang memenuhi syarat bergulir terus di sini.",
"cp.previousNo1Title": "Top 3 periode sebelumnya",
"cp.rewardMedal7": "Medali*7 hari",
"cp.rewardMedalNote": "(Medali permanen setelah mendapatkannya 3 kali)",

View File

@ -487,8 +487,8 @@
"cp.tabPlaza": "CP Plaza",
"cp.tabRank": "CP Sıralaması",
"cp.tabRewards": "Ödüller",
"cp.plazaGiftTitle": "Samimiyet değeri >= 5000 olan CP çiftleri burada görünür",
"cp.plazaGiftSubtitle": "(Son 10 kayıt artan sırayla gösterilir.)",
"cp.plazaGiftTitle": "5.000 coinden daha değerli CP kurma hediyeleri burada gösterilir",
"cp.plazaGiftSubtitle": "Koşulları karşılayan en yeni CP çiftleri burada sürekli kayar.",
"cp.previousNo1Title": "Önceki Top 3",
"cp.rewardMedal7": "Madalya*7 gün",
"cp.rewardMedalNote": "(3 kez alındıktan sonra kalıcı madalya)",

View File

@ -493,8 +493,8 @@
"cp.tabPlaza": "CP 广场",
"cp.tabRank": "CP 排行",
"cp.tabRewards": "奖励",
"cp.plazaGiftTitle": "CP 之间亲密值 >= 5000 的 CP 会展示在这里",
"cp.plazaGiftSubtitle": "(展示最近 10 条记录,按时间升序排列。)",
"cp.plazaGiftTitle": "组 CP 时赠送价值超过 5000 金币的礼物会展示在这里",
"cp.plazaGiftSubtitle": "最近符合条件的 CP 组合会在这里持续轮播。",
"cp.previousNo1Title": "上期前三名",
"cp.rewardMedal7": "勋章*7天",
"cp.rewardMedalNote": "(获得 3 次后永久)",