子币商以及aslan充值活动
This commit is contained in:
parent
939acdfc31
commit
f776b87a01
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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": "المحفظة",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": "钱包",
|
||||
|
||||
516
common_aslan/aslan-common.js
Normal file
516
common_aslan/aslan-common.js
Normal 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 || '') },
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
})();
|
||||
@ -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');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user