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 @@
-

用户

+

User

- - + +
@@ -48,50 +56,64 @@ U
-
目标用户
+
Target User
-
UID: -
- - -
-

赠送道具

+

Gift Props

+
+ +
+ +
- -
- +
- 加载中... + Loading...
- + 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);