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

🎨 refactor: Refactor dashboard statistics cards and charts layout

- Consolidate 8 individual stat cards into 4 grouped cards:
  * Account Data (Current Balance, Historical Consumption)
  * Usage Statistics (Request Count, Statistics Count)
  * Resource Consumption (Statistics Quota, Statistics Tokens)
  * Performance Metrics (Average RPM, Average TPM)

- Add gradient header backgrounds with white text for card titles:
  * Blue gradient for Account Data
  * Green gradient for Usage Statistics
  * Yellow gradient for Resource Consumption
  * Pink gradient for Performance Metrics

- Implement mini trend charts using real API data:
  * Replace mock data with actual time-series data from API
  * Hide x and y axes to show pure trend lines
  * Display trends only for metrics with available historical data
  * Remove trend charts for Current Balance, Historical Consumption, and Request Count

- Merge model analysis charts into single card:
  * Combine "Model Consumption Distribution" and "Model Call Count Ratio"
  * Use responsive grid layout (vertical on mobile, horizontal on desktop)
  * Update card title to "Model Data Analysis"

- Optimize chart configurations:
  * Hide axes, legends, and tooltips for mini trend charts
  * Maintain color consistency between metrics and trend lines
  * Improve performance by processing all trend data in single API call
Apple\Apple 9 месяцев назад
Родитель
Сommit
1c3cd7984c

+ 2 - 1
web/src/components/table/ChannelsTable.js

@@ -1638,7 +1638,8 @@ const ChannelsTable = () => {
       <Card
       <Card
         className="!rounded-2xl overflow-hidden"
         className="!rounded-2xl overflow-hidden"
         title={renderHeader()}
         title={renderHeader()}
-        shadows='hover'
+        shadows='always'
+        bordered={false}
       >
       >
         <Table
         <Table
           columns={getVisibleColumns()}
           columns={getVisibleColumns()}

+ 2 - 1
web/src/components/table/LogsTable.js

@@ -1259,7 +1259,8 @@ const LogsTable = () => {
             </div>
             </div>
           </div>
           </div>
         }
         }
-        shadows='hover'
+        shadows='always'
+        bordered={false}
       >
       >
         <Table
         <Table
           columns={getVisibleColumns()}
           columns={getVisibleColumns()}

+ 2 - 1
web/src/components/table/MjLogsTable.js

@@ -864,7 +864,8 @@ const LogsTable = () => {
               </div>
               </div>
             </div>
             </div>
           }
           }
-          shadows='hover'
+          shadows='always'
+          bordered={false}
         >
         >
           <Table
           <Table
             columns={getVisibleColumns()}
             columns={getVisibleColumns()}

+ 3 - 3
web/src/components/table/ModelPricing.js

@@ -241,7 +241,7 @@ const ModelPricing = () => {
           let price = parseFloat(text) * groupRatio[selectedGroup];
           let price = parseFloat(text) * groupRatio[selectedGroup];
           content = (
           content = (
             <div className="text-gray-700">
             <div className="text-gray-700">
-              ${t('模型价格')}:${price.toFixed(3)}
+              {t('模型价格')}:${price.toFixed(3)}
             </div>
             </div>
           );
           );
         }
         }
@@ -451,7 +451,7 @@ const ModelPricing = () => {
 
 
   // 搜索和操作区组件
   // 搜索和操作区组件
   const SearchAndActions = useMemo(() => (
   const SearchAndActions = useMemo(() => (
-    <Card className="!rounded-xl mb-6" shadows='hover'>
+    <Card className="!rounded-xl mb-6" bordered={false}>
       <div className="flex flex-wrap items-center gap-4">
       <div className="flex flex-wrap items-center gap-4">
         <div className="flex-1 min-w-[200px]">
         <div className="flex-1 min-w-[200px]">
           <Input
           <Input
@@ -482,7 +482,7 @@ const ModelPricing = () => {
 
 
   // 表格组件
   // 表格组件
   const ModelTable = useMemo(() => (
   const ModelTable = useMemo(() => (
-    <Card className="!rounded-xl overflow-hidden" shadows='hover'>
+    <Card className="!rounded-xl overflow-hidden" bordered={false}>
       <Table
       <Table
         columns={columns}
         columns={columns}
         dataSource={filteredModels}
         dataSource={filteredModels}

+ 2 - 1
web/src/components/table/RedemptionsTable.js

@@ -501,7 +501,8 @@ const RedemptionsTable = () => {
       <Card
       <Card
         className="!rounded-2xl overflow-hidden"
         className="!rounded-2xl overflow-hidden"
         title={renderHeader()}
         title={renderHeader()}
-        shadows='hover'
+        shadows='always'
+        bordered={false}
       >
       >
         <Table
         <Table
           columns={columns}
           columns={columns}

+ 2 - 1
web/src/components/table/TaskLogsTable.js

@@ -702,7 +702,8 @@ const LogsTable = () => {
               </div>
               </div>
             </div>
             </div>
           }
           }
-          shadows='hover'
+          shadows='always'
+          bordered={false}
         >
         >
           <Table
           <Table
             columns={getVisibleColumns()}
             columns={getVisibleColumns()}

+ 4 - 94
web/src/components/table/TokensTable.js

@@ -1,5 +1,4 @@
-import React, { useEffect, useState, useContext } from 'react';
-import { useNavigate } from 'react-router-dom';
+import React, { useEffect, useState } from 'react';
 import {
 import {
   API,
   API,
   copy,
   copy,
@@ -21,8 +20,6 @@ import {
   Table,
   Table,
   Tag,
   Tag,
   Input,
   Input,
-  Divider,
-  Avatar,
 } from '@douyinfe/semi-ui';
 } from '@douyinfe/semi-ui';
 
 
 import {
 import {
@@ -36,13 +33,9 @@ import {
   IconStop,
   IconStop,
   IconPlay,
   IconPlay,
   IconMore,
   IconMore,
-  IconMoneyExchangeStroked,
-  IconHistogram,
-  IconRotate,
 } from '@douyinfe/semi-icons';
 } from '@douyinfe/semi-icons';
 import EditToken from '../../pages/Token/EditToken';
 import EditToken from '../../pages/Token/EditToken';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
-import { UserContext } from '../../context/User';
 
 
 function renderTimestamp(timestamp) {
 function renderTimestamp(timestamp) {
   return <>{timestamp2string(timestamp)}</>;
   return <>{timestamp2string(timestamp)}</>;
@@ -50,8 +43,6 @@ function renderTimestamp(timestamp) {
 
 
 const TokensTable = () => {
 const TokensTable = () => {
   const { t } = useTranslation();
   const { t } = useTranslation();
-  const navigate = useNavigate();
-  const [userState, userDispatch] = useContext(UserContext);
 
 
   const renderStatus = (status, model_limits_enabled = false) => {
   const renderStatus = (status, model_limits_enabled = false) => {
     switch (status) {
     switch (status) {
@@ -431,26 +422,9 @@ const TokensTable = () => {
     window.open(url, '_blank');
     window.open(url, '_blank');
   };
   };
 
 
-  // 获取用户数据
-  const getUserData = async () => {
-    try {
-      const res = await API.get(`/api/user/self`);
-      const { success, message, data } = res.data;
-      if (success) {
-        userDispatch({ type: 'login', payload: data });
-      } else {
-        showError(message);
-      }
-    } catch (error) {
-      console.error('获取用户数据失败:', error);
-      showError(t('获取用户数据失败'));
-    }
-  };
 
 
-  useEffect(() => {
-    // 获取用户数据以确保显示正确的余额和使用量
-    getUserData();
 
 
+  useEffect(() => {
     loadTokens(0)
     loadTokens(0)
       .then()
       .then()
       .catch((reason) => {
       .catch((reason) => {
@@ -574,71 +548,6 @@ const TokensTable = () => {
 
 
   const renderHeader = () => (
   const renderHeader = () => (
     <div className="flex flex-col w-full">
     <div className="flex flex-col w-full">
-      <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
-        <Card
-          shadows='hover'
-          className="bg-blue-50 border-0 !rounded-2xl w-full"
-          headerLine={false}
-          onClick={() => navigate('/console/topup')}
-        >
-          <div className="flex items-center">
-            <Avatar
-              className="mr-3"
-              size="medium"
-              color="blue"
-            >
-              <IconMoneyExchangeStroked size="large" />
-            </Avatar>
-            <div>
-              <div className="text-sm text-gray-500">{t('当前余额')}</div>
-              <div className="text-xl font-semibold">{renderQuota(userState?.user?.quota)}</div>
-            </div>
-          </div>
-        </Card>
-
-        <Card
-          shadows='hover'
-          className="bg-purple-50 border-0 !rounded-2xl w-full"
-          headerLine={false}
-        >
-          <div className="flex items-center">
-            <Avatar
-              className="mr-3"
-              size="medium"
-              color="purple"
-            >
-              <IconHistogram size="large" />
-            </Avatar>
-            <div>
-              <div className="text-sm text-gray-500">{t('累计消费')}</div>
-              <div className="text-xl font-semibold">{renderQuota(userState?.user?.used_quota)}</div>
-            </div>
-          </div>
-        </Card>
-
-        <Card
-          shadows='hover'
-          className="bg-green-50 border-0 !rounded-2xl w-full"
-          headerLine={false}
-        >
-          <div className="flex items-center">
-            <Avatar
-              className="mr-3"
-              size="medium"
-              color="green"
-            >
-              <IconRotate size="large" />
-            </Avatar>
-            <div>
-              <div className="text-sm text-gray-500">{t('请求次数')}</div>
-              <div className="text-xl font-semibold">{userState?.user?.request_count || 0}</div>
-            </div>
-          </div>
-        </Card>
-      </div>
-
-      <Divider margin="12px" />
-
       <div className="flex flex-col md:flex-row justify-between items-center gap-4 w-full">
       <div className="flex flex-col md:flex-row justify-between items-center gap-4 w-full">
         <div className="flex gap-2 w-full md:w-auto order-2 md:order-1">
         <div className="flex gap-2 w-full md:w-auto order-2 md:order-1">
           <Button
           <Button
@@ -723,7 +632,8 @@ const TokensTable = () => {
       <Card
       <Card
         className="!rounded-2xl overflow-hidden"
         className="!rounded-2xl overflow-hidden"
         title={renderHeader()}
         title={renderHeader()}
-        shadows='hover'
+        shadows='always'
+        bordered={false}
       >
       >
         <Table
         <Table
           columns={columns}
           columns={columns}

+ 2 - 1
web/src/components/table/UsersTable.js

@@ -551,7 +551,8 @@ const UsersTable = () => {
       <Card
       <Card
         className="!rounded-2xl overflow-hidden"
         className="!rounded-2xl overflow-hidden"
         title={renderHeader()}
         title={renderHeader()}
-        shadows='hover'
+        shadows='always'
+        bordered={false}
       >
       >
         <Table
         <Table
           columns={columns}
           columns={columns}

+ 6 - 1
web/src/i18n/locales/en.json

@@ -1551,5 +1551,10 @@
   "提供基础功能演示,方便用户了解系统特性。": "Provide basic feature demonstrations to help users understand the system features.",
   "提供基础功能演示,方便用户了解系统特性。": "Provide basic feature demonstrations to help users understand the system features.",
   "适用于为多个用户提供服务的场景": "Suitable for scenarios where multiple users are provided.",
   "适用于为多个用户提供服务的场景": "Suitable for scenarios where multiple users are provided.",
   "适用于个人使用的场景,不需要设置模型价格": "Suitable for personal use, no need to set model price.",
   "适用于个人使用的场景,不需要设置模型价格": "Suitable for personal use, no need to set model price.",
-  "适用于展示系统功能的场景,提供基础功能演示": "Suitable for scenarios where the system functions are displayed, providing basic feature demonstrations."
+  "适用于展示系统功能的场景,提供基础功能演示": "Suitable for scenarios where the system functions are displayed, providing basic feature demonstrations.",
+  "账户数据": "Account Data",
+  "使用统计": "Usage Statistics",
+  "资源消耗": "Resource Consumption",
+  "性能指标": "Performance Indicators",
+  "模型数据分析": "Model Data Analysis"
 }
 }

+ 272 - 92
web/src/pages/Detail/index.js

@@ -208,6 +208,55 @@ const Detail = (props) => {
   // 添加一个新的状态来存储模型-颜色映射
   // 添加一个新的状态来存储模型-颜色映射
   const [modelColors, setModelColors] = useState({});
   const [modelColors, setModelColors] = useState({});
 
 
+  // 添加趋势数据状态
+  const [trendData, setTrendData] = useState({
+    balance: [],
+    usedQuota: [],
+    requestCount: [],
+    times: [],
+    consumeQuota: [],
+    tokens: [],
+    rpm: [],
+    tpm: []
+  });
+
+  // 迷你趋势图配置
+  const getTrendSpec = (data, color) => ({
+    type: 'line',
+    data: [{ id: 'trend', values: data.map((val, idx) => ({ x: idx, y: val })) }],
+    xField: 'x',
+    yField: 'y',
+    height: 40,
+    width: 100,
+    axes: [
+      {
+        orient: 'bottom',
+        visible: false
+      },
+      {
+        orient: 'left',
+        visible: false
+      }
+    ],
+    padding: 0,
+    autoFit: false,
+    legends: { visible: false },
+    tooltip: { visible: false },
+    crosshair: { visible: false },
+    line: {
+      style: {
+        stroke: color,
+        lineWidth: 2
+      }
+    },
+    point: {
+      visible: false
+    },
+    background: {
+      fill: 'transparent'
+    }
+  });
+
   // 显示搜索Modal
   // 显示搜索Modal
   const showSearchModal = () => {
   const showSearchModal = () => {
     setSearchModalVisible(true);
     setSearchModalVisible(true);
@@ -282,12 +331,75 @@ const Detail = (props) => {
     let uniqueModels = new Set();
     let uniqueModels = new Set();
     let totalTokens = 0;
     let totalTokens = 0;
 
 
-    // 收集所有唯一的模型名称
+    // 趋势数据处理
+    let timePoints = [];
+    let timeQuotaMap = new Map();
+    let timeTokensMap = new Map();
+    let timeCountMap = new Map();
+
+    // 收集所有唯一的模型名称和时间点
     data.forEach((item) => {
     data.forEach((item) => {
       uniqueModels.add(item.model_name);
       uniqueModels.add(item.model_name);
       totalTokens += item.token_used;
       totalTokens += item.token_used;
       totalQuota += item.quota;
       totalQuota += item.quota;
       totalTimes += item.count;
       totalTimes += item.count;
+
+      // 记录时间点
+      const timeKey = timestamp2string1(item.created_at, dataExportDefaultTime);
+      if (!timePoints.includes(timeKey)) {
+        timePoints.push(timeKey);
+      }
+
+      // 按时间点累加数据
+      if (!timeQuotaMap.has(timeKey)) {
+        timeQuotaMap.set(timeKey, 0);
+        timeTokensMap.set(timeKey, 0);
+        timeCountMap.set(timeKey, 0);
+      }
+      timeQuotaMap.set(timeKey, timeQuotaMap.get(timeKey) + item.quota);
+      timeTokensMap.set(timeKey, timeTokensMap.get(timeKey) + item.token_used);
+      timeCountMap.set(timeKey, timeCountMap.get(timeKey) + item.count);
+    });
+
+    // 确保时间点有序
+    timePoints.sort();
+
+    // 生成趋势数据
+    const quotaTrend = timePoints.map(time => timeQuotaMap.get(time) || 0);
+    const tokensTrend = timePoints.map(time => timeTokensMap.get(time) || 0);
+    const countTrend = timePoints.map(time => timeCountMap.get(time) || 0);
+
+    // 计算RPM和TPM趋势
+    const rpmTrend = [];
+    const tpmTrend = [];
+
+    if (timePoints.length >= 2) {
+      const interval = dataExportDefaultTime === 'hour'
+        ? 60 // 分钟/小时
+        : dataExportDefaultTime === 'day'
+          ? 1440 // 分钟/天
+          : 10080; // 分钟/周
+
+      for (let i = 0; i < timePoints.length; i++) {
+        rpmTrend.push(timeCountMap.get(timePoints[i]) / interval);
+        tpmTrend.push(timeTokensMap.get(timePoints[i]) / interval);
+      }
+    }
+
+    // 更新趋势数据状态
+    setTrendData({
+      // 账户数据不在API返回中,保持空数组
+      balance: [],
+      usedQuota: [],
+      // 使用统计
+      requestCount: [], // 没有总请求次数趋势数据
+      times: countTrend,
+      // 资源消耗
+      consumeQuota: quotaTrend,
+      tokens: tokensTrend,
+      // 性能指标
+      rpm: rpmTrend,
+      tpm: tpmTrend
     });
     });
 
 
     // 处理颜色映射
     // 处理颜色映射
@@ -336,10 +448,10 @@ const Detail = (props) => {
     }));
     }));
 
 
     // 生成时间点序列
     // 生成时间点序列
-    let timePoints = Array.from(
+    let chartTimePoints = Array.from(
       new Set([...aggregatedData.values()].map((d) => d.time)),
       new Set([...aggregatedData.values()].map((d) => d.time)),
     );
     );
-    if (timePoints.length < 7) {
+    if (chartTimePoints.length < 7) {
       const lastTime = Math.max(...data.map((item) => item.created_at));
       const lastTime = Math.max(...data.map((item) => item.created_at));
       const interval =
       const interval =
         dataExportDefaultTime === 'hour'
         dataExportDefaultTime === 'hour'
@@ -348,13 +460,13 @@ const Detail = (props) => {
             ? 86400
             ? 86400
             : 604800;
             : 604800;
 
 
-      timePoints = Array.from({ length: 7 }, (_, i) =>
+      chartTimePoints = Array.from({ length: 7 }, (_, i) =>
         timestamp2string1(lastTime - (6 - i) * interval, dataExportDefaultTime),
         timestamp2string1(lastTime - (6 - i) * interval, dataExportDefaultTime),
       );
       );
     }
     }
 
 
     // 生成柱状图数据
     // 生成柱状图数据
-    timePoints.forEach((time) => {
+    chartTimePoints.forEach((time) => {
       // 为每个时间点收集所有模型的数据
       // 为每个时间点收集所有模型的数据
       let timeData = Array.from(uniqueModels).map((model) => {
       let timeData = Array.from(uniqueModels).map((model) => {
         const key = `${time}-${model}`;
         const key = `${time}-${model}`;
@@ -441,71 +553,103 @@ const Detail = (props) => {
   }, []);
   }, []);
 
 
   // 数据卡片信息
   // 数据卡片信息
-  const statsData = [
+  const groupedStatsData = [
     {
     {
-      title: t('当前余额'),
-      value: renderQuota(userState?.user?.quota),
-      icon: <IconMoneyExchangeStroked size="large" />,
+      title: t('账户数据'),
       color: 'bg-blue-50',
       color: 'bg-blue-50',
-      avatarColor: 'blue',
-      onClick: () => navigate('/console/topup'),
-    },
-    {
-      title: t('历史消耗'),
-      value: renderQuota(userState?.user?.used_quota),
-      icon: <IconHistogram size="large" />,
-      color: 'bg-purple-50',
-      avatarColor: 'purple',
+      items: [
+        {
+          title: t('当前余额'),
+          value: renderQuota(userState?.user?.quota),
+          icon: <IconMoneyExchangeStroked size="large" />,
+          avatarColor: 'blue',
+          onClick: () => navigate('/console/topup'),
+          trendData: [], // 当前余额没有趋势数据
+          trendColor: '#3b82f6'
+        },
+        {
+          title: t('历史消耗'),
+          value: renderQuota(userState?.user?.used_quota),
+          icon: <IconHistogram size="large" />,
+          avatarColor: 'purple',
+          trendData: [], // 历史消耗没有趋势数据
+          trendColor: '#8b5cf6'
+        }
+      ]
     },
     },
     {
     {
-      title: t('请求次数'),
-      value: userState.user?.request_count,
-      icon: <IconRotate size="large" />,
+      title: t('使用统计'),
       color: 'bg-green-50',
       color: 'bg-green-50',
-      avatarColor: 'green',
+      items: [
+        {
+          title: t('请求次数'),
+          value: userState.user?.request_count,
+          icon: <IconRotate size="large" />,
+          avatarColor: 'green',
+          trendData: [], // 请求次数没有趋势数据
+          trendColor: '#10b981'
+        },
+        {
+          title: t('统计次数'),
+          value: times,
+          icon: <IconPulse size="large" />,
+          avatarColor: 'cyan',
+          trendData: trendData.times,
+          trendColor: '#06b6d4'
+        }
+      ]
     },
     },
     {
     {
-      title: t('统计额度'),
-      value: renderQuota(consumeQuota),
-      icon: <IconCoinMoneyStroked size="large" />,
+      title: t('资源消耗'),
       color: 'bg-yellow-50',
       color: 'bg-yellow-50',
-      avatarColor: 'yellow',
-    },
-    {
-      title: t('统计Tokens'),
-      value: isNaN(consumeTokens) ? 0 : consumeTokens,
-      icon: <IconTextStroked size="large" />,
-      color: 'bg-pink-50',
-      avatarColor: 'pink',
-    },
-    {
-      title: t('统计次数'),
-      value: times,
-      icon: <IconPulse size="large" />,
-      color: 'bg-teal-50',
-      avatarColor: 'cyan',
+      items: [
+        {
+          title: t('统计额度'),
+          value: renderQuota(consumeQuota),
+          icon: <IconCoinMoneyStroked size="large" />,
+          avatarColor: 'yellow',
+          trendData: trendData.consumeQuota,
+          trendColor: '#f59e0b'
+        },
+        {
+          title: t('统计Tokens'),
+          value: isNaN(consumeTokens) ? 0 : consumeTokens,
+          icon: <IconTextStroked size="large" />,
+          avatarColor: 'pink',
+          trendData: trendData.tokens,
+          trendColor: '#ec4899'
+        }
+      ]
     },
     },
     {
     {
-      title: t('平均RPM'),
-      value: (
-        times /
-        ((Date.parse(end_timestamp) - Date.parse(start_timestamp)) / 60000)
-      ).toFixed(3),
-      icon: <IconStopwatchStroked size="large" />,
+      title: t('性能指标'),
       color: 'bg-indigo-50',
       color: 'bg-indigo-50',
-      avatarColor: 'indigo',
-    },
-    {
-      title: t('平均TPM'),
-      value: (() => {
-        const tpm = consumeTokens /
-          ((Date.parse(end_timestamp) - Date.parse(start_timestamp)) / 60000);
-        return isNaN(tpm) ? '0' : tpm.toFixed(3);
-      })(),
-      icon: <IconTypograph size="large" />,
-      color: 'bg-orange-50',
-      avatarColor: 'orange',
-    },
+      items: [
+        {
+          title: t('平均RPM'),
+          value: (
+            times /
+            ((Date.parse(end_timestamp) - Date.parse(start_timestamp)) / 60000)
+          ).toFixed(3),
+          icon: <IconStopwatchStroked size="large" />,
+          avatarColor: 'indigo',
+          trendData: trendData.rpm,
+          trendColor: '#6366f1'
+        },
+        {
+          title: t('平均TPM'),
+          value: (() => {
+            const tpm = consumeTokens /
+              ((Date.parse(end_timestamp) - Date.parse(start_timestamp)) / 60000);
+            return isNaN(tpm) ? '0' : tpm.toFixed(3);
+          })(),
+          icon: <IconTypograph size="large" />,
+          avatarColor: 'orange',
+          trendData: trendData.tpm,
+          trendColor: '#f97316'
+        }
+      ]
+    }
   ];
   ];
 
 
   // 获取问候语
   // 获取问候语
@@ -612,48 +756,84 @@ const Detail = (props) => {
       <Spin spinning={loading}>
       <Spin spinning={loading}>
         <div className="mb-4">
         <div className="mb-4">
           <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
           <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
-            {statsData.map((stat, idx) => (
+            {groupedStatsData.map((group, idx) => (
               <Card
               <Card
                 key={idx}
                 key={idx}
-                shadows='hover'
-                className={`${stat.color} border-0 !rounded-2xl w-full`}
-                headerLine={false}
-                onClick={stat.onClick}
+                shadows='always'
+                bordered={false}
+                className={`${group.color} border-0 !rounded-2xl w-full`}
+                headerLine={true}
+                header={<div style={{ color: 'white', fontWeight: 'bold', fontSize: '16px' }}>{group.title}</div>}
+                headerStyle={{
+                  background: idx === 0
+                    ? 'linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%)'
+                    : idx === 1
+                      ? 'linear-gradient(135deg, #10b981 0%, #34d399 100%)'
+                      : idx === 2
+                        ? 'linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%)'
+                        : 'linear-gradient(135deg, #ec4899 0%, #f472b6 100%)',
+                  borderTopLeftRadius: '16px',
+                  borderTopRightRadius: '16px',
+                  padding: '12px 16px',
+                }}
               >
               >
-                <div className="flex items-center">
-                  <Avatar
-                    className="mr-3"
-                    size="medium"
-                    color={stat.avatarColor}
-                  >
-                    {stat.icon}
-                  </Avatar>
-                  <div>
-                    <div className="text-sm text-gray-500">{stat.title}</div>
-                    <div className="text-xl font-semibold">{stat.value}</div>
-                  </div>
+                <div className="space-y-4">
+                  {group.items.map((item, itemIdx) => (
+                    <div
+                      key={itemIdx}
+                      className="flex items-center justify-between cursor-pointer"
+                      onClick={item.onClick}
+                    >
+                      <div className="flex items-center">
+                        <Avatar
+                          className="mr-3"
+                          size="small"
+                          color={item.avatarColor}
+                        >
+                          {item.icon}
+                        </Avatar>
+                        <div>
+                          <div className="text-xs text-gray-500">{item.title}</div>
+                          <div className="text-lg font-semibold">{item.value}</div>
+                        </div>
+                      </div>
+                      {item.trendData && item.trendData.length > 0 && (
+                        <div className="w-24 h-10">
+                          <VChart
+                            spec={getTrendSpec(item.trendData, item.trendColor)}
+                            option={{ mode: 'desktop-browser' }}
+                          />
+                        </div>
+                      )}
+                    </div>
+                  ))}
                 </div>
                 </div>
               </Card>
               </Card>
             ))}
             ))}
           </div>
           </div>
         </div>
         </div>
 
 
-        <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
-          <Card shadows='hover' className="shadow-sm !rounded-2xl" headerLine={true} title={t('模型消耗分布')}>
-            <div style={{ height: 400 }}>
-              <VChart
-                spec={spec_line}
-                option={{ mode: 'desktop-browser' }}
-              />
-            </div>
-          </Card>
-
-          <Card shadows='hover' className="shadow-sm !rounded-2xl" headerLine={true} title={t('模型调用次数占比')}>
-            <div style={{ height: 400 }}>
-              <VChart
-                spec={spec_pie}
-                option={{ mode: 'desktop-browser' }}
-              />
+        <div className="grid grid-cols-1 lg:grid-cols-1 gap-6 mb-6">
+          <Card
+            shadows='always'
+            bordered={false}
+            className="shadow-sm !rounded-2xl"
+            headerLine={true}
+            title={t('模型数据分析')}
+          >
+            <div className="grid grid-cols-1 xl:grid-cols-2 gap-6">
+              <div style={{ height: 400 }}>
+                <VChart
+                  spec={spec_line}
+                  option={{ mode: 'desktop-browser' }}
+                />
+              </div>
+              <div style={{ height: 400 }}>
+                <VChart
+                  spec={spec_pie}
+                  option={{ mode: 'desktop-browser' }}
+                />
+              </div>
             </div>
             </div>
           </Card>
           </Card>
         </div>
         </div>