Jelajahi Sumber

♻️ refactor(dashboard): 重构财务数据可视化大屏实现方案

- 移除基于Recharts的旧版图表组件,采用自定义React组件实现
- 删除FinancialDashboard主页面及其所有子组件文件
- 更新技术文档,明确图表实现技术决策和设计规范
- 【技术决策】选择自定义组件而非Recharts以提升设计还原度和动画控制精度
- 【性能优化】针对大屏展示场景优化渲染性能,避免通用图表库的开销
yourname 2 bulan lalu
induk
melakukan
21099f372b

+ 16 - 2
docs/stories/006.002.实现财务数据可视化大屏页面.md

@@ -94,8 +94,22 @@ Draft
 - **样式**: Tailwind CSS 4.1.11
 - **状态管理**: React Query 5.83.0 (服务端状态) + Context (本地状态)
 - **UI组件库**: shadcn/ui (基于Radix UI)
-- **可视化组件**: Recharts (已集成在 `src/client/components/ui/chart.tsx`)
-- **动画库**: Framer Motion (推荐) 或 React Spring (备选) - 为图表数据柱添加流畅的动画效果
+- **动画库**: Framer Motion (推荐) - 为图表数据柱添加流畅的动画效果
+- **图表实现**: **自定义React组件** (基于Figma设计,不使用Recharts)
+
+### 图表实现技术决策
+**选择自定义React组件而非Recharts的原因**:
+
+1. **设计还原度**: Figma设计包含复杂的渐变、装饰元素和精确的视觉效果,Recharts难以完全还原
+2. **动画控制**: 自定义组件可以精确控制每个柱形的生长动画、悬停效果和过渡动画
+3. **性能优化**: 针对大屏展示优化,避免Recharts的通用性带来的性能开销
+4. **维护性**: 完全控制组件结构和样式,便于后续定制和维护
+
+**基于Figma设计的组件特点** [Source: docs/战略部署主页面figma-jsx.md#L130-L317]:
+- 每个柱形元素都是独立的React组件
+- 支持多种颜色变体:蓝色渐变、黄色渐变、紫色渐变等
+- 包含复杂的装饰元素和渐变效果
+- 使用绝对定位和精确像素控制
 
 ### 动画效果规范
 **推荐动画库**: Framer Motion (性能优秀,API简洁)

+ 0 - 355
src/client/home/pages/FinancialDashboard/FinancialDashboard.tsx

@@ -1,355 +0,0 @@
-import { useState } from 'react';
-import { useQuery } from '@tanstack/react-query';
-import { AssetMetrics } from './components/AssetMetrics';
-import { ProfitMetrics } from './components/ProfitMetrics';
-import { IncomeMetrics } from './components/IncomeMetrics';
-import { DebtRatioMetrics } from './components/DebtRatioMetrics';
-import { VariationModal } from './components/VariationModal';
-import { dashClient } from '../../../api';
-
-// 图片资源常量 - 这些URL来自Figma设计
-const imgProperty1 = "https://www.figma.com/api/mcp/asset/ad3f1fa8-0cec-4ff9-a24b-f3dea29b0925";
-const imgEllipse4119 = "https://www.figma.com/api/mcp/asset/fcf74522-1000-42fd-8999-d2f9337c522e";
-const img6 = "https://www.figma.com/api/mcp/asset/816963c1-ec66-4f4f-9e3c-8c00047d33dd";
-const img7 = "https://www.figma.com/api/mcp/asset/22bc7622-a7da-401d-9451-5cf73bd1532c";
-const img8 = "https://www.figma.com/api/mcp/asset/4a995039-f8b4-4442-9960-d25cd2d1958f";
-const img9 = "https://www.figma.com/api/mcp/asset/9a551809-608f-48bc-889b-7c0c5823b923";
-const img10 = "https://www.figma.com/api/mcp/asset/ded89b08-063f-4157-9180-3f5db085cd63";
-const img11 = "https://www.figma.com/api/mcp/asset/67c8f7ae-1bee-4847-b616-fa67f0b69404";
-const img12 = "https://www.figma.com/api/mcp/asset/bd7171cc-ccf5-448f-b785-68a5378281d6";
-const img13 = "https://www.figma.com/api/mcp/asset/fab64b95-af76-4ae2-9f5c-6448dca5dd51";
-
-interface FinancialData {
-  code: number;
-  msg: string;
-  rows: Array<{
-    assetTotalNet: Array<{
-      id: number;
-      year: number;
-      assetTotal: number;
-      assetNet: number;
-      dataDeadline: string;
-      createTime: string;
-      updateTime: string;
-    }>;
-    profitTotalNet: Array<{
-      id: number;
-      year: number;
-      profitTotal: number;
-      profitNet: number;
-      dataDeadline: string;
-      createTime: string;
-      updateTime: string;
-    }>;
-    incomeStatement: Array<{
-      id: number;
-      year: number;
-      income: number;
-      dataDeadline: string;
-      createTime: string;
-      updateTime: string;
-    }>;
-    assetLiabilityRatio: Array<{
-      id: number;
-      year: number;
-      assetLiabilityRatio: number;
-      dataDeadline: string;
-      createTime: string;
-      updateTime: string;
-    }>;
-  }>;
-}
-
-export function FinancialDashboard() {
-  const [isModalOpen, setIsModalOpen] = useState(false);
-
-  // 获取财务数据
-  const { data: financialData, isLoading, error } = useQuery({
-    queryKey: ['financial-data'],
-    queryFn: async () => {
-      const response = await dashClient.outlook.$get();
-      return response.json() as Promise<FinancialData>;
-    },
-  });
-
-  const handleOpenModal = () => {
-    setIsModalOpen(true);
-  };
-
-  const handleCloseModal = () => {
-    setIsModalOpen(false);
-  };
-
-  // 加载状态
-  if (isLoading) {
-    return (
-      <div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-white p-6 flex items-center justify-center">
-        <div className="text-center">
-          <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-white mx-auto mb-4"></div>
-          <p className="text-lg">加载财务数据中...</p>
-        </div>
-      </div>
-    );
-  }
-
-  // 错误状态
-  if (error) {
-    return (
-      <div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-white p-6 flex items-center justify-center">
-        <div className="text-center">
-          <div className="text-red-400 text-4xl mb-4">⚠️</div>
-          <h2 className="text-xl font-bold mb-2">数据加载失败</h2>
-          <p className="text-slate-300">无法获取财务数据,请稍后重试</p>
-        </div>
-      </div>
-    );
-  }
-
-  // 数据为空状态
-  if (!financialData || !financialData.rows || financialData.rows.length === 0) {
-    return (
-      <div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-white p-6 flex items-center justify-center">
-        <div className="text-center">
-          <div className="text-slate-400 text-4xl mb-4">📊</div>
-          <h2 className="text-xl font-bold mb-2">暂无财务数据</h2>
-          <p className="text-slate-300">当前没有可显示的财务数据</p>
-        </div>
-      </div>
-    );
-  }
-
-  const financialRow = financialData.rows[0];
-
-  return (
-    <div className="relative w-[1920px] h-[1080px] overflow-hidden">
-      {/* 背景层 */}
-      <div className="absolute inset-0 pointer-events-none">
-        <img
-          alt="背景"
-          className="absolute max-w-none object-cover size-full"
-          src={imgProperty1}
-        />
-        <div className="absolute bg-[rgba(33,33,33,0.6)] inset-0" />
-      </div>
-
-      {/* 头部装饰 */}
-      <div className="absolute h-[74px] left-0 top-px w-[1920px]">
-        {/* 左侧装饰 */}
-        <div className="absolute bottom-[13.48%] left-0 right-[65.73%] top-[-1.32%]">
-          <img alt="装饰左" className="absolute h-[64.5px] left-0 top-0 w-[663px]" src={img6} />
-          <img alt="装饰线条" className="absolute h-[8px] left-[124.5px] top-[27px] w-[301px]" src={img7} />
-          <img alt="装饰细节" className="absolute h-0 left-0 top-[22px] w-[162px]" src={img8} />
-          <img alt="装饰底部" className="absolute h-[22.5px] left-0 top-[42px] w-[661px]" src={img9} />
-        </div>
-
-        {/* 右侧装饰 */}
-        <div className="absolute flex h-[65px] items-center justify-center left-[calc(50%+638px)] top-[calc(50%+-5.48px)] translate-x-[-50%] translate-y-[-50%] w-[658px]">
-          <div className="flex-none rotate-[180deg] scale-y-[-100%]">
-            <div className="h-[65px] relative w-[658px]">
-              <img alt="装饰右" className="absolute h-[64.5px] left-0 top-0 w-[663px]" src={img6} />
-              <img alt="装饰线条右" className="absolute h-[8px] left-[124.5px] top-[27px] w-[301px]" src={img7} />
-              <img alt="装饰细节右" className="absolute h-0 left-0 top-[22px] w-[162px]" src={img8} />
-              <img alt="装饰底部右" className="absolute h-[22.5px] left-0 top-[42px] w-[661px]" src={img9} />
-            </div>
-          </div>
-        </div>
-
-        {/* 标题区域 */}
-        <div className="absolute h-[74px] left-[calc(50%+4px)] top-[calc(50%+-502px)] translate-x-[-50%] translate-y-[-50%] w-[690px]">
-          <div className="absolute bottom-[-12.16%] left-[9.13%] right-[9.13%] top-full">
-            <img alt="发光边" className="absolute inset-[-22.22%_-0.35%]" src={img10} />
-          </div>
-          <div className="absolute bottom-0 left-[0.14%] right-0 top-0">
-            <img alt="标题背景" className="absolute inset-[-10.81%_-3.48%_-16.22%_-3.48%]" src={img11} />
-          </div>
-          <p className="[text-shadow:0px_0px_20px_#041441] absolute bg-clip-text bg-gradient-to-t font-['HarmonyOS_Sans_SC:Regular',sans-serif] from-[#cbced1] inset-[27.03%_36.23%_29.73%_35.94%] leading-[32px] not-italic text-[36px] text-center to-[#ffffff] to-[37.5%] tracking-[3px]" style={{ WebkitTextFillColor: "transparent" }}>
-            战 略 部 署
-          </p>
-          <div className="absolute bottom-0 left-[18.55%] right-[18.26%] top-full">
-            <img alt="顶部导航" className="absolute bottom-[-2px] left-0 right-0 top-[-2px]" src={img12} />
-          </div>
-        </div>
-      </div>
-
-      {/* 四个数据模块网格布局 - 严格按照1920*1080分辨率 */}
-      <div className="absolute content-stretch flex gap-[20px] items-center left-1/2 top-[79.5px] translate-x-[-50%]">
-        {/* 左侧列 */}
-        <div className="content-stretch flex flex-col gap-[16px] items-start relative shrink-0 w-[934.496px]">
-          {/* 资产负债率模块 */}
-          <div className="h-[480px] overflow-clip relative shrink-0 w-full">
-            <div className="absolute h-[490px] left-0 overflow-clip top-0 w-[930px]">
-              <img alt="模块底部" className="absolute h-[490px] left-0 overflow-clip top-0 w-[930px]" src={img13} />
-            </div>
-            <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 w-[93px]">
-                  <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]">
-                        {/* 这里应该放置柱形元素组件 */}
-                      </div>
-                    </div>
-                    <div className="flex flex-col font-['PingFang_SC:Regular',sans-serif] justify-center leading-[0] not-italic 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]">
-                        {/* 这里应该放置柱形元素组件 */}
-                      </div>
-                    </div>
-                    <div className="flex flex-col font-['PingFang_SC:Regular',sans-serif] h-[26px] justify-center leading-[0] not-italic relative shrink-0 text-[18px] text-white w-[80px]">
-                      <p className="leading-[25.2px] whitespace-pre-wrap">资产净额</p>
-                    </div>
-                  </div>
-                </div>
-                <p className="font-['PingFang_SC:Regular',sans-serif] leading-[25.2px] not-italic relative shrink-0 text-[14px] text-[rgba(255,255,255,0.8)]">
-                  *数据截止至2025年9月
-                </p>
-              </div>
-              <p className="font-['PingFang_SC:Regular',sans-serif] leading-[25.2px] not-italic relative shrink-0 text-[14px] text-[rgba(255,255,255,0.8)]">
-                单位:亿元
-              </p>
-              {/* 这里应该放置图表组件 */}
-              <AssetMetrics data={financialRow.assetTotalNet} />
-            </div>
-          </div>
-
-          {/* 收入模块 */}
-          <div className="h-[480px] overflow-clip relative shrink-0 w-full">
-            <div className="absolute h-[490px] left-0 overflow-clip top-0 w-[930px]">
-              <img alt="模块底部" className="absolute h-[490px] left-0 overflow-clip top-0 w-[930px]" src={img13} />
-            </div>
-            <div className="absolute content-stretch flex flex-col gap-[10px] h-[399.296px] items-start left-[calc(50%+-0.25px)] top-[calc(50%+17.65px)] translate-x-[-50%] translate-y-[-50%] w-[847.001px]">
-              <div className="content-stretch flex items-center justify-between relative shrink-0 w-full">
-                <div className="content-stretch flex gap-[20px] items-end relative shrink-0">
-                  <div className="flex items-center justify-center relative shrink-0">
-                    <div className="flex-none rotate-[180deg]">
-                      {/* 这里应该放置柱形元素组件 */}
-                    </div>
-                  </div>
-                  <p className="font-['PingFang_SC:Regular',sans-serif] leading-[25.2px] not-italic relative shrink-0 text-[18px] text-white">
-                    收入
-                  </p>
-                </div>
-                <p className="font-['PingFang_SC:Regular',sans-serif] leading-[25.2px] not-italic relative shrink-0 text-[14px] text-[rgba(255,255,255,0.8)] w-[156px] whitespace-pre-wrap">
-                  *数据截止至2025年9月
-                </p>
-              </div>
-              <p className="font-['PingFang_SC:Regular',sans-serif] h-[26px] leading-[25.2px] not-italic relative shrink-0 text-[14px] text-[rgba(255,255,255,0.8)] w-full whitespace-pre-wrap">
-                单位:亿元
-              </p>
-              {/* 这里应该放置图表组件 */}
-              <IncomeMetrics data={financialRow.incomeStatement} />
-            </div>
-          </div>
-        </div>
-
-        {/* 右侧列 */}
-        <div className="content-stretch flex flex-col gap-[16px] items-start relative shrink-0 w-[934.496px]">
-          {/* 利润总额与净利润模块 */}
-          <div className="h-[480px] overflow-clip relative shrink-0 w-full">
-            <div className="absolute h-[490px] left-0 overflow-clip top-0 w-[930px]">
-              <img alt="模块底部" className="absolute h-[490px] left-0 overflow-clip top-0 w-[930px]" src={img13} />
-            </div>
-            <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]">
-                        {/* 这里应该放置柱形元素组件 */}
-                      </div>
-                    </div>
-                    <div className="flex flex-col font-['PingFang_SC:Regular',sans-serif] justify-center leading-[0] not-italic 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]">
-                        {/* 这里应该放置柱形元素组件 */}
-                      </div>
-                    </div>
-                    <div className="flex flex-col font-['PingFang_SC:Regular',sans-serif] h-[26px] justify-center leading-[0] not-italic relative shrink-0 text-[18px] text-white w-[80px]">
-                      <p className="leading-[25.2px] whitespace-pre-wrap">净利润</p>
-                    </div>
-                  </div>
-                </div>
-                <p className="font-['PingFang_SC:Regular',sans-serif] leading-[25.2px] not-italic relative shrink-0 text-[14px] text-[rgba(255,255,255,0.8)]">
-                  *数据截止至2025年9月
-                </p>
-              </div>
-              <p className="font-['PingFang_SC:Regular',sans-serif] leading-[25.2px] not-italic relative shrink-0 text-[14px] text-[rgba(255,255,255,0.8)]">
-                单位:亿元
-              </p>
-              {/* 这里应该放置图表组件 */}
-              <ProfitMetrics data={financialRow.profitTotalNet} />
-            </div>
-          </div>
-
-          {/* 资产负债率(百分比)模块 */}
-          <div className="h-[480px] overflow-clip relative shrink-0 w-full">
-            <div className="absolute h-[490px] left-0 overflow-clip top-0 w-[930px]">
-              <img alt="模块底部" className="absolute h-[490px] left-0 overflow-clip top-0 w-[930px]" src={img13} />
-            </div>
-            <div className="absolute content-stretch flex flex-col gap-[10px] h-[399.296px] items-start left-[calc(50%+-0.25px)] top-[calc(50%+17.65px)] translate-x-[-50%] translate-y-[-50%] w-[847.001px]">
-              <div className="content-stretch flex items-center justify-between relative shrink-0 w-full">
-                <div className="content-stretch flex gap-[20px] items-center relative shrink-0">
-                  <div className="bg-[#297ad8] box-border content-stretch flex flex-col items-center justify-end pb-0 pt-[74px] px-0 relative shrink-0 size-[14px]">
-                    <div className="bg-[#46bdbd] content-stretch flex h-[7px] items-start justify-center relative shrink-0 w-full">
-                      <div className="border-[0.1px] border-solid border-white box-border content-stretch flex flex-[1_0_0] items-center justify-center min-h-px min-w-px relative shrink-0">
-                        <div className="bg-white border-[0.1px] border-solid border-white flex-[1_0_0] h-[2px] min-h-px min-w-px shrink-0" />
-                      </div>
-                    </div>
-                  </div>
-                  <p className="font-['PingFang_SC:Regular',sans-serif] leading-[25.2px] not-italic relative shrink-0 text-[18px] text-white">
-                    资产负债率
-                  </p>
-                </div>
-                <p className="font-['PingFang_SC:Regular',sans-serif] leading-[25.2px] not-italic relative shrink-0 text-[14px] text-[rgba(255,255,255,0.8)] w-[156px] whitespace-pre-wrap">
-                  *数据截止至2025年9月
-                </p>
-              </div>
-              <p className="font-['PingFang_SC:Regular',sans-serif] h-[26px] leading-[25.2px] not-italic relative shrink-0 text-[14px] text-[rgba(255,255,255,0.8)] w-full whitespace-pre-wrap">
-                单位:%
-              </p>
-              {/* 这里应该放置图表组件 */}
-              <DebtRatioMetrics data={financialRow.assetLiabilityRatio} />
-            </div>
-          </div>
-        </div>
-      </div>
-
-      {/* 右下角浮动按钮 */}
-      <div className="absolute left-[1765px] top-[923px]">
-        <div className="absolute flex items-center justify-center left-[1765px] size-[108px] top-[923px]">
-          <div className="flex-none rotate-[180deg] scale-y-[-100%]">
-            <div className="relative size-[108px]">
-              <div className="absolute aspect-[84/84] bottom-[2.38%] flex items-center justify-center left-1/2 top-[2.38%] translate-x-[-50%]">
-                <div className="flex-none rotate-[180deg] scale-y-[-100%] size-[80px]">
-                  <div className="relative size-full">
-                    <div className="absolute inset-[-1.25%]">
-                      <img alt="按钮背景" className="block max-w-none size-full" src={imgEllipse4119} />
-                    </div>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
-        <button
-          onClick={handleOpenModal}
-          className="absolute font-['PingFang_SC:Medium',sans-serif] h-[51px] leading-[normal] left-[1818.5px] not-italic text-[20px] text-center text-white top-[952px] tracking-[0.6349px] translate-x-[-50%] w-[55px] whitespace-pre-wrap bg-transparent border-none cursor-pointer"
-        >
-          <p className="mb-0">变动</p>
-          <p>幅度</p>
-        </button>
-      </div>
-
-      {/* 变动幅度弹窗 */}
-      <VariationModal isOpen={isModalOpen} onClose={handleCloseModal} />
-    </div>
-  );
-}

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

@@ -1,116 +0,0 @@
-
-interface AssetData {
-  id: number;
-  year: number;
-  assetTotal: number;
-  assetNet: number;
-  dataDeadline: string;
-  createTime: string;
-  updateTime: string;
-}
-
-interface AssetMetricsProps {
-  data: AssetData[];
-}
-
-export function AssetMetrics({ data }: AssetMetricsProps) {
-  // 如果没有数据,显示空状态
-  if (!data || data.length === 0) {
-    return (
-      <div className="h-full flex items-center justify-center">
-        <div className="text-center text-slate-400">
-          <div className="text-2xl mb-2">📊</div>
-          <p>暂无资产数据</p>
-        </div>
-      </div>
-    );
-  }
-
-  // 将数据转换为显示格式,将元转换为亿元
-  const displayData = data.map(item => ({
-    year: `${item.year}年`,
-    totalAssets: item.assetTotal / 100000000, // 转换为亿元
-    netAssets: item.assetNet / 100000000,     // 转换为亿元
-  }));
-
-  const maxValue = Math.max(...displayData.map(d => Math.max(d.totalAssets, d.netAssets)));
-
-  return (
-    <div className="h-full">
-      {/* 模块标题 */}
-      <div className="flex justify-between items-center mb-6">
-        <div className="flex items-center gap-4">
-          <div className="flex items-center gap-2">
-            <div className="w-3 h-3 bg-blue-500 rounded-sm"></div>
-            <span className="text-lg text-white">资产总额</span>
-          </div>
-          <div className="flex items-center gap-2">
-            <div className="w-3 h-3 bg-yellow-500 rounded-sm"></div>
-            <span className="text-lg text-white">资产净额</span>
-          </div>
-        </div>
-        <span className="text-sm text-slate-400">*数据截止至2025年9月</span>
-      </div>
-
-      {/* 单位 */}
-      <div className="text-sm text-slate-400 mb-4">单位:亿元</div>
-
-      {/* 图表区域 */}
-      <div className="relative h-64">
-        {/* Y轴刻度 */}
-        <div className="absolute left-0 top-0 bottom-0 w-12 flex flex-col justify-between text-sm text-slate-400">
-          {[1000, 800, 600, 400, 200, 100, 0].map((value) => (
-            <div key={value} className="text-right pr-2">
-              {value}
-            </div>
-          ))}
-        </div>
-
-        {/* 图表内容 */}
-        <div className="ml-12 h-full relative">
-          {/* 网格线 */}
-          <div className="absolute inset-0 flex flex-col justify-between">
-            {[...Array(7)].map((_, i) => (
-              <div key={i} className="border-t border-slate-600/30"></div>
-            ))}
-          </div>
-
-          {/* 数据条 */}
-          <div className="flex items-end justify-between h-full px-4">
-            {displayData.map((item) => (
-              <div key={item.year} className="flex flex-col items-center gap-2">
-                {/* 数据标签 */}
-                <div className="text-center">
-                  <div className="text-sm text-white">{item.totalAssets}</div>
-                  <div className="text-sm text-white">{item.netAssets}</div>
-                </div>
-
-                {/* 数据条 */}
-                <div className="flex gap-1 items-end">
-                  {/* 资产总额柱状图 */}
-                  <div className="flex flex-col items-center">
-                    <div
-                      className="w-6 bg-gradient-to-b from-blue-500 to-blue-700 rounded-t-sm"
-                      style={{ height: `${(item.totalAssets / maxValue) * 80}%` }}
-                    ></div>
-                  </div>
-
-                  {/* 资产净额柱状图 */}
-                  <div className="flex flex-col items-center">
-                    <div
-                      className="w-6 bg-gradient-to-b from-yellow-500 to-yellow-700 rounded-t-sm"
-                      style={{ height: `${(item.netAssets / maxValue) * 80}%` }}
-                    ></div>
-                  </div>
-                </div>
-
-                {/* 年份标签 */}
-                <div className="text-sm text-slate-400 mt-2">{item.year}</div>
-              </div>
-            ))}
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-}

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

@@ -1,96 +0,0 @@
-
-interface DebtRatioData {
-  id: number;
-  year: number;
-  assetLiabilityRatio: number;
-  dataDeadline: string;
-  createTime: string;
-  updateTime: string;
-}
-
-interface DebtRatioMetricsProps {
-  data: DebtRatioData[];
-}
-
-export function DebtRatioMetrics({ data }: DebtRatioMetricsProps) {
-  // 如果没有数据,显示空状态
-  if (!data || data.length === 0) {
-    return (
-      <div className="h-full flex items-center justify-center">
-        <div className="text-center text-slate-400">
-          <div className="text-2xl mb-2">📊</div>
-          <p>暂无资产负债率数据</p>
-        </div>
-      </div>
-    );
-  }
-
-  // 将数据转换为显示格式
-  const displayData = data.map(item => ({
-    year: `${item.year}年`,
-    ratio: item.assetLiabilityRatio, // 百分比数据,不需要转换
-  }));
-
-  const maxValue = Math.max(...displayData.map(d => d.ratio));
-
-  return (
-    <div className="h-full">
-      {/* 模块标题 */}
-      <div className="flex justify-between items-center mb-6">
-        <div className="flex items-center gap-2">
-          <div className="w-3 h-3 bg-red-500 rounded-sm"></div>
-          <span className="text-lg text-white">资产负债率</span>
-        </div>
-        <span className="text-sm text-slate-400">*数据截止至2025年9月</span>
-      </div>
-
-      {/* 单位 */}
-      <div className="text-sm text-slate-400 mb-4">单位:%</div>
-
-      {/* 图表区域 */}
-      <div className="relative h-64">
-        {/* Y轴刻度 */}
-        <div className="absolute left-0 top-0 bottom-0 w-12 flex flex-col justify-between text-sm text-slate-400">
-          {[100, 80, 60, 40, 20, 0].map((value) => (
-            <div key={value} className="text-right pr-2">
-              {value}
-            </div>
-          ))}
-        </div>
-
-        {/* 图表内容 */}
-        <div className="ml-12 h-full relative">
-          {/* 网格线 */}
-          <div className="absolute inset-0 flex flex-col justify-between">
-            {[...Array(6)].map((_, i) => (
-              <div key={i} className="border-t border-slate-600/30"></div>
-            ))}
-          </div>
-
-          {/* 数据条 */}
-          <div className="flex items-end justify-between h-full px-4">
-            {displayData.map((item) => (
-              <div key={item.year} className="flex flex-col items-center gap-2">
-                {/* 数据标签 */}
-                <div className="bg-gradient-to-b from-red-500 to-red-700 border border-red-400 backdrop-blur-sm px-3 py-2 rounded text-center shadow-lg">
-                  <div className="text-sm font-medium text-white">{item.ratio.toFixed(1)}%</div>
-                </div>
-
-                {/* 数据条 */}
-                <div className="flex flex-col items-center">
-                  <div
-                    className="w-12 bg-gradient-to-b from-red-500 to-red-700 rounded-t-lg"
-                    style={{ height: `${(item.ratio / maxValue) * 80}%` }}
-                  ></div>
-                </div>
-
-                {/* 年份标签 */}
-                <div className="text-sm text-slate-400 mt-2">{item.year}</div>
-              </div>
-            ))}
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-}

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

@@ -1,96 +0,0 @@
-
-interface IncomeData {
-  id: number;
-  year: number;
-  income: number;
-  dataDeadline: string;
-  createTime: string;
-  updateTime: string;
-}
-
-interface IncomeMetricsProps {
-  data: IncomeData[];
-}
-
-export function IncomeMetrics({ data }: IncomeMetricsProps) {
-  // 如果没有数据,显示空状态
-  if (!data || data.length === 0) {
-    return (
-      <div className="h-full flex items-center justify-center">
-        <div className="text-center text-slate-400">
-          <div className="text-2xl mb-2">📊</div>
-          <p>暂无收入数据</p>
-        </div>
-      </div>
-    );
-  }
-
-  // 将数据转换为显示格式,将元转换为亿元
-  const displayData = data.map(item => ({
-    year: `${item.year}年`,
-    income: item.income / 100000000, // 转换为亿元
-  }));
-
-  const maxValue = Math.max(...displayData.map(d => d.income));
-
-  return (
-    <div className="h-full">
-      {/* 模块标题 */}
-      <div className="flex justify-between items-center mb-6">
-        <div className="flex items-center gap-2">
-          <div className="w-3 h-3 bg-green-500 rounded-sm"></div>
-          <span className="text-lg text-white">收入</span>
-        </div>
-        <span className="text-sm text-slate-400">*数据截止至2025年9月</span>
-      </div>
-
-      {/* 单位 */}
-      <div className="text-sm text-slate-400 mb-4">单位:亿元</div>
-
-      {/* 图表区域 */}
-      <div className="relative h-64">
-        {/* Y轴刻度 */}
-        <div className="absolute left-0 top-0 bottom-0 w-12 flex flex-col justify-between text-sm text-slate-400">
-          {[600, 500, 400, 300, 200, 100, 0].map((value) => (
-            <div key={value} className="text-right pr-2">
-              {value}
-            </div>
-          ))}
-        </div>
-
-        {/* 图表内容 */}
-        <div className="ml-12 h-full relative">
-          {/* 网格线 */}
-          <div className="absolute inset-0 flex flex-col justify-between">
-            {[...Array(7)].map((_, i) => (
-              <div key={i} className="border-t border-slate-600/30"></div>
-            ))}
-          </div>
-
-          {/* 数据条 */}
-          <div className="flex items-end justify-between h-full px-4">
-            {displayData.map((item) => (
-              <div key={item.year} className="flex flex-col items-center gap-2">
-                {/* 数据标签 */}
-                <div className="bg-gradient-to-b from-orange-500 to-orange-700 border border-orange-400 backdrop-blur-sm px-3 py-2 rounded text-center shadow-lg">
-                  <div className="text-sm font-medium text-white">{item.income.toFixed(1)}</div>
-                </div>
-
-                {/* 数据条 */}
-                <div className="flex flex-col items-center">
-                  <div
-                    className="w-12 bg-gradient-to-b from-green-500 to-green-700 rounded-t-lg"
-                    style={{ height: `${(item.income / maxValue) * 80}%` }}
-                  ></div>
-                </div>
-
-                {/* 年份标签 */}
-                <div className="text-sm text-slate-400 mt-2">{item.year}</div>
-              </div>
-            ))}
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-}

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

@@ -1,129 +0,0 @@
-
-interface ProfitData {
-  id: number;
-  year: number;
-  profitTotal: number;
-  profitNet: number;
-  dataDeadline: string;
-  createTime: string;
-  updateTime: string;
-}
-
-interface ProfitMetricsProps {
-  data: ProfitData[];
-}
-
-export function ProfitMetrics({ data }: ProfitMetricsProps) {
-  // 如果没有数据,显示空状态
-  if (!data || data.length === 0) {
-    return (
-      <div className="h-full flex items-center justify-center">
-        <div className="text-center text-slate-400">
-          <div className="text-2xl mb-2">📊</div>
-          <p>暂无利润数据</p>
-        </div>
-      </div>
-    );
-  }
-
-  // 将数据转换为显示格式,将元转换为亿元
-  const displayData = data.map(item => ({
-    year: `${item.year}年`,
-    totalProfit: item.profitTotal / 100000000, // 转换为亿元
-    netProfit: item.profitNet / 100000000,     // 转换为亿元
-  }));
-
-  const maxPositive = Math.max(...displayData.map(d => Math.max(d.totalProfit, d.netProfit)).filter(v => v > 0));
-  const maxNegative = Math.min(...displayData.map(d => Math.min(d.totalProfit, d.netProfit)).filter(v => v < 0));
-
-  return (
-    <div className="h-full">
-      {/* 模块标题 */}
-      <div className="flex justify-between items-center mb-6">
-        <div className="flex items-center gap-4">
-          <div className="flex items-center gap-2">
-            <div className="w-3 h-3 bg-cyan-500 rounded-sm"></div>
-            <span className="text-lg text-white">利润总额</span>
-          </div>
-          <div className="flex items-center gap-2">
-            <div className="w-3 h-3 bg-purple-500 rounded-sm"></div>
-            <span className="text-lg text-white">净利润</span>
-          </div>
-        </div>
-        <span className="text-sm text-slate-400">*数据截止至2025年9月</span>
-      </div>
-
-      {/* 单位 */}
-      <div className="text-sm text-slate-400 mb-4">单位:亿元</div>
-
-      {/* 图表区域 */}
-      <div className="relative h-64">
-        {/* Y轴刻度 */}
-        <div className="absolute left-0 top-0 bottom-0 w-12 flex flex-col justify-between text-sm text-slate-400">
-          {[2, 1.5, 1, 0.75, 0.5, 0.25, 0, -1, -2].map((value) => (
-            <div key={value} className="text-right pr-2">
-              {value}
-            </div>
-          ))}
-        </div>
-
-        {/* 图表内容 */}
-        <div className="ml-12 h-full relative">
-          {/* 网格线 */}
-          <div className="absolute inset-0 flex flex-col justify-between">
-            {[...Array(9)].map((_, i) => (
-              <div key={i} className="border-t border-slate-600/30"></div>
-            ))}
-          </div>
-
-          {/* 数据条 */}
-          <div className="flex items-end justify-between h-full px-4">
-            {displayData.map((item) => (
-              <div key={item.year} className="flex flex-col items-center gap-2">
-                {/* 数据标签 */}
-                <div className="text-center">
-                  <div className="text-sm text-white">{item.totalProfit}</div>
-                  <div className="text-sm text-white">{item.netProfit}</div>
-                </div>
-
-                {/* 数据条 */}
-                <div className="flex gap-2 items-end">
-                  {/* 利润总额柱状图 */}
-                  <div className="flex flex-col items-center">
-                    <div
-                      className={`w-8 rounded-t-sm ${
-                        item.totalProfit >= 0
-                          ? 'bg-gradient-to-b from-cyan-500 to-cyan-700'
-                          : 'bg-gradient-to-b from-red-500 to-red-700'
-                      }`}
-                      style={{
-                        height: `${Math.abs(item.totalProfit) / Math.max(maxPositive, Math.abs(maxNegative)) * 40}%`
-                      }}
-                    ></div>
-                  </div>
-
-                  {/* 净利润柱状图 */}
-                  <div className="flex flex-col items-center">
-                    <div
-                      className={`w-8 rounded-t-sm ${
-                        item.netProfit >= 0
-                          ? 'bg-gradient-to-b from-purple-500 to-purple-700'
-                          : 'bg-gradient-to-b from-red-500 to-red-700'
-                      }`}
-                      style={{
-                        height: `${Math.abs(item.netProfit) / Math.max(maxPositive, Math.abs(maxNegative)) * 40}%`
-                      }}
-                    ></div>
-                  </div>
-                </div>
-
-                {/* 年份标签 */}
-                <div className="text-sm text-slate-400 mt-2">{item.year}</div>
-              </div>
-            ))}
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-}

+ 0 - 80
src/client/home/pages/FinancialDashboard/components/VariationModal.tsx

@@ -1,80 +0,0 @@
-
-interface VariationModalProps {
-  isOpen: boolean;
-  onClose: () => void;
-}
-
-export function VariationModal({ isOpen, onClose }: VariationModalProps) {
-  // 如果弹窗未打开,不渲染任何内容
-  if (!isOpen) {
-    return null;
-  }
-
-  // 示例数据 - 实际应该从API获取
-  const variationData = [
-    { metric: '资产总额', value: '+15.2%', color: 'text-blue-400' },
-    { metric: '资产净额', value: '+12.8%', color: 'text-yellow-400' },
-    { metric: '利润总额', value: '+8.5%', color: 'text-cyan-400' },
-    { metric: '净利润', value: '+7.2%', color: 'text-purple-400' },
-    { metric: '收入', value: '+18.3%', color: 'text-green-400' },
-    { metric: '资产负债率', value: '-2.1%', color: 'text-red-400' },
-  ];
-
-  return (
-    <>
-      {/* 1. 遮罩层 - 提供视觉隔离和点击外部关闭功能 */}
-      <div
-        className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50"
-        onClick={onClose}
-      />
-
-      {/* 2. 弹出框容器 - 弹窗主体容器 */}
-      <div className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-50 w-full max-w-2xl">
-        <div className="bg-slate-800/90 backdrop-blur-md border border-slate-600/50 rounded-xl shadow-2xl">
-
-          {/* 3. 弹出框数据 - 弹窗内容区域 */}
-          <div className="p-8">
-            {/* 标题 */}
-            <div className="text-center mb-8">
-              <h2 className="text-2xl font-bold text-white mb-2">
-                财务指标变动幅度
-              </h2>
-              <p className="text-slate-400">
-                2024年与2023年相比的变动情况
-              </p>
-            </div>
-
-            {/* 数据网格 */}
-            <div className="grid grid-cols-2 gap-6">
-              {variationData.map((item) => (
-                <div
-                  key={item.metric}
-                  className="bg-slate-700/50 backdrop-blur-sm rounded-lg p-4 border border-slate-600/30"
-                >
-                  <div className="text-center">
-                    <div className="text-lg font-medium text-white mb-2">
-                      {item.metric}
-                    </div>
-                    <div className={`text-2xl font-bold ${item.color}`}>
-                      {item.value}
-                    </div>
-                  </div>
-                </div>
-              ))}
-            </div>
-
-            {/* 4. 返回按钮 - 弹窗操作控件 */}
-            <div className="flex justify-center mt-8">
-              <button
-                onClick={onClose}
-                className="bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 rounded-lg font-medium transition-colors duration-200 shadow-lg hover:shadow-xl"
-              >
-                返回
-              </button>
-            </div>
-          </div>
-        </div>
-      </div>
-    </>
-  );
-}