Match the TV header quote to the product list buy and sell prices.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -35,6 +35,8 @@ export interface Product {
|
||||
updated_at: any
|
||||
deleted_at: any
|
||||
price?:number | undefined
|
||||
price_buy?: number | null
|
||||
price_sell?: number | null
|
||||
price_profit?: number | null
|
||||
price_offset_percent?: number | null
|
||||
sellPrice?:number | undefined
|
||||
|
||||
@@ -61,6 +61,15 @@ function sellCashFee(product: Product, base: number) {
|
||||
return resolveProductCashFeeSellRial(product, base, cashFeeOptions(product, 'sell'))
|
||||
}
|
||||
|
||||
/** سمت خرید/فروش همکار؛ اگر سرور نفرستاده باشد همان قیمت وسط قبلی */
|
||||
function colleagueSideUnit(product: Product, side: 'buy' | 'sell', live: number): number {
|
||||
const sided = side === 'buy' ? Number(product.price_buy ?? 0) : Number(product.price_sell ?? 0)
|
||||
if (sided > 0) {
|
||||
return sided
|
||||
}
|
||||
return getColleagueSourcedUnitPrice(product, live)
|
||||
}
|
||||
|
||||
/** قیمت منبع همکار؛ اگر خالی بود آبشده همان مظنهٔ صفحه اصلی را میگیرد */
|
||||
function getColleagueSourcedUnitPrice(product: Product, live: number, apiPrice = Number(product.price ?? 0)): number {
|
||||
if (apiPrice > 0) {
|
||||
@@ -134,7 +143,7 @@ export function getProductBaseBuyPrice(product?: Product | null): number {
|
||||
}
|
||||
|
||||
if (isColleaguePriceProduct(product)) {
|
||||
return getColleagueSourcedUnitPrice(product, getTradeBuyPrice(lastPrice.value))
|
||||
return colleagueSideUnit(product, 'buy', getTradeBuyPrice(lastPrice.value))
|
||||
}
|
||||
|
||||
if (isMeltedProductUsingAppPrice(product) || isAppPriceProduct(product)) {
|
||||
@@ -211,11 +220,7 @@ export function getProductBaseSellPrice(product?: Product | null): number {
|
||||
}
|
||||
|
||||
if (isColleaguePriceProduct(product)) {
|
||||
return getColleagueSourcedUnitPrice(
|
||||
product,
|
||||
getTradeSellPrice(lastPrice.value),
|
||||
Number(product.sellPrice ?? product.price ?? 0),
|
||||
)
|
||||
return colleagueSideUnit(product, 'sell', getTradeSellPrice(lastPrice.value))
|
||||
}
|
||||
|
||||
if (isMeltedProductUsingAppPrice(product) || isAppPriceProduct(product)) {
|
||||
|
||||
@@ -4,10 +4,10 @@ import type { Product } from '@/models/products'
|
||||
import { getTvProducts, getTvServerTime } from '@/api/products'
|
||||
import { getConfigApp } from '@/utils/config'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
import { getTvProductBuyPrice, getTvProductSellPrice, isTvProductPriceInactive } from '@/utils/productPrice'
|
||||
import { getLastPrices, getTradeBuyPrice, getTradeSellPrice, lastPrice, startLastPricePolling, stopLastPricePolling } from '@/utils/price'
|
||||
import { getProductBuyPrice, getProductSellPrice, getTvProductBuyPrice, getTvProductSellPrice } from '@/utils/productPrice'
|
||||
import { getLastPrices, lastPrice, startLastPricePolling, stopLastPricePolling } from '@/utils/price'
|
||||
import { usePriceWithoutType } from '@/utils/usePrice'
|
||||
import { getPriceDisplayMessage, getPriceDisplayState, NO_QUOTE_MESSAGE } from '@/utils/storeHours'
|
||||
import { getStoreClosedMessage, isStoreClosedBySchedule, NO_QUOTE_MESSAGE } from '@/utils/storeHours'
|
||||
import { isMeltedTradeGroup, isMetalCommodityGroup } from '@/utils/productCategories'
|
||||
import { priceSocketConnected, priceStreamLifecycle } from '@/utils/priceSocketState'
|
||||
import logoFallback from '@/assets/img/logo_full_.png'
|
||||
@@ -21,7 +21,7 @@ moment.loadPersian({ usePersianDigits: false })
|
||||
|
||||
const CURSOR_HIDE_MS = 2500
|
||||
const TV_HTML_CLASS = 'app-layout--tv'
|
||||
const REFRESH_MS = 15000
|
||||
const REFRESH_MS = 3000
|
||||
/** دقت تابلو: هزار تومان = ۱۰٬۰۰۰ ریال — باقیمانده به پایین حذف میشود */
|
||||
const TV_TOMAN_STEP = 1000
|
||||
|
||||
@@ -213,14 +213,34 @@ export function useTvMonitorPage() {
|
||||
|| tvNewsItems.value.length > 0,
|
||||
)
|
||||
|
||||
const statusBanner = computed(() => {
|
||||
void lastPrice.value
|
||||
const state = getPriceDisplayState()
|
||||
return state === 'open' ? '' : getPriceDisplayMessage(state)
|
||||
const statusBanner = computed(() => (isStoreClosedBySchedule() ? getStoreClosedMessage() : ''))
|
||||
|
||||
const mainTvProduct = computed(() => {
|
||||
void settingsStore.settingsRevision
|
||||
const raw = String(settingsStore.getSettingByKey('main_product') ?? '').trim()
|
||||
if (!raw) return null
|
||||
const match = catalogProducts.value.find(
|
||||
(product) => String(product.id) === raw || String(product.reading_method ?? '') === raw,
|
||||
)
|
||||
if (!match) return null
|
||||
if (products.value.some(isShowOnTv) && !isShowOnTv(match)) return null
|
||||
return match
|
||||
})
|
||||
|
||||
const liveBuy = computed(() => getTradeBuyPrice(lastPrice.value))
|
||||
const liveSell = computed(() => getTradeSellPrice(lastPrice.value))
|
||||
const showMainQuote = computed(() => !!mainTvProduct.value)
|
||||
|
||||
const liveBuy = computed(() => {
|
||||
void lastPrice.value
|
||||
const product = mainTvProduct.value
|
||||
return product ? getProductBuyPrice(product) : 0
|
||||
})
|
||||
const liveSell = computed(() => {
|
||||
void lastPrice.value
|
||||
const product = mainTvProduct.value
|
||||
return product ? getProductSellPrice(product) : 0
|
||||
})
|
||||
|
||||
const mainQuoteReady = computed(() => liveBuy.value > 0 || liveSell.value > 0)
|
||||
|
||||
const isLive = computed(
|
||||
() => priceSocketConnected.value || priceStreamLifecycle.value === 'open',
|
||||
@@ -315,8 +335,12 @@ export function useTvMonitorPage() {
|
||||
return simorghWeightLabel(product)
|
||||
}
|
||||
|
||||
function tvQuoteMissing(product: Product) {
|
||||
return Number(product.tv_price ?? 0) <= 0 && Number(product.tv_sell_price ?? 0) <= 0
|
||||
}
|
||||
|
||||
function isProductTvInactive(product: Product) {
|
||||
return isTvProductPriceInactive(product)
|
||||
return tvQuoteMissing(product)
|
||||
}
|
||||
|
||||
function productBuyLabel(product: Product) {
|
||||
@@ -330,7 +354,9 @@ export function useTvMonitorPage() {
|
||||
}
|
||||
|
||||
function liveQuoteLabel(value: number) {
|
||||
return formatTvPrice(value)
|
||||
if (!mainQuoteReady.value) return NO_QUOTE_MESSAGE
|
||||
const label = formatTvPrice(value)
|
||||
return label === '—' ? NO_QUOTE_MESSAGE : label
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -384,6 +410,8 @@ export function useTvMonitorPage() {
|
||||
tvNewsItems,
|
||||
newsTickerTitle,
|
||||
statusBanner,
|
||||
showMainQuote,
|
||||
mainQuoteReady,
|
||||
liveBuy,
|
||||
liveSell,
|
||||
isLive,
|
||||
|
||||
+26
-5
@@ -15,18 +15,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tv-monitor__quote">
|
||||
<div v-if="showMainQuote" class="tv-monitor__quote">
|
||||
<span class="tv-monitor__quote-label">مظنه لحظهای</span>
|
||||
<div class="tv-monitor__quote-prices">
|
||||
<div class="tv-monitor__quote-item tv-monitor__quote-item--sell">
|
||||
<span>فروش</span>
|
||||
<strong>{{ liveQuoteLabel(liveBuy) }}</strong>
|
||||
<em>تومان</em>
|
||||
<strong :class="{ 'is-empty': !mainQuoteReady }">{{ liveQuoteLabel(liveSell) }}</strong>
|
||||
<em v-if="mainQuoteReady">تومان</em>
|
||||
</div>
|
||||
<div class="tv-monitor__quote-item tv-monitor__quote-item--buy">
|
||||
<span>خرید</span>
|
||||
<strong>{{ liveQuoteLabel(liveSell) }}</strong>
|
||||
<em>تومان</em>
|
||||
<strong :class="{ 'is-empty': !mainQuoteReady }">{{ liveQuoteLabel(liveBuy) }}</strong>
|
||||
<em v-if="mainQuoteReady">تومان</em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -267,6 +267,9 @@ const {
|
||||
tvNewsItems,
|
||||
newsTickerTitle,
|
||||
statusBanner,
|
||||
showMainQuote,
|
||||
mainQuoteReady,
|
||||
mainQuoteReady,
|
||||
liveBuy,
|
||||
liveSell,
|
||||
isLive,
|
||||
@@ -389,6 +392,7 @@ const coinColumns = computed(() => [
|
||||
}
|
||||
|
||||
.tv-monitor__brand {
|
||||
grid-column: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
@@ -419,6 +423,7 @@ const coinColumns = computed(() => [
|
||||
}
|
||||
|
||||
.tv-monitor__quote {
|
||||
grid-column: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -476,9 +481,17 @@ const coinColumns = computed(() => [
|
||||
&--buy strong {
|
||||
color: var(--tv-buy);
|
||||
}
|
||||
|
||||
strong.is-empty {
|
||||
font-size: clamp(13px, 1.15vw, 18px);
|
||||
font-weight: 600;
|
||||
direction: rtl;
|
||||
color: var(--tv-muted);
|
||||
}
|
||||
}
|
||||
|
||||
.tv-monitor__clock {
|
||||
grid-column: 3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
@@ -1092,6 +1105,14 @@ const coinColumns = computed(() => [
|
||||
order: 3;
|
||||
}
|
||||
|
||||
.tv-monitor__clock {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.tv-monitor__clock {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.tv-monitor__columns {
|
||||
grid-template-columns: 1fr;
|
||||
overflow: auto;
|
||||
|
||||
Reference in New Issue
Block a user