fix: improve enterprise template and signature layouts
This commit is contained in:
@@ -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 }) {
|
||||
|
||||
@@ -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> },
|
||||
]}
|
||||
/>
|
||||
|
||||
+175
-6
@@ -2817,25 +2817,194 @@ h3 {
|
||||
|
||||
.admin-enterprise-signature-list .signature-summary {
|
||||
gap: var(--space-2);
|
||||
grid-template-columns: 22px minmax(96px, 0.9fr) minmax(132px, 1fr) minmax(104px, 0.85fr) repeat(4, minmax(64px, 0.55fr)) minmax(206px, auto);
|
||||
min-width: 1040px;
|
||||
grid-template-columns: 22px minmax(104px, 0.85fr) minmax(132px, 1fr) minmax(104px, 0.8fr) repeat(5, minmax(76px, 0.55fr)) 184px;
|
||||
min-width: 0;
|
||||
padding-inline: var(--space-3);
|
||||
}
|
||||
|
||||
.admin-enterprise-signature-list .signature-actions {
|
||||
display: flex;
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
justify-content: flex-end;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
justify-self: end;
|
||||
min-width: 206px;
|
||||
white-space: nowrap;
|
||||
min-width: 184px;
|
||||
width: 184px;
|
||||
}
|
||||
|
||||
.admin-enterprise-signature-list .signature-actions .ui-button {
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.carrier-report-summary {
|
||||
align-items: flex-start;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.carrier-report-summary small {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-xs);
|
||||
line-height: 24px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.admin-enterprise-template-list {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.admin-enterprise-template-row {
|
||||
align-items: center;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
display: grid;
|
||||
gap: var(--space-5);
|
||||
grid-template-columns: minmax(220px, 1.15fr) minmax(280px, 1.6fr) minmax(136px, 0.65fr) 188px;
|
||||
min-width: 0;
|
||||
padding: var(--space-5);
|
||||
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
|
||||
}
|
||||
|
||||
.admin-enterprise-template-row:hover {
|
||||
border-color: var(--color-border-strong);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.admin-enterprise-template-row__identity,
|
||||
.admin-enterprise-template-row__content,
|
||||
.admin-enterprise-template-row__meta {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.admin-enterprise-template-row__title {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.admin-enterprise-template-row__title > strong {
|
||||
color: var(--color-text-strong);
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
.admin-enterprise-template-row dl {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
margin: var(--space-3) 0 0;
|
||||
}
|
||||
|
||||
.admin-enterprise-template-row dl div {
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
grid-template-columns: 42px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.admin-enterprise-template-row dt,
|
||||
.admin-enterprise-template-row__content > span,
|
||||
.admin-enterprise-template-row__meta > span {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.admin-enterprise-template-row dd {
|
||||
color: var(--color-text-strong);
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.admin-enterprise-template-row__content p {
|
||||
color: var(--color-text-strong);
|
||||
display: -webkit-box;
|
||||
line-height: 1.65;
|
||||
margin: var(--space-2) 0;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.admin-enterprise-template-row__content small {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.admin-enterprise-template-row__meta {
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.admin-enterprise-template-row__meta strong {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.admin-enterprise-template-row__actions {
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.admin-enterprise-template-row__actions .ui-button {
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.admin-enterprise-template-row {
|
||||
grid-template-columns: minmax(210px, 1fr) minmax(260px, 1.35fr) 168px;
|
||||
}
|
||||
|
||||
.admin-enterprise-template-row__meta {
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
.admin-enterprise-template-row__actions {
|
||||
grid-column: 3;
|
||||
grid-row: 1 / span 2;
|
||||
}
|
||||
|
||||
.admin-enterprise-signature-list .signature-summary {
|
||||
grid-template-columns: 22px repeat(7, minmax(76px, 1fr)) 184px;
|
||||
}
|
||||
|
||||
.admin-enterprise-signature-list .signature-summary > div:nth-of-type(3) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
.admin-enterprise-template-row {
|
||||
align-items: stretch;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.admin-enterprise-template-row__meta,
|
||||
.admin-enterprise-template-row__actions {
|
||||
grid-column: auto;
|
||||
grid-row: auto;
|
||||
}
|
||||
|
||||
.admin-enterprise-signature-list .signature-summary {
|
||||
grid-template-columns: 22px repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.admin-enterprise-signature-list .signature-summary > div,
|
||||
.admin-enterprise-signature-list .signature-summary > div:nth-of-type(3) {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.admin-enterprise-signature-list .signature-actions {
|
||||
grid-column: 2 / -1;
|
||||
justify-self: stretch;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.admin-split-filter {
|
||||
align-items: end;
|
||||
display: grid;
|
||||
|
||||
Reference in New Issue
Block a user