fix: connect remaining sms pages to real backend
This commit is contained in:
@@ -1,400 +1,160 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { CalendarDays, Check, Search, X } from 'lucide-react';
|
||||
import { adminApi, type RiskReviewTask } from '@/api/adminApi';
|
||||
import { Breadcrumb, Button, Input, Modal, Select, Table, Tag, Textarea, type TableColumn } from '@/components/ui';
|
||||
|
||||
type SmsAuditStatus = 'pending' | 'approved' | 'rejected';
|
||||
|
||||
type SmsAuditRecord = {
|
||||
id: string;
|
||||
customer: string;
|
||||
industry: string;
|
||||
application: string;
|
||||
submittedAt: string;
|
||||
content: string;
|
||||
chars: number;
|
||||
billCount: number;
|
||||
phoneCount: number;
|
||||
reasons: string[];
|
||||
status: SmsAuditStatus;
|
||||
reviewedAt?: string;
|
||||
const statusLabel: Record<string, string> = {
|
||||
pending_review: '待审核',
|
||||
approved: '已通过',
|
||||
rejected: '已驳回',
|
||||
};
|
||||
|
||||
type PhoneRecord = {
|
||||
id: number;
|
||||
phone: string;
|
||||
location: string;
|
||||
carrier: string;
|
||||
const statusTone: Record<string, 'warning' | 'success' | 'danger'> = {
|
||||
pending_review: 'warning',
|
||||
approved: 'success',
|
||||
rejected: 'danger',
|
||||
};
|
||||
|
||||
const initialSmsAudits: SmsAuditRecord[] = [
|
||||
{
|
||||
id: 'SMS-20250106-001',
|
||||
customer: '广州XXXXX有限公司',
|
||||
industry: 'XXXXXXXX行业',
|
||||
application: '应用名称示例',
|
||||
submittedAt: '2025-01-06 09:02:28',
|
||||
content: '【深圳市北健科技有限公司】KH58户主,您录名的您好您好。1)童套充不要禁运控。2)禁止去速1200m左右的来水,收费收在2;3)恐怕接待,我接待您恐来子。单,现现确下里况要可图的注明技术想上,统统统他只有禁运信息;最可以请点仙应出他有记。',
|
||||
chars: 300,
|
||||
billCount: 1,
|
||||
phoneCount: 256,
|
||||
reasons: ['模板', '签名', '引流信息'],
|
||||
status: 'pending',
|
||||
},
|
||||
{
|
||||
id: 'SMS-20250105-001',
|
||||
customer: '上海XXXXX有限公司',
|
||||
industry: 'XXXXXXXX行业',
|
||||
application: '客户通知服务',
|
||||
submittedAt: '2025-01-05 08:02:28',
|
||||
content: '【某某公司】尊敬的客户您好,您好。感谢您选择光纤宽带!如遇问题请联系客服...',
|
||||
chars: 100,
|
||||
billCount: 1,
|
||||
phoneCount: 156,
|
||||
reasons: ['模板', '签名', '引流信息'],
|
||||
status: 'approved',
|
||||
reviewedAt: '2026-01-12 17:27:28',
|
||||
},
|
||||
{
|
||||
id: 'SMS-20250105-002',
|
||||
customer: '上海XXXXX有限公司',
|
||||
industry: 'XXXXXXXX行业',
|
||||
application: '会员通知',
|
||||
submittedAt: '2025-01-05 08:02:28',
|
||||
content: '【某某公司】尊敬的客户您好,您好。',
|
||||
chars: 50,
|
||||
billCount: 1,
|
||||
phoneCount: 2762,
|
||||
reasons: ['模板', '签名'],
|
||||
status: 'approved',
|
||||
reviewedAt: '2026-01-12 17:27:28',
|
||||
},
|
||||
{
|
||||
id: 'SMS-20250105-003',
|
||||
customer: '北京XXXXX有限公司',
|
||||
industry: 'XXXXXXXX营销',
|
||||
application: '营销推广平台',
|
||||
submittedAt: '2025-01-05 08:02:28',
|
||||
content: '【大童芝】签住链,应控计 移奇亭某间遇道进使用请您动幼时此从此出收在2026-01-06 17:00-17:30至取到交投买元画山点。',
|
||||
chars: 200,
|
||||
billCount: 1,
|
||||
phoneCount: 256,
|
||||
reasons: ['模板'],
|
||||
status: 'pending',
|
||||
},
|
||||
];
|
||||
|
||||
const rejectReasons = ['内容不发', '模板未提交', '签名未报备', '引流未报备完成', '内容包含敏感词', '号码格式错误', '缺少必要信息', '违反运营商规定', '签名与内容不符', '模板变量不匹配'];
|
||||
|
||||
const initialPhoneRows: PhoneRecord[] = [
|
||||
{ id: 1, phone: '188xxxx9999', location: '河南 信阳', carrier: '移动' },
|
||||
{ id: 2, phone: '133xxxx1111', location: '河南 信阳', carrier: '移动' },
|
||||
{ id: 3, phone: '134xxxx2222', location: '河南 信阳', carrier: '移动' },
|
||||
{ id: 4, phone: '156xxxxyyyy', location: '山东 青岛', carrier: '联通' },
|
||||
{ id: 5, phone: '178625xxxxx', location: '山东 青岛', carrier: '联通' },
|
||||
{ id: 6, phone: '190xxxxyyyy', location: '山东 青岛', carrier: '联通' },
|
||||
{ id: 7, phone: '190xxxxyyyy', location: '山东 青岛', carrier: '联通' },
|
||||
{ id: 8, phone: '190xxxxyyyy', location: '山东 青岛', carrier: '电信' },
|
||||
{ id: 9, phone: '177xxxx5555', location: '北京', carrier: '移动' },
|
||||
{ id: 10, phone: '189xxxx6666', location: '上海', carrier: '电信' },
|
||||
];
|
||||
|
||||
function SmsEditModal({
|
||||
record,
|
||||
onClose,
|
||||
onSubmit,
|
||||
}: {
|
||||
record: SmsAuditRecord;
|
||||
onClose: () => void;
|
||||
onSubmit: (content: string) => void;
|
||||
}) {
|
||||
const [content, setContent] = useState(record.content);
|
||||
const billingCount = Math.max(1, Math.ceil(content.length / 67));
|
||||
|
||||
return (
|
||||
<Modal
|
||||
footer={(
|
||||
<>
|
||||
<Button onClick={onClose} variant="ghost">取消</Button>
|
||||
<Button onClick={() => onSubmit(content)}>确认修改</Button>
|
||||
</>
|
||||
)}
|
||||
onClose={onClose}
|
||||
open
|
||||
size="xl"
|
||||
title={<div className="template-modal-title"><h2>修改短信内容</h2><p>请修改短信内容后点击确认</p></div>}
|
||||
>
|
||||
<div className="sms-audit-edit">
|
||||
<div className="sms-audit-edit__meta">
|
||||
<span>企业名称:<strong>{record.customer}</strong></span>
|
||||
<span>所属应用:<strong>{record.application}</strong></span>
|
||||
<span>提交时间:<strong>{record.submittedAt}</strong></span>
|
||||
</div>
|
||||
<Textarea label="短信内容" onChange={(event) => setContent(event.target.value)} rows={9} value={content} />
|
||||
<div className="sms-audit-edit__count">
|
||||
<span>已输入 {content.length} 字</span>
|
||||
<strong>计费 {billingCount} 条</strong>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function SmsRejectModal({
|
||||
record,
|
||||
onClose,
|
||||
onSubmit,
|
||||
}: {
|
||||
record: SmsAuditRecord;
|
||||
onClose: () => void;
|
||||
onSubmit: (reason: string) => void;
|
||||
}) {
|
||||
const [reason, setReason] = useState('');
|
||||
|
||||
function appendReason(nextReason: string) {
|
||||
setReason((current) => (current ? `${current};${nextReason}` : nextReason));
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
footer={(
|
||||
<>
|
||||
<Button onClick={() => onSubmit(reason)} disabled={!reason.trim()}>确认</Button>
|
||||
<Button onClick={onClose} variant="ghost">取消</Button>
|
||||
</>
|
||||
)}
|
||||
onClose={onClose}
|
||||
open
|
||||
title={<div className="template-modal-title"><h2>确认驳回</h2><p>请选择或输入驳回原因</p></div>}
|
||||
>
|
||||
<div className="sms-reject-modal">
|
||||
<Textarea label="* 驳回原因" onChange={(event) => setReason(event.target.value)} placeholder="请输入驳回原因" rows={4} value={reason} />
|
||||
<div>
|
||||
<strong>常见原因:</strong>
|
||||
<div className="reject-reason-tags">
|
||||
{rejectReasons.map((item) => (
|
||||
<button key={item} onClick={() => appendReason(item)} type="button">{item}</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<button className="audit-collapse-link" type="button">收起</button>
|
||||
<p className="muted">当前处理:{record.customer}</p>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function PhoneListModal({
|
||||
record,
|
||||
onClose,
|
||||
}: {
|
||||
record: SmsAuditRecord;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const phoneRows = useMemo(
|
||||
() => initialPhoneRows.filter((item) => !keyword || item.phone.includes(keyword)),
|
||||
[keyword],
|
||||
);
|
||||
const columns: Array<TableColumn<PhoneRecord>> = [
|
||||
{ key: 'id', title: '序号', width: '120px', render: (item) => item.id },
|
||||
{ key: 'phone', title: '手机号码', render: (item) => <strong>{item.phone}</strong> },
|
||||
{ key: 'location', title: '号码归属地', render: (item) => item.location },
|
||||
{ key: 'carrier', title: '运营商', render: (item) => item.carrier },
|
||||
];
|
||||
|
||||
return (
|
||||
<Modal
|
||||
footer={<Button onClick={onClose} variant="ghost">关闭</Button>}
|
||||
onClose={onClose}
|
||||
open
|
||||
size="xl"
|
||||
title={<div className="template-modal-title"><h2>号码列表</h2><p>{record.customer},共 {record.phoneCount} 个号码</p></div>}
|
||||
>
|
||||
<div className="phone-list-modal">
|
||||
<Input
|
||||
autoFocus
|
||||
onChange={(event) => setKeyword(event.target.value)}
|
||||
placeholder="手机号"
|
||||
prefix={<Search size={18} />}
|
||||
value={keyword}
|
||||
/>
|
||||
<div className="phone-list-toolbar">
|
||||
<Select
|
||||
options={[
|
||||
{ label: '10条/页', value: '10' },
|
||||
{ label: '20条/页', value: '20' },
|
||||
{ label: '50条/页', value: '50' },
|
||||
]}
|
||||
value="10"
|
||||
/>
|
||||
<div>
|
||||
<Button disabled size="sm" variant="ghost">上一页</Button>
|
||||
<Button size="sm" variant="ghost">24</Button>
|
||||
<Button size="sm" variant="secondary">25</Button>
|
||||
<Button size="sm" variant="ghost">26</Button>
|
||||
<span>...</span>
|
||||
<Button size="sm" variant="ghost">63</Button>
|
||||
<Button size="sm" variant="ghost">下一页</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="phone-list-table">
|
||||
<Table columns={columns} data={phoneRows} rowKey="id" />
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export function AdminSmsAuditPage() {
|
||||
const [records, setRecords] = useState(initialSmsAudits);
|
||||
const [records, setRecords] = useState<RiskReviewTask[]>([]);
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [company, setCompany] = useState('');
|
||||
const [application, setApplication] = useState('');
|
||||
const [status, setStatus] = useState('pending_review');
|
||||
const [date, setDate] = useState('');
|
||||
const [editRecord, setEditRecord] = useState<SmsAuditRecord | null>(null);
|
||||
const [rejectRecord, setRejectRecord] = useState<SmsAuditRecord | null>(null);
|
||||
const [phoneListRecord, setPhoneListRecord] = useState<SmsAuditRecord | null>(null);
|
||||
const [error, setError] = useState('');
|
||||
const [approveTarget, setApproveTarget] = useState<RiskReviewTask | 'batch' | null>(null);
|
||||
const [rejectTarget, setRejectTarget] = useState<RiskReviewTask | null>(null);
|
||||
const [rejectReason, setRejectReason] = useState('');
|
||||
|
||||
function loadData() {
|
||||
adminApi.listRiskReviewTasks({ status: status === 'all' ? undefined : status })
|
||||
.then((items) => {
|
||||
setRecords(items);
|
||||
setError('');
|
||||
})
|
||||
.catch((failure: Error) => setError(failure.message || '短信审核任务加载失败'));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, [status]);
|
||||
|
||||
const filteredRecords = useMemo(
|
||||
() => records.filter((record) => {
|
||||
const matchesCompany = !company || record.customer === company;
|
||||
const matchesApplication = !application || record.application === application;
|
||||
const matchesKeyword = !keyword || record.content.includes(keyword);
|
||||
const matchesDate = !date || record.submittedAt.startsWith(date);
|
||||
return matchesCompany && matchesApplication && matchesKeyword && matchesDate;
|
||||
const matchesKeyword = !keyword || [record.taskNo, record.content, record.reviewReason, record.rejectReason].join(' ').includes(keyword);
|
||||
const matchesDate = !date || record.createdAt.startsWith(date);
|
||||
return matchesKeyword && matchesDate;
|
||||
}),
|
||||
[application, company, date, keyword, records],
|
||||
[date, keyword, records],
|
||||
);
|
||||
|
||||
function updateStatus(id: string, status: SmsAuditStatus) {
|
||||
setRecords((items) => items.map((item) => (
|
||||
item.id === id ? { ...item, status, reviewedAt: status === 'pending' ? undefined : '2026-01-12 17:27:28' } : item
|
||||
)));
|
||||
async function approveRecord(record: RiskReviewTask) {
|
||||
await adminApi.approveRiskReviewTask(record.id, '运营审核通过');
|
||||
setApproveTarget(null);
|
||||
loadData();
|
||||
}
|
||||
|
||||
function updateContent(content: string) {
|
||||
if (!editRecord) return;
|
||||
setRecords((items) => items.map((item) => (
|
||||
item.id === editRecord.id ? { ...item, content, chars: content.length, billCount: Math.max(1, Math.ceil(content.length / 67)) } : item
|
||||
)));
|
||||
setEditRecord(null);
|
||||
async function approveBatch() {
|
||||
await Promise.all(filteredRecords.filter((item) => item.status === 'pending_review').map((item) => adminApi.approveRiskReviewTask(item.id, '运营批量审核通过')));
|
||||
setApproveTarget(null);
|
||||
loadData();
|
||||
}
|
||||
|
||||
function rejectRecordWithReason() {
|
||||
if (!rejectRecord) return;
|
||||
updateStatus(rejectRecord.id, 'rejected');
|
||||
setRejectRecord(null);
|
||||
async function rejectRecord() {
|
||||
if (!rejectTarget) return;
|
||||
await adminApi.rejectRiskReviewTask(rejectTarget.id, rejectReason || '运营审核驳回');
|
||||
setRejectTarget(null);
|
||||
setRejectReason('');
|
||||
loadData();
|
||||
}
|
||||
|
||||
const columns: Array<TableColumn<RiskReviewTask>> = [
|
||||
{ key: 'taskNo', title: '任务编号', width: '180px', render: (record) => <strong>{record.taskNo}</strong> },
|
||||
{ key: 'content', title: '短信内容', render: (record) => <span className="table-long-text">{record.content}</span> },
|
||||
{ key: 'phoneTotal', title: '号码数', width: '110px', render: (record) => record.phoneTotal.toLocaleString('zh-CN') },
|
||||
{ key: 'createdAt', title: '提交时间', width: '190px', render: (record) => record.createdAt },
|
||||
{ key: 'reason', title: '审核原因', render: (record) => record.reviewReason ?? record.rejectReason ?? record.riskHits?.map((item) => item.reason).join(';') ?? '-' },
|
||||
{
|
||||
key: 'status',
|
||||
title: '状态',
|
||||
width: '110px',
|
||||
render: (record) => <Tag tone={statusTone[record.status] ?? 'warning'}>{statusLabel[record.status] ?? record.status}</Tag>,
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
title: '操作',
|
||||
width: '160px',
|
||||
align: 'right',
|
||||
render: (record) => record.status === 'pending_review' ? (
|
||||
<div className="audit-actions">
|
||||
<Button icon={<Check size={15} />} onClick={() => setApproveTarget(record)} size="sm" variant="success">通过</Button>
|
||||
<Button icon={<X size={15} />} onClick={() => setRejectTarget(record)} size="sm" variant="danger">驳回</Button>
|
||||
</div>
|
||||
) : null,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="page-stack admin-audit-page">
|
||||
<Breadcrumb items={['审核中心', '短信审核']} />
|
||||
{error ? <p className="form-error">{error}</p> : null}
|
||||
<div className="surface sms-audit-filter">
|
||||
<h2>筛选条件</h2>
|
||||
<div className="audit-filter-grid audit-filter-grid--sms">
|
||||
<Select
|
||||
label="企业"
|
||||
onChange={(event) => setCompany(event.target.value)}
|
||||
options={[{ label: '请选择企业', value: '' }, ...Array.from(new Set(records.map((item) => item.customer))).map((item) => ({ label: item, value: item }))]}
|
||||
value={company}
|
||||
label="审核状态"
|
||||
onChange={(event) => setStatus(event.target.value)}
|
||||
options={[
|
||||
{ label: '全部状态', value: 'all' },
|
||||
{ label: '待审核', value: 'pending_review' },
|
||||
{ label: '已通过', value: 'approved' },
|
||||
{ label: '已驳回', value: 'rejected' },
|
||||
]}
|
||||
value={status}
|
||||
/>
|
||||
<Select
|
||||
disabled={!company}
|
||||
label="应用"
|
||||
onChange={(event) => setApplication(event.target.value)}
|
||||
options={[{ label: company ? '请选择应用' : '请先选择企业', value: '' }, ...Array.from(new Set(records.filter((item) => !company || item.customer === company).map((item) => item.application))).map((item) => ({ label: item, value: item }))]}
|
||||
value={application}
|
||||
/>
|
||||
<Input label="短信内容" onChange={(event) => setKeyword(event.target.value)} placeholder="请输入短信内容关键词" value={keyword} />
|
||||
<Input label="短信内容" onChange={(event) => setKeyword(event.target.value)} placeholder="请输入任务编号、内容或审核原因" value={keyword} />
|
||||
<Input label="提交日期" onChange={(event) => setDate(event.target.value)} placeholder="yyyy-mm-dd" prefix={<CalendarDays size={16} />} value={date} />
|
||||
<div className="audit-filter-actions">
|
||||
<Button icon={<Search size={17} />}>查询</Button>
|
||||
<Button onClick={() => { setCompany(''); setApplication(''); setKeyword(''); setDate(''); }} variant="ghost">重置</Button>
|
||||
<Button icon={<Search size={17} />} onClick={loadData}>查询</Button>
|
||||
<Button onClick={() => { setKeyword(''); setDate(''); setStatus('pending_review'); }} variant="ghost">重置</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sms-bulk-actions">
|
||||
<span>批量操作:</span>
|
||||
<Button icon={<Check size={16} />} onClick={() => setRecords((items) => items.map((item) => ({ ...item, status: 'approved', reviewedAt: '2026-01-12 17:27:28' })))} variant="secondary">批量通过</Button>
|
||||
<Button icon={<X size={16} />} onClick={() => setRejectRecord(filteredRecords[0] ?? null)} variant="danger">批量驳回</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sms-audit-toolbar">
|
||||
<span>10条/页</span>
|
||||
<div>
|
||||
<Button disabled size="sm" variant="ghost">上一页</Button>
|
||||
<Button size="sm" variant="secondary">1</Button>
|
||||
<Button size="sm" variant="ghost">2</Button>
|
||||
<Button size="sm" variant="ghost">63</Button>
|
||||
<Button size="sm" variant="ghost">下一页</Button>
|
||||
<Button disabled={filteredRecords.every((item) => item.status !== 'pending_review')} icon={<Check size={16} />} onClick={() => setApproveTarget('batch')} variant="success">批量通过</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="surface sms-audit-list">
|
||||
<div className="sms-audit-head">
|
||||
<span><input aria-label="全选" type="checkbox" /></span>
|
||||
<span>客户信息</span>
|
||||
<span>短信内容</span>
|
||||
<span>号码数量</span>
|
||||
<span>审核原因</span>
|
||||
<span>审核结果</span>
|
||||
<span>操作</span>
|
||||
</div>
|
||||
{filteredRecords.map((record) => (
|
||||
<article className="sms-audit-row" key={record.id}>
|
||||
<span><input aria-label={`选择 ${record.id}`} type="checkbox" /></span>
|
||||
<div className="sms-audit-customer">
|
||||
<strong>{record.customer}</strong>
|
||||
<span>{record.industry}</span>
|
||||
<small>{record.submittedAt} 提交</small>
|
||||
</div>
|
||||
<p className="sms-audit-content">{record.content}</p>
|
||||
<div className="sms-audit-count">
|
||||
<span>字符:<strong>{record.chars}</strong></span>
|
||||
<span>计费:<strong>{record.billCount}条</strong></span>
|
||||
<span>号码:<a>{record.phoneCount}</a></span>
|
||||
<button onClick={() => setPhoneListRecord(record)} type="button">查看列表</button>
|
||||
</div>
|
||||
<div className="sms-audit-reasons">
|
||||
{record.reasons.map((reason) => <Tag key={reason} tone={reason === '模板' ? 'success' : 'neutral'}>{reason}</Tag>)}
|
||||
</div>
|
||||
<div className="sms-audit-result">
|
||||
{record.status === 'pending' ? <span className="audit-dot audit-dot--warning">审核中</span> : null}
|
||||
{record.status === 'approved' ? <span className="audit-dot audit-dot--success">审核通过</span> : null}
|
||||
{record.status === 'rejected' ? <span className="audit-dot audit-dot--danger">已驳回</span> : null}
|
||||
{record.reviewedAt ? <small>{record.reviewedAt}</small> : null}
|
||||
</div>
|
||||
<div className="sms-audit-actions">
|
||||
<Button onClick={() => updateStatus(record.id, 'approved')} size="sm" variant="secondary">通过</Button>
|
||||
<Button onClick={() => setRejectRecord(record)} size="sm" variant="danger">驳回</Button>
|
||||
<Button onClick={() => setEditRecord(record)} size="sm" variant="ghost">修改</Button>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
<Table columns={columns} data={filteredRecords} emptyText="暂无短信审核任务" rowKey="id" />
|
||||
</div>
|
||||
|
||||
{editRecord ? (
|
||||
<SmsEditModal
|
||||
onClose={() => setEditRecord(null)}
|
||||
onSubmit={updateContent}
|
||||
record={editRecord}
|
||||
/>
|
||||
) : null}
|
||||
<Modal
|
||||
footer={(
|
||||
<>
|
||||
<Button onClick={() => setApproveTarget(null)} variant="ghost">取消</Button>
|
||||
<Button onClick={() => approveTarget === 'batch' ? void approveBatch() : approveTarget ? void approveRecord(approveTarget) : undefined} variant="success">确认通过</Button>
|
||||
</>
|
||||
)}
|
||||
onClose={() => setApproveTarget(null)}
|
||||
open={Boolean(approveTarget)}
|
||||
title="确认通过"
|
||||
>
|
||||
<p>{approveTarget === 'batch' ? `确认通过 ${filteredRecords.filter((item) => item.status === 'pending_review').length} 条待审核任务?` : '确认通过该短信审核任务?'}</p>
|
||||
</Modal>
|
||||
|
||||
{rejectRecord ? (
|
||||
<SmsRejectModal
|
||||
onClose={() => setRejectRecord(null)}
|
||||
onSubmit={rejectRecordWithReason}
|
||||
record={rejectRecord}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{phoneListRecord ? (
|
||||
<PhoneListModal
|
||||
onClose={() => setPhoneListRecord(null)}
|
||||
record={phoneListRecord}
|
||||
/>
|
||||
) : null}
|
||||
<Modal
|
||||
footer={(
|
||||
<>
|
||||
<Button onClick={() => setRejectTarget(null)} variant="ghost">取消</Button>
|
||||
<Button disabled={!rejectReason.trim()} onClick={() => void rejectRecord()} variant="danger">确认驳回</Button>
|
||||
</>
|
||||
)}
|
||||
onClose={() => setRejectTarget(null)}
|
||||
open={Boolean(rejectTarget)}
|
||||
title="确认驳回"
|
||||
>
|
||||
<Textarea label="驳回原因" onChange={(event) => setRejectReason(event.target.value)} rows={4} value={rejectReason} />
|
||||
</Modal>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user