重构消息通知弹窗
This commit is contained in:
parent
8647997e63
commit
79b6f4bade
@ -27,6 +27,7 @@ import { Textarea } from '@/components/ui/textarea';
|
|||||||
import { useToast } from '@/components/ui/use-toast';
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
import { Loader2, Eye, Code } from 'lucide-react';
|
import { Loader2, Eye, Code } from 'lucide-react';
|
||||||
import { NotificationChannelType } from '../../../NotificationChannel/List/types';
|
import { NotificationChannelType } from '../../../NotificationChannel/List/types';
|
||||||
|
import { TemplateConfigFormFactory } from './template-config-forms/TemplateConfigFormFactory';
|
||||||
import type {
|
import type {
|
||||||
NotificationTemplateDTO,
|
NotificationTemplateDTO,
|
||||||
ChannelTypeOption,
|
ChannelTypeOption,
|
||||||
@ -58,6 +59,13 @@ const formSchema = z.object({
|
|||||||
description: z.string().max(500, '描述不能超过500个字符').optional(),
|
description: z.string().max(500, '描述不能超过500个字符').optional(),
|
||||||
channelType: z.nativeEnum(NotificationChannelType),
|
channelType: z.nativeEnum(NotificationChannelType),
|
||||||
contentTemplate: z.string().min(1, '请输入模板内容'),
|
contentTemplate: z.string().min(1, '请输入模板内容'),
|
||||||
|
templateConfig: z.object({
|
||||||
|
// 企业微信配置
|
||||||
|
messageType: z.enum(['TEXT', 'MARKDOWN', 'FILE']).optional(),
|
||||||
|
// 邮件配置
|
||||||
|
contentType: z.enum(['TEXT', 'HTML']).optional(),
|
||||||
|
priority: z.enum(['LOW', 'NORMAL', 'HIGH']).optional(),
|
||||||
|
}).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type FormValues = z.infer<typeof formSchema>;
|
type FormValues = z.infer<typeof formSchema>;
|
||||||
@ -165,6 +173,7 @@ export const NotificationTemplateDialog: React.FC<NotificationTemplateDialogProp
|
|||||||
description: '',
|
description: '',
|
||||||
channelType: NotificationChannelType.WEWORK,
|
channelType: NotificationChannelType.WEWORK,
|
||||||
contentTemplate: '',
|
contentTemplate: '',
|
||||||
|
templateConfig: TemplateConfigFormFactory.getDefaultConfig(NotificationChannelType.WEWORK),
|
||||||
});
|
});
|
||||||
setShowPreview(false);
|
setShowPreview(false);
|
||||||
}
|
}
|
||||||
@ -182,6 +191,7 @@ export const NotificationTemplateDialog: React.FC<NotificationTemplateDialogProp
|
|||||||
description: data.description || '',
|
description: data.description || '',
|
||||||
channelType: data.channelType,
|
channelType: data.channelType,
|
||||||
contentTemplate: data.contentTemplate,
|
contentTemplate: data.contentTemplate,
|
||||||
|
templateConfig: data.templateConfig || TemplateConfigFormFactory.getDefaultConfig(data.channelType),
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast({
|
toast({
|
||||||
@ -204,6 +214,7 @@ export const NotificationTemplateDialog: React.FC<NotificationTemplateDialogProp
|
|||||||
description: values.description,
|
description: values.description,
|
||||||
channelType: values.channelType,
|
channelType: values.channelType,
|
||||||
contentTemplate: values.contentTemplate,
|
contentTemplate: values.contentTemplate,
|
||||||
|
templateConfig: values.templateConfig || TemplateConfigFormFactory.getDefaultConfig(values.channelType),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mode === 'edit' && editId) {
|
if (mode === 'edit' && editId) {
|
||||||
@ -332,6 +343,25 @@ export const NotificationTemplateDialog: React.FC<NotificationTemplateDialogProp
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 模板配置 */}
|
||||||
|
{watchedChannelType && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>模板配置</Label>
|
||||||
|
<div className="border rounded-lg p-4 bg-muted/30">
|
||||||
|
{TemplateConfigFormFactory.createConfigForm(
|
||||||
|
watchedChannelType as NotificationChannelType,
|
||||||
|
{
|
||||||
|
register,
|
||||||
|
errors,
|
||||||
|
setValue,
|
||||||
|
configState: {},
|
||||||
|
updateConfigState: () => {},
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 模板编辑器区域 */}
|
{/* 模板编辑器区域 */}
|
||||||
<div className="flex-1 flex gap-4 min-h-0">
|
<div className="flex-1 flex gap-4 min-h-0">
|
||||||
{/* 编辑器 */}
|
{/* 编辑器 */}
|
||||||
|
|||||||
@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* 邮件模板配置表单
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
|
import type { TemplateConfigComponentProps } from '../template-config-strategies/types';
|
||||||
|
|
||||||
|
export const EmailTemplateConfigForm: React.FC<TemplateConfigComponentProps> = ({
|
||||||
|
register,
|
||||||
|
errors,
|
||||||
|
setValue,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="contentType">内容类型</Label>
|
||||||
|
<Select
|
||||||
|
onValueChange={(value) => setValue('templateConfig.contentType', value)}
|
||||||
|
defaultValue="TEXT"
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择内容类型" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="TEXT">纯文本</SelectItem>
|
||||||
|
<SelectItem value="HTML">HTML格式</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
{errors?.templateConfig?.contentType && (
|
||||||
|
<p className="text-sm text-destructive mt-1">
|
||||||
|
{errors.templateConfig.contentType.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="priority">优先级</Label>
|
||||||
|
<Select
|
||||||
|
onValueChange={(value) => setValue('templateConfig.priority', value)}
|
||||||
|
defaultValue="NORMAL"
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择优先级" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="LOW">低</SelectItem>
|
||||||
|
<SelectItem value="NORMAL">普通</SelectItem>
|
||||||
|
<SelectItem value="HIGH">高</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
{errors?.templateConfig?.priority && (
|
||||||
|
<p className="text-sm text-destructive mt-1">
|
||||||
|
{errors.templateConfig.priority.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* 模板配置表单工厂
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { NotificationChannelType } from '../../../../NotificationChannel/List/types';
|
||||||
|
import type { TemplateConfigComponentProps } from '../template-config-strategies/types';
|
||||||
|
import { WeworkTemplateConfigForm } from './WeworkTemplateConfigForm';
|
||||||
|
import { EmailTemplateConfigForm } from './EmailTemplateConfigForm';
|
||||||
|
|
||||||
|
export class TemplateConfigFormFactory {
|
||||||
|
static createConfigForm(
|
||||||
|
type: NotificationChannelType,
|
||||||
|
props: TemplateConfigComponentProps
|
||||||
|
): React.ReactElement | null {
|
||||||
|
switch (type) {
|
||||||
|
case NotificationChannelType.WEWORK:
|
||||||
|
return <WeworkTemplateConfigForm {...props} />;
|
||||||
|
|
||||||
|
case NotificationChannelType.EMAIL:
|
||||||
|
return <EmailTemplateConfigForm {...props} />;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static getSupportedTypes(): NotificationChannelType[] {
|
||||||
|
return [
|
||||||
|
NotificationChannelType.WEWORK,
|
||||||
|
NotificationChannelType.EMAIL,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDefaultConfig(type: NotificationChannelType): any {
|
||||||
|
switch (type) {
|
||||||
|
case NotificationChannelType.WEWORK:
|
||||||
|
return { messageType: 'TEXT' };
|
||||||
|
|
||||||
|
case NotificationChannelType.EMAIL:
|
||||||
|
return { contentType: 'TEXT', priority: 'NORMAL' };
|
||||||
|
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* 企业微信模板配置表单
|
||||||
|
*/
|
||||||
|
import React from 'react';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
|
import type { TemplateConfigComponentProps } from '../template-config-strategies/types';
|
||||||
|
|
||||||
|
export const WeworkTemplateConfigForm: React.FC<TemplateConfigComponentProps> = ({
|
||||||
|
register,
|
||||||
|
errors,
|
||||||
|
setValue,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="messageType">消息类型</Label>
|
||||||
|
<Select
|
||||||
|
onValueChange={(value) => setValue('templateConfig.messageType', value)}
|
||||||
|
defaultValue="TEXT"
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择消息类型" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="TEXT">文本消息</SelectItem>
|
||||||
|
<SelectItem value="MARKDOWN">Markdown消息</SelectItem>
|
||||||
|
<SelectItem value="FILE">文件消息</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
{errors?.templateConfig?.messageType && (
|
||||||
|
<p className="text-sm text-destructive mt-1">
|
||||||
|
{errors.templateConfig.messageType.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* 模板配置策略类型定义
|
||||||
|
*/
|
||||||
|
import { NotificationChannelType } from '../../../../NotificationChannel/List/types';
|
||||||
|
|
||||||
|
export interface TemplateConfigStrategy {
|
||||||
|
/** 渠道类型 */
|
||||||
|
type: NotificationChannelType;
|
||||||
|
|
||||||
|
/** 获取默认配置 */
|
||||||
|
getDefaultConfig(): any;
|
||||||
|
|
||||||
|
/** 验证配置 */
|
||||||
|
validateConfig(config: any): boolean;
|
||||||
|
|
||||||
|
/** 从API数据加载配置到表单状态 */
|
||||||
|
loadFromData(data: any): TemplateConfigState;
|
||||||
|
|
||||||
|
/** 从表单状态构建API配置 */
|
||||||
|
buildConfig(state: TemplateConfigState, formConfig: any): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TemplateConfigState {
|
||||||
|
/** 额外的状态数据 */
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TemplateConfigComponentProps {
|
||||||
|
/** 表单注册函数 */
|
||||||
|
register: any;
|
||||||
|
|
||||||
|
/** 表单错误 */
|
||||||
|
errors: any;
|
||||||
|
|
||||||
|
/** 设置表单值 */
|
||||||
|
setValue: any;
|
||||||
|
|
||||||
|
/** 配置状态 */
|
||||||
|
configState: TemplateConfigState;
|
||||||
|
|
||||||
|
/** 更新配置状态 */
|
||||||
|
updateConfigState: (updates: Partial<TemplateConfigState>) => void;
|
||||||
|
}
|
||||||
@ -22,6 +22,9 @@ export interface NotificationTemplateDTO extends BaseResponse {
|
|||||||
|
|
||||||
/** 内容模板(FreeMarker格式) */
|
/** 内容模板(FreeMarker格式) */
|
||||||
contentTemplate: string;
|
contentTemplate: string;
|
||||||
|
|
||||||
|
/** 模板配置(根据 channelType 不同而不同) */
|
||||||
|
templateConfig?: WeworkTemplateConfig | EmailTemplateConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
@ -51,6 +54,53 @@ export interface ChannelTypeOption {
|
|||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 5. 模板配置类型
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/** 模板配置基类 */
|
||||||
|
export interface BaseTemplateConfig {
|
||||||
|
// 运行时通过 channelType 判断具体类型
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 企业微信模板配置 */
|
||||||
|
export interface WeworkTemplateConfig extends BaseTemplateConfig {
|
||||||
|
/** 消息类型 */
|
||||||
|
messageType: 'TEXT' | 'MARKDOWN' | 'FILE';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 邮件模板配置 */
|
||||||
|
export interface EmailTemplateConfig extends BaseTemplateConfig {
|
||||||
|
/** 内容类型 */
|
||||||
|
contentType: 'TEXT' | 'HTML';
|
||||||
|
/** 优先级 */
|
||||||
|
priority: 'LOW' | 'NORMAL' | 'HIGH';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 6. 类型守卫函数
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为企业微信模板配置
|
||||||
|
*/
|
||||||
|
export function isWeworkTemplateConfig(
|
||||||
|
config: BaseTemplateConfig,
|
||||||
|
channelType: NotificationChannelType
|
||||||
|
): config is WeworkTemplateConfig {
|
||||||
|
return channelType === 'WEWORK' && 'messageType' in config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为邮件模板配置
|
||||||
|
*/
|
||||||
|
export function isEmailTemplateConfig(
|
||||||
|
config: BaseTemplateConfig,
|
||||||
|
channelType: NotificationChannelType
|
||||||
|
): config is EmailTemplateConfig {
|
||||||
|
return channelType === 'EMAIL' && 'contentType' in config && 'priority' in config;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// 7. 模板渲染参数
|
// 7. 模板渲染参数
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user