React - LineChart组件编写(用于查看每日流水图表)

一、简单版本

  • LineChart.tsx
javascript 复制代码
// src/component/LineChart/LineChart.tsx
import React, {
  useEffect,
  useRef,
  useImperativeHandle,
  forwardRef,
  useMemo,
  useCallback,
} from 'react';
import * as echarts from 'echarts/core';
import type { ComposeOption } from 'echarts/core';
import type { LineSeriesOption } from 'echarts/charts';
import type {
  GridComponentOption,
  TooltipComponentOption,
  LegendComponentOption,
} from 'echarts/components';
import { SVGRenderer } from 'echarts/renderers';
import ResizeObserver from 'resize-observer-polyfill';
import classNames from 'classnames';
import { numberWithCommas } from '@/utils/numberWithCommas';
import './LineChart.css';

// 注册必要组件
const { LineChart: ELineChart } = require('echarts/charts');
const {
  GridComponent,
  TooltipComponent,
  LegendComponent,
} = require('echarts/components');

echarts.use([
  ELineChart,
  GridComponent,
  TooltipComponent,
  LegendComponent,
  SVGRenderer,
]);

type ECOption = ComposeOption<
    | LineSeriesOption
    | GridComponentOption
    | TooltipComponentOption
    | LegendComponentOption
>;

export interface IChartData {
  date: string;
  value: number | string;
}

interface ExtraDataItem {
  data: IChartData[];
  rgbColor: number[];
  colorPrefix?: string;
  labelPrefix?: string;
}

interface LineChartProps {
  data: IChartData[];
  extraData?: ExtraDataItem[];
  rgbColor?: number[];
  xAxisName?: string;
  yAxisName?: string;
  valueUnit?: string;
  isEmptyTipsVisible?: boolean;
  labelPrefix?: string;
  colorPrefix?: string;
  labelProcessor?: (data: IChartData) => string;
  renderTooltip?: (params: any) => string;
  name?: string;
  isOriginX?: boolean;
  height?: string;
  yAxisTextAlign?: 'left' | 'center' | 'right';
}

export interface LineChartHandle {
  showLoading: () => void;
  hideLoading: () => void;
  getInstance: () => echarts.ECharts | null;
}

const LineChart = forwardRef<LineChartHandle, LineChartProps>(({
  data,
  extraData = [],
  rgbColor = [255, 150, 0],
  xAxisName = '日期',
  yAxisName = '流水金额(元)',
  valueUnit = '元',
  isEmptyTipsVisible = false,
  labelPrefix,
  colorPrefix,
  labelProcessor,
  renderTooltip,
  name = 'tmp',
  isOriginX = false,
  height = '200px',
  yAxisTextAlign = 'center',
}, ref) => {
  const chartRef = useRef<HTMLDivElement>(null);
  const chartInstance = useRef<echarts.ECharts | null>(null);
  const observerRef = useRef<ResizeObserver | null>(null);

  // 修改后的x轴数据处理逻辑
  const xAxisData = useMemo(() => {
    const dataSources = [data, ...extraData.map(item => item.data)];
    const validData = dataSources.find(d => d?.length) || [];
    return validData.map((item: IChartData) =>
      (isOriginX
        ? item.date
        : item.date.split('-')[2]), // 直接取日期部分(第三个分割项)
    );
  }, [data, extraData, isOriginX]);

  // 修改系列配置保留完整数据对象
  const createSeries = useCallback((
    color: number[],
    seriesData: IChartData[],
    seriesName?: string,
  ): LineSeriesOption => ({
    name: seriesName || 'tmp',
    data: seriesData, // 保留完整数据对象
    type: 'line',
    encode: {
      x: 'date',  // 指定x轴字段
      y: 'value',  // 指定y轴字段
    },
    // 其他保持不变的配置...
    smooth: true,
    symbol: 'circle',
    symbolSize: 8,
    areaStyle: {
      color: {
        type: 'linear',
        x: 0,
        y: 0,
        x2: 0,
        y2: 1,
        colorStops: [
          { offset: 0, color: `rgba(${color.join(',')},0.7)` },
          { offset: 1, color: `rgba(${color.join(',')},0)` },
        ],
      },
    },
    itemStyle: {
      color: `rgb(${color.join(',')})`,
      borderColor: 'white',
      borderWidth: 2,
    },
  }), []);

  // 获取图表配置
  const getOptions = useCallback((): ECOption => {
    const yAxisCache: Record<string, boolean> = {};

    return {
      grid: {
        // left: 40,
        left: 60, // 增加左侧间距
        right: 30,
        bottom: 60, // 足够空间防止裁剪
        top: 40,
      },
      xAxis: {
        name: `{offset|${xAxisName}}`,
        nameLocation: 'end',
        nameGap: 5,
        nameTextStyle: {
          rich: {
            offset: {
              lineHeight: 24, // 增加行高实现下移
              padding: [8, 0, 0, 0], // 调整上边距
              align: 'center',
              color: '#999',
              fontSize: 12,
            },
          },
        },
        data: xAxisData,
        axisLine: {
          lineStyle: { width: 1, color: '#D5D9E0' },
        },
        axisLabel: { color: '#999' },
        axisTick: { alignWithLabel: true },
        axisPointer: {
          show: true,
          type: 'line',
          lineStyle: {
            type: 'dashed',
            color: `rgb(${rgbColor.join(',')})`,
          },
        },
      },
      yAxis: {
        name: yAxisName,
        nameTextStyle: {
          color: '#999',
          fontSize: 12,
          padding: [0, 0, 0, 0],
          align: yAxisTextAlign,
        },
        type: 'value',
        splitLine: {
          lineStyle: {
            color: '#E6E9F0',
            type: 'dashed',
          },
        },
        axisLine: { show: false },
        axisLabel: {
          color: '#999',
          margin: 0,
          interval: 1, // ✅ 正确间隔控制
          formatter: (value: number) => { // ✅ 移除index参数
            let result = String(value);
            if (value >= 1e8) result = `${(value / 1e8).toFixed(1)}亿`;
            else if (value >= 1e7) result = `${(value / 1e7).toFixed(1)}kw`;
            else if (value >= 1e4) result = `${(value / 1e4).toFixed(1)}w`;

            return `${result}${valueUnit}`; // ✅ 直接返回结果
          }
        },
        axisTick: { show: false },
      },
      // 在图表配置中修改tooltip配置项
      tooltip: {
        trigger: 'axis',
        backgroundColor: 'rgba(17,18,18,0.7)', // 加深背景色
        textStyle: {
          color: '#fff', // 明确设置字体颜色
          fontSize: 12,
          lineHeight: 17, // 增加行高
        },
        // 修改tooltip formatter部分
        formatter: (params: any) => {
          if (renderTooltip) return renderTooltip(params);
          const firstParam = params?.[0];
          if (!firstParam) return '';

          // 安全访问数据
          const dataIndex = firstParam.dataIndex;
          const dateValue = firstParam.data?.date || '';

          // 正确解析日期格式
          const [year, month, day] = dateValue.split('-');

          const partial = isOriginX
            ? [dateValue]
            : [`${month}月${day}日`]; // 直接使用分割后的月日
          // 处理主数据
          if (data?.length && data[dataIndex]) {
            const currentData = data[dataIndex];
            // 带2位小数(银行金额常用格式)
            const moneyFormat = new Intl.NumberFormat('zh-CN', {
              style: 'decimal',
            });
            const value = labelProcessor?.(currentData) || moneyFormat.format(Number(currentData.value));
            partial.push(
              `${colorPrefix ? `<div class="color-dot" style="background:${colorPrefix}"></div>` : ''}
      ${labelPrefix || ''} ${value} ${valueUnit}`,
            );
          }

          // 处理额外数据
          extraData?.forEach(item => {
            if (item.data[dataIndex]) {
              const currentValue = item.data[dataIndex].value;
              const value = numberWithCommas(Number(currentValue));
              partial.push(
                `${item.colorPrefix ? `<div class="color-dot" style="background:${item.colorPrefix}"></div>` : ''}
        ${item.labelPrefix || ''} ${value} ${valueUnit}`,
              );
            }
          });

          return partial.join('</br>');
        },
        padding: [8, 18, 8, 18],
      },
      series: [
        ...(extraData?.map(item =>
          createSeries(item.rgbColor, item.data, item.labelPrefix),
        ) || []),
        createSeries(rgbColor, data, name),
      ],
    };
  }, [xAxisData, rgbColor, extraData, data, name, isOriginX, xAxisName, yAxisName, yAxisTextAlign, valueUnit, renderTooltip, labelProcessor, colorPrefix, labelPrefix, createSeries]);

  // 初始化图表
  useEffect(() => {
    if (!chartRef.current) return;

    chartInstance.current = echarts.init(chartRef.current, null, {
      renderer: 'svg',
    });
    chartInstance.current.setOption(getOptions());

    // 响应式处理
    const resizeHandler = () => chartInstance.current?.resize();
    observerRef.current = new ResizeObserver(() => resizeHandler());
    observerRef.current.observe(chartRef.current);

    return () => {
      observerRef.current?.disconnect();
      chartInstance.current?.dispose();
    };
  }, [getOptions]);

  // 更新图表
  useEffect(() => {
    chartInstance.current?.setOption(getOptions());
  }, [getOptions]);

  // 暴露组件方法
  useImperativeHandle(ref, () => ({
    showLoading: () => {
      chartInstance.current?.showLoading({
        text: '',
        color: '#FF9600',
      });
    },
    hideLoading: () => chartInstance.current?.hideLoading(),
    getInstance: () => chartInstance.current,
  }));

  return (
    <div className="line-chart" style={{ height }}>
      <div ref={chartRef} className="chart" />
      <div className={classNames('empty-tips', { 'empty-tips--visible': isEmptyTipsVisible })}>
          暂无数据, 本月活跃大神的数据将在当月2号6时以后更新.
      </div>
    </div>
  );
});

export default LineChart;
  • LineChart.css
javascript 复制代码
/* src/components/LineChart/LineChart.css */
.line-chart {
  width: 100%;
  position: relative;
  /* 添加最小高度防止内容塌陷 */
  min-height: 120px;
}

.chart {
  width: 100%;
  height: 100%;
  /* 修复图表可能出现的模糊问题 */
  transform: translateZ(0);
}

.empty-tips {
  /* 优化空状态样式 */
  position: absolute;
  width: 80%;
  max-width: 280px;
  padding: 16px;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  background: rgba(0, 0, 0, 0.65);
  border-radius: 8px;
  color: #fff;
  font-size: 14px;
  text-align: center;
  line-height: 1.5;
  opacity: 0;
  transition: opacity 0.3s;
  pointer-events: none;
}

.empty-tips--visible {
  opacity: 1;
  /* 添加轻微动画 */
  animation: fade-in 0.3s ease;
}

/* 新增tooltip容器样式 */
.echarts-tooltip {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
  border-radius: 8px !important;
  backdrop-filter: blur(4px);
}

.color-dot {
  /* 优化颜色点显示 */
  width: 10px;
  height: 10px;
  border-radius: 50%;
  margin-right: 6px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  /* 修复对齐问题 */
  display: inline-flex;
  vertical-align: baseline;
}

@keyframes fade-in {
  from { opacity: 0; transform: translate(-50%, -45%); }
  to { opacity: 1; transform: translate(-50%, -50%); }
}
复制代码
DashboardPage.tsx
javascript 复制代码
// src/pages/Dashboard/index.tsx
import React, { useRef } from 'react';
import LineChart, {IChartData, LineChartHandle} from '@/component/LineChart';

const DashboardPage = () => {
  const chartRef = useRef<LineChartHandle>(null);

  // 示例数据
  const data: IChartData[] = [
    { date: '2024-05-01', value: 12345670 },
    { date: '2024-05-02', value: 2345678 },
    { date: '2024-05-03', value: 3456789 },
    { date: '2024-05-04', value: 0 },
  ];

  const extraData = [
    {
      data: [
        { date: '2024-05-01', value: 5000000 },
        { date: '2024-05-02', value: 600000 },
        { date: '2024-05-03', value: 700000 },
        { date: '2024-05-04', value: 0 },
      ],
      rgbColor: [100, 200, 255],
      colorPrefix: '#64c8ff',
      labelPrefix: '辅助流水',
    },
  ];

  return (
    <div style={{ padding: 24 }}>
      <h2>数据看板</h2>
      <div style={{ marginTop: 20, height: '500px' }}>
        <LineChart
          ref={chartRef}
          data={data}
          extraData={extraData}
          rgbColor={[255, 150, 0]}
          height="100%"
          xAxisName="日期"
          yAxisName="流水金额(元)"
          valueUnit="元"
          colorPrefix="#FF9600"
          labelPrefix="主要流水"
        />
      </div>
    </div>
  );
};

export default DashboardPage;

二、优化版本,价格interval

  • LineChart.tsx
javascript 复制代码
// src/components/LineChart/LineChart.tsx
import React, {
  useEffect,
  useRef,
  useImperativeHandle,
  forwardRef,
  useMemo,
  useCallback,
} from 'react';
import * as echarts from 'echarts/core';
import type { ComposeOption } from 'echarts/core';
import type { LineSeriesOption } from 'echarts/charts';
import type {
  GridComponentOption,
  TooltipComponentOption,
  LegendComponentOption,
} from 'echarts/components';
import { SVGRenderer } from 'echarts/renderers';
import ResizeObserver from 'resize-observer-polyfill';
import classNames from 'classnames';
import { numberWithCommas } from '@/utils/numberWithCommas';
import './LineChart.css';

// 注册必要组件
const { LineChart: ELineChart } = require('echarts/charts');
const {
  GridComponent,
  TooltipComponent,
  LegendComponent,
} = require('echarts/components');

echarts.use([
  ELineChart,
  GridComponent,
  TooltipComponent,
  LegendComponent,
  SVGRenderer,
]);

type ECOption = ComposeOption<
    | LineSeriesOption
    | GridComponentOption
    | TooltipComponentOption
    | LegendComponentOption
>;

export interface IChartData {
  date: string;
  value: number | string;
}

interface ExtraDataItem {
  data: IChartData[];
  rgbColor: number[];
  colorPrefix?: string;
  labelPrefix?: string;
}

interface LineChartProps {
  data: IChartData[];
  extraData?: ExtraDataItem[];
  rgbColor?: number[];
  xAxisName?: string;
  yAxisName?: string;
  valueUnit?: string;
  isEmptyTipsVisible?: boolean;
  labelPrefix?: string;
  colorPrefix?: string;
  labelProcessor?: (data: IChartData) => string;
  renderTooltip?: (params: any) => string;
  name?: string;
  isOriginX?: boolean;
  height?: string;
  yAxisTextAlign?: 'left' | 'center' | 'right';
  interval?: number; // 日期间隔步长,例如:2就代表每隔一天 (01,03,05,...)
}

export interface LineChartHandle {
  showLoading: () => void;
  hideLoading: () => void;
  getInstance: () => echarts.ECharts | null;
}

const LineChart = forwardRef<LineChartHandle, LineChartProps>(({
  data,
  extraData = [],
  rgbColor = [255, 150, 0],
  xAxisName = '日期',
  yAxisName = '流水金额(元)',
  valueUnit = '元',
  isEmptyTipsVisible = false,
  labelPrefix,
  colorPrefix,
  labelProcessor,
  renderTooltip,
  name = 'tmp',
  isOriginX = false,
  height = '200px',
  yAxisTextAlign = 'center',
  interval = 1,
}, ref) => {
  const chartRef = useRef<HTMLDivElement>(null);
  const chartInstance = useRef<echarts.ECharts | null>(null);
  const observerRef = useRef<ResizeObserver | null>(null);

  // 修改后的x轴数据处理逻辑
  const xAxisData = useMemo(() => {
    const dataSources = [data, ...extraData.map(item => item.data)];
    const validData = dataSources.find(d => d?.length) || [];
    return validData.map((item: IChartData) =>
      (isOriginX
        ? item.date
        : item.date.split('-')[2]), // 直接取日期部分(第三个分割项)
    );
  }, [data, extraData, isOriginX]);

  // 修改系列配置保留完整数据对象
  const createSeries = useCallback((
    color: number[],
    seriesData: IChartData[],
    seriesName?: string,
  ): LineSeriesOption => ({
    name: seriesName || 'tmp',
    data: seriesData, // 保留完整数据对象
    type: 'line',
    encode: {
      x: 'date',  // 指定x轴字段
      y: 'value',  // 指定y轴字段
    },
    // 其他保持不变的配置...
    smooth: true,
    symbol: 'circle',
    symbolSize: 8,
    areaStyle: {
      color: {
        type: 'linear',
        x: 0,
        y: 0,
        x2: 0,
        y2: 1,
        colorStops: [
          { offset: 0, color: `rgba(${color.join(',')},0.7)` },
          { offset: 1, color: `rgba(${color.join(',')},0)` },
        ],
      },
    },
    itemStyle: {
      color: `rgb(${color.join(',')})`,
      borderColor: 'white',
      borderWidth: 2,
    },
  }), []);

  // 获取图表配置
  const getOptions = useCallback((): ECOption => {
    const yAxisCache: Record<string, boolean> = {};

    return {
      grid: {
        // left: 40,
        left: 60, // 增加左侧间距
        right: 30,
        bottom: 60, // 足够空间防止裁剪
        top: 40,
      },
      xAxis: {
        name: `{offset|${xAxisName}}`,
        nameLocation: 'end',
        nameGap: 5,
        nameTextStyle: {
          rich: {
            offset: {
              lineHeight: 24, // 增加行高实现下移
              padding: [8, 0, 0, 0], // 调整上边距
              align: 'center',
              color: '#999',
              fontSize: 12,
            },
          },
        },
        data: xAxisData,
        axisLine: {
          lineStyle: { width: 1, color: '#D5D9E0' },
        },
        axisLabel: {
          interval: 0, // 显示所有标签
          color: '#999',
          formatter: (value: string, index: number) => {
            return index % interval === 0 ? value : ''; // 不满足间隔时返回空
          },
        },
        axisTick: { alignWithLabel: true },
        axisPointer: {
          show: true,
          type: 'line',
          lineStyle: {
            type: 'dashed',
            color: `rgb(${rgbColor.join(',')})`,
          },
        },
      },
      yAxis: {
        name: yAxisName,
        nameTextStyle: {
          color: '#999',
          fontSize: 12,
          padding: [0, 0, 0, 0],
          align: yAxisTextAlign,
        },
        type: 'value',
        splitLine: {
          lineStyle: {
            color: '#E6E9F0',
            type: 'dashed',
          },
        },
        axisLine: { show: false },
        axisLabel: {
          color: '#999',
          margin: 0,
          interval: 1, // ✅ 正确间隔控制
          formatter: (value: number) => { // ✅ 移除index参数
            let result = String(value);
            if (value >= 1e8) result = `${(value / 1e8).toFixed(1)}亿`;
            else if (value >= 1e7) result = `${(value / 1e7).toFixed(1)}kw`;
            else if (value >= 1e4) result = `${(value / 1e4).toFixed(1)}w`;

            return `${result}${valueUnit}`; // ✅ 直接返回结果
          },
        },
        axisTick: { show: false },
      },
      // 在图表配置中修改tooltip配置项
      tooltip: {
        trigger: 'axis',
        backgroundColor: 'rgba(17,18,18,0.7)', // 加深背景色
        textStyle: {
          color: '#fff', // 明确设置字体颜色
          fontSize: 12,
          lineHeight: 17, // 增加行高
        },
        // 修改tooltip formatter部分
        formatter: (params: any) => {
          if (renderTooltip) return renderTooltip(params);
          const firstParam = params?.[0];
          if (!firstParam) return '';

          // 安全访问数据
          const dataIndex = firstParam.dataIndex;
          const dateValue = firstParam.data?.date || '';

          // 正确解析日期格式
          const [year, month, day] = dateValue.split('-');

          const partial = isOriginX
            ? [dateValue]
            : [`${month}月${day}日`]; // 直接使用分割后的月日
          // 处理主数据
          if (data?.length && data[dataIndex]) {
            const currentData = data[dataIndex];
            // 带2位小数(银行金额常用格式)
            const moneyFormat = new Intl.NumberFormat('zh-CN', {
              style: 'decimal',
            });
            const value = labelProcessor?.(currentData) || moneyFormat.format(Number(currentData.value));
            partial.push(
              `${colorPrefix ? `<div class="color-dot" style="background:${colorPrefix}"></div>` : ''}
      ${labelPrefix || ''} ${value} ${valueUnit}`,
            );
          }

          // 处理额外数据
          extraData?.forEach(item => {
            if (item.data[dataIndex]) {
              const currentValue = item.data[dataIndex].value;
              const value = numberWithCommas(Number(currentValue));
              partial.push(
                `${item.colorPrefix ? `<div class="color-dot" style="background:${item.colorPrefix}"></div>` : ''}
        ${item.labelPrefix || ''} ${value} ${valueUnit}`,
              );
            }
          });

          return partial.join('</br>');
        },
        padding: [8, 18, 8, 18],
      },
      series: [
        ...(extraData?.map(item =>
          createSeries(item.rgbColor, item.data, item.labelPrefix),
        ) || []),
        createSeries(rgbColor, data, name),
      ],
    };
  }, [xAxisData, rgbColor, extraData, data, name, isOriginX, xAxisName, yAxisName, yAxisTextAlign, valueUnit, renderTooltip, labelProcessor, colorPrefix, labelPrefix, createSeries]);

  // 初始化图表
  useEffect(() => {
    if (!chartRef.current) return;

    chartInstance.current = echarts.init(chartRef.current, null, {
      renderer: 'svg',
    });
    chartInstance.current.setOption(getOptions());

    // 响应式处理
    const resizeHandler = () => chartInstance.current?.resize();
    observerRef.current = new ResizeObserver(() => resizeHandler());
    observerRef.current.observe(chartRef.current);

    return () => {
      observerRef.current?.disconnect();
      chartInstance.current?.dispose();
    };
  }, [getOptions]);

  // 更新图表
  useEffect(() => {
    chartInstance.current?.setOption(getOptions());
  }, [getOptions]);

  // 暴露组件方法
  useImperativeHandle(ref, () => ({
    showLoading: () => {
      chartInstance.current?.showLoading({
        text: '',
        color: '#FF9600',
      });
    },
    hideLoading: () => chartInstance.current?.hideLoading(),
    getInstance: () => chartInstance.current,
  }));

  return (
    <div className="line-chart" style={{ height }}>
      <div ref={chartRef} className="chart" />
      <div className={classNames('empty-tips', { 'empty-tips--visible': isEmptyTipsVisible })}>
          暂无数据, 本月活跃大神的数据将在当月2号6时以后更新.
      </div>
    </div>
  );
});

export default LineChart;
  • LineChart.css
javascript 复制代码
/* src/components/LineChart/LineChart.css */
.line-chart {
  width: 100%;
  position: relative;
  /* 添加最小高度防止内容塌陷 */
  min-height: 120px;
}

.chart {
  width: 100%;
  height: 100%;
  /* 修复图表可能出现的模糊问题 */
  transform: translateZ(0);
}

.empty-tips {
  /* 优化空状态样式 */
  position: absolute;
  width: 80%;
  max-width: 280px;
  padding: 16px;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  background: rgba(0, 0, 0, 0.65);
  border-radius: 8px;
  color: #fff;
  font-size: 14px;
  text-align: center;
  line-height: 1.5;
  opacity: 0;
  transition: opacity 0.3s;
  pointer-events: none;
}

.empty-tips--visible {
  opacity: 1;
  /* 添加轻微动画 */
  animation: fade-in 0.3s ease;
}

/* 新增tooltip容器样式 */
.echarts-tooltip {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
  border-radius: 8px !important;
  backdrop-filter: blur(4px);
}

.color-dot {
  /* 优化颜色点显示 */
  width: 10px;
  height: 10px;
  border-radius: 50%;
  margin-right: 6px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  /* 修复对齐问题 */
  display: inline-flex;
  vertical-align: baseline;
}

@keyframes fade-in {
  from { opacity: 0; transform: translate(-50%, -45%); }
  to { opacity: 1; transform: translate(-50%, -50%); }
}
  • DashboardPage.tsx
javascript 复制代码
import React, { useRef } from 'react';
import LineChart, {IChartData, LineChartHandle} from '@/component/LineChart';

const DashboardPage = () => {
  const chartRef = useRef<LineChartHandle>(null);

  // 生成完整5月份数据(31天)
  const generateMonthlyData = () => {
    const daysInMonth = 31;
    const baseValue = 10000000; // 基础值1千万
    const data: IChartData[] = [];
    const extra: IChartData[] = [];

    // 带波动的数据生成函数
    const generateDataPoint = (day: number, isExtra = false) => {
      // 基础波动(-20% ~ +20%)
      let fluctuation = 1 + (Math.random() * 0.4 - 0.2);

      // 周末效应(周六周日减少15%)
      const date = new Date(2024, 4, day);
      if ([0, 6].includes(date.getDay())) {
        fluctuation *= 0.85;
      }

      // 月末促销(最后三天增加40%)
      if (day > 28) fluctuation *= 1.4;

      // 额外数据波动较小(-15% ~ +15%)
      if (isExtra) {
        fluctuation = 1 + (Math.random() * 0.3 - 0.15);
        return baseValue * 0.4 * fluctuation; // 主数据的40%左右
      }

      return baseValue * fluctuation;
    };

    for (let day = 1; day <= daysInMonth; day++) {
      const dateString = new Date(2024, 4, day).toLocaleDateString('zh-CN', {
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
      })
        .replace(/\//g, '-');

      data.push({
        date: dateString,
        value: Math.round(generateDataPoint(day)),
      });

      extra.push({
        date: dateString,
        value: Math.round(generateDataPoint(day, true)),
      });
    }

    return { data, extra };
  };

  const { data, extra } = generateMonthlyData();

  const extraData = [
    {
      data: extra,
      rgbColor: [100, 200, 255],
      colorPrefix: '#64c8ff',
      labelPrefix: '辅助流水',
    },
  ];

  return (
    <div style={{ padding: 24 }}>
      <h2>数据看板</h2>
      <div style={{ marginTop: 20, height: '500px' }}>
        <LineChart
          ref={chartRef}
          data={data}
          extraData={extraData}
          rgbColor={[255, 150, 0]}
          height="100%"
          xAxisName="日期"
          yAxisName="流水金额(元)"
          valueUnit="元"
          colorPrefix="#FF9600"
          labelPrefix="主要流水"
          interval={2}
        />
      </div>
    </div>
  );
};

export default DashboardPage;
相关推荐
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment10 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端