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