feat: harden platform workflows and UI governance
This commit is contained in:
@@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, 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, Pagination, Select, Tabs, Tag, Textarea } from '@/components/ui';
|
||||
import { Breadcrumb, Button, DeleteRiskAction, Input, Modal, Pagination, Select, Tabs, Tag, Textarea } from '@/components/ui';
|
||||
import { replaceLeadingSmsSignature } from '@/utils/smsSignature';
|
||||
|
||||
type TemplateFormState = {
|
||||
@@ -99,6 +99,8 @@ function TemplateFormModal({
|
||||
category: item?.category ?? '行业通知',
|
||||
variables: item?.variables?.map((variable) => ({ name: variable.name, example: variable.example ?? undefined, required: variable.required ?? true })) ?? [],
|
||||
});
|
||||
const initialForm = useRef(form).current;
|
||||
const dirty = JSON.stringify(form) !== JSON.stringify(initialForm);
|
||||
const tenantApplications = applications.filter((application) => application.tenantId === form.tenantId && application.status !== 'deleted');
|
||||
const tenantSignatures = signatures.filter((signature) => (
|
||||
signature.tenantId === form.tenantId
|
||||
@@ -146,9 +148,10 @@ function TemplateFormModal({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
footer={(
|
||||
dirty={dirty}
|
||||
footer={({ requestClose }) => (
|
||||
<>
|
||||
<Button onClick={onClose} variant="ghost">取消</Button>
|
||||
<Button onClick={requestClose} variant="ghost">取消</Button>
|
||||
<Button disabled={!form.tenantId || !form.applicationId || !form.signatureId || !form.name || !form.content.trim()} onClick={() => onSubmit({ ...form, variables: currentVariables })}>保存</Button>
|
||||
</>
|
||||
)}
|
||||
@@ -277,7 +280,6 @@ function ConfirmModal({ message, onCancel, onConfirm }: { message: string; onCan
|
||||
export function AdminEnterpriseTemplatesPage() {
|
||||
const [activeTab, setActiveTab] = useState<'sms' | 'mms'>('sms');
|
||||
const [applications, setApplications] = useState<ClientSmsApplication[]>([]);
|
||||
const [deleteTarget, setDeleteTarget] = useState<ClientSmsTemplate | null>(null);
|
||||
const [enterpriseKeyword, setEnterpriseKeyword] = useState('');
|
||||
const [appliedEnterpriseKeyword, setAppliedEnterpriseKeyword] = useState('');
|
||||
const [applicationKeyword, setApplicationKeyword] = useState('');
|
||||
@@ -363,15 +365,6 @@ export function AdminEnterpriseTemplatesPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmDelete() {
|
||||
if (!deleteTarget) {
|
||||
return;
|
||||
}
|
||||
await adminApi.changeEnterpriseTemplateStatus(deleteTarget.id, 'deleted', '运营端删除模板');
|
||||
setDeleteTarget(null);
|
||||
await loadData();
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="page-stack admin-customer-split-page">
|
||||
<div className="page-heading">
|
||||
@@ -442,7 +435,7 @@ export function AdminEnterpriseTemplatesPage() {
|
||||
<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>
|
||||
<DeleteRiskAction onCompleted={() => void loadData()} portal="admin" targetId={template.id} targetType="template" />
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
@@ -475,13 +468,6 @@ export function AdminEnterpriseTemplatesPage() {
|
||||
/>
|
||||
) : null}
|
||||
{templatePreview ? <TemplatePreviewModal item={templatePreview} onClose={() => setTemplatePreview(null)} /> : null}
|
||||
{deleteTarget ? (
|
||||
<ConfirmModal
|
||||
message={`确认删除模板“${deleteTarget.name}”吗?删除后会写入真实后台。`}
|
||||
onCancel={() => setDeleteTarget(null)}
|
||||
onConfirm={() => { void confirmDelete(); }}
|
||||
/>
|
||||
) : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user