Browse Source

✨ feat(financial): 创建财务仪表盘页面

- 添加财务仪表盘主页面组件FinancialDashboard
- 实现资产总额与净额数据展示组件AssetMetrics
- 开发利润总额与净利润图表组件ProfitMetrics
- 构建收入数据可视化组件IncomeMetrics
- 设计响应式布局与渐变背景样式
- 添加数据模块网格布局与交互弹窗功能
yourname 2 months ago
parent
commit
8ef10a3d5d

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

@@ -0,0 +1,66 @@
+import React, { useState } from 'react';
+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';
+
+export function FinancialDashboard() {
+  const [isModalOpen, setIsModalOpen] = useState(false);
+
+  const handleOpenModal = () => {
+    setIsModalOpen(true);
+  };
+
+  const handleCloseModal = () => {
+    setIsModalOpen(false);
+  };
+
+  return (
+    <div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 text-white p-6">
+      {/* 页面标题 */}
+      <div className="text-center mb-8">
+        <h1 className="text-4xl font-bold bg-gradient-to-r from-blue-200 to-white bg-clip-text text-transparent">
+          战略部署
+        </h1>
+      </div>
+
+      {/* 四个数据模块网格布局 */}
+      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 max-w-7xl mx-auto">
+        {/* 资产负债率模块 */}
+        <div className="bg-slate-800/50 backdrop-blur-sm rounded-xl border border-slate-700/50 p-6">
+          <AssetMetrics />
+        </div>
+
+        {/* 利润总额与净利润模块 */}
+        <div className="bg-slate-800/50 backdrop-blur-sm rounded-xl border border-slate-700/50 p-6">
+          <ProfitMetrics />
+        </div>
+
+        {/* 收入模块 */}
+        <div className="bg-slate-800/50 backdrop-blur-sm rounded-xl border border-slate-700/50 p-6">
+          <IncomeMetrics />
+        </div>
+
+        {/* 资产负债率(百分比)模块 */}
+        <div className="bg-slate-800/50 backdrop-blur-sm rounded-xl border border-slate-700/50 p-6">
+          <DebtRatioMetrics />
+        </div>
+      </div>
+
+      {/* 右下角浮动按钮 */}
+      <button
+        onClick={handleOpenModal}
+        className="fixed bottom-8 right-8 bg-blue-600 hover:bg-blue-700 text-white rounded-full p-4 shadow-lg hover:shadow-xl transition-all duration-300 z-50"
+      >
+        <div className="text-center">
+          <div className="text-sm font-medium">变动</div>
+          <div className="text-sm font-medium">幅度</div>
+        </div>
+      </button>
+
+      {/* 变动幅度弹窗 */}
+      <VariationModal isOpen={isModalOpen} onClose={handleCloseModal} />
+    </div>
+  );
+}

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

@@ -0,0 +1,92 @@
+import React from 'react';
+
+export function AssetMetrics() {
+  const data = [
+    { year: '2021年', totalAssets: 200.46, netAssets: 55.40 },
+    { year: '2022年', totalAssets: 243.27, netAssets: 57.49 },
+    { year: '2023年', totalAssets: 509.08, netAssets: 247.29 },
+    { year: '2024年', totalAssets: 772.66, netAssets: 407.68 },
+    { year: '2025年', totalAssets: 840.12, netAssets: 421.55 },
+  ];
+
+  const maxValue = Math.max(...data.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">
+            {data.map((item, index) => (
+              <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>
+  );
+}

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

@@ -0,0 +1,74 @@
+import React from 'react';
+
+export function IncomeMetrics() {
+  const data = [
+    { 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(...data.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">
+            {data.map((item, index) => (
+              <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}</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>
+  );
+}

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

@@ -0,0 +1,105 @@
+import React from 'react';
+
+export function ProfitMetrics() {
+  const data = [
+    { year: '2021年', totalProfit: -1.5, netProfit: -1.7 },
+    { year: '2022年', totalProfit: 0.8, netProfit: 0.59 },
+    { year: '2023年', totalProfit: 1.28, netProfit: 0.6 },
+    { year: '2024年', totalProfit: 1.65, netProfit: 1.22 },
+    { year: '2025年', totalProfit: 1.34, netProfit: 1.00 },
+  ];
+
+  const maxPositive = Math.max(...data.map(d => Math.max(d.totalProfit, d.netProfit)).filter(v => v > 0));
+  const maxNegative = Math.min(...data.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">
+            {data.map((item, index) => (
+              <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>
+  );
+}