🎨 refactor: MultiKeyManageModal: cleaner stats UI, remove chart, integrate toolbar/pagination, and improve UX
- Replace custom dots with Semi Badge types (success/danger/warning); add compact Progress bars - Remove pie chart and related deps/config; move total key count and mode tags into the modal title - Rework header using Row/Col; three equal stat cards (enabled/manual-disabled/auto-disabled) - Integrate toolbar into Table title; wrap content with Card; use Table’s native empty state - Make “Enable All” conditional (hidden when all keys are enabled), mirroring “Disable All” - Unify numeric typography (current/total same size) for better readability - Default page size set to 10; fallback to 10 when backend page_size is absent; page-size options: 10/20/50/100 - Cleanup imports and dead code (remove VChart and pie-spec logic) - Minor spacing polish (extra bottom margin before table), no footer buttons
This commit is contained in:
@@ -544,7 +544,7 @@ export const getChannelsColumns = ({
|
|||||||
menu={[
|
menu={[
|
||||||
{
|
{
|
||||||
node: 'item',
|
node: 'item',
|
||||||
name: t('多key管理'),
|
name: t('多密钥管理'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
setCurrentMultiKeyChannel(record);
|
setCurrentMultiKeyChannel(record);
|
||||||
setShowMultiKeyManageModal(true);
|
setShowMultiKeyManageModal(true);
|
||||||
|
|||||||
@@ -30,20 +30,17 @@ import {
|
|||||||
Popconfirm,
|
Popconfirm,
|
||||||
Empty,
|
Empty,
|
||||||
Spin,
|
Spin,
|
||||||
Banner,
|
|
||||||
Select,
|
Select,
|
||||||
Pagination
|
Row,
|
||||||
|
Col,
|
||||||
|
Badge,
|
||||||
|
Progress,
|
||||||
|
Card
|
||||||
} from '@douyinfe/semi-ui';
|
} from '@douyinfe/semi-ui';
|
||||||
import {
|
import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations';
|
||||||
IconRefresh,
|
|
||||||
IconDelete,
|
|
||||||
IconClose,
|
|
||||||
IconSave,
|
|
||||||
IconSetting
|
|
||||||
} from '@douyinfe/semi-icons';
|
|
||||||
import { API, showError, showSuccess, timestamp2string } from '../../../../helpers/index.js';
|
import { API, showError, showSuccess, timestamp2string } from '../../../../helpers/index.js';
|
||||||
|
|
||||||
const { Text, Title } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
const MultiKeyManageModal = ({
|
const MultiKeyManageModal = ({
|
||||||
visible,
|
visible,
|
||||||
@@ -55,13 +52,13 @@ const MultiKeyManageModal = ({
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [keyStatusList, setKeyStatusList] = useState([]);
|
const [keyStatusList, setKeyStatusList] = useState([]);
|
||||||
const [operationLoading, setOperationLoading] = useState({});
|
const [operationLoading, setOperationLoading] = useState({});
|
||||||
|
|
||||||
// Pagination states
|
// Pagination states
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [pageSize, setPageSize] = useState(50);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [totalPages, setTotalPages] = useState(0);
|
const [totalPages, setTotalPages] = useState(0);
|
||||||
|
|
||||||
// Statistics states
|
// Statistics states
|
||||||
const [enabledCount, setEnabledCount] = useState(0);
|
const [enabledCount, setEnabledCount] = useState(0);
|
||||||
const [manualDisabledCount, setManualDisabledCount] = useState(0);
|
const [manualDisabledCount, setManualDisabledCount] = useState(0);
|
||||||
@@ -73,7 +70,7 @@ const MultiKeyManageModal = ({
|
|||||||
// Load key status data
|
// Load key status data
|
||||||
const loadKeyStatus = async (page = currentPage, size = pageSize, status = statusFilter) => {
|
const loadKeyStatus = async (page = currentPage, size = pageSize, status = statusFilter) => {
|
||||||
if (!channel?.id) return;
|
if (!channel?.id) return;
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const requestData = {
|
const requestData = {
|
||||||
@@ -82,22 +79,22 @@ const MultiKeyManageModal = ({
|
|||||||
page: page,
|
page: page,
|
||||||
page_size: size
|
page_size: size
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add status filter if specified
|
// Add status filter if specified
|
||||||
if (status !== null) {
|
if (status !== null) {
|
||||||
requestData.status = status;
|
requestData.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await API.post('/api/channel/multi_key/manage', requestData);
|
const res = await API.post('/api/channel/multi_key/manage', requestData);
|
||||||
|
|
||||||
if (res.data.success) {
|
if (res.data.success) {
|
||||||
const data = res.data.data;
|
const data = res.data.data;
|
||||||
setKeyStatusList(data.keys || []);
|
setKeyStatusList(data.keys || []);
|
||||||
setTotal(data.total || 0);
|
setTotal(data.total || 0);
|
||||||
setCurrentPage(data.page || 1);
|
setCurrentPage(data.page || 1);
|
||||||
setPageSize(data.page_size || 50);
|
setPageSize(data.page_size || 10);
|
||||||
setTotalPages(data.total_pages || 0);
|
setTotalPages(data.total_pages || 0);
|
||||||
|
|
||||||
// Update statistics (these are always the overall statistics)
|
// Update statistics (these are always the overall statistics)
|
||||||
setEnabledCount(data.enabled_count || 0);
|
setEnabledCount(data.enabled_count || 0);
|
||||||
setManualDisabledCount(data.manual_disabled_count || 0);
|
setManualDisabledCount(data.manual_disabled_count || 0);
|
||||||
@@ -117,14 +114,14 @@ const MultiKeyManageModal = ({
|
|||||||
const handleDisableKey = async (keyIndex) => {
|
const handleDisableKey = async (keyIndex) => {
|
||||||
const operationId = `disable_${keyIndex}`;
|
const operationId = `disable_${keyIndex}`;
|
||||||
setOperationLoading(prev => ({ ...prev, [operationId]: true }));
|
setOperationLoading(prev => ({ ...prev, [operationId]: true }));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await API.post('/api/channel/multi_key/manage', {
|
const res = await API.post('/api/channel/multi_key/manage', {
|
||||||
channel_id: channel.id,
|
channel_id: channel.id,
|
||||||
action: 'disable_key',
|
action: 'disable_key',
|
||||||
key_index: keyIndex
|
key_index: keyIndex
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.data.success) {
|
if (res.data.success) {
|
||||||
showSuccess(t('密钥已禁用'));
|
showSuccess(t('密钥已禁用'));
|
||||||
await loadKeyStatus(currentPage, pageSize); // Reload current page
|
await loadKeyStatus(currentPage, pageSize); // Reload current page
|
||||||
@@ -143,14 +140,14 @@ const MultiKeyManageModal = ({
|
|||||||
const handleEnableKey = async (keyIndex) => {
|
const handleEnableKey = async (keyIndex) => {
|
||||||
const operationId = `enable_${keyIndex}`;
|
const operationId = `enable_${keyIndex}`;
|
||||||
setOperationLoading(prev => ({ ...prev, [operationId]: true }));
|
setOperationLoading(prev => ({ ...prev, [operationId]: true }));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await API.post('/api/channel/multi_key/manage', {
|
const res = await API.post('/api/channel/multi_key/manage', {
|
||||||
channel_id: channel.id,
|
channel_id: channel.id,
|
||||||
action: 'enable_key',
|
action: 'enable_key',
|
||||||
key_index: keyIndex
|
key_index: keyIndex
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.data.success) {
|
if (res.data.success) {
|
||||||
showSuccess(t('密钥已启用'));
|
showSuccess(t('密钥已启用'));
|
||||||
await loadKeyStatus(currentPage, pageSize); // Reload current page
|
await loadKeyStatus(currentPage, pageSize); // Reload current page
|
||||||
@@ -168,13 +165,13 @@ const MultiKeyManageModal = ({
|
|||||||
// Enable all disabled keys
|
// Enable all disabled keys
|
||||||
const handleEnableAll = async () => {
|
const handleEnableAll = async () => {
|
||||||
setOperationLoading(prev => ({ ...prev, enable_all: true }));
|
setOperationLoading(prev => ({ ...prev, enable_all: true }));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await API.post('/api/channel/multi_key/manage', {
|
const res = await API.post('/api/channel/multi_key/manage', {
|
||||||
channel_id: channel.id,
|
channel_id: channel.id,
|
||||||
action: 'enable_all_keys'
|
action: 'enable_all_keys'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.data.success) {
|
if (res.data.success) {
|
||||||
showSuccess(res.data.message || t('已启用所有密钥'));
|
showSuccess(res.data.message || t('已启用所有密钥'));
|
||||||
// Reset to first page after bulk operation
|
// Reset to first page after bulk operation
|
||||||
@@ -194,13 +191,13 @@ const MultiKeyManageModal = ({
|
|||||||
// Disable all enabled keys
|
// Disable all enabled keys
|
||||||
const handleDisableAll = async () => {
|
const handleDisableAll = async () => {
|
||||||
setOperationLoading(prev => ({ ...prev, disable_all: true }));
|
setOperationLoading(prev => ({ ...prev, disable_all: true }));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await API.post('/api/channel/multi_key/manage', {
|
const res = await API.post('/api/channel/multi_key/manage', {
|
||||||
channel_id: channel.id,
|
channel_id: channel.id,
|
||||||
action: 'disable_all_keys'
|
action: 'disable_all_keys'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.data.success) {
|
if (res.data.success) {
|
||||||
showSuccess(res.data.message || t('已禁用所有密钥'));
|
showSuccess(res.data.message || t('已禁用所有密钥'));
|
||||||
// Reset to first page after bulk operation
|
// Reset to first page after bulk operation
|
||||||
@@ -220,13 +217,13 @@ const MultiKeyManageModal = ({
|
|||||||
// Delete all disabled keys
|
// Delete all disabled keys
|
||||||
const handleDeleteDisabledKeys = async () => {
|
const handleDeleteDisabledKeys = async () => {
|
||||||
setOperationLoading(prev => ({ ...prev, delete_disabled: true }));
|
setOperationLoading(prev => ({ ...prev, delete_disabled: true }));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await API.post('/api/channel/multi_key/manage', {
|
const res = await API.post('/api/channel/multi_key/manage', {
|
||||||
channel_id: channel.id,
|
channel_id: channel.id,
|
||||||
action: 'delete_disabled_keys'
|
action: 'delete_disabled_keys'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.data.success) {
|
if (res.data.success) {
|
||||||
showSuccess(res.data.message);
|
showSuccess(res.data.message);
|
||||||
// Reset to first page after deletion as data structure might change
|
// Reset to first page after deletion as data structure might change
|
||||||
@@ -285,17 +282,24 @@ const MultiKeyManageModal = ({
|
|||||||
}
|
}
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
|
// Percentages for progress display
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 取消饼图:不再需要图表数据与配置
|
||||||
|
|
||||||
// Get status tag component
|
// Get status tag component
|
||||||
const renderStatusTag = (status) => {
|
const renderStatusTag = (status) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 1:
|
case 1:
|
||||||
return <Tag color='green' shape='circle'>{t('已启用')}</Tag>;
|
return <Tag color='green' shape='circle' size='small'>{t('已启用')}</Tag>;
|
||||||
case 2:
|
case 2:
|
||||||
return <Tag color='red' shape='circle'>{t('已禁用')}</Tag>;
|
return <Tag color='red' shape='circle' size='small'>{t('已禁用')}</Tag>;
|
||||||
case 3:
|
case 3:
|
||||||
return <Tag color='orange' shape='circle'>{t('自动禁用')}</Tag>;
|
return <Tag color='orange' shape='circle' size='small'>{t('自动禁用')}</Tag>;
|
||||||
default:
|
default:
|
||||||
return <Tag color='grey' shape='circle'>{t('未知状态')}</Tag>;
|
return <Tag color='grey' shape='circle' size='small'>{t('未知状态')}</Tag>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -318,13 +322,11 @@ const MultiKeyManageModal = ({
|
|||||||
{
|
{
|
||||||
title: t('状态'),
|
title: t('状态'),
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
width: 100,
|
|
||||||
render: (status) => renderStatusTag(status),
|
render: (status) => renderStatusTag(status),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('禁用原因'),
|
title: t('禁用原因'),
|
||||||
dataIndex: 'reason',
|
dataIndex: 'reason',
|
||||||
width: 220,
|
|
||||||
render: (reason, record) => {
|
render: (reason, record) => {
|
||||||
if (record.status === 1 || !reason) {
|
if (record.status === 1 || !reason) {
|
||||||
return <Text type='quaternary'>-</Text>;
|
return <Text type='quaternary'>-</Text>;
|
||||||
@@ -341,7 +343,6 @@ const MultiKeyManageModal = ({
|
|||||||
{
|
{
|
||||||
title: t('禁用时间'),
|
title: t('禁用时间'),
|
||||||
dataIndex: 'disabled_time',
|
dataIndex: 'disabled_time',
|
||||||
width: 150,
|
|
||||||
render: (time, record) => {
|
render: (time, record) => {
|
||||||
if (record.status === 1 || !time) {
|
if (record.status === 1 || !time) {
|
||||||
return <Text type='quaternary'>-</Text>;
|
return <Text type='quaternary'>-</Text>;
|
||||||
@@ -358,7 +359,8 @@ const MultiKeyManageModal = ({
|
|||||||
{
|
{
|
||||||
title: t('操作'),
|
title: t('操作'),
|
||||||
key: 'action',
|
key: 'action',
|
||||||
width: 120,
|
fixed: 'right',
|
||||||
|
width: 100,
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<Space>
|
<Space>
|
||||||
{record.status === 1 ? (
|
{record.status === 1 ? (
|
||||||
@@ -389,196 +391,194 @@ const MultiKeyManageModal = ({
|
|||||||
<Modal
|
<Modal
|
||||||
title={
|
title={
|
||||||
<Space>
|
<Space>
|
||||||
<IconSetting />
|
<Text>{t('多密钥管理')}</Text>
|
||||||
<span>{t('多密钥管理')} - {channel?.name}</span>
|
{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>
|
</Space>
|
||||||
}
|
}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
width={900}
|
width={900}
|
||||||
footer={
|
footer={null}
|
||||||
<Space>
|
|
||||||
<Button onClick={onCancel}>{t('关闭')}</Button>
|
|
||||||
<Button
|
|
||||||
icon={<IconRefresh />}
|
|
||||||
onClick={() => loadKeyStatus(currentPage, pageSize)}
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
{t('刷新')}
|
|
||||||
</Button>
|
|
||||||
<Popconfirm
|
|
||||||
title={t('确定要启用所有密钥吗?')}
|
|
||||||
onConfirm={handleEnableAll}
|
|
||||||
position={'topRight'}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type='primary'
|
|
||||||
loading={operationLoading.enable_all}
|
|
||||||
>
|
|
||||||
{t('启用全部')}
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>
|
|
||||||
{enabledCount > 0 && (
|
|
||||||
<Popconfirm
|
|
||||||
title={t('确定要禁用所有的密钥吗?')}
|
|
||||||
onConfirm={handleDisableAll}
|
|
||||||
okType={'danger'}
|
|
||||||
position={'topRight'}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type='danger'
|
|
||||||
loading={operationLoading.disable_all}
|
|
||||||
>
|
|
||||||
{t('禁用全部')}
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>
|
|
||||||
)}
|
|
||||||
<Popconfirm
|
|
||||||
title={t('确定要删除所有已自动禁用的密钥吗?')}
|
|
||||||
content={t('此操作不可撤销,将永久删除已自动禁用的密钥')}
|
|
||||||
onConfirm={handleDeleteDisabledKeys}
|
|
||||||
okType={'danger'}
|
|
||||||
position={'topRight'}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type='danger'
|
|
||||||
icon={<IconDelete />}
|
|
||||||
loading={operationLoading.delete_disabled}
|
|
||||||
>
|
|
||||||
{t('删除自动禁用密钥')}
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>
|
|
||||||
</Space>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
<div className="flex flex-col mb-5">
|
||||||
{/* Statistics Banner */}
|
{/* Stats & Mode */}
|
||||||
<Banner
|
<div
|
||||||
type='info'
|
className="rounded-xl p-4 mb-3"
|
||||||
style={{ marginBottom: '16px', flexShrink: 0 }}
|
style={{
|
||||||
description={
|
background: 'var(--semi-color-bg-1)',
|
||||||
<div>
|
border: '1px solid var(--semi-color-border)'
|
||||||
<Text>
|
}}
|
||||||
{t('总共 {{total}} 个密钥,{{enabled}} 个已启用,{{manual}} 个手动禁用,{{auto}} 个自动禁用', {
|
>
|
||||||
total: total,
|
<Row gutter={16} align="middle">
|
||||||
enabled: enabledCount,
|
<Col span={8}>
|
||||||
manual: manualDisabledCount,
|
<div style={{ background: 'var(--semi-color-bg-0)', border: '1px solid var(--semi-color-border)', borderRadius: 12, padding: 12 }}>
|
||||||
auto: autoDisabledCount
|
<div className="flex items-center gap-2 mb-2">
|
||||||
})}
|
<Badge dot type='success' />
|
||||||
</Text>
|
<Text type='tertiary'>{t('已启用')}</Text>
|
||||||
{channel?.channel_info?.multi_key_mode && (
|
|
||||||
<div style={{ marginTop: '4px' }}>
|
|
||||||
<Text type='quaternary' style={{ fontSize: '12px' }}>
|
|
||||||
{t('多密钥模式')}: {channel.channel_info.multi_key_mode === 'random' ? t('随机') : t('轮询')}
|
|
||||||
</Text>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="flex items-end gap-2 mb-2">
|
||||||
</div>
|
<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 }} />
|
||||||
{/* Filter Controls */}
|
</div>
|
||||||
<div style={{ marginBottom: '16px', display: 'flex', alignItems: 'center', gap: '12px', flexShrink: 0 }}>
|
</Col>
|
||||||
<Text style={{ fontSize: '14px', fontWeight: '500' }}>{t('状态筛选')}:</Text>
|
<Col span={8}>
|
||||||
<Select
|
<div style={{ background: 'var(--semi-color-bg-0)', border: '1px solid var(--semi-color-border)', borderRadius: 12, padding: 12 }}>
|
||||||
value={statusFilter}
|
<div className="flex items-center gap-2 mb-2">
|
||||||
onChange={handleStatusFilterChange}
|
<Badge dot type='danger' />
|
||||||
style={{ width: '120px' }}
|
<Text type='tertiary'>{t('手动禁用')}</Text>
|
||||||
size='small'
|
</div>
|
||||||
placeholder={t('全部状态')}
|
<div className="flex items-end gap-2 mb-2">
|
||||||
>
|
<Text style={{ fontSize: 18, fontWeight: 700, color: '#ef4444' }}>{manualDisabledCount}</Text>
|
||||||
<Select.Option value={null}>{t('全部状态')}</Select.Option>
|
<Text style={{ fontSize: 18, color: 'var(--semi-color-text-2)' }}>/ {total}</Text>
|
||||||
<Select.Option value={1}>{t('已启用')}</Select.Option>
|
</div>
|
||||||
<Select.Option value={2}>{t('手动禁用')}</Select.Option>
|
<Progress percent={manualDisabledPercent} showInfo={false} size="small" stroke="#ef4444" style={{ height: 6, borderRadius: 999 }} />
|
||||||
<Select.Option value={3}>{t('自动禁用')}</Select.Option>
|
</div>
|
||||||
</Select>
|
</Col>
|
||||||
{statusFilter !== null && (
|
<Col span={8}>
|
||||||
<Text type='quaternary' style={{ fontSize: '12px' }}>
|
<div style={{ background: 'var(--semi-color-bg-0)', border: '1px solid var(--semi-color-border)', borderRadius: 12, padding: 12 }}>
|
||||||
{t('当前显示 {{count}} 条筛选结果', { count: total })}
|
<div className="flex items-center gap-2 mb-2">
|
||||||
</Text>
|
<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>
|
</div>
|
||||||
|
|
||||||
{/* Key Status Table */}
|
{/* Table */}
|
||||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
|
<div className="flex-1 flex flex-col min-h-0">
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
{keyStatusList.length > 0 ? (
|
<Card className='!rounded-xl'>
|
||||||
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
<Table
|
||||||
<div style={{ flex: 1, overflow: 'auto', marginBottom: '16px' }}>
|
title={() => (
|
||||||
<Table
|
<Row gutter={12} style={{ width: '100%' }}>
|
||||||
columns={columns}
|
<Col span={14}>
|
||||||
dataSource={keyStatusList}
|
<Row gutter={12} style={{ alignItems: 'center' }}>
|
||||||
pagination={false}
|
<Col>
|
||||||
size='small'
|
<Select
|
||||||
bordered
|
value={statusFilter}
|
||||||
rowKey='index'
|
onChange={handleStatusFilterChange}
|
||||||
scroll={{ y: 'calc(100vh - 400px)' }}
|
size='small'
|
||||||
/>
|
placeholder={t('全部状态')}
|
||||||
</div>
|
>
|
||||||
|
<Select.Option value={null}>{t('全部状态')}</Select.Option>
|
||||||
{/* Pagination */}
|
<Select.Option value={1}>{t('已启用')}</Select.Option>
|
||||||
{total > 0 && (
|
<Select.Option value={2}>{t('手动禁用')}</Select.Option>
|
||||||
<div style={{
|
<Select.Option value={3}>{t('自动禁用')}</Select.Option>
|
||||||
display: 'flex',
|
</Select>
|
||||||
justifyContent: 'space-between',
|
</Col>
|
||||||
alignItems: 'center',
|
</Row>
|
||||||
flexShrink: 0,
|
</Col>
|
||||||
padding: '12px 0',
|
<Col span={10} style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
borderTop: '1px solid var(--semi-color-border)',
|
<Space>
|
||||||
backgroundColor: 'var(--semi-color-bg-1)'
|
<Button
|
||||||
}}>
|
size='small'
|
||||||
<Text type='quaternary' style={{ fontSize: '12px' }}>
|
type='tertiary'
|
||||||
{t('显示第 {{start}}-{{end}} 条,共 {{total}} 条', {
|
onClick={() => loadKeyStatus(currentPage, pageSize)}
|
||||||
start: (currentPage - 1) * pageSize + 1,
|
loading={loading}
|
||||||
end: Math.min(currentPage * pageSize, total),
|
>
|
||||||
total: total
|
{t('刷新')}
|
||||||
})}
|
</Button>
|
||||||
</Text>
|
{(manualDisabledCount + autoDisabledCount) > 0 && (
|
||||||
|
<Popconfirm
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
title={t('确定要启用所有密钥吗?')}
|
||||||
<Text type='quaternary' style={{ fontSize: '12px' }}>
|
onConfirm={handleEnableAll}
|
||||||
{t('每页显示')}:
|
position={'topRight'}
|
||||||
</Text>
|
>
|
||||||
<Select
|
<Button
|
||||||
value={pageSize}
|
size='small'
|
||||||
onChange={handlePageSizeChange}
|
type='primary'
|
||||||
size='small'
|
loading={operationLoading.enable_all}
|
||||||
style={{ width: '80px' }}
|
>
|
||||||
>
|
{t('启用全部')}
|
||||||
<Select.Option value={50}>50</Select.Option>
|
</Button>
|
||||||
<Select.Option value={100}>100</Select.Option>
|
</Popconfirm>
|
||||||
<Select.Option value={500}>500</Select.Option>
|
)}
|
||||||
<Select.Option value={1000}>1000</Select.Option>
|
{enabledCount > 0 && (
|
||||||
</Select>
|
<Popconfirm
|
||||||
|
title={t('确定要禁用所有的密钥吗?')}
|
||||||
<Pagination
|
onConfirm={handleDisableAll}
|
||||||
current={currentPage}
|
okType={'danger'}
|
||||||
total={total}
|
position={'topRight'}
|
||||||
pageSize={pageSize}
|
>
|
||||||
showSizeChanger={false}
|
<Button
|
||||||
showQuickJumper
|
size='small'
|
||||||
size='small'
|
type='danger'
|
||||||
onChange={handlePageChange}
|
loading={operationLoading.disable_all}
|
||||||
showTotal={(total, range) =>
|
>
|
||||||
t('第 {{current}} / {{total}} 页', {
|
{t('禁用全部')}
|
||||||
current: currentPage,
|
</Button>
|
||||||
total: totalPages
|
</Popconfirm>
|
||||||
})
|
)}
|
||||||
}
|
<Popconfirm
|
||||||
/>
|
title={t('确定要删除所有已自动禁用的密钥吗?')}
|
||||||
</div>
|
content={t('此操作不可撤销,将永久删除已自动禁用的密钥')}
|
||||||
</div>
|
onConfirm={handleDeleteDisabledKeys}
|
||||||
|
okType={'danger'}
|
||||||
|
position={'topRight'}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size='small'
|
||||||
|
type='warning'
|
||||||
|
loading={operationLoading.delete_disabled}
|
||||||
|
>
|
||||||
|
{t('删除自动禁用密钥')}
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
)}
|
)}
|
||||||
</div>
|
columns={columns}
|
||||||
) : (
|
dataSource={keyStatusList}
|
||||||
!loading && (
|
pagination={{
|
||||||
<Empty
|
currentPage: currentPage,
|
||||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
pageSize: pageSize,
|
||||||
title={t('暂无密钥数据')}
|
total: total,
|
||||||
description={t('请检查渠道配置或刷新重试')}
|
showSizeChanger: true,
|
||||||
/>
|
showQuickJumper: true,
|
||||||
)
|
pageSizeOptions: ['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>
|
</Spin>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1890,5 +1890,22 @@
|
|||||||
"未知供应商": "Unknown",
|
"未知供应商": "Unknown",
|
||||||
"共 {{count}} 个模型": "{{count}} models",
|
"共 {{count}} 个模型": "{{count}} models",
|
||||||
"倍率信息": "Ratio information",
|
"倍率信息": "Ratio information",
|
||||||
"倍率是用于系统计算不同模型的最终价格用的,如果您不理解倍率,请忽略": "The ratio is used to calculate the final price of different models in the system. If you do not understand the ratio, please ignore it."
|
"倍率是用于系统计算不同模型的最终价格用的,如果您不理解倍率,请忽略": "The ratio is used to calculate the final price of different models in the system. If you do not understand the ratio, please ignore it.",
|
||||||
|
"多密钥管理": "Multi-key management",
|
||||||
|
"总密钥数": "Total key count",
|
||||||
|
"随机模式": "Random mode",
|
||||||
|
"轮询模式": "Polling mode",
|
||||||
|
"手动禁用": "Manually disabled",
|
||||||
|
"自动禁用": "Auto disabled",
|
||||||
|
"暂无密钥数据": "No key data",
|
||||||
|
"请检查渠道配置或刷新重试": "Please check the channel configuration or refresh and try again",
|
||||||
|
"全部状态": "All status",
|
||||||
|
"索引": "Index",
|
||||||
|
"禁用原因": "Disable reason",
|
||||||
|
"禁用时间": "Disable time",
|
||||||
|
"确定要启用所有密钥吗?": "Are you sure you want to enable all keys?",
|
||||||
|
"确定要禁用所有的密钥吗?": "Are you sure you want to disable all keys?",
|
||||||
|
"确定要删除所有已自动禁用的密钥吗?": "Are you sure you want to delete all automatically disabled keys?",
|
||||||
|
"此操作不可撤销,将永久删除已自动禁用的密钥": "This operation cannot be undone, and all automatically disabled keys will be permanently deleted.",
|
||||||
|
"删除自动禁用密钥": "Delete auto disabled keys"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user