经理中心

This commit is contained in:
zhx 2026-05-12 11:31:48 +08:00
parent d82aec3805
commit c39b9dc240
12 changed files with 964 additions and 140 deletions

View File

@ -0,0 +1,67 @@
<!doctype html>
<html lang="en">
<head>
<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>Admin Center</title>
<link rel="stylesheet" href="../center-static/center-static.css" />
</head>
<body>
<div class="center-page">
<header class="page-header">
<button class="icon-button" type="button" data-action="back" aria-label="Back"><span></span></button>
<h1 id="pageTitle">Admin Center</h1>
<button class="language-button" type="button" data-action="language">EN</button>
</header>
<main class="content">
<section class="surface-card hero-card">
<div class="hero-row">
<div class="hero-title" id="heroTitle">My available salary</div>
<button class="hero-link" id="heroLink" type="button">Details</button>
</div>
<div class="hero-value" id="heroValue">$0</div>
</section>
<section class="summary-grid" id="summaryGrid"></section>
<div id="sections"></div>
</main>
<div class="toast" id="toast" hidden></div>
</div>
<script>
window.CENTER_PAGE = {
title: "Admin Center",
identity: "ADMIN",
heroTitle: "My available salary",
heroLink: "Available income",
heroRoute: "/available-income",
balanceType: "ADMIN_SALARY",
summaries: [
{ label: "Role", value: "Admin" },
{ label: "Scope", value: "BD / Agency" }
],
sections: [
{
title: "Operations",
items: [
{ label: "Item distribution", route: "/item-distribution", icon: "I" },
{ label: "Admin policy", route: "/admin-policy", icon: "P" }
]
},
{
title: "Teams",
items: [
{ label: "Invite BD Leader", route: "/invite-bd-leader", icon: "+" },
{ label: "My BD Leader teams", route: "/my-BDLeader-teams", icon: "L" },
{ label: "Invite BD", route: "/invite-bd", icon: "+" },
{ label: "My BD teams", route: "/my-BD-teams", icon: "B" },
{ label: "Invite Agency", route: "/invite-agency", icon: "+" },
{ label: "My Agency teams", route: "/my-agency-teams", icon: "A" },
{ label: "Invite Recharge Agency", route: "/invite-recharge-agency", icon: "+" },
{ label: "My Recharge Agency", route: "/my-recharge-agency", icon: "R" }
]
}
]
};
</script>
<script src="../center-static/center-static.js"></script>
</body>
</html>

View File

@ -1,67 +1,19 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<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>Admin Center</title>
<link rel="stylesheet" href="../center-static/center-static.css" />
</head>
<body>
<div class="center-page">
<header class="page-header">
<button class="icon-button" type="button" data-action="back" aria-label="Back"><span></span></button>
<h1 id="pageTitle">Admin Center</h1>
<button class="language-button" type="button" data-action="language">EN</button>
</header>
<main class="content">
<section class="surface-card hero-card">
<div class="hero-row">
<div class="hero-title" id="heroTitle">My available salary</div>
<button class="hero-link" id="heroLink" type="button">Details</button>
</div>
<div class="hero-value" id="heroValue">$0</div>
</section>
<section class="summary-grid" id="summaryGrid"></section>
<div id="sections"></div>
</main>
<div class="toast" id="toast" hidden></div>
</div>
<script>
window.CENTER_PAGE = {
title: "Admin Center",
identity: "ADMIN",
heroTitle: "My available salary",
heroLink: "Available income",
heroRoute: "/available-income",
balanceType: "ADMIN_SALARY",
summaries: [
{ label: "Role", value: "Admin" },
{ label: "Scope", value: "BD / Agency" }
],
sections: [
{
title: "Operations",
items: [
{ label: "Item distribution", route: "/item-distribution", icon: "I" },
{ label: "Admin policy", route: "/admin-policy", icon: "P" }
]
},
{
title: "Teams",
items: [
{ label: "Invite BD Leader", route: "/invite-bd-leader", icon: "+" },
{ label: "My BD Leader teams", route: "/my-BDLeader-teams", icon: "L" },
{ label: "Invite BD", route: "/invite-bd", icon: "+" },
{ label: "My BD teams", route: "/my-BD-teams", icon: "B" },
{ label: "Invite Agency", route: "/invite-agency", icon: "+" },
{ label: "My Agency teams", route: "/my-agency-teams", icon: "A" },
{ label: "Invite Recharge Agency", route: "/invite-recharge-agency", icon: "+" },
{ label: "My Recharge Agency", route: "/my-recharge-agency", icon: "R" }
]
}
]
};
</script>
<script src="../center-static/center-static.js"></script>
</body>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var target = new URL("https://h5.haiyihy.com/hyapp/manager-center/index.html")
var source = new URL(window.location.href)
source.searchParams.forEach(function (value, key) {
target.searchParams.set(key, value)
})
target.searchParams.set("h5v", "20260507-1726")
window.location.replace(target.toString())
</script>
</body>
</html>

View File

@ -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-0107" />
<link rel="stylesheet" href="./style.css?v=20260511-0109" />
</head>
<body>
<div class="manager-center" aria-label="Manager Center" data-i18n-aria="page_label" data-loading="true">
@ -65,55 +65,140 @@
</div>
</section>
<section class="card gift-card">
<div class="section-head">
<h2 data-i18n="gift_props">Gift Props</h2>
</div>
<label class="field-label" id="propsTypeLabelText" data-i18n="gift_type">Gift Type</label>
<div class="custom-select" id="propsTypeSelect" data-value="RIDE">
<button
class="custom-select-button"
id="propsTypeButton"
type="button"
aria-expanded="false"
aria-haspopup="listbox"
aria-labelledby="propsTypeLabelText propsTypeSelectedLabel"
>
<span id="propsTypeSelectedLabel">Ride</span>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="m7 9 5 5 5-5" />
<section class="card action-card" id="actionCard" hidden>
<button class="action-item" id="giftEntryButton" type="button">
<span class="action-item-icon" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M20 12v8H4v-8" />
<path d="M2 7h20v5H2z" />
<path d="M12 22V7" />
<path d="M12 7H7.5a2.5 2.5 0 1 1 2.2-3.7L12 7Z" />
<path d="M12 7h4.5a2.5 2.5 0 1 0-2.2-3.7L12 7Z" />
</svg>
</button>
<div class="custom-select-menu" id="propsTypeMenu" role="listbox" aria-labelledby="propsTypeLabelText" hidden>
<button type="button" role="option" data-value="RIDE" data-i18n="prop_type_ride">Ride</button>
<button type="button" role="option" data-value="NOBLE_VIP" data-i18n="prop_type_noble_vip">Noble VIP</button>
<button type="button" role="option" data-value="AVATAR_FRAME" data-i18n="prop_type_avatar_frame">Avatar Frame</button>
<button type="button" role="option" data-value="CHAT_BUBBLE" data-i18n="prop_type_chat_bubble">Chat Bubble</button>
<button type="button" role="option" data-value="DATA_CARD" data-i18n="prop_type_data_card">Profile Card</button>
</div>
</div>
<div class="props-status" id="propsStatus" role="status" hidden></div>
<div class="props-list" id="propsList"></div>
<button class="primary-action" id="sendPropsButton" type="button" disabled data-i18n="send">Send</button>
</section>
<section class="card ban-card" id="banCard" hidden>
<div class="section-head">
<h2 data-i18n="ban_user">Ban User</h2>
<span class="danger-chip" id="banEligibility">-</span>
</div>
<textarea id="banReason" maxlength="200" rows="3" placeholder="Ban reason" data-i18n-placeholder="ban_reason"></textarea>
<button class="danger-action" id="banButton" type="button" disabled data-i18n="ban">Ban</button>
<div class="form-status" id="banStatus" role="status" hidden></div>
</span>
<span class="action-item-copy">
<span class="action-item-title" data-i18n="gift_props">Gift Props</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 is-danger" id="banEntryButton" type="button" disabled>
<span class="action-item-icon" aria-hidden="true">
<svg viewBox="0 0 24 24">
<circle cx="12" cy="12" r="9" />
<path d="m8 8 8 8" />
</svg>
</span>
<span class="action-item-copy">
<span class="action-item-title" data-i18n="ban_user">Ban User</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="unbanEntryButton" type="button" disabled>
<span class="action-item-icon" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M12 3 20 7v5c0 5-3.4 8-8 9-4.6-1-8-4-8-9V7l8-4Z" />
<path d="m8.5 12.5 2.2 2.2 4.8-5" />
</svg>
</span>
<span class="action-item-copy">
<span class="action-item-title" data-i18n="unban_user">Unban User</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>
</section>
</main>
<div class="modal-mask" id="giftModal" role="dialog" aria-modal="true" aria-labelledby="giftModalTitle" hidden>
<section class="modal-sheet">
<div class="modal-head">
<h2 id="giftModalTitle" data-i18n="gift_props">Gift Props</h2>
<button class="modal-close" type="button" data-modal-close aria-label="Close" data-i18n-aria="close">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M6 6 18 18M18 6 6 18" />
</svg>
</button>
</div>
<div class="modal-body">
<label class="field-label" id="propsTypeLabelText" data-i18n="gift_type">Gift Type</label>
<div class="custom-select" id="propsTypeSelect" data-value="RIDE">
<button
class="custom-select-button"
id="propsTypeButton"
type="button"
aria-expanded="false"
aria-haspopup="listbox"
aria-labelledby="propsTypeLabelText propsTypeSelectedLabel"
>
<span id="propsTypeSelectedLabel">Ride</span>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="m7 9 5 5 5-5" />
</svg>
</button>
<div class="custom-select-menu" id="propsTypeMenu" role="listbox" aria-labelledby="propsTypeLabelText" hidden>
<button type="button" role="option" data-value="RIDE" data-i18n="prop_type_ride">Ride</button>
<button type="button" role="option" data-value="NOBLE_VIP" data-i18n="prop_type_noble_vip">Noble VIP</button>
<button type="button" role="option" data-value="AVATAR_FRAME" data-i18n="prop_type_avatar_frame">Avatar Frame</button>
<button type="button" role="option" data-value="CHAT_BUBBLE" data-i18n="prop_type_chat_bubble">Chat Bubble</button>
<button type="button" role="option" data-value="DATA_CARD" data-i18n="prop_type_data_card">Profile Card</button>
</div>
</div>
<div class="props-status" id="propsStatus" role="status" hidden></div>
<div class="props-list" id="propsList"></div>
<button class="primary-action" id="sendPropsButton" type="button" disabled data-i18n="send">Send</button>
</div>
</section>
</div>
<div class="modal-mask" id="banModal" role="dialog" aria-modal="true" aria-labelledby="banModalTitle" hidden>
<section class="modal-sheet">
<div class="modal-head">
<h2 id="banModalTitle" data-i18n="ban_user">Ban User</h2>
<button class="modal-close" type="button" data-modal-close aria-label="Close" data-i18n-aria="close">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M6 6 18 18M18 6 6 18" />
</svg>
</button>
</div>
<div class="modal-body">
<span class="danger-chip" id="banEligibility">-</span>
<textarea id="banReason" maxlength="200" rows="3" placeholder="Ban reason" data-i18n-placeholder="ban_reason"></textarea>
<button class="danger-action" id="banButton" type="button" disabled data-i18n="ban">Ban</button>
<div class="form-status" id="banStatus" role="status" hidden></div>
</div>
</section>
</div>
<div class="modal-mask" id="unbanModal" role="dialog" aria-modal="true" aria-labelledby="unbanModalTitle" hidden>
<section class="modal-sheet">
<div class="modal-head">
<h2 id="unbanModalTitle" data-i18n="unban_user">Unban User</h2>
<button class="modal-close" type="button" data-modal-close aria-label="Close" data-i18n-aria="close">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M6 6 18 18M18 6 6 18" />
</svg>
</button>
</div>
<div class="modal-body">
<span class="danger-chip" id="unbanEligibility">-</span>
<textarea id="unbanReason" maxlength="200" rows="3" placeholder="Unban reason" data-i18n-placeholder="unban_reason"></textarea>
<button class="primary-action" id="unbanButton" type="button" disabled data-i18n="unban">Unban</button>
<div class="form-status" id="unbanStatus" role="status" hidden></div>
</div>
</section>
</div>
<div class="loading-mask" id="loadingMask" role="status" aria-live="polite">
<div class="loader" aria-hidden="true"></div>
<span data-i18n="loading">Loading...</span>
</div>
<div class="toast" id="toast" role="status" hidden></div>
</div>
<script src="./script.js?v=20260511-0108" defer></script>
<script src="./script.js?v=20260511-0112" defer></script>
</body>
</html>

View File

@ -4,6 +4,7 @@
"back": "رجوع",
"title": "مركز المدير",
"language_button_aria": "تغيير اللغة",
"close": "إغلاق",
"current_manager": "المدير الحالي",
"user": "المستخدم",
"user_search_placeholder": "أدخل معرّف المستخدم أو الحساب",
@ -20,14 +21,18 @@
"no_giftable_props": "لا توجد عناصر قابلة للإهداء",
"send": "إرسال",
"sending": "جار الإرسال...",
"confirm_send": "هل تريد إرسال هذا العنصر؟",
"send_success": "تم الإرسال بنجاح",
"ban_user": "حظر المستخدم",
"ban_reason": "سبب الحظر",
"ban": "حظر",
"confirm_ban": "هل تريد حظر هذا المستخدم؟",
"submitting": "جار الإرسال...",
"ban_success": "تم حظر المستخدم",
"unban_user": "إلغاء حظر المستخدم",
"unban_reason": "سبب إلغاء الحظر",
"unban": "إلغاء الحظر",
"unban_success": "تم إلغاء حظر المستخدم",
"can_unban": "يمكن إلغاء الحظر",
"cannot_unban": "لا يمكن إلغاء الحظر",
"loading": "جار التحميل...",
"uid_label": "UID",
"days": "{count} يوم",
@ -40,6 +45,8 @@
"not_manager": "الحساب الحالي ليس مديرًا",
"user_not_found": "المستخدم غير موجود",
"user_recharged": "لدى هذا المستخدم سجلات شحن ولا يمكن حظره",
"user_already_banned": "هذا المستخدم محظور بالفعل",
"user_not_banned": "هذا المستخدم غير محظور",
"prop_not_giftable": "هذا العنصر غير متاح كهديّة من المدير",
"vip_not_giftable": "لا يمكن إهداء عنصر VIP هذا",
"request_failed": "فشل الطلب",

View File

@ -4,6 +4,7 @@
"back": "Back",
"title": "Manager Center",
"language_button_aria": "Change language",
"close": "Close",
"current_manager": "Current Manager",
"user": "User",
"user_search_placeholder": "Enter user ID or account",
@ -20,14 +21,18 @@
"no_giftable_props": "No giftable props",
"send": "Send",
"sending": "Sending...",
"confirm_send": "Send this prop?",
"send_success": "Sent successfully",
"ban_user": "Ban User",
"ban_reason": "Ban reason",
"ban": "Ban",
"confirm_ban": "Ban this user?",
"submitting": "Submitting...",
"ban_success": "User banned",
"unban_user": "Unban User",
"unban_reason": "Unban reason",
"unban": "Unban",
"unban_success": "User unbanned",
"can_unban": "Can unban",
"cannot_unban": "Cannot unban",
"loading": "Loading...",
"uid_label": "UID",
"days": "{count} days",
@ -40,6 +45,8 @@
"not_manager": "Current account is not a manager",
"user_not_found": "User not found",
"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",
"prop_not_giftable": "This prop is not available for manager gifting",
"vip_not_giftable": "This noble prop cannot be gifted",
"request_failed": "Request failed",

View File

@ -4,6 +4,7 @@
"back": "Kembali",
"title": "Pusat Manajer",
"language_button_aria": "Ubah bahasa",
"close": "Tutup",
"current_manager": "Manajer Saat Ini",
"user": "Pengguna",
"user_search_placeholder": "Masukkan ID pengguna atau akun",
@ -20,14 +21,18 @@
"no_giftable_props": "Tidak ada properti yang dapat dikirim",
"send": "Kirim",
"sending": "Mengirim...",
"confirm_send": "Kirim properti ini?",
"send_success": "Berhasil dikirim",
"ban_user": "Blokir Pengguna",
"ban_reason": "Alasan blokir",
"ban": "Blokir",
"confirm_ban": "Blokir pengguna ini?",
"submitting": "Mengirim...",
"ban_success": "Pengguna diblokir",
"unban_user": "Buka Blokir Pengguna",
"unban_reason": "Alasan buka blokir",
"unban": "Buka blokir",
"unban_success": "Blokir pengguna dibuka",
"can_unban": "Dapat dibuka",
"cannot_unban": "Tidak dapat dibuka",
"loading": "Memuat...",
"uid_label": "UID",
"days": "{count} hari",
@ -40,6 +45,8 @@
"not_manager": "Akun saat ini bukan manajer",
"user_not_found": "Pengguna tidak ditemukan",
"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",
"prop_not_giftable": "Properti ini tidak tersedia untuk hadiah manajer",
"vip_not_giftable": "Properti noble ini tidak dapat dikirim",
"request_failed": "Permintaan gagal",

View File

@ -4,6 +4,7 @@
"back": "Geri",
"title": "Yönetici Merkezi",
"language_button_aria": "Dili değiştir",
"close": "Kapat",
"current_manager": "Mevcut Yönetici",
"user": "Kullanıcı",
"user_search_placeholder": "Kullanıcı ID veya hesap gir",
@ -20,14 +21,18 @@
"no_giftable_props": "Gönderilebilir eşya yok",
"send": "Gönder",
"sending": "Gönderiliyor...",
"confirm_send": "Bu eşyayı göndermek istiyor musun?",
"send_success": "Başarıyla gönderildi",
"ban_user": "Kullanıcıyı Yasakla",
"ban_reason": "Yasaklama nedeni",
"ban": "Yasakla",
"confirm_ban": "Bu kullanıcıyı yasaklamak istiyor musun?",
"submitting": "Gönderiliyor...",
"ban_success": "Kullanıcı yasaklandı",
"unban_user": "Yasağı Kaldır",
"unban_reason": "Yasağı kaldırma nedeni",
"unban": "Yasağı kaldır",
"unban_success": "Kullanıcının yasağı kaldırıldı",
"can_unban": "Yasak kaldırılabilir",
"cannot_unban": "Yasak kaldırılamaz",
"loading": "Yükleniyor...",
"uid_label": "UID",
"days": "{count} gün",
@ -40,6 +45,8 @@
"not_manager": "Mevcut hesap yönetici değil",
"user_not_found": "Kullanıcı bulunamadı",
"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",
"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",

View File

@ -18,6 +18,7 @@
back: "Back",
title: "Manager Center",
language_button_aria: "Change language",
close: "Close",
current_manager: "Current Manager",
user: "User",
user_search_placeholder: "Enter user ID or account",
@ -34,14 +35,18 @@
no_giftable_props: "No giftable props",
send: "Send",
sending: "Sending...",
confirm_send: "Send this prop?",
send_success: "Sent successfully",
ban_user: "Ban User",
ban_reason: "Ban reason",
ban: "Ban",
confirm_ban: "Ban this user?",
submitting: "Submitting...",
ban_success: "User banned",
unban_user: "Unban User",
unban_reason: "Unban reason",
unban: "Unban",
unban_success: "User unbanned",
can_unban: "Can unban",
cannot_unban: "Cannot unban",
loading: "Loading...",
uid_label: "UID",
days: "{count} days",
@ -54,6 +59,8 @@
not_manager: "Current account is not a manager",
user_not_found: "User not found",
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",
prop_not_giftable: "This prop is not available for manager gifting",
vip_not_giftable: "This noble prop cannot be gifted",
request_failed: "Request failed",
@ -65,6 +72,7 @@
back: "رجوع",
title: "مركز المدير",
language_button_aria: "تغيير اللغة",
close: "إغلاق",
current_manager: "المدير الحالي",
user: "المستخدم",
user_search_placeholder: "أدخل معرّف المستخدم أو الحساب",
@ -81,14 +89,18 @@
no_giftable_props: "لا توجد عناصر قابلة للإهداء",
send: "إرسال",
sending: "جار الإرسال...",
confirm_send: "هل تريد إرسال هذا العنصر؟",
send_success: "تم الإرسال بنجاح",
ban_user: "حظر المستخدم",
ban_reason: "سبب الحظر",
ban: "حظر",
confirm_ban: "هل تريد حظر هذا المستخدم؟",
submitting: "جار الإرسال...",
ban_success: "تم حظر المستخدم",
unban_user: "إلغاء حظر المستخدم",
unban_reason: "سبب إلغاء الحظر",
unban: "إلغاء الحظر",
unban_success: "تم إلغاء حظر المستخدم",
can_unban: "يمكن إلغاء الحظر",
cannot_unban: "لا يمكن إلغاء الحظر",
loading: "جار التحميل...",
uid_label: "UID",
days: "{count} يوم",
@ -101,6 +113,8 @@
not_manager: "الحساب الحالي ليس مديرًا",
user_not_found: "المستخدم غير موجود",
user_recharged: "لدى هذا المستخدم سجلات شحن ولا يمكن حظره",
user_already_banned: "هذا المستخدم محظور بالفعل",
user_not_banned: "هذا المستخدم غير محظور",
prop_not_giftable: "هذا العنصر غير متاح كهديّة من المدير",
vip_not_giftable: "لا يمكن إهداء عنصر VIP هذا",
request_failed: "فشل الطلب",
@ -112,6 +126,7 @@
back: "Geri",
title: "Yönetici Merkezi",
language_button_aria: "Dili değiştir",
close: "Kapat",
current_manager: "Mevcut Yönetici",
user: "Kullanıcı",
user_search_placeholder: "Kullanıcı ID veya hesap gir",
@ -128,14 +143,18 @@
no_giftable_props: "Gönderilebilir eşya yok",
send: "Gönder",
sending: "Gönderiliyor...",
confirm_send: "Bu eşyayı göndermek istiyor musun?",
send_success: "Başarıyla gönderildi",
ban_user: "Kullanıcıyı Yasakla",
ban_reason: "Yasaklama nedeni",
ban: "Yasakla",
confirm_ban: "Bu kullanıcıyı yasaklamak istiyor musun?",
submitting: "Gönderiliyor...",
ban_success: "Kullanıcı yasaklandı",
unban_user: "Yasağı Kaldır",
unban_reason: "Yasağı kaldırma nedeni",
unban: "Yasağı kaldır",
unban_success: "Kullanıcının yasağı kaldırıldı",
can_unban: "Yasak kaldırılabilir",
cannot_unban: "Yasak kaldırılamaz",
loading: "Yükleniyor...",
uid_label: "UID",
days: "{count} gün",
@ -148,6 +167,8 @@
not_manager: "Mevcut hesap yönetici değil",
user_not_found: "Kullanıcı bulunamadı",
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",
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",
@ -159,6 +180,7 @@
back: "Kembali",
title: "Pusat Manajer",
language_button_aria: "Ubah bahasa",
close: "Tutup",
current_manager: "Manajer Saat Ini",
user: "Pengguna",
user_search_placeholder: "Masukkan ID pengguna atau akun",
@ -175,14 +197,18 @@
no_giftable_props: "Tidak ada properti yang dapat dikirim",
send: "Kirim",
sending: "Mengirim...",
confirm_send: "Kirim properti ini?",
send_success: "Berhasil dikirim",
ban_user: "Blokir Pengguna",
ban_reason: "Alasan blokir",
ban: "Blokir",
confirm_ban: "Blokir pengguna ini?",
submitting: "Mengirim...",
ban_success: "Pengguna diblokir",
unban_user: "Buka Blokir Pengguna",
unban_reason: "Alasan buka blokir",
unban: "Buka blokir",
unban_success: "Blokir pengguna dibuka",
can_unban: "Dapat dibuka",
cannot_unban: "Tidak dapat dibuka",
loading: "Memuat...",
uid_label: "UID",
days: "{count} hari",
@ -195,6 +221,8 @@
not_manager: "Akun saat ini bukan manajer",
user_not_found: "Pengguna tidak ditemukan",
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",
prop_not_giftable: "Properti ini tidak tersedia untuk hadiah manajer",
vip_not_giftable: "Properti noble ini tidak dapat dikirim",
request_failed: "Permintaan gagal",
@ -261,7 +289,7 @@
if (window.location.protocol === "file:") return fallbackMessages[normalizedLang];
try {
const response = await fetch(`./locales/${normalizedLang}.json?v=20260511-0105`, { cache: "no-store" });
const response = await fetch(`./locales/${normalizedLang}.json?v=20260511-0107`, { cache: "no-store" });
if (response.ok) return await response.json();
} catch (error) {
return fallbackMessages[normalizedLang];
@ -477,6 +505,10 @@
return "/app/h5/manager-center/users/ban";
}
function endpointUnbanUser() {
return "/app/h5/manager-center/users/unban";
}
function setLoading(isLoading) {
const root = $(".manager-center");
const mask = $("#loadingMask");
@ -502,6 +534,58 @@
}, 2300);
}
function syncModalState() {
document.body.classList.toggle("is-modal-open", Boolean(document.querySelector(".modal-mask:not([hidden])")));
}
function setModalOpen(selector, open) {
const modal = $(selector);
if (!modal) return;
modal.hidden = !open;
if (open) {
setLanguageMenuOpen(false);
setPropsTypeMenuOpen(false);
}
syncModalState();
}
function closeModals() {
document.querySelectorAll(".modal-mask").forEach((modal) => {
modal.hidden = true;
});
setPropsTypeMenuOpen(false);
syncModalState();
}
function openGiftModal() {
if (!state.target) {
showToast(message("enter_user"));
return;
}
setModalOpen("#giftModal", true);
updateActionState();
}
function openBanModal() {
if (!state.target) {
showToast(message("enter_user"));
return;
}
setStatus($("#banStatus"), "");
setModalOpen("#banModal", true);
updateActionState();
}
function openUnbanModal() {
if (!state.target) {
showToast(message("enter_user"));
return;
}
setStatus($("#unbanStatus"), "");
setModalOpen("#unbanModal", true);
updateActionState();
}
function closePage() {
try {
if (window.app && typeof window.app.closePage === "function") {
@ -554,7 +638,12 @@
}
function canBanTarget(target) {
return Boolean(target) && target.canBan !== false && !target.hasRecharge;
return Boolean(target) && target.canBan !== false && !target.hasRecharge && !canUnbanTarget(target);
}
function canUnbanTarget(target) {
const status = String(target?.accountStatus || "").trim().toUpperCase();
return Boolean(target) && (status === "FREEZE" || status === "ARCHIVE" || status === "ARCHIVE_DEVICE");
}
function userIDText(user) {
@ -578,24 +667,33 @@
function renderTarget() {
const card = $("#targetCard");
const banCard = $("#banCard");
const actionCard = $("#actionCard");
if (!state.target) {
if (card) card.hidden = true;
if (banCard) banCard.hidden = true;
if (actionCard) actionCard.hidden = true;
closeModals();
updateActionState();
return;
}
const target = state.target;
card.hidden = false;
banCard.hidden = false;
if (card) card.hidden = false;
if (actionCard) actionCard.hidden = false;
$("#targetName").textContent = displayName(target);
$("#targetUid").textContent = userIDText(target);
$("#targetAccountStatus").textContent = target.accountStatus || "-";
const canBan = canBanTarget(target);
const banEligibility = $("#banEligibility");
banEligibility.textContent = canBan ? message("can_ban") : message("cannot_ban");
banEligibility.classList.toggle("is-danger", !canBan);
if (banEligibility) {
banEligibility.textContent = canBan ? message("can_ban") : message("cannot_ban");
banEligibility.classList.toggle("is-danger", !canBan);
}
const canUnban = canUnbanTarget(target);
const unbanEligibility = $("#unbanEligibility");
if (unbanEligibility) {
unbanEligibility.textContent = canUnban ? message("can_unban") : message("cannot_unban");
unbanEligibility.classList.toggle("is-danger", !canUnban);
}
renderAvatar("#targetAvatar", "#targetAvatarFallback", target.userAvatar, displayName(target), "U");
updateActionState();
}
@ -659,10 +757,18 @@
}
function updateActionState() {
const giftEntryButton = $("#giftEntryButton");
const banEntryButton = $("#banEntryButton");
const unbanEntryButton = $("#unbanEntryButton");
const sendButton = $("#sendPropsButton");
const banButton = $("#banButton");
const unbanButton = $("#unbanButton");
if (giftEntryButton) giftEntryButton.disabled = !state.target;
if (banEntryButton) banEntryButton.disabled = !state.target;
if (unbanEntryButton) unbanEntryButton.disabled = !state.target;
if (sendButton) sendButton.disabled = !state.target || !state.selectedPropsId;
if (banButton) banButton.disabled = !canBanTarget(state.target);
if (unbanButton) unbanButton.disabled = !canUnbanTarget(state.target);
}
async function loadProfile() {
@ -710,7 +816,6 @@
async function sendProps() {
if (!state.target || !state.selectedPropsId) return;
if (!window.confirm(message("confirm_send"))) return;
const button = $("#sendPropsButton");
button.disabled = true;
@ -722,6 +827,7 @@
propsId: state.selectedPropsId
}
});
setModalOpen("#giftModal", false);
showToast(message("send_success"));
} catch (error) {
showToast(friendlyError(error));
@ -732,7 +838,6 @@
async function banUser() {
if (!canBanTarget(state.target)) return;
if (!window.confirm(message("confirm_ban"))) return;
const button = $("#banButton");
button.disabled = true;
@ -747,6 +852,7 @@
});
state.target.accountStatus = "ARCHIVE";
renderTarget();
setModalOpen("#banModal", false);
setStatus($("#banStatus"), "");
showToast(message("ban_success"));
} catch (error) {
@ -756,12 +862,40 @@
}
}
async function unbanUser() {
if (!canUnbanTarget(state.target)) return;
const button = $("#unbanButton");
button.disabled = true;
setStatus($("#unbanStatus"), message("submitting"));
try {
await requestGoJSON(endpointUnbanUser(), {
method: "POST",
body: {
userId: displayUserID(state.target),
reason: $("#unbanReason").value || ""
}
});
state.target.accountStatus = "NORMAL";
renderTarget();
setModalOpen("#unbanModal", false);
setStatus($("#unbanStatus"), "");
showToast(message("unban_success"));
} catch (error) {
setStatus($("#unbanStatus"), friendlyError(error), "error");
} finally {
updateActionState();
}
}
function friendlyError(error) {
const code = error?.code || error?.response?.code || error?.response?.errorCodeName || "";
if (code === "missing_token") return message("missing_token");
if (code === "not_manager") return message("not_manager");
if (code === "user_not_found") return message("user_not_found");
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 === "prop_not_giftable") return message("prop_not_giftable");
if (code === "vip_not_giftable") return message("vip_not_giftable");
return error?.message || message("request_failed");
@ -784,6 +918,17 @@
setLanguageMenuOpen(false);
setPropsTypeMenuOpen(false);
});
$("#giftEntryButton")?.addEventListener("click", openGiftModal);
$("#banEntryButton")?.addEventListener("click", openBanModal);
$("#unbanEntryButton")?.addEventListener("click", openUnbanModal);
document.querySelectorAll("[data-modal-close]").forEach((button) => {
button.addEventListener("click", closeModals);
});
document.querySelectorAll(".modal-mask").forEach((modal) => {
modal.addEventListener("click", (event) => {
if (event.target === modal) closeModals();
});
});
$("#propsTypeButton")?.addEventListener("click", (event) => {
event.stopPropagation();
setLanguageMenuOpen(false);
@ -799,10 +944,12 @@
if (event.key !== "Escape") return;
setLanguageMenuOpen(false);
setPropsTypeMenuOpen(false);
closeModals();
});
$("#searchForm")?.addEventListener("submit", searchUser);
$("#sendPropsButton")?.addEventListener("click", sendProps);
$("#banButton")?.addEventListener("click", banUser);
$("#unbanButton")?.addEventListener("click", unbanUser);
}
async function init() {

View File

@ -34,6 +34,10 @@ body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
body.is-modal-open {
overflow: hidden;
}
button,
input,
textarea {
@ -268,12 +272,79 @@ button:disabled {
color: var(--danger);
}
.search-card,
.gift-card,
.ban-card {
.search-card {
padding: 14px;
}
.action-card {
display: grid;
gap: 4px;
padding: 6px;
}
.action-item {
display: grid;
grid-template-columns: 42px minmax(0, 1fr) 24px;
gap: 10px;
align-items: center;
width: 100%;
min-height: 58px;
border-radius: 6px;
padding: 8px;
background: #fff;
color: var(--text);
text-align: left;
}
.action-item:active {
background: #f4f7f8;
}
.action-item-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 42px;
height: 42px;
border-radius: 8px;
background: #e7f8f4;
color: var(--primary-dark);
}
.action-item.is-danger .action-item-icon {
background: var(--danger-bg);
color: var(--danger);
}
.action-item-icon svg,
.action-item-arrow {
width: 22px;
height: 22px;
fill: none;
stroke: currentColor;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2.2;
}
.action-item-copy {
display: grid;
min-width: 0;
}
.action-item-title {
overflow: hidden;
font-size: 15px;
font-weight: 900;
text-overflow: ellipsis;
white-space: nowrap;
}
.action-item-arrow {
justify-self: end;
color: #9aa4af;
}
.section-head {
display: flex;
align-items: center;
@ -560,6 +631,84 @@ textarea:focus {
color: var(--danger);
}
.modal-mask {
position: fixed;
z-index: 30;
inset: 0;
display: flex;
align-items: flex-end;
justify-content: center;
padding-top: 48px;
background: rgba(17, 24, 32, 0.48);
}
.modal-sheet {
display: flex;
width: min(100%, 430px);
max-height: calc(100vh - 48px);
flex-direction: column;
overflow: hidden;
border-radius: 8px 8px 0 0;
background: var(--card-bg);
box-shadow: 0 -18px 44px rgba(17, 24, 32, 0.22);
}
.modal-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 14px 14px 10px;
border-bottom: 1px solid var(--line);
}
.modal-head h2 {
margin: 0;
font-size: 17px;
font-weight: 900;
letter-spacing: 0;
}
.modal-close {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 34px;
width: 34px;
height: 34px;
border-radius: 50%;
background: #f2f4f7;
color: #48525f;
}
.modal-close svg {
width: 20px;
height: 20px;
fill: none;
stroke: currentColor;
stroke-linecap: round;
stroke-width: 2.4;
}
.modal-body {
overflow-y: auto;
padding: 14px 14px calc(18px + env(safe-area-inset-bottom));
}
.modal-body .danger-chip {
display: flex;
width: fit-content;
margin-bottom: 10px;
}
.modal-body textarea {
display: block;
}
.modal-body .props-list {
max-height: min(42vh, 330px);
}
.loading-mask {
position: absolute;
z-index: 20;
@ -614,7 +763,8 @@ textarea:focus {
[dir="rtl"] textarea,
[dir="rtl"] .custom-select-button,
[dir="rtl"] .custom-select-menu button,
[dir="rtl"] .prop-option {
[dir="rtl"] .prop-option,
[dir="rtl"] .action-item {
text-align: right;
}
@ -622,6 +772,10 @@ textarea:focus {
padding: 0 14px 0 12px;
}
[dir="rtl"] .action-item-arrow {
transform: rotate(180deg);
}
@keyframes spin {
to {
transform: rotate(360deg);

View File

@ -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>Recharge Center</title>
<link rel="stylesheet" href="./style.css?v=20260430-0109" />
<link rel="stylesheet" href="./style.css?v=20260511-0100" />
</head>
<body>
<div class="recharge-center" aria-label="Recharge Center" data-i18n-aria="page_label" data-loading="true">
@ -105,6 +105,39 @@
<button class="mini-action mini-action-icon" type="button" data-change-target aria-label="Change" data-i18n-aria="change">X</button>
</section>
<section class="card auto-verify-card" id="autoVerifyCard" hidden>
<div class="section-head">
<div>
<div class="section-title" data-i18n="auto_verify_title">Auto recharge verification</div>
<div class="section-subtitle" data-i18n="auto_verify_subtitle">Verify Binance transfer and issue coins</div>
</div>
<button class="auto-verify-toggle" id="autoVerifyToggle" type="button" data-i18n="open_auto_verify">Open</button>
</div>
<form class="auto-verify-form" id="autoVerifyForm" autocomplete="off" hidden>
<label class="auto-field">
<span data-i18n="order_no">Order no.</span>
<input id="autoOrderInput" type="text" inputmode="text" data-i18n-placeholder="order_no_placeholder" placeholder="Enter order no." />
</label>
<label class="auto-field">
<span data-i18n="transfer_amount_usdt">Transfer amount</span>
<input id="autoAmountInput" type="text" inputmode="decimal" data-i18n-placeholder="transfer_amount_placeholder" placeholder="Enter USDT amount" />
</label>
<label class="auto-field">
<span data-i18n="transfer_type">Transfer type</span>
<select id="autoTransferType">
<option value="BINANCE_PAY" data-i18n="binance_pay_transfer">Binance internal</option>
<option value="CHAIN" data-i18n="chain_transfer">On-chain transfer</option>
</select>
</label>
<label class="auto-field">
<span data-i18n="bill_no">Bill no.</span>
<input id="autoBillInput" type="text" inputmode="text" data-i18n-placeholder="bill_no_placeholder" placeholder="Enter bill no. or txId" />
</label>
<button id="autoVerifyButton" type="submit" data-i18n="verify_recharge">Verify recharge</button>
</form>
<div class="form-status" id="autoVerifyStatus" role="status" hidden></div>
</section>
<section class="card coin-recharge-card" id="coinRechargeCard" hidden>
<div class="section-head">
<div>
@ -205,6 +238,6 @@
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
</div>
<script src="./script.js?v=20260430-0109" defer></script>
<script src="./script.js?v=20260511-0100" defer></script>
</body>
</html>

View File

@ -44,6 +44,24 @@
coin_recharge_subtitle: "Enter coins amount",
coin_amount_placeholder: "Enter coins amount",
confirm_recharge: "Confirm recharge",
auto_verify_title: "Auto recharge verification",
auto_verify_subtitle: "Verify Binance transfer and issue coins",
open_auto_verify: "Open",
close_auto_verify: "Close",
order_no: "Order no.",
order_no_placeholder: "Enter order no.",
transfer_amount_usdt: "Transfer amount",
transfer_amount_placeholder: "Enter USDT amount",
transfer_type: "Transfer type",
binance_pay_transfer: "Binance internal",
chain_transfer: "On-chain transfer",
bill_no: "Bill no.",
bill_no_placeholder: "Enter bill no. or txId",
verify_recharge: "Verify recharge",
verifying: "Verifying...",
auto_verify_success: "Verification success",
please_enter_order_no: "Please enter order no.",
please_enter_bill_no: "Please enter bill no.",
loading_data: "Loading...",
loading_mask: "loading....",
no_country: "No supported country",
@ -106,6 +124,24 @@
coin_recharge_subtitle: "أدخل عدد العملات",
coin_amount_placeholder: "أدخل عدد العملات",
confirm_recharge: "تأكيد الشحن",
auto_verify_title: "تحقق الشحن التلقائي",
auto_verify_subtitle: "تحقق من تحويل Binance وأرسل العملات",
open_auto_verify: "فتح",
close_auto_verify: "إغلاق",
order_no: "رقم الطلب",
order_no_placeholder: "أدخل رقم الطلب",
transfer_amount_usdt: "مبلغ التحويل",
transfer_amount_placeholder: "أدخل مبلغ USDT",
transfer_type: "نوع التحويل",
binance_pay_transfer: "تحويل داخل Binance",
chain_transfer: "تحويل على الشبكة",
bill_no: "رقم الفاتورة",
bill_no_placeholder: "أدخل رقم الفاتورة أو txId",
verify_recharge: "تحقق من الشحن",
verifying: "جار التحقق...",
auto_verify_success: "تم التحقق بنجاح",
please_enter_order_no: "يرجى إدخال رقم الطلب",
please_enter_bill_no: "يرجى إدخال رقم الفاتورة",
loading_data: "جار التحميل...",
loading_mask: "جار التحميل....",
no_country: "لا توجد دولة مدعومة",
@ -168,6 +204,24 @@
coin_recharge_subtitle: "Coin miktari gir",
coin_amount_placeholder: "Coin miktari gir",
confirm_recharge: "Yuklemeyi onayla",
auto_verify_title: "Otomatik yukleme dogrulama",
auto_verify_subtitle: "Binance transferini dogrula ve coin ver",
open_auto_verify: "Ac",
close_auto_verify: "Kapat",
order_no: "Siparis no.",
order_no_placeholder: "Siparis no. gir",
transfer_amount_usdt: "Transfer tutari",
transfer_amount_placeholder: "USDT tutari gir",
transfer_type: "Transfer tipi",
binance_pay_transfer: "Binance ici",
chain_transfer: "Zincir uzeri",
bill_no: "Fatura no.",
bill_no_placeholder: "Fatura no. veya txId gir",
verify_recharge: "Yuklemeyi dogrula",
verifying: "Dogrulaniyor...",
auto_verify_success: "Dogrulama basarili",
please_enter_order_no: "Lutfen siparis no. girin",
please_enter_bill_no: "Lutfen fatura no. girin",
loading_data: "Yukleniyor...",
loading_mask: "yukleniyor....",
no_country: "Desteklenen ulke yok",
@ -230,6 +284,24 @@
coin_recharge_subtitle: "Masukkan jumlah koin",
coin_amount_placeholder: "Masukkan jumlah koin",
confirm_recharge: "Konfirmasi isi ulang",
auto_verify_title: "Verifikasi isi ulang otomatis",
auto_verify_subtitle: "Verifikasi transfer Binance dan kirim koin",
open_auto_verify: "Buka",
close_auto_verify: "Tutup",
order_no: "No. pesanan",
order_no_placeholder: "Masukkan no. pesanan",
transfer_amount_usdt: "Jumlah transfer",
transfer_amount_placeholder: "Masukkan jumlah USDT",
transfer_type: "Jenis transfer",
binance_pay_transfer: "Internal Binance",
chain_transfer: "Transfer on-chain",
bill_no: "No. tagihan",
bill_no_placeholder: "Masukkan no. tagihan atau txId",
verify_recharge: "Verifikasi isi ulang",
verifying: "Memverifikasi...",
auto_verify_success: "Verifikasi berhasil",
please_enter_order_no: "Masukkan no. pesanan",
please_enter_bill_no: "Masukkan no. tagihan",
loading_data: "Memuat...",
loading_mask: "memuat....",
no_country: "Negara tidak tersedia",
@ -270,6 +342,16 @@
loadingConfig: false,
ordering: false,
activeOrderKey: "",
autoVerifyOpen: false,
autoVerifyLocked: false,
autoVerifyStatusText: "",
autoVerifyStatusType: "",
autoVerifyForm: {
orderNo: "",
transferAmount: "",
transferType: "BINANCE_PAY",
billNo: ""
},
rechargeAmount: "",
rechargeLocked: false,
rechargeStatusText: "",
@ -472,6 +554,10 @@
return "/wallet/freight/ship";
}
function endpointAutoVerify() {
return "/app/h5/binance-recharge/verify";
}
function endpointTransferHistory({ userId, type, searchId = "", lastId = "" }) {
const query = new URLSearchParams({
userId: String(userId || ""),
@ -648,6 +734,16 @@
status.classList.toggle("is-error", type === "error");
}
function setAutoVerifyStatus(text = "", type = "") {
state.autoVerifyStatusText = text;
state.autoVerifyStatusType = type;
const status = document.querySelector("#autoVerifyStatus");
if (!status) return;
status.textContent = text;
status.hidden = !text;
status.classList.toggle("is-error", type === "error");
}
function setHistoryStatus(text = "", type = "") {
state.historyStatusText = text;
state.historyStatusType = type;
@ -672,6 +768,15 @@
return String(value || "").replace(/\D/g, "").replace(/^0+(?=\d)/, "");
}
function sanitizeMoneyAmount(value) {
const raw = String(value || "").replace(/[^\d.]/g, "");
const parts = raw.split(".");
const integerPart = parts.shift() || "";
const decimalPart = parts.join("").slice(0, 8);
const normalizedInteger = integerPart.replace(/^0+(?=\d)/, "");
return decimalPart ? `${normalizedInteger || "0"}.${decimalPart}` : normalizedInteger;
}
function renderSearch() {
const input = document.querySelector("#accountInput");
const button = document.querySelector("#searchButton");
@ -754,6 +859,43 @@
renderProfileAvatar(state.profile, "#targetAvatar", "#targetAvatarFallback");
}
function renderAutoVerify() {
const card = document.querySelector("#autoVerifyCard");
const form = document.querySelector("#autoVerifyForm");
const toggle = document.querySelector("#autoVerifyToggle");
const orderInput = document.querySelector("#autoOrderInput");
const amountInput = document.querySelector("#autoAmountInput");
const typeSelect = document.querySelector("#autoTransferType");
const billInput = document.querySelector("#autoBillInput");
const button = document.querySelector("#autoVerifyButton");
const values = state.autoVerifyForm;
const amount = Number(values.transferAmount);
const canSubmit = Boolean(
state.profile &&
!state.autoVerifyLocked &&
String(values.orderNo || "").trim() &&
Number.isFinite(amount) &&
amount > 0 &&
String(values.billNo || "").trim()
);
if (card) card.hidden = !state.profile;
if (form) form.hidden = !state.autoVerifyOpen || !state.profile;
if (toggle) {
toggle.hidden = !state.profile;
toggle.textContent = state.autoVerifyOpen ? message("close_auto_verify") : message("open_auto_verify");
}
if (orderInput && document.activeElement !== orderInput) orderInput.value = values.orderNo;
if (amountInput && document.activeElement !== amountInput) amountInput.value = values.transferAmount;
if (typeSelect && document.activeElement !== typeSelect) typeSelect.value = values.transferType;
if (billInput && document.activeElement !== billInput) billInput.value = values.billNo;
if (button) {
button.disabled = !canSubmit;
button.textContent = state.autoVerifyLocked ? message("verifying") : message("verify_recharge");
}
setAutoVerifyStatus(state.autoVerifyStatusText, state.autoVerifyStatusType);
}
function renderCoinRecharge() {
const card = document.querySelector("#coinRechargeCard");
const input = document.querySelector("#coinAmountInput");
@ -1073,6 +1215,7 @@
renderBalance();
renderSearch();
renderTarget();
renderAutoVerify();
renderCoinRecharge();
renderWhatsappModal();
}
@ -1225,6 +1368,15 @@
state.searchLocked = true;
state.profile = null;
state.payProfile = null;
state.autoVerifyOpen = false;
state.autoVerifyForm = {
orderNo: "",
transferAmount: "",
transferType: "BINANCE_PAY",
billNo: ""
};
state.autoVerifyStatusText = "";
state.autoVerifyStatusType = "";
state.rechargeAmount = "";
state.rechargeStatusText = "";
state.rechargeStatusType = "";
@ -1241,6 +1393,14 @@
const profile = Array.isArray(result) ? result[0] : result;
if (!profile || !pickProfileId(profile)) throw new Error(message("user_not_found"));
state.profile = profile;
state.autoVerifyForm = {
orderNo: "",
transferAmount: "",
transferType: "BINANCE_PAY",
billNo: ""
};
state.autoVerifyStatusText = "";
state.autoVerifyStatusType = "";
state.rechargeAmount = "";
state.rechargeStatusText = "";
state.rechargeStatusType = "";
@ -1282,6 +1442,88 @@
}
}
async function verifyAutoRecharge(event) {
if (event) event.preventDefault();
if (state.autoVerifyLocked) return;
const values = state.autoVerifyForm;
values.orderNo = String(document.querySelector("#autoOrderInput")?.value || values.orderNo || "").trim();
values.transferAmount = sanitizeMoneyAmount(document.querySelector("#autoAmountInput")?.value || values.transferAmount);
values.transferType = String(document.querySelector("#autoTransferType")?.value || values.transferType || "BINANCE_PAY");
values.billNo = String(document.querySelector("#autoBillInput")?.value || values.billNo || "").trim();
const userId = pickProfileId(state.profile);
const amount = Number(values.transferAmount);
if (!userId) {
const text = message("user_not_found");
setAutoVerifyStatus(text, "error");
showToast(text);
renderAll();
return;
}
if (!values.orderNo) {
const text = message("please_enter_order_no");
setAutoVerifyStatus(text, "error");
showToast(text);
renderAll();
return;
}
if (!Number.isFinite(amount) || amount <= 0) {
const text = message("please_enter_valid_amount");
setAutoVerifyStatus(text, "error");
showToast(text);
renderAll();
return;
}
if (!values.billNo) {
const text = message("please_enter_bill_no");
setAutoVerifyStatus(text, "error");
showToast(text);
renderAll();
return;
}
state.autoVerifyLocked = true;
setAutoVerifyStatus(message("verifying"));
renderAll();
try {
const result = await requestGoJSON(endpointAutoVerify(), {
method: "POST",
body: {
billNo: values.billNo,
orderNo: values.orderNo,
targetUserId: String(userId),
transferAmount: values.transferAmount,
transferType: values.transferType
}
}) || {};
const gold = formatCoins(result.goldAmount || 0);
const successText = `${message("auto_verify_success")}: ${gold} ${message("coins")}`;
state.autoVerifyForm = {
orderNo: "",
transferAmount: "",
transferType: "BINANCE_PAY",
billNo: ""
};
resetHistoryCache();
setAutoVerifyStatus(successText);
showToast(successText);
if (String(userId) === String(pickProfileId(state.selfProfile))) {
await fetchCoinBalance();
}
if (state.historyOpen) fetchHistoryRecords();
} catch (error) {
console.error("Auto recharge verification failed:", error);
const text = error.response?.errorMsg || error.message || message("failed_to_load");
setAutoVerifyStatus(text, "error");
showToast(text);
} finally {
state.autoVerifyLocked = false;
renderAll();
}
}
async function rechargeCoins(event) {
if (event) event.preventDefault();
if (state.rechargeLocked) return;
@ -1509,6 +1751,15 @@
function changeTarget() {
state.profile = null;
state.payProfile = null;
state.autoVerifyOpen = false;
state.autoVerifyForm = {
orderNo: "",
transferAmount: "",
transferType: "BINANCE_PAY",
billNo: ""
};
state.autoVerifyStatusText = "";
state.autoVerifyStatusType = "";
state.rechargeAmount = "";
state.rechargeStatusText = "";
state.rechargeStatusType = "";
@ -1704,8 +1955,36 @@
setRechargeStatus("");
renderCoinRecharge();
});
document.querySelector("#autoVerifyToggle")?.addEventListener("click", () => {
state.autoVerifyOpen = !state.autoVerifyOpen;
renderAutoVerify();
if (state.autoVerifyOpen) window.setTimeout(() => document.querySelector("#autoOrderInput")?.focus(), 60);
});
document.querySelector("#autoOrderInput")?.addEventListener("input", (event) => {
state.autoVerifyForm.orderNo = String(event.target.value || "");
if (state.autoVerifyStatusText) setAutoVerifyStatus("");
renderAutoVerify();
});
document.querySelector("#autoAmountInput")?.addEventListener("input", (event) => {
const value = sanitizeMoneyAmount(event.target.value);
state.autoVerifyForm.transferAmount = value;
if (event.target.value !== value) event.target.value = value;
if (state.autoVerifyStatusText) setAutoVerifyStatus("");
renderAutoVerify();
});
document.querySelector("#autoTransferType")?.addEventListener("change", (event) => {
state.autoVerifyForm.transferType = String(event.target.value || "BINANCE_PAY");
if (state.autoVerifyStatusText) setAutoVerifyStatus("");
renderAutoVerify();
});
document.querySelector("#autoBillInput")?.addEventListener("input", (event) => {
state.autoVerifyForm.billNo = String(event.target.value || "");
if (state.autoVerifyStatusText) setAutoVerifyStatus("");
renderAutoVerify();
});
document.querySelector("#searchForm")?.addEventListener("submit", searchTarget);
document.querySelector("#coinRechargeForm")?.addEventListener("submit", rechargeCoins);
document.querySelector("#autoVerifyForm")?.addEventListener("submit", verifyAutoRecharge);
document.querySelector("[data-change-target]")?.addEventListener("click", changeTarget);
document.querySelector("[data-help-open]")?.addEventListener("click", () => setModalOpen("#helpModal", true));
document.querySelector("[data-history-open]")?.addEventListener("click", () => openHistoryModal());

View File

@ -33,7 +33,8 @@ body {
}
button,
input {
input,
select {
font: inherit;
}
@ -189,7 +190,8 @@ button {
}
.search-card,
.coin-recharge-card {
.coin-recharge-card,
.auto-verify-card {
display: grid;
gap: 14px;
padding: 16px;
@ -242,6 +244,76 @@ button {
gap: 12px;
}
.auto-verify-toggle {
min-width: 64px;
min-height: 34px;
padding: 0 12px;
border-radius: 999px;
background: #e7fbf8;
color: #0aa397;
font-size: 12px;
font-weight: 950;
}
.auto-verify-form {
display: grid;
gap: 12px;
}
.auto-field {
display: grid;
gap: 7px;
min-width: 0;
}
.auto-field span {
color: #555b63;
font-size: 12px;
font-weight: 900;
line-height: 1.2;
}
.auto-field input,
.auto-field select {
width: 100%;
min-width: 0;
height: 44px;
border: 1px solid #e4eeee;
border-radius: 8px;
outline: 0;
padding: 0 12px;
background: #fbfdfd;
color: #24282e;
font-size: 14px;
font-weight: 850;
}
.auto-field select {
appearance: none;
background-image:
linear-gradient(45deg, transparent 50%, #7a7f87 50%),
linear-gradient(135deg, #7a7f87 50%, transparent 50%);
background-position:
calc(100% - 18px) 19px,
calc(100% - 13px) 19px;
background-size:
5px 5px,
5px 5px;
background-repeat: no-repeat;
}
[dir="rtl"] .auto-field select {
background-position:
18px 19px,
13px 19px;
}
.auto-field input:focus,
.auto-field select:focus {
border-color: rgba(21, 189, 169, 0.7);
box-shadow: 0 0 0 3px rgba(21, 189, 169, 0.12);
}
.search-form input {
width: 100%;
min-width: 0;
@ -301,6 +373,7 @@ button {
.search-form button,
.coin-recharge-form button,
.auto-verify-form button,
.package-button,
.mini-action {
min-height: 44px;
@ -322,8 +395,14 @@ button {
padding: 0 14px;
}
.auto-verify-form button {
width: 100%;
padding: 0 14px;
}
.search-form button:disabled,
.coin-recharge-form button:disabled,
.auto-verify-form button:disabled,
.package-button:disabled,
.mini-action:disabled {
opacity: 0.55;