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:
2026-09-27 21:42:17 +03:30
co-authored by Cursor
parent 7770c763cc
commit 8083c4f06d
4 changed files with 79 additions and 23 deletions
+2
View File
@@ -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
+11 -6
View File
@@ -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)) {
+40 -12
View File
@@ -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
View File
@@ -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;