feat(web): Home page ui upgrade

This commit is contained in:
zhaoying
2026-03-07 12:20:55 +08:00
parent 0b3b241436
commit e2b6c713e7
22 changed files with 1016 additions and 1000 deletions

View File

@@ -0,0 +1,77 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:17:05
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-10 11:35:52
*/
/**
* Line Chart Card Component
* Displays time-series data with ECharts line chart
* Supports multiple series and date range selection
*/
import { type FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Select } from 'antd'
import dayjs from 'dayjs'
import Card from './Card'
import { getWorkspaceApiStatistics } from '@/api/application'
import LineChart, { type ChartData } from '@/components/Charts/LineChart'
const seriesList = ['total_calls', 'app_calls', 'service_calls']
const ApiLineCard: FC = () => {
const { t } = useTranslation()
const options = [
{ label: t('dashboard.lastDays', { days: 7 }), value: 7 },
{ label: t('dashboard.lastDays', { days: 30 }), value: 30 },
{ label: t('dashboard.lastDays', { days: 90 }), value: 90 },
{ label: t('dashboard.lastHalfYear'), value: 180 },
{ label: t('dashboard.lastYear'), value: 365 },
]
const [chartData, setChartData] = useState<ChartData[]>([])
const [query, setQuery] = useState(7)
useEffect(() => {
getWorkspaceApiStatistics({
start_date: dayjs().subtract(query - 1, 'd').startOf('d').valueOf(),
end_date: dayjs().endOf('d').valueOf(),
})
.then(res => {
setChartData(res as ChartData[])
})
}, [query])
/** Format series list for legend */
const formatSeriesList = () => {
const list: Record<string, string> = {}
seriesList.forEach(key => {
list[key] = t(`dashboard.${key}`)
})
return list
}
return (
<Card
title={t(`dashboard.apiCallTrends`)}
headerOperate={
<Select
value={query}
options={options}
onChange={(value) => setQuery(value)}
className="rb:w-35!"
/>
}
className={`rb:pb-6`}
>
<LineChart
chartData={chartData}
seriesList={formatSeriesList()}
height={239}
/>
</Card>
)
}
export default ApiLineCard

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:27:28
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:27:28
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-26 11:16:09
*/
/**
* Card Component
@@ -21,15 +21,19 @@ interface CardProps {
title: string;
headerOperate?: ReactNode;
className?: string;
bodyClassName?: string;
}
const Card: FC<CardProps> = ({ children, title, headerOperate, className }) => {
const Card: FC<CardProps> = ({ children, title, headerOperate, className, bodyClassName }) => {
return (
<RbCard
headerType="borderless"
title={title}
extra={headerOperate}
className={`rb:h-full! ${className}`}
variant="borderless"
className={`rb:h-full! rb:bg-[#FFFFFF]! ${className}`}
bodyClassName={bodyClassName}
headerClassName="rb:min-h-[58px]!"
>
{children}
</RbCard>

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 17:17:05
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:18:32
* @Last Modified time: 2026-02-10 11:59:10
*/
/**
* Line Chart Card Component
@@ -10,21 +10,18 @@
* Supports multiple series and date range selection
*/
import { type FC, useRef } from 'react'
import { type FC } from 'react'
import { useTranslation } from 'react-i18next'
import { Select } from 'antd'
import ReactEcharts from 'echarts-for-react';
import * as echarts from 'echarts';
import { formatDateTime } from '@/utils/format';
import Empty from '@/components/Empty'
import Card from './Card'
import AreaLineChart, { type ChartData } from '@/components/Charts/AreaLineChart';
/**
* Component props
*/
interface LineCardProps {
chartData: Array<Record<string, string | number>>;
chartData: ChartData[];
limit: number;
onChange: (value: string, type: string) => void;
type: string;
@@ -32,30 +29,8 @@ interface LineCardProps {
seriesList: string[];
}
/** ECharts series configuration */
const SeriesConfig = {
type: 'line',
stack: 'Total',
smooth: true,
lineStyle: {
width: 3
},
showSymbol: false,
label: {
show: true,
position: 'top'
},
emphasis: {
focus: 'series'
},
data: [220, 302, 181, 234, 210, 290, 150]
}
/** Chart color palette */
const Colors = ['#FFB048', '#4DA8FF', '#155EEF']
const LineCard: FC<LineCardProps> = ({ chartData, limit, onChange, type, className, seriesList }) => {
const { t } = useTranslation()
const chartRef = useRef<ReactEcharts>(null);
const options = [
{ label: t('dashboard.lastDays', { days: 7 }), value: 7 },
{ label: t('dashboard.lastDays', { days: 30 }), value: 30 },
@@ -63,39 +38,15 @@ const LineCard: FC<LineCardProps> = ({ chartData, limit, onChange, type, classNa
{ label: t('dashboard.lastHalfYear'), value: 180 },
{ label: t('dashboard.lastYear'), value: 365 },
]
/** Generate series data with gradient colors */
const getSeries = () => {
const list = seriesList.map((key, index) => {
return {
...SeriesConfig,
name: t(`dashboard.${key}`),
data: chartData.map(vo => vo[key]),
areaStyle: {
opacity: 0.8,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: Colors[index]
},
{
offset: 1,
color: '#FFFFFF'
}
])
},
}
/** Format series list for legend */
const formatSeriesList = () => {
const list: Record<string, string> = {}
seriesList.forEach(key => {
list[key] = t(`dashboard.${key}`)
})
return list
}
/** Format series list for legend */
const formatSeriesList = () => {
return seriesList.map(key => ({
...SeriesConfig,
name: t(`dashboard.${key}`),
}))
}
return (
<Card
@@ -104,79 +55,18 @@ const LineCard: FC<LineCardProps> = ({ chartData, limit, onChange, type, classNa
<Select
value={limit}
options={options}
onChange={(value) => onChange(String(value), type)}
style={{ width: '150px' }}
onChange={(value) => onChange(String(value), type)}
className="rb:w-35!"
/>
}
className={`rb:pb-6 ${className}`}
>
{chartData && chartData.length > 0 ? (
<ReactEcharts
ref={chartRef}
option={{
color: Colors,
tooltip: {
trigger: 'axis',
extraCssText: 'box-shadow: 0px 2px 6px 0px rgba(33,35,50,0.16); border-radius: 8px;',
axisPointer: {
type: 'line',
crossStyle: {
color: '#5F6266',
},
lineStyle: {
color: '#5F6266',
},
label: {
show: false
}
},
},
legend: {
data: formatSeriesList(),
textStyle: {
color: '#5B6167',
fontFamily: 'PingFangSC, PingFang SC',
lineHeight: 16,
},
itemGap: 32,
padding: 0,
itemWidth: 26,
itemHeight: 10,
left: 'center'
},
grid: {
left: 4,
right: '3%',
bottom: 0,
containLabel: true
},
xAxis: {
type: 'category',
data: chartData.map(item => formatDateTime(item.created_at, 'DD/MM')),
boundaryGap: false,
},
yAxis: {
type: 'value',
axisLabel: {
color: '#A8A9AA',
fontFamily: 'PingFangSC, PingFang SC',
align: 'right',
lineHeight: 17,
},
axisLine: {
lineStyle: {
color: '#EBEBEB',
}
},
},
series: getSeries()
}}
style={{ height: '265px', width: '100%', minWidth: '100%', boxSizing: 'border-box' }}
opts={{ renderer: 'canvas' }}
notMerge={true}
lazyUpdate={true}
/>
) : <Empty size={120} className="rb:mt-12 rb:mb-20.25" />}
<AreaLineChart
xAxisKey="date"
chartData={chartData}
seriesList={formatSeriesList()}
height={239}
/>
</Card>
)
}

View File

@@ -1,137 +1,39 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:16:45
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:16:45
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-10 11:57:35
*/
/**
* Pie Chart Card Component
* Displays knowledge base type distribution with ECharts donut chart
*/
import { type FC, useRef, useEffect } from 'react'
import { type FC } from 'react'
import { useTranslation } from 'react-i18next'
import ReactEcharts from 'echarts-for-react';
import Card from './Card'
import Loading from '@/components/Empty/Loading'
import Empty from '@/components/Empty'
import PieChart, { type ChartData } from '@/components/Charts/PieChart'
/**
* Component props
*/
interface PieCardProps {
chartData: Array<Record<string, string | number>>;
chartData: ChartData[];
loading: boolean;
}
/** Chart color palette */
const Colors = ['#155EEF', '#31E8FF', '#AD88FF', '#FFB048', '#4DA8FF', '#03BDFF']
const PieCard: FC<PieCardProps> = ({ chartData, loading }) => {
const { t } = useTranslation()
const chartRef = useRef<ReactEcharts>(null);
const resizeScheduledRef = useRef(false)
useEffect(() => {
const handleResize = () => {
if (chartRef.current && !resizeScheduledRef.current) {
resizeScheduledRef.current = true
requestAnimationFrame(() => {
chartRef.current?.getEchartsInstance().resize();
resizeScheduledRef.current = false
});
}
}
const resizeObserver = new ResizeObserver(handleResize)
const chartElement = chartRef.current?.getEchartsInstance().getDom().parentElement
if (chartElement) {
resizeObserver.observe(chartElement)
}
return () => {
resizeObserver.disconnect()
}
}, [chartData])
return (
<Card
title={t('dashboard.knowledgeBaseTypeDistribution')}
>
{loading
? <Loading size={249} />
: !chartData || chartData.length === 0
? <Empty size={120} className="rb:mt-12 rb:mb-20.25" />
: <ReactEcharts
option={{
color: Colors,
tooltip: {
trigger: 'item',
textStyle: {
color: '#5B6167',
fontSize: 12,
width: 27,
height: 16,
},
formatter: '{d}%',
padding: [8, 5],
backgroundColor: '#FFFFFF',
borderColor: '#DFE4ED',
extraCssText: 'width: 36px; height: 36px; box-shadow: 0px 2px 4px 0px rgba(33,35,50,0.12);border-radius: 36px;'
},
legend: {
right: 20 ,
top: 'middle',
padding: 0,
itemWidth: 12,
itemHeight: 12,
borderRadius: 2,
orient: 'vertical',
textStyle: {
color: '#5B6167',
fontFamily: 'PingFangSC, PingFang SC',
lineHeight: 16,
}
},
series: [
{
name: 'Access From',
type: 'pie',
radius: ['60%', '100%'],
avoidLabelOverlap: false,
percentPrecision: 0,
padAngle: 4,
width: 200,
height: 200,
left: '10%',
top: 'middle',
itemStyle: {
borderRadius: 0
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 24,
fontWeight: 'bold',
color: '#212332',
formatter: '{d}%\n{b}',
}
},
labelLine: {
show: false
},
data: chartData
}
]
}}
style={{ height: '265px', width: '100%', minWidth: '400px' }}
notMerge={true}
lazyUpdate={true}
/>
? <Loading size={249} />
: <PieChart chartData={chartData} />
}
</Card>
)

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:16:38
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:16:38
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-11 14:57:35
*/
/**
* Quick Operation Component
@@ -11,15 +11,16 @@
*/
import { type FC } from 'react'
import clsx from 'clsx';
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom';
import { Flex } from 'antd';
import Card from './Card';
import applicationIcon from '@/assets/images/menu/application_active.svg';
import knowledgeIcon from '@/assets/images/menu/knowledge_active.svg';
import memoryConversationIcon from '@/assets/images/menu/memoryConversation_active.svg';
import applicationIcon from '@/assets/images/home/application.svg';
import knowledgeIcon from '@/assets/images/home/knowledge.svg';
import memoryConversationIcon from '@/assets/images/home/memoryConversation.svg';
import helpCenterIcon from '@/assets/images/menu/helpCenter_active.svg'
import arrowTopRight from '@/assets/images/home/arrow_top_right.svg';
/** Quick operation items configuration */
const quickOperations = [
@@ -29,6 +30,13 @@ const quickOperations = [
{ key: 'helpCenter', url: '' },
]
const bgStyleList = [
'rb:bg-[rgba(21,94,239,0.1)]',
'rb:bg-[rgba(156,111,255,0.1)]',
'rb:bg-[rgba(255,176,72,0.1)]',
'rb:bg-[rgba(77,168,255,0.1)]'
]
/** Icon mapping for quick operations */
const quickOperationIcons: {[key: string]: string | undefined} = {
createNewApplication: applicationIcon,
@@ -62,17 +70,19 @@ const QuickOperation:FC = () => {
return (
<Card
title={t('dashboard.quickOperation')}
bodyClassName="rb:pt-0! rb:pb-[14px]! rb:px-4!"
>
<div className="rb:grid rb:grid-cols-4 rb:gap-4">
{quickOperations.map(item => (
<div key={item.key} className="rb:rounded-lg rb:p-[20px_16px] rb:border rb:border-[#DFE4ED] rb:cursor-pointer rb:hover:border-[#155EEF]" onClick={() => handleJump(item.url)}>
<div className="rb:flex rb:justify-between">
<img className="rb:w-8 rb:h-8" src={quickOperationIcons[item.key]} />
<img className="rb:w-4 rb:h-4" src={arrowTopRight} />
<div className="rb:grid rb:grid-cols-1 rb:gap-3">
{quickOperations.map((item, index) => (
<Flex key={item.key} align="center" gap={20} className={clsx("rb:relative rb:rounded-xl rb:py-2! rb:px-3! rb:cursor-pointer", bgStyleList[index])} onClick={() => handleJump(item.url)}>
<div className="rb:size-8 rb:rounded-lg rb:p-1 rb:bg-[#FFFFFF]">
<img className="rb:size-6" src={quickOperationIcons[item.key]} />
</div>
<div className="rb:mt-6 rb:text-[#212332] rb:text-[16px] rb:leading-5 rb:font-medium">{t(`dashboard.${item.key}`)}</div>
<div className="rb:mt-2 rb:text-[#5B6167] rb:text-[12px] rb:font-regular">{t(`dashboard.${item.key}Desc`)}</div>
</div>
<div>
<div className="rb:text-[14px] rb:leading-5 rb:font-medium">{t(`dashboard.${item.key}`)}</div>
<div className="rb:mt-0.5 rb:text-[#5B6167] rb:text-[12px] rb:font-regular">{t(`dashboard.${item.key}Desc`)}</div>
</div>
</Flex>
))}
</div>
</Card>

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:15:33
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:15:33
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-11 14:48:31
*/
/**
* Recent Activity Component
@@ -13,7 +13,7 @@
import { type FC, useEffect, useState } from 'react'
import clsx from 'clsx'
import { useTranslation } from 'react-i18next'
import { Skeleton } from 'antd';
import { Skeleton, Flex } from 'antd';
import chunkCountIcon from '@/assets/images/home/chunk_count.svg';
import statementsCountIcon from '@/assets/images/home/statements_count.svg';
@@ -79,30 +79,31 @@ const RecentActivity:FC = () => {
return (
<Card
title={t('dashboard.recentMemoryActivities')}
bodyClassName="rb:pt-0! rb:pb-[14px]! rb:px-4!"
>
{loading
? <Skeleton />
: !recentActivities || Object.keys(recentActivities).length === 0
? <Empty url={activityEmpty} subTitle={t('dashboard.activityEmpty')} size={120} className="rb:mt-11.25 rb:mb-20.25" />
: activityList.map((item, index) => (
<div key={item.key} className={clsx("rb:flex rb:justify-between rb:items-center rb:not-italic", {
'rb:mt-6': index !== 0
})}>
<div className="rb:flex rb:items-center rb:text-[#060419] rb:text-[16px] rb:font-medium">
<img className="rb:w-10 rb:h-10 rb:mr-4" src={item.icon} />
<div>
{t(`dashboard.${item.key}`)}
<div className="rb:text-[#5B6167] rb:text-[14px] rb:font-normal">
{item.key === 'triplet_count'
? t(`dashboard.${item.key}_desc`, { entities_count: recentActivities.triplet_entities_count, relations_count: recentActivities.triplet_relations_count })
: t(`dashboard.${item.key}_desc`, { count: recentActivities[item.key as keyof RecentActivities] })
}
: <Flex vertical gap={24} justify="space-around">
{activityList.map((item) => (
<Flex key={item.key} align="center" justify="space-between" className={clsx("rb:not-italic")}>
<Flex align="center" gap={20}>
<img className="rb:size-10" src={item.icon} />
<div>
<div className="rb:text-[16px] rb:leading-5.5 rb:font-medium">{t(`dashboard.${item.key}`)}</div>
<div className="rb:text-[#7B8085] rb:text-[14px] rb:font-regular rb:mt-1 rb:leading-4.5">
{item.key === 'triplet_count'
? t(`dashboard.${item.key}_desc`, { entities_count: recentActivities.triplet_entities_count, relations_count: recentActivities.triplet_relations_count })
: t(`dashboard.${item.key}_desc`, { count: recentActivities[item.key as keyof RecentActivities] })
}
</div>
</div>
</div>
</div>
<div className="rb:text-[#5F6266] rb:text-right rb:whitespace-nowrap">{data?.latest_relative || ''}</div>
</div>
))
</Flex>
<div className="rb:text-[#7B8085] rb:text-right rb:whitespace-nowrap">{data?.latest_relative || ''}</div>
</Flex>
))}
</Flex>
}
</Card>
)

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:15:04
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:15:04
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-26 11:15:15
*/
/**
* Tag List Component
@@ -12,13 +12,24 @@
import { type FC, useEffect, useState } from 'react'
import clsx from 'clsx'
import { useTranslation } from 'react-i18next'
import { Skeleton } from 'antd';
import { Skeleton, Flex } from 'antd';
import tagEmpty from '@/assets/images/home/tagEmpty.svg'
import Empty from '@/components/Empty';
import Card from './Card';
import { getHotMemoryTags } from '@/api/memory';
const btnStyleList = [
'rb:bg-[rgba(21,94,239,0.06)]',
'rb:bg-[rgba(33,35,50,0.06)]',
'rb:bg-[rgba(156,111,255,0.06)]'
]
const numStyleList = [
'rb:bg-[#155EEF]',
'rb:bg-[#212332]',
'rb:bg-[#9C6FFF]'
]
const TagList:FC = () => {
const { t } = useTranslation()
const [loading, setLoading] = useState<boolean>(false)
@@ -38,23 +49,24 @@ const TagList:FC = () => {
return (
<Card
title={t('dashboard.popularMemoryTags')}
bodyClassName='rb:overflow-hidden! rb:pt-0! rb:pb-4! rb:pl-4! rb:pr-3.25!'
className="rb:min-h-[calc(100vh-744px)]"
>
{loading
? <Skeleton />
: !tagList || tagList.length === 0
? <Empty url={tagEmpty} title={t('dashboard.activityEmpty')} size={120} className="rb:mt-9 rb:mb-20.25" />
: <div className="rb:gap-3 rb:flex rb:flex-wrap">
: <Flex wrap className="rb:gap-x-3! rb:gap-y-2.5!">
{tagList.map((item, index) => (
<div
key={item.name}
className={clsx("rb:pt-1.5 rb:pb-1.5 rb:pr-5.75 rb:pl-5 rb:border rb:leading-5 rb:bg-white rb:rounded-[17px]", {
'rb:border-[rgba(21,94,239,0.4)] rb:text-[#155EEF]': index % 3 === 0,
'rb:border-[rgba(255,138,76,0.4)] rb:text-[#FF5D34]': index % 3 === 1,
'rb:border-[rgba(54,159,33,0.4)] rb:text-[#369F21]': index % 3 === 2,
})}
>{item.name} {item.frequency}</div>
className={clsx("rb:rounded-[17px] rb:py-1.5 rb:pl-3 rb:pr-2", btnStyleList[index % 3])}
>
{item.name}
<span className={clsx('rb:px-2 rb:py-0.5 rb:rounded-[10px] rb:text-[#FFFFFF] rb:text-[12px] rb:font-bold rb:font-[MiSans-Demibold] rb:ml-2', numStyleList[index % 3])}>{item.frequency}</span>
</div>
))}
</div>
</Flex>
}
</Card>
)

View File

@@ -0,0 +1,112 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:28:07
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-11 14:57:55
*/
/**
* Top Card List Component
* Displays dashboard summary cards for key metrics
* Shows total memory capacity, applications, knowledge bases, and API calls
*/
import { type FC } from 'react'
import { useTranslation } from 'react-i18next'
import clsx from 'clsx';
import { Flex } from 'antd';
import totalMemoryCapacity from '@/assets/images/home/totalMemoryCapacity.svg';
import userMemory from '@/assets/images/home/userMemory.svg';
import knowledgeBaseCount from '@/assets/images/home/knowledgeBaseCount.svg';
import apiCallCount from '@/assets/images/home/apiCallCount.svg';
import type { DashboardData } from '../index'
/** Card configuration with styling */
const list = [
{
key: 'totalMemoryCapacity',
icon: totalMemoryCapacity,
// value: '45,678',
// trendValue: '12.5%',
// trend: 'up',
// trendDesc: 'comparedToYesterday',
background: 'rb:bg-[url("@/assets/images/home/totalMemoryCapacity.png")] rb:bg-cover rb:bg-no-repeat',
},
{
key: 'application',
icon: userMemory,
// value: '32,145',
// trendValue: '12.5%',
// trend: 'down',
// trendDesc: 'comparedToYesterday',
// background: 'linear-gradient( 180deg, #F1FBF5 0%, #F9FDFF 100%)',
},
{
key: 'knowledgeBaseCount',
icon: knowledgeBaseCount,
// value: '13,533',
// trendValue: '15.7%',
// trend: 'up',
// trendDesc: 'thisWeek',
// background: 'linear-gradient( 180deg, #E6F5FE 0%, #FBFDFF 100%)',
},
{
key: 'apiCallCount',
icon: apiCallCount,
// value: '856.2k',
// trendValue: '23.1%',
// trend: 'up',
// trendDesc: 'comparedToYesterday',
// background: 'linear-gradient( 180deg, #F8F6F5 0%, #FAFDFF 100%)',
},
]
/**
* Component props
* @param data - Dashboard statistics data
*/
const TopCardList: FC<{data?: DashboardData}> = ({ data }) => {
const { t } = useTranslation()
return (
<div className="rb:grid rb:grid-cols-2 rb:gap-3">
{list.map((item) => {
return (
<div
key={item.key}
className={`rb:rounded-2xl rb:bg-[#FFFFFF] rb:py-4 rb:px-3 ${item.background || ''}`}
>
<div className={clsx("rb:text-[12px] rb:leading-4", {
'rb:text-[#FFFFFF]': item.key === 'totalMemoryCapacity',
'rb:text-[#5B6167]': item.key !== 'totalMemoryCapacity',
})}>{t(`dashboard.${item.key}`)}</div>
<div className={clsx("rb:text-[20px] rb:font-bold rb:leading-7 rb:mt-1 rb:font-[MiSans-Bold]", {
'rb:text-[#FFFFFF]': item.key === 'totalMemoryCapacity',
// 'rb:text-[#171719]': item.key !== 'totalMemoryCapacity',
})}>
{data?.[item.key as keyof DashboardData] || 0}
</div>
<Flex align="center" className={clsx('rb:font-medium rb:mt-7.5!', {
'rb:text-[#FFFFFF]': item.key === 'totalMemoryCapacity',
'rb:text-[#369F21]': item.key !== 'totalMemoryCapacity',
})}>
0%
<div className={clsx("rb:size-3.5 rb:cursor-pointer rb:bg-cover", {
"rb:bg-[url('@/assets/images/home/arrow_up.svg')]": item.key === 'totalMemoryCapacity',
"rb:bg-[url('@/assets/images/home/arrow_up_success.svg')]": item.key !== 'totalMemoryCapacity',
})}></div>
</Flex>
<div className={clsx("rb:text-[12px] rb:leading-4 rb:mt-0.5", {
'rb:text-[#FFFFFF]': item.key === 'totalMemoryCapacity',
'rb:text-[#5B6167]': item.key !== 'totalMemoryCapacity',
})}>
{t('dashboard.comparedToYesterday')}
</div>
</div>
)
})}
</div>
)
}
export default TopCardList

View File

@@ -1,97 +0,0 @@
.card {
border-radius: 12px;
border: 1px solid #DFE4ED;
padding: 0;
}
.header {
padding: 20px;
line-height: 44px;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 16px;
color: #5B6167;
font-style: normal;
display: flex;
align-items: center;
border-bottom: 1px solid #DFE4ED;
}
.avatar {
width: 44px;
height: 44px;
background: #FFFFFF;
box-shadow: 0px 2px 6px 0px rgba(33, 35, 50, 0.1);
border-radius: 22px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
}
.avatar img {
width: 24px;
height: 24px;
}
.content {
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: Gilroy, Gilroy;
font-weight: 800;
font-size: 28px;
color: #212332;
text-align: left;
font-style: normal;
}
.content-right {
text-align: right;
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 12px;
color: #5F6266;
line-height: 16px;
font-style: normal;
row-gap: 4px;
}
.trend {
font-family: PingFangSC, PingFang SC;
font-weight: 500;
font-size: 14px;
line-height: 16px;
font-style: normal;
padding-left: 15px;
position: relative;
margin-bottom: 4px;
display: inline-block;
}
.trend::before {
width: 14px;
height: 14px;
content: '';
position: absolute;
left: 0;
top: 1px;
background-repeat: no-repeat;
background-size: contain;
}
.trend.up {
color: #369F21;
}
.trend.up::before {
background-image: url('@/assets/images/home/arrow_up_success.svg');
}
.trend.down {
color: #FF5D34;
}
.trend.down::before {
background-image: url('@/assets/images/home/arrow_down.png');
}
.trend-desc {
font-family: PingFangSC, PingFang SC;
font-weight: 500;
font-size: 14px;
color: #155EEF;
line-height: 16px;
text-align: left;
font-style: normal;
}

View File

@@ -1,94 +0,0 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 17:28:07
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:28:07
*/
/**
* Top Card List Component
* Displays dashboard summary cards for key metrics
* Shows total memory capacity, applications, knowledge bases, and API calls
*/
import { type FC } from 'react'
import { useTranslation } from 'react-i18next'
import totalMemoryCapacity from '@/assets/images/home/totalMemoryCapacity.svg';
import userMemory from '@/assets/images/home/userMemory.svg';
import knowledgeBaseCount from '@/assets/images/home/knowledgeBaseCount.svg';
import apiCallCount from '@/assets/images/home/apiCallCount.svg';
import styles from './index.module.css'
import type { DashboardData } from '../../index'
/** Card configuration with styling */
const list = [
{
key: 'totalMemoryCapacity',
icon: totalMemoryCapacity,
// value: '45,678',
// trendValue: '12.5%',
// trend: 'up',
// trendDesc: 'comparedToYesterday',
background: 'linear-gradient(180deg, #E6EFFE 0%, #F9FDFF 100%)',
},
{
key: 'application',
icon: userMemory,
// value: '32,145',
// trendValue: '12.5%',
// trend: 'down',
// trendDesc: 'comparedToYesterday',
background: 'linear-gradient( 180deg, #F1FBF5 0%, #F9FDFF 100%)',
},
{
key: 'knowledgeBaseCount',
icon: knowledgeBaseCount,
// value: '13,533',
// trendValue: '15.7%',
// trend: 'up',
// trendDesc: 'thisWeek',
background: 'linear-gradient( 180deg, #E6F5FE 0%, #FBFDFF 100%)',
},
{
key: 'apiCallCount',
icon: apiCallCount,
// value: '856.2k',
// trendValue: '23.1%',
// trend: 'up',
// trendDesc: 'comparedToYesterday',
background: 'linear-gradient( 180deg, #F8F6F5 0%, #FAFDFF 100%)',
},
]
/**
* Component props
* @param data - Dashboard statistics data
*/
const TopCardList: FC<{data?: DashboardData}> = ({ data }) => {
const { t } = useTranslation()
return (
<div className="rb:grid rb:grid-cols-4 rb:gap-4">
{list.map((item) => {
return (
<div
key={item.key}
className={styles.card}
style={{
background: item.background,
}}
>
<div className={styles.header}>
<div className={styles.avatar}><img src={item.icon} /></div>
<div className={styles.headerTitle}>{t(`dashboard.${item.key}`)}</div>
</div>
<div className={styles.content}>
{data?.[item.key as keyof DashboardData] || 0}
</div>
</div>
)
})}
</div>
)
}
export default TopCardList

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 17:12:43
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 17:26:04
* @Last Modified time: 2026-02-10 11:57:58
*/
/**
* Home Dashboard Page
@@ -19,6 +19,7 @@ import { getDashboardData, getMemoryIncrement, getKbTypes } from '@/api/memory';
import RecentActivity from './components/RecentActivity'
import TagList from './components/TagList'
import QuickOperation from './components/QuickOperation'
import ApiLineCard from './components/ApiLineCard'
/**
* Dashboard statistics data
@@ -120,42 +121,39 @@ const Home = () => {
}
return (
<div className="rb:pb-6">
<TopCardList data={dashboardData} />
<Row className="rb:mt-4" gutter={16}>
<Col span={12}>
<LineCard
chartData={memoryIncrement}
limit={limit}
onChange={handleRangeChange}
type="memoryGrowthTrend"
seriesList={['total_num']}
/>
</Col>
<Col span={12}>
<PieCard
loading={loading.knowledgeTypeDistribution}
chartData={knowledgeTypeDistribution}
/>
</Col>
</Row>
<Row className="rb:mt-4" gutter={16}>
<Col span={12}>
<RecentActivity />
</Col>
<Col span={12}>
<TagList />
</Col>
</Row>
<Row className="rb:mt-4" gutter={16}>
<Col span={24}>
<QuickOperation />
</Col>
</Row>
</div>
<Row gutter={[12, 12]}>
<Col span={8}>
<TopCardList data={dashboardData} />
</Col>
<Col span={8}>
<LineCard
chartData={memoryIncrement}
limit={limit}
onChange={handleRangeChange}
type="memoryGrowthTrend"
seriesList={['total_num']}
/>
</Col>
<Col span={8}>
<ApiLineCard
/>
</Col>
<Col span={8}>
<PieCard
loading={loading.knowledgeTypeDistribution}
chartData={knowledgeTypeDistribution}
/>
</Col>
<Col span={8}>
<RecentActivity />
</Col>
<Col span={8}>
<QuickOperation />
</Col>
<Col span={24}>
<TagList />
</Col>
</Row>
);
}