create landing page for boursemes
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
import { inject, provide, type InjectionKey } from 'vue'
|
||||
import { useConfigStore } from '@/store/configStore'
|
||||
|
||||
export interface AboutUsPageContext {
|
||||
config: ReturnType<typeof useConfigStore>
|
||||
}
|
||||
|
||||
export const ABOUT_US_PAGE_CONTEXT_KEY: InjectionKey<AboutUsPageContext> = Symbol('aboutUsPageContext')
|
||||
|
||||
export function useAboutUsPage() {
|
||||
const config = useConfigStore()
|
||||
|
||||
const context: AboutUsPageContext = {
|
||||
config,
|
||||
}
|
||||
|
||||
provide(ABOUT_US_PAGE_CONTEXT_KEY, context)
|
||||
return context
|
||||
}
|
||||
|
||||
export function useAboutUsPageContext() {
|
||||
const context = inject(ABOUT_US_PAGE_CONTEXT_KEY)
|
||||
if (!context) {
|
||||
throw new Error('useAboutUsPageContext must be used within a page that calls useAboutUsPage()')
|
||||
}
|
||||
return context
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="about-us-page-root">
|
||||
<DesktopAboutUsView />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useAboutUsPage } from './composables/useAboutUsPage'
|
||||
import DesktopAboutUsView from './desktop/DesktopAboutUsView.vue'
|
||||
|
||||
useAboutUsPage()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.about-us-page-root {
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f4f5f7;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/*
|
||||
* Mobile shell locks html/body overflow. Scroll the landing inside the page root.
|
||||
* Desktop must NOT use overflow:auto here — a full-height auto container with
|
||||
* overscroll-behavior:contain steals wheel events and blocks document scroll.
|
||||
*/
|
||||
html:not(.app-layout--desktop) .app-main > .about-us-page-root {
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior-y: contain;
|
||||
}
|
||||
|
||||
html.dlanding-menu-open .about-us-page-root {
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,206 @@
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { getAllPrices } from '@/api/home'
|
||||
import { getProductsCatalog, getProductsCatalogRef } from '@/composables/useProductsCatalog'
|
||||
import { getTradeBuyPrice, getTradeSellPrice } from '@/utils/price'
|
||||
|
||||
export type ChartMetalType = 'katod' | 'maftol' | 'shamsh'
|
||||
|
||||
type ChartPoint = [number, number]
|
||||
|
||||
/**
|
||||
* مارکآپ ثابت روی قیمت بورس کالا برای نمودار:
|
||||
* کاتد +۳٪ ، مفتول +۶٪ ، شمش +۱۲٪
|
||||
*/
|
||||
export const CHART_METAL_MARKUP_RATIO: Record<ChartMetalType, number> = {
|
||||
katod: 0.03,
|
||||
maftol: 0.06,
|
||||
shamsh: 0.12,
|
||||
}
|
||||
|
||||
/** Module-level chart state — one dataset shared by mobile and desktop PriceChart instances. */
|
||||
const priceChartSeries = ref<{ name: string; data: ChartPoint[] }[]>([
|
||||
{ name: 'قیمت', data: [] },
|
||||
])
|
||||
const rawChartPoints = ref<ChartPoint[]>([])
|
||||
const chartSelection = ref('DAILY')
|
||||
const chartLoading = ref(false)
|
||||
const selectedChartMetal = ref<ChartMetalType>('katod')
|
||||
const catalogProducts = getProductsCatalogRef()
|
||||
|
||||
const maftolChartRatio = computed(() => CHART_METAL_MARKUP_RATIO.maftol)
|
||||
const shamshChartRatio = computed(() => CHART_METAL_MARKUP_RATIO.shamsh)
|
||||
|
||||
const selectedMetalChartRatio = computed(
|
||||
() => CHART_METAL_MARKUP_RATIO[selectedChartMetal.value],
|
||||
)
|
||||
|
||||
let chartFetchPromise: Promise<void> | null = null
|
||||
let chartInitialized = false
|
||||
let metalOffsetPromise: Promise<void> | null = null
|
||||
let metalOffsetLoaded = false
|
||||
|
||||
function applyChartRatio(basePrice: number, ratio: number) {
|
||||
if (!ratio) {
|
||||
return basePrice
|
||||
}
|
||||
|
||||
return Math.round(basePrice + basePrice * ratio)
|
||||
}
|
||||
|
||||
/** بالاترین/پایینترین امروز — همان مارکآپ ثابت فلز روی قیمت بورس کالا */
|
||||
export function resolveMetalDayRangePrices(options: {
|
||||
price: { up?: { buy?: number; sell?: number }; buy?: number; sell?: number; price_last?: number } | null | undefined
|
||||
metal: ChartMetalType
|
||||
metalRatio: number
|
||||
}) {
|
||||
const baseHigh = options.price?.up?.buy ?? getTradeBuyPrice(options.price as Parameters<typeof getTradeBuyPrice>[0])
|
||||
const baseLow = options.price?.up?.sell ?? getTradeSellPrice(options.price as Parameters<typeof getTradeSellPrice>[0])
|
||||
const ratio = options.metalRatio || CHART_METAL_MARKUP_RATIO[options.metal]
|
||||
|
||||
return {
|
||||
high: applyChartRatio(baseHigh, ratio),
|
||||
low: applyChartRatio(baseLow, ratio),
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeChartTimestamp(timestamp: number) {
|
||||
return timestamp < 1_000_000_000_000 ? timestamp * 1000 : timestamp
|
||||
}
|
||||
|
||||
function resolvePricesMap(response: unknown): Record<string, Array<{ timestamp: number; value: number }>> | null {
|
||||
if (!response || typeof response !== 'object') {
|
||||
return null
|
||||
}
|
||||
|
||||
const body = response as Record<string, unknown>
|
||||
const nested = body.data
|
||||
|
||||
if (nested && typeof nested === 'object' && 'prices' in nested) {
|
||||
return (nested as { prices: Record<string, Array<{ timestamp: number; value: number }>> }).prices
|
||||
}
|
||||
|
||||
if ('prices' in body) {
|
||||
return body.prices as Record<string, Array<{ timestamp: number; value: number }>>
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function buildChartPoints(items: Array<{ timestamp: number; value: number }>): ChartPoint[] {
|
||||
return items.map((item) => [
|
||||
normalizeChartTimestamp(item.timestamp),
|
||||
item.value - 1,
|
||||
])
|
||||
}
|
||||
|
||||
function syncChartSeries() {
|
||||
const ratio = selectedMetalChartRatio.value
|
||||
priceChartSeries.value[0].data = rawChartPoints.value.map(
|
||||
([timestamp, value]) => [timestamp, applyChartRatio(value, ratio)] as ChartPoint,
|
||||
)
|
||||
}
|
||||
|
||||
watch([selectedChartMetal, maftolChartRatio, shamshChartRatio], () => {
|
||||
syncChartSeries()
|
||||
})
|
||||
|
||||
async function fetchPrices(force = false) {
|
||||
if (!force && chartInitialized && rawChartPoints.value.length > 0) {
|
||||
syncChartSeries()
|
||||
return
|
||||
}
|
||||
|
||||
if (!force && chartFetchPromise) {
|
||||
return chartFetchPromise
|
||||
}
|
||||
|
||||
chartFetchPromise = (async () => {
|
||||
try {
|
||||
chartLoading.value = true
|
||||
const response = await getAllPrices()
|
||||
const data = resolvePricesMap(response)
|
||||
const selectedData = data?.[chartSelection.value] ?? []
|
||||
rawChartPoints.value = buildChartPoints(selectedData)
|
||||
syncChartSeries()
|
||||
chartInitialized = true
|
||||
} catch (error) {
|
||||
console.error('Error fetching prices:', error)
|
||||
} finally {
|
||||
chartLoading.value = false
|
||||
chartFetchPromise = null
|
||||
}
|
||||
})()
|
||||
|
||||
return chartFetchPromise
|
||||
}
|
||||
|
||||
async function loadMetalChartOffsets(force = false) {
|
||||
if (!force && metalOffsetLoaded && catalogProducts.value.length > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!force && metalOffsetPromise) {
|
||||
return metalOffsetPromise
|
||||
}
|
||||
|
||||
metalOffsetPromise = (async () => {
|
||||
try {
|
||||
await getProductsCatalog(force)
|
||||
syncChartSeries()
|
||||
metalOffsetLoaded = true
|
||||
} catch (error) {
|
||||
console.error('Error loading boursecu chart metal ratios:', error)
|
||||
syncChartSeries()
|
||||
} finally {
|
||||
metalOffsetPromise = null
|
||||
}
|
||||
})()
|
||||
|
||||
return metalOffsetPromise
|
||||
}
|
||||
|
||||
function updateChartData(timeline: string) {
|
||||
chartSelection.value = timeline
|
||||
void fetchPrices(true)
|
||||
}
|
||||
|
||||
function setChartMetal(metal: ChartMetalType) {
|
||||
selectedChartMetal.value = metal
|
||||
syncChartSeries()
|
||||
}
|
||||
|
||||
export function usePriceChart() {
|
||||
return {
|
||||
priceChartSeries,
|
||||
chartSelection,
|
||||
chartLoading,
|
||||
selectedChartMetal,
|
||||
maftolChartRatio,
|
||||
shamshChartRatio,
|
||||
selectedMetalChartRatio,
|
||||
updateChartData,
|
||||
fetchPrices,
|
||||
loadMaftolOffset: loadMetalChartOffsets,
|
||||
loadMetalChartOffsets,
|
||||
setChartMetal,
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchHomeChartPrices(force = false) {
|
||||
return fetchPrices(force)
|
||||
}
|
||||
|
||||
export function loadHomeMaftolOffset(force = false) {
|
||||
return loadMetalChartOffsets(force)
|
||||
}
|
||||
|
||||
export function resetHomePriceChart() {
|
||||
priceChartSeries.value = [{ name: 'قیمت', data: [] }]
|
||||
rawChartPoints.value = []
|
||||
chartSelection.value = 'DAILY'
|
||||
selectedChartMetal.value = 'katod'
|
||||
chartInitialized = false
|
||||
chartFetchPromise = null
|
||||
metalOffsetLoaded = false
|
||||
metalOffsetPromise = null
|
||||
}
|
||||
Reference in New Issue
Block a user