增加代码编辑器表单组件
This commit is contained in:
parent
c2e1d69ee4
commit
826c2d1a76
@ -4,7 +4,6 @@
|
||||
*/
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import {
|
||||
Dialog,
|
||||
@ -14,27 +13,35 @@ import {
|
||||
DialogBody,
|
||||
DialogFooter,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Plus, Info, Settings } from 'lucide-react';
|
||||
import { Settings } from 'lucide-react';
|
||||
import { FormDesigner, FormRenderer, type FormSchema } from '@/components/FormDesigner';
|
||||
import Editor from '@/components/Editor';
|
||||
import { NotificationChannelType } from '../../../NotificationChannel/List/types';
|
||||
import type { editor } from 'monaco-editor';
|
||||
|
||||
// 模板变量类型(可选)
|
||||
export interface TemplateVariable {
|
||||
name: string;
|
||||
description?: string;
|
||||
type?: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
interface TemplateEditorProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
channelType: NotificationChannelType;
|
||||
variables: TemplateVariable[];
|
||||
onVariableInsert: (variable: TemplateVariable) => void;
|
||||
onFormDataChange?: (data: Record<string, any>) => void; // 新增:表单数据变化回调
|
||||
mode?: 'create' | 'edit'; // 新增:模式标识
|
||||
variables?: TemplateVariable[]; // 可选
|
||||
onVariableInsert?: (variable: TemplateVariable) => void; // 可选
|
||||
onFormDataChange?: (data: Record<string, any>) => void; // 表单数据变化回调
|
||||
mode?: 'create' | 'edit'; // 模式标识
|
||||
}
|
||||
|
||||
export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
channelType,
|
||||
variables,
|
||||
variables = [],
|
||||
onVariableInsert,
|
||||
onFormDataChange,
|
||||
mode = 'edit',
|
||||
@ -167,12 +174,12 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
||||
})),
|
||||
|
||||
// 变量建议
|
||||
...variables.map(variable => ({
|
||||
...(variables || []).map(variable => ({
|
||||
label: `\${${variable.name}}`,
|
||||
kind: monaco.languages.CompletionItemKind.Variable,
|
||||
insertText: `\${${variable.name}}`,
|
||||
documentation: variable.description,
|
||||
detail: `${variable.type} - ${variable.required ? 'Required' : 'Optional'}`
|
||||
detail: `${variable.type ?? ''} ${variable.required ? '- Required' : ''}`
|
||||
}))
|
||||
];
|
||||
|
||||
@ -288,14 +295,12 @@ export const TemplateEditor: React.FC<TemplateEditorProps> = ({
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-hidden">
|
||||
{console.log('formSchema:', formSchema)}
|
||||
{formSchema ? (
|
||||
<ScrollArea className="h-full">
|
||||
<div className="p-3">
|
||||
<FormRenderer
|
||||
schema={formSchema}
|
||||
onChange={(data) => {
|
||||
console.log('FormRenderer onChange called with data:', data);
|
||||
setFormData(data);
|
||||
}}
|
||||
showSubmit={false}
|
||||
|
||||
@ -19,11 +19,12 @@ import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { useToast } from '@/components/ui/use-toast';
|
||||
import { Loader2, Send, TestTube } from 'lucide-react';
|
||||
import type { NotificationTemplateDTO, TemplateRenderRequest } from '../types';
|
||||
import { Loader2, Send, TestTube, Settings } from 'lucide-react';
|
||||
import type { NotificationTemplateDTO } from '../types';
|
||||
import type { NotificationChannelDTO } from '../../../NotificationChannel/List/types';
|
||||
import { getChannelsPage } from '../../../NotificationChannel/List/service';
|
||||
import { sendTestMessage, renderTemplate, type TestMessageRequest } from '../service';
|
||||
import { sendTestMessage, type TestMessageRequest } from '../service';
|
||||
import { FormDesigner, FormRenderer, type FormSchema } from '@/components/FormDesigner';
|
||||
|
||||
interface TestTemplateDialogProps {
|
||||
open: boolean;
|
||||
@ -41,23 +42,21 @@ const TestTemplateDialog: React.FC<TestTemplateDialogProps> = ({
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [channels, setChannels] = useState<NotificationChannelDTO[]>([]);
|
||||
const [selectedChannelId, setSelectedChannelId] = useState<string>('');
|
||||
const [params, setParams] = useState<Record<string, string>>({});
|
||||
const [renderedContent, setRenderedContent] = useState<string>('');
|
||||
const [formSchema, setFormSchema] = useState<FormSchema | null>(null);
|
||||
const [formData, setFormData] = useState<Record<string, any>>({});
|
||||
const [designerOpen, setDesignerOpen] = useState(false);
|
||||
|
||||
// 加载渠道列表
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
loadChannels();
|
||||
initParams();
|
||||
// 打开时清空上次数据,用户可通过表单设计器自定义参数
|
||||
setFormSchema(null);
|
||||
setFormData({});
|
||||
}
|
||||
}, [open, template]);
|
||||
|
||||
// 当参数变化时重新渲染模板
|
||||
useEffect(() => {
|
||||
if (template?.code && Object.keys(params).length > 0) {
|
||||
renderTemplateContent();
|
||||
}
|
||||
}, [params, template?.code]);
|
||||
// 参数变化不再触发后端渲染(此页面不调用渲染接口)
|
||||
|
||||
const loadChannels = async () => {
|
||||
try {
|
||||
@ -77,60 +76,9 @@ const TestTemplateDialog: React.FC<TestTemplateDialogProps> = ({
|
||||
};
|
||||
|
||||
// 渲染模板内容
|
||||
const renderTemplateContent = async () => {
|
||||
if (!template?.code) return;
|
||||
|
||||
try {
|
||||
const renderRequest: TemplateRenderRequest = {
|
||||
templateCode: template.code,
|
||||
params: params,
|
||||
};
|
||||
|
||||
const result = await renderTemplate(renderRequest);
|
||||
setRenderedContent(result.content || '');
|
||||
} catch (error: any) {
|
||||
console.error('渲染模板失败:', error);
|
||||
setRenderedContent(template.contentTemplate || '');
|
||||
}
|
||||
};
|
||||
// 不再调用后端渲染接口,此处保留占位以说明设计意图
|
||||
|
||||
// 初始化参数
|
||||
const initParams = () => {
|
||||
if (!template?.contentTemplate) return;
|
||||
|
||||
// 从模板内容中提取变量 ${variableName}
|
||||
const variableRegex = /\$\{([^}]+)\}/g;
|
||||
const variables = new Set<string>();
|
||||
let match;
|
||||
|
||||
while ((match = variableRegex.exec(template.contentTemplate)) !== null) {
|
||||
variables.add(match[1]);
|
||||
}
|
||||
|
||||
// 初始化参数对象
|
||||
const initialParams: Record<string, string> = {};
|
||||
variables.forEach(variable => {
|
||||
initialParams[variable] = getDefaultValue(variable);
|
||||
});
|
||||
|
||||
setParams(initialParams);
|
||||
};
|
||||
|
||||
// 获取变量的默认值
|
||||
const getDefaultValue = (variable: string): string => {
|
||||
const defaultValues: Record<string, string> = {
|
||||
projectName: '测试项目',
|
||||
buildNumber: '123',
|
||||
environment: 'test',
|
||||
status: 'success',
|
||||
message: '这是一条测试消息',
|
||||
time: new Date().toLocaleString(),
|
||||
url: 'https://example.com',
|
||||
user: '测试用户',
|
||||
};
|
||||
|
||||
return defaultValues[variable] || `测试${variable}`;
|
||||
};
|
||||
// 不再基于正则推断参数,改为表单设计器动态定义
|
||||
|
||||
// 发送测试消息
|
||||
const handleSend = async () => {
|
||||
@ -147,7 +95,7 @@ const TestTemplateDialog: React.FC<TestTemplateDialogProps> = ({
|
||||
const request: TestMessageRequest = {
|
||||
channelId: parseInt(selectedChannelId),
|
||||
notificationTemplateId: template.id,
|
||||
params: params,
|
||||
params: formData || {},
|
||||
};
|
||||
|
||||
await sendTestMessage(request);
|
||||
@ -172,8 +120,8 @@ const TestTemplateDialog: React.FC<TestTemplateDialogProps> = ({
|
||||
// 重置状态
|
||||
const handleClose = () => {
|
||||
setSelectedChannelId('');
|
||||
setParams({});
|
||||
setRenderedContent('');
|
||||
setFormSchema(null);
|
||||
setFormData({});
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
@ -217,43 +165,37 @@ const TestTemplateDialog: React.FC<TestTemplateDialogProps> = ({
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 模板参数 */}
|
||||
{Object.keys(params).length > 0 && (
|
||||
<div className="space-y-3">
|
||||
{/* 模板参数(动态表单) */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label>模板参数</Label>
|
||||
<div className="space-y-2">
|
||||
{Object.entries(params).map(([key, value]) => (
|
||||
<div key={key} className="space-y-1">
|
||||
<Label htmlFor={`param-${key}`} className="text-sm font-normal">
|
||||
{key}
|
||||
</Label>
|
||||
<Input
|
||||
id={`param-${key}`}
|
||||
value={value}
|
||||
onChange={(e) => setParams(prev => ({
|
||||
...prev,
|
||||
[key]: e.target.value
|
||||
}))}
|
||||
placeholder={`请输入${key}`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Button type="button" variant="ghost" size="sm" onClick={() => setDesignerOpen(true)}>
|
||||
<Settings className="h-4 w-4 mr-1" /> 设计参数表单
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{formSchema ? (
|
||||
<div className="border rounded p-3 bg-muted/30">
|
||||
<FormRenderer
|
||||
schema={formSchema}
|
||||
onChange={(data) => setFormData(data)}
|
||||
showSubmit={false}
|
||||
showCancel={false}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-xs text-muted-foreground">未定义参数表单,点击右上角按钮进行设计。</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 模板内容预览 */}
|
||||
{/* 模板内容 */}
|
||||
<div className="space-y-2">
|
||||
<Label>渲染结果预览</Label>
|
||||
<Label>模板内容</Label>
|
||||
<Textarea
|
||||
value={renderedContent || template?.contentTemplate || ''}
|
||||
value={template?.contentTemplate || ''}
|
||||
readOnly
|
||||
className="min-h-[120px] bg-muted/30 text-sm"
|
||||
placeholder="模板渲染结果将在这里显示..."
|
||||
placeholder="模板内容"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{renderedContent ? '✓ 已根据参数渲染' : '显示原始模板内容'}
|
||||
</p>
|
||||
</div>
|
||||
</DialogBody>
|
||||
|
||||
@ -279,6 +221,31 @@ const TestTemplateDialog: React.FC<TestTemplateDialogProps> = ({
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
{/* 参数表单设计器 */}
|
||||
<Dialog open={designerOpen} onOpenChange={setDesignerOpen}>
|
||||
<DialogContent className="max-w-5xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||
<DialogHeader>
|
||||
<DialogTitle>参数表单设计器</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogBody className="flex-1 overflow-hidden">
|
||||
<div className="h-[70vh]">
|
||||
<FormDesigner
|
||||
value={formSchema}
|
||||
onChange={setFormSchema}
|
||||
onSave={(schema) => {
|
||||
setFormSchema(schema);
|
||||
setDesignerOpen(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setDesignerOpen(false)}>取消</Button>
|
||||
<Button onClick={() => setDesignerOpen(false)}>完成</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user