前端重构

This commit is contained in:
Yvan 2026-01-08 14:11:10 +08:00
parent cf16ab6448
commit b770c4a3b6
12 changed files with 912 additions and 329 deletions

View File

@ -136,7 +136,7 @@ const AboutPage: React.FC = () => {
{/* 头部信息 */} {/* 头部信息 */}
<Card className={styles.headerCard} variant="borderless"> <Card className={styles.headerCard} variant="borderless">
<div className={styles.logoWrapper}> <div className={styles.logoWrapper}>
<Logo size={80} /> <Logo size="large" />
</div> </div>
<Title level={2} className={styles.title}> <Title level={2} className={styles.title}>
Kratos Admin Kratos Admin

View File

@ -1,36 +1,57 @@
/** /**
* KRA - Dashboard Banner Component * KRA - Dashboard Banner Component
* GVA banner.vue -
*/ */
import React from 'react'; import React from 'react';
import { Card } from 'antd'; import { Carousel } from 'antd';
import Logo from '@/components/Logo/Logo';
interface BannerItem {
img: string;
link: string;
}
const banners: BannerItem[] = [
{
img: 'https://go-kratos.dev/img/logo.svg',
link: 'https://go-kratos.dev/'
},
{
img: 'https://avatars.githubusercontent.com/u/68029786?s=200&v=4',
link: 'https://github.com/go-kratos/kratos'
},
];
const Banner: React.FC = () => { const Banner: React.FC = () => {
const openLink = (link: string) => {
window.open(link, '_blank');
};
return ( return (
<Card <div style={{ marginTop: '-8px', height: '160px', overflow: 'hidden' }}>
className="kra-dashboard-card" <Carousel autoplay>
styles={{ body: { padding: 0, height: '160px', overflow: 'hidden' } }} {banners.map((item, index) => (
variant="borderless" <div key={index}>
> <div
<div style={{ onClick={() => openLink(item.link)}
height: '100%', style={{
background: 'linear-gradient(135deg, #1890ff 0%, #722ed1 100%)', height: '160px',
display: 'flex', cursor: 'pointer',
flexDirection: 'column', background: 'linear-gradient(135deg, #1890ff 0%, #722ed1 100%)',
alignItems: 'center', display: 'flex',
justifyContent: 'center', alignItems: 'center',
color: '#fff', justifyContent: 'center',
padding: '24px', }}
}}> >
<Logo size="large" /> <img
<h3 style={{ margin: '12px 0 4px', fontSize: '18px', fontWeight: 600 }}> src={item.img}
Kratos Admin alt="banner"
</h3> style={{ maxHeight: '80px', maxWidth: '80%', objectFit: 'contain' }}
<p style={{ margin: 0, fontSize: '12px', opacity: 0.85 }}> />
</div>
</p> </div>
</div> ))}
</Card> </Carousel>
</div>
); );
}; };

View File

@ -0,0 +1,86 @@
/**
* KRA - Mini Line Chart Component
* 线
*/
import React, { useEffect, useRef } from 'react';
interface MiniLineChartProps {
data: number[];
color?: string;
}
const MiniLineChart: React.FC<MiniLineChartProps> = ({ data, color = '#1890ff' }) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const width = canvas.width;
const height = canvas.height;
const padding = 10;
// 清空画布
ctx.clearRect(0, 0, width, height);
if (data.length < 2) return;
const max = Math.max(...data);
const min = Math.min(...data);
const range = max - min || 1;
const stepX = (width - padding * 2) / (data.length - 1);
// 绘制渐变区域
const gradient = ctx.createLinearGradient(0, 0, 0, height);
gradient.addColorStop(0, `${color}40`);
gradient.addColorStop(1, `${color}00`);
ctx.beginPath();
ctx.moveTo(padding, height - padding);
data.forEach((value, index) => {
const x = padding + index * stepX;
const y = height - padding - ((value - min) / range) * (height - padding * 2);
if (index === 0) {
ctx.lineTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
ctx.lineTo(padding + (data.length - 1) * stepX, height - padding);
ctx.closePath();
ctx.fillStyle = gradient;
ctx.fill();
// 绘制折线
ctx.beginPath();
data.forEach((value, index) => {
const x = padding + index * stepX;
const y = height - padding - ((value - min) / range) * (height - padding * 2);
if (index === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
ctx.strokeStyle = color;
ctx.lineWidth = 2;
ctx.stroke();
}, [data, color]);
return (
<canvas
ref={canvasRef}
width={150}
height={80}
style={{ width: '100%', height: '100%' }}
/>
);
};
export default MiniLineChart;

View File

@ -1,61 +1,100 @@
/** /**
* KRA - Dashboard Notice Component * KRA - Dashboard Notice Component
* GVA notice.vue
*/ */
import React from 'react'; import React from 'react';
import { Card, List, Tag, Typography } from 'antd'; import { Card, Tag, Tooltip } from 'antd';
import { NotificationOutlined } from '@ant-design/icons';
const { Text } = Typography;
interface NoticeItem { interface NoticeItem {
id: number; type: 'primary' | 'success' | 'warning' | 'danger' | 'info';
typeTitle: string;
title: string; title: string;
type: 'info' | 'warning' | 'success';
time: string;
} }
const notices: NoticeItem[] = [ const notices: NoticeItem[] = [
{ id: 1, title: '系统将于今晚进行维护升级', type: 'warning', time: '2小时前' }, {
{ id: 2, title: '新版本 v1.0.0 已发布', type: 'success', time: '1天前' }, type: 'primary',
{ id: 3, title: '欢迎使用 Kratos Admin', type: 'info', time: '3天前' }, typeTitle: '公告',
title: '欢迎使用 Kratos Admin 管理系统。'
},
{
type: 'success',
typeTitle: '通知',
title: '系统已完成最新版本升级。'
},
{
type: 'warning',
typeTitle: '警告',
title: '请定期备份重要数据。'
},
{
type: 'danger',
typeTitle: '违规',
title: '请勿在生产环境使用默认密码。'
},
{
type: 'info',
typeTitle: '信息',
title: '感谢您对开源事业的支持。'
},
{
type: 'primary',
typeTitle: '公告',
title: '让创意更有价值。'
},
{
type: 'success',
typeTitle: '通知',
title: '让劳动更有意义。'
},
{
type: 'warning',
typeTitle: '警告',
title: '让思维更有深度。'
},
{
type: 'danger',
typeTitle: '错误',
title: '让生活更有趣味。'
},
{
type: 'info',
typeTitle: '信息',
title: '让公司更有活力。'
}
]; ];
const typeColors = { const typeColors: Record<string, string> = {
info: 'blue', primary: 'blue',
warning: 'orange',
success: 'green', success: 'green',
warning: 'orange',
danger: 'red',
info: 'default',
}; };
const Notice: React.FC = () => { const Notice: React.FC = () => {
return ( return (
<Card <Card title="公告" className="kra-dashboard-card" variant="borderless" extra={<a href="#"></a>}>
title="公告" <div style={{ maxHeight: '200px', overflow: 'auto' }}>
className="kra-dashboard-card" {notices.map((item, index) => (
variant="borderless" <div key={index} style={{ display: 'flex', alignItems: 'center', marginBottom: '6px', gap: '12px' }}>
extra={<a href="#"></a>} <Tag color={typeColors[item.type]} style={{ margin: 0, flexShrink: 0 }}>
> {item.typeTitle}
<List </Tag>
size="small" <Tooltip title={item.title} placement="top">
dataSource={notices} <div style={{
renderItem={(item) => ( fontSize: '12px',
<List.Item style={{ padding: '8px 0' }}> color: '#666',
<div style={{ width: '100%' }}> overflow: 'hidden',
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '4px' }}> textOverflow: 'ellipsis',
<NotificationOutlined style={{ color: '#999', fontSize: '12px' }} /> whiteSpace: 'nowrap'
<Text ellipsis style={{ flex: 1, fontSize: '13px' }}> }}>
{item.title} {item.title}
</Text>
</div> </div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> </Tooltip>
<Tag color={typeColors[item.type]} style={{ margin: 0 }}> </div>
{item.type === 'info' ? '通知' : item.type === 'warning' ? '警告' : '成功'} ))}
</Tag> </div>
<Text type="secondary" style={{ fontSize: '12px' }}>{item.time}</Text>
</div>
</div>
</List.Item>
)}
/>
</Card> </Card>
); );
}; };

View File

@ -1,133 +1,98 @@
/** /**
* KRA - Dashboard Plugin Table Component * KRA - Dashboard Plugin Table Component
* GVA pluginTable.vue -
*/ */
import React from 'react'; import React from 'react';
import { Card, Table, Tag, Space, Button } from 'antd'; import { Card, Table } from 'antd';
import type { ColumnsType } from 'antd/es/table'; import type { ColumnsType } from 'antd/es/table';
import { GithubOutlined, LinkOutlined } from '@ant-design/icons';
interface PluginItem { interface PluginItem {
key: string; ranking: number;
name: string; title: string;
description: string; click_num: number;
version: string; hot: number;
status: 'active' | 'inactive' | 'beta'; link: string;
author: string;
} }
const plugins: PluginItem[] = [ const tableData: PluginItem[] = [
{ {
key: '1', ranking: 1,
name: '公告管理', title: '组织管理插件:更方便管理组织,分配资源权限。',
description: '系统公告发布与管理', click_num: 523,
version: 'v1.0.0', hot: 263,
status: 'active', link: 'https://go-kratos.dev/'
author: 'KRA Team',
}, },
{ {
key: '2', ranking: 2,
name: '邮件服务', title: 'Kubernetes容器管理Kubernetes 原生资源管理提供炫酷的YAML编辑Pod终端。',
description: '邮件配置与发送服务', click_num: 416,
version: 'v1.0.0', hot: 223,
status: 'active', link: 'https://go-kratos.dev/'
author: 'KRA Team',
}, },
{ {
key: '3', ranking: 3,
name: '代码生成器', title: '定时任务配置化管理:本插件用于对系统内部的定时任务进行配置化管理。',
description: '自动生成 CRUD 代码', click_num: 337,
version: 'v0.9.0', hot: 176,
status: 'beta', link: 'https://go-kratos.dev/'
author: 'KRA Team',
}, },
{ {
key: '4', ranking: 4,
name: '文件管理', title: '官网CMS系统基于Kratos开发的企业官网类CMS系统。',
description: '文件上传与管理', click_num: 292,
version: 'v1.0.0', hot: 145,
status: 'active', link: 'https://go-kratos.dev/'
author: 'KRA Team',
}, },
{
ranking: 5,
title: '微信支付插件:提供扫码支付功能(需自行对接业务)。',
click_num: 173,
hot: 110,
link: 'https://go-kratos.dev/'
}
]; ];
const statusColors = {
active: 'green',
inactive: 'default',
beta: 'orange',
};
const statusLabels = {
active: '已启用',
inactive: '未启用',
beta: '测试中',
};
const columns: ColumnsType<PluginItem> = [ const columns: ColumnsType<PluginItem> = [
{ {
title: '插件名称', title: '排名',
dataIndex: 'name', dataIndex: 'ranking',
key: 'name', width: 80,
width: 120, align: 'center',
}, },
{ {
title: '描述', title: '插件标题',
dataIndex: 'description', dataIndex: 'title',
key: 'description',
ellipsis: true, ellipsis: true,
}, render: (text, record) => (
{ <a
title: '版本', href={record.link}
dataIndex: 'version', target="_blank"
key: 'version', rel="noopener noreferrer"
width: 80, style={{ color: '#1890ff' }}
}, >
{ {text}
title: '状态', </a>
dataIndex: 'status',
key: 'status',
width: 80,
render: (status: keyof typeof statusColors) => (
<Tag color={statusColors[status]}>{statusLabels[status]}</Tag>
), ),
}, },
{ {
title: '作者', title: '关注度',
dataIndex: 'author', dataIndex: 'click_num',
key: 'author', width: 100,
},
{
title: '热度值',
dataIndex: 'hot',
width: 100, width: 100,
}, },
]; ];
const PluginTable: React.FC = () => { const PluginTable: React.FC = () => {
return ( return (
<Card <Card title="最新插件" className="kra-dashboard-card" variant="borderless">
title="最新插件"
className="kra-dashboard-card"
variant="borderless"
extra={
<Space>
<Button
type="link"
icon={<GithubOutlined />}
href="https://github.com/go-kratos/kratos"
target="_blank"
>
GitHub
</Button>
<Button
type="link"
icon={<LinkOutlined />}
href="https://go-kratos.dev/"
target="_blank"
>
</Button>
</Space>
}
>
<Table <Table
columns={columns} columns={columns}
dataSource={plugins} dataSource={tableData}
rowKey="ranking"
pagination={false} pagination={false}
size="small" size="small"
/> />

View File

@ -1,83 +1,100 @@
/** /**
* KRA - Dashboard Quick Links Component * KRA - Dashboard Quick Links Component
* GVA quickLinks.vue
*/ */
import React from 'react'; import React from 'react';
import { Card, Row, Col } from 'antd'; import { Card, Row, Col } from 'antd';
import { import {
UserOutlined, MenuOutlined,
SettingOutlined, LinkOutlined,
FileTextOutlined,
DatabaseOutlined,
ApiOutlined,
SafetyOutlined, SafetyOutlined,
UserOutlined,
FolderOutlined,
CodeOutlined,
ReadOutlined,
AppstoreOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { history } from '@umijs/max'; import { history } from '@umijs/max';
interface QuickLink { interface ShortcutItem {
title: string;
icon: React.ReactNode; icon: React.ReactNode;
title: string;
path: string; path: string;
color: string; isExternal?: boolean;
} }
const links: QuickLink[] = [ // 内部快捷功能
{ title: '用户管理', icon: <UserOutlined />, path: '/system/user', color: '#1890ff' }, const shortcuts: ShortcutItem[] = [
{ title: '角色管理', icon: <SafetyOutlined />, path: '/system/authority', color: '#52c41a' }, { icon: <MenuOutlined />, title: '菜单管理', path: '/system/menu' },
{ title: '菜单管理', icon: <FileTextOutlined />, path: '/system/menu', color: '#faad14' }, { icon: <LinkOutlined />, title: 'API管理', path: '/system/api' },
{ title: 'API管理', icon: <ApiOutlined />, path: '/system/api', color: '#722ed1' }, { icon: <SafetyOutlined />, title: '角色管理', path: '/system/authority' },
{ title: '字典管理', icon: <DatabaseOutlined />, path: '/system/dictionary', color: '#eb2f96' }, { icon: <UserOutlined />, title: '用户管理', path: '/system/user' },
{ title: '系统配置', icon: <SettingOutlined />, path: '/systemTools/system', color: '#13c2c2' }, { icon: <FolderOutlined />, title: '自动化包', path: '/systemTools/autoCodeAdmin' },
{ icon: <CodeOutlined />, title: '自动代码', path: '/systemTools/autoCode' },
];
// 外部链接
const externalLinks: ShortcutItem[] = [
{ icon: <ReadOutlined />, title: 'Kratos文档', path: 'https://go-kratos.dev/', isExternal: true },
{ icon: <AppstoreOutlined />, title: 'GitHub', path: 'https://github.com/go-kratos/kratos', isExternal: true },
]; ];
const QuickLinks: React.FC = () => { const QuickLinks: React.FC = () => {
const handleClick = (path: string) => { const handleClick = (item: ShortcutItem) => {
history.push(path); if (item.isExternal) {
window.open(item.path, '_blank');
} else {
history.push(item.path);
}
}; };
const renderItem = (item: ShortcutItem, index: number) => (
<Col span={8} key={index}>
<div
onClick={() => handleClick(item)}
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
marginBottom: '12px',
cursor: 'pointer',
}}
className="quick-link-item"
>
<div
style={{
width: '32px',
height: '32px',
borderRadius: '4px',
background: '#f0f0f0',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transition: 'all 0.2s',
}}
className="quick-link-icon"
>
{item.icon}
</div>
<div style={{ fontSize: '12px', marginTop: '8px', color: '#666' }}>
{item.title}
</div>
</div>
</Col>
);
return ( return (
<Card <Card title="快捷功能" className="kra-dashboard-card" variant="borderless" extra={<a href="#"></a>}>
title="快捷功能" <div style={{ marginTop: '32px' }}>
className="kra-dashboard-card" <Row>{shortcuts.map(renderItem)}</Row>
variant="borderless" <Row style={{ marginTop: '32px' }}>{externalLinks.map(renderItem)}</Row>
> </div>
<Row gutter={[12, 12]}> <style>{`
{links.map((link, index) => ( .quick-link-item:hover .quick-link-icon {
<Col span={12} key={index}> background: #1890ff !important;
<div color: #fff;
onClick={() => handleClick(link.path)} }
style={{ `}</style>
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '12px 8px',
borderRadius: '8px',
cursor: 'pointer',
transition: 'all 0.2s',
background: '#fafafa',
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = `${link.color}10`;
e.currentTarget.style.transform = 'translateY(-2px)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = '#fafafa';
e.currentTarget.style.transform = 'translateY(0)';
}}
>
<div
style={{
fontSize: '20px',
color: link.color,
marginBottom: '4px',
}}
>
{link.icon}
</div>
<span style={{ fontSize: '12px', color: '#666' }}>{link.title}</span>
</div>
</Col>
))}
</Row>
</Card> </Card>
); );
}; };

View File

@ -1,36 +1,33 @@
/** /**
* KRA - Dashboard Statistics Cards * KRA - Dashboard Statistics Cards
* GVA charts.vue -
*/ */
import React from 'react'; import React from 'react';
import { Row, Col, Card, Statistic } from 'antd'; import { Row, Col, Card, Statistic } from 'antd';
import { UserOutlined, TeamOutlined, CheckCircleOutlined } from '@ant-design/icons'; import { ArrowUpOutlined } from '@ant-design/icons';
import MiniLineChart from './MiniLineChart';
interface StatItem { interface StatItem {
title: string; title: string;
value: number; value: number;
icon: React.ReactNode; data: number[];
color: string;
suffix?: string;
} }
const stats: StatItem[] = [ const stats: StatItem[] = [
{ {
title: '访问人数', title: '访问人数',
value: 12580, value: 268500,
icon: <UserOutlined />, data: [12, 22, 32, 45, 32, 78, 89, 92],
color: '#1890ff',
}, },
{ {
title: '新增客户', title: '新增客户',
value: 368, value: 268500,
icon: <TeamOutlined />, data: [1, 2, 43, 5, 67, 78, 89, 12],
color: '#52c41a',
}, },
{ {
title: '解决数量', title: '解决数量',
value: 1024, value: 268500,
icon: <CheckCircleOutlined />, data: [12, 22, 32, 45, 32, 78, 89, 92],
color: '#722ed1',
}, },
]; ];
@ -40,28 +37,19 @@ const StatCards: React.FC = () => {
{stats.map((stat, index) => ( {stats.map((stat, index) => (
<Col xs={24} sm={8} key={index}> <Col xs={24} sm={8} key={index}>
<Card className="kra-dashboard-card" variant="borderless"> <Card className="kra-dashboard-card" variant="borderless">
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}> <div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div <div>
style={{ <div style={{ fontWeight: 'bold', marginBottom: '8px' }}>{stat.title}</div>
width: '48px', <div style={{ marginTop: '16px' }}>
height: '48px', <Statistic value={stat.value} />
borderRadius: '8px', </div>
background: `${stat.color}15`, <div style={{ marginTop: '8px', color: '#52c41a', fontSize: '14px', fontWeight: 'bold' }}>
display: 'flex', +80% <ArrowUpOutlined />
alignItems: 'center', </div>
justifyContent: 'center', </div>
fontSize: '24px', <div style={{ width: '50%', height: '80px', position: 'relative' }}>
color: stat.color, <MiniLineChart data={stat.data} />
}}
>
{stat.icon}
</div> </div>
<Statistic
title={stat.title}
value={stat.value}
suffix={stat.suffix}
styles={{ content: { color: stat.color } }}
/>
</div> </div>
</Card> </Card>
</Col> </Col>

View File

@ -0,0 +1,87 @@
/**
* KRA - Dashboard Update Table Component
* GVA table.vue -
*/
import React from 'react';
import { Card, Table } from 'antd';
import type { ColumnsType } from 'antd/es/table';
interface UpdateItem {
ranking: number;
title: string;
click_num: number;
hot: number;
}
const tableData: UpdateItem[] = [
{
ranking: 1,
title: '更简洁的使用界面,更快速的操作体验',
click_num: 523,
hot: 263
},
{
ranking: 2,
title: '更优质的服务,更便捷的使用体验',
click_num: 416,
hot: 223
},
{
ranking: 3,
title: '更快速的创意实现,更高效的工作效率',
click_num: 337,
hot: 176
},
{
ranking: 4,
title: '更多的创意资源,更多的创意灵感',
click_num: 292,
hot: 145
},
{
ranking: 5,
title: '更合理的代码结构,更清晰的代码逻辑',
click_num: 173,
hot: 110
}
];
const columns: ColumnsType<UpdateItem> = [
{
title: '排名',
dataIndex: 'ranking',
width: 80,
align: 'center',
},
{
title: '内容标题',
dataIndex: 'title',
ellipsis: true,
},
{
title: '关注度',
dataIndex: 'click_num',
width: 100,
},
{
title: '热度值',
dataIndex: 'hot',
width: 100,
},
];
const UpdateTable: React.FC = () => {
return (
<Card title="最新更新" className="kra-dashboard-card" variant="borderless">
<Table
columns={columns}
dataSource={tableData}
rowKey="ranking"
pagination={false}
size="small"
/>
</Card>
);
};
export default UpdateTable;

View File

@ -0,0 +1,63 @@
/**
* KRA - Dashboard Wiki Component
* GVA wiki.vue -
*/
import React from 'react';
import { Card, Row, Col } from 'antd';
interface WikiItem {
title: string;
url: string;
}
const wikis: WikiItem[] = [
{
title: 'React',
url: 'https://react.dev/'
},
{
title: 'Kratos 文档',
url: 'https://go-kratos.dev/'
},
{
title: 'Ant Design',
url: 'https://ant.design/'
},
{
title: 'UmiJS',
url: 'https://umijs.org/'
},
{
title: 'GitHub 仓库',
url: 'https://github.com/go-kratos/kratos'
}
];
const Wiki: React.FC = () => {
return (
<Card title="文档" className="kra-dashboard-card" variant="borderless" extra={<a href="#"></a>}>
<Row gutter={[8, 8]}>
{wikis.map((item, index) => (
<Col span={12} key={index}>
<a
href={item.url}
target="_blank"
rel="noopener noreferrer"
style={{
fontSize: '14px',
color: '#666',
textDecoration: 'none',
}}
onMouseEnter={(e) => (e.currentTarget.style.color = '#1890ff')}
onMouseLeave={(e) => (e.currentTarget.style.color = '#666')}
>
{item.title}
</a>
</Col>
))}
</Row>
</Card>
);
};
export default Wiki;

View File

@ -1,9 +1,12 @@
/** /**
* KRA - Dashboard Components Index * KRA - Dashboard Components Export
*/ */
export { default as Banner } from './Banner'; export { default as Banner } from './Banner';
export { default as StatCards } from './StatCards'; export { default as StatCards } from './StatCards';
export { default as Charts } from './Charts'; export { default as Charts } from './Charts';
export { default as QuickLinks } from './QuickLinks'; export { default as QuickLinks } from './QuickLinks';
export { default as Notice } from './Notice'; export { default as Notice } from './Notice';
export { default as Wiki } from './Wiki';
export { default as UpdateTable } from './UpdateTable';
export { default as PluginTable } from './PluginTable'; export { default as PluginTable } from './PluginTable';
export { default as MiniLineChart } from './MiniLineChart';

View File

@ -1,44 +1,143 @@
/** /**
* KRA - Dashboard Styles * Dashboard 样式
*/ */
.dashboard {
.kra-dashboard { :global {
padding: 16px; .ant-page-header {
padding: 0;
}
}
} }
.kra-dashboard-card { .statCard {
background: #fff;
border-radius: 8px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
height: 100%; height: 100%;
} }
.kra-dashboard-card-header { .statContent {
display: flex;
justify-content: space-between;
align-items: center;
}
.statInfo {
flex: 1;
}
.statValue {
margin: 8px 0;
}
.statTrend {
font-size: 14px;
font-weight: 600;
}
.statChart {
margin-left: 16px;
}
.chartCard {
min-height: 300px;
}
.dataItem {
text-align: center;
}
.dataValue {
font-size: 28px;
font-weight: 600;
line-height: 1.2;
}
.dataName {
color: #666;
margin: 8px 0;
font-size: 14px;
}
.progressSection {
margin-top: 32px;
padding-top: 24px;
border-top: 1px solid #f0f0f0;
}
.progressTitle {
font-size: 16px;
font-weight: 500;
color: #333;
}
.progressItem {
margin-bottom: 8px;
}
.progressLabel {
margin-bottom: 8px;
color: #666;
font-size: 14px;
:global(.anticon) {
margin-right: 8px;
}
}
.quickCard {
height: 100%;
}
.shortcutItem {
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 4px;
cursor: pointer;
border-radius: 8px;
transition: all 0.2s;
&:hover {
background: #f0f5ff;
.shortcutIcon {
background: #1890ff;
color: #fff;
}
}
}
.shortcutIcon {
width: 32px;
height: 32px;
border-radius: 4px;
background: #f0f0f0;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center;
font-size: 16px;
transition: all 0.2s;
}
.shortcutTitle {
font-size: 12px;
margin-top: 8px;
color: #666;
text-align: center;
}
.noticeCard,
.docCard,
.sysCard {
height: 100%;
}
.sysItem {
display: flex;
justify-content: space-between; justify-content: space-between;
padding: 16px; align-items: center;
padding: 12px 0;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
h3 { &:last-child {
margin: 0; border-bottom: none;
font-size: 16px;
font-weight: 600;
}
}
.kra-dashboard-card-body {
padding: 16px;
}
// Dark mode
:global(.dark) {
.kra-dashboard-card {
background: #1f1f1f;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.kra-dashboard-card-header {
border-color: #303030;
} }
} }

View File

@ -1,57 +1,272 @@
/** /**
* KRA - Dashboard Page *
* Main dashboard with statistics, charts, and quick links * GVA Dashboard
*/ */
import React from 'react'; import React from 'react';
import { Row, Col } from 'antd'; import { PageContainer } from '@ant-design/pro-components';
import { Helmet } from '@umijs/max'; import { Card, Col, Row, Statistic, Tag, List, Typography, Progress, Space, Tooltip } from 'antd';
import Banner from './components/Banner'; import {
import StatCards from './components/StatCards'; UserOutlined,
import Charts from './components/Charts'; TeamOutlined,
import QuickLinks from './components/QuickLinks'; ApiOutlined,
import Notice from './components/Notice'; MenuOutlined,
import PluginTable from './components/PluginTable'; RiseOutlined,
import './index.less'; SettingOutlined,
DatabaseOutlined,
LinkOutlined,
SafetyOutlined,
CloudServerOutlined,
CodeOutlined,
FolderOutlined,
} from '@ant-design/icons';
import { history } from '@umijs/max';
import styles from './index.less';
const { Text } = Typography;
// 统计卡片数据
const statsData = [
{ title: '访问人数', value: 268500, icon: <UserOutlined />, trend: '+80%', color: '#1890ff', percent: 80 },
{ title: '新增客户', value: 12580, icon: <TeamOutlined />, trend: '+25%', color: '#52c41a', percent: 65 },
{ title: '解决数量', value: 8846, icon: <ApiOutlined />, trend: '+15%', color: '#722ed1', percent: 45 },
];
// 快捷功能 - 与GVA quickLinks.vue 一致
const shortcuts = [
{ icon: <MenuOutlined />, title: '菜单管理', path: '/admin/menu' },
{ icon: <LinkOutlined />, title: 'API管理', path: '/admin/api' },
{ icon: <SafetyOutlined />, title: '角色管理', path: '/admin/authority' },
{ icon: <UserOutlined />, title: '用户管理', path: '/admin/user' },
{ icon: <FolderOutlined />, title: '自动化包', path: '/systemTools/autoCodeAdmin' },
{ icon: <CodeOutlined />, title: '自动代码', path: '/systemTools/autoCode' },
];
// 公告数据
const notices = [
{ type: 'processing', typeTitle: '公告', title: 'Kratos Admin v1.0 正式发布,欢迎使用!' },
{ type: 'success', typeTitle: '通知', title: '系统已完成安全升级,请放心使用。' },
{ type: 'warning', typeTitle: '警告', title: '请定期修改密码,确保账户安全。' },
{ type: 'error', typeTitle: '重要', title: '数据库将于本周日凌晨进行维护。' },
{ type: 'default', typeTitle: '信息', title: '感谢您对 Kratos Admin 的支持!' },
];
// 内容数据
const contentData = [
{ name: '用户', value: 128, color: '#1890ff' },
{ name: '角色', value: 8, color: '#52c41a' },
{ name: 'API', value: 256, color: '#722ed1' },
{ name: '菜单', value: 32, color: '#faad14' },
{ name: '字典', value: 15, color: '#13c2c2' },
{ name: '部门', value: 12, color: '#eb2f96' },
];
// 文档链接
const docLinks = [
{ title: 'Kratos 官方文档', url: 'https://go-kratos.dev/' },
{ title: 'Ant Design Pro', url: 'https://pro.ant.design/' },
{ title: 'React 官方文档', url: 'https://react.dev/' },
{ title: 'Go 语言文档', url: 'https://go.dev/doc/' },
];
const Dashboard: React.FC = () => { const Dashboard: React.FC = () => {
return ( return (
<div className="kra-dashboard"> <PageContainer
<Helmet> header={{ title: '' }}
<title> - Kratos Admin</title> className={styles.dashboard}
</Helmet> >
{/* 统计卡片行 */}
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
{/* Statistics Cards */} {statsData.map((item, index) => (
<Col xs={24} sm={12} lg={8} key={index}>
<Card className={styles.statCard} variant="borderless">
<div className={styles.statContent}>
<div className={styles.statInfo}>
<Text type="secondary">{item.title}</Text>
<div className={styles.statValue}>
<Statistic value={item.value} valueStyle={{ fontSize: 28, fontWeight: 600 }} />
</div>
<div className={styles.statTrend}>
<RiseOutlined style={{ color: '#52c41a' }} />
<Text style={{ color: '#52c41a', marginLeft: 4 }}>{item.trend}</Text>
</div>
</div>
<div className={styles.statChart}>
<Progress
type="circle"
percent={item.percent}
size={80}
strokeColor={item.color}
format={() => <span style={{ fontSize: 14 }}>{item.percent}%</span>}
/>
</div>
</div>
</Card>
</Col>
))}
</Row>
{/* 主内容区 */}
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
{/* 左侧内容数据 */}
<Col xs={24} lg={18}> <Col xs={24} lg={18}>
<StatCards /> <Card title="内容数据" variant="borderless" className={styles.chartCard}>
<Row gutter={[16, 24]}>
{contentData.map((item, index) => (
<Col xs={12} sm={8} md={4} key={index}>
<div className={styles.dataItem}>
<div className={styles.dataValue} style={{ color: item.color }}>
{item.value}
</div>
<div className={styles.dataName}>{item.name}</div>
<Progress
percent={Math.min((item.value / 300) * 100, 100)}
showInfo={false}
strokeColor={item.color}
size="small"
/>
</div>
</Col>
))}
</Row>
<div className={styles.progressSection}>
<div className={styles.progressTitle}>使</div>
<Row gutter={[32, 16]} style={{ marginTop: 16 }}>
<Col xs={24} sm={12}>
<div className={styles.progressItem}>
<div className={styles.progressLabel}>
<CloudServerOutlined /> CPU 使
</div>
<Progress percent={45} strokeColor="#1890ff" />
</div>
</Col>
<Col xs={24} sm={12}>
<div className={styles.progressItem}>
<div className={styles.progressLabel}>
<DatabaseOutlined /> 使
</div>
<Progress percent={68} strokeColor="#52c41a" />
</div>
</Col>
<Col xs={24} sm={12}>
<div className={styles.progressItem}>
<div className={styles.progressLabel}>
<SafetyOutlined /> 使
</div>
<Progress percent={32} strokeColor="#722ed1" />
</div>
</Col>
<Col xs={24} sm={12}>
<div className={styles.progressItem}>
<div className={styles.progressLabel}>
<ApiOutlined /> API
</div>
<Progress percent={85} strokeColor="#faad14" />
</div>
</Col>
</Row>
</div>
</Card>
</Col> </Col>
{/* Quick Links */} {/* 右侧快捷功能 */}
<Col xs={24} lg={6}> <Col xs={24} lg={6}>
<QuickLinks /> <Card title="快捷功能" variant="borderless" className={styles.quickCard}>
</Col> <Row gutter={[8, 16]}>
{shortcuts.map((item, index) => (
{/* Main Chart */} <Col span={8} key={index}>
<Col xs={24} lg={18}> <div
<Charts /> className={styles.shortcutItem}
</Col> onClick={() => history.push(item.path)}
>
{/* Notice */} <div className={styles.shortcutIcon}>{item.icon}</div>
<Col xs={24} lg={6}> <Text className={styles.shortcutTitle}>{item.title}</Text>
<Notice /> </div>
</Col> </Col>
))}
{/* Plugin Table */} </Row>
<Col xs={24} lg={18}> </Card>
<PluginTable />
</Col>
{/* Banner */}
<Col xs={24} lg={6}>
<Banner />
</Col> </Col>
</Row> </Row>
</div>
{/* 底部区域 */}
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
{/* 公告 */}
<Col xs={24} md={12} lg={8}>
<Card
title="公告"
variant="borderless"
extra={<a></a>}
className={styles.noticeCard}
>
<List
size="small"
dataSource={notices}
renderItem={(item) => (
<List.Item style={{ padding: '8px 0', border: 'none' }}>
<Space>
<Tag color={item.type}>{item.typeTitle}</Tag>
<Tooltip title={item.title}>
<Text ellipsis style={{ maxWidth: 200 }}>{item.title}</Text>
</Tooltip>
</Space>
</List.Item>
)}
/>
</Card>
</Col>
{/* 文档链接 */}
<Col xs={24} md={12} lg={8}>
<Card
title="文档"
variant="borderless"
extra={<a></a>}
className={styles.docCard}
>
<List
size="small"
dataSource={docLinks}
renderItem={(item) => (
<List.Item style={{ padding: '8px 0', border: 'none' }}>
<a href={item.url} target="_blank" rel="noopener noreferrer">
<Space>
<LinkOutlined />
<Text>{item.title}</Text>
</Space>
</a>
</List.Item>
)}
/>
</Card>
</Col>
{/* 系统信息 */}
<Col xs={24} lg={8}>
<Card title="系统信息" variant="borderless" className={styles.sysCard}>
<div className={styles.sysItem}>
<Text type="secondary"></Text>
<Text strong>Kratos Admin</Text>
</div>
<div className={styles.sysItem}>
<Text type="secondary"></Text>
<Text strong>Go Kratos</Text>
</div>
<div className={styles.sysItem}>
<Text type="secondary"></Text>
<Text strong>React + Ant Design Pro</Text>
</div>
<div className={styles.sysItem}>
<Text type="secondary"></Text>
<Text strong>v1.0.0</Text>
</div>
<div className={styles.sysItem}>
<Text type="secondary"></Text>
<Tag color="success"></Tag>
</div>
</Card>
</Col>
</Row>
</PageContainer>
); );
}; };