225 lines
6.0 KiB
Vue
225 lines
6.0 KiB
Vue
<template>
|
||
<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">
|
||
<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 ref="chartContainerRef" id="chart-timeline" class="w-full" :class="chartInnerClass">
|
||
<apexchart
|
||
v-if="canRenderChart"
|
||
:key="`${layout}-${variant}-${selectedMetal}-${chartSelection}-${seriesLength}`"
|
||
type="area"
|
||
width="100%"
|
||
:height="chartHeight"
|
||
:options="chartOptions"
|
||
:series="props.priceChartSeries"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||
import { useConfigStore } from '@/store/configStore'
|
||
import { usePrice } from '@/utils/usePrice'
|
||
import { getProjectFontFamily } from '@/utils/config'
|
||
import { useElementSize } from '@vueuse/core'
|
||
|
||
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 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 chartOptions = computed(() => {
|
||
const fontFamily = getProjectFontFamily()
|
||
|
||
return {
|
||
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) => 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) => 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>
|
||
|
||
<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>
|