import { useState, useEffect, useCallback } from "react" import { Plus, Pencil, Trash2, Send, FileText } from "lucide-react" import { Button } from "@/components/ui/button" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { Badge } from "@/components/ui/badge" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" 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 { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog" import { fetchPushTemplates, createPushTemplate, updatePushTemplate, deletePushTemplate, sendTestPush, } from "@/lib/api/settings-api" import { PUSH_TRIGGER_LABELS } from "@/lib/constants" import type { PushTemplate, PushTriggerType, PushTemplateFormData } from "@/types/settings" const triggerBadgeColors: Record = { streak: "default", achievement: "secondary", event: "outline", manual: "default", } export function PushTemplateTab() { const [templates, setTemplates] = useState([]) const [loading, setLoading] = useState(true) const [dialogOpen, setDialogOpen] = useState(false) const [deleteOpen, setDeleteOpen] = useState(false) const [editingTemplate, setEditingTemplate] = useState(null) const [deletingTemplate, setDeletingTemplate] = useState(null) const [submitting, setSubmitting] = useState(false) const [testSending, setTestSending] = useState(false) // 表单状态 const [formData, setFormData] = useState({ name: "", title: "", body: "", triggerType: "manual", }) const loadTemplates = useCallback(async () => { setLoading(true) try { const res = await fetchPushTemplates() setTemplates(res.data) } catch { setTemplates([]) } finally { setLoading(false) } }, []) useEffect(() => { loadTemplates() }, [loadTemplates]) function openCreateDialog() { setEditingTemplate(null) setFormData({ name: "", title: "", body: "", triggerType: "manual", }) setDialogOpen(true) } function openEditDialog(template: PushTemplate) { setEditingTemplate(template) setFormData({ name: template.name, title: template.title, body: template.body, triggerType: template.triggerType, }) setDialogOpen(true) } function openDeleteDialog(template: PushTemplate) { setDeletingTemplate(template) setDeleteOpen(true) } async function handleSubmit() { setSubmitting(true) try { if (editingTemplate) { await updatePushTemplate(editingTemplate.id, formData) } else { await createPushTemplate(formData) } setDialogOpen(false) await loadTemplates() } finally { setSubmitting(false) } } async function handleDelete() { if (!deletingTemplate) return await deletePushTemplate(deletingTemplate.id) setDeleteOpen(false) setDeletingTemplate(null) await loadTemplates() } async function handleSendTest(template: PushTemplate) { setTestSending(true) try { await sendTestPush(template.id) // TODO: 显示成功提示 } catch { // TODO: 显示错误提示 } finally { setTestSending(false) } } return ( <>

推送文案模板

管理各类推送通知的文案模板,支持变量占位符

{/* 可用变量说明 */}

可用变量

在推送内容中使用 {"{{variable}}"} 形式插入变量。

  • {"{{userName}}"} - 用户昵称
  • {"{{streakDays}}"} - 连续签到天数
  • {"{{achievementName}}"} - 成就名称
  • {"{{eventName}}"} - 活动名称
模板名称 触发类型 标题 内容预览 操作 {loading ? ( 加载中... ) : templates.length === 0 ? ( 暂无推送模板 ) : ( templates.map((template) => ( {template.name} {PUSH_TRIGGER_LABELS[template.triggerType]} {template.title} {template.body}
)) )}
{/* 创建/编辑对话框 */} {editingTemplate ? "编辑模板" : "新建模板"} 配置推送通知的标题和内容,支持变量占位符
setFormData({ ...formData, name: e.target.value })} placeholder="例如:连胜提醒" />
setFormData({ ...formData, title: e.target.value })} placeholder="例如:恭喜获得成就!" />