feat: V21 Admin 页面样式对齐 (#31)

This commit is contained in:
CI Bot
2026-06-26 22:15:42 +08:00
6 changed files with 385 additions and 306 deletions
+21 -13
View File
@@ -1,24 +1,32 @@
import React from 'react';
import { Button, Card, Result } from 'antd';
import { useNavigate } from 'react-router-dom';
import './Admin.css';
const AdminComingSoon: React.FC = () => {
const navigate = useNavigate();
return (
<div style={{ padding: '24px' }}>
<Card>
<Result
status="info"
title="Admin 后台暂未开放"
subTitle="当前版本未接入后台用户、监控、日志、分析等后端服务,因此不展示模拟运营数据,也不会提供假操作入口。"
extra={[
<Button key="workspaces" type="primary" onClick={() => navigate('/workspaces')}>
</Button>,
]}
/>
</Card>
<div className="admin-coming-soon-page">
<div className="xx-result-center">
<Card className="xx-card">
<Result
status="info"
title="Admin 后台暂未开放"
subTitle="当前版本未接入后台用户、监控、日志、分析等后端服务,因此不展示模拟运营数据,也不会提供假操作入口。"
extra={[
<Button
key="workspaces"
type="primary"
onClick={() => navigate('/workspaces')}
className="xx-primary-btn"
>
</Button>,
]}
/>
</Card>
</div>
</div>
);
};
+135 -80
View File
@@ -24,9 +24,22 @@ import {
DollarOutlined,
ProjectOutlined,
} from '@ant-design/icons';
import './Admin.css';
const { RangePicker } = DatePicker;
// V21 配色
const V21_COLORS = {
primary: '#4f46e5',
primaryLight: '#6366f1',
success: '#10b981',
successLight: '#34d399',
warning: '#f59e0b',
warningLight: '#fbbf24',
purple: '#8b5cf6',
purpleLight: '#a78bfa',
};
const Analytics: React.FC = () => {
// 模拟用户增长数据
const userGrowthData = [
@@ -50,17 +63,17 @@ const Analytics: React.FC = () => {
// 订阅计划分布
const planDistribution = [
{ name: 'Free', value: 485, color: '#8884d8' },
{ name: 'Pro', value: 120, color: '#82ca9d' },
{ name: 'Enterprise', value: 15, color: '#ffc658' },
{ name: 'Free', value: 485, color: '#a78bfa' },
{ name: 'Pro', value: 120, color: '#34d399' },
{ name: 'Enterprise', value: 15, color: '#fbbf24' },
];
// 活跃度统计
const activityStats = [
{ metric: '日活跃用户 (DAU)', value: 892, growth: '+12.5%', icon: <UserAddOutlined /> },
{ metric: '月活跃用户 (MAU)', value: 3456, growth: '+8.3%', icon: <ArrowUpOutlined /> },
{ metric: '本月收入', value: 13365, prefix: '¥', growth: '+51.6%', icon: <DollarOutlined /> },
{ metric: '活跃项目数', value: 2341, growth: '+18.2%', icon: <ProjectOutlined /> },
{ metric: '日活跃用户 (DAU)', value: 892, growth: '+12.5%', icon: <UserAddOutlined />, colorClass: 'primary' },
{ metric: '月活跃用户 (MAU)', value: 3456, growth: '+8.3%', icon: <ArrowUpOutlined />, colorClass: 'success' },
{ metric: '本月收入', value: 13365, prefix: '¥', growth: '+51.6%', icon: <DollarOutlined />, colorClass: 'warning' },
{ metric: '活跃项目数', value: 2341, growth: '+18.2%', icon: <ProjectOutlined />, colorClass: 'purple' },
];
// 用户留存表格
@@ -80,107 +93,149 @@ const Analytics: React.FC = () => {
{ title: 'Day 90', dataIndex: 'day90', key: 'day90' },
];
const colorMap: Record<string, string> = {
primary: '#4f46e5',
success: '#10b981',
warning: '#f59e0b',
purple: '#8b5cf6',
};
return (
<div style={{ padding: '24px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
<h1></h1>
<Space>
<RangePicker />
</Space>
<div className="analytics-page">
<div className="xx-page-head">
<div className="xx-page-head-content">
<h2></h2>
<p></p>
</div>
<div className="xx-page-head-actions">
<Space>
<RangePicker className="xx-date-picker" />
</Space>
</div>
</div>
{/* 关键指标 */}
<Row gutter={16} style={{ marginBottom: 24 }}>
<Row gutter={24} className="xx-grid-4">
{activityStats.map((stat, index) => (
<Col span={6} key={index}>
<Card>
<Statistic
title={stat.metric}
value={stat.value}
prefix={stat.prefix}
suffix={stat.icon}
valueStyle={{ color: '#3f8600' }}
/>
<div style={{ marginTop: 8, color: '#52c41a', fontSize: 14 }}>
<div className="xx-stat-card">
<div className="xx-stat-card-header">
<div className={`xx-stat-card-icon ${stat.colorClass}`}>
{stat.icon}
</div>
</div>
<div className="xx-stat-card-label">{stat.metric}</div>
<div
className="xx-stat-card-value"
style={{ color: colorMap[stat.colorClass] }}
>
{stat.prefix}{stat.value.toLocaleString()}
</div>
<div className="xx-stat-card-growth">
{stat.growth} vs
</div>
</Card>
</div>
</Col>
))}
</Row>
{/* 图表区域 */}
<Row gutter={16} style={{ marginBottom: 24 }}>
<Row gutter={24} style={{ marginBottom: 24 }}>
{/* 用户增长趋势 */}
<Col span={16}>
<Card title="用户增长趋势">
<ResponsiveContainer width="100%" height={300}>
<LineChart data={userGrowthData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="users" stroke="#8884d8" name="总用户" />
<Line type="monotone" dataKey="paid" stroke="#82ca9d" name="付费用户" />
</LineChart>
</ResponsiveContainer>
</Card>
<div className="xx-chart-container">
<Card title="用户增长趋势">
<ResponsiveContainer width="100%" height={300}>
<LineChart data={userGrowthData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Legend />
<Line
type="monotone"
dataKey="users"
stroke={V21_COLORS.primary}
name="总用户"
strokeWidth={3}
/>
<Line
type="monotone"
dataKey="paid"
stroke={V21_COLORS.success}
name="付费用户"
strokeWidth={3}
/>
</LineChart>
</ResponsiveContainer>
</Card>
</div>
</Col>
{/* 订阅计划分布 */}
<Col span={8}>
<Card title="订阅计划分布">
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie
data={planDistribution}
cx="50%"
cy="50%"
labelLine={false}
label={(entry) => `${entry.name}: ${entry.value}`}
outerRadius={80}
fill="#8884d8"
dataKey="value"
>
{planDistribution.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip />
</PieChart>
</ResponsiveContainer>
</Card>
<div className="xx-chart-container">
<Card title="订阅计划分布">
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie
data={planDistribution}
cx="50%"
cy="50%"
labelLine={false}
label={(entry) => `${entry.name}: ${entry.value}`}
outerRadius={80}
fill="#8884d8"
dataKey="value"
>
{planDistribution.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip />
</PieChart>
</ResponsiveContainer>
</Card>
</div>
</Col>
</Row>
{/* 营收趋势 */}
<Row gutter={16} style={{ marginBottom: 24 }}>
<Row gutter={24} style={{ marginBottom: 24 }}>
<Col span={24}>
<Card title="营收趋势">
<ResponsiveContainer width="100%" height={300}>
<BarChart data={revenueData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="revenue" fill="#ffc658" name="收入 (¥)" />
</BarChart>
</ResponsiveContainer>
</Card>
<div className="xx-chart-container">
<Card title="营收趋势">
<ResponsiveContainer width="100%" height={300}>
<BarChart data={revenueData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Legend />
<Bar
dataKey="revenue"
fill={V21_COLORS.warning}
name="收入 (¥)"
radius={[8, 8, 0, 0]}
/>
</BarChart>
</ResponsiveContainer>
</Card>
</div>
</Col>
</Row>
{/* 用户留存表 */}
<Card title="用户留存率 (Cohort Analysis)">
<Table
columns={retentionColumns}
dataSource={retentionData}
rowKey="cohort"
pagination={false}
/>
</Card>
<div className="xx-table-wrapper">
<Card title="用户留存率 (Cohort Analysis)">
<Table
columns={retentionColumns}
dataSource={retentionData}
rowKey="cohort"
pagination={false}
/>
</Card>
</div>
</div>
);
};
+42 -24
View File
@@ -9,14 +9,15 @@ import {
DollarOutlined,
RiseOutlined,
} from '@ant-design/icons';
import './Admin.css';
const Dashboard: React.FC = () => {
// 模拟数据
const stats = [
{ title: '总用户数', value: 1234, icon: <UserOutlined />, color: '#1890ff' },
{ title: '付费用户', value: 156, icon: <CrownOutlined />, color: '#52c41a' },
{ title: '月收入', value: 15444, prefix: '¥', icon: <DollarOutlined />, color: '#faad14' },
{ title: '活跃用户', value: 892, icon: <RiseOutlined />, color: '#722ed1' },
{ title: '总用户数', value: 1234, icon: <UserOutlined />, colorClass: 'primary' },
{ title: '付费用户', value: 156, icon: <CrownOutlined />, colorClass: 'success' },
{ title: '月收入', value: 15444, prefix: '¥', icon: <DollarOutlined />, colorClass: 'warning' },
{ title: '活跃用户', value: 892, icon: <RiseOutlined />, colorClass: 'purple' },
];
const recentUsers = [
@@ -31,34 +32,51 @@ const Dashboard: React.FC = () => {
{ title: '注册时间', dataIndex: 'created_at', key: 'created_at' },
];
const colorMap: Record<string, string> = {
primary: '#4f46e5',
success: '#10b981',
warning: '#f59e0b',
purple: '#8b5cf6',
};
return (
<div style={{ padding: '24px' }}>
<h1>Dashboard</h1>
<div className="dashboard-page">
<div className="xx-page-head-simple">
<h2>Dashboard</h2>
<p></p>
</div>
<Row gutter={16} style={{ marginBottom: 24 }}>
<Row gutter={24} className="xx-grid-4">
{stats.map((stat, index) => (
<Col span={6} key={index}>
<Card>
<Statistic
title={stat.title}
value={stat.value}
prefix={stat.prefix}
valueStyle={{ color: stat.color }}
suffix={stat.icon}
/>
</Card>
<div className="xx-stat-card">
<div className="xx-stat-card-header">
<div className={`xx-stat-card-icon ${stat.colorClass}`}>
{stat.icon}
</div>
</div>
<div className="xx-stat-card-label">{stat.title}</div>
<div
className="xx-stat-card-value"
style={{ color: colorMap[stat.colorClass] }}
>
{stat.prefix}{stat.value.toLocaleString()}
</div>
</div>
</Col>
))}
</Row>
<Card title="最近注册用户">
<Table
columns={columns}
dataSource={recentUsers}
rowKey="id"
pagination={false}
/>
</Card>
<div className="xx-table-wrapper">
<Card title="最近注册用户" className="xx-card">
<Table
columns={columns}
dataSource={recentUsers}
rowKey="id"
pagination={false}
/>
</Card>
</div>
</div>
);
};
+85 -88
View File
@@ -10,6 +10,7 @@ import {
EyeOutlined,
} from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
import './Admin.css';
const { Search } = Input;
const { RangePicker } = DatePicker;
@@ -118,14 +119,14 @@ const LogViewer: React.FC = () => {
},
];
const getLevelColor = (level: string) => {
const colors: Record<string, string> = {
INFO: 'blue',
WARN: 'orange',
ERROR: 'red',
DEBUG: 'default',
const getLevelTagClass = (level: string) => {
const classes: Record<string, string> = {
INFO: 'xx-tag info',
WARN: 'xx-tag warning',
ERROR: 'xx-tag error',
DEBUG: 'xx-tag debug',
};
return colors[level] || 'default';
return classes[level] || 'xx-tag debug';
};
const handleViewDetails = (log: LogEntry) => {
@@ -146,7 +147,7 @@ const LogViewer: React.FC = () => {
dataIndex: 'level',
key: 'level',
width: 100,
render: (level: string) => <Tag color={getLevelColor(level)}>{level}</Tag>,
render: (level: string) => <Tag className={getLevelTagClass(level)}>{level}</Tag>,
filters: [
{ text: 'INFO', value: 'INFO' },
{ text: 'WARN', value: 'WARN' },
@@ -205,53 +206,61 @@ const LogViewer: React.FC = () => {
];
return (
<div style={{ padding: '24px' }}>
<h1></h1>
<div className="log-viewer-page">
<div className="xx-page-head-simple">
<h2></h2>
<p></p>
</div>
<Card style={{ marginBottom: 16 }}>
<Space size="middle" wrap>
<Search
placeholder="搜索日志内容..."
allowClear
enterButton={<SearchOutlined />}
style={{ width: 300 }}
/>
<div className="xx-filter-bar">
<Search
placeholder="搜索日志内容..."
allowClear
enterButton={<SearchOutlined />}
className="xx-search-input"
style={{ width: 280 }}
/>
<Select
placeholder="日志级别"
style={{ width: 120 }}
value={filterLevel}
onChange={setFilterLevel}
>
<Option value="all"></Option>
<Option value="INFO">INFO</Option>
<Option value="WARN">WARN</Option>
<Option value="ERROR">ERROR</Option>
<Option value="DEBUG">DEBUG</Option>
</Select>
<Select
placeholder="日志级别"
className="xx-select"
style={{ width: 130 }}
value={filterLevel}
onChange={setFilterLevel}
>
<Option value="all"></Option>
<Option value="INFO">INFO</Option>
<Option value="WARN">WARN</Option>
<Option value="ERROR">ERROR</Option>
<Option value="DEBUG">DEBUG</Option>
</Select>
<Select
placeholder="服务"
style={{ width: 120 }}
value={filterService}
onChange={setFilterService}
>
<Option value="all"></Option>
<Option value="API">API</Option>
<Option value="Database">Database</Option>
<Option value="Celery">Celery</Option>
<Option value="ObjectStorage">Object Storage</Option>
<Option value="Redis">Redis</Option>
</Select>
<Select
placeholder="服务"
className="xx-select"
style={{ width: 140 }}
value={filterService}
onChange={setFilterService}
>
<Option value="all"></Option>
<Option value="API">API</Option>
<Option value="Database">Database</Option>
<Option value="Celery">Celery</Option>
<Option value="ObjectStorage">Object Storage</Option>
<Option value="Redis">Redis</Option>
</Select>
<RangePicker showTime />
<RangePicker showTime className="xx-date-picker" />
<Button icon={<FilterOutlined />} disabled></Button>
<Button icon={<DownloadOutlined />} disabled></Button>
</Space>
</Card>
<Button icon={<FilterOutlined />} disabled className="xx-ghost-btn">
</Button>
<Button icon={<DownloadOutlined />} disabled className="xx-ghost-btn">
</Button>
</div>
<Card>
<div className="xx-table-wrapper">
<Table
columns={columns}
dataSource={logs}
@@ -264,7 +273,7 @@ const LogViewer: React.FC = () => {
}}
scroll={{ x: 1200 }}
/>
</Card>
</div>
{/* 日志详情抽屉 */}
<Drawer
@@ -273,67 +282,55 @@ const LogViewer: React.FC = () => {
width={600}
open={drawerVisible}
onClose={() => setDrawerVisible(false)}
className="xx-drawer"
>
{selectedLog && (
<div>
<div style={{ marginBottom: 16 }}>
<strong>:</strong>
<div style={{ marginTop: 4, color: '#595959' }}>{selectedLog.timestamp}</div>
<div className="xx-log-detail">
<div className="xx-log-detail-label"></div>
<div className="xx-log-detail-value">{selectedLog.timestamp}</div>
</div>
<div style={{ marginBottom: 16 }}>
<strong>:</strong>
<div style={{ marginTop: 4 }}>
<Tag color={getLevelColor(selectedLog.level)}>{selectedLog.level}</Tag>
<div className="xx-log-detail">
<div className="xx-log-detail-label"></div>
<div className="xx-log-detail-value">
<Tag className={getLevelTagClass(selectedLog.level)}>{selectedLog.level}</Tag>
</div>
</div>
<div style={{ marginBottom: 16 }}>
<strong>:</strong>
<div style={{ marginTop: 4, color: '#595959' }}>{selectedLog.service}</div>
<div className="xx-log-detail">
<div className="xx-log-detail-label"></div>
<div className="xx-log-detail-value">{selectedLog.service}</div>
</div>
{selectedLog.user && (
<div style={{ marginBottom: 16 }}>
<strong>:</strong>
<div style={{ marginTop: 4, color: '#595959' }}>{selectedLog.user}</div>
<div className="xx-log-detail">
<div className="xx-log-detail-label"></div>
<div className="xx-log-detail-value">{selectedLog.user}</div>
</div>
)}
<div style={{ marginBottom: 16 }}>
<strong>:</strong>
<div style={{ marginTop: 4, color: '#595959' }}>{selectedLog.action}</div>
<div className="xx-log-detail">
<div className="xx-log-detail-label"></div>
<div className="xx-log-detail-value">{selectedLog.action}</div>
</div>
<div style={{ marginBottom: 16 }}>
<strong>:</strong>
<div
style={{
marginTop: 4,
padding: 12,
background: '#f5f5f5',
borderRadius: 4,
color: '#595959',
whiteSpace: 'pre-wrap',
}}
>
{selectedLog.details}
</div>
<div className="xx-log-detail">
<div className="xx-log-detail-label"></div>
<div className="xx-log-detail-code">{selectedLog.details}</div>
</div>
{selectedLog.ip && (
<div style={{ marginBottom: 16 }}>
<strong>IP :</strong>
<div style={{ marginTop: 4, color: '#595959' }}>{selectedLog.ip}</div>
<div className="xx-log-detail">
<div className="xx-log-detail-label">IP </div>
<div className="xx-log-detail-value">{selectedLog.ip}</div>
</div>
)}
{selectedLog.request_id && (
<div style={{ marginBottom: 16 }}>
<strong>Request ID:</strong>
<div style={{ marginTop: 4, fontFamily: 'monospace', color: '#595959' }}>
{selectedLog.request_id}
</div>
<div className="xx-log-detail">
<div className="xx-log-detail-label">Request ID</div>
<div className="xx-log-detail-code">{selectedLog.request_id}</div>
</div>
)}
</div>
+79 -86
View File
@@ -13,6 +13,7 @@ import {
CloudServerOutlined,
ThunderboltOutlined,
} from '@ant-design/icons';
import './Admin.css';
const SystemMonitor: React.FC = () => {
const [refreshTime, setRefreshTime] = useState(new Date());
@@ -84,7 +85,7 @@ const SystemMonitor: React.FC = () => {
dataIndex: 'errors',
key: 'errors',
render: (errors: number) => (
<Tag color={errors > 10 ? 'red' : errors > 5 ? 'orange' : 'green'}>{errors}</Tag>
<Tag className={errors > 10 ? 'xx-tag error' : errors > 5 ? 'xx-tag warning' : 'xx-tag success'}>{errors}</Tag>
),
},
];
@@ -97,7 +98,7 @@ const SystemMonitor: React.FC = () => {
key: 'level',
width: 100,
render: (level: string) => (
<Tag color={level === 'ERROR' ? 'red' : 'orange'}>{level}</Tag>
<Tag className={level === 'ERROR' ? 'xx-tag error' : 'xx-tag warning'}>{level}</Tag>
),
},
{ title: '服务', dataIndex: 'service', key: 'service', width: 120 },
@@ -117,111 +118,103 @@ const SystemMonitor: React.FC = () => {
// 模拟实时更新
useEffect(() => {
const interval = setInterval(refreshMetrics, 5000);
return () => clearInterval(interval);
}, []);
const getProgressColor = (value: number) => {
if (value > 80) return '#ff4d4f';
if (value > 60) return '#faad14';
return '#52c41a';
if (value > 80) return '#ef4444';
if (value > 60) return '#f59e0b';
return '#10b981';
};
const getProgressClass = (value: number) => {
if (value > 80) return 'xx-progress-warning';
if (value > 60) return 'xx-progress-warning';
return 'xx-progress-success';
};
const resourceMetrics = [
{ key: 'cpu', label: 'CPU 使用率', value: systemMetrics.cpu, icon: <ThunderboltOutlined />, bg: 'linear-gradient(135deg, #6366f1, #4f46e5)' },
{ key: 'memory', label: '内存使用率', value: systemMetrics.memory, icon: <DatabaseOutlined />, bg: 'linear-gradient(135deg, #34d399, #10b981)' },
{ key: 'disk', label: '磁盘使用率', value: systemMetrics.disk, icon: <CloudServerOutlined />, bg: 'linear-gradient(135deg, #fbbf24, #f59e0b)' },
{ key: 'network', label: '网络使用率', value: systemMetrics.network, icon: <ApiOutlined />, bg: 'linear-gradient(135deg, #a78bfa, #8b5cf6)' },
];
return (
<div style={{ padding: '24px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
<h1></h1>
<Space>
<span style={{ color: '#8c8c8c' }}>: {refreshTime.toLocaleTimeString()}</span>
<Button icon={<ReloadOutlined />} onClick={refreshMetrics}></Button>
</Space>
<div className="system-monitor-page">
<div className="xx-page-head">
<div className="xx-page-head-content">
<h2></h2>
<p> API </p>
</div>
<div className="xx-page-head-actions">
<Space>
<span className="xx-refresh-time">: {refreshTime.toLocaleTimeString()}</span>
<Button icon={<ReloadOutlined />} onClick={refreshMetrics} className="xx-primary-btn">
</Button>
</Space>
</div>
</div>
{/* 系统资源监控 */}
<Row gutter={16} style={{ marginBottom: 24 }}>
<Col span={6}>
<Card>
<div style={{ textAlign: 'center' }}>
<ThunderboltOutlined style={{ fontSize: 32, color: '#1890ff', marginBottom: 8 }} />
<div style={{ marginBottom: 16 }}>CPU 使</div>
<Row gutter={24} className="xx-grid-4">
{resourceMetrics.map((metric) => (
<Col span={6} key={metric.key}>
<div className="xx-resource-card">
<div
className="xx-resource-card-icon"
style={{ background: metric.bg, color: 'white' }}
>
{metric.icon}
</div>
<div className="xx-resource-card-label">{metric.label}</div>
<Progress
type="circle"
percent={systemMetrics.cpu}
strokeColor={getProgressColor(systemMetrics.cpu)}
percent={metric.value}
strokeColor={getProgressColor(metric.value)}
className={getProgressClass(metric.value)}
/>
</div>
</Card>
</Col>
<Col span={6}>
<Card>
<div style={{ textAlign: 'center' }}>
<DatabaseOutlined style={{ fontSize: 32, color: '#52c41a', marginBottom: 8 }} />
<div style={{ marginBottom: 16 }}>使</div>
<Progress
type="circle"
percent={systemMetrics.memory}
strokeColor={getProgressColor(systemMetrics.memory)}
/>
</div>
</Card>
</Col>
<Col span={6}>
<Card>
<div style={{ textAlign: 'center' }}>
<CloudServerOutlined style={{ fontSize: 32, color: '#faad14', marginBottom: 8 }} />
<div style={{ marginBottom: 16 }}>使</div>
<Progress
type="circle"
percent={systemMetrics.disk}
strokeColor={getProgressColor(systemMetrics.disk)}
/>
</div>
</Card>
</Col>
<Col span={6}>
<Card>
<div style={{ textAlign: 'center' }}>
<ApiOutlined style={{ fontSize: 32, color: '#722ed1', marginBottom: 8 }} />
<div style={{ marginBottom: 16 }}>使</div>
<Progress
type="circle"
percent={systemMetrics.network}
strokeColor={getProgressColor(systemMetrics.network)}
/>
</div>
</Card>
</Col>
</Col>
))}
</Row>
{/* 服务健康状态 */}
<Card title="服务健康状态" style={{ marginBottom: 24 }}>
<Table
columns={serviceColumns}
dataSource={services}
rowKey="name"
pagination={false}
/>
</Card>
<div className="xx-table-wrapper" style={{ marginBottom: 24 }}>
<Card title="服务健康状态" className="xx-card">
<Table
columns={serviceColumns}
dataSource={services}
rowKey="name"
pagination={false}
/>
</Card>
</div>
{/* API 请求统计 */}
<Card title="API 请求统计 (最近 1 小时)" style={{ marginBottom: 24 }}>
<Table
columns={apiColumns}
dataSource={apiStats}
rowKey="endpoint"
pagination={false}
/>
</Card>
<div className="xx-table-wrapper" style={{ marginBottom: 24 }}>
<Card title="API 请求统计 (最近 1 小时)" className="xx-card">
<Table
columns={apiColumns}
dataSource={apiStats}
rowKey="endpoint"
pagination={false}
/>
</Card>
</div>
{/* 最近错误 */}
<Card title="最近错误日志">
<Table
columns={errorColumns}
dataSource={recentErrors}
rowKey="time"
pagination={false}
/>
</Card>
<div className="xx-table-wrapper">
<Card title="最近错误日志" className="xx-card">
<Table
columns={errorColumns}
dataSource={recentErrors}
rowKey="time"
pagination={false}
/>
</Card>
</div>
</div>
);
};
+23 -15
View File
@@ -4,6 +4,7 @@
import React from 'react';
import { Table, Button, Space, Tag, Input, Card } from 'antd';
import { SearchOutlined, LockOutlined, UnlockOutlined } from '@ant-design/icons';
import './Admin.css';
const { Search } = Input;
@@ -44,7 +45,7 @@ const UserManagement: React.FC = () => {
dataIndex: 'is_email_verified',
key: 'is_email_verified',
render: (verified: boolean) => (
<Tag color={verified ? 'success' : 'default'}>
<Tag className={verified ? 'xx-tag success' : 'xx-tag debug'}>
{verified ? '已验证' : '未验证'}
</Tag>
),
@@ -54,7 +55,7 @@ const UserManagement: React.FC = () => {
dataIndex: 'status',
key: 'status',
render: (status: string) => (
<Tag color={status === 'active' ? 'success' : 'error'}>
<Tag className={status === 'active' ? 'xx-tag success' : 'xx-tag error'}>
{status === 'active' ? '正常' : '已封禁'}
</Tag>
),
@@ -87,19 +88,26 @@ const UserManagement: React.FC = () => {
];
return (
<div style={{ padding: '24px' }}>
<h1></h1>
<Card>
<div style={{ marginBottom: 16 }}>
<Search
placeholder="搜索用户名或邮箱"
allowClear
enterButton={<SearchOutlined />}
style={{ width: 300 }}
/>
</div>
<Table columns={columns} dataSource={users} rowKey="id" />
</Card>
<div className="user-management-page">
<div className="xx-page-head-simple">
<h2></h2>
<p></p>
</div>
<div className="xx-table-wrapper">
<Card title="用户列表" className="xx-card">
<div style={{ marginBottom: 16 }}>
<Search
placeholder="搜索用户名或邮箱"
allowClear
enterButton={<SearchOutlined />}
className="xx-search-input"
style={{ width: 320 }}
/>
</div>
<Table columns={columns} dataSource={users} rowKey="id" />
</Card>
</div>
</div>
);
};