子币商以及aslan充值活动

This commit is contained in:
zhx 2026-07-09 18:15:08 +08:00
parent 939acdfc31
commit f776b87a01
13 changed files with 1143 additions and 132 deletions

View File

@ -204,7 +204,7 @@
<script src="../../common/api.js?v=20260708-yumi-layered"></script>
<script src="../../common/jsbridge.js?v=20260708-yumi-layered"></script>
<script src="../../common/toast.js?v=20260708-yumi-layered"></script>
<script src="../../yumi_commmon/yumi-common.js?v=20260708-yumi-api"></script>
<script src="../../common_yumi/yumi-common.js?v=20260708-yumi-api"></script>
<script src="./yumi.js?v=20260708-yumi-api"></script>
</body>
</html>

View File

@ -7,8 +7,8 @@
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
/>
<title>Recharge Reward</title>
<link rel="stylesheet" href="../common/theme.css?v=20260707" />
<link rel="stylesheet" href="./style.css?v=20260707-modules" />
<link rel="stylesheet" href="../../common/theme.css?v=20260709" />
<link rel="stylesheet" href="./style.css?v=20260709-api" />
</head>
<body>
<div class="app-viewport" id="appViewport">
@ -18,7 +18,11 @@
aria-label="Recharge Reward activity"
data-i18n-aria="rechargeReward.pageLabel"
>
<img class="bg bg-top" src="./assets/recharge-activity/bg-top.png" alt="" />
<img
class="bg bg-top"
src="./assets/recharge-activity/bg-top.png"
alt=""
/>
<img
class="bg bg-mid bg-mid-a"
src="./assets/recharge-activity/bg-mid-a.png"
@ -56,17 +60,44 @@
src="./assets/recharge-activity/countdown-frame.png"
alt=""
/>
<strong class="count-number" data-count="days">01</strong>
<strong class="count-number" data-count="hours">01</strong>
<strong class="count-number" data-count="minutes">01</strong>
<strong class="count-number" data-count="seconds">01</strong>
<span class="count-label" data-i18n="rechargeReward.days">Days</span>
<span class="count-label" data-i18n="rechargeReward.hours">Hours</span>
<span class="count-label" data-i18n="rechargeReward.minutes">Minutes</span>
<span class="count-label" data-i18n="rechargeReward.seconds">Seconds</span>
<strong class="count-number" data-count="days"
>01</strong
>
<strong class="count-number" data-count="hours"
>01</strong
>
<strong class="count-number" data-count="minutes"
>01</strong
>
<strong class="count-number" data-count="seconds"
>01</strong
>
<span
class="count-label"
data-i18n="rechargeReward.days"
>Days</span
>
<span
class="count-label"
data-i18n="rechargeReward.hours"
>Hours</span
>
<span
class="count-label"
data-i18n="rechargeReward.minutes"
>Minutes</span
>
<span
class="count-label"
data-i18n="rechargeReward.seconds"
>Seconds</span
>
</section>
<section class="profile-card" aria-label="My recharge status">
<section
class="profile-card"
aria-label="My recharge status"
>
<img
class="profile-frame"
src="./assets/recharge-activity/profile-frame.png"
@ -78,7 +109,9 @@
alt=""
data-profile-avatar
/>
<h1 class="profile-name" data-profile-name>nanfangjian</h1>
<h1 class="profile-name" data-profile-name>
nanfangjian
</h1>
<p class="profile-id" data-profile-id>ID: 12345678</p>
<p class="profile-recharge" data-profile-recharge>
Accumlated Recharge:5998
@ -92,7 +125,9 @@
<script>
document.documentElement.dataset.hyAppCode = 'aslan';
</script>
<script src="../common/i18n.js?v=20260707"></script>
<script src="./script.js?v=20260707-modules"></script>
<script src="../../common/i18n.js?v=20260709"></script>
<script src="../../common/toast.js?v=20260709"></script>
<script src="../../common_aslan/aslan-common.js?v=20260709"></script>
<script src="./script.js?v=20260709-api"></script>
</body>
</html>

View File

@ -1,7 +1,10 @@
(function () {
var ASSET_BASE = './assets/recharge-activity/';
var viewport = document.getElementById('appViewport');
var page = document.getElementById('page');
var stage = document.querySelector('.stage');
var rewardSections = document.getElementById('rewardSections');
var bgBottom = document.querySelector('.bg-bottom');
var countNodes = {
days: document.querySelector('[data-count="days"]'),
hours: document.querySelector('[data-count="hours"]'),
@ -10,40 +13,37 @@
};
var DESIGN_WIDTH = 1080;
var DESIGN_HEIGHT = 3273;
var VIEWPORT_WIDTH = 375;
var DEFAULT_DESIGN_HEIGHT = 3273;
var designHeight = DEFAULT_DESIGN_HEIGHT;
var countdownEndAt = resolveCountdownEndAt();
var activityData = {
var countdownTimer = 0;
var state = {
user: {
name: 'nanfangjian',
id: '12345678',
name: '',
id: '',
avatar: ASSET_BASE + 'profile-avatar.png',
accumulatedRecharge: '5998',
accumulatedRecharge: 0,
},
rewards: [
{
amount: '$100',
top: 1776,
background: ASSET_BASE + 'reward-section-a.png',
claim: { text: 'Receive', claimable: true, claimed: false },
cards: [
{ price: '$100', icon: ASSET_BASE + 'reward-icon.png', left: 111, top: 293 },
{ price: '$100', icon: ASSET_BASE + 'reward-icon.png', left: 560, top: 293 },
],
},
{
amount: '$100',
top: 2466,
background: ASSET_BASE + 'reward-section-b.png',
claim: { text: 'Receive', claimable: true, claimed: false },
cards: [
{ price: '$100', icon: ASSET_BASE + 'reward-icon.png', left: 70.5, top: 278 },
{ price: '$100', icon: ASSET_BASE + 'reward-icon.png', left: 339.5, top: 278 },
{ price: '$100', icon: ASSET_BASE + 'reward-icon.png', left: 608.5, top: 278 },
],
},
],
rewards: [],
claiming: {},
loading: true,
error: '',
};
var fallbackCards = [
{
price: '$100',
icon: ASSET_BASE + 'reward-icon.png',
left: 111,
top: 293,
},
{
price: '$100',
icon: ASSET_BASE + 'reward-icon.png',
left: 560,
top: 293,
},
];
function getParams() {
return new URLSearchParams(window.location.search || '');
@ -68,25 +68,78 @@
return String(Math.max(0, value)).padStart(2, '0');
}
function showToast(message) {
if (window.HyAppToast && window.HyAppToast.show) {
window.HyAppToast.show(message);
return;
}
window.dispatchEvent(
new CustomEvent('hyapp:toast', { detail: { message: message } })
);
}
function numeric(value, fallback) {
var number = Number(value);
return Number.isFinite(number) ? number : fallback || 0;
}
function parseJSON(value) {
if (!value || typeof value !== 'string') return null;
try {
return JSON.parse(value);
} catch (_) {
return null;
}
}
function firstValue() {
for (var i = 0; i < arguments.length; i += 1) {
var value = arguments[i];
if (value !== undefined && value !== null && value !== '')
return value;
}
return '';
}
function formatUSD(value) {
var amount = numeric(value, 0);
return (
'$' +
amount.toLocaleString('en-US', {
maximumFractionDigits: 2,
minimumFractionDigits: amount % 1 === 0 ? 0 : 2,
})
);
}
function resolveCountdownEndAt() {
var params = getParams();
var explicitEnd = Date.parse(params.get('end_at') || params.get('endAt') || '');
if (Number.isFinite(explicitEnd) && explicitEnd > Date.now()) return explicitEnd;
var explicitEnd = Date.parse(
params.get('end_at') || params.get('endAt') || ''
);
if (Number.isFinite(explicitEnd) && explicitEnd > Date.now())
return explicitEnd;
var hours = Number(params.get('hours'));
if (Number.isFinite(hours) && hours > 0) return Date.now() + hours * 60 * 60 * 1000;
if (Number.isFinite(hours) && hours > 0)
return Date.now() + hours * 60 * 60 * 1000;
// This page is intentionally interface-free for now; when an API is added,
// use the server period end time here and do not recover failed requests with mock data.
// 后端月累充接口当前只返回活动文案周期,不返回毫秒级结束时间;倒计时仍支持 URL 显式覆盖,默认只作为页面视觉计时。
return Date.now() + 25 * 60 * 60 * 1000 + 61 * 1000;
}
function updateScale() {
var scale = Math.min(window.innerWidth / VIEWPORT_WIDTH, 1);
var scaledHeight = Math.ceil(DESIGN_HEIGHT * (VIEWPORT_WIDTH / DESIGN_WIDTH));
var scaledHeight = Math.ceil(
designHeight * (VIEWPORT_WIDTH / DESIGN_WIDTH)
);
document.documentElement.style.setProperty('--app-scale', String(scale));
document.documentElement.style.setProperty(
'--app-scale',
String(scale)
);
viewport.style.minHeight = scaledHeight + 'px';
page.style.minHeight = scaledHeight + 'px';
document.body.style.minHeight = Math.ceil(scaledHeight * scale) + 'px';
}
@ -104,14 +157,175 @@
countNodes.seconds.textContent = twoDigits(seconds);
}
function normalizeUser(profile, statusData) {
profile = profile || {};
var params = getParams();
return {
name: firstValue(
profile.userNickname,
profile.username,
profile.name,
params.get('name'),
'Aslan'
),
id: String(
firstValue(
profile.specialId,
profile.displayUserId,
profile.display_user_id,
profile.account,
profile.id,
profile.userId,
params.get('uid'),
params.get('id'),
''
)
),
avatar: firstValue(
profile.userAvatar,
profile.avatar,
profile.headImg,
profile.headImage,
params.get('avatar'),
ASSET_BASE + 'profile-avatar.png'
),
accumulatedRecharge: firstValue(
statusData && statusData.monthlyRecharge,
params.get('recharge'),
0
),
};
}
function rewardProps(resource) {
var group =
resource && resource.activityResource
? resource.activityResource.propsGroup || {}
: {};
return Array.isArray(group.activityRewardProps)
? group.activityRewardProps
: [];
}
function rule(resource) {
return resource && resource.activityResource
? resource.activityResource.rule || {}
: {};
}
function thresholdAmount(resource) {
var data = parseJSON(rule(resource).jsonData) || {};
return numeric(data.quantity, 0);
}
function cardLayout(count, index) {
if (count >= 4) {
return (
[
{ left: 70.5, top: 278 },
{ left: 292.5, top: 278 },
{ left: 514.5, top: 278 },
{ left: 736.5, top: 278 },
][index] || { left: 736.5, top: 278 }
);
}
if (count === 3) {
return (
[
{ left: 70.5, top: 278 },
{ left: 339.5, top: 278 },
{ left: 608.5, top: 278 },
][index] || { left: 608.5, top: 278 }
);
}
return (
[
{ left: 111, top: 293 },
{ left: 560, top: 293 },
][index] || { left: 560, top: 293 }
);
}
function normalizeCard(item, threshold, count, index) {
var pos = cardLayout(count, index);
return {
price:
item && item.amount
? formatUSD(item.amount)
: formatUSD(threshold),
icon: (item && item.cover) || ASSET_BASE + 'reward-icon.png',
left: pos.left,
top: pos.top,
};
}
function normalizeReward(resource, index) {
var currentRule = rule(resource);
var ruleId = String(currentRule.id || resource.ruleId || '');
var threshold = thresholdAmount(resource);
var props = rewardProps(resource).slice(0, 4);
var receiveStatus = String(resource.receiveStatus || '').toUpperCase();
var claimed = receiveStatus === 'RECEIVED';
var claimable = receiveStatus === 'NOT_RECEIVED';
var cardCount = props.length || fallbackCards.length;
return {
ruleId: ruleId,
amount: formatUSD(threshold),
top: 1776 + index * 690,
background:
index % 2 === 0
? ASSET_BASE + 'reward-section-a.png'
: ASSET_BASE + 'reward-section-b.png',
claim: {
text: claimed
? t('rechargeReward.claimed', 'Claimed')
: t('rechargeReward.claim', 'Claim'),
claimable: claimable,
claimed: claimed,
loading: Boolean(state.claiming[ruleId]),
},
cards: props.length
? props.map(function (item, cardIndex) {
return normalizeCard(
item,
threshold,
cardCount,
cardIndex
);
})
: fallbackCards.map(function (card) {
return Object.assign({}, card, {
price: formatUSD(threshold),
});
}),
};
}
function normalizeRewards(statusData) {
var resources =
statusData && Array.isArray(statusData.activityResources)
? statusData.activityResources
: [];
return resources
.map(function (item, index) {
return normalizeReward(item, index);
})
.filter(function (item) {
return item.ruleId;
});
}
function renderProfile(user) {
document.querySelector('[data-profile-avatar]').src = user.avatar;
document.querySelector('[data-profile-name]').textContent = user.name;
document.querySelector('[data-profile-avatar]').src =
user.avatar || ASSET_BASE + 'profile-avatar.png';
document.querySelector('[data-profile-name]').textContent =
user.name || 'Aslan';
document.querySelector('[data-profile-id]').textContent =
t('rechargeReward.idPrefix', 'ID:') + ' ' + user.id;
t('rechargeReward.idPrefix', 'ID:') + ' ' + (user.id || '-');
document.querySelector('[data-profile-recharge]').textContent =
t('rechargeReward.accumulatedRecharge', 'Accumlated Recharge:') +
user.accumulatedRecharge;
t('rechargeReward.accumulatedRecharge', 'Accumulated Recharge:') +
formatUSD(user.accumulatedRecharge || 0);
}
function renderRewardCard(card) {
@ -135,31 +349,65 @@
].join('');
}
function renderClaimButton(claim) {
var stateClass = claim.claimed ? ' is-claimed' : claim.claimable ? ' is-claimable' : ' is-disabled';
function renderClaimButton(section) {
var claim = section.claim || {};
var stateClass = claim.claimed
? ' is-claimed'
: claim.claimable
? ' is-claimable'
: ' is-disabled';
var disabled = !claim.claimable || claim.claimed || claim.loading;
return [
'<button class="claim-button',
stateClass,
'" type="button"',
claim.claimable && !claim.claimed ? '' : ' disabled',
'" type="button" data-rule-id="',
escapeHTML(section.ruleId),
'"',
disabled ? ' disabled' : '',
'>',
'<img class="claim-button-bg" src="',
ASSET_BASE,
'receive-button-bg.png" alt="" />',
'<span>',
escapeHTML(claim.text || 'Receive'),
escapeHTML(
claim.loading
? t('rechargeReward.claiming', 'Claiming...')
: claim.text || t('rechargeReward.claim', 'Claim')
),
'</span>',
'</button>',
].join('');
}
function renderRewards(rewards) {
if (state.loading) {
rewardSections.innerHTML =
'<p class="reward-message">' +
escapeHTML(t('rechargeReward.loading', 'Loading...')) +
'</p>';
return;
}
if (state.error) {
rewardSections.innerHTML =
'<p class="reward-message is-error">' +
escapeHTML(state.error) +
'</p>';
return;
}
if (!rewards.length) {
rewardSections.innerHTML =
'<p class="reward-message">' +
escapeHTML(t('rechargeReward.noRewards', 'No rewards yet')) +
'</p>';
return;
}
rewardSections.innerHTML = rewards
.map(function (section) {
var title = t('rechargeReward.rechargeTitle', 'Recharge {amount}').replace(
'{amount}',
section.amount
);
var title = t(
'rechargeReward.rechargeTitle',
'Recharge {amount}'
).replace('{amount}', section.amount);
return [
'<section class="reward-section" style="--section-top:',
section.top,
@ -177,27 +425,172 @@
return renderRewardCard(card);
})
.join(''),
renderClaimButton(section.claim || { text: 'Receive', claimable: false }),
renderClaimButton(section),
'</section>',
].join('');
})
.join('');
}
function updateDesignHeight() {
var rewardCount = Math.max(state.rewards.length, 2);
designHeight = Math.max(
DEFAULT_DESIGN_HEIGHT,
1776 + rewardCount * 690 + 120
);
stage.style.height = designHeight + 'px';
if (bgBottom)
bgBottom.style.top = Math.max(2695, designHeight - 578) + 'px';
updateScale();
}
function renderPage() {
document.title = t('rechargeReward.pageTitle', 'Recharge Reward');
renderProfile(activityData.user);
renderRewards(activityData.rewards);
renderProfile(state.user);
updateDesignHeight();
renderRewards(state.rewards);
}
function mockData() {
var params = getParams();
var claimed = params.get('claimed') === '1';
return Promise.resolve({
profile: {
userNickname: params.get('name') || 'Aslan',
account: params.get('uid') || '12345678',
userAvatar:
params.get('avatar') || ASSET_BASE + 'profile-avatar.png',
},
status: {
monthlyRecharge: numeric(params.get('recharge'), 5998),
activityResources: [
{
receiveStatus: claimed ? 'RECEIVED' : 'NOT_RECEIVED',
activityResource: {
rule: {
id: '1000000000000000001',
jsonData: '{"quantity":100}',
},
propsGroup: { activityRewardProps: [] },
},
},
{
receiveStatus: 'INELIGIBLE',
activityResource: {
rule: {
id: '1000000000000000002',
jsonData: '{"quantity":200}',
},
propsGroup: { activityRewardProps: [] },
},
},
],
},
});
}
function loadRemoteData() {
if (!window.AslanCommon) {
return Promise.reject(new Error('Aslan bridge unavailable'));
}
return Promise.all([
window.AslanCommon.user.profile().catch(function (error) {
console.warn('Aslan profile failed', error);
return null;
}),
window.AslanCommon.rechargeReward.status(),
]).then(function (result) {
return { profile: result[0], status: result[1] };
});
}
function loadData() {
state.loading = true;
state.error = '';
renderPage();
var params = getParams();
var loader =
params.get('mock') === '1' || params.get('mock') === 'true'
? mockData()
: loadRemoteData();
return loader
.then(function (payload) {
var statusData = payload.status || {};
state.user = normalizeUser(payload.profile, statusData);
state.rewards = normalizeRewards(statusData);
state.loading = false;
state.error = '';
renderPage();
})
.catch(function (error) {
console.warn('Aslan recharge reward load failed', error);
state.loading = false;
state.error =
(error && error.message) ||
t('rechargeReward.loadFailed', 'Load failed');
renderPage();
});
}
function markClaimed(ruleId) {
state.rewards = state.rewards.map(function (item) {
if (item.ruleId !== ruleId) return item;
item.claim.claimed = true;
item.claim.claimable = false;
item.claim.loading = false;
item.claim.text = t('rechargeReward.claimed', 'Claimed');
return item;
});
}
function claimReward(ruleId) {
if (!ruleId || state.claiming[ruleId] || !window.AslanCommon) return;
state.claiming[ruleId] = true;
renderPage();
// 领取必须只提交后端规则 Long 的字符串形式,避免 JS Number 精度损失导致发错档位。
window.AslanCommon.rechargeReward
.claim(ruleId)
.then(function () {
delete state.claiming[ruleId];
markClaimed(ruleId);
renderPage();
showToast(t('rechargeReward.claimSuccess', 'Claimed'));
return loadData();
})
.catch(function (error) {
delete state.claiming[ruleId];
renderPage();
showToast(
(error && error.message) ||
t('rechargeReward.claimFailed', 'Claim failed')
);
});
}
function bindEvents() {
rewardSections.addEventListener('click', function (event) {
var button = event.target.closest('[data-rule-id]');
if (!button || button.disabled) return;
claimReward(button.getAttribute('data-rule-id'));
});
window.addEventListener('resize', updateScale);
window.addEventListener('hyapp:i18n-ready', function () {
renderPage();
});
}
function init() {
updateScale();
updateCountdown();
renderPage();
bindEvents();
loadData();
window.setInterval(updateCountdown, 1000);
window.addEventListener('resize', updateScale);
window.addEventListener('hyapp:i18n-ready', renderPage);
if (countdownTimer) window.clearInterval(countdownTimer);
countdownTimer = window.setInterval(updateCountdown, 1000);
}
init();

View File

@ -16,11 +16,7 @@ body {
overflow-x: hidden;
background: var(--hy-theme-bg);
color: #f6e0b2;
font-family:
Arial,
"Helvetica Neue",
Helvetica,
sans-serif;
font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;
}
body {
@ -138,19 +134,19 @@ body {
0 0 14px rgba(255, 210, 135, 0.58);
}
.count-number[data-count="days"] {
.count-number[data-count='days'] {
left: 97px;
}
.count-number[data-count="hours"] {
.count-number[data-count='hours'] {
left: 301px;
}
.count-number[data-count="minutes"] {
.count-number[data-count='minutes'] {
left: 506px;
}
.count-number[data-count="seconds"] {
.count-number[data-count='seconds'] {
left: 706px;
}
@ -260,6 +256,33 @@ body {
width: 900px;
}
.reward-message {
position: absolute;
left: 90px;
top: 1810px;
z-index: 5;
width: 900px;
margin: 0;
padding: 36px 44px;
border: 3px solid rgba(255, 206, 149, 0.4);
border-radius: 28px;
background: rgba(54, 24, 9, 0.76);
color: #f6e0b2;
font-size: 38px;
font-weight: 900;
line-height: 52px;
letter-spacing: 0;
text-align: center;
text-shadow:
0 2px 0 #3a1704,
0 0 12px rgba(255, 207, 113, 0.36);
}
.reward-message.is-error {
border-color: rgba(255, 112, 112, 0.52);
color: #ffd1c2;
}
.title-pill {
position: absolute;
left: 0;
@ -366,6 +389,11 @@ body {
opacity: 0.68;
}
.claim-button.is-claimed,
.claim-button.is-disabled {
filter: grayscale(0.45);
}
.claim-button-bg {
position: absolute;
inset: 0;

View File

@ -65,6 +65,14 @@
"rechargeReward.idPrefix": "المعرّف:",
"rechargeReward.accumulatedRecharge": "إجمالي الشحن:",
"rechargeReward.rechargeTitle": "اشحن {amount}",
"rechargeReward.claim": "استلام",
"rechargeReward.claiming": "جار الاستلام...",
"rechargeReward.claimed": "تم الاستلام",
"rechargeReward.claimSuccess": "تم الاستلام",
"rechargeReward.claimFailed": "فشل الاستلام",
"rechargeReward.loading": "جار التحميل...",
"rechargeReward.noRewards": "لا توجد مكافآت بعد",
"rechargeReward.loadFailed": "فشل التحميل",
"wheel.pageTitle": "عجلة يومي",
"wheel.pageLabel": "عجلة يومي",
"wheel.classic": "كلاسيكي",
@ -480,11 +488,11 @@
"coinseller_center.load_failed": "فشل التحميل. حاول مرة أخرى لاحقا.",
"coinseller_center.transfer_failed": "فشل التحويل.",
"coinseller_center.sub_sellers": "بائعو العملات الفرعيون",
"coinseller_center.sub_sellers_desc": "ادع وأدر بائعي العملات الفرعيين المباشرين",
"coinseller_center.sub_sellers_desc": "ادع بائعي عملات فرعيين وأدر المعتمدين مباشرة",
"coinseller_center.invite_sub_seller": "دعوة بائع عملات فرعي",
"coinseller_center.invite_sub_seller_hint": "أدخل معرف المستخدم لفتح بائع عملات فرعي مباشر.",
"coinseller_center.invite_sub_seller_hint": "أدخل معرف المستخدم. سيظهر البائع بعد المراجعة الرسمية.",
"coinseller_center.invite_now": "ادع الآن",
"coinseller_center.invite_success": "تم إرسال الدعوة.",
"coinseller_center.invite_success": "ستراجع الجهة الرسمية دعوتك خلال 24 ساعة.",
"coinseller_center.invite_failed": "فشلت الدعوة.",
"coinseller_center.no_sub_sellers": "لا يوجد بائعو عملات فرعيون بعد.",
"coinseller_center.sub_seller_wallet": "المحفظة",

View File

@ -65,6 +65,14 @@
"rechargeReward.idPrefix": "ID:",
"rechargeReward.accumulatedRecharge": "Accumulated Recharge:",
"rechargeReward.rechargeTitle": "Recharge {amount}",
"rechargeReward.claim": "Claim",
"rechargeReward.claiming": "Claiming...",
"rechargeReward.claimed": "Claimed",
"rechargeReward.claimSuccess": "Claimed",
"rechargeReward.claimFailed": "Claim failed",
"rechargeReward.loading": "Loading...",
"rechargeReward.noRewards": "No rewards yet",
"rechargeReward.loadFailed": "Load failed",
"wheel.pageTitle": "Yumi Wheel",
"wheel.pageLabel": "Yumi Wheel",
"wheel.classic": "Classic",
@ -486,11 +494,11 @@
"coinseller_center.load_failed": "Load failed. Try again later.",
"coinseller_center.transfer_failed": "Transfer failed.",
"coinseller_center.sub_sellers": "My sub coin sellers",
"coinseller_center.sub_sellers_desc": "Invite and manage direct sub coin sellers",
"coinseller_center.sub_sellers_desc": "Invite sub coin sellers and manage approved direct sellers",
"coinseller_center.invite_sub_seller": "Invite sub coin seller",
"coinseller_center.invite_sub_seller_hint": "Enter the user's display ID to open a direct sub coin seller.",
"coinseller_center.invite_sub_seller_hint": "Enter the user's display ID. The seller will appear after official review.",
"coinseller_center.invite_now": "Invite now",
"coinseller_center.invite_success": "Invitation submitted.",
"coinseller_center.invite_success": "Official will review your invitation within 24 hours.",
"coinseller_center.invite_failed": "Invite failed.",
"coinseller_center.no_sub_sellers": "No sub coin sellers yet.",
"coinseller_center.sub_seller_wallet": "Wallet",

View File

@ -65,6 +65,14 @@
"rechargeReward.idPrefix": "ID:",
"rechargeReward.accumulatedRecharge": "Recarga acumulada:",
"rechargeReward.rechargeTitle": "Recarga {amount}",
"rechargeReward.claim": "Reclamar",
"rechargeReward.claiming": "Reclamando...",
"rechargeReward.claimed": "Reclamado",
"rechargeReward.claimSuccess": "Reclamado",
"rechargeReward.claimFailed": "Error al reclamar",
"rechargeReward.loading": "Cargando...",
"rechargeReward.noRewards": "No hay recompensas todavía",
"rechargeReward.loadFailed": "Error al cargar",
"wheel.pageTitle": "Rueda de Yumi",
"wheel.pageLabel": "Rueda de Yumi",
"wheel.classic": "Clásico",
@ -480,11 +488,11 @@
"coinseller_center.load_failed": "Error al cargar. Inténtalo de nuevo más tarde.",
"coinseller_center.transfer_failed": "Transferencia fallida.",
"coinseller_center.sub_sellers": "Mis subvendedores de monedas",
"coinseller_center.sub_sellers_desc": "Invita y gestiona subvendedores directos",
"coinseller_center.sub_sellers_desc": "Invita subvendedores y gestiona los directos aprobados",
"coinseller_center.invite_sub_seller": "Invitar subvendedor",
"coinseller_center.invite_sub_seller_hint": "Ingresa el ID visible del usuario para abrir un subvendedor directo.",
"coinseller_center.invite_sub_seller_hint": "Ingresa el ID visible del usuario. Aparecera despues de la revision oficial.",
"coinseller_center.invite_now": "Invitar ahora",
"coinseller_center.invite_success": "Invitación enviada.",
"coinseller_center.invite_success": "El equipo oficial revisara tu invitacion en 24 horas.",
"coinseller_center.invite_failed": "Error al invitar.",
"coinseller_center.no_sub_sellers": "Aún no hay subvendedores.",
"coinseller_center.sub_seller_wallet": "Billetera",

View File

@ -32,6 +32,14 @@
"rechargeReward.idPrefix": "ID:",
"rechargeReward.accumulatedRecharge": "Recharge akumulatif:",
"rechargeReward.rechargeTitle": "Recharge {amount}",
"rechargeReward.claim": "Klaim",
"rechargeReward.claiming": "Mengklaim...",
"rechargeReward.claimed": "Diklaim",
"rechargeReward.claimSuccess": "Diklaim",
"rechargeReward.claimFailed": "Klaim gagal",
"rechargeReward.loading": "Memuat...",
"rechargeReward.noRewards": "Belum ada hadiah",
"rechargeReward.loadFailed": "Gagal memuat",
"wheel.pageTitle": "Yumi Wheel",
"wheel.pageLabel": "Yumi Wheel",
"wheel.classic": "Classic",
@ -447,11 +455,11 @@
"coinseller_center.load_failed": "Gagal memuat. Coba lagi nanti.",
"coinseller_center.transfer_failed": "Transfer gagal.",
"coinseller_center.sub_sellers": "Sub penjual koin saya",
"coinseller_center.sub_sellers_desc": "Undang dan kelola sub penjual koin langsung",
"coinseller_center.sub_sellers_desc": "Undang sub penjual koin dan kelola seller langsung yang disetujui",
"coinseller_center.invite_sub_seller": "Undang sub penjual koin",
"coinseller_center.invite_sub_seller_hint": "Masukkan ID tampilan pengguna untuk membuka sub penjual koin langsung.",
"coinseller_center.invite_sub_seller_hint": "Masukkan ID tampilan pengguna. Seller akan muncul setelah tinjauan resmi.",
"coinseller_center.invite_now": "Undang sekarang",
"coinseller_center.invite_success": "Undangan terkirim.",
"coinseller_center.invite_success": "Pihak resmi akan meninjau undangan Anda dalam 24 jam.",
"coinseller_center.invite_failed": "Undangan gagal.",
"coinseller_center.no_sub_sellers": "Belum ada sub penjual koin.",
"coinseller_center.sub_seller_wallet": "Dompet",

View File

@ -32,6 +32,14 @@
"rechargeReward.idPrefix": "ID:",
"rechargeReward.accumulatedRecharge": "Toplam yükleme:",
"rechargeReward.rechargeTitle": "{amount} yükle",
"rechargeReward.claim": "Al",
"rechargeReward.claiming": "Alınıyor...",
"rechargeReward.claimed": "Alındı",
"rechargeReward.claimSuccess": "Alındı",
"rechargeReward.claimFailed": "Alma başarısız",
"rechargeReward.loading": "Yükleniyor...",
"rechargeReward.noRewards": "Henüz ödül yok",
"rechargeReward.loadFailed": "Yükleme başarısız",
"wheel.pageTitle": "Yumi Çarkı",
"wheel.pageLabel": "Yumi Çarkı",
"wheel.classic": "Klasik",
@ -447,11 +455,11 @@
"coinseller_center.load_failed": "Yükleme başarısız. Daha sonra tekrar deneyin.",
"coinseller_center.transfer_failed": "Transfer başarısız.",
"coinseller_center.sub_sellers": "Alt coin sellerlarım",
"coinseller_center.sub_sellers_desc": "Doğrudan alt coin seller davet edin ve yönetin",
"coinseller_center.sub_sellers_desc": "Alt coin seller davet edin ve onaylı doğrudan sellerları yönetin",
"coinseller_center.invite_sub_seller": "Alt coin seller davet et",
"coinseller_center.invite_sub_seller_hint": "Doğrudan alt coin seller açmak için kullanıcının görünen ID'sini girin.",
"coinseller_center.invite_sub_seller_hint": "Kullanıcının görünen ID'sini girin. Seller resmi incelemeden sonra görünür.",
"coinseller_center.invite_now": "Şimdi davet et",
"coinseller_center.invite_success": "Davet gönderildi.",
"coinseller_center.invite_success": "Yetkililer davetinizi 24 saat içinde inceleyecek.",
"coinseller_center.invite_failed": "Davet başarısız.",
"coinseller_center.no_sub_sellers": "Henüz alt coin seller yok.",
"coinseller_center.sub_seller_wallet": "Cüzdan",

View File

@ -32,6 +32,14 @@
"rechargeReward.idPrefix": "ID",
"rechargeReward.accumulatedRecharge": "累计充值:",
"rechargeReward.rechargeTitle": "充值 {amount}",
"rechargeReward.claim": "领取",
"rechargeReward.claiming": "领取中...",
"rechargeReward.claimed": "已领取",
"rechargeReward.claimSuccess": "领取成功",
"rechargeReward.claimFailed": "领取失败",
"rechargeReward.loading": "加载中...",
"rechargeReward.noRewards": "暂无奖励",
"rechargeReward.loadFailed": "加载失败",
"wheel.pageTitle": "Yumi 转盘",
"wheel.pageLabel": "Yumi 转盘",
"wheel.classic": "Classic",
@ -453,11 +461,11 @@
"coinseller_center.load_failed": "加载失败,请稍后重试。",
"coinseller_center.transfer_failed": "转账失败。",
"coinseller_center.sub_sellers": "我的子币商",
"coinseller_center.sub_sellers_desc": "邀请并管理直开子币商",
"coinseller_center.sub_sellers_desc": "邀请子币商,审核通过后管理直属子币商",
"coinseller_center.invite_sub_seller": "邀请子币商",
"coinseller_center.invite_sub_seller_hint": "输入用户展示 ID直开为你的子币商。",
"coinseller_center.invite_sub_seller_hint": "输入用户展示 ID提交申请后由官方审核。",
"coinseller_center.invite_now": "立即邀请",
"coinseller_center.invite_success": "邀请已提交。",
"coinseller_center.invite_success": "官方会在24小时内审核您的邀请。",
"coinseller_center.invite_failed": "邀请失败。",
"coinseller_center.no_sub_sellers": "暂无子币商。",
"coinseller_center.sub_seller_wallet": "钱包",

View File

@ -0,0 +1,516 @@
(function () {
'use strict';
var PROD_API_BASE = 'https://api.atuchat.com/';
var LOCAL_API_BASE = 'http://127.0.0.1:1100/';
var APP_CODE = 'aslan';
var DEFAULT_ORIGIN = 'ATYOU';
var readyPromise = null;
var headers = {
authorization: '',
'req-app-intel':
'version=1.0.0;build=1;model=H5;sysVersion=web;channel=Web',
'req-client': 'H5',
'req-imei': 'H5-STATIC',
'req-lang': 'en',
'req-sys-origin': 'origin=ATYOU;originChild=ATYOU',
'req-version': 'V2',
'req-zone': 'Asia/Shanghai',
'Req-Atyou': 'true',
};
function currentParams() {
var params = new URLSearchParams(window.location.search || '');
var hash = window.location.hash || '';
if (hash.indexOf('?') >= 0) {
new URLSearchParams(hash.split('?').slice(1).join('?')).forEach(
function (value, key) {
if (!params.has(key)) params.set(key, value);
}
);
}
return params;
}
function query(name) {
return currentParams().get(name);
}
function normalizeBaseURL(value) {
return String(value || '').replace(/\/+$/, '');
}
function resolveEnv() {
var env = String(query('env') || '')
.trim()
.toLowerCase();
return env === 'local' || env === 'test' || env === 'dev' ? env : '';
}
function baseURL() {
var override =
query('apiBase') ||
query('api_base') ||
query('aslan_api_base') ||
window.ASLAN_API_BASE_URL ||
(window.AslanCommonConfig && window.AslanCommonConfig.apiBase);
if (override) return normalizeBaseURL(override);
return resolveEnv() === 'local'
? normalizeBaseURL(LOCAL_API_BASE)
: normalizeBaseURL(PROD_API_BASE);
}
function buildURL(path, queryData) {
var url = new URL(
String(path || '').replace(/^\/+/, ''),
baseURL() + '/'
);
Object.keys(queryData || {}).forEach(function (key) {
var value = queryData[key];
if (value === undefined || value === null || value === '') return;
url.searchParams.set(key, value);
});
return url.toString();
}
function normalizeAuthorization(value) {
var text = String(value || '').trim();
if (!text) return '';
return /^Bearer\s+/i.test(text) ? text : 'Bearer ' + text;
}
function splitHeaderPairs(value) {
var result = {};
String(value || '')
.split(';')
.forEach(function (item) {
var index = item.indexOf('=');
if (index > -1) {
result[item.slice(0, index).trim()] = item
.slice(index + 1)
.trim();
}
});
return result;
}
function putHeader(target, key, value) {
if (
value === undefined ||
value === null ||
value === '' ||
target[key]
) {
return;
}
target[key] = String(value);
}
function normalizeHeaderCandidate(value, depth) {
depth = depth || 0;
if (depth > 4 || value === null || value === undefined) return {};
if (typeof value === 'string') {
var trimmed = value.trim();
if (!trimmed) return {};
if (trimmed.charAt(0) === '{' || trimmed.charAt(0) === '[') {
try {
return normalizeHeaderCandidate(
JSON.parse(trimmed),
depth + 1
);
} catch (_) {
return {};
}
}
return { authorization: trimmed };
}
if (Array.isArray(value)) {
return value.reduce(function (result, item) {
return Object.assign(
result,
normalizeHeaderCandidate(item, depth + 1)
);
}, {});
}
if (typeof value !== 'object') return {};
var appIntel = splitHeaderPairs(
value['Req-App-Intel'] || value['req-app-intel']
);
var sysOrigin = splitHeaderPairs(
value['Req-Sys-Origin'] || value['req-sys-origin']
);
var normalized = {};
putHeader(
normalized,
'authorization',
value.Authorization ||
value.authorization ||
value.token ||
value.accessToken
);
putHeader(
normalized,
'reqLang',
value['Req-Lang'] ||
value['req-lang'] ||
value.reqLang ||
value.lang
);
putHeader(
normalized,
'appVersion',
appIntel.version || value.appVersion || value['App-Version']
);
putHeader(
normalized,
'buildVersion',
appIntel.build || value.buildVersion
);
putHeader(normalized, 'model', appIntel.model || value.model);
putHeader(normalized, 'channel', appIntel.channel || value.channel);
putHeader(
normalized,
'reqImei',
appIntel['Req-Imei'] ||
appIntel.imei ||
value['Req-Imei'] ||
value['req-imei'] ||
value.reqImei
);
putHeader(
normalized,
'origin',
sysOrigin.origin || value.sysOrigin || value.origin
);
putHeader(
normalized,
'child',
sysOrigin.originChild ||
sysOrigin.child ||
value.sysOriginChild ||
value.child
);
['headerInfo', 'headers', 'auth', 'data', 'state'].forEach(
function (key) {
Object.assign(
normalized,
normalizeHeaderCandidate(value[key], depth + 1)
);
}
);
return normalized;
}
function setHeadersFromApp(rawOrObject) {
var data = normalizeHeaderCandidate(rawOrObject || {});
var next = {};
if (data.authorization) {
next.authorization = normalizeAuthorization(data.authorization);
}
if (data.reqLang) next['req-lang'] = data.reqLang;
if (
data.appVersion ||
data.buildVersion ||
data.model ||
data.channel
) {
next['req-app-intel'] = [
data.appVersion ? 'version=' + data.appVersion : '',
data.buildVersion ? 'build=' + data.buildVersion : '',
data.model ? 'model=' + data.model : '',
data.channel ? 'channel=' + data.channel : '',
]
.filter(Boolean)
.join(';');
}
if (data.reqImei) next['req-imei'] = data.reqImei;
if (data.origin) {
next['req-sys-origin'] = [
'origin=' + data.origin,
'originChild=' + (data.child || data.origin),
].join(';');
}
headers = Object.assign(headers, next);
return headers;
}
function discoverQueryHeaders() {
return {
authorization:
query('authorization') ||
query('Authorization') ||
query('token') ||
query('accessToken') ||
query('access_token'),
reqLang: query('lang') || query('language') || query('locale'),
origin: query('sysOrigin') || query('origin') || DEFAULT_ORIGIN,
child:
query('sysOriginChild') ||
query('originChild') ||
DEFAULT_ORIGIN,
channel: query('channel') || query('appChannel'),
};
}
function readStorageHeaders(storage) {
var collected = {};
if (!storage) return collected;
[
'authorization',
'Authorization',
'token',
'accessToken',
'headerInfo',
'headersFromApp',
'appHeaders',
'user',
'userInfo',
].forEach(function (key) {
try {
Object.assign(
collected,
normalizeHeaderCandidate(storage.getItem(key))
);
} catch (_) {}
});
return collected;
}
function applyDiscoveredHeaders() {
setHeadersFromApp(discoverQueryHeaders());
setHeadersFromApp(window.__APP_HEADERS__);
setHeadersFromApp(window.__HEADER_INFO__);
setHeadersFromApp(window.headerInfo);
try {
setHeadersFromApp(readStorageHeaders(window.localStorage));
setHeadersFromApp(readStorageHeaders(window.sessionStorage));
} catch (_) {}
}
function ensureAslanAppCode() {
document.documentElement.dataset.hyAppCode = APP_CODE;
var params = currentParams();
if (
params.get('app_code') ||
params.get('app') ||
params.get('appCode') ||
!window.history ||
!window.history.replaceState
) {
return;
}
var url = new URL(window.location.href);
url.searchParams.set('app_code', APP_CODE);
window.history.replaceState(null, '', url.toString());
}
function isAppEnvironment() {
return Boolean(
window.app ||
window.FlutterApp ||
window.FlutterPageControl ||
window.webkit
);
}
function requestAccessOrigin() {
return new Promise(function (resolve) {
var settled = false;
var timeoutId = null;
function finish(raw) {
if (raw) setHeadersFromApp(raw);
if (settled) return;
settled = true;
if (timeoutId) window.clearTimeout(timeoutId);
resolve(raw || null);
}
window.renderData = finish;
window.getIosAccessOriginParam = finish;
if (!isAppEnvironment()) {
finish(null);
return;
}
// Aslan Flutter 在页面完成后注入 window.app.getAccessOrigin这里轮询短时间等待避免首屏请求早于注入而丢登录态。
if (
window.app &&
typeof window.app.getAccessOrigin === 'function'
) {
try {
finish(window.app.getAccessOrigin());
return;
} catch (_) {}
}
if (
window.FlutterApp &&
typeof window.FlutterApp.postMessage === 'function'
) {
try {
window.FlutterApp.postMessage('requestAccessOrigin');
} catch (_) {}
}
var attempt = 0;
var poll = function () {
attempt += 1;
if (
window.app &&
typeof window.app.getAccessOrigin === 'function'
) {
try {
finish(window.app.getAccessOrigin());
return;
} catch (_) {}
}
if (!settled && attempt < 30) window.setTimeout(poll, 80);
};
poll();
timeoutId = window.setTimeout(function () {
finish(null);
}, 2400);
});
}
function connectToApp() {
if (readyPromise) return readyPromise;
ensureAslanAppCode();
applyDiscoveredHeaders();
readyPromise = requestAccessOrigin().then(function () {
return {
baseURL: baseURL(),
headers: Object.assign({}, headers),
authenticated: Boolean(headers.authorization),
};
});
return readyPromise;
}
function parseResponse(response) {
return response.text().then(function (text) {
var payload = text ? JSON.parse(text) : {};
if (!response.ok) {
var httpError = new Error(
payload.message ||
payload.errorMsg ||
response.statusText ||
'request_failed'
);
httpError.status = response.status;
httpError.payload = payload;
throw httpError;
}
if (payload && payload.status === false) {
var apiError = new Error(
payload.message ||
payload.errorMsg ||
payload.error ||
'api_request_failed'
);
apiError.payload = payload;
apiError.code = payload.errorCode || payload.code;
throw apiError;
}
if (
payload &&
Object.prototype.hasOwnProperty.call(payload, 'body')
) {
return payload.body;
}
if (
payload &&
Object.prototype.hasOwnProperty.call(payload, 'data')
) {
return payload.data;
}
return payload;
});
}
function request(path, options) {
var opts = options || {};
var body = opts.body;
if (body && typeof body === 'object' && !(body instanceof FormData)) {
body = JSON.stringify(body);
}
return connectToApp().then(function () {
var requestHeaders = Object.assign({}, headers, opts.headers || {});
if (!requestHeaders.authorization)
delete requestHeaders.authorization;
if (body) {
requestHeaders['content-type'] =
requestHeaders['content-type'] || 'application/json';
}
return window
.fetch(buildURL(path, opts.query), {
method: opts.method || 'GET',
headers: requestHeaders,
body: body,
credentials: 'omit',
cache: 'no-store',
})
.then(parseResponse);
});
}
function back() {
if (
window.FlutterPageControl &&
window.FlutterPageControl.postMessage
) {
window.FlutterPageControl.postMessage('close_page');
return;
}
if (window.HyAppBridge && window.HyAppBridge.back) {
window.HyAppBridge.back();
return;
}
window.history.back();
}
window.AslanCommon = {
baseURL: baseURL,
buildURL: buildURL,
headers: function () {
return Object.assign({}, headers);
},
connectToApp: connectToApp,
setHeadersFromApp: setHeadersFromApp,
request: request,
get: function (path, queryData) {
return request(path, { method: 'GET', query: queryData });
},
post: function (path, body, queryData) {
return request(path, {
method: 'POST',
body: body,
query: queryData,
});
},
back: back,
user: {
profile: function () {
return request('/user/user-profile/user-id', { method: 'GET' });
},
},
rechargeReward: {
status: function () {
return request(
'/activity/cumulative-recharge/recharge-reward',
{
method: 'GET',
}
);
},
claim: function (ruleId) {
return request('/activity/cumulative-recharge', {
method: 'POST',
body: { id: String(ruleId || '') },
});
},
},
};
})();

View File

@ -701,9 +701,15 @@
: Array.isArray(source)
? source
: [];
return items.map(function (item, index) {
return normalizeSubSeller(item, index);
});
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() {
@ -1301,6 +1307,14 @@
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'),
@ -1310,22 +1324,13 @@
return;
}
if (state.isMock) {
data.subSellers.unshift({
uid: displayID,
userId: displayID,
name: t(
'coinseller_center.user_name_template',
'User {id}'
).replace('{id}', displayID),
avatar: avatarData(displayID, '#efe4ff', '#7d57c7'),
status: 'active',
balance: 0,
});
input.value = '';
renderSubSellers();
closeModal('inviteSubSellerModal');
toast(
t('coinseller_center.invite_success', 'Invitation submitted.')
t(
'coinseller_center.invite_success',
'Official will review your invitation within 24 hours.'
)
);
return;
}
@ -1345,41 +1350,27 @@
t('coinseller_center.loading', 'Loading...'),
''
);
// 直开子币商是有状态写操作,必须使用 command_id 让移动端重试或重复点击时由服务端保持幂等
// 邀请子币商现在只创建待审核申请command_id 保证移动端重试不会重复生成多个 pending 申请
window.HyAppAPI.wallet
.inviteCoinSellerSubSeller({
command_id: subSellerCommandID('invite'),
target_display_user_id: displayID,
})
.then(function (response) {
var payload = unwrapData(response) || {};
var seller = normalizeSubSeller(
payload.child ||
payload.sub_seller ||
payload.subSeller ||
payload,
data.subSellers.length
);
if (seller.uid) {
data.subSellers = data.subSellers.filter(function (item) {
return String(item.uid) !== String(seller.uid);
});
data.subSellers.unshift(seller);
}
input.value = '';
setStatus(
$('inviteSubSellerStatus'),
t(
'coinseller_center.invite_success',
'Invitation submitted.'
'Official will review your invitation within 24 hours.'
),
'success'
);
renderSubSellers();
// 申请提交成功不代表父子关系已经生效,列表必须继续以服务端 active 关系为准。
toast(
t(
'coinseller_center.invite_success',
'Invitation submitted.'
'Official will review your invitation within 24 hours.'
)
);
closeModal('inviteSubSellerModal');