This commit is contained in:
dengqichen 2024-12-30 14:08:21 +08:00
parent 298866cf00
commit 5e85c51bc4
2 changed files with 334 additions and 358 deletions

View File

@ -1,365 +1,344 @@
import React, { useState } from 'react'; import React, {useState} from 'react';
import { Card, Table, Button, Space, Modal, Form, Input, message, Select, InputNumber, Switch, Tag, Tooltip } from 'antd'; import {Card, Table, Button, Space, Modal, Form, Input, message, Select, InputNumber, Switch, Tag, Tooltip} from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined, SyncOutlined, CheckCircleOutlined, CloseCircleOutlined, LinkOutlined, MinusCircleOutlined } from '@ant-design/icons'; import {PlusOutlined, EditOutlined, DeleteOutlined, SyncOutlined, CheckCircleOutlined, CloseCircleOutlined, LinkOutlined, MinusCircleOutlined} from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table'; import type {ColumnsType} from 'antd/es/table';
import { useTableData } from '@/hooks/useTableData'; import {useTableData} from '@/hooks/useTableData';
import * as service from './service'; import * as service from './service';
import { SystemType, AuthType, SyncStatus, ExternalSystemResponse, ExternalSystemRequest, ExternalSystemQuery } from './types'; import {SystemType, AuthType, SyncStatus, ExternalSystemResponse, ExternalSystemRequest, ExternalSystemQuery} from './types';
const ExternalPage: React.FC = () => { const ExternalPage: React.FC = () => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [modalVisible, setModalVisible] = useState(false); const [modalVisible, setModalVisible] = useState(false);
const [editingSystem, setEditingSystem] = useState<ExternalSystemResponse | null>(null); const [editingSystem, setEditingSystem] = useState<ExternalSystemResponse | null>(null);
const { const {
list, list,
loading, loading,
pagination, pagination,
handleTableChange, handleTableChange,
handleCreate, handleCreate,
handleUpdate, handleUpdate,
handleDelete, handleDelete,
refresh refresh
} = useTableData<ExternalSystemResponse, ExternalSystemQuery, ExternalSystemRequest, ExternalSystemRequest>({ } = useTableData<ExternalSystemResponse, ExternalSystemQuery, ExternalSystemRequest, ExternalSystemRequest>({
service: { service: {
list: service.getExternalSystemsPage, list: service.getExternalSystemsPage,
create: service.createExternalSystem, create: service.createExternalSystem,
update: service.updateExternalSystem, update: service.updateExternalSystem,
delete: service.deleteExternalSystem delete: service.deleteExternalSystem
}, },
config: { config: {
message: { message: {
createSuccess: '创建系统成功', createSuccess: '创建系统成功',
updateSuccess: '更新系统成功', updateSuccess: '更新系统成功',
deleteSuccess: '删除系统成功' deleteSuccess: '删除系统成功'
} }
}
});
const handleAdd = () => {
setEditingSystem(null);
form.resetFields();
form.setFieldsValue({
enabled: true,
sort: 1,
authType: AuthType.BASIC
});
setModalVisible(true);
};
const handleEdit = (record: ExternalSystemResponse) => {
setEditingSystem(record);
form.setFieldsValue({
...record,
password: undefined // 不显示密码
});
setModalVisible(true);
};
const handleTestConnection = async (id: number) => {
try {
const success = await service.testConnection(id);
message.success(success ? '连接成功' : '连接失败');
} catch (error) {
message.error('测试连接失败');
}
};
const handleStatusChange = async (id: number, enabled: boolean) => {
try {
await service.updateStatus(id, enabled);
message.success('更新状态成功');
refresh();
} catch (error) {
message.error('更新状态失败');
}
};
const handleSubmit = async () => {
try {
const values = await form.validateFields();
let success = false;
if (editingSystem) {
success = await handleUpdate(editingSystem.id, values);
} else {
success = await handleCreate(values);
}
if (success) {
setModalVisible(false);
}
} catch (error: any) {
// 如果是表单验证错误,不显示错误消息
if (!error.errorFields) {
// 如果是后端返回的错误,显示后端的错误消息
if (error.response?.data) {
message.error(error.response.data.message || '操作失败');
} else {
message.error(error.message || '操作失败');
} }
} });
}
};
const columns: ColumnsType<ExternalSystemResponse> = [ const handleAdd = () => {
{ setEditingSystem(null);
title: '系统名称', form.resetFields();
dataIndex: 'name', form.setFieldsValue({
width: 200, enabled: true,
}, sort: 1,
{ authType: AuthType.BASIC
title: '系统类型', });
dataIndex: 'type', setModalVisible(true);
width: 120, };
render: (type: SystemType) => {
const typeMap = {
[SystemType.JENKINS]: 'Jenkins',
[SystemType.GIT]: 'Git',
[SystemType.ZENTAO]: '禅道'
};
return typeMap[type];
}
},
{
title: '系统地址',
dataIndex: 'url',
width: 300,
render: (url: string) => (
<a href={url} target="_blank" rel="noopener noreferrer">
{url}
</a>
),
},
{
title: '认证方式',
dataIndex: 'authType',
width: 120,
render: (authType: AuthType) => {
const authTypeMap = {
[AuthType.BASIC]: '用户名密码',
[AuthType.TOKEN]: '令牌',
[AuthType.OAUTH]: 'OAuth2'
};
return authTypeMap[authType];
}
},
{
title: '同步状态',
dataIndex: 'syncStatus',
width: 120,
render: (status: SyncStatus, record) => {
const statusConfig = {
[SyncStatus.SUCCESS]: { icon: <CheckCircleOutlined />, color: 'success', text: '成功' },
[SyncStatus.FAILED]: { icon: <CloseCircleOutlined />, color: 'error', text: '失败' },
[SyncStatus.RUNNING]: { icon: <SyncOutlined spin />, color: 'processing', text: '同步中' },
NONE: { icon: <MinusCircleOutlined />, color: 'default', text: '未同步' }
};
const config = statusConfig[status] || statusConfig.NONE;
return (
<Tooltip title={record.lastSyncTime || '未同步'}>
<Tag icon={config.icon} color={config.color}>
{config.text}
</Tag>
</Tooltip>
);
}
},
{
title: '最后连接时间',
dataIndex: 'lastConnectTime',
width: 150,
render: (time: string) => time || '-'
},
{
title: '状态',
dataIndex: 'enabled',
width: 100,
render: (enabled: boolean, record) => (
<Switch
checked={enabled}
onChange={(checked) => handleStatusChange(record.id, checked)}
checkedChildren="否"
unCheckedChildren="是"
/>
)
},
{
title: '操作',
key: 'action',
width: 280,
render: (_, record) => (
<Space>
<Button
type="link"
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
>
</Button>
<Button
type="link"
icon={<LinkOutlined />}
onClick={() => handleTestConnection(record.id)}
>
</Button>
<Button
type="link"
danger
icon={<DeleteOutlined />}
onClick={() => handleDelete(record.id)}
>
</Button>
</Space>
),
},
];
return ( const handleEdit = (record: ExternalSystemResponse) => {
<div> setEditingSystem(record);
<Card> form.setFieldsValue({
<div style={{ marginBottom: 16 }}> ...record,
<Button password: undefined // 不显示密码
type="primary" });
icon={<PlusOutlined />} setModalVisible(true);
onClick={handleAdd} };
>
const handleTestConnection = async (id: number) => {
</Button> try {
const success = await service.testConnection(id);
message.success(success ? '连接成功' : '连接失败');
} catch (error) {
message.error('测试连接失败');
}
};
const handleStatusChange = async (id: number, enabled: boolean) => {
try {
await service.updateStatus(id, enabled);
message.success('更新状态成功');
refresh();
} catch (error) {
message.error('更新状态失败');
}
};
const handleSubmit = async () => {
try {
const values = await form.validateFields();
let success = false;
if (editingSystem) {
success = await handleUpdate(editingSystem.id, values);
} else {
success = await handleCreate(values);
}
if (success) {
setModalVisible(false);
}
} catch (error: any) {
// 如果是表单验证错误,不显示错误消息
if (!error.errorFields) {
// 如果是后端返回的错误,显示后端的错误消息
if (error.response?.data) {
message.error(error.response.data.message || '操作失败');
} else {
message.error(error.message || '操作失败');
}
}
}
};
const columns: ColumnsType<ExternalSystemResponse> = [
{
title: '系统名称',
dataIndex: 'name',
width: 200,
},
{
title: '系统类型',
dataIndex: 'type',
width: 120,
render: (type: SystemType) => {
const typeMap = {
[SystemType.JENKINS]: 'Jenkins',
[SystemType.GIT]: 'Git',
[SystemType.ZENTAO]: '禅道'
};
return typeMap[type];
}
},
{
title: '系统地址',
dataIndex: 'url',
width: 300,
render: (url: string) => (
<a href={url} target="_blank" rel="noopener noreferrer">
{url}
</a>
),
},
{
title: '认证方式',
dataIndex: 'authType',
width: 120,
render: (authType: AuthType) => {
const authTypeMap = {
[AuthType.BASIC]: '用户名密码',
[AuthType.TOKEN]: '令牌',
[AuthType.OAUTH]: 'OAuth2'
};
return authTypeMap[authType];
}
},
{
title: '最后连接时间',
dataIndex: 'lastConnectTime',
width: 150,
render: (time: string) => time || '-'
},
{
title: '状态',
dataIndex: 'enabled',
width: 100,
render: (enabled: boolean, record) => (
<Switch
checked={enabled}
onChange={(checked) => handleStatusChange(record.id, checked)}
checkedChildren="否"
unCheckedChildren="是"
/>
)
},
{
title: '操作',
key: 'action',
width: 280,
render: (_, record) => (
<Space>
<Button
type="link"
icon={<EditOutlined/>}
onClick={() => handleEdit(record)}
>
</Button>
<Button
type="link"
icon={<LinkOutlined/>}
onClick={() => handleTestConnection(record.id)}
>
</Button>
<Button
type="link"
danger
icon={<DeleteOutlined/>}
onClick={() => handleDelete(record.id)}
>
</Button>
</Space>
),
},
];
return (
<div>
<Card>
<div style={{marginBottom: 16}}>
<Button
type="primary"
icon={<PlusOutlined/>}
onClick={handleAdd}
>
</Button>
</div>
<Table
columns={columns}
dataSource={list}
rowKey="id"
loading={loading}
pagination={pagination}
onChange={handleTableChange}
/>
</Card>
<Modal
title={editingSystem ? '编辑系统' : '新增系统'}
open={modalVisible}
onOk={handleSubmit}
onCancel={() => setModalVisible(false)}
width={600}
>
<Form
form={form}
layout="vertical"
>
<Form.Item
name="name"
label="系统名称"
rules={[{required: true, message: '请输入系统名称'}]}
>
<Input placeholder="请输入系统名称"/>
</Form.Item>
<Form.Item
name="type"
label="系统类型"
rules={[{required: true, message: '请选择系统类型'}]}
>
<Select>
<Select.Option value={SystemType.JENKINS}>Jenkins</Select.Option>
<Select.Option value={SystemType.GIT}>Git</Select.Option>
<Select.Option value={SystemType.ZENTAO}></Select.Option>
</Select>
</Form.Item>
<Form.Item
name="url"
label="系统地址"
rules={[
{required: true, message: '请输入系统地址'},
{type: 'url', message: '请输入有效的URL'}
]}
>
<Input placeholder="请输入系统地址"/>
</Form.Item>
<Form.Item
name="authType"
label="认证方式"
rules={[{required: true, message: '请选择认证方式'}]}
>
<Select>
<Select.Option value={AuthType.BASIC}></Select.Option>
<Select.Option value={AuthType.TOKEN}></Select.Option>
<Select.Option value={AuthType.OAUTH}>OAuth2</Select.Option>
</Select>
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) => prevValues.authType !== currentValues.authType}
>
{({getFieldValue}) => {
const authType = getFieldValue('authType');
if (authType === AuthType.BASIC) {
return (
<>
<Form.Item
name="username"
label="用户名"
rules={[{required: true, message: '请输入用户名'}]}
>
<Input placeholder="请输入用户名"/>
</Form.Item>
<Form.Item
name="password"
label="密码"
rules={[{required: !editingSystem, message: '请输入密码'}]}
>
<Input.Password placeholder={editingSystem ? '不修改请留空' : '请输入密码'}/>
</Form.Item>
</>
);
}
if (authType === AuthType.TOKEN) {
return (
<Form.Item
name="token"
label="访问令牌"
rules={[{required: !editingSystem, message: '请输入访问令牌'}]}
>
<Input.Password placeholder={editingSystem ? '不修改请留空' : '请输入访问令牌'}/>
</Form.Item>
);
}
return null;
}}
</Form.Item>
<Form.Item
name="sort"
label="显示排序"
rules={[{required: true, message: '请输入显示排序'}]}
>
<InputNumber style={{width: '100%'}} min={0} placeholder="请输入显示排序"/>
</Form.Item>
<Form.Item
name="remark"
label="备注"
>
<Input.TextArea rows={4} placeholder="请输入备注"/>
</Form.Item>
<Form.Item
name="enabled"
label="是否禁用"
valuePropName="checked"
>
<Switch checkedChildren="否" unCheckedChildren="是"/>
</Form.Item>
</Form>
</Modal>
</div> </div>
<Table );
columns={columns}
dataSource={list}
rowKey="id"
loading={loading}
pagination={pagination}
onChange={handleTableChange}
/>
</Card>
<Modal
title={editingSystem ? '编辑系统' : '新增系统'}
open={modalVisible}
onOk={handleSubmit}
onCancel={() => setModalVisible(false)}
width={600}
>
<Form
form={form}
layout="vertical"
>
<Form.Item
name="name"
label="系统名称"
rules={[{ required: true, message: '请输入系统名称' }]}
>
<Input placeholder="请输入系统名称" />
</Form.Item>
<Form.Item
name="type"
label="系统类型"
rules={[{ required: true, message: '请选择系统类型' }]}
>
<Select>
<Select.Option value={SystemType.JENKINS}>Jenkins</Select.Option>
<Select.Option value={SystemType.GIT}>Git</Select.Option>
<Select.Option value={SystemType.ZENTAO}></Select.Option>
</Select>
</Form.Item>
<Form.Item
name="url"
label="系统地址"
rules={[
{ required: true, message: '请输入系统地址' },
{ type: 'url', message: '请输入有效的URL' }
]}
>
<Input placeholder="请输入系统地址" />
</Form.Item>
<Form.Item
name="authType"
label="认证方式"
rules={[{ required: true, message: '请选择认证方式' }]}
>
<Select>
<Select.Option value={AuthType.BASIC}></Select.Option>
<Select.Option value={AuthType.TOKEN}></Select.Option>
<Select.Option value={AuthType.OAUTH}>OAuth2</Select.Option>
</Select>
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) => prevValues.authType !== currentValues.authType}
>
{({ getFieldValue }) => {
const authType = getFieldValue('authType');
if (authType === AuthType.BASIC) {
return (
<>
<Form.Item
name="username"
label="用户名"
rules={[{ required: true, message: '请输入用户名' }]}
>
<Input placeholder="请输入用户名" />
</Form.Item>
<Form.Item
name="password"
label="密码"
rules={[{ required: !editingSystem, message: '请输入密码' }]}
>
<Input.Password placeholder={editingSystem ? '不修改请留空' : '请输入密码'} />
</Form.Item>
</>
);
}
if (authType === AuthType.TOKEN) {
return (
<Form.Item
name="token"
label="访问令牌"
rules={[{ required: !editingSystem, message: '请输入访问令牌' }]}
>
<Input.Password placeholder={editingSystem ? '不修改请留空' : '请输入访问令牌'} />
</Form.Item>
);
}
return null;
}}
</Form.Item>
<Form.Item
name="sort"
label="显示排序"
rules={[{ required: true, message: '请输入显示排序' }]}
>
<InputNumber style={{ width: '100%' }} min={0} placeholder="请输入显示排序" />
</Form.Item>
<Form.Item
name="remark"
label="备注"
>
<Input.TextArea rows={4} placeholder="请输入备注" />
</Form.Item>
<Form.Item
name="enabled"
label="是否禁用"
valuePropName="checked"
>
<Switch checkedChildren="否" unCheckedChildren="是" />
</Form.Item>
</Form>
</Modal>
</div>
);
}; };
export default ExternalPage; export default ExternalPage;

View File

@ -1,5 +1,4 @@
import { BaseResponse } from '@/types/base/response'; import {BaseQuery, BaseResponse} from "@/types/base";
import {BaseQuery} from "@/types/base";
// 系统类型枚举 // 系统类型枚举
export enum SystemType { export enum SystemType {
@ -41,8 +40,6 @@ export interface ExternalSystemResponse extends BaseResponse {
username?: string; username?: string;
password?: string; password?: string;
token?: string; token?: string;
syncStatus: SyncStatus;
lastSyncTime?: string;
lastConnectTime?: string; lastConnectTime?: string;
config?: string; config?: string;
} }