Commit 6731a2c2 by Apple\Apple

🎨 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
parent 1ed8a1e0
...@@ -1638,7 +1638,8 @@ const ChannelsTable = () => { ...@@ -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()}
......
...@@ -1259,7 +1259,8 @@ const LogsTable = () => { ...@@ -1259,7 +1259,8 @@ const LogsTable = () => {
</div> </div>
</div> </div>
} }
shadows='hover' shadows='always'
bordered={false}
> >
<Table <Table
columns={getVisibleColumns()} columns={getVisibleColumns()}
......
...@@ -864,7 +864,8 @@ const LogsTable = () => { ...@@ -864,7 +864,8 @@ const LogsTable = () => {
</div> </div>
</div> </div>
} }
shadows='hover' shadows='always'
bordered={false}
> >
<Table <Table
columns={getVisibleColumns()} columns={getVisibleColumns()}
......
...@@ -241,7 +241,7 @@ const ModelPricing = () => { ...@@ -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 = () => { ...@@ -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 = () => { ...@@ -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}
......
...@@ -501,7 +501,8 @@ const RedemptionsTable = () => { ...@@ -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}
......
...@@ -702,7 +702,8 @@ const LogsTable = () => { ...@@ -702,7 +702,8 @@ const LogsTable = () => {
</div> </div>
</div> </div>
} }
shadows='hover' shadows='always'
bordered={false}
> >
<Table <Table
columns={getVisibleColumns()} columns={getVisibleColumns()}
......
import React, { useEffect, useState, useContext } from 'react'; import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { import {
API, API,
copy, copy,
...@@ -21,8 +20,6 @@ import { ...@@ -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 { ...@@ -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) { ...@@ -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 = () => { ...@@ -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 = () => { ...@@ -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 = () => { ...@@ -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}
......
...@@ -551,7 +551,8 @@ const UsersTable = () => { ...@@ -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}
......
...@@ -1551,5 +1551,10 @@ ...@@ -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"
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment