258 lines
9.5 KiB
TypeScript
258 lines
9.5 KiB
TypeScript
import { useState } from 'react';
|
||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||
import { Button } from '@/components/ui/button';
|
||
import { Input } from '@/components/ui/input';
|
||
import { Label } from '@/components/ui/label';
|
||
import { Textarea } from '@/components/ui/textarea';
|
||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||
import { Card } from '@/components/ui/card';
|
||
import { Badge } from '@/components/ui/badge';
|
||
import { Calendar } from '@/components/ui/calendar';
|
||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||
import { Send, Clock, CalendarIcon } from 'lucide-react';
|
||
import { format } from 'date-fns';
|
||
import { zhCN } from 'date-fns/locale';
|
||
import { MessageTemplate } from '@/types/message';
|
||
import { MessageSendFormData } from '../types';
|
||
|
||
interface SendMessageDialogProps {
|
||
open: boolean;
|
||
onOpenChange: (open: boolean) => void;
|
||
templates: MessageTemplate[];
|
||
formData: MessageSendFormData;
|
||
onFormDataChange: (data: MessageSendFormData) => void;
|
||
onSend: () => void;
|
||
getTypeIcon: (type: string) => JSX.Element;
|
||
getTypeLabel: (type: string) => string;
|
||
}
|
||
|
||
export function SendMessageDialog({
|
||
open,
|
||
onOpenChange,
|
||
templates,
|
||
formData,
|
||
onFormDataChange,
|
||
onSend,
|
||
getTypeIcon,
|
||
getTypeLabel
|
||
}: SendMessageDialogProps) {
|
||
const replaceVariables = (content: string, variables: Record<string, string>): string => {
|
||
let result = content;
|
||
Object.entries(variables).forEach(([key, value]) => {
|
||
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value || `{{${key}}}`);
|
||
});
|
||
return result;
|
||
};
|
||
|
||
const handleTemplateChange = (templateId: string) => {
|
||
const template = templates.find(t => t.id === templateId);
|
||
if (template) {
|
||
// 初始化变量
|
||
const vars: Record<string, string> = {};
|
||
template.variables.forEach(v => {
|
||
vars[v] = '';
|
||
});
|
||
|
||
onFormDataChange({
|
||
...formData,
|
||
templateId,
|
||
type: template.type,
|
||
subject: template.subject || '',
|
||
content: template.content,
|
||
variables: vars,
|
||
});
|
||
}
|
||
};
|
||
|
||
const selectedTemplate = templates.find(t => t.id === formData.templateId);
|
||
|
||
return (
|
||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||
<DialogContent className="max-w-3xl max-h-[90vh] overflow-y-auto">
|
||
<DialogHeader>
|
||
<DialogTitle>
|
||
<div className="flex items-center gap-2">
|
||
<Send className="w-5 h-5 text-green-600" />
|
||
发送消息
|
||
</div>
|
||
</DialogTitle>
|
||
<DialogDescription className="sr-only">
|
||
选择消息模版并发送消息
|
||
</DialogDescription>
|
||
</DialogHeader>
|
||
<div className="space-y-4">
|
||
{/* 选择模版 */}
|
||
<div>
|
||
<Label>选择消息模版 *</Label>
|
||
<Select value={formData.templateId} onValueChange={handleTemplateChange}>
|
||
<SelectTrigger>
|
||
<SelectValue placeholder="请选择消息模版" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{templates.filter(t => t.isActive).map(template => (
|
||
<SelectItem key={template.id} value={template.id}>
|
||
<div className="flex items-center gap-2">
|
||
{getTypeIcon(template.type)}
|
||
<span>{template.name}</span>
|
||
<Badge variant="outline" className="text-xs">
|
||
{getTypeLabel(template.type)}
|
||
</Badge>
|
||
</div>
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
|
||
{/* 发送方式 */}
|
||
<div>
|
||
<Label>发送方式 *</Label>
|
||
<Select
|
||
value={formData.sendType}
|
||
onValueChange={(value: 'immediate' | 'scheduled') => onFormDataChange({ ...formData, sendType: value })}
|
||
>
|
||
<SelectTrigger>
|
||
<SelectValue />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="immediate">
|
||
<div className="flex items-center gap-2">
|
||
<Send className="w-4 h-4" />
|
||
实时发送
|
||
</div>
|
||
</SelectItem>
|
||
<SelectItem value="scheduled">
|
||
<div className="flex items-center gap-2">
|
||
<Clock className="w-4 h-4" />
|
||
定时发送
|
||
</div>
|
||
</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
|
||
{/* 定时发送设置 */}
|
||
{formData.sendType === 'scheduled' && (
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<Label>发送日期 *</Label>
|
||
<Popover>
|
||
<PopoverTrigger asChild>
|
||
<Button variant="outline" className="w-full justify-start">
|
||
<CalendarIcon className="w-4 h-4 mr-2" />
|
||
{formData.scheduledDate ? (
|
||
format(formData.scheduledDate, 'yyyy年MM月dd日', { locale: zhCN })
|
||
) : (
|
||
'选择日期'
|
||
)}
|
||
</Button>
|
||
</PopoverTrigger>
|
||
<PopoverContent className="w-auto p-0" align="start">
|
||
<Calendar
|
||
mode="single"
|
||
selected={formData.scheduledDate}
|
||
onSelect={(date) => onFormDataChange({ ...formData, scheduledDate: date })}
|
||
locale={zhCN}
|
||
disabled={(date) => date < new Date(new Date().setHours(0, 0, 0, 0))}
|
||
/>
|
||
</PopoverContent>
|
||
</Popover>
|
||
</div>
|
||
<div>
|
||
<Label>发送时间 *</Label>
|
||
<Input
|
||
type="time"
|
||
value={formData.scheduledTime}
|
||
onChange={(e) => onFormDataChange({ ...formData, scheduledTime: e.target.value })}
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* 接收人 */}
|
||
<div>
|
||
<Label>接收人 *</Label>
|
||
<Textarea
|
||
value={formData.recipients}
|
||
onChange={(e) => onFormDataChange({ ...formData, recipients: e.target.value })}
|
||
placeholder={
|
||
formData.type === 'sms' ? '输入手机号,多个用逗号或换行分隔' :
|
||
formData.type === 'email' ? '输入邮箱地址,多个用逗号或换行分隔' :
|
||
formData.type === 'push' ? '输入设备ID或用户ID,多个用逗号或换行分隔' :
|
||
'输入用户名,多个用逗号或换行分隔'
|
||
}
|
||
rows={3}
|
||
/>
|
||
<p className="text-xs text-muted-foreground mt-1">
|
||
支持多个接收人,使用逗号、分号或换行分隔
|
||
</p>
|
||
</div>
|
||
|
||
{/* 消息主题(邮件和推送) */}
|
||
{(formData.type === 'email' || formData.type === 'push') && (
|
||
<div>
|
||
<Label>消息主题</Label>
|
||
<Input
|
||
value={formData.subject}
|
||
onChange={(e) => onFormDataChange({ ...formData, subject: e.target.value })}
|
||
placeholder="输入消息主题"
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
{/* 变量填写 */}
|
||
{selectedTemplate && selectedTemplate.variables.length > 0 && (
|
||
<div>
|
||
<Label>填写变量 *</Label>
|
||
<Card className="p-4 bg-gray-50">
|
||
<div className="grid grid-cols-2 gap-4">
|
||
{selectedTemplate.variables.map(variable => (
|
||
<div key={variable}>
|
||
<Label htmlFor={`var-${variable}`} className="text-xs">
|
||
{variable}
|
||
</Label>
|
||
<Input
|
||
id={`var-${variable}`}
|
||
value={formData.variables[variable] || ''}
|
||
onChange={(e) => onFormDataChange({
|
||
...formData,
|
||
variables: {
|
||
...formData.variables,
|
||
[variable]: e.target.value,
|
||
},
|
||
})}
|
||
placeholder={`输入 ${variable}`}
|
||
className="mt-1"
|
||
/>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
)}
|
||
|
||
{/* 消息内容预览 */}
|
||
{formData.content && (
|
||
<div>
|
||
<Label>消息内容预览</Label>
|
||
<Card className="p-4 bg-blue-50 border-blue-200">
|
||
<pre className="text-sm whitespace-pre-wrap">
|
||
{replaceVariables(formData.content, formData.variables)}
|
||
</pre>
|
||
</Card>
|
||
</div>
|
||
)}
|
||
</div>
|
||
<DialogFooter>
|
||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||
取消
|
||
</Button>
|
||
<Button onClick={onSend} className="bg-green-600 hover:bg-green-700">
|
||
<Send className="w-4 h-4 mr-2" />
|
||
{formData.sendType === 'immediate' ? '立即发送' : '创建定时任务'}
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
);
|
||
} |