vip.html 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <!-- vipActive false 未激活
  4. 激活条件
  5. 1 invited(布尔) 邀请一名好友
  6. 2 deposited(布尔) 是否累计充值10 USDT
  7. *满足以上条件即可激活vip权益
  8. -->
  9. <head>
  10. <meta charset="UTF-8">
  11. <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  12. <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
  13. <title>BEX会员等级</title>
  14. <script src="/token.js"></script>
  15. <script src="/js/url-cache.js"></script>
  16. <script>
  17. checkCache()
  18. (function () {
  19. const supportedThemes = ['dark', 'light'];
  20. const defaultTheme = 'light';
  21. function normalizeTheme(theme) {
  22. return supportedThemes.includes(theme) ? theme : '';
  23. }
  24. function getInitialTheme() {
  25. const urlParams = new URLSearchParams(window.location.search);
  26. const themeFromUrl = normalizeTheme(urlParams.get('theme'));
  27. if (themeFromUrl) return themeFromUrl;
  28. if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
  29. return 'light';
  30. }
  31. return defaultTheme;
  32. }
  33. document.documentElement.classList.add('theme-' + getInitialTheme());
  34. })();
  35. </script>
  36. <link rel="stylesheet" href="/css/business-theme.css">
  37. <link rel="stylesheet" href="/css/vip.css">
  38. </head>
  39. <body>
  40. <div class="container">
  41. <div class="header">
  42. <svg id="backButton" width="16" height="16" viewBox="0 0 16 16" fill="none"
  43. xmlns="http://www.w3.org/2000/svg">
  44. <path d="M10.4 13.6L4.8 8L10.4 2.4" stroke="white" stroke-width="1.6" stroke-linecap="round"
  45. stroke-linejoin="round" />
  46. </svg>
  47. <div class="header-title" data-i18n="headerTitle">BEX会员等级</div>
  48. <div></div>
  49. </div>
  50. <div class="vip-card">
  51. <i class="vip-bg"></i>
  52. <div class="vip-box">
  53. <div class="vip-card-header">
  54. <div class="vip-level-badge">
  55. <div class="vip-level-title" data-i18n="vipLevelTitle">会员等级</div>
  56. <div class="vip-level-text" data-field="vipLevel"></div>
  57. </div>
  58. </div>
  59. <!-- 已激活:进度条状态 -->
  60. <div class="vip-active-content" id="vipActiveContent">
  61. <div class="progress-items">
  62. <div class="progress-item">
  63. <div class="progress-label" data-i18n="progressContribution"> <span
  64. data-field="contribution"></span> </div>
  65. <div class="progress-bar">
  66. <div class="progress-bar-fill" data-field="contributionProgress"></div>
  67. </div>
  68. </div>
  69. <div class="progress-item">
  70. <div class="progress-label" data-i18n="progressStaking"><span data-field="staking"></span>
  71. </div>
  72. <div class="progress-bar">
  73. <div class="progress-bar-fill" data-field="stakingProgress"></div>
  74. </div>
  75. </div>
  76. </div>
  77. <div class="vip-card-tip" data-i18n="vipTip">*满足以上条件即可升级至 VIP3</div>
  78. </div>
  79. <!-- 未激活:条件展示状态 -->
  80. <div class="vip-inactive-content" id="vipInactiveContent" style="display:none;">
  81. <div class="activation-conditions">
  82. <div class="activation-condition">
  83. <div class="condition-icon" id="invitedIcon">
  84. <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
  85. <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.5" />
  86. <path d="M4.5 8L7 10.5L11.5 5.5" stroke="currentColor" stroke-width="1.5"
  87. stroke-linecap="round" stroke-linejoin="round" />
  88. </svg>
  89. </div>
  90. <span class="condition-label" data-i18n="activationInvite">邀请一名好友</span>
  91. <span class="condition-status" data-field="invitedStatus"></span>
  92. </div>
  93. <div class="activation-condition">
  94. <div class="condition-icon" id="depositedIcon">
  95. <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
  96. <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.5" />
  97. <path d="M4.5 8L7 10.5L11.5 5.5" stroke="currentColor" stroke-width="1.5"
  98. stroke-linecap="round" stroke-linejoin="round" />
  99. </svg>
  100. </div>
  101. <span class="condition-label" data-i18n="activationDeposit">累计充值10 USDT</span>
  102. <span class="condition-status" data-field="depositedStatus"></span>
  103. </div>
  104. </div>
  105. <div class="vip-card-tip" data-i18n="vipActiveTip">*满足以上条件即可解锁VIP权益</div>
  106. </div>
  107. </div>
  108. </div>
  109. <!-- <div class="section-title" data-i18n="benefitsTitle">等级权益</div> -->
  110. <div class="benefits-tabs" id="benefitsTabs">
  111. <div class="benefits-tab active" data-tab="requirements" data-i18n="benefitsTabRequirements">等级权益</div>
  112. <div class="benefits-tab" data-tab="benefits" data-i18n="benefitsTabBenefits">升级规则</div>
  113. </div>
  114. <div class="benefits-table" id="benefitsTable">
  115. <div>
  116. <div class="benefits-header requirements-header" style="display: none;">
  117. <div data-i18n="benefitsVipLevel" class="td">会员等级</div>
  118. <div data-i18n="benefitsContribution" class="td">贡献值</div>
  119. <div data-i18n="benefitsStaking" class="td">个人质押要求</div>
  120. </div>
  121. <div class="benefits-header benefits-ben-header">
  122. <div data-i18n="benefitsVipLevel" class="td">会员等级</div>
  123. <div data-i18n="benefitsTeamReward" class="td">团队质押动态奖励</div>
  124. <div data-i18n="benefitsTradeReturn" class="td">交易返佣比例</div>
  125. </div>
  126. <div id="benefitsBody" style="display: none;"></div>
  127. <div id="benefitsBodyAlt"></div>
  128. </div>
  129. </div>
  130. <div class="contribution-section" id="contributionSection">
  131. <div class="contribution-title" data-i18n="contributionTitle">贡献值获取方式</div>
  132. <div class="contribution-card" id="contributionCard">
  133. </div>
  134. <div class="contribution-tip" data-i18n="contributionTip">*降级规则: 所有级别如贡献值下降,原级别保留 1 个月;下个月如继续未达标,则降级至对应实际级别
  135. </div>
  136. </div>
  137. <div class="description-modal" id="descriptionModal" style="display: none;">
  138. <div class="modal-overlay" id="modalOverlay"></div>
  139. <div class="modal-content">
  140. <div class="modal-header">
  141. <span class="modal-title" data-i18n="modalTitle">说明</span>
  142. <svg class="modal-close" id="modalClose" width="24" height="24" viewBox="0 0 24 24" fill="none"
  143. stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  144. <line x1="18" y1="6" x2="6" y2="18"></line>
  145. <line x1="6" y1="6" x2="18" y2="18"></line>
  146. </svg>
  147. </div>
  148. <div class="modal-body">
  149. <p id="modalDescription"></p>
  150. </div>
  151. </div>
  152. </div>
  153. <div class="loading-overlay" id="loadingOverlay" style="display: flex;">
  154. <div class="loading-content">
  155. <div class="loading-spinner"></div>
  156. <span class="loading-text" data-i18n="loadingText">加载中...</span>
  157. </div>
  158. </div>
  159. <div class="error-overlay" id="errorOverlay" style="display: none;">
  160. <div class="error-content">
  161. <div class="error-icon">!</div>
  162. <span class="error-text" data-i18n="errorText">加载失败,请稍后重试</span>
  163. <div class="error-actions">
  164. <button class="error-retry" id="retryBtn" data-i18n="retryBtn">重试</button>
  165. <button class="error-retry" id="errorBackBtn" data-i18n="errorBackBtn">返回</button>
  166. </div>
  167. </div>
  168. </div>
  169. </div>
  170. <script src="/js/theme.js"></script>
  171. <script src="/js/app.js"></script>
  172. <script src="/js/i18n/config.js"></script>
  173. <script src="/js/i18n/locales/zh.js"></script>
  174. <script src="/js/i18n/locales/en.js"></script>
  175. <script src="/js/i18n/locales/zh_TW.js"></script>
  176. <script src="/js/i18n/locales/ja.js"></script>
  177. <script src="/js/i18n/locales/ko.js"></script>
  178. <script src="/js/i18n/locales/th.js"></script>
  179. <script src="/js/i18n/locales/fr.js"></script>
  180. <script src="/js/i18n/locales/es.js"></script>
  181. <script src="/js/i18n/locales/pt.js"></script>
  182. <script src="/js/i18n/locales/de.js"></script>
  183. <script src="/js/i18n/index.js"></script>
  184. <script>
  185. (function () {
  186. function applyTranslations(lang, nextVipLevel) {
  187. BEXI18n.applyTranslations('vip', {
  188. params: { nextVipLevel: 'VIP' + (nextVipLevel || '') },
  189. htmlKeys: ['progressContribution', 'progressStaking', 'vipTip', 'contributionPersonal', 'contributionTeam', 'contributionSpot', 'contributionFutures']
  190. });
  191. }
  192. const API_URL = '/api/v1';
  193. const loadingOverlay = document.getElementById('loadingOverlay');
  194. const errorOverlay = document.getElementById('errorOverlay');
  195. const retryBtn = document.getElementById('retryBtn');
  196. const errorBackBtn = document.getElementById('errorBackBtn');
  197. const benefitsBody = document.getElementById('benefitsBody');
  198. const benefitsBodyAlt = document.getElementById('benefitsBodyAlt');
  199. const backButton = document.getElementById('backButton');
  200. const benefitsTabs = document.querySelectorAll('.benefits-tab');
  201. const contributionCard = document.getElementById('contributionCard');
  202. const descriptionModal = document.getElementById('descriptionModal');
  203. const modalOverlay = document.getElementById('modalOverlay');
  204. const modalClose = document.getElementById('modalClose');
  205. const modalDescription = document.getElementById('modalDescription');
  206. benefitsTabs.forEach(tab => {
  207. tab.addEventListener('click', function () {
  208. const tabName = this.dataset.tab;
  209. benefitsTabs.forEach(t => t.classList.remove('active'));
  210. this.classList.add('active');
  211. if (tabName === 'requirements') {
  212. document.querySelector('.requirements-header.requirements-header').style.display = 'none';
  213. document.querySelector('.benefits-header.benefits-ben-header').style.display = 'grid';
  214. benefitsBody.style.display = 'none';
  215. benefitsBodyAlt.style.display = 'block';
  216. } else {
  217. document.querySelector('.requirements-header.requirements-header').style.display = 'grid';
  218. document.querySelector('.benefits-header.benefits-ben-header').style.display = 'none';
  219. benefitsBody.style.display = 'block';
  220. benefitsBodyAlt.style.display = 'none';
  221. }
  222. });
  223. });
  224. backButton.addEventListener('click', function () {
  225. if (window.flutter_inappwebview && window.flutter_inappwebview.callHandler) {
  226. window.flutter_inappwebview.callHandler('closeWebview');
  227. }
  228. });
  229. errorBackBtn.addEventListener('click', function () {
  230. if (window.flutter_inappwebview && window.flutter_inappwebview.callHandler) {
  231. window.flutter_inappwebview.callHandler('closeWebview');
  232. }
  233. });
  234. function showDescriptionModal(description) {
  235. modalDescription.textContent = description;
  236. descriptionModal.style.display = 'flex';
  237. }
  238. function hideDescriptionModal() {
  239. descriptionModal.style.display = 'none';
  240. }
  241. modalOverlay.addEventListener('click', hideDescriptionModal);
  242. modalClose.addEventListener('click', hideDescriptionModal);
  243. function showLoading() {
  244. loadingOverlay.style.display = 'flex';
  245. errorOverlay.style.display = 'none';
  246. }
  247. function hideLoading() {
  248. loadingOverlay.style.display = 'none';
  249. }
  250. function showError() {
  251. loadingOverlay.style.display = 'none';
  252. errorOverlay.style.display = 'flex';
  253. document.getElementById('benefitsTabs').style.display = 'none';
  254. document.getElementById('benefitsTable').style.display = 'none';
  255. document.getElementById('contributionSection').style.display = 'none';
  256. }
  257. function renderBenefitsRows(vipList) {
  258. if (!Array.isArray(vipList) || vipList.length === 0) {
  259. console.warn('VIP列表数据为空或格式错误');
  260. return;
  261. }
  262. benefitsBody.innerHTML = '';
  263. const lang = getCurrentLang();
  264. const translation = BEXI18n.getPage('vip', lang);
  265. const stakingUnit = translation.stakingUnit;
  266. vipList.forEach(item => {
  267. const row = document.createElement('div');
  268. row.className = 'benefits-row';
  269. row.dataset.vip = item.vipLevel;
  270. row.innerHTML = `
  271. <div class="vip-name td">
  272. <img src="/images/vip${item.vipLevel}.png" alt="${item.vipName}">
  273. <span>${item.vipName}</span>
  274. </div>
  275. <div class="td">${item.contributionValue}</div>
  276. <div class="td">${item.personalPledgeRequirements}${stakingUnit}</div>
  277. `;
  278. benefitsBody.appendChild(row);
  279. });
  280. }
  281. function renderBenefitsAltRows(vipList) {
  282. if (!Array.isArray(vipList) || vipList.length === 0) {
  283. console.warn('VIP列表数据为空或格式错误');
  284. return;
  285. }
  286. benefitsBodyAlt.innerHTML = '';
  287. const lang = getCurrentLang();
  288. const translation = BEXI18n.getPage('vip', lang);
  289. const stakingUnit = translation.stakingUnit;
  290. vipList.forEach(item => {
  291. const row = document.createElement('div');
  292. row.className = 'benefits-row';
  293. row.dataset.vip = item.vipLevel;
  294. row.innerHTML = `
  295. <div class="vip-name td">
  296. <img src="/images/vip${item.vipLevel}.png" alt="${item.vipName}">
  297. <span>${item.vipName}</span>
  298. </div>
  299. <div class="td">${item.teamStakingDynamicRewards}${translation.teamRewardUnit}</div>
  300. <div class="td">${item.transactionRefundRatio}${translation.tradeReturnUnit}</div>
  301. `;
  302. benefitsBodyAlt.appendChild(row);
  303. });
  304. }
  305. function renderContributionItems(contributionRuleConfigs) {
  306. const lang = getCurrentLang();
  307. const translation = BEXI18n.getPage('vip', lang);
  308. const stakingUnit = translation.stakingUnitWithSpace || translation.stakingUnit;
  309. const currencySymbol = 'U';
  310. const ruleCodeMap = {
  311. 'PERSONAL_STAKE': { key: 'personalStaking', i18nKey: 'contributionPersonal', unit: stakingUnit },
  312. 'TEAM_STAKE': { key: 'teamStaking', i18nKey: 'contributionTeam', unit: stakingUnit },
  313. 'SPOT_TRADE': { key: 'spotTrading', i18nKey: 'contributionSpot', unit: currencySymbol },
  314. 'FUTURES_TRADE': { key: 'futuresTrading', i18nKey: 'contributionFutures', unit: currencySymbol }
  315. };
  316. const rules = {};
  317. const items = [];
  318. const order = ['PERSONAL_STAKE', 'TEAM_STAKE', 'SPOT_TRADE', 'FUTURES_TRADE'];
  319. contributionRuleConfigs.forEach(item => {
  320. const mapping = ruleCodeMap[item.ruleCode];
  321. if (mapping) {
  322. rules[mapping.key] = item.threshold + mapping.unit;
  323. items.push({
  324. ruleCode: item.ruleCode,
  325. key: mapping.key,
  326. i18nKey: mapping.i18nKey,
  327. value: item.threshold + mapping.unit,
  328. description: item.description,
  329. orderIndex: order.indexOf(item.ruleCode)
  330. });
  331. }
  332. });
  333. items.sort((a, b) => a.orderIndex - b.orderIndex);
  334. contributionCard.innerHTML = '';
  335. items.forEach(item => {
  336. const itemDiv = document.createElement('div');
  337. itemDiv.className = 'contribution-item';
  338. itemDiv.innerHTML = translation[item.i18nKey].replace(
  339. new RegExp(`<span class="highlight" data-field="rules\\.${item.key}">[^<]*</span>`, 'g'),
  340. `<span class="highlight">${item.value}</span>`
  341. );
  342. if (item.description) {
  343. const infoIcon = document.createElement('div');
  344. infoIcon.className = 'contribution-info-icon';
  345. infoIcon.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  346. <circle cx="12" cy="12" r="10"></circle>
  347. <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
  348. <line x1="12" y1="17" x2="12.01" y2="17"></line>
  349. </svg>`;
  350. infoIcon.addEventListener('click', () => showDescriptionModal(item.description));
  351. itemDiv.appendChild(infoIcon);
  352. }
  353. contributionCard.appendChild(itemDiv);
  354. });
  355. return rules;
  356. }
  357. function insertData(data) {
  358. const vipBenefitManagements = data?.vipBenefitManagements || [];
  359. const contributionRuleConfigs = data?.contributionRuleConfigs || [];
  360. const vipActive = data?.vipActive !== false;
  361. const invited = data?.invited || false;
  362. const deposited = data?.deposited || false;
  363. if (vipBenefitManagements.length > 0) {
  364. renderBenefitsRows(vipBenefitManagements);
  365. renderBenefitsAltRows(vipBenefitManagements);
  366. }
  367. const rules = renderContributionItems(contributionRuleConfigs);
  368. const lang = getCurrentLang();
  369. const translation = BEXI18n.getPage('vip', lang);
  370. const stakingUnit = translation.stakingUnitWithSpace || translation.stakingUnit;
  371. // 已激活 / 未激活 状态切换
  372. const vipActiveContent = document.getElementById('vipActiveContent');
  373. const vipInactiveContent = document.getElementById('vipInactiveContent');
  374. if (!vipActive) {
  375. // 未激活:显示条件卡片
  376. vipActiveContent.style.display = 'none';
  377. vipInactiveContent.style.display = 'block';
  378. // 设置等级文字为"未激活"
  379. const vipLevelEl = document.querySelector('[data-field="vipLevel"]');
  380. if (vipLevelEl) {
  381. vipLevelEl.textContent = translation.vipInactive || '未激活';
  382. vipLevelEl.classList.add('inactive');
  383. }
  384. // 设置邀请好友条件
  385. const invitedIcon = document.getElementById('invitedIcon');
  386. const invitedStatus = document.querySelector('[data-field="invitedStatus"]');
  387. if (invitedIcon && invitedStatus) {
  388. invitedIcon.className = 'condition-icon ' + (invited ? 'completed' : 'pending');
  389. invitedStatus.textContent = invited ? (translation.activationCompleted || '已完成') : (translation.activationPending || '待完成');
  390. }
  391. // 设置充值条件
  392. const depositedIcon = document.getElementById('depositedIcon');
  393. const depositedStatus = document.querySelector('[data-field="depositedStatus"]');
  394. if (depositedIcon && depositedStatus) {
  395. depositedIcon.className = 'condition-icon ' + (deposited ? 'completed' : 'pending');
  396. depositedStatus.textContent = deposited ? (translation.activationCompleted || '已完成') : (translation.activationPending || '待完成');
  397. }
  398. return;
  399. }
  400. // 已激活:显示进度条(原有逻辑)
  401. vipActiveContent.style.display = 'block';
  402. vipInactiveContent.style.display = 'none';
  403. const currentVipLevel = data?.vipLevel || 0;
  404. const personalValue = data?.personalValue || 0;
  405. const personalStakeValue = data?.personalStakeValue || 0;
  406. const currentLevelInfo = vipBenefitManagements.find(item => item.vipLevel === currentVipLevel);
  407. const nextLevelInfo = vipBenefitManagements.find(item => item.vipLevel === currentVipLevel + 1);
  408. let contributionProgress = 0;
  409. let stakingProgress = 0;
  410. let contributionNeeded = 0;
  411. let stakingNeeded = 0;
  412. if (nextLevelInfo) {
  413. const currentContribution = currentLevelInfo ? currentLevelInfo.contributionValue || 0 : 0;
  414. const currentStaking = currentLevelInfo ? currentLevelInfo.personalPledgeRequirements || 0 : 0;
  415. const nextContribution = nextLevelInfo.contributionValue || 0;
  416. const nextStaking = nextLevelInfo.personalPledgeRequirements || 0;
  417. const contributionDelta = mathSub(nextContribution, currentContribution);
  418. const stakingDelta = mathSub(nextStaking, currentStaking);
  419. const currentContributionIncrement = mathSub(personalValue, currentContribution);
  420. const currentStakingIncrement = mathSub(personalStakeValue, currentStaking);
  421. contributionNeeded = Math.max(0, mathSub(contributionDelta, currentContributionIncrement));
  422. stakingNeeded = Math.max(0, mathSub(stakingDelta, currentStakingIncrement));
  423. contributionProgress = contributionDelta > 0 ? Math.min(100, Math.max(0, (currentContributionIncrement / contributionDelta) * 100)) : 0;
  424. stakingProgress = stakingDelta > 0 ? Math.min(100, Math.max(0, (currentStakingIncrement / stakingDelta) * 100)) : 0;
  425. } else {
  426. contributionProgress = 100;
  427. stakingProgress = 100;
  428. }
  429. const elements = document.querySelectorAll('[data-field]');
  430. elements.forEach(element => {
  431. const field = element.dataset.field;
  432. let value = getNestedValue(data, field);
  433. if (field === 'vipLevel' && data.vipName) {
  434. value = data.vipName;
  435. }
  436. if (field.startsWith('rules.')) {
  437. const ruleKey = field.replace('rules.', '');
  438. value = rules[ruleKey];
  439. }
  440. if (field === 'contribution') {
  441. value = contributionNeeded + translation.contributionValueUnit;
  442. }
  443. if (field === 'staking') {
  444. value = stakingNeeded + stakingUnit;
  445. }
  446. if (field === 'personalValue') {
  447. value = value + translation.contributionValueUnit;
  448. }
  449. if (field === 'personalStakeValue') {
  450. value = value + stakingUnit;
  451. }
  452. if (field === 'contributionProgress') {
  453. value = contributionProgress.toFixed(0);
  454. }
  455. if (field === 'stakingProgress') {
  456. value = stakingProgress.toFixed(0);
  457. }
  458. if (value !== undefined && value !== null) {
  459. if (field.includes('Progress')) {
  460. element.style.width = value + '%';
  461. } else {
  462. element.textContent = value;
  463. }
  464. }
  465. });
  466. }
  467. function getNestedValue(obj, path) {
  468. return path.split('.').reduce((current, key) => {
  469. return current && current[key] !== undefined ? current[key] : undefined;
  470. }, obj);
  471. }
  472. var token = window.WELFARE_TOKEN || '';
  473. async function getFilterFromNative() {
  474. try {
  475. const filter = await window.flutter_inappwebview.callHandler('getToken');
  476. token = filter;
  477. return filter;
  478. } catch (e) {
  479. console.error('JSBridge 调用失败', e);
  480. return null;
  481. }
  482. }
  483. async function fetchData() {
  484. showLoading();
  485. try {
  486. const headers = {
  487. 'Content-Type': 'application/json',
  488. 'Accept': 'application/json',
  489. 'X-Requested-With': 'XMLHttpRequest',
  490. 'localeName': getCurrentLang(),
  491. ...globalHeaders
  492. };
  493. if (token) {
  494. headers['Authorization'] = token;
  495. }
  496. const response = await fetch(API_URL + '/userVipInfo/info', {
  497. method: 'GET',
  498. headers: headers,
  499. credentials: 'include',
  500. timeout: 10000
  501. });
  502. if (!response.ok) {
  503. throw new Error(`请求失败! status: ${response.status}`);
  504. }
  505. const result = await response.json();
  506. const data = result.data || {};
  507. const currentVipLevel = data.vipLevel || 0;
  508. const nextVipLevel = currentVipLevel + 1;
  509. applyTranslations(getCurrentLang(), nextVipLevel);
  510. insertData(data);
  511. } catch (error) {
  512. console.error('Data fetch error:', error);
  513. showError()
  514. } finally {
  515. hideLoading();
  516. }
  517. }
  518. retryBtn.addEventListener('click', fetchData);
  519. document.addEventListener('DOMContentLoaded', async function () {
  520. if (window.BEXTheme) {
  521. window.BEXTheme.init();
  522. }
  523. const lang = getCurrentLang();
  524. applyTranslations(lang);
  525. await getFilterFromNative();
  526. fetchData();
  527. });
  528. })();
  529. </script>
  530. </body>
  531. </html>