重构消息通知弹窗

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,
notificationChannelTypesConfig,
notificationChannelsConfig,
notificationTemplatesConfig,
usersConfig,
rolesConfig,
departmentsConfig
@ -32,6 +33,7 @@ class DataSourceRegistryImpl extends BaseRegistry<DataSourceType, DataSourceConf
this.register(DataSourceType.DOCKER_REGISTRIES, dockerRegistriesConfig);
this.register(DataSourceType.NOTIFICATION_CHANNEL_TYPES, notificationChannelTypesConfig);
this.register(DataSourceType.NOTIFICATION_CHANNELS, notificationChannelsConfig);
this.register(DataSourceType.NOTIFICATION_TEMPLATES, notificationTemplatesConfig);
this.register(DataSourceType.USERS, usersConfig);
this.register(DataSourceType.ROLES, rolesConfig);
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',
NOTIFICATION_CHANNEL_TYPES = 'NOTIFICATION_CHANNEL_TYPES',
NOTIFICATION_CHANNELS = 'NOTIFICATION_CHANNELS',
NOTIFICATION_TEMPLATES = 'NOTIFICATION_TEMPLATES',
USERS = 'USERS',
ROLES = 'ROLES',
DEPARTMENTS = 'DEPARTMENTS',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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