2026-05-14 13:44:43 +03:30
|
|
|
|
<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>
|
2026-06-15 11:32:46 +03:30
|
|
|
|
<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>
|
2026-05-14 13:44:43 +03:30
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2026-06-15 11:32:46 +03:30
|
|
|
|
import * as echarts from 'echarts'
|
|
|
|
|
|
import type { EChartsOption } from 'echarts'
|
|
|
|
|
|
import { ref, computed, watch, onBeforeUnmount, nextTick } from 'vue'
|
2026-05-14 13:44:43 +03:30
|
|
|
|
import { useConfigStore } from '@/store/configStore'
|
2026-06-15 11:32:46 +03:30
|
|
|
|
import { usePrice } from '@/utils/usePrice'
|
|
|
|
|
|
|
|
|
|
|
|
type ChartPoint = [number, number]
|
2026-05-14 13:44:43 +03:30
|
|
|
|
|
|
|
|
|
|
const props = defineProps<{
|
2026-06-15 11:32:46 +03:30
|
|
|
|
priceChartSeries?: Array<{ name?: string; data?: unknown[] }> | null
|
2026-05-14 13:44:43 +03:30
|
|
|
|
}>()
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
|
updateData: [timeline: string]
|
|
|
|
|
|
}>()
|
|
|
|
|
|
|
2026-06-15 11:32:46 +03:30
|
|
|
|
const configStore = useConfigStore()
|
2026-05-14 13:44:43 +03:30
|
|
|
|
const selectedValue = ref('DAILY')
|
2026-06-15 11:32:46 +03:30
|
|
|
|
const chartEl = ref<HTMLElement | null>(null)
|
|
|
|
|
|
let chart: echarts.ECharts | null = null
|
|
|
|
|
|
let renderToken = 0
|
2026-05-14 13:44:43 +03:30
|
|
|
|
|
2026-06-15 11:32:46 +03:30
|
|
|
|
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])
|
2026-05-14 13:44:43 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-15 11:32:46 +03:30
|
|
|
|
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'
|
2026-05-31 16:27:59 +03:30
|
|
|
|
|
|
|
|
|
|
return {
|
2026-06-15 11:32:46 +03:30
|
|
|
|
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', {
|
2026-05-14 13:44:43 +03:30
|
|
|
|
hour: '2-digit',
|
|
|
|
|
|
minute: '2-digit'
|
|
|
|
|
|
})}`
|
2026-06-15 11:32:46 +03:30
|
|
|
|
|
|
|
|
|
|
return `${dateText}<br/>${toPersianNumber(usePrice(value, true, 'ه.ریال'))}`
|
2026-05-14 13:44:43 +03:30
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-06-15 11:32:46 +03:30
|
|
|
|
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' })
|
2026-05-14 13:44:43 +03:30
|
|
|
|
}
|
2026-06-15 11:32:46 +03:30
|
|
|
|
|
|
|
|
|
|
chart.setOption(buildOption(points), true)
|
|
|
|
|
|
chart.resize()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Price chart render failed:', error)
|
|
|
|
|
|
destroyChart()
|
2026-05-14 13:44:43 +03:30
|
|
|
|
}
|
2026-06-15 11:32:46 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => [chartPoints.value.length, selectedValue.value, configStore.config?.color] as const,
|
|
|
|
|
|
() => {
|
|
|
|
|
|
void renderChart()
|
|
|
|
|
|
},
|
|
|
|
|
|
{ immediate: true, flush: 'post' }
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
|
renderToken++
|
|
|
|
|
|
destroyChart()
|
2026-05-31 16:27:59 +03:30
|
|
|
|
})
|
2026-05-14 13:44:43 +03:30
|
|
|
|
|
|
|
|
|
|
const handleUpdate = (timeline: string) => {
|
2026-06-15 11:32:46 +03:30
|
|
|
|
selectedValue.value = timeline
|
2026-05-14 13:44:43 +03:30
|
|
|
|
emit('updateData', timeline)
|
|
|
|
|
|
}
|
2026-06-15 11:32:46 +03:30
|
|
|
|
</script>
|