Files
boursecu_user/src/views/base/home/components/PriceChart.vue
T
2026-06-15 11:32:46 +03:30

202 lines
4.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="bg-gray-50 w-full flex justify-evenly rounded-lg py-4 mt-4 h-[15rem]">
<div class="w-full text-center pt-2">
<a-radio-group v-model:value="selectedValue" button-style="solid">
<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 class="min-h-[200px] relative">
<div v-if="hasChartData" ref="chartEl" class="h-[200px] w-full" />
<div
v-else
class="h-[200px] flex items-center justify-center text-gray-400 text-sm"
>
داده نمودار در دسترس نیست
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import * as echarts from 'echarts'
import type { EChartsOption } from 'echarts'
import { ref, computed, watch, onBeforeUnmount, nextTick } from 'vue'
import { useConfigStore } from '@/store/configStore'
import { usePrice } from '@/utils/usePrice'
type ChartPoint = [number, number]
const props = defineProps<{
priceChartSeries?: Array<{ name?: string; data?: unknown[] }> | null
}>()
const emit = defineEmits<{
updateData: [timeline: string]
}>()
const configStore = useConfigStore()
const selectedValue = ref('DAILY')
const chartEl = ref<HTMLElement | null>(null)
let chart: echarts.ECharts | null = null
let renderToken = 0
function extractPoints(): ChartPoint[] {
const source = props.priceChartSeries
if (!Array.isArray(source) || source.length === 0) {
return []
}
const raw = source[0]?.data
if (!Array.isArray(raw)) {
return []
}
return raw
.map((item) => {
if (!Array.isArray(item) || item.length < 2) {
return null
}
let x = Number(item[0])
let y = Number(item[1])
if (!Number.isFinite(x) || !Number.isFinite(y) || y <= 0) {
return null
}
if (x > 0 && x < 1e11) {
x *= 1000
}
return [x, y] as ChartPoint
})
.filter((point): point is ChartPoint => point !== null)
.sort((a, b) => a[0] - b[0])
}
const chartPoints = computed(() => extractPoints())
const hasChartData = computed(() => chartPoints.value.length >= 2)
const toPersianNumber = (num: number) =>
num.toString().replace(/\d/g, (d) => '۰۱۲۳۴۵۶۷۸۹'[d])
function buildOption(points: ChartPoint[]): EChartsOption {
const color = configStore.config?.color || '#CEA34C'
return {
animation: true,
grid: {
left: 4,
right: 4,
top: 8,
bottom: 4,
containLabel: false
},
xAxis: {
type: 'time',
show: false,
boundaryGap: false
},
yAxis: {
type: 'value',
show: false,
scale: true
},
tooltip: {
trigger: 'axis',
confine: true,
formatter: (params) => {
const row = Array.isArray(params) ? params[0] : params
if (!row || !Array.isArray(row.value)) {
return ''
}
const [timestamp, value] = row.value as [number, number]
const date = new Date(timestamp)
const dateText = `${date.toLocaleDateString('fa-IR')} - ${date.toLocaleTimeString('fa-IR', {
hour: '2-digit',
minute: '2-digit'
})}`
return `${dateText}<br/>${toPersianNumber(usePrice(value, true, 'ه.ریال'))}`
}
},
series: [
{
name: 'قیمت',
type: 'line',
smooth: true,
showSymbol: false,
data: points,
lineStyle: {
width: 2,
color
},
areaStyle: {
color,
opacity: 0.25
}
}
]
}
}
function destroyChart() {
if (!chart) {
return
}
chart.dispose()
chart = null
}
async function renderChart() {
const token = ++renderToken
if (!hasChartData.value) {
destroyChart()
return
}
await nextTick()
await nextTick()
if (token !== renderToken || !chartEl.value) {
return
}
const points = chartPoints.value
try {
if (!chart || chart.isDisposed()) {
chart = echarts.init(chartEl.value, undefined, { renderer: 'canvas' })
}
chart.setOption(buildOption(points), true)
chart.resize()
} catch (error) {
console.error('Price chart render failed:', error)
destroyChart()
}
}
watch(
() => [chartPoints.value.length, selectedValue.value, configStore.config?.color] as const,
() => {
void renderChart()
},
{ immediate: true, flush: 'post' }
)
onBeforeUnmount(() => {
renderToken++
destroyChart()
})
const handleUpdate = (timeline: string) => {
selectedValue.value = timeline
emit('updateData', timeline)
}
</script>