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; 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> = ({ const EnvironmentModal: React.FC<EnvironmentModalProps> = ({
visible, visible,
onCancel, onCancel,
@ -105,18 +91,6 @@ const EnvironmentModal: React.FC<EnvironmentModalProps> = ({
<Input placeholder="请输入环境编码"/> <Input placeholder="请输入环境编码"/>
</Form.Item> </Form.Item>
<Form.Item
name="buildType"
label="构建方式"
rules={[{required: true, message: '请选择构建方式'}]}
>
<Select
placeholder="请选择构建方式"
options={buildTypeOptions}
style={{width: '100%'}}
/>
</Form.Item>
<Form.Item <Form.Item
name="envDesc" name="envDesc"
label="环境描述" 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 {PageContainer} from '@ant-design/pro-layout';
import {Card, Row, Col, Typography, Button, message, Empty, Tooltip, Tag} from 'antd'; import {Button, message, Popconfirm, Space, Tag, Form, Input, Row, Col} from 'antd';
import {PlusOutlined, RocketOutlined, CloudServerOutlined, SettingOutlined, DeleteOutlined, EditOutlined} from '@ant-design/icons'; import {PlusOutlined, DeleteOutlined, EditOutlined, SearchOutlined, ReloadOutlined} from '@ant-design/icons';
import {getEnvironmentList, deleteEnvironment} from './service'; import {getEnvironmentList, deleteEnvironment, getEnvironmentPage} from './service';
import type {Environment} from './types'; import type {Environment} from './types';
import {BuildTypeEnum, DeployTypeEnum} from './types';
import EnvironmentModal from './components/EnvironmentModal'; import EnvironmentModal from './components/EnvironmentModal';
import { ProTable } from '@ant-design/pro-components';
const {Title, Text} = Typography; import type { ProColumns } from '@ant-design/pro-components';
// 构建方式和部署方式的显示映射
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进行自动化工作流'
},
};
const EnvironmentList: React.FC = () => { const EnvironmentList: React.FC = () => {
const [environments, setEnvironments] = useState<Environment[]>([]);
const [loading, setLoading] = useState(false);
const [modalVisible, setModalVisible] = useState(false); const [modalVisible, setModalVisible] = useState(false);
const [currentEnvironment, setCurrentEnvironment] = useState<Environment>(); const [currentEnvironment, setCurrentEnvironment] = useState<Environment>();
const actionRef = React.useRef();
const fetchEnvironments = async () => { const [form] = Form.useForm();
setLoading(true);
try {
const data = await getEnvironmentList();
setEnvironments(data);
} catch (error) {
message.error('获取环境列表失败');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchEnvironments();
}, []);
const handleDelete = async (id: number) => { const handleDelete = async (id: number) => {
try { try {
await deleteEnvironment(id); await deleteEnvironment(id);
message.success('删除成功'); message.success('删除成功');
fetchEnvironments(); actionRef.current?.reload();
} catch (error) { } catch (error) {
message.error('删除失败'); message.error('删除失败');
} }
@ -74,157 +34,141 @@ const EnvironmentList: React.FC = () => {
setModalVisible(true); setModalVisible(true);
}; };
const EnvironmentCard = ({environment}: { environment: Environment }) => { const columns: ProColumns<Environment>[] = [
const buildType = buildTypeMap[environment.buildType]; {
return ( title: '环境编码',
<Card dataIndex: 'envCode',
hoverable width: 120,
className="environment-card" copyable: true,
style={{ ellipsis: true,
borderRadius: '12px', fixed: 'left',
overflow: 'hidden', },
height: '100%', {
transition: 'all 0.3s ease', title: '环境名称',
}} dataIndex: 'envName',
actions={[ width: 150,
<Tooltip title="编辑环境"> 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 <Button
type="text" type="link"
icon={<EditOutlined/>}
onClick={() => handleEdit(environment)}
>
</Button>
</Tooltip>,
<Tooltip title="删除环境">
<Button
type="text"
danger danger
icon={<DeleteOutlined/>} icon={<DeleteOutlined/>}
onClick={() => handleDelete(environment.id)}
> >
</Button> </Button>
</Tooltip> </Popconfirm>
]} </Space>
> ),
<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>
);
};
return ( 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} <ProTable<Environment>
onCancel={() => setModalVisible(false)} columns={columns}
onSuccess={() => { actionRef={actionRef}
setModalVisible(false); cardBordered
fetchEnvironments(); request={async (params) => {
}} const { current, pageSize } = params;
initialValues={currentEnvironment} const data = await getEnvironmentPage({
/> current,
</PageContainer> 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'; 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 { export interface Environment {
id: number; id: number;
@ -18,8 +7,6 @@ export interface Environment {
envName: string; envName: string;
envDesc?: string; envDesc?: string;
sort: number; sort: number;
buildType: BuildTypeEnum;
deployType: DeployTypeEnum;
} }
export interface CreateEnvironmentRequest { export interface CreateEnvironmentRequest {
@ -28,8 +15,6 @@ export interface CreateEnvironmentRequest {
envName: string; envName: string;
envDesc?: string; envDesc?: string;
sort: number; sort: number;
buildType: BuildTypeEnum;
deployType: DeployTypeEnum;
} }
export interface UpdateEnvironmentRequest { export interface UpdateEnvironmentRequest {
@ -38,8 +23,6 @@ export interface UpdateEnvironmentRequest {
envName: string; envName: string;
envDesc?: string; envDesc?: string;
sort: number; sort: number;
buildType: BuildTypeEnum;
deployType: DeployTypeEnum;
} }
export interface EnvironmentQuery extends BaseQuery { export interface EnvironmentQuery extends BaseQuery {

View File

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