This commit is contained in:
dengqichen 2024-12-24 15:26:11 +08:00
parent 201d9ca054
commit 6dfd7b914f
5 changed files with 169 additions and 326 deletions

View File

@ -11,20 +11,6 @@ interface EnvironmentModalProps {
initialValues?: Environment;
}
const {Option} = Select;
const buildTypeOptions = [
{label: 'Jenkins构建', value: BuildTypeEnum.JENKINS},
{label: 'GitLab Runner构建', value: BuildTypeEnum.GITLAB_RUNNER},
{label: 'GitHub Action构建', value: BuildTypeEnum.GITHUB_ACTION},
];
const deployTypeOptions = [
{label: 'Kubernetes集群部署', value: DeployTypeEnum.K8S},
{label: 'Docker容器部署', value: DeployTypeEnum.DOCKER},
{label: '虚拟机部署', value: DeployTypeEnum.VM},
];
const EnvironmentModal: React.FC<EnvironmentModalProps> = ({
visible,
onCancel,
@ -105,18 +91,6 @@ const EnvironmentModal: React.FC<EnvironmentModalProps> = ({
<Input placeholder="请输入环境编码"/>
</Form.Item>
<Form.Item
name="buildType"
label="构建方式"
rules={[{required: true, message: '请选择构建方式'}]}
>
<Select
placeholder="请选择构建方式"
options={buildTypeOptions}
style={{width: '100%'}}
/>
</Form.Item>
<Form.Item
name="envDesc"
label="环境描述"

View File

@ -1,43 +0,0 @@
.environment-card {
&:hover {
transform: translateY(-4px);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.ant-card-actions {
background: #fafafa;
border-top: 1px solid #f0f0f0;
li {
margin: 12px 0;
&:not(:last-child) {
border-right: 1px solid #f0f0f0;
}
}
}
.ant-card-body {
padding: 0;
}
.ant-tag {
border: none;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
&:hover {
opacity: 0.8;
}
}
.ant-typography {
margin-bottom: 0;
}
.ant-btn {
&:hover {
transform: scale(1.05);
transition: all 0.3s ease;
}
}
}

View File

@ -1,64 +1,24 @@
import React, {useState, useEffect} from 'react';
import React, {useState} from 'react';
import {PageContainer} from '@ant-design/pro-layout';
import {Card, Row, Col, Typography, Button, message, Empty, Tooltip, Tag} from 'antd';
import {PlusOutlined, RocketOutlined, CloudServerOutlined, SettingOutlined, DeleteOutlined, EditOutlined} from '@ant-design/icons';
import {getEnvironmentList, deleteEnvironment} from './service';
import {Button, message, Popconfirm, Space, Tag, Form, Input, Row, Col} from 'antd';
import {PlusOutlined, DeleteOutlined, EditOutlined, SearchOutlined, ReloadOutlined} from '@ant-design/icons';
import {getEnvironmentList, deleteEnvironment, getEnvironmentPage} from './service';
import type {Environment} from './types';
import {BuildTypeEnum, DeployTypeEnum} from './types';
import EnvironmentModal from './components/EnvironmentModal';
const {Title, Text} = Typography;
// 构建方式和部署方式的显示映射
const buildTypeMap = {
[BuildTypeEnum.JENKINS]: {
label: 'Jenkins构建',
color: '#1890ff',
icon: <RocketOutlined/>,
description: '使用Jenkins进行自动化构建和部署'
},
[BuildTypeEnum.GITLAB_RUNNER]: {
label: 'GitLab Runner构建',
color: '#722ed1',
icon: <CloudServerOutlined/>,
description: '使用GitLab Runner进行CI/CD流程'
},
[BuildTypeEnum.GITHUB_ACTION]: {
label: 'GitHub Action构建',
color: '#52c41a',
icon: <SettingOutlined/>,
description: '使用GitHub Action进行自动化工作流'
},
};
import { ProTable } from '@ant-design/pro-components';
import type { ProColumns } from '@ant-design/pro-components';
const EnvironmentList: React.FC = () => {
const [environments, setEnvironments] = useState<Environment[]>([]);
const [loading, setLoading] = useState(false);
const [modalVisible, setModalVisible] = useState(false);
const [currentEnvironment, setCurrentEnvironment] = useState<Environment>();
const fetchEnvironments = async () => {
setLoading(true);
try {
const data = await getEnvironmentList();
setEnvironments(data);
} catch (error) {
message.error('获取环境列表失败');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchEnvironments();
}, []);
const actionRef = React.useRef();
const [form] = Form.useForm();
const handleDelete = async (id: number) => {
try {
await deleteEnvironment(id);
message.success('删除成功');
fetchEnvironments();
actionRef.current?.reload();
} catch (error) {
message.error('删除失败');
}
@ -74,157 +34,141 @@ const EnvironmentList: React.FC = () => {
setModalVisible(true);
};
const EnvironmentCard = ({environment}: { environment: Environment }) => {
const buildType = buildTypeMap[environment.buildType];
return (
<Card
hoverable
className="environment-card"
style={{
borderRadius: '12px',
overflow: 'hidden',
height: '100%',
transition: 'all 0.3s ease',
}}
actions={[
<Tooltip title="编辑环境">
const columns: ProColumns<Environment>[] = [
{
title: '环境编码',
dataIndex: 'envCode',
width: 120,
copyable: true,
ellipsis: true,
fixed: 'left',
},
{
title: '环境名称',
dataIndex: 'envName',
width: 150,
ellipsis: true,
},
{
title: '环境描述',
dataIndex: 'envDesc',
ellipsis: true,
},
{
title: '构建类型',
dataIndex: 'buildType',
width: 100,
render: (buildType) => (
<Tag color={buildType === 'JENKINS' ? 'blue' : 'green'}>
{buildType}
</Tag>
),
},
{
title: '排序',
dataIndex: 'sort',
width: 80,
sorter: true,
},
{
title: '操作',
width: 180,
key: 'action',
valueType: 'option',
fixed: 'right',
render: (_, record) => (
<Space>
<Button
type="link"
icon={<EditOutlined/>}
onClick={() => handleEdit(record)}
>
</Button>
<Popconfirm
title="确定要删除该环境吗?"
description="删除后将无法恢复,请谨慎操作"
onConfirm={() => handleDelete(record.id)}
>
<Button
type="text"
icon={<EditOutlined/>}
onClick={() => handleEdit(environment)}
>
</Button>
</Tooltip>,
<Tooltip title="删除环境">
<Button
type="text"
type="link"
danger
icon={<DeleteOutlined/>}
onClick={() => handleDelete(environment.id)}
>
</Button>
</Tooltip>
]}
>
<div style={{position: 'relative', padding: '16px'}}>
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: '16px'
}}>
<Title level={4} style={{margin: 0}}>
{environment.envName}
</Title>
<Tag color={buildType.color} style={{borderRadius: '12px', padding: '0 12px'}}>
{buildType.icon} {buildType.label}
</Tag>
</div>
<Text
type="secondary"
style={{
display: 'block',
marginBottom: '24px',
height: '40px',
overflow: 'hidden',
textOverflow: 'ellipsis',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
display: '-webkit-box',
}}
>
{environment.envDesc || '暂无描述'}
</Text>
<div style={{
background: '#f5f5f5',
borderRadius: '8px',
padding: '16px',
marginBottom: '16px'
}}>
<Text type="secondary"></Text>
<div style={{
fontFamily: 'monaco, monospace',
fontSize: '14px',
color: '#262626',
marginTop: '8px',
padding: '8px',
background: '#fff',
borderRadius: '4px',
border: '1px solid #f0f0f0'
}}>
{environment.envCode}
</div>
</div>
</div>
</Card>
);
};
</Popconfirm>
</Space>
),
},
];
return (
<PageContainer
header={{
title: '环境管理',
extra: [
<Button
key="add"
type="primary"
icon={<PlusOutlined/>}
onClick={handleAdd}
style={{
borderRadius: '8px',
padding: '0 24px',
height: '40px',
boxShadow: '0 2px 0 rgba(0,0,0,0.045)',
transition: 'all 0.3s ease',
}}
>
</Button>
],
}}
>
<div style={{padding: '24px'}}>
<Row gutter={[24, 24]}>
{environments.length > 0 ? (
environments.map((environment) => (
<Col key={environment.id} xs={24} sm={12} md={8} lg={6}>
<EnvironmentCard environment={environment}/>
</Col>
))
) : (
<Col span={24}>
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description="暂无环境数据"
style={{
background: '#fff',
padding: '40px',
borderRadius: '8px',
}}
>
<Button type="primary" onClick={handleAdd}>
</Button>
</Empty>
</Col>
)}
</Row>
</div>
<EnvironmentModal
visible={modalVisible}
onCancel={() => setModalVisible(false)}
onSuccess={() => {
setModalVisible(false);
fetchEnvironments();
}}
initialValues={currentEnvironment}
/>
</PageContainer>
<>
<ProTable<Environment>
columns={columns}
actionRef={actionRef}
cardBordered
request={async (params) => {
const { current, pageSize } = params;
const data = await getEnvironmentPage({
current,
pageSize,
});
return {
data: data.content || [],
success: true,
total: data.totalElements || 0,
};
}}
rowKey="id"
search={{
labelWidth: 'auto',
span: {
xs: 24,
sm: 12,
md: 8,
lg: 6,
xl: 6,
xxl: 6,
},
}}
options={{
setting: {
listsHeight: 400,
},
}}
form={{
syncToUrl: true,
}}
pagination={{
pageSize: 10,
showQuickJumper: true,
}}
dateFormatter="string"
toolBarRender={() => [
<Button
key="add"
type="primary"
onClick={handleAdd}
icon={<PlusOutlined/>}
>
</Button>,
]}
/>
<EnvironmentModal
visible={modalVisible}
onCancel={() => setModalVisible(false)}
onSuccess={() => {
setModalVisible(false);
actionRef.current?.reload();
}}
initialValues={currentEnvironment}
/>
</>
);
};

View File

@ -1,16 +1,5 @@
import type { BaseQuery } from '@/types/base';
export enum BuildTypeEnum {
JENKINS = 'JENKINS',
GITLAB_RUNNER = 'GITLAB_RUNNER',
GITHUB_ACTION = 'GITHUB_ACTION'
}
export enum DeployTypeEnum {
K8S = 'K8S',
DOCKER = 'DOCKER',
VM = 'VM'
}
export interface Environment {
id: number;
@ -18,8 +7,6 @@ export interface Environment {
envName: string;
envDesc?: string;
sort: number;
buildType: BuildTypeEnum;
deployType: DeployTypeEnum;
}
export interface CreateEnvironmentRequest {
@ -28,8 +15,6 @@ export interface CreateEnvironmentRequest {
envName: string;
envDesc?: string;
sort: number;
buildType: BuildTypeEnum;
deployType: DeployTypeEnum;
}
export interface UpdateEnvironmentRequest {
@ -38,8 +23,6 @@ export interface UpdateEnvironmentRequest {
envName: string;
envDesc?: string;
sort: number;
buildType: BuildTypeEnum;
deployType: DeployTypeEnum;
}
export interface EnvironmentQuery extends BaseQuery {

View File

@ -13,6 +13,7 @@ import {
import {getProjectGroupList, deleteProjectGroup} from './service';
import type {ProjectGroup} from './types';
import ProjectGroupModal from './components/ProjectGroupModal';
import { ProjectGroupTypeEnum } from './types';
const {Title, Text} = Typography;
const {Search} = Input;
@ -61,34 +62,34 @@ const ProjectList: React.FC = () => {
setModalVisible(true);
};
const getProjectTypeInfo = (type: string) => {
switch (type) {
case 'PRODUCT':
return {
type: 'PRODUCT',
label: '产品项目组',
color: '#1890ff',
icon: <RocketOutlined/>,
description: '产品相关的项目组'
};
case 'PLATFORM':
return {
type: 'PLATFORM',
label: '平台项目组',
color: '#52c41a',
icon: <EnvironmentOutlined/>,
description: '平台相关的项目组'
};
default:
return {
type: 'BUSINESS',
label: '业务项目组',
color: '#722ed1',
icon: <TeamOutlined/>,
description: '业务相关的项目组'
};
}
};
const getProjectTypeInfo = (type: ProjectGroupTypeEnum) => {
switch (type) {
case ProjectGroupTypeEnum.PRODUCT:
return {
type: ProjectGroupTypeEnum.PRODUCT,
label: '产品型',
color: '#1890ff',
icon: <RocketOutlined/>,
description: '产品相关的项目组'
};
case ProjectGroupTypeEnum.PROJECT:
return {
type: ProjectGroupTypeEnum.PROJECT,
label: '项目型',
color: '#52c41a',
icon: <EnvironmentOutlined/>,
description: '项目型相关的项目组'
};
default:
return {
type: ProjectGroupTypeEnum.PROJECT,
label: '项目型',
color: '#722ed1',
icon: <TeamOutlined/>,
description: '项目型相关的项目组'
};
}
};
const filteredProjects = projects.filter(project => {
const matchesSearch = searchText ? (
@ -147,7 +148,7 @@ const ProjectList: React.FC = () => {
alignItems: 'center',
gap: '8px'
}}>
{projectGroup.projectGroupName}
{projectGroup.projectGroupName}({projectGroup.projectGroupCode})
<Tag
color={typeInfo.color}
style={{
@ -195,21 +196,6 @@ const ProjectList: React.FC = () => {
borderRadius: '12px',
boxShadow: '0 2px 8px rgba(0,0,0,0.04)',
}}>
<div>
<Text type="secondary" style={{fontSize: '13px', display: 'block', marginBottom: '8px'}}></Text>
<div style={{
color: '#262626',
fontFamily: 'monaco, monospace',
fontSize: '14px',
padding: '8px 12px',
background: '#f5f5f5',
borderRadius: '6px',
letterSpacing: '0.5px',
}}>
{projectGroup.projectGroupCode}
</div>
</div>
<Row gutter={16}>
<Col span={8}>
<Statistic
@ -319,11 +305,10 @@ const ProjectList: React.FC = () => {
value={projectType}
onChange={setProjectType}
options={[
{ label: '全部类型', value: 'ALL' },
{ label: '产品项目组', value: 'PRODUCT' },
{ label: '平台项目组', value: 'PLATFORM' },
{ label: '业务项目组', value: 'BUSINESS' }
]}
{ label: '全部类型', value: 'ALL' },
{ label: '产品项目组', value: ProjectGroupTypeEnum.PRODUCT },
{ label: '项目项目组', value: ProjectGroupTypeEnum.PROJECT }
]}
size="large"
/>
</Col>