diff --git a/h5/hyapp/manager-center/index.html b/h5/hyapp/manager-center/index.html
index 4a92724..4ebd888 100644
--- a/h5/hyapp/manager-center/index.html
+++ b/h5/hyapp/manager-center/index.html
@@ -1,21 +1,29 @@
-
+
- 经理中心
-
+ Manager Center
+
-
+
@@ -25,7 +33,7 @@
M
-
当前经理
+
Current Manager
-
UID: -
@@ -33,11 +41,11 @@
@@ -48,50 +56,64 @@
U
-
目标用户
+
Target User
-
UID: -
-
- -
-
赠送道具
+ Gift Props
+
+
+
-
-
- 赠送
+ Send
-
封禁用户
+ Ban User
-
-
- 封禁
+
+ Ban
-
+
diff --git a/h5/hyapp/manager-center/locales/ar.json b/h5/hyapp/manager-center/locales/ar.json
new file mode 100644
index 0000000..8898ed2
--- /dev/null
+++ b/h5/hyapp/manager-center/locales/ar.json
@@ -0,0 +1,47 @@
+{
+ "document_title": "مركز المدير",
+ "page_label": "مركز المدير",
+ "back": "رجوع",
+ "title": "مركز المدير",
+ "language_button_aria": "تغيير اللغة",
+ "current_manager": "المدير الحالي",
+ "user": "المستخدم",
+ "user_search_placeholder": "أدخل معرّف المستخدم أو الحساب",
+ "search": "بحث",
+ "searching": "جار البحث...",
+ "target_user": "المستخدم المستهدف",
+ "gift_props": "إرسال العناصر",
+ "gift_type": "نوع الهدية",
+ "prop_type_ride": "مركبة",
+ "prop_type_noble_vip": "VIP نبيل",
+ "prop_type_avatar_frame": "إطار الصورة",
+ "prop_type_chat_bubble": "فقاعة الدردشة",
+ "prop_type_data_card": "بطاقة الملف",
+ "no_giftable_props": "لا توجد عناصر قابلة للإهداء",
+ "send": "إرسال",
+ "sending": "جار الإرسال...",
+ "confirm_send": "هل تريد إرسال هذا العنصر؟",
+ "send_success": "تم الإرسال بنجاح",
+ "ban_user": "حظر المستخدم",
+ "ban_reason": "سبب الحظر",
+ "ban": "حظر",
+ "confirm_ban": "هل تريد حظر هذا المستخدم؟",
+ "submitting": "جار الإرسال...",
+ "ban_success": "تم حظر المستخدم",
+ "loading": "جار التحميل...",
+ "uid_label": "UID",
+ "days": "{count} يوم",
+ "recharged": "تم الشحن",
+ "not_recharged": "لم يتم الشحن",
+ "recharge_unknown": "حالة الشحن غير معروفة",
+ "can_ban": "يمكن الحظر",
+ "cannot_ban": "لا يمكن الحظر",
+ "missing_token": "رمز الدخول مفقود",
+ "not_manager": "الحساب الحالي ليس مديرًا",
+ "user_not_found": "المستخدم غير موجود",
+ "user_recharged": "لدى هذا المستخدم سجلات شحن ولا يمكن حظره",
+ "prop_not_giftable": "هذا العنصر غير متاح كهديّة من المدير",
+ "vip_not_giftable": "لا يمكن إهداء عنصر VIP هذا",
+ "request_failed": "فشل الطلب",
+ "enter_user": "أدخل معرّف المستخدم أو الحساب"
+}
diff --git a/h5/hyapp/manager-center/locales/en.json b/h5/hyapp/manager-center/locales/en.json
new file mode 100644
index 0000000..b3fe437
--- /dev/null
+++ b/h5/hyapp/manager-center/locales/en.json
@@ -0,0 +1,47 @@
+{
+ "document_title": "Manager Center",
+ "page_label": "Manager Center",
+ "back": "Back",
+ "title": "Manager Center",
+ "language_button_aria": "Change language",
+ "current_manager": "Current Manager",
+ "user": "User",
+ "user_search_placeholder": "Enter user ID or account",
+ "search": "Search",
+ "searching": "Searching...",
+ "target_user": "Target User",
+ "gift_props": "Gift Props",
+ "gift_type": "Gift Type",
+ "prop_type_ride": "Ride",
+ "prop_type_noble_vip": "Noble VIP",
+ "prop_type_avatar_frame": "Avatar Frame",
+ "prop_type_chat_bubble": "Chat Bubble",
+ "prop_type_data_card": "Profile Card",
+ "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",
+ "loading": "Loading...",
+ "uid_label": "UID",
+ "days": "{count} days",
+ "recharged": "Recharged",
+ "not_recharged": "Not recharged",
+ "recharge_unknown": "Recharge unknown",
+ "can_ban": "Can ban",
+ "cannot_ban": "Cannot ban",
+ "missing_token": "Missing token",
+ "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",
+ "prop_not_giftable": "This prop is not available for manager gifting",
+ "vip_not_giftable": "This noble prop cannot be gifted",
+ "request_failed": "Request failed",
+ "enter_user": "Enter user ID or account"
+}
diff --git a/h5/hyapp/manager-center/locales/id.json b/h5/hyapp/manager-center/locales/id.json
new file mode 100644
index 0000000..f88ef44
--- /dev/null
+++ b/h5/hyapp/manager-center/locales/id.json
@@ -0,0 +1,47 @@
+{
+ "document_title": "Pusat Manajer",
+ "page_label": "Pusat Manajer",
+ "back": "Kembali",
+ "title": "Pusat Manajer",
+ "language_button_aria": "Ubah bahasa",
+ "current_manager": "Manajer Saat Ini",
+ "user": "Pengguna",
+ "user_search_placeholder": "Masukkan ID pengguna atau akun",
+ "search": "Cari",
+ "searching": "Mencari...",
+ "target_user": "Pengguna Target",
+ "gift_props": "Kirim Properti",
+ "gift_type": "Jenis Hadiah",
+ "prop_type_ride": "Kendaraan",
+ "prop_type_noble_vip": "Noble VIP",
+ "prop_type_avatar_frame": "Bingkai Avatar",
+ "prop_type_chat_bubble": "Gelembung Chat",
+ "prop_type_data_card": "Kartu Profil",
+ "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",
+ "loading": "Memuat...",
+ "uid_label": "UID",
+ "days": "{count} hari",
+ "recharged": "Sudah isi ulang",
+ "not_recharged": "Belum isi ulang",
+ "recharge_unknown": "Status isi ulang tidak diketahui",
+ "can_ban": "Dapat diblokir",
+ "cannot_ban": "Tidak dapat diblokir",
+ "missing_token": "Token tidak ditemukan",
+ "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",
+ "prop_not_giftable": "Properti ini tidak tersedia untuk hadiah manajer",
+ "vip_not_giftable": "Properti noble ini tidak dapat dikirim",
+ "request_failed": "Permintaan gagal",
+ "enter_user": "Masukkan ID pengguna atau akun"
+}
diff --git a/h5/hyapp/manager-center/locales/tr.json b/h5/hyapp/manager-center/locales/tr.json
new file mode 100644
index 0000000..466b7f6
--- /dev/null
+++ b/h5/hyapp/manager-center/locales/tr.json
@@ -0,0 +1,47 @@
+{
+ "document_title": "Yönetici Merkezi",
+ "page_label": "Yönetici Merkezi",
+ "back": "Geri",
+ "title": "Yönetici Merkezi",
+ "language_button_aria": "Dili değiştir",
+ "current_manager": "Mevcut Yönetici",
+ "user": "Kullanıcı",
+ "user_search_placeholder": "Kullanıcı ID veya hesap gir",
+ "search": "Ara",
+ "searching": "Aranıyor...",
+ "target_user": "Hedef Kullanıcı",
+ "gift_props": "Eşya Gönder",
+ "gift_type": "Hediye Türü",
+ "prop_type_ride": "Araç",
+ "prop_type_noble_vip": "Noble VIP",
+ "prop_type_avatar_frame": "Avatar Çerçevesi",
+ "prop_type_chat_bubble": "Sohbet Balonu",
+ "prop_type_data_card": "Profil Kartı",
+ "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ı",
+ "loading": "Yükleniyor...",
+ "uid_label": "UID",
+ "days": "{count} gün",
+ "recharged": "Yükleme yaptı",
+ "not_recharged": "Yükleme yapmadı",
+ "recharge_unknown": "Yükleme durumu bilinmiyor",
+ "can_ban": "Yasaklanabilir",
+ "cannot_ban": "Yasaklanamaz",
+ "missing_token": "Token eksik",
+ "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",
+ "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",
+ "enter_user": "Kullanıcı ID veya hesap gir"
+}
diff --git a/h5/hyapp/manager-center/script.js b/h5/hyapp/manager-center/script.js
index 4352f3d..396a652 100644
--- a/h5/hyapp/manager-center/script.js
+++ b/h5/hyapp/manager-center/script.js
@@ -1,9 +1,207 @@
(function () {
+ const supportedLanguages = ["en", "ar", "tr", "id"];
+ const languageLabels = {
+ en: "EN",
+ ar: "AR",
+ tr: "TR",
+ id: "ID"
+ };
const API_BASE_URL = "https://jvapi.haiyihy.com";
const GO_API_PATH_PREFIX = "/go";
const LOCAL_GO_API_BASE_URL = "http://127.0.0.1:1100";
const defaultPropsType = "RIDE";
+ const fallbackMessages = {
+ en: {
+ document_title: "Manager Center",
+ page_label: "Manager Center",
+ back: "Back",
+ title: "Manager Center",
+ language_button_aria: "Change language",
+ current_manager: "Current Manager",
+ user: "User",
+ user_search_placeholder: "Enter user ID or account",
+ search: "Search",
+ searching: "Searching...",
+ target_user: "Target User",
+ gift_props: "Gift Props",
+ gift_type: "Gift Type",
+ prop_type_ride: "Ride",
+ prop_type_noble_vip: "Noble VIP",
+ prop_type_avatar_frame: "Avatar Frame",
+ prop_type_chat_bubble: "Chat Bubble",
+ prop_type_data_card: "Profile Card",
+ 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",
+ loading: "Loading...",
+ uid_label: "UID",
+ days: "{count} days",
+ recharged: "Recharged",
+ not_recharged: "Not recharged",
+ recharge_unknown: "Recharge unknown",
+ can_ban: "Can ban",
+ cannot_ban: "Cannot ban",
+ missing_token: "Missing token",
+ 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",
+ prop_not_giftable: "This prop is not available for manager gifting",
+ vip_not_giftable: "This noble prop cannot be gifted",
+ request_failed: "Request failed",
+ enter_user: "Enter user ID or account"
+ },
+ ar: {
+ document_title: "مركز المدير",
+ page_label: "مركز المدير",
+ back: "رجوع",
+ title: "مركز المدير",
+ language_button_aria: "تغيير اللغة",
+ current_manager: "المدير الحالي",
+ user: "المستخدم",
+ user_search_placeholder: "أدخل معرّف المستخدم أو الحساب",
+ search: "بحث",
+ searching: "جار البحث...",
+ target_user: "المستخدم المستهدف",
+ gift_props: "إرسال العناصر",
+ gift_type: "نوع الهدية",
+ prop_type_ride: "مركبة",
+ prop_type_noble_vip: "VIP نبيل",
+ prop_type_avatar_frame: "إطار الصورة",
+ prop_type_chat_bubble: "فقاعة الدردشة",
+ prop_type_data_card: "بطاقة الملف",
+ no_giftable_props: "لا توجد عناصر قابلة للإهداء",
+ send: "إرسال",
+ sending: "جار الإرسال...",
+ confirm_send: "هل تريد إرسال هذا العنصر؟",
+ send_success: "تم الإرسال بنجاح",
+ ban_user: "حظر المستخدم",
+ ban_reason: "سبب الحظر",
+ ban: "حظر",
+ confirm_ban: "هل تريد حظر هذا المستخدم؟",
+ submitting: "جار الإرسال...",
+ ban_success: "تم حظر المستخدم",
+ loading: "جار التحميل...",
+ uid_label: "UID",
+ days: "{count} يوم",
+ recharged: "تم الشحن",
+ not_recharged: "لم يتم الشحن",
+ recharge_unknown: "حالة الشحن غير معروفة",
+ can_ban: "يمكن الحظر",
+ cannot_ban: "لا يمكن الحظر",
+ missing_token: "رمز الدخول مفقود",
+ not_manager: "الحساب الحالي ليس مديرًا",
+ user_not_found: "المستخدم غير موجود",
+ user_recharged: "لدى هذا المستخدم سجلات شحن ولا يمكن حظره",
+ prop_not_giftable: "هذا العنصر غير متاح كهديّة من المدير",
+ vip_not_giftable: "لا يمكن إهداء عنصر VIP هذا",
+ request_failed: "فشل الطلب",
+ enter_user: "أدخل معرّف المستخدم أو الحساب"
+ },
+ tr: {
+ document_title: "Yönetici Merkezi",
+ page_label: "Yönetici Merkezi",
+ back: "Geri",
+ title: "Yönetici Merkezi",
+ language_button_aria: "Dili değiştir",
+ current_manager: "Mevcut Yönetici",
+ user: "Kullanıcı",
+ user_search_placeholder: "Kullanıcı ID veya hesap gir",
+ search: "Ara",
+ searching: "Aranıyor...",
+ target_user: "Hedef Kullanıcı",
+ gift_props: "Eşya Gönder",
+ gift_type: "Hediye Türü",
+ prop_type_ride: "Araç",
+ prop_type_noble_vip: "Noble VIP",
+ prop_type_avatar_frame: "Avatar Çerçevesi",
+ prop_type_chat_bubble: "Sohbet Balonu",
+ prop_type_data_card: "Profil Kartı",
+ 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ı",
+ loading: "Yükleniyor...",
+ uid_label: "UID",
+ days: "{count} gün",
+ recharged: "Yükleme yaptı",
+ not_recharged: "Yükleme yapmadı",
+ recharge_unknown: "Yükleme durumu bilinmiyor",
+ can_ban: "Yasaklanabilir",
+ cannot_ban: "Yasaklanamaz",
+ missing_token: "Token eksik",
+ 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",
+ 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",
+ enter_user: "Kullanıcı ID veya hesap gir"
+ },
+ id: {
+ document_title: "Pusat Manajer",
+ page_label: "Pusat Manajer",
+ back: "Kembali",
+ title: "Pusat Manajer",
+ language_button_aria: "Ubah bahasa",
+ current_manager: "Manajer Saat Ini",
+ user: "Pengguna",
+ user_search_placeholder: "Masukkan ID pengguna atau akun",
+ search: "Cari",
+ searching: "Mencari...",
+ target_user: "Pengguna Target",
+ gift_props: "Kirim Properti",
+ gift_type: "Jenis Hadiah",
+ prop_type_ride: "Kendaraan",
+ prop_type_noble_vip: "Noble VIP",
+ prop_type_avatar_frame: "Bingkai Avatar",
+ prop_type_chat_bubble: "Gelembung Chat",
+ prop_type_data_card: "Kartu Profil",
+ 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",
+ loading: "Memuat...",
+ uid_label: "UID",
+ days: "{count} hari",
+ recharged: "Sudah isi ulang",
+ not_recharged: "Belum isi ulang",
+ recharge_unknown: "Status isi ulang tidak diketahui",
+ can_ban: "Dapat diblokir",
+ cannot_ban: "Tidak dapat diblokir",
+ missing_token: "Token tidak ditemukan",
+ 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",
+ prop_not_giftable: "Properti ini tidak tersedia untuk hadiah manajer",
+ vip_not_giftable: "Properti noble ini tidak dapat dikirim",
+ request_failed: "Permintaan gagal",
+ enter_user: "Masukkan ID pengguna atau akun"
+ }
+ };
+
const state = {
manager: null,
target: null,
@@ -13,6 +211,8 @@
};
const params = readURLParams();
+ let currentLang = normalizeLanguage(params.get("lang"));
+ let currentMessages = fallbackMessages[currentLang] || fallbackMessages.en;
function readURLParams() {
const result = new URLSearchParams(window.location.search);
@@ -51,6 +251,129 @@
return "";
}
+ function normalizeLanguage(lang) {
+ const value = String(lang || "en").toLowerCase();
+ return supportedLanguages.includes(value) ? value : "en";
+ }
+
+ async function loadMessages(lang) {
+ const normalizedLang = normalizeLanguage(lang);
+ if (window.location.protocol === "file:") return fallbackMessages[normalizedLang];
+
+ try {
+ const response = await fetch(`./locales/${normalizedLang}.json?v=20260511-0105`, { cache: "no-store" });
+ if (response.ok) return await response.json();
+ } catch (error) {
+ return fallbackMessages[normalizedLang];
+ }
+
+ return fallbackMessages[normalizedLang];
+ }
+
+ function localizedValue(messages, key) {
+ return messages?.[key] || fallbackMessages[currentLang]?.[key] || fallbackMessages.en[key] || "";
+ }
+
+ function message(key, fallback = "") {
+ return localizedValue(currentMessages, key) || fallback || key;
+ }
+
+ function renderMessages(messages) {
+ document.querySelectorAll("[data-i18n]").forEach((node) => {
+ const value = localizedValue(messages, node.dataset.i18n);
+ if (value) node.textContent = value;
+ });
+
+ document.querySelectorAll("[data-i18n-aria]").forEach((node) => {
+ const value = localizedValue(messages, node.dataset.i18nAria);
+ if (value) node.setAttribute("aria-label", value);
+ });
+
+ document.querySelectorAll("[data-i18n-placeholder]").forEach((node) => {
+ const value = localizedValue(messages, node.dataset.i18nPlaceholder);
+ if (value) node.setAttribute("placeholder", value);
+ });
+ }
+
+ function setLanguageMenuOpen(open) {
+ const languageButton = $(".language-button");
+ const languageMenu = $(".language-menu");
+ if (!languageButton || !languageMenu) return;
+
+ languageMenu.hidden = !open;
+ languageButton.setAttribute("aria-expanded", String(open));
+ }
+
+ function renderLanguageControls(messages) {
+ const languageButton = $(".language-button");
+ if (languageButton) {
+ languageButton.textContent = languageLabels[currentLang];
+ languageButton.setAttribute("aria-label", localizedValue(messages, "language_button_aria") || "Change language");
+ }
+
+ document.querySelectorAll(".language-menu [data-lang]").forEach((button) => {
+ const isActive = button.dataset.lang === currentLang;
+ button.classList.toggle("is-active", isActive);
+ button.setAttribute("aria-pressed", String(isActive));
+ });
+ }
+
+ function setPropsTypeMenuOpen(open) {
+ const root = $("#propsTypeSelect");
+ const button = $("#propsTypeButton");
+ const menu = $("#propsTypeMenu");
+ if (!root || !button || !menu) return;
+
+ root.classList.toggle("is-open", open);
+ menu.hidden = !open;
+ button.setAttribute("aria-expanded", String(open));
+ }
+
+ function renderPropsTypeSelect() {
+ const root = $("#propsTypeSelect");
+ const label = $("#propsTypeSelectedLabel");
+ if (root) root.dataset.value = state.propsType;
+ if (label) label.textContent = propTypeLabel(state.propsType);
+
+ document.querySelectorAll("#propsTypeMenu [data-value]").forEach((button) => {
+ const isSelected = button.dataset.value === state.propsType;
+ button.classList.toggle("is-selected", isSelected);
+ button.setAttribute("aria-selected", String(isSelected));
+ });
+ }
+
+ async function choosePropsType(value) {
+ const nextValue = String(value || defaultPropsType);
+ setPropsTypeMenuOpen(false);
+ if (nextValue === state.propsType) {
+ renderPropsTypeSelect();
+ return;
+ }
+ state.propsType = nextValue;
+ renderPropsTypeSelect();
+ try {
+ await loadProps();
+ } catch (error) {
+ setStatus($("#propsStatus"), friendlyError(error), "error");
+ }
+ }
+
+ async function applyLanguage(lang) {
+ currentLang = normalizeLanguage(lang);
+ const messages = await loadMessages(currentLang);
+ currentMessages = messages;
+
+ document.documentElement.lang = currentLang;
+ document.documentElement.dir = currentLang === "ar" ? "rtl" : "ltr";
+ document.title = message("document_title", "Manager Center");
+ renderMessages(messages);
+ renderLanguageControls(messages);
+ renderPropsTypeSelect();
+ renderManager(state.manager);
+ renderTarget();
+ renderProps();
+ }
+
function $(selector) {
return document.querySelector(selector);
}
@@ -95,7 +418,7 @@
const sysOrigin = params.get("sysOrigin") || params.get("origin") || "ATYOU";
return {
Authorization: normalizeAuthorization(token),
- "req-lang": params.get("lang") || "zh-CN",
+ "req-lang": currentLang,
"req-client": "H5",
"req-version": "V2",
"req-zone": Intl.DateTimeFormat().resolvedOptions().timeZone || "Asia/Shanghai",
@@ -107,7 +430,7 @@
async function requestGoJSON(path, options = {}) {
const token = readRawParam("token");
if (!token) {
- const error = new Error("缺少登录信息");
+ const error = new Error(message("missing_token"));
error.code = "missing_token";
throw error;
}
@@ -226,17 +549,30 @@
return String(user?.userId || user?.id || "-");
}
- function displayAccount(user) {
- return user?.account ? ` (${user.account})` : "";
+ function displayShortID(user) {
+ return String(user?.actualAccount || user?.account || user?.shortId || user?.userId || user?.id || "-");
}
function canBanTarget(target) {
return Boolean(target) && target.canBan !== false && !target.hasRecharge;
}
+ function userIDText(user) {
+ return `${message("uid_label", "UID")}: ${displayShortID(user)}`;
+ }
+
+ function propTypeLabel(type) {
+ const key = `prop_type_${String(type || "").toLowerCase()}`;
+ return message(key, type || "-");
+ }
+
+ function formatDays(days) {
+ return message("days", "{count} days").replace("{count}", String(days || 0));
+ }
+
function renderManager(manager) {
$("#managerName").textContent = displayName(manager);
- $("#managerUid").textContent = `UID: ${displayUserID(manager)}${displayAccount(manager)}`;
+ $("#managerUid").textContent = userIDText(manager);
renderAvatar("#managerAvatar", "#managerAvatarFallback", manager?.userAvatar, displayName(manager), "M");
}
@@ -254,14 +590,11 @@
card.hidden = false;
banCard.hidden = false;
$("#targetName").textContent = displayName(target);
- $("#targetUid").textContent = `UID: ${displayUserID(target)}${displayAccount(target)}`;
+ $("#targetUid").textContent = userIDText(target);
$("#targetAccountStatus").textContent = target.accountStatus || "-";
- const rechargeStatus = $("#targetRechargeStatus");
const canBan = canBanTarget(target);
- rechargeStatus.textContent = target.hasRecharge ? "已充值" : (canBan ? "未充值" : "充值状态未知");
- rechargeStatus.classList.toggle("is-danger", Boolean(target.hasRecharge) || !canBan);
const banEligibility = $("#banEligibility");
- banEligibility.textContent = canBan ? "可封禁" : "不可封禁";
+ banEligibility.textContent = canBan ? message("can_ban") : message("cannot_ban");
banEligibility.classList.toggle("is-danger", !canBan);
renderAvatar("#targetAvatar", "#targetAvatarFallback", target.userAvatar, displayName(target), "U");
updateActionState();
@@ -272,7 +605,7 @@
if (!list) return;
list.replaceChildren();
if (!state.props.length) {
- setStatus($("#propsStatus"), "没有可赠送道具");
+ setStatus($("#propsStatus"), message("no_giftable_props"));
updateActionState();
return;
}
@@ -307,12 +640,12 @@
name.textContent = item.name || `ID ${item.id}`;
const meta = document.createElement("span");
meta.className = "prop-meta";
- meta.textContent = item.vipLevel ? `${item.type} · VIP ${item.vipLevel}` : item.type;
+ meta.textContent = item.vipLevel ? `${propTypeLabel(item.type)} · VIP ${item.vipLevel}` : propTypeLabel(item.type);
copy.append(name, meta);
const days = document.createElement("span");
days.className = "prop-days";
- days.textContent = `${item.days || 0}天`;
+ days.textContent = formatDays(item.days);
button.append(cover, copy, days);
button.addEventListener("click", () => {
@@ -339,10 +672,10 @@
}
async function loadProps() {
- setStatus($("#propsStatus"), "加载中...");
state.props = [];
state.selectedPropsId = "";
renderProps();
+ setStatus($("#propsStatus"), message("loading"));
const body = await requestGoJSON(endpointProps(state.propsType));
state.props = Array.isArray(body?.records) ? body.records : [];
@@ -355,12 +688,12 @@
const input = $("#accountInput");
const account = String(input?.value || "").trim();
if (!account) {
- setStatus($("#searchStatus"), "请输入用户 ID 或账号", "error");
+ setStatus($("#searchStatus"), message("enter_user"), "error");
return;
}
$("#searchButton").disabled = true;
- setStatus($("#searchStatus"), "搜索中...");
+ setStatus($("#searchStatus"), message("searching"));
try {
const body = await requestGoJSON(endpointSearchUser(account));
state.target = body?.user || body || null;
@@ -377,7 +710,7 @@
async function sendProps() {
if (!state.target || !state.selectedPropsId) return;
- if (!window.confirm("确认赠送该道具?")) return;
+ if (!window.confirm(message("confirm_send"))) return;
const button = $("#sendPropsButton");
button.disabled = true;
@@ -389,7 +722,7 @@
propsId: state.selectedPropsId
}
});
- showToast("赠送成功");
+ showToast(message("send_success"));
} catch (error) {
showToast(friendlyError(error));
} finally {
@@ -399,11 +732,11 @@
async function banUser() {
if (!canBanTarget(state.target)) return;
- if (!window.confirm("确认封禁该用户?")) return;
+ if (!window.confirm(message("confirm_ban"))) return;
const button = $("#banButton");
button.disabled = true;
- setStatus($("#banStatus"), "提交中...");
+ setStatus($("#banStatus"), message("submitting"));
try {
await requestGoJSON(endpointBanUser(), {
method: "POST",
@@ -415,7 +748,7 @@
state.target.accountStatus = "ARCHIVE";
renderTarget();
setStatus($("#banStatus"), "");
- showToast("封禁成功");
+ showToast(message("ban_success"));
} catch (error) {
setStatus($("#banStatus"), friendlyError(error), "error");
} finally {
@@ -425,36 +758,60 @@
function friendlyError(error) {
const code = error?.code || error?.response?.code || error?.response?.errorCodeName || "";
- if (code === "missing_token") return "缺少登录信息";
- if (code === "not_manager") return "当前账号不是经理";
- if (code === "user_not_found") return "用户不存在";
- if (code === "user_recharged") return "该用户已有充值记录,不能封禁";
- if (code === "prop_not_giftable") return "该道具未开放经理赠送";
- if (code === "vip_not_giftable") return "该贵族道具不可赠送";
- return error?.message || "请求失败";
+ 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 === "prop_not_giftable") return message("prop_not_giftable");
+ if (code === "vip_not_giftable") return message("vip_not_giftable");
+ return error?.message || message("request_failed");
}
function bindEvents() {
$(".back-button")?.addEventListener("click", closePage);
- $("#searchForm")?.addEventListener("submit", searchUser);
- $("#propsTypeSelect")?.addEventListener("change", async (event) => {
- state.propsType = event.target.value || defaultPropsType;
- try {
- await loadProps();
- } catch (error) {
- setStatus($("#propsStatus"), friendlyError(error), "error");
- }
+ $(".language-button")?.addEventListener("click", (event) => {
+ event.stopPropagation();
+ setLanguageMenuOpen(Boolean($(".language-menu")?.hidden));
});
+ document.querySelectorAll(".language-menu [data-lang]").forEach((button) => {
+ button.addEventListener("click", async (event) => {
+ event.stopPropagation();
+ setLanguageMenuOpen(false);
+ await applyLanguage(button.dataset.lang);
+ });
+ });
+ document.addEventListener("click", () => {
+ setLanguageMenuOpen(false);
+ setPropsTypeMenuOpen(false);
+ });
+ $("#propsTypeButton")?.addEventListener("click", (event) => {
+ event.stopPropagation();
+ setLanguageMenuOpen(false);
+ setPropsTypeMenuOpen(Boolean($("#propsTypeMenu")?.hidden));
+ });
+ document.querySelectorAll("#propsTypeMenu [data-value]").forEach((button) => {
+ button.addEventListener("click", async (event) => {
+ event.stopPropagation();
+ await choosePropsType(button.dataset.value);
+ });
+ });
+ document.addEventListener("keydown", (event) => {
+ if (event.key !== "Escape") return;
+ setLanguageMenuOpen(false);
+ setPropsTypeMenuOpen(false);
+ });
+ $("#searchForm")?.addEventListener("submit", searchUser);
$("#sendPropsButton")?.addEventListener("click", sendProps);
$("#banButton")?.addEventListener("click", banUser);
}
async function init() {
bindEvents();
- $("#propsTypeSelect").value = state.propsType;
+ renderPropsTypeSelect();
+ await applyLanguage(currentLang);
if (!readRawParam("token")) {
setLoading(false);
- setStatus($("#searchStatus"), "缺少登录信息", "error");
+ setStatus($("#searchStatus"), message("missing_token"), "error");
return;
}
diff --git a/h5/hyapp/manager-center/style.css b/h5/hyapp/manager-center/style.css
index 9288e15..78d3e0b 100644
--- a/h5/hyapp/manager-center/style.css
+++ b/h5/hyapp/manager-center/style.css
@@ -36,7 +36,6 @@ body {
button,
input,
-select,
textarea {
font: inherit;
}
@@ -110,6 +109,58 @@ button:disabled {
stroke-linejoin: round;
}
+.language-switcher {
+ position: relative;
+ justify-self: end;
+}
+
+.language-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 42px;
+ height: 30px;
+ border: 1px solid rgba(35, 40, 46, 0.12);
+ border-radius: 15px;
+ background: rgba(255, 255, 255, 0.76);
+ color: var(--text);
+ font-size: 13px;
+ font-weight: 900;
+ box-shadow: 0 4px 14px rgba(32, 36, 42, 0.08);
+}
+
+.language-menu {
+ position: absolute;
+ z-index: 8;
+ top: calc(100% + 7px);
+ right: 0;
+ display: grid;
+ gap: 2px;
+ min-width: 58px;
+ padding: 5px;
+ border: 1px solid rgba(35, 40, 46, 0.1);
+ border-radius: 8px;
+ background: rgba(255, 255, 255, 0.96);
+ box-shadow: 0 10px 26px rgba(36, 46, 60, 0.14);
+}
+
+.language-menu button {
+ min-height: 28px;
+ border-radius: 6px;
+ color: var(--text);
+ font-size: 12px;
+ font-weight: 900;
+}
+
+.language-menu button.is-active {
+ background: #e7f8f4;
+ color: var(--primary-dark);
+}
+
+.language-menu[hidden] {
+ display: none;
+}
+
.content {
position: relative;
z-index: 1;
@@ -245,7 +296,6 @@ button:disabled {
}
input,
-select,
textarea {
width: 100%;
border: 1px solid var(--line);
@@ -256,8 +306,11 @@ textarea {
}
input,
-select {
+button.custom-select-button {
height: 44px;
+}
+
+input {
padding: 0 12px;
}
@@ -268,7 +321,6 @@ textarea {
}
input:focus,
-select:focus,
textarea:focus {
border-color: var(--primary);
background: #fff;
@@ -312,22 +364,120 @@ textarea:focus {
font-weight: 800;
}
+.custom-select {
+ position: relative;
+}
+
+.custom-select-button {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 24px;
+ align-items: center;
+ width: 100%;
+ border: 1px solid var(--primary);
+ border-radius: 8px;
+ padding: 0 12px 0 14px;
+ background: #fff;
+ color: var(--text);
+ text-align: left;
+ box-shadow: 0 0 0 5px rgba(24, 184, 159, 0.1);
+}
+
+.custom-select-button span {
+ overflow: hidden;
+ font-size: 16px;
+ font-weight: 800;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.custom-select-button svg {
+ width: 22px;
+ height: 22px;
+ fill: none;
+ justify-self: end;
+ stroke: currentColor;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ stroke-width: 2.2;
+ transition: transform 0.16s ease;
+}
+
+.custom-select.is-open .custom-select-button svg {
+ transform: rotate(180deg);
+}
+
+.custom-select-menu {
+ position: absolute;
+ z-index: 12;
+ top: calc(100% + 8px);
+ right: 0;
+ left: 0;
+ display: grid;
+ gap: 4px;
+ max-height: 220px;
+ overflow-y: auto;
+ border: 1px solid rgba(35, 40, 46, 0.1);
+ border-radius: 8px;
+ padding: 6px;
+ background: rgba(255, 255, 255, 0.98);
+ box-shadow: 0 16px 36px rgba(32, 36, 42, 0.18);
+}
+
+.custom-select-menu button {
+ min-height: 36px;
+ border-radius: 6px;
+ padding: 0 11px;
+ background: transparent;
+ color: var(--text);
+ font-size: 14px;
+ font-weight: 800;
+ text-align: left;
+}
+
+.custom-select-menu button.is-selected {
+ background: #e7f8f4;
+ color: var(--primary-dark);
+}
+
+.custom-select-menu button:active {
+ background: #dff4ef;
+}
+
+.custom-select-menu[hidden] {
+ display: none;
+}
+
.props-list {
display: grid;
- gap: 8px;
- margin-top: 12px;
+ gap: 6px;
+ max-height: 330px;
+ margin-top: 10px;
+ overflow-y: auto;
+ padding-right: 2px;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: rgba(24, 184, 159, 0.45) transparent;
+}
+
+.props-list::-webkit-scrollbar {
+ width: 5px;
+}
+
+.props-list::-webkit-scrollbar-thumb {
+ border-radius: 999px;
+ background: rgba(24, 184, 159, 0.42);
}
.prop-option {
display: grid;
- grid-template-columns: 52px 1fr auto;
+ grid-template-columns: 42px minmax(0, 1fr) auto;
align-items: center;
- gap: 10px;
+ gap: 8px;
width: 100%;
- min-height: 68px;
+ min-height: 54px;
border: 1px solid var(--line);
border-radius: 8px;
- padding: 8px;
+ padding: 6px 8px;
background: #fff;
color: inherit;
text-align: left;
@@ -339,10 +489,10 @@ textarea:focus {
}
.prop-cover {
- width: 52px;
- height: 52px;
+ width: 42px;
+ height: 42px;
overflow: hidden;
- border-radius: 8px;
+ border-radius: 7px;
background: #eef2f5;
}
@@ -364,29 +514,32 @@ textarea:focus {
}
.prop-copy {
+ display: grid;
+ gap: 2px;
min-width: 0;
}
.prop-name {
overflow: hidden;
- font-size: 15px;
+ font-size: 14px;
font-weight: 900;
+ line-height: 18px;
text-overflow: ellipsis;
white-space: nowrap;
}
.prop-meta {
- margin-top: 4px;
color: var(--muted);
- font-size: 12px;
+ font-size: 11px;
+ line-height: 14px;
}
.prop-days {
border-radius: 999px;
- padding: 5px 8px;
+ padding: 4px 8px;
background: #f2f4f7;
color: #596273;
- font-size: 12px;
+ font-size: 11px;
font-weight: 900;
}
@@ -447,6 +600,28 @@ textarea:focus {
text-align: center;
}
+[dir="rtl"] .back-button svg {
+ transform: rotate(180deg);
+}
+
+[dir="rtl"] .language-menu {
+ right: auto;
+ left: 0;
+}
+
+[dir="rtl"] .profile-copy,
+[dir="rtl"] input,
+[dir="rtl"] textarea,
+[dir="rtl"] .custom-select-button,
+[dir="rtl"] .custom-select-menu button,
+[dir="rtl"] .prop-option {
+ text-align: right;
+}
+
+[dir="rtl"] .custom-select-button {
+ padding: 0 14px 0 12px;
+}
+
@keyframes spin {
to {
transform: rotate(360deg);