tv changes
This commit is contained in:
@@ -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<HTMLElement | null>(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,
|
||||
|
||||
+102
-21
@@ -21,10 +21,12 @@
|
||||
<div class="tv-monitor__quote-item tv-monitor__quote-item--sell">
|
||||
<span>فروش</span>
|
||||
<strong>{{ liveQuoteLabel(liveBuy) }}</strong>
|
||||
<em>تومان</em>
|
||||
</div>
|
||||
<div class="tv-monitor__quote-item tv-monitor__quote-item--buy">
|
||||
<span>خرید</span>
|
||||
<strong>{{ liveQuoteLabel(liveSell) }}</strong>
|
||||
<em>تومان</em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -119,12 +121,14 @@
|
||||
:class="{ 'tv-row__price--empty': isProductTvInactive(item) }"
|
||||
>
|
||||
<strong>{{ productBuyLabel(item) }}</strong>
|
||||
<em v-if="!isProductTvInactive(item) && productBuyLabel(item) !== '—'">تومان</em>
|
||||
</div>
|
||||
<div
|
||||
class="tv-row__price tv-row__price--buy"
|
||||
:class="{ 'tv-row__price--empty': isProductTvInactive(item) }"
|
||||
>
|
||||
<strong>{{ productSellLabel(item) }}</strong>
|
||||
<em v-if="!isProductTvInactive(item) && productSellLabel(item) !== '—'">تومان</em>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@@ -146,7 +150,7 @@
|
||||
<div
|
||||
v-if="tickerProducts.length"
|
||||
class="tv-ticker"
|
||||
:style="{ '--tv-ticker-duration': `${Math.max(tickerProducts.length * 7, 36)}s` }"
|
||||
:style="{ '--tv-ticker-duration': `${Math.max(tickerProducts.length * 14, 64)}s` }"
|
||||
>
|
||||
<div class="tv-ticker__label">
|
||||
<strong>{{ tickerTitle }}</strong>
|
||||
@@ -163,19 +167,42 @@
|
||||
<span class="tv-ticker__price tv-ticker__price--sell">
|
||||
فروش
|
||||
<b>{{ productBuyLabel(entry.product) }}</b>
|
||||
<i>تومان</i>
|
||||
</span>
|
||||
<span class="tv-ticker__price tv-ticker__price--buy">
|
||||
خرید
|
||||
<b>{{ productSellLabel(entry.product) }}</b>
|
||||
<i>تومان</i>
|
||||
</span>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="extraTickerProducts.length"
|
||||
v-if="tvNewsItems.length"
|
||||
class="tv-ticker tv-ticker--extra tv-ticker--news"
|
||||
:style="{ '--tv-ticker-duration': `${newsTickerDuration}s` }"
|
||||
>
|
||||
<div class="tv-ticker__label">
|
||||
<strong>{{ newsTickerTitle }}</strong>
|
||||
<span>اطلاعرسانی</span>
|
||||
</div>
|
||||
<div class="tv-ticker__viewport">
|
||||
<div class="tv-ticker__track">
|
||||
<article
|
||||
v-for="entry in newsTickerLoop"
|
||||
:key="`news-${entry.copy}-${entry.index}`"
|
||||
class="tv-ticker__item tv-ticker__item--news"
|
||||
>
|
||||
<span class="tv-ticker__headline">{{ entry.text }}</span>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="extraTickerProducts.length"
|
||||
class="tv-ticker tv-ticker--extra"
|
||||
:style="{ '--tv-ticker-duration': `${Math.max(extraTickerProducts.length * 7, 36)}s` }"
|
||||
:style="{ '--tv-ticker-duration': `${Math.max(extraTickerProducts.length * 18, 80)}s` }"
|
||||
>
|
||||
<div class="tv-ticker__label">
|
||||
<strong>{{ extraTickerTitle }}</strong>
|
||||
@@ -192,10 +219,12 @@
|
||||
<span class="tv-ticker__price tv-ticker__price--sell">
|
||||
فروش
|
||||
<b>{{ productBuyLabel(entry.product) }}</b>
|
||||
<i>تومان</i>
|
||||
</span>
|
||||
<span class="tv-ticker__price tv-ticker__price--buy">
|
||||
خرید
|
||||
<b>{{ productSellLabel(entry.product) }}</b>
|
||||
<i>تومان</i>
|
||||
</span>
|
||||
</article>
|
||||
</div>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user