1
This commit is contained in:
parent
51aa8b6994
commit
09a6a020ce
@ -1,23 +1,23 @@
|
||||
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 {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 {getApplicationPage, deleteApplication} from './service';
|
||||
import {getProjectGroupList} from '../../ProjectGroup/List/service';
|
||||
import type { Application } from './types';
|
||||
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 ApplicationList: React.FC = () => {
|
||||
const [applications, setApplications] = useState<Application[]>([]);
|
||||
const [projectGroups, setProjects] = useState<ProjectGroup[]>([]);
|
||||
const [selectedProjectGroupId, setSelectedProjectGroupId] = useState<number>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [currentApplication, setCurrentApplication] = useState<Application>();
|
||||
const actionRef = React.useRef<ActionType>();
|
||||
|
||||
// 获取项目列表
|
||||
const fetchProjects = async () => {
|
||||
@ -32,41 +32,25 @@ const ApplicationList: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 获取应用列表
|
||||
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(() => {
|
||||
fetchApplications();
|
||||
}, [selectedProjectGroupId]);
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await deleteApplication(id);
|
||||
message.success('删除成功');
|
||||
fetchApplications();
|
||||
actionRef.current?.reload();
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
if (!selectedProjectGroupId) {
|
||||
message.warning('请先选择项目组');
|
||||
return;
|
||||
}
|
||||
setCurrentApplication(undefined);
|
||||
setModalVisible(true);
|
||||
};
|
||||
@ -78,6 +62,7 @@ const ApplicationList: React.FC = () => {
|
||||
|
||||
const handleProjectChange = (value: number) => {
|
||||
setSelectedProjectGroupId(value);
|
||||
actionRef.current?.reload();
|
||||
};
|
||||
|
||||
// 根据应用编码推测应用类型
|
||||
@ -87,237 +72,175 @@ const ApplicationList: React.FC = () => {
|
||||
return {icon: <CloudUploadOutlined/>, name: '后端服务', color: '#722ed1'};
|
||||
};
|
||||
|
||||
const ApplicationCard = ({ application }: { application: Application }) => {
|
||||
const appType = getAppType(application.appCode);
|
||||
|
||||
const columns: ProColumns<Application>[] = [
|
||||
{
|
||||
title: '应用编码',
|
||||
dataIndex: 'appCode',
|
||||
width: 180,
|
||||
copyable: true,
|
||||
ellipsis: true,
|
||||
fixed: 'left',
|
||||
render: (text, record) => {
|
||||
const appType = getAppType(record.appCode);
|
||||
return (
|
||||
<Card
|
||||
hoverable
|
||||
className="application-card"
|
||||
style={{
|
||||
height: '100%',
|
||||
borderRadius: '12px',
|
||||
overflow: 'hidden',
|
||||
border: '1px solid #f0f0f0',
|
||||
transition: 'all 0.3s',
|
||||
}}
|
||||
bodyStyle={{
|
||||
padding: '24px',
|
||||
background: `linear-gradient(145deg, #ffffff 0%, ${appType.color}0A 100%)`,
|
||||
}}
|
||||
actions={[
|
||||
<Space>
|
||||
<Tooltip title={appType.name}>
|
||||
{appType.icon}
|
||||
</Tooltip>
|
||||
{text}
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
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) => [
|
||||
<Button
|
||||
type="text"
|
||||
key="edit"
|
||||
icon={<EditOutlined style={{ color: appType.color }} />}
|
||||
onClick={() => handleEdit(application)}
|
||||
type="link"
|
||||
icon={<EditOutlined/>}
|
||||
onClick={() => handleEdit(record)}
|
||||
>
|
||||
编辑
|
||||
</Button>,
|
||||
<Popconfirm
|
||||
key="delete"
|
||||
title="确定要删除该应用吗?"
|
||||
onConfirm={() => handleDelete(application.id)}
|
||||
description="删除后将无法恢复,请谨慎操作"
|
||||
onConfirm={() => handleDelete(record.id)}
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
type="link"
|
||||
danger
|
||||
icon={<DeleteOutlined/>}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>,
|
||||
]}
|
||||
<Button
|
||||
key="deploy"
|
||||
type="link"
|
||||
icon={<CloudUploadOutlined/>}
|
||||
>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '-12px',
|
||||
right: '-12px',
|
||||
padding: '4px 8px',
|
||||
background: appType.color,
|
||||
color: '#fff',
|
||||
borderRadius: '0 0 0 8px',
|
||||
fontSize: '12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px'
|
||||
}}>
|
||||
{appType.icon}
|
||||
{appType.name}
|
||||
</div>
|
||||
|
||||
<Title level={4} style={{
|
||||
marginBottom: '16px',
|
||||
color: '#1f1f1f',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px'
|
||||
}}>
|
||||
{application.appName}
|
||||
<Tag
|
||||
color={application.appStatus === 'ENABLE' ? '#52c41a' : '#d9d9d9'}
|
||||
style={{
|
||||
borderRadius: '4px',
|
||||
fontSize: '12px',
|
||||
padding: '0 8px',
|
||||
}}
|
||||
>
|
||||
{application.appStatus === 'ENABLE' ? '启用' : '禁用'}
|
||||
</Tag>
|
||||
</Title>
|
||||
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{
|
||||
display: 'block',
|
||||
marginBottom: '20px',
|
||||
height: '40px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
display: '-webkit-box',
|
||||
}}
|
||||
>
|
||||
{application.appDesc || '暂无描述'}
|
||||
</Text>
|
||||
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '12px',
|
||||
padding: '16px',
|
||||
background: '#fff',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.04)',
|
||||
}}>
|
||||
<div>
|
||||
<Text type="secondary" style={{ fontSize: '12px' }}>应用编码</Text>
|
||||
<div style={{
|
||||
color: '#262626',
|
||||
fontFamily: 'monospace',
|
||||
marginTop: '4px',
|
||||
fontSize: '14px',
|
||||
padding: '4px 8px',
|
||||
background: '#f5f5f5',
|
||||
borderRadius: '4px',
|
||||
}}>
|
||||
{application.appCode}
|
||||
</div>
|
||||
</div>
|
||||
<Space size="large">
|
||||
<div>
|
||||
<Text type="secondary" style={{ fontSize: '12px' }}>排序</Text>
|
||||
<div style={{
|
||||
color: '#262626',
|
||||
textAlign: 'center',
|
||||
marginTop: '4px',
|
||||
fontSize: '14px',
|
||||
}}>
|
||||
{application.sort}
|
||||
</div>
|
||||
</div>
|
||||
<Tooltip title="更多功能开发中...">
|
||||
<Button type="text" size="small">查看详情</Button>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const currentProject = projectGroups.find(p => p.id === selectedProjectGroupId);
|
||||
部署测试
|
||||
</Button>
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
header={{
|
||||
title: '应用管理',
|
||||
subTitle: (
|
||||
<Space>
|
||||
<span>当前项目组:</span>
|
||||
<Select
|
||||
value={selectedProjectGroupId}
|
||||
onChange={handleProjectChange}
|
||||
style={{ width: 200 }}
|
||||
placeholder="请选择项目组"
|
||||
showSearch
|
||||
optionFilterProp="children"
|
||||
>
|
||||
{projectGroups.map(project => (
|
||||
<Option key={project.id} value={project.id}>
|
||||
{project.projectGroupName}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Space>
|
||||
),
|
||||
extra: [
|
||||
<>
|
||||
<ProTable<Application>
|
||||
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={() => [
|
||||
<Button
|
||||
key="add"
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleAdd}
|
||||
style={{
|
||||
borderRadius: '6px',
|
||||
boxShadow: '0 2px 0 rgba(0,0,0,0.045)',
|
||||
}}
|
||||
icon={<PlusOutlined/>}
|
||||
disabled={!selectedProjectGroupId}
|
||||
>
|
||||
新建应用
|
||||
</Button>
|
||||
],
|
||||
}}
|
||||
>
|
||||
<div style={{ padding: '24px' }}>
|
||||
<Row gutter={[24, 24]}>
|
||||
{applications.map(application => (
|
||||
<Col xs={24} sm={12} md={8} lg={6} key={application.id}>
|
||||
<ApplicationCard application={application} />
|
||||
</Col>
|
||||
))}
|
||||
{selectedProjectGroupId && (
|
||||
<Col xs={24} sm={12} md={8} lg={6}>
|
||||
<Card
|
||||
hoverable
|
||||
onClick={handleAdd}
|
||||
style={{
|
||||
height: '100%',
|
||||
borderRadius: '12px',
|
||||
border: '1px dashed #d9d9d9',
|
||||
background: '#fafafa',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minHeight: '250px',
|
||||
}}
|
||||
bodyStyle={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<PlusOutlined style={{ fontSize: '24px', color: '#8c8c8c' }} />
|
||||
<div style={{ marginTop: '8px', color: '#8c8c8c' }}>新建应用</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
</div>
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
|
||||
{selectedProjectGroupId && (
|
||||
<ApplicationModal
|
||||
visible={modalVisible}
|
||||
onCancel={() => setModalVisible(false)}
|
||||
onSuccess={fetchApplications}
|
||||
onSuccess={() => {
|
||||
setModalVisible(false);
|
||||
actionRef.current?.reload();
|
||||
}}
|
||||
initialValues={currentApplication}
|
||||
projectGroupId={selectedProjectGroupId!}
|
||||
projectGroupId={selectedProjectGroupId}
|
||||
/>
|
||||
</PageContainer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -104,7 +104,6 @@ const EnvironmentList: React.FC = () => {
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
<>
|
||||
<ProTable<Environment>
|
||||
columns={columns}
|
||||
|
||||
@ -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<ProjectGroup[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const ProjectGroupList: React.FC = () => {
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [currentProject, setCurrentProject] = useState<ProjectGroup>();
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [projectType, setProjectType] = useState<string>('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<ActionType>();
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
await deleteProjectGroup(id);
|
||||
message.success('删除成功');
|
||||
fetchProjects();
|
||||
actionRef.current?.reload();
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
}
|
||||
@ -91,277 +70,177 @@ const ProjectList: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
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 (
|
||||
<Card
|
||||
hoverable
|
||||
className="project-card"
|
||||
style={{
|
||||
height: '100%',
|
||||
borderRadius: '16px',
|
||||
overflow: 'hidden',
|
||||
border: 'none',
|
||||
transition: 'all 0.3s',
|
||||
boxShadow: '0 1px 2px 0 rgba(0,0,0,0.03), 0 1px 6px -1px rgba(0,0,0,0.02), 0 2px 4px 0 rgba(0,0,0,0.02)',
|
||||
'&:hover': {
|
||||
transform: 'translateY(-2px)',
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
|
||||
const columns: ProColumns<ProjectGroup>[] = [
|
||||
{
|
||||
title: '项目组编码',
|
||||
dataIndex: 'projectGroupCode',
|
||||
width: 120,
|
||||
copyable: true,
|
||||
ellipsis: true,
|
||||
fixed: 'left',
|
||||
},
|
||||
}}
|
||||
bodyStyle={{
|
||||
padding: '24px',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
background: `linear-gradient(145deg, #ffffff 0%, ${typeInfo.color}08 100%)`,
|
||||
}}
|
||||
>
|
||||
<div style={{flex: 1}}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: '16px',
|
||||
position: 'relative'
|
||||
}}>
|
||||
<div style={{flex: 1}}>
|
||||
<Title level={4} style={{
|
||||
margin: 0,
|
||||
color: '#1f1f1f',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px'
|
||||
}}>
|
||||
{projectGroup.projectGroupName}({projectGroup.projectGroupCode})
|
||||
<Tag
|
||||
color={typeInfo.color}
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: '0 8px',
|
||||
borderRadius: '4px',
|
||||
fontSize: '12px'
|
||||
}}
|
||||
>
|
||||
{
|
||||
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 (
|
||||
<Tag color={typeInfo.color}>
|
||||
{typeInfo.label}
|
||||
</Tag>
|
||||
</Title>
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{
|
||||
display: 'block',
|
||||
marginTop: '8px',
|
||||
height: '40px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
display: '-webkit-box',
|
||||
}}
|
||||
>
|
||||
{projectGroup.projectGroupDesc || '暂无描述'}
|
||||
</Text>
|
||||
</div>
|
||||
<div style={{
|
||||
color: typeInfo.color,
|
||||
fontSize: '24px',
|
||||
opacity: 0.8,
|
||||
marginLeft: '16px'
|
||||
}}>
|
||||
{Icon}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '16px',
|
||||
padding: '20px',
|
||||
background: '#fff',
|
||||
borderRadius: '12px',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.04)',
|
||||
}}>
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Statistic
|
||||
title={<Text type="secondary" style={{fontSize: '13px'}}>环境数量</Text>}
|
||||
value={projectGroup.environments.length}
|
||||
prefix={<EnvironmentOutlined/>}
|
||||
valueStyle={{fontSize: '16px', color: typeInfo.color}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Statistic
|
||||
title={<Text type="secondary" style={{fontSize: '13px'}}>项目个数</Text>}
|
||||
value={projectGroup.applications.length}
|
||||
prefix={<TeamOutlined/>}
|
||||
valueStyle={{fontSize: '16px', color: typeInfo.color}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Statistic
|
||||
title={<Text type="secondary" style={{fontSize: '13px'}}>成员数量</Text>}
|
||||
prefix={<TeamOutlined/>}
|
||||
valueStyle={{fontSize: '16px', color: typeInfo.color}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Divider style={{margin: '8px 0'}}/>
|
||||
|
||||
<Space size="middle" style={{justifyContent: 'center'}}>
|
||||
);
|
||||
},
|
||||
valueEnum: {
|
||||
[ProjectGroupTypeEnum.PRODUCT]: { text: '产品型' },
|
||||
[ProjectGroupTypeEnum.PROJECT]: { text: '项目型' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '环境数量',
|
||||
dataIndex: 'environments',
|
||||
width: 100,
|
||||
render: (_, record) => (
|
||||
<Tooltip title="环境数量">
|
||||
<Space>
|
||||
<EnvironmentOutlined />
|
||||
{record.environments?.length || 0}
|
||||
</Space>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '项目数量',
|
||||
dataIndex: 'applications',
|
||||
width: 100,
|
||||
render: (_, record) => (
|
||||
<Tooltip title="项目数量">
|
||||
<Space>
|
||||
<TeamOutlined />
|
||||
{record.applications?.length || 0}
|
||||
</Space>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sort',
|
||||
width: 80,
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 180,
|
||||
key: 'action',
|
||||
valueType: 'option',
|
||||
fixed: 'right',
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
type="link"
|
||||
icon={<EditOutlined/>}
|
||||
onClick={() => handleEdit(projectGroup)}
|
||||
style={{
|
||||
borderRadius: '6px',
|
||||
background: typeInfo.color,
|
||||
border: 'none'
|
||||
}}
|
||||
onClick={() => handleEdit(record)}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title="确定要删除该项目组吗?"
|
||||
description="删除后将无法恢复,请谨慎操作"
|
||||
onConfirm={() => handleDelete(projectGroup.id)}
|
||||
onConfirm={() => handleDelete(record.id)}
|
||||
>
|
||||
<Button
|
||||
type="link"
|
||||
danger
|
||||
icon={<DeleteOutlined/>}
|
||||
style={{borderRadius: '6px'}}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
<Button
|
||||
icon={<TeamOutlined/>}
|
||||
style={{borderRadius: '6px'}}
|
||||
>
|
||||
成员
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
header={{
|
||||
title: '项目组管理',
|
||||
extra: [
|
||||
<>
|
||||
<ProTable<ProjectGroup>
|
||||
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={() => [
|
||||
<Button
|
||||
key="add"
|
||||
type="primary"
|
||||
icon={<PlusOutlined/>}
|
||||
onClick={handleAdd}
|
||||
style={{
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 2px 0 rgba(0,0,0,0.045)',
|
||||
height: '40px',
|
||||
padding: '0 24px',
|
||||
}}
|
||||
icon={<PlusOutlined/>}
|
||||
>
|
||||
新建项目组
|
||||
</Button>
|
||||
],
|
||||
}}
|
||||
>
|
||||
<div style={{marginBottom: '24px', padding: '0 24px'}}>
|
||||
<Row gutter={16} align="middle">
|
||||
<Col flex="300px">
|
||||
<Search
|
||||
placeholder="搜索项目组名称、编码或描述"
|
||||
allowClear
|
||||
enterButton={<><SearchOutlined/> 搜索</>}
|
||||
size="large"
|
||||
value={searchText}
|
||||
onChange={e => setSearchText(e.target.value)}
|
||||
style={{width: '100%'}}
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<Select
|
||||
placeholder="项目组类型"
|
||||
style={{ width: 200 }}
|
||||
value={projectType}
|
||||
onChange={setProjectType}
|
||||
options={[
|
||||
{ label: '全部类型', value: 'ALL' },
|
||||
{ label: '产品项目组', value: ProjectGroupTypeEnum.PRODUCT },
|
||||
{ label: '项目项目组', value: ProjectGroupTypeEnum.PROJECT }
|
||||
</Button>,
|
||||
]}
|
||||
size="large"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<div style={{padding: '0 24px 24px'}}>
|
||||
<Row gutter={[24, 24]}>
|
||||
{filteredProjects.map(project => (
|
||||
<Col xs={24} sm={12} md={8} lg={6} key={project.id}>
|
||||
<ProjectCard projectGroup={project}/>
|
||||
</Col>
|
||||
))}
|
||||
<Col xs={24} sm={12} md={8} lg={6}>
|
||||
<Card
|
||||
hoverable
|
||||
onClick={handleAdd}
|
||||
style={{
|
||||
height: '100%',
|
||||
borderRadius: '16px',
|
||||
border: '1px dashed #d9d9d9',
|
||||
background: '#fafafa',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minHeight: '380px',
|
||||
transition: 'all 0.3s',
|
||||
}}
|
||||
bodyStyle={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<PlusOutlined style={{fontSize: '28px', color: '#8c8c8c'}}/>
|
||||
<div style={{marginTop: '12px', color: '#8c8c8c', fontSize: '16px'}}>新建项目组</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<ProjectGroupModal
|
||||
visible={modalVisible}
|
||||
onCancel={() => setModalVisible(false)}
|
||||
onSuccess={fetchProjects}
|
||||
onSuccess={() => {
|
||||
setModalVisible(false);
|
||||
actionRef.current?.reload();
|
||||
}}
|
||||
initialValues={currentProject}
|
||||
/>
|
||||
</PageContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectList;
|
||||
export default ProjectGroupList;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user