1137 lines
39 KiB
JavaScript
1137 lines
39 KiB
JavaScript
(function () {
|
|
const API_BASE_URL = "https://jvapi.haiyihy.com";
|
|
const supportedLanguages = ["en", "ar", "tr", "id"];
|
|
const languageLabels = {
|
|
en: "EN",
|
|
ar: "AR",
|
|
tr: "TR",
|
|
id: "ID"
|
|
};
|
|
|
|
const translations = {
|
|
en: {
|
|
document_title: "Agency Center",
|
|
page_label: "Agency Center",
|
|
back: "Back",
|
|
title: "Agency Center",
|
|
language_button_aria: "Change language",
|
|
uid_prefix: "UID:",
|
|
bd_prefix: "BD:",
|
|
salary: "Salary",
|
|
withdraw_exchange: "Withdraw/Exchange",
|
|
open_withdraw_exchange: "Open withdraw and exchange page",
|
|
host_list: "Host List",
|
|
open_host_list: "Open host list",
|
|
team_bill: "Salary Bills",
|
|
open_team_bill: "Open salary bills",
|
|
streaming_duration: "Work Time (min)",
|
|
target: "Target",
|
|
daily_details: "Daily data",
|
|
gift_points: "Gift points",
|
|
online_minutes: "Online minutes",
|
|
no_daily_data: "No daily data",
|
|
open_daily_data: "Open daily data",
|
|
close: "Close",
|
|
open_messages: "Open messages",
|
|
users_message: "Messages",
|
|
messages_loading: "Loading...",
|
|
no_messages: "No messages",
|
|
unknown_user: "Unknown user",
|
|
agree: "Agree",
|
|
refuse: "Refuse",
|
|
operation_failed: "Operation failed",
|
|
approved_application: "Approved {name}'s application.",
|
|
rejected_application: "Rejected {name}'s application.",
|
|
platform_policy: "Agency Policy",
|
|
how_to_invite_member: "How to invite host",
|
|
open_invite_help: "Open invite host guide",
|
|
invite_download_app: "Invite users to download APP.",
|
|
invited_users_login: "Invited users need to log in to the APP.",
|
|
join_team_steps: "Me > Host Center > Apply to join the team.",
|
|
enter_team_id: "Enter the team ID, click send to join.",
|
|
completed: "Completed",
|
|
in_progress: "In progress",
|
|
out_of_account: "Out of account",
|
|
pending: "Pending",
|
|
not_agency_title: "Agency access unavailable",
|
|
not_agency_text: "This account is not an agency account.",
|
|
missing_token_text: "Open this page from the app or add a token in the URL.",
|
|
apply_now: "Apply",
|
|
missing_token: "Missing token",
|
|
load_failed: "Failed to load data"
|
|
},
|
|
ar: {
|
|
document_title: "مركز الوكالة",
|
|
page_label: "مركز الوكالة",
|
|
back: "رجوع",
|
|
title: "مركز الوكالة",
|
|
language_button_aria: "تغيير اللغة",
|
|
uid_prefix: "UID:",
|
|
bd_prefix: "BD:",
|
|
salary: "الراتب",
|
|
withdraw_exchange: "سحب/استبدال",
|
|
open_withdraw_exchange: "فتح صفحة السحب والاستبدال",
|
|
host_list: "قائمة المضيفين",
|
|
open_host_list: "فتح قائمة المضيفين",
|
|
team_bill: "Salary Bills",
|
|
open_team_bill: "Open salary bills",
|
|
streaming_duration: "وقت العمل (دقيقة)",
|
|
target: "الهدف",
|
|
daily_details: "البيانات اليومية",
|
|
gift_points: "نقاط الهدايا",
|
|
online_minutes: "دقائق الاتصال",
|
|
no_daily_data: "لا توجد بيانات يومية",
|
|
open_daily_data: "فتح البيانات اليومية",
|
|
close: "إغلاق",
|
|
open_messages: "فتح الرسائل",
|
|
users_message: "الرسائل",
|
|
messages_loading: "جار التحميل...",
|
|
no_messages: "لا توجد رسائل",
|
|
unknown_user: "مستخدم غير معروف",
|
|
agree: "موافقة",
|
|
refuse: "رفض",
|
|
operation_failed: "فشلت العملية",
|
|
approved_application: "تمت الموافقة على طلب {name}.",
|
|
rejected_application: "تم رفض طلب {name}.",
|
|
platform_policy: "سياسة الوكالة",
|
|
how_to_invite_member: "كيفية دعوة مضيف",
|
|
open_invite_help: "فتح دليل دعوة مضيف",
|
|
invite_download_app: "ادع المستخدمين لتنزيل التطبيق.",
|
|
invited_users_login: "يجب على المستخدمين المدعوين تسجيل الدخول إلى التطبيق.",
|
|
join_team_steps: "أنا > مركز المضيف > طلب الانضمام إلى الفريق.",
|
|
enter_team_id: "أدخل معرف الفريق، ثم اضغط إرسال للانضمام.",
|
|
completed: "مكتمل",
|
|
in_progress: "قيد التنفيذ",
|
|
out_of_account: "خارج الحساب",
|
|
pending: "قيد الانتظار",
|
|
not_agency_title: "الوصول للوكالة غير متاح",
|
|
not_agency_text: "هذا الحساب ليس حساب وكالة.",
|
|
missing_token_text: "افتح هذه الصفحة من التطبيق أو أضف token في الرابط.",
|
|
apply_now: "تقديم",
|
|
missing_token: "رمز الدخول مفقود",
|
|
load_failed: "فشل تحميل البيانات"
|
|
},
|
|
tr: {
|
|
document_title: "Ajans Merkezi",
|
|
page_label: "Ajans Merkezi",
|
|
back: "Geri",
|
|
title: "Ajans Merkezi",
|
|
language_button_aria: "Dili degistir",
|
|
uid_prefix: "UID:",
|
|
bd_prefix: "BD:",
|
|
salary: "Maas",
|
|
withdraw_exchange: "Cek/Degistir",
|
|
open_withdraw_exchange: "Cekme ve degisim sayfasini ac",
|
|
host_list: "Host Listesi",
|
|
open_host_list: "Host listesini ac",
|
|
team_bill: "Salary Bills",
|
|
open_team_bill: "Open salary bills",
|
|
streaming_duration: "Calisma Suresi (dk)",
|
|
target: "Hedef",
|
|
daily_details: "Gunluk veri",
|
|
gift_points: "Hediye puani",
|
|
online_minutes: "Online dakika",
|
|
no_daily_data: "Gunluk veri yok",
|
|
open_daily_data: "Gunluk veriyi ac",
|
|
close: "Kapat",
|
|
open_messages: "Mesajlari ac",
|
|
users_message: "Mesajlar",
|
|
messages_loading: "Yukleniyor...",
|
|
no_messages: "Mesaj yok",
|
|
unknown_user: "Bilinmeyen kullanici",
|
|
agree: "Onayla",
|
|
refuse: "Reddet",
|
|
operation_failed: "Islem basarisiz",
|
|
approved_application: "{name} basvurusu onaylandi.",
|
|
rejected_application: "{name} basvurusu reddedildi.",
|
|
platform_policy: "Ajans Politikasi",
|
|
how_to_invite_member: "Host nasil davet edilir",
|
|
open_invite_help: "Host davet rehberini ac",
|
|
invite_download_app: "Kullanicilari APP indirmeye davet et.",
|
|
invited_users_login: "Davet edilen kullanicilarin APP'e giris yapmasi gerekiyor.",
|
|
join_team_steps: "Ben > Host Merkezi > Takima katilmak icin basvur.",
|
|
enter_team_id: "Takim ID'sini girin, katilmak icin gonder'e tiklayin.",
|
|
completed: "Tamamlandi",
|
|
in_progress: "Devam ediyor",
|
|
out_of_account: "Hesap disi",
|
|
pending: "Beklemede",
|
|
not_agency_title: "Ajans erisimi yok",
|
|
not_agency_text: "Bu hesap ajans hesabi degil.",
|
|
missing_token_text: "Bu sayfayi uygulamadan acin veya URL'ye token ekleyin.",
|
|
apply_now: "Basvur",
|
|
missing_token: "Token eksik",
|
|
load_failed: "Veri yuklenemedi"
|
|
},
|
|
id: {
|
|
document_title: "Pusat Agensi",
|
|
page_label: "Pusat Agensi",
|
|
back: "Kembali",
|
|
title: "Pusat Agensi",
|
|
language_button_aria: "Ubah bahasa",
|
|
uid_prefix: "UID:",
|
|
bd_prefix: "BD:",
|
|
salary: "Gaji",
|
|
withdraw_exchange: "Tarik/Tukar",
|
|
open_withdraw_exchange: "Buka halaman tarik dan tukar",
|
|
host_list: "Daftar Host",
|
|
open_host_list: "Buka daftar host",
|
|
team_bill: "Salary Bills",
|
|
open_team_bill: "Open salary bills",
|
|
streaming_duration: "Waktu Kerja (mnt)",
|
|
target: "Target",
|
|
daily_details: "Data harian",
|
|
gift_points: "Poin hadiah",
|
|
online_minutes: "Menit online",
|
|
no_daily_data: "Tidak ada data harian",
|
|
open_daily_data: "Buka data harian",
|
|
close: "Tutup",
|
|
open_messages: "Buka pesan",
|
|
users_message: "Pesan",
|
|
messages_loading: "Memuat...",
|
|
no_messages: "Tidak ada pesan",
|
|
unknown_user: "Pengguna tidak dikenal",
|
|
agree: "Setuju",
|
|
refuse: "Tolak",
|
|
operation_failed: "Operasi gagal",
|
|
approved_application: "Pengajuan {name} disetujui.",
|
|
rejected_application: "Pengajuan {name} ditolak.",
|
|
platform_policy: "Kebijakan agensi",
|
|
how_to_invite_member: "Cara mengundang host",
|
|
open_invite_help: "Buka panduan undang host",
|
|
invite_download_app: "Undang pengguna untuk mengunduh APP.",
|
|
invited_users_login: "Pengguna yang diundang harus login ke APP.",
|
|
join_team_steps: "Saya > Host Center > Ajukan bergabung ke tim.",
|
|
enter_team_id: "Masukkan ID tim, lalu ketuk kirim untuk bergabung.",
|
|
completed: "Selesai",
|
|
in_progress: "Berjalan",
|
|
out_of_account: "Di luar akun",
|
|
pending: "Tertunda",
|
|
not_agency_title: "Akses agensi tidak tersedia",
|
|
not_agency_text: "Akun ini bukan akun agensi.",
|
|
missing_token_text: "Buka halaman ini dari aplikasi atau tambahkan token di URL.",
|
|
apply_now: "Ajukan",
|
|
missing_token: "Token tidak ditemukan",
|
|
load_failed: "Gagal memuat data"
|
|
}
|
|
};
|
|
|
|
const params = readURLParams();
|
|
let currentLang = normalizeLanguage(params.get("lang"));
|
|
let currentMessages = translations[currentLang] || translations.en;
|
|
let currentProfile = null;
|
|
let currentTeamInfo = null;
|
|
let currentBdProfile = null;
|
|
let currentIdentity = null;
|
|
let currentHasAgencyAccess = true;
|
|
let currentAccessReason = "";
|
|
let currentStreamingDuration = "0";
|
|
let currentGiftValue = "0";
|
|
let currentDailyTargets = [];
|
|
let currentUnreadMessageCount = 0;
|
|
let currentMessageItems = [];
|
|
let currentMessagesLoading = false;
|
|
let currentTeamMemberCount = null;
|
|
|
|
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 normalizeLanguage(lang) {
|
|
const value = String(lang || "en").toLowerCase();
|
|
return supportedLanguages.includes(value) ? value : "en";
|
|
}
|
|
|
|
function t(key, fallback) {
|
|
return currentMessages[key] || translations.en[key] || fallback || key;
|
|
}
|
|
|
|
function formatMessageTemplate(key, values = {}) {
|
|
return t(key).replace(/\{(\w+)\}/g, (match, name) => values[name] ?? match);
|
|
}
|
|
|
|
function apiBaseURL() {
|
|
const override = window.HYAPP_API_BASE_URL || params.get("apiBase");
|
|
if (override) return String(override).replace(/\/+$/, "");
|
|
return API_BASE_URL.replace(/\/+$/, "");
|
|
}
|
|
|
|
function normalizeAuthorization(token) {
|
|
const value = String(token || "").trim();
|
|
if (!value) return "";
|
|
return /^bearer\s+/i.test(value) ? value : `Bearer ${value}`;
|
|
}
|
|
|
|
function authHeadersFromToken(token) {
|
|
return {
|
|
Authorization: normalizeAuthorization(token),
|
|
"req-lang": currentLang,
|
|
"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=ATYOU;originChild=ATYOU"
|
|
};
|
|
}
|
|
|
|
async function requestJSON(path, options = {}) {
|
|
const token = readRawParam("token");
|
|
if (!token) throw new Error(t("missing_token", "Missing token"));
|
|
|
|
const headers = authHeadersFromToken(token);
|
|
const method = options.method || "GET";
|
|
const hasBody = options.body !== undefined;
|
|
if (hasBody) headers["Content-Type"] = "application/json";
|
|
|
|
const response = await fetch(new URL(path, `${apiBaseURL()}/`).toString(), {
|
|
method,
|
|
cache: "no-store",
|
|
headers,
|
|
body: hasBody ? JSON.stringify(options.body) : undefined
|
|
});
|
|
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.response = data;
|
|
throw error;
|
|
}
|
|
return data.body ?? data.data ?? null;
|
|
}
|
|
|
|
function isTruthyIdentityValue(value) {
|
|
if (typeof value === "boolean") return value;
|
|
if (typeof value === "number") return value === 1;
|
|
if (typeof value === "string") return ["true", "1", "yes"].includes(value.toLowerCase());
|
|
return false;
|
|
}
|
|
|
|
function hasAgencyAccess(identity) {
|
|
if (!identity) return false;
|
|
return isTruthyIdentityValue(identity.agent) ||
|
|
isTruthyIdentityValue(identity.freightAgent) ||
|
|
isTruthyIdentityValue(identity.yumi_manager) ||
|
|
isTruthyIdentityValue(identity.yumiManager) ||
|
|
isTruthyIdentityValue(identity.admin);
|
|
}
|
|
|
|
function setLoading(isLoading) {
|
|
const root = document.querySelector(".agency-center");
|
|
const loadingMask = document.querySelector("#loadingMask");
|
|
if (root) root.dataset.loading = isLoading ? "true" : "false";
|
|
if (loadingMask) {
|
|
loadingMask.hidden = !isLoading;
|
|
loadingMask.setAttribute("aria-busy", String(isLoading));
|
|
}
|
|
}
|
|
|
|
function setAgencyView(hasAccess, reason) {
|
|
currentHasAgencyAccess = hasAccess;
|
|
currentAccessReason = reason || "";
|
|
|
|
const root = document.querySelector(".agency-center");
|
|
if (root) root.dataset.view = hasAccess ? "agency" : "no-access";
|
|
|
|
document.querySelectorAll(".agent-only").forEach((node) => {
|
|
node.hidden = !hasAccess;
|
|
});
|
|
|
|
const accessCard = document.querySelector("#accessCard");
|
|
if (accessCard) accessCard.hidden = hasAccess;
|
|
|
|
const accessText = document.querySelector("#accessText");
|
|
if (accessText && reason === "missing-token") accessText.textContent = t("missing_token_text");
|
|
if (accessText && reason !== "missing-token") accessText.textContent = t("not_agency_text");
|
|
}
|
|
|
|
function formatMoney(value) {
|
|
const number = Number(value);
|
|
return Number.isFinite(number) ? number.toFixed(2) : "0.00";
|
|
}
|
|
|
|
function pickCurrentSalary(body) {
|
|
if (typeof body === "number") return body;
|
|
if (body && typeof body === "object") {
|
|
return body.currentSalary ?? body.availableBalance ?? body.balance ?? 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
function pickProfileName(profile) {
|
|
return profile?.userNickname || profile?.name || "Agency";
|
|
}
|
|
|
|
function pickProfileAccount(profile) {
|
|
return profile?.ownSpecialId?.account || profile?.account || profile?.id || "-";
|
|
}
|
|
|
|
function pickTeamInfo(body) {
|
|
return body?.teamProfile || body || null;
|
|
}
|
|
|
|
function pickTeamId(teamInfo) {
|
|
return teamInfo?.id || teamInfo?.teamId || teamInfo?.teamProfile?.id || "";
|
|
}
|
|
|
|
function pickBdLabel(profile) {
|
|
if (!profile) return "-";
|
|
const name = profile.userNickname || profile.name || "BD";
|
|
const account = profile.ownSpecialId?.account || profile.account || profile.id || "";
|
|
if (!account || String(account) === String(name)) return name;
|
|
return `${name} (${account})`;
|
|
}
|
|
|
|
function renderProfile(profile = currentProfile) {
|
|
currentProfile = profile;
|
|
const displayName = pickProfileName(profile);
|
|
const displayAccount = pickProfileAccount(profile);
|
|
const nameNode = document.querySelector("#profileName");
|
|
const uidNode = document.querySelector("#profileUid");
|
|
const avatarImage = document.querySelector("#profileAvatar");
|
|
const avatarFallback = document.querySelector("#profileAvatarFallback");
|
|
|
|
if (nameNode) nameNode.textContent = displayName;
|
|
if (uidNode) uidNode.textContent = `${t("uid_prefix")} ${displayAccount}`.trim();
|
|
|
|
if (avatarFallback) {
|
|
avatarFallback.textContent = displayName.slice(0, 1).toUpperCase() || "A";
|
|
avatarFallback.hidden = false;
|
|
}
|
|
|
|
if (!avatarImage) return;
|
|
const avatar = profile?.userAvatar ? String(profile.userAvatar) : "";
|
|
avatarImage.alt = displayName;
|
|
if (!avatar) {
|
|
avatarImage.hidden = true;
|
|
avatarImage.removeAttribute("src");
|
|
return;
|
|
}
|
|
|
|
avatarImage.hidden = false;
|
|
if (avatarFallback) avatarFallback.hidden = true;
|
|
avatarImage.onerror = () => {
|
|
avatarImage.hidden = true;
|
|
avatarImage.removeAttribute("src");
|
|
if (avatarFallback) avatarFallback.hidden = false;
|
|
};
|
|
avatarImage.src = avatar;
|
|
}
|
|
|
|
function renderTeamInfo(teamInfo = currentTeamInfo) {
|
|
currentTeamInfo = teamInfo;
|
|
syncLinks();
|
|
}
|
|
|
|
function renderTeamMemberCount(count = currentTeamMemberCount) {
|
|
currentTeamMemberCount = count;
|
|
const countNode = document.querySelector("#teamMemberCount");
|
|
if (!countNode) return;
|
|
|
|
const number = Number(count);
|
|
if (Number.isFinite(number)) {
|
|
countNode.textContent = String(Math.max(0, Math.trunc(number)));
|
|
return;
|
|
}
|
|
|
|
countNode.textContent = "-";
|
|
}
|
|
|
|
function renderMessageUnread(count = currentUnreadMessageCount) {
|
|
currentUnreadMessageCount = Number.isFinite(Number(count)) ? Math.max(0, Number(count)) : 0;
|
|
const dot = document.querySelector("#messageUnreadDot");
|
|
const messageButton = document.querySelector(".profile-message-button");
|
|
if (dot) dot.hidden = currentUnreadMessageCount <= 0;
|
|
if (messageButton) messageButton.dataset.unread = currentUnreadMessageCount > 0 ? "true" : "false";
|
|
}
|
|
|
|
function pickMessageRecords(body) {
|
|
if (Array.isArray(body)) return body;
|
|
if (Array.isArray(body?.records)) return body.records;
|
|
if (Array.isArray(body?.list)) return body.list;
|
|
return [];
|
|
}
|
|
|
|
function normalizeMessageRecord(record) {
|
|
const profile = record?.createUserProfile || record?.userProfile || record?.user || {};
|
|
const name = profile.userNickname || profile.name || record?.name || record?.userNickname || t("unknown_user");
|
|
return {
|
|
id: record?.id,
|
|
reason: record?.reason || "",
|
|
teamId: record?.teamId || "",
|
|
status: record?.status || "WAIT",
|
|
createTime: record?.createTime || "",
|
|
userId: profile.id || record?.userId || record?.createUserId || "",
|
|
account: profile.ownSpecialId?.account || profile.account || record?.account || record?.userAccount || "",
|
|
name,
|
|
avatar: profile.userAvatar || record?.avatar || record?.userAvatar || ""
|
|
};
|
|
}
|
|
|
|
function createMessageAvatar(message) {
|
|
const avatar = document.createElement("div");
|
|
avatar.className = "message-avatar";
|
|
|
|
const fallback = document.createElement("span");
|
|
fallback.textContent = (message.name || t("unknown_user")).slice(0, 1).toUpperCase();
|
|
avatar.appendChild(fallback);
|
|
|
|
if (message.avatar) {
|
|
const image = document.createElement("img");
|
|
image.src = message.avatar;
|
|
image.alt = message.name;
|
|
image.addEventListener("error", () => {
|
|
image.remove();
|
|
});
|
|
avatar.appendChild(image);
|
|
}
|
|
|
|
return avatar;
|
|
}
|
|
|
|
function createMessageCard(message) {
|
|
const card = document.createElement("div");
|
|
card.className = "message-card";
|
|
|
|
const user = document.createElement("div");
|
|
user.className = "message-user";
|
|
user.appendChild(createMessageAvatar(message));
|
|
|
|
const copy = document.createElement("div");
|
|
copy.className = "message-copy";
|
|
|
|
const name = document.createElement("div");
|
|
name.className = "message-name";
|
|
name.textContent = message.name || t("unknown_user");
|
|
|
|
const idLine = document.createElement("div");
|
|
idLine.className = "message-id";
|
|
idLine.textContent = `ID : ${message.account || message.userId || "-"}`;
|
|
|
|
copy.appendChild(name);
|
|
copy.appendChild(idLine);
|
|
user.appendChild(copy);
|
|
|
|
const actions = document.createElement("div");
|
|
actions.className = "message-actions";
|
|
|
|
const agree = document.createElement("button");
|
|
agree.type = "button";
|
|
agree.className = "message-action agree-btn";
|
|
agree.textContent = t("agree");
|
|
agree.addEventListener("click", () => processMessageApplication(message, "AGREE"));
|
|
|
|
const refuse = document.createElement("button");
|
|
refuse.type = "button";
|
|
refuse.className = "message-action refuse-btn";
|
|
refuse.textContent = t("refuse");
|
|
refuse.addEventListener("click", () => processMessageApplication(message, "REJECT"));
|
|
|
|
actions.appendChild(agree);
|
|
actions.appendChild(refuse);
|
|
|
|
card.appendChild(user);
|
|
card.appendChild(actions);
|
|
return card;
|
|
}
|
|
|
|
function renderMessageList(items = currentMessageItems) {
|
|
currentMessageItems = Array.isArray(items) ? items : [];
|
|
const list = document.querySelector("#messageList");
|
|
if (!list) return;
|
|
|
|
if (currentMessagesLoading) {
|
|
const loading = document.createElement("div");
|
|
loading.className = "message-empty";
|
|
loading.textContent = t("messages_loading");
|
|
replaceNodeChildren(list, [loading]);
|
|
return;
|
|
}
|
|
|
|
if (!currentMessageItems.length) {
|
|
const empty = document.createElement("div");
|
|
empty.className = "message-empty";
|
|
empty.textContent = t("no_messages");
|
|
replaceNodeChildren(list, [empty]);
|
|
return;
|
|
}
|
|
|
|
replaceNodeChildren(list, currentMessageItems.map(createMessageCard));
|
|
}
|
|
|
|
function renderBdProfile(profile = currentBdProfile) {
|
|
currentBdProfile = profile;
|
|
const bdName = document.querySelector("#bdName");
|
|
if (bdName) bdName.textContent = `${t("bd_prefix")} ${pickBdLabel(profile)}`.trim();
|
|
}
|
|
|
|
function renderSalary(value) {
|
|
const salaryNode = document.querySelector("#currentSalary");
|
|
if (salaryNode) salaryNode.textContent = formatMoney(value);
|
|
}
|
|
|
|
function formatStreamingDuration(value) {
|
|
const numericValue = Number(value);
|
|
if (!Number.isFinite(numericValue)) return "0";
|
|
const safeValue = Math.max(0, numericValue);
|
|
return Number.isInteger(safeValue) ? String(safeValue) : String(Number(safeValue.toFixed(2)));
|
|
}
|
|
|
|
function pickFirstMemberWorkTarget(body) {
|
|
const targets = Array.isArray(body?.targets) ? body.targets : [];
|
|
return targets[0] || body?.target || null;
|
|
}
|
|
|
|
function pickStreamingDuration(body) {
|
|
const workTarget = pickFirstMemberWorkTarget(body);
|
|
return body?.onlineTime ?? body?.target?.onlineTime ?? workTarget?.target?.onlineTime ?? workTarget?.onlineTime ?? 0;
|
|
}
|
|
|
|
function renderStreamingDuration(value) {
|
|
currentStreamingDuration = formatStreamingDuration(value);
|
|
const durationNode = document.querySelector("#streamingDuration");
|
|
if (durationNode) durationNode.textContent = currentStreamingDuration;
|
|
}
|
|
|
|
function formatCountValue(value) {
|
|
if (value === null || value === undefined || value === "") return "-";
|
|
const number = Number(value);
|
|
if (Number.isFinite(number) && Number.isInteger(number)) return String(number);
|
|
if (Number.isFinite(number)) return String(Number(number.toFixed(2)));
|
|
return String(value);
|
|
}
|
|
|
|
function renderCurrentGiftValue(value) {
|
|
currentGiftValue = formatCountValue(value === null || value === undefined || value === "" ? 0 : value);
|
|
const giftNode = document.querySelector("#currentGiftValue");
|
|
const dailyGiftNode = document.querySelector("#dailyCurrentGift");
|
|
if (giftNode) giftNode.textContent = currentGiftValue;
|
|
if (dailyGiftNode) dailyGiftNode.textContent = currentGiftValue;
|
|
}
|
|
|
|
function pickGiftValue(workTarget) {
|
|
const target = workTarget?.target || workTarget || {};
|
|
return target.acceptGiftValue ?? target.giftValue ?? target.giftScore ?? target.currentGiftValue ?? null;
|
|
}
|
|
|
|
function pickDailyTargets(workTarget) {
|
|
const target = workTarget?.target || workTarget || {};
|
|
return Array.isArray(target.dailyTargets) ? target.dailyTargets : [];
|
|
}
|
|
|
|
function formatDateNumber(value) {
|
|
const text = String(value || "");
|
|
if (/^\d{8}$/.test(text)) {
|
|
return `${text.slice(0, 4)}.${text.slice(4, 6)}.${text.slice(6, 8)}`;
|
|
}
|
|
return text || "-";
|
|
}
|
|
|
|
function createDailyCell(label, value) {
|
|
const cell = document.createElement("div");
|
|
cell.className = "daily-cell";
|
|
|
|
const labelNode = document.createElement("span");
|
|
labelNode.textContent = label;
|
|
|
|
const valueNode = document.createElement("strong");
|
|
valueNode.textContent = value;
|
|
|
|
cell.appendChild(labelNode);
|
|
cell.appendChild(valueNode);
|
|
return cell;
|
|
}
|
|
|
|
function createDailyRow(item) {
|
|
const row = document.createElement("div");
|
|
row.className = "daily-row";
|
|
|
|
const date = document.createElement("div");
|
|
date.className = "daily-date";
|
|
date.textContent = formatDateNumber(item?.dateNumber);
|
|
|
|
const values = document.createElement("div");
|
|
values.className = "daily-values";
|
|
values.appendChild(createDailyCell(t("gift_points"), formatCountValue(item?.acceptGiftValue ?? 0)));
|
|
values.appendChild(createDailyCell(t("online_minutes"), formatCountValue(item?.onlineTime ?? 0)));
|
|
|
|
row.appendChild(date);
|
|
row.appendChild(values);
|
|
return row;
|
|
}
|
|
|
|
function replaceNodeChildren(node, children) {
|
|
while (node.firstChild) node.removeChild(node.firstChild);
|
|
children.forEach((child) => node.appendChild(child));
|
|
}
|
|
|
|
function renderDailyTargets(targets = currentDailyTargets) {
|
|
currentDailyTargets = Array.isArray(targets) ? targets : [];
|
|
const list = document.querySelector("#dailyList");
|
|
if (!list) return;
|
|
|
|
if (!currentDailyTargets.length) {
|
|
const empty = document.createElement("div");
|
|
empty.className = "daily-empty";
|
|
empty.textContent = t("no_daily_data");
|
|
replaceNodeChildren(list, [empty]);
|
|
return;
|
|
}
|
|
|
|
replaceNodeChildren(list, currentDailyTargets.map(createDailyRow));
|
|
}
|
|
|
|
function renderMemberWorkSummary(memberWork) {
|
|
const workTarget = pickFirstMemberWorkTarget(memberWork);
|
|
renderStreamingDuration(pickStreamingDuration(memberWork));
|
|
renderCurrentGiftValue(pickGiftValue(workTarget) ?? 0);
|
|
renderDailyTargets(pickDailyTargets(workTarget));
|
|
}
|
|
|
|
function setDailyModalOpen(open) {
|
|
const modal = document.querySelector("#dailyModal");
|
|
if (!modal) return;
|
|
modal.hidden = !open;
|
|
if (open) {
|
|
const inviteModal = document.querySelector("#inviteModal");
|
|
if (inviteModal) inviteModal.hidden = true;
|
|
const messageModal = document.querySelector("#messageModal");
|
|
if (messageModal) messageModal.hidden = true;
|
|
renderDailyTargets();
|
|
}
|
|
syncModalBodyLock();
|
|
}
|
|
|
|
function setInviteModalOpen(open) {
|
|
const modal = document.querySelector("#inviteModal");
|
|
if (!modal) return;
|
|
modal.hidden = !open;
|
|
if (open) {
|
|
const dailyModal = document.querySelector("#dailyModal");
|
|
if (dailyModal) dailyModal.hidden = true;
|
|
const messageModal = document.querySelector("#messageModal");
|
|
if (messageModal) messageModal.hidden = true;
|
|
}
|
|
syncModalBodyLock();
|
|
}
|
|
|
|
function setMessagesModalOpen(open) {
|
|
const modal = document.querySelector("#messageModal");
|
|
if (!modal) return;
|
|
modal.hidden = !open;
|
|
if (open) {
|
|
const dailyModal = document.querySelector("#dailyModal");
|
|
const inviteModal = document.querySelector("#inviteModal");
|
|
if (dailyModal) dailyModal.hidden = true;
|
|
if (inviteModal) inviteModal.hidden = true;
|
|
renderMessageList();
|
|
}
|
|
syncModalBodyLock();
|
|
}
|
|
|
|
function syncModalBodyLock() {
|
|
const hasOpenModal = Array.from(document.querySelectorAll(".daily-modal, .message-modal")).some((modal) => !modal.hidden);
|
|
document.body.classList.toggle("daily-modal-open", hasOpenModal);
|
|
}
|
|
|
|
function buildPageURL(target, options = {}) {
|
|
const url = new URL(target, window.location.href);
|
|
const nextParams = new URLSearchParams(url.search);
|
|
|
|
params.forEach((value, key) => {
|
|
if (!nextParams.has(key)) nextParams.set(key, value);
|
|
});
|
|
|
|
const token = readRawParam("token");
|
|
if (token) nextParams.set("token", token);
|
|
if (currentLang) nextParams.set("lang", currentLang);
|
|
if (options.teamId) nextParams.set("teamId", String(options.teamId));
|
|
if (options.members !== undefined && options.members !== null && options.members !== "") {
|
|
nextParams.set("members", String(options.members));
|
|
}
|
|
|
|
url.search = nextParams.toString();
|
|
return url.toString();
|
|
}
|
|
|
|
function syncLinks() {
|
|
const teamId = pickTeamId(currentTeamInfo);
|
|
|
|
document.querySelectorAll("[data-route]").forEach((link) => {
|
|
const options = {};
|
|
if (link.dataset.policy === "team" && teamId) options.teamId = teamId;
|
|
if (link.dataset.teamMember === "true") {
|
|
if (teamId) options.teamId = teamId;
|
|
if (currentTeamMemberCount !== null && currentTeamMemberCount !== undefined) {
|
|
options.members = currentTeamMemberCount;
|
|
}
|
|
}
|
|
if (link.dataset.teamBill === "true" && teamId) options.teamId = teamId;
|
|
link.setAttribute("href", buildPageURL(link.dataset.route, options));
|
|
});
|
|
}
|
|
|
|
function showToast(message) {
|
|
const toast = document.querySelector("#toast");
|
|
if (!toast) return;
|
|
toast.textContent = message;
|
|
toast.hidden = false;
|
|
window.clearTimeout(showToast.timer);
|
|
showToast.timer = window.setTimeout(() => {
|
|
toast.hidden = true;
|
|
}, 2200);
|
|
}
|
|
|
|
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 agency center page failed:", error);
|
|
if (window.history.length > 1) window.history.back();
|
|
}
|
|
}
|
|
|
|
function setLanguageMenuOpen(open) {
|
|
const languageButton = document.querySelector(".language-button");
|
|
const languageMenu = document.querySelector(".language-menu");
|
|
if (!languageButton || !languageMenu) return;
|
|
languageMenu.hidden = !open;
|
|
languageButton.setAttribute("aria-expanded", String(open));
|
|
}
|
|
|
|
function renderLanguageControls() {
|
|
const languageButton = document.querySelector(".language-button");
|
|
if (languageButton) {
|
|
languageButton.textContent = languageLabels[currentLang];
|
|
languageButton.setAttribute("aria-label", t("language_button_aria"));
|
|
}
|
|
|
|
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 renderMessages() {
|
|
document.querySelectorAll("[data-i18n]").forEach((node) => {
|
|
const value = currentMessages[node.dataset.i18n];
|
|
if (value) node.textContent = value;
|
|
});
|
|
|
|
document.querySelectorAll("[data-i18n-aria]").forEach((node) => {
|
|
const value = currentMessages[node.dataset.i18nAria];
|
|
if (value) node.setAttribute("aria-label", value);
|
|
});
|
|
}
|
|
|
|
function applyLanguage(lang) {
|
|
currentLang = normalizeLanguage(lang);
|
|
currentMessages = translations[currentLang] || translations.en;
|
|
document.documentElement.lang = currentLang;
|
|
document.documentElement.dir = currentLang === "ar" ? "rtl" : "ltr";
|
|
document.title = t("document_title");
|
|
renderMessages();
|
|
renderLanguageControls();
|
|
renderProfile();
|
|
renderTeamInfo();
|
|
renderBdProfile();
|
|
renderStreamingDuration(currentStreamingDuration);
|
|
renderCurrentGiftValue(currentGiftValue);
|
|
renderDailyTargets();
|
|
renderMessageUnread();
|
|
renderMessageList();
|
|
renderTeamMemberCount();
|
|
syncLinks();
|
|
if (!currentHasAgencyAccess) setAgencyView(false, currentAccessReason);
|
|
}
|
|
|
|
async function fetchIdentity() {
|
|
currentIdentity = await requestJSON("/app/h5/identity");
|
|
return currentIdentity;
|
|
}
|
|
|
|
async function fetchUserProfile() {
|
|
const profile = await requestJSON("/team/member/profile");
|
|
renderProfile(profile);
|
|
return profile;
|
|
}
|
|
|
|
async function fetchTeamInfo() {
|
|
const body = await requestJSON("/team/entry");
|
|
const teamInfo = pickTeamInfo(body);
|
|
renderTeamInfo(teamInfo);
|
|
return teamInfo;
|
|
}
|
|
|
|
async function fetchBdProfile() {
|
|
const profile = await requestJSON("/team/bd-profile");
|
|
renderBdProfile(profile);
|
|
return profile;
|
|
}
|
|
|
|
async function fetchBankBalance() {
|
|
const body = await requestJSON("/wallet/bank/balance");
|
|
renderSalary(pickCurrentSalary(body));
|
|
}
|
|
|
|
function pickTeamMemberCount(body) {
|
|
if (typeof body === "number") return body;
|
|
if (body && typeof body === "object") {
|
|
return body.memberQuantity ?? body.count ?? body.total ?? body.memberCount ?? null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async function fetchTeamMemberCount(teamId) {
|
|
if (!teamId) {
|
|
renderTeamMemberCount(null);
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const query = new URLSearchParams({ id: String(teamId) });
|
|
const body = await requestJSON(`/team/members/count?${query.toString()}`);
|
|
const count = pickTeamMemberCount(body);
|
|
renderTeamMemberCount(count);
|
|
syncLinks();
|
|
return count;
|
|
} catch (error) {
|
|
console.error("Failed to load team member count:", error);
|
|
renderTeamMemberCount(null);
|
|
syncLinks();
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function fetchMemberWork(memberId) {
|
|
if (!memberId) {
|
|
console.warn("Missing memberId for /team/member-work");
|
|
renderMemberWorkSummary(null);
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const url = new URL("/team/member-work", `${apiBaseURL()}/`);
|
|
url.searchParams.set("userId", String(memberId));
|
|
const memberWork = await requestJSON(`${url.pathname}${url.search}`);
|
|
renderMemberWorkSummary(memberWork);
|
|
return memberWork;
|
|
} catch (error) {
|
|
console.error("Failed to load member work:", error);
|
|
renderMemberWorkSummary(null);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function fetchMessageList(teamId) {
|
|
if (!teamId) {
|
|
currentMessageItems = [];
|
|
renderMessageUnread(0);
|
|
renderMessageList();
|
|
return [];
|
|
}
|
|
|
|
currentMessagesLoading = true;
|
|
renderMessageList();
|
|
try {
|
|
const query = new URLSearchParams({ teamId: String(teamId), status: "WAIT" });
|
|
const body = await requestJSON(`/team/user/apply/record?${query.toString()}`);
|
|
currentMessageItems = pickMessageRecords(body).map(normalizeMessageRecord);
|
|
renderMessageUnread(currentMessageItems.length);
|
|
return currentMessageItems;
|
|
} catch (error) {
|
|
console.error("Failed to load messages:", error);
|
|
currentMessageItems = [];
|
|
renderMessageUnread(0);
|
|
return [];
|
|
} finally {
|
|
currentMessagesLoading = false;
|
|
renderMessageList();
|
|
}
|
|
}
|
|
|
|
async function processMessageApplication(message, status) {
|
|
if (!message?.id) return;
|
|
|
|
try {
|
|
await requestJSON("/team/process/user/apply", {
|
|
method: "POST",
|
|
body: {
|
|
id: message.id,
|
|
status
|
|
}
|
|
});
|
|
|
|
currentMessageItems = currentMessageItems.filter((item) => item.id !== message.id);
|
|
renderMessageUnread(currentMessageItems.length);
|
|
renderMessageList();
|
|
|
|
const messageKey = status === "AGREE" ? "approved_application" : "rejected_application";
|
|
showToast(formatMessageTemplate(messageKey, { name: message.name || t("unknown_user") }));
|
|
} catch (error) {
|
|
console.error("Failed to process message application:", error);
|
|
showToast(error.message || t("operation_failed"));
|
|
}
|
|
}
|
|
|
|
function bindEvents() {
|
|
document.querySelector(".back-button")?.addEventListener("click", closePage);
|
|
|
|
document.querySelector(".language-button")?.addEventListener("click", (event) => {
|
|
event.stopPropagation();
|
|
const languageMenu = document.querySelector(".language-menu");
|
|
setLanguageMenuOpen(Boolean(languageMenu?.hidden));
|
|
});
|
|
|
|
document.querySelectorAll(".language-menu [data-lang]").forEach((button) => {
|
|
button.addEventListener("click", (event) => {
|
|
event.stopPropagation();
|
|
setLanguageMenuOpen(false);
|
|
applyLanguage(button.dataset.lang);
|
|
});
|
|
});
|
|
|
|
document.querySelector(".gift-value")?.addEventListener("click", () => {
|
|
setDailyModalOpen(true);
|
|
});
|
|
|
|
document.querySelector(".profile-message-button")?.addEventListener("click", () => {
|
|
setMessagesModalOpen(true);
|
|
fetchMessageList(pickTeamId(currentTeamInfo));
|
|
});
|
|
|
|
document.querySelector(".invite-help-button")?.addEventListener("click", () => {
|
|
setInviteModalOpen(true);
|
|
});
|
|
|
|
document.querySelectorAll("[data-close-daily]").forEach((node) => {
|
|
node.addEventListener("click", () => {
|
|
setDailyModalOpen(false);
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll("[data-close-invite]").forEach((node) => {
|
|
node.addEventListener("click", () => {
|
|
setInviteModalOpen(false);
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll("[data-close-messages]").forEach((node) => {
|
|
node.addEventListener("click", () => {
|
|
setMessagesModalOpen(false);
|
|
});
|
|
});
|
|
|
|
document.addEventListener("click", () => {
|
|
setLanguageMenuOpen(false);
|
|
});
|
|
|
|
document.addEventListener("keydown", (event) => {
|
|
if (event.key === "Escape") {
|
|
setDailyModalOpen(false);
|
|
setInviteModalOpen(false);
|
|
setMessagesModalOpen(false);
|
|
}
|
|
});
|
|
}
|
|
|
|
async function initializePage() {
|
|
setLoading(true);
|
|
applyLanguage(currentLang);
|
|
bindEvents();
|
|
syncLinks();
|
|
|
|
const token = readRawParam("token");
|
|
if (!token) {
|
|
setAgencyView(false, "missing-token");
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const firstWave = [
|
|
fetchIdentity(),
|
|
fetchUserProfile()
|
|
];
|
|
const firstResults = await Promise.allSettled(firstWave);
|
|
firstResults.forEach((result) => {
|
|
if (result.status === "rejected") console.warn("Agency center data request failed:", result.reason);
|
|
});
|
|
|
|
const hasAccess = hasAgencyAccess(currentIdentity);
|
|
setAgencyView(hasAccess);
|
|
|
|
if (hasAccess) {
|
|
const agencyResults = await Promise.allSettled([
|
|
fetchTeamInfo(),
|
|
fetchBdProfile(),
|
|
fetchBankBalance()
|
|
]);
|
|
agencyResults.forEach((result) => {
|
|
if (result.status === "rejected") console.warn("Agency center data request failed:", result.reason);
|
|
});
|
|
|
|
const memberId = currentProfile?.id || currentProfile?.account;
|
|
const teamId = pickTeamId(currentTeamInfo);
|
|
const dependentResults = await Promise.allSettled([
|
|
fetchMemberWork(memberId),
|
|
fetchMessageList(teamId),
|
|
fetchTeamMemberCount(teamId)
|
|
]);
|
|
dependentResults.forEach((result) => {
|
|
if (result.status === "rejected") console.warn("Agency center dependent request failed:", result.reason);
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to initialize agency center:", error);
|
|
showToast(error.message || t("load_failed"));
|
|
setAgencyView(hasAgencyAccess(currentIdentity));
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
initializePage();
|
|
})();
|