优化代码
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