|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import React from 'react'; |
|
|
import { |
|
|
Button, |
|
|
Space, |
|
|
Tag, |
|
|
Tooltip, |
|
|
Progress, |
|
|
Popover, |
|
|
Typography, |
|
|
Dropdown, |
|
|
} from '@douyinfe/semi-ui'; |
|
|
import { IconMore } from '@douyinfe/semi-icons'; |
|
|
import { renderGroup, renderNumber, renderQuota } from '../../../helpers'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const renderRole = (role, t) => { |
|
|
switch (role) { |
|
|
case 1: |
|
|
return ( |
|
|
<Tag color='blue' shape='circle'> |
|
|
{t('普通用户')} |
|
|
</Tag> |
|
|
); |
|
|
case 10: |
|
|
return ( |
|
|
<Tag color='yellow' shape='circle'> |
|
|
{t('管理员')} |
|
|
</Tag> |
|
|
); |
|
|
case 100: |
|
|
return ( |
|
|
<Tag color='orange' shape='circle'> |
|
|
{t('超级管理员')} |
|
|
</Tag> |
|
|
); |
|
|
default: |
|
|
return ( |
|
|
<Tag color='red' shape='circle'> |
|
|
{t('未知身份')} |
|
|
</Tag> |
|
|
); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const renderUsername = (text, record) => { |
|
|
const remark = record.remark; |
|
|
if (!remark) { |
|
|
return <span>{text}</span>; |
|
|
} |
|
|
const maxLen = 10; |
|
|
const displayRemark = |
|
|
remark.length > maxLen ? remark.slice(0, maxLen) + '…' : remark; |
|
|
return ( |
|
|
<Space spacing={2}> |
|
|
<span>{text}</span> |
|
|
<Tooltip content={remark} position='top' showArrow> |
|
|
<Tag color='white' shape='circle' className='!text-xs'> |
|
|
<div className='flex items-center gap-1'> |
|
|
<div |
|
|
className='w-2 h-2 flex-shrink-0 rounded-full' |
|
|
style={{ backgroundColor: '#10b981' }} |
|
|
/> |
|
|
{displayRemark} |
|
|
</div> |
|
|
</Tag> |
|
|
</Tooltip> |
|
|
</Space> |
|
|
); |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const renderStatistics = (text, record, showEnableDisableModal, t) => { |
|
|
const isDeleted = record.DeletedAt !== null; |
|
|
|
|
|
|
|
|
let tagColor = 'grey'; |
|
|
let tagText = t('未知状态'); |
|
|
if (isDeleted) { |
|
|
tagColor = 'red'; |
|
|
tagText = t('已注销'); |
|
|
} else if (record.status === 1) { |
|
|
tagColor = 'green'; |
|
|
tagText = t('已启用'); |
|
|
} else if (record.status === 2) { |
|
|
tagColor = 'red'; |
|
|
tagText = t('已禁用'); |
|
|
} |
|
|
|
|
|
const content = ( |
|
|
<Tag color={tagColor} shape='circle' size='small'> |
|
|
{tagText} |
|
|
</Tag> |
|
|
); |
|
|
|
|
|
const tooltipContent = ( |
|
|
<div className='text-xs'> |
|
|
<div> |
|
|
{t('调用次数')}: {renderNumber(record.request_count)} |
|
|
</div> |
|
|
</div> |
|
|
); |
|
|
|
|
|
return ( |
|
|
<Tooltip content={tooltipContent} position='top'> |
|
|
{content} |
|
|
</Tooltip> |
|
|
); |
|
|
}; |
|
|
|
|
|
|
|
|
const renderQuotaUsage = (text, record, t) => { |
|
|
const { Paragraph } = Typography; |
|
|
const used = parseInt(record.used_quota) || 0; |
|
|
const remain = parseInt(record.quota) || 0; |
|
|
const total = used + remain; |
|
|
const percent = total > 0 ? (remain / total) * 100 : 0; |
|
|
const popoverContent = ( |
|
|
<div className='text-xs p-2'> |
|
|
<Paragraph copyable={{ content: renderQuota(used) }}> |
|
|
{t('已用额度')}: {renderQuota(used)} |
|
|
</Paragraph> |
|
|
<Paragraph copyable={{ content: renderQuota(remain) }}> |
|
|
{t('剩余额度')}: {renderQuota(remain)} ({percent.toFixed(0)}%) |
|
|
</Paragraph> |
|
|
<Paragraph copyable={{ content: renderQuota(total) }}> |
|
|
{t('总额度')}: {renderQuota(total)} |
|
|
</Paragraph> |
|
|
</div> |
|
|
); |
|
|
return ( |
|
|
<Popover content={popoverContent} position='top'> |
|
|
<Tag color='white' shape='circle'> |
|
|
<div className='flex flex-col items-end'> |
|
|
<span className='text-xs leading-none'>{`${renderQuota(remain)} / ${renderQuota(total)}`}</span> |
|
|
<Progress |
|
|
percent={percent} |
|
|
aria-label='quota usage' |
|
|
format={() => `${percent.toFixed(0)}%`} |
|
|
style={{ width: '100%', marginTop: '1px', marginBottom: 0 }} |
|
|
/> |
|
|
</div> |
|
|
</Tag> |
|
|
</Popover> |
|
|
); |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const renderInviteInfo = (text, record, t) => { |
|
|
return ( |
|
|
<div> |
|
|
<Space spacing={1}> |
|
|
<Tag color='white' shape='circle' className='!text-xs'> |
|
|
{t('邀请')}: {renderNumber(record.aff_count)} |
|
|
</Tag> |
|
|
<Tag color='white' shape='circle' className='!text-xs'> |
|
|
{t('收益')}: {renderQuota(record.aff_history_quota)} |
|
|
</Tag> |
|
|
<Tag color='white' shape='circle' className='!text-xs'> |
|
|
{record.inviter_id === 0 |
|
|
? t('无邀请人') |
|
|
: `${t('邀请人')}: ${record.inviter_id}`} |
|
|
</Tag> |
|
|
</Space> |
|
|
</div> |
|
|
); |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const renderOperations = ( |
|
|
text, |
|
|
record, |
|
|
{ |
|
|
setEditingUser, |
|
|
setShowEditUser, |
|
|
showPromoteModal, |
|
|
showDemoteModal, |
|
|
showEnableDisableModal, |
|
|
showDeleteModal, |
|
|
showResetPasskeyModal, |
|
|
showResetTwoFAModal, |
|
|
t, |
|
|
}, |
|
|
) => { |
|
|
if (record.DeletedAt !== null) { |
|
|
return <></>; |
|
|
} |
|
|
|
|
|
const moreMenu = [ |
|
|
{ |
|
|
node: 'item', |
|
|
name: t('重置 Passkey'), |
|
|
onClick: () => showResetPasskeyModal(record), |
|
|
}, |
|
|
{ |
|
|
node: 'item', |
|
|
name: t('重置 2FA'), |
|
|
onClick: () => showResetTwoFAModal(record), |
|
|
}, |
|
|
{ |
|
|
node: 'divider', |
|
|
}, |
|
|
{ |
|
|
node: 'item', |
|
|
name: t('注销'), |
|
|
type: 'danger', |
|
|
onClick: () => showDeleteModal(record), |
|
|
}, |
|
|
]; |
|
|
|
|
|
return ( |
|
|
<Space> |
|
|
{record.status === 1 ? ( |
|
|
<Button |
|
|
type='danger' |
|
|
size='small' |
|
|
onClick={() => showEnableDisableModal(record, 'disable')} |
|
|
> |
|
|
{t('禁用')} |
|
|
</Button> |
|
|
) : ( |
|
|
<Button |
|
|
size='small' |
|
|
onClick={() => showEnableDisableModal(record, 'enable')} |
|
|
> |
|
|
{t('启用')} |
|
|
</Button> |
|
|
)} |
|
|
<Button |
|
|
type='tertiary' |
|
|
size='small' |
|
|
onClick={() => { |
|
|
setEditingUser(record); |
|
|
setShowEditUser(true); |
|
|
}} |
|
|
> |
|
|
{t('编辑')} |
|
|
</Button> |
|
|
<Button |
|
|
type='warning' |
|
|
size='small' |
|
|
onClick={() => showPromoteModal(record)} |
|
|
> |
|
|
{t('提升')} |
|
|
</Button> |
|
|
<Button |
|
|
type='secondary' |
|
|
size='small' |
|
|
onClick={() => showDemoteModal(record)} |
|
|
> |
|
|
{t('降级')} |
|
|
</Button> |
|
|
<Dropdown menu={moreMenu} trigger='click' position='bottomRight'> |
|
|
<Button type='tertiary' size='small' icon={<IconMore />} /> |
|
|
</Dropdown> |
|
|
</Space> |
|
|
); |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const getUsersColumns = ({ |
|
|
t, |
|
|
setEditingUser, |
|
|
setShowEditUser, |
|
|
showPromoteModal, |
|
|
showDemoteModal, |
|
|
showEnableDisableModal, |
|
|
showDeleteModal, |
|
|
showResetPasskeyModal, |
|
|
showResetTwoFAModal, |
|
|
}) => { |
|
|
return [ |
|
|
{ |
|
|
title: 'ID', |
|
|
dataIndex: 'id', |
|
|
}, |
|
|
{ |
|
|
title: t('用户名'), |
|
|
dataIndex: 'username', |
|
|
render: (text, record) => renderUsername(text, record), |
|
|
}, |
|
|
{ |
|
|
title: t('状态'), |
|
|
dataIndex: 'info', |
|
|
render: (text, record, index) => |
|
|
renderStatistics(text, record, showEnableDisableModal, t), |
|
|
}, |
|
|
{ |
|
|
title: t('剩余额度/总额度'), |
|
|
key: 'quota_usage', |
|
|
render: (text, record) => renderQuotaUsage(text, record, t), |
|
|
}, |
|
|
{ |
|
|
title: t('分组'), |
|
|
dataIndex: 'group', |
|
|
render: (text, record, index) => { |
|
|
return <div>{renderGroup(text)}</div>; |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
title: t('角色'), |
|
|
dataIndex: 'role', |
|
|
render: (text, record, index) => { |
|
|
return <div>{renderRole(text, t)}</div>; |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
title: t('邀请信息'), |
|
|
dataIndex: 'invite', |
|
|
render: (text, record, index) => renderInviteInfo(text, record, t), |
|
|
}, |
|
|
{ |
|
|
title: '', |
|
|
dataIndex: 'operate', |
|
|
fixed: 'right', |
|
|
width: 200, |
|
|
render: (text, record, index) => |
|
|
renderOperations(text, record, { |
|
|
setEditingUser, |
|
|
setShowEditUser, |
|
|
showPromoteModal, |
|
|
showDemoteModal, |
|
|
showEnableDisableModal, |
|
|
showDeleteModal, |
|
|
showResetPasskeyModal, |
|
|
showResetTwoFAModal, |
|
|
t, |
|
|
}), |
|
|
}, |
|
|
]; |
|
|
}; |
|
|
|