This commit is contained in:
dengqichen 2024-12-26 13:37:14 +08:00
parent e8cd2575eb
commit 656413d3f7
7 changed files with 324 additions and 182 deletions

View File

@ -1,7 +1,8 @@
import React, { useEffect } from 'react';
import { Modal, Form, Input, InputNumber, Radio, message } from 'antd';
import { Modal, Form, Input, InputNumber, Radio, message, Select } from 'antd';
import type { Application } from '../types';
import { createApplication, updateApplication } from '../service';
import { DevelopmentLanguageTypeEnum } from '../types';
interface ApplicationModalProps {
visible: boolean;
@ -24,12 +25,15 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
useEffect(() => {
if (visible) {
if (initialValues) {
form.setFieldsValue(initialValues);
form.setFieldsValue({
...initialValues,
});
} else {
form.setFieldsValue({
appStatus: 'ENABLE',
form.setFieldsValue({
projectGroupId,
enabled: true,
sort: 0,
projectGroupId: projectGroupId // 设置项目组ID的初始值
language: DevelopmentLanguageTypeEnum.JAVA,
});
}
}
@ -40,7 +44,7 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
const values = await form.validateFields();
const submitData = {
...values,
projectGroupId: projectGroupId // 确保提交时包含项目组ID
projectGroupId,
};
if (isEdit) {
@ -70,13 +74,18 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
onOk={handleSubmit}
onCancel={handleCancel}
destroyOnClose
width={600}
>
<Form
form={form}
layout="vertical"
initialValues={{ appStatus: 'ENABLE', sort: 0 }}
initialValues={{
enabled: true,
sort: 0,
language: DevelopmentLanguageTypeEnum.JAVA,
}}
>
{/* 添加一个隐藏的表单项来存储项目组ID */}
{/* 隐藏的项目组ID字段 */}
<Form.Item name="projectGroupId" hidden>
<Input />
</Form.Item>
@ -108,13 +117,37 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
</Form.Item>
<Form.Item
name="appStatus"
name="repoUrl"
label="仓库地址"
rules={[
{ required: true, message: '请输入仓库地址' },
{ type: 'url', message: '请输入有效的URL地址' },
]}
>
<Input placeholder="请输入仓库地址" />
</Form.Item>
<Form.Item
name="language"
label="开发语言"
rules={[{ required: true, message: '请选择开发语言' }]}
>
<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>
</Form.Item>
<Form.Item
name="enabled"
label="应用状态"
rules={[{ required: true, message: '请选择应用状态' }]}
>
<Radio.Group>
<Radio value="ENABLE"></Radio>
<Radio value="DISABLE"></Radio>
<Radio value={true}></Radio>
<Radio value={false}></Radio>
</Radio.Group>
</Form.Item>

View File

@ -1,11 +1,25 @@
import React, {useState, useEffect} from 'react';
import {PageContainer} from '@ant-design/pro-layout';
import {Button, message, Popconfirm, Tag, Space, Tooltip, Select} from 'antd';
import {PlusOutlined, EditOutlined, DeleteOutlined, CodeOutlined, CloudUploadOutlined, ApiOutlined} from '@ant-design/icons';
import {
PlusOutlined,
EditOutlined,
DeleteOutlined,
CodeOutlined,
CloudUploadOutlined,
ApiOutlined,
GithubOutlined,
JavaOutlined,
NodeIndexOutlined,
PythonOutlined,
} from '@ant-design/icons';
import {getApplicationPage, deleteApplication} from './service';
import {getProjectGroupList} from '../../ProjectGroup/List/service';
import type {Application, ApplicationQuery} from './types';
import {DevelopmentLanguageTypeEnum} from './types';
import type {ProjectGroup} from '../../ProjectGroup/List/types';
import {ProjectGroupTypeEnum} from '../../ProjectGroup/List/types';
import {getProjectTypeInfo} from '../../ProjectGroup/List/utils';
import ApplicationModal from './components/ApplicationModal';
import {ProTable} from '@ant-design/pro-components';
import type {ProColumns, ActionType} from '@ant-design/pro-components';
@ -65,11 +79,40 @@ const ApplicationList: React.FC = () => {
actionRef.current?.reload();
};
// 根据应用编码推测应用类型
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 getLanguageInfo = (language: DevelopmentLanguageTypeEnum) => {
switch (language) {
case DevelopmentLanguageTypeEnum.JAVA:
return {
label: 'Java',
icon: <JavaOutlined/>,
color: '#E76F00'
};
case DevelopmentLanguageTypeEnum.NODE_JS:
return {
label: 'NodeJS',
icon: <NodeIndexOutlined/>,
color: '#339933'
};
case DevelopmentLanguageTypeEnum.PYTHON:
return {
label: 'Python',
icon: <PythonOutlined/>,
color: '#3776AB'
};
case DevelopmentLanguageTypeEnum.GO:
return {
label: 'Go',
icon: <CodeOutlined/>,
color: '#00ADD8'
};
default:
return {
label: language || '未知',
icon: <CodeOutlined/>,
color: '#666666'
};
}
};
const columns: ProColumns<Application>[] = [
@ -80,17 +123,6 @@ const ApplicationList: React.FC = () => {
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: '应用名称',
@ -98,6 +130,22 @@ const ApplicationList: React.FC = () => {
width: 150,
ellipsis: true,
},
{
title: '项目组',
dataIndex: ['projectGroup', 'projectGroupName'],
width: 150,
ellipsis: true,
render: (_, record) => (
<Space>
{record.projectGroup?.projectGroupName}
{record.projectGroup?.type && (
<Tag color={getProjectTypeInfo(record.projectGroup.type).color}>
{getProjectTypeInfo(record.projectGroup.type).label}
</Tag>
)}
</Space>
),
},
{
title: '应用描述',
dataIndex: 'appDesc',
@ -105,12 +153,50 @@ const ApplicationList: React.FC = () => {
width: 200,
},
{
title: '应用状态',
dataIndex: 'appStatus',
title: '仓库地址',
dataIndex: 'repoUrl',
width: 200,
ellipsis: true,
render: (_, record) => record.repoUrl ? (
<Space>
<GithubOutlined/>
<a href={record.repoUrl} target="_blank" rel="noopener noreferrer">
{record.repoUrl}
</a>
</Space>
) : '-',
},
{
title: '开发语言',
dataIndex: 'language',
width: 120,
render: (language) => {
const langInfo = getLanguageInfo(language as DevelopmentLanguageTypeEnum);
return (
<Tag color={langInfo.color}>
<Space>
{langInfo.icon}
{langInfo.label}
</Space>
</Tag>
);
},
filters: [
{text: 'Java', value: DevelopmentLanguageTypeEnum.JAVA},
{text: 'NodeJS', value: DevelopmentLanguageTypeEnum.NODE_JS},
{text: 'Python', value: DevelopmentLanguageTypeEnum.PYTHON},
{text: 'Go', value: DevelopmentLanguageTypeEnum.GO},
],
filterMode: 'menu',
filtered: false,
},
{
title: '状态',
dataIndex: 'enabled',
width: 100,
valueEnum: {
'ENABLE': {text: '启用', status: 'Success'},
'DISABLE': {text: '禁用', status: 'Default'},
true: {text: '启用', status: 'Success'},
false: {text: '禁用', status: 'Default'},
},
},
{
@ -121,7 +207,7 @@ const ApplicationList: React.FC = () => {
},
{
title: '操作',
width: 200,
width: 150,
key: 'action',
valueType: 'option',
fixed: 'right',
@ -149,19 +235,37 @@ const ApplicationList: React.FC = () => {
</Button>
</Popconfirm>,
<Button
key="deploy"
type="link"
icon={<CloudUploadOutlined/>}
>
</Button>
],
},
];
return (
<>
<PageContainer
header={{
title: '应用管理',
subTitle: (
<Space>
<span></span>
<Select
value={selectedProjectGroupId}
onChange={handleProjectChange}
style={{width: 200}}
options={projectGroups.map(project => ({
label: (
<Space>
{project.projectGroupName}
<Tag color={getProjectTypeInfo(project.type).color}>
{getProjectTypeInfo(project.type).label}
</Tag>
</Space>
),
value: project.id,
}))}
/>
</Space>
),
}}
>
<ProTable<Application>
columns={columns}
actionRef={actionRef}
@ -178,10 +282,9 @@ const ApplicationList: React.FC = () => {
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,
enabled: params.enabled as boolean,
};
const data = await getApplicationPage(queryParams);
return {
@ -240,7 +343,7 @@ const ApplicationList: React.FC = () => {
projectGroupId={selectedProjectGroupId}
/>
)}
</>
</PageContainer>
);
};

View File

@ -1,31 +1,43 @@
import type { BaseQuery } from '@/types/base';
import type {BaseQuery} from '@/types/base';
import {ProjectGroup} from "@/pages/Deploy/ProjectGroup/List/types";
export enum DevelopmentLanguageTypeEnum {
JAVA = 'JAVA',
NODE_JS = 'NODE_JS',
PYTHON = 'PYTHON',
GO = 'GO'
}
export interface Application {
id: number;
projectGroupId: number;
appCode: string;
appName: string;
appDesc?: string;
appStatus: 'ENABLE' | 'DISABLE';
sort: number;
id: number;
appCode: string;
appName: string;
appDesc?: string;
repoUrl: string;
language: DevelopmentLanguageTypeEnum;
enabled: boolean;
sort: number;
projectGroup: ProjectGroup;
}
export interface CreateApplicationRequest {
projectGroupId: number;
appCode: string;
appName: string;
appDesc?: string;
appStatus: string;
sort: number;
projectGroupId: number;
appCode: string;
appName: string;
appDesc?: string;
repoUrl: string;
language: DevelopmentLanguageTypeEnum;
enabled: boolean;
sort: number;
}
export interface UpdateApplicationRequest extends CreateApplicationRequest {
id: number;
id: number;
}
export interface ApplicationQuery extends BaseQuery {
projectGroupId?: number;
appCode?: string;
appName?: string;
appStatus?: string;
projectGroupId?: number;
appCode?: string;
appName?: string;
enabled?: boolean;
}

View File

@ -1,11 +1,8 @@
import React, {useEffect, useState} from 'react';
import {Modal, Form, Input, InputNumber, Radio, message, Select} from 'antd';
import React, {useEffect} from 'react';
import {Modal, Form, Input, InputNumber, Radio, message} from 'antd';
import type {ProjectGroup} from '../types';
import type {Environment} from '../../../Environment/List/types';
import {createProjectGroup, updateProjectGroup} from '../service';
import {getEnvironmentList} from '../../../Environment/List/service';
import {BuildTypeEnum} from "../../../Environment/List/types";
import {ProjectGroupTypeEnum} from "../types";
import {ProjectGroupTypeEnum} from '../types';
interface ProjectGroupModalProps {
visible: boolean;
@ -14,57 +11,44 @@ interface ProjectGroupModalProps {
initialValues?: ProjectGroup;
}
const buildProjectGroupTypes = [
{label: '产品', value: ProjectGroupTypeEnum.PRODUCT},
{label: '项目', value: ProjectGroupTypeEnum.PROJECT}
];
const ProjectGroupModal: React.FC<ProjectGroupModalProps> = ({
visible,
onCancel,
onSuccess,
initialValues,
}) => {
visible,
onCancel,
onSuccess,
initialValues,
}) => {
const [form] = Form.useForm();
const [environments, setEnvironments] = useState<Environment[]>([]);
const isEdit = !!initialValues;
// 获取环境列表
useEffect(() => {
const fetchEnvironments = async () => {
try {
const data = await getEnvironmentList();
setEnvironments(data);
} catch (error) {
message.error('获取环境列表失败');
if (visible) {
if (initialValues) {
// 将布尔值转换为字符串
form.setFieldsValue({
...initialValues,
enabled: initialValues.enabled?.toString()
});
} else {
form.setFieldsValue({
type: ProjectGroupTypeEnum.PROJECT,
enabled: 'true',
sort: 0
});
}
};
fetchEnvironments();
}, []);
useEffect(() => {
if (visible && initialValues) {
// 设置初始值包括环境ID列表
const envIds = initialValues.environments?.map(env => env.id) || [];
form.setFieldsValue({
...initialValues,
environments: envIds
});
}
}, [visible, initialValues, form]);
const handleSubmit = async () => {
try {
const values = await form.validateFields();
// 转换环境ID数组为对象数组
const environments = (values.environments || []).map((id: number) => ({id}));
// 将字符串转换回布尔值
const submitData = {
...values,
environments
enabled: values.enabled === 'true'
};
if (isEdit) {
await updateProjectGroup({...submitData, id: initialValues.id});
await updateProjectGroup({...submitData, id: initialValues?.id});
message.success('更新成功');
} else {
await createProjectGroup(submitData);
@ -90,24 +74,16 @@ const ProjectGroupModal: React.FC<ProjectGroupModalProps> = ({
onOk={handleSubmit}
onCancel={handleCancel}
destroyOnClose
width={560}
>
<Form
form={form}
layout="vertical"
initialValues={{projectGroupStatus: 'ENABLE', sort: 0}}
initialValues={{
type: ProjectGroupTypeEnum.PROJECT,
enabled: 'true',
sort: 0
}}
>
<Form.Item
name="type"
label="项目组类型"
rules={[{required: true, message: '请选择项目组类型'}]}
>
<Select
placeholder="请选择项目组类型"
options={buildProjectGroupTypes}
style={{width: '100%'}}
/>
</Form.Item>
<Form.Item
name="projectGroupCode"
label="项目组编码"
@ -133,32 +109,29 @@ const ProjectGroupModal: React.FC<ProjectGroupModalProps> = ({
>
<Input.TextArea rows={4} placeholder="请输入项目组描述"/>
</Form.Item>
<Form.Item
name="environments"
label="关联环境"
rules={[{required: true, message: '请选择关联环境'}]}
>
<Select
mode="multiple"
placeholder="请选择关联环境"
optionFilterProp="label"
options={environments.map(env => ({
label: env.envName,
value: env.id,
}))}
/>
</Form.Item>
<Form.Item
name="projectGroupStatus"
label="项目组状态"
rules={[{required: true, message: '请选择项目组状态'}]}
initialValue="ENABLE"
name="type"
label="项目组类型"
rules={[{required: true, message: '请选择项目组类型'}]}
>
<Radio.Group>
<Radio value="ENABLE"></Radio>
<Radio value="DISABLE"></Radio>
<Radio value={ProjectGroupTypeEnum.PRODUCT}></Radio>
<Radio value={ProjectGroupTypeEnum.PROJECT}></Radio>
</Radio.Group>
</Form.Item>
<Form.Item
name="enabled"
label="项目组状态"
rules={[{required: true, message: '请选择项目组状态'}]}
>
<Radio.Group>
<Radio value="true"></Radio>
<Radio value="false"></Radio>
</Radio.Group>
</Form.Item>
<Form.Item
name="sort"
label="排序"

View File

@ -7,14 +7,14 @@ import {
DeleteOutlined,
TeamOutlined,
EnvironmentOutlined,
RocketOutlined
} from '@ant-design/icons';
import {getProjectGroupPage, deleteProjectGroup} from './service';
import type {ProjectGroup, ProjectGroupQueryParams} from './types';
import {ProjectGroupTypeEnum} from './types';
import {getProjectTypeInfo} from './utils';
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';
import {ProTable} from '@ant-design/pro-components';
import type {ProColumns, ActionType} from '@ant-design/pro-components';
const ProjectGroupList: React.FC = () => {
const [modalVisible, setModalVisible] = useState(false);
@ -41,35 +41,6 @@ const ProjectGroupList: React.FC = () => {
setModalVisible(true);
};
const getProjectTypeInfo = (type: ProjectGroupTypeEnum) => {
switch (type) {
case ProjectGroupTypeEnum.PRODUCT:
return {
type: ProjectGroupTypeEnum.PRODUCT,
label: '产品型',
color: '#1890ff',
icon: <RocketOutlined/>,
description: '产品型相关的项目组'
};
case ProjectGroupTypeEnum.PROJECT:
return {
type: ProjectGroupTypeEnum.PROJECT,
label: '项目型',
color: '#52c41a',
icon: <EnvironmentOutlined/>,
description: '项目型相关的项目组'
};
default:
return {
type: ProjectGroupTypeEnum.PROJECT,
label: '项目型',
color: '#722ed1',
icon: <TeamOutlined/>,
description: '项目型相关的项目组'
};
}
};
const columns: ProColumns<ProjectGroup>[] = [
{
title: '项目组编码',
@ -93,18 +64,32 @@ const ProjectGroupList: React.FC = () => {
{
title: '项目组类型',
dataIndex: 'type',
width: 100,
width: 150,
render: (type) => {
const typeInfo = getProjectTypeInfo(type as ProjectGroupTypeEnum);
return (
<Tag color={typeInfo.color}>
{typeInfo.label}
<Space>
{typeInfo.icon}
{typeInfo.label}
</Space>
</Tag>
);
},
filters: [
{text: '产品型', value: ProjectGroupTypeEnum.PRODUCT},
{text: '项目型', value: ProjectGroupTypeEnum.PROJECT},
],
filterMode: 'menu',
filtered: false,
},
{
title: '状态',
dataIndex: 'enabled',
width: 100,
valueEnum: {
[ProjectGroupTypeEnum.PRODUCT]: { text: '产品型' },
[ProjectGroupTypeEnum.PROJECT]: { text: '项目型' },
true: {text: '启用', status: 'Success'},
false: {text: '禁用', status: 'Default'},
},
},
{
@ -114,7 +99,7 @@ const ProjectGroupList: React.FC = () => {
render: (_, record) => (
<Tooltip title="环境数量">
<Space>
<EnvironmentOutlined />
<EnvironmentOutlined/>
{record.environments?.length || 0}
</Space>
</Tooltip>
@ -127,7 +112,7 @@ const ProjectGroupList: React.FC = () => {
render: (_, record) => (
<Tooltip title="项目数量">
<Space>
<TeamOutlined />
<TeamOutlined/>
{record.applications?.length || 0}
</Space>
</Tooltip>
@ -145,6 +130,7 @@ const ProjectGroupList: React.FC = () => {
key: 'action',
valueType: 'option',
fixed: 'right',
align: 'center',
render: (_, record) => (
<Space>
<Button
@ -184,7 +170,7 @@ const ProjectGroupList: React.FC = () => {
pageNum: params.current,
projectGroupName: params.projectGroupName as string,
projectGroupCode: params.projectGroupCode as string,
projectGroupStatus: params.projectGroupStatus as string,
enabled: params.enabled as boolean,
};
const data = await getProjectGroupPage(queryParams);
return {

View File

@ -2,13 +2,11 @@ import {BaseResponse, BaseRequest, BaseQuery} from '@/types/base';
import {Environment} from '../../Environment/List/types';
import {Application} from "@/pages/Deploy/Application/List/types";
export enum ProjectGroupTypeEnum {
PRODUCT = 'PRODUCT',
PROJECT = 'PROJECT'
}
// 项目基础信息
export interface ProjectGroup extends BaseResponse {
tenantCode: string;
@ -16,7 +14,7 @@ export interface ProjectGroup extends BaseResponse {
projectGroupCode: string;
projectGroupName: string;
projectGroupDesc?: string;
projectGroupStatus: 'ENABLE' | 'DISABLE';
enabled: boolean;
environments: Environment[];
applications: Application[];
sort: number;
@ -29,7 +27,7 @@ export interface CreateProjectGroupRequest extends BaseRequest {
projectGroupCode: string;
projectGroupName: string;
projectGroupDesc?: string;
projectGroupStatus: string;
enabled: boolean;
sort: number;
}
@ -42,5 +40,5 @@ export interface UpdateProjectGroupRequest extends CreateProjectGroupRequest {
export interface ProjectGroupQueryParams extends BaseQuery {
projectGroupName?: string;
projectGroupCode?: string;
projectGroupStatus?: string;
enabled?: boolean;
}

View File

@ -0,0 +1,37 @@
import React from 'react';
import {ProjectOutlined, RocketOutlined} from '@ant-design/icons';
import {ProjectGroupTypeEnum} from './types';
interface ProjectTypeInfo {
type: ProjectGroupTypeEnum;
label: string;
color: string;
icon: React.ReactNode;
}
// 获取项目组类型信息
export const getProjectTypeInfo = (type: ProjectGroupTypeEnum): ProjectTypeInfo => {
switch (type) {
case ProjectGroupTypeEnum.PRODUCT:
return {
type: ProjectGroupTypeEnum.PRODUCT,
label: '产品型',
color: '#1890ff',
icon: <RocketOutlined/>,
};
case ProjectGroupTypeEnum.PROJECT:
return {
type: ProjectGroupTypeEnum.PROJECT,
label: '项目型',
color: '#52c41a',
icon: <ProjectOutlined/>,
};
default:
return {
type: type,
label: type || '未知',
color: '#666666',
icon: <ProjectOutlined/>,
};
}
};