619 lines
22 KiB
JavaScript
619 lines
22 KiB
JavaScript
(function () {
|
||
var ui = window.FamiCenterUI;
|
||
var fallbackAvatar =
|
||
'../../products/fami/centers/assets/fami/agency-avatar.png';
|
||
var state = {
|
||
loading: true,
|
||
profile: null,
|
||
owner: null,
|
||
balance: 0,
|
||
stats: null,
|
||
hosts: [],
|
||
dailyHosts: {},
|
||
activeAsset: 'diamond',
|
||
activeDate: '',
|
||
view: 'summary',
|
||
inviteProfile: null,
|
||
inviting: false,
|
||
statsRequestSequence: 0,
|
||
dailyRequestSequence: 0,
|
||
};
|
||
var dates = [];
|
||
var contactController = null;
|
||
|
||
var summaryDefinitions = [
|
||
{
|
||
key: 'total_earnings',
|
||
label: 'fami.total_earnings',
|
||
fallback: 'Total Earnings',
|
||
},
|
||
{
|
||
key: 'gift_income',
|
||
label: 'fami.gift_income',
|
||
fallback: 'Gift Income',
|
||
},
|
||
{
|
||
key: 'share_income',
|
||
label: 'fami.share_income',
|
||
fallback: 'Share Income',
|
||
},
|
||
{
|
||
key: 'total_hosts',
|
||
label: 'fami.total_hosts',
|
||
fallback: 'Total Hosts',
|
||
},
|
||
{
|
||
key: 'gifted_host_count',
|
||
label: 'fami.gifted_hosts',
|
||
fallback: 'Gifted Hosts',
|
||
},
|
||
];
|
||
|
||
var mockHosts = Array.from({ length: 5 }, function (_, index) {
|
||
return {
|
||
membership_id: 'membership-' + index,
|
||
host_user_id: 'host-' + index,
|
||
display_user_id: '12345' + (index + 10),
|
||
username: 'host ' + (index + 1),
|
||
avatar: fallbackAvatar,
|
||
diamond_earnings: 123214,
|
||
};
|
||
});
|
||
|
||
var mock = {
|
||
user: {
|
||
user_id: 'agency-owner-1',
|
||
display_user_id: '123456789',
|
||
username: 'konghuimingc',
|
||
avatar: fallbackAvatar,
|
||
contact_info: '+65 1234 5678',
|
||
},
|
||
overview: {
|
||
agency: {
|
||
agency_id: '123456789',
|
||
name: 'konghuimingc',
|
||
avatar: fallbackAvatar,
|
||
created_at_ms: Date.UTC(2099, 8, 9),
|
||
},
|
||
},
|
||
coins: {
|
||
balances: [{ asset_type: 'COIN', available_amount: 1000000000 }],
|
||
},
|
||
stats: {
|
||
summary: {
|
||
total_earnings: 999999,
|
||
gift_income: 999999,
|
||
share_income: 999999,
|
||
total_hosts: 999999,
|
||
gifted_host_count: 999999,
|
||
},
|
||
hosts: mockHosts,
|
||
},
|
||
};
|
||
|
||
function profileForOverview(payload, owner) {
|
||
var data = ui.unwrap(payload) || {};
|
||
var agency = data.agency || {};
|
||
return {
|
||
id: String(agency.agency_id || ''),
|
||
displayId: (owner && owner.displayId) || '',
|
||
name: String(agency.name || 'Agency'),
|
||
avatar: String(agency.avatar || ''),
|
||
createdAt: agency.created_at_ms || 0,
|
||
};
|
||
}
|
||
|
||
function normalizeHost(item) {
|
||
item = item || {};
|
||
return {
|
||
id: String(item.host_user_id || ''),
|
||
membershipId: String(item.membership_id || ''),
|
||
displayId: String(item.display_user_id || ''),
|
||
name: String(item.username || 'Host'),
|
||
avatar: String(item.avatar || ''),
|
||
diamondEarnings: ui.number(item.diamond_earnings),
|
||
};
|
||
}
|
||
|
||
function summaryValues() {
|
||
var summary = (state.stats && state.stats.summary) || {};
|
||
return summaryDefinitions.map(function (definition) {
|
||
return ui.number(summary[definition.key]);
|
||
});
|
||
}
|
||
|
||
function applyStats(payload, date) {
|
||
var stats = ui.unwrap(payload) || { summary: {}, hosts: [] };
|
||
var hosts = Array.isArray(stats.hosts)
|
||
? stats.hosts.map(normalizeHost)
|
||
: [];
|
||
state.stats = stats;
|
||
state.hosts = hosts;
|
||
if (date) state.dailyHosts[date] = hosts;
|
||
}
|
||
|
||
function hostValue(host, index) {
|
||
var date = dates[index] && dates[index].value;
|
||
var daily = state.dailyHosts[date] || [];
|
||
var item = daily.find(function (candidate) {
|
||
return candidate.id === host.id;
|
||
});
|
||
return item ? item.diamondEarnings : 0;
|
||
}
|
||
|
||
function renderProfile() {
|
||
var profile = state.profile || {};
|
||
var owner = state.owner || {};
|
||
ui.setAvatar(ui.$('agencyAvatar'), profile.avatar, fallbackAvatar);
|
||
ui.$('agencyName').textContent = profile.name || '-';
|
||
ui.$('agencyUserId').textContent = ui
|
||
.t('fami.user_id', 'User ID:{id}')
|
||
.replace('{id}', profile.displayId || '-');
|
||
var welcomeDate = profile.createdAt
|
||
? new Date(profile.createdAt)
|
||
: null;
|
||
var welcome = '';
|
||
if (welcomeDate && !Number.isNaN(welcomeDate.getTime())) {
|
||
// Figma 固定使用点分隔日期;字段值仍来自 agency.created_at_ms,不由客户端推断公会成立时间。
|
||
welcome = [
|
||
welcomeDate.getFullYear(),
|
||
String(welcomeDate.getMonth() + 1).padStart(2, '0'),
|
||
String(welcomeDate.getDate()).padStart(2, '0'),
|
||
].join('.');
|
||
}
|
||
if (ui.isMock()) welcome = '2099.99.99';
|
||
ui.$('agencyWelcome').textContent = ui
|
||
.t('fami.welcome_date', 'Welcome {date}')
|
||
.replace('{date}', welcome || '-');
|
||
ui.$('contactName').textContent = owner.name || profile.name || '-';
|
||
ui.$('contactUserId').textContent = ui
|
||
.t('fami.user_id', 'User ID:{id}')
|
||
.replace('{id}', owner.displayId || profile.displayId || '-');
|
||
if (contactController)
|
||
contactController.setContact(owner.contact || '');
|
||
}
|
||
|
||
function renderDates() {
|
||
dates = ui.dateWindow(9);
|
||
if (!state.activeDate && dates.length)
|
||
state.activeDate = dates[0].value;
|
||
ui.renderDateStrip(
|
||
ui.$('dateStrip'),
|
||
dates.slice(0, 7),
|
||
state.activeDate,
|
||
selectDate
|
||
);
|
||
}
|
||
|
||
function renderMetrics() {
|
||
var grid = ui.$('metricGrid');
|
||
grid.replaceChildren();
|
||
summaryValues().forEach(function (value, index) {
|
||
var card = document.createElement('div');
|
||
card.className = 'agency-metric';
|
||
var valueRow = document.createElement('div');
|
||
valueRow.className = 'agency-metric__value';
|
||
if (index < 3) {
|
||
var icon = document.createElement('img');
|
||
icon.src =
|
||
'../../products/fami/centers/assets/fami/diamond.png';
|
||
icon.alt = '';
|
||
valueRow.appendChild(icon);
|
||
}
|
||
var number = document.createElement('strong');
|
||
number.textContent = ui.formatNumber(value).replace(/,/g, '');
|
||
valueRow.appendChild(number);
|
||
var definition = summaryDefinitions[index];
|
||
var label = document.createElement('span');
|
||
label.textContent = ui.isMock()
|
||
? ui.t('fami.total_return', 'Total Return')
|
||
: ui.t(definition.label, definition.fallback);
|
||
card.appendChild(valueRow);
|
||
card.appendChild(label);
|
||
grid.appendChild(card);
|
||
});
|
||
}
|
||
|
||
function renderTabs() {
|
||
document.querySelectorAll('.agency-tab').forEach(function (tab) {
|
||
var active = tab.getAttribute('data-type') === state.activeAsset;
|
||
tab.classList.toggle('is-active', active);
|
||
tab.setAttribute('aria-selected', String(active));
|
||
});
|
||
}
|
||
|
||
function renderHostTable() {
|
||
var hostTable = ui.$('hostTable');
|
||
// mock 模式固定复现 Figma 已输入的搜索态,但不能让占位 ID 把设计稿中的示例行过滤掉;真实入口仍按用户提交值筛选。
|
||
var query = ui.isMock()
|
||
? ''
|
||
: String(ui.$('hostSearchInput').value || '')
|
||
.trim()
|
||
.toLowerCase();
|
||
var hosts = state.hosts.filter(function (host) {
|
||
if (!query) return true;
|
||
return (
|
||
host.displayId.toLowerCase().includes(query) ||
|
||
host.name.toLowerCase().includes(query)
|
||
);
|
||
});
|
||
var grid = document.createElement('div');
|
||
grid.className = 'agency-host-table__grid';
|
||
var header = document.createElement('div');
|
||
header.className = 'agency-host-table__header';
|
||
dates.slice(0, 9).forEach(function (date) {
|
||
var head = document.createElement('span');
|
||
head.className = 'agency-host-table__head';
|
||
head.textContent = ui.isMock()
|
||
? ui.t('fami.today', 'Today')
|
||
: date.label;
|
||
head.setAttribute('aria-label', date.label + ' ' + date.value);
|
||
header.appendChild(head);
|
||
});
|
||
var rows = document.createElement('div');
|
||
rows.className = 'agency-host-table__rows';
|
||
hosts.forEach(function (host) {
|
||
var avatar = document.createElement('img');
|
||
avatar.className = 'agency-host-table__avatar';
|
||
avatar.alt = '';
|
||
ui.setAvatar(avatar, host.avatar, fallbackAvatar);
|
||
rows.appendChild(avatar);
|
||
dates.slice(0, 8).forEach(function (_, index) {
|
||
var value = document.createElement('span');
|
||
value.className = 'agency-host-table__value';
|
||
value.textContent = ui
|
||
.formatNumber(hostValue(host, index))
|
||
.replace(/,/g, '');
|
||
rows.appendChild(value);
|
||
});
|
||
});
|
||
grid.appendChild(header);
|
||
grid.appendChild(rows);
|
||
hostTable.replaceChildren(grid);
|
||
}
|
||
|
||
function renderView() {
|
||
ui.$('summaryView').hidden = state.view !== 'summary';
|
||
ui.$('hostView').hidden = state.view !== 'hosts';
|
||
if (state.view === 'hosts') renderHostTable();
|
||
}
|
||
|
||
function render() {
|
||
renderProfile();
|
||
ui.$('goldBalance').textContent = ui.formatNumber(state.balance);
|
||
renderTabs();
|
||
renderDates();
|
||
renderMetrics();
|
||
renderView();
|
||
}
|
||
|
||
function applyPayloads(
|
||
userPayload,
|
||
overviewPayload,
|
||
coinPayload,
|
||
statsPayload
|
||
) {
|
||
state.owner = ui.profileFrom(userPayload, 'Agency');
|
||
state.profile = profileForOverview(overviewPayload, state.owner);
|
||
state.balance = ui.walletAvailable(coinPayload, 'COIN');
|
||
applyStats(statsPayload, state.activeDate);
|
||
state.loading = false;
|
||
render();
|
||
}
|
||
|
||
function selectDate(value) {
|
||
state.activeDate = value;
|
||
renderDates();
|
||
if (ui.isMock()) {
|
||
applyStats(mock.stats, value);
|
||
renderMetrics();
|
||
return;
|
||
}
|
||
var api = window.HyAppAPI && window.HyAppAPI.agencyCenter;
|
||
if (!api || !api.stats) return;
|
||
var sequence = ++state.statsRequestSequence;
|
||
// 汇总卡必须与当前选中 UTC 自然日一致;迟到响应只写入其日期缓存,不覆盖后来选择。
|
||
api.stats({ start_date: value, end_date: value })
|
||
.then(function (payload) {
|
||
if (sequence !== state.statsRequestSequence) return;
|
||
applyStats(payload, value);
|
||
renderMetrics();
|
||
})
|
||
.catch(function (error) {
|
||
if (sequence !== state.statsRequestSequence) return;
|
||
ui.toast(
|
||
(error && error.message) ||
|
||
ui.t(
|
||
'agency_center.load_failed',
|
||
'Load failed. Try again later.'
|
||
)
|
||
);
|
||
});
|
||
}
|
||
|
||
function loadDailyMatrix() {
|
||
if (ui.isMock()) {
|
||
dates.slice(0, 8).forEach(function (date) {
|
||
state.dailyHosts[date.value] = mockHosts.map(normalizeHost);
|
||
});
|
||
renderHostTable();
|
||
return;
|
||
}
|
||
var api = window.HyAppAPI && window.HyAppAPI.agencyCenter;
|
||
if (!api || !api.stats) return;
|
||
var sequence = ++state.dailyRequestSequence;
|
||
// 矩阵每列是一个自然日,现有 stats 契约按区间聚合;并行读取 8 个单日且独立容错,绝不把某日聚合值复制到其他日期。
|
||
Promise.all(
|
||
dates.slice(0, 8).map(function (date) {
|
||
return ui.safe('agency-stats-' + date.value, function () {
|
||
return api.stats({
|
||
start_date: date.value,
|
||
end_date: date.value,
|
||
});
|
||
});
|
||
})
|
||
).then(function (payloads) {
|
||
if (sequence !== state.dailyRequestSequence) return;
|
||
payloads.forEach(function (payload, index) {
|
||
var day = dates[index] && dates[index].value;
|
||
var data = ui.unwrap(payload) || {};
|
||
state.dailyHosts[day] = Array.isArray(data.hosts)
|
||
? data.hosts.map(normalizeHost)
|
||
: [];
|
||
});
|
||
renderHostTable();
|
||
});
|
||
}
|
||
|
||
function load() {
|
||
dates = ui.dateWindow(9);
|
||
if (!state.activeDate && dates.length)
|
||
state.activeDate = dates[0].value;
|
||
if (ui.isMock()) {
|
||
applyPayloads(mock.user, mock.overview, mock.coins, mock.stats);
|
||
return;
|
||
}
|
||
var api = window.HyAppAPI;
|
||
if (!api || !api.agencyCenter || !api.agencyCenter.stats) {
|
||
state.loading = false;
|
||
render();
|
||
ui.toast(
|
||
ui.t('agency_center.api_not_ready', 'API module is not ready.')
|
||
);
|
||
return;
|
||
}
|
||
|
||
// 公会资料、负责人、COIN 余额和日统计来自明确接口;旧 salary 字段只代表 USD 工资,不能继续冒充 Gold Coins。
|
||
Promise.all([
|
||
ui.safe('agency-owner', function () {
|
||
return api.user.me();
|
||
}),
|
||
ui.safe('agency-overview', function () {
|
||
return api.agencyCenter.overview();
|
||
}),
|
||
ui.safe('agency-coins', function () {
|
||
return api.wallet.balances('COIN');
|
||
}),
|
||
ui.safe('agency-stats', function () {
|
||
return api.agencyCenter.stats({
|
||
start_date: state.activeDate,
|
||
end_date: state.activeDate,
|
||
});
|
||
}),
|
||
]).then(function (results) {
|
||
applyPayloads(results[0], results[1], results[2], results[3]);
|
||
if (!results.some(Boolean)) {
|
||
ui.toast(
|
||
ui.t(
|
||
'agency_center.load_failed',
|
||
'Load failed. Try again later.'
|
||
)
|
||
);
|
||
}
|
||
});
|
||
}
|
||
|
||
function openWithdraw() {
|
||
// 资金页必须由产品上下文构造 URL,保证无 app_code 参数的旧客户端仍使用 Fami 租户。
|
||
window.location.href = window.HyGuild.product.buildURL('../wallet/', {
|
||
identity: 'AGENCY',
|
||
});
|
||
}
|
||
|
||
function setInviteStatus(message, success) {
|
||
var node = ui.$('inviteStatus');
|
||
node.textContent = message || '';
|
||
node.hidden = !message;
|
||
node.classList.toggle('is-success', !!success);
|
||
}
|
||
|
||
function normalizeInviteProfile(payload, fallbackId) {
|
||
var profile = ui.profileFrom(payload, 'Host');
|
||
if (!profile.id) return null;
|
||
if (!profile.displayId) profile.displayId = fallbackId;
|
||
return profile;
|
||
}
|
||
|
||
function renderInviteResult() {
|
||
var root = ui.$('inviteResult');
|
||
root.replaceChildren();
|
||
if (!state.inviteProfile) return;
|
||
var row = document.createElement('div');
|
||
row.className = 'fami-search-result';
|
||
var avatar = document.createElement('img');
|
||
avatar.alt = '';
|
||
ui.setAvatar(avatar, state.inviteProfile.avatar, fallbackAvatar);
|
||
var copy = document.createElement('div');
|
||
copy.className = 'fami-search-result__copy';
|
||
var name = document.createElement('strong');
|
||
name.textContent = state.inviteProfile.name || 'Host';
|
||
var id = document.createElement('span');
|
||
id.textContent = 'ID: ' + state.inviteProfile.displayId;
|
||
var action = document.createElement('button');
|
||
action.className = 'fami-search-result__action';
|
||
action.type = 'button';
|
||
action.disabled = state.inviting;
|
||
action.textContent = state.inviting
|
||
? ui.t('agency_center.pending', 'Pending')
|
||
: ui.t('agency_center.invite', 'Invite');
|
||
action.addEventListener('click', inviteHost);
|
||
copy.appendChild(name);
|
||
copy.appendChild(id);
|
||
row.appendChild(avatar);
|
||
row.appendChild(copy);
|
||
row.appendChild(action);
|
||
root.appendChild(row);
|
||
}
|
||
|
||
function searchInvite(event) {
|
||
event.preventDefault();
|
||
var value = String(ui.$('inviteSearchInput').value || '').trim();
|
||
state.inviteProfile = null;
|
||
renderInviteResult();
|
||
if (!value) return;
|
||
if (ui.isMock()) {
|
||
state.inviteProfile = ui.profileFrom(mock.user, 'Host');
|
||
state.inviteProfile.id = 'mock-host-' + value;
|
||
state.inviteProfile.displayId = value;
|
||
renderInviteResult();
|
||
return;
|
||
}
|
||
var api = window.HyAppAPI && window.HyAppAPI.user;
|
||
if (!api || !api.resolveDisplayUserID) {
|
||
setInviteStatus(
|
||
ui.t('agency_center.api_not_ready', 'API module is not ready.')
|
||
);
|
||
return;
|
||
}
|
||
ui.$('inviteSearchButton').disabled = true;
|
||
setInviteStatus(ui.t('agency_center.searching', 'Searching...'));
|
||
api.resolveDisplayUserID(value)
|
||
.then(function (payload) {
|
||
state.inviteProfile = normalizeInviteProfile(payload, value);
|
||
setInviteStatus(
|
||
state.inviteProfile
|
||
? ''
|
||
: ui.t('agency_center.no_users_found', 'No users found')
|
||
);
|
||
renderInviteResult();
|
||
})
|
||
.catch(function (error) {
|
||
setInviteStatus(
|
||
(error && error.message) ||
|
||
ui.t(
|
||
'agency_center.search_failed',
|
||
'Search failed. Try again later.'
|
||
)
|
||
);
|
||
})
|
||
.finally(function () {
|
||
ui.$('inviteSearchButton').disabled = false;
|
||
});
|
||
}
|
||
|
||
function inviteHost() {
|
||
if (!state.inviteProfile || state.inviting) return;
|
||
if (ui.isMock()) {
|
||
setInviteStatus(
|
||
ui.t(
|
||
'agency_center.invite_sent',
|
||
'Invite sent. Waiting for confirmation.'
|
||
),
|
||
true
|
||
);
|
||
return;
|
||
}
|
||
var api = window.HyAppAPI && window.HyAppAPI.agencyCenter;
|
||
if (!api || !api.inviteHost) return;
|
||
state.inviting = true;
|
||
renderInviteResult();
|
||
// 邀请使用唯一 command_id 交给服务端幂等;结果未确认前禁用按钮,避免 WebView 连点产生多份待确认邀请。
|
||
api.inviteHost({
|
||
command_id: ui.commandId('agency-invite-host'),
|
||
target_user_id: state.inviteProfile.id,
|
||
})
|
||
.then(function () {
|
||
setInviteStatus(
|
||
ui.t(
|
||
'agency_center.invite_sent',
|
||
'Invite sent. Waiting for confirmation.'
|
||
),
|
||
true
|
||
);
|
||
state.inviteProfile = null;
|
||
renderInviteResult();
|
||
})
|
||
.catch(function (error) {
|
||
setInviteStatus(
|
||
(error && error.message) ||
|
||
ui.t(
|
||
'agency_center.invite_failed',
|
||
'Invite failed. Try again later.'
|
||
)
|
||
);
|
||
})
|
||
.finally(function () {
|
||
state.inviting = false;
|
||
renderInviteResult();
|
||
});
|
||
}
|
||
|
||
function openInvite() {
|
||
ui.$('hostSearchInput').value = ui.isMock() ? '1235456' : '';
|
||
state.view = 'hosts';
|
||
renderView();
|
||
loadDailyMatrix();
|
||
ui.openModal('inviteModal');
|
||
ui.$('inviteSearchInput').value = '';
|
||
state.inviteProfile = null;
|
||
setInviteStatus('');
|
||
renderInviteResult();
|
||
}
|
||
|
||
function init() {
|
||
contactController = ui.bindContact();
|
||
ui.$('backButton').addEventListener('click', function () {
|
||
if (state.view === 'hosts') {
|
||
state.view = 'summary';
|
||
renderView();
|
||
return;
|
||
}
|
||
ui.goBack();
|
||
});
|
||
ui.$('withdrawButton').addEventListener('click', openWithdraw);
|
||
ui.$('addHostButton').addEventListener('click', openInvite);
|
||
ui.$('inviteSearchForm').addEventListener('submit', searchInvite);
|
||
ui.$('hostSearchForm').addEventListener('submit', function (event) {
|
||
event.preventDefault();
|
||
renderHostTable();
|
||
});
|
||
document
|
||
.querySelectorAll('[data-invite-close]')
|
||
.forEach(function (node) {
|
||
node.addEventListener('click', function () {
|
||
ui.closeModal('inviteModal');
|
||
});
|
||
});
|
||
document
|
||
.querySelectorAll('.agency-tab:not([disabled])')
|
||
.forEach(function (tab) {
|
||
tab.addEventListener('click', function () {
|
||
state.activeAsset = tab.getAttribute('data-type');
|
||
renderTabs();
|
||
});
|
||
});
|
||
window.addEventListener('hyapp:i18n-ready', render);
|
||
render();
|
||
load();
|
||
if (window.HyAppBridge) {
|
||
window.HyAppBridge.ready({
|
||
page: 'agency-center',
|
||
app_code: 'fami',
|
||
mock: ui.isMock(),
|
||
});
|
||
}
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
})();
|