Merge remote-tracking branch 'origin/modular' into modular
# Conflicts: # src/views/base/home/composables/usePriceChart.ts
This commit is contained in:
+4
-3
@@ -208,10 +208,11 @@ const {
|
||||
handlePointerMove: onSupportPointerMove,
|
||||
handlePointerUp: supportPointerUp,
|
||||
handlePointerCancel: onSupportPointerCancel
|
||||
} = useDraggableFloatingButton('khaneghi_support_button_position', {
|
||||
} = useDraggableFloatingButton('khaneghi_support_button_position_v2', {
|
||||
width: 118,
|
||||
height: 42,
|
||||
defaultBottomOffset: 120
|
||||
defaultBottomGap: 12,
|
||||
defaultRightOffset: 16
|
||||
})
|
||||
|
||||
function openSupportLink() {
|
||||
@@ -322,7 +323,7 @@ function update() {
|
||||
|
||||
#support-button {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
z-index: 5100;
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
border-radius: 9999px;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getStoreCategories } from '@/api/categories'
|
||||
|
||||
/** تبهای فیلتر محصول — فقط از API دستهبندی (هماهنگ با پنل ادمین) */
|
||||
/** تبهای فیلتر محصول — دستههای فعال که محصول دارند (خودکار از API) */
|
||||
export async function loadProductCategoryTabNames(): Promise<string[]> {
|
||||
const categories = await getStoreCategories()
|
||||
return categories.map((item) => item.name)
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
import { lastPrice } from '@/utils/price'
|
||||
|
||||
export interface StoreTradingHours {
|
||||
start: string
|
||||
end: string
|
||||
closePriceEnabled: boolean
|
||||
}
|
||||
|
||||
export interface EffectiveTradingHours {
|
||||
start: string
|
||||
end: string
|
||||
enforced: boolean
|
||||
}
|
||||
|
||||
let productTradingHours: { start: string; end: string } | null = null
|
||||
|
||||
function parseTimeToSeconds(time: string): number {
|
||||
const parts = time.trim().split(':').map(Number)
|
||||
const [hours = 0, minutes = 0, seconds = 0] = parts
|
||||
return hours * 3600 + minutes * 60 + seconds
|
||||
}
|
||||
|
||||
export function formatTradingTimeDisplay(time: string): string {
|
||||
const parts = time.trim().split(':')
|
||||
if (parts.length >= 3) {
|
||||
return `${parts[0]}:${parts[1]}:${parts[2]}`
|
||||
}
|
||||
if (parts.length >= 2) {
|
||||
return `${parts[0]}:${parts[1]}:00`
|
||||
}
|
||||
return time
|
||||
}
|
||||
|
||||
export function setProductTradingHours(start?: string | null, end?: string | null) {
|
||||
if (start && end) {
|
||||
productTradingHours = { start, end }
|
||||
}
|
||||
}
|
||||
|
||||
export function getStoreTradingHours(): StoreTradingHours {
|
||||
const settings = useSettingsStore()
|
||||
const closeRaw = settings.getSettingByKey('close_price')
|
||||
const closePriceEnabled =
|
||||
closeRaw === true ||
|
||||
closeRaw === 1 ||
|
||||
String(closeRaw ?? 'false').trim().toLowerCase() === 'true'
|
||||
|
||||
return {
|
||||
start: String(settings.getSettingByKey('startDay') ?? '00:00'),
|
||||
end: String(settings.getSettingByKey('endDay') ?? '23:59'),
|
||||
closePriceEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
export function getEffectiveTradingHours(): EffectiveTradingHours {
|
||||
const settings = getStoreTradingHours()
|
||||
if (settings.closePriceEnabled) {
|
||||
return {
|
||||
start: settings.start,
|
||||
end: settings.end,
|
||||
enforced: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (productTradingHours?.start && productTradingHours?.end) {
|
||||
return {
|
||||
start: productTradingHours.start,
|
||||
end: productTradingHours.end,
|
||||
enforced: true,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
start: settings.start,
|
||||
end: settings.end,
|
||||
enforced: false,
|
||||
}
|
||||
}
|
||||
|
||||
export function isStoreClosedBySchedule(now = new Date()): boolean {
|
||||
const { start, end, enforced } = getEffectiveTradingHours()
|
||||
if (!enforced) {
|
||||
return false
|
||||
}
|
||||
|
||||
const currentSeconds = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds()
|
||||
const openSeconds = parseTimeToSeconds(start)
|
||||
const closeSeconds = parseTimeToSeconds(end)
|
||||
|
||||
return currentSeconds < openSeconds || currentSeconds > closeSeconds
|
||||
}
|
||||
|
||||
export function isStoreClosed(): boolean {
|
||||
if (lastPrice.value?.closed === true) {
|
||||
return true
|
||||
}
|
||||
|
||||
return isStoreClosedBySchedule()
|
||||
}
|
||||
|
||||
export function getStoreClosedMessage(): string {
|
||||
const { start, end } = getEffectiveTradingHours()
|
||||
const open = formatTradingTimeDisplay(start)
|
||||
const close = formatTradingTimeDisplay(end)
|
||||
return `فروشگاه در حال حاضر بسته است. ساعت کاری از ${open} تا ${close} میباشد.`
|
||||
}
|
||||
@@ -17,9 +17,6 @@ export interface SupportMessengerSettings {
|
||||
|
||||
export const supportMessengerSettings = ref<SupportMessengerSettings>({})
|
||||
|
||||
export const DEFAULT_SUPPORT_MESSENGER_TEXT =
|
||||
'جهت شارژ حساب کاربری خود، کلمه "شماره حساب" را برای پشتیبانی ارسال نمایید.'
|
||||
|
||||
function getSupportSettingValue(
|
||||
settings: Array<{ key?: string; is_active?: boolean | number; value?: unknown }>,
|
||||
key: string
|
||||
@@ -39,9 +36,11 @@ export function buildSupportMessengerUrl(messenger?: string, messengerId?: strin
|
||||
|
||||
if (/^https?:\/\//i.test(id)) return id
|
||||
|
||||
const type = (messenger ?? '').trim().toLowerCase() || 'bale'
|
||||
const type = (messenger ?? '').trim().toLowerCase()
|
||||
const username = id.replace(/^@/, '')
|
||||
|
||||
if (!type) return ''
|
||||
|
||||
switch (type) {
|
||||
case 'bale':
|
||||
if (id.includes('ble.ir')) {
|
||||
@@ -59,7 +58,7 @@ export function buildSupportMessengerUrl(messenger?: string, messengerId?: strin
|
||||
case 'soroush':
|
||||
return `https://splus.ir/splus/${username}`
|
||||
default:
|
||||
return `https://ble.ir/${username}`
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +68,7 @@ export async function loadSupportMessengerFromSettings() {
|
||||
if (!settings || !Array.isArray(settings)) return
|
||||
|
||||
supportMessengerSettings.value = {
|
||||
messenger: getSupportSettingValue(settings, 'support_messenger') || 'bale',
|
||||
messenger: getSupportSettingValue(settings, 'support_messenger'),
|
||||
messengerId: getSupportSettingValue(settings, 'support_messenger_id'),
|
||||
messengerText: getSupportSettingValue(settings, 'support_messenger_text'),
|
||||
}
|
||||
|
||||
@@ -1,24 +1,54 @@
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
const DRAG_THRESHOLD = 6
|
||||
const POSITION_VERSION = 1
|
||||
|
||||
type FabPosition = { x: number; y: number }
|
||||
|
||||
type SavedFabOffset = {
|
||||
version: number
|
||||
dx: number
|
||||
dy: number
|
||||
}
|
||||
|
||||
type DraggableFloatingOptions = {
|
||||
width?: number
|
||||
height?: number
|
||||
defaultBottomOffset?: number
|
||||
/** فاصله از پایینِ نوار منو + montzar (px) — مثل تصویر */
|
||||
defaultBottomGap?: number
|
||||
defaultRightOffset?: number
|
||||
}
|
||||
|
||||
function readBottomChromeHeight(): number {
|
||||
const styles = getComputedStyle(document.documentElement)
|
||||
const direct = parseFloat(styles.getPropertyValue('--app-bottom-chrome-height'))
|
||||
if (!Number.isNaN(direct) && direct > 0) {
|
||||
return direct
|
||||
}
|
||||
|
||||
const nav = parseFloat(styles.getPropertyValue('--app-nav-height')) || 80
|
||||
const creditOffset = parseFloat(styles.getPropertyValue('--app-credit-footer-offset'))
|
||||
if (!Number.isNaN(creditOffset) && creditOffset > 0) {
|
||||
return nav + creditOffset
|
||||
}
|
||||
|
||||
const probe = document.createElement('div')
|
||||
probe.style.cssText = 'position:fixed;visibility:hidden;pointer-events:none;height:var(--app-bottom-chrome-height)'
|
||||
document.body.appendChild(probe)
|
||||
const measured = probe.getBoundingClientRect().height
|
||||
document.body.removeChild(probe)
|
||||
|
||||
return measured > 0 ? measured : nav + 28
|
||||
}
|
||||
|
||||
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 defaultBottomGap = options.defaultBottomGap ?? 12
|
||||
const defaultRightOffset = options.defaultRightOffset ?? 16
|
||||
|
||||
const pos = ref<FabPosition>({ x: 0, y: 0 })
|
||||
const dragging = ref(false)
|
||||
@@ -44,26 +74,40 @@ export function useDraggableFloatingButton(
|
||||
function getHorizontalOffset() {
|
||||
const vw = window.innerWidth
|
||||
const contentWidth = getViewportWidth()
|
||||
|
||||
return Math.max(0, (vw - contentWidth) / 2)
|
||||
}
|
||||
|
||||
function resetToDefault() {
|
||||
function getDefaultPosition(): FabPosition {
|
||||
const vh = window.innerHeight
|
||||
const offsetX = getHorizontalOffset()
|
||||
const contentWidth = getViewportWidth()
|
||||
const bottomChrome = readBottomChromeHeight()
|
||||
|
||||
pos.value = {
|
||||
return {
|
||||
x: offsetX + contentWidth - width - defaultRightOffset,
|
||||
y: vh - defaultBottomOffset - height
|
||||
y: vh - bottomChrome - defaultBottomGap - height
|
||||
}
|
||||
}
|
||||
|
||||
function applySavedOffset(dx: number, dy: number) {
|
||||
const base = getDefaultPosition()
|
||||
pos.value = {
|
||||
x: base.x + dx,
|
||||
y: base.y + dy
|
||||
}
|
||||
clampToViewport()
|
||||
}
|
||||
|
||||
function clampToViewport() {
|
||||
const vw = window.innerWidth
|
||||
const vh = window.innerHeight
|
||||
const offsetX = getHorizontalOffset()
|
||||
const contentWidth = getViewportWidth()
|
||||
const minX = offsetX
|
||||
const maxX = offsetX + contentWidth - width
|
||||
|
||||
pos.value = {
|
||||
x: Math.max(0, Math.min(vw - width, pos.value.x)),
|
||||
x: Math.max(minX, Math.min(maxX, pos.value.x)),
|
||||
y: Math.max(0, Math.min(vh - height, pos.value.y))
|
||||
}
|
||||
}
|
||||
@@ -72,25 +116,56 @@ export function useDraggableFloatingButton(
|
||||
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()
|
||||
|
||||
const parsed = JSON.parse(raw) as SavedFabOffset
|
||||
if (
|
||||
parsed?.version === POSITION_VERSION
|
||||
&& typeof parsed.dx === 'number'
|
||||
&& typeof parsed.dy === 'number'
|
||||
) {
|
||||
applySavedOffset(parsed.dx, parsed.dy)
|
||||
return true
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function savePosition() {
|
||||
localStorage.setItem(storageKey, JSON.stringify(pos.value))
|
||||
const base = getDefaultPosition()
|
||||
const payload: SavedFabOffset = {
|
||||
version: POSITION_VERSION,
|
||||
dx: pos.value.x - base.x,
|
||||
dy: pos.value.y - base.y
|
||||
}
|
||||
localStorage.setItem(storageKey, JSON.stringify(payload))
|
||||
}
|
||||
|
||||
function resetToDefault() {
|
||||
pos.value = getDefaultPosition()
|
||||
}
|
||||
|
||||
function handleResize() {
|
||||
clampToViewport()
|
||||
savePosition()
|
||||
try {
|
||||
const raw = localStorage.getItem(storageKey)
|
||||
if (raw) {
|
||||
const parsed = JSON.parse(raw) as SavedFabOffset
|
||||
if (
|
||||
parsed?.version === POSITION_VERSION
|
||||
&& typeof parsed.dx === 'number'
|
||||
&& typeof parsed.dy === 'number'
|
||||
) {
|
||||
applySavedOffset(parsed.dx, parsed.dy)
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
resetToDefault()
|
||||
}
|
||||
|
||||
function handlePointerDown(event: PointerEvent) {
|
||||
|
||||
@@ -24,3 +24,58 @@ export function useTowDecimal(num: string | number): string | number {
|
||||
export function useFormat(num: string | number, unit = '') {
|
||||
return usePrice(Math.abs(+useTowDecimal(num)), true, unit)
|
||||
}
|
||||
|
||||
type TransactionPriceSource = {
|
||||
cash?: number | null
|
||||
gold?: number | null
|
||||
order?: {
|
||||
price?: number | null
|
||||
total_price?: number | null
|
||||
amount?: number | null
|
||||
} | null
|
||||
}
|
||||
|
||||
export function getTransactionTradePrice(item: TransactionPriceSource): number {
|
||||
const orderPrice = Number(item.order?.price)
|
||||
if (Number.isFinite(orderPrice) && orderPrice > 0) {
|
||||
return Math.round(orderPrice)
|
||||
}
|
||||
|
||||
const cashAmount = Math.abs(Number(item.cash) || 0)
|
||||
const goldAmount =
|
||||
Math.abs(Number(item.gold) || 0) || Math.abs(Number(item.order?.amount) || 0)
|
||||
|
||||
if (goldAmount > 0 && cashAmount > 0) {
|
||||
return Math.round(cashAmount / goldAmount)
|
||||
}
|
||||
|
||||
const totalPrice = Number(item.order?.total_price)
|
||||
if (Number.isFinite(totalPrice) && totalPrice > 0) {
|
||||
return Math.round(totalPrice)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
export function getTransactionGoldAmount(item: TransactionPriceSource): number {
|
||||
const gold = Number(item.gold) || 0
|
||||
if (Math.abs(gold) > 0) {
|
||||
return gold
|
||||
}
|
||||
|
||||
const orderAmount = Number(item.order?.amount) || 0
|
||||
if (orderAmount > 0) {
|
||||
const cash = Number(item.cash) || 0
|
||||
if (cash < 0) return orderAmount
|
||||
if (cash > 0) return -orderAmount
|
||||
return orderAmount
|
||||
}
|
||||
|
||||
return gold
|
||||
}
|
||||
|
||||
export function formatGoldAmount(amount?: number | null): string {
|
||||
const num = Number(amount)
|
||||
if (!Number.isFinite(num)) return '0'
|
||||
return String(Number(num.toFixed(3)))
|
||||
}
|
||||
|
||||
@@ -239,6 +239,7 @@ import {useConfigStore} from "@/store/configStore";
|
||||
import { calcFeeInGrams } from '@/utils/serviceFee'
|
||||
import { formatCashWalletBalanceText, formatGoldWalletBalanceText } from '@/utils/productDealMethod'
|
||||
import { getCashWalletBalance } from '@/utils/productAssets'
|
||||
import { isStoreClosed, getStoreClosedMessage } from '@/utils/storeHours'
|
||||
|
||||
const infoVisible = ref(false)
|
||||
const walletStore = useWalletStore()
|
||||
@@ -325,6 +326,7 @@ const equivalentPrice = computed(() => usePriceWithoutType(feeInRial.value.toFix
|
||||
|
||||
const disableTransaction = computed(() =>
|
||||
loading.value ||
|
||||
isStoreClosed() ||
|
||||
form.value?.goldValue === undefined ||
|
||||
form.value?.goldValue === '' ||
|
||||
form.value?.goldCash === '' ||
|
||||
@@ -640,10 +642,18 @@ async function initPrice() {
|
||||
}
|
||||
|
||||
function okConfirm() {
|
||||
if (isStoreClosed()) {
|
||||
message.warning(getStoreClosedMessage())
|
||||
return
|
||||
}
|
||||
confirmAction.value = true
|
||||
}
|
||||
|
||||
async function ok() {
|
||||
if (isStoreClosed()) {
|
||||
message.warning(getStoreClosedMessage())
|
||||
return
|
||||
}
|
||||
try {
|
||||
loading.value = true
|
||||
infoVisible.value = true
|
||||
|
||||
@@ -236,6 +236,7 @@ import type { Product } from '@/models/products'
|
||||
import { useFundUserStore } from '@/store/fundStore'
|
||||
import {useConfigStore} from "@/store/configStore";
|
||||
import { calcFeeInGrams, loadServiceFeeFromSettings } from '@/utils/serviceFee'
|
||||
import { isStoreClosed, getStoreClosedMessage } from '@/utils/storeHours'
|
||||
import { formatCashWalletBalanceText, getWeightUnit } from '@/utils/productDealMethod'
|
||||
import { formatProductWalletBalanceText, getCashWalletBalance, getProductInventoryQuantity } from '@/utils/productAssets'
|
||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||
@@ -344,6 +345,7 @@ const priceUnavailable = computed(() => {
|
||||
|
||||
const disableTransaction = computed(() =>
|
||||
loading.value ||
|
||||
isStoreClosed() ||
|
||||
form.value?.goldValue === undefined ||
|
||||
form.value?.goldValue === '' ||
|
||||
form.value?.goldCash === '' ||
|
||||
@@ -633,6 +635,10 @@ async function initPrice() {
|
||||
}
|
||||
|
||||
function okConfirm() {
|
||||
if (isStoreClosed()) {
|
||||
message.warning(getStoreClosedMessage())
|
||||
return
|
||||
}
|
||||
if (cashValidationMessage.value) {
|
||||
message.warning(cashValidationMessage.value)
|
||||
return
|
||||
@@ -641,6 +647,10 @@ function okConfirm() {
|
||||
}
|
||||
|
||||
async function ok() {
|
||||
if (isStoreClosed()) {
|
||||
message.warning(getStoreClosedMessage())
|
||||
return
|
||||
}
|
||||
try {
|
||||
loading.value = true
|
||||
infoVisible.value = true
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<AssetsSection v-else @openGoldModal="openGoldModal" @openRialModal="openRialModal"/>
|
||||
|
||||
<!-- پیام بسته بودن بازار -->
|
||||
<MarketClosedMessage v-if="showClosedMessage" :message="closedMessage"/>
|
||||
<MarketClosedMessage v-if="showClosedMessage" :message="marketClosedText"/>
|
||||
|
||||
<!-- قیمت فعلی -->
|
||||
<CurrentPrice/>
|
||||
@@ -89,6 +89,7 @@ import {useConfigStore} from '@/store/configStore'
|
||||
import {useFundUserStore} from '@/store/fundStore'
|
||||
import {usePriceWithoutType} from '../../../utils/usePrice'
|
||||
import {getLastPrices, lastPrice, price, startLastPricePolling, stopLastPricePolling} from '@/utils/price'
|
||||
import { getStoreClosedMessage, isStoreClosed } from '@/utils/storeHours'
|
||||
import {getConstByKey} from '@/api/const'
|
||||
import {message, Modal} from 'ant-design-vue'
|
||||
|
||||
@@ -126,9 +127,11 @@ const { assetsSummary, loadWalletAssets } = useWalletAssets()
|
||||
// Config
|
||||
const isGaroosi = computed(() => configStore.config.name === 'garoosi')
|
||||
const closedMessage = computed(() => userStore.getValueByKey('closed_message'))
|
||||
const showClosedMessage = computed(() =>
|
||||
closedMessage.value &&
|
||||
lastPrice?.value?.price_last === 0)
|
||||
const marketClosedText = computed(() => {
|
||||
if (!isStoreClosed()) return ''
|
||||
return closedMessage.value || getStoreClosedMessage()
|
||||
})
|
||||
const showClosedMessage = computed(() => !!marketClosedText.value)
|
||||
|
||||
|
||||
// State
|
||||
|
||||
@@ -297,6 +297,7 @@ import {
|
||||
} from '@/utils/productOrderLimit'
|
||||
import { useWalletAssets } from '@/composables/useWalletAssets'
|
||||
import { formatProductWalletBalanceText, getCashWalletBalance, getProductInventoryQuantity } from '@/utils/productAssets'
|
||||
import { isStoreClosed, getStoreClosedMessage } from '@/utils/storeHours'
|
||||
|
||||
const typeDeal = computed(() => getProductDealMethodOptions(props?.ProductItem?.group_type, isBuy.value))
|
||||
|
||||
@@ -438,6 +439,7 @@ const cashValidationMessage = computed(() =>
|
||||
|
||||
const disableTransaction = computed(() =>
|
||||
loading.value ||
|
||||
isStoreClosed() ||
|
||||
form.value?.goldValue === undefined ||
|
||||
form.value?.goldValue === '' ||
|
||||
form.value?.goldCash === '' ||
|
||||
@@ -795,11 +797,19 @@ async function initPrice() {
|
||||
// })
|
||||
|
||||
function okConfirm() {
|
||||
if (isStoreClosed()) {
|
||||
message.warning(getStoreClosedMessage())
|
||||
return
|
||||
}
|
||||
confirmAction.value = true
|
||||
refInput.value?.validateFields()
|
||||
}
|
||||
|
||||
async function ok() {
|
||||
if (isStoreClosed()) {
|
||||
message.warning(getStoreClosedMessage())
|
||||
return
|
||||
}
|
||||
try {
|
||||
loading.value = true
|
||||
infoVisible.value = true
|
||||
|
||||
@@ -13,6 +13,15 @@
|
||||
</ARadioGroup>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="storeIsClosed"
|
||||
class="mx-5 mb-3 rounded-lg bg-red-50 px-3 py-4 text-center text-red-600"
|
||||
>
|
||||
<div class="font-bold">مشتریان گرامی</div>
|
||||
<p class="mt-2 leading-relaxed">{{ storeClosedMessage }}</p>
|
||||
<p class="mt-2">از همراهی و اعتماد شما سپاسگزاریم.🤍</p>
|
||||
</div>
|
||||
|
||||
<div class="mx-5 pb-30 pt-2" style="color: #1b2b07">
|
||||
<div class="mt-3" v-for="item in items" :key="item.id">
|
||||
<ProductItem
|
||||
@@ -70,8 +79,7 @@
|
||||
<div class="text-center">
|
||||
<div class="font-bold text-red-600">مشتریان گرامی</div>
|
||||
<div class="mt-2 leading-relaxed">
|
||||
<p>فروشگاه در حال حاضر بسته است.</p>
|
||||
<p>ساعت کاری فروشگاه از {{ openTime }} تا {{ closeTime }} میباشد.</p>
|
||||
<p>{{ storeClosedMessage }}</p>
|
||||
<p>از همراهی و اعتماد شما سپاسگزاریم.🤍</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -103,7 +111,15 @@ import { isMetalCommodityGroup, isMeltedTradeGroup } from '@/utils/productCatego
|
||||
import { isBoursecuDealProductGroup } from '@/utils/productDealMethod'
|
||||
import { loadServiceFeeFromSettings } from '@/utils/serviceFee'
|
||||
import { loadProductCategoryTabNames } from '@/utils/categoryTabs'
|
||||
import { getLastPrices, startLastPricePolling, stopLastPricePolling } from '@/utils/price'
|
||||
import { getLastPrices, lastPrice, startLastPricePolling, stopLastPricePolling } from '@/utils/price'
|
||||
import {
|
||||
formatTradingTimeDisplay,
|
||||
getEffectiveTradingHours,
|
||||
getStoreClosedMessage,
|
||||
isStoreClosed,
|
||||
setProductTradingHours
|
||||
} from '@/utils/storeHours'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
|
||||
const activekey = ref('طلا' ||'آبشده')
|
||||
const showFilter = ref(false)
|
||||
@@ -113,21 +129,29 @@ const closeTime = ref<string>('')
|
||||
const openTime = ref<string>('')
|
||||
const isAfterCloseTime = ref<boolean>(false)
|
||||
const isProductInactive = ref<boolean>(false)
|
||||
const storeIsClosed = computed(() => isStoreClosed())
|
||||
const storeClosedMessage = computed(() => getStoreClosedMessage())
|
||||
|
||||
watch([openTime, closeTime], ([open, close]) => {
|
||||
if (!open || !close) return
|
||||
const now = new Date()
|
||||
const currentSeconds = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds()
|
||||
const [oh, om, os] = open.split(':').map(Number)
|
||||
const [ch, cm, cs] = close.split(':').map(Number)
|
||||
const openSeconds = oh * 3600 + om * 60 + os
|
||||
const closeSeconds = ch * 3600 + cm * 60 + cs
|
||||
isAfterCloseTime.value = currentSeconds < openSeconds || currentSeconds > closeSeconds
|
||||
watch([openTime, closeTime], () => {
|
||||
syncStoreClosedState()
|
||||
})
|
||||
|
||||
function syncStoreClosedState() {
|
||||
if (isStoreClosed()) {
|
||||
showFilter.value = false
|
||||
isAfterCloseTime.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function clickProduct(item?: Product) {
|
||||
if (!item?.show) return
|
||||
|
||||
if (isStoreClosed()) {
|
||||
showFilter.value = false
|
||||
isAfterCloseTime.value = true
|
||||
return
|
||||
}
|
||||
|
||||
if (isProductPriceInactive(item)) {
|
||||
showFilter.value = false
|
||||
isProductInactive.value = true
|
||||
@@ -144,6 +168,11 @@ function onProductOrdered() {
|
||||
}
|
||||
|
||||
watch(showFilter, (open) => {
|
||||
if (open && isStoreClosed()) {
|
||||
showFilter.value = false
|
||||
isAfterCloseTime.value = true
|
||||
return
|
||||
}
|
||||
if (!open) {
|
||||
orderSheetKey.value += 1
|
||||
}
|
||||
@@ -161,6 +190,7 @@ const firstItems = ref<Product[]>()
|
||||
const items = ref<Product[]>()
|
||||
const typeItems = ref<string[]>([])
|
||||
let productListInterval: ReturnType<typeof setInterval> | undefined
|
||||
let storeHoursInterval: ReturnType<typeof setInterval> | undefined
|
||||
|
||||
async function loadCategoryTabs() {
|
||||
try {
|
||||
@@ -212,22 +242,30 @@ async function refreshProducts() {
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => lastPrice.value?.closed, syncStoreClosedState)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
await useSettingsStore().fetchSettings()
|
||||
await getLastPrices()
|
||||
startLastPricePolling()
|
||||
items.value = await getProducts()
|
||||
|
||||
if (items.value?.length) {
|
||||
closeTime.value = items.value[0].auto_close_at
|
||||
openTime.value = items.value[0].auto_open_at
|
||||
setProductTradingHours(items.value[0].auto_open_at, items.value[0].auto_close_at)
|
||||
}
|
||||
|
||||
const hours = getEffectiveTradingHours()
|
||||
openTime.value = formatTradingTimeDisplay(hours.start)
|
||||
closeTime.value = formatTradingTimeDisplay(hours.end)
|
||||
syncStoreClosedState()
|
||||
|
||||
await loadCategoryTabs()
|
||||
firstItems.value = items.value
|
||||
await loadServiceFeeFromSettings()
|
||||
productListInterval = setInterval(refreshProducts, 10000)
|
||||
storeHoursInterval = setInterval(syncStoreClosedState, 30000)
|
||||
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
@@ -239,7 +277,9 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
if (router.currentRoute.value.query.state) {
|
||||
if (router.currentRoute.value.query.product) {
|
||||
if (isStoreClosed()) {
|
||||
isAfterCloseTime.value = true
|
||||
} else if (router.currentRoute.value.query.product) {
|
||||
clickProduct(items.value?.find(x => x.title === 'سکه'))
|
||||
} else {
|
||||
clickProduct(items.value?.find(x => x.title === 'آبشده نقدی'))
|
||||
@@ -253,6 +293,10 @@ onUnmounted(() => {
|
||||
clearInterval(productListInterval)
|
||||
productListInterval = undefined
|
||||
}
|
||||
if (storeHoursInterval) {
|
||||
clearInterval(storeHoursInterval)
|
||||
storeHoursInterval = undefined
|
||||
}
|
||||
})
|
||||
// watch(lastPrice, async () => {
|
||||
// // console.log(items);
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
<div>{{ toJalaliOutOfUTC(item.expire_at) }}</div>
|
||||
</div>
|
||||
<div class="flex justify-between mt-2">
|
||||
<div :class="item.gold >= 0? 'text-green-600':'text-red-600'"
|
||||
class="text-gray-500 text-xs number-ltr">{{ item.gold }}
|
||||
<div :class="getTransactionGoldAmount(item) >= 0? 'text-green-600':'text-red-600'"
|
||||
class="text-gray-500 text-xs number-ltr">{{ formatGoldAmount(getTransactionGoldAmount(item)) }}
|
||||
<div>{{item.unit}}</div>
|
||||
</div>
|
||||
<div class="text-gray-500 flex gap-1 text-xs items-center number-ltr">
|
||||
@@ -29,7 +29,7 @@
|
||||
<div class="text-gray-500">قیمت معامله:</div>
|
||||
<div class="text-gray-500 flex gap-1 text-xs items-center">
|
||||
<!-- <div class="text-base">{{ typeof parseInt((Math.abs(item.cash) / Math.abs(item.gold)).toFixed(0)) }}</div>-->
|
||||
<div class="text-base">{{ usePriceWithoutType(parseInt((Math.abs(item.cash) / Math.abs(item.gold)).toFixed(0))) }}</div>
|
||||
<div class="text-base">{{ usePriceWithoutType(getTransactionTradePrice(item)) }}</div>
|
||||
ریال
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,16 +62,16 @@
|
||||
</div>
|
||||
<div class="flex justify-between mb-3">
|
||||
<div class="text-primary">مقدار {{ item.wallet?.product ? item.wallet.product.title : getMeltedGoldTradeLabel() }} مبادله شده:</div>
|
||||
<div :class="item.gold >= 0? 'text-green-600' : 'text-red-600'" class="flex flex-col items-end text-xs number-ltr">
|
||||
<div :class="getTransactionGoldAmount(item) >= 0? 'text-green-600' : 'text-red-600'" class="flex flex-col items-end text-xs number-ltr">
|
||||
<div class="flex items-center">
|
||||
<div class="text-base ml-1">{{ item.gold }}</div>
|
||||
<div class="text-base ml-1">{{ formatGoldAmount(getTransactionGoldAmount(item)) }}</div>
|
||||
{{ item.unit }}
|
||||
</div>
|
||||
<div
|
||||
v-if="item.wallet?.product && getProductTotalWeightLabel(Math.abs(item.gold ?? 0), item.wallet.product)"
|
||||
v-if="item.wallet?.product && getProductTotalWeightLabel(Math.abs(getTransactionGoldAmount(item)), item.wallet.product)"
|
||||
class="text-gray-500"
|
||||
>
|
||||
{{ getProductTotalWeightLabel(Math.abs(item.gold ?? 0), item.wallet.product) }}
|
||||
{{ getProductTotalWeightLabel(Math.abs(getTransactionGoldAmount(item)), item.wallet.product) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -146,7 +146,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Icon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { usePriceWithoutType } from '../../../../utils/usePrice'
|
||||
import { formatGoldAmount, getTransactionGoldAmount, getTransactionTradePrice, usePriceWithoutType } from '../../../../utils/usePrice'
|
||||
import BasicActionSheet from '@/components/BasicPacks/BasicActionSheet.vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<div v-if="isGold" class="flex w-full justify-between mt-2">
|
||||
<div class="text-gray-500">قیمت معامله:</div>
|
||||
<div class="text-gray-500 flex gap-1 text-xs items-center">
|
||||
<div class="text-base">{{ usePriceWithoutType((Math.abs(item.cash) / Math.abs(item.gold)).toFixed(0)) }}</div>
|
||||
<div class="text-base">{{ usePriceWithoutType(getTransactionTradePrice(item)) }}</div>
|
||||
ریال
|
||||
</div>
|
||||
</div>
|
||||
@@ -138,7 +138,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Icon from "@/components/BasicPacks/BasicIcon.vue";
|
||||
import {usePriceWithoutType} from "../../../../utils/usePrice";
|
||||
import {getTransactionTradePrice, usePriceWithoutType} from "../../../../utils/usePrice";
|
||||
import BasicActionSheet from "@/components/BasicPacks/BasicActionSheet.vue";
|
||||
import {computed, ref} from "vue";
|
||||
import BasicButton from "@/components/BasicPacks/BasicButton.vue";
|
||||
|
||||
@@ -418,7 +418,6 @@ import { useConfigStore } from '@/store/configStore'
|
||||
import { isBazretalaProject } from '@/utils/config'
|
||||
import {
|
||||
buildSupportMessengerUrl,
|
||||
DEFAULT_SUPPORT_MESSENGER_TEXT,
|
||||
loadSupportMessengerFromSettings,
|
||||
supportMessengerSettings,
|
||||
} from '@/utils/supportMessenger'
|
||||
@@ -437,9 +436,7 @@ const withdrawInfModal = ref<boolean>(false)
|
||||
|
||||
const config = useConfigStore()
|
||||
|
||||
const supportMessengerText = computed(() =>
|
||||
supportMessengerSettings.value.messengerText || DEFAULT_SUPPORT_MESSENGER_TEXT
|
||||
)
|
||||
const supportMessengerText = computed(() => supportMessengerSettings.value.messengerText ?? '')
|
||||
|
||||
const supportMessengerLink = computed(() =>
|
||||
buildSupportMessengerUrl(
|
||||
|
||||
Reference in New Issue
Block a user