From b68687c61e43729e236783d8da37a82b13ffc302 Mon Sep 17 00:00:00 2001 From: hongjli <3117313295@qq.com> Date: 星期一, 28 四月 2025 11:22:26 +0800 Subject: [PATCH] 渲染echarts图优化 --- src/app/globals.css | 29 +++++ src/app/chat/page.tsx | 237 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 202 insertions(+), 64 deletions(-) diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx index 8656771..7c8d06c 100644 --- a/src/app/chat/page.tsx +++ b/src/app/chat/page.tsx @@ -44,8 +44,11 @@ function EchartsRenderer({ code }: { code: string }) { const [error, setError] = useState<string | null>(null); const [isLoading, setIsLoading] = useState(true); + const [isModalOpen, setIsModalOpen] = useState(false); const chartContainerRef = useRef<HTMLDivElement>(null); + const modalChartRef = useRef<HTMLDivElement>(null); const hasRenderedRef = useRef<boolean>(false); + const chartInstanceRef = useRef<any>(null); // 鍒濆鍖朌OM瀹瑰櫒 useEffect(() => { @@ -85,105 +88,163 @@ // 鏍囪涓哄凡娓叉煋 hasRenderedRef.current = true; - // 绠�鍖栫殑鍥捐〃鍒濆鍖栭�昏緫 - const loadChart = async () => { + // 鍒濆鍖栧浘琛� + const initializeChart = async () => { try { - // 鍔ㄦ�佸鍏charts const echarts = await import('echarts'); - // 鍐嶆纭DOM鍏冪礌瀛樺湪骞跺叿鏈夋湁鏁堝昂瀵� - if (!chartContainerRef.current || - !chartContainerRef.current.offsetWidth || - !chartContainerRef.current.offsetHeight) { - console.error('鍥捐〃瀹瑰櫒灏哄鏃犳晥锛岃烦杩囧垵濮嬪寲'); - setError('鍥捐〃瀹瑰櫒灏哄鏃犳晥'); - setIsLoading(false); - return; - } + // 鍒濆鍖栧浘琛� + const chartInstance = echarts.init(chartContainerRef.current); + chartInstanceRef.current = chartInstance; - // 鍒濆鍖栧浘琛� - 娣诲姞娓叉煋鍣ㄧ被鍨嬪弬鏁� - const chartInstance = echarts.init( - chartContainerRef.current, - undefined, - { renderer: 'canvas', devicePixelRatio: window.devicePixelRatio || 1 } - ); - + // 灏濊瘯瑙f瀽鍜岃缃浘琛ㄩ�夐」 try { - // 瀹夊叏澶勭悊浠g爜 - const safeCode = code.replace(/window\.option/g, '_uniqueOptionVar'); - - // 鎵ц浠g爜鑾峰彇閰嶇疆 - const getFinalOption = new Function(` + const safeCode = code.replace(/window\.option/g, 'option'); + const safeFunc = new Function(` "use strict"; let option; - let _uniqueOptionVar; try { ${safeCode} - return option || _uniqueOptionVar; + return option; } catch (e) { - console.error("ECharts鍐呴儴鎵ц閿欒:", e); + console.error("鍥捐〃浠g爜鎵ц閿欒:", e); return null; } `); - const chartOption = getFinalOption(); + const chartOption = safeFunc(); - // 搴旂敤閰嶇疆 if (chartOption) { chartInstance.setOption(chartOption); + setError(null); } else { - // 搴旂敤榛樿閰嶇疆 - chartInstance.setOption({ - title: { text: '鍥捐〃鏁版嵁瑙f瀽澶辫触' }, - xAxis: { type: 'category', data: ['鏃犳暟鎹�'] }, - yAxis: { type: 'value' }, - series: [{ data: [0], type: 'bar' }] - }); + throw new Error("鏃犳硶鑾峰彇鍥捐〃閰嶇疆"); } - - setIsLoading(false); } catch (e) { console.error('鍥捐〃浠g爜鎵ц閿欒:', e); - setError('鍥捐〃瑙f瀽澶辫触'); - setIsLoading(false); + setError('鍥捐〃閰嶇疆閿欒'); + + // 璁剧疆涓�涓粯璁ゅ浘琛ㄤ互鏄剧ず閿欒 + chartInstance.setOption({ + title: { text: '鍥捐〃閰嶇疆閿欒' }, + xAxis: { type: 'category', data: ['閿欒'] }, + yAxis: { type: 'value' }, + series: [{ data: [0], type: 'bar' }] + }); } - // 绐楀彛澶у皬鏀瑰彉鏃讹紝閲嶈鍥捐〃澶у皬 + // 娣诲姞绐楀彛澶у皬鍙樺寲鐩戝惉鍣� const handleResize = () => chartInstance.resize(); window.addEventListener('resize', handleResize); - // 缁勪欢鍗歌浇鏃舵竻鐞� + // 娓呯悊鍑芥暟 return () => { window.removeEventListener('resize', handleResize); - try { - chartInstance.dispose(); - } catch (e) {} + chartInstance.dispose(); }; } catch (e) { - console.error('鍥捐〃鍔犺浇澶辫触:', e); + console.error('ECharts鍔犺浇澶辫触:', e); setError('鍥捐〃搴撳姞杞藉け璐�'); + } finally { setIsLoading(false); } }; - // 鎵ц鍔犺浇 - loadChart(); + initializeChart(); }, 500); // 澧炲姞寤惰繜锛岀‘淇滵OM瀹屽叏娓叉煋 return () => clearTimeout(timer); }, [code]); + // 褰撳叏灞忕姸鎬佸彉鍖栨椂閲嶆柊璋冩暣鍥捐〃澶у皬 + useEffect(() => { + if (chartInstanceRef.current) { + setTimeout(() => { + chartInstanceRef.current.resize(); + }, 300); // 缁橠OM涓�浜涙椂闂存潵鏇存柊 + } + }, [isModalOpen]); + + // 澶勭悊鍏ㄥ睆鍒囨崲 + const toggleModal = useCallback(() => { + setIsModalOpen(prev => !prev); + }, []); + + // 澶勭悊妯℃�佺獥鍙g殑鍥捐〃 + useEffect(() => { + if (!isModalOpen || !modalChartRef.current) return; + + const initModalChart = async () => { + try { + const echarts = await import('echarts'); + + // 鍒濆鍖栨ā鎬佺獥鍙d腑鐨勫浘琛� + const modalChartInstance = echarts.init(modalChartRef.current); + + // 濡傛灉涓诲浘琛ㄥ凡缁忓垵濮嬪寲锛屽鐢ㄥ叾閰嶇疆 + if (chartInstanceRef.current) { + const option = chartInstanceRef.current.getOption(); + modalChartInstance.setOption(option); + } else { + // 濡傛灉涓诲浘琛ㄦ病鏈夊垵濮嬪寲锛屽皾璇曚粠浠g爜鍒濆鍖� + try { + const safeCode = code.replace(/window\.option/g, 'option'); + const safeFunc = new Function(` + "use strict"; + let option; + try { + ${safeCode} + return option; + } catch (e) { + console.error("妯℃�佸浘琛ㄤ唬鐮佹墽琛岄敊璇�:", e); + return null; + } + `); + + const chartOption = safeFunc(); + + if (chartOption) { + modalChartInstance.setOption(chartOption); + } else { + throw new Error("鏃犳硶鑾峰彇妯℃�佸浘琛ㄩ厤缃�"); + } + } catch (e) { + console.error('妯℃�佸浘琛ㄤ唬鐮佹墽琛岄敊璇�:', e); + + // 璁剧疆涓�涓粯璁ゅ浘琛� + modalChartInstance.setOption({ + title: { text: '鍥捐〃閰嶇疆閿欒' }, + xAxis: { type: 'category', data: ['閿欒'] }, + yAxis: { type: 'value' }, + series: [{ data: [0], type: 'bar' }] + }); + } + } + + // 娣诲姞绐楀彛澶у皬鍙樺寲鐩戝惉鍣� + const handleResize = () => modalChartInstance.resize(); + window.addEventListener('resize', handleResize); + + // 杩斿洖娓呯悊鍑芥暟 + return () => { + window.removeEventListener('resize', handleResize); + modalChartInstance.dispose(); + }; + } catch (e) { + console.error('妯℃�佸浘琛ㄥ垵濮嬪寲澶辫触:', e); + } + }; + + // 寤惰繜涓�鐐规墽琛岋紝纭繚DOM宸叉洿鏂板畬鎴� + const timer = setTimeout(initModalChart, 100); + return () => clearTimeout(timer); + }, [isModalOpen, code]); + // 绠�鍖栫殑娓叉煋閫昏緫锛岀‘淇濆鍣ㄦ湁鏄庣‘鐨勫昂瀵� return ( - <div className="relative my-4 echart-container"> - {error && ( - <div className="absolute top-0 left-0 right-0 bg-red-50 border border-red-200 text-red-600 px-3 py-2 rounded-md text-xs z-10"> - {error} - </div> - )} - + <div className="relative my-4"> {isLoading && ( - <div className="absolute inset-0 flex items-center justify-center bg-white bg-opacity-70 z-10"> + <div className="absolute inset-0 flex items-center justify-center bg-white/70 z-10"> <div className="flex flex-col items-center"> <div className="animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-blue-500 mb-3"></div> <p className="text-gray-500">鍥捐〃鍔犺浇涓�...</p> @@ -191,17 +252,65 @@ </div> )} - <div - ref={chartContainerRef} + {error && ( + <div className="absolute top-0 left-0 right-0 bg-red-50 border border-red-200 text-red-600 px-3 py-2 rounded-md text-xs z-10"> + {error} + </div> + )} + + {/* 鍥捐〃瀹瑰櫒 */} + <div + ref={chartContainerRef} className="w-full bg-white border border-gray-200 rounded-lg overflow-hidden chart-container" style={{ - height: '400px', - minHeight: '300px', // 纭繚鏈夋渶灏忛珮搴� - visibility: 'visible', // 纭繚瀹瑰櫒鍙 - position: 'relative' // 鍒涘缓BFC + height: '300px', + minHeight: '300px', + visibility: 'visible', + position: 'relative' }} - data-echarts-container="true" // 娣诲姞鏍囪瘑灞炴�� + data-echarts-container="true" /> + + {/* 鎵撳紑寮圭獥鎸夐挳 */} + <button + onClick={() => setIsModalOpen(true)} + className="absolute top-2 right-2 bg-white/90 hover:bg-white p-2 rounded-full shadow-md z-20 transition-all duration-300 hover:scale-110 cursor-pointer" + title="鍦ㄥ脊绐椾腑鏌ョ湅" + > + <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> + <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 3h6v6m0-6L14 9M9 21H3v-6m0 6l7-7" /> + </svg> + </button> + + {/* 寮瑰嚭寮忓璇濇 */} + {isModalOpen && ( + <div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/50 p-4"> + <div className="bg-white rounded-lg w-[90vw] max-w-6xl h-[80vh] relative flex flex-col"> + {/* 瀵硅瘽妗嗗ご閮� */} + <div className="flex justify-between items-center p-4 border-b"> + <h3 className="text-xl font-semibold text-gray-800">鏁版嵁鍙鍖栧浘琛�</h3> + <button + onClick={() => setIsModalOpen(false)} + className="p-1 rounded-full hover:bg-gray-100 cursor-pointer" + aria-label="鍏抽棴" + > + <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> + <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> + </svg> + </button> + </div> + + {/* 瀵硅瘽妗嗕富浣� - 鍥捐〃 */} + <div className="flex-1 overflow-hidden p-4"> + <div + ref={modalChartRef} + className="w-full h-full" + data-echarts-modal + ></div> + </div> + </div> + </div> + )} </div> ); } @@ -319,7 +428,7 @@ <button onClick={handleSend} disabled={isStreaming || !inputText.trim() || !isMessageComplete} - className="h-12 px-5 bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white font-medium rounded-xl transition-all duration-300 flex items-center gap-2 shadow-lg shadow-blue-500/20 hover:shadow-blue-500/30 hover:scale-[1.02] active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none disabled:shadow-none" + className="h-12 px-5 bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white font-medium rounded-xl transition-all duration-300 flex items-center gap-2 shadow-lg shadow-blue-500/20 hover:shadow-blue-500/30 hover:scale-[1.02] active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none disabled:shadow-none cursor-pointer" > <span>{isStreaming ? '鍥炲涓�...' : (isMessageComplete ? '鍙戦��' : '澶勭悊涓�...')}</span> <svg xmlns="http://www.w3.org/2000/svg" className={`h-4 w-4 transform rotate-45 ${isStreaming ? 'animate-pulse' : ''}`} viewBox="0 0 20 20" fill="currentColor"> diff --git a/src/app/globals.css b/src/app/globals.css index 7a4563e..611ff50 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -841,3 +841,32 @@ .no-flash-link { -webkit-tap-highlight-color: transparent; } + +/* 娑堟伅鐢熸垚鏃堕攣瀹氭粴鍔� */ +body.generating-message { + overflow: hidden !important; +} + +/* 婊氬姩閿佸畾閬僵 */ +.scroll-lock-overlay { + position: fixed; + inset: 0; + z-index: 50; + background: transparent; + pointer-events: all; + touch-action: none; +} + +/* 鍥捐〃鍏ㄥ睆妯″紡鏃剁殑鏍峰紡 */ +body:has(.echart-wrapper[data-echarts-container]) { + scroll-behavior: smooth; +} + +.echart-wrapper[data-echarts-container] { + transition: all 0.3s ease-in-out; +} + +/* 褰撳浜庡叏灞忔ā寮忔椂闃叉椤甸潰婊氬姩 */ +body:has(.echart-wrapper:has(+ .fullscreen-controls)) { + overflow: hidden; +} -- Gitblit v1.9.3