fix: show coin seller transfer details

This commit is contained in:
zhx 2026-07-01 15:17:30 +08:00
parent f7a3a68a1d
commit d2cc6a3dd4
2 changed files with 173 additions and 47 deletions

View File

@ -158,6 +158,11 @@
return Number.isFinite(number) ? number : 0; return Number.isFinite(number) ? number : 0;
} }
function positiveNumber(value) {
var number = toNumber(value);
return number > 0 ? number : 0;
}
function firstValue(source, keys) { function firstValue(source, keys) {
if (!source) return ''; if (!source) return '';
for (var i = 0; i < keys.length; i += 1) { for (var i = 0; i < keys.length; i += 1) {
@ -210,9 +215,7 @@
function hasAPI() { function hasAPI() {
return ( return (
window.HyAppAPI && window.HyAppAPI && window.HyAppAPI.user && window.HyAppAPI.wallet
window.HyAppAPI.user &&
window.HyAppAPI.wallet
); );
} }
@ -340,6 +343,67 @@
); );
} }
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) { function normalizeResolvedUser(payload, keyword) {
var source = unwrapData(payload) || {}; var source = unwrapData(payload) || {};
var identity = source.identity || source.user_identity || source; var identity = source.identity || source.user_identity || source;
@ -399,7 +463,8 @@
); );
var type = delta >= 0 ? 'income' : 'expenditure'; var type = delta >= 0 ? 'income' : 'expenditure';
var counterpartyUserID = String( var counterpartyUserID = String(
firstValue(item, ['counterparty_user_id', 'counterpartyUserId']) || '' firstValue(item, ['counterparty_user_id', 'counterpartyUserId']) ||
''
); );
var counterpartyDisplayID = String( var counterpartyDisplayID = String(
firstValue(item, [ firstValue(item, [
@ -419,19 +484,21 @@
'counterpartyAvatar', 'counterpartyAvatar',
]); ]);
var counterpartyUID = counterpartyDisplayID || counterpartyUserID; var counterpartyUID = counterpartyDisplayID || counterpartyUserID;
// 入账流水也可能来自某个用户的工资转币商,标题必须优先展示对方资料;只有后台进货这类没有对方用户的流水才展示库存入账。
var name = var name =
type === 'income' counterpartyName ||
? t('coinseller_center.stock_transfer', 'Stock transfer') (counterpartyUID
: counterpartyName || ? t(
(counterpartyUID 'coinseller_center.user_name_template',
? t( 'User {id}'
'coinseller_center.user_name_template', ).replace('{id}', counterpartyUID)
'User {id}' : type === 'income'
).replace('{id}', counterpartyUID) ? t('coinseller_center.stock_transfer', 'Stock transfer')
: t('coinseller_center.transfer_out', 'Transfer out')); : t('coinseller_center.transfer_out', 'Transfer out'));
var uid = var uid =
counterpartyUID || counterpartyUID ||
(type === 'income' ? t('coinseller_center.system', 'System') : '-'); (type === 'income' ? t('coinseller_center.system', 'System') : '-');
var transferAmount = normalizeTransferAmount(item);
return { return {
id: id:
firstValue(item, ['transaction_id', 'transactionId']) || firstValue(item, ['transaction_id', 'transactionId']) ||
@ -439,14 +506,16 @@
type: type, type: type,
name: name, name: name,
uid: uid, uid: uid,
avatar: counterpartyAvatar || avatarData( avatar:
name, counterpartyAvatar ||
type === 'income' ? '#e7f7f3' : '#dbc8ff', avatarData(
type === 'income' ? '#31a88f' : '#7d57c7' name,
), type === 'income' ? '#e7f7f3' : '#dbc8ff',
type === 'income' ? '#31a88f' : '#7d57c7'
),
coins: Math.abs(delta), coins: Math.abs(delta),
amount: 0, amount: transferAmount,
currency: '', currency: normalizeTransferCurrency(item, transferAmount),
time: timeLabelFromMS( time: timeLabelFromMS(
firstValue(item, ['created_at_ms', 'createdAtMs']) firstValue(item, ['created_at_ms', 'createdAtMs'])
), ),
@ -485,7 +554,12 @@
function loadRealData() { function loadRealData() {
if (!hasAPI()) { if (!hasAPI()) {
toast(t('coinseller_center.load_failed', 'Load failed. Try again later.')); toast(
t(
'coinseller_center.load_failed',
'Load failed. Try again later.'
)
);
return Promise.resolve(); return Promise.resolve();
} }
state.loading = true; state.loading = true;
@ -518,7 +592,10 @@
}) })
.catch(function (error) { .catch(function (error) {
if (window.console && window.console.warn) { if (window.console && window.console.warn) {
window.console.warn('[coinseller-center] api failed', error); window.console.warn(
'[coinseller-center] api failed',
error
);
} }
toast( toast(
t( t(
@ -651,7 +728,8 @@
return data.records return data.records
.filter(function (record) { .filter(function (record) {
return ( return (
record.type === state.historyTab && recordMatchesQuery(record) record.type === state.historyTab &&
recordMatchesQuery(record)
); );
}) })
.slice(0, state.historyLimit); .slice(0, state.historyLimit);
@ -662,7 +740,9 @@
var avatar = document.createElement('div'); var avatar = document.createElement('div');
var main = document.createElement('div'); var main = document.createElement('div');
var title = document.createElement('div'); var title = document.createElement('div');
var metaGroup = document.createElement('div');
var meta = document.createElement('div'); var meta = document.createElement('div');
var transfer = document.createElement('span');
var side = document.createElement('div'); var side = document.createElement('div');
var amount = document.createElement('strong'); var amount = document.createElement('strong');
var time = document.createElement('span'); var time = document.createElement('span');
@ -671,15 +751,21 @@
avatar.className = 'record-avatar'; avatar.className = 'record-avatar';
main.className = 'record-main'; main.className = 'record-main';
title.className = 'record-title'; title.className = 'record-title';
metaGroup.className = 'record-meta-group';
meta.className = 'record-meta'; meta.className = 'record-meta';
transfer.className = 'record-transfer';
side.className = side.className =
'record-side' + (record.type === 'income' ? ' is-income' : ''); 'record-side' + (record.type === 'income' ? ' is-income' : '');
renderAvatar(avatar, record); renderAvatar(avatar, record);
title.textContent = record.name || '-'; title.textContent = record.name || '-';
meta.textContent = t('coinseller_center.uid_prefix', 'UID: {id}').replace( meta.textContent = t(
'{id}', 'coinseller_center.uid_prefix',
record.uid || '-' 'UID: {id}'
).replace('{id}', record.uid || '-');
transfer.textContent = formatTransferAmount(
record.amount,
record.currency
); );
amount.textContent = amount.textContent =
(record.type === 'income' ? '+' : '-') + (record.type === 'income' ? '+' : '-') +
@ -689,7 +775,9 @@
time.textContent = record.time || ''; time.textContent = record.time || '';
main.appendChild(title); main.appendChild(title);
main.appendChild(meta); metaGroup.appendChild(meta);
if (transfer.textContent) metaGroup.appendChild(transfer);
main.appendChild(metaGroup);
side.appendChild(amount); side.appendChild(amount);
side.appendChild(time); side.appendChild(time);
row.appendChild(avatar); row.appendChild(avatar);
@ -709,13 +797,16 @@
function renderHistory() { function renderHistory() {
document.querySelectorAll('[data-history-tab]').forEach(function (tab) { document.querySelectorAll('[data-history-tab]').forEach(function (tab) {
var active = tab.getAttribute('data-history-tab') === state.historyTab; var active =
tab.getAttribute('data-history-tab') === state.historyTab;
tab.classList.toggle('is-active', active); tab.classList.toggle('is-active', active);
tab.setAttribute('aria-selected', String(active)); tab.setAttribute('aria-selected', String(active));
}); });
var all = data.records.filter(function (record) { var all = data.records.filter(function (record) {
return record.type === state.historyTab && recordMatchesQuery(record); return (
record.type === state.historyTab && recordMatchesQuery(record)
);
}); });
var records = visibleHistoryRecords(); var records = visibleHistoryRecords();
$('loadMoreButton').hidden = records.length >= all.length; $('loadMoreButton').hidden = records.length >= all.length;
@ -783,7 +874,10 @@
renderRechargeState(); renderRechargeState();
setStatus( setStatus(
$('searchStatus'), $('searchStatus'),
t('coinseller_center.user_not_found', 'User not found.'), t(
'coinseller_center.user_not_found',
'User not found.'
),
'warning' 'warning'
); );
return; return;
@ -795,7 +889,10 @@
}) })
.catch(function (error) { .catch(function (error) {
if (window.console && window.console.warn) { if (window.console && window.console.warn) {
window.console.warn('[coinseller-center] user search failed', error); window.console.warn(
'[coinseller-center] user search failed',
error
);
} }
state.target = null; state.target = null;
renderTarget(); renderTarget();
@ -832,12 +929,9 @@
function appendTransferRecord(amount, response) { function appendTransferRecord(amount, response) {
var payload = unwrapData(response) || {}; var payload = unwrapData(response) || {};
var coins = toNumber( var coins = toNumber(
firstValue(payload, [ firstValue(payload, ['amount', 'coin_amount', 'coinAmount'])
'amount',
'coin_amount',
'coinAmount',
])
); );
var transferAmount = normalizeTransferAmount(payload);
data.records.unshift({ data.records.unshift({
id: id:
firstValue(payload, ['transaction_id', 'transactionId']) || firstValue(payload, ['transaction_id', 'transactionId']) ||
@ -847,8 +941,8 @@
uid: state.target.uid, uid: state.target.uid,
avatar: state.target.avatar, avatar: state.target.avatar,
coins: coins || amount, coins: coins || amount,
amount: 0, amount: transferAmount,
currency: '', currency: normalizeTransferCurrency(payload, transferAmount),
time: currentTimeLabel(), time: currentTimeLabel(),
}); });
} }
@ -858,7 +952,10 @@
if (!state.target) { if (!state.target) {
setStatus( setStatus(
$('rechargeStatus'), $('rechargeStatus'),
t('coinseller_center.select_user_first', 'Select a user first.'), t(
'coinseller_center.select_user_first',
'Select a user first.'
),
'warning' 'warning'
); );
return; return;
@ -877,7 +974,10 @@
if (amount > data.balance.coins) { if (amount > data.balance.coins) {
setStatus( setStatus(
$('rechargeStatus'), $('rechargeStatus'),
t('coinseller_center.amount_exceeds_stock', 'Amount exceeds stock.'), t(
'coinseller_center.amount_exceeds_stock',
'Amount exceeds stock.'
),
'warning' 'warning'
); );
return; return;
@ -921,13 +1021,19 @@
}) })
.catch(function (error) { .catch(function (error) {
if (window.console && window.console.warn) { if (window.console && window.console.warn) {
window.console.warn('[coinseller-center] transfer failed', error); window.console.warn(
'[coinseller-center] transfer failed',
error
);
} }
setStatus($('rechargeStatus'), '', ''); setStatus($('rechargeStatus'), '', '');
toast( toast(
error && error.message error && error.message
? error.message ? error.message
: t('coinseller_center.transfer_failed', 'Transfer failed.') : t(
'coinseller_center.transfer_failed',
'Transfer failed.'
)
); );
}) })
.finally(function () { .finally(function () {
@ -1006,11 +1112,13 @@
$('helpButton').addEventListener('click', function () { $('helpButton').addEventListener('click', function () {
openModal('helpModal'); openModal('helpModal');
}); });
document.querySelectorAll('[data-close-modal]').forEach(function (node) { document
node.addEventListener('click', function () { .querySelectorAll('[data-close-modal]')
closeModal(node.getAttribute('data-close-modal')); .forEach(function (node) {
node.addEventListener('click', function () {
closeModal(node.getAttribute('data-close-modal'));
});
}); });
});
$('searchForm').addEventListener('submit', function (event) { $('searchForm').addEventListener('submit', function (event) {
event.preventDefault(); event.preventDefault();
var value = $('userSearchInput').value.trim(); var value = $('userSearchInput').value.trim();

View File

@ -405,9 +405,18 @@
white-space: nowrap; white-space: nowrap;
} }
.record-meta { .record-meta-group {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px 8px;
margin-top: 5px; margin-top: 5px;
min-width: 0;
}
.record-meta {
overflow: hidden; overflow: hidden;
min-width: 0;
color: var(--muted); color: var(--muted);
font-size: 12px; font-size: 12px;
font-weight: 800; font-weight: 800;
@ -416,6 +425,15 @@
white-space: nowrap; white-space: nowrap;
} }
.record-transfer {
flex: 0 0 auto;
color: var(--primary-deep);
font-size: 12px;
font-weight: 900;
line-height: 1.2;
white-space: nowrap;
}
.record-side { .record-side {
display: grid; display: grid;
justify-items: end; justify-items: end;