feat(quotes): show world copper, dollar, ton, and kilo on home
Add assets chart hover so the portfolio line shows the value under the pointer. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -66,3 +66,6 @@
|
||||
- 2026-11-03 | ویرایش/حذف تبدیل در انتظار | همان تاریخچه + `CopperConvertRequestService::update/remove` | فقط status=pending؛ ویرایش فرم را با همان حلقهها پر میکند و PUT میزند
|
||||
- 2026-11-03 | چیدمان دسکتاپ تبدیل | `convert/desktop/DesktopConvertView.vue` | مثل کیف پول: `dwallet-grid`؛ فرم راست، تاریخچه چپ با `variant=side`
|
||||
- 2026-11-03 | مداد ویرایش تبدیل | `convert/components/CopperConvertHistory.vue` | فقط pending؛ آیکون `vuesax-linear:edit` کنار وضعیت، همان `beginEdit`
|
||||
- 2026-11-03 | چهار قیمت هیرو خانه | `home/desktop/components/DesktopLivePrice.vue` + سرور `ZaarboxPriceService::copperDollarReferences` | مس جهانی=کلید `basemetal_london_copper`، دلار آزاد=`tgju_usd`؛ تن=ضرب، کیلو=تقسیم بر ۱۰۰۰
|
||||
- 2026-11-03 | چهار قیمت موبایل خانه | `home/components/CurrentPrice.vue` + `composables/useHomeQuoteRows.ts` | همان چهار ردیف دسکتاپ؛ منطق مشترک در useHomeQuoteRows
|
||||
- 2026-11-03 | هاور نمودار دارایی | `assets/index.vue` + `useAssetsPortfolioChart.ts` + `chartAreaPaths.ts` | نزدیکترین نقطهٔ رسمشده؛ مقدار همان نمونهٔ دارایی، نه قیمت مس
|
||||
|
||||
@@ -35,4 +35,8 @@ export interface Price {
|
||||
/** montzar | zaarbox | main_product | store = قیمت زنده، database_last = آخرین قیمت ذخیرهشده */
|
||||
price_source?: string | null
|
||||
last_historical?: number
|
||||
copper_world?: number
|
||||
dollar_free?: number
|
||||
copper_ton?: string
|
||||
copper_kilo?: string
|
||||
}
|
||||
@@ -3,6 +3,13 @@ export type ChartSeriesPoint = {
|
||||
value: number
|
||||
}
|
||||
|
||||
export type ChartPlotSample = {
|
||||
x: number
|
||||
y: number
|
||||
value: number
|
||||
timeMs: number
|
||||
}
|
||||
|
||||
const CHART_PAD_Y = 14
|
||||
const DEFAULT_MAX_POINTS = 22
|
||||
|
||||
@@ -67,16 +74,17 @@ export function buildSeriesChartPaths(
|
||||
linePath: '',
|
||||
areaPath: '',
|
||||
endDot: null as { x: number; y: number } | null,
|
||||
samples: [] as ChartPlotSample[],
|
||||
width,
|
||||
height,
|
||||
}
|
||||
if (points.length < 2) return empty
|
||||
|
||||
const maxPoints = options?.maxPoints ?? DEFAULT_MAX_POINTS
|
||||
const useSmooth = options?.smooth ?? true
|
||||
|
||||
let series = downsampleSeries(points, maxPoints)
|
||||
if (useSmooth) {
|
||||
series = smoothSeries(series, 3)
|
||||
}
|
||||
const sampled = downsampleSeries(points, maxPoints)
|
||||
const series = useSmooth ? smoothSeries(sampled, 3) : sampled
|
||||
|
||||
const values = series.map((point) => point.value)
|
||||
let min = Math.min(...values)
|
||||
@@ -97,11 +105,20 @@ export function buildSeriesChartPaths(
|
||||
const linePath = buildSmoothLinePath(plot)
|
||||
const areaPath = `${linePath} L${width} ${height} L0 ${height} Z`
|
||||
const last = plot[plot.length - 1]
|
||||
const samples = plot.map((point, index) => ({
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
value: sampled[index]?.value ?? series[index].value,
|
||||
timeMs: sampled[index]?.timeMs ?? series[index].timeMs,
|
||||
}))
|
||||
|
||||
return {
|
||||
hasData: true,
|
||||
linePath,
|
||||
areaPath,
|
||||
endDot: last ? { x: last.x, y: last.y } : null,
|
||||
samples,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
type LandingHistoryRange,
|
||||
} from '@/api/zaarboxPriceHistory'
|
||||
import { buildSeriesChartPaths } from '@/utils/chartAreaPaths'
|
||||
import { usePrice } from '@/utils/usePrice'
|
||||
const CHART_RANGE_OPTIONS = [
|
||||
{ value: 'DAILY' as const, shortLabel: '۲۴ ساعت', label: 'ارزش دارایی — ۲۴ ساعت گذشته' },
|
||||
{ value: 'WEEKLY' as const, shortLabel: 'هفته', label: 'ارزش دارایی — هفته گذشته' },
|
||||
@@ -32,6 +33,7 @@ export function useAssetsPortfolioChart(
|
||||
}
|
||||
|
||||
function setChartRange(range: LandingHistoryRange) {
|
||||
chartHover.value = null
|
||||
chartRange.value = range
|
||||
void loadChartHistory(range)
|
||||
}
|
||||
@@ -61,6 +63,53 @@ export function useAssetsPortfolioChart(
|
||||
|
||||
const portfolioChart = computed(() => buildSeriesChartPaths(portfolioSeries.value))
|
||||
|
||||
const chartHover = ref<{
|
||||
xPercent: number
|
||||
yPercent: number
|
||||
valueLabel: string
|
||||
timeLabel: string
|
||||
} | null>(null)
|
||||
|
||||
function formatChartTimeLabel(timeMs: number) {
|
||||
try {
|
||||
return new Intl.DateTimeFormat('fa-IR', {
|
||||
timeZone: 'Asia/Tehran',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
}).format(new Date(timeMs))
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
function onChartPointerMove(event: PointerEvent) {
|
||||
const chart = portfolioChart.value
|
||||
if (!chart.hasData || chart.samples.length < 2) {
|
||||
chartHover.value = null
|
||||
return
|
||||
}
|
||||
const target = event.currentTarget as HTMLElement | null
|
||||
if (!target) return
|
||||
const rect = target.getBoundingClientRect()
|
||||
if (rect.width <= 0 || rect.height <= 0) return
|
||||
const ratio = Math.min(1, Math.max(0, (event.clientX - rect.left) / rect.width))
|
||||
const index = Math.round(ratio * (chart.samples.length - 1))
|
||||
const sample = chart.samples[index]
|
||||
if (!sample) return
|
||||
chartHover.value = {
|
||||
xPercent: (sample.x / chart.width) * 100,
|
||||
yPercent: (sample.y / chart.height) * 100,
|
||||
valueLabel: usePrice(sample.value),
|
||||
timeLabel: formatChartTimeLabel(sample.timeMs),
|
||||
}
|
||||
}
|
||||
|
||||
function onChartPointerLeave() {
|
||||
chartHover.value = null
|
||||
}
|
||||
|
||||
watch(
|
||||
() => cashRial.value + metalRial.value,
|
||||
(total) => {
|
||||
@@ -81,6 +130,9 @@ export function useAssetsPortfolioChart(
|
||||
chartDrawn,
|
||||
chartLoading,
|
||||
portfolioChart,
|
||||
chartHover,
|
||||
onChartPointerMove,
|
||||
onChartPointerLeave,
|
||||
setChartRange,
|
||||
initAssetsPortfolioChart,
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
<div
|
||||
class="assets-chart__plot"
|
||||
:class="{ 'is-drawn': chartDrawn }"
|
||||
@pointermove="onChartPointerMove"
|
||||
@pointerleave="onChartPointerLeave"
|
||||
>
|
||||
<svg
|
||||
v-if="portfolioChart.hasData"
|
||||
@@ -54,6 +56,30 @@
|
||||
</g>
|
||||
</svg>
|
||||
<p v-else class="assets-chart__empty">نمودار ارزش دارایی در دسترس نیست</p>
|
||||
<template v-if="chartHover">
|
||||
<div
|
||||
class="assets-chart__crosshair"
|
||||
:style="{ left: `${chartHover.xPercent}%` }"
|
||||
/>
|
||||
<div
|
||||
class="assets-chart__dot"
|
||||
:style="{
|
||||
left: `${chartHover.xPercent}%`,
|
||||
top: `${chartHover.yPercent}%`,
|
||||
}"
|
||||
/>
|
||||
<div
|
||||
class="assets-chart__tip"
|
||||
:class="{
|
||||
'is-near-end': chartHover.xPercent > 68,
|
||||
'is-near-start': chartHover.xPercent < 22,
|
||||
}"
|
||||
:style="{ left: `${chartHover.xPercent}%` }"
|
||||
>
|
||||
<strong>{{ chartHover.valueLabel }}</strong>
|
||||
<span v-if="chartHover.timeLabel">{{ chartHover.timeLabel }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="assets-chart__ranges">
|
||||
<button
|
||||
@@ -216,8 +242,11 @@ const {
|
||||
chartRange,
|
||||
chartRangeOptions,
|
||||
chartDrawn,
|
||||
portfolioChart,
|
||||
setChartRange,
|
||||
portfolioChart,
|
||||
chartHover,
|
||||
onChartPointerMove,
|
||||
onChartPointerLeave,
|
||||
setChartRange,
|
||||
initAssetsPortfolioChart,
|
||||
} = useAssetsPortfolioChart(cashWallet, metalRialValue)
|
||||
const portfolioInsight = computed(() => assetsSummary.value?.portfolio)
|
||||
@@ -453,6 +482,67 @@ onMounted(async () => {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.assets-chart__crosshair {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
background: rgba(201, 138, 95, 0.45);
|
||||
pointer-events: none;
|
||||
transform: translateX(-50%);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.assets-chart__dot {
|
||||
position: absolute;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
border-radius: 50%;
|
||||
background: #c98a5f;
|
||||
box-shadow: 0 0 0 5px rgba(201, 138, 95, 0.2);
|
||||
pointer-events: none;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.assets-chart__tip {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
z-index: 4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
padding: 6px 8px;
|
||||
border-radius: 10px;
|
||||
background: rgba(26, 43, 71, 0.94);
|
||||
color: #fff;
|
||||
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.18);
|
||||
pointer-events: none;
|
||||
transform: translateX(-50%);
|
||||
white-space: nowrap;
|
||||
|
||||
&.is-near-end {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
&.is-near-start {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
strong {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
direction: ltr;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
span {
|
||||
color: rgba(255, 255, 255, 0.78);
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.assets-chart__ranges {
|
||||
display: flex;
|
||||
gap: 0.4rem;
|
||||
|
||||
@@ -29,31 +29,15 @@
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="price-label price-label--boursecu">
|
||||
قیمت لحظهای کاتد مس
|
||||
</div>
|
||||
<div class="w-full items-center flex justify-center">
|
||||
<div v-if="price" class="flex gap-1 text-xs items-center">
|
||||
<div
|
||||
v-if="percentChange !== 0"
|
||||
class="price-change-badge"
|
||||
:class="percentChange > 0 ? 'price-change-badge--up' : 'price-change-badge--down'"
|
||||
>
|
||||
<Icon
|
||||
:key="displayGoldVal"
|
||||
:size="15"
|
||||
:icon="percentChange > 0 ? 'vuesax-linear:send' : 'vuesax-linear:received'"
|
||||
:color="percentChange > 0 ? 'green-600' : 'red-600'"
|
||||
/>
|
||||
<span class="price-change-badge__value">{{ formatSignedPercentChange(percentChange) }}</span>
|
||||
</div>
|
||||
<span
|
||||
class="o-text !text-xl font-bold price-odometer--boursecu"
|
||||
style="direction: ltr"
|
||||
>{{ usePriceWithoutType(displayGoldVal) }}</span>
|
||||
<span class="price-unit--boursecu">ریال</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="hero-quotes">
|
||||
<li v-for="row in quoteRows" :key="row.label" class="hero-quotes__row">
|
||||
<span class="hero-quotes__label">{{ row.label }}</span>
|
||||
<span class="hero-quotes__value" dir="ltr">
|
||||
<bdi>{{ row.value }}</bdi>
|
||||
<small v-if="row.unit">{{ row.unit }}</small>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -62,7 +46,7 @@
|
||||
import { computed } from 'vue'
|
||||
import Icon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { getTradeBuyPrice, lastPrice, percentPriceChanged, price } from '@/utils/price'
|
||||
import { useHomeQuoteRows } from '@/views/base/home/composables/useHomeQuoteRows'
|
||||
import {
|
||||
NO_QUOTE_MESSAGE,
|
||||
STORE_CLOSED_TITLE,
|
||||
@@ -70,7 +54,6 @@ import {
|
||||
getStoreClosedHint,
|
||||
isHomeQuotePending,
|
||||
} from '@/utils/storeHours'
|
||||
import { formatSignedPercentChange, usePriceWithoutType } from '@/utils/usePrice'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
@@ -98,17 +81,7 @@ const containerClass = computed(() => {
|
||||
return 'boursecu-price-box boursecu-price-box--open'
|
||||
})
|
||||
|
||||
const percentChange = computed(() => {
|
||||
if (!getTradeBuyPrice(lastPrice.value)) return 0
|
||||
return percentPriceChanged()
|
||||
})
|
||||
|
||||
const displayGoldVal = computed(() => {
|
||||
if (displayState.value !== 'open') return 0
|
||||
return getTradeBuyPrice(lastPrice.value)
|
||||
})
|
||||
|
||||
void price
|
||||
const { quoteRows } = useHomeQuoteRows()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -128,20 +101,42 @@ void price
|
||||
min-height: 88px;
|
||||
}
|
||||
|
||||
.price-label--boursecu {
|
||||
.hero-quotes {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
padding: 0 12px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.hero-quotes__row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.hero-quotes__label {
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.price-odometer--boursecu {
|
||||
color: #ffffff !important;
|
||||
.hero-quotes__value {
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.price-unit--boursecu {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
.hero-quotes__value small {
|
||||
margin-left: 4px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@@ -191,30 +186,4 @@ void price
|
||||
.status-box--boursecu .status-box__hint {
|
||||
color: rgba(255, 255, 255, 0.78);
|
||||
}
|
||||
|
||||
.price-change-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.2rem 0.65rem;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.price-change-badge--up {
|
||||
background: #dcfce7;
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.price-change-badge--down {
|
||||
background: #fee2e2;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.price-change-badge__value {
|
||||
direction: ltr;
|
||||
unicode-bidi: isolate;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { lastPrice } from '@/utils/price'
|
||||
import { usePriceWithoutType } from '@/utils/usePrice'
|
||||
|
||||
const BOARD_FEED_URL = 'https://price.zaarbox.ir/api/free'
|
||||
|
||||
type BoardRefs = { copper: number; dollar: number }
|
||||
|
||||
const boardCache = ((globalThis as typeof globalThis & {
|
||||
__boursecuBoardRefs?: { started: boolean; refs: BoardRefs | null }
|
||||
}).__boursecuBoardRefs ??= { started: false, refs: null })
|
||||
|
||||
function boardRowTotal(row: Record<string, unknown>): number {
|
||||
const price = Math.round(Number(row.price) || 0)
|
||||
if (price <= 0) return 0
|
||||
return price + Math.round(Number(row.profit) || 0)
|
||||
}
|
||||
|
||||
function worldCopperScore(row: Record<string, unknown>): number | null {
|
||||
const key = String(row.key ?? '').trim().toLowerCase()
|
||||
const name = String(row.name ?? '').trim()
|
||||
if (key.includes('basemetal_london_copper')) return 0
|
||||
if (name === 'TGJU فلز مس') return 1
|
||||
if (name.includes('مس جهانی')) return 2
|
||||
return null
|
||||
}
|
||||
|
||||
function freeDollarScore(row: Record<string, unknown>): number | null {
|
||||
const key = String(row.key ?? '').trim().toLowerCase()
|
||||
const name = String(row.name ?? '').trim()
|
||||
if (key.includes('copper') || name.includes('کاتد') || name.includes('مس')) return null
|
||||
if (key === 'tgju_usd') return 0
|
||||
if (name === 'TGJU دلار' || name === 'tgju دلار آمریکا') return 1
|
||||
if (name.includes('دلار') && name.includes('آزاد')) return 2
|
||||
return null
|
||||
}
|
||||
|
||||
function pickBoardRefs(rows: unknown[]) {
|
||||
let copper = 0
|
||||
let dollar = 0
|
||||
let copperScore = Number.POSITIVE_INFINITY
|
||||
let dollarScore = Number.POSITIVE_INFINITY
|
||||
for (const item of rows) {
|
||||
if (!item || typeof item !== 'object') continue
|
||||
const row = item as Record<string, unknown>
|
||||
const total = boardRowTotal(row)
|
||||
if (total <= 0) continue
|
||||
const copperRank = worldCopperScore(row)
|
||||
if (copperRank !== null && copperRank < copperScore) {
|
||||
copperScore = copperRank
|
||||
copper = total
|
||||
}
|
||||
const dollarRank = freeDollarScore(row)
|
||||
if (dollarRank !== null && dollarRank < dollarScore) {
|
||||
dollarScore = dollarRank
|
||||
dollar = total
|
||||
}
|
||||
}
|
||||
return copper > 0 && dollar > 0 ? { copper, dollar } : null
|
||||
}
|
||||
|
||||
const sharedBoardRefs = ref<BoardRefs | null>(boardCache.refs)
|
||||
|
||||
export function useHomeQuoteRows() {
|
||||
function ensureBoardRefs() {
|
||||
const liveCopper = Number(lastPrice.value?.copper_world ?? 0)
|
||||
const liveDollar = Number(lastPrice.value?.dollar_free ?? 0)
|
||||
if (liveCopper > 0 && liveDollar > 0) return
|
||||
if (boardCache.refs) {
|
||||
sharedBoardRefs.value = boardCache.refs
|
||||
return
|
||||
}
|
||||
if (boardCache.started) return
|
||||
boardCache.started = true
|
||||
void fetch(BOARD_FEED_URL)
|
||||
.then((res) => res.json())
|
||||
.then((rows: unknown) => {
|
||||
if (!Array.isArray(rows)) return
|
||||
const picked = pickBoardRefs(rows)
|
||||
if (!picked) return
|
||||
boardCache.refs = picked
|
||||
sharedBoardRefs.value = picked
|
||||
})
|
||||
.catch(() => {
|
||||
boardCache.started = false
|
||||
})
|
||||
}
|
||||
|
||||
watch(lastPrice, () => ensureBoardRefs(), { immediate: true })
|
||||
|
||||
const quoteRows = computed(() => {
|
||||
const fromServer = Number(lastPrice.value?.copper_world ?? 0) > 0
|
||||
&& Number(lastPrice.value?.dollar_free ?? 0) > 0
|
||||
const copper = fromServer
|
||||
? Number(lastPrice.value?.copper_world)
|
||||
: (sharedBoardRefs.value?.copper ?? 0)
|
||||
const dollar = fromServer
|
||||
? Number(lastPrice.value?.dollar_free)
|
||||
: (sharedBoardRefs.value?.dollar ?? 0)
|
||||
const tonText = String(lastPrice.value?.copper_ton ?? '')
|
||||
const kiloText = String(lastPrice.value?.copper_kilo ?? '')
|
||||
const ton = fromServer && tonText !== '' && tonText !== '0'
|
||||
? Number(tonText)
|
||||
: (copper > 0 && dollar > 0 ? copper * dollar : 0)
|
||||
const kilo = fromServer && kiloText !== '' && kiloText !== '0'
|
||||
? Number(kiloText)
|
||||
: (ton > 0 ? ton / 1000 : 0)
|
||||
const text = (value: number) => (value > 0 ? usePriceWithoutType(value) : '—')
|
||||
|
||||
return [
|
||||
{ label: 'قیمت مس جهانی', value: text(copper), unit: copper > 0 ? 'دلار' : '' },
|
||||
{ label: 'قیمت دلار بازار آزاد', value: text(dollar), unit: dollar > 0 ? 'ریال' : '' },
|
||||
{ label: 'قیمت تن', value: text(ton), unit: ton > 0 ? 'ریال' : '' },
|
||||
{ label: 'قیمت کیلوگرم', value: text(kilo), unit: kilo > 0 ? 'ریال' : '' },
|
||||
]
|
||||
})
|
||||
|
||||
return { quoteRows }
|
||||
}
|
||||
@@ -32,27 +32,15 @@
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<span class="dhome-label dhome-label--boursecu">
|
||||
قیمت لحظهای کاتد مس
|
||||
</span>
|
||||
<div class="dhome-hero-price__value-row">
|
||||
<span
|
||||
v-if="percentChange !== 0"
|
||||
class="dhome-hero-price__delta"
|
||||
:class="percentChange > 0 ? 'dhome-hero-price__delta--up' : 'dhome-hero-price__delta--down'"
|
||||
>
|
||||
<BasicIcon
|
||||
:icon="percentChange > 0 ? 'vuesax-linear:arrow-up-2' : 'vuesax-linear:arrow-down-2'"
|
||||
size="14"
|
||||
/>
|
||||
<span class="dhome-hero-price__delta-value">{{ formatSignedPercentChange(percentChange) }}</span>
|
||||
</span>
|
||||
<span
|
||||
class="dhome-hero-price__odometer o-text dhome-hero-price__odometer--boursecu"
|
||||
style="direction: ltr"
|
||||
>{{ usePriceWithoutType(displayGoldVal) }}</span>
|
||||
<span class="dhome-hero-price__unit dhome-hero-price__unit--boursecu">ریال</span>
|
||||
</div>
|
||||
<ul class="hero-quotes">
|
||||
<li v-for="row in quoteRows" :key="row.label" class="hero-quotes__row">
|
||||
<span class="hero-quotes__label">{{ row.label }}</span>
|
||||
<span class="hero-quotes__value" dir="ltr">
|
||||
<bdi>{{ row.value }}</bdi>
|
||||
<small v-if="row.unit">{{ row.unit }}</small>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -61,7 +49,7 @@
|
||||
import { computed } from 'vue'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { getTradeBuyPrice, lastPrice, percentPriceChanged } from '@/utils/price'
|
||||
import { useHomeQuoteRows } from '@/views/base/home/composables/useHomeQuoteRows'
|
||||
import {
|
||||
NO_QUOTE_MESSAGE,
|
||||
STORE_CLOSED_TITLE,
|
||||
@@ -69,7 +57,6 @@ import {
|
||||
getStoreClosedHint,
|
||||
isHomeQuotePending,
|
||||
} from '@/utils/storeHours'
|
||||
import { formatSignedPercentChange, usePriceWithoutType } from '@/utils/usePrice'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
@@ -87,15 +74,7 @@ const statusHint = computed(() => {
|
||||
return 'در حال حاضر قیمتی برای نمایش وجود ندارد.'
|
||||
})
|
||||
|
||||
const displayGoldVal = computed(() => {
|
||||
if (displayState.value !== 'open') return 0
|
||||
return getTradeBuyPrice(lastPrice.value)
|
||||
})
|
||||
|
||||
const percentChange = computed(() => {
|
||||
if (!getTradeBuyPrice(lastPrice.value)) return 0
|
||||
return percentPriceChanged()
|
||||
})
|
||||
const { quoteRows } = useHomeQuoteRows()
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@@ -127,6 +106,44 @@ const percentChange = computed(() => {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.hero-quotes {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.hero-quotes__row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.hero-quotes__label {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.hero-quotes__value {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hero-quotes__value small {
|
||||
margin-left: 4px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.dhome-hero-price__closed {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user