hyapp-h5/activity/wheel/script.js
2026-06-18 18:03:49 +08:00

1356 lines
50 KiB
JavaScript

(function () {
var ASSET_BASE = './assets/other/';
var COMMON_FALLBACK = {
loading: 'Loading',
rule: 'rule',
rewards: 'Rewards',
balance: 'Balance',
noDate: 'No more data',
balanceNotEnough: 'Insufficient balance',
rewardUnavailable: 'Rewards are not available yet',
requestFailed: 'Request failed',
};
var WHEEL_FALLBACK = {
classic: 'Classic',
inLove: 'luxury',
advanced: 'Advanced',
historyRecord: 'History Record',
rewardTips:
'Congratulations on receiving: A precious gift worth {} gold coins! Already put in the backpack',
drawAgain: 'Draw again',
userAward: 'Congratulations to {1} for receiving the {2} award',
rule1: '1. Select a wheel and draw with coins.',
rule2: '2. Rewards are issued to the backpack after a successful draw.',
rule3: '3. This activity has no relationship with Apple or Google.',
};
var TABS = [
{ key: 'classic', labelKey: 'classic' },
{ key: 'luxury', labelKey: 'inLove' },
{ key: 'advanced', labelKey: 'advanced' },
];
var CONFIGS = {
classic: {
key: 'classic',
amountKey: 'luckyBoxDrawAmount',
enabledKey: 'luckyBoxEnabled',
pageSize: 10,
type: 'grid',
gridClass: 'grid-classic',
stageClass: 'wheel-classic',
lightPrefix: 'lucky_box_',
minSpeed: 2000,
sequence: [1, 2, 3, 5, 8, 7, 6, 4, 1, 2, 3, 5, 8, 7, 6, 4, 1],
},
luxury: {
key: 'luxury',
amountKey: 'middleLuckyBoxDrawAmount',
enabledKey: 'middleLuckyBoxEnabled',
pageSize: 20,
type: 'round',
gridClass: 'grid-luxury',
stageClass: 'wheel-luxury',
lightPrefix: 'lucky_box_tow_',
hasMarqueeBox: true,
},
advanced: {
key: 'advanced',
amountKey: 'advancedLuckyBoxDrawAmount',
enabledKey: 'advancedLuckyBoxEnabled',
pageSize: 10,
type: 'grid',
gridClass: 'grid-advanced',
stageClass: 'wheel-advanced',
minSpeed: 1000,
hasMarqueeBox: true,
sequence: [1, 2, 3, 4, 6, 8, 12, 11, 10, 9, 7, 5, 1],
},
};
function query() {
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 readQuery(name) {
return query().get(name) || '';
}
function toast(message) {
if (window.HyAppToast && window.HyAppToast.show) {
window.HyAppToast.show(message);
return;
}
window.alert(message);
}
function stateFor() {
return {
rewards: [],
gridItems: [],
activeIndex: null,
isSpinning: false,
spinInterval: null,
spinSpeed: 50,
targetIndex: null,
goldBtn: [
{ name: '1 Time', value: 1 },
{ name: '10 Time', value: 10 },
{ name: '50 Time', value: 50 },
],
activeBtnIndex: 1,
showRule: false,
showRecord: false,
showReward: false,
currentReward: { rewardValue: 0, rewards: [] },
drawAmount: 0,
historyRecord: [],
roomId: '',
loading: false,
finished: true,
pageNo: 1,
isInit: false,
pageLoading: false,
drawFailed: false,
currentBackground: 'light',
backgroundInterval: null,
rotateDeg: 0,
rotationTimer: null,
};
}
function envelopeData(response) {
if (response && Object.prototype.hasOwnProperty.call(response, 'data'))
return response.data;
return response;
}
function firstPresentValue(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 normalizeCompareValue(value) {
return String(value || '')
.trim()
.toLowerCase();
}
function resolveAssetUrl(url) {
var value = String(url || '').trim();
if (!value) return '';
if (
/^(https?:)?\/\//i.test(value) ||
/^(data|blob):/i.test(value) ||
value.indexOf('/') === 0 ||
value.indexOf('./') === 0 ||
value.indexOf('../') === 0
)
return value;
return ASSET_BASE + value;
}
function isDynamicAssetUrl(url) {
var clean = String(url || '')
.split('?')[0]
.split('#')[0];
return /\.(svga|pag|mp4|webm|mov)$/i.test(clean);
}
function positiveModulo(value, size) {
return ((value % size) + size) % size;
}
function roundPrizeAngle(index, count) {
var sectorAngle = 360 / Math.max(1, count || 1);
// 奖品图标本身按这个角度排布,指针停止也必须使用同一个基准,否则会停在扇区中间而不是奖品上。
return index * sectorAngle + sectorAngle / 50;
}
function normalizeImageObject(source, keys, fallbackKey) {
var target = Object.assign({}, source || {});
var image = firstPresentValue(target, keys);
if (image) target[fallbackKey] = resolveAssetUrl(image);
return target;
}
function normalizeGridItem(item) {
var target = Object.assign({}, item || {});
var merchandise = normalizeImageObject(
target.merchandise || target,
[
'coverUrl',
'cover',
'resourceCover',
'avatar',
'icon',
'imageUrl',
],
'coverUrl'
);
target.merchandise = merchandise;
return target;
}
function parseJSON(value) {
if (!value || typeof value !== 'string') return {};
try {
return JSON.parse(value);
} catch (error) {
return {};
}
}
function normalizeWheelReward(item) {
var target = Object.assign({}, item || {});
var metadata = parseJSON(
firstPresentValue(target, ['metadataJson', 'metadata_json'])
);
var rewardType = firstPresentValue(target, [
'rewardType',
'reward_type',
]);
var metadataResourceId = firstPresentValue(metadata, [
'resource_id',
'resourceId',
]);
var rewardId =
firstPresentValue(target, [
'rewardId',
'reward_id',
'resourceId',
'resource_id',
]) || metadataResourceId;
var metadataAssetUrl = firstPresentValue(metadata, [
'asset_url',
'assetUrl',
]);
var previewUrl =
firstPresentValue(metadata, ['preview_url', 'previewUrl']) ||
(isDynamicAssetUrl(metadataAssetUrl) ? '' : metadataAssetUrl) ||
firstPresentValue(target, [
'coverUrl',
'cover',
'resourceCover',
'avatar',
'icon',
'imageUrl',
'previewUrl',
'preview_url',
]);
var animationUrl = firstPresentValue(metadata, [
'animation_url',
'animationUrl',
]);
if (
!isDynamicAssetUrl(animationUrl) &&
isDynamicAssetUrl(metadataAssetUrl)
) {
animationUrl = metadataAssetUrl;
}
var displayName = firstPresentValue(target, [
'displayName',
'display_name',
'name',
'rewardName',
]);
var rewardCoins = Number(
firstPresentValue(target, [
'rewardCoins',
'reward_coins',
'value',
]) || 0
);
var displayValue = Number(
firstPresentValue(target, [
'displayValue',
'display_value',
'rewardValue',
'reward_value',
'value',
'rewardCoins',
'reward_coins',
]) || 0
);
var rewardCount = Number(
firstPresentValue(target, [
'rewardCount',
'reward_count',
'count',
]) || 1
);
target.id =
firstPresentValue(target, ['id', 'tierId', 'tier_id']) ||
rewardId ||
displayName;
target.tierId = firstPresentValue(target, ['tierId', 'tier_id']);
target.rewardId = rewardId;
target.rewardConfigId = firstPresentValue(target, [
'rewardConfigId',
'reward_config_id',
]);
target.resourceId =
firstPresentValue(target, ['resourceId', 'resource_id']) ||
metadataResourceId;
target.rewardType = rewardType;
target.value = displayValue || rewardCoins || rewardCount;
target.displayGoldAmount = target.value;
target.rewardCount = rewardCount;
target.count = rewardCount;
target.name = displayName;
target.coverUrl = resolveAssetUrl(previewUrl);
target.animationUrl =
rewardType === 'coin' ? '' : resolveAssetUrl(animationUrl);
target.merchandise = {
name: displayName,
coverUrl: target.coverUrl,
animationUrl: target.animationUrl,
};
return normalizeGridItem(target);
}
function normalizeWheelRewards(data) {
data = data || {};
var items = Array.isArray(data)
? data
: data.rewards || data.tiers || (data.config && data.config.tiers);
return Array.isArray(items)
? items.map(normalizeWheelReward).map(normalizeGridItem)
: [];
}
function normalizeRewardItem(item) {
var target = normalizeImageObject(
item,
[
'coverUrl',
'cover',
'resourceCover',
'avatar',
'icon',
'imageUrl',
'previewUrl',
'preview_url',
],
'coverUrl'
);
var metadata = parseJSON(
firstPresentValue(target, ['metadataJson', 'metadata_json'])
);
var assetUrl =
firstPresentValue(metadata, ['asset_url', 'assetUrl']) ||
firstPresentValue(target, ['assetUrl', 'asset_url']);
if (isDynamicAssetUrl(target.coverUrl)) target.coverUrl = '';
if (!target.coverUrl && assetUrl && !isDynamicAssetUrl(assetUrl)) {
target.coverUrl = resolveAssetUrl(assetUrl);
}
var animationUrl =
firstPresentValue(metadata, ['animation_url', 'animationUrl']) ||
firstPresentValue(target, ['animationUrl', 'animation_url']);
if (!isDynamicAssetUrl(animationUrl) && isDynamicAssetUrl(assetUrl)) {
animationUrl = assetUrl;
}
target.animationUrl = isDynamicAssetUrl(animationUrl)
? resolveAssetUrl(animationUrl)
: '';
return target;
}
function rewardCountValue(item) {
var value = Number(
firstPresentValue(item, ['count', 'rewardCount', 'reward_count']) ||
0
);
return value > 0 ? value : 1;
}
function rewardGroupKey(item) {
item = item || {};
var metadata = parseJSON(
firstPresentValue(item, ['metadataJson', 'metadata_json'])
);
var metadataResourceId = firstPresentValue(metadata, [
'resource_id',
'resourceId',
]);
return [
firstPresentValue(item, [
'resourceId',
'resource_id',
'rewardId',
'reward_id',
'id',
]) || metadataResourceId,
firstPresentValue(item, [
'rewardType',
'reward_type',
'resourceType',
'resource_type',
]) ||
firstPresentValue(metadata, ['resource_type', 'resourceType']),
firstPresentValue(item, ['animationUrl', 'animation_url']),
firstPresentValue(item, [
'coverUrl',
'cover_url',
'previewUrl',
'preview_url',
'cover',
]),
firstPresentValue(item, [
'name',
'displayName',
'display_name',
'rewardName',
'reward_name',
]),
rewardDisplayValue(item),
]
.map(function (value) {
return normalizeCompareValue(value);
})
.join('|');
}
function aggregateRewardItems(items) {
var map = {};
var list = [];
(items || []).forEach(function (item) {
var key = rewardGroupKey(item);
var count = rewardCountValue(item);
if (!map[key]) {
map[key] = Object.assign({}, item, {
count: 0,
rewardCount: 0,
});
list.push(map[key]);
}
map[key].count += count;
map[key].rewardCount = map[key].count;
});
return list;
}
function normalizeHistoryRecord(record) {
var target = Object.assign({}, record || {});
var rewards = Array.isArray(target.rewards) ? target.rewards : [];
target.rewards = rewards.map(normalizeRewardItem);
return target;
}
function normalizeHintItem(item) {
return normalizeImageObject(
item,
[
'avatar',
'coverUrl',
'cover',
'resourceCover',
'icon',
'imageUrl',
],
'avatar'
);
}
function prizePreviewSource(item) {
item = item || {};
var merchandise = item.merchandise || {};
var assetUrl =
firstPresentValue(merchandise, ['assetUrl', 'asset_url']) ||
firstPresentValue(item, ['assetUrl', 'asset_url']);
var animationUrl =
firstPresentValue(merchandise, ['animationUrl', 'animation_url']) ||
firstPresentValue(item, ['animationUrl', 'animation_url']);
if (!isDynamicAssetUrl(animationUrl) && isDynamicAssetUrl(assetUrl)) {
animationUrl = assetUrl;
}
var coverUrl =
firstPresentValue(merchandise, [
'coverUrl',
'cover',
'previewUrl',
'preview_url',
]) ||
firstPresentValue(item, [
'coverUrl',
'cover',
'resourceCover',
'previewUrl',
'preview_url',
'avatar',
'icon',
'imageUrl',
]) ||
(isDynamicAssetUrl(assetUrl) ? '' : assetUrl);
animationUrl = resolveAssetUrl(animationUrl);
return {
animationUrl: isDynamicAssetUrl(animationUrl) ? animationUrl : '',
coverUrl: resolveAssetUrl(coverUrl),
};
}
function normalizeDrawResult(data) {
var target = Object.assign({ rewardValue: 0, rewards: [] }, data || {});
var rewards = Array.isArray(target.rewards) ? target.rewards : [];
target.rawRewards = rewards.slice();
if (
rewards.length === 0 &&
firstPresentValue(target, [
'selectedTierId',
'selected_tier_id',
'rewardType',
'reward_type',
'rewardId',
'reward_id',
])
) {
rewards = [normalizeWheelReward(target)];
target.rawRewards = [Object.assign({}, data || {})];
}
target.rewards = rewards
.map(normalizeWheelReward)
.map(normalizeRewardItem);
target.rewards = aggregateRewardItems(target.rewards);
if (target.rewards.length > 0) {
var aggregateValue = target.rewards.reduce(function (sum, item) {
return sum + rewardDisplayValue(item) * rewardCountValue(item);
}, 0);
if (aggregateValue > 0) target.rewardValue = aggregateValue;
}
return target;
}
function normalizeWalletBalance(payload) {
var data = envelopeData(payload) || {};
var list = Array.isArray(data)
? data
: data.balances || data.values || data.list || [];
var info = Array.isArray(list)
? list.find(function (item) {
var type = firstPresentValue(item, [
'asset_type',
'assetType',
'walletType',
'type',
]);
return String(type || '').toUpperCase() === 'COIN';
})
: null;
return info
? Number(
firstPresentValue(info, [
'available_amount',
'availableAmount',
'balance',
'amount',
]) || 0
)
: 0;
}
function createCommandID(prefix) {
var random = '';
if (window.crypto && window.crypto.getRandomValues) {
var values = new Uint32Array(2);
window.crypto.getRandomValues(values);
random = values[0].toString(36) + values[1].toString(36);
} else {
random = Math.random().toString(36).slice(2);
}
return (
String(prefix || 'wheel') +
'_' +
Date.now().toString(36) +
'_' +
random
);
}
function firstDrawReward(drawResult) {
drawResult = drawResult || {};
if (
Array.isArray(drawResult.rawRewards) &&
drawResult.rawRewards.length > 0
)
return drawResult.rawRewards[0];
if (Array.isArray(drawResult.records) && drawResult.records.length > 0)
return drawResult.records[0];
if (Array.isArray(drawResult.rewards) && drawResult.rewards.length > 0)
return drawResult.rewards[0];
return null;
}
function rewardDisplayValue(reward) {
return Number(
firstPresentValue(reward, [
'value',
'displayGoldAmount',
'goldAmount',
'rewardValue',
'amount',
]) || 0
);
}
function gridItemDisplayValue(item) {
return Number(
firstPresentValue(item, [
'value',
'displayGoldAmount',
'goldAmount',
]) || 0
);
}
function resolveRewardTargetIndex(gridItems, drawResult) {
var reward = firstDrawReward(drawResult);
if (!reward || !Array.isArray(gridItems) || gridItems.length === 0)
return 0;
var rewardMetadata = parseJSON(
firstPresentValue(reward, ['metadataJson', 'metadata_json'])
);
var metadataResourceId = firstPresentValue(rewardMetadata, [
'resource_id',
'resourceId',
]);
var rewardId = normalizeCompareValue(
firstPresentValue(reward, [
'id',
'tierId',
'tier_id',
'selectedTierId',
'selected_tier_id',
'rewardId',
'reward_id',
'rewardConfigId',
'reward_config_id',
'resourceId',
'resource_id',
]) || metadataResourceId
);
var rewardName = normalizeCompareValue(
firstPresentValue(reward, [
'name',
'resourceName',
'resource_name',
'rewardName',
'reward_name',
]) ||
firstPresentValue(rewardMetadata, [
'resource_name',
'resourceName',
'resource_code',
'resourceCode',
])
);
var rewardCover = normalizeCompareValue(
firstPresentValue(reward, [
'coverUrl',
'cover_url',
'cover',
'resourceCover',
'resource_cover',
'previewUrl',
'preview_url',
]) ||
firstPresentValue(rewardMetadata, [
'preview_url',
'previewUrl',
'asset_url',
'assetUrl',
])
);
var rewardValue = rewardDisplayValue(reward);
function matchBy(predicate) {
for (var index = 0; index < gridItems.length; index += 1) {
if (predicate(gridItems[index] || {})) return index;
}
return -1;
}
var byId = rewardId
? matchBy(function (item) {
return (
normalizeCompareValue(
firstPresentValue(item, [
'id',
'rewardId',
'reward_id',
'rewardConfigId',
'reward_config_id',
'resourceId',
'resource_id',
])
) === rewardId
);
})
: -1;
if (byId >= 0) return byId;
var byCoverValue = rewardCover
? matchBy(function (item) {
var merchandise = item.merchandise || {};
return (
normalizeCompareValue(
firstPresentValue(merchandise, ['coverUrl', 'cover'])
) === rewardCover &&
(!rewardValue ||
gridItemDisplayValue(item) === rewardValue)
);
})
: -1;
if (byCoverValue >= 0) return byCoverValue;
var byNameValue = rewardName
? matchBy(function (item) {
var merchandise = item.merchandise || {};
return (
normalizeCompareValue(
firstPresentValue(merchandise, [
'name',
'resourceName',
'resource_name',
'rewardName',
'reward_name',
])
) === rewardName &&
(!rewardValue ||
gridItemDisplayValue(item) === rewardValue)
);
})
: -1;
if (byNameValue >= 0) return byNameValue;
var byValue = rewardValue
? matchBy(function (item) {
return gridItemDisplayValue(item) === rewardValue;
})
: -1;
return byValue >= 0 ? byValue : 0;
}
new Vue({
el: '#app',
data: function () {
return {
tabs: TABS,
currentLanguage: 'en',
currentPage: 'classic',
userBalance: 0,
pageLoading: true,
enabled: {
classic: true,
luxury: true,
advanced: true,
},
preview: {
open: false,
coverUrl: '',
animationUrl: '',
animationReady: false,
},
pages: {
classic: stateFor(),
luxury: stateFor(),
advanced: stateFor(),
},
};
},
computed: {
active: function () {
return this.pages[this.currentPage];
},
currentConfig: function () {
return CONFIGS[this.currentPage];
},
pageClass: function () {
return this.currentConfig.stageClass;
},
durationSpeed: function () {
var count = this.active.rewards.length || 1;
return 10 * count + 's';
},
marqueeRewards: function () {
return this.active.rewards.concat(this.active.rewards);
},
balanceEnough: function () {
return (
this.userBalance >=
this.active.goldBtn[this.active.activeBtnIndex].value *
this.active.drawAmount
);
},
},
created: function () {
this.currentLanguage = this.resolveLanguage();
document.documentElement.lang = this.currentLanguage;
document.documentElement.dir =
this.currentLanguage === 'ar' ? 'rtl' : 'ltr';
this.currentPage = this.firstPageFromURL();
this.initRoomID();
this.loadData();
},
beforeDestroy: function () {
this.closePrizePreview();
if (this.previewPlayer && this.previewPlayer.destroy) {
this.previewPlayer.destroy();
}
Object.keys(this.pages).forEach(
function (key) {
this.stopTimers(this.pages[key]);
}.bind(this)
);
},
methods: {
resolveLanguage: function () {
if (window.HyAppI18n && window.HyAppI18n.lang)
return window.HyAppI18n.lang();
return readQuery('appLanguage') || readQuery('lang') || 'en';
},
t: function (key) {
var fullKey = 'wheel.' + key;
var fallback = WHEEL_FALLBACK[key] || fullKey;
return window.HyAppI18n && window.HyAppI18n.t
? window.HyAppI18n.t(fullKey, fallback)
: fallback;
},
common: function (key) {
var fullKey = 'wheel.common.' + key;
var fallback = COMMON_FALLBACK[key] || key;
return window.HyAppI18n && window.HyAppI18n.t
? window.HyAppI18n.t(fullKey, fallback)
: fallback;
},
replaceOne: function (key, value) {
return this.t(key).replace(/{}/g, value);
},
replaceTwo: function (key, first, second) {
return this.t(key)
.replace(/\{1}/g, first)
.replace(/\{2}/g, second);
},
asset: function (name) {
return ASSET_BASE + name;
},
ensurePreviewPlayer: function () {
var target = this.$refs.previewEffect;
if (!target || !window.HyAppEffectPlayer) return null;
if (
this.previewPlayer &&
this.previewPlayer.container === target
)
return this.previewPlayer;
if (this.previewPlayer && this.previewPlayer.destroy) {
this.previewPlayer.destroy();
}
this.previewPlayer = window.HyAppEffectPlayer.create(target, {
activeClass: 'is-active',
fit: 'contain',
loop: true,
muted: true,
svgaScript: '../../common/vendor/svga.min.js',
waitForWindowLoad: false,
});
return this.previewPlayer;
},
openPrizePreview: function (item) {
var source = prizePreviewSource(item);
if (!source.animationUrl && !source.coverUrl) return;
this.closePrizePreview();
this.preview = {
open: true,
coverUrl: source.coverUrl,
animationUrl: source.animationUrl,
animationReady: false,
};
// 动态资源优先,但必须保留静态图兜底;播放器加载失败时用户仍能看到预览图,不出现空黑层。
this.$nextTick(function () {
if (!this.preview.animationUrl) return;
var player = this.ensurePreviewPlayer();
if (!player) return;
var animationUrl = this.preview.animationUrl;
player
.play(animationUrl, {
svgaScript: '../../common/vendor/svga.min.js',
waitForWindowLoad: false,
})
.then(
function () {
if (
this.preview.open &&
this.preview.animationUrl === animationUrl
) {
this.preview.animationReady = true;
}
}.bind(this)
)
.catch(
function () {
if (
this.preview.open &&
this.preview.animationUrl === animationUrl
) {
this.preview.animationReady = false;
}
}.bind(this)
);
});
},
closePrizePreview: function () {
if (this.previewPlayer) this.previewPlayer.stop();
this.preview = {
open: false,
coverUrl: '',
animationUrl: '',
animationReady: false,
};
},
firstPageFromURL: function () {
var page = readQuery('wheel') || readQuery('tab') || '';
var hash = window.location.hash || '';
if (page === 'middle') page = 'luxury';
if (!page && hash.indexOf('advanced') !== -1) page = 'advanced';
if (!page && hash.indexOf('middle') !== -1) page = 'luxury';
return CONFIGS[page] ? page : 'classic';
},
initRoomID: function () {
var roomId = readQuery('roomId') || readQuery('room_id') || '';
Object.keys(this.pages).forEach(
function (key) {
this.pages[key].roomId = roomId;
}.bind(this)
);
},
wheelAPI: function () {
if (!window.HyAppAPI || !window.HyAppAPI.activityWheel) {
return null;
}
return window.HyAppAPI.activityWheel;
},
loadData: function () {
this.pageLoading = true;
return this.getUserWalletInfo()
.then(
function () {
this.pageLoading = false;
this.loadPageData(this.currentPage);
}.bind(this)
)
.catch(
function () {
// 钱包接口依赖登录态,失败时不能让活动页白屏;后续抽奖仍会由余额判断和服务端扣费共同拦截。
this.pageLoading = false;
this.loadPageData(this.currentPage);
}.bind(this)
);
},
getUserWalletInfo: function () {
if (!window.HyAppAPI || !window.HyAppAPI.wallet)
return Promise.reject(new Error('HyAppAPI unavailable'));
return window.HyAppAPI.wallet.balances('COIN').then(
function (res) {
this.userBalance = normalizeWalletBalance(res);
}.bind(this)
);
},
loadPageData: function (page) {
var state = this.pages[page];
var config = CONFIGS[page];
this.configHandle(state, config);
this.recordsHandle(state, config);
this.userRecordsHandle(state, config);
},
rewardsListHandle: function (state, config) {
var api = this.wheelAPI();
if (!api)
return Promise.reject(new Error('HyAppAPI unavailable'));
return api
.config(config.key)
.then(
function (res) {
state.isInit = true;
var data = envelopeData(res) || {};
var drawPrice = Number(
firstPresentValue(data, [
'drawPriceCoins',
'draw_price_coins',
'drawPrice',
'draw_price',
]) || 0
);
if (drawPrice > 0) state.drawAmount = drawPrice;
this.enabled[config.key] =
firstPresentValue(data, [
'enabled',
config.enabledKey,
]) !== false;
// 后端配置来自资源组:金币只要求预览图字段为空也能展示金额,礼物/道具/装扮则统一读取 metadata 的预览图和动态图。
state.gridItems = this.enabled[config.key]
? normalizeWheelRewards(data)
: [];
}.bind(this)
)
.catch(function (error) {
state.isInit = true;
state.gridItems = [];
console.warn('wheel rewards load failed', error);
});
},
drawHandle: function (state, config) {
var api = this.wheelAPI();
if (!api) {
state.drawFailed = true;
this.stopSpinning(state);
toast(this.common('requestFailed'));
return Promise.resolve();
}
return api
.draw(config.key, {
command_id: createCommandID(config.key),
amount:
state.goldBtn[state.activeBtnIndex].value *
state.drawAmount,
times: state.goldBtn[state.activeBtnIndex].value,
roomId: state.roomId,
})
.then(
function (res) {
var data = normalizeDrawResult(envelopeData(res));
state.drawFailed = false;
if (data.coin_balance_after !== undefined) {
this.userBalance = Number(
data.coin_balance_after || 0
);
} else {
this.getUserWalletInfo();
}
this.recordsHandle(state, config);
state.currentReward = data;
state.targetIndex = resolveRewardTargetIndex(
state.gridItems,
data
);
}.bind(this)
)
.catch(
function (error) {
state.drawFailed = true;
this.stopSpinning(state);
toast(
error.message || this.common('requestFailed')
);
}.bind(this)
);
},
recordsHandle: function (state, config) {
var api = this.wheelAPI();
if (!api) return Promise.resolve();
state.loading = true;
if (state.isInit) state.pageNo += 1;
return api
.history(config.key, {
pageNo: state.pageNo,
pageSize: config.pageSize,
})
.then(function (res) {
var data = envelopeData(res) || {};
var records = Array.isArray(data.records)
? data.records
: [];
if (records.length === 0) state.finished = true;
state.loading = false;
// 历史记录和中奖弹窗使用同一批奖品字段,统一标准化后再追加,分页加载不会把裸文件名带进 DOM。
state.historyRecord = state.historyRecord.concat(
records.map(normalizeHistoryRecord)
);
})
.catch(function () {
state.loading = false;
state.finished = true;
});
},
userRecordsHandle: function (state, config) {
var api = this.wheelAPI();
if (!api) return Promise.resolve();
return api
.hints(config.key)
.then(function (res) {
var data = envelopeData(res) || {};
if (Array.isArray(data.values))
state.rewards = data.values.map(normalizeHintItem);
})
.catch(function () {
state.rewards = [];
});
},
configHandle: function (state, config) {
return this.rewardsListHandle(state, config).then(
function () {
if (!this.enabled[this.currentPage])
this.handleSelect(this.firstEnabledPage());
}.bind(this)
);
},
firstEnabledPage: function () {
for (var index = 0; index < TABS.length; index += 1) {
if (this.enabled[TABS[index].key]) return TABS[index].key;
}
return 'classic';
},
handleGoldBtn: function (index) {
this.active.activeBtnIndex = index;
},
handleSelect: function (tab) {
if (!CONFIGS[tab] || !this.enabled[tab]) return;
this.currentPage = tab;
if (!this.pages[tab].gridItems.length) this.loadPageData(tab);
},
startLottery: function () {
var state = this.active;
var config = this.currentConfig;
if (!state.gridItems.length || state.drawAmount <= 0) {
toast(this.common('rewardUnavailable'));
return;
}
if (!this.balanceEnough) {
toast(this.common('balanceNotEnough'));
return;
}
if (state.isSpinning) return;
state.showReward = false;
state.drawFailed = false;
state.isInit = false;
state.finished = false;
state.pageNo = 1;
state.historyRecord = [];
state.targetIndex = null;
state.activeIndex = null;
if (config.type === 'round') this.backgroundChange(state);
this.startSpinning(state, config);
this.drawHandle(state, config);
this.stopShowReward(state, config);
},
startSpinning: function (state, config) {
if (config.type === 'round') {
this.startRoundSpinning(state);
return;
}
state.isSpinning = true;
var sequenceIndex = 0;
var spinCount = 0;
var maxFastSpins = 25;
var speedIncrement = 100;
var minSpeed = config.minSpeed;
state.spinInterval = window.setInterval(
function () {
state.activeIndex = config.sequence[sequenceIndex] - 1;
sequenceIndex =
(sequenceIndex + 1) % config.sequence.length;
spinCount += 1;
if (config.key === 'classic') {
state.currentBackground =
state.currentBackground === 'light'
? 'light2'
: 'light';
}
if (
spinCount > maxFastSpins &&
state.targetIndex !== null
) {
state.spinSpeed += speedIncrement;
if (
Math.abs(
state.activeIndex - state.targetIndex
) <= 4
)
state.spinSpeed += speedIncrement * 2;
if (
state.activeIndex === state.targetIndex &&
state.spinSpeed >= minSpeed
)
this.stopSpinning(state);
}
}.bind(this),
state.spinSpeed
);
},
startRoundSpinning: function (state) {
var startWait = Date.now();
var waitStartDeg = state.rotateDeg;
state.isSpinning = true;
if (state.rotationTimer)
window.cancelAnimationFrame(state.rotationTimer);
var animateToTarget = function () {
var count = Math.max(1, state.gridItems.length || 1);
var targetIndex = Number(state.targetIndex || 0);
var duration = 4200;
var startTime = Date.now();
var startDeg = state.rotateDeg;
var currentDeg = positiveModulo(startDeg, 360);
var targetBaseDeg = roundPrizeAngle(targetIndex, count);
var forwardDelta = positiveModulo(
targetBaseDeg - currentDeg,
360
);
// 指针转盘必须等接口给出真实 targetIndex 后,从当前角度继续正向缓停到奖品图标角度;不能归零或使用假结果。
var targetDeg = startDeg + 360 * 6 + forwardDelta;
var animate = function () {
var elapsed = Date.now() - startTime;
var progress = Math.min(1, elapsed / duration);
var easedProgress = this.roundStopEase(progress);
state.rotateDeg =
startDeg + (targetDeg - startDeg) * easedProgress;
if (progress >= 1) {
state.isSpinning = false;
state.rotateDeg = targetDeg;
state.rotationTimer = null;
this.showDrawReward(state);
return;
}
state.rotationTimer =
window.requestAnimationFrame(animate);
}.bind(this);
animate();
}.bind(this);
var waitForTarget = function () {
if (
state.targetIndex !== null &&
state.targetIndex !== undefined
) {
animateToTarget();
return;
}
state.rotateDeg =
waitStartDeg + ((Date.now() - startWait) / 1000) * 720;
state.rotationTimer =
window.requestAnimationFrame(waitForTarget);
};
waitForTarget();
},
customEase: function (value) {
return value < 0.5
? 2 * value * value
: 1 - Math.pow(-2 * value + 2, 3) / 2;
},
roundStopEase: function (value) {
return 1 - Math.pow(1 - value, 4);
},
backgroundChange: function (state) {
state.backgroundInterval = window.setInterval(function () {
state.currentBackground =
state.currentBackground === 'light'
? 'light2'
: 'light';
}, 100);
},
showDrawReward: function (state) {
if (state.drawFailed || state.targetIndex === null) return;
if (
state.targetIndex !== null &&
state.targetIndex !== undefined &&
typeof state.activeIndex === 'number'
) {
state.activeIndex = state.targetIndex;
}
state.showReward = true;
state.isInit = true;
window.clearInterval(state.backgroundInterval);
},
stopShowReward: function (state, config) {
if (config && config.type === 'round') return;
window.setTimeout(
function () {
this.showDrawReward(state);
}.bind(this),
3300
);
},
stopSpinning: function (state) {
this.stopTimers(state);
state.isSpinning = false;
state.spinSpeed = 50;
state.activeIndex =
state.targetIndex !== null &&
state.targetIndex !== undefined
? state.targetIndex
: null;
},
stopTimers: function (state) {
window.clearInterval(state.spinInterval);
window.clearInterval(state.backgroundInterval);
if (state.rotationTimer)
window.cancelAnimationFrame(state.rotationTimer);
state.spinInterval = null;
state.rotationTimer = null;
},
getPrizePosition: function (index) {
var count = Math.max(1, this.active.gridItems.length || 1);
var radius = 150;
var center = { x: 50, y: 50 };
var itemAngle = roundPrizeAngle(index, count);
var angle = (itemAngle * Math.PI) / 180;
var distance = radius * 0.25;
var x = center.x + Math.sin(angle) * distance;
var y = center.y - Math.cos(angle) * distance;
var textRotation = itemAngle;
return {
left: x + '%',
top: y + '%',
transform:
'translate(-50%, -50%) rotate(' + textRotation + 'deg)',
};
},
buttonStyle: function (index) {
if (this.currentPage !== 'luxury') return {};
var name =
'lucky_box_tow_' +
(index === this.active.activeBtnIndex ? '' : 'un') +
'btn' +
(index + 1) +
'.png';
return { backgroundImage: 'url(' + this.asset(name) + ')' };
},
pointerStyle: function () {
if (this.currentPage !== 'luxury') return {};
return { '--pointer-rotate': this.active.rotateDeg + 'deg' };
},
formatDate: function (time, format) {
if (!time) return '';
var date = new Date(
Number(time) || String(time).replace(/-/g, '/')
);
function zero(value) {
return (value < 10 ? '0' : '') + value;
}
return (format || 'yyyy-MM-dd HH:mm:ss').replace(
/yyyy|MM|dd|HH|mm|ss/g,
function (token) {
if (token === 'yyyy') return zero(date.getFullYear());
if (token === 'MM') return zero(date.getMonth() + 1);
if (token === 'dd') return zero(date.getDate());
if (token === 'HH') return zero(date.getHours());
if (token === 'mm') return zero(date.getMinutes());
return zero(date.getSeconds());
}
);
},
},
});
})();