import { startTransition, useEffect, useRef, useState } from 'react'; import { Edit3, MessageSquare, Plus, Search } from 'lucide-react'; import { Button, DeleteRiskAction, Input, Modal, Pagination, Select, Tag, Textarea } from '@/components/ui'; import { clientApi, type ClientSmsApplication, type ClientSmsSignatureView, type ClientSmsTemplate, } from '@/api/adminApi'; import { formatDateTime } from '@/utils/dateTime'; import { replaceLeadingSmsSignature } from '@/utils/smsSignature'; import { templateVariableError, templateVariableNameHint, templateVariableNamePattern, } from '@/utils/templateVariables'; import './ClientTemplatesPage.css'; type TemplateVariable = { name: string; example?: string; required?: boolean; }; type TemplateFormState = { applicationId: string; signatureId: string; name: string; content: string; variables: TemplateVariable[]; }; const statusTone: Record = { approved: 'success', pending: 'info', rejected: 'danger', draft: 'warning', }; const statusLabel: Record = { approved: '已通过', pending: '审核中', rejected: '已驳回', draft: '草稿', disabled: '已禁用', }; const recommendedVariables = [ ['验证码', 'code'], ['手机号', 'phone'], ['姓名', 'name'], ['日期', 'date'], ['金额', 'amount'], ['时间', 'time'], ['余额', 'balance'], ['地址', 'address'], ['快递单号', 'trackingNumber'], ['链接', 'link'], ]; function extractVariables(content: string): TemplateVariable[] { return Array.from(new Set(Array.from(content.matchAll(/\$\{([^}]+)\}/g)).map((match) => match[1]))).map((name) => ({ name, required: true, })); } function billingUnits(content: string) { if (!content) return 1; return content.length <= 70 ? 1 : Math.ceil(content.length / 67); } function TemplateModal({ applications, item, onClose, onSubmit, signatures, }: { applications: ClientSmsApplication[]; item?: ClientSmsTemplate; onClose: () => void; onSubmit: (state: TemplateFormState) => void; signatures: ClientSmsSignatureView[]; }) { const [customVariable, setCustomVariable] = useState(''); const [customVariableError, setCustomVariableError] = 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({ applicationId: item?.applicationId ?? '', signatureId: item?.signatureId ?? '', name: item?.name ?? '', content: initialContent, variables: item?.variables?.map((variable) => ({ name: variable.name, example: variable.example ?? undefined, required: variable.required ?? true, })) ?? [], }); const [initialForm] = useState(form); const dirty = JSON.stringify(form) !== JSON.stringify(initialForm); const variableError = templateVariableError(form.content); const application = applications.find((candidate) => candidate.id === form.applicationId); const availableSignatures = signatures.filter( (signature) => signature.auditStatus === 'approved' && (!application || signature.tenantId === application.tenantId) && (!signature.applicationId || signature.applicationId === form.applicationId), ); const variables = 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 = availableSignatures.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; if (!templateVariableNamePattern.test(normalized)) { setCustomVariableError(templateVariableNameHint); return; } 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) { update( 'variables', variables.map((variable) => (variable.name === name ? { ...variable, example } : variable)), ); } return ( ( <> )} onClose={onClose} open size="xl" title={item ? '编辑短信模板' : '添加短信模板'} >
update('name', event.target.value)} placeholder="请输入模板名称" value={form.name} />