fix(): 修改整体布局
This commit is contained in:
83
src/App.css
83
src/App.css
@@ -34,7 +34,7 @@
|
|||||||
--bg-secondary: #ffffff;
|
--bg-secondary: #ffffff;
|
||||||
--panel: #ffffff;
|
--panel: #ffffff;
|
||||||
--panel-solid: #ffffff;
|
--panel-solid: #ffffff;
|
||||||
--soft: #f1f3f7;
|
--soft: #f8f9fc;
|
||||||
--text: #171a1f;
|
--text: #171a1f;
|
||||||
--text-secondary: #4d5562;
|
--text-secondary: #4d5562;
|
||||||
--muted: #6b7280;
|
--muted: #6b7280;
|
||||||
@@ -51,8 +51,9 @@
|
|||||||
--bad: #b6404a;
|
--bad: #b6404a;
|
||||||
--border: #e5e7ef;
|
--border: #e5e7ef;
|
||||||
--border-subtle: #f0f1f5;
|
--border-subtle: #f0f1f5;
|
||||||
--shadow: 0 10px 25px rgba(10,16,25,.08);
|
--shadow: 0 2px 8px rgba(10,16,25,.04);
|
||||||
--shadow-lg: 0 20px 40px rgba(10,16,25,.12);
|
--shadow-md: 0 4px 12px rgba(10,16,25,.06);
|
||||||
|
--shadow-lg: 0 8px 20px rgba(10,16,25,.08);
|
||||||
--shadow-colored: 0 10px 30px rgba(102, 126, 234, 0.15);
|
--shadow-colored: 0 10px 30px rgba(102, 126, 234, 0.15);
|
||||||
--glow: 0 0 20px rgba(102, 126, 234, 0.2);
|
--glow: 0 0 20px rgba(102, 126, 234, 0.2);
|
||||||
}
|
}
|
||||||
@@ -233,14 +234,67 @@ a{color:inherit; text-decoration:none}
|
|||||||
background:var(--panel); color:var(--text); box-shadow:var(--shadow);
|
background:var(--panel); color:var(--text); box-shadow:var(--shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.content{padding:28px 24px; display:flex; flex-direction:column; gap:20px; flex:1; overflow-y:auto; min-height:0; box-sizing:border-box}
|
.content{
|
||||||
|
padding:32px 28px;
|
||||||
|
display:flex;
|
||||||
|
flex-direction:column;
|
||||||
|
gap:24px;
|
||||||
|
flex:1;
|
||||||
|
overflow-y:auto;
|
||||||
|
min-height:0;
|
||||||
|
box-sizing:border-box;
|
||||||
|
max-width: 1600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页面标题样式 */
|
||||||
|
.page-header {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text);
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header .page-description {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
.panel{
|
.panel{
|
||||||
background:var(--panel); border:1px solid var(--border); border-radius:16px; padding:18px; box-shadow:var(--shadow);
|
background:var(--panel);
|
||||||
|
border:1px solid var(--border);
|
||||||
|
border-radius:12px;
|
||||||
|
padding:20px 24px;
|
||||||
|
box-shadow:var(--shadow);
|
||||||
width:100%;
|
width:100%;
|
||||||
|
transition: box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel:hover {
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel h2{
|
||||||
|
margin:0 0 12px 0;
|
||||||
|
font-size:16px;
|
||||||
|
font-weight:600;
|
||||||
|
color:var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel .desc{
|
||||||
|
color:var(--text-secondary);
|
||||||
|
font-size:13px;
|
||||||
|
margin-bottom:16px;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
.panel h2{margin:0 0 8px 0; font-size:16px}
|
|
||||||
.panel .desc{color:var(--muted); font-size:13px; margin-bottom:10px}
|
|
||||||
|
|
||||||
.flex-2{display:grid; grid-template-columns: 1fr 1fr; gap:16px}
|
.flex-2{display:grid; grid-template-columns: 1fr 1fr; gap:16px}
|
||||||
.flex-3{display:grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap:16px}
|
.flex-3{display:grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap:16px}
|
||||||
@@ -251,7 +305,7 @@ a{color:inherit; text-decoration:none}
|
|||||||
background: linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0.7) 100%);
|
background: linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0.7) 100%);
|
||||||
border:1px solid rgba(102, 126, 234, 0.1);
|
border:1px solid rgba(102, 126, 234, 0.1);
|
||||||
border-radius:16px;
|
border-radius:16px;
|
||||||
padding:18px;
|
padding:20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
@@ -280,21 +334,22 @@ a{color:inherit; text-decoration:none}
|
|||||||
.kpi:nth-child(2)::before { background: var(--success-gradient); }
|
.kpi:nth-child(2)::before { background: var(--success-gradient); }
|
||||||
.kpi:nth-child(3)::before { background: var(--warning-gradient); }
|
.kpi:nth-child(3)::before { background: var(--warning-gradient); }
|
||||||
.kpi:nth-child(4)::before { background: var(--secondary-gradient); }
|
.kpi:nth-child(4)::before { background: var(--secondary-gradient); }
|
||||||
.kpi h3{margin:0; font-size:13px; color:var(--muted); font-weight: 600; letter-spacing: 0.3px;}
|
.kpi h3{margin:0; font-size:12px; color:var(--muted); font-weight: 600; letter-spacing: 0.5px; text-transform: uppercase;}
|
||||||
.kpi .num{
|
.kpi .num{
|
||||||
font-size:28px;
|
font-size:32px;
|
||||||
font-weight:900;
|
font-weight:800;
|
||||||
margin-top:8px;
|
margin-top:12px;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
}
|
}
|
||||||
.kpi .delta{font-size:12px; margin-left:8px; padding:2px 6px; border-radius:8px; background:var(--panel); border:1px solid var(--border)}
|
.kpi .delta{font-size:12px; margin-left:8px; padding:2px 6px; border-radius:8px; background:var(--panel); border:1px solid var(--border)}
|
||||||
|
|
||||||
.table{width:100%; border-collapse:collapse; font-size:14px; min-width:800px; table-layout:fixed; box-sizing:border-box}
|
.table{width:100%; border-collapse:collapse; font-size:14px; min-width:800px; table-layout:fixed; box-sizing:border-box}
|
||||||
.table th, .table td{padding:10px 12px; border-bottom:1px solid var(--border); text-align:left; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
|
.table th, .table td{padding:12px 16px; border-bottom:1px solid var(--border); text-align:left; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
|
||||||
.table thead th{color:var(--muted); font-weight:600}
|
.table thead th{color:var(--muted); font-weight:600; font-size:13px; text-transform: uppercase; letter-spacing: 0.5px;}
|
||||||
.table-container{overflow-x:auto; width:100%; max-width:100%; -webkit-overflow-scrolling:touch; box-sizing:border-box}
|
.table-container{overflow-x:auto; width:100%; max-width:100%; -webkit-overflow-scrolling:touch; box-sizing:border-box}
|
||||||
.table th:nth-child(1), .table td:nth-child(1){width:8%}
|
.table th:nth-child(1), .table td:nth-child(1){width:8%}
|
||||||
.table th:nth-child(2), .table td:nth-child(2){width:15%}
|
.table th:nth-child(2), .table td:nth-child(2){width:15%}
|
||||||
|
|||||||
@@ -165,6 +165,9 @@ const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|||||||
<DynamicMenu collapsed={collapsed} />
|
<DynamicMenu collapsed={collapsed} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 间隔区域 - 将登出按钮推到底部 */}
|
||||||
|
<div style={{ flex: 1 }}></div>
|
||||||
|
|
||||||
{/* 用户登录状态和登出按钮 */}
|
{/* 用户登录状态和登出按钮 */}
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: collapsed ? '12px 8px' : '12px 16px',
|
padding: collapsed ? '12px 8px' : '12px 16px',
|
||||||
@@ -214,16 +217,6 @@ const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{
|
|
||||||
fontSize: '12px',
|
|
||||||
color: 'var(--muted)',
|
|
||||||
textAlign: collapsed ? 'center' : 'left',
|
|
||||||
padding: '12px 16px',
|
|
||||||
borderTop: '1px solid var(--border-subtle)'
|
|
||||||
}}>
|
|
||||||
{!collapsed ? 'v1.1 · React Admin' : 'v1.1'}
|
|
||||||
</div>
|
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
{/* 主内容区 */}
|
{/* 主内容区 */}
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ import React from 'react';
|
|||||||
|
|
||||||
const Finance: React.FC = () => {
|
const Finance: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<section className="panel">
|
<div>
|
||||||
<h2>财务管理</h2>
|
<div className="page-header">
|
||||||
<div className="desc">收入统计、成本分析、财务报表</div>
|
<h1>财务管理</h1>
|
||||||
|
<p className="page-description">收入统计、成本分析、财务报表</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="kpis">
|
<div className="kpis">
|
||||||
<div className="kpi">
|
<div className="kpi">
|
||||||
<h3>本月收入</h3>
|
<h3>本月收入</h3>
|
||||||
@@ -40,7 +42,7 @@ const Finance: React.FC = () => {
|
|||||||
<h2>财务报表</h2>
|
<h2>财务报表</h2>
|
||||||
<div className="chart">详细财务报表占位</div>
|
<div className="chart">详细财务报表占位</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ const FinanceTransactionLogs: React.FC = () => {
|
|||||||
style={{ marginBottom: 20 }}
|
style={{ marginBottom: 20 }}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
||||||
{/* 第一行:用户ID、订单编号、流水类型、状态、时间范围 */}
|
{/* 第一行:用户ID、订单编号、流水类型 */}
|
||||||
<div style={{ display: 'flex', gap: '16px', flexWrap: 'wrap', alignItems: 'center' }}>
|
<div style={{ display: 'flex', gap: '16px', flexWrap: 'wrap', alignItems: 'center' }}>
|
||||||
<Form.Item name="user_id" label="用户PID:" style={{ margin: 0, flex: '0 0 auto' }}>
|
<Form.Item name="user_id" label="用户PID:" style={{ margin: 0, flex: '0 0 auto' }}>
|
||||||
<Input allowClear placeholder="用户ID" style={{ width: 200 }} />
|
<Input allowClear placeholder="用户ID" style={{ width: 200 }} />
|
||||||
@@ -73,7 +73,7 @@ const FinanceTransactionLogs: React.FC = () => {
|
|||||||
<Input allowClear placeholder="订单编号" style={{ width: 200 }} />
|
<Input allowClear placeholder="订单编号" style={{ width: 200 }} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="type" label="流水类型:" style={{ margin: 0, flex: '0 0 auto' }}>
|
<Form.Item name="type" label="流水类型:" style={{ margin: 0, flex: '0 0 auto' }}>
|
||||||
<Select allowClear placeholder="充值/退款/消费" style={{ width: 220 }}
|
<Select allowClear placeholder="充值/退款/消费" style={{ width: 200 }}
|
||||||
options={[
|
options={[
|
||||||
{ label: 'recharge', value: 'recharge' },
|
{ label: 'recharge', value: 'recharge' },
|
||||||
{ label: 'refund', value: 'refund' },
|
{ label: 'refund', value: 'refund' },
|
||||||
@@ -81,8 +81,12 @@ const FinanceTransactionLogs: React.FC = () => {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 第二行:状态、时间范围和按钮 */}
|
||||||
|
<div style={{ display: 'flex', gap: '16px', alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
<Form.Item name="status" label="状态:" style={{ margin: 0, flex: '0 0 auto' }}>
|
<Form.Item name="status" label="状态:" style={{ margin: 0, flex: '0 0 auto' }}>
|
||||||
<Select allowClear placeholder="成功/失败/待定" style={{ width: 220 }}
|
<Select allowClear placeholder="成功/失败/待定" style={{ width: 200 }}
|
||||||
options={[
|
options={[
|
||||||
{ label: 'success', value: 'success' },
|
{ label: 'success', value: 'success' },
|
||||||
{ label: 'failed', value: 'failed' },
|
{ label: 'failed', value: 'failed' },
|
||||||
@@ -90,10 +94,6 @@ const FinanceTransactionLogs: React.FC = () => {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 第二行:时间范围和按钮 */}
|
|
||||||
<div style={{ display: 'flex', gap: '16px', alignItems: 'center', flexWrap: 'wrap' }}>
|
|
||||||
<Form.Item name="range" label="时间范围:" style={{ margin: 0, flex: '0 0 auto' }}>
|
<Form.Item name="range" label="时间范围:" style={{ margin: 0, flex: '0 0 auto' }}>
|
||||||
<DatePicker.RangePicker style={{ width: 300 }} />
|
<DatePicker.RangePicker style={{ width: 300 }} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -550,7 +550,7 @@ const InviteCodes: React.FC = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '24px' }}>
|
<div>
|
||||||
<Card title="邀请码管理" style={{ marginBottom: 16 }}>
|
<Card title="邀请码管理" style={{ marginBottom: 16 }}>
|
||||||
{/* 统计信息 */}
|
{/* 统计信息 */}
|
||||||
{statistics && (
|
{statistics && (
|
||||||
|
|||||||
@@ -1,63 +1,21 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React from 'react';
|
||||||
import { QuotaApiService } from '../services/api';
|
|
||||||
|
|
||||||
interface QuotaHistoryItem {
|
|
||||||
id: number;
|
|
||||||
user_id: string;
|
|
||||||
api_group: string;
|
|
||||||
project_id: string;
|
|
||||||
day: string;
|
|
||||||
account: string;
|
|
||||||
model: string;
|
|
||||||
quota_used: number;
|
|
||||||
quota_used_text?: string;
|
|
||||||
created_at: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Monitoring: React.FC = () => {
|
const Monitoring: React.FC = () => {
|
||||||
const [quotaData, setQuotaData] = useState<QuotaHistoryItem[]>([]);
|
// 静态统计数据
|
||||||
const [startDate, setStartDate] = useState('');
|
const stats = {
|
||||||
const [endDate, setEndDate] = useState('');
|
totalQuota: 0,
|
||||||
|
totalUsers: 0,
|
||||||
// 初始化日期范围(最近30天)
|
totalProjects: 0,
|
||||||
useEffect(() => {
|
totalModels: 0,
|
||||||
const today = new Date();
|
};
|
||||||
const thirtyDaysAgo = new Date();
|
|
||||||
thirtyDaysAgo.setDate(today.getDate() - 30);
|
|
||||||
|
|
||||||
setEndDate(today.toISOString().split('T')[0]);
|
|
||||||
setStartDate(thirtyDaysAgo.toISOString().split('T')[0]);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 计算统计数据
|
|
||||||
const stats = React.useMemo(() => {
|
|
||||||
if (quotaData.length === 0) {
|
|
||||||
return {
|
|
||||||
totalQuota: 0,
|
|
||||||
totalUsers: 0,
|
|
||||||
totalProjects: 0,
|
|
||||||
totalModels: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalQuota = quotaData.reduce((sum, item) => sum + item.quota_used, 0);
|
|
||||||
const uniqueUsers = new Set(quotaData.map(item => item.user_id)).size;
|
|
||||||
const uniqueProjects = new Set(quotaData.map(item => item.project_id)).size;
|
|
||||||
const uniqueModels = new Set(quotaData.map(item => item.model)).size;
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalQuota,
|
|
||||||
totalUsers: uniqueUsers,
|
|
||||||
totalProjects: uniqueProjects,
|
|
||||||
totalModels: uniqueModels,
|
|
||||||
};
|
|
||||||
}, [quotaData]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="panel">
|
<div>
|
||||||
<h2>Token 使用概览</h2>
|
<div className="page-header">
|
||||||
<div className="desc">总使用量、活跃用户、项目数量、模型统计</div>
|
<h1>Token 使用概览</h1>
|
||||||
|
<p className="page-description">总使用量、活跃用户、项目数量、模型统计</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="kpis">
|
<div className="kpis">
|
||||||
<div className="kpi">
|
<div className="kpi">
|
||||||
<h3>总 Token 使用量</h3>
|
<h3>总 Token 使用量</h3>
|
||||||
@@ -76,7 +34,7 @@ const Monitoring: React.FC = () => {
|
|||||||
<div className="num">{stats.totalModels}</div>
|
<div className="num">{stats.totalModels}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
const Operations: React.FC = () => {
|
const Operations: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
return (
|
return (
|
||||||
<section className="panel">
|
<div>
|
||||||
<h2>运营管理</h2>
|
<div className="page-header">
|
||||||
<div className="desc">用户运营、内容管理、活动推广</div>
|
<h1>运营管理</h1>
|
||||||
|
<p className="page-description">用户运营、内容管理、活动推广</p>
|
||||||
<div style={{ margin: '16px 0' }}>
|
</div>
|
||||||
<Card>
|
|
||||||
|
<div style={{ marginBottom: '24px' }}>
|
||||||
|
<Card bordered={false}>
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Button type="primary" onClick={() => navigate('/mcp-provider-pricing')}>
|
<Button type="primary" onClick={() => navigate('/mcp-provider-pricing')}>
|
||||||
MCP 价格配置
|
MCP 价格配置
|
||||||
@@ -44,7 +46,7 @@ const Operations: React.FC = () => {
|
|||||||
<div className="chart">活动效果统计占位</div>
|
<div className="chart">活动效果统计占位</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ import React from 'react';
|
|||||||
|
|
||||||
const Overview: React.FC = () => {
|
const Overview: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<section className="panel">
|
<div>
|
||||||
<h2>系统总览</h2>
|
<div className="page-header">
|
||||||
<div className="desc">系统整体运行状态、关键指标概览</div>
|
<h1>系统总览</h1>
|
||||||
|
<p className="page-description">系统整体运行状态、关键指标概览</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="kpis">
|
<div className="kpis">
|
||||||
<div className="kpi">
|
<div className="kpi">
|
||||||
<h3>总用户数</h3>
|
<h3>总用户数</h3>
|
||||||
@@ -21,10 +23,10 @@ const Overview: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="kpi">
|
<div className="kpi">
|
||||||
<h3>系统状态</h3>
|
<h3>系统状态</h3>
|
||||||
<div className="num" style={{color: 'var(--success)'}}>正常</div>
|
<div className="num" style={{color: 'var(--good)'}}>正常</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-2">
|
<div className="flex-2">
|
||||||
<div className="panel">
|
<div className="panel">
|
||||||
<h2>最近7天用户活跃度</h2>
|
<h2>最近7天用户活跃度</h2>
|
||||||
@@ -35,7 +37,7 @@ const Overview: React.FC = () => {
|
|||||||
<div className="chart">模型使用量排行占位</div>
|
<div className="chart">模型使用量排行占位</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ const UserFeedback: React.FC = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '24px' }}>
|
<div>
|
||||||
<Title level={2}>用户反馈管理</Title>
|
<Title level={2}>用户反馈管理</Title>
|
||||||
|
|
||||||
{/* 统计卡片 */}
|
{/* 统计卡片 */}
|
||||||
|
|||||||
@@ -378,8 +378,13 @@ const UserManagement: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<div className="page-header">
|
||||||
|
<h1>用户管理</h1>
|
||||||
|
<p className="page-description">后台用户账号管理、权限分配、状态控制</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 统计卡片 */}
|
{/* 统计卡片 */}
|
||||||
<Row gutter={16} style={{ marginBottom: 16 }}>
|
<Row gutter={16} style={{ marginBottom: 24 }}>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Card>
|
<Card>
|
||||||
<Statistic
|
<Statistic
|
||||||
@@ -423,15 +428,18 @@ const UserManagement: React.FC = () => {
|
|||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
{/* 操作栏 */}
|
{/* 操作栏 */}
|
||||||
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between' }}>
|
<Card bordered={false} style={{ marginBottom: 16 }}>
|
||||||
<h2>用户管理</h2>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<Button type="primary" icon={<SettingOutlined />} onClick={handleOpenChangeRole}>
|
<h3 style={{ margin: 0, fontSize: '16px', fontWeight: 600 }}>用户列表</h3>
|
||||||
变更用户角色
|
<Button type="primary" icon={<SettingOutlined />} onClick={handleOpenChangeRole}>
|
||||||
</Button>
|
变更用户角色
|
||||||
</div>
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* 用户表格 */}
|
{/* 用户表格 */}
|
||||||
<Table
|
<Card bordered={false}>
|
||||||
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={users}
|
dataSource={users}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
@@ -449,7 +457,8 @@ const UserManagement: React.FC = () => {
|
|||||||
fetchUsers(current, size);
|
fetchUsers(current, size);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* 用户表单弹窗 */}
|
{/* 用户表单弹窗 */}
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
Reference in New Issue
Block a user