2026-06-04 23:47:28 +08:00

859 lines
29 KiB
JavaScript

(function () {
var FIRST_BILL_MONTH = '2026-06';
var BD_SALARY_ASSET = 'BD_SALARY_USD';
var data = {
profile: null,
balance: { available: 0 },
billMonth: currentMonth(),
agencyBill: {
agencyNumber: 0,
totalSalary: 0,
totalRecharge: 0,
memberBillList: [],
},
inviteRecords: [],
};
var state = {
billMonth: normalizeBillMonth(data.billMonth),
inviteQuery: '',
inviteMode: 'records',
inviteStatus: '',
inviteStatusType: '',
searched: [],
inviting: {},
searching: false,
loading: true,
};
function $(id) {
return document.getElementById(id);
}
function api() {
return window.HyAppAPI || {};
}
function centerAPI() {
return api().bdCenter || null;
}
function t(key, fallback) {
return window.HyAppI18n && window.HyAppI18n.t
? window.HyAppI18n.t(key, fallback)
: fallback || key;
}
function avatarData(text, background, foreground) {
var raw =
String(text || 'B')
.trim()
.charAt(0) || 'B';
var letter = raw.replace(/[<>&"']/g, '');
var svg =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96">' +
'<rect width="96" height="96" rx="48" fill="' +
background +
'"/>' +
'<text x="50%" y="54%" text-anchor="middle" dominant-baseline="middle" fill="' +
foreground +
'" font-family="Arial, sans-serif" font-size="38" font-weight="800">' +
letter +
'</text>' +
'</svg>';
return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg);
}
function currentMonth() {
var now = new Date();
return (
String(now.getFullYear()) +
'-' +
String(now.getMonth() + 1).padStart(2, '0')
);
}
function monthDate(value) {
var parts = String(value || currentMonth()).split('-');
var year = Number(parts[0]) || new Date().getFullYear();
var month = Number(parts[1]) || new Date().getMonth() + 1;
return new Date(year, month - 1, 1);
}
function monthValue(date) {
return (
String(date.getFullYear()) +
'-' +
String(date.getMonth() + 1).padStart(2, '0')
);
}
function monthTime(value) {
return monthDate(value).getTime();
}
function normalizeBillMonth(value) {
if (monthTime(value) < monthTime(FIRST_BILL_MONTH)) {
return FIRST_BILL_MONTH;
}
if (monthTime(value) > monthTime(currentMonth())) {
return currentMonth();
}
return monthValue(monthDate(value));
}
function monthLabel(value) {
var date = monthDate(value);
return (
String(date.getFullYear()) +
'年' +
String(date.getMonth() + 1).padStart(2, '0') +
'月'
);
}
function monthOptions() {
var options = [];
var cursor = monthDate(currentMonth());
var first = monthDate(FIRST_BILL_MONTH);
while (cursor.getTime() >= first.getTime()) {
options.push(new Date(cursor.getFullYear(), cursor.getMonth(), 1));
cursor = new Date(cursor.getFullYear(), cursor.getMonth() - 1, 1);
}
return options;
}
function money(value) {
var number = Number(value || 0);
return number.toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
}
function count(value) {
return Number(value || 0).toLocaleString('en-US', {
maximumFractionDigits: 0,
});
}
function initial(text) {
return String(text || '')
.trim()
.charAt(0)
.toUpperCase();
}
function firstValue(item, keys) {
if (!item) return '';
for (var i = 0; i < keys.length; i += 1) {
var value = item[keys[i]];
if (value !== undefined && value !== null && value !== '') {
return value;
}
}
return '';
}
function displayName(user, fallback) {
return (
firstValue(user, ['username', 'name', 'display_user_id']) ||
fallback ||
'-'
);
}
function displayID(user, fallback) {
return (
firstValue(user, ['display_user_id', 'displayUserID']) ||
firstValue(user, ['user_id', 'userId', 'id']) ||
fallback ||
'-'
);
}
function salaryAmount(salary) {
if (!salary) return 0;
if (salary.display_amount !== undefined) return Number(salary.display_amount);
if (salary.displayAmount !== undefined) return Number(salary.displayAmount);
if (salary.available_amount !== undefined) {
return Number(salary.available_amount || 0) / 100;
}
if (salary.availableAmount !== undefined) {
return Number(salary.availableAmount || 0) / 100;
}
return 0;
}
function toast(message) {
if (window.HyAppToast) {
window.HyAppToast.show(message);
return;
}
window.alert(message);
}
function replaceChildren(node, children) {
node.textContent = '';
children.forEach(function (child) {
node.appendChild(child);
});
}
function setSkeleton(element, loading, text) {
if (!element) return;
element.classList.toggle('skeleton-line', Boolean(loading));
if (loading) {
element.textContent = '';
return;
}
element.textContent = text || '';
}
function renderImageAvatar(container, src, name, fallbackClass) {
container.textContent = '';
if (!src) {
var fallback = document.createElement('span');
fallback.className = fallbackClass || 'member-fallback';
fallback.textContent = initial(name) || 'B';
container.appendChild(fallback);
return;
}
var image = document.createElement('img');
image.src = src;
image.alt = '';
image.addEventListener('error', function () {
container.textContent = '';
var fallback = document.createElement('span');
fallback.className = fallbackClass || 'member-fallback';
fallback.textContent = initial(name) || 'B';
container.appendChild(fallback);
});
container.appendChild(image);
}
function normalizeProfile(payload) {
var profile = payload && payload.profile ? payload.profile : payload || {};
var name = displayName(profile, 'BD');
return {
name: name,
uid: displayID(profile),
avatar: firstValue(profile, ['avatar']) || avatarData(initial(name) || 'B', '#dbc8ff', '#7d57c7'),
};
}
function normalizeAgencyItem(item) {
var agency = (item && item.agency) || item || {};
var owner = (item && item.owner) || {};
var name =
firstValue(agency, ['name']) ||
displayName(owner, 'Agency ' + displayID(owner, displayID(agency)));
var account =
firstValue(agency, ['agency_id', 'agencyId']) ||
displayID(owner, displayID(agency));
return {
name: name,
account: account,
avatar:
firstValue(agency, ['avatar']) ||
firstValue(owner, ['avatar']) ||
avatarData(initial(name) || 'A', '#dbc8ff', '#7d57c7'),
teamSalaryAmount: Number(firstValue(item, ['team_salary', 'teamSalary']) || 0),
teamRechargeAmount: Number(firstValue(item, ['team_recharge', 'teamRecharge']) || 0),
teamMemberCount: Number(
firstValue(item, ['host_count', 'hostCount']) ||
firstValue(agency, ['host_count', 'hostCount']) ||
0
),
};
}
function applyOverview(payload) {
var overview = payload && payload.overview ? payload.overview : {};
data.profile = normalizeProfile(payload || {});
data.balance = { available: salaryAmount(payload && payload.salary) };
data.agencyBill.agencyNumber = Number(
firstValue(overview, ['total_agency', 'totalAgency']) || 0
);
}
function applyAgencies(payload) {
var items = payload && (payload.items || payload.agencies) ? payload.items || payload.agencies : [];
var rows = items.map(normalizeAgencyItem);
data.agencyBill.memberBillList = rows;
data.agencyBill.agencyNumber = rows.length;
data.agencyBill.totalSalary = rows.reduce(function (sum, item) {
return sum + Number(item.teamSalaryAmount || 0);
}, 0);
data.agencyBill.totalRecharge = rows.reduce(function (sum, item) {
return sum + Number(item.teamRechargeAmount || 0);
}, 0);
}
function normalizeInviteProfile(payload, fallbackID) {
var user = payload && (payload.user || payload.profile || payload);
if (!user) return null;
var userID = firstValue(user, ['user_id', 'userId', 'id']);
if (!userID) return null;
var account = displayID(user, fallbackID);
var name = displayName(user, 'User ' + account);
return {
userId: String(userID),
id: String(userID),
name: name,
account: account,
avatar: firstValue(user, ['avatar']) || avatarData(initial(name) || 'U', '#dbc8ff', '#7d57c7'),
};
}
function renderProfile() {
var image = $('profileAvatar');
var fallback = $('profileAvatarFallback');
var profile = data.profile || {};
var loading = Boolean(state.loading);
setSkeleton($('profileName'), loading, profile.name || '-');
setSkeleton(
$('profileUID'),
loading,
'UID: ' + (profile.uid || '-')
);
fallback.classList.toggle('avatar-skeleton', loading);
if (loading) {
image.hidden = true;
image.removeAttribute('src');
fallback.hidden = false;
fallback.textContent = '';
return;
}
fallback.textContent = initial(profile.name) || 'B';
image.onload = function () {
image.hidden = false;
fallback.hidden = true;
};
image.onerror = function () {
image.hidden = true;
fallback.hidden = false;
};
image.src = profile.avatar || '';
if (!profile.avatar) {
image.hidden = true;
fallback.hidden = false;
}
}
function renderBalance() {
var loading = Boolean(state.loading);
var coinIcon = document.querySelector('.salary-value .coin-icon');
if (coinIcon) coinIcon.classList.toggle('skeleton-coin', loading);
setSkeleton(
$('availableBalance'),
loading,
money(data.balance && data.balance.available)
);
}
function summaryCell(label, value) {
var cell = document.createElement('div');
var labelNode = document.createElement('span');
var valueNode = document.createElement('strong');
labelNode.textContent = label;
valueNode.textContent = value;
cell.appendChild(labelNode);
cell.appendChild(valueNode);
return cell;
}
function summarySkeletonCell() {
var cell = document.createElement('div');
var labelNode = document.createElement('span');
var valueNode = document.createElement('strong');
labelNode.className = 'skeleton-line';
valueNode.className = 'skeleton-line';
cell.appendChild(labelNode);
cell.appendChild(valueNode);
return cell;
}
function skeletonMemberRow() {
var row = document.createElement('article');
var avatar = document.createElement('div');
var main = document.createElement('div');
var name = document.createElement('div');
var meta = document.createElement('div');
var side = document.createElement('div');
row.className = 'member-row is-skeleton';
avatar.className = 'member-avatar avatar-skeleton';
main.className = 'member-main';
name.className = 'member-name skeleton-line';
meta.className = 'member-meta skeleton-line';
side.className = 'member-side';
for (var i = 0; i < 3; i += 1) {
var line = document.createElement('div');
line.className = 'skeleton-line';
side.appendChild(line);
}
main.appendChild(name);
main.appendChild(meta);
row.appendChild(avatar);
row.appendChild(main);
row.appendChild(side);
return row;
}
function memberRow(item) {
var row = document.createElement('article');
var avatar = document.createElement('div');
var main = document.createElement('div');
var name = document.createElement('div');
var meta = document.createElement('div');
var side = document.createElement('div');
row.className = 'member-row';
avatar.className = 'member-avatar';
main.className = 'member-main';
name.className = 'member-name';
meta.className = 'member-meta';
side.className = 'member-side';
renderImageAvatar(avatar, item.avatar, item.name);
name.textContent = item.name || '-';
meta.textContent = 'UID: ' + (item.account || '-');
[
t('bd_center.team_salary', 'Team salary') +
': $' +
money(item.teamSalaryAmount),
t('bd_center.recharge', 'Recharge') +
': $' +
money(item.teamRechargeAmount),
t('bd_center.host', 'Host') + ': ' + count(item.teamMemberCount),
].forEach(function (line) {
var node = document.createElement('div');
node.textContent = line;
side.appendChild(node);
});
main.appendChild(name);
main.appendChild(meta);
row.appendChild(avatar);
row.appendChild(main);
row.appendChild(side);
return row;
}
function renderTeamList() {
var bill = data.agencyBill;
$('billMonthText').textContent = monthLabel(state.billMonth);
$('billRange').textContent = state.billMonth.replace('-', '.');
if (state.loading) {
replaceChildren($('teamListSummary'), [
summarySkeletonCell(),
summarySkeletonCell(),
]);
replaceChildren($('teamListContent'), [
skeletonMemberRow(),
skeletonMemberRow(),
skeletonMemberRow(),
]);
return;
}
replaceChildren($('teamListSummary'), [
summaryCell(
t('bd_center.agency_number', 'Agency number'),
count(bill.agencyNumber)
),
summaryCell(
t('bd_center.team_salary', 'Team salary'),
'$' + money(bill.totalSalary)
),
]);
var rows = (bill.memberBillList || []).map(memberRow);
if (!rows.length) {
var empty = document.createElement('div');
empty.className = 'empty-state';
empty.textContent = t('bd_center.no_data', 'No data');
rows = [empty];
}
replaceChildren($('teamListContent'), rows);
}
function monthOption(date) {
var value = monthValue(date);
var option = document.createElement('button');
option.className = 'month-option';
option.type = 'button';
option.setAttribute('role', 'option');
option.setAttribute('data-month', value);
option.setAttribute('aria-selected', String(value === state.billMonth));
option.textContent = monthLabel(value);
if (value === state.billMonth) option.classList.add('is-selected');
return option;
}
function renderMonthSheet() {
replaceChildren(
$('monthOptionList'),
monthOptions().map(function (date) {
return monthOption(date);
})
);
}
function openMonthSheet() {
renderMonthSheet();
$('monthSheet').hidden = false;
$('monthSheet').setAttribute('aria-hidden', 'false');
document.body.classList.add('modal-open');
window.setTimeout(function () {
var selected = $('monthOptionList').querySelector('.is-selected');
if (selected && selected.scrollIntoView) {
selected.scrollIntoView({ block: 'center' });
}
}, 0);
}
function closeMonthSheet() {
$('monthSheet').hidden = true;
$('monthSheet').setAttribute('aria-hidden', 'true');
document.body.classList.remove('modal-open');
}
function selectMonth(value) {
var nextMonth = normalizeBillMonth(value);
if (!nextMonth || nextMonth === state.billMonth) {
closeMonthSheet();
return;
}
state.billMonth = nextMonth;
closeMonthSheet();
renderTeamList();
}
function inviteProfile(item) {
return item.profile || item;
}
function statusLabel(status) {
if (status === 'success' || status === 'Success' || status === 'accepted') {
return t('bd_center.success', 'Success');
}
if (status === 'pending' || status === 'Pending') return t('bd_center.pending', 'Pending');
return '';
}
function commandID(prefix, targetUserID) {
return [
prefix,
targetUserID,
Date.now(),
Math.random().toString(16).slice(2),
].join('-');
}
function inviteCard(item) {
var profile = inviteProfile(item);
var status = item.status || '';
var card = document.createElement('article');
var avatar = document.createElement('div');
var main = document.createElement('div');
var name = document.createElement('div');
var meta = document.createElement('div');
var side = document.createElement('div');
card.className = 'invite-user-card';
avatar.className = 'invite-user-avatar';
main.className = 'invite-user-main';
name.className = 'invite-user-name';
meta.className = 'invite-user-meta';
side.className = 'invite-user-side';
renderImageAvatar(
avatar,
profile.avatar,
profile.name,
'invite-avatar-fallback'
);
name.textContent = profile.name || '-';
meta.textContent = 'UID: ' + (profile.account || '-');
main.appendChild(name);
main.appendChild(meta);
if (status) {
var badge = document.createElement('span');
badge.className =
'invite-badge ' +
(status === 'pending' || status === 'Pending' ? 'pending' : 'success');
badge.textContent = statusLabel(status);
side.appendChild(badge);
} else {
var targetUserID = String(profile.userId || profile.id || '');
var invite = document.createElement('button');
invite.className = 'invite-action';
invite.type = 'button';
invite.disabled = Boolean(state.inviting[targetUserID]);
invite.textContent = state.inviting[targetUserID]
? t('bd_center.pending', 'Pending')
: t('bd_center.invite', 'Invite');
invite.addEventListener('click', function () {
submitInvite(profile);
});
side.appendChild(invite);
}
card.appendChild(avatar);
card.appendChild(main);
card.appendChild(side);
return card;
}
function setInviteStatus(text, type) {
var node = $('inviteStatus');
node.textContent = text || '';
node.hidden = !text;
node.classList.toggle('is-error', type === 'error');
node.classList.toggle('is-success', type === 'success');
}
function renderInviteModal() {
var rows =
state.inviteMode === 'search'
? state.searched.map(inviteCard)
: data.inviteRecords.map(inviteCard);
setInviteStatus(state.inviteStatus, state.inviteStatusType);
replaceChildren($('inviteList'), rows);
$('inviteEmpty').hidden =
rows.length > 0 || Boolean(state.inviteStatus);
$('inviteSearchButton').disabled =
!state.inviteQuery.trim() || Boolean(state.searching);
}
function openInviteModal() {
state.inviteQuery = '';
state.inviteMode = 'records';
state.searched = [];
state.inviteStatus = '';
state.inviteStatusType = '';
state.searching = false;
$('inviteSearchInput').value = '';
$('inviteModal').hidden = false;
$('inviteModal').setAttribute('aria-hidden', 'false');
document.body.classList.add('modal-open');
renderInviteModal();
window.setTimeout(function () {
$('inviteSearchInput').focus();
}, 80);
}
function closeInviteModal() {
$('inviteModal').hidden = true;
$('inviteModal').setAttribute('aria-hidden', 'true');
document.body.classList.remove('modal-open');
}
function searchInvite(event) {
event.preventDefault();
var queryValue = state.inviteQuery.trim();
state.inviteMode = 'search';
state.inviteStatus = '';
state.inviteStatusType = '';
state.searched = [];
if (!queryValue) {
renderInviteModal();
return;
}
if (!api().user || !api().user.resolveDisplayUserID) {
state.inviteStatus = t(
'bd_center.api_not_ready',
'API module is not ready.'
);
state.inviteStatusType = 'error';
renderInviteModal();
return;
}
state.inviteStatus = t('bd_center.searching', 'Searching...');
state.searching = true;
renderInviteModal();
api()
.user.resolveDisplayUserID(queryValue)
.then(function (payload) {
var profile = normalizeInviteProfile(payload, queryValue);
state.inviteStatus = profile
? ''
: t('bd_center.no_users_found', 'No users found');
state.inviteStatusType = '';
state.searched = profile ? [profile] : [];
renderInviteModal();
})
.catch(function (error) {
state.inviteStatus =
(error && error.message) ||
t('bd_center.search_failed', 'Search failed. Try again later.');
state.inviteStatusType = 'error';
state.searched = [];
renderInviteModal();
})
.finally(function () {
state.searching = false;
renderInviteModal();
});
}
function submitInvite(profile) {
var targetUserID = String(profile.userId || profile.id || '');
if (!targetUserID) return;
if (!centerAPI() || !centerAPI().inviteAgency) {
state.inviteStatus = t(
'bd_center.api_not_ready',
'API module is not ready.'
);
state.inviteStatusType = 'error';
renderInviteModal();
return;
}
state.inviting[targetUserID] = true;
renderInviteModal();
centerAPI()
.inviteAgency({
command_id: commandID('bd-agency', targetUserID),
target_user_id: targetUserID,
agency_name: profile.name,
})
.then(function () {
data.inviteRecords.unshift({
id: 'inv-' + Date.now(),
status: 'success',
profile: profile,
});
state.inviteMode = 'records';
state.searched = [];
state.inviteStatus = t('bd_center.agency_added', 'Agency added.');
state.inviteStatusType = 'success';
loadRealData().catch(function () {});
renderInviteModal();
})
.catch(function (error) {
state.inviteStatus =
(error && error.message) ||
t('bd_center.invite_failed', 'Invite failed. Try again later.');
state.inviteStatusType = 'error';
renderInviteModal();
})
.finally(function () {
delete state.inviting[targetUserID];
renderInviteModal();
});
}
function navigateToWithdrawExchange() {
var params = new URLSearchParams(window.location.search);
params.set('identity', 'BD');
params.set('salaryType', BD_SALARY_ASSET);
params.set('asset_type', BD_SALARY_ASSET);
if (!params.has('mode')) params.set('mode', 'withdraw');
window.location.href =
'../withdraw-exchange/index.html?' + params.toString();
}
function setLoading(loading) {
state.loading = Boolean(loading);
render();
}
function loadRealData() {
var service = centerAPI();
setLoading(true);
if (
!service ||
!service.overview ||
!service.agencies ||
!service.inviteAgency
) {
setLoading(false);
return Promise.reject(
new Error(t('bd_center.api_not_ready', 'API module is not ready.'))
);
}
return Promise.all([
service.overview(),
service.agencies(50),
]).then(function (results) {
applyOverview(results[0] || {});
applyAgencies(results[1] || {});
return true;
}).catch(function (error) {
data.profile = null;
data.balance = { available: 0 };
data.agencyBill = {
agencyNumber: 0,
totalSalary: 0,
totalRecharge: 0,
memberBillList: [],
};
throw error;
}).finally(function () {
setLoading(false);
});
}
function bindEvents() {
$('backButton').addEventListener('click', function () {
if (window.HyAppBridge) {
window.HyAppBridge.back();
return;
}
if (window.history.length > 1) window.history.back();
});
$('withdrawExchangeButton').addEventListener(
'click',
navigateToWithdrawExchange
);
$('inviteAgentButton').addEventListener('click', openInviteModal);
$('inviteBackdrop').addEventListener('click', closeInviteModal);
$('inviteCloseButton').addEventListener('click', closeInviteModal);
$('inviteSearchInput').addEventListener('input', function (event) {
state.inviteQuery = event.target.value;
if (!state.inviteQuery.trim()) {
state.inviteMode = 'records';
state.searched = [];
state.inviteStatus = '';
state.inviteStatusType = '';
}
renderInviteModal();
});
$('inviteSearchForm').addEventListener('submit', searchInvite);
$('billMonthButton').addEventListener('click', openMonthSheet);
$('monthSheetBackdrop').addEventListener('click', closeMonthSheet);
$('monthSheetCloseButton').addEventListener('click', closeMonthSheet);
$('monthOptionList').addEventListener('click', function (event) {
var option = event.target.closest('[data-month]');
if (!option) return;
selectMonth(option.getAttribute('data-month'));
});
}
function render() {
renderProfile();
renderBalance();
renderTeamList();
renderInviteModal();
}
document.addEventListener('DOMContentLoaded', function () {
render();
bindEvents();
loadRealData().catch(function (error) {
toast(
(error && error.message) ||
t('bd_center.load_failed', 'Load failed. Try again later.')
);
});
if (window.HyAppBridge) {
window.HyAppBridge.ready({
page: 'bd-center',
});
}
});
window.addEventListener('hyapp:i18n-ready', function () {
render();
});
})();