diff --git a/h5/assets/yumi/cp-ranking/avatar-sample.png b/h5/assets/yumi/cp-ranking/avatar-sample.png new file mode 100644 index 0000000..6d0410b Binary files /dev/null and b/h5/assets/yumi/cp-ranking/avatar-sample.png differ diff --git a/h5/assets/yumi/cp-ranking/countdown-heart.png b/h5/assets/yumi/cp-ranking/countdown-heart.png new file mode 100644 index 0000000..7dc39ce Binary files /dev/null and b/h5/assets/yumi/cp-ranking/countdown-heart.png differ diff --git a/h5/assets/yumi/cp-ranking/cp-row-frame.png b/h5/assets/yumi/cp-ranking/cp-row-frame.png new file mode 100644 index 0000000..beada06 Binary files /dev/null and b/h5/assets/yumi/cp-ranking/cp-row-frame.png differ diff --git a/h5/assets/yumi/cp-ranking/gift-plane.png b/h5/assets/yumi/cp-ranking/gift-plane.png new file mode 100644 index 0000000..91ed66d Binary files /dev/null and b/h5/assets/yumi/cp-ranking/gift-plane.png differ diff --git a/h5/assets/yumi/cp-ranking/hero-mask.png b/h5/assets/yumi/cp-ranking/hero-mask.png new file mode 100644 index 0000000..4325e51 --- /dev/null +++ b/h5/assets/yumi/cp-ranking/hero-mask.png @@ -0,0 +1,3 @@ + + + diff --git a/h5/assets/yumi/cp-ranking/hero.png b/h5/assets/yumi/cp-ranking/hero.png new file mode 100644 index 0000000..40fc6a1 Binary files /dev/null and b/h5/assets/yumi/cp-ranking/hero.png differ diff --git a/h5/assets/yumi/cp-ranking/page-texture.jpg b/h5/assets/yumi/cp-ranking/page-texture.jpg new file mode 100644 index 0000000..b09b5b9 Binary files /dev/null and b/h5/assets/yumi/cp-ranking/page-texture.jpg differ diff --git a/h5/assets/yumi/cp-ranking/panel-bottom.png b/h5/assets/yumi/cp-ranking/panel-bottom.png new file mode 100644 index 0000000..53ba53f Binary files /dev/null and b/h5/assets/yumi/cp-ranking/panel-bottom.png differ diff --git a/h5/assets/yumi/cp-ranking/panel-middle.png b/h5/assets/yumi/cp-ranking/panel-middle.png new file mode 100644 index 0000000..9054f6c Binary files /dev/null and b/h5/assets/yumi/cp-ranking/panel-middle.png differ diff --git a/h5/assets/yumi/cp-ranking/panel-top.png b/h5/assets/yumi/cp-ranking/panel-top.png new file mode 100644 index 0000000..aae1b95 Binary files /dev/null and b/h5/assets/yumi/cp-ranking/panel-top.png differ diff --git a/h5/assets/yumi/cp-ranking/rank-crown-1.png b/h5/assets/yumi/cp-ranking/rank-crown-1.png new file mode 100644 index 0000000..36ff918 Binary files /dev/null and b/h5/assets/yumi/cp-ranking/rank-crown-1.png differ diff --git a/h5/assets/yumi/cp-ranking/rank-crown-2.png b/h5/assets/yumi/cp-ranking/rank-crown-2.png new file mode 100644 index 0000000..f5b7719 Binary files /dev/null and b/h5/assets/yumi/cp-ranking/rank-crown-2.png differ diff --git a/h5/assets/yumi/cp-ranking/rank-crown-3.png b/h5/assets/yumi/cp-ranking/rank-crown-3.png new file mode 100644 index 0000000..642754e Binary files /dev/null and b/h5/assets/yumi/cp-ranking/rank-crown-3.png differ diff --git a/h5/assets/yumi/cp-ranking/rank-heart-glow.png b/h5/assets/yumi/cp-ranking/rank-heart-glow.png new file mode 100644 index 0000000..f60e775 Binary files /dev/null and b/h5/assets/yumi/cp-ranking/rank-heart-glow.png differ diff --git a/h5/assets/yumi/cp-ranking/rank-heart.png b/h5/assets/yumi/cp-ranking/rank-heart.png new file mode 100644 index 0000000..267ef3c Binary files /dev/null and b/h5/assets/yumi/cp-ranking/rank-heart.png differ diff --git a/h5/assets/yumi/cp-ranking/reward-cell-frame.png b/h5/assets/yumi/cp-ranking/reward-cell-frame.png new file mode 100644 index 0000000..970adfe Binary files /dev/null and b/h5/assets/yumi/cp-ranking/reward-cell-frame.png differ diff --git a/h5/assets/yumi/cp-ranking/reward-frame-mask.png b/h5/assets/yumi/cp-ranking/reward-frame-mask.png new file mode 100644 index 0000000..dac15b7 --- /dev/null +++ b/h5/assets/yumi/cp-ranking/reward-frame-mask.png @@ -0,0 +1,3 @@ + + + diff --git a/h5/assets/yumi/cp-ranking/reward-frame.png b/h5/assets/yumi/cp-ranking/reward-frame.png new file mode 100644 index 0000000..db808e9 Binary files /dev/null and b/h5/assets/yumi/cp-ranking/reward-frame.png differ diff --git a/h5/assets/yumi/cp-ranking/reward-icon.png b/h5/assets/yumi/cp-ranking/reward-icon.png new file mode 100644 index 0000000..7096656 Binary files /dev/null and b/h5/assets/yumi/cp-ranking/reward-icon.png differ diff --git a/h5/assets/yumi/cp-ranking/row-wave.png b/h5/assets/yumi/cp-ranking/row-wave.png new file mode 100644 index 0000000..a8a5d6e Binary files /dev/null and b/h5/assets/yumi/cp-ranking/row-wave.png differ diff --git a/h5/assets/yumi/cp-ranking/tab-active.png b/h5/assets/yumi/cp-ranking/tab-active.png new file mode 100644 index 0000000..f84e36a Binary files /dev/null and b/h5/assets/yumi/cp-ranking/tab-active.png differ diff --git a/h5/assets/yumi/cp-ranking/tab-default.png b/h5/assets/yumi/cp-ranking/tab-default.png new file mode 100644 index 0000000..aa4fd71 Binary files /dev/null and b/h5/assets/yumi/cp-ranking/tab-default.png differ diff --git a/h5/assets/yumi/cp-ranking/title-art.png b/h5/assets/yumi/cp-ranking/title-art.png new file mode 100644 index 0000000..060565f Binary files /dev/null and b/h5/assets/yumi/cp-ranking/title-art.png differ diff --git a/h5/assets/yumi/cp-ranking/top-card.png b/h5/assets/yumi/cp-ranking/top-card.png new file mode 100644 index 0000000..0ec7ec1 Binary files /dev/null and b/h5/assets/yumi/cp-ranking/top-card.png differ diff --git a/h5/assets/yumi/cp-ranking/top-wing-left.png b/h5/assets/yumi/cp-ranking/top-wing-left.png new file mode 100644 index 0000000..775100f Binary files /dev/null and b/h5/assets/yumi/cp-ranking/top-wing-left.png differ diff --git a/h5/assets/yumi/cp-ranking/top-wing-right.png b/h5/assets/yumi/cp-ranking/top-wing-right.png new file mode 100644 index 0000000..f412ff7 Binary files /dev/null and b/h5/assets/yumi/cp-ranking/top-wing-right.png differ diff --git a/h5/hyapp/manager-center/index.html b/h5/hyapp/manager-center/index.html index 84440c5..3060638 100644 --- a/h5/hyapp/manager-center/index.html +++ b/h5/hyapp/manager-center/index.html @@ -4,7 +4,7 @@ Manager Center - +
@@ -66,6 +66,22 @@ + +
+
+
+

BD Leaders

+
0
+
+ +
+ +
+
- + diff --git a/h5/hyapp/manager-center/locales/ar.json b/h5/hyapp/manager-center/locales/ar.json index 1419c60..c26534d 100644 --- a/h5/hyapp/manager-center/locales/ar.json +++ b/h5/hyapp/manager-center/locales/ar.json @@ -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": "فشل الطلب", diff --git a/h5/hyapp/manager-center/locales/en.json b/h5/hyapp/manager-center/locales/en.json index 4533064..2e751cc 100644 --- a/h5/hyapp/manager-center/locales/en.json +++ b/h5/hyapp/manager-center/locales/en.json @@ -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", diff --git a/h5/hyapp/manager-center/locales/id.json b/h5/hyapp/manager-center/locales/id.json index 58f94d8..3a158c8 100644 --- a/h5/hyapp/manager-center/locales/id.json +++ b/h5/hyapp/manager-center/locales/id.json @@ -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", diff --git a/h5/hyapp/manager-center/locales/tr.json b/h5/hyapp/manager-center/locales/tr.json index 2ca5507..dcc7b1f 100644 --- a/h5/hyapp/manager-center/locales/tr.json +++ b/h5/hyapp/manager-center/locales/tr.json @@ -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", diff --git a/h5/hyapp/manager-center/script.js b/h5/hyapp/manager-center/script.js index 5a4845b..1e31b55 100644 --- a/h5/hyapp/manager-center/script.js +++ b/h5/hyapp/manager-center/script.js @@ -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 { diff --git a/h5/hyapp/manager-center/style.css b/h5/hyapp/manager-center/style.css index 66d8c91..a5f7351 100644 --- a/h5/hyapp/manager-center/style.css +++ b/h5/hyapp/manager-center/style.css @@ -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); diff --git a/h5/yumi/cp-ranking/cp-ranking.css b/h5/yumi/cp-ranking/cp-ranking.css new file mode 100644 index 0000000..b1b4972 --- /dev/null +++ b/h5/yumi/cp-ranking/cp-ranking.css @@ -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; +} diff --git a/h5/yumi/cp-ranking/cp-ranking.js b/h5/yumi/cp-ranking/cp-ranking.js new file mode 100644 index 0000000..5bb697a --- /dev/null +++ b/h5/yumi/cp-ranking/cp-ranking.js @@ -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 = '
No ranking data
'; + 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" + ? '' + : '
' + escapeHtml(total) + '
'; + return '
' + + '' + + '' + + '
' + + '' + + '' + escapeHtml(left.name) + '' + + '
' + + '
' + center + '
' + + '
' + + '' + + '' + escapeHtml(right.name) + '' + + '
' + + '
'; + } + + 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 '
' + + 'Rank ' + rank + '' + + '
' + + '' + + '' + escapeHtml(left.name) + '' + + '
' + + '
' + + '' + + '' + escapeHtml(right.name) + '' + + '
'; + } + + 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 ? '
' + escapeHtml(item.note) + '
' : ""; + return '
' + + '
' + + '
' + escapeHtml(item.title) + '
' + + note + + '
'; + } + + 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, "'"); + } + + 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" } + ] + ]; + } +})(); diff --git a/h5/yumi/cp-ranking/index.html b/h5/yumi/cp-ranking/index.html new file mode 100644 index 0000000..c8fbad3 --- /dev/null +++ b/h5/yumi/cp-ranking/index.html @@ -0,0 +1,123 @@ + + + + + + + + + + Last Weekly CP + + + + + +
+
+ + Last Weekly CP + + + +
+
+ + 02 + D +
+
+ + 02 + H +
+
+ + 02 + M +
+
+ + 02 + S +
+
+ + + +
+
+ +

Gifts exchanged between CPs ≥ 5000 gold will be displayed here

+

(Display the last 10 records, sorted in ascending order.)

+
+
+ +
+ +

The previous number one position

+

(Display the last 10 records, sorted in ascending order.)

+
+
+
+ +
+
+ +
+ +
+
+ + 02 + D +
+
+ + 02 + H +
+
+ + 02 + M +
+
+ + 02 + S +
+
+
+
+
+ +
+
+ + Rank 1 +
+
+
+ + Rank 2 +
+
+
+ + Rank 3 +
+
+
+ + +
+
+
+ + +