fix some things

This commit is contained in:
2026-09-14 12:57:53 +03:30
parent 569e62fef4
commit 71da064cab
6 changed files with 78 additions and 11 deletions
+19
View File
@@ -10,6 +10,25 @@ export const getTvProducts = async (): Promise<Product[]> => {
const response = await useAxios.get<unknown>(`/customer/tv/products`, { timeout: 15000 }) const response = await useAxios.get<unknown>(`/customer/tv/products`, { timeout: 15000 })
return Array.isArray(response) ? (response as Product[]) : [] return Array.isArray(response) ? (response as Product[]) : []
} }
export type TvServerTime = {
unix: number
iso: string
timezone: string
}
export const getTvServerTime = async (): Promise<TvServerTime | null> => {
try {
const response = await useAxios.get<TvServerTime>(`/customer/tv/time`, { timeout: 10000 })
if (response && typeof (response as TvServerTime).unix === 'number') {
return response as TvServerTime
}
} catch {
/* ignore */
}
return null
}
export const getProductById = async ( id:number|null): Promise<any> => { export const getProductById = async ( id:number|null): Promise<any> => {
return await useAxios.get<Product[]>(`/customer/product/list/${id}`, { timeout: 15000 }) return await useAxios.get<Product[]>(`/customer/product/list/${id}`, { timeout: 15000 })
} }
+1
View File
@@ -3,6 +3,7 @@ import type {Wallet} from "@/models/wallet";
export interface Product { export interface Product {
id: number id: number
img: string | undefined img: string | undefined
image?: string | null
title: string title: string
amount: any amount: any
metal_id: any metal_id: any
+2 -1
View File
@@ -10,6 +10,7 @@ export type ProductImageSource = {
file?: { id?: number | null; src?: string | null; full_src?: string | null; url?: string | null } | null file?: { id?: number | null; src?: string | null; full_src?: string | null; url?: string | null } | null
file_id?: number | string | null file_id?: number | string | null
img?: string | null img?: string | null
image?: string | null
group_type?: string | null group_type?: string | null
} | null | undefined } | null | undefined
@@ -103,7 +104,7 @@ export function getProductFallbackImage(product?: ProductImageSource): string {
} }
export function resolveProductImageSrc(product?: ProductImageSource): string { export function resolveProductImageSrc(product?: ProductImageSource): string {
const remoteSrc = product?.file?.full_src || product?.file?.url || product?.file?.src const remoteSrc = product?.file?.full_src || product?.file?.url || product?.file?.src || product?.image || product?.img
if (isUsableImageSrc(remoteSrc)) { if (isUsableImageSrc(remoteSrc)) {
const url = absolutizeImageUrl(remoteSrc!.trim()) const url = absolutizeImageUrl(remoteSrc!.trim())
const version = product?.file_id ?? product?.file?.id const version = product?.file_id ?? product?.file?.id
+2 -2
View File
@@ -277,7 +277,7 @@ export function getTvProductBuyPrice(product?: Product | null): number {
} }
const unit = tvBuyUnitPrice(product) const unit = tvBuyUnitPrice(product)
if (unit > 0) { if (unit > 0) {
return unit + resolveProductCashFeeBuyRial(product, unit) return unit
} }
return getProductBuyPrice(product) return getProductBuyPrice(product)
} }
@@ -288,7 +288,7 @@ export function getTvProductSellPrice(product?: Product | null): number {
} }
const unit = tvSellUnitPrice(product) const unit = tvSellUnitPrice(product)
if (unit > 0) { if (unit > 0) {
return unit - resolveProductCashFeeSellRial(product, unit) return unit
} }
return getProductSellPrice(product) return getProductSellPrice(product)
} }
+26 -4
View File
@@ -32,7 +32,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted, onUnmounted, ref } from 'vue' import { computed, onMounted, onUnmounted, ref } from 'vue'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue' import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import { getTvProducts } from '@/api/products' import { getTvProducts, getTvServerTime } from '@/api/products'
import type { Product } from '@/models/products' import type { Product } from '@/models/products'
import { getConfigApp } from '@/utils/config' import { getConfigApp } from '@/utils/config'
import { getTvProductBuyPrice, getTvProductSellPrice, isTvProductPriceInactive } from '@/utils/productPrice' import { getTvProductBuyPrice, getTvProductSellPrice, isTvProductPriceInactive } from '@/utils/productPrice'
@@ -47,6 +47,7 @@ const REFRESH_MS = 15000
const loading = ref(true) const loading = ref(true)
const products = ref<Product[]>([]) const products = ref<Product[]>([])
const now = ref(Date.now()) const now = ref(Date.now())
let serverOffsetMs = 0
let clockTimer: ReturnType<typeof setInterval> | undefined let clockTimer: ReturnType<typeof setInterval> | undefined
let listTimer: ReturnType<typeof setInterval> | undefined let listTimer: ReturnType<typeof setInterval> | undefined
@@ -57,6 +58,7 @@ const shopName = computed(
const clockText = computed(() => const clockText = computed(() =>
new Date(now.value).toLocaleTimeString('fa-IR', { new Date(now.value).toLocaleTimeString('fa-IR', {
timeZone: 'Asia/Tehran',
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit',
second: '2-digit', second: '2-digit',
@@ -99,10 +101,27 @@ const rows = computed(() => {
}) })
}) })
function normalizeTvProduct(raw: Product): Product {
const image = (raw as Product & { image?: string | null }).image
if (image && !raw.img) {
return { ...raw, img: image }
}
return raw
}
async function syncServerClock() {
const t0 = Date.now()
const payload = await getTvServerTime()
const t1 = Date.now()
if (!payload?.unix) return
const rttHalf = (t1 - t0) / 2
serverOffsetMs = payload.unix * 1000 + rttHalf - t1
}
async function loadProducts() { async function loadProducts() {
try { try {
const list = await getTvProducts() const list = await getTvProducts()
products.value = Array.isArray(list) ? list : [] products.value = (Array.isArray(list) ? list : []).map(normalizeTvProduct)
} catch { } catch {
if (!products.value.length) { if (!products.value.length) {
products.value = [] products.value = []
@@ -112,17 +131,20 @@ async function loadProducts() {
} }
} }
onMounted(() => { onMounted(async () => {
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore()
initPriceRealtimeChannel((key) => settingsStore.getSettingByKey(key)) initPriceRealtimeChannel((key) => settingsStore.getSettingByKey(key))
startLastPricePolling() startLastPricePolling()
void getLastPrices() void getLastPrices()
await syncServerClock()
now.value = Date.now() + serverOffsetMs
void loadProducts() void loadProducts()
clockTimer = setInterval(() => { clockTimer = setInterval(() => {
now.value = Date.now() now.value = Date.now() + serverOffsetMs
}, 1000) }, 1000)
listTimer = setInterval(() => { listTimer = setInterval(() => {
void loadProducts() void loadProducts()
void syncServerClock()
}, REFRESH_MS) }, REFRESH_MS)
}) })
+28 -4
View File
@@ -1,7 +1,7 @@
import { computed, onMounted, onUnmounted, ref } from 'vue' import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useFullscreen } from '@vueuse/core' import { useFullscreen } from '@vueuse/core'
import type { Product } from '@/models/products' import type { Product } from '@/models/products'
import { getTvProducts } from '@/api/products' import { getTvProducts, getTvServerTime } from '@/api/products'
import { getConfigApp } from '@/utils/config' import { getConfigApp } from '@/utils/config'
import { useSettingsStore } from '@/store/settingsStore' import { useSettingsStore } from '@/store/settingsStore'
import { getTvProductBuyPrice, getTvProductSellPrice, isTvProductPriceInactive } from '@/utils/productPrice' import { getTvProductBuyPrice, getTvProductSellPrice, isTvProductPriceInactive } from '@/utils/productPrice'
@@ -73,7 +73,10 @@ function isBankCoin(product: Product) {
const title = (product.title ?? '').trim() const title = (product.title ?? '').trim()
if (group === 'سکه' || group === 'سکه بانکی') return true if (group === 'سکه' || group === 'سکه بانکی') return true
if (group.includes('سکه') && !group.includes('پارسیان')) return true if (group.includes('سکه') && !group.includes('پارسیان')) return true
return title.includes('بانکی') if (title.includes('بانکی')) return true
// API سبک TV ممکن است group_type نداشته باشد
if (title.includes('سکه') && !title.includes('پارسیان')) return true
return false
} }
function bankCoinRank(product: Product) { function bankCoinRank(product: Product) {
@@ -116,6 +119,8 @@ export function useTvMonitorPage() {
let clockTimer: ReturnType<typeof setInterval> | undefined let clockTimer: ReturnType<typeof setInterval> | undefined
let cursorTimer: ReturnType<typeof setTimeout> | undefined let cursorTimer: ReturnType<typeof setTimeout> | undefined
let listTimer: ReturnType<typeof setInterval> | undefined let listTimer: ReturnType<typeof setInterval> | undefined
/** اختلاف میلی‌ثانیهٔ ساعت سرور (Asia/Tehran) با Date.now محلی */
let serverOffsetMs = 0
const brandName = computed( const brandName = computed(
() => String(getConfigApp('name_fa') || getConfigApp('name') || 'تابلو قیمت'), () => String(getConfigApp('name_fa') || getConfigApp('name') || 'تابلو قیمت'),
@@ -130,6 +135,23 @@ export function useTvMonitorPage() {
return marked.length ? marked : list return marked.length ? marked : list
}) })
function normalizeTvProduct(raw: Product): Product {
const image = (raw as Product & { image?: string | null }).image
if (image && !raw.img) {
return { ...raw, img: image }
}
return raw
}
async function syncServerClock() {
const t0 = Date.now()
const payload = await getTvServerTime()
const t1 = Date.now()
if (!payload?.unix) return
const rttHalf = (t1 - t0) / 2
serverOffsetMs = payload.unix * 1000 + rttHalf - t1
}
const parsianCoins = computed(() => const parsianCoins = computed(() =>
catalogProducts.value catalogProducts.value
.filter(isParsianCoin) .filter(isParsianCoin)
@@ -207,7 +229,7 @@ export function useTvMonitorPage() {
async function loadProducts() { async function loadProducts() {
try { try {
const list = await getTvProducts() const list = await getTvProducts()
products.value = Array.isArray(list) ? list : [] products.value = (Array.isArray(list) ? list : []).map(normalizeTvProduct)
} catch { } catch {
if (!products.value.length) { if (!products.value.length) {
products.value = [] products.value = []
@@ -218,7 +240,7 @@ export function useTvMonitorPage() {
} }
function updateClock() { function updateClock() {
const now = moment() const now = moment(Date.now() + serverOffsetMs).utcOffset(210)
nowLabel.value = now.format('HH:mm:ss') nowLabel.value = now.format('HH:mm:ss')
dateLabel.value = now.format('dddd jD jMMMM jYYYY') dateLabel.value = now.format('dddd jD jMMMM jYYYY')
} }
@@ -313,6 +335,7 @@ export function useTvMonitorPage() {
onMounted(async () => { onMounted(async () => {
document.documentElement.classList.add(TV_HTML_CLASS) document.documentElement.classList.add(TV_HTML_CLASS)
await syncServerClock()
updateClock() updateClock()
hideCursorSoon() hideCursorSoon()
clockTimer = setInterval(updateClock, 1000) clockTimer = setInterval(updateClock, 1000)
@@ -325,6 +348,7 @@ export function useTvMonitorPage() {
void loadProducts() void loadProducts()
listTimer = setInterval(() => { listTimer = setInterval(() => {
void loadProducts() void loadProducts()
void syncServerClock()
}, REFRESH_MS) }, REFRESH_MS)
document.title = `${brandName.value} | تابلو قیمت` document.title = `${brandName.value} | تابلو قیمت`