update pay function
This commit is contained in:
@@ -1,63 +1,10 @@
|
|||||||
// src/components/ChatBot/EChartsRenderer.js
|
// src/components/ChatBot/EChartsRenderer.js
|
||||||
// ECharts 图表渲染组件
|
// ECharts 图表渲染组件
|
||||||
|
|
||||||
import React, { useEffect, useRef, useCallback, useState } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { Box, useColorModeValue, Skeleton } from '@chakra-ui/react';
|
import { Box, useColorModeValue } from '@chakra-ui/react';
|
||||||
import * as echarts from 'echarts';
|
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 图表渲染组件
|
* ECharts 图表渲染组件
|
||||||
* @param {Object} option - ECharts 配置对象
|
* @param {Object} option - ECharts 配置对象
|
||||||
@@ -67,9 +14,6 @@ const isValidOption = (option) => {
|
|||||||
export const EChartsRenderer = ({ option, height = 400, variant = 'auto' }) => {
|
export const EChartsRenderer = ({ option, height = 400, variant = 'auto' }) => {
|
||||||
const chartRef = useRef(null);
|
const chartRef = useRef(null);
|
||||||
const chartInstance = useRef(null);
|
const chartInstance = useRef(null);
|
||||||
const resizeObserverRef = useRef(null);
|
|
||||||
const [isReady, setIsReady] = useState(false);
|
|
||||||
const initTimeoutRef = useRef(null);
|
|
||||||
|
|
||||||
// 系统颜色模式
|
// 系统颜色模式
|
||||||
const systemBgColor = useColorModeValue('white', 'transparent');
|
const systemBgColor = useColorModeValue('white', 'transparent');
|
||||||
@@ -79,46 +23,31 @@ export const EChartsRenderer = ({ option, height = 400, variant = 'auto' }) => {
|
|||||||
const isDarkMode = variant === 'dark' ? true : variant === 'light' ? false : systemIsDark;
|
const isDarkMode = variant === 'dark' ? true : variant === 'light' ? false : systemIsDark;
|
||||||
const bgColor = variant === 'dark' ? 'transparent' : variant === 'light' ? 'white' : systemBgColor;
|
const bgColor = variant === 'dark' ? 'transparent' : variant === 'light' ? 'white' : systemBgColor;
|
||||||
|
|
||||||
// 初始化或更新图表的函数
|
useEffect(() => {
|
||||||
const initChart = useCallback(() => {
|
if (!chartRef.current || !option) {
|
||||||
if (!chartRef.current || !option) return;
|
console.warn('[EChartsRenderer] Missing chartRef or option');
|
||||||
|
|
||||||
// 验证配置是否有效
|
|
||||||
if (!isValidOption(option)) {
|
|
||||||
console.warn('EChartsRenderer: Invalid or empty chart configuration, skipping render');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保容器有有效尺寸
|
// 延迟初始化,确保 DOM 已渲染
|
||||||
const containerWidth = chartRef.current.offsetWidth;
|
const timer = setTimeout(() => {
|
||||||
const containerHeight = chartRef.current.offsetHeight;
|
try {
|
||||||
|
// 如果已有实例,先销毁
|
||||||
if (containerWidth < 50 || containerHeight < 50) {
|
if (chartInstance.current) {
|
||||||
// 容器尺寸太小,延迟重试
|
chartInstance.current.dispose();
|
||||||
initTimeoutRef.current = setTimeout(initChart, 100);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化图表(支持深色模式)
|
// 初始化图表
|
||||||
if (!chartInstance.current) {
|
chartInstance.current = echarts.init(chartRef.current, isDarkMode ? 'dark' : null);
|
||||||
chartInstance.current = echarts.init(chartRef.current, isDarkMode ? 'dark' : null, {
|
|
||||||
renderer: 'canvas',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 深色模式下的默认文字颜色
|
// 深色模式下的样式调整
|
||||||
const darkModeTextStyle = isDarkMode
|
const darkModeStyle = isDarkMode ? {
|
||||||
? {
|
backgroundColor: 'transparent',
|
||||||
textStyle: { color: '#e5e7eb' },
|
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 = {
|
const finalOption = {
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
grid: {
|
grid: {
|
||||||
left: '3%',
|
left: '3%',
|
||||||
@@ -126,108 +55,42 @@ export const EChartsRenderer = ({ option, height = 400, variant = 'auto' }) => {
|
|||||||
bottom: '3%',
|
bottom: '3%',
|
||||||
containLabel: true,
|
containLabel: true,
|
||||||
},
|
},
|
||||||
|
...darkModeStyle,
|
||||||
...option,
|
...option,
|
||||||
...darkModeTextStyle,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 设置图表配置(使用 try-catch 防止 ECharts 内部错误)
|
// 设置配置
|
||||||
try {
|
chartInstance.current.setOption(finalOption);
|
||||||
chartInstance.current.setOption(defaultOption, true);
|
|
||||||
setIsReady(true);
|
console.log('[EChartsRenderer] Chart rendered successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('EChartsRenderer: Failed to render chart', 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);
|
}, 100);
|
||||||
});
|
|
||||||
resizeObserverRef.current.observe(chartRef.current);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 窗口 resize 事件作为备用
|
// 窗口 resize 处理
|
||||||
|
const handleResize = () => {
|
||||||
|
chartInstance.current?.resize();
|
||||||
|
};
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
clearTimeout(timer);
|
||||||
window.removeEventListener('resize', handleResize);
|
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) {
|
if (chartInstance.current) {
|
||||||
chartInstance.current.dispose();
|
chartInstance.current.dispose();
|
||||||
chartInstance.current = null;
|
chartInstance.current = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, [option, isDarkMode]);
|
||||||
|
|
||||||
return (
|
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
|
<Box
|
||||||
ref={chartRef}
|
ref={chartRef}
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height={`${height}px`}
|
||||||
bg={bgColor}
|
bg={bgColor}
|
||||||
borderRadius="md"
|
borderRadius="md"
|
||||||
boxShadow="sm"
|
|
||||||
opacity={isReady ? 1 : 0}
|
|
||||||
transition="opacity 0.3s ease"
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,39 @@
|
|||||||
// src/components/ChatBot/MarkdownWithCharts.js
|
// src/components/ChatBot/MarkdownWithCharts.js
|
||||||
// 支持 ECharts 图表的 Markdown 渲染组件
|
// 支持 ECharts 图表的 Markdown 渲染组件
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Box, Alert, AlertIcon, Text, VStack, Code, useColorModeValue, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@chakra-ui/react';
|
import { Box, Alert, AlertIcon, Text, VStack, Code, useColorModeValue, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@chakra-ui/react';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
import { EChartsRenderer } from './EChartsRenderer';
|
import { EChartsRenderer } from './EChartsRenderer';
|
||||||
import { logger } from '@utils/logger';
|
import { logger } from '@utils/logger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 稳定的图表组件包装器
|
||||||
|
* 使用 useMemo 避免 option 对象引用变化导致的重复渲染
|
||||||
|
*/
|
||||||
|
const StableChart = React.memo(({ jsonString, height, variant }) => {
|
||||||
|
const chartOption = useMemo(() => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(jsonString);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[StableChart] JSON parse error:', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, [jsonString]);
|
||||||
|
|
||||||
|
if (!chartOption) {
|
||||||
|
return (
|
||||||
|
<Alert status="warning" borderRadius="md">
|
||||||
|
<AlertIcon />
|
||||||
|
<Text fontSize="sm">图表配置解析失败</Text>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <EChartsRenderer option={chartOption} height={height} variant={variant} />;
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析 Markdown 内容,提取 ECharts 代码块
|
* 解析 Markdown 内容,提取 ECharts 代码块
|
||||||
* 支持处理:
|
* 支持处理:
|
||||||
@@ -309,172 +335,39 @@ export const MarkdownWithCharts = ({ content, variant = 'auto' }) => {
|
|||||||
);
|
);
|
||||||
} else if (part.type === 'chart') {
|
} else if (part.type === 'chart') {
|
||||||
// 渲染 ECharts 图表
|
// 渲染 ECharts 图表
|
||||||
try {
|
// 清理可能的残留字符
|
||||||
// 清理可能的 Markdown 残留符号和代码块标记
|
|
||||||
let cleanContent = part.content.trim();
|
let cleanContent = part.content.trim();
|
||||||
|
|
||||||
// 移除可能残留的代码块结束标记
|
|
||||||
cleanContent = cleanContent.replace(/```\s*$/g, '').trim();
|
cleanContent = cleanContent.replace(/```\s*$/g, '').trim();
|
||||||
|
|
||||||
// 移除可能的前后空白和不可见字符
|
// 调试日志
|
||||||
cleanContent = cleanContent.replace(/^\s+|\s+$/g, '');
|
console.log('[MarkdownWithCharts] Rendering chart, content length:', cleanContent.length);
|
||||||
|
console.log('[MarkdownWithCharts] Content preview:', cleanContent.substring(0, 100));
|
||||||
|
|
||||||
// ========== 增强的 JSON 修复逻辑 ==========
|
// 验证 JSON 是否可以解析
|
||||||
// 使用栈来跟踪括号和字符串状态
|
|
||||||
const stack = [];
|
|
||||||
let inString = false;
|
|
||||||
let escape = false;
|
|
||||||
let stringStartPos = -1;
|
|
||||||
|
|
||||||
for (let i = 0; i < cleanContent.length; i++) {
|
|
||||||
const char = cleanContent[i];
|
|
||||||
|
|
||||||
if (escape) {
|
|
||||||
escape = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (char === '\\' && inString) {
|
|
||||||
escape = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (char === '"') {
|
|
||||||
if (inString) {
|
|
||||||
inString = false;
|
|
||||||
stringStartPos = -1;
|
|
||||||
} else {
|
|
||||||
inString = true;
|
|
||||||
stringStartPos = i;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inString) continue;
|
|
||||||
|
|
||||||
if (char === '{' || char === '[') {
|
|
||||||
stack.push(char);
|
|
||||||
} else if (char === '}') {
|
|
||||||
if (stack.length > 0 && stack[stack.length - 1] === '{') {
|
|
||||||
stack.pop();
|
|
||||||
}
|
|
||||||
} else if (char === ']') {
|
|
||||||
if (stack.length > 0 && stack[stack.length - 1] === '[') {
|
|
||||||
stack.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修复1: 如果字符串未闭合,需要关闭字符串
|
|
||||||
if (inString) {
|
|
||||||
logger.warn('检测到未闭合的字符串,尝试修复', {
|
|
||||||
position: stringStartPos,
|
|
||||||
});
|
|
||||||
// 找到最后一个有意义的位置(非空白)
|
|
||||||
let lastMeaningful = cleanContent.length - 1;
|
|
||||||
while (lastMeaningful > stringStartPos && /\s/.test(cleanContent[lastMeaningful])) {
|
|
||||||
lastMeaningful--;
|
|
||||||
}
|
|
||||||
// 截取到最后一个有意义的字符,然后闭合字符串
|
|
||||||
cleanContent = cleanContent.substring(0, lastMeaningful + 1) + '"';
|
|
||||||
logger.info('字符串已修复(添加闭合引号)');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修复2: 如果栈不为空,说明有未闭合的括号,需要补全
|
|
||||||
if (stack.length > 0) {
|
|
||||||
logger.warn('检测到不完整的 ECharts JSON,尝试修复', {
|
|
||||||
unclosed: stack.join(''),
|
|
||||||
});
|
|
||||||
|
|
||||||
// 按照栈的逆序补全闭合括号
|
|
||||||
while (stack.length > 0) {
|
|
||||||
const open = stack.pop();
|
|
||||||
cleanContent += open === '{' ? '}' : ']';
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('ECharts JSON 括号已修复');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修复3: 尝试清理可能的尾部垃圾字符(如截断导致的无效字符)
|
|
||||||
// 找到最后一个有效的 JSON 结束字符
|
|
||||||
const lastValidEnd = Math.max(
|
|
||||||
cleanContent.lastIndexOf('}'),
|
|
||||||
cleanContent.lastIndexOf(']'),
|
|
||||||
cleanContent.lastIndexOf('"')
|
|
||||||
);
|
|
||||||
if (lastValidEnd > 0 && lastValidEnd < cleanContent.length - 1) {
|
|
||||||
const tail = cleanContent.substring(lastValidEnd + 1).trim();
|
|
||||||
// 如果尾部不是有效的 JSON 字符,则截断
|
|
||||||
if (tail && !/^[,\}\]\s]*$/.test(tail)) {
|
|
||||||
logger.warn('检测到尾部垃圾字符,截断处理', { tail: tail.substring(0, 50) });
|
|
||||||
cleanContent = cleanContent.substring(0, lastValidEnd + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试解析 JSON
|
|
||||||
let chartOption;
|
|
||||||
try {
|
try {
|
||||||
chartOption = JSON.parse(cleanContent);
|
const testParse = JSON.parse(cleanContent);
|
||||||
} catch (parseError) {
|
console.log('[MarkdownWithCharts] JSON valid, has series:', !!testParse.series);
|
||||||
// 如果解析失败,尝试更激进的修复
|
} catch (e) {
|
||||||
logger.warn('首次 JSON 解析失败,尝试更激进的修复', { error: parseError.message });
|
console.error('[MarkdownWithCharts] JSON parse error:', e.message);
|
||||||
|
console.log('[MarkdownWithCharts] Problematic content:', cleanContent.substring(0, 300));
|
||||||
|
|
||||||
// 尝试找到 JSON 的有效开始和结束
|
return (
|
||||||
const jsonStart = cleanContent.indexOf('{');
|
<Alert status="warning" key={index} borderRadius="md">
|
||||||
if (jsonStart >= 0) {
|
<AlertIcon />
|
||||||
let fixedContent = cleanContent.substring(jsonStart);
|
<VStack align="flex-start" spacing={1} flex="1">
|
||||||
// 重新计算并补全括号
|
<Text fontSize="sm" fontWeight="bold">
|
||||||
const fixStack = [];
|
图表配置解析失败
|
||||||
let fixInString = false;
|
</Text>
|
||||||
let fixEscape = false;
|
<Text fontSize="xs" color="gray.600">
|
||||||
|
错误: {e.message}
|
||||||
for (let i = 0; i < fixedContent.length; i++) {
|
</Text>
|
||||||
const char = fixedContent[i];
|
<Code fontSize="xs" maxW="100%" overflow="auto" whiteSpace="pre-wrap">
|
||||||
if (fixEscape) { fixEscape = false; continue; }
|
{cleanContent.substring(0, 300)}
|
||||||
if (char === '\\' && fixInString) { fixEscape = true; continue; }
|
{cleanContent.length > 300 ? '...' : ''}
|
||||||
if (char === '"') { fixInString = !fixInString; continue; }
|
</Code>
|
||||||
if (fixInString) continue;
|
</VStack>
|
||||||
if (char === '{' || char === '[') fixStack.push(char);
|
</Alert>
|
||||||
else if (char === '}' && fixStack.length > 0 && fixStack[fixStack.length - 1] === '{') fixStack.pop();
|
);
|
||||||
else if (char === ']' && fixStack.length > 0 && fixStack[fixStack.length - 1] === '[') fixStack.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果仍在字符串中,关闭字符串
|
|
||||||
if (fixInString) fixedContent += '"';
|
|
||||||
// 补全括号
|
|
||||||
while (fixStack.length > 0) {
|
|
||||||
const open = fixStack.pop();
|
|
||||||
fixedContent += open === '{' ? '}' : ']';
|
|
||||||
}
|
|
||||||
|
|
||||||
chartOption = JSON.parse(fixedContent);
|
|
||||||
logger.info('激进修复成功');
|
|
||||||
} else {
|
|
||||||
throw parseError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证是否是有效的 ECharts 配置
|
|
||||||
if (!chartOption || typeof chartOption !== 'object') {
|
|
||||||
throw new Error('Invalid chart configuration: not an object');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证 series 是否存在且有效
|
|
||||||
if (!chartOption.series) {
|
|
||||||
throw new Error('Invalid chart configuration: missing series');
|
|
||||||
}
|
|
||||||
const series = Array.isArray(chartOption.series) ? chartOption.series : [chartOption.series];
|
|
||||||
const hasValidSeries = series.some(s => s && s.data && Array.isArray(s.data) && s.data.length > 0);
|
|
||||||
if (!hasValidSeries) {
|
|
||||||
throw new Error('Invalid chart configuration: series has no valid data');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证 xAxis(如果存在)
|
|
||||||
if (chartOption.xAxis) {
|
|
||||||
const xAxis = Array.isArray(chartOption.xAxis) ? chartOption.xAxis[0] : chartOption.xAxis;
|
|
||||||
if (xAxis && xAxis.type === 'category' && (!xAxis.data || !Array.isArray(xAxis.data) || xAxis.data.length === 0)) {
|
|
||||||
throw new Error('Invalid chart configuration: xAxis category type requires data');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -486,36 +379,9 @@ export const MarkdownWithCharts = ({ content, variant = 'auto' }) => {
|
|||||||
borderRadius="md"
|
borderRadius="md"
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
>
|
>
|
||||||
<EChartsRenderer option={chartOption} height={350} variant={variant} />
|
<StableChart jsonString={cleanContent} height={350} variant={variant} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
} catch (error) {
|
|
||||||
// 记录详细的错误信息
|
|
||||||
logger.error('解析 ECharts 配置失败', {
|
|
||||||
error: error.message,
|
|
||||||
contentLength: part.content.length,
|
|
||||||
contentPreview: part.content.substring(0, 200),
|
|
||||||
errorStack: error.stack
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Alert status="warning" key={index} borderRadius="md">
|
|
||||||
<AlertIcon />
|
|
||||||
<VStack align="flex-start" spacing={1} flex="1">
|
|
||||||
<Text fontSize="sm" fontWeight="bold">
|
|
||||||
图表配置解析失败
|
|
||||||
</Text>
|
|
||||||
<Text fontSize="xs" color="gray.600">
|
|
||||||
错误: {error.message}
|
|
||||||
</Text>
|
|
||||||
<Code fontSize="xs" maxW="100%" overflow="auto" whiteSpace="pre-wrap">
|
|
||||||
{part.content.substring(0, 300)}
|
|
||||||
{part.content.length > 300 ? '...' : ''}
|
|
||||||
</Code>
|
|
||||||
</VStack>
|
|
||||||
</Alert>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user