From f265ccfa57dc0ee362672c8d0908da6b6682099a Mon Sep 17 00:00:00 2001 From: zhx Date: Fri, 29 May 2026 20:04:29 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B7=A5=E8=B5=84=E5=8E=86=E5=8F=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- h5/hyapp/withdraw-exchange/index.html | 27 ++- h5/hyapp/withdraw-exchange/script.js | 321 +++++++++++++++++++++++++- h5/hyapp/withdraw-exchange/style.css | 199 ++++++++++++++++ 3 files changed, 540 insertions(+), 7 deletions(-) diff --git a/h5/hyapp/withdraw-exchange/index.html b/h5/hyapp/withdraw-exchange/index.html index 8e71dda..7056a1d 100644 --- a/h5/hyapp/withdraw-exchange/index.html +++ b/h5/hyapp/withdraw-exchange/index.html @@ -4,7 +4,7 @@ Withdraw/Exchange - +
@@ -29,7 +29,14 @@
-
salary
+
+
salary
+ +
0.00 @@ -142,8 +149,22 @@
+
- + diff --git a/h5/hyapp/withdraw-exchange/script.js b/h5/hyapp/withdraw-exchange/script.js index bbdcf88..8c6b2b4 100644 --- a/h5/hyapp/withdraw-exchange/script.js +++ b/h5/hyapp/withdraw-exchange/script.js @@ -61,7 +61,19 @@ exchange_success: "Exchange success", exchange_failed: "Exchange failed", transfer_success: "Transfer success", - transfer_failed: "Transfer failed" + transfer_failed: "Transfer failed", + salary_history: "Salary history", + close: "Close", + loading: "Loading...", + no_salary_history: "No salary history", + income: "Income", + expense: "Expense", + balance: "Balance", + period: "Period", + days: "Days", + target: "Target", + work_report: "Work report", + load_salary_history_failed: "Failed to load salary history" }, ar: { page_label: "السحب والتبديل", @@ -113,7 +125,19 @@ exchange_success: "تم التبديل", exchange_failed: "فشل التبديل", transfer_success: "تم التحويل", - transfer_failed: "فشل التحويل" + transfer_failed: "فشل التحويل", + salary_history: "سجل الراتب", + close: "إغلاق", + loading: "جار التحميل...", + no_salary_history: "لا يوجد سجل راتب", + income: "دخل", + expense: "مصروف", + balance: "الرصيد", + period: "الفترة", + days: "الأيام", + target: "الهدف", + work_report: "تقرير العمل", + load_salary_history_failed: "فشل تحميل سجل الراتب" }, tr: { page_label: "Çekme ve değişim", @@ -165,7 +189,19 @@ exchange_success: "Exchange success", exchange_failed: "Exchange failed", transfer_success: "Transfer success", - transfer_failed: "Transfer failed" + transfer_failed: "Transfer failed", + salary_history: "Salary history", + close: "Close", + loading: "Loading...", + no_salary_history: "No salary history", + income: "Income", + expense: "Expense", + balance: "Balance", + period: "Period", + days: "Days", + target: "Target", + work_report: "Work report", + load_salary_history_failed: "Failed to load salary history" }, id: { page_label: "Tarik dan tukar", @@ -217,7 +253,19 @@ exchange_success: "Penukaran berhasil", exchange_failed: "Penukaran gagal", transfer_success: "Transfer berhasil", - transfer_failed: "Transfer gagal" + transfer_failed: "Transfer gagal", + salary_history: "Riwayat gaji", + close: "Tutup", + loading: "Memuat...", + no_salary_history: "Tidak ada riwayat gaji", + income: "Masuk", + expense: "Keluar", + balance: "Saldo", + period: "Periode", + days: "Hari", + target: "Target", + work_report: "Laporan kerja", + load_salary_history_failed: "Gagal memuat riwayat gaji" } }; @@ -232,9 +280,14 @@ let selectedPaymentInfo = null; let selectedPayee = null; let currentProfile = null; + let currentIdentityInfo = null; let isSubmitting = false; let isSavingAddress = false; let isSearchingPayee = false; + let isSalaryHistoryLoading = false; + let salaryHistoryItems = []; + let salaryHistoryTotal = 0; + let salaryHistoryLoadedType = ""; const inputs = { withdraw: document.querySelector("#withdrawAmount"), @@ -255,6 +308,10 @@ const searchPayeeButton = document.querySelector("#searchPayeeButton"); const exchangeRateText = document.querySelector("#exchangeRateText"); const salarySelector = document.querySelector("#salarySelector"); + const salaryHistoryButton = document.querySelector("#salaryHistoryButton"); + const salaryHistoryModal = document.querySelector("#salaryHistoryModal"); + const salaryHistoryList = document.querySelector("#salaryHistoryList"); + const salaryHistoryTotalNode = document.querySelector("#salaryHistoryTotal"); function readURLParams() { const result = new URLSearchParams(window.location.search); @@ -423,6 +480,18 @@ return Number.isFinite(number) ? number : 0; } + function isTruthyIdentityValue(value) { + if (value === true || value === 1) return true; + const text = String(value || "").trim().toLowerCase(); + return text === "true" || text === "1" || text === "yes"; + } + + function currentUserId() { + return currentProfile?.id || + currentProfile?.userId || + firstRawParam(["userId", "UserId", "uid", "UID", "id"]); + } + function activeInput() { return inputs[currentMode] || null; } @@ -461,6 +530,9 @@ if (!nextType || nextType === currentSalaryType) return; currentSalaryType = nextType; renderBalance(salaryBalanceValue(nextType)); + if (salaryHistoryModal && !salaryHistoryModal.hidden && salaryHistoryLoadedType !== activeSalaryType()) { + openSalaryHistory(); + } } function isValidTrc20Address(value) { @@ -501,6 +573,7 @@ if (exchangeRateText) exchangeRateText.textContent = formatExchangeRate(); renderSalarySelector(); renderPayee(); + renderSalaryHistory(); updateSubmitState(); } @@ -593,6 +666,104 @@ updateSubmitState(); } + function setSalaryHistoryModalOpen(open) { + if (!salaryHistoryModal) return; + salaryHistoryModal.hidden = !open; + document.body.classList.toggle("salary-history-open", open); + } + + function formatHistoryDate(value) { + if (!value) return ""; + const text = String(value).replace("T", " "); + return text.length > 16 ? text.slice(0, 16) : text; + } + + function formatBillPeriod(value) { + const text = String(value || ""); + if (text.length < 6) return text; + return `${text.slice(0, 4)}.${text.slice(4, 6)}`; + } + + function salaryTypeLabel(type) { + const value = normalizeSalaryType(type); + if (value === "BD_SALARY") return message("bd_salary", "BD salary"); + if (value === "BD_LEADER_SALARY") return message("bd_leader_salary", "BD Leader salary"); + return message("available_income", "salary"); + } + + function historyItemTitle(item) { + return item.title || + item.eventDesc || + item.eventDescribe || + item.salaryTypeName || + item.salaryEvent || + item.event || + message("work_report", "Work report"); + } + + function createSalaryHistoryItem(item) { + const row = document.createElement("article"); + row.className = "salary-history-item"; + + const main = document.createElement("div"); + main.className = "salary-history-item-main"; + + const title = document.createElement("div"); + title.className = "salary-history-item-title"; + title.textContent = historyItemTitle(item); + + const meta = document.createElement("div"); + meta.className = "salary-history-item-meta"; + const metaItems = []; + if (item.date) metaItems.push(item.date); + if (item.period) metaItems.push(`${message("period", "Period")}: ${item.period}`); + if (item.target) metaItems.push(`${message("target", "Target")}: ${item.target}`); + if (item.days) metaItems.push(`${message("days", "Days")}: ${item.days}`); + if (item.balance !== undefined && item.balance !== null && item.balance !== "") { + metaItems.push(`${message("balance", "Balance")}: $${formatMoney(item.balance)}`); + } + meta.textContent = metaItems.join(" "); + + main.append(title, meta); + + const isOut = Number(item.type) === 1; + const amount = document.createElement("div"); + amount.className = `salary-history-amount${isOut ? " is-out" : ""}`; + amount.textContent = `${isOut ? "-" : "+"}$${formatMoney(item.amount)}`; + + row.append(main, amount); + if (item.status) { + const status = document.createElement("div"); + status.className = "salary-history-status"; + status.textContent = item.status; + row.appendChild(status); + } + return row; + } + + function renderSalaryHistory() { + if (salaryHistoryTotalNode) salaryHistoryTotalNode.textContent = `$${formatMoney(salaryHistoryTotal)}`; + if (!salaryHistoryList) return; + + if (isSalaryHistoryLoading) { + const loading = document.createElement("div"); + loading.className = "salary-history-empty"; + loading.textContent = message("loading", "Loading..."); + salaryHistoryList.replaceChildren(loading); + return; + } + + if (!salaryHistoryItems.length) { + const empty = document.createElement("div"); + empty.className = "salary-history-empty"; + empty.textContent = message("no_salary_history", "No salary history"); + salaryHistoryList.replaceChildren(empty); + return; + } + + salaryHistoryList.replaceChildren(...salaryHistoryItems.map(createSalaryHistoryItem)); + } + function renderPayee() { if (!payeeResult) return; @@ -674,6 +845,144 @@ } } + async function fetchIdentityInfo() { + if (currentIdentityInfo) return currentIdentityInfo; + try { + const data = await requestJSON("/app/h5/identity"); + currentIdentityInfo = responseBody(data) || {}; + } catch (error) { + console.warn("Failed to load identity info:", error); + currentIdentityInfo = {}; + } + return currentIdentityInfo; + } + + function normalizeStatusLabel(status) { + const value = String(status || "").trim(); + if (value === "SETTLED" || value === "PAY_OUT" || value === "Completed") return "Completed"; + if (value === "UNPAID" || value === "In Progress") return "In progress"; + if (value === "HANG_UP" || value === "Out of account" || value === "Out Of Account") return "Out of account"; + if (value === "Pending") return "Pending"; + return value; + } + + function memberWorkSalary(target, identity) { + const settlement = target?.settlementResult || {}; + const memberSalary = moneyNumber(settlement.memberSalary); + const ownSalary = moneyNumber(settlement.ownSalary); + const totalSalary = moneyNumber(settlement.totalSalary); + if (isTruthyIdentityValue(identity?.agent)) return memberSalary + ownSalary; + if (isTruthyIdentityValue(identity?.anchor)) return memberSalary; + return totalSalary || memberSalary || ownSalary; + } + + async function fetchMemberWorkHistory() { + if (!currentProfile) await fetchUserProfile(); + const userId = currentUserId(); + if (!userId) throw new Error(message("missing_token", "Missing token")); + + const [identity, data] = await Promise.all([ + fetchIdentityInfo(), + requestJSON(`/team/member-work?userId=${encodeURIComponent(userId)}`) + ]); + const body = responseBody(data) || {}; + const targets = Array.isArray(body.targets) ? body.targets : []; + return { + total: body.historySalary ?? availableBalance, + items: targets.map((item) => { + const target = item.target || {}; + return { + id: item.id, + title: item.billTitle || message("work_report", "Work report"), + amount: memberWorkSalary(target, identity), + type: 0, + status: normalizeStatusLabel(item.status), + target: target.acceptGiftValue, + days: target.effectiveDay, + period: item.billBelong ? formatBillPeriod(item.billBelong) : "", + date: item.billTitle || "" + }; + }) + }; + } + + async function fetchSalaryAccountHistory() { + if (!currentProfile) await fetchUserProfile(); + const query = new URLSearchParams({ + salaryType: activeSalaryType(), + current: "1", + size: "20" + }); + const userId = currentUserId(); + if (userId) query.set("userId", String(userId)); + + const data = await requestJSON(`/wallet/salary-account/running-water?${query.toString()}`); + const body = responseBody(data) || data || {}; + const records = Array.isArray(body.records) ? body.records : Array.isArray(body.list) ? body.list : []; + return { + total: availableBalance, + items: records.map((item) => ({ + id: item.id, + title: item.eventDesc || item.salaryTypeName || salaryTypeLabel(item.salaryType), + amount: item.amount, + balance: item.availableBalance ?? item.balance, + type: item.type, + status: item.salaryEvent || "", + period: item.billBelong ? formatBillPeriod(item.billBelong) : "", + date: formatHistoryDate(item.createdAt) + })) + }; + } + + async function fetchBankWaterHistory() { + const data = await requestJSON("/wallet/bank/balance/water/details"); + const records = Array.isArray(responseBody(data)) ? responseBody(data) : Array.isArray(data) ? data : []; + return { + total: availableBalance, + items: records.map((item) => ({ + id: item.id, + title: item.eventDescribe || item.event || message("salary_history", "Salary history"), + amount: item.amount, + balance: item.balance, + type: item.type, + status: item.event || "", + date: formatHistoryDate(item.createTime) + })) + }; + } + + async function fetchSalaryHistory() { + if (isSalaryAccountMode()) return fetchSalaryAccountHistory(); + try { + return await fetchMemberWorkHistory(); + } catch (error) { + console.warn("Failed to load member work history, falling back to bank water:", error); + return fetchBankWaterHistory(); + } + } + + async function openSalaryHistory() { + salaryHistoryLoadedType = activeSalaryType(); + salaryHistoryTotal = availableBalance; + salaryHistoryItems = []; + isSalaryHistoryLoading = true; + renderSalaryHistory(); + setSalaryHistoryModalOpen(true); + + try { + const result = await fetchSalaryHistory(); + salaryHistoryTotal = moneyNumber(result.total ?? availableBalance); + salaryHistoryItems = Array.isArray(result.items) ? result.items : []; + } catch (error) { + console.error("Failed to load salary history:", error); + salaryHistoryItems = []; + showToast(error.message || message("load_salary_history_failed", "Failed to load salary history")); + } finally { + isSalaryHistoryLoading = false; + renderSalaryHistory(); + } + } + async function fetchPaymentInfo() { try { const data = await requestJSON("/wallet/bank-card/list"); @@ -944,6 +1253,10 @@ searchPayeeButton?.addEventListener("click", searchPayee); saveAddressButton?.addEventListener("click", saveAddress); submitButton?.addEventListener("click", submitCurrentMode); + salaryHistoryButton?.addEventListener("click", openSalaryHistory); + document.querySelectorAll("[data-close-salary-history]").forEach((node) => { + node.addEventListener("click", () => setSalaryHistoryModalOpen(false)); + }); syncBackFallback(); applyLanguage(currentLang); diff --git a/h5/hyapp/withdraw-exchange/style.css b/h5/hyapp/withdraw-exchange/style.css index 1a80e45..d0ab18b 100644 --- a/h5/hyapp/withdraw-exchange/style.css +++ b/h5/hyapp/withdraw-exchange/style.css @@ -197,12 +197,44 @@ select { padding: 20px 24px 19px; } +.balance-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + .balance-label { color: var(--muted); font-size: 14px; font-weight: 800; } +.balance-history-button { + display: flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + flex: 0 0 34px; + border-radius: 50%; + background: #effafa; + color: #25282e; +} + +.balance-history-button svg { + width: 22px; + height: 22px; +} + +.balance-history-button path { + fill: none; + stroke: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2.6; +} + .balance-value { display: flex; align-items: center; @@ -623,6 +655,7 @@ select { position: fixed; left: 50%; bottom: 34px; + z-index: 70; transform: translateX(-50%); padding: 10px 14px; border-radius: 999px; @@ -632,6 +665,172 @@ select { font-weight: 850; } +.salary-history-modal { + position: fixed; + inset: 0; + z-index: 60; + display: flex; + align-items: flex-end; + justify-content: center; +} + +.salary-history-backdrop { + position: absolute; + inset: 0; + background: rgba(14, 19, 24, 0.44); +} + +.salary-history-dialog { + position: relative; + z-index: 1; + display: flex; + width: min(100%, 430px); + max-height: 78vh; + flex-direction: column; + overflow: hidden; + border-radius: 18px 18px 0 0; + background: #fff; + box-shadow: 0 -14px 36px rgba(26, 35, 44, 0.16); +} + +.salary-history-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 18px 18px 12px; +} + +.salary-history-head h2 { + min-width: 0; + margin: 0; + overflow: hidden; + color: #24282e; + font-size: 18px; + font-weight: 950; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.salary-history-close { + display: flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + flex: 0 0 34px; + border-radius: 50%; + background: #f3f5f5; + color: #676b72; + font-size: 28px; + line-height: 1; +} + +.salary-history-summary { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin: 0 18px 12px; + padding: 14px; + border-radius: 8px; + background: #f3fbfa; +} + +.salary-history-summary span { + color: var(--muted); + font-size: 13px; + font-weight: 850; +} + +.salary-history-summary strong { + min-width: 0; + overflow-wrap: anywhere; + color: #24282e; + font-size: 20px; + font-weight: 950; +} + +.salary-history-list { + display: grid; + gap: 10px; + overflow-y: auto; + padding: 0 18px calc(18px + env(safe-area-inset-bottom)); +} + +.salary-history-item { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 8px 12px; + padding: 14px; + border: 1px solid #eef1f1; + border-radius: 8px; + background: #fbfcfc; +} + +.salary-history-item-main { + min-width: 0; +} + +.salary-history-item-title { + overflow: hidden; + color: #25282e; + font-size: 14px; + font-weight: 900; + line-height: 1.25; + text-overflow: ellipsis; + white-space: nowrap; +} + +.salary-history-item-meta { + display: flex; + flex-wrap: wrap; + gap: 6px 10px; + margin-top: 6px; + color: var(--muted); + font-size: 12px; + font-weight: 750; + line-height: 1.25; +} + +.salary-history-amount { + color: #20a569; + font-size: 15px; + font-weight: 950; + line-height: 1.25; + white-space: nowrap; +} + +.salary-history-amount.is-out { + color: #e05d4f; +} + +.salary-history-status { + justify-self: start; + grid-column: 1 / -1; + width: max-content; + max-width: 100%; + padding: 3px 8px; + border-radius: 999px; + background: #edf8f7; + color: #0d8f83; + font-size: 11px; + font-weight: 900; +} + +.salary-history-empty { + padding: 24px 12px 30px; + color: var(--muted); + font-size: 14px; + font-weight: 850; + text-align: center; +} + +.salary-history-open { + overflow: hidden; +} + .home-indicator { position: fixed; left: 50%;