Split desktop and mobile layouts with shared page data so APIs fetch once per context.
Add layout shells, desktop views, module-level page caches, and UI fixes for auth, home, wallet, transactions, address, and related pages. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,17 +1,45 @@
|
||||
<template>
|
||||
<div class="bg-gray-50 w-full flex justify-evenly rounded-lg py-4 mt-4 h-[15rem]">
|
||||
<div
|
||||
class="bg-gray-50 w-full rounded-lg py-4"
|
||||
:class="[
|
||||
chartContainerClass,
|
||||
variant === 'desktop' ? 'mt-0' : 'mt-4',
|
||||
layout === 'boursecu' && variant !== 'desktop' ? 'price-chart-box--boursecu' : '',
|
||||
]"
|
||||
>
|
||||
<div class="w-full text-center pt-2">
|
||||
<a-radio-group v-model:value="selectedValue" button-style="solid">
|
||||
<div v-if="showMetalSelector" class="chart-metal-segment mb-3">
|
||||
<button
|
||||
type="button"
|
||||
class="chart-metal-segment__item"
|
||||
:class="{ 'chart-metal-segment__item--active': selectedMetal === 'katod' }"
|
||||
@click="emit('update:selectedMetal', 'katod')"
|
||||
>
|
||||
کاتد مس
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="chart-metal-segment__item"
|
||||
:class="{ 'chart-metal-segment__item--active': selectedMetal === 'maftol' }"
|
||||
@click="emit('update:selectedMetal', 'maftol')"
|
||||
>
|
||||
مفتول مس
|
||||
</button>
|
||||
</div>
|
||||
<a-radio-group v-model:value="selectedValue" button-style="solid" class="chart-timeline-group">
|
||||
<a-radio-button value="DAILY" @click="handleUpdate('DAILY')">24 ساعت اخیر</a-radio-button>
|
||||
<a-radio-button value="WEEKLY" @click="handleUpdate('WEEKLY')">هفته گذشته</a-radio-button>
|
||||
<a-radio-button value="MONTHLY" @click="handleUpdate('MONTHLY')">ماه گذشته</a-radio-button>
|
||||
</a-radio-group>
|
||||
<div id="chart-timeline">
|
||||
<div ref="chartContainerRef" id="chart-timeline" class="w-full" :class="chartInnerClass">
|
||||
<apexchart
|
||||
type="area"
|
||||
height="200"
|
||||
:options="chartOptions"
|
||||
:series="props.priceChartSeries"
|
||||
v-if="canRenderChart"
|
||||
:key="`${layout}-${variant}-${selectedMetal}-${chartSelection}-${seriesLength}`"
|
||||
type="area"
|
||||
width="100%"
|
||||
:height="chartHeight"
|
||||
:options="chartOptions"
|
||||
:series="props.priceChartSeries"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -19,21 +47,71 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import { useConfigStore } from '@/store/configStore'
|
||||
import {usePrice} from "@/utils/usePrice";
|
||||
import {getProjectFontFamily} from "@/utils/config";
|
||||
import { usePrice } from '@/utils/usePrice'
|
||||
import { getProjectFontFamily } from '@/utils/config'
|
||||
import { useElementSize } from '@vueuse/core'
|
||||
|
||||
const props = defineProps<{
|
||||
priceChartSeries: any[]
|
||||
}>()
|
||||
import type { ChartMetalType } from '../composables/usePriceChart'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
priceChartSeries: any[]
|
||||
layout?: 'default' | 'boursecu'
|
||||
variant?: 'mobile' | 'desktop'
|
||||
chartSelection?: string
|
||||
selectedMetal?: ChartMetalType
|
||||
}>(),
|
||||
{
|
||||
layout: 'default',
|
||||
variant: 'mobile',
|
||||
chartSelection: 'DAILY',
|
||||
selectedMetal: 'katod',
|
||||
}
|
||||
)
|
||||
|
||||
const chartHeight = computed(() => (props.variant === 'desktop' ? 320 : 200))
|
||||
const chartContainerClass = computed(() => {
|
||||
if (props.variant === 'desktop') {
|
||||
return 'min-h-[22rem]'
|
||||
}
|
||||
return props.layout === 'default' ? 'min-h-[15rem] h-[15rem]' : 'min-h-[15rem]'
|
||||
})
|
||||
const chartInnerClass = computed(() =>
|
||||
props.variant === 'desktop' ? 'min-h-[320px]' : 'min-h-[200px]'
|
||||
)
|
||||
|
||||
const showMetalSelector = computed(() => props.layout === 'boursecu')
|
||||
|
||||
const emit = defineEmits<{
|
||||
updateData: [timeline: string]
|
||||
'update:selectedMetal': [metal: ChartMetalType]
|
||||
}>()
|
||||
|
||||
const config = useConfigStore()
|
||||
const selectedValue = ref('DAILY')
|
||||
const chartContainerRef = ref<HTMLElement | null>(null)
|
||||
const chartMounted = ref(false)
|
||||
const selectedValue = ref(props.chartSelection)
|
||||
|
||||
const { width: chartWidth } = useElementSize(chartContainerRef)
|
||||
|
||||
const seriesLength = computed(() => props.priceChartSeries?.[0]?.data?.length ?? 0)
|
||||
const canRenderChart = computed(
|
||||
() => chartMounted.value && chartWidth.value > 0 && seriesLength.value > 0
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.chartSelection,
|
||||
(timeline) => {
|
||||
selectedValue.value = timeline
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
chartMounted.value = true
|
||||
})
|
||||
|
||||
const toPersianNumber = (num: number) => {
|
||||
return num.toString().replace(/\d/g, (d) => '۰۱۲۳۴۵۶۷۸۹'[d])
|
||||
@@ -43,67 +121,108 @@ const chartOptions = computed(() => {
|
||||
const fontFamily = getProjectFontFamily()
|
||||
|
||||
return {
|
||||
chart: {
|
||||
id: 'نوسانات قیمت',
|
||||
type: 'area',
|
||||
height: 200,
|
||||
toolbar: { show: false },
|
||||
zoom: { autoScaleYaxis: true }
|
||||
},
|
||||
grid: {
|
||||
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
||||
yaxis: { lines: { show: false } }
|
||||
},
|
||||
stroke: { width: 2 },
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
// formatter: (val) => toPersianNumber(val),
|
||||
style: {
|
||||
fontFamily
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
tickAmount: 8,
|
||||
show: false,
|
||||
labels: {
|
||||
chart: {
|
||||
id: 'نوسانات قیمت',
|
||||
type: 'area',
|
||||
height: 200,
|
||||
toolbar: { show: false },
|
||||
zoom: { autoScaleYaxis: true },
|
||||
redrawOnParentResize: true,
|
||||
},
|
||||
grid: {
|
||||
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
||||
yaxis: { lines: { show: false } },
|
||||
},
|
||||
stroke: { width: 2 },
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
style: {
|
||||
fontFamily,
|
||||
},
|
||||
},
|
||||
yaxis: {
|
||||
tickAmount: 8,
|
||||
show: false,
|
||||
labels: {
|
||||
formatter: (val: number) => toPersianNumber(usePrice(val, true, 'ه.ریال')),
|
||||
style: { fontSize: '13px', fontFamily },
|
||||
},
|
||||
},
|
||||
xaxis: {
|
||||
show: false,
|
||||
type: 'datetime',
|
||||
axisTicks: { show: false },
|
||||
tooltip: { enabled: false },
|
||||
labels: { show: false },
|
||||
},
|
||||
tooltip: {
|
||||
x: {
|
||||
formatter: (value: number) => {
|
||||
const date = new Date(value)
|
||||
return `${date.toLocaleDateString('fa-IR')} - ${date.toLocaleTimeString('fa-IR', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})}`
|
||||
},
|
||||
},
|
||||
formatter: (val: number) => toPersianNumber(usePrice(val, true, 'ه.ریال')),
|
||||
style: { fontSize: '13px', fontFamily }
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
show: false,
|
||||
type: 'datetime',
|
||||
axisTicks: { show: false },
|
||||
tooltip: { enabled: false },
|
||||
labels: { show: false }
|
||||
},
|
||||
tooltip: {
|
||||
x: {
|
||||
formatter: (value: number) => {
|
||||
const date = new Date(value)
|
||||
return `${date.toLocaleDateString('fa-IR')} - ${date.toLocaleTimeString('fa-IR', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}`
|
||||
}
|
||||
style: { fontFamily },
|
||||
},
|
||||
colors: [config.config.color],
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
shadeIntensity: 1,
|
||||
opacityFrom: 0.7,
|
||||
opacityTo: 0.9,
|
||||
stops: [0, 100],
|
||||
},
|
||||
},
|
||||
formatter: (val: number) => toPersianNumber(usePrice(val, true, 'ه.ریال')),
|
||||
style: { fontFamily }
|
||||
},
|
||||
colors: [config.config.color],
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
shadeIntensity: 1,
|
||||
opacityFrom: 0.7,
|
||||
opacityTo: 0.9,
|
||||
stops: [0, 100]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const handleUpdate = (timeline: string) => {
|
||||
emit('updateData', timeline)
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@boursecu-surface-border: #d4e3de;
|
||||
|
||||
.price-chart-box--boursecu {
|
||||
border: 2px solid @boursecu-surface-border;
|
||||
}
|
||||
|
||||
.chart-metal-segment {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 3px;
|
||||
border-radius: 10px;
|
||||
background: #dfe8e5;
|
||||
}
|
||||
|
||||
.chart-metal-segment__item {
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
min-width: 6.5rem;
|
||||
padding: 0.4rem 0.75rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: @App-primary-color;
|
||||
opacity: 0.65;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
transition: background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.chart-metal-segment__item--active {
|
||||
background: #fff;
|
||||
color: @App-primary-color;
|
||||
opacity: 1;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 1px 4px rgba(10, 27, 55, 0.12);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user