fix: align reporting fields queries and disk monitoring
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
Modal,
|
||||
Pagination,
|
||||
QueryPanel,
|
||||
QueryButtons,
|
||||
Select,
|
||||
Tag,
|
||||
type DateRangeValue,
|
||||
@@ -97,6 +98,7 @@ export function ClientBatchTasksPage() {
|
||||
const [keyword, setKeyword] = useState(() => searchParams.get('taskNo') ?? '');
|
||||
const [application, setApplication] = useState('all');
|
||||
const [submittedDateRange, setSubmittedDateRange] = useState<DateRangeValue>(() => recentBeijingDateRange(7));
|
||||
const [applied, setApplied] = useState(() => ({ keyword, application, submittedDateRange }));
|
||||
const [hoveredTaskId, setHoveredTaskId] = useState<string | null>(null);
|
||||
const [page, setPage] = useState(1);
|
||||
const [selectedTask, setSelectedTask] = useState<BatchTask | null>(null);
|
||||
@@ -106,10 +108,10 @@ export function ClientBatchTasksPage() {
|
||||
function loadTasks(targetPage = page) {
|
||||
setLoading(true);
|
||||
clientApi.listBatchTasksPage({
|
||||
keyword: keyword.trim() || undefined,
|
||||
applicationKeyword: application === 'all' ? undefined : application,
|
||||
createdAtFrom: submittedDateRange.start,
|
||||
createdAtTo: submittedDateRange.end,
|
||||
keyword: applied.keyword.trim() || undefined,
|
||||
applicationKeyword: applied.application === 'all' ? undefined : applied.application,
|
||||
createdAtFrom: applied.submittedDateRange.start,
|
||||
createdAtTo: applied.submittedDateRange.end,
|
||||
page: targetPage,
|
||||
pageSize,
|
||||
})
|
||||
@@ -124,7 +126,21 @@ export function ClientBatchTasksPage() {
|
||||
|
||||
useEffect(() => {
|
||||
loadTasks(page);
|
||||
}, [page]);
|
||||
}, [applied, page]);
|
||||
|
||||
function query() {
|
||||
setPage(1);
|
||||
setApplied({ keyword, application, submittedDateRange });
|
||||
}
|
||||
|
||||
function reset() {
|
||||
const defaults = { keyword: '', application: 'all', submittedDateRange: recentBeijingDateRange(7) };
|
||||
setKeyword('');
|
||||
setApplication('all');
|
||||
setSubmittedDateRange(defaults.submittedDateRange);
|
||||
setPage(1);
|
||||
setApplied(defaults);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
clientApi.listApplicationOptions()
|
||||
@@ -236,8 +252,7 @@ export function ClientBatchTasksPage() {
|
||||
onChange={(event) => setKeyword(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
setPage(1);
|
||||
loadTasks(1);
|
||||
query();
|
||||
}
|
||||
}}
|
||||
placeholder="输入发送批次号搜索"
|
||||
@@ -246,7 +261,7 @@ export function ClientBatchTasksPage() {
|
||||
/>
|
||||
<Select label="选择应用" onChange={(event) => setApplication(event.target.value)} options={applicationOptions} value={application} />
|
||||
<DateRangeInput label="提交时间" onChange={setSubmittedDateRange} value={submittedDateRange} />
|
||||
<Button onClick={() => { setPage(1); loadTasks(1); }} variant="primary">查询</Button>
|
||||
<QueryButtons onQuery={query} onReset={reset} />
|
||||
</QueryPanel>
|
||||
|
||||
<div className="surface batch-table-card">
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { ClientBatchTasksPage } from './ClientBatchTasksPage';
|
||||
import { ClientSendDetailPage } from './ClientSendDetailPage';
|
||||
import { ClientUplinkMessagesPage } from './ClientUplinkMessagesPage';
|
||||
|
||||
const { clientApi } = vi.hoisted(() => ({ clientApi: {
|
||||
listApplicationOptions: vi.fn(), listBatchTasksPage: vi.fn(), listMessages: vi.fn(), listUplinkMessagesPage: vi.fn(),
|
||||
} }));
|
||||
vi.mock('@/api/adminApi', () => ({ clientApi }));
|
||||
|
||||
describe('explicit client queries', () => {
|
||||
beforeEach(() => {
|
||||
Object.values(clientApi).forEach((mock) => mock.mockReset());
|
||||
clientApi.listApplicationOptions.mockResolvedValue([]);
|
||||
for (const method of [clientApi.listBatchTasksPage, clientApi.listMessages, clientApi.listUplinkMessagesPage]) method.mockResolvedValue({ items: [], total: 25, page: 1, pageSize: 10 });
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ Component: ClientBatchTasksPage, method: clientApi.listBatchTasksPage, label: '发送批次号', key: 'keyword' },
|
||||
{ Component: ClientSendDetailPage, method: clientApi.listMessages, label: '短信内容', key: 'contentKeyword' },
|
||||
{ Component: ClientUplinkMessagesPage, method: clientApi.listUplinkMessagesPage, label: '上行内容', key: 'keyword' },
|
||||
])('$label only applies filters on Query or Reset, including pagination back to page one', async ({ Component, method, label, key }) => {
|
||||
render(<MemoryRouter><Component /></MemoryRouter>);
|
||||
await waitFor(() => expect(method).toHaveBeenCalledTimes(1));
|
||||
fireEvent.change(screen.getByLabelText(label), { target: { value: '待查询' } });
|
||||
await act(async () => { await new Promise((resolve) => setTimeout(resolve, 350)); });
|
||||
expect(method).toHaveBeenCalledTimes(1);
|
||||
fireEvent.click(screen.getByRole('button', { name: '下一页' }));
|
||||
await waitFor(() => expect(method).toHaveBeenLastCalledWith(expect.objectContaining({ page: 2, [key]: undefined })));
|
||||
fireEvent.click(screen.getByRole('button', { name: '查询' }));
|
||||
await waitFor(() => expect(method).toHaveBeenLastCalledWith(expect.objectContaining({ page: 1, [key]: '待查询' })));
|
||||
fireEvent.change(screen.getByLabelText(label), { target: { value: '未提交' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '下一页' }));
|
||||
await waitFor(() => expect(method).toHaveBeenLastCalledWith(expect.objectContaining({ page: 2, [key]: '待查询' })));
|
||||
fireEvent.click(screen.getByRole('button', { name: '上一页' }));
|
||||
await waitFor(() => expect(method).toHaveBeenLastCalledWith(expect.objectContaining({ page: 1, [key]: '待查询' })));
|
||||
fireEvent.click(screen.getByRole('button', { name: '重置' }));
|
||||
await waitFor(() => expect(method).toHaveBeenLastCalledWith(expect.objectContaining({ page: 1, [key]: undefined })));
|
||||
expect(screen.getByLabelText(label)).toHaveValue('');
|
||||
});
|
||||
});
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
Input,
|
||||
Pagination,
|
||||
QueryPanel,
|
||||
QueryButtons,
|
||||
Select,
|
||||
Tag,
|
||||
type DateRangeValue,
|
||||
@@ -71,6 +72,7 @@ export function ClientSendDetailPage() {
|
||||
const [dateRange, setDateRange] = useState<DateRangeValue>(() => recentBeijingDateRange(7));
|
||||
const [contentKeyword, setContentKeyword] = useState('');
|
||||
const [phoneKeyword, setPhoneKeyword] = useState('');
|
||||
const [applied, setApplied] = useState(() => ({ applicationId, status, dateRange, contentKeyword, phoneKeyword }));
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [applications, setApplications] = useState<Array<{ id: string; name: string }>>([]);
|
||||
@@ -80,12 +82,12 @@ export function ClientSendDetailPage() {
|
||||
function loadData(targetPage = page) {
|
||||
setLoading(true);
|
||||
clientApi.listMessages({
|
||||
applicationId: applicationId === 'all' ? undefined : applicationId,
|
||||
phoneNumber: phoneKeyword || undefined,
|
||||
status: status === 'all' ? undefined : status,
|
||||
contentKeyword: contentKeyword || undefined,
|
||||
queuedAtFrom: dateRange.start || undefined,
|
||||
queuedAtTo: dateRange.end || undefined,
|
||||
applicationId: applied.applicationId === 'all' ? undefined : applied.applicationId,
|
||||
phoneNumber: applied.phoneKeyword.trim() || undefined,
|
||||
status: applied.status === 'all' ? undefined : applied.status,
|
||||
contentKeyword: applied.contentKeyword.trim() || undefined,
|
||||
queuedAtFrom: applied.dateRange.start || undefined,
|
||||
queuedAtTo: applied.dateRange.end || undefined,
|
||||
page: targetPage,
|
||||
pageSize: 10,
|
||||
})
|
||||
@@ -100,7 +102,7 @@ export function ClientSendDetailPage() {
|
||||
|
||||
useEffect(() => {
|
||||
loadData(page);
|
||||
}, [applicationId, contentKeyword, dateRange.end, dateRange.start, page, phoneKeyword, status]);
|
||||
}, [applied, page]);
|
||||
|
||||
useEffect(() => {
|
||||
clientApi.listApplicationOptions()
|
||||
@@ -121,9 +123,21 @@ export function ClientSendDetailPage() {
|
||||
const currentPage = Math.min(page, totalPages);
|
||||
const visibleRows = filteredRows;
|
||||
|
||||
useEffect(() => {
|
||||
function query() {
|
||||
setPage(1);
|
||||
}, [applicationId, contentKeyword, dateRange.end, dateRange.start, phoneKeyword, status]);
|
||||
setApplied({ applicationId, status, dateRange, contentKeyword, phoneKeyword });
|
||||
}
|
||||
|
||||
function reset() {
|
||||
const defaults = { applicationId: 'all', status: 'all', dateRange: recentBeijingDateRange(7), contentKeyword: '', phoneKeyword: '' };
|
||||
setApplicationId(defaults.applicationId);
|
||||
setStatus(defaults.status);
|
||||
setDateRange(defaults.dateRange);
|
||||
setContentKeyword('');
|
||||
setPhoneKeyword('');
|
||||
setPage(1);
|
||||
setApplied(defaults);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="page-stack">
|
||||
@@ -165,6 +179,7 @@ export function ClientSendDetailPage() {
|
||||
prefix={<Smartphone size={16} />}
|
||||
value={phoneKeyword}
|
||||
/>
|
||||
<QueryButtons onQuery={query} onReset={reset} />
|
||||
</QueryPanel>
|
||||
|
||||
{error ? <p className="form-error">{error}</p> : null}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fireEvent, render, screen, within } from '@testing-library/react';
|
||||
import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { ClientSignaturesPage } from './ClientSignaturesPage';
|
||||
|
||||
@@ -57,6 +57,7 @@ describe('ClientSignaturesPage drainage presentation', () => {
|
||||
clientApi.listApplicationOptions.mockResolvedValue([{ id: 'app-1', name: '测试应用', status: 'active' }]);
|
||||
clientApi.getSignatureWorkspace.mockResolvedValue({ items: [signature], summary: { total: 1, pending: 0, approved: 1, rejected: 0, draft: 0 }, total: 1, page: 1, pageSize: 10 });
|
||||
clientApi.listApplicationReportFields.mockResolvedValue([]);
|
||||
clientApi.listCommonApplicationReportFields.mockResolvedValue([]);
|
||||
});
|
||||
|
||||
it('groups the three real carrier summaries into a readable status area', async () => {
|
||||
@@ -82,4 +83,32 @@ describe('ClientSignaturesPage drainage presentation', () => {
|
||||
expect(within(dialog).queryByLabelText('名称')).not.toBeInTheDocument();
|
||||
expect(within(dialog).queryByLabelText('访问地址')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('keeps the newest application field requirements when older requests finish late', async () => {
|
||||
let resolveOld!: (value: unknown[]) => void;
|
||||
clientApi.listCommonApplicationReportFields.mockReturnValue(new Promise((resolve) => { resolveOld = resolve; }));
|
||||
clientApi.listApplicationReportFields.mockResolvedValue([{ id: 'current', code: 'owner', name: '应用最新主体', fieldType: 'string', required: true }]);
|
||||
render(<ClientSignaturesPage />);
|
||||
await screen.findByText('【测试签名】');
|
||||
fireEvent.click(screen.getByRole('button', { name: '新增签名' }));
|
||||
const dialog = screen.getByRole('dialog', { name: '新增签名' });
|
||||
expect(within(dialog).getByRole('button', { name: '提交审核' })).toBeDisabled();
|
||||
fireEvent.change(within(dialog).getByLabelText('所属应用'), { target: { value: 'app-1' } });
|
||||
await within(dialog).findByLabelText('* 应用最新主体');
|
||||
await act(async () => resolveOld([{ id: 'old', code: 'old', name: '过期字段', fieldType: 'string', required: false }]));
|
||||
expect(within(dialog).queryByLabelText('过期字段')).not.toBeInTheDocument();
|
||||
expect(within(dialog).getByLabelText('* 应用最新主体')).toBeVisible();
|
||||
});
|
||||
|
||||
it('blocks signature submission when current reporting requirements cannot be loaded', async () => {
|
||||
clientApi.listCommonApplicationReportFields.mockRejectedValue(new Error('字段加载失败'));
|
||||
render(<ClientSignaturesPage />);
|
||||
await screen.findByText('【测试签名】');
|
||||
fireEvent.click(screen.getByRole('button', { name: '新增签名' }));
|
||||
const dialog = screen.getByRole('dialog', { name: '新增签名' });
|
||||
fireEvent.change(within(dialog).getByLabelText('短信签名'), { target: { value: '【新签名】' } });
|
||||
await waitFor(() => expect(within(dialog).getByText('字段加载失败')).toBeVisible());
|
||||
expect(within(dialog).getByRole('button', { name: '提交审核' })).toBeDisabled();
|
||||
expect(clientApi.createSignature).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { ChevronDown, ChevronRight, Edit3, FileCheck2, Globe2, Plus, RotateCcw, Search, Trash2, Upload } from 'lucide-react';
|
||||
import { Button, CarrierTag, DeleteRiskAction, FileActions, Input, Modal, Pagination, Select, Textarea } from '@/components/ui';
|
||||
import {
|
||||
@@ -72,6 +72,7 @@ function ReviewFields({
|
||||
? <Input
|
||||
key={field.id}
|
||||
label={`${field.required ? '* ' : ''}${field.name}`}
|
||||
hint={field.description ?? undefined}
|
||||
onChange={(event) => onChange(field.code, event.target.value)}
|
||||
value={String(values[field.code] ?? '')}
|
||||
/>
|
||||
@@ -103,7 +104,10 @@ function SignatureModal({
|
||||
}) {
|
||||
const [applicationId, setApplicationId] = useState(signature?.applicationId ?? '');
|
||||
const [name, setName] = useState(signature?.name ?? '');
|
||||
const [fields, setFields] = useState<ClientApplicationReportField[]>([]);
|
||||
const [fieldResult, setFieldResult] = useState<{ applicationId: string; fields: ClientApplicationReportField[]; error: string } | null>(null);
|
||||
const fieldsLoading = fieldResult?.applicationId !== applicationId;
|
||||
const fields = fieldsLoading ? [] : fieldResult?.fields ?? [];
|
||||
const fieldsError = fieldsLoading ? '' : fieldResult?.error ?? '';
|
||||
const [values, setValues] = useState<Record<string, unknown>>(signature?.reportValues ?? {});
|
||||
const [uploadingCode, setUploadingCode] = useState('');
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -111,10 +115,13 @@ function SignatureModal({
|
||||
const [nameInputError, setNameInputError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
const request = applicationId
|
||||
? clientApi.listApplicationReportFields(applicationId, 'signature')
|
||||
: clientApi.listCommonApplicationReportFields('signature');
|
||||
request.then(setFields).catch((failure: Error) => setError(failure.message || '审核资料加载失败'));
|
||||
request.then((items) => { if (active) setFieldResult({ applicationId, fields: items, error: '' }); })
|
||||
.catch((failure: Error) => { if (active) setFieldResult({ applicationId, fields: [], error: failure.message || '审核资料加载失败' }); });
|
||||
return () => { active = false; };
|
||||
}, [applicationId]);
|
||||
|
||||
async function upload(field: ClientApplicationReportField, file?: File) {
|
||||
@@ -132,6 +139,7 @@ function SignatureModal({
|
||||
}
|
||||
|
||||
async function save() {
|
||||
if (fieldsLoading || fieldsError) return;
|
||||
if (!isCompleteSmsSignature(name)) {
|
||||
setError(getSmsSignatureValidationError(name) ?? '短信签名格式不正确');
|
||||
return;
|
||||
@@ -157,7 +165,7 @@ function SignatureModal({
|
||||
const signatureNameValid = !nameInputError && isCompleteSmsSignature(name);
|
||||
const signatureNameError = nameInputError || (name ? getSmsSignatureValidationError(name) : undefined);
|
||||
return <Modal
|
||||
footer={<><Button onClick={onClose} variant="ghost">取消</Button><Button disabled={!signatureNameValid || missingRequired || saving || Boolean(uploadingCode)} onClick={() => void save()}>{saving ? '提交中...' : '提交审核'}</Button></>}
|
||||
footer={<><Button onClick={onClose} variant="ghost">取消</Button><Button disabled={!signatureNameValid || missingRequired || saving || fieldsLoading || Boolean(fieldsError) || Boolean(uploadingCode)} onClick={() => void save()}>{saving ? '提交中...' : '提交审核'}</Button></>}
|
||||
onClose={onClose}
|
||||
open
|
||||
size="xl"
|
||||
@@ -190,7 +198,7 @@ function SignatureModal({
|
||||
/>
|
||||
<section className="client-signature-form-section">
|
||||
<div><h3>审核资料</h3><p>请按要求填写或上传,资料仅用于签名审核。</p></div>
|
||||
<ReviewFields fields={fields} onChange={(code, value) => setValues((current) => ({ ...current, [code]: value }))} onUpload={(field, file) => void upload(field, file)} uploadingCode={uploadingCode} values={values} />
|
||||
{fieldsLoading ? <p>正在加载报备资料要求...</p> : fieldsError ? <p className="form-error">{fieldsError}</p> : <ReviewFields fields={fields} onChange={(code, value) => setValues((current) => ({ ...current, [code]: value }))} onUpload={(field, file) => void upload(field, file)} uploadingCode={uploadingCode} values={values} />}
|
||||
</section>
|
||||
{error ? <p className="form-error">{error}</p> : null}
|
||||
</div>
|
||||
@@ -263,6 +271,7 @@ function DrainageModal({ item, signature, onClose, onSaved }: { item?: ClientDra
|
||||
}
|
||||
|
||||
export function ClientSignaturesPage() {
|
||||
const requestSequence = useRef(0);
|
||||
const [applications, setApplications] = useState<ClientSmsApplication[]>([]);
|
||||
const [workspace, setWorkspace] = useState<ClientSignatureWorkspace>(EMPTY_WORKSPACE);
|
||||
const [keyword, setKeyword] = useState('');
|
||||
@@ -279,6 +288,7 @@ export function ClientSignaturesPage() {
|
||||
const pageSize = 10;
|
||||
|
||||
function loadData(targetPage = page) {
|
||||
const sequence = ++requestSequence.current;
|
||||
setLoading(true);
|
||||
Promise.all([
|
||||
clientApi.listApplicationOptions(),
|
||||
@@ -290,12 +300,13 @@ export function ClientSignaturesPage() {
|
||||
}),
|
||||
])
|
||||
.then(([applicationItems, signatureWorkspace]) => {
|
||||
if (sequence !== requestSequence.current) return;
|
||||
setApplications(applicationItems.filter((item) => item.status === 'active'));
|
||||
setWorkspace(signatureWorkspace);
|
||||
setError('');
|
||||
})
|
||||
.catch((failure: Error) => setError(failure.message || '签名与引流信息加载失败'))
|
||||
.finally(() => setLoading(false));
|
||||
.catch((failure: Error) => { if (sequence === requestSequence.current) setError(failure.message || '签名与引流信息加载失败'); })
|
||||
.finally(() => { if (sequence === requestSequence.current) setLoading(false); });
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -209,6 +209,7 @@ function TemplateModal({
|
||||
}
|
||||
|
||||
export function ClientTemplatesPage() {
|
||||
const requestSequence = useRef(0);
|
||||
const [applications, setApplications] = useState<ClientSmsApplication[]>([]);
|
||||
const [templates, setTemplates] = useState<ClientSmsTemplate[]>([]);
|
||||
const [signatures, setSignatures] = useState<ClientSmsSignatureView[]>([]);
|
||||
@@ -221,17 +222,19 @@ export function ClientTemplatesPage() {
|
||||
const pageSize = 10;
|
||||
|
||||
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]) => {
|
||||
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) => setError(reason.message || '短信模板加载失败'))
|
||||
.finally(() => setLoading(false));
|
||||
.catch((reason: Error) => { if (sequence === requestSequence.current) setError(reason.message || '短信模板加载失败'); })
|
||||
.finally(() => { if (sequence === requestSequence.current) setLoading(false); });
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Modal,
|
||||
Pagination,
|
||||
QueryPanel,
|
||||
QueryButtons,
|
||||
Table,
|
||||
type DateRangeValue,
|
||||
type TableColumn,
|
||||
@@ -30,6 +31,7 @@ export function ClientUplinkMessagesPage() {
|
||||
const [phoneKeyword, setPhoneKeyword] = useState('');
|
||||
const [contentKeyword, setContentKeyword] = useState('');
|
||||
const [dateRange, setDateRange] = useState<DateRangeValue>(() => recentBeijingDateRange(7));
|
||||
const [applied, setApplied] = useState(() => ({ phoneKeyword, contentKeyword, dateRange }));
|
||||
const [selectedMessage, setSelectedMessage] = useState<SmsUplinkMessage | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [matching, setMatching] = useState(false);
|
||||
@@ -41,7 +43,7 @@ export function ClientUplinkMessagesPage() {
|
||||
|
||||
function loadData(targetPage = page) {
|
||||
setLoading(true);
|
||||
clientApi.listUplinkMessagesPage({ phoneNumber: phoneKeyword || undefined, keyword: contentKeyword || undefined, startTime: dateRange.start ? `${dateRange.start}T00:00:00+08:00` : undefined, endTime: dateRange.end ? `${dateRange.end}T23:59:59+08:00` : undefined, page: targetPage, pageSize })
|
||||
clientApi.listUplinkMessagesPage({ phoneNumber: applied.phoneKeyword.trim() || undefined, keyword: applied.contentKeyword.trim() || undefined, startTime: applied.dateRange.start ? `${applied.dateRange.start}T00:00:00+08:00` : undefined, endTime: applied.dateRange.end ? `${applied.dateRange.end}T23:59:59+08:00` : undefined, page: targetPage, pageSize })
|
||||
.then((result) => {
|
||||
setMessages(result.items);
|
||||
setTotal(result.total);
|
||||
@@ -68,14 +70,22 @@ export function ClientUplinkMessagesPage() {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setPage(1);
|
||||
const timer = window.setTimeout(() => loadData(1), 300);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [phoneKeyword, contentKeyword, dateRange.start, dateRange.end]);
|
||||
loadData(page);
|
||||
}, [applied, page]);
|
||||
|
||||
useEffect(() => {
|
||||
if (page > 1) loadData(page);
|
||||
}, [page]);
|
||||
function query() {
|
||||
setPage(1);
|
||||
setApplied({ phoneKeyword, contentKeyword, dateRange });
|
||||
}
|
||||
|
||||
function reset() {
|
||||
const defaults = { phoneKeyword: '', contentKeyword: '', dateRange: recentBeijingDateRange(7) };
|
||||
setPhoneKeyword('');
|
||||
setContentKeyword('');
|
||||
setDateRange(defaults.dateRange);
|
||||
setPage(1);
|
||||
setApplied(defaults);
|
||||
}
|
||||
|
||||
const columns = useMemo<Array<TableColumn<SmsUplinkMessage>>>(() => [
|
||||
{ key: 'phoneNumber', title: '手机号码', width: '180px', render: (record) => <strong>{record.phoneNumber}</strong> },
|
||||
@@ -119,6 +129,7 @@ export function ClientUplinkMessagesPage() {
|
||||
prefix={<Search size={16} />}
|
||||
value={contentKeyword}
|
||||
/>
|
||||
<QueryButtons onQuery={query} onReset={reset} />
|
||||
</QueryPanel>
|
||||
|
||||
{error ? <p className="form-error">{error}</p> : null}
|
||||
|
||||
Reference in New Issue
Block a user