diff --git a/frontend/src/pages/Deploy/Application/List/index.tsx b/frontend/src/pages/Deploy/Application/List/index.tsx index 8a8b37bf..791cb8e4 100644 --- a/frontend/src/pages/Deploy/Application/List/index.tsx +++ b/frontend/src/pages/Deploy/Application/List/index.tsx @@ -1,324 +1,247 @@ -import React, { useState, useEffect } from 'react'; -import { PageContainer } from '@ant-design/pro-layout'; -import { Card, Row, Col, Typography, Button, message, Popconfirm, Tag, Space, Tooltip, Select } from 'antd'; -import { PlusOutlined, EditOutlined, DeleteOutlined, CodeOutlined, CloudUploadOutlined, ApiOutlined } from '@ant-design/icons'; -import { getApplicationList, deleteApplication } from './service'; -import { getProjectGroupList } from '../../ProjectGroup/List/service'; -import type { Application } from './types'; -import type { ProjectGroup } from '../../ProjectGroup/List/types'; +import React, {useState, useEffect} from 'react'; +import {PageContainer} from '@ant-design/pro-layout'; +import {Button, message, Popconfirm, Tag, Space, Tooltip, Select} from 'antd'; +import {PlusOutlined, EditOutlined, DeleteOutlined, CodeOutlined, CloudUploadOutlined, ApiOutlined} from '@ant-design/icons'; +import {getApplicationPage, deleteApplication} from './service'; +import {getProjectGroupList} from '../../ProjectGroup/List/service'; +import type {Application, ApplicationQuery} from './types'; +import type {ProjectGroup} from '../../ProjectGroup/List/types'; import ApplicationModal from './components/ApplicationModal'; +import {ProTable} from '@ant-design/pro-components'; +import type {ProColumns, ActionType} from '@ant-design/pro-components'; -const { Title, Text } = Typography; -const { Option } = Select; +const {Option} = Select; const ApplicationList: React.FC = () => { - const [applications, setApplications] = useState([]); - const [projectGroups, setProjects] = useState([]); - const [selectedProjectGroupId, setSelectedProjectGroupId] = useState(); - const [loading, setLoading] = useState(false); - const [modalVisible, setModalVisible] = useState(false); - const [currentApplication, setCurrentApplication] = useState(); + const [projectGroups, setProjects] = useState([]); + const [selectedProjectGroupId, setSelectedProjectGroupId] = useState(); + const [modalVisible, setModalVisible] = useState(false); + const [currentApplication, setCurrentApplication] = useState(); + const actionRef = React.useRef(); - // 获取项目列表 - const fetchProjects = async () => { - try { - const data = await getProjectGroupList(); - setProjects(data); - if (data.length > 0 && !selectedProjectGroupId) { - setSelectedProjectGroupId(data[0].id); - } - } catch (error) { - message.error('获取项目组列表失败'); - } - }; + // 获取项目列表 + const fetchProjects = async () => { + try { + const data = await getProjectGroupList(); + setProjects(data); + if (data.length > 0 && !selectedProjectGroupId) { + setSelectedProjectGroupId(data[0].id); + } + } catch (error) { + message.error('获取项目组列表失败'); + } + }; - // 获取应用列表 - const fetchApplications = async () => { - if (!selectedProjectGroupId) return; - - setLoading(true); - try { - const data = await getApplicationList(); - // TODO: 这里需要根据selectedProjectId筛选应用 - setApplications(data.filter(app => app.projectGroupId === selectedProjectGroupId)); - } catch (error) { - message.error('获取应用列表失败'); - } finally { - setLoading(false); - } - }; + useEffect(() => { + fetchProjects(); + }, []); - useEffect(() => { - fetchProjects(); - }, []); + const handleDelete = async (id: number) => { + try { + await deleteApplication(id); + message.success('删除成功'); + actionRef.current?.reload(); + } catch (error) { + message.error('删除失败'); + } + }; - useEffect(() => { - fetchApplications(); - }, [selectedProjectGroupId]); + const handleAdd = () => { + if (!selectedProjectGroupId) { + message.warning('请先选择项目组'); + return; + } + setCurrentApplication(undefined); + setModalVisible(true); + }; - const handleDelete = async (id: number) => { - try { - await deleteApplication(id); - message.success('删除成功'); - fetchApplications(); - } catch (error) { - message.error('删除失败'); - } - }; + const handleEdit = (application: Application) => { + setCurrentApplication(application); + setModalVisible(true); + }; - const handleAdd = () => { - setCurrentApplication(undefined); - setModalVisible(true); - }; + const handleProjectChange = (value: number) => { + setSelectedProjectGroupId(value); + actionRef.current?.reload(); + }; - const handleEdit = (application: Application) => { - setCurrentApplication(application); - setModalVisible(true); - }; + // 根据应用编码推测应用类型 + const getAppType = (appCode: string) => { + if (appCode.toLowerCase().includes('web')) return {icon: , name: '前端应用', color: '#1890ff'}; + if (appCode.toLowerCase().includes('api')) return {icon: , name: 'API服务', color: '#52c41a'}; + return {icon: , name: '后端服务', color: '#722ed1'}; + }; - const handleProjectChange = (value: number) => { - setSelectedProjectGroupId(value); - }; + const columns: ProColumns[] = [ + { + title: '应用编码', + dataIndex: 'appCode', + width: 180, + copyable: true, + ellipsis: true, + fixed: 'left', + render: (text, record) => { + const appType = getAppType(record.appCode); + return ( + + + {appType.icon} + + {text} + + ); + }, + }, + { + title: '应用名称', + dataIndex: 'appName', + width: 150, + ellipsis: true, + }, + { + title: '应用描述', + dataIndex: 'appDesc', + ellipsis: true, + width: 200, + }, + { + title: '应用状态', + dataIndex: 'appStatus', + width: 100, + valueEnum: { + 'ENABLE': {text: '启用', status: 'Success'}, + 'DISABLE': {text: '禁用', status: 'Default'}, + }, + }, + { + title: '排序', + dataIndex: 'sort', + width: 80, + sorter: true, + }, + { + title: '操作', + width: 200, + key: 'action', + valueType: 'option', + fixed: 'right', + align: 'center', + render: (_, record) => [ + , + handleDelete(record.id)} + > + + , + + ], + }, + ]; - // 根据应用编码推测应用类型 - const getAppType = (appCode: string) => { - if (appCode.toLowerCase().includes('web')) return { icon: , name: '前端应用', color: '#1890ff' }; - if (appCode.toLowerCase().includes('api')) return { icon: , name: 'API服务', color: '#52c41a' }; - return { icon: , name: '后端服务', color: '#722ed1' }; - }; - - const ApplicationCard = ({ application }: { application: Application }) => { - const appType = getAppType(application.appCode); - return ( - } - onClick={() => handleEdit(application)} - > - 编辑 - , - handleDelete(application.id)} - > - - , - ]} - > -
-
- {appType.icon} - {appType.name} -
- - - {application.appName} - <Tag - color={application.appStatus === 'ENABLE' ? '#52c41a' : '#d9d9d9'} - style={{ - borderRadius: '4px', - fontSize: '12px', - padding: '0 8px', - }} - > - {application.appStatus === 'ENABLE' ? '启用' : '禁用'} - </Tag> - + <> + + columns={columns} + actionRef={actionRef} + scroll={{x: 1300}} + cardBordered + request={async (params) => { + if (!selectedProjectGroupId) { + return { + data: [], + success: true, + total: 0, + }; + } + const queryParams: ApplicationQuery = { + pageSize: params.pageSize, + pageNum: params.current, + projectGroupId: selectedProjectGroupId, + appCode: params.appCode as string, + appName: params.appName as string, + appStatus: params.appStatus as string, + }; + const data = await getApplicationPage(queryParams); + 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={() => [ + , + ]} + /> - - {application.appDesc || '暂无描述'} - - -
-
- 应用编码 -
- {application.appCode} -
-
- -
- 排序 -
- {application.sort} -
-
- - - -
-
-
-
+ {selectedProjectGroupId && ( + setModalVisible(false)} + onSuccess={() => { + setModalVisible(false); + actionRef.current?.reload(); + }} + initialValues={currentApplication} + projectGroupId={selectedProjectGroupId} + /> + )} + ); - }; - - const currentProject = projectGroups.find(p => p.id === selectedProjectGroupId); - - return ( - - 当前项目组: - - - ), - extra: [ - - ], - }} - > -
- - {applications.map(application => ( - - - - ))} - {selectedProjectGroupId && ( - - -
- -
新建应用
-
-
- - )} -
-
- - setModalVisible(false)} - onSuccess={fetchApplications} - initialValues={currentApplication} - projectGroupId={selectedProjectGroupId!} - /> -
- ); }; export default ApplicationList; diff --git a/frontend/src/pages/Deploy/Environment/List/index.tsx b/frontend/src/pages/Deploy/Environment/List/index.tsx index 6499de7d..bfadb5bd 100644 --- a/frontend/src/pages/Deploy/Environment/List/index.tsx +++ b/frontend/src/pages/Deploy/Environment/List/index.tsx @@ -5,8 +5,8 @@ import {PlusOutlined, DeleteOutlined, EditOutlined, SearchOutlined, ReloadOutlin import {getEnvironmentList, deleteEnvironment, getEnvironmentPage} from './service'; import type {Environment} from './types'; import EnvironmentModal from './components/EnvironmentModal'; -import { ProTable } from '@ant-design/pro-components'; -import type { ProColumns } from '@ant-design/pro-components'; +import {ProTable} from '@ant-design/pro-components'; +import type {ProColumns} from '@ant-design/pro-components'; const EnvironmentList: React.FC = () => { const [modalVisible, setModalVisible] = useState(false); @@ -104,71 +104,70 @@ const EnvironmentList: React.FC = () => { ]; return ( + <> + + 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={() => [ + , + ]} + /> - <> - - 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={() => [ - , - ]} - /> - - setModalVisible(false)} - onSuccess={() => { - setModalVisible(false); - actionRef.current?.reload(); - }} - initialValues={currentEnvironment} - /> - + setModalVisible(false)} + onSuccess={() => { + setModalVisible(false); + actionRef.current?.reload(); + }} + initialValues={currentEnvironment} + /> + ); }; diff --git a/frontend/src/pages/Deploy/ProjectGroup/List/index.tsx b/frontend/src/pages/Deploy/ProjectGroup/List/index.tsx index a64f0c54..bec35554 100644 --- a/frontend/src/pages/Deploy/ProjectGroup/List/index.tsx +++ b/frontend/src/pages/Deploy/ProjectGroup/List/index.tsx @@ -1,52 +1,31 @@ -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, Popconfirm, Space, Tag, Input, Select, Tooltip, Statistic, Divider} from 'antd'; +import {Button, message, Popconfirm, Space, Tag, Tooltip} 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 {getProjectGroupPage, deleteProjectGroup} from './service'; +import type {ProjectGroup, ProjectGroupQueryParams} from './types'; import ProjectGroupModal from './components/ProjectGroupModal'; import { ProjectGroupTypeEnum } from './types'; +import { ProTable } from '@ant-design/pro-components'; +import type { ProColumns, ActionType } from '@ant-design/pro-components'; -const {Title, Text} = Typography; -const {Search} = Input; - -const ProjectList: React.FC = () => { - const [projects, setProjects] = useState([]); - const [loading, setLoading] = useState(false); +const ProjectGroupList: React.FC = () => { 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 actionRef = React.useRef(); const handleDelete = async (id: number) => { try { await deleteProjectGroup(id); message.success('删除成功'); - fetchProjects(); + actionRef.current?.reload(); } catch (error) { message.error('删除失败'); } @@ -63,305 +42,205 @@ const ProjectList: React.FC = () => { }; const getProjectTypeInfo = (type: ProjectGroupTypeEnum) => { - switch (type) { - case ProjectGroupTypeEnum.PRODUCT: - return { - type: ProjectGroupTypeEnum.PRODUCT, - label: '产品型', - color: '#1890ff', - icon: , - description: '产品型相关的项目组' - }; - case ProjectGroupTypeEnum.PROJECT: - return { - type: ProjectGroupTypeEnum.PROJECT, - label: '项目型', - color: '#52c41a', - icon: , - description: '项目型相关的项目组' - }; - default: - return { - type: ProjectGroupTypeEnum.PROJECT, - label: '项目型', - color: '#722ed1', - icon: , - description: '项目型相关的项目组' - }; - } - }; - - 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.type).type === projectType; - - return matchesSearch && matchesType; - }); - - const ProjectCard = ({projectGroup}: { projectGroup: ProjectGroup }) => { - const typeInfo = getProjectTypeInfo(projectGroup.type); - const Icon = typeInfo.icon; - - return ( - -
-
-
- - {projectGroup.projectGroupName}({projectGroup.projectGroupCode}) - <Tag - color={typeInfo.color} - style={{ - margin: 0, - padding: '0 8px', - borderRadius: '4px', - fontSize: '12px' - }} - > - {typeInfo.label} - </Tag> - - - {projectGroup.projectGroupDesc || '暂无描述'} - -
-
- {Icon} -
-
- -
- - - 环境数量} - value={projectGroup.environments.length} - prefix={} - valueStyle={{fontSize: '16px', color: typeInfo.color}} - /> - - - 项目个数} - value={projectGroup.applications.length} - prefix={} - valueStyle={{fontSize: '16px', color: typeInfo.color}} - /> - - - 成员数量} - prefix={} - valueStyle={{fontSize: '16px', color: typeInfo.color}} - /> - - - - - - - - handleDelete(projectGroup.id)} - > - - - - -
-
-
- ); + switch (type) { + case ProjectGroupTypeEnum.PRODUCT: + return { + type: ProjectGroupTypeEnum.PRODUCT, + label: '产品型', + color: '#1890ff', + icon: , + description: '产品型相关的项目组' + }; + case ProjectGroupTypeEnum.PROJECT: + return { + type: ProjectGroupTypeEnum.PROJECT, + label: '项目型', + color: '#52c41a', + icon: , + description: '项目型相关的项目组' + }; + default: + return { + type: ProjectGroupTypeEnum.PROJECT, + label: '项目型', + color: '#722ed1', + icon: , + description: '项目型相关的项目组' + }; + } }; + const columns: ProColumns[] = [ + { + title: '项目组编码', + dataIndex: 'projectGroupCode', + width: 120, + copyable: true, + ellipsis: true, + fixed: 'left', + }, + { + title: '项目组名称', + dataIndex: 'projectGroupName', + width: 150, + ellipsis: true, + }, + { + title: '项目组描述', + dataIndex: 'projectGroupDesc', + ellipsis: true, + }, + { + title: '项目组类型', + dataIndex: 'type', + width: 100, + render: (type) => { + const typeInfo = getProjectTypeInfo(type as ProjectGroupTypeEnum); + return ( + + {typeInfo.label} + + ); + }, + valueEnum: { + [ProjectGroupTypeEnum.PRODUCT]: { text: '产品型' }, + [ProjectGroupTypeEnum.PROJECT]: { text: '项目型' }, + }, + }, + { + title: '环境数量', + dataIndex: 'environments', + width: 100, + render: (_, record) => ( + + + + {record.environments?.length || 0} + + + ), + }, + { + title: '项目数量', + dataIndex: 'applications', + width: 100, + render: (_, record) => ( + + + + {record.applications?.length || 0} + + + ), + }, + { + title: '排序', + dataIndex: 'sort', + width: 80, + sorter: true, + }, + { + title: '操作', + width: 180, + key: 'action', + valueType: 'option', + fixed: 'right', + render: (_, record) => ( + + + handleDelete(record.id)} + > + + + + ), + }, + ]; + return ( - + + columns={columns} + actionRef={actionRef} + cardBordered + request={async (params) => { + const queryParams: ProjectGroupQueryParams = { + pageSize: params.pageSize, + pageNum: params.current, + projectGroupName: params.projectGroupName as string, + projectGroupCode: params.projectGroupCode as string, + projectGroupStatus: params.projectGroupStatus as string, + }; + const data = await getProjectGroupPage(queryParams); + 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={() => [ - ], - }} - > -
- - - 搜索} - size="large" - value={searchText} - onChange={e => setSearchText(e.target.value)} - style={{width: '100%'}} - /> - - -