CP和经理中心添加bd leader
BIN
h5/assets/yumi/cp-ranking/avatar-sample.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
h5/assets/yumi/cp-ranking/countdown-heart.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
h5/assets/yumi/cp-ranking/cp-row-frame.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
h5/assets/yumi/cp-ranking/gift-plane.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
3
h5/assets/yumi/cp-ranking/hero-mask.png
Normal file
@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 1080 948.834" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Rectangle 33" d="M0 0H1080V922C1080 922 1041 861.5 830 861.5C619 861.5 381.082 973.074 179 944C70.6212 928.407 0 858 0 858V0Z" fill="var(--fill-0, #D9D9D9)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 353 B |
BIN
h5/assets/yumi/cp-ranking/hero.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
h5/assets/yumi/cp-ranking/page-texture.jpg
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
h5/assets/yumi/cp-ranking/panel-bottom.png
Normal file
|
After Width: | Height: | Size: 202 KiB |
BIN
h5/assets/yumi/cp-ranking/panel-middle.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
h5/assets/yumi/cp-ranking/panel-top.png
Normal file
|
After Width: | Height: | Size: 202 KiB |
BIN
h5/assets/yumi/cp-ranking/rank-crown-1.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
h5/assets/yumi/cp-ranking/rank-crown-2.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
h5/assets/yumi/cp-ranking/rank-crown-3.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
h5/assets/yumi/cp-ranking/rank-heart-glow.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
h5/assets/yumi/cp-ranking/rank-heart.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
h5/assets/yumi/cp-ranking/reward-cell-frame.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
3
h5/assets/yumi/cp-ranking/reward-frame-mask.png
Normal file
@ -0,0 +1,3 @@
|
||||
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 240 240" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect id="Rectangle 43" width="240" height="240" fill="var(--fill-0, #D9D9D9)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 260 B |
BIN
h5/assets/yumi/cp-ranking/reward-frame.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
h5/assets/yumi/cp-ranking/reward-icon.png
Normal file
|
After Width: | Height: | Size: 253 KiB |
BIN
h5/assets/yumi/cp-ranking/row-wave.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
h5/assets/yumi/cp-ranking/tab-active.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
h5/assets/yumi/cp-ranking/tab-default.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
h5/assets/yumi/cp-ranking/title-art.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
h5/assets/yumi/cp-ranking/top-card.png
Normal file
|
After Width: | Height: | Size: 505 KiB |
BIN
h5/assets/yumi/cp-ranking/top-wing-left.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
h5/assets/yumi/cp-ranking/top-wing-right.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||
<title>Manager Center</title>
|
||||
<link rel="stylesheet" href="./style.css?v=20260511-0109" />
|
||||
<link rel="stylesheet" href="./style.css?v=20260522-0101" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="manager-center" aria-label="Manager Center" data-i18n-aria="page_label" data-loading="true">
|
||||
@ -66,6 +66,22 @@
|
||||
</section>
|
||||
|
||||
<section class="card action-card" id="actionCard" hidden>
|
||||
<button class="action-item" id="addBDLeaderButton" type="button" disabled>
|
||||
<span class="action-item-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
|
||||
<circle cx="9" cy="7" r="4" />
|
||||
<path d="M19 8v6" />
|
||||
<path d="M22 11h-6" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="action-item-copy">
|
||||
<span class="action-item-title" data-i18n="add_bd_leader">Add BD Leader</span>
|
||||
</span>
|
||||
<svg class="action-item-arrow" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="m9 6 6 6-6 6" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="action-item" id="giftEntryButton" type="button">
|
||||
<span class="action-item-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24">
|
||||
@ -112,6 +128,18 @@
|
||||
</svg>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section class="card bd-leader-card">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2 data-i18n="bd_leaders">BD Leaders</h2>
|
||||
<div class="section-subtitle" id="bdLeaderCount">0</div>
|
||||
</div>
|
||||
<button class="text-button" id="refreshBDLeaderButton" type="button" data-i18n="refresh">Refresh</button>
|
||||
</div>
|
||||
<div class="form-status" id="bdLeaderStatus" role="status" hidden></div>
|
||||
<div class="bd-leader-list" id="bdLeaderList"></div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div class="modal-mask" id="giftModal" role="dialog" aria-modal="true" aria-labelledby="giftModalTitle" hidden>
|
||||
@ -200,6 +228,6 @@
|
||||
</div>
|
||||
<div class="toast" id="toast" role="status" hidden></div>
|
||||
</div>
|
||||
<script src="./script.js?v=20260511-0112" defer></script>
|
||||
<script src="./script.js?v=20260522-0101" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -11,6 +11,14 @@
|
||||
"search": "بحث",
|
||||
"searching": "جار البحث...",
|
||||
"target_user": "المستخدم المستهدف",
|
||||
"bd_leaders": "قادة BD",
|
||||
"bd_leader_count": "{count} قادة BD",
|
||||
"refresh": "تحديث",
|
||||
"refreshing": "جار التحديث...",
|
||||
"no_bd_leaders": "لا يوجد قادة BD بعد",
|
||||
"add_bd_leader": "إضافة قائد BD",
|
||||
"adding_bd_leader": "جار الإضافة...",
|
||||
"add_bd_leader_success": "تمت إضافة قائد BD",
|
||||
"gift_props": "إرسال العناصر",
|
||||
"gift_type": "نوع الهدية",
|
||||
"prop_type_ride": "مركبة",
|
||||
@ -48,6 +56,11 @@
|
||||
"user_recharged": "لدى هذا المستخدم سجلات شحن ولا يمكن حظره",
|
||||
"user_already_banned": "هذا المستخدم محظور بالفعل",
|
||||
"user_not_banned": "هذا المستخدم غير محظور",
|
||||
"manager_region_required": "منطقة المدير مطلوبة",
|
||||
"bd_leader_origin_mismatch": "المستخدم ليس ضمن نظام هذا المدير",
|
||||
"user_is_admin": "لا يمكن إضافة مستخدم إداري كقائد BD",
|
||||
"user_already_bd_leader": "هذا المستخدم قائد BD بالفعل",
|
||||
"user_already_bound_bd_leader": "هذا المستخدم مرتبط بالفعل بقائد BD",
|
||||
"prop_not_giftable": "هذا العنصر غير متاح كهديّة من المدير",
|
||||
"vip_not_giftable": "لا يمكن إهداء عنصر VIP هذا",
|
||||
"request_failed": "فشل الطلب",
|
||||
|
||||
@ -11,6 +11,14 @@
|
||||
"search": "Search",
|
||||
"searching": "Searching...",
|
||||
"target_user": "Target User",
|
||||
"bd_leaders": "BD Leaders",
|
||||
"bd_leader_count": "{count} BD Leaders",
|
||||
"refresh": "Refresh",
|
||||
"refreshing": "Refreshing...",
|
||||
"no_bd_leaders": "No BD Leaders yet",
|
||||
"add_bd_leader": "Add BD Leader",
|
||||
"adding_bd_leader": "Adding...",
|
||||
"add_bd_leader_success": "BD Leader added",
|
||||
"gift_props": "Gift Props",
|
||||
"gift_type": "Gift Type",
|
||||
"prop_type_ride": "Ride",
|
||||
@ -48,6 +56,11 @@
|
||||
"user_recharged": "This user has recharge records and cannot be banned",
|
||||
"user_already_banned": "This user is already banned",
|
||||
"user_not_banned": "This user is not banned",
|
||||
"manager_region_required": "Manager region is required",
|
||||
"bd_leader_origin_mismatch": "The user is not in this manager's origin",
|
||||
"user_is_admin": "Admin users cannot be added as BD Leaders",
|
||||
"user_already_bd_leader": "This user is already a BD Leader",
|
||||
"user_already_bound_bd_leader": "This user is already bound to a BD Leader",
|
||||
"prop_not_giftable": "This prop is not available for manager gifting",
|
||||
"vip_not_giftable": "This noble prop cannot be gifted",
|
||||
"request_failed": "Request failed",
|
||||
|
||||
@ -11,6 +11,14 @@
|
||||
"search": "Cari",
|
||||
"searching": "Mencari...",
|
||||
"target_user": "Pengguna Target",
|
||||
"bd_leaders": "BD Leader",
|
||||
"bd_leader_count": "{count} BD Leader",
|
||||
"refresh": "Segarkan",
|
||||
"refreshing": "Menyegarkan...",
|
||||
"no_bd_leaders": "Belum ada BD Leader",
|
||||
"add_bd_leader": "Tambah BD Leader",
|
||||
"adding_bd_leader": "Menambahkan...",
|
||||
"add_bd_leader_success": "BD Leader ditambahkan",
|
||||
"gift_props": "Kirim Properti",
|
||||
"gift_type": "Jenis Hadiah",
|
||||
"prop_type_ride": "Kendaraan",
|
||||
@ -48,6 +56,11 @@
|
||||
"user_recharged": "Pengguna ini memiliki riwayat isi ulang dan tidak dapat diblokir",
|
||||
"user_already_banned": "Pengguna ini sudah diblokir",
|
||||
"user_not_banned": "Pengguna ini tidak diblokir",
|
||||
"manager_region_required": "Region manajer diperlukan",
|
||||
"bd_leader_origin_mismatch": "Pengguna tidak berada di origin manajer ini",
|
||||
"user_is_admin": "Pengguna admin tidak dapat ditambahkan sebagai BD Leader",
|
||||
"user_already_bd_leader": "Pengguna ini sudah menjadi BD Leader",
|
||||
"user_already_bound_bd_leader": "Pengguna ini sudah terhubung ke BD Leader",
|
||||
"prop_not_giftable": "Properti ini tidak tersedia untuk hadiah manajer",
|
||||
"vip_not_giftable": "Properti noble ini tidak dapat dikirim",
|
||||
"request_failed": "Permintaan gagal",
|
||||
|
||||
@ -11,6 +11,14 @@
|
||||
"search": "Ara",
|
||||
"searching": "Aranıyor...",
|
||||
"target_user": "Hedef Kullanıcı",
|
||||
"bd_leaders": "BD Liderleri",
|
||||
"bd_leader_count": "{count} BD Lideri",
|
||||
"refresh": "Yenile",
|
||||
"refreshing": "Yenileniyor...",
|
||||
"no_bd_leaders": "Henüz BD lideri yok",
|
||||
"add_bd_leader": "BD Lideri Ekle",
|
||||
"adding_bd_leader": "Ekleniyor...",
|
||||
"add_bd_leader_success": "BD lideri eklendi",
|
||||
"gift_props": "Eşya Gönder",
|
||||
"gift_type": "Hediye Türü",
|
||||
"prop_type_ride": "Araç",
|
||||
@ -48,6 +56,11 @@
|
||||
"user_recharged": "Bu kullanıcının yükleme kaydı var, yasaklanamaz",
|
||||
"user_already_banned": "Bu kullanıcı zaten yasaklı",
|
||||
"user_not_banned": "Bu kullanıcı yasaklı değil",
|
||||
"manager_region_required": "Yönetici bölgesi gerekli",
|
||||
"bd_leader_origin_mismatch": "Kullanıcı bu yöneticinin origin değerinde değil",
|
||||
"user_is_admin": "Admin kullanıcılar BD lideri eklenemez",
|
||||
"user_already_bd_leader": "Bu kullanıcı zaten BD lideri",
|
||||
"user_already_bound_bd_leader": "Bu kullanıcı zaten bir BD liderine bağlı",
|
||||
"prop_not_giftable": "Bu eşya yönetici hediyesi için açık değil",
|
||||
"vip_not_giftable": "Bu noble eşyası gönderilemez",
|
||||
"request_failed": "İstek başarısız",
|
||||
|
||||
@ -25,6 +25,14 @@
|
||||
search: "Search",
|
||||
searching: "Searching...",
|
||||
target_user: "Target User",
|
||||
bd_leaders: "BD Leaders",
|
||||
bd_leader_count: "{count} BD Leaders",
|
||||
refresh: "Refresh",
|
||||
refreshing: "Refreshing...",
|
||||
no_bd_leaders: "No BD Leaders yet",
|
||||
add_bd_leader: "Add BD Leader",
|
||||
adding_bd_leader: "Adding...",
|
||||
add_bd_leader_success: "BD Leader added",
|
||||
gift_props: "Gift Props",
|
||||
gift_type: "Gift Type",
|
||||
prop_type_ride: "Ride",
|
||||
@ -62,6 +70,11 @@
|
||||
user_recharged: "This user has recharge records and cannot be banned",
|
||||
user_already_banned: "This user is already banned",
|
||||
user_not_banned: "This user is not banned",
|
||||
manager_region_required: "Manager region is required",
|
||||
bd_leader_origin_mismatch: "The user is not in this manager's origin",
|
||||
user_is_admin: "Admin users cannot be added as BD Leaders",
|
||||
user_already_bd_leader: "This user is already a BD Leader",
|
||||
user_already_bound_bd_leader: "This user is already bound to a BD Leader",
|
||||
prop_not_giftable: "This prop is not available for manager gifting",
|
||||
vip_not_giftable: "This noble prop cannot be gifted",
|
||||
request_failed: "Request failed",
|
||||
@ -80,6 +93,14 @@
|
||||
search: "بحث",
|
||||
searching: "جار البحث...",
|
||||
target_user: "المستخدم المستهدف",
|
||||
bd_leaders: "قادة BD",
|
||||
bd_leader_count: "{count} قادة BD",
|
||||
refresh: "تحديث",
|
||||
refreshing: "جار التحديث...",
|
||||
no_bd_leaders: "لا يوجد قادة BD بعد",
|
||||
add_bd_leader: "إضافة قائد BD",
|
||||
adding_bd_leader: "جار الإضافة...",
|
||||
add_bd_leader_success: "تمت إضافة قائد BD",
|
||||
gift_props: "إرسال العناصر",
|
||||
gift_type: "نوع الهدية",
|
||||
prop_type_ride: "مركبة",
|
||||
@ -117,6 +138,11 @@
|
||||
user_recharged: "لدى هذا المستخدم سجلات شحن ولا يمكن حظره",
|
||||
user_already_banned: "هذا المستخدم محظور بالفعل",
|
||||
user_not_banned: "هذا المستخدم غير محظور",
|
||||
manager_region_required: "منطقة المدير مطلوبة",
|
||||
bd_leader_origin_mismatch: "المستخدم ليس ضمن نظام هذا المدير",
|
||||
user_is_admin: "لا يمكن إضافة مستخدم إداري كقائد BD",
|
||||
user_already_bd_leader: "هذا المستخدم قائد BD بالفعل",
|
||||
user_already_bound_bd_leader: "هذا المستخدم مرتبط بالفعل بقائد BD",
|
||||
prop_not_giftable: "هذا العنصر غير متاح كهديّة من المدير",
|
||||
vip_not_giftable: "لا يمكن إهداء عنصر VIP هذا",
|
||||
request_failed: "فشل الطلب",
|
||||
@ -135,6 +161,14 @@
|
||||
search: "Ara",
|
||||
searching: "Aranıyor...",
|
||||
target_user: "Hedef Kullanıcı",
|
||||
bd_leaders: "BD Liderleri",
|
||||
bd_leader_count: "{count} BD Lideri",
|
||||
refresh: "Yenile",
|
||||
refreshing: "Yenileniyor...",
|
||||
no_bd_leaders: "Henüz BD lideri yok",
|
||||
add_bd_leader: "BD Lideri Ekle",
|
||||
adding_bd_leader: "Ekleniyor...",
|
||||
add_bd_leader_success: "BD lideri eklendi",
|
||||
gift_props: "Eşya Gönder",
|
||||
gift_type: "Hediye Türü",
|
||||
prop_type_ride: "Araç",
|
||||
@ -172,6 +206,11 @@
|
||||
user_recharged: "Bu kullanıcının yükleme kaydı var, yasaklanamaz",
|
||||
user_already_banned: "Bu kullanıcı zaten yasaklı",
|
||||
user_not_banned: "Bu kullanıcı yasaklı değil",
|
||||
manager_region_required: "Yönetici bölgesi gerekli",
|
||||
bd_leader_origin_mismatch: "Kullanıcı bu yöneticinin origin değerinde değil",
|
||||
user_is_admin: "Admin kullanıcılar BD lideri eklenemez",
|
||||
user_already_bd_leader: "Bu kullanıcı zaten BD lideri",
|
||||
user_already_bound_bd_leader: "Bu kullanıcı zaten bir BD liderine bağlı",
|
||||
prop_not_giftable: "Bu eşya yönetici hediyesi için açık değil",
|
||||
vip_not_giftable: "Bu noble eşyası gönderilemez",
|
||||
request_failed: "İstek başarısız",
|
||||
@ -190,6 +229,14 @@
|
||||
search: "Cari",
|
||||
searching: "Mencari...",
|
||||
target_user: "Pengguna Target",
|
||||
bd_leaders: "BD Leader",
|
||||
bd_leader_count: "{count} BD Leader",
|
||||
refresh: "Segarkan",
|
||||
refreshing: "Menyegarkan...",
|
||||
no_bd_leaders: "Belum ada BD Leader",
|
||||
add_bd_leader: "Tambah BD Leader",
|
||||
adding_bd_leader: "Menambahkan...",
|
||||
add_bd_leader_success: "BD Leader ditambahkan",
|
||||
gift_props: "Kirim Properti",
|
||||
gift_type: "Jenis Hadiah",
|
||||
prop_type_ride: "Kendaraan",
|
||||
@ -227,6 +274,11 @@
|
||||
user_recharged: "Pengguna ini memiliki riwayat isi ulang dan tidak dapat diblokir",
|
||||
user_already_banned: "Pengguna ini sudah diblokir",
|
||||
user_not_banned: "Pengguna ini tidak diblokir",
|
||||
manager_region_required: "Region manajer diperlukan",
|
||||
bd_leader_origin_mismatch: "Pengguna tidak berada di origin manajer ini",
|
||||
user_is_admin: "Pengguna admin tidak dapat ditambahkan sebagai BD Leader",
|
||||
user_already_bd_leader: "Pengguna ini sudah menjadi BD Leader",
|
||||
user_already_bound_bd_leader: "Pengguna ini sudah terhubung ke BD Leader",
|
||||
prop_not_giftable: "Properti ini tidak tersedia untuk hadiah manajer",
|
||||
vip_not_giftable: "Properti noble ini tidak dapat dikirim",
|
||||
request_failed: "Permintaan gagal",
|
||||
@ -239,7 +291,11 @@
|
||||
target: null,
|
||||
propsType: defaultPropsType,
|
||||
props: [],
|
||||
selectedPropsId: ""
|
||||
selectedPropsId: "",
|
||||
bdLeaders: [],
|
||||
bdLeadersLoading: false,
|
||||
bdLeadersError: "",
|
||||
addingBDLeader: false
|
||||
};
|
||||
|
||||
const params = readURLParams();
|
||||
@ -293,7 +349,7 @@
|
||||
if (window.location.protocol === "file:") return fallbackMessages[normalizedLang];
|
||||
|
||||
try {
|
||||
const response = await fetch(`./locales/${normalizedLang}.json?v=20260511-0107`, { cache: "no-store" });
|
||||
const response = await fetch(`./locales/${normalizedLang}.json?v=20260522-0101`, { cache: "no-store" });
|
||||
if (response.ok) return await response.json();
|
||||
} catch (error) {
|
||||
return fallbackMessages[normalizedLang];
|
||||
@ -404,6 +460,7 @@
|
||||
renderManager(state.manager);
|
||||
renderTarget();
|
||||
renderProps();
|
||||
renderBDLeaders();
|
||||
}
|
||||
|
||||
function $(selector) {
|
||||
@ -505,6 +562,10 @@
|
||||
return "/app/h5/manager-center/props/send";
|
||||
}
|
||||
|
||||
function endpointBDLeaders() {
|
||||
return "/app/h5/manager-center/bd-leaders";
|
||||
}
|
||||
|
||||
function endpointBanUser() {
|
||||
return "/app/h5/manager-center/users/ban";
|
||||
}
|
||||
@ -637,6 +698,10 @@
|
||||
return String(user?.userId || user?.id || "-");
|
||||
}
|
||||
|
||||
function sameUserID(left, right) {
|
||||
return displayUserID(left) === displayUserID(right);
|
||||
}
|
||||
|
||||
function displayShortID(user) {
|
||||
return String(user?.actualAccount || user?.account || user?.shortId || user?.userId || user?.id || "-");
|
||||
}
|
||||
@ -663,6 +728,26 @@
|
||||
return message("days", "{count} days").replace("{count}", String(days || 0));
|
||||
}
|
||||
|
||||
function formatBDLeaderCount(count) {
|
||||
return message("bd_leader_count", "{count} BD Leaders").replace("{count}", String(count || 0));
|
||||
}
|
||||
|
||||
function formatDateTime(value) {
|
||||
if (!value) return "";
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return "";
|
||||
try {
|
||||
return new Intl.DateTimeFormat(currentLang, {
|
||||
month: "short",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit"
|
||||
}).format(date);
|
||||
} catch (error) {
|
||||
return date.toLocaleString();
|
||||
}
|
||||
}
|
||||
|
||||
function giftDays(item) {
|
||||
const days = Number(item?.days);
|
||||
if (Number.isFinite(days) && days > 0) return days;
|
||||
@ -766,16 +851,93 @@
|
||||
updateActionState();
|
||||
}
|
||||
|
||||
function isTargetBDLeader() {
|
||||
if (!state.target) return false;
|
||||
return state.bdLeaders.some((leader) => sameUserID(leader, state.target));
|
||||
}
|
||||
|
||||
function renderBDLeaders() {
|
||||
const list = $("#bdLeaderList");
|
||||
const count = $("#bdLeaderCount");
|
||||
if (count) count.textContent = formatBDLeaderCount(state.bdLeaders.length);
|
||||
if (!list) return;
|
||||
|
||||
list.replaceChildren();
|
||||
if (state.bdLeadersLoading) {
|
||||
setStatus($("#bdLeaderStatus"), message("refreshing"));
|
||||
updateActionState();
|
||||
return;
|
||||
}
|
||||
if (state.bdLeadersError) {
|
||||
setStatus($("#bdLeaderStatus"), state.bdLeadersError, "error");
|
||||
updateActionState();
|
||||
return;
|
||||
}
|
||||
if (!state.bdLeaders.length) {
|
||||
setStatus($("#bdLeaderStatus"), message("no_bd_leaders"));
|
||||
updateActionState();
|
||||
return;
|
||||
}
|
||||
setStatus($("#bdLeaderStatus"), "");
|
||||
|
||||
state.bdLeaders.forEach((leader) => {
|
||||
const row = document.createElement("div");
|
||||
row.className = "bd-leader-row";
|
||||
|
||||
const avatar = document.createElement("span");
|
||||
avatar.className = "bd-leader-avatar";
|
||||
if (leader.userAvatar) {
|
||||
const img = document.createElement("img");
|
||||
img.alt = "";
|
||||
img.src = leader.userAvatar;
|
||||
avatar.appendChild(img);
|
||||
} else {
|
||||
const fallback = document.createElement("span");
|
||||
fallback.textContent = firstLetter(displayName(leader), "B");
|
||||
avatar.appendChild(fallback);
|
||||
}
|
||||
|
||||
const copy = document.createElement("span");
|
||||
copy.className = "bd-leader-copy";
|
||||
const name = document.createElement("span");
|
||||
name.className = "bd-leader-name";
|
||||
name.textContent = displayName(leader);
|
||||
const meta = document.createElement("span");
|
||||
meta.className = "bd-leader-meta";
|
||||
const metaItems = [userIDText(leader)];
|
||||
if (leader.regionId) metaItems.push(leader.regionId);
|
||||
if (leader.originSys) metaItems.push(leader.originSys);
|
||||
meta.textContent = metaItems.join(" · ");
|
||||
copy.append(name, meta);
|
||||
|
||||
const time = document.createElement("span");
|
||||
time.className = "bd-leader-time";
|
||||
time.textContent = formatDateTime(leader.createTime);
|
||||
|
||||
row.append(avatar, copy, time);
|
||||
list.appendChild(row);
|
||||
});
|
||||
updateActionState();
|
||||
}
|
||||
|
||||
function updateActionState() {
|
||||
const addBDLeaderButton = $("#addBDLeaderButton");
|
||||
const giftEntryButton = $("#giftEntryButton");
|
||||
const banEntryButton = $("#banEntryButton");
|
||||
const unbanEntryButton = $("#unbanEntryButton");
|
||||
const refreshBDLeaderButton = $("#refreshBDLeaderButton");
|
||||
const sendButton = $("#sendPropsButton");
|
||||
const banButton = $("#banButton");
|
||||
const unbanButton = $("#unbanButton");
|
||||
if (addBDLeaderButton) {
|
||||
addBDLeaderButton.disabled = !state.target || state.addingBDLeader || isTargetBDLeader();
|
||||
const title = addBDLeaderButton.querySelector(".action-item-title");
|
||||
if (title) title.textContent = state.addingBDLeader ? message("adding_bd_leader") : message("add_bd_leader");
|
||||
}
|
||||
if (giftEntryButton) giftEntryButton.disabled = !state.target;
|
||||
if (banEntryButton) banEntryButton.disabled = !state.target;
|
||||
if (unbanEntryButton) unbanEntryButton.disabled = !state.target;
|
||||
if (refreshBDLeaderButton) refreshBDLeaderButton.disabled = state.bdLeadersLoading;
|
||||
if (sendButton) sendButton.disabled = !state.target || !state.selectedPropsId;
|
||||
if (banButton) banButton.disabled = !canBanTarget(state.target);
|
||||
if (unbanButton) unbanButton.disabled = !canUnbanTarget(state.target);
|
||||
@ -799,6 +961,23 @@
|
||||
renderProps();
|
||||
}
|
||||
|
||||
async function loadBDLeaders() {
|
||||
state.bdLeadersLoading = true;
|
||||
state.bdLeadersError = "";
|
||||
renderBDLeaders();
|
||||
try {
|
||||
const body = await requestGoJSON(endpointBDLeaders());
|
||||
state.bdLeaders = Array.isArray(body?.records) ? body.records : [];
|
||||
setStatus($("#bdLeaderStatus"), "");
|
||||
} catch (error) {
|
||||
state.bdLeadersError = friendlyError(error);
|
||||
throw error;
|
||||
} finally {
|
||||
state.bdLeadersLoading = false;
|
||||
renderBDLeaders();
|
||||
}
|
||||
}
|
||||
|
||||
async function searchUser(event) {
|
||||
event.preventDefault();
|
||||
const input = $("#accountInput");
|
||||
@ -824,6 +1003,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function addBDLeader() {
|
||||
if (!state.target || state.addingBDLeader) return;
|
||||
if (isTargetBDLeader()) {
|
||||
showToast(message("user_already_bd_leader"));
|
||||
return;
|
||||
}
|
||||
|
||||
state.addingBDLeader = true;
|
||||
updateActionState();
|
||||
try {
|
||||
await requestGoJSON(endpointBDLeaders(), {
|
||||
method: "POST",
|
||||
body: {
|
||||
userId: displayUserID(state.target)
|
||||
}
|
||||
});
|
||||
showToast(message("add_bd_leader_success"));
|
||||
await loadBDLeaders();
|
||||
} catch (error) {
|
||||
showToast(friendlyError(error));
|
||||
} finally {
|
||||
state.addingBDLeader = false;
|
||||
updateActionState();
|
||||
}
|
||||
}
|
||||
|
||||
async function sendProps() {
|
||||
if (!state.target || !state.selectedPropsId) return;
|
||||
|
||||
@ -906,6 +1111,11 @@
|
||||
if (code === "user_recharged") return message("user_recharged");
|
||||
if (code === "user_already_banned") return message("user_already_banned");
|
||||
if (code === "user_not_banned") return message("user_not_banned");
|
||||
if (code === "manager_region_required") return message("manager_region_required");
|
||||
if (code === "bd_leader_origin_mismatch") return message("bd_leader_origin_mismatch");
|
||||
if (code === "user_is_admin") return message("user_is_admin");
|
||||
if (code === "user_already_bd_leader") return message("user_already_bd_leader");
|
||||
if (code === "user_already_bound_bd_leader") return message("user_already_bound_bd_leader");
|
||||
if (code === "prop_not_giftable") return message("prop_not_giftable");
|
||||
if (code === "vip_not_giftable") return message("vip_not_giftable");
|
||||
return error?.message || message("request_failed");
|
||||
@ -928,6 +1138,12 @@
|
||||
setLanguageMenuOpen(false);
|
||||
setPropsTypeMenuOpen(false);
|
||||
});
|
||||
$("#addBDLeaderButton")?.addEventListener("click", addBDLeader);
|
||||
$("#refreshBDLeaderButton")?.addEventListener("click", () => {
|
||||
loadBDLeaders().catch((error) => {
|
||||
showToast(friendlyError(error));
|
||||
});
|
||||
});
|
||||
$("#giftEntryButton")?.addEventListener("click", openGiftModal);
|
||||
$("#banEntryButton")?.addEventListener("click", openBanModal);
|
||||
$("#unbanEntryButton")?.addEventListener("click", openUnbanModal);
|
||||
@ -973,7 +1189,7 @@
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all([loadProfile(), loadProps()]);
|
||||
await Promise.all([loadProfile(), loadProps(), loadBDLeaders()]);
|
||||
} catch (error) {
|
||||
showToast(friendlyError(error));
|
||||
} finally {
|
||||
|
||||
@ -360,6 +360,100 @@ button:disabled {
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
margin-top: 3px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.text-button {
|
||||
flex: 0 0 auto;
|
||||
min-height: 32px;
|
||||
border-radius: 8px;
|
||||
padding: 0 10px;
|
||||
background: #e7f8f4;
|
||||
color: var(--primary-dark);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.bd-leader-card {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.bd-leader-list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.bd-leader-row {
|
||||
display: grid;
|
||||
grid-template-columns: 42px minmax(0, 1fr) auto;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
min-height: 58px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.bd-leader-avatar {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
background: #eef2f5;
|
||||
}
|
||||
|
||||
.bd-leader-avatar img,
|
||||
.bd-leader-avatar span {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.bd-leader-avatar img {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.bd-leader-avatar span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--primary-dark);
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.bd-leader-copy {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.bd-leader-name {
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
font-weight: 900;
|
||||
line-height: 18px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bd-leader-meta,
|
||||
.bd-leader-time {
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.bd-leader-time {
|
||||
max-width: 76px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 88px;
|
||||
@ -764,7 +858,8 @@ textarea:focus {
|
||||
[dir="rtl"] .custom-select-button,
|
||||
[dir="rtl"] .custom-select-menu button,
|
||||
[dir="rtl"] .prop-option,
|
||||
[dir="rtl"] .action-item {
|
||||
[dir="rtl"] .action-item,
|
||||
[dir="rtl"] .bd-leader-row {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@ -776,6 +871,10 @@ textarea:focus {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
[dir="rtl"] .bd-leader-time {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
|
||||
715
h5/yumi/cp-ranking/cp-ranking.css
Normal file
@ -0,0 +1,715 @@
|
||||
:root {
|
||||
--scale: 0.36;
|
||||
--canvas-width: 1080px;
|
||||
--canvas-height: 4560px;
|
||||
--page-bg: #ffadd3;
|
||||
--deep-red: #a01626;
|
||||
--muted-pink: #da698c;
|
||||
--gold: #ffe4a6;
|
||||
--asset-root: "../../assets/yumi/cp-ranking";
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100%;
|
||||
background: var(--page-bg);
|
||||
font-family:
|
||||
"Source Han Sans SC", "PingFang SC", "Helvetica Neue", Arial, sans-serif;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--deep-red);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
button {
|
||||
font: inherit;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.page {
|
||||
width: calc(var(--canvas-width) * var(--scale));
|
||||
max-width: 100vw;
|
||||
height: calc(var(--canvas-height) * var(--scale));
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 173, 211, 0.97), rgba(255, 173, 211, 0.97)),
|
||||
url("../../assets/yumi/cp-ranking/page-texture.jpg") center top / 100% auto repeat-y;
|
||||
}
|
||||
|
||||
.canvas {
|
||||
position: relative;
|
||||
width: var(--canvas-width);
|
||||
height: var(--canvas-height);
|
||||
transform: scale(var(--scale));
|
||||
transform-origin: left top;
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 173, 211, 0.95), rgba(255, 173, 211, 0.95)),
|
||||
url("../../assets/yumi/cp-ranking/page-texture.jpg") center top / 1080px auto repeat-y;
|
||||
}
|
||||
|
||||
.asset {
|
||||
position: absolute;
|
||||
display: block;
|
||||
max-width: none;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hero-image {
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 1080px;
|
||||
height: 1027px;
|
||||
object-fit: fill;
|
||||
}
|
||||
|
||||
.title-art {
|
||||
left: -232px;
|
||||
top: 356px;
|
||||
width: 1478px;
|
||||
height: 831px;
|
||||
object-fit: fill;
|
||||
}
|
||||
|
||||
.rules-button {
|
||||
position: absolute;
|
||||
left: 940px;
|
||||
top: 169px;
|
||||
width: 176px;
|
||||
height: 94px;
|
||||
padding: 0 0 8px;
|
||||
border: 0;
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
background: url("../../assets/yumi/cp-ranking/tab-default.png") center / 100% 100% no-repeat;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.rules-button:active,
|
||||
.tab-button:active {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
.countdown {
|
||||
position: absolute;
|
||||
left: 61px;
|
||||
top: 1046px;
|
||||
width: 964px;
|
||||
height: 144px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 148px);
|
||||
justify-content: space-between;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.countdown-item {
|
||||
position: relative;
|
||||
width: 148px;
|
||||
height: 144px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.countdown-item img,
|
||||
.mini-countdown-item img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: fill;
|
||||
}
|
||||
|
||||
.countdown-value {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 43px;
|
||||
width: 148px;
|
||||
color: #fff;
|
||||
font-size: 40px;
|
||||
font-weight: 800;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.countdown-label {
|
||||
position: absolute;
|
||||
left: 176px;
|
||||
top: 43px;
|
||||
width: 40px;
|
||||
color: #a83015;
|
||||
font-size: 40px;
|
||||
font-weight: 800;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.countdown-item:last-child .countdown-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 1217px;
|
||||
width: 1059px;
|
||||
height: 182px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 340px);
|
||||
gap: 19px;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 340px;
|
||||
height: 182px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
color: #fff;
|
||||
background: url("../../assets/yumi/cp-ranking/tab-default.png") center / 100% 100% no-repeat;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.tab-button.is-active {
|
||||
background-image: url("../../assets/yumi/cp-ranking/tab-active.png");
|
||||
}
|
||||
|
||||
.tab-label {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 73px;
|
||||
width: 340px;
|
||||
height: 40px;
|
||||
color: #fff;
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tab-panel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-panel.is-active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ornate-panel {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
width: 1048px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.panel-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.panel-bg::before,
|
||||
.panel-bg::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 1048px;
|
||||
height: 308px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.panel-bg::before {
|
||||
top: 0;
|
||||
background-image: url("../../assets/yumi/cp-ranking/panel-top.png");
|
||||
}
|
||||
|
||||
.panel-bg::after {
|
||||
bottom: 0;
|
||||
background-image: url("../../assets/yumi/cp-ranking/panel-bottom.png");
|
||||
}
|
||||
|
||||
.panel-bg {
|
||||
background:
|
||||
url("../../assets/yumi/cp-ranking/panel-middle.png") center 308px / 1048px calc(100% - 616px) no-repeat;
|
||||
}
|
||||
|
||||
.ornate-panel h2 {
|
||||
position: absolute;
|
||||
left: 98px;
|
||||
right: 98px;
|
||||
margin: 0;
|
||||
color: #a83015;
|
||||
font-size: 30px;
|
||||
font-weight: 650;
|
||||
line-height: 38px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.panel-subtitle {
|
||||
position: absolute;
|
||||
left: 98px;
|
||||
right: 98px;
|
||||
margin: 0;
|
||||
color: var(--muted-pink);
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.plaza-live-panel {
|
||||
top: 1414px;
|
||||
height: 1517px;
|
||||
}
|
||||
|
||||
.plaza-live-panel h2 {
|
||||
top: 178px;
|
||||
}
|
||||
|
||||
.plaza-live-panel .panel-subtitle {
|
||||
top: 269px;
|
||||
}
|
||||
|
||||
.plaza-history-panel {
|
||||
top: 2941px;
|
||||
height: 1491px;
|
||||
}
|
||||
|
||||
.plaza-history-panel h2 {
|
||||
top: 176px;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.plaza-history-panel .panel-subtitle {
|
||||
top: 242px;
|
||||
}
|
||||
|
||||
.cp-row-list {
|
||||
position: absolute;
|
||||
left: 84px;
|
||||
width: 880px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.plaza-live-panel .cp-row-list {
|
||||
top: 332px;
|
||||
}
|
||||
|
||||
.plaza-history-panel .cp-row-list {
|
||||
top: 306px;
|
||||
}
|
||||
|
||||
.rank-list-panel .cp-row-list {
|
||||
top: 306px;
|
||||
}
|
||||
|
||||
.cp-row {
|
||||
position: relative;
|
||||
width: 880px;
|
||||
height: 237px;
|
||||
background: url("../../assets/yumi/cp-ranking/cp-row-frame.png") center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.cp-user {
|
||||
position: absolute;
|
||||
top: 33px;
|
||||
width: 166px;
|
||||
height: 171px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cp-user-left {
|
||||
left: 59px;
|
||||
}
|
||||
|
||||
.cp-user-right {
|
||||
right: 59px;
|
||||
}
|
||||
|
||||
.cp-avatar {
|
||||
width: 135px;
|
||||
height: 135px;
|
||||
margin: 0 auto;
|
||||
border: 3px solid #ffebbb;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
background: #ffd4e6;
|
||||
}
|
||||
|
||||
.cp-name {
|
||||
display: block;
|
||||
width: 166px;
|
||||
margin-top: 0;
|
||||
color: var(--deep-red);
|
||||
font-size: 25px;
|
||||
font-weight: 650;
|
||||
line-height: 36px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.row-center {
|
||||
position: absolute;
|
||||
left: 278px;
|
||||
top: 0;
|
||||
width: 324px;
|
||||
height: 237px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.row-wave {
|
||||
position: absolute;
|
||||
top: 93px;
|
||||
width: 179px;
|
||||
height: 50px;
|
||||
object-fit: fill;
|
||||
}
|
||||
|
||||
.row-wave-left {
|
||||
left: 187px;
|
||||
}
|
||||
|
||||
.row-wave-right {
|
||||
right: 187px;
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.gift-icon {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.score-heart {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: 264px;
|
||||
height: 238px;
|
||||
background: url("../../assets/yumi/cp-ranking/rank-heart-glow.png") center / contain no-repeat;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--deep-red);
|
||||
font-size: 30px;
|
||||
font-weight: 650;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 96px;
|
||||
color: var(--muted-pink);
|
||||
font-size: 24px;
|
||||
line-height: 34px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.podium {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 1392px;
|
||||
width: 1080px;
|
||||
height: 710px;
|
||||
}
|
||||
|
||||
.podium-card {
|
||||
position: absolute;
|
||||
background: url("../../assets/yumi/cp-ranking/top-card.png") center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.podium-card.rank-1 {
|
||||
left: 158px;
|
||||
top: 0;
|
||||
width: 760px;
|
||||
height: 397px;
|
||||
}
|
||||
|
||||
.podium-card.rank-2 {
|
||||
left: -70px;
|
||||
top: 392px;
|
||||
width: 608px;
|
||||
height: 303px;
|
||||
}
|
||||
|
||||
.podium-card.rank-3 {
|
||||
left: 539px;
|
||||
top: 392px;
|
||||
width: 608px;
|
||||
height: 303px;
|
||||
}
|
||||
|
||||
.podium-crown {
|
||||
position: absolute;
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.rank-1 .podium-crown {
|
||||
left: 303px;
|
||||
top: 22px;
|
||||
width: 154px;
|
||||
height: 108px;
|
||||
}
|
||||
|
||||
.rank-2 .podium-crown,
|
||||
.rank-3 .podium-crown {
|
||||
left: 245px;
|
||||
top: 29px;
|
||||
width: 118px;
|
||||
height: 83px;
|
||||
}
|
||||
|
||||
.podium-person {
|
||||
position: absolute;
|
||||
top: 158px;
|
||||
width: 181px;
|
||||
height: 239px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rank-1 .podium-person-left {
|
||||
left: 183px;
|
||||
}
|
||||
|
||||
.rank-1 .podium-person-right {
|
||||
right: 178px;
|
||||
}
|
||||
|
||||
.rank-2 .podium-person,
|
||||
.rank-3 .podium-person {
|
||||
top: 126px;
|
||||
width: 145px;
|
||||
height: 192px;
|
||||
}
|
||||
|
||||
.rank-2 .podium-person-left,
|
||||
.rank-3 .podium-person-left {
|
||||
left: 146px;
|
||||
}
|
||||
|
||||
.rank-2 .podium-person-right,
|
||||
.rank-3 .podium-person-right {
|
||||
left: 321px;
|
||||
}
|
||||
|
||||
.podium-avatar {
|
||||
width: 181px;
|
||||
height: 181px;
|
||||
border: 3px solid #ffebbb;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.rank-2 .podium-avatar,
|
||||
.rank-3 .podium-avatar {
|
||||
width: 145px;
|
||||
height: 145px;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.podium-name {
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
color: var(--deep-red);
|
||||
font-size: 30px;
|
||||
font-weight: 650;
|
||||
line-height: 36px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.rank-2 .podium-name,
|
||||
.rank-3 .podium-name {
|
||||
margin-top: 15px;
|
||||
font-size: 24px;
|
||||
line-height: 29px;
|
||||
}
|
||||
|
||||
.rank-list-panel {
|
||||
top: 2152px;
|
||||
height: 1519px;
|
||||
}
|
||||
|
||||
.mini-countdown {
|
||||
position: absolute;
|
||||
left: 99px;
|
||||
top: 173px;
|
||||
width: 849px;
|
||||
height: 115px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 118px);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.mini-countdown-item {
|
||||
position: relative;
|
||||
width: 118px;
|
||||
height: 115px;
|
||||
}
|
||||
|
||||
.mini-countdown-item span {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 29px;
|
||||
width: 118px;
|
||||
color: #fff;
|
||||
font-size: 40px;
|
||||
font-weight: 800;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mini-countdown-item b {
|
||||
position: absolute;
|
||||
left: 154px;
|
||||
top: 29px;
|
||||
color: #a83015;
|
||||
font-size: 40px;
|
||||
font-weight: 800;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.mini-countdown-item:last-child b {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.reward-panel {
|
||||
height: 1075px;
|
||||
}
|
||||
|
||||
.reward-panel-1 {
|
||||
top: 1424px;
|
||||
}
|
||||
|
||||
.reward-panel-2 {
|
||||
top: 2509px;
|
||||
}
|
||||
|
||||
.reward-panel-3 {
|
||||
top: 3594px;
|
||||
}
|
||||
|
||||
.reward-crown {
|
||||
position: absolute;
|
||||
left: 443px;
|
||||
top: 148px;
|
||||
width: 162px;
|
||||
height: 114px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.reward-panel-2 .reward-crown {
|
||||
width: 161px;
|
||||
}
|
||||
|
||||
.reward-panel-3 .reward-crown {
|
||||
width: 158px;
|
||||
}
|
||||
|
||||
.reward-grid {
|
||||
position: absolute;
|
||||
left: 104px;
|
||||
top: 272px;
|
||||
width: 824px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 240px);
|
||||
column-gap: 60px;
|
||||
row-gap: 52px;
|
||||
}
|
||||
|
||||
.reward-card {
|
||||
width: 240px;
|
||||
min-height: 280px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.reward-frame {
|
||||
position: relative;
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
background: url("../../assets/yumi/cp-ranking/reward-cell-frame.png") center / 100% 100% no-repeat;
|
||||
}
|
||||
|
||||
.reward-frame img {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 25px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.reward-title {
|
||||
margin-top: 14px;
|
||||
color: var(--deep-red);
|
||||
font-size: 24px;
|
||||
font-weight: 650;
|
||||
line-height: 30px;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
.reward-note {
|
||||
margin-top: 4px;
|
||||
color: var(--muted-pink);
|
||||
font-size: 18px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.copyright {
|
||||
position: absolute;
|
||||
left: 115px;
|
||||
top: 4472px;
|
||||
width: 850px;
|
||||
margin: 0;
|
||||
color: var(--muted-pink);
|
||||
font-size: 24px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tab-panel-rank.is-active ~ .copyright {
|
||||
top: 3711px;
|
||||
}
|
||||
|
||||
.tab-panel-rewards.is-active ~ .copyright {
|
||||
top: 4709px;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
579
h5/yumi/cp-ranking/cp-ranking.js
Normal file
@ -0,0 +1,579 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var API_BASE_URL = "https://jvapi.haiyihy.com";
|
||||
var ASSET_ROOT = "../../assets/yumi/cp-ranking";
|
||||
var DEFAULT_AVATAR = ASSET_ROOT + "/avatar-sample.png";
|
||||
var DEBUG = /(?:^|[?&])debug=1(?:&|$)/.test(window.location.search);
|
||||
var PAGE_HEIGHTS = {
|
||||
plaza: 4560,
|
||||
rank: 3825,
|
||||
rewards: 4800
|
||||
};
|
||||
|
||||
var commonHeaders = {
|
||||
authorization: "",
|
||||
"req-app-intel": "version=1.0.0;build=1;model=H5;sysVersion=web;channel=Web",
|
||||
"req-client": "H5",
|
||||
"req-imei": "H5-STATIC",
|
||||
"req-lang": "en",
|
||||
"req-sys-origin": "origin=ATYOU;originChild=ATYOU",
|
||||
"req-version": "V2",
|
||||
"req-zone": "Asia/Shanghai"
|
||||
};
|
||||
|
||||
var state = {
|
||||
activeTab: initialTab(),
|
||||
ready: false,
|
||||
rows: sampleRankRows(),
|
||||
lastRows: sampleRankRows().slice(0, 4),
|
||||
rewards: sampleRewards(),
|
||||
countdownTarget: resolveCountdownTarget()
|
||||
};
|
||||
|
||||
var dom = {};
|
||||
var countdownTimer = null;
|
||||
|
||||
init();
|
||||
|
||||
function init() {
|
||||
dom.page = document.getElementById("page");
|
||||
dom.canvas = document.getElementById("canvas");
|
||||
dom.tabs = Array.prototype.slice.call(document.querySelectorAll("[data-tab]"));
|
||||
dom.panels = Array.prototype.slice.call(document.querySelectorAll("[data-panel]"));
|
||||
dom.status = document.getElementById("status");
|
||||
dom.plazaList = document.getElementById("plazaList");
|
||||
dom.historyList = document.getElementById("historyList");
|
||||
dom.rankList = document.getElementById("rankList");
|
||||
dom.podium = document.getElementById("podium");
|
||||
dom.rewardGroups = [
|
||||
document.getElementById("rewardGroup1"),
|
||||
document.getElementById("rewardGroup2"),
|
||||
document.getElementById("rewardGroup3")
|
||||
];
|
||||
|
||||
bindEvents();
|
||||
syncScale();
|
||||
applyTab(state.activeTab);
|
||||
render();
|
||||
startCountdown();
|
||||
initializeAppConnection().then(loadRemoteData).catch(function (error) {
|
||||
console.warn("CP ranking init failed:", error);
|
||||
state.ready = true;
|
||||
render();
|
||||
});
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
window.addEventListener("resize", syncScale);
|
||||
|
||||
dom.tabs.forEach(function (button) {
|
||||
button.addEventListener("click", function () {
|
||||
applyTab(button.getAttribute("data-tab"));
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("rulesButton").addEventListener("click", function () {
|
||||
openRules();
|
||||
});
|
||||
|
||||
dom.canvas.addEventListener("click", function (event) {
|
||||
var target = event.target.closest("[data-user-id]");
|
||||
if (!target) return;
|
||||
openUser(target.getAttribute("data-user-id"));
|
||||
});
|
||||
}
|
||||
|
||||
function syncScale() {
|
||||
var viewport = Math.min(window.innerWidth || 375, 430);
|
||||
var scale = viewport / 1080;
|
||||
document.documentElement.style.setProperty("--scale", String(scale));
|
||||
}
|
||||
|
||||
function initialTab() {
|
||||
var value = getParam("tab") || "plaza";
|
||||
return PAGE_HEIGHTS[value] ? value : "plaza";
|
||||
}
|
||||
|
||||
function applyTab(tab) {
|
||||
if (!PAGE_HEIGHTS[tab]) tab = "plaza";
|
||||
state.activeTab = tab;
|
||||
document.documentElement.style.setProperty("--canvas-height", PAGE_HEIGHTS[tab] + "px");
|
||||
|
||||
dom.tabs.forEach(function (button) {
|
||||
button.classList.toggle("is-active", button.getAttribute("data-tab") === tab);
|
||||
});
|
||||
dom.panels.forEach(function (panel) {
|
||||
panel.classList.toggle("is-active", panel.getAttribute("data-panel") === tab);
|
||||
});
|
||||
|
||||
var title = tab === "rank" ? "CP Rank" : tab === "rewards" ? "Rewards" : "CP Plaza";
|
||||
setStatus(title);
|
||||
}
|
||||
|
||||
async function initializeAppConnection() {
|
||||
if (!isAppEnvironment()) {
|
||||
setHeadersFromApp(defaultBrowserHeaders());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var raw = await getAccessOriginFromApp();
|
||||
var parsed = JSON.parse(raw);
|
||||
setHeadersFromApp(normalizeAccessOrigin(parsed));
|
||||
} catch (error) {
|
||||
console.warn("Failed to parse app access origin:", error);
|
||||
setHeadersFromApp(defaultBrowserHeaders());
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRemoteData() {
|
||||
try {
|
||||
var rankingResponse = await apiGet("/activity/week-cp-gift/ranking-data?type=WEEK");
|
||||
var ranking = unwrapBody(rankingResponse) || {};
|
||||
var rows = normalizeRankRows(ranking.thisWeekUserTop);
|
||||
var lastRows = normalizeRankRows(ranking.lastWeekUserTop);
|
||||
if (rows.length) state.rows = rows;
|
||||
if (lastRows.length) state.lastRows = lastRows;
|
||||
if (!lastRows.length && rows.length) state.lastRows = rows.slice(0, 4);
|
||||
} catch (error) {
|
||||
debugLog("CP ranking data failed:", error);
|
||||
}
|
||||
|
||||
try {
|
||||
var rewardResponse = await apiGet("/activity/week-cp-gift/list-reward-pool?type=WEEK_CP_GIFT");
|
||||
var rewards = normalizeRewardGroups(unwrapBody(rewardResponse));
|
||||
if (rewards.length) state.rewards = rewards;
|
||||
} catch (error) {
|
||||
debugLog("CP reward pool failed:", error);
|
||||
}
|
||||
|
||||
state.ready = true;
|
||||
render();
|
||||
}
|
||||
|
||||
function render() {
|
||||
renderRows(dom.plazaList, state.rows.slice(0, 4), "gift");
|
||||
renderRows(dom.historyList, state.lastRows.slice(0, 4), "score");
|
||||
renderRows(dom.rankList, state.rows.slice(3, 7), "score");
|
||||
renderPodium(state.rows.slice(0, 3));
|
||||
renderRewards();
|
||||
}
|
||||
|
||||
function renderRows(container, rows, mode) {
|
||||
if (!container) return;
|
||||
if (!rows.length) {
|
||||
container.innerHTML = '<div class="cp-row"><div class="empty-state">No ranking data</div></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = rows.map(function (row) {
|
||||
return renderCpRow(row, mode);
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function renderCpRow(row, mode) {
|
||||
var left = normalizeUser(row.left);
|
||||
var right = normalizeUser(row.right);
|
||||
var total = row.total || "0";
|
||||
var center = mode === "gift"
|
||||
? '<img class="gift-icon" src="' + ASSET_ROOT + '/gift-plane.png" alt="">'
|
||||
: '<div class="score-heart">' + escapeHtml(total) + '</div>';
|
||||
return '<div class="cp-row">' +
|
||||
'<img class="row-wave row-wave-left" src="' + ASSET_ROOT + '/row-wave.png" alt="">' +
|
||||
'<img class="row-wave row-wave-right" src="' + ASSET_ROOT + '/row-wave.png" alt="">' +
|
||||
'<div class="cp-user cp-user-left" data-user-id="' + escapeAttribute(left.id) + '">' +
|
||||
'<img class="cp-avatar" src="' + escapeAttribute(left.avatar) + '" alt="" onerror="this.onerror=null;this.src="' + DEFAULT_AVATAR + '"">' +
|
||||
'<span class="cp-name">' + escapeHtml(left.name) + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="row-center">' + center + '</div>' +
|
||||
'<div class="cp-user cp-user-right" data-user-id="' + escapeAttribute(right.id) + '">' +
|
||||
'<img class="cp-avatar" src="' + escapeAttribute(right.avatar) + '" alt="" onerror="this.onerror=null;this.src="' + DEFAULT_AVATAR + '"">' +
|
||||
'<span class="cp-name">' + escapeHtml(right.name) + '</span>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function renderPodium(rows) {
|
||||
if (!dom.podium) return;
|
||||
var padded = rows.slice();
|
||||
while (padded.length < 3) padded.push(sampleRankRows()[padded.length]);
|
||||
var order = [padded[0], padded[1], padded[2]];
|
||||
dom.podium.innerHTML = order.map(function (row, index) {
|
||||
return renderPodiumCard(row, index + 1);
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function renderPodiumCard(row, rank) {
|
||||
var left = normalizeUser(row.left);
|
||||
var right = normalizeUser(row.right);
|
||||
var crown = ASSET_ROOT + "/rank-crown-" + rank + ".png";
|
||||
return '<div class="podium-card rank-' + rank + '">' +
|
||||
'<img class="podium-crown" src="' + crown + '" alt="Rank ' + rank + '">' +
|
||||
'<div class="podium-person podium-person-left" data-user-id="' + escapeAttribute(left.id) + '">' +
|
||||
'<img class="podium-avatar" src="' + escapeAttribute(left.avatar) + '" alt="" onerror="this.onerror=null;this.src="' + DEFAULT_AVATAR + '"">' +
|
||||
'<span class="podium-name">' + escapeHtml(left.name) + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="podium-person podium-person-right" data-user-id="' + escapeAttribute(right.id) + '">' +
|
||||
'<img class="podium-avatar" src="' + escapeAttribute(right.avatar) + '" alt="" onerror="this.onerror=null;this.src="' + DEFAULT_AVATAR + '"">' +
|
||||
'<span class="podium-name">' + escapeHtml(right.name) + '</span>' +
|
||||
'</div></div>';
|
||||
}
|
||||
|
||||
function renderRewards() {
|
||||
dom.rewardGroups.forEach(function (container, index) {
|
||||
if (!container) return;
|
||||
var rewards = state.rewards[index] || [];
|
||||
container.innerHTML = rewards.map(renderRewardCard).join("");
|
||||
});
|
||||
}
|
||||
|
||||
function renderRewardCard(item) {
|
||||
var icon = item.cover || ASSET_ROOT + "/reward-icon.png";
|
||||
var note = item.note ? '<div class="reward-note">' + escapeHtml(item.note) + '</div>' : "";
|
||||
return '<div class="reward-card">' +
|
||||
'<div class="reward-frame"><img src="' + escapeAttribute(icon) + '" alt="" onerror="this.onerror=null;this.src="' + ASSET_ROOT + '/reward-icon.png""></div>' +
|
||||
'<div class="reward-title">' + escapeHtml(item.title) + '</div>' +
|
||||
note +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function normalizeRankRows(list) {
|
||||
if (!Array.isArray(list)) return [];
|
||||
return list.map(function (item) {
|
||||
return {
|
||||
rank: item.rank,
|
||||
total: item.total || formatNumber(item.totalNumber),
|
||||
left: {
|
||||
id: item.userId,
|
||||
name: item.userNickname,
|
||||
avatar: item.userAvatar
|
||||
},
|
||||
right: {
|
||||
id: item.cpUserId,
|
||||
name: item.cpUserNickname,
|
||||
avatar: item.cpUserAvatar
|
||||
}
|
||||
};
|
||||
}).filter(function (row) {
|
||||
return row.left.id || row.right.id;
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeRewardGroups(body) {
|
||||
if (!Array.isArray(body)) return [];
|
||||
return body.map(function (group) {
|
||||
var list = Array.isArray(group.activityRewardProps) ? group.activityRewardProps : [];
|
||||
return list.map(function (item) {
|
||||
return {
|
||||
title: rewardTitle(item),
|
||||
note: item.remark || "",
|
||||
cover: item.cover || ""
|
||||
};
|
||||
});
|
||||
}).filter(function (group) {
|
||||
return group.length;
|
||||
}).slice(0, 3);
|
||||
}
|
||||
|
||||
function rewardTitle(item) {
|
||||
if (!item) return "Reward";
|
||||
var name = item.content || item.detailType || item.type || "Reward";
|
||||
var quantity = item.quantity || item.amount;
|
||||
if (quantity && String(name).indexOf("*") < 0) {
|
||||
return name + "*" + quantity;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
function normalizeUser(user) {
|
||||
user = user || {};
|
||||
return {
|
||||
id: user.id || "",
|
||||
name: user.name || "Namename",
|
||||
avatar: user.avatar || DEFAULT_AVATAR
|
||||
};
|
||||
}
|
||||
|
||||
function startCountdown() {
|
||||
updateCountdown();
|
||||
if (countdownTimer) window.clearInterval(countdownTimer);
|
||||
countdownTimer = window.setInterval(updateCountdown, 1000);
|
||||
}
|
||||
|
||||
function updateCountdown() {
|
||||
var remain = Math.max(0, state.countdownTarget - Date.now());
|
||||
var seconds = Math.floor(remain / 1000);
|
||||
var days = Math.floor(seconds / 86400);
|
||||
seconds -= days * 86400;
|
||||
var hours = Math.floor(seconds / 3600);
|
||||
seconds -= hours * 3600;
|
||||
var minutes = Math.floor(seconds / 60);
|
||||
seconds -= minutes * 60;
|
||||
setCountdownValue("days", days);
|
||||
setCountdownValue("hours", hours);
|
||||
setCountdownValue("minutes", minutes);
|
||||
setCountdownValue("seconds", seconds);
|
||||
}
|
||||
|
||||
function setCountdownValue(key, value) {
|
||||
document.querySelectorAll('[data-countdown="' + key + '"]').forEach(function (node) {
|
||||
node.textContent = pad2(value);
|
||||
});
|
||||
}
|
||||
|
||||
function resolveCountdownTarget() {
|
||||
var endTime = getParam("endTime") || getParam("endAt");
|
||||
if (endTime) {
|
||||
var parsed = /^\d+$/.test(endTime) ? Number(endTime) : Date.parse(endTime);
|
||||
if (parsed && parsed < 1000000000000) parsed *= 1000;
|
||||
if (parsed && parsed > Date.now()) return parsed;
|
||||
}
|
||||
return Date.now() + (2 * 86400 + 2 * 3600 + 2 * 60 + 2) * 1000;
|
||||
}
|
||||
|
||||
async function apiRequest(path, options) {
|
||||
var url = path.indexOf("http") === 0 ? path : API_BASE_URL + path;
|
||||
var init = Object.assign({ method: "GET" }, options || {});
|
||||
init.headers = Object.assign({}, commonHeaders, init.headers || {});
|
||||
if (init.body && typeof init.body !== "string" && !(init.body instanceof FormData)) {
|
||||
init.headers["Content-Type"] = "application/json";
|
||||
init.body = JSON.stringify(init.body);
|
||||
}
|
||||
var response = await fetch(url, init);
|
||||
var data = await response.json();
|
||||
if (!response.ok) throw new Error("HTTP " + response.status);
|
||||
if (data && data.status === false) throw new Error(data.errorMsg || data.message || "API Error");
|
||||
return data;
|
||||
}
|
||||
|
||||
function apiGet(path) {
|
||||
return apiRequest(path, { method: "GET" });
|
||||
}
|
||||
|
||||
function debugLog() {
|
||||
if (!DEBUG || !window.console) return;
|
||||
console.debug.apply(console, arguments);
|
||||
}
|
||||
|
||||
function unwrapBody(response) {
|
||||
if (response && Object.prototype.hasOwnProperty.call(response, "body")) return response.body;
|
||||
return response;
|
||||
}
|
||||
|
||||
function setHeadersFromApp(info) {
|
||||
var next = {};
|
||||
if (info.authorization !== undefined) next.authorization = info.authorization;
|
||||
if (info.reqLang) next["req-lang"] = info.reqLang;
|
||||
if (info.appVersion || info.buildVersion || info.appChannel) {
|
||||
var appIntel = [];
|
||||
if (info.appVersion) appIntel.push("version=" + info.appVersion);
|
||||
if (info.buildVersion) appIntel.push("build=" + info.buildVersion);
|
||||
if (info.appChannel) appIntel.push("channel=" + info.appChannel);
|
||||
if (appIntel.length) next["req-app-intel"] = appIntel.join(";");
|
||||
}
|
||||
if (info.reqImei) next["req-imei"] = info.reqImei;
|
||||
if (info.sysOrigin) {
|
||||
var origin = ["origin=" + info.sysOrigin];
|
||||
if (info.sysOriginChild) origin.push("originChild=" + info.sysOriginChild);
|
||||
next["req-sys-origin"] = origin.join(";");
|
||||
}
|
||||
commonHeaders = Object.assign({}, commonHeaders, next);
|
||||
}
|
||||
|
||||
function defaultBrowserHeaders() {
|
||||
var lang = (navigator.language || "en").toLowerCase();
|
||||
return {
|
||||
authorization: "",
|
||||
reqLang: lang.indexOf("ar") === 0 ? "ar" : lang.indexOf("tr") === 0 ? "tr" : lang.indexOf("bn") === 0 ? "bn" : "en",
|
||||
appVersion: "1.0.0",
|
||||
buildVersion: "1",
|
||||
appChannel: "Web",
|
||||
reqImei: "H5-STATIC",
|
||||
sysOrigin: "ATYOU",
|
||||
sysOriginChild: "ATYOU"
|
||||
};
|
||||
}
|
||||
|
||||
function getAccessOriginFromApp() {
|
||||
return new Promise(function (resolve) {
|
||||
var resolved = false;
|
||||
var finish = function (value) {
|
||||
if (resolved) return;
|
||||
resolved = true;
|
||||
resolve(value || "{}");
|
||||
};
|
||||
window.renderData = finish;
|
||||
window.getIosAccessOriginParam = finish;
|
||||
|
||||
if (window.app && typeof window.app.getAccessOrigin === "function") {
|
||||
try {
|
||||
finish(window.app.getAccessOrigin());
|
||||
} catch (error) {
|
||||
finish("{}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.FlutterApp && window.FlutterApp.postMessage) {
|
||||
window.FlutterApp.postMessage("requestAccessOrigin");
|
||||
}
|
||||
window.setTimeout(function () {
|
||||
finish("{}");
|
||||
}, 800);
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeAccessOrigin(data) {
|
||||
if (!data) return {};
|
||||
var appIntel = parseKeyValueList(data["Req-App-Intel"] || "");
|
||||
var sysOrigin = parseKeyValueList(data["Req-Sys-Origin"] || "");
|
||||
return {
|
||||
reqLang: data["Req-Lang"],
|
||||
authorization: data.Authorization,
|
||||
buildVersion: appIntel.build,
|
||||
appVersion: appIntel.version,
|
||||
appChannel: appIntel.channel,
|
||||
reqImei: appIntel["Req-Imei"],
|
||||
sysOrigin: sysOrigin.origin,
|
||||
sysOriginChild: sysOrigin.originChild || sysOrigin.child
|
||||
};
|
||||
}
|
||||
|
||||
function parseKeyValueList(value) {
|
||||
var result = {};
|
||||
String(value || "").split(";").forEach(function (part) {
|
||||
var index = part.indexOf("=");
|
||||
if (index < 0) return;
|
||||
result[part.slice(0, index)] = part.slice(index + 1);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function isAppEnvironment() {
|
||||
return !!(window.app || window.webkit || window.FlutterPageControl || window.FlutterApp);
|
||||
}
|
||||
|
||||
function openUser(id) {
|
||||
if (!id) return;
|
||||
try {
|
||||
if (window.app && window.app.viewUserInfo) {
|
||||
window.app.viewUserInfo(id);
|
||||
} else if (window.FlutterPageControl && window.FlutterPageControl.postMessage) {
|
||||
window.FlutterPageControl.postMessage("view_user_info:" + id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("open user failed:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function openRules() {
|
||||
var message = "CP Ranking Rules\\n1. Gifts exchanged between CPs count toward the ranking.\\n2. CPs with at least 5000 gold exchanged can appear in CP Plaza.\\n3. Final rewards follow the activity settlement result.";
|
||||
try {
|
||||
if (window.app && window.app.openRuleDialog) {
|
||||
window.app.openRuleDialog(message);
|
||||
} else if (window.FlutterPageControl && window.FlutterPageControl.postMessage) {
|
||||
window.FlutterPageControl.postMessage("open_rules:" + message);
|
||||
} else {
|
||||
window.alert(message);
|
||||
}
|
||||
} catch (error) {
|
||||
window.alert(message);
|
||||
}
|
||||
}
|
||||
|
||||
function getParam(name) {
|
||||
var hashQuery = "";
|
||||
if (window.location.hash && window.location.hash.indexOf("?") >= 0) {
|
||||
hashQuery = window.location.hash.slice(window.location.hash.indexOf("?") + 1);
|
||||
}
|
||||
var hashValue = hashQuery ? new URLSearchParams(hashQuery).get(name) : null;
|
||||
var searchValue = new URLSearchParams(window.location.search).get(name);
|
||||
return hashValue || searchValue;
|
||||
}
|
||||
|
||||
function setStatus(text) {
|
||||
if (dom.status) dom.status.textContent = text;
|
||||
}
|
||||
|
||||
function pad2(value) {
|
||||
value = Math.max(0, Number(value) || 0);
|
||||
return value < 10 ? "0" + value : String(value);
|
||||
}
|
||||
|
||||
function formatNumber(value) {
|
||||
if (value == null || value === "") return "0";
|
||||
var number = Number(value);
|
||||
if (!Number.isFinite(number)) return String(value);
|
||||
if (number >= 1000000) return (number / 1000000).toFixed(1).replace(/\\.0$/, "") + "M";
|
||||
if (number >= 1000) return (number / 1000).toFixed(1).replace(/\\.0$/, "") + "K";
|
||||
return String(number);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value == null ? "" : value)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function escapeAttribute(value) {
|
||||
return escapeHtml(value);
|
||||
}
|
||||
|
||||
function sampleRankRows() {
|
||||
var names = [
|
||||
["Ariya", "Mira"],
|
||||
["Sana", "Lina"],
|
||||
["Nora", "Yara"],
|
||||
["Hana", "Rina"],
|
||||
["Maya", "Luna"],
|
||||
["Iris", "Zara"],
|
||||
["Alia", "Nina"]
|
||||
];
|
||||
return names.map(function (pair, index) {
|
||||
return {
|
||||
rank: index + 1,
|
||||
total: ["1234567", "982340", "750120", "608880", "502340", "456700", "388200"][index],
|
||||
left: {
|
||||
id: "sample-left-" + index,
|
||||
name: pair[0],
|
||||
avatar: DEFAULT_AVATAR
|
||||
},
|
||||
right: {
|
||||
id: "sample-right-" + index,
|
||||
name: pair[1],
|
||||
avatar: DEFAULT_AVATAR
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function sampleRewards() {
|
||||
return [
|
||||
[
|
||||
{ title: "Medal*7 days", note: "(Permanent medal after getting 3 times)" },
|
||||
{ title: "Honor banner*7 days" },
|
||||
{ title: "Headwear*7days" },
|
||||
{ title: "Mount*7days" },
|
||||
{ title: "Coins*20000" },
|
||||
{ title: "Duke*7days" }
|
||||
],
|
||||
[
|
||||
{ title: "Medal*7 days", note: "(Permanent medal after getting 3 times)" },
|
||||
{ title: "Honor banner*7 days" },
|
||||
{ title: "Mount*7days" },
|
||||
{ title: "Coins*15000" },
|
||||
{ title: "Marquis*7days" }
|
||||
],
|
||||
[
|
||||
{ title: "Medal*7 days", note: "(Permanent medal after getting 3 times)" },
|
||||
{ title: "Honor banner*7 days" },
|
||||
{ title: "Coins*10000" },
|
||||
{ title: "Count*7days" }
|
||||
]
|
||||
];
|
||||
}
|
||||
})();
|
||||
123
h5/yumi/cp-ranking/index.html
Normal file
@ -0,0 +1,123 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<title>Last Weekly CP</title>
|
||||
<link rel="stylesheet" href="./cp-ranking.css?v=2026052202" />
|
||||
<script defer src="./cp-ranking.js?v=2026052202"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main class="page" id="page">
|
||||
<section class="canvas" id="canvas" aria-label="Last Weekly CP">
|
||||
<img class="hero-image asset" src="../../assets/yumi/cp-ranking/hero.png" alt="" />
|
||||
<img class="title-art asset" src="../../assets/yumi/cp-ranking/title-art.png" alt="Last Weekly CP" />
|
||||
|
||||
<button class="rules-button" id="rulesButton" type="button" aria-label="Rules">Rules</button>
|
||||
|
||||
<div class="countdown" aria-label="Activity countdown">
|
||||
<div class="countdown-item">
|
||||
<img src="../../assets/yumi/cp-ranking/countdown-heart.png" alt="" />
|
||||
<span class="countdown-value" data-countdown="days">02</span>
|
||||
<span class="countdown-label">D</span>
|
||||
</div>
|
||||
<div class="countdown-item">
|
||||
<img src="../../assets/yumi/cp-ranking/countdown-heart.png" alt="" />
|
||||
<span class="countdown-value" data-countdown="hours">02</span>
|
||||
<span class="countdown-label">H</span>
|
||||
</div>
|
||||
<div class="countdown-item">
|
||||
<img src="../../assets/yumi/cp-ranking/countdown-heart.png" alt="" />
|
||||
<span class="countdown-value" data-countdown="minutes">02</span>
|
||||
<span class="countdown-label">M</span>
|
||||
</div>
|
||||
<div class="countdown-item">
|
||||
<img src="../../assets/yumi/cp-ranking/countdown-heart.png" alt="" />
|
||||
<span class="countdown-value" data-countdown="seconds">02</span>
|
||||
<span class="countdown-label">S</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="tabs" aria-label="CP activity tabs">
|
||||
<button class="tab-button is-active" type="button" data-tab="plaza"><span class="tab-label">CP Plaza</span></button>
|
||||
<button class="tab-button" type="button" data-tab="rank"><span class="tab-label">CP Rank</span></button>
|
||||
<button class="tab-button" type="button" data-tab="rewards"><span class="tab-label">Rewards</span></button>
|
||||
</nav>
|
||||
|
||||
<section class="tab-panel tab-panel-plaza is-active" data-panel="plaza" aria-label="CP Plaza">
|
||||
<div class="ornate-panel plaza-live-panel">
|
||||
<div class="panel-bg" aria-hidden="true"></div>
|
||||
<h2>Gifts exchanged between CPs ≥ 5000 gold will be displayed here</h2>
|
||||
<p class="panel-subtitle">(Display the last 10 records, sorted in ascending order.)</p>
|
||||
<div class="cp-row-list" id="plazaList"></div>
|
||||
</div>
|
||||
|
||||
<div class="ornate-panel plaza-history-panel">
|
||||
<div class="panel-bg" aria-hidden="true"></div>
|
||||
<h2>The previous number one position</h2>
|
||||
<p class="panel-subtitle">(Display the last 10 records, sorted in ascending order.)</p>
|
||||
<div class="cp-row-list" id="historyList"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tab-panel tab-panel-rank" data-panel="rank" aria-label="CP Rank">
|
||||
<div class="podium" id="podium"></div>
|
||||
|
||||
<div class="ornate-panel rank-list-panel">
|
||||
<div class="panel-bg" aria-hidden="true"></div>
|
||||
<div class="mini-countdown">
|
||||
<div class="mini-countdown-item">
|
||||
<img src="../../assets/yumi/cp-ranking/countdown-heart.png" alt="" />
|
||||
<span data-countdown="days">02</span>
|
||||
<b>D</b>
|
||||
</div>
|
||||
<div class="mini-countdown-item">
|
||||
<img src="../../assets/yumi/cp-ranking/countdown-heart.png" alt="" />
|
||||
<span data-countdown="hours">02</span>
|
||||
<b>H</b>
|
||||
</div>
|
||||
<div class="mini-countdown-item">
|
||||
<img src="../../assets/yumi/cp-ranking/countdown-heart.png" alt="" />
|
||||
<span data-countdown="minutes">02</span>
|
||||
<b>M</b>
|
||||
</div>
|
||||
<div class="mini-countdown-item">
|
||||
<img src="../../assets/yumi/cp-ranking/countdown-heart.png" alt="" />
|
||||
<span data-countdown="seconds">02</span>
|
||||
<b>S</b>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cp-row-list" id="rankList"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tab-panel tab-panel-rewards" data-panel="rewards" aria-label="Rewards">
|
||||
<div class="ornate-panel reward-panel reward-panel-1">
|
||||
<div class="panel-bg" aria-hidden="true"></div>
|
||||
<img class="reward-crown" src="../../assets/yumi/cp-ranking/rank-crown-1.png" alt="Rank 1" />
|
||||
<div class="reward-grid" id="rewardGroup1"></div>
|
||||
</div>
|
||||
<div class="ornate-panel reward-panel reward-panel-2">
|
||||
<div class="panel-bg" aria-hidden="true"></div>
|
||||
<img class="reward-crown" src="../../assets/yumi/cp-ranking/rank-crown-2.png" alt="Rank 2" />
|
||||
<div class="reward-grid" id="rewardGroup2"></div>
|
||||
</div>
|
||||
<div class="ornate-panel reward-panel reward-panel-3">
|
||||
<div class="panel-bg" aria-hidden="true"></div>
|
||||
<img class="reward-crown" src="../../assets/yumi/cp-ranking/rank-crown-3.png" alt="Rank 3" />
|
||||
<div class="reward-grid" id="rewardGroup3"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<p class="copyright">The copyright of this activity belongs to Shila</p>
|
||||
<div class="sr-only" id="status" aria-live="polite"></div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
||||