Просмотр исходного кода

✨ feat(FinancialDashboard): add interactive animation for all charts

- add click-triggered growth animation for AssetMetrics, DebtRatioMetrics, IncomeMetrics and ProfitMetrics components
- implement animationProgress state management to control animation effects
- add numerical value display on top of bars with fade-in effect
- remove Tooltip component and replace with direct value display
- add cursor-pointer style to chart containers for better UX

✨ feat(chart): enhance 3D bar components with animation support

- modify all custom bar components to accept animationProgress prop
- implement animated height calculation based on progress value
- adjust SVG coordinate transformation to support animation
- add value display on top of bars with position calculation
- optimize gradient and path rendering during animation

🔧 chore(chart): refactor common animation logic

- extract animation timing and progress calculation logic
- add requestAnimationFrame for smooth animation effects
- implement anti-repetitive click protection with isAnimating state
- standardize animation duration to 1.5 seconds across all charts
- add cursor pointer style to interactive chart areas
yourname 2 месяцев назад
Родитель
Сommit
05fdc48d33

+ 105 - 14
src/client/home/pages/FinancialDashboard/components/AssetMetrics.tsx

@@ -1,8 +1,9 @@
+import { useState } from 'react';
 import BarElement from './BarElement';
 import BaseContainer from './BaseContainer';
 import ModuleHeader from './ModuleHeader';
 import ModuleHeaderBackground from './ModuleHeaderBackground';
-import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
+import { BarChart, Bar, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from 'recharts';
 
 interface AssetMetricsProps {
   className?: string;
@@ -25,16 +26,27 @@ const chartData = assetData.map(item => ({
 
 // 自定义 3D 柱状图组件 - 资产总额(高版本)
 const AssetTotalBar = (props: any) => {
-  const { x, y, width, height } = props;
+  const { x, y, width, height, animationProgress, payload } = props;
+
+  // 根据动画进度计算实际高度
+  const animatedHeight = height * animationProgress;
+  const animatedY = y + (height - animatedHeight);
 
   // 将Recharts的坐标系转换为SVG的坐标系
   const svgWidth = 36;
   const svgHeight = 225;
   const scaleX = width / svgWidth;
-  const scaleY = height / svgHeight;
+  const scaleY = animatedHeight / svgHeight;
+
+  // 数值显示位置(在柱子顶部上方)
+  const valueX = x + width / 2;
+  const valueY = animatedY - 8;
 
   return (
-    <g transform={`translate(${x},${y}) scale(${scaleX},${scaleY})`}>
+    <>
+      <g
+        transform={`translate(${x},${animatedY}) scale(${scaleX},${scaleY})`}
+      >
       <path d="M35.25 222H0.25L0.25 3L35.25 3L35.25 222Z" fill="url(#paint0_linear_1977_58501)"/>
       <path d="M35.25 222H0.25L0.25 3L35.25 3L35.25 222Z" fill="url(#paint1_linear_1977_58501)" fillOpacity="0.6"/>
       <ellipse cx="17.75" cy="221.5" rx="17.5" ry="2.5" fill="url(#paint2_linear_1977_58501)"/>
@@ -74,22 +86,47 @@ const AssetTotalBar = (props: any) => {
           <stop offset="1" stopColor="white"/>
         </linearGradient>
       </defs>
-    </g>
+      </g>
+
+      {/* 数值标签 */}
+      <text
+        x={valueX}
+        y={valueY}
+        textAnchor="middle"
+        fill="white"
+        fontSize="12"
+        fontWeight="bold"
+        opacity={animationProgress > 0.8 ? 1 : 0} // 动画后期才显示
+      >
+        {payload['资产总额']}
+      </text>
+    </>
   );
 };
 
 // 自定义 3D 柱状图组件 - 资产净额(高版本)
 const AssetNetBar = (props: any) => {
-  const { x, y, width, height } = props;
+  const { x, y, width, height, animationProgress, payload } = props;
+
+  // 根据动画进度计算实际高度
+  const animatedHeight = height * animationProgress;
+  const animatedY = y + (height - animatedHeight);
 
   // 将Recharts的坐标系转换为SVG的坐标系
   const svgWidth = 36;
   const svgHeight = 135;
   const scaleX = width / svgWidth;
-  const scaleY = height / svgHeight;
+  const scaleY = animatedHeight / svgHeight;
+
+  // 数值显示位置(在柱子顶部上方)
+  const valueX = x + width / 2;
+  const valueY = animatedY - 8;
 
   return (
-    <g transform={`translate(${x},${y}) scale(${scaleX},${scaleY})`}>
+    <>
+      <g
+        transform={`translate(${x},${animatedY}) scale(${scaleX},${scaleY})`}
+      >
       <rect width="35" height="132" transform="translate(0.25 3)" fill="url(#paint0_linear_1977_58512)"/>
       <path d="M35.25 7L0.25 7L0.25 3L35.25 3V7Z" fill="url(#paint1_linear_1977_58512)"/>
       <path d="M35.25 7L0.25 7L0.25 3L35.25 3V7Z" fill="url(#paint2_linear_1977_58512)" fillOpacity="0.8"/>
@@ -134,11 +171,54 @@ const AssetNetBar = (props: any) => {
           <stop offset="1" stopColor="white"/>
         </linearGradient>
       </defs>
-    </g>
+      </g>
+
+      {/* 数值标签 */}
+      <text
+        x={valueX}
+        y={valueY}
+        textAnchor="middle"
+        fill="white"
+        fontSize="12"
+        fontWeight="bold"
+        opacity={animationProgress > 0.8 ? 1 : 0} // 动画后期才显示
+      >
+        {payload['资产净额']}
+      </text>
+    </>
   );
 };
 
 export function AssetMetrics({ className }: AssetMetricsProps) {
+  const [isAnimating, setIsAnimating] = useState(false);
+  const [animationProgress, setAnimationProgress] = useState(1); // 1表示完全显示
+
+  const handleChartClick = () => {
+    if (isAnimating) return; // 防止重复点击
+
+    setIsAnimating(true);
+    setAnimationProgress(0); // 从0开始生长
+
+    // 动画持续时间1.5秒
+    const duration = 1500;
+    const startTime = Date.now();
+
+    const animate = () => {
+      const elapsed = Date.now() - startTime;
+      const progress = Math.min(elapsed / duration, 1);
+
+      setAnimationProgress(progress);
+
+      if (progress < 1) {
+        requestAnimationFrame(animate);
+      } else {
+        setIsAnimating(false);
+      }
+    };
+
+    requestAnimationFrame(animate);
+  };
+
   return (
     <div className={`h-[480px] overflow-clip relative shrink-0 w-full ${className || ''}`}>
       {/* 底部容器 - 使用BaseContainer作为背景 */}
@@ -180,7 +260,7 @@ export function AssetMetrics({ className }: AssetMetricsProps) {
           </p>
 
           {/* Recharts图表区域 */}
-          <div className="w-full h-[300px] mt-4">
+          <div className="w-full h-[300px] mt-4 cursor-pointer" onClick={handleChartClick}>
             <ResponsiveContainer width="100%" height="100%">
               <BarChart
                 data={chartData}
@@ -203,22 +283,32 @@ export function AssetMetrics({ className }: AssetMetricsProps) {
                   tickLine={false}
                   tick={{ fill: 'rgba(255,255,255,0.8)', fontSize: 12 }}
                 />
-                <Tooltip
+                {/* <Tooltip
                   contentStyle={{
                     backgroundColor: 'rgba(0,0,0,0.8)',
                     border: '1px solid rgba(255,255,255,0.2)',
                     color: 'white'
                   }}
                   formatter={(value) => [`${value} 亿元`, '']}
-                />
+                /> */}
                 <Bar
                   dataKey="资产总额"
-                  shape={<AssetTotalBar />}
+                  shape={(props: any) => (
+                    <AssetTotalBar
+                      {...props}
+                      animationProgress={animationProgress}
+                    />
+                  )}
                   barSize={45}
                 />
                 <Bar
                   dataKey="资产净额"
-                  shape={<AssetNetBar />}
+                  shape={(props: any) => (
+                    <AssetNetBar
+                      {...props}
+                      animationProgress={animationProgress}
+                    />
+                  )}
                   barSize={45}
                 />
               </BarChart>
@@ -229,6 +319,7 @@ export function AssetMetrics({ className }: AssetMetricsProps) {
       {/* 模块标题 */}
       <ModuleHeaderBackground className="absolute h-[38px] left-0 top-0 w-[594.001px]" />
       <ModuleHeader className="absolute h-[38px] left-0 overflow-clip top-0 w-[594px]" title="资产负债率" />
+
     </div>
   );
 }

+ 121 - 19
src/client/home/pages/FinancialDashboard/components/DebtRatioMetrics.tsx

@@ -1,7 +1,8 @@
+import { useState } from 'react';
 import BarElement from './BarElement';
 import BaseContainer from './BaseContainer';
 import ModuleHeader from './ModuleHeader';
-import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
+import { BarChart, Bar, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from 'recharts';
 
 interface DebtRatioMetricsProps {
   className?: string;
@@ -23,8 +24,108 @@ const chartData = debtRatioData.map(item => ({
   '剩余部分': 98 - item.ratio // 蓝色柱子,剩余部分(减去分隔线高度)
 }));
 
+// 自定义资产负债率柱状图组件
+const DebtRatioBar = (props: any) => {
+  const { x, y, width, height, animationProgress, payload } = props;
+
+  // 根据动画进度计算实际高度
+  const animatedHeight = height * animationProgress;
+  const animatedY = y + (height - animatedHeight);
+
+  return (
+    <>
+      <rect
+        x={x}
+        y={animatedY}
+        width={width}
+        height={animatedHeight}
+        fill="#46BDBD"
+      />
+      {/* 数值标签 */}
+      <text
+        x={x + width / 2}
+        y={animatedY - 8}
+        textAnchor="middle"
+        fill="white"
+        fontSize="12"
+        fontWeight="bold"
+        opacity={animationProgress > 0.8 ? 1 : 0}
+      >
+        {payload['资产负债率']}
+      </text>
+    </>
+  );
+};
+
+// 自定义分隔线柱状图组件
+const SeparatorBar = (props: any) => {
+  const { x, y, width, height, animationProgress } = props;
+
+  // 根据动画进度计算实际高度
+  const animatedHeight = height * animationProgress;
+  const animatedY = y + (height - animatedHeight);
+
+  return (
+    <rect
+      x={x}
+      y={animatedY}
+      width={width}
+      height={animatedHeight}
+      fill="white"
+    />
+  );
+};
+
+// 自定义剩余部分柱状图组件
+const RemainingBar = (props: any) => {
+  const { x, y, width, height, animationProgress } = props;
+
+  // 根据动画进度计算实际高度
+  const animatedHeight = height * animationProgress;
+  const animatedY = y + (height - animatedHeight);
+
+  return (
+    <rect
+      x={x}
+      y={animatedY}
+      width={width}
+      height={animatedHeight}
+      fill="#297AD8"
+    />
+  );
+};
+
 
 export function DebtRatioMetrics({ className }: DebtRatioMetricsProps) {
+  const [isAnimating, setIsAnimating] = useState(false);
+  const [animationProgress, setAnimationProgress] = useState(1); // 1表示完全显示
+
+  const handleChartClick = () => {
+    if (isAnimating) return; // 防止重复点击
+
+    setIsAnimating(true);
+    setAnimationProgress(0); // 从0开始生长
+
+    // 动画持续时间1.5秒
+    const duration = 1500;
+    const startTime = Date.now();
+
+    const animate = () => {
+      const elapsed = Date.now() - startTime;
+      const progress = Math.min(elapsed / duration, 1);
+
+      setAnimationProgress(progress);
+
+      if (progress < 1) {
+        requestAnimationFrame(animate);
+      } else {
+        setIsAnimating(false);
+      }
+    };
+
+    requestAnimationFrame(animate);
+  };
+
   return (
     <div className={`h-[480px] overflow-clip relative shrink-0 w-full ${className || ''}`}>
       {/* 底部容器 - 使用BaseContainer作为背景 */}
@@ -55,7 +156,7 @@ export function DebtRatioMetrics({ className }: DebtRatioMetricsProps) {
           </p>
 
           {/* Recharts图表区域 */}
-          <div className="w-full h-[300px] mt-4">
+          <div className="w-full h-[300px] mt-4 cursor-pointer" onClick={handleChartClick}>
             <ResponsiveContainer width="100%" height="100%">
               <BarChart
                 data={chartData}
@@ -79,35 +180,36 @@ export function DebtRatioMetrics({ className }: DebtRatioMetricsProps) {
                   tick={{ fill: 'rgba(255,255,255,0.8)', fontSize: 12 }}
                   domain={[0, 100]}
                 />
-                <Tooltip
-                  contentStyle={{
-                    backgroundColor: 'rgba(0,0,0,0.8)',
-                    border: '1px solid rgba(255,255,255,0.2)',
-                    color: 'white'
-                  }}
-                  formatter={(value, name) => {
-                    // 只显示资产负债率的tooltip,不显示剩余部分和分隔线
-                    if (name === '资产负债率') {
-                      return [`${value} %`, '资产负债率'];
-                    }
-                    return null;
-                  }}
-                />
                 <Bar
                   dataKey="资产负债率"
-                  fill="#46BDBD"
+                  shape={(props: any) => (
+                    <DebtRatioBar
+                      {...props}
+                      animationProgress={animationProgress}
+                    />
+                  )}
                   barSize={60}
                   stackId="debtRatio"
                 />
                 <Bar
                   dataKey="分隔线"
-                  fill="white"
+                  shape={(props: any) => (
+                    <SeparatorBar
+                      {...props}
+                      animationProgress={animationProgress}
+                    />
+                  )}
                   barSize={60}
                   stackId="debtRatio"
                 />
                 <Bar
                   dataKey="剩余部分"
-                  fill="#297AD8"
+                  shape={(props: any) => (
+                    <RemainingBar
+                      {...props}
+                      animationProgress={animationProgress}
+                    />
+                  )}
                   barSize={60}
                   stackId="debtRatio"
                 />

+ 65 - 15
src/client/home/pages/FinancialDashboard/components/IncomeMetrics.tsx

@@ -1,7 +1,8 @@
+import { useState } from 'react';
 import BarElement from './BarElement';
 import BaseContainer from './BaseContainer';
 import ModuleHeader from './ModuleHeader';
-import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
+import { BarChart, Bar, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from 'recharts';
 
 interface IncomeMetricsProps {
   className?: string;
@@ -23,16 +24,25 @@ const chartData = incomeData.map(item => ({
 
 // 自定义 3D 柱状图组件 - 基于提供的SVG设计
 const ThreeDBar = (props: any) => {
-  const { x, y, width, height } = props;
+  const { x, y, width, height, animationProgress, payload } = props;
+
+  // 根据动画进度计算实际高度
+  const animatedHeight = height * animationProgress;
+  const animatedY = y + (height - animatedHeight);
 
   // 将Recharts的坐标系转换为SVG的坐标系
   const svgWidth = 80;
   const svgHeight = 254;
   const scaleX = width / svgWidth;
-  const scaleY = height / svgHeight;
+  const scaleY = animatedHeight / svgHeight;
+
+  // 数值显示位置(在柱子顶部上方)
+  const valueX = x + width / 2;
+  const valueY = animatedY - 8;
 
   return (
-    <g transform={`translate(${x},${y}) scale(${scaleX},${scaleY})`}>
+    <>
+      <g transform={`translate(${x},${animatedY}) scale(${scaleX},${scaleY})`}>
       <path d="M40.5 252H80L80 3L40.5 3L40.5 252Z" fill="url(#paint0_linear_1977_58682)"/>
       <rect width="1" height="249" transform="matrix(-1 0 0 1 40.5 3)" fill="url(#paint1_linear_1977_58682)"/>
       <path d="M0 252H39.5L39.5 3L0 3L0 252Z" fill="url(#paint2_linear_1977_58682)"/>
@@ -70,11 +80,54 @@ const ThreeDBar = (props: any) => {
           <stop offset="1" stopColor="white" stopOpacity="0.3"/>
         </linearGradient>
       </defs>
-    </g>
+      </g>
+
+      {/* 数值标签 */}
+      <text
+        x={valueX}
+        y={valueY}
+        textAnchor="middle"
+        fill="white"
+        fontSize="12"
+        fontWeight="bold"
+        opacity={animationProgress > 0.8 ? 1 : 0} // 动画后期才显示
+      >
+        {payload['收入']}
+      </text>
+    </>
   );
 };
 
 export function IncomeMetrics({ className }: IncomeMetricsProps) {
+  const [isAnimating, setIsAnimating] = useState(false);
+  const [animationProgress, setAnimationProgress] = useState(1); // 1表示完全显示
+
+  const handleChartClick = () => {
+    if (isAnimating) return; // 防止重复点击
+
+    setIsAnimating(true);
+    setAnimationProgress(0); // 从0开始生长
+
+    // 动画持续时间1.5秒
+    const duration = 1500;
+    const startTime = Date.now();
+
+    const animate = () => {
+      const elapsed = Date.now() - startTime;
+      const progress = Math.min(elapsed / duration, 1);
+
+      setAnimationProgress(progress);
+
+      if (progress < 1) {
+        requestAnimationFrame(animate);
+      } else {
+        setIsAnimating(false);
+      }
+    };
+
+    requestAnimationFrame(animate);
+  };
+
   return (
     <div className={`h-[480px] overflow-clip relative shrink-0 w-full ${className || ''}`}>
       {/* 底部容器 - 使用BaseContainer作为背景 */}
@@ -105,7 +158,7 @@ export function IncomeMetrics({ className }: IncomeMetricsProps) {
           </p>
 
           {/* Recharts图表区域 */}
-          <div className="w-full h-[300px] mt-4">
+          <div className="w-full h-[300px] mt-4 cursor-pointer" onClick={handleChartClick}>
             <ResponsiveContainer width="100%" height="100%">
               <BarChart
                 data={chartData}
@@ -128,17 +181,14 @@ export function IncomeMetrics({ className }: IncomeMetricsProps) {
                   tickLine={false}
                   tick={{ fill: 'rgba(255,255,255,0.8)', fontSize: 12 }}
                 />
-                <Tooltip
-                  contentStyle={{
-                    backgroundColor: 'rgba(0,0,0,0.8)',
-                    border: '1px solid rgba(255,255,255,0.2)',
-                    color: 'white'
-                  }}
-                  formatter={(value) => [`${value} 亿元`, '']}
-                />
                 <Bar
                   dataKey="收入"
-                  shape={<ThreeDBar />}
+                  shape={(props: any) => (
+                    <ThreeDBar
+                      {...props}
+                      animationProgress={animationProgress}
+                    />
+                  )}
                   barSize={60}
                 />
               </BarChart>

+ 98 - 20
src/client/home/pages/FinancialDashboard/components/ProfitMetrics.tsx

@@ -1,8 +1,9 @@
+import { useState } from 'react';
 import BarElement from './BarElement';
 import BaseContainer from './BaseContainer';
 import ModuleHeader from './ModuleHeader';
 import ModuleHeaderBackground from './ModuleHeaderBackground';
-import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
+import { BarChart, Bar, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from 'recharts';
 
 interface ProfitMetricsProps {
   className?: string;
@@ -25,16 +26,25 @@ const chartData = profitData.map(item => ({
 
 // 自定义 3D 柱状图组件 - 利润总额
 const ProfitTotalBar = (props: any) => {
-  const { x, y, width, height } = props;
+  const { x, y, width, height, animationProgress, payload } = props;
+
+  // 根据动画进度计算实际高度
+  const animatedHeight = height * animationProgress;
+  const animatedY = y + (height - animatedHeight);
 
   // 将Recharts的坐标系转换为SVG的坐标系
   const svgWidth = 50;
   const svgHeight = 165;
   const scaleX = width / svgWidth;
-  const scaleY = height / svgHeight;
+  const scaleY = animatedHeight / svgHeight;
+
+  // 数值显示位置(在柱子顶部上方)
+  const valueX = x + width / 2;
+  const valueY = animatedY - 8;
 
   return (
-    <g transform={`translate(${x},${y}) scale(${scaleX},${scaleY})`}>
+    <>
+      <g transform={`translate(${x},${animatedY}) scale(${scaleX},${scaleY})`}>
       <path d="M24.5 163H0L0 3L24.5 3L24.5 163Z" fill="url(#paint0_linear_1977_58918)"/>
       <rect x="24.5" y="3" width="1" height="160" fill="url(#paint1_linear_1977_58918)"/>
       <path d="M50 163H25.5L25.5 3L50 3L50 163Z" fill="url(#paint2_linear_1977_58918)"/>
@@ -72,22 +82,45 @@ const ProfitTotalBar = (props: any) => {
           <stop offset="1" stopColor="white" stopOpacity="0.3"/>
         </linearGradient>
       </defs>
-    </g>
+      </g>
+
+      {/* 数值标签 */}
+      <text
+        x={valueX}
+        y={valueY}
+        textAnchor="middle"
+        fill="white"
+        fontSize="12"
+        fontWeight="bold"
+        opacity={animationProgress > 0.8 ? 1 : 0} // 动画后期才显示
+      >
+        {payload['利润总额']}
+      </text>
+    </>
   );
 };
 
 // 自定义 3D 柱状图组件 - 净利润
 const ProfitNetBar = (props: any) => {
-  const { x, y, width, height } = props;
+  const { x, y, width, height, animationProgress, payload } = props;
+
+  // 根据动画进度计算实际高度
+  const animatedHeight = height * animationProgress;
+  const animatedY = y + (height - animatedHeight);
 
   // 将Recharts的坐标系转换为SVG的坐标系
   const svgWidth = 50;
   const svgHeight = 134;
   const scaleX = width / svgWidth;
-  const scaleY = height / svgHeight;
+  const scaleY = animatedHeight / svgHeight;
+
+  // 数值显示位置(在柱子顶部上方)
+  const valueX = x + width / 2;
+  const valueY = animatedY - 8;
 
   return (
-    <g transform={`translate(${x},${y}) scale(${scaleX},${scaleY})`}>
+    <>
+      <g transform={`translate(${x},${animatedY}) scale(${scaleX},${scaleY})`}>
       <path d="M24.5 132H0L0 3L24.5 3L24.5 132Z" fill="url(#paint0_linear_1977_58932)"/>
       <rect x="24.5" y="3" width="1" height="129" fill="url(#paint1_linear_1977_58932)"/>
       <path d="M50 132H25.5L25.5 3L50 3V132Z" fill="url(#paint2_linear_1977_58932)"/>
@@ -125,11 +158,54 @@ const ProfitNetBar = (props: any) => {
           <stop offset="1" stopColor="white" stopOpacity="0.3"/>
         </linearGradient>
       </defs>
-    </g>
+      </g>
+
+      {/* 数值标签 */}
+      <text
+        x={valueX}
+        y={valueY}
+        textAnchor="middle"
+        fill="white"
+        fontSize="12"
+        fontWeight="bold"
+        opacity={animationProgress > 0.8 ? 1 : 0} // 动画后期才显示
+      >
+        {payload['净利润']}
+      </text>
+    </>
   );
 };
 
 export function ProfitMetrics({ className }: ProfitMetricsProps) {
+  const [isAnimating, setIsAnimating] = useState(false);
+  const [animationProgress, setAnimationProgress] = useState(1); // 1表示完全显示
+
+  const handleChartClick = () => {
+    if (isAnimating) return; // 防止重复点击
+
+    setIsAnimating(true);
+    setAnimationProgress(0); // 从0开始生长
+
+    // 动画持续时间1.5秒
+    const duration = 1500;
+    const startTime = Date.now();
+
+    const animate = () => {
+      const elapsed = Date.now() - startTime;
+      const progress = Math.min(elapsed / duration, 1);
+
+      setAnimationProgress(progress);
+
+      if (progress < 1) {
+        requestAnimationFrame(animate);
+      } else {
+        setIsAnimating(false);
+      }
+    };
+
+    requestAnimationFrame(animate);
+  };
+
   return (
     <div className={`h-[480px] overflow-clip relative shrink-0 w-full ${className || ''}`}>
       {/* 底部容器 - 使用BaseContainer作为背景 */}
@@ -170,7 +246,7 @@ export function ProfitMetrics({ className }: ProfitMetricsProps) {
           </p>
 
           {/* Recharts图表区域 */}
-          <div className="w-full h-[300px] mt-4">
+          <div className="w-full h-[300px] mt-4 cursor-pointer" onClick={handleChartClick}>
             <ResponsiveContainer width="100%" height="100%">
               <BarChart
                 data={chartData}
@@ -193,22 +269,24 @@ export function ProfitMetrics({ className }: ProfitMetricsProps) {
                   tickLine={false}
                   tick={{ fill: 'rgba(255,255,255,0.8)', fontSize: 12 }}
                 />
-                <Tooltip
-                  contentStyle={{
-                    backgroundColor: 'rgba(0,0,0,0.8)',
-                    border: '1px solid rgba(255,255,255,0.2)',
-                    color: 'white'
-                  }}
-                  formatter={(value) => [`${value} 亿元`, '']}
-                />
                 <Bar
                   dataKey="利润总额"
-                  shape={<ProfitTotalBar />}
+                  shape={(props: any) => (
+                    <ProfitTotalBar
+                      {...props}
+                      animationProgress={animationProgress}
+                    />
+                  )}
                   barSize={60}
                 />
                 <Bar
                   dataKey="净利润"
-                  shape={<ProfitNetBar />}
+                  shape={(props: any) => (
+                    <ProfitNetBar
+                      {...props}
+                      animationProgress={animationProgress}
+                    />
+                  )}
                   barSize={60}
                 />
               </BarChart>