vip.html 28 KB

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