重构消息通知弹窗

This commit is contained in:
dengqichen 2025-11-13 19:11:02 +08:00
parent 8f8b90abb2
commit 123b9054da
10 changed files with 67 additions and 21 deletions

View File

@ -14,6 +14,7 @@ import {
applicationsConfig, applicationsConfig,
notificationChannelTypesConfig, notificationChannelTypesConfig,
notificationChannelsConfig, notificationChannelsConfig,
notificationTemplatesConfig,
usersConfig, usersConfig,
rolesConfig, rolesConfig,
departmentsConfig departmentsConfig
@ -32,6 +33,7 @@ class DataSourceRegistryImpl extends BaseRegistry<DataSourceType, DataSourceConf
this.register(DataSourceType.DOCKER_REGISTRIES, dockerRegistriesConfig); this.register(DataSourceType.DOCKER_REGISTRIES, dockerRegistriesConfig);
this.register(DataSourceType.NOTIFICATION_CHANNEL_TYPES, notificationChannelTypesConfig); this.register(DataSourceType.NOTIFICATION_CHANNEL_TYPES, notificationChannelTypesConfig);
this.register(DataSourceType.NOTIFICATION_CHANNELS, notificationChannelsConfig); this.register(DataSourceType.NOTIFICATION_CHANNELS, notificationChannelsConfig);
this.register(DataSourceType.NOTIFICATION_TEMPLATES, notificationTemplatesConfig);
this.register(DataSourceType.USERS, usersConfig); this.register(DataSourceType.USERS, usersConfig);
this.register(DataSourceType.ROLES, rolesConfig); this.register(DataSourceType.ROLES, rolesConfig);
this.register(DataSourceType.DEPARTMENTS, departmentsConfig); this.register(DataSourceType.DEPARTMENTS, departmentsConfig);

View File

@ -188,3 +188,16 @@ export const departmentsConfig: DataSourceConfig = {
} }
}; };
/**
*
*/
export const notificationTemplatesConfig: DataSourceConfig = {
url: '/api/v1/notification-template/list',
transform: (data: any[]) => {
return data.map((item: any) => ({
label: `(${item.channelType})-${item.name}`,
value: item.id
}));
}
};

View File

@ -12,6 +12,7 @@ export enum DataSourceType {
DOCKER_REGISTRIES = 'DOCKER_REGISTRIES', DOCKER_REGISTRIES = 'DOCKER_REGISTRIES',
NOTIFICATION_CHANNEL_TYPES = 'NOTIFICATION_CHANNEL_TYPES', NOTIFICATION_CHANNEL_TYPES = 'NOTIFICATION_CHANNEL_TYPES',
NOTIFICATION_CHANNELS = 'NOTIFICATION_CHANNELS', NOTIFICATION_CHANNELS = 'NOTIFICATION_CHANNELS',
NOTIFICATION_TEMPLATES = 'NOTIFICATION_TEMPLATES',
USERS = 'USERS', USERS = 'USERS',
ROLES = 'ROLES', ROLES = 'ROLES',
DEPARTMENTS = 'DEPARTMENTS', DEPARTMENTS = 'DEPARTMENTS',

View File

@ -67,9 +67,13 @@ const DeploymentFormModal: React.FC<DeploymentFormModalProps> = ({
}, },
// 通知信息(使用环境配置的通知设置) // 通知信息(使用环境配置的通知设置)
notification: { notification: {
required: environment.notificationConfig?.deployNotificationEnabled ? 'true' : 'false', notificationChannelId: environment.notificationConfig?.notificationChannelId,
channelId: environment.notificationConfig?.notificationChannelId?.toString() || '', deployNotificationEnabled: environment.notificationConfig?.deployNotificationEnabled,
channelName: environment.notificationConfig?.notificationChannelName || '', deployNotificationTemplateId: environment.notificationConfig?.deployNotificationTemplateId,
buildNotificationEnabled: environment.notificationConfig?.buildNotificationEnabled,
buildNotificationTemplateId: environment.notificationConfig?.buildNotificationTemplateId,
buildFailureFileEnabled: environment.notificationConfig?.buildFailureFileEnabled,
buildFailureNotificationTemplateId: environment.notificationConfig?.buildFailureNotificationTemplateId,
}, },
}; };
}, [app, environment, teamId]); // 只在这些依赖变化时重新生成 }, [app, environment, teamId]); // 只在这些依赖变化时重新生成

View File

@ -61,9 +61,11 @@ export interface ApplicationConfig {
export interface NotificationConfig { export interface NotificationConfig {
notificationChannelId: number; notificationChannelId: number;
deployNotificationEnabled: boolean; deployNotificationEnabled: boolean;
deployNotificationTemplateId: number;
buildNotificationEnabled: boolean; buildNotificationEnabled: boolean;
buildNotificationTemplateId: number;
buildFailureFileEnabled: boolean; buildFailureFileEnabled: boolean;
notificationChannelName: string; buildFailureNotificationTemplateId: number;
} }
export interface DeployEnvironment { export interface DeployEnvironment {

View File

@ -56,6 +56,7 @@ const formSchema = z.object({
.regex(/^[a-zA-Z][a-zA-Z0-9_]*$/, '模板编码只能包含字母、数字和下划线,且以字母开头'), .regex(/^[a-zA-Z][a-zA-Z0-9_]*$/, '模板编码只能包含字母、数字和下划线,且以字母开头'),
description: z.string().max(500, '描述不能超过500个字符').optional(), description: z.string().max(500, '描述不能超过500个字符').optional(),
channelType: z.nativeEnum(NotificationChannelType), channelType: z.nativeEnum(NotificationChannelType),
titleTemplate: z.string().min(1, '请输入标题模板'),
contentTemplate: z.string().min(1, '请输入模板内容'), contentTemplate: z.string().min(1, '请输入模板内容'),
templateConfig: z.object({ templateConfig: z.object({
// 用于后端多态反序列化,必须存在 // 用于后端多态反序列化,必须存在
@ -100,6 +101,7 @@ export const NotificationTemplateDialog: React.FC<NotificationTemplateDialogProp
mode: 'onChange', mode: 'onChange',
defaultValues: { defaultValues: {
channelType: NotificationChannelType.WEWORK, channelType: NotificationChannelType.WEWORK,
titleTemplate: '',
contentTemplate: '', contentTemplate: '',
}, },
}); });
@ -147,6 +149,7 @@ export const NotificationTemplateDialog: React.FC<NotificationTemplateDialogProp
code: '', code: '',
description: '', description: '',
channelType: NotificationChannelType.WEWORK, channelType: NotificationChannelType.WEWORK,
titleTemplate: '',
contentTemplate: '', contentTemplate: '',
templateConfig: TemplateConfigFormFactory.getDefaultConfig(NotificationChannelType.WEWORK), templateConfig: TemplateConfigFormFactory.getDefaultConfig(NotificationChannelType.WEWORK),
}); });
@ -177,6 +180,7 @@ export const NotificationTemplateDialog: React.FC<NotificationTemplateDialogProp
code: data.code, code: data.code,
description: data.description || '', description: data.description || '',
channelType: data.channelType, channelType: data.channelType,
titleTemplate: data.titleTemplate || '',
contentTemplate: data.contentTemplate, contentTemplate: data.contentTemplate,
templateConfig: cfg, templateConfig: cfg,
}); });
@ -239,6 +243,7 @@ export const NotificationTemplateDialog: React.FC<NotificationTemplateDialogProp
code: values.code, code: values.code,
description: values.description, description: values.description,
channelType: values.channelType, channelType: values.channelType,
titleTemplate: values.titleTemplate,
contentTemplate: values.contentTemplate, contentTemplate: values.contentTemplate,
enabled: currentEnabled, enabled: currentEnabled,
templateConfig: templateConfig, templateConfig: templateConfig,
@ -372,6 +377,21 @@ export const NotificationTemplateDialog: React.FC<NotificationTemplateDialogProp
</div> </div>
</div> </div>
{/* 标题模板 */}
<div className="space-y-2">
<Label htmlFor="titleTemplate">
<span className="text-destructive">*</span>
</Label>
<Input
id="titleTemplate"
placeholder="请输入标题模板支持FreeMarker语法"
{...register('titleTemplate')}
/>
{errors.titleTemplate && (
<p className="text-sm text-destructive">{errors.titleTemplate.message}</p>
)}
</div>
{/* 模板配置 */} {/* 模板配置 */}
{watchedChannelType && ( {watchedChannelType && (
<div className="space-y-2"> <div className="space-y-2">

View File

@ -356,6 +356,7 @@ const NotificationTemplateList: React.FC = () => {
<TableRow> <TableRow>
<TableHead></TableHead> <TableHead></TableHead>
<TableHead></TableHead> <TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead> <TableHead></TableHead>
<TableHead></TableHead> <TableHead></TableHead>
<TableHead></TableHead> <TableHead></TableHead>
@ -365,13 +366,13 @@ const NotificationTemplateList: React.FC = () => {
<TableBody> <TableBody>
{loading ? ( {loading ? (
<TableRow> <TableRow>
<TableCell colSpan={6} className="h-32 text-center"> <TableCell colSpan={7} className="h-32 text-center">
<Loader2 className="h-8 w-8 animate-spin mx-auto" /> <Loader2 className="h-8 w-8 animate-spin mx-auto" />
</TableCell> </TableCell>
</TableRow> </TableRow>
) : list.length === 0 ? ( ) : list.length === 0 ? (
<TableRow> <TableRow>
<TableCell colSpan={6} className="h-32 text-center text-muted-foreground"> <TableCell colSpan={7} className="h-32 text-center text-muted-foreground">
</TableCell> </TableCell>
</TableRow> </TableRow>
@ -380,6 +381,9 @@ const NotificationTemplateList: React.FC = () => {
<TableRow key={template.id}> <TableRow key={template.id}>
<TableCell className="font-medium">{template.name}</TableCell> <TableCell className="font-medium">{template.name}</TableCell>
<TableCell>{template.code}</TableCell> <TableCell>{template.code}</TableCell>
<TableCell className="max-w-xs truncate" title={template.titleTemplate}>
{template.titleTemplate || '-'}
</TableCell>
<TableCell> <TableCell>
<Badge variant="outline"> <Badge variant="outline">
{getChannelTypeLabel(template.channelType)} {getChannelTypeLabel(template.channelType)}

View File

@ -44,7 +44,7 @@ export const getTemplateById = async (id: number): Promise<NotificationTemplateD
* *
*/ */
export const createTemplate = async ( export const createTemplate = async (
template: Pick<NotificationTemplateDTO, 'name' | 'code' | 'description' | 'channelType' | 'contentTemplate' | 'templateConfig'> template: Pick<NotificationTemplateDTO, 'name' | 'code' | 'description' | 'channelType' | 'titleTemplate' | 'contentTemplate' | 'templateConfig'>
): Promise<NotificationTemplateDTO> => { ): Promise<NotificationTemplateDTO> => {
// 确保 channelType 在 templateConfig 之前 // 确保 channelType 在 templateConfig 之前
const payload = { const payload = {
@ -52,6 +52,7 @@ export const createTemplate = async (
code: template.code, code: template.code,
description: template.description, description: template.description,
channelType: template.channelType, channelType: template.channelType,
titleTemplate: template.titleTemplate,
contentTemplate: template.contentTemplate, contentTemplate: template.contentTemplate,
templateConfig: template.templateConfig, templateConfig: template.templateConfig,
enabled: true, // 默认启用 enabled: true, // 默认启用
@ -64,7 +65,7 @@ export const createTemplate = async (
*/ */
export const updateTemplate = async ( export const updateTemplate = async (
id: number, id: number,
template: Pick<NotificationTemplateDTO, 'id' | 'name' | 'code' | 'description' | 'channelType' | 'contentTemplate' | 'templateConfig' | 'enabled'> template: Pick<NotificationTemplateDTO, 'id' | 'name' | 'code' | 'description' | 'channelType' | 'titleTemplate' | 'contentTemplate' | 'templateConfig' | 'enabled'>
): Promise<NotificationTemplateDTO> => { ): Promise<NotificationTemplateDTO> => {
// 确保字段顺序channelType 必须在 templateConfig 之前 // 确保字段顺序channelType 必须在 templateConfig 之前
const payload = { const payload = {
@ -73,6 +74,7 @@ export const updateTemplate = async (
code: template.code, code: template.code,
description: template.description, description: template.description,
channelType: template.channelType, channelType: template.channelType,
titleTemplate: template.titleTemplate,
contentTemplate: template.contentTemplate, contentTemplate: template.contentTemplate,
enabled: template.enabled, enabled: template.enabled,
templateConfig: template.templateConfig, templateConfig: template.templateConfig,

View File

@ -20,6 +20,9 @@ export interface NotificationTemplateDTO extends BaseResponse {
/** 渠道类型 */ /** 渠道类型 */
channelType: NotificationChannelType; channelType: NotificationChannelType;
/** 标题模板FreeMarker格式 */
titleTemplate: string;
/** 内容模板FreeMarker格式 */ /** 内容模板FreeMarker格式 */
contentTemplate: string; contentTemplate: string;

View File

@ -71,21 +71,16 @@ export const NotificationNodeDefinition: ConfigurableNodeDefinition = {
"x-dataSource": DataSourceType.NOTIFICATION_CHANNELS, "x-dataSource": DataSourceType.NOTIFICATION_CHANNELS,
"x-allow-variable": true "x-allow-variable": true
}, },
title: { notificationTemplateId: {
type: "string", type: "number",
title: "通知标题", title: "通知模板",
description: "通知消息的标题", description: "选择通知模板,或输入动态值",
default: "${notification.title}" "x-dataSource": DataSourceType.NOTIFICATION_TEMPLATES,
}, "x-allow-variable": true,
content: { "x-depends-on": "channelId"
type: "string",
title: "通知内容",
description: "通知消息的正文内容,支持变量表达式",
format: "textarea",
default: "${notification.context}"
} }
}, },
required: ["channelId", "title", "content"] required: ["channelId", "notificationTemplateId"]
}, },
outputs: defineNodeOutputs() outputs: defineNodeOutputs()
}; };