|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import React from 'react'; |
|
|
import { |
|
|
Button, |
|
|
Dropdown, |
|
|
InputNumber, |
|
|
Modal, |
|
|
Space, |
|
|
SplitButtonGroup, |
|
|
Tag, |
|
|
Tooltip, |
|
|
Typography, |
|
|
} from '@douyinfe/semi-ui'; |
|
|
import { |
|
|
timestamp2string, |
|
|
renderGroup, |
|
|
renderQuota, |
|
|
getChannelIcon, |
|
|
renderQuotaWithAmount, |
|
|
showSuccess, |
|
|
showError, |
|
|
} from '../../../helpers'; |
|
|
import { CHANNEL_OPTIONS } from '../../../constants'; |
|
|
import { IconTreeTriangleDown, IconMore } from '@douyinfe/semi-icons'; |
|
|
import { FaRandom } from 'react-icons/fa'; |
|
|
|
|
|
|
|
|
const renderType = (type, channelInfo = undefined, t) => { |
|
|
let type2label = new Map(); |
|
|
for (let i = 0; i < CHANNEL_OPTIONS.length; i++) { |
|
|
type2label[CHANNEL_OPTIONS[i].value] = CHANNEL_OPTIONS[i]; |
|
|
} |
|
|
type2label[0] = { value: 0, label: t('未知类型'), color: 'grey' }; |
|
|
|
|
|
let icon = getChannelIcon(type); |
|
|
|
|
|
if (channelInfo?.is_multi_key) { |
|
|
icon = |
|
|
channelInfo?.multi_key_mode === 'random' ? ( |
|
|
<div className='flex items-center gap-1'> |
|
|
<FaRandom className='text-blue-500' /> |
|
|
{icon} |
|
|
</div> |
|
|
) : ( |
|
|
<div className='flex items-center gap-1'> |
|
|
<IconTreeTriangleDown className='text-blue-500' /> |
|
|
{icon} |
|
|
</div> |
|
|
); |
|
|
} |
|
|
|
|
|
return ( |
|
|
<Tag color={type2label[type]?.color} shape='circle' prefixIcon={icon}> |
|
|
{type2label[type]?.label} |
|
|
</Tag> |
|
|
); |
|
|
}; |
|
|
|
|
|
const renderTagType = (t) => { |
|
|
return ( |
|
|
<Tag color='light-blue' shape='circle' type='light'> |
|
|
{t('标签聚合')} |
|
|
</Tag> |
|
|
); |
|
|
}; |
|
|
|
|
|
const renderStatus = (status, channelInfo = undefined, t) => { |
|
|
if (channelInfo) { |
|
|
if (channelInfo.is_multi_key) { |
|
|
let keySize = channelInfo.multi_key_size; |
|
|
let enabledKeySize = keySize; |
|
|
if (channelInfo.multi_key_status_list) { |
|
|
enabledKeySize = |
|
|
keySize - Object.keys(channelInfo.multi_key_status_list).length; |
|
|
} |
|
|
return renderMultiKeyStatus(status, keySize, enabledKeySize, t); |
|
|
} |
|
|
} |
|
|
switch (status) { |
|
|
case 1: |
|
|
return ( |
|
|
<Tag color='green' shape='circle'> |
|
|
{t('已启用')} |
|
|
</Tag> |
|
|
); |
|
|
case 2: |
|
|
return ( |
|
|
<Tag color='red' shape='circle'> |
|
|
{t('已禁用')} |
|
|
</Tag> |
|
|
); |
|
|
case 3: |
|
|
return ( |
|
|
<Tag color='yellow' shape='circle'> |
|
|
{t('自动禁用')} |
|
|
</Tag> |
|
|
); |
|
|
default: |
|
|
return ( |
|
|
<Tag color='grey' shape='circle'> |
|
|
{t('未知状态')} |
|
|
</Tag> |
|
|
); |
|
|
} |
|
|
}; |
|
|
|
|
|
const renderMultiKeyStatus = (status, keySize, enabledKeySize, t) => { |
|
|
switch (status) { |
|
|
case 1: |
|
|
return ( |
|
|
<Tag color='green' shape='circle'> |
|
|
{t('已启用')} {enabledKeySize}/{keySize} |
|
|
</Tag> |
|
|
); |
|
|
case 2: |
|
|
return ( |
|
|
<Tag color='red' shape='circle'> |
|
|
{t('已禁用')} {enabledKeySize}/{keySize} |
|
|
</Tag> |
|
|
); |
|
|
case 3: |
|
|
return ( |
|
|
<Tag color='yellow' shape='circle'> |
|
|
{t('自动禁用')} {enabledKeySize}/{keySize} |
|
|
</Tag> |
|
|
); |
|
|
default: |
|
|
return ( |
|
|
<Tag color='grey' shape='circle'> |
|
|
{t('未知状态')} {enabledKeySize}/{keySize} |
|
|
</Tag> |
|
|
); |
|
|
} |
|
|
}; |
|
|
|
|
|
const renderResponseTime = (responseTime, t) => { |
|
|
let time = responseTime / 1000; |
|
|
time = time.toFixed(2) + t(' 秒'); |
|
|
if (responseTime === 0) { |
|
|
return ( |
|
|
<Tag color='grey' shape='circle'> |
|
|
{t('未测试')} |
|
|
</Tag> |
|
|
); |
|
|
} else if (responseTime <= 1000) { |
|
|
return ( |
|
|
<Tag color='green' shape='circle'> |
|
|
{time} |
|
|
</Tag> |
|
|
); |
|
|
} else if (responseTime <= 3000) { |
|
|
return ( |
|
|
<Tag color='lime' shape='circle'> |
|
|
{time} |
|
|
</Tag> |
|
|
); |
|
|
} else if (responseTime <= 5000) { |
|
|
return ( |
|
|
<Tag color='yellow' shape='circle'> |
|
|
{time} |
|
|
</Tag> |
|
|
); |
|
|
} else { |
|
|
return ( |
|
|
<Tag color='red' shape='circle'> |
|
|
{time} |
|
|
</Tag> |
|
|
); |
|
|
} |
|
|
}; |
|
|
|
|
|
export const getChannelsColumns = ({ |
|
|
t, |
|
|
COLUMN_KEYS, |
|
|
updateChannelBalance, |
|
|
manageChannel, |
|
|
manageTag, |
|
|
submitTagEdit, |
|
|
testChannel, |
|
|
setCurrentTestChannel, |
|
|
setShowModelTestModal, |
|
|
setEditingChannel, |
|
|
setShowEdit, |
|
|
setShowEditTag, |
|
|
setEditingTag, |
|
|
copySelectedChannel, |
|
|
refresh, |
|
|
activePage, |
|
|
channels, |
|
|
setShowMultiKeyManageModal, |
|
|
setCurrentMultiKeyChannel, |
|
|
}) => { |
|
|
return [ |
|
|
{ |
|
|
key: COLUMN_KEYS.ID, |
|
|
title: t('ID'), |
|
|
dataIndex: 'id', |
|
|
}, |
|
|
{ |
|
|
key: COLUMN_KEYS.NAME, |
|
|
title: t('名称'), |
|
|
dataIndex: 'name', |
|
|
render: (text, record, index) => { |
|
|
if (record.remark && record.remark.trim() !== '') { |
|
|
return ( |
|
|
<Tooltip |
|
|
content={ |
|
|
<div className='flex flex-col gap-2 max-w-xs'> |
|
|
<div className='text-sm'>{record.remark}</div> |
|
|
<Button |
|
|
size='small' |
|
|
type='primary' |
|
|
theme='outline' |
|
|
onClick={(e) => { |
|
|
e.stopPropagation(); |
|
|
navigator.clipboard |
|
|
.writeText(record.remark) |
|
|
.then(() => { |
|
|
showSuccess(t('复制成功')); |
|
|
}) |
|
|
.catch(() => { |
|
|
showError(t('复制失败')); |
|
|
}); |
|
|
}} |
|
|
> |
|
|
{t('复制')} |
|
|
</Button> |
|
|
</div> |
|
|
} |
|
|
trigger='hover' |
|
|
position='topLeft' |
|
|
> |
|
|
<span>{text}</span> |
|
|
</Tooltip> |
|
|
); |
|
|
} |
|
|
return text; |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
key: COLUMN_KEYS.GROUP, |
|
|
title: t('分组'), |
|
|
dataIndex: 'group', |
|
|
render: (text, record, index) => ( |
|
|
<div> |
|
|
<Space spacing={2}> |
|
|
{text |
|
|
?.split(',') |
|
|
.sort((a, b) => { |
|
|
if (a === 'default') return -1; |
|
|
if (b === 'default') return 1; |
|
|
return a.localeCompare(b); |
|
|
}) |
|
|
.map((item, index) => renderGroup(item))} |
|
|
</Space> |
|
|
</div> |
|
|
), |
|
|
}, |
|
|
{ |
|
|
key: COLUMN_KEYS.TYPE, |
|
|
title: t('类型'), |
|
|
dataIndex: 'type', |
|
|
render: (text, record, index) => { |
|
|
if (record.children === undefined) { |
|
|
if (record.channel_info) { |
|
|
if (record.channel_info.is_multi_key) { |
|
|
return <>{renderType(text, record.channel_info, t)}</>; |
|
|
} |
|
|
} |
|
|
return <>{renderType(text, undefined, t)}</>; |
|
|
} else { |
|
|
return <>{renderTagType(t)}</>; |
|
|
} |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
key: COLUMN_KEYS.STATUS, |
|
|
title: t('状态'), |
|
|
dataIndex: 'status', |
|
|
render: (text, record, index) => { |
|
|
if (text === 3) { |
|
|
if (record.other_info === '') { |
|
|
record.other_info = '{}'; |
|
|
} |
|
|
let otherInfo = JSON.parse(record.other_info); |
|
|
let reason = otherInfo['status_reason']; |
|
|
let time = otherInfo['status_time']; |
|
|
return ( |
|
|
<div> |
|
|
<Tooltip |
|
|
content={ |
|
|
t('原因:') + reason + t(',时间:') + timestamp2string(time) |
|
|
} |
|
|
> |
|
|
{renderStatus(text, record.channel_info, t)} |
|
|
</Tooltip> |
|
|
</div> |
|
|
); |
|
|
} else { |
|
|
return renderStatus(text, record.channel_info, t); |
|
|
} |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
key: COLUMN_KEYS.RESPONSE_TIME, |
|
|
title: t('响应时间'), |
|
|
dataIndex: 'response_time', |
|
|
render: (text, record, index) => <div>{renderResponseTime(text, t)}</div>, |
|
|
}, |
|
|
{ |
|
|
key: COLUMN_KEYS.BALANCE, |
|
|
title: t('已用/剩余'), |
|
|
dataIndex: 'expired_time', |
|
|
render: (text, record, index) => { |
|
|
if (record.children === undefined) { |
|
|
return ( |
|
|
<div> |
|
|
<Space spacing={1}> |
|
|
<Tooltip content={t('已用额度')}> |
|
|
<Tag color='white' type='ghost' shape='circle'> |
|
|
{renderQuota(record.used_quota)} |
|
|
</Tag> |
|
|
</Tooltip> |
|
|
<Tooltip |
|
|
content={t('剩余额度$') + record.balance + t(',点击更新')} |
|
|
> |
|
|
<Tag |
|
|
color='white' |
|
|
type='ghost' |
|
|
shape='circle' |
|
|
onClick={() => updateChannelBalance(record)} |
|
|
> |
|
|
{renderQuotaWithAmount(record.balance)} |
|
|
</Tag> |
|
|
</Tooltip> |
|
|
</Space> |
|
|
</div> |
|
|
); |
|
|
} else { |
|
|
return ( |
|
|
<Tooltip content={t('已用额度')}> |
|
|
<Tag color='white' type='ghost' shape='circle'> |
|
|
{renderQuota(record.used_quota)} |
|
|
</Tag> |
|
|
</Tooltip> |
|
|
); |
|
|
} |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
key: COLUMN_KEYS.PRIORITY, |
|
|
title: t('优先级'), |
|
|
dataIndex: 'priority', |
|
|
render: (text, record, index) => { |
|
|
if (record.children === undefined) { |
|
|
return ( |
|
|
<div> |
|
|
<InputNumber |
|
|
style={{ width: 70 }} |
|
|
name='priority' |
|
|
onBlur={(e) => { |
|
|
manageChannel(record.id, 'priority', record, e.target.value); |
|
|
}} |
|
|
keepFocus={true} |
|
|
innerButtons |
|
|
defaultValue={record.priority} |
|
|
min={-999} |
|
|
size='small' |
|
|
/> |
|
|
</div> |
|
|
); |
|
|
} else { |
|
|
return ( |
|
|
<InputNumber |
|
|
style={{ width: 70 }} |
|
|
name='priority' |
|
|
keepFocus={true} |
|
|
onBlur={(e) => { |
|
|
Modal.warning({ |
|
|
title: t('修改子渠道优先级'), |
|
|
content: |
|
|
t('确定要修改所有子渠道优先级为 ') + |
|
|
e.target.value + |
|
|
t(' 吗?'), |
|
|
onOk: () => { |
|
|
if (e.target.value === '') { |
|
|
return; |
|
|
} |
|
|
submitTagEdit('priority', { |
|
|
tag: record.key, |
|
|
priority: e.target.value, |
|
|
}); |
|
|
}, |
|
|
}); |
|
|
}} |
|
|
innerButtons |
|
|
defaultValue={record.priority} |
|
|
min={-999} |
|
|
size='small' |
|
|
/> |
|
|
); |
|
|
} |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
key: COLUMN_KEYS.WEIGHT, |
|
|
title: t('权重'), |
|
|
dataIndex: 'weight', |
|
|
render: (text, record, index) => { |
|
|
if (record.children === undefined) { |
|
|
return ( |
|
|
<div> |
|
|
<InputNumber |
|
|
style={{ width: 70 }} |
|
|
name='weight' |
|
|
onBlur={(e) => { |
|
|
manageChannel(record.id, 'weight', record, e.target.value); |
|
|
}} |
|
|
keepFocus={true} |
|
|
innerButtons |
|
|
defaultValue={record.weight} |
|
|
min={0} |
|
|
size='small' |
|
|
/> |
|
|
</div> |
|
|
); |
|
|
} else { |
|
|
return ( |
|
|
<InputNumber |
|
|
style={{ width: 70 }} |
|
|
name='weight' |
|
|
keepFocus={true} |
|
|
onBlur={(e) => { |
|
|
Modal.warning({ |
|
|
title: t('修改子渠道权重'), |
|
|
content: |
|
|
t('确定要修改所有子渠道权重为 ') + |
|
|
e.target.value + |
|
|
t(' 吗?'), |
|
|
onOk: () => { |
|
|
if (e.target.value === '') { |
|
|
return; |
|
|
} |
|
|
submitTagEdit('weight', { |
|
|
tag: record.key, |
|
|
weight: e.target.value, |
|
|
}); |
|
|
}, |
|
|
}); |
|
|
}} |
|
|
innerButtons |
|
|
defaultValue={record.weight} |
|
|
min={-999} |
|
|
size='small' |
|
|
/> |
|
|
); |
|
|
} |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
key: COLUMN_KEYS.OPERATE, |
|
|
title: '', |
|
|
dataIndex: 'operate', |
|
|
fixed: 'right', |
|
|
render: (text, record, index) => { |
|
|
if (record.children === undefined) { |
|
|
const moreMenuItems = [ |
|
|
{ |
|
|
node: 'item', |
|
|
name: t('删除'), |
|
|
type: 'danger', |
|
|
onClick: () => { |
|
|
Modal.confirm({ |
|
|
title: t('确定是否要删除此渠道?'), |
|
|
content: t('此修改将不可逆'), |
|
|
onOk: () => { |
|
|
(async () => { |
|
|
await manageChannel(record.id, 'delete', record); |
|
|
await refresh(); |
|
|
setTimeout(() => { |
|
|
if (channels.length === 0 && activePage > 1) { |
|
|
refresh(activePage - 1); |
|
|
} |
|
|
}, 100); |
|
|
})(); |
|
|
}, |
|
|
}); |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
node: 'item', |
|
|
name: t('复制'), |
|
|
type: 'tertiary', |
|
|
onClick: () => { |
|
|
Modal.confirm({ |
|
|
title: t('确定是否要复制此渠道?'), |
|
|
content: t('复制渠道的所有信息'), |
|
|
onOk: () => copySelectedChannel(record), |
|
|
}); |
|
|
}, |
|
|
}, |
|
|
]; |
|
|
|
|
|
return ( |
|
|
<Space wrap> |
|
|
<SplitButtonGroup |
|
|
className='overflow-hidden' |
|
|
aria-label={t('测试单个渠道操作项目组')} |
|
|
> |
|
|
<Button |
|
|
size='small' |
|
|
type='tertiary' |
|
|
onClick={() => testChannel(record, '')} |
|
|
> |
|
|
{t('测试')} |
|
|
</Button> |
|
|
<Button |
|
|
size='small' |
|
|
type='tertiary' |
|
|
icon={<IconTreeTriangleDown />} |
|
|
onClick={() => { |
|
|
setCurrentTestChannel(record); |
|
|
setShowModelTestModal(true); |
|
|
}} |
|
|
/> |
|
|
</SplitButtonGroup> |
|
|
|
|
|
{record.status === 1 ? ( |
|
|
<Button |
|
|
type='danger' |
|
|
size='small' |
|
|
onClick={() => manageChannel(record.id, 'disable', record)} |
|
|
> |
|
|
{t('禁用')} |
|
|
</Button> |
|
|
) : ( |
|
|
<Button |
|
|
size='small' |
|
|
onClick={() => manageChannel(record.id, 'enable', record)} |
|
|
> |
|
|
{t('启用')} |
|
|
</Button> |
|
|
)} |
|
|
|
|
|
{record.channel_info?.is_multi_key ? ( |
|
|
<SplitButtonGroup aria-label={t('多密钥渠道操作项目组')}> |
|
|
<Button |
|
|
type='tertiary' |
|
|
size='small' |
|
|
onClick={() => { |
|
|
setEditingChannel(record); |
|
|
setShowEdit(true); |
|
|
}} |
|
|
> |
|
|
{t('编辑')} |
|
|
</Button> |
|
|
<Dropdown |
|
|
trigger='click' |
|
|
position='bottomRight' |
|
|
menu={[ |
|
|
{ |
|
|
node: 'item', |
|
|
name: t('多密钥管理'), |
|
|
onClick: () => { |
|
|
setCurrentMultiKeyChannel(record); |
|
|
setShowMultiKeyManageModal(true); |
|
|
}, |
|
|
}, |
|
|
]} |
|
|
> |
|
|
<Button |
|
|
type='tertiary' |
|
|
size='small' |
|
|
icon={<IconTreeTriangleDown />} |
|
|
/> |
|
|
</Dropdown> |
|
|
</SplitButtonGroup> |
|
|
) : ( |
|
|
<Button |
|
|
type='tertiary' |
|
|
size='small' |
|
|
onClick={() => { |
|
|
setEditingChannel(record); |
|
|
setShowEdit(true); |
|
|
}} |
|
|
> |
|
|
{t('编辑')} |
|
|
</Button> |
|
|
)} |
|
|
|
|
|
<Dropdown |
|
|
trigger='click' |
|
|
position='bottomRight' |
|
|
menu={moreMenuItems} |
|
|
> |
|
|
<Button icon={<IconMore />} type='tertiary' size='small' /> |
|
|
</Dropdown> |
|
|
</Space> |
|
|
); |
|
|
} else { |
|
|
|
|
|
return ( |
|
|
<Space wrap> |
|
|
<Button |
|
|
type='tertiary' |
|
|
size='small' |
|
|
onClick={() => manageTag(record.key, 'enable')} |
|
|
> |
|
|
{t('启用全部')} |
|
|
</Button> |
|
|
<Button |
|
|
type='tertiary' |
|
|
size='small' |
|
|
onClick={() => manageTag(record.key, 'disable')} |
|
|
> |
|
|
{t('禁用全部')} |
|
|
</Button> |
|
|
<Button |
|
|
type='tertiary' |
|
|
size='small' |
|
|
onClick={() => { |
|
|
setShowEditTag(true); |
|
|
setEditingTag(record.key); |
|
|
}} |
|
|
> |
|
|
{t('编辑')} |
|
|
</Button> |
|
|
</Space> |
|
|
); |
|
|
} |
|
|
}, |
|
|
}, |
|
|
]; |
|
|
}; |
|
|
|