From 62523bc38297b1ab29f4db8972b5461e26ed54d6 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Tue, 11 Nov 2025 18:08:04 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=89=E6=96=B9=E7=B3=BB=E7=BB=9F=E5=AF=86?= =?UTF-8?q?=E7=A0=81=E5=8A=A0=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/NotificationConfigDialog.tsx | 390 ++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 frontend/src/pages/Deploy/Team/List/components/NotificationConfigDialog.tsx diff --git a/frontend/src/pages/Deploy/Team/List/components/NotificationConfigDialog.tsx b/frontend/src/pages/Deploy/Team/List/components/NotificationConfigDialog.tsx new file mode 100644 index 00000000..28f9640d --- /dev/null +++ b/frontend/src/pages/Deploy/Team/List/components/NotificationConfigDialog.tsx @@ -0,0 +1,390 @@ +import React, { useEffect, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import * as z from 'zod'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, + DialogBody, + DialogDescription, +} from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Switch } from '@/components/ui/switch'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { useToast } from '@/components/ui/use-toast'; +import { Loader2 } from 'lucide-react'; +import { + getTeamEnvironmentConfig, + updateTeamEnvironmentConfig, + getNotificationChannels, +} from '../service'; + +// 表单验证 Schema +const formSchema = z + .object({ + notificationConfig: z + .object({ + id: z.number().optional(), + notificationChannelId: z.number().nullish(), + deployNotificationEnabled: z.boolean().default(false), + buildNotificationEnabled: z.boolean().default(false), + buildFailureFileEnabled: z.boolean().default(false), + }) + .optional(), + }) + .refine( + (data) => { + // 如果启用了部署通知或构建通知,则通知渠道必填 + if ( + (data.notificationConfig?.deployNotificationEnabled ||data.notificationConfig?.buildNotificationEnabled) && !data.notificationConfig?.notificationChannelId + ) { + return false; + } + return true; + }, + { + message: '启用通知时必须选择通知渠道', + path: ['notificationConfig', 'notificationChannelId'], + } + ); + +type FormData = z.infer; + +interface NotificationChannel { + id: number; + name: string; + type: string; +} + +interface NotificationConfigDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + teamId: number; + environmentId: number; + environmentName: string; + onSuccess?: () => void; +} + +export const NotificationConfigDialog: React.FC< + NotificationConfigDialogProps +> = ({ open, onOpenChange, teamId, environmentId, environmentName, onSuccess }) => { + const { toast } = useToast(); + const [submitting, setSubmitting] = useState(false); + const [loading, setLoading] = useState(false); + const [configId, setConfigId] = useState(null); + const [currentConfig, setCurrentConfig] = useState(null); // 存储完整的环境配置 + const [notificationChannels, setNotificationChannels] = useState< + NotificationChannel[] + >([]); + const [loadingChannels, setLoadingChannels] = useState(false); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + notificationConfig: { + notificationChannelId: undefined, + deployNotificationEnabled: false, + buildNotificationEnabled: false, + buildFailureFileEnabled: false + }, + }, + }); + + // 加载通知渠道列表 + const loadNotificationChannels = async () => { + setLoadingChannels(true); + try { + const channels = await getNotificationChannels(); + setNotificationChannels(channels || []); + } catch (error: any) { + toast({ + title: '加载失败', + description: error.message || '无法加载通知渠道列表', + variant: 'destructive', + }); + } finally { + setLoadingChannels(false); + } + }; + + // 加载环境配置 + const loadEnvironmentConfig = async () => { + setLoading(true); + try { + const config = await getTeamEnvironmentConfig(teamId, environmentId); + + if (config) { + setConfigId(config.id); + setCurrentConfig(config); // 保存完整配置 + form.reset({ + notificationConfig: config.notificationConfig || { + notificationChannelId: undefined, + deployNotificationEnabled: false, + buildNotificationEnabled: false, + }, + }); + } + } catch (error: any) { + if (error.response?.status === 404) { + // 配置不存在 + toast({ + title: '提示', + description: '请先配置该环境的基本信息', + variant: 'destructive', + }); + onOpenChange(false); + } else { + toast({ + title: '加载失败', + description: error.message || '无法加载环境配置', + variant: 'destructive', + }); + } + } finally { + setLoading(false); + } + }; + + // 对话框打开时初始化 + useEffect(() => { + if (open) { + loadNotificationChannels(); + loadEnvironmentConfig(); + } + }, [open, teamId, environmentId]); + + const handleSubmit = async (data: FormData) => { + if (!configId || !currentConfig) { + toast({ + title: '错误', + description: '请先配置该环境的基本信息', + variant: 'destructive', + }); + return; + } + + setSubmitting(true); + try { + // 提交完整的环境配置,只更新通知部分 + const payload: any = { + teamId: currentConfig.teamId, + environmentId: currentConfig.environmentId, + approvalRequired: currentConfig.approvalRequired, + approverUserIds: currentConfig.approverUserIds || [], + requireCodeReview: currentConfig.requireCodeReview, + remark: currentConfig.remark, + // 更新通知配置 + notificationConfig: + data.notificationConfig && + (data.notificationConfig.deployNotificationEnabled || + data.notificationConfig.buildNotificationEnabled || + data.notificationConfig.buildFailureFileEnabled) + ? { + id: data.notificationConfig.id, + notificationChannelId: + data.notificationConfig.notificationChannelId, + deployNotificationEnabled: + data.notificationConfig.deployNotificationEnabled, + buildNotificationEnabled: + data.notificationConfig.buildNotificationEnabled, + buildFailureFileEnabled: + data.notificationConfig.buildFailureFileEnabled, + } + : undefined, + }; + + await updateTeamEnvironmentConfig(configId, payload); + + toast({ + title: '保存成功', + description: '通知配置已更新', + }); + + onSuccess?.(); + onOpenChange(false); + } catch (error: any) { + toast({ + title: '保存失败', + description: error.message || '无法保存通知配置', + variant: 'destructive', + }); + } finally { + setSubmitting(false); + } + }; + + return ( + + + + 通知配置 + + 配置 {environmentName} 环境的通知设置 + + + + {loading ? ( + +
+ +
+
+ ) : ( +
+ + + {/* 通知渠道选择 - 始终显示 */} + ( + + 通知渠道 + + +
+ 选择通知渠道后,启用下方的通知类型 +
+
+ )} + /> + + {/* 部署通知 */} + ( + +
+ 部署通知 +
+ 部署状态变更时发送通知 +
+
+ + + +
+ )} + /> + + {/* 构建通知 */} + ( + +
+ 构建通知 +
+ 构建状态变更时发送通知 +
+
+ + + +
+ )} + /> + + {/* 构建通知 */} + ( + +
+ 失败日志 +
+ 构建失败时是否发送日志 +
+
+ + + +
+ )} + /> +
+ + + + + +
+ + )} +
+
+ ); +};