增加代码编辑器表单组件
This commit is contained in:
parent
79b6f4bade
commit
dd04c93e9b
@ -23,15 +23,12 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { useToast } from '@/components/ui/use-toast';
|
||||
import { Loader2, Eye, Code } from 'lucide-react';
|
||||
import { NotificationChannelType } from '../../../NotificationChannel/List/types';
|
||||
import { TemplateConfigFormFactory } from './template-config-forms/TemplateConfigFormFactory';
|
||||
import type {
|
||||
NotificationTemplateDTO,
|
||||
ChannelTypeOption,
|
||||
TemplateVariable
|
||||
} from '../types';
|
||||
import {
|
||||
createTemplate,
|
||||
@ -39,7 +36,7 @@ import {
|
||||
getTemplateById,
|
||||
checkTemplateCodeUnique,
|
||||
} from '../service';
|
||||
import { TemplateEditor, TemplatePreview } from './';
|
||||
import { TemplateEditor, TemplateRender } from './';
|
||||
|
||||
interface NotificationTemplateDialogProps {
|
||||
open: boolean;
|
||||
@ -81,7 +78,6 @@ export const NotificationTemplateDialog: React.FC<NotificationTemplateDialogProp
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [showPreview, setShowPreview] = useState(false);
|
||||
const [variables, setVariables] = useState<TemplateVariable[]>([]);
|
||||
const [formData, setFormData] = useState<Record<string, any>>({});
|
||||
|
||||
const {
|
||||
@ -107,34 +103,6 @@ export const NotificationTemplateDialog: React.FC<NotificationTemplateDialogProp
|
||||
const watchedCode = watch('code');
|
||||
const watchedTemplate = watch('contentTemplate');
|
||||
|
||||
// 获取模板变量(硬编码常用变量)
|
||||
const getVariables = (channelType: NotificationChannelType): TemplateVariable[] => {
|
||||
const commonVars: TemplateVariable[] = [
|
||||
{ name: 'projectName', description: '项目名称', example: 'deploy-ease-platform' },
|
||||
{ name: 'buildNumber', description: '构建号', example: '123' },
|
||||
{ name: 'buildStatus', description: '构建状态', example: 'SUCCESS' },
|
||||
{ name: 'operator', description: '操作人', example: 'admin' },
|
||||
{ name: 'timestamp', description: '时间戳', example: new Date().toLocaleString() },
|
||||
];
|
||||
|
||||
if (channelType === NotificationChannelType.EMAIL) {
|
||||
return [
|
||||
...commonVars,
|
||||
{ name: 'recipient', description: '收件人', example: 'user@example.com' },
|
||||
{ name: 'subject', description: '邮件主题', example: '部署通知' },
|
||||
];
|
||||
}
|
||||
|
||||
return commonVars;
|
||||
};
|
||||
|
||||
// 监听渠道类型变化
|
||||
useEffect(() => {
|
||||
if (watchedChannelType) {
|
||||
setVariables(getVariables(watchedChannelType));
|
||||
}
|
||||
}, [watchedChannelType]);
|
||||
|
||||
// 验证编码唯一性
|
||||
const validateCodeUnique = async (code: string) => {
|
||||
if (!code || code.length < 1) return;
|
||||
@ -238,134 +206,127 @@ export const NotificationTemplateDialog: React.FC<NotificationTemplateDialogProp
|
||||
}
|
||||
};
|
||||
|
||||
// 插入变量
|
||||
const handleInsertVariable = (variable: TemplateVariable) => {
|
||||
const currentTemplate = watchedTemplate || '';
|
||||
const variableText = `\${${variable.name}}`;
|
||||
setValue('contentTemplate', currentTemplate + variableText);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-6xl h-[90vh] overflow-hidden flex flex-col">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{mode === 'edit' ? '编辑' : '创建'}通知模板
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{mode === 'edit' ? '编辑' : '创建'}通知模板
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogBody className="flex-1 overflow-hidden">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-6 w-6 animate-spin mr-2" />
|
||||
加载中...
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="h-full flex flex-col space-y-4">
|
||||
{/* 基础信息 */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">
|
||||
模板名称 <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="name"
|
||||
placeholder="例如:部署成功通知"
|
||||
{...register('name')}
|
||||
/>
|
||||
{errors.name && (
|
||||
<p className="text-sm text-destructive">{errors.name.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="code">
|
||||
模板编码 <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="code"
|
||||
placeholder="例如:deploy_success"
|
||||
disabled={mode === 'edit'}
|
||||
{...register('code')}
|
||||
/>
|
||||
{mode === 'edit' && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
编辑时不可修改模板编码
|
||||
</p>
|
||||
)}
|
||||
{errors.code && (
|
||||
<p className="text-sm text-destructive">{errors.code.message}</p>
|
||||
)}
|
||||
</div>
|
||||
<DialogBody className="flex-1 overflow-hidden">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-6 w-6 animate-spin mr-2" />
|
||||
加载中...
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="h-full flex flex-col space-y-4">
|
||||
{/* 基础信息 */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">
|
||||
模板名称 <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="name"
|
||||
placeholder="例如:部署成功通知"
|
||||
{...register('name')}
|
||||
/>
|
||||
{errors.name && (
|
||||
<p className="text-sm text-destructive">{errors.name.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="channelType">
|
||||
渠道类型 <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Select
|
||||
value={watchedChannelType}
|
||||
onValueChange={(value) => setValue('channelType', value as NotificationChannelType)}
|
||||
disabled={mode === 'edit'}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="请选择渠道类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{channelTypes.map((type) => (
|
||||
<SelectItem key={type.code} value={type.code}>
|
||||
{type.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{mode === 'edit' && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
编辑时不可修改渠道类型
|
||||
</p>
|
||||
)}
|
||||
{errors.channelType && (
|
||||
<p className="text-sm text-destructive">{errors.channelType.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="description">描述(可选)</Label>
|
||||
<Input
|
||||
id="description"
|
||||
placeholder="模板用途说明"
|
||||
{...register('description')}
|
||||
/>
|
||||
{errors.description && (
|
||||
<p className="text-sm text-destructive">{errors.description.message}</p>
|
||||
)}
|
||||
</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 className="space-y-2">
|
||||
<Label htmlFor="code">
|
||||
模板编码 <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="code"
|
||||
placeholder="例如:deploy_success"
|
||||
disabled={mode === 'edit'}
|
||||
{...register('code')}
|
||||
/>
|
||||
{mode === 'edit' && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
编辑时不可修改模板编码
|
||||
</p>
|
||||
)}
|
||||
{errors.code && (
|
||||
<p className="text-sm text-destructive">{errors.code.message}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 模板编辑器区域 */}
|
||||
<div className="flex-1 flex gap-4 min-h-0">
|
||||
{/* 编辑器 */}
|
||||
<div className="flex-1 flex flex-col min-w-0">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="channelType">
|
||||
渠道类型 <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Select
|
||||
value={watchedChannelType}
|
||||
onValueChange={(value) => setValue('channelType', value as NotificationChannelType)}
|
||||
disabled={mode === 'edit'}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="请选择渠道类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{channelTypes.map((type) => (
|
||||
<SelectItem key={type.code} value={type.code}>
|
||||
{type.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{mode === 'edit' && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
编辑时不可修改渠道类型
|
||||
</p>
|
||||
)}
|
||||
{errors.channelType && (
|
||||
<p className="text-sm text-destructive">{errors.channelType.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="description">描述(可选)</Label>
|
||||
<Input
|
||||
id="description"
|
||||
placeholder="模板用途说明"
|
||||
{...register('description')}
|
||||
/>
|
||||
{errors.description && (
|
||||
<p className="text-sm text-destructive">{errors.description.message}</p>
|
||||
)}
|
||||
</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 flex-col min-h-0">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Label>
|
||||
模板内容 <span className="text-destructive">*</span>
|
||||
@ -390,8 +351,6 @@ export const NotificationTemplateDialog: React.FC<NotificationTemplateDialogProp
|
||||
value={watchedTemplate}
|
||||
onChange={(value) => setValue('contentTemplate', value)}
|
||||
channelType={watchedChannelType}
|
||||
variables={variables}
|
||||
onVariableInsert={handleInsertVariable}
|
||||
onFormDataChange={setFormData}
|
||||
mode={mode}
|
||||
/>
|
||||
@ -403,43 +362,51 @@ export const NotificationTemplateDialog: React.FC<NotificationTemplateDialogProp
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</DialogBody>
|
||||
|
||||
{/* 预览面板 - 只在编辑模式显示 */}
|
||||
{mode === 'edit' && showPreview && (
|
||||
<div className="w-1/2 flex flex-col min-w-0">
|
||||
<Label className="mb-2">预览</Label>
|
||||
<div className="flex-1 min-h-0">
|
||||
<TemplatePreview
|
||||
template={watchedTemplate}
|
||||
channelType={watchedChannelType}
|
||||
variables={variables}
|
||||
formData={formData}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
{!loading && (
|
||||
<DialogFooter>
|
||||
<Button variant="outline" type="button" onClick={() => onOpenChange(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
disabled={saving || !isValid}
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
>
|
||||
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{mode === 'edit' ? '保存' : '创建'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
)}
|
||||
</DialogBody>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{!loading && (
|
||||
<DialogFooter>
|
||||
<Button variant="outline" type="button" onClick={() => onOpenChange(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
disabled={saving || !isValid}
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
>
|
||||
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{mode === 'edit' ? '保存' : '创建'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{/* 预览弹窗 - 只在编辑模式显示 */}
|
||||
{mode === 'edit' && showPreview && (
|
||||
<Dialog open={showPreview} onOpenChange={setShowPreview}>
|
||||
<DialogContent className="max-w-3xl max-h-[80vh] overflow-hidden flex flex-col">
|
||||
<DialogHeader>
|
||||
<DialogTitle>模板预览</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogBody className="flex-1 overflow-hidden">
|
||||
<TemplateRender
|
||||
template={watchedTemplate}
|
||||
channelType={watchedChannelType}
|
||||
formData={formData}
|
||||
/>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowPreview(false)}>
|
||||
关闭
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -288,17 +288,20 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-h-0">
|
||||
{console.log('formSchema:', formSchema)}
|
||||
{formSchema ? (
|
||||
<ScrollArea className="h-full">
|
||||
<div className="p-3">
|
||||
<FormRenderer
|
||||
schema={formSchema}
|
||||
onSubmit={(data) => {
|
||||
setFormData(data);
|
||||
}}
|
||||
showSubmit={false}
|
||||
showCancel={false}
|
||||
/>
|
||||
<FormRenderer
|
||||
schema={formSchema}
|
||||
onChange={(data) => {
|
||||
console.log('FormRenderer onChange called with data:', data);
|
||||
setFormData(data);
|
||||
}}
|
||||
showSubmit={false}
|
||||
showCancel={false}
|
||||
readonly={false}
|
||||
/>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
) : (
|
||||
|
||||
@ -1,127 +0,0 @@
|
||||
/**
|
||||
* 模板预览组件
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Loader2, RefreshCw, AlertCircle } from 'lucide-react';
|
||||
import { NotificationChannelType } from '../../../NotificationChannel/List/types';
|
||||
|
||||
interface TemplatePreviewProps {
|
||||
template: string;
|
||||
channelType: NotificationChannelType;
|
||||
formData?: Record<string, any>; // 来自FormDesigner的表单数据
|
||||
}
|
||||
|
||||
export const TemplatePreview: React.FC<TemplatePreviewProps> = ({
|
||||
template,
|
||||
channelType,
|
||||
formData,
|
||||
}) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [previewContent, setPreviewContent] = useState('');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// 预览模板
|
||||
const handlePreview = async () => {
|
||||
if (!template.trim()) {
|
||||
setPreviewContent('');
|
||||
setError(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// 预览时直接显示模板内容,不调用后端渲染
|
||||
setPreviewContent(template);
|
||||
} catch (error: any) {
|
||||
setError('预览失败');
|
||||
setPreviewContent('');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 自动预览
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
handlePreview();
|
||||
}, 500);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [template, channelType, formData]);
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col border rounded-lg">
|
||||
|
||||
{/* 当有formData时显示提示信息 */}
|
||||
{formData && Object.keys(formData).length > 0 && (
|
||||
<div className="p-3 border-b bg-blue-50">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-medium text-blue-900">使用表单设计器数据</h3>
|
||||
<p className="text-xs text-blue-700 mt-1">
|
||||
正在使用表单设计器中的参数进行模板渲染
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handlePreview}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
) : (
|
||||
<RefreshCw className="h-3 w-3" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 预览内容 */}
|
||||
<div className="flex-1 flex flex-col min-h-0">
|
||||
<div className="p-3 border-b">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-medium">预览结果</h3>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setPreviewContent('')}
|
||||
>
|
||||
✕
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 p-3 overflow-hidden">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<Loader2 className="h-6 w-6 animate-spin mr-2" />
|
||||
渲染中...
|
||||
</div>
|
||||
) : error ? (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
<div className="h-full overflow-auto">
|
||||
<div className="whitespace-pre-wrap font-mono text-sm bg-muted/30 p-3 rounded border min-h-full">
|
||||
{previewContent || '请输入模板内容进行预览'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 模板预览组件
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Loader2, AlertCircle } from 'lucide-react';
|
||||
import { NotificationChannelType } from '../../../NotificationChannel/List/types';
|
||||
|
||||
interface TemplateRenderProps {
|
||||
template: string;
|
||||
channelType: NotificationChannelType;
|
||||
formData?: Record<string, any>; // 来自FormDesigner的表单数据
|
||||
}
|
||||
|
||||
export const TemplateRender: React.FC<TemplateRenderProps> = ({
|
||||
template,
|
||||
channelType,
|
||||
formData,
|
||||
}) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [previewContent, setPreviewContent] = useState('');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// 预览模板
|
||||
const handlePreview = async () => {
|
||||
console.log('handlePreview called, template:', template);
|
||||
console.log('formData:', formData);
|
||||
|
||||
if (!template.trim()) {
|
||||
setPreviewContent('');
|
||||
setError(null);
|
||||
console.log('Template is empty, returning');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// 不调用render接口,直接使用模板内容
|
||||
setPreviewContent(template);
|
||||
} catch (error: any) {
|
||||
setError('渲染失败');
|
||||
setPreviewContent('');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 自动预览
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
handlePreview();
|
||||
}, 500);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [template, channelType, formData]);
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<Loader2 className="h-6 w-6 animate-spin mr-2" />
|
||||
加载中...
|
||||
</div>
|
||||
) : error ? (
|
||||
<Alert variant="destructive" className="m-4">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
<div className="flex-1 p-4 overflow-auto">
|
||||
<div className="whitespace-pre-wrap font-mono text-sm bg-muted/30 p-4 rounded border min-h-full">
|
||||
{previewContent || '请输入模板内容进行预览'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -3,5 +3,5 @@
|
||||
*/
|
||||
export { default as NotificationTemplateDialog } from './NotificationTemplateDialog';
|
||||
export { TemplateEditor } from './TemplateEditor';
|
||||
export { TemplatePreview } from './TemplatePreview';
|
||||
export { TemplateRender } from './TemplateRender';
|
||||
export { default as TestTemplateDialog } from './TestTemplateDialog';
|
||||
|
||||
@ -1,109 +0,0 @@
|
||||
/**
|
||||
* 通知模板使用示例
|
||||
*/
|
||||
import { renderTemplate } from '../service';
|
||||
import type { TemplateRenderRequest } from '../types';
|
||||
|
||||
/**
|
||||
* 渲染Jenkins构建通知模板示例
|
||||
*/
|
||||
export const renderJenkinsBuildNotification = async () => {
|
||||
const request: TemplateRenderRequest = {
|
||||
templateCode: 'jenkins_build_wework',
|
||||
params: {
|
||||
projectName: 'deploy-ease-platform',
|
||||
buildNumber: 123,
|
||||
buildStatus: 'SUCCESS',
|
||||
buildUrl: 'http://jenkins.example.com/job/deploy-ease-platform/123/',
|
||||
duration: '2m 30s',
|
||||
commitId: 'abc123def',
|
||||
commitMessage: 'feat: 添加通知模板功能',
|
||||
author: 'developer@example.com',
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await renderTemplate(request);
|
||||
if (response.success) {
|
||||
console.log('渲染成功:', response.content);
|
||||
return response.content;
|
||||
} else {
|
||||
console.error('渲染失败:', response.error);
|
||||
throw new Error(response.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('调用渲染接口失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 渲染部署成功通知模板示例
|
||||
*/
|
||||
export const renderDeploySuccessNotification = async () => {
|
||||
const request: TemplateRenderRequest = {
|
||||
templateCode: 'deploy_success_email',
|
||||
params: {
|
||||
applicationName: '部署平台',
|
||||
environment: '生产环境',
|
||||
version: 'v1.2.3',
|
||||
deployTime: new Date().toLocaleString('zh-CN'),
|
||||
deployUrl: 'https://deploy.example.com',
|
||||
operator: '运维团队',
|
||||
releaseNotes: [
|
||||
'新增通知模板管理功能',
|
||||
'优化部署流程',
|
||||
'修复已知问题',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await renderTemplate(request);
|
||||
if (response.success) {
|
||||
return response.content;
|
||||
} else {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('渲染部署通知失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 在其他业务模块中使用模板渲染
|
||||
*
|
||||
* 使用方式:
|
||||
* 1. 导入渲染函数
|
||||
* 2. 准备模板编码和参数
|
||||
* 3. 调用渲染接口
|
||||
* 4. 处理渲染结果
|
||||
*/
|
||||
export const useTemplateInBusiness = async (
|
||||
templateCode: string,
|
||||
businessData: Record<string, any>
|
||||
) => {
|
||||
const request: TemplateRenderRequest = {
|
||||
templateCode,
|
||||
params: businessData,
|
||||
};
|
||||
|
||||
const response = await renderTemplate(request);
|
||||
|
||||
if (response.success) {
|
||||
// 渲染成功,可以发送通知
|
||||
return {
|
||||
success: true,
|
||||
content: response.content,
|
||||
};
|
||||
} else {
|
||||
// 渲染失败,记录错误
|
||||
console.error(`模板 ${templateCode} 渲染失败:`, response.error);
|
||||
return {
|
||||
success: false,
|
||||
error: response.error,
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -117,12 +117,16 @@ export const checkTemplateCodeUnique = async (
|
||||
};
|
||||
|
||||
/**
|
||||
* 渲染模板(根据模板编码)
|
||||
* 渲染模板(根据模板内容)
|
||||
*/
|
||||
export const renderTemplate = async (
|
||||
renderRequest: TemplateRenderRequest
|
||||
templateContext: string,
|
||||
params: Record<string, any>
|
||||
): Promise<{ content: string; success: boolean; error?: string }> => {
|
||||
return request.post(`${API_PREFIX}/render`, renderRequest);
|
||||
return request.post(`${API_PREFIX}/render`, {
|
||||
templateContext,
|
||||
params
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -105,8 +105,8 @@ export function isEmailTemplateConfig(
|
||||
// 7. 模板渲染参数
|
||||
// ============================================
|
||||
export interface TemplateRenderRequest {
|
||||
/** 模板编码 */
|
||||
templateCode: string;
|
||||
/** 模板内容 */
|
||||
templateContext: string;
|
||||
|
||||
/** 模板参数 */
|
||||
params: Record<string, any>;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user