| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162 |
- import React, { useEffect, useRef, useMemo } from 'react';
- import Taro from '@tarojs/taro';
- import { Canvas } from '@tarojs/components';
- import { uCharts } from '../lib/charts/index';
- import type { ChartsConfig, TouchEvent } from '../lib/charts/index';
- import type { ExtendedCanvasContext } from '../types';
- /**
- * BaseChart 组件的 Props 接口
- */
- export interface BaseChartProps {
- /** Canvas 元素的 ID,必须唯一 */
- canvasId: string;
- /** 图表宽度(像素),默认为屏幕宽度 */
- width?: number;
- /** 图表高度(像素),默认根据宽高比计算 */
- height?: number;
- /** 设备像素比,默认根据环境自动设置 */
- pixelRatio?: number;
- /** 图表类型 */
- type: ChartsConfig['type'];
- /** X 轴分类数据 */
- categories?: string[];
- /** 系列数据 */
- series?: ChartsConfig['series'];
- /** 额外的图表配置 */
- config?: Partial<ChartsConfig>;
- /** 触摸开始事件 */
- onTouchStart?: (e: TouchEvent) => void;
- /** 触摸移动事件 */
- onTouchMove?: (e: TouchEvent) => void;
- /** 触摸结束事件 */
- onTouchEnd?: (e: TouchEvent) => void;
- }
- /**
- * BaseChart 基础图表组件
- *
- * 封装 Canvas 创建和销毁逻辑,提供响应式尺寸计算和像素比适配
- */
- export const BaseChart: React.FC<BaseChartProps> = (props) => {
- const {
- canvasId,
- width,
- height,
- pixelRatio,
- type,
- categories = [],
- series = [],
- config = {},
- onTouchStart,
- onTouchMove,
- onTouchEnd,
- } = props;
- const chartRef = useRef<uCharts | null>(null);
- /**
- * 计算响应式尺寸和像素比
- */
- const { cWidth, cHeight, actualPixelRatio } = useMemo(() => {
- const sysInfo = Taro.getSystemInfoSync();
- // 支付宝小程序需要使用实际的 pixelRatio,其他平台使用 1
- const pr = pixelRatio ?? (Taro.getEnv() === Taro.ENV_TYPE.ALIPAY ? sysInfo.pixelRatio : 1);
- const cw = width ?? pr * sysInfo.windowWidth;
- const ch = height ?? (500 / 750 * cw);
- return { cWidth: cw, cHeight: ch, actualPixelRatio: pr };
- }, [width, height, pixelRatio]);
- /**
- * Canvas props 根据 platform 动态计算
- */
- const canvasProps = useMemo(() => {
- if (Taro.getEnv() === Taro.ENV_TYPE.ALIPAY) {
- return {
- width: String(cWidth),
- height: String(cHeight),
- style: { width: '100%', height: '100%' }
- };
- }
- return {
- style: { width: `${cWidth}px`, height: `${cHeight}px` }
- };
- }, [cWidth, cHeight]);
- /**
- * 初始化图表实例
- */
- useEffect(() => {
- // 使用 setTimeout 确保 Canvas DOM 元素已渲染完成
- const timer = setTimeout(() => {
- const rawCtx = Taro.createCanvasContext(canvasId);
- // 检查 canvas 上下文是否有效
- if (!rawCtx) {
- console.error('[BaseChart] CanvasContext创建失败!')
- return
- }
- // 将 Taro CanvasContext 转换为 uCharts 需要的 CanvasContext
- // 注意:不能使用展开运算符,因为这会复制方法但丢失 this 指向
- const ctx = rawCtx as ExtendedCanvasContext;
- // 添加 uCharts 需要的 width 和 height 属性
- ctx.width = cWidth;
- ctx.height = cHeight;
- const chartConfig: ChartsConfig = {
- type,
- context: ctx,
- categories,
- series,
- width: cWidth,
- height: cHeight,
- pixelRatio: actualPixelRatio,
- ...config,
- };
- chartRef.current = new uCharts(chartConfig);
- }, 0)
- return () => {
- // 清理定时器和图表实例
- clearTimeout(timer)
- chartRef.current = null;
- };
- }, [canvasId, type, categories, series, cWidth, cHeight, actualPixelRatio, config]);
- /**
- * 触摸开始事件处理
- */
- const handleTouchStart = (e: any) => {
- onTouchStart?.(e as TouchEvent);
- };
- /**
- * 触摸移动事件处理
- */
- const handleTouchMove = (e: any) => {
- onTouchMove?.(e as TouchEvent);
- };
- /**
- * 触摸结束事件处理
- */
- const handleTouchEnd = (e: any) => {
- onTouchEnd?.(e as TouchEvent);
- };
- return (
- <Canvas
- canvasId={canvasId}
- id={canvasId}
- {...canvasProps}
- onTouchStart={handleTouchStart}
- onTouchMove={handleTouchMove}
- onTouchEnd={handleTouchEnd}
- type="2d"
- />
- );
- };
- export default BaseChart;
|