From 366157e026bc49343679bc5492c27d2c78d28643 Mon Sep 17 00:00:00 2001 From: adonis Date: Thu, 11 Jun 2026 16:30:08 +0330 Subject: [PATCH 1/8] fix price --- components.d.ts | 2 ++ src/utils/productPrice.ts | 10 ++++++++-- src/views/base/products/index.vue | 5 ++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/components.d.ts b/components.d.ts index 7dd7195..2da2181 100644 --- a/components.d.ts +++ b/components.d.ts @@ -25,6 +25,7 @@ declare module 'vue' { ATag: typeof import('ant-design-vue/es')['Tag'] ATextarea: typeof import('ant-design-vue/es')['Textarea'] ATooltip: typeof import('ant-design-vue/es')['Tooltip'] + AUpload: typeof import('ant-design-vue/es')['Upload'] BaseDatePicker: typeof import('./src/components/BasicPacks/BaseDatePicker/src/BaseDatePicker.vue')['default'] Basic: typeof import('./src/components/BasicPacks/BaseDatePicker/src/datePick/demo/basic.vue')['default'] BasicActionSheet: typeof import('./src/components/BasicPacks/BasicActionSheet.vue')['default'] @@ -68,5 +69,6 @@ declare module 'vue' { TransactionSkeleton: typeof import('./src/components/TransactionSkeleton.vue')['default'] VanActionSheet: typeof import('vant/es')['ActionSheet'] VanDialog: typeof import('vant/es')['Dialog'] + VanField: typeof import('vant/es')['Field'] } } diff --git a/src/utils/productPrice.ts b/src/utils/productPrice.ts index ed67af2..0f580de 100644 --- a/src/utils/productPrice.ts +++ b/src/utils/productPrice.ts @@ -14,7 +14,10 @@ function applyPriceOffsetPercent(basePrice: number, offsetPercent?: number | nul /** قیمت خرید نمایشی محصول (همان مقداری که در لیست و اکشن‌شیت نشان داده می‌شود) */ export function getProductBuyPrice(product: Product): number { if (isMeltedProductUsingAppPrice(product)) { - const base = applyPriceOffsetPercent(getTradeBuyPrice(lastPrice.value), product.price_offset_percent) + const tradeBuy = getTradeBuyPrice(lastPrice.value) + const base = tradeBuy > 0 + ? applyPriceOffsetPercent(tradeBuy, product.price_offset_percent) + : (product.price ?? 0) return base + (product.cash_fee_buy ?? 0) } @@ -36,7 +39,10 @@ export function getProductBuyPrice(product: Product): number { /** قیمت فروش نمایشی محصول آبشده‌ای */ export function getProductSellPrice(product: Product): number { if (isMeltedProductUsingAppPrice(product)) { - const base = applyPriceOffsetPercent(getTradeSellPrice(lastPrice.value), product.price_offset_percent) + const tradeSell = getTradeSellPrice(lastPrice.value) + const base = tradeSell > 0 + ? applyPriceOffsetPercent(tradeSell, product.price_offset_percent) + : (product.price ?? 0) return base - (product.cash_fee_sell ?? 0) } diff --git a/src/views/base/products/index.vue b/src/views/base/products/index.vue index 14f9734..11b14ca 100644 --- a/src/views/base/products/index.vue +++ b/src/views/base/products/index.vue @@ -102,6 +102,7 @@ import { isProductPriceInactive } from '@/utils/productPrice' import { isMetalCommodityGroup, isMeltedTradeGroup } from '@/utils/productCategories' import { isBoursecuDealProductGroup } from '@/utils/productDealMethod' import { loadServiceFeeFromSettings } from '@/utils/serviceFee' +import { getLastPrices, startLastPricePolling, stopLastPricePolling } from '@/utils/price' const activekey = ref('طلا' ||'آبشده') const showFilter = ref(false) @@ -210,6 +211,8 @@ async function refreshProducts() { onMounted(async () => { try { loading.value = true + await getLastPrices() + startLastPricePolling() items.value = await getProducts() if (items.value?.length) { @@ -225,7 +228,6 @@ onMounted(async () => { }) firstItems.value = items.value await loadServiceFeeFromSettings() - // فقط لیست محصولات هر ۱۰ ثانیه — last فقط در صفحه اصلی poll می‌شود productListInterval = setInterval(refreshProducts, 10000) } catch (e) { @@ -247,6 +249,7 @@ onMounted(async () => { } }) onUnmounted(() => { + stopLastPricePolling() if (productListInterval) { clearInterval(productListInterval) productListInterval = undefined From 298f9a6055d2c9427e0ac856807b4a7dd1c1d3f5 Mon Sep 17 00:00:00 2001 From: adonis Date: Thu, 11 Jun 2026 21:21:15 +0330 Subject: [PATCH 2/8] fix bug --- src/config-loader.js | 16 ++++ src/utils/inputRules.ts | 6 +- src/utils/productAssets.ts | 19 ++++- src/utils/productPrice.ts | 76 ++++++++++++++----- src/views/base/copper&silver/index.vue | 10 ++- src/views/base/gold/index.vue | 13 ++-- src/views/base/home/components/PriceStats.vue | 2 +- .../base/home/composables/usePriceChart.ts | 2 +- .../base/products/components/DealProduct.vue | 20 +++-- .../base/products/components/ProductItem.vue | 27 +------ src/views/base/wallet/index.vue | 16 ++-- src/views/login/components/Register.vue | 2 +- src/views/login/index.vue | 4 +- 13 files changed, 135 insertions(+), 78 deletions(-) diff --git a/src/config-loader.js b/src/config-loader.js index cdedbf4..2ca87ae 100644 --- a/src/config-loader.js +++ b/src/config-loader.js @@ -97,6 +97,22 @@ window.__APP_CONFIG__ = config window.__CONFIG_READY__ = true + if (config?.user?.name_fa) { + document.title = config.user.name_fa + } + + if (config?.user?.avatar) { + const faviconUrl = config.user.avatar + const existingIcons = document.querySelectorAll('link[rel*="icon"]') + existingIcons.forEach((icon) => icon.remove()) + + const link = document.createElement('link') + link.rel = 'icon' + link.type = 'image/png' + link.href = faviconUrl + '?v=' + Date.now() + document.head.appendChild(link) + } + // Resolve the promise if (configResolve) { configResolve(config) diff --git a/src/utils/inputRules.ts b/src/utils/inputRules.ts index a6c9624..01289d7 100644 --- a/src/utils/inputRules.ts +++ b/src/utils/inputRules.ts @@ -32,6 +32,7 @@ import {computed, ref} from 'vue' import type {Rule} from 'ant-design-vue/es/form' import {useWalletStore} from "@/store/walletStore"; import { isCashLikeDealMethod, isMainMetalWalletDealMethod, getInsufficientCashWalletMessage, getInsufficientGoldWalletMessage } from '@/utils/productDealMethod' +import { getCashWalletBalance } from '@/utils/productAssets' export const mobileValidation = (title?: string): Rule => { const label = title ?? 'شماره تلفن' @@ -160,13 +161,14 @@ const checkNumber = async (_rule: Rule, value: number) => { return Promise.reject('استفاده از اعداد اعشاری مجاز نمی باشد') } if (isBuyP){ - if (!walletCash?.walletInfo?.cash && isCashLikeDealMethod(methodP)){ + const availableCash = getCashWalletBalance(walletCash?.walletInfo) + if (!availableCash && isCashLikeDealMethod(methodP)){ return Promise.reject(getInsufficientCashWalletMessage(groupTypeP)) } if (!walletCash?.walletInfo?.gold && isMainMetalWalletDealMethod(methodP)){ return Promise.reject(getInsufficientGoldWalletMessage(groupTypeP, methodP)) } - if (priceP * value > walletCash?.walletInfo?.cash && isCashLikeDealMethod(methodP)) + if (priceP * value > availableCash && isCashLikeDealMethod(methodP)) return Promise.reject(getInsufficientCashWalletMessage(groupTypeP)) if (amountP * value > walletCash?.walletInfo?.gold && isMainMetalWalletDealMethod(methodP)) return Promise.reject(getInsufficientGoldWalletMessage(groupTypeP, methodP)) diff --git a/src/utils/productAssets.ts b/src/utils/productAssets.ts index fb2cd43..6e66ec7 100644 --- a/src/utils/productAssets.ts +++ b/src/utils/productAssets.ts @@ -334,17 +334,32 @@ export function getTotalMetalWeight( return Number(walletInfo?.treasure ?? walletInfo?.gold_sum ?? walletInfo?.count_gold ?? 0) } -/** فقط موجودی نقدی کیف اصلی — فیلد gold (مثل 0.3 کیلوگرم) جزو دارایی ریالی نیست */ +/** موجودی ریالی کیف اصلی — با main_wallet_products فیلد cash ممکن است کیف محصول باشد */ export function getRialCashAmount( walletInfo?: Record | null, wallets?: UserWalletLine[] | null ): number { + const aggregated = walletInfo?.count_cash_wallet ?? walletInfo?.count_cash + if (aggregated != null && aggregated !== '') { + const parsed = Number(aggregated) + if (!Number.isNaN(parsed)) { + return parsed + } + } + const mainCash = getMainWalletCash(wallets) if (mainCash > 0) { return mainCash } - return Number(walletInfo?.count_cash ?? walletInfo?.cash ?? 0) + return Number(walletInfo?.cash ?? 0) +} + +export function getCashWalletBalance( + walletInfo?: Record | null, + wallets?: UserWalletLine[] | null +): number { + return getRialCashAmount(walletInfo, wallets) } export function getTotalRialEquivalent(walletInfo?: Record | null): number { diff --git a/src/utils/productPrice.ts b/src/utils/productPrice.ts index 0f580de..11d2112 100644 --- a/src/utils/productPrice.ts +++ b/src/utils/productPrice.ts @@ -11,50 +11,84 @@ function applyPriceOffsetPercent(basePrice: number, offsetPercent?: number | nul return Math.round(basePrice * (1 + offset / 100)) } -/** قیمت خرید نمایشی محصول (همان مقداری که در لیست و اکشن‌شیت نشان داده می‌شود) */ -export function getProductBuyPrice(product: Product): number { +/** قیمت پایه بدون کارمزد — اگر صفر باشد نباید فقط cash_fee به‌عنوان قیمت نمایش داده شود */ +export function getProductBaseBuyPrice(product: Product): number { if (isMeltedProductUsingAppPrice(product)) { const tradeBuy = getTradeBuyPrice(lastPrice.value) - const base = tradeBuy > 0 - ? applyPriceOffsetPercent(tradeBuy, product.price_offset_percent) - : (product.price ?? 0) - return base + (product.cash_fee_buy ?? 0) + if (tradeBuy > 0) { + return applyPriceOffsetPercent(tradeBuy, product.price_offset_percent) + } + // provider صفر — آخرین قیمت ذخیره‌شده از API (وقتی use_last_price_if_zero فعال است) + return Number(product.price ?? 0) } if (isMeltedTradeGroup(product.group_type)) { - return (product.price ?? 0) + (product.cash_fee_buy ?? 0) + return Number(product.price ?? 0) } if (isMetalCommodityGroup(product.group_type)) { - const base = (product.price_profit ?? 0) + (product.price ?? 0) - return base + (product.cash_fee_buy ?? 0) + return Number(product.price_profit ?? 0) + Number(product.price ?? 0) } - const basePrice = (product.price ?? 0) + (product.cash_fee_buy ?? 0) - return product.direct_price === 0 - ? basePrice * (product.amount ?? 0) - : basePrice + return Number(product.price ?? 0) } -/** قیمت فروش نمایشی محصول آبشده‌ای */ -export function getProductSellPrice(product: Product): number { +/** قیمت خرید نمایشی محصول (همان مقداری که در لیست و اکشن‌شیت نشان داده می‌شود) */ +export function getProductBuyPrice(product: Product): number { + const base = getProductBaseBuyPrice(product) + if (base <= 0) { + return 0 + } + + const fee = Number(product.cash_fee_buy ?? 0) + + if ( + isMeltedProductUsingAppPrice(product) + || isMeltedTradeGroup(product.group_type) + || isMetalCommodityGroup(product.group_type) + ) { + return base + fee + } + + const withFee = base + fee + return product.direct_price === 0 + ? withFee * Number(product.amount ?? 0) + : withFee +} + +function getProductBaseSellPrice(product: Product): number { if (isMeltedProductUsingAppPrice(product)) { const tradeSell = getTradeSellPrice(lastPrice.value) - const base = tradeSell > 0 - ? applyPriceOffsetPercent(tradeSell, product.price_offset_percent) - : (product.price ?? 0) - return base - (product.cash_fee_sell ?? 0) + if (tradeSell > 0) { + return applyPriceOffsetPercent(tradeSell, product.price_offset_percent) + } + // provider صفر — همان آخرین قیمت ذخیره‌شده (فروش از همان مبنا) + return Number(product.price ?? 0) } if (isMeltedTradeGroup(product.group_type)) { - return (product.price ?? 0) - (product.cash_fee_sell ?? 0) + return Number(product.price ?? 0) } return getTradeSellPrice(lastPrice.value) } +/** قیمت فروش نمایشی محصول آبشده‌ای */ +export function getProductSellPrice(product: Product): number { + const base = getProductBaseSellPrice(product) + if (base <= 0) { + return 0 + } + + if (isMeltedProductUsingAppPrice(product) || isMeltedTradeGroup(product.group_type)) { + return base - Number(product.cash_fee_sell ?? 0) + } + + return base +} + export function isProductPriceInactive(product: Product): boolean { - return getProductBuyPrice(product) <= 0 + return getProductBaseBuyPrice(product) <= 0 } export { isMeltedTradeGroup, isMeltedProductUsingAppPrice } from '@/utils/productCategories' diff --git a/src/views/base/copper&silver/index.vue b/src/views/base/copper&silver/index.vue index a299c29..65369b3 100644 --- a/src/views/base/copper&silver/index.vue +++ b/src/views/base/copper&silver/index.vue @@ -68,14 +68,14 @@ -->
- {{ formatCashWalletBalanceText(usePriceWithoutType(walletStore?.walletInfo?.cash ?? 0), props?.ProductItem?.group_type) }} + {{ formatCashWalletBalanceText(usePriceWithoutType(cashWalletBalance), props?.ProductItem?.group_type) }}
+ getCashWalletBalance(walletStore.walletInfo, userStore.userInfo?.wallets) +) const fundStore = useFundUserStore() const confirmAction = ref(false) const noCompleteVisible = ref(false) @@ -490,7 +494,7 @@ const unitGoldCalculator = computed(() => { }) const unitCashCalculator = computed(() => { - return (walletStore?.walletInfo?.cash ?? 0) / 100 + return cashWalletBalance.value / 100 }) const limitedPrice = computed(() => { diff --git a/src/views/base/gold/index.vue b/src/views/base/gold/index.vue index a2399aa..e9431df 100644 --- a/src/views/base/gold/index.vue +++ b/src/views/base/gold/index.vue @@ -70,14 +70,14 @@ -->
- {{ formatCashWalletBalanceText(usePriceWithoutType(walletStore?.walletInfo?.cash ?? 0), props?.ProductItem?.group_type) }} + {{ formatCashWalletBalanceText(usePriceWithoutType(cashWalletBalance), props?.ProductItem?.group_type) }}
-
موجودی کیف پول ناکافی می باشد.
@@ -237,7 +237,7 @@ import { useFundUserStore } from '@/store/fundStore' import {useConfigStore} from "@/store/configStore"; import { calcFeeInGrams, loadServiceFeeFromSettings } from '@/utils/serviceFee' import { formatCashWalletBalanceText, getWeightUnit } from '@/utils/productDealMethod' -import { formatProductWalletBalanceText, getProductInventoryQuantity } from '@/utils/productAssets' +import { formatProductWalletBalanceText, getCashWalletBalance, getProductInventoryQuantity } from '@/utils/productAssets' import { useWalletAssets } from '@/composables/useWalletAssets' import { isMeltedTradeGroup } from '@/utils/productCategories' import { useSettingsStore } from '@/store/settingsStore' @@ -256,6 +256,9 @@ const { meltedGoldLabel } = storeToRefs(settingsStore) const weightUnit = computed(() => getWeightUnit()) const fundStore = useFundUserStore() const { assetsSummary, loadWalletAssets } = useWalletAssets() +const cashWalletBalance = computed(() => + getCashWalletBalance(walletStore.walletInfo, userStore.userInfo?.wallets) +) const confirmAction = ref(false) const noCompleteVisible = ref(false) const buyItems = ref([]) @@ -514,7 +517,7 @@ function percentChange(val: number) { const unitGoldCalculator = computed(() => productWalletBalance.value / 100) const unitCashCalculator = computed(() => { - return (walletStore?.walletInfo?.cash ?? 0) / 100 + return cashWalletBalance.value / 100 }) const limitedPrice = computed(() => { diff --git a/src/views/base/home/components/PriceStats.vue b/src/views/base/home/components/PriceStats.vue index 59ffe47..c0094fe 100644 --- a/src/views/base/home/components/PriceStats.vue +++ b/src/views/base/home/components/PriceStats.vue @@ -35,4 +35,4 @@ const props = defineProps<{ const highPrice = computed(() => props.price?.up?.buy ?? getTradeBuyPrice(props.price)) const lowPrice = computed(() => props.price?.up?.sell ?? getTradeSellPrice(props.price)) - \ No newline at end of file + diff --git a/src/views/base/home/composables/usePriceChart.ts b/src/views/base/home/composables/usePriceChart.ts index 9a56a33..c24be2f 100644 --- a/src/views/base/home/composables/usePriceChart.ts +++ b/src/views/base/home/composables/usePriceChart.ts @@ -30,4 +30,4 @@ export function usePriceChart() { } return { priceChartSeries, chartLoading, updateChartData, fetchPrices } -} \ No newline at end of file +} diff --git a/src/views/base/products/components/DealProduct.vue b/src/views/base/products/components/DealProduct.vue index 4a1b940..d41776c 100644 --- a/src/views/base/products/components/DealProduct.vue +++ b/src/views/base/products/components/DealProduct.vue @@ -296,7 +296,7 @@ import { productMinOrderCash } from '@/utils/productOrderLimit' import { useWalletAssets } from '@/composables/useWalletAssets' -import { formatProductWalletBalanceText, getProductInventoryQuantity } from '@/utils/productAssets' +import { formatProductWalletBalanceText, getCashWalletBalance, getProductInventoryQuantity } from '@/utils/productAssets' const typeDeal = computed(() => getProductDealMethodOptions(props?.ProductItem?.group_type, isBuy.value)) @@ -352,16 +352,20 @@ const buyMetalWalletBalanceText = computed(() => : goldWalletBalanceText.value ) -const cashWalletBalanceText = computed(() => - formatCashWalletBalanceText( - usePriceWithoutType(walletStore?.walletInfo?.cash ?? 0), - productGroupType.value - ) -) const infoVisible = ref(false) const walletStore = useWalletStore() const { assetsSummary, loadWalletAssets } = useWalletAssets() const userStore = useUserStore() +const cashWalletBalance = computed(() => + getCashWalletBalance(walletStore.walletInfo, userStore.userInfo?.wallets) +) + +const cashWalletBalanceText = computed(() => + formatCashWalletBalanceText( + usePriceWithoutType(cashWalletBalance.value), + productGroupType.value + ) +) const confirmAction = ref(false) const noCompleteVisible = ref(false) const buyItems = ref([]) @@ -622,7 +626,7 @@ const unitGoldCalculator = computed(() => { }) const unitCashCalculator = computed(() => { - return (walletStore?.walletInfo?.cash ?? 0) / 100 + return cashWalletBalance.value / 100 }) const limitedPrice = computed(() => { diff --git a/src/views/base/products/components/ProductItem.vue b/src/views/base/products/components/ProductItem.vue index db94b2f..8a36820 100644 --- a/src/views/base/products/components/ProductItem.vue +++ b/src/views/base/products/components/ProductItem.vue @@ -11,8 +11,7 @@ -
{{usePrice(goldPriceValue)}}
-
{{usePrice(price)}}
+
{{ usePrice(getProductBuyPrice(props.ProductItem)) }}
@@ -33,7 +32,6 @@ import {usePrice} from '@/utils/usePrice'; import {getFileUrl} from '@/utils/file'; import {computed, ref} from "vue"; import { getProductBuyPrice } from '@/utils/productPrice' -import { isMeltedTradeGroup } from '@/utils/productCategories' interface Prop { ProductItem: Product @@ -68,29 +66,6 @@ const displayImage = computed(() => { if (fileUrl) return fileUrl return defaultImages[props.ProductItem.group_type] ?? defaultImg }) -// const typeOfProduct=(val:string)=>{ -// if (val.includes('گوی')) -// return 'گوی' -// if (val.includes('شمش')) -// return 'شمش' -// if (val.includes('مکعب')) -// return 'مکعب' -// if (val.includes('24 عیار')) -// return '24 عیار' -// -// return 'آب شده' -// } - -const price = computed(()=>{ - if(props.ProductItem.direct_price === 0){ - return ((props.ProductItem.price ?? 0) + (props.ProductItem.cash_fee_buy ?? 0)) * props.ProductItem.amount - } - else { - return ((props.ProductItem.price ?? 0) + (props.ProductItem.cash_fee_buy ?? 0)) - } -}) - -const goldPriceValue = computed(() => getProductBuyPrice(props.ProductItem)) diff --git a/src/views/base/wallet/index.vue b/src/views/base/wallet/index.vue index ba91a48..f583afe 100644 --- a/src/views/base/wallet/index.vue +++ b/src/views/base/wallet/index.vue @@ -48,12 +48,12 @@
حداقل مبلغ برداشتی 100 هزار تومان می باشد.
- {{ formatCashWalletBalanceText(usePriceWithoutType(walletStore?.walletInfo?.cash ?? 0)) }} + {{ formatCashWalletBalanceText(usePriceWithoutType(cashWalletBalance)) }}
import { computed, onMounted, ref } from 'vue' import { formatCashWalletBalanceText } from '@/utils/productDealMethod' +import { getCashWalletBalance } from '@/utils/productAssets' import { usePriceWithoutType } from '@/utils/usePrice' import { loading, loadingMore } from '@/utils' import BasicButton from '@/components/BasicPacks/BasicButton.vue' @@ -413,6 +414,9 @@ import { const isDeposit = ref(true) const walletStore = useWalletStore() const userStore = useUserStore() +const cashWalletBalance = computed(() => + getCashWalletBalance(walletStore.walletInfo, userStore.userInfo?.wallets) +) const showAddCard = ref(false) const chargeAccountModal = ref(false) const showConfirmCode = ref(false) @@ -458,11 +462,11 @@ const cardForm = ref({ const disableButton = computed(() => { if (!loading.value) { - if (walletStore?.walletInfo?.cash && !isDeposit.value) { + if (cashWalletBalance.value && !isDeposit.value) { if (cardItems.value.length > 0) { return ( - walletStore?.walletInfo?.cash <= 0 || - walletStore?.walletInfo?.cash < parseInt(form.value?.value) || + cashWalletBalance.value <= 0 || + cashWalletBalance.value < parseInt(form.value?.value) || isNaN(parseInt(form.value?.value)) || parseInt(form.value?.value) < 1000000 ) @@ -477,7 +481,7 @@ const disableButton = computed(() => { const bganim = ref(null) const unitCalculator = computed(() => { - return (walletStore?.walletInfo?.cash ?? 0) / 100 + return cashWalletBalance.value / 100 }) function copyCode(val: string) { diff --git a/src/views/login/components/Register.vue b/src/views/login/components/Register.vue index 63f6858..676d2ff 100644 --- a/src/views/login/components/Register.vue +++ b/src/views/login/components/Register.vue @@ -170,7 +170,7 @@ import DialogVantOneButton from '@/components/DialogVantOneButton.vue' import { registerCustomer } from '@/api/login' import router from '@/router' import {useConfigStore} from "@/store/configStore"; -import {getProjectLoginLogo, getProjectLoginTaglineLines, hasCustomLoginLogoLayout} from "@/utils/config"; +import {getProjectLoginLogo, getProjectLoginTaglineLines, hasCustomLoginLogoLayout, isBoursecuProject} from "@/utils/config"; const config = useConfigStore() // const stepTwoVerify = ref(false) diff --git a/src/views/login/index.vue b/src/views/login/index.vue index f6635d6..7b19824 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -10,10 +10,10 @@
- + - +
From 13d2ce88052bb6eedf6770296bda21e68240f9bd Mon Sep 17 00:00:00 2001 From: adonis Date: Thu, 11 Jun 2026 21:49:46 +0330 Subject: [PATCH 3/8] fix bug 3 --- src/models/products.ts | 1 + src/utils/productPrice.ts | 54 +++++++++++++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/models/products.ts b/src/models/products.ts index 22cc688..c743fe0 100644 --- a/src/models/products.ts +++ b/src/models/products.ts @@ -25,6 +25,7 @@ export interface Product { max_order_cash?: number | null direct_price: number | undefined price_offset_percent?: number | null + use_last_price_if_zero?: boolean reading_method: any created_at: any updated_at: any diff --git a/src/utils/productPrice.ts b/src/utils/productPrice.ts index 11d2112..d8d2669 100644 --- a/src/utils/productPrice.ts +++ b/src/utils/productPrice.ts @@ -2,6 +2,10 @@ import type { Product } from '@/models/products' import { getTradeBuyPrice, getTradeSellPrice, lastPrice } from '@/utils/price' import { isMetalCommodityGroup, isMeltedProductUsingAppPrice, isMeltedTradeGroup } from '@/utils/productCategories' +function productUsesLastPriceIfZero(product: Product): boolean { + return Boolean(product.use_last_price_if_zero) +} + function applyPriceOffsetPercent(basePrice: number, offsetPercent?: number | null) { const offset = Number(offsetPercent ?? 0) if (!offset) { @@ -14,12 +18,27 @@ function applyPriceOffsetPercent(basePrice: number, offsetPercent?: number | nul /** قیمت پایه بدون کارمزد — اگر صفر باشد نباید فقط cash_fee به‌عنوان قیمت نمایش داده شود */ export function getProductBaseBuyPrice(product: Product): number { if (isMeltedProductUsingAppPrice(product)) { - const tradeBuy = getTradeBuyPrice(lastPrice.value) - if (tradeBuy > 0) { - return applyPriceOffsetPercent(tradeBuy, product.price_offset_percent) + const apiPrice = Number(product.price ?? 0) + const useLast = productUsesLastPriceIfZero(product) + + if (!useLast) { + const liveBuy = getTradeBuyPrice(lastPrice.value) + if (liveBuy > 0) { + return applyPriceOffsetPercent(liveBuy, product.price_offset_percent) + } + return 0 } - // provider صفر — آخرین قیمت ذخیره‌شده از API (وقتی use_last_price_if_zero فعال است) - return Number(product.price ?? 0) + + if (apiPrice > 0) { + return apiPrice + } + + const liveBuy = getTradeBuyPrice(lastPrice.value) + if (liveBuy > 0) { + return applyPriceOffsetPercent(liveBuy, product.price_offset_percent) + } + + return 0 } if (isMeltedTradeGroup(product.group_type)) { @@ -58,12 +77,27 @@ export function getProductBuyPrice(product: Product): number { function getProductBaseSellPrice(product: Product): number { if (isMeltedProductUsingAppPrice(product)) { - const tradeSell = getTradeSellPrice(lastPrice.value) - if (tradeSell > 0) { - return applyPriceOffsetPercent(tradeSell, product.price_offset_percent) + const apiPrice = Number(product.price ?? 0) + const useLast = productUsesLastPriceIfZero(product) + + if (!useLast) { + const liveSell = getTradeSellPrice(lastPrice.value) + if (liveSell > 0) { + return applyPriceOffsetPercent(liveSell, product.price_offset_percent) + } + return 0 } - // provider صفر — همان آخرین قیمت ذخیره‌شده (فروش از همان مبنا) - return Number(product.price ?? 0) + + if (apiPrice > 0) { + return apiPrice + } + + const liveSell = getTradeSellPrice(lastPrice.value) + if (liveSell > 0) { + return applyPriceOffsetPercent(liveSell, product.price_offset_percent) + } + + return 0 } if (isMeltedTradeGroup(product.group_type)) { From 8c5f48e05ac5114838ebe12a51ea0ff43147b8e0 Mon Sep 17 00:00:00 2001 From: adonis Date: Sat, 13 Jun 2026 12:21:18 +0330 Subject: [PATCH 4/8] store 5 --- src/models/home.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/models/home.ts b/src/models/home.ts index 8ccf275..82fd025 100644 --- a/src/models/home.ts +++ b/src/models/home.ts @@ -27,6 +27,5 @@ export interface Price { closed?: boolean time?: string main_product?: number - taban_gohar?: number montzar?: number } \ No newline at end of file From 1632b5cd02b024f319a3b6d37002b7c04e8a1381 Mon Sep 17 00:00:00 2001 From: adonis Date: Sat, 13 Jun 2026 13:52:47 +0330 Subject: [PATCH 5/8] kashkool ver 1 --- components.d.ts | 1 + src/App.vue | 90 ++++++++++--- src/utils/useDraggableFloatingButton.ts | 165 ++++++++++++++++++++++++ src/views/base/index.vue | 13 -- 4 files changed, 239 insertions(+), 30 deletions(-) create mode 100644 src/utils/useDraggableFloatingButton.ts diff --git a/components.d.ts b/components.d.ts index 2da2181..d71d442 100644 --- a/components.d.ts +++ b/components.d.ts @@ -8,6 +8,7 @@ export {} declare module 'vue' { export interface GlobalComponents { AButton: typeof import('ant-design-vue/es')['Button'] + AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider'] ADivider: typeof import('ant-design-vue/es')['Divider'] AForm: typeof import('ant-design-vue/es')['Form'] AFormItem: typeof import('ant-design-vue/es')['FormItem'] diff --git a/src/App.vue b/src/App.vue index 2738761..5778bc0 100644 --- a/src/App.vue +++ b/src/App.vue @@ -84,15 +84,24 @@
نسخه جدید منتشر شد!
- -
+
h(LoadingOutlined, { const showSplash = ref(true) const popVisible = ref(false) +const GAROOSI_SUPPORT_URL = 'https://ble.ir/garoosigallery' + +const supportLink = computed(() => { + if (config.config?.name === 'garoosi') { + return GAROOSI_SUPPORT_URL + } + return buildSupportMessengerUrl( + supportMessengerSettings.value.messenger, + supportMessengerSettings.value.messengerId + ) +}) + +const showSupportButton = computed(() => !!supportLink.value) + +const { + dragging: supportDragging, + fabStyle: supportButtonStyle, + handlePointerDown: onSupportPointerDown, + handlePointerMove: onSupportPointerMove, + handlePointerUp: supportPointerUp, + handlePointerCancel: onSupportPointerCancel +} = useDraggableFloatingButton('khaneghi_support_button_position', { + width: 110, + height: 44, + defaultBottomOffset: 120 +}) + +function openSupportLink() { + if (!supportLink.value) return + window.open(supportLink.value, '_blank', 'noopener,noreferrer') +} + +function handleSupportPointerUp(event: PointerEvent) { + if (supportPointerUp(event)) { + openSupportLink() + } +} + const antTheme = computed(() => { if (!config.config?.color) return null @@ -164,6 +217,7 @@ const antTheme = computed(() => { onMounted(async () => { await config.getConfigData() + await loadSupportMessengerFromSettings() console.log(config?.config?.color) checkVersion() setTimeout(() => { @@ -235,18 +289,20 @@ function update() { #support-button { position: fixed; - bottom: calc(80px + var(--app-credit-footer-offset, 28px) + 12px); - right: 20px; - width: 70px; - height: 70px; - //border-radius: 50%; - display: flex; - justify-content: center; - align-items: center; - //box-shadow: 0 4px 10px rgba(0,0,0,0.3); - cursor: pointer; - transition: transform 0.2s ease, box-shadow 0.2s ease; z-index: 9999; + cursor: grab; + user-select: none; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + border-radius: 0.5rem; + + &.is-dragging { + cursor: grabbing; + box-shadow: 0 8px 18px rgba(0, 0, 0, 0.22); + } + + img { + pointer-events: none; + } } @media screen and (min-width: 426px) { diff --git a/src/utils/useDraggableFloatingButton.ts b/src/utils/useDraggableFloatingButton.ts new file mode 100644 index 0000000..1bb61e8 --- /dev/null +++ b/src/utils/useDraggableFloatingButton.ts @@ -0,0 +1,165 @@ +import { computed, onMounted, onUnmounted, ref } from 'vue' + +const DRAG_THRESHOLD = 6 + +type FabPosition = { x: number; y: number } + +type DraggableFloatingOptions = { + width?: number + height?: number + defaultBottomOffset?: number + defaultRightOffset?: number +} + +export function useDraggableFloatingButton( + storageKey: string, + options: DraggableFloatingOptions = {} +) { + const width = options.width ?? 96 + const height = options.height ?? 44 + const defaultBottomOffset = options.defaultBottomOffset ?? 120 + const defaultRightOffset = options.defaultRightOffset ?? 20 + + const pos = ref({ x: 0, y: 0 }) + const dragging = ref(false) + + let pointerId: number | null = null + let startX = 0 + let startY = 0 + let originX = 0 + let originY = 0 + let hasMoved = false + + const fabStyle = computed(() => ({ + left: `${pos.value.x}px`, + top: `${pos.value.y}px`, + width: `${width}px`, + touchAction: 'none' + })) + + function getViewportWidth() { + return Math.min(window.innerWidth, 426) + } + + function getHorizontalOffset() { + const vw = window.innerWidth + const contentWidth = getViewportWidth() + return Math.max(0, (vw - contentWidth) / 2) + } + + function resetToDefault() { + const vh = window.innerHeight + const offsetX = getHorizontalOffset() + const contentWidth = getViewportWidth() + + pos.value = { + x: offsetX + contentWidth - width - defaultRightOffset, + y: vh - defaultBottomOffset - height + } + } + + function clampToViewport() { + const vw = window.innerWidth + const vh = window.innerHeight + + pos.value = { + x: Math.max(0, Math.min(vw - width, pos.value.x)), + y: Math.max(0, Math.min(vh - height, pos.value.y)) + } + } + + function loadPosition() { + try { + const raw = localStorage.getItem(storageKey) + if (!raw) return false + const parsed = JSON.parse(raw) as FabPosition + if (typeof parsed?.x === 'number' && typeof parsed?.y === 'number') { + pos.value = parsed + clampToViewport() + return true + } + } catch { + // ignore + } + return false + } + + function savePosition() { + localStorage.setItem(storageKey, JSON.stringify(pos.value)) + } + + function handleResize() { + clampToViewport() + savePosition() + } + + function handlePointerDown(event: PointerEvent) { + dragging.value = true + hasMoved = false + pointerId = event.pointerId + startX = event.clientX + startY = event.clientY + originX = pos.value.x + originY = pos.value.y + ;(event.currentTarget as HTMLElement)?.setPointerCapture?.(event.pointerId) + } + + function handlePointerMove(event: PointerEvent) { + if (!dragging.value || pointerId !== event.pointerId) return + + const dx = event.clientX - startX + const dy = event.clientY - startY + + if (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD) { + hasMoved = true + } + + pos.value = { + x: originX + dx, + y: originY + dy + } + clampToViewport() + } + + function finishPointer(event: PointerEvent) { + if (!dragging.value || pointerId !== event.pointerId) return false + + dragging.value = false + pointerId = null + savePosition() + + const clicked = !hasMoved + hasMoved = false + return clicked + } + + function handlePointerUp(event: PointerEvent) { + return finishPointer(event) + } + + function handlePointerCancel(event: PointerEvent) { + finishPointer(event) + return false + } + + onMounted(() => { + if (!loadPosition()) { + resetToDefault() + } + window.addEventListener('resize', handleResize) + }) + + onUnmounted(() => { + window.removeEventListener('resize', handleResize) + }) + + return { + dragging, + fabStyle, + handlePointerDown, + handlePointerMove, + handlePointerUp, + handlePointerCancel, + resetToDefault + } +} diff --git a/src/views/base/index.vue b/src/views/base/index.vue index 82f34f7..fde1d30 100644 --- a/src/views/base/index.vue +++ b/src/views/base/index.vue @@ -118,8 +118,6 @@ :src="getProjectNavLogo(config.config?.avatar)" />
-
-
شماره شبا: IR 080160000000001143804581
- - + -
-
همراه گرامی
-
-
جهت شارژ حساب کاربری خود،
-
مبلغ موردنظر را به شماره کارت زیر واریز نموده
-
و به شماره پشتیبانی 09121784258 در برنامه ی بله اطلاع دهید.
-
-
(گنجينه طلاي حاجي ابوالفتح - بانک ملی)
-
شماره کارت: 6037998800496928
-
شماره شبا: IR 110170000000372468150002
-
-
-
مشتریان گرامی
-
- {{ supportMessengerText }}
- +
+

راهنمای شارژ حساب

+

+ جهت شارژ حساب کاربری خود، مبلغ موردنظر را به شماره کارت زیر واریز نموده و به شماره پشتیبانی + 09121784258 + در برنامه بله اطلاع دهید. +

+
+
(گنجینه طلای حاجی ابوالفتح - بانک ملی)
+
شماره کارت: 6037998800496928
+
شماره شبا: IR 110170000000372468150002
+
+
+ +
+

راهنمای شارژ حساب

+

{{ supportMessengerText }}

+ پیام به پشتیبانی - +
-
-
مشتری گرامی
-
-
واریز مبلغ مورد نظر، نهایتاً ظرف مدت ۲۴ ساعت آینده انجام می‌پذیرد.
+
+
+ +
+
+

زمان واریز برداشت

+

+ واریز مبلغ مورد نظر، نهایتاً ظرف مدت ۲۴ ساعت آینده انجام می‌پذیرد. +

@@ -766,4 +786,112 @@ async function handleOnComplete(value: string) { margin: 0 auto; } } + +.wallet-info-modal__body { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 4px 8px 8px; +} + +.wallet-info-modal__icon { + display: flex; + align-items: center; + justify-content: center; + width: 56px; + height: 56px; + border-radius: 9999px; + background: #ecfdf5; + color: #059669; + margin-bottom: 14px; +} + +.wallet-info-modal__icon--info { + background: #eff6ff; + color: #2563eb; +} + +.wallet-info-modal__title { + margin: 0 0 10px; + font-size: 17px; + font-weight: 700; + color: #0f172a; + line-height: 1.5; +} + +.wallet-info-modal__text { + margin: 0; + font-size: 14px; + line-height: 1.85; + color: #475569; +} + +.wallet-info-modal__warn { + margin: 12px 0 0; + padding: 10px 12px; + border-radius: 10px; + background: #fef2f2; + color: #b91c1c; + font-size: 13px; + line-height: 1.7; +} + +.wallet-info-modal__card-box { + margin-top: 14px; + width: 100%; + padding: 12px 14px; + border-radius: 12px; + background: #f8fafc; + border: 1px solid #e2e8f0; + color: #334155; + font-size: 13px; + line-height: 1.8; + text-align: right; + direction: rtl; +} + +.wallet-info-modal__action { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + width: 100%; + margin-top: 16px; + padding: 11px 14px; + border-radius: 12px; + font-size: 14px; + font-weight: 600; + text-decoration: none; + transition: background-color 0.2s ease, border-color 0.2s ease; +} + +.wallet-info-modal__action--outline { + color: #0f766e; + background: #f0fdfa; + border: 1px solid #99f6e4; +} + +.wallet-info-modal__action--outline:hover { + background: #ccfbf1; + border-color: #5eead4; +} + +.wallet-info-modal__ok { + height: 44px !important; + border-radius: 12px !important; + font-weight: 600; +} + + + From 3254a6dadbabc741e1fda4dea23612c11e6e2dfa Mon Sep 17 00:00:00 2001 From: adonis Date: Sat, 13 Jun 2026 22:28:29 +0330 Subject: [PATCH 8/8] support ui changed 2 --- src/utils/supportMessenger.ts | 22 ++++++++++++++-------- src/views/base/wallet/index.vue | 14 +++----------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/utils/supportMessenger.ts b/src/utils/supportMessenger.ts index 947d74d..b6f648e 100644 --- a/src/utils/supportMessenger.ts +++ b/src/utils/supportMessenger.ts @@ -20,10 +20,16 @@ export const supportMessengerSettings = ref({}) export const DEFAULT_SUPPORT_MESSENGER_TEXT = 'جهت شارژ حساب کاربری خود، کلمه "شماره حساب" را برای پشتیبانی ارسال نمایید.' -function getActiveSettingValue(settings: Array<{ key?: string; is_active?: boolean; value?: unknown }>, key: string) { - const setting = settings.find((item) => item?.key === key && item?.is_active === true) - const value = setting?.value +function getSupportSettingValue( + settings: Array<{ key?: string; is_active?: boolean | number; value?: unknown }>, + key: string +) { + const setting = settings.find((item) => item?.key === key) + if (!setting) return '' + + const value = setting.value if (value === undefined || value === null) return '' + return String(value).trim() } @@ -33,7 +39,7 @@ export function buildSupportMessengerUrl(messenger?: string, messengerId?: strin if (/^https?:\/\//i.test(id)) return id - const type = (messenger ?? '').trim().toLowerCase() + const type = (messenger ?? '').trim().toLowerCase() || 'bale' const username = id.replace(/^@/, '') switch (type) { @@ -53,7 +59,7 @@ export function buildSupportMessengerUrl(messenger?: string, messengerId?: strin case 'soroush': return `https://splus.ir/splus/${username}` default: - return '' + return `https://ble.ir/${username}` } } @@ -63,9 +69,9 @@ export async function loadSupportMessengerFromSettings() { if (!settings || !Array.isArray(settings)) return supportMessengerSettings.value = { - messenger: getActiveSettingValue(settings, 'support_messenger'), - messengerId: getActiveSettingValue(settings, 'support_messenger_id'), - messengerText: getActiveSettingValue(settings, 'support_messenger_text'), + messenger: getSupportSettingValue(settings, 'support_messenger') || 'bale', + messengerId: getSupportSettingValue(settings, 'support_messenger_id'), + messengerText: getSupportSettingValue(settings, 'support_messenger_text'), } } catch (e) { console.log(e) diff --git a/src/views/base/wallet/index.vue b/src/views/base/wallet/index.vue index 12f234c..a13c755 100644 --- a/src/views/base/wallet/index.vue +++ b/src/views/base/wallet/index.vue @@ -326,19 +326,11 @@
+

راهنمای شارژ حساب