update pay function
This commit is contained in:
@@ -1,63 +1,10 @@
|
||||
// src/components/ChatBot/EChartsRenderer.js
|
||||
// ECharts 图表渲染组件
|
||||
|
||||
import React, { useEffect, useRef, useCallback, useState } from 'react';
|
||||
import { Box, useColorModeValue, Skeleton } from '@chakra-ui/react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Box, useColorModeValue } from '@chakra-ui/react';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
/**
|
||||
* 验证 ECharts 配置是否有效
|
||||
*/
|
||||
const isValidOption = (option) => {
|
||||
if (!option || typeof option !== 'object') return false;
|
||||
|
||||
// 检查 xAxis 配置(柱状图/折线图必须)
|
||||
if (option.xAxis !== undefined) {
|
||||
const xAxis = Array.isArray(option.xAxis) ? option.xAxis[0] : option.xAxis;
|
||||
// xAxis 必须存在且有效
|
||||
if (!xAxis || typeof xAxis !== 'object') {
|
||||
return false;
|
||||
}
|
||||
// category 类型必须有 data
|
||||
if (xAxis.type === 'category' && (!xAxis.data || !Array.isArray(xAxis.data) || xAxis.data.length === 0)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查 yAxis 配置
|
||||
if (option.yAxis !== undefined) {
|
||||
const yAxis = Array.isArray(option.yAxis) ? option.yAxis[0] : option.yAxis;
|
||||
if (!yAxis || typeof yAxis !== 'object') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查 series 配置(必须有且有效)
|
||||
if (!option.series) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const series = Array.isArray(option.series) ? option.series : [option.series];
|
||||
if (series.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 至少有一个 series 有有效数据
|
||||
const hasValidSeries = series.some(s => {
|
||||
if (!s || typeof s !== 'object') return false;
|
||||
// 检查 data 是否存在且为数组
|
||||
if (!s.data || !Array.isArray(s.data)) return false;
|
||||
// 检查 data 是否有内容
|
||||
return s.data.length > 0;
|
||||
});
|
||||
|
||||
if (!hasValidSeries) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* ECharts 图表渲染组件
|
||||
* @param {Object} option - ECharts 配置对象
|
||||
@@ -67,9 +14,6 @@ const isValidOption = (option) => {
|
||||
export const EChartsRenderer = ({ option, height = 400, variant = 'auto' }) => {
|
||||
const chartRef = useRef(null);
|
||||
const chartInstance = useRef(null);
|
||||
const resizeObserverRef = useRef(null);
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
const initTimeoutRef = useRef(null);
|
||||
|
||||
// 系统颜色模式
|
||||
const systemBgColor = useColorModeValue('white', 'transparent');
|
||||
@@ -79,155 +23,74 @@ export const EChartsRenderer = ({ option, height = 400, variant = 'auto' }) => {
|
||||
const isDarkMode = variant === 'dark' ? true : variant === 'light' ? false : systemIsDark;
|
||||
const bgColor = variant === 'dark' ? 'transparent' : variant === 'light' ? 'white' : systemBgColor;
|
||||
|
||||
// 初始化或更新图表的函数
|
||||
const initChart = useCallback(() => {
|
||||
if (!chartRef.current || !option) return;
|
||||
|
||||
// 验证配置是否有效
|
||||
if (!isValidOption(option)) {
|
||||
console.warn('EChartsRenderer: Invalid or empty chart configuration, skipping render');
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保容器有有效尺寸
|
||||
const containerWidth = chartRef.current.offsetWidth;
|
||||
const containerHeight = chartRef.current.offsetHeight;
|
||||
|
||||
if (containerWidth < 50 || containerHeight < 50) {
|
||||
// 容器尺寸太小,延迟重试
|
||||
initTimeoutRef.current = setTimeout(initChart, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化图表(支持深色模式)
|
||||
if (!chartInstance.current) {
|
||||
chartInstance.current = echarts.init(chartRef.current, isDarkMode ? 'dark' : null, {
|
||||
renderer: 'canvas',
|
||||
});
|
||||
}
|
||||
|
||||
// 深色模式下的默认文字颜色
|
||||
const darkModeTextStyle = isDarkMode
|
||||
? {
|
||||
textStyle: { color: '#e5e7eb' },
|
||||
title: { textStyle: { color: '#f3f4f6' }, ...option?.title },
|
||||
legend: { textStyle: { color: '#d1d5db' }, ...option?.legend },
|
||||
xAxis: { axisLabel: { color: '#9ca3af' }, axisLine: { lineStyle: { color: '#4b5563' } }, ...option?.xAxis },
|
||||
yAxis: { axisLabel: { color: '#9ca3af' }, axisLine: { lineStyle: { color: '#4b5563' } }, splitLine: { lineStyle: { color: '#374151' } }, ...option?.yAxis },
|
||||
}
|
||||
: {};
|
||||
|
||||
// 设置默认主题配置
|
||||
const defaultOption = {
|
||||
backgroundColor: 'transparent',
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
...option,
|
||||
...darkModeTextStyle,
|
||||
};
|
||||
|
||||
// 设置图表配置(使用 try-catch 防止 ECharts 内部错误)
|
||||
try {
|
||||
chartInstance.current.setOption(defaultOption, true);
|
||||
setIsReady(true);
|
||||
} catch (error) {
|
||||
console.error('EChartsRenderer: Failed to render chart', error);
|
||||
// 销毁出错的图表实例
|
||||
chartInstance.current?.dispose();
|
||||
chartInstance.current = null;
|
||||
return;
|
||||
}
|
||||
}, [option, isDarkMode]);
|
||||
|
||||
// 处理容器尺寸变化
|
||||
const handleResize = useCallback(() => {
|
||||
if (chartInstance.current) {
|
||||
// 使用 requestAnimationFrame 确保在下一帧渲染时调整大小
|
||||
requestAnimationFrame(() => {
|
||||
chartInstance.current?.resize();
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// 使用 setTimeout 确保 DOM 已经完全渲染
|
||||
initTimeoutRef.current = setTimeout(() => {
|
||||
initChart();
|
||||
}, 50);
|
||||
|
||||
// 使用 ResizeObserver 监听容器尺寸变化
|
||||
if (chartRef.current && typeof ResizeObserver !== 'undefined') {
|
||||
resizeObserverRef.current = new ResizeObserver((entries) => {
|
||||
// 防抖处理
|
||||
if (initTimeoutRef.current) {
|
||||
clearTimeout(initTimeoutRef.current);
|
||||
}
|
||||
initTimeoutRef.current = setTimeout(() => {
|
||||
handleResize();
|
||||
}, 100);
|
||||
});
|
||||
resizeObserverRef.current.observe(chartRef.current);
|
||||
if (!chartRef.current || !option) {
|
||||
console.warn('[EChartsRenderer] Missing chartRef or option');
|
||||
return;
|
||||
}
|
||||
|
||||
// 窗口 resize 事件作为备用
|
||||
// 延迟初始化,确保 DOM 已渲染
|
||||
const timer = setTimeout(() => {
|
||||
try {
|
||||
// 如果已有实例,先销毁
|
||||
if (chartInstance.current) {
|
||||
chartInstance.current.dispose();
|
||||
}
|
||||
|
||||
// 初始化图表
|
||||
chartInstance.current = echarts.init(chartRef.current, isDarkMode ? 'dark' : null);
|
||||
|
||||
// 深色模式下的样式调整
|
||||
const darkModeStyle = isDarkMode ? {
|
||||
backgroundColor: 'transparent',
|
||||
textStyle: { color: '#e5e7eb' },
|
||||
} : {};
|
||||
|
||||
// 合并配置
|
||||
const finalOption = {
|
||||
backgroundColor: 'transparent',
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
...darkModeStyle,
|
||||
...option,
|
||||
};
|
||||
|
||||
// 设置配置
|
||||
chartInstance.current.setOption(finalOption);
|
||||
|
||||
console.log('[EChartsRenderer] Chart rendered successfully');
|
||||
} catch (error) {
|
||||
console.error('[EChartsRenderer] Failed to render chart:', error);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// 窗口 resize 处理
|
||||
const handleResize = () => {
|
||||
chartInstance.current?.resize();
|
||||
};
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
window.removeEventListener('resize', handleResize);
|
||||
if (resizeObserverRef.current) {
|
||||
resizeObserverRef.current.disconnect();
|
||||
}
|
||||
if (initTimeoutRef.current) {
|
||||
clearTimeout(initTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, [initChart, handleResize]);
|
||||
|
||||
// option 变化时重新渲染
|
||||
useEffect(() => {
|
||||
if (chartInstance.current && option && isValidOption(option)) {
|
||||
initChart();
|
||||
}
|
||||
}, [option, initChart]);
|
||||
|
||||
// 组件卸载时销毁图表
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (chartInstance.current) {
|
||||
chartInstance.current.dispose();
|
||||
chartInstance.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
}, [option, isDarkMode]);
|
||||
|
||||
return (
|
||||
<Box position="relative" width="100%" height={`${height}px`}>
|
||||
{!isReady && (
|
||||
<Skeleton
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
width="100%"
|
||||
height="100%"
|
||||
borderRadius="md"
|
||||
startColor={isDarkMode ? 'gray.700' : 'gray.100'}
|
||||
endColor={isDarkMode ? 'gray.600' : 'gray.200'}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
ref={chartRef}
|
||||
width="100%"
|
||||
height="100%"
|
||||
bg={bgColor}
|
||||
borderRadius="md"
|
||||
boxShadow="sm"
|
||||
opacity={isReady ? 1 : 0}
|
||||
transition="opacity 0.3s ease"
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
ref={chartRef}
|
||||
width="100%"
|
||||
height={`${height}px`}
|
||||
bg={bgColor}
|
||||
borderRadius="md"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user