| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <!-- vipActive false 未激活
- 激活条件
- 1 invited(布尔) 邀请一名好友
- 2 deposited(布尔) 是否累计充值10 USDT
- *满足以上条件即可激活vip权益
- -->
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
- <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
- <title>BEX会员等级</title>
- <script src="/token.js"></script>
- <script src="/js/url-cache.js"></script>
- <script>
- checkCache()
- (function () {
- const supportedThemes = ['dark', 'light'];
- const defaultTheme = 'light';
- function normalizeTheme(theme) {
- return supportedThemes.includes(theme) ? theme : '';
- }
- function getInitialTheme() {
- const urlParams = new URLSearchParams(window.location.search);
- const themeFromUrl = normalizeTheme(urlParams.get('theme'));
- if (themeFromUrl) return themeFromUrl;
- if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
- return 'light';
- }
- return defaultTheme;
- }
- document.documentElement.classList.add('theme-' + getInitialTheme());
- })();
- </script>
- <link rel="stylesheet" href="/css/business-theme.css">
- <link rel="stylesheet" href="/css/vip.css">
- </head>
- <body>
- <div class="container">
-
- <div class="header">
- <svg id="backButton" width="16" height="16" viewBox="0 0 16 16" fill="none"
- xmlns="http://www.w3.org/2000/svg">
- <path d="M10.4 13.6L4.8 8L10.4 2.4" stroke="white" stroke-width="1.6" stroke-linecap="round"
- stroke-linejoin="round" />
- </svg>
- <div class="header-title" data-i18n="headerTitle">BEX会员等级</div>
- <div></div>
- </div>
- <div class="vip-card">
- <i class="vip-bg"></i>
- <div class="vip-box">
- <div class="vip-card-header">
- <div class="vip-level-badge">
- <div class="vip-level-title" data-i18n="vipLevelTitle">会员等级</div>
- <div class="vip-level-text" data-field="vipLevel"></div>
- </div>
- </div>
- <!-- 已激活:进度条状态 -->
- <div class="vip-active-content" id="vipActiveContent">
- <div class="progress-items">
- <div class="progress-item">
- <div class="progress-label" data-i18n="progressContribution"> <span
- data-field="contribution"></span> </div>
- <div class="progress-bar">
- <div class="progress-bar-fill" data-field="contributionProgress"></div>
- </div>
- </div>
- <div class="progress-item">
- <div class="progress-label" data-i18n="progressStaking"><span data-field="staking"></span>
- </div>
- <div class="progress-bar">
- <div class="progress-bar-fill" data-field="stakingProgress"></div>
- </div>
- </div>
- </div>
- <div class="vip-card-tip" data-i18n="vipTip">*满足以上条件即可升级至 VIP3</div>
- </div>
- <!-- 未激活:条件展示状态 -->
- <div class="vip-inactive-content" id="vipInactiveContent" style="display:none;">
- <div class="activation-conditions">
- <div class="activation-condition">
- <div class="condition-icon" id="invitedIcon">
- <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
- <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.5" />
- <path d="M4.5 8L7 10.5L11.5 5.5" stroke="currentColor" stroke-width="1.5"
- stroke-linecap="round" stroke-linejoin="round" />
- </svg>
- </div>
- <span class="condition-label" data-i18n="activationInvite">邀请一名好友</span>
- <span class="condition-status" data-field="invitedStatus"></span>
- </div>
- <div class="activation-condition">
- <div class="condition-icon" id="depositedIcon">
- <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
- <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.5" />
- <path d="M4.5 8L7 10.5L11.5 5.5" stroke="currentColor" stroke-width="1.5"
- stroke-linecap="round" stroke-linejoin="round" />
- </svg>
- </div>
- <span class="condition-label" data-i18n="activationDeposit">累计充值10 USDT</span>
- <span class="condition-status" data-field="depositedStatus"></span>
- </div>
- </div>
- <div class="vip-card-tip" data-i18n="vipActiveTip">*满足以上条件即可解锁VIP权益</div>
- </div>
- </div>
- </div>
- <!-- <div class="section-title" data-i18n="benefitsTitle">等级权益</div> -->
- <div class="benefits-tabs" id="benefitsTabs">
- <div class="benefits-tab active" data-tab="requirements" data-i18n="benefitsTabRequirements">等级权益</div>
- <div class="benefits-tab" data-tab="benefits" data-i18n="benefitsTabBenefits">升级规则</div>
- </div>
- <div class="benefits-table" id="benefitsTable">
- <div>
- <div class="benefits-header requirements-header" style="display: none;">
- <div data-i18n="benefitsVipLevel" class="td">会员等级</div>
- <div data-i18n="benefitsContribution" class="td">贡献值</div>
- <div data-i18n="benefitsStaking" class="td">个人质押要求</div>
- </div>
- <div class="benefits-header benefits-ben-header">
- <div data-i18n="benefitsVipLevel" class="td">会员等级</div>
- <div data-i18n="benefitsTeamReward" class="td">团队质押动态奖励</div>
- <div data-i18n="benefitsTradeReturn" class="td">交易返佣比例</div>
- </div>
- <div id="benefitsBody" style="display: none;"></div>
- <div id="benefitsBodyAlt"></div>
- </div>
- </div>
- <div class="contribution-section" id="contributionSection">
- <div class="contribution-title" data-i18n="contributionTitle">贡献值获取方式</div>
- <div class="contribution-card" id="contributionCard">
- </div>
- <div class="contribution-tip" data-i18n="contributionTip">*降级规则: 所有级别如贡献值下降,原级别保留 1 个月;下个月如继续未达标,则降级至对应实际级别
- </div>
- </div>
- <div class="description-modal" id="descriptionModal" style="display: none;">
- <div class="modal-overlay" id="modalOverlay"></div>
- <div class="modal-content">
- <div class="modal-header">
- <span class="modal-title" data-i18n="modalTitle">说明</span>
- <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">
- <line x1="18" y1="6" x2="6" y2="18"></line>
- <line x1="6" y1="6" x2="18" y2="18"></line>
- </svg>
- </div>
- <div class="modal-body">
- <p id="modalDescription"></p>
- </div>
- </div>
- </div>
- <div class="loading-overlay" id="loadingOverlay" style="display: flex;">
- <div class="loading-content">
- <div class="loading-spinner"></div>
- <span class="loading-text" data-i18n="loadingText">加载中...</span>
- </div>
- </div>
- <div class="error-overlay" id="errorOverlay" style="display: none;">
- <div class="error-content">
- <div class="error-icon">!</div>
- <span class="error-text" data-i18n="errorText">加载失败,请稍后重试</span>
- <div class="error-actions">
- <button class="error-retry" id="retryBtn" data-i18n="retryBtn">重试</button>
- <button class="error-retry" id="errorBackBtn" data-i18n="errorBackBtn">返回</button>
- </div>
- </div>
- </div>
- </div>
- <script src="/js/theme.js"></script>
- <script src="/js/app.js"></script>
- <script src="/js/i18n/config.js"></script>
- <script src="/js/i18n/locales/zh.js"></script>
- <script src="/js/i18n/locales/en.js"></script>
- <script src="/js/i18n/locales/zh_TW.js"></script>
- <script src="/js/i18n/locales/ja.js"></script>
- <script src="/js/i18n/locales/ko.js"></script>
- <script src="/js/i18n/locales/th.js"></script>
- <script src="/js/i18n/locales/fr.js"></script>
- <script src="/js/i18n/locales/es.js"></script>
- <script src="/js/i18n/locales/pt.js"></script>
- <script src="/js/i18n/locales/de.js"></script>
- <script src="/js/i18n/index.js"></script>
- <script>
- (function () {
- function applyTranslations(lang, nextVipLevel) {
- BEXI18n.applyTranslations('vip', {
- params: { nextVipLevel: 'VIP' + (nextVipLevel || '') },
- htmlKeys: ['progressContribution', 'progressStaking', 'vipTip', 'contributionPersonal', 'contributionTeam', 'contributionSpot', 'contributionFutures']
- });
- }
- const API_URL = '/api/v1';
- const loadingOverlay = document.getElementById('loadingOverlay');
- const errorOverlay = document.getElementById('errorOverlay');
- const retryBtn = document.getElementById('retryBtn');
- const errorBackBtn = document.getElementById('errorBackBtn');
- const benefitsBody = document.getElementById('benefitsBody');
- const benefitsBodyAlt = document.getElementById('benefitsBodyAlt');
- const backButton = document.getElementById('backButton');
- const benefitsTabs = document.querySelectorAll('.benefits-tab');
- const contributionCard = document.getElementById('contributionCard');
- const descriptionModal = document.getElementById('descriptionModal');
- const modalOverlay = document.getElementById('modalOverlay');
- const modalClose = document.getElementById('modalClose');
- const modalDescription = document.getElementById('modalDescription');
- benefitsTabs.forEach(tab => {
- tab.addEventListener('click', function () {
- const tabName = this.dataset.tab;
- benefitsTabs.forEach(t => t.classList.remove('active'));
- this.classList.add('active');
- if (tabName === 'requirements') {
- document.querySelector('.requirements-header.requirements-header').style.display = 'none';
- document.querySelector('.benefits-header.benefits-ben-header').style.display = 'grid';
- benefitsBody.style.display = 'none';
- benefitsBodyAlt.style.display = 'block';
- } else {
- document.querySelector('.requirements-header.requirements-header').style.display = 'grid';
- document.querySelector('.benefits-header.benefits-ben-header').style.display = 'none';
- benefitsBody.style.display = 'block';
- benefitsBodyAlt.style.display = 'none';
- }
- });
- });
- backButton.addEventListener('click', function () {
- if (window.flutter_inappwebview && window.flutter_inappwebview.callHandler) {
- window.flutter_inappwebview.callHandler('closeWebview');
- }
- });
- errorBackBtn.addEventListener('click', function () {
- if (window.flutter_inappwebview && window.flutter_inappwebview.callHandler) {
- window.flutter_inappwebview.callHandler('closeWebview');
- }
- });
- function showDescriptionModal(description) {
- modalDescription.textContent = description;
- descriptionModal.style.display = 'flex';
- }
- function hideDescriptionModal() {
- descriptionModal.style.display = 'none';
- }
- modalOverlay.addEventListener('click', hideDescriptionModal);
- modalClose.addEventListener('click', hideDescriptionModal);
- function showLoading() {
- loadingOverlay.style.display = 'flex';
- errorOverlay.style.display = 'none';
- }
- function hideLoading() {
- loadingOverlay.style.display = 'none';
- }
- function showError() {
- loadingOverlay.style.display = 'none';
- errorOverlay.style.display = 'flex';
- document.getElementById('benefitsTabs').style.display = 'none';
- document.getElementById('benefitsTable').style.display = 'none';
- document.getElementById('contributionSection').style.display = 'none';
- }
- function renderBenefitsRows(vipList) {
- if (!Array.isArray(vipList) || vipList.length === 0) {
- console.warn('VIP列表数据为空或格式错误');
- return;
- }
- benefitsBody.innerHTML = '';
- const lang = getCurrentLang();
- const translation = BEXI18n.getPage('vip', lang);
- const stakingUnit = translation.stakingUnit;
- vipList.forEach(item => {
- const row = document.createElement('div');
- row.className = 'benefits-row';
- row.dataset.vip = item.vipLevel;
- row.innerHTML = `
- <div class="vip-name td">
- <img src="/images/vip${item.vipLevel}.png" alt="${item.vipName}">
- <span>${item.vipName}</span>
- </div>
- <div class="td">${item.contributionValue}</div>
- <div class="td">${item.personalPledgeRequirements}${stakingUnit}</div>
- `;
- benefitsBody.appendChild(row);
- });
- }
- function renderBenefitsAltRows(vipList) {
- if (!Array.isArray(vipList) || vipList.length === 0) {
- console.warn('VIP列表数据为空或格式错误');
- return;
- }
- benefitsBodyAlt.innerHTML = '';
- const lang = getCurrentLang();
- const translation = BEXI18n.getPage('vip', lang);
- const stakingUnit = translation.stakingUnit;
- vipList.forEach(item => {
- const row = document.createElement('div');
- row.className = 'benefits-row';
- row.dataset.vip = item.vipLevel;
- row.innerHTML = `
- <div class="vip-name td">
- <img src="/images/vip${item.vipLevel}.png" alt="${item.vipName}">
- <span>${item.vipName}</span>
- </div>
- <div class="td">${item.teamStakingDynamicRewards}${translation.teamRewardUnit}</div>
- <div class="td">${item.transactionRefundRatio}${translation.tradeReturnUnit}</div>
- `;
- benefitsBodyAlt.appendChild(row);
- });
- }
- function renderContributionItems(contributionRuleConfigs) {
- const lang = getCurrentLang();
- const translation = BEXI18n.getPage('vip', lang);
- const stakingUnit = translation.stakingUnitWithSpace || translation.stakingUnit;
- const currencySymbol = 'U';
- const ruleCodeMap = {
- 'PERSONAL_STAKE': { key: 'personalStaking', i18nKey: 'contributionPersonal', unit: stakingUnit },
- 'TEAM_STAKE': { key: 'teamStaking', i18nKey: 'contributionTeam', unit: stakingUnit },
- 'SPOT_TRADE': { key: 'spotTrading', i18nKey: 'contributionSpot', unit: currencySymbol },
- 'FUTURES_TRADE': { key: 'futuresTrading', i18nKey: 'contributionFutures', unit: currencySymbol }
- };
- const rules = {};
- const items = [];
- const order = ['PERSONAL_STAKE', 'TEAM_STAKE', 'SPOT_TRADE', 'FUTURES_TRADE'];
- contributionRuleConfigs.forEach(item => {
- const mapping = ruleCodeMap[item.ruleCode];
- if (mapping) {
- rules[mapping.key] = item.threshold + mapping.unit;
- items.push({
- ruleCode: item.ruleCode,
- key: mapping.key,
- i18nKey: mapping.i18nKey,
- value: item.threshold + mapping.unit,
- description: item.description,
- orderIndex: order.indexOf(item.ruleCode)
- });
- }
- });
- items.sort((a, b) => a.orderIndex - b.orderIndex);
- contributionCard.innerHTML = '';
- items.forEach(item => {
- const itemDiv = document.createElement('div');
- itemDiv.className = 'contribution-item';
- itemDiv.innerHTML = translation[item.i18nKey].replace(
- new RegExp(`<span class="highlight" data-field="rules\\.${item.key}">[^<]*</span>`, 'g'),
- `<span class="highlight">${item.value}</span>`
- );
- if (item.description) {
- const infoIcon = document.createElement('div');
- infoIcon.className = 'contribution-info-icon';
- 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">
- <circle cx="12" cy="12" r="10"></circle>
- <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
- <line x1="12" y1="17" x2="12.01" y2="17"></line>
- </svg>`;
- infoIcon.addEventListener('click', () => showDescriptionModal(item.description));
- itemDiv.appendChild(infoIcon);
- }
- contributionCard.appendChild(itemDiv);
- });
- return rules;
- }
- function insertData(data) {
- const vipBenefitManagements = data?.vipBenefitManagements || [];
- const contributionRuleConfigs = data?.contributionRuleConfigs || [];
- const vipActive = data?.vipActive !== false;
- const invited = data?.invited || false;
- const deposited = data?.deposited || false;
- if (vipBenefitManagements.length > 0) {
- renderBenefitsRows(vipBenefitManagements);
- renderBenefitsAltRows(vipBenefitManagements);
- }
- const rules = renderContributionItems(contributionRuleConfigs);
- const lang = getCurrentLang();
- const translation = BEXI18n.getPage('vip', lang);
- const stakingUnit = translation.stakingUnitWithSpace || translation.stakingUnit;
- // 已激活 / 未激活 状态切换
- const vipActiveContent = document.getElementById('vipActiveContent');
- const vipInactiveContent = document.getElementById('vipInactiveContent');
- if (!vipActive) {
- // 未激活:显示条件卡片
- vipActiveContent.style.display = 'none';
- vipInactiveContent.style.display = 'block';
- // 设置等级文字为"未激活"
- const vipLevelEl = document.querySelector('[data-field="vipLevel"]');
- if (vipLevelEl) {
- vipLevelEl.textContent = translation.vipInactive || '未激活';
- vipLevelEl.classList.add('inactive');
- }
- // 设置邀请好友条件
- const invitedIcon = document.getElementById('invitedIcon');
- const invitedStatus = document.querySelector('[data-field="invitedStatus"]');
- if (invitedIcon && invitedStatus) {
- invitedIcon.className = 'condition-icon ' + (invited ? 'completed' : 'pending');
- invitedStatus.textContent = invited ? (translation.activationCompleted || '已完成') : (translation.activationPending || '待完成');
- }
- // 设置充值条件
- const depositedIcon = document.getElementById('depositedIcon');
- const depositedStatus = document.querySelector('[data-field="depositedStatus"]');
- if (depositedIcon && depositedStatus) {
- depositedIcon.className = 'condition-icon ' + (deposited ? 'completed' : 'pending');
- depositedStatus.textContent = deposited ? (translation.activationCompleted || '已完成') : (translation.activationPending || '待完成');
- }
- return;
- }
- // 已激活:显示进度条(原有逻辑)
- vipActiveContent.style.display = 'block';
- vipInactiveContent.style.display = 'none';
- const currentVipLevel = data?.vipLevel || 0;
- const personalValue = data?.personalValue || 0;
- const personalStakeValue = data?.personalStakeValue || 0;
- const currentLevelInfo = vipBenefitManagements.find(item => item.vipLevel === currentVipLevel);
- const nextLevelInfo = vipBenefitManagements.find(item => item.vipLevel === currentVipLevel + 1);
- let contributionProgress = 0;
- let stakingProgress = 0;
- let contributionNeeded = 0;
- let stakingNeeded = 0;
- if (nextLevelInfo) {
- const currentContribution = currentLevelInfo ? currentLevelInfo.contributionValue || 0 : 0;
- const currentStaking = currentLevelInfo ? currentLevelInfo.personalPledgeRequirements || 0 : 0;
- const nextContribution = nextLevelInfo.contributionValue || 0;
- const nextStaking = nextLevelInfo.personalPledgeRequirements || 0;
- const contributionDelta = mathSub(nextContribution, currentContribution);
- const stakingDelta = mathSub(nextStaking, currentStaking);
- const currentContributionIncrement = mathSub(personalValue, currentContribution);
- const currentStakingIncrement = mathSub(personalStakeValue, currentStaking);
- contributionNeeded = Math.max(0, mathSub(contributionDelta, currentContributionIncrement));
- stakingNeeded = Math.max(0, mathSub(stakingDelta, currentStakingIncrement));
- contributionProgress = contributionDelta > 0 ? Math.min(100, Math.max(0, (currentContributionIncrement / contributionDelta) * 100)) : 0;
- stakingProgress = stakingDelta > 0 ? Math.min(100, Math.max(0, (currentStakingIncrement / stakingDelta) * 100)) : 0;
- } else {
- contributionProgress = 100;
- stakingProgress = 100;
- }
- const elements = document.querySelectorAll('[data-field]');
- elements.forEach(element => {
- const field = element.dataset.field;
- let value = getNestedValue(data, field);
- if (field === 'vipLevel' && data.vipName) {
- value = data.vipName;
- }
- if (field.startsWith('rules.')) {
- const ruleKey = field.replace('rules.', '');
- value = rules[ruleKey];
- }
- if (field === 'contribution') {
- value = contributionNeeded + translation.contributionValueUnit;
- }
- if (field === 'staking') {
- value = stakingNeeded + stakingUnit;
- }
- if (field === 'personalValue') {
- value = value + translation.contributionValueUnit;
- }
- if (field === 'personalStakeValue') {
- value = value + stakingUnit;
- }
- if (field === 'contributionProgress') {
- value = contributionProgress.toFixed(0);
- }
- if (field === 'stakingProgress') {
- value = stakingProgress.toFixed(0);
- }
- if (value !== undefined && value !== null) {
- if (field.includes('Progress')) {
- element.style.width = value + '%';
- } else {
- element.textContent = value;
- }
- }
- });
- }
- function getNestedValue(obj, path) {
- return path.split('.').reduce((current, key) => {
- return current && current[key] !== undefined ? current[key] : undefined;
- }, obj);
- }
- var token = window.WELFARE_TOKEN || '';
- async function getFilterFromNative() {
- try {
- const filter = await window.flutter_inappwebview.callHandler('getToken');
- token = filter;
- return filter;
- } catch (e) {
- console.error('JSBridge 调用失败', e);
- return null;
- }
- }
- async function fetchData() {
- showLoading();
- try {
- const headers = {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- 'X-Requested-With': 'XMLHttpRequest',
- 'localeName': getCurrentLang(),
- ...globalHeaders
- };
- if (token) {
- headers['Authorization'] = token;
- }
- const response = await fetch(API_URL + '/userVipInfo/info', {
- method: 'GET',
- headers: headers,
- credentials: 'include',
- timeout: 10000
- });
- if (!response.ok) {
- throw new Error(`请求失败! status: ${response.status}`);
- }
- const result = await response.json();
- const data = result.data || {};
- const currentVipLevel = data.vipLevel || 0;
- const nextVipLevel = currentVipLevel + 1;
- applyTranslations(getCurrentLang(), nextVipLevel);
- insertData(data);
- } catch (error) {
- console.error('Data fetch error:', error);
- showError()
- } finally {
- hideLoading();
- }
- }
- retryBtn.addEventListener('click', fetchData);
- document.addEventListener('DOMContentLoaded', async function () {
- if (window.BEXTheme) {
- window.BEXTheme.init();
- }
- const lang = getCurrentLang();
- applyTranslations(lang);
- await getFilterFromNative();
- fetchData();
- });
- })();
- </script>
- </body>
- </html>
|