基于 React + Vite + Ant Design Pro 搭建登录、动态菜单、布局主题与数据总览,并补充按菜单整理的 UI 设计稿。 Co-authored-by: Cursor <cursoragent@cursor.com>
218 lines
6.1 KiB
TypeScript
218 lines
6.1 KiB
TypeScript
import { ApiOutlined, MessageOutlined, RiseOutlined, TeamOutlined, ThunderboltOutlined } from '@ant-design/icons';
|
|
import { Line, Pie } from '@ant-design/charts';
|
|
import { ProCard } from '@ant-design/pro-components';
|
|
import { Col, Progress, Row, Select, Space, Statistic, Table, Tag, Typography, theme } from 'antd';
|
|
import type { ColumnsType } from 'antd/es/table';
|
|
import type { ReactNode } from 'react';
|
|
|
|
import { themeConfig } from '@/styles/theme';
|
|
|
|
import {
|
|
alertList,
|
|
conversationTrend,
|
|
modelShare,
|
|
overviewStats,
|
|
topModels,
|
|
type AlertItem,
|
|
type OverviewStat,
|
|
} from './mock';
|
|
|
|
const iconMap: Record<OverviewStat['icon'], ReactNode> = {
|
|
message: <MessageOutlined />,
|
|
token: <ThunderboltOutlined />,
|
|
user: <TeamOutlined />,
|
|
api: <ApiOutlined />,
|
|
};
|
|
|
|
const alertColumns: ColumnsType<AlertItem> = [
|
|
{ title: '类型', dataIndex: 'type', width: 100 },
|
|
{ title: '内容', dataIndex: 'content', ellipsis: true },
|
|
{
|
|
title: '级别',
|
|
dataIndex: 'level',
|
|
width: 80,
|
|
render: (level: AlertItem['level']) => {
|
|
const color = level === '高' ? 'error' : level === '中' ? 'warning' : 'default';
|
|
return <Tag color={color}>{level}</Tag>;
|
|
},
|
|
},
|
|
{ title: '时间', dataIndex: 'time', width: 170 },
|
|
{
|
|
title: '状态',
|
|
dataIndex: 'status',
|
|
width: 90,
|
|
render: (status: AlertItem['status']) => <Tag color={status === '已恢复' ? 'success' : 'error'}>{status}</Tag>,
|
|
},
|
|
];
|
|
|
|
const primary = themeConfig.token.colorPrimary;
|
|
|
|
export default function Dashboard() {
|
|
const { token } = theme.useToken();
|
|
|
|
const lineConfig = {
|
|
data: conversationTrend,
|
|
xField: 'date',
|
|
yField: 'value',
|
|
height: 280,
|
|
autoFit: true,
|
|
// 给曲线/圆点留边,避免贴边裁切
|
|
padding: [12, 16, 8, 8], // 上 右 下 左,可按观感微调
|
|
// 或用 inset: 12 / insetRight: 16
|
|
shapeField: 'smooth',
|
|
style: {
|
|
stroke: primary,
|
|
lineWidth: 2,
|
|
},
|
|
area: {
|
|
style: {
|
|
fill: `linear-gradient(-90deg, ${primary}00 0%, ${primary}40 100%)`,
|
|
},
|
|
},
|
|
point: {
|
|
sizeField: 4,
|
|
shapeField: 'circle',
|
|
style: {
|
|
fill: primary,
|
|
stroke: '#fff',
|
|
lineWidth: 1,
|
|
},
|
|
},
|
|
axis: {
|
|
y: {
|
|
labelFormatter: (v: number) => `${Math.round(v / 1000)}k`,
|
|
},
|
|
},
|
|
tooltip: {
|
|
items: [{ channel: 'y', name: '对话量' }],
|
|
},
|
|
};
|
|
|
|
const pieConfig = {
|
|
data: modelShare,
|
|
angleField: 'value',
|
|
colorField: 'type',
|
|
height: 280,
|
|
innerRadius: 0.6,
|
|
legend: {
|
|
color: {
|
|
position: 'right' as const,
|
|
itemLabelFontSize: 12,
|
|
},
|
|
},
|
|
label: false,
|
|
annotations: [
|
|
{
|
|
type: 'text' as const,
|
|
style: {
|
|
text: '总计\n100%',
|
|
x: '50%',
|
|
y: '50%',
|
|
textAlign: 'center',
|
|
fontSize: 14,
|
|
fill: token.colorTextSecondary,
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
return (
|
|
<Space vertical size={16} style={{ width: '100%' }}>
|
|
<Row gutter={[16, 16]}>
|
|
{overviewStats.map(item => (
|
|
<Col key={item.key} xs={24} sm={12} md={6}>
|
|
<ProCard key={item.key} colSpan={{ xs: 24, sm: 12, md: 6 }} variant="outlined">
|
|
<Statistic
|
|
title={
|
|
<Space>
|
|
<span style={{ color: token.colorPrimary }}>{iconMap[item.icon]}</span>
|
|
{item.title}
|
|
</Space>
|
|
}
|
|
value={item.value}
|
|
suffix={
|
|
<Typography.Text type="success" style={{ fontSize: 13 }}>
|
|
<RiseOutlined /> 较昨日 ↑ {item.trend}%
|
|
</Typography.Text>
|
|
}
|
|
/>
|
|
</ProCard>
|
|
</Col>
|
|
))}
|
|
</Row>
|
|
|
|
<Row gutter={[16, 16]}>
|
|
<Col xs={24} lg={16} style={{ minWidth: 0 }}>
|
|
<ProCard
|
|
title="近7日对话趋势"
|
|
variant="outlined"
|
|
styles={{ body: { overflow: 'hidden' } }}
|
|
extra={
|
|
<Select
|
|
defaultValue="7d"
|
|
style={{ width: 100 }}
|
|
options={[
|
|
{ label: '近7日', value: '7d' },
|
|
{ label: '近30日', value: '30d' },
|
|
]}
|
|
/>
|
|
}
|
|
>
|
|
<Line {...lineConfig} autoFit containerStyle={{ width: '100%', height: 280 }} />
|
|
</ProCard>
|
|
</Col>
|
|
<Col xs={24} lg={8}>
|
|
<ProCard title="模型调用占比" variant="outlined">
|
|
<Pie {...pieConfig} />
|
|
</ProCard>
|
|
</Col>
|
|
</Row>
|
|
|
|
<Row gutter={[16, 16]}>
|
|
<Col xs={24} lg={16}>
|
|
<ProCard title="异常告警" variant="outlined">
|
|
<Table<AlertItem>
|
|
rowKey="id"
|
|
size="middle"
|
|
columns={alertColumns}
|
|
dataSource={alertList}
|
|
pagination={{ pageSize: 5, showTotal: total => `共 ${total} 条` }}
|
|
/>
|
|
</ProCard>
|
|
</Col>
|
|
<Col xs={24} lg={8}>
|
|
<ProCard
|
|
title="热门模型 TOP5"
|
|
variant="outlined"
|
|
extra={
|
|
<Typography.Link type="secondary" style={{ fontSize: 13 }}>
|
|
查看更多 >
|
|
</Typography.Link>
|
|
}
|
|
>
|
|
<Space vertical size={16} style={{ width: '100%' }}>
|
|
{topModels.map((item, index) => (
|
|
<div key={item.name}>
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
marginBottom: 6,
|
|
}}
|
|
>
|
|
<Typography.Text>
|
|
{index + 1}. {item.name}
|
|
</Typography.Text>
|
|
<Typography.Text type="secondary">{item.percent}%</Typography.Text>
|
|
</div>
|
|
<Progress percent={item.percent} showInfo={false} strokeColor={token.colorPrimary} size="small" />
|
|
</div>
|
|
))}
|
|
</Space>
|
|
</ProCard>
|
|
</Col>
|
|
</Row>
|
|
</Space>
|
|
);
|
|
}
|