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

✨ feat(financial-dashboard): 完成财务数据可视化大屏组件化重构

- 创建四个独立的财务数据模块组件【AssetMetrics、ProfitMetrics、IncomeMetrics、DebtRatioMetrics】
- 重构BarElement组件支持多种数据类型变体和动态高度计算
- 集成GridBackground和BackgroundOverlay背景组件增强视觉效果
- 更新页面标题为"战略部署"以符合业务需求
- 实现响应式柱形图展示,支持正负值显示和不同数据范围的自适应高度
- 优化组件结构和代码复用性
yourname 2 месяцев назад
Родитель
Сommit
267cd580df

+ 26 - 17
docs/stories/006.003.实现财务数据可视化大屏静态页面.md

@@ -41,23 +41,23 @@ Ready for Review
     - [x] **先查看JSXMD文件** - 参考 `docs/战略部署主页面figma-jsx.md#L357-L370` 中的网格背景设计规范
   - [x] `src/client/home/pages/FinancialDashboard/components/BackgroundOverlay.tsx` - 常规界面组件(包含背景图片和遮罩层)
     - [x] **先查看JSXMD文件** - 参考 `docs/战略部署主页面figma-jsx.md#L371-L386` 中的背景遮罩设计规范
-- [ ] 创建四个财务数据模块组件 (AC: 2, 4)
-  - [ ] `src/client/home/pages/FinancialDashboard/components/AssetMetrics.tsx` - 资产负债率模块(资产总额与资产净额)
-    - [ ] **先查看JSXMD文件** - 参考 `docs/战略部署主页面figma-jsx.md` 中的资产负债率模块设计规范
-    - [ ] **使用静态数据** - 使用硬编码的示例数据渲染资产总额与资产净额图表
-    - [ ] **添加静态图表** - 实现图表布局和样式,不添加动画效果
-  - [ ] `src/client/home/pages/FinancialDashboard/components/ProfitMetrics.tsx` - 利润总额与净利润模块
-    - [ ] **先查看JSXMD文件** - 参考 `docs/战略部署主页面figma-jsx.md` 中的利润模块设计规范
-    - [ ] **使用静态数据** - 使用硬编码的示例数据渲染利润总额与净利润图表
-    - [ ] **添加静态图表** - 实现图表布局和样式,不添加动画效果
-  - [ ] `src/client/home/pages/FinancialDashboard/components/IncomeMetrics.tsx` - 收入模块
-    - [ ] **先查看JSXMD文件** - 参考 `docs/战略部署主页面figma-jsx.md` 中的收入模块设计规范
-    - [ ] **使用静态数据** - 使用硬编码的示例数据渲染收入图表
-    - [ ] **添加静态图表** - 实现图表布局和样式,不添加动画效果
-  - [ ] `src/client/home/pages/FinancialDashboard/components/DebtRatioMetrics.tsx` - 资产负债率(百分比)模块
-    - [ ] **先查看JSXMD文件** - 参考 `docs/战略部署主页面figma-jsx.md` 中的资产负债率百分比模块设计规范
-    - [ ] **使用静态数据** - 使用硬编码的示例数据渲染资产负债率图表
-    - [ ] **添加静态图表** - 实现图表布局和样式,不添加动画效果
+- [x] 创建四个财务数据模块组件 (AC: 2, 4)
+  - [x] `src/client/home/pages/FinancialDashboard/components/AssetMetrics.tsx` - 资产负债率模块(资产总额与资产净额)
+    - [x] **先查看JSXMD文件** - 参考 `docs/战略部署主页面figma-jsx.md` 中的资产负债率模块设计规范
+    - [x] **使用静态数据** - 使用硬编码的示例数据渲染资产总额与资产净额图表
+    - [x] **添加静态图表** - 实现图表布局和样式,不添加动画效果
+  - [x] `src/client/home/pages/FinancialDashboard/components/ProfitMetrics.tsx` - 利润总额与净利润模块
+    - [x] **先查看JSXMD文件** - 参考 `docs/战略部署主页面figma-jsx.md` 中的利润模块设计规范
+    - [x] **使用静态数据** - 使用硬编码的示例数据渲染利润总额与净利润图表
+    - [x] **添加静态图表** - 实现图表布局和样式,不添加动画效果
+  - [x] `src/client/home/pages/FinancialDashboard/components/IncomeMetrics.tsx` - 收入模块
+    - [x] **先查看JSXMD文件** - 参考 `docs/战略部署主页面figma-jsx.md` 中的收入模块设计规范
+    - [x] **使用静态数据** - 使用硬编码的示例数据渲染收入图表
+    - [x] **添加静态图表** - 实现图表布局和样式,不添加动画效果
+  - [x] `src/client/home/pages/FinancialDashboard/components/DebtRatioMetrics.tsx` - 资产负债率(百分比)模块
+    - [x] **先查看JSXMD文件** - 参考 `docs/战略部署主页面figma-jsx.md` 中的资产负债率百分比模块设计规范
+    - [x] **使用静态数据** - 使用硬编码的示例数据渲染资产负债率图表
+    - [x] **添加静态图表** - 实现图表布局和样式,不添加动画效果
 - [ ] 下载和配置图片资源 (AC: 2)
   - [ ] 创建 `public/financial-dashboard/` 目录
   - [ ] 下载所有Figma设计中的图片资源
@@ -268,6 +268,11 @@ for file in *.png; do if file "$file" | grep -q "SVG"; then mv "$file" "${file%.
 - ✅ 创建了TimeIcon组件(时间图标)
 - ✅ 创建了GridBackground组件(底部网格)
 - ✅ 创建了BackgroundOverlay组件(背景遮罩)
+- ✅ 创建了AssetMetrics组件(资产负债率模块)
+- ✅ 创建了ProfitMetrics组件(利润总额与净利润模块)
+- ✅ 创建了IncomeMetrics组件(收入模块)
+- ✅ 创建了DebtRatioMetrics组件(资产负债率百分比模块)
+- ✅ 修复了BaseContainer组件缺少className的问题
 - ✅ 通过所有验证测试
 
 ### File List
@@ -280,5 +285,9 @@ for file in *.png; do if file "$file" | grep -q "SVG"; then mv "$file" "${file%.
 - `src/client/home/pages/FinancialDashboard/components/TimeIcon.tsx` (新建)
 - `src/client/home/pages/FinancialDashboard/components/GridBackground.tsx` (新建)
 - `src/client/home/pages/FinancialDashboard/components/BackgroundOverlay.tsx` (新建)
+- `src/client/home/pages/FinancialDashboard/components/AssetMetrics.tsx` (新建)
+- `src/client/home/pages/FinancialDashboard/components/ProfitMetrics.tsx` (新建)
+- `src/client/home/pages/FinancialDashboard/components/IncomeMetrics.tsx` (新建)
+- `src/client/home/pages/FinancialDashboard/components/DebtRatioMetrics.tsx` (新建)
 
 ## QA Results

+ 13 - 81
src/client/home/pages/FinancialDashboard/FinancialDashboard.tsx

@@ -1,36 +1,9 @@
-
-// 静态数据定义 - 基于故事中的示例数据
-const assetData = [
-  { year: '2021年', assetTotal: 200.46, assetNet: 55.40 },
-  { year: '2022年', assetTotal: 243.27, assetNet: 57.49 },
-  { year: '2023年', assetTotal: 509.08, assetNet: 247.29 },
-  { year: '2024年', assetTotal: 772.66, assetNet: 407.68 },
-  { year: '2025年', assetTotal: 840.12, assetNet: 421.55 }
-];
-
-const profitData = [
-  { year: '2021年', profitTotal: -1.5, profitNet: -1.7 },
-  { year: '2022年', profitTotal: 0.8, profitNet: 0.59 },
-  { year: '2023年', profitTotal: 1.28, profitNet: 0.6 },
-  { year: '2024年', profitTotal: 1.65, profitNet: 1.22 },
-  { year: '2025年', profitTotal: 1.34, profitNet: 1.00 }
-];
-
-const incomeData = [
-  { year: '2021年', income: 161.29 },
-  { year: '2022年', income: 243.27 },
-  { year: '2023年', income: 509.08 },
-  { year: '2024年', income: 772.66 },
-  { year: '2025年', income: 840.12 }
-];
-
-const debtRatioData = [
-  { year: '2021年', ratio: 73.37 },
-  { year: '2022年', ratio: 76.37 },
-  { year: '2023年', ratio: 51.42 },
-  { year: '2024年', ratio: 47.24 },
-  { year: '2025年', ratio: 49.82 }
-];
+import { AssetMetrics } from './components/AssetMetrics';
+import { ProfitMetrics } from './components/ProfitMetrics';
+import { IncomeMetrics } from './components/IncomeMetrics';
+import { DebtRatioMetrics } from './components/DebtRatioMetrics';
+import GridBackground from './components/GridBackground';
+import BackgroundOverlay from './components/BackgroundOverlay';
 
 export default function FinancialDashboard() {
   return (
@@ -47,72 +20,31 @@ export default function FinancialDashboard() {
 
       {/* 底部网格和背景 */}
       <div className="absolute inset-0">
-        {/* 网格背景和常规界面组件将在后续实现 */}
+        <BackgroundOverlay />
+        <GridBackground />
       </div>
 
       {/* 头部区域 */}
       <div className="absolute top-0 left-0 w-full h-[74px] bg-gradient-to-r from-blue-900/80 to-purple-900/80">
         {/* 头部装饰将在后续实现 */}
         <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
-          <h1 className="text-2xl font-bold text-white">财务数据可视化大屏</h1>
+          <h1 className="text-2xl font-bold text-white">战略部署</h1>
         </div>
       </div>
 
       {/* 四个数据模块容器 */}
       <div className="absolute top-[100px] left-0 w-full h-[900px] grid grid-cols-2 grid-rows-2 gap-6 p-6">
         {/* 资产负债率模块 */}
-        <div className="bg-gray-800/80 rounded-lg p-4 border border-gray-600">
-          <h2 className="text-lg font-semibold mb-4">资产负债率</h2>
-          <div className="space-y-2">
-            {assetData.map((item, index) => (
-              <div key={index} className="flex justify-between">
-                <span>{item.year}</span>
-                <span>资产总额: {item.assetTotal}亿</span>
-                <span>资产净额: {item.assetNet}亿</span>
-              </div>
-            ))}
-          </div>
-        </div>
+        <AssetMetrics />
 
         {/* 收入模块 */}
-        <div className="bg-gray-800/80 rounded-lg p-4 border border-gray-600">
-          <h2 className="text-lg font-semibold mb-4">收入</h2>
-          <div className="space-y-2">
-            {incomeData.map((item, index) => (
-              <div key={index} className="flex justify-between">
-                <span>{item.year}</span>
-                <span>{item.income}亿</span>
-              </div>
-            ))}
-          </div>
-        </div>
+        <IncomeMetrics />
 
         {/* 利润总额与净利润模块 */}
-        <div className="bg-gray-800/80 rounded-lg p-4 border border-gray-600">
-          <h2 className="text-lg font-semibold mb-4">利润总额与净利润</h2>
-          <div className="space-y-2">
-            {profitData.map((item, index) => (
-              <div key={index} className="flex justify-between">
-                <span>{item.year}</span>
-                <span>利润总额: {item.profitTotal}亿</span>
-                <span>净利润: {item.profitNet}亿</span>
-              </div>
-            ))}
-          </div>
-        </div>
+        <ProfitMetrics />
 
         {/* 资产负债率(百分比)模块 */}
-        <div className="bg-gray-800/80 rounded-lg p-4 border border-gray-600">
-          <h2 className="text-lg font-semibold mb-4">资产负债率(百分比)</h2>
-          <div className="space-y-2">
-            {debtRatioData.map((item, index) => (
-              <div key={index} className="flex justify-between">
-                <span>{item.year}</span>
-                <span>{item.ratio}%</span>
-              </div>
-            ))}
-          </div>
-        </div>
+        <DebtRatioMetrics />
       </div>
 
       {/* 右下角浮动按钮 */}

+ 130 - 0
src/client/home/pages/FinancialDashboard/components/AssetMetrics.tsx

@@ -0,0 +1,130 @@
+import BarElement from './BarElement';
+import BaseContainer from './BaseContainer';
+
+interface AssetMetricsProps {
+  className?: string;
+}
+
+const assetData = [
+  { year: '2021年', assetTotal: 200.46, assetNet: 55.40 },
+  { year: '2022年', assetTotal: 243.27, assetNet: 57.49 },
+  { year: '2023年', assetTotal: 509.08, assetNet: 247.29 },
+  { year: '2024年', assetTotal: 772.66, assetNet: 407.68 },
+  { year: '2025年', assetTotal: 840.12, assetNet: 421.55 }
+];
+
+const maxValue = Math.max(...assetData.map(d => Math.max(d.assetTotal, d.assetNet)));
+
+const calculateHeight = (value: number) => {
+  const maxHeight = 224; // 最大柱形高度
+  return (value / maxValue) * maxHeight;
+};
+
+export function AssetMetrics({ className }: AssetMetricsProps) {
+  return (
+    <div className={`h-[480px] overflow-clip relative ${className || ''}`}>
+      {/* 底部容器 - 使用BaseContainer作为背景 */}
+      <BaseContainer className="absolute h-[490px] left-0 overflow-clip top-0 w-[930px]" />
+
+      {/* 表格区域 */}
+      <div className="absolute content-stretch flex flex-col gap-[20px] h-[388px] items-start left-[41.4px] top-[79px] w-[847.191px]">
+          {/* 顶部标题和说明 */}
+          <div className="content-stretch flex items-start justify-between relative shrink-0 w-[847px]">
+            <div className="content-stretch flex gap-[20px] items-center relative shrink-0">
+              <div className="content-stretch flex gap-[10px] h-[26px] items-center relative shrink-0">
+                <div className="flex items-center justify-center relative shrink-0">
+                  <div className="flex-none rotate-[180deg]">
+                    <BarElement variant="asset-total" className="size-[14px]" />
+                  </div>
+                </div>
+                <div className="flex flex-col justify-center leading-[0] relative shrink-0 text-[18px] text-white w-[83px]">
+                  <p className="leading-[25.2px] whitespace-pre-wrap">资产总额</p>
+                </div>
+              </div>
+              <div className="content-stretch flex gap-[20px] h-[26px] items-center relative shrink-0">
+                <div className="flex items-center justify-center relative shrink-0">
+                  <div className="flex-none rotate-[180deg]">
+                    <BarElement variant="asset-net" className="h-[10.769px] w-[14px] rounded-[2px]" />
+                  </div>
+                </div>
+                <div className="flex flex-col h-[26px] justify-center leading-[0] relative shrink-0 text-[18px] text-white w-[80px]">
+                  <p className="leading-[25.2px] whitespace-pre-wrap">资产净额</p>
+                </div>
+              </div>
+            </div>
+            <p className="leading-[25.2px] relative shrink-0 text-[14px] text-[rgba(255,255,255,0.8)]">
+              *数据截止至2025年9月
+            </p>
+          </div>
+
+          <p className="leading-[25.2px] relative shrink-0 text-[14px] text-[rgba(255,255,255,0.8)]">
+            单位:亿元
+          </p>
+
+          {/* 年份标签 */}
+          <div className="grid-cols-[max-content] grid-rows-[max-content] inline-grid justify-items-start leading-[0] relative shrink-0">
+            <div className="col-[1] grid-cols-[max-content] grid-rows-[max-content] inline-grid justify-items-start leading-[21.504px] ml-[93px] mt-[calc(50%+125.72px)] relative row-[1] text-[15px] text-[rgba(255,255,255,0.8)] whitespace-pre-wrap">
+              {assetData.map((data, index) => (
+                <p
+                  key={data.year}
+                  className={`col-[1] mt-0 relative row-[1] ${
+                    index === 0 ? 'ml-0 w-[49.92px]' :
+                    index === 1 ? 'ml-[207.76px] text-right translate-x-[-100%] w-[53.76px]' :
+                    index === 2 ? 'ml-[376.68px] text-right translate-x-[-100%] w-[55.68px]' :
+                    index === 3 ? 'ml-[490px] w-[52.48px]' :
+                    'ml-[656px] mt-px w-[58px]'
+                  }`}
+                >
+                  {data.year}
+                </p>
+              ))}
+            </div>
+          </div>
+
+          {/* 柱形图数据 */}
+          <div className="box-border col-[1] content-stretch flex items-end justify-between ml-[69px] mt-[3.44px] relative row-[1] w-[753px]">
+            {assetData.map((data) => (
+              <div key={data.year} className="content-stretch flex gap-[10px] items-end relative shrink-0">
+                {/* 资产总额 */}
+                <div className="content-stretch flex flex-col gap-[10px] items-center relative shrink-0 w-[43px]">
+                  <p className="leading-[21.504px] min-w-full not-italic relative shrink-0 text-[13px] text-white w-[min-content] whitespace-pre-wrap">
+                    {data.assetTotal}
+                  </p>
+                  <div className="flex items-center justify-center relative shrink-0">
+                    <div className="flex-none rotate-[180deg]">
+                      <BarElement
+                        variant="asset-total"
+                        className={`w-[35px]`}
+                        style={{ height: `${calculateHeight(data.assetTotal)}px` }}
+                      />
+                    </div>
+                  </div>
+                </div>
+
+                {/* 资产净额 */}
+                <div className="content-stretch flex flex-col gap-[10px] items-center relative shrink-0 w-[35px]">
+                  <p className="leading-[21.504px] min-w-full not-italic relative shrink-0 text-[13px] text-white w-[min-content] whitespace-pre-wrap">
+                    {data.assetNet}
+                  </p>
+                  <div className="flex items-center justify-center relative shrink-0">
+                    <div className="flex-none rotate-[180deg]">
+                      <BarElement
+                        variant="asset-net"
+                        className={`w-[35px] rounded-[2px]`}
+                        style={{ height: `${calculateHeight(data.assetNet)}px` }}
+                      />
+                    </div>
+                  </div>
+                </div>
+              </div>
+            ))}
+          </div>
+        </div>
+
+      {/* 模块标题 */}
+      <div className="absolute top-4 left-4">
+        <p className="text-[20px] font-medium text-white">资产负债率</p>
+      </div>
+    </div>
+  );
+}

+ 35 - 12
src/client/home/pages/FinancialDashboard/components/BarElement.tsx

@@ -1,13 +1,13 @@
 interface BarElementProps {
   className?: string;
-  property1?: "柱形元素";
-  property2?: "1-1" | "1-2" | "2-1" | "2-4" | "2-3";
+  variant?: "asset-total" | "asset-net" | "profit-total" | "profit-net" | "income" | "debt-ratio";
+  style?: React.CSSProperties;
 }
 
-export default function BarElement({ className, property1 = "柱形元素", property2 = "2-1" }: BarElementProps) {
-  if (property1 === "柱形元素" && property2 === "2-4") {
+export default function BarElement({ className, variant = "profit-total", style }: BarElementProps) {
+  if (variant === "debt-ratio") {
     return (
-      <div className={className} data-name="Property 1=柱形元素, Property 2=2-4">
+      <div className={className} style={style} data-name="Property 1=柱形元素, Property 2=2-4">
         <div className="flex flex-[1_0_0] items-center justify-center mb-[-3px] min-h-px min-w-px relative shrink-0 w-full">
           <div className="flex-none rotate-[180deg] size-full">
             <div className="box-border content-stretch flex flex-col items-start pb-[3px] pt-0 px-0 relative size-full">
@@ -42,9 +42,9 @@ export default function BarElement({ className, property1 = "柱形元素", prop
     );
   }
 
-  if (property1 === "柱形元素" && property2 === "1-2") {
+  if (variant === "asset-net") {
     return (
-      <div className={className} data-name="Property 1=柱形元素, Property 2=1-2">
+      <div className={className} style={style} data-name="Property 1=柱形元素, Property 2=1-2">
         <div className="flex flex-[1_0_0] items-center justify-center mb-[-3px] min-h-px min-w-px relative shrink-0 w-full">
           <div className="flex-none rotate-[180deg] size-full">
             <div className="bg-gradient-to-b box-border content-stretch flex flex-col from-[rgba(237,206,89,0.808)] items-start pb-[3px] pt-0 px-0 relative size-full to-[rgba(237,206,89,0)]">
@@ -65,9 +65,9 @@ export default function BarElement({ className, property1 = "柱形元素", prop
     );
   }
 
-  if (property1 === "柱形元素" && property2 === "1-1") {
+  if (variant === "asset-total") {
     return (
-      <div className={className} data-name="Property 1=柱形元素, Property 2=1-1">
+      <div className={className} style={style} data-name="Property 1=柱形元素, Property 2=1-1">
         <div className="flex flex-[1_0_0] items-center justify-center mb-[-3px] min-h-px min-w-px relative shrink-0 w-full">
           <div className="flex-none rotate-[180deg] size-full">
             <div className="bg-gradient-to-b box-border content-stretch flex flex-col from-[rgba(162,190,255,0.808)] items-start pb-[3px] pt-0 px-0 relative size-full to-[rgba(162,190,255,0)]">
@@ -88,9 +88,9 @@ export default function BarElement({ className, property1 = "柱形元素", prop
     );
   }
 
-  if (property1 === "柱形元素" && property2 === "2-3") {
+  if (variant === "profit-net") {
     return (
-      <div className={className} data-name="Property 1=柱形元素, Property 2=2-3">
+      <div className={className} style={style} data-name="Property 1=柱形元素, Property 2=2-3">
         <div className="flex flex-[1_0_0] items-center justify-center mb-[-3px] min-h-px min-w-px relative shrink-0 w-full">
           <div className="flex-none rotate-[180deg] size-full">
             <div className="bg-gradient-to-b box-border content-stretch flex flex-col from-[rgba(255,173,255,0.808)] items-start pb-[3px] pt-0 px-0 relative size-full to-[rgba(255,173,255,0)]">
@@ -111,7 +111,30 @@ export default function BarElement({ className, property1 = "柱形元素", prop
     );
   }
 
-  // 默认变体 (2-1)
+  if (variant === "income") {
+    return (
+      <div className={className} style={style} data-name="Property 1=柱形元素, Property 2=1-1">
+        <div className="flex flex-[1_0_0] items-center justify-center mb-[-3px] min-h-px min-w-px relative shrink-0 w-full">
+          <div className="flex-none rotate-[180deg] size-full">
+            <div className="bg-gradient-to-b box-border content-stretch flex flex-col from-[rgba(162,190,255,0.808)] items-start pb-[3px] pt-0 px-0 relative size-full to-[rgba(162,190,255,0)]">
+              <div className="h-[4px] mb-[-3px] relative shrink-0 w-full" data-name="Vector">
+                <img alt="" className="block max-w-none size-full" src="/financial-dashboard/vector4.svg" />
+              </div>
+            </div>
+          </div>
+        </div>
+        <div className="flex items-center justify-center mb-[-3px] relative shrink-0 w-full">
+          <div className="flex-none rotate-[180deg] w-full">
+            <div className="h-[6px] relative w-full">
+              <img alt="" className="block max-w-none size-full" src="/financial-dashboard/frame10908.svg" />
+            </div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  // 默认变体 (profit-total)
   return (
     <div className={className} data-name="Property 1=柱形元素, Property 2=2-1">
       <div className="flex flex-[1_0_0] items-center justify-center mb-[-3px] min-h-px min-w-px relative shrink-0 w-full">

+ 103 - 0
src/client/home/pages/FinancialDashboard/components/DebtRatioMetrics.tsx

@@ -0,0 +1,103 @@
+import BarElement from './BarElement';
+import BaseContainer from './BaseContainer';
+
+interface DebtRatioMetricsProps {
+  className?: string;
+}
+
+const debtRatioData = [
+  { year: '2021年', ratio: 73.37 },
+  { year: '2022年', ratio: 76.37 },
+  { year: '2023年', ratio: 51.42 },
+  { year: '2024年', ratio: 47.24 },
+  { year: '2025年', ratio: 49.82 }
+];
+
+const maxValue = Math.max(...debtRatioData.map(d => d.ratio));
+
+const calculateHeight = (value: number) => {
+  const maxHeight = 150; // 最大柱形高度
+  return (value / maxValue) * maxHeight;
+};
+
+export function DebtRatioMetrics({ className }: DebtRatioMetricsProps) {
+  return (
+    <div className={`h-[480px] overflow-clip relative ${className || ''}`}>
+      {/* 底部容器 - 使用BaseContainer作为背景 */}
+      <BaseContainer className="absolute h-[490px] left-0 overflow-clip top-0 w-[930px]" />
+        {/* 表格区域 */}
+        <div className="absolute content-stretch flex flex-col gap-[20px] h-[388px] items-start left-[41.4px] top-[79px] w-[847.191px]">
+          {/* 顶部标题和说明 */}
+          <div className="content-stretch flex items-start justify-between relative shrink-0 w-[847px]">
+            <div className="content-stretch flex gap-[20px] items-center relative shrink-0">
+              <div className="content-stretch flex gap-[10px] h-[26px] items-center relative shrink-0">
+                <div className="flex items-center justify-center relative shrink-0">
+                  <div className="flex-none rotate-[180deg]">
+                    <BarElement variant="debt-ratio" className="size-[14px]" />
+                  </div>
+                </div>
+                <div className="flex flex-col justify-center leading-[0] relative shrink-0 text-[18px] text-white w-[83px]">
+                  <p className="leading-[25.2px] whitespace-pre-wrap">资产负债率</p>
+                </div>
+              </div>
+            </div>
+            <p className="leading-[25.2px] relative shrink-0 text-[14px] text-[rgba(255,255,255,0.8)]">
+              *数据截止至2025年9月
+            </p>
+          </div>
+
+          <p className="leading-[25.2px] relative shrink-0 text-[14px] text-[rgba(255,255,255,0.8)]">
+            单位:%
+          </p>
+
+          {/* 年份标签 */}
+          <div className="grid-cols-[max-content] grid-rows-[max-content] inline-grid justify-items-start leading-[0] relative shrink-0">
+            <div className="col-[1] grid-cols-[max-content] grid-rows-[max-content] inline-grid justify-items-start leading-[21.504px] ml-[93px] mt-[calc(50%+125.72px)] relative row-[1] text-[15px] text-[rgba(255,255,255,0.8)] whitespace-pre-wrap">
+              {debtRatioData.map((data, index) => (
+                <p
+                  key={data.year}
+                  className={`col-[1] mt-0 relative row-[1] ${
+                    index === 0 ? 'ml-0 w-[49.92px]' :
+                    index === 1 ? 'ml-[207.76px] text-right translate-x-[-100%] w-[53.76px]' :
+                    index === 2 ? 'ml-[376.68px] text-right translate-x-[-100%] w-[55.68px]' :
+                    index === 3 ? 'ml-[490px] w-[52.48px]' :
+                    'ml-[656px] mt-px w-[58px]'
+                  }`}
+                >
+                  {data.year}
+                </p>
+              ))}
+            </div>
+          </div>
+
+          {/* 柱形图数据 */}
+          <div className="box-border col-[1] content-stretch flex items-end justify-between ml-[69px] mt-[3.44px] relative row-[1] w-[753px]">
+            {debtRatioData.map((data) => (
+              <div key={data.year} className="content-stretch flex gap-[10px] items-end relative shrink-0">
+                {/* 资产负债率 */}
+                <div className="content-stretch flex flex-col gap-[10px] items-center relative shrink-0 w-[43px]">
+                  <p className="leading-[21.504px] min-w-full not-italic relative shrink-0 text-[13px] text-white w-[min-content] whitespace-pre-wrap">
+                    {data.ratio}
+                  </p>
+                  <div className="flex items-center justify-center relative shrink-0">
+                    <div className="flex-none rotate-[180deg]">
+                      <BarElement
+                        variant="debt-ratio"
+                        className={`w-[35px]`}
+                        style={{ height: `${calculateHeight(data.ratio)}px` }}
+                      />
+                    </div>
+                  </div>
+                </div>
+              </div>
+            ))}
+          </div>
+        </div>
+
+      {/* 模块标题 */}
+      <div className="absolute top-4 left-4">
+        <p className="text-[20px] font-medium text-white">资产负债率</p>
+      </div>
+    </div>
+  );
+}

+ 103 - 0
src/client/home/pages/FinancialDashboard/components/IncomeMetrics.tsx

@@ -0,0 +1,103 @@
+import BarElement from './BarElement';
+import BaseContainer from './BaseContainer';
+
+interface IncomeMetricsProps {
+  className?: string;
+}
+
+const incomeData = [
+  { year: '2021年', income: 161.29 },
+  { year: '2022年', income: 243.27 },
+  { year: '2023年', income: 509.08 },
+  { year: '2024年', income: 772.66 },
+  { year: '2025年', income: 840.12 }
+];
+
+const maxValue = Math.max(...incomeData.map(d => d.income));
+
+const calculateHeight = (value: number) => {
+  const maxHeight = 224; // 最大柱形高度
+  return (value / maxValue) * maxHeight;
+};
+
+export function IncomeMetrics({ className }: IncomeMetricsProps) {
+  return (
+    <div className={`h-[480px] overflow-clip relative ${className || ''}`}>
+      {/* 底部容器 - 使用BaseContainer作为背景 */}
+      <BaseContainer className="absolute h-[490px] left-0 overflow-clip top-0 w-[930px]" />
+        {/* 表格区域 */}
+        <div className="absolute content-stretch flex flex-col gap-[20px] h-[388px] items-start left-[41.4px] top-[79px] w-[847.191px]">
+          {/* 顶部标题和说明 */}
+          <div className="content-stretch flex items-start justify-between relative shrink-0 w-[847px]">
+            <div className="content-stretch flex gap-[20px] items-center relative shrink-0">
+              <div className="content-stretch flex gap-[10px] h-[26px] items-center relative shrink-0">
+                <div className="flex items-center justify-center relative shrink-0">
+                  <div className="flex-none rotate-[180deg]">
+                    <BarElement variant="income" className="size-[14px]" />
+                  </div>
+                </div>
+                <div className="flex flex-col justify-center leading-[0] relative shrink-0 text-[18px] text-white w-[83px]">
+                  <p className="leading-[25.2px] whitespace-pre-wrap">收入</p>
+                </div>
+              </div>
+            </div>
+            <p className="leading-[25.2px] relative shrink-0 text-[14px] text-[rgba(255,255,255,0.8)]">
+              *数据截止至2025年9月
+            </p>
+          </div>
+
+          <p className="leading-[25.2px] relative shrink-0 text-[14px] text-[rgba(255,255,255,0.8)]">
+            单位:亿元
+          </p>
+
+          {/* 年份标签 */}
+          <div className="grid-cols-[max-content] grid-rows-[max-content] inline-grid justify-items-start leading-[0] relative shrink-0">
+            <div className="col-[1] grid-cols-[max-content] grid-rows-[max-content] inline-grid justify-items-start leading-[21.504px] ml-[93px] mt-[calc(50%+125.72px)] relative row-[1] text-[15px] text-[rgba(255,255,255,0.8)] whitespace-pre-wrap">
+              {incomeData.map((data, index) => (
+                <p
+                  key={data.year}
+                  className={`col-[1] mt-0 relative row-[1] ${
+                    index === 0 ? 'ml-0 w-[49.92px]' :
+                    index === 1 ? 'ml-[207.76px] text-right translate-x-[-100%] w-[53.76px]' :
+                    index === 2 ? 'ml-[376.68px] text-right translate-x-[-100%] w-[55.68px]' :
+                    index === 3 ? 'ml-[490px] w-[52.48px]' :
+                    'ml-[656px] mt-px w-[58px]'
+                  }`}
+                >
+                  {data.year}
+                </p>
+              ))}
+            </div>
+          </div>
+
+          {/* 柱形图数据 */}
+          <div className="box-border col-[1] content-stretch flex items-end justify-between ml-[69px] mt-[3.44px] relative row-[1] w-[753px]">
+            {incomeData.map((data) => (
+              <div key={data.year} className="content-stretch flex gap-[10px] items-end relative shrink-0">
+                {/* 收入 */}
+                <div className="content-stretch flex flex-col gap-[10px] items-center relative shrink-0 w-[43px]">
+                  <p className="leading-[21.504px] min-w-full not-italic relative shrink-0 text-[13px] text-white w-[min-content] whitespace-pre-wrap">
+                    {data.income}
+                  </p>
+                  <div className="flex items-center justify-center relative shrink-0">
+                    <div className="flex-none rotate-[180deg]">
+                      <BarElement
+                        variant="income"
+                        className={`w-[35px]`}
+                        style={{ height: `${calculateHeight(data.income)}px` }}
+                      />
+                    </div>
+                  </div>
+                </div>
+              </div>
+            ))}
+          </div>
+        </div>
+
+      {/* 模块标题 */}
+      <div className="absolute top-4 left-4">
+        <p className="text-[20px] font-medium text-white">收入</p>
+      </div>
+    </div>
+  );
+}

+ 134 - 0
src/client/home/pages/FinancialDashboard/components/ProfitMetrics.tsx

@@ -0,0 +1,134 @@
+import BarElement from './BarElement';
+import BaseContainer from './BaseContainer';
+
+interface ProfitMetricsProps {
+  className?: string;
+}
+
+const profitData = [
+  { year: '2021年', profitTotal: -1.5, profitNet: -1.7 },
+  { year: '2022年', profitTotal: 0.8, profitNet: 0.59 },
+  { year: '2023年', profitTotal: 1.28, profitNet: 0.6 },
+  { year: '2024年', profitTotal: 1.65, profitNet: 1.22 },
+  { year: '2025年', profitTotal: 1.34, profitNet: 1.00 }
+];
+
+const maxPositiveValue = Math.max(...profitData.map(d => Math.max(d.profitTotal, d.profitNet)));
+const minNegativeValue = Math.min(...profitData.map(d => Math.min(d.profitTotal, d.profitNet)));
+
+const calculateHeight = (value: number) => {
+  const maxHeight = 100; // 最大柱形高度
+  if (value >= 0) {
+    return (value / maxPositiveValue) * maxHeight;
+  } else {
+    return (Math.abs(value) / Math.abs(minNegativeValue)) * maxHeight;
+  }
+};
+
+export function ProfitMetrics({ className }: ProfitMetricsProps) {
+  return (
+    <div className={`h-[480px] overflow-clip relative ${className || ''}`}>
+      {/* 底部容器 - 使用BaseContainer作为背景 */}
+      <BaseContainer className="absolute h-[490px] left-0 overflow-clip top-0 w-[930px]" />
+        {/* 表格区域 */}
+        <div className="absolute content-stretch flex flex-col gap-[20px] h-[388px] items-start left-[41.4px] top-[79px] w-[847.191px]">
+          {/* 顶部标题和说明 */}
+          <div className="content-stretch flex items-start justify-between relative shrink-0 w-[847px]">
+            <div className="content-stretch flex gap-[20px] items-center relative shrink-0">
+              <div className="content-stretch flex gap-[10px] h-[26px] items-center relative shrink-0">
+                <div className="flex items-center justify-center relative shrink-0">
+                  <div className="flex-none rotate-[180deg]">
+                    <BarElement variant="profit-total" className="size-[14px]" />
+                  </div>
+                </div>
+                <div className="flex flex-col justify-center leading-[0] relative shrink-0 text-[18px] text-white w-[83px]">
+                  <p className="leading-[25.2px] whitespace-pre-wrap">利润总额</p>
+                </div>
+              </div>
+              <div className="content-stretch flex gap-[20px] h-[26px] items-center relative shrink-0">
+                <div className="flex items-center justify-center relative shrink-0">
+                  <div className="flex-none rotate-[180deg]">
+                    <BarElement variant="profit-net" className="h-[10.769px] w-[14px] rounded-[2px]" />
+                  </div>
+                </div>
+                <div className="flex flex-col h-[26px] justify-center leading-[0] relative shrink-0 text-[18px] text-white w-[80px]">
+                  <p className="leading-[25.2px] whitespace-pre-wrap">净利润</p>
+                </div>
+              </div>
+            </div>
+            <p className="leading-[25.2px] relative shrink-0 text-[14px] text-[rgba(255,255,255,0.8)]">
+              *数据截止至2025年9月
+            </p>
+          </div>
+
+          <p className="leading-[25.2px] relative shrink-0 text-[14px] text-[rgba(255,255,255,0.8)]">
+            单位:亿元
+          </p>
+
+          {/* 年份标签 */}
+          <div className="grid-cols-[max-content] grid-rows-[max-content] inline-grid justify-items-start leading-[0] relative shrink-0">
+            <div className="col-[1] grid-cols-[max-content] grid-rows-[max-content] inline-grid justify-items-start leading-[21.504px] ml-[93px] mt-[calc(50%+125.72px)] relative row-[1] text-[15px] text-[rgba(255,255,255,0.8)] whitespace-pre-wrap">
+              {profitData.map((data, index) => (
+                <p
+                  key={data.year}
+                  className={`col-[1] mt-0 relative row-[1] ${
+                    index === 0 ? 'ml-0 w-[49.92px]' :
+                    index === 1 ? 'ml-[207.76px] text-right translate-x-[-100%] w-[53.76px]' :
+                    index === 2 ? 'ml-[376.68px] text-right translate-x-[-100%] w-[55.68px]' :
+                    index === 3 ? 'ml-[490px] w-[52.48px]' :
+                    'ml-[656px] mt-px w-[58px]'
+                  }`}
+                >
+                  {data.year}
+                </p>
+              ))}
+            </div>
+          </div>
+
+          {/* 柱形图数据 */}
+          <div className="box-border col-[1] content-stretch flex items-end justify-between ml-[69px] mt-[3.44px] relative row-[1] w-[753px]">
+            {profitData.map((data) => (
+              <div key={data.year} className="content-stretch flex gap-[10px] items-end relative shrink-0">
+                {/* 利润总额 */}
+                <div className="content-stretch flex flex-col gap-[10px] items-center relative shrink-0 w-[43px]">
+                  <p className="leading-[21.504px] min-w-full not-italic relative shrink-0 text-[13px] text-white w-[min-content] whitespace-pre-wrap">
+                    {data.profitTotal}
+                  </p>
+                  <div className="flex items-center justify-center relative shrink-0">
+                    <div className="flex-none rotate-[180deg]">
+                      <BarElement
+                        variant="profit-total"
+                        className={`w-[35px]`}
+                        style={{ height: `${calculateHeight(data.profitTotal)}px` }}
+                      />
+                    </div>
+                  </div>
+                </div>
+
+                {/* 净利润 */}
+                <div className="content-stretch flex flex-col gap-[10px] items-center relative shrink-0 w-[35px]">
+                  <p className="leading-[21.504px] min-w-full not-italic relative shrink-0 text-[13px] text-white w-[min-content] whitespace-pre-wrap">
+                    {data.profitNet}
+                  </p>
+                  <div className="flex items-center justify-center relative shrink-0">
+                    <div className="flex-none rotate-[180deg]">
+                      <BarElement
+                        variant="profit-net"
+                        className={`w-[35px] rounded-[2px]`}
+                        style={{ height: `${calculateHeight(data.profitNet)}px` }}
+                      />
+                    </div>
+                  </div>
+                </div>
+              </div>
+            ))}
+          </div>
+        </div>
+
+      {/* 模块标题 */}
+      <div className="absolute top-4 left-4">
+        <p className="text-[20px] font-medium text-white">利润总额与净利润</p>
+      </div>
+    </div>
+  );
+}