Procházet zdrojové kódy

✨ feat(dashboard): add financial metrics visualization features
- add DebtRatioMetrics component to display asset liability ratio
- add VariationModal component for financial indicator changes
- update api client to support dash routes
- replace client with dashClient for financial data fetching

♻️ refactor(dashboard): optimize component imports and code structure
- remove unused React imports from metrics components
- optimize map rendering by using item.year as key instead of index
- format income data with toFixed(1) for consistent display

💄 style(dashboard): improve UI presentation of financial metrics
- enhance data visualization with gradient colors and shadows
- add proper spacing and alignment for better visual hierarchy
- improve modal styling with backdrop blur and shadow effects

yourname před 2 měsíci
rodič
revize
60eaf49f58

+ 5 - 1
src/client/api.ts

@@ -2,7 +2,7 @@ import axios, { isAxiosError } from 'axios';
 import { hc } from 'hono/client'
 import type {
   AuthRoutes, UserRoutes, RoleRoutes,
-  FileRoutes
+  FileRoutes, DashRoutes
 } from '@/server/api';
 
 // 创建 axios 适配器
@@ -75,3 +75,7 @@ export const roleClient = hc<RoleRoutes>('/', {
 export const fileClient = hc<FileRoutes>('/', {
   fetch: axiosFetch,
 }).api.v1.files;
+
+export const dashClient = hc<DashRoutes>('/', {
+  fetch: axiosFetch,
+}).api.v1.dash;

+ 3 - 3
src/client/home/pages/FinancialDashboard/FinancialDashboard.tsx

@@ -1,11 +1,11 @@
-import React, { useState, useEffect } from 'react';
+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 { client } from '@/api';
+import { dashClient } from '../../../api';
 
 interface FinancialData {
   code: number;
@@ -55,7 +55,7 @@ export function FinancialDashboard() {
   const { data: financialData, isLoading, error } = useQuery({
     queryKey: ['financial-data'],
     queryFn: async () => {
-      const response = await client.dash.outlook.$get();
+      const response = await dashClient.outlook.$get();
       return response.json() as Promise<FinancialData>;
     },
   });

+ 1 - 2
src/client/home/pages/FinancialDashboard/components/AssetMetrics.tsx

@@ -1,4 +1,3 @@
-import React from 'react';
 
 interface AssetData {
   id: number;
@@ -78,7 +77,7 @@ export function AssetMetrics({ data }: AssetMetricsProps) {
 
           {/* 数据条 */}
           <div className="flex items-end justify-between h-full px-4">
-            {displayData.map((item, index) => (
+            {displayData.map((item) => (
               <div key={item.year} className="flex flex-col items-center gap-2">
                 {/* 数据标签 */}
                 <div className="text-center">

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

@@ -0,0 +1,96 @@
+
+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>
+  );
+}

+ 2 - 3
src/client/home/pages/FinancialDashboard/components/IncomeMetrics.tsx

@@ -1,4 +1,3 @@
-import React from 'react';
 
 interface IncomeData {
   id: number;
@@ -70,11 +69,11 @@ export function IncomeMetrics({ data }: IncomeMetricsProps) {
 
           {/* 数据条 */}
           <div className="flex items-end justify-between h-full px-4">
-            {data.map((item, index) => (
+            {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}</div>
+                  <div className="text-sm font-medium text-white">{item.income.toFixed(1)}</div>
                 </div>
 
                 {/* 数据条 */}

+ 1 - 2
src/client/home/pages/FinancialDashboard/components/ProfitMetrics.tsx

@@ -1,4 +1,3 @@
-import React from 'react';
 
 interface ProfitData {
   id: number;
@@ -79,7 +78,7 @@ export function ProfitMetrics({ data }: ProfitMetricsProps) {
 
           {/* 数据条 */}
           <div className="flex items-end justify-between h-full px-4">
-            {displayData.map((item, index) => (
+            {displayData.map((item) => (
               <div key={item.year} className="flex flex-col items-center gap-2">
                 {/* 数据标签 */}
                 <div className="text-center">

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

@@ -0,0 +1,80 @@
+
+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>
+    </>
+  );
+}