From aaa91868a347768cde7d273961a12dd33be04ca9 Mon Sep 17 00:00:00 2001 From: hzm <934585316@qq.com> Date: Tue, 24 Feb 2026 10:14:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=8A=98=E7=BA=BF?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E7=AD=BE=E7=BB=98=E5=88=B6=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=9C=86=E8=A7=92=E7=9F=A9=E5=BD=A2=E8=83=8C=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/FundTrendChart.jsx | 89 +++++++++++++++++++------------ package-lock.json | 4 +- 2 files changed, 57 insertions(+), 36 deletions(-) diff --git a/app/components/FundTrendChart.jsx b/app/components/FundTrendChart.jsx index aa96343..f8edc06 100644 --- a/app/components/FundTrendChart.jsx +++ b/app/components/FundTrendChart.jsx @@ -39,7 +39,7 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans useEffect(() => { // If collapsed, don't fetch data unless we have no data yet if (!isExpanded && data.length > 0) return; - + let active = true; setLoading(true); setError(null); @@ -229,41 +229,69 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans const ctx = chart.ctx; const datasets = chart.data.datasets; const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--primary').trim() || '#22d3ee'; - - // Helper function to draw point label + + // 绘制圆角矩形(兼容无 roundRect 的环境) + const drawRoundRect = (left, top, w, h, r) => { + const rad = Math.min(r, w / 2, h / 2); + ctx.beginPath(); + ctx.moveTo(left + rad, top); + ctx.lineTo(left + w - rad, top); + ctx.quadraticCurveTo(left + w, top, left + w, top + rad); + ctx.lineTo(left + w, top + h - rad); + ctx.quadraticCurveTo(left + w, top + h, left + w - rad, top + h); + ctx.lineTo(left + rad, top + h); + ctx.quadraticCurveTo(left, top + h, left, top + h - rad); + ctx.lineTo(left, top + rad); + ctx.quadraticCurveTo(left, top, left + rad, top); + ctx.closePath(); + }; + const drawPointLabel = (datasetIndex, index, text, bgColor, textColor = '#ffffff', yOffset = 0) => { const meta = chart.getDatasetMeta(datasetIndex); if (!meta.data[index]) return; const element = meta.data[index]; - // Check if element is visible/not skipped if (element.skip) return; - + const x = element.x; const y = element.y + yOffset; - + const paddingH = 10; + const paddingV = 6; + const radius = 8; + ctx.save(); ctx.font = 'bold 11px sans-serif'; - const labelWidth = ctx.measureText(text).width + 12; - - // Draw label above the point + const textW = ctx.measureText(text).width; + const w = textW + paddingH * 2; + const h = 18; + const left = x - w / 2; + const top = y - 24; + + drawRoundRect(left, top, w, h, radius); ctx.globalAlpha = 0.7; ctx.fillStyle = bgColor; - ctx.fillRect(x - labelWidth/2, y - 24, labelWidth, 18); - + ctx.fill(); + ctx.globalAlpha = 0.7; ctx.fillStyle = textColor; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; - ctx.fillText(text, x, y - 15); + ctx.fillText(text, x, top + h / 2); ctx.restore(); }; - // 1. Draw default labels for first buy and sell points + // Resolve active elements (hover/focus) first — used to decide whether to show default labels + let activeElements = []; + if (chart.tooltip?._active?.length) { + activeElements = chart.tooltip._active; + } else { + activeElements = chart.getActiveElements(); + } + + // 1. Draw default labels for first buy and sell points only when NOT focused/hovering // Index 1 is Buy, Index 2 is Sell - if (datasets[1] && datasets[1].data) { + if (!activeElements?.length && datasets[1] && datasets[1].data) { const firstBuyIndex = datasets[1].data.findIndex(v => v !== null && v !== undefined); if (firstBuyIndex !== -1) { - // Check collision with Sell let sellIndex = -1; if (datasets[2] && datasets[2].data) { sellIndex = datasets[2].data.findIndex(v => v !== null && v !== undefined); @@ -272,7 +300,7 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans drawPointLabel(1, firstBuyIndex, '买入', primaryColor, '#ffffff', isCollision ? -20 : 0); } } - if (datasets[2] && datasets[2].data) { + if (!activeElements?.length && datasets[2] && datasets[2].data) { const firstSellIndex = datasets[2].data.findIndex(v => v !== null && v !== undefined); if (firstSellIndex !== -1) { drawPointLabel(2, firstSellIndex, '卖出', '#f87171'); @@ -280,13 +308,6 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans } // 2. Handle active elements (hover crosshair) - let activeElements = []; - if (chart.tooltip?._active?.length) { - activeElements = chart.tooltip._active; - } else { - activeElements = chart.getActiveElements(); - } - if (activeElements && activeElements.length) { const activePoint = activeElements[0]; const x = activePoint.element.x; @@ -305,11 +326,11 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans // Draw vertical line ctx.moveTo(x, topY); ctx.lineTo(x, bottomY); - + // Draw horizontal line (based on first point - usually the main line) ctx.moveTo(leftX, y); ctx.lineTo(rightX, y); - + ctx.stroke(); // Draw labels @@ -320,7 +341,7 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans // Draw Axis Labels based on the first point (main line) const datasetIndex = activePoint.datasetIndex; const index = activePoint.index; - + const labels = chart.data.labels; if (labels && datasets && datasets[datasetIndex] && datasets[datasetIndex].data) { @@ -360,13 +381,13 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans // Determine background color based on dataset index // 1 = Buy (主题色), 2 = Sell (与折线图红色一致) const bgColor = dsIndex === 1 ? primaryColor : '#f87171'; - + // If collision, offset Buy label upwards let yOffset = 0; if (isCollision && dsIndex === 1) { yOffset = -20; } - + drawPointLabel(dsIndex, element.index, label, bgColor, '#ffffff', yOffset); } }); @@ -375,10 +396,10 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans } } }], []); // 移除 data 依赖,因为我们直接从 chart 实例读取数据 - + return (