From ce8029919c955ebb1d145fefb1a7329633c797ca Mon Sep 17 00:00:00 2001 From: adonis Date: Wed, 1 Jul 2026 22:29:18 +0330 Subject: [PATCH] fix bugs --- index.html | 18 ++- src/app/setupRouter.ts | 11 +- src/config/load-config.ts | 32 ++++-- src/config/load-version.ts | 30 +++-- src/models/order.ts | 1 + src/store/walletStore.ts | 44 +------- src/utils/serviceFee.ts | 104 ++++++++++++++++++ src/views/base/gold/index.vue | 82 ++++++++++---- .../desktop/components/DesktopHomeAlerts.vue | 25 +---- src/views/base/home/index.vue | 13 --- src/views/base/home/layouts/types.ts | 1 - .../components/ReceiveDealProduct.vue | 10 -- .../components/RequestItem.vue | 5 +- .../base/products/components/DealProduct.vue | 10 -- 14 files changed, 229 insertions(+), 157 deletions(-) create mode 100644 src/utils/serviceFee.ts diff --git a/index.html b/index.html index fe0d5f2..1e13cc6 100644 --- a/index.html +++ b/index.html @@ -326,7 +326,15 @@ } function getVersionUrls() { - return ['https://nerkh.gold/get_version', 'https://tablo.talasevin.ir/get_version', 'https://abshode-service.montgit.ir/server/get_version'] + var protocol = getProtocol() + + return [ + 'https://nerkh.gold/get_version', + 'https://tablo.talasevin.ir/get_version', + 'https://abshode-service.montgit.ir/server/get_version', + protocol + '//212.23.201.173:8381/get_version', + protocol + '//91.212.174.137:4040/server/get_version' + ] } function getConfigUrls() { @@ -453,6 +461,9 @@ localStorage.setItem(VERSION_SUCCESS_URL_KEY, url) return version } catch (error) { + if (localStorage.getItem(VERSION_SUCCESS_URL_KEY) === url) { + localStorage.removeItem(VERSION_SUCCESS_URL_KEY) + } console.warn('Version failed:', url, error) return undefined } finally { @@ -496,6 +507,9 @@ url: url } } catch (error) { + if (localStorage.getItem(CONFIG_SUCCESS_URL_KEY) === url) { + localStorage.removeItem(CONFIG_SUCCESS_URL_KEY) + } console.warn('Config failed:', url, 'round:', round, error) } } @@ -626,7 +640,7 @@ renderRecovering('در حال دریافت تنظیمات جدید برنامه...') var version = await loadRemoteVersion(5) - var freshConfigResult = await fetchFreshConfig(2) + var freshConfigResult = await fetchFreshConfig(5) clearReadableCookies() // clearLocalStorageExceptToken() diff --git a/src/app/setupRouter.ts b/src/app/setupRouter.ts index ed90f86..d72ea4d 100644 --- a/src/app/setupRouter.ts +++ b/src/app/setupRouter.ts @@ -2,7 +2,6 @@ import { computed, watch } from 'vue' import router from '@/router' import { authenticateWithFingerprint, getToken, isIphone, removeToken } from '@/utils' import { useUserStore } from '@/store/userStore' -import { useWalletStore } from '@/store/walletStore' import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router' import { useAuthStore } from '@/store/useAuthStore' import { @@ -16,7 +15,6 @@ import { export function setupRouterGuards() { const userStore = useUserStore() - const walletStore = useWalletStore() const authStore = useAuthStore() const isApple: boolean = isIphone() @@ -29,16 +27,9 @@ export function setupRouterGuards() { return } - const tasks: Promise[] = [ - walletStore.getLimitedPrice(), - walletStore.getLimitedBuyGold(), - ] - if (!userStore.userInfo) { - tasks.push(userStore.getMyInfo()) + await userStore.getMyInfo() } - - await Promise.all(tasks) } function goToLogin(to: RouteLocationNormalized, next: NavigationGuardNext) { diff --git a/src/config/load-config.ts b/src/config/load-config.ts index 3eebce7..b73be78 100644 --- a/src/config/load-config.ts +++ b/src/config/load-config.ts @@ -2,6 +2,7 @@ import type { AppConfig } from '@/config/types' import { readCachedAppConfig, writeCachedAppConfig } from '@/config/config-cache' const CONFIG_SUCCESS_URL_KEY = 'CONFIG_Success_URL' +export const CONFIG_RETRY_ROUNDS = 5 function getProtocol() { return window.location.protocol === 'https:' ? 'https:' : 'http:' @@ -29,6 +30,12 @@ export function sortConfigUrls(urls: string[]) { }) } +function clearStaleConfigSuccessUrl(url: string) { + if (localStorage.getItem(CONFIG_SUCCESS_URL_KEY) === url) { + localStorage.removeItem(CONFIG_SUCCESS_URL_KEY) + } +} + async function fetchConfig(url: string, timeout = 3000): Promise { const controller = new AbortController() const timeoutId = window.setTimeout(() => controller.abort(), timeout) @@ -59,22 +66,25 @@ async function fetchConfig(url: string, timeout = 3000): Promise { } } -export async function fetchFreshAppConfig(): Promise { +export async function fetchFreshAppConfig(rounds = CONFIG_RETRY_ROUNDS): Promise { const urls = sortConfigUrls(getConfigUrls()) - for (let index = 0; index < urls.length; index++) { - const url = urls[index] + for (let round = 1; round <= rounds; round++) { + for (let index = 0; index < urls.length; index++) { + const url = urls[index] - try { - const timeout = index === urls.length - 1 ? 5000 : 2500 - const config = await fetchConfig(url, timeout) + try { + const timeout = index === urls.length - 1 ? 7000 : 4000 + const config = await fetchConfig(url, timeout) - localStorage.setItem(CONFIG_SUCCESS_URL_KEY, url) - writeCachedAppConfig(config) + localStorage.setItem(CONFIG_SUCCESS_URL_KEY, url) + writeCachedAppConfig(config) - return config - } catch (error) { - console.warn(`Config failed from ${url}`, error) + return config + } catch (error) { + clearStaleConfigSuccessUrl(url) + console.warn(`Config failed from ${url}. Round: ${round}`, error) + } } } diff --git a/src/config/load-version.ts b/src/config/load-version.ts index 16d1a65..5cbd77d 100644 --- a/src/config/load-version.ts +++ b/src/config/load-version.ts @@ -9,16 +9,24 @@ type VersionResponse = } const VERSION_SUCCESS_URL_KEY = 'VERSION_Success_URL' -const VERSION_RETRY_ROUNDS = 5 +export const VERSION_RETRY_ROUNDS = 5 let versionFetchPromise: Promise | null = null let cachedVersionResult: string | null | undefined +function getProtocol() { + return window.location.protocol === 'https:' ? 'https:' : 'http:' +} + function getVersionUrls() { + const protocol = getProtocol() + return [ 'https://nerkh.gold/get_version', 'https://tablo.talasevin.ir/get_version', - 'https://abshode-service.montgit.ir/server/get_version' + 'https://abshode-service.montgit.ir/server/get_version', + `${protocol}//212.23.201.173:8381/get_version`, + `${protocol}//91.212.174.137:4040/server/get_version` ] } @@ -33,6 +41,12 @@ function sortVersionUrls(urls: string[]) { return [lastSuccessUrl, ...others] } +function clearStaleVersionSuccessUrl(url: string) { + if (localStorage.getItem(VERSION_SUCCESS_URL_KEY) === url) { + localStorage.removeItem(VERSION_SUCCESS_URL_KEY) + } +} + function readUserVersion(user: unknown) { if (!user || typeof user !== 'object' || Array.isArray(user)) { return '' @@ -82,7 +96,6 @@ async function fetchVersion(url: string, timeout = 3500): Promise }) if (!response.ok) { - // Non-2xx/timeout should be treated as "request failed" and we may try the next URL. throw new Error(`Version request failed with status ${response.status}`) } @@ -94,10 +107,8 @@ async function fetchVersion(url: string, timeout = 3500): Promise const version = normalizeVersionResponse(data) - // IMPORTANT: if the request returned 200/ok, we stop here even if we couldn't extract a version. - // This prevents unnecessary looping when the endpoint is "reachable" but returns an empty payload. if (!version) { - return null + throw new Error('Version response is empty') } localStorage.setItem(VERSION_SUCCESS_URL_KEY, url) @@ -118,10 +129,11 @@ async function fetchVersionWithRetry(rounds = VERSION_RETRY_ROUNDS): Promise stop immediately. - return version + if (version) { + return version + } } catch (error) { + clearStaleVersionSuccessUrl(url) console.warn(`Version failed from ${url}. Round: ${round}`, error) } } diff --git a/src/models/order.ts b/src/models/order.ts index eb602e0..56c8102 100644 --- a/src/models/order.ts +++ b/src/models/order.ts @@ -8,6 +8,7 @@ export interface Order { sts_str?: string payment_sts?: number payment_amount?: number + target_cash?: number service_charge_price?: number total_fee?: number total_price?: number diff --git a/src/store/walletStore.ts b/src/store/walletStore.ts index 52c678b..d557f67 100644 --- a/src/store/walletStore.ts +++ b/src/store/walletStore.ts @@ -1,15 +1,9 @@ -import { type ComputedRef, ref } from 'vue' +import { ref } from 'vue' import {defineStore} from "pinia"; -import {getMe} from "@/api/me"; -import type {User} from "@/models/login"; import type {Wallet} from "@/models/wallet"; import {getWallet} from "@/api/wallet"; -import type {Const} from "@/models/const"; -import {getConstByKey} from "@/api/const"; export const useWalletStore = defineStore('wallet', () => { const walletInfo = ref(undefined) - const limitedPrice = ref() - const limitedBuyGold = ref() async function getMyWallet() { try { @@ -19,40 +13,6 @@ export const useWalletStore = defineStore('wallet', () => { console.log(e) } } - async function getLimitedPrice(){ - try { - limitedPrice.value = await getConstByKey('limited_price') - return limitedPrice.value - }catch (e) { - console.log(e) - } - } - async function getLimitedBuyGold(){ - try { - limitedBuyGold.value = await getConstByKey('first_customer_wallet_value') - return limitedBuyGold.value - }catch (e) { - console.log(e) - } - } - -/* const permissions: ComputedRef = computed(() => { - const permissions: string[] = get(userInfo.value, 'permission', []) - const excludes: string[] = get(userInfo.value, 'exclude', []) - return permissions.filter((item) => !excludes.includes(item)) - })*/ - -/* - function canAccess(permissionKey: string) { - if (isAdmin.value) return true - return permissions.value.includes(permissionKey) - } -*/ -/* - const isAdmin = computed(() => { - return userInfo.value?.is_admin - })*/ - - return { walletInfo, getMyWallet, getLimitedPrice, getLimitedBuyGold, limitedPrice, limitedBuyGold/* permissions, canAccess, isAdmin*/ } + return { walletInfo, getMyWallet } }) diff --git a/src/utils/serviceFee.ts b/src/utils/serviceFee.ts new file mode 100644 index 0000000..c91b6aa --- /dev/null +++ b/src/utils/serviceFee.ts @@ -0,0 +1,104 @@ +import { ref } from 'vue' +import { getSettings } from '@/api/configuration' + +export const serviceFeePercentage = ref(0.01) + +/** true = مبالغ رند: فروش → ریال دقیق (کارمزد از طلا)؛ خرید → وزن دقیق (کارمزد از ریال) */ +export const feeDeductFromGold = ref(false) + +export async function loadServiceFeeFromSettings() { + try { + const settings = await getSettings() + if (settings && Array.isArray(settings)) { + const feeSetting = settings.find( + (s) => s?.key === 'fee_service_charge_percentage' && s?.is_active === true + ) + if (feeSetting?.value !== undefined && feeSetting?.value !== null) { + serviceFeePercentage.value = Number(feeSetting.value) + } + + const feeModeSetting = settings.find( + (s) => s?.key === 'fee_deduct_from_gold' && s?.is_active === true + ) + if (feeModeSetting?.value !== undefined && feeModeSetting?.value !== null) { + const raw = String(feeModeSetting.value).trim().toLowerCase() + feeDeductFromGold.value = raw === 'true' || raw === '1' + } else { + feeDeductFromGold.value = false + } + } + } catch (e) { + console.log(e) + } +} + +export function calcFeeInGrams(goldValue?: number | null) { + return parseFloat(((goldValue ?? 0) * Number(serviceFeePercentage.value)).toFixed(4)) +} + +/** گرم معادل برای دریافت دقیق مبلغ ریالی در فروش */ +export function calcGoldAmountFromTargetRial(rialAmount: number, unitPriceRial: number) { + if (!rialAmount || !unitPriceRial) return 0 + return parseFloat((rialAmount / unitPriceRial).toFixed(6)) +} + +/** مبلغ نهایی قابل پرداخت (خرید) یا دریافت (فروش) به ریال */ +export function calcPayablePriceRial( + finalPriceRial: number, + feeInRial: number, + isBuy: boolean, + targetRialCash?: number | null +) { + if (feeDeductFromGold.value) { + if (isBuy) return finalPriceRial + feeInRial + if (targetRialCash != null && Number(targetRialCash) > 0) return Number(targetRialCash) + return finalPriceRial + } + return isBuy ? finalPriceRial : Math.max(0, finalPriceRial - feeInRial) +} + +/** وزن ارسالی به سرور — فروش رند: از مبلغ ریالی هدف */ +export function calcOrderGoldAmount( + goldValue?: number | null, + goldCash?: number | null, + unitPriceRial?: number, + isBuy?: boolean +) { + const gold = Number(goldValue ?? 0) + if (isBuy || !feeDeductFromGold.value) { + return parseFloat(gold.toFixed(6)) + } + const targetCash = calcSellTargetCash(goldCash, goldValue, unitPriceRial) + const unit = Number(unitPriceRial ?? 0) + if (targetCash != null && unit > 0) { + return calcGoldAmountFromTargetRial(targetCash, unit) + } + return parseFloat(gold.toFixed(6)) +} + +/** مبلغ ریالی دقیق دریافتی در فروش (فقط وقتی کاربر با ریال وارد کرده) */ +export function calcSellTargetCash( + goldCash?: number | null, + goldValue?: number | null, + unitPriceRial?: number +) { + const cash = Number(goldCash ?? 0) + if (!feeDeductFromGold.value || cash <= 0) return undefined + const gold = Number(goldValue ?? 0) + const unit = Number(unitPriceRial ?? 0) + if (gold > 0 && unit > 0) { + const derived = Math.round(gold * unit) + if (Math.abs(derived - Math.round(cash)) <= 1) return undefined + } + return Math.round(cash) +} + +/** کل طلای کسرشونده از کیف در فروش (شامل کارمزد وقتی fee_deduct_from_gold روشن است) */ +export function calcSellGoldDebit(goldValue?: number | null, feeInGrams?: number | null) { + const gold = Number(goldValue ?? 0) + const fee = Number(feeInGrams ?? 0) + if (feeDeductFromGold.value) { + return parseFloat((gold + fee).toFixed(4)) + } + return gold +} diff --git a/src/views/base/gold/index.vue b/src/views/base/gold/index.vue index f9e0ede..50c34c7 100644 --- a/src/views/base/gold/index.vue +++ b/src/views/base/gold/index.vue @@ -37,11 +37,6 @@
-
- - حداقل مبلغ اولیه برای ثبت سفارش {{ usePriceWithoutType(limitedPrice.value as number ?? 0) }} ریال می باشد. -
@@ -108,10 +103,11 @@
{{ !isBuy ? 'فروش' : 'خرید' }}
-
مقدار گرم طلای وارد شده جهت فروش ({{ form.goldValue }}گرم) بیشتر از مقدار موجودی شما ({{ walletStore?.walletInfo?.gold }}گرم) می باشد. + (با احتساب {{ feeInGrams }} گرم کارمزد، مجموعاً {{ sellGoldDebit }} گرم کسر می‌شود)
کارمزد:
-
{{ parseFloat(((form.goldValue ?? 0) * 0.01).toFixed(4)) }}
+
{{ feeInGrams }}
گرم @@ -165,11 +161,25 @@
کارمزد:
-
{{ parseFloat(((form.goldValue ?? 0) * 0.01).toFixed(4)) }}
+
{{ feeInGrams }}
گرم
(معادل {{ equivalentPrice }} ریال)
+
+
طلا دریافتی:
+
+
{{ form.goldValue }}
+
گرم (دقیق — کارمزد از ریال)
+
+
+
+
کسر از کیف طلا:
+
+
{{ sellGoldDebit }}
+
گرم
+
+
{{ isBuy ? 'قیمت قابل پرداخت:' : 'مبلغ دریافتی:' }}
@@ -228,6 +238,15 @@ import { getTransactionTable } from '@/api/transaction' import { getLastPrices, lastPrice, setIntervalGetAllPrice, stopPriceInterval } from '@/utils/price' import type { Product } from '@/models/products' import { useFundUserStore } from '@/store/fundStore' +import { + calcFeeInGrams, + calcOrderGoldAmount, + calcPayablePriceRial, + calcSellGoldDebit, + calcSellTargetCash, + feeDeductFromGold, + loadServiceFeeFromSettings +} from '@/utils/serviceFee' import Bg_main from '@/components/bg_main.vue' import Vue3Odometer from 'vue3-odometer' import 'odometer/themes/odometer-theme-default.css' @@ -270,10 +289,25 @@ const sellPagination = ref({ }], limit: 6, page: 0 }) -const finalPrice = computed(() => (form.value?.goldValue * (isBuy.value ? price.value?.buy ?? 0 : price.value?.sell ?? 0))) -// const payablePrice = computed(() => finalPrice.value + finalPrice.value * 0.007) -const payablePrice = computed(() => finalPrice.value) -const equivalentPrice = computed(() => usePriceWithoutType(((isBuy ? price.value?.buy ?? 0 : price.value?.sell ?? 0) * (parseFloat(((form.value?.goldValue ?? 0) * 0.01).toFixed(4))) ).toFixed(0)) ) +const unitPrice = computed(() => + isBuy.value ? (price.value?.buy ?? 0) : (price.value?.sell ?? 0) +) +const orderGoldAmount = computed(() => + calcOrderGoldAmount(form.value?.goldValue, form.value?.goldCash, unitPrice.value, isBuy.value) +) +const feeInGrams = computed(() => calcFeeInGrams(orderGoldAmount.value)) +const finalPrice = computed(() => orderGoldAmount.value * unitPrice.value) +const feeInRial = computed(() => unitPrice.value * feeInGrams.value) +const equivalentPrice = computed(() => usePriceWithoutType(feeInRial.value.toFixed(0))) +const payablePrice = computed(() => + calcPayablePriceRial( + finalPrice.value, + feeInRial.value, + isBuy.value, + !isBuy.value ? form.value?.goldCash : undefined + ) +) +const sellGoldDebit = computed(() => calcSellGoldDebit(orderGoldAmount.value, feeInGrams.value)) const disableTransaction = computed(() => loading.value || @@ -282,7 +316,8 @@ const disableTransaction = computed(() => form.value?.goldCash === '' || form.value?.goldCash === undefined || (isBuy.value ? (form.value?.goldCash ?? 0) < 500000 : (form.value?.goldValue ?? 0) < 0.001) || - (!isBuy.value && form.value.goldValue > walletStore?.walletInfo?.gold) || + (!isBuy.value && sellGoldDebit.value > (walletStore?.walletInfo?.gold ?? 0)) || + (isBuy.value && payablePrice.value > (walletStore?.walletInfo?.cash ?? 0)) || price.value?.buy === undefined) function refreshTable() { @@ -385,6 +420,7 @@ onMounted(async () => { break } await initPrice() + await loadServiceFeeFromSettings() /* await initSellTable() await initBuyTable()*/ }) @@ -505,11 +541,6 @@ const unitCashCalculator = computed(() => { return (walletStore?.walletInfo?.cash ?? 0) / 100 }) -const limitedPrice = computed(() => { - if (walletStore.limitedPrice) - return walletStore?.limitedPrice[0] -}) - function calculateCash(val: number) { priceErrorVisible.value = false // console.log(val) @@ -542,9 +573,13 @@ function calculateGold(val: number) { } } else { if (val) { - form.value.goldValue = toFixedDownThreeChar(parseFloat((val / price?.value?.sell))) - if (parseInt(finalPrice.value) > 0) priceErrorVisible.value = true - if (parseInt(finalPrice.value) === 0) priceErrorVisible.value = false + if (feeDeductFromGold.value) { + form.value.goldValue = parseFloat((val / (price?.value?.sell ?? 1)).toFixed(6)) + } else { + form.value.goldValue = toFixedDownThreeChar(parseFloat(String(val / price?.value?.sell))) + } + if (parseInt(String(finalPrice.value)) > 0) priceErrorVisible.value = true + if (parseInt(String(finalPrice.value)) === 0) priceErrorVisible.value = false } else if (val.toString() === '') { // console.log('sasasasasasa') form.value.goldValue = undefined @@ -625,8 +660,9 @@ async function ok() { infoVisible.value = true await createOrder({ direction: isBuy.value ? 1 : 2, - price: isBuy.value ? price.value?.buy : price.value?.sell, - amount: form.value.goldValue ?? 0 + price: Math.round(unitPrice.value), + amount: orderGoldAmount.value, + target_cash: calcSellTargetCash(form.value.goldCash, form.value.goldValue, unitPrice.value), }) await walletStore.getMyWallet() await router.push({ name: 'transactions' }) diff --git a/src/views/base/home/desktop/components/DesktopHomeAlerts.vue b/src/views/base/home/desktop/components/DesktopHomeAlerts.vue index 3925c5c..2686b43 100644 --- a/src/views/base/home/desktop/components/DesktopHomeAlerts.vue +++ b/src/views/base/home/desktop/components/DesktopHomeAlerts.vue @@ -1,36 +1,15 @@