From 0109f3a744fa05683f658e189415610fe426ecfe Mon Sep 17 00:00:00 2001 From: safavimohadeseh78 Date: Sat, 11 Jul 2026 12:42:56 +0200 Subject: [PATCH] Unify store-closed messaging when the market is closed or has no quote. Treat missing quotes as closed across home and products, show only the admin closed_message as hint, and block trading with consistent notices. Co-authored-by: Cursor --- components.d.ts | 4 - src/utils/storeHours.ts | 89 ++++++++++++------- src/views/base/copper&silver/index.vue | 6 +- src/views/base/gold/index.vue | 6 +- .../base/home/components/CurrentPrice.vue | 13 ++- .../base/home/composables/useHomePage.ts | 4 +- .../desktop/components/DesktopLivePrice.vue | 13 ++- .../base/products/components/DealProduct.vue | 6 +- .../products/composables/useProductsPage.ts | 16 ++-- .../products/desktop/DesktopProductsView.vue | 8 +- src/views/base/products/index.vue | 8 +- .../products/mobile/MobileProductsView.vue | 8 +- 12 files changed, 99 insertions(+), 82 deletions(-) diff --git a/components.d.ts b/components.d.ts index 751269f..43ea738 100644 --- a/components.d.ts +++ b/components.d.ts @@ -15,16 +15,13 @@ declare module 'vue' { AInputPassword: typeof import('ant-design-vue/es')['InputPassword'] AModal: typeof import('ant-design-vue/es')['Modal'] AppCreditLink: typeof import('./src/components/AppCreditLink.vue')['default'] - ARadio: typeof import('ant-design-vue/es')['Radio'] ARadioButton: typeof import('ant-design-vue/es')['RadioButton'] ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] ASelect: typeof import('ant-design-vue/es')['Select'] ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] ASlider: typeof import('ant-design-vue/es')['Slider'] ASpin: typeof import('ant-design-vue/es')['Spin'] - ASwitch: typeof import('ant-design-vue/es')['Switch'] ATag: typeof import('ant-design-vue/es')['Tag'] - ATextarea: typeof import('ant-design-vue/es')['Textarea'] ATooltip: typeof import('ant-design-vue/es')['Tooltip'] BaseDatePicker: typeof import('./src/components/BasicPacks/BaseDatePicker/src/BaseDatePicker.vue')['default'] Basic: typeof import('./src/components/BasicPacks/BaseDatePicker/src/datePick/demo/basic.vue')['default'] @@ -70,6 +67,5 @@ 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/storeHours.ts b/src/utils/storeHours.ts index 5232cc3..ecad229 100644 --- a/src/utils/storeHours.ts +++ b/src/utils/storeHours.ts @@ -128,39 +128,69 @@ export function isStoreClosedBySchedule(now = new Date()): boolean { return false } + return isOutsideStoreTradingHours(now, start, end) +} + +export function isOutsideStoreTradingHours( + now = new Date(), + start?: string, + end?: string, +): boolean { + const hours = start != null && end != null + ? { start, end } + : getEffectiveTradingHours() + const currentSeconds = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds() - const openSeconds = parseTimeToSeconds(start) - const closeSeconds = parseTimeToSeconds(end) + const openSeconds = parseTimeToSeconds(hours.start) + const closeSeconds = parseTimeToSeconds(hours.end) return currentSeconds < openSeconds || currentSeconds > closeSeconds } +export function hasLivePriceQuote(): boolean { + const p = lastPrice.value + return getTradeBuyPrice(p) > 0 || getTradeSellPrice(p) > 0 +} + export function isStoreClosed(): boolean { void productTradingHoursVersion.value - const { closePriceEnabled } = getStoreTradingHours() - if (closePriceEnabled) { - if (lastPrice.value?.closed === true) { - return true - } - - return isStoreClosedBySchedule() + if (lastPrice.value?.closed === true) { + return true } - // وقتی close_price خاموش است، صفر بودن قیمت مرجع به معنی بسته بودن فروشگاه نیست. return isStoreClosedBySchedule() } -export function getStoreClosedMessage(): string { - const { start, end } = getEffectiveTradingHours() - const open = formatTradingTimeDisplay(start) - const close = formatTradingTimeDisplay(end) +export const STORE_CLOSED_TITLE = 'فروشگاه بسته است' - if (!open || !close) { - return 'فروشگاه در حال حاضر بسته است.' +export function stripHtmlMessage(message: string): string { + return message.replace(/<[^>]*>/g, '').trim() +} + +export function getStoreClosedHint(customMessage?: unknown): string { + const custom = typeof customMessage === 'string' ? customMessage.trim() : '' + if (custom) { + return stripHtmlMessage(custom) } + return '' +} - return `فروشگاه در حال حاضر بسته است. ساعت کاری از ${open} تا ${close} می‌باشد.` +export function getStoreClosedMessage(): string { + return '' +} + +/** برای toast/اعلان: پیام ادمین یا عنوان پیش‌فرض */ +export function resolveStoreClosedNotice(customMessage?: unknown): string { + return getStoreClosedHint(customMessage) || STORE_CLOSED_TITLE +} + +export function isMarketClosedForDisplay(): boolean { + return getPriceDisplayState() === 'closed' +} + +export function shouldBlockStoreTrading(): boolean { + return isMarketClosedForDisplay() } export type PriceDisplayState = 'open' | 'closed' | 'no_quote' @@ -168,34 +198,27 @@ export type PriceDisplayState = 'open' | 'closed' | 'no_quote' export const NO_QUOTE_MESSAGE = 'مظنه در دسترس نیست' /** - * وضعیت نمایش قیمت در صفحه اصلی: - * - closed → فروشگاه بسته است (خارج از ساعت کاری close_price یا بازه محصول) - * - no_quote → مظنه در دسترس نیست (قیمت زنده نداریم و show_last_price خاموش است) - * - open → نمایش عادی قیمت - * - * اولویت با status ارسالی از سرور است؛ اگر نبود به منطق سمت کلاینت برمی‌گردیم. + * وضعیت نمایش قیمت: + * - closed → فروشگاه بسته است (ساعت کاری، سیگنال سرور، یا نبود مظنه) + * - open → نمایش عادی قیمت */ export function getPriceDisplayState(): PriceDisplayState { void productTradingHoursVersion.value const p = lastPrice.value - const status = p?.status - if (status === 'closed') { + if (p?.status === 'closed' || p?.closed === true) { return 'closed' } - if (status === 'no_quote') { - return getTradeBuyPrice(p) > 0 ? 'open' : 'no_quote' - } - if (status === 'open') { - return getTradeBuyPrice(p) > 0 || getTradeSellPrice(p) > 0 ? 'open' : 'no_quote' - } - // سرور status نفرستاده (سازگاری با نسخه قدیمی) → استنتاج سمت کلاینت if (isStoreClosed()) { return 'closed' } - return getTradeBuyPrice(p) > 0 || getTradeSellPrice(p) > 0 ? 'open' : 'no_quote' + if (!hasLivePriceQuote()) { + return 'closed' + } + + return 'open' } export function getPriceDisplayMessage(state: PriceDisplayState = getPriceDisplayState()): string { diff --git a/src/views/base/copper&silver/index.vue b/src/views/base/copper&silver/index.vue index 58dafe6..89f9bba 100644 --- a/src/views/base/copper&silver/index.vue +++ b/src/views/base/copper&silver/index.vue @@ -242,7 +242,7 @@ import { calcFeeInGrams, calcPayablePriceRial, calcSellGoldDebit, feeDeductFromG import { resolveProductCashFeeBuyRial, resolveProductCashFeeSellRial } from '@/utils/productCashFee' import { formatCashWalletBalanceText, formatGoldWalletBalanceText } from '@/utils/productDealMethod' import { getCashWalletBalance } from '@/utils/productAssets' -import { isStoreClosed, getStoreClosedMessage } from '@/utils/storeHours' +import { isStoreClosed, resolveStoreClosedNotice } from '@/utils/storeHours' import { resetTransactionsPageData } from '@/views/base/transactions/composables/useTransactionsPageData' const infoVisible = ref(false) @@ -663,7 +663,7 @@ async function initPrice() { function okConfirm() { if (isStoreClosed()) { - message.warning(getStoreClosedMessage()) + message.warning(resolveStoreClosedNotice(userStore.getValueByKey('closed_message'))) return } confirmAction.value = true @@ -671,7 +671,7 @@ function okConfirm() { async function ok() { if (isStoreClosed()) { - message.warning(getStoreClosedMessage()) + message.warning(resolveStoreClosedNotice(userStore.getValueByKey('closed_message'))) return } try { diff --git a/src/views/base/gold/index.vue b/src/views/base/gold/index.vue index ad23ae1..0134c65 100644 --- a/src/views/base/gold/index.vue +++ b/src/views/base/gold/index.vue @@ -346,7 +346,7 @@ import { serviceFeePercentage, type TradeInputLock, } from '@/utils/serviceFee' -import { isStoreClosed, getStoreClosedMessage } from '@/utils/storeHours' +import { isStoreClosed, resolveStoreClosedNotice } from '@/utils/storeHours' import { resetTransactionsPageData } from '@/views/base/transactions/composables/useTransactionsPageData' import { formatCashWalletBalanceText, getWeightUnit } from '@/utils/productDealMethod' import { formatProductWalletBalanceText, getCashWalletBalance, getProductInventoryQuantity } from '@/utils/productAssets' @@ -776,7 +776,7 @@ async function initPrice() { function okConfirm() { if (isStoreClosed()) { - message.warning(getStoreClosedMessage()) + message.warning(resolveStoreClosedNotice(userStore.getValueByKey('closed_message'))) return } if (cashValidationMessage.value) { @@ -791,7 +791,7 @@ async function ok() { return } if (isStoreClosed()) { - message.warning(getStoreClosedMessage()) + message.warning(resolveStoreClosedNotice(userStore.getValueByKey('closed_message'))) return } try { diff --git a/src/views/base/home/components/CurrentPrice.vue b/src/views/base/home/components/CurrentPrice.vue index 7c8dce9..46c41d8 100644 --- a/src/views/base/home/components/CurrentPrice.vue +++ b/src/views/base/home/components/CurrentPrice.vue @@ -13,7 +13,7 @@ />
{{ statusTitle }}
-
{{ statusHint }}
+
{{ statusHint }}
@@ -55,8 +55,9 @@ import { useUserStore } from '@/store/userStore' import { getTradeBuyPrice, lastPrice, percentPriceChanged, price } from '@/utils/price' import { NO_QUOTE_MESSAGE, + STORE_CLOSED_TITLE, getPriceDisplayState, - getStoreClosedMessage, + getStoreClosedHint, } from '@/utils/storeHours' import Vue3Odometer from 'vue3-odometer' import 'odometer/themes/odometer-theme-default.css' @@ -67,16 +68,12 @@ const userStore = useUserStore() const displayState = computed(() => getPriceDisplayState()) const statusTitle = computed(() => - displayState.value === 'closed' ? 'فروشگاه بسته است' : NO_QUOTE_MESSAGE + displayState.value === 'closed' ? STORE_CLOSED_TITLE : NO_QUOTE_MESSAGE ) const statusHint = computed(() => { if (displayState.value === 'closed') { - const custom = userStore.getValueByKey('closed_message') - if (typeof custom === 'string' && custom.trim()) { - return custom.replace(/<[^>]*>/g, '').trim() - } - return getStoreClosedMessage() + return getStoreClosedHint(userStore.getValueByKey('closed_message')) } return 'در حال حاضر مظنه‌ای برای نمایش وجود ندارد.' }) diff --git a/src/views/base/home/composables/useHomePage.ts b/src/views/base/home/composables/useHomePage.ts index aff17f4..d153e72 100644 --- a/src/views/base/home/composables/useHomePage.ts +++ b/src/views/base/home/composables/useHomePage.ts @@ -12,7 +12,7 @@ import { } from '@/utils/productAssets' import { useSettingsStore } from '@/store/settingsStore' import { getTradeBuyPrice, lastPrice, stopLastPricePolling } from '@/utils/price' -import { getStoreClosedMessage, isStoreClosed } from '@/utils/storeHours' +import { isStoreClosed } from '@/utils/storeHours' import { message, Modal } from 'ant-design-vue' import { useBanners } from './useBanners' import { usePriceChart, type ChartMetalType } from './usePriceChart' @@ -74,7 +74,7 @@ export function useHomePage() { const closedMessage = computed(() => userStore.getValueByKey('closed_message')) const marketClosedText = computed(() => { if (!isStoreClosed()) return '' - // فقط پیام سفارشی ادمین رو نشون بده — پیام ساعت کاری در CurrentPrice نمایش داده میشه + // فقط پیام سفارشی ادمین — در CurrentPrice نمایش داده می‌شود const custom = typeof closedMessage.value === 'string' ? closedMessage.value.trim() : '' return custom || '' }) diff --git a/src/views/base/home/desktop/components/DesktopLivePrice.vue b/src/views/base/home/desktop/components/DesktopLivePrice.vue index 3061e71..657d7f1 100644 --- a/src/views/base/home/desktop/components/DesktopLivePrice.vue +++ b/src/views/base/home/desktop/components/DesktopLivePrice.vue @@ -16,7 +16,7 @@ />

{{ statusTitle }}

-

{{ statusHint }}

+

{{ statusHint }}

@@ -55,8 +55,9 @@ import { useUserStore } from '@/store/userStore' import { getTradeBuyPrice, lastPrice, percentPriceChanged } from '@/utils/price' import { NO_QUOTE_MESSAGE, + STORE_CLOSED_TITLE, getPriceDisplayState, - getStoreClosedMessage, + getStoreClosedHint, } from '@/utils/storeHours' import { formatSignedPercentChange } from '@/utils/usePrice' @@ -65,16 +66,12 @@ const userStore = useUserStore() const displayState = computed(() => getPriceDisplayState()) const statusTitle = computed(() => - displayState.value === 'closed' ? 'فروشگاه بسته است' : NO_QUOTE_MESSAGE + displayState.value === 'closed' ? STORE_CLOSED_TITLE : NO_QUOTE_MESSAGE ) const statusHint = computed(() => { if (displayState.value === 'closed') { - const custom = userStore.getValueByKey('closed_message') - if (typeof custom === 'string' && custom.trim()) { - return custom.replace(/<[^>]*>/g, '').trim() - } - return getStoreClosedMessage() + return getStoreClosedHint(userStore.getValueByKey('closed_message')) } return 'در حال حاضر مظنه‌ای برای نمایش وجود ندارد.' }) diff --git a/src/views/base/products/components/DealProduct.vue b/src/views/base/products/components/DealProduct.vue index 2470a65..526b0e8 100644 --- a/src/views/base/products/components/DealProduct.vue +++ b/src/views/base/products/components/DealProduct.vue @@ -321,7 +321,7 @@ import { } from '@/utils/productOrderLimit' import { useWalletAssets } from '@/composables/useWalletAssets' import { formatProductWalletBalanceText, formatInventoryCardValue, getCashWalletBalance, getProductInventoryQuantity } from '@/utils/productAssets' -import { isStoreClosed, getStoreClosedMessage } from '@/utils/storeHours' +import { isStoreClosed, resolveStoreClosedNotice } from '@/utils/storeHours' const catalogProducts = getProductsCatalogRef() @@ -864,7 +864,7 @@ async function initPrice() { function okConfirm() { if (isStoreClosed()) { - message.warning(getStoreClosedMessage()) + message.warning(resolveStoreClosedNotice(userStore.getValueByKey('closed_message'))) return } confirmAction.value = true @@ -873,7 +873,7 @@ function okConfirm() { async function ok() { if (isStoreClosed()) { - message.warning(getStoreClosedMessage()) + message.warning(resolveStoreClosedNotice(userStore.getValueByKey('closed_message'))) return } try { diff --git a/src/views/base/products/composables/useProductsPage.ts b/src/views/base/products/composables/useProductsPage.ts index 3b57e92..bb4cc22 100644 --- a/src/views/base/products/composables/useProductsPage.ts +++ b/src/views/base/products/composables/useProductsPage.ts @@ -13,7 +13,8 @@ import { loading } from '@/utils' import type { Product } from '@/models/products' import { cloneDeep } from 'lodash' import { isProductPriceInactive } from '@/utils/productPrice' -import { getStoreClosedMessage, isStoreClosed } from '@/utils/storeHours' +import { getStoreClosedHint, shouldBlockStoreTrading } from '@/utils/storeHours' +import { useUserStore } from '@/store/userStore' import { lastPrice } from '@/utils/price' import { changeProductsTab, @@ -50,15 +51,18 @@ export const PRODUCTS_PAGE_CONTEXT_KEY: InjectionKey = Symbol('productsPageContext') export function useProductsPageProvider() { + const userStore = useUserStore() const showFilter = ref(false) const orderSheetKey = ref(0) const selectedProduct = ref() const isAfterCloseTime = ref(false) const isProductInactive = ref(false) - const storeClosedMessage = computed(() => getStoreClosedMessage()) + const storeClosedMessage = computed(() => + getStoreClosedHint(userStore.getValueByKey('closed_message')), + ) function syncStoreClosedState() { - if (isStoreClosed()) { + if (shouldBlockStoreTrading()) { showFilter.value = false isAfterCloseTime.value = true } @@ -73,7 +77,7 @@ export function useProductsPageProvider() { function clickProduct(item?: Product) { if (!item?.show) return - if (isStoreClosed()) { + if (shouldBlockStoreTrading()) { showFilter.value = false isAfterCloseTime.value = true return @@ -95,7 +99,7 @@ export function useProductsPageProvider() { } watch(showFilter, (open) => { - if (open && isStoreClosed()) { + if (open && shouldBlockStoreTrading()) { showFilter.value = false isAfterCloseTime.value = true return @@ -115,7 +119,7 @@ export function useProductsPageProvider() { } if (router.currentRoute.value.query.state) { - if (isStoreClosed()) { + if (shouldBlockStoreTrading()) { isAfterCloseTime.value = true } else if (router.currentRoute.value.query.product) { clickProduct(productsItems.value?.find((x) => x.title === 'سکه')) diff --git a/src/views/base/products/desktop/DesktopProductsView.vue b/src/views/base/products/desktop/DesktopProductsView.vue index 1399d09..d63c154 100644 --- a/src/views/base/products/desktop/DesktopProductsView.vue +++ b/src/views/base/products/desktop/DesktopProductsView.vue @@ -10,7 +10,7 @@
🔒 - فروشگاه بسته است + {{ STORE_CLOSED_TITLE }} — {{ storeClosedMessage }}
@@ -57,7 +57,7 @@ import DesktopPageTopbar from '@/layouts/components/DesktopPageTopbar.vue' import DesktopProductCard from './components/DesktopProductCard.vue' import { useProductsPageContext } from '../composables/useProductsPage' -import { getPriceDisplayState, isStoreClosedBySchedule, getStoreClosedMessage } from '@/utils/storeHours' +import { isMarketClosedForDisplay, STORE_CLOSED_TITLE } from '@/utils/storeHours' import { computed } from 'vue' const { @@ -68,10 +68,10 @@ const { changeTab, clickProduct, isProductPriceInactive, + storeClosedMessage, } = useProductsPageContext() -const storeClosed = computed(() => getPriceDisplayState() === 'closed' || isStoreClosedBySchedule()) -const storeClosedMessage = computed(() => getStoreClosedMessage()) +const storeClosed = computed(() => isMarketClosedForDisplay()) diff --git a/src/views/base/products/index.vue b/src/views/base/products/index.vue index 6fa2ad4..7096fe3 100644 --- a/src/views/base/products/index.vue +++ b/src/views/base/products/index.vue @@ -52,10 +52,9 @@
-
مشتریان گرامی
-
-

{{ storeClosedMessage }}

-

از همراهی و اعتماد شما سپاسگزاریم.🤍

+
{{ STORE_CLOSED_TITLE }}
+
+ {{ storeClosedMessage }}