1869 lines
62 KiB
JavaScript
1869 lines
62 KiB
JavaScript
(function () {
|
||
var SELLER_ASSET_TYPE = 'COIN_SELLER_COIN';
|
||
var query = new URLSearchParams(window.location.search);
|
||
|
||
var mock = {
|
||
profile: {
|
||
name: 'CoinSeller Mina',
|
||
uid: '163009',
|
||
region: 'Saudi Arabia',
|
||
avatar: avatarData('C', '#dbc8ff', '#7d57c7'),
|
||
},
|
||
balance: {
|
||
coins: 1285000,
|
||
},
|
||
users: [
|
||
{
|
||
uid: '2001',
|
||
userId: '9002001',
|
||
name: 'Alice',
|
||
avatar: avatarData('A', '#dbc8ff', '#7d57c7'),
|
||
},
|
||
{
|
||
uid: '2002',
|
||
userId: '9002002',
|
||
name: 'Bob',
|
||
avatar: avatarData('B', '#f4ebff', '#7d57c7'),
|
||
},
|
||
{
|
||
uid: '2003',
|
||
userId: '9002003',
|
||
name: 'Nora',
|
||
avatar: avatarData('N', '#efe4ff', '#7d57c7'),
|
||
},
|
||
],
|
||
quickAmounts: [10000, 50000, 100000],
|
||
records: [
|
||
{
|
||
id: 'tx-001',
|
||
type: 'income',
|
||
name: 'Stock transfer',
|
||
uid: 'System',
|
||
avatar: avatarData('S', '#e7f7f3', '#31a88f'),
|
||
coins: 300000,
|
||
amount: 36,
|
||
currency: 'USD',
|
||
time: '2026-06-04 10:30',
|
||
},
|
||
{
|
||
id: 'tx-002',
|
||
type: 'expenditure',
|
||
name: 'Alice',
|
||
uid: '2001',
|
||
avatar: avatarData('A', '#dbc8ff', '#7d57c7'),
|
||
coins: 110000,
|
||
amount: 12.5,
|
||
currency: 'USD',
|
||
time: '2026-06-04 10:10',
|
||
},
|
||
{
|
||
id: 'tx-003',
|
||
type: 'expenditure',
|
||
name: 'Bob',
|
||
uid: '2002',
|
||
avatar: avatarData('B', '#f4ebff', '#7d57c7'),
|
||
coins: 88000,
|
||
amount: 9.25,
|
||
currency: 'USD',
|
||
time: '2026-06-04 09:20',
|
||
},
|
||
{
|
||
id: 'tx-004',
|
||
type: 'income',
|
||
name: 'Manager recharge',
|
||
uid: 'System',
|
||
avatar: avatarData('M', '#e7f7f3', '#31a88f'),
|
||
coins: 180000,
|
||
amount: 22,
|
||
currency: 'USD',
|
||
time: '2026-06-03 18:42',
|
||
},
|
||
],
|
||
subSellerPermission: {
|
||
visible: true,
|
||
canInvite: true,
|
||
canTransfer: true,
|
||
},
|
||
subSellers: [
|
||
{
|
||
uid: '7001001',
|
||
userId: '107001001',
|
||
name: 'Sub Seller Sara',
|
||
avatar: avatarData('S', '#efe4ff', '#7d57c7'),
|
||
status: 'active',
|
||
balance: 320000,
|
||
},
|
||
{
|
||
uid: '7001002',
|
||
userId: '107001002',
|
||
name: 'Sub Seller Omar',
|
||
avatar: avatarData('O', '#e7f7f3', '#31a88f'),
|
||
status: 'active',
|
||
balance: 180000,
|
||
},
|
||
],
|
||
};
|
||
|
||
var data = {
|
||
profile: emptyProfile(),
|
||
balance: { coins: 0 },
|
||
users: [],
|
||
quickAmounts: mock.quickAmounts.slice(),
|
||
records: [],
|
||
subSellerPermission: emptySubSellerPermission(),
|
||
subSellers: [],
|
||
};
|
||
|
||
var state = {
|
||
target: null,
|
||
coinAmount: '',
|
||
historyTab: 'income',
|
||
historyQuery: '',
|
||
historyLimit: 3,
|
||
isMock: query.get('mock') === '1',
|
||
loading: query.get('mock') !== '1',
|
||
searching: false,
|
||
submitting: false,
|
||
subSellerLoading: false,
|
||
invitingSubSeller: false,
|
||
subSellerTransferTarget: null,
|
||
subSellerTransferAmount: '',
|
||
subSellerTransferSubmitting: false,
|
||
};
|
||
|
||
function $(id) {
|
||
return document.getElementById(id);
|
||
}
|
||
|
||
function t(key, fallback) {
|
||
return window.HyAppI18n && window.HyAppI18n.t
|
||
? window.HyAppI18n.t(key, fallback)
|
||
: fallback || key;
|
||
}
|
||
|
||
function avatarData(text, background, foreground) {
|
||
var value =
|
||
String(text || 'C')
|
||
.trim()
|
||
.charAt(0) || 'C';
|
||
var letter = value.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 emptyProfile() {
|
||
return {
|
||
name: '',
|
||
uid: '',
|
||
region: '',
|
||
avatar: '',
|
||
};
|
||
}
|
||
|
||
function emptySubSellerPermission() {
|
||
return {
|
||
visible: false,
|
||
canInvite: false,
|
||
canTransfer: false,
|
||
};
|
||
}
|
||
|
||
function initial(text) {
|
||
return String(text || '')
|
||
.trim()
|
||
.charAt(0)
|
||
.toUpperCase();
|
||
}
|
||
|
||
function count(value) {
|
||
return Number(value || 0).toLocaleString('en-US', {
|
||
maximumFractionDigits: 0,
|
||
});
|
||
}
|
||
|
||
function toNumber(value) {
|
||
var number = Number(value);
|
||
return Number.isFinite(number) ? number : 0;
|
||
}
|
||
|
||
function positiveNumber(value) {
|
||
var number = toNumber(value);
|
||
return number > 0 ? number : 0;
|
||
}
|
||
|
||
function booleanValue(value) {
|
||
if (value === true || value === 1) return true;
|
||
if (value === false || value === 0) return false;
|
||
var text = String(value || '')
|
||
.trim()
|
||
.toLowerCase();
|
||
return text === 'true' || text === '1' || text === 'yes';
|
||
}
|
||
|
||
function firstValue(source, keys) {
|
||
if (!source) return '';
|
||
for (var i = 0; i < keys.length; i += 1) {
|
||
var value = source[keys[i]];
|
||
if (value !== undefined && value !== null && value !== '') {
|
||
return value;
|
||
}
|
||
}
|
||
return '';
|
||
}
|
||
|
||
function unwrapData(payload) {
|
||
if (!payload || typeof payload !== 'object') return payload;
|
||
return Object.prototype.hasOwnProperty.call(payload, 'data')
|
||
? payload.data
|
||
: payload;
|
||
}
|
||
|
||
function replaceChildren(node, children) {
|
||
node.textContent = '';
|
||
children.forEach(function (child) {
|
||
node.appendChild(child);
|
||
});
|
||
}
|
||
|
||
function toast(message) {
|
||
if (!message) return;
|
||
if (window.HyAppToast && window.HyAppToast.show) {
|
||
window.HyAppToast.show(message);
|
||
return;
|
||
}
|
||
window.alert(message);
|
||
}
|
||
|
||
function setStatus(node, message, type) {
|
||
node.hidden = !message;
|
||
node.textContent = message || '';
|
||
node.className = 'form-status' + (type ? ' ' + type : '');
|
||
}
|
||
|
||
function setSkeleton(element, loading, text) {
|
||
if (!element) return;
|
||
element.classList.toggle('skeleton-line', !!loading);
|
||
if (loading) {
|
||
element.textContent = '';
|
||
return;
|
||
}
|
||
element.textContent = text || '';
|
||
}
|
||
|
||
function hasAPI() {
|
||
return (
|
||
window.HyAppAPI && window.HyAppAPI.user && window.HyAppAPI.wallet
|
||
);
|
||
}
|
||
|
||
function hasSubSellerAPI() {
|
||
return (
|
||
hasAPI() &&
|
||
window.HyAppAPI.wallet.getCoinSellerSubSellerPermission &&
|
||
window.HyAppAPI.wallet.listCoinSellerSubSellers
|
||
);
|
||
}
|
||
|
||
function setImage(image, fallback, src, name) {
|
||
if (!image || !fallback) return;
|
||
fallback.textContent = initial(name) || 'C';
|
||
if (!src) {
|
||
image.hidden = true;
|
||
image.removeAttribute('src');
|
||
fallback.hidden = false;
|
||
return;
|
||
}
|
||
image.onload = function () {
|
||
image.hidden = false;
|
||
fallback.hidden = true;
|
||
};
|
||
image.onerror = function () {
|
||
image.hidden = true;
|
||
fallback.hidden = false;
|
||
};
|
||
image.src = src;
|
||
}
|
||
|
||
function renderAvatar(container, item) {
|
||
container.textContent = '';
|
||
if (item.avatar) {
|
||
var image = document.createElement('img');
|
||
image.src = item.avatar;
|
||
image.alt = '';
|
||
image.addEventListener('error', function () {
|
||
container.textContent = initial(item.name || item.uid) || 'U';
|
||
});
|
||
container.appendChild(image);
|
||
return;
|
||
}
|
||
container.textContent = initial(item.name || item.uid) || 'U';
|
||
}
|
||
|
||
function normalizeProfile(payload) {
|
||
var source = unwrapData(payload) || {};
|
||
return {
|
||
name:
|
||
firstValue(source, [
|
||
'username',
|
||
'nickname',
|
||
'name',
|
||
'display_user_id',
|
||
'displayUserID',
|
||
'user_id',
|
||
'userId',
|
||
]) || '-',
|
||
uid:
|
||
firstValue(source, [
|
||
'display_user_id',
|
||
'displayUserID',
|
||
'displayUserId',
|
||
'user_id',
|
||
'userId',
|
||
]) || '-',
|
||
region:
|
||
firstValue(source, [
|
||
'country_display_name',
|
||
'countryDisplayName',
|
||
'country_name',
|
||
'countryName',
|
||
'region_name',
|
||
'regionName',
|
||
'country',
|
||
'region',
|
||
]) || '',
|
||
avatar:
|
||
firstValue(source, [
|
||
'avatar',
|
||
'avatar_url',
|
||
'avatarUrl',
|
||
'profile_picture_url',
|
||
'profilePictureUrl',
|
||
]) || '',
|
||
};
|
||
}
|
||
|
||
function normalizeBalance(payload) {
|
||
var source = unwrapData(payload) || {};
|
||
var balances = Array.isArray(source.balances)
|
||
? source.balances
|
||
: Array.isArray(source)
|
||
? source
|
||
: [];
|
||
var matched =
|
||
balances.find(function (item) {
|
||
return item && item.asset_type === SELLER_ASSET_TYPE;
|
||
}) ||
|
||
balances.find(function (item) {
|
||
return item && item.assetType === SELLER_ASSET_TYPE;
|
||
}) ||
|
||
balances[0] ||
|
||
{};
|
||
return {
|
||
coins: toNumber(
|
||
firstValue(matched, [
|
||
'available_amount',
|
||
'availableAmount',
|
||
'available',
|
||
'amount',
|
||
])
|
||
),
|
||
};
|
||
}
|
||
|
||
function timeLabelFromMS(value) {
|
||
var number = Number(value || 0);
|
||
if (!Number.isFinite(number) || number <= 0) return '';
|
||
var date = new Date(number);
|
||
if (Number.isNaN(date.getTime())) return '';
|
||
return (
|
||
date.getFullYear() +
|
||
'-' +
|
||
String(date.getMonth() + 1).padStart(2, '0') +
|
||
'-' +
|
||
String(date.getDate()).padStart(2, '0') +
|
||
' ' +
|
||
String(date.getHours()).padStart(2, '0') +
|
||
':' +
|
||
String(date.getMinutes()).padStart(2, '0')
|
||
);
|
||
}
|
||
|
||
function normalizeTransferAmount(item) {
|
||
// 转账美金来自服务端账务 metadata;不同入账来源汇率可能不同,前端不能用金币数反推,缺字段时保持不展示。
|
||
var minor = positiveNumber(
|
||
firstValue(item, [
|
||
'transfer_usd_minor',
|
||
'transferUsdMinor',
|
||
'salary_usd_minor',
|
||
'salaryUsdMinor',
|
||
'recharge_usd_minor',
|
||
'rechargeUsdMinor',
|
||
'usd_minor_amount',
|
||
'usdMinorAmount',
|
||
])
|
||
);
|
||
if (minor > 0) return minor / 100;
|
||
var micro = positiveNumber(
|
||
firstValue(item, [
|
||
'paid_amount_micro',
|
||
'paidAmountMicro',
|
||
'amount_usdt_micro',
|
||
'amountUsdtMicro',
|
||
])
|
||
);
|
||
if (micro > 0) return micro / 1000000;
|
||
return positiveNumber(
|
||
firstValue(item, [
|
||
'amount_usd',
|
||
'amountUsd',
|
||
'transfer_amount_usd',
|
||
'transferAmountUsd',
|
||
])
|
||
);
|
||
}
|
||
|
||
function normalizeTransferCurrency(item, amount) {
|
||
return amount > 0
|
||
? String(
|
||
firstValue(item, [
|
||
'transfer_currency_code',
|
||
'transferCurrencyCode',
|
||
'recharge_currency_code',
|
||
'rechargeCurrencyCode',
|
||
'paid_currency_code',
|
||
'paidCurrencyCode',
|
||
'currency_code',
|
||
'currencyCode',
|
||
]) || 'USD'
|
||
).toUpperCase()
|
||
: '';
|
||
}
|
||
|
||
function formatTransferAmount(amount, currency) {
|
||
if (!amount || amount <= 0) return '';
|
||
var code = String(currency || 'USD').toUpperCase();
|
||
var value = Number(amount).toLocaleString('en-US', {
|
||
minimumFractionDigits: 2,
|
||
maximumFractionDigits: 2,
|
||
});
|
||
return code + ' ' + value;
|
||
}
|
||
|
||
function normalizeResolvedUser(payload, keyword) {
|
||
var source = unwrapData(payload) || {};
|
||
var identity = source.identity || source.user_identity || source;
|
||
var profile = source.profile || source.user || source;
|
||
var displayID =
|
||
firstValue(identity, [
|
||
'display_user_id',
|
||
'displayUserID',
|
||
'displayUserId',
|
||
'display_id',
|
||
'displayId',
|
||
]) ||
|
||
firstValue(profile, [
|
||
'display_user_id',
|
||
'displayUserID',
|
||
'displayUserId',
|
||
'display_id',
|
||
'displayId',
|
||
]) ||
|
||
String(keyword || '').trim();
|
||
var userID =
|
||
firstValue(identity, ['user_id', 'userId', 'id']) ||
|
||
firstValue(profile, ['user_id', 'userId', 'id']) ||
|
||
'';
|
||
return {
|
||
uid: displayID,
|
||
userId: userID,
|
||
name:
|
||
firstValue(profile, ['username', 'nickname', 'name']) ||
|
||
firstValue(identity, ['username', 'nickname', 'name']) ||
|
||
t('coinseller_center.user_name_template', 'User {id}').replace(
|
||
'{id}',
|
||
displayID
|
||
),
|
||
avatar:
|
||
firstValue(profile, [
|
||
'avatar',
|
||
'avatar_url',
|
||
'avatarUrl',
|
||
'profile_picture_url',
|
||
'profilePictureUrl',
|
||
]) ||
|
||
firstValue(identity, [
|
||
'avatar',
|
||
'avatar_url',
|
||
'avatarUrl',
|
||
'profile_picture_url',
|
||
'profilePictureUrl',
|
||
]) ||
|
||
'',
|
||
};
|
||
}
|
||
|
||
function normalizeTransactionRecord(item, index) {
|
||
var delta = toNumber(
|
||
firstValue(item, ['available_delta', 'availableDelta'])
|
||
);
|
||
var type = delta >= 0 ? 'income' : 'expenditure';
|
||
var counterpartyUserID = String(
|
||
firstValue(item, ['counterparty_user_id', 'counterpartyUserId']) ||
|
||
''
|
||
);
|
||
var counterpartyDisplayID = String(
|
||
firstValue(item, [
|
||
'counterparty_display_user_id',
|
||
'counterpartyDisplayUserId',
|
||
'counterpartyDisplayUserID',
|
||
]) || ''
|
||
);
|
||
var counterpartyName = firstValue(item, [
|
||
'counterparty_username',
|
||
'counterpartyUsername',
|
||
'counterparty_name',
|
||
'counterpartyName',
|
||
]);
|
||
var counterpartyAvatar = firstValue(item, [
|
||
'counterparty_avatar',
|
||
'counterpartyAvatar',
|
||
]);
|
||
var counterpartyUID = counterpartyDisplayID || counterpartyUserID;
|
||
// 入账流水也可能来自某个用户的工资转币商,标题必须优先展示对方资料;只有后台进货这类没有对方用户的流水才展示库存入账。
|
||
var name =
|
||
counterpartyName ||
|
||
(counterpartyUID
|
||
? t(
|
||
'coinseller_center.user_name_template',
|
||
'User {id}'
|
||
).replace('{id}', counterpartyUID)
|
||
: type === 'income'
|
||
? t('coinseller_center.stock_transfer', 'Stock transfer')
|
||
: t('coinseller_center.transfer_out', 'Transfer out'));
|
||
var uid =
|
||
counterpartyUID ||
|
||
(type === 'income' ? t('coinseller_center.system', 'System') : '-');
|
||
var transferAmount = normalizeTransferAmount(item);
|
||
return {
|
||
id:
|
||
firstValue(item, ['transaction_id', 'transactionId']) ||
|
||
String(firstValue(item, ['entry_id', 'entryId']) || index),
|
||
type: type,
|
||
name: name,
|
||
uid: uid,
|
||
avatar:
|
||
counterpartyAvatar ||
|
||
avatarData(
|
||
name,
|
||
type === 'income' ? '#e7f7f3' : '#dbc8ff',
|
||
type === 'income' ? '#31a88f' : '#7d57c7'
|
||
),
|
||
coins: Math.abs(delta),
|
||
amount: transferAmount,
|
||
currency: normalizeTransferCurrency(item, transferAmount),
|
||
time: timeLabelFromMS(
|
||
firstValue(item, ['created_at_ms', 'createdAtMs'])
|
||
),
|
||
};
|
||
}
|
||
|
||
function normalizeTransactions(payload) {
|
||
var source = unwrapData(payload) || {};
|
||
var items = Array.isArray(source.items)
|
||
? source.items
|
||
: Array.isArray(source.transactions)
|
||
? source.transactions
|
||
: Array.isArray(source)
|
||
? source
|
||
: [];
|
||
return items.map(function (item, index) {
|
||
return normalizeTransactionRecord(item || {}, index);
|
||
});
|
||
}
|
||
|
||
function normalizeSubSellerPermission(payload) {
|
||
var source = unwrapData(payload) || {};
|
||
var canInvite = booleanValue(
|
||
firstValue(source, [
|
||
'can_invite_sub_seller',
|
||
'canInviteSubSeller',
|
||
'can_open_sub_seller',
|
||
'canOpenSubSeller',
|
||
'can_invite',
|
||
'canInvite',
|
||
])
|
||
);
|
||
var canTransfer = booleanValue(
|
||
firstValue(source, [
|
||
'can_transfer_sub_seller',
|
||
'canTransferSubSeller',
|
||
'can_transfer',
|
||
'canTransfer',
|
||
])
|
||
);
|
||
var canList = booleanValue(
|
||
firstValue(source, [
|
||
'can_list_sub_sellers',
|
||
'canListSubSellers',
|
||
'can_list',
|
||
'canList',
|
||
])
|
||
);
|
||
var explicitVisible = firstValue(source, [
|
||
'visible',
|
||
'enabled',
|
||
'allowed',
|
||
'has_permission',
|
||
'hasPermission',
|
||
]);
|
||
return {
|
||
visible:
|
||
explicitVisible === '' || explicitVisible === undefined
|
||
? canInvite || canTransfer || canList
|
||
: booleanValue(explicitVisible),
|
||
canInvite: canInvite,
|
||
canTransfer: canTransfer,
|
||
};
|
||
}
|
||
|
||
function normalizeSubSeller(item, index) {
|
||
var source = item || {};
|
||
var displayID =
|
||
firstValue(source, [
|
||
'display_user_id',
|
||
'displayUserID',
|
||
'displayUserId',
|
||
'display_id',
|
||
'displayId',
|
||
]) || '';
|
||
var userID = firstValue(source, ['user_id', 'userId', 'id']) || '';
|
||
var name =
|
||
firstValue(source, ['username', 'nickname', 'name']) ||
|
||
t('coinseller_center.user_name_template', 'User {id}').replace(
|
||
'{id}',
|
||
displayID || userID || index + 1
|
||
);
|
||
return {
|
||
uid: displayID || userID,
|
||
userId: userID,
|
||
name: name,
|
||
avatar:
|
||
firstValue(source, [
|
||
'avatar',
|
||
'avatar_url',
|
||
'avatarUrl',
|
||
'profile_picture_url',
|
||
'profilePictureUrl',
|
||
]) || '',
|
||
status: firstValue(source, ['status']) || '',
|
||
balance: toNumber(
|
||
firstValue(source, [
|
||
'coin_seller_balance',
|
||
'coinSellerBalance',
|
||
'available_amount',
|
||
'availableAmount',
|
||
'balance',
|
||
'amount',
|
||
])
|
||
),
|
||
region:
|
||
firstValue(source, [
|
||
'country_display_name',
|
||
'countryDisplayName',
|
||
'region_name',
|
||
'regionName',
|
||
'country_name',
|
||
'countryName',
|
||
]) || '',
|
||
};
|
||
}
|
||
|
||
function normalizeSubSellers(payload) {
|
||
var source = unwrapData(payload) || {};
|
||
var items = Array.isArray(source.items)
|
||
? source.items
|
||
: Array.isArray(source.sub_sellers)
|
||
? source.sub_sellers
|
||
: Array.isArray(source.subSellers)
|
||
? source.subSellers
|
||
: Array.isArray(source.children)
|
||
? source.children
|
||
: Array.isArray(source)
|
||
? source
|
||
: [];
|
||
return items
|
||
.map(function (item, index) {
|
||
return normalizeSubSeller(item, index);
|
||
})
|
||
.filter(function (item) {
|
||
var status = String(item.status || '').toLowerCase();
|
||
// 列表接口的服务端契约只返回 active 直属关系;这里再过滤一次,避免异常数据把 pending 申请展示成可转账子币商。
|
||
return !status || status === 'active';
|
||
});
|
||
}
|
||
|
||
function applyMockData() {
|
||
data.profile = Object.assign({}, mock.profile);
|
||
data.balance = Object.assign({}, mock.balance);
|
||
data.users = mock.users.slice();
|
||
data.quickAmounts = mock.quickAmounts.slice();
|
||
data.records = mock.records.slice();
|
||
data.subSellerPermission = Object.assign({}, mock.subSellerPermission);
|
||
data.subSellers = mock.subSellers.slice();
|
||
}
|
||
|
||
function applyEmptyData() {
|
||
data.profile = emptyProfile();
|
||
data.balance = { coins: 0 };
|
||
data.users = [];
|
||
data.quickAmounts = mock.quickAmounts.slice();
|
||
data.records = [];
|
||
data.subSellerPermission = emptySubSellerPermission();
|
||
data.subSellers = [];
|
||
}
|
||
|
||
function loadSubSellerCapability() {
|
||
if (!hasSubSellerAPI()) {
|
||
data.subSellerPermission = emptySubSellerPermission();
|
||
data.subSellers = [];
|
||
return Promise.resolve();
|
||
}
|
||
state.subSellerLoading = true;
|
||
renderSubSellers();
|
||
return window.HyAppAPI.wallet
|
||
.getCoinSellerSubSellerPermission()
|
||
.then(function (response) {
|
||
data.subSellerPermission =
|
||
normalizeSubSellerPermission(response);
|
||
// 子币商列表属于权限增强区;只有服务端明确给到能力时才拉取,避免普通币商看到空功能入口。
|
||
if (!data.subSellerPermission.visible) {
|
||
data.subSellers = [];
|
||
return null;
|
||
}
|
||
return window.HyAppAPI.wallet
|
||
.listCoinSellerSubSellers({
|
||
page: 1,
|
||
page_size: 50,
|
||
})
|
||
.then(function (listResponse) {
|
||
data.subSellers = normalizeSubSellers(listResponse);
|
||
});
|
||
})
|
||
.catch(function (error) {
|
||
if (window.console && window.console.warn) {
|
||
window.console.warn(
|
||
'[coinseller-center] sub seller api failed',
|
||
error
|
||
);
|
||
}
|
||
data.subSellerPermission = emptySubSellerPermission();
|
||
data.subSellers = [];
|
||
})
|
||
.finally(function () {
|
||
state.subSellerLoading = false;
|
||
renderSubSellers();
|
||
});
|
||
}
|
||
|
||
function loadRealData() {
|
||
if (!hasAPI()) {
|
||
toast(
|
||
t(
|
||
'coinseller_center.load_failed',
|
||
'Load failed. Try again later.'
|
||
)
|
||
);
|
||
return Promise.resolve();
|
||
}
|
||
state.loading = true;
|
||
render();
|
||
return Promise.all([
|
||
window.HyAppAPI.user.me(),
|
||
window.HyAppAPI.wallet.balances(SELLER_ASSET_TYPE),
|
||
window.HyAppAPI.wallet.transactions
|
||
? window.HyAppAPI.wallet
|
||
.transactions({
|
||
asset_type: SELLER_ASSET_TYPE,
|
||
page: 1,
|
||
page_size: 20,
|
||
})
|
||
.catch(function (error) {
|
||
if (window.console && window.console.warn) {
|
||
window.console.warn(
|
||
'[coinseller-center] records api failed',
|
||
error
|
||
);
|
||
}
|
||
return null;
|
||
})
|
||
: Promise.resolve(null),
|
||
loadSubSellerCapability(),
|
||
])
|
||
.then(function (results) {
|
||
data.profile = normalizeProfile(results[0]);
|
||
data.balance = normalizeBalance(results[1]);
|
||
data.records = normalizeTransactions(results[2]);
|
||
})
|
||
.catch(function (error) {
|
||
if (window.console && window.console.warn) {
|
||
window.console.warn(
|
||
'[coinseller-center] api failed',
|
||
error
|
||
);
|
||
}
|
||
toast(
|
||
t(
|
||
'coinseller_center.load_failed',
|
||
'Load failed. Try again later.'
|
||
)
|
||
);
|
||
})
|
||
.finally(function () {
|
||
state.loading = false;
|
||
render();
|
||
});
|
||
}
|
||
|
||
function renderProfile() {
|
||
var image = $('profileAvatar');
|
||
var fallback = $('profileAvatarFallback');
|
||
var profile = data.profile;
|
||
var loading = !!state.loading;
|
||
|
||
setSkeleton($('profileName'), loading, profile.name || '');
|
||
setSkeleton(
|
||
$('profileUID'),
|
||
loading,
|
||
profile.uid
|
||
? t('coinseller_center.uid_prefix', 'UID: {id}').replace(
|
||
'{id}',
|
||
profile.uid
|
||
)
|
||
: ''
|
||
);
|
||
fallback.classList.toggle('avatar-skeleton', loading);
|
||
if (loading) {
|
||
image.hidden = true;
|
||
image.removeAttribute('src');
|
||
fallback.hidden = false;
|
||
fallback.textContent = '';
|
||
return;
|
||
}
|
||
|
||
setImage(image, fallback, profile.avatar, profile.name || profile.uid);
|
||
}
|
||
|
||
function renderBalance() {
|
||
setSkeleton($('coinBalance'), state.loading, count(data.balance.coins));
|
||
var coinIcon = document.querySelector('.balance-value .coin-icon');
|
||
if (coinIcon) {
|
||
coinIcon.classList.toggle('skeleton-coin', !!state.loading);
|
||
}
|
||
}
|
||
|
||
function createSubSellerRow(item) {
|
||
var row = document.createElement('article');
|
||
var avatar = document.createElement('div');
|
||
var main = document.createElement('div');
|
||
var title = document.createElement('div');
|
||
var meta = document.createElement('div');
|
||
var balance = document.createElement('div');
|
||
var action = document.createElement('button');
|
||
|
||
row.className = 'sub-seller-row';
|
||
avatar.className = 'record-avatar';
|
||
main.className = 'sub-seller-main';
|
||
title.className = 'record-title';
|
||
meta.className = 'record-meta';
|
||
balance.className = 'sub-seller-balance';
|
||
action.className = 'sub-seller-transfer';
|
||
action.type = 'button';
|
||
action.disabled =
|
||
!data.subSellerPermission.canTransfer ||
|
||
state.subSellerTransferSubmitting;
|
||
action.textContent = t('coinseller_center.transfer', 'Transfer');
|
||
|
||
renderAvatar(avatar, item);
|
||
title.textContent = item.name || '-';
|
||
meta.textContent = t(
|
||
'coinseller_center.uid_prefix',
|
||
'UID: {id}'
|
||
).replace('{id}', item.uid || '-');
|
||
balance.textContent =
|
||
t('coinseller_center.sub_seller_wallet', 'Wallet') +
|
||
': ' +
|
||
count(item.balance) +
|
||
' ' +
|
||
t('coinseller_center.coins_lower', 'coins');
|
||
action.addEventListener('click', function () {
|
||
openSubSellerTransfer(item);
|
||
});
|
||
|
||
main.appendChild(title);
|
||
main.appendChild(meta);
|
||
if (item.balance > 0) main.appendChild(balance);
|
||
row.appendChild(avatar);
|
||
row.appendChild(main);
|
||
if (data.subSellerPermission.canTransfer) row.appendChild(action);
|
||
return row;
|
||
}
|
||
|
||
function renderSubSellers() {
|
||
var section = $('subSellerSection');
|
||
if (!section) return;
|
||
var permission = data.subSellerPermission || emptySubSellerPermission();
|
||
section.hidden = !permission.visible;
|
||
$('inviteSubSellerButton').hidden = !permission.canInvite;
|
||
if (!permission.visible) return;
|
||
|
||
if (state.subSellerLoading) {
|
||
setStatus(
|
||
$('subSellerStatus'),
|
||
t('coinseller_center.loading', 'Loading...'),
|
||
''
|
||
);
|
||
} else {
|
||
setStatus(
|
||
$('subSellerStatus'),
|
||
data.subSellers.length
|
||
? ''
|
||
: t(
|
||
'coinseller_center.no_sub_sellers',
|
||
'No sub coin sellers yet.'
|
||
),
|
||
data.subSellers.length ? '' : 'warning'
|
||
);
|
||
}
|
||
replaceChildren(
|
||
$('subSellerList'),
|
||
data.subSellers.map(function (item) {
|
||
return createSubSellerRow(item);
|
||
})
|
||
);
|
||
}
|
||
|
||
function renderQuickAmounts() {
|
||
replaceChildren(
|
||
$('quickAmounts'),
|
||
data.quickAmounts.map(function (amount) {
|
||
var button = document.createElement('button');
|
||
button.className =
|
||
'quick-amount' +
|
||
(String(amount) === state.coinAmount ? ' is-active' : '');
|
||
button.type = 'button';
|
||
button.textContent = count(amount);
|
||
button.addEventListener('click', function () {
|
||
state.coinAmount = String(amount);
|
||
$('coinAmountInput').value = state.coinAmount;
|
||
setStatus($('rechargeStatus'), '', '');
|
||
renderRechargeState();
|
||
});
|
||
return button;
|
||
})
|
||
);
|
||
}
|
||
|
||
function renderTarget() {
|
||
var card = $('targetCard');
|
||
if (!state.target) {
|
||
card.hidden = true;
|
||
$('rechargeTargetName').textContent = '-';
|
||
return;
|
||
}
|
||
card.hidden = false;
|
||
renderAvatar($('targetAvatar'), state.target);
|
||
$('targetName').textContent = state.target.name;
|
||
$('targetUID').textContent = t(
|
||
'coinseller_center.uid_prefix',
|
||
'UID: {id}'
|
||
).replace('{id}', state.target.uid);
|
||
$('rechargeTargetName').textContent =
|
||
t('coinseller_center.target_user', 'Target user') +
|
||
': ' +
|
||
state.target.name;
|
||
}
|
||
|
||
function parsedCoinAmount() {
|
||
var input = $('coinAmountInput');
|
||
if (input) {
|
||
var sanitized = String(input.value || '').replace(/[^\d]/g, '');
|
||
if (input.value !== sanitized) input.value = sanitized;
|
||
state.coinAmount = sanitized;
|
||
}
|
||
return Number(state.coinAmount || 0);
|
||
}
|
||
|
||
function renderRechargeState() {
|
||
var amount = parsedCoinAmount();
|
||
var disabled =
|
||
state.loading ||
|
||
state.submitting ||
|
||
state.searching ||
|
||
!state.target ||
|
||
amount <= 0;
|
||
$('rechargeButton').disabled = disabled;
|
||
$('searchButton').disabled = state.searching;
|
||
renderQuickAmounts();
|
||
}
|
||
|
||
function recordMatchesQuery(record) {
|
||
if (!state.historyQuery) return true;
|
||
var keyword = state.historyQuery.toLowerCase();
|
||
return (
|
||
String(record.name || '')
|
||
.toLowerCase()
|
||
.indexOf(keyword) >= 0 ||
|
||
String(record.uid || '')
|
||
.toLowerCase()
|
||
.indexOf(keyword) >= 0
|
||
);
|
||
}
|
||
|
||
function visibleHistoryRecords() {
|
||
return data.records
|
||
.filter(function (record) {
|
||
return (
|
||
record.type === state.historyTab &&
|
||
recordMatchesQuery(record)
|
||
);
|
||
})
|
||
.slice(0, state.historyLimit);
|
||
}
|
||
|
||
function createRecordRow(record) {
|
||
var row = document.createElement('article');
|
||
var avatar = document.createElement('div');
|
||
var main = document.createElement('div');
|
||
var title = document.createElement('div');
|
||
var metaGroup = document.createElement('div');
|
||
var meta = document.createElement('div');
|
||
var transfer = document.createElement('span');
|
||
var side = document.createElement('div');
|
||
var amount = document.createElement('strong');
|
||
var time = document.createElement('span');
|
||
|
||
row.className = 'record-row';
|
||
avatar.className = 'record-avatar';
|
||
main.className = 'record-main';
|
||
title.className = 'record-title';
|
||
metaGroup.className = 'record-meta-group';
|
||
meta.className = 'record-meta';
|
||
transfer.className = 'record-transfer';
|
||
side.className =
|
||
'record-side' + (record.type === 'income' ? ' is-income' : '');
|
||
|
||
renderAvatar(avatar, record);
|
||
title.textContent = record.name || '-';
|
||
meta.textContent = t(
|
||
'coinseller_center.uid_prefix',
|
||
'UID: {id}'
|
||
).replace('{id}', record.uid || '-');
|
||
transfer.textContent = formatTransferAmount(
|
||
record.amount,
|
||
record.currency
|
||
);
|
||
amount.textContent =
|
||
(record.type === 'income' ? '+' : '-') +
|
||
count(record.coins) +
|
||
' ' +
|
||
t('coinseller_center.coins_lower', 'coins');
|
||
time.textContent = record.time || '';
|
||
|
||
main.appendChild(title);
|
||
metaGroup.appendChild(meta);
|
||
if (transfer.textContent) metaGroup.appendChild(transfer);
|
||
main.appendChild(metaGroup);
|
||
side.appendChild(amount);
|
||
side.appendChild(time);
|
||
row.appendChild(avatar);
|
||
row.appendChild(main);
|
||
row.appendChild(side);
|
||
return row;
|
||
}
|
||
|
||
function renderRecent() {
|
||
replaceChildren(
|
||
$('recentList'),
|
||
data.records.slice(0, 3).map(function (record) {
|
||
return createRecordRow(record);
|
||
})
|
||
);
|
||
}
|
||
|
||
function renderHistory() {
|
||
document.querySelectorAll('[data-history-tab]').forEach(function (tab) {
|
||
var active =
|
||
tab.getAttribute('data-history-tab') === state.historyTab;
|
||
tab.classList.toggle('is-active', active);
|
||
tab.setAttribute('aria-selected', String(active));
|
||
});
|
||
|
||
var all = data.records.filter(function (record) {
|
||
return (
|
||
record.type === state.historyTab && recordMatchesQuery(record)
|
||
);
|
||
});
|
||
var records = visibleHistoryRecords();
|
||
$('loadMoreButton').hidden = records.length >= all.length;
|
||
setStatus(
|
||
$('historyStatus'),
|
||
all.length
|
||
? ''
|
||
: t(
|
||
'coinseller_center.no_transaction_records',
|
||
'No transaction records'
|
||
),
|
||
all.length ? '' : 'warning'
|
||
);
|
||
replaceChildren(
|
||
$('historyList'),
|
||
records.map(function (record) {
|
||
return createRecordRow(record);
|
||
})
|
||
);
|
||
}
|
||
|
||
function findMockUser(keyword) {
|
||
var normalized = String(keyword || '')
|
||
.trim()
|
||
.toLowerCase();
|
||
return data.users.find(function (user) {
|
||
return (
|
||
String(user.uid || '')
|
||
.toLowerCase()
|
||
.indexOf(normalized) >= 0 ||
|
||
String(user.name || '')
|
||
.toLowerCase()
|
||
.indexOf(normalized) >= 0
|
||
);
|
||
});
|
||
}
|
||
|
||
function findUser(keyword) {
|
||
if (state.isMock) {
|
||
return Promise.resolve(findMockUser(keyword) || null);
|
||
}
|
||
if (!hasAPI() || !window.HyAppAPI.user.resolveDisplayUserID) {
|
||
return Promise.reject(new Error('api_not_ready'));
|
||
}
|
||
return window.HyAppAPI.user
|
||
.resolveDisplayUserID(keyword)
|
||
.then(function (payload) {
|
||
return normalizeResolvedUser(payload, keyword);
|
||
});
|
||
}
|
||
|
||
function handleSearch(value) {
|
||
state.searching = true;
|
||
setStatus(
|
||
$('searchStatus'),
|
||
t('coinseller_center.loading', 'Loading...'),
|
||
''
|
||
);
|
||
renderRechargeState();
|
||
findUser(value)
|
||
.then(function (user) {
|
||
if (!user) {
|
||
state.target = null;
|
||
renderTarget();
|
||
renderRechargeState();
|
||
setStatus(
|
||
$('searchStatus'),
|
||
t(
|
||
'coinseller_center.user_not_found',
|
||
'User not found.'
|
||
),
|
||
'warning'
|
||
);
|
||
return;
|
||
}
|
||
state.target = user;
|
||
renderTarget();
|
||
renderRechargeState();
|
||
setStatus($('searchStatus'), '', '');
|
||
})
|
||
.catch(function (error) {
|
||
if (window.console && window.console.warn) {
|
||
window.console.warn(
|
||
'[coinseller-center] user search failed',
|
||
error
|
||
);
|
||
}
|
||
state.target = null;
|
||
renderTarget();
|
||
renderRechargeState();
|
||
setStatus(
|
||
$('searchStatus'),
|
||
t('coinseller_center.user_not_found', 'User not found.'),
|
||
'warning'
|
||
);
|
||
})
|
||
.finally(function () {
|
||
state.searching = false;
|
||
renderRechargeState();
|
||
});
|
||
}
|
||
|
||
function commandID() {
|
||
return [
|
||
'coin-seller-transfer',
|
||
Date.now(),
|
||
Math.random().toString(36).slice(2, 8),
|
||
].join('-');
|
||
}
|
||
|
||
function subSellerCommandID(action) {
|
||
return [
|
||
'coin-seller-sub-seller',
|
||
action || 'action',
|
||
Date.now(),
|
||
Math.random().toString(36).slice(2, 8),
|
||
].join('-');
|
||
}
|
||
|
||
function buildTransferPayload(amount) {
|
||
return {
|
||
command_id: commandID(),
|
||
target_display_user_id: String(state.target.uid || '').trim(),
|
||
amount: amount,
|
||
reason: 'player recharge',
|
||
};
|
||
}
|
||
|
||
function appendTransferRecord(amount, response) {
|
||
var payload = unwrapData(response) || {};
|
||
var coins = toNumber(
|
||
firstValue(payload, ['amount', 'coin_amount', 'coinAmount'])
|
||
);
|
||
var transferAmount = normalizeTransferAmount(payload);
|
||
data.records.unshift({
|
||
id:
|
||
firstValue(payload, ['transaction_id', 'transactionId']) ||
|
||
'tx-' + Date.now(),
|
||
type: 'expenditure',
|
||
name: state.target.name,
|
||
uid: state.target.uid,
|
||
avatar: state.target.avatar,
|
||
coins: coins || amount,
|
||
amount: transferAmount,
|
||
currency: normalizeTransferCurrency(payload, transferAmount),
|
||
time: currentTimeLabel(),
|
||
});
|
||
}
|
||
|
||
function appendSubSellerTransferRecord(amount, response) {
|
||
var target = state.subSellerTransferTarget;
|
||
if (!target) return;
|
||
var payload = unwrapData(response) || {};
|
||
var coins = toNumber(
|
||
firstValue(payload, ['amount', 'coin_amount', 'coinAmount'])
|
||
);
|
||
data.records.unshift({
|
||
id:
|
||
firstValue(payload, ['transaction_id', 'transactionId']) ||
|
||
'sub-seller-tx-' + Date.now(),
|
||
type: 'expenditure',
|
||
name: target.name,
|
||
uid: target.uid,
|
||
avatar: target.avatar,
|
||
coins: coins || amount,
|
||
amount: 0,
|
||
currency: '',
|
||
time: currentTimeLabel(),
|
||
});
|
||
}
|
||
|
||
function refreshSubSellerFromResponse(target, payload, amount) {
|
||
if (!target) return;
|
||
var balanceAfter = firstValue(payload, [
|
||
'sub_seller_balance_after',
|
||
'subSellerBalanceAfter',
|
||
'child_balance_after',
|
||
'childBalanceAfter',
|
||
'target_balance_after',
|
||
'targetBalanceAfter',
|
||
]);
|
||
data.subSellers = data.subSellers.map(function (item) {
|
||
if (String(item.uid) !== String(target.uid)) return item;
|
||
return Object.assign({}, item, {
|
||
balance:
|
||
balanceAfter !== '' && balanceAfter !== undefined
|
||
? toNumber(balanceAfter)
|
||
: item.balance + amount,
|
||
});
|
||
});
|
||
}
|
||
|
||
function inviteSubSeller() {
|
||
var input = $('inviteSubSellerInput');
|
||
var displayID = String(input.value || '').trim();
|
||
if (!data.subSellerPermission.canInvite) {
|
||
setStatus(
|
||
$('inviteSubSellerStatus'),
|
||
t('coinseller_center.invite_failed', 'Invite failed.'),
|
||
'warning'
|
||
);
|
||
return;
|
||
}
|
||
if (!displayID) {
|
||
setStatus(
|
||
$('inviteSubSellerStatus'),
|
||
t('coinseller_center.enter_user_id', 'Enter user ID'),
|
||
'warning'
|
||
);
|
||
return;
|
||
}
|
||
if (state.isMock) {
|
||
input.value = '';
|
||
closeModal('inviteSubSellerModal');
|
||
toast(
|
||
t(
|
||
'coinseller_center.invite_success',
|
||
'Official will review your invitation within 24 hours.'
|
||
)
|
||
);
|
||
return;
|
||
}
|
||
if (!hasAPI() || !window.HyAppAPI.wallet.inviteCoinSellerSubSeller) {
|
||
setStatus(
|
||
$('inviteSubSellerStatus'),
|
||
t('coinseller_center.invite_failed', 'Invite failed.'),
|
||
'warning'
|
||
);
|
||
return;
|
||
}
|
||
|
||
state.invitingSubSeller = true;
|
||
$('submitInviteSubSellerButton').disabled = true;
|
||
setStatus(
|
||
$('inviteSubSellerStatus'),
|
||
t('coinseller_center.loading', 'Loading...'),
|
||
''
|
||
);
|
||
// 邀请子币商现在只创建待审核申请;command_id 保证移动端重试不会重复生成多个 pending 申请。
|
||
window.HyAppAPI.wallet
|
||
.inviteCoinSellerSubSeller({
|
||
command_id: subSellerCommandID('invite'),
|
||
target_display_user_id: displayID,
|
||
})
|
||
.then(function (response) {
|
||
input.value = '';
|
||
setStatus(
|
||
$('inviteSubSellerStatus'),
|
||
t(
|
||
'coinseller_center.invite_success',
|
||
'Official will review your invitation within 24 hours.'
|
||
),
|
||
'success'
|
||
);
|
||
// 申请提交成功不代表父子关系已经生效,列表必须继续以服务端 active 关系为准。
|
||
toast(
|
||
t(
|
||
'coinseller_center.invite_success',
|
||
'Official will review your invitation within 24 hours.'
|
||
)
|
||
);
|
||
closeModal('inviteSubSellerModal');
|
||
})
|
||
.catch(function (error) {
|
||
if (window.console && window.console.warn) {
|
||
window.console.warn(
|
||
'[coinseller-center] invite sub seller failed',
|
||
error
|
||
);
|
||
}
|
||
setStatus(
|
||
$('inviteSubSellerStatus'),
|
||
error && error.message
|
||
? error.message
|
||
: t(
|
||
'coinseller_center.invite_failed',
|
||
'Invite failed.'
|
||
),
|
||
'warning'
|
||
);
|
||
})
|
||
.finally(function () {
|
||
state.invitingSubSeller = false;
|
||
$('submitInviteSubSellerButton').disabled = false;
|
||
});
|
||
}
|
||
|
||
function openSubSellerTransfer(item) {
|
||
state.subSellerTransferTarget = item;
|
||
state.subSellerTransferAmount = '';
|
||
$('subSellerTransferAmountInput').value = '';
|
||
$('subSellerTransferTargetName').textContent =
|
||
t(
|
||
'coinseller_center.transfer_to_sub_seller',
|
||
'Transfer to sub seller'
|
||
) +
|
||
': ' +
|
||
item.name;
|
||
setStatus($('subSellerTransferStatus'), '', '');
|
||
renderSubSellerTransferState();
|
||
openModal('subSellerTransferModal');
|
||
window.setTimeout(function () {
|
||
$('subSellerTransferAmountInput').focus();
|
||
}, 80);
|
||
}
|
||
|
||
function parsedSubSellerTransferAmount() {
|
||
var input = $('subSellerTransferAmountInput');
|
||
if (input) {
|
||
var sanitized = String(input.value || '').replace(/[^\d]/g, '');
|
||
if (input.value !== sanitized) input.value = sanitized;
|
||
state.subSellerTransferAmount = sanitized;
|
||
}
|
||
return Number(state.subSellerTransferAmount || 0);
|
||
}
|
||
|
||
function renderSubSellerTransferState() {
|
||
var amount = parsedSubSellerTransferAmount();
|
||
$('submitSubSellerTransferButton').disabled =
|
||
state.loading ||
|
||
state.subSellerTransferSubmitting ||
|
||
!state.subSellerTransferTarget ||
|
||
amount <= 0;
|
||
}
|
||
|
||
function transferToSubSeller() {
|
||
var amount = parsedSubSellerTransferAmount();
|
||
var target = state.subSellerTransferTarget;
|
||
if (!target) {
|
||
setStatus(
|
||
$('subSellerTransferStatus'),
|
||
t(
|
||
'coinseller_center.select_sub_seller_first',
|
||
'Select a sub coin seller first.'
|
||
),
|
||
'warning'
|
||
);
|
||
return;
|
||
}
|
||
if (amount <= 0) {
|
||
setStatus(
|
||
$('subSellerTransferStatus'),
|
||
t(
|
||
'coinseller_center.valid_amount_required',
|
||
'Please enter a valid amount.'
|
||
),
|
||
'warning'
|
||
);
|
||
return;
|
||
}
|
||
if (amount > data.balance.coins) {
|
||
setStatus(
|
||
$('subSellerTransferStatus'),
|
||
t(
|
||
'coinseller_center.amount_exceeds_stock',
|
||
'Amount exceeds stock.'
|
||
),
|
||
'warning'
|
||
);
|
||
return;
|
||
}
|
||
if (state.isMock) {
|
||
data.balance.coins -= amount;
|
||
refreshSubSellerFromResponse(target, {}, amount);
|
||
appendSubSellerTransferRecord(amount, {});
|
||
finishSubSellerTransfer();
|
||
return;
|
||
}
|
||
if (
|
||
!hasAPI() ||
|
||
!window.HyAppAPI.wallet.transferCoinSellerToSubSeller
|
||
) {
|
||
setStatus($('subSellerTransferStatus'), '', '');
|
||
toast(t('coinseller_center.transfer_failed', 'Transfer failed.'));
|
||
return;
|
||
}
|
||
|
||
state.subSellerTransferSubmitting = true;
|
||
setStatus(
|
||
$('subSellerTransferStatus'),
|
||
t('coinseller_center.loading', 'Loading...'),
|
||
''
|
||
);
|
||
renderSubSellerTransferState();
|
||
// 子币商转账目标是对方的币商专用钱包,不是玩家普通金币;payload 单独命名,避免误走老的玩家充值接口。
|
||
window.HyAppAPI.wallet
|
||
.transferCoinSellerToSubSeller({
|
||
command_id: subSellerCommandID('transfer'),
|
||
target_display_user_id: String(target.uid || '').trim(),
|
||
amount: amount,
|
||
reason: 'sub coin seller stock transfer',
|
||
})
|
||
.then(function (response) {
|
||
var payload = unwrapData(response) || {};
|
||
var balanceAfter = firstValue(payload, [
|
||
'parent_balance_after',
|
||
'parentBalanceAfter',
|
||
'seller_balance_after',
|
||
'sellerBalanceAfter',
|
||
]);
|
||
data.balance.coins =
|
||
balanceAfter !== '' && balanceAfter !== undefined
|
||
? toNumber(balanceAfter)
|
||
: data.balance.coins - amount;
|
||
refreshSubSellerFromResponse(target, payload, amount);
|
||
appendSubSellerTransferRecord(amount, response);
|
||
finishSubSellerTransfer();
|
||
})
|
||
.catch(function (error) {
|
||
if (window.console && window.console.warn) {
|
||
window.console.warn(
|
||
'[coinseller-center] sub seller transfer failed',
|
||
error
|
||
);
|
||
}
|
||
setStatus($('subSellerTransferStatus'), '', '');
|
||
toast(
|
||
error && error.message
|
||
? error.message
|
||
: t(
|
||
'coinseller_center.transfer_failed',
|
||
'Transfer failed.'
|
||
)
|
||
);
|
||
})
|
||
.finally(function () {
|
||
state.subSellerTransferSubmitting = false;
|
||
renderSubSellerTransferState();
|
||
renderSubSellers();
|
||
});
|
||
}
|
||
|
||
function rechargeNow() {
|
||
var amount = parsedCoinAmount();
|
||
if (!state.target) {
|
||
setStatus(
|
||
$('rechargeStatus'),
|
||
t(
|
||
'coinseller_center.select_user_first',
|
||
'Select a user first.'
|
||
),
|
||
'warning'
|
||
);
|
||
return;
|
||
}
|
||
if (amount <= 0) {
|
||
setStatus(
|
||
$('rechargeStatus'),
|
||
t(
|
||
'coinseller_center.valid_amount_required',
|
||
'Please enter a valid amount.'
|
||
),
|
||
'warning'
|
||
);
|
||
return;
|
||
}
|
||
if (amount > data.balance.coins) {
|
||
setStatus(
|
||
$('rechargeStatus'),
|
||
t(
|
||
'coinseller_center.amount_exceeds_stock',
|
||
'Amount exceeds stock.'
|
||
),
|
||
'warning'
|
||
);
|
||
return;
|
||
}
|
||
if (state.isMock) {
|
||
data.balance.coins -= amount;
|
||
appendTransferRecord(amount, {});
|
||
finishRecharge();
|
||
return;
|
||
}
|
||
if (!hasAPI() || !window.HyAppAPI.wallet.transferCoinFromSeller) {
|
||
setStatus($('rechargeStatus'), '', '');
|
||
toast(t('coinseller_center.transfer_failed', 'Transfer failed.'));
|
||
return;
|
||
}
|
||
|
||
state.submitting = true;
|
||
setStatus(
|
||
$('rechargeStatus'),
|
||
t('coinseller_center.loading', 'Loading...'),
|
||
''
|
||
);
|
||
renderRechargeState();
|
||
window.HyAppAPI.wallet
|
||
.transferCoinFromSeller(buildTransferPayload(amount))
|
||
.then(function (response) {
|
||
var payload = unwrapData(response) || {};
|
||
var balanceAfter = toNumber(
|
||
firstValue(payload, [
|
||
'seller_balance_after',
|
||
'sellerBalanceAfter',
|
||
])
|
||
);
|
||
if (balanceAfter > 0 || payload.seller_balance_after === 0) {
|
||
data.balance.coins = balanceAfter;
|
||
} else {
|
||
data.balance.coins -= amount;
|
||
}
|
||
appendTransferRecord(amount, response);
|
||
finishRecharge();
|
||
})
|
||
.catch(function (error) {
|
||
if (window.console && window.console.warn) {
|
||
window.console.warn(
|
||
'[coinseller-center] transfer failed',
|
||
error
|
||
);
|
||
}
|
||
setStatus($('rechargeStatus'), '', '');
|
||
toast(
|
||
error && error.message
|
||
? error.message
|
||
: t(
|
||
'coinseller_center.transfer_failed',
|
||
'Transfer failed.'
|
||
)
|
||
);
|
||
})
|
||
.finally(function () {
|
||
state.submitting = false;
|
||
renderRechargeState();
|
||
});
|
||
}
|
||
|
||
function finishRecharge() {
|
||
state.coinAmount = '';
|
||
$('coinAmountInput').value = '';
|
||
setStatus(
|
||
$('rechargeStatus'),
|
||
t('coinseller_center.recharge_success', 'Recharge success.'),
|
||
'success'
|
||
);
|
||
renderBalance();
|
||
renderRecent();
|
||
renderRechargeState();
|
||
if (!$('historyModal').hidden) renderHistory();
|
||
closeModal('rechargeModal');
|
||
toast(t('coinseller_center.recharge_success', 'Recharge success.'));
|
||
}
|
||
|
||
function finishSubSellerTransfer() {
|
||
state.subSellerTransferAmount = '';
|
||
$('subSellerTransferAmountInput').value = '';
|
||
setStatus(
|
||
$('subSellerTransferStatus'),
|
||
t('coinseller_center.transfer_success', 'Transfer success.'),
|
||
'success'
|
||
);
|
||
renderBalance();
|
||
renderSubSellers();
|
||
renderRecent();
|
||
renderSubSellerTransferState();
|
||
if (!$('historyModal').hidden) renderHistory();
|
||
closeModal('subSellerTransferModal');
|
||
toast(t('coinseller_center.transfer_success', 'Transfer success.'));
|
||
}
|
||
|
||
function currentTimeLabel() {
|
||
var now = new Date();
|
||
return (
|
||
now.getFullYear() +
|
||
'-' +
|
||
String(now.getMonth() + 1).padStart(2, '0') +
|
||
'-' +
|
||
String(now.getDate()).padStart(2, '0') +
|
||
' ' +
|
||
String(now.getHours()).padStart(2, '0') +
|
||
':' +
|
||
String(now.getMinutes()).padStart(2, '0')
|
||
);
|
||
}
|
||
|
||
function openModal(id) {
|
||
var modal = $(id);
|
||
if (!modal) return;
|
||
modal.hidden = false;
|
||
modal.setAttribute('aria-hidden', 'false');
|
||
document.body.classList.add('modal-open');
|
||
}
|
||
|
||
function closeModal(id) {
|
||
var modal = $(id);
|
||
if (!modal) return;
|
||
modal.hidden = true;
|
||
modal.setAttribute('aria-hidden', 'true');
|
||
var hasOpen = Array.prototype.some.call(
|
||
document.querySelectorAll('.sheet-modal'),
|
||
function (node) {
|
||
return !node.hidden;
|
||
}
|
||
);
|
||
document.body.classList.toggle('modal-open', hasOpen);
|
||
}
|
||
|
||
function bindEvents() {
|
||
$('backButton').addEventListener('click', function () {
|
||
if (window.HyAppBridge) window.HyAppBridge.back();
|
||
});
|
||
$('historyButton').addEventListener('click', function () {
|
||
state.historyLimit = 3;
|
||
renderHistory();
|
||
openModal('historyModal');
|
||
});
|
||
$('viewAllHistoryButton').addEventListener('click', function () {
|
||
state.historyLimit = 3;
|
||
renderHistory();
|
||
openModal('historyModal');
|
||
});
|
||
$('helpButton').addEventListener('click', function () {
|
||
openModal('helpModal');
|
||
});
|
||
$('inviteSubSellerButton').addEventListener('click', function () {
|
||
$('inviteSubSellerInput').value = '';
|
||
setStatus($('inviteSubSellerStatus'), '', '');
|
||
openModal('inviteSubSellerModal');
|
||
window.setTimeout(function () {
|
||
$('inviteSubSellerInput').focus();
|
||
}, 80);
|
||
});
|
||
document
|
||
.querySelectorAll('[data-close-modal]')
|
||
.forEach(function (node) {
|
||
node.addEventListener('click', function () {
|
||
closeModal(node.getAttribute('data-close-modal'));
|
||
});
|
||
});
|
||
$('searchForm').addEventListener('submit', function (event) {
|
||
event.preventDefault();
|
||
var value = $('userSearchInput').value.trim();
|
||
if (!value) {
|
||
setStatus(
|
||
$('searchStatus'),
|
||
t('coinseller_center.enter_user_id', 'Enter user ID'),
|
||
'warning'
|
||
);
|
||
return;
|
||
}
|
||
handleSearch(value);
|
||
});
|
||
$('openRechargeButton').addEventListener('click', function () {
|
||
if (!state.target) return;
|
||
state.coinAmount = '';
|
||
$('coinAmountInput').value = '';
|
||
setStatus($('rechargeStatus'), '', '');
|
||
renderRechargeState();
|
||
openModal('rechargeModal');
|
||
window.setTimeout(function () {
|
||
$('coinAmountInput').focus();
|
||
}, 80);
|
||
});
|
||
$('coinAmountInput').addEventListener('input', function () {
|
||
state.coinAmount = $('coinAmountInput').value.replace(/[^\d]/g, '');
|
||
$('coinAmountInput').value = state.coinAmount;
|
||
setStatus($('rechargeStatus'), '', '');
|
||
renderRechargeState();
|
||
});
|
||
$('rechargeForm').addEventListener('submit', function (event) {
|
||
event.preventDefault();
|
||
rechargeNow();
|
||
});
|
||
$('inviteSubSellerForm').addEventListener('submit', function (event) {
|
||
event.preventDefault();
|
||
inviteSubSeller();
|
||
});
|
||
$('subSellerTransferAmountInput').addEventListener(
|
||
'input',
|
||
function () {
|
||
state.subSellerTransferAmount = $(
|
||
'subSellerTransferAmountInput'
|
||
).value.replace(/[^\d]/g, '');
|
||
$('subSellerTransferAmountInput').value =
|
||
state.subSellerTransferAmount;
|
||
setStatus($('subSellerTransferStatus'), '', '');
|
||
renderSubSellerTransferState();
|
||
}
|
||
);
|
||
$('subSellerTransferForm').addEventListener('submit', function (event) {
|
||
event.preventDefault();
|
||
transferToSubSeller();
|
||
});
|
||
document.querySelectorAll('[data-history-tab]').forEach(function (tab) {
|
||
tab.addEventListener('click', function () {
|
||
state.historyTab = tab.getAttribute('data-history-tab');
|
||
state.historyLimit = 3;
|
||
renderHistory();
|
||
});
|
||
});
|
||
$('historySearchForm').addEventListener('submit', function (event) {
|
||
event.preventDefault();
|
||
state.historyQuery = $('historySearchInput').value.trim();
|
||
state.historyLimit = 3;
|
||
renderHistory();
|
||
});
|
||
$('historyClearButton').addEventListener('click', function () {
|
||
state.historyQuery = '';
|
||
$('historySearchInput').value = '';
|
||
state.historyLimit = 3;
|
||
renderHistory();
|
||
});
|
||
$('loadMoreButton').addEventListener('click', function () {
|
||
state.historyLimit += 3;
|
||
renderHistory();
|
||
});
|
||
document.addEventListener('keydown', function (event) {
|
||
if (event.key !== 'Escape') return;
|
||
closeModal('historyModal');
|
||
closeModal('helpModal');
|
||
closeModal('inviteSubSellerModal');
|
||
closeModal('subSellerTransferModal');
|
||
});
|
||
}
|
||
|
||
function render() {
|
||
renderProfile();
|
||
renderBalance();
|
||
renderQuickAmounts();
|
||
renderTarget();
|
||
renderRechargeState();
|
||
renderSubSellers();
|
||
renderSubSellerTransferState();
|
||
renderRecent();
|
||
}
|
||
|
||
function init() {
|
||
if (state.isMock) {
|
||
applyMockData();
|
||
} else {
|
||
applyEmptyData();
|
||
}
|
||
render();
|
||
bindEvents();
|
||
if (window.HyAppBridge) {
|
||
window.HyAppBridge.ready({
|
||
page: 'coinseller-center',
|
||
mock: state.isMock,
|
||
});
|
||
}
|
||
if (!state.isMock) loadRealData();
|
||
}
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
} else {
|
||
init();
|
||
}
|
||
|
||
window.addEventListener('hyapp:i18n-ready', function () {
|
||
render();
|
||
if (!$('historyModal').hidden) renderHistory();
|
||
});
|
||
})();
|