|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import React, { useState, useEffect } from 'react'; |
|
|
import { useTranslation } from 'react-i18next'; |
|
|
import { |
|
|
Modal, |
|
|
Button, |
|
|
Table, |
|
|
Tag, |
|
|
Typography, |
|
|
Space, |
|
|
Tooltip, |
|
|
Popconfirm, |
|
|
Empty, |
|
|
Spin, |
|
|
Select, |
|
|
Row, |
|
|
Col, |
|
|
Badge, |
|
|
Progress, |
|
|
Card, |
|
|
} from '@douyinfe/semi-ui'; |
|
|
import { |
|
|
IllustrationNoResult, |
|
|
IllustrationNoResultDark, |
|
|
} from '@douyinfe/semi-illustrations'; |
|
|
import { |
|
|
API, |
|
|
showError, |
|
|
showSuccess, |
|
|
timestamp2string, |
|
|
} from '../../../../helpers'; |
|
|
|
|
|
const { Text } = Typography; |
|
|
|
|
|
const MultiKeyManageModal = ({ visible, onCancel, channel, onRefresh }) => { |
|
|
const { t } = useTranslation(); |
|
|
const [loading, setLoading] = useState(false); |
|
|
const [keyStatusList, setKeyStatusList] = useState([]); |
|
|
const [operationLoading, setOperationLoading] = useState({}); |
|
|
|
|
|
|
|
|
const [currentPage, setCurrentPage] = useState(1); |
|
|
const [pageSize, setPageSize] = useState(10); |
|
|
const [total, setTotal] = useState(0); |
|
|
const [totalPages, setTotalPages] = useState(0); |
|
|
|
|
|
|
|
|
const [enabledCount, setEnabledCount] = useState(0); |
|
|
const [manualDisabledCount, setManualDisabledCount] = useState(0); |
|
|
const [autoDisabledCount, setAutoDisabledCount] = useState(0); |
|
|
|
|
|
|
|
|
const [statusFilter, setStatusFilter] = useState(null); |
|
|
|
|
|
|
|
|
const loadKeyStatus = async ( |
|
|
page = currentPage, |
|
|
size = pageSize, |
|
|
status = statusFilter, |
|
|
) => { |
|
|
if (!channel?.id) return; |
|
|
|
|
|
setLoading(true); |
|
|
try { |
|
|
const requestData = { |
|
|
channel_id: channel.id, |
|
|
action: 'get_key_status', |
|
|
page: page, |
|
|
page_size: size, |
|
|
}; |
|
|
|
|
|
|
|
|
if (status !== null) { |
|
|
requestData.status = status; |
|
|
} |
|
|
|
|
|
const res = await API.post('/api/channel/multi_key/manage', requestData); |
|
|
|
|
|
if (res.data.success) { |
|
|
const data = res.data.data; |
|
|
setKeyStatusList(data.keys || []); |
|
|
setTotal(data.total || 0); |
|
|
setCurrentPage(data.page || 1); |
|
|
setPageSize(data.page_size || 10); |
|
|
setTotalPages(data.total_pages || 0); |
|
|
|
|
|
|
|
|
setEnabledCount(data.enabled_count || 0); |
|
|
setManualDisabledCount(data.manual_disabled_count || 0); |
|
|
setAutoDisabledCount(data.auto_disabled_count || 0); |
|
|
} else { |
|
|
showError(res.data.message); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error(error); |
|
|
showError(t('获取密钥状态失败')); |
|
|
} finally { |
|
|
setLoading(false); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const handleDisableKey = async (keyIndex) => { |
|
|
const operationId = `disable_${keyIndex}`; |
|
|
setOperationLoading((prev) => ({ ...prev, [operationId]: true })); |
|
|
|
|
|
try { |
|
|
const res = await API.post('/api/channel/multi_key/manage', { |
|
|
channel_id: channel.id, |
|
|
action: 'disable_key', |
|
|
key_index: keyIndex, |
|
|
}); |
|
|
|
|
|
if (res.data.success) { |
|
|
showSuccess(t('密钥已禁用')); |
|
|
await loadKeyStatus(currentPage, pageSize); |
|
|
onRefresh && onRefresh(); |
|
|
} else { |
|
|
showError(res.data.message); |
|
|
} |
|
|
} catch (error) { |
|
|
showError(t('禁用密钥失败')); |
|
|
} finally { |
|
|
setOperationLoading((prev) => ({ ...prev, [operationId]: false })); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const handleEnableKey = async (keyIndex) => { |
|
|
const operationId = `enable_${keyIndex}`; |
|
|
setOperationLoading((prev) => ({ ...prev, [operationId]: true })); |
|
|
|
|
|
try { |
|
|
const res = await API.post('/api/channel/multi_key/manage', { |
|
|
channel_id: channel.id, |
|
|
action: 'enable_key', |
|
|
key_index: keyIndex, |
|
|
}); |
|
|
|
|
|
if (res.data.success) { |
|
|
showSuccess(t('密钥已启用')); |
|
|
await loadKeyStatus(currentPage, pageSize); |
|
|
onRefresh && onRefresh(); |
|
|
} else { |
|
|
showError(res.data.message); |
|
|
} |
|
|
} catch (error) { |
|
|
showError(t('启用密钥失败')); |
|
|
} finally { |
|
|
setOperationLoading((prev) => ({ ...prev, [operationId]: false })); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const handleEnableAll = async () => { |
|
|
setOperationLoading((prev) => ({ ...prev, enable_all: true })); |
|
|
|
|
|
try { |
|
|
const res = await API.post('/api/channel/multi_key/manage', { |
|
|
channel_id: channel.id, |
|
|
action: 'enable_all_keys', |
|
|
}); |
|
|
|
|
|
if (res.data.success) { |
|
|
showSuccess(res.data.message || t('已启用所有密钥')); |
|
|
|
|
|
setCurrentPage(1); |
|
|
await loadKeyStatus(1, pageSize); |
|
|
onRefresh && onRefresh(); |
|
|
} else { |
|
|
showError(res.data.message); |
|
|
} |
|
|
} catch (error) { |
|
|
showError(t('启用所有密钥失败')); |
|
|
} finally { |
|
|
setOperationLoading((prev) => ({ ...prev, enable_all: false })); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const handleDisableAll = async () => { |
|
|
setOperationLoading((prev) => ({ ...prev, disable_all: true })); |
|
|
|
|
|
try { |
|
|
const res = await API.post('/api/channel/multi_key/manage', { |
|
|
channel_id: channel.id, |
|
|
action: 'disable_all_keys', |
|
|
}); |
|
|
|
|
|
if (res.data.success) { |
|
|
showSuccess(res.data.message || t('已禁用所有密钥')); |
|
|
|
|
|
setCurrentPage(1); |
|
|
await loadKeyStatus(1, pageSize); |
|
|
onRefresh && onRefresh(); |
|
|
} else { |
|
|
showError(res.data.message); |
|
|
} |
|
|
} catch (error) { |
|
|
showError(t('禁用所有密钥失败')); |
|
|
} finally { |
|
|
setOperationLoading((prev) => ({ ...prev, disable_all: false })); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const handleDeleteDisabledKeys = async () => { |
|
|
setOperationLoading((prev) => ({ ...prev, delete_disabled: true })); |
|
|
|
|
|
try { |
|
|
const res = await API.post('/api/channel/multi_key/manage', { |
|
|
channel_id: channel.id, |
|
|
action: 'delete_disabled_keys', |
|
|
}); |
|
|
|
|
|
if (res.data.success) { |
|
|
showSuccess(res.data.message); |
|
|
|
|
|
setCurrentPage(1); |
|
|
await loadKeyStatus(1, pageSize); |
|
|
onRefresh && onRefresh(); |
|
|
} else { |
|
|
showError(res.data.message); |
|
|
} |
|
|
} catch (error) { |
|
|
showError(t('删除禁用密钥失败')); |
|
|
} finally { |
|
|
setOperationLoading((prev) => ({ ...prev, delete_disabled: false })); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const handleDeleteKey = async (keyIndex) => { |
|
|
const operationId = `delete_${keyIndex}`; |
|
|
setOperationLoading((prev) => ({ ...prev, [operationId]: true })); |
|
|
|
|
|
try { |
|
|
const res = await API.post('/api/channel/multi_key/manage', { |
|
|
channel_id: channel.id, |
|
|
action: 'delete_key', |
|
|
key_index: keyIndex, |
|
|
}); |
|
|
|
|
|
if (res.data.success) { |
|
|
showSuccess(t('密钥已删除')); |
|
|
await loadKeyStatus(currentPage, pageSize); |
|
|
onRefresh && onRefresh(); |
|
|
} else { |
|
|
showError(res.data.message); |
|
|
} |
|
|
} catch (error) { |
|
|
showError(t('删除密钥失败')); |
|
|
} finally { |
|
|
setOperationLoading((prev) => ({ ...prev, [operationId]: false })); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const handlePageChange = (page) => { |
|
|
setCurrentPage(page); |
|
|
loadKeyStatus(page, pageSize); |
|
|
}; |
|
|
|
|
|
|
|
|
const handlePageSizeChange = (size) => { |
|
|
setPageSize(size); |
|
|
setCurrentPage(1); |
|
|
loadKeyStatus(1, size); |
|
|
}; |
|
|
|
|
|
|
|
|
const handleStatusFilterChange = (status) => { |
|
|
setStatusFilter(status); |
|
|
setCurrentPage(1); |
|
|
loadKeyStatus(1, pageSize, status); |
|
|
}; |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
if (visible && channel?.id) { |
|
|
setCurrentPage(1); |
|
|
loadKeyStatus(1, pageSize); |
|
|
} |
|
|
}, [visible, channel?.id]); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
if (!visible) { |
|
|
setCurrentPage(1); |
|
|
setKeyStatusList([]); |
|
|
setTotal(0); |
|
|
setTotalPages(0); |
|
|
setEnabledCount(0); |
|
|
setManualDisabledCount(0); |
|
|
setAutoDisabledCount(0); |
|
|
setStatusFilter(null); |
|
|
} |
|
|
}, [visible]); |
|
|
|
|
|
|
|
|
const enabledPercent = |
|
|
total > 0 ? Math.round((enabledCount / total) * 100) : 0; |
|
|
const manualDisabledPercent = |
|
|
total > 0 ? Math.round((manualDisabledCount / total) * 100) : 0; |
|
|
const autoDisabledPercent = |
|
|
total > 0 ? Math.round((autoDisabledCount / total) * 100) : 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const renderStatusTag = (status) => { |
|
|
switch (status) { |
|
|
case 1: |
|
|
return ( |
|
|
<Tag color='green' shape='circle' size='small'> |
|
|
{t('已启用')} |
|
|
</Tag> |
|
|
); |
|
|
case 2: |
|
|
return ( |
|
|
<Tag color='red' shape='circle' size='small'> |
|
|
{t('已禁用')} |
|
|
</Tag> |
|
|
); |
|
|
case 3: |
|
|
return ( |
|
|
<Tag color='orange' shape='circle' size='small'> |
|
|
{t('自动禁用')} |
|
|
</Tag> |
|
|
); |
|
|
default: |
|
|
return ( |
|
|
<Tag color='grey' shape='circle' size='small'> |
|
|
{t('未知状态')} |
|
|
</Tag> |
|
|
); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const columns = [ |
|
|
{ |
|
|
title: t('索引'), |
|
|
dataIndex: 'index', |
|
|
render: (text) => `#${text}`, |
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{ |
|
|
title: t('状态'), |
|
|
dataIndex: 'status', |
|
|
render: (status) => renderStatusTag(status), |
|
|
}, |
|
|
{ |
|
|
title: t('禁用原因'), |
|
|
dataIndex: 'reason', |
|
|
render: (reason, record) => { |
|
|
if (record.status === 1 || !reason) { |
|
|
return <Text type='quaternary'>-</Text>; |
|
|
} |
|
|
return ( |
|
|
<Tooltip content={reason}> |
|
|
<Text style={{ maxWidth: '200px', display: 'block' }} ellipsis> |
|
|
{reason} |
|
|
</Text> |
|
|
</Tooltip> |
|
|
); |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
title: t('禁用时间'), |
|
|
dataIndex: 'disabled_time', |
|
|
render: (time, record) => { |
|
|
if (record.status === 1 || !time) { |
|
|
return <Text type='quaternary'>-</Text>; |
|
|
} |
|
|
return ( |
|
|
<Tooltip content={timestamp2string(time)}> |
|
|
<Text style={{ fontSize: '12px' }}>{timestamp2string(time)}</Text> |
|
|
</Tooltip> |
|
|
); |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
title: t('操作'), |
|
|
key: 'action', |
|
|
fixed: 'right', |
|
|
width: 150, |
|
|
render: (_, record) => ( |
|
|
<Space> |
|
|
{record.status === 1 ? ( |
|
|
<Button |
|
|
type='danger' |
|
|
size='small' |
|
|
loading={operationLoading[`disable_${record.index}`]} |
|
|
onClick={() => handleDisableKey(record.index)} |
|
|
> |
|
|
{t('禁用')} |
|
|
</Button> |
|
|
) : ( |
|
|
<Button |
|
|
type='primary' |
|
|
size='small' |
|
|
loading={operationLoading[`enable_${record.index}`]} |
|
|
onClick={() => handleEnableKey(record.index)} |
|
|
> |
|
|
{t('启用')} |
|
|
</Button> |
|
|
)} |
|
|
<Popconfirm |
|
|
title={t('确定要删除此密钥吗?')} |
|
|
content={t('此操作不可撤销,将永久删除该密钥')} |
|
|
onConfirm={() => handleDeleteKey(record.index)} |
|
|
okType={'danger'} |
|
|
position={'topRight'} |
|
|
> |
|
|
<Button |
|
|
type='danger' |
|
|
size='small' |
|
|
loading={operationLoading[`delete_${record.index}`]} |
|
|
> |
|
|
{t('删除')} |
|
|
</Button> |
|
|
</Popconfirm> |
|
|
</Space> |
|
|
), |
|
|
}, |
|
|
]; |
|
|
|
|
|
return ( |
|
|
<Modal |
|
|
title={ |
|
|
<Space> |
|
|
<Text>{t('多密钥管理')}</Text> |
|
|
{channel?.name && ( |
|
|
<Tag size='small' shape='circle' color='white'> |
|
|
{channel.name} |
|
|
</Tag> |
|
|
)} |
|
|
<Tag size='small' shape='circle' color='white'> |
|
|
{t('总密钥数')}: {total} |
|
|
</Tag> |
|
|
{channel?.channel_info?.multi_key_mode && ( |
|
|
<Tag size='small' shape='circle' color='white'> |
|
|
{channel.channel_info.multi_key_mode === 'random' |
|
|
? t('随机模式') |
|
|
: t('轮询模式')} |
|
|
</Tag> |
|
|
)} |
|
|
</Space> |
|
|
} |
|
|
visible={visible} |
|
|
onCancel={onCancel} |
|
|
width={900} |
|
|
footer={null} |
|
|
> |
|
|
<div className='flex flex-col mb-5'> |
|
|
{/* Stats & Mode */} |
|
|
<div |
|
|
className='rounded-xl p-4 mb-3' |
|
|
style={{ |
|
|
background: 'var(--semi-color-bg-1)', |
|
|
border: '1px solid var(--semi-color-border)', |
|
|
}} |
|
|
> |
|
|
<Row gutter={16} align='middle'> |
|
|
<Col span={8}> |
|
|
<div |
|
|
style={{ |
|
|
background: 'var(--semi-color-bg-0)', |
|
|
border: '1px solid var(--semi-color-border)', |
|
|
borderRadius: 12, |
|
|
padding: 12, |
|
|
}} |
|
|
> |
|
|
<div className='flex items-center gap-2 mb-2'> |
|
|
<Badge dot type='success' /> |
|
|
<Text type='tertiary'>{t('已启用')}</Text> |
|
|
</div> |
|
|
<div className='flex items-end gap-2 mb-2'> |
|
|
<Text |
|
|
style={{ fontSize: 18, fontWeight: 700, color: '#22c55e' }} |
|
|
> |
|
|
{enabledCount} |
|
|
</Text> |
|
|
<Text |
|
|
style={{ fontSize: 18, color: 'var(--semi-color-text-2)' }} |
|
|
> |
|
|
/ {total} |
|
|
</Text> |
|
|
</div> |
|
|
<Progress |
|
|
percent={enabledPercent} |
|
|
showInfo={false} |
|
|
size='small' |
|
|
stroke='#22c55e' |
|
|
style={{ height: 6, borderRadius: 999 }} |
|
|
/> |
|
|
</div> |
|
|
</Col> |
|
|
<Col span={8}> |
|
|
<div |
|
|
style={{ |
|
|
background: 'var(--semi-color-bg-0)', |
|
|
border: '1px solid var(--semi-color-border)', |
|
|
borderRadius: 12, |
|
|
padding: 12, |
|
|
}} |
|
|
> |
|
|
<div className='flex items-center gap-2 mb-2'> |
|
|
<Badge dot type='danger' /> |
|
|
<Text type='tertiary'>{t('手动禁用')}</Text> |
|
|
</div> |
|
|
<div className='flex items-end gap-2 mb-2'> |
|
|
<Text |
|
|
style={{ fontSize: 18, fontWeight: 700, color: '#ef4444' }} |
|
|
> |
|
|
{manualDisabledCount} |
|
|
</Text> |
|
|
<Text |
|
|
style={{ fontSize: 18, color: 'var(--semi-color-text-2)' }} |
|
|
> |
|
|
/ {total} |
|
|
</Text> |
|
|
</div> |
|
|
<Progress |
|
|
percent={manualDisabledPercent} |
|
|
showInfo={false} |
|
|
size='small' |
|
|
stroke='#ef4444' |
|
|
style={{ height: 6, borderRadius: 999 }} |
|
|
/> |
|
|
</div> |
|
|
</Col> |
|
|
<Col span={8}> |
|
|
<div |
|
|
style={{ |
|
|
background: 'var(--semi-color-bg-0)', |
|
|
border: '1px solid var(--semi-color-border)', |
|
|
borderRadius: 12, |
|
|
padding: 12, |
|
|
}} |
|
|
> |
|
|
<div className='flex items-center gap-2 mb-2'> |
|
|
<Badge dot type='warning' /> |
|
|
<Text type='tertiary'>{t('自动禁用')}</Text> |
|
|
</div> |
|
|
<div className='flex items-end gap-2 mb-2'> |
|
|
<Text |
|
|
style={{ fontSize: 18, fontWeight: 700, color: '#f59e0b' }} |
|
|
> |
|
|
{autoDisabledCount} |
|
|
</Text> |
|
|
<Text |
|
|
style={{ fontSize: 18, color: 'var(--semi-color-text-2)' }} |
|
|
> |
|
|
/ {total} |
|
|
</Text> |
|
|
</div> |
|
|
<Progress |
|
|
percent={autoDisabledPercent} |
|
|
showInfo={false} |
|
|
size='small' |
|
|
stroke='#f59e0b' |
|
|
style={{ height: 6, borderRadius: 999 }} |
|
|
/> |
|
|
</div> |
|
|
</Col> |
|
|
</Row> |
|
|
</div> |
|
|
|
|
|
{/* Table */} |
|
|
<div className='flex-1 flex flex-col min-h-0'> |
|
|
<Spin spinning={loading}> |
|
|
<Card className='!rounded-xl'> |
|
|
<Table |
|
|
title={() => ( |
|
|
<Row gutter={12} style={{ width: '100%' }}> |
|
|
<Col span={14}> |
|
|
<Row gutter={12} style={{ alignItems: 'center' }}> |
|
|
<Col> |
|
|
<Select |
|
|
value={statusFilter} |
|
|
onChange={handleStatusFilterChange} |
|
|
size='small' |
|
|
placeholder={t('全部状态')} |
|
|
> |
|
|
<Select.Option value={null}> |
|
|
{t('全部状态')} |
|
|
</Select.Option> |
|
|
<Select.Option value={1}> |
|
|
{t('已启用')} |
|
|
</Select.Option> |
|
|
<Select.Option value={2}> |
|
|
{t('手动禁用')} |
|
|
</Select.Option> |
|
|
<Select.Option value={3}> |
|
|
{t('自动禁用')} |
|
|
</Select.Option> |
|
|
</Select> |
|
|
</Col> |
|
|
</Row> |
|
|
</Col> |
|
|
<Col |
|
|
span={10} |
|
|
style={{ display: 'flex', justifyContent: 'flex-end' }} |
|
|
> |
|
|
<Space> |
|
|
<Button |
|
|
size='small' |
|
|
type='tertiary' |
|
|
onClick={() => loadKeyStatus(currentPage, pageSize)} |
|
|
loading={loading} |
|
|
> |
|
|
{t('刷新')} |
|
|
</Button> |
|
|
{manualDisabledCount + autoDisabledCount > 0 && ( |
|
|
<Popconfirm |
|
|
title={t('确定要启用所有密钥吗?')} |
|
|
onConfirm={handleEnableAll} |
|
|
position={'topRight'} |
|
|
> |
|
|
<Button |
|
|
size='small' |
|
|
type='primary' |
|
|
loading={operationLoading.enable_all} |
|
|
> |
|
|
{t('启用全部')} |
|
|
</Button> |
|
|
</Popconfirm> |
|
|
)} |
|
|
{enabledCount > 0 && ( |
|
|
<Popconfirm |
|
|
title={t('确定要禁用所有的密钥吗?')} |
|
|
onConfirm={handleDisableAll} |
|
|
okType={'danger'} |
|
|
position={'topRight'} |
|
|
> |
|
|
<Button |
|
|
size='small' |
|
|
type='danger' |
|
|
loading={operationLoading.disable_all} |
|
|
> |
|
|
{t('禁用全部')} |
|
|
</Button> |
|
|
</Popconfirm> |
|
|
)} |
|
|
<Popconfirm |
|
|
title={t('确定要删除所有已自动禁用的密钥吗?')} |
|
|
content={t( |
|
|
'此操作不可撤销,将永久删除已自动禁用的密钥', |
|
|
)} |
|
|
onConfirm={handleDeleteDisabledKeys} |
|
|
okType={'danger'} |
|
|
position={'topRight'} |
|
|
> |
|
|
<Button |
|
|
size='small' |
|
|
type='warning' |
|
|
loading={operationLoading.delete_disabled} |
|
|
> |
|
|
{t('删除自动禁用密钥')} |
|
|
</Button> |
|
|
</Popconfirm> |
|
|
</Space> |
|
|
</Col> |
|
|
</Row> |
|
|
)} |
|
|
columns={columns} |
|
|
dataSource={keyStatusList} |
|
|
pagination={{ |
|
|
currentPage: currentPage, |
|
|
pageSize: pageSize, |
|
|
total: total, |
|
|
showSizeChanger: true, |
|
|
showQuickJumper: true, |
|
|
pageSizeOpts: [10, 20, 50, 100], |
|
|
onChange: (page, size) => { |
|
|
setCurrentPage(page); |
|
|
loadKeyStatus(page, size); |
|
|
}, |
|
|
onShowSizeChange: (current, size) => { |
|
|
setCurrentPage(1); |
|
|
handlePageSizeChange(size); |
|
|
}, |
|
|
}} |
|
|
size='small' |
|
|
bordered={false} |
|
|
rowKey='index' |
|
|
scroll={{ x: 'max-content' }} |
|
|
empty={ |
|
|
<Empty |
|
|
image={ |
|
|
<IllustrationNoResult |
|
|
style={{ width: 140, height: 140 }} |
|
|
/> |
|
|
} |
|
|
darkModeImage={ |
|
|
<IllustrationNoResultDark |
|
|
style={{ width: 140, height: 140 }} |
|
|
/> |
|
|
} |
|
|
title={t('暂无密钥数据')} |
|
|
description={t('请检查渠道配置或刷新重试')} |
|
|
style={{ padding: 30 }} |
|
|
/> |
|
|
} |
|
|
/> |
|
|
</Card> |
|
|
</Spin> |
|
|
</div> |
|
|
</div> |
|
|
</Modal> |
|
|
); |
|
|
}; |
|
|
|
|
|
export default MultiKeyManageModal; |
|
|
|