fix some bugs from ghatreh

This commit is contained in:
2026-09-13 16:32:37 +03:30
parent f6b1044a17
commit 6f725bc0ef
19 changed files with 219 additions and 106 deletions
+4 -1
View File
@@ -12,7 +12,7 @@ import {
setFingerTimeOut,
setHasFingerPrint
} from '@/app/authBoot'
import { recoverMobileViewportAfterAuth } from '@/layouts/composables/useMobileScrollReset'
import { clearVantOverlays, recoverMobileViewportAfterAuth } from '@/layouts/composables/useMobileScrollReset'
export function setupRouterGuards() {
const userStore = useUserStore()
@@ -180,6 +180,9 @@ export function setupRouterGuards() {
})
router.afterEach((to, from) => {
if (to.name === 'login') {
clearVantOverlays()
}
if (document.documentElement.classList.contains('app-layout--desktop')) return
if (from.name === 'login') {
recoverMobileViewportAfterAuth()
+4
View File
@@ -120,6 +120,10 @@ body {
pointer-events: auto !important;
}
.van-dialog {
z-index: var(--app-z-dialog, 5300) !important;
}
body.van-overflow-hidden {
.bottom-appbar,
.app-credit-footer,
+1 -13
View File
@@ -1,7 +1,6 @@
import { ref } from 'vue'
import { getProducts } from '@/api/products'
import type { Product } from '@/models/products'
import { getLastStreamProductsList } from '@/utils/productStreamBridge'
/** کش مشترک لیست محصولات — dedupe درخواست‌های polling */
const catalogProducts = ref<Product[]>([])
@@ -12,22 +11,11 @@ export function getProductsCatalogRef() {
}
export function findProductInCatalog(id: number): Product | undefined {
const cached = catalogProducts.value.find((p) => p.id === id)
if (cached) {
return cached
}
return getLastStreamProductsList()?.find((p) => p.id === id)
return catalogProducts.value.find((p) => p.id === id)
}
export function getProductsCatalog(force = false): Promise<Product[]> {
if (!force) {
const streamed = getLastStreamProductsList()
if (streamed?.length) {
catalogProducts.value = streamed
return Promise.resolve(streamed)
}
if (catalogProducts.value.length > 0) {
return Promise.resolve(catalogProducts.value)
}
@@ -41,6 +41,12 @@ function clearBodyScrollLock() {
}
}
/** leftover van-dialog overlay after logout/navigation — blocks all clicks */
export function clearVantOverlays() {
document.querySelectorAll('body > .van-overlay').forEach((el) => el.remove())
clearBodyScrollLock()
}
function resetAllScrollPositions() {
window.scrollTo(0, 0)
document.documentElement.scrollTop = 0
+1
View File
@@ -47,6 +47,7 @@ const {
watch(
() => route.fullPath,
() => {
closeOverflowMenu()
resetMobileAppScroll()
},
{ flush: 'post' },
+5 -2
View File
@@ -2,12 +2,15 @@
<Transition>
<div
v-if="show"
class="top-0 bottom-0 bg-black wrap-fix z-5000 opacity-60"
class="top-0 bottom-0 bg-black wrap-fix z-5200 opacity-60"
@click="$emit('close')"
/>
</Transition>
<Transition name="slide-fade">
<div v-if="show" class="wrap-fix bottom-11vh z-5000 flex justify-center items-end">
<div
v-if="show"
class="wrap-fix z-5200 flex justify-center items-end bottom-[calc(var(--app-bottom-chrome-height)+1.5rem)]"
>
<div class="relative flex flex-col items-center">
<div
class="flex items-center justify-center bg-white rounded-full w-[30px] h-[30px] mb-2"
@@ -53,6 +53,9 @@
&__title {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__trailing {
+5
View File
@@ -234,6 +234,11 @@ export function getProductSellPrice(product?: Product | null): number {
return base - resolveProductCashFeeSellRial(product, base)
}
/** کارت فروشگاه: قیمت نمایشی صفر → «بدون قیمت» */
export function isProductWithoutDisplayPrice(product?: Product | null): boolean {
return getProductBuyPrice(product) <= 0
}
export function isProductPriceInactive(product?: Product | null): boolean {
if (!product) {
return true
+4 -2
View File
@@ -9,12 +9,14 @@ function asBool(value: boolean | number | null | undefined) {
export function isProductBuyOpen(product: TradeOpenProduct) {
if (!product) return true
return asBool(product?.open_buy)
if (product.open_buy === undefined || product.open_buy === null) return true
return asBool(product.open_buy)
}
export function isProductSellOpen(product: TradeOpenProduct) {
if (!product) return true
return asBool(product?.open_sell)
if (product.open_sell === undefined || product.open_sell === null) return true
return asBool(product.open_sell)
}
export function isAnyProductTradeOpen(product: TradeOpenProduct) {
@@ -1,5 +1,5 @@
<template>
<DialogVant v-model:show="visible" :closeOnClickOverlay="false" :timeOut="-1">
<DialogVant v-model:show="visible" :closeOnClickOverlay="true" :timeOut="-1">
<template v-slot:title>
<div class="flex flex-col items-center">
<DotLottieVue
+7 -10
View File
@@ -337,7 +337,7 @@
</div>
<AppHeader @logout="showLogoutConfirm = true" />
</div>
<DialogVant v-model:show="showLogoutConfirm" :closeOnClickOverlay="false" :timeOut="-1">
<DialogVant v-model:show="showLogoutConfirm" :closeOnClickOverlay="true" :timeOut="-1">
<!-- <template v-slot:img>
<img :src="registerDelayImage" alt="" class="opacity-80">
</template>-->
@@ -411,7 +411,7 @@ import 'swiper/swiper-bundle.css';
import img2 from '@/assets/img/banner/2.png'
import Icon from '@/components/BasicPacks/BasicIcon.vue'
import { usePrice, usePriceWithoutType, useTowDecimal, formatGoldAmount } from '../../../utils/usePrice'
import {computed, createVNode, onActivated, onMounted, onUnmounted, ref, watch} from 'vue'
import {computed, createVNode, nextTick, onActivated, onMounted, onUnmounted, ref, watch} from 'vue'
import router from '@/router'
import * as echarts from 'echarts'
import { useIntersectionObserver } from '@vueuse/core'
@@ -443,6 +443,7 @@ import type { PInventory, Product } from '@/models/products'
import { useWalletAssets } from '@/composables/useWalletAssets'
import { resolveAssetsRialValue } from '@/utils/productAssets'
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
import { clearVantOverlays } from '@/layouts/composables/useMobileScrollReset'
import { message, Modal, Divider } from 'ant-design-vue'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import BasicModal from '@/components/BasicPacks/BasicModal.vue'
@@ -864,17 +865,13 @@ const showLogoutConfirm = ref<boolean>()
async function logout() {
logoutLoading.value = true
// baseConfirm('آیا از حساب کاربری خارج می شوید؟',async () => {
showLogoutConfirm.value = false
userStore.userInfo = undefined
walletStore.walletInfo = undefined
/* userStore.$reset()
walletStore.$reset()*/
removeToken()
await router.push({ name: 'login' })
logoutLoading.value = true
window.location.reload()
// console.log('salam')
// })
await nextTick()
clearVantOverlays()
window.location.assign(router.resolve({ name: 'login' }).href)
}
async function openGoldModal() {
@@ -64,8 +64,10 @@
<template>
<div
class="product-card relative flex cursor-pointer rounded-xl border border-gray-200 bg-white p-3 transition-all duration-300 hover:-translate-y-0.5 hover:shadow-lg"
:class="!isProductTradingHoursOpen(props.ProductItem) && 'opacity-50 grayscale'"
class="product-card relative flex rounded-xl border border-gray-200 bg-white p-3 transition-all duration-300"
:class="isDimmed
? 'cursor-not-allowed opacity-50 grayscale pointer-events-none'
: 'cursor-pointer hover:-translate-y-0.5 hover:shadow-lg'"
>
<span
v-if="isOutOfStock"
@@ -93,8 +95,11 @@
</div>
</div>
<div class="mt-2 !text-primary text-sm font-bold">
{{ usePrice(getProductBuyPrice(props.ProductItem)) }}
<div
class="mt-2 text-sm font-bold"
:class="hasNoPrice ? 'text-gray-400' : '!text-primary'"
>
{{ hasNoPrice ? 'بدون قیمت' : usePrice(getProductBuyPrice(props.ProductItem)) }}
</div>
</div>
</div>
@@ -104,7 +109,7 @@
import { computed } from 'vue'
import type { Product } from '@/models/products'
import { usePrice } from '@/utils/usePrice'
import { getProductBuyPrice } from '@/utils/productPrice'
import { getProductBuyPrice, isProductWithoutDisplayPrice } from '@/utils/productPrice'
import { isProductOutOfStock } from '@/utils/productCategories'
import { isProductTradingHoursOpen } from '@/utils/productTradeOpen'
import ProductImage from '@/components/ProductImage.vue'
@@ -116,6 +121,10 @@ interface Props {
const props = defineProps<Props>()
const isOutOfStock = computed(() => isProductOutOfStock(props.ProductItem))
const hasNoPrice = computed(() => isProductWithoutDisplayPrice(props.ProductItem))
const isDimmed = computed(
() => hasNoPrice.value || !isProductTradingHoursOpen(props.ProductItem),
)
const amountProduct = computed(() => {
if (!props.ProductItem.amount) return ''
@@ -1,7 +1,9 @@
<template>
<div
class="product-card relative flex h-full flex-col rounded-xl border border-gray-200 bg-white p-3 transition-all duration-300 hover:-translate-y-1 hover:shadow-lg cursor-pointer"
:class="!isProductTradingHoursOpen(props.ProductItem) && 'opacity-50 grayscale'"
class="product-card relative flex h-full flex-col rounded-xl border border-gray-200 bg-white p-3 transition-all duration-300"
:class="isDimmed
? 'cursor-not-allowed opacity-50 grayscale pointer-events-none'
: 'cursor-pointer hover:-translate-y-1 hover:shadow-lg'"
>
<span
v-if="isOutOfStock"
@@ -26,8 +28,11 @@
{{ amountProduct }}
</div>
<div class="mt-auto !text-primary text-sm font-bold">
{{ usePrice(getProductBuyPrice(props.ProductItem)) }}
<div
class="mt-auto text-sm font-bold"
:class="hasNoPrice ? 'text-gray-400' : '!text-primary'"
>
{{ hasNoPrice ? 'بدون قیمت' : usePrice(getProductBuyPrice(props.ProductItem)) }}
</div>
</div>
</template>
@@ -36,7 +41,7 @@
import { computed } from 'vue'
import type { Product } from '@/models/products'
import { usePrice } from '@/utils/usePrice'
import { getProductBuyPrice } from '@/utils/productPrice'
import { getProductBuyPrice, isProductWithoutDisplayPrice } from '@/utils/productPrice'
import { isProductOutOfStock } from '@/utils/productCategories'
import { isProductTradingHoursOpen } from '@/utils/productTradeOpen'
import ProductImage from '@/components/ProductImage.vue'
@@ -48,6 +53,10 @@ interface Props {
const props = defineProps<Props>()
const isOutOfStock = computed(() => isProductOutOfStock(props.ProductItem))
const hasNoPrice = computed(() => isProductWithoutDisplayPrice(props.ProductItem))
const isDimmed = computed(
() => hasNoPrice.value || !isProductTradingHoursOpen(props.ProductItem),
)
const amountProduct = computed(() => {
if (!props.ProductItem.amount) return ''
@@ -10,7 +10,7 @@ import {
priceWssEnabled,
shouldUsePricePolling,
} from '@/utils/priceSocketState'
import { subscribeProductListStream } from '@/utils/productStreamBridge'
import { getLastStreamProductsList, subscribeProductListStream } from '@/utils/productStreamBridge'
let initPromise: Promise<void> | null = null
let productListInterval: ReturnType<typeof setInterval> | undefined
@@ -96,9 +96,15 @@ function mergeLiveProductList(incoming: Product[], current?: Product[]): Product
const prevId = productFileId(prev)
const nextId = productFileId(item)
const keepTradeFlags = {
open_buy: 'open_buy' in item ? item.open_buy : prev.open_buy,
open_sell: 'open_sell' in item ? item.open_sell : prev.open_sell,
}
if (!productHasDisplayImage(item) && productHasDisplayImage(prev)) {
return {
...item,
...keepTradeFlags,
file: prev.file,
file_id: prev.file_id ?? (prev.file as { id?: number } | undefined)?.id ?? item.file_id,
img: prev.img,
@@ -108,13 +114,14 @@ function mergeLiveProductList(incoming: Product[], current?: Product[]): Product
if (prevId > nextId) {
return {
...item,
...keepTradeFlags,
file: prev.file,
file_id: prev.file_id,
img: prev.img,
}
}
return item
return { ...item, ...keepTradeFlags }
})
}
@@ -302,11 +309,9 @@ export function onProductsPageProviderUnmounted() {
}
export async function initProductsPageData(force = false) {
if (!force && initialized.value) {
if (!force && initialized.value && productsFirstItems.value?.length) {
syncProductListPolling()
if (productsFirstItems.value?.length) {
productsPageLoading.value = false
}
productsPageLoading.value = false
return
}
@@ -320,10 +325,13 @@ export async function initProductsPageData(force = false) {
productsPageLoading.value = true
}
await loadCategoryTabs()
await refreshProductsPageData()
// stream-first: polling فقط وقتی SSE قطع/غیرفعال است
if (shouldUsePricePolling()) {
await refreshProductsPageData()
if (!productsFirstItems.value?.length) {
const streamed = getLastStreamProductsList()
if (streamed?.length) {
applyProductsList(streamed)
}
}
initialized.value = true
@@ -331,11 +339,7 @@ export async function initProductsPageData(force = false) {
} catch (error) {
console.error('Products page initialization error:', error)
} finally {
// اگر استریم قبل از init لیست را داده، loading را دوباره true نکرده‌ایم؛
// اگر API/polling تمام شد یا محصول داریم، اسپینر را قطع کن.
if (productsFirstItems.value?.length || shouldUsePricePolling()) {
productsPageLoading.value = false
}
productsPageLoading.value = false
initPromise = null
}
})()
+2 -1
View File
@@ -79,7 +79,7 @@ import img from '@/assets/img/user_ly1.svg'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import ProductItemVertical from '@/views/base/products/components/ProductItemVertical.vue'
import MobileProductsView from '@/views/base/products/mobile/MobileProductsView.vue'
import { isProductVisible } from '@/utils/productPrice'
import { isProductVisible, isProductWithoutDisplayPrice } from '@/utils/productPrice'
import { isProductTradingHoursOpen } from '@/utils/productTradeOpen'
import {
productsActiveKey,
@@ -105,6 +105,7 @@ const visibleTypeItems = computed(() => {
function clickProduct(item: Product) {
if (!isProductTradingHoursOpen(item)) return
if (isProductWithoutDisplayPrice(item)) return
if (isProductVisible(item)) {
router.push({ name: 'productDetail', params: { id: item.id } })
}
@@ -7,7 +7,7 @@ import { findProductInCatalog, getProductsCatalog, getProductsCatalogRef } from
import { getLastPrices, stopPriceInterval } from '@/utils/price'
import { getProductBuyPrice } from '@/utils/productPrice'
import { isProductOutOfStock } from '@/utils/productCategories'
import { isProductTradingHoursOpen } from '@/utils/productTradeOpen'
import { isProductBuyOpen, isProductTradingHoursOpen } from '@/utils/productTradeOpen'
import { loading } from '@/utils'
import type { Product } from '@/models/products'
@@ -16,6 +16,7 @@ export interface ProductDetailPageContext {
showFilter: ReturnType<typeof ref<boolean>>
isOutOfStock: ReturnType<typeof computed<boolean>>
hoursOpen: ReturnType<typeof computed<boolean>>
canBuy: ReturnType<typeof computed<boolean>>
handleBuyClick: () => void
goToProduct: (id: number) => void
getProductBuyPrice: typeof getProductBuyPrice
@@ -37,6 +38,7 @@ export function useProductDetailPage() {
const isOutOfStock = computed(() => isProductOutOfStock(product.value))
const hoursOpen = computed(() => isProductTradingHoursOpen(product.value))
const canBuy = computed(() => isProductBuyOpen(product.value))
watch(productId, (newId, oldId) => {
if (newId && newId !== oldId) {
@@ -46,7 +48,7 @@ export function useProductDetailPage() {
})
watch(
() => getProductsCatalogRef().value.map((p) => `${p.id}:${p.trade_open}:${p.show}`).join('|'),
() => getProductsCatalogRef().value.map((p) => `${p.id}:${p.trade_open}:${p.show}:${p.open_buy}`).join('|'),
() => {
const current = product.value
if (!current?.id) return
@@ -56,6 +58,12 @@ export function useProductDetailPage() {
if (typeof live.trade_open === 'boolean') {
current.trade_open = live.trade_open
}
if (typeof live.open_buy === 'boolean') {
current.open_buy = live.open_buy
}
if (typeof live.open_sell === 'boolean') {
current.open_sell = live.open_sell
}
if (showFilter.value && !isProductTradingHoursOpen(current)) {
showFilter.value = false
}
@@ -65,9 +73,16 @@ export function useProductDetailPage() {
async function loadProduct() {
try {
loading.value = true
await getProductsCatalog()
let foundProduct = findProductInCatalog(Number(productId.value))
if (!foundProduct) foundProduct = await getProductById(Number(productId.value))
const id = Number(productId.value)
const [detail] = await Promise.all([
getProductById(id).catch(() => null),
getProductsCatalog(),
])
const fromCatalog = findProductInCatalog(id)
const foundProduct =
detail && typeof detail === 'object'
? { ...fromCatalog, ...detail }
: fromCatalog
if (!foundProduct) {
message.error('محصول یافت نشد')
await router.push({ name: 'buyProduct' })
@@ -110,6 +125,7 @@ export function useProductDetailPage() {
showFilter,
isOutOfStock,
hoursOpen,
canBuy,
handleBuyClick,
goToProduct,
getProductBuyPrice,
@@ -18,7 +18,7 @@
<div class="dproduct-info__price-row">
<span class="dproduct-info__price">{{ usePrice(getProductBuyPrice(product)) }}</span>
<BasicButton
v-if="hoursOpen && product.open_buy && !isOutOfStock"
v-if="hoursOpen && canBuy && !isOutOfStock"
type="primary"
size="large"
@click="handleBuyClick"
@@ -26,7 +26,7 @@
خرید محصول
</BasicButton>
<div v-else-if="!hoursOpen" class="text-gray-500">این محصول در حال حاضر در دسترس نیست</div>
<div v-else-if="!product.open_buy" class="text-gray-500">خرید این محصول در حال حاضر امکان پذیر نیست</div>
<div v-else-if="!canBuy" class="text-gray-500">خرید این محصول در حال حاضر امکان پذیر نیست</div>
<div v-else class="text-gray-500">ناموجود</div>
</div>
</div>
@@ -73,7 +73,7 @@ import { usePrice } from '@/utils/usePrice'
import { isProductOutOfStock } from '@/utils/productCategories'
import { useProductDetailPageContext } from '../composables/useProductDetailPage'
const { product, isOutOfStock, hoursOpen, handleBuyClick, goToProduct, getProductBuyPrice } = useProductDetailPageContext()
const { product, isOutOfStock, hoursOpen, canBuy, handleBuyClick, goToProduct, getProductBuyPrice } = useProductDetailPageContext()
</script>
<style scoped>
@@ -1,60 +1,88 @@
<template>
<div>
<div class="bottom-0 fixed top-0">
<img :src="img" alt="" class="w-inherit" :style="{ backgroundRepeat: 'repeat' }" />
<div class="relative">
<div class="mobile-page-bg" aria-hidden="true">
<img :src="img" alt="" />
</div>
<MobilePageHeader :title="product?.title || 'جزئیات محصول'" show-back />
<a-spin :spinning="loading">
<div class="!h-full overflow-y-scroll">
<div class="mx-5 pb-30 pt-5" style="color: #1b2b07">
<div class="bg-gray-100 rounded-lg w-full flex justify-center mb-4">
<ProductImage :product="product" :dimmed="isOutOfStock" img-class="max-w-full h-auto" />
<div class="mpage-content relative z-1 pb-[calc(var(--app-bottom-chrome-height)+1rem)]">
<div class="flex flex-col gap-3 px-5">
<div class="flex justify-center rounded-2xl border border-gray-200 bg-white p-5">
<ProductImage
:product="product"
:dimmed="isOutOfStock"
img-class="max-h-52 w-full object-contain"
/>
</div>
<div class="text-2xl font-bold mb-4">{{ product?.title }}</div>
<div class="flex items-center justify-between mb-4 gap-4">
<div class="text-xl font-semibold text-primary">
{{ usePrice(getProductBuyPrice(product)) }}
</div>
<div class="flex-1">
<BasicButton
v-if="hoursOpen && product?.open_buy && !isOutOfStock"
class="w-full py-3"
type="primary"
size="large"
@click="handleBuyClick"
<div class="flex flex-col gap-3 rounded-2xl border border-gray-200 bg-white p-4">
<div>
<div class="mb-1 text-xs text-gray-400">قیمت خرید</div>
<div
class="text-xl font-bold"
:class="hasNoPrice ? 'text-gray-400' : 'text-primary'"
>
خرید محصول
</BasicButton>
<div v-else-if="!hoursOpen" class="text-center text-gray-500 py-4">
این محصول در حال حاضر در دسترس نیست
{{ hasNoPrice ? 'بدون قیمت' : usePrice(getProductBuyPrice(product)) }}
</div>
<div v-else-if="!product?.open_buy" class="text-center text-gray-500 py-4">
خرید این محصول در حال حاضر امکان پذیر نیست
</div>
<div v-else class="text-center text-gray-500 py-4">ناموجود</div>
<div v-if="amountLabel" class="mt-1 text-xs text-gray-500">{{ amountLabel }}</div>
</div>
<BasicButton
v-if="hoursOpen && canBuy && !isOutOfStock && !hasNoPrice"
class="!w-full py-3"
type="primary"
size="large"
@click="handleBuyClick"
>
خرید محصول
</BasicButton>
<div
v-else
class="rounded-xl bg-gray-50 px-3 py-3 text-center text-sm text-gray-500"
>
{{ statusText }}
</div>
</div>
<div v-if="product?.description" class="mb-6">
<div class="text-lg font-semibold mb-2">توضیحات محصول:</div>
<div class="text-base text-gray-700 whitespace-pre-line">{{ product.description }}</div>
<div
v-if="product?.description"
class="flex flex-col gap-1.5 rounded-2xl border border-gray-200 bg-white p-4"
>
<div class="font-semibold">توضیحات محصول</div>
<div class="text-sm text-gray-600 whitespace-pre-line">{{ product.description }}</div>
</div>
<div v-if="product?.similar?.length" class="mt-8">
<div class="text-lg font-semibold mb-4">محصولات مشابه</div>
<swiper :slides-per-view="2.25" :space-between="16" class="similar-products-swiper">
<div v-if="product?.similar?.length" class="pt-1">
<div class="font-semibold">محصولات مشابه</div>
<div class="mb-3 text-xs text-gray-400">از همین دسته می‌توانید انتخاب کنید</div>
<swiper :slides-per-view="2.15" :space-between="12" class="similar-products-swiper">
<swiper-slide v-for="item in product.similar" :key="item.id">
<div class="bg-white rounded-lg shadow p-4 cursor-pointer h-full" @click="goToProduct(item.id)">
<div class="bg-gray-100 rounded-md flex justify-center p-4 mb-2">
<div
class="flex h-full flex-col rounded-2xl border border-gray-200 bg-white p-3"
:class="isProductWithoutDisplayPrice(item)
? 'pointer-events-none cursor-not-allowed opacity-50 grayscale'
: 'cursor-pointer'"
@click="selectSimilar(item)"
>
<div class="mb-2 flex h-20 items-center justify-center rounded-xl bg-gray-50">
<ProductImage
:product="item"
:dimmed="isProductOutOfStock(item)"
img-class="h-20 object-contain"
img-class="h-16 object-contain"
/>
</div>
<div class="text-sm font-semibold mb-1 line-clamp-2">{{ item.title }}</div>
<div class="text-sm text-primary font-bold mb-3">
{{ usePrice(getProductBuyPrice(item)) }}
<div class="mb-1 line-clamp-2 min-h-8 text-xs font-semibold">{{ item.title }}</div>
<div
class="mt-auto text-sm font-bold"
:class="isProductWithoutDisplayPrice(item) ? 'text-gray-400' : 'text-primary'"
>
{{
isProductWithoutDisplayPrice(item)
? 'بدون قیمت'
: usePrice(getProductBuyPrice(item))
}}
</div>
</div>
</swiper-slide>
@@ -67,17 +95,49 @@
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import img from '@/assets/img/user_ly1.svg'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import ProductImage from '@/components/ProductImage.vue'
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
import type { Product } from '@/models/products'
import { loading } from '@/utils'
import { isProductOutOfStock } from '@/utils/productCategories'
import { getProductBuyPrice, isProductWithoutDisplayPrice } from '@/utils/productPrice'
import { usePrice } from '@/utils/usePrice'
import { Swiper, SwiperSlide } from 'swiper/vue'
import 'swiper/swiper-bundle.css'
import { useProductDetailPageContext } from '../composables/useProductDetailPage'
const { product, isOutOfStock, hoursOpen, handleBuyClick, goToProduct, getProductBuyPrice } = useProductDetailPageContext()
const {
product,
isOutOfStock,
hoursOpen,
canBuy,
handleBuyClick,
goToProduct,
} = useProductDetailPageContext()
const hasNoPrice = computed(() => isProductWithoutDisplayPrice(product.value))
const amountLabel = computed(() => {
const amount = Number(product.value?.amount ?? 0)
if (!amount) return ''
if (amount < 1) return `${amount * 1000} سوت`
return `${amount} گرم`
})
const statusText = computed(() => {
if (hasNoPrice.value) return 'قیمت این محصول الان مشخص نیست'
if (!hoursOpen.value) return 'این محصول در حال حاضر در دسترس نیست'
if (!canBuy.value) return 'خرید این محصول در حال حاضر امکان پذیر نیست'
return 'ناموجود'
})
function selectSimilar(item: Product) {
if (isProductWithoutDisplayPrice(item)) return
goToProduct(item.id)
}
</script>
<style scoped>
@@ -85,7 +145,7 @@ const { product, isOutOfStock, hoursOpen, handleBuyClick, goToProduct, getProduc
color: #d3a233;
}
.similar-products-swiper {
padding-bottom: 10px;
padding-bottom: 4px;
}
.similar-products-swiper :deep(.swiper-slide) {
height: auto;
@@ -1,4 +1,4 @@
import { computed, inject, onMounted, provide, ref, watch, type InjectionKey } from 'vue'
import { computed, inject, nextTick, onMounted, provide, ref, watch, type InjectionKey } from 'vue'
import { loading, removeToken } from '@/utils'
import {
guideTypePassword,
@@ -18,6 +18,7 @@ import { useSettingsStore } from '@/store/settingsStore'
import { useSupportMessenger } from '@/composables/useSupportMessenger'
import { isUserSupportChatEnabled, unreadSupportCount } from '@/utils/chat'
import { openGoldDayCalculator } from '@/composables/useGoldDayCalculator'
import { clearVantOverlays } from '@/layouts/composables/useMobileScrollReset'
export interface UserMenuAction {
id: string
@@ -265,8 +266,9 @@ kh.kashkolgold.ir/login?id=${userStore.userInfo?.referral_id}`
walletStore.walletInfo = undefined
removeToken()
showLogoutConfirm.value = false
await router.push({ name: 'login' })
window.location.reload()
await nextTick()
clearVantOverlays()
window.location.assign(router.resolve({ name: 'login' }).href)
}
onMounted(() => {