-
-
- {environment.envName}
-
-
- {buildType.icon} {buildType.label}
-
-
-
-
}
+ 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',
+ }}
+ >
+ 新建环境
+
+ ],
}}
- >
- {environment.envDesc || '暂无描述'}
-
-
-
-
环境编码
-
- {environment.envCode}
+ >
+
+
+ {environments.length > 0 ? (
+ environments.map((environment) => (
+
+
+
+ ))
+ ) : (
+
+
+
+
+
+ )}
+
-
-
-
- setModalVisible(false)}
+ onSuccess={() => {
+ setModalVisible(false);
+ fetchEnvironments();
}}
- >
- {deployType.icon} {deployType.label}
-
-
- 排序: {environment.sort}
-
-
-
+ initialValues={currentEnvironment}
+ />
+
);
- };
-
- return (
-
}
- 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',
- }}
- >
- 新建环境
-
- ],
- }}
- >
-
-
- {environments.length > 0 ? (
- environments.map((environment) => (
-
-
-
- ))
- ) : (
-
-
-
-
-
- )}
-
-
-
-
setModalVisible(false)}
- onSuccess={() => {
- setModalVisible(false);
- fetchEnvironments();
- }}
- initialValues={currentEnvironment}
- />
-
- );
};
export default EnvironmentList;
diff --git a/frontend/src/pages/Deploy/ProjectGroup/List/components/ProjectGroupModal.tsx b/frontend/src/pages/Deploy/ProjectGroup/List/components/ProjectGroupModal.tsx
index 65bf8f70..d17d0dee 100644
--- a/frontend/src/pages/Deploy/ProjectGroup/List/components/ProjectGroupModal.tsx
+++ b/frontend/src/pages/Deploy/ProjectGroup/List/components/ProjectGroupModal.tsx
@@ -1,159 +1,149 @@
-import React, { useEffect, useState } from 'react';
-import { Modal, Form, Input, InputNumber, Radio, message, Select } from 'antd';
-import type { ProjectGroup } from '../types';
-import type { Environment } from '../../../Environment/List/types';
-import { createProjectGroup, updateProjectGroup } from '../service';
-import { getEnvironmentList } from '../../../Environment/List/service';
+import React, {useEffect, useState} from 'react';
+import {Modal, Form, Input, InputNumber, Radio, message, Select} from 'antd';
+import type {ProjectGroup} from '../types';
+import type {Environment} from '../../../Environment/List/types';
+import {createProjectGroup, updateProjectGroup} from '../service';
+import {getEnvironmentList} from '../../../Environment/List/service';
interface ProjectGroupModalProps {
- visible: boolean;
- onCancel: () => void;
- onSuccess: () => void;
- initialValues?: ProjectGroup;
+ visible: boolean;
+ onCancel: () => void;
+ onSuccess: () => void;
+ initialValues?: ProjectGroup;
}
const ProjectGroupModal: React.FC = ({
- visible,
- onCancel,
- onSuccess,
- initialValues,
-}) => {
- const [form] = Form.useForm();
- const [environments, setEnvironments] = useState([]);
- const isEdit = !!initialValues;
+ visible,
+ onCancel,
+ onSuccess,
+ initialValues,
+ }) => {
+ const [form] = Form.useForm();
+ const [environments, setEnvironments] = useState([]);
+ const isEdit = !!initialValues;
- // 获取环境列表
- useEffect(() => {
- const fetchEnvironments = async () => {
- try {
- const data = await getEnvironmentList();
- setEnvironments(data);
- } catch (error) {
- message.error('获取环境列表失败');
- }
+ // 获取环境列表
+ useEffect(() => {
+ const fetchEnvironments = async () => {
+ try {
+ const data = await getEnvironmentList();
+ setEnvironments(data);
+ } catch (error) {
+ message.error('获取环境列表失败');
+ }
+ };
+ fetchEnvironments();
+ }, []);
+
+ useEffect(() => {
+ if (visible && initialValues) {
+ // 设置初始值,包括环境ID列表
+ const envIds = initialValues.environments?.map(env => env.id) || [];
+ form.setFieldsValue({
+ ...initialValues,
+ environments: envIds
+ });
+ }
+ }, [visible, initialValues, form]);
+
+ const handleSubmit = async () => {
+ try {
+ const values = await form.validateFields();
+ // 转换环境ID数组为对象数组
+ const environments = (values.environments || []).map((id: number) => ({id}));
+ const submitData = {
+ ...values,
+ environments
+ };
+
+ if (isEdit) {
+ await updateProjectGroup({...submitData, id: initialValues.id});
+ message.success('更新成功');
+ } else {
+ await createProjectGroup(submitData);
+ message.success('创建成功');
+ }
+ onSuccess();
+ onCancel();
+ form.resetFields();
+ } catch (error) {
+ message.error(isEdit ? '更新失败' : '创建失败');
+ }
};
- fetchEnvironments();
- }, []);
- useEffect(() => {
- if (visible && initialValues) {
- // 设置初始值,包括环境ID列表
- const envIds = initialValues.environments?.map(env => env.id) || [];
- form.setFieldsValue({
- ...initialValues,
- environments: envIds
- });
- }
- }, [visible, initialValues, form]);
+ const handleCancel = () => {
+ form.resetFields();
+ onCancel();
+ };
- const handleSubmit = async () => {
- try {
- const values = await form.validateFields();
- // 转换环境ID数组为对象数组
- const environments = (values.environments || []).map((id: number) => ({ id }));
- const submitData = {
- ...values,
- environments
- };
-
- if (isEdit) {
- await updateProjectGroup({ ...submitData, id: initialValues.id });
- message.success('更新成功');
- } else {
- await createProjectGroup(submitData);
- message.success('创建成功');
- }
- onSuccess();
- onCancel();
- form.resetFields();
- } catch (error) {
- message.error(isEdit ? '更新失败' : '创建失败');
- }
- };
-
- const handleCancel = () => {
- form.resetFields();
- onCancel();
- };
-
- return (
-
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
- 启用
- 禁用
-
-
-
-
-
-
-
-
-
-
-
- );
+
+
+
+
+
+
+
+ 启用
+ 禁用
+
+
+
+
+ );
};
export default ProjectGroupModal;
\ No newline at end of file
diff --git a/frontend/src/pages/Deploy/ProjectGroup/List/index.less b/frontend/src/pages/Deploy/ProjectGroup/List/index.less
deleted file mode 100644
index 31030915..00000000
--- a/frontend/src/pages/Deploy/ProjectGroup/List/index.less
+++ /dev/null
@@ -1,188 +0,0 @@
-.project-card {
- &:hover {
- transform: translateY(-4px);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
- }
-
- .ant-card-body {
- padding: 0;
- }
-
- .project-content {
- padding: 20px;
- }
-
- // 项目组头部
- .project-header {
- display: flex;
- align-items: flex-start;
- gap: 12px;
- margin-bottom: 16px;
-
- .project-type {
- margin: 0;
- padding: 6px;
- border-radius: 6px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 16px;
- }
-
- .title-section {
- flex: 1;
- min-width: 0;
-
- .project-name {
- margin: 0;
- font-size: 16px;
- line-height: 1.4;
- color: #1f1f1f;
- }
-
- .project-code {
- margin-top: 4px;
- font-size: 13px;
- color: #666;
- font-family: 'monaco, monospace';
- }
- }
- }
-
- // 项目组描述
- .project-desc {
- color: #666;
- font-size: 13px;
- line-height: 1.6;
- margin-bottom: 16px;
- overflow: hidden;
- text-overflow: ellipsis;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- }
-
- // 项目组统计
- .project-stats {
- display: flex;
- gap: 16px;
- margin-bottom: 20px;
- padding: 12px;
- background: #fafafa;
- border-radius: 6px;
-
- .stat-item {
- display: flex;
- align-items: center;
- gap: 6px;
- color: #666;
- font-size: 13px;
-
- .anticon {
- font-size: 14px;
- color: #8c8c8c;
- }
- }
- }
-
- // 环境列表
- .environments-section {
- margin-bottom: 20px;
-
- .env-list {
- display: flex;
- flex-direction: column;
- gap: 8px;
-
- .env-item {
- padding: 12px;
- background: #fafafa;
- border-radius: 6px;
- transition: all 0.3s ease;
-
- &:hover {
- background: #f0f0f0;
- }
-
- .env-main {
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 12px;
-
- .env-info {
- flex: 1;
- min-width: 0;
-
- .env-name {
- display: block;
- font-size: 14px;
- font-weight: 500;
- color: #262626;
- margin-bottom: 4px;
- }
-
- .env-code {
- display: block;
- font-size: 12px;
- color: #666;
- font-family: 'monaco, monospace';
- }
- }
-
- .env-tags {
- display: flex;
- gap: 4px;
- flex-shrink: 0;
-
- .ant-tag {
- margin: 0;
- padding: 4px;
- border: none;
- border-radius: 4px;
- display: flex;
- align-items: center;
- justify-content: center;
-
- .anticon {
- font-size: 12px;
- }
- }
- }
- }
-
- .env-desc {
- margin-top: 8px;
- font-size: 12px;
- color: #8c8c8c;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- }
- }
- }
-
- // 操作按钮
- .project-actions {
- display: flex;
- gap: 8px;
- padding-top: 16px;
- border-top: 1px dashed #f0f0f0;
-
- .ant-btn {
- padding: 4px 12px;
- height: 32px;
- border-radius: 4px;
-
- &:hover {
- transform: translateY(-1px);
- background: #fafafa;
- }
-
- &.ant-btn-dangerous:hover {
- background: #fff1f0;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/frontend/src/pages/Deploy/ProjectGroup/List/index.tsx b/frontend/src/pages/Deploy/ProjectGroup/List/index.tsx
index 1b39acef..280773b5 100644
--- a/frontend/src/pages/Deploy/ProjectGroup/List/index.tsx
+++ b/frontend/src/pages/Deploy/ProjectGroup/List/index.tsx
@@ -1,342 +1,367 @@
-import React, { useState, useEffect } from 'react';
-import { PageContainer } from '@ant-design/pro-layout';
-import { Card, Row, Col, Typography, Button, message, Input, Select, Empty, Tooltip, Tag, Space } from 'antd';
-import { PlusOutlined, SearchOutlined, RocketOutlined, TeamOutlined, EnvironmentOutlined, EditOutlined, DeleteOutlined, ClockCircleOutlined, CloudServerOutlined, SettingOutlined } from '@ant-design/icons';
-import { getProjectGroupList, deleteProjectGroup } from './service';
-import type { ProjectGroup } from './types';
+import React, {useState, useEffect} from 'react';
+import {PageContainer} from '@ant-design/pro-layout';
+import {Card, Row, Col, Typography, Button, message, Popconfirm, Space, Tag, Input, Select, Tooltip, Statistic, Divider} from 'antd';
+import {
+ PlusOutlined,
+ EditOutlined,
+ DeleteOutlined,
+ TeamOutlined,
+ SearchOutlined,
+ EnvironmentOutlined,
+ RocketOutlined
+} from '@ant-design/icons';
+import {getProjectGroupList, deleteProjectGroup} from './service';
+import type {ProjectGroup} from './types';
import ProjectGroupModal from './components/ProjectGroupModal';
-import { BuildTypeEnum, DeployTypeEnum } from '../../Environment/List/types';
-const { Title, Text } = Typography;
-const { Search } = Input;
+const {Title, Text} = Typography;
+const {Search} = Input;
const ProjectList: React.FC = () => {
- const [projects, setProjects] = useState([]);
- const [loading, setLoading] = useState(false);
- const [modalVisible, setModalVisible] = useState(false);
- const [currentProject, setCurrentProject] = useState();
- const [searchText, setSearchText] = useState('');
- const [projectType, setProjectType] = useState('ALL');
+ const [projects, setProjects] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [modalVisible, setModalVisible] = useState(false);
+ const [currentProject, setCurrentProject] = useState();
+ const [searchText, setSearchText] = useState('');
+ const [projectType, setProjectType] = useState('ALL');
- const fetchProjects = async () => {
- setLoading(true);
- try {
- const data = await getProjectGroupList();
- setProjects(data);
- } catch (error) {
- message.error('获取项目组列表失败');
- } finally {
- setLoading(false);
- }
- };
-
- useEffect(() => {
- fetchProjects();
- }, []);
-
- const handleDelete = async (id: number) => {
- try {
- await deleteProjectGroup(id);
- message.success('删除成功');
- fetchProjects();
- } catch (error) {
- message.error('删除失败');
- }
- };
-
- const handleAdd = () => {
- setCurrentProject(undefined);
- setModalVisible(true);
- };
-
- const handleEdit = (project: ProjectGroup) => {
- setCurrentProject(project);
- setModalVisible(true);
- };
-
- const getProjectTypeInfo = (code: string) => {
- if (code.toUpperCase().includes('DEMO')) {
- return {
- type: 'DEMO',
- label: '示例项目组',
- color: '#1890ff',
- icon: ,
- description: '用于演示和测试的项目组'
- };
- }
- if (code.toUpperCase().includes('PLATFORM')) {
- return {
- type: 'PLATFORM',
- label: '平台项目组',
- color: '#52c41a',
- icon: ,
- description: '平台相关的核心项目组'
- };
- }
- return {
- type: 'BUSINESS',
- label: '业务项目组',
- color: '#722ed1',
- icon: ,
- description: '具体业务相关的项目组'
+ const fetchProjects = async () => {
+ setLoading(true);
+ try {
+ const data = await getProjectGroupList();
+ setProjects(data);
+ } catch (error) {
+ message.error('获取项目组列表失败');
+ } finally {
+ setLoading(false);
+ }
};
- };
- const filteredProjects = projects.filter(project => {
- const matchesSearch = searchText ?
- project.projectGroupName.toLowerCase().includes(searchText.toLowerCase()) ||
- project.projectGroupCode.toLowerCase().includes(searchText.toLowerCase()) ||
- project.projectGroupDesc?.toLowerCase().includes(searchText.toLowerCase())
- : true;
+ useEffect(() => {
+ fetchProjects();
+ }, []);
- const matchesType = projectType === 'ALL' ? true : getProjectTypeInfo(project.projectGroupCode).type === projectType;
+ const handleDelete = async (id: number) => {
+ try {
+ await deleteProjectGroup(id);
+ message.success('删除成功');
+ fetchProjects();
+ } catch (error) {
+ message.error('删除失败');
+ }
+ };
- return matchesSearch && matchesType;
- });
+ const handleAdd = () => {
+ setCurrentProject(undefined);
+ setModalVisible(true);
+ };
- const buildTypeMap = {
- [BuildTypeEnum.JENKINS]: {
- label: 'Jenkins构建',
- color: '#1890ff',
- icon: ,
- },
- [BuildTypeEnum.GITLAB_RUNNER]: {
- label: 'GitLab Runner构建',
- color: '#722ed1',
- icon: ,
- },
- [BuildTypeEnum.GITHUB_ACTION]: {
- label: 'GitHub Action构建',
- color: '#52c41a',
- icon: ,
- },
- };
+ const handleEdit = (project: ProjectGroup) => {
+ setCurrentProject(project);
+ setModalVisible(true);
+ };
- const deployTypeMap = {
- [DeployTypeEnum.K8S]: {
- label: 'K8S',
- color: '#1890ff',
- icon: ,
- },
- [DeployTypeEnum.DOCKER]: {
- label: 'Docker',
- color: '#13c2c2',
- icon: ,
- },
- [DeployTypeEnum.VM]: {
- label: 'VM',
- color: '#faad14',
- icon: ,
- },
- };
+ const getProjectTypeInfo = (code: string) => {
+ if (code.toUpperCase().includes('DEMO')) {
+ return {
+ type: 'DEMO',
+ label: '示例项目组',
+ color: '#1890ff',
+ icon: ,
+ description: '用于演示和测试的项目组'
+ };
+ }
+ if (code.toUpperCase().includes('PLATFORM')) {
+ return {
+ type: 'PLATFORM',
+ label: '平台项目组',
+ color: '#52c41a',
+ icon: ,
+ description: '平台相关的核心项目组'
+ };
+ }
+ return {
+ type: 'BUSINESS',
+ label: '业务项目组',
+ color: '#722ed1',
+ icon: ,
+ description: '具体业务相关的项目组'
+ };
+ };
- const ProjectCard = ({ project }: { project: ProjectGroup }) => {
- const typeInfo = getProjectTypeInfo(project.projectGroupCode);
+ const filteredProjects = projects.filter(project => {
+ const matchesSearch = searchText ? (
+ project.projectGroupName.toLowerCase().includes(searchText.toLowerCase()) ||
+ project.projectGroupCode.toLowerCase().includes(searchText.toLowerCase()) ||
+ project.projectGroupDesc?.toLowerCase().includes(searchText.toLowerCase())
+ ) : true;
+
+ const matchesType = projectType === 'ALL' ? true :
+ getProjectTypeInfo(project.projectGroupCode).type === projectType;
+
+ return matchesSearch && matchesType;
+ });
+
+ const ProjectCard = ({project}: { project: ProjectGroup }) => {
+ const typeInfo = getProjectTypeInfo(project.projectGroupCode);
+ const Icon = typeInfo.icon;
+
+ return (
+
+
+
+
+
+ {project.projectGroupName}
+
+ {typeInfo.label}
+
+
+
+ {project.projectGroupDesc || '暂无描述'}
+
+
+
+ {Icon}
+
+
+
+
+
+
项目组编码
+
+ {project.projectGroupCode}
+
+
+
+
+
+ 环境数量}
+ value={2}
+ prefix={}
+ valueStyle={{fontSize: '16px', color: typeInfo.color}}
+ />
+
+
+ 项目个数}
+ value={5}
+ prefix={}
+ valueStyle={{fontSize: '16px', color: typeInfo.color}}
+ />
+
+
+ 成员数量}
+ value={5}
+ prefix={}
+ valueStyle={{fontSize: '16px', color: typeInfo.color}}
+ />
+
+
+
+
+
+
+
+ handleDelete(project.id)}
+ >
+
+
+
+
+
+
+
+ );
+ };
return (
-
-
- {/* 项目组标题和类型 */}
-
-
- {typeInfo.icon}
-
-
-
- {project.projectGroupName}
-
-
- {project.projectGroupCode}
-
-
-
-
- {/* 项目组描述 */}
-
- {project.projectGroupDesc || '暂无描述'}
-
-
- {/* 项目组统计信息 */}
-
-
-
- {project.environments?.length || 0} 个环境
-
-
-
- 5 个成员
-
-
-
- 2小时前更新
-
-
-
- {/* 环境列表 */}
- {project.environments && project.environments.length > 0 && (
-
-
- {project.environments.map(env => {
- const buildType = buildTypeMap[env.buildType];
- const deployType = deployTypeMap[env.deployType];
-
- return (
-
-
-
- {env.envName}
- {env.envCode}
-
-
-
- {buildType.icon}
-
-
- {deployType.icon}
-
-
-
- {env.envDesc && (
-
{env.envDesc}
- )}
-
- );
- })}
-
-
- )}
-
- {/* 操作按钮 */}
-
- }
- onClick={() => handleEdit(project)}
- >
- 编辑
-
- }
- >
- 成员
-
- }
- onClick={() => handleDelete(project.id)}
- >
- 删除
-
-
-
-
- );
- };
-
- return (
- }
- 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',
+ }
+ onClick={handleAdd}
+ style={{
+ borderRadius: '8px',
+ boxShadow: '0 2px 0 rgba(0,0,0,0.045)',
+ height: '40px',
+ padding: '0 24px',
+ }}
+ >
+ 新建项目组
+
+ ],
}}
- >
- 新建项目组
-
- ],
- }}
- >
-
-
-
- 搜索>}
- size="large"
- value={searchText}
- onChange={e => setSearchText(e.target.value)}
- style={{ width: '100%' }}
- />
-
-
-
-
-
-
+ >
+
+
+
+ 搜索>}
+ size="large"
+ value={searchText}
+ onChange={e => setSearchText(e.target.value)}
+ style={{width: '100%'}}
+ />
+
+
+
-
-
- {filteredProjects.length > 0 ? (
- filteredProjects.map((project) => (
-
-
-
- ))
- ) : (
-
-
-
-
-
- )}
-
-
+
+
+ {filteredProjects.map(project => (
+
+
+
+ ))}
+
+
+
+
+
+
+
- setModalVisible(false)}
- onSuccess={() => {
- setModalVisible(false);
- fetchProjects();
- }}
- initialValues={currentProject}
- />
-
- );
+ setModalVisible(false)}
+ onSuccess={fetchProjects}
+ initialValues={currentProject}
+ />
+
+ );
};
export default ProjectList;