Sync live quote handling, SSE product images, store hours, file URL normalization, PWA icon cache, TV monitor, about software, SuperAdmin menu gates, price alerts, and related UX from modular while preserving boursecu branding and config constraints. Co-authored-by: Cursor <cursoragent@cursor.com>
162 lines
4.6 KiB
Vue
162 lines
4.6 KiB
Vue
<template>
|
||
<div
|
||
class="dhome-surface dhome-hero-price"
|
||
:class="{
|
||
'dhome-hero-price--boursecu-open': displayState === 'open' || quotePending,
|
||
'dhome-hero-price--boursecu-closed': displayState !== 'open' && !quotePending,
|
||
}"
|
||
>
|
||
<div
|
||
v-if="quotePending"
|
||
class="flex flex-col items-center gap-2 py-4"
|
||
aria-busy="true"
|
||
aria-label="در حال بارگذاری قیمت"
|
||
>
|
||
<div class="h-3 w-24 rounded boursecu-price-skeleton animate-pulse" />
|
||
<div class="h-4 w-32 rounded boursecu-price-skeleton animate-pulse" />
|
||
<div class="h-8 w-40 rounded boursecu-price-skeleton animate-pulse" />
|
||
</div>
|
||
|
||
<template v-else-if="displayState !== 'open'">
|
||
<div class="dhome-hero-price__closed">
|
||
<div class="dhome-hero-price__closed-icon">
|
||
<BasicIcon
|
||
:icon="displayState === 'closed' ? 'vuesax-linear:lock' : 'vuesax-linear:info-circle'"
|
||
size="22"
|
||
color="#ffffff"
|
||
/>
|
||
</div>
|
||
<p class="dhome-hero-price__closed-title">{{ statusTitle }}</p>
|
||
<p v-if="statusHint" class="dhome-hero-price__closed-hint">{{ statusHint }}</p>
|
||
</div>
|
||
</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>
|
||
</template>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed } from 'vue'
|
||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||
import { useUserStore } from '@/store/userStore'
|
||
import { getTradeBuyPrice, lastPrice, percentPriceChanged } from '@/utils/price'
|
||
import {
|
||
NO_QUOTE_MESSAGE,
|
||
STORE_CLOSED_TITLE,
|
||
getPriceDisplayState,
|
||
getStoreClosedHint,
|
||
isHomeQuotePending,
|
||
} from '@/utils/storeHours'
|
||
import { formatSignedPercentChange, usePriceWithoutType } from '@/utils/usePrice'
|
||
|
||
const userStore = useUserStore()
|
||
|
||
const displayState = computed(() => getPriceDisplayState())
|
||
const quotePending = computed(() => isHomeQuotePending())
|
||
|
||
const statusTitle = computed(() =>
|
||
displayState.value === 'closed' ? STORE_CLOSED_TITLE : NO_QUOTE_MESSAGE
|
||
)
|
||
|
||
const statusHint = computed(() => {
|
||
if (displayState.value === 'closed') {
|
||
return getStoreClosedHint(userStore.getValueByKey('closed_message'))
|
||
}
|
||
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()
|
||
})
|
||
</script>
|
||
|
||
<style scoped lang="less">
|
||
.dhome-hero-price--boursecu-open {
|
||
background: linear-gradient(135deg, #c98a4a 0%, #a86528 100%);
|
||
border-color: transparent;
|
||
box-shadow: 0 4px 14px rgba(168, 101, 40, 0.28);
|
||
}
|
||
|
||
.dhome-hero-price--boursecu-closed {
|
||
background: linear-gradient(135deg, #4a5568 0%, #2d3748 100%);
|
||
border-color: transparent;
|
||
box-shadow: 0 4px 14px rgba(45, 55, 72, 0.22);
|
||
}
|
||
|
||
.dhome-label--boursecu {
|
||
color: rgba(255, 255, 255, 0.92);
|
||
}
|
||
|
||
.dhome-hero-price__odometer--boursecu {
|
||
color: #ffffff !important;
|
||
}
|
||
|
||
.dhome-hero-price__unit--boursecu {
|
||
color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
|
||
.boursecu-price-skeleton {
|
||
background: rgba(255, 255, 255, 0.25);
|
||
}
|
||
|
||
.dhome-hero-price__closed {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 6px;
|
||
}
|
||
|
||
.dhome-hero-price__closed-icon {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 44px;
|
||
height: 44px;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.12);
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.dhome-hero-price__closed-title {
|
||
margin: 0;
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.dhome-hero-price__closed-hint {
|
||
margin: 0;
|
||
font-size: 12px;
|
||
line-height: 1.7;
|
||
color: rgba(255, 255, 255, 0.78);
|
||
}
|
||
</style>
|