last update
This commit is contained in:
@@ -6,104 +6,196 @@
|
||||
<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">
|
||||
<apexchart
|
||||
type="area"
|
||||
height="200"
|
||||
:options="chartOptions"
|
||||
:series="props.priceChartSeries"
|
||||
/>
|
||||
<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 { ref, computed } from 'vue'
|
||||
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";
|
||||
import {getProjectFontFamily} from "@/utils/config";
|
||||
import { usePrice } from '@/utils/usePrice'
|
||||
|
||||
type ChartPoint = [number, number]
|
||||
|
||||
const props = defineProps<{
|
||||
priceChartSeries: any[]
|
||||
priceChartSeries?: Array<{ name?: string; data?: unknown[] }> | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
updateData: [timeline: string]
|
||||
}>()
|
||||
|
||||
const config = useConfigStore()
|
||||
const configStore = useConfigStore()
|
||||
const selectedValue = ref('DAILY')
|
||||
const chartEl = ref<HTMLElement | null>(null)
|
||||
let chart: echarts.ECharts | null = null
|
||||
let renderToken = 0
|
||||
|
||||
const toPersianNumber = (num: number) => {
|
||||
return num.toString().replace(/\d/g, (d) => '۰۱۲۳۴۵۶۷۸۹'[d])
|
||||
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 chartOptions = computed(() => {
|
||||
const fontFamily = getProjectFontFamily()
|
||||
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 {
|
||||
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: {
|
||||
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', {
|
||||
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, 'ه.ریال'))}`
|
||||
}
|
||||
},
|
||||
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]
|
||||
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>
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user