feat: optimize signature workflows and high-frequency queries

This commit is contained in:
hectorzhao
2026-09-02 15:45:48 +08:00
parent 9b8196ecab
commit ad89e8fed7
48 changed files with 1334 additions and 352 deletions
+24 -15
View File
@@ -276,6 +276,7 @@ export function ClientSignaturesPage() {
const [workspace, setWorkspace] = useState<ClientSignatureWorkspace>(EMPTY_WORKSPACE);
const [keyword, setKeyword] = useState('');
const [applicationFilter, setApplicationFilter] = useState('');
const [appliedFilters, setAppliedFilters] = useState({ keyword: '', applicationId: '' });
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());
const [signatureModal, setSignatureModal] = useState<ClientSmsSignatureView | 'new'>();
const [drainageModal, setDrainageModal] = useState<{ signature: ClientSmsSignatureView; item?: ClientDrainageInfo }>();
@@ -290,18 +291,14 @@ export function ClientSignaturesPage() {
function loadData(targetPage = page) {
const sequence = ++requestSequence.current;
setLoading(true);
Promise.all([
clientApi.listApplicationOptions(),
clientApi.getSignatureWorkspace({
keyword: keyword.trim() || undefined,
applicationId: applicationFilter || undefined,
clientApi.getSignatureWorkspace({
keyword: appliedFilters.keyword || undefined,
applicationId: appliedFilters.applicationId || undefined,
page: targetPage,
pageSize,
}),
])
.then(([applicationItems, signatureWorkspace]) => {
})
.then((signatureWorkspace) => {
if (sequence !== requestSequence.current) return;
setApplications(applicationItems.filter((item) => item.status === 'active'));
setWorkspace(signatureWorkspace);
setError('');
})
@@ -310,9 +307,20 @@ export function ClientSignaturesPage() {
}
useEffect(() => {
const timer = window.setTimeout(() => loadData(page), 300);
return () => window.clearTimeout(timer);
}, [applicationFilter, keyword, page, refreshVersion]);
loadData(page);
}, [appliedFilters, page, refreshVersion]);
useEffect(() => {
let cancelled = false;
void clientApi.listApplicationOptions()
.then((applicationItems) => {
if (!cancelled) setApplications(applicationItems.filter((item) => item.status === 'active'));
})
.catch((failure: Error) => {
if (!cancelled) setError(failure.message || '应用选项加载失败');
});
return () => { cancelled = true; };
}, []);
const filteredItems = workspace.items;
const totalPages = Math.max(1, Math.ceil(workspace.total / pageSize));
@@ -342,7 +350,7 @@ export function ClientSignaturesPage() {
setKeyword('');
setApplicationFilter('');
setPage(1);
setRefreshVersion((version) => version + 1);
setAppliedFilters({ keyword: '', applicationId: '' });
};
return <section className="page-stack client-signature-page">
<header className="client-signature-heading">
@@ -354,8 +362,9 @@ export function ClientSignaturesPage() {
</header>
<div className="client-signature-toolbar">
<Input onChange={(event) => { setKeyword(event.target.value); setPage(1); }} placeholder="搜索签名或应用" prefix={<Search size={17} />} value={keyword} />
<Select onChange={(event) => { setApplicationFilter(event.target.value); setPage(1); }} options={[{ label: '全部应用', value: '' }, ...applications.map((item) => ({ label: item.name, value: item.id }))]} value={applicationFilter} />
<Input onChange={(event) => setKeyword(event.target.value)} placeholder="搜索签名或应用" prefix={<Search size={17} />} value={keyword} />
<Select onChange={(event) => setApplicationFilter(event.target.value)} options={[{ label: '全部应用', value: '' }, ...applications.map((item) => ({ label: item.name, value: item.id }))]} value={applicationFilter} />
<Button icon={<Search size={15} />} onClick={() => { setPage(1); setAppliedFilters({ keyword: keyword.trim(), applicationId: applicationFilter }); }}></Button>
<Button icon={<RotateCcw size={15} />} onClick={resetFilters} variant="ghost"></Button>
</div>
+24 -8
View File
@@ -214,6 +214,7 @@ export function ClientTemplatesPage() {
const [templates, setTemplates] = useState<ClientSmsTemplate[]>([]);
const [signatures, setSignatures] = useState<ClientSmsSignatureView[]>([]);
const [keyword, setKeyword] = useState('');
const [appliedKeyword, setAppliedKeyword] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(true);
const [modalTemplate, setModalTemplate] = useState<ClientSmsTemplate | 'new' | null>(null);
@@ -224,13 +225,11 @@ export function ClientTemplatesPage() {
function loadData(targetPage = page) {
const sequence = ++requestSequence.current;
setLoading(true);
Promise.all([clientApi.listApplicationOptions(), clientApi.listTemplatesPage({ includeHistory: true, keyword: keyword.trim() || undefined, page: targetPage, pageSize }), clientApi.listSignatureOptions()])
.then(([applicationItems, templateResult, signatureItems]) => {
clientApi.listTemplatesPage({ includeHistory: true, keyword: appliedKeyword || undefined, page: targetPage, pageSize })
.then((templateResult) => {
if (sequence !== requestSequence.current) return;
setApplications(applicationItems.filter((item) => item.status === 'active'));
setTemplates(templateResult.items.filter((item) => item.auditStatus !== 'deleted' && item.auditStatus !== 'disabled'));
setTotal(templateResult.total);
setSignatures(signatureItems);
setError('');
})
.catch((reason: Error) => { if (sequence === requestSequence.current) setError(reason.message || '短信模板加载失败'); })
@@ -238,9 +237,22 @@ export function ClientTemplatesPage() {
}
useEffect(() => {
const timer = window.setTimeout(() => loadData(page), 300);
return () => window.clearTimeout(timer);
}, [page, keyword]);
loadData(page);
}, [appliedKeyword, page]);
useEffect(() => {
let cancelled = false;
void Promise.all([clientApi.listApplicationOptions(), clientApi.listSignatureOptions()])
.then(([applicationItems, signatureItems]) => {
if (cancelled) return;
setApplications(applicationItems.filter((item) => item.status === 'active'));
setSignatures(signatureItems);
})
.catch((reason: Error) => {
if (!cancelled) setError(reason.message || '模板选项加载失败');
});
return () => { cancelled = true; };
}, []);
const filteredTemplates = templates;
const totalPages = Math.max(1, Math.ceil(total / pageSize));
@@ -282,11 +294,15 @@ export function ClientTemplatesPage() {
<div className="template-toolbar">
<Input
onChange={(event) => { setKeyword(event.target.value); setPage(1); }}
onChange={(event) => setKeyword(event.target.value)}
placeholder="搜索模板名称、应用、签名或内容"
prefix={<Search size={17} />}
value={keyword}
/>
<div className="ui-query-actions">
<Button icon={<Search size={17} />} onClick={() => { setPage(1); setAppliedKeyword(keyword.trim()); }}></Button>
<Button onClick={() => { setKeyword(''); setPage(1); setAppliedKeyword(''); }} variant="ghost"></Button>
</div>
<Button icon={<Plus size={17} />} onClick={() => setModalTemplate('new')}></Button>
</div>
{loading ? <p className="muted">...</p> : null}