Files
datahub/frontend/src/components/DashboardTrendChart.vue
T
2026-03-20 14:38:18 +08:00

122 lines
3.0 KiB
Vue

<script setup lang="ts">
import { Line } from '@antv/g2plot'
import type { DashboardTrendPoint } from '@/types/api'
const props = defineProps<{
data: DashboardTrendPoint[]
loading: boolean
groupBy: 'day' | 'week' | 'month'
dataType: string | undefined
}>()
const emit = defineEmits<{
'update:groupBy': ['day' | 'week' | 'month']
'update:dataType': [string | undefined]
}>()
const chartRef = ref<HTMLDivElement>()
let chart: Line | null = null
const groupByOptions = [
{ label: '日', value: 'day' },
{ label: '周', value: 'week' },
{ label: '月', value: 'month' },
]
const dataTypeOptions = [
{ label: '全部', value: '' },
{ label: '订单', value: 'order' },
{ label: '产品', value: 'product' },
{ label: '退款', value: 'refund' },
{ label: '库存', value: 'inventory' },
]
const flatData = computed(() => {
const result: { date: string; value: number; category: string }[] = []
for (const point of props.data) {
result.push({ date: point.date, value: point.success, category: '成功' })
result.push({ date: point.date, value: point.failed, category: '失败' })
}
return result
})
function createChart() {
if (!chartRef.value) return
chart = new Line(chartRef.value, {
data: flatData.value,
xField: 'date',
yField: 'value',
seriesField: 'category',
color: ['#52c41a', '#ff4d4f'],
smooth: true,
legend: { position: 'top' },
xAxis: { label: { autoRotate: true } },
yAxis: { label: { formatter: (v: string) => v } },
animation: false,
})
chart.render()
}
function handleGroupByChange(e: unknown) {
const val = (e as { target: { value?: string } }).target.value as 'day' | 'week' | 'month'
emit('update:groupBy', val)
}
function handleDataTypeChange(val: unknown) {
emit('update:dataType', (val as string) || undefined)
}
watch(flatData, (newData) => {
if (newData.length === 0 && chart) {
chart.destroy()
chart = null
} else if (!chart && chartRef.value && newData.length > 0) {
nextTick(() => createChart())
} else if (chart) {
chart.changeData(newData)
}
}, { flush: 'post' })
onMounted(() => {
if (props.data.length > 0) {
createChart()
}
})
onBeforeUnmount(() => {
if (chart) {
chart.destroy()
chart = null
}
})
</script>
<template>
<a-card title="数据趋势" class="mb-4">
<template #extra>
<a-space>
<a-radio-group
:value="groupBy"
size="small"
@change="handleGroupByChange"
>
<a-radio-button v-for="opt in groupByOptions" :key="opt.value" :value="opt.value">
{{ opt.label }}
</a-radio-button>
</a-radio-group>
<a-select
:value="dataType ?? ''"
:options="dataTypeOptions"
style="width: 100px"
size="small"
@change="handleDataTypeChange"
/>
</a-space>
</template>
<a-spin :spinning="loading">
<div ref="chartRef" style="height: 350px" />
<a-empty v-if="!loading && data.length === 0" description="暂无趋势数据" />
</a-spin>
</a-card>
</template>