diff --git a/apps/miniprogram/src/components/TrendChart/index.scss b/apps/miniprogram/src/components/TrendChart/index.scss new file mode 100644 index 0000000..7e26901 --- /dev/null +++ b/apps/miniprogram/src/components/TrendChart/index.scss @@ -0,0 +1,17 @@ +@import '../../styles/variables.scss'; + +.trend-chart { + width: 100%; +} + +.trend-chart-empty { + display: flex; + align-items: center; + justify-content: center; + height: 500rpx; +} + +.trend-chart-empty-text { + font-size: 28px; + color: $tx3; +} diff --git a/apps/miniprogram/src/components/TrendChart/index.tsx b/apps/miniprogram/src/components/TrendChart/index.tsx new file mode 100644 index 0000000..e91f32c --- /dev/null +++ b/apps/miniprogram/src/components/TrendChart/index.tsx @@ -0,0 +1,98 @@ +import React, { useEffect, useRef, useCallback } from 'react'; +import { View, Text } from '@tarojs/components'; +import { EChart } from 'echarts-taro3-react'; +import './index.scss'; + +interface TrendChartProps { + data: { date: string; value: number }[]; + referenceMin?: number; + referenceMax?: number; + unit?: string; + height?: number; +} + +export default function TrendChart({ data, referenceMin, referenceMax, unit = '', height = 500 }: TrendChartProps) { + const chartRef = useRef(null); + + const getOption = useCallback(() => { + if (!data || data.length === 0) return null; + + const series: any[] = []; + const markArea: any = {}; + + if (referenceMin != null && referenceMax != null) { + markArea.data = [[ + { yAxis: referenceMin, itemStyle: { color: 'rgba(5,150,105,0.08)' } }, + { yAxis: referenceMax }, + ]]; + } + + series.push({ + type: 'line', + data: data.map((d) => d.value), + smooth: true, + symbol: 'circle', + symbolSize: 6, + lineStyle: { color: '#0891B2', width: 2 }, + itemStyle: { color: '#0891B2' }, + areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [{ offset: 0, color: 'rgba(8,145,178,0.15)' }, { offset: 1, color: 'rgba(8,145,178,0.01)' }] } }, + markArea: markArea.data ? { silent: true, data: markArea.data } : undefined, + markPoint: (referenceMin != null && referenceMax != null) ? { + data: data + .filter((d) => d.value < referenceMin || d.value > referenceMax) + .map((d) => ({ + coord: [data.indexOf(d), d.value], + itemStyle: { color: '#DC2626' }, + symbolSize: 12, + })), + } : undefined, + }); + + return { + grid: { left: 45, right: 15, top: 20, bottom: 30 }, + xAxis: { + type: 'category', + data: data.map((d) => d.date.slice(5)), + axisLabel: { fontSize: 10, color: '#94A3B8' }, + axisLine: { lineStyle: { color: '#E5E7EB' } }, + }, + yAxis: { + type: 'value', + axisLabel: { fontSize: 10, color: '#94A3B8' }, + splitLine: { lineStyle: { color: '#F3F4F6' } }, + }, + tooltip: { + trigger: 'axis', + formatter: (params: any) => { + const p = params[0]; + const idx = p.dataIndex; + return `${data[idx]?.date || ''}\n${p.value}${unit ? ' ' + unit : ''}`; + }, + }, + series, + }; + }, [data, referenceMin, referenceMax, unit]); + + useEffect(() => { + if (chartRef.current && data && data.length > 0) { + const option = getOption(); + if (option) { + chartRef.current.refresh(option); + } + } + }, [data, getOption]); + + if (!data || data.length === 0) { + return ( + + 暂无数据 + + ); + } + + return ( + + + + ); +}