From b211c8773b287ecd331c2ae374a8731f2a0ab497 Mon Sep 17 00:00:00 2001 From: mohammad sedighi Date: Fri, 11 Sep 2026 16:44:28 +0330 Subject: [PATCH] tv changes --- src/views/tv/composables/useTvMonitorPage.ts | 59 +++++++-- src/views/tv/index.vue | 123 +++++++++++++++---- 2 files changed, 154 insertions(+), 28 deletions(-) diff --git a/src/views/tv/composables/useTvMonitorPage.ts b/src/views/tv/composables/useTvMonitorPage.ts index c1d61bd..ed73214 100644 --- a/src/views/tv/composables/useTvMonitorPage.ts +++ b/src/views/tv/composables/useTvMonitorPage.ts @@ -3,12 +3,13 @@ import { useFullscreen } from '@vueuse/core' import type { Product } from '@/models/products' import { getTvProducts } 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 { usePriceWithoutType } from '@/utils/usePrice' import { getPriceDisplayMessage, getPriceDisplayState, NO_QUOTE_MESSAGE } from '@/utils/storeHours' import { isMeltedTradeGroup, isMetalCommodityGroup } from '@/utils/productCategories' import { priceSocketConnected, priceStreamLifecycle } from '@/utils/priceSocketState' -import { usePrice } from '@/utils/usePrice' import logoFallback from '@/assets/img/logo_full_.png' //@ts-ignore import moment from 'moment-jalaali' @@ -21,6 +22,37 @@ moment.loadPersian({ usePersianDigits: false }) const CURSOR_HIDE_MS = 2500 const TV_HTML_CLASS = 'app-layout--tv' const REFRESH_MS = 15000 +/** دقت تابلو: هزار تومان = ۱۰٬۰۰۰ ریال — باقی‌مانده به پایین حذف می‌شود */ +const TV_TOMAN_STEP = 1000 + +function parseTvNews(raw: unknown): string[] { + const text = String(raw ?? '').trim() + if (!text) return [] + + try { + const decoded = JSON.parse(text) + if (Array.isArray(decoded)) { + return decoded.map((item) => String(item ?? '').trim()).filter(Boolean) + } + } catch { + // هر خط یک خبر + } + + return text + .split(/\r?\n/) + .map((line) => line.replace(/^[-•*]\s+/, '').trim()) + .filter(Boolean) +} + +function floorTvToman(rial: number): number { + if (!Number.isFinite(rial) || rial <= 0) return 0 + return Math.floor(rial / 10 / TV_TOMAN_STEP) * TV_TOMAN_STEP +} + +function formatTvPrice(rial: number): string { + const toman = floorTvToman(rial) + return toman > 0 ? usePriceWithoutType(toman) : '—' +} function productText(product: Product) { return `${product.group_type ?? ''} ${product.title ?? ''}` @@ -70,6 +102,7 @@ function isGoldBar(product: Product) { } export function useTvMonitorPage() { + const settingsStore = useSettingsStore() const rootEl = ref(null) const { isFullscreen, enter: enterFullscreen, exit: exitFullscreen } = useFullscreen(rootEl) @@ -143,11 +176,23 @@ export function useTvMonitorPage() { extraTickerProducts.value.length ? 'سایر مظنه‌ها' : '', ) + const tvNewsItems = computed(() => { + void settingsStore.settingsRevision + const raw = settingsStore.getSettingByKey('tv_news') ?? getConfigApp('tv_news') + if (raw == null) { + return ['تا حالا از اپ کشکول طلای آنلاین خریدی؟'] + } + return parseTvNews(raw) + }) + + const newsTickerTitle = computed(() => (tvNewsItems.value.length ? 'اخبار' : '')) + const hasBoardContent = computed( () => hasCoinBoard.value || tickerProducts.value.length > 0 - || extraTickerProducts.value.length > 0, + || extraTickerProducts.value.length > 0 + || tvNewsItems.value.length > 0, ) const statusBanner = computed(() => { @@ -258,18 +303,16 @@ export function useTvMonitorPage() { function productBuyLabel(product: Product) { if (isProductTvInactive(product)) return NO_QUOTE_MESSAGE - const value = getTvProductBuyPrice(product) - return value > 0 ? usePrice(value) : '—' + return formatTvPrice(getTvProductBuyPrice(product)) } function productSellLabel(product: Product) { if (isProductTvInactive(product)) return NO_QUOTE_MESSAGE - const value = getTvProductSellPrice(product) - return value > 0 ? usePrice(value) : '—' + return formatTvPrice(getTvProductSellPrice(product)) } function liveQuoteLabel(value: number) { - return value > 0 ? usePrice(value) : '—' + return formatTvPrice(value) } onMounted(async () => { @@ -318,6 +361,8 @@ export function useTvMonitorPage() { tickerTitle, extraTickerProducts, extraTickerTitle, + tvNewsItems, + newsTickerTitle, statusBanner, liveBuy, liveSell, diff --git a/src/views/tv/index.vue b/src/views/tv/index.vue index d6564b4..6c92d35 100644 --- a/src/views/tv/index.vue +++ b/src/views/tv/index.vue @@ -21,10 +21,12 @@
فروش {{ liveQuoteLabel(liveBuy) }} + تومان
خرید {{ liveQuoteLabel(liveSell) }} + تومان
@@ -119,12 +121,14 @@ :class="{ 'tv-row__price--empty': isProductTvInactive(item) }" > {{ productBuyLabel(item) }} + تومان
{{ productSellLabel(item) }} + تومان
@@ -146,7 +150,7 @@
{{ tickerTitle }} @@ -163,19 +167,42 @@ فروش {{ productBuyLabel(entry.product) }} + تومان خرید {{ productSellLabel(entry.product) }} + تومان
+
+ {{ newsTickerTitle }} + اطلاع‌رسانی +
+
+
+
+ {{ entry.text }} +
+
+
+
+
{{ extraTickerTitle }} @@ -192,10 +219,12 @@ فروش {{ productBuyLabel(entry.product) }} + تومان خرید {{ productSellLabel(entry.product) }} + تومان
@@ -235,6 +264,8 @@ const { tickerTitle, extraTickerProducts, extraTickerTitle, + tvNewsItems, + newsTickerTitle, statusBanner, liveBuy, liveSell, @@ -268,8 +299,21 @@ function loopTicker(items: Product[]) { return copies.flatMap((copy) => items.map((product) => ({ product, copy }))) } +function loopNews(items: string[]) { + if (!items.length) return [] + const copies = items.length >= 1 && !prefersReducedMotion() ? [0, 1] : [0] + return copies.flatMap((copy) => items.map((text, index) => ({ text, copy, index }))) +} + +function newsDuration(items: string[]) { + const chars = items.reduce((sum, text) => sum + text.length, 0) + return Math.max(items.length * 22, Math.ceil(chars / 3), 90) +} + const shamsTickerLoop = computed(() => loopTicker(tickerProducts.value)) const extraTickerLoop = computed(() => loopTicker(extraTickerProducts.value)) +const newsTickerLoop = computed(() => loopNews(tvNewsItems.value)) +const newsTickerDuration = computed(() => newsDuration(tvNewsItems.value)) const coinColumns = computed(() => [ { @@ -391,8 +435,8 @@ const coinColumns = computed(() => [ } .tv-monitor__quote-item { - min-width: 180px; - padding: 8px 14px; + min-width: 220px; + padding: 10px 18px; border-radius: 14px; text-align: center; background: var(--tv-quote-bg); @@ -400,18 +444,27 @@ const coinColumns = computed(() => [ span { display: block; - font-size: 12px; + font-size: 13px; color: var(--tv-muted); } strong { display: block; margin-top: 2px; - font-size: clamp(16px, 1.5vw, 22px); - font-weight: 700; + font-size: clamp(22px, 2.4vw, 38px); + font-weight: 800; font-family: iran-sans, sans-serif; direction: ltr; unicode-bidi: isolate; + line-height: 1.15; + } + + em { + display: block; + margin-top: 2px; + font-style: normal; + font-size: clamp(11px, 0.95vw, 14px); + color: var(--tv-muted); } &--sell strong { @@ -554,7 +607,7 @@ const coinColumns = computed(() => [ .tv-col__labels { display: grid; - grid-template-columns: 56px minmax(0, 0.9fr) minmax(0, 2fr); + grid-template-columns: 52px minmax(0, 0.7fr) minmax(0, 2.4fr); gap: 10px; align-items: center; margin-bottom: 8px; @@ -574,7 +627,7 @@ const coinColumns = computed(() => [ .tv-col__label { display: block; text-align: center; - font-size: clamp(13px, 1.1vw, 16px); + font-size: clamp(14px, 1.2vw, 18px); font-weight: 700; } @@ -648,13 +701,13 @@ const coinColumns = computed(() => [ .tv-row { position: relative; display: grid; - grid-template-columns: minmax(44px, 56px) minmax(0, 0.9fr) minmax(0, 2fr); + grid-template-columns: minmax(44px, 52px) minmax(0, 0.7fr) minmax(0, 2.4fr); align-items: center; gap: 10px; flex: 0 0 auto; - height: clamp(64px, 8vh, 80px); - max-height: 80px; - min-height: 64px; + height: clamp(76px, 10vh, 104px); + max-height: 104px; + min-height: 76px; min-width: 0; overflow: hidden; padding: 8px 12px; @@ -706,7 +759,7 @@ const coinColumns = computed(() => [ h3 { margin: 0; overflow: hidden; - font-size: clamp(13px, 1.2vw, 20px); + font-size: clamp(14px, 1.25vw, 22px); font-weight: 700; line-height: 1.3; text-overflow: ellipsis; @@ -740,11 +793,12 @@ const coinColumns = computed(() => [ .tv-row__price { display: flex; + flex-direction: column; align-items: center; justify-content: center; min-width: 0; min-height: 0; - padding: 6px 8px; + padding: 6px 6px; border-radius: 12px; text-align: center; background: transparent; @@ -761,12 +815,22 @@ const coinColumns = computed(() => [ max-width: 100%; margin-top: 0; overflow: hidden; - font-size: clamp(10px, 12cqi, 16px); - font-weight: 700; + font-size: clamp(18px, 22cqi, 34px); + font-weight: 800; font-family: iran-sans, sans-serif; white-space: nowrap; direction: ltr; unicode-bidi: isolate; + line-height: 1.1; + } + + em { + display: block; + margin-top: 2px; + font-style: normal; + font-size: clamp(10px, 8cqi, 13px); + color: var(--tv-card-muted); + line-height: 1; } &--sell strong { @@ -916,6 +980,17 @@ const coinColumns = computed(() => [ font-weight: 700; } +.tv-ticker__item--news { + gap: 18px; + padding: 0 36px; +} + +.tv-ticker__headline { + font-size: clamp(16px, 1.35vw, 22px); + font-weight: 700; + letter-spacing: 0.01em; +} + .tv-ticker__price { display: inline-flex; align-items: baseline; @@ -923,14 +998,20 @@ const coinColumns = computed(() => [ font-size: 13px; b { - font-size: clamp(13px, 1.05vw, 16px); - font-weight: 700; + font-size: clamp(16px, 1.35vw, 22px); + font-weight: 800; font-family: iran-sans, sans-serif; white-space: nowrap; direction: ltr; unicode-bidi: isolate; } + i { + font-style: normal; + font-size: 11px; + opacity: 0.78; + } + &--sell b { color: #ffe08a; } @@ -979,7 +1060,7 @@ const coinColumns = computed(() => [ .tv-col__labels, .tv-row { - grid-template-columns: minmax(40px, 48px) minmax(0, 0.85fr) minmax(0, 2.1fr); + grid-template-columns: minmax(40px, 48px) minmax(0, 0.65fr) minmax(0, 2.5fr); } }