This commit is contained in:
zhx 2026-06-22 17:34:33 +08:00
parent 3479dca81b
commit b603946051
7 changed files with 321 additions and 3 deletions

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=20260522-0101" />
<link rel="stylesheet" href="./style.css?v=20260622-0100" />
</head>
<body>
<div class="manager-center" aria-label="Manager Center" data-i18n-aria="page_label" data-loading="true">
@ -61,6 +61,7 @@
<div class="meta" id="targetUid">UID: -</div>
<div class="pill-row">
<span class="status-pill" id="targetAccountStatus">-</span>
<span class="status-pill" id="targetCountryStatus">-</span>
</div>
</div>
</section>
@ -82,6 +83,22 @@
<path d="m9 6 6 6-6 6" />
</svg>
</button>
<button class="action-item" id="countryEntryButton" 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="M3 12h18" />
<path d="M12 3a14 14 0 0 1 0 18" />
<path d="M12 3a14 14 0 0 0 0 18" />
</svg>
</span>
<span class="action-item-copy">
<span class="action-item-title" data-i18n="change_country">Change Country</span>
</span>
<svg class="action-item-arrow" viewBox="0 0 24 24" aria-hidden="true">
<path d="m9 6 6 6-6 6" />
</svg>
</button>
<button class="action-item" id="giftEntryButton" type="button">
<span class="action-item-icon" aria-hidden="true">
<svg viewBox="0 0 24 24">
@ -142,6 +159,41 @@
</section>
</main>
<div class="modal-mask" id="countryModal" role="dialog" aria-modal="true" aria-labelledby="countryModalTitle" hidden>
<section class="modal-sheet">
<div class="modal-head">
<h2 id="countryModalTitle" data-i18n="change_country">Change Country</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="countryLabelText" data-i18n="select_country">Select country</label>
<div class="current-country" id="currentCountryText">-</div>
<div class="custom-select" id="countrySelect" data-value="">
<button
class="custom-select-button"
id="countryButton"
type="button"
aria-expanded="false"
aria-haspopup="listbox"
aria-labelledby="countryLabelText countrySelectedLabel"
>
<span id="countrySelectedLabel">-</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="countryMenu" role="listbox" aria-labelledby="countryLabelText" hidden></div>
</div>
<button class="primary-action" id="changeCountryButton" type="button" disabled data-i18n="change_country">Change Country</button>
<div class="form-status" id="countryStatus" role="status" hidden></div>
</div>
</section>
</div>
<div class="modal-mask" id="giftModal" role="dialog" aria-modal="true" aria-labelledby="giftModalTitle" hidden>
<section class="modal-sheet">
<div class="modal-head">
@ -228,6 +280,6 @@
</div>
<div class="toast" id="toast" role="status" hidden></div>
</div>
<script src="./script.js?v=20260529-1300" defer></script>
<script src="./script.js?v=20260622-0100" defer></script>
</body>
</html>

View File

@ -11,6 +11,12 @@
"search": "بحث",
"searching": "جار البحث...",
"target_user": "المستخدم المستهدف",
"current_country": "Current country",
"change_country": "Change Country",
"select_country": "Select country",
"changing_country": "Changing...",
"country_changed": "Country changed",
"no_countries": "No countries available",
"bd_leaders": "قادة BD",
"bd_leader_count": "{count} قادة BD",
"refresh": "تحديث",
@ -65,6 +71,9 @@
"user_is_admin": "لا يمكن إضافة مستخدم إداري كقائد BD",
"user_already_bd_leader": "هذا المستخدم قائد BD بالفعل",
"user_already_bound_bd_leader": "هذا المستخدم مرتبط بالفعل بقائد BD",
"user_identity_locked": "This user has Host, Agency, BD, BD Leader, or Coin Seller identity and cannot be moved",
"country_not_found": "Country not found",
"country_required": "Select country",
"prop_not_giftable": "هذا العنصر غير متاح كهديّة من المدير",
"vip_not_giftable": "لا يمكن إهداء عنصر VIP هذا",
"manager_feature_disabled": "تم تعطيل هذه الميزة من الإدارة",

View File

@ -11,6 +11,12 @@
"search": "Search",
"searching": "Searching...",
"target_user": "Target User",
"current_country": "Current country",
"change_country": "Change Country",
"select_country": "Select country",
"changing_country": "Changing...",
"country_changed": "Country changed",
"no_countries": "No countries available",
"bd_leaders": "BD Leaders",
"bd_leader_count": "{count} BD Leaders",
"refresh": "Refresh",
@ -65,6 +71,9 @@
"user_is_admin": "Admin users cannot be added as BD Leaders",
"user_already_bd_leader": "This user is already a BD Leader",
"user_already_bound_bd_leader": "This user is already bound to a BD Leader",
"user_identity_locked": "This user has Host, Agency, BD, BD Leader, or Coin Seller identity and cannot be moved",
"country_not_found": "Country not found",
"country_required": "Select country",
"prop_not_giftable": "This prop is not available for manager gifting",
"vip_not_giftable": "This noble prop cannot be gifted",
"manager_feature_disabled": "This feature is disabled by admin",

View File

@ -11,6 +11,12 @@
"search": "Cari",
"searching": "Mencari...",
"target_user": "Pengguna Target",
"current_country": "Current country",
"change_country": "Change Country",
"select_country": "Select country",
"changing_country": "Changing...",
"country_changed": "Country changed",
"no_countries": "No countries available",
"bd_leaders": "BD Leader",
"bd_leader_count": "{count} BD Leader",
"refresh": "Segarkan",
@ -65,6 +71,9 @@
"user_is_admin": "Pengguna admin tidak dapat ditambahkan sebagai BD Leader",
"user_already_bd_leader": "Pengguna ini sudah menjadi BD Leader",
"user_already_bound_bd_leader": "Pengguna ini sudah terhubung ke BD Leader",
"user_identity_locked": "This user has Host, Agency, BD, BD Leader, or Coin Seller identity and cannot be moved",
"country_not_found": "Country not found",
"country_required": "Select country",
"prop_not_giftable": "Properti ini tidak tersedia untuk hadiah manajer",
"vip_not_giftable": "Properti noble ini tidak dapat dikirim",
"manager_feature_disabled": "Fitur ini dinonaktifkan oleh admin",

View File

@ -11,6 +11,12 @@
"search": "Ara",
"searching": "Aranıyor...",
"target_user": "Hedef Kullanıcı",
"current_country": "Current country",
"change_country": "Change Country",
"select_country": "Select country",
"changing_country": "Changing...",
"country_changed": "Country changed",
"no_countries": "No countries available",
"bd_leaders": "BD Liderleri",
"bd_leader_count": "{count} BD Lideri",
"refresh": "Yenile",
@ -65,6 +71,9 @@
"user_is_admin": "Admin kullanıcılar BD lideri eklenemez",
"user_already_bd_leader": "Bu kullanıcı zaten BD lideri",
"user_already_bound_bd_leader": "Bu kullanıcı zaten bir BD liderine bağlı",
"user_identity_locked": "This user has Host, Agency, BD, BD Leader, or Coin Seller identity and cannot be moved",
"country_not_found": "Country not found",
"country_required": "Select country",
"prop_not_giftable": "Bu eşya yönetici hediyesi için açık değil",
"vip_not_giftable": "Bu noble eşyası gönderilemez",
"manager_feature_disabled": "Bu özellik admin tarafından kapatıldı",

View File

@ -25,6 +25,12 @@
search: "Search",
searching: "Searching...",
target_user: "Target User",
current_country: "Current country",
change_country: "Change Country",
select_country: "Select country",
changing_country: "Changing...",
country_changed: "Country changed",
no_countries: "No countries available",
bd_leaders: "BD Leaders",
bd_leader_count: "{count} BD Leaders",
refresh: "Refresh",
@ -79,6 +85,9 @@
user_is_admin: "Admin users cannot be added as BD Leaders",
user_already_bd_leader: "This user is already a BD Leader",
user_already_bound_bd_leader: "This user is already bound to a BD Leader",
user_identity_locked: "This user has Host, Agency, BD, BD Leader, or Coin Seller identity and cannot be moved",
country_not_found: "Country not found",
country_required: "Select country",
prop_not_giftable: "This prop is not available for manager gifting",
vip_not_giftable: "This noble prop cannot be gifted",
manager_feature_disabled: "This feature is disabled by admin",
@ -98,6 +107,12 @@
search: "بحث",
searching: "جار البحث...",
target_user: "المستخدم المستهدف",
current_country: "Current country",
change_country: "Change Country",
select_country: "Select country",
changing_country: "Changing...",
country_changed: "Country changed",
no_countries: "No countries available",
bd_leaders: "قادة BD",
bd_leader_count: "{count} قادة BD",
refresh: "تحديث",
@ -152,6 +167,9 @@
user_is_admin: "لا يمكن إضافة مستخدم إداري كقائد BD",
user_already_bd_leader: "هذا المستخدم قائد BD بالفعل",
user_already_bound_bd_leader: "هذا المستخدم مرتبط بالفعل بقائد BD",
user_identity_locked: "This user has Host, Agency, BD, BD Leader, or Coin Seller identity and cannot be moved",
country_not_found: "Country not found",
country_required: "Select country",
prop_not_giftable: "هذا العنصر غير متاح كهديّة من المدير",
vip_not_giftable: "لا يمكن إهداء عنصر VIP هذا",
manager_feature_disabled: "تم تعطيل هذه الميزة من الإدارة",
@ -171,6 +189,12 @@
search: "Ara",
searching: "Aranıyor...",
target_user: "Hedef Kullanıcı",
current_country: "Current country",
change_country: "Change Country",
select_country: "Select country",
changing_country: "Changing...",
country_changed: "Country changed",
no_countries: "No countries available",
bd_leaders: "BD Liderleri",
bd_leader_count: "{count} BD Lideri",
refresh: "Yenile",
@ -225,6 +249,9 @@
user_is_admin: "Admin kullanıcılar BD lideri eklenemez",
user_already_bd_leader: "Bu kullanıcı zaten BD lideri",
user_already_bound_bd_leader: "Bu kullanıcı zaten bir BD liderine bağlı",
user_identity_locked: "This user has Host, Agency, BD, BD Leader, or Coin Seller identity and cannot be moved",
country_not_found: "Country not found",
country_required: "Select country",
prop_not_giftable: "Bu eşya yönetici hediyesi için açık değil",
vip_not_giftable: "Bu noble eşyası gönderilemez",
manager_feature_disabled: "Bu özellik admin tarafından kapatıldı",
@ -244,6 +271,12 @@
search: "Cari",
searching: "Mencari...",
target_user: "Pengguna Target",
current_country: "Current country",
change_country: "Change Country",
select_country: "Select country",
changing_country: "Changing...",
country_changed: "Country changed",
no_countries: "No countries available",
bd_leaders: "BD Leader",
bd_leader_count: "{count} BD Leader",
refresh: "Segarkan",
@ -298,6 +331,9 @@
user_is_admin: "Pengguna admin tidak dapat ditambahkan sebagai BD Leader",
user_already_bd_leader: "Pengguna ini sudah menjadi BD Leader",
user_already_bound_bd_leader: "Pengguna ini sudah terhubung ke BD Leader",
user_identity_locked: "This user has Host, Agency, BD, BD Leader, or Coin Seller identity and cannot be moved",
country_not_found: "Country not found",
country_required: "Select country",
prop_not_giftable: "Properti ini tidak tersedia untuk hadiah manajer",
vip_not_giftable: "Properti noble ini tidak dapat dikirim",
manager_feature_disabled: "Fitur ini dinonaktifkan oleh admin",
@ -313,6 +349,10 @@
propsType: defaultPropsType,
props: [],
selectedPropsId: "",
countries: [],
selectedCountryId: "",
countriesLoading: false,
changingCountry: false,
bdLeaders: [],
bdLeadersLoading: false,
bdLeadersError: "",
@ -370,7 +410,7 @@
if (window.location.protocol === "file:") return fallbackMessages[normalizedLang];
try {
const response = await fetch(`./locales/${normalizedLang}.json?v=20260522-0101`, { cache: "no-store" });
const response = await fetch(`./locales/${normalizedLang}.json?v=20260622-0100`, { cache: "no-store" });
if (response.ok) return await response.json();
} catch (error) {
return fallbackMessages[normalizedLang];
@ -438,6 +478,17 @@
button.setAttribute("aria-expanded", String(open));
}
function setCountryMenuOpen(open) {
const root = $("#countrySelect");
const button = $("#countryButton");
const menu = $("#countryMenu");
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");
@ -451,9 +502,43 @@
});
}
function renderCountrySelect() {
const root = $("#countrySelect");
const label = $("#countrySelectedLabel");
const menu = $("#countryMenu");
if (root) root.dataset.value = state.selectedCountryId || "";
if (label) label.textContent = countryLabel(selectedCountry()) || message("select_country");
if (!menu) return;
menu.replaceChildren();
state.countries.forEach((country) => {
const button = document.createElement("button");
button.type = "button";
button.setAttribute("role", "option");
button.dataset.value = String(country.id);
button.textContent = countryLabel(country);
const isSelected = String(country.id) === String(state.selectedCountryId);
button.classList.toggle("is-selected", isSelected);
button.setAttribute("aria-selected", String(isSelected));
button.addEventListener("click", (event) => {
event.stopPropagation();
chooseCountry(country.id);
});
menu.appendChild(button);
});
}
function chooseCountry(countryId) {
state.selectedCountryId = String(countryId || "");
setCountryMenuOpen(false);
renderCountrySelect();
updateActionState();
}
async function choosePropsType(value) {
const nextValue = String(value || defaultPropsType);
setPropsTypeMenuOpen(false);
setCountryMenuOpen(false);
if (nextValue === state.propsType) {
renderPropsTypeSelect();
return;
@ -478,6 +563,7 @@
renderMessages(messages);
renderLanguageControls(messages);
renderPropsTypeSelect();
renderCountrySelect();
renderManager(state.manager);
renderTarget();
renderProps();
@ -576,6 +662,14 @@
return `/app/h5/manager-center/users/search?account=${encodeURIComponent(account)}`;
}
function endpointCountries() {
return "/app/h5/manager-center/countries";
}
function endpointChangeUserCountry() {
return "/app/h5/manager-center/users/country";
}
function endpointProps(type) {
return `/app/h5/manager-center/props?type=${encodeURIComponent(type || "")}`;
}
@ -632,6 +726,7 @@
if (open) {
setLanguageMenuOpen(false);
setPropsTypeMenuOpen(false);
setCountryMenuOpen(false);
}
syncModalState();
}
@ -641,6 +736,7 @@
modal.hidden = true;
});
setPropsTypeMenuOpen(false);
setCountryMenuOpen(false);
syncModalState();
}
@ -657,6 +753,29 @@
updateActionState();
}
async function openCountryModal() {
if (!state.target) {
showToast(message("enter_user"));
return;
}
setStatus($("#countryStatus"), "");
const currentCountryText = $("#currentCountryText");
if (currentCountryText) currentCountryText.textContent = `${message("current_country")}: ${displayCountryText(state.target)}`;
state.selectedCountryId = String(state.target.countryId || matchingCountryId(state.target) || "");
setModalOpen("#countryModal", true);
renderCountrySelect();
updateActionState();
if (!state.countries.length) {
try {
await loadCountries();
} catch (error) {
setStatus($("#countryStatus"), friendlyError(error), "error");
}
}
}
function openBanModal() {
if (!featureEnabled("banUser")) {
showToast(message("ban_user_disabled"));
@ -775,6 +894,32 @@
return `${message("uid_label", "UID")}: ${displayShortID(user)}`;
}
function displayCountryText(user) {
const name = String(user?.countryName || "").trim();
const code = String(user?.countryCode || "").trim();
if (name && code) return `${name} (${code})`;
return name || code || "-";
}
function countryLabel(country) {
if (!country) return "";
const name = String(country.countryName || "").trim();
const code = String(country.countryCode || "").trim();
if (name && code) return `${name} (${code})`;
return name || code || "";
}
function selectedCountry() {
return state.countries.find((country) => String(country.id) === String(state.selectedCountryId)) || null;
}
function matchingCountryId(user) {
const code = String(user?.countryCode || "").trim().toUpperCase();
if (!code) return "";
const country = state.countries.find((item) => String(item.countryCode || "").trim().toUpperCase() === code);
return country?.id || "";
}
function propTypeLabel(type) {
const key = `prop_type_${String(type || "").toLowerCase()}`;
return message(key, type || "-");
@ -833,6 +978,7 @@
$("#targetName").textContent = displayName(target);
$("#targetUid").textContent = userIDText(target);
$("#targetAccountStatus").textContent = target.accountStatus || "-";
$("#targetCountryStatus").textContent = displayCountryText(target);
const canBan = canBanTarget(target);
const banEligibility = $("#banEligibility");
if (banEligibility) {
@ -978,11 +1124,13 @@
function updateActionState() {
const addBDLeaderButton = $("#addBDLeaderButton");
const countryEntryButton = $("#countryEntryButton");
const giftEntryButton = $("#giftEntryButton");
const banEntryButton = $("#banEntryButton");
const unbanEntryButton = $("#unbanEntryButton");
const refreshBDLeaderButton = $("#refreshBDLeaderButton");
const sendButton = $("#sendPropsButton");
const changeCountryButton = $("#changeCountryButton");
const banButton = $("#banButton");
const unbanButton = $("#unbanButton");
if (addBDLeaderButton) {
@ -1003,6 +1151,11 @@
const title = giftEntryButton.querySelector(".action-item-title");
if (title) title.textContent = disabledByFeature ? message("gift_props_disabled") : message("gift_props");
}
if (countryEntryButton) {
countryEntryButton.disabled = !state.target || state.countriesLoading || state.changingCountry;
const title = countryEntryButton.querySelector(".action-item-title");
if (title) title.textContent = message("change_country");
}
if (banEntryButton) {
const disabledByFeature = !featureEnabled("banUser");
banEntryButton.disabled = disabledByFeature || !state.target;
@ -1019,6 +1172,11 @@
}
if (refreshBDLeaderButton) refreshBDLeaderButton.disabled = state.bdLeadersLoading;
if (sendButton) sendButton.disabled = !featureEnabled("giftProps") || !state.target || !state.selectedPropsId;
if (changeCountryButton) {
const currentID = String(state.target?.countryId || matchingCountryId(state.target) || "");
changeCountryButton.disabled = !state.target || state.countriesLoading || state.changingCountry || !state.selectedCountryId || state.selectedCountryId === currentID;
changeCountryButton.textContent = state.changingCountry ? message("changing_country") : message("change_country");
}
if (banButton) banButton.disabled = !featureEnabled("banUser") || !canBanTarget(state.target);
if (unbanButton) unbanButton.disabled = !featureEnabled("unbanUser") || !canUnbanTarget(state.target);
}
@ -1047,6 +1205,22 @@
renderProps();
}
async function loadCountries() {
state.countriesLoading = true;
setStatus($("#countryStatus"), message("loading"));
updateActionState();
try {
const body = await requestGoJSON(endpointCountries());
state.countries = Array.isArray(body?.records) ? body.records : [];
if (!state.selectedCountryId) state.selectedCountryId = String(state.target?.countryId || matchingCountryId(state.target) || "");
setStatus($("#countryStatus"), state.countries.length ? "" : message("no_countries"));
renderCountrySelect();
} finally {
state.countriesLoading = false;
updateActionState();
}
}
async function loadBDLeaders() {
state.bdLeadersLoading = true;
state.bdLeadersError = "";
@ -1205,6 +1379,37 @@
}
}
async function changeUserCountry() {
if (!state.target) return;
if (!state.selectedCountryId) {
setStatus($("#countryStatus"), message("country_required"), "error");
return;
}
state.changingCountry = true;
setStatus($("#countryStatus"), message("changing_country"));
updateActionState();
try {
const body = await requestGoJSON(endpointChangeUserCountry(), {
method: "POST",
body: {
userId: displayUserID(state.target),
countryId: state.selectedCountryId
}
});
state.target = body?.user || state.target;
renderTarget();
setModalOpen("#countryModal", false);
setStatus($("#countryStatus"), "");
showToast(message("country_changed"));
} catch (error) {
setStatus($("#countryStatus"), friendlyError(error), "error");
} finally {
state.changingCountry = false;
updateActionState();
}
}
function friendlyError(error) {
const code = error?.code || error?.response?.code || error?.response?.errorCodeName || "";
if (code === "missing_token") return message("missing_token");
@ -1218,6 +1423,9 @@
if (code === "user_is_admin") return message("user_is_admin");
if (code === "user_already_bd_leader") return message("user_already_bd_leader");
if (code === "user_already_bound_bd_leader") return message("user_already_bound_bd_leader");
if (code === "user_identity_locked") return message("user_identity_locked");
if (code === "country_not_found") return message("country_not_found");
if (code === "country_required") return message("country_required");
if (code === "prop_not_giftable") return message("prop_not_giftable");
if (code === "vip_not_giftable") return message("vip_not_giftable");
if (code === "manager_feature_disabled") return message("manager_feature_disabled");
@ -1228,6 +1436,7 @@
$(".back-button")?.addEventListener("click", closePage);
$(".language-button")?.addEventListener("click", (event) => {
event.stopPropagation();
setCountryMenuOpen(false);
setLanguageMenuOpen(Boolean($(".language-menu")?.hidden));
});
document.querySelectorAll(".language-menu [data-lang]").forEach((button) => {
@ -1240,6 +1449,7 @@
document.addEventListener("click", () => {
setLanguageMenuOpen(false);
setPropsTypeMenuOpen(false);
setCountryMenuOpen(false);
});
$("#addBDLeaderButton")?.addEventListener("click", addBDLeader);
$("#refreshBDLeaderButton")?.addEventListener("click", () => {
@ -1248,6 +1458,7 @@
});
});
$("#giftEntryButton")?.addEventListener("click", openGiftModal);
$("#countryEntryButton")?.addEventListener("click", openCountryModal);
$("#banEntryButton")?.addEventListener("click", openBanModal);
$("#unbanEntryButton")?.addEventListener("click", openUnbanModal);
document.querySelectorAll("[data-modal-close]").forEach((button) => {
@ -1261,6 +1472,7 @@
$("#propsTypeButton")?.addEventListener("click", (event) => {
event.stopPropagation();
setLanguageMenuOpen(false);
setCountryMenuOpen(false);
setPropsTypeMenuOpen(Boolean($("#propsTypeMenu")?.hidden));
});
document.querySelectorAll("#propsTypeMenu [data-value]").forEach((button) => {
@ -1269,16 +1481,24 @@
await choosePropsType(button.dataset.value);
});
});
$("#countryButton")?.addEventListener("click", (event) => {
event.stopPropagation();
setLanguageMenuOpen(false);
setPropsTypeMenuOpen(false);
setCountryMenuOpen(Boolean($("#countryMenu")?.hidden));
});
document.addEventListener("keydown", (event) => {
if (event.key !== "Escape") return;
setLanguageMenuOpen(false);
setPropsTypeMenuOpen(false);
setCountryMenuOpen(false);
closeModals();
});
$("#searchForm")?.addEventListener("submit", searchUser);
$("#sendPropsButton")?.addEventListener("click", sendProps);
$("#banButton")?.addEventListener("click", banUser);
$("#unbanButton")?.addEventListener("click", unbanUser);
$("#changeCountryButton")?.addEventListener("click", changeUserCountry);
}
async function init() {

View File

@ -538,6 +538,16 @@ textarea:focus {
font-weight: 800;
}
.current-country {
margin: 0 0 10px;
border-radius: 8px;
padding: 9px 10px;
background: #f1f5f9;
color: #475569;
font-size: 13px;
font-weight: 800;
}
.custom-select {
position: relative;
}