This commit is contained in:
asp_ly 2024-12-26 23:08:12 +08:00
parent 8f5c05d16f
commit 224a3fe5cc
7 changed files with 625 additions and 550 deletions

View File

@ -1,11 +1,11 @@
import React, { useEffect } from 'react';
import { Modal, Form, Input, InputNumber, Radio, message, Select } from 'antd';
import React, {useState} from 'react';
import {Modal, Form, Input, Select, Switch, InputNumber, App} from 'antd';
import type {Application} from '../types';
import { createApplication, updateApplication } from '../service';
import {DevelopmentLanguageTypeEnum} from '../types';
import {createApplication, updateApplication} from '../service';
interface ApplicationModalProps {
visible: boolean;
open: boolean;
onCancel: () => void;
onSuccess: () => void;
initialValues?: Application;
@ -13,68 +13,58 @@ interface ApplicationModalProps {
}
const ApplicationModal: React.FC<ApplicationModalProps> = ({
visible,
open,
onCancel,
onSuccess,
initialValues,
projectGroupId,
}) => {
const [form] = Form.useForm();
const isEdit = !!initialValues;
useEffect(() => {
if (visible) {
if (initialValues) {
form.setFieldsValue({
...initialValues,
});
} else {
form.setFieldsValue({
projectGroupId,
enabled: true,
sort: 0,
language: DevelopmentLanguageTypeEnum.JAVA,
});
}
}
}, [visible, initialValues, form, projectGroupId]);
const [loading, setLoading] = useState(false);
const {message: messageApi} = App.useApp();
const isEdit = !!initialValues?.id;
const handleSubmit = async () => {
try {
setLoading(true);
const values = await form.validateFields();
const submitData = {
if (isEdit) {
await updateApplication({
...values,
id: initialValues.id,
});
} else {
await createApplication({
...values,
projectGroupId,
};
if (isEdit) {
await updateApplication({ ...submitData, id: initialValues?.id });
message.success('更新成功');
} else {
await createApplication(submitData);
message.success('创建成功');
});
}
messageApi.success(`${isEdit ? '更新' : '创建'}成功`);
form.resetFields();
onSuccess();
onCancel();
form.resetFields();
} catch (error) {
message.error(isEdit ? '更新失败' : '创建失败');
if (error instanceof Error) {
messageApi.error(`${isEdit ? '更新' : '创建'}失败: ${error.message}`);
} else {
messageApi.error(`${isEdit ? '更新' : '创建'}失败`);
}
} finally {
setLoading(false);
}
};
const handleCancel = () => {
form.resetFields();
onCancel();
};
return (
<Modal
title={isEdit ? '编辑应用' : '新建应用'}
open={visible}
title={`${isEdit ? '编辑' : '新建'}应用`}
open={open}
onCancel={() => {
form.resetFields();
onCancel();
}}
onOk={handleSubmit}
onCancel={handleCancel}
confirmLoading={loading}
maskClosable={false}
destroyOnClose
width={600}
>
<Form
form={form}
@ -82,38 +72,55 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
initialValues={{
enabled: true,
sort: 0,
language: DevelopmentLanguageTypeEnum.JAVA,
...initialValues,
}}
>
{/* 隐藏的项目组ID字段 */}
<Form.Item name="projectGroupId" hidden>
<Input />
</Form.Item>
<Form.Item
name="appCode"
label="应用编码"
rules={[
{required: true, message: '请输入应用编码'},
{ pattern: /^[A-Za-z0-9_-]+$/, message: '应用编码只能包含字母、数字、下划线和连字符' }
{max: 50, message: '应用编码不能超过50个字符'},
]}
tooltip="应用的唯一标识,创建后不可修改"
>
<Input placeholder="请输入应用编码" disabled={isEdit} />
<Input
placeholder="请输入应用编码"
disabled={isEdit}
maxLength={50}
/>
</Form.Item>
<Form.Item
name="appName"
label="应用名称"
rules={[{ required: true, message: '请输入应用名称' }]}
rules={[
{required: true, message: '请输入应用名称'},
{max: 50, message: '应用名称不能超过50个字符'},
]}
tooltip="应用的显示名称"
>
<Input placeholder="请输入应用名称" />
<Input
placeholder="请输入应用名称"
maxLength={50}
/>
</Form.Item>
<Form.Item
name="appDesc"
label="应用描述"
name="language"
label="开发语言"
rules={[{required: true, message: '请选择开发语言'}]}
tooltip="应用的主要开发语言,创建后不可修改"
>
<Input.TextArea rows={4} placeholder="请输入应用描述" />
<Select
placeholder="请选择开发语言"
disabled={isEdit}
>
<Select.Option value={DevelopmentLanguageTypeEnum.JAVA}>Java</Select.Option>
<Select.Option value={DevelopmentLanguageTypeEnum.NODE_JS}>NodeJS</Select.Option>
<Select.Option value={DevelopmentLanguageTypeEnum.PYTHON}>Python</Select.Option>
<Select.Option value={DevelopmentLanguageTypeEnum.GO}>Go</Select.Option>
</Select>
</Form.Item>
<Form.Item
@ -123,40 +130,42 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
{required: true, message: '请输入仓库地址'},
{type: 'url', message: '请输入有效的URL地址'},
]}
tooltip="应用代码仓库的URL地址"
>
<Input placeholder="请输入仓库地址" />
<Input
placeholder="请输入仓库地址"
/>
</Form.Item>
<Form.Item
name="language"
label="开发语言"
rules={[{ required: true, message: '请选择开发语言' }]}
name="appDesc"
label="应用描述"
rules={[{max: 200, message: '应用描述不能超过200个字符'}]}
tooltip="应用的详细描述信息"
>
<Select placeholder="请选择开发语言">
<Select.Option value={DevelopmentLanguageTypeEnum.JAVA}>Java</Select.Option>
<Select.Option value={DevelopmentLanguageTypeEnum.NODE_JS}>NodeJS</Select.Option>
<Select.Option value={DevelopmentLanguageTypeEnum.PYTHON}>Python</Select.Option>
<Select.Option value={DevelopmentLanguageTypeEnum.GO}>Go</Select.Option>
</Select>
<Input.TextArea
placeholder="请输入应用描述"
maxLength={200}
showCount
rows={4}
/>
</Form.Item>
<Form.Item
name="enabled"
label="应用状态"
rules={[{ required: true, message: '请选择应用状态' }]}
label="状态"
valuePropName="checked"
tooltip="是否启用该应用"
>
<Radio.Group>
<Radio value={true}></Radio>
<Radio value={false}></Radio>
</Radio.Group>
<Switch checkedChildren="启用" unCheckedChildren="禁用"/>
</Form.Item>
<Form.Item
name="sort"
label="排序"
rules={[{ required: true, message: '请输入排序值' }]}
tooltip="数字越小越靠前"
>
<InputNumber min={0} placeholder="请输入排序值" style={{ width: '100%' }} />
<InputNumber min={0} style={{width: '100%'}}/>
</Form.Item>
</Form>
</Modal>

View File

@ -1,13 +1,11 @@
import React, {useState, useEffect} from 'react';
import {PageContainer} from '@ant-design/pro-layout';
import {Button, message, Popconfirm, Tag, Space, Tooltip, Select} from 'antd';
import {Button, Space, Popconfirm, Tag, Select, App} from 'antd';
import {
PlusOutlined,
EditOutlined,
DeleteOutlined,
CodeOutlined,
CloudUploadOutlined,
ApiOutlined,
GithubOutlined,
JavaOutlined,
NodeIndexOutlined,
@ -32,6 +30,7 @@ const ApplicationList: React.FC = () => {
const [modalVisible, setModalVisible] = useState(false);
const [currentApplication, setCurrentApplication] = useState<Application>();
const actionRef = React.useRef<ActionType>();
const {message: messageApi} = App.useApp();
// 获取项目列表
const fetchProjects = async () => {
@ -42,7 +41,7 @@ const ApplicationList: React.FC = () => {
setSelectedProjectGroupId(data[0].id);
}
} catch (error) {
message.error('获取项目组列表失败');
messageApi.error('获取项目组列表失败');
}
};
@ -53,16 +52,16 @@ const ApplicationList: React.FC = () => {
const handleDelete = async (id: number) => {
try {
await deleteApplication(id);
message.success('删除成功');
messageApi.success('删除成功');
actionRef.current?.reload();
} catch (error) {
message.error('删除失败');
messageApi.error('删除失败');
}
};
const handleAdd = () => {
if (!selectedProjectGroupId) {
message.warning('请先选择项目组');
messageApi.warning('请先选择项目组');
return;
}
setCurrentApplication(undefined);
@ -79,6 +78,17 @@ const ApplicationList: React.FC = () => {
actionRef.current?.reload();
};
const handleModalClose = () => {
setModalVisible(false);
setCurrentApplication(undefined);
};
const handleSuccess = () => {
setModalVisible(false);
setCurrentApplication(undefined);
actionRef.current?.reload();
};
// 获取开发语言信息
const getLanguageInfo = (language: DevelopmentLanguageTypeEnum) => {
switch (language) {
@ -207,19 +217,20 @@ const ApplicationList: React.FC = () => {
},
{
title: '操作',
width: 150,
width: 180,
key: 'action',
valueType: 'option',
fixed: 'right',
align: 'center',
render: (_, record) => [
<Button
key="edit"
type="link"
icon={<EditOutlined/>}
onClick={() => handleEdit(record)}
>
<Space>
<EditOutlined/>
</Space>
</Button>,
<Popconfirm
key="delete"
@ -230,22 +241,68 @@ const ApplicationList: React.FC = () => {
<Button
type="link"
danger
icon={<DeleteOutlined/>}
>
<Space>
<DeleteOutlined/>
</Space>
</Button>
</Popconfirm>,
</Popconfirm>
],
},
];
return (
<>
<PageContainer
header={{
title: '应用管理',
extra: [
<Select
key="project-select"
value={selectedProjectGroupId}
onChange={handleProjectChange}
style={{width: 200}}
placeholder="请选择项目组"
>
{projectGroups.map((project) => (
<Option key={project.id} value={project.id}>
{project.projectGroupName}
</Option>
))}
</Select>,
],
}}
>
<ProTable<Application>
columns={columns}
actionRef={actionRef}
scroll={{x: 'max-content'}}
cardBordered
rowKey="id"
search={false}
options={{
setting: false,
density: false,
fullScreen: false,
reload: false,
}}
toolbar={{
actions: [
<Button
key="add"
type="primary"
onClick={handleAdd}
icon={<PlusOutlined/>}
disabled={!selectedProjectGroupId}
>
</Button>
],
}}
pagination={{
pageSize: 10,
showQuickJumper: true,
}}
request={async (params) => {
if (!selectedProjectGroupId) {
return {
@ -254,9 +311,11 @@ const ApplicationList: React.FC = () => {
total: 0,
};
}
try {
const queryParams: ApplicationQuery = {
pageSize: params.pageSize,
pageNum: params.current,
projectGroupId: selectedProjectGroupId,
appCode: params.appCode as string,
appName: params.appName as string,
enabled: params.enabled as boolean,
@ -267,58 +326,27 @@ const ApplicationList: React.FC = () => {
success: true,
total: data.totalElements || 0,
};
} catch (error) {
messageApi.error('获取应用列表失败');
return {
data: [],
success: false,
total: 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/>}
disabled={!selectedProjectGroupId}
>
</Button>,
]}
/>
{selectedProjectGroupId && (
{modalVisible && selectedProjectGroupId && (
<ApplicationModal
visible={modalVisible}
onCancel={() => setModalVisible(false)}
onSuccess={() => {
setModalVisible(false);
actionRef.current?.reload();
}}
open={modalVisible}
onCancel={handleModalClose}
onSuccess={handleSuccess}
initialValues={currentApplication}
projectGroupId={selectedProjectGroupId}
/>
)}
</>
</PageContainer>
);
};

View File

@ -1,5 +1,5 @@
import React, {useEffect, useState, useCallback} from 'react';
import {Modal, Form, Select, message, Switch, InputNumber} from 'antd';
import {Modal, Form, Select, message, Switch, InputNumber, App} from 'antd';
import type {DeploymentConfig, DeployConfigTemplate} from '../types';
import {createDeploymentConfig, updateDeploymentConfig, getDeployConfigTemplates} from '../service';
import {getApplicationList} from '../../../Application/List/service';
@ -33,6 +33,7 @@ const DeploymentConfigModal: React.FC<DeploymentConfigModalProps> = ({
const [selectedTemplate, setSelectedTemplate] = useState<DeployConfigTemplate>();
const [buildVariables, setBuildVariables] = useState<Record<string, any>>({});
const [loading, setLoading] = useState(false);
const {message: messageApi} = App.useApp();
const isEdit = !!initialValues?.id;
// 获取应用列表
@ -41,9 +42,9 @@ const DeploymentConfigModal: React.FC<DeploymentConfigModalProps> = ({
const data = await getApplicationList();
setApplications(data);
} catch (error) {
message.error('获取应用列表失败');
messageApi.error('获取应用列表失败');
}
}, []);
}, [messageApi]);
// 获取配置模板
const fetchTemplates = useCallback(async () => {
@ -51,9 +52,9 @@ const DeploymentConfigModal: React.FC<DeploymentConfigModalProps> = ({
const data = await getDeployConfigTemplates();
setTemplates(data);
} catch (error) {
message.error('获取配置模板失败');
messageApi.error('获取配置模板失败');
}
}, []);
}, [messageApi]);
// 在模态框显示时获取数据
useEffect(() => {
@ -115,14 +116,14 @@ const DeploymentConfigModal: React.FC<DeploymentConfigModalProps> = ({
} else {
await createDeploymentConfig(submitData);
}
message.success(`${isEdit ? '更新' : '创建'}成功`);
messageApi.success(`${isEdit ? '更新' : '创建'}成功`);
form.resetFields();
onSuccess();
} catch (error) {
if (error instanceof Error) {
message.error(`${isEdit ? '更新' : '创建'}失败: ${error.message}`);
messageApi.error(`${isEdit ? '更新' : '创建'}失败: ${error.message}`);
} else {
message.error(`${isEdit ? '更新' : '创建'}失败`);
messageApi.error(`${isEdit ? '更新' : '创建'}失败`);
}
} finally {
setLoading(false);

View File

@ -1,34 +1,30 @@
import React, {useEffect} from 'react';
import {Modal, Form, Input, InputNumber, Select, message} from 'antd';
import React, {useState} from 'react';
import {Modal, Form, Input, Select, Switch, InputNumber, App} from 'antd';
import type {Environment} from '../types';
import {BuildTypeEnum, DeployTypeEnum} from '../types';
import {createEnvironment, updateEnvironment} from '../service';
import {getBuildTypeInfo, getDeployTypeInfo} from '../utils';
interface EnvironmentModalProps {
visible: boolean;
open: boolean;
onCancel: () => void;
onSuccess: () => void;
initialValues?: Environment;
}
const EnvironmentModal: React.FC<EnvironmentModalProps> = ({
visible,
open,
onCancel,
onSuccess,
initialValues,
}) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const {message: messageApi} = App.useApp();
const isEdit = !!initialValues?.id;
useEffect(() => {
if (visible && initialValues) {
form.setFieldsValue(initialValues);
}
}, [visible, initialValues, form]);
const handleSubmit = async () => {
try {
setLoading(true);
const values = await form.validateFields();
if (isEdit) {
await updateEnvironment({
@ -38,30 +34,40 @@ const EnvironmentModal: React.FC<EnvironmentModalProps> = ({
} else {
await createEnvironment(values);
}
message.success(`${isEdit ? '更新' : '创建'}成功`);
messageApi.success(`${isEdit ? '更新' : '创建'}成功`);
form.resetFields();
onSuccess();
} catch (error) {
message.error(`${isEdit ? '更新' : '创建'}失败`);
if (error instanceof Error) {
messageApi.error(`${isEdit ? '更新' : '创建'}失败: ${error.message}`);
} else {
messageApi.error(`${isEdit ? '更新' : '创建'}失败`);
}
} finally {
setLoading(false);
}
};
return (
<Modal
title={`${isEdit ? '编辑' : '新建'}环境`}
open={visible}
open={open}
onCancel={() => {
form.resetFields();
onCancel();
}}
onOk={handleSubmit}
width={600}
confirmLoading={loading}
maskClosable={false}
destroyOnClose
>
<Form
form={form}
layout="vertical"
initialValues={{
enabled: true,
sort: 0,
...initialValues,
}}
>
<Form.Item
@ -69,10 +75,15 @@ const EnvironmentModal: React.FC<EnvironmentModalProps> = ({
label="环境编码"
rules={[
{required: true, message: '请输入环境编码'},
{max: 50, message: '环境编码长度不能超过50个字符'},
{max: 50, message: '环境编码不能超过50个字符'},
]}
tooltip="环境的唯一标识,创建后不可修改"
>
<Input placeholder="请输入环境编码"/>
<Input
placeholder="请输入环境编码"
disabled={isEdit}
maxLength={50}
/>
</Form.Item>
<Form.Item
@ -80,74 +91,75 @@ const EnvironmentModal: React.FC<EnvironmentModalProps> = ({
label="环境名称"
rules={[
{required: true, message: '请输入环境名称'},
{max: 50, message: '环境名称长度不能超过50个字符'},
{max: 50, message: '环境名称不能超过50个字符'},
]}
tooltip="环境的显示名称"
>
<Input placeholder="请输入环境名称"/>
</Form.Item>
<Form.Item
name="envDesc"
label="环境描述"
rules={[{max: 200, message: '环境描述长度不能超过200个字符'}]}
>
<Input.TextArea rows={4} placeholder="请输入环境描述"/>
<Input
placeholder="请输入环境名称"
maxLength={50}
/>
</Form.Item>
<Form.Item
name="buildType"
label="构建方式"
rules={[{required: true, message: '请选择构建方式'}]}
label="构建类型"
rules={[{required: true, message: '请选择构建类型'}]}
tooltip="环境的构建类型,创建后不可修改"
>
<Select placeholder="请选择构建方式">
{Object.values(BuildTypeEnum).map((type) => {
const typeInfo = getBuildTypeInfo(type);
return (
<Select.Option key={type} value={type}>
<div style={{
display: 'inline-flex',
alignItems: 'center',
gap: '4px',
}}>
<span style={{ color: typeInfo.color }}>{typeInfo.icon}</span>
<span style={{ color: typeInfo.color }}>{typeInfo.label}</span>
</div>
</Select.Option>
);
})}
<Select
placeholder="请选择构建类型"
disabled={isEdit}
>
<Select.Option value={BuildTypeEnum.LOCAL}></Select.Option>
<Select.Option value={BuildTypeEnum.REMOTE}></Select.Option>
</Select>
</Form.Item>
<Form.Item
name="deployType"
label="部署方式"
rules={[{required: true, message: '请选择部署方式'}]}
label="部署类型"
rules={[{required: true, message: '请选择部署类型'}]}
tooltip="环境的部署类型,创建后不可修改"
>
<Select placeholder="请选择部署方式">
{Object.values(DeployTypeEnum).map((type) => {
const typeInfo = getDeployTypeInfo(type);
return (
<Select.Option key={type} value={type}>
<div style={{
display: 'inline-flex',
alignItems: 'center',
gap: '4px',
}}>
<span style={{ color: typeInfo.color }}>{typeInfo.icon}</span>
<span style={{ color: typeInfo.color }}>{typeInfo.label}</span>
</div>
</Select.Option>
);
})}
<Select
placeholder="请选择部署类型"
disabled={isEdit}
>
<Select.Option value={DeployTypeEnum.KUBERNETES}>Kubernetes</Select.Option>
<Select.Option value={DeployTypeEnum.DOCKER}>Docker</Select.Option>
</Select>
</Form.Item>
<Form.Item
name="envDesc"
label="环境描述"
rules={[{max: 200, message: '环境描述不能超过200个字符'}]}
tooltip="环境的详细描述信息"
>
<Input.TextArea
placeholder="请输入环境描述"
maxLength={200}
showCount
rows={4}
/>
</Form.Item>
<Form.Item
name="enabled"
label="状态"
valuePropName="checked"
tooltip="是否启用该环境"
>
<Switch checkedChildren="启用" unCheckedChildren="禁用"/>
</Form.Item>
<Form.Item
name="sort"
label="排序"
rules={[{required: true, message: '请输入排序号'}]}
tooltip="数字越小越靠前"
>
<InputNumber min={0} max={999} style={{width: '100%'}}/>
<InputNumber min={0} style={{width: '100%'}}/>
</Form.Item>
</Form>
</Modal>

View File

@ -1,6 +1,6 @@
import React, {useState} from 'react';
import {PageContainer} from '@ant-design/pro-layout';
import {Button, message, Popconfirm, Tag} from 'antd';
import {Button, Space, Popconfirm, Tag, App} from 'antd';
import {PlusOutlined, EditOutlined, DeleteOutlined} from '@ant-design/icons';
import {getEnvironmentPage, deleteEnvironment} from './service';
import type {Environment, EnvironmentQueryParams} from './types';
@ -14,14 +14,15 @@ const EnvironmentList: React.FC = () => {
const [modalVisible, setModalVisible] = useState(false);
const [currentEnvironment, setCurrentEnvironment] = useState<Environment>();
const actionRef = React.useRef<ActionType>();
const {message: messageApi} = App.useApp();
const handleDelete = async (id: number) => {
try {
await deleteEnvironment(id);
message.success('删除成功');
messageApi.success('删除成功');
actionRef.current?.reload();
} catch (error) {
message.error('删除失败');
messageApi.error('删除失败');
}
};
@ -35,24 +36,16 @@ const EnvironmentList: React.FC = () => {
setModalVisible(true);
};
const renderTag = (icon: React.ReactNode, label: string, color: string) => (
<div style={{
display: 'inline-flex',
alignItems: 'center',
padding: '0 7px',
fontSize: '14px',
lineHeight: '22px',
whiteSpace: 'nowrap',
background: color + '10',
border: `1px solid ${color}`,
borderRadius: '4px',
cursor: 'default',
gap: '4px',
}}>
{icon}
<span style={{ color }}>{label}</span>
</div>
);
const handleModalClose = () => {
setModalVisible(false);
setCurrentEnvironment(undefined);
};
const handleSuccess = () => {
setModalVisible(false);
setCurrentEnvironment(undefined);
actionRef.current?.reload();
};
const columns: ProColumns<Environment>[] = [
{
@ -73,16 +66,21 @@ const EnvironmentList: React.FC = () => {
title: '环境描述',
dataIndex: 'envDesc',
ellipsis: true,
width: '30%',
},
{
title: '构建方式',
title: '构建类型',
dataIndex: 'buildType',
width: 180,
width: 120,
render: (buildType) => {
if (!buildType) return '-';
const typeInfo = getBuildTypeInfo(buildType as BuildTypeEnum);
return renderTag(typeInfo.icon, typeInfo.label, typeInfo.color);
return (
<Tag color={typeInfo.color}>
<Space>
{typeInfo.icon}
{typeInfo.label}
</Space>
</Tag>
);
},
filters: [
{text: 'Jenkins构建', value: BuildTypeEnum.JENKINS},
@ -93,13 +91,19 @@ const EnvironmentList: React.FC = () => {
filtered: false,
},
{
title: '部署方式',
title: '部署类型',
dataIndex: 'deployType',
width: 180,
width: 120,
render: (deployType) => {
if (!deployType) return '-';
const typeInfo = getDeployTypeInfo(deployType as DeployTypeEnum);
return renderTag(typeInfo.icon, typeInfo.label, typeInfo.color);
return (
<Tag color={typeInfo.color}>
<Space>
{typeInfo.icon}
{typeInfo.label}
</Space>
</Tag>
);
},
filters: [
{text: 'Kubernetes集群部署', value: DeployTypeEnum.K8S},
@ -109,11 +113,19 @@ const EnvironmentList: React.FC = () => {
filterMode: 'menu',
filtered: false,
},
{
title: '状态',
dataIndex: 'enabled',
width: 100,
valueEnum: {
true: {text: '启用', status: 'Success'},
false: {text: '禁用', status: 'Default'},
},
},
{
title: '排序',
dataIndex: 'sort',
width: 80,
align: 'center',
sorter: true,
},
{
@ -122,44 +134,73 @@ const EnvironmentList: React.FC = () => {
key: 'action',
valueType: 'option',
fixed: 'right',
align: 'center',
render: (_, record) => (
<div style={{ display: 'flex', gap: '8px', justifyContent: 'center' }}>
render: (_, record) => [
<Button
key="edit"
type="link"
size="small"
style={{ padding: 0 }}
onClick={() => handleEdit(record)}
>
<EditOutlined />
</Button>
<Space>
<EditOutlined/>
</Space>
</Button>,
<Popconfirm
key="delete"
title="确定要删除该环境吗?"
description="删除后将无法恢复,请谨慎操作"
onConfirm={() => handleDelete(record.id)}
>
<Button
type="link"
size="small"
danger
style={{ padding: 0 }}
>
<DeleteOutlined />
<Space>
<DeleteOutlined/>
</Space>
</Button>
</Popconfirm>
</div>
),
],
},
];
return (
<>
<PageContainer>
<ProTable<Environment>
columns={columns}
actionRef={actionRef}
scroll={{x: 'max-content'}}
cardBordered
rowKey="id"
search={false}
options={{
setting: false,
density: false,
fullScreen: false,
reload: false,
}}
toolbar={{
actions: [
<Button
key="add"
type="primary"
onClick={handleAdd}
icon={<PlusOutlined/>}
>
</Button>
],
}}
form={{
syncToUrl: true,
}}
pagination={{
pageSize: 10,
showQuickJumper: true,
}}
request={async (params) => {
try {
const queryParams: EnvironmentQueryParams = {
pageSize: params.pageSize,
pageNum: params.current,
@ -174,54 +215,26 @@ const EnvironmentList: React.FC = () => {
success: true,
total: data.totalElements || 0,
};
} catch (error) {
messageApi.error('获取环境列表失败');
return {
data: [],
success: false,
total: 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>,
]}
/>
{modalVisible && (
<EnvironmentModal
visible={modalVisible}
onCancel={() => setModalVisible(false)}
onSuccess={() => {
setModalVisible(false);
actionRef.current?.reload();
}}
open={modalVisible}
onCancel={handleModalClose}
onSuccess={handleSuccess}
initialValues={currentEnvironment}
/>
</>
)}
</PageContainer>
);
};

View File

@ -1,87 +1,73 @@
import React, {useEffect} from 'react';
import {Modal, Form, Input, InputNumber, Radio, message} from 'antd';
import React, {useState} from 'react';
import {Modal, Form, Input, Select, Switch, InputNumber, App} from 'antd';
import type {ProjectGroup} from '../types';
import {createProjectGroup, updateProjectGroup} from '../service';
import {ProjectGroupTypeEnum} from '../types';
import {createProjectGroup, updateProjectGroup} from '../service';
interface ProjectGroupModalProps {
visible: boolean;
open: boolean;
onCancel: () => void;
onSuccess: () => void;
initialValues?: ProjectGroup;
}
const ProjectGroupModal: React.FC<ProjectGroupModalProps> = ({
visible,
open,
onCancel,
onSuccess,
initialValues,
}) => {
const [form] = Form.useForm();
const isEdit = !!initialValues;
useEffect(() => {
if (visible) {
if (initialValues) {
// 将布尔值转换为字符串
form.setFieldsValue({
...initialValues,
enabled: initialValues.enabled?.toString()
});
} else {
form.setFieldsValue({
type: ProjectGroupTypeEnum.PROJECT,
enabled: 'true',
sort: 0
});
}
}
}, [visible, initialValues, form]);
const [loading, setLoading] = useState(false);
const {message: messageApi} = App.useApp();
const isEdit = !!initialValues?.id;
const handleSubmit = async () => {
try {
setLoading(true);
const values = await form.validateFields();
// 将字符串转换回布尔值
const submitData = {
...values,
enabled: values.enabled === 'true'
};
if (isEdit) {
await updateProjectGroup({...submitData, id: initialValues?.id});
message.success('更新成功');
await updateProjectGroup({
...values,
id: initialValues.id,
});
} else {
await createProjectGroup(submitData);
message.success('创建成功');
await createProjectGroup(values);
}
messageApi.success(`${isEdit ? '更新' : '创建'}成功`);
form.resetFields();
onSuccess();
onCancel();
form.resetFields();
} catch (error) {
message.error(isEdit ? '更新失败' : '创建失败');
if (error instanceof Error) {
messageApi.error(`${isEdit ? '更新' : '创建'}失败: ${error.message}`);
} else {
messageApi.error(`${isEdit ? '更新' : '创建'}失败`);
}
} finally {
setLoading(false);
}
};
const handleCancel = () => {
form.resetFields();
onCancel();
};
return (
<Modal
title={isEdit ? '编辑项目组' : '新建项目组'}
open={visible}
title={`${isEdit ? '编辑' : '新建'}项目组`}
open={open}
onCancel={() => {
form.resetFields();
onCancel();
}}
onOk={handleSubmit}
onCancel={handleCancel}
confirmLoading={loading}
maskClosable={false}
destroyOnClose
>
<Form
form={form}
layout="vertical"
initialValues={{
type: ProjectGroupTypeEnum.PROJECT,
enabled: 'true',
sort: 0
enabled: true,
sort: 0,
...initialValues,
}}
>
<Form.Item
@ -89,55 +75,76 @@ const ProjectGroupModal: React.FC<ProjectGroupModalProps> = ({
label="项目组编码"
rules={[
{required: true, message: '请输入项目组编码'},
{pattern: /^[A-Za-z0-9_-]+$/, message: '项目组编码只能包含字母、数字、下划线和连字符'}
{max: 50, message: '项目组编码不能超过50个字符'},
]}
tooltip="项目组的唯一标识,创建后不可修改"
>
<Input placeholder="请输入项目组编码" disabled={isEdit}/>
<Input
placeholder="请输入项目组编码"
disabled={isEdit}
maxLength={50}
/>
</Form.Item>
<Form.Item
name="projectGroupName"
label="项目组名称"
rules={[{required: true, message: '请输入项目组名称'}]}
rules={[
{required: true, message: '请输入项目组名称'},
{max: 50, message: '项目组名称不能超过50个字符'},
]}
tooltip="项目组的显示名称"
>
<Input placeholder="请输入项目组名称"/>
</Form.Item>
<Form.Item
name="projectGroupDesc"
label="项目组描述"
>
<Input.TextArea rows={4} placeholder="请输入项目组描述"/>
<Input
placeholder="请输入项目组名称"
maxLength={50}
/>
</Form.Item>
<Form.Item
name="type"
label="项目组类型"
rules={[{required: true, message: '请选择项目组类型'}]}
tooltip="项目组的类型,创建后不可修改"
>
<Radio.Group>
<Radio value={ProjectGroupTypeEnum.PRODUCT}></Radio>
<Radio value={ProjectGroupTypeEnum.PROJECT}></Radio>
</Radio.Group>
<Select
placeholder="请选择项目组类型"
disabled={isEdit}
>
<Select.Option value={ProjectGroupTypeEnum.PRODUCT}></Select.Option>
<Select.Option value={ProjectGroupTypeEnum.PROJECT}></Select.Option>
</Select>
</Form.Item>
<Form.Item
name="projectGroupDesc"
label="项目组描述"
rules={[{max: 200, message: '项目组描述不能超过200个字符'}]}
tooltip="项目组的详细描述信息"
>
<Input.TextArea
placeholder="请输入项目组描述"
maxLength={200}
showCount
rows={4}
/>
</Form.Item>
<Form.Item
name="enabled"
label="项目组状态"
rules={[{required: true, message: '请选择项目组状态'}]}
label="状态"
valuePropName="checked"
tooltip="是否启用该项目组"
>
<Radio.Group>
<Radio value="true"></Radio>
<Radio value="false"></Radio>
</Radio.Group>
<Switch checkedChildren="启用" unCheckedChildren="禁用"/>
</Form.Item>
<Form.Item
name="sort"
label="排序"
rules={[{required: true, message: '请输入排序值'}]}
tooltip="数字越小越靠前"
>
<InputNumber min={0} placeholder="请输入排序值" style={{width: '100%'}}/>
<InputNumber min={0} style={{width: '100%'}}/>
</Form.Item>
</Form>
</Modal>

View File

@ -1,6 +1,6 @@
import React, {useState} from 'react';
import {PageContainer} from '@ant-design/pro-layout';
import {Button, message, Popconfirm, Space, Tag, Tooltip} from 'antd';
import {Button, Space, Popconfirm, Tag, App} from 'antd';
import {
PlusOutlined,
EditOutlined,
@ -20,14 +20,15 @@ const ProjectGroupList: React.FC = () => {
const [modalVisible, setModalVisible] = useState(false);
const [currentProject, setCurrentProject] = useState<ProjectGroup>();
const actionRef = React.useRef<ActionType>();
const {message: messageApi} = App.useApp();
const handleDelete = async (id: number) => {
try {
await deleteProjectGroup(id);
message.success('删除成功');
messageApi.success('删除成功');
actionRef.current?.reload();
} catch (error) {
message.error('删除失败');
messageApi.error('删除失败');
}
};
@ -41,6 +42,17 @@ const ProjectGroupList: React.FC = () => {
setModalVisible(true);
};
const handleModalClose = () => {
setModalVisible(false);
setCurrentProject(undefined);
};
const handleSuccess = () => {
setModalVisible(false);
setCurrentProject(undefined);
actionRef.current?.reload();
};
const columns: ProColumns<ProjectGroup>[] = [
{
title: '项目组编码',
@ -97,12 +109,10 @@ const ProjectGroupList: React.FC = () => {
dataIndex: 'environments',
width: 100,
render: (_, record) => (
<Tooltip title="环境数量">
<Space>
<EnvironmentOutlined/>
{record?.totalEnvironments || 0}
</Space>
</Tooltip>
),
},
{
@ -110,12 +120,10 @@ const ProjectGroupList: React.FC = () => {
dataIndex: 'applications',
width: 100,
render: (_, record) => (
<Tooltip title="项目数量">
<Space>
<TeamOutlined/>
{record?.totalApplications || 0}
</Space>
</Tooltip>
),
},
{
@ -126,26 +134,20 @@ const ProjectGroupList: React.FC = () => {
},
{
title: '操作',
width: 280,
width: 180,
key: 'action',
valueType: 'option',
fixed: 'right',
align: 'center',
render: (_, record) => [
<Button
key="edit"
type="link"
icon={<EditOutlined/>}
onClick={() => handleEdit(record)}
>
<Space>
<EditOutlined/>
</Button>,
<Button
key="bind"
type="link"
icon={<EnvironmentOutlined/>}
>
</Space>
</Button>,
<Popconfirm
key="delete"
@ -156,9 +158,11 @@ const ProjectGroupList: React.FC = () => {
<Button
type="link"
danger
icon={<DeleteOutlined/>}
>
<Space>
<DeleteOutlined/>
</Space>
</Button>
</Popconfirm>
],
@ -166,13 +170,42 @@ const ProjectGroupList: React.FC = () => {
];
return (
<>
<PageContainer>
<ProTable<ProjectGroup>
columns={columns}
actionRef={actionRef}
scroll={{x: 'max-content'}}
cardBordered
rowKey="id"
search={false}
options={{
setting: false,
density: false,
fullScreen: false,
reload: false,
}}
toolbar={{
actions: [
<Button
key="add"
type="primary"
onClick={handleAdd}
icon={<PlusOutlined/>}
>
</Button>
],
}}
form={{
syncToUrl: true,
ignoreRules: false,
}}
pagination={{
pageSize: 10,
showQuickJumper: true,
}}
request={async (params) => {
try {
const queryParams: ProjectGroupQueryParams = {
pageSize: params.pageSize,
pageNum: params.current,
@ -186,54 +219,26 @@ const ProjectGroupList: React.FC = () => {
success: true,
total: data.totalElements || 0,
};
} catch (error) {
messageApi.error('获取项目组列表失败');
return {
data: [],
success: false,
total: 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>,
]}
/>
{modalVisible && (
<ProjectGroupModal
visible={modalVisible}
onCancel={() => setModalVisible(false)}
onSuccess={() => {
setModalVisible(false);
actionRef.current?.reload();
}}
open={modalVisible}
onCancel={handleModalClose}
onSuccess={handleSuccess}
initialValues={currentProject}
/>
</>
)}
</PageContainer>
);
};