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
@@ -3,6 +3,7 @@ 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;
@@ -85,17 +86,25 @@ function TemplateFormModal({
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>({
tenantId: item?.tenantId ?? '',
applicationId: item?.applicationId ?? '',
signatureId: item?.signatureId ?? '',
name: item?.name ?? '',
content: item?.content ?? '',
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');
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 extends keyof TemplateFormState>(key: Key, value: TemplateFormState[Key]) {
@@ -106,6 +115,14 @@ function TemplateFormModal({
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) {
@@ -132,7 +149,7 @@ function TemplateFormModal({
footer={(
<>
<Button onClick={onClose} variant="ghost"></Button>
<Button disabled={!form.tenantId || !form.applicationId || !form.name || !form.content.trim()} onClick={() => onSubmit({ ...form, variables: currentVariables })}></Button>
<Button disabled={!form.tenantId || !form.applicationId || !form.signatureId || !form.name || !form.content.trim()} onClick={() => onSubmit({ ...form, variables: currentVariables })}></Button>
</>
)}
onClose={onClose}
@@ -164,19 +181,21 @@ function TemplateFormModal({
/>
<Select
label="签名"
onChange={(event) => update('signatureId', event.target.value)}
onChange={(event) => selectSignature(event.target.value)}
options={[
{ label: '不绑定签名', value: '' },
{ label: '请选择签名', value: '' },
...tenantSignatures.map((signature) => ({ label: signature.name, value: signature.id })),
]}
required
value={form.signatureId}
/>
<Input label="模板名称" onChange={(event) => update('name', event.target.value)} placeholder="请输入模板名称" required value={form.name} />
<Input label="模板分类" onChange={(event) => update('category', event.target.value)} placeholder="行业通知/营销推广/验证码" value={form.category} />
<Textarea
hint="模板内容必须以所选签名开头;选择或切换签名时系统会自动填入或替换完整签名,例如:【XX公司】验证码为${code}。"
label="模板内容"
onChange={(event) => setContent(event.target.value)}
placeholder="例如:尊敬的${name},您的验证码为${code}。"
placeholder="请选择签名后填写正文,例如:尊敬的${name},您的验证码为${code}。"
required
rows={8}
ref={contentRef}