chatapp-h5/h5/exchange-gold-coins/exchange-gold-coins.js
2026-04-24 21:13:53 +08:00

443 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const API_BASE_URL = "https://jvapi.haiyihy.com";
const icons = {
back: "../host-center/icons/back.svg",
arrow: "../host-center/icons/arrow.svg",
coin: "./icons/coin.svg"
};
let commonHeaders = {
authorization: "Bearer undefined",
"req-app-intel": "version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google",
"req-client": "Android",
"req-imei": "8fa54d728ab449e04f9292329ed44bda",
"req-lang": "id-ID",
"req-sys-origin": "origin=ATYOU;originChild=ATYOU",
"req-version": "V2",
"req-zone": "Asia/Shanghai"
};
const coinOptions = [
{ id: 1, amount: 10500, price: 1 },
{ id: 2, amount: 52500, price: 5 },
{ id: 3, amount: 105000, price: 10 },
{ id: 4, amount: 525000, price: 50 },
{ id: 5, amount: 1050000, price: 100 },
{ id: 6, amount: 2100000, price: 200 }
];
const i18n = {
en: {
exchange_gold_coins: "Exchange Coins",
information: "Information",
details: "Details",
available_salaries: "Available salaries",
loading: "Loading",
exchange: "Exchange",
tips: "Tips",
confirm_exchange: "Confirm exchange?",
cancel: "Cancel",
confirm: "Confirm",
failed_to_load_balance: "Failed to load balance.",
exchange_not_available: "Exchange is not available.",
request_not_acceptable: "Request not acceptable.",
failed_to_load_user_info: "Failed to load user info.",
please_select_coin_amount: "Please select coin amount.",
insufficient_balance: "Insufficient balance. Balance: {balance}, price: {price}.",
exchange_success: "Exchange success: ${price}.",
exchange_failed: "Exchange failed.",
exchange_error_occurred: "Exchange error occurred."
},
ar: {
exchange_gold_coins: "استبدال العملات",
information: "معلومات",
details: "التفاصيل",
available_salaries: "الرواتب المتاحة",
loading: "جار التحميل",
exchange: "استبدال",
tips: "نصائح",
confirm_exchange: "تأكيد الاستبدال؟",
cancel: "إلغاء",
confirm: "تأكيد",
failed_to_load_balance: "فشل تحميل الرصيد.",
exchange_not_available: "الاستبدال غير متاح.",
request_not_acceptable: "الطلب غير مقبول.",
failed_to_load_user_info: "فشل تحميل معلومات المستخدم.",
please_select_coin_amount: "يرجى اختيار كمية العملات.",
insufficient_balance: "الرصيد غير كاف. الرصيد: {balance}، السعر: {price}.",
exchange_success: "تم الاستبدال بنجاح: ${price}.",
exchange_failed: "فشل الاستبدال.",
exchange_error_occurred: "حدث خطأ في الاستبدال."
},
tr: {
exchange_gold_coins: "Coin Değiştir",
information: "Bilgi",
details: "Detaylar",
available_salaries: "Kullanılabilir maaşlar",
loading: "Yükleniyor",
exchange: "Değiştir",
tips: "İpuçları",
confirm_exchange: "Değişimi onaylıyor musunuz?",
cancel: "İptal",
confirm: "Onayla",
failed_to_load_balance: "Bakiye yüklenemedi.",
exchange_not_available: "Değişim kullanılamıyor.",
request_not_acceptable: "İstek kabul edilemez.",
failed_to_load_user_info: "Kullanıcı bilgisi yüklenemedi.",
please_select_coin_amount: "Lütfen coin miktarı seçin.",
insufficient_balance: "Yetersiz bakiye. Bakiye: {balance}, fiyat: {price}.",
exchange_success: "Değişim başarılı: ${price}.",
exchange_failed: "Değişim başarısız.",
exchange_error_occurred: "Değişim hatası oluştu."
},
bn: {
exchange_gold_coins: "কয়েন এক্সচেঞ্জ",
information: "তথ্য",
details: "বিস্তারিত",
available_salaries: "উপলভ্য বেতন",
loading: "লোড হচ্ছে",
exchange: "এক্সচেঞ্জ",
tips: "টিপস",
confirm_exchange: "এক্সচেঞ্জ নিশ্চিত করবেন?",
cancel: "বাতিল",
confirm: "নিশ্চিত",
failed_to_load_balance: "ব্যালেন্স লোড ব্যর্থ।",
exchange_not_available: "এক্সচেঞ্জ উপলভ্য নয়।",
request_not_acceptable: "রিকোয়েস্ট গ্রহণযোগ্য নয়।",
failed_to_load_user_info: "ইউজার তথ্য লোড ব্যর্থ।",
please_select_coin_amount: "কয়েন পরিমাণ নির্বাচন করুন।",
insufficient_balance: "ব্যালেন্স অপর্যাপ্ত। ব্যালেন্স: {balance}, মূল্য: {price}।",
exchange_success: "এক্সচেঞ্জ সফল: ${price}।",
exchange_failed: "এক্সচেঞ্জ ব্যর্থ।",
exchange_error_occurred: "এক্সচেঞ্জ ত্রুটি ঘটেছে।"
}
};
const state = {
lang: resolveLanguage(),
balance: "0",
selectedId: null,
isBalanceLoading: false,
isUserLoading: false,
role: ""
};
const $ = (selector) => document.querySelector(selector);
const $$ = (selector) => Array.from(document.querySelectorAll(selector));
function translate(key, params = {}) {
let text = (i18n[state.lang] && i18n[state.lang][key]) || i18n.en[key] || key;
Object.entries(params).forEach(([name, value]) => {
text = text.replace(`{${name}}`, value);
});
return text;
}
function resolveLanguage() {
const params = new URLSearchParams(window.location.search);
const lang = params.get("lang") || navigator.language || "en";
if (lang.startsWith("ar")) return "ar";
if (lang.startsWith("tr")) return "tr";
if (lang.startsWith("bn")) return "bn";
return "en";
}
function splitHeaderPairs(value) {
const result = {};
if (!value) return result;
value.split(";").forEach((item) => {
const index = item.indexOf("=");
if (index > -1) result[item.slice(0, index)] = item.slice(index + 1);
});
return result;
}
function parseAccessOrigin(raw) {
const data = JSON.parse(raw);
const appIntel = splitHeaderPairs(data["Req-App-Intel"]);
const sysOrigin = splitHeaderPairs(data["Req-Sys-Origin"]);
return {
reqLang: data["Req-Lang"],
authorization: data.Authorization,
buildVersion: appIntel.build,
appVersion: appIntel.version,
appChannel: appIntel.channel,
reqImei: appIntel["Req-Imei"],
sysOrigin: sysOrigin.origin,
sysOriginChild: sysOrigin.child
};
}
function setHeadersFromApp(headers) {
const next = {};
if (headers.authorization) next.authorization = headers.authorization;
if (headers.reqLang) next["req-lang"] = headers.reqLang;
if (headers.appVersion || headers.buildVersion || headers.appChannel) {
const intel = [];
if (headers.appVersion) intel.push(`version=${headers.appVersion}`);
if (headers.buildVersion) intel.push(`build=${headers.buildVersion}`);
if (headers.appChannel) intel.push(`channel=${headers.appChannel}`);
next["req-app-intel"] = intel.join(";");
}
if (headers.reqImei) next["req-imei"] = headers.reqImei;
if (headers.sysOrigin) {
const origin = [`origin=${headers.sysOrigin}`];
if (headers.sysOriginChild) origin.push(`child=${headers.sysOriginChild}`);
next["req-sys-origin"] = origin.join(";");
}
commonHeaders = { ...commonHeaders, ...next };
}
function connectToApp() {
return new Promise((resolve) => {
window.renderData = resolve;
window.getIosAccessOriginParam = resolve;
if (!(window.app || window.webkit || window.FlutterPageControl)) {
setHeadersFromApp({
authorization: "",
reqLang: state.lang,
appVersion: "1.0.0",
buildVersion: "1",
appChannel: "Web",
reqImei: "H5-STATIC",
sysOrigin: "LIKEI",
sysOriginChild: "LIKEI"
});
resolve(null);
return;
}
let attempt = 0;
const poll = () => {
attempt += 1;
if (window.app && typeof window.app.getAccessOrigin === "function") {
resolve(window.app.getAccessOrigin());
return;
}
if (attempt < 50) {
window.setTimeout(poll, 100);
return;
}
if (window.FlutterApp && typeof window.FlutterApp.postMessage === "function") {
window.FlutterApp.postMessage("requestAccessOrigin");
}
resolve(null);
};
poll();
}).then((raw) => {
if (raw) setHeadersFromApp(parseAccessOrigin(raw));
});
}
async function apiGet(path) {
const response = await fetch(`${API_BASE_URL}${path}`, {
method: "GET",
headers: commonHeaders
});
return parseApiResponse(response);
}
async function apiPost(path, body) {
const response = await fetch(`${API_BASE_URL}${path}`, {
method: "POST",
headers: {
...commonHeaders,
"content-type": "application/json"
},
body: JSON.stringify(body || {})
});
return parseApiResponse(response);
}
async function parseApiResponse(response) {
const data = await response.json().catch(() => ({}));
if (!response.ok || data.status === false) {
const error = new Error(data.errorMsg || data.message || `HTTP Error: ${response.status}`);
error.status = response.status;
error.response = data;
error.errorCode = data.errorCode;
throw error;
}
return data;
}
function navigateTo(route) {
const url = new URL(route, window.location.origin);
const lang = new URLSearchParams(window.location.search).get("lang");
if (lang && !url.searchParams.has("lang")) url.searchParams.set("lang", lang);
window.location.href = `${url.pathname}${url.search}${url.hash}`;
}
function closePage() {
if (window.history.length > 1) {
window.history.back();
return;
}
if (window.app && typeof window.app.closePage === "function") {
window.app.closePage();
return;
}
if (window.FlutterPageControl && typeof window.FlutterPageControl.postMessage === "function") {
window.FlutterPageControl.postMessage("close_page");
}
}
function showToast(message) {
const toast = $("#toast");
toast.textContent = message || "Request failed";
toast.hidden = false;
window.clearTimeout(showToast.timer);
showToast.timer = window.setTimeout(() => {
toast.hidden = true;
}, 2500);
}
function showModal() {
$("#confirmModal").hidden = false;
}
function hideModal() {
$("#confirmModal").hidden = true;
}
async function fetchBalance() {
state.isBalanceLoading = true;
renderBalance();
try {
const result = await apiGet("/wallet/bank/balance");
state.balance = result.status && typeof result.body === "number" ? result.body.toFixed(2) : "0.00";
} catch (error) {
console.error("Failed to fetch bank balance:", error);
state.balance = "0.00";
showToast(translate("failed_to_load_balance"));
} finally {
state.isBalanceLoading = false;
renderBalance();
}
}
async function checkExchangeAvailable() {
state.isUserLoading = true;
try {
const result = await apiGet("/wallet/bank/check/exchange-gold");
if (result.status && result.body) {
state.role = result.body.role || "";
console.debug("用户角色:", state.role);
return;
}
state.role = "";
closePage();
} catch (error) {
if (error.errorCode === 1083) {
showToast(translate("exchange_not_available"));
} else if (error.message && error.message.includes("HTTP Error: 406")) {
showToast(translate("request_not_acceptable"));
} else {
showToast(translate("failed_to_load_user_info"));
}
closePage();
state.role = "";
} finally {
state.isUserLoading = false;
}
}
async function confirmExchange() {
hideModal();
if (!state.selectedId) {
showToast(translate("please_select_coin_amount"));
return;
}
const option = coinOptions.find((item) => item.id === state.selectedId);
const price = option.price;
const balance = parseFloat(state.balance);
if (Number.isNaN(balance) || balance < price) {
showToast(translate("insufficient_balance", { balance: balance.toFixed(2), price }));
return;
}
try {
const result = await apiPost("/wallet/bank/exchange/gold", {
coinId: option.id,
amount: option.price,
price: option.price
});
if (result.status === true || result.errorCode === 0) {
showToast(translate("exchange_success", { price: option.price }));
await fetchBalance();
state.selectedId = null;
renderCoins();
} else {
showToast(result.message || translate("exchange_failed"));
}
} catch (error) {
console.error("Exchange error:", error);
showToast(translate("exchange_error_occurred"));
}
}
function renderI18n() {
document.documentElement.lang = state.lang;
document.documentElement.dir = state.lang === "ar" ? "rtl" : "ltr";
$(".lang-button").textContent = state.lang.toUpperCase();
$$("[data-i18n]").forEach((node) => {
node.textContent = translate(node.dataset.i18n);
});
}
function renderIcons() {
$("#backIcon").src = icons.back;
$("#detailsArrowIcon").src = icons.arrow;
}
function renderBalance() {
$("#balanceLoading").hidden = !state.isBalanceLoading;
$("#balanceValue").hidden = state.isBalanceLoading;
$("#balanceValue").textContent = state.balance;
}
function renderCoins() {
const grid = $("#coinGrid");
grid.innerHTML = "";
coinOptions.forEach((option) => {
const item = document.createElement("button");
item.type = "button";
item.className = `coin-item${state.selectedId === option.id ? " selected" : ""}`;
item.innerHTML = `<img src="${icons.coin}" alt=""><div class="coin-price">$${option.price}</div>`;
item.addEventListener("click", () => {
state.selectedId = state.selectedId === option.id ? null : option.id;
renderCoins();
});
grid.appendChild(item);
});
$("#exchangeButton").disabled = !state.selectedId;
}
function bindEvents() {
$("[data-action='back']").addEventListener("click", closePage);
$("[data-action='language']").addEventListener("click", () => navigateTo("/language"));
$$("[data-route]").forEach((node) => node.addEventListener("click", () => navigateTo(node.dataset.route)));
$("#exchangeButton").addEventListener("click", () => {
if (state.selectedId) showModal();
});
$("#confirmModal").addEventListener("click", hideModal);
$(".modal-card").addEventListener("click", (event) => event.stopPropagation());
$("[data-action='cancel']").addEventListener("click", hideModal);
$("[data-action='confirm']").addEventListener("click", confirmExchange);
}
async function init() {
renderI18n();
renderIcons();
renderBalance();
renderCoins();
bindEvents();
await connectToApp();
checkExchangeAvailable();
fetchBalance();
console.log("document.documentElement.getAttribute('dir') === 'rtl'", document.documentElement.getAttribute("dir") === "rtl");
}
init();