(function () { var viewport = document.getElementById('appViewport'); var page = document.getElementById('page'); var dailyRoot = document.getElementById('dailyTasks'); var exclusiveRoot = document.getElementById('exclusiveTasks'); var stateRoot = document.getElementById('taskState'); var backButton = document.getElementById('backButton'); var COMMAND_PREFIX = 'task_claim_command:'; var COIN_IMAGE = './assets/coin.png'; var GAME_IMAGE = './assets/figma-game-icon.png'; var state = { loading: true, error: '', sections: [], claiming: {}, }; var mockData = { server_time_ms: Date.now(), next_refresh_at_ms: Date.now() + 6 * 60 * 60 * 1000, sections: [ { section: 'daily', items: [ mockTask( 'daily-mic', 'daily', 'Use the microphone for 1 min', 'mic_online_minute', 0, 1000000, 1100, 'in_progress', false, 'mic' ), mockTask( 'daily-game', 'daily', 'Use the microphone for 1 min', 'game_spend_coin', 1000000, 1000000, 1100, 'completed', true, 'gamepad' ), mockTask( 'daily-gift', 'daily', 'Use the microphone for 1 min', 'gift_spend_coin', 1000000, 1000000, 1100, 'claimed', false, 'gift' ), ], }, { section: 'exclusive', items: [ mockTask( 'exclusive-game-preview', 'exclusive', 'Use the microphone for 1 min', 'game_spend_coin', 0, 1000000, 1100, 'in_progress', false, 'game_preview' ), mockTask( 'exclusive-game', 'exclusive', 'Use the microphone for 1 min', 'game_spend_coin', 1000000, 1000000, 1100, 'completed', true, 'gamepad' ), mockTask( 'exclusive-gift', 'exclusive', 'Use the microphone for 1 min', 'gift_spend_coin', 1000000, 1000000, 1100, 'claimed', false, 'gift' ), ], }, ], }; function t(key, fallback) { if (window.HyAppI18n && window.HyAppI18n.t) { return window.HyAppI18n.t(key, fallback); } return fallback || key; } function mockTask( id, type, title, metric, progress, target, reward, status, claimable, iconKey ) { return { task_id: id, task_type: type, task_day: type === 'daily' ? new Date().toISOString().slice(0, 10) : 'lifetime', title: title, metric_type: metric, progress_value: progress, target_value: target, reward_coin_amount: reward, status: status, claimable: claimable, icon_key: iconKey, icon_url: iconKey === 'game_preview' ? GAME_IMAGE : '', action_type: iconKey === 'gift' ? 'gift_panel' : iconKey === 'gamepad' ? 'room_random_game' : 'wallet', action_param: iconKey === 'gamepad' ? 'dice' : '', action_payload: iconKey === 'gamepad' ? { game_id: 'dice' } : {}, }; } function isMockMode() { var params = new URLSearchParams(window.location.search || ''); return params.get('mock') === '1' || params.get('mock') === 'true'; } function escapeHTML(value) { return String(value == null ? '' : value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function escapeAttr(value) { return escapeHTML(value).replace(/`/g, '`'); } function clampProgress(item) { var target = Math.max( 0, Number(item.target_value || item.targetValue || 0) ); var progress = Math.max( 0, Number(item.progress_value || item.progressValue || 0) ); return { target: target, progress: target > 0 ? Math.min(progress, target) : progress, }; } function formatProgress(item) { var values = clampProgress(item); return String(values.progress) + '/' + String(values.target); } function formatReward(value) { var amount = Number(value || 0); if (!Number.isFinite(amount)) return '0'; if (amount >= 1000000) return trimNumber(amount / 1000000) + 'M'; if (amount >= 1000) return trimNumber(amount / 1000) + 'k'; return String(amount); } function trimNumber(value) { var rounded = Math.round(value * 10) / 10; return String(rounded).replace(/\.0$/, ''); } function claimKey(item) { return [ item.task_id || item.taskId, item.task_type || item.taskType, item.task_day || item.taskDay, ].join('|'); } function commandStorageKey(item) { return COMMAND_PREFIX + claimKey(item); } function commandID(item) { var storageKey = commandStorageKey(item); var cached = window.sessionStorage.getItem(storageKey); if (cached) return cached; // 领取命令以任务主键和页面时间组成,保存在 sessionStorage;如果请求超时但服务端已入账,用户重试会继续用同一 command_id 命中后端幂等。 var next = 'h5-task-claim-' + Date.now() + '-' + Math.random().toString(16).slice(2) + '-' + String(item.task_id || item.taskId || ''); window.sessionStorage.setItem(storageKey, next); return next; } function clearCommandID(item) { window.sessionStorage.removeItem(commandStorageKey(item)); } function normalizeSections(payload) { var sections = payload && Array.isArray(payload.sections) ? payload.sections : []; var byName = sections.reduce(function (map, section) { map[String(section.section || '')] = Array.isArray(section.items) ? section.items : []; return map; }, {}); return [ { section: 'daily', items: byName.daily || [] }, { section: 'exclusive', items: byName.exclusive || [] }, ]; } function iconKind(item) { var key = String(item.icon_key || item.iconKey || '').toLowerCase(); var metric = String( item.metric_type || item.metricType || '' ).toLowerCase(); if (key.indexOf('mic') >= 0 || metric.indexOf('mic') >= 0) return 'mic'; if (key.indexOf('gift') >= 0 || metric.indexOf('gift') >= 0) return 'gift'; if (key.indexOf('game') >= 0 || metric.indexOf('game') >= 0) return 'gamepad'; if (key.indexOf('cp') >= 0 || metric.indexOf('cp') >= 0) return 'cp'; if (key.indexOf('mood') >= 0 || metric.indexOf('mood') >= 0) return 'mood'; return 'coin'; } function iconHTML(item) { var iconURL = item.icon_url || item.iconUrl || ''; if (iconURL) { // 后台 icon_url 是可配置资源,可能因为地址过期、跨域或资源未上传而加载失败;失败时用内置图标兜底,不能让任务列表留下空方块。 return [ '', ].join(''); } return ( '' ); } function svgForIcon(kind) { if (kind === 'mic') { return ''; } if (kind === 'gift') { return ''; } if (kind === 'cp') { return ''; } if (kind === 'mood') { return ''; } if (kind === 'coin') { return ''; } return ''; } function actionState(item) { if (item.status === 'claimed') { return { label: t('tasks.claimed', 'Claimed'), className: 'is-claimed', disabled: true, }; } if (item.claimable) { return { label: t('tasks.claim', 'Claim'), className: 'is-claimable', disabled: false, }; } return { label: t('tasks.go', 'Go'), className: 'is-go', disabled: false, }; } function renderSection(root, items) { if (!items.length) { root.innerHTML = '