This commit is contained in:
zhx 2026-06-11 01:01:40 +08:00
parent c7a9801549
commit a95322b0d2
16 changed files with 4376 additions and 153 deletions

View File

@ -7,3 +7,7 @@ H5 通用主题放在 common/theme.css新页面默认引用该文件。
页面样式不要硬编码主色,优先消费 common/theme.css 里的 CSS 变量;只有 Figma 强制要求 100% 还原时才允许局部覆盖。 页面样式不要硬编码主色,优先消费 common/theme.css 里的 CSS 变量;只有 Figma 强制要求 100% 还原时才允许局部覆盖。
H5 多语言能力放在 common/i18n.js 和 common/locales/\*.json当前面向英语 en、阿拉伯语 ar、土耳其语 tr、西班牙语 es。页面文案使用 data-i18n / data-i18n-placeholder / data-i18n-aria 绑定语言包 key新增页面必须优先复用 common 的语言包机制,不在页面脚本里硬编码多语言字典。 H5 多语言能力放在 common/i18n.js 和 common/locales/\*.json当前面向英语 en、阿拉伯语 ar、土耳其语 tr、西班牙语 es。页面文案使用 data-i18n / data-i18n-placeholder / data-i18n-aria 绑定语言包 key新增页面必须优先复用 common 的语言包机制,不在页面脚本里硬编码多语言字典。
所有逻辑功能配置默认打开;只有密钥、地址、三方账号等必须由环境提供的敏感值使用占位符或环境配置,不在默认配置里伪造可用凭证。
所有业务逻辑、状态流转、权限判断、支付链路、异步流程都保持高密度逻辑注释,注释解释为什么这样处理和失败分支,不复述语法。

View File

@ -6,6 +6,7 @@ var default_api = 'https://api.global-interaction.com/';
var API_ENV_KEY = 'hyapp_h5_env'; var API_ENV_KEY = 'hyapp_h5_env';
var ACCESS_TOKEN_KEY = 'hyapp_access_token'; var ACCESS_TOKEN_KEY = 'hyapp_access_token';
var APP_CODE_KEY = 'hyapp_app_code'; var APP_CODE_KEY = 'hyapp_app_code';
var RENEWED_ACCESS_TOKEN_HEADER = 'X-Hyapp-Access-Token';
var memoryEnv = ''; var memoryEnv = '';
var memoryAccessToken = ''; var memoryAccessToken = '';
var memoryAppCode = ''; var memoryAppCode = '';
@ -14,6 +15,36 @@ var default_api = 'https://api.global-interaction.com/';
return new URLSearchParams(window.location.search).get(name); return new URLSearchParams(window.location.search).get(name);
} }
function normalizeToken(value) {
var token = String(value || '').trim();
if (!token) return '';
if (/^Bearer\s+/i.test(token)) token = token.replace(/^Bearer\s+/i, '');
var match = token.match(
/[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/
);
return match ? match[0] : token;
}
function decodeBase64URLJSON(value) {
try {
var normalized = String(value || '').replace(/-/g, '+').replace(/_/g, '/');
while (normalized.length % 4) normalized += '=';
return JSON.parse(window.atob(normalized));
} catch (_) {
return null;
}
}
function isExpiredToken(token) {
var parts = String(token || '').split('.');
if (parts.length < 2) return false;
var payload = decodeBase64URLJSON(parts[1]);
if (!payload || !payload.exp) return false;
// JWT exp 是秒级时间戳;本地 H5 不能只凭 query 有 token 就进入 token 模式,过期 token 会被 gateway 当成未登录。
// 这里预留 5 秒偏移,避免页面发请求时刚好跨过过期点,导致 H5 隐藏账号输入但服务端收到空 display_user_id。
return Number(payload.exp) * 1000 <= Date.now() + 5000;
}
function normalizeBaseURL(value) { function normalizeBaseURL(value) {
return String(value || '').replace(/\/+$/, '') + '/'; return String(value || '').replace(/\/+$/, '') + '/';
} }
@ -85,19 +116,36 @@ var default_api = 'https://api.global-interaction.com/';
} }
function getAccessToken() { function getAccessToken() {
if (memoryAccessToken && !isExpiredToken(memoryAccessToken))
return memoryAccessToken;
if (memoryAccessToken && isExpiredToken(memoryAccessToken)) {
memoryAccessToken = '';
if (shouldPersist()) window.localStorage.removeItem(ACCESS_TOKEN_KEY);
}
var queryToken = var queryToken =
readQuery('token') || normalizeToken(readQuery('token')) ||
readQuery('access_token') || normalizeToken(readQuery('access_token')) ||
readQuery('accessToken'); normalizeToken(readQuery('accessToken'));
if (queryToken) { if (queryToken) {
if (isExpiredToken(queryToken)) {
if (shouldPersist())
window.localStorage.removeItem(ACCESS_TOKEN_KEY);
return '';
}
memoryAccessToken = queryToken; memoryAccessToken = queryToken;
if (shouldPersist()) if (shouldPersist())
window.localStorage.setItem(ACCESS_TOKEN_KEY, queryToken); window.localStorage.setItem(ACCESS_TOKEN_KEY, queryToken);
return queryToken; return queryToken;
} }
if (memoryAccessToken) return memoryAccessToken;
if (!shouldPersist()) return ''; if (!shouldPersist()) return '';
return window.localStorage.getItem(ACCESS_TOKEN_KEY) || ''; var storedToken = normalizeToken(
window.localStorage.getItem(ACCESS_TOKEN_KEY) || ''
);
if (storedToken && isExpiredToken(storedToken)) {
window.localStorage.removeItem(ACCESS_TOKEN_KEY);
return '';
}
return storedToken;
} }
function getAppCode() { function getAppCode() {
@ -114,6 +162,8 @@ var default_api = 'https://api.global-interaction.com/';
} }
function setAccessToken(token) { function setAccessToken(token) {
token = normalizeToken(token);
if (token && isExpiredToken(token)) token = '';
memoryAccessToken = token || ''; memoryAccessToken = token || '';
if (token) { if (token) {
if (shouldPersist()) if (shouldPersist())
@ -184,6 +234,14 @@ var default_api = 'https://api.global-interaction.com/';
}); });
} }
function persistRenewedAccessToken(response) {
var renewedToken =
response && response.headers
? response.headers.get(RENEWED_ACCESS_TOKEN_HEADER)
: '';
if (renewedToken) setAccessToken(renewedToken);
}
function request(path, options) { function request(path, options) {
var opts = options || {}; var opts = options || {};
var headers = Object.assign({}, opts.headers || {}); var headers = Object.assign({}, opts.headers || {});
@ -210,7 +268,10 @@ var default_api = 'https://api.global-interaction.com/';
cache: shouldPersist() ? 'default' : 'no-store', cache: shouldPersist() ? 'default' : 'no-store',
credentials: opts.credentials || 'omit', credentials: opts.credentials || 'omit',
}) })
.then(parseResponse); .then(function (response) {
persistRenewedAccessToken(response);
return parseResponse(response);
});
} }
var vipAPI = { var vipAPI = {
@ -235,6 +296,9 @@ var default_api = 'https://api.global-interaction.com/';
overview: function () { overview: function () {
return request('/api/v1/users/me/overview', { method: 'GET' }); return request('/api/v1/users/me/overview', { method: 'GET' });
}, },
appearance: function () {
return request('/api/v1/users/me/appearance', { method: 'GET' });
},
hostIdentity: function () { hostIdentity: function () {
return request('/api/v1/users/me/host-identity', { return request('/api/v1/users/me/host-identity', {
method: 'GET', method: 'GET',
@ -280,6 +344,50 @@ var default_api = 'https://api.global-interaction.com/';
}, },
}; };
// H5 充值 API 只做路径和参数名封装token、app_code、env、错误 envelope 仍由 request 统一处理。
// 金额、金币数、普通/币商钱包、MiFaPay 参数和 USDT 链上校验都以后端订单快照为准。
var rechargeAPI = {
context: function (displayUserID) {
return request('/api/v1/recharge/h5/context', {
method: 'GET',
query: { display_user_id: displayUserID || '' },
});
},
options: function (displayUserID) {
return request('/api/v1/recharge/h5/options', {
method: 'GET',
query: { display_user_id: displayUserID || '' },
});
},
createOrder: function (payload) {
return request('/api/v1/recharge/h5/orders', {
method: 'POST',
body: payload || {},
});
},
submitTx: function (orderID, payload) {
return request(
'/api/v1/recharge/h5/orders/' +
encodeURIComponent(orderID || '') +
'/tx',
{
method: 'POST',
body: payload || {},
}
);
},
getOrder: function (orderID, displayUserID) {
return request(
'/api/v1/recharge/h5/orders/' +
encodeURIComponent(orderID || ''),
{
method: 'GET',
query: { display_user_id: displayUserID || '' },
}
);
},
};
var salaryWalletAPI = { var salaryWalletAPI = {
overview: function (identity) { overview: function (identity) {
return request('/api/v1/salary-wallet/overview', { return request('/api/v1/salary-wallet/overview', {
@ -548,6 +656,48 @@ var default_api = 'https://api.global-interaction.com/';
}, },
}; };
var diceGameAPI = {
config: function (gameID) {
return request('/api/v1/games/dice/config', {
method: 'GET',
query: { game_id: gameID || 'dice' },
});
},
match: function (payload) {
return request('/api/v1/games/dice/match', {
method: 'POST',
body: payload || {},
});
},
getMatch: function (matchID) {
return request(
'/api/v1/games/dice/matches/' +
encodeURIComponent(matchID || ''),
{ method: 'GET' }
);
},
roll: function (matchID) {
return request(
'/api/v1/games/dice/matches/' +
encodeURIComponent(matchID || '') +
'/roll',
{ method: 'POST', body: {} }
);
},
cancel: function (matchID) {
return request(
'/api/v1/games/dice/matches/' +
encodeURIComponent(matchID || '') +
'/cancel',
{ method: 'POST', body: {} }
);
},
};
var gameAPI = {
dice: diceGameAPI,
};
window.HyAppAPI = { window.HyAppAPI = {
baseURL: resolveBaseURL, baseURL: resolveBaseURL,
buildURL: buildURL, buildURL: buildURL,
@ -574,6 +724,7 @@ var default_api = 'https://api.global-interaction.com/';
vip: vipAPI, vip: vipAPI,
user: userAPI, user: userAPI,
wallet: walletAPI, wallet: walletAPI,
recharge: rechargeAPI,
salaryWallet: salaryWalletAPI, salaryWallet: salaryWalletAPI,
host: hostAPI, host: hostAPI,
hostCenter: hostCenterAPI, hostCenter: hostCenterAPI,
@ -584,6 +735,7 @@ var default_api = 'https://api.global-interaction.com/';
superadminCenter: superadminCenterAPI, superadminCenter: superadminCenterAPI,
level: levelAPI, level: levelAPI,
weeklyStar: weeklyStarAPI, weeklyStar: weeklyStarAPI,
game: gameAPI,
}; };
clearLocalDevCache(); clearLocalDevCache();
})(); })();

View File

@ -10,6 +10,28 @@
var messages = {}; var messages = {};
var currentLang = ''; var currentLang = '';
function scopedSupported() {
var scoped =
window.HyAppI18nSupported || window.HyAppSupportedLanguages || null;
if (!Array.isArray(scoped)) return SUPPORTED;
var normalized = [];
scoped.forEach(function (value) {
var lang = String(value || '')
.trim()
.toLowerCase()
.replace('_', '-');
if (lang.indexOf('ar') === 0) lang = 'ar';
else if (lang.indexOf('tr') === 0) lang = 'tr';
else if (lang.indexOf('es') === 0) lang = 'es';
else if (lang.indexOf('zh') === 0) lang = 'zh';
else if (lang.indexOf('id') === 0) lang = 'id';
if (SUPPORTED.indexOf(lang) >= 0 && normalized.indexOf(lang) < 0) {
normalized.push(lang);
}
});
return normalized.length ? normalized : SUPPORTED;
}
function storageGet(key) { function storageGet(key) {
try { try {
return window.localStorage ? window.localStorage.getItem(key) : ''; return window.localStorage ? window.localStorage.getItem(key) : '';
@ -28,8 +50,34 @@
} }
} }
function currentParams() {
var parsed =
window.HyAppParams &&
window.HyAppParams.current &&
window.HyAppParams.current.raw;
if (parsed && parsed.get) return parsed;
var params = new URLSearchParams(window.location.search);
var hash = window.location.hash || '';
if (hash.indexOf('?') >= 0) {
new URLSearchParams(hash.split('?').slice(1).join('?')).forEach(
function (value, key) {
if (!params.has(key)) params.set(key, value);
}
);
}
return params;
}
function queryLang() { function queryLang() {
return new URLSearchParams(window.location.search).get('lang') || ''; var params = currentParams();
return (
params.get('lang') ||
params.get('language') ||
params.get('locale') ||
params.get('app_lang') ||
params.get('appLanguage') ||
''
);
} }
function normalizeLang(value) { function normalizeLang(value) {
@ -38,12 +86,12 @@
.toLowerCase() .toLowerCase()
.replace('_', '-'); .replace('_', '-');
if (!lang) return ''; if (!lang) return '';
if (lang.indexOf('ar') === 0) return 'ar'; if (lang.indexOf('ar') === 0) lang = 'ar';
if (lang.indexOf('tr') === 0) return 'tr'; else if (lang.indexOf('tr') === 0) lang = 'tr';
if (lang.indexOf('es') === 0) return 'es'; else if (lang.indexOf('es') === 0) lang = 'es';
if (lang.indexOf('zh') === 0) return 'zh'; else if (lang.indexOf('zh') === 0) lang = 'zh';
if (lang.indexOf('id') === 0) return 'id'; else if (lang.indexOf('id') === 0) lang = 'id';
return SUPPORTED.indexOf(lang) >= 0 ? lang : ''; return scopedSupported().indexOf(lang) >= 0 ? lang : '';
} }
function resolveLang() { function resolveLang() {
@ -182,7 +230,7 @@
lang: function () { lang: function () {
return currentLang || DEFAULT_LANG; return currentLang || DEFAULT_LANG;
}, },
supported: SUPPORTED.slice(), supported: scopedSupported(),
}; };
function init() { function init() {

View File

@ -8,6 +8,17 @@
"diceMatch.unknownOpponent": "?", "diceMatch.unknownOpponent": "?",
"diceMatch.win": "فوز", "diceMatch.win": "فوز",
"diceMatch.coins": "عملات", "diceMatch.coins": "عملات",
"diceMatch.changeLanguage": "تغيير اللغة",
"diceMatch.loading": "جار التحميل",
"diceMatch.roll": "ارمِ",
"diceMatch.rolling": "جار الرمي...",
"diceMatch.ready": "جاهز",
"diceMatch.draw": "تعادل",
"diceMatch.compare": "مقارنة",
"diceMatch.apiUnavailable": "واجهة لعبة النرد غير متاحة",
"diceMatch.selectStake": "اختر قيمة الرهان",
"diceMatch.requestFailed": "فشل الطلب",
"diceMatch.configFeeNote": "الرسوم {fee}% · المجمع {pool}%",
"roomReward.pageTitle": "فعالية مكافآت الغرفة", "roomReward.pageTitle": "فعالية مكافآت الغرفة",
"roomReward.pageLabel": "فعالية مكافآت الغرفة", "roomReward.pageLabel": "فعالية مكافآت الغرفة",
"roomReward.currentTarget": "هدف المكافأة الحالي", "roomReward.currentTarget": "هدف المكافأة الحالي",
@ -397,5 +408,55 @@
"cp.rewardMarquis7": "ماركيز*7 أيام", "cp.rewardMarquis7": "ماركيز*7 أيام",
"cp.rewardCoins10000": "عملات*10000", "cp.rewardCoins10000": "عملات*10000",
"cp.rewardCount7": "عدّاد*7 أيام", "cp.rewardCount7": "عدّاد*7 أيام",
"cp.copyright": "حقوق هذا النشاط تعود إلى Shila" "cp.copyright": "حقوق هذا النشاط تعود إلى Shila",
"recharge.pageTitle": "الشحن",
"recharge.pageSubtitle": "اختر باقة عملات وادفع عبر USDT-TRC20 أو طريقة دفع محلية مدعومة.",
"recharge.changeLanguage": "تغيير اللغة",
"recharge.accountTitle": "الحساب",
"recharge.accountHint": "أكد الحساب قبل اختيار الباقة.",
"recharge.userIdPlaceholder": "أدخل معرف المستخدم",
"recharge.searchAccount": "تأكيد",
"recharge.loading": "جار التحميل...",
"recharge.accountConfirmed": "تم تأكيد الحساب",
"recharge.ordinaryUser": "مستخدم",
"recharge.coinSeller": "بائع عملات",
"recharge.productTitle": "الباقة",
"recharge.productEmpty": "لا توجد باقة متاحة لهذا الحساب.",
"recharge.paymentTitle": "الدفع",
"recharge.backToPackages": "العودة إلى الباقات",
"recharge.selectedPackage": "الباقة المحددة",
"recharge.selectProductFirst": "اختر باقة أولا.",
"recharge.noPaymentMethods": "لا توجد طريقة دفع متاحة.",
"recharge.usdtTrc20": "USDT-TRC20",
"recharge.usdtNetwork": "شبكة TRON",
"recharge.thirdPartyPayment": "دفع محلي",
"recharge.createOrder": "إنشاء الطلب",
"recharge.creating": "جار الإنشاء...",
"recharge.orderTitle": "الطلب",
"recharge.paymentOperation": "عملية الدفع",
"recharge.close": "إغلاق",
"recharge.orderId": "رقم الطلب",
"recharge.orderStatus": "الحالة",
"recharge.payRedirecting": "جار فتح صفحة الدفع...",
"recharge.payOpenHint": "افتح صفحة الدفع وأكمل الدفع.",
"recharge.openPayment": "فتح الدفع",
"recharge.usdtAddress": "عنوان USDT-TRC20",
"recharge.usdtHint": "حوّل USDT ثم أرسل TXID.",
"recharge.copy": "نسخ",
"recharge.txHashPlaceholder": "أدخل TXID بعد الدفع",
"recharge.submitTx": "إرسال TXID",
"recharge.submitted": "جار الإرسال...",
"recharge.txSubmitted": "تم إرسال TXID",
"recharge.paidSuccess": "اكتمل الدفع",
"recharge.orderFailed": "فشل الطلب",
"recharge.accountRequired": "أدخل معرف المستخدم",
"recharge.orderCreateFailed": "فشل إنشاء الطلب",
"recharge.loadFailed": "فشل التحميل",
"recharge.copied": "تم النسخ",
"recharge.copyFailed": "فشل النسخ",
"recharge.coins": "عملات",
"recharge.status.pending": "قيد الانتظار",
"recharge.status.redirected": "بانتظار الدفع",
"recharge.status.credited": "تمت الإضافة",
"recharge.status.failed": "فشل"
} }

View File

@ -8,6 +8,17 @@
"diceMatch.unknownOpponent": "?", "diceMatch.unknownOpponent": "?",
"diceMatch.win": "Win", "diceMatch.win": "Win",
"diceMatch.coins": "Coins", "diceMatch.coins": "Coins",
"diceMatch.changeLanguage": "Change language",
"diceMatch.loading": "Loading",
"diceMatch.roll": "Roll",
"diceMatch.rolling": "Rolling...",
"diceMatch.ready": "Ready",
"diceMatch.draw": "Draw",
"diceMatch.compare": "Compare",
"diceMatch.apiUnavailable": "Dice API unavailable",
"diceMatch.selectStake": "Select a stake",
"diceMatch.requestFailed": "Request failed",
"diceMatch.configFeeNote": "Fee {fee}% · Pool {pool}%",
"roomReward.pageTitle": "Room Reward Event", "roomReward.pageTitle": "Room Reward Event",
"roomReward.pageLabel": "Room Reward Event", "roomReward.pageLabel": "Room Reward Event",
"roomReward.currentTarget": "Current reward target", "roomReward.currentTarget": "Current reward target",
@ -403,5 +414,55 @@
"cp.rewardMarquis7": "Marquis*7days", "cp.rewardMarquis7": "Marquis*7days",
"cp.rewardCoins10000": "Coins*10000", "cp.rewardCoins10000": "Coins*10000",
"cp.rewardCount7": "Count*7days", "cp.rewardCount7": "Count*7days",
"cp.copyright": "The copyright of this activity belongs to Shila" "cp.copyright": "The copyright of this activity belongs to Shila",
"recharge.pageTitle": "Recharge",
"recharge.pageSubtitle": "Choose a coin package and pay with USDT-TRC20 or a supported local payment method.",
"recharge.changeLanguage": "Change language",
"recharge.accountTitle": "Account",
"recharge.accountHint": "Confirm the account before selecting a package.",
"recharge.userIdPlaceholder": "Enter user ID",
"recharge.searchAccount": "Confirm",
"recharge.loading": "Loading...",
"recharge.accountConfirmed": "Account confirmed",
"recharge.ordinaryUser": "User",
"recharge.coinSeller": "Coin seller",
"recharge.productTitle": "Package",
"recharge.productEmpty": "No package is available for this account.",
"recharge.paymentTitle": "Payment",
"recharge.backToPackages": "Back to packages",
"recharge.selectedPackage": "Selected package",
"recharge.selectProductFirst": "Select a package first.",
"recharge.noPaymentMethods": "No payment method is available.",
"recharge.usdtTrc20": "USDT-TRC20",
"recharge.usdtNetwork": "TRON network",
"recharge.thirdPartyPayment": "Local payment",
"recharge.createOrder": "Create order",
"recharge.creating": "Creating...",
"recharge.orderTitle": "Order",
"recharge.paymentOperation": "Payment operation",
"recharge.close": "Close",
"recharge.orderId": "Order ID",
"recharge.orderStatus": "Status",
"recharge.payRedirecting": "Opening payment page...",
"recharge.payOpenHint": "Open the payment page and finish the payment.",
"recharge.openPayment": "Open payment",
"recharge.usdtAddress": "USDT-TRC20 address",
"recharge.usdtHint": "Transfer USDT and submit the TXID.",
"recharge.copy": "Copy",
"recharge.txHashPlaceholder": "Enter TXID after payment",
"recharge.submitTx": "Submit TXID",
"recharge.submitted": "Submitting...",
"recharge.txSubmitted": "TXID submitted",
"recharge.paidSuccess": "Payment completed",
"recharge.orderFailed": "Order failed",
"recharge.accountRequired": "Enter user ID",
"recharge.orderCreateFailed": "Create order failed",
"recharge.loadFailed": "Load failed",
"recharge.copied": "Copied",
"recharge.copyFailed": "Copy failed",
"recharge.coins": "coins",
"recharge.status.pending": "Pending",
"recharge.status.redirected": "Waiting for payment",
"recharge.status.credited": "Credited",
"recharge.status.failed": "Failed"
} }

View File

@ -8,6 +8,17 @@
"diceMatch.unknownOpponent": "?", "diceMatch.unknownOpponent": "?",
"diceMatch.win": "Victoria", "diceMatch.win": "Victoria",
"diceMatch.coins": "Monedas", "diceMatch.coins": "Monedas",
"diceMatch.changeLanguage": "Cambiar idioma",
"diceMatch.loading": "Cargando",
"diceMatch.roll": "Tirar",
"diceMatch.rolling": "Tirando...",
"diceMatch.ready": "Listo",
"diceMatch.draw": "Empate",
"diceMatch.compare": "Comparar",
"diceMatch.apiUnavailable": "API del juego de dados no disponible",
"diceMatch.selectStake": "Selecciona una apuesta",
"diceMatch.requestFailed": "Error de solicitud",
"diceMatch.configFeeNote": "Comisión {fee}% · Bolsa {pool}%",
"roomReward.pageTitle": "Evento de recompensas de sala", "roomReward.pageTitle": "Evento de recompensas de sala",
"roomReward.pageLabel": "Evento de recompensas de sala", "roomReward.pageLabel": "Evento de recompensas de sala",
"roomReward.currentTarget": "Objetivo de recompensa actual", "roomReward.currentTarget": "Objetivo de recompensa actual",
@ -397,5 +408,55 @@
"cp.rewardMarquis7": "Marqués*7 días", "cp.rewardMarquis7": "Marqués*7 días",
"cp.rewardCoins10000": "Monedas*10000", "cp.rewardCoins10000": "Monedas*10000",
"cp.rewardCount7": "Contador*7 días", "cp.rewardCount7": "Contador*7 días",
"cp.copyright": "Los derechos de esta actividad pertenecen a Shila" "cp.copyright": "Los derechos de esta actividad pertenecen a Shila",
"recharge.pageTitle": "Recarga",
"recharge.pageSubtitle": "Elige un paquete de monedas y paga con USDT-TRC20 o un método local compatible.",
"recharge.changeLanguage": "Cambiar idioma",
"recharge.accountTitle": "Cuenta",
"recharge.accountHint": "Confirma la cuenta antes de elegir un paquete.",
"recharge.userIdPlaceholder": "Ingresa el ID de usuario",
"recharge.searchAccount": "Confirmar",
"recharge.loading": "Cargando...",
"recharge.accountConfirmed": "Cuenta confirmada",
"recharge.ordinaryUser": "Usuario",
"recharge.coinSeller": "Vendedor de monedas",
"recharge.productTitle": "Paquete",
"recharge.productEmpty": "No hay paquetes disponibles para esta cuenta.",
"recharge.paymentTitle": "Pago",
"recharge.backToPackages": "Volver a paquetes",
"recharge.selectedPackage": "Paquete seleccionado",
"recharge.selectProductFirst": "Elige un paquete primero.",
"recharge.noPaymentMethods": "No hay métodos de pago disponibles.",
"recharge.usdtTrc20": "USDT-TRC20",
"recharge.usdtNetwork": "Red TRON",
"recharge.thirdPartyPayment": "Pago local",
"recharge.createOrder": "Crear pedido",
"recharge.creating": "Creando...",
"recharge.orderTitle": "Pedido",
"recharge.paymentOperation": "Operación de pago",
"recharge.close": "Cerrar",
"recharge.orderId": "ID de pedido",
"recharge.orderStatus": "Estado",
"recharge.payRedirecting": "Abriendo página de pago...",
"recharge.payOpenHint": "Abre la página de pago y completa el pago.",
"recharge.openPayment": "Abrir pago",
"recharge.usdtAddress": "Dirección USDT-TRC20",
"recharge.usdtHint": "Transfiere USDT y envía el TXID.",
"recharge.copy": "Copiar",
"recharge.txHashPlaceholder": "Ingresa el TXID después del pago",
"recharge.submitTx": "Enviar TXID",
"recharge.submitted": "Enviando...",
"recharge.txSubmitted": "TXID enviado",
"recharge.paidSuccess": "Pago completado",
"recharge.orderFailed": "Pedido fallido",
"recharge.accountRequired": "Ingresa el ID de usuario",
"recharge.orderCreateFailed": "No se pudo crear el pedido",
"recharge.loadFailed": "Error al cargar",
"recharge.copied": "Copiado",
"recharge.copyFailed": "Error al copiar",
"recharge.coins": "monedas",
"recharge.status.pending": "Pendiente",
"recharge.status.redirected": "Esperando pago",
"recharge.status.credited": "Acreditado",
"recharge.status.failed": "Fallido"
} }

View File

@ -397,5 +397,55 @@
"cp.rewardMarquis7": "Marquis*7 hari", "cp.rewardMarquis7": "Marquis*7 hari",
"cp.rewardCoins10000": "Koin*10000", "cp.rewardCoins10000": "Koin*10000",
"cp.rewardCount7": "Hitungan*7 hari", "cp.rewardCount7": "Hitungan*7 hari",
"cp.copyright": "Hak cipta aktivitas ini milik Shila" "cp.copyright": "Hak cipta aktivitas ini milik Shila",
"recharge.pageTitle": "Top up",
"recharge.pageSubtitle": "Pilih paket coin dan bayar dengan USDT-TRC20 atau metode lokal yang didukung.",
"recharge.changeLanguage": "Ganti bahasa",
"recharge.accountTitle": "Akun",
"recharge.accountHint": "Konfirmasi akun sebelum memilih paket.",
"recharge.userIdPlaceholder": "Masukkan ID pengguna",
"recharge.searchAccount": "Konfirmasi",
"recharge.loading": "Memuat...",
"recharge.accountConfirmed": "Akun dikonfirmasi",
"recharge.ordinaryUser": "Pengguna",
"recharge.coinSeller": "Coin seller",
"recharge.productTitle": "Paket",
"recharge.productEmpty": "Tidak ada paket untuk akun ini.",
"recharge.paymentTitle": "Pembayaran",
"recharge.backToPackages": "Kembali ke paket",
"recharge.selectedPackage": "Paket dipilih",
"recharge.selectProductFirst": "Pilih paket terlebih dahulu.",
"recharge.noPaymentMethods": "Tidak ada metode pembayaran.",
"recharge.usdtTrc20": "USDT-TRC20",
"recharge.usdtNetwork": "Jaringan TRON",
"recharge.thirdPartyPayment": "Pembayaran lokal",
"recharge.createOrder": "Buat pesanan",
"recharge.creating": "Membuat...",
"recharge.orderTitle": "Pesanan",
"recharge.paymentOperation": "Operasi pembayaran",
"recharge.close": "Tutup",
"recharge.orderId": "ID pesanan",
"recharge.orderStatus": "Status",
"recharge.payRedirecting": "Membuka halaman pembayaran...",
"recharge.payOpenHint": "Buka halaman pembayaran dan selesaikan pembayaran.",
"recharge.openPayment": "Buka pembayaran",
"recharge.usdtAddress": "Alamat USDT-TRC20",
"recharge.usdtHint": "Transfer USDT lalu kirim TXID.",
"recharge.copy": "Salin",
"recharge.txHashPlaceholder": "Masukkan TXID setelah bayar",
"recharge.submitTx": "Kirim TXID",
"recharge.submitted": "Mengirim...",
"recharge.txSubmitted": "TXID terkirim",
"recharge.paidSuccess": "Pembayaran selesai",
"recharge.orderFailed": "Pesanan gagal",
"recharge.accountRequired": "Masukkan ID pengguna",
"recharge.orderCreateFailed": "Gagal membuat pesanan",
"recharge.loadFailed": "Gagal memuat",
"recharge.copied": "Disalin",
"recharge.copyFailed": "Gagal menyalin",
"recharge.coins": "coin",
"recharge.status.pending": "Menunggu",
"recharge.status.redirected": "Menunggu pembayaran",
"recharge.status.credited": "Masuk",
"recharge.status.failed": "Gagal"
} }

View File

@ -397,5 +397,55 @@
"cp.rewardMarquis7": "Marki*7 gün", "cp.rewardMarquis7": "Marki*7 gün",
"cp.rewardCoins10000": "Coin*10000", "cp.rewardCoins10000": "Coin*10000",
"cp.rewardCount7": "Sayaç*7 gün", "cp.rewardCount7": "Sayaç*7 gün",
"cp.copyright": "Bu etkinliğin telif hakkı Shila'ya aittir" "cp.copyright": "Bu etkinliğin telif hakkı Shila'ya aittir",
"recharge.pageTitle": "Yükleme",
"recharge.pageSubtitle": "Coin paketi seçin ve USDT-TRC20 ya da desteklenen yerel ödeme yöntemiyle ödeyin.",
"recharge.changeLanguage": "Dili değiştir",
"recharge.accountTitle": "Hesap",
"recharge.accountHint": "Paket seçmeden önce hesabı onaylayın.",
"recharge.userIdPlaceholder": "Kullanıcı ID girin",
"recharge.searchAccount": "Onayla",
"recharge.loading": "Yükleniyor...",
"recharge.accountConfirmed": "Hesap onaylandı",
"recharge.ordinaryUser": "Kullanıcı",
"recharge.coinSeller": "Coin seller",
"recharge.productTitle": "Paket",
"recharge.productEmpty": "Bu hesap için paket yok.",
"recharge.paymentTitle": "Ödeme",
"recharge.backToPackages": "Paketlere dön",
"recharge.selectedPackage": "Seçilen paket",
"recharge.selectProductFirst": "Önce paket seçin.",
"recharge.noPaymentMethods": "Kullanılabilir ödeme yöntemi yok.",
"recharge.usdtTrc20": "USDT-TRC20",
"recharge.usdtNetwork": "TRON ağı",
"recharge.thirdPartyPayment": "Yerel ödeme",
"recharge.createOrder": "Sipariş oluştur",
"recharge.creating": "Oluşturuluyor...",
"recharge.orderTitle": "Sipariş",
"recharge.paymentOperation": "Ödeme işlemi",
"recharge.close": "Kapat",
"recharge.orderId": "Sipariş ID",
"recharge.orderStatus": "Durum",
"recharge.payRedirecting": "Ödeme sayfasıılıyor...",
"recharge.payOpenHint": "Ödeme sayfasınıın ve ödemeyi tamamlayın.",
"recharge.openPayment": "Ödemeyi aç",
"recharge.usdtAddress": "USDT-TRC20 adresi",
"recharge.usdtHint": "USDT gönderin ve TXID girin.",
"recharge.copy": "Kopyala",
"recharge.txHashPlaceholder": "Ödemeden sonra TXID girin",
"recharge.submitTx": "TXID gönder",
"recharge.submitted": "Gönderiliyor...",
"recharge.txSubmitted": "TXID gönderildi",
"recharge.paidSuccess": "Ödeme tamamlandı",
"recharge.orderFailed": "Sipariş başarısız",
"recharge.accountRequired": "Kullanıcı ID girin",
"recharge.orderCreateFailed": "Sipariş oluşturulamadı",
"recharge.loadFailed": "Yükleme başarısız",
"recharge.copied": "Kopyalandı",
"recharge.copyFailed": "Kopyalama başarısız",
"recharge.coins": "coin",
"recharge.status.pending": "Bekliyor",
"recharge.status.redirected": "Ödeme bekleniyor",
"recharge.status.credited": "Yüklendi",
"recharge.status.failed": "Başarısız"
} }

View File

@ -403,5 +403,55 @@
"cp.rewardMarquis7": "侯爵*7天", "cp.rewardMarquis7": "侯爵*7天",
"cp.rewardCoins10000": "金币*10000", "cp.rewardCoins10000": "金币*10000",
"cp.rewardCount7": "计数*7天", "cp.rewardCount7": "计数*7天",
"cp.copyright": "本活动版权归 Shila 所有" "cp.copyright": "本活动版权归 Shila 所有",
"recharge.pageTitle": "充值",
"recharge.pageSubtitle": "选择金币档位,使用 USDT-TRC20 或支持的本地支付方式付款。",
"recharge.changeLanguage": "切换语言",
"recharge.accountTitle": "账号",
"recharge.accountHint": "确认账号后再选择充值档位。",
"recharge.userIdPlaceholder": "输入用户 ID",
"recharge.searchAccount": "确认",
"recharge.loading": "加载中...",
"recharge.accountConfirmed": "账号已确认",
"recharge.ordinaryUser": "普通用户",
"recharge.coinSeller": "币商",
"recharge.productTitle": "充值档位",
"recharge.productEmpty": "当前账号暂无可用档位。",
"recharge.paymentTitle": "支付方式",
"recharge.backToPackages": "返回充值档位",
"recharge.selectedPackage": "已选档位",
"recharge.selectProductFirst": "请先选择充值档位。",
"recharge.noPaymentMethods": "当前暂无可用支付方式。",
"recharge.usdtTrc20": "USDT-TRC20",
"recharge.usdtNetwork": "TRON 网络",
"recharge.thirdPartyPayment": "本地支付",
"recharge.createOrder": "创建订单",
"recharge.creating": "创建中...",
"recharge.orderTitle": "订单",
"recharge.paymentOperation": "支付操作",
"recharge.close": "关闭",
"recharge.orderId": "订单号",
"recharge.orderStatus": "状态",
"recharge.payRedirecting": "正在打开支付页面...",
"recharge.payOpenHint": "打开支付页面并完成付款。",
"recharge.openPayment": "打开支付",
"recharge.usdtAddress": "USDT-TRC20 收款地址",
"recharge.usdtHint": "转账 USDT 后提交 TXID。",
"recharge.copy": "复制",
"recharge.txHashPlaceholder": "付款后输入 TXID",
"recharge.submitTx": "提交 TXID",
"recharge.submitted": "提交中...",
"recharge.txSubmitted": "TXID 已提交",
"recharge.paidSuccess": "支付已完成",
"recharge.orderFailed": "订单失败",
"recharge.accountRequired": "请输入用户 ID",
"recharge.orderCreateFailed": "创建订单失败",
"recharge.loadFailed": "加载失败",
"recharge.copied": "已复制",
"recharge.copyFailed": "复制失败",
"recharge.coins": "金币",
"recharge.status.pending": "待支付",
"recharge.status.redirected": "等待支付",
"recharge.status.credited": "已到账",
"recharge.status.failed": "失败"
} }

View File

@ -28,10 +28,31 @@
function normalizeToken(value) { function normalizeToken(value) {
var token = String(value || '').trim(); var token = String(value || '').trim();
if (!token) return ''; if (!token) return '';
if (/^Bearer\s+/i.test(token)) token = token.replace(/^Bearer\s+/i, '');
var match = token.match( var match = token.match(
/[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/ /[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/
); );
return match ? match[0] : token; token = match ? match[0] : token;
return isExpiredToken(token) ? '' : token;
}
function decodeBase64URLJSON(value) {
try {
var normalized = String(value || '').replace(/-/g, '+').replace(/_/g, '/');
while (normalized.length % 4) normalized += '=';
return JSON.parse(window.atob(normalized));
} catch (_) {
return null;
}
}
function isExpiredToken(token) {
var parts = String(token || '').split('.');
if (parts.length < 2) return false;
var payload = decodeBase64URLJSON(parts[1]);
if (!payload || !payload.exp) return false;
// params 是页面入口模式的判定来源;过期 JWT 必须当作无 token否则充值页会隐藏用户 ID 输入并触发 INVALID_ARGUMENT。
return Number(payload.exp) * 1000 <= Date.now() + 5000;
} }
function parse() { function parse() {

1
common/vendor/svga.min.js vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,35 @@
<svg width="132" height="132" viewBox="0 0 132 132" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient id="matchingCenterGlow" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(66 66) rotate(90) scale(66)">
<stop offset="0" stop-color="#0AF4FF" stop-opacity="0.44"/>
<stop offset="0.48" stop-color="#0087FF" stop-opacity="0.28"/>
<stop offset="1" stop-color="#001C5B" stop-opacity="0"/>
</radialGradient>
<linearGradient id="matchingCenterStroke" x1="25" y1="15" x2="104" y2="116" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#7FFFFF"/>
<stop offset="0.47" stop-color="#00A8FF"/>
<stop offset="1" stop-color="#B7FFFF"/>
</linearGradient>
<filter id="matchingCenterBlur" x="-20" y="-20" width="172" height="172" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feGaussianBlur stdDeviation="2.2"/>
</filter>
<filter id="matchingCenterShadow" x="-16" y="-16" width="164" height="164" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feDropShadow dx="0" dy="0" stdDeviation="5" flood-color="#00B7FF" flood-opacity="0.82"/>
</filter>
</defs>
<circle cx="66" cy="66" r="60" fill="url(#matchingCenterGlow)"/>
<g filter="url(#matchingCenterBlur)" opacity="0.9">
<path d="M18 64C18.8 38.4 39.5 18.2 65.2 17.6" stroke="#6FFFFF" stroke-width="2.2" stroke-linecap="round" stroke-dasharray="30 15"/>
<path d="M112 69C110.8 94.8 89.7 114.4 64 114.3" stroke="#4CEBFF" stroke-width="2.2" stroke-linecap="round" stroke-dasharray="28 18"/>
<path d="M35.5 25.2C52.2 13 75 11.8 93.2 22.6" stroke="#2EBBFF" stroke-width="1.8" stroke-linecap="round" stroke-dasharray="18 13"/>
<path d="M28.2 97.5C17.2 82.4 14.6 62.5 21.5 45.2" stroke="#0087FF" stroke-width="1.8" stroke-linecap="round" stroke-dasharray="16 12"/>
</g>
<g filter="url(#matchingCenterShadow)">
<circle cx="66" cy="66" r="49" stroke="url(#matchingCenterStroke)" stroke-width="2.8" stroke-linecap="round" stroke-dasharray="86 19 22 12"/>
<circle cx="66" cy="66" r="41" stroke="#107BFF" stroke-width="2" stroke-linecap="round" stroke-dasharray="12 8"/>
<circle cx="66" cy="66" r="34" stroke="#7DFBFF" stroke-width="2.6" stroke-linecap="round" stroke-dasharray="52 15 9 13"/>
<circle cx="66" cy="66" r="27" stroke="#006EFF" stroke-width="1.8" stroke-linecap="round" stroke-dasharray="8 7"/>
<circle cx="66" cy="66" r="19" fill="#0098FF" fill-opacity="0.28" stroke="#2AECFF" stroke-width="2"/>
<path d="M66 8.5V14.5M66 117.5V123.5M8.5 66H14.5M117.5 66H123.5" stroke="#8CFFFF" stroke-width="1.8" stroke-linecap="round" opacity="0.82"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,37 @@
<svg width="132" height="132" viewBox="0 0 132 132" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient id="matchingCenterGlow" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(66 66) rotate(90) scale(66)">
<stop offset="0" stop-color="#0AF4FF" stop-opacity="0.44"/>
<stop offset="0.48" stop-color="#0087FF" stop-opacity="0.28"/>
<stop offset="1" stop-color="#001C5B" stop-opacity="0"/>
</radialGradient>
<linearGradient id="matchingCenterStroke" x1="25" y1="15" x2="104" y2="116" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#7FFFFF"/>
<stop offset="0.47" stop-color="#00A8FF"/>
<stop offset="1" stop-color="#B7FFFF"/>
</linearGradient>
<filter id="matchingCenterBlur" x="-20" y="-20" width="172" height="172" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feGaussianBlur stdDeviation="2.2"/>
</filter>
<filter id="matchingCenterShadow" x="-16" y="-16" width="164" height="164" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feDropShadow dx="0" dy="0" stdDeviation="5" flood-color="#00B7FF" flood-opacity="0.82"/>
</filter>
</defs>
<circle cx="66" cy="66" r="60" fill="url(#matchingCenterGlow)"/>
<g filter="url(#matchingCenterBlur)" opacity="0.9">
<path d="M18 64C18.8 38.4 39.5 18.2 65.2 17.6" stroke="#6FFFFF" stroke-width="2.2" stroke-linecap="round" stroke-dasharray="30 15"/>
<path d="M112 69C110.8 94.8 89.7 114.4 64 114.3" stroke="#4CEBFF" stroke-width="2.2" stroke-linecap="round" stroke-dasharray="28 18"/>
<path d="M35.5 25.2C52.2 13 75 11.8 93.2 22.6" stroke="#2EBBFF" stroke-width="1.8" stroke-linecap="round" stroke-dasharray="18 13"/>
<path d="M28.2 97.5C17.2 82.4 14.6 62.5 21.5 45.2" stroke="#0087FF" stroke-width="1.8" stroke-linecap="round" stroke-dasharray="16 12"/>
</g>
<g filter="url(#matchingCenterShadow)">
<circle cx="66" cy="66" r="49" stroke="url(#matchingCenterStroke)" stroke-width="2.8" stroke-linecap="round" stroke-dasharray="86 19 22 12"/>
<circle cx="66" cy="66" r="41" stroke="#107BFF" stroke-width="2" stroke-linecap="round" stroke-dasharray="12 8"/>
<circle cx="66" cy="66" r="34" stroke="#7DFBFF" stroke-width="2.6" stroke-linecap="round" stroke-dasharray="52 15 9 13"/>
<circle cx="66" cy="66" r="27" stroke="#006EFF" stroke-width="1.8" stroke-linecap="round" stroke-dasharray="8 7"/>
<circle cx="66" cy="66" r="19" fill="#0098FF" fill-opacity="0.28" stroke="#2AECFF" stroke-width="2"/>
<path d="M74.2 74.1L80.5 80.4" stroke="white" stroke-width="3.1" stroke-linecap="round"/>
<circle cx="64" cy="64" r="8.4" stroke="white" stroke-width="3.1"/>
<path d="M66 8.5V14.5M66 117.5V123.5M8.5 66H14.5M117.5 66H123.5" stroke="#8CFFFF" stroke-width="1.8" stroke-linecap="round" opacity="0.82"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff