feat: 初始化 ChatOne 管理后台前端

基于 React + Vite + Ant Design Pro 搭建登录、动态菜单、布局主题与数据总览,并补充按菜单整理的 UI 设计稿。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-25 23:57:53 +08:00
parent ed9b23ba90
commit a09dcef3f1
59 changed files with 7296 additions and 129 deletions

View File

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