1
This commit is contained in:
parent
51aa8b6994
commit
09a6a020ce
@ -1,324 +1,247 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, {useState, useEffect} from 'react';
|
||||||
import { PageContainer } from '@ant-design/pro-layout';
|
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 {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 {getProjectGroupList} from '../../ProjectGroup/List/service';
|
||||||
import type { Application } from './types';
|
import type {Application, ApplicationQuery} from './types';
|
||||||
import type { ProjectGroup } from '../../ProjectGroup/List/types';
|
import type {ProjectGroup} from '../../ProjectGroup/List/types';
|
||||||
import ApplicationModal from './components/ApplicationModal';
|
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 ApplicationList: React.FC = () => {
|
||||||
const [applications, setApplications] = useState<Application[]>([]);
|
const [projectGroups, setProjects] = useState<ProjectGroup[]>([]);
|
||||||
const [projectGroups, setProjects] = useState<ProjectGroup[]>([]);
|
const [selectedProjectGroupId, setSelectedProjectGroupId] = useState<number>();
|
||||||
const [selectedProjectGroupId, setSelectedProjectGroupId] = useState<number>();
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [currentApplication, setCurrentApplication] = useState<Application>();
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
const actionRef = React.useRef<ActionType>();
|
||||||
const [currentApplication, setCurrentApplication] = useState<Application>();
|
|
||||||
|
|
||||||
// 获取项目列表
|
// 获取项目列表
|
||||||
const fetchProjects = async () => {
|
const fetchProjects = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await getProjectGroupList();
|
const data = await getProjectGroupList();
|
||||||
setProjects(data);
|
setProjects(data);
|
||||||
if (data.length > 0 && !selectedProjectGroupId) {
|
if (data.length > 0 && !selectedProjectGroupId) {
|
||||||
setSelectedProjectGroupId(data[0].id);
|
setSelectedProjectGroupId(data[0].id);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('获取项目组列表失败');
|
message.error('获取项目组列表失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取应用列表
|
useEffect(() => {
|
||||||
const fetchApplications = async () => {
|
fetchProjects();
|
||||||
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(() => {
|
const handleDelete = async (id: number) => {
|
||||||
fetchProjects();
|
try {
|
||||||
}, []);
|
await deleteApplication(id);
|
||||||
|
message.success('删除成功');
|
||||||
|
actionRef.current?.reload();
|
||||||
|
} catch (error) {
|
||||||
|
message.error('删除失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleAdd = () => {
|
||||||
fetchApplications();
|
if (!selectedProjectGroupId) {
|
||||||
}, [selectedProjectGroupId]);
|
message.warning('请先选择项目组');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setCurrentApplication(undefined);
|
||||||
|
setModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
const handleDelete = async (id: number) => {
|
const handleEdit = (application: Application) => {
|
||||||
try {
|
setCurrentApplication(application);
|
||||||
await deleteApplication(id);
|
setModalVisible(true);
|
||||||
message.success('删除成功');
|
};
|
||||||
fetchApplications();
|
|
||||||
} catch (error) {
|
|
||||||
message.error('删除失败');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleProjectChange = (value: number) => {
|
||||||
setCurrentApplication(undefined);
|
setSelectedProjectGroupId(value);
|
||||||
setModalVisible(true);
|
actionRef.current?.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (application: Application) => {
|
// 根据应用编码推测应用类型
|
||||||
setCurrentApplication(application);
|
const getAppType = (appCode: string) => {
|
||||||
setModalVisible(true);
|
if (appCode.toLowerCase().includes('web')) return {icon: <CodeOutlined/>, name: '前端应用', color: '#1890ff'};
|
||||||
};
|
if (appCode.toLowerCase().includes('api')) return {icon: <ApiOutlined/>, name: 'API服务', color: '#52c41a'};
|
||||||
|
return {icon: <CloudUploadOutlined/>, name: '后端服务', color: '#722ed1'};
|
||||||
|
};
|
||||||
|
|
||||||
const handleProjectChange = (value: number) => {
|
const columns: ProColumns<Application>[] = [
|
||||||
setSelectedProjectGroupId(value);
|
{
|
||||||
};
|
title: '应用编码',
|
||||||
|
dataIndex: 'appCode',
|
||||||
|
width: 180,
|
||||||
|
copyable: true,
|
||||||
|
ellipsis: true,
|
||||||
|
fixed: 'left',
|
||||||
|
render: (text, record) => {
|
||||||
|
const appType = getAppType(record.appCode);
|
||||||
|
return (
|
||||||
|
<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
|
||||||
|
key="edit"
|
||||||
|
type="link"
|
||||||
|
icon={<EditOutlined/>}
|
||||||
|
onClick={() => handleEdit(record)}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>,
|
||||||
|
<Popconfirm
|
||||||
|
key="delete"
|
||||||
|
title="确定要删除该应用吗?"
|
||||||
|
description="删除后将无法恢复,请谨慎操作"
|
||||||
|
onConfirm={() => handleDelete(record.id)}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
danger
|
||||||
|
icon={<DeleteOutlined/>}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>,
|
||||||
|
<Button
|
||||||
|
key="deploy"
|
||||||
|
type="link"
|
||||||
|
icon={<CloudUploadOutlined/>}
|
||||||
|
>
|
||||||
|
部署测试
|
||||||
|
</Button>
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// 根据应用编码推测应用类型
|
|
||||||
const getAppType = (appCode: string) => {
|
|
||||||
if (appCode.toLowerCase().includes('web')) return { icon: <CodeOutlined />, name: '前端应用', color: '#1890ff' };
|
|
||||||
if (appCode.toLowerCase().includes('api')) return { icon: <ApiOutlined />, name: 'API服务', color: '#52c41a' };
|
|
||||||
return { icon: <CloudUploadOutlined />, name: '后端服务', color: '#722ed1' };
|
|
||||||
};
|
|
||||||
|
|
||||||
const ApplicationCard = ({ application }: { application: Application }) => {
|
|
||||||
const appType = getAppType(application.appCode);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<>
|
||||||
hoverable
|
<ProTable<Application>
|
||||||
className="application-card"
|
columns={columns}
|
||||||
style={{
|
actionRef={actionRef}
|
||||||
height: '100%',
|
scroll={{x: 1300}}
|
||||||
borderRadius: '12px',
|
cardBordered
|
||||||
overflow: 'hidden',
|
request={async (params) => {
|
||||||
border: '1px solid #f0f0f0',
|
if (!selectedProjectGroupId) {
|
||||||
transition: 'all 0.3s',
|
return {
|
||||||
}}
|
data: [],
|
||||||
bodyStyle={{
|
success: true,
|
||||||
padding: '24px',
|
total: 0,
|
||||||
background: `linear-gradient(145deg, #ffffff 0%, ${appType.color}0A 100%)`,
|
};
|
||||||
}}
|
}
|
||||||
actions={[
|
const queryParams: ApplicationQuery = {
|
||||||
<Button
|
pageSize: params.pageSize,
|
||||||
type="text"
|
pageNum: params.current,
|
||||||
key="edit"
|
projectGroupId: selectedProjectGroupId,
|
||||||
icon={<EditOutlined style={{ color: appType.color }} />}
|
appCode: params.appCode as string,
|
||||||
onClick={() => handleEdit(application)}
|
appName: params.appName as string,
|
||||||
>
|
appStatus: params.appStatus as string,
|
||||||
编辑
|
};
|
||||||
</Button>,
|
const data = await getApplicationPage(queryParams);
|
||||||
<Popconfirm
|
return {
|
||||||
key="delete"
|
data: data.content || [],
|
||||||
title="确定要删除该应用吗?"
|
success: true,
|
||||||
onConfirm={() => handleDelete(application.id)}
|
total: data.totalElements || 0,
|
||||||
>
|
};
|
||||||
<Button
|
}}
|
||||||
type="text"
|
rowKey="id"
|
||||||
danger
|
search={{
|
||||||
icon={<DeleteOutlined />}
|
labelWidth: 'auto',
|
||||||
>
|
span: {
|
||||||
删除
|
xs: 24,
|
||||||
</Button>
|
sm: 12,
|
||||||
</Popconfirm>,
|
md: 8,
|
||||||
]}
|
lg: 6,
|
||||||
>
|
xl: 6,
|
||||||
<div style={{ position: 'relative' }}>
|
xxl: 6,
|
||||||
<div style={{
|
},
|
||||||
position: 'absolute',
|
}}
|
||||||
top: '-12px',
|
options={{
|
||||||
right: '-12px',
|
setting: {
|
||||||
padding: '4px 8px',
|
listsHeight: 400,
|
||||||
background: appType.color,
|
},
|
||||||
color: '#fff',
|
}}
|
||||||
borderRadius: '0 0 0 8px',
|
form={{
|
||||||
fontSize: '12px',
|
syncToUrl: true,
|
||||||
display: 'flex',
|
}}
|
||||||
alignItems: 'center',
|
pagination={{
|
||||||
gap: '4px'
|
pageSize: 10,
|
||||||
}}>
|
showQuickJumper: true,
|
||||||
{appType.icon}
|
}}
|
||||||
{appType.name}
|
dateFormatter="string"
|
||||||
</div>
|
toolBarRender={() => [
|
||||||
|
<Button
|
||||||
<Title level={4} style={{
|
key="add"
|
||||||
marginBottom: '16px',
|
type="primary"
|
||||||
color: '#1f1f1f',
|
onClick={handleAdd}
|
||||||
display: 'flex',
|
icon={<PlusOutlined/>}
|
||||||
alignItems: 'center',
|
disabled={!selectedProjectGroupId}
|
||||||
gap: '8px'
|
>
|
||||||
}}>
|
新建应用
|
||||||
{application.appName}
|
</Button>,
|
||||||
<Tag
|
]}
|
||||||
color={application.appStatus === 'ENABLE' ? '#52c41a' : '#d9d9d9'}
|
/>
|
||||||
style={{
|
|
||||||
borderRadius: '4px',
|
|
||||||
fontSize: '12px',
|
|
||||||
padding: '0 8px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{application.appStatus === 'ENABLE' ? '启用' : '禁用'}
|
|
||||||
</Tag>
|
|
||||||
</Title>
|
|
||||||
|
|
||||||
<Text
|
{selectedProjectGroupId && (
|
||||||
type="secondary"
|
<ApplicationModal
|
||||||
style={{
|
visible={modalVisible}
|
||||||
display: 'block',
|
onCancel={() => setModalVisible(false)}
|
||||||
marginBottom: '20px',
|
onSuccess={() => {
|
||||||
height: '40px',
|
setModalVisible(false);
|
||||||
overflow: 'hidden',
|
actionRef.current?.reload();
|
||||||
textOverflow: 'ellipsis',
|
}}
|
||||||
WebkitLineClamp: 2,
|
initialValues={currentApplication}
|
||||||
WebkitBoxOrient: 'vertical',
|
projectGroupId={selectedProjectGroupId}
|
||||||
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);
|
|
||||||
|
|
||||||
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: [
|
|
||||||
<Button
|
|
||||||
key="add"
|
|
||||||
type="primary"
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
onClick={handleAdd}
|
|
||||||
style={{
|
|
||||||
borderRadius: '6px',
|
|
||||||
boxShadow: '0 2px 0 rgba(0,0,0,0.045)',
|
|
||||||
}}
|
|
||||||
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>
|
|
||||||
|
|
||||||
<ApplicationModal
|
|
||||||
visible={modalVisible}
|
|
||||||
onCancel={() => setModalVisible(false)}
|
|
||||||
onSuccess={fetchApplications}
|
|
||||||
initialValues={currentApplication}
|
|
||||||
projectGroupId={selectedProjectGroupId!}
|
|
||||||
/>
|
|
||||||
</PageContainer>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ApplicationList;
|
export default ApplicationList;
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import {PlusOutlined, DeleteOutlined, EditOutlined, SearchOutlined, ReloadOutlin
|
|||||||
import {getEnvironmentList, deleteEnvironment, getEnvironmentPage} from './service';
|
import {getEnvironmentList, deleteEnvironment, getEnvironmentPage} from './service';
|
||||||
import type {Environment} from './types';
|
import type {Environment} from './types';
|
||||||
import EnvironmentModal from './components/EnvironmentModal';
|
import EnvironmentModal from './components/EnvironmentModal';
|
||||||
import { ProTable } from '@ant-design/pro-components';
|
import {ProTable} from '@ant-design/pro-components';
|
||||||
import type { ProColumns } from '@ant-design/pro-components';
|
import type {ProColumns} from '@ant-design/pro-components';
|
||||||
|
|
||||||
const EnvironmentList: React.FC = () => {
|
const EnvironmentList: React.FC = () => {
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
@ -104,71 +104,70 @@ const EnvironmentList: React.FC = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<ProTable<Environment>
|
||||||
|
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={() => [
|
||||||
|
<Button
|
||||||
|
key="add"
|
||||||
|
type="primary"
|
||||||
|
onClick={handleAdd}
|
||||||
|
icon={<PlusOutlined/>}
|
||||||
|
>
|
||||||
|
新建环境
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
<>
|
<EnvironmentModal
|
||||||
<ProTable<Environment>
|
visible={modalVisible}
|
||||||
columns={columns}
|
onCancel={() => setModalVisible(false)}
|
||||||
actionRef={actionRef}
|
onSuccess={() => {
|
||||||
cardBordered
|
setModalVisible(false);
|
||||||
request={async (params) => {
|
actionRef.current?.reload();
|
||||||
const { current, pageSize } = params;
|
}}
|
||||||
const data = await getEnvironmentPage({
|
initialValues={currentEnvironment}
|
||||||
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={() => [
|
|
||||||
<Button
|
|
||||||
key="add"
|
|
||||||
type="primary"
|
|
||||||
onClick={handleAdd}
|
|
||||||
icon={<PlusOutlined/>}
|
|
||||||
>
|
|
||||||
新建环境
|
|
||||||
</Button>,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<EnvironmentModal
|
|
||||||
visible={modalVisible}
|
|
||||||
onCancel={() => setModalVisible(false)}
|
|
||||||
onSuccess={() => {
|
|
||||||
setModalVisible(false);
|
|
||||||
actionRef.current?.reload();
|
|
||||||
}}
|
|
||||||
initialValues={currentEnvironment}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,52 +1,31 @@
|
|||||||
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, Popconfirm, Space, Tag, Input, Select, Tooltip, Statistic, Divider} from 'antd';
|
import {Button, message, Popconfirm, Space, Tag, Tooltip} from 'antd';
|
||||||
import {
|
import {
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
TeamOutlined,
|
TeamOutlined,
|
||||||
SearchOutlined,
|
|
||||||
EnvironmentOutlined,
|
EnvironmentOutlined,
|
||||||
RocketOutlined
|
RocketOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import {getProjectGroupList, deleteProjectGroup} from './service';
|
import {getProjectGroupPage, deleteProjectGroup} from './service';
|
||||||
import type {ProjectGroup} from './types';
|
import type {ProjectGroup, ProjectGroupQueryParams} from './types';
|
||||||
import ProjectGroupModal from './components/ProjectGroupModal';
|
import ProjectGroupModal from './components/ProjectGroupModal';
|
||||||
import { ProjectGroupTypeEnum } from './types';
|
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 ProjectGroupList: React.FC = () => {
|
||||||
const {Search} = Input;
|
|
||||||
|
|
||||||
const ProjectList: React.FC = () => {
|
|
||||||
const [projects, setProjects] = useState<ProjectGroup[]>([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
const [currentProject, setCurrentProject] = useState<ProjectGroup>();
|
const [currentProject, setCurrentProject] = useState<ProjectGroup>();
|
||||||
const [searchText, setSearchText] = useState('');
|
const actionRef = React.useRef<ActionType>();
|
||||||
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 handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
await deleteProjectGroup(id);
|
await deleteProjectGroup(id);
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
fetchProjects();
|
actionRef.current?.reload();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('删除失败');
|
message.error('删除失败');
|
||||||
}
|
}
|
||||||
@ -63,305 +42,205 @@ const ProjectList: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getProjectTypeInfo = (type: ProjectGroupTypeEnum) => {
|
const getProjectTypeInfo = (type: ProjectGroupTypeEnum) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ProjectGroupTypeEnum.PRODUCT:
|
case ProjectGroupTypeEnum.PRODUCT:
|
||||||
return {
|
return {
|
||||||
type: ProjectGroupTypeEnum.PRODUCT,
|
type: ProjectGroupTypeEnum.PRODUCT,
|
||||||
label: '产品型',
|
label: '产品型',
|
||||||
color: '#1890ff',
|
color: '#1890ff',
|
||||||
icon: <RocketOutlined/>,
|
icon: <RocketOutlined/>,
|
||||||
description: '产品型相关的项目组'
|
description: '产品型相关的项目组'
|
||||||
};
|
};
|
||||||
case ProjectGroupTypeEnum.PROJECT:
|
case ProjectGroupTypeEnum.PROJECT:
|
||||||
return {
|
return {
|
||||||
type: ProjectGroupTypeEnum.PROJECT,
|
type: ProjectGroupTypeEnum.PROJECT,
|
||||||
label: '项目型',
|
label: '项目型',
|
||||||
color: '#52c41a',
|
color: '#52c41a',
|
||||||
icon: <EnvironmentOutlined/>,
|
icon: <EnvironmentOutlined/>,
|
||||||
description: '项目型相关的项目组'
|
description: '项目型相关的项目组'
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
type: ProjectGroupTypeEnum.PROJECT,
|
type: ProjectGroupTypeEnum.PROJECT,
|
||||||
label: '项目型',
|
label: '项目型',
|
||||||
color: '#722ed1',
|
color: '#722ed1',
|
||||||
icon: <TeamOutlined/>,
|
icon: <TeamOutlined/>,
|
||||||
description: '项目型相关的项目组'
|
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 (
|
|
||||||
<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)',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
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'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{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'}}>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
icon={<EditOutlined/>}
|
|
||||||
onClick={() => handleEdit(projectGroup)}
|
|
||||||
style={{
|
|
||||||
borderRadius: '6px',
|
|
||||||
background: typeInfo.color,
|
|
||||||
border: 'none'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
编辑
|
|
||||||
</Button>
|
|
||||||
<Popconfirm
|
|
||||||
title="确定要删除该项目组吗?"
|
|
||||||
description="删除后将无法恢复,请谨慎操作"
|
|
||||||
onConfirm={() => handleDelete(projectGroup.id)}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
danger
|
|
||||||
icon={<DeleteOutlined/>}
|
|
||||||
style={{borderRadius: '6px'}}
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>
|
|
||||||
<Button
|
|
||||||
icon={<TeamOutlined/>}
|
|
||||||
style={{borderRadius: '6px'}}
|
|
||||||
>
|
|
||||||
成员
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const columns: ProColumns<ProjectGroup>[] = [
|
||||||
|
{
|
||||||
|
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 (
|
||||||
|
<Tag color={typeInfo.color}>
|
||||||
|
{typeInfo.label}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
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="link"
|
||||||
|
icon={<EditOutlined/>}
|
||||||
|
onClick={() => handleEdit(record)}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Popconfirm
|
||||||
|
title="确定要删除该项目组吗?"
|
||||||
|
description="删除后将无法恢复,请谨慎操作"
|
||||||
|
onConfirm={() => handleDelete(record.id)}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
danger
|
||||||
|
icon={<DeleteOutlined/>}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer
|
<>
|
||||||
header={{
|
<ProTable<ProjectGroup>
|
||||||
title: '项目组管理',
|
columns={columns}
|
||||||
extra: [
|
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
|
<Button
|
||||||
key="add"
|
key="add"
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<PlusOutlined/>}
|
|
||||||
onClick={handleAdd}
|
onClick={handleAdd}
|
||||||
style={{
|
icon={<PlusOutlined/>}
|
||||||
borderRadius: '8px',
|
|
||||||
boxShadow: '0 2px 0 rgba(0,0,0,0.045)',
|
|
||||||
height: '40px',
|
|
||||||
padding: '0 24px',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
新建项目组
|
新建项目组
|
||||||
</Button>
|
</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 }
|
|
||||||
]}
|
|
||||||
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
|
<ProjectGroupModal
|
||||||
visible={modalVisible}
|
visible={modalVisible}
|
||||||
onCancel={() => setModalVisible(false)}
|
onCancel={() => setModalVisible(false)}
|
||||||
onSuccess={fetchProjects}
|
onSuccess={() => {
|
||||||
|
setModalVisible(false);
|
||||||
|
actionRef.current?.reload();
|
||||||
|
}}
|
||||||
initialValues={currentProject}
|
initialValues={currentProject}
|
||||||
/>
|
/>
|
||||||
</PageContainer>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProjectList;
|
export default ProjectGroupList;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user