import { useEffect, useMemo, useRef, useState } from 'react'; import { Edit3, Eye, Plus, Search, Trash2 } from 'lucide-react'; import { adminApi, type ClientSmsApplication, type ClientSmsSignature, type ClientSmsTemplate, type TenantOption } from '@/api/adminApi'; import { formatDateTime } from '@/utils/dateTime'; import { Breadcrumb, Button, Input, Modal, Pagination, Select, Tabs, Tag, Textarea } from '@/components/ui'; import { replaceLeadingSmsSignature } from '@/utils/smsSignature'; type TemplateFormState = { tenantId: string; applicationId: string; signatureId: string; name: string; content: string; category: string; variables: TemplateVariable[]; }; type TemplateVariable = { name: string; example?: string; required?: boolean; }; const recommendedVariables = [ ['验证码', 'code'], ['手机号', 'phone'], ['姓名', 'name'], ['日期', 'date'], ['金额', 'amount'], ['时间', 'time'], ['余额', 'balance'], ['地址', 'address'], ['天数', 'days'], ['快递单号', 'trackingNumber'], ['案件号', 'caseNumber'], ['链接', 'link'], ['站点', 'station'], ]; function extractVariables(content: string): TemplateVariable[] { const matches = content.match(/\$\{[a-zA-Z0-9_]+\}/g) ?? []; return [...new Set(matches)].map((match) => ({ name: match.slice(2, -1), required: true })); } function formatDate(value?: string) { return formatDateTime(value); } function statusTone(status: string) { if (status === 'approved') return 'success'; if (status === 'rejected') return 'danger'; if (status === 'deleted') return 'neutral'; return 'info'; } const auditStatusLabel: Record = { approved: '已通过', draft: '草稿', pending: '审核中', rejected: '已驳回', deleted: '已删除', }; function billingUnits(content: string) { if (!content) { return 1; } return content.length <= 70 ? 1 : Math.ceil(content.length / 67); } function TemplateFormModal({ applications, item, onClose, onSubmit, signatures, tenants, }: { applications: ClientSmsApplication[]; item?: ClientSmsTemplate; onClose: () => void; onSubmit: (state: TemplateFormState) => void; signatures: ClientSmsSignature[]; tenants: TenantOption[]; }) { const [customVariable, setCustomVariable] = useState(''); const [variablesOpen, setVariablesOpen] = useState(false); const contentRef = useRef(null); const initialSignature = signatures.find((signature) => signature.id === item?.signatureId); const initialContent = item?.signatureId ? replaceLeadingSmsSignature(item.content, initialSignature?.name) : item?.content ?? ''; const [form, setForm] = useState({ tenantId: item?.tenantId ?? '', applicationId: item?.applicationId ?? '', signatureId: item?.signatureId ?? '', name: item?.name ?? '', content: initialContent, category: item?.category ?? '行业通知', variables: item?.variables?.map((variable) => ({ name: variable.name, example: variable.example ?? undefined, required: variable.required ?? true })) ?? [], }); const tenantApplications = applications.filter((application) => application.tenantId === form.tenantId && application.status !== 'deleted'); const tenantSignatures = signatures.filter((signature) => ( signature.tenantId === form.tenantId && signature.auditStatus !== 'deleted' && (!signature.applicationId || signature.applicationId === form.applicationId) )); const currentVariables = form.variables.length ? form.variables : extractVariables(form.content); function update(key: Key, value: TemplateFormState[Key]) { setForm((current) => ({ ...current, [key]: value })); } function setContent(content: string) { setForm((current) => ({ ...current, content, variables: extractVariables(content) })); } function selectSignature(signatureId: string) { const signature = tenantSignatures.find((candidate) => candidate.id === signatureId); setForm((current) => { const content = replaceLeadingSmsSignature(current.content, signature?.name); return { ...current, signatureId, content, variables: extractVariables(content) }; }); } function insertVariable(name: string) { const normalized = name.trim(); if (!normalized) { return; } const token = `\${${normalized}}`; const textarea = contentRef.current; const start = textarea?.selectionStart ?? form.content.length; const end = textarea?.selectionEnd ?? start; setContent(`${form.content.slice(0, start)}${token}${form.content.slice(end)}`); requestAnimationFrame(() => { contentRef.current?.focus(); contentRef.current?.setSelectionRange(start + token.length, start + token.length); }); } function updateVariableExample(name: string, example: string) { const variables = currentVariables.map((variable) => variable.name === name ? { ...variable, example } : variable); update('variables', variables); } return ( )} onClose={onClose} open size="xl" title={

{item ? '编辑短信模板' : '添加短信模板'}

模板内容和变量将写入真实后台。

} >
update('applicationId', event.target.value)} options={[ { label: '请选择应用', value: '' }, ...tenantApplications.map((application) => ({ label: application.name, value: application.id })), ]} required value={form.applicationId} /> update('name', event.target.value)} placeholder="请输入模板名称" required value={form.name} /> update('category', event.target.value)} placeholder="行业通知/营销推广/验证码" value={form.category} />