|
@@ -1,8 +1,9 @@
|
|
|
|
|
+import { useState } from 'react';
|
|
|
import BarElement from './BarElement';
|
|
import BarElement from './BarElement';
|
|
|
import BaseContainer from './BaseContainer';
|
|
import BaseContainer from './BaseContainer';
|
|
|
import ModuleHeader from './ModuleHeader';
|
|
import ModuleHeader from './ModuleHeader';
|
|
|
import ModuleHeaderBackground from './ModuleHeaderBackground';
|
|
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 {
|
|
interface AssetMetricsProps {
|
|
|
className?: string;
|
|
className?: string;
|
|
@@ -25,16 +26,27 @@ const chartData = assetData.map(item => ({
|
|
|
|
|
|
|
|
// 自定义 3D 柱状图组件 - 资产总额(高版本)
|
|
// 自定义 3D 柱状图组件 - 资产总额(高版本)
|
|
|
const AssetTotalBar = (props: any) => {
|
|
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的坐标系
|
|
// 将Recharts的坐标系转换为SVG的坐标系
|
|
|
const svgWidth = 36;
|
|
const svgWidth = 36;
|
|
|
const svgHeight = 225;
|
|
const svgHeight = 225;
|
|
|
const scaleX = width / svgWidth;
|
|
const scaleX = width / svgWidth;
|
|
|
- const scaleY = height / svgHeight;
|
|
|
|
|
|
|
+ const scaleY = animatedHeight / svgHeight;
|
|
|
|
|
+
|
|
|
|
|
+ // 数值显示位置(在柱子顶部上方)
|
|
|
|
|
+ const valueX = x + width / 2;
|
|
|
|
|
+ const valueY = animatedY - 8;
|
|
|
|
|
|
|
|
return (
|
|
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(#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"/>
|
|
<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)"/>
|
|
<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"/>
|
|
<stop offset="1" stopColor="white"/>
|
|
|
</linearGradient>
|
|
</linearGradient>
|
|
|
</defs>
|
|
</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 柱状图组件 - 资产净额(高版本)
|
|
// 自定义 3D 柱状图组件 - 资产净额(高版本)
|
|
|
const AssetNetBar = (props: any) => {
|
|
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的坐标系
|
|
// 将Recharts的坐标系转换为SVG的坐标系
|
|
|
const svgWidth = 36;
|
|
const svgWidth = 36;
|
|
|
const svgHeight = 135;
|
|
const svgHeight = 135;
|
|
|
const scaleX = width / svgWidth;
|
|
const scaleX = width / svgWidth;
|
|
|
- const scaleY = height / svgHeight;
|
|
|
|
|
|
|
+ const scaleY = animatedHeight / svgHeight;
|
|
|
|
|
+
|
|
|
|
|
+ // 数值显示位置(在柱子顶部上方)
|
|
|
|
|
+ const valueX = x + width / 2;
|
|
|
|
|
+ const valueY = animatedY - 8;
|
|
|
|
|
|
|
|
return (
|
|
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)"/>
|
|
<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(#paint1_linear_1977_58512)"/>
|
|
|
<path d="M35.25 7L0.25 7L0.25 3L35.25 3V7Z" fill="url(#paint2_linear_1977_58512)" fillOpacity="0.8"/>
|
|
<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"/>
|
|
<stop offset="1" stopColor="white"/>
|
|
|
</linearGradient>
|
|
</linearGradient>
|
|
|
</defs>
|
|
</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) {
|
|
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 (
|
|
return (
|
|
|
<div className={`h-[480px] overflow-clip relative shrink-0 w-full ${className || ''}`}>
|
|
<div className={`h-[480px] overflow-clip relative shrink-0 w-full ${className || ''}`}>
|
|
|
{/* 底部容器 - 使用BaseContainer作为背景 */}
|
|
{/* 底部容器 - 使用BaseContainer作为背景 */}
|
|
@@ -180,7 +260,7 @@ export function AssetMetrics({ className }: AssetMetricsProps) {
|
|
|
</p>
|
|
</p>
|
|
|
|
|
|
|
|
{/* Recharts图表区域 */}
|
|
{/* 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%">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
|
<BarChart
|
|
<BarChart
|
|
|
data={chartData}
|
|
data={chartData}
|
|
@@ -203,22 +283,32 @@ export function AssetMetrics({ className }: AssetMetricsProps) {
|
|
|
tickLine={false}
|
|
tickLine={false}
|
|
|
tick={{ fill: 'rgba(255,255,255,0.8)', fontSize: 12 }}
|
|
tick={{ fill: 'rgba(255,255,255,0.8)', fontSize: 12 }}
|
|
|
/>
|
|
/>
|
|
|
- <Tooltip
|
|
|
|
|
|
|
+ {/* <Tooltip
|
|
|
contentStyle={{
|
|
contentStyle={{
|
|
|
backgroundColor: 'rgba(0,0,0,0.8)',
|
|
backgroundColor: 'rgba(0,0,0,0.8)',
|
|
|
border: '1px solid rgba(255,255,255,0.2)',
|
|
border: '1px solid rgba(255,255,255,0.2)',
|
|
|
color: 'white'
|
|
color: 'white'
|
|
|
}}
|
|
}}
|
|
|
formatter={(value) => [`${value} 亿元`, '']}
|
|
formatter={(value) => [`${value} 亿元`, '']}
|
|
|
- />
|
|
|
|
|
|
|
+ /> */}
|
|
|
<Bar
|
|
<Bar
|
|
|
dataKey="资产总额"
|
|
dataKey="资产总额"
|
|
|
- shape={<AssetTotalBar />}
|
|
|
|
|
|
|
+ shape={(props: any) => (
|
|
|
|
|
+ <AssetTotalBar
|
|
|
|
|
+ {...props}
|
|
|
|
|
+ animationProgress={animationProgress}
|
|
|
|
|
+ />
|
|
|
|
|
+ )}
|
|
|
barSize={45}
|
|
barSize={45}
|
|
|
/>
|
|
/>
|
|
|
<Bar
|
|
<Bar
|
|
|
dataKey="资产净额"
|
|
dataKey="资产净额"
|
|
|
- shape={<AssetNetBar />}
|
|
|
|
|
|
|
+ shape={(props: any) => (
|
|
|
|
|
+ <AssetNetBar
|
|
|
|
|
+ {...props}
|
|
|
|
|
+ animationProgress={animationProgress}
|
|
|
|
|
+ />
|
|
|
|
|
+ )}
|
|
|
barSize={45}
|
|
barSize={45}
|
|
|
/>
|
|
/>
|
|
|
</BarChart>
|
|
</BarChart>
|
|
@@ -229,6 +319,7 @@ export function AssetMetrics({ className }: AssetMetricsProps) {
|
|
|
{/* 模块标题 */}
|
|
{/* 模块标题 */}
|
|
|
<ModuleHeaderBackground className="absolute h-[38px] left-0 top-0 w-[594.001px]" />
|
|
<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="资产负债率" />
|
|
<ModuleHeader className="absolute h-[38px] left-0 overflow-clip top-0 w-[594px]" title="资产负债率" />
|
|
|
|
|
+
|
|
|
</div>
|
|
</div>
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|