1
This commit is contained in:
parent
66a59ad039
commit
48f8459331
@ -0,0 +1,123 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { Modal, Form, Input, InputNumber, Radio, message } from 'antd';
|
||||||
|
import type { Application } from '../types';
|
||||||
|
import { createApplication, updateApplication } from '../service';
|
||||||
|
|
||||||
|
interface ApplicationModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
onSuccess: () => void;
|
||||||
|
initialValues?: Application;
|
||||||
|
projectId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ApplicationModal: React.FC<ApplicationModalProps> = ({
|
||||||
|
visible,
|
||||||
|
onCancel,
|
||||||
|
onSuccess,
|
||||||
|
initialValues,
|
||||||
|
projectId,
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const isEdit = !!initialValues;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
if (initialValues) {
|
||||||
|
form.setFieldsValue(initialValues);
|
||||||
|
} else {
|
||||||
|
form.setFieldsValue({ projectId, appStatus: 'ENABLE', sort: 0 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [visible, initialValues, form, projectId]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
if (isEdit) {
|
||||||
|
await updateApplication({ ...values, id: initialValues.id });
|
||||||
|
message.success('更新成功');
|
||||||
|
} else {
|
||||||
|
await createApplication(values);
|
||||||
|
message.success('创建成功');
|
||||||
|
}
|
||||||
|
onSuccess();
|
||||||
|
onCancel();
|
||||||
|
form.resetFields();
|
||||||
|
} catch (error) {
|
||||||
|
message.error(isEdit ? '更新失败' : '创建失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
form.resetFields();
|
||||||
|
onCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={isEdit ? '编辑应用' : '新建应用'}
|
||||||
|
open={visible}
|
||||||
|
onOk={handleSubmit}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
initialValues={{ appStatus: 'ENABLE', sort: 0 }}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="appCode"
|
||||||
|
label="应用编码"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: '请输入应用编码' },
|
||||||
|
{ pattern: /^[A-Za-z0-9_-]+$/, message: '应用编码只能包含字母、数字、下划线和连字符' }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入应用编码" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="appName"
|
||||||
|
label="应用名称"
|
||||||
|
rules={[{ required: true, message: '请输入应用名称' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入应用名称" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="appDesc"
|
||||||
|
label="应用描述"
|
||||||
|
>
|
||||||
|
<Input.TextArea rows={4} placeholder="请输入应用描述" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="appStatus"
|
||||||
|
label="应用状态"
|
||||||
|
rules={[{ required: true, message: '请选择应用状态' }]}
|
||||||
|
>
|
||||||
|
<Radio.Group>
|
||||||
|
<Radio value="ENABLE">启用</Radio>
|
||||||
|
<Radio value="DISABLE">禁用</Radio>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="sort"
|
||||||
|
label="排序"
|
||||||
|
rules={[{ required: true, message: '请输入排序值' }]}
|
||||||
|
>
|
||||||
|
<InputNumber min={0} placeholder="请输入排序值" style={{ width: '100%' }} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="projectId" hidden>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ApplicationModal;
|
||||||
273
frontend/src/pages/Deploy/Application/List/index.tsx
Normal file
273
frontend/src/pages/Deploy/Application/List/index.tsx
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { PageContainer } from '@ant-design/pro-layout';
|
||||||
|
import { Card, Row, Col, Typography, Button, message, Popconfirm, Tag, Space, Tooltip } from 'antd';
|
||||||
|
import { PlusOutlined, EditOutlined, DeleteOutlined, CodeOutlined, CloudUploadOutlined, ApiOutlined } from '@ant-design/icons';
|
||||||
|
import { getApplicationList, deleteApplication } from './service';
|
||||||
|
import type { Application } from './types';
|
||||||
|
import ApplicationModal from './components/ApplicationModal';
|
||||||
|
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
|
||||||
|
const ApplicationList: React.FC = () => {
|
||||||
|
const [applications, setApplications] = useState<Application[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
|
const [currentApplication, setCurrentApplication] = useState<Application>();
|
||||||
|
|
||||||
|
// TODO: 这里需要从上下文或者URL参数获取projectId
|
||||||
|
const projectId = 1;
|
||||||
|
|
||||||
|
const fetchApplications = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const data = await getApplicationList();
|
||||||
|
setApplications(data);
|
||||||
|
} catch (error) {
|
||||||
|
message.error('获取应用列表失败');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
fetchApplications();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
try {
|
||||||
|
await deleteApplication(id);
|
||||||
|
message.success('删除成功');
|
||||||
|
fetchApplications();
|
||||||
|
} catch (error) {
|
||||||
|
message.error('删除失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
setCurrentApplication(undefined);
|
||||||
|
setModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (application: Application) => {
|
||||||
|
setCurrentApplication(application);
|
||||||
|
setModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据应用编码推测应用类型
|
||||||
|
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 (
|
||||||
|
<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={[
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
key="edit"
|
||||||
|
icon={<EditOutlined style={{ color: appType.color }} />}
|
||||||
|
onClick={() => handleEdit(application)}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>,
|
||||||
|
<Popconfirm
|
||||||
|
key="delete"
|
||||||
|
title="确定要删除该应用吗?"
|
||||||
|
onConfirm={() => handleDelete(application.id)}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
danger
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer
|
||||||
|
header={{
|
||||||
|
title: '应用管理',
|
||||||
|
extra: [
|
||||||
|
<Button
|
||||||
|
key="add"
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={handleAdd}
|
||||||
|
style={{
|
||||||
|
borderRadius: '6px',
|
||||||
|
boxShadow: '0 2px 0 rgba(0,0,0,0.045)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
新建应用
|
||||||
|
</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>
|
||||||
|
))}
|
||||||
|
<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}
|
||||||
|
projectId={projectId}
|
||||||
|
/>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ApplicationList;
|
||||||
33
frontend/src/pages/Deploy/Application/List/service.ts
Normal file
33
frontend/src/pages/Deploy/Application/List/service.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
import type { CreateApplicationRequest, UpdateApplicationRequest, Application, ApplicationQuery } from './types';
|
||||||
|
import type { Page } from '@/types/base';
|
||||||
|
|
||||||
|
const BASE_URL = '/api/v1/applications';
|
||||||
|
|
||||||
|
// 创建应用
|
||||||
|
export const createApplication = (data: CreateApplicationRequest) =>
|
||||||
|
request.post<void>(BASE_URL, data);
|
||||||
|
|
||||||
|
// 更新应用
|
||||||
|
export const updateApplication = (data: UpdateApplicationRequest) =>
|
||||||
|
request.put<void>(`${BASE_URL}/${data.id}`, data);
|
||||||
|
|
||||||
|
// 删除应用
|
||||||
|
export const deleteApplication = (id: number) =>
|
||||||
|
request.delete<void>(`${BASE_URL}/${id}`);
|
||||||
|
|
||||||
|
// 获取应用详情
|
||||||
|
export const getApplication = (id: number) =>
|
||||||
|
request.get<Application>(`${BASE_URL}/${id}`);
|
||||||
|
|
||||||
|
// 分页查询应用列表
|
||||||
|
export const getApplicationPage = (params?: ApplicationQuery) =>
|
||||||
|
request.get<Page<Application>>(`${BASE_URL}/page`, { params });
|
||||||
|
|
||||||
|
// 获取所有应用列表
|
||||||
|
export const getApplicationList = () =>
|
||||||
|
request.get<Application[]>(BASE_URL);
|
||||||
|
|
||||||
|
// 条件查询应用列表
|
||||||
|
export const getApplicationListByCondition = (params?: ApplicationQuery) =>
|
||||||
|
request.get<Application[]>(`${BASE_URL}/list`, { params });
|
||||||
31
frontend/src/pages/Deploy/Application/List/types.ts
Normal file
31
frontend/src/pages/Deploy/Application/List/types.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import type { BaseQuery } from '@/types/base';
|
||||||
|
|
||||||
|
export interface Application {
|
||||||
|
id: number;
|
||||||
|
projectId: number;
|
||||||
|
appCode: string;
|
||||||
|
appName: string;
|
||||||
|
appDesc?: string;
|
||||||
|
appStatus: 'ENABLE' | 'DISABLE';
|
||||||
|
sort: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateApplicationRequest {
|
||||||
|
projectId: number;
|
||||||
|
appCode: string;
|
||||||
|
appName: string;
|
||||||
|
appDesc?: string;
|
||||||
|
appStatus: string;
|
||||||
|
sort: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateApplicationRequest extends CreateApplicationRequest {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApplicationQuery extends BaseQuery {
|
||||||
|
projectId?: number;
|
||||||
|
appCode?: string;
|
||||||
|
appName?: string;
|
||||||
|
appStatus?: string;
|
||||||
|
}
|
||||||
@ -0,0 +1,118 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { Modal, Form, Input, InputNumber, message } from 'antd';
|
||||||
|
import type { Environment } from '../types';
|
||||||
|
import { createEnvironment, updateEnvironment } from '../service';
|
||||||
|
|
||||||
|
interface EnvironmentModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
onSuccess: () => void;
|
||||||
|
initialValues?: Environment;
|
||||||
|
projectId: number;
|
||||||
|
tenantCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EnvironmentModal: React.FC<EnvironmentModalProps> = ({
|
||||||
|
visible,
|
||||||
|
onCancel,
|
||||||
|
onSuccess,
|
||||||
|
initialValues,
|
||||||
|
projectId,
|
||||||
|
tenantCode,
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const isEdit = !!initialValues;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
if (initialValues) {
|
||||||
|
form.setFieldsValue(initialValues);
|
||||||
|
} else {
|
||||||
|
form.setFieldsValue({ projectId, tenantCode, sort: 0 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [visible, initialValues, form, projectId, tenantCode]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
if (isEdit) {
|
||||||
|
await updateEnvironment({ ...values, id: initialValues.id });
|
||||||
|
message.success('更新成功');
|
||||||
|
} else {
|
||||||
|
await createEnvironment(values);
|
||||||
|
message.success('创建成功');
|
||||||
|
}
|
||||||
|
onSuccess();
|
||||||
|
onCancel();
|
||||||
|
form.resetFields();
|
||||||
|
} catch (error) {
|
||||||
|
message.error(isEdit ? '更新失败' : '创建失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
form.resetFields();
|
||||||
|
onCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={isEdit ? '编辑环境' : '新建环境'}
|
||||||
|
open={visible}
|
||||||
|
onOk={handleSubmit}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
initialValues={{ sort: 0 }}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="envCode"
|
||||||
|
label="环境编码"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: '请输入环境编码' },
|
||||||
|
{ pattern: /^[A-Za-z0-9_-]+$/, message: '环境编码只能包含字母、数字、下划线和连字符' }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入环境编码" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="envName"
|
||||||
|
label="环境名称"
|
||||||
|
rules={[{ required: true, message: '请输入环境名称' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入环境名称" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="envDesc"
|
||||||
|
label="环境描述"
|
||||||
|
>
|
||||||
|
<Input.TextArea rows={4} placeholder="请输入环境描述" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="sort"
|
||||||
|
label="排序"
|
||||||
|
rules={[{ required: true, message: '请输入排序值' }]}
|
||||||
|
>
|
||||||
|
<InputNumber min={0} placeholder="请输入排序值" style={{ width: '100%' }} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="projectId" hidden>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="tenantCode" hidden>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EnvironmentModal;
|
||||||
317
frontend/src/pages/Deploy/Environment/List/index.tsx
Normal file
317
frontend/src/pages/Deploy/Environment/List/index.tsx
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { PageContainer } from '@ant-design/pro-layout';
|
||||||
|
import { Card, Row, Col, Typography, Button, message, Popconfirm, Tag, Space, Progress } from 'antd';
|
||||||
|
import {
|
||||||
|
PlusOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
CloudServerOutlined,
|
||||||
|
DatabaseOutlined,
|
||||||
|
ClusterOutlined,
|
||||||
|
DesktopOutlined
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { getEnvironmentList, deleteEnvironment } from './service';
|
||||||
|
import type { Environment } from './types';
|
||||||
|
import EnvironmentModal from './components/EnvironmentModal';
|
||||||
|
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
|
||||||
|
const EnvironmentList: React.FC = () => {
|
||||||
|
const [environments, setEnvironments] = useState<Environment[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
|
const [currentEnvironment, setCurrentEnvironment] = useState<Environment>();
|
||||||
|
|
||||||
|
// TODO: 这里需要从上下文或者URL参数获取projectId
|
||||||
|
const projectId = 1;
|
||||||
|
|
||||||
|
const fetchEnvironments = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const data = await getEnvironmentList();
|
||||||
|
setEnvironments(data);
|
||||||
|
} catch (error) {
|
||||||
|
message.error('获取环境列表失败');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
fetchEnvironments();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
try {
|
||||||
|
await deleteEnvironment(id);
|
||||||
|
message.success('删除成功');
|
||||||
|
fetchEnvironments();
|
||||||
|
} catch (error) {
|
||||||
|
message.error('删除失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
setCurrentEnvironment(undefined);
|
||||||
|
setModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (environment: Environment) => {
|
||||||
|
setCurrentEnvironment(environment);
|
||||||
|
setModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据环境编码获取环境信息
|
||||||
|
const getEnvInfo = (envCode: string) => {
|
||||||
|
const envTypes = {
|
||||||
|
dev: {
|
||||||
|
icon: <DesktopOutlined />,
|
||||||
|
name: '开发环境',
|
||||||
|
color: '#1890ff',
|
||||||
|
usage: 45
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
icon: <DatabaseOutlined />,
|
||||||
|
name: '测试环境',
|
||||||
|
color: '#52c41a',
|
||||||
|
usage: 65
|
||||||
|
},
|
||||||
|
staging: {
|
||||||
|
icon: <ClusterOutlined />,
|
||||||
|
name: '预发环境',
|
||||||
|
color: '#faad14',
|
||||||
|
usage: 85
|
||||||
|
},
|
||||||
|
prod: {
|
||||||
|
icon: <CloudServerOutlined />,
|
||||||
|
name: '生产环境',
|
||||||
|
color: '#f5222d',
|
||||||
|
usage: 92
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const envType = Object.entries(envTypes).find(([key]) =>
|
||||||
|
envCode.toLowerCase().includes(key)
|
||||||
|
);
|
||||||
|
|
||||||
|
return envType ? envType[1] : {
|
||||||
|
icon: <CloudServerOutlined />,
|
||||||
|
name: '其他环境',
|
||||||
|
color: '#8c8c8c',
|
||||||
|
usage: 50
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const EnvironmentCard = ({ environment }: { environment: Environment }) => {
|
||||||
|
const envInfo = getEnvInfo(environment.envCode);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
hoverable
|
||||||
|
className="environment-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%, ${envInfo.color}0A 100%)`,
|
||||||
|
}}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
key="edit"
|
||||||
|
icon={<EditOutlined style={{ color: envInfo.color }} />}
|
||||||
|
onClick={() => handleEdit(environment)}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>,
|
||||||
|
<Popconfirm
|
||||||
|
key="delete"
|
||||||
|
title="确定要删除该环境吗?"
|
||||||
|
onConfirm={() => handleDelete(environment.id)}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
danger
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<div style={{ position: 'relative' }}>
|
||||||
|
<Space
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '-12px',
|
||||||
|
right: '-12px',
|
||||||
|
padding: '4px 12px',
|
||||||
|
background: envInfo.color,
|
||||||
|
color: '#fff',
|
||||||
|
borderRadius: '0 0 0 12px',
|
||||||
|
fontSize: '12px',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{envInfo.icon}
|
||||||
|
<span>{envInfo.name}</span>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<Title level={4} style={{
|
||||||
|
marginBottom: '16px',
|
||||||
|
color: '#1f1f1f',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px'
|
||||||
|
}}>
|
||||||
|
{environment.envName}
|
||||||
|
<Tag
|
||||||
|
color={environment.envStatus === 'ENABLE' ? '#52c41a' : '#d9d9d9'}
|
||||||
|
style={{
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
padding: '0 8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{environment.envStatus === 'ENABLE' ? '启用' : '禁用'}
|
||||||
|
</Tag>
|
||||||
|
</Title>
|
||||||
|
|
||||||
|
<Text
|
||||||
|
type="secondary"
|
||||||
|
style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '20px',
|
||||||
|
height: '40px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
WebkitLineClamp: 2,
|
||||||
|
WebkitBoxOrient: 'vertical',
|
||||||
|
display: '-webkit-box',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{environment.envDesc || '暂无描述'}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '16px',
|
||||||
|
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',
|
||||||
|
}}>
|
||||||
|
{environment.envCode}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '8px'
|
||||||
|
}}>
|
||||||
|
<Text type="secondary" style={{ fontSize: '12px' }}>资源使用率</Text>
|
||||||
|
<Text type="secondary" style={{ fontSize: '12px' }}>{envInfo.usage}%</Text>
|
||||||
|
</div>
|
||||||
|
<Progress
|
||||||
|
percent={envInfo.usage}
|
||||||
|
strokeColor={envInfo.color}
|
||||||
|
size="small"
|
||||||
|
showInfo={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer
|
||||||
|
header={{
|
||||||
|
title: '环境管理',
|
||||||
|
extra: [
|
||||||
|
<Button
|
||||||
|
key="add"
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={handleAdd}
|
||||||
|
style={{
|
||||||
|
borderRadius: '6px',
|
||||||
|
boxShadow: '0 2px 0 rgba(0,0,0,0.045)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
新建环境
|
||||||
|
</Button>
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ padding: '24px' }}>
|
||||||
|
<Row gutter={[24, 24]}>
|
||||||
|
{environments.map(environment => (
|
||||||
|
<Col xs={24} sm={12} md={8} lg={6} key={environment.id}>
|
||||||
|
<EnvironmentCard environment={environment} />
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<EnvironmentModal
|
||||||
|
visible={modalVisible}
|
||||||
|
onCancel={() => setModalVisible(false)}
|
||||||
|
onSuccess={fetchEnvironments}
|
||||||
|
initialValues={currentEnvironment}
|
||||||
|
projectId={projectId}
|
||||||
|
/>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EnvironmentList;
|
||||||
33
frontend/src/pages/Deploy/Environment/List/service.ts
Normal file
33
frontend/src/pages/Deploy/Environment/List/service.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
import type { CreateEnvironmentRequest, UpdateEnvironmentRequest, Environment, EnvironmentQuery } from './types';
|
||||||
|
import type { Page } from '@/types/base';
|
||||||
|
|
||||||
|
const BASE_URL = '/api/v1/environments';
|
||||||
|
|
||||||
|
// 创建环境
|
||||||
|
export const createEnvironment = (data: CreateEnvironmentRequest) =>
|
||||||
|
request.post<void>(BASE_URL, data);
|
||||||
|
|
||||||
|
// 更新环境
|
||||||
|
export const updateEnvironment = (data: UpdateEnvironmentRequest) =>
|
||||||
|
request.put<void>(`${BASE_URL}/${data.id}`, data);
|
||||||
|
|
||||||
|
// 删除环境
|
||||||
|
export const deleteEnvironment = (id: number) =>
|
||||||
|
request.delete<void>(`${BASE_URL}/${id}`);
|
||||||
|
|
||||||
|
// 获取环境详情
|
||||||
|
export const getEnvironment = (id: number) =>
|
||||||
|
request.get<Environment>(`${BASE_URL}/${id}`);
|
||||||
|
|
||||||
|
// 分页查询环境列表
|
||||||
|
export const getEnvironmentPage = (params?: EnvironmentQuery) =>
|
||||||
|
request.get<Page<Environment>>(`${BASE_URL}/page`, { params });
|
||||||
|
|
||||||
|
// 获取所有环境列表
|
||||||
|
export const getEnvironmentList = () =>
|
||||||
|
request.get<Environment[]>(BASE_URL);
|
||||||
|
|
||||||
|
// 条件查询环境列表
|
||||||
|
export const getEnvironmentListByCondition = (params?: EnvironmentQuery) =>
|
||||||
|
request.get<Environment[]>(`${BASE_URL}/list`, { params });
|
||||||
31
frontend/src/pages/Deploy/Environment/List/types.ts
Normal file
31
frontend/src/pages/Deploy/Environment/List/types.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import type { BaseQuery } from '@/types/base';
|
||||||
|
|
||||||
|
export interface Environment {
|
||||||
|
id: number;
|
||||||
|
tenantCode: string;
|
||||||
|
projectId: number;
|
||||||
|
envCode: string;
|
||||||
|
envName: string;
|
||||||
|
envDesc?: string;
|
||||||
|
sort: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateEnvironmentRequest {
|
||||||
|
tenantCode: string;
|
||||||
|
projectId: number;
|
||||||
|
envCode: string;
|
||||||
|
envName: string;
|
||||||
|
envDesc?: string;
|
||||||
|
sort: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateEnvironmentRequest extends CreateEnvironmentRequest {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EnvironmentQuery extends BaseQuery {
|
||||||
|
tenantCode?: string;
|
||||||
|
projectId?: number;
|
||||||
|
envCode?: string;
|
||||||
|
envName?: string;
|
||||||
|
}
|
||||||
@ -0,0 +1,113 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { Modal, Form, Input, InputNumber, Radio, message } from 'antd';
|
||||||
|
import type { Project } from '../types';
|
||||||
|
import { createProject, updateProject } from '../service';
|
||||||
|
|
||||||
|
interface ProjectModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
onSuccess: () => void;
|
||||||
|
initialValues?: Project;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProjectModal: React.FC<ProjectModalProps> = ({
|
||||||
|
visible,
|
||||||
|
onCancel,
|
||||||
|
onSuccess,
|
||||||
|
initialValues,
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const isEdit = !!initialValues;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible && initialValues) {
|
||||||
|
form.setFieldsValue(initialValues);
|
||||||
|
}
|
||||||
|
}, [visible, initialValues, form]);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
if (isEdit) {
|
||||||
|
await updateProject({ ...values, id: initialValues.id });
|
||||||
|
message.success('更新成功');
|
||||||
|
} else {
|
||||||
|
await createProject(values);
|
||||||
|
message.success('创建成功');
|
||||||
|
}
|
||||||
|
onSuccess();
|
||||||
|
onCancel();
|
||||||
|
form.resetFields();
|
||||||
|
} catch (error) {
|
||||||
|
message.error(isEdit ? '更新失败' : '创建失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
form.resetFields();
|
||||||
|
onCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={isEdit ? '编辑项目' : '新建项目'}
|
||||||
|
open={visible}
|
||||||
|
onOk={handleSubmit}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
initialValues={{ projectStatus: 'ENABLE', sort: 0 }}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="projectCode"
|
||||||
|
label="项目编码"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: '请输入项目编码' },
|
||||||
|
{ pattern: /^[A-Za-z0-9_-]+$/, message: '项目编码只能包含字母、数字、下划线和连字符' }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入项目编码" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="projectName"
|
||||||
|
label="项目名称"
|
||||||
|
rules={[{ required: true, message: '请输入项目名称' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入项目名称" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="projectDesc"
|
||||||
|
label="项目描述"
|
||||||
|
>
|
||||||
|
<Input.TextArea rows={4} placeholder="请输入项目描述" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="projectStatus"
|
||||||
|
label="项目状态"
|
||||||
|
rules={[{ required: true, message: '请选择项目状态' }]}
|
||||||
|
>
|
||||||
|
<Radio.Group>
|
||||||
|
<Radio value="ENABLE">启用</Radio>
|
||||||
|
<Radio value="DISABLE">禁用</Radio>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="sort"
|
||||||
|
label="排序"
|
||||||
|
rules={[{ required: true, message: '请输入排序值' }]}
|
||||||
|
>
|
||||||
|
<InputNumber min={0} placeholder="请输入排序值" style={{ width: '100%' }} />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProjectModal;
|
||||||
217
frontend/src/pages/Deploy/Project/List/index.tsx
Normal file
217
frontend/src/pages/Deploy/Project/List/index.tsx
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { PageContainer } from '@ant-design/pro-layout';
|
||||||
|
import { Card, Row, Col, Typography, Button, message, Popconfirm, Tag } from 'antd';
|
||||||
|
import { PlusOutlined, TeamOutlined, AppstoreOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||||
|
import { getProjectList, deleteProject } from './service';
|
||||||
|
import type { Project } from './types';
|
||||||
|
import ProjectModal from './components/ProjectModal';
|
||||||
|
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
|
||||||
|
const ProjectList: React.FC = () => {
|
||||||
|
const [projects, setProjects] = useState<Project[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
|
const [currentProject, setCurrentProject] = useState<Project>();
|
||||||
|
|
||||||
|
const fetchProjects = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const data = await getProjectList();
|
||||||
|
setProjects(data);
|
||||||
|
} catch (error) {
|
||||||
|
message.error('获取项目列表失败');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchProjects();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
try {
|
||||||
|
await deleteProject(id);
|
||||||
|
message.success('删除成功');
|
||||||
|
fetchProjects();
|
||||||
|
} catch (error) {
|
||||||
|
message.error('删除失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
setCurrentProject(undefined);
|
||||||
|
setModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (project: Project) => {
|
||||||
|
setCurrentProject(project);
|
||||||
|
setModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ProjectCard = ({ project }: { project: Project }) => (
|
||||||
|
<Card
|
||||||
|
hoverable
|
||||||
|
className="project-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%, #f8f9ff 100%)',
|
||||||
|
}}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
key="edit"
|
||||||
|
icon={<EditOutlined style={{ color: '#1890ff' }} />}
|
||||||
|
onClick={() => handleEdit(project)}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>,
|
||||||
|
<Popconfirm
|
||||||
|
key="delete"
|
||||||
|
title="确定要删除该项目吗?"
|
||||||
|
onConfirm={() => handleDelete(project.id)}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
danger
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<div style={{ position: 'relative', paddingTop: '8px' }}>
|
||||||
|
<Title level={4} style={{ marginBottom: '16px', color: '#1f1f1f' }}>
|
||||||
|
{project.projectName}
|
||||||
|
<Tag
|
||||||
|
color={project.projectStatus === 'ENABLE' ? '#52c41a' : '#d9d9d9'}
|
||||||
|
style={{
|
||||||
|
marginLeft: '8px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
padding: '0 8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{project.projectStatus === 'ENABLE' ? '启用' : '禁用'}
|
||||||
|
</Tag>
|
||||||
|
</Title>
|
||||||
|
<Text
|
||||||
|
type="secondary"
|
||||||
|
style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: '20px',
|
||||||
|
height: '40px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
display: '-webkit-box',
|
||||||
|
WebkitLineClamp: 2,
|
||||||
|
WebkitBoxOrient: 'vertical',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{project.projectDesc || '暂无描述'}
|
||||||
|
</Text>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '12px',
|
||||||
|
background: '#f8f9fa',
|
||||||
|
borderRadius: '8px',
|
||||||
|
}}>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<Text type="secondary" style={{ fontSize: '12px' }}>项目编码</Text>
|
||||||
|
<div style={{
|
||||||
|
color: '#262626',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
marginTop: '4px',
|
||||||
|
fontSize: '14px',
|
||||||
|
}}>
|
||||||
|
{project.projectCode}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginLeft: '24px' }}>
|
||||||
|
<Text type="secondary" style={{ fontSize: '12px' }}>排序</Text>
|
||||||
|
<div style={{
|
||||||
|
color: '#262626',
|
||||||
|
textAlign: 'center',
|
||||||
|
marginTop: '4px',
|
||||||
|
fontSize: '14px',
|
||||||
|
}}>
|
||||||
|
{project.sort}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer
|
||||||
|
header={{
|
||||||
|
title: '项目管理',
|
||||||
|
extra: [
|
||||||
|
<Button
|
||||||
|
key="add"
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={handleAdd}
|
||||||
|
style={{
|
||||||
|
borderRadius: '6px',
|
||||||
|
boxShadow: '0 2px 0 rgba(0,0,0,0.045)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
新建项目
|
||||||
|
</Button>
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ padding: '24px' }}>
|
||||||
|
<Row gutter={[24, 24]}>
|
||||||
|
{projects.map(project => (
|
||||||
|
<Col xs={24} sm={12} md={8} lg={6} key={project.id}>
|
||||||
|
<ProjectCard project={project} />
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
<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>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProjectList;
|
||||||
41
frontend/src/pages/Deploy/Project/List/service.ts
Normal file
41
frontend/src/pages/Deploy/Project/List/service.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
import type { CreateProjectRequest, UpdateProjectRequest, Project, ProjectQueryParams } from './types';
|
||||||
|
import type { Page } from '@/types/base';
|
||||||
|
|
||||||
|
const BASE_URL = '/api/v1/projects';
|
||||||
|
|
||||||
|
// 创建项目
|
||||||
|
export const createProject = (data: CreateProjectRequest) =>
|
||||||
|
request.post<void>(BASE_URL, data);
|
||||||
|
|
||||||
|
// 更新项目
|
||||||
|
export const updateProject = (data: UpdateProjectRequest) =>
|
||||||
|
request.put<void>(`${BASE_URL}/${data.id}`, data);
|
||||||
|
|
||||||
|
// 删除项目
|
||||||
|
export const deleteProject = (id: number) =>
|
||||||
|
request.delete<void>(`${BASE_URL}/${id}`);
|
||||||
|
|
||||||
|
// 获取项目详情
|
||||||
|
export const getProject = (id: number) =>
|
||||||
|
request.get<Project>(`${BASE_URL}/${id}`);
|
||||||
|
|
||||||
|
// 分页查询项目列表
|
||||||
|
export const getProjectPage = (params?: ProjectQueryParams) =>
|
||||||
|
request.get<Page<Project>>(`${BASE_URL}/page`, { params });
|
||||||
|
|
||||||
|
// 获取所有项目列表
|
||||||
|
export const getProjectList = () =>
|
||||||
|
request.get<Project[]>(BASE_URL);
|
||||||
|
|
||||||
|
// 条件查询项目列表
|
||||||
|
export const getProjectListByCondition = (params?: ProjectQueryParams) =>
|
||||||
|
request.get<Project[]>(`${BASE_URL}/list`, { params });
|
||||||
|
|
||||||
|
// 批量处理项目
|
||||||
|
export const batchProcessProject = (data: any) =>
|
||||||
|
request.post<void>(`${BASE_URL}/batch`, data);
|
||||||
|
|
||||||
|
// 导出项目数据
|
||||||
|
export const exportProject = () =>
|
||||||
|
request.get(`${BASE_URL}/export`, { responseType: 'blob' });
|
||||||
33
frontend/src/pages/Deploy/Project/List/types.ts
Normal file
33
frontend/src/pages/Deploy/Project/List/types.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { BaseResponse, BaseRequest, BaseQuery } from '@/types/base';
|
||||||
|
|
||||||
|
// 项目基础信息
|
||||||
|
export interface Project extends BaseResponse {
|
||||||
|
tenantId: number;
|
||||||
|
projectCode: string;
|
||||||
|
projectName: string;
|
||||||
|
projectDesc?: string;
|
||||||
|
projectStatus: string;
|
||||||
|
sort: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建项目请求参数
|
||||||
|
export interface CreateProjectRequest extends BaseRequest {
|
||||||
|
tenantId: number;
|
||||||
|
projectCode: string;
|
||||||
|
projectName: string;
|
||||||
|
projectDesc?: string;
|
||||||
|
projectStatus: string;
|
||||||
|
sort: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新项目请求参数
|
||||||
|
export interface UpdateProjectRequest extends CreateProjectRequest {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页查询参数
|
||||||
|
export interface ProjectQueryParams extends BaseQuery {
|
||||||
|
projectName?: string;
|
||||||
|
projectCode?: string;
|
||||||
|
projectStatus?: string;
|
||||||
|
}
|
||||||
@ -6,7 +6,6 @@ import {login} from './service';
|
|||||||
import {getEnabledTenants} from '@/pages/System/Tenant/service';
|
import {getEnabledTenants} from '@/pages/System/Tenant/service';
|
||||||
import {setToken, setUserInfo, setMenus} from '../../store/userSlice';
|
import {setToken, setUserInfo, setMenus} from '../../store/userSlice';
|
||||||
import {getCurrentUserMenus} from '@/pages/System/Menu/service';
|
import {getCurrentUserMenus} from '@/pages/System/Menu/service';
|
||||||
import type { MenuResponse } from '@/pages/System/Menu/types';
|
|
||||||
import type { TenantResponse } from '@/pages/System/Tenant/types';
|
import type { TenantResponse } from '@/pages/System/Tenant/types';
|
||||||
import styles from './index.module.css';
|
import styles from './index.module.css';
|
||||||
|
|
||||||
|
|||||||
@ -18,142 +18,142 @@ export const getCurrentUserMenus = async () => {
|
|||||||
const menus = await request.get<MenuResponse[]>(`${BASE_URL}/current`);
|
const menus = await request.get<MenuResponse[]>(`${BASE_URL}/current`);
|
||||||
console.log('Backend menus:', menus);
|
console.log('Backend menus:', menus);
|
||||||
|
|
||||||
// 添加首页路由
|
// // 添加首页路由
|
||||||
const dashboard: MenuResponse = {
|
// const dashboard: MenuResponse = {
|
||||||
id: 0,
|
// id: 0,
|
||||||
createTime: new Date().toISOString(),
|
// createTime: new Date().toISOString(),
|
||||||
updateTime: new Date().toISOString(),
|
// updateTime: new Date().toISOString(),
|
||||||
version: 0,
|
// version: 0,
|
||||||
name: "首页",
|
// name: "首页",
|
||||||
path: "/dashboard",
|
// path: "/dashboard",
|
||||||
component: "/Dashboard/index",
|
// component: "/Dashboard/index",
|
||||||
icon: "dashboard",
|
// icon: "dashboard",
|
||||||
type: MenuTypeEnum.MENU,
|
// type: MenuTypeEnum.MENU,
|
||||||
parentId: 0,
|
// parentId: 0,
|
||||||
sort: 0,
|
// sort: 0,
|
||||||
hidden: false,
|
// hidden: false,
|
||||||
enabled: true,
|
// enabled: true,
|
||||||
createBy: "system",
|
// createBy: "system",
|
||||||
updateBy: "system"
|
// updateBy: "system"
|
||||||
};
|
// };
|
||||||
|
//
|
||||||
// 添加工作流菜单
|
// // 添加工作流菜单
|
||||||
const workflow: MenuResponse = {
|
// const workflow: MenuResponse = {
|
||||||
id: -2,
|
// id: -2,
|
||||||
createTime: new Date().toISOString(),
|
// createTime: new Date().toISOString(),
|
||||||
updateTime: new Date().toISOString(),
|
// updateTime: new Date().toISOString(),
|
||||||
version: 0,
|
// version: 0,
|
||||||
name: "工作流管理",
|
// name: "工作流管理",
|
||||||
path: "/workflow",
|
// path: "/workflow",
|
||||||
icon: "apartment",
|
// icon: "apartment",
|
||||||
type: MenuTypeEnum.DIRECTORY,
|
// type: MenuTypeEnum.DIRECTORY,
|
||||||
parentId: 0,
|
// parentId: 0,
|
||||||
sort: 2,
|
// sort: 2,
|
||||||
hidden: false,
|
// hidden: false,
|
||||||
enabled: true,
|
// enabled: true,
|
||||||
createBy: "system",
|
// createBy: "system",
|
||||||
updateBy: "system",
|
// updateBy: "system",
|
||||||
children: [
|
// children: [
|
||||||
{
|
// {
|
||||||
id: -21,
|
// id: -21,
|
||||||
createTime: new Date().toISOString(),
|
// createTime: new Date().toISOString(),
|
||||||
updateTime: new Date().toISOString(),
|
// updateTime: new Date().toISOString(),
|
||||||
version: 0,
|
// version: 0,
|
||||||
name: "流程定义",
|
// name: "流程定义",
|
||||||
path: "/workflow/definition",
|
// path: "/workflow/definition",
|
||||||
component: "/pages/Workflow/Definition/index",
|
// component: "/pages/Workflow/Definition/index",
|
||||||
icon: "apartment",
|
// icon: "apartment",
|
||||||
type: MenuTypeEnum.MENU,
|
// type: MenuTypeEnum.MENU,
|
||||||
parentId: -2,
|
// parentId: -2,
|
||||||
sort: 0,
|
// sort: 0,
|
||||||
hidden: false,
|
// hidden: false,
|
||||||
enabled: true,
|
// enabled: true,
|
||||||
createBy: "system",
|
// createBy: "system",
|
||||||
updateBy: "system"
|
// updateBy: "system"
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
id: 1000,
|
// id: 1000,
|
||||||
createTime: new Date().toISOString(),
|
// createTime: new Date().toISOString(),
|
||||||
updateTime: new Date().toISOString(),
|
// updateTime: new Date().toISOString(),
|
||||||
version: 0,
|
// version: 0,
|
||||||
name: "流程实例",
|
// name: "流程实例",
|
||||||
path: "/workflow/instance",
|
// path: "/workflow/instance",
|
||||||
component: "/Workflow/Instance/index",
|
// component: "/Workflow/Instance/index",
|
||||||
icon: "instance",
|
// icon: "instance",
|
||||||
type: MenuTypeEnum.DIRECTORY,
|
// type: MenuTypeEnum.DIRECTORY,
|
||||||
parentId: -2,
|
// parentId: -2,
|
||||||
sort: 1,
|
// sort: 1,
|
||||||
hidden: false,
|
// hidden: false,
|
||||||
enabled: true,
|
// enabled: true,
|
||||||
createBy: "system",
|
// createBy: "system",
|
||||||
updateBy: "system",
|
// updateBy: "system",
|
||||||
children: [
|
// children: [
|
||||||
{
|
// {
|
||||||
id: 1001,
|
// id: 1001,
|
||||||
createTime: new Date().toISOString(),
|
// createTime: new Date().toISOString(),
|
||||||
updateTime: new Date().toISOString(),
|
// updateTime: new Date().toISOString(),
|
||||||
version: 0,
|
// version: 0,
|
||||||
name: "工作流实例",
|
// name: "工作流实例",
|
||||||
path: "/workflow/instance",
|
// path: "/workflow/instance",
|
||||||
component: "/Workflow/Instance/index",
|
// component: "/Workflow/Instance/index",
|
||||||
icon: "instance",
|
// icon: "instance",
|
||||||
type: MenuTypeEnum.MENU,
|
// type: MenuTypeEnum.MENU,
|
||||||
parentId: 1000,
|
// parentId: 1000,
|
||||||
sort: 1,
|
// sort: 1,
|
||||||
hidden: false
|
// hidden: false
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
id: 1002,
|
// id: 1002,
|
||||||
createTime: new Date().toISOString(),
|
// createTime: new Date().toISOString(),
|
||||||
updateTime: new Date().toISOString(),
|
// updateTime: new Date().toISOString(),
|
||||||
version: 0,
|
// version: 0,
|
||||||
name: "节点设计",
|
// name: "节点设计",
|
||||||
path: "/workflow/node-design",
|
// path: "/workflow/node-design",
|
||||||
component: "/Workflow/NodeDesign/index",
|
// component: "/Workflow/NodeDesign/index",
|
||||||
icon: "node-design",
|
// icon: "node-design",
|
||||||
type: MenuTypeEnum.MENU,
|
// type: MenuTypeEnum.MENU,
|
||||||
parentId: 1000,
|
// parentId: 1000,
|
||||||
sort: 2,
|
// sort: 2,
|
||||||
hidden: false
|
// hidden: false
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
id: -23,
|
// id: -23,
|
||||||
createTime: new Date().toISOString(),
|
// createTime: new Date().toISOString(),
|
||||||
updateTime: new Date().toISOString(),
|
// updateTime: new Date().toISOString(),
|
||||||
version: 0,
|
// version: 0,
|
||||||
name: "流程监控",
|
// name: "流程监控",
|
||||||
path: "/workflow/monitor",
|
// path: "/workflow/monitor",
|
||||||
component: "/pages/Workflow/Monitor/index",
|
// component: "/pages/Workflow/Monitor/index",
|
||||||
icon: "dashboard",
|
// icon: "dashboard",
|
||||||
type: MenuTypeEnum.MENU,
|
// type: MenuTypeEnum.MENU,
|
||||||
parentId: -2,
|
// parentId: -2,
|
||||||
sort: 2,
|
// sort: 2,
|
||||||
hidden: false,
|
// hidden: false,
|
||||||
enabled: true,
|
// enabled: true,
|
||||||
createBy: "system",
|
// createBy: "system",
|
||||||
updateBy: "system"
|
// updateBy: "system"
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
id: -24,
|
// id: -24,
|
||||||
createTime: new Date().toISOString(),
|
// createTime: new Date().toISOString(),
|
||||||
updateTime: new Date().toISOString(),
|
// updateTime: new Date().toISOString(),
|
||||||
version: 0,
|
// version: 0,
|
||||||
name: "日志流",
|
// name: "日志流",
|
||||||
path: "/workflow/log-stream/:processInstanceId",
|
// path: "/workflow/log-stream/:processInstanceId",
|
||||||
component: "/pages/LogStream/index",
|
// component: "/pages/LogStream/index",
|
||||||
icon: "file-text",
|
// icon: "file-text",
|
||||||
type: MenuTypeEnum.MENU,
|
// type: MenuTypeEnum.MENU,
|
||||||
parentId: -2,
|
// parentId: -2,
|
||||||
sort: 3,
|
// sort: 3,
|
||||||
hidden: false,
|
// hidden: false,
|
||||||
enabled: true,
|
// enabled: true,
|
||||||
createBy: "system",
|
// createBy: "system",
|
||||||
updateBy: "system"
|
// updateBy: "system"
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
};
|
// };
|
||||||
|
|
||||||
// 添加X6测试菜单
|
// 添加X6测试菜单
|
||||||
// const x6Test: MenuResponse = {
|
// const x6Test: MenuResponse = {
|
||||||
@ -192,7 +192,7 @@ export const getCurrentUserMenus = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const processedMenus = menus.map(processMenu);
|
const processedMenus = menus.map(processMenu);
|
||||||
const allMenus = [dashboard, workflow, ...processedMenus];
|
const allMenus = [ ...processedMenus];
|
||||||
console.log('All menus:', allMenus);
|
console.log('All menus:', allMenus);
|
||||||
return allMenus;
|
return allMenus;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,24 +1,24 @@
|
|||||||
import {createBrowserRouter, Navigate} from 'react-router-dom';
|
import { createBrowserRouter, Navigate } from 'react-router-dom';
|
||||||
import {lazy, Suspense} from 'react';
|
import { lazy, Suspense } from 'react';
|
||||||
import {Spin} from 'antd';
|
import { Spin } from 'antd';
|
||||||
import Login from '../pages/Login';
|
import Login from '../pages/Login';
|
||||||
import BasicLayout from '../layouts/BasicLayout';
|
import BasicLayout from '../layouts/BasicLayout';
|
||||||
import {useSelector} from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import {RootState} from '../store';
|
import { RootState } from '../store';
|
||||||
|
|
||||||
// 加中组件
|
// 加中组件
|
||||||
const LoadingComponent = () => (
|
const LoadingComponent = () => (
|
||||||
<div style={{padding: 24, textAlign: 'center'}}>
|
<div style={{ padding: 24, textAlign: 'center' }}>
|
||||||
<Spin size="large"/>
|
<Spin size="large" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
// 路由守卫
|
// 路由守卫
|
||||||
const PrivateRoute = ({children}: { children: React.ReactNode }) => {
|
const PrivateRoute = ({ children }: { children: React.ReactNode }) => {
|
||||||
const token = useSelector((state: RootState) => state.user.token);
|
const token = useSelector((state: RootState) => state.user.token);
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return <Navigate to="/login"/>;
|
return <Navigate to="/login" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
@ -39,24 +39,27 @@ const WorkflowMonitor = lazy(() => import('../pages/Workflow/Monitor'));
|
|||||||
const LogStreamPage = lazy(() => import('../pages/LogStream'));
|
const LogStreamPage = lazy(() => import('../pages/LogStream'));
|
||||||
const NodeDesign = lazy(() => import('../pages/Workflow/NodeDesign'));
|
const NodeDesign = lazy(() => import('../pages/Workflow/NodeDesign'));
|
||||||
const NodeDesignForm = lazy(() => import('../pages/Workflow/NodeDesign/Design'));
|
const NodeDesignForm = lazy(() => import('../pages/Workflow/NodeDesign/Design'));
|
||||||
|
const ProjectList = lazy(() => import('../pages/Deploy/Project/List'));
|
||||||
|
const ApplicationList = lazy(() => import('../pages/Deploy/Application/List'));
|
||||||
|
const EnvironmentList = lazy(() => import('../pages/Deploy/Environment/List'));
|
||||||
|
|
||||||
// 创建路由
|
// 创建路由
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
element: <Login/>
|
element: <Login />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
element: (
|
element: (
|
||||||
<PrivateRoute>
|
<PrivateRoute>
|
||||||
<BasicLayout/>
|
<BasicLayout />
|
||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
element: <Navigate to="/dashboard" replace/>
|
element: <Navigate to="/dashboard" replace />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'dashboard',
|
path: 'dashboard',
|
||||||
@ -66,6 +69,23 @@ const router = createBrowserRouter([
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'deploy',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'project',
|
||||||
|
element: <Suspense fallback={<LoadingComponent/>}><ProjectList/></Suspense>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'application',
|
||||||
|
element: <Suspense fallback={<LoadingComponent/>}><ApplicationList/></Suspense>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'environment',
|
||||||
|
element: <Suspense fallback={<LoadingComponent/>}><EnvironmentList/></Suspense>
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'system',
|
path: 'system',
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user