1010 lines
33 KiB
JavaScript
1010 lines
33 KiB
JavaScript
(function () {
|
||
'use strict';
|
||
|
||
var ASSET_BASE = './huwaa/assets/layers/';
|
||
var params = new URLSearchParams(window.location.search || '');
|
||
var useMock = params.get('mock') === '1';
|
||
var DESIGN_PRIZES = [
|
||
{
|
||
id: 'medium-orange',
|
||
name: 'Medium',
|
||
image: ASSET_BASE + 'chest-orange.webp',
|
||
points: 500,
|
||
},
|
||
{
|
||
id: 'medium-green',
|
||
name: 'Medium',
|
||
image: ASSET_BASE + 'chest-green.webp',
|
||
points: 500,
|
||
},
|
||
{
|
||
id: 'medium-purple',
|
||
name: 'Medium',
|
||
image: ASSET_BASE + 'chest-white-gold.webp',
|
||
points: 500,
|
||
},
|
||
{
|
||
id: 'medium-teal',
|
||
name: 'Medium',
|
||
image: ASSET_BASE + 'chest-blue.webp',
|
||
points: 500,
|
||
},
|
||
{
|
||
id: 'medium-gold',
|
||
name: 'Medium',
|
||
image: ASSET_BASE + 'chest-purple.webp',
|
||
points: 500,
|
||
},
|
||
{
|
||
id: 'medium-red',
|
||
name: 'Medium',
|
||
image: ASSET_BASE + 'chest-red.webp',
|
||
points: 500,
|
||
},
|
||
{
|
||
id: 'medium-brown',
|
||
name: 'Medium',
|
||
image: ASSET_BASE + 'chest-gold.webp',
|
||
points: 500,
|
||
},
|
||
];
|
||
var DESIGN_TASKS = [
|
||
{
|
||
id: 'microphone-1',
|
||
title: 'Use the microphone for 1 min',
|
||
description: 'Friend completes biometric authentication',
|
||
titleKey: 'huwaa_invite.task_title',
|
||
descriptionKey: 'huwaa_invite.task_description',
|
||
reward: 1,
|
||
completed: true,
|
||
},
|
||
{
|
||
id: 'microphone-2',
|
||
title: 'Use the microphone for 1 min',
|
||
description: 'Friend completes biometric authentication',
|
||
titleKey: 'huwaa_invite.task_title',
|
||
descriptionKey: 'huwaa_invite.task_description',
|
||
reward: 1,
|
||
completed: true,
|
||
},
|
||
{
|
||
id: 'microphone-3',
|
||
title: 'Use the microphone for 1 min',
|
||
description: 'Friend completes biometric authentication',
|
||
titleKey: 'huwaa_invite.task_title',
|
||
descriptionKey: 'huwaa_invite.task_description',
|
||
reward: 1,
|
||
completed: true,
|
||
},
|
||
];
|
||
var state = {
|
||
wheelID: params.get('wheel_id') || params.get('wheelId') || 'cash',
|
||
busy: false,
|
||
pointBalance: useMock ? 89094 : 0,
|
||
cashValue: useMock ? 8.9 : 0,
|
||
ratePoints: '90K',
|
||
withdrawalRemaining: useMock ? 1000 : 0,
|
||
drawCost: 5,
|
||
prizes: DESIGN_PRIZES.slice(),
|
||
tasks: DESIGN_TASKS.slice(),
|
||
estimatedRewards: [],
|
||
inviteCode: '',
|
||
inviteLink: '',
|
||
spinCount: 0,
|
||
};
|
||
|
||
function $(selector) {
|
||
return document.querySelector(selector);
|
||
}
|
||
|
||
function $all(selector) {
|
||
return Array.prototype.slice.call(document.querySelectorAll(selector));
|
||
}
|
||
|
||
function t(key, fallback) {
|
||
if (window.HyAppI18n && window.HyAppI18n.t) {
|
||
return window.HyAppI18n.t(key, fallback);
|
||
}
|
||
return fallback || key;
|
||
}
|
||
|
||
function toast(message) {
|
||
var target = window.HyToast || window.HyAppToast;
|
||
if (target && target.show) {
|
||
target.show(message);
|
||
return;
|
||
}
|
||
window.alert(message);
|
||
}
|
||
|
||
function text(value) {
|
||
return String(
|
||
value === undefined || value === null ? '' : value
|
||
).trim();
|
||
}
|
||
|
||
function number(value, fallback) {
|
||
var result = Number(value);
|
||
return Number.isFinite(result) ? result : Number(fallback || 0);
|
||
}
|
||
|
||
function first(source, keys) {
|
||
source = source || {};
|
||
for (var index = 0; index < keys.length; index += 1) {
|
||
var value = source[keys[index]];
|
||
if (value !== undefined && value !== null && value !== '') {
|
||
return value;
|
||
}
|
||
}
|
||
return '';
|
||
}
|
||
|
||
function envelope(payload) {
|
||
if (payload && Object.prototype.hasOwnProperty.call(payload, 'data')) {
|
||
return payload.data;
|
||
}
|
||
return payload || {};
|
||
}
|
||
|
||
function parseJSON(value) {
|
||
if (!value || typeof value !== 'string') return {};
|
||
try {
|
||
return JSON.parse(value);
|
||
} catch (error) {
|
||
return {};
|
||
}
|
||
}
|
||
|
||
function formatNumber(value) {
|
||
var lang =
|
||
window.HyAppI18n && window.HyAppI18n.lang
|
||
? window.HyAppI18n.lang()
|
||
: 'en';
|
||
return new Intl.NumberFormat(lang).format(number(value));
|
||
}
|
||
|
||
function resolveAssetURL(value) {
|
||
value = text(value);
|
||
if (!value) return '';
|
||
if (
|
||
/^(https?:)?\/\//i.test(value) ||
|
||
/^(data|blob):/i.test(value) ||
|
||
value.charAt(0) === '/' ||
|
||
value.indexOf('./') === 0 ||
|
||
value.indexOf('../') === 0
|
||
) {
|
||
return value;
|
||
}
|
||
// 旧版资源组只返回 wheel/assets/other 下的裸文件名;统一补齐路径,避免把配置内容误当当前 invite 目录的相对文件。
|
||
return new URL('../wheel/assets/other/' + value, window.location.href)
|
||
.href;
|
||
}
|
||
|
||
function normalizeReward(item, fallback, index) {
|
||
item = item || {};
|
||
fallback = fallback || DESIGN_PRIZES[index % DESIGN_PRIZES.length];
|
||
var metadata = parseJSON(
|
||
first(item, ['metadata_json', 'metadataJson'])
|
||
);
|
||
var image =
|
||
first(metadata, [
|
||
'preview_url',
|
||
'previewUrl',
|
||
'asset_url',
|
||
'assetUrl',
|
||
]) ||
|
||
first(item, [
|
||
'cover_url',
|
||
'coverUrl',
|
||
'cover',
|
||
'resource_cover',
|
||
'resourceCover',
|
||
'preview_url',
|
||
'previewUrl',
|
||
'image_url',
|
||
'imageUrl',
|
||
'icon',
|
||
'avatar',
|
||
]);
|
||
var name =
|
||
first(item, [
|
||
'display_name',
|
||
'displayName',
|
||
'reward_name',
|
||
'rewardName',
|
||
'name',
|
||
]) || fallback.name;
|
||
var points = number(
|
||
first(item, [
|
||
'display_value',
|
||
'displayValue',
|
||
'reward_value',
|
||
'rewardValue',
|
||
'reward_coins',
|
||
'rewardCoins',
|
||
'points',
|
||
'value',
|
||
'amount',
|
||
]),
|
||
fallback.points
|
||
);
|
||
return {
|
||
id:
|
||
text(
|
||
first(item, [
|
||
'id',
|
||
'tier_id',
|
||
'tierId',
|
||
'reward_id',
|
||
'rewardId',
|
||
'resource_id',
|
||
'resourceId',
|
||
])
|
||
) || fallback.id,
|
||
name: text(name) || fallback.name,
|
||
image: resolveAssetURL(image) || fallback.image,
|
||
points: points,
|
||
raw: item,
|
||
};
|
||
}
|
||
|
||
function rewardItems(payload) {
|
||
var data = envelope(payload);
|
||
var values = Array.isArray(data)
|
||
? data
|
||
: data.rewards ||
|
||
data.tiers ||
|
||
data.items ||
|
||
(data.config && (data.config.rewards || data.config.tiers)) ||
|
||
[];
|
||
if (!Array.isArray(values)) return [];
|
||
return values.map(function (item, index) {
|
||
return normalizeReward(item, DESIGN_PRIZES[index], index);
|
||
});
|
||
}
|
||
|
||
function normalizeTasks(payload) {
|
||
var data = envelope(payload);
|
||
var values =
|
||
data.tasks ||
|
||
data.invite_tasks ||
|
||
data.inviteTasks ||
|
||
(data.config && data.config.tasks) ||
|
||
[];
|
||
if (!Array.isArray(values)) return [];
|
||
return values.slice(0, 3).map(function (item, index) {
|
||
var status = text(first(item, ['status', 'state'])).toLowerCase();
|
||
return {
|
||
id:
|
||
text(first(item, ['id', 'task_id', 'taskId'])) ||
|
||
'task-' + index,
|
||
title:
|
||
text(first(item, ['title', 'name'])) ||
|
||
DESIGN_TASKS[index].title,
|
||
description:
|
||
text(
|
||
first(item, ['description', 'desc', 'subtitle', 'hint'])
|
||
) || DESIGN_TASKS[index].description,
|
||
reward: number(
|
||
first(item, [
|
||
'reward_spins',
|
||
'rewardSpins',
|
||
'reward',
|
||
'value',
|
||
]),
|
||
1
|
||
),
|
||
completed:
|
||
first(item, [
|
||
'completed',
|
||
'is_completed',
|
||
'isCompleted',
|
||
]) === true ||
|
||
status === 'completed' ||
|
||
status === 'claimed',
|
||
action: first(item, ['action', 'action_url', 'actionUrl']),
|
||
raw: item,
|
||
};
|
||
});
|
||
}
|
||
|
||
function normalizeWalletBalance(payload) {
|
||
var data = envelope(payload);
|
||
var values = Array.isArray(data)
|
||
? data
|
||
: data.balances || data.values || data.list || [];
|
||
if (!Array.isArray(values)) return 0;
|
||
var wallet = values.find(function (item) {
|
||
return (
|
||
text(
|
||
first(item, [
|
||
'asset_type',
|
||
'assetType',
|
||
'wallet_type',
|
||
'walletType',
|
||
'type',
|
||
])
|
||
).toUpperCase() === 'COIN'
|
||
);
|
||
});
|
||
if (!wallet) return 0;
|
||
return number(
|
||
first(wallet, [
|
||
'available_amount',
|
||
'availableAmount',
|
||
'balance',
|
||
'amount',
|
||
])
|
||
);
|
||
}
|
||
|
||
function createCommandID() {
|
||
var random = Math.random().toString(36).slice(2);
|
||
if (window.crypto && window.crypto.getRandomValues) {
|
||
var values = new Uint32Array(2);
|
||
window.crypto.getRandomValues(values);
|
||
random = values[0].toString(36) + values[1].toString(36);
|
||
}
|
||
return 'huwaa_cash_' + Date.now().toString(36) + '_' + random;
|
||
}
|
||
|
||
function setText(selector, value) {
|
||
var node = $(selector);
|
||
if (node) node.textContent = value;
|
||
}
|
||
|
||
function renderWallet() {
|
||
setText('#huwaaRatePoints', state.ratePoints);
|
||
setText('#huwaaPointBalance', formatNumber(state.pointBalance));
|
||
setText(
|
||
'#huwaaCashValue',
|
||
'≈$' +
|
||
number(state.cashValue, state.pointBalance / 10000).toFixed(1)
|
||
);
|
||
setText('#huwaaPointsLeft', formatNumber(state.withdrawalRemaining));
|
||
setText('#huwaaSpinCost', '*' + formatNumber(state.drawCost));
|
||
}
|
||
|
||
function renderPrizes() {
|
||
$all('.huwaa-prize').forEach(function (button, index) {
|
||
var prize = state.prizes[index] || DESIGN_PRIZES[index];
|
||
var image = button.querySelector('img');
|
||
var label = button.querySelector('span');
|
||
image.src = prize.image;
|
||
image.onerror = function () {
|
||
image.onerror = null;
|
||
image.src = DESIGN_PRIZES[index].image;
|
||
};
|
||
var prizeName = prize.raw
|
||
? prize.name
|
||
: t('huwaa_invite.medium', 'Medium');
|
||
label.textContent = prizeName;
|
||
button.setAttribute(
|
||
'aria-label',
|
||
prizeName +
|
||
(prize.points
|
||
? ', ' + formatNumber(prize.points) + ' points'
|
||
: '')
|
||
);
|
||
});
|
||
}
|
||
|
||
function renderTasks() {
|
||
var list = $('#huwaaTaskList');
|
||
var template = $('#huwaaTaskTemplate');
|
||
if (!list || !template) return;
|
||
list.textContent = '';
|
||
state.tasks.slice(0, 3).forEach(function (task) {
|
||
var fragment = template.content.cloneNode(true);
|
||
var card = fragment.querySelector('.huwaa-task-card');
|
||
var status = fragment.querySelector('[data-task-status]');
|
||
fragment.querySelector('[data-task-title]').textContent =
|
||
task.titleKey ? t(task.titleKey, task.title) : task.title;
|
||
fragment.querySelector('[data-task-description]').textContent =
|
||
task.descriptionKey
|
||
? t(task.descriptionKey, task.description)
|
||
: task.description;
|
||
fragment.querySelector('[data-task-reward]').textContent =
|
||
'+' + formatNumber(task.reward);
|
||
card.classList.toggle('is-pending', !task.completed);
|
||
if (task.completed) {
|
||
status.textContent = t('huwaa_invite.completed', 'COMPLETED');
|
||
} else {
|
||
var action = document.createElement('button');
|
||
action.className = status.className;
|
||
action.type = 'button';
|
||
action.textContent = t('huwaa_invite.go', 'GO');
|
||
action.addEventListener('click', function () {
|
||
openTask(task);
|
||
});
|
||
status.replaceWith(action);
|
||
}
|
||
list.appendChild(fragment);
|
||
});
|
||
}
|
||
|
||
function defaultEstimatedRewards() {
|
||
var values = [];
|
||
for (var index = 0; index < 12; index += 1) {
|
||
values.push({
|
||
id: 'estimated-' + index,
|
||
name: useMock ? 'DDDDD' : t('huwaa_invite.reward', 'Reward'),
|
||
image: ASSET_BASE + 'reward-calendar-check.webp',
|
||
points: 0,
|
||
});
|
||
}
|
||
return values;
|
||
}
|
||
|
||
function renderEstimatedRewards() {
|
||
var grid = $('#huwaaEstimatedGrid');
|
||
var template = $('#huwaaEstimatedTemplate');
|
||
if (!grid || !template) return;
|
||
var rewards = state.estimatedRewards.length
|
||
? state.estimatedRewards
|
||
: defaultEstimatedRewards();
|
||
grid.textContent = '';
|
||
rewards.slice(0, 12).forEach(function (reward) {
|
||
var fragment = template.content.cloneNode(true);
|
||
var image = fragment.querySelector('img');
|
||
image.src =
|
||
reward.image || ASSET_BASE + 'reward-calendar-check.webp';
|
||
image.onerror = function () {
|
||
image.onerror = null;
|
||
image.src = ASSET_BASE + 'reward-calendar-check.webp';
|
||
};
|
||
fragment.querySelector('strong').textContent =
|
||
reward.name || t('huwaa_invite.reward', 'Reward');
|
||
grid.appendChild(fragment);
|
||
});
|
||
}
|
||
|
||
function renderAll() {
|
||
renderWallet();
|
||
renderPrizes();
|
||
renderTasks();
|
||
renderEstimatedRewards();
|
||
}
|
||
|
||
function setBusy(busy) {
|
||
state.busy = Boolean(busy);
|
||
$('#huwaaStage').classList.toggle('is-busy', state.busy);
|
||
$('#huwaaSpinButton').disabled = state.busy;
|
||
$('#huwaaStage').setAttribute('aria-busy', String(state.busy));
|
||
}
|
||
|
||
function setActivePrize(index) {
|
||
$all('.huwaa-prize').forEach(function (node, nodeIndex) {
|
||
node.classList.toggle('is-active', nodeIndex === index);
|
||
});
|
||
}
|
||
|
||
function wait(milliseconds) {
|
||
return new Promise(function (resolve) {
|
||
window.setTimeout(resolve, milliseconds);
|
||
});
|
||
}
|
||
|
||
function animateSpin(targetIndex) {
|
||
var total = state.prizes.length || DESIGN_PRIZES.length;
|
||
var loops = total * 2 + targetIndex + 1;
|
||
var chain = Promise.resolve();
|
||
for (var step = 0; step < loops; step += 1) {
|
||
(function (currentStep) {
|
||
chain = chain.then(function () {
|
||
setActivePrize(currentStep % total);
|
||
return wait(60 + Math.max(0, currentStep - loops + 6) * 25);
|
||
});
|
||
})(step);
|
||
}
|
||
return chain.then(function () {
|
||
setActivePrize(targetIndex);
|
||
});
|
||
}
|
||
|
||
function rewardTargetIndex(reward) {
|
||
if (!reward) return 0;
|
||
var id = text(reward.id).toLowerCase();
|
||
var name = text(reward.name).toLowerCase();
|
||
var points = number(reward.points);
|
||
var index = state.prizes.findIndex(function (prize) {
|
||
return id && text(prize.id).toLowerCase() === id;
|
||
});
|
||
if (index >= 0) return index;
|
||
index = state.prizes.findIndex(function (prize) {
|
||
return (
|
||
name &&
|
||
text(prize.name).toLowerCase() === name &&
|
||
(!points || number(prize.points) === points)
|
||
);
|
||
});
|
||
return index >= 0 ? index : 0;
|
||
}
|
||
|
||
function normalizeDrawResult(payload) {
|
||
var data = envelope(payload);
|
||
var rawRewards =
|
||
data.rewards ||
|
||
data.records ||
|
||
data.items ||
|
||
data.reward_items ||
|
||
[];
|
||
var firstReward = Array.isArray(rawRewards)
|
||
? rawRewards[0]
|
||
: rawRewards;
|
||
var normalized = normalizeReward(
|
||
firstReward || data,
|
||
state.prizes[0],
|
||
0
|
||
);
|
||
var balanceAfter = first(data, [
|
||
'coin_balance_after',
|
||
'coinBalanceAfter',
|
||
'balance_after',
|
||
'balanceAfter',
|
||
]);
|
||
return {
|
||
reward: normalized,
|
||
balanceAfter:
|
||
balanceAfter === '' ? undefined : number(balanceAfter),
|
||
};
|
||
}
|
||
|
||
function mockDrawResult() {
|
||
var index = state.spinCount % state.prizes.length;
|
||
state.spinCount += 1;
|
||
return Promise.resolve({
|
||
reward: Object.assign({}, state.prizes[index], {
|
||
name: 'Medium Reward',
|
||
points: 500,
|
||
}),
|
||
// 视觉夹具保持 Figma 三个状态使用同一余额;真实环境只接受服务端返回的扣减后余额。
|
||
balanceAfter: undefined,
|
||
});
|
||
}
|
||
|
||
function requestDraw() {
|
||
if (useMock) return mockDrawResult();
|
||
var api = window.HyAppAPI && window.HyAppAPI.activityWheel;
|
||
var hasToken =
|
||
window.HyAppAPI &&
|
||
window.HyAppAPI.getAccessToken &&
|
||
window.HyAppAPI.getAccessToken();
|
||
if (!api || !api.draw || !hasToken) {
|
||
return Promise.reject(
|
||
new Error(
|
||
t('huwaa_invite.login_required', 'Please log in first')
|
||
)
|
||
);
|
||
}
|
||
// 抽奖金额和 command_id 必须一次性发给服务端;页面不在失败时本地补发奖励,避免网络错误造成视觉中奖但账本未入账。
|
||
return api
|
||
.draw(state.wheelID, {
|
||
command_id: createCommandID(),
|
||
amount: state.drawCost,
|
||
times: 1,
|
||
})
|
||
.then(normalizeDrawResult);
|
||
}
|
||
|
||
function showWin(reward) {
|
||
reward = reward || state.prizes[0];
|
||
var image = $('#huwaaWinImage');
|
||
image.src = reward.image || state.prizes[0].image;
|
||
image.onerror = function () {
|
||
image.onerror = null;
|
||
image.src = state.prizes[0].image;
|
||
};
|
||
setText(
|
||
'#huwaaWinReward',
|
||
reward.raw
|
||
? reward.name
|
||
: t('huwaa_invite.medium_reward', 'Medium Reward')
|
||
);
|
||
setText(
|
||
'#huwaaWinPoints',
|
||
'(' +
|
||
formatNumber(reward.points || 0) +
|
||
' ' +
|
||
t('huwaa_invite.points_label', 'Points') +
|
||
')'
|
||
);
|
||
$('#huwaaWinModal').hidden = false;
|
||
syncModalLock();
|
||
$('#huwaaWinTitle').focus({ preventScroll: true });
|
||
}
|
||
|
||
function closeWin() {
|
||
$('#huwaaWinModal').hidden = true;
|
||
syncModalLock();
|
||
setActivePrize(-1);
|
||
$('#huwaaSpinButton').focus({ preventScroll: true });
|
||
}
|
||
|
||
function startSpin() {
|
||
if (state.busy) return;
|
||
setBusy(true);
|
||
requestDraw()
|
||
.then(function (result) {
|
||
var target = rewardTargetIndex(result.reward);
|
||
return animateSpin(target).then(function () {
|
||
if (result.balanceAfter !== undefined) {
|
||
state.pointBalance = result.balanceAfter;
|
||
state.cashValue = state.pointBalance / 10000;
|
||
renderWallet();
|
||
}
|
||
showWin(result.reward);
|
||
if (!useMock) loadEstimatedRewards();
|
||
});
|
||
})
|
||
.catch(function (error) {
|
||
setActivePrize(-1);
|
||
toast(
|
||
(error && error.message) ||
|
||
t('huwaa_invite.request_failed', 'Request failed')
|
||
);
|
||
})
|
||
.then(function () {
|
||
setBusy(false);
|
||
});
|
||
}
|
||
|
||
function openRewardDrawer() {
|
||
renderEstimatedRewards();
|
||
$('#huwaaRewardDrawer').hidden = false;
|
||
syncModalLock();
|
||
$('#huwaaRewardTitle').focus
|
||
? $('#huwaaRewardTitle').focus({ preventScroll: true })
|
||
: undefined;
|
||
}
|
||
|
||
function closeRewardDrawer() {
|
||
$('#huwaaRewardDrawer').hidden = true;
|
||
syncModalLock();
|
||
$('#huwaaRewardEntry').focus({ preventScroll: true });
|
||
}
|
||
|
||
function syncModalLock() {
|
||
document.documentElement.classList.toggle(
|
||
'huwaa-modal-open',
|
||
!$('#huwaaWinModal').hidden || !$('#huwaaRewardDrawer').hidden
|
||
);
|
||
}
|
||
|
||
function openTask(task) {
|
||
if (task.completed) return;
|
||
if (task.action) {
|
||
window.location.href = task.action;
|
||
return;
|
||
}
|
||
if (window.HyAppBridge && window.HyAppBridge.post) {
|
||
window.HyAppBridge.post('huwaaInviteTaskAction', {
|
||
task_id: task.id,
|
||
});
|
||
return;
|
||
}
|
||
toast(t('huwaa_invite.open_app_task', 'Open this task in Huwaa'));
|
||
}
|
||
|
||
function currentInviteLandingURL() {
|
||
return new URL('../invite-landing/', window.location.href);
|
||
}
|
||
|
||
function buildInviteLink() {
|
||
if (!state.inviteCode) return '';
|
||
var url = currentInviteLandingURL();
|
||
['env', 'lang', 'app_code', 'appCode'].forEach(function (key) {
|
||
var value = text(params.get(key));
|
||
if (value) url.searchParams.set(key, value);
|
||
});
|
||
url.searchParams.set('app_code', 'huwaa');
|
||
url.searchParams.set('invite_code', state.inviteCode);
|
||
url.searchParams.set('inviteCode', state.inviteCode);
|
||
url.searchParams.set('code', state.inviteCode);
|
||
return url.toString();
|
||
}
|
||
|
||
function copyText(value) {
|
||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||
return navigator.clipboard.writeText(value);
|
||
}
|
||
return new Promise(function (resolve, reject) {
|
||
var input = document.createElement('textarea');
|
||
input.value = value;
|
||
input.setAttribute('readonly', 'readonly');
|
||
input.style.position = 'fixed';
|
||
input.style.left = '-9999px';
|
||
document.body.appendChild(input);
|
||
input.select();
|
||
var copied = false;
|
||
try {
|
||
copied = document.execCommand('copy');
|
||
} catch (error) {
|
||
copied = false;
|
||
}
|
||
input.remove();
|
||
copied ? resolve() : reject(new Error('copy failed'));
|
||
});
|
||
}
|
||
|
||
function shareInvite() {
|
||
var link = state.inviteLink || buildInviteLink();
|
||
if (!link) {
|
||
toast(
|
||
t(
|
||
'huwaa_invite.invite_unavailable',
|
||
'Invitation link is not available yet'
|
||
)
|
||
);
|
||
return;
|
||
}
|
||
if (navigator.share) {
|
||
navigator
|
||
.share({
|
||
title: t('huwaa_invite.invite_now', 'Invite Friends Now'),
|
||
url: link,
|
||
})
|
||
.catch(function (error) {
|
||
if (error && error.name === 'AbortError') return;
|
||
copyInviteLink(link);
|
||
});
|
||
return;
|
||
}
|
||
copyInviteLink(link);
|
||
}
|
||
|
||
function copyInviteLink(link) {
|
||
copyText(link)
|
||
.then(function () {
|
||
toast(t('huwaa_invite.copied', 'Copied'));
|
||
})
|
||
.catch(function () {
|
||
toast(t('huwaa_invite.copy_failed', 'Copy failed'));
|
||
});
|
||
}
|
||
|
||
function withdraw() {
|
||
var walletURL = text(
|
||
params.get('wallet_url') || params.get('walletUrl')
|
||
);
|
||
if (walletURL) {
|
||
window.location.href = walletURL;
|
||
return;
|
||
}
|
||
if (window.HyAppBridge && window.HyAppBridge.post) {
|
||
window.HyAppBridge.post('huwaaCashWheelWithdraw', {
|
||
points: state.pointBalance,
|
||
});
|
||
return;
|
||
}
|
||
toast(
|
||
t('huwaa_invite.open_wallet', 'Open the Huwaa wallet to withdraw')
|
||
);
|
||
}
|
||
|
||
function goBack() {
|
||
if (window.HyAppBridge && window.HyAppBridge.back) {
|
||
window.HyAppBridge.back();
|
||
return;
|
||
}
|
||
if (window.history.length > 1) {
|
||
window.history.back();
|
||
}
|
||
}
|
||
|
||
function applyWheelConfig(payload) {
|
||
var data = envelope(payload);
|
||
var prizes = rewardItems(data);
|
||
var tasks = normalizeTasks(data);
|
||
var drawCost = number(
|
||
first(data, [
|
||
'draw_price_coins',
|
||
'drawPriceCoins',
|
||
'draw_price',
|
||
'drawPrice',
|
||
'spin_cost',
|
||
'spinCost',
|
||
])
|
||
);
|
||
if (prizes.length) {
|
||
state.prizes = prizes.slice(0, 7);
|
||
while (state.prizes.length < 7) {
|
||
state.prizes.push(DESIGN_PRIZES[state.prizes.length]);
|
||
}
|
||
if (!state.estimatedRewards.length) {
|
||
state.estimatedRewards = prizes.slice(0, 12);
|
||
}
|
||
}
|
||
if (tasks.length) state.tasks = tasks;
|
||
if (drawCost > 0) state.drawCost = drawCost;
|
||
var threshold = number(
|
||
first(data, [
|
||
'withdraw_threshold',
|
||
'withdrawThreshold',
|
||
'withdraw_points',
|
||
'withdrawPoints',
|
||
])
|
||
);
|
||
if (threshold > 0) {
|
||
state.ratePoints =
|
||
threshold >= 1000
|
||
? Math.round(threshold / 1000) + 'K'
|
||
: formatNumber(threshold);
|
||
state.withdrawalRemaining = Math.max(
|
||
0,
|
||
threshold - state.pointBalance
|
||
);
|
||
}
|
||
renderAll();
|
||
}
|
||
|
||
function loadWheelConfig() {
|
||
var api = window.HyAppAPI && window.HyAppAPI.activityWheel;
|
||
if (useMock || !api || !api.config) return Promise.resolve();
|
||
return api
|
||
.config(state.wheelID)
|
||
.then(applyWheelConfig)
|
||
.catch(function () {
|
||
// 配置缺失时保留 Figma 的独立本地视觉层,但抽奖仍走服务端且会失败提示,不在前端伪造奖励。
|
||
});
|
||
}
|
||
|
||
function loadWallet() {
|
||
var api = window.HyAppAPI && window.HyAppAPI.wallet;
|
||
var hasToken =
|
||
window.HyAppAPI &&
|
||
window.HyAppAPI.getAccessToken &&
|
||
window.HyAppAPI.getAccessToken();
|
||
if (useMock || !api || !api.balances || !hasToken) {
|
||
return Promise.resolve();
|
||
}
|
||
return api
|
||
.balances('COIN')
|
||
.then(function (payload) {
|
||
state.pointBalance = normalizeWalletBalance(payload);
|
||
state.cashValue = state.pointBalance / 10000;
|
||
renderWallet();
|
||
})
|
||
.catch(function () {
|
||
// 钱包失败不能阻塞页面结构;余额保持 0,实际抽奖仍由服务端余额校验兜底。
|
||
});
|
||
}
|
||
|
||
function normalizeHistoryRewards(payload) {
|
||
var data = envelope(payload);
|
||
var records = data.records || data.items || data.values || [];
|
||
if (!Array.isArray(records)) return [];
|
||
var rewards = [];
|
||
records.forEach(function (record) {
|
||
var values = record.rewards ||
|
||
record.items ||
|
||
record.reward_items || [record];
|
||
if (!Array.isArray(values)) values = [values];
|
||
values.forEach(function (value) {
|
||
rewards.push(
|
||
normalizeReward(
|
||
value,
|
||
DESIGN_PRIZES[rewards.length % DESIGN_PRIZES.length],
|
||
rewards.length
|
||
)
|
||
);
|
||
});
|
||
});
|
||
return rewards.slice(0, 12);
|
||
}
|
||
|
||
function loadEstimatedRewards() {
|
||
var api = window.HyAppAPI && window.HyAppAPI.activityWheel;
|
||
var hasToken =
|
||
window.HyAppAPI &&
|
||
window.HyAppAPI.getAccessToken &&
|
||
window.HyAppAPI.getAccessToken();
|
||
if (useMock || !api || !api.history || !hasToken) {
|
||
renderEstimatedRewards();
|
||
return Promise.resolve();
|
||
}
|
||
return api
|
||
.history(state.wheelID, { pageNo: 1, pageSize: 12 })
|
||
.then(function (payload) {
|
||
var rewards = normalizeHistoryRewards(payload);
|
||
if (rewards.length) state.estimatedRewards = rewards;
|
||
renderEstimatedRewards();
|
||
})
|
||
.catch(function () {
|
||
renderEstimatedRewards();
|
||
});
|
||
}
|
||
|
||
function applyInviteOverview(payload) {
|
||
var data = envelope(payload);
|
||
var invite = data.invite || (data.profile && data.profile.invite) || {};
|
||
state.inviteCode = text(
|
||
first(invite, [
|
||
'my_invite_code',
|
||
'myInviteCode',
|
||
'invite_code',
|
||
'inviteCode',
|
||
]) ||
|
||
first(data, [
|
||
'my_invite_code',
|
||
'myInviteCode',
|
||
'invite_code',
|
||
'inviteCode',
|
||
])
|
||
);
|
||
state.inviteLink = buildInviteLink();
|
||
}
|
||
|
||
function loadInviteOverview() {
|
||
if (useMock) {
|
||
state.inviteCode = 'HUWAA2026';
|
||
state.inviteLink = buildInviteLink();
|
||
return Promise.resolve();
|
||
}
|
||
var api = window.HyAppAPI;
|
||
var hasToken = api && api.getAccessToken && api.getAccessToken();
|
||
if (!api || !api.get || !hasToken) return Promise.resolve();
|
||
return api
|
||
.get('/api/v1/users/me/invite-overview')
|
||
.then(applyInviteOverview)
|
||
.catch(function () {
|
||
// 分享按钮在邀请数据缺失时保持不可分享状态,绝不拼接伪邀请码。
|
||
});
|
||
}
|
||
|
||
function bindEvents() {
|
||
$('#huwaaBackButton').addEventListener('click', goBack);
|
||
$('#huwaaWithdrawButton').addEventListener('click', withdraw);
|
||
$('#huwaaSpinButton').addEventListener('click', startSpin);
|
||
$('#huwaaInviteButton').addEventListener('click', shareInvite);
|
||
$('#huwaaRewardEntry').addEventListener('click', openRewardDrawer);
|
||
$('#huwaaAwesomeButton').addEventListener('click', closeWin);
|
||
$('[data-close-win]').addEventListener('click', closeWin);
|
||
$('[data-close-rewards]').addEventListener('click', closeRewardDrawer);
|
||
$all('.huwaa-prize').forEach(function (button) {
|
||
button.addEventListener('click', openRewardDrawer);
|
||
});
|
||
document.addEventListener('keydown', function (event) {
|
||
if (event.key !== 'Escape') return;
|
||
if (!$('#huwaaWinModal').hidden) closeWin();
|
||
if (!$('#huwaaRewardDrawer').hidden) closeRewardDrawer();
|
||
});
|
||
}
|
||
|
||
function init() {
|
||
bindEvents();
|
||
renderAll();
|
||
$('#huwaaPage').dataset.state = 'loading';
|
||
Promise.all([
|
||
loadWallet(),
|
||
loadWheelConfig(),
|
||
loadEstimatedRewards(),
|
||
loadInviteOverview(),
|
||
]).then(function () {
|
||
$('#huwaaPage').dataset.state = 'ready';
|
||
});
|
||
if (window.HyAppBridge && window.HyAppBridge.ready) {
|
||
window.HyAppBridge.ready({
|
||
page: 'huwaa-invite-cash-wheel',
|
||
app_code: 'huwaa',
|
||
});
|
||
}
|
||
}
|
||
|
||
window.addEventListener('hyapp:i18n-ready', function () {
|
||
renderAll();
|
||
});
|
||
|
||
window.__HuwaaInvite = {
|
||
state: state,
|
||
openRewards: openRewardDrawer,
|
||
spin: startSpin,
|
||
};
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
} else {
|
||
init();
|
||
}
|
||
})();
|