472 lines
15 KiB
JavaScript
472 lines
15 KiB
JavaScript
(function () {
|
|
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 state = {
|
|
manager: null,
|
|
target: null,
|
|
propsType: defaultPropsType,
|
|
props: [],
|
|
selectedPropsId: ""
|
|
};
|
|
|
|
const params = readURLParams();
|
|
|
|
function readURLParams() {
|
|
const result = new URLSearchParams(window.location.search);
|
|
const hashQuery = window.location.hash.includes("?") ? window.location.hash.split("?")[1] : "";
|
|
const hashParams = new URLSearchParams(hashQuery);
|
|
hashParams.forEach((value, key) => {
|
|
if (!result.has(key)) result.set(key, value);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
function readRawParam(name) {
|
|
const queries = [window.location.search.replace(/^\?/, "")];
|
|
if (window.location.hash.includes("?")) queries.push(window.location.hash.split("?")[1]);
|
|
|
|
for (const query of queries) {
|
|
for (const part of query.split("&")) {
|
|
if (!part) continue;
|
|
const equalIndex = part.indexOf("=");
|
|
const rawKey = equalIndex >= 0 ? part.slice(0, equalIndex) : part;
|
|
const rawValue = equalIndex >= 0 ? part.slice(equalIndex + 1) : "";
|
|
let decodedKey = "";
|
|
try {
|
|
decodedKey = decodeURIComponent(rawKey);
|
|
} catch (error) {
|
|
continue;
|
|
}
|
|
if (decodedKey !== name) continue;
|
|
try {
|
|
return decodeURIComponent(rawValue.replace(/\+/g, "%2B"));
|
|
} catch (error) {
|
|
return rawValue;
|
|
}
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function $(selector) {
|
|
return document.querySelector(selector);
|
|
}
|
|
|
|
function apiBaseURL() {
|
|
const override = window.HYAPP_API_BASE_URL || params.get("apiBase");
|
|
if (override) return String(override).replace(/\/+$/, "");
|
|
return API_BASE_URL.replace(/\/+$/, "");
|
|
}
|
|
|
|
function isLocalDocumentsFile() {
|
|
if (window.location.protocol !== "file:") return false;
|
|
let pathname = window.location.pathname || "";
|
|
try {
|
|
pathname = decodeURIComponent(pathname);
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
return pathname.startsWith("/Users/hy/Documents/");
|
|
}
|
|
|
|
function goApiBaseURL() {
|
|
const override = window.HYAPP_GO_API_BASE_URL || params.get("goApiBase") || params.get("goBase");
|
|
if (override) return String(override).replace(/\/+$/, "");
|
|
if (isLocalDocumentsFile()) return `${LOCAL_GO_API_BASE_URL}${GO_API_PATH_PREFIX}`;
|
|
return `${apiBaseURL()}${GO_API_PATH_PREFIX}`;
|
|
}
|
|
|
|
function buildRequestURL(baseURL, path) {
|
|
const base = `${String(baseURL || API_BASE_URL).replace(/\/+$/, "")}/`;
|
|
const target = String(path || "").replace(/^\/+/, "");
|
|
return new URL(target, base).toString();
|
|
}
|
|
|
|
function normalizeAuthorization(token) {
|
|
const value = String(token || "").trim();
|
|
if (!value) return "";
|
|
return /^bearer\s+/i.test(value) ? value : `Bearer ${value}`;
|
|
}
|
|
|
|
function authHeadersFromToken(token) {
|
|
const sysOrigin = params.get("sysOrigin") || params.get("origin") || "ATYOU";
|
|
return {
|
|
Authorization: normalizeAuthorization(token),
|
|
"req-lang": params.get("lang") || "zh-CN",
|
|
"req-client": "H5",
|
|
"req-version": "V2",
|
|
"req-zone": Intl.DateTimeFormat().resolvedOptions().timeZone || "Asia/Shanghai",
|
|
"req-app-intel": "version=1.0.0;build=1;model=H5;sysVersion=Web;channel=Web",
|
|
"req-sys-origin": `origin=${sysOrigin};originChild=${sysOrigin}`
|
|
};
|
|
}
|
|
|
|
async function requestGoJSON(path, options = {}) {
|
|
const token = readRawParam("token");
|
|
if (!token) {
|
|
const error = new Error("缺少登录信息");
|
|
error.code = "missing_token";
|
|
throw error;
|
|
}
|
|
|
|
const headers = authHeadersFromToken(token);
|
|
const request = {
|
|
method: options.method || "GET",
|
|
cache: "no-store",
|
|
headers
|
|
};
|
|
if (options.body !== undefined) {
|
|
headers["Content-Type"] = "application/json";
|
|
request.body = JSON.stringify(options.body);
|
|
}
|
|
|
|
const response = await fetch(buildRequestURL(goApiBaseURL(), path), request);
|
|
const data = await response.json().catch(() => ({}));
|
|
if (!response.ok || data.status === false) {
|
|
const error = new Error(data.errorMsg || data.message || `Request failed: ${response.status}`);
|
|
error.code = data.code || data.errorCodeName || "";
|
|
error.response = data;
|
|
throw error;
|
|
}
|
|
return data.body ?? data.data ?? null;
|
|
}
|
|
|
|
function endpointProfile() {
|
|
return "/app/h5/manager-center/profile";
|
|
}
|
|
|
|
function endpointSearchUser(account) {
|
|
return `/app/h5/manager-center/users/search?account=${encodeURIComponent(account)}`;
|
|
}
|
|
|
|
function endpointProps(type) {
|
|
return `/app/h5/manager-center/props?type=${encodeURIComponent(type || "")}`;
|
|
}
|
|
|
|
function endpointSendProps() {
|
|
return "/app/h5/manager-center/props/send";
|
|
}
|
|
|
|
function endpointBanUser() {
|
|
return "/app/h5/manager-center/users/ban";
|
|
}
|
|
|
|
function setLoading(isLoading) {
|
|
const root = $(".manager-center");
|
|
const mask = $("#loadingMask");
|
|
if (root) root.dataset.loading = isLoading ? "true" : "false";
|
|
if (mask) mask.hidden = !isLoading;
|
|
}
|
|
|
|
function setStatus(node, text, type = "") {
|
|
if (!node) return;
|
|
node.textContent = text || "";
|
|
node.hidden = !text;
|
|
node.classList.toggle("is-error", type === "error");
|
|
}
|
|
|
|
function showToast(text) {
|
|
const toast = $("#toast");
|
|
if (!toast) return;
|
|
toast.textContent = text || "";
|
|
toast.hidden = !text;
|
|
window.clearTimeout(showToast.timer);
|
|
showToast.timer = window.setTimeout(() => {
|
|
toast.hidden = true;
|
|
}, 2300);
|
|
}
|
|
|
|
function closePage() {
|
|
try {
|
|
if (window.app && typeof window.app.closePage === "function") {
|
|
window.app.closePage();
|
|
return;
|
|
}
|
|
if (window.FlutterPageControl && typeof window.FlutterPageControl.postMessage === "function") {
|
|
window.FlutterPageControl.postMessage("close_page");
|
|
return;
|
|
}
|
|
if (window.history.length > 1) window.history.back();
|
|
} catch (error) {
|
|
console.error("close manager center failed:", error);
|
|
if (window.history.length > 1) window.history.back();
|
|
}
|
|
}
|
|
|
|
function firstLetter(name, fallback) {
|
|
const value = String(name || fallback || "U").trim();
|
|
return value ? value.slice(0, 1).toUpperCase() : "U";
|
|
}
|
|
|
|
function renderAvatar(imgSelector, fallbackSelector, url, name, fallback) {
|
|
const img = $(imgSelector);
|
|
const fallbackNode = $(fallbackSelector);
|
|
if (!img || !fallbackNode) return;
|
|
const src = String(url || "").trim();
|
|
fallbackNode.textContent = firstLetter(name, fallback);
|
|
if (src) {
|
|
img.src = src;
|
|
img.hidden = false;
|
|
fallbackNode.hidden = true;
|
|
} else {
|
|
img.removeAttribute("src");
|
|
img.hidden = true;
|
|
fallbackNode.hidden = false;
|
|
}
|
|
}
|
|
|
|
function displayName(user) {
|
|
return user?.userNickname || user?.nickname || user?.name || "-";
|
|
}
|
|
|
|
function displayUserID(user) {
|
|
return String(user?.userId || user?.id || "-");
|
|
}
|
|
|
|
function displayAccount(user) {
|
|
return user?.account ? ` (${user.account})` : "";
|
|
}
|
|
|
|
function canBanTarget(target) {
|
|
return Boolean(target) && target.canBan !== false && !target.hasRecharge;
|
|
}
|
|
|
|
function renderManager(manager) {
|
|
$("#managerName").textContent = displayName(manager);
|
|
$("#managerUid").textContent = `UID: ${displayUserID(manager)}${displayAccount(manager)}`;
|
|
renderAvatar("#managerAvatar", "#managerAvatarFallback", manager?.userAvatar, displayName(manager), "M");
|
|
}
|
|
|
|
function renderTarget() {
|
|
const card = $("#targetCard");
|
|
const banCard = $("#banCard");
|
|
if (!state.target) {
|
|
if (card) card.hidden = true;
|
|
if (banCard) banCard.hidden = true;
|
|
updateActionState();
|
|
return;
|
|
}
|
|
|
|
const target = state.target;
|
|
card.hidden = false;
|
|
banCard.hidden = false;
|
|
$("#targetName").textContent = displayName(target);
|
|
$("#targetUid").textContent = `UID: ${displayUserID(target)}${displayAccount(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.classList.toggle("is-danger", !canBan);
|
|
renderAvatar("#targetAvatar", "#targetAvatarFallback", target.userAvatar, displayName(target), "U");
|
|
updateActionState();
|
|
}
|
|
|
|
function renderProps() {
|
|
const list = $("#propsList");
|
|
if (!list) return;
|
|
list.replaceChildren();
|
|
if (!state.props.length) {
|
|
setStatus($("#propsStatus"), "没有可赠送道具");
|
|
updateActionState();
|
|
return;
|
|
}
|
|
setStatus($("#propsStatus"), "");
|
|
|
|
state.props.forEach((item) => {
|
|
const button = document.createElement("button");
|
|
button.type = "button";
|
|
button.className = "prop-option";
|
|
button.dataset.propsId = String(item.id);
|
|
if (String(item.id) === String(state.selectedPropsId)) {
|
|
button.classList.add("is-selected");
|
|
}
|
|
|
|
const cover = document.createElement("span");
|
|
cover.className = "prop-cover";
|
|
if (item.cover || item.sourceUrl) {
|
|
const img = document.createElement("img");
|
|
img.alt = "";
|
|
img.src = item.cover || item.sourceUrl;
|
|
cover.appendChild(img);
|
|
} else {
|
|
const fallback = document.createElement("span");
|
|
fallback.textContent = firstLetter(item.name, "G");
|
|
cover.appendChild(fallback);
|
|
}
|
|
|
|
const copy = document.createElement("span");
|
|
copy.className = "prop-copy";
|
|
const name = document.createElement("span");
|
|
name.className = "prop-name";
|
|
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;
|
|
copy.append(name, meta);
|
|
|
|
const days = document.createElement("span");
|
|
days.className = "prop-days";
|
|
days.textContent = `${item.days || 0}天`;
|
|
|
|
button.append(cover, copy, days);
|
|
button.addEventListener("click", () => {
|
|
state.selectedPropsId = String(item.id);
|
|
renderProps();
|
|
updateActionState();
|
|
});
|
|
list.appendChild(button);
|
|
});
|
|
updateActionState();
|
|
}
|
|
|
|
function updateActionState() {
|
|
const sendButton = $("#sendPropsButton");
|
|
const banButton = $("#banButton");
|
|
if (sendButton) sendButton.disabled = !state.target || !state.selectedPropsId;
|
|
if (banButton) banButton.disabled = !canBanTarget(state.target);
|
|
}
|
|
|
|
async function loadProfile() {
|
|
const body = await requestGoJSON(endpointProfile());
|
|
state.manager = body?.manager || body || {};
|
|
renderManager(state.manager);
|
|
}
|
|
|
|
async function loadProps() {
|
|
setStatus($("#propsStatus"), "加载中...");
|
|
state.props = [];
|
|
state.selectedPropsId = "";
|
|
renderProps();
|
|
|
|
const body = await requestGoJSON(endpointProps(state.propsType));
|
|
state.props = Array.isArray(body?.records) ? body.records : [];
|
|
state.selectedPropsId = state.props.length ? String(state.props[0].id) : "";
|
|
renderProps();
|
|
}
|
|
|
|
async function searchUser(event) {
|
|
event.preventDefault();
|
|
const input = $("#accountInput");
|
|
const account = String(input?.value || "").trim();
|
|
if (!account) {
|
|
setStatus($("#searchStatus"), "请输入用户 ID 或账号", "error");
|
|
return;
|
|
}
|
|
|
|
$("#searchButton").disabled = true;
|
|
setStatus($("#searchStatus"), "搜索中...");
|
|
try {
|
|
const body = await requestGoJSON(endpointSearchUser(account));
|
|
state.target = body?.user || body || null;
|
|
renderTarget();
|
|
setStatus($("#searchStatus"), "");
|
|
} catch (error) {
|
|
state.target = null;
|
|
renderTarget();
|
|
setStatus($("#searchStatus"), friendlyError(error), "error");
|
|
} finally {
|
|
$("#searchButton").disabled = false;
|
|
}
|
|
}
|
|
|
|
async function sendProps() {
|
|
if (!state.target || !state.selectedPropsId) return;
|
|
if (!window.confirm("确认赠送该道具?")) return;
|
|
|
|
const button = $("#sendPropsButton");
|
|
button.disabled = true;
|
|
try {
|
|
await requestGoJSON(endpointSendProps(), {
|
|
method: "POST",
|
|
body: {
|
|
acceptUserId: displayUserID(state.target),
|
|
propsId: state.selectedPropsId
|
|
}
|
|
});
|
|
showToast("赠送成功");
|
|
} catch (error) {
|
|
showToast(friendlyError(error));
|
|
} finally {
|
|
updateActionState();
|
|
}
|
|
}
|
|
|
|
async function banUser() {
|
|
if (!canBanTarget(state.target)) return;
|
|
if (!window.confirm("确认封禁该用户?")) return;
|
|
|
|
const button = $("#banButton");
|
|
button.disabled = true;
|
|
setStatus($("#banStatus"), "提交中...");
|
|
try {
|
|
await requestGoJSON(endpointBanUser(), {
|
|
method: "POST",
|
|
body: {
|
|
userId: displayUserID(state.target),
|
|
reason: $("#banReason").value || ""
|
|
}
|
|
});
|
|
state.target.accountStatus = "ARCHIVE";
|
|
renderTarget();
|
|
setStatus($("#banStatus"), "");
|
|
showToast("封禁成功");
|
|
} catch (error) {
|
|
setStatus($("#banStatus"), friendlyError(error), "error");
|
|
} finally {
|
|
updateActionState();
|
|
}
|
|
}
|
|
|
|
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 || "请求失败";
|
|
}
|
|
|
|
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");
|
|
}
|
|
});
|
|
$("#sendPropsButton")?.addEventListener("click", sendProps);
|
|
$("#banButton")?.addEventListener("click", banUser);
|
|
}
|
|
|
|
async function init() {
|
|
bindEvents();
|
|
$("#propsTypeSelect").value = state.propsType;
|
|
if (!readRawParam("token")) {
|
|
setLoading(false);
|
|
setStatus($("#searchStatus"), "缺少登录信息", "error");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await Promise.all([loadProfile(), loadProps()]);
|
|
} catch (error) {
|
|
showToast(friendlyError(error));
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
init();
|
|
})();
|