优化代码
This commit is contained in:
1 parent
4d904fd185
commit
cbce63db8a
3 files changed
+666
No files matched your search
@@ -0,0 +1,26 @@
|
|||||||
|
import { get, post, put } from './request';
|
||||||
|
import type { NotificationChannel } from '../types';
|
||||||
|
|
||||||
|
const PROPERTY_ID_NOTIFICATION_CHANNELS = 'notification_channels';
|
||||||
|
|
||||||
|
// 获取通知渠道列表
|
||||||
|
export const getNotificationChannels = async (): Promise<NotificationChannel[]> => {
|
||||||
|
const response = await get<{ id: string; name: string; value: NotificationChannel[] }>(
|
||||||
|
`/admin/properties/${PROPERTY_ID_NOTIFICATION_CHANNELS}`
|
||||||
|
);
|
||||||
|
return response.data.value || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存通知渠道列表
|
||||||
|
export const saveNotificationChannels = async (channels: NotificationChannel[]): Promise<void> => {
|
||||||
|
await put(`/admin/properties/${PROPERTY_ID_NOTIFICATION_CHANNELS}`, {
|
||||||
|
name: '通知渠道配置',
|
||||||
|
value: channels,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 测试通知渠道(从数据库读取配置)
|
||||||
|
export const testNotificationChannel = async (type: string): Promise<{ message: string }> => {
|
||||||
|
const response = await post<{ message: string }>(`/admin/notification-channels/${type}/test`);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
@@ -0,0 +1,344 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Button, Table, Space, App, Modal, Form, Input, Switch, Tag, Select } from 'antd';
|
||||||
|
import { Plus, Pencil, Trash2, TestTube } from 'lucide-react';
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import type { NotificationChannel } from '../../types';
|
||||||
|
import {
|
||||||
|
getNotificationChannels,
|
||||||
|
saveNotificationChannels,
|
||||||
|
testNotificationChannel,
|
||||||
|
} from '../../api/notification-channel';
|
||||||
|
import { getErrorMessage } from '../../lib/utils';
|
||||||
|
|
||||||
|
// 支持的渠道类型
|
||||||
|
const CHANNEL_TYPES = [
|
||||||
|
{ label: '钉钉', value: 'dingtalk' },
|
||||||
|
{ label: '企业微信', value: 'wecom' },
|
||||||
|
{ label: '飞书', value: 'feishu' },
|
||||||
|
{ label: '自定义Webhook', value: 'webhook' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const NotificationChannels = () => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const { message: messageApi, modal: modalApi } = App.useApp();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const [isSelectModalOpen, setIsSelectModalOpen] = useState(false);
|
||||||
|
const [editingType, setEditingType] = useState<string | null>(null);
|
||||||
|
const [selectedType, setSelectedType] = useState<string>('');
|
||||||
|
|
||||||
|
// 获取通知渠道列表
|
||||||
|
const { data: channels = [], isLoading } = useQuery({
|
||||||
|
queryKey: ['notificationChannels'],
|
||||||
|
queryFn: getNotificationChannels,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 保存 mutation
|
||||||
|
const saveMutation = useMutation({
|
||||||
|
mutationFn: saveNotificationChannels,
|
||||||
|
onSuccess: () => {
|
||||||
|
messageApi.success('保存成功');
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['notificationChannels'] });
|
||||||
|
setIsModalOpen(false);
|
||||||
|
setEditingType(null);
|
||||||
|
form.resetFields();
|
||||||
|
},
|
||||||
|
onError: (error: unknown) => {
|
||||||
|
messageApi.error(getErrorMessage(error, '保存失败'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试 mutation
|
||||||
|
const testMutation = useMutation({
|
||||||
|
mutationFn: testNotificationChannel,
|
||||||
|
onSuccess: () => {
|
||||||
|
messageApi.success('测试通知已发送');
|
||||||
|
},
|
||||||
|
onError: (error: unknown) => {
|
||||||
|
messageApi.error(getErrorMessage(error, '测试失败'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
setIsSelectModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectType = () => {
|
||||||
|
if (!selectedType) {
|
||||||
|
messageApi.warning('请选择渠道类型');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已存在
|
||||||
|
const exists = channels.find((ch) => ch.type === selectedType);
|
||||||
|
if (exists) {
|
||||||
|
messageApi.warning('该渠道类型已存在,请直接编辑');
|
||||||
|
setIsSelectModalOpen(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEditingType(selectedType);
|
||||||
|
form.setFieldsValue({
|
||||||
|
type: selectedType,
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
|
setIsSelectModalOpen(false);
|
||||||
|
setIsModalOpen(true);
|
||||||
|
setSelectedType('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (record: NotificationChannel) => {
|
||||||
|
setEditingType(record.type);
|
||||||
|
// 将配置展开到表单字段
|
||||||
|
form.setFieldsValue({
|
||||||
|
type: record.type,
|
||||||
|
enabled: record.enabled,
|
||||||
|
...record.config, // 展开配置对象
|
||||||
|
});
|
||||||
|
setIsModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (type: string) => {
|
||||||
|
modalApi.confirm({
|
||||||
|
title: '确认删除',
|
||||||
|
content: '确定要删除这个通知渠道配置吗?',
|
||||||
|
onOk: () => {
|
||||||
|
// 从列表中删除该类型的渠道
|
||||||
|
const newChannels = channels.filter((ch) => ch.type !== type);
|
||||||
|
saveMutation.mutate(newChannels);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTest = (type: string) => {
|
||||||
|
testMutation.mutate(type);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
const { type, enabled, ...configFields } = values;
|
||||||
|
|
||||||
|
// 构建配置对象
|
||||||
|
const config: Record<string, any> = {};
|
||||||
|
Object.keys(configFields).forEach((key) => {
|
||||||
|
if (configFields[key] !== undefined && configFields[key] !== '') {
|
||||||
|
config[key] = configFields[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const newChannel: NotificationChannel = {
|
||||||
|
type,
|
||||||
|
enabled,
|
||||||
|
config,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新或添加渠道
|
||||||
|
const existingIndex = channels.findIndex((ch) => ch.type === type);
|
||||||
|
let newChannels: NotificationChannel[];
|
||||||
|
if (existingIndex >= 0) {
|
||||||
|
// 更新现有渠道
|
||||||
|
newChannels = [...channels];
|
||||||
|
newChannels[existingIndex] = newChannel;
|
||||||
|
} else {
|
||||||
|
// 添加新渠道
|
||||||
|
newChannels = [...channels, newChannel];
|
||||||
|
}
|
||||||
|
|
||||||
|
saveMutation.mutate(newChannels);
|
||||||
|
} catch (error) {
|
||||||
|
// 表单验证失败
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTypeLabel = (type: string) => {
|
||||||
|
const types = {
|
||||||
|
dingtalk: '钉钉',
|
||||||
|
wecom: '企业微信',
|
||||||
|
feishu: '飞书',
|
||||||
|
email: '邮件',
|
||||||
|
webhook: '自定义Webhook',
|
||||||
|
};
|
||||||
|
return types[type as keyof typeof types] || type;
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '渠道类型',
|
||||||
|
dataIndex: 'type',
|
||||||
|
key: 'type',
|
||||||
|
render: (type: string) => getTypeLabel(type),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'enabled',
|
||||||
|
key: 'enabled',
|
||||||
|
render: (enabled: boolean) =>
|
||||||
|
enabled ? <Tag color="success">启用</Tag> : <Tag>禁用</Tag>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
render: (_: unknown, record: NotificationChannel) => (
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
icon={<TestTube size={14} />}
|
||||||
|
onClick={() => handleTest(record.type)}
|
||||||
|
loading={testMutation.isPending}
|
||||||
|
disabled={!record.enabled}
|
||||||
|
>
|
||||||
|
测试
|
||||||
|
</Button>
|
||||||
|
<Button type="link" size="small" icon={<Pencil size={14} />} onClick={() => handleEdit(record)}>
|
||||||
|
配置
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
danger
|
||||||
|
icon={<Trash2 size={14} />}
|
||||||
|
onClick={() => handleDelete(record.type)}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 获取未配置的渠道类型
|
||||||
|
const availableTypes = CHANNEL_TYPES.filter((type) => !channels.find((ch) => ch.type === type.value));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="mb-4 flex justify-between items-start">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-bold">通知渠道管理</h2>
|
||||||
|
<p className="text-gray-500 mt-2">配置钉钉、企业微信、飞书和自定义Webhook通知渠道</p>
|
||||||
|
</div>
|
||||||
|
<Button type="primary" icon={<Plus size={16} />} onClick={handleAdd} disabled={availableTypes.length === 0}>
|
||||||
|
添加渠道
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table columns={columns} dataSource={channels} rowKey="type" loading={isLoading} />
|
||||||
|
|
||||||
|
{/* 选择渠道类型对话框 */}
|
||||||
|
<Modal
|
||||||
|
title="选择通知渠道类型"
|
||||||
|
open={isSelectModalOpen}
|
||||||
|
onOk={handleSelectType}
|
||||||
|
onCancel={() => {
|
||||||
|
setIsSelectModalOpen(false);
|
||||||
|
setSelectedType('');
|
||||||
|
}}
|
||||||
|
width={400}
|
||||||
|
>
|
||||||
|
<Form layout="vertical">
|
||||||
|
<Form.Item label="渠道类型" required>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择要添加的渠道类型"
|
||||||
|
value={selectedType || undefined}
|
||||||
|
onChange={setSelectedType}
|
||||||
|
options={availableTypes}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
title={`配置${getTypeLabel(editingType || '')}`}
|
||||||
|
open={isModalOpen}
|
||||||
|
onOk={handleSubmit}
|
||||||
|
onCancel={() => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
setEditingType(null);
|
||||||
|
form.resetFields();
|
||||||
|
}}
|
||||||
|
confirmLoading={saveMutation.isPending}
|
||||||
|
width={600}
|
||||||
|
>
|
||||||
|
<Form form={form} layout="vertical">
|
||||||
|
<Form.Item name="type" hidden>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label="启用" name="enabled" valuePropName="checked" initialValue={true}>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item noStyle shouldUpdate={(prev, curr) => prev.type !== curr.type}>
|
||||||
|
{({ getFieldValue }) => {
|
||||||
|
const type = getFieldValue('type');
|
||||||
|
if (type === 'dingtalk') {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
label="访问令牌 (Access Token)"
|
||||||
|
name="secretKey"
|
||||||
|
rules={[{ required: true, message: '请输入访问令牌' }]}
|
||||||
|
tooltip="在钉钉机器人配置中获取的 access_token"
|
||||||
|
>
|
||||||
|
<Input placeholder="输入访问令牌" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="加签密钥(可选)" name="signSecret" tooltip="如果启用了加签,请填写 SEC 开头的密钥">
|
||||||
|
<Input.Password placeholder="SEC 开头的加签密钥" />
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (type === 'wecom') {
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
label="Webhook Key"
|
||||||
|
name="secretKey"
|
||||||
|
rules={[{ required: true, message: '请输入 Webhook Key' }]}
|
||||||
|
tooltip="企业微信群机器人的 Webhook Key"
|
||||||
|
>
|
||||||
|
<Input placeholder="输入 Webhook Key" />
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (type === 'feishu') {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
label="Webhook Token"
|
||||||
|
name="secretKey"
|
||||||
|
rules={[{ required: true, message: '请输入 Webhook Token' }]}
|
||||||
|
tooltip="飞书群机器人的 Webhook Token"
|
||||||
|
>
|
||||||
|
<Input placeholder="输入 Webhook Token" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="签名密钥(可选)" name="signSecret" tooltip="如果启用了签名验证,请填写密钥">
|
||||||
|
<Input.Password placeholder="输入签名密钥" />
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (type === 'webhook') {
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
label="Webhook URL"
|
||||||
|
name="url"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: '请输入 Webhook URL' },
|
||||||
|
{ type: 'url', message: '请输入有效的 URL' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="https://your-server.com/webhook" />
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotificationChannels;
|
||||||
@@ -0,0 +1,296 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Button, Table, Space, App, Modal, Form, Input, Switch, Select, Tag } from 'antd';
|
||||||
|
import { Plus, Pencil, Trash2, TestTube } from 'lucide-react';
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import type { NotificationChannel } from '../../types';
|
||||||
|
import {
|
||||||
|
getNotificationChannels,
|
||||||
|
createNotificationChannel,
|
||||||
|
updateNotificationChannel,
|
||||||
|
deleteNotificationChannel,
|
||||||
|
testNotificationChannel,
|
||||||
|
} from '../../api/notification-channel';
|
||||||
|
import { getErrorMessage } from '../../lib/utils';
|
||||||
|
|
||||||
|
const NotificationChannels = () => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const { message: messageApi, modal: modalApi } = App.useApp();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const [editingChannel, setEditingChannel] = useState<NotificationChannel | null>(null);
|
||||||
|
|
||||||
|
// 获取通知渠道列表
|
||||||
|
const { data: channels = [], isLoading } = useQuery({
|
||||||
|
queryKey: ['notificationChannels'],
|
||||||
|
queryFn: getNotificationChannels,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建 mutation
|
||||||
|
const createMutation = useMutation({
|
||||||
|
mutationFn: createNotificationChannel,
|
||||||
|
onSuccess: () => {
|
||||||
|
messageApi.success('创建成功');
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['notificationChannels'] });
|
||||||
|
setIsModalOpen(false);
|
||||||
|
form.resetFields();
|
||||||
|
},
|
||||||
|
onError: (error: unknown) => {
|
||||||
|
messageApi.error(getErrorMessage(error, '创建失败'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新 mutation
|
||||||
|
const updateMutation = useMutation({
|
||||||
|
mutationFn: ({ id, channel }: { id: string; channel: NotificationChannel }) =>
|
||||||
|
updateNotificationChannel(id, channel),
|
||||||
|
onSuccess: () => {
|
||||||
|
messageApi.success('更新成功');
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['notificationChannels'] });
|
||||||
|
setIsModalOpen(false);
|
||||||
|
setEditingChannel(null);
|
||||||
|
form.resetFields();
|
||||||
|
},
|
||||||
|
onError: (error: unknown) => {
|
||||||
|
messageApi.error(getErrorMessage(error, '更新失败'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 删除 mutation
|
||||||
|
const deleteMutation = useMutation({
|
||||||
|
mutationFn: deleteNotificationChannel,
|
||||||
|
onSuccess: () => {
|
||||||
|
messageApi.success('删除成功');
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['notificationChannels'] });
|
||||||
|
},
|
||||||
|
onError: (error: unknown) => {
|
||||||
|
messageApi.error(getErrorMessage(error, '删除失败'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试 mutation
|
||||||
|
const testMutation = useMutation({
|
||||||
|
mutationFn: testNotificationChannel,
|
||||||
|
onSuccess: () => {
|
||||||
|
messageApi.success('测试通知已发送');
|
||||||
|
},
|
||||||
|
onError: (error: unknown) => {
|
||||||
|
messageApi.error(getErrorMessage(error, '测试失败'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleCreate = () => {
|
||||||
|
setEditingChannel(null);
|
||||||
|
form.resetFields();
|
||||||
|
setIsModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (record: NotificationChannel) => {
|
||||||
|
setEditingChannel(record);
|
||||||
|
form.setFieldsValue(record);
|
||||||
|
setIsModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (id: string) => {
|
||||||
|
modalApi.confirm({
|
||||||
|
title: '确认删除',
|
||||||
|
content: '确定要删除这个通知渠道吗?',
|
||||||
|
onOk: () => deleteMutation.mutate(id),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTest = (id: string) => {
|
||||||
|
testMutation.mutate(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
if (editingChannel?.id) {
|
||||||
|
updateMutation.mutate({ id: editingChannel.id, channel: values });
|
||||||
|
} else {
|
||||||
|
createMutation.mutate(values);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 表单验证失败
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTypeLabel = (type: string) => {
|
||||||
|
const types = {
|
||||||
|
dingtalk: '钉钉',
|
||||||
|
wecom: '企业微信',
|
||||||
|
feishu: '飞书',
|
||||||
|
email: '邮件',
|
||||||
|
webhook: '自定义Webhook',
|
||||||
|
};
|
||||||
|
return types[type as keyof typeof types] || type;
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '类型',
|
||||||
|
dataIndex: 'type',
|
||||||
|
key: 'type',
|
||||||
|
render: (type: string) => getTypeLabel(type),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'enabled',
|
||||||
|
key: 'enabled',
|
||||||
|
render: (enabled: boolean) =>
|
||||||
|
enabled ? <Tag color="success">启用</Tag> : <Tag>禁用</Tag>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
render: (_: unknown, record: NotificationChannel) => (
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
icon={<TestTube size={14} />}
|
||||||
|
onClick={() => handleTest(record.id!)}
|
||||||
|
loading={testMutation.isPending}
|
||||||
|
>
|
||||||
|
测试
|
||||||
|
</Button>
|
||||||
|
<Button type="link" size="small" icon={<Pencil size={14} />} onClick={() => handleEdit(record)}>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
danger
|
||||||
|
icon={<Trash2 size={14} />}
|
||||||
|
onClick={() => handleDelete(record.id!)}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="mb-4 flex justify-between items-center">
|
||||||
|
<h2 className="text-xl font-bold">通知渠道管理</h2>
|
||||||
|
<Button type="primary" icon={<Plus size={16} />} onClick={handleCreate}>
|
||||||
|
创建通知渠道
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table columns={columns} dataSource={channels} rowKey="id" loading={isLoading} />
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
title={editingChannel ? '编辑通知渠道' : '创建通知渠道'}
|
||||||
|
open={isModalOpen}
|
||||||
|
onOk={handleSubmit}
|
||||||
|
onCancel={() => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
setEditingChannel(null);
|
||||||
|
form.resetFields();
|
||||||
|
}}
|
||||||
|
confirmLoading={createMutation.isPending || updateMutation.isPending}
|
||||||
|
width={600}
|
||||||
|
>
|
||||||
|
<Form form={form} layout="vertical">
|
||||||
|
<Form.Item label="渠道名称" name="name" rules={[{ required: true, message: '请输入渠道名称' }]}>
|
||||||
|
<Input placeholder="例如:运维群通知" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label="渠道类型" name="type" rules={[{ required: true, message: '请选择渠道类型' }]}>
|
||||||
|
<Select
|
||||||
|
placeholder="选择通知渠道类型"
|
||||||
|
options={[
|
||||||
|
{ label: '钉钉', value: 'dingtalk' },
|
||||||
|
{ label: '企业微信', value: 'wecom' },
|
||||||
|
{ label: '飞书', value: 'feishu' },
|
||||||
|
{ label: '自定义Webhook', value: 'webhook' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label="启用" name="enabled" valuePropName="checked" initialValue={true}>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item noStyle shouldUpdate={(prev, curr) => prev.type !== curr.type}>
|
||||||
|
{({ getFieldValue }) => {
|
||||||
|
const type = getFieldValue('type');
|
||||||
|
if (type === 'dingtalk') {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
label="Webhook URL"
|
||||||
|
name="dingTalkWebhook"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: '请输入钉钉 Webhook URL' },
|
||||||
|
{ type: 'url', message: '请输入有效的 URL' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="https://oapi.dingtalk.com/robot/send?access_token=..." />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="加签密钥(可选)" name="dingTalkSecret">
|
||||||
|
<Input.Password placeholder="SEC 开头的加签密钥" />
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (type === 'wecom') {
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
label="Webhook URL"
|
||||||
|
name="weComWebhook"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: '请输入企业微信 Webhook URL' },
|
||||||
|
{ type: 'url', message: '请输入有效的 URL' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..." />
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (type === 'feishu') {
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
label="Webhook URL"
|
||||||
|
name="feishuWebhook"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: '请输入飞书 Webhook URL' },
|
||||||
|
{ type: 'url', message: '请输入有效的 URL' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="https://open.feishu.cn/open-apis/bot/v2/hook/..." />
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (type === 'webhook') {
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
label="Webhook URL"
|
||||||
|
name="customWebhookUrl"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: '请输入自定义 Webhook URL' },
|
||||||
|
{ type: 'url', message: '请输入有效的 URL' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder="https://your-server.com/webhook" />
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotificationChannels;
|
||||||
Reference in new issue
Block a user