diff --git a/frontend/src/pages/Deploy/Environment/List/index.less b/frontend/src/pages/Deploy/Environment/List/index.less new file mode 100644 index 00000000..5b50ec0f --- /dev/null +++ b/frontend/src/pages/Deploy/Environment/List/index.less @@ -0,0 +1,43 @@ +.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; + } + } +} \ No newline at end of file diff --git a/frontend/src/pages/Deploy/Environment/List/index.tsx b/frontend/src/pages/Deploy/Environment/List/index.tsx index 69febb9d..a654c04e 100644 --- a/frontend/src/pages/Deploy/Environment/List/index.tsx +++ b/frontend/src/pages/Deploy/Environment/List/index.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { PageContainer } from '@ant-design/pro-layout'; -import { Card, Row, Col, Typography, Button, message, Popconfirm, Space, Tag, Tooltip } from 'antd'; -import { PlusOutlined, EditOutlined, DeleteOutlined, RocketOutlined } from '@ant-design/icons'; +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 type { Environment } from './types'; import { BuildTypeEnum, DeployTypeEnum } from './types'; @@ -14,16 +14,19 @@ const buildTypeMap = { [BuildTypeEnum.JENKINS]: { label: 'Jenkins构建', color: '#1890ff', + icon: , description: '使用Jenkins进行自动化构建和部署' }, [BuildTypeEnum.GITLAB_RUNNER]: { label: 'GitLab Runner构建', color: '#722ed1', + icon: , description: '使用GitLab Runner进行CI/CD流程' }, [BuildTypeEnum.GITHUB_ACTION]: { label: 'GitHub Action构建', color: '#52c41a', + icon: , description: '使用GitHub Action进行自动化工作流' }, }; @@ -32,16 +35,19 @@ const deployTypeMap = { [DeployTypeEnum.K8S]: { label: 'Kubernetes集群部署', color: '#1890ff', + icon: , description: '部署到Kubernetes容器编排平台' }, [DeployTypeEnum.DOCKER]: { label: 'Docker容器部署', color: '#13c2c2', + icon: , description: '部署为Docker独立容器' }, [DeployTypeEnum.VM]: { label: '虚拟机部署', color: '#faad14', + icon: , description: '部署到传统虚拟机环境' }, }; @@ -91,71 +97,52 @@ const EnvironmentList: React.FC = () => { const EnvironmentCard = ({ environment }: { environment: Environment }) => { const buildType = buildTypeMap[environment.buildType]; const deployType = deployTypeMap[environment.deployType]; - + return ( - , - handleDelete(environment.id)} - > - - , + ]} > -
+
- + <Title level={4} style={{ margin: 0 }}> {environment.envName} - + + {buildType.icon} {buildType.label} +
{
-
- 环境编码 -
- {environment.envCode} -
+ 环境编码 +
+ {environment.envCode}
- - -
- 构建方式 - - - {buildType.label} - - -
-
- 部署方式 - - - {deployType.label} - - -
-
- 排序 -
- {environment.sort} -
-
-
+
+ +
+ + + {deployType.icon} {deployType.label} + + + 排序: {environment.sort}
@@ -258,9 +219,10 @@ const EnvironmentList: React.FC = () => { onClick={handleAdd} style={{ borderRadius: '8px', - boxShadow: '0 2px 0 rgba(0,0,0,0.045)', - height: '40px', padding: '0 24px', + height: '40px', + boxShadow: '0 2px 0 rgba(0,0,0,0.045)', + transition: 'all 0.3s ease', }} > 新建环境 @@ -270,47 +232,39 @@ const EnvironmentList: React.FC = () => { >
- {environments.map(environment => ( - - + {environments.length > 0 ? ( + environments.map((environment) => ( + + + + )) + ) : ( + + + + - ))} - - -
- -
新建环境
-
-
- + )}
setModalVisible(false)} - onSuccess={fetchEnvironments} + onSuccess={() => { + setModalVisible(false); + fetchEnvironments(); + }} initialValues={currentEnvironment} /> diff --git a/frontend/src/pages/Deploy/ProjectGroup/List/components/ProjectModal.tsx b/frontend/src/pages/Deploy/ProjectGroup/List/components/ProjectGroupModal.tsx similarity index 96% rename from frontend/src/pages/Deploy/ProjectGroup/List/components/ProjectModal.tsx rename to frontend/src/pages/Deploy/ProjectGroup/List/components/ProjectGroupModal.tsx index 5ea9a850..65bf8f70 100644 --- a/frontend/src/pages/Deploy/ProjectGroup/List/components/ProjectModal.tsx +++ b/frontend/src/pages/Deploy/ProjectGroup/List/components/ProjectGroupModal.tsx @@ -5,14 +5,14 @@ import type { Environment } from '../../../Environment/List/types'; import { createProjectGroup, updateProjectGroup } from '../service'; import { getEnvironmentList } from '../../../Environment/List/service'; -interface ProjectModalProps { +interface ProjectGroupModalProps { visible: boolean; onCancel: () => void; onSuccess: () => void; initialValues?: ProjectGroup; } -const ProjectModal: React.FC = ({ +const ProjectGroupModal: React.FC = ({ visible, onCancel, onSuccess, @@ -83,6 +83,7 @@ const ProjectModal: React.FC = ({ onOk={handleSubmit} onCancel={handleCancel} destroyOnClose + width={560} >
= ({ ); }; -export default ProjectModal; +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 new file mode 100644 index 00000000..bdaf050c --- /dev/null +++ b/frontend/src/pages/Deploy/ProjectGroup/List/index.less @@ -0,0 +1,171 @@ +.project-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; + } + } + + .ant-space { + .anticon { + font-size: 14px; + } + } + + .environment-section { + .section-header { + margin-bottom: 16px; + } + } + + .environment-list { + .environment-item { + background: #fff; + border-radius: 8px; + padding: 16px; + border: 1px solid #f0f0f0; + margin-bottom: 12px; + transition: all 0.3s ease; + + &:last-child { + margin-bottom: 0; + } + + &:hover { + transform: translateX(4px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + border-color: #d9d9d9; + } + + .env-header { + .env-title { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + } + + .env-tags { + display: flex; + gap: 6px; + + .env-tag { + margin: 0; + padding: 4px 8px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + + .anticon { + font-size: 14px; + } + } + } + + .env-meta { + display: flex; + align-items: center; + gap: 12px; + + .env-code { + font-family: 'monaco, monospace'; + color: #666; + background: #f5f5f5; + padding: 2px 8px; + border-radius: 4px; + font-size: 12px; + margin: 0; + } + + .env-desc { + font-size: 12px; + color: #8c8c8c; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + cursor: help; + } + } + } + + .env-footer { + margin-top: 12px; + padding-top: 12px; + border-top: 1px dashed #f0f0f0; + + .anticon { + opacity: 0.6; + } + } + } + } + + .info-section { + background: #f5f5f5; + border-radius: 8px; + padding: 16px; + margin-bottom: 16px; + transition: all 0.3s ease; + + &:hover { + background: #f0f0f0; + } + + .section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + } + + .section-content { + background: #fff; + border-radius: 4px; + border: 1px solid #f0f0f0; + padding: 8px; + + &.code { + font-family: 'monaco, monospace'; + font-size: 14px; + color: #262626; + } + } + } +} \ 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 e87d3cc7..77b8ca49 100644 --- a/frontend/src/pages/Deploy/ProjectGroup/List/index.tsx +++ b/frontend/src/pages/Deploy/ProjectGroup/List/index.tsx @@ -1,19 +1,11 @@ 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, - ClockCircleOutlined, - RocketOutlined -} from '@ant-design/icons'; +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 ProjectModal from './components/ProjectModal'; +import ProjectGroupModal from './components/ProjectGroupModal'; +import { BuildTypeEnum, DeployTypeEnum } from '../../Environment/List/types'; const { Title, Text } = Typography; const { Search } = Input; @@ -91,204 +83,221 @@ const ProjectList: React.FC = () => { }; const filteredProjects = projects.filter(project => { - const matchesSearch = searchText ? ( + const matchesSearch = searchText ? project.projectGroupName.toLowerCase().includes(searchText.toLowerCase()) || project.projectGroupCode.toLowerCase().includes(searchText.toLowerCase()) || project.projectGroupDesc?.toLowerCase().includes(searchText.toLowerCase()) - ) : true; + : true; - const matchesType = projectType === 'ALL' ? true : - getProjectTypeInfo(project.projectGroupCode).type === projectType; + const matchesType = projectType === 'ALL' ? true : getProjectTypeInfo(project.projectGroupCode).type === projectType; return matchesSearch && matchesType; }); + 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 deployTypeMap = { + [DeployTypeEnum.K8S]: { + label: 'K8S', + color: '#1890ff', + icon: , + }, + [DeployTypeEnum.DOCKER]: { + label: 'Docker', + color: '#13c2c2', + icon: , + }, + [DeployTypeEnum.VM]: { + label: 'VM', + color: '#faad14', + icon: , + }, + }; + const ProjectCard = ({ project }: { project: ProjectGroup }) => { const typeInfo = getProjectTypeInfo(project.projectGroupCode); - const Icon = typeInfo.icon; - + return ( + + , + + + , + + + + ]} > -
+
-
- - {project.projectGroupName} - <Tag - color={typeInfo.color} - style={{ - margin: 0, - padding: '0 8px', - borderRadius: '4px', - fontSize: '12px' - }} - > - {typeInfo.label} - </Tag> - - - {project.projectGroupDesc || '暂无描述'} - + + {project.projectGroupName} + + + + {typeInfo.icon} {typeInfo.label} + + +
+ + + {project.projectGroupDesc || '暂无描述'} + + +
+
+ 项目组编码
-
- {Icon} +
+ {project.projectGroupCode}
+ {project.environments && project.environments.length > 0 && ( +
+
+
+ 关联环境 + + {project.environments.length} 个环境 + +
+ + + +
+
+ {project.environments.map(env => { + const buildType = buildTypeMap[env.buildType]; + const deployType = deployTypeMap[env.deployType]; + + return ( +
+
+
+ {env.envName} +
+ + + {buildType.icon} + + + + + {deployType.icon} + + +
+
+
+ + + {env.envCode} + + + {env.envDesc && ( + + + {env.envDesc} + + + )} +
+
+
+ + + + 最近部署:2小时前 + + + + 部署次数:12次 + + +
+
+ ); + })} +
+
+ )} +
-
- 项目组编码 -
- {project.projectGroupCode} -
-
- - - - 环境数量} - value={2} - prefix={} - valueStyle={{ fontSize: '16px', color: typeInfo.color }} - /> - - - 成员数量} - value={5} - prefix={} - valueStyle={{ fontSize: '16px', color: typeInfo.color }} - /> - - - -
- 最近活动 -
- - 2小时前 -
-
- -
- 排序 -
- {project.sort} -
-
- - - - - - handleDelete(project.id)} - > - - - + + + 2小时前 + 排序: {project.sort}
@@ -307,9 +316,10 @@ const ProjectList: React.FC = () => { onClick={handleAdd} style={{ borderRadius: '8px', - boxShadow: '0 2px 0 rgba(0,0,0,0.045)', - height: '40px', padding: '0 24px', + height: '40px', + boxShadow: '0 2px 0 rgba(0,0,0,0.045)', + transition: 'all 0.3s ease', }} > 新建项目组 @@ -350,47 +360,39 @@ const ProjectList: React.FC = () => {
- {filteredProjects.map(project => ( - - + {filteredProjects.length > 0 ? ( + filteredProjects.map((project) => ( + + + + )) + ) : ( + + + + - ))} - - -
- -
新建项目组
-
-
- + )}
- setModalVisible(false)} - onSuccess={fetchProjects} + onSuccess={() => { + setModalVisible(false); + fetchProjects(); + }} initialValues={currentProject} /> diff --git a/frontend/src/pages/Deploy/ProjectGroup/List/types.ts b/frontend/src/pages/Deploy/ProjectGroup/List/types.ts index a534628a..d7325471 100644 --- a/frontend/src/pages/Deploy/ProjectGroup/List/types.ts +++ b/frontend/src/pages/Deploy/ProjectGroup/List/types.ts @@ -1,4 +1,5 @@ import { BaseResponse, BaseRequest, BaseQuery } from '@/types/base'; +import { Environment } from '../../Environment/List/types'; // 项目基础信息 export interface ProjectGroup extends BaseResponse { @@ -6,7 +7,8 @@ export interface ProjectGroup extends BaseResponse { projectGroupCode: string; projectGroupName: string; projectGroupDesc?: string; - projectGroupStatus: string; + projectGroupStatus: 'ENABLE' | 'DISABLE'; + environments: Environment[]; sort: number; }