invite-activity.html 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport"
  6. content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
  7. <title></title>
  8. <script src="/js/url-cache.js"></script>
  9. <script src="/token.js"></script>
  10. <link rel="stylesheet" href="/css/business-theme.css">
  11. <link rel="stylesheet" href="/css/invite-activity.css">
  12. <link rel="preload" as="image" href="/images/group-470833-bg.png">
  13. <!-- 关键图片预加载占位,后续由业务方补充 -->
  14. </head>
  15. <body>
  16. <div class="container" id="container">
  17. <main class="main-view" id="mainView"></main>
  18. </div>
  19. <div class="loading-overlay" id="loadingOverlay" style="display: flex;">
  20. <div class="loading-spinner"></div>
  21. <div class="loading-text" data-i18n="loadingText">加载中...</div>
  22. </div>
  23. <div class="error-overlay" id="errorOverlay" style="display: none;">
  24. <div class="error-content">
  25. <div class="error-text" data-i18n="errorText">加载失败,请稍后重试</div>
  26. <div class="error-actions">
  27. <button class="error-retry" id="retryBtn" data-i18n="retryBtn">重试</button>
  28. <button class="error-retry" id="errorBackBtn" data-i18n="errorBackBtn">返回</button>
  29. </div>
  30. </div>
  31. </div>
  32. <div class="toast" id="toast"></div>
  33. <div class="modal-overlay" id="modalOverlay" style="display: none;"></div>
  34. <script src="/js/common.js"></script>
  35. <script>
  36. (function () {
  37. if (window.BEXTheme) {
  38. window.BEXTheme.skipAutoInit();
  39. }
  40. document.documentElement.classList.add('theme-dark');
  41. })();
  42. </script>
  43. <script>
  44. (function () {
  45. var API_BASE = '/api/v1/user/inviteActivityEntry';
  46. var config = null;
  47. var activities = [];
  48. var token = window.WELFARE_TOKEN || '';
  49. var countdownTimers = {};
  50. var sliderVolume = 0;
  51. var T = function (key, params) {
  52. return window.BEXI18n ? window.BEXI18n.t('inviteActivity', key, params) : key;
  53. };
  54. function getDefaultHeaders() {
  55. return window.BEXI18n ? window.BEXI18n.getDefaultHeaders() : {
  56. 'Content-Type': 'application/json',
  57. 'Accept': 'application/json',
  58. 'X-Requested-With': 'XMLHttpRequest'
  59. };
  60. }
  61. function showLoading() {
  62. document.getElementById('loadingOverlay').style.display = 'flex';
  63. hideError();
  64. }
  65. function hideLoading() {
  66. document.getElementById('loadingOverlay').style.display = 'none';
  67. }
  68. function showError() {
  69. document.getElementById('errorOverlay').style.display = 'flex';
  70. }
  71. function hideError() {
  72. document.getElementById('errorOverlay').style.display = 'none';
  73. }
  74. function showToast(message) {
  75. var toast = document.getElementById('toast');
  76. toast.textContent = message;
  77. toast.classList.add('show');
  78. setTimeout(function () { toast.classList.remove('show'); }, 2500);
  79. }
  80. function formatAmount(val) {
  81. var num = parseFloat(val) || 0;
  82. var str = num.toString();
  83. var dot = str.indexOf('.');
  84. if (dot === -1) return str;
  85. return str.substring(0, dot + 3).replace(/\.?0+$/, '');
  86. }
  87. function getNowUtc() {
  88. return Date.now();
  89. }
  90. function fetchConfig() {
  91. return window.BEXApi.requestWithAuth(API_BASE + '/config', {
  92. method: 'GET',
  93. headers: getDefaultHeaders()
  94. },token);
  95. }
  96. function fetchActivityList() {
  97. return window.BEXApi.requestWithAuth(API_BASE + '/activityList', {
  98. method: 'GET',
  99. headers: getDefaultHeaders()
  100. },token);
  101. }
  102. function getInviteLink(obj) {
  103. var row = { ...obj };
  104. var params = new URLSearchParams(row).toString();
  105. return window.location.origin + '/html/invite/invite-register.html?' + params;
  106. }
  107. function escapeHtml(text) {
  108. var div = document.createElement('div');
  109. div.textContent = text || '';
  110. return div.innerHTML;
  111. }
  112. function copyText(text) {
  113. if (navigator.clipboard && navigator.clipboard.writeText) {
  114. navigator.clipboard.writeText(text).then(
  115. function () { showToast(T('copySuccess')); },
  116. function () { fallbackCopy(text); }
  117. );
  118. } else {
  119. fallbackCopy(text);
  120. }
  121. }
  122. function fallbackCopy(text) {
  123. var input = document.createElement('textarea');
  124. input.value = text;
  125. input.style.position = 'fixed';
  126. input.style.opacity = '0';
  127. document.body.appendChild(input);
  128. input.select();
  129. try {
  130. document.execCommand('copy');
  131. showToast(T('copySuccess'));
  132. } catch (e) {
  133. showToast(T('copyFailed'));
  134. }
  135. document.body.removeChild(input);
  136. }
  137. function estimateCommission(volumeK) {
  138. if (!config) return 0;
  139. var ratio = parseFloat(config.friendTradeVolumeToCommissionRatio) || 0;
  140. var upper = parseFloat(config.friendTradeVolumeUpperLimitK) || 0;
  141. var v = Math.min(volumeK, upper);
  142. return parseInt(v * ratio / 100);
  143. }
  144. function stopCountdowns() {
  145. Object.keys(countdownTimers).forEach(function (id) {
  146. clearInterval(countdownTimers[id]);
  147. });
  148. countdownTimers = {};
  149. }
  150. function startActivityCountdown(activity) {
  151. var id = activity.id;
  152. if (!activity.endTime) return;
  153. var endTime = new Date(activity.endTime.replace(' ', 'T')).getTime();
  154. var daysEl = document.getElementById('activityDays_' + id);
  155. if (!daysEl) return;
  156. function tick() {
  157. var remaining = endTime - getNowUtc();
  158. var daysEl2 = document.getElementById('activityDays_' + id);
  159. if (!daysEl2) {
  160. clearInterval(countdownTimers[id]);
  161. delete countdownTimers[id];
  162. return;
  163. }
  164. if (remaining <= 0) {
  165. clearInterval(countdownTimers[id]);
  166. delete countdownTimers[id];
  167. daysEl2.textContent = '00';
  168. var timeEl = document.getElementById('activityTime_' + id);
  169. if (timeEl) timeEl.textContent = '00 : 00 : 00';
  170. var btn = document.getElementById('activityBtn_' + id);
  171. if (btn) {
  172. btn.disabled = true;
  173. var btnSpan = btn.querySelector('span');
  174. if (btnSpan) btnSpan.textContent = T('btnEnded');
  175. }
  176. return;
  177. }
  178. var days = Math.floor(remaining / 86400000);
  179. var hours = Math.floor((remaining % 86400000) / 3600000);
  180. var minutes = Math.floor((remaining % 3600000) / 60000);
  181. var seconds = Math.floor((remaining % 60000) / 1000);
  182. var pad = function (n) { return n < 10 ? '0' + n : '' + n; };
  183. daysEl2.textContent = pad(days);
  184. var timeEl = document.getElementById('activityTime_' + id);
  185. if (timeEl) timeEl.textContent = pad(hours) + ' : ' + pad(minutes) + ' : ' + pad(seconds);
  186. }
  187. tick();
  188. countdownTimers[id] = setInterval(tick, 1000);
  189. }
  190. function renderPage() {
  191. var main = document.getElementById('mainView');
  192. main.innerHTML = '';
  193. renderHero(main);
  194. renderCommission(main);
  195. renderActivityList(main);
  196. renderInviteLink(main);
  197. BEXI18n.applyTranslations('inviteActivity');
  198. }
  199. function renderHero(container) {
  200. var title = (config && config.title) ? config.title : T('heroTitle');
  201. var rate = formatAmount(config && config.rebateRate);
  202. var duration = (config && config.rebateDurationDays) || 0;
  203. // var isKyc = !!(config && config.isKycVerified);
  204. var isKyc = false;
  205. var btnText = isKyc ? T('activatedBtn') : T('activateBtn');
  206. var btnDisabled = isKyc ? 'disabled' : '';
  207. var html =
  208. '<div class="hero-card">' +
  209. '<div class="hero-card-inner">' +
  210. '<div class="hero-bg"></div>' +
  211. '<div class="hero-title">' + escapeHtml(title) + '</div>' +
  212. '<div class="hero-subtitle">' + T('heroSubtitle', { rate: rate }) + '</div>' +
  213. '<div class="hero-rights">' +
  214. '<div class="hero-rights-line left"></div>' +
  215. '<span class="hero-rights-text" data-i18n="heroRebateRights">返佣权益</span>' +
  216. '<div class="hero-rights-line"></div>' +
  217. '</div>' +
  218. '<div class="hero-stats">' +
  219. '<div class="hero-stat">' +
  220. '<div><span class="hero-stat-value">' + duration + '</span><span class="hero-stat-unit" data-i18n="heroDays">天</span></div>' +
  221. '<div class="hero-stat-label" data-i18n="heroRebateDuration">返佣时长</div>' +
  222. '</div>' +
  223. '<div class="hero-stat">' +
  224. '<div><span class="hero-stat-value">' + rate + '</span><span class="hero-stat-unit">%</span></div>' +
  225. '<div class="hero-stat-label" data-i18n="heroRebateRate">返佣比例</div>' +
  226. '</div>' +
  227. '</div>' +
  228. '</div>' +
  229. '<button class="hero-activate-btn" id="heroActivateBtn" ' + btnDisabled + '>' + btnText + '</button>' +
  230. '</div>';
  231. container.insertAdjacentHTML('beforeend', html);
  232. document.getElementById('heroActivateBtn').onclick = function () {
  233. // if (isKyc) return;
  234. showActivateModal();
  235. };
  236. }
  237. function renderCommission(container) {
  238. var lower = parseFloat(config && config.friendTradeVolumeLowerLimitK) || 0;
  239. var upper = parseFloat(config && config.friendTradeVolumeUpperLimitK) || 0;
  240. sliderVolume = lower;
  241. var initialCommission = estimateCommission(sliderVolume);
  242. var maxCommission = estimateCommission(upper);
  243. var html =
  244. '<div class="commission-section">' +
  245. '<div class="commission-left">' +
  246. '<i class="commission-tag-label" data-i18n="commissionLeftLabel">' + T('commissionLeftLabel') + '</i>' +
  247. '<p class="meny">≈ $<span id="commissionCurrentVal">' + formatAmount(initialCommission) + '</span></p>' +
  248. '<p class="info" data-i18n="commissionMonthly">' + T('commissionMonthly') + '</p>' +
  249. '</div>' +
  250. '<div class="commission-right">' +
  251. '<i class="commission-tag-label" data-i18n="commissionRightLabel">' + T('commissionRightLabel') + '</i>' +
  252. '<p class="meny">≈ $<span id="commissionMaxVal">' + formatAmount(maxCommission) + '</span> <span class="info" data-i18n="commissionStarting">' + T('commissionStarting') + '</span></p>' +
  253. '<p class="info" data-i18n="commissionMonthly">' + T('commissionMonthly') + '</p>' +
  254. '</div>' +
  255. '</div>' +
  256. '<div class="volume-section">' +
  257. '<div class="volume-slider-wrap">' +
  258. '<span class="volume-title" data-i18n="friendVolume">' + T('friendVolume') + '</span>' +
  259. '<span class="volume-tx">' + formatAmount(lower) + 'K</span>' +
  260. '<div class="volume-box" id="volumeBox">' +
  261. '<div class="volume-track">' +
  262. '<div class="volume-progress" id="volumeProgress"></div>' +
  263. '</div>' +
  264. '<div class="volume-thumb-box" id="volumeThumb"><i class="volume-thumb" ></i></div>' +
  265. '<div class="volume-current" id="volumeCurrent">' + formatAmount(lower) + 'K</div>' +
  266. '</div>' +
  267. '<span class="volume-tx">' + formatAmount(upper) + 'K</span>' +
  268. '</div>' +
  269. '</div>';
  270. container.insertAdjacentHTML('beforeend', html);
  271. var volumeBox = document.getElementById('volumeBox');
  272. var thumb = document.getElementById('volumeThumb');
  273. var progress = document.getElementById('volumeProgress');
  274. var currentLabel = document.getElementById('volumeCurrent');
  275. // var currentValEl = document.getElementById('commissionCurrentVal');
  276. var maxValEl = document.getElementById('commissionMaxVal');
  277. function setValueFromPercent(percent) {
  278. percent = Math.max(0, Math.min(1, percent));
  279. var raw = lower + percent * (upper - lower);
  280. return Math.round(raw);
  281. }
  282. function updateSlider(clientX) {
  283. var rect = volumeBox.getBoundingClientRect();
  284. var x = clientX - rect.left;
  285. var p = rect.width > 0 ? x / rect.width : 0;
  286. var val = setValueFromPercent(p);
  287. sliderVolume = val;
  288. renderSliderUI(val);
  289. }
  290. function renderSliderUI(val) {
  291. var min = lower;
  292. var max = upper;
  293. var rawPercent = max === min ? 0 : (val - min) / (max - min);
  294. var visualPercent = 0 + rawPercent * 100;
  295. currentLabel.textContent = formatAmount(val) + 'K';
  296. currentLabel.style.left = visualPercent + '%';
  297. thumb.style.left = visualPercent + '%';
  298. progress.style.width = visualPercent + '%';
  299. maxValEl.textContent = formatAmount(estimateCommission(val));
  300. }
  301. var dragging = false;
  302. thumb.addEventListener('touchstart', function (e) {
  303. dragging = true;
  304. e.preventDefault();
  305. }, { passive: false });
  306. thumb.addEventListener('mousedown', function (e) {
  307. dragging = true;
  308. e.preventDefault();
  309. });
  310. document.addEventListener('touchmove', function (e) {
  311. if (!dragging) return;
  312. updateSlider(e.touches[0].clientX);
  313. }, { passive: false });
  314. document.addEventListener('mousemove', function (e) {
  315. if (!dragging) return;
  316. updateSlider(e.clientX);
  317. });
  318. document.addEventListener('touchend', function () { dragging = false; });
  319. document.addEventListener('mouseup', function () { dragging = false; });
  320. volumeBox.addEventListener('click', function (e) {
  321. if (e.target === thumb) return;
  322. updateSlider(e.clientX);
  323. });
  324. renderSliderUI(lower);
  325. }
  326. function renderActivityList(container) {
  327. var html = '<div class="activity-list-section">' +
  328. '<div class="activity-list-title" data-i18n="activityListTitle">邀请活动</div>';
  329. if (!activities || activities.length === 0) {
  330. html += '<div class="activity-empty" data-i18n="activityEmpty">暂无活动</div>';
  331. html += '</div>';
  332. container.insertAdjacentHTML('beforeend', html);
  333. return;
  334. }
  335. for (var i = 0; i < activities.length; i++) {
  336. var act = activities[i];
  337. var isEnded = !act.endTime || getNowUtc() >= new Date(act.endTime.replace(' ', 'T')).getTime();
  338. var btnText = isEnded ? T('btnEnded') : T('btnJoin');
  339. var btnDisabled = isEnded ? 'disabled' : '';
  340. html +=
  341. '<div class="activity-card">' +
  342. '<div class="activity-card-bg"></div>' +
  343. '<div class="activity-card-image"' + (act.icon ? ' style="background-image: url(' + escapeHtml(act.icon) + ')"' : '') + '></div>' +
  344. '<div class="activity-card-title">' + escapeHtml(act.activityName || '') + '</div>' +
  345. '<div class="activity-card-footer">' +
  346. '<div class="activity-countdown">' +
  347. '<span class="activity-countdown-label" data-i18n="countdownLabel">距结束</span>' +
  348. '<span class="activity-countdown-days" id="activityDays_' + act.id + '">00</span>' +
  349. '<span class="activity-countdown-unit" data-i18n="daysUnit">天</span>' +
  350. '<span class="activity-countdown-time" id="activityTime_' + act.id + '">00 : 00 : 00</span>' +
  351. '</div>' +
  352. '<button class="activity-btn" id="activityBtn_' + act.id + '" ' + btnDisabled + '>' +
  353. '<span>' + btnText + '</span>' +
  354. '<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">' +
  355. '<g clip-path="url(#clip0_13933_579)">' +
  356. '<path d="M8.79187 5L1.29187 5" stroke="currentColor"/>' +
  357. '<path d="M6.29188 2.5L8.79187 5L6.29188 7.5" stroke="currentColor"/>' +
  358. '</g>' +
  359. '<defs>' +
  360. '<clipPath id="clip0_13933_579">' +
  361. '<rect width="10" height="10" fill="currentColor" transform="translate(10 10) rotate(180)"/>' +
  362. '</clipPath>' +
  363. '</defs>' +
  364. '</svg>' +
  365. '</button>' +
  366. '</div>' +
  367. '</div>';
  368. }
  369. html += '</div>';
  370. container.insertAdjacentHTML('beforeend', html);
  371. for (var j = 0; j < activities.length; j++) {
  372. (function (act) {
  373. var btn = document.getElementById('activityBtn_' + act.id);
  374. if (btn) {
  375. btn.onclick = function () {
  376. navigateTo('/html/business/bidirectional-activity.html', { activityId: act.id });
  377. };
  378. }
  379. startActivityCountdown(act);
  380. })(activities[j]);
  381. }
  382. }
  383. function renderInviteLink(container) {
  384. var inviteCode = (config && config.inviteCode) || '';
  385. var link = getInviteLink({
  386. inviteCode: inviteCode || '',
  387. });
  388. var html =
  389. '<div class="invite-link-section">' +
  390. '<div class="invite-link-card">' +
  391. '<div class="invite-link-info">' +
  392. '<div class="invite-link-label" data-i18n="inviteLinkLabel">邀请链接</div>' +
  393. '<div class="invite-link-url" id="inviteLinkUrl">' + escapeHtml(link) + '</div>' +
  394. '</div>' +
  395. '<div class="invite-link-actions">' +
  396. '<div class="invite-link-copy-btn" id="inviteLinkCopyBtn" aria-label="' + T('copyBtn') + '">' +
  397. '<svg viewBox="0 0 18 18" fill="none"><path d="M4.875 4.66185V2.92969C4.875 2.3472 5.3472 1.875 5.92969 1.875H15.0703C15.6528 1.875 16.125 2.3472 16.125 2.92969V12.0703C16.125 12.6528 15.6528 13.125 15.0703 13.125H13.3186" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/><path d="M12.0703 4.875H2.92969C2.3472 4.875 1.875 5.3472 1.875 5.92969V15.0703C1.875 15.6528 2.3472 16.125 2.92969 16.125H12.0703C12.6528 16.125 13.125 15.6528 13.125 15.0703V5.92969C13.125 5.3472 12.6528 4.875 12.0703 4.875Z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>' +
  398. '</div>' +
  399. '</div>' +
  400. '</div>' +
  401. '<div class="invite-link-share-btn" id="inviteLinkShareBtn" aria-label="Share">' +
  402. // '<svg viewBox="0 0 18 18" fill="none"><circle cx="9" cy="9" r="7.5" stroke="currentColor" stroke-width="1.2"/><circle cx="9" cy="9" r="3" stroke="currentColor" stroke-width="1.2"/></svg>' +
  403. '<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">' +
  404. '<path d="M10.5 2.25H15.75V7.5" stroke="currentColor" stroke-width="1.2"/>' +
  405. '<path d="M15.75 11.0526V14.625C15.75 15.2463 15.2463 15.75 14.625 15.75H3.375C2.75368 15.75 2.25 15.2463 2.25 14.625V3.375C2.25 2.75368 2.75368 2.25 3.375 2.25H6.75" stroke="currentColor" stroke-width="1.2"/>' +
  406. '<path d="M9.67493 8.32344L15.4124 2.58594" stroke="currentColor" stroke-width="1.2"/>' +
  407. '</svg>' +
  408. '</div>' +
  409. '</div>';
  410. container.insertAdjacentHTML('beforeend', html);
  411. document.getElementById('inviteLinkCopyBtn').onclick = function () {
  412. copyText(getInviteLink({
  413. inviteCode: config?.inviteCode || '',
  414. // activityId: config?.relatedInviteActivityId || '',
  415. }));
  416. };
  417. document.getElementById('inviteLinkShareBtn').onclick = jumpToInvite;
  418. // 底部邀请栏滚动显隐动画
  419. var inviteSection = document.querySelector('.invite-link-section');
  420. var scrollThreshold = 80; // 滚动超过80px显示
  421. var ticking = false;
  422. function updateInviteSectionVisibility() {
  423. var docHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight || 0);
  424. var winHeight = window.innerHeight || document.documentElement.clientHeight || 0;
  425. var maxScrollTop = docHeight - winHeight;
  426. // 页面不可滚动,或最大滚动距离不超过阈值时,始终显示
  427. if (maxScrollTop <= 0 || maxScrollTop <= scrollThreshold) {
  428. inviteSection.classList.add('visible');
  429. return;
  430. }
  431. var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
  432. if (scrollTop > scrollThreshold) {
  433. inviteSection.classList.add('visible');
  434. } else {
  435. inviteSection.classList.remove('visible');
  436. }
  437. }
  438. window.addEventListener('scroll', function () {
  439. if (!ticking) {
  440. window.requestAnimationFrame(function () {
  441. updateInviteSectionVisibility();
  442. ticking = false;
  443. });
  444. ticking = true;
  445. }
  446. }, { passive: true });
  447. // 初始检查一次
  448. updateInviteSectionVisibility();
  449. }
  450. function showActivateModal() {
  451. var overlay = document.getElementById('modalOverlay');
  452. const ID = config?.relatedInviteActivityId;
  453. const item = activities.find(item => item.id === ID);
  454. overlay.className = 'modal-overlay centered';
  455. overlay.innerHTML =
  456. '<div class="activate-modal">' +
  457. '<div class="activate-modal-title" data-i18n="modalActivateTitle">立即激活</div>' +
  458. '<div class="activate-modal-row" id="activateGoVerifyRow">' +
  459. '<span class="activate-modal-row-text" data-i18n="completeIdentity">完成身份认证</span>' +
  460. '<p class="activate-modal-row-action">' +
  461. '<span data-i18n="goVerify">去认证</span>' +
  462. '<svg width="14" height="14" viewBox="0 0 18 18" fill="none"><path d="M6.5 4l5 5-5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>' +
  463. '</span>' +
  464. '</p>' +
  465. '</div>' +
  466. '<button class="activate-modal-btn" id="activateGoInviteBtn" data-i18n="goInvite">去邀请</button>' +
  467. '<div class="activate-modal-note">' + T('statsNote', { endTime: item?.startTime?.split('T')?.[0]+ ' ' +item?.startTime?.split('T')?.[1] }) + '</div>' +
  468. '</div>';
  469. overlay.style.display = 'flex';
  470. overlay.onclick = function (e) { if (e.target === overlay) closeModal(); };
  471. BEXI18n.applyTranslations('inviteActivity');
  472. document.getElementById('activateGoVerifyRow').onclick = jumpToKyc;
  473. document.getElementById('activateGoInviteBtn').onclick = jumpToInvite;
  474. }
  475. function closeModal() {
  476. var overlay = document.getElementById('modalOverlay');
  477. overlay.style.display = 'none';
  478. overlay.innerHTML = '';
  479. }
  480. window.closeInviteActivityModal = closeModal;
  481. function jumpToKyc() {
  482. window.BEXBridge.goRouterName('/my/detail/identity-verification')
  483. }
  484. function jumpToInvite() {
  485. var activityId = (config && config.relatedInviteActivityId);
  486. var inviteCode = (config && config.inviteCode);
  487. window.BEXBridge.showInviteView({ inviteCode, activityId });
  488. }
  489. function bindStaticEvents() {
  490. document.getElementById('retryBtn').onclick = loadData;
  491. document.getElementById('errorBackBtn').onclick = function () { window.BEXBridge.closeWebview(); };
  492. }
  493. function applyTranslations() {
  494. if (window.BEXI18n) window.BEXI18n.applyTranslations('inviteActivity');
  495. }
  496. async function loadData() {
  497. showLoading();
  498. stopCountdowns();
  499. try {
  500. var results = await Promise.all([
  501. fetchConfig().catch(function (e) { throw { source: 'config', error: e }; }),
  502. fetchActivityList().catch(function (e) { throw { source: 'activityList', error: e }; })
  503. ]);
  504. config = results[0];
  505. activities = results[1] || [];
  506. hideError();
  507. renderPage();
  508. } catch (e) {
  509. console.error(e);
  510. showError();
  511. } finally {
  512. hideLoading();
  513. }
  514. }
  515. async function init() {
  516. try {
  517. token = await window.BEXBridge.getToken();
  518. } catch (error) {
  519. }
  520. bindStaticEvents();
  521. applyTranslations();
  522. await loadData();
  523. }
  524. document.addEventListener('DOMContentLoaded', init);
  525. })();
  526. </script>
  527. </body>
  528. </html>