fix: improve enterprise template and signature layouts

This commit is contained in:
hectorzhao
2026-07-13 10:31:25 +08:00
parent d4b2df82da
commit 19d47b45eb
6 changed files with 248 additions and 38 deletions
@@ -82,12 +82,14 @@ type CarrierReportSummary = { status: string; approved: number; total: number };
function CarrierReportTag({ summary }: { summary?: CarrierReportSummary }) {
if (!summary || summary.status === 'not_applicable' || summary.total === 0) return <Tag tone="neutral"></Tag>;
if (summary.status === 'approved') return <Tag tone="success">{summary.approved}/{summary.total}</Tag>;
if (summary.status === 'failed' || summary.status === 'rejected') return <Tag tone="danger">{summary.approved}/{summary.total}</Tag>;
if (summary.status === 'waiting_material') return <Tag tone="warning">{summary.approved}/{summary.total}</Tag>;
if (summary.approved > 0) return <Tag tone="info">{summary.approved}/{summary.total}</Tag>;
if (summary.status === 'reporting' || summary.status === 'exporting') return <Tag tone="info">0/{summary.total}</Tag>;
return <Tag tone="neutral">0/{summary.total}</Tag>;
let label = '未报备';
let tone: 'success' | 'danger' | 'warning' | 'info' | 'neutral' = 'neutral';
if (summary.status === 'approved') { label = '全部通过'; tone = 'success'; }
else if (summary.status === 'failed' || summary.status === 'rejected') { label = '报备失败'; tone = 'danger'; }
else if (summary.status === 'waiting_material') { label = '资料待补充'; tone = 'warning'; }
else if (summary.approved > 0) { label = '部分通过'; tone = 'info'; }
else if (summary.status === 'reporting' || summary.status === 'exporting') { label = '报备中'; tone = 'info'; }
return <span className="carrier-report-summary"><Tag tone={tone}>{label}</Tag><small>{summary.approved}/{summary.total}</small></span>;
}
function AuditStatusTag({ status }: { status: string }) {
+54 -26
View File
@@ -2,7 +2,7 @@ import { useEffect, useMemo, 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, Select, Table, Tabs, Tag, Textarea, type TableColumn } from '@/components/ui';
import { Breadcrumb, Button, Input, Modal, Pagination, Select, Tabs, Tag, Textarea } from '@/components/ui';
type TemplateFormState = {
tenantId: string;
@@ -259,6 +259,7 @@ export function AdminEnterpriseTemplatesPage() {
const [templateKeyword, setTemplateKeyword] = useState('');
const [appliedTemplateKeyword, setAppliedTemplateKeyword] = useState('');
const [tenants, setTenants] = useState<TenantOption[]>([]);
const [page, setPage] = useState(1);
async function loadData(filters = { enterpriseKeyword: appliedEnterpriseKeyword, templateKeyword: appliedTemplateKeyword }) {
try {
@@ -288,6 +289,14 @@ export function AdminEnterpriseTemplatesPage() {
return (!appliedEnterpriseKeyword || enterprise.includes(appliedEnterpriseKeyword))
&& (!appliedTemplateKeyword || item.name.includes(appliedTemplateKeyword) || item.content.includes(appliedTemplateKeyword) || application.includes(appliedTemplateKeyword));
}), [appliedEnterpriseKeyword, appliedTemplateKeyword, templates]);
const pageSize = 10;
const totalPages = Math.max(1, Math.ceil(filteredTemplates.length / pageSize));
const currentPage = Math.min(page, totalPages);
const visibleTemplates = filteredTemplates.slice((currentPage - 1) * pageSize, currentPage * pageSize);
useEffect(() => {
setPage(1);
}, [appliedEnterpriseKeyword, appliedTemplateKeyword]);
async function saveTemplate(state: TemplateFormState) {
const existing = templateModal && templateModal !== 'new' ? templateModal : null;
@@ -328,30 +337,6 @@ export function AdminEnterpriseTemplatesPage() {
await loadData();
}
const columns: Array<TableColumn<ClientSmsTemplate>> = [
{ key: 'name', title: '模板名称', width: '180px', render: (record) => <strong>{record.name}</strong> },
{ key: 'tenant', title: '企业', width: '240px', render: (record) => record.tenant?.name ?? record.tenantId },
{ key: 'application', title: '应用', width: '180px', render: (record) => record.application?.name ?? record.applicationId },
{ key: 'signature', title: '签名', width: '160px', render: (record) => record.signature?.name ?? '-' },
{ key: 'content', title: '模板内容', width: '420px', render: (record) => <span className="table-long-text table-long-text--sms-template">{record.content}</span> },
{ key: 'variables', title: '变量', width: '120px', render: (record) => `${record.variables?.length ?? 0}` },
{ key: 'status', title: '审核状态', width: '130px', render: (record) => <Tag tone={statusTone(record.auditStatus)}>{auditStatusLabel[record.auditStatus] ?? record.auditStatus}</Tag> },
{ key: 'updatedAt', title: '更新时间', width: '170px', render: (record) => formatDate(record.updatedAt) },
{
key: 'actions',
title: '操作',
align: 'right',
width: '220px',
render: (record) => (
<div className="table-actions">
<Button icon={<Eye size={15} />} onClick={() => setTemplatePreview(record)} size="sm" variant="ghost"></Button>
<Button icon={<Edit3 size={15} />} onClick={() => setTemplateModal(record)} size="sm" variant="ghost"></Button>
<Button icon={<Trash2 size={15} />} onClick={() => setDeleteTarget(record)} size="sm" variant="danger"></Button>
</div>
),
},
];
return (
<section className="page-stack admin-customer-split-page">
<div className="page-heading">
@@ -387,7 +372,50 @@ export function AdminEnterpriseTemplatesPage() {
onChange={(value) => setActiveTab(value as 'sms' | 'mms')}
value={activeTab}
items={[
{ label: '短信模板', value: 'sms', content: <Table columns={columns} data={filteredTemplates} emptyText="暂无企业模板" rowKey="id" /> },
{ label: '短信模板', value: 'sms', content: (
<div className="admin-enterprise-template-list">
{visibleTemplates.map((template) => (
<article className="admin-enterprise-template-row" key={template.id}>
<div className="admin-enterprise-template-row__identity">
<div className="admin-enterprise-template-row__title">
<strong>{template.name}</strong>
<Tag tone={statusTone(template.auditStatus)}>{auditStatusLabel[template.auditStatus] ?? template.auditStatus}</Tag>
</div>
<dl>
<div><dt></dt><dd>{template.tenant?.name ?? template.tenantId}</dd></div>
<div><dt></dt><dd>{template.application?.name ?? template.applicationId}</dd></div>
<div><dt></dt><dd>{template.signature?.name ?? '未绑定'}</dd></div>
</dl>
</div>
<div className="admin-enterprise-template-row__content">
<span></span>
<p>{template.content}</p>
<small>{template.variables?.length ?? 0} </small>
</div>
<div className="admin-enterprise-template-row__meta">
<span></span>
<strong>{formatDate(template.updatedAt)}</strong>
</div>
<div className="admin-enterprise-template-row__actions">
<Button icon={<Eye size={15} />} onClick={() => setTemplatePreview(template)} size="sm" variant="ghost"></Button>
<Button icon={<Edit3 size={15} />} onClick={() => setTemplateModal(template)} size="sm" variant="ghost"></Button>
<Button icon={<Trash2 size={15} />} onClick={() => setDeleteTarget(template)} size="sm" variant="danger"></Button>
</div>
</article>
))}
{filteredTemplates.length === 0 ? <div className="ui-table__empty"></div> : null}
<Pagination
nextDisabled={currentPage >= totalPages}
onNext={() => setPage((value) => Math.min(totalPages, value + 1))}
onPageChange={setPage}
onPrevious={() => setPage((value) => Math.max(1, value - 1))}
page={currentPage}
previousDisabled={currentPage <= 1}
total={filteredTemplates.length}
totalPages={totalPages}
/>
</div>
) },
{ label: '彩信模板', pending: true, value: 'mms', content: <div className="ui-table__empty"></div> },
]}
/>