feat: complete drainage review and admin search workflows

This commit is contained in:
hectorzhao
2026-07-14 09:58:18 +08:00
parent dec2430b7e
commit 6421671259
33 changed files with 1107 additions and 255 deletions
+31 -30
View File
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from 'react';
import { Plus, Search, Trash2 } from 'lucide-react';
import { Breadcrumb, Button, Input, Modal, Select, Table, Textarea, type TableColumn } from '@/components/ui';
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 & {
@@ -16,9 +16,12 @@ export function AdminEnterpriseBlacklistPage() {
const [items, setItems] = useState<EnterpriseBlacklistItem[]>([]);
const [tenants, setTenants] = useState<TenantOption[]>([]);
const [applications, setApplications] = useState<EnterpriseApplication[]>([]);
const [keyword, setKeyword] = useState('');
const [filterTenantId, setFilterTenantId] = useState('');
const [filterApplicationId, setFilterApplicationId] = useState('');
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('');
@@ -26,9 +29,9 @@ export function AdminEnterpriseBlacklistPage() {
const [modalOpen, setModalOpen] = useState(false);
const [error, setError] = useState('');
function loadData() {
function loadData(filters = appliedFilters) {
Promise.all([
adminApi.listEnterpriseBlacklist({ tenantId: filterTenantId || undefined, applicationId: filterApplicationId || undefined, keyword }),
adminApi.listEnterpriseBlacklist(filters),
adminApi.listTenants(),
adminApi.listEnterpriseApplications(),
])
@@ -45,11 +48,6 @@ export function AdminEnterpriseBlacklistPage() {
loadData();
}, []);
const filteredItems = useMemo(() => items.filter((item) => {
const text = [item.tenant?.name, item.application?.name, item.phoneNumber, item.reason, item.status].join(' ');
return !keyword || text.includes(keyword);
}), [items, keyword]);
const filterApplications = applications.filter((application) => application.tenantId === filterTenantId && application.status !== 'deleted');
const modalApplications = applications.filter((application) => application.tenantId === formTenantId && application.status !== 'deleted');
const columns = useMemo<Array<TableColumn<EnterpriseBlacklistItem>>>(() => [
@@ -58,19 +56,19 @@ export function AdminEnterpriseBlacklistPage() {
{ 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) => record.status ?? '-' },
{ 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 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' })
@@ -98,32 +96,35 @@ export function AdminEnterpriseBlacklistPage() {
<div className="surface admin-security-filter">
<Input
label="搜索"
onChange={(event) => setKeyword(event.target.value)}
placeholder="搜索企业、应用、手机号或原因"
label="企业名称"
onChange={(event) => setEnterpriseKeyword(event.target.value)}
placeholder="请输入企业名称"
prefix={<Search size={16} />}
value={keyword}
value={enterpriseKeyword}
/>
<Select
label="企业"
onChange={(event) => { setFilterTenantId(event.target.value); setFilterApplicationId(''); }}
options={[{ label: '全部企业', value: '' }, ...tenants.map((item) => ({ label: item.name, value: item.id }))]}
value={filterTenantId}
<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) => setFilterApplicationId(event.target.value)}
options={[{ label: filterTenantId ? '全部应用' : '请先选择企业', value: '' }, ...filterApplications.map((item) => ({ label: item.name, value: item.id }))]}
value={filterApplicationId}
label="状态"
onChange={(event) => setStatus(event.target.value)}
options={[{ label: '全部状态', value: 'all' }, { label: '生效中', value: 'active' }, { label: '已删除', value: 'deleted' }]}
value={status}
/>
<div className="admin-security-filter__actions">
<Button icon={<Search size={16} />} onClick={loadData}></Button>
<Button onClick={() => { setKeyword(''); setFilterTenantId(''); setFilterApplicationId(''); }} variant="ghost"></Button>
<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={filteredItems} emptyText="暂无企业黑名单记录" rowKey="id" />
<Table columns={columns} data={items} emptyText="暂无企业黑名单记录" rowKey="id" />
</div>
<Modal