161 lines
7.8 KiB
TypeScript
161 lines
7.8 KiB
TypeScript
import { useEffect, useMemo, useState } from 'react';
|
|
import { Plus, Search, Trash2 } from 'lucide-react';
|
|
import { Breadcrumb, Button, Input, Modal, Select, Table, Tag, Textarea, type TableColumn } from '@/components/ui';
|
|
import { adminApi, type DictionaryItem, type EnterpriseApplication, type TenantOption } from '@/api/adminApi';
|
|
|
|
type EnterpriseBlacklistItem = DictionaryItem & {
|
|
tenantId?: string;
|
|
applicationId?: string;
|
|
tenant?: TenantOption;
|
|
application?: EnterpriseApplication;
|
|
phoneNumber?: string;
|
|
reason?: string | null;
|
|
};
|
|
|
|
export function AdminEnterpriseBlacklistPage() {
|
|
const [items, setItems] = useState<EnterpriseBlacklistItem[]>([]);
|
|
const [tenants, setTenants] = useState<TenantOption[]>([]);
|
|
const [applications, setApplications] = useState<EnterpriseApplication[]>([]);
|
|
const [enterpriseKeyword, setEnterpriseKeyword] = useState('');
|
|
const [applicationKeyword, setApplicationKeyword] = useState('');
|
|
const [phoneKeyword, setPhoneKeyword] = useState('');
|
|
const [reasonKeyword, setReasonKeyword] = useState('');
|
|
const [status, setStatus] = useState('all');
|
|
const [appliedFilters, setAppliedFilters] = useState({ enterpriseKeyword: '', applicationKeyword: '', phoneNumber: '', reasonKeyword: '', status: 'all' });
|
|
const [formTenantId, setFormTenantId] = useState('');
|
|
const [formApplicationId, setFormApplicationId] = useState('');
|
|
const [phone, setPhone] = useState('');
|
|
const [reason, setReason] = useState('');
|
|
const [modalOpen, setModalOpen] = useState(false);
|
|
const [error, setError] = useState('');
|
|
|
|
function loadData(filters = appliedFilters) {
|
|
Promise.all([
|
|
adminApi.listEnterpriseBlacklist(filters),
|
|
adminApi.listTenants(),
|
|
adminApi.listEnterpriseApplications(),
|
|
])
|
|
.then(([blacklist, tenantItems, applicationItems]) => {
|
|
setItems(blacklist as EnterpriseBlacklistItem[]);
|
|
setTenants(tenantItems);
|
|
setApplications(applicationItems);
|
|
setError('');
|
|
})
|
|
.catch((failure: Error) => setError(failure.message || '企业黑名单加载失败'));
|
|
}
|
|
|
|
useEffect(() => {
|
|
loadData();
|
|
}, []);
|
|
|
|
const modalApplications = applications.filter((application) => application.tenantId === formTenantId && application.status !== 'deleted');
|
|
|
|
const columns = useMemo<Array<TableColumn<EnterpriseBlacklistItem>>>(() => [
|
|
{ key: 'enterprise', title: '企业名称', width: '180px', render: (record) => <strong>{record.tenant?.name ?? record.tenantId}</strong> },
|
|
{ key: 'application', title: '应用名称', width: '180px', render: (record) => <span>{record.application?.name ?? record.applicationId}</span> },
|
|
{ key: 'phone', title: '手机号码', width: '150px', render: (record) => <strong>{record.phoneNumber}</strong> },
|
|
{ key: 'createdAt', title: '入库时间', width: '170px', render: (record) => record.createdAt ?? '-' },
|
|
{ key: 'reason', title: '入库原因', render: (record) => record.reason ?? '-' },
|
|
{ key: 'status', title: '状态', width: '130px', render: (record) => <Tag tone={record.status === 'active' ? 'success' : 'neutral'}>{record.status === 'active' ? '生效中' : record.status === 'deleted' ? '已删除' : record.status ?? '-'}</Tag> },
|
|
{
|
|
key: 'actions',
|
|
title: '操作',
|
|
width: '130px',
|
|
align: 'right',
|
|
render: (record) => (
|
|
<Button icon={<Trash2 size={15} />} onClick={() => adminApi.deleteEnterpriseBlacklist(record.id).then(() => loadData()).catch((failure: Error) => setError(failure.message))} size="sm" variant="danger">
|
|
删除
|
|
</Button>
|
|
),
|
|
},
|
|
], [appliedFilters]);
|
|
|
|
function addItem() {
|
|
adminApi.createEnterpriseBlacklist({ tenantId: formTenantId, applicationId: formApplicationId, phoneNumber: phone, reason, status: 'active' })
|
|
.then(() => {
|
|
setFormTenantId('');
|
|
setFormApplicationId('');
|
|
setPhone('');
|
|
setReason('');
|
|
setModalOpen(false);
|
|
loadData();
|
|
})
|
|
.catch((failure: Error) => setError(failure.message || '添加企业黑名单失败'));
|
|
}
|
|
|
|
return (
|
|
<section className="page-stack admin-security-page">
|
|
<div className="page-heading">
|
|
<div>
|
|
<Breadcrumb items={['安全控制', '企业黑名单']} />
|
|
<h1>企业黑名单</h1>
|
|
</div>
|
|
<Button icon={<Plus size={16} />} onClick={() => setModalOpen(true)}>添加黑名单</Button>
|
|
</div>
|
|
{error ? <p className="form-error">{error}</p> : null}
|
|
|
|
<div className="surface admin-security-filter">
|
|
<Input
|
|
label="企业名称"
|
|
onChange={(event) => setEnterpriseKeyword(event.target.value)}
|
|
placeholder="请输入企业名称"
|
|
prefix={<Search size={16} />}
|
|
value={enterpriseKeyword}
|
|
/>
|
|
<Input
|
|
label="企业应用"
|
|
onChange={(event) => setApplicationKeyword(event.target.value)}
|
|
placeholder="请输入企业应用名称"
|
|
prefix={<Search size={16} />}
|
|
value={applicationKeyword}
|
|
/>
|
|
<Input label="手机号码" onChange={(event) => setPhoneKeyword(event.target.value)} placeholder="请输入手机号码" value={phoneKeyword} />
|
|
<Input label="入库原因" onChange={(event) => setReasonKeyword(event.target.value)} placeholder="请输入入库原因" value={reasonKeyword} />
|
|
<Select
|
|
label="状态"
|
|
onChange={(event) => setStatus(event.target.value)}
|
|
options={[{ label: '全部状态', value: 'all' }, { label: '生效中', value: 'active' }]}
|
|
value={status}
|
|
/>
|
|
<div className="admin-security-filter__actions">
|
|
<Button icon={<Search size={16} />} onClick={() => { const filters = { enterpriseKeyword: enterpriseKeyword.trim(), applicationKeyword: applicationKeyword.trim(), phoneNumber: phoneKeyword.trim(), reasonKeyword: reasonKeyword.trim(), status }; setAppliedFilters(filters); loadData(filters); }}>查询</Button>
|
|
<Button onClick={() => { const filters = { enterpriseKeyword: '', applicationKeyword: '', phoneNumber: '', reasonKeyword: '', status: 'all' }; setEnterpriseKeyword(''); setApplicationKeyword(''); setPhoneKeyword(''); setReasonKeyword(''); setStatus('all'); setAppliedFilters(filters); loadData(filters); }} variant="ghost">重置</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="surface admin-security-table-card">
|
|
<Table columns={columns} data={items} emptyText="暂无企业黑名单记录" rowKey="id" />
|
|
</div>
|
|
|
|
<Modal
|
|
footer={(
|
|
<>
|
|
<Button onClick={() => setModalOpen(false)} variant="ghost">取消</Button>
|
|
<Button disabled={!formTenantId || !formApplicationId || !phone} onClick={addItem}>确认添加</Button>
|
|
</>
|
|
)}
|
|
onClose={() => setModalOpen(false)}
|
|
open={modalOpen}
|
|
title="添加企业黑名单"
|
|
>
|
|
<div className="admin-security-form">
|
|
<Select
|
|
label="企业"
|
|
onChange={(event) => { setFormTenantId(event.target.value); setFormApplicationId(''); }}
|
|
options={[{ label: '请选择企业', value: '' }, ...tenants.map((item) => ({ label: item.name, value: item.id }))]}
|
|
value={formTenantId}
|
|
/>
|
|
<Select
|
|
label="短信应用"
|
|
onChange={(event) => setFormApplicationId(event.target.value)}
|
|
options={[{ label: formTenantId ? '请选择应用' : '请先选择企业', value: '' }, ...modalApplications.map((item) => ({ label: item.name, value: item.id }))]}
|
|
value={formApplicationId}
|
|
/>
|
|
<Input label="手机号码" onChange={(event) => setPhone(event.target.value)} placeholder="请输入手机号码" value={phone} />
|
|
<Textarea label="入库原因" onChange={(event) => setReason(event.target.value)} placeholder="请输入入库原因" rows={3} value={reason} />
|
|
</div>
|
|
</Modal>
|
|
</section>
|
|
);
|
|
}
|