Fundamental_Analysis/frontend/src/components/TradingViewWidget.tsx
Lv, Qi d28f3c5266 feat: update analysis workflow and fix LLM client connection issues
- Enhance LlmClient to handle malformed URLs and HTML error responses
- Improve logging in report-generator-service worker
- Update frontend API routes and hooks for analysis
- Update various service configurations and persistence logic
2025-11-19 17:30:52 +08:00

149 lines
4.1 KiB
TypeScript

'use client';
import { useEffect, useRef } from 'react';
interface TradingViewWidgetProps {
symbol: string;
market?: string;
height?: number;
width?: string;
}
declare global {
interface Window {
TradingView: any;
}
}
export function TradingViewWidget({
symbol,
market = 'china',
height = 400,
width = '100%'
}: TradingViewWidgetProps) {
const containerRef = useRef<HTMLDivElement>(null);
// 将中国股票代码转换为TradingView格式
const getTradingViewSymbol = (symbol: string, market: string) => {
if (market === 'china' || market === 'cn') {
// 处理中国股票代码
if (symbol.includes('.')) {
const [code, exchange] = symbol.split('.');
if (exchange === 'SH') {
return `SSE:${code}`;
} else if (exchange === 'SZ') {
return `SZSE:${code}`;
}
}
// 如果没有后缀,尝试推断
const onlyDigits = symbol.replace(/\D/g, '');
if (onlyDigits.length === 6) {
const first = onlyDigits[0];
if (first === '6') {
return `SSE:${onlyDigits}`;
} else if (first === '0' || first === '3') {
return `SZSE:${onlyDigits}`;
}
}
return symbol;
}
return symbol;
};
useEffect(() => {
if (typeof window === 'undefined') return;
if (!symbol) return;
const tradingViewSymbol = getTradingViewSymbol(symbol, market);
const script = document.createElement('script');
script.src = 'https://s3.tradingview.com/external-embedding/embed-widget-advanced-chart.js';
script.async = true;
script.innerHTML = JSON.stringify({
autosize: true,
symbol: tradingViewSymbol,
interval: 'D',
timezone: 'Asia/Shanghai',
theme: 'light',
style: '1',
locale: 'zh_CN',
toolbar_bg: '#f1f3f6',
enable_publishing: false,
hide_top_toolbar: false,
hide_legend: false,
save_image: false,
container_id: `tradingview_${symbol}`,
studies: [],
show_popup_button: false,
no_referrer_id: true,
referrer_id: 'fundamental-analysis',
// 强制启用对数坐标
logarithmic: true,
disabled_features: [
'use_localstorage_for_settings',
'volume_force_overlay',
'create_volume_indicator_by_default'
],
enabled_features: [
'side_toolbar_in_fullscreen_mode',
'header_in_fullscreen_mode'
],
overrides: {
'paneProperties.background': '#ffffff',
'paneProperties.vertGridProperties.color': '#e1e3e6',
'paneProperties.horzGridProperties.color': '#e1e3e6',
'symbolWatermarkProperties.transparency': 90,
'scalesProperties.textColor': '#333333',
// 对数坐标设置
'scalesProperties.logarithmic': true,
'rightPriceScale.mode': 1,
'leftPriceScale.mode': 1,
'paneProperties.priceScaleProperties.log': true,
'paneProperties.priceScaleProperties.mode': 1
},
// 强制启用对数坐标
studies_overrides: {
'volume.volume.color.0': '#00bcd4',
'volume.volume.color.1': '#ff9800',
'volume.volume.transparency': 70
}
});
const container = containerRef.current;
if (container) {
// 避免重复挂载与 Next 热更新多次执行导致的报错
container.innerHTML = '';
// 延迟到下一帧,确保容器已插入并可获取 iframe.contentWindow
requestAnimationFrame(() => {
try {
if (container.isConnected) {
container.appendChild(script);
}
} catch (e) {
// 忽略偶发性 contentWindow 不可用的报错
console.warn('TradingView widget mount error:', e);
}
});
}
return () => {
const c = containerRef.current;
if (c) {
try {
c.innerHTML = '';
} catch {}
}
};
}, [symbol, market]);
return (
<div className="w-full">
<div
ref={containerRef}
id={`tradingview_${symbol}`}
style={{ height: `${height}px`, width }}
className="border rounded-lg overflow-hidden"
/>
</div>
);
}