| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | 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> |
| | ); |
| | } |
| | }, |
| | }, |
| | ]; |
| | }; |
| |
|