feat: polish reporting templates and shared controls

This commit is contained in:
hectorzhao
2026-07-15 16:31:49 +08:00
parent 28fb8e9038
commit a7a4e8d9f6
21 changed files with 488 additions and 119 deletions
+28 -5
View File
@@ -3,6 +3,7 @@ import { Edit3, MessageSquare, Plus, Search, Trash2 } from 'lucide-react';
import { Button, Input, Modal, Pagination, Select, Tag, Textarea } from '@/components/ui';
import { clientApi, type ClientSmsApplication, type ClientSmsSignature, type ClientSmsTemplate } from '@/api/adminApi';
import { formatDateTime } from '@/utils/dateTime';
import { replaceLeadingSmsSignature } from '@/utils/smsSignature';
type TemplateVariable = {
name: string;
@@ -73,12 +74,16 @@ function TemplateModal({
const [customVariable, setCustomVariable] = useState('');
const [variablesOpen, setVariablesOpen] = useState(false);
const contentRef = useRef<HTMLTextAreaElement>(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<TemplateFormState>({
applicationId: item?.applicationId ?? '',
signatureId: item?.signatureId ?? '',
name: item?.name ?? '',
category: item?.category ?? '行业通知',
content: item?.content ?? '',
content: initialContent,
variables: item?.variables?.map((variable) => ({ name: variable.name, example: variable.example ?? undefined, required: variable.required ?? true })) ?? [],
});
const application = applications.find((candidate) => candidate.id === form.applicationId);
@@ -97,6 +102,14 @@ function TemplateModal({
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.trim();
if (!normalized) return;
@@ -120,7 +133,7 @@ function TemplateModal({
footer={(
<>
<Button onClick={onClose} variant="ghost"></Button>
<Button disabled={!form.applicationId || !form.name || !form.content.trim()} onClick={() => onSubmit({ ...form, variables })}></Button>
<Button disabled={!form.applicationId || !form.signatureId || !form.name || !form.content.trim()} onClick={() => onSubmit({ ...form, variables })}></Button>
</>
)}
onClose={onClose}
@@ -137,13 +150,23 @@ function TemplateModal({
/>
<Select
label="短信签名"
onChange={(event) => update('signatureId', event.target.value)}
options={[{ label: '不绑定签名', value: '' }, ...availableSignatures.map((signature) => ({ label: signature.name, value: signature.id }))]}
onChange={(event) => selectSignature(event.target.value)}
options={[{ label: '请选择签名', value: '' }, ...availableSignatures.map((signature) => ({ label: signature.name, value: signature.id }))]}
required
value={form.signatureId}
/>
<Input label="模板名称" onChange={(event) => update('name', event.target.value)} placeholder="请输入模板名称" value={form.name} />
<Input label="模板分类" onChange={(event) => update('category', event.target.value)} placeholder="行业通知/营销推广/验证码" value={form.category} />
<Textarea label="模板内容" onChange={(event) => setContent(event.target.value)} placeholder="变量格式:${code}" ref={contentRef} rows={6} value={form.content} />
<Textarea
hint="模板内容必须以所选签名开头;选择或切换签名时系统会自动填入或替换完整签名,例如:【XX公司】验证码为${code}。"
label="模板内容"
onChange={(event) => setContent(event.target.value)}
placeholder="请选择签名后填写正文,变量格式:${code}"
ref={contentRef}
required
rows={6}
value={form.content}
/>
<div className="template-form-meta">
<button onClick={() => setVariablesOpen((current) => !current)} type="button">
<Plus size={16} /> {variablesOpen ? '收起变量面板' : '插入变量'}